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

Try to fix AC sensitivity for no impact contingency after normal contingency #631

Merged
merged 7 commits into from
Oct 19, 2022
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 @@ -725,7 +725,7 @@ public void checkContingencies(LfNetwork lfNetwork, List<PropagatedContingency>
&& contingency.getHvdcIdsToOpen().isEmpty()
&& contingency.getGeneratorIdsToLose().isEmpty()
&& contingency.getLoadIdsToShift().isEmpty()) {
LOGGER.warn("Contingency {} has no impact", contingency.getContingency().getId());
LOGGER.warn("Contingency '{}' has no impact", contingency.getContingency().getId());
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import com.powsybl.openloadflow.OpenLoadFlowParameters;
import com.powsybl.openloadflow.ac.equations.AcEquationType;
import com.powsybl.openloadflow.ac.equations.AcVariableType;
import com.powsybl.openloadflow.ac.nr.NewtonRaphson;
import com.powsybl.openloadflow.ac.nr.NewtonRaphsonStatus;
import com.powsybl.openloadflow.ac.outerloop.AcLoadFlowContext;
import com.powsybl.openloadflow.ac.outerloop.AcLoadFlowParameters;
Expand Down Expand Up @@ -117,13 +118,14 @@ private void calculatePostContingencySensitivityValues(List<LfSensitivityFactor<
activePowerDistribution.run(lfNetwork, lfContingency.getActivePowerLoss());
}

context.getParameters().setVoltageInitializer(new PreviousValueVoltageInitializer());
AcLoadFlowResult result = new AcloadFlowEngine(context)
.run();
if (!runLoadFlow(context, false)) {
// write contingency status
resultWriter.writeContingencyStatus(contingencyIndex, SensitivityAnalysisResult.Status.FAILURE);
return;
}

// write contingency status
resultWriter.writeContingencyStatus(contingencyIndex, (result.getNewtonRaphsonStatus() == NewtonRaphsonStatus.CONVERGED) ?
SensitivityAnalysisResult.Status.SUCCESS : SensitivityAnalysisResult.Status.FAILURE);
resultWriter.writeContingencyStatus(contingencyIndex, SensitivityAnalysisResult.Status.SUCCESS);

// if we have at least one bus target voltage linked to a ratio tap changer, we have to rebuild the AC equation
// system obtained just before the transformer steps rounding.
Expand Down Expand Up @@ -162,6 +164,21 @@ public void checkContingencies(LfNetwork lfNetwork, List<PropagatedContingency>
}
}

private static boolean runLoadFlow(AcLoadFlowContext context, boolean throwsExceptionIfNoConvergence) {
AcLoadFlowResult result = new AcloadFlowEngine(context)
.run();
if (result.getNewtonRaphsonStatus() != NewtonRaphsonStatus.CONVERGED
&& result.getNewtonRaphsonStatus() != NewtonRaphsonStatus.NO_CALCULATION) {
if (throwsExceptionIfNoConvergence) {
throw new PowsyblException("Loadflow diverged with status " + result.getNewtonRaphsonStatus());
} else {
LOGGER.warn("Loadflow diverged with status {}", result.getNewtonRaphsonStatus());
return false;
}
}
return true;
}

