diff --git a/doc/Changelog.md b/doc/Changelog.md index deb17056..c1506892 100644 --- a/doc/Changelog.md +++ b/doc/Changelog.md @@ -4,6 +4,7 @@ **In development** +- {gh-pr}`146` {gh-issue}`145` Fix handling of quantities in input sequences - {gh-pr}`142` {gh-issue}`136` Add `Bus.res_voltage_unbalance()` method to get the Voltage Unbalance Factor (VUF) as defined by the IEC standard IEC 61000-3-14. - {gh-pr}`141` {gh-issue}`137` Add `ElectricalNetwork.to_graph()` to get a `networkx.Graph` object diff --git a/roseau/load_flow/models/buses.py b/roseau/load_flow/models/buses.py index 10aad31b..5d1b532a 100644 --- a/roseau/load_flow/models/buses.py +++ b/roseau/load_flow/models/buses.py @@ -92,13 +92,12 @@ def potentials(self) -> Q_[ComplexArray]: return self._potentials @potentials.setter - @ureg_wraps(None, (None, "V"), strict=False) def potentials(self, value: Sequence[complex]) -> None: if len(value) != len(self.phases): msg = f"Incorrect number of potentials: {len(value)} instead of {len(self.phases)}" logger.error(msg) raise RoseauLoadFlowException(msg=msg, code=RoseauLoadFlowExceptionCode.BAD_POTENTIALS_SIZE) - self._potentials = np.asarray(value, dtype=complex) + self._potentials = np.array([Q_(v, "V").m for v in value], dtype=complex) self._invalidate_network_results() def _res_potentials_getter(self, warning: bool) -> ComplexArray: diff --git a/roseau/load_flow/models/loads/loads.py b/roseau/load_flow/models/loads/loads.py index fbe0e582..4aee824c 100644 --- a/roseau/load_flow/models/loads/loads.py +++ b/roseau/load_flow/models/loads/loads.py @@ -72,6 +72,7 @@ def __init__(self, id: Id, bus: Bus, *, phases: Optional[str] = None, **kwargs: self.phases = phases self.bus = bus self._symbol = {"power": "S", "current": "I", "impedance": "Z"}[self._type] + self._unit = {"power": "VA", "current": "A", "impedance": "ohm"}[self._type] if len(phases) == 2 and "n" not in phases: # This is a delta load that has one element connected between two phases self._size = 1 @@ -111,12 +112,13 @@ def _validate_value(self, value: Sequence[complex]) -> ComplexArray: raise RoseauLoadFlowException( msg=msg, code=RoseauLoadFlowExceptionCode.from_string(f"BAD_{self._symbol}_SIZE") ) + value = np.array([Q_(v, self._unit).m for v in value], dtype=complex) # A load cannot have any zero impedance if self._type == "impedance" and np.isclose(value, 0).any(): msg = f"An impedance of the load {self.id!r} is null" logger.error(msg) raise RoseauLoadFlowException(msg=msg, code=RoseauLoadFlowExceptionCode.BAD_Z_VALUE) - return np.asarray(value, dtype=complex) + return value def _res_potentials_getter(self, warning: bool) -> ComplexArray: self._raise_disconnected_error() @@ -271,7 +273,6 @@ def powers(self) -> Q_[ComplexArray]: return self._powers @powers.setter - @ureg_wraps(None, (None, "VA"), strict=False) def powers(self, value: Sequence[complex]) -> None: value = self._validate_value(value) if self.is_flexible: @@ -380,7 +381,6 @@ def currents(self) -> Q_[ComplexArray]: return self._currents @currents.setter - @ureg_wraps(None, (None, "A"), strict=False) def currents(self, value: Sequence[complex]) -> None: self._currents = self._validate_value(value) self._invalidate_network_results() @@ -431,7 +431,6 @@ def impedances(self) -> Q_[ComplexArray]: return self._impedances @impedances.setter - @ureg_wraps(None, (None, "ohm"), strict=False) def impedances(self, impedances: Sequence[complex]) -> None: self._impedances = self._validate_value(impedances) self._invalidate_network_results() diff --git a/roseau/load_flow/models/sources.py b/roseau/load_flow/models/sources.py index 27492b13..e5dd71a0 100644 --- a/roseau/load_flow/models/sources.py +++ b/roseau/load_flow/models/sources.py @@ -90,13 +90,12 @@ def voltages(self) -> Q_[ComplexArray]: return self._voltages @voltages.setter - @ureg_wraps(None, (None, "V"), strict=False) def voltages(self, voltages: Sequence[complex]) -> None: if len(voltages) != self._size: msg = f"Incorrect number of voltages: {len(voltages)} instead of {self._size}" logger.error(msg) raise RoseauLoadFlowException(msg, code=RoseauLoadFlowExceptionCode.BAD_VOLTAGES_SIZE) - self._voltages = np.asarray(voltages, dtype=complex) + self._voltages = np.array([Q_(v, "V").m for v in voltages], dtype=complex) self._invalidate_network_results() @property diff --git a/roseau/load_flow/models/tests/test_buses.py b/roseau/load_flow/models/tests/test_buses.py index 5fd92ff9..5e9c348f 100644 --- a/roseau/load_flow/models/tests/test_buses.py +++ b/roseau/load_flow/models/tests/test_buses.py @@ -1,6 +1,7 @@ import numpy as np import pandas as pd import pytest +from pint.errors import DimensionalityError from roseau.load_flow import ( Q_, @@ -20,6 +21,31 @@ ) +def test_bus_units(): + # Good unit constructor + bus = Bus("bus", phases="abc", potentials=Q_([1, 1, 1], "kV")) + assert np.allclose(bus._potentials, [1000, 1000, 1000]) + + # Good unit setter + bus = Bus("bus", phases="abc", potentials=[100, 100, 100]) + assert np.allclose(bus._potentials, [100, 100, 100]) + bus.potentials = Q_([1, 1, 1], "kV") + assert np.allclose(bus._potentials, [1000, 1000, 1000]) + + # Units in a list + bus = Bus("bus", phases="abc", potentials=[Q_(1_000, "V"), Q_(1, "kV"), Q_(0.001, "MV")]) + assert np.allclose(bus._potentials, [1000, 1000, 1000]) + + # Bad unit constructor + with pytest.raises(DimensionalityError, match=r"Cannot convert from 'ampere' \(\[current\]\) to 'volt'"): + Bus("bus", phases="abc", potentials=Q_([100, 100, 100], "A")) + + # Bad unit setter + bus = Bus("bus", phases="abc", potentials=[100, 100, 100]) + with pytest.raises(DimensionalityError, match=r"Cannot convert from 'ampere' \(\[current\]\) to 'volt'"): + bus.potentials = Q_([100, 100, 100], "A") + + def test_bus_potentials_of_phases(): bus = Bus("bus", phases="abcn") bus._res_potentials = [1, 2, 3, 4] diff --git a/roseau/load_flow/models/tests/test_loads.py b/roseau/load_flow/models/tests/test_loads.py index 4682e3b7..ca87c69e 100644 --- a/roseau/load_flow/models/tests/test_loads.py +++ b/roseau/load_flow/models/tests/test_loads.py @@ -354,13 +354,25 @@ def test_loads_units(): load.powers = Q_([1, 1, 1], "kVA") assert np.allclose(load._powers, [1000, 1000, 1000]) + # Also works as a quantity array + load = PowerLoad("load", bus, powers=Q_(10, "kVA") * np.ones(3), phases="abcn") + assert np.allclose(load._powers, [10000, 10000, 10000]) + + # Units in a list + load = PowerLoad("load", bus, powers=[Q_(1_000, "VA"), Q_(1, "kVA"), Q_(0.001, "MVA")], phases="abcn") + assert np.allclose(load._powers, [1000, 1000, 1000]) + load = CurrentLoad("load", bus, currents=[Q_(1_000, "A"), Q_(1, "kA"), Q_(0.001, "MA")], phases="abcn") + assert np.allclose(load._currents, [1000, 1000, 1000]) + load = ImpedanceLoad("load", bus, impedances=[Q_(1_000, "ohm"), Q_(1, "kohm"), Q_(0.001, "Mohm")], phases="abcn") + assert np.allclose(load._impedances, [1000, 1000, 1000]) + # Bad unit constructor - with pytest.raises(DimensionalityError, match=r"Cannot convert from 'ampere' \(\[current\]\) to 'VA'"): + with pytest.raises(DimensionalityError, match=r"Cannot convert from 'ampere' \(\[current\]\) to 'volt_ampere'"): PowerLoad("load", bus, powers=Q_([100, 100, 100], "A"), phases="abcn") # Bad unit setter load = PowerLoad("load", bus, powers=[100, 100, 100], phases="abcn") - with pytest.raises(DimensionalityError, match=r"Cannot convert from 'ampere' \(\[current\]\) to 'VA'"): + with pytest.raises(DimensionalityError, match=r"Cannot convert from 'ampere' \(\[current\]\) to 'volt_ampere'"): load.powers = Q_([100, 100, 100], "A") diff --git a/roseau/load_flow/models/tests/test_sources.py b/roseau/load_flow/models/tests/test_sources.py new file mode 100644 index 00000000..895d09f7 --- /dev/null +++ b/roseau/load_flow/models/tests/test_sources.py @@ -0,0 +1,32 @@ +import numpy as np +import pytest +from pint.errors import DimensionalityError + +from roseau.load_flow import Q_, Bus, VoltageSource + + +def test_source_units(): + bus = Bus("bus", phases="abcn") + + # Good unit constructor + vs = VoltageSource("vs", bus, voltages=Q_([1, 1, 1], "kV"), phases="abcn") + assert np.allclose(vs._voltages, [1000, 1000, 1000]) + + # Good unit setter + vs = VoltageSource("vs", bus, voltages=[100, 100, 100], phases="abcn") + assert np.allclose(vs._voltages, [100, 100, 100]) + vs.voltages = Q_([1, 1, 1], "kV") + assert np.allclose(vs._voltages, [1000, 1000, 1000]) + + # Units in a list + vs = VoltageSource("vs", bus, voltages=[Q_(1_000, "V"), Q_(1, "kV"), Q_(0.001, "MV")], phases="abcn") + assert np.allclose(vs._voltages, [1000, 1000, 1000]) + + # Bad unit constructor + with pytest.raises(DimensionalityError, match=r"Cannot convert from 'ampere' \(\[current\]\) to 'volt'"): + VoltageSource("vs", bus, voltages=Q_([100, 100, 100], "A"), phases="abcn") + + # Bad unit setter + vs = VoltageSource("vs", bus, voltages=[100, 100, 100], phases="abcn") + with pytest.raises(DimensionalityError, match=r"Cannot convert from 'ampere' \(\[current\]\) to 'volt'"): + vs.voltages = Q_([100, 100, 100], "A")