Skip to content

Commit

Permalink
Improve AC Load Flow reports (#977)
Browse files Browse the repository at this point in the history
Signed-off-by: Caio Luke <caioluke97@gmail.com>
Co-authored-by: Damien Jeandemange <damien.jeandemange@artelys.com>
Co-authored-by: Hadrien <hadrien.godard@artelys.com>
  • Loading branch information
3 people authored Mar 8, 2024
1 parent 28c04ec commit 1fc200e
Show file tree
Hide file tree
Showing 27 changed files with 1,331 additions and 368 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@
*/
public class AcOuterLoopContext extends AbstractOuterLoopContext<AcVariableType, AcEquationType, AcLoadFlowParameters, AcLoadFlowContext> {

private int iteration;
private int iteration; // current iterations of this single outer loop type

private int outerLoopTotalIterations; // current total iterations over all outer loop types, for reporting purposes

private AcSolverResult lastSolverResult;

Expand All @@ -41,4 +43,12 @@ public AcSolverResult getLastSolverResult() {
public void setLastSolverResult(AcSolverResult lastSolverResult) {
this.lastSolverResult = lastSolverResult;
}

public int getOuterLoopTotalIterations() {
return outerLoopTotalIterations;
}

public void setOuterLoopTotalIterations(int outerLoopTotalIterations) {
this.outerLoopTotalIterations = outerLoopTotalIterations;
}
}
12 changes: 8 additions & 4 deletions src/main/java/com/powsybl/openloadflow/ac/AcloadFlowEngine.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@

import com.google.common.collect.Lists;
import com.powsybl.commons.reporter.Reporter;
import com.powsybl.commons.reporter.TypedValue;
import com.powsybl.openloadflow.ac.equations.AcEquationType;
import com.powsybl.openloadflow.ac.equations.AcVariableType;
import com.powsybl.openloadflow.ac.solver.*;
Expand Down Expand Up @@ -76,6 +75,7 @@ private void runOuterLoop(AcOuterLoop outerLoop, AcOuterLoopContext outerLoopCon

// check outer loop status
outerLoopContext.setIteration(outerLoopIteration.getValue());
outerLoopContext.setOuterLoopTotalIterations(runningContext.outerLoopTotalIterations);
outerLoopContext.setLastSolverResult(runningContext.lastSolverResult);
outerLoopContext.setLoadFlowContext(context);
outerLoopStatus = outerLoop.check(outerLoopContext, olReporter);
Expand All @@ -90,7 +90,8 @@ private void runOuterLoop(AcOuterLoop outerLoop, AcOuterLoopContext outerLoopCon
solver.getName(),
context.getNetwork().getNumCC(),
context.getNetwork().getNumSC(),
outerLoopIteration.toInteger() + 1, outerLoop.getName());
runningContext.outerLoopTotalIterations + 1,
outerLoop.getName());
}

// if not yet stable, restart solver
Expand All @@ -104,6 +105,10 @@ private void runOuterLoop(AcOuterLoop outerLoop, AcOuterLoopContext outerLoopCon
} while (outerLoopStatus == OuterLoopStatus.UNSTABLE
&& runningContext.lastSolverResult.getStatus() == AcSolverStatus.CONVERGED
&& runningContext.outerLoopTotalIterations < context.getParameters().getMaxOuterLoopIterations());

if (outerLoopStatus != OuterLoopStatus.STABLE) {
Reports.reportUnsuccessfulOuterLoop(olReporter, outerLoopStatus.name());
}
}

@Override
Expand Down Expand Up @@ -206,8 +211,7 @@ public AcLoadFlowResult run() {

LOGGER.info("Ac loadflow complete on network {} (result={})", context.getNetwork(), result);

Reports.reportAcLfComplete(context.getNetwork().getReporter(), result.getSolverStatus().name(),
result.getSolverStatus() == AcSolverStatus.CONVERGED ? TypedValue.INFO_SEVERITY : TypedValue.ERROR_SEVERITY);
Reports.reportAcLfComplete(context.getNetwork().getReporter(), result.isSuccess(), result.getSolverStatus().name(), result.getOuterLoopStatus().name());

context.setResult(result);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@
import com.powsybl.openloadflow.lf.outerloop.OuterLoopStatus;
import com.powsybl.openloadflow.network.*;
import com.powsybl.openloadflow.util.PerUnit;
import com.powsybl.openloadflow.util.Reports;
import org.apache.commons.lang3.Range;
import org.apache.commons.lang3.mutable.MutableBoolean;
import org.apache.commons.lang3.mutable.MutableInt;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
Expand Down Expand Up @@ -77,6 +78,7 @@ public DenseMatrix calculateSensitivityValues(List<LfBranch> controllerBranches,
return rhs;
}

@SuppressWarnings("unchecked")
private EquationTerm<AcVariableType, AcEquationType> getI1(LfBranch controlledBranch) {
return (EquationTerm<AcVariableType, AcEquationType>) controlledBranch.getI1();
}
Expand All @@ -92,9 +94,9 @@ public double calculateSensitivityFromA2I(LfBranch controllerBranch, LfBranch co
}
}