/**
* https://people.montefiore.uliege.be/vct/elec0029/lf.pdf / Equation 32 is transposed
*/
Expand Down Expand Up @@ -235,8 +252,7 @@ public void analyse(Network network, List<PropagatedContingency> contingencies,

try (AcLoadFlowContext context = new AcLoadFlowContext(lfNetwork, acParameters)) {

new AcloadFlowEngine(context)
.run();
runLoadFlow(context, true);

// index factors by variable group to compute a minimal number of states
SensitivityFactorGroupList<AcVariableType, AcEquationType> factorGroups = createFactorGroups(validLfFactors.stream()
Expand Down Expand Up @@ -286,65 +302,74 @@ public void analyse(Network network, List<PropagatedContingency> contingencies,

NetworkState networkState = NetworkState.save(lfNetwork);

contingencies.forEach(contingency -> contingency.toLfContingency(lfNetwork)
.ifPresentOrElse(lfContingency -> {
List<LfSensitivityFactor<AcVariableType, AcEquationType>> contingencyFactors = validFactorHolder.getFactorsForContingency(lfContingency.getId());
contingencyFactors.forEach(lfFactor -> {
lfFactor.setSensitivityValuePredefinedResult(null);
lfFactor.setFunctionPredefinedResult(null);
});

lfContingency.apply(lfParameters.getBalanceType());

Map<LfBus, Double> postContingencySlackParticipationByBus;
Set<LfBus> slackConnectedComponent;
if (lfContingency.getDisabledBuses().isEmpty()) {
// contingency not breaking connectivity
LOGGER.debug("Contingency {} without loss of connectivity", lfContingency.getId());
slackConnectedComponent = new HashSet<>(lfNetwork.getBuses());

// Sensitivity values 0 and function reference NaN in case of a sensitivity on a disabled branch
contingencyFactors.stream()
.filter(lfFactor -> lfFactor.getFunctionElement() instanceof LfBranch)
.filter(lfFactor -> lfContingency.getDisabledBranches().contains(lfFactor.getFunctionElement()))
.forEach(lfFactor -> {
lfFactor.setSensitivityValuePredefinedResult(0d);
lfFactor.setFunctionPredefinedResult(Double.NaN);
});

// Sensitivity values 0 in case of a sensitivity from the transformer phase of a disabled transformer
contingencyFactors.stream()
.filter(lfFactor -> lfFactor.getVariableType().equals(SensitivityVariableType.TRANSFORMER_PHASE))
.filter(lfFactor -> lfContingency.getDisabledBranches().contains(lfNetwork.getBranchById(lfFactor.getVariableId())))
.forEach(lfFactor -> lfFactor.setSensitivityValuePredefinedResult(0d));
} else {
// contingency breaking connectivity
LOGGER.debug("Contingency {} with loss of connectivity", lfContingency.getId());
// we check if factors are still in the main component
slackConnectedComponent = new HashSet<>(lfNetwork.getBuses()).stream().filter(Predicate.not(lfContingency.getDisabledBuses()::contains)).collect(Collectors.toSet());
setPredefinedResults(contingencyFactors, lfContingency.getDisabledBuses(), lfContingency.getDisabledBranches());
// we recompute GLSK weights if needed
rescaleGlsk(factorGroups, lfContingency.getDisabledBuses());
}

// compute the participation for each injection factor (+1 on the injection and then -participation factor on all
// buses that contain elements participating to slack distribution)
if (lfParameters.isDistributedSlack()) {
postContingencySlackParticipationByBus = getParticipatingElements(slackConnectedComponent, lfParameters.getBalanceType(), lfParametersExt).stream().collect(Collectors.toMap(
ParticipatingElement::getLfBus, element -> -element.getFactor(), Double::sum));
} else {
postContingencySlackParticipationByBus = Collections.singletonMap(lfNetwork.getSlackBus(), -1d);
}
calculatePostContingencySensitivityValues(contingencyFactors, lfContingency, lfNetwork, context, factorGroups, postContingencySlackParticipationByBus,
lfParameters, lfParametersExt, lfContingency.getIndex(), resultWriter, Boolean.TRUE.equals(hasBusTargetVoltage.getRight()));

networkState.restore();
}, () -> {
// It means that the contingency has no impact.
calculateSensitivityValues(validFactorHolder.getFactorsForContingency(contingency.getContingency().getId()), factorGroups, factorsStates, contingency.getIndex(), resultWriter);
// write contingency status
resultWriter.writeContingencyStatus(contingency.getIndex(), SensitivityAnalysisResult.Status.NO_IMPACT);
}));
// we always restart from base case voltages for contingency simulation
context.getParameters().setVoltageInitializer(new PreviousValueVoltageInitializer());

contingencies.forEach(contingency -> {
LOGGER.info("Simulate contingency '{}'", contingency.getContingency().getId());
contingency.toLfContingency(lfNetwork)
.ifPresentOrElse(lfContingency -> {
List<LfSensitivityFactor<AcVariableType, AcEquationType>> contingencyFactors = validFactorHolder.getFactorsForContingency(lfContingency.getId());
contingencyFactors.forEach(lfFactor -> {
lfFactor.setSensitivityValuePredefinedResult(null);
lfFactor.setFunctionPredefinedResult(null);
});

lfContingency.apply(lfParameters.getBalanceType());

Map<LfBus, Double> postContingencySlackParticipationByBus;
Set<LfBus> slackConnectedComponent;
if (lfContingency.getDisabledBuses().isEmpty()) {
// contingency not breaking connectivity
LOGGER.debug("Contingency '{}' without loss of connectivity", lfContingency.getId());
slackConnectedComponent = new HashSet<>(lfNetwork.getBuses());

// Sensitivity values 0 and function reference NaN in case of a sensitivity on a disabled branch
contingencyFactors.stream()
.filter(lfFactor -> lfFactor.getFunctionElement() instanceof LfBranch)
.filter(lfFactor -> lfContingency.getDisabledBranches().contains(lfFactor.getFunctionElement()))
.forEach(lfFactor -> {
lfFactor.setSensitivityValuePredefinedResult(0d);
lfFactor.setFunctionPredefinedResult(Double.NaN);
});

// Sensitivity values 0 in case of a sensitivity from the transformer phase of a disabled transformer
contingencyFactors.stream()
.filter(lfFactor -> lfFactor.getVariableType().equals(SensitivityVariableType.TRANSFORMER_PHASE))
.filter(lfFactor -> lfContingency.getDisabledBranches().contains(lfNetwork.getBranchById(lfFactor.getVariableId())))
.forEach(lfFactor -> lfFactor.setSensitivityValuePredefinedResult(0d));
} else {
// contingency breaking connectivity
LOGGER.debug("Contingency {} with loss of connectivity", lfContingency.getId());
// we check if factors are still in the main component
slackConnectedComponent = new HashSet<>(lfNetwork.getBuses()).stream().filter(Predicate.not(lfContingency.getDisabledBuses()::contains)).collect(Collectors.toSet());
setPredefinedResults(contingencyFactors, lfContingency.getDisabledBuses(), lfContingency.getDisabledBranches());
// we recompute GLSK weights if needed
rescaleGlsk(factorGroups, lfContingency.getDisabledBuses());
}

// compute the participation for each injection factor (+1 on the injection and then -participation factor on all
// buses that contain elements participating to slack distribution)
if (lfParameters.isDistributedSlack()) {
postContingencySlackParticipationByBus = getParticipatingElements(slackConnectedComponent, lfParameters.getBalanceType(), lfParametersExt).stream().collect(Collectors.toMap(
ParticipatingElement::getLfBus, element -> -element.getFactor(), Double::sum));
} else {
postContingencySlackParticipationByBus = Collections.singletonMap(lfNetwork.getSlackBus(), -1d);
}
calculatePostContingencySensitivityValues(contingencyFactors, lfContingency, lfNetwork, context, factorGroups, postContingencySlackParticipationByBus,
lfParameters, lfParametersExt, lfContingency.getIndex(), resultWriter, Boolean.TRUE.equals(hasBusTargetVoltage.getRight()));

networkState.restore();
}, () -> {
// it means that the contingency has no impact.
// we need to force the state vector to be re-initialized from base case network state
NewtonRaphson.initStateVector(lfNetwork, context.getEquationSystem(), context.getParameters().getVoltageInitializer());

calculateSensitivityValues(validFactorHolder.getFactorsForContingency(contingency.getContingency().getId()), factorGroups, factorsStates, contingency.getIndex(), resultWriter);
// write contingency status
resultWriter.writeContingencyStatus(contingency.getIndex(), SensitivityAnalysisResult.Status.NO_IMPACT);
});
});
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1087,4 +1087,42 @@ void testSwitchContingency2() {
assertTrue(e.getCause() instanceof PowsyblException);
assertEquals("Switch contingency is not yet supported with sensitivity function of type BUS_VOLTAGE", e.getCause().getMessage());
}

@Test
void testNoImpactContingencyAfterNormalContingency() {
Network network = ConnectedComponentNetworkFactory.createTwoCcLinkedByTwoLines();
// we open l45 at both sides
Line l13 = network.getLine("l13");
l13.getTerminal1().disconnect();
l13.getTerminal2().disconnect();

SensitivityAnalysisParameters sensiParameters = createParameters(false, "b1_vl_0", true);
sensiParameters.getLoadFlowParameters().setBalanceType(LoadFlowParameters.BalanceType.PROPORTIONAL_TO_LOAD);

List<Contingency> contingencies = List.of(new Contingency("lines", List.of(new BranchContingency("l46"), new BranchContingency("l56"))),
new Contingency("l13", new BranchContingency("l13")));

ContingencyContext contingencyContext = new ContingencyContext("l13", ContingencyContextType.SPECIFIC);
SensitivityFactor factor = new SensitivityFactor(SensitivityFunctionType.BRANCH_ACTIVE_POWER_1, "l46",
SensitivityVariableType.INJECTION_ACTIVE_POWER,
"d1", false,
contingencyContext);
List<SensitivityFactor> factors = List.of(factor);

SensitivityAnalysisResult result = sensiRunner.run(network, factors, contingencies, Collections.emptyList(), sensiParameters);
assertEquals(0.0735, result.getBranchFlow1SensitivityValue("l13", "d1", "l46"), LoadFlowAssert.DELTA_POWER);
assertEquals(SensitivityAnalysisResult.Status.NO_IMPACT, result.getContingencyStatus("l13"));
}

@Test
void testMaxIterationReachedAfterContingency() {
Network network = EurostagTutorialExample1Factory.create();
network.getLine("NHV1_NHV2_1").setX(1000);
List<Contingency> contingencies = List.of(new Contingency("NHV1_NHV2_2", List.of(new BranchContingency("NHV1_NHV2_2"))));
List<SensitivityFactor> factors = List.of(createBranchFlowPerInjectionIncrease("NHV1_NHV2_1", "LOAD"));
SensitivityAnalysisParameters parameters = new SensitivityAnalysisParameters();
parameters.getLoadFlowParameters().setDistributedSlack(false);
SensitivityAnalysisResult result = sensiRunner.run(network, factors, contingencies, Collections.emptyList(), parameters);
assertEquals(SensitivityAnalysisResult.Status.FAILURE, result.getContingencyStatus("NHV1_NHV2_2"));
}
}