Skip to content

Commit 74220e6

Browse files
committed
fix residuals for global angle
Signed-off-by: Santiago Figueroa Manrique <figueroa1395@gmail.com>
1 parent 4a17de8 commit 74220e6

File tree

2 files changed

+63
-35
lines changed

2 files changed

+63
-35
lines changed

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

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -147,10 +147,15 @@ template <symmetry_tag current_sensor_symmetry_> class CurrentSensor : public Ge
147147
CurrentSensorOutput<sym_calc> get_generic_output(ComplexValue<sym_calc> const& i) const {
148148
CurrentSensorOutput<sym_calc> output{};
149149
output.id = id();
150-
ComplexValue<sym_calc> const i_residual{process_mean_val<sym_calc>(i_measured_ - i) * base_current_};
151150
output.energized = 1; // current sensor is always energized
152-
output.i_residual = cabs(i_residual);
153-
output.i_angle_residual = arg(i_residual);
151+
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();
158+
}
154159
return output;
155160
}
156161
};

tests/cpp_unit_tests/test_current_sensor.cpp

Lines changed: 55 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -33,26 +33,36 @@ TEST_CASE("Test current sensor") {
3333
{MeasuredTerminalType::branch_from, MeasuredTerminalType::branch_to, MeasuredTerminalType::branch3_1,
3434
MeasuredTerminalType::branch3_2, MeasuredTerminalType::branch3_3}) {
3535
CAPTURE(terminal_type);
36+
// for (auto const angle_measurement_type : {AngleMeasurementType::global_angle,
37+
// AngleMeasurementType::local_angle}) {
38+
// }
39+
auto const angle_measurement_type = AngleMeasurementType::global_angle;
40+
CAPTURE(angle_measurement_type);
3641

3742
CurrentSensorInput<symmetric_t> sym_current_sensor_input{};
3843
sym_current_sensor_input.id = 0;
3944
sym_current_sensor_input.measured_object = 1;
4045
sym_current_sensor_input.measured_terminal_type = terminal_type;
41-
sym_current_sensor_input.angle_measurement_type = AngleMeasurementType::local_angle;
46+
sym_current_sensor_input.angle_measurement_type = angle_measurement_type;
4247
sym_current_sensor_input.i_sigma = 1.0;
4348
sym_current_sensor_input.i_measured = 1.0 * 1e3;
44-
sym_current_sensor_input.i_angle_measured = 0.0;
49+
sym_current_sensor_input.i_angle_measured = pi / 4.;
4550
sym_current_sensor_input.i_angle_sigma = 0.2;
4651

4752
double const u_rated = 10.0e3;
4853
double const base_current = base_power_3p / u_rated / sqrt3;
4954
double const i_pu = 1.0e3 / base_current;
5055
double const i_sigma_pu = 1.0 / base_current;
51-
double const i_variance_pu = pow(i_sigma_pu, 2);
52-
double const i_angle_variance_pu = pow(0.2, 2);
56+
double const i_variance_pu = i_sigma_pu * i_sigma_pu;
57+
double const i_angle = pi / 4.;
58+
double const i_angle_sigma_pi = 0.2;
59+
double const i_angle_variance_pu = i_angle_sigma_pi * i_angle_sigma_pi;
5360

54-
ComplexValue<symmetric_t> const i_sym = (1.0 * 1e3 + 1i * 0.0) / base_current;
55-
ComplexValue<asymmetric_t> const i_asym = i_sym * RealValue<asymmetric_t>{1.0};
61+
auto const i_sym = ComplexValue<symmetric_t>{(1e3 * cos(i_angle) + 1i * 1e3 * sin(i_angle)) / base_current};
62+
auto const i_asym = ComplexValue<asymmetric_t>{
63+
(1e3 * cos(i_angle) + 1i * 1e3 * sin(i_angle)) / base_current,
64+
(1e3 * cos(i_angle + deg_240) + 1i * 1e3 * sin(i_angle + deg_240)) / base_current,
65+
(1e3 * cos(i_angle + deg_120) + 1i * 1e3 * sin(i_angle + deg_120)) / base_current};
5666

5767
CurrentSensor<symmetric_t> const sym_current_sensor{sym_current_sensor_input, u_rated};
5868

@@ -65,60 +75,73 @@ TEST_CASE("Test current sensor") {
6575
sym_current_sensor.get_output<asymmetric_t>(i_asym);
6676

6777
// Check symmetric sensor output for symmetric parameters
68-
CHECK(sym_sensor_param.angle_measurement_type == AngleMeasurementType::local_angle);
69-
CHECK(sym_sensor_param.measurement.real_component.variance == doctest::Approx(i_variance_pu));
78+
if constexpr (angle_measurement_type == AngleMeasurementType::global_angle) {
79+
CHECK(sym_sensor_param.angle_measurement_type == AngleMeasurementType::global_angle);
80+
} else {
81+
CHECK(sym_sensor_param.angle_measurement_type == AngleMeasurementType::local_angle);
82+
}
83+
// Var(I_Re) ≈ Var(I) * cos^2(pi/4) + Var(θ) * I^2 * sin^2(pi/4)
84+
CHECK(sym_sensor_param.measurement.real_component.variance ==
85+
doctest::Approx(0.5 * (i_variance_pu + i_angle_variance_pu * i_pu * i_pu)));
86+
// Var(I_Im) ≈ Var(I) * sin^2(pi/4) + Var(θ) * I^2 * cos^2(pi/4)
7087
CHECK(sym_sensor_param.measurement.imag_component.variance ==
71-
doctest::Approx(i_angle_variance_pu * i_pu * i_pu));
72-
CHECK(real(sym_sensor_param.measurement.value()) == doctest::Approx(i_pu));
73-
CHECK(imag(sym_sensor_param.measurement.value()) == doctest::Approx(0.0));
88+
doctest::Approx(0.5 * (i_variance_pu + i_angle_variance_pu * i_pu * i_pu)));
89+
CHECK(real(sym_sensor_param.measurement.value()) == doctest::Approx(i_pu * cos(i_angle)));
90+
CHECK(imag(sym_sensor_param.measurement.value()) == doctest::Approx(i_pu * sin(i_angle)));
7491

7592
CHECK(sym_sensor_output.id == 0);
7693
CHECK(sym_sensor_output.energized == 1);
7794
CHECK(sym_sensor_output.i_residual == doctest::Approx(0.0));
7895
CHECK(sym_sensor_output.i_angle_residual == doctest::Approx(0.0));
7996

8097
// Check symmetric sensor output for asymmetric parameters
81-
CHECK(asym_sensor_param.measurement.real_component.variance[0] == doctest::Approx(i_variance_pu));
98+
CHECK(asym_sensor_param.measurement.real_component.variance[0] ==
99+
doctest::Approx(0.5 * (i_variance_pu + i_angle_variance_pu * i_pu * i_pu)));
100+
auto const shifted_i_angle = i_angle + deg_240;
82101
CHECK(asym_sensor_param.measurement.imag_component.variance[1] ==
83-
doctest::Approx(i_variance_pu * sin(deg_240) * sin(deg_240) +
84-
i_angle_variance_pu * i_pu * i_pu * cos(deg_240) * cos(deg_240)));
85-
CHECK(real(asym_sensor_param.measurement.value()[0]) == doctest::Approx(i_pu));
86-
CHECK(imag(asym_sensor_param.measurement.value()[1]) == doctest::Approx(i_pu * sin(deg_240)));
102+
doctest::Approx(i_variance_pu * sin(shifted_i_angle) * sin(shifted_i_angle) +
103+
i_angle_variance_pu * i_pu * i_pu * cos(shifted_i_angle) * cos(shifted_i_angle)));
104+
CHECK(real(asym_sensor_param.measurement.value()[0]) == doctest::Approx(i_pu * cos(i_angle)));
105+
CHECK(imag(asym_sensor_param.measurement.value()[1]) == doctest::Approx(i_pu * sin(shifted_i_angle)));
87106

