Skip to content

Commit 1ff7066

Browse files
authored
Merge pull request #961 from PowerGridModel/fix-current-sensor-output-residuals
Fix output residuals for current sensor
2 parents 22d88bc + bd9fec2 commit 1ff7066

File tree

5 files changed

+262
-145
lines changed

5 files changed

+262
-145
lines changed

power_grid_model_c/power_grid_model/include/power_grid_model/all_components.hpp

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -31,16 +31,4 @@ using AllComponents =
3131
ComponentList<Node, Line, Link, GenericBranch, Transformer, ThreeWindingTransformer, Shunt, Source, SymGenerator,
3232
AsymGenerator, SymLoad, AsymLoad, SymPowerSensor, AsymPowerSensor, SymVoltageSensor,
3333
AsymVoltageSensor, SymCurrentSensor, AsymCurrentSensor, Fault, TransformerTapRegulator>;
34-
35-
template <typename T>
36-
concept power_or_current_sensor_c =
37-
std::derived_from<T, GenericPowerSensor> || std::derived_from<T, GenericCurrentSensor>;
38-
39-
static_assert(power_or_current_sensor_c<SymPowerSensor>);
40-
static_assert(power_or_current_sensor_c<AsymPowerSensor>);
41-
static_assert(power_or_current_sensor_c<SymCurrentSensor>);
42-
static_assert(power_or_current_sensor_c<AsymCurrentSensor>);
43-
static_assert(!power_or_current_sensor_c<SymVoltageSensor>);
44-
static_assert(!power_or_current_sensor_c<AsymVoltageSensor>);
45-
4634
} // namespace power_grid_model

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

