@@ -25,63 +25,61 @@ void check_nan_preserving_equality(RealValue<asymmetric_t> const& actual, RealVa
25
25
check_nan_preserving_equality (actual (i), expected (i));
26
26
}
27
27
}
28
+ using TerminalAndAngleTypePair = std::pair<MeasuredTerminalType, AngleMeasurementType>;
29
+
30
+ auto const terminal_and_angle_measurement_types = [] {
31
+ std::vector<TerminalAndAngleTypePair> result;
32
+ for (auto const terminal_type :
33
+ {MeasuredTerminalType::branch_from, MeasuredTerminalType::branch_to, MeasuredTerminalType::branch3_1,
34
+ MeasuredTerminalType::branch3_2, MeasuredTerminalType::branch3_3}) {
35
+ for (auto const angle_measurement_type :
36
+ {AngleMeasurementType::global_angle, AngleMeasurementType::local_angle}) {
37
+ result.emplace_back (terminal_type, angle_measurement_type);
38
+ }
39
+ }
40
+ return result;
41
+ }();
28
42
} // namespace
29
43
30
44
TEST_CASE (" Test current sensor" ) {
31
45
SUBCASE (" Symmetric Current Sensor" ) {
32
- for (auto const terminal_type :
33
- {MeasuredTerminalType::branch_from, MeasuredTerminalType::branch_to, MeasuredTerminalType::branch3_1,
34
- MeasuredTerminalType::branch3_2, MeasuredTerminalType::branch3_3}) {
46
+ for (auto const [terminal_type, angle_measurement_type] : terminal_and_angle_measurement_types) {
35
47
CAPTURE (terminal_type);
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;
48
+ CAPTURE (angle_measurement_type);
49
+
50
+ CurrentSensorInput<symmetric_t > sym_current_sensor_input{};
51
+ sym_current_sensor_input.id = 0 ;
52
+ sym_current_sensor_input.measured_object = 1 ;
53
+ sym_current_sensor_input.measured_terminal_type = terminal_type;
54
+ sym_current_sensor_input.angle_measurement_type = angle_measurement_type;
55
+ sym_current_sensor_input.i_sigma = 1.0 ;
56
+ sym_current_sensor_input.i_measured = 1.0 * 1e3 ;
57
+ sym_current_sensor_input.i_angle_measured = pi / 4 .;
58
+ sym_current_sensor_input.i_angle_sigma = 0.2 ;
58
59
60
+ double const u_rated = 10.0e3 ;
61
+ double const base_current = base_power_3p / u_rated / sqrt3;
62
+ double const i_pu = 1.0e3 / base_current;
63
+ double const i_sigma_pu = 1.0 / base_current;
64
+ double const i_variance_pu = i_sigma_pu * i_sigma_pu;
65
+ double const i_angle = pi / 4 .;
66
+ double const i_angle_sigma_pi = 0.2 ;
67
+ double const i_angle_variance_pu = i_angle_sigma_pi * i_angle_sigma_pi;
68
+
69
+ CurrentSensor<symmetric_t > const sym_current_sensor{sym_current_sensor_input, u_rated};
70
+ CHECK (sym_current_sensor.get_terminal_type () == terminal_type);
71
+ CHECK (sym_current_sensor.get_angle_measurement_type () == angle_measurement_type);
72
+
73
+ SUBCASE (" Output for symmetric parameters" ) {
59
74
auto const i_sym = ComplexValue<symmetric_t >{(1e3 * exp (1 .0i * i_angle)) / base_current};
60
- auto const i_asym = ComplexValue<asymmetric_t >{(1e3 * exp (1 .0i * i_angle)) / base_current,
61
- (1e3 * exp (1 .0i * (i_angle + deg_240))) / base_current,
62
- (1e3 * exp (1 .0i * (i_angle + deg_120))) / base_current};
63
- auto const i_asym_local = ComplexValue<asymmetric_t >{(1e3 * exp (1 .0i * i_angle)) / base_current,
64
- (1e3 * exp (1 .0i * i_angle)) / base_current,
65
- (1e3 * exp (1 .0i * i_angle)) / base_current};
66
-
67
- CurrentSensor<symmetric_t > const sym_current_sensor{sym_current_sensor_input, u_rated};
68
-
69
75
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
76
CurrentSensorOutput<symmetric_t > const sym_sensor_output =
73
77
(angle_measurement_type == AngleMeasurementType::global_angle)
74
78
? sym_current_sensor.get_output <symmetric_t >(i_sym, ComplexValue<symmetric_t >{1.0 })
75
79
: sym_current_sensor.get_output <symmetric_t >(conj (i_sym), ComplexValue<symmetric_t >{1.0 });
76
- CurrentSensorOutput<asymmetric_t > const sym_sensor_output_asym_param =
77
- (angle_measurement_type == AngleMeasurementType::global_angle)
78
- ? sym_current_sensor.get_output <asymmetric_t >(i_asym, ComplexValue<asymmetric_t >{1.0 })
79
- : sym_current_sensor.get_output <asymmetric_t >(conj (i_asym_local),
80
- ComplexValue<asymmetric_t >{1.0 });
81
80
82
81
// Check symmetric sensor output for symmetric parameters
83
82
CHECK (sym_sensor_param.angle_measurement_type == angle_measurement_type);
84
-
85
83
// Var(I_Re) ≈ Var(I) * cos^2(pi/4) + Var(θ) * I^2 * sin^2(pi/4)
86
84
CHECK (sym_sensor_param.measurement .real_component .variance ==
87
85
doctest::Approx (0.5 * (i_variance_pu + i_angle_variance_pu * i_pu * i_pu)));
@@ -95,8 +93,25 @@ TEST_CASE("Test current sensor") {
95
93
CHECK (sym_sensor_output.energized == 1 );
96
94
CHECK (sym_sensor_output.i_residual == doctest::Approx (0.0 ));
97
95
CHECK (sym_sensor_output.i_angle_residual == doctest::Approx (0.0 ));
96
+ }
97
+
98
+ SUBCASE (" Output for asymmetric parameters" ) {
99
+ auto const i_asym = ComplexValue<asymmetric_t >{(1e3 * exp (1 .0i * i_angle)) / base_current,
100
+ (1e3 * exp (1 .0i * (i_angle + deg_240))) / base_current,
101
+ (1e3 * exp (1 .0i * (i_angle + deg_120))) / base_current};
102
+ auto const i_asym_local = ComplexValue<asymmetric_t >{(1e3 * exp (1 .0i * i_angle)) / base_current,
103
+ (1e3 * exp (1 .0i * i_angle)) / base_current,
104
+ (1e3 * exp (1 .0i * i_angle)) / base_current};
105
+ CurrentSensorCalcParam<asymmetric_t > asym_sensor_param = sym_current_sensor.calc_param <asymmetric_t >();
106
+ CurrentSensorOutput<asymmetric_t > const sym_sensor_output_asym_param =
107
+ (angle_measurement_type == AngleMeasurementType::global_angle)
108
+ ? sym_current_sensor.get_output <asymmetric_t >(i_asym, ComplexValue<asymmetric_t >{1.0 })
109
+ : sym_current_sensor.get_output <asymmetric_t >(conj (i_asym_local),
110
+ ComplexValue<asymmetric_t >{1.0 });
98
111
99
112
// Check symmetric sensor output for asymmetric parameters
113
+ CHECK (asym_sensor_param.angle_measurement_type == angle_measurement_type);
114
+
100
115
CHECK (asym_sensor_param.measurement .real_component .variance [0 ] ==
101
116
doctest::Approx (0.5 * (i_variance_pu + i_angle_variance_pu * i_pu * i_pu)));
102
117
auto const shifted_i_angle = i_angle + deg_240;
@@ -113,8 +128,6 @@ TEST_CASE("Test current sensor") {
113
128
CHECK (sym_sensor_output_asym_param.i_residual [phase] == doctest::Approx (0.0 ));
114
129
CHECK (sym_sensor_output_asym_param.i_angle_residual [phase] == doctest::Approx (0.0 ));
115
130
}
116
- CHECK (sym_current_sensor.get_terminal_type () == terminal_type);
117
- CHECK (sym_current_sensor.get_angle_measurement_type () == angle_measurement_type);
118
131
}
119
132
}
120
133
SUBCASE (" Wrong measured terminal type" ) {
@@ -135,71 +148,52 @@ TEST_CASE("Test current sensor") {
135
148
SUBCASE (" Symmetric calculation parameters" ) {
136
149
double const u_rated = 10.0e3 ;
137
150
double const base_current = base_power_3p / u_rated / sqrt3;
138
- for (auto const terminal_type :
139
- {MeasuredTerminalType::branch_from, MeasuredTerminalType::branch_to, MeasuredTerminalType::branch3_1,
140
- MeasuredTerminalType::branch3_2, MeasuredTerminalType::branch3_3}) {
141
- for (auto const angle_measurement_type :
142
- {AngleMeasurementType::global_angle, AngleMeasurementType::local_angle}) {
143
- CurrentSensor<symmetric_t > sym_current_sensor{{.id = 1 ,
144
- .measured_object = 1 ,
145
- .measured_terminal_type = terminal_type,
146
- .angle_measurement_type = angle_measurement_type},
147
- u_rated};
148
-
149
- SUBCASE (" No phase shift" ) {
150
- sym_current_sensor.update ({.id = 1 ,
151
- .i_sigma = 1.0 ,
152
- .i_angle_sigma = 0.2 ,
153
- .i_measured = 1.0 ,
154
- .i_angle_measured = 0.0 });
155
- auto const sym_param = sym_current_sensor.calc_param <symmetric_t >();
156
-
157
- CHECK (sym_param.angle_measurement_type == angle_measurement_type);
158
- CHECK (sym_param.measurement .real_component .variance ==
159
- doctest::Approx (pow (1.0 / base_current, 2 )));
160
- CHECK (sym_param.measurement .imag_component .variance ==
161
- doctest::Approx (pow (0.2 / base_current, 2 )));
162
- CHECK (real (sym_param.measurement .value ()) == doctest::Approx (1.0 / base_current));
163
- CHECK (imag (sym_param.measurement .value ()) == doctest::Approx (0.0 / base_current));
164
- }
165
-
166
- SUBCASE (" 90deg phase shift" ) {
167
- sym_current_sensor.update ({.id = 1 ,
168
- .i_sigma = 1.0 ,
169
- .i_angle_sigma = 0.2 ,
170
- .i_measured = 1.0 ,
171
- .i_angle_measured = pi / 2 });
172
- auto const sym_param = sym_current_sensor.calc_param <symmetric_t >();
173
-
174
- CHECK (sym_param.angle_measurement_type == angle_measurement_type);
175
- CHECK (sym_param.measurement .real_component .variance ==
176
- doctest::Approx (pow (0.2 / base_current, 2 )));
177
- CHECK (sym_param.measurement .imag_component .variance ==
178
- doctest::Approx (pow (1.0 / base_current, 2 )));
179
- CHECK (real (sym_param.measurement .value ()) == doctest::Approx (0.0 / base_current));
180
- CHECK (imag (sym_param.measurement .value ()) == doctest::Approx (1.0 / base_current));
181
- }
182
-
183
- SUBCASE (" 45deg phase shift" ) {
184
- using std::numbers::sqrt2;
185
- constexpr auto inv_sqrt2 = sqrt2 / 2 ;
186
-
187
- sym_current_sensor.update ({.id = 1 ,
188
- .i_sigma = 1.0 ,
189
- .i_angle_sigma = 0.2 ,
190
- .i_measured = 1.0 ,
191
- .i_angle_measured = pi / 4 });
192
- auto const sym_param = sym_current_sensor.calc_param <symmetric_t >();
193
-
194
- CHECK (sym_param.angle_measurement_type == angle_measurement_type);
195
- CHECK (sym_param.measurement .real_component .variance ==
196
- doctest::Approx (1.04 / 2.0 / (base_current * base_current)));
197
- CHECK (sym_param.measurement .imag_component .variance ==
198
- doctest::Approx (sym_param.measurement .real_component .variance ));
199
- CHECK (real (sym_param.measurement .value ()) == doctest::Approx (inv_sqrt2 / base_current));
200
- CHECK (imag (sym_param.measurement .value ()) ==
201
- doctest::Approx (real (sym_param.measurement .value ())));
202
- }
151
+ for (auto const [terminal_type, angle_measurement_type] : terminal_and_angle_measurement_types) {
152
+ CurrentSensor<symmetric_t > sym_current_sensor{{.id = 1 ,
153
+ .measured_object = 1 ,
154
+ .measured_terminal_type = terminal_type,
155
+ .angle_measurement_type = angle_measurement_type},
156
+ u_rated};
157
+
158
+ SUBCASE (" No phase shift" ) {
159
+ sym_current_sensor.update (
160
+ {.id = 1 , .i_sigma = 1.0 , .i_angle_sigma = 0.2 , .i_measured = 1.0 , .i_angle_measured = 0.0 });
161
+ auto const sym_param = sym_current_sensor.calc_param <symmetric_t >();
162
+
163
+ CHECK (sym_param.angle_measurement_type == angle_measurement_type);
164
+ CHECK (sym_param.measurement .real_component .variance == doctest::Approx (pow (1.0 / base_current, 2 )));
165
+ CHECK (sym_param.measurement .imag_component .variance == doctest::Approx (pow (0.2 / base_current, 2 )));
166
+ CHECK (real (sym_param.measurement .value ()) == doctest::Approx (1.0 / base_current));
167
+ CHECK (imag (sym_param.measurement .value ()) == doctest::Approx (0.0 / base_current));
168
+ }
169
+
170
+ SUBCASE (" 90deg phase shift" ) {
171
+ sym_current_sensor.update (
172
+ {.id = 1 , .i_sigma = 1.0 , .i_angle_sigma = 0.2 , .i_measured = 1.0 , .i_angle_measured = pi / 2 });
173
+ auto const sym_param = sym_current_sensor.calc_param <symmetric_t >();
174
+
175
+ CHECK (sym_param.angle_measurement_type == angle_measurement_type);
176
+ CHECK (sym_param.measurement .real_component .variance == doctest::Approx (pow (0.2 / base_current, 2 )));
177
+ CHECK (sym_param.measurement .imag_component .variance == doctest::Approx (pow (1.0 / base_current, 2 )));
178
+ CHECK (real (sym_param.measurement .value ()) == doctest::Approx (0.0 / base_current));
179
+ CHECK (imag (sym_param.measurement .value ()) == doctest::Approx (1.0 / base_current));
180
+ }
181
+
182
+ SUBCASE (" 45deg phase shift" ) {
183
+ using std::numbers::sqrt2;
184
+ constexpr auto inv_sqrt2 = sqrt2 / 2 ;
185
+
186
+ sym_current_sensor.update (
187
+ {.id = 1 , .i_sigma = 1.0 , .i_angle_sigma = 0.2 , .i_measured = 1.0 , .i_angle_measured = pi / 4 });
188
+ auto const sym_param = sym_current_sensor.calc_param <symmetric_t >();
189
+
190
+ CHECK (sym_param.angle_measurement_type == angle_measurement_type);
191
+ CHECK (sym_param.measurement .real_component .variance ==
192
+ doctest::Approx (1.04 / 2.0 / (base_current * base_current)));
193
+ CHECK (sym_param.measurement .imag_component .variance ==
194
+ doctest::Approx (sym_param.measurement .real_component .variance ));
195
+ CHECK (real (sym_param.measurement .value ()) == doctest::Approx (inv_sqrt2 / base_current));
196
+ CHECK (imag (sym_param.measurement .value ()) == doctest::Approx (real (sym_param.measurement .value ())));
203
197
}
204
198
}
205
199
}
@@ -219,10 +213,10 @@ TEST_CASE("Test current sensor") {
219
213
.i_measured = i_measured,
220
214
.i_angle_measured = i_angle_measured},
221
215
u_rated};
222
-
223
216
CurrentSensorUpdate<symmetric_t > cs_update{
224
217
.id = 1 , .i_sigma = nan, .i_angle_sigma = nan, .i_measured = nan, .i_angle_measured = nan};
225
218
auto expected = cs_update;
219
+ CAPTURE (expected.id );
226
220
227
221
SUBCASE (" Identical" ) {
228
222
// default values
0 commit comments