power-grid-model-ds 1.3.3__tar.gz → 1.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. {power_grid_model_ds-1.3.3 → power_grid_model_ds-1.4.0}/PKG-INFO +1 -1
  2. power_grid_model_ds-1.4.0/PYPI_VERSION +1 -0
  3. {power_grid_model_ds-1.3.3 → power_grid_model_ds-1.4.0}/src/power_grid_model_ds/_core/fancypy.py +3 -19
  4. {power_grid_model_ds-1.3.3 → power_grid_model_ds-1.4.0}/src/power_grid_model_ds/_core/load_flow.py +19 -7
  5. {power_grid_model_ds-1.3.3 → power_grid_model_ds-1.4.0}/src/power_grid_model_ds/_core/model/arrays/base/array.py +8 -0
  6. {power_grid_model_ds-1.3.3 → power_grid_model_ds-1.4.0}/src/power_grid_model_ds/_core/model/graphs/container.py +0 -28
  7. {power_grid_model_ds-1.3.3 → power_grid_model_ds-1.4.0}/src/power_grid_model_ds/_core/model/graphs/models/base.py +12 -36
  8. {power_grid_model_ds-1.3.3 → power_grid_model_ds-1.4.0}/src/power_grid_model_ds/_core/model/grids/base.py +18 -0
  9. power_grid_model_ds-1.4.0/src/power_grid_model_ds/_core/model/utils.py +22 -0
  10. {power_grid_model_ds-1.3.3 → power_grid_model_ds-1.4.0}/src/power_grid_model_ds/_core/utils/misc.py +21 -0
  11. {power_grid_model_ds-1.3.3 → power_grid_model_ds-1.4.0}/src/power_grid_model_ds/_core/visualizer/app.py +1 -0
  12. {power_grid_model_ds-1.3.3 → power_grid_model_ds-1.4.0}/src/power_grid_model_ds/_core/visualizer/parsers.py +23 -1
  13. {power_grid_model_ds-1.3.3 → power_grid_model_ds-1.4.0}/src/power_grid_model_ds.egg-info/PKG-INFO +1 -1
  14. {power_grid_model_ds-1.3.3 → power_grid_model_ds-1.4.0}/src/power_grid_model_ds.egg-info/SOURCES.txt +1 -0
  15. power_grid_model_ds-1.3.3/PYPI_VERSION +0 -1
  16. {power_grid_model_ds-1.3.3 → power_grid_model_ds-1.4.0}/LICENSE +0 -0
  17. {power_grid_model_ds-1.3.3 → power_grid_model_ds-1.4.0}/README.md +0 -0
  18. {power_grid_model_ds-1.3.3 → power_grid_model_ds-1.4.0}/pyproject.toml +0 -0
  19. {power_grid_model_ds-1.3.3 → power_grid_model_ds-1.4.0}/setup.cfg +0 -0
  20. {power_grid_model_ds-1.3.3 → power_grid_model_ds-1.4.0}/setup.py +0 -0
  21. {power_grid_model_ds-1.3.3 → power_grid_model_ds-1.4.0}/src/power_grid_model_ds/__init__.py +0 -0
  22. {power_grid_model_ds-1.3.3 → power_grid_model_ds-1.4.0}/src/power_grid_model_ds/_core/__init__.py +0 -0
  23. {power_grid_model_ds-1.3.3 → power_grid_model_ds-1.4.0}/src/power_grid_model_ds/_core/data_source/__init__.py +0 -0
  24. {power_grid_model_ds-1.3.3 → power_grid_model_ds-1.4.0}/src/power_grid_model_ds/_core/data_source/generator/__init__.py +0 -0
  25. {power_grid_model_ds-1.3.3 → power_grid_model_ds-1.4.0}/src/power_grid_model_ds/_core/data_source/generator/arrays/__init__.py +0 -0
  26. {power_grid_model_ds-1.3.3 → power_grid_model_ds-1.4.0}/src/power_grid_model_ds/_core/data_source/generator/arrays/base.py +0 -0
  27. {power_grid_model_ds-1.3.3 → power_grid_model_ds-1.4.0}/src/power_grid_model_ds/_core/data_source/generator/arrays/line.py +0 -0
  28. {power_grid_model_ds-1.3.3 → power_grid_model_ds-1.4.0}/src/power_grid_model_ds/_core/data_source/generator/arrays/node.py +0 -0
  29. {power_grid_model_ds-1.3.3 → power_grid_model_ds-1.4.0}/src/power_grid_model_ds/_core/data_source/generator/arrays/source.py +0 -0
  30. {power_grid_model_ds-1.3.3 → power_grid_model_ds-1.4.0}/src/power_grid_model_ds/_core/data_source/generator/arrays/transformer.py +0 -0
  31. {power_grid_model_ds-1.3.3 → power_grid_model_ds-1.4.0}/src/power_grid_model_ds/_core/data_source/generator/grid_generators.py +0 -0
  32. {power_grid_model_ds-1.3.3 → power_grid_model_ds-1.4.0}/src/power_grid_model_ds/_core/model/__init__.py +0 -0
  33. {power_grid_model_ds-1.3.3 → power_grid_model_ds-1.4.0}/src/power_grid_model_ds/_core/model/arrays/__init__.py +0 -0
  34. {power_grid_model_ds-1.3.3 → power_grid_model_ds-1.4.0}/src/power_grid_model_ds/_core/model/arrays/base/__init__.py +0 -0
  35. {power_grid_model_ds-1.3.3 → power_grid_model_ds-1.4.0}/src/power_grid_model_ds/_core/model/arrays/base/_build.py +0 -0
  36. {power_grid_model_ds-1.3.3 → power_grid_model_ds-1.4.0}/src/power_grid_model_ds/_core/model/arrays/base/_filters.py +0 -0
  37. {power_grid_model_ds-1.3.3 → power_grid_model_ds-1.4.0}/src/power_grid_model_ds/_core/model/arrays/base/_modify.py +0 -0
  38. {power_grid_model_ds-1.3.3 → power_grid_model_ds-1.4.0}/src/power_grid_model_ds/_core/model/arrays/base/_optional.py +0 -0
  39. {power_grid_model_ds-1.3.3 → power_grid_model_ds-1.4.0}/src/power_grid_model_ds/_core/model/arrays/base/_string.py +0 -0
  40. {power_grid_model_ds-1.3.3 → power_grid_model_ds-1.4.0}/src/power_grid_model_ds/_core/model/arrays/base/errors.py +0 -0
  41. {power_grid_model_ds-1.3.3 → power_grid_model_ds-1.4.0}/src/power_grid_model_ds/_core/model/arrays/pgm_arrays.py +0 -0
  42. {power_grid_model_ds-1.3.3 → power_grid_model_ds-1.4.0}/src/power_grid_model_ds/_core/model/constants.py +0 -0
  43. {power_grid_model_ds-1.3.3 → power_grid_model_ds-1.4.0}/src/power_grid_model_ds/_core/model/containers/__init__.py +0 -0
  44. {power_grid_model_ds-1.3.3 → power_grid_model_ds-1.4.0}/src/power_grid_model_ds/_core/model/containers/base.py +0 -0
  45. {power_grid_model_ds-1.3.3 → power_grid_model_ds-1.4.0}/src/power_grid_model_ds/_core/model/dtypes/__init__.py +0 -0
  46. {power_grid_model_ds-1.3.3 → power_grid_model_ds-1.4.0}/src/power_grid_model_ds/_core/model/dtypes/appliances.py +0 -0
  47. {power_grid_model_ds-1.3.3 → power_grid_model_ds-1.4.0}/src/power_grid_model_ds/_core/model/dtypes/branches.py +0 -0
  48. {power_grid_model_ds-1.3.3 → power_grid_model_ds-1.4.0}/src/power_grid_model_ds/_core/model/dtypes/id.py +0 -0
  49. {power_grid_model_ds-1.3.3 → power_grid_model_ds-1.4.0}/src/power_grid_model_ds/_core/model/dtypes/nodes.py +0 -0
  50. {power_grid_model_ds-1.3.3 → power_grid_model_ds-1.4.0}/src/power_grid_model_ds/_core/model/dtypes/regulators.py +0 -0
  51. {power_grid_model_ds-1.3.3 → power_grid_model_ds-1.4.0}/src/power_grid_model_ds/_core/model/dtypes/sensors.py +0 -0
  52. {power_grid_model_ds-1.3.3 → power_grid_model_ds-1.4.0}/src/power_grid_model_ds/_core/model/enums/__init__.py +0 -0
  53. {power_grid_model_ds-1.3.3 → power_grid_model_ds-1.4.0}/src/power_grid_model_ds/_core/model/enums/nodes.py +0 -0
  54. {power_grid_model_ds-1.3.3 → power_grid_model_ds-1.4.0}/src/power_grid_model_ds/_core/model/graphs/__init__.py +0 -0
  55. {power_grid_model_ds-1.3.3 → power_grid_model_ds-1.4.0}/src/power_grid_model_ds/_core/model/graphs/errors.py +0 -0
  56. {power_grid_model_ds-1.3.3 → power_grid_model_ds-1.4.0}/src/power_grid_model_ds/_core/model/graphs/models/__init__.py +0 -0
  57. {power_grid_model_ds-1.3.3 → power_grid_model_ds-1.4.0}/src/power_grid_model_ds/_core/model/graphs/models/_rustworkx_search.py +0 -0
  58. {power_grid_model_ds-1.3.3 → power_grid_model_ds-1.4.0}/src/power_grid_model_ds/_core/model/graphs/models/rustworkx.py +0 -0
  59. {power_grid_model_ds-1.3.3 → power_grid_model_ds-1.4.0}/src/power_grid_model_ds/_core/model/grids/__init__.py +0 -0
  60. {power_grid_model_ds-1.3.3 → power_grid_model_ds-1.4.0}/src/power_grid_model_ds/_core/model/grids/_text_sources.py +0 -0
  61. {power_grid_model_ds-1.3.3 → power_grid_model_ds-1.4.0}/src/power_grid_model_ds/_core/model/grids/helpers.py +0 -0
  62. {power_grid_model_ds-1.3.3 → power_grid_model_ds-1.4.0}/src/power_grid_model_ds/_core/utils/__init__.py +0 -0
  63. {power_grid_model_ds-1.3.3 → power_grid_model_ds-1.4.0}/src/power_grid_model_ds/_core/utils/pickle.py +0 -0
  64. {power_grid_model_ds-1.3.3 → power_grid_model_ds-1.4.0}/src/power_grid_model_ds/_core/utils/zip.py +0 -0
  65. {power_grid_model_ds-1.3.3 → power_grid_model_ds-1.4.0}/src/power_grid_model_ds/_core/visualizer/__init__.py +0 -0
  66. {power_grid_model_ds-1.3.3 → power_grid_model_ds-1.4.0}/src/power_grid_model_ds/_core/visualizer/callbacks/__init__.py +0 -0
  67. {power_grid_model_ds-1.3.3 → power_grid_model_ds-1.4.0}/src/power_grid_model_ds/_core/visualizer/callbacks/element_scaling.py +0 -0
  68. {power_grid_model_ds-1.3.3 → power_grid_model_ds-1.4.0}/src/power_grid_model_ds/_core/visualizer/callbacks/element_selection.py +0 -0
  69. {power_grid_model_ds-1.3.3 → power_grid_model_ds-1.4.0}/src/power_grid_model_ds/_core/visualizer/callbacks/layout_dropdown.py +0 -0
  70. {power_grid_model_ds-1.3.3 → power_grid_model_ds-1.4.0}/src/power_grid_model_ds/_core/visualizer/callbacks/search_form.py +0 -0
  71. {power_grid_model_ds-1.3.3 → power_grid_model_ds-1.4.0}/src/power_grid_model_ds/_core/visualizer/layout/__init__.py +0 -0
  72. {power_grid_model_ds-1.3.3 → power_grid_model_ds-1.4.0}/src/power_grid_model_ds/_core/visualizer/layout/colors.py +0 -0
  73. {power_grid_model_ds-1.3.3 → power_grid_model_ds-1.4.0}/src/power_grid_model_ds/_core/visualizer/layout/cytoscape_config.py +0 -0
  74. {power_grid_model_ds-1.3.3 → power_grid_model_ds-1.4.0}/src/power_grid_model_ds/_core/visualizer/layout/cytoscape_html.py +0 -0
  75. {power_grid_model_ds-1.3.3 → power_grid_model_ds-1.4.0}/src/power_grid_model_ds/_core/visualizer/layout/cytoscape_styling.py +0 -0
  76. {power_grid_model_ds-1.3.3 → power_grid_model_ds-1.4.0}/src/power_grid_model_ds/_core/visualizer/layout/header.py +0 -0
  77. {power_grid_model_ds-1.3.3 → power_grid_model_ds-1.4.0}/src/power_grid_model_ds/_core/visualizer/layout/legenda.py +0 -0
  78. {power_grid_model_ds-1.3.3 → power_grid_model_ds-1.4.0}/src/power_grid_model_ds/_core/visualizer/layout/search_form.py +0 -0
  79. {power_grid_model_ds-1.3.3 → power_grid_model_ds-1.4.0}/src/power_grid_model_ds/_core/visualizer/layout/selection_output.py +0 -0
  80. {power_grid_model_ds-1.3.3 → power_grid_model_ds-1.4.0}/src/power_grid_model_ds/arrays.py +0 -0
  81. {power_grid_model_ds-1.3.3 → power_grid_model_ds-1.4.0}/src/power_grid_model_ds/constants.py +0 -0
  82. {power_grid_model_ds-1.3.3 → power_grid_model_ds-1.4.0}/src/power_grid_model_ds/enums.py +0 -0
  83. {power_grid_model_ds-1.3.3 → power_grid_model_ds-1.4.0}/src/power_grid_model_ds/errors.py +0 -0
  84. {power_grid_model_ds-1.3.3 → power_grid_model_ds-1.4.0}/src/power_grid_model_ds/fancypy.py +0 -0
  85. {power_grid_model_ds-1.3.3 → power_grid_model_ds-1.4.0}/src/power_grid_model_ds/generators.py +0 -0
  86. {power_grid_model_ds-1.3.3 → power_grid_model_ds-1.4.0}/src/power_grid_model_ds/graph_models.py +0 -0
  87. {power_grid_model_ds-1.3.3 → power_grid_model_ds-1.4.0}/src/power_grid_model_ds/visualizer.py +0 -0
  88. {power_grid_model_ds-1.3.3 → power_grid_model_ds-1.4.0}/src/power_grid_model_ds.egg-info/dependency_links.txt +0 -0
  89. {power_grid_model_ds-1.3.3 → power_grid_model_ds-1.4.0}/src/power_grid_model_ds.egg-info/requires.txt +0 -0
  90. {power_grid_model_ds-1.3.3 → power_grid_model_ds-1.4.0}/src/power_grid_model_ds.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: power-grid-model-ds
