Skip to content

Commit 9ae8138

Browse files
Merge pull request #130 from alliander-opensource/feature/id-lookup
Add get indexer function to lookup from IDs to index sequence
2 parents 07adea0 + bc389b2 commit 9ae8138

File tree

5 files changed

+117
-30
lines changed

5 files changed

+117
-30
lines changed

docs/python-api-reference.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,22 @@ class PowerGridModel:
137137
"""
138138
pass
139139

140+
def get_indexer(self,
141+
component_type: str,
142+
ids: np.ndarray):
143+
"""
144+
Get array of indexers given array of ids for component type
145+
146+
Args:
147+
component_type: type of component
148+
ids: array of ids
149+
150+
Returns:
151+
array of inderxers, same shape as input array ids
152+
153+
"""
154+
pass
155+
140156
def copy(self) -> 'PowerGridModel':
141157
"""
142158

include/power_grid_model/main_model.hpp

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ class MainModelImpl<ExtraRetrievableTypes<ExtraRetrievableType...>, ComponentLis
7878
DataPointer<false> const& data_ptr, Idx position);
7979
using CheckUpdateFunc = bool (*)(ConstDataPointer const& component_update);
8080
using GetSeqIdxFunc = std::vector<Idx2D> (*)(MainModelImpl const& x, ConstDataPointer const& component_update);
81+
using GetIndexerFunc = void (*)(MainModelImpl const& x, ID const* id_begin, Idx size, Idx* indexer_begin);
8182

8283
public:
8384
// constructor with data
@@ -376,6 +377,25 @@ class MainModelImpl<ExtraRetrievableTypes<ExtraRetrievableType...>, ComponentLis
376377
comp_coup_.reset();
377378
}
378379

