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.
- power_grid_model_ds/__init__.py +9 -0
- power_grid_model_ds/_core/__init__.py +0 -0
- power_grid_model_ds/_core/data_source/__init__.py +0 -0
- power_grid_model_ds/_core/data_source/generator/__init__.py +0 -0
- power_grid_model_ds/_core/data_source/generator/arrays/__init__.py +0 -0
- power_grid_model_ds/_core/data_source/generator/arrays/base.py +25 -0
- power_grid_model_ds/_core/data_source/generator/arrays/line.py +133 -0
- power_grid_model_ds/_core/data_source/generator/arrays/node.py +37 -0
- power_grid_model_ds/_core/data_source/generator/arrays/source.py +30 -0
- power_grid_model_ds/_core/data_source/generator/arrays/transformer.py +37 -0
- power_grid_model_ds/_core/data_source/generator/grid_generators.py +78 -0
- power_grid_model_ds/_core/fancypy.py +66 -0
- power_grid_model_ds/_core/load_flow.py +140 -0
- power_grid_model_ds/_core/model/__init__.py +0 -0
- power_grid_model_ds/_core/model/arrays/__init__.py +43 -0
- power_grid_model_ds/_core/model/arrays/base/__init__.py +0 -0
- power_grid_model_ds/_core/model/arrays/base/_build.py +166 -0
- power_grid_model_ds/_core/model/arrays/base/_filters.py +115 -0
- power_grid_model_ds/_core/model/arrays/base/_modify.py +64 -0
- power_grid_model_ds/_core/model/arrays/base/_optional.py +11 -0
- power_grid_model_ds/_core/model/arrays/base/_string.py +94 -0
- power_grid_model_ds/_core/model/arrays/base/array.py +325 -0
- power_grid_model_ds/_core/model/arrays/base/errors.py +17 -0
- power_grid_model_ds/_core/model/arrays/pgm_arrays.py +122 -0
- power_grid_model_ds/_core/model/constants.py +27 -0
- power_grid_model_ds/_core/model/containers/__init__.py +0 -0
- power_grid_model_ds/_core/model/containers/base.py +244 -0
- power_grid_model_ds/_core/model/containers/grid_protocol.py +22 -0
- power_grid_model_ds/_core/model/dtypes/__init__.py +0 -0
- power_grid_model_ds/_core/model/dtypes/appliances.py +39 -0
- power_grid_model_ds/_core/model/dtypes/branches.py +117 -0
- power_grid_model_ds/_core/model/dtypes/id.py +19 -0
- power_grid_model_ds/_core/model/dtypes/nodes.py +27 -0
- power_grid_model_ds/_core/model/dtypes/regulators.py +30 -0
- power_grid_model_ds/_core/model/dtypes/sensors.py +63 -0
- power_grid_model_ds/_core/model/enums/__init__.py +0 -0
- power_grid_model_ds/_core/model/enums/nodes.py +16 -0
- power_grid_model_ds/_core/model/graphs/__init__.py +0 -0
- power_grid_model_ds/_core/model/graphs/container.py +158 -0
- power_grid_model_ds/_core/model/graphs/errors.py +19 -0
- power_grid_model_ds/_core/model/graphs/models/__init__.py +7 -0
- power_grid_model_ds/_core/model/graphs/models/_rustworkx_search.py +63 -0
- power_grid_model_ds/_core/model/graphs/models/base.py +326 -0
- power_grid_model_ds/_core/model/graphs/models/rustworkx.py +119 -0
- power_grid_model_ds/_core/model/grids/__init__.py +0 -0
- power_grid_model_ds/_core/model/grids/_text_sources.py +119 -0
- power_grid_model_ds/_core/model/grids/base.py +434 -0
- power_grid_model_ds/_core/model/grids/helpers.py +122 -0
- power_grid_model_ds/_core/utils/__init__.py +0 -0
- power_grid_model_ds/_core/utils/misc.py +41 -0
- power_grid_model_ds/_core/utils/pickle.py +47 -0
- power_grid_model_ds/_core/utils/zip.py +72 -0
- power_grid_model_ds/arrays.py +39 -0
- power_grid_model_ds/constants.py +7 -0
- power_grid_model_ds/enums.py +7 -0
- power_grid_model_ds/errors.py +27 -0
- power_grid_model_ds/fancypy.py +9 -0
- power_grid_model_ds/generators.py +11 -0
- power_grid_model_ds/graph_models.py +8 -0
- power_grid_model_ds-0.0.1a11709467271.dist-info/LICENSE +292 -0
- power_grid_model_ds-0.0.1a11709467271.dist-info/METADATA +80 -0
- power_grid_model_ds-0.0.1a11709467271.dist-info/RECORD +64 -0
- power_grid_model_ds-0.0.1a11709467271.dist-info/WHEEL +5 -0
- 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
|