3
- Version: 1.3.3
3
+ Version: 1.4.0
4
4
  Summary: Power Grid Model extension which provides a grid data structure for simulation and analysis
5
5
  Author-email: Contributors to the Power Grid Model project <powergridmodel@lfenergy.org>
6
6
  License: MPL-2.0
@@ -0,0 +1 @@
1
+ 1.4.0
@@ -8,6 +8,8 @@ from typing import TYPE_CHECKING, Union
8
8
 
9
9
  import numpy as np
10
10
 
11
+ from power_grid_model_ds._core.utils.misc import array_equal_with_nan
12
+
11
13
  if TYPE_CHECKING:
12
14
  from power_grid_model_ds._core.model.arrays.base.array import FancyArray
13
15
 
@@ -44,23 +46,5 @@ def sort(array: "FancyArray", axis=-1, kind=None, order=None) -> "FancyArray":
44
46
  def array_equal(array1: "FancyArray", array2: "FancyArray", equal_nan: bool = True) -> bool:
45
47
  """Return True if two arrays are equal."""
46
48
  if equal_nan:
47
- return _array_equal_with_nan(array1, array2)
49
+ return array_equal_with_nan(array1.data, array2.data)
48
50
  return np.array_equal(array1.data, array2.data)
49
-
50
-
51
- def _array_equal_with_nan(array1: "FancyArray", array2: "FancyArray") -> bool:
52
- # np.array_equal does not work with NaN values in structured arrays, so we need to compare column by column.
53
- # related issue: https://github.com/numpy/numpy/issues/21539
54
-
55
- if array1.columns != array2.columns:
56
- return False
57
-
58
- for column in array1.columns:
59
- column_dtype = array1.dtype[column]
60
- if np.issubdtype(column_dtype, np.str_):
61
- if not np.array_equal(array1[column], array2[column]):
62
- return False
63
- continue
64
- if not np.array_equal(array1[column], array2[column], equal_nan=True):
65
- return False
66
- return True
@@ -4,6 +4,7 @@
4
4
 
