Skip to content

Index

frequenz.quantities ¤

Types for holding quantities with units.

This library provide types for holding quantities with units. The main goal is to avoid mistakes while working with different types of quantities, for example avoiding adding a length to a time.

It also prevents mistakes when operating between the same quantity but in different units, like adding a power in Joules to a power in Watts without converting one of them.

Quantities store the value in a base unit, and then provide methods to get that quantity as a particular unit. They can only be constructed using special constructors with the form Quantity.from_<unit>, for example Power.from_watts(10.0).

Internally quantities store values as floats, so regular float issues and limitations apply, although some of them are tried to be mitigated.

Quantities are also immutable, so operations between quantities return a new instance of the quantity.

This library provides the following types:

  • ApparentPower: A quantity representing apparent power.
  • Current: A quantity representing an electric current.
  • Energy: A quantity representing energy.
  • Frequency: A quantity representing frequency.
  • Percentage: A quantity representing a percentage.
  • Power: A quantity representing power.
  • ReactivePower: A quantity representing reactive power.
  • Temperature: A quantity representing temperature.
  • Voltage: A quantity representing electric voltage.

There is also the unitless Quantity class. All quantities are subclasses of this class and it can be used as a base to create new quantities. Using the Quantity class directly is discouraged, as it doesn't provide any unit conversion methods.

Example
from datetime import timedelta
from frequenz.quantities import Power, Voltage, Current, Energy

# Create a power quantity
power = Power.from_watts(230.0)

# Printing uses a unit to make the string as short as possible
print(f"Power: {power}")  # Power: 230.0 W
# The precision can be changed
print(f"Power: {power:0.3}")  # Power: 230.000 W
# The conversion methods can be used to get the value in a particular unit
print(f"Power in MW: {power.as_megawatt()}")  # Power in MW: 0.00023 MW

# Create a voltage quantity
voltage = Voltage.from_volts(230.0)

# Calculate the current
current = power / voltage
assert isinstance(current, Current)
print(f"Current: {current}")  # Current: 1.0 A
assert current.isclose(Current.from_amperes(1.0))

# Calculate the energy
energy = power * timedelta(hours=1)
assert isinstance(energy, Energy)
print(f"Energy: {energy}")  # Energy: 230.0 Wh
print(f"Energy in kWh: {energy.as_kilowatt_hours()}") # Energy in kWh: 0.23

# Invalid operations are not permitted
# (when using a type hinting linter like mypy, this will be caught at linting time)
try:
    power + voltage
except TypeError as e:
    print(f"Error: {e}")  # Error: unsupported operand type(s) for +: 'Power' and 'Voltage'

This library also provides an experimental module with marshmallow fields and a base schema to serialize and deserialize quantities using the marshmallow library. To use it, you need to make sure to install this package with the marshmallow optional dependencies (e.g. pip install frequenz-quantities[marshmallow]).

Classes¤

frequenz.quantities.ApparentPower ¤

Bases: Quantity

A apparent power quantity.

Objects of this type are wrappers around float values and are immutable.

The constructors accept a single float value, the as_*() methods return a float value, and each of the arithmetic operators supported by this type are actually implemented using floating-point arithmetic.

So all considerations about floating-point arithmetic apply to this type as well.

Source code in frequenz/quantities/_apparent_power.py
class ApparentPower(
    Quantity,
    metaclass=NoDefaultConstructible,
    exponent_unit_map={
        -3: "mVA",
        0: "VA",
        3: "kVA",
        6: "MVA",
    },
):
    """A apparent power quantity.

    Objects of this type are wrappers around `float` values and are immutable.

    The constructors accept a single `float` value, the `as_*()` methods return a
    `float` value, and each of the arithmetic operators supported by this type are
    actually implemented using floating-point arithmetic.

    So all considerations about floating-point arithmetic apply to this type as well.
    """

    @classmethod
    def from_volt_amperes(cls, value: float) -> Self:
        """Initialize a new apparent power quantity.

        Args:
            value: The apparent power in volt-amperes (VA).

        Returns:
            A new apparent power quantity.
        """
        return cls._new(value)

    @classmethod
    def from_milli_volt_amperes(cls, mva: float) -> Self:
        """Initialize a new apparent power quantity.

        Args:
            mva: The apparent power in millivolt-amperes (mVA).

        Returns:
            A new apparent power quantity.
        """
        return cls._new(mva, exponent=-3)

    @classmethod
    def from_kilo_volt_amperes(cls, kva: float) -> Self:
        """Initialize a new apparent power quantity.

        Args:
            kva: The apparent power in kilovolt-amperes (kVA).

        Returns:
            A new apparent power quantity.
        """
        return cls._new(kva, exponent=3)

    @classmethod
    def from_mega_volt_amperes(cls, mva: float) -> Self:
        """Initialize a new apparent power quantity.

        Args:
            mva: The apparent power in megavolt-amperes (MVA).

        Returns:
            A new apparent power quantity.
        """
        return cls._new(mva, exponent=6)

    def as_volt_amperes(self) -> float:
        """Return the apparent power in volt-amperes (VA).

        Returns:
            The apparent power in volt-amperes (VA).
        """
        return self._base_value

    def as_milli_volt_amperes(self) -> float:
        """Return the apparent power in millivolt-amperes (mVA).

        Returns:
            The apparent power in millivolt-amperes (mVA).
        """
        return self._base_value * 1e3

    def as_kilo_volt_amperes(self) -> float:
        """Return the apparent power in kilovolt-amperes (kVA).

        Returns:
            The apparent power in kilovolt-amperes (kVA).
        """
        return self._base_value / 1e3

    def as_mega_volt_amperes(self) -> float:
        """Return the apparent power in megavolt-amperes (MVA).

        Returns:
            The apparent power in megavolt-amperes (MVA).
        """
        return self._base_value / 1e6

    @overload
    def __mul__(self, scalar: float, /) -> Self:
        """Scale this power by a scalar.

        Args:
            scalar: The scalar by which to scale this power.

        Returns:
            The scaled power.
        """

    @overload
    def __mul__(self, percent: Percentage, /) -> Self:
        """Scale this power by a percentage.

        Args:
            percent: The percentage by which to scale this power.

        Returns:
            The scaled power.
        """

    def __mul__(self, other: float | Percentage, /) -> Self:
        """Return a power or energy from multiplying this power by the given value.

        Args:
            other: The scalar, percentage or duration to multiply by.

        Returns:
            A power or energy.
        """
        from ._percentage import Percentage  # pylint: disable=import-outside-toplevel

        match other:
            case float() | Percentage():
                return super().__mul__(other)
            case _:
                return NotImplemented

    # We need the ignore here because otherwise mypy will give this error:
    # > Overloaded operator methods can't have wider argument types in overrides
    # The problem seems to be when the other type implements an **incompatible**
    # __rmul__ method, which is not the case here, so we should be safe.
    # Please see this example:
    # https://github.com/python/mypy/blob/c26f1297d4f19d2d1124a30efc97caebb8c28616/test-data/unit/check-overloading.test#L4738C1-L4769C55
    # And a discussion in a mypy issue here:
    # https://github.com/python/mypy/issues/4985#issuecomment-389692396
    @overload  # type: ignore[override]
    def __truediv__(self, other: float, /) -> Self:
        """Divide this power by a scalar.

        Args:
            other: The scalar to divide this power by.

        Returns:
            The divided power.
        """

    @overload
    def __truediv__(self, other: Self, /) -> float:
        """Return the ratio of this power to another.

        Args:
            other: The other power.

        Returns:
            The ratio of this power to another.
        """

    @overload
    def __truediv__(self, current: Current, /) -> Voltage:
        """Return a voltage from dividing this power by the given current.

        Args:
            current: The current to divide by.

        Returns:
            A voltage from dividing this power by the a current.
        """

    @overload
    def __truediv__(self, voltage: Voltage, /) -> Current:
        """Return a current from dividing this power by the given voltage.

        Args:
            voltage: The voltage to divide by.

        Returns:
            A current from dividing this power by a voltage.
        """

    def __truediv__(
        self, other: float | Self | Current | Voltage, /
    ) -> Self | float | Voltage | Current:
        """Return a current or voltage from dividing this power by the given value.

        Args:
            other: The scalar, power, current or voltage to divide by.

        Returns:
            A current or voltage from dividing this power by the given value.
        """
        from ._current import Current  # pylint: disable=import-outside-toplevel
        from ._voltage import Voltage  # pylint: disable=import-outside-toplevel

        match other:
            case float():
                return super().__truediv__(other)
            case ApparentPower():
                return self._base_value / other._base_value
            case Current():
                return Voltage._new(self._base_value / other._base_value)
            case Voltage():
                return Current._new(self._base_value / other._base_value)
            case _:
                return NotImplemented
Attributes¤
base_unit property ¤
base_unit: str | None

Return the base unit of this quantity.

None if this quantity has no unit.

RETURNS DESCRIPTION
str | None

The base unit of this quantity.

base_value property ¤
base_value: float

Return the value of this quantity in the base unit.

RETURNS DESCRIPTION
float

The value of this quantity in the base unit.

Functions¤
__abs__ ¤
__abs__() -> Self

Return the absolute value of this quantity.

RETURNS DESCRIPTION
Self

The absolute value of this quantity.

Source code in frequenz/quantities/_quantity.py
def __abs__(self) -> Self:
    """Return the absolute value of this quantity.

    Returns:
        The absolute value of this quantity.
    """
    absolute = type(self).__new__(type(self))
    absolute._base_value = abs(self._base_value)
    return absolute
__add__ ¤
__add__(other: Self) -> Self

Return the sum of this quantity and another.

PARAMETER DESCRIPTION
other

The other quantity.

TYPE: Self

RETURNS DESCRIPTION
Self

The sum of this quantity and another.

Source code in frequenz/quantities/_quantity.py
def __add__(self, other: Self) -> Self:
    """Return the sum of this quantity and another.

    Args:
        other: The other quantity.

    Returns:
        The sum of this quantity and another.
    """
    if not type(other) is type(self):
        return NotImplemented
    summe = type(self).__new__(type(self))
    summe._base_value = self._base_value + other._base_value
    return summe
__eq__ ¤
__eq__(other: object) -> bool

Return whether this quantity is equal to another.

PARAMETER DESCRIPTION
other

The other quantity.

TYPE: object

RETURNS DESCRIPTION
bool

Whether this quantity is equal to another.

Source code in frequenz/quantities/_quantity.py
def __eq__(self, other: object) -> bool:
    """Return whether this quantity is equal to another.

    Args:
        other: The other quantity.

    Returns:
        Whether this quantity is equal to another.
    """
    if not type(other) is type(self):
        return NotImplemented
    # The above check ensures that both quantities are the exact same type, because
    # `isinstance` returns true for subclasses and superclasses.  But the above check
    # doesn't help mypy identify the type of other,  so the below line is necessary.
    assert isinstance(other, self.__class__)
    return self._base_value == other._base_value
__format__ ¤
__format__(__format_spec: str) -> str

Return a formatted string representation of this quantity.

If specified, must be of this form: [0].{precision}. If a 0 is not given, the trailing zeros will be omitted. If no precision is given, the default is 3.

The returned string will use the unit that will result in the maximum precision, based on the magnitude of the value.

Example
from frequenz.quantities import Current
c = Current.from_amperes(0.2345)
assert f"{c:.2}" == "234.5 mA"
c = Current.from_amperes(1.2345)
assert f"{c:.2}" == "1.23 A"
c = Current.from_milliamperes(1.2345)
assert f"{c:.6}" == "1.2345 mA"
PARAMETER DESCRIPTION
__format_spec

The format specifier.

TYPE: str

RETURNS DESCRIPTION
str

A string representation of this quantity.

RAISES DESCRIPTION
ValueError

If the given format specifier is invalid.

Source code in frequenz/quantities/_quantity.py
def __format__(self, __format_spec: str) -> str:
    """Return a formatted string representation of this quantity.

    If specified, must be of this form: `[0].{precision}`.  If a 0 is not given, the
    trailing zeros will be omitted.  If no precision is given, the default is 3.

    The returned string will use the unit that will result in the maximum precision,
    based on the magnitude of the value.

    Example:
        ```python
        from frequenz.quantities import Current
        c = Current.from_amperes(0.2345)
        assert f"{c:.2}" == "234.5 mA"
        c = Current.from_amperes(1.2345)
        assert f"{c:.2}" == "1.23 A"
        c = Current.from_milliamperes(1.2345)
        assert f"{c:.6}" == "1.2345 mA"
        ```

    Args:
        __format_spec: The format specifier.

    Returns:
        A string representation of this quantity.

    Raises:
        ValueError: If the given format specifier is invalid.
    """
    keep_trailing_zeros = False
    if __format_spec != "":
        fspec_parts = __format_spec.split(".")
        if (
            len(fspec_parts) != 2
            or fspec_parts[0] not in ("", "0")
            or not fspec_parts[1].isdigit()
        ):
            raise ValueError(
                "Invalid format specifier. Must be empty or `[0].{precision}`"
            )
        if fspec_parts[0] == "0":
            keep_trailing_zeros = True
        precision = int(fspec_parts[1])
    else:
        precision = 3
    if not self._exponent_unit_map:
        return f"{self._base_value:.{precision}f}"

    if math.isinf(self._base_value) or math.isnan(self._base_value):
        return f"{self._base_value} {self._exponent_unit_map[0]}"

    if abs_value := abs(self._base_value):
        precision_pow = 10 ** (precision)
        # Prevent numbers like 999.999999 being rendered as 1000 V
        # instead of 1 kV.
        # This could happen because the str formatting function does
        # rounding as well.
        # This is an imperfect solution that works for _most_ cases.
        # isclose parameters were chosen according to the observed cases
        if math.isclose(abs_value, precision_pow, abs_tol=1e-4, rel_tol=0.01):
            # If the value is close to the precision, round it
            exponent = math.ceil(math.log10(precision_pow))
        else:
            exponent = math.floor(math.log10(abs_value))
    else:
        exponent = 0

    unit_place = exponent - exponent % 3
    if unit_place < min(self._exponent_unit_map):
        unit = self._exponent_unit_map[min(self._exponent_unit_map.keys())]
        unit_place = min(self._exponent_unit_map)
    elif unit_place > max(self._exponent_unit_map):
        unit = self._exponent_unit_map[max(self._exponent_unit_map.keys())]
        unit_place = max(self._exponent_unit_map)
    else:
        unit = self._exponent_unit_map[unit_place]

    value_str = f"{self._base_value / 10 ** unit_place:.{precision}f}"

    if value_str in ("-0", "0"):
        stripped = value_str
    else:
        stripped = value_str.rstrip("0").rstrip(".")

    if not keep_trailing_zeros:
        value_str = stripped
    unit_str = unit if stripped not in ("-0", "0") else self._exponent_unit_map[0]
    return f"{value_str} {unit_str}"
__ge__ ¤
__ge__(other: Self) -> bool

Return whether this quantity is greater than or equal to another.

PARAMETER DESCRIPTION
other

The other quantity.

TYPE: Self

RETURNS DESCRIPTION
bool

Whether this quantity is greater than or equal to another.

Source code in frequenz/quantities/_quantity.py
def __ge__(self, other: Self) -> bool:
    """Return whether this quantity is greater than or equal to another.

    Args:
        other: The other quantity.

    Returns:
        Whether this quantity is greater than or equal to another.
    """
    if not type(other) is type(self):
        return NotImplemented
    return self._base_value >= other._base_value
__gt__ ¤
__gt__(other: Self) -> bool

Return whether this quantity is greater than another.

PARAMETER DESCRIPTION
other

The other quantity.

TYPE: Self

RETURNS DESCRIPTION
bool

Whether this quantity is greater than another.

Source code in frequenz/quantities/_quantity.py
def __gt__(self, other: Self) -> bool:
    """Return whether this quantity is greater than another.

    Args:
        other: The other quantity.

    Returns:
        Whether this quantity is greater than another.
    """
    if not type(other) is type(self):
        return NotImplemented
    return self._base_value > other._base_value
__init__ ¤
__init__(value: float, exponent: int = 0) -> None

Initialize a new quantity.

PARAMETER DESCRIPTION
value

The value of this quantity in a given exponent of the base unit.

TYPE: float

exponent

The exponent of the base unit the given value is in.

TYPE: int DEFAULT: 0

Source code in frequenz/quantities/_quantity.py
def __init__(self, value: float, exponent: int = 0) -> None:
    """Initialize a new quantity.

    Args:
        value: The value of this quantity in a given exponent of the base unit.
        exponent: The exponent of the base unit the given value is in.
    """
    self._base_value = value * 10.0**exponent
__init_subclass__ ¤
__init_subclass__(
    exponent_unit_map: dict[int, str]
) -> None

Initialize a new subclass of Quantity.

PARAMETER DESCRIPTION
exponent_unit_map

A mapping from the exponent of the base unit to the unit symbol.

TYPE: dict[int, str]

RAISES DESCRIPTION
ValueError

If the given exponent_unit_map does not contain a base unit (exponent 0).

Source code in frequenz/quantities/_quantity.py
def __init_subclass__(cls, exponent_unit_map: dict[int, str]) -> None:
    """Initialize a new subclass of Quantity.

    Args:
        exponent_unit_map: A mapping from the exponent of the base unit to the unit
            symbol.

    Raises:
        ValueError: If the given exponent_unit_map does not contain a base unit
            (exponent 0).
    """
    if 0 not in exponent_unit_map:
        raise ValueError("Expected a base unit for the type (for exponent 0)")
    cls._exponent_unit_map = exponent_unit_map
    super().__init_subclass__()
__le__ ¤
__le__(other: Self) -> bool

Return whether this quantity is less than or equal to another.

PARAMETER DESCRIPTION
other

The other quantity.

TYPE: Self

RETURNS DESCRIPTION
bool

Whether this quantity is less than or equal to another.

Source code in frequenz/quantities/_quantity.py
def __le__(self, other: Self) -> bool:
    """Return whether this quantity is less than or equal to another.

    Args:
        other: The other quantity.

    Returns:
        Whether this quantity is less than or equal to another.
    """
    if not type(other) is type(self):
        return NotImplemented
    return self._base_value <= other._base_value
__lt__ ¤
__lt__(other: Self) -> bool

Return whether this quantity is less than another.

PARAMETER DESCRIPTION
other

The other quantity.

TYPE: Self

RETURNS DESCRIPTION
bool

Whether this quantity is less than another.

Source code in frequenz/quantities/_quantity.py
def __lt__(self, other: Self) -> bool:
    """Return whether this quantity is less than another.

    Args:
        other: The other quantity.

    Returns:
        Whether this quantity is less than another.
    """
    if not type(other) is type(self):
        return NotImplemented
    return self._base_value < other._base_value
__mod__ ¤
__mod__(other: Self) -> Self

Return the remainder of this quantity and another.

PARAMETER DESCRIPTION
other

The other quantity.

TYPE: Self

RETURNS DESCRIPTION
Self

The remainder of this quantity and another.

Source code in frequenz/quantities/_quantity.py
def __mod__(self, other: Self) -> Self:
    """Return the remainder of this quantity and another.

    Args:
        other: The other quantity.

    Returns:
        The remainder of this quantity and another.
    """
    return self._new(self._base_value % other._base_value)
__mul__ ¤
__mul__(scalar: float) -> Self
__mul__(percent: Percentage) -> Self
__mul__(other: float | Percentage) -> Self

Return a power or energy from multiplying this power by the given value.

PARAMETER DESCRIPTION
other

The scalar, percentage or duration to multiply by.

TYPE: float | Percentage

RETURNS DESCRIPTION
Self

A power or energy.

Source code in frequenz/quantities/_apparent_power.py
def __mul__(self, other: float | Percentage, /) -> Self:
    """Return a power or energy from multiplying this power by the given value.

    Args:
        other: The scalar, percentage or duration to multiply by.

    Returns:
        A power or energy.
    """
    from ._percentage import Percentage  # pylint: disable=import-outside-toplevel

    match other:
        case float() | Percentage():
            return super().__mul__(other)
        case _:
            return NotImplemented
__neg__ ¤
__neg__() -> Self

Return the negation of this quantity.

RETURNS DESCRIPTION
Self

The negation of this quantity.

Source code in frequenz/quantities/_quantity.py
def __neg__(self) -> Self:
    """Return the negation of this quantity.

    Returns:
        The negation of this quantity.
    """
    negation = type(self).__new__(type(self))
    negation._base_value = -self._base_value
    return negation
__pos__ ¤
__pos__() -> Self

Return this quantity.

RETURNS DESCRIPTION
Self

This quantity.

Source code in frequenz/quantities/_quantity.py
def __pos__(self) -> Self:
    """Return this quantity.

    Returns:
        This quantity.
    """
    return self
__repr__ ¤
__repr__() -> str

Return a representation of this quantity.

RETURNS DESCRIPTION
str

A representation of this quantity.

Source code in frequenz/quantities/_quantity.py
def __repr__(self) -> str:
    """Return a representation of this quantity.

    Returns:
        A representation of this quantity.
    """
    return f"{type(self).__name__}(value={self._base_value}, exponent=0)"
__round__ ¤
__round__(ndigits: int | None = None) -> Self

Round this quantity to the given number of digits.

PARAMETER DESCRIPTION
ndigits

The number of digits to round to.

TYPE: int | None DEFAULT: None

RETURNS DESCRIPTION
Self

The rounded quantity.

Source code in frequenz/quantities/_quantity.py
def __round__(self, ndigits: int | None = None) -> Self:
    """Round this quantity to the given number of digits.

    Args:
        ndigits: The number of digits to round to.

    Returns:
        The rounded quantity.
    """
    return self._new(round(self._base_value, ndigits))
__str__ ¤
__str__() -> str

Return a string representation of this quantity.

RETURNS DESCRIPTION
str

A string representation of this quantity.

Source code in frequenz/quantities/_quantity.py
def __str__(self) -> str:
    """Return a string representation of this quantity.

    Returns:
        A string representation of this quantity.
    """
    return self.__format__("")
__sub__ ¤
__sub__(other: Self) -> Self

Return the difference of this quantity and another.

PARAMETER DESCRIPTION
other

The other quantity.

TYPE: Self

RETURNS DESCRIPTION
Self

The difference of this quantity and another.

Source code in frequenz/quantities/_quantity.py
def __sub__(self, other: Self) -> Self:
    """Return the difference of this quantity and another.

    Args:
        other: The other quantity.

    Returns:
        The difference of this quantity and another.
    """
    if not type(other) is type(self):
        return NotImplemented
    difference = type(self).__new__(type(self))
    difference._base_value = self._base_value - other._base_value
    return difference
__truediv__ ¤
__truediv__(other: float) -> Self
__truediv__(other: Self) -> float
__truediv__(current: Current) -> Voltage
__truediv__(voltage: Voltage) -> Current
__truediv__(
    other: float | Self | Current | Voltage,
) -> Self | float | Voltage | Current

Return a current or voltage from dividing this power by the given value.

PARAMETER DESCRIPTION
other

The scalar, power, current or voltage to divide by.

TYPE: float | Self | Current | Voltage

RETURNS DESCRIPTION
Self | float | Voltage | Current

A current or voltage from dividing this power by the given value.

Source code in frequenz/quantities/_apparent_power.py
def __truediv__(
    self, other: float | Self | Current | Voltage, /
) -> Self | float | Voltage | Current:
    """Return a current or voltage from dividing this power by the given value.

    Args:
        other: The scalar, power, current or voltage to divide by.

    Returns:
        A current or voltage from dividing this power by the given value.
    """
    from ._current import Current  # pylint: disable=import-outside-toplevel
    from ._voltage import Voltage  # pylint: disable=import-outside-toplevel

    match other:
        case float():
            return super().__truediv__(other)
        case ApparentPower():
            return self._base_value / other._base_value
        case Current():
            return Voltage._new(self._base_value / other._base_value)
        case Voltage():
            return Current._new(self._base_value / other._base_value)
        case _:
            return NotImplemented
as_kilo_volt_amperes ¤
as_kilo_volt_amperes() -> float

Return the apparent power in kilovolt-amperes (kVA).

RETURNS DESCRIPTION
float

The apparent power in kilovolt-amperes (kVA).

Source code in frequenz/quantities/_apparent_power.py
def as_kilo_volt_amperes(self) -> float:
    """Return the apparent power in kilovolt-amperes (kVA).

    Returns:
        The apparent power in kilovolt-amperes (kVA).
    """
    return self._base_value / 1e3
as_mega_volt_amperes ¤
as_mega_volt_amperes() -> float

Return the apparent power in megavolt-amperes (MVA).

RETURNS DESCRIPTION
float

The apparent power in megavolt-amperes (MVA).

Source code in frequenz/quantities/_apparent_power.py
def as_mega_volt_amperes(self) -> float:
    """Return the apparent power in megavolt-amperes (MVA).

    Returns:
        The apparent power in megavolt-amperes (MVA).
    """
    return self._base_value / 1e6
as_milli_volt_amperes ¤
as_milli_volt_amperes() -> float

Return the apparent power in millivolt-amperes (mVA).

RETURNS DESCRIPTION
float

The apparent power in millivolt-amperes (mVA).

Source code in frequenz/quantities/_apparent_power.py
def as_milli_volt_amperes(self) -> float:
    """Return the apparent power in millivolt-amperes (mVA).

    Returns:
        The apparent power in millivolt-amperes (mVA).
    """
    return self._base_value * 1e3
as_volt_amperes ¤
as_volt_amperes() -> float

Return the apparent power in volt-amperes (VA).

RETURNS DESCRIPTION
float

The apparent power in volt-amperes (VA).

Source code in frequenz/quantities/_apparent_power.py
def as_volt_amperes(self) -> float:
    """Return the apparent power in volt-amperes (VA).

    Returns:
        The apparent power in volt-amperes (VA).
    """
    return self._base_value
from_kilo_volt_amperes classmethod ¤
from_kilo_volt_amperes(kva: float) -> Self

Initialize a new apparent power quantity.

PARAMETER DESCRIPTION
kva

The apparent power in kilovolt-amperes (kVA).

TYPE: float

RETURNS DESCRIPTION
Self

A new apparent power quantity.

Source code in frequenz/quantities/_apparent_power.py
@classmethod
def from_kilo_volt_amperes(cls, kva: float) -> Self:
    """Initialize a new apparent power quantity.

    Args:
        kva: The apparent power in kilovolt-amperes (kVA).

    Returns:
        A new apparent power quantity.
    """
    return cls._new(kva, exponent=3)
from_mega_volt_amperes classmethod ¤
from_mega_volt_amperes(mva: float) -> Self

Initialize a new apparent power quantity.

PARAMETER DESCRIPTION
mva

The apparent power in megavolt-amperes (MVA).

TYPE: float

RETURNS DESCRIPTION
Self

A new apparent power quantity.

Source code in frequenz/quantities/_apparent_power.py
@classmethod
def from_mega_volt_amperes(cls, mva: float) -> Self:
    """Initialize a new apparent power quantity.

    Args:
        mva: The apparent power in megavolt-amperes (MVA).

    Returns:
        A new apparent power quantity.
    """
    return cls._new(mva, exponent=6)
from_milli_volt_amperes classmethod ¤
from_milli_volt_amperes(mva: float) -> Self

Initialize a new apparent power quantity.

PARAMETER DESCRIPTION
mva

The apparent power in millivolt-amperes (mVA).

TYPE: float

RETURNS DESCRIPTION
Self

A new apparent power quantity.

Source code in frequenz/quantities/_apparent_power.py
@classmethod
def from_milli_volt_amperes(cls, mva: float) -> Self:
    """Initialize a new apparent power quantity.

    Args:
        mva: The apparent power in millivolt-amperes (mVA).

    Returns:
        A new apparent power quantity.
    """
    return cls._new(mva, exponent=-3)
from_string classmethod ¤
from_string(string: str) -> Self

Return a quantity from a string representation.

PARAMETER DESCRIPTION
string

The string representation of the quantity.

TYPE: str

RETURNS DESCRIPTION
Self

A quantity object with the value given in the string.

RAISES DESCRIPTION
ValueError

If the string does not match the expected format.

Source code in frequenz/quantities/_quantity.py
@classmethod
def from_string(cls, string: str) -> Self:
    """Return a quantity from a string representation.

    Args:
        string: The string representation of the quantity.

    Returns:
        A quantity object with the value given in the string.

    Raises:
        ValueError: If the string does not match the expected format.

    """
    split_string = string.split(" ")

    if len(split_string) != 2:
        raise ValueError(
            f"Expected a string of the form 'value unit', got {string}"
        )

    assert cls._exponent_unit_map is not None
    exp_map = cls._exponent_unit_map

    for exponent, unit in exp_map.items():
        if unit == split_string[1]:
            instance = cls.__new__(cls)
            try:
                instance._base_value = float(split_string[0]) * 10**exponent
            except ValueError as error:
                raise ValueError(f"Failed to parse string '{string}'.") from error

            return instance

    raise ValueError(f"Unknown unit {split_string[1]}")
from_volt_amperes classmethod ¤
from_volt_amperes(value: float) -> Self

Initialize a new apparent power quantity.

PARAMETER DESCRIPTION
value

The apparent power in volt-amperes (VA).

TYPE: float

RETURNS DESCRIPTION
Self

A new apparent power quantity.

Source code in frequenz/quantities/_apparent_power.py
@classmethod
def from_volt_amperes(cls, value: float) -> Self:
    """Initialize a new apparent power quantity.

    Args:
        value: The apparent power in volt-amperes (VA).

    Returns:
        A new apparent power quantity.
    """
    return cls._new(value)
isclose ¤
isclose(
    other: Self,
    rel_tol: float = 1e-09,
    abs_tol: float = 0.0,
) -> bool

Return whether this quantity is close to another.

PARAMETER DESCRIPTION
other

The quantity to compare to.

TYPE: Self

rel_tol

The relative tolerance.

TYPE: float DEFAULT: 1e-09

abs_tol

The absolute tolerance.

TYPE: float DEFAULT: 0.0

RETURNS DESCRIPTION
bool

Whether this quantity is close to another.

Source code in frequenz/quantities/_quantity.py
def isclose(self, other: Self, rel_tol: float = 1e-9, abs_tol: float = 0.0) -> bool:
    """Return whether this quantity is close to another.

    Args:
        other: The quantity to compare to.
        rel_tol: The relative tolerance.
        abs_tol: The absolute tolerance.

    Returns:
        Whether this quantity is close to another.
    """
    return math.isclose(
        self._base_value,
        other._base_value,  # pylint: disable=protected-access
        rel_tol=rel_tol,
        abs_tol=abs_tol,
    )
isinf ¤
isinf() -> bool

Return whether this quantity is infinite.

RETURNS DESCRIPTION
bool

Whether this quantity is infinite.

Source code in frequenz/quantities/_quantity.py
def isinf(self) -> bool:
    """Return whether this quantity is infinite.

    Returns:
        Whether this quantity is infinite.
    """
    return math.isinf(self._base_value)
isnan ¤
isnan() -> bool

Return whether this quantity is NaN.

RETURNS DESCRIPTION
bool

Whether this quantity is NaN.

Source code in frequenz/quantities/_quantity.py
def isnan(self) -> bool:
    """Return whether this quantity is NaN.

    Returns:
        Whether this quantity is NaN.
    """
    return math.isnan(self._base_value)
zero classmethod ¤
zero() -> Self

Return a quantity with value 0.0.

RETURNS DESCRIPTION
Self

A quantity with value 0.0.

Source code in frequenz/quantities/_quantity.py
@classmethod
def zero(cls) -> Self:
    """Return a quantity with value 0.0.

    Returns:
        A quantity with value 0.0.
    """
    _zero = cls._zero_cache.get(cls, None)
    if _zero is None:
        _zero = cls.__new__(cls)
        _zero._base_value = 0.0
        cls._zero_cache[cls] = _zero
    assert isinstance(_zero, cls)
    return _zero

frequenz.quantities.Current ¤

Bases: Quantity

A current quantity.

Objects of this type are wrappers around float values and are immutable.

The constructors accept a single float value, the as_*() methods return a float value, and each of the arithmetic operators supported by this type are actually implemented using floating-point arithmetic.

So all considerations about floating-point arithmetic apply to this type as well.

Source code in frequenz/quantities/_current.py
class Current(
    Quantity,
    metaclass=NoDefaultConstructible,
    exponent_unit_map={
        -3: "mA",
        0: "A",
    },
):
    """A current quantity.

    Objects of this type are wrappers around `float` values and are immutable.

    The constructors accept a single `float` value, the `as_*()` methods return a
    `float` value, and each of the arithmetic operators supported by this type are
    actually implemented using floating-point arithmetic.

    So all considerations about floating-point arithmetic apply to this type as well.
    """

    @classmethod
    def from_amperes(cls, amperes: float) -> Self:
        """Initialize a new current quantity.

        Args:
            amperes: The current in amperes.

        Returns:
            A new current quantity.
        """
        return cls._new(amperes)

    @classmethod
    def from_milliamperes(cls, milliamperes: float) -> Self:
        """Initialize a new current quantity.

        Args:
            milliamperes: The current in milliamperes.

        Returns:
            A new current quantity.
        """
        return cls._new(milliamperes, exponent=-3)

    def as_amperes(self) -> float:
        """Return the current in amperes.

        Returns:
            The current in amperes.
        """
        return self._base_value

    def as_milliamperes(self) -> float:
        """Return the current in milliamperes.

        Returns:
            The current in milliamperes.
        """
        return self._base_value * 1e3

    # See comment for Power.__mul__ for why we need the ignore here.
    @overload  # type: ignore[override]
    def __mul__(self, scalar: float, /) -> Self:
        """Scale this current by a scalar.

        Args:
            scalar: The scalar by which to scale this current.

        Returns:
            The scaled current.
        """

    @overload
    def __mul__(self, percent: Percentage, /) -> Self:
        """Scale this current by a percentage.

        Args:
            percent: The percentage by which to scale this current.

        Returns:
            The scaled current.
        """

    @overload
    def __mul__(self, other: Voltage, /) -> Power:
        """Multiply the current by a voltage to get a power.

        Args:
            other: The voltage.

        Returns:
            The calculated power.
        """

    def __mul__(self, other: float | Percentage | Voltage, /) -> Self | Power:
        """Return a current or power from multiplying this current by the given value.

        Args:
            other: The scalar, percentage or voltage to multiply by.

        Returns:
            A current or power.
        """
        from ._percentage import Percentage  # pylint: disable=import-outside-toplevel
        from ._power import Power  # pylint: disable=import-outside-toplevel
        from ._voltage import Voltage  # pylint: disable=import-outside-toplevel

        match other:
            case float() | Percentage():
                return super().__mul__(other)
            case Voltage():
                return Power._new(self._base_value * other._base_value)
            case _:
                return NotImplemented
Attributes¤
base_unit property ¤
base_unit: str | None

Return the base unit of this quantity.

None if this quantity has no unit.

RETURNS DESCRIPTION
str | None

The base unit of this quantity.

base_value property ¤
base_value: float

Return the value of this quantity in the base unit.

RETURNS DESCRIPTION
float

The value of this quantity in the base unit.

Functions¤
__abs__ ¤
__abs__() -> Self

Return the absolute value of this quantity.

RETURNS DESCRIPTION
Self

The absolute value of this quantity.

Source code in frequenz/quantities/_quantity.py
def __abs__(self) -> Self:
    """Return the absolute value of this quantity.

    Returns:
        The absolute value of this quantity.
    """
    absolute = type(self).__new__(type(self))
    absolute._base_value = abs(self._base_value)
    return absolute
__add__ ¤
__add__(other: Self) -> Self

Return the sum of this quantity and another.

PARAMETER DESCRIPTION
other

The other quantity.

TYPE: Self

RETURNS DESCRIPTION
Self

The sum of this quantity and another.

Source code in frequenz/quantities/_quantity.py
def __add__(self, other: Self) -> Self:
    """Return the sum of this quantity and another.

    Args:
        other: The other quantity.

    Returns:
        The sum of this quantity and another.
    """
    if not type(other) is type(self):
        return NotImplemented
    summe = type(self).__new__(type(self))
    summe._base_value = self._base_value + other._base_value
    return summe
__eq__ ¤
__eq__(other: object) -> bool

Return whether this quantity is equal to another.

PARAMETER DESCRIPTION
other

The other quantity.

TYPE: object

RETURNS DESCRIPTION
bool

Whether this quantity is equal to another.

Source code in frequenz/quantities/_quantity.py
def __eq__(self, other: object) -> bool:
    """Return whether this quantity is equal to another.

    Args:
        other: The other quantity.

    Returns:
        Whether this quantity is equal to another.
    """
    if not type(other) is type(self):
        return NotImplemented
    # The above check ensures that both quantities are the exact same type, because
    # `isinstance` returns true for subclasses and superclasses.  But the above check
    # doesn't help mypy identify the type of other,  so the below line is necessary.
    assert isinstance(other, self.__class__)
    return self._base_value == other._base_value
__format__ ¤
__format__(__format_spec: str) -> str

Return a formatted string representation of this quantity.

If specified, must be of this form: [0].{precision}. If a 0 is not given, the trailing zeros will be omitted. If no precision is given, the default is 3.

The returned string will use the unit that will result in the maximum precision, based on the magnitude of the value.

Example
from frequenz.quantities import Current
c = Current.from_amperes(0.2345)
assert f"{c:.2}" == "234.5 mA"
c = Current.from_amperes(1.2345)
assert f"{c:.2}" == "1.23 A"
c = Current.from_milliamperes(1.2345)
assert f"{c:.6}" == "1.2345 mA"
PARAMETER DESCRIPTION
__format_spec

The format specifier.

TYPE: str

RETURNS DESCRIPTION
str

A string representation of this quantity.

RAISES DESCRIPTION
ValueError

If the given format specifier is invalid.

Source code in frequenz/quantities/_quantity.py
def __format__(self, __format_spec: str) -> str:
    """Return a formatted string representation of this quantity.

    If specified, must be of this form: `[0].{precision}`.  If a 0 is not given, the
    trailing zeros will be omitted.  If no precision is given, the default is 3.

    The returned string will use the unit that will result in the maximum precision,
    based on the magnitude of the value.

    Example:
        ```python
        from frequenz.quantities import Current
        c = Current.from_amperes(0.2345)
        assert f"{c:.2}" == "234.5 mA"
        c = Current.from_amperes(1.2345)
        assert f"{c:.2}" == "1.23 A"
        c = Current.from_milliamperes(1.2345)
        assert f"{c:.6}" == "1.2345 mA"
        ```

    Args:
        __format_spec: The format specifier.

    Returns:
        A string representation of this quantity.

    Raises:
        ValueError: If the given format specifier is invalid.
    """
    keep_trailing_zeros = False
    if __format_spec != "":
        fspec_parts = __format_spec.split(".")
        if (
            len(fspec_parts) != 2
            or fspec_parts[0] not in ("", "0")
            or not fspec_parts[1].isdigit()
        ):
            raise ValueError(
                "Invalid format specifier. Must be empty or `[0].{precision}`"
            )
        if fspec_parts[0] == "0":
            keep_trailing_zeros = True
        precision = int(fspec_parts[1])
    else:
        precision = 3
    if not self._exponent_unit_map:
        return f"{self._base_value:.{precision}f}"

    if math.isinf(self._base_value) or math.isnan(self._base_value):
        return f"{self._base_value} {self._exponent_unit_map[0]}"

    if abs_value := abs(self._base_value):
        precision_pow = 10 ** (precision)
        # Prevent numbers like 999.999999 being rendered as 1000 V
        # instead of 1 kV.
        # This could happen because the str formatting function does
        # rounding as well.
        # This is an imperfect solution that works for _most_ cases.
        # isclose parameters were chosen according to the observed cases
        if math.isclose(abs_value, precision_pow, abs_tol=1e-4, rel_tol=0.01):
            # If the value is close to the precision, round it
            exponent = math.ceil(math.log10(precision_pow))
        else:
            exponent = math.floor(math.log10(abs_value))
    else:
        exponent = 0

    unit_place = exponent - exponent % 3
    if unit_place < min(self._exponent_unit_map):
        unit = self._exponent_unit_map[min(self._exponent_unit_map.keys())]
        unit_place = min(self._exponent_unit_map)
    elif unit_place > max(self._exponent_unit_map):
        unit = self._exponent_unit_map[max(self._exponent_unit_map.keys())]
        unit_place = max(self._exponent_unit_map)
    else:
        unit = self._exponent_unit_map[unit_place]

    value_str = f"{self._base_value / 10 ** unit_place:.{precision}f}"

    if value_str in ("-0", "0"):
        stripped = value_str
    else:
        stripped = value_str.rstrip("0").rstrip(".")

    if not keep_trailing_zeros:
        value_str = stripped
    unit_str = unit if stripped not in ("-0", "0") else self._exponent_unit_map[0]
    return f"{value_str} {unit_str}"
__ge__ ¤
__ge__(other: Self) -> bool

Return whether this quantity is greater than or equal to another.

PARAMETER DESCRIPTION
other

The other quantity.

TYPE: Self

RETURNS DESCRIPTION
bool

Whether this quantity is greater than or equal to another.

Source code in frequenz/quantities/_quantity.py
def __ge__(self, other: Self) -> bool:
    """Return whether this quantity is greater than or equal to another.

    Args:
        other: The other quantity.

    Returns:
        Whether this quantity is greater than or equal to another.
    """
    if not type(other) is type(self):
        return NotImplemented
    return self._base_value >= other._base_value
__gt__ ¤
__gt__(other: Self) -> bool

Return whether this quantity is greater than another.

PARAMETER DESCRIPTION
other

The other quantity.

TYPE: Self

RETURNS DESCRIPTION
bool

Whether this quantity is greater than another.

Source code in frequenz/quantities/_quantity.py
def __gt__(self, other: Self) -> bool:
    """Return whether this quantity is greater than another.

    Args:
        other: The other quantity.

    Returns:
        Whether this quantity is greater than another.
    """
    if not type(other) is type(self):
        return NotImplemented
    return self._base_value > other._base_value
__init__ ¤
__init__(value: float, exponent: int = 0) -> None

Initialize a new quantity.

PARAMETER DESCRIPTION
value

The value of this quantity in a given exponent of the base unit.

TYPE: float

exponent

The exponent of the base unit the given value is in.

TYPE: int DEFAULT: 0

Source code in frequenz/quantities/_quantity.py
def __init__(self, value: float, exponent: int = 0) -> None:
    """Initialize a new quantity.

    Args:
        value: The value of this quantity in a given exponent of the base unit.
        exponent: The exponent of the base unit the given value is in.
    """
    self._base_value = value * 10.0**exponent
__init_subclass__ ¤
__init_subclass__(
    exponent_unit_map: dict[int, str]
) -> None

Initialize a new subclass of Quantity.

PARAMETER DESCRIPTION
exponent_unit_map

A mapping from the exponent of the base unit to the unit symbol.

TYPE: dict[int, str]

RAISES DESCRIPTION
ValueError

If the given exponent_unit_map does not contain a base unit (exponent 0).

Source code in frequenz/quantities/_quantity.py
def __init_subclass__(cls, exponent_unit_map: dict[int, str]) -> None:
    """Initialize a new subclass of Quantity.

    Args:
        exponent_unit_map: A mapping from the exponent of the base unit to the unit
            symbol.

    Raises:
        ValueError: If the given exponent_unit_map does not contain a base unit
            (exponent 0).
    """
    if 0 not in exponent_unit_map:
        raise ValueError("Expected a base unit for the type (for exponent 0)")
    cls._exponent_unit_map = exponent_unit_map
    super().__init_subclass__()
__le__ ¤
__le__(other: Self) -> bool

Return whether this quantity is less than or equal to another.

PARAMETER DESCRIPTION
other

The other quantity.

TYPE: Self

RETURNS DESCRIPTION
bool

Whether this quantity is less than or equal to another.

Source code in frequenz/quantities/_quantity.py
def __le__(self, other: Self) -> bool:
    """Return whether this quantity is less than or equal to another.

    Args:
        other: The other quantity.

    Returns:
        Whether this quantity is less than or equal to another.
    """
    if not type(other) is type(self):
        return NotImplemented
    return self._base_value <= other._base_value
__lt__ ¤
__lt__(other: Self) -> bool

Return whether this quantity is less than another.

PARAMETER DESCRIPTION
other

The other quantity.

TYPE: Self

RETURNS DESCRIPTION
bool

Whether this quantity is less than another.

Source code in frequenz/quantities/_quantity.py
def __lt__(self, other: Self) -> bool:
    """Return whether this quantity is less than another.

    Args:
        other: The other quantity.

    Returns:
        Whether this quantity is less than another.
    """
    if not type(other) is type(self):
        return NotImplemented
    return self._base_value < other._base_value
__mod__ ¤
__mod__(other: Self) -> Self

Return the remainder of this quantity and another.

PARAMETER DESCRIPTION
other

The other quantity.

TYPE: Self

RETURNS DESCRIPTION
Self

The remainder of this quantity and another.

Source code in frequenz/quantities/_quantity.py
def __mod__(self, other: Self) -> Self:
    """Return the remainder of this quantity and another.

    Args:
        other: The other quantity.

    Returns:
        The remainder of this quantity and another.
    """
    return self._new(self._base_value % other._base_value)
__mul__ ¤
__mul__(scalar: float) -> Self
__mul__(percent: Percentage) -> Self
__mul__(other: Voltage) -> Power
__mul__(
    other: float | Percentage | Voltage,
) -> Self | Power

Return a current or power from multiplying this current by the given value.

PARAMETER DESCRIPTION
other

The scalar, percentage or voltage to multiply by.

TYPE: float | Percentage | Voltage

RETURNS DESCRIPTION
Self | Power

A current or power.

Source code in frequenz/quantities/_current.py
def __mul__(self, other: float | Percentage | Voltage, /) -> Self | Power:
    """Return a current or power from multiplying this current by the given value.

    Args:
        other: The scalar, percentage or voltage to multiply by.

    Returns:
        A current or power.
    """
    from ._percentage import Percentage  # pylint: disable=import-outside-toplevel
    from ._power import Power  # pylint: disable=import-outside-toplevel
    from ._voltage import Voltage  # pylint: disable=import-outside-toplevel

    match other:
        case float() | Percentage():
            return super().__mul__(other)
        case Voltage():
            return Power._new(self._base_value * other._base_value)
        case _:
            return NotImplemented
__neg__ ¤
__neg__() -> Self

Return the negation of this quantity.

RETURNS DESCRIPTION
Self

The negation of this quantity.

Source code in frequenz/quantities/_quantity.py
def __neg__(self) -> Self:
    """Return the negation of this quantity.

    Returns:
        The negation of this quantity.
    """
    negation = type(self).__new__(type(self))
    negation._base_value = -self._base_value
    return negation
__pos__ ¤
__pos__() -> Self

Return this quantity.

RETURNS DESCRIPTION
Self

This quantity.

Source code in frequenz/quantities/_quantity.py
def __pos__(self) -> Self:
    """Return this quantity.

    Returns:
        This quantity.
    """
    return self
__repr__ ¤
__repr__() -> str

Return a representation of this quantity.

RETURNS DESCRIPTION
str

A representation of this quantity.

Source code in frequenz/quantities/_quantity.py
def __repr__(self) -> str:
    """Return a representation of this quantity.

    Returns:
        A representation of this quantity.
    """
    return f"{type(self).__name__}(value={self._base_value}, exponent=0)"
__round__ ¤
__round__(ndigits: int | None = None) -> Self

Round this quantity to the given number of digits.

PARAMETER DESCRIPTION
ndigits

The number of digits to round to.

TYPE: int | None DEFAULT: None

RETURNS DESCRIPTION
Self

The rounded quantity.

Source code in frequenz/quantities/_quantity.py
def __round__(self, ndigits: int | None = None) -> Self:
    """Round this quantity to the given number of digits.

    Args:
        ndigits: The number of digits to round to.

    Returns:
        The rounded quantity.
    """
    return self._new(round(self._base_value, ndigits))
__str__ ¤
__str__() -> str

Return a string representation of this quantity.

RETURNS DESCRIPTION
str

A string representation of this quantity.

Source code in frequenz/quantities/_quantity.py
def __str__(self) -> str:
    """Return a string representation of this quantity.

    Returns:
        A string representation of this quantity.
    """
    return self.__format__("")
__sub__ ¤
__sub__(other: Self) -> Self

Return the difference of this quantity and another.

PARAMETER DESCRIPTION
other

The other quantity.

TYPE: Self

RETURNS DESCRIPTION
Self

The difference of this quantity and another.

Source code in frequenz/quantities/_quantity.py
def __sub__(self, other: Self) -> Self:
    """Return the difference of this quantity and another.

    Args:
        other: The other quantity.

    Returns:
        The difference of this quantity and another.
    """
    if not type(other) is type(self):
        return NotImplemented
    difference = type(self).__new__(type(self))
    difference._base_value = self._base_value - other._base_value
    return difference
__truediv__ ¤
__truediv__(other: float) -> Self
__truediv__(other: Self) -> float
__truediv__(value: float | Self) -> Self | float

Divide this quantity by a scalar or another quantity.

PARAMETER DESCRIPTION
value

The scalar or quantity to divide this quantity by.

TYPE: float | Self

RETURNS DESCRIPTION
Self | float

The divided quantity or the ratio of this quantity to another.

Source code in frequenz/quantities/_quantity.py
def __truediv__(self, value: float | Self, /) -> Self | float:
    """Divide this quantity by a scalar or another quantity.

    Args:
        value: The scalar or quantity to divide this quantity by.

    Returns:
        The divided quantity or the ratio of this quantity to another.
    """
    match value:
        case float():
            return type(self)._new(self._base_value / value)
        case Quantity() if type(value) is type(self):
            return self._base_value / value._base_value
        case _:
            return NotImplemented
as_amperes ¤
as_amperes() -> float

Return the current in amperes.

RETURNS DESCRIPTION
float

The current in amperes.

Source code in frequenz/quantities/_current.py
def as_amperes(self) -> float:
    """Return the current in amperes.

    Returns:
        The current in amperes.
    """
    return self._base_value
as_milliamperes ¤
as_milliamperes() -> float

Return the current in milliamperes.

RETURNS DESCRIPTION
float

The current in milliamperes.

Source code in frequenz/quantities/_current.py
def as_milliamperes(self) -> float:
    """Return the current in milliamperes.

    Returns:
        The current in milliamperes.
    """
    return self._base_value * 1e3
from_amperes classmethod ¤
from_amperes(amperes: float) -> Self

Initialize a new current quantity.

PARAMETER DESCRIPTION
amperes

The current in amperes.

TYPE: float

RETURNS DESCRIPTION
Self

A new current quantity.

Source code in frequenz/quantities/_current.py
@classmethod
def from_amperes(cls, amperes: float) -> Self:
    """Initialize a new current quantity.

    Args:
        amperes: The current in amperes.

    Returns:
        A new current quantity.
    """
    return cls._new(amperes)
from_milliamperes classmethod ¤
from_milliamperes(milliamperes: float) -> Self

Initialize a new current quantity.

PARAMETER DESCRIPTION
milliamperes

The current in milliamperes.

TYPE: float

RETURNS DESCRIPTION
Self

A new current quantity.

Source code in frequenz/quantities/_current.py
@classmethod
def from_milliamperes(cls, milliamperes: float) -> Self:
    """Initialize a new current quantity.

    Args:
        milliamperes: The current in milliamperes.

    Returns:
        A new current quantity.
    """
    return cls._new(milliamperes, exponent=-3)
from_string classmethod ¤
from_string(string: str) -> Self

Return a quantity from a string representation.

PARAMETER DESCRIPTION
string

The string representation of the quantity.

TYPE: str

RETURNS DESCRIPTION
Self

A quantity object with the value given in the string.

RAISES DESCRIPTION
ValueError

If the string does not match the expected format.

Source code in frequenz/quantities/_quantity.py
@classmethod
def from_string(cls, string: str) -> Self:
    """Return a quantity from a string representation.

    Args:
        string: The string representation of the quantity.

    Returns:
        A quantity object with the value given in the string.

    Raises:
        ValueError: If the string does not match the expected format.

    """
    split_string = string.split(" ")

    if len(split_string) != 2:
        raise ValueError(
            f"Expected a string of the form 'value unit', got {string}"
        )

    assert cls._exponent_unit_map is not None
    exp_map = cls._exponent_unit_map

    for exponent, unit in exp_map.items():
        if unit == split_string[1]:
            instance = cls.__new__(cls)
            try:
                instance._base_value = float(split_string[0]) * 10**exponent
            except ValueError as error:
                raise ValueError(f"Failed to parse string '{string}'.") from error

            return instance

    raise ValueError(f"Unknown unit {split_string[1]}")
isclose ¤
isclose(
    other: Self,
    rel_tol: float = 1e-09,
    abs_tol: float = 0.0,
) -> bool

Return whether this quantity is close to another.

PARAMETER DESCRIPTION
other

The quantity to compare to.

TYPE: Self

rel_tol

The relative tolerance.

TYPE: float DEFAULT: 1e-09

abs_tol

The absolute tolerance.

TYPE: float DEFAULT: 0.0

RETURNS DESCRIPTION
bool

Whether this quantity is close to another.

Source code in frequenz/quantities/_quantity.py
def isclose(self, other: Self, rel_tol: float = 1e-9, abs_tol: float = 0.0) -> bool:
    """Return whether this quantity is close to another.

    Args:
        other: The quantity to compare to.
        rel_tol: The relative tolerance.
        abs_tol: The absolute tolerance.

    Returns:
        Whether this quantity is close to another.
    """
    return math.isclose(
        self._base_value,
        other._base_value,  # pylint: disable=protected-access
        rel_tol=rel_tol,
        abs_tol=abs_tol,
    )
isinf ¤
isinf() -> bool

Return whether this quantity is infinite.

RETURNS DESCRIPTION
bool

Whether this quantity is infinite.

Source code in frequenz/quantities/_quantity.py
def isinf(self) -> bool:
    """Return whether this quantity is infinite.

    Returns:
        Whether this quantity is infinite.
    """
    return math.isinf(self._base_value)
isnan ¤
isnan() -> bool

Return whether this quantity is NaN.

RETURNS DESCRIPTION
bool

Whether this quantity is NaN.

Source code in frequenz/quantities/_quantity.py
def isnan(self) -> bool:
    """Return whether this quantity is NaN.

    Returns:
        Whether this quantity is NaN.
    """
    return math.isnan(self._base_value)
zero classmethod ¤
zero() -> Self

Return a quantity with value 0.0.

RETURNS DESCRIPTION
Self

A quantity with value 0.0.

Source code in frequenz/quantities/_quantity.py
@classmethod
def zero(cls) -> Self:
    """Return a quantity with value 0.0.

    Returns:
        A quantity with value 0.0.
    """
    _zero = cls._zero_cache.get(cls, None)
    if _zero is None:
        _zero = cls.__new__(cls)
        _zero._base_value = 0.0
        cls._zero_cache[cls] = _zero
    assert isinstance(_zero, cls)
    return _zero

frequenz.quantities.Energy ¤

Bases: Quantity

An energy quantity.

Objects of this type are wrappers around float values and are immutable.

The constructors accept a single float value, the as_*() methods return a float value, and each of the arithmetic operators supported by this type are actually implemented using floating-point arithmetic.

So all considerations about floating-point arithmetic apply to this type as well.

Source code in frequenz/quantities/_energy.py
class Energy(
    Quantity,
    metaclass=NoDefaultConstructible,
    exponent_unit_map={
        0: "Wh",
        3: "kWh",
        6: "MWh",
    },
):
    """An energy quantity.

    Objects of this type are wrappers around `float` values and are immutable.

    The constructors accept a single `float` value, the `as_*()` methods return a
    `float` value, and each of the arithmetic operators supported by this type are
    actually implemented using floating-point arithmetic.

    So all considerations about floating-point arithmetic apply to this type as well.
    """

    @classmethod
    def from_watt_hours(cls, watt_hours: float) -> Self:
        """Initialize a new energy quantity.

        Args:
            watt_hours: The energy in watt hours.

        Returns:
            A new energy quantity.
        """
        return cls._new(watt_hours)

    @classmethod
    def from_kilowatt_hours(cls, kilowatt_hours: float) -> Self:
        """Initialize a new energy quantity.

        Args:
            kilowatt_hours: The energy in kilowatt hours.

        Returns:
            A new energy quantity.
        """
        return cls._new(kilowatt_hours, exponent=3)

    @classmethod
    def from_megawatt_hours(cls, megawatt_hours: float) -> Self:
        """Initialize a new energy quantity.

        Args:
            megawatt_hours: The energy in megawatt hours.

        Returns:
            A new energy quantity.
        """
        return cls._new(megawatt_hours, exponent=6)

    def as_watt_hours(self) -> float:
        """Return the energy in watt hours.

        Returns:
            The energy in watt hours.
        """
        return self._base_value

    def as_kilowatt_hours(self) -> float:
        """Return the energy in kilowatt hours.

        Returns:
            The energy in kilowatt hours.
        """
        return self._base_value / 1e3

    def as_megawatt_hours(self) -> float:
        """Return the energy in megawatt hours.

        Returns:
            The energy in megawatt hours.
        """
        return self._base_value / 1e6

    def __mul__(self, other: float | Percentage) -> Self:
        """Scale this energy by a percentage.

        Args:
            other: The percentage by which to scale this energy.

        Returns:
            The scaled energy.
        """
        from ._percentage import Percentage  # pylint: disable=import-outside-toplevel

        match other:
            case float():
                return self._new(self._base_value * other)
            case Percentage():
                return self._new(self._base_value * other.as_fraction())
            case _:
                return NotImplemented

    # See the comment for Power.__mul__ for why we need the ignore here.
    @overload  # type: ignore[override]
    def __truediv__(self, other: float, /) -> Self:
        """Divide this energy by a scalar.

        Args:
            other: The scalar to divide this energy by.

        Returns:
            The divided energy.
        """

    @overload
    def __truediv__(self, other: Self, /) -> float:
        """Return the ratio of this energy to another.

        Args:
            other: The other energy.

        Returns:
            The ratio of this energy to another.
        """

    @overload
    def __truediv__(self, duration: timedelta, /) -> Power:
        """Return a power from dividing this energy by the given duration.

        Args:
            duration: The duration to divide by.

        Returns:
            A power from dividing this energy by the given duration.
        """

    @overload
    def __truediv__(self, power: Power, /) -> timedelta:
        """Return a duration from dividing this energy by the given power.

        Args:
            power: The power to divide by.

        Returns:
            A duration from dividing this energy by the given power.
        """

    def __truediv__(
        self, other: float | Self | timedelta | Power, /
    ) -> Self | float | Power | timedelta:
        """Return a power or duration from dividing this energy by the given value.

        Args:
            other: The scalar, energy, power or duration to divide by.

        Returns:
            A power or duration from dividing this energy by the given value.
        """
        from ._power import Power  # pylint: disable=import-outside-toplevel

        match other:
            case float():
                return super().__truediv__(other)
            case Energy():
                return self._base_value / other._base_value
            case timedelta():
                return Power._new(self._base_value / (other.total_seconds() / 3600.0))
            case Power():
                return timedelta(
                    seconds=(self._base_value / other._base_value) * 3600.0
                )
            case _:
                return NotImplemented
Attributes¤
base_unit property ¤
base_unit: str | None

Return the base unit of this quantity.

None if this quantity has no unit.

RETURNS DESCRIPTION
str | None

The base unit of this quantity.

base_value property ¤
base_value: float

Return the value of this quantity in the base unit.

RETURNS DESCRIPTION
float

The value of this quantity in the base unit.

Functions¤
__abs__ ¤
__abs__() -> Self

Return the absolute value of this quantity.

RETURNS DESCRIPTION
Self

The absolute value of this quantity.

Source code in frequenz/quantities/_quantity.py
def __abs__(self) -> Self:
    """Return the absolute value of this quantity.

    Returns:
        The absolute value of this quantity.
    """
    absolute = type(self).__new__(type(self))
    absolute._base_value = abs(self._base_value)
    return absolute
__add__ ¤
__add__(other: Self) -> Self

Return the sum of this quantity and another.

PARAMETER DESCRIPTION
other

The other quantity.

TYPE: Self

RETURNS DESCRIPTION
Self

The sum of this quantity and another.

Source code in frequenz/quantities/_quantity.py
def __add__(self, other: Self) -> Self:
    """Return the sum of this quantity and another.

    Args:
        other: The other quantity.

    Returns:
        The sum of this quantity and another.
    """
    if not type(other) is type(self):
        return NotImplemented
    summe = type(self).__new__(type(self))
    summe._base_value = self._base_value + other._base_value
    return summe
__eq__ ¤
__eq__(other: object) -> bool

Return whether this quantity is equal to another.

PARAMETER DESCRIPTION
other

The other quantity.

TYPE: object

RETURNS DESCRIPTION
bool

Whether this quantity is equal to another.

Source code in frequenz/quantities/_quantity.py
def __eq__(self, other: object) -> bool:
    """Return whether this quantity is equal to another.

    Args:
        other: The other quantity.

    Returns:
        Whether this quantity is equal to another.
    """
    if not type(other) is type(self):
        return NotImplemented
    # The above check ensures that both quantities are the exact same type, because
    # `isinstance` returns true for subclasses and superclasses.  But the above check
    # doesn't help mypy identify the type of other,  so the below line is necessary.
    assert isinstance(other, self.__class__)
    return self._base_value == other._base_value
__format__ ¤
__format__(__format_spec: str) -> str

Return a formatted string representation of this quantity.

If specified, must be of this form: [0].{precision}. If a 0 is not given, the trailing zeros will be omitted. If no precision is given, the default is 3.

The returned string will use the unit that will result in the maximum precision, based on the magnitude of the value.

Example
from frequenz.quantities import Current
c = Current.from_amperes(0.2345)
assert f"{c:.2}" == "234.5 mA"
c = Current.from_amperes(1.2345)
assert f"{c:.2}" == "1.23 A"
c = Current.from_milliamperes(1.2345)
assert f"{c:.6}" == "1.2345 mA"
PARAMETER DESCRIPTION
__format_spec

The format specifier.

TYPE: str

RETURNS DESCRIPTION
str

A string representation of this quantity.

RAISES DESCRIPTION
ValueError

If the given format specifier is invalid.

Source code in frequenz/quantities/_quantity.py
def __format__(self, __format_spec: str) -> str:
    """Return a formatted string representation of this quantity.

    If specified, must be of this form: `[0].{precision}`.  If a 0 is not given, the
    trailing zeros will be omitted.  If no precision is given, the default is 3.

    The returned string will use the unit that will result in the maximum precision,
    based on the magnitude of the value.

    Example:
        ```python
        from frequenz.quantities import Current
        c = Current.from_amperes(0.2345)
        assert f"{c:.2}" == "234.5 mA"
        c = Current.from_amperes(1.2345)
        assert f"{c:.2}" == "1.23 A"
        c = Current.from_milliamperes(1.2345)
        assert f"{c:.6}" == "1.2345 mA"
        ```

    Args:
        __format_spec: The format specifier.

    Returns:
        A string representation of this quantity.

    Raises:
        ValueError: If the given format specifier is invalid.
    """
    keep_trailing_zeros = False
    if __format_spec != "":
        fspec_parts = __format_spec.split(".")
        if (
            len(fspec_parts) != 2
            or fspec_parts[0] not in ("", "0")
            or not fspec_parts[1].isdigit()
        ):
            raise ValueError(
                "Invalid format specifier. Must be empty or `[0].{precision}`"
            )
        if fspec_parts[0] == "0":
            keep_trailing_zeros = True
        precision = int(fspec_parts[1])
    else:
        precision = 3
    if not self._exponent_unit_map:
        return f"{self._base_value:.{precision}f}"

    if math.isinf(self._base_value) or math.isnan(self._base_value):
        return f"{self._base_value} {self._exponent_unit_map[0]}"

    if abs_value := abs(self._base_value):
        precision_pow = 10 ** (precision)
        # Prevent numbers like 999.999999 being rendered as 1000 V
        # instead of 1 kV.
        # This could happen because the str formatting function does
        # rounding as well.
        # This is an imperfect solution that works for _most_ cases.
        # isclose parameters were chosen according to the observed cases
        if math.isclose(abs_value, precision_pow, abs_tol=1e-4, rel_tol=0.01):
            # If the value is close to the precision, round it
            exponent = math.ceil(math.log10(precision_pow))
        else:
            exponent = math.floor(math.log10(abs_value))
    else:
        exponent = 0

    unit_place = exponent - exponent % 3
    if unit_place < min(self._exponent_unit_map):
        unit = self._exponent_unit_map[min(self._exponent_unit_map.keys())]
        unit_place = min(self._exponent_unit_map)
    elif unit_place > max(self._exponent_unit_map):
        unit = self._exponent_unit_map[max(self._exponent_unit_map.keys())]
        unit_place = max(self._exponent_unit_map)
    else:
        unit = self._exponent_unit_map[unit_place]

    value_str = f"{self._base_value / 10 ** unit_place:.{precision}f}"

    if value_str in ("-0", "0"):
        stripped = value_str
    else:
        stripped = value_str.rstrip("0").rstrip(".")

    if not keep_trailing_zeros:
        value_str = stripped
    unit_str = unit if stripped not in ("-0", "0") else self._exponent_unit_map[0]
    return f"{value_str} {unit_str}"
__ge__ ¤
__ge__(other: Self) -> bool

Return whether this quantity is greater than or equal to another.

PARAMETER DESCRIPTION
other

The other quantity.

TYPE: Self

RETURNS DESCRIPTION
bool

Whether this quantity is greater than or equal to another.

Source code in frequenz/quantities/_quantity.py
def __ge__(self, other: Self) -> bool:
    """Return whether this quantity is greater than or equal to another.

    Args:
        other: The other quantity.

    Returns:
        Whether this quantity is greater than or equal to another.
    """
    if not type(other) is type(self):
        return NotImplemented
    return self._base_value >= other._base_value
__gt__ ¤
__gt__(other: Self) -> bool

Return whether this quantity is greater than another.

PARAMETER DESCRIPTION
other

The other quantity.

TYPE: Self

RETURNS DESCRIPTION
bool

Whether this quantity is greater than another.

Source code in frequenz/quantities/_quantity.py
def __gt__(self, other: Self) -> bool:
    """Return whether this quantity is greater than another.

    Args:
        other: The other quantity.

    Returns:
        Whether this quantity is greater than another.
    """
    if not type(other) is type(self):
        return NotImplemented
    return self._base_value > other._base_value
__init__ ¤
__init__(value: float, exponent: int = 0) -> None

Initialize a new quantity.

PARAMETER DESCRIPTION
value

The value of this quantity in a given exponent of the base unit.

TYPE: float

exponent

The exponent of the base unit the given value is in.

TYPE: int DEFAULT: 0

Source code in frequenz/quantities/_quantity.py
def __init__(self, value: float, exponent: int = 0) -> None:
    """Initialize a new quantity.

    Args:
        value: The value of this quantity in a given exponent of the base unit.
        exponent: The exponent of the base unit the given value is in.
    """
    self._base_value = value * 10.0**exponent
__init_subclass__ ¤
__init_subclass__(
    exponent_unit_map: dict[int, str]
) -> None

Initialize a new subclass of Quantity.

PARAMETER DESCRIPTION
exponent_unit_map

A mapping from the exponent of the base unit to the unit symbol.

TYPE: dict[int, str]

RAISES DESCRIPTION
ValueError

If the given exponent_unit_map does not contain a base unit (exponent 0).

Source code in frequenz/quantities/_quantity.py
def __init_subclass__(cls, exponent_unit_map: dict[int, str]) -> None:
    """Initialize a new subclass of Quantity.

    Args:
        exponent_unit_map: A mapping from the exponent of the base unit to the unit
            symbol.

    Raises:
        ValueError: If the given exponent_unit_map does not contain a base unit
            (exponent 0).
    """
    if 0 not in exponent_unit_map:
        raise ValueError("Expected a base unit for the type (for exponent 0)")
    cls._exponent_unit_map = exponent_unit_map
    super().__init_subclass__()
__le__ ¤
__le__(other: Self) -> bool

Return whether this quantity is less than or equal to another.

PARAMETER DESCRIPTION
other

The other quantity.

TYPE: Self

RETURNS DESCRIPTION
bool

Whether this quantity is less than or equal to another.

Source code in frequenz/quantities/_quantity.py
def __le__(self, other: Self) -> bool:
    """Return whether this quantity is less than or equal to another.

    Args:
        other: The other quantity.

    Returns:
        Whether this quantity is less than or equal to another.
    """
    if not type(other) is type(self):
        return NotImplemented
    return self._base_value <= other._base_value
__lt__ ¤
__lt__(other: Self) -> bool

Return whether this quantity is less than another.

PARAMETER DESCRIPTION
other

The other quantity.

TYPE: Self

RETURNS DESCRIPTION
bool

Whether this quantity is less than another.

Source code in frequenz/quantities/_quantity.py
def __lt__(self, other: Self) -> bool:
    """Return whether this quantity is less than another.

    Args:
        other: The other quantity.

    Returns:
        Whether this quantity is less than another.
    """
    if not type(other) is type(self):
        return NotImplemented
    return self._base_value < other._base_value
__mod__ ¤
__mod__(other: Self) -> Self

Return the remainder of this quantity and another.

PARAMETER DESCRIPTION
other

The other quantity.

TYPE: Self

RETURNS DESCRIPTION
Self

The remainder of this quantity and another.

Source code in frequenz/quantities/_quantity.py
def __mod__(self, other: Self) -> Self:
    """Return the remainder of this quantity and another.

    Args:
        other: The other quantity.

    Returns:
        The remainder of this quantity and another.
    """
    return self._new(self._base_value % other._base_value)
__mul__ ¤
__mul__(other: float | Percentage) -> Self

Scale this energy by a percentage.

PARAMETER DESCRIPTION
other

The percentage by which to scale this energy.

TYPE: float | Percentage

RETURNS DESCRIPTION
Self

The scaled energy.

Source code in frequenz/quantities/_energy.py
def __mul__(self, other: float | Percentage) -> Self:
    """Scale this energy by a percentage.

    Args:
        other: The percentage by which to scale this energy.

    Returns:
        The scaled energy.
    """
    from ._percentage import Percentage  # pylint: disable=import-outside-toplevel

    match other:
        case float():
            return self._new(self._base_value * other)
        case Percentage():
            return self._new(self._base_value * other.as_fraction())
        case _:
            return NotImplemented
__neg__ ¤
__neg__() -> Self

Return the negation of this quantity.

RETURNS DESCRIPTION
Self

The negation of this quantity.

Source code in frequenz/quantities/_quantity.py
def __neg__(self) -> Self:
    """Return the negation of this quantity.

    Returns:
        The negation of this quantity.
    """
    negation = type(self).__new__(type(self))
    negation._base_value = -self._base_value
    return negation
__pos__ ¤
__pos__() -> Self

Return this quantity.

RETURNS DESCRIPTION
Self

This quantity.

Source code in frequenz/quantities/_quantity.py
def __pos__(self) -> Self:
    """Return this quantity.

    Returns:
        This quantity.
    """
    return self
__repr__ ¤
__repr__() -> str

Return a representation of this quantity.

RETURNS DESCRIPTION
str

A representation of this quantity.

Source code in frequenz/quantities/_quantity.py
def __repr__(self) -> str:
    """Return a representation of this quantity.

    Returns:
        A representation of this quantity.
    """
    return f"{type(self).__name__}(value={self._base_value}, exponent=0)"
__round__ ¤
__round__(ndigits: int | None = None) -> Self

Round this quantity to the given number of digits.

PARAMETER DESCRIPTION
ndigits

The number of digits to round to.

TYPE: int | None DEFAULT: None

RETURNS DESCRIPTION
Self

The rounded quantity.

Source code in frequenz/quantities/_quantity.py
def __round__(self, ndigits: int | None = None) -> Self:
    """Round this quantity to the given number of digits.

    Args:
        ndigits: The number of digits to round to.

    Returns:
        The rounded quantity.
    """
    return self._new(round(self._base_value, ndigits))
__str__ ¤
__str__() -> str

Return a string representation of this quantity.

RETURNS DESCRIPTION
str

A string representation of this quantity.

Source code in frequenz/quantities/_quantity.py
def __str__(self) -> str:
    """Return a string representation of this quantity.

    Returns:
        A string representation of this quantity.
    """
    return self.__format__("")
__sub__ ¤
__sub__(other: Self) -> Self

Return the difference of this quantity and another.

PARAMETER DESCRIPTION
other

The other quantity.

TYPE: Self

RETURNS DESCRIPTION
Self

The difference of this quantity and another.

Source code in frequenz/quantities/_quantity.py
def __sub__(self, other: Self) -> Self:
    """Return the difference of this quantity and another.

    Args:
        other: The other quantity.

    Returns:
        The difference of this quantity and another.
    """
    if not type(other) is type(self):
        return NotImplemented
    difference = type(self).__new__(type(self))
    difference._base_value = self._base_value - other._base_value
    return difference
__truediv__ ¤
__truediv__(other: float) -> Self
__truediv__(other: Self) -> float
__truediv__(duration: timedelta) -> Power
__truediv__(power: Power) -> timedelta
__truediv__(
    other: float | Self | timedelta | Power,
) -> Self | float | Power | timedelta

Return a power or duration from dividing this energy by the given value.

PARAMETER DESCRIPTION
other

The scalar, energy, power or duration to divide by.

TYPE: float | Self | timedelta | Power

RETURNS DESCRIPTION
Self | float | Power | timedelta

A power or duration from dividing this energy by the given value.

Source code in frequenz/quantities/_energy.py
def __truediv__(
    self, other: float | Self | timedelta | Power, /
) -> Self | float | Power | timedelta:
    """Return a power or duration from dividing this energy by the given value.

    Args:
        other: The scalar, energy, power or duration to divide by.

    Returns:
        A power or duration from dividing this energy by the given value.
    """
    from ._power import Power  # pylint: disable=import-outside-toplevel

    match other:
        case float():
            return super().__truediv__(other)
        case Energy():
            return self._base_value / other._base_value
        case timedelta():
            return Power._new(self._base_value / (other.total_seconds() / 3600.0))
        case Power():
            return timedelta(
                seconds=(self._base_value / other._base_value) * 3600.0
            )
        case _:
            return NotImplemented
as_kilowatt_hours ¤
as_kilowatt_hours() -> float

Return the energy in kilowatt hours.

RETURNS DESCRIPTION
float

The energy in kilowatt hours.

Source code in frequenz/quantities/_energy.py
def as_kilowatt_hours(self) -> float:
    """Return the energy in kilowatt hours.

    Returns:
        The energy in kilowatt hours.
    """
    return self._base_value / 1e3
as_megawatt_hours ¤
as_megawatt_hours() -> float

Return the energy in megawatt hours.

RETURNS DESCRIPTION
float

The energy in megawatt hours.

Source code in frequenz/quantities/_energy.py
def as_megawatt_hours(self) -> float:
    """Return the energy in megawatt hours.

    Returns:
        The energy in megawatt hours.
    """
    return self._base_value / 1e6
as_watt_hours ¤
as_watt_hours() -> float

Return the energy in watt hours.

RETURNS DESCRIPTION
float

The energy in watt hours.

Source code in frequenz/quantities/_energy.py
def as_watt_hours(self) -> float:
    """Return the energy in watt hours.

    Returns:
        The energy in watt hours.
    """
    return self._base_value
from_kilowatt_hours classmethod ¤
from_kilowatt_hours(kilowatt_hours: float) -> Self

Initialize a new energy quantity.

PARAMETER DESCRIPTION
kilowatt_hours

The energy in kilowatt hours.

TYPE: float

RETURNS DESCRIPTION
Self

A new energy quantity.

Source code in frequenz/quantities/_energy.py
@classmethod
def from_kilowatt_hours(cls, kilowatt_hours: float) -> Self:
    """Initialize a new energy quantity.

    Args:
        kilowatt_hours: The energy in kilowatt hours.

    Returns:
        A new energy quantity.
    """
    return cls._new(kilowatt_hours, exponent=3)
from_megawatt_hours classmethod ¤
from_megawatt_hours(megawatt_hours: float) -> Self

Initialize a new energy quantity.

PARAMETER DESCRIPTION
megawatt_hours

The energy in megawatt hours.

TYPE: float

RETURNS DESCRIPTION
Self

A new energy quantity.

Source code in frequenz/quantities/_energy.py
@classmethod
def from_megawatt_hours(cls, megawatt_hours: float) -> Self:
    """Initialize a new energy quantity.

    Args:
        megawatt_hours: The energy in megawatt hours.

    Returns:
        A new energy quantity.
    """
    return cls._new(megawatt_hours, exponent=6)
from_string classmethod ¤
from_string(string: str) -> Self

Return a quantity from a string representation.

PARAMETER DESCRIPTION
string

The string representation of the quantity.

TYPE: str

RETURNS DESCRIPTION
Self

A quantity object with the value given in the string.

RAISES DESCRIPTION
ValueError

If the string does not match the expected format.

Source code in frequenz/quantities/_quantity.py
@classmethod
def from_string(cls, string: str) -> Self:
    """Return a quantity from a string representation.

    Args:
        string: The string representation of the quantity.

    Returns:
        A quantity object with the value given in the string.

    Raises:
        ValueError: If the string does not match the expected format.

    """
    split_string = string.split(" ")

    if len(split_string) != 2:
        raise ValueError(
            f"Expected a string of the form 'value unit', got {string}"
        )

    assert cls._exponent_unit_map is not None
    exp_map = cls._exponent_unit_map

    for exponent, unit in exp_map.items():
        if unit == split_string[1]:
            instance = cls.__new__(cls)
            try:
                instance._base_value = float(split_string[0]) * 10**exponent
            except ValueError as error:
                raise ValueError(f"Failed to parse string '{string}'.") from error

            return instance

    raise ValueError(f"Unknown unit {split_string[1]}")
from_watt_hours classmethod ¤
from_watt_hours(watt_hours: float) -> Self

Initialize a new energy quantity.

PARAMETER DESCRIPTION
watt_hours

The energy in watt hours.

TYPE: float

RETURNS DESCRIPTION
Self

A new energy quantity.

Source code in frequenz/quantities/_energy.py
@classmethod
def from_watt_hours(cls, watt_hours: float) -> Self:
    """Initialize a new energy quantity.

    Args:
        watt_hours: The energy in watt hours.

    Returns:
        A new energy quantity.
    """
    return cls._new(watt_hours)
isclose ¤
isclose(
    other: Self,
    rel_tol: float = 1e-09,
    abs_tol: float = 0.0,
) -> bool

Return whether this quantity is close to another.

PARAMETER DESCRIPTION
other

The quantity to compare to.

TYPE: Self

rel_tol

The relative tolerance.

TYPE: float DEFAULT: 1e-09

abs_tol

The absolute tolerance.

TYPE: float DEFAULT: 0.0

RETURNS DESCRIPTION
bool

Whether this quantity is close to another.

Source code in frequenz/quantities/_quantity.py
def isclose(self, other: Self, rel_tol: float = 1e-9, abs_tol: float = 0.0) -> bool:
    """Return whether this quantity is close to another.

    Args:
        other: The quantity to compare to.
        rel_tol: The relative tolerance.
        abs_tol: The absolute tolerance.

    Returns:
        Whether this quantity is close to another.
    """
    return math.isclose(
        self._base_value,
        other._base_value,  # pylint: disable=protected-access
        rel_tol=rel_tol,
        abs_tol=abs_tol,
    )
isinf ¤
isinf() -> bool

Return whether this quantity is infinite.

RETURNS DESCRIPTION
bool

Whether this quantity is infinite.

Source code in frequenz/quantities/_quantity.py
def isinf(self) -> bool:
    """Return whether this quantity is infinite.

    Returns:
        Whether this quantity is infinite.
    """
    return math.isinf(self._base_value)
isnan ¤
isnan() -> bool

Return whether this quantity is NaN.

RETURNS DESCRIPTION
bool

Whether this quantity is NaN.

Source code in frequenz/quantities/_quantity.py
def isnan(self) -> bool:
    """Return whether this quantity is NaN.

    Returns:
        Whether this quantity is NaN.
    """
    return math.isnan(self._base_value)
zero classmethod ¤
zero() -> Self

Return a quantity with value 0.0.

RETURNS DESCRIPTION
Self

A quantity with value 0.0.

Source code in frequenz/quantities/_quantity.py
@classmethod
def zero(cls) -> Self:
    """Return a quantity with value 0.0.

    Returns:
        A quantity with value 0.0.
    """
    _zero = cls._zero_cache.get(cls, None)
    if _zero is None:
        _zero = cls.__new__(cls)
        _zero._base_value = 0.0
        cls._zero_cache[cls] = _zero
    assert isinstance(_zero, cls)
    return _zero

frequenz.quantities.Frequency ¤

Bases: Quantity

A frequency quantity.

Objects of this type are wrappers around float values and are immutable.

The constructors accept a single float value, the as_*() methods return a float value, and each of the arithmetic operators supported by this type are actually implemented using floating-point arithmetic.

So all considerations about floating-point arithmetic apply to this type as well.

Source code in frequenz/quantities/_frequency.py
class Frequency(
    Quantity,
    metaclass=NoDefaultConstructible,
    exponent_unit_map={0: "Hz", 3: "kHz", 6: "MHz", 9: "GHz"},
):
    """A frequency quantity.

    Objects of this type are wrappers around `float` values and are immutable.

    The constructors accept a single `float` value, the `as_*()` methods return a
    `float` value, and each of the arithmetic operators supported by this type are
    actually implemented using floating-point arithmetic.

    So all considerations about floating-point arithmetic apply to this type as well.
    """

    @classmethod
    def from_hertz(cls, hertz: float) -> Self:
        """Initialize a new frequency quantity.

        Args:
            hertz: The frequency in hertz.

        Returns:
            A new frequency quantity.
        """
        return cls._new(hertz)

    @classmethod
    def from_kilohertz(cls, kilohertz: float) -> Self:
        """Initialize a new frequency quantity.

        Args:
            kilohertz: The frequency in kilohertz.

        Returns:
            A new frequency quantity.
        """
        return cls._new(kilohertz, exponent=3)

    @classmethod
    def from_megahertz(cls, megahertz: float) -> Self:
        """Initialize a new frequency quantity.

        Args:
            megahertz: The frequency in megahertz.

        Returns:
            A new frequency quantity.
        """
        return cls._new(megahertz, exponent=6)

    @classmethod
    def from_gigahertz(cls, gigahertz: float) -> Self:
        """Initialize a new frequency quantity.

        Args:
            gigahertz: The frequency in gigahertz.

        Returns:
            A new frequency quantity.
        """
        return cls._new(gigahertz, exponent=9)

    def as_hertz(self) -> float:
        """Return the frequency in hertz.

        Returns:
            The frequency in hertz.
        """
        return self._base_value

    def as_kilohertz(self) -> float:
        """Return the frequency in kilohertz.

        Returns:
            The frequency in kilohertz.
        """
        return self._base_value / 1e3

    def as_megahertz(self) -> float:
        """Return the frequency in megahertz.

        Returns:
            The frequency in megahertz.
        """
        return self._base_value / 1e6

    def as_gigahertz(self) -> float:
        """Return the frequency in gigahertz.

        Returns:
            The frequency in gigahertz.
        """
        return self._base_value / 1e9

    def period(self) -> timedelta:
        """Return the period of the frequency.

        Returns:
            The period of the frequency.
        """
        return timedelta(seconds=1.0 / self._base_value)
Attributes¤
base_unit property ¤
base_unit: str | None

Return the base unit of this quantity.

None if this quantity has no unit.

RETURNS DESCRIPTION
str | None

The base unit of this quantity.

base_value property ¤
base_value: float

Return the value of this quantity in the base unit.

RETURNS DESCRIPTION
float

The value of this quantity in the base unit.

Functions¤
__abs__ ¤
__abs__() -> Self

Return the absolute value of this quantity.

RETURNS DESCRIPTION
Self

The absolute value of this quantity.

Source code in frequenz/quantities/_quantity.py
def __abs__(self) -> Self:
    """Return the absolute value of this quantity.

    Returns:
        The absolute value of this quantity.
    """
    absolute = type(self).__new__(type(self))
    absolute._base_value = abs(self._base_value)
    return absolute
__add__ ¤
__add__(other: Self) -> Self

Return the sum of this quantity and another.

PARAMETER DESCRIPTION
other

The other quantity.

TYPE: Self

RETURNS DESCRIPTION
Self

The sum of this quantity and another.

Source code in frequenz/quantities/_quantity.py
def __add__(self, other: Self) -> Self:
    """Return the sum of this quantity and another.

    Args:
        other: The other quantity.

    Returns:
        The sum of this quantity and another.
    """
    if not type(other) is type(self):
        return NotImplemented
    summe = type(self).__new__(type(self))
    summe._base_value = self._base_value + other._base_value
    return summe
__eq__ ¤
__eq__(other: object) -> bool

Return whether this quantity is equal to another.

PARAMETER DESCRIPTION
other

The other quantity.

TYPE: object

RETURNS DESCRIPTION
bool

Whether this quantity is equal to another.

Source code in frequenz/quantities/_quantity.py
def __eq__(self, other: object) -> bool:
    """Return whether this quantity is equal to another.

    Args:
        other: The other quantity.

    Returns:
        Whether this quantity is equal to another.
    """
    if not type(other) is type(self):
        return NotImplemented
    # The above check ensures that both quantities are the exact same type, because
    # `isinstance` returns true for subclasses and superclasses.  But the above check
    # doesn't help mypy identify the type of other,  so the below line is necessary.
    assert isinstance(other, self.__class__)
    return self._base_value == other._base_value
__format__ ¤
__format__(__format_spec: str) -> str

Return a formatted string representation of this quantity.

If specified, must be of this form: [0].{precision}. If a 0 is not given, the trailing zeros will be omitted. If no precision is given, the default is 3.

The returned string will use the unit that will result in the maximum precision, based on the magnitude of the value.

Example
from frequenz.quantities import Current
c = Current.from_amperes(0.2345)
assert f"{c:.2}" == "234.5 mA"
c = Current.from_amperes(1.2345)
assert f"{c:.2}" == "1.23 A"
c = Current.from_milliamperes(1.2345)
assert f"{c:.6}" == "1.2345 mA"
PARAMETER DESCRIPTION
__format_spec

The format specifier.

TYPE: str

RETURNS DESCRIPTION
str

A string representation of this quantity.

RAISES DESCRIPTION
ValueError

If the given format specifier is invalid.

Source code in frequenz/quantities/_quantity.py
def __format__(self, __format_spec: str) -> str:
    """Return a formatted string representation of this quantity.

    If specified, must be of this form: `[0].{precision}`.  If a 0 is not given, the
    trailing zeros will be omitted.  If no precision is given, the default is 3.

    The returned string will use the unit that will result in the maximum precision,
    based on the magnitude of the value.

    Example:
        ```python
        from frequenz.quantities import Current
        c = Current.from_amperes(0.2345)
        assert f"{c:.2}" == "234.5 mA"
        c = Current.from_amperes(1.2345)
        assert f"{c:.2}" == "1.23 A"
        c = Current.from_milliamperes(1.2345)
        assert f"{c:.6}" == "1.2345 mA"
        ```

    Args:
        __format_spec: The format specifier.

    Returns:
        A string representation of this quantity.

    Raises:
        ValueError: If the given format specifier is invalid.
    """
    keep_trailing_zeros = False
    if __format_spec != "":
        fspec_parts = __format_spec.split(".")
        if (
            len(fspec_parts) != 2
            or fspec_parts[0] not in ("", "0")
            or not fspec_parts[1].isdigit()
        ):
            raise ValueError(
                "Invalid format specifier. Must be empty or `[0].{precision}`"
            )
        if fspec_parts[0] == "0":
            keep_trailing_zeros = True
        precision = int(fspec_parts[1])
    else:
        precision = 3
    if not self._exponent_unit_map:
        return f"{self._base_value:.{precision}f}"

    if math.isinf(self._base_value) or math.isnan(self._base_value):
        return f"{self._base_value} {self._exponent_unit_map[0]}"

    if abs_value := abs(self._base_value):
        precision_pow = 10 ** (precision)
        # Prevent numbers like 999.999999 being rendered as 1000 V
        # instead of 1 kV.
        # This could happen because the str formatting function does
        # rounding as well.
        # This is an imperfect solution that works for _most_ cases.
        # isclose parameters were chosen according to the observed cases
        if math.isclose(abs_value, precision_pow, abs_tol=1e-4, rel_tol=0.01):
            # If the value is close to the precision, round it
            exponent = math.ceil(math.log10(precision_pow))
        else:
            exponent = math.floor(math.log10(abs_value))
    else:
        exponent = 0

    unit_place = exponent - exponent % 3
    if unit_place < min(self._exponent_unit_map):
        unit = self._exponent_unit_map[min(self._exponent_unit_map.keys())]
        unit_place = min(self._exponent_unit_map)
    elif unit_place > max(self._exponent_unit_map):
        unit = self._exponent_unit_map[max(self._exponent_unit_map.keys())]
        unit_place = max(self._exponent_unit_map)
    else:
        unit = self._exponent_unit_map[unit_place]

    value_str = f"{self._base_value / 10 ** unit_place:.{precision}f}"

    if value_str in ("-0", "0"):
        stripped = value_str
    else:
        stripped = value_str.rstrip("0").rstrip(".")

    if not keep_trailing_zeros:
        value_str = stripped
    unit_str = unit if stripped not in ("-0", "0") else self._exponent_unit_map[0]
    return f"{value_str} {unit_str}"
__ge__ ¤
__ge__(other: Self) -> bool

Return whether this quantity is greater than or equal to another.

PARAMETER DESCRIPTION
other

The other quantity.

TYPE: Self

RETURNS DESCRIPTION
bool

Whether this quantity is greater than or equal to another.

Source code in frequenz/quantities/_quantity.py
def __ge__(self, other: Self) -> bool:
    """Return whether this quantity is greater than or equal to another.

    Args:
        other: The other quantity.

    Returns:
        Whether this quantity is greater than or equal to another.
    """
    if not type(other) is type(self):
        return NotImplemented
    return self._base_value >= other._base_value
__gt__ ¤
__gt__(other: Self) -> bool

Return whether this quantity is greater than another.

PARAMETER DESCRIPTION
other

The other quantity.

TYPE: Self

RETURNS DESCRIPTION
bool

Whether this quantity is greater than another.

Source code in frequenz/quantities/_quantity.py
def __gt__(self, other: Self) -> bool:
    """Return whether this quantity is greater than another.

    Args:
        other: The other quantity.

    Returns:
        Whether this quantity is greater than another.
    """
    if not type(other) is type(self):
        return NotImplemented
    return self._base_value > other._base_value
__init__ ¤
__init__(value: float, exponent: int = 0) -> None

Initialize a new quantity.

PARAMETER DESCRIPTION
value

The value of this quantity in a given exponent of the base unit.

TYPE: float

exponent

The exponent of the base unit the given value is in.

TYPE: int DEFAULT: 0

Source code in frequenz/quantities/_quantity.py
def __init__(self, value: float, exponent: int = 0) -> None:
    """Initialize a new quantity.

    Args:
        value: The value of this quantity in a given exponent of the base unit.
        exponent: The exponent of the base unit the given value is in.
    """
    self._base_value = value * 10.0**exponent
__init_subclass__ ¤
__init_subclass__(
    exponent_unit_map: dict[int, str]
) -> None

Initialize a new subclass of Quantity.

PARAMETER DESCRIPTION
exponent_unit_map

A mapping from the exponent of the base unit to the unit symbol.

TYPE: dict[int, str]

RAISES DESCRIPTION
ValueError

If the given exponent_unit_map does not contain a base unit (exponent 0).

Source code in frequenz/quantities/_quantity.py
def __init_subclass__(cls, exponent_unit_map: dict[int, str]) -> None:
    """Initialize a new subclass of Quantity.

    Args:
        exponent_unit_map: A mapping from the exponent of the base unit to the unit
            symbol.

    Raises:
        ValueError: If the given exponent_unit_map does not contain a base unit
            (exponent 0).
    """
    if 0 not in exponent_unit_map:
        raise ValueError("Expected a base unit for the type (for exponent 0)")
    cls._exponent_unit_map = exponent_unit_map
    super().__init_subclass__()
__le__ ¤
__le__(other: Self) -> bool

Return whether this quantity is less than or equal to another.

PARAMETER DESCRIPTION
other

The other quantity.

TYPE: Self

RETURNS DESCRIPTION
bool

Whether this quantity is less than or equal to another.

Source code in frequenz/quantities/_quantity.py
def __le__(self, other: Self) -> bool:
    """Return whether this quantity is less than or equal to another.

    Args:
        other: The other quantity.

    Returns:
        Whether this quantity is less than or equal to another.
    """
    if not type(other) is type(self):
        return NotImplemented
    return self._base_value <= other._base_value
__lt__ ¤
__lt__(other: Self) -> bool

Return whether this quantity is less than another.

PARAMETER DESCRIPTION
other

The other quantity.

TYPE: Self

RETURNS DESCRIPTION
bool

Whether this quantity is less than another.

Source code in frequenz/quantities/_quantity.py
def __lt__(self, other: Self) -> bool:
    """Return whether this quantity is less than another.

    Args:
        other: The other quantity.

    Returns:
        Whether this quantity is less than another.
    """
    if not type(other) is type(self):
        return NotImplemented
    return self._base_value < other._base_value
__mod__ ¤
__mod__(other: Self) -> Self

Return the remainder of this quantity and another.

PARAMETER DESCRIPTION
other

The other quantity.

TYPE: Self

RETURNS DESCRIPTION
Self

The remainder of this quantity and another.

Source code in frequenz/quantities/_quantity.py
def __mod__(self, other: Self) -> Self:
    """Return the remainder of this quantity and another.

    Args:
        other: The other quantity.

    Returns:
        The remainder of this quantity and another.
    """
    return self._new(self._base_value % other._base_value)
__mul__ ¤
__mul__(scalar: float) -> Self
__mul__(percent: Percentage) -> Self
__mul__(value: float | Percentage) -> Self

Scale this quantity by a scalar or percentage.

PARAMETER DESCRIPTION
value

The scalar or percentage by which to scale this quantity.

TYPE: float | Percentage

RETURNS DESCRIPTION
Self

The scaled quantity.

Source code in frequenz/quantities/_quantity.py
def __mul__(self, value: float | Percentage, /) -> Self:
    """Scale this quantity by a scalar or percentage.

    Args:
        value: The scalar or percentage by which to scale this quantity.

    Returns:
        The scaled quantity.
    """
    from ._percentage import Percentage  # pylint: disable=import-outside-toplevel

    match value:
        case float():
            return type(self)._new(self._base_value * value)
        case Percentage():
            return type(self)._new(self._base_value * value.as_fraction())
        case _:
            return NotImplemented
__neg__ ¤
__neg__() -> Self

Return the negation of this quantity.

RETURNS DESCRIPTION
Self

The negation of this quantity.

Source code in frequenz/quantities/_quantity.py
def __neg__(self) -> Self:
    """Return the negation of this quantity.

    Returns:
        The negation of this quantity.
    """
    negation = type(self).__new__(type(self))
    negation._base_value = -self._base_value
    return negation
__pos__ ¤
__pos__() -> Self

Return this quantity.

RETURNS DESCRIPTION
Self

This quantity.

Source code in frequenz/quantities/_quantity.py
def __pos__(self) -> Self:
    """Return this quantity.

    Returns:
        This quantity.
    """
    return self
__repr__ ¤
__repr__() -> str

Return a representation of this quantity.

RETURNS DESCRIPTION
str

A representation of this quantity.

Source code in frequenz/quantities/_quantity.py
def __repr__(self) -> str:
    """Return a representation of this quantity.

    Returns:
        A representation of this quantity.
    """
    return f"{type(self).__name__}(value={self._base_value}, exponent=0)"
__round__ ¤
__round__(ndigits: int | None = None) -> Self

Round this quantity to the given number of digits.

PARAMETER DESCRIPTION
ndigits

The number of digits to round to.

TYPE: int | None DEFAULT: None

RETURNS DESCRIPTION
Self

The rounded quantity.

Source code in frequenz/quantities/_quantity.py
def __round__(self, ndigits: int | None = None) -> Self:
    """Round this quantity to the given number of digits.

    Args:
        ndigits: The number of digits to round to.

    Returns:
        The rounded quantity.
    """
    return self._new(round(self._base_value, ndigits))
__str__ ¤
__str__() -> str

Return a string representation of this quantity.

RETURNS DESCRIPTION
str

A string representation of this quantity.

Source code in frequenz/quantities/_quantity.py
def __str__(self) -> str:
    """Return a string representation of this quantity.

    Returns:
        A string representation of this quantity.
    """
    return self.__format__("")
__sub__ ¤
__sub__(other: Self) -> Self

Return the difference of this quantity and another.

PARAMETER DESCRIPTION
other

The other quantity.

TYPE: Self

RETURNS DESCRIPTION
Self

The difference of this quantity and another.

Source code in frequenz/quantities/_quantity.py
def __sub__(self, other: Self) -> Self:
    """Return the difference of this quantity and another.

    Args:
        other: The other quantity.

    Returns:
        The difference of this quantity and another.
    """
    if not type(other) is type(self):
        return NotImplemented
    difference = type(self).__new__(type(self))
    difference._base_value = self._base_value - other._base_value
    return difference
__truediv__ ¤
__truediv__(other: float) -> Self
__truediv__(other: Self) -> float
__truediv__(value: float | Self) -> Self | float

Divide this quantity by a scalar or another quantity.

PARAMETER DESCRIPTION
value

The scalar or quantity to divide this quantity by.

TYPE: float | Self

RETURNS DESCRIPTION
Self | float

The divided quantity or the ratio of this quantity to another.

Source code in frequenz/quantities/_quantity.py
def __truediv__(self, value: float | Self, /) -> Self | float:
    """Divide this quantity by a scalar or another quantity.

    Args:
        value: The scalar or quantity to divide this quantity by.

    Returns:
        The divided quantity or the ratio of this quantity to another.
    """
    match value:
        case float():
            return type(self)._new(self._base_value / value)
        case Quantity() if type(value) is type(self):
            return self._base_value / value._base_value
        case _:
            return NotImplemented
as_gigahertz ¤
as_gigahertz() -> float

Return the frequency in gigahertz.

RETURNS DESCRIPTION
float

The frequency in gigahertz.

Source code in frequenz/quantities/_frequency.py
def as_gigahertz(self) -> float:
    """Return the frequency in gigahertz.

    Returns:
        The frequency in gigahertz.
    """
    return self._base_value / 1e9
as_hertz ¤
as_hertz() -> float

Return the frequency in hertz.

RETURNS DESCRIPTION
float

The frequency in hertz.

Source code in frequenz/quantities/_frequency.py
def as_hertz(self) -> float:
    """Return the frequency in hertz.

    Returns:
        The frequency in hertz.
    """
    return self._base_value
as_kilohertz ¤
as_kilohertz() -> float

Return the frequency in kilohertz.

RETURNS DESCRIPTION
float

The frequency in kilohertz.

Source code in frequenz/quantities/_frequency.py
def as_kilohertz(self) -> float:
    """Return the frequency in kilohertz.

    Returns:
        The frequency in kilohertz.
    """
    return self._base_value / 1e3
as_megahertz ¤
as_megahertz() -> float

Return the frequency in megahertz.

RETURNS DESCRIPTION
float

The frequency in megahertz.

Source code in frequenz/quantities/_frequency.py
def as_megahertz(self) -> float:
    """Return the frequency in megahertz.

    Returns:
        The frequency in megahertz.
    """
    return self._base_value / 1e6
from_gigahertz classmethod ¤
from_gigahertz(gigahertz: float) -> Self

Initialize a new frequency quantity.

PARAMETER DESCRIPTION
gigahertz

The frequency in gigahertz.

TYPE: float

RETURNS DESCRIPTION
Self

A new frequency quantity.

Source code in frequenz/quantities/_frequency.py
@classmethod
def from_gigahertz(cls, gigahertz: float) -> Self:
    """Initialize a new frequency quantity.

    Args:
        gigahertz: The frequency in gigahertz.

    Returns:
        A new frequency quantity.
    """
    return cls._new(gigahertz, exponent=9)
from_hertz classmethod ¤
from_hertz(hertz: float) -> Self

Initialize a new frequency quantity.

PARAMETER DESCRIPTION
hertz

The frequency in hertz.

TYPE: float

RETURNS DESCRIPTION
Self

A new frequency quantity.

Source code in frequenz/quantities/_frequency.py
@classmethod
def from_hertz(cls, hertz: float) -> Self:
    """Initialize a new frequency quantity.

    Args:
        hertz: The frequency in hertz.

    Returns:
        A new frequency quantity.
    """
    return cls._new(hertz)
from_kilohertz classmethod ¤
from_kilohertz(kilohertz: float) -> Self

Initialize a new frequency quantity.

PARAMETER DESCRIPTION
kilohertz

The frequency in kilohertz.

TYPE: float

RETURNS DESCRIPTION
Self

A new frequency quantity.

Source code in frequenz/quantities/_frequency.py
@classmethod
def from_kilohertz(cls, kilohertz: float) -> Self:
    """Initialize a new frequency quantity.

    Args:
        kilohertz: The frequency in kilohertz.

    Returns:
        A new frequency quantity.
    """
    return cls._new(kilohertz, exponent=3)
from_megahertz classmethod ¤
from_megahertz(megahertz: float) -> Self

Initialize a new frequency quantity.

PARAMETER DESCRIPTION
megahertz

The frequency in megahertz.

TYPE: float

RETURNS DESCRIPTION
Self

A new frequency quantity.

Source code in frequenz/quantities/_frequency.py
@classmethod
def from_megahertz(cls, megahertz: float) -> Self:
    """Initialize a new frequency quantity.

    Args:
        megahertz: The frequency in megahertz.

    Returns:
        A new frequency quantity.
    """
    return cls._new(megahertz, exponent=6)
from_string classmethod ¤
from_string(string: str) -> Self

Return a quantity from a string representation.

PARAMETER DESCRIPTION
string

The string representation of the quantity.

TYPE: str

RETURNS DESCRIPTION
Self

A quantity object with the value given in the string.

RAISES DESCRIPTION
ValueError

If the string does not match the expected format.

Source code in frequenz/quantities/_quantity.py
@classmethod
def from_string(cls, string: str) -> Self:
    """Return a quantity from a string representation.

    Args:
        string: The string representation of the quantity.

    Returns:
        A quantity object with the value given in the string.

    Raises:
        ValueError: If the string does not match the expected format.

    """
    split_string = string.split(" ")

    if len(split_string) != 2:
        raise ValueError(
            f"Expected a string of the form 'value unit', got {string}"
        )

    assert cls._exponent_unit_map is not None
    exp_map = cls._exponent_unit_map

    for exponent, unit in exp_map.items():
        if unit == split_string[1]:
            instance = cls.__new__(cls)
            try:
                instance._base_value = float(split_string[0]) * 10**exponent
            except ValueError as error:
                raise ValueError(f"Failed to parse string '{string}'.") from error

            return instance

    raise ValueError(f"Unknown unit {split_string[1]}")
isclose ¤
isclose(
    other: Self,
    rel_tol: float = 1e-09,
    abs_tol: float = 0.0,
) -> bool

Return whether this quantity is close to another.

PARAMETER DESCRIPTION
other

The quantity to compare to.

TYPE: Self

rel_tol

The relative tolerance.

TYPE: float DEFAULT: 1e-09

abs_tol

The absolute tolerance.

TYPE: float DEFAULT: 0.0

RETURNS DESCRIPTION
bool

Whether this quantity is close to another.

Source code in frequenz/quantities/_quantity.py
def isclose(self, other: Self, rel_tol: float = 1e-9, abs_tol: float = 0.0) -> bool:
    """Return whether this quantity is close to another.

    Args:
        other: The quantity to compare to.
        rel_tol: The relative tolerance.
        abs_tol: The absolute tolerance.

    Returns:
        Whether this quantity is close to another.
    """
    return math.isclose(
        self._base_value,
        other._base_value,  # pylint: disable=protected-access
        rel_tol=rel_tol,
        abs_tol=abs_tol,
    )
isinf ¤
isinf() -> bool

Return whether this quantity is infinite.

RETURNS DESCRIPTION
bool

Whether this quantity is infinite.

Source code in frequenz/quantities/_quantity.py
def isinf(self) -> bool:
    """Return whether this quantity is infinite.

    Returns:
        Whether this quantity is infinite.
    """
    return math.isinf(self._base_value)
isnan ¤
isnan() -> bool

Return whether this quantity is NaN.

RETURNS DESCRIPTION
bool

Whether this quantity is NaN.

Source code in frequenz/quantities/_quantity.py
def isnan(self) -> bool:
    """Return whether this quantity is NaN.

    Returns:
        Whether this quantity is NaN.
    """
    return math.isnan(self._base_value)
period ¤
period() -> timedelta

Return the period of the frequency.

RETURNS DESCRIPTION
timedelta

The period of the frequency.

Source code in frequenz/quantities/_frequency.py
def period(self) -> timedelta:
    """Return the period of the frequency.

    Returns:
        The period of the frequency.
    """
    return timedelta(seconds=1.0 / self._base_value)
zero classmethod ¤
zero() -> Self

Return a quantity with value 0.0.

RETURNS DESCRIPTION
Self

A quantity with value 0.0.

Source code in frequenz/quantities/_quantity.py
@classmethod
def zero(cls) -> Self:
    """Return a quantity with value 0.0.

    Returns:
        A quantity with value 0.0.
    """
    _zero = cls._zero_cache.get(cls, None)
    if _zero is None:
        _zero = cls.__new__(cls)
        _zero._base_value = 0.0
        cls._zero_cache[cls] = _zero
    assert isinstance(_zero, cls)
    return _zero

frequenz.quantities.Percentage ¤

Bases: Quantity

A percentage quantity.

Objects of this type are wrappers around float values and are immutable.

The constructors accept a single float value, the as_*() methods return a float value, and each of the arithmetic operators supported by this type are actually implemented using floating-point arithmetic.

So all considerations about floating-point arithmetic apply to this type as well.

Source code in frequenz/quantities/_percentage.py
class Percentage(
    Quantity,
    metaclass=NoDefaultConstructible,
    exponent_unit_map={0: "%"},
):
    """A percentage quantity.

    Objects of this type are wrappers around `float` values and are immutable.

    The constructors accept a single `float` value, the `as_*()` methods return a
    `float` value, and each of the arithmetic operators supported by this type are
    actually implemented using floating-point arithmetic.

    So all considerations about floating-point arithmetic apply to this type as well.
    """

    @classmethod
    def from_percent(cls, percent: float) -> Self:
        """Initialize a new percentage quantity from a percent value.

        Args:
            percent: The percent value, normally in the 0.0-100.0 range.

        Returns:
            A new percentage quantity.
        """
        return cls._new(percent)

    @classmethod
    def from_fraction(cls, fraction: float) -> Self:
        """Initialize a new percentage quantity from a fraction.

        Args:
            fraction: The fraction, normally in the 0.0-1.0 range.

        Returns:
            A new percentage quantity.
        """
        return cls._new(fraction * 100)

    def as_percent(self) -> float:
        """Return this quantity as a percentage.

        Returns:
            This quantity as a percentage.
        """
        return self._base_value

    def as_fraction(self) -> float:
        """Return this quantity as a fraction.

        Returns:
            This quantity as a fraction.
        """
        return self._base_value / 100
Attributes¤
base_unit property ¤
base_unit: str | None

Return the base unit of this quantity.

None if this quantity has no unit.

RETURNS DESCRIPTION
str | None

The base unit of this quantity.

base_value property ¤
base_value: float

Return the value of this quantity in the base unit.

RETURNS DESCRIPTION
float

The value of this quantity in the base unit.

Functions¤
__abs__ ¤
__abs__() -> Self

Return the absolute value of this quantity.

RETURNS DESCRIPTION
Self

The absolute value of this quantity.

Source code in frequenz/quantities/_quantity.py
def __abs__(self) -> Self:
    """Return the absolute value of this quantity.

    Returns:
        The absolute value of this quantity.
    """
    absolute = type(self).__new__(type(self))
    absolute._base_value = abs(self._base_value)
    return absolute
__add__ ¤
__add__(other: Self) -> Self

Return the sum of this quantity and another.

PARAMETER DESCRIPTION
other

The other quantity.

TYPE: Self

RETURNS DESCRIPTION
Self

The sum of this quantity and another.

Source code in frequenz/quantities/_quantity.py
def __add__(self, other: Self) -> Self:
    """Return the sum of this quantity and another.

    Args:
        other: The other quantity.

    Returns:
        The sum of this quantity and another.
    """
    if not type(other) is type(self):
        return NotImplemented
    summe = type(self).__new__(type(self))
    summe._base_value = self._base_value + other._base_value
    return summe
__eq__ ¤
__eq__(other: object) -> bool

Return whether this quantity is equal to another.

PARAMETER DESCRIPTION
other

The other quantity.

TYPE: object

RETURNS DESCRIPTION
bool

Whether this quantity is equal to another.

Source code in frequenz/quantities/_quantity.py
def __eq__(self, other: object) -> bool:
    """Return whether this quantity is equal to another.

    Args:
        other: The other quantity.

    Returns:
        Whether this quantity is equal to another.
    """
    if not type(other) is type(self):
        return NotImplemented
    # The above check ensures that both quantities are the exact same type, because
    # `isinstance` returns true for subclasses and superclasses.  But the above check
    # doesn't help mypy identify the type of other,  so the below line is necessary.
    assert isinstance(other, self.__class__)
    return self._base_value == other._base_value
__format__ ¤
__format__(__format_spec: str) -> str

Return a formatted string representation of this quantity.

If specified, must be of this form: [0].{precision}. If a 0 is not given, the trailing zeros will be omitted. If no precision is given, the default is 3.

The returned string will use the unit that will result in the maximum precision, based on the magnitude of the value.

Example
from frequenz.quantities import Current
c = Current.from_amperes(0.2345)
assert f"{c:.2}" == "234.5 mA"
c = Current.from_amperes(1.2345)
assert f"{c:.2}" == "1.23 A"
c = Current.from_milliamperes(1.2345)
assert f"{c:.6}" == "1.2345 mA"
PARAMETER DESCRIPTION
__format_spec

The format specifier.

TYPE: str

RETURNS DESCRIPTION
str

A string representation of this quantity.

RAISES DESCRIPTION
ValueError

If the given format specifier is invalid.

Source code in frequenz/quantities/_quantity.py
def __format__(self, __format_spec: str) -> str:
    """Return a formatted string representation of this quantity.

    If specified, must be of this form: `[0].{precision}`.  If a 0 is not given, the
    trailing zeros will be omitted.  If no precision is given, the default is 3.

    The returned string will use the unit that will result in the maximum precision,
    based on the magnitude of the value.

    Example:
        ```python
        from frequenz.quantities import Current
        c = Current.from_amperes(0.2345)
        assert f"{c:.2}" == "234.5 mA"
        c = Current.from_amperes(1.2345)
        assert f"{c:.2}" == "1.23 A"
        c = Current.from_milliamperes(1.2345)
        assert f"{c:.6}" == "1.2345 mA"
        ```

    Args:
        __format_spec: The format specifier.

    Returns:
        A string representation of this quantity.

    Raises:
        ValueError: If the given format specifier is invalid.
    """
    keep_trailing_zeros = False
    if __format_spec != "":
        fspec_parts = __format_spec.split(".")
        if (
            len(fspec_parts) != 2
            or fspec_parts[0] not in ("", "0")
            or not fspec_parts[1].isdigit()
        ):
            raise ValueError(
                "Invalid format specifier. Must be empty or `[0].{precision}`"
            )
        if fspec_parts[0] == "0":
            keep_trailing_zeros = True
        precision = int(fspec_parts[1])
    else:
        precision = 3
    if not self._exponent_unit_map:
        return f"{self._base_value:.{precision}f}"

    if math.isinf(self._base_value) or math.isnan(self._base_value):
        return f"{self._base_value} {self._exponent_unit_map[0]}"

    if abs_value := abs(self._base_value):
        precision_pow = 10 ** (precision)
        # Prevent numbers like 999.999999 being rendered as 1000 V
        # instead of 1 kV.
        # This could happen because the str formatting function does
        # rounding as well.
        # This is an imperfect solution that works for _most_ cases.
        # isclose parameters were chosen according to the observed cases
        if math.isclose(abs_value, precision_pow, abs_tol=1e-4, rel_tol=0.01):
            # If the value is close to the precision, round it
            exponent = math.ceil(math.log10(precision_pow))
        else:
            exponent = math.floor(math.log10(abs_value))
    else:
        exponent = 0

    unit_place = exponent - exponent % 3
    if unit_place < min(self._exponent_unit_map):
        unit = self._exponent_unit_map[min(self._exponent_unit_map.keys())]
        unit_place = min(self._exponent_unit_map)
    elif unit_place > max(self._exponent_unit_map):
        unit = self._exponent_unit_map[max(self._exponent_unit_map.keys())]
        unit_place = max(self._exponent_unit_map)
    else:
        unit = self._exponent_unit_map[unit_place]

    value_str = f"{self._base_value / 10 ** unit_place:.{precision}f}"

    if value_str in ("-0", "0"):
        stripped = value_str
    else:
        stripped = value_str.rstrip("0").rstrip(".")

    if not keep_trailing_zeros:
        value_str = stripped
    unit_str = unit if stripped not in ("-0", "0") else self._exponent_unit_map[0]
    return f"{value_str} {unit_str}"
__ge__ ¤
__ge__(other: Self) -> bool

Return whether this quantity is greater than or equal to another.

PARAMETER DESCRIPTION
other

The other quantity.

TYPE: Self

RETURNS DESCRIPTION
bool

Whether this quantity is greater than or equal to another.

Source code in frequenz/quantities/_quantity.py
def __ge__(self, other: Self) -> bool:
    """Return whether this quantity is greater than or equal to another.

    Args:
        other: The other quantity.

    Returns:
        Whether this quantity is greater than or equal to another.
    """
    if not type(other) is type(self):
        return NotImplemented
    return self._base_value >= other._base_value
__gt__ ¤
__gt__(other: Self) -> bool

Return whether this quantity is greater than another.

PARAMETER DESCRIPTION
other

The other quantity.

TYPE: Self

RETURNS DESCRIPTION
bool

Whether this quantity is greater than another.

Source code in frequenz/quantities/_quantity.py
def __gt__(self, other: Self) -> bool:
    """Return whether this quantity is greater than another.

    Args:
        other: The other quantity.

    Returns:
        Whether this quantity is greater than another.
    """
    if not type(other) is type(self):
        return NotImplemented
    return self._base_value > other._base_value
__init__ ¤
__init__(value: float, exponent: int = 0) -> None

Initialize a new quantity.

PARAMETER DESCRIPTION
value

The value of this quantity in a given exponent of the base unit.

TYPE: float

exponent

The exponent of the base unit the given value is in.

TYPE: int DEFAULT: 0

Source code in frequenz/quantities/_quantity.py
def __init__(self, value: float, exponent: int = 0) -> None:
    """Initialize a new quantity.

    Args:
        value: The value of this quantity in a given exponent of the base unit.
        exponent: The exponent of the base unit the given value is in.
    """
    self._base_value = value * 10.0**exponent
__init_subclass__ ¤
__init_subclass__(
    exponent_unit_map: dict[int, str]
) -> None

Initialize a new subclass of Quantity.

PARAMETER DESCRIPTION
exponent_unit_map

A mapping from the exponent of the base unit to the unit symbol.

TYPE: dict[int, str]

RAISES DESCRIPTION
ValueError

If the given exponent_unit_map does not contain a base unit (exponent 0).

Source code in frequenz/quantities/_quantity.py
def __init_subclass__(cls, exponent_unit_map: dict[int, str]) -> None:
    """Initialize a new subclass of Quantity.

    Args:
        exponent_unit_map: A mapping from the exponent of the base unit to the unit
            symbol.

    Raises:
        ValueError: If the given exponent_unit_map does not contain a base unit
            (exponent 0).
    """
    if 0 not in exponent_unit_map:
        raise ValueError("Expected a base unit for the type (for exponent 0)")
    cls._exponent_unit_map = exponent_unit_map
    super().__init_subclass__()
__le__ ¤
__le__(other: Self) -> bool

Return whether this quantity is less than or equal to another.

PARAMETER DESCRIPTION
other

The other quantity.

TYPE: Self

RETURNS DESCRIPTION
bool

Whether this quantity is less than or equal to another.

Source code in frequenz/quantities/_quantity.py
def __le__(self, other: Self) -> bool:
    """Return whether this quantity is less than or equal to another.

    Args:
        other: The other quantity.

    Returns:
        Whether this quantity is less than or equal to another.
    """
    if not type(other) is type(self):
        return NotImplemented
    return self._base_value <= other._base_value
__lt__ ¤
__lt__(other: Self) -> bool

Return whether this quantity is less than another.

PARAMETER DESCRIPTION
other

The other quantity.

TYPE: Self

RETURNS DESCRIPTION
bool

Whether this quantity is less than another.

Source code in frequenz/quantities/_quantity.py
def __lt__(self, other: Self) -> bool:
    """Return whether this quantity is less than another.

    Args:
        other: The other quantity.

    Returns:
        Whether this quantity is less than another.
    """
    if not type(other) is type(self):
        return NotImplemented
    return self._base_value < other._base_value
__mod__ ¤
__mod__(other: Self) -> Self

Return the remainder of this quantity and another.

PARAMETER DESCRIPTION
other

The other quantity.

TYPE: Self

RETURNS DESCRIPTION
Self

The remainder of this quantity and another.

Source code in frequenz/quantities/_quantity.py
def __mod__(self, other: Self) -> Self:
    """Return the remainder of this quantity and another.

    Args:
        other: The other quantity.

    Returns:
        The remainder of this quantity and another.
    """
    return self._new(self._base_value % other._base_value)
__mul__ ¤
__mul__(scalar: float) -> Self
__mul__(percent: Percentage) -> Self
__mul__(value: float | Percentage) -> Self

Scale this quantity by a scalar or percentage.

PARAMETER DESCRIPTION
value

The scalar or percentage by which to scale this quantity.

TYPE: float | Percentage

RETURNS DESCRIPTION
Self

The scaled quantity.

Source code in frequenz/quantities/_quantity.py
def __mul__(self, value: float | Percentage, /) -> Self:
    """Scale this quantity by a scalar or percentage.

    Args:
        value: The scalar or percentage by which to scale this quantity.

    Returns:
        The scaled quantity.
    """
    from ._percentage import Percentage  # pylint: disable=import-outside-toplevel

    match value:
        case float():
            return type(self)._new(self._base_value * value)
        case Percentage():
            return type(self)._new(self._base_value * value.as_fraction())
        case _:
            return NotImplemented
__neg__ ¤
__neg__() -> Self

Return the negation of this quantity.

RETURNS DESCRIPTION
Self

The negation of this quantity.

Source code in frequenz/quantities/_quantity.py
def __neg__(self) -> Self:
    """Return the negation of this quantity.

    Returns:
        The negation of this quantity.
    """
    negation = type(self).__new__(type(self))
    negation._base_value = -self._base_value
    return negation
__pos__ ¤
__pos__() -> Self

Return this quantity.

RETURNS DESCRIPTION
Self

This quantity.

Source code in frequenz/quantities/_quantity.py
def __pos__(self) -> Self:
    """Return this quantity.

    Returns:
        This quantity.
    """
    return self
__repr__ ¤
__repr__() -> str

Return a representation of this quantity.

RETURNS DESCRIPTION
str

A representation of this quantity.

Source code in frequenz/quantities/_quantity.py
def __repr__(self) -> str:
    """Return a representation of this quantity.

    Returns:
        A representation of this quantity.
    """
    return f"{type(self).__name__}(value={self._base_value}, exponent=0)"
__round__ ¤
__round__(ndigits: int | None = None) -> Self

Round this quantity to the given number of digits.

PARAMETER DESCRIPTION
ndigits

The number of digits to round to.

TYPE: int | None DEFAULT: None

RETURNS DESCRIPTION
Self

The rounded quantity.

Source code in frequenz/quantities/_quantity.py
def __round__(self, ndigits: int | None = None) -> Self:
    """Round this quantity to the given number of digits.

    Args:
        ndigits: The number of digits to round to.

    Returns:
        The rounded quantity.
    """
    return self._new(round(self._base_value, ndigits))
__str__ ¤
__str__() -> str

Return a string representation of this quantity.

RETURNS DESCRIPTION
str

A string representation of this quantity.

Source code in frequenz/quantities/_quantity.py
def __str__(self) -> str:
    """Return a string representation of this quantity.

    Returns:
        A string representation of this quantity.
    """
    return self.__format__("")
__sub__ ¤
__sub__(other: Self) -> Self

Return the difference of this quantity and another.

PARAMETER DESCRIPTION
other

The other quantity.

TYPE: Self

RETURNS DESCRIPTION
Self

The difference of this quantity and another.

Source code in frequenz/quantities/_quantity.py
def __sub__(self, other: Self) -> Self:
    """Return the difference of this quantity and another.

    Args:
        other: The other quantity.

    Returns:
        The difference of this quantity and another.
    """
    if not type(other) is type(self):
        return NotImplemented
    difference = type(self).__new__(type(self))
    difference._base_value = self._base_value - other._base_value
    return difference
__truediv__ ¤
__truediv__(other: float) -> Self
__truediv__(other: Self) -> float
__truediv__(value: float | Self) -> Self | float

Divide this quantity by a scalar or another quantity.

PARAMETER DESCRIPTION
value

The scalar or quantity to divide this quantity by.

TYPE: float | Self

RETURNS DESCRIPTION
Self | float

The divided quantity or the ratio of this quantity to another.

Source code in frequenz/quantities/_quantity.py
def __truediv__(self, value: float | Self, /) -> Self | float:
    """Divide this quantity by a scalar or another quantity.

    Args:
        value: The scalar or quantity to divide this quantity by.

    Returns:
        The divided quantity or the ratio of this quantity to another.
    """
    match value:
        case float():
            return type(self)._new(self._base_value / value)
        case Quantity() if type(value) is type(self):
            return self._base_value / value._base_value
        case _:
            return NotImplemented
as_fraction ¤
as_fraction() -> float

Return this quantity as a fraction.

RETURNS DESCRIPTION
float

This quantity as a fraction.

Source code in frequenz/quantities/_percentage.py
def as_fraction(self) -> float:
    """Return this quantity as a fraction.

    Returns:
        This quantity as a fraction.
    """
    return self._base_value / 100
as_percent ¤
as_percent() -> float

Return this quantity as a percentage.

RETURNS DESCRIPTION
float

This quantity as a percentage.

Source code in frequenz/quantities/_percentage.py
def as_percent(self) -> float:
    """Return this quantity as a percentage.

    Returns:
        This quantity as a percentage.
    """
    return self._base_value
from_fraction classmethod ¤
from_fraction(fraction: float) -> Self

Initialize a new percentage quantity from a fraction.

PARAMETER DESCRIPTION
fraction

The fraction, normally in the 0.0-1.0 range.

TYPE: float

RETURNS DESCRIPTION
Self

A new percentage quantity.

Source code in frequenz/quantities/_percentage.py
@classmethod
def from_fraction(cls, fraction: float) -> Self:
    """Initialize a new percentage quantity from a fraction.

    Args:
        fraction: The fraction, normally in the 0.0-1.0 range.

    Returns:
        A new percentage quantity.
    """
    return cls._new(fraction * 100)
from_percent classmethod ¤
from_percent(percent: float) -> Self

Initialize a new percentage quantity from a percent value.

PARAMETER DESCRIPTION
percent

The percent value, normally in the 0.0-100.0 range.

TYPE: float

RETURNS DESCRIPTION
Self

A new percentage quantity.

Source code in frequenz/quantities/_percentage.py
@classmethod
def from_percent(cls, percent: float) -> Self:
    """Initialize a new percentage quantity from a percent value.

    Args:
        percent: The percent value, normally in the 0.0-100.0 range.

    Returns:
        A new percentage quantity.
    """
    return cls._new(percent)
from_string classmethod ¤
from_string(string: str) -> Self

Return a quantity from a string representation.

PARAMETER DESCRIPTION
string

The string representation of the quantity.

TYPE: str

RETURNS DESCRIPTION
Self

A quantity object with the value given in the string.

RAISES DESCRIPTION
ValueError

If the string does not match the expected format.

Source code in frequenz/quantities/_quantity.py
@classmethod
def from_string(cls, string: str) -> Self:
    """Return a quantity from a string representation.

    Args:
        string: The string representation of the quantity.

    Returns:
        A quantity object with the value given in the string.

    Raises:
        ValueError: If the string does not match the expected format.

    """
    split_string = string.split(" ")

    if len(split_string) != 2:
        raise ValueError(
            f"Expected a string of the form 'value unit', got {string}"
        )

    assert cls._exponent_unit_map is not None
    exp_map = cls._exponent_unit_map

    for exponent, unit in exp_map.items():
        if unit == split_string[1]:
            instance = cls.__new__(cls)
            try:
                instance._base_value = float(split_string[0]) * 10**exponent
            except ValueError as error:
                raise ValueError(f"Failed to parse string '{string}'.") from error

            return instance

    raise ValueError(f"Unknown unit {split_string[1]}")
isclose ¤
isclose(
    other: Self,
    rel_tol: float = 1e-09,
    abs_tol: float = 0.0,
) -> bool

Return whether this quantity is close to another.

PARAMETER DESCRIPTION
other

The quantity to compare to.

TYPE: Self

rel_tol

The relative tolerance.

TYPE: float DEFAULT: 1e-09

abs_tol

The absolute tolerance.

TYPE: float DEFAULT: 0.0

RETURNS DESCRIPTION
bool

Whether this quantity is close to another.

Source code in frequenz/quantities/_quantity.py
def isclose(self, other: Self, rel_tol: float = 1e-9, abs_tol: float = 0.0) -> bool:
    """Return whether this quantity is close to another.

    Args:
        other: The quantity to compare to.
        rel_tol: The relative tolerance.
        abs_tol: The absolute tolerance.

    Returns:
        Whether this quantity is close to another.
    """
    return math.isclose(
        self._base_value,
        other._base_value,  # pylint: disable=protected-access
        rel_tol=rel_tol,
        abs_tol=abs_tol,
    )
isinf ¤
isinf() -> bool

Return whether this quantity is infinite.

RETURNS DESCRIPTION
bool

Whether this quantity is infinite.

Source code in frequenz/quantities/_quantity.py
def isinf(self) -> bool:
    """Return whether this quantity is infinite.

    Returns:
        Whether this quantity is infinite.
    """
    return math.isinf(self._base_value)
isnan ¤
isnan() -> bool

Return whether this quantity is NaN.

RETURNS DESCRIPTION
bool

Whether this quantity is NaN.

Source code in frequenz/quantities/_quantity.py
def isnan(self) -> bool:
    """Return whether this quantity is NaN.

    Returns:
        Whether this quantity is NaN.
    """
    return math.isnan(self._base_value)
zero classmethod ¤
zero() -> Self

Return a quantity with value 0.0.

RETURNS DESCRIPTION
Self

A quantity with value 0.0.

Source code in frequenz/quantities/_quantity.py
@classmethod
def zero(cls) -> Self:
    """Return a quantity with value 0.0.

    Returns:
        A quantity with value 0.0.
    """
    _zero = cls._zero_cache.get(cls, None)
    if _zero is None:
        _zero = cls.__new__(cls)
        _zero._base_value = 0.0
        cls._zero_cache[cls] = _zero
    assert isinstance(_zero, cls)
    return _zero

frequenz.quantities.Power ¤

Bases: Quantity

A power quantity.

Objects of this type are wrappers around float values and are immutable.

The constructors accept a single float value, the as_*() methods return a float value, and each of the arithmetic operators supported by this type are actually implemented using floating-point arithmetic.

So all considerations about floating-point arithmetic apply to this type as well.

Source code in frequenz/quantities/_power.py
class Power(
    Quantity,
    metaclass=NoDefaultConstructible,
    exponent_unit_map={
        -3: "mW",
        0: "W",
        3: "kW",
        6: "MW",
    },
):
    """A power quantity.

    Objects of this type are wrappers around `float` values and are immutable.

    The constructors accept a single `float` value, the `as_*()` methods return a
    `float` value, and each of the arithmetic operators supported by this type are
    actually implemented using floating-point arithmetic.

    So all considerations about floating-point arithmetic apply to this type as well.
    """

    @classmethod
    def from_watts(cls, watts: float) -> Self:
        """Initialize a new power quantity.

        Args:
            watts: The power in watts.

        Returns:
            A new power quantity.
        """
        return cls._new(watts)

    @classmethod
    def from_milliwatts(cls, milliwatts: float) -> Self:
        """Initialize a new power quantity.

        Args:
            milliwatts: The power in milliwatts.

        Returns:
            A new power quantity.
        """
        return cls._new(milliwatts, exponent=-3)

    @classmethod
    def from_kilowatts(cls, kilowatts: float) -> Self:
        """Initialize a new power quantity.

        Args:
            kilowatts: The power in kilowatts.

        Returns:
            A new power quantity.
        """
        return cls._new(kilowatts, exponent=3)

    @classmethod
    def from_megawatts(cls, megawatts: float) -> Self:
        """Initialize a new power quantity.

        Args:
            megawatts: The power in megawatts.

        Returns:
            A new power quantity.
        """
        return cls._new(megawatts, exponent=6)

    def as_watts(self) -> float:
        """Return the power in watts.

        Returns:
            The power in watts.
        """
        return self._base_value

    def as_kilowatts(self) -> float:
        """Return the power in kilowatts.

        Returns:
            The power in kilowatts.
        """
        return self._base_value / 1e3

    def as_megawatts(self) -> float:
        """Return the power in megawatts.

        Returns:
            The power in megawatts.
        """
        return self._base_value / 1e6

    # We need the ignore here because otherwise mypy will give this error:
    # > Overloaded operator methods can't have wider argument types in overrides
    # The problem seems to be when the other type implements an **incompatible**
    # __rmul__ method, which is not the case here, so we should be safe.
    # Please see this example:
    # https://github.com/python/mypy/blob/c26f1297d4f19d2d1124a30efc97caebb8c28616/test-data/unit/check-overloading.test#L4738C1-L4769C55
    # And a discussion in a mypy issue here:
    # https://github.com/python/mypy/issues/4985#issuecomment-389692396
    @overload  # type: ignore[override]
    def __mul__(self, scalar: float, /) -> Self:
        """Scale this power by a scalar.

        Args:
            scalar: The scalar by which to scale this power.

        Returns:
            The scaled power.
        """

    @overload
    def __mul__(self, percent: Percentage, /) -> Self:
        """Scale this power by a percentage.

        Args:
            percent: The percentage by which to scale this power.

        Returns:
            The scaled power.
        """

    @overload
    def __mul__(self, other: timedelta, /) -> Energy:
        """Return an energy from multiplying this power by the given duration.

        Args:
            other: The duration to multiply by.

        Returns:
            The calculated energy.
        """

    def __mul__(self, other: float | Percentage | timedelta, /) -> Self | Energy:
        """Return a power or energy from multiplying this power by the given value.

        Args:
            other: The scalar, percentage or duration to multiply by.

        Returns:
            A power or energy.
        """
        from ._energy import Energy  # pylint: disable=import-outside-toplevel
        from ._percentage import Percentage  # pylint: disable=import-outside-toplevel

        match other:
            case float() | Percentage():
                return super().__mul__(other)
            case timedelta():
                return Energy._new(self._base_value * other.total_seconds() / 3600.0)
            case _:
                return NotImplemented

    # See the comment for Power.__mul__ for why we need the ignore here.
    @overload  # type: ignore[override]
    def __truediv__(self, other: float, /) -> Self:
        """Divide this power by a scalar.

        Args:
            other: The scalar to divide this power by.

        Returns:
            The divided power.
        """

    @overload
    def __truediv__(self, other: Self, /) -> float:
        """Return the ratio of this power to another.

        Args:
            other: The other power.

        Returns:
            The ratio of this power to another.
        """

    @overload
    def __truediv__(self, current: Current, /) -> Voltage:
        """Return a voltage from dividing this power by the given current.

        Args:
            current: The current to divide by.

        Returns:
            A voltage from dividing this power by the a current.
        """

    @overload
    def __truediv__(self, voltage: Voltage, /) -> Current:
        """Return a current from dividing this power by the given voltage.

        Args:
            voltage: The voltage to divide by.

        Returns:
            A current from dividing this power by a voltage.
        """

    def __truediv__(
        self, other: float | Self | Current | Voltage, /
    ) -> Self | float | Voltage | Current:
        """Return a current or voltage from dividing this power by the given value.

        Args:
            other: The scalar, power, current or voltage to divide by.

        Returns:
            A current or voltage from dividing this power by the given value.
        """
        from ._current import Current  # pylint: disable=import-outside-toplevel
        from ._voltage import Voltage  # pylint: disable=import-outside-toplevel

        match other:
            case float():
                return super().__truediv__(other)
            case Power():
                return self._base_value / other._base_value
            case Current():
                return Voltage._new(self._base_value / other._base_value)
            case Voltage():
                return Current._new(self._base_value / other._base_value)
            case _:
                return NotImplemented
Attributes¤
base_unit property ¤
base_unit: str | None

Return the base unit of this quantity.

None if this quantity has no unit.

RETURNS DESCRIPTION
str | None

The base unit of this quantity.

base_value property ¤
base_value: float

Return the value of this quantity in the base unit.

RETURNS DESCRIPTION
float

The value of this quantity in the base unit.

Functions¤
__abs__ ¤
__abs__() -> Self

Return the absolute value of this quantity.

RETURNS DESCRIPTION
Self

The absolute value of this quantity.

Source code in frequenz/quantities/_quantity.py
def __abs__(self) -> Self:
    """Return the absolute value of this quantity.

    Returns:
        The absolute value of this quantity.
    """
    absolute = type(self).__new__(type(self))
    absolute._base_value = abs(self._base_value)
    return absolute
__add__ ¤
__add__(other: Self) -> Self

Return the sum of this quantity and another.

PARAMETER DESCRIPTION
other

The other quantity.

TYPE: Self

RETURNS DESCRIPTION
Self

The sum of this quantity and another.

Source code in frequenz/quantities/_quantity.py
def __add__(self, other: Self) -> Self:
    """Return the sum of this quantity and another.

    Args:
        other: The other quantity.

    Returns:
        The sum of this quantity and another.
    """
    if not type(other) is type(self):
        return NotImplemented
    summe = type(self).__new__(type(self))
    summe._base_value = self._base_value + other._base_value
    return summe
__eq__ ¤
__eq__(other: object) -> bool

Return whether this quantity is equal to another.

PARAMETER DESCRIPTION
other

The other quantity.

TYPE: object

RETURNS DESCRIPTION
bool

Whether this quantity is equal to another.

Source code in frequenz/quantities/_quantity.py
def __eq__(self, other: object) -> bool:
    """Return whether this quantity is equal to another.

    Args:
        other: The other quantity.

    Returns:
        Whether this quantity is equal to another.
    """
    if not type(other) is type(self):
        return NotImplemented
    # The above check ensures that both quantities are the exact same type, because
    # `isinstance` returns true for subclasses and superclasses.  But the above check
    # doesn't help mypy identify the type of other,  so the below line is necessary.
    assert isinstance(other, self.__class__)
    return self._base_value == other._base_value
__format__ ¤
__format__(__format_spec: str) -> str

Return a formatted string representation of this quantity.

If specified, must be of this form: [0].{precision}. If a 0 is not given, the trailing zeros will be omitted. If no precision is given, the default is 3.

The returned string will use the unit that will result in the maximum precision, based on the magnitude of the value.

Example
from frequenz.quantities import Current
c = Current.from_amperes(0.2345)
assert f"{c:.2}" == "234.5 mA"
c = Current.from_amperes(1.2345)
assert f"{c:.2}" == "1.23 A"
c = Current.from_milliamperes(1.2345)
assert f"{c:.6}" == "1.2345 mA"
PARAMETER DESCRIPTION
__format_spec

The format specifier.

TYPE: str

RETURNS DESCRIPTION
str

A string representation of this quantity.

RAISES DESCRIPTION
ValueError

If the given format specifier is invalid.

Source code in frequenz/quantities/_quantity.py
def __format__(self, __format_spec: str) -> str:
    """Return a formatted string representation of this quantity.

    If specified, must be of this form: `[0].{precision}`.  If a 0 is not given, the
    trailing zeros will be omitted.  If no precision is given, the default is 3.

    The returned string will use the unit that will result in the maximum precision,
    based on the magnitude of the value.

    Example:
        ```python
        from frequenz.quantities import Current
        c = Current.from_amperes(0.2345)
        assert f"{c:.2}" == "234.5 mA"
        c = Current.from_amperes(1.2345)
        assert f"{c:.2}" == "1.23 A"
        c = Current.from_milliamperes(1.2345)
        assert f"{c:.6}" == "1.2345 mA"
        ```

    Args:
        __format_spec: The format specifier.

    Returns:
        A string representation of this quantity.

    Raises:
        ValueError: If the given format specifier is invalid.
    """
    keep_trailing_zeros = False
    if __format_spec != "":
        fspec_parts = __format_spec.split(".")
        if (
            len(fspec_parts) != 2
            or fspec_parts[0] not in ("", "0")
            or not fspec_parts[1].isdigit()
        ):
            raise ValueError(
                "Invalid format specifier. Must be empty or `[0].{precision}`"
            )
        if fspec_parts[0] == "0":
            keep_trailing_zeros = True
        precision = int(fspec_parts[1])
    else:
        precision = 3
    if not self._exponent_unit_map:
        return f"{self._base_value:.{precision}f}"

    if math.isinf(self._base_value) or math.isnan(self._base_value):
        return f"{self._base_value} {self._exponent_unit_map[0]}"

    if abs_value := abs(self._base_value):
        precision_pow = 10 ** (precision)
        # Prevent numbers like 999.999999 being rendered as 1000 V
        # instead of 1 kV.
        # This could happen because the str formatting function does
        # rounding as well.
        # This is an imperfect solution that works for _most_ cases.
        # isclose parameters were chosen according to the observed cases
        if math.isclose(abs_value, precision_pow, abs_tol=1e-4, rel_tol=0.01):
            # If the value is close to the precision, round it
            exponent = math.ceil(math.log10(precision_pow))
        else:
            exponent = math.floor(math.log10(abs_value))
    else:
        exponent = 0

    unit_place = exponent - exponent % 3
    if unit_place < min(self._exponent_unit_map):
        unit = self._exponent_unit_map[min(self._exponent_unit_map.keys())]
        unit_place = min(self._exponent_unit_map)
    elif unit_place > max(self._exponent_unit_map):
        unit = self._exponent_unit_map[max(self._exponent_unit_map.keys())]
        unit_place = max(self._exponent_unit_map)
    else:
        unit = self._exponent_unit_map[unit_place]

    value_str = f"{self._base_value / 10 ** unit_place:.{precision}f}"

    if value_str in ("-0", "0"):
        stripped = value_str
    else:
        stripped = value_str.rstrip("0").rstrip(".")

    if not keep_trailing_zeros:
        value_str = stripped
    unit_str = unit if stripped not in ("-0", "0") else self._exponent_unit_map[0]
    return f"{value_str} {unit_str}"
__ge__ ¤
__ge__(other: Self) -> bool

Return whether this quantity is greater than or equal to another.

PARAMETER DESCRIPTION
other

The other quantity.

TYPE: Self

RETURNS DESCRIPTION
bool

Whether this quantity is greater than or equal to another.

Source code in frequenz/quantities/_quantity.py
def __ge__(self, other: Self) -> bool:
    """Return whether this quantity is greater than or equal to another.

    Args:
        other: The other quantity.

    Returns:
        Whether this quantity is greater than or equal to another.
    """
    if not type(other) is type(self):
        return NotImplemented
    return self._base_value >= other._base_value
__gt__ ¤
__gt__(other: Self) -> bool

Return whether this quantity is greater than another.

PARAMETER DESCRIPTION
other

The other quantity.

TYPE: Self

RETURNS DESCRIPTION
bool

Whether this quantity is greater than another.

Source code in frequenz/quantities/_quantity.py
def __gt__(self, other: Self) -> bool:
    """Return whether this quantity is greater than another.

    Args:
        other: The other quantity.

    Returns:
        Whether this quantity is greater than another.
    """
    if not type(other) is type(self):
        return NotImplemented
    return self._base_value > other._base_value
__init__ ¤
__init__(value: float, exponent: int = 0) -> None

Initialize a new quantity.

PARAMETER DESCRIPTION
value

The value of this quantity in a given exponent of the base unit.

TYPE: float

exponent

The exponent of the base unit the given value is in.

TYPE: int DEFAULT: 0

Source code in frequenz/quantities/_quantity.py
def __init__(self, value: float, exponent: int = 0) -> None:
    """Initialize a new quantity.

    Args:
        value: The value of this quantity in a given exponent of the base unit.
        exponent: The exponent of the base unit the given value is in.
    """
    self._base_value = value * 10.0**exponent
__init_subclass__ ¤
__init_subclass__(
    exponent_unit_map: dict[int, str]
) -> None

Initialize a new subclass of Quantity.

PARAMETER DESCRIPTION
exponent_unit_map

A mapping from the exponent of the base unit to the unit symbol.

TYPE: dict[int, str]

RAISES DESCRIPTION
ValueError

If the given exponent_unit_map does not contain a base unit (exponent 0).

Source code in frequenz/quantities/_quantity.py
def __init_subclass__(cls, exponent_unit_map: dict[int, str]) -> None:
    """Initialize a new subclass of Quantity.

    Args:
        exponent_unit_map: A mapping from the exponent of the base unit to the unit
            symbol.

    Raises:
        ValueError: If the given exponent_unit_map does not contain a base unit
            (exponent 0).
    """
    if 0 not in exponent_unit_map:
        raise ValueError("Expected a base unit for the type (for exponent 0)")
    cls._exponent_unit_map = exponent_unit_map
    super().__init_subclass__()
__le__ ¤
__le__(other: Self) -> bool

Return whether this quantity is less than or equal to another.

PARAMETER DESCRIPTION
other

The other quantity.

TYPE: Self

RETURNS DESCRIPTION
bool

Whether this quantity is less than or equal to another.

Source code in frequenz/quantities/_quantity.py
def __le__(self, other: Self) -> bool:
    """Return whether this quantity is less than or equal to another.

    Args:
        other: The other quantity.

    Returns:
        Whether this quantity is less than or equal to another.
    """
    if not type(other) is type(self):
        return NotImplemented
    return self._base_value <= other._base_value
__lt__ ¤
__lt__(other: Self) -> bool

Return whether this quantity is less than another.

PARAMETER DESCRIPTION
other

The other quantity.

TYPE: Self

RETURNS DESCRIPTION
bool

Whether this quantity is less than another.

Source code in frequenz/quantities/_quantity.py
def __lt__(self, other: Self) -> bool:
    """Return whether this quantity is less than another.

    Args:
        other: The other quantity.

    Returns:
        Whether this quantity is less than another.
    """
    if not type(other) is type(self):
        return NotImplemented
    return self._base_value < other._base_value
__mod__ ¤
__mod__(other: Self) -> Self

Return the remainder of this quantity and another.

PARAMETER DESCRIPTION
other

The other quantity.

TYPE: Self

RETURNS DESCRIPTION
Self

The remainder of this quantity and another.

Source code in frequenz/quantities/_quantity.py
def __mod__(self, other: Self) -> Self:
    """Return the remainder of this quantity and another.

    Args:
        other: The other quantity.

    Returns:
        The remainder of this quantity and another.
    """
    return self._new(self._base_value % other._base_value)
__mul__ ¤
__mul__(scalar: float) -> Self
__mul__(percent: Percentage) -> Self
__mul__(other: timedelta) -> Energy
__mul__(
    other: float | Percentage | timedelta,
) -> Self | Energy

Return a power or energy from multiplying this power by the given value.

PARAMETER DESCRIPTION
other

The scalar, percentage or duration to multiply by.

TYPE: float | Percentage | timedelta

RETURNS DESCRIPTION
Self | Energy

A power or energy.

Source code in frequenz/quantities/_power.py
def __mul__(self, other: float | Percentage | timedelta, /) -> Self | Energy:
    """Return a power or energy from multiplying this power by the given value.

    Args:
        other: The scalar, percentage or duration to multiply by.

    Returns:
        A power or energy.
    """
    from ._energy import Energy  # pylint: disable=import-outside-toplevel
    from ._percentage import Percentage  # pylint: disable=import-outside-toplevel

    match other:
        case float() | Percentage():
            return super().__mul__(other)
        case timedelta():
            return Energy._new(self._base_value * other.total_seconds() / 3600.0)
        case _:
            return NotImplemented
__neg__ ¤
__neg__() -> Self

Return the negation of this quantity.

RETURNS DESCRIPTION
Self

The negation of this quantity.

Source code in frequenz/quantities/_quantity.py
def __neg__(self) -> Self:
    """Return the negation of this quantity.

    Returns:
        The negation of this quantity.
    """
    negation = type(self).__new__(type(self))
    negation._base_value = -self._base_value
    return negation
__pos__ ¤
__pos__() -> Self

Return this quantity.

RETURNS DESCRIPTION
Self

This quantity.

Source code in frequenz/quantities/_quantity.py
def __pos__(self) -> Self:
    """Return this quantity.

    Returns:
        This quantity.
    """
    return self
__repr__ ¤
__repr__() -> str

Return a representation of this quantity.

RETURNS DESCRIPTION
str

A representation of this quantity.

Source code in frequenz/quantities/_quantity.py
def __repr__(self) -> str:
    """Return a representation of this quantity.

    Returns:
        A representation of this quantity.
    """
    return f"{type(self).__name__}(value={self._base_value}, exponent=0)"
__round__ ¤
__round__(ndigits: int | None = None) -> Self

Round this quantity to the given number of digits.

PARAMETER DESCRIPTION
ndigits

The number of digits to round to.

TYPE: int | None DEFAULT: None

RETURNS DESCRIPTION
Self

The rounded quantity.

Source code in frequenz/quantities/_quantity.py
def __round__(self, ndigits: int | None = None) -> Self:
    """Round this quantity to the given number of digits.

    Args:
        ndigits: The number of digits to round to.

    Returns:
        The rounded quantity.
    """
    return self._new(round(self._base_value, ndigits))
__str__ ¤
__str__() -> str

Return a string representation of this quantity.

RETURNS DESCRIPTION
str

A string representation of this quantity.

Source code in frequenz/quantities/_quantity.py
def __str__(self) -> str:
    """Return a string representation of this quantity.

    Returns:
        A string representation of this quantity.
    """
    return self.__format__("")
__sub__ ¤
__sub__(other: Self) -> Self

Return the difference of this quantity and another.

PARAMETER DESCRIPTION
other

The other quantity.

TYPE: Self

RETURNS DESCRIPTION
Self

The difference of this quantity and another.

Source code in frequenz/quantities/_quantity.py
def __sub__(self, other: Self) -> Self:
    """Return the difference of this quantity and another.

    Args:
        other: The other quantity.

    Returns:
        The difference of this quantity and another.
    """
    if not type(other) is type(self):
        return NotImplemented
    difference = type(self).__new__(type(self))
    difference._base_value = self._base_value - other._base_value
    return difference
__truediv__ ¤
__truediv__(other: float) -> Self
__truediv__(other: Self) -> float
__truediv__(current: Current) -> Voltage
__truediv__(voltage: Voltage) -> Current
__truediv__(
    other: float | Self | Current | Voltage,
) -> Self | float | Voltage | Current

Return a current or voltage from dividing this power by the given value.

PARAMETER DESCRIPTION
other

The scalar, power, current or voltage to divide by.

TYPE: float | Self | Current | Voltage

RETURNS DESCRIPTION
Self | float | Voltage | Current

A current or voltage from dividing this power by the given value.

Source code in frequenz/quantities/_power.py
def __truediv__(
    self, other: float | Self | Current | Voltage, /
) -> Self | float | Voltage | Current:
    """Return a current or voltage from dividing this power by the given value.

    Args:
        other: The scalar, power, current or voltage to divide by.

    Returns:
        A current or voltage from dividing this power by the given value.
    """
    from ._current import Current  # pylint: disable=import-outside-toplevel
    from ._voltage import Voltage  # pylint: disable=import-outside-toplevel

    match other:
        case float():
            return super().__truediv__(other)
        case Power():
            return self._base_value / other._base_value
        case Current():
            return Voltage._new(self._base_value / other._base_value)
        case Voltage():
            return Current._new(self._base_value / other._base_value)
        case _:
            return NotImplemented
as_kilowatts ¤
as_kilowatts() -> float

Return the power in kilowatts.

RETURNS DESCRIPTION
float

The power in kilowatts.

Source code in frequenz/quantities/_power.py
def as_kilowatts(self) -> float:
    """Return the power in kilowatts.

    Returns:
        The power in kilowatts.
    """
    return self._base_value / 1e3
as_megawatts ¤
as_megawatts() -> float

Return the power in megawatts.

RETURNS DESCRIPTION
float

The power in megawatts.

Source code in frequenz/quantities/_power.py
def as_megawatts(self) -> float:
    """Return the power in megawatts.

    Returns:
        The power in megawatts.
    """
    return self._base_value / 1e6
as_watts ¤
as_watts() -> float

Return the power in watts.

RETURNS DESCRIPTION
float

The power in watts.

Source code in frequenz/quantities/_power.py
def as_watts(self) -> float:
    """Return the power in watts.

    Returns:
        The power in watts.
    """
    return self._base_value
from_kilowatts classmethod ¤
from_kilowatts(kilowatts: float) -> Self

Initialize a new power quantity.

PARAMETER DESCRIPTION
kilowatts

The power in kilowatts.

TYPE: float

RETURNS DESCRIPTION
Self

A new power quantity.

Source code in frequenz/quantities/_power.py
@classmethod
def from_kilowatts(cls, kilowatts: float) -> Self:
    """Initialize a new power quantity.

    Args:
        kilowatts: The power in kilowatts.

    Returns:
        A new power quantity.
    """
    return cls._new(kilowatts, exponent=3)
from_megawatts classmethod ¤
from_megawatts(megawatts: float) -> Self

Initialize a new power quantity.

PARAMETER DESCRIPTION
megawatts

The power in megawatts.

TYPE: float

RETURNS DESCRIPTION
Self

A new power quantity.

Source code in frequenz/quantities/_power.py
@classmethod
def from_megawatts(cls, megawatts: float) -> Self:
    """Initialize a new power quantity.

    Args:
        megawatts: The power in megawatts.

    Returns:
        A new power quantity.
    """
    return cls._new(megawatts, exponent=6)
from_milliwatts classmethod ¤
from_milliwatts(milliwatts: float) -> Self

Initialize a new power quantity.

PARAMETER DESCRIPTION
milliwatts

The power in milliwatts.

TYPE: float

RETURNS DESCRIPTION
Self

A new power quantity.

Source code in frequenz/quantities/_power.py
@classmethod
def from_milliwatts(cls, milliwatts: float) -> Self:
    """Initialize a new power quantity.

    Args:
        milliwatts: The power in milliwatts.

    Returns:
        A new power quantity.
    """
    return cls._new(milliwatts, exponent=-3)
from_string classmethod ¤
from_string(string: str) -> Self

Return a quantity from a string representation.

PARAMETER DESCRIPTION
string

The string representation of the quantity.

TYPE: str

RETURNS DESCRIPTION
Self

A quantity object with the value given in the string.

RAISES DESCRIPTION
ValueError

If the string does not match the expected format.

Source code in frequenz/quantities/_quantity.py
@classmethod
def from_string(cls, string: str) -> Self:
    """Return a quantity from a string representation.

    Args:
        string: The string representation of the quantity.

    Returns:
        A quantity object with the value given in the string.

    Raises:
        ValueError: If the string does not match the expected format.

    """
    split_string = string.split(" ")

    if len(split_string) != 2:
        raise ValueError(
            f"Expected a string of the form 'value unit', got {string}"
        )

    assert cls._exponent_unit_map is not None
    exp_map = cls._exponent_unit_map

    for exponent, unit in exp_map.items():
        if unit == split_string[1]:
            instance = cls.__new__(cls)
            try:
                instance._base_value = float(split_string[0]) * 10**exponent
            except ValueError as error:
                raise ValueError(f"Failed to parse string '{string}'.") from error

            return instance

    raise ValueError(f"Unknown unit {split_string[1]}")
from_watts classmethod ¤
from_watts(watts: float) -> Self

Initialize a new power quantity.

PARAMETER DESCRIPTION
watts

The power in watts.

TYPE: float

RETURNS DESCRIPTION
Self

A new power quantity.

Source code in frequenz/quantities/_power.py
@classmethod
def from_watts(cls, watts: float) -> Self:
    """Initialize a new power quantity.

    Args:
        watts: The power in watts.

    Returns:
        A new power quantity.
    """
    return cls._new(watts)
isclose ¤
isclose(
    other: Self,
    rel_tol: float = 1e-09,
    abs_tol: float = 0.0,
) -> bool

Return whether this quantity is close to another.

PARAMETER DESCRIPTION
other

The quantity to compare to.

TYPE: Self

rel_tol

The relative tolerance.

TYPE: float DEFAULT: 1e-09

abs_tol

The absolute tolerance.

TYPE: float DEFAULT: 0.0

RETURNS DESCRIPTION
bool

Whether this quantity is close to another.

Source code in frequenz/quantities/_quantity.py
def isclose(self, other: Self, rel_tol: float = 1e-9, abs_tol: float = 0.0) -> bool:
    """Return whether this quantity is close to another.

    Args:
        other: The quantity to compare to.
        rel_tol: The relative tolerance.
        abs_tol: The absolute tolerance.

    Returns:
        Whether this quantity is close to another.
    """
    return math.isclose(
        self._base_value,
        other._base_value,  # pylint: disable=protected-access
        rel_tol=rel_tol,
        abs_tol=abs_tol,
    )
isinf ¤
isinf() -> bool

Return whether this quantity is infinite.

RETURNS DESCRIPTION
bool

Whether this quantity is infinite.

Source code in frequenz/quantities/_quantity.py
def isinf(self) -> bool:
    """Return whether this quantity is infinite.

    Returns:
        Whether this quantity is infinite.
    """
    return math.isinf(self._base_value)
isnan ¤
isnan() -> bool

Return whether this quantity is NaN.

RETURNS DESCRIPTION
bool

Whether this quantity is NaN.

Source code in frequenz/quantities/_quantity.py
def isnan(self) -> bool:
    """Return whether this quantity is NaN.

    Returns:
        Whether this quantity is NaN.
    """
    return math.isnan(self._base_value)
zero classmethod ¤
zero() -> Self

Return a quantity with value 0.0.

RETURNS DESCRIPTION
Self

A quantity with value 0.0.

Source code in frequenz/quantities/_quantity.py
@classmethod
def zero(cls) -> Self:
    """Return a quantity with value 0.0.

    Returns:
        A quantity with value 0.0.
    """
    _zero = cls._zero_cache.get(cls, None)
    if _zero is None:
        _zero = cls.__new__(cls)
        _zero._base_value = 0.0
        cls._zero_cache[cls] = _zero
    assert isinstance(_zero, cls)
    return _zero

frequenz.quantities.Quantity ¤

A quantity with a unit.

Quantities try to behave like float and are also immutable.

Source code in frequenz/quantities/_quantity.py
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
class Quantity:
    """A quantity with a unit.

    Quantities try to behave like float and are also immutable.
    """

    _base_value: float
    """The value of this quantity in the base unit."""

    _exponent_unit_map: dict[int, str] | None = None
    """A mapping from the exponent of the base unit to the unit symbol.

    If None, this quantity has no unit.  None is possible only when using the base
    class.  Sub-classes must define this.
    """

    def __init__(self, value: float, exponent: int = 0) -> None:
        """Initialize a new quantity.

        Args:
            value: The value of this quantity in a given exponent of the base unit.
            exponent: The exponent of the base unit the given value is in.
        """
        self._base_value = value * 10.0**exponent

    @classmethod
    def _new(cls, value: float, *, exponent: int = 0) -> Self:
        """Instantiate a new quantity subclass instance.

        Args:
            value: The value of this quantity in a given exponent of the base unit.
            exponent: The exponent of the base unit the given value is in.

        Returns:
            A new quantity subclass instance.
        """
        self = cls.__new__(cls)
        self._base_value = value * 10.0**exponent
        return self

    def __init_subclass__(cls, exponent_unit_map: dict[int, str]) -> None:
        """Initialize a new subclass of Quantity.

        Args:
            exponent_unit_map: A mapping from the exponent of the base unit to the unit
                symbol.

        Raises:
            ValueError: If the given exponent_unit_map does not contain a base unit
                (exponent 0).
        """
        if 0 not in exponent_unit_map:
            raise ValueError("Expected a base unit for the type (for exponent 0)")
        cls._exponent_unit_map = exponent_unit_map
        super().__init_subclass__()

    _zero_cache: dict[type, Quantity] = {}
    """Cache for zero singletons.

    This is a workaround for mypy getting confused when using @functools.cache and
    @classmethod combined with returning Self. It believes the resulting type of this
    method is Self and complains that members of the actual class don't exist in Self,
    so we need to implement the cache ourselves.
    """

    @classmethod
    def zero(cls) -> Self:
        """Return a quantity with value 0.0.

        Returns:
            A quantity with value 0.0.
        """
        _zero = cls._zero_cache.get(cls, None)
        if _zero is None:
            _zero = cls.__new__(cls)
            _zero._base_value = 0.0
            cls._zero_cache[cls] = _zero
        assert isinstance(_zero, cls)
        return _zero

    @classmethod
    def from_string(cls, string: str) -> Self:
        """Return a quantity from a string representation.

        Args:
            string: The string representation of the quantity.

        Returns:
            A quantity object with the value given in the string.

        Raises:
            ValueError: If the string does not match the expected format.

        """
        split_string = string.split(" ")

        if len(split_string) != 2:
            raise ValueError(
                f"Expected a string of the form 'value unit', got {string}"
            )

        assert cls._exponent_unit_map is not None
        exp_map = cls._exponent_unit_map

        for exponent, unit in exp_map.items():
            if unit == split_string[1]:
                instance = cls.__new__(cls)
                try:
                    instance._base_value = float(split_string[0]) * 10**exponent
                except ValueError as error:
                    raise ValueError(f"Failed to parse string '{string}'.") from error

                return instance

        raise ValueError(f"Unknown unit {split_string[1]}")

    @property
    def base_value(self) -> float:
        """Return the value of this quantity in the base unit.

        Returns:
            The value of this quantity in the base unit.
        """
        return self._base_value

    def __round__(self, ndigits: int | None = None) -> Self:
        """Round this quantity to the given number of digits.

        Args:
            ndigits: The number of digits to round to.

        Returns:
            The rounded quantity.
        """
        return self._new(round(self._base_value, ndigits))

    def __pos__(self) -> Self:
        """Return this quantity.

        Returns:
            This quantity.
        """
        return self

    def __mod__(self, other: Self) -> Self:
        """Return the remainder of this quantity and another.

        Args:
            other: The other quantity.

        Returns:
            The remainder of this quantity and another.
        """
        return self._new(self._base_value % other._base_value)

    @property
    def base_unit(self) -> str | None:
        """Return the base unit of this quantity.

        None if this quantity has no unit.

        Returns:
            The base unit of this quantity.
        """
        if not self._exponent_unit_map:
            return None
        return self._exponent_unit_map[0]

    def isnan(self) -> bool:
        """Return whether this quantity is NaN.

        Returns:
            Whether this quantity is NaN.
        """
        return math.isnan(self._base_value)

    def isinf(self) -> bool:
        """Return whether this quantity is infinite.

        Returns:
            Whether this quantity is infinite.
        """
        return math.isinf(self._base_value)

    def isclose(self, other: Self, rel_tol: float = 1e-9, abs_tol: float = 0.0) -> bool:
        """Return whether this quantity is close to another.

        Args:
            other: The quantity to compare to.
            rel_tol: The relative tolerance.
            abs_tol: The absolute tolerance.

        Returns:
            Whether this quantity is close to another.
        """
        return math.isclose(
            self._base_value,
            other._base_value,  # pylint: disable=protected-access
            rel_tol=rel_tol,
            abs_tol=abs_tol,
        )

    def __repr__(self) -> str:
        """Return a representation of this quantity.

        Returns:
            A representation of this quantity.
        """
        return f"{type(self).__name__}(value={self._base_value}, exponent=0)"

    def __str__(self) -> str:
        """Return a string representation of this quantity.

        Returns:
            A string representation of this quantity.
        """
        return self.__format__("")

    # pylint: disable=too-many-branches
    def __format__(self, __format_spec: str) -> str:
        """Return a formatted string representation of this quantity.

        If specified, must be of this form: `[0].{precision}`.  If a 0 is not given, the
        trailing zeros will be omitted.  If no precision is given, the default is 3.

        The returned string will use the unit that will result in the maximum precision,
        based on the magnitude of the value.

        Example:
            ```python
            from frequenz.quantities import Current
            c = Current.from_amperes(0.2345)
            assert f"{c:.2}" == "234.5 mA"
            c = Current.from_amperes(1.2345)
            assert f"{c:.2}" == "1.23 A"
            c = Current.from_milliamperes(1.2345)
            assert f"{c:.6}" == "1.2345 mA"
            ```

        Args:
            __format_spec: The format specifier.

        Returns:
            A string representation of this quantity.

        Raises:
            ValueError: If the given format specifier is invalid.
        """
        keep_trailing_zeros = False
        if __format_spec != "":
            fspec_parts = __format_spec.split(".")
            if (
                len(fspec_parts) != 2
                or fspec_parts[0] not in ("", "0")
                or not fspec_parts[1].isdigit()
            ):
                raise ValueError(
                    "Invalid format specifier. Must be empty or `[0].{precision}`"
                )
            if fspec_parts[0] == "0":
                keep_trailing_zeros = True
            precision = int(fspec_parts[1])
        else:
            precision = 3
        if not self._exponent_unit_map:
            return f"{self._base_value:.{precision}f}"

        if math.isinf(self._base_value) or math.isnan(self._base_value):
            return f"{self._base_value} {self._exponent_unit_map[0]}"

        if abs_value := abs(self._base_value):
            precision_pow = 10 ** (precision)
            # Prevent numbers like 999.999999 being rendered as 1000 V
            # instead of 1 kV.
            # This could happen because the str formatting function does
            # rounding as well.
            # This is an imperfect solution that works for _most_ cases.
            # isclose parameters were chosen according to the observed cases
            if math.isclose(abs_value, precision_pow, abs_tol=1e-4, rel_tol=0.01):
                # If the value is close to the precision, round it
                exponent = math.ceil(math.log10(precision_pow))
            else:
                exponent = math.floor(math.log10(abs_value))
        else:
            exponent = 0

        unit_place = exponent - exponent % 3
        if unit_place < min(self._exponent_unit_map):
            unit = self._exponent_unit_map[min(self._exponent_unit_map.keys())]
            unit_place = min(self._exponent_unit_map)
        elif unit_place > max(self._exponent_unit_map):
            unit = self._exponent_unit_map[max(self._exponent_unit_map.keys())]
            unit_place = max(self._exponent_unit_map)
        else:
            unit = self._exponent_unit_map[unit_place]

        value_str = f"{self._base_value / 10 ** unit_place:.{precision}f}"

        if value_str in ("-0", "0"):
            stripped = value_str
        else:
            stripped = value_str.rstrip("0").rstrip(".")

        if not keep_trailing_zeros:
            value_str = stripped
        unit_str = unit if stripped not in ("-0", "0") else self._exponent_unit_map[0]
        return f"{value_str} {unit_str}"

    def __add__(self, other: Self) -> Self:
        """Return the sum of this quantity and another.

        Args:
            other: The other quantity.

        Returns:
            The sum of this quantity and another.
        """
        if not type(other) is type(self):
            return NotImplemented
        summe = type(self).__new__(type(self))
        summe._base_value = self._base_value + other._base_value
        return summe

    def __sub__(self, other: Self) -> Self:
        """Return the difference of this quantity and another.

        Args:
            other: The other quantity.

        Returns:
            The difference of this quantity and another.
        """
        if not type(other) is type(self):
            return NotImplemented
        difference = type(self).__new__(type(self))
        difference._base_value = self._base_value - other._base_value
        return difference

    @overload
    def __mul__(self, scalar: float, /) -> Self:
        """Scale this quantity by a scalar.

        Args:
            scalar: The scalar by which to scale this quantity.

        Returns:
            The scaled quantity.
        """

    @overload
    def __mul__(self, percent: Percentage, /) -> Self:
        """Scale this quantity by a percentage.

        Args:
            percent: The percentage by which to scale this quantity.

        Returns:
            The scaled quantity.
        """

    def __mul__(self, value: float | Percentage, /) -> Self:
        """Scale this quantity by a scalar or percentage.

        Args:
            value: The scalar or percentage by which to scale this quantity.

        Returns:
            The scaled quantity.
        """
        from ._percentage import Percentage  # pylint: disable=import-outside-toplevel

        match value:
            case float():
                return type(self)._new(self._base_value * value)
            case Percentage():
                return type(self)._new(self._base_value * value.as_fraction())
            case _:
                return NotImplemented

    @overload
    def __truediv__(self, other: float, /) -> Self:
        """Divide this quantity by a scalar.

        Args:
            other: The scalar or percentage to divide this quantity by.

        Returns:
            The divided quantity.
        """

    @overload
    def __truediv__(self, other: Self, /) -> float:
        """Return the ratio of this quantity to another.

        Args:
            other: The other quantity.

        Returns:
            The ratio of this quantity to another.
        """

    def __truediv__(self, value: float | Self, /) -> Self | float:
        """Divide this quantity by a scalar or another quantity.

        Args:
            value: The scalar or quantity to divide this quantity by.

        Returns:
            The divided quantity or the ratio of this quantity to another.
        """
        match value:
            case float():
                return type(self)._new(self._base_value / value)
            case Quantity() if type(value) is type(self):
                return self._base_value / value._base_value
            case _:
                return NotImplemented

    def __gt__(self, other: Self) -> bool:
        """Return whether this quantity is greater than another.

        Args:
            other: The other quantity.

        Returns:
            Whether this quantity is greater than another.
        """
        if not type(other) is type(self):
            return NotImplemented
        return self._base_value > other._base_value

    def __ge__(self, other: Self) -> bool:
        """Return whether this quantity is greater than or equal to another.

        Args:
            other: The other quantity.

        Returns:
            Whether this quantity is greater than or equal to another.
        """
        if not type(other) is type(self):
            return NotImplemented
        return self._base_value >= other._base_value

    def __lt__(self, other: Self) -> bool:
        """Return whether this quantity is less than another.

        Args:
            other: The other quantity.

        Returns:
            Whether this quantity is less than another.
        """
        if not type(other) is type(self):
            return NotImplemented
        return self._base_value < other._base_value

    def __le__(self, other: Self) -> bool:
        """Return whether this quantity is less than or equal to another.

        Args:
            other: The other quantity.

        Returns:
            Whether this quantity is less than or equal to another.
        """
        if not type(other) is type(self):
            return NotImplemented
        return self._base_value <= other._base_value

    def __eq__(self, other: object) -> bool:
        """Return whether this quantity is equal to another.

        Args:
            other: The other quantity.

        Returns:
            Whether this quantity is equal to another.
        """
        if not type(other) is type(self):
            return NotImplemented
        # The above check ensures that both quantities are the exact same type, because
        # `isinstance` returns true for subclasses and superclasses.  But the above check
        # doesn't help mypy identify the type of other,  so the below line is necessary.
        assert isinstance(other, self.__class__)
        return self._base_value == other._base_value

    def __neg__(self) -> Self:
        """Return the negation of this quantity.

        Returns:
            The negation of this quantity.
        """
        negation = type(self).__new__(type(self))
        negation._base_value = -self._base_value
        return negation

    def __abs__(self) -> Self:
        """Return the absolute value of this quantity.

        Returns:
            The absolute value of this quantity.
        """
        absolute = type(self).__new__(type(self))
        absolute._base_value = abs(self._base_value)
        return absolute
