diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index c3fbeea..5e747f4 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -7,3 +7,5 @@ This is the initial release, extracted from the [SDK v1.0.0rc601](https://github ## New Features - Added support for `__round__` (`round(quantity)`), `__pos__` (`+quantity`) and `__mod__` (`quantity % quantity`) operators. +- Add `ReactivePower` quantity. +- Add `ApparentPower` quantity. diff --git a/src/frequenz/quantities/__init__.py b/src/frequenz/quantities/__init__.py index fcc68b1..f282f0d 100644 --- a/src/frequenz/quantities/__init__.py +++ b/src/frequenz/quantities/__init__.py @@ -77,12 +77,14 @@ """ +from ._apparent_power import ApparentPower from ._current import Current from ._energy import Energy from ._frequency import Frequency from ._percentage import Percentage from ._power import Power from ._quantity import Quantity +from ._reactive_power import ReactivePower from ._temperature import Temperature from ._voltage import Voltage @@ -93,6 +95,8 @@ "Percentage", "Power", "Quantity", + "ReactivePower", + "ApparentPower", "Temperature", "Voltage", ] diff --git a/src/frequenz/quantities/_apparent_power.py b/src/frequenz/quantities/_apparent_power.py new file mode 100644 index 0000000..e5df26a --- /dev/null +++ b/src/frequenz/quantities/_apparent_power.py @@ -0,0 +1,234 @@ +# License: MIT +# Copyright © 2024 Frequenz Energy-as-a-Service GmbH + +"""Types for holding apparent power quantities with units.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, Self, overload + +from ._quantity import NoDefaultConstructible, Quantity + +if TYPE_CHECKING: + from ._current import Current + from ._percentage import Percentage + from ._voltage import Voltage + + +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 diff --git a/src/frequenz/quantities/_reactive_power.py b/src/frequenz/quantities/_reactive_power.py new file mode 100644 index 0000000..8a43f47 --- /dev/null +++ b/src/frequenz/quantities/_reactive_power.py @@ -0,0 +1,235 @@ +# License: MIT +# Copyright © 2024 Frequenz Energy-as-a-Service GmbH + +"""Types for holding reactive power quantities with units.""" + + +from __future__ import annotations + +from typing import TYPE_CHECKING, Self, overload + +from ._quantity import NoDefaultConstructible, Quantity + +if TYPE_CHECKING: + from ._current import Current + from ._percentage import Percentage + from ._voltage import Voltage + + +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 diff --git a/tests/test_quantities.py b/tests/test_quantities.py index ce9ffdb..f316e04 100644 --- a/tests/test_quantities.py +++ b/tests/test_quantities.py @@ -3,6 +3,7 @@ """Tests for quantity types.""" +# pylint: disable=too-many-lines import inspect from datetime import timedelta from typing import Callable @@ -13,12 +14,14 @@ from frequenz import quantities from frequenz.quantities import ( + ApparentPower, Current, Energy, Frequency, Percentage, Power, Quantity, + ReactivePower, Temperature, Voltage, ) @@ -90,7 +93,7 @@ class Fz2( assert len(_QUANTITY_CTORS) >= _SANITFY_NUM_CLASSES -def test_zero() -> None: +def test_zero() -> None: # pylint: disable=too-many-statements """Test the zero value for quantity.""" assert Quantity(0.0) == Quantity.zero() assert Quantity(0.0, exponent=100) == Quantity.zero() @@ -110,6 +113,20 @@ def test_zero() -> None: assert Power.zero().as_kilowatts() == 0.0 assert Power.zero() is Power.zero() # It is a "singleton" + assert ReactivePower.from_volt_amperes_reactive(0.0) == ReactivePower.zero() + assert ReactivePower.from_kilo_volt_amperes_reactive(0.0) == ReactivePower.zero() + assert isinstance(ReactivePower.zero(), ReactivePower) + assert ReactivePower.zero().as_volt_amperes_reactive() == 0.0 + assert ReactivePower.zero().as_kilo_volt_amperes_reactive() == 0.0 + assert ReactivePower.zero() is ReactivePower.zero() # It is a "singleton" + + assert ApparentPower.from_volt_amperes(0.0) == ApparentPower.zero() + assert ApparentPower.from_kilo_volt_amperes(0.0) == ApparentPower.zero() + assert isinstance(ApparentPower.zero(), ApparentPower) + assert ApparentPower.zero().as_volt_amperes() == 0.0 + assert ApparentPower.zero().as_kilo_volt_amperes() == 0.0 + assert ApparentPower.zero() is ApparentPower.zero() # It is a "singleton" + assert Current.from_amperes(0.0) == Current.zero() assert Current.from_milliamperes(0.0) == Current.zero() assert isinstance(Current.zero(), Current) @@ -223,8 +240,14 @@ def test_string_representation() -> None: assert f"{Power.from_watts(0.000124445):.0}" == "0 W" assert f"{Energy.from_watt_hours(0.124445):.0}" == "0 Wh" + assert f"{ReactivePower.from_volt_amperes_reactive(0.000124445):.0}" == "0 VAR" + assert f"{ApparentPower.from_volt_amperes(0.000124445):.0}" == "0 VA" assert f"{Power.from_watts(-0.0):.0}" == "-0 W" assert f"{Power.from_watts(0.0):.0}" == "0 W" + assert f"{ReactivePower.from_volt_amperes_reactive(-0.0):.0}" == "-0 VAR" + assert f"{ReactivePower.from_volt_amperes_reactive(0.0):.0}" == "0 VAR" + assert f"{ApparentPower.from_volt_amperes(-0.0):.0}" == "-0 VA" + assert f"{ApparentPower.from_volt_amperes(0.0):.0}" == "0 VA" assert f"{Voltage.from_volts(999.9999850988388)}" == "1 kV" @@ -359,6 +382,46 @@ def test_power() -> None: Power(1.0, exponent=0) +def test_reactive_power() -> None: + """Test the reactive power class.""" + power = ReactivePower.from_milli_volt_amperes_reactive(0.0000002) + assert f"{power:.9}" == "0.0000002 mVAR" + power = ReactivePower.from_kilo_volt_amperes_reactive(10000000.2) + assert f"{power}" == "10000 MVAR" + + power = ReactivePower.from_kilo_volt_amperes_reactive(1.2) + assert power.as_volt_amperes_reactive() == 1200.0 + assert power.as_mega_volt_amperes_reactive() == 0.0012 + assert power.as_kilo_volt_amperes_reactive() == 1.2 + assert power == ReactivePower.from_milli_volt_amperes_reactive(1200000.0) + assert power == ReactivePower.from_mega_volt_amperes_reactive(0.0012) + assert power != ReactivePower.from_volt_amperes_reactive(1000.0) + + with pytest.raises(TypeError): + # using the default constructor should raise. + ReactivePower(1.0, exponent=0) + + +def test_apparent_power() -> None: + """Test the apparent power class.""" + power = ApparentPower.from_milli_volt_amperes(0.0000002) + assert f"{power:.9}" == "0.0000002 mVA" + power = ApparentPower.from_kilo_volt_amperes(10000000.2) + assert f"{power}" == "10000 MVA" + + power = ApparentPower.from_kilo_volt_amperes(1.2) + assert power.as_volt_amperes() == 1200.0 + assert power.as_mega_volt_amperes() == 0.0012 + assert power.as_kilo_volt_amperes() == 1.2 + assert power == ApparentPower.from_milli_volt_amperes(1200000.0) + assert power == ApparentPower.from_mega_volt_amperes(0.0012) + assert power != ApparentPower.from_volt_amperes(1000.0) + + with pytest.raises(TypeError): + # using the default constructor should raise. + ApparentPower(1.0, exponent=0) + + def test_current() -> None: """Test the current class.""" current = Current.from_milliamperes(0.0000002) @@ -445,8 +508,8 @@ def test_quantity_compositions() -> None: assert power == current * voltage assert energy / power == timedelta(hours=6.2) - assert energy / timedelta(hours=6.2) == power assert energy == power * timedelta(hours=6.2) + assert energy / timedelta(hours=6.2) == power def test_frequency() -> None: @@ -484,6 +547,14 @@ def test_neg() -> None: assert -power == Power.from_watts(-1000.0) assert -(-power) == power + reactive_power = ReactivePower.from_volt_amperes_reactive(1000.0) + assert -reactive_power == ReactivePower.from_volt_amperes_reactive(-1000.0) + assert -(-reactive_power) == reactive_power + + apparent_power = ApparentPower.from_volt_amperes(1000.0) + assert -apparent_power == ApparentPower.from_volt_amperes(-1000.0) + assert -(-apparent_power) == apparent_power + voltage = Voltage.from_volts(230.0) assert -voltage == Voltage.from_volts(-230.0) assert -(-voltage) == voltage @@ -510,6 +581,14 @@ def test_pos() -> None: assert +power == power assert +(+power) == power + reactive_power = ReactivePower.from_volt_amperes_reactive(1000.0) + assert +reactive_power == reactive_power + assert +(+reactive_power) == reactive_power + + apparent_power = ApparentPower.from_volt_amperes(1000.0) + assert +apparent_power == apparent_power + assert +(+apparent_power) == apparent_power + voltage = Voltage.from_volts(230.0) assert +voltage == voltage assert +(+voltage) == voltage @@ -536,6 +615,12 @@ def test_inf() -> None: assert f"{Power.from_watts(float('inf'))}" == "inf W" assert f"{Power.from_watts(float('-inf'))}" == "-inf W" + assert f"{ReactivePower.from_volt_amperes_reactive(float('inf'))}" == "inf VAR" + assert f"{ReactivePower.from_volt_amperes_reactive(float('-inf'))}" == "-inf VAR" + + assert f"{ApparentPower.from_volt_amperes(float('inf'))}" == "inf VA" + assert f"{ApparentPower.from_volt_amperes(float('-inf'))}" == "-inf VA" + assert f"{Voltage.from_volts(float('inf'))}" == "inf V" assert f"{Voltage.from_volts(float('-inf'))}" == "-inf V" @@ -555,6 +640,8 @@ def test_inf() -> None: def test_nan() -> None: """Test proper formating when using nan in quantities.""" assert f"{Power.from_watts(float('nan'))}" == "nan W" + assert f"{ReactivePower.from_volt_amperes_reactive(float('nan'))}" == "nan VAR" + assert f"{ApparentPower.from_volt_amperes(float('nan'))}" == "nan VA" assert f"{Voltage.from_volts(float('nan'))}" == "nan V" assert f"{Current.from_amperes(float('nan'))}" == "nan A" assert f"{Energy.from_watt_hours(float('nan'))}" == "nan Wh" @@ -568,6 +655,14 @@ def test_abs() -> None: assert abs(power) == Power.from_watts(1000.0) assert abs(-power) == Power.from_watts(1000.0) + reactive_power = ReactivePower.from_volt_amperes_reactive(1000.0) + assert abs(reactive_power) == ReactivePower.from_volt_amperes_reactive(1000.0) + assert abs(-reactive_power) == ReactivePower.from_volt_amperes_reactive(1000.0) + + apparent_power = ApparentPower.from_volt_amperes(1000.0) + assert abs(apparent_power) == ApparentPower.from_volt_amperes(1000.0) + assert abs(-apparent_power) == ApparentPower.from_volt_amperes(1000.0) + voltage = Voltage.from_volts(230.0) assert abs(voltage) == Voltage.from_volts(230.0) assert abs(-voltage) == Voltage.from_volts(230.0) @@ -765,6 +860,8 @@ def test_quantity_divided_by_self( Energy.from_kilowatt_hours(500.0), Frequency.from_hertz(50), Power.from_watts(1000.0), + ApparentPower.from_volt_amperes(1000.0), + ReactivePower.from_volt_amperes_reactive(1000.0), Quantity(30.0), Temperature.from_celsius(30), Voltage.from_volts(230.0), @@ -789,6 +886,8 @@ def test_invalid_current_divisions(divisor: Quantity) -> None: Quantity(30.0), Temperature.from_celsius(30), Voltage.from_volts(230.0), + ReactivePower.from_volt_amperes_reactive(1000.0), + ApparentPower.from_volt_amperes(1000.0), ], ids=lambda q: q.__class__.__name__, ) @@ -808,6 +907,8 @@ def test_invalid_energy_divisions(divisor: Quantity) -> None: Current.from_amperes(2), Energy.from_kilowatt_hours(500.0), Power.from_watts(1000.0), + ApparentPower.from_volt_amperes(1000.0), + ReactivePower.from_volt_amperes_reactive(1000.0), Quantity(30.0), Temperature.from_celsius(30), Voltage.from_volts(230.0), @@ -831,6 +932,8 @@ def test_invalid_frequency_divisions(divisor: Quantity) -> None: Energy.from_kilowatt_hours(500.0), Frequency.from_hertz(50), Power.from_watts(1000.0), + ApparentPower.from_volt_amperes(1000.0), + ReactivePower.from_volt_amperes_reactive(1000.0), Quantity(30.0), Temperature.from_celsius(30), Voltage.from_volts(230.0), @@ -854,6 +957,8 @@ def test_invalid_percentage_divisions(divisor: Quantity) -> None: Frequency.from_hertz(50), Quantity(30.0), Temperature.from_celsius(30), + ReactivePower.from_volt_amperes_reactive(1000.0), + ApparentPower.from_volt_amperes(1000.0), ], ids=lambda q: q.__class__.__name__, ) @@ -874,6 +979,8 @@ def test_invalid_power_divisions(divisor: Quantity) -> None: Energy.from_kilowatt_hours(500.0), Frequency.from_hertz(50), Power.from_watts(1000.0), + ReactivePower.from_volt_amperes_reactive(1000.0), + ApparentPower.from_volt_amperes(1000.0), Temperature.from_celsius(30), Voltage.from_volts(230.0), ], @@ -896,6 +1003,8 @@ def test_invalid_quantity_divisions(divisor: Quantity) -> None: Energy.from_kilowatt_hours(500.0), Frequency.from_hertz(50), Power.from_watts(1000.0), + ReactivePower.from_volt_amperes_reactive(1000.0), + ApparentPower.from_volt_amperes(1000.0), Quantity(30.0), Voltage.from_volts(230.0), ], @@ -918,6 +1027,8 @@ def test_invalid_temperature_divisions(divisor: Quantity) -> None: Energy.from_kilowatt_hours(500.0), Frequency.from_hertz(50), Power.from_watts(1000.0), + ReactivePower.from_volt_amperes_reactive(1000.0), + ApparentPower.from_volt_amperes(1000.0), Quantity(30.0), Temperature.from_celsius(30), ], @@ -936,7 +1047,10 @@ def test_invalid_voltage_divisions(divisor: Quantity) -> None: # We can't use _QUANTITY_TYPES here, because it will break the tests, as hypothesis # will generate more values, some of which are unsupported by the quantities. See the # test comment for more details. -@pytest.mark.parametrize("quantity_type", [Power, Voltage, Current, Energy, Frequency]) +@pytest.mark.parametrize( + "quantity_type", + [Power, Voltage, Current, Energy, Frequency, ReactivePower, ApparentPower], +) @pytest.mark.parametrize("exponent", [0, 3, 6, 9]) @hypothesis.settings( max_examples=1000