Skip to content

Commit af31f1d

Browse files
committed
improve SVC validation rules
Signed-off-by: Samir Romdhani <samir.romdhani_externe@rte-france.com>
1 parent 5046a98 commit af31f1d

File tree

3 files changed

+194
-74
lines changed

3 files changed

+194
-74
lines changed

loadflow/loadflow-validation/src/main/java/com/powsybl/loadflow/validation/StaticVarCompensatorsValidation.java

Lines changed: 9 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -100,9 +100,9 @@ public boolean checkSVCs(StaticVarCompensator svc, ValidationConfig config, Vali
100100
double nominalVcontroller = svc.getTerminal().getVoltageLevel().getNominalV();
101101
double vController = bus != null ? bus.getV() : Double.NaN;
102102
double vControlled = svc.getRegulatingTerminal() != null
103-
? ValidationUtils.getTerminalState(svc.getRegulatingTerminal()).v()
103+
? getTerminalState(svc.getRegulatingTerminal()).v()
104104
: vController;
105-
ValidationUtils.TerminalState terminalState = ValidationUtils.getTerminalState(svc.getTerminal());
105+
TerminalState terminalState = getTerminalState(svc.getTerminal());
106106
boolean connected = terminalState.connected();
107107
boolean mainComponent = terminalState.mainComponent();
108108
return checkSVCs(svc.getId(), p, q, vControlled, vController, nominalVcontroller, reactivePowerSetpoint, voltageSetpoint, regulationMode, regulating, bMin, bMax, connected, mainComponent, config, svcsWriter);
@@ -122,23 +122,6 @@ public boolean checkSVCs(String id, double p, double q, double vControlled, doub
122122
}
123123
}
124124