private boolean checkCurrentLimiterPhaseControls(AcSensitivityContext sensitivityContext, IncrementalContextData contextData,
List<TransformerPhaseControl> currentLimiterPhaseControls) {
MutableBoolean updated = new MutableBoolean(false);
private int checkCurrentLimiterPhaseControls(AcSensitivityContext sensitivityContext, IncrementalContextData contextData,
List<TransformerPhaseControl> currentLimiterPhaseControls) {
MutableInt numOfCurrentLimiterPstsThatChangedTap = new MutableInt(0);

for (TransformerPhaseControl phaseControl : currentLimiterPhaseControls) {
LfBranch controllerBranch = phaseControl.getControllerBranch();
Expand All @@ -116,11 +118,11 @@ private boolean checkCurrentLimiterPhaseControls(AcSensitivityContext sensitivit
Range<Integer> tapPositionRange = piModel.getTapPositionRange();
piModel.updateTapPositionToExceedNewA1(da, MAX_TAP_SHIFT, controllerContext.getAllowedDirection()).ifPresent(direction -> {
controllerContext.updateAllowedDirection(direction);
updated.setValue(true);
numOfCurrentLimiterPstsThatChangedTap.add(1);
});

if (piModel.getTapPosition() != oldTapPosition) {
logger.debug("Controller branch '{}' change tap from {} to {} to limit current (full range: {})", controllerBranch.getId(),
logger.debug("Controller branch '{}' changed tap from {} to {} to limit current (full range: {})", controllerBranch.getId(),
oldTapPosition, piModel.getTapPosition(), tapPositionRange);

double discreteDa = piModel.getA1() - oldA1;
Expand All @@ -129,8 +131,7 @@ private boolean checkCurrentLimiterPhaseControls(AcSensitivityContext sensitivit
}
}
}

return updated.booleanValue();
return numOfCurrentLimiterPstsThatChangedTap.getValue();
}

private void checkImpactOnOtherPhaseShifters(AcSensitivityContext sensitivityContext, TransformerPhaseControl phaseControl,
Expand Down Expand Up @@ -169,7 +170,6 @@ private static double computeI(TransformerPhaseControl phaseControl) {

@Override
public OuterLoopStatus check(AcOuterLoopContext context, Reporter reporter) {
OuterLoopStatus status = OuterLoopStatus.STABLE;

var contextData = (IncrementalContextData) context.getData();

Expand All @@ -195,24 +195,43 @@ public OuterLoopStatus check(AcOuterLoopContext context, Reporter reporter) {
});
}

if (!currentLimiterPhaseControls.isEmpty() || !activePowerControlPhaseControls.isEmpty()) {
var sensitivityContext = new AcSensitivityContext(network,
controllerBranches,
context.getLoadFlowContext().getEquationSystem(),
context.getLoadFlowContext().getJacobianMatrix());

if (!currentLimiterPhaseControls.isEmpty()
&& checkCurrentLimiterPhaseControls(sensitivityContext,
contextData,
currentLimiterPhaseControls)) {
status = OuterLoopStatus.UNSTABLE;
}
OuterLoopStatus status = OuterLoopStatus.STABLE;

if (currentLimiterPhaseControls.isEmpty() && activePowerControlPhaseControls.isEmpty()) {
return status;
}

var sensitivityContext = new AcSensitivityContext(network,
controllerBranches,
context.getLoadFlowContext().getEquationSystem(),
context.getLoadFlowContext().getJacobianMatrix());

final int numOfCurrentLimiterPstsThatChangedTap;
final int numOfActivePowerControlPstsThatChangedTap;
if (!currentLimiterPhaseControls.isEmpty()) {
numOfCurrentLimiterPstsThatChangedTap = checkCurrentLimiterPhaseControls(sensitivityContext,
contextData,
currentLimiterPhaseControls);
} else {
numOfCurrentLimiterPstsThatChangedTap = 0;
}

if (!activePowerControlPhaseControls.isEmpty()
&& checkActivePowerControlPhaseControls(sensitivityContext,
contextData,
activePowerControlPhaseControls)) {
status = OuterLoopStatus.UNSTABLE;
if (!activePowerControlPhaseControls.isEmpty()) {
numOfActivePowerControlPstsThatChangedTap = checkActivePowerControlPhaseControls(sensitivityContext,
contextData,
activePowerControlPhaseControls);
} else {
numOfActivePowerControlPstsThatChangedTap = 0;
}

if (numOfCurrentLimiterPstsThatChangedTap + numOfActivePowerControlPstsThatChangedTap != 0) {
status = OuterLoopStatus.UNSTABLE;
Reporter iterationReporter = Reports.createOuterLoopIterationReporter(reporter, context.getOuterLoopTotalIterations() + 1);
if (numOfCurrentLimiterPstsThatChangedTap != 0) {
Reports.reportCurrentLimiterPstsChangedTaps(iterationReporter, numOfCurrentLimiterPstsThatChangedTap);
}
if (numOfActivePowerControlPstsThatChangedTap != 0) {
Reports.reportActivePowerControlPstsChangedTaps(iterationReporter, numOfActivePowerControlPstsThatChangedTap);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,68 +53,71 @@ public void initialize(AcOuterLoopContext context) {
@Override
public OuterLoopStatus check(AcOuterLoopContext context, Reporter reporter) {
double slackBusActivePowerMismatch = context.getLastSolverResult().getSlackBusActivePowerMismatch();
if (Math.abs(slackBusActivePowerMismatch) > slackBusPMaxMismatch / PerUnit.SB) {
ActivePowerDistribution.Result result = activePowerDistribution.run(context.getNetwork(), slackBusActivePowerMismatch);
double remainingMismatch = result.remainingMismatch();
double distributedActivePower = slackBusActivePowerMismatch - remainingMismatch;
DistributedSlackContextData contextData = (DistributedSlackContextData) context.getData();
contextData.addDistributedActivePower(distributedActivePower);
if (Math.abs(remainingMismatch) > ActivePowerDistribution.P_RESIDUE_EPS) {
OpenLoadFlowParameters.SlackDistributionFailureBehavior slackDistributionFailureBehavior = context.getLoadFlowContext().getParameters().getSlackDistributionFailureBehavior();
LfGenerator referenceGenerator = context.getNetwork().getReferenceGenerator();
if (OpenLoadFlowParameters.SlackDistributionFailureBehavior.DISTRIBUTE_ON_REFERENCE_GENERATOR == slackDistributionFailureBehavior) {
if (referenceGenerator == null) {
// no reference generator, fall back internally to FAIL mode
slackDistributionFailureBehavior = OpenLoadFlowParameters.SlackDistributionFailureBehavior.FAIL;
Reports.reportMismatchDistributionFailure(reporter, context.getIteration(), remainingMismatch * PerUnit.SB);
}
} else {
Reports.reportMismatchDistributionFailure(reporter, context.getIteration(), remainingMismatch * PerUnit.SB);
}
boolean shouldDistributeSlack = Math.abs(slackBusActivePowerMismatch) > slackBusPMaxMismatch / PerUnit.SB;

if (!shouldDistributeSlack) {
LOGGER.debug("Already balanced");
return OuterLoopStatus.STABLE;
}

switch (slackDistributionFailureBehavior) {
case THROW ->
throw new PowsyblException("Failed to distribute slack bus active power mismatch, "
+ remainingMismatch * PerUnit.SB + " MW remains");
case LEAVE_ON_SLACK_BUS -> {
LOGGER.warn("Failed to distribute slack bus active power mismatch, {} MW remains",
remainingMismatch * PerUnit.SB);
return result.movedBuses() ? OuterLoopStatus.UNSTABLE : OuterLoopStatus.STABLE;
}
case DISTRIBUTE_ON_REFERENCE_GENERATOR -> {
Objects.requireNonNull(referenceGenerator, () -> "No reference generator in " + context.getNetwork());
// remaining goes to reference generator, without any limit consideration
referenceGenerator.setTargetP(referenceGenerator.getTargetP() + remainingMismatch);
reportAndLogSuccess(context, reporter, slackBusActivePowerMismatch, result);
return OuterLoopStatus.UNSTABLE;
}
case FAIL -> {
LOGGER.error("Failed to distribute slack bus active power mismatch, {} MW remains",
remainingMismatch * PerUnit.SB);
// Mismatches reported in LoadFlowResult on slack bus(es) are the mismatches of the last NR run.
// Since we will not be re-running an NR, revert distributedActivePower reporting which would otherwise be misleading.
// Said differently, we report that we didn't distribute anything, and this is indeed consistent with the network state.
contextData.addDistributedActivePower(-distributedActivePower);
return OuterLoopStatus.FAILED;
}
Reporter iterationReporter = Reports.createOuterLoopIterationReporter(reporter, context.getOuterLoopTotalIterations() + 1);
ActivePowerDistribution.Result result = activePowerDistribution.run(context.getNetwork(), slackBusActivePowerMismatch);
double remainingMismatch = result.remainingMismatch();
double distributedActivePower = slackBusActivePowerMismatch - remainingMismatch;
DistributedSlackContextData contextData = (DistributedSlackContextData) context.getData();
contextData.addDistributedActivePower(distributedActivePower);
if (Math.abs(remainingMismatch) > ActivePowerDistribution.P_RESIDUE_EPS) {
OpenLoadFlowParameters.SlackDistributionFailureBehavior slackDistributionFailureBehavior = context.getLoadFlowContext().getParameters().getSlackDistributionFailureBehavior();
LfGenerator referenceGenerator = context.getNetwork().getReferenceGenerator();
if (OpenLoadFlowParameters.SlackDistributionFailureBehavior.DISTRIBUTE_ON_REFERENCE_GENERATOR == slackDistributionFailureBehavior) {
if (referenceGenerator == null) {
// no reference generator, fall back internally to FAIL mode
slackDistributionFailureBehavior = OpenLoadFlowParameters.SlackDistributionFailureBehavior.FAIL;
Reports.reportMismatchDistributionFailure(iterationReporter, remainingMismatch * PerUnit.SB);
}
} else {
reportAndLogSuccess(context, reporter, slackBusActivePowerMismatch, result);
return OuterLoopStatus.UNSTABLE;
Reports.reportMismatchDistributionFailure(iterationReporter, remainingMismatch * PerUnit.SB);
}
}

Reports.reportNoMismatchDistribution(reporter, context.getIteration());

LOGGER.debug("Already balanced");

return OuterLoopStatus.STABLE;
switch (slackDistributionFailureBehavior) {
case THROW ->
throw new PowsyblException("Failed to distribute slack bus active power mismatch, "
+ remainingMismatch * PerUnit.SB + " MW remains");
case LEAVE_ON_SLACK_BUS -> {
LOGGER.warn("Failed to distribute slack bus active power mismatch, {} MW remains",
remainingMismatch * PerUnit.SB);
return result.movedBuses() ? OuterLoopStatus.UNSTABLE : OuterLoopStatus.STABLE;
}
case DISTRIBUTE_ON_REFERENCE_GENERATOR -> {
Objects.requireNonNull(referenceGenerator, () -> "No reference generator in " + context.getNetwork());
// remaining goes to reference generator, without any limit consideration
referenceGenerator.setTargetP(referenceGenerator.getTargetP() + remainingMismatch);
// create a new result with iteration++, 0.0 mismatch and movedBuses to true
result = new ActivePowerDistribution.Result(result.iteration() + 1, 0.0, true);
reportAndLogSuccess(iterationReporter, slackBusActivePowerMismatch, result);
return OuterLoopStatus.UNSTABLE;
}
case FAIL -> {
LOGGER.error("Failed to distribute slack bus active power mismatch, {} MW remains",
remainingMismatch * PerUnit.SB);
// Mismatches reported in LoadFlowResult on slack bus(es) are the mismatches of the last NR run.
// Since we will not be re-running an NR, revert distributedActivePower reporting which would otherwise be misleading.
// Said differently, we report that we didn't distribute anything, and this is indeed consistent with the network state.
contextData.addDistributedActivePower(-distributedActivePower);
return OuterLoopStatus.FAILED;
}
default -> throw new IllegalArgumentException("Unknown slackDistributionFailureBehavior");
}
} else {
reportAndLogSuccess(iterationReporter, slackBusActivePowerMismatch, result);
return OuterLoopStatus.UNSTABLE;
}
}

private static void reportAndLogSuccess(AcOuterLoopContext context, Reporter reporter, double slackBusActivePowerMismatch, ActivePowerDistribution.Result result) {
Reports.reportMismatchDistributionSuccess(reporter, context.getIteration(), slackBusActivePowerMismatch * PerUnit.SB, result.iteration());
private static void reportAndLogSuccess(Reporter reporter, double slackBusActivePowerMismatch, ActivePowerDistribution.Result result) {
Reports.reportMismatchDistributionSuccess(reporter, slackBusActivePowerMismatch * PerUnit.SB, result.iteration());

LOGGER.info("Slack bus active power ({} MW) distributed in {} iterations",
LOGGER.info("Slack bus active power ({} MW) distributed in {} distribution iteration(s)",
slackBusActivePowerMismatch * PerUnit.SB, result.iteration());
}
}
Loading

0 comments on commit 1fc200e

Please sign in to comment.