power-grid-model-ds 0.0.1a11709467271__py3-none-any.whl

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 (64) hide show
  1. power_grid_model_ds/__init__.py +9 -0
  2. power_grid_model_ds/_core/__init__.py +0 -0
  3. power_grid_model_ds/_core/data_source/__init__.py +0 -0
  4. power_grid_model_ds/_core/data_source/generator/__init__.py +0 -0
  5. power_grid_model_ds/_core/data_source/generator/arrays/__init__.py +0 -0
  6. power_grid_model_ds/_core/data_source/generator/arrays/base.py +25 -0
  7. power_grid_model_ds/_core/data_source/generator/arrays/line.py +133 -0
  8. power_grid_model_ds/_core/data_source/generator/arrays/node.py +37 -0
  9. power_grid_model_ds/_core/data_source/generator/arrays/source.py +30 -0
  10. power_grid_model_ds/_core/data_source/generator/arrays/transformer.py +37 -0
  11. power_grid_model_ds/_core/data_source/generator/grid_generators.py +78 -0
  12. power_grid_model_ds/_core/fancypy.py +66 -0
  13. power_grid_model_ds/_core/load_flow.py +140 -0
  14. power_grid_model_ds/_core/model/__init__.py +0 -0
  15. power_grid_model_ds/_core/model/arrays/__init__.py +43 -0
  16. power_grid_model_ds/_core/model/arrays/base/__init__.py +0 -0
  17. power_grid_model_ds/_core/model/arrays/base/_build.py +166 -0
  18. power_grid_model_ds/_core/model/arrays/base/_filters.py +115 -0
  19. power_grid_model_ds/_core/model/arrays/base/_modify.py +64 -0
  20. power_grid_model_ds/_core/model/arrays/base/_optional.py +11 -0
  21. power_grid_model_ds/_core/model/arrays/base/_string.py +94 -0
  22. power_grid_model_ds/_core/model/arrays/base/array.py +325 -0
  23. power_grid_model_ds/_core/model/arrays/base/errors.py +17 -0
  24. power_grid_model_ds/_core/model/arrays/pgm_arrays.py +122 -0
  25. power_grid_model_ds/_core/model/constants.py +27 -0
  26. power_grid_model_ds/_core/model/containers/__init__.py +0 -0
  27. power_grid_model_ds/_core/model/containers/base.py +244 -0
  28. power_grid_model_ds/_core/model/containers/grid_protocol.py +22 -0
  29. power_grid_model_ds/_core/model/dtypes/__init__.py +0 -0
  30. power_grid_model_ds/_core/model/dtypes/appliances.py +39 -0
  31. power_grid_model_ds/_core/model/dtypes/branches.py +117 -0
  32. power_grid_model_ds/_core/model/dtypes/id.py +19 -0
  33. power_grid_model_ds/_core/model/dtypes/nodes.py +27 -0
  34. power_grid_model_ds/_core/model/dtypes/regulators.py +30 -0
  35. power_grid_model_ds/_core/model/dtypes/sensors.py +63 -0
  36. power_grid_model_ds/_core/model/enums/__init__.py +0 -0
  37. power_grid_model_ds/_core/model/enums/nodes.py +16 -0
  38. power_grid_model_ds/_core/model/graphs/__init__.py +0 -0
  39. power_grid_model_ds/_core/model/graphs/container.py +158 -0
  40. power_grid_model_ds/_core/model/graphs/errors.py +19 -0
  41. power_grid_model_ds/_core/model/graphs/models/__init__.py +7 -0
  42. power_grid_model_ds/_core/model/graphs/models/_rustworkx_search.py +63 -0
  43. power_grid_model_ds/_core/model/graphs/models/base.py +326 -0
  44. power_grid_model_ds/_core/model/graphs/models/rustworkx.py +119 -0
  45. power_grid_model_ds/_core/model/grids/__init__.py +0 -0
  46. power_grid_model_ds/_core/model/grids/_text_sources.py +119 -0
  47. power_grid_model_ds/_core/model/grids/base.py +434 -0
  48. power_grid_model_ds/_core/model/grids/helpers.py +122 -0
  49. power_grid_model_ds/_core/utils/__init__.py +0 -0
  50. power_grid_model_ds/_core/utils/misc.py +41 -0
  51. power_grid_model_ds/_core/utils/pickle.py +47 -0
  52. power_grid_model_ds/_core/utils/zip.py +72 -0
  53. power_grid_model_ds/arrays.py +39 -0
  54. power_grid_model_ds/constants.py +7 -0
  55. power_grid_model_ds/enums.py +7 -0
  56. power_grid_model_ds/errors.py +27 -0
  57. power_grid_model_ds/fancypy.py +9 -0
  58. power_grid_model_ds/generators.py +11 -0
  59. power_grid_model_ds/graph_models.py +8 -0
  60. power_grid_model_ds-0.0.1a11709467271.dist-info/LICENSE +292 -0
  61. power_grid_model_ds-0.0.1a11709467271.dist-info/METADATA +80 -0
  62. power_grid_model_ds-0.0.1a11709467271.dist-info/RECORD +64 -0
  63. power_grid_model_ds-0.0.1a11709467271.dist-info/WHEEL +5 -0
  64. power_grid_model_ds-0.0.1a11709467271.dist-info/top_level.txt +1 -0