380+
/*
381+
the the sequence indexer given an input array of ID's for a given component type
382+
*/
383+
void get_indexer(std::string const& component_type, ID const* id_begin, Idx size, Idx* indexer_begin) {
384+
// static function array
385+
static constexpr std::array<GetIndexerFunc, n_types> get_indexer_func{
386+
[](MainModelImpl const& model, ID const* id_begin, Idx size, Idx* indexer_begin) {
387+
std::transform(id_begin, id_begin + size, indexer_begin, [&model](ID id) {
388+
return model.components_.template get_idx_by_id<ComponentType>(id).pos;
389+
});
390+
}...};
391+
// search component type name
392+
for (ComponentEntry const& entry : AllComponents::component_index_map) {
393+
if (entry.name == component_type) {
394+
return get_indexer_func[entry.index](*this, id_begin, size, indexer_begin);
395+
}
396+
}
397+
}
398+
379399
private:
380400
template <bool sym, typename InputType, std::vector<InputType> (MainModelImpl::*PrepareInputFn)(),
381401
MathOutput<sym> (MathSolver<sym>::*SolveFn)(InputType const&, double, Idx, CalculationInfo&,
@@ -417,7 +437,7 @@ class MainModelImpl<ExtraRetrievableTypes<ExtraRetrievableType...>, ComponentLis
417437
std::map<std::string, std::vector<Idx2D>> get_sequence_idx_map(ConstDataset const& update_data) const {
418438
// function pointer array to get cached idx
419439
static constexpr std::array<GetSeqIdxFunc, n_types> get_seq_idx{
420-
[](MainModelImpl const& x, ConstDataPointer const& component_update) -> std::vector<Idx2D> {
440+
[](MainModelImpl const& model, ConstDataPointer const& component_update) -> std::vector<Idx2D> {
421441
using UpdateType = typename ComponentType::UpdateType;
422442
// no batch
423443
if (component_update.batch_size() < 1) {
@@ -427,8 +447,8 @@ class MainModelImpl<ExtraRetrievableTypes<ExtraRetrievableType...>, ComponentLis
427447
auto const [it_begin, it_end] = component_update.template get_iterators<UpdateType>(0);
428448
// vector
429449
std::vector<Idx2D> seq_idx(std::distance(it_begin, it_end));
430-
std::transform(it_begin, it_end, seq_idx.begin(), [x](UpdateType const& update) {
431-
return x.components_.template get_idx_by_id<ComponentType>(update.id);
450+
std::transform(it_begin, it_end, seq_idx.begin(), [&model](UpdateType const& update) {
451+
return model.components_.template get_idx_by_id<ComponentType>(update.id);
432452
});
433453
return seq_idx;
434454
}...};

src/power_grid_model/_power_grid_core.pyx

Lines changed: 61 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,19 @@ from .enum import CalculationMethod
1515

1616
cimport numpy as cnp
1717
from cython.operator cimport dereference as deref
18-
from libc.stdint cimport int8_t, int32_t
18+
from libc.stdint cimport int8_t
1919
from libcpp cimport bool
2020
from libcpp.map cimport map
2121
from libcpp.string cimport string
2222
from libcpp.vector cimport vector
2323

24-
VALIDATOR_MSG = "Try validate_input_data() or validate_batch_data() to validate your data."
24+
# idx and id types
25+
from libc.stdint cimport int32_t as idx_t # isort: skip
26+
cdef np_idx_t = np.int32
27+
from libc.stdint cimport int32_t as id_t # isort: skip
28+
cdef np_id_t = np.int32
29+
30+
cdef VALIDATOR_MSG = "Try validate_input_data() or validate_batch_data() to validate your data."
2531

2632
cdef extern from "power_grid_model/auxiliary/meta_data_gen.hpp" namespace "power_grid_model::meta_data":
2733
cppclass DataAttribute:
@@ -110,10 +116,10 @@ cdef _generate_component_meta_data(MetaData & cpp_component_meta_data):
110116
cdef extern from "power_grid_model/auxiliary/dataset.hpp" namespace "power_grid_model":
111117
cppclass MutableDataPointer:
112118
MutableDataPointer()
113-
MutableDataPointer(void * ptr, const int32_t * indptr, int32_t size)
119+
MutableDataPointer(void * ptr, const idx_t * indptr, idx_t size)
114120
cppclass ConstDataPointer:
115121
ConstDataPointer()
116-
ConstDataPointer(const void * ptr, const int32_t * indptr, int32_t size)
122+
ConstDataPointer(const void * ptr, const idx_t * indptr, idx_t size)
117123

118124
cdef extern from "power_grid_model/main_model.hpp" namespace "power_grid_model":
119125
cppclass CalculationMethodCPP "::power_grid_model::CalculationMethod":
@@ -122,43 +128,48 @@ cdef extern from "power_grid_model/main_model.hpp" namespace "power_grid_model":
122128
bool independent
123129
bool cache_topology
124130
cppclass MainModel:
125-
map[string, int32_t] all_component_count()
131+
map[string, idx_t] all_component_count()
126132
BatchParameter calculate_sym_power_flow "calculate_power_flow<true>"(
127133
double error_tolerance,
128-
int32_t max_iterations,
134+
idx_t max_iterations,
129135
CalculationMethodCPP calculation_method,
130136
const map[string, MutableDataPointer] & result_data,
131137
const map[string, ConstDataPointer] & update_data,
132-
int32_t threading
138+
idx_t threading
133139
) except+
134140
BatchParameter calculate_asym_power_flow "calculate_power_flow<false>"(
135141
double error_tolerance,
136-
int32_t max_iterations,
142+
idx_t max_iterations,
137143
CalculationMethodCPP calculation_method,
138144
const map[string, MutableDataPointer] & result_data,
139145
const map[string, ConstDataPointer] & update_data,
140-
int32_t threading
146+
idx_t threading
141147
) except+
142148
BatchParameter calculate_sym_state_estimation "calculate_state_estimation<true>"(
143149
double error_tolerance,
144-
int32_t max_iterations,
150+
idx_t max_iterations,
145151
CalculationMethodCPP calculation_method,
146152
const map[string, MutableDataPointer] & result_data,
147153
const map[string, ConstDataPointer] & update_data,
148-
int32_t threading
154+
idx_t threading
149155
) except+
150156
BatchParameter calculate_asym_state_estimation "calculate_state_estimation<false>"(
151157
double error_tolerance,
152-
int32_t max_iterations,
158+
idx_t max_iterations,
153159
CalculationMethodCPP calculation_method,
154160
const map[string, MutableDataPointer] & result_data,
155161
const map[string, ConstDataPointer] & update_data,
156-
int32_t threading
162+
idx_t threading
157163
) except+
158164
void update_component(
159165
const map[string, ConstDataPointer] & update_data,
160-
int32_t pos
166+
idx_t pos
161167
) except+
168+
void get_indexer(
169+
const string& component_type,
170+
const id_t* id_begin,
171+
idx_t size,
172+
idx_t* indexer_begin) except+
162173

163174
cdef extern from "<optional>":
164175
cppclass OptionalMainModel "::std::optional<::power_grid_model::MainModel>":
@@ -169,7 +180,7 @@ cdef extern from "<optional>":
169180
MainModel & emplace(
170181
double system_frequency,
171182
const map[string, ConstDataPointer] & input_data,
172-
int32_t pos) except+
183+
idx_t pos) except+
173184

174185
# internally used meta data, to prevent modification
175186
cdef _power_grid_meta_data = _generate_meta_data()
@@ -183,7 +194,7 @@ cdef map[string, ConstDataPointer] generate_const_ptr_map(data: Dict[str, Dict[s
183194
data_arr = v['data']
184195
indptr_arr = v['indptr']
185196
result[k.encode()] = ConstDataPointer(
186-
cnp.PyArray_DATA(data_arr), < const int32_t*>cnp.PyArray_DATA(indptr_arr),
197+
cnp.PyArray_DATA(data_arr), < const idx_t*>cnp.PyArray_DATA(indptr_arr),
187198
v['batch_size'])
188199
return result
189200

@@ -195,7 +206,7 @@ cdef map[string, MutableDataPointer] generate_ptr_map(data: Dict[str, Dict[str,
195206
data_arr = v['data']
196207
indptr_arr = v['indptr']
197208
result[k.encode()] = MutableDataPointer(
198-
cnp.PyArray_DATA(data_arr), < const int32_t * > cnp.PyArray_DATA(indptr_arr),
209+
cnp.PyArray_DATA(data_arr), < const idx_t * > cnp.PyArray_DATA(indptr_arr),
199210
v['batch_size'])
200211
return result
201212

@@ -234,10 +245,10 @@ cdef _prepare_cpp_array(data_type: str,
234245
data = v
235246
ndim = v.ndim
236247
if ndim == 1:
237-
indptr = np.array([0, v.size], dtype=np.int32)
248+
indptr = np.array([0, v.size], dtype=np_idx_t)
238249
batch_size = 1
239250
elif ndim == 2: # (n_batch, n_component)
240-
indptr = np.arange(v.shape[0] + 1, dtype=np.int32) * v.shape[1]
251+
indptr = np.arange(v.shape[0] + 1, dtype=np_idx_t) * v.shape[1]
241252
batch_size = v.shape[0]
242253
else:
243254
raise ValueError(f"Array can only be 1D or 2D. {VALIDATOR_MSG}")
@@ -257,7 +268,7 @@ cdef _prepare_cpp_array(data_type: str,
257268
raise ValueError(f"indptr should be increasing. {VALIDATOR_MSG}")
258269
# convert array
259270
data = np.ascontiguousarray(data, dtype=schema[component_name]['dtype'])
260-
indptr = np.ascontiguousarray(indptr, dtype=np.int32)
271+
indptr = np.ascontiguousarray(indptr, dtype=np_idx_t)
261272
return_dict[component_name] = {
262273
'data': data,
263274
'indptr': indptr,
@@ -302,6 +313,29 @@ cdef class PowerGridModel:
302313
self.independent = False
303314
self.cache_topology = False
304315

316+
def get_indexer(self,
317+
component_type: str,
318+
ids: np.ndarray):
319+
"""
320+
Get array of indexers given array of ids for component type
321+
322+
Args:
323+
component_type: type of component
324+
ids: array of ids
325+
326+
Returns:
327+
array of inderxers, same shape as input array ids
328+
329+
"""
330+
cdef cnp.ndarray ids_c = np.ascontiguousarray(ids, dtype=np_id_t)
331+
cdef cnp.ndarray indexer = np.empty_like(ids_c, dtype=np_idx_t, order='C')
332+
cdef const id_t* id_begin = <const id_t*> cnp.PyArray_DATA(ids_c)
333+
cdef idx_t* indexer_begin = <idx_t*> cnp.PyArray_DATA(indexer)
334+
cdef idx_t size = ids.size
335+
# call c function
336+
self._get_model().get_indexer(component_type.encode(), id_begin, size, indexer_begin)
337+
return indexer
338+
305339
def copy(self) -> PowerGridModel:
306340
"""
307341

@@ -333,10 +367,10 @@ cdef class PowerGridModel:
333367
calculation_type,
334368
bool symmetric,
335369
double error_tolerance,
336-
int32_t max_iterations,
370+
idx_t max_iterations,
337371
calculation_method: Union[CalculationMethod, str],
338372
update_data: Optional[Dict[str, Union[np.ndarray, Dict[str, np.ndarray]]]],
339-
int32_t threading
373+
idx_t threading
340374
):
341375
"""
342376
Core calculation routine
@@ -453,10 +487,10 @@ cdef class PowerGridModel:
453487
def calculate_power_flow(self, *,
454488
bool symmetric=True,
455489
double error_tolerance=1e-8,
456-
int32_t max_iterations=20,
490+
idx_t max_iterations=20,
457491
calculation_method: Union[CalculationMethod, str] = CalculationMethod.newton_raphson,
458492
update_data: Optional[Dict[str, Union[np.ndarray, Dict[str, np.ndarray]]]] = None,
459-
int32_t threading=-1
493+
idx_t threading=-1
460494
) -> Dict[str, np.ndarray]:
461495
"""
462496
Calculate power flow once with the current model attributes.
@@ -519,10 +553,10 @@ cdef class PowerGridModel:
519553
def calculate_state_estimation(self, *,
520554
bool symmetric=True,
521555
double error_tolerance=1e-8,
522-
int32_t max_iterations=20,
556+
idx_t max_iterations=20,
523557
calculation_method: Union[CalculationMethod, str] = CalculationMethod.iterative_linear,
524558
update_data: Optional[Dict[str, Union[np.ndarray, Dict[str, np.ndarray]]]] = None,
525-
int32_t threading=-1
559+
idx_t threading=-1
526560
) -> Dict[str, np.ndarray]:
527561
"""
528562
Calculate state estimation once with the current model attributes.
@@ -592,7 +626,7 @@ cdef class PowerGridModel:
592626
value: integer count of elements of this type
593627
"""
594628
all_component_count = {}
595-
cdef map[string, int32_t] cpp_count = self._get_model().all_component_count()
629+
cdef map[string, idx_t] cpp_count = self._get_model().all_component_count()
596630
for map_entry in cpp_count:
597631
all_component_count[map_entry.first.decode()] = map_entry.second
598632
return all_component_count

tests/cpp_unit_tests/test_main_model.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,14 @@ TEST_CASE("Test main model") {
104104
main_model.add_component<AsymVoltageSensor>(asym_voltage_sensor_input);
105105
main_model.set_construction_complete();
106106

107+
SUBCASE("Test get indexer") {
108+
IdxVector const node_id{2, 1, 3, 2};
109+
IdxVector const expected_indexer{1, 0, 2, 1};
110+
IdxVector indexer(4);
111+
main_model.get_indexer("node", node_id.data(), 4, indexer.data());
112+
CHECK(indexer == expected_indexer);
113+
}
114+
107115
SUBCASE("Test duplicated id") {
108116
MainModel main_model2{50.0};
109117
node_input[1].id = 1;

tests/unit/test_0Z_model_validation.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from copy import copy
66
from pathlib import Path
77

8+
import numpy as np
89
import pytest
910

1011
from power_grid_model import PowerGridModel
@@ -36,6 +37,14 @@ def test_single_validation(
3637
reference_result = case_data["output"]
3738
compare_result(result, reference_result, rtol, atol)
3839

40+
# test get indexer
41+
for component_name, input_array in case_data["input"].items():
42+
ids_array = input_array["id"].copy()
43+
np.random.shuffle(ids_array)
44+
indexer_array = model.get_indexer(component_name, ids_array)
45+
# check
46+
assert np.all(input_array["id"][indexer_array] == ids_array)
47+
3948
# export data if needed
4049
if EXPORT_OUTPUT:
4150
save_json_data(f"{case_id}.json", result)

0 commit comments

Comments
 (0)