com.powsybl.openloadflow.network.impl.PropagatedContingency Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of powsybl-open-loadflow Show documentation
Show all versions of powsybl-open-loadflow Show documentation
An open source loadflow based on PowSyBl
The newest version!
/**
* Copyright (c) 2021, RTE (http://www.rte-france.com)
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
* SPDX-License-Identifier: MPL-2.0
*/
package com.powsybl.openloadflow.network.impl;
import com.google.common.collect.Sets;
import com.powsybl.commons.PowsyblException;
import com.powsybl.contingency.Contingency;
import com.powsybl.contingency.ContingencyElement;
import com.powsybl.iidm.network.*;
import com.powsybl.iidm.network.util.HvdcUtils;
import com.powsybl.openloadflow.graph.GraphConnectivity;
import com.powsybl.openloadflow.network.*;
import com.powsybl.openloadflow.util.PerUnit;
import org.apache.commons.lang3.tuple.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
import java.util.stream.Collectors;
/**
* @author Geoffroy Jamgotchian {@literal }
* @author Gaël Macherel {@literal }
*/
public class PropagatedContingency {
protected static final Logger LOGGER = LoggerFactory.getLogger(PropagatedContingency.class);
private final Contingency contingency;
private final int index;
private final Set switchesToOpen;
private final Set terminalsToDisconnect;
private final Set busIdsToLose;
private final Map branchIdsToOpen = new LinkedHashMap<>();
private final Set hvdcIdsToOpen = new HashSet<>();
private final Set generatorIdsToLose = new HashSet<>();
private final Map loadIdsToLose = new HashMap<>();
private final Map shuntIdsToShift = new HashMap<>();
public Contingency getContingency() {
return contingency;
}
public int getIndex() {
return index;
}
public Set getBusIdsToLose() {
return busIdsToLose;
}
public Map getBranchIdsToOpen() {
return branchIdsToOpen;
}
public Set getGeneratorIdsToLose() {
return generatorIdsToLose;
}
public Map getLoadIdsToLose() {
return loadIdsToLose;
}
public PropagatedContingency(Contingency contingency, int index, Set switchesToOpen, Set terminalsToDisconnect,
Set busIdsToLose) {
this.contingency = Objects.requireNonNull(contingency);
this.index = index;
this.switchesToOpen = Objects.requireNonNull(switchesToOpen);
this.terminalsToDisconnect = Objects.requireNonNull(terminalsToDisconnect);
this.busIdsToLose = Objects.requireNonNull(busIdsToLose);
}
public static List createList(Network network, List contingencies, LfTopoConfig topoConfig,
PropagatedContingencyCreationParameters creationParameters) {
List propagatedContingencies = new ArrayList<>();
for (int index = 0; index < contingencies.size(); index++) {
Contingency contingency = contingencies.get(index);
PropagatedContingency propagatedContingency =
PropagatedContingency.create(network, contingency, index, topoConfig, creationParameters);
propagatedContingencies.add(propagatedContingency);
topoConfig.getSwitchesToOpen().addAll(propagatedContingency.switchesToOpen);
topoConfig.getBusIdsToLose().addAll(propagatedContingency.busIdsToLose);
}
return propagatedContingencies;
}
private static PropagatedContingency create(Network network, Contingency contingency, int index, LfTopoConfig topoConfig,
PropagatedContingencyCreationParameters creationParameters) {
Set switchesToOpen = new HashSet<>();
Set terminalsToDisconnect = new HashSet<>();
Set busIdsToLose = new HashSet<>();
// process elements of the contingency
for (ContingencyElement element : contingency.getElements()) {
Identifiable> identifiable = getIdentifiable(network, element);
switch (identifiable.getType()) {
case BUS:
Bus bus = (Bus) identifiable;
if (bus.getVoltageLevel().getTopologyKind() == TopologyKind.BUS_BREAKER) {
busIdsToLose.add(identifiable.getId());
bus.visitConnectedEquipments(new DefaultTopologyVisitor() {
public void visitBranch(Branch> branch, TwoSides side) {
if (side == TwoSides.ONE) {
topoConfig.getBranchIdsOpenableSide1().add(branch.getId());
} else {
topoConfig.getBranchIdsOpenableSide2().add(branch.getId());
}
}
@Override
public void visitLine(Line line, TwoSides side) {
visitBranch(line, side);
}
@Override
public void visitTwoWindingsTransformer(TwoWindingsTransformer transformer, TwoSides side) {
visitBranch(transformer, side);
}
@Override
public void visitThreeWindingsTransformer(ThreeWindingsTransformer transformer, ThreeSides side) {
topoConfig.getBranchIdsOpenableSide1().add(LfLegBranch.getId(transformer.getId(), side.getNum()));
}
});
} else {
throw new UnsupportedOperationException("Unsupported contingency element type " + element.getType() + ": voltage level should be in bus/breaker topology");
}
break;
case BUSBAR_SECTION:
if (creationParameters.isContingencyPropagation()) {
ContingencyTripping.createContingencyTripping(network, identifiable).traverse(switchesToOpen, terminalsToDisconnect);
} else {
ContingencyTripping.createBusbarSectionMinimalTripping(network, (BusbarSection) identifiable).traverse(switchesToOpen, terminalsToDisconnect);
}
terminalsToDisconnect.addAll(getTerminals(identifiable));
break;
case SWITCH:
switchesToOpen.add((Switch) identifiable);
break;
default:
if (creationParameters.isContingencyPropagation()) {
ContingencyTripping.createContingencyTripping(network, identifiable).traverse(switchesToOpen, terminalsToDisconnect);
}
terminalsToDisconnect.addAll(getTerminals(identifiable));
}
}
PropagatedContingency propagatedContingency = new PropagatedContingency(contingency, index, switchesToOpen, terminalsToDisconnect, busIdsToLose);
propagatedContingency.complete(topoConfig, creationParameters);
return propagatedContingency;
}
private void addBranchToOpen(K branchId, DisabledBranchStatus status, Map branchIdsToOpen) {
DisabledBranchStatus oldStatus = branchIdsToOpen.get(branchId);
if (oldStatus == null) {
branchIdsToOpen.put(branchId, status);
} else if (status == DisabledBranchStatus.BOTH_SIDES || status != oldStatus) {
branchIdsToOpen.put(branchId, DisabledBranchStatus.BOTH_SIDES);
}
}
private void complete(LfTopoConfig topoConfig, PropagatedContingencyCreationParameters creationParameters) {
for (Switch sw : switchesToOpen) {
addBranchToOpen(sw.getId(), DisabledBranchStatus.BOTH_SIDES, branchIdsToOpen); // we open both sides
}
// process terminals disconnected, in particular process injection power shift
for (Terminal terminal : terminalsToDisconnect) {
Connectable> connectable = terminal.getConnectable();
switch (connectable.getType()) {
case LINE,
TWO_WINDINGS_TRANSFORMER:
Branch> branch = (Branch>) connectable;
if (terminal == branch.getTerminal1()) {
addBranchToOpen(connectable.getId(), DisabledBranchStatus.SIDE_1, branchIdsToOpen);
topoConfig.getBranchIdsOpenableSide1().add(connectable.getId());
} else {
addBranchToOpen(connectable.getId(), DisabledBranchStatus.SIDE_2, branchIdsToOpen);
topoConfig.getBranchIdsOpenableSide2().add(connectable.getId());
}
break;
case DANGLING_LINE:
DanglingLine dl = (DanglingLine) connectable;
// as we terminal is only on network side, we open both sides in LF network
if (dl.isPaired()) {
addBranchToOpen(dl.getTieLine().orElseThrow().getId(), DisabledBranchStatus.BOTH_SIDES, branchIdsToOpen);
} else {
addBranchToOpen(dl.getId(), DisabledBranchStatus.BOTH_SIDES, branchIdsToOpen);
}
break;
case GENERATOR,
STATIC_VAR_COMPENSATOR,
BATTERY:
generatorIdsToLose.add(connectable.getId());
break;
case LOAD:
Load load = (Load) connectable;
loadIdsToLose.put(load.getId(), PowerShift.createPowerShift(load, creationParameters.isSlackDistributionOnConformLoad()));
break;
case SHUNT_COMPENSATOR:
ShuntCompensator shunt = (ShuntCompensator) connectable;
if (creationParameters.isShuntCompensatorVoltageControlOn() && shunt.isVoltageRegulatorOn()) {
throw new UnsupportedOperationException("Shunt compensator '" + shunt.getId() + "' with voltage control on: not supported yet");
}
double zb = PerUnit.zb(shunt.getTerminal().getVoltageLevel().getNominalV());
shuntIdsToShift.put(shunt.getId(), new AdmittanceShift(shunt.getG() * zb,
shunt.getB() * zb));
break;
case HVDC_CONVERTER_STATION:
// in case of a hvdc contingency, both converter station will go through this case.
// in case of the lost of one VSC converter station only, the transmission of active power is stopped
// but the other converter station, if present, keeps it voltage control if present.
HvdcConverterStation> station = (HvdcConverterStation>) connectable;
hvdcIdsToOpen.add(station.getHvdcLine().getId());
if (connectable instanceof VscConverterStation) {
generatorIdsToLose.add(connectable.getId());
} else {
LccConverterStation lcc = (LccConverterStation) connectable;
PowerShift lccPowerShift = new PowerShift(HvdcUtils.getConverterStationTargetP(lcc) / PerUnit.SB, 0,
HvdcUtils.getLccConverterStationLoadTargetQ(lcc) / PerUnit.SB);
loadIdsToLose.put(lcc.getId(), lccPowerShift);
}
break;
case BUSBAR_SECTION:
// we don't care
break;
case THREE_WINDINGS_TRANSFORMER:
// terminal in always by construction the side 1 of the LF branch
ThreeWindingsTransformer twt = (ThreeWindingsTransformer) connectable;
for (ThreeSides side : ThreeSides.values()) {
if (twt.getTerminal(side) == terminal) {
addBranchToOpen(LfLegBranch.getId(side, connectable.getId()), DisabledBranchStatus.SIDE_1, branchIdsToOpen);
topoConfig.getBranchIdsOpenableSide1().add(LfLegBranch.getId(connectable.getId(), side.getNum()));
break;
}
}
break;
default:
throw new UnsupportedOperationException("Unsupported by propagation contingency element type: "
+ connectable.getType());
}
}
}
private static List extends Terminal> getTerminals(Identifiable> identifiable) {
if (identifiable instanceof Connectable>) {
return ((Connectable>) identifiable).getTerminals();
}
if (identifiable instanceof HvdcLine hvdcLine) {
return List.of(hvdcLine.getConverterStation1().getTerminal(), hvdcLine.getConverterStation2().getTerminal());
}
if (identifiable instanceof TieLine line) {
return List.of(line.getDanglingLine1().getTerminal(), line.getDanglingLine2().getTerminal());
}
if (identifiable instanceof Switch) {
return Collections.emptyList();
}
throw new UnsupportedOperationException("Unsupported contingency element type: " + identifiable.getType());
}
private static Identifiable> getIdentifiable(Network network, ContingencyElement element) {
Identifiable> identifiable;
String identifiableType = switch (element.getType()) {
case BRANCH, LINE, TWO_WINDINGS_TRANSFORMER -> {
identifiable = network.getBranch(element.getId());
yield "Branch";
}
case HVDC_LINE -> {
identifiable = network.getHvdcLine(element.getId());
yield "HVDC line";
}
case DANGLING_LINE -> {
identifiable = network.getDanglingLine(element.getId());
yield "Dangling line";
}
case GENERATOR -> {
identifiable = network.getGenerator(element.getId());
yield "Generator";
}
case STATIC_VAR_COMPENSATOR -> {
identifiable = network.getStaticVarCompensator(element.getId());
yield "Static var compensator";
}
case LOAD -> {
identifiable = network.getLoad(element.getId());
yield "Load";
}
case SHUNT_COMPENSATOR -> {
identifiable = network.getShuntCompensator(element.getId());
yield "Shunt compensator";
}
case SWITCH -> {
identifiable = network.getSwitch(element.getId());
yield "Switch";
}
case THREE_WINDINGS_TRANSFORMER -> {
identifiable = network.getThreeWindingsTransformer(element.getId());
yield "Three windings transformer";
}
case BUSBAR_SECTION -> {
identifiable = network.getBusbarSection(element.getId());
yield "Busbar section";
}
case TIE_LINE -> {
identifiable = network.getTieLine(element.getId());
yield "Tie line";
}
case BUS -> {
identifiable = network.getBusBreakerView().getBus(element.getId());
yield "Configured bus";
}
case BATTERY -> {
identifiable = network.getBattery(element.getId());
yield "Battery";
}
default ->
throw new UnsupportedOperationException("Unsupported contingency element type: " + element.getType());
};
if (identifiable == null) {
throw new PowsyblException(identifiableType + " '" + element.getId() + "' not found in the network");
}
return identifiable;
}
public boolean hasNoImpact() {
return branchIdsToOpen.isEmpty()
&& hvdcIdsToOpen.isEmpty() && generatorIdsToLose.isEmpty()
&& loadIdsToLose.isEmpty() && shuntIdsToShift.isEmpty() && busIdsToLose.isEmpty();
}
private static boolean isSlackBusIsolated(GraphConnectivity connectivity, LfBus slackBus) {
// check that slack bus belongs to the largest component.
// Largest component has always the number 0.
int number = connectivity.getComponentNumber(slackBus);
if (number != 0) {
// if not main component anymore but same size as the main one, still consider it as not isolated
// (mainly useful for unit test small networks...)
return connectivity.getLargestConnectedComponent().size() != connectivity.getConnectedComponent(slackBus).size();
}
return false;
}
private Map findBranchToOpenDirectlyImpactedByContingency(LfNetwork network) {
// we add the branches connected to buses to lose.
Map branchesToOpen = branchIdsToOpen.entrySet().stream()
.map(e -> Pair.of(network.getBranchById(e.getKey()), e.getValue()))
.filter(e -> e.getKey() != null)
.collect(Collectors.toMap(Pair::getKey, Pair::getValue, (disabledBranchStatus, disabledBranchStatus2) -> {
throw new IllegalStateException();
}, LinkedHashMap::new));
busIdsToLose.stream().map(network::getBusById)
.filter(Objects::nonNull)
.forEach(bus -> {
bus.getBranches().forEach(branch -> {
DisabledBranchStatus status = branch.getBus1() == bus ? DisabledBranchStatus.SIDE_1 : DisabledBranchStatus.SIDE_2;
addBranchToOpen(branch, status, branchesToOpen);
});
});
return branchesToOpen;
}
record ContingencyConnectivityLossImpact(boolean ok, int createdSynchronousComponents, Set busesToLost, Set hvdcsWithoutPower) {
}
private ContingencyConnectivityLossImpact findBusesAndBranchesImpactedBecauseOfConnectivityLoss(LfNetwork network, Map branchesToOpen, boolean relocateSlackBus) {
// update connectivity with triggered branches of this network
// note that this will define the main component as the one containing the first slack bus
GraphConnectivity connectivity = network.getConnectivity();
connectivity.startTemporaryChanges();
try {
branchesToOpen.keySet().stream()
.filter(LfBranch::isConnectedAtBothSides)
.forEach(connectivity::removeEdge);
if (relocateSlackBus && isSlackBusIsolated(connectivity, network.getSlackBus())) {
LOGGER.warn("Contingency '{}' leads to an isolated slack bus: relocate slack bus inside main component",
contingency.getId());
// if a contingency leads to an isolated slack bus, we need to relocate the slack bus
// we select a new slack bus excluding buses from isolated component
Set excludedBuses = Sets.difference(Set.copyOf(network.getBuses()), connectivity.getLargestConnectedComponent());
network.setExcludedSlackBuses(excludedBuses);
// reverse main component to the one containing the relocated slack bus
connectivity.setMainComponentVertex(network.getSlackBus());
}
// add to contingency description buses and branches that won't be part of the main connected
// component in post contingency state
int createdSynchronousComponents = connectivity.getNbConnectedComponents() - 1;
Set busesToLost = connectivity.getVerticesRemovedFromMainComponent();
// as we know here the connectivity after contingency, we have to reset active power flow of a hvdc line
// if one bus of the line is lost.
Set hvdcsWithoutFlow = new HashSet<>();
for (LfHvdc hvdcLine : network.getHvdcs()) {
if (checkIsolatedBus(hvdcLine.getBus1(), hvdcLine.getBus2(), busesToLost, connectivity)
|| checkIsolatedBus(hvdcLine.getBus2(), hvdcLine.getBus1(), busesToLost, connectivity)) {
hvdcsWithoutFlow.add(hvdcLine);
}
}
return new ContingencyConnectivityLossImpact(true, createdSynchronousComponents, busesToLost, hvdcsWithoutFlow);
} finally {
// reset connectivity to discard triggered elements
connectivity.undoTemporaryChanges();
}
}
private boolean checkIsolatedBus(LfBus bus1, LfBus bus2, Set busesToLost, GraphConnectivity connectivity) {
return busesToLost.contains(bus1) && !busesToLost.contains(bus2) && Networks.isIsolatedBusForHvdc(bus1, connectivity);
}
private static boolean isConnectedAfterContingencySide1(Map branchesToOpen, LfBranch branch) {
DisabledBranchStatus status = branchesToOpen.get(branch);
return status == null || status == DisabledBranchStatus.SIDE_2;
}
private static boolean isConnectedAfterContingencySide2(Map branchesToOpen, LfBranch branch) {
DisabledBranchStatus status = branchesToOpen.get(branch);
return status == null || status == DisabledBranchStatus.SIDE_1;
}
public Optional toLfContingency(LfNetwork network) {
return toLfContingency(network, true);
}
public Optional toLfContingency(LfNetwork network, boolean relocateSlackBus) {
// find branch to open because of direct impact of the contingency (including propagation is activated)
Map branchesToOpen = findBranchToOpenDirectlyImpactedByContingency(network);
// find branches to open and buses to lost not directly from the contingency impact but as a consequence of
// loss of connectivity once contingency applied on the network
ContingencyConnectivityLossImpact connectivityLossImpact = findBusesAndBranchesImpactedBecauseOfConnectivityLoss(network, branchesToOpen, relocateSlackBus);
if (!connectivityLossImpact.ok) {
return Optional.empty();
}
Set busesToLost = connectivityLossImpact.busesToLost(); // nothing else
for (LfBus busToLost : busesToLost) {
busToLost.getBranches()
.forEach(branch -> {
// fully disable if branch is connected to 2 buses to lost or open on the other side
LfBus otherSideBus;
boolean otherSideConnected;
if (branch.getBus1() == busToLost) {
otherSideBus = branch.getBus2();
otherSideConnected = branch.isConnectedSide2() && isConnectedAfterContingencySide2(branchesToOpen, branch);
} else {
otherSideBus = branch.getBus1();
otherSideConnected = branch.isConnectedSide1() && isConnectedAfterContingencySide1(branchesToOpen, branch);
}
if (busesToLost.contains(otherSideBus) || !otherSideConnected) {
addBranchToOpen(branch, DisabledBranchStatus.BOTH_SIDES, branchesToOpen);
}
});
}
Map shunts = new LinkedHashMap<>(1);
for (var e : shuntIdsToShift.entrySet()) {
LfShunt shunt = network.getShuntById(e.getKey());
if (shunt != null) { // could be in another component
shunts.computeIfAbsent(shunt, k -> new AdmittanceShift())
.add(e.getValue());
}
}
Set generators = new LinkedHashSet<>(1);
for (String generatorId : generatorIdsToLose) {
LfGenerator generator = network.getGeneratorById(generatorId);
if (generator != null) { // could be in another component
generators.add(generator);
}
}
Map loads = new LinkedHashMap<>(1);
for (var e : loadIdsToLose.entrySet()) {
String loadId = e.getKey();
PowerShift powerShift = e.getValue();
LfLoad load = network.getLoadById(loadId);
if (load != null) { // could be in another component
LfLostLoad lostLoad = loads.computeIfAbsent(load, k -> new LfLostLoad());
lostLoad.getPowerShift().add(powerShift);
lostLoad.getOriginalIds().add(loadId);
lostLoad.updateNotParticipatingLoadP0(load, loadId, powerShift);
}
}
// find hvdc lines that are part of this network
Set lostHvdcs = hvdcIdsToOpen.stream()
.map(network::getHvdcById)
.filter(Objects::nonNull) // could be in another component
.collect(Collectors.toCollection(LinkedHashSet::new));
for (LfHvdc hvdcLine : network.getHvdcs()) {
if (busesToLost.contains(hvdcLine.getBus1()) || busesToLost.contains(hvdcLine.getBus2())) {
lostHvdcs.add(hvdcLine);
}
}
if (branchesToOpen.isEmpty()
&& busesToLost.isEmpty()
&& shunts.isEmpty()
&& loads.isEmpty()
&& generators.isEmpty()
&& lostHvdcs.isEmpty()
&& connectivityLossImpact.hvdcsWithoutPower().isEmpty()) {
LOGGER.debug("Contingency '{}' has no impact", contingency.getId());
return Optional.empty();
}
return Optional.of(new LfContingency(contingency.getId(), index, connectivityLossImpact.createdSynchronousComponents,
new DisabledNetwork(busesToLost, branchesToOpen, lostHvdcs), shunts, loads, generators,
connectivityLossImpact.hvdcsWithoutPower()));
}
/**
* In general, a {@link PropagatedContingency} is translated to a {@link LfContingency}. During the translation,
* cleans are performed to deal with branches connected at one side and slack bus loss. But in some fast DC computations,
* we don't want to use {@link LfContingency} object, so a dedicated clean method directly available on a {@link PropagatedContingency}
* is needed. It contains:
* - Removing branches out of this synchronous component from branches to open.
* - Removing branches connected at one side from branches to open.
* - Removing slack bus from buses lost (not supported yet).
* - Adding branches connected to buses lost in branches to open.
*/
public static void cleanContingencies(LfNetwork lfNetwork, List contingencies) {
for (PropagatedContingency contingency : contingencies) {
// Elements have already been checked and found in PropagatedContingency, so there is no need to
// check them again
Set branchesToRemove = new HashSet<>(); // branches connected to one side, or switches
for (String branchId : contingency.getBranchIdsToOpen().keySet()) {
LfBranch lfBranch = lfNetwork.getBranchById(branchId);
if (lfBranch == null) {
branchesToRemove.add(branchId); // disconnected branch
continue;
}
if (!lfBranch.isConnectedAtBothSides()) {
branchesToRemove.add(branchId); // branch connected only on one side
}
}
branchesToRemove.forEach(branchToRemove -> contingency.getBranchIdsToOpen().remove(branchToRemove));
// update branches to open connected with buses in contingency. This is an approximation:
// these branches are indeed just open at one side.
String slackBusId = null;
for (String busId : contingency.getBusIdsToLose()) {
LfBus bus = lfNetwork.getBusById(busId);
if (bus != null) {
if (bus.isSlack()) {
// slack bus disabling is not supported in DC because the relocation is done from propagated contingency
// to LfContingency
// we keep the slack bus enabled and the connected branches
LOGGER.error("Contingency '{}' leads to the loss of a slack bus: slack bus kept", contingency.getContingency().getId());
slackBusId = busId;
} else {
bus.getBranches().forEach(branch -> contingency.getBranchIdsToOpen().put(branch.getId(), DisabledBranchStatus.BOTH_SIDES));
}
}
}
if (slackBusId != null) {
contingency.getBusIdsToLose().remove(slackBusId);
}
if (contingency.hasNoImpact()) {
LOGGER.warn("Contingency '{}' has no impact", contingency.getContingency().getId());
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy