Skip to content

Make quantities generic over float and Decimal #61

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: v1.x.x
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 35 additions & 26 deletions src/frequenz/quantities/_apparent_power.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@

from __future__ import annotations

from decimal import Decimal
from typing import TYPE_CHECKING, Self, overload

from ._quantity import NoDefaultConstructible, Quantity
from ._quantity import BaseValueT, NoDefaultConstructible, Quantity

if TYPE_CHECKING:
from ._current import Current
Expand All @@ -16,7 +17,7 @@


class ApparentPower(
Quantity,
Quantity[BaseValueT],
metaclass=NoDefaultConstructible,
exponent_unit_map={
-3: "mVA",
Expand All @@ -37,7 +38,7 @@ class ApparentPower(
"""

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

Args:
Expand All @@ -49,7 +50,7 @@ def from_volt_amperes(cls, value: float) -> Self:
return cls._new(value)

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

Args:
Expand All @@ -61,7 +62,7 @@ def from_milli_volt_amperes(cls, mva: float) -> Self:
return cls._new(mva, exponent=-3)

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

Args:
Expand All @@ -73,7 +74,7 @@ def from_kilo_volt_amperes(cls, kva: float) -> Self:
return cls._new(kva, exponent=3)

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

Args:
Expand All @@ -84,40 +85,40 @@ def from_mega_volt_amperes(cls, mva: float) -> Self:
"""
return cls._new(mva, exponent=6)

def as_volt_amperes(self) -> float:
def as_volt_amperes(self) -> BaseValueT:
"""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:
def as_milli_volt_amperes(self) -> BaseValueT:
"""Return the apparent power in millivolt-amperes (mVA).

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

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

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

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

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

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

Args:
Expand All @@ -128,7 +129,7 @@ def __mul__(self, scalar: float, /) -> Self:
"""

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

Args:
Expand All @@ -138,7 +139,7 @@ def __mul__(self, percent: Percentage, /) -> Self:
The scaled power.
"""

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

Args:
Expand All @@ -150,8 +151,8 @@ def __mul__(self, other: float | Percentage, /) -> Self:
from ._percentage import Percentage # pylint: disable=import-outside-toplevel

match other:
case float() | Percentage():
return super().__mul__(other)
case float() | Percentage() | Decimal():
return super().__mul__(other) # type: ignore[operator]
case _:
return NotImplemented

Expand All @@ -164,7 +165,7 @@ def __mul__(self, other: float | Percentage, /) -> Self:
# 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:
def __truediv__(self, other: BaseValueT, /) -> Self:
"""Divide this power by a scalar.

Args:
Expand All @@ -175,7 +176,7 @@ def __truediv__(self, other: float, /) -> Self:
"""

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

Args:
Expand All @@ -186,7 +187,7 @@ def __truediv__(self, other: Self, /) -> float:
"""

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

Args:
Expand All @@ -197,7 +198,7 @@ def __truediv__(self, current: Current, /) -> Voltage:
"""

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

Args:
Expand All @@ -208,8 +209,16 @@ def __truediv__(self, voltage: Voltage, /) -> Current:
"""

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

Args:
Expand All @@ -222,8 +231,8 @@ def __truediv__(
from ._voltage import Voltage # pylint: disable=import-outside-toplevel

match other:
case float():
return super().__truediv__(other)
case float() | Decimal():
return super().__truediv__(other) # type: ignore[operator]
case ApparentPower():
return self._base_value / other._base_value
case Current():
Expand Down
29 changes: 16 additions & 13 deletions src/frequenz/quantities/_current.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@

from __future__ import annotations

from decimal import Decimal
from typing import TYPE_CHECKING, Self, overload

from ._quantity import NoDefaultConstructible, Quantity
from ._quantity import BaseValueT, NoDefaultConstructible, Quantity

if TYPE_CHECKING:
from ._percentage import Percentage
Expand All @@ -17,7 +18,7 @@


class Current(
Quantity,
Quantity[BaseValueT],
metaclass=NoDefaultConstructible,
exponent_unit_map={
-3: "mA",
Expand All @@ -36,7 +37,7 @@ class Current(
"""

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

Args:
Expand All @@ -48,7 +49,7 @@ def from_amperes(cls, amperes: float) -> Self:
return cls._new(amperes)

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

Args:
Expand All @@ -59,25 +60,25 @@ def from_milliamperes(cls, milliamperes: float) -> Self:
"""
return cls._new(milliamperes, exponent=-3)

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

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

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

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

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

Args:
Expand All @@ -88,7 +89,7 @@ def __mul__(self, scalar: float, /) -> Self:
"""

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

Args:
Expand All @@ -99,7 +100,7 @@ def __mul__(self, percent: Percentage, /) -> Self:
"""

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

Args:
Expand All @@ -109,7 +110,9 @@ def __mul__(self, other: Voltage, /) -> Power:
The calculated power.
"""

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

Args:
Expand All @@ -123,8 +126,8 @@ def __mul__(self, other: float | Percentage | Voltage, /) -> Self | Power:
from ._voltage import Voltage # pylint: disable=import-outside-toplevel

match other:
case float() | Percentage():
return super().__mul__(other)
case float() | Decimal() | Percentage():
return super().__mul__(other) # type: ignore[operator]
case Voltage():
return Power._new(self._base_value * other._base_value)
case _:
Expand Down
Loading
Loading