125-
/**
126-
*
127-
* Rule1: active power (p) (within threshold) should be equal to 0 <br/>
128-
* Rule2: **reactivePowerSetpoint** must be undefined or equal to 0 if NO (**p** or **q**)<br/>
129-
* - TODO (doc states that p should be equal to 0, if so **reactivePowerSetpoint** must be undefined or equal to 0)<br/>
130-
* - Suggestion => check only if (q undefined or equal to 0 then **reactivePowerSetpoint** must be 0)<br/><br/>
131-
* Rule3: **regulationMode = REACTIVE_POWER** then same condition as generator without voltage regulation<br/>
132-
* - Rule3.1: => (config, reactivePowerSetpoint, qMin, qMax) not defined => OK<br/>
133-
* - Rule3.2: => q must match reactivePowerSetpoint (within threshold)<br/><br/>
134-
* Rule4: **regulationMode = VOLTAGE** then same condition as generator with voltage regulation<br/>
135-
* - Rule4.1: => (config, qMin, qMax, vControlled, voltageSetpoint) not defined => OK<br/>
136-
* - Rule4.2: => V is lower than voltageSetpoint (within threshold) AND q must match qMax (within threshold)<br/>
137-
* - Rule4.3: => V is higher than voltageSetpoint (within threshold) AND q must match Qmin (within threshold)<br/>
138-
* - Rule4.4: => V is at the controlled bus (within threshold) AND q is bounded within [Qmin=-bMax*V*V, Qmax=-bMin*V*V]<br/><br/>
139-
* Rule5: if regulating is false then reactive power (q) should be equal to 0<br/>
140-
*
141-
*/
142125
public boolean checkSVCs(String id, double p, double q, double vControlled, double vController, double nominalVcontroller, double reactivePowerSetpoint, double voltageSetpoint,
143126
RegulationMode regulationMode, boolean regulating, double bMin, double bMax, boolean connected, boolean mainComponent,
144127
ValidationConfig config, ValidationWriter svcsWriter) {
@@ -148,11 +131,14 @@ public boolean checkSVCs(String id, double p, double q, double vControlled, doub
148131
boolean validated = true;
149132

150133
if (connected && ValidationUtils.isMainComponent(config, mainComponent)) {
134+
// Rule2: **reactivePowerSetpoint** must be 0 if p or q is missing (NaN)
151135
if (Double.isNaN(p) || Double.isNaN(q)) {
152-
// Rule2: **reactivePowerSetpoint** must be 0 if p or q is missing (NaN)
153-
validated = checkSVCsNaNValues(id, p, q, reactivePowerSetpoint);
136+
// a validation error should be detected if there is a setpoint but no p or q
137+
if (!isUndefinedOrZero(reactivePowerSetpoint, 0.0)) {
138+
LOGGER.warn("{} {}: {}: P={} Q={} reactivePowerSetpoint={}", ValidationType.SVCS, ValidationUtils.VALIDATION_ERROR, id, p, q, reactivePowerSetpoint);
139+
validated = false;
140+
}
154141
} else {
155-
// Rule1, Rule3, Rule4, Rule5
156142
validated = checkSVCsValues(id, p, q, vControlled, vController, nominalVcontroller, reactivePowerSetpoint, voltageSetpoint, regulationMode, regulating, bMin, bMax, config);
157143
}
158144
}
@@ -164,15 +150,6 @@ public boolean checkSVCs(String id, double p, double q, double vControlled, doub
164150
return validated;
165151
}
166152

167-
private static boolean checkSVCsNaNValues(String id, double p, double q, double reactivePowerSetpoint) {
168-
// a validation error should be detected if there is a setpoint but no p or q
169-
if (!isUndefinedOrZero(reactivePowerSetpoint, 0.0)) {
170-
LOGGER.warn("{} {}: {}: P={} Q={} reactivePowerSetpoint={}", ValidationType.SVCS, ValidationUtils.VALIDATION_ERROR, id, p, q, reactivePowerSetpoint);
171-
return false;
172-
}
173-
return true;
174-
}
175-
176153
private static boolean checkSVCsValues(String id, double p, double q, double vControlled, double vController,
177154
double nominalVcontroller, double reactivePowerSetpoint, double voltageSetpoint,
178155
RegulationMode regulationMode, boolean regulating, double bMin, double bMax, ValidationConfig config) {
@@ -206,11 +183,9 @@ private static boolean checkSVCsValues(String id, double p, double q, double vCo
206183
vController, vControlled, voltageSetpoint);
207184
validated = false;
208185
}
209-
210186
// Rule5: if regulating is false then reactive power (q) should be equal to 0
211187
if (notRegulatingKo(regulating, q, config)) {
212-
LOGGER.warn("{} {}: {}: regulator mode={} - Q={} ", ValidationType.SVCS, ValidationUtils.VALIDATION_ERROR,
213-
id, regulationMode, q);
188+
LOGGER.warn("{} {}: {}: regulator mode={} - Q={} ", ValidationType.SVCS, ValidationUtils.VALIDATION_ERROR, id, regulationMode, q);
214189
validated = false;
215190
}
216191
return validated;

loadflow/loadflow-validation/src/main/java/com/powsybl/loadflow/validation/ValidationUtils.java

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -122,13 +122,7 @@ public static double computeShuntExpectedQ(double bPerSection, int sectionCount,
122122
return -bPerSection * sectionCount * v * v;
123123
}
124124

125-
public record QBounds(double qMin, double qMax) { }
126-
127125
public static double voltageFrom(double vBus, double nominalV) {
128126
return (Double.isNaN(vBus) || vBus == 0.0) ? nominalV : vBus;
129127
}
130-
131-
public static boolean isWithinInclusive(double value, double min, double max, double epsilon) {
132-
return boundedWithin(min, max, value, epsilon);
133-
}
134128
}

loadflow/loadflow-validation/src/test/java/com/powsybl/loadflow/validation/StaticVarCompensatorsValidationTest.java

Lines changed: 185 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
*/
88
package com.powsybl.loadflow.validation;
99

10-
import static org.junit.jupiter.api.Assertions.assertFalse;
11-
import static org.junit.jupiter.api.Assertions.assertTrue;
10+
import static org.junit.jupiter.api.Assertions.*;
11+
import static org.mockito.Mockito.mock;
1212
import static org.mockito.Mockito.when;
1313

1414
import java.io.IOException;
@@ -17,7 +17,6 @@
1717
import org.apache.commons.io.output.NullWriter;
1818
import org.junit.jupiter.api.BeforeEach;
1919
import org.junit.jupiter.api.Test;
20-
import org.mockito.Mockito;
2120

2221
import com.powsybl.iidm.network.Bus;
2322
import com.powsybl.iidm.network.Network;
@@ -55,32 +54,32 @@ class StaticVarCompensatorsValidationTest extends AbstractValidationTest {
5554
void setUp() throws IOException {
5655
super.setUp();
5756

58-
Bus svcBus = Mockito.mock(Bus.class);
59-
Mockito.when(svcBus.getV()).thenReturn(v);
60-
Mockito.when(svcBus.isInMainConnectedComponent()).thenReturn(mainComponent);
61-
62-
svcBusView = Mockito.mock(BusView.class);
63-
Mockito.when(svcBusView.getBus()).thenReturn(svcBus);
64-
Mockito.when(svcBusView.getConnectableBus()).thenReturn(svcBus);
65-
66-
VoltageLevel voltageLevel = Mockito.mock(VoltageLevel.class);
67-
Mockito.when(voltageLevel.getNominalV()).thenReturn(nominalV);
68-
69-
svcTerminal = Mockito.mock(Terminal.class);
70-
Mockito.when(svcTerminal.getP()).thenReturn(p);
71-
Mockito.when(svcTerminal.getQ()).thenReturn(q);
72-
Mockito.when(svcTerminal.getBusView()).thenReturn(svcBusView);
73-
Mockito.when(svcTerminal.getVoltageLevel()).thenReturn(voltageLevel);
74-
75-
svc = Mockito.mock(StaticVarCompensator.class);
76-
Mockito.when(svc.getId()).thenReturn("svc");
77-
Mockito.when(svc.getTerminal()).thenReturn(svcTerminal);
78-
Mockito.when(svc.getReactivePowerSetpoint()).thenReturn(reactivePowerSetpoint);
79-
Mockito.when(svc.getVoltageSetpoint()).thenReturn(voltageSetpoint);
80-
Mockito.when(svc.getRegulationMode()).thenReturn(regulationMode);
81-
Mockito.when(svc.isRegulating()).thenReturn(regulating);
82-
Mockito.when(svc.getBmin()).thenReturn(bMin);
83-
Mockito.when(svc.getBmax()).thenReturn(bMax);
57+
Bus svcBus = mock(Bus.class);
58+
when(svcBus.getV()).thenReturn(v);
59+
when(svcBus.isInMainConnectedComponent()).thenReturn(mainComponent);
60+
61+
svcBusView = mock(BusView.class);
62+
when(svcBusView.getBus()).thenReturn(svcBus);
63+
when(svcBusView.getConnectableBus()).thenReturn(svcBus);
64+
65+
VoltageLevel voltageLevel = mock(VoltageLevel.class);
66+
when(voltageLevel.getNominalV()).thenReturn(nominalV);
67+
68+
svcTerminal = mock(Terminal.class);
69+
when(svcTerminal.getP()).thenReturn(p);
70+
when(svcTerminal.getQ()).thenReturn(q);
71+
when(svcTerminal.getBusView()).thenReturn(svcBusView);
72+
when(svcTerminal.getVoltageLevel()).thenReturn(voltageLevel);
73+
74+
svc = mock(StaticVarCompensator.class);
75+
when(svc.getId()).thenReturn("svc");
76+
when(svc.getTerminal()).thenReturn(svcTerminal);
77+
when(svc.getReactivePowerSetpoint()).thenReturn(reactivePowerSetpoint);
78+
when(svc.getVoltageSetpoint()).thenReturn(voltageSetpoint);
79+
when(svc.getRegulationMode()).thenReturn(regulationMode);
80+
when(svc.isRegulating()).thenReturn(regulating);
81+
when(svc.getBmin()).thenReturn(bMin);
82+
when(svc.getBmax()).thenReturn(bMax);
8483
}
8584

8685
@Test
@@ -169,19 +168,19 @@ void checkSvcsValues() {
169168
void checkSvcs() {
170169
// active power should be equal to 0
171170
assertTrue(StaticVarCompensatorsValidation.INSTANCE.checkSVCs(svc, strictConfig, NullWriter.INSTANCE));
172-
Mockito.when(svcTerminal.getP()).thenReturn(-39.8);
171+
when(svcTerminal.getP()).thenReturn(-39.8);
173172
assertFalse(StaticVarCompensatorsValidation.INSTANCE.checkSVCs(svc, strictConfig, NullWriter.INSTANCE));
174173

175174
// the unit is disconnected
176-
Mockito.when(svcBusView.getBus()).thenReturn(null);
175+
when(svcBusView.getBus()).thenReturn(null);
177176
assertTrue(StaticVarCompensatorsValidation.INSTANCE.checkSVCs(svc, strictConfig, NullWriter.INSTANCE));
178177
}
179178

180179
@Test
181180
void checkNetworkSvcs() throws IOException {
182-
Network network = Mockito.mock(Network.class);
183-
Mockito.when(network.getId()).thenReturn("network");
184-
Mockito.when(network.getStaticVarCompensatorStream()).thenAnswer(dummy -> Stream.of(svc));
181+
Network network = mock(Network.class);
182+
when(network.getId()).thenReturn("network");
183+
when(network.getStaticVarCompensatorStream()).thenAnswer(dummy -> Stream.of(svc));
185184

186185
assertTrue(StaticVarCompensatorsValidation.INSTANCE.checkSVCs(network, looseConfig, data));
187186

@@ -218,4 +217,156 @@ void checkSVCReactivePowerSetpointWhenPOrQMissing() {
218217
assertTrue(StaticVarCompensatorsValidation.INSTANCE.checkSVCs(svc, strictConfig, NullWriter.INSTANCE));
219218
}
220219

220+
// Rule 3: regulationMode = REACTIVE_POWER
221+
// Test condition: required inputs missing => OK only if okMissingValues=true
222+
@Test
223+
void checkSVCReactivePowerModeMissingInputs() {
224+
// Given regulation enabled, and regulationMode is REACTIVE_POWER
225+
when(svc.getRegulationMode()).thenReturn(RegulationMode.REACTIVE_POWER);
226+
when(svc.isRegulating()).thenReturn(true);
227+
// Given Rule1, Rule2 (OK)
228+
when(svcTerminal.getP()).thenReturn(0.0);
229+
when(svc.getReactivePowerSetpoint()).thenReturn(Double.NaN);
230+
// When
231+
strictConfig.setOkMissingValues(false);
232+
// Then
233+
assertFalse(StaticVarCompensatorsValidation.INSTANCE.checkSVCs(svc, strictConfig, NullWriter.INSTANCE));
234+
// When
235+
strictConfig.setOkMissingValues(true);
236+
// Then
237+
assertTrue(StaticVarCompensatorsValidation.INSTANCE.checkSVCs(svc, strictConfig, NullWriter.INSTANCE));
238+
}
239+
240+
// Rule 3: regulationMode = REACTIVE_POWER
241+
// Test condition: Q must match reactivePowerSetpoint within threshold
242+
@Test
243+
void checkSVCReactivePowerModeQMustMatchSetpoint() {
244+
// Given regulation enabled, and regulationMode is REACTIVE_POWER
245+
when(svc.getRegulationMode()).thenReturn(RegulationMode.REACTIVE_POWER);
246+
when(svc.isRegulating()).thenReturn(true);
247+
248+
when(svcTerminal.getP()).thenReturn(0.0);
249+
when(svc.getReactivePowerSetpoint()).thenReturn(3.0);
250+
// When
251+
when(svcTerminal.getQ()).thenReturn(3.01);
252+
// Then
253+
assertTrue(StaticVarCompensatorsValidation.INSTANCE.checkSVCs(svc, strictConfig, NullWriter.INSTANCE));
254+
// When
255+
when(svcTerminal.getQ()).thenReturn(3.5); // diff > (threshold = 0.01)
256+
// Then
257+
assertFalse(StaticVarCompensatorsValidation.INSTANCE.checkSVCs(svc, strictConfig, NullWriter.INSTANCE));
258+
}
259+
260+
// Rule 4: regulationMode = VOLTAGE
261+
// Test condition: required inputs missing => OK only if okMissingValues=true
262+
@Test
263+
void checkSVCVoltageModeMissingInputs() {
264+
// Given regulation enabled, and regulationMode is VOLTAGE
265+
when(svc.getRegulationMode()).thenReturn(RegulationMode.VOLTAGE);
266+
when(svc.isRegulating()).thenReturn(true);
267+
// Given Rule1, Rule 2 (OK)
268+
when(svcTerminal.getQ()).thenReturn(0.0);
269+
when(svc.getVoltageSetpoint()).thenReturn(Double.NaN);
270+
// When
271+
strictConfig.setOkMissingValues(false);
272+
// Then
273+
assertFalse(StaticVarCompensatorsValidation.INSTANCE.checkSVCs(svc, strictConfig, NullWriter.INSTANCE));
274+
// When
275+
strictConfig.setOkMissingValues(true);
276+
// Then
277+
assertTrue(StaticVarCompensatorsValidation.INSTANCE.checkSVCs(svc, strictConfig, NullWriter.INSTANCE));
278+
}
279+
280+
// Rule 4: regulationMode = VOLTAGE
281+
// Test condition: V controlled < V setPoint, then Q must match maxQ.
282+
@Test
283+
void checkSVCVoltageModeVLowerThanSetpoint() {
284+
// Given regulation enabled, and regulationMode is VOLTAGE
285+
when(svc.getRegulationMode()).thenReturn(RegulationMode.VOLTAGE);
286+
when(svc.isRegulating()).thenReturn(true);
287+
when(svcTerminal.getP()).thenReturn(0.0);
288+
// Given V controlled < V setpoint
289+
Terminal regulatingTerminal = regulatingTerminalWithVoltage(360.0); // V controlled = 360.0
290+
when(svc.getRegulatingTerminal()).thenReturn(regulatingTerminal);
291+
when(svc.getVoltageSetpoint()).thenReturn(380.0); // V setpoint 380.0
292+
double expectedQmax = -bMin * v * v;
293+
assertEquals(1444.0, expectedQmax);
294+
// Given q matching Qmax
295+
when(svcTerminal.getQ()).thenReturn(expectedQmax);
296+
assertTrue(StaticVarCompensatorsValidation.INSTANCE.checkSVCs(svc, strictConfig, NullWriter.INSTANCE));
297+
// Given q not matching Qmax
298+
when(svcTerminal.getQ()).thenReturn(1300.0);
299+
assertFalse(StaticVarCompensatorsValidation.INSTANCE.checkSVCs(svc, strictConfig, NullWriter.INSTANCE));
300+
}
301+
302+
303+
// Rule 4: regulationMode = VOLTAGE
304+
// Test condition: V regulation > V setPoint, then Q must match minQ.
305+
@Test
306+
void checkSVCVoltageModeVHigherThanSetpoint() {
307+
// Given regulation enabled, and regulationMode is VOLTAGE
308+
when(svc.getRegulationMode()).thenReturn(RegulationMode.VOLTAGE);
309+
when(svc.isRegulating()).thenReturn(true);
310+
when(svcTerminal.getP()).thenReturn(0.0);
311+
// Given V controlled > V setpoint
312+
Terminal regulatingTerminal = regulatingTerminalWithVoltage(400.0); // V controlled = 400.0
313+
when(svc.getRegulatingTerminal()).thenReturn(regulatingTerminal);
314+
when(svc.getVoltageSetpoint()).thenReturn(380.0); // V setpoint 380.0
315+
double expectedQmin = -bMax * v * v;
316+
assertEquals(-14440.0, expectedQmin);
317+
// Given q matching Qmin
318+
when(svcTerminal.getQ()).thenReturn(expectedQmin);
319+
assertTrue(StaticVarCompensatorsValidation.INSTANCE.checkSVCs(svc, strictConfig, NullWriter.INSTANCE));
320+
// Given q not matching Qmin
321+
when(svcTerminal.getQ()).thenReturn(-13000.0);
322+
assertFalse(StaticVarCompensatorsValidation.INSTANCE.checkSVCs(svc, strictConfig, NullWriter.INSTANCE));
323+
}
324+
325+
// Rule 4: regulationMode = VOLTAGE
326+
// Test condition: V regulation ~ V setPoint, then Q must be within [minQ, maxQ].
327+
@Test
328+
void checkSVCVoltageModeVAtSetpoint() {
329+
// Given regulation enabled, and regulationMode is VOLTAGE
330+
when(svc.getRegulationMode()).thenReturn(RegulationMode.VOLTAGE);
331+
when(svc.isRegulating()).thenReturn(true);
332+
when(svcTerminal.getP()).thenReturn(0.0);
333+
// Given V controlled ~ V setpoint
334+
Terminal regulatingTerminal = regulatingTerminalWithVoltage(380.0); // V controlled = 380.0.0
335+
when(svc.getRegulatingTerminal()).thenReturn(regulatingTerminal);
336+
when(svc.getVoltageSetpoint()).thenReturn(380.0); // V setpoint 380.0
337+
double expectedQmax = -bMin * v * v;
338+
assertEquals(1444.0, expectedQmax);
339+
double expectedQmin = -bMax * v * v;
340+
assertEquals(-14440.0, expectedQmin);
341+
// Given q inside bounds [-14440.0, 1444.0]
342+
when(svcTerminal.getQ()).thenReturn(0.0); // inside bounds [-14440.0, 1444.0]
343+
assertTrue(StaticVarCompensatorsValidation.INSTANCE.checkSVCs(svc, strictConfig, NullWriter.INSTANCE));
344+
// Given q outside bounds [-14440.0, 1444.0]
345+
when(svcTerminal.getQ()).thenReturn(2000.0);
346+
assertFalse(StaticVarCompensatorsValidation.INSTANCE.checkSVCs(svc, strictConfig, NullWriter.INSTANCE));
347+
}
348+
349+
// Rule 5: if regulating is false then reactive power (Q) should be equal to 0 (within threshold)
350+
@Test
351+
void checkSVCWhenNoRegulatingQShouldBeZero() {
352+
when(svc.getRegulationMode()).thenReturn(RegulationMode.VOLTAGE);
353+
when(svc.isRegulating()).thenReturn(false);
354+
when(svcTerminal.getP()).thenReturn(0.0);
355+
when(svcTerminal.getQ()).thenReturn(0.009); // ~ threshold => invalid result
356+
assertTrue(StaticVarCompensatorsValidation.INSTANCE.checkSVCs(svc, strictConfig, NullWriter.INSTANCE));
357+
when(svcTerminal.getQ()).thenReturn(0.02); // > threshold => invalid result
358+
assertFalse(StaticVarCompensatorsValidation.INSTANCE.checkSVCs(svc, strictConfig, NullWriter.INSTANCE));
359+
}
360+
361+
private Terminal regulatingTerminalWithVoltage(double vControlled) {
362+
Bus controlledBus = mock(Bus.class);
363+
when(controlledBus.getV()).thenReturn(vControlled);
364+
365+
BusView controlledBusView = mock(BusView.class);
366+
when(controlledBusView.getBus()).thenReturn(controlledBus);
367+
368+
Terminal regulatingTerminal = mock(Terminal.class);
369+
when(regulatingTerminal.getBusView()).thenReturn(controlledBusView);
370+
return regulatingTerminal;
371+
}
221372
}

0 commit comments

Comments
 (0)