Attributes¤
base_unit property ¤
base_unit: str | None

Return the base unit of this quantity.

None if this quantity has no unit.

RETURNS DESCRIPTION
str | None

The base unit of this quantity.

base_value property ¤
base_value: float

Return the value of this quantity in the base unit.

RETURNS DESCRIPTION
float

The value of this quantity in the base unit.

Functions¤
__abs__ ¤
__abs__() -> Self

Return the absolute value of this quantity.

RETURNS DESCRIPTION
Self

The absolute value of this quantity.

Source code in frequenz/quantities/_quantity.py
def __abs__(self) -> Self:
    """Return the absolute value of this quantity.

    Returns:
        The absolute value of this quantity.
    """
    absolute = type(self).__new__(type(self))
    absolute._base_value = abs(self._base_value)
    return absolute
__add__ ¤
__add__(other: Self) -> Self

Return the sum of this quantity and another.

PARAMETER DESCRIPTION
other

The other quantity.

TYPE: Self

RETURNS DESCRIPTION
Self

The sum of this quantity and another.

Source code in frequenz/quantities/_quantity.py
def __add__(self, other: Self) -> Self:
    """Return the sum of this quantity and another.

    Args:
        other: The other quantity.

    Returns:
        The sum of this quantity and another.
    """
    if not type(other) is type(self):
        return NotImplemented
    summe = type(self).__new__(type(self))
    summe._base_value = self._base_value + other._base_value
    return summe
__eq__ ¤
__eq__(other: object) -> bool

Return whether this quantity is equal to another.

PARAMETER DESCRIPTION
other

The other quantity.

TYPE: object

RETURNS DESCRIPTION
bool

Whether this quantity is equal to another.

Source code in frequenz/quantities/_quantity.py
def __eq__(self, other: object) -> bool:
    """Return whether this quantity is equal to another.

    Args:
        other: The other quantity.

    Returns:
        Whether this quantity is equal to another.
    """
    if not type(other) is type(self):
        return NotImplemented
    # The above check ensures that both quantities are the exact same type, because
    # `isinstance` returns true for subclasses and superclasses.  But the above check
    # doesn't help mypy identify the type of other,  so the below line is necessary.
    assert isinstance(other, self.__class__)
    return self._base_value == other._base_value
__format__ ¤
__format__(__format_spec: str) -> str

Return a formatted string representation of this quantity.

If specified, must be of this form: [0].{precision}. If a 0 is not given, the trailing zeros will be omitted. If no precision is given, the default is 3.

The returned string will use the unit that will result in the maximum precision, based on the magnitude of the value.

Example
from frequenz.quantities import Current
c = Current.from_amperes(0.2345)
assert f"{c:.2}" == "234.5 mA"
c = Current.from_amperes(1.2345)
assert f"{c:.2}" == "1.23 A"
c = Current.from_milliamperes(1.2345)
assert f"{c:.6}" == "1.2345 mA"
PARAMETER DESCRIPTION
__format_spec

The format specifier.

TYPE: str

RETURNS DESCRIPTION
str

A string representation of this quantity.

RAISES DESCRIPTION
ValueError

If the given format specifier is invalid.

Source code in frequenz/quantities/_quantity.py
def __format__(self, __format_spec: str) -> str:
    """Return a formatted string representation of this quantity.

    If specified, must be of this form: `[0].{precision}`.  If a 0 is not given, the
    trailing zeros will be omitted.  If no precision is given, the default is 3.

    The returned string will use the unit that will result in the maximum precision,
    based on the magnitude of the value.

    Example:
        ```python
        from frequenz.quantities import Current
        c = Current.from_amperes(0.2345)
        assert f"{c:.2}" == "234.5 mA"
        c = Current.from_amperes(1.2345)
        assert f"{c:.2}" == "1.23 A"
        c = Current.from_milliamperes(1.2345)
        assert f"{c:.6}" == "1.2345 mA"
        ```

    Args:
        __format_spec: The format specifier.

    Returns:
        A string representation of this quantity.

    Raises:
        ValueError: If the given format specifier is invalid.
    """
    keep_trailing_zeros = False
    if __format_spec != "":
        fspec_parts = __format_spec.split(".")
        if (
            len(fspec_parts) != 2
            or fspec_parts[0] not in ("", "0")
            or not fspec_parts[1].isdigit()
        ):
            raise ValueError(
                "Invalid format specifier. Must be empty or `[0].{precision}`"
            )
        if fspec_parts[0] == "0":
            keep_trailing_zeros = True
        precision = int(fspec_parts[1])
    else:
        precision = 3
    if not self._exponent_unit_map:
        return f"{self._base_value:.{precision}f}"

    if math.isinf(self._base_value) or math.isnan(self._base_value):
        return f"{self._base_value} {self._exponent_unit_map[0]}"

    if abs_value := abs(self._base_value):
        precision_pow = 10 ** (precision)
        # Prevent numbers like 999.999999 being rendered as 1000 V
        # instead of 1 kV.
        # This could happen because the str formatting function does
        # rounding as well.
        # This is an imperfect solution that works for _most_ cases.
        # isclose parameters were chosen according to the observed cases
        if math.isclose(abs_value, precision_pow, abs_tol=1e-4, rel_tol=0.01):
            # If the value is close to the precision, round it
            exponent = math.ceil(math.log10(precision_pow))
        else:
            exponent = math.floor(math.log10(abs_value))
    else:
        exponent = 0

    unit_place = exponent - exponent % 3
    if unit_place < min(self._exponent_unit_map):
        unit = self._exponent_unit_map[min(self._exponent_unit_map.keys())]
        unit_place = min(self._exponent_unit_map)
    elif unit_place > max(self._exponent_unit_map):
        unit = self._exponent_unit_map[max(self._exponent_unit_map.keys())]
        unit_place = max(self._exponent_unit_map)
    else:
        unit = self._exponent_unit_map[unit_place]

    value_str = f"{self._base_value / 10 ** unit_place:.{precision}f}"

    if value_str in ("-0", "0"):
        stripped = value_str
    else:
        stripped = value_str.rstrip("0").rstrip(".")

    if not keep_trailing_zeros:
        value_str = stripped
    unit_str = unit if stripped not in ("-0", "0") else self._exponent_unit_map[0]
    return f"{value_str} {unit_str}"
__ge__ ¤
__ge__(other: Self) -> bool

Return whether this quantity is greater than or equal to another.

PARAMETER DESCRIPTION
other

The other quantity.

TYPE: Self

RETURNS DESCRIPTION
bool

Whether this quantity is greater than or equal to another.

Source code in frequenz/quantities/_quantity.py
def __ge__(self, other: Self) -> bool:
    """Return whether this quantity is greater than or equal to another.

    Args:
        other: The other quantity.

    Returns:
        Whether this quantity is greater than or equal to another.
    """
    if not type(other) is type(self):
        return NotImplemented
    return self._base_value >= other._base_value
__gt__ ¤
__gt__(other: Self) -> bool

Return whether this quantity is greater than another.

PARAMETER DESCRIPTION
other

The other quantity.

TYPE: Self

RETURNS DESCRIPTION
bool

Whether this quantity is greater than another.

Source code in frequenz/quantities/_quantity.py
def __gt__(self, other: Self) -> bool:
    """Return whether this quantity is greater than another.

    Args:
        other: The other quantity.

    Returns:
        Whether this quantity is greater than another.
    """
    if not type(other) is type(self):
        return NotImplemented
    return self._base_value > other._base_value
__init__ ¤
__init__(value: float, exponent: int = 0) -> None

Initialize a new quantity.

PARAMETER DESCRIPTION
value

The value of this quantity in a given exponent of the base unit.

TYPE: float

exponent

The exponent of the base unit the given value is in.

TYPE: int DEFAULT: 0

Source code in frequenz/quantities/_quantity.py
def __init__(self, value: float, exponent: int = 0) -> None:
    """Initialize a new quantity.

    Args:
        value: The value of this quantity in a given exponent of the base unit.
        exponent: The exponent of the base unit the given value is in.
    """
    self._base_value = value * 10.0**exponent
__init_subclass__ ¤
__init_subclass__(
    exponent_unit_map: dict[int, str]
) -> None

Initialize a new subclass of Quantity.

PARAMETER DESCRIPTION
exponent_unit_map

A mapping from the exponent of the base unit to the unit symbol.

TYPE: dict[int, str]

RAISES DESCRIPTION
ValueError

If the given exponent_unit_map does not contain a base unit (exponent 0).

Source code in frequenz/quantities/_quantity.py
def __init_subclass__(cls, exponent_unit_map: dict[int, str]) -> None:
    """Initialize a new subclass of Quantity.

    Args:
        exponent_unit_map: A mapping from the exponent of the base unit to the unit
            symbol.

    Raises:
        ValueError: If the given exponent_unit_map does not contain a base unit
            (exponent 0).
    """
    if 0 not in exponent_unit_map:
        raise ValueError("Expected a base unit for the type (for exponent 0)")
    cls._exponent_unit_map = exponent_unit_map
    super().__init_subclass__()
__le__ ¤
__le__(other: Self) -> bool

Return whether this quantity is less than or equal to another.

PARAMETER DESCRIPTION
other

The other quantity.

TYPE: Self

RETURNS DESCRIPTION
bool

Whether this quantity is less than or equal to another.

Source code in frequenz/quantities/_quantity.py
def __le__(self, other: Self) -> bool:
    """Return whether this quantity is less than or equal to another.

    Args:
        other: The other quantity.

    Returns:
        Whether this quantity is less than or equal to another.
    """
    if not type(other) is type(self):
        return NotImplemented
    return self._base_value <= other._base_value
__lt__ ¤
__lt__(other: Self) -> bool

Return whether this quantity is less than another.

PARAMETER DESCRIPTION
other

The other quantity.

TYPE: Self

RETURNS DESCRIPTION
bool

Whether this quantity is less than another.

Source code in frequenz/quantities/_quantity.py
def __lt__(self, other: Self) -> bool:
    """Return whether this quantity is less than another.

    Args:
        other: The other quantity.

    Returns:
        Whether this quantity is less than another.
    """
    if not type(other) is type(self):
        return NotImplemented
    return self._base_value < other._base_value
__mod__ ¤
__mod__(other: Self) -> Self

Return the remainder of this quantity and another.

PARAMETER DESCRIPTION
other

The other quantity.

TYPE: Self

RETURNS DESCRIPTION
Self

The remainder of this quantity and another.

Source code in frequenz/quantities/_quantity.py
def __mod__(self, other: Self) -> Self:
    """Return the remainder of this quantity and another.

    Args:
        other: The other quantity.

    Returns:
        The remainder of this quantity and another.
    """
    return self._new(self._base_value % other._base_value)
__mul__ ¤
__mul__(scalar: float) -> Self
__mul__(percent: Percentage) -> Self
__mul__(value: float | Percentage) -> Self

Scale this quantity by a scalar or percentage.

PARAMETER DESCRIPTION
value

The scalar or percentage by which to scale this quantity.

TYPE: float | Percentage

RETURNS DESCRIPTION
Self

The scaled quantity.

Source code in frequenz/quantities/_quantity.py
def __mul__(self, value: float | Percentage, /) -> Self:
    """Scale this quantity by a scalar or percentage.

    Args:
        value: The scalar or percentage by which to scale this quantity.

    Returns:
        The scaled quantity.
    """
    from ._percentage import Percentage  # pylint: disable=import-outside-toplevel

    match value:
        case float():
            return type(self)._new(self._base_value * value)
        case Percentage():
            return type(self)._new(self._base_value * value.as_fraction())
        case _:
            return NotImplemented
__neg__ ¤
__neg__() -> Self

Return the negation of this quantity.

RETURNS DESCRIPTION
Self

The negation of this quantity.

Source code in frequenz/quantities/_quantity.py
def __neg__(self) -> Self:
    """Return the negation of this quantity.

    Returns:
        The negation of this quantity.
    """
    negation = type(self).__new__(type(self))
    negation._base_value = -self._base_value
    return negation
__pos__ ¤
__pos__() -> Self

Return this quantity.

RETURNS DESCRIPTION
Self

This quantity.

Source code in frequenz/quantities/_quantity.py
def __pos__(self) -> Self:
    """Return this quantity.

    Returns:
        This quantity.
    """
    return self
__repr__ ¤
__repr__() -> str

Return a representation of this quantity.

RETURNS DESCRIPTION
str

A representation of this quantity.

Source code in frequenz/quantities/_quantity.py
def __repr__(self) -> str:
    """Return a representation of this quantity.

    Returns:
        A representation of this quantity.
    """
    return f"{type(self).__name__}(value={self._base_value}, exponent=0)"
__round__ ¤
__round__(ndigits: int | None = None) -> Self

Round this quantity to the given number of digits.

PARAMETER DESCRIPTION
ndigits

The number of digits to round to.

TYPE: int | None DEFAULT: None

RETURNS DESCRIPTION
Self

The rounded quantity.

Source code in frequenz/quantities/_quantity.py
def __round__(self, ndigits: int | None = None) -> Self:
    """Round this quantity to the given number of digits.

    Args:
        ndigits: The number of digits to round to.

    Returns:
        The rounded quantity.
    """
    return self._new(round(self._base_value, ndigits))
__str__ ¤
__str__() -> str

Return a string representation of this quantity.

RETURNS DESCRIPTION
str

A string representation of this quantity.

Source code in frequenz/quantities/_quantity.py
def __str__(self) -> str:
    """Return a string representation of this quantity.

    Returns:
        A string representation of this quantity.
    """
    return self.__format__("")
__sub__ ¤
__sub__(other: Self) -> Self

Return the difference of this quantity and another.

PARAMETER DESCRIPTION
other

The other quantity.

TYPE: Self

RETURNS DESCRIPTION
Self

The difference of this quantity and another.

Source code in frequenz/quantities/_quantity.py
def __sub__(self, other: Self) -> Self:
    """Return the difference of this quantity and another.

    Args:
        other: The other quantity.

    Returns:
        The difference of this quantity and another.
    """
    if not type(other) is type(self):
        return NotImplemented
    difference = type(self).__new__(type(self))
    difference._base_value = self._base_value - other._base_value
    return difference
__truediv__ ¤
__truediv__(other: float) -> Self
__truediv__(other: Self) -> float
__truediv__(value: float | Self) -> Self | float

Divide this quantity by a scalar or another quantity.

PARAMETER DESCRIPTION
value

The scalar or quantity to divide this quantity by.

TYPE: float | Self

RETURNS DESCRIPTION
Self | float

The divided quantity or the ratio of this quantity to another.

Source code in frequenz/quantities/_quantity.py
def __truediv__(self, value: float | Self, /) -> Self | float:
    """Divide this quantity by a scalar or another quantity.

    Args:
        value: The scalar or quantity to divide this quantity by.

    Returns:
        The divided quantity or the ratio of this quantity to another.
    """
    match value:
        case float():
            return type(self)._new(self._base_value / value)
        case Quantity() if type(value) is type(self):
            return self._base_value / value._base_value
        case _:
            return NotImplemented
from_string classmethod ¤
from_string(string: str) -> Self

Return a quantity from a string representation.

PARAMETER DESCRIPTION
string

The string representation of the quantity.

TYPE: str

RETURNS DESCRIPTION
Self

A quantity object with the value given in the string.

RAISES DESCRIPTION
ValueError

If the string does not match the expected format.

Source code in frequenz/quantities/_quantity.py
@classmethod
def from_string(cls, string: str) -> Self:
    """Return a quantity from a string representation.

    Args:
        string: The string representation of the quantity.

    Returns:
        A quantity object with the value given in the string.

    Raises:
        ValueError: If the string does not match the expected format.

    """
    split_string = string.split(" ")

    if len(split_string) != 2:
        raise ValueError(
            f"Expected a string of the form 'value unit', got {string}"
        )

    assert cls._exponent_unit_map is not None
    exp_map = cls._exponent_unit_map

    for exponent, unit in exp_map.items():
        if unit == split_string[1]:
            instance = cls.__new__(cls)
            try:
                instance._base_value = float(split_string[0]) * 10**exponent
            except ValueError as error:
                raise ValueError(f"Failed to parse string '{string}'.") from error

            return instance

    raise ValueError(f"Unknown unit {split_string[1]}")
isclose ¤
isclose(
    other: Self,
    rel_tol: float = 1e-09,
    abs_tol: float = 0.0,
) -> bool

Return whether this quantity is close to another.

PARAMETER DESCRIPTION
other

The quantity to compare to.

TYPE: Self

rel_tol

The relative tolerance.

TYPE: float DEFAULT: 1e-09

abs_tol

The absolute tolerance.

TYPE: float DEFAULT: 0.0

RETURNS DESCRIPTION
bool

Whether this quantity is close to another.

Source code in frequenz/quantities/_quantity.py
def isclose(self, other: Self, rel_tol: float = 1e-9, abs_tol: float = 0.0) -> bool:
    """Return whether this quantity is close to another.

    Args:
        other: The quantity to compare to.
        rel_tol: The relative tolerance.
        abs_tol: The absolute tolerance.

    Returns:
        Whether this quantity is close to another.
    """
    return math.isclose(
        self._base_value,
        other._base_value,  # pylint: disable=protected-access
        rel_tol=rel_tol,
        abs_tol=abs_tol,
    )
isinf ¤
isinf() -> bool

Return whether this quantity is infinite.

RETURNS DESCRIPTION
bool

Whether this quantity is infinite.

Source code in frequenz/quantities/_quantity.py
def isinf(self) -> bool:
    """Return whether this quantity is infinite.

    Returns:
        Whether this quantity is infinite.
    """
    return math.isinf(self._base_value)
isnan ¤
isnan() -> bool

Return whether this quantity is NaN.

RETURNS DESCRIPTION
bool

Whether this quantity is NaN.

Source code in frequenz/quantities/_quantity.py
def isnan(self) -> bool:
    """Return whether this quantity is NaN.

    Returns:
        Whether this quantity is NaN.
    """
    return math.isnan(self._base_value)
zero classmethod ¤
zero() -> Self

Return a quantity with value 0.0.

RETURNS DESCRIPTION
Self

A quantity with value 0.0.

Source code in frequenz/quantities/_quantity.py
@classmethod
def zero(cls) -> Self:
    """Return a quantity with value 0.0.

    Returns:
        A quantity with value 0.0.
    """
    _zero = cls._zero_cache.get(cls, None)
    if _zero is None:
        _zero = cls.__new__(cls)
        _zero._base_value = 0.0
        cls._zero_cache[cls] = _zero
    assert isinstance(_zero, cls)
    return _zero

frequenz.quantities.ReactivePower ¤

Bases: Quantity

A reactive power quantity.

Objects of this type are wrappers around float values and are immutable.

The constructors accept a single float value, the as_*() methods return a float value, and each of the arithmetic operators supported by this type are actually implemented using floating-point arithmetic.

So all considerations about floating-point arithmetic apply to this type as well.

Source code in frequenz/quantities/_reactive_power.py
class ReactivePower(
    Quantity,
    metaclass=NoDefaultConstructible,
    exponent_unit_map={
        -3: "mVAR",
        0: "VAR",
        3: "kVAR",
        6: "MVAR",
    },
):
    """A reactive power quantity.

    Objects of this type are wrappers around `float` values and are immutable.

    The constructors accept a single `float` value, the `as_*()` methods return a
    `float` value, and each of the arithmetic operators supported by this type are
    actually implemented using floating-point arithmetic.

    So all considerations about floating-point arithmetic apply to this type as well.
    """

    @classmethod
    def from_volt_amperes_reactive(cls, value: float) -> Self:
        """Initialize a new reactive power quantity.

        Args:
            value: The reactive power in volt-amperes reactive (VAR).

        Returns:
            A new reactive power quantity.
        """
        return cls._new(value)

    @classmethod
    def from_milli_volt_amperes_reactive(cls, mvars: float) -> Self:
        """Initialize a new reactive power quantity.

        Args:
            mvars: The reactive power in millivolt-amperes reactive (mVAR).

        Returns:
            A new reactive power quantity.
        """
        return cls._new(mvars, exponent=-3)

    @classmethod
    def from_kilo_volt_amperes_reactive(cls, kvars: float) -> Self:
        """Initialize a new reactive power quantity.

        Args:
            kvars: The reactive power in kilovolt-amperes reactive (kVAR).

        Returns:
            A new reactive power quantity.
        """
        return cls._new(kvars, exponent=3)

    @classmethod
    def from_mega_volt_amperes_reactive(cls, mvars: float) -> Self:
        """Initialize a new reactive power quantity.

        Args:
            mvars: The reactive power in megavolt-amperes reactive (MVAR).

        Returns:
            A new reactive power quantity.
        """
        return cls._new(mvars, exponent=6)

    def as_volt_amperes_reactive(self) -> float:
        """Return the reactive power in volt-amperes reactive (VAR).

        Returns:
            The reactive power in volt-amperes reactive (VAR).
        """
        return self._base_value

    def as_milli_volt_amperes_reactive(self) -> float:
        """Return the reactive power in millivolt-amperes reactive (mVAR).

        Returns:
            The reactive power in millivolt-amperes reactive (mVAR).
        """
        return self._base_value * 1e3

    def as_kilo_volt_amperes_reactive(self) -> float:
        """Return the reactive power in kilovolt-amperes reactive (kVAR).

        Returns:
            The reactive power in kilovolt-amperes reactive (kVAR).
        """
        return self._base_value / 1e3

    def as_mega_volt_amperes_reactive(self) -> float:
        """Return the reactive power in megavolt-amperes reactive (MVAR).

        Returns:
            The reactive power in megavolt-amperes reactive (MVAR).
        """
        return self._base_value / 1e6

    @overload
    def __mul__(self, scalar: float, /) -> Self:
        """Scale this power by a scalar.

        Args:
            scalar: The scalar by which to scale this power.

        Returns:
            The scaled power.
        """

    @overload
    def __mul__(self, percent: Percentage, /) -> Self:
        """Scale this power by a percentage.

        Args:
            percent: The percentage by which to scale this power.

        Returns:
            The scaled power.
        """

    def __mul__(self, other: float | Percentage, /) -> Self:
        """Return a power or energy from multiplying this power by the given value.

        Args:
            other: The scalar, percentage or duration to multiply by.

        Returns:
            A power or energy.
        """
        from ._percentage import Percentage  # pylint: disable=import-outside-toplevel

        match other:
            case float() | Percentage():
                return super().__mul__(other)
            case _:
                return NotImplemented

    # We need the ignore here because otherwise mypy will give this error:
    # > Overloaded operator methods can't have wider argument types in overrides
    # The problem seems to be when the other type implements an **incompatible**
    # __rmul__ method, which is not the case here, so we should be safe.
    # Please see this example:
    # https://github.com/python/mypy/blob/c26f1297d4f19d2d1124a30efc97caebb8c28616/test-data/unit/check-overloading.test#L4738C1-L4769C55
    # And a discussion in a mypy issue here:
    # https://github.com/python/mypy/issues/4985#issuecomment-389692396
    @overload  # type: ignore[override]
    def __truediv__(self, other: float, /) -> Self:
        """Divide this power by a scalar.

        Args:
            other: The scalar to divide this power by.

        Returns:
            The divided power.
        """

    @overload
    def __truediv__(self, other: Self, /) -> float:
        """Return the ratio of this power to another.

        Args:
            other: The other power.

        Returns:
            The ratio of this power to another.
        """

    @overload
    def __truediv__(self, current: Current, /) -> Voltage:
        """Return a voltage from dividing this power by the given current.

        Args:
            current: The current to divide by.

        Returns:
            A voltage from dividing this power by the a current.
        """

    @overload
    def __truediv__(self, voltage: Voltage, /) -> Current:
        """Return a current from dividing this power by the given voltage.

        Args:
            voltage: The voltage to divide by.

        Returns:
            A current from dividing this power by a voltage.
        """

    def __truediv__(
        self, other: float | Self | Current | Voltage, /
    ) -> Self | float | Voltage | Current:
        """Return a current or voltage from dividing this power by the given value.

        Args:
            other: The scalar, power, current or voltage to divide by.

        Returns:
            A current or voltage from dividing this power by the given value.
        """
        from ._current import Current  # pylint: disable=import-outside-toplevel
        from ._voltage import Voltage  # pylint: disable=import-outside-toplevel

        match other:
            case float():
                return super().__truediv__(other)
            case ReactivePower():
                return self._base_value / other._base_value
            case Current():
                return Voltage._new(self._base_value / other._base_value)
            case Voltage():
                return Current._new(self._base_value / other._base_value)
            case _:
                return NotImplemented
Attributes¤
base_unit property ¤
base_unit: str | None

Return the base unit of this quantity.

None if this quantity has no unit.

RETURNS DESCRIPTION
str | None

The base unit of this quantity.

base_value property ¤
base_value: float

Return the value of this quantity in the base unit.

RETURNS DESCRIPTION
float

The value of this quantity in the base unit.

Functions¤
__abs__ ¤
__abs__() -> Self

Return the absolute value of this quantity.

RETURNS DESCRIPTION
Self

The absolute value of this quantity.

Source code in frequenz/quantities/_quantity.py
def __abs__(self) -> Self:
    """Return the absolute value of this quantity.

    Returns:
        The absolute value of this quantity.
    """
    absolute = type(self).__new__(type(self))
    absolute._base_value = abs(self._base_value)
    return absolute
__add__ ¤
__add__(other: Self) -> Self

Return the sum of this quantity and another.

PARAMETER DESCRIPTION
other

The other quantity.

TYPE: Self

RETURNS DESCRIPTION
Self

The sum of this quantity and another.

Source code in frequenz/quantities/_quantity.py
def __add__(self, other: Self) -> Self:
    """Return the sum of this quantity and another.

    Args:
        other: The other quantity.

    Returns:
        The sum of this quantity and another.
    """
    if not type(other) is type(self):
        return NotImplemented
    summe = type(self).__new__(type(self))
    summe._base_value = self._base_value + other._base_value
    return summe
__eq__ ¤
__eq__(other: object) -> bool

Return whether this quantity is equal to another.

PARAMETER DESCRIPTION
other

The other quantity.

TYPE: object

RETURNS DESCRIPTION
bool

Whether this quantity is equal to another.

Source code in frequenz/quantities/_quantity.py
def __eq__(self, other: object) -> bool:
    """Return whether this quantity is equal to another.

    Args:
        other: The other quantity.

    Returns:
        Whether this quantity is equal to another.
    """
    if not type(other) is type(self):
        return NotImplemented
    # The above check ensures that both quantities are the exact same type, because
    # `isinstance` returns true for subclasses and superclasses.  But the above check
    # doesn't help mypy identify the type of other,  so the below line is necessary.
    assert isinstance(other, self.__class__)
    return self._base_value == other._base_value
__format__ ¤
__format__(__format_spec: str) -> str

Return a formatted string representation of this quantity.

If specified, must be of this form: [0].{precision}. If a 0 is not given, the trailing zeros will be omitted. If no precision is given, the default is 3.

The returned string will use the unit that will result in the maximum precision, based on the magnitude of the value.

Example
from frequenz.quantities import Current
c = Current.from_amperes(0.2345)
assert f"{c:.2}" == "234.5 mA"
c = Current.from_amperes(1.2345)
assert f"{c:.2}" == "1.23 A"
c = Current.from_milliamperes(1.2345)
assert f"{c:.6}" == "1.2345 mA"
PARAMETER DESCRIPTION
__format_spec

The format specifier.

TYPE: str

RETURNS DESCRIPTION
str

A string representation of this quantity.

RAISES DESCRIPTION
ValueError

If the given format specifier is invalid.

