Skip to content

Commit ce9b9bd

Browse files
committed
initial implementation for local current sensor
Signed-off-by: Santiago Figueroa Manrique <figueroa1395@gmail.com>
1 parent 4d190d8 commit ce9b9bd

File tree

3 files changed

+99
-54
lines changed

3 files changed

+99
-54
lines changed

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

Lines changed: 26 additions & 16 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,25 +140,32 @@ 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();
150156
output.energized = 1; // current sensor is always energized
157+
auto i_output = i;
151158
auto const i_calc_param = calc_param<sym_calc>();
152-
auto const i_measured_complex = i_calc_param.measurement.value();
153-
if (i_calc_param.angle_measurement_type == AngleMeasurementType::global_angle) {
154-
output.i_residual = (cabs(i_measured_complex) - cabs(i)) * base_current_;
155-
output.i_angle_residual = arg(i_measured_complex) - arg(i);
156-
} else {
157-
throw NotImplementedError();
159+
auto const& angle_measurement_type = i_calc_param.angle_measurement_type;
160+
auto const& i_measured_complex = i_calc_param.measurement.value();
161+
if (angle_measurement_type == AngleMeasurementType::local_angle) {
162+
// I_l = conj(I_g) * exp(i * u_angle)
163+
// Tranform back the output angle to the local angle frame of reference
164+
auto const u_angle = arg(u);
165+
i_output = conj(i_output) * (cos(u_angle) + 1i * sin(u_angle));
158166
}
167+
output.i_residual = (cabs(i_measured_complex) - cabs(i_output)) * base_current_;
168+
output.i_angle_residual = arg(i_measured_complex) - arg(i_output);
159169
return output;
160170
}
161171
};

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

Lines changed: 71 additions & 36 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,25 +281,9 @@ 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