5
5
  """Power flow functions and classes"""
6
6
 
7
+ import warnings
7
8
  from typing import Dict, Optional
8
9
 
9
10
  import numpy as np
@@ -50,18 +51,29 @@ class PowerGridModelInterface:
50
51
  self.grid = grid or Grid.empty()
51
52
  self.system_frequency = system_frequency
52
53
 
53
- self.input_data = input_data or {}
54
+ self._input_data = input_data or {}
54
55
  self.output_data: dict[str, NDArray] = {}
55
56
  self.model: Optional[PowerGridModel] = None
56
57
 
58
+ @property
59
+ def input_data(self) -> Dict[str, NDArray]:
60
+ """Get the input data for the PowerGridModel."""
61
+ warnings.warn(
62
+ "Input data has been made private and will be removed as public properety in a future version. "
63
+ "Do not use it directly.",
64
+ DeprecationWarning,
65
+ stacklevel=2,
66
+ )
67
+ return self._input_data
68
+
57
69
  def create_input_from_grid(self):
58
70
  """
59
71
  Create input for the PowerGridModel
60
72
  """
61
73
  for array_name in PGM_ARRAYS:
62
74
  pgm_array = self._create_power_grid_array(array_name=array_name)
63
- self.input_data[array_name] = pgm_array
64
- return self.input_data
75
+ self._input_data[array_name] = pgm_array
76
+ return self._input_data
65
77
 
