Skip to content

Commit 5fde4cb

Browse files
authored
Merge branch 'main' into feature/only-important-integration-tests
2 parents e52a2f4 + 1f174e3 commit 5fde4cb

File tree

22 files changed

+236
-171
lines changed

22 files changed

+236
-171
lines changed

.github/workflows/reuse-compliance.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,4 @@ jobs:
2929
- name: checkout
3030
uses: actions/checkout@v4
3131
- name: REUSE Compliance Check
32-
uses: fsfe/reuse-action@v4
32+
uses: fsfe/reuse-action@v5

code_generation/meta_data.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
# define dataclass for meta data
66

77
from dataclasses import dataclass
8-
from typing import Optional
98

109
from dataclasses_json import DataClassJsonMixin
1110

@@ -15,19 +14,19 @@ class Attribute(DataClassJsonMixin):
1514
data_type: str
1615
names: str | list[str]
1716
description: str
18-
nan_value: Optional[str] = None
17+
nan_value: str | None = None
1918

2019

2120
@dataclass
2221
class AttributeClass(DataClassJsonMixin):
2322
name: str
2423
attributes: list[Attribute]
25-
full_attributes: Optional[list[Attribute]] = None
26-
base: Optional[str] = None
24+
full_attributes: list[Attribute] | None = None
25+
base: str | None = None
2726
is_template: bool = False
28-
full_name: Optional[str] = None
29-
specification_names: Optional[list[str]] = None
30-
base_attributes: Optional[dict[str, list[Attribute]]] = None
27+
full_name: str | None = None
28+
specification_names: list[str] | None = None
29+
base_attributes: dict[str, list[Attribute]] | None = None
3130

3231

3332
@dataclass

docs/api_reference/python-api-reference.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ This is the Python API reference for the `power-grid-model` library
1414
.. autoclass:: PowerGridModel
1515
:show-inheritance:
1616
.. autofunction:: initialize_array
17+
.. autofunction:: attribute_dtype
18+
.. autofunction:: attribute_empty_value
1719
1820
.. py:data:: power_grid_meta_data
1921
:type: typing.PowerGridMetaData

docs/examples/Power Flow Example.ipynb