Lines changed: 39 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,12 @@ class GenericCurrentSensor : public Sensor {
2626
MeasuredTerminalType get_terminal_type() const { return terminal_type_; }
2727
AngleMeasurementType get_angle_measurement_type() const { return angle_measurement_type_; }
2828

29-
template <symmetry_tag sym> CurrentSensorOutput<sym> get_output(ComplexValue<sym> const& i) const {
29+
template <symmetry_tag sym>
30+
CurrentSensorOutput<sym> get_output(ComplexValue<sym> const& i, ComplexValue<sym> const& u) const {
3031
if constexpr (is_symmetric_v<sym>) {
31-
return get_sym_output(i);
32+
return get_sym_output(i, u);
3233
} else {
33-
return get_asym_output(i);
34+
return get_asym_output(i, u);
3435
}
3536
}
3637

@@ -62,8 +63,10 @@ class GenericCurrentSensor : public Sensor {
6263
virtual CurrentSensorCalcParam<symmetric_t> sym_calc_param() const = 0;
6364
virtual CurrentSensorCalcParam<asymmetric_t> asym_calc_param() const = 0;
6465

65-
virtual CurrentSensorOutput<symmetric_t> get_sym_output(ComplexValue<symmetric_t> const& i) const = 0;
66-
virtual CurrentSensorOutput<asymmetric_t> get_asym_output(ComplexValue<asymmetric_t> const& i) const = 0;
66+
virtual CurrentSensorOutput<symmetric_t> get_sym_output(ComplexValue<symmetric_t> const& i,
67+
ComplexValue<symmetric_t> const& u) const = 0;
68+
virtual CurrentSensorOutput<asymmetric_t> get_asym_output(ComplexValue<asymmetric_t> const& i,
69+
ComplexValue<asymmetric_t> const& u) const = 0;
6770
};
6871

6972
template <symmetry_tag current_sensor_symmetry_> class CurrentSensor : public GenericCurrentSensor {
@@ -137,20 +140,43 @@ template <symmetry_tag current_sensor_symmetry_> class CurrentSensor : public Ge
137140
return CurrentSensorCalcParam<sym_calc>{.angle_measurement_type = angle_measurement_type(),
138141
.measurement = DecomposedComplexRandVar<sym_calc>(i_polar)};
139142
}
140-
CurrentSensorOutput<symmetric_t> get_sym_output(ComplexValue<symmetric_t> const& i) const final {
141-
return get_generic_output<symmetric_t>(i);
143+
CurrentSensorOutput<symmetric_t> get_sym_output(ComplexValue<symmetric_t> const& i,
144+
ComplexValue<symmetric_t> const& u) const final {
145+
return get_generic_output<symmetric_t>(i, u);
142146
}
143-
CurrentSensorOutput<asymmetric_t> get_asym_output(ComplexValue<asymmetric_t> const& i) const final {
144-
return get_generic_output<asymmetric_t>(i);
147+
CurrentSensorOutput<asymmetric_t> get_asym_output(ComplexValue<asymmetric_t> const& i,
148+
ComplexValue<asymmetric_t> const& u) const final {
149+
return get_generic_output<asymmetric_t>(i, u);
145150
}
146151
template <symmetry_tag sym_calc>
147-
CurrentSensorOutput<sym_calc> get_generic_output(ComplexValue<sym_calc> const& i) const {
152+
CurrentSensorOutput<sym_calc> get_generic_output(ComplexValue<sym_calc> const& i,
153+
ComplexValue<sym_calc> const& u) const {
148154
CurrentSensorOutput<sym_calc> output{};
149155
output.id = id();
150-
ComplexValue<sym_calc> const i_residual{process_mean_val<sym_calc>(i_measured_ - i) * base_current_};
151156
output.energized = 1; // current sensor is always energized
152-
output.i_residual = cabs(i_residual);
153-
output.i_angle_residual = arg(i_residual);
157+
auto const i_calc_param = calc_param<sym_calc>();
158+
auto const angle_measurement_type = i_calc_param.angle_measurement_type;
159+
auto const i_measured_complex = i_calc_param.measurement.value();
160+
ComplexValue<sym_calc> i_output = [&i, &u, &angle_measurement_type] {
161+
switch (angle_measurement_type) {
162+
case AngleMeasurementType::global_angle: {
163+
return i;
164+
}
165+
case AngleMeasurementType::local_angle: {
166+
// I_l = conj(I_g) * exp(i * u_angle)
167+
// Tranform back the output angle to the local angle frame of reference
168+
auto const u_angle = arg(u);
169+
return ComplexValue<sym_calc>{conj(i) * (cos(u_angle) + 1i * sin(u_angle))};
170+
}
171+
default: {
172+
throw MissingCaseForEnumError{"generic output angle measurement type", angle_measurement_type};
173+
}
174+
}
175+
}();
176+
output.i_residual = (cabs(i_measured_complex) - cabs(i_output)) * base_current_;
177+
// arg(e^i * u_angle) = u_angle in (-pi, pi]
178+
auto const unbounded_i_angle_residual = arg(i_measured_complex) - arg(i_output);
179+
output.i_angle_residual = arg(ComplexValue<sym_calc>{exp(1.0i * unbounded_i_angle_residual)});
154180
return output;
155181
}
156182
};

power_grid_model_c/power_grid_model/include/power_grid_model/main_core/output.hpp

Lines changed: 78 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -242,14 +242,14 @@ inline auto output_result(Component const& voltage_sensor, MainModelState<Compon
242242
}
243243

244244
// output power sensor
245-
template <power_or_current_sensor_c Component, class ComponentContainer,
245+
template <std::derived_from<GenericPowerSensor> Component, class ComponentContainer,
246246
steady_state_solver_output_type SolverOutputType>
247247
requires model_component_state_c<MainModelState, ComponentContainer, Component>
248-
constexpr auto output_result(Component const& power_or_current_sensor, MainModelState<ComponentContainer> const& state,
248+
constexpr auto output_result(Component const& power_sensor, MainModelState<ComponentContainer> const& state,
249249
std::vector<SolverOutputType> const& solver_output, Idx const obj_seq) {
250250
using sym = typename SolverOutputType::sym;
251251

252-
auto const terminal_type = power_or_current_sensor.get_terminal_type();
252+
auto const terminal_type = power_sensor.get_terminal_type();
253253
Idx2D const obj_math_id = [&]() {
254254
switch (terminal_type) {
255255
using enum MeasuredTerminalType;
@@ -281,7 +281,7 @@ constexpr auto output_result(Component const& power_or_current_sensor, MainModel
281281
}();
282282

283283
if (obj_math_id.group == -1) {
284-
return power_or_current_sensor.template get_null_output<sym>();
284+
return power_sensor.template get_null_output<sym>();
285285
}
286286

287287
switch (terminal_type) {
@@ -295,30 +295,91 @@ constexpr auto output_result(Component const& power_or_current_sensor, MainModel
295295
case branch3_2:
296296
[[fallthrough]];
297297
case branch3_3:
298-
return power_or_current_sensor.template get_output<sym>(
299-
solver_output[obj_math_id.group].branch[obj_math_id.pos].s_f);
298+
return power_sensor.template get_output<sym>(solver_output[obj_math_id.group].branch[obj_math_id.pos].s_f);
300299
case branch_to:
301-
return power_or_current_sensor.template get_output<sym>(
302-
solver_output[obj_math_id.group].branch[obj_math_id.pos].s_t);
300+
return power_sensor.template get_output<sym>(solver_output[obj_math_id.group].branch[obj_math_id.pos].s_t);
303301
case source:
304-
return power_or_current_sensor.template get_output<sym>(
305-
solver_output[obj_math_id.group].source[obj_math_id.pos].s);
302+
return power_sensor.template get_output<sym>(solver_output[obj_math_id.group].source[obj_math_id.pos].s);
306303
case shunt:
307-
return power_or_current_sensor.template get_output<sym>(
308-
solver_output[obj_math_id.group].shunt[obj_math_id.pos].s);
304+
return power_sensor.template get_output<sym>(solver_output[obj_math_id.group].shunt[obj_math_id.pos].s);
309305
case load:
310306
[[fallthrough]];
311307
case generator:
312-
return power_or_current_sensor.template get_output<sym>(
313-
solver_output[obj_math_id.group].load_gen[obj_math_id.pos].s);
308+
return power_sensor.template get_output<sym>(solver_output[obj_math_id.group].load_gen[obj_math_id.pos].s);
314309
case node:
315-
return power_or_current_sensor.template get_output<sym>(
316-
solver_output[obj_math_id.group].bus_injection[obj_math_id.pos]);
310+
return power_sensor.template get_output<sym>(solver_output[obj_math_id.group].bus_injection[obj_math_id.pos]);
317311
default:
318312
throw MissingCaseForEnumError{std::format("{} output_result()", Component::name), terminal_type};
319313
}
320314
}
321-
template <power_or_current_sensor_c Component, class ComponentContainer,
315+
template <std::derived_from<GenericPowerSensor> Component, class ComponentContainer,
316+
short_circuit_solver_output_type SolverOutputType>
317+
requires model_component_state_c<MainModelState, ComponentContainer, Component>
318+
constexpr auto output_result(Component const& power_or_current_sensor,
319+
MainModelState<ComponentContainer> const& /* state */,
320+
std::vector<SolverOutputType> const& /* solver_output */, Idx const /* obj_seq */) {
321+
return power_or_current_sensor.get_null_sc_output();
322+
}
323+
324+
// output current sensor
325+
template <std::derived_from<GenericCurrentSensor> Component, class ComponentContainer,
326+
steady_state_solver_output_type SolverOutputType>
327+
requires model_component_state_c<MainModelState, ComponentContainer, Component>
328+
constexpr auto output_result(Component const& current_sensor, MainModelState<ComponentContainer> const& state,
329+
std::vector<SolverOutputType> const& solver_output, Idx const obj_seq) {
330+
using sym = typename SolverOutputType::sym;
331+
332+
auto const terminal_type = current_sensor.get_terminal_type();
333+
Idx2D const obj_math_id = [&]() {
334+
switch (terminal_type) {
335+
using enum MeasuredTerminalType;
336+
337+
case branch_from:
338+
[[fallthrough]];
339+
case branch_to:
340+
return state.topo_comp_coup->branch[obj_seq];
341+
// from branch3, get relevant math object branch based on the measured side
342+
case branch3_1:
343+
return Idx2D{state.topo_comp_coup->branch3[obj_seq].group, state.topo_comp_coup->branch3[obj_seq].pos[0]};
344+
case branch3_2:
345+
return Idx2D{state.topo_comp_coup->branch3[obj_seq].group, state.topo_comp_coup->branch3[obj_seq].pos[1]};
346+
case branch3_3:
347+
return Idx2D{state.topo_comp_coup->branch3[obj_seq].group, state.topo_comp_coup->branch3[obj_seq].pos[2]};
348+
default:
349+
throw MissingCaseForEnumError{std::format("{} output_result()", Component::name), terminal_type};
350+
}
351+
}();
352+
353+
if (obj_math_id.group == -1) {
354+
return current_sensor.template get_null_output<sym>();
355+
}
356+
357+
auto const topological_index = get_topology_index<Branch>(state, obj_math_id);
358+
auto const branch_nodes = get_branch_nodes<Branch>(state, topological_index);
359+
auto const node_from_math_id = get_math_id<Node>(state, branch_nodes[0]);
360+
auto const node_to_math_id = get_math_id<Node>(state, branch_nodes[1]);
361+
362+
switch (terminal_type) {
363+
using enum MeasuredTerminalType;
364+
365+
case branch_from:
366+
// all power sensors in branch3 are at from side in the mathematical model
367+
[[fallthrough]];
368+
case branch3_1:
369+
[[fallthrough]];
370+
case branch3_2:
371+
[[fallthrough]];
372+
case branch3_3:
373+
return current_sensor.template get_output<sym>(solver_output[obj_math_id.group].branch[obj_math_id.pos].i_f,
374+
solver_output[node_from_math_id.group].u[node_from_math_id.pos]);
375+
case branch_to:
376+
return current_sensor.template get_output<sym>(solver_output[obj_math_id.group].branch[obj_math_id.pos].i_t,
377+
solver_output[node_to_math_id.group].u[node_to_math_id.pos]);
378+
default:
379+
throw MissingCaseForEnumError{std::format("{} output_result()", Component::name), terminal_type};
380+
}
381+
}
382+
template <std::derived_from<GenericCurrentSensor> Component, class ComponentContainer,
322383
short_circuit_solver_output_type SolverOutputType>
323384
requires model_component_state_c<MainModelState, ComponentContainer, Component>
324385
constexpr auto output_result(Component const& power_or_current_sensor,

0 commit comments

Comments
 (0)