Skip to content

Commit d0e8fbd

Browse files
authored
Merge pull request #917 from PowerGridModel/feature/refactor-measured-values-statistics
Refactor to DecomposedComplexRandVar
2 parents a07d39e + e4a863f commit d0e8fbd

File tree

11 files changed

+377
-304
lines changed

11 files changed

+377
-304
lines changed

power_grid_model_c/power_grid_model/include/power_grid_model/calculation_parameters.hpp

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -88,15 +88,7 @@ template <symmetry_tag sym> using VoltageSensorCalcParam = UniformComplexRandVar
8888
// The value is the complex power
8989
// * for appliances, it is always in injection direction
9090
// * for branches, the direction is node -> branch
91-
template <symmetry_tag sym_type> struct PowerSensorCalcParam {
92-
using sym = sym_type;
93-
94-
static constexpr bool symmetric{is_symmetric_v<sym>};
95-
96-
ComplexValue<sym> value{};
97-
RealValue<sym> p_variance{}; // variance (sigma^2) of the error range of the active power, in p.u.
98-
RealValue<sym> q_variance{}; // variance (sigma^2) of the error range of the reactive power, in p.u.
99-
};
91+
template <symmetry_tag sym> using PowerSensorCalcParam = DecomposedComplexRandVar<sym>;
10092

10193
// current sensor calculation parameters for state estimation
10294
// The value is the complex current
@@ -107,9 +99,7 @@ template <symmetry_tag sym_type> struct CurrentSensorCalcParam {
10799
static constexpr bool symmetric{is_symmetric_v<sym>};
108100

109101
AngleMeasurementType angle_measurement_type{};
110-
ComplexValue<sym> value{};
111-
RealValue<sym> i_real_variance{}; // variance (sigma^2) of the error range of real part of the current, in p.u.
112-
RealValue<sym> i_imag_variance{}; // variance (sigma^2) of the error range of imaginary part of the current, in p.u.
102+
DecomposedComplexRandVar<sym> measurement;
113103
};
114104

115105
template <typename T>

