Skip to content

Commit 68632e2

Browse files
committed
improve Generator validation rules
Signed-off-by: Samir Romdhani <samir.romdhani_externe@rte-france.com>
1 parent 4667241 commit 68632e2

File tree

6 files changed

+172
-73
lines changed

6 files changed

+172
-73
lines changed

docs/user/itools/loadflow-validation.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -211,10 +211,11 @@ $$
211211
\end{align*}
212212
$$
213213

214-
In the PowSyBl validation, there are a few tricks to handle special cases:
214+
In the PowSyBl validation, there are a few tricks to handle special cases before applying the nominal active/reactive/voltage rules
215+
- if `P` or `Q` is missing, validation fails if setpoints are defined and non-zero
215216
- if $minQ > maxQ$, then the values are switched to recover a meaningful interval if `noRequirementIfReactiveBoundInversion = false`
216217
- in case of a missing value, the corresponding test is OK
217-
- $minQ$ and $maxQ$ are function of $P$. If $targetP$ is outside $[minP, maxP]$, no test is done.
218+
- $minQ$ and $maxQ$ are function of $P$. If $targetP$ is outside $[minP, maxP]$, and `noRequirementIfSetpointOutsidePowerBounds = true`, generator validation checks are bypassed.
218219

219220
### Loads
220221
<span style="color: red">To be implemented, with tests similar to generators with voltage regulation.</span>

loadflow/loadflow-validation/notes/note.md

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -99,8 +99,7 @@
9999
| Condition4 | `same checks as a generator with voltage regulation with the following bounds: Qmin, Qmax` | `voltageRegulationModeKo` | Rule4.1, Rule4.2, Rule4.3, Rule4.4 | - |
100100
| Condition5 | - | `notRegulatingKo` | if regulating is false then reactive power should be equal to 0 | - `add this rule in the doc` |
101101

102-
103-
- Used util methods inValidationUtils
102+
- Used util methods in ValidationUtils
104103
- `isUndefinedOrZero`
105104
- `isOutsideTolerance`
106105
### Generator validation
@@ -109,16 +108,30 @@
109108
- core grid model: https://powsybl.readthedocs.io/projects/powsybl-core/en/stable/grid_model/network_subnetwork.html#generator
110109
- core tool loadflow-validation: https://powsybl.readthedocs.io/projects/powsybl-core/en/stable/user/itools/loadflow-validation.html#generators
111110
##### Notes (draft)
112-
- [ ] Rule1: when maxQ < minQ if noRequirementIfReactiveBoundInversion (parameter) return true (TODO)
113-
- [ ] Rule2: when targetP < minP or targetP > maxP if noRequirementIfSetpointOutsidePowerBounds (parameter) return true (TODO)
114-
- [ ] Rule3: p or q should be defined if voltage and a target (targetP and targetQ) defined => voltage not mentioned in the condition in code (TODO)
115-
- [x] Rule4: Active power (p) must match setpoint (expectedP) (within threshold)
116-
- [x] Rule5: if voltageRegulatorOn="false" then reactive power (Q) should match to setpoint (targetQ) (within threshold)
117-
- [x] Rule6: if voltageRegulatorOn="true"
118-
* Rule6.1: (minQ/maxQ/targetV) are not defined => OK
119-
* Rule6.2: If V > targetV + threshold, generator (Qgen) must be at min reactive limit
120-
* Rule6.3: If V < targetV - threshold, generator (Qgen) must be at max reactive limit
121-
* Rule6.4: If |V-targetV| <= threshold, generator (Qgen) must be within [minQ, maxQ]
111+
112+
> [!NOTE]
113+
> Rule1 : If `P` or `Q` is missing, validation fails if setpoints are defined and non-zero
114+
115+
> [!NOTE]
116+
> Rule2 : If reactive limits are inverted (`maxQ < minQ`) and noRequirementIfReactiveBoundInversion = true, generator validation are bypassed.
117+
118+
> [!NOTE]
119+
> Rule3 : active setpoint outside bounds bypass
120+
> If `targetP` is outside `[minP, maxP]` (with tolerance) and noRequirementIfSetpointOutsidePowerBounds = true, generator validation are bypassed
121+
122+
> [!NOTE]
123+
> Rule4: Active power p matches expected setpoint
124+
> Active power p must match setpoint (expectedP) (within threshold)
125+
126+
> [!NOTE]
127+
> Rule5: If voltage regulator is disabled, Q matches targetQ
128+
> Reactive power q should match to setpoint (targetQ) (within threshold) when voltageRegulatorOn = false
129+
130+
> [!NOTE]
131+
> Rule6: If voltage regulator ON, Reactive power q follow V/targetV logic
132+
> - qGen at minQ if V > targetV + threshold
133+
> - qGen at maxQ if V < targetV - threshold
134+
> - else qGen within [minQ, maxQ])
122135
123136
##### Actions TODO
124137

