Skip to content

Commit 5545d38

Browse files
feat: support all component types in interface (#114)
* feat: support all component types in interface Signed-off-by: Vincent Koppen <vincent.koppen@alliander.com> * linting Signed-off-by: Vincent Koppen <vincent.koppen@alliander.com> --------- Signed-off-by: Vincent Koppen <vincent.koppen@alliander.com>
1 parent 2771843 commit 5545d38

File tree

6 files changed

+77
-45
lines changed

6 files changed

+77
-45
lines changed

docs/demos/connecting_new_consumer.ipynb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@
131131
"metadata": {},
132132
"outputs": [],
133133
"source": [
134-
"from power_grid_model_ds._core.load_flow import PowerGridModelInterface\n",
134+
"from power_grid_model_ds import PowerGridModelInterface\n",
135135
"\n",
136136
"R_PER_KM = 0.1\n",
137137
"X_PER_KM = 0.1\n",

src/power_grid_model_ds/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
#
33
# SPDX-License-Identifier: MPL-2.0
44

5-
from power_grid_model_ds._core.load_flow import PowerGridModelInterface
65
from power_grid_model_ds._core.model.graphs.container import GraphContainer
76
from power_grid_model_ds._core.model.grids.base import Grid
7+
from power_grid_model_ds._core.power_grid_model_interface import PowerGridModelInterface
88

99
__all__ = ["Grid", "GraphContainer", "PowerGridModelInterface"]

src/power_grid_model_ds/_core/load_flow.py renamed to src/power_grid_model_ds/_core/power_grid_model_interface.py

Lines changed: 13 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -9,25 +9,10 @@
99

1010
import numpy as np
1111
from numpy.typing import NDArray
12-
from power_grid_model import CalculationMethod, PowerGridModel, initialize_array
12+
from power_grid_model import CalculationMethod, ComponentType, PowerGridModel, initialize_array
1313

1414
from power_grid_model_ds._core.model.grids.base import Grid
1515

16-
PGM_ARRAYS = [
17-
"node",
18-
"line",
19-
"link",
20-
"transformer",
21-
"three_winding_transformer",
22-
"sym_load",
23-
"sym_gen",
24-
"source",
25-
"transformer_tap_regulator",
26-
"sym_power_sensor",
27-
"sym_voltage_sensor",
28-
"asym_voltage_sensor",
29-
]
30-
3116

3217
class PGMCoreException(Exception):
3318
"""Raised when there is an error in running the power grid model"""
@@ -70,7 +55,9 @@ def create_input_from_grid(self):
7055
"""
7156
Create input for the PowerGridModel
7257
"""
73-
for array_name in PGM_ARRAYS:
58+
for array_name in ComponentType:
59+
if not hasattr(self.grid, array_name):
60+
continue
7461
pgm_array = self._create_power_grid_array(array_name=array_name)
7562
self._input_data[array_name] = pgm_array
7663
return self._input_data
@@ -86,8 +73,8 @@ def create_grid_from_input_data(self, check_ids: bool = True) -> Grid:
8673
8774
Returns a Grid object with the arrays filled with the PowerGridModel input.
8875
"""
89-
for pgm_name in PGM_ARRAYS:
90-
if pgm_name in self._input_data:
76+
for pgm_name in ComponentType:
77+
if pgm_name in self._input_data and hasattr(self.grid, pgm_name):
9178
pgm_ds_array_class = getattr(self.grid, pgm_name).__class__
9279
pgm_ds_array = pgm_ds_array_class(self._input_data[pgm_name])
9380
self.grid.append(pgm_ds_array, check_max_id=False)
@@ -155,12 +142,13 @@ def update_grid(self) -> None:
155142
"""
156143
if not self.output_data:
157144
raise PGMCoreException("Can not update grid without output_data")
158-
for array_name in PGM_ARRAYS:
159-
if array_name in self.output_data.keys():
160-
internal_array = getattr(self.grid, array_name)
161-
pgm_output_array = self.output_data[array_name]
162-
fields = self._match_dtypes(pgm_output_array.dtype, internal_array.dtype)
163-
internal_array[fields] = pgm_output_array[fields]
145+
for array_name in self.output_data.keys():
146+
if not hasattr(self.grid, array_name):
147+
continue
148+
internal_array = getattr(self.grid, array_name)
149+
pgm_output_array = self.output_data[array_name]
150+
fields = self._match_dtypes(pgm_output_array.dtype, internal_array.dtype)
151+
internal_array[fields] = pgm_output_array[fields]
164152

165153
@staticmethod
166154
def _match_dtypes(first_dtype: np.dtype, second_dtype: np.dtype):

src/power_grid_model_ds/errors.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
#
33
# SPDX-License-Identifier: MPL-2.0
44

5-
from power_grid_model_ds._core.load_flow import PGMCoreException
65
from power_grid_model_ds._core.model.arrays.base.errors import (
76
ArrayDefinitionError,
87
MultipleRecordsReturned,
@@ -14,6 +13,7 @@
1413
MissingNodeError,
1514
NoPathBetweenNodes,
1615
)
16+
from power_grid_model_ds._core.power_grid_model_interface import PGMCoreException
1717

1818
__all__ = [
1919
"PGMCoreException",

tests/integration/loadflow/test_power_grid_model.py

Lines changed: 60 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,23 @@
33
# SPDX-License-Identifier: MPL-2.0
44

55

6+
from enum import StrEnum
7+
from unittest.mock import patch
8+
69
import numpy as np
710
import pytest
8-
from power_grid_model import TapChangingStrategy, initialize_array
11+
from power_grid_model import ComponentType, TapChangingStrategy, initialize_array
912

1013
from power_grid_model_ds._core.data_source.generator.grid_generators import RadialGridGenerator
11-
from power_grid_model_ds._core.load_flow import PowerGridModelInterface
1214
from power_grid_model_ds._core.model.arrays import (
1315
LineArray,
1416
NodeArray,
1517
SourceArray,
1618
SymLoadArray,
1719
)
1820
from power_grid_model_ds._core.model.grids.base import Grid
19-
from tests.fixtures.arrays import DefaultedCustomLineArray, DefaultedCustomNodeArray
21+
from power_grid_model_ds._core.power_grid_model_interface import PowerGridModelInterface
22+
from tests.fixtures.arrays import DefaultedCustomNodeArray
2023
from tests.fixtures.grid_classes import ExtendedGrid, ExtendedGridNoDefaults
2124

2225
# pylint: disable=missing-function-docstring,missing-class-docstring
@@ -111,27 +114,55 @@ def test_grid_with_automatic_tap_regulator(self, grid_with_tap_regulator: Grid):
111114
assert output["transformer_tap_regulator"]["tap_pos"][0] > 0
112115

113116

114-
class PowerGridModelInterfaceMethods:
115-
def test_update_grid(self):
116-
"""Tests the power flow on a randomly configured grid and update grid with results"""
117-
grid_generator = RadialGridGenerator(grid_class=Grid, nr_nodes=5, nr_sources=1, nr_nops=0)
118-
grid = grid_generator.run(seed=0)
117+
@pytest.fixture
118+
def base_grid() -> Grid:
119+
grid_generator = RadialGridGenerator(grid_class=Grid, nr_nodes=5, nr_sources=1, nr_nops=0)
120+
return grid_generator.run(seed=0)
119121

120-
grid.node = DefaultedCustomNodeArray(grid.node.data)
121-
grid.line = DefaultedCustomLineArray(grid.line.data)
122122

123-
core_interface = PowerGridModelInterface(grid=grid)
123+
class TestPowerGridModelInterfaceMethods:
124+
@pytest.fixture
125+
def extended_grid(self) -> ExtendedGrid:
126+
grid_generator = RadialGridGenerator(grid_class=ExtendedGrid, nr_nodes=5, nr_sources=1, nr_nops=0)
127+
return grid_generator.run(seed=0)
128+
129+
def test_update_grid(self, extended_grid):
130+
"""Tests the power flow on a randomly configured grid and update grid with results"""
131+
core_interface = PowerGridModelInterface(grid=extended_grid)
132+
124133
core_interface.create_input_from_grid()
125134
core_interface.calculate_power_flow()
126135
core_interface.update_grid()
127136

128137
# voltage should be in neighbourhood of 10500
129-
assert grid.node.u[0] == pytest.approx(10_500, 0.1)
130-
assert grid.node.u[1] == pytest.approx(10_500, 0.1)
138+
assert extended_grid.node.u[0] == pytest.approx(10_500, 0.1)
139+
assert extended_grid.node.u[1] == pytest.approx(10_500, 0.1)
131140
# all lines have a current
132-
assert all(grid.line.i_from > 0)
141+
assert all(extended_grid.line.i_from > 0)
142+
143+
def test_create_input_from_grid_with_additional_component(self, extended_grid):
144+
# If we delete the node array
145+
del extended_grid.node
146+
# The input_data should have a node key
147+
core_interface = PowerGridModelInterface(grid=extended_grid)
148+
input_data = core_interface.create_input_from_grid()
149+
assert "node" not in input_data
150+
151+
def test_update_grid_with_additional_component(self, extended_grid):
152+
core_interface = PowerGridModelInterface(grid=extended_grid)
133153

134-
def test_update_model(self):
154+
core_interface.create_input_from_grid()
155+
core_interface.calculate_power_flow()
156+
157+
# When we delete the node array from the grid
158+
del core_interface.grid.node
159+
core_interface.update_grid()
160+
161+
# We should not crash and still fill the line array with results
162+
assert not hasattr(extended_grid, "node")
163+
assert all(extended_grid.line.i_from > 0)
164+
165+
def test_update_model(self, extended_grid):
135166
"""Test whether a pgm model can be updated and returns different results"""
136167
grid_generator = RadialGridGenerator(grid_class=Grid, nr_nodes=5, nr_sources=1, nr_nops=0)
137168
grid = grid_generator.run(seed=0)
@@ -182,7 +213,7 @@ def test_setup_model(self):
182213

183214
core_interface = PowerGridModelInterface(grid=grid)
184215
assert core_interface.model is None
185-
assert core_interface._input_data is None
216+
assert core_interface._input_data == {}
186217
core_interface.setup_model()
187218
assert core_interface.model
188219
assert core_interface._input_data
@@ -324,3 +355,16 @@ def test_create_extended_grid_without_default_from_input_data(self, input_data_p
324355

325356
with pytest.raises(ValueError, match="Missing required columns: {'u'}"):
326357
core_interface.create_grid_from_input_data()
358+
359+
def test_with_additional_component_type_in_input_data(self, input_data_pgm):
360+
# If there is a new component from PGM in the input_data
361+
input_data_pgm["new_component"] = initialize_array("input", "node", 2)
362+
ExtendedComponentType = StrEnum( # type: ignore
363+
"ExtendedComponentType",
364+
{component.name: component.value for component in ComponentType} | {"NEW_COMPONENT": "new_component"},
365+
)
366+
367+
# We should skip it, but still succesfully generate a grid
368+
with patch("power_grid_model_ds._core.power_grid_model_interface.ComponentType", ExtendedComponentType):
369+
core_interface = PowerGridModelInterface(input_data=input_data_pgm)
370+
core_interface.create_grid_from_input_data()

tests/unit/data_source/generator/test_grid_generators.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@
1212
from power_grid_model_ds._core.data_source.generator.arrays.node import NodeGenerator
1313
from power_grid_model_ds._core.data_source.generator.arrays.source import SourceGenerator
1414
from power_grid_model_ds._core.data_source.generator.grid_generators import RadialGridGenerator
15-
from power_grid_model_ds._core.load_flow import PowerGridModelInterface
1615
from power_grid_model_ds._core.model.arrays import LineArray, NodeArray, SourceArray, SymLoadArray
1716
from power_grid_model_ds._core.model.graphs.models.base import BaseGraphModel
1817
from power_grid_model_ds._core.model.grids.base import Grid
18+
from power_grid_model_ds._core.power_grid_model_interface import PowerGridModelInterface
1919

2020

2121
def test_generate_random_grid():

0 commit comments

Comments
 (0)