ContingencyTripping.java
/**
* Copyright (c) 2020, 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.powsybl.commons.PowsyblException;
import com.powsybl.iidm.network.*;
import com.powsybl.math.graph.TraverseResult;
import java.util.*;
/**
* @author Florian Dupuy {@literal <florian.dupuy at rte-france.com>}
*/
public class ContingencyTripping {
private static final ContingencyTripping NO_OP_TRIPPING = new ContingencyTripping(Collections.emptyList(), (s, tt, nt, nbv) -> null);
@FunctionalInterface
private interface NodeBreakerTraverserFactory {
VoltageLevel.NodeBreakerView.TopologyTraverser create(
Set<Switch> stoppingSwitches, Set<Terminal> traversedTerminals, List<Terminal> neighbourTerminals,
VoltageLevel.NodeBreakerView nodeBreakerView);
}
private final List<? extends Terminal> terminals;
private final NodeBreakerTraverserFactory nodeBreakerTraverserFactory;
public ContingencyTripping(List<? extends Terminal> terminals, NodeBreakerTraverserFactory nodeBreakerTraverserFactory) {
this.terminals = terminals;
this.nodeBreakerTraverserFactory = nodeBreakerTraverserFactory;
}
public ContingencyTripping(Terminal terminal, NodeBreakerTraverserFactory nodeBreakerTraverserFactory) {
this(List.of(terminal), nodeBreakerTraverserFactory);
}
public static ContingencyTripping createBranchTripping(Network network, Branch<?> branch) {
return createBranchTripping(network, branch, null);
}
public static ContingencyTripping createBranchTripping(Network network, Branch<?> branch, String voltageLevelId) {
Objects.requireNonNull(network);
Objects.requireNonNull(branch);
if (voltageLevelId != null) {
if (voltageLevelId.equals(branch.getTerminal1().getVoltageLevel().getId())) {
return new ContingencyTripping(branch.getTerminal1(), NodeBreakerTraverser::new);
} else if (voltageLevelId.equals(branch.getTerminal2().getVoltageLevel().getId())) {
return new ContingencyTripping(branch.getTerminal2(), NodeBreakerTraverser::new);
} else {
throw new PowsyblException("VoltageLevel '" + voltageLevelId + "' not connected to branch '" + branch.getId() + "'");
}
} else {
return new ContingencyTripping(List.of(branch.getTerminal1(), branch.getTerminal2()), NodeBreakerTraverser::new);
}
}
public static ContingencyTripping createInjectionTripping(Network network, Injection<?> injection) {
Objects.requireNonNull(network);
Objects.requireNonNull(injection);
return new ContingencyTripping(injection.getTerminal(), NodeBreakerTraverser::new);
}
public static ContingencyTripping createThreeWindingsTransformerTripping(Network network, ThreeWindingsTransformer twt) {
Objects.requireNonNull(network);
Objects.requireNonNull(twt);
return new ContingencyTripping(twt.getTerminals(), NodeBreakerTraverser::new);
}
public static ContingencyTripping createBusbarSectionMinimalTripping(Network network, BusbarSection bbs) {
Objects.requireNonNull(network);
Objects.requireNonNull(bbs);
NodeBreakerTraverserFactory minimalTraverserFactory = (stoppingSwitches, traversedTerminals, neighbourTerminals, nbv) ->
// To have the minimal tripping ("no propagation") with a busbar section we still need to traverse the
// voltage level starting from that busbar section, stopping at first switch encountered (which will be
// marked as retained afterwards), in order to have the smallest lost bus in breaker view
// Note that neighbourTerminals is not filled up: we don't want to propagate to neighbouring voltage levels as
// this is a minimal tripping ("no propagation")
(nodeBefore, sw, nodeAfter) -> {
if (sw != null) {
if (!sw.isOpen()) {
stoppingSwitches.add(sw);
}
return TraverseResult.TERMINATE_PATH;
} else {
nbv.getOptionalTerminal(nodeAfter).ifPresent(traversedTerminals::add);
return TraverseResult.CONTINUE;
}
};
return new ContingencyTripping(bbs.getTerminal(), minimalTraverserFactory);
}
public static ContingencyTripping createContingencyTripping(Network network, Identifiable<?> identifiable) {
switch (identifiable.getType()) {
case LINE,
TWO_WINDINGS_TRANSFORMER,
TIE_LINE:
return ContingencyTripping.createBranchTripping(network, (Branch<?>) identifiable);
case DANGLING_LINE,
GENERATOR,
LOAD,
SHUNT_COMPENSATOR,
STATIC_VAR_COMPENSATOR,
BUSBAR_SECTION,
BATTERY:
return ContingencyTripping.createInjectionTripping(network, (Injection<?>) identifiable);
case THREE_WINDINGS_TRANSFORMER:
return ContingencyTripping.createThreeWindingsTransformerTripping(network, (ThreeWindingsTransformer) identifiable);
case HVDC_LINE,
SWITCH:
return ContingencyTripping.NO_OP_TRIPPING;
default:
throw new UnsupportedOperationException("Unsupported contingency element type: " + identifiable.getType());
}
}
public void traverse(Set<Switch> switchesToOpen, Set<Terminal> terminalsToDisconnect) {
Set<Terminal> traversedTerminals = new HashSet<>();
terminals.forEach(t -> traverseFromTerminal(t, switchesToOpen, traversedTerminals));
terminalsToDisconnect.addAll(traversedTerminals);
}
/**
* Recursive method to calculate the switches to open and the traversed terminals from a terminal
* @param terminal starting terminal
* @param switchesToOpen set of switches which would be opened by the contingency propagation from terminal
* @param traversedTerminals set of terminals traversed by the contingency propagation
*/
private void traverseFromTerminal(Terminal terminal, Set<Switch> switchesToOpen, Set<Terminal> traversedTerminals) {
Objects.requireNonNull(terminal);
Objects.requireNonNull(switchesToOpen);
Objects.requireNonNull(traversedTerminals);
if (traversedTerminals.contains(terminal)) {
return;
}
if (terminal.getVoltageLevel().getTopologyKind() == TopologyKind.NODE_BREAKER) {
traversedTerminals.add(terminal);
List<Terminal> neighbourTerminals = traverseNodeBreakerVoltageLevelsFromTerminal(terminal, switchesToOpen, traversedTerminals);
// Recursive call to continue the traverser in affected neighbouring voltage levels
neighbourTerminals.forEach(t -> traverseFromTerminal(t, switchesToOpen, traversedTerminals));
} else {
// In bus breaker view we have no idea what kind of switch it was in the initial node/breaker topology
// so to keep things simple we do not propagate the fault
if (terminal.isConnected()) {
traversedTerminals.add(terminal);
}
}
}
private List<Terminal> traverseNodeBreakerVoltageLevelsFromTerminal(Terminal terminal, Set<Switch> switchesToOpen,
Set<Terminal> traversedTerminals) {
int initNode = terminal.getNodeBreakerView().getNode();
VoltageLevel.NodeBreakerView nodeBreakerView = terminal.getVoltageLevel().getNodeBreakerView();
List<Terminal> neighbourTerminals = new ArrayList<>();
VoltageLevel.NodeBreakerView.TopologyTraverser traverser = nodeBreakerTraverserFactory.create(
switchesToOpen, traversedTerminals, neighbourTerminals, nodeBreakerView);
nodeBreakerView.traverse(initNode, traverser);
return neighbourTerminals;
}
}