Skip to content

Commit 040ba60

Browse files
committed
unit test for asymmetric current sensor
Signed-off-by: Santiago Figueroa Manrique <figueroa1395@gmail.com>
1 parent ce9b9bd commit 040ba60

File tree

1 file changed

+107
-80
lines changed

1 file changed

+107
-80
lines changed

tests/cpp_unit_tests/test_current_sensor.cpp

Lines changed: 107 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -33,87 +33,114 @@ 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);
41-
42-
CurrentSensorInput<symmetric_t> sym_current_sensor_input{};
43-
sym_current_sensor_input.id = 0;
44-
sym_current_sensor_input.measured_object = 1;
45-
sym_current_sensor_input.measured_terminal_type = terminal_type;
46-
sym_current_sensor_input.angle_measurement_type = angle_measurement_type;
47-
sym_current_sensor_input.i_sigma = 1.0;
48-
sym_current_sensor_input.i_measured = 1.0 * 1e3;
49-
sym_current_sensor_input.i_angle_measured = pi / 4.;
50-
sym_current_sensor_input.i_angle_sigma = 0.2;
51-
52-
double const u_rated = 10.0e3;
53-
double const base_current = base_power_3p / u_rated / sqrt3;
54-
double const i_pu = 1.0e3 / base_current;
55-
double const i_sigma_pu = 1.0 / base_current;
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;
60-
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};
66-
67-
CurrentSensor<symmetric_t> const sym_current_sensor{sym_current_sensor_input, u_rated};
68-
69-
CurrentSensorCalcParam<symmetric_t> sym_sensor_param = sym_current_sensor.calc_param<symmetric_t>();
70-
CurrentSensorCalcParam<asymmetric_t> asym_sensor_param = sym_current_sensor.calc_param<asymmetric_t>();
71-
72-
CurrentSensorOutput<symmetric_t> const sym_sensor_output =
73-
sym_current_sensor.get_output<symmetric_t>(i_sym, ComplexValue<symmetric_t>{0.0});
74-
CurrentSensorOutput<asymmetric_t> sym_sensor_output_asym_param =
75-
sym_current_sensor.get_output<asymmetric_t>(i_asym, ComplexValue<asymmetric_t>{0.0});
76-
77-
// Check symmetric sensor output for symmetric parameters
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);
36+
for (auto const angle_measurement_type :
37+
{AngleMeasurementType::global_angle, AngleMeasurementType::local_angle}) {
38+
CAPTURE(angle_measurement_type);
39+
40+
CurrentSensorInput<symmetric_t> sym_current_sensor_input{};
41+
sym_current_sensor_input.id = 0;
42+
sym_current_sensor_input.measured_object = 1;
43+
sym_current_sensor_input.measured_terminal_type = terminal_type;
44+
sym_current_sensor_input.angle_measurement_type = angle_measurement_type;
45+
sym_current_sensor_input.i_sigma = 1.0;
46+
sym_current_sensor_input.i_measured = 1.0 * 1e3;
47+
sym_current_sensor_input.i_angle_measured = pi / 4.;
48+
sym_current_sensor_input.i_angle_sigma = 0.2;
49+
50+
double const u_rated = 10.0e3;
51+
double const base_current = base_power_3p / u_rated / sqrt3;
52+
double const i_pu = 1.0e3 / base_current;
53+
double const i_sigma_pu = 1.0 / base_current;
54+
double const i_variance_pu = i_sigma_pu * i_sigma_pu;
55+
double const i_angle = pi / 4.;
56+
double const i_angle_sigma_pi = 0.2;
57+
double const i_angle_variance_pu = i_angle_sigma_pi * i_angle_sigma_pi;
58+
59+
auto const i_sym =
60+
ComplexValue<symmetric_t>{(1e3 * cos(i_angle) + 1i * 1e3 * sin(i_angle)) / base_current};
61+
auto const i_asym = ComplexValue<asymmetric_t>{
62+
(1e3 * cos(i_angle) + 1i * 1e3 * sin(i_angle)) / base_current,
63+
(1e3 * cos(i_angle + deg_240) + 1i * 1e3 * sin(i_angle + deg_240)) / base_current,
64+
(1e3 * cos(i_angle + deg_120) + 1i * 1e3 * sin(i_angle + deg_120)) / base_current};
65+
auto const i_asym_local =
66+
ComplexValue<asymmetric_t>{(1e3 * cos(i_angle) + 1i * 1e3 * sin(i_angle)) / base_current,
67+
(1e3 * cos(i_angle) + 1i * 1e3 * sin(i_angle)) / base_current,
68+
(1e3 * cos(i_angle) + 1i * 1e3 * sin(i_angle)) / base_current};
69+
70+
CurrentSensor<symmetric_t> const sym_current_sensor{sym_current_sensor_input, u_rated};
71+
72+
CurrentSensorCalcParam<symmetric_t> sym_sensor_param = sym_current_sensor.calc_param<symmetric_t>();
73+
CurrentSensorCalcParam<asymmetric_t> asym_sensor_param = sym_current_sensor.calc_param<asymmetric_t>();
74+
75+
CurrentSensorOutput<symmetric_t> const sym_sensor_output =
76+
sym_current_sensor.get_output<symmetric_t>(i_sym, ComplexValue<symmetric_t>{1.0});
77+
CurrentSensorOutput<asymmetric_t> sym_sensor_output_asym_param =
78+
sym_current_sensor.get_output<asymmetric_t>(i_asym, ComplexValue<asymmetric_t>{1.0});
79+
80+
// These two are only to test the residuals for local angle measurements.
81+
// conj(i) simulates the phase shift for local angle output residuals when the reference voltage is
82+
// real.
83+
CurrentSensorOutput<symmetric_t> const sym_sensor_output_local_residuals =
84+
sym_current_sensor.get_output<symmetric_t>(conj(i_sym), ComplexValue<symmetric_t>{1.0});
85+
CurrentSensorOutput<asymmetric_t> sym_sensor_output_asym_param_local_residuals =
86+
sym_current_sensor.get_output<asymmetric_t>(conj(i_asym_local), ComplexValue<asymmetric_t>{1.0});
87+
88+
// Check symmetric sensor output for symmetric parameters
89+
if (angle_measurement_type == AngleMeasurementType::global_angle) {
90+
CHECK(sym_sensor_param.angle_measurement_type == AngleMeasurementType::global_angle);
91+
} else {
92+
CHECK(sym_sensor_param.angle_measurement_type == AngleMeasurementType::local_angle);
93+
}
94+
// Var(I_Re) ≈ Var(I) * cos^2(pi/4) + Var(θ) * I^2 * sin^2(pi/4)
95+
CHECK(sym_sensor_param.measurement.real_component.variance ==
96+
doctest::Approx(0.5 * (i_variance_pu + i_angle_variance_pu * i_pu * i_pu)));
97+
// Var(I_Im) ≈ Var(I) * sin^2(pi/4) + Var(θ) * I^2 * cos^2(pi/4)
98+
CHECK(sym_sensor_param.measurement.imag_component.variance ==
99+
doctest::Approx(0.5 * (i_variance_pu + i_angle_variance_pu * i_pu * i_pu)));
100+
CHECK(real(sym_sensor_param.measurement.value()) == doctest::Approx(i_pu * cos(i_angle)));
101+
CHECK(imag(sym_sensor_param.measurement.value()) == doctest::Approx(i_pu * sin(i_angle)));
102+
103+
CHECK(sym_sensor_output.id == 0);
104+
CHECK(sym_sensor_output.energized == 1);
105+
if (angle_measurement_type == AngleMeasurementType::global_angle) {
106+
CHECK(sym_sensor_output.i_residual == doctest::Approx(0.0));
107+
CHECK(sym_sensor_output.i_angle_residual == doctest::Approx(0.0));
108+
} else {
109+
CHECK(sym_sensor_output_local_residuals.i_residual == doctest::Approx(0.0));
110+
CHECK(sym_sensor_output_local_residuals.i_angle_residual == doctest::Approx(0.0));
111+
}
112+
113+
// Check symmetric sensor output for asymmetric parameters
114+
CHECK(asym_sensor_param.measurement.real_component.variance[0] ==
115+
doctest::Approx(0.5 * (i_variance_pu + i_angle_variance_pu * i_pu * i_pu)));
116+
auto const shifted_i_angle = i_angle + deg_240;
117+
CHECK(asym_sensor_param.measurement.imag_component.variance[1] ==
118+
doctest::Approx(i_variance_pu * sin(shifted_i_angle) * sin(shifted_i_angle) +
119+
i_angle_variance_pu * i_pu * i_pu * cos(shifted_i_angle) * cos(shifted_i_angle)));
120+
CHECK(real(asym_sensor_param.measurement.value()[0]) == doctest::Approx(i_pu * cos(i_angle)));
121+
CHECK(imag(asym_sensor_param.measurement.value()[1]) == doctest::Approx(i_pu * sin(shifted_i_angle)));
122+
123+
CHECK(sym_sensor_output_asym_param.id == 0);
124+
CHECK(sym_sensor_output_asym_param.energized == 1);
125+
for (auto phase = 0; phase < 3; ++phase) {
126+
CAPTURE(phase);
127+
if (angle_measurement_type == AngleMeasurementType::global_angle) {
128+
CHECK(sym_sensor_output_asym_param.i_residual[phase] == doctest::Approx(0.0));
129+
CHECK(sym_sensor_output_asym_param.i_angle_residual[phase] == doctest::Approx(0.0));
130+
} else {
131+
CHECK(sym_sensor_output_asym_param_local_residuals.i_residual[phase] == doctest::Approx(0.0));
132+
CHECK(sym_sensor_output_asym_param_local_residuals.i_angle_residual[phase] ==
133+
doctest::Approx(0.0));
134+
}
135+
}
136+
137+
CHECK(sym_current_sensor.get_terminal_type() == terminal_type);
138+
if (angle_measurement_type == AngleMeasurementType::global_angle) {
139+
CHECK(sym_current_sensor.get_angle_measurement_type() == AngleMeasurementType::global_angle);
140+
} else {
141+
CHECK(sym_current_sensor.get_angle_measurement_type() == AngleMeasurementType::local_angle);
142+
}
82143
}
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)
87-
CHECK(sym_sensor_param.measurement.imag_component.variance ==
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)));
91-
92-
CHECK(sym_sensor_output.id == 0);
93-
CHECK(sym_sensor_output.energized == 1);
94-
CHECK(sym_sensor_output.i_residual == doctest::Approx(0.0));
95-
CHECK(sym_sensor_output.i_angle_residual == doctest::Approx(0.0));
96-
97-
// Check symmetric sensor output for asymmetric parameters
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;
101-
CHECK(asym_sensor_param.measurement.imag_component.variance[1] ==
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)));
106-
107-
CHECK(sym_sensor_output_asym_param.id == 0);
108-
CHECK(sym_sensor_output_asym_param.energized == 1);
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-
}
113-
114-
CHECK(sym_current_sensor.get_terminal_type() == terminal_type);
115-
116-
CHECK(sym_current_sensor.get_angle_measurement_type() == AngleMeasurementType::global_angle);
117144
}
118145
SUBCASE("Wrong measured terminal type") {
119146
for (auto const terminal_type :

0 commit comments

Comments
 (0)