diff --git a/src/main/java/com/powsybl/openloadflow/OpenLoadFlowParameters.java b/src/main/java/com/powsybl/openloadflow/OpenLoadFlowParameters.java index edf63ea1d5..69a270835d 100644 --- a/src/main/java/com/powsybl/openloadflow/OpenLoadFlowParameters.java +++ b/src/main/java/com/powsybl/openloadflow/OpenLoadFlowParameters.java @@ -42,9 +42,11 @@ public enum LowImpedanceBranchMode { private final List additionalObservers = new ArrayList<>(); + private boolean loadPowerFactorConstant = LOAD_POWER_FACTOR_CONSTANT_DEFAULT_VALUE; + @Override public String getName() { - return "SimpleLoadFlowParameters"; + return "OpenLoadFlowParameters"; } public SlackBusSelector getSlackBusSelector() { @@ -87,6 +89,15 @@ public List getAdditionalObservers() { return additionalObservers; } + public boolean isLoadPowerFactorConstant() { + return loadPowerFactorConstant; + } + + public OpenLoadFlowParameters setLoadPowerFactorConstant(boolean loadPowerFactorConstant) { + this.loadPowerFactorConstant = loadPowerFactorConstant; + return this; + } + public static OpenLoadFlowParameters load() { return new OpenLoadFlowConfigLoader().load(PlatformConfig.defaultConfig()); } @@ -99,14 +110,15 @@ public OpenLoadFlowParameters load(PlatformConfig platformConfig) { OpenLoadFlowParameters parameters = new OpenLoadFlowParameters(); platformConfig.getOptionalModuleConfig("open-loadflow-default-parameters") - .ifPresent(config -> { - parameters.setSlackBusSelector(getSlackBusSelector(config)); - parameters.setLowImpedanceBranchMode(config.getEnumProperty(LOW_IMPEDANCE_BRANCH_MODE_PARAM_NAME, LowImpedanceBranchMode.class, LOW_IMPEDANCE_BRANCH_MODE_DEFAULT_VALUE)); - parameters.setVoltageRemoteControl(config.getBooleanProperty(VOLTAGE_REMOTE_CONTROLE_PARAM_NAME, VOLTAGE_REMOTE_CONTROLE_DEFAULT_VALUE)); - parameters.setThrowsExceptionInCaseOfSlackDistributionFailure( + .ifPresent(config -> parameters + .setSlackBusSelector(getSlackBusSelector(config)) + .setLowImpedanceBranchMode(config.getEnumProperty(LOW_IMPEDANCE_BRANCH_MODE_PARAM_NAME, LowImpedanceBranchMode.class, LOW_IMPEDANCE_BRANCH_MODE_DEFAULT_VALUE)) + .setVoltageRemoteControl(config.getBooleanProperty(VOLTAGE_REMOTE_CONTROLE_PARAM_NAME, VOLTAGE_REMOTE_CONTROLE_DEFAULT_VALUE)) + .setThrowsExceptionInCaseOfSlackDistributionFailure( config.getBooleanProperty(THROWS_EXCEPTION_IN_CASE_OF_SLACK_DISTRIBUTION_FAILURE_PARAM_NAME, THROWS_EXCEPTION_IN_CASE_OF_SLACK_DISTRIBUTION_FAILURE_DEFAULT_VALUE) - ); - }); + ) + .setLoadPowerFactorConstant(config.getBooleanProperty(LOAD_POWER_FACTOR_CONSTANT_PARAM_NAME, LOAD_POWER_FACTOR_CONSTANT_DEFAULT_VALUE)) + ); return parameters; } diff --git a/src/main/java/com/powsybl/openloadflow/OpenLoadFlowProvider.java b/src/main/java/com/powsybl/openloadflow/OpenLoadFlowProvider.java index 6d3681e0bf..df651dec07 100644 --- a/src/main/java/com/powsybl/openloadflow/OpenLoadFlowProvider.java +++ b/src/main/java/com/powsybl/openloadflow/OpenLoadFlowProvider.java @@ -132,6 +132,7 @@ public static AcLoadFlowParameters createAcParameters(Network network, MatrixFac LOGGER.info("Split shunt admittance: {}", parameters.isTwtSplitShuntAdmittance()); LOGGER.info("Direct current: {}", parameters.isDc()); LOGGER.info("Transformer voltage control: {}", parameters.isTransformerVoltageControlOn()); + LOGGER.info("Load power factor constant: {}", parametersExt.isLoadPowerFactorConstant()); List outerLoops = new ArrayList<>(); if (parameters.isDistributedSlack()) { @@ -140,10 +141,10 @@ public static AcLoadFlowParameters createAcParameters(Network network, MatrixFac outerLoops.add(new DistributedSlackOnGenerationOuterLoop(parametersExt.isThrowsExceptionInCaseOfSlackDistributionFailure())); break; case PROPORTIONAL_TO_LOAD: - outerLoops.add(new DistributedSlackOnLoadOuterLoop(parametersExt.isThrowsExceptionInCaseOfSlackDistributionFailure(), false)); + outerLoops.add(new DistributedSlackOnLoadOuterLoop(parametersExt.isThrowsExceptionInCaseOfSlackDistributionFailure(), false, parametersExt.isLoadPowerFactorConstant())); break; case PROPORTIONAL_TO_CONFORM_LOAD: - outerLoops.add(new DistributedSlackOnLoadOuterLoop(parametersExt.isThrowsExceptionInCaseOfSlackDistributionFailure(), true)); + outerLoops.add(new DistributedSlackOnLoadOuterLoop(parametersExt.isThrowsExceptionInCaseOfSlackDistributionFailure(), true, parametersExt.isLoadPowerFactorConstant())); break; case PROPORTIONAL_TO_GENERATION_P: // to be implemented. throw new UnsupportedOperationException("Unsupported balance type mode: " + parameters.getBalanceType()); diff --git a/src/main/java/com/powsybl/openloadflow/ac/DistributedSlackOnGenerationOuterLoop.java b/src/main/java/com/powsybl/openloadflow/ac/DistributedSlackOnGenerationOuterLoop.java index 0d6f7725bb..46eea101bb 100644 --- a/src/main/java/com/powsybl/openloadflow/ac/DistributedSlackOnGenerationOuterLoop.java +++ b/src/main/java/com/powsybl/openloadflow/ac/DistributedSlackOnGenerationOuterLoop.java @@ -81,11 +81,8 @@ public double run(List> participatingElements, } if (newTargetP != targetP) { - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Rescale '{}' active power target: {} -> {}", - generator.getId(), targetP * PerUnit.SB, newTargetP * PerUnit.SB); - } - + LOGGER.trace("Rescale '{}' active power target: {} -> {}", + generator.getId(), targetP * PerUnit.SB, newTargetP * PerUnit.SB); generator.setTargetP(newTargetP); done += newTargetP - targetP; modifiedBuses++; diff --git a/src/main/java/com/powsybl/openloadflow/ac/DistributedSlackOnLoadOuterLoop.java b/src/main/java/com/powsybl/openloadflow/ac/DistributedSlackOnLoadOuterLoop.java index 947dc7045f..18cea904d2 100644 --- a/src/main/java/com/powsybl/openloadflow/ac/DistributedSlackOnLoadOuterLoop.java +++ b/src/main/java/com/powsybl/openloadflow/ac/DistributedSlackOnLoadOuterLoop.java @@ -23,11 +23,13 @@ public class DistributedSlackOnLoadOuterLoop extends AbstractDistributedSlackOut private static final Logger LOGGER = LoggerFactory.getLogger(DistributedSlackOnLoadOuterLoop.class); - private boolean distributedSlackOnConformLoad = false; + private final boolean distributedSlackOnConformLoad; + private final boolean loadPowerFactorConstant; - public DistributedSlackOnLoadOuterLoop(boolean throwsExceptionInCaseOfFailure, boolean distributedSlackOnConformLoad) { + public DistributedSlackOnLoadOuterLoop(boolean throwsExceptionInCaseOfFailure, boolean distributedSlackOnConformLoad, boolean loadPowerFactorConstant) { super(throwsExceptionInCaseOfFailure); this.distributedSlackOnConformLoad = distributedSlackOnConformLoad; + this.loadPowerFactorConstant = loadPowerFactorConstant; } @Override @@ -38,10 +40,10 @@ public String getType() { @Override public List> getParticipatingElements(LfNetwork network) { return network.getBuses() - .stream() - .filter(bus -> bus.getPositiveLoadCount() > 0 && getVariableLoadTargetP(bus) > 0) - .map(bus -> new ParticipatingElement<>(bus, getVariableLoadTargetP(bus))) - .collect(Collectors.toList()); + .stream() + .filter(bus -> bus.getPositiveLoadCount() > 0 && getVariableLoadTargetP(bus) > 0) + .map(bus -> new ParticipatingElement<>(bus, getVariableLoadTargetP(bus))) + .collect(Collectors.toList()); } private double getVariableLoadTargetP(LfBus bus) { @@ -63,24 +65,27 @@ public double run(List> participatingElements, int i LfBus bus = participatingBus.element; double factor = participatingBus.factor; - double targetP = bus.getLoadTargetP(); - double newTargetP = targetP - remainingMismatch * factor; + double loadTargetP = bus.getLoadTargetP(); + double newLoadTargetP = loadTargetP - remainingMismatch * factor; // We stop when the load produces power. - if (newTargetP <= 0) { - newTargetP = 0; + double minLoadTargetP = distributedSlackOnConformLoad ? bus.getFixedLoadTargetP() : 0; + if (newLoadTargetP <= minLoadTargetP) { + newLoadTargetP = minLoadTargetP; loadsAtMin++; it.remove(); } - if (newTargetP != targetP) { - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Rescale '{}' active power target: {} -> {}", - bus.getId(), targetP * PerUnit.SB, newTargetP * PerUnit.SB); + if (newLoadTargetP != loadTargetP) { + LOGGER.trace("Rescale '{}' active power target: {} -> {}", + bus.getId(), loadTargetP * PerUnit.SB, newLoadTargetP * PerUnit.SB); + + if (loadPowerFactorConstant) { + ensurePowerFactorConstant(bus, newLoadTargetP); } - bus.setLoadTargetP(newTargetP); - done += targetP - newTargetP; + bus.setLoadTargetP(newLoadTargetP); + done += loadTargetP - newLoadTargetP; modifiedBuses++; } } @@ -90,4 +95,16 @@ public double run(List> participatingElements, int i return done; } + + private void ensurePowerFactorConstant(LfBus bus, double newLoadTargetP) { + // if loadPowerFactorConstant is true, when updating targetP on loads, + // we have to keep the power factor constant by updating targetQ. + double constantRatio = bus.getLoadTargetQ() / bus.getLoadTargetP(); // power factor constant is equivalent to P/Q ratio constant + double newLoadTargetQ = newLoadTargetP * constantRatio; + if (newLoadTargetQ != bus.getLoadTargetQ()) { + LOGGER.trace("Rescale '{}' reactive power target on load: {} -> {}", + bus.getId(), bus.getLoadTargetQ() * PerUnit.SB, newLoadTargetQ * PerUnit.SB); + bus.setLoadTargetQ(newLoadTargetQ); + } + } } diff --git a/src/main/java/com/powsybl/openloadflow/network/LfBus.java b/src/main/java/com/powsybl/openloadflow/network/LfBus.java index 940440e119..c911c5624f 100644 --- a/src/main/java/com/powsybl/openloadflow/network/LfBus.java +++ b/src/main/java/com/powsybl/openloadflow/network/LfBus.java @@ -61,6 +61,10 @@ public interface LfBus { double getLoadTargetQ(); + void setLoadTargetQ(double loadTargetQ); + + double getFixedLoadTargetQ(); + double getGenerationTargetP(); double getGenerationTargetQ(); 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 ec8bedc0f9..b9b6ebf668 100644 --- a/src/main/java/com/powsybl/openloadflow/network/impl/AbstractLfBus.java +++ b/src/main/java/com/powsybl/openloadflow/network/impl/AbstractLfBus.java @@ -53,8 +53,12 @@ public abstract class AbstractLfBus implements LfBus { protected int positiveLoadCount = 0; + protected double initialLoadTargetQ = 0; + protected double loadTargetQ = 0; + protected double fixedLoadTargetQ = 0; + protected double generationTargetQ = 0; protected double targetV = Double.NaN; @@ -218,12 +222,14 @@ private List getGeneratorIds() { void addLoad(Load load) { loads.add(load); initialLoadTargetP += load.getP0(); + initialLoadTargetQ += load.getQ0(); loadTargetP += load.getP0(); + loadTargetQ += load.getQ0(); LoadDetail loadDetail = load.getExtension(LoadDetail.class); if (loadDetail != null) { fixedLoadTargetP = loadDetail.getFixedActivePower(); + fixedLoadTargetQ = loadDetail.getFixedReactivePower(); } - loadTargetQ += load.getQ0(); if (load.getP0() >= 0) { positiveLoadCount++; } @@ -232,6 +238,7 @@ void addLoad(Load load) { void addBattery(Battery battery) { batteries.add(battery); initialLoadTargetP += battery.getP0(); + initialLoadTargetQ += battery.getQ0(); loadTargetP += battery.getP0(); loadTargetQ += battery.getQ0(); } @@ -343,6 +350,16 @@ public double getLoadTargetQ() { return loadTargetQ / PerUnit.SB; } + @Override + public void setLoadTargetQ(double loadTargetQ) { + this.loadTargetQ = loadTargetQ * PerUnit.SB; + } + + @Override + public double getFixedLoadTargetQ() { + return fixedLoadTargetQ / PerUnit.SB; + } + private double getLimitQ(ToDoubleFunction limitQ) { return generators.stream() .mapToDouble(generator -> generator.hasVoltageControl() ? limitQ.applyAsDouble(generator) @@ -461,11 +478,12 @@ public void updateState(boolean reactiveLimits, boolean writeSlackBus) { updateGeneratorsState(voltageControl ? calculatedQ + loadTargetQ : generationTargetQ, reactiveLimits); // update load power - double factor = initialLoadTargetP != 0 ? loadTargetP / initialLoadTargetP : 1; + double factorP = initialLoadTargetP != 0 ? loadTargetP / initialLoadTargetP : 1; + double factorQ = initialLoadTargetQ != 0 ? loadTargetQ / initialLoadTargetQ : 1; for (Load load : loads) { load.getTerminal() - .setP(load.getP0() >= 0 ? factor * load.getP0() : load.getP0()) - .setQ(load.getQ0()); + .setP(load.getP0() >= 0 ? factorP * load.getP0() : load.getP0()) + .setQ(load.getQ0() >= 0 ? factorQ * load.getQ0() : load.getQ0()); } // update battery power (which are not part of slack distribution) diff --git a/src/main/java/com/powsybl/openloadflow/sa/OpenSecurityAnalysis.java b/src/main/java/com/powsybl/openloadflow/sa/OpenSecurityAnalysis.java index c0f67b621e..4a2be3d3eb 100644 --- a/src/main/java/com/powsybl/openloadflow/sa/OpenSecurityAnalysis.java +++ b/src/main/java/com/powsybl/openloadflow/sa/OpenSecurityAnalysis.java @@ -297,7 +297,8 @@ private void distributedMismatch(LfNetwork network, double mismatch, LoadFlowPar case PROPORTIONAL_TO_LOAD: case PROPORTIONAL_TO_CONFORM_LOAD: DistributedSlackOnLoadOuterLoop onLoadOuterLoop = new DistributedSlackOnLoadOuterLoop(openLoadFlowParameters.isThrowsExceptionInCaseOfSlackDistributionFailure(), - loadFlowParameters.getBalanceType() == LoadFlowParameters.BalanceType.PROPORTIONAL_TO_CONFORM_LOAD); + loadFlowParameters.getBalanceType() == LoadFlowParameters.BalanceType.PROPORTIONAL_TO_CONFORM_LOAD, + openLoadFlowParameters.isLoadPowerFactorConstant()); onLoadOuterLoop.run(onLoadOuterLoop.getParticipatingElements(network), -1, mismatch); break; case PROPORTIONAL_TO_GENERATION_P: @@ -486,6 +487,7 @@ private void setBusState(LfBus bus, BusState busState, AcloadFlowEngine engine) bus.setV(busState.v); bus.setAngle(busState.angle); bus.setLoadTargetP(busState.loadTargetP); + bus.setLoadTargetQ(busState.loadTargetQ); bus.getGenerators().forEach(g -> g.setTargetP(busState.generatorsTargetP.get(g.getId()))); if (busState.hasVoltageControl && !bus.hasVoltageControl()) { // b is now PQ bus. ReactiveLimitsOuterLoop.switchPqPv(bus, engine.getEquationSystem(), engine.getVariableSet()); @@ -500,6 +502,7 @@ private static class BusState { private final double v; private final double angle; private final double loadTargetP; + private final double loadTargetQ; private final Map generatorsTargetP; private final boolean hasVoltageControl; private final double generationTargetQ; @@ -508,6 +511,7 @@ public BusState(LfBus b) { this.v = b.getV(); this.angle = b.getAngle(); this.loadTargetP = b.getLoadTargetP(); + this.loadTargetQ = b.getLoadTargetQ(); this.generatorsTargetP = b.getGenerators().stream().collect(Collectors.toMap(LfGenerator::getId, LfGenerator::getTargetP)); this.hasVoltageControl = b.hasVoltageControl(); this.generationTargetQ = b.getGenerationTargetQ(); diff --git a/src/main/java/com/powsybl/openloadflow/util/ParameterConstants.java b/src/main/java/com/powsybl/openloadflow/util/ParameterConstants.java index 2166c2ff2d..901657b39f 100644 --- a/src/main/java/com/powsybl/openloadflow/util/ParameterConstants.java +++ b/src/main/java/com/powsybl/openloadflow/util/ParameterConstants.java @@ -28,6 +28,9 @@ public final class ParameterConstants { public static final String LOW_IMPEDANCE_BRANCH_MODE_PARAM_NAME = "lowImpedanceBranchMode"; public static final LowImpedanceBranchMode LOW_IMPEDANCE_BRANCH_MODE_DEFAULT_VALUE = LowImpedanceBranchMode.REPLACE_BY_ZERO_IMPEDANCE_LINE; + public static final String LOAD_POWER_FACTOR_CONSTANT_PARAM_NAME = "loadPowerFactorConstant"; + public static final boolean LOAD_POWER_FACTOR_CONSTANT_DEFAULT_VALUE = false; + private ParameterConstants() { } } diff --git a/src/test/java/com/powsybl/openloadflow/ac/DistributedSlackOnLoadTest.java b/src/test/java/com/powsybl/openloadflow/ac/DistributedSlackOnLoadTest.java index e3af3a5801..b027085f1a 100644 --- a/src/test/java/com/powsybl/openloadflow/ac/DistributedSlackOnLoadTest.java +++ b/src/test/java/com/powsybl/openloadflow/ac/DistributedSlackOnLoadTest.java @@ -6,9 +6,15 @@ */ package com.powsybl.openloadflow.ac; +import static com.powsybl.openloadflow.util.LoadFlowAssert.assertActivePowerEquals; +import static com.powsybl.openloadflow.util.LoadFlowAssert.assertLoadFlowResultsEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + import com.powsybl.iidm.network.Load; import com.powsybl.iidm.network.Network; import com.powsybl.iidm.network.extensions.LoadDetailAdder; +import com.powsybl.iidm.network.test.EurostagTutorialExample1Factory; import com.powsybl.loadflow.LoadFlow; import com.powsybl.loadflow.LoadFlowParameters; import com.powsybl.loadflow.LoadFlowResult; @@ -17,11 +23,10 @@ import com.powsybl.openloadflow.OpenLoadFlowProvider; import com.powsybl.openloadflow.network.DistributedSlackNetworkFactory; import com.powsybl.openloadflow.network.MostMeshedSlackBusSelector; +import com.powsybl.openloadflow.util.LoadFlowResultBuilder; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import static com.powsybl.openloadflow.util.LoadFlowAssert.assertActivePowerEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; /** * @author Anne Tilloy @@ -37,6 +42,9 @@ class DistributedSlackOnLoadTest { private Load l6; private LoadFlow.Runner loadFlowRunner; private LoadFlowParameters parameters; + private OpenLoadFlowParameters parametersExt; + + public static final double DELTA_MISMATCH = 1E-4d; @BeforeEach void setUp() { @@ -49,8 +57,8 @@ void setUp() { l6 = network.getLoad("l6"); loadFlowRunner = new LoadFlow.Runner(new OpenLoadFlowProvider(new DenseMatrixFactory())); parameters = new LoadFlowParameters().setDistributedSlack(true) - .setBalanceType(LoadFlowParameters.BalanceType.PROPORTIONAL_TO_LOAD); - OpenLoadFlowParameters parametersExt = new OpenLoadFlowParameters() + .setBalanceType(LoadFlowParameters.BalanceType.PROPORTIONAL_TO_LOAD); + parametersExt = new OpenLoadFlowParameters() .setSlackBusSelector(new MostMeshedSlackBusSelector()); parameters.addExtension(OpenLoadFlowParameters.class, parametersExt); } @@ -65,6 +73,11 @@ void test() { assertActivePowerEquals(175, l4.getTerminal()); assertActivePowerEquals(12.5, l5.getTerminal()); assertActivePowerEquals(-50, l6.getTerminal()); // same as p0 because p0 < 0 + LoadFlowResult loadFlowResultExpected = new LoadFlowResultBuilder(true) + .addMetrics("3", "CONVERGED") + .addComponentResult(0, LoadFlowResult.ComponentResult.Status.CONVERGED, 3, "b4_vl_0", 1.6895598253796607E-7) + .build(); + assertLoadFlowResultsEquals(loadFlowResultExpected, result); } @Test @@ -82,5 +95,62 @@ void testWithLoadDetail() { assertActivePowerEquals(178.182, l4.getTerminal()); assertActivePowerEquals(12.727, l5.getTerminal()); assertActivePowerEquals(-50, l6.getTerminal()); // same as p0 because p0 < 0 + LoadFlowResult loadFlowResultExpected = new LoadFlowResultBuilder(true) + .addMetrics("3", "CONVERGED") + .addComponentResult(0, LoadFlowResult.ComponentResult.Status.CONVERGED, 3, "b4_vl_0", 9.726437433243973E-8) + .build(); + assertLoadFlowResultsEquals(loadFlowResultExpected, result); + } + + private void assertPowerFactor(Network network) { + switch (parameters.getBalanceType()) { + case PROPORTIONAL_TO_CONFORM_LOAD: + case PROPORTIONAL_TO_LOAD: + for (Load load : network.getLoads()) { + assertEquals(load.getP0() / load.getQ0(), + load.getTerminal().getP() / load.getTerminal().getQ(), + DELTA_MISMATCH, "Power factor should be a constant value"); + } + break; + default: + break; + } + } + + @Test + void testPowerFactorConstant() { + // PROPORTIONAL_TO_LOAD and power factor constant for loads + parameters.setBalanceType(LoadFlowParameters.BalanceType.PROPORTIONAL_TO_LOAD); + parametersExt.setLoadPowerFactorConstant(true); + Network network1 = EurostagTutorialExample1Factory.create(); + + LoadFlowResult loadFlowResult1 = loadFlowRunner.run(network1, parameters); + + assertPowerFactor(network1); + LoadFlowResult loadFlowResultExpected1 = new LoadFlowResultBuilder(true) + .addMetrics("5", "CONVERGED") + .addComponentResult(0, LoadFlowResult.ComponentResult.Status.CONVERGED, 5, "VLHV1_0", -3.06844963660069E-5) + .build(); + assertLoadFlowResultsEquals(loadFlowResultExpected1, loadFlowResult1); + + // PROPORTIONAL_TO_CONFORM_LOAD and power factor constant for loads + parameters.setBalanceType(LoadFlowParameters.BalanceType.PROPORTIONAL_TO_CONFORM_LOAD); + parametersExt.setLoadPowerFactorConstant(true); + Network network2 = EurostagTutorialExample1Factory.create(); + // fixedActivePower and FixedReactivePower are unbalanced + network2.getLoad("LOAD").newExtension(LoadDetailAdder.class) + .withFixedActivePower(500).withVariableActivePower(100) + .withFixedReactivePower(150).withVariableReactivePower(50) + .add(); + + //when + LoadFlowResult loadFlowResult2 = loadFlowRunner.run(network2, parameters); + + // then + assertPowerFactor(network2); + LoadFlowResult loadFlowResultExpected2 = new LoadFlowResultBuilder(true).addMetrics("5", "CONVERGED") + .addComponentResult(0, LoadFlowResult.ComponentResult.Status.CONVERGED, 5, "VLHV1_0", 1.340823176931849E-5) + .build(); + assertLoadFlowResultsEquals(loadFlowResultExpected2, loadFlowResult2); } } diff --git a/src/test/java/com/powsybl/openloadflow/util/LoadFlowAssert.java b/src/test/java/com/powsybl/openloadflow/util/LoadFlowAssert.java index bfc98f8dc6..94205d1991 100644 --- a/src/test/java/com/powsybl/openloadflow/util/LoadFlowAssert.java +++ b/src/test/java/com/powsybl/openloadflow/util/LoadFlowAssert.java @@ -6,11 +6,14 @@ */ package com.powsybl.openloadflow.util; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + import com.powsybl.iidm.network.Bus; import com.powsybl.iidm.network.Terminal; +import com.powsybl.loadflow.LoadFlowResult; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; +import java.util.Iterator; /** * @author Geoffroy Jamgotchian @@ -20,6 +23,7 @@ public final class LoadFlowAssert { public static final double DELTA_ANGLE = 1E-6d; public static final double DELTA_V = 1E-3d; public static final double DELTA_POWER = 1E-3d; + public static final double DELTA_MISMATCH = 1E-4d; private LoadFlowAssert() { } @@ -47,4 +51,28 @@ public static void assertUndefinedActivePower(Terminal terminal) { public static void assertUndefinedReactivePower(Terminal terminal) { assertTrue(Double.isNaN(terminal.getQ())); } + + public static void assertLoadFlowResultsEquals(LoadFlowResult loadFlowResultExpected, LoadFlowResult loadFlowResult) { + assertEquals(loadFlowResultExpected.isOk(), loadFlowResult.isOk(), + "Wrong load flow result summary"); + assertEquals(loadFlowResultExpected.getComponentResults().size(), + loadFlowResult.getComponentResults().size(), + "Wrong sub network count"); + Iterator componentResultIteratorExpected = loadFlowResultExpected.getComponentResults().iterator(); + Iterator componentResultIterator = loadFlowResult.getComponentResults().iterator(); + // loop over sub networks + while (componentResultIteratorExpected.hasNext()) { + LoadFlowResult.ComponentResult componentResultExpected = componentResultIteratorExpected.next(); + LoadFlowResult.ComponentResult componentResult = componentResultIterator.next(); + assertEquals(componentResultExpected.getStatus(), + componentResult.getStatus(), + "Wrong load flow result status"); + assertEquals(componentResultExpected.getIterationCount(), + componentResult.getIterationCount(), + "Wrong iteration count"); + assertEquals(componentResultExpected.getSlackBusActivePowerMismatch(), + componentResult.getSlackBusActivePowerMismatch(), DELTA_MISMATCH, + "Wrong active power mismatch"); + } + } } diff --git a/src/test/java/com/powsybl/openloadflow/util/LoadFlowResultBuilder.java b/src/test/java/com/powsybl/openloadflow/util/LoadFlowResultBuilder.java new file mode 100644 index 0000000000..99eef15436 --- /dev/null +++ b/src/test/java/com/powsybl/openloadflow/util/LoadFlowResultBuilder.java @@ -0,0 +1,53 @@ +/** + * Copyright (c) 2020, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package com.powsybl.openloadflow.util; + +import com.powsybl.loadflow.LoadFlowResult; +import com.powsybl.loadflow.LoadFlowResultImpl; +import java.security.InvalidParameterException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @author Fabien Rigaux + */ +public class LoadFlowResultBuilder { + private Map metrics = new HashMap<>(); + private Boolean ok; + private String logs; + private List componentResults = new ArrayList<>(); + + public LoadFlowResultBuilder(Boolean ok) { + this.ok = ok; + } + + public LoadFlowResultBuilder addMetrics(String networkIterations, String networkStatus) { + int networkNumber = this.metrics.size() / 2; + this.metrics.put("network_" + networkNumber + "_iterations", networkIterations); + this.metrics.put("network_" + networkNumber + "_status", networkStatus); + return this; + } + + public LoadFlowResultBuilder setLogs(String logs) { + this.logs = logs; + return this; + } + + public LoadFlowResultBuilder addComponentResult(int componentNum, LoadFlowResult.ComponentResult.Status status, int iterationCount, String slackBusId, double slackBusActivePowerMismatch) { + this.componentResults.add(new LoadFlowResultImpl.ComponentResultImpl(componentNum, status, iterationCount, slackBusId, slackBusActivePowerMismatch)); + return this; + } + + public LoadFlowResult build() throws InvalidParameterException { + if (metrics.isEmpty() || ok == null || componentResults.isEmpty()) { + throw new InvalidParameterException("cannot build with given information"); + } + return new LoadFlowResultImpl(ok, metrics, logs, componentResults); + } +}