From c1c355b23e22aa0d93fd50b8bfc8ba1714d04167 Mon Sep 17 00:00:00 2001 From: Thijs Baaijen <13253091+Thijss@users.noreply.github.com> Date: Wed, 16 Jul 2025 18:26:52 +0200 Subject: [PATCH 1/4] Add from_pgm_data Signed-off-by: Thijs Baaijen <13253091+Thijss@users.noreply.github.com> --- .../_core/model/grids/base.py | 19 +++++++++++++++++++ tests/unit/model/grids/test_grid_base.py | 7 +++++++ 2 files changed, 26 insertions(+) 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 bb8f35d..ca12721 100644 --- a/src/power_grid_model_ds/_core/model/grids/base.py +++ b/src/power_grid_model_ds/_core/model/grids/base.py @@ -440,6 +440,25 @@ def set_feeder_ids(self): set_is_feeder(grid=self) set_feeder_ids(grid=self) + @classmethod + def from_pgm_data(cls, pgm_data: dict[str, npt.NDArray], load_graphs: bool = True) -> "Grid": + """Initialize the grid from PowerGridModel input data. + + Args: + pgm_data (dict[str, npt.NDArray]): The PowerGridModel input data. + """ + new_grid = cls.empty() + for array_name, array_data in pgm_data.items(): + pgm_array_class = getattr(new_grid, array_name).__class__ + pgm_array = pgm_array_class(array_data) + setattr(new_grid, array_name, pgm_array) + + new_grid._id_counter = new_grid.max_id + + if load_graphs: + new_grid.graphs = GraphContainer.from_arrays(new_grid) + return new_grid + def _add_branch_array(branch: BranchArray | Branch3Array, grid: Grid): """Add a branch array to the grid""" diff --git a/tests/unit/model/grids/test_grid_base.py b/tests/unit/model/grids/test_grid_base.py index ce3dfc8..cd6224d 100644 --- a/tests/unit/model/grids/test_grid_base.py +++ b/tests/unit/model/grids/test_grid_base.py @@ -348,3 +348,10 @@ def test_from_txt_file(self, tmp_path: Path): assert 1 == grid.branches.filter(to_status=0).size assert 1 == grid.transformer.size np.testing.assert_array_equal([14, 10, 11, 12, 13, 15, 16, 17], grid.branches.id) + + +def test_from_pgm_input_data(input_data_pgm): + grid = Grid.from_pgm_data(input_data_pgm) + assert grid.node.size == 3 + assert grid.line.size == 2 + grid.check_ids() From 7eb99ae2e7e450b52b2db51af89734bbb0e15753 Mon Sep 17 00:00:00 2001 From: Thijs Baaijen <13253091+Thijss@users.noreply.github.com> Date: Wed, 16 Jul 2025 18:36:56 +0200 Subject: [PATCH 2/4] move array_equal_with_nan Signed-off-by: Thijs Baaijen <13253091+Thijss@users.noreply.github.com> --- src/power_grid_model_ds/_core/fancypy.py | 18 +++--------------- src/power_grid_model_ds/_core/utils/misc.py | 20 ++++++++++++++++++++ 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/src/power_grid_model_ds/_core/fancypy.py b/src/power_grid_model_ds/_core/fancypy.py index a037e4f..cc30fcb 100644 --- a/src/power_grid_model_ds/_core/fancypy.py +++ b/src/power_grid_model_ds/_core/fancypy.py @@ -8,6 +8,8 @@ import numpy as np +from power_grid_model_ds._core.utils.misc import array_equal_with_nan + if TYPE_CHECKING: from power_grid_model_ds._core.model.arrays.base.array import FancyArray @@ -49,18 +51,4 @@ def array_equal(array1: "FancyArray", array2: "FancyArray", equal_nan: bool = Tr def _array_equal_with_nan(array1: "FancyArray", array2: "FancyArray") -> bool: - # np.array_equal does not work with NaN values in structured arrays, so we need to compare column by column. - # related issue: https://github.com/numpy/numpy/issues/21539 - - if array1.columns != array2.columns: - return False - - for column in array1.columns: - column_dtype = array1.dtype[column] - if np.issubdtype(column_dtype, np.str_): - if not np.array_equal(array1[column], array2[column]): - return False - continue - if not np.array_equal(array1[column], array2[column], equal_nan=True): - return False - return True + return array_equal_with_nan(array1.data, array2.data) diff --git a/src/power_grid_model_ds/_core/utils/misc.py b/src/power_grid_model_ds/_core/utils/misc.py index 0eeb64f..f6ee3f2 100644 --- a/src/power_grid_model_ds/_core/utils/misc.py +++ b/src/power_grid_model_ds/_core/utils/misc.py @@ -39,3 +39,23 @@ def get_inherited_attrs(cls: Type, *private_attributes): retrieved_attributes[private_attr] = attr_dict return retrieved_attributes + + +def array_equal_with_nan(array1: np.ndarray, array2: np.ndarray) -> bool: + """Compare two structured arrays for equality, treating NaN values as equal. + + np.array_equal does not work with NaN values in structured arrays, so we need to compare column by column. + related issue: https://github.com/numpy/numpy/issues/21539 + """ + if array1.dtype.names != array2.dtype.names: + return False + + for column in array1.dtype.names: + column_dtype = array1.dtype[column] + if np.issubdtype(column_dtype, np.str_): + if not np.array_equal(array1[column], array2[column]): + return False + continue + if not np.array_equal(array1[column], array2[column], equal_nan=True): + return False + return True From d92746c46d5ae94545033333f715d0f6bcc52aad Mon Sep 17 00:00:00 2001 From: Thijs Baaijen <13253091+Thijss@users.noreply.github.com> Date: Wed, 16 Jul 2025 18:37:06 +0200 Subject: [PATCH 3/4] add integration test Signed-off-by: Thijs Baaijen <13253091+Thijss@users.noreply.github.com> --- tests/integration/loadflow/test_power_grid_model.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/integration/loadflow/test_power_grid_model.py b/tests/integration/loadflow/test_power_grid_model.py index a91816e..3b7ae64 100644 --- a/tests/integration/loadflow/test_power_grid_model.py +++ b/tests/integration/loadflow/test_power_grid_model.py @@ -23,6 +23,7 @@ ) from power_grid_model_ds._core.model.arrays.pgm_arrays import TransformerTapRegulatorArray from power_grid_model_ds._core.model.grids.base import Grid +from power_grid_model_ds._core.utils.misc import array_equal_with_nan from tests.unit.model.grids.test_custom_grid import CustomGrid # pylint: disable=missing-function-docstring,missing-class-docstring @@ -605,3 +606,12 @@ def test_create_extended_grid_without_default_from_input_data(self, input_data_p with pytest.raises(ValueError, match="Missing required columns: {'extra_field'}"): core_interface.create_grid_from_input_data() + + +def test_convert_from_and_to_pgm_data(input_data_pgm): + grid = Grid.from_pgm_data(input_data_pgm) + pgm = PowerGridModelInterface(grid=grid) + pgm.create_input_from_grid() + + for array_name in pgm.input_data.keys(): + assert array_equal_with_nan(pgm.input_data[array_name], input_data_pgm[array_name]) From bc53e4048fb2ff6714fd08c89b2fa29ebb6ae2e8 Mon Sep 17 00:00:00 2001 From: Thijs Baaijen <13253091+Thijss@users.noreply.github.com> Date: Thu, 17 Jul 2025 09:43:27 +0200 Subject: [PATCH 4/4] fix typing issues Signed-off-by: Thijs Baaijen <13253091+Thijss@users.noreply.github.com> --- src/power_grid_model_ds/_core/utils/misc.py | 3 ++- .../_core/visualizer/callbacks/element_selection.py | 2 +- .../_core/visualizer/layout/cytoscape_config.py | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/power_grid_model_ds/_core/utils/misc.py b/src/power_grid_model_ds/_core/utils/misc.py index f6ee3f2..bf64df4 100644 --- a/src/power_grid_model_ds/_core/utils/misc.py +++ b/src/power_grid_model_ds/_core/utils/misc.py @@ -50,7 +50,8 @@ def array_equal_with_nan(array1: np.ndarray, array2: np.ndarray) -> bool: if array1.dtype.names != array2.dtype.names: return False - for column in array1.dtype.names: + columns: Sequence[str] = array1.dtype.names + for column in columns: column_dtype = array1.dtype[column] if np.issubdtype(column_dtype, np.str_): if not np.array_equal(array1[column], array2[column]): diff --git a/src/power_grid_model_ds/_core/visualizer/callbacks/element_selection.py b/src/power_grid_model_ds/_core/visualizer/callbacks/element_selection.py index ac03df3..03e8fbd 100644 --- a/src/power_grid_model_ds/_core/visualizer/callbacks/element_selection.py +++ b/src/power_grid_model_ds/_core/visualizer/callbacks/element_selection.py @@ -27,7 +27,7 @@ def display_selected_element(node_data, edge_data): def _to_data_table(data: dict[str, Any]): columns = data.keys() - data_table = dash_table.DataTable( + data_table = dash_table.DataTable( # type: ignore[attr-defined] data=[data], columns=[{"name": key, "id": key} for key in columns], editable=False ) return data_table diff --git a/src/power_grid_model_ds/_core/visualizer/layout/cytoscape_config.py b/src/power_grid_model_ds/_core/visualizer/layout/cytoscape_config.py index 40a8288..009b258 100644 --- a/src/power_grid_model_ds/_core/visualizer/layout/cytoscape_config.py +++ b/src/power_grid_model_ds/_core/visualizer/layout/cytoscape_config.py @@ -46,7 +46,7 @@ placeholder="Select layout", value="", clearable=False, - options=[{"label": name.capitalize(), "value": name} for name in LAYOUT_OPTIONS], + options=[{"label": name.capitalize(), "value": name} for name in LAYOUT_OPTIONS], # type: ignore style={"width": "200px"}, ), style={"margin": "0 20px 0 10px"},