power_grid_model_c/power_grid_model/include/power_grid_model/component/current_sensor.hpp

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -134,11 +134,8 @@ template <symmetry_tag current_sensor_symmetry_> class CurrentSensor : public Ge
134134
template <symmetry_tag sym_calc> CurrentSensorCalcParam<sym_calc> calc_decomposed_param() const {
135135
auto const i_polar = PolarComplexRandVar<current_sensor_symmetry>(
136136
{i_measured_, i_sigma_ * i_sigma_}, {i_angle_measured_, i_angle_sigma_ * i_angle_sigma_});
137-
auto const i_decomposed = DecomposedComplexRandVar<sym_calc>(i_polar);
138137
return CurrentSensorCalcParam<sym_calc>{.angle_measurement_type = angle_measurement_type(),
139-
.value = i_decomposed.value(),
140-
.i_real_variance = i_decomposed.real_component.variance,
141-
.i_imag_variance = i_decomposed.imag_component.variance};
138+
.measurement = DecomposedComplexRandVar<sym_calc>(i_polar)};
142139
}
143140
CurrentSensorOutput<symmetric_t> get_sym_output(ComplexValue<symmetric_t> const& i) const final {
144141
return get_generic_output<symmetric_t>(i);

power_grid_model_c/power_grid_model/include/power_grid_model/component/power_sensor.hpp

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -131,34 +131,37 @@ template <symmetry_tag power_sensor_symmetry_> class PowerSensor : public Generi
131131
PowerSensorCalcParam<symmetric_t> sym_calc_param() const final {
132132
PowerSensorCalcParam<symmetric_t> calc_param{};
133133
if (is_normal(p_sigma_) && is_normal(q_sigma_)) {
134-
calc_param.p_variance = mean_val(p_sigma_ * p_sigma_);
135-
calc_param.q_variance = mean_val(q_sigma_ * q_sigma_);
134+
calc_param.real_component.variance = mean_val(p_sigma_ * p_sigma_);
135+
calc_param.imag_component.variance = mean_val(q_sigma_ * q_sigma_);
136136
} else {
137137
auto const variance = is_nan(p_sigma_) ? apparent_power_sigma_ * apparent_power_sigma_ / 2
138138
: std::numeric_limits<double>::infinity();
139-
calc_param.p_variance = variance;
140-
calc_param.q_variance = variance;
139+
calc_param.real_component.variance = variance;
140+
calc_param.imag_component.variance = variance;
141141
}
142-
assert(is_nan(calc_param.p_variance) == is_nan(calc_param.q_variance));
142+
assert(is_nan(calc_param.real_component.variance) == is_nan(calc_param.imag_component.variance));
143143

144-
calc_param.value = mean_val(s_measured_);
144+
auto const mean_s_measured = mean_val(s_measured_);
145+
calc_param.real_component.value = real(mean_s_measured);
146+
calc_param.imag_component.value = imag(mean_s_measured);
145147
return calc_param;
146148
}
147149
PowerSensorCalcParam<asymmetric_t> asym_calc_param() const final {
148150
PowerSensorCalcParam<asymmetric_t> calc_param{};
149151
if (is_normal(p_sigma_) && is_normal(q_sigma_)) {
150-
calc_param.p_variance = RealValue<asymmetric_t>{p_sigma_ * p_sigma_};
151-
calc_param.q_variance = RealValue<asymmetric_t>{q_sigma_ * q_sigma_};
152+
calc_param.real_component.variance = RealValue<asymmetric_t>{p_sigma_ * p_sigma_};
153+
calc_param.imag_component.variance = RealValue<asymmetric_t>{q_sigma_ * q_sigma_};
152154
} else {
153155
auto const variance =
154156
RealValue<asymmetric_t>{is_nan(p_sigma_) ? apparent_power_sigma_ * apparent_power_sigma_ / 2
155157
: std::numeric_limits<double>::infinity()};
156-
calc_param.p_variance = variance;
157-
calc_param.q_variance = variance;
158+
calc_param.real_component.variance = variance;
159+
calc_param.imag_component.variance = variance;
158160
}
159-
assert(is_nan(calc_param.p_variance) == is_nan(calc_param.q_variance));
161+
assert(is_nan(calc_param.real_component.variance) == is_nan(calc_param.imag_component.variance));
160162

161-
calc_param.value = piecewise_complex_value(s_measured_);
163+
calc_param.real_component.value = RealValue<asymmetric_t>{real(s_measured_)};
164+
calc_param.imag_component.value = RealValue<asymmetric_t>{imag(s_measured_)};
162165
return calc_param;
163166
}
164167
PowerSensorOutput<symmetric_t> get_sym_output(ComplexValue<symmetric_t> const& s) const final {

power_grid_model_c/power_grid_model/include/power_grid_model/math_solver/iterative_linear_se_solver.hpp

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -196,9 +196,10 @@ template <symmetry_tag sym_type> class IterativeLinearSESolver {
196196
if (measured_value.has_shunt(obj)) {
197197
// G += (-Ys)^H * (variance^-1) * (-Ys)
198198
auto const& shunt_power = measured_value.shunt_power(obj);
199-
block.g() += dot(hermitian_transpose(param.shunt_param[obj]),
200-
diagonal_inverse(shunt_power.p_variance + shunt_power.q_variance),
201-
param.shunt_param[obj]);
199+
block.g() +=
200+
dot(hermitian_transpose(param.shunt_param[obj]),
201+
diagonal_inverse(static_cast<IndependentComplexRandVar<sym>>(shunt_power).variance),
202+
param.shunt_param[obj]);
202203
}
203204
}
204205
// branch
@@ -214,7 +215,7 @@ template <symmetry_tag sym_type> class IterativeLinearSESolver {
214215
auto const& power = std::invoke(branch_power_[measured_side], measured_value, obj);
215216
block.g() +=
216217
dot(hermitian_transpose(param.branch_param[obj].value[measured_side * 2 + b0]),
217-
diagonal_inverse(power.p_variance + power.q_variance),
218+
diagonal_inverse(static_cast<IndependentComplexRandVar<sym>>(power).variance),
218219
param.branch_param[obj].value[measured_side * 2 + b1]);
219220
}
220221
}
@@ -229,8 +230,8 @@ template <symmetry_tag sym_type> class IterativeLinearSESolver {
229230
if (row == col) {
230231
// assign variance to diagonal of 3x3 tensor, for asym
231232
auto const& injection = measured_value.bus_injection(row);
232-
block.r() = ComplexTensor<sym>{
233-
static_cast<ComplexValue<sym>>(-(injection.p_variance + injection.q_variance))};
233+
block.r() = ComplexTensor<sym>{static_cast<ComplexValue<sym>>(
234+
-(static_cast<IndependentComplexRandVar<sym>>(injection).variance))};
234235
}
235236
}
236237
// injection measurement not exist
@@ -286,8 +287,10 @@ template <symmetry_tag sym_type> class IterativeLinearSESolver {
286287
if (measured_value.has_shunt(obj)) {
287288
PowerSensorCalcParam<sym> const& m = measured_value.shunt_power(obj);
288289
// eta += (-Ys)^H * (variance^-1) * i_shunt
289-
rhs_block.eta() -= dot(hermitian_transpose(param.shunt_param[obj]),
290-
diagonal_inverse(m.p_variance + m.q_variance), conj(m.value / u[bus]));
290+
rhs_block.eta() -=
291+
dot(hermitian_transpose(param.shunt_param[obj]),
292+
diagonal_inverse(static_cast<IndependentComplexRandVar<sym>>(m).variance),
293+
conj(m.value() / u[bus]));
291294
}
292295
}
293296
// branch
@@ -307,14 +310,15 @@ template <symmetry_tag sym_type> class IterativeLinearSESolver {
307310
// eta += Y{side, b}^H * (variance^-1) * i_branch_{f, t}
308311
rhs_block.eta() +=
309312
dot(hermitian_transpose(param.branch_param[obj].value[measured_side * 2 + b]),
310-
diagonal_inverse(m.p_variance + m.q_variance), conj(m.value / u[measured_bus]));
313+
diagonal_inverse(static_cast<IndependentComplexRandVar<sym>>(m).variance),
314+
conj(m.value() / u[measured_bus]));
311315
}
312316
}
313317
}
314318
}
315319
// fill block with injection measurement, need to convert to current
316320
if (measured_value.has_bus_injection(bus)) {
317-
rhs_block.tau() = conj(measured_value.bus_injection(bus).value / u[bus]);
321+
rhs_block.tau() = conj(measured_value.bus_injection(bus).value() / u[bus]);
318322
}
319323
}
320324
}

