Skip to content

Commit 3c92bd6

Browse files
authored
Implement minTargetP and maxTargetP from ActivePowerControl extension (#1043)
Signed-off-by: VIDAL Didier (Externe) <didier.vidal_externe@rte-france.com>
1 parent b4c0e21 commit 3c92bd6

13 files changed

+163
-60
lines changed

src/main/java/com/powsybl/openloadflow/network/LargestGeneratorSlackBusSelector.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ public LargestGeneratorSlackBusSelector(double plausibleActivePowerLimit, Set<Co
3333
}
3434

3535
private static double getMaxP(LfBus bus) {
36-
return bus.getGenerators().stream().mapToDouble(LfGenerator::getMaxP).sum();
36+
return bus.getGenerators().stream().mapToDouble(LfGenerator::getMaxTargetP).sum();
3737
}
3838

3939
private boolean isGeneratorInvalid(LfGenerator generator) {

src/main/java/com/powsybl/openloadflow/network/LfAction.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -335,7 +335,7 @@ private void applyGeneratorChange(LfNetworkParameters networkParameters) {
335335
double newTargetP = generatorChange.isRelative() ? generator.getTargetP() + generatorChange.activePowerValue() : generatorChange.activePowerValue();
336336
generator.setTargetP(newTargetP);
337337
generator.setInitialTargetP(newTargetP);
338-
if (!AbstractLfGenerator.checkActivePowerControl(generator.getId(), generator.getTargetP(), generator.getMinP(), generator.getMaxP(),
338+
if (!AbstractLfGenerator.checkActivePowerControl(generator.getId(), generator.getTargetP(), generator.getMaxP(), generator.getMinTargetP(), generator.getMaxTargetP(),
339339
networkParameters.getPlausibleActivePowerLimit(), networkParameters.isUseActiveLimits(), null)) {
340340
generator.setParticipating(false);
341341
}

src/main/java/com/powsybl/openloadflow/network/LfGenerator.java

+16
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,22 @@ static double qToK(LfGenerator generator, double q) {
7272

7373
void setTargetP(double targetP);
7474

75+
/**
76+
* The minimum target P for active power operations (can be different from minP if minTargetP is set in the ActivePowerControl extension)
77+
* This limit is taken into account in the slack distribution.
78+
*/
79+
default double getMinTargetP() {
80+
return getMinP();
81+
}
82+
83+
/**
84+
* The maximum target P for active power operations (can be different from maxP if maxTargetP is set in the ActivePowerControl extension)
85+
* This limit is taken into account in the slack distribution.
86+
*/
87+
default double getMaxTargetP() {
88+
return getMaxP();
89+
}
90+
7591
double getMinP();
7692

7793
double getMaxP();

src/main/java/com/powsybl/openloadflow/network/LfNetwork.java

+2
Original file line numberDiff line numberDiff line change
@@ -527,6 +527,8 @@ private void writeJson(LfGenerator generator, JsonGenerator jsonGenerator) throw
527527
jsonGenerator.writeBooleanField("voltageControl", generator.getGeneratorControlType() == LfGenerator.GeneratorControlType.VOLTAGE);
528528
jsonGenerator.writeNumberField("minP", generator.getMinP());
529529
jsonGenerator.writeNumberField("maxP", generator.getMaxP());
530+
jsonGenerator.writeNumberField("minTargetP", generator.getMinTargetP());
531+
jsonGenerator.writeNumberField("maxTargetP", generator.getMaxTargetP());
530532
}
531533

532534
public void writeJson(Writer writer) {

src/main/java/com/powsybl/openloadflow/network/ReferenceBusGeneratorPrioritySelector.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ public SelectedReferenceBus select(LfNetwork lfNetwork) {
4343
// if multiple generators of same priority, select based on highest maxP
4444
LfGenerator referenceGenerator = lfGenerators.stream()
4545
.filter(g -> g.getReferencePriority() == priority)
46-
.min(Comparator.comparingDouble(LfGenerator::getMaxP).reversed().thenComparing(LfGenerator::getId)
46+
.min(Comparator.comparingDouble(LfGenerator::getMaxTargetP).reversed().thenComparing(LfGenerator::getId)
4747
).orElseThrow(() -> new IllegalStateException("No reference Generator for network " + lfNetwork));
4848
LfBus referenceBus = referenceGenerator.getBus();
4949
return new SelectedGeneratorReferenceBus(referenceBus, METHOD_NAME, referenceGenerator);

src/main/java/com/powsybl/openloadflow/network/impl/AbstractLfBus.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -419,7 +419,7 @@ public double getLoadTargetQ() {
419419

420420
@Override
421421
public double getMaxP() {
422-
return generators.stream().mapToDouble(LfGenerator::getMaxP).sum();
422+
return generators.stream().mapToDouble(LfGenerator::getMaxTargetP).sum();
423423
}
424424

425425
private double getLimitQ(ToDoubleFunction<LfGenerator> limitQ) {

src/main/java/com/powsybl/openloadflow/network/impl/AbstractLfGenerator.java

+42-9
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
import com.powsybl.commons.PowsyblException;
1111
import com.powsybl.iidm.network.*;
12+
import com.powsybl.iidm.network.extensions.ActivePowerControl;
1213
import com.powsybl.openloadflow.network.*;
1314
import com.powsybl.openloadflow.network.LfAsymGenerator;
1415
import com.powsybl.openloadflow.util.PerUnit;
@@ -61,6 +62,37 @@ protected AbstractLfGenerator(LfNetwork network, double targetP) {
6162
this.network = Objects.requireNonNull(network);
6263
}
6364

65+
protected record ActivePowerControlHelper(boolean participating, double participationFactor, double droop, double minTargetP, double maxTargetP) {
66+
67+
@SuppressWarnings("unchecked")
68+
static ActivePowerControlHelper create(Injection<?> injection, double minP, double maxP) {
69+
boolean participating = true;
70+
double participationFactor = 0;
71+
double droop = DEFAULT_DROOP;
72+
double minTargetP = minP;
73+
double maxTargetP = maxP;
74+
75+
var activePowerControl = injection.getExtension(ActivePowerControl.class);
76+
if (activePowerControl != null) {
77+
participating = activePowerControl.isParticipate();
78+
if (!Double.isNaN(activePowerControl.getDroop())) {
79+
droop = activePowerControl.getDroop();
80+
}
81+
if (activePowerControl.getParticipationFactor() > 0) {
82+
participationFactor = activePowerControl.getParticipationFactor();
83+
}
84+
if (activePowerControl.getMinTargetP().isPresent()) {
85+
minTargetP = activePowerControl.getMinTargetP().getAsDouble();
86+
}
87+
if (activePowerControl.getMaxTargetP().isPresent()) {
88+
maxTargetP = activePowerControl.getMaxTargetP().getAsDouble();
89+
}
90+
}
91+
92+
return new ActivePowerControlHelper(participating, participationFactor, droop, minTargetP, maxTargetP);
93+
}
94+
}
95+
6496
@Override
6597
public String getOriginalId() {
6698
return getId();
@@ -326,7 +358,8 @@ public void setParticipating(boolean participating) {
326358
// nothing to do
327359
}
328360

329-
public static boolean checkActivePowerControl(String generatorId, double targetP, double minP, double maxP, double plausibleActivePowerLimit,
361+
public static boolean checkActivePowerControl(String generatorId, double targetP, double maxP,
362+
double minTargetP, double maxTargetP, double plausibleActivePowerLimit,
330363
boolean useActiveLimits, LfNetworkLoadingReport report) {
331364
boolean participating = true;
332365
if (Math.abs(targetP) < POWER_EPSILON_SI) {
@@ -352,25 +385,25 @@ public static boolean checkActivePowerControl(String generatorId, double targetP
352385
// if active power limits are not to be enforced, we can skip the rest of the checks
353386
return participating;
354387
}
355-
if (targetP > maxP) {
356-
LOGGER.trace("Discard generator '{}' from active power control because targetP ({} MW) > maxP ({} MW)",
357-
generatorId, targetP, maxP);
388+
if (targetP > maxTargetP) {
389+
LOGGER.trace("Discard generator '{}' from active power control because targetP ({} MW) > maxTargetP ({} MW)",
390+
generatorId, targetP, maxTargetP);
358391
if (report != null) {
359392
report.generatorsDiscardedFromActivePowerControlBecauseTargetPGreaterThanMaxP++;
360393
}
361394
participating = false;
362395
}
363-
if (targetP < minP) {
364-
LOGGER.trace("Discard generator '{}' from active power control because targetP ({} MW) < minP ({} MW)",
365-
generatorId, targetP, minP);
396+
if (targetP < minTargetP) {
397+
LOGGER.trace("Discard generator '{}' from active power control because targetP ({} MW) < minTargetP ({} MW)",
398+
generatorId, targetP, minTargetP);
366399
if (report != null) {
367400
report.generatorsDiscardedFromActivePowerControlBecauseTargetPLowerThanMinP++;
368401
}
369402
participating = false;
370403
}
371-
if ((maxP - minP) < POWER_EPSILON_SI) {
404+
if ((maxTargetP - minTargetP) < POWER_EPSILON_SI) {
372405
LOGGER.trace("Discard generator '{}' from active power control because maxP ({} MW) equals minP ({} MW)",
373-
generatorId, maxP, minP);
406+
generatorId, minTargetP, minTargetP);
374407
if (report != null) {
375408
report.generatorsDiscardedFromActivePowerControlBecauseMaxPEqualsMinP++;
376409
}

src/main/java/com/powsybl/openloadflow/network/impl/LfBatteryImpl.java

+24-18
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99

1010
import com.powsybl.iidm.network.Battery;
1111
import com.powsybl.iidm.network.ReactiveLimits;
12-
import com.powsybl.iidm.network.extensions.ActivePowerControl;
1312
import com.powsybl.iidm.network.extensions.VoltageRegulation;
1413
import com.powsybl.openloadflow.network.LfNetwork;
1514
import com.powsybl.openloadflow.network.LfNetworkParameters;
@@ -28,28 +27,25 @@ public final class LfBatteryImpl extends AbstractLfGenerator {
2827

2928
private boolean participating;
3029

31-
private double droop;
30+
private final double droop;
3231

33-
private double participationFactor;
32+
private final double participationFactor;
33+
34+
private final double maxTargetP;
35+
36+
private final double minTargetP;
3437

3538
private LfBatteryImpl(Battery battery, LfNetwork network, LfNetworkParameters parameters, LfNetworkLoadingReport report) {
3639
super(network, battery.getTargetP() / PerUnit.SB);
3740
this.batteryRef = Ref.create(battery, parameters.isCacheEnabled());
38-
participating = true;
39-
droop = DEFAULT_DROOP;
40-
// get participation factor from extension
41-
ActivePowerControl<Battery> activePowerControl = battery.getExtension(ActivePowerControl.class);
42-
if (activePowerControl != null) {
43-
participating = activePowerControl.isParticipate();
44-
if (!Double.isNaN(activePowerControl.getDroop())) {
45-
droop = activePowerControl.getDroop();
46-
}
47-
if (activePowerControl.getParticipationFactor() > 0) {
48-
participationFactor = activePowerControl.getParticipationFactor();
49-
}
50-
}
51-
52-
if (!checkActivePowerControl(getId(), battery.getTargetP(), battery.getMinP(), battery.getMaxP(),
41+
var apcHelper = ActivePowerControlHelper.create(battery, battery.getMinP(), battery.getMaxP());
42+
participating = apcHelper.participating();
43+
participationFactor = apcHelper.participationFactor();
44+
droop = apcHelper.droop();
45+
minTargetP = apcHelper.minTargetP();
46+
maxTargetP = apcHelper.maxTargetP();
47+
48+
if (!checkActivePowerControl(getId(), battery.getTargetP(), battery.getMaxP(), minTargetP, maxTargetP,
5349
parameters.getPlausibleActivePowerLimit(), parameters.isUseActiveLimits(), report)) {
5450
participating = false;
5551
}
@@ -93,6 +89,16 @@ public double getMaxP() {
9389
return getBattery().getMaxP() / PerUnit.SB;
9490
}
9591

92+
@Override
93+
public double getMinTargetP() {
94+
return minTargetP / PerUnit.SB;
95+
}
96+
97+
@Override
98+
public double getMaxTargetP() {
99+
return maxTargetP / PerUnit.SB;
100+
}
101+
96102
@Override
97103
protected Optional<ReactiveLimits> getReactiveLimits() {
98104
return Optional.of(getBattery().getReactiveLimits());

src/main/java/com/powsybl/openloadflow/network/impl/LfGeneratorImpl.java

+23-17
Original file line numberDiff line numberDiff line change
@@ -35,37 +35,33 @@ public final class LfGeneratorImpl extends AbstractLfGenerator {
3535

3636
private boolean participating;
3737

38-
private double droop;
38+
private final double droop;
3939

40-
private double participationFactor;
40+
private final double participationFactor;
4141

4242
private Double qPercent;
4343

4444
private final boolean forceVoltageControl;
4545

46+
private final double maxTargetP;
47+
48+
private final double minTargetP;
49+
4650
private LfGeneratorImpl(Generator generator, LfNetwork network, LfNetworkParameters parameters, LfNetworkLoadingReport report) {
4751
super(network, generator.getTargetP() / PerUnit.SB);
4852
this.generatorRef = Ref.create(generator, parameters.isCacheEnabled());
4953
// we force voltage control of generators tagged as condensers or tagged as fictitious if the dedicated mode is activated.
5054
forceVoltageControl = generator.isCondenser() || generator.isFictitious() && parameters.getFictitiousGeneratorVoltageControlCheckMode() == OpenLoadFlowParameters.FictitiousGeneratorVoltageControlCheckMode.FORCED;
51-
participating = true;
52-
droop = DEFAULT_DROOP;
55+
var apcHelper = ActivePowerControlHelper.create(generator, generator.getMinP(), generator.getMaxP());
56+
participating = apcHelper.participating();
57+
participationFactor = apcHelper.participationFactor();
58+
droop = apcHelper.droop();
59+
minTargetP = apcHelper.minTargetP();
60+
maxTargetP = apcHelper.maxTargetP();
5361

5462
setReferencePriority(ReferencePriority.get(generator));
5563

56-
// get participation factor and droop from extension
57-
ActivePowerControl<Generator> activePowerControl = generator.getExtension(ActivePowerControl.class);
58-
if (activePowerControl != null) {
59-
participating = activePowerControl.isParticipate();
60-
if (!Double.isNaN(activePowerControl.getDroop())) {
61-
droop = activePowerControl.getDroop();
62-
}
63-
if (activePowerControl.getParticipationFactor() > 0) {
64-
participationFactor = activePowerControl.getParticipationFactor();
65-
}
66-
}
67-
68-
if (!checkActivePowerControl(generator.getId(), generator.getTargetP(), generator.getMinP(), generator.getMaxP(),
64+
if (!checkActivePowerControl(generator.getId(), generator.getTargetP(), generator.getMaxP(), minTargetP, maxTargetP,
6965
parameters.getPlausibleActivePowerLimit(), parameters.isUseActiveLimits(), report)) {
7066
participating = false;
7167
}
@@ -170,6 +166,16 @@ public double getMaxP() {
170166
return getGenerator().getMaxP() / PerUnit.SB;
171167
}
172168

169+
@Override
170+
public double getMinTargetP() {
171+
return minTargetP / PerUnit.SB;
172+
}
173+
174+
@Override
175+
public double getMaxTargetP() {
176+
return maxTargetP / PerUnit.SB;
177+
}
178+
173179
@Override
174180
protected Optional<ReactiveLimits> getReactiveLimits() {
175181
return Optional.of(getGenerator().getReactiveLimits());

src/main/java/com/powsybl/openloadflow/network/util/GenerationActivePowerDistributionStep.java

+10-10
Original file line numberDiff line numberDiff line change
@@ -86,24 +86,24 @@ public double run(List<ParticipatingElement> participatingElements, int iteratio
8686
LfGenerator generator = (LfGenerator) participatingGenerator.getElement();
8787
double factor = participatingGenerator.getFactor();
8888

89-
double minP = useActiveLimits ? generator.getMinP() : -Double.MAX_VALUE;
90-
double maxP = useActiveLimits ? generator.getMaxP() : Double.MAX_VALUE;
89+
double minTargetP = useActiveLimits ? generator.getMinTargetP() : -Double.MAX_VALUE;
90+
double maxTargetP = useActiveLimits ? generator.getMaxTargetP() : Double.MAX_VALUE;
9191
double targetP = generator.getTargetP();
9292

9393
// we don't want to change the generation sign
9494
if (targetP < 0) {
95-
maxP = Math.min(maxP, 0);
95+
maxTargetP = Math.min(maxTargetP, 0);
9696
} else {
97-
minP = Math.max(minP, 0);
97+
minTargetP = Math.max(minTargetP, 0);
9898
}
9999

100100
double newTargetP = targetP + mismatch * factor;
101-
if (mismatch > 0 && newTargetP > maxP) {
102-
newTargetP = maxP;
101+
if (mismatch > 0 && newTargetP > maxTargetP) {
102+
newTargetP = maxTargetP;
103103
generatorsAtMax++;
104104
it.remove();
105-
} else if (mismatch < 0 && newTargetP < minP) {
106-
newTargetP = minP;
105+
} else if (mismatch < 0 && newTargetP < minTargetP) {
106+
newTargetP = minTargetP;
107107
generatorsAtMin++;
108108
it.remove();
109109
}
@@ -126,10 +126,10 @@ public double run(List<ParticipatingElement> participatingElements, int iteratio
126126

127127
private double getParticipationFactor(LfGenerator generator) {
128128
return switch (participationType) {
129-
case MAX -> generator.getMaxP() / generator.getDroop();
129+
case MAX -> generator.getMaxTargetP() / generator.getDroop();
130130
case TARGET -> Math.abs(generator.getTargetP());
131131
case PARTICIPATION_FACTOR -> generator.getParticipationFactor();
132-
case REMAINING_MARGIN -> Math.max(0.0, generator.getMaxP() - generator.getTargetP());
132+
case REMAINING_MARGIN -> Math.max(0.0, generator.getMaxTargetP() - generator.getTargetP());
133133
};
134134
}
135135

src/test/java/com/powsybl/openloadflow/ac/DistributedSlackOnGenerationTest.java

+36
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,42 @@ void testProportionalToP() {
9898
assertActivePowerEquals(-117.236, g4.getTerminal());
9999
}
100100

101+
@Test
102+
void testProportionalToPWithTargetLimit() {
103+
// decrease g1 max limit power, so that distributed slack algo reach the g1 max
104+
g1.setMaxP(105);
105+
g1.getExtension(ActivePowerControl.class).setMaxTargetP(103);
106+
parameters.setBalanceType(LoadFlowParameters.BalanceType.PROPORTIONAL_TO_GENERATION_P);
107+
LoadFlowResult result = loadFlowRunner.run(network, parameters);
108+
109+
assertTrue(result.isFullyConverged());
110+
assertActivePowerEquals(-103, g1.getTerminal());
111+
assertActivePowerEquals(-261.579, g2.getTerminal());
112+
assertActivePowerEquals(-117.711, g3.getTerminal());
113+
assertActivePowerEquals(-117.711, g4.getTerminal());
114+
115+
// now compensation down
116+
Load l1 = network.getLoad("l1");
117+
l1.setP0(400); // was 600
118+
result = loadFlowRunner.run(network, parameters);
119+
120+
assertTrue(result.isFullyConverged());
121+
assertActivePowerEquals(-83.333, g1.getTerminal());
122+
assertActivePowerEquals(-166.667, g2.getTerminal());
123+
assertActivePowerEquals(-75.000, g3.getTerminal());
124+
assertActivePowerEquals(-75.000, g4.getTerminal());
125+
126+
// With a minTargetP for g1
127+
g1.getExtension(ActivePowerControl.class).setMinTargetP(85);
128+
129+
result = loadFlowRunner.run(network, parameters);
130+
assertTrue(result.isFullyConverged());
131+
assertActivePowerEquals(-85, g1.getTerminal());
132+
assertActivePowerEquals(-165.790, g2.getTerminal());
133+
assertActivePowerEquals(-74.605, g3.getTerminal());
134+
assertActivePowerEquals(-74.605, g4.getTerminal());
135+
}
136+
101137
@Test
102138
@SuppressWarnings("unchecked")
103139
void testProportionalToParticipationFactor() {

src/test/resources/n.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@
99
"targetQ" : 3.01,
1010
"voltageControl" : true,
1111
"minP" : -99.9999,
12-
"maxP" : 49.99
12+
"maxP" : 49.99,
13+
"minTargetP" : -99.9999,
14+
"maxTargetP" : 49.99
1315
} ]
1416
}, {
1517
"id" : "VLHV1_0",

0 commit comments

Comments
 (0)