Skip to content

Commit eaca61a

Browse files
Merge pull request #131 from alliander-opensource/feature/three-phase-nan
Change update for three phase values with NaN's
2 parents 0b7b0da + 0e48b3d commit eaca61a

File tree

10 files changed

+379
-33
lines changed

10 files changed

+379
-33
lines changed

include/power_grid_model/component/load_gen.hpp

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -78,15 +78,12 @@ class LoadGen final : public std::conditional_t<is_gen, GenericGenerator, Generi
7878
}
7979

8080
void set_power(RealValue<sym> const& new_p_specified, RealValue<sym> const& new_q_specified) {
81-
RealValue<sym> ps, qs;
82-
if (is_nan(new_p_specified))
83-
ps = real(s_specified_);
84-
else
85-
ps = direction_ / base_power<sym> * new_p_specified;
86-
if (is_nan(new_q_specified))
87-
qs = imag(s_specified_);
88-
else
89-
qs = direction_ / base_power<sym> * new_q_specified;
81+
double const scalar = direction_ / base_power<sym>;
82+
RealValue<sym> ps = real(s_specified_);
83+
RealValue<sym> qs = imag(s_specified_);
84+
update_real_value<sym>(new_p_specified, ps, scalar);
85+
update_real_value<sym>(new_q_specified, qs, scalar);
86+
9087
s_specified_ = ps + 1.0i * qs;
9188
}
9289