Source code in frequenz/quantities/_quantity.py
def __format__(self, __format_spec: str) -> str:
    """Return a formatted string representation of this quantity.

    If specified, must be of this form: `[0].{precision}`.  If a 0 is not given, the
    trailing zeros will be omitted.  If no precision is given, the default is 3.

    The returned string will use the unit that will result in the maximum precision,
    based on the magnitude of the value.

    Example:
        ```python
        from frequenz.quantities import Current
        c = Current.from_amperes(0.2345)
        assert f"{c:.2}" == "234.5 mA"
        c = Current.from_amperes(1.2345)
        assert f"{c:.2}" == "1.23 A"
        c = Current.from_milliamperes(1.2345)
        assert f"{c:.6}" == "1.2345 mA"
        ```

    Args:
        __format_spec: The format specifier.

    Returns:
        A string representation of this quantity.

    Raises:
        ValueError: If the given format specifier is invalid.
    """
    keep_trailing_zeros = False
    if __format_spec != "":
        fspec_parts = __format_spec.split(".")
        if (
            len(fspec_parts) != 2
            or fspec_parts[0] not in ("", "0")
            or not fspec_parts[1].isdigit()
        ):
            raise ValueError(
                "Invalid format specifier. Must be empty or `[0].{precision}`"
            )
        if fspec_parts[0] == "0":
            keep_trailing_zeros = True
        precision = int(fspec_parts[1])
    else:
        precision = 3
    if not self._exponent_unit_map:
        return f"{self._base_value:.{precision}f}"

    if math.isinf(self._base_value) or math.isnan(self._base_value):
        return f"{self._base_value} {self._exponent_unit_map[0]}"

    if abs_value := abs(self._base_value):
        precision_pow = 10 ** (precision)
        # Prevent numbers like 999.999999 being rendered as 1000 V
        # instead of 1 kV.
        # This could happen because the str formatting function does
        # rounding as well.
        # This is an imperfect solution that works for _most_ cases.
        # isclose parameters were chosen according to the observed cases
        if math.isclose(abs_value, precision_pow, abs_tol=1e-4, rel_tol=0.01):
            # If the value is close to the precision, round it
            exponent = math.ceil(math.log10(precision_pow))
        else:
            exponent = math.floor(math.log10(abs_value))
    else:
        exponent = 0

    unit_place = exponent - exponent % 3
    if unit_place < min(self._exponent_unit_map):
        unit = self._exponent_unit_map[min(self._exponent_unit_map.keys())]
        unit_place = min(self._exponent_unit_map)
    elif unit_place > max(self._exponent_unit_map):
        unit = self._exponent_unit_map[max(self._exponent_unit_map.keys())]
        unit_place = max(self._exponent_unit_map)
    else:
        unit = self._exponent_unit_map[unit_place]

    value_str = f"{self._base_value / 10 ** unit_place:.{precision}f}"

    if value_str in ("-0", "0"):
        stripped = value_str
    else:
        stripped = value_str.rstrip("0").rstrip(".")

    if not keep_trailing_zeros:
        value_str = stripped
    unit_str = unit if stripped not in ("-0", "0") else self._exponent_unit_map[0]
    return f"{value_str} {unit_str}"
__ge__ ¤
__ge__(other: Self) -> bool

Return whether this quantity is greater than or equal to another.

PARAMETER DESCRIPTION
other

The other quantity.

TYPE: Self

RETURNS DESCRIPTION
bool

Whether this quantity is greater than or equal to another.

Source code in frequenz/quantities/_quantity.py
def __ge__(self, other: Self) -> bool:
    """Return whether this quantity is greater than or equal to another.

    Args:
        other: The other quantity.

    Returns:
        Whether this quantity is greater than or equal to another.
    """
    if not type(other) is type(self):
        return NotImplemented
    return self._base_value >= other._base_value
__gt__ ¤
__gt__(other: Self) -> bool

Return whether this quantity is greater than another.

PARAMETER DESCRIPTION
other

The other quantity.

TYPE: Self

RETURNS DESCRIPTION
bool

Whether this quantity is greater than another.

Source code in frequenz/quantities/_quantity.py
def __gt__(self, other: Self) -> bool:
    """Return whether this quantity is greater than another.

    Args:
        other: The other quantity.

    Returns:
        Whether this quantity is greater than another.
    """
    if not type(other) is type(self):
        return NotImplemented
    return self._base_value > other._base_value
__init__ ¤
__init__(value: float, exponent: int = 0) -> None

Initialize a new quantity.

PARAMETER DESCRIPTION
value

The value of this quantity in a given exponent of the base unit.

TYPE: float

exponent

The exponent of the base unit the given value is in.

TYPE: int DEFAULT: 0

Source code in frequenz/quantities/_quantity.py
def __init__(self, value: float, exponent: int = 0) -> None:
    """Initialize a new quantity.

    Args:
        value: The value of this quantity in a given exponent of the base unit.
        exponent: The exponent of the base unit the given value is in.
    """
    self._base_value = value * 10.0**exponent
__init_subclass__ ¤
__init_subclass__(
    exponent_unit_map: dict[int, str]
) -> None

Initialize a new subclass of Quantity.

PARAMETER DESCRIPTION
exponent_unit_map

A mapping from the exponent of the base unit to the unit symbol.

TYPE: dict[int, str]

RAISES DESCRIPTION
ValueError

If the given exponent_unit_map does not contain a base unit (exponent 0).

Source code in frequenz/quantities/_quantity.py
def __init_subclass__(cls, exponent_unit_map: dict[int, str]) -> None:
    """Initialize a new subclass of Quantity.

    Args:
        exponent_unit_map: A mapping from the exponent of the base unit to the unit
            symbol.

    Raises:
        ValueError: If the given exponent_unit_map does not contain a base unit
            (exponent 0).
    """
    if 0 not in exponent_unit_map:
        raise ValueError("Expected a base unit for the type (for exponent 0)")
    cls._exponent_unit_map = exponent_unit_map
    super().__init_subclass__()
__le__ ¤
__le__(other: Self) -> bool

Return whether this quantity is less than or equal to another.

PARAMETER DESCRIPTION
other

The other quantity.

TYPE: Self

RETURNS DESCRIPTION
bool

Whether this quantity is less than or equal to another.

Source code in frequenz/quantities/_quantity.py
def __le__(self, other: Self) -> bool:
    """Return whether this quantity is less than or equal to another.

    Args:
        other: The other quantity.

    Returns:
        Whether this quantity is less than or equal to another.
    """
    if not type(other) is type(self):
        return NotImplemented
    return self._base_value <= other._base_value
__lt__ ¤
__lt__(other: Self) -> bool

Return whether this quantity is less than another.

PARAMETER DESCRIPTION
other

The other quantity.

TYPE: Self

RETURNS DESCRIPTION
bool

Whether this quantity is less than another.

Source code in frequenz/quantities/_quantity.py
def __lt__(self, other: Self) -> bool:
    """Return whether this quantity is less than another.

    Args:
        other: The other quantity.

    Returns:
        Whether this quantity is less than another.
    """
    if not type(other) is type(self):
        return NotImplemented
    return self._base_value < other._base_value
__mod__ ¤
__mod__(other: Self) -> Self

Return the remainder of this quantity and another.

PARAMETER DESCRIPTION
other

The other quantity.

TYPE: Self

RETURNS DESCRIPTION
Self

The remainder of this quantity and another.

Source code in frequenz/quantities/_quantity.py
def __mod__(self, other: Self) -> Self:
    """Return the remainder of this quantity and another.

    Args:
        other: The other quantity.

    Returns:
        The remainder of this quantity and another.
    """
    return self._new(self._base_value % other._base_value)
__mul__ ¤
__mul__(scalar: float) -> Self
__mul__(percent: Percentage) -> Self
__mul__(other: float | Percentage) -> Self

Return a power or energy from multiplying this power by the given value.

PARAMETER DESCRIPTION
other

The scalar, percentage or duration to multiply by.

TYPE: float | Percentage

RETURNS DESCRIPTION
Self

A power or energy.

Source code in frequenz/quantities/_reactive_power.py
def __mul__(self, other: float | Percentage, /) -> Self:
    """Return a power or energy from multiplying this power by the given value.

    Args:
        other: The scalar, percentage or duration to multiply by.

    Returns:
        A power or energy.
    """
    from ._percentage import Percentage  # pylint: disable=import-outside-toplevel

    match other:
        case float() | Percentage():
            return super().__mul__(other)
        case _:
            return NotImplemented
__neg__ ¤
__neg__() -> Self

Return the negation of this quantity.

RETURNS DESCRIPTION
Self

The negation of this quantity.

Source code in frequenz/quantities/_quantity.py
def __neg__(self) -> Self:
    """Return the negation of this quantity.

    Returns:
        The negation of this quantity.
    """
    negation = type(self).__new__(type(self))
    negation._base_value = -self._base_value
    return negation
__pos__ ¤
__pos__() -> Self

Return this quantity.

RETURNS DESCRIPTION
Self

This quantity.

Source code in frequenz/quantities/_quantity.py
def __pos__(self) -> Self:
    """Return this quantity.

    Returns:
        This quantity.
    """
    return self
__repr__ ¤
__repr__() -> str

Return a representation of this quantity.

RETURNS DESCRIPTION
str

A representation of this quantity.

Source code in frequenz/quantities/_quantity.py
def __repr__(self) -> str:
    """Return a representation of this quantity.

    Returns:
        A representation of this quantity.
    """
    return f"{type(self).__name__}(value={self._base_value}, exponent=0)"
__round__ ¤
__round__(ndigits: int | None = None) -> Self

Round this quantity to the given number of digits.

PARAMETER DESCRIPTION
ndigits

The number of digits to round to.

TYPE: int | None DEFAULT: None

RETURNS DESCRIPTION
Self

The rounded quantity.

Source code in frequenz/quantities/_quantity.py
def __round__(self, ndigits: int | None = None) -> Self:
    """Round this quantity to the given number of digits.

    Args:
        ndigits: The number of digits to round to.

    Returns:
        The rounded quantity.
    """
    return self._new(round(self._base_value, ndigits))
__str__ ¤
__str__() -> str

Return a string representation of this quantity.

RETURNS DESCRIPTION
str

A string representation of this quantity.

Source code in frequenz/quantities/_quantity.py
def __str__(self) -> str:
    """Return a string representation of this quantity.

    Returns:
        A string representation of this quantity.
    """
    return self.__format__("")
__sub__ ¤
__sub__(other: Self) -> Self

Return the difference of this quantity and another.

PARAMETER DESCRIPTION
other

The other quantity.

TYPE: Self

RETURNS DESCRIPTION
Self

The difference of this quantity and another.

Source code in frequenz/quantities/_quantity.py
def __sub__(self, other: Self) -> Self:
    """Return the difference of this quantity and another.

    Args:
        other: The other quantity.

    Returns:
        The difference of this quantity and another.
    """
    if not type(other) is type(self):
        return NotImplemented
    difference = type(self).__new__(type(self))
    difference._base_value = self._base_value - other._base_value
    return difference
__truediv__ ¤
__truediv__(other: float) -> Self
__truediv__(other: Self) -> float
__truediv__(current: Current) -> Voltage
__truediv__(voltage: Voltage) -> Current
__truediv__(
    other: float | Self | Current | Voltage,
) -> Self | float | Voltage | Current

Return a current or voltage from dividing this power by the given value.

PARAMETER DESCRIPTION
other

The scalar, power, current or voltage to divide by.

TYPE: float | Self | Current | Voltage

RETURNS DESCRIPTION
Self | float | Voltage | Current

A current or voltage from dividing this power by the given value.

Source code in frequenz/quantities/_reactive_power.py
def __truediv__(
    self, other: float | Self | Current | Voltage, /
) -> Self | float | Voltage | Current:
    """Return a current or voltage from dividing this power by the given value.

    Args:
        other: The scalar, power, current or voltage to divide by.

    Returns:
        A current or voltage from dividing this power by the given value.
    """
    from ._current import Current  # pylint: disable=import-outside-toplevel
    from ._voltage import Voltage  # pylint: disable=import-outside-toplevel

    match other:
        case float():
            return super().__truediv__(other)
        case ReactivePower():
            return self._base_value / other._base_value
        case Current():
            return Voltage._new(self._base_value / other._base_value)
        case Voltage():
            return Current._new(self._base_value / other._base_value)
        case _:
            return NotImplemented
as_kilo_volt_amperes_reactive ¤
as_kilo_volt_amperes_reactive() -> float

Return the reactive power in kilovolt-amperes reactive (kVAR).

RETURNS DESCRIPTION
float

The reactive power in kilovolt-amperes reactive (kVAR).

Source code in frequenz/quantities/_reactive_power.py
def as_kilo_volt_amperes_reactive(self) -> float:
    """Return the reactive power in kilovolt-amperes reactive (kVAR).

    Returns:
        The reactive power in kilovolt-amperes reactive (kVAR).
    """
    return self._base_value / 1e3
as_mega_volt_amperes_reactive ¤
as_mega_volt_amperes_reactive() -> float

Return the reactive power in megavolt-amperes reactive (MVAR).

RETURNS DESCRIPTION
float

The reactive power in megavolt-amperes reactive (MVAR).

Source code in frequenz/quantities/_reactive_power.py
def as_mega_volt_amperes_reactive(self) -> float:
    """Return the reactive power in megavolt-amperes reactive (MVAR).

    Returns:
        The reactive power in megavolt-amperes reactive (MVAR).
    """
    return self._base_value / 1e6
as_milli_volt_amperes_reactive ¤
as_milli_volt_amperes_reactive() -> float

Return the reactive power in millivolt-amperes reactive (mVAR).

RETURNS DESCRIPTION
float

The reactive power in millivolt-amperes reactive (mVAR).

Source code in frequenz/quantities/_reactive_power.py
def as_milli_volt_amperes_reactive(self) -> float:
    """Return the reactive power in millivolt-amperes reactive (mVAR).

    Returns:
        The reactive power in millivolt-amperes reactive (mVAR).
    """
    return self._base_value * 1e3
as_volt_amperes_reactive ¤
as_volt_amperes_reactive() -> float

Return the reactive power in volt-amperes reactive (VAR).

RETURNS DESCRIPTION
float

The reactive power in volt-amperes reactive (VAR).

Source code in frequenz/quantities/_reactive_power.py
def as_volt_amperes_reactive(self) -> float:
    """Return the reactive power in volt-amperes reactive (VAR).

    Returns:
        The reactive power in volt-amperes reactive (VAR).
    """
    return self._base_value
from_kilo_volt_amperes_reactive classmethod ¤
from_kilo_volt_amperes_reactive(kvars: float) -> Self

Initialize a new reactive power quantity.

PARAMETER DESCRIPTION
kvars

The reactive power in kilovolt-amperes reactive (kVAR).

TYPE: float

RETURNS DESCRIPTION
Self

A new reactive power quantity.

Source code in frequenz/quantities/_reactive_power.py
@classmethod
def from_kilo_volt_amperes_reactive(cls, kvars: float) -> Self:
    """Initialize a new reactive power quantity.

    Args:
        kvars: The reactive power in kilovolt-amperes reactive (kVAR).

    Returns:
        A new reactive power quantity.
    """
    return cls._new(kvars, exponent=3)
from_mega_volt_amperes_reactive classmethod ¤
from_mega_volt_amperes_reactive(mvars: float) -> Self

Initialize a new reactive power quantity.

PARAMETER DESCRIPTION
mvars

The reactive power in megavolt-amperes reactive (MVAR).

TYPE: float

RETURNS DESCRIPTION
Self

A new reactive power quantity.

Source code in frequenz/quantities/_reactive_power.py
@classmethod
def from_mega_volt_amperes_reactive(cls, mvars: float) -> Self:
    """Initialize a new reactive power quantity.

    Args:
        mvars: The reactive power in megavolt-amperes reactive (MVAR).

    Returns:
        A new reactive power quantity.
    """
    return cls._new(mvars, exponent=6)
from_milli_volt_amperes_reactive classmethod ¤
from_milli_volt_amperes_reactive(mvars: float) -> Self

Initialize a new reactive power quantity.

PARAMETER DESCRIPTION
mvars

The reactive power in millivolt-amperes reactive (mVAR).

TYPE: float

RETURNS DESCRIPTION
Self

A new reactive power quantity.

Source code in frequenz/quantities/_reactive_power.py
@classmethod
def from_milli_volt_amperes_reactive(cls, mvars: float) -> Self:
    """Initialize a new reactive power quantity.

    Args:
        mvars: The reactive power in millivolt-amperes reactive (mVAR).

    Returns:
        A new reactive power quantity.
    """
    return cls._new(mvars, exponent=-3)
from_string classmethod ¤
from_string(string: str) -> Self

Return a quantity from a string representation.

PARAMETER DESCRIPTION
string

The string representation of the quantity.

TYPE: str

RETURNS DESCRIPTION
Self

A quantity object with the value given in the string.

RAISES DESCRIPTION
ValueError

If the string does not match the expected format.

Source code in frequenz/quantities/_quantity.py
@classmethod
def from_string(cls, string: str) -> Self:
    """Return a quantity from a string representation.

    Args:
        string: The string representation of the quantity.

    Returns:
        A quantity object with the value given in the string.

    Raises:
        ValueError: If the string does not match the expected format.

    """
    split_string = string.split(" ")

    if len(split_string) != 2:
        raise ValueError(
            f"Expected a string of the form 'value unit', got {string}"
        )

    assert cls._exponent_unit_map is not None
    exp_map = cls._exponent_unit_map

    for exponent, unit in exp_map.items():
        if unit == split_string[1]:
            instance = cls.__new__(cls)
            try:
                instance._base_value = float(split_string[0]) * 10**exponent
            except ValueError as error:
                raise ValueError(f"Failed to parse string '{string}'.") from error

            return instance

    raise ValueError(f"Unknown unit {split_string[1]}")
from_volt_amperes_reactive classmethod ¤
from_volt_amperes_reactive(value: float) -> Self

Initialize a new reactive power quantity.

PARAMETER DESCRIPTION
value

The reactive power in volt-amperes reactive (VAR).

TYPE: float

RETURNS DESCRIPTION
Self

A new reactive power quantity.

Source code in frequenz/quantities/_reactive_power.py
@classmethod
def from_volt_amperes_reactive(cls, value: float) -> Self:
    """Initialize a new reactive power quantity.

    Args:
        value: The reactive power in volt-amperes reactive (VAR).

    Returns:
        A new reactive power quantity.
    """
    return cls._new(value)
isclose ¤
isclose(
    other: Self,
    rel_tol: float = 1e-09,
    abs_tol: float = 0.0,
) -> bool

Return whether this quantity is close to another.

PARAMETER DESCRIPTION
other

The quantity to compare to.

TYPE: Self

rel_tol

The relative tolerance.

TYPE: float DEFAULT: 1e-09

abs_tol

The absolute tolerance.

TYPE: float DEFAULT: 0.0

RETURNS DESCRIPTION
bool

Whether this quantity is close to another.

Source code in frequenz/quantities/_quantity.py
def isclose(self, other: Self, rel_tol: float = 1e-9, abs_tol: float = 0.0) -> bool:
    """Return whether this quantity is close to another.

    Args:
        other: The quantity to compare to.
        rel_tol: The relative tolerance.
        abs_tol: The absolute tolerance.

    Returns:
        Whether this quantity is close to another.
    """
    return math.isclose(
        self._base_value,
        other._base_value,  # pylint: disable=protected-access
        rel_tol=rel_tol,
        abs_tol=abs_tol,
    )
isinf ¤
isinf() -> bool

Return whether this quantity is infinite.

RETURNS DESCRIPTION
bool

Whether this quantity is infinite.

Source code in frequenz/quantities/_quantity.py
def isinf(self) -> bool:
    """Return whether this quantity is infinite.

    Returns:
        Whether this quantity is infinite.
    """
    return math.isinf(self._base_value)
isnan ¤
isnan() -> bool

Return whether this quantity is NaN.

RETURNS DESCRIPTION
bool

Whether this quantity is NaN.

Source code in frequenz/quantities/_quantity.py
def isnan(self) -> bool:
    """Return whether this quantity is NaN.

    Returns:
        Whether this quantity is NaN.
    """
    return math.isnan(self._base_value)
zero classmethod ¤
zero() -> Self

Return a quantity with value 0.0.

RETURNS DESCRIPTION
Self

A quantity with value 0.0.

Source code in frequenz/quantities/_quantity.py
@classmethod
def zero(cls) -> Self:
    """Return a quantity with value 0.0.

    Returns:
        A quantity with value 0.0.
    """
    _zero = cls._zero_cache.get(cls, None)
    if _zero is None:
        _zero = cls.__new__(cls)
        _zero._base_value = 0.0
        cls._zero_cache[cls] = _zero
    assert isinstance(_zero, cls)
    return _zero

frequenz.quantities.Temperature ¤

Bases: Quantity

A temperature quantity (in degrees Celsius).

Source code in frequenz/quantities/_temperature.py
class Temperature(
    Quantity,
    metaclass=NoDefaultConstructible,
    exponent_unit_map={
        0: "°C",
    },
):
    """A temperature quantity (in degrees Celsius)."""

    @classmethod
    def from_celsius(cls, value: float) -> Self:
        """Initialize a new temperature quantity.

        Args:
            value: The temperature in degrees Celsius.

        Returns:
            A new temperature quantity.
        """
        return cls._new(value)

    def as_celsius(self) -> float:
        """Return the temperature in degrees Celsius.

        Returns:
            The temperature in degrees Celsius.
        """
        return self._base_value
Attributes¤
base_unit property ¤
base_unit: str | None

Return the base unit of this quantity.

None if this quantity has no unit.

RETURNS DESCRIPTION
str | None

The base unit of this quantity.

base_value property ¤
base_value: float

Return the value of this quantity in the base unit.

RETURNS DESCRIPTION
float

The value of this quantity in the base unit.

Functions¤
__abs__ ¤
__abs__() -> Self

Return the absolute value of this quantity.

RETURNS DESCRIPTION
Self

The absolute value of this quantity.

Source code in frequenz/quantities/_quantity.py
def __abs__(self) -> Self:
    """Return the absolute value of this quantity.

    Returns:
        The absolute value of this quantity.
    """
    absolute = type(self).__new__(type(self))
    absolute._base_value = abs(self._base_value)
    return absolute
__add__ ¤
__add__(other: Self) -> Self

Return the sum of this quantity and another.

PARAMETER DESCRIPTION
other

The other quantity.

TYPE: Self

RETURNS DESCRIPTION
Self

The sum of this quantity and another.

Source code in frequenz/quantities/_quantity.py
def __add__(self, other: Self) -> Self:
    """Return the sum of this quantity and another.

    Args:
        other: The other quantity.

    Returns:
        The sum of this quantity and another.
    """
    if not type(other) is type(self):
        return NotImplemented
    summe = type(self).__new__(type(self))
    summe._base_value = self._base_value + other._base_value
    return summe
__eq__ ¤
__eq__(other: object) -> bool

Return whether this quantity is equal to another.

PARAMETER DESCRIPTION
other

The other quantity.

TYPE: object

RETURNS DESCRIPTION
bool

Whether this quantity is equal to another.

Source code in frequenz/quantities/_quantity.py
def __eq__(self, other: object) -> bool:
    """Return whether this quantity is equal to another.

    Args:
        other: The other quantity.

    Returns:
        Whether this quantity is equal to another.
    """
    if not type(other) is type(self):
        return NotImplemented
    # The above check ensures that both quantities are the exact same type, because
    # `isinstance` returns true for subclasses and superclasses.  But the above check
    # doesn't help mypy identify the type of other,  so the below line is necessary.
    assert isinstance(other, self.__class__)
    return self._base_value == other._base_value
__format__ ¤
__format__(__format_spec: str) -> str

Return a formatted string representation of this quantity.

If specified, must be of this form: [0].{precision}. If a 0 is not given, the trailing zeros will be omitted. If no precision is given, the default is 3.

The returned string will use the unit that will result in the maximum precision, based on the magnitude of the value.

Example
from frequenz.quantities import Current
c = Current.from_amperes(0.2345)
assert f"{c:.2}" == "234.5 mA"
c = Current.from_amperes(1.2345)
assert f"{c:.2}" == "1.23 A"
c = Current.from_milliamperes(1.2345)
assert f"{c:.6}" == "1.2345 mA"
PARAMETER DESCRIPTION
__format_spec

The format specifier.

TYPE: str

RETURNS DESCRIPTION
str

A string representation of this quantity.

RAISES DESCRIPTION
ValueError

If the given format specifier is invalid.

Source code in frequenz/quantities/_quantity.py
def __format__(self, __format_spec: str) -> str:
    """Return a formatted string representation of this quantity.

    If specified, must be of this form: `[0].{precision}`.  If a 0 is not given, the
    trailing zeros will be omitted.  If no precision is given, the default is 3.

    The returned string will use the unit that will result in the maximum precision,
    based on the magnitude of the value.

    Example:
        ```python
        from frequenz.quantities import Current
        c = Current.from_amperes(0.2345)
        assert f"{c:.2}" == "234.5 mA"
        c = Current.from_amperes(1.2345)
        assert f"{c:.2}" == "1.23 A"
        c = Current.from_milliamperes(1.2345)
        assert f"{c:.6}" == "1.2345 mA"
        ```

    Args:
        __format_spec: The format specifier.

    Returns:
        A string representation of this quantity.

    Raises:
        ValueError: If the given format specifier is invalid.
    """
    keep_trailing_zeros = False
    if __format_spec != "":
        fspec_parts = __format_spec.split(".")
        if (
            len(fspec_parts) != 2
            or fspec_parts[0] not in ("", "0")
            or not fspec_parts[1].isdigit()
        ):
            raise ValueError(
                "Invalid format specifier. Must be empty or `[0].{precision}`"
            )
        if fspec_parts[0] == "0":
            keep_trailing_zeros = True
        precision = int(fspec_parts[1])
    else:
        precision = 3
    if not self._exponent_unit_map:
        return f"{self._base_value:.{precision}f}"

    if math.isinf(self._base_value) or math.isnan(self._base_value):
        return f"{self._base_value} {self._exponent_unit_map[0]}"

    if abs_value := abs(self._base_value):
        precision_pow = 10 ** (precision)
        # Prevent numbers like 999.999999 being rendered as 1000 V
        # instead of 1 kV.
        # This could happen because the str formatting function does
        # rounding as well.
        # This is an imperfect solution that works for _most_ cases.
        # isclose parameters were chosen according to the observed cases
        if math.isclose(abs_value, precision_pow, abs_tol=1e-4, rel_tol=0.01):
            # If the value is close to the precision, round it
            exponent = math.ceil(math.log10(precision_pow))
        else:
            exponent = math.floor(math.log10(abs_value))
    else:
        exponent = 0

    unit_place = exponent - exponent % 3
    if unit_place < min(self._exponent_unit_map):
        unit = self._exponent_unit_map[min(self._exponent_unit_map.keys())]
        unit_place = min(self._exponent_unit_map)
    elif unit_place > max(self._exponent_unit_map):
        unit = self._exponent_unit_map[max(self._exponent_unit_map.keys())]
        unit_place = max(self._exponent_unit_map)
    else:
        unit = self._exponent_unit_map[unit_place]

    value_str = f"{self._base_value / 10 ** unit_place:.{precision}f}"

    if value_str in ("-0", "0"):
        stripped = value_str
    else:
        stripped = value_str.rstrip("0").rstrip(".")

    if not keep_trailing_zeros:
        value_str = stripped
    unit_str = unit if stripped not in ("-0", "0") else self._exponent_unit_map[0]
    return f"{value_str} {unit_str}"
__ge__ ¤
__ge__(other: Self) -> bool

Return whether this quantity is greater than or equal to another.

PARAMETER DESCRIPTION
other

The other quantity.

TYPE: Self

RETURNS DESCRIPTION
bool

Whether this quantity is greater than or equal to another.

Source code in frequenz/quantities/_quantity.py
def __ge__(self, other: Self) -> bool:
    """Return whether this quantity is greater than or equal to another.

    Args:
        other: The other quantity.

    Returns:
        Whether this quantity is greater than or equal to another.
    """
    if not type(other) is type(self):
        return NotImplemented
    return self._base_value >= other._base_value
__gt__ ¤
__gt__(other: Self) -> bool

Return whether this quantity is greater than another.

PARAMETER DESCRIPTION
other

The other quantity.

TYPE: Self

RETURNS DESCRIPTION
bool

Whether this quantity is greater than another.

Source code in frequenz/quantities/_quantity.py
def __gt__(self, other: Self) -> bool:
    """Return whether this quantity is greater than another.

    Args:
        other: The other quantity.

    Returns:
        Whether this quantity is greater than another.
    """
    if not type(other) is type(self):
        return NotImplemented
    return self._base_value > other._base_value
__init__ ¤
__init__(value: float, exponent: int = 0) -> None

Initialize a new quantity.

PARAMETER DESCRIPTION
value

The value of this quantity in a given exponent of the base unit.

TYPE: float

exponent

The exponent of the base unit the given value is in.

TYPE: int DEFAULT: 0

Source code in frequenz/quantities/_quantity.py
def __init__(self, value: float, exponent: int = 0) -> None:
    """Initialize a new quantity.

    Args:
        value: The value of this quantity in a given exponent of the base unit.
        exponent: The exponent of the base unit the given value is in.
    """
    self._base_value = value * 10.0**exponent
__init_subclass__ ¤
__init_subclass__(
    exponent_unit_map: dict[int, str]
) -> None

Initialize a new subclass of Quantity.

PARAMETER DESCRIPTION
exponent_unit_map

A mapping from the exponent of the base unit to the unit symbol.

TYPE: dict[int, str]

RAISES DESCRIPTION
ValueError

If the given exponent_unit_map does not contain a base unit (exponent 0).

Source code in frequenz/quantities/_quantity.py
def __init_subclass__(cls, exponent_unit_map: dict[int, str]) -> None:
    """Initialize a new subclass of Quantity.

    Args:
        exponent_unit_map: A mapping from the exponent of the base unit to the unit
            symbol.

    Raises:
        ValueError: If the given exponent_unit_map does not contain a base unit
            (exponent 0).
    """
    if 0 not in exponent_unit_map:
        raise ValueError("Expected a base unit for the type (for exponent 0)")
    cls._exponent_unit_map = exponent_unit_map
    super().__init_subclass__()
__le__ ¤
__le__(other: Self) -> bool

Return whether this quantity is less than or equal to another.

PARAMETER DESCRIPTION
other

The other quantity.

TYPE: Self

RETURNS DESCRIPTION
bool

Whether this quantity is less than or equal to another.

Source code in frequenz/quantities/_quantity.py
def __le__(self, other: Self) -> bool:
    """Return whether this quantity is less than or equal to another.

    Args:
        other: The other quantity.

    Returns:
        Whether this quantity is less than or equal to another.
    """
    if not type(other) is type(self):
        return NotImplemented
    return self._base_value <= other._base_value
__lt__ ¤
__lt__(other: Self) -> bool

Return whether this quantity is less than another.

PARAMETER DESCRIPTION
other

The other quantity.

TYPE: Self

RETURNS DESCRIPTION
bool

Whether this quantity is less than another.

Source code in frequenz/quantities/_quantity.py
def __lt__(self, other: Self) -> bool:
    """Return whether this quantity is less than another.

    Args:
        other: The other quantity.

    Returns:
        Whether this quantity is less than another.
    """
    if not type(other) is type(self):
        return NotImplemented
    return self._base_value < other._base_value
__mod__ ¤
__mod__(other: Self) -> Self

Return the remainder of this quantity and another.

PARAMETER DESCRIPTION
other

The other quantity.

TYPE: Self

RETURNS DESCRIPTION
Self

The remainder of this quantity and another.

Source code in frequenz/quantities/_quantity.py
def __mod__(self, other: Self) -> Self:
    """Return the remainder of this quantity and another.

    Args:
        other: The other quantity.

    Returns:
        The remainder of this quantity and another.
    """
    return self._new(self._base_value % other._base_value)
__mul__ ¤
__mul__(scalar: float) -> Self
__mul__(percent: Percentage) -> Self
__mul__(value: float | Percentage) -> Self

Scale this quantity by a scalar or percentage.

PARAMETER DESCRIPTION
value

The scalar or percentage by which to scale this quantity.

TYPE: float | Percentage

RETURNS DESCRIPTION
Self

The scaled quantity.

Source code in frequenz/quantities/_quantity.py
def __mul__(self, value: float | Percentage, /) -> Self:
    """Scale this quantity by a scalar or percentage.

    Args:
        value: The scalar or percentage by which to scale this quantity.

    Returns:
        The scaled quantity.
    """
    from ._percentage import Percentage  # pylint: disable=import-outside-toplevel

    match value:
        case float():
            return type(self)._new(self._base_value * value)
        case Percentage():
            return type(self)._new(self._base_value * value.as_fraction())
        case _:
            return NotImplemented