66
78
  def create_grid_from_input_data(self, check_ids: bool = True) -> Grid:
67
79
  """
@@ -75,9 +87,9 @@ class PowerGridModelInterface:
75
87
  Returns a Grid object with the arrays filled with the PowerGridModel input.
76
88
  """
77
89
  for pgm_name in PGM_ARRAYS:
78
- if pgm_name in self.input_data:
90
+ if pgm_name in self._input_data:
79
91
  pgm_ds_array_class = getattr(self.grid, pgm_name).__class__
80
- pgm_ds_array = pgm_ds_array_class(self.input_data[pgm_name])
92
+ pgm_ds_array = pgm_ds_array_class(self._input_data[pgm_name])
81
93
  self.grid.append(pgm_ds_array, check_max_id=False)
82
94
  if check_ids:
83
95
  self.grid.check_ids()
@@ -155,6 +167,6 @@ class PowerGridModelInterface:
155
167
  return list(set(first_dtype.names).intersection(set(second_dtype.names))) # type: ignore[arg-type]
156
168
 
157
169
  def _setup_model(self):
158
- self.input_data = self.input_data or self.create_input_from_grid()
159
- self.model = PowerGridModel(self.input_data, system_frequency=self.system_frequency)
170
+ self._input_data = self._input_data or self.create_input_from_grid()
171
+ self.model = PowerGridModel(self._input_data, system_frequency=self.system_frequency)
160
172
  return self.model