88107
CHECK(sym_sensor_output_asym_param.id == 0);
89108
CHECK(sym_sensor_output_asym_param.energized == 1);
90-
CHECK(sym_sensor_output_asym_param.i_residual[0] == doctest::Approx(0.0));
91-
CHECK(sym_sensor_output_asym_param.i_angle_residual[1] == doctest::Approx(0.0));
109+
for (auto i = 0; i < 3; ++i) {
110+
CHECK(sym_sensor_output_asym_param.i_residual[i] == doctest::Approx(0.0));
111+
CHECK(sym_sensor_output_asym_param.i_angle_residual[i] == doctest::Approx(0.0));
112+
}
92113

93114
CHECK(sym_current_sensor.get_terminal_type() == terminal_type);
94115

95-
CHECK(sym_current_sensor.get_angle_measurement_type() == AngleMeasurementType::local_angle);
116+
CHECK(sym_current_sensor.get_angle_measurement_type() == AngleMeasurementType::global_angle);
96117
}
97118
SUBCASE("Wrong measured terminal type") {
98119
for (auto const terminal_type :
99120
{MeasuredTerminalType::source, MeasuredTerminalType::shunt, MeasuredTerminalType::load,
100121
MeasuredTerminalType::generator, MeasuredTerminalType::node}) {
101-
CHECK_THROWS_AS((CurrentSensor<symmetric_t>{
102-
{1, 1, terminal_type, AngleMeasurementType::local_angle, 1.0, 1.0, 1.0, 1.0}, 1.0}),
103-
InvalidMeasuredTerminalType);
122+
CHECK_THROWS_AS(
123+
(CurrentSensor<symmetric_t>{
124+
{1, 1, terminal_type, AngleMeasurementType::global_angle, 1.0, 1.0, 1.0, 1.0}, 1.0}),
125+
InvalidMeasuredTerminalType);
104126
}
105127
}
106128
SUBCASE("Symmetric calculation parameters") {
107129
double const u_rated = 10.0e3;
108130
double const base_current = base_power_3p / u_rated / sqrt3;
109131

110-
CurrentSensor<symmetric_t> sym_current_sensor{{.id = 1,
111-
.measured_object = 1,
112-
.measured_terminal_type = MeasuredTerminalType::branch3_1,
113-
.angle_measurement_type = AngleMeasurementType::local_angle},
114-
u_rated};
132+
CurrentSensor<symmetric_t> sym_current_sensor{
133+
{.id = 1,
134+
.measured_object = 1,
135+
.measured_terminal_type = MeasuredTerminalType::branch3_1,
136+
.angle_measurement_type = AngleMeasurementType::global_angle},
137+
u_rated};
115138

116139
SUBCASE("No phase shift") {
117140
sym_current_sensor.update(
118141
{.id = 1, .i_sigma = 1.0, .i_angle_sigma = 0.2, .i_measured = 1.0, .i_angle_measured = 0.0});
119142
auto const sym_param = sym_current_sensor.calc_param<symmetric_t>();
120143

121-
CHECK(sym_param.angle_measurement_type == AngleMeasurementType::local_angle);
144+
CHECK(sym_param.angle_measurement_type == AngleMeasurementType::global_angle);
122145
CHECK(sym_param.measurement.real_component.variance == doctest::Approx(pow(1.0 / base_current, 2)));
123146
CHECK(sym_param.measurement.imag_component.variance == doctest::Approx(pow(0.2 / base_current, 2)));
124147
CHECK(real(sym_param.measurement.value()) == doctest::Approx(1.0 / base_current));
@@ -130,7 +153,7 @@ TEST_CASE("Test current sensor") {
130153
{.id = 1, .i_sigma = 1.0, .i_angle_sigma = 0.2, .i_measured = 1.0, .i_angle_measured = pi / 2});
131154
auto const sym_param = sym_current_sensor.calc_param<symmetric_t>();
132155

133-
CHECK(sym_param.angle_measurement_type == AngleMeasurementType::local_angle);
156+
CHECK(sym_param.angle_measurement_type == AngleMeasurementType::global_angle);
134157
CHECK(sym_param.measurement.real_component.variance == doctest::Approx(pow(0.2 / base_current, 2)));
135158
CHECK(sym_param.measurement.imag_component.variance == doctest::Approx(pow(1.0 / base_current, 2)));
136159
CHECK(real(sym_param.measurement.value()) == doctest::Approx(0.0 / base_current));
@@ -145,7 +168,7 @@ TEST_CASE("Test current sensor") {
145168
{.id = 1, .i_sigma = 1.0, .i_angle_sigma = 0.2, .i_measured = 1.0, .i_angle_measured = pi / 4});
146169
auto const sym_param = sym_current_sensor.calc_param<symmetric_t>();
147170

148-
CHECK(sym_param.angle_measurement_type == AngleMeasurementType::local_angle);
171+
CHECK(sym_param.angle_measurement_type == AngleMeasurementType::global_angle);
149172
CHECK(sym_param.measurement.real_component.variance ==
150173
doctest::Approx(1.04 / 2.0 / (base_current * base_current)));
151174
CHECK(sym_param.measurement.imag_component.variance ==
@@ -164,7 +187,7 @@ TEST_CASE("Test current sensor") {
164187
CurrentSensor<symmetric_t> const current_sensor{{.id = 1,
165188
.measured_object = 1,
166189
.measured_terminal_type = MeasuredTerminalType::branch3_1,
167-
.angle_measurement_type = AngleMeasurementType::local_angle,
190+
.angle_measurement_type = AngleMeasurementType::global_angle,
168191
.i_sigma = i_sigma,
169192
.i_angle_sigma = i_angle_sigma,
170193
.i_measured = i_measured,
@@ -289,7 +312,7 @@ TEST_CASE("Test current sensor") {
289312
CurrentSensor<asymmetric_t> const current_sensor{{.id = 1,
290313
.measured_object = 1,
291314
.measured_terminal_type = measured_terminal_type,
292-
.angle_measurement_type = AngleMeasurementType::local_angle,
315+
.angle_measurement_type = AngleMeasurementType::global_angle,
293316
.i_sigma = i_sigma,
294317
.i_angle_sigma = i_angle_sigma,
295318
.i_measured = i_measured,

0 commit comments

Comments
 (0)