287-
auto const get_current_or_power_branch_f = [&solver_output, &obj_math_id]<power_or_current_sensor_c Sensor>() {
288-
if constexpr (std::derived_from<Sensor, GenericPowerSensor>) {
289-
return solver_output[obj_math_id.group].branch[obj_math_id.pos].s_f;
290-
} else if constexpr (std::derived_from<Sensor, GenericCurrentSensor>) {
291-
return solver_output[obj_math_id.group].branch[obj_math_id.pos].i_f;
292-
}
293-
};
294-
295-
auto const get_current_or_power_branch_t = [&solver_output, &obj_math_id]<power_or_current_sensor_c Sensor>() {
296-
if constexpr (std::derived_from<Sensor, GenericPowerSensor>) {
297-
return solver_output[obj_math_id.group].branch[obj_math_id.pos].s_t;
298-
} else if constexpr (std::derived_from<Sensor, GenericCurrentSensor>) {
299-
return solver_output[obj_math_id.group].branch[obj_math_id.pos].i_t;
300-
}
301-
};
302-
303287
switch (terminal_type) {
304288
using enum MeasuredTerminalType;
305289

@@ -311,25 +295,78 @@ constexpr auto output_result(Component const& power_or_current_sensor, MainModel
311295
case branch3_2:
312296
[[fallthrough]];
313297
case branch3_3:
314-
return power_or_current_sensor.template get_output<sym>(
315-
get_current_or_power_branch_f.template operator()<Component>());
298+
return power_sensor.template get_output<sym>(solver_output[obj_math_id.group].branch[obj_math_id.pos].s_f);
316299
case branch_to:
317-
return power_or_current_sensor.template get_output<sym>(
318-
get_current_or_power_branch_t.template operator()<Component>());
300+
return power_sensor.template get_output<sym>(solver_output[obj_math_id.group].branch[obj_math_id.pos].s_t);
319301
case source:
320-
return power_or_current_sensor.template get_output<sym>(
321-
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);
322303
case shunt:
323-
return power_or_current_sensor.template get_output<sym>(
324-
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);
325305
case load:
326306
[[fallthrough]];
327307
case generator:
328-
return power_or_current_sensor.template get_output<sym>(
329-
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);
330309
case node:
331-
return power_or_current_sensor.template get_output<sym>(
332-
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]);
311+
default:
312+
throw MissingCaseForEnumError{std::format("{} output_result()", Component::name), terminal_type};
313+
}
314+
}
315+
316+
// output current sensor
317+
template <std::derived_from<GenericCurrentSensor> Component, class ComponentContainer,
318+
steady_state_solver_output_type SolverOutputType>
319+
requires model_component_state_c<MainModelState, ComponentContainer, Component>
320+
constexpr auto output_result(Component const& current_sensor, MainModelState<ComponentContainer> const& state,
321+
std::vector<SolverOutputType> const& solver_output, Idx const obj_seq) {
322+
using sym = typename SolverOutputType::sym;
323+
324+
auto const terminal_type = current_sensor.get_terminal_type();
325+
Idx2D const obj_math_id = [&]() {
326+
switch (terminal_type) {
327+
using enum MeasuredTerminalType;
328+
329+
case branch_from:
330+
[[fallthrough]];
331+
case branch_to:
332+
return state.topo_comp_coup->branch[obj_seq];
333+
// from branch3, get relevant math object branch based on the measured side
334+
case branch3_1:
335+
return Idx2D{state.topo_comp_coup->branch3[obj_seq].group, state.topo_comp_coup->branch3[obj_seq].pos[0]};
336+
case branch3_2:
337+
return Idx2D{state.topo_comp_coup->branch3[obj_seq].group, state.topo_comp_coup->branch3[obj_seq].pos[1]};
338+
case branch3_3:
339+
return Idx2D{state.topo_comp_coup->branch3[obj_seq].group, state.topo_comp_coup->branch3[obj_seq].pos[2]};
340+
default:
341+
throw MissingCaseForEnumError{std::format("{} output_result()", Component::name), terminal_type};
342+
}
343+
}();
344+
345+
if (obj_math_id.group == -1) {
346+
return current_sensor.template get_null_output<sym>();
347+
}
348+
349+
auto const topological_index = get_topology_index<Branch>(state, obj_math_id);
350+
auto const branch_nodes = get_branch_nodes<Branch>(state, topological_index);
351+
auto const node_from_math_id = get_math_id<Node>(state, branch_nodes[0]);
352+
auto const node_to_math_id = get_math_id<Node>(state, branch_nodes[1]);
353+
354+
switch (terminal_type) {
355+
using enum MeasuredTerminalType;
356+
357+
case branch_from:
358+
// all power sensors in branch3 are at from side in the mathematical model
359+
[[fallthrough]];
360+
case branch3_1:
361+
[[fallthrough]];
362+
case branch3_2:
363+
[[fallthrough]];
364+
case branch3_3:
365+
return current_sensor.template get_output<sym>(solver_output[obj_math_id.group].branch[obj_math_id.pos].i_f,
366+
solver_output[node_from_math_id.group].u[node_from_math_id.pos]);
367+
case branch_to:
368+
return current_sensor.template get_output<sym>(solver_output[obj_math_id.group].branch[obj_math_id.pos].i_t,
369+
solver_output[node_to_math_id.group].u[node_to_math_id.pos]);
333370
default:
334371
throw MissingCaseForEnumError{std::format("{} output_result()", Component::name), terminal_type};
335372
}
@@ -421,8 +458,7 @@ constexpr ResIt output_result(MainModelState<ComponentContainer> const& state,
421458
MathOutput<std::vector<SolverOutputType>> const& math_output, ResIt res_it) {
422459
return detail::produce_output<Component, Idx2D>(
423460
state, res_it, [&state, &math_output](Component const& component, Idx2D const math_id) {
424-
return output_result<Component>(component, state, math_output.solver_output,
425-
math_id); // probably here is where all the math_id things are called
461+
return output_result<Component>(component, state, math_output.solver_output, math_id);
426462
});
427463
}
428464
template <std::derived_from<Base> Component, class ComponentContainer, solver_output_type SolverOutputType,
@@ -431,8 +467,7 @@ template <std::derived_from<Base> Component, class ComponentContainer, solver_ou
431467
requires(Component const& component, MainModelState<ComponentContainer> const& state,
432468
std::vector<SolverOutputType> const& solver_output, Idx obj_seq) {
433469
{
434-
output_result<Component>(component, state, solver_output,
435-
obj_seq) // probably here is where the sensors things are called
470+
output_result<Component>(component, state, solver_output, obj_seq)
436471
} -> detail::assignable_to<std::add_lvalue_reference_t<std::iter_value_t<ResIt>>>;
437472
}
438473
constexpr ResIt output_result(MainModelState<ComponentContainer> const& state,

tests/cpp_unit_tests/test_current_sensor.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,9 @@ TEST_CASE("Test current sensor") {
7070
CurrentSensorCalcParam<asymmetric_t> asym_sensor_param = sym_current_sensor.calc_param<asymmetric_t>();
7171

7272
CurrentSensorOutput<symmetric_t> const sym_sensor_output =
73-
sym_current_sensor.get_output<symmetric_t>(i_sym);
73+
sym_current_sensor.get_output<symmetric_t>(i_sym, ComplexValue<symmetric_t>{0.0});
7474
CurrentSensorOutput<asymmetric_t> sym_sensor_output_asym_param =
75-
sym_current_sensor.get_output<asymmetric_t>(i_asym);
75+
sym_current_sensor.get_output<asymmetric_t>(i_asym, ComplexValue<asymmetric_t>{0.0});
7676

7777
// Check symmetric sensor output for symmetric parameters
7878
if constexpr (angle_measurement_type == AngleMeasurementType::global_angle) {

0 commit comments

Comments
 (0)