@@ -131,7 +144,10 @@
131144
| Condition5 | \|targetQ - Q\| < ε | `voltageRegulatorOn="false"` | - The reactive power (Q) should match to setpoint (targetQ) (within threshold) | |
132145
| Condition6 | .. | `voltageRegulatorOn="true" ` | - Rule6.1, Rule6.2, Rule6.3, Rule6.4 | |
133146

134-
147+
- Used util methods in ValidationUtils
148+
- `isActivePowerKo`
149+
- `isReactivePowerKo`
150+
- `isVoltageRegulationKo`
135151

136152
### Buses validation
137153

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

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -61,19 +61,15 @@ public boolean checkFlows(BranchData branch, ValidationConfig config, Validation
6161
boolean validated = true;
6262

6363
if (!branch.isConnected1()) {
64-
//Rule1: checks disconnected terminal
6564
validated &= checkDisconnectedTerminal(branch.getId(), "1", branch.getP1(), branch.getComputedP1(), branch.getQ1(), branch.getComputedQ1(), config);
6665
}
6766
if (!branch.isConnected2()) {
68-
//Rule1: checks disconnected terminal
6967
validated &= checkDisconnectedTerminal(branch.getId(), "2", branch.getP2(), branch.getComputedP2(), branch.getQ2(), branch.getComputedQ2(), config);
7068
}
7169
if (branch.isConnected1() && ValidationUtils.isMainComponent(config, branch.isMainComponent1())) {
72-
//Rule2: checks connected terminal
7370
validated &= checkConnectedTerminal(branch.getId(), "1", branch.getP1(), branch.getComputedP1(), branch.getQ1(), branch.getComputedQ1(), config);
7471
}
7572
if (branch.isConnected2() && ValidationUtils.isMainComponent(config, branch.isMainComponent2())) {
76-
//Rule2: checks connected terminal
7773
validated &= checkConnectedTerminal(branch.getId(), "2", branch.getP2(), branch.getComputedP2(), branch.getQ2(), branch.getComputedQ2(), config);
7874
}
7975
try {

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

Lines changed: 23 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
import com.powsybl.iidm.network.Network;
2424
import com.powsybl.loadflow.validation.io.ValidationWriter;
2525

26+
import static com.powsybl.loadflow.validation.ValidationUtils.*;
27+
2628
/**
2729
*
2830
* @author Massimo Ferraro {@literal <massimo.ferraro@techrain.eu>}
@@ -129,15 +131,14 @@ public boolean checkGenerators(String id, double p, double q, double v, double t
129131

130132
double expectedP = getExpectedP(guesser, id, p, targetP, minP, maxP, config.getThreshold());
131133
if (connected && ValidationUtils.isMainComponent(config, mainComponent)) {
132-
if (Double.isNaN(p) || Double.isNaN(q)) {
133-
validated = checkGeneratorsNaNValues(id, p, q, targetP, targetQ); //Rule 1:
134-
} else if (checkReactiveBoundInversion(minQ, maxQ, config)) { //Rule 2: when maxQ < minQ if noRequirementIfReactiveBoundInversion return true
134+
if (areNaN(p, q)) {
135+
validated = validateMissingPQRule(id, p, q, targetP, targetQ);
136+
} else if (isReactiveBoundInverted(minQ, maxQ, config.getThreshold(), config.isNoRequirementIfReactiveBoundInversion())) {
135137
validated = true;
136-
} else if (checkSetpointOutsidePowerBounds(targetP, minP, maxP, config)) { //Rule 3: when targetP < minP or targetP > maxP if noRequirementIfSetpointOutsidePowerBounds return true
138+
} else if (isSetpointOutsidePowerBounds(targetP, minP, maxP, config.getThreshold(), config.isNoRequirementIfSetpointOutsidePowerBounds())) {
137139
validated = true;
138140
} else {
139-
//Rule 4, Rule 5, Rule 6
140-
validated = checkGeneratorsValues(id, p, q, v, expectedP, targetQ, targetV, voltageRegulatorOn, minQ, maxQ, config);
141+
validated = validateGeneratorNominalRules(id, p, q, v, expectedP, targetQ, targetV, voltageRegulatorOn, minQ, maxQ, config);
141142
}
142143
}
143144
try {
@@ -166,73 +167,41 @@ private static double getExpectedP(BalanceTypeGuesser guesser, String id, double
166167
}
167168
}
168169

169-
private static boolean checkGeneratorsNaNValues(String id, double p, double q, double targetP, double targetQ) {
170+
private static boolean validateMissingPQRule(String id, double p, double q, double targetP, double targetQ) {
170171
// a validation error should be detected if there is both a voltage and a target but no p or q
171-
if (!Double.isNaN(targetP) && targetP != 0
172-
|| !Double.isNaN(targetQ) && targetQ != 0) {
172+
if (!Double.isNaN(targetP) && targetP != 0 || !Double.isNaN(targetQ) && targetQ != 0) {
173173
LOGGER.warn("{} {}: {}: P={} targetP={} - Q={} targetQ={}", ValidationType.GENERATORS, ValidationUtils.VALIDATION_ERROR, id, p, targetP, q, targetQ);
174174
return false;
175175
}
176176
return true;
177177
}
178178

179-
/**
180-
* Rule4: Active power (p) must match setpoint (expectedP) (within threshold)
181-
* Rule5: if voltageRegulatorOn="false" then reactive power (Q) should match to setpoint (targetQ) (within threshold)
182-
* Rule3: if voltageRegulatorOn="true" then either
183-
* Rule6.1: (minQ/maxQ/targetV) must be defined
184-
* Rule6.2: If V > targetV + threshold, generator (Qgen) must be at min reactive limit
185-
* Rule6.3: If V < targetV - threshold, generator (Qgen) must be at max reactive limit
186-
* Rule6.4: If |V-targetV| <= threshold, generator (Qgen) must be within [minQ, maxQ]
187-
*/
188-
private static boolean checkGeneratorsValues(String id, double p, double q, double v, double expectedP, double targetQ, double targetV,
179+
private static boolean validateGeneratorNominalRules(String id, double p, double q, double v, double expectedP, double targetQ, double targetV,
189180
boolean voltageRegulatorOn, double minQ, double maxQ, ValidationConfig config) {
190181
boolean validated = true;
191182
double threshold = config.getThreshold();
192-
// Rule4: Active power (p) must match setpoint (expectedP) (within threshold)
193-
if (ValidationUtils.areNaN(config, expectedP) || Math.abs(p + expectedP) > threshold) {
194-
LOGGER.warn("{} {}: {}: P={} expectedP={}", ValidationType.GENERATORS, ValidationUtils.VALIDATION_ERROR, id, p, expectedP);
183+
// Rule: Active power p matches expected setpoint
184+
if (isActivePowerKo(p, expectedP, config, threshold)) {
185+
LOGGER.warn("{} {}: {}: P={} expectedP={}",
186+
ValidationType.GENERATORS, ValidationUtils.VALIDATION_ERROR, id, p, expectedP);
195187
validated = false;
196188
}
197-
//Rule5: if voltageRegulatorOn="false" then reactive power (Q) should match to setpoint (targetQ) (within threshold)
198-
if (!voltageRegulatorOn && (ValidationUtils.areNaN(config, targetQ) || Math.abs(q + targetQ) > threshold)) {
189+
190+
//Rule: If voltage regulator is disabled, Reactive power Q matches targetQ
191+
if (!voltageRegulatorOn && (areNaN(config, targetQ) || isReactivePowerKo(q, targetQ, threshold))) {
199192
LOGGER.warn("{} {}: {}: voltage regulator off - Q={} targetQ={}", ValidationType.GENERATORS, ValidationUtils.VALIDATION_ERROR, id, q, targetQ);
200193
validated = false;
201194
}
202-
// Rule6, then
203-
// either Rule6.1, Rule6.2, Rule6.3 or Rule6.4
204-
//
205-
// if voltageRegulatorOn="true" then
206-
// either if minQ/maxQ/targetV are not NaN,
207-
// or q is equal to g.getReactiveLimits().getMinQ(p) and V is higher than g.getTargetV()
208-
// or q is equal to g.getReactiveLimits().getMaxQ(p) and V is lower than g.getTargetV()
209-
// or V at the connected bus is equal to g.getTargetV() and the reactive bounds are satisfied
195+
210196
double qGen = -q;
211-
if (voltageRegulatorOn
212-
&& (ValidationUtils.areNaN(config, minQ, maxQ, targetV)
213-
|| v > targetV + threshold && Math.abs(qGen - getMinQ(minQ, maxQ)) > threshold
214-
|| v < targetV - threshold && Math.abs(qGen - getMaxQ(minQ, maxQ)) > threshold
215-
|| Math.abs(v - targetV) <= threshold && !ValidationUtils.boundedWithin(minQ, maxQ, qGen, threshold))) {
197+
// Rule: If voltage regulator ON, Reactive power q follow V/targetV logic
198+
// - qGen at minQ if V > targetV + threshold
199+
// - qGen at maxQ if V < targetV - threshold
200+
// - else qGen within [minQ, maxQ])
201+
if (voltageRegulatorOn && (ValidationUtils.areNaN(config, minQ, maxQ, targetV) || isVoltageRegulationKo(qGen, v, targetV, minQ, maxQ, threshold))) {
216202
LOGGER.warn("{} {}: {}: voltage regulator on - Q={} minQ={} maxQ={} - V={} targetV={}", ValidationType.GENERATORS, ValidationUtils.VALIDATION_ERROR, id, qGen, minQ, maxQ, v, targetV);
217203
validated = false;
218204
}
219205
return validated;
220206
}
221-
222-
private static double getMaxQ(double minQ, double maxQ) {
223-
return Math.max(maxQ, minQ);
224-
}
225-
226-
private static double getMinQ(double minQ, double maxQ) {
227-
return Math.min(maxQ, minQ);
228-
}
229-
230-
private static boolean checkReactiveBoundInversion(double minQ, double maxQ, ValidationConfig config) {
231-
return maxQ < minQ - config.getThreshold() && config.isNoRequirementIfReactiveBoundInversion();
232-
}
233-
234-
private static boolean checkSetpointOutsidePowerBounds(double targetP, double minP, double maxP, ValidationConfig config) {
235-
return (targetP < minP - config.getThreshold() || targetP > maxP + config.getThreshold()) && config.isNoRequirementIfSetpointOutsidePowerBounds();
236-
}
237-
238207
}

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

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,17 @@ public static boolean areNaN(ValidationConfig config, float... values) {
5858
return areNaN;
5959
}
6060

61+
public static boolean areNaN(double... values) {
62+
boolean areMissing = false;
63+
for (double value : values) {
64+
if (Double.isNaN(value)) {
65+
areMissing = true;
66+
break;
67+
}
68+
}
69+
return areMissing;
70+
}
71+
6172
public static boolean areNaN(ValidationConfig config, double... values) {
6273
Objects.requireNonNull(config);
6374
if (config.areOkMissingValues()) {
@@ -126,4 +137,48 @@ public static double computeShuntExpectedQ(double bPerSection, int sectionCount,
126137
public static double voltageFrom(double vBus, double nominalV) {
127138
return (Double.isNaN(vBus) || vBus == 0.0) ? nominalV : vBus;
128139
}
140+
141+
public static boolean isActivePowerKo(double p, double expectedP, ValidationConfig config, double threshold) {
142+
return areNaN(config, expectedP) || Math.abs(p + expectedP) > threshold;
143+
}
144+
145+
/**
146+
* Generator: rule for valid result <code> | targetQ - Q | < threshold </code>
147+
*/
148+
public static boolean isReactivePowerKo(double q, double targetQ, double threshold) {
149+
return Math.abs(q + targetQ) >= threshold;
150+
}
151+
152+
/**
153+
* Generator: rules for valid result:</p>
154+
* <code> targetV - V < threshold && |Q - minQ| <= threshold</code></p>
155+
* <code> V - targetV < threshold && |Q - maxQ| <= threshold</code></p>
156+
* <code> |V - targetV| < threshold && minQ <= Q <= maxQ </code>
157+
*/
158+
public static boolean isVoltageRegulationKo(double qGen, double v, double targetV, double minQ, double maxQ, double threshold) {
159+
160+
// When V is higher than g.getTargetV() then q must equal to g.getReactiveLimits().getMinQ(p)
161+
// When V is lower than g.getTargetV() q must equal to g.getReactiveLimits().getMaxQ(p)
162+
// When V is equal to g.getTargetV() then q (reactive bounds) must satisfy
163+
return v > targetV + threshold && Math.abs(qGen - Math.min(minQ, maxQ)) > threshold
164+
|| v < targetV - threshold && Math.abs(qGen - Math.max(minQ, maxQ)) > threshold
165+
|| Math.abs(v - targetV) <= threshold && !boundedWithin(minQ, maxQ, qGen, threshold);
166+
}
167+
168+
/**
169+
* Generator: rule for valid result <p/>
170+
* If reactive limits are inverted (`maxQ < minQ`) and noRequirementIfReactiveBoundInversion = true, generator validation OK.
171+
*/
172+
public static boolean isReactiveBoundInverted(double minQ, double maxQ, double threshold, boolean isNoRequirementIfReactiveBoundInversion) {
173+
return maxQ < minQ - threshold && isNoRequirementIfReactiveBoundInversion;
174+
}
175+
176+
/**
177+
* Generator: rule for valid result<p/>
178+
* Active setpoint outside bounds, if `targetP` is outside `[minP, maxP]` and noRequirementIfSetpointOutsidePowerBounds = true, generator validation OK
179+
*/
180+
public static boolean isSetpointOutsidePowerBounds(double targetP, double minP, double maxP, double threshold, boolean isNoRequirementIfSetpointOutsidePowerBounds) {
181+
return (targetP < minP - threshold || targetP > maxP + threshold) && isNoRequirementIfSetpointOutsidePowerBounds;
182+
}
183+
129184
}

0 commit comments

Comments
 (0)