Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle slack distribution failure behavior in DC Load Flow #1146

Merged
merged 17 commits into from
Dec 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't we use the distributeSlack() method to avoid duplication ? The output of distributeSlack() is not used anywhere else (in WoodburyEngine the method is called without using its output) so it can be changed to return the result variable

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I inlined it to avoid a double-call to getActivePowerMismatch, indeed we should perhaps move distributeSlack() to Woodbury, and create an issue about usage of slack distribution failure behavior in Woodbury engine

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
Loading