@@ -323,3 +323,11 @@ class FancyArray(ABC):
323
323
  if pandas is None:
324
324
  raise ImportError("pandas is not installed")
325
325
  return pandas.DataFrame(self._data)
326
+
327
+ @classmethod
328
+ def from_extended(cls: Type[Self], extended: Self) -> Self:
329
+ """Create an instance from an extended array."""
330
+ if not isinstance(extended, cls):
331
+ raise TypeError(f"Extended array must be of type {cls.__name__}, got {type(extended).__name__}")
332
+ dtype = cls.get_dtype()
333
+ return cls(data=np.array(extended[list(dtype.names)], dtype=dtype))
@@ -5,7 +5,6 @@
5
5
  """Stores the GraphContainer class"""
6
6
 
7
7
  import dataclasses
8
- import warnings
9
8
  from dataclasses import dataclass
10
9
  from typing import TYPE_CHECKING, Generator
11
10
 
@@ -63,45 +62,18 @@ class GraphContainer:
63
62
  graph = getattr(self, field.name)
64
63
  graph.add_node_array(node_array=node_array, raise_on_fail=False)
65
64
 
66
- def add_node(self, node: NodeArray) -> None:
67
- """Add a node to all graphs"""
68
- warnings.warn(
69
- "add_node is deprecated and will be removed in a future release, use add_node_array instead",
70
- category=DeprecationWarning,
71
- stacklevel=2,
72
- )
73
- self.add_node_array(node_array=node)
74
-
75
65
  def add_branch_array(self, branch_array: BranchArray) -> None:
