diff --git a/src/main/java/com/powsybl/openloadflow/network/impl/AbstractLfBus.java b/src/main/java/com/powsybl/openloadflow/network/impl/AbstractLfBus.java index 8ba9998f47..ef35f1d418 100644 --- a/src/main/java/com/powsybl/openloadflow/network/impl/AbstractLfBus.java +++ b/src/main/java/com/powsybl/openloadflow/network/impl/AbstractLfBus.java @@ -415,22 +415,26 @@ public void addHvdc(LfHvdc hvdc) { hvdcs.add(Objects.requireNonNull(hvdc)); } - private static double dispatchQ(List generatorsThatControlVoltage, boolean reactiveLimits, double qToDispatch) { + protected static double dispatchQ(List generatorsThatControlVoltage, boolean reactiveLimits, double qToDispatch) { double residueQ = 0; - double calculatedQ = qToDispatch / generatorsThatControlVoltage.size(); + if (generatorsThatControlVoltage.isEmpty()) { + throw new IllegalArgumentException("the generator list to dispatch Q can not be empty"); + } + double qToBeDispatchedByGenerator = qToDispatch / generatorsThatControlVoltage.size(); Iterator itG = generatorsThatControlVoltage.iterator(); while (itG.hasNext()) { LfGenerator generator = itG.next(); - if (reactiveLimits && calculatedQ < generator.getMinQ()) { - generator.setCalculatedQ(generator.getCalculatedQ() + generator.getMinQ()); - residueQ += calculatedQ - generator.getMinQ(); + double generatorAlreadyCalculatedQ = generator.getCalculatedQ(); + if (reactiveLimits && qToBeDispatchedByGenerator + generatorAlreadyCalculatedQ < generator.getMinQ()) { + residueQ += qToBeDispatchedByGenerator + generatorAlreadyCalculatedQ - generator.getMinQ(); + generator.setCalculatedQ(generator.getMinQ()); itG.remove(); - } else if (reactiveLimits && calculatedQ > generator.getMaxQ()) { - generator.setCalculatedQ(generator.getCalculatedQ() + generator.getMaxQ()); - residueQ += calculatedQ - generator.getMaxQ(); + } else if (reactiveLimits && qToBeDispatchedByGenerator + generatorAlreadyCalculatedQ > generator.getMaxQ()) { + residueQ += qToBeDispatchedByGenerator + generatorAlreadyCalculatedQ - generator.getMaxQ(); + generator.setCalculatedQ(generator.getMaxQ()); itG.remove(); } else { - generator.setCalculatedQ(generator.getCalculatedQ() + calculatedQ); + generator.setCalculatedQ(generatorAlreadyCalculatedQ + qToBeDispatchedByGenerator); } } return residueQ; @@ -458,7 +462,7 @@ void updateGeneratorsState(double generationQ, boolean reactiveLimits) { @Override public void updateState(boolean reactiveLimits, boolean writeSlackBus, boolean distributedOnConformLoad, boolean loadPowerFactorConstant) { // update generator reactive power - updateGeneratorsState(voltageControlEnabled ? q.eval() * PerUnit.SB + loadTargetQ : generationTargetQ, reactiveLimits); + updateGeneratorsState(voltageControlEnabled ? q.eval() * PerUnit.SB + loadTargetQ : generationTargetQ, reactiveLimits); // update load power lfLoads.updateState(getLoadTargetP() - getInitialLoadTargetP(), loadPowerFactorConstant); diff --git a/src/test/java/com/powsybl/openloadflow/ac/AcLoadFlowEurostagTutorialExample1Test.java b/src/test/java/com/powsybl/openloadflow/ac/AcLoadFlowEurostagTutorialExample1Test.java index ca522566be..6501607318 100644 --- a/src/test/java/com/powsybl/openloadflow/ac/AcLoadFlowEurostagTutorialExample1Test.java +++ b/src/test/java/com/powsybl/openloadflow/ac/AcLoadFlowEurostagTutorialExample1Test.java @@ -409,4 +409,28 @@ void testWithDisconnectedGenerator() { assertActivePowerEquals(Double.NaN, gen.getTerminal()); assertReactivePowerEquals(Double.NaN, gen.getTerminal()); } + + @Test + void testGeneratorReactiveLimits() { + Network network = EurostagTutorialExample1Factory.create(); + network.getGenerator("GEN").newMinMaxReactiveLimits().setMinQ(0).setMaxQ(120).add(); + network.getVoltageLevel("VLGEN").newGenerator().setId("GEN1") + .setBus("NGEN").setConnectableBus("NGEN") + .setMinP(-9999.99D).setMaxP(9999.99D) + .setVoltageRegulatorOn(true).setTargetV(24.5D) + .setTargetP(607.0D).setTargetQ(301.0D).add(); + network.getGenerator("GEN1").newMinMaxReactiveLimits().setMinQ(0).setMaxQ(160).add(); + LoadFlowParameters parameters = new LoadFlowParameters().setNoGeneratorReactiveLimits(false) + .setDistributedSlack(false) + .setVoltageInitMode(LoadFlowParameters.VoltageInitMode.DC_VALUES); + loadFlowRunner.run(network, parameters); + network.getGenerators().forEach(gen -> { + if (gen.getReactiveLimits() instanceof MinMaxReactiveLimits) { + assertTrue(-gen.getTerminal().getQ() <= ((MinMaxReactiveLimits) gen.getReactiveLimits()).getMaxQ()); + assertTrue(-gen.getTerminal().getQ() >= ((MinMaxReactiveLimits) gen.getReactiveLimits()).getMinQ()); + } + }); + assertEquals(-120, network.getGenerator("GEN").getTerminal().getQ()); + assertEquals(-160, network.getGenerator("GEN1").getTerminal().getQ(), 0.01); + } } diff --git a/src/test/java/com/powsybl/openloadflow/network/impl/LfBusImplTest.java b/src/test/java/com/powsybl/openloadflow/network/impl/LfBusImplTest.java index ea2c88e308..57ee5b5e98 100644 --- a/src/test/java/com/powsybl/openloadflow/network/impl/LfBusImplTest.java +++ b/src/test/java/com/powsybl/openloadflow/network/impl/LfBusImplTest.java @@ -9,6 +9,7 @@ import com.powsybl.iidm.network.*; import com.powsybl.iidm.network.extensions.VoltagePerReactivePowerControlAdder; import com.powsybl.iidm.network.test.EurostagTutorialExample1Factory; +import com.powsybl.iidm.network.test.FourSubstationsNodeBreakerFactory; import com.powsybl.openloadflow.network.LfGenerator; import com.powsybl.openloadflow.network.LfNetwork; import com.powsybl.openloadflow.network.MostMeshedSlackBusSelector; @@ -17,6 +18,8 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import static com.powsybl.openloadflow.util.LoadFlowAssert.DELTA_POWER; @@ -142,4 +145,76 @@ void updateGeneratorsStateTest() { } Assertions.assertEquals(generationQ, sumQ, DELTA_POWER, "sum of generators calculatedQ should be equals to qToDispatch"); } + + private List createLfGeneratorsWithInitQ(List initQs) { + Network network = FourSubstationsNodeBreakerFactory.create(); + LfNetworkLoadingReport lfNetworkLoadingReport = new LfNetworkLoadingReport(); + LfGenerator lfGenerator1 = LfGeneratorImpl.create(network.getGenerator("GH1"), + false, 100, true, lfNetworkLoadingReport); + lfGenerator1.setCalculatedQ(initQs.get(0)); + LfGenerator lfGenerator2 = LfGeneratorImpl.create(network.getGenerator("GH2"), + false, 200, true, lfNetworkLoadingReport); + lfGenerator2.setCalculatedQ(initQs.get(1)); + LfGenerator lfGenerator3 = LfGeneratorImpl.create(network.getGenerator("GH3"), + false, 200, true, lfNetworkLoadingReport); + lfGenerator3.setCalculatedQ(initQs.get(2)); + List generators = new ArrayList<>(); + generators.add(lfGenerator1); + generators.add(lfGenerator2); + generators.add(lfGenerator3); + return generators; + } + + @Test + void dispatchQForMaxTest() { + List generators = createLfGeneratorsWithInitQ(Arrays.asList(0d, 0d, 0d)); + LfGenerator generatorToRemove = generators.get(1); + double qToDispatch = 21; + double residueQ = AbstractLfBus.dispatchQ(generators, true, qToDispatch); + double totalCalculatedQ = generators.get(0).getCalculatedQ() + generators.get(1).getCalculatedQ() + generatorToRemove.getCalculatedQ(); + Assertions.assertEquals(7.0, generators.get(0).getCalculatedQ()); + Assertions.assertEquals(7.0, generators.get(1).getCalculatedQ()); + Assertions.assertEquals(2, generators.size()); + Assertions.assertEquals(qToDispatch - totalCalculatedQ, residueQ, 0.00001); + Assertions.assertEquals(generatorToRemove.getMaxQ(), generatorToRemove.getCalculatedQ()); + } + + @Test + void dispatchQTestWithInitialQForMax() { + List generators = createLfGeneratorsWithInitQ(Arrays.asList(1.5d, 1d, 3d)); + double qInitial = generators.get(0).getCalculatedQ() + generators.get(1).getCalculatedQ() + generators.get(2).getCalculatedQ(); + LfGenerator generatorToRemove1 = generators.get(1); + LfGenerator generatorToRemove2 = generators.get(2); + double qToDispatch = 20; + double residueQ = AbstractLfBus.dispatchQ(generators, true, qToDispatch); + double totalCalculatedQ = generators.get(0).getCalculatedQ() + generatorToRemove1.getCalculatedQ() + generatorToRemove2.getCalculatedQ(); + Assertions.assertEquals(1, generators.size()); + Assertions.assertEquals(qToDispatch + qInitial - totalCalculatedQ, residueQ, 0.0001); + Assertions.assertEquals(8.17, generators.get(0).getCalculatedQ(), 0.01); + Assertions.assertEquals(generatorToRemove1.getMaxQ(), generatorToRemove1.getCalculatedQ(), 0.01); + Assertions.assertEquals(generatorToRemove2.getMaxQ(), generatorToRemove2.getCalculatedQ(), 0.01); + } + + @Test + void dispatchQForMinTest() { + List generators = createLfGeneratorsWithInitQ(Arrays.asList(0d, 0d, 0d)); + LfGenerator generatorToRemove2 = generators.get(1); + LfGenerator generatorToRemove3 = generators.get(2); + double qToDispatch = -21; + double residueQ = AbstractLfBus.dispatchQ(generators, true, qToDispatch); + double totalCalculatedQ = generators.get(0).getCalculatedQ() + generatorToRemove2.getCalculatedQ() + generatorToRemove3.getCalculatedQ(); + Assertions.assertEquals(-7.0, generators.get(0).getCalculatedQ()); + Assertions.assertEquals(1, generators.size()); + Assertions.assertEquals(qToDispatch - totalCalculatedQ, residueQ, 0.00001); + Assertions.assertEquals(generatorToRemove2.getMinQ(), generatorToRemove2.getCalculatedQ()); + Assertions.assertEquals(generatorToRemove3.getMinQ(), generatorToRemove3.getCalculatedQ()); + } + + @Test + void dispatchQEmptyListTest() { + List generators = new ArrayList<>(); + double qToDispatch = -21; + Assertions.assertThrows(IllegalArgumentException.class, () -> AbstractLfBus.dispatchQ(generators, true, qToDispatch), + "the generator list to dispatch Q can not be empty"); + } }