diff --git a/src/main/java/com/powsybl/openloadflow/dc/DcLoadFlowEngine.java b/src/main/java/com/powsybl/openloadflow/dc/DcLoadFlowEngine.java index 57479ba908..b0ea1d5cb5 100644 --- a/src/main/java/com/powsybl/openloadflow/dc/DcLoadFlowEngine.java +++ b/src/main/java/com/powsybl/openloadflow/dc/DcLoadFlowEngine.java @@ -204,7 +204,12 @@ public DcLoadFlowResult run() { double distributedActivePower = 0.0; boolean isAreaInterchangeControl = outerLoops.stream().anyMatch(DcAreaInterchangeControlOuterLoop.class::isInstance); - if (parameters.isDistributedSlack() || isAreaInterchangeControl) { + // In DC LoadFlow slack mismatch is distributed when mismatch is above epsilon (P_RESIDUE_EPS 1e-3 MW). + // This is different from AC LoadFlow distributing slack when mismatch is above OLF parameter slackBusPMaxMismatch (default 1 MW). + // The reason of the difference is that in DC we can eliminate completely (within epsilon) the slack mismatch + // in a single distribution (unless all generator are hitting limits), whereas in AC reaching epsilon would require too many solver iterations. + if ((parameters.isDistributedSlack() || isAreaInterchangeControl) && + Math.abs(initialSlackBusActivePowerMismatch) > ActivePowerDistribution.P_RESIDUE_EPS) { LoadFlowParameters.BalanceType balanceType = parameters.getBalanceType(); boolean useActiveLimits = parameters.getNetworkParameters().isUseActiveLimits(); ActivePowerDistribution activePowerDistribution = ActivePowerDistribution.create(balanceType, false, useActiveLimits); @@ -228,6 +233,8 @@ public DcLoadFlowResult run() { ); double remainingMismatch = resultWbh.remainingMismatch(); distributedActivePower = initialSlackBusActivePowerMismatch - remainingMismatch; + // In the case of slack mismatch not being fully distributed due to e.g. all generators hitting limits, the remaining mismatch is + // checked against OLF parameter slackBusPMaxMismatch (and not epsilon) for appropriate reporting of distribution success or failure. if (Math.abs(remainingMismatch) > context.getParameters().getSlackBusPMaxMismatch() / PerUnit.SB) { Reports.reportMismatchDistributionFailure(reportNode, remainingMismatch * PerUnit.SB); } else { diff --git a/src/test/java/com/powsybl/openloadflow/dc/DcLoadFlowTest.java b/src/test/java/com/powsybl/openloadflow/dc/DcLoadFlowTest.java index eda687b67b..9d253f49a1 100644 --- a/src/test/java/com/powsybl/openloadflow/dc/DcLoadFlowTest.java +++ b/src/test/java/com/powsybl/openloadflow/dc/DcLoadFlowTest.java @@ -527,7 +527,6 @@ void outerLoopMaxTotalIterationTest() throws IOException { Network balance: active generation=140 MW, active load=140 MW, reactive generation=0 MVar, reactive load=55 MVar Angle reference bus: VL1_0 Slack bus: VL1_0 - Slack bus active power (0 MW) distributed in 0 distribution iteration(s) + Outer loop IncrementalPhaseControl Outer loop unsuccessful with status: UNSTABLE Maximum number of outerloop iterations reached: 1 diff --git a/src/test/java/com/powsybl/openloadflow/sa/OpenSecurityAnalysisTest.java b/src/test/java/com/powsybl/openloadflow/sa/OpenSecurityAnalysisTest.java index 5fdec74523..64babe337f 100644 --- a/src/test/java/com/powsybl/openloadflow/sa/OpenSecurityAnalysisTest.java +++ b/src/test/java/com/powsybl/openloadflow/sa/OpenSecurityAnalysisTest.java @@ -1955,7 +1955,28 @@ void reportTest() throws IOException { .setLoadFlowParameters(loadFlowParameters); runSecurityAnalysis(network, contingencies, Collections.emptyList(), securityAnalysisParameters, reportNode); - assertReportEquals("/saReport.txt", reportNode); + assertReportEquals("/saReportAC.txt", reportNode); + } + + @Test + void reportTestDc() throws IOException { + var network = EurostagFactory.fix(EurostagTutorialExample1Factory.create()); + + List contingencies = createAllBranchesContingencies(network); + + ReportNode reportNode = ReportNode.newRootReportNode() + .withResourceBundles(PowsyblOpenLoadFlowReportResourceBundle.BASE_NAME, PowsyblTestReportResourceBundle.TEST_BASE_NAME) + .withMessageTemplate("TestSecurityAnalysis") + .build(); + + LoadFlowParameters loadFlowParameters = new LoadFlowParameters() + .setDc(true) + .setHvdcAcEmulation(false); + SecurityAnalysisParameters securityAnalysisParameters = new SecurityAnalysisParameters() + .setLoadFlowParameters(loadFlowParameters); + runSecurityAnalysis(network, contingencies, Collections.emptyList(), securityAnalysisParameters, reportNode); + + assertReportEquals("/saReportDC.txt", reportNode); } @Test diff --git a/src/test/resources/saReport.txt b/src/test/resources/saReportAC.txt similarity index 100% rename from src/test/resources/saReport.txt rename to src/test/resources/saReportAC.txt diff --git a/src/test/resources/saReportDC.txt b/src/test/resources/saReportDC.txt new file mode 100644 index 0000000000..0a21caa149 --- /dev/null +++ b/src/test/resources/saReportDC.txt @@ -0,0 +1,22 @@ ++ Test security analysis report + + DC security analysis on network 'sim1' + + Network CC0 SC0 + + Network info + Network has 4 buses and 4 branches + Network balance: active generation=607 MW, active load=600 MW, reactive generation=0 MVar, reactive load=200 MVar + Angle reference bus: VLHV1_0 + Slack bus: VLHV1_0 + + Pre-contingency simulation + Slack bus active power (-7 MW) distributed in 1 distribution iteration(s) + DC load flow completed (solverSuccess=true, outerloopStatus=STABLE) + + Post-contingency simulation 'NHV1_NHV2_1' + DC load flow completed (solverSuccess=true, outerloopStatus=STABLE) + + Post-contingency simulation 'NHV1_NHV2_2' + DC load flow completed (solverSuccess=true, outerloopStatus=STABLE) + + Post-contingency simulation 'NGEN_NHV1' + Contingency caused the loss of 600 MW injection: 0 MW distributed, 600 MW remaining. + Failed to distribute slack bus active power mismatch, 600 MW remains + DC load flow completed (solverSuccess=false, outerloopStatus=FAILED) + + Post-contingency simulation 'NHV2_NLOAD' + Contingency caused the loss of -600 MW injection: -600 MW distributed, 0 MW remaining. + DC load flow completed (solverSuccess=true, outerloopStatus=STABLE)