76
66
  """Add a branch to all graphs"""
77
67
  for field in self.graph_attributes:
78
68
  graph = getattr(self, field.name)
79
69
  graph.add_branch_array(branch_array=branch_array)
80
70
 
81
- def add_branch(self, branch: BranchArray) -> None:
82
- """Add a branch to all graphs"""
83
- warnings.warn(
84
- "add_branch is deprecated and will be removed in a future release, use add_branch_array instead",
85
- category=DeprecationWarning,
86
- stacklevel=2,
87
- )
88
- self.add_branch_array(branch_array=branch)
89
-
90
71
  def add_branch3_array(self, branch3_array: Branch3Array) -> None:
91
72
  """Add a branch to all graphs"""
92
73
  for field in self.graph_attributes:
93
74
  graph = getattr(self, field.name)
94
75
  graph.add_branch3_array(branch3_array=branch3_array)
95
76
 
96
- def add_branch3(self, branch: Branch3Array) -> None:
97
- """Add a branch to all graphs"""
98
- warnings.warn(
99
- "add_branch3 is deprecated and will be removed in a future release, use add_branch3_array instead",
100
- category=DeprecationWarning,
101
- stacklevel=2,
102
- )
103
- self.add_branch3_array(branch3_array=branch)
104
-
105
77
  def delete_node(self, node: NodeArray) -> None:
106
78
  """Remove a node from all graphs"""
107
79
  for field in dataclasses.fields(self):
@@ -2,7 +2,6 @@
2
2
  #
3
3
  # SPDX-License-Identifier: MPL-2.0
4
4
 
5
- import warnings
6
5
  from abc import ABC, abstractmethod
7
6
  from contextlib import contextmanager
8
7
  from typing import TYPE_CHECKING, Generator
@@ -16,6 +15,7 @@ from power_grid_model_ds._core.model.graphs.errors import (
16
15
  MissingNodeError,
17
16
  NoPathBetweenNodes,
18
17
  )
18
+ from power_grid_model_ds._core.model.utils import _get_branch3_branches
19
19
 
20
20
  if TYPE_CHECKING:
21
21
  from power_grid_model_ds._core.model.grids.base import Grid
@@ -253,23 +253,17 @@ class BaseGraphModel(ABC):
253
253
 
254
254
  return [self._internals_to_externals(path) for path in internal_paths]
255
255
 
256
- def get_components(self, substation_nodes: list[int] | None = None) -> list[list[int]]:
257
- """Returns all separate components when the substation_nodes are removed of the graph as lists"""
258
- if substation_nodes:
259
- warnings.warn(
260
- message="""
261
- get_components: substation_nodes argument is deprecated and will be removed in a future release.
262
- The functionality is still available with the use of the `tmp_remove_nodes` context manager.
263
-
264
- Example:
265
- >>> with graph.tmp_remove_nodes(substation_nodes):
266
- >>> components = graph.get_components()
267
- """,
268
- category=DeprecationWarning,
269
- stacklevel=2,
270
- )
271
- with self.tmp_remove_nodes(substation_nodes or []):
272
- internal_components = self._get_components()
256
+ def get_components(self) -> list[list[int]]:
257
+ """Returns all separate components when the substation_nodes are removed of the graph as lists
258
+
259
+ If you want to get the components of the graph without certain nodes,
260
+ use the `tmp_remove_nodes` context manager.
261
+
262
+ Example:
263
+ >>> with graph.tmp_remove_nodes(substation_nodes):
264
+ >>> components = graph.get_components()
265
+ """
266
+ internal_components = self._get_components()
273
267
  return [self._internals_to_externals(component) for component in internal_components]
