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

Improve propagating by dealing with internal connections #774

Merged
merged 7 commits into from
Jun 6, 2023
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 @@ -17,13 +17,13 @@
*/
public class ContingencyTripping {

private static final ContingencyTripping NO_OP_TRIPPING = new ContingencyTripping(Collections.emptyList(), (s, tt, nt, n, nbv) -> null);
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,
int initNode, VoltageLevel.NodeBreakerView nodeBreakerView);
VoltageLevel.NodeBreakerView nodeBreakerView);
}

private final List<? extends Terminal> terminals;
Expand Down Expand Up @@ -77,7 +77,7 @@ public static ContingencyTripping createBusbarSectionMinimalTripping(Network net
Objects.requireNonNull(network);
Objects.requireNonNull(bbs);

NodeBreakerTraverserFactory minimalTraverserFactory = (stoppingSwitches, neighbourTerminals, traversedTerminals, n, nbv) ->
NodeBreakerTraverserFactory minimalTraverserFactory = (stoppingSwitches, neighbourTerminals, traversedTerminals, 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
Expand Down Expand Up @@ -163,7 +163,7 @@ private List<Terminal> traverseNodeBreakerVoltageLevelsFromTerminal(Terminal ter

List<Terminal> neighbourTerminals = new ArrayList<>();
VoltageLevel.NodeBreakerView.TopologyTraverser traverser = nodeBreakerTraverserFactory.create(
switchesToOpen, traversedTerminals, neighbourTerminals, initNode, nodeBreakerView);
switchesToOpen, traversedTerminals, neighbourTerminals, nodeBreakerView);
nodeBreakerView.traverse(initNode, traverser);

return neighbourTerminals;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import com.powsybl.iidm.network.*;
import com.powsybl.math.graph.TraverseResult;

import java.util.HashSet;
import java.util.List;
import java.util.Set;

Expand All @@ -20,69 +21,69 @@ public class NodeBreakerTraverser implements VoltageLevel.NodeBreakerView.Topolo
private final Set<Switch> switchesToOpen;
private final Set<Terminal> traversedTerminals;
private final List<Terminal> nextTerminals;
private final int initNode;
private final VoltageLevel.NodeBreakerView nodeBreakerView;

public NodeBreakerTraverser(Set<Switch> switchesToOpen, Set<Terminal> traversedTerminals, List<Terminal> nextTerminals,
int initNode, VoltageLevel.NodeBreakerView nodeBreakerView) {
VoltageLevel.NodeBreakerView nodeBreakerView) {
this.switchesToOpen = switchesToOpen;
this.traversedTerminals = traversedTerminals;
this.nextTerminals = nextTerminals;
this.initNode = initNode;
this.nodeBreakerView = nodeBreakerView;
}

@Override
public TraverseResult traverse(int nodeBefore, Switch sw, int nodeAfter) {
if (sw != null) {
if (sw.isOpen()) {
if (sw != null && sw.isOpen()) {
return TraverseResult.TERMINATE_PATH;
}

if (hasAttachedEquipment(nodeBefore) && traverserStopsAtOtherEdges(sw, nodeBefore, nodeAfter)) {
// Switch is just after a traversed terminal which will be disconnected, and traverser stops at other start edges
if (isOpenable(sw)) {
// The traverser can stop now and no need to retain current switch
return TraverseResult.TERMINATE_PATH;
}

if (nodeBefore == initNode && traverserStopsAtOtherStartEdges(sw, initNode)) {
// Switch is just after contingency and traverser stops at other start edges
if (isOpenable(sw)) {
// The traverser can stop now and no need to retain current switch
return TraverseResult.TERMINATE_PATH;
}
if (traverserWouldStopAfter(sw, nodeAfter)) {
// As the traverser would stop just after, it can stop now (without retaining current switch)
return TraverseResult.TERMINATE_PATH;
}
if (!hasAttachedEquipment(nodeAfter) && traverserStopsAtOtherEdges(sw, nodeAfter, nodeBefore)) {
// As the traverser would stop just after, it can stop now (without retaining current switch)
return TraverseResult.TERMINATE_PATH;
}
}

if (isOpenable(sw)) {
// The current switch is openable: the traverser could stop and the switch could be retained,
// but, to avoid unnecessary retained switches, the traverser does not retain it in two cases
if (traverserWouldStopAfter(sw, nodeAfter)) {
// Continuing traversing might lead in some cases to more retained switches, but in practice the
// switches after are often opened and sometimes followed by an end node
return TraverseResult.CONTINUE;
}
if (isEquivalentToStopAfterSwitch(sw, nodeAfter)) {
// Retaining the switch is equivalent to stop at the node after if the node after the switch is an end node (e.g. load or generator)
sw.getVoltageLevel().getNodeBreakerView().getOptionalTerminal(nodeAfter).ifPresent(this::terminalTraversed);
return TraverseResult.TERMINATE_PATH;
}
switchesToOpen.add(sw);
if (isOpenable(sw)) {
// The current switch is openable: the traverser could stop and the switch could be retained,
// but, to avoid unnecessary retained switches, the traverser does not retain it in two cases
if (!hasAttachedEquipment(nodeAfter) && traverserStopsAtOtherEdges(sw, nodeAfter, nodeBefore)) {
// Continuing traversing might lead in some cases to more retained switches, but in practice the
// switches after are often opened and sometimes followed by an end node
return TraverseResult.CONTINUE;
}
if (isEquivalentToStopAfterSwitch(sw, nodeAfter)) {
// Retaining the switch is equivalent to stop at the node after if the node after the switch is an end node (e.g. load or generator)
nodeBreakerView.getOptionalTerminal(nodeAfter).ifPresent(this::terminalTraversed);
return TraverseResult.TERMINATE_PATH;
}
switchesToOpen.add(sw);
return TraverseResult.TERMINATE_PATH;
}

// The traverser continues, hence nodeAfter is traversed
nodeBreakerView.getOptionalTerminal(nodeAfter).ifPresent(this::terminalTraversed);
return TraverseResult.CONTINUE;
}

private boolean hasAttachedEquipment(int nodeBefore) {
return nodeBreakerView.getOptionalTerminal(nodeBefore).isPresent();
}

private void terminalTraversed(Terminal terminal) {
traversedTerminals.add(terminal);
((Connectable<?>) terminal.getConnectable()).getTerminals().stream()
.filter(t -> t != terminal)
.forEach(nextTerminals::add);
}

private static boolean isEquivalentToStopAfterSwitch(Switch sw, int nodeAfter) {
Terminal terminal2 = sw.getVoltageLevel().getNodeBreakerView().getTerminal(nodeAfter);
private boolean isEquivalentToStopAfterSwitch(Switch sw, int nodeAfter) {
Terminal terminal2 = nodeBreakerView.getTerminal(nodeAfter);
if (terminal2 != null) {
IdentifiableType connectableAfter = terminal2.getConnectable().getType();
boolean endNodeAfter = connectableAfter == IdentifiableType.GENERATOR
Expand All @@ -92,44 +93,55 @@ private static boolean isEquivalentToStopAfterSwitch(Switch sw, int nodeAfter) {
|| connectableAfter == IdentifiableType.SHUNT_COMPENSATOR;

if (endNodeAfter) { // check that there isn't another (closed) switch or internal connection at node after
VoltageLevel.NodeBreakerView nbv = sw.getVoltageLevel().getNodeBreakerView();
return noInternalConnectionAtNode(nodeAfter, nbv)
&& nbv.getSwitchStream(nodeAfter).noneMatch(s -> s != sw && !s.isOpen());
return noInternalConnectionAtNode(nodeAfter)
&& nodeBreakerView.getSwitchStream(nodeAfter).noneMatch(s -> s != sw && !s.isOpen());
}
}
return false;
}

private boolean traverserWouldStopAfter(Switch aSwitch, int nodeAfter) {
// The traverser would stop just after current switch if node after is a junction of switches only,
// with all other switches either opened or openable
VoltageLevel.NodeBreakerView nbv = aSwitch.getVoltageLevel().getNodeBreakerView();
if (!nbv.getOptionalTerminal(nodeAfter).isPresent() && noInternalConnectionAtNode(nodeAfter, nbv)) {
// No terminal nor internal connection at node after, thus there are only switches
return allOtherSwitchesOpenOrOpenable(aSwitch, nodeAfter, nbv);
}
return false;
/**
* Return if propagation would stop at all edges starting from nodeStart, but different from the edge defined by
* nodeStart ---aSwitch--- nodeEnd,
*/
private boolean traverserStopsAtOtherEdges(Switch aSwitch, int nodeStart, int nodeEnd) {
// The traverser stops at other start edges if node is a direct or indirect junction of switches only, with all
// other switches either opened or openable.
// An indirect junction means through internal connections.
return internalConnectionsEndOnOpenOrOpenableSwitches(nodeStart, nodeEnd)
&& allOtherSwitchesOpenOrOpenable(aSwitch, nodeStart);
}

private boolean traverserStopsAtOtherStartEdges(Switch aSwitch, int initNode) {
// The traverser stops at other start edges if:
// - no internal connection at init node
// - and all other switches connected to init node are either open or openable
VoltageLevel.NodeBreakerView nbv = aSwitch.getVoltageLevel().getNodeBreakerView();
return noInternalConnectionAtNode(initNode, nbv)
&& allOtherSwitchesOpenOrOpenable(aSwitch, initNode, nbv);
private boolean allOtherSwitchesOpenOrOpenable(Switch aSwitch, int node) {
return nodeBreakerView.getSwitchStream(node).filter(s -> s != aSwitch).allMatch(NodeBreakerTraverser::isOpenOrOpenable);
}

private static boolean allOtherSwitchesOpenOrOpenable(Switch aSwitch, int node, VoltageLevel.NodeBreakerView nbv) {
return nbv.getSwitchStream(node).filter(s -> s != aSwitch).allMatch(NodeBreakerTraverser::isOpenOrOpenable);
private boolean noInternalConnectionAtNode(int node) {
return nodeBreakerView.getNodeInternalConnectedToStream(node).findFirst().isEmpty();
}

private static boolean noInternalConnectionAtNode(int node, VoltageLevel.NodeBreakerView nbv) {
return nbv.getNodeInternalConnectedToStream(node).findFirst().isEmpty();
private boolean internalConnectionsEndOnOpenOrOpenableSwitches(int node, int dismissedNode) {
Set<Integer> visitedNodes = new HashSet<>();
visitedNodes.add(dismissedNode);
return internalConnectionsEndOnOpenOrOpenableSwitches(node, visitedNodes);
}

private boolean internalConnectionsEndOnOpenOrOpenableSwitches(int nStart, Set<Integer> visitedNodes) {
if (!visitedNodes.contains(nStart)) {
visitedNodes.add(nStart);
for (int n : nodeBreakerView.getNodesInternalConnectedTo(nStart)) {
if (!visitedNodes.contains(n) && (hasAttachedEquipment(n)
|| nodeBreakerView.getSwitchStream(n).anyMatch(s -> !NodeBreakerTraverser.isOpenOrOpenable(s))
|| !internalConnectionsEndOnOpenOrOpenableSwitches(n, visitedNodes))) {
return false;
}
}
}
return true;
}

private static boolean isOpenable(Switch aSwitch) {
return !aSwitch.isFictitious() && aSwitch.getKind() == SwitchKind.BREAKER;
return aSwitch != null && !aSwitch.isFictitious() && aSwitch.getKind() == SwitchKind.BREAKER;
}

private static boolean isOpenOrOpenable(Switch aSwitch) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,39 @@ void testInternalConnection() {
checkTerminalIds(terminalsToDisconnect, "D", "CE", "CI", "CJ");
}

@Test
void testInternalConnectionEndingAtSwitches() {
Network network = FictitiousSwitchFactory.create();

network.getSwitch("L").setFictitious(false);
network.getSwitch("BB").setFictitious(false);

VoltageLevel.NodeBreakerView c = network.getVoltageLevel("C").getNodeBreakerView();
c.newInternalConnection().setNode1(4).setNode2(5).add();
c.newInternalConnection().setNode1(5).setNode2(6).add();
c.newBreaker().setId("ZZ").setNode1(6).setNode2(0).add();

Set<Switch> switchesToOpen = new HashSet<>();
Set<Terminal> terminalsToDisconnect = new HashSet<>();
ContingencyTripping.createBranchTripping(network, network.getBranch("CJ")).traverse(switchesToOpen, terminalsToDisconnect);
assertTrue(switchesToOpen.isEmpty());
checkTerminalIds(terminalsToDisconnect, "CJ");

c.newInternalConnection().setNode1(5).setNode2(7).add();
Switch b = c.newBreaker().setId("ZY").setNode1(7).setNode2(0).add();

terminalsToDisconnect.clear();
ContingencyTripping.createBranchTripping(network, network.getBranch("CJ")).traverse(switchesToOpen, terminalsToDisconnect);
assertTrue(switchesToOpen.isEmpty());
checkTerminalIds(terminalsToDisconnect, "CJ");

b.setFictitious(true);
terminalsToDisconnect.clear();
ContingencyTripping.createBranchTripping(network, network.getBranch("CJ")).traverse(switchesToOpen, terminalsToDisconnect);
checkSwitches(switchesToOpen, "L", "ZZ");
checkTerminalIds(terminalsToDisconnect, "D", "CI", "CJ");
}

@Test
void testStopAtStartEdges() {
Network network = FictitiousSwitchFactory.create();
Expand Down