diff --git a/src/main/java/com/powsybl/openloadflow/ac/outerloop/DistributedSlackOuterLoop.java b/src/main/java/com/powsybl/openloadflow/ac/outerloop/DistributedSlackOuterLoop.java index 8df3c11a2a..fdd3a16917 100644 --- a/src/main/java/com/powsybl/openloadflow/ac/outerloop/DistributedSlackOuterLoop.java +++ b/src/main/java/com/powsybl/openloadflow/ac/outerloop/DistributedSlackOuterLoop.java @@ -56,7 +56,8 @@ public void initialize(AcOuterLoopContext context) { @Override public OuterLoopResult check(AcOuterLoopContext context, ReportNode reportNode) { double slackBusActivePowerMismatch = context.getLastSolverResult().getSlackBusActivePowerMismatch(); - boolean shouldDistributeSlack = Math.abs(slackBusActivePowerMismatch) > slackBusPMaxMismatch / PerUnit.SB; + double absMismatch = Math.abs(slackBusActivePowerMismatch); + boolean shouldDistributeSlack = absMismatch > slackBusPMaxMismatch / PerUnit.SB && absMismatch > ActivePowerDistribution.P_RESIDUE_EPS; if (!shouldDistributeSlack) { LOGGER.debug("Already balanced"); diff --git a/src/main/java/com/powsybl/openloadflow/network/BusDcState.java b/src/main/java/com/powsybl/openloadflow/network/BusDcState.java index 585e6877f1..ebd78b7a13 100644 --- a/src/main/java/com/powsybl/openloadflow/network/BusDcState.java +++ b/src/main/java/com/powsybl/openloadflow/network/BusDcState.java @@ -18,6 +18,7 @@ public class BusDcState extends ElementState { private final Map generatorsTargetP; + private final Map generatorsInitialTargetP; private final Map participatingGenerators; private final Map disablingStatusGenerators; private final List loadStates; @@ -45,6 +46,7 @@ protected void restore(LfLoad load) { public BusDcState(LfBus bus) { super(bus); this.generatorsTargetP = bus.getGenerators().stream().collect(Collectors.toMap(LfGenerator::getId, LfGenerator::getTargetP)); + this.generatorsInitialTargetP = bus.getGenerators().stream().collect(Collectors.toMap(LfGenerator::getId, LfGenerator::getInitialTargetP)); this.participatingGenerators = bus.getGenerators().stream().collect(Collectors.toMap(LfGenerator::getId, LfGenerator::isParticipating)); this.disablingStatusGenerators = bus.getGenerators().stream().collect(Collectors.toMap(LfGenerator::getId, LfGenerator::isDisabled)); loadStates = bus.getLoads().stream().map(load -> createLoadState().save(load)).toList(); @@ -58,6 +60,7 @@ protected LoadDcState createLoadState() { public void restore() { super.restore(); element.getGenerators().forEach(g -> g.setTargetP(generatorsTargetP.get(g.getId()))); + element.getGenerators().forEach(g -> g.setInitialTargetP(generatorsInitialTargetP.get(g.getId()))); element.getGenerators().forEach(g -> g.setParticipating(participatingGenerators.get(g.getId()))); element.getGenerators().forEach(g -> g.setDisabled(disablingStatusGenerators.get(g.getId()))); for (int i = 0; i < loadStates.size(); i++) { diff --git a/src/main/java/com/powsybl/openloadflow/network/LfAction.java b/src/main/java/com/powsybl/openloadflow/network/LfAction.java index e126a31b0d..f5ee32a1b5 100644 --- a/src/main/java/com/powsybl/openloadflow/network/LfAction.java +++ b/src/main/java/com/powsybl/openloadflow/network/LfAction.java @@ -334,6 +334,7 @@ private void applyGeneratorChange(LfNetworkParameters networkParameters) { if (!generator.isDisabled()) { double newTargetP = generatorChange.isRelative() ? generator.getTargetP() + generatorChange.activePowerValue() : generatorChange.activePowerValue(); generator.setTargetP(newTargetP); + generator.setInitialTargetP(newTargetP); if (!AbstractLfGenerator.checkActivePowerControl(generator.getId(), generator.getTargetP(), generator.getMinP(), generator.getMaxP(), networkParameters.getPlausibleActivePowerLimit(), networkParameters.isUseActiveLimits(), null)) { generator.setParticipating(false); diff --git a/src/main/java/com/powsybl/openloadflow/network/LfContingency.java b/src/main/java/com/powsybl/openloadflow/network/LfContingency.java index 1f55b75f98..37a472e449 100644 --- a/src/main/java/com/powsybl/openloadflow/network/LfContingency.java +++ b/src/main/java/com/powsybl/openloadflow/network/LfContingency.java @@ -15,7 +15,6 @@ import java.io.UncheckedIOException; import java.io.Writer; import java.util.*; -import java.util.stream.Collectors; /** * @author Geoffroy Jamgotchian {@literal } @@ -61,8 +60,8 @@ public LfContingency(String id, int index, int createdSynchronousComponentsCount for (LfBus bus : disabledNetwork.getBuses()) { disconnectedLoadActivePower += bus.getLoadTargetP(); disconnectedGenerationActivePower += bus.getGenerationTargetP(); - disconnectedElementIds.addAll(bus.getGenerators().stream().map(LfGenerator::getId).collect(Collectors.toList())); - disconnectedElementIds.addAll(bus.getLoads().stream().flatMap(l -> l.getOriginalIds().stream()).collect(Collectors.toList())); + disconnectedElementIds.addAll(bus.getGenerators().stream().map(LfGenerator::getId).toList()); + disconnectedElementIds.addAll(bus.getLoads().stream().flatMap(l -> l.getOriginalIds().stream()).toList()); bus.getControllerShunt().ifPresent(shunt -> disconnectedElementIds.addAll(shunt.getOriginalIds())); bus.getShunt().ifPresent(shunt -> disconnectedElementIds.addAll(shunt.getOriginalIds())); } @@ -75,7 +74,7 @@ public LfContingency(String id, int index, int createdSynchronousComponentsCount disconnectedGenerationActivePower += generator.getTargetP(); disconnectedElementIds.add(generator.getOriginalId()); } - disconnectedElementIds.addAll(disabledNetwork.getBranches().stream().map(LfBranch::getId).collect(Collectors.toList())); + disconnectedElementIds.addAll(disabledNetwork.getBranches().stream().map(LfBranch::getId).toList()); // FIXME: shuntsShift has to be included in the disconnected elements. } @@ -156,14 +155,15 @@ public void apply(LoadFlowParameters.BalanceType balanceType) { Set generatorBuses = new HashSet<>(); for (LfGenerator generator : lostGenerators) { generator.setTargetP(0); + generator.setInitialTargetP(0); LfBus bus = generator.getBus(); generatorBuses.add(bus); generator.setParticipating(false); generator.setDisabled(true); if (generator.getGeneratorControlType() != LfGenerator.GeneratorControlType.OFF) { generator.setGeneratorControlType(LfGenerator.GeneratorControlType.OFF); - bus.getGeneratorVoltageControl().ifPresent(vc -> vc.updateReactiveKeys()); - bus.getGeneratorReactivePowerControl().ifPresent(rc -> rc.updateReactiveKeys()); + bus.getGeneratorVoltageControl().ifPresent(GeneratorVoltageControl::updateReactiveKeys); + bus.getGeneratorReactivePowerControl().ifPresent(GeneratorReactivePowerControl::updateReactiveKeys); } else { bus.setGenerationTargetQ(bus.getGenerationTargetQ() - generator.getTargetQ()); } diff --git a/src/main/java/com/powsybl/openloadflow/network/LfGenerator.java b/src/main/java/com/powsybl/openloadflow/network/LfGenerator.java index 1140bc03d6..6b8952eb3c 100644 --- a/src/main/java/com/powsybl/openloadflow/network/LfGenerator.java +++ b/src/main/java/com/powsybl/openloadflow/network/LfGenerator.java @@ -64,6 +64,10 @@ static double qToK(LfGenerator generator, double q) { double getInitialTargetP(); + void setInitialTargetP(double initialTargetP); + + void setInitialTargetPToTargetP(); + double getTargetP(); void setTargetP(double targetP); diff --git a/src/main/java/com/powsybl/openloadflow/network/NetworkState.java b/src/main/java/com/powsybl/openloadflow/network/NetworkState.java index 078fbb7f12..4b6d23f27a 100644 --- a/src/main/java/com/powsybl/openloadflow/network/NetworkState.java +++ b/src/main/java/com/powsybl/openloadflow/network/NetworkState.java @@ -43,6 +43,7 @@ protected NetworkState(LfNetwork network, List busStates, List b.getGenerators().stream()).forEach(LfGenerator::setInitialTargetPToTargetP); List busStates = ElementState.save(network.getBuses(), BusState::save); List branchStates = ElementState.save(network.getBranches(), BranchState::save); List hvdcStates = ElementState.save(network.getHvdcs(), HvdcState::save); diff --git a/src/main/java/com/powsybl/openloadflow/network/impl/AbstractLfInjection.java b/src/main/java/com/powsybl/openloadflow/network/impl/AbstractLfInjection.java index 6f776a1928..39257b8489 100644 --- a/src/main/java/com/powsybl/openloadflow/network/impl/AbstractLfInjection.java +++ b/src/main/java/com/powsybl/openloadflow/network/impl/AbstractLfInjection.java @@ -27,6 +27,14 @@ public double getInitialTargetP() { return initialTargetP; } + public void setInitialTargetP(double initialTargetP) { + this.initialTargetP = initialTargetP; + } + + public void setInitialTargetPToTargetP() { + initialTargetP = targetP; + } + public double getTargetP() { return targetP; } diff --git a/src/main/java/com/powsybl/openloadflow/network/impl/LfVscConverterStationImpl.java b/src/main/java/com/powsybl/openloadflow/network/impl/LfVscConverterStationImpl.java index 233c12451a..755c896c8c 100644 --- a/src/main/java/com/powsybl/openloadflow/network/impl/LfVscConverterStationImpl.java +++ b/src/main/java/com/powsybl/openloadflow/network/impl/LfVscConverterStationImpl.java @@ -73,6 +73,16 @@ public double getTargetP() { } } + @Override + public void setInitialTargetP(double initialTargetP) { + // no-op + } + + @Override + public void setInitialTargetPToTargetP() { + // no-op + } + @Override public double getLossFactor() { return lossFactor; diff --git a/src/main/java/com/powsybl/openloadflow/network/util/ActivePowerDistribution.java b/src/main/java/com/powsybl/openloadflow/network/util/ActivePowerDistribution.java index 8eb01d2bda..e8ba1e5052 100644 --- a/src/main/java/com/powsybl/openloadflow/network/util/ActivePowerDistribution.java +++ b/src/main/java/com/powsybl/openloadflow/network/util/ActivePowerDistribution.java @@ -13,7 +13,10 @@ import java.util.Collection; import java.util.List; +import java.util.Map; import java.util.Objects; +import java.util.function.Function; +import java.util.stream.Collectors; /** * @author Geoffroy Jamgotchian {@literal } @@ -25,15 +28,13 @@ public final class ActivePowerDistribution { */ public static final double P_RESIDUE_EPS = Math.pow(10, -5); - public record StepResult(double done, boolean movedBuses) { } - public interface Step { String getElementType(); List getParticipatingElements(Collection buses); - StepResult run(List participatingElements, int iteration, double remainingMismatch); + double run(List participatingElements, int iteration, double remainingMismatch); } public record Result(int iteration, double remainingMismatch, boolean movedBuses) { } @@ -54,25 +55,26 @@ public Result run(LfNetwork network, double activePowerMismatch) { public Result run(Collection buses, double activePowerMismatch) { List participatingElements = step.getParticipatingElements(buses); + final Map initialP = participatingElements.stream() + .collect(Collectors.toUnmodifiableMap(Function.identity(), ParticipatingElement::getTargetP)); int iteration = 0; double remainingMismatch = activePowerMismatch; - boolean movedBuses = false; while (!participatingElements.isEmpty() && Math.abs(remainingMismatch) > P_RESIDUE_EPS) { if (ParticipatingElement.participationFactorNorm(participatingElements) > 0.0) { - StepResult stepResult = step.run(participatingElements, iteration, remainingMismatch); - remainingMismatch -= stepResult.done(); - if (stepResult.movedBuses()) { - movedBuses = true; - } + double done = step.run(participatingElements, iteration, remainingMismatch); + remainingMismatch -= done; } else { break; } iteration++; } + final boolean movedBuses = initialP.entrySet().stream() + .anyMatch(e -> Math.abs(e.getKey().getTargetP() - e.getValue()) > P_RESIDUE_EPS); + return new Result(iteration, remainingMismatch, movedBuses); } diff --git a/src/main/java/com/powsybl/openloadflow/network/util/GenerationActivePowerDistributionStep.java b/src/main/java/com/powsybl/openloadflow/network/util/GenerationActivePowerDistributionStep.java index fec7aded65..1796b41fa2 100644 --- a/src/main/java/com/powsybl/openloadflow/network/util/GenerationActivePowerDistributionStep.java +++ b/src/main/java/com/powsybl/openloadflow/network/util/GenerationActivePowerDistributionStep.java @@ -16,6 +16,7 @@ import java.util.Collection; import java.util.Iterator; +import java.util.LinkedList; import java.util.List; import java.util.stream.Collectors; @@ -55,16 +56,27 @@ public List getParticipatingElements(Collection bus .flatMap(bus -> bus.getGenerators().stream()) .filter(generator -> isParticipating(generator) && getParticipationFactor(generator) != 0) .map(generator -> new ParticipatingElement(generator, getParticipationFactor(generator))) - .collect(Collectors.toList()); + .collect(Collectors.toCollection(LinkedList::new)); } @Override - public ActivePowerDistribution.StepResult run(List participatingElements, int iteration, double remainingMismatch) { + public double run(List participatingElements, int iteration, double remainingMismatch) { // normalize participation factors at each iteration start as some // generators might have reach a limit and have been discarded ParticipatingElement.normalizeParticipationFactors(participatingElements); double done = 0d; + double mismatch = remainingMismatch; + if (iteration == 0) { + // "undo" everything from targetP to go back to initialP + for (ParticipatingElement participatingGenerator : participatingElements) { + LfGenerator generator = (LfGenerator) participatingGenerator.getElement(); + done += generator.getInitialTargetP() - generator.getTargetP(); + mismatch -= generator.getInitialTargetP() - generator.getTargetP(); + generator.setTargetP(generator.getInitialTargetP()); + } + } + int modifiedBuses = 0; int generatorsAtMax = 0; int generatorsAtMin = 0; @@ -85,12 +97,12 @@ public ActivePowerDistribution.StepResult run(List partici minP = Math.max(minP, 0); } - double newTargetP = targetP + remainingMismatch * factor; - if (remainingMismatch > 0 && newTargetP > maxP) { + double newTargetP = targetP + mismatch * factor; + if (mismatch > 0 && newTargetP > maxP) { newTargetP = maxP; generatorsAtMax++; it.remove(); - } else if (remainingMismatch < 0 && newTargetP < minP) { + } else if (mismatch < 0 && newTargetP < minP) { newTargetP = minP; generatorsAtMin++; it.remove(); @@ -106,10 +118,10 @@ public ActivePowerDistribution.StepResult run(List partici } LOGGER.debug("{} MW / {} MW distributed at iteration {} to {} generators ({} at max power, {} at min power)", - done * PerUnit.SB, remainingMismatch * PerUnit.SB, iteration, modifiedBuses, + done * PerUnit.SB, mismatch * PerUnit.SB, iteration, modifiedBuses, generatorsAtMax, generatorsAtMin); - return new ActivePowerDistribution.StepResult(done, modifiedBuses != 0); + return done; } private double getParticipationFactor(LfGenerator generator) { diff --git a/src/main/java/com/powsybl/openloadflow/network/util/LoadActivePowerDistributionStep.java b/src/main/java/com/powsybl/openloadflow/network/util/LoadActivePowerDistributionStep.java index ed34e422a4..67fbd1b3fc 100644 --- a/src/main/java/com/powsybl/openloadflow/network/util/LoadActivePowerDistributionStep.java +++ b/src/main/java/com/powsybl/openloadflow/network/util/LoadActivePowerDistributionStep.java @@ -15,6 +15,7 @@ import java.util.Collection; import java.util.Iterator; +import java.util.LinkedList; import java.util.List; import java.util.stream.Collectors; @@ -42,7 +43,7 @@ public List getParticipatingElements(Collection bus .filter(bus -> bus.isParticipating() && !bus.isDisabled() && !bus.isFictitious()) .flatMap(bus -> bus.getLoads().stream()) .map(load -> new ParticipatingElement(load, getParticipationFactor(load))) - .collect(Collectors.toList()); + .collect(Collectors.toCollection(LinkedList::new)); } private double getParticipationFactor(LfLoad load) { @@ -50,7 +51,7 @@ private double getParticipationFactor(LfLoad load) { } @Override - public ActivePowerDistribution.StepResult run(List participatingElements, int iteration, double remainingMismatch) { + public double run(List participatingElements, int iteration, double remainingMismatch) { // normalize participation factors at each iteration start as some // loads might have reach zero and have been discarded. ParticipatingElement.normalizeParticipationFactors(participatingElements); @@ -84,7 +85,7 @@ public ActivePowerDistribution.StepResult run(List partici LOGGER.debug("{} MW / {} MW distributed at iteration {} to {} buses ({} at min consumption)", -done * PerUnit.SB, -remainingMismatch * PerUnit.SB, iteration, modifiedBuses, loadsAtMin); - return new ActivePowerDistribution.StepResult(done, modifiedBuses != 0); + return done; } private static void ensurePowerFactorConstant(LfLoad load, double newLoadTargetP) { diff --git a/src/main/java/com/powsybl/openloadflow/network/util/ParticipatingElement.java b/src/main/java/com/powsybl/openloadflow/network/util/ParticipatingElement.java index 40ba293a51..3affe65649 100644 --- a/src/main/java/com/powsybl/openloadflow/network/util/ParticipatingElement.java +++ b/src/main/java/com/powsybl/openloadflow/network/util/ParticipatingElement.java @@ -35,6 +35,16 @@ public double getFactor() { return factor; } + public double getTargetP() { + if (element instanceof LfGenerator generator) { + return generator.getTargetP(); + } else if (element instanceof LfLoad load) { + return load.getTargetP(); + } else { + return Double.NaN; + } + } + public static double participationFactorNorm(List participatingElements) { return participatingElements.stream() .mapToDouble(participatingGenerator -> participatingGenerator.factor) diff --git a/src/test/java/com/powsybl/openloadflow/ac/DistributedSlackOnGenerationTest.java b/src/test/java/com/powsybl/openloadflow/ac/DistributedSlackOnGenerationTest.java index 89ea02de66..26a629c0b8 100644 --- a/src/test/java/com/powsybl/openloadflow/ac/DistributedSlackOnGenerationTest.java +++ b/src/test/java/com/powsybl/openloadflow/ac/DistributedSlackOnGenerationTest.java @@ -362,7 +362,7 @@ void notEnoughActivePowerDistributeNoReferenceGeneratorTest() { @Test void generatorWithNegativeTargetP() { - Network network = EurostagFactory.fix(EurostagTutorialExample1Factory.create()); + network = EurostagFactory.fix(EurostagTutorialExample1Factory.create()); network.getGenerator("GEN").setMaxP(1000); network.getGenerator("GEN").setTargetP(-607); network.getLoad("LOAD").setP0(-600); @@ -374,7 +374,7 @@ void generatorWithNegativeTargetP() { @Test void generatorWithMaxPEqualsToMinP() { - Network network = EurostagFactory.fix(EurostagTutorialExample1Factory.create()); + network = EurostagFactory.fix(EurostagTutorialExample1Factory.create()); network.getGenerator("GEN").setMaxP(1000); network.getGenerator("GEN").setMinP(1000); network.getGenerator("GEN").setTargetP(1000); @@ -403,7 +403,7 @@ void nonParticipatingBus() { @Test void generatorWithTargetPLowerThanMinP() { - Network network = EurostagFactory.fix(EurostagTutorialExample1Factory.create()); + network = EurostagFactory.fix(EurostagTutorialExample1Factory.create()); network.getGenerator("GEN").setMaxP(1000); network.getGenerator("GEN").setMinP(200); network.getGenerator("GEN").setTargetP(100); @@ -436,11 +436,11 @@ void notParticipatingTest() { @Test void batteryTest() { - Network network = DistributedSlackNetworkFactory.createWithBattery(); - Generator g1 = network.getGenerator("g1"); - Generator g2 = network.getGenerator("g2"); - Generator g3 = network.getGenerator("g3"); - Generator g4 = network.getGenerator("g4"); + network = DistributedSlackNetworkFactory.createWithBattery(); + g1 = network.getGenerator("g1"); + g2 = network.getGenerator("g2"); + g3 = network.getGenerator("g3"); + g4 = network.getGenerator("g4"); Battery bat1 = network.getBattery("bat1"); Battery bat2 = network.getBattery("bat2"); LoadFlowResult result = loadFlowRunner.run(network, parameters); @@ -458,11 +458,11 @@ void batteryTest() { @Test @SuppressWarnings("unchecked") void batteryTestProportionalToParticipationFactor() { - Network network = DistributedSlackNetworkFactory.createWithBattery(); - Generator g1 = network.getGenerator("g1"); - Generator g2 = network.getGenerator("g2"); - Generator g3 = network.getGenerator("g3"); - Generator g4 = network.getGenerator("g4"); + network = DistributedSlackNetworkFactory.createWithBattery(); + g1 = network.getGenerator("g1"); + g2 = network.getGenerator("g2"); + g3 = network.getGenerator("g3"); + g4 = network.getGenerator("g4"); Battery bat1 = network.getBattery("bat1"); Battery bat2 = network.getBattery("bat2"); g1.getExtension(ActivePowerControl.class).setParticipationFactor(Double.NaN); @@ -485,11 +485,11 @@ void batteryTestProportionalToParticipationFactor() { @Test void testDistributedActivePower() { parameters.setUseReactiveLimits(true).getExtension(OpenLoadFlowParameters.class).setSlackBusPMaxMismatch(0.0001); - Network network = DistributedSlackNetworkFactory.createWithLossesAndPvPqTypeSwitch(); - Generator g1 = network.getGenerator("g1"); - Generator g2 = network.getGenerator("g2"); - Generator g3 = network.getGenerator("g3"); - Generator g4 = network.getGenerator("g4"); + network = DistributedSlackNetworkFactory.createWithLossesAndPvPqTypeSwitch(); + g1 = network.getGenerator("g1"); + g2 = network.getGenerator("g2"); + g3 = network.getGenerator("g3"); + g4 = network.getGenerator("g4"); LoadFlowResult result = loadFlowRunner.run(network, parameters); assertTrue(result.isFullyConverged()); // we were getting 132.47279 when computing distributedActivePower as initial NR slack - final NR slack, while difference targetP - P was only 120.1961 @@ -505,10 +505,48 @@ void testDistributedActivePower() { @Test void testDistributedActivePowerSlackDistributionDisabled() { parameters.setUseReactiveLimits(true).setDistributedSlack(false); - Network network = DistributedSlackNetworkFactory.createWithLossesAndPvPqTypeSwitch(); + network = DistributedSlackNetworkFactory.createWithLossesAndPvPqTypeSwitch(); LoadFlowResult result = loadFlowRunner.run(network, parameters); assertTrue(result.isFullyConverged()); // we were getting 12.307 when computing distributedActivePower as initial NR slack - final NR slack, expecting zero here assertEquals(0.0, result.getComponentResults().get(0).getDistributedActivePower(), LoadFlowAssert.DELTA_POWER); } + + @Test + void testSlackMismatchChangingSign() { + parameters.setUseReactiveLimits(true).getExtension(OpenLoadFlowParameters.class).setSlackBusPMaxMismatch(0.0001); + network = DistributedSlackNetworkFactory.createWithLossesAndPvPqTypeSwitch(); + g1 = network.getGenerator("g1"); + g2 = network.getGenerator("g2"); + g3 = network.getGenerator("g3"); + g4 = network.getGenerator("g4"); + + parameters.setBalanceType(LoadFlowParameters.BalanceType.PROPORTIONAL_TO_GENERATION_PARTICIPATION_FACTOR); + for (var g : network.getGenerators()) { + ActivePowerControl ext = g.getExtension(ActivePowerControl.class); + ext.setParticipationFactor(1.0); + } + + g1.setMaxP(110.0); + g3.setMaxP(110.0); + g4.setMaxP(110.0); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + + var expectedDistributedActivePower = -network.getGeneratorStream().mapToDouble(g -> g.getTargetP() + g.getTerminal().getP()).sum(); + assertEquals(120.1976, expectedDistributedActivePower, LoadFlowAssert.DELTA_POWER); + assertEquals(expectedDistributedActivePower, result.getComponentResults().get(0).getDistributedActivePower(), LoadFlowAssert.DELTA_POWER); + + // All generators have the same participation factor, and should increase generation by 120.1976 MW + // generator | targetP | maxP + // ----------|---------|------- + // g1 | 100 | 110 --> expected to hit limit 110MW with 10MW distributed + // g2 | 90 | 300 --> expected to pick up the remaining slack 70.1976 MW + // g3 | 90 | 110 --> expected to hit limit 110MW with 20MW distributed + // g4 | 90 | 110 --> expected to hit limit 110MW with 20MW distributed + assertActivePowerEquals(-110.000, g1.getTerminal()); + assertActivePowerEquals(-270.1976, g2.getTerminal()); + assertActivePowerEquals(-110.000, g3.getTerminal()); + assertActivePowerEquals(-110.000, g4.getTerminal()); + } }