274
268
 
275
269
  def get_connected(
@@ -421,21 +415,3 @@ Example:
421
415
 
422
416
  @abstractmethod
423
417
  def _all_branches(self) -> Generator[tuple[int, int], None, None]: ...
424
-
425
-
426
- def _get_branch3_branches(branch3: Branch3Array) -> BranchArray:
427
- node_1 = branch3.node_1.item()
428
- node_2 = branch3.node_2.item()
429
- node_3 = branch3.node_3.item()
430
-
431
- status_1 = branch3.status_1.item()
432
- status_2 = branch3.status_2.item()
433
- status_3 = branch3.status_3.item()
434
-
435
- branches = BranchArray.zeros(3)
436
- branches.from_node = [node_1, node_1, node_2]
437
- branches.to_node = [node_2, node_3, node_3]
438
- branches.from_status = [status_1, status_1, status_2]
439
- branches.to_status = [status_2, status_3, status_3]
440
-
441
- return branches
@@ -440,6 +440,24 @@ class Grid(FancyArrayContainer):
440
440
  set_is_feeder(grid=self)
441
441
  set_feeder_ids(grid=self)
442
442
 
443
+ @classmethod
444
+ def from_extended(cls, extended: "Grid") -> "Grid":
445
+ """Create a grid from an extended Grid object."""
446
+ new_grid = cls.empty()
447
+
448
+ # Add nodes first, so that branches can reference them
449
+ new_grid.append(new_grid.node.__class__.from_extended(extended.node))
450
+
451
+ for field in dataclasses.fields(cls):
452
+ if field.name == "node":
453
+ continue # already added
454
+ if issubclass(field.type, FancyArray):
455
+ extended_array = getattr(extended, field.name)
456
+ new_array = field.type.from_extended(extended_array)
457
+ new_grid.append(new_array, check_max_id=False)
458
+
459
+ return new_grid
460
+
443
461
 
444
462
  def _add_branch_array(branch: BranchArray | Branch3Array, grid: Grid):
445
463
  """Add a branch array to the grid"""
@@ -0,0 +1,22 @@
1
+ # SPDX-FileCopyrightText: Contributors to the Power Grid Model project <powergridmodel@lfenergy.org>
2
+ #
3
+ # SPDX-License-Identifier: MPL-2.0
4
+ from power_grid_model_ds._core.model.arrays.pgm_arrays import Branch3Array, BranchArray
5
+
6
+
7
+ def _get_branch3_branches(branch3: Branch3Array) -> BranchArray:
8
+ node_1 = branch3.node_1.item()
9
+ node_2 = branch3.node_2.item()
10
+ node_3 = branch3.node_3.item()
11
+
12
+ status_1 = branch3.status_1.item()
13
+ status_2 = branch3.status_2.item()
14
+ status_3 = branch3.status_3.item()
15
+
16
+ branches = BranchArray.zeros(3)
17
+ branches.from_node = [node_1, node_1, node_2]
18
+ branches.to_node = [node_2, node_3, node_3]
19
+ branches.from_status = [status_1, status_1, status_2]
20
+ branches.to_status = [status_2, status_3, status_3]
21
+
22
+ return branches
@@ -39,3 +39,24 @@ def get_inherited_attrs(cls: Type, *private_attributes):
39
39
  retrieved_attributes[private_attr] = attr_dict
40
40
 
41
41
  return retrieved_attributes
42
+
43
+
44
+ def array_equal_with_nan(array1: np.ndarray, array2: np.ndarray) -> bool:
45
+ """Compare two structured arrays for equality, treating NaN values as equal.
46
+
47
+ np.array_equal does not work with NaN values in structured arrays, so we need to compare column by column.
48
+ related issue: https://github.com/numpy/numpy/issues/21539
49
+ """
50
+ if array1.dtype.names != array2.dtype.names:
51
+ return False
52
+
53
+ columns: Sequence[str] = array1.dtype.names
54
+ for column in columns:
55
+ column_dtype = array1.dtype[column]
56
+ if np.issubdtype(column_dtype, np.str_):
57
+ if not np.array_equal(array1[column], array2[column]):
58
+ return False
59
+ continue
60
+ if not np.array_equal(array1[column], array2[column], equal_nan=True):
61
+ return False
62
+ return True
@@ -60,6 +60,7 @@ def _get_columns_store(grid: Grid) -> dcc.Store:
60
60
  "line": grid.line.columns,
61
61
  "link": grid.link.columns,
62
62
  "transformer": grid.transformer.columns,
63
+ "three_winding_transformer": grid.three_winding_transformer.columns,
63
64
  "branch": grid.branches.columns,
64
65
  },