Lines changed: 22 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@
4949
"\n",
5050
"from power_grid_model import LoadGenType, ComponentType, DatasetType, ComponentAttributeFilterOptions\n",
5151
"from power_grid_model import PowerGridModel, CalculationMethod, CalculationType\n",
52-
"from power_grid_model import initialize_array, power_grid_meta_data"
52+
"from power_grid_model import initialize_array, attribute_dtype"
5353
]
5454
},
5555
{
@@ -127,7 +127,7 @@
127127
"\n",
128128
"A columnar data format better integrates with most databases. In addition, it may bring memory and, in some cases, even computational performance improvements, because unused attribute columns can be omitted.\n",
129129
"\n",
130-
"Make sure to provide the correct `dtype` to the numpy arrays, exposed for each dataset type, component and attribute via the `power_grid_meta_data` object."
130+
"Make sure to provide the correct `dtype` to the numpy arrays, exposed for each dataset type, component and attribute via the helper function `attribute_dtype` function."
131131
]
132132
},
133133
{
@@ -137,12 +137,11 @@
137137
"metadata": {},
138138
"outputs": [],
139139
"source": [
140-
"source_attribute_dtypes = power_grid_meta_data[DatasetType.input][ComponentType.source].dtype\n",
141140
"source_columns = {\n",
142-
" \"id\": np.array([10], dtype=source_attribute_dtypes[\"id\"]),\n",
143-
" \"node\": np.array([1], dtype=source_attribute_dtypes[\"node\"]),\n",
144-
" \"status\": np.array([1], dtype=source_attribute_dtypes[\"status\"]),\n",
145-
" \"u_ref\": np.array([1.0], dtype=source_attribute_dtypes[\"u_ref\"]),\n",
141+
" \"id\": np.array([10], dtype=attribute_dtype(DatasetType.input, ComponentType.source, \"id\")),\n",
142+
" \"node\": np.array([1], dtype=attribute_dtype(DatasetType.input, ComponentType.source, \"node\")),\n",
143+
" \"status\": np.array([1], dtype=attribute_dtype(DatasetType.input, ComponentType.source, \"status\")),\n",
144+
" \"u_ref\": np.array([1.0], dtype=attribute_dtype(DatasetType.input, ComponentType.source, \"u_ref\")),\n",
146145
" # We're not creating columns for u_ref_angle, sk, ... Instead, the default values are used. This saves us memory.\n",
147146
"}\n",
148147
"\n",
@@ -674,10 +673,11 @@
674673
"metadata": {},
675674
"outputs": [],
676675
"source": [
677-
"line_update_dtype = power_grid_meta_data[DatasetType.update][ComponentType.line].dtype\n",
678676
"columnar_update_line = {\n",
679-
" \"id\": np.array([3], dtype=line_update_dtype[\"id\"]), # change line ID 3\n",
680-
" \"from_status\": np.array([0], dtype=line_update_dtype[\"from_status\"]), # switch off at from side\n",
677+
" \"id\": np.array([3], dtype=attribute_dtype(DatasetType.update, ComponentType.line, \"id\")), # change line ID 3\n",
678+
" \"from_status\": np.array(\n",
679+
" [0], dtype=attribute_dtype(DatasetType.update, ComponentType.line, \"from_status\")\n",
680+
" ), # switch off at from side\n",
681681
"}\n",
682682
"# leave to-side swiching status the same, no need to specify\n",
683683
"\n",
@@ -699,11 +699,10 @@
699699
"metadata": {},
700700
"outputs": [],
701701
"source": [
702-
"line_update_dtype = power_grid_meta_data[DatasetType.update][ComponentType.line].dtype\n",
703702
"columnar_no_ID_update_line = {\n",
704703
" # Update IDs are not specified\n",
705704
" \"from_status\": np.array(\n",
706-
" [0, 1, 1], dtype=line_update_dtype[\"from_status\"]\n",
705+
" [0, 1, 1], dtype=attribute_dtype(DatasetType.update, ComponentType.line, \"from_status\")\n",
707706
" ), # The update for the whole column needs to be specified\n",
708707
"}\n",
709708
"# leave to-side swiching status the same, no need to specify\n",
@@ -1404,16 +1403,16 @@
14041403
"output_type": "stream",
14051404
"text": [
14061405
"Node data with invalid results\n",
1407-
"[[0.99940117 0.99268579 0.99452137]\n",
1408-
" [0.99934769 0.98622639 0.98935286]\n",
1409-
" [0.99928838 0.97965401 0.98409554]\n",
1410-
" [0. 0. 0. ]\n",
1411-
" [0.99915138 0.96614948 0.97329879]\n",
1412-
" [0.99907317 0.95920586 0.96775071]\n",
1413-
" [0.9989881 0.95212621 0.96209647]\n",
1414-
" [0. 0. 0. ]\n",
1415-
" [0.99879613 0.93753005 0.95044796]\n",
1416-
" [0.9986885 0.92999747 0.94444167]]\n",
1406+
"[[9.99401170e-001 9.92685785e-001 9.94521366e-001]\n",
1407+
" [9.99347687e-001 9.86226389e-001 9.89352855e-001]\n",
1408+
" [9.99288384e-001 9.79654011e-001 9.84095542e-001]\n",
1409+
" [3.94357132e+180 2.87518198e+161 2.04418455e+214]\n",
1410+
" [9.99151380e-001 9.66149483e-001 9.73298790e-001]\n",
1411+
" [9.99073166e-001 9.59205860e-001 9.67750710e-001]\n",
1412+
" [9.98988099e-001 9.52126208e-001 9.62096474e-001]\n",
1413+
" [0.00000000e+000 0.00000000e+000 0.00000000e+000]\n",
1414+
" [9.98796126e-001 9.37530046e-001 9.50447962e-001]\n",
1415+
" [9.98688504e-001 9.29997471e-001 9.44441670e-001]]\n",
14171416
"Node data with only valid results\n",
14181417
"[[0.99940117 0.99268579 0.99452137]\n",
14191418
" [0.99934769 0.98622639 0.98935286]\n",
@@ -1456,7 +1455,7 @@
14561455
],
14571456
"metadata": {
14581457
"kernelspec": {
1459-
"display_name": "venv",
1458+
"display_name": ".venv",
14601459
"language": "python",
14611460
"name": "python3"
14621461
},

power_grid_model_c/power_grid_model/include/power_grid_model/main_model_impl.hpp

Lines changed: 43 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -316,12 +316,9 @@ class MainModelImpl<ExtraRetrievableTypes<ExtraRetrievableType...>, ComponentLis
316316

317317
// update all components
318318
template <cache_type_c CacheType>
319-
void
320-
update_component(ConstDataset const& update_data, Idx pos,
321-
std::array<std::reference_wrapper<std::vector<Idx2D> const>, n_types> const& sequence_idx_map) {
319+
void update_component(ConstDataset const& update_data, Idx pos, SequenceIdxView const& sequence_idx_map) {
322320
run_functor_with_all_types_return_void([this, pos, &update_data, &sequence_idx_map]<typename CT>() {
323-
this->update_component<CT, CacheType>(update_data, pos,
324-
std::get<index_of_component<CT>>(sequence_idx_map).get());
321+
this->update_component<CT, CacheType>(update_data, pos, std::get<index_of_component<CT>>(sequence_idx_map));
325322
});
326323
}
327324
template <cache_type_c CacheType>
@@ -432,36 +429,28 @@ class MainModelImpl<ExtraRetrievableTypes<ExtraRetrievableType...>, ComponentLis
432429
return get_sequence(buffer_span);
433430
}
434431
SequenceIdx get_sequence_idx_map(ConstDataset const& update_data, Idx scenario_idx,
435-
ComponentFlags const& to_store) const {
436-
// TODO: (jguo) this function could be encapsulated in UpdateCompIndependence in update.hpp
437-
return run_functor_with_all_types_return_array([this, scenario_idx, &update_data, &to_store]<typename CT>() {
438-
if (!to_store[index_of_component<CT>]) {
439-
return std::vector<Idx2D>{};
440-
}
441-
auto const independence = check_components_independence<CT>(update_data);
442-
validate_update_data_independence(independence);
443-
return get_component_sequence<CT>(update_data, scenario_idx, independence);
444-
});
445-
}
446-
// get sequence idx map of an entire batch for fast caching of component sequences
447-
SequenceIdx get_sequence_idx_map(ConstDataset const& update_data, ComponentFlags const& to_store) const {
432+
ComponentFlags const& components_to_store) const {
448433
// TODO: (jguo) this function could be encapsulated in UpdateCompIndependence in update.hpp
449-
return run_functor_with_all_types_return_array([this, &update_data, &to_store]<typename CT>() {
450-
if (!to_store[index_of_component<CT>]) {
451-
return std::vector<Idx2D>{};
452-
}
453-
auto const independence = check_components_independence<CT>(update_data);
454-
validate_update_data_independence(independence);
455-
return get_component_sequence<CT>(update_data, 0, independence);
456-
});
434+
return run_functor_with_all_types_return_array(
435+
[this, scenario_idx, &update_data, &components_to_store]<typename CT>() {
436+
if (!std::get<index_of_component<CT>>(components_to_store)) {
437+
return std::vector<Idx2D>{};
438+
}
439+
auto const independence = check_components_independence<CT>(update_data);
440+
validate_update_data_independence(independence);
441+
return get_component_sequence<CT>(update_data, scenario_idx, independence);
442+
});
457443
}
444+
// Get sequence idx map of an entire batch for fast caching of component sequences.
445+
// The sequence idx map of the batch is the same as that of the first scenario in the batch (assuming homogeneity)
446+
// This is the entry point for permanent updates.
458447
SequenceIdx get_sequence_idx_map(ConstDataset const& update_data) const {
459448
constexpr ComponentFlags all_true = [] {
460449
ComponentFlags result{};
461450
std::ranges::fill(result, true);
462451
return result;
463452
}();
464-
return get_sequence_idx_map(update_data, all_true);
453+
return get_sequence_idx_map(update_data, 0, all_true);
465454
}
466455

467456
private:
@@ -621,9 +610,10 @@ class MainModelImpl<ExtraRetrievableTypes<ExtraRetrievableType...>, ComponentLis
621610
// const ref of current instance
622611
MainModelImpl const& base_model = *this;
623612

624-
// cache component update order if possible
613+
// cache component update order where possible.
614+
// the order for a cacheable (independent) component by definition is the same across all scenarios
625615
auto const is_independent = is_update_independent(update_data);
626-
all_scenarios_sequence = get_sequence_idx_map(update_data, is_independent);
616+
all_scenarios_sequence = get_sequence_idx_map(update_data, 0, is_independent);
627617

628618
return [&base_model, &exceptions, &infos, &calculation_fn, &result_data, &update_data,
629619
&all_scenarios_sequence = std::as_const(all_scenarios_sequence),
@@ -640,15 +630,8 @@ class MainModelImpl<ExtraRetrievableTypes<ExtraRetrievableType...>, ComponentLis
640630
auto model = copy_model_functor(start);
641631

642632
SequenceIdx current_scenario_sequence_cache = SequenceIdx{};
643-
std::array<std::reference_wrapper<std::vector<Idx2D> const>, n_types> const current_scenario_sequence =
644-
run_functor_with_all_types_return_array(
645-
[&is_independent, &all_scenarios_sequence, &current_scenario_sequence_cache]<typename CT>() {
646-
constexpr auto comp_idx = index_of_component<CT>;
647-
return is_independent[comp_idx] ? std::cref(all_scenarios_sequence[comp_idx])
648-
: std::cref(current_scenario_sequence_cache[comp_idx]);
649-
});
650-
auto [setup, winddown] = scenario_update_restore(model, update_data, current_scenario_sequence,
651-
current_scenario_sequence_cache, is_independent, infos);
633+
auto [setup, winddown] = scenario_update_restore(model, update_data, is_independent, all_scenarios_sequence,
634+
current_scenario_sequence_cache, infos);
652635

653636
auto calculate_scenario = MainModelImpl::call_with<Idx>(
654637
[&model, &calculation_fn, &result_data, &infos](Idx scenario_idx) {
@@ -720,28 +703,41 @@ class MainModelImpl<ExtraRetrievableTypes<ExtraRetrievableType...>, ComponentLis
720703
};
721704
}
722705

723-
static auto scenario_update_restore(
724-
MainModelImpl& model, ConstDataset const& update_data,
725-
std::array<std::reference_wrapper<std::vector<Idx2D> const>, n_types> const& scenario_sequence,
726-
SequenceIdx& current_scenario_sequence_cache, ComponentFlags const& is_independent,
727-
std::vector<CalculationInfo>& infos) noexcept {
706+
static auto scenario_update_restore(MainModelImpl& model, ConstDataset const& update_data,
707+
ComponentFlags const& is_independent, SequenceIdx const& all_scenario_sequence,
708+
SequenceIdx& current_scenario_sequence_cache,
709+
std::vector<CalculationInfo>& infos) noexcept {
728710
auto do_update_cache = [&is_independent] {
729711
ComponentFlags result;
730712
std::ranges::transform(is_independent, result.begin(), std::logical_not<>{});
731713
return result;
732714
}();
733715

716+
auto const scenario_sequence = [&all_scenario_sequence, &current_scenario_sequence_cache,
717+
&is_independent]() -> SequenceIdxView {
718+
return run_functor_with_all_types_return_array(
719+
[&all_scenario_sequence, &current_scenario_sequence_cache, &is_independent]<typename CT>() {
720+
constexpr auto comp_idx = index_of_component<CT>;
721+
if (std::get<comp_idx>(is_independent)) {
722+
return std::span<Idx2D const>{std::get<comp_idx>(all_scenario_sequence)};
723+
}
724+
return std::span<Idx2D const>{std::get<comp_idx>(current_scenario_sequence_cache)};
725+
});
726+
};
727+
734728
return std::make_pair(
735-
[&model, &update_data, &scenario_sequence, &current_scenario_sequence_cache,
729+
[&model, &update_data, scenario_sequence, &current_scenario_sequence_cache,
736730
do_update_cache_ = std::move(do_update_cache), &infos](Idx scenario_idx) {
737731
Timer const t_update_model(infos[scenario_idx], 1200, "Update model");
738732
current_scenario_sequence_cache =
739733
model.get_sequence_idx_map(update_data, scenario_idx, do_update_cache_);
740-
model.template update_component<cached_update_t>(update_data, scenario_idx, scenario_sequence);
734+
735+
model.template update_component<cached_update_t>(update_data, scenario_idx, scenario_sequence());
741736
},
742-
[&model, &scenario_sequence, &current_scenario_sequence_cache, &infos](Idx scenario_idx) {
737+
[&model, scenario_sequence, &current_scenario_sequence_cache, &infos](Idx scenario_idx) {
743738
Timer const t_update_model(infos[scenario_idx], 1201, "Restore model");
744-
model.restore_components(scenario_sequence);
739+
740+
model.restore_components(scenario_sequence());
745741
std::ranges::for_each(current_scenario_sequence_cache,
746742
[](auto& comp_seq_idx) { comp_seq_idx.clear(); });
747743
});

power_grid_model_c/power_grid_model/include/power_grid_model/math_solver/observability.hpp

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -45,17 +45,17 @@ Idx count_bus_injection_sensors(const Idx n_bus, const MeasuredValues<sym>& meas
4545

4646
template <symmetry_tag sym>
4747
std::tuple<Idx, Idx> count_voltage_sensors(const Idx n_bus, const MeasuredValues<sym>& measured_values) {
48-
Idx n_voltage_magnitude{};
49-
Idx n_voltage_phasor{};
48+
Idx n_voltage_sensor{};
49+
Idx n_voltage_phasor_sensor{};
5050
for (Idx bus = 0; bus != n_bus; ++bus) {
5151
if (measured_values.has_voltage(bus)) {
52-
n_voltage_magnitude++;
52+
n_voltage_sensor++;
5353
if (measured_values.has_angle_measurement(bus)) {
54-
n_voltage_phasor++;
54+
n_voltage_phasor_sensor++;
5555
}
5656
}
5757
}
58-
return std::make_tuple(n_voltage_magnitude, n_voltage_phasor);
58+
return std::make_tuple(n_voltage_sensor, n_voltage_phasor_sensor);
5959
}
6060

6161
} // namespace detail
@@ -66,17 +66,19 @@ inline void necessary_observability_check(MeasuredValues<sym> const& measured_va
6666
Idx const n_bus{topo->n_bus()};
6767
std::vector<BranchIdx> const& branch_bus_idx{topo->branch_bus_idx};
6868

69-
auto const [n_voltage_magnitude, n_voltage_phasor] = detail::count_voltage_sensors(n_bus, measured_values);
70-
if (n_voltage_magnitude + n_voltage_phasor < 1) {
69+
auto const [n_voltage_sensor, n_voltage_phasor_sensor] = detail::count_voltage_sensors(n_bus, measured_values);
70+
if (n_voltage_sensor < 1) {
7171
throw NotObservableError{};
7272
}
73+
7374
Idx const n_injection_sensor = detail::count_bus_injection_sensors(n_bus, measured_values);
7475
Idx const n_branch_sensor = detail::count_branch_sensors(branch_bus_idx, n_bus, measured_values);
76+
Idx const n_power_sensor = n_injection_sensor + n_branch_sensor;
7577

76-
if (n_voltage_phasor == 0 && n_branch_sensor + n_injection_sensor < n_bus - 1) {
78+
if (n_voltage_phasor_sensor == 0 && n_power_sensor < n_bus - 1) {
7779
throw NotObservableError{};
7880
}
79-
if (n_voltage_phasor > 0 && n_branch_sensor + n_injection_sensor + n_voltage_phasor < n_bus) {
81+
if (n_voltage_phasor_sensor > 0 && n_power_sensor + n_voltage_phasor_sensor < n_bus) {
8082
throw NotObservableError{};
8183
}
8284
}

src/power_grid_model/__init__.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,12 @@
55
"""Power Grid Model"""
66

77
from power_grid_model._core.dataset_definitions import ComponentType, DatasetType
8-
from power_grid_model._core.power_grid_meta import initialize_array, power_grid_meta_data
8+
from power_grid_model._core.power_grid_meta import (
9+
attribute_dtype,
10+
attribute_empty_value,
11+
initialize_array,
12+
power_grid_meta_data,
13+
)
914
from power_grid_model._core.power_grid_model import PowerGridModel
1015
from power_grid_model.enum import (
1116
Branch3Side,

src/power_grid_model/_core/error_handling.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
"""
88

99
import re
10-
from typing import Optional
1110

1211
import numpy as np
1312

@@ -115,7 +114,7 @@ def _interpret_error(message: str, decode_error: bool = True) -> PowerGridError:
115114
return PowerGridError(message)
116115

117116

118-
def find_error(batch_size: int = 1, decode_error: bool = True) -> Optional[RuntimeError]:
117+
def find_error(batch_size: int = 1, decode_error: bool = True) -> RuntimeError | None:
119118
"""
120119
Check if there is an error and return it
121120
@@ -171,7 +170,7 @@ def assert_no_error(batch_size: int = 1, decode_error: bool = True):
171170

172171
def handle_errors(
173172
continue_on_batch_error: bool, batch_size: int = 1, decode_error: bool = True
174-
) -> Optional[PowerGridBatchError]:
173+
) -> PowerGridBatchError | None:
175174
"""
176175
Handle any errors in the way that is specified.
177176
@@ -184,10 +183,10 @@ def handle_errors(
184183
error: Any errors previously encountered, unless it was a batch error and continue_on_batch_error was True.
185184
186185
Returns:
187-
Optional[PowerGridBatchError]: None if there were no errors, or the previously encountered
188-
error if it was a batch error and continue_on_batch_error was True.
186+
PowerGridBatchError | None: None if there were no errors, or the previously encountered
187+
error if it was a batch error and continue_on_batch_error was True.
189188
"""
190-
error: Optional[RuntimeError] = find_error(batch_size=batch_size, decode_error=decode_error)
189+
error: RuntimeError | None = find_error(batch_size=batch_size, decode_error=decode_error)
191190
if error is None:
192191
return None
193192

0 commit comments

Comments
 (0)