power_grid_model_c/power_grid_model/include/power_grid_model/math_solver/measured_values.hpp

Lines changed: 35 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -332,8 +332,8 @@ template <symmetry_tag sym> class MeasuredValues {
332332

333333
// combine valid appliance_injection_measurement and direct_injection_measurement
334334
// three scenarios; check if we have valid injection measurement
335-
auto const uncertain_direct_injection =
336-
is_inf(direct_injection_measurement.p_variance) || is_inf(direct_injection_measurement.q_variance);
335+
auto const uncertain_direct_injection = is_inf(direct_injection_measurement.real_component.variance) ||
336+
is_inf(direct_injection_measurement.imag_component.variance);
337337

338338
bus_injection_[bus].idx_bus_injection = static_cast<Idx>(power_main_value_.size());
339339
if (n_unmeasured > 0) {
@@ -343,8 +343,8 @@ template <symmetry_tag sym> class MeasuredValues {
343343
// only direct injection
344344
power_main_value_.push_back(direct_injection_measurement);
345345
}
346-
} else if (uncertain_direct_injection || any_zero(appliance_injection_measurement.p_variance) ||
347-
any_zero(appliance_injection_measurement.q_variance)) {
346+
} else if (uncertain_direct_injection || any_zero(appliance_injection_measurement.real_component.variance) ||
347+
any_zero(appliance_injection_measurement.imag_component.variance)) {
348348
// only appliance injection if
349349
// there is no direct injection measurement,
350350
// or we have zero injection
@@ -370,13 +370,15 @@ template <symmetry_tag sym> class MeasuredValues {
370370
}
371371

372372
auto const& appliance_measurement = extra_value_[appliance_idx];
373-
if (is_inf(appliance_measurement.p_variance) || is_inf(appliance_measurement.q_variance)) {
373+
if (is_inf(appliance_measurement.real_component.variance) ||
374+
is_inf(appliance_measurement.imag_component.variance)) {
374375
++n_unmeasured;
375376
return;
376377
}
377-
measurements.value += appliance_measurement.value;
378-
measurements.p_variance += appliance_measurement.p_variance;
379-
measurements.q_variance += appliance_measurement.q_variance;
378+
measurements.real_component.value += appliance_measurement.real_component.value;
379+
measurements.imag_component.value += appliance_measurement.imag_component.value;
380+
measurements.real_component.variance += appliance_measurement.real_component.variance;
381+
measurements.imag_component.variance += appliance_measurement.imag_component.variance;
380382
}
381383

382384
void process_branch_measurements(StateEstimationInput<sym> const& input) {
@@ -463,22 +465,25 @@ template <symmetry_tag sym> class MeasuredValues {
463465
for (auto pos : sensors) {
464466
auto const& measurement = data[pos];
465467

466-
accumulated_inverse_p_variance += RealValue<sym>{1.0} / measurement.p_variance;
467-
accumulated_inverse_q_variance += RealValue<sym>{1.0} / measurement.q_variance;
468+
accumulated_inverse_p_variance += RealValue<sym>{1.0} / measurement.real_component.variance;
469+
accumulated_inverse_q_variance += RealValue<sym>{1.0} / measurement.imag_component.variance;
468470

469-
accumulated_p_value += real(measurement.value) / measurement.p_variance;
470-
accumulated_q_value += imag(measurement.value) / measurement.q_variance;
471+
accumulated_p_value += measurement.real_component.value / measurement.real_component.variance;
472+
accumulated_q_value += measurement.imag_component.value / measurement.imag_component.variance;
471473
}
472474

473475
if (is_normal(accumulated_inverse_p_variance) && is_normal(accumulated_inverse_q_variance)) {
474-
return PowerSensorCalcParam<sym>{accumulated_p_value / accumulated_inverse_p_variance +
475-
1.0i * accumulated_q_value / accumulated_inverse_q_variance,
476-
RealValue<sym>{1.0} / accumulated_inverse_p_variance,
477-
RealValue<sym>{1.0} / accumulated_inverse_q_variance};
476+
return PowerSensorCalcParam<sym>{
477+
.real_component = {.value = accumulated_p_value / accumulated_inverse_p_variance,
478+
.variance = RealValue<sym>{1.0} / accumulated_inverse_p_variance},
479+
.imag_component = {.value = accumulated_q_value / accumulated_inverse_q_variance,
480+
.variance = RealValue<sym>{1.0} / accumulated_inverse_q_variance}};
478481
}
479-
return PowerSensorCalcParam<sym>{accumulated_p_value + 1.0i * accumulated_q_value,
480-
RealValue<sym>{std::numeric_limits<double>::infinity()},
481-
RealValue<sym>{std::numeric_limits<double>::infinity()}};
482+
return PowerSensorCalcParam<sym>{
483+
.real_component = {.value = accumulated_p_value,
484+
.variance = RealValue<sym>{std::numeric_limits<double>::infinity()}},
485+
.imag_component = {.value = accumulated_q_value,
486+
.variance = RealValue<sym>{std::numeric_limits<double>::infinity()}}};
482487
}
483488

484489
template <sensor_calc_param_type CalcParam, bool only_magnitude = false>
@@ -538,7 +543,7 @@ template <symmetry_tag sym> class MeasuredValues {
538543
unconstrained_min(x.variance);
539544
}
540545
for (auto const& x : power_main_value_) {
541-
auto const variance = x.p_variance + x.q_variance;
546+
auto const variance = x.real_component.variance + x.imag_component.variance;
542547
if constexpr (is_symmetric_v<sym>) {
543548
unconstrained_min(variance);
544549
} else {
@@ -552,8 +557,8 @@ template <symmetry_tag sym> class MeasuredValues {
552557
auto const inv_norm_var = 1.0 / min_var;
553558
std::ranges::for_each(voltage_main_value_, [inv_norm_var](auto& x) { x.variance *= inv_norm_var; });
554559
std::ranges::for_each(power_main_value_, [inv_norm_var](auto& x) {
555-
x.p_variance *= inv_norm_var;
556-
x.q_variance *= inv_norm_var;
560+
x.real_component.variance *= inv_norm_var;
561+
x.imag_component.variance *= inv_norm_var;
557562
});
558563
}
559564

@@ -563,17 +568,17 @@ template <symmetry_tag sym> class MeasuredValues {
563568
FlowVector& source_flow) const {
564569
// calculate residual, divide, and assign to unmeasured (but connected) appliances
565570
ComplexValue<sym> const s_residual_per_appliance =
566-
(s - bus_appliance_injection.value) / static_cast<double>(n_unmeasured);
571+
(s - bus_appliance_injection.value()) / static_cast<double>(n_unmeasured);
567572
for (Idx const load_gen : load_gens) {
568573
if (has_load_gen(load_gen)) {
569-
load_gen_flow[load_gen].s = load_gen_power(load_gen).value;
574+
load_gen_flow[load_gen].s = load_gen_power(load_gen).value();
570575
} else if (idx_load_gen_power_[load_gen] == unmeasured) {
571576
load_gen_flow[load_gen].s = s_residual_per_appliance;
572577
}
573578
}
574579
for (Idx const source : sources) {
575580
if (has_source(source)) {
576-
source_flow[source].s = source_power(source).value;
581+
source_flow[source].s = source_power(source).value();
577582
} else if (idx_source_power_[source] == unmeasured) {
578583
source_flow[source].s = s_residual_per_appliance;
579584
}
@@ -586,13 +591,14 @@ template <symmetry_tag sym> class MeasuredValues {
586591
FlowVector& source_flow) const {
587592
// residual normalized by variance
588593
// mu = (sum[S_i] - S_cal) / sum[variance]
589-
auto const delta = bus_appliance_injection.value - s;
590-
ComplexValue<sym> const mu =
591-
real(delta) / bus_appliance_injection.p_variance + 1.0i * imag(delta) / bus_appliance_injection.q_variance;
594+
auto const delta = ComplexValue<sym>{bus_appliance_injection.value() - s};
595+
ComplexValue<sym> const mu = real(delta) / bus_appliance_injection.real_component.variance +
596+
1.0i * imag(delta) / bus_appliance_injection.imag_component.variance;
592597

593598
// S_i = S_i_mea - var_i * mu
594599
auto const calculate_injection = [&mu](auto const& power) {
595-
return power.value - (power.p_variance * real(mu) + 1.0i * power.q_variance * imag(mu));
600+
return ComplexValue<sym>{power.value() - ((power.real_component.variance * real(mu)) +
601+
(1.0i * power.imag_component.variance * imag(mu)))};
596602
};
597603

598604
for (Idx const load_gen : load_gens) {

0 commit comments

Comments
 (0)