65
66
  )
@@ -6,7 +6,8 @@ from typing import Any, Literal
6
6
 
7
7
  from power_grid_model_ds._core.model.arrays.base.array import FancyArray
8
8
  from power_grid_model_ds._core.model.grids.base import Grid
9
- from power_grid_model_ds.arrays import BranchArray, NodeArray
9
+ from power_grid_model_ds._core.model.utils import _get_branch3_branches
10
+ from power_grid_model_ds.arrays import Branch3Array, BranchArray, NodeArray
10
11
 
11
12
 
12
13
  def parse_node_array(nodes: NodeArray) -> list[dict[str, Any]]:
@@ -32,6 +33,27 @@ def parse_branches(grid: Grid) -> list[dict[str, Any]]:
32
33
  parsed_branches.extend(parse_branch_array(grid.line, "line"))
33
34
  parsed_branches.extend(parse_branch_array(grid.link, "link"))
34
35
  parsed_branches.extend(parse_branch_array(grid.transformer, "transformer"))
36
+ parsed_branches.extend(parse_branch3_array(grid.three_winding_transformer, "transformer"))
37
+ return parsed_branches
38
+
39
+
40
+ def parse_branch3_array(branches: Branch3Array, group: Literal["transformer"]) -> list[dict[str, Any]]:
41
+ """Parse the three-winding transformer array."""
42
+ parsed_branches = []
43
+ columns = branches.columns
44
+ for branch in branches:
45
+ for branch_ in _get_branch3_branches(branch):
46
+ cyto_elements = {"data": _array_to_dict(branch_, columns)}
47
+ cyto_elements["data"].update(
48
+ {
49
+ # IDs need to be unique, so we combine the branch ID with the from and to nodes
50
+ "id": str(branch.id.item()) + f"_{branch_.from_node.item()}_{branch_.to_node.item()}",
51
+ "source": str(branch_.from_node.item()),
52
+ "target": str(branch_.to_node.item()),
53
+ "group": group,
54
+ }
55
+ )
56
+ parsed_branches.append(cyto_elements)
35
57
  return parsed_branches
36
58
 
37
59
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: power-grid-model-ds
3
- Version: 1.3.3
3
+ Version: 1.4.0
4
4
  Summary: Power Grid Model extension which provides a grid data structure for simulation and analysis
5
5
  Author-email: Contributors to the Power Grid Model project <powergridmodel@lfenergy.org>
6
6
  License: MPL-2.0
@@ -31,6 +31,7 @@ src/power_grid_model_ds/_core/data_source/generator/arrays/source.py
31
31
  src/power_grid_model_ds/_core/data_source/generator/arrays/transformer.py
32
32
  src/power_grid_model_ds/_core/model/__init__.py
33
33
  src/power_grid_model_ds/_core/model/constants.py
34
+ src/power_grid_model_ds/_core/model/utils.py
34
35
  src/power_grid_model_ds/_core/model/arrays/__init__.py
35
36
  src/power_grid_model_ds/_core/model/arrays/pgm_arrays.py
36
37
  src/power_grid_model_ds/_core/model/arrays/base/__init__.py
@@ -1 +0,0 @@
1
- 1.3.3