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 75944fb293..2bfcbe4a90 100644 --- a/src/main/java/com/powsybl/openloadflow/ac/outerloop/DistributedSlackOuterLoop.java +++ b/src/main/java/com/powsybl/openloadflow/ac/outerloop/DistributedSlackOuterLoop.java @@ -80,7 +80,7 @@ public OuterLoopResult check(AcOuterLoopContext context, ReportNode reportNode) if (Math.abs(remainingMismatch) > ActivePowerDistribution.P_RESIDUE_EPS) { Reports.reportMismatchDistributionFailure(iterationReportNode, remainingMismatch * PerUnit.SB); } else { - reportAndLogSuccess(iterationReportNode, slackBusActivePowerMismatch, resultAfterBh); + ActivePowerDistribution.reportAndLogSuccess(iterationReportNode, slackBusActivePowerMismatch, resultAfterBh); } DistributedSlackContextData contextData = (DistributedSlackContextData) context.getData(); contextData.addDistributedActivePower(distributedActivePower); @@ -97,10 +97,4 @@ public double getSlackBusActivePowerMismatch(AcOuterLoopContext context) { return context.getLastSolverResult().getSlackBusActivePowerMismatch(); } - private static void reportAndLogSuccess(ReportNode reportNode, double slackBusActivePowerMismatch, ActivePowerDistribution.ResultAfterFailureBehaviorHandling result) { - Reports.reportMismatchDistributionSuccess(reportNode, slackBusActivePowerMismatch * PerUnit.SB, result.iteration()); - - LOGGER.info("Slack bus active power ({} MW) distributed in {} distribution iteration(s)", - slackBusActivePowerMismatch * PerUnit.SB, result.iteration()); - } } diff --git a/src/main/java/com/powsybl/openloadflow/dc/DcLoadFlowEngine.java b/src/main/java/com/powsybl/openloadflow/dc/DcLoadFlowEngine.java index 638c8edcb8..47f0001013 100644 --- a/src/main/java/com/powsybl/openloadflow/dc/DcLoadFlowEngine.java +++ b/src/main/java/com/powsybl/openloadflow/dc/DcLoadFlowEngine.java @@ -8,10 +8,10 @@ package com.powsybl.openloadflow.dc; import com.google.common.collect.Lists; -import com.powsybl.commons.PowsyblException; import com.powsybl.commons.report.ReportNode; import com.powsybl.loadflow.LoadFlowParameters; import com.powsybl.math.matrix.MatrixException; +import com.powsybl.openloadflow.OpenLoadFlowParameters; import com.powsybl.openloadflow.dc.equations.DcEquationType; import com.powsybl.openloadflow.dc.equations.DcVariableType; import com.powsybl.openloadflow.equations.*; @@ -25,6 +25,7 @@ import com.powsybl.openloadflow.network.util.ActivePowerDistribution; import com.powsybl.openloadflow.network.util.UniformValueVoltageInitializer; import com.powsybl.openloadflow.network.util.VoltageInitializer; +import com.powsybl.openloadflow.util.PerUnit; import com.powsybl.openloadflow.util.Reports; import org.apache.commons.lang3.tuple.Pair; import org.slf4j.Logger; @@ -163,34 +164,6 @@ private void runOuterLoop(DcOuterLoop outerLoop, DcOuterLoopContext outerLoopCon } } - private double handleDistributionBehaviour(DcLoadFlowContext context, double distributedActivePower) { - double remainingMismatch = getActivePowerMismatch(context.getNetwork().getBuses()); - if (remainingMismatch > context.getParameters().getSlackBusPMaxMismatch()) { - switch (context.getParameters().getSlackDistributionFailureBehavior()) { - case FAIL -> { - LOGGER.error("DC loadflow failed to distribute slack bus active power on network {}", context.getNetwork()); - // TODO : how to return the failure in the DCLoadFlow result since it is not in an OuterLoop ? - return distributedActivePower; - } - case THROW -> throw new PowsyblException("DC loadflow failed to distribute slack bus active power on network"); - case DISTRIBUTE_ON_REFERENCE_GENERATOR -> { - if (!context.getParameters().isAreaInterchangeControl()) { - LfGenerator referenceGenerator = context.getNetwork().getReferenceGenerator(); - Objects.requireNonNull(referenceGenerator, () -> "No reference generator in " + context.getNetwork()); - referenceGenerator.setTargetP(referenceGenerator.getTargetP() + remainingMismatch); - LOGGER.warn("Could not distribute slack bus active power, remaining mismatch {} is redistributed to reference generator {}", remainingMismatch, referenceGenerator.getId()); - return distributedActivePower + remainingMismatch; - } else { - // If AreaInterchangeControl is activated, do not distribute in reference generator and behave like fail case - LOGGER.error("DC loadflow failed to distribute slack bus active power on network {}", context.getNetwork()); - return distributedActivePower; - } - } - } - } - return distributedActivePower; - } - public static boolean solve(double[] targetVectorArray, JacobianMatrix jacobianMatrix, ReportNode reportNode) { @@ -235,8 +208,41 @@ public DcLoadFlowResult run() { double initialSlackBusActivePowerMismatch = getActivePowerMismatch(network.getBuses()); double distributedActivePower = 0.0; if (parameters.isDistributedSlack() || parameters.isAreaInterchangeControl()) { - distributedActivePower = distributeSlack(network, network.getBuses(), parameters.getBalanceType(), parameters.getNetworkParameters().isUseActiveLimits()); - distributedActivePower = handleDistributionBehaviour(context, distributedActivePower); + LoadFlowParameters.BalanceType balanceType = parameters.getBalanceType(); + boolean useActiveLimits = parameters.getNetworkParameters().isUseActiveLimits(); + ActivePowerDistribution activePowerDistribution = ActivePowerDistribution.create(balanceType, false, useActiveLimits); + var result = activePowerDistribution.run(network, initialSlackBusActivePowerMismatch); + final LfGenerator referenceGenerator; + final OpenLoadFlowParameters.SlackDistributionFailureBehavior behavior; + if (parameters.isAreaInterchangeControl()) { + // actual behavior will be handled by the outerloop itself, just leave on slack bus here + behavior = OpenLoadFlowParameters.SlackDistributionFailureBehavior.LEAVE_ON_SLACK_BUS; + referenceGenerator = null; + } else { + behavior = parameters.getSlackDistributionFailureBehavior(); + referenceGenerator = context.getNetwork().getReferenceGenerator(); + } + ActivePowerDistribution.ResultAfterFailureBehaviorHandling resultAfterBh = ActivePowerDistribution.handleDistributionFailureBehavior( + behavior, + referenceGenerator, + initialSlackBusActivePowerMismatch, + result, + "Failed to distribute slack bus active power mismatch, %.2f MW remains" + ); + double remainingMismatch = resultAfterBh.remainingMismatch(); + distributedActivePower = initialSlackBusActivePowerMismatch - remainingMismatch; + distributedActivePower += resultAfterBh.additionalDistributedActivePower(); + if (Math.abs(remainingMismatch) > ActivePowerDistribution.P_RESIDUE_EPS) { + Reports.reportMismatchDistributionFailure(reportNode, remainingMismatch * PerUnit.SB); + } else { + ActivePowerDistribution.reportAndLogSuccess(reportNode, initialSlackBusActivePowerMismatch, resultAfterBh); + } + if (resultAfterBh.failed()) { + runningContext.lastSolverSuccess = false; + runningContext.lastOuterLoopResult = new OuterLoopResult("DistributedSlack", OuterLoopStatus.FAILED, resultAfterBh.failedMessage()); + Reports.reportDcLfComplete(reportNode, runningContext.lastSolverSuccess, runningContext.lastOuterLoopResult.status().name()); + return buildDcLoadFlowResult(network, runningContext, initialSlackBusActivePowerMismatch, distributedActivePower); + } } // we need to copy the target array because JacobianMatrix.solveTransposed take as an input the second member 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 c23f381ad9..3d057630eb 100644 --- a/src/main/java/com/powsybl/openloadflow/network/util/ActivePowerDistribution.java +++ b/src/main/java/com/powsybl/openloadflow/network/util/ActivePowerDistribution.java @@ -8,12 +8,14 @@ package com.powsybl.openloadflow.network.util; import com.powsybl.commons.PowsyblException; +import com.powsybl.commons.report.ReportNode; import com.powsybl.loadflow.LoadFlowParameters; import com.powsybl.openloadflow.OpenLoadFlowParameters; import com.powsybl.openloadflow.network.LfBus; import com.powsybl.openloadflow.network.LfGenerator; import com.powsybl.openloadflow.network.LfNetwork; import com.powsybl.openloadflow.util.PerUnit; +import com.powsybl.openloadflow.util.Reports; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -164,4 +166,11 @@ public static ResultAfterFailureBehaviorHandling handleDistributionFailureBehavi return resultAfterFailureBehaviorHandling; } + + public static void reportAndLogSuccess(ReportNode reportNode, double slackBusActivePowerMismatch, ResultAfterFailureBehaviorHandling result) { + Reports.reportMismatchDistributionSuccess(reportNode, slackBusActivePowerMismatch * PerUnit.SB, result.iteration()); + + LOGGER.info("Slack bus active power ({} MW) distributed in {} distribution iteration(s)", + slackBusActivePowerMismatch * PerUnit.SB, result.iteration()); + } } diff --git a/src/test/java/com/powsybl/openloadflow/dc/DcLoadFlowTest.java b/src/test/java/com/powsybl/openloadflow/dc/DcLoadFlowTest.java index 8f70f96f2b..dc8236e648 100644 --- a/src/test/java/com/powsybl/openloadflow/dc/DcLoadFlowTest.java +++ b/src/test/java/com/powsybl/openloadflow/dc/DcLoadFlowTest.java @@ -116,6 +116,33 @@ void tuto1Test() { assertEquals(-450, line2.getTerminal2().getP(), 0.01); } + @Test + void testSlackDistributionEnabledResults() { + Network network = EurostagFactory.fix(EurostagTutorialExample1Factory.create()); + + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + var componentResults = result.getComponentResults(); + assertEquals(1, componentResults.size()); + assertEquals(1, componentResults.get(0).getSlackBusResults().size()); + assertEquals(-7.0, componentResults.get(0).getDistributedActivePower(), 1e-3); + assertEquals(0.0, componentResults.get(0).getSlackBusResults().get(0).getActivePowerMismatch(), 1e-3); + } + + @Test + void testSlackDistributionDisabledResults() { + Network network = EurostagFactory.fix(EurostagTutorialExample1Factory.create()); + + parameters.setDistributedSlack(false); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + var componentResults = result.getComponentResults(); + assertEquals(1, componentResults.size()); + assertEquals(1, componentResults.get(0).getSlackBusResults().size()); + assertEquals(0.0, componentResults.get(0).getDistributedActivePower(), 1e-3); + assertEquals(-7.0, componentResults.get(0).getSlackBusResults().get(0).getActivePowerMismatch(), 1e-3); + } + @Test void fourBusesTest() { Network network = FourBusNetworkFactory.create(); @@ -470,16 +497,26 @@ void testDcSlackDistributionFailureBehavior() { parameters.setBalanceType(LoadFlowParameters.BalanceType.PROPORTIONAL_TO_GENERATION_P); Generator referenceGenerator = network.getGenerator("B1-G"); - parametersExt.setSlackDistributionFailureBehavior(OpenLoadFlowParameters.SlackDistributionFailureBehavior.FAIL); + parametersExt.setSlackDistributionFailureBehavior(OpenLoadFlowParameters.SlackDistributionFailureBehavior.LEAVE_ON_SLACK_BUS); var result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertEquals(1, result.getComponentResults().size()); + assertEquals(LoadFlowResult.ComponentResult.Status.CONVERGED, result.getComponentResults().get(0).getStatus()); + assertEquals(321.9, result.getComponentResults().get(0).getSlackBusResults().get(0).getActivePowerMismatch(), 0.01); + assertEquals(0, result.getComponentResults().get(0).getDistributedActivePower(), 0.01); + + parametersExt.setSlackDistributionFailureBehavior(OpenLoadFlowParameters.SlackDistributionFailureBehavior.FAIL); + result = loadFlowRunner.run(network, parameters); + assertFalse(result.isFullyConverged()); + assertEquals(1, result.getComponentResults().size()); + assertEquals(LoadFlowResult.ComponentResult.Status.FAILED, result.getComponentResults().get(0).getStatus()); + assertEquals("Outer loop failed: Failed to distribute slack bus active power mismatch, 321.90 MW remains", result.getComponentResults().get(0).getStatusText()); assertEquals(321.9, result.getComponentResults().get(0).getSlackBusResults().get(0).getActivePowerMismatch(), 0.01); assertEquals(0, result.getComponentResults().get(0).getDistributedActivePower(), 0.01); - assertActivePowerEquals(-128.9, referenceGenerator.getTerminal()); - // TODO : receive failure parametersExt.setSlackDistributionFailureBehavior(OpenLoadFlowParameters.SlackDistributionFailureBehavior.THROW); CompletionException e = assertThrows(CompletionException.class, () -> loadFlowRunner.run(network, parameters)); - assertEquals("DC loadflow failed to distribute slack bus active power on network", e.getCause().getMessage()); + assertEquals("Failed to distribute slack bus active power mismatch, 321.90 MW remains", e.getCause().getMessage()); parametersExt.setSlackDistributionFailureBehavior(OpenLoadFlowParameters.SlackDistributionFailureBehavior.DISTRIBUTE_ON_REFERENCE_GENERATOR); parametersExt.setReferenceBusSelectionMode(ReferenceBusSelectionMode.GENERATOR_REFERENCE_PRIORITY); diff --git a/src/test/resources/multipleConnectedComponentsDcReport.txt b/src/test/resources/multipleConnectedComponentsDcReport.txt index 4096a91606..c75d627731 100644 --- a/src/test/resources/multipleConnectedComponentsDcReport.txt +++ b/src/test/resources/multipleConnectedComponentsDcReport.txt @@ -6,6 +6,7 @@ Network balance: active generation=3.0 MW, active load=2.0 MW, reactive generation=0.0 MVar, reactive load=0.0 MVar Angle reference bus: b1_vl_0 Slack bus: b1_vl_0 + Slack bus active power (-0.9999999999999997 MW) distributed in 1 distribution iteration(s) DC load flow completed (solverSuccess=true, outerloopStatus=STABLE) + Network CC1 SC1 + Network info @@ -13,5 +14,6 @@ Network balance: active generation=2.0 MW, active load=4.0 MW, reactive generation=0.0 MVar, reactive load=0.0 MVar Angle reference bus: b5_vl_0 Slack bus: b5_vl_0 + Slack bus active power (2.0 MW) distributed in 1 distribution iteration(s) DC load flow completed (solverSuccess=true, outerloopStatus=STABLE) No calculation will be done on 1 network(s) that have no generators