include/power_grid_model/component/power_sensor.hpp

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -77,10 +77,13 @@ class PowerSensor : public GenericPowerSensor {
7777
{};
7878

7979
UpdateChange update(PowerSensorUpdate<sym> const& power_sensor_update) {
80-
if (!is_nan(power_sensor_update.p_measured) && !is_nan(power_sensor_update.q_measured)) {
81-
s_measured_ = ((power_sensor_update.p_measured + 1i * power_sensor_update.q_measured) /
82-
base_power<sym>)*convert_direction();
83-
}
80+
double const scalar = convert_direction() / base_power<sym>;
81+
RealValue<sym> ps = real(s_measured_);
82+
RealValue<sym> qs = imag(s_measured_);
83+
update_real_value<sym>(power_sensor_update.p_measured, ps, scalar);
84+
update_real_value<sym>(power_sensor_update.q_measured, qs, scalar);
85+
s_measured_ = ps + 1.0i * qs;
86+
8487
if (!is_nan(power_sensor_update.power_sigma)) {
8588
power_sigma_ = power_sensor_update.power_sigma / base_power<sym>;
8689
}

include/power_grid_model/component/voltage_sensor.hpp

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -70,15 +70,14 @@ class VoltageSensor : public GenericVoltageSensor {
7070
u_angle_measured_{voltage_sensor_input.u_angle_measured} {};
7171

7272
UpdateChange update(VoltageSensorUpdate<sym> const& voltage_sensor_update) {
73-
if (!is_nan(voltage_sensor_update.u_measured)) {
74-
u_measured_ = voltage_sensor_update.u_measured / (u_rated_ * u_scale<sym>);
75-
}
73+
double const scalar = 1 / (u_rated_ * u_scale<sym>);
74+
update_real_value<sym>(voltage_sensor_update.u_measured, u_measured_, scalar);
75+
update_real_value<sym>(voltage_sensor_update.u_angle_measured, u_angle_measured_, 1.0);
76+
7677
if (!is_nan(voltage_sensor_update.u_sigma)) {
7778
u_sigma_ = voltage_sensor_update.u_sigma / (u_rated_ * u_scale<sym>);
7879
}
79-
if (!is_nan(voltage_sensor_update.u_angle_measured)) {
80-
u_angle_measured_ = voltage_sensor_update.u_angle_measured;
81-
}
80+
8281
return {false, false};
8382
}
8483

include/power_grid_model/three_phase_tensor.hpp

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,33 @@ inline bool is_nan(IntS x) {
321321
return x == na_IntS;
322322
}
323323

324+
/* update real values
325+
326+
RealValue is only updated when the update value is not nan
327+
328+
symmetric: update 1.0 with nan -> 1.0
329+
update 1.0 with 2.0 -> 2.0
330+
331+
asymmetric: update [1.0, nan, nan] with [nan, nan, 2.0] -> [1.0, nan, 2.0]
332+
333+
The function assumes that the current value is normalized and new value should be normalized with scalar
334+
*/
335+
template <bool sym, class Proxy>
336+
void update_real_value(RealValue<sym> const& new_value, Proxy&& current_value, double scalar) {
337+
if constexpr (sym) {
338+
if (!is_nan(new_value)) {
339+
current_value = scalar * new_value;
340+
}
341+
}
342+
else {
343+
for (size_t i = 0; i != 3; ++i) {
344+
if (!is_nan(new_value(i))) {
345+
current_value(i) = scalar * new_value(i);
346+
}
347+
}
348+
}
349+
}
350+
324351
// symmetric component matrix
325352
inline ComplexTensor<false> get_sym_matrix() {
326353
ComplexTensor<false> m;

src/power_grid_model/validation/utils.py

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -109,12 +109,19 @@ def update_component_data(component: str, input_data: np.ndarray, update_data: n
109109
mask = ~np.isnan(update_data[field])
110110
else:
111111
mask = np.not_equal(update_data[field], nan)
112+
112113
if mask.ndim == 2:
113-
mask = np.any(mask, axis=1)
114-
data = update_data[["id", field]][mask]
115-
idx = np.where(input_data["id"] == np.reshape(data["id"], (-1, 1)))
116-
if isinstance(idx, tuple):
117-
input_data[field][idx[1]] = data[field]
114+
for phase in range(mask.shape[1]):
115+
# find indexers of to-be-updated object
116+
sub_mask = mask[:, phase]
117+
idx = get_indexer(input_data["id"], update_data["id"][sub_mask])
118+
# update
119+
input_data[field][idx, phase] = update_data[field][sub_mask, phase]
120+
else:
121+
# find indexers of to-be-updated object
122+
idx = get_indexer(input_data["id"], update_data["id"][mask])
123+
# update
124+
input_data[field][idx] = update_data[field][mask]
118125

119126

120127
def errors_to_string(
@@ -158,3 +165,25 @@ def nan_type(component: str, field: str, data_type="input"):
158165
It silently returns float('nan') if data_type/component/field can't be found.
159166
"""
160167
return power_grid_meta_data.get(data_type, {}).get(component, {}).get("nans", {}).get(field, float("nan"))
168+
169+
170+
def get_indexer(input_ids: np.ndarray, update_ids: np.ndarray) -> np.ndarray:
171+
"""
172+
Given array of ids from input and update dataset.
173+
Find the posision of each id in the update dataset in the context of input dataset.
174+
This is needed to update values in the dataset by id lookup.
175+
Internally this is done by sorting the input ids, then using binary search lookup.
176+
177+
Args:
178+
input_ids: array of ids in the input dataset
179+
update_ids: array of ids in the update dataset
180+
181+
Returns:
182+
np.ndarray: array of positions of the ids from update dataset in the input dataset
183+
the following should hold
184+
input_ids[result] == update_ids
185+
"""
186+
permutation_sort = np.argsort(input_ids) # complexity O(N_input * logN_input)
187+
return permutation_sort[
188+
np.searchsorted(input_ids, update_ids, sorter=permutation_sort)
189+
] # complexity O(N_update * logN_input)

tests/cpp_unit_tests/test_load_gen.cpp

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,40 @@ TEST_CASE("Test load generator") {
245245
CHECK(asym_result.q(1) == doctest::Approx(1e5));
246246
}
247247

248+
SUBCASE("Test set_power - sym") {
249+
// update with nan, nothing happens
250+
sym_gen_pq.set_power(RealValue<true>{nan}, RealValue<true>{nan});
251+
ComplexValue<true> s_1 = sym_gen_pq.calc_param<true>();
252+
CHECK(real(s_1) == 3.0);
253+
CHECK(imag(s_1) == 3.0);
254+
255+
// update with values, s changes
256+
sym_gen_pq.set_power(RealValue<true>{4.0e6}, RealValue<true>{5.0e6});
257+
ComplexValue<true> s_2 = sym_gen_pq.calc_param<true>();
258+
CHECK(real(s_2) == 4.0);
259+
CHECK(imag(s_2) == 5.0);
260+
}
261+
262+
SUBCASE("Test set_power - asym") {
263+
// update with {nan, nan, nan}, nothing happens
264+
asym_load_pq.set_power(RealValue<false>{nan}, RealValue<false>{nan});
265+
ComplexValue<false> s_1 = asym_load_pq.calc_param<false>();
266+
for (size_t i = 0; i != 3; i++) {
267+
CHECK(real(s_1(i)) == -3.0);
268+
CHECK(imag(s_1(i)) == -3.0);
269+
}
270+
271+
// update some with nan, some with values
272+
asym_load_pq.set_power(RealValue<false>{2.0e6, nan, 3.0e6}, RealValue<false>{nan, 4.0e6, nan});
273+
ComplexValue<false> s_2 = asym_load_pq.calc_param<false>();
274+
CHECK(real(s_2(0)) == -6.0);
275+
CHECK(real(s_2(1)) == -3.0); // not updated
276+
CHECK(real(s_2(2)) == -9.0);
277+
CHECK(imag(s_2(0)) == -3.0); // not updated
278+
CHECK(imag(s_2(1)) == -12.0);
279+
CHECK(imag(s_2(2)) == -3.0); // not updated
280+
}
281+
248282
SUBCASE("Test no source") {
249283
auto const s = sym_gen_pq.calc_param<false>(false);
250284
CHECK(real(s)(0) == doctest::Approx(0.0));

0 commit comments

Comments
 (0)