@@ -66,6 +66,8 @@ template <symmetry_tag sym_type> class IterativeLinearSESolver {
66
66
using sym = sym_type;
67
67
68
68
static constexpr auto is_iterative = true ;
69
+ static constexpr auto has_current_sensor_implemented =
70
+ true ; // TODO(mgovers): for testing purposes; remove after NRSE has current sensor implemented
69
71
70
72
private:
71
73
// block size 2 for symmetric, 6 for asym
@@ -146,6 +148,10 @@ template <symmetry_tag sym_type> class IterativeLinearSESolver {
146
148
&MeasuredValues<sym>::has_branch_to_power};
147
149
static constexpr std::array branch_power_{&MeasuredValues<sym>::branch_from_power,
148
150
&MeasuredValues<sym>::branch_to_power};
151
+ static constexpr std::array has_branch_current_{&MeasuredValues<sym>::has_branch_from_current,
152
+ &MeasuredValues<sym>::has_branch_to_current};
153
+ static constexpr std::array branch_current_{&MeasuredValues<sym>::branch_from_current,
154
+ &MeasuredValues<sym>::branch_to_current};
149
155
150
156
Idx n_bus_;
151
157
// shared topo data
@@ -205,19 +211,32 @@ template <symmetry_tag sym_type> class IterativeLinearSESolver {
205
211
}
206
212
// branch
207
213
else {
208
- // branch from- and to-side index at 0, and 1 position
209
- IntS const b0 = static_cast <IntS>(type) / 2 ;
210
- IntS const b1 = static_cast <IntS>(type) % 2 ;
214
+ auto const add_branch_measurement = [&block, ¶m, obj,
215
+ type](IntS measured_side,
216
+ RealValue<sym> const & branch_current_variance) {
217
+ // branch from- and to-side index at 0, and 1 position
218
+ IntS const b0 = static_cast <IntS>(type) / 2 ;
219
+ IntS const b1 = static_cast <IntS>(type) % 2 ;
220
+ block.g () += dot (hermitian_transpose (param.branch_param [obj].value [measured_side * 2 + b0]),
221
+ diagonal_inverse (branch_current_variance),
222
+ param.branch_param [obj].value [measured_side * 2 + b1]);
223
+ };
211
224
// measured at from-side: 0, to-side: 1
212
225
for (IntS const measured_side : std::array<IntS, 2 >{0 , 1 }) {
213
226
// has measurement
214
227
if (std::invoke (has_branch_power_[measured_side], measured_value, obj)) {
215
228
// G += Y{side, b0}^H * (variance^-1) * Y{side, b1}
216
229
auto const & power = std::invoke (branch_power_[measured_side], measured_value, obj);
217
- block.g () +=
218
- dot (hermitian_transpose (param.branch_param [obj].value [measured_side * 2 + b0]),
219
- diagonal_inverse (static_cast <IndependentComplexRandVar<sym>>(power).variance ),
220
- param.branch_param [obj].value [measured_side * 2 + b1]);
230
+ // assume voltage ~ 1 p.u. => current variance = power variance * 1² = power variance
231
+ add_branch_measurement (measured_side,
232
+ static_cast <IndependentComplexRandVar<sym>>(power).variance );
233
+ }
234
+ if (std::invoke (has_branch_current_[measured_side], measured_value, obj)) {
235
+ // G += (variance^-1)
236
+ auto const & current =
237
+ std::invoke (branch_current_[measured_side], measured_value, obj).measurement ;
238
+ add_branch_measurement (measured_side,
239
+ static_cast <IndependentComplexRandVar<sym>>(current).variance );
221
240
}
222
241
}
223
242
}
@@ -296,23 +315,42 @@ template <symmetry_tag sym_type> class IterativeLinearSESolver {
296
315
}
297
316
// branch
298
317
else {
299
- // branch is either ff or tt
300
- IntS const b = static_cast <IntS>(type) / 2 ;
301
- assert (b == static_cast <IntS>(type) % 2 );
318
+ auto const add_branch_measurement = [&rhs_block, ¶m, obj,
319
+ type](IntS measured_side,
320
+ IndependentComplexRandVar<sym> const & current) {
321
+ // branch is either ff or tt
322
+ IntS const b = static_cast <IntS>(type) / 2 ;
323
+ // eta += Y{side, b}^H * (variance^-1) * i_branch_{f, t}
324
+ rhs_block.eta () +=
325
+ dot (hermitian_transpose (param.branch_param [obj].value [measured_side * 2 + b]),
326
+
327
+ diagonal_inverse (current.variance ), current.value );
328
+ };
302
329
// measured at from-side: 0, to-side: 1
303
330
for (IntS const measured_side : std::array<IntS, 2 >{0 , 1 }) {
331
+ // the current needs to be calculated with the voltage of the measured bus side
332
+ // NOTE: not the bus that is currently being processed!
333
+ Idx const measured_bus = branch_bus_idx[obj][measured_side];
334
+
304
335
// has measurement
305
336
if (std::invoke (has_branch_power_[measured_side], measured_value, obj)) {
306
337
PowerSensorCalcParam<sym> const & m =
307
338
std::invoke (branch_power_[measured_side], measured_value, obj);
308
- // the current needs to be calculated with the voltage of the measured bus side
309
- // NOTE: not the current bus!
310
- Idx const measured_bus = branch_bus_idx[obj][measured_side];
311
- // eta += Y{side, b}^H * (variance^-1) * i_branch_{f, t}
312
- rhs_block.eta () +=
313
- dot (hermitian_transpose (param.branch_param [obj].value [measured_side * 2 + b]),
314
- diagonal_inverse (static_cast <IndependentComplexRandVar<sym>>(m).variance ),
315
- conj (m.value () / u[measured_bus]));
339
+ auto const apparent_power = static_cast <IndependentComplexRandVar<sym>>(m);
340
+ add_branch_measurement (measured_side,
341
+ {.value = conj (apparent_power.value / u[measured_bus]),
342
+ .variance = apparent_power.variance });
343
+ }
344
+ if (std::invoke (has_branch_current_[measured_side], measured_value, obj)) {
345
+ CurrentSensorCalcParam<sym> const m =
346
+ std::invoke (branch_current_[measured_side], measured_value, obj);
347
+ // for local angle current sensors, the current needs to be offset with the phase offset
348
+ // of the measured bus side. NOTE: not the bus that is currently being processed!
349
+ auto measured_current = static_cast <IndependentComplexRandVar<sym>>(m.measurement );
350
+ if (m.angle_measurement_type == AngleMeasurementType::local_angle) {
351
+ measured_current.value *= phase_shift (u[measured_bus]); // offset with the phase shift
352
+ }
353
+ add_branch_measurement (measured_side, measured_current);
316
354
}
317
355
}
318
356
}
0 commit comments