Skip to content

Commit

Permalink
Handle slack distribution failure behavior in DC Load Flow (#1146)
Browse files Browse the repository at this point in the history
Signed-off-by: PRABAKARAN Sylvestre <sylvestre.prabakaran@rte-france.com>
Co-authored-by: Damien Jeandemange <damien.jeandemange@artelys.com>
  • Loading branch information
SylvestreSakti and jeandemanged authored Dec 12, 2024
1 parent dde1dc6 commit 714b35e
Show file tree
Hide file tree
Showing 5 changed files with 117 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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, resultWbh);
ActivePowerDistribution.reportAndLogSuccess(iterationReportNode, slackBusActivePowerMismatch, resultWbh);
}
DistributedSlackContextData contextData = (DistributedSlackContextData) context.getData();
contextData.addDistributedActivePower(distributedActivePower);
Expand All @@ -97,10 +97,4 @@ public double getSlackBusActivePowerMismatch(AcOuterLoopContext context) {
return context.getLastSolverResult().getSlackBusActivePowerMismatch();
}

private static void reportAndLogSuccess(ReportNode reportNode, double slackBusActivePowerMismatch, ActivePowerDistribution.ResultWithFailureBehaviorHandling 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());
}
}
40 changes: 38 additions & 2 deletions src/main/java/com/powsybl/openloadflow/dc/DcLoadFlowEngine.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,21 @@
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.*;
import com.powsybl.openloadflow.lf.LoadFlowEngine;
import com.powsybl.openloadflow.lf.outerloop.OuterLoopResult;
import com.powsybl.openloadflow.lf.outerloop.OuterLoopStatus;
import com.powsybl.openloadflow.network.LfBus;
import com.powsybl.openloadflow.network.LfGenerator;
import com.powsybl.openloadflow.network.LfNetwork;
import com.powsybl.openloadflow.network.LfNetworkLoader;
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;
Expand Down Expand Up @@ -205,8 +208,41 @@ public DcLoadFlowResult run() {
double initialSlackBusActivePowerMismatch = getActivePowerMismatch(network.getBuses());
double distributedActivePower = 0.0;
if (parameters.isDistributedSlack() || parameters.isAreaInterchangeControl()) {
// FIXME handle distribution failure
distributedActivePower = distributeSlack(network, network.getBuses(), parameters.getBalanceType(), parameters.getNetworkParameters().isUseActiveLimits());
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.ResultWithFailureBehaviorHandling resultWbh = ActivePowerDistribution.handleDistributionFailureBehavior(
behavior,
referenceGenerator,
initialSlackBusActivePowerMismatch,
result,
"Failed to distribute slack bus active power mismatch, %.2f MW remains"
);
double remainingMismatch = resultWbh.remainingMismatch();
distributedActivePower = initialSlackBusActivePowerMismatch - remainingMismatch;
if (Math.abs(remainingMismatch) > ActivePowerDistribution.P_RESIDUE_EPS) {
Reports.reportMismatchDistributionFailure(reportNode, remainingMismatch * PerUnit.SB);
} else {
ActivePowerDistribution.reportAndLogSuccess(reportNode, initialSlackBusActivePowerMismatch, resultWbh);
}
if (resultWbh.failed()) {
distributedActivePower -= resultWbh.failedDistributedActivePower();
runningContext.lastSolverSuccess = false;
runningContext.lastOuterLoopResult = new OuterLoopResult("DistributedSlack", OuterLoopStatus.FAILED, resultWbh.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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -165,4 +167,11 @@ public static ResultWithFailureBehaviorHandling handleDistributionFailureBehavio

return resultWithFailureBehaviorHandling;
}

public static void reportAndLogSuccess(ReportNode reportNode, double slackBusActivePowerMismatch, ResultWithFailureBehaviorHandling 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());
}
}
74 changes: 67 additions & 7 deletions src/test/java/com/powsybl/openloadflow/dc/DcLoadFlowTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
package com.powsybl.openloadflow.dc;

import com.powsybl.commons.report.ReportNode;
import com.powsybl.computation.local.LocalComputationManager;
import com.powsybl.ieeecdf.converter.IeeeCdfNetworkFactory;
import com.powsybl.iidm.network.*;
import com.powsybl.iidm.network.test.EurostagTutorialExample1Factory;
Expand All @@ -26,13 +27,16 @@
import com.powsybl.openloadflow.util.PerUnit;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

import java.util.List;
import java.util.concurrent.CompletionException;

import static com.powsybl.openloadflow.util.LoadFlowAssert.assertActivePowerEquals;
import static com.powsybl.openloadflow.util.LoadFlowAssert.assertReportContains;
import static org.junit.jupiter.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;

/**
* @author Sylvain Leclerc {@literal <sylvain.leclerc at rte-france.com>}
Expand Down Expand Up @@ -116,6 +120,21 @@ void tuto1Test() {
assertEquals(-450, line2.getTerminal2().getP(), 0.01);
}

@ParameterizedTest(name = "distributedSlack={0}")
@ValueSource(booleans = {true, false})
void testSlackDistributionEnabledDisabledResults(boolean distributedSlack) {
Network network = EurostagFactory.fix(EurostagTutorialExample1Factory.create());

parameters.setDistributedSlack(distributedSlack);
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(distributedSlack ? -7.0 : 0.0, componentResults.get(0).getDistributedActivePower(), 1e-3);
assertEquals(distributedSlack ? 0.0 : -7.0, componentResults.get(0).getSlackBusResults().get(0).getActivePowerMismatch(), 1e-3);
}

@Test
void fourBusesTest() {
Network network = FourBusNetworkFactory.create();
Expand Down Expand Up @@ -259,8 +278,8 @@ void multiCcTest() {
.setVoltageRegulatorOn(false)
.add();
for (Line l : List.of(network.getLine("L13-14-1"),
network.getLine("L6-13-1"),
network.getLine("L6-12-1"))) {
network.getLine("L6-13-1"),
network.getLine("L6-12-1"))) {
l.getTerminal1().disconnect();
l.getTerminal2().disconnect();
}
Expand Down Expand Up @@ -320,8 +339,8 @@ void shuntCompensatorActivePowerZero() {
.setBus("NLOAD")
.setSectionCount(1)
.newLinearModel()
.setBPerSection(0.111)
.setMaximumSectionCount(1)
.setBPerSection(0.111)
.setMaximumSectionCount(1)
.add()
.add();
loadFlowRunner.run(network, parameters);
Expand Down Expand Up @@ -473,7 +492,7 @@ void outerLoopMaxTotalIterationTest() {

// For this case, AIC outer loop needs 3 iterations to be stable, phase control needs 1.
parametersExt.setAreaInterchangePMaxMismatch(1)
.setMaxOuterLoopIterations(1);
.setMaxOuterLoopIterations(1);
var result = loadFlowRunner.run(network, parameters);
assertFalse(result.isFullyConverged());
assertEquals(LoadFlowResult.ComponentResult.Status.MAX_ITERATION_REACHED, result.getComponentResults().get(0).getStatus());
Expand Down Expand Up @@ -510,4 +529,45 @@ void testDcApproxIgnoreG() {
assertEquals(307.436, line2.getTerminal1().getP(), 0.01);
assertEquals(-307.436, line2.getTerminal2().getP(), 0.01);
}

@Test
void testDcSlackDistributionFailureBehavior() {
Network network = IeeeCdfNetworkFactory.create57();
parameters.setBalanceType(LoadFlowParameters.BalanceType.PROPORTIONAL_TO_GENERATION_P);
Generator referenceGenerator = network.getGenerator("B1-G");

parametersExt.setSlackDistributionFailureBehavior(OpenLoadFlowParameters.SlackDistributionFailureBehavior.LEAVE_ON_SLACK_BUS);
ReportNode reportNode = ReportNode.newRootReportNode().withMessageTemplate("test", "test").build();
var result = loadFlowRunner.run(network, network.getVariantManager().getWorkingVariantId(), LocalComputationManager.getDefault(), parameters, reportNode);
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);
assertReportContains("Failed to distribute slack bus active power mismatch, [-+]?321\\.\\d* MW remains", reportNode);

parametersExt.setSlackDistributionFailureBehavior(OpenLoadFlowParameters.SlackDistributionFailureBehavior.FAIL);
reportNode = ReportNode.newRootReportNode().withMessageTemplate("test", "test").build();
result = loadFlowRunner.run(network, network.getVariantManager().getWorkingVariantId(), LocalComputationManager.getDefault(), parameters, reportNode);
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);
assertReportContains("Failed to distribute slack bus active power mismatch, [-+]?321\\.\\d* MW remains", reportNode);

parametersExt.setSlackDistributionFailureBehavior(OpenLoadFlowParameters.SlackDistributionFailureBehavior.THROW);
CompletionException e = assertThrows(CompletionException.class, () -> loadFlowRunner.run(network, parameters));
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);
reportNode = ReportNode.newRootReportNode().withMessageTemplate("test", "test").build();
result = loadFlowRunner.run(network, network.getVariantManager().getWorkingVariantId(), LocalComputationManager.getDefault(), parameters, reportNode);
assertEquals(0, result.getComponentResults().get(0).getSlackBusResults().get(0).getActivePowerMismatch(), 0.01);
assertEquals(321.9, result.getComponentResults().get(0).getDistributedActivePower(), 0.01);
assertActivePowerEquals(-450.8, referenceGenerator.getTerminal()); // -128.9 - 321.9 = -450.8
assertReportContains("Slack bus active power \\([-+]?321\\.\\d* MW\\) distributed in 1 distribution iteration\\(s\\)", reportNode);
}
}
2 changes: 2 additions & 0 deletions src/test/resources/multipleConnectedComponentsDcReport.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@
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
Network has 3 buses and 4 branches
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

0 comments on commit 714b35e

Please sign in to comment.