diff --git a/doc/Changelog.md b/doc/Changelog.md index 6e134f13..89b6b4f8 100644 --- a/doc/Changelog.md +++ b/doc/Changelog.md @@ -4,6 +4,7 @@ **In development** +- {gh-pr}`128` Add the properties `z_line`, `y_shunt` and `with_shunt` to the `Line` class. - {gh-pr}`125` Speed-up build of conda workflow using mamba. ## Version 0.5.0 diff --git a/doc/models/Line/ShuntLine.md b/doc/models/Line/ShuntLine.md index d0342b5a..363a5645 100644 --- a/doc/models/Line/ShuntLine.md +++ b/doc/models/Line/ShuntLine.md @@ -128,6 +128,28 @@ load = PowerLoad( id="load", bus=bus2, powers=Q_(np.array([5.0, 2.5, 0]) * (1 - 0.3j), "kVA") ) +# The impedance matrix (in Ohm) can be accessed from the line instance +line.z_line +# array( +# [[0.3+0.35j, 0. +0.25j, 0. +0.25j, 0. +0.25j], +# [0. +0.25j, 0.3+0.35j, 0. +0.25j, 0. +0.25j], +# [0. +0.25j, 0. +0.25j, 0.3+0.35j, 0. +0.25j], +# [0. +0.25j, 0. +0.25j, 0. +0.25j, 0.3+0.35j]] +# ) + +# The shunt admittance matrix (in Siemens) can be accessed from the line instance +line.y_shunt +# array( +# [[2.e-05+4.75e-04j, 0.e+00-6.80e-05j, 0.e+00-1.00e-05j, 0.e+00-6.80e-05j], +# [0.e+00-6.80e-05j, 2.e-05+4.75e-04j, 0.e+00-6.80e-05j, 0.e+00-1.00e-05j], +# [0.e+00-1.00e-05j, 0.e+00-6.80e-05j, 2.e-05+4.75e-04j, 0.e+00-6.80e-05j], +# [0.e+00-6.80e-05j, 0.e+00-1.00e-05j, 0.e+00-6.80e-05j, 2.e-05+4.75e-04j]] +# ) + +# For a shunt line, the property `with_shunt` is True +line.with_shunt +# True + # Create a network and solve a load flow en = ElectricalNetwork.from_element(bus1) auth = ("username", "password") diff --git a/doc/models/Line/SimplifiedLine.md b/doc/models/Line/SimplifiedLine.md index 45869da1..fa83c1bf 100644 --- a/doc/models/Line/SimplifiedLine.md +++ b/doc/models/Line/SimplifiedLine.md @@ -74,6 +74,27 @@ load = PowerLoad( id="load", bus=bus2, powers=Q_(np.array([5.0, 2.5, 0]) * (1 - 0.3j), "kVA") ) +# The impedance matrix (in Ohm) can be accessed from the line instance +line.z_line +# array( +# [[0.35+0.j, 0. +0.j, 0. +0.j, 0. +0.j], +# [0. +0.j, 0.35+0.j, 0. +0.j, 0. +0.j], +# [0. +0.j, 0. +0.j, 0.35+0.j, 0. +0.j], +# [0. +0.j, 0. +0.j, 0. +0.j, 0.35+0.j]] +# ) + +# For a simplified line, the property `with_shunt` is False and the `y_shunt` matrix is zero +line.with_shunt +# False + +line.y_shunt +# array( +# [[0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j], +# [0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j], +# [0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j], +# [0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j]] +# ) + # Create a network and solve a load flow en = ElectricalNetwork.from_element(bus1) auth = ("username", "password") diff --git a/doc/models/Line/index.md b/doc/models/Line/index.md index dc2cad5e..d622f17b 100644 --- a/doc/models/Line/index.md +++ b/doc/models/Line/index.md @@ -8,8 +8,8 @@ $\underline{Y}$. ## Matrices definition -Before diving into the different line models, lets define the series impedance matrix $Z$, and the -shunt admittance matrix $Y$ used to model the lines. +Before diving into the different line models, lets define the series impedance matrix $\underline{Z}$, and the +shunt admittance matrix $\underline{Y}$ used to model the lines. ### Series impedance matrix @@ -158,6 +158,11 @@ shunt_line_parameters = LineParameters( ) ``` +```{tip} +The `Line` instance itself has the `z_line` and `y_shunt` properties. They retrieve the line impedance in $\Omega$ +and the line shunt admittance in Siemens (taking into account the length of the line). +``` + There are several alternative constructors for `LineParameters` objects. The description of them can be found in the dedicated [Line parameters page](Parameters.md). diff --git a/roseau/load_flow/models/lines/lines.py b/roseau/load_flow/models/lines/lines.py index f6862de8..725212b5 100644 --- a/roseau/load_flow/models/lines/lines.py +++ b/roseau/load_flow/models/lines/lines.py @@ -217,7 +217,7 @@ def __init__( self._initialized = True # Handle the ground - if self.ground is not None and not self.parameters.with_shunt: + if self.ground is not None and not self.with_shunt: warnings.warn( message=( f"The ground element must not be provided for line {self.id!r} as it does not have a shunt " @@ -227,7 +227,7 @@ def __init__( stacklevel=2, ) self.ground = None - elif self.parameters.with_shunt: + elif self.with_shunt: # Connect the ground self._connect(self.ground) @@ -260,7 +260,7 @@ def parameters(self, value: LineParameters) -> None: raise RoseauLoadFlowException(msg=msg, code=RoseauLoadFlowExceptionCode.BAD_Z_LINE_SHAPE) if value.with_shunt: - if self._initialized and not self.parameters.with_shunt: + if self._initialized and not self.with_shunt: msg = "Cannot set line parameters with a shunt to a line that does not have shunt components." logger.error(msg) raise RoseauLoadFlowException(msg=msg, code=RoseauLoadFlowExceptionCode.BAD_LINE_MODEL) @@ -273,18 +273,33 @@ def parameters(self, value: LineParameters) -> None: logger.error(msg) raise RoseauLoadFlowException(msg=msg, code=RoseauLoadFlowExceptionCode.BAD_LINE_TYPE) else: - if self._initialized and self.parameters.with_shunt: + if self._initialized and self.with_shunt: msg = "Cannot set line parameters without a shunt to a line that has shunt components." logger.error(msg) raise RoseauLoadFlowException(msg=msg, code=RoseauLoadFlowExceptionCode.BAD_LINE_MODEL) self._parameters = value self._invalidate_network_results() + @property + @ureg_wraps("ohm", (None,), strict=False) + def z_line(self) -> Q_[np.ndarray]: + """Impedance of the line in Ohm""" + return self.parameters._z_line * self._length + + @property + @ureg_wraps("S", (None,), strict=False) + def y_shunt(self) -> Q_[np.ndarray]: + """Shunt admittance of the line in Siemens""" + return self.parameters._y_shunt * self._length + + @property + def with_shunt(self) -> bool: + return self.parameters.with_shunt + def _res_series_values_getter(self, warning: bool) -> tuple[np.ndarray, np.ndarray]: pot1, pot2 = self._res_potentials_getter(warning) # V du_line = pot1 - pot2 - z_line = self.parameters.z_line * self.length - i_line = np.linalg.inv(z_line.m_as("ohm")) @ du_line # Zₗ x Iₗ = ΔU -> I = Zₗ⁻¹ x ΔU + i_line = np.linalg.inv(self.z_line.m_as("ohm")) @ du_line # Zₗ x Iₗ = ΔU -> I = Zₗ⁻¹ x ΔU return du_line, i_line def _res_series_currents_getter(self, warning: bool) -> np.ndarray: @@ -308,19 +323,18 @@ def res_series_power_losses(self) -> Q_[np.ndarray]: return self._res_series_power_losses_getter(warning=True) def _res_shunt_values_getter(self, warning: bool) -> tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]: - assert self.parameters.with_shunt, "this method only works when there is a shunt" - y_shunt = self.parameters.y_shunt + assert self.with_shunt, "This method only works when there is a shunt" assert self.ground is not None pot1, pot2 = self._res_potentials_getter(warning) vg = self.ground.res_potential.m_as("V") - y_shunt = (y_shunt * self.length).m_as("S") + y_shunt = self.y_shunt.m_as("S") yg = y_shunt.sum(axis=1) # y_ig = Y_ia + Y_ib + Y_ic + Y_in for i in {a, b, c, n} i1_shunt = (y_shunt @ pot1 - yg * vg) / 2 i2_shunt = (y_shunt @ pot2 - yg * vg) / 2 return pot1, pot2, i1_shunt, i2_shunt def _res_shunt_currents_getter(self, warning: bool) -> tuple[np.ndarray, np.ndarray]: - if not self.parameters.with_shunt: + if not self.with_shunt: zeros = np.zeros(len(self.phases), dtype=complex) return zeros[:], zeros[:] _, _, cur1, cur2 = self._res_shunt_values_getter(warning) @@ -333,7 +347,7 @@ def res_shunt_currents(self) -> tuple[Q_[np.ndarray], Q_[np.ndarray]]: return self._res_shunt_currents_getter(warning=True) def _res_shunt_power_losses_getter(self, warning: bool) -> np.ndarray: - if not self.parameters.with_shunt: + if not self.with_shunt: return np.zeros(len(self.phases), dtype=complex) pot1, pot2, cur1, cur2 = self._res_shunt_values_getter(warning) return pot1 * cur1.conj() + pot2 * cur2.conj() diff --git a/roseau/load_flow/models/tests/test_lines.py b/roseau/load_flow/models/tests/test_lines.py index bcfa954a..5d6f4d24 100644 --- a/roseau/load_flow/models/tests/test_lines.py +++ b/roseau/load_flow/models/tests/test_lines.py @@ -2,8 +2,8 @@ import pytest from pint import DimensionalityError -from roseau.load_flow import RoseauLoadFlowException, RoseauLoadFlowExceptionCode -from roseau.load_flow.models import Bus, Line, LineParameters +from roseau.load_flow.exceptions import RoseauLoadFlowException, RoseauLoadFlowExceptionCode +from roseau.load_flow.models import Bus, Ground, Line, LineParameters from roseau.load_flow.units import Q_ @@ -61,3 +61,37 @@ def test_lines_units(): line = Line("line", bus1=bus1, bus2=bus2, parameters=lp, length=5) with pytest.raises(DimensionalityError, match=r"Cannot convert from 'ampere' \(\[current\]\) to 'km'"): line.length = Q_(6.5, "A") + + +def test_line_parameters_shortcut(): + bus1 = Bus("bus1", phases="abcn") + bus2 = Bus("bus1", phases="abcn") + + # + # Without shunt + # + lp = LineParameters("lp", z_line=np.eye(4, dtype=complex)) + + # Z + line = Line("line", bus1=bus1, bus2=bus2, parameters=lp, length=Q_(50, "m")) + assert np.allclose(line.z_line.m_as("ohm"), 0.05 * np.eye(4, dtype=complex)) + + # Y + assert not line.with_shunt + assert np.allclose(line.y_shunt.m_as("S"), np.zeros(shape=(4, 4), dtype=complex)) + + # + # With shunt + # + z_line = 0.01 * np.eye(4, dtype=complex) + y_shunt = 1e-5 * np.eye(4, dtype=complex) + lp = LineParameters("lp", z_line=z_line, y_shunt=y_shunt) + + # Z + ground = Ground("ground") + line = Line("line", bus1=bus1, bus2=bus2, parameters=lp, length=Q_(50, "m"), ground=ground) + assert np.allclose(line.z_line.m_as("ohm"), 0.05 * z_line) + + # Y + assert line.with_shunt + assert np.allclose(line.y_shunt.m_as("S"), 0.05 * y_shunt) diff --git a/roseau/load_flow/solvers.py b/roseau/load_flow/solvers.py index 5071c115..4ef18260 100644 --- a/roseau/load_flow/solvers.py +++ b/roseau/load_flow/solvers.py @@ -1,7 +1,7 @@ import logging from typing import Optional -from roseau.load_flow import RoseauLoadFlowException, RoseauLoadFlowExceptionCode +from roseau.load_flow.exceptions import RoseauLoadFlowException, RoseauLoadFlowExceptionCode from roseau.load_flow.typing import JsonDict, Solver logger = logging.getLogger(__name__) diff --git a/roseau/load_flow/tests/test_solvers.py b/roseau/load_flow/tests/test_solvers.py index f684d869..ac03fa59 100644 --- a/roseau/load_flow/tests/test_solvers.py +++ b/roseau/load_flow/tests/test_solvers.py @@ -1,6 +1,6 @@ import pytest -from roseau.load_flow import RoseauLoadFlowException, RoseauLoadFlowExceptionCode +from roseau.load_flow.exceptions import RoseauLoadFlowException, RoseauLoadFlowExceptionCode from roseau.load_flow.solvers import check_solver_params