diff --git a/src/power_grid_model_ds/_core/model/arrays/__init__.py b/src/power_grid_model_ds/_core/model/arrays/__init__.py index 780deb2..891ef7e 100644 --- a/src/power_grid_model_ds/_core/model/arrays/__init__.py +++ b/src/power_grid_model_ds/_core/model/arrays/__init__.py @@ -7,14 +7,18 @@ """ from power_grid_model_ds._core.model.arrays.pgm_arrays import ( + AsymCurrentSensorArray, + AsymLineArray, AsymVoltageSensorArray, Branch3Array, BranchArray, + GenericBranchArray, IdArray, LineArray, LinkArray, NodeArray, SourceArray, + SymCurrentSensorArray, SymGenArray, SymLoadArray, SymPowerSensorArray, @@ -26,8 +30,10 @@ __all__ = [ "AsymVoltageSensorArray", + "AsymCurrentSensorArray", "Branch3Array", "BranchArray", + "GenericBranchArray", "IdArray", "LineArray", "LinkArray", @@ -35,8 +41,10 @@ "SourceArray", "SymLoadArray", "SymGenArray", + "SymCurrentSensorArray", "SymPowerSensorArray", "SymVoltageSensorArray", + "AsymLineArray", "ThreeWindingTransformerArray", "TransformerArray", "TransformerTapRegulatorArray", diff --git a/src/power_grid_model_ds/_core/model/arrays/pgm_arrays.py b/src/power_grid_model_ds/_core/model/arrays/pgm_arrays.py index 9280d72..e37746c 100644 --- a/src/power_grid_model_ds/_core/model/arrays/pgm_arrays.py +++ b/src/power_grid_model_ds/_core/model/arrays/pgm_arrays.py @@ -13,8 +13,10 @@ from power_grid_model_ds._core.model.arrays.base.array import FancyArray from power_grid_model_ds._core.model.dtypes.appliances import Source, SymGen, SymLoad from power_grid_model_ds._core.model.dtypes.branches import ( + AsymLine, Branch, Branch3, + GenericBranch, Line, Link, ThreeWindingTransformer, @@ -23,7 +25,13 @@ from power_grid_model_ds._core.model.dtypes.id import Id from power_grid_model_ds._core.model.dtypes.nodes import Node from power_grid_model_ds._core.model.dtypes.regulators import TransformerTapRegulator -from power_grid_model_ds._core.model.dtypes.sensors import AsymVoltageSensor, SymPowerSensor, SymVoltageSensor +from power_grid_model_ds._core.model.dtypes.sensors import ( + AsymCurrentSensor, + AsymVoltageSensor, + SymCurrentSensor, + SymPowerSensor, + SymVoltageSensor, +) # pylint: disable=missing-class-docstring @@ -99,6 +107,14 @@ class TransformerArray(Transformer, BranchArray): pass +class GenericBranchArray(GenericBranch, BranchArray): + pass + + +class AsymLineArray(AsymLine, BranchArray): + pass + + class Branch3Array(IdArray, Branch3): def as_branches(self) -> BranchArray: """Convert Branch3Array to BranchArray.""" @@ -140,3 +156,11 @@ class SymVoltageSensorArray(IdArray, SymVoltageSensor): class AsymVoltageSensorArray(IdArray, AsymVoltageSensor): pass + + +class SymCurrentSensorArray(IdArray, SymCurrentSensor): + pass + + +class AsymCurrentSensorArray(IdArray, AsymCurrentSensor): + pass diff --git a/src/power_grid_model_ds/_core/model/dtypes/branches.py b/src/power_grid_model_ds/_core/model/dtypes/branches.py index e4a2aea..8b25939 100644 --- a/src/power_grid_model_ds/_core/model/dtypes/branches.py +++ b/src/power_grid_model_ds/_core/model/dtypes/branches.py @@ -43,6 +43,28 @@ class Line(Branch): i_n: NDArray[np.float64] # rated current +class GenericBranch(Branch): + """GenericBranch data type (generic_branch in power-grid-model) + + Off-nominal ratio k and phase shift theta are modelled explicitly. Rated power sn optional. + The impedance (r1, x1) and admittance (g1, b1) are given wrt the to-side. + """ + + r1: NDArray[np.float64] # positive-sequence resistance + x1: NDArray[np.float64] # positive-sequence reactance + g1: NDArray[np.float64] # positive-sequence conductance + b1: NDArray[np.float64] # positive-sequence susceptance + k: NDArray[np.float64] # off-nominal ratio + theta: NDArray[np.float64] # angle shift (radian) + sn: NDArray[np.float64] # rated power + + _defaults = { + "k": 1.0, + "theta": 0.0, + "sn": 0.0, + } + + class Transformer(Branch): """Transformer data type""" @@ -115,3 +137,34 @@ class ThreeWindingTransformer(Branch3): pk_12_max: NDArray[np.float64] pk_13_max: NDArray[np.float64] pk_23_max: NDArray[np.float64] + + +class AsymLine(Branch): + """Asymmetric Line data type (asym_line in power-grid-model) + + Supports 3 or 4 phase (with neutral) resistance / reactance matrices and optional capacitance matrix + or sequence capacitances c0/c1. If c_* matrix is omitted, c0 & c1 may be specified instead. + Only include fields; validation logic handled elsewhere (not implemented here yet). + """ + + # Resistance matrix entries (series) + r_aa: NDArray[np.float64] + r_ba: NDArray[np.float64] + r_bb: NDArray[np.float64] + r_ca: NDArray[np.float64] + r_cb: NDArray[np.float64] + r_cc: NDArray[np.float64] + + # Reactance matrix entries (series) + x_aa: NDArray[np.float64] + x_ba: NDArray[np.float64] + x_bb: NDArray[np.float64] + x_ca: NDArray[np.float64] + x_cb: NDArray[np.float64] + x_cc: NDArray[np.float64] + + # Alternative sequence capacitances + c0: NDArray[np.float64] + c1: NDArray[np.float64] + + i_n: NDArray[np.float64] # rated current diff --git a/src/power_grid_model_ds/_core/model/dtypes/sensors.py b/src/power_grid_model_ds/_core/model/dtypes/sensors.py index 513e016..b441e08 100644 --- a/src/power_grid_model_ds/_core/model/dtypes/sensors.py +++ b/src/power_grid_model_ds/_core/model/dtypes/sensors.py @@ -61,3 +61,26 @@ class AsymVoltageSensor(GenericVoltageSensor): u_sigma: NDArray3[np.float64] # std of 3 voltages u_measured: NDArray3[np.float64] # measured 3 voltages u_angle_measured: NDArray3[np.float64] # measured 3 phases + + +class GenericCurrentSensor(Sensor): + """Base class for current sensor data type""" + + measured_terminal_type: NDArray[np.int32] + angle_measurement_type: NDArray[np.int32] + i_sigma: NDArray[np.float64] + i_angle_sigma: NDArray[np.float64] + + +class SymCurrentSensor(GenericCurrentSensor): + """SymCurrentSensor data type""" + + i_measured: NDArray[np.float64] + i_angle_measured: NDArray[np.float64] + + +class AsymCurrentSensor(GenericCurrentSensor): + """AsymCurrentSensor data type""" + + i_measured: NDArray3[np.float64] + i_angle_measured: NDArray3[np.float64] diff --git a/src/power_grid_model_ds/_core/model/grids/base.py b/src/power_grid_model_ds/_core/model/grids/base.py index aacd636..248aee1 100644 --- a/src/power_grid_model_ds/_core/model/grids/base.py +++ b/src/power_grid_model_ds/_core/model/grids/base.py @@ -17,13 +17,17 @@ from power_grid_model_ds._core import fancypy as fp from power_grid_model_ds._core.model.arrays import ( + AsymCurrentSensorArray, + AsymLineArray, AsymVoltageSensorArray, Branch3Array, BranchArray, + GenericBranchArray, LineArray, LinkArray, NodeArray, SourceArray, + SymCurrentSensorArray, SymGenArray, SymLoadArray, SymPowerSensorArray, @@ -72,6 +76,8 @@ class Grid(FancyArrayContainer): three_winding_transformer: ThreeWindingTransformerArray line: LineArray link: LinkArray + generic_branch: GenericBranchArray + asym_line: AsymLineArray source: SourceArray sym_load: SymLoadArray @@ -84,6 +90,8 @@ class Grid(FancyArrayContainer): sym_power_sensor: SymPowerSensorArray sym_voltage_sensor: SymVoltageSensorArray asym_voltage_sensor: AsymVoltageSensorArray + sym_current_sensor: SymCurrentSensorArray + asym_current_sensor: AsymCurrentSensorArray def __str__(self) -> str: """String representation of the grid. @@ -114,8 +122,12 @@ def __str__(self) -> str: suffix_str = f"{suffix_str},link" elif branch.id in self.line.id: pass # no suffix needed + elif branch.id in self.generic_branch.id: + suffix_str = f"{suffix_str},generic_branch" + elif branch.id in self.asym_line.id: + suffix_str = f"{suffix_str},asym_line" else: - raise ValueError(f"Branch {branch.id} is not a transformer, link or line") + raise ValueError(f"Branch {branch.id} is not a transformer, link, line, generic_branch or asym_line") grid_str += f"{from_node_str} {to_node_str} {suffix_str}\n" return grid_str @@ -141,7 +153,7 @@ def branch_arrays(self) -> list[BranchArray]: return branch_arrays def get_typed_branches(self, branch_ids: list[int] | npt.NDArray[np.int32]) -> BranchArray: - """Find a matching LineArray, LinkArray or TransformerArray for the given branch_ids + """Find a matching Branch-subtype array for the given branch_ids Raises: ValueError: @@ -162,7 +174,7 @@ def reverse_branches(self, branches: BranchArray): """Reverse the direction of the branches.""" if not branches.size: return - if not isinstance(branches, (LineArray, LinkArray, TransformerArray)): + if not isinstance(branches, (LineArray, LinkArray, TransformerArray, GenericBranchArray, AsymLineArray)): try: branches = self.get_typed_branches(branches.id) except ValueError: diff --git a/src/power_grid_model_ds/arrays.py b/src/power_grid_model_ds/arrays.py index 5fdfc45..80f08c6 100644 --- a/src/power_grid_model_ds/arrays.py +++ b/src/power_grid_model_ds/arrays.py @@ -3,14 +3,18 @@ # SPDX-License-Identifier: MPL-2.0 from power_grid_model_ds._core.model.arrays import ( + AsymCurrentSensorArray, + AsymLineArray, AsymVoltageSensorArray, Branch3Array, BranchArray, + GenericBranchArray, IdArray, LineArray, LinkArray, NodeArray, SourceArray, + SymCurrentSensorArray, SymGenArray, SymLoadArray, SymPowerSensorArray, @@ -26,6 +30,8 @@ "BranchArray", "LinkArray", "LineArray", + "GenericBranchArray", + "AsymLineArray", "TransformerArray", "Branch3Array", "ThreeWindingTransformerArray", @@ -34,6 +40,8 @@ "SymLoadArray", "TransformerTapRegulatorArray", "AsymVoltageSensorArray", + "AsymCurrentSensorArray", "SymPowerSensorArray", "SymVoltageSensorArray", + "SymCurrentSensorArray", ] diff --git a/tests/unit/model/grids/test_grid_base.py b/tests/unit/model/grids/test_grid_base.py index 1527a80..cfbffa6 100644 --- a/tests/unit/model/grids/test_grid_base.py +++ b/tests/unit/model/grids/test_grid_base.py @@ -38,10 +38,14 @@ def test_initialize_empty_grid(grid: Grid): "_id_counter", "transformer_tap_regulator", "asym_voltage_sensor", + "sym_current_sensor", + "asym_current_sensor", "three_winding_transformer", "transformer", "node", "line", + "generic_branch", + "asym_line", "sym_gen", "graphs", "sym_voltage_sensor",