@@ -0,0 +1,325 @@
1
+ # SPDX-FileCopyrightText: Contributors to the Power Grid Model project <powergridmodel@lfenergy.org>
2
+ #
3
+ # SPDX-License-Identifier: MPL-2.0
4
+
5
+ from abc import ABC
6
+ from collections import namedtuple
7
+ from copy import copy
8
+ from functools import lru_cache
9
+ from typing import Any, Iterable, Literal, Type, TypeVar
10
+
11
+ import numpy as np
12
+ from numpy.typing import ArrayLike, NDArray
13
+
14
+ from power_grid_model_ds._core.model.arrays.base._build import build_array
15
+ from power_grid_model_ds._core.model.arrays.base._filters import apply_exclude, apply_filter, apply_get, get_filter_mask
16
+ from power_grid_model_ds._core.model.arrays.base._modify import check_ids, re_order, update_by_id
17
+ from power_grid_model_ds._core.model.arrays.base._optional import pandas
18
+ from power_grid_model_ds._core.model.arrays.base._string import convert_array_to_string
19
+ from power_grid_model_ds._core.model.arrays.base.errors import ArrayDefinitionError
20
+ from power_grid_model_ds._core.model.constants import EMPTY_ID, empty
21
+ from power_grid_model_ds._core.utils.misc import get_inherited_attrs
22
+
23
+ # pylint: disable=missing-function-docstring, too-many-public-methods
24
+
25
+ _RESERVED_COLUMN_NAMES: set = set(dir(np.array([]))).union({"data"})
26
+ _DEFAULT_STR_LENGTH: int = 50
27
+
28
+ Column = NDArray
29
+
30
+ Self = TypeVar("Self", bound="FancyArray")
31
+
32
+
33
+ class FancyArray(ABC):
34
+ """Base class for all arrays.
35
+
36
+ You can create your own array by subclassing FancyArray.
37
+ Array-columns can be defined by adding class attributes with the column name and the numpy dtype.
38
+
39
+ Example:
40
+ >>> class MyArray(FancyArray):
41
+ >>> id: NDArray[np.int64]
42
+ >>> name: NDArray[np.str_]
43
+ >>> value: NDArray[np.float64]
44
+
45
+ Note on string-columns:
46
+ The default length for string columns is stored in _DEFAULT_STR_LENGTH.
47
+ To change this, you can set the _str_lengths class attribute.
48
+
49
+ Example:
50
+ >>> class MyArray(FancyArray):
51
+ >>> name: NDArray[np.str_]
52
+ >>> _str_lengths = {"name": 100}
53
+
54
+ Extra note on string-columns:
55
+ Where possible, it is recommended use IntEnum's instead of string-columns to reduce memory usage.
56
+ """
57
+
58
+ _data: NDArray = np.ndarray([])
59
+ _defaults: dict[str, Any] = {}
60
+ _str_lengths: dict[str, int] = {}
61
+
62
+ def __init__(self: Self, *args, data: NDArray | None = None, **kwargs):
63
+ if data is None:
64
+ self._data = build_array(*args, dtype=self.get_dtype(), defaults=self.get_defaults(), **kwargs)
65
+ else:
66
+ self._data = data
67
+
68
+ @property
69
+ def data(self: Self):
70
+ return self._data
71
+
72
+ @classmethod
73
+ @lru_cache
74
+ def get_defaults(cls) -> dict[str, Any]:
75
+ return get_inherited_attrs(cls, "_defaults")["_defaults"]
76
+
77
+ @classmethod
78
+ @lru_cache
79
+ def get_dtype(cls):
80
+ annotations = get_inherited_attrs(cls, "_str_lengths")
81
+ str_lengths = annotations.pop("_str_lengths")
82
+ dtypes = {}
83
+ for name, dtype in annotations.items():
84
+ if len(dtype.__args__) > 1:
85
+ # regular numpy dtype (i.e. without shape)
86
+ dtypes[name] = dtype.__args__[1].__args__[0]
87
+ elif hasattr(dtype, "__metadata__"):
88
+ # metadata annotation contains shape
89
+ # define dtype using a (type, shape) tuple
90
+ # see: #1 in https://numpy.org/doc/stable/user/basics.rec.html#structured-datatype-creation
91
+ dtype_type = dtype.__args__[0].__args__[1].__args__[0]
92
+ dtype_shape = dtype.__metadata__[0].__args__
93
+ dtypes[name] = (dtype_type, dtype_shape)
94
+ else:
95
+ raise ValueError(f"dtype {dtype} not understood or supported")
96
+
97
+ if not dtypes:
98
+ raise ArrayDefinitionError("Array has no defined Columns")
99
+ if reserved := set(dtypes.keys()) & _RESERVED_COLUMN_NAMES:
100
+ raise ArrayDefinitionError(f"Columns cannot be reserved names: {reserved}")
101
+
102
+ dtype_list = []
103
+ for name, dtype in dtypes.items():
104
+ if dtype is np.str_:
105
+ string_length = str_lengths.get(name, _DEFAULT_STR_LENGTH)
106
+ dtype_list.append((name, np.dtype(f"U{string_length}")))
107
+ elif dtype is tuple:
108
+ dtype_list.append((name, *dtype))
109
+ else:
110
+ dtype_list.append((name, dtype))
111
+ return np.dtype(dtype_list)
112
+
113
+ def __repr__(self: Self) -> str:
114
+ try:
115
+ data = getattr(self, "data")
116
+ if data.size > 3:
117
+ return f"{self.__class__.__name__}([{data[:3]}]... + {data.size - 3} more rows)"
118
+ return f"{self.__class__.__name__}([{data}])"
119
+ except AttributeError:
120
+ return self.__class__.__name__ + "()"
121
+
122
+ def __str__(self) -> str:
123
+ return self.as_table()
124
+
125
+ def __len__(self) -> int:
126
+ return len(self._data)
127
+
128
+ def __iter__(self: Self):
129
+ for record in self._data:
130
+ yield self.__class__(data=np.array([record]))
131
+
132
+ def __getattr__(self: Self, attr):
133
+ if attr == "__array_interface__":
134
+ # prevent unintended usage of numpy functions. np.unique/np.sort give wrong results.
135
+ raise TypeError(
136
+ "Cannot use numpy functions directly on FancyArray. "
137
+ "Instead, you can use (fancypy) fp.unique/fp.sort or np.unique/np.sort on array.data"
138
+ )
139
+ if attr.startswith("__"): # prevent unintended usage of numpy magic methods
140
+ raise AttributeError(f"Cannot get attribute {attr} on {self.__class__.__name__}")
141
+
142
+ if attr in self.get_dtype().names:
143
+ return self._data[attr]
144
+ return getattr(self._data, attr)
145
+
146
+ def __setattr__(self: Self, attr: str, value: object) -> None:
147
+ if attr in ["_data", "_defaults"]:
148
+ super().__setattr__(attr, value)
149
+ return
150
+ try:
151
+ self._data[attr] = value # type: ignore[call-overload]
152
+ except (AttributeError, ValueError) as error:
153
+ raise AttributeError(f"Cannot set attribute {attr} on {self.__class__.__name__}") from error
154
+
155
+ def __getitem__(self: Self, item):
156
+ """Used by for-loops, slicing [0:3], column-access ['id'], row-access [0], multi-column access.
157
+ Note: If a single item is requested, return a named tuple instead of a np.void object.
158
+ """
159
+
160
+ result = self._data.__getitem__(item)
161
+
162
+ if isinstance(item, (list, tuple)) and (len(item) == 0 or np.array(item).dtype.type is np.bool_):
163
+ return self.__class__(data=result)
164
+ if isinstance(item, (str, list, tuple)):
165
+ return result
166
+ if isinstance(result, np.void):
167
+ return self.__class__(data=np.array([result]))
168
+ return self.__class__(data=result)
169
+
170
+ def __setitem__(self: Self, key, value):
171
+ if isinstance(value, FancyArray):
172
+ value = value.data
173
+ return self._data.__setitem__(key, value)
174
+
175
+ def __contains__(self: Self, item: Self) -> bool:
176
+ if isinstance(item, FancyArray):
177
+ return item.data in self._data
178
+ return False
179
+
180
+ def __hash__(self: Self):
181
+ return hash(f"{self.__class__} {self}")
182
+
183
+ def __eq__(self: Self, other):
184
+ return self._data.__eq__(other.data)
185
+
186
+ def __copy__(self: Self):
187
+ return self.__class__(data=copy(self._data))
188
+
189
+ def copy(self: Self):
190
+ """Return a copy of this array including its data"""
191
+ return copy(self)
192
+
193
+ @classmethod
194
+ def zeros(cls: Type[Self], num: int, empty_id: bool = True) -> Self:
195
+ """Construct an array filled with zeros.
196
+
197
+ If 'empty_id' is True, the 'id' column will be filled with EMPTY_ID values.
198
+ """
199
+ dtype = cls.get_dtype()
200
+ zeros_array = np.zeros(num, dtype=dtype)
201
+ if empty_id and "id" in dtype.names:
202
+ zeros_array["id"] = EMPTY_ID # type: ignore[call-overload]
203
+ return cls(data=zeros_array)
204
+
205
+ @classmethod
206
+ def empty(cls: Type[Self], num: int, use_defaults: bool = True) -> Self:
207
+ """Construct an array filled with 'empty' values.
208
+
209
+ 'empty' values differs per dtype:
210
+ string: "" (empty string)
211
+ float: np.nan
212
+ integer: minimum value
213
+
214
+ if 'use_defaults' is True, the default values will be used instead where possible.
215
+ """
216
+ array_dtype = cls.get_dtype()
217
+ array = np.zeros(num, dtype=array_dtype)
218
+
219
+ defaults = cls.get_defaults()
220
+ for column in array_dtype.names:
221
+ if use_defaults and column in defaults and defaults[column] is not empty:
222
+ array[column] = defaults[column]
223
+ else:
224
+ array[column] = empty(array_dtype[column])
225
+ return cls(data=array)
226
+
227
+ def is_empty(self, column: str) -> NDArray[np.bool_]:
228
+ """Check if a column is filled with 'empty' values."""
229
+ empty_value = self.get_empty_value(column)
230
+ if empty_value is np.nan:
231
+ return np.isnan(self._data[column])
232
+ return np.isin(self._data[column], empty_value)
233
+
234
+ def get_empty_value(self, column: str) -> float | int | str | bool:
235
+ array_dtype = self.get_dtype()
236
+ return empty(array_dtype[column])
237
+
238
+ def set_empty(self, column: str):
239
+ """Set a column to its 'empty' value."""
240
+ array_dtype = self.get_dtype()
241
+ self._data[column] = empty(array_dtype[column]) # type: ignore[call-overload]
242
+
243
+ @property
244
+ def columns(self) -> list[str]:
245
+ return list(self.get_dtype().names)
246
+
247
+ @property
248
+ def record(self):
249
+ """Return a named tuple of the first record in the array."""
250
+ if self.size != 1:
251
+ raise ValueError(f"Cannot return record of array with size {self.size}")
252
+
253
+ class_name = self.__class__.__name__
254
+ tpl_cls = namedtuple(f"{class_name}Record", self.dtype.names)
255
+ if isinstance(self._data, np.void):
256
+ return tpl_cls(*self._data)
257
+ return tpl_cls(*self._data[0])
258
+
259
+ def filter(
260
+ self: Self,
261
+ *args: int | Iterable[int] | np.ndarray,
262
+ mode_: Literal["AND", "OR"] = "AND",
263
+ **kwargs: Any | list[Any] | np.ndarray,
264
+ ) -> Self:
265
+ return self.__class__(data=apply_filter(*args, array=self._data, mode_=mode_, **kwargs))
266
+
267
+ def exclude(
268
+ self: Self,
269
+ *args: int | Iterable[int] | np.ndarray,
270
+ mode_: Literal["AND", "OR"] = "AND",
271
+ **kwargs: Any | list[Any] | np.ndarray,
272
+ ) -> Self:
273
+ return self.__class__(data=apply_exclude(*args, array=self._data, mode_=mode_, **kwargs))
274
+
275
+ def get(
276
+ self: Self,
277
+ *args: int | Iterable[int] | np.ndarray,
278
+ mode_: Literal["AND", "OR"] = "AND",
279
+ **kwargs: Any | list[Any] | np.ndarray,
280
+ ) -> Self:
281
+ return self.__class__(data=apply_get(*args, array=self._data, mode_=mode_, **kwargs))
282
+
283
+ def filter_mask(
284
+ self: Self,
285
+ *args: int | Iterable[int] | np.ndarray,
286
+ mode_: Literal["AND", "OR"] = "AND",
287
+ **kwargs: Any | list[Any] | np.ndarray,
288
+ ) -> np.ndarray:
289
+ return get_filter_mask(*args, array=self._data, mode_=mode_, **kwargs)
290
+
291
+ def exclude_mask(
292
+ self: Self,
293
+ *args: int | Iterable[int] | np.ndarray,
294
+ mode_: Literal["AND", "OR"] = "AND",
295
+ **kwargs: Any | list[Any] | np.ndarray,
296
+ ) -> np.ndarray:
297
+ return ~get_filter_mask(*args, array=self._data, mode_=mode_, **kwargs)
298
+
299
+ def re_order(self: Self, new_order: ArrayLike, column: str = "id") -> Self:
300
+ return self.__class__(data=re_order(self._data, new_order, column=column))
301
+
302
+ def update_by_id(self: Self, ids: ArrayLike, allow_missing: bool = False, **kwargs) -> None:
303
+ try:
304
+ _ = update_by_id(self._data, ids, allow_missing, **kwargs)
305
+ except ValueError as error:
306
+ raise ValueError(f"Cannot update {self.__class__.__name__}. {error}") from error
307
+
308
+ def get_updated_by_id(self: Self, ids: ArrayLike, allow_missing: bool = False, **kwargs) -> Self:
309
+ try:
310
+ mask = update_by_id(self._data, ids, allow_missing, **kwargs)
311
+ return self.__class__(data=self._data[mask])
312
+ except ValueError as error:
313
+ raise ValueError(f"Cannot update {self.__class__.__name__}. {error}") from error
314
+
315
+ def check_ids(self: Self, return_duplicates: bool = False) -> NDArray | None:
316
+ return check_ids(self._data, return_duplicates=return_duplicates)
317
+
318
+ def as_table(self: Self, column_width: int | str = "auto", rows: int = 10) -> str:
319
+ return convert_array_to_string(self, column_width=column_width, rows=rows)
320
+
321
+ def as_df(self: Self):
322
+ """Convert to pandas DataFrame"""
323
+ if pandas is None:
324
+ raise ImportError("pandas is not installed")
325
+ return pandas.DataFrame(self._data)
@@ -0,0 +1,17 @@
1
+ # SPDX-FileCopyrightText: Contributors to the Power Grid Model project <powergridmodel@lfenergy.org>
2
+ #
3
+ # SPDX-License-Identifier: MPL-2.0
4
+
5
+ """A collection of errors"""
6
+
7
+
8
+ class ArrayDefinitionError(Exception):
9
+ """Raised when an array is defined incorrectly"""
10
+
11
+
12
+ class RecordDoesNotExist(IndexError):
13
+ """Raised when a record is accessed that does not exist"""
14
+
15
+
16
+ class MultipleRecordsReturned(IndexError):
17
+ """Raised when unexpectedly, multiple records are found."""
@@ -0,0 +1,122 @@
1
+ # SPDX-FileCopyrightText: Contributors to the Power Grid Model project <powergridmodel@lfenergy.org>
2
+ #
3
+ # SPDX-License-Identifier: MPL-2.0
4
+
5
+ """Arrays"""
6
+
7
+ from typing import Literal
8
+
9
+ import numpy as np
10
+ from numpy.typing import NDArray
11
+
12
+ from power_grid_model_ds._core.model.arrays.base.array import FancyArray
13
+ from power_grid_model_ds._core.model.dtypes.appliances import Source, SymGen, SymLoad
14
+ from power_grid_model_ds._core.model.dtypes.branches import (
15
+ Branch,
16
+ Branch3,
17
+ Line,
18
+ Link,
19
+ ThreeWindingTransformer,
20
+ Transformer,
21
+ )
22
+ from power_grid_model_ds._core.model.dtypes.id import Id
23
+ from power_grid_model_ds._core.model.dtypes.nodes import Node
24
+ from power_grid_model_ds._core.model.dtypes.regulators import TransformerTapRegulator
25
+ from power_grid_model_ds._core.model.dtypes.sensors import AsymVoltageSensor, SymPowerSensor, SymVoltageSensor
26
+
27
+ # pylint: disable=missing-class-docstring
28
+
29
+
30
+ class IdArray(Id, FancyArray):
31
+ pass
32
+
33
+
34
+ class SymLoadArray(IdArray, SymLoad):
35
+ pass
36
+
37
+
38
+ class SymGenArray(IdArray, SymGen):
39
+ pass
40
+
41
+
42
+ class SourceArray(IdArray, Source):
43
+ pass
44
+
45
+
46
+ class NodeArray(IdArray, Node):
47
+ pass
48
+
49
+
50
+ class BranchArray(IdArray, Branch):
51
+ @property
52
+ def node_ids(self):
53
+ """Return both from_node and to_node in one array"""
54
+ return np.concatenate([self.data["from_node"], self.data["to_node"]])
55
+
56
+ @property
57
+ def is_active(self) -> NDArray[np.bool_]:
58
+ """Returns boolean whether branch is closed at both ends"""
59
+ return np.logical_and(self.from_status == 1, self.to_status == 1)
60
+
61
+ def filter_parallel(self, n_parallel: int, mode: Literal["eq", "neq"]) -> "BranchArray":
62
+ """Return branches that have n_parallel connections.
63
+
64
+ Args:
65
+ branches: BranchArray.
66
+ n_parallel: the number of connections between the same nodes
67
+ mode: mode of comparison. "eq" (equal) or "neq" (non-equal).
68
+
69
+ Returns:
70
+ - when n_parallel is 1 and mode is 'eq', the function returns branches that are not parallel.
71
+ - when n_parallel is 1 and mode is 'neq', the function returns branches that are parallel.
72
+ """
73
+ _, index, counts = np.unique(self[["from_node", "to_node"]], return_counts=True, return_index=True)
74
+
75
+ match mode:
76
+ case "eq":
77
+ counts_mask = counts == n_parallel
78
+ case "neq":
79
+ counts_mask = counts != n_parallel
80
+ case _:
81
+ raise ValueError(f"mode {mode} not supported")
82
+
83
+ if mode == "eq" and n_parallel == 1:
84
+ return self[index][counts_mask]
85
+ filtered_branches = self[index][counts_mask]
86
+ return self.filter(from_node=filtered_branches.from_node, to_node=filtered_branches.to_node)
87
+
88
+
89
+ class LinkArray(Link, BranchArray):
90
+ pass
91
+
92
+
93
+ class LineArray(Line, BranchArray):
94
+ pass
95
+
96
+
97
+ class TransformerArray(Transformer, BranchArray):
98
+ pass
99
+
100
+
101
+ class Branch3Array(IdArray, Branch3):
102
+ pass
103
+
104
+
105
+ class ThreeWindingTransformerArray(Branch3Array, ThreeWindingTransformer):
106
+ pass
107
+
108
+
109
+ class TransformerTapRegulatorArray(IdArray, TransformerTapRegulator):
110
+ pass
111
+
112
+
113
+ class SymPowerSensorArray(IdArray, SymPowerSensor):
114
+ pass
115
+
116
+
117
+ class SymVoltageSensorArray(IdArray, SymVoltageSensor):
118
+ pass
119
+
120
+
121
+ class AsymVoltageSensorArray(IdArray, AsymVoltageSensor):
122
+ pass
@@ -0,0 +1,27 @@
1
+ # SPDX-FileCopyrightText: Contributors to the Power Grid Model project <powergridmodel@lfenergy.org>
2
+ #
3
+ # SPDX-License-Identifier: MPL-2.0
4
+
5
+ """Model constants"""
6
+
7
+ import numpy as np
8
+
9
+
10
+ def empty(dtype: type):
11
+ """Returns the empty value for the given dtype."""
12
+ if hasattr(dtype, "subdtype") and hasattr(dtype, "base"): # special case for custom 'NDArray3' dtype
13
+ dtype = dtype.base
14
+
15
+ if np.issubdtype(dtype, np.str_):
16
+ return ""
17
+ if np.issubdtype(dtype, np.dtype("bool")):
18
+ return False
19
+ if np.issubdtype(dtype, np.float64):
20
+ return np.nan
21
+ try:
22
+ return np.iinfo(dtype).min
23
+ except ValueError as error:
24
+ raise ValueError("Unsupported dtype") from error
25
+
26
+
27
+ EMPTY_ID = empty(np.int32)
File without changes