__neg__ ¤
__neg__() -> Self

Return the negation of this quantity.

RETURNS DESCRIPTION
Self

The negation of this quantity.

Source code in frequenz/quantities/_quantity.py
def __neg__(self) -> Self:
    """Return the negation of this quantity.

    Returns:
        The negation of this quantity.
    """
    negation = type(self).__new__(type(self))
    negation._base_value = -self._base_value
    return negation
__pos__ ¤
__pos__() -> Self

Return this quantity.

RETURNS DESCRIPTION
Self

This quantity.

Source code in frequenz/quantities/_quantity.py
def __pos__(self) -> Self:
    """Return this quantity.

    Returns:
        This quantity.
    """
    return self
__repr__ ¤
__repr__() -> str

Return a representation of this quantity.

RETURNS DESCRIPTION
str

A representation of this quantity.

Source code in frequenz/quantities/_quantity.py
def __repr__(self) -> str:
    """Return a representation of this quantity.

    Returns:
        A representation of this quantity.
    """
    return f"{type(self).__name__}(value={self._base_value}, exponent=0)"
__round__ ¤
__round__(ndigits: int | None = None) -> Self

Round this quantity to the given number of digits.

PARAMETER DESCRIPTION
ndigits

The number of digits to round to.

TYPE: int | None DEFAULT: None

RETURNS DESCRIPTION
Self

The rounded quantity.

Source code in frequenz/quantities/_quantity.py
def __round__(self, ndigits: int | None = None) -> Self:
    """Round this quantity to the given number of digits.

    Args:
        ndigits: The number of digits to round to.

    Returns:
        The rounded quantity.
    """
    return self._new(round(self._base_value, ndigits))
__str__ ¤
__str__() -> str

Return a string representation of this quantity.

RETURNS DESCRIPTION
str

A string representation of this quantity.

Source code in frequenz/quantities/_quantity.py
def __str__(self) -> str:
    """Return a string representation of this quantity.

    Returns:
        A string representation of this quantity.
    """
    return self.__format__("")
__sub__ ¤
__sub__(other: Self) -> Self

Return the difference of this quantity and another.

PARAMETER DESCRIPTION
other

The other quantity.

TYPE: Self

RETURNS DESCRIPTION
Self

The difference of this quantity and another.

Source code in frequenz/quantities/_quantity.py
def __sub__(self, other: Self) -> Self:
    """Return the difference of this quantity and another.

    Args:
        other: The other quantity.

    Returns:
        The difference of this quantity and another.
    """
    if not type(other) is type(self):
        return NotImplemented
    difference = type(self).__new__(type(self))
    difference._base_value = self._base_value - other._base_value
    return difference
__truediv__ ¤
__truediv__(other: float) -> Self
__truediv__(other: Self) -> float
__truediv__(value: float | Self) -> Self | float

Divide this quantity by a scalar or another quantity.

PARAMETER DESCRIPTION
value

The scalar or quantity to divide this quantity by.

TYPE: float | Self

RETURNS DESCRIPTION
Self | float

The divided quantity or the ratio of this quantity to another.

Source code in frequenz/quantities/_quantity.py
def __truediv__(self, value: float | Self, /) -> Self | float:
    """Divide this quantity by a scalar or another quantity.

    Args:
        value: The scalar or quantity to divide this quantity by.

    Returns:
        The divided quantity or the ratio of this quantity to another.
    """
    match value:
        case float():
            return type(self)._new(self._base_value / value)
        case Quantity() if type(value) is type(self):
            return self._base_value / value._base_value
        case _:
            return NotImplemented
as_celsius ¤
as_celsius() -> float

Return the temperature in degrees Celsius.

RETURNS DESCRIPTION
float

The temperature in degrees Celsius.

Source code in frequenz/quantities/_temperature.py
def as_celsius(self) -> float:
    """Return the temperature in degrees Celsius.

    Returns:
        The temperature in degrees Celsius.
    """
    return self._base_value
from_celsius classmethod ¤
from_celsius(value: float) -> Self

Initialize a new temperature quantity.

PARAMETER DESCRIPTION
value

The temperature in degrees Celsius.

TYPE: float

RETURNS DESCRIPTION
Self

A new temperature quantity.

Source code in frequenz/quantities/_temperature.py
@classmethod
def from_celsius(cls, value: float) -> Self:
    """Initialize a new temperature quantity.

    Args:
        value: The temperature in degrees Celsius.

    Returns:
        A new temperature quantity.
    """
    return cls._new(value)
from_string classmethod ¤
from_string(string: str) -> Self

Return a quantity from a string representation.

PARAMETER DESCRIPTION
string

The string representation of the quantity.

TYPE: str

RETURNS DESCRIPTION
Self

A quantity object with the value given in the string.

RAISES DESCRIPTION
ValueError

If the string does not match the expected format.

Source code in frequenz/quantities/_quantity.py
@classmethod
def from_string(cls, string: str) -> Self:
    """Return a quantity from a string representation.

    Args:
        string: The string representation of the quantity.

    Returns:
        A quantity object with the value given in the string.

    Raises:
        ValueError: If the string does not match the expected format.

    """
    split_string = string.split(" ")

    if len(split_string) != 2:
        raise ValueError(
            f"Expected a string of the form 'value unit', got {string}"
        )

    assert cls._exponent_unit_map is not None
    exp_map = cls._exponent_unit_map

    for exponent, unit in exp_map.items():
        if unit == split_string[1]:
            instance = cls.__new__(cls)
            try:
                instance._base_value = float(split_string[0]) * 10**exponent
            except ValueError as error:
                raise ValueError(f"Failed to parse string '{string}'.") from error

            return instance

    raise ValueError(f"Unknown unit {split_string[1]}")
isclose ¤
isclose(
    other: Self,
    rel_tol: float = 1e-09,
    abs_tol: float = 0.0,
) -> bool

Return whether this quantity is close to another.

PARAMETER DESCRIPTION
other

The quantity to compare to.

TYPE: Self

rel_tol

The relative tolerance.

TYPE: float DEFAULT: 1e-09

abs_tol

The absolute tolerance.

TYPE: float DEFAULT: 0.0

RETURNS DESCRIPTION
bool

Whether this quantity is close to another.

Source code in frequenz/quantities/_quantity.py
def isclose(self, other: Self, rel_tol: float = 1e-9, abs_tol: float = 0.0) -> bool:
    """Return whether this quantity is close to another.

    Args:
        other: The quantity to compare to.
        rel_tol: The relative tolerance.
        abs_tol: The absolute tolerance.

    Returns:
        Whether this quantity is close to another.
    """
    return math.isclose(
        self._base_value,
        other._base_value,  # pylint: disable=protected-access
        rel_tol=rel_tol,
        abs_tol=abs_tol,
    )
isinf ¤
isinf() -> bool

Return whether this quantity is infinite.

RETURNS DESCRIPTION
bool

Whether this quantity is infinite.

Source code in frequenz/quantities/_quantity.py
def isinf(self) -> bool:
    """Return whether this quantity is infinite.

    Returns:
        Whether this quantity is infinite.
    """
    return math.isinf(self._base_value)
isnan ¤
isnan() -> bool

Return whether this quantity is NaN.

RETURNS DESCRIPTION
bool

Whether this quantity is NaN.

Source code in frequenz/quantities/_quantity.py
def isnan(self) -> bool:
    """Return whether this quantity is NaN.

    Returns:
        Whether this quantity is NaN.
    """
    return math.isnan(self._base_value)
zero classmethod ¤
zero() -> Self

Return a quantity with value 0.0.

RETURNS DESCRIPTION
Self

A quantity with value 0.0.

Source code in frequenz/quantities/_quantity.py
@classmethod
def zero(cls) -> Self:
    """Return a quantity with value 0.0.

    Returns:
        A quantity with value 0.0.
    """
    _zero = cls._zero_cache.get(cls, None)
    if _zero is None:
        _zero = cls.__new__(cls)
        _zero._base_value = 0.0
        cls._zero_cache[cls] = _zero
    assert isinstance(_zero, cls)
    return _zero

frequenz.quantities.Voltage ¤

Bases: Quantity

A voltage quantity.

Objects of this type are wrappers around float values and are immutable.

The constructors accept a single float value, the as_*() methods return a float value, and each of the arithmetic operators supported by this type are actually implemented using floating-point arithmetic.

So all considerations about floating-point arithmetic apply to this type as well.

Source code in frequenz/quantities/_voltage.py
class Voltage(
    Quantity,
    metaclass=NoDefaultConstructible,
    exponent_unit_map={0: "V", -3: "mV", 3: "kV"},
):
    """A voltage quantity.

    Objects of this type are wrappers around `float` values and are immutable.

    The constructors accept a single `float` value, the `as_*()` methods return a
    `float` value, and each of the arithmetic operators supported by this type are
    actually implemented using floating-point arithmetic.

    So all considerations about floating-point arithmetic apply to this type as well.
    """

    @classmethod
    def from_volts(cls, volts: float) -> Self:
        """Initialize a new voltage quantity.

        Args:
            volts: The voltage in volts.

        Returns:
            A new voltage quantity.
        """
        return cls._new(volts)

    @classmethod
    def from_millivolts(cls, millivolts: float) -> Self:
        """Initialize a new voltage quantity.

        Args:
            millivolts: The voltage in millivolts.

        Returns:
            A new voltage quantity.
        """
        return cls._new(millivolts, exponent=-3)

    @classmethod
    def from_kilovolts(cls, kilovolts: float) -> Self:
        """Initialize a new voltage quantity.

        Args:
            kilovolts: The voltage in kilovolts.

        Returns:
            A new voltage quantity.
        """
        return cls._new(kilovolts, exponent=3)

    def as_volts(self) -> float:
        """Return the voltage in volts.

        Returns:
            The voltage in volts.
        """
        return self._base_value

    def as_millivolts(self) -> float:
        """Return the voltage in millivolts.

        Returns:
            The voltage in millivolts.
        """
        return self._base_value * 1e3

    def as_kilovolts(self) -> float:
        """Return the voltage in kilovolts.

        Returns:
            The voltage in kilovolts.
        """
        return self._base_value / 1e3

    # See comment for Power.__mul__ for why we need the ignore here.
    @overload  # type: ignore[override]
    def __mul__(self, scalar: float, /) -> Self:
        """Scale this voltage by a scalar.

        Args:
            scalar: The scalar by which to scale this voltage.

        Returns:
            The scaled voltage.
        """

    @overload
    def __mul__(self, percent: Percentage, /) -> Self:
        """Scale this voltage by a percentage.

        Args:
            percent: The percentage by which to scale this voltage.

        Returns:
            The scaled voltage.
        """

    @overload
    def __mul__(self, other: Current, /) -> Power:
        """Multiply the voltage by the current to get the power.

        Args:
            other: The current to multiply the voltage with.

        Returns:
            The calculated power.
        """

    def __mul__(self, other: float | Percentage | Current, /) -> Self | Power:
        """Return a voltage or power from multiplying this voltage by the given value.

        Args:
            other: The scalar, percentage or current to multiply by.

        Returns:
            The calculated voltage or power.
        """
        from ._current import Current  # pylint: disable=import-outside-toplevel
        from ._percentage import Percentage  # pylint: disable=import-outside-toplevel
        from ._power import Power  # pylint: disable=import-outside-toplevel

        match other:
            case float() | Percentage():
                return super().__mul__(other)
            case Current():
                return Power._new(self._base_value * other._base_value)
            case _:
                return NotImplemented
Attributes¤
base_unit property ¤
base_unit: str | None

Return the base unit of this quantity.

None if this quantity has no unit.

RETURNS DESCRIPTION
str | None

The base unit of this quantity.

base_value property ¤
base_value: float

Return the value of this quantity in the base unit.

RETURNS DESCRIPTION
float

The value of this quantity in the base unit.

Functions¤
__abs__ ¤
__abs__() -> Self

Return the absolute value of this quantity.

RETURNS DESCRIPTION
Self

The absolute value of this quantity.

Source code in frequenz/quantities/_quantity.py
def __abs__(self) -> Self:
    """Return the absolute value of this quantity.

    Returns:
        The absolute value of this quantity.
    """
    absolute = type(self).__new__(type(self))
    absolute._base_value = abs(self._base_value)
    return absolute
__add__ ¤
__add__(other: Self) -> Self

Return the sum of this quantity and another.

PARAMETER DESCRIPTION
other

The other quantity.

TYPE: Self

RETURNS DESCRIPTION
Self

The sum of this quantity and another.

Source code in frequenz/quantities/_quantity.py
def __add__(self, other: Self) -> Self:
    """Return the sum of this quantity and another.

    Args:
        other: The other quantity.

    Returns:
        The sum of this quantity and another.
    """
    if not type(other) is type(self):
        return NotImplemented
    summe = type(self).__new__(type(self))
    summe._base_value = self._base_value + other._base_value
    return summe
__eq__ ¤
__eq__(other: object) -> bool

Return whether this quantity is equal to another.

PARAMETER DESCRIPTION
other

The other quantity.

TYPE: object

RETURNS DESCRIPTION
bool

Whether this quantity is equal to another.

Source code in frequenz/quantities/_quantity.py
def __eq__(self, other: object) -> bool:
    """Return whether this quantity is equal to another.

    Args:
        other: The other quantity.

    Returns:
        Whether this quantity is equal to another.
    """
    if not type(other) is type(self):
        return NotImplemented
    # The above check ensures that both quantities are the exact same type, because
    # `isinstance` returns true for subclasses and superclasses.  But the above check
    # doesn't help mypy identify the type of other,  so the below line is necessary.
    assert isinstance(other, self.__class__)
    return self._base_value == other._base_value
__format__ ¤
__format__(__format_spec: str) -> str

Return a formatted string representation of this quantity.

If specified, must be of this form: [0].{precision}. If a 0 is not given, the trailing zeros will be omitted. If no precision is given, the default is 3.

The returned string will use the unit that will result in the maximum precision, based on the magnitude of the value.

Example
from frequenz.quantities import Current
c = Current.from_amperes(0.2345)
assert f"{c:.2}" == "234.5 mA"
c = Current.from_amperes(1.2345)
assert f"{c:.2}" == "1.23 A"
c = Current.from_milliamperes(1.2345)
assert f"{c:.6}" == "1.2345 mA"
PARAMETER DESCRIPTION
__format_spec

The format specifier.

TYPE: str

RETURNS DESCRIPTION
str

A string representation of this quantity.

RAISES DESCRIPTION
ValueError

If the given format specifier is invalid.

Source code in frequenz/quantities/_quantity.py
def __format__(self, __format_spec: str) -> str:
    """Return a formatted string representation of this quantity.

    If specified, must be of this form: `[0].{precision}`.  If a 0 is not given, the
    trailing zeros will be omitted.  If no precision is given, the default is 3.

    The returned string will use the unit that will result in the maximum precision,
    based on the magnitude of the value.

    Example:
        ```python
        from frequenz.quantities import Current
        c = Current.from_amperes(0.2345)
        assert f"{c:.2}" == "234.5 mA"
        c = Current.from_amperes(1.2345)
        assert f"{c:.2}" == "1.23 A"
        c = Current.from_milliamperes(1.2345)
        assert f"{c:.6}" == "1.2345 mA"
        ```

    Args:
        __format_spec: The format specifier.

    Returns:
        A string representation of this quantity.

    Raises:
        ValueError: If the given format specifier is invalid.
    """
    keep_trailing_zeros = False
    if __format_spec != "":
        fspec_parts = __format_spec.split(".")
        if (
            len(fspec_parts) != 2
            or fspec_parts[0] not in ("", "0")
            or not fspec_parts[1].isdigit()
        ):
            raise ValueError(
                "Invalid format specifier. Must be empty or `[0].{precision}`"
            )
        if fspec_parts[0] == "0":
            keep_trailing_zeros = True
        precision = int(fspec_parts[1])
    else:
        precision = 3
    if not self._exponent_unit_map:
        return f"{self._base_value:.{precision}f}"

    if math.isinf(self._base_value) or math.isnan(self._base_value):
        return f"{self._base_value} {self._exponent_unit_map[0]}"

    if abs_value := abs(self._base_value):
        precision_pow = 10 ** (precision)
        # Prevent numbers like 999.999999 being rendered as 1000 V
        # instead of 1 kV.
        # This could happen because the str formatting function does
        # rounding as well.
        # This is an imperfect solution that works for _most_ cases.
        # isclose parameters were chosen according to the observed cases
        if math.isclose(abs_value, precision_pow, abs_tol=1e-4, rel_tol=0.01):
            # If the value is close to the precision, round it
            exponent = math.ceil(math.log10(precision_pow))
        else:
            exponent = math.floor(math.log10(abs_value))
    else:
        exponent = 0

    unit_place = exponent - exponent % 3
    if unit_place < min(self._exponent_unit_map):
        unit = self._exponent_unit_map[min(self._exponent_unit_map.keys())]
        unit_place = min(self._exponent_unit_map)
    elif unit_place > max(self._exponent_unit_map):
        unit = self._exponent_unit_map[max(self._exponent_unit_map.keys())]
        unit_place = max(self._exponent_unit_map)
    else:
        unit = self._exponent_unit_map[unit_place]

    value_str = f"{self._base_value / 10 ** unit_place:.{precision}f}"

    if value_str in ("-0", "0"):
        stripped = value_str
    else:
        stripped = value_str.rstrip("0").rstrip(".")

    if not keep_trailing_zeros:
        value_str = stripped
    unit_str = unit if stripped not in ("-0", "0") else self._exponent_unit_map[0]
    return f"{value_str} {unit_str}"
__ge__ ¤
__ge__(other: Self) -> bool

Return whether this quantity is greater than or equal to another.

PARAMETER DESCRIPTION
other

The other quantity.

TYPE: Self

RETURNS DESCRIPTION
bool

Whether this quantity is greater than or equal to another.

Source code in frequenz/quantities/_quantity.py
def __ge__(self, other: Self) -> bool:
    """Return whether this quantity is greater than or equal to another.

    Args:
        other: The other quantity.

    Returns:
        Whether this quantity is greater than or equal to another.
    """
    if not type(other) is type(self):
        return NotImplemented
    return self._base_value >= other._base_value
__gt__ ¤
__gt__(other: Self) -> bool

Return whether this quantity is greater than another.

PARAMETER DESCRIPTION
other

The other quantity.

TYPE: Self

RETURNS DESCRIPTION
bool

Whether this quantity is greater than another.

Source code in frequenz/quantities/_quantity.py
def __gt__(self, other: Self) -> bool:
    """Return whether this quantity is greater than another.

    Args:
        other: The other quantity.

    Returns:
        Whether this quantity is greater than another.
    """
    if not type(other) is type(self):
        return NotImplemented
    return self._base_value > other._base_value
__init__ ¤
__init__(value: float, exponent: int = 0) -> None

Initialize a new quantity.

PARAMETER DESCRIPTION
value

The value of this quantity in a given exponent of the base unit.

TYPE: float

exponent

The exponent of the base unit the given value is in.

TYPE: int DEFAULT: 0

Source code in frequenz/quantities/_quantity.py
def __init__(self, value: float, exponent: int = 0) -> None:
    """Initialize a new quantity.

    Args:
        value: The value of this quantity in a given exponent of the base unit.
        exponent: The exponent of the base unit the given value is in.
    """
    self._base_value = value * 10.0**exponent
__init_subclass__ ¤
__init_subclass__(
    exponent_unit_map: dict[int, str]
) -> None

Initialize a new subclass of Quantity.

PARAMETER DESCRIPTION
exponent_unit_map

A mapping from the exponent of the base unit to the unit symbol.

TYPE: dict[int, str]

RAISES DESCRIPTION
ValueError

If the given exponent_unit_map does not contain a base unit (exponent 0).

Source code in frequenz/quantities/_quantity.py
def __init_subclass__(cls, exponent_unit_map: dict[int, str]) -> None:
    """Initialize a new subclass of Quantity.

    Args:
        exponent_unit_map: A mapping from the exponent of the base unit to the unit
            symbol.

    Raises:
        ValueError: If the given exponent_unit_map does not contain a base unit
            (exponent 0).
    """
    if 0 not in exponent_unit_map:
        raise ValueError("Expected a base unit for the type (for exponent 0)")
    cls._exponent_unit_map = exponent_unit_map
    super().__init_subclass__()
__le__ ¤
__le__(other: Self) -> bool

Return whether this quantity is less than or equal to another.

PARAMETER DESCRIPTION
other

The other quantity.

TYPE: Self

RETURNS DESCRIPTION
bool

Whether this quantity is less than or equal to another.

Source code in frequenz/quantities/_quantity.py
def __le__(self, other: Self) -> bool:
    """Return whether this quantity is less than or equal to another.

    Args:
        other: The other quantity.

    Returns:
        Whether this quantity is less than or equal to another.
    """
    if not type(other) is type(self):
        return NotImplemented
    return self._base_value <= other._base_value
__lt__ ¤
__lt__(other: Self) -> bool

Return whether this quantity is less than another.

PARAMETER DESCRIPTION
other

The other quantity.

TYPE: Self

RETURNS DESCRIPTION
bool

Whether this quantity is less than another.

Source code in frequenz/quantities/_quantity.py
def __lt__(self, other: Self) -> bool:
    """Return whether this quantity is less than another.

    Args:
        other: The other quantity.

    Returns:
        Whether this quantity is less than another.
    """
    if not type(other) is type(self):
        return NotImplemented
    return self._base_value < other._base_value
__mod__ ¤
__mod__(other: Self) -> Self

Return the remainder of this quantity and another.

PARAMETER DESCRIPTION
other

The other quantity.

TYPE: Self

RETURNS DESCRIPTION
Self

The remainder of this quantity and another.

Source code in frequenz/quantities/_quantity.py
def __mod__(self, other: Self) -> Self:
    """Return the remainder of this quantity and another.

    Args:
        other: The other quantity.

    Returns:
        The remainder of this quantity and another.
    """
    return self._new(self._base_value % other._base_value)
__mul__ ¤
__mul__(scalar: float) -> Self
__mul__(percent: Percentage) -> Self
__mul__(other: Current) -> Power
__mul__(
    other: float | Percentage | Current,
) -> Self | Power

Return a voltage or power from multiplying this voltage by the given value.

PARAMETER DESCRIPTION
other

The scalar, percentage or current to multiply by.

TYPE: float | Percentage | Current

RETURNS DESCRIPTION
Self | Power

The calculated voltage or power.

Source code in frequenz/quantities/_voltage.py
def __mul__(self, other: float | Percentage | Current, /) -> Self | Power:
    """Return a voltage or power from multiplying this voltage by the given value.

    Args:
        other: The scalar, percentage or current to multiply by.

    Returns:
        The calculated voltage or power.
    """
    from ._current import Current  # pylint: disable=import-outside-toplevel
    from ._percentage import Percentage  # pylint: disable=import-outside-toplevel
    from ._power import Power  # pylint: disable=import-outside-toplevel

    match other:
        case float() | Percentage():
            return super().__mul__(other)
        case Current():
            return Power._new(self._base_value * other._base_value)
        case _:
            return NotImplemented
__neg__ ¤
__neg__() -> Self

Return the negation of this quantity.

RETURNS DESCRIPTION
Self

The negation of this quantity.

Source code in frequenz/quantities/_quantity.py
def __neg__(self) -> Self:
    """Return the negation of this quantity.

    Returns:
        The negation of this quantity.
    """
    negation = type(self).__new__(type(self))
    negation._base_value = -self._base_value
    return negation
__pos__ ¤
__pos__() -> Self

Return this quantity.

RETURNS DESCRIPTION
Self

This quantity.

Source code in frequenz/quantities/_quantity.py
def __pos__(self) -> Self:
    """Return this quantity.

    Returns:
        This quantity.
    """
    return self
__repr__ ¤
__repr__() -> str

Return a representation of this quantity.

RETURNS DESCRIPTION
str

A representation of this quantity.

Source code in frequenz/quantities/_quantity.py
def __repr__(self) -> str:
    """Return a representation of this quantity.

    Returns:
        A representation of this quantity.
    """
    return f"{type(self).__name__}(value={self._base_value}, exponent=0)"
__round__ ¤
__round__(ndigits: int | None = None) -> Self

Round this quantity to the given number of digits.

PARAMETER DESCRIPTION
ndigits

The number of digits to round to.

TYPE: int | None DEFAULT: None

RETURNS DESCRIPTION
Self

The rounded quantity.

Source code in frequenz/quantities/_quantity.py
def __round__(self, ndigits: int | None = None) -> Self:
    """Round this quantity to the given number of digits.

    Args:
        ndigits: The number of digits to round to.

    Returns:
        The rounded quantity.
    """
    return self._new(round(self._base_value, ndigits))
__str__ ¤
__str__() -> str

Return a string representation of this quantity.

RETURNS DESCRIPTION
str

A string representation of this quantity.

Source code in frequenz/quantities/_quantity.py
def __str__(self) -> str:
    """Return a string representation of this quantity.

    Returns:
        A string representation of this quantity.
    """
    return self.__format__("")
__sub__ ¤
__sub__(other: Self) -> Self

Return the difference of this quantity and another.

PARAMETER DESCRIPTION
other

The other quantity.

TYPE: Self

RETURNS DESCRIPTION
Self

The difference of this quantity and another.

Source code in frequenz/quantities/_quantity.py
def __sub__(self, other: Self) -> Self:
    """Return the difference of this quantity and another.

    Args:
        other: The other quantity.

    Returns:
        The difference of this quantity and another.
    """
    if not type(other) is type(self):
        return NotImplemented
    difference = type(self).__new__(type(self))
    difference._base_value = self._base_value - other._base_value
    return difference
__truediv__ ¤
__truediv__(other: float) -> Self
__truediv__(other: Self) -> float
__truediv__(value: float | Self) -> Self | float

Divide this quantity by a scalar or another quantity.

PARAMETER DESCRIPTION
value

The scalar or quantity to divide this quantity by.

TYPE: float | Self

RETURNS DESCRIPTION
Self | float

The divided quantity or the ratio of this quantity to another.

Source code in frequenz/quantities/_quantity.py
def __truediv__(self, value: float | Self, /) -> Self | float:
    """Divide this quantity by a scalar or another quantity.

    Args:
        value: The scalar or quantity to divide this quantity by.

    Returns:
        The divided quantity or the ratio of this quantity to another.
    """
    match value:
        case float():
            return type(self)._new(self._base_value / value)
        case Quantity() if type(value) is type(self):
            return self._base_value / value._base_value
        case _:
            return NotImplemented
as_kilovolts ¤
as_kilovolts() -> float

Return the voltage in kilovolts.

RETURNS DESCRIPTION
float

The voltage in kilovolts.

Source code in frequenz/quantities/_voltage.py
def as_kilovolts(self) -> float:
    """Return the voltage in kilovolts.

    Returns:
        The voltage in kilovolts.
    """
    return self._base_value / 1e3
as_millivolts ¤
as_millivolts() -> float

Return the voltage in millivolts.

RETURNS DESCRIPTION
float

The voltage in millivolts.

Source code in frequenz/quantities/_voltage.py
def as_millivolts(self) -> float:
    """Return the voltage in millivolts.

    Returns:
        The voltage in millivolts.
    """
    return self._base_value * 1e3
as_volts ¤
as_volts() -> float

Return the voltage in volts.

RETURNS DESCRIPTION
float

The voltage in volts.

Source code in frequenz/quantities/_voltage.py
def as_volts(self) -> float:
    """Return the voltage in volts.

    Returns:
        The voltage in volts.
    """
    return self._base_value
from_kilovolts classmethod ¤
from_kilovolts(kilovolts: float) -> Self

Initialize a new voltage quantity.

PARAMETER DESCRIPTION
kilovolts

The voltage in kilovolts.

TYPE: float

RETURNS DESCRIPTION
Self

A new voltage quantity.

Source code in frequenz/quantities/_voltage.py
@classmethod
def from_kilovolts(cls, kilovolts: float) -> Self:
    """Initialize a new voltage quantity.

    Args:
        kilovolts: The voltage in kilovolts.

    Returns:
        A new voltage quantity.
    """
    return cls._new(kilovolts, exponent=3)
from_millivolts classmethod ¤
from_millivolts(millivolts: float) -> Self

Initialize a new voltage quantity.

PARAMETER DESCRIPTION
millivolts

The voltage in millivolts.

TYPE: float

RETURNS DESCRIPTION
Self

A new voltage quantity.

Source code in frequenz/quantities/_voltage.py
@classmethod
def from_millivolts(cls, millivolts: float) -> Self:
    """Initialize a new voltage quantity.

    Args:
        millivolts: The voltage in millivolts.

    Returns:
        A new voltage quantity.
    """
    return cls._new(millivolts, exponent=-3)
from_string classmethod ¤
from_string(string: str) -> Self

Return a quantity from a string representation.

PARAMETER DESCRIPTION
string

The string representation of the quantity.

TYPE: str

RETURNS DESCRIPTION
Self

A quantity object with the value given in the string.

RAISES DESCRIPTION
ValueError

If the string does not match the expected format.

Source code in frequenz/quantities/_quantity.py
@classmethod
def from_string(cls, string: str) -> Self:
    """Return a quantity from a string representation.

    Args:
        string: The string representation of the quantity.

    Returns:
        A quantity object with the value given in the string.

    Raises:
        ValueError: If the string does not match the expected format.

    """
    split_string = string.split(" ")

    if len(split_string) != 2:
        raise ValueError(
            f"Expected a string of the form 'value unit', got {string}"
        )

    assert cls._exponent_unit_map is not None
    exp_map = cls._exponent_unit_map

    for exponent, unit in exp_map.items():
        if unit == split_string[1]:
            instance = cls.__new__(cls)
            try:
                instance._base_value = float(split_string[0]) * 10**exponent
            except ValueError as error:
                raise ValueError(f"Failed to parse string '{string}'.") from error

            return instance

    raise ValueError(f"Unknown unit {split_string[1]}")
from_volts classmethod ¤
from_volts(volts: float) -> Self

Initialize a new voltage quantity.

PARAMETER DESCRIPTION
volts

The voltage in volts.

TYPE: float

RETURNS DESCRIPTION
Self

A new voltage quantity.

Source code in frequenz/quantities/_voltage.py
@classmethod
def from_volts(cls, volts: float) -> Self:
    """Initialize a new voltage quantity.

    Args:
        volts: The voltage in volts.

    Returns:
        A new voltage quantity.
    """
    return cls._new(volts)
isclose ¤
isclose(
    other: Self,
    rel_tol: float = 1e-09,
    abs_tol: float = 0.0,
) -> bool

Return whether this quantity is close to another.

PARAMETER DESCRIPTION
other

The quantity to compare to.

TYPE: Self

rel_tol

The relative tolerance.

TYPE: float DEFAULT: 1e-09

abs_tol

The absolute tolerance.

TYPE: float DEFAULT: 0.0

RETURNS DESCRIPTION
bool

Whether this quantity is close to another.

Source code in frequenz/quantities/_quantity.py
def isclose(self, other: Self, rel_tol: float = 1e-9, abs_tol: float = 0.0) -> bool:
    """Return whether this quantity is close to another.

    Args:
        other: The quantity to compare to.
        rel_tol: The relative tolerance.
        abs_tol: The absolute tolerance.

    Returns:
        Whether this quantity is close to another.
    """
    return math.isclose(
        self._base_value,
        other._base_value,  # pylint: disable=protected-access
        rel_tol=rel_tol,
        abs_tol=abs_tol,
    )
isinf ¤
isinf() -> bool

Return whether this quantity is infinite.

RETURNS DESCRIPTION
bool

Whether this quantity is infinite.

Source code in frequenz/quantities/_quantity.py
def isinf(self) -> bool:
    """Return whether this quantity is infinite.

    Returns:
        Whether this quantity is infinite.
    """
    return math.isinf(self._base_value)
isnan ¤
isnan() -> bool

Return whether this quantity is NaN.

RETURNS DESCRIPTION
bool

Whether this quantity is NaN.

Source code in frequenz/quantities/_quantity.py
def isnan(self) -> bool:
    """Return whether this quantity is NaN.

    Returns:
        Whether this quantity is NaN.
    """
    return math.isnan(self._base_value)
zero classmethod ¤
zero() -> Self

Return a quantity with value 0.0.

RETURNS DESCRIPTION
Self

A quantity with value 0.0.

Source code in frequenz/quantities/_quantity.py
@classmethod
def zero(cls) -> Self:
    """Return a quantity with value 0.0.

    Returns:
        A quantity with value 0.0.
    """
    _zero = cls._zero_cache.get(cls, None)
    if _zero is None:
        _zero = cls.__new__(cls)
        _zero._base_value = 0.0
        cls._zero_cache[cls] = _zero
    assert isinstance(_zero, cls)
    return _zero