diff --git a/network-store-iidm-impl/src/main/java/com/powsybl/network/store/iidm/impl/TerminalImpl.java b/network-store-iidm-impl/src/main/java/com/powsybl/network/store/iidm/impl/TerminalImpl.java index f197a2b1c..8b10a834e 100644 --- a/network-store-iidm-impl/src/main/java/com/powsybl/network/store/iidm/impl/TerminalImpl.java +++ b/network-store-iidm-impl/src/main/java/com/powsybl/network/store/iidm/impl/TerminalImpl.java @@ -146,7 +146,7 @@ private Resource getVoltageLevelResource() { private Set getBusbarSectionNodes(Resource voltageLevelResource) { return index.getStoreClient().getVoltageLevelBusbarSections(index.getNetwork().getUuid(), index.getWorkingVariantNum(), voltageLevelResource.getId()) .stream().map(resource -> resource.getAttributes().getNode()) - .collect(Collectors.toSet()); + .collect(Collectors.toCollection(LinkedHashSet::new)); } private static Graph filterSwitches(Graph graph, Predicate filter) { @@ -242,63 +242,54 @@ private boolean disconnectNodeBreaker(Resource voltageLe // create a graph with only closed switches Graph graph = NodeBreakerTopology.INSTANCE.buildGraph(index, voltageLevelResource, false, true); Set busbarSectionNodes = getBusbarSectionNodes(voltageLevelResource); + Integer bbs0 = busbarSectionNodes.iterator().next(); + for (Integer busbarSectionNode : busbarSectionNodes) { + graph.addEdge(bbs0, busbarSectionNode, new Edge(new InternalConnectionAttributes(bbs0, busbarSectionNode))); + } // inspect connectivity of graph without its non fictitious breakers to check if disconnection is possible Predicate isSwitchOpenable = switchAttributes -> switchAttributes.getKind() == SwitchKind.BREAKER && !switchAttributes.isFictitious() && !switchAttributes.isOpen(); - ConnectivityInspector connectivityInspector - = new ConnectivityInspector<>(filterSwitches(graph, isSwitchOpenable.negate())); + Graph noOpenableSwitchGraph = filterSwitches(graph, isSwitchOpenable.negate()); + ConnectivityInspector connectivityInspector = new ConnectivityInspector<>(noOpenableSwitchGraph); List> connectedSets = connectivityInspector.connectedSets(); if (connectedSets.size() == 1) { - // it means that terminal is connected to a busbar section through disconnectors only + // it means that terminal is connected to a busbar section through non-openable switches only // so there is no way to disconnect the terminal } else { - // build an aggregated graph where we only keep breakers - int aggregatedNodeCount = 0; - Graph breakerOnlyGraph = new Pseudograph<>(Edge.class); - Map nodeToAggregatedNode = new HashMap<>(); - + // build the graph between connected sets where the edges are openable switches + Graph, Edge> breakerOnlyGraph = new Pseudograph<>(Edge.class); for (Set connectedSet : connectedSets) { - breakerOnlyGraph.addVertex(aggregatedNodeCount); - for (int node : connectedSet) { - nodeToAggregatedNode.put(node, aggregatedNodeCount); - } - aggregatedNodeCount++; + breakerOnlyGraph.addVertex(connectedSet); } for (Edge edge : graph.edgeSet()) { - if (edge.getBiConnectable() instanceof SwitchAttributes) { - SwitchAttributes switchAttributes = (SwitchAttributes) edge.getBiConnectable(); - if (isSwitchOpenable.test(switchAttributes)) { - int node1 = graph.getEdgeSource(edge); - int node2 = graph.getEdgeTarget(edge); - int aggregatedNode1 = nodeToAggregatedNode.get(node1); - int aggregatedNode2 = nodeToAggregatedNode.get(node2); - breakerOnlyGraph.addEdge(aggregatedNode1, aggregatedNode2, edge); - } + if (edge.getBiConnectable() instanceof SwitchAttributes switchAttributes && isSwitchOpenable.test(switchAttributes)) { + int node1 = graph.getEdgeSource(edge); + int node2 = graph.getEdgeTarget(edge); + Set aggregatedNode1 = connectivityInspector.connectedSetOf(node1); + Set aggregatedNode2 = connectivityInspector.connectedSetOf(node2); + breakerOnlyGraph.addEdge(aggregatedNode1, aggregatedNode2, edge); } } Set openedSwitches = new HashSet<>(); // find minimal cuts from terminal to each of the busbar section in the aggregated breaker only graph // so that we can open the minimal number of breaker to disconnect the terminal - MinimumSTCutAlgorithm minCutAlgo = new EdmondsKarpMFImpl<>(breakerOnlyGraph); - for (int busbarSectionNode : busbarSectionNodes) { - int aggregatedNode = nodeToAggregatedNode.get(getAttributes().getNode()); - int busbarSectionAggregatedNode = nodeToAggregatedNode.get(busbarSectionNode); - // if that terminal is connected to a busbar section through disconnectors only or that terminal is a busbar section - // so there is no way to disconnect the terminal - if (aggregatedNode == busbarSectionAggregatedNode) { - continue; - } - minCutAlgo.calculateMinCut(aggregatedNode, busbarSectionAggregatedNode); - for (Edge edge : minCutAlgo.getCutEdges()) { - if (edge.getBiConnectable() instanceof SwitchAttributes) { - SwitchAttributes switchAttributes = (SwitchAttributes) edge.getBiConnectable(); - switchAttributes.setOpen(true); - index.updateSwitchResource(switchAttributes.getResource()); - openedSwitches.add(switchAttributes.getResource().getId()); - done = true; - } + MinimumSTCutAlgorithm, Edge> minCutAlgo = new EdmondsKarpMFImpl<>(breakerOnlyGraph); + Set aggregatedNode = connectivityInspector.connectedSetOf(getAttributes().getNode()); + Set busbarSectionAggregatedNode = connectivityInspector.connectedSetOf(bbs0); + // if that terminal is connected to a busbar section through disconnectors only or that terminal is a busbar section + // so there is no way to disconnect the terminal + if (aggregatedNode == busbarSectionAggregatedNode) { + return false; + } + minCutAlgo.calculateMinCut(aggregatedNode, busbarSectionAggregatedNode); + for (Edge edge : minCutAlgo.getCutEdges()) { + if (edge.getBiConnectable() instanceof SwitchAttributes switchAttributes) { + switchAttributes.setOpen(true); + index.updateSwitchResource(switchAttributes.getResource()); + openedSwitches.add(switchAttributes.getResource().getId()); + done = true; } } diff --git a/network-store-iidm-impl/src/test/java/com/powsybl/network/store/iidm/impl/NodeBreakerDisconnectionDiamondPathBugTest.java b/network-store-iidm-impl/src/test/java/com/powsybl/network/store/iidm/impl/NodeBreakerDisconnectionDiamondPathBugTest.java index 00f843fb5..43d2a86ef 100644 --- a/network-store-iidm-impl/src/test/java/com/powsybl/network/store/iidm/impl/NodeBreakerDisconnectionDiamondPathBugTest.java +++ b/network-store-iidm-impl/src/test/java/com/powsybl/network/store/iidm/impl/NodeBreakerDisconnectionDiamondPathBugTest.java @@ -18,13 +18,14 @@ public class NodeBreakerDisconnectionDiamondPathBugTest { /** + *
      *     L
      *     |
      *  ---1---
      *  |     |
      * BR1   BR2
      *  |     |
-     *  ---0--- BBS1
+     *  ---0--- BBS1
*/ private Network createNetwork() { Network network = Network.create("test", "test"); @@ -63,19 +64,21 @@ private Network createNetwork() { } /** + *
      *     L
      *     |
      *   2 |-------
      *     |      |
      *  -------   |
      *  |     |   D1
-     * BR1   D2   |
-     *  |     |   |
-     *  ---1---   |
-     *     |      |
-     *    BR2     |
-     *     |      |
-     *  ---0------- BBS1
+     * BR1   D2   |                     LA
+     *  |     |   |                      |
+     *  ---1---   |                      4
+     *     |      |                      |
+     *    BR2     |                     BR4
+     *     |      |                      |
+     *  ---0-------   -----BR3-----   ---3---
+     *    BBS1                         BBS2
*/ private Network createNetwork2() { Network network = Network.create("test", "test"); @@ -92,6 +95,28 @@ private Network createNetwork2() { .setId("BBS1") .setNode(0) .add(); + vl.getNodeBreakerView().newBusbarSection() + .setId("BBS2") + .setNode(3) + .add(); + vl.getNodeBreakerView().newBreaker() + .setId("BR3") + .setNode1(0) + .setNode2(3) + .setOpen(false) + .add(); + vl.getNodeBreakerView().newBreaker() + .setId("BR4") + .setNode1(3) + .setNode2(4) + .setOpen(false) + .add(); + vl.newLoad() + .setId("LA") + .setNode(4) + .setP0(1) + .setQ0(1) + .add(); vl.newLoad() .setId("L") .setNode(2) @@ -125,6 +150,187 @@ private Network createNetwork2() { return network; } + /** + *
+     *    L1                   L2
+     *     |                    |
+     *     1                    3
+     *     |                    |
+     *    D1                   D2
+     *     |                    |
+     *  ---0---  ----BR----  ---2---
+     *    BBS1                BBS2
+ */ + private Network createNetwork3() { + Network network = Network.create("test", "test"); + Substation s = network.newSubstation() + .setId("S") + .setCountry(Country.FR) + .add(); + VoltageLevel vl = s.newVoltageLevel() + .setId("VL") + .setNominalV(400.0) + .setTopologyKind(TopologyKind.NODE_BREAKER) + .add(); + vl.getNodeBreakerView().newBusbarSection() + .setId("BBS1") + .setNode(0) + .add(); + vl.getNodeBreakerView().newBusbarSection() + .setId("BBS2") + .setNode(2) + .add(); + vl.getNodeBreakerView().newBreaker() + .setId("BR") + .setNode1(0) + .setNode2(2) + .setOpen(false) + .add(); + vl.newLoad() + .setId("L1") + .setNode(1) + .setP0(1) + .setQ0(1) + .add(); + vl.newLoad() + .setId("L2") + .setNode(3) + .setP0(1) + .setQ0(1) + .add(); + vl.getNodeBreakerView().newDisconnector() + .setId("D1") + .setNode1(0) + .setNode2(1) + .setOpen(false) + .add(); + vl.getNodeBreakerView().newDisconnector() + .setId("D2") + .setNode1(2) + .setNode2(3) + .setOpen(false) + .add(); + return network; + } + + /** + *
+     *     Load        Line    2WT
+     *       |          |       |
+     *   ----2---       3       4
+     *   |      |       |       |
+     *  BR2    BR3     BR4     BR5
+     *   |      |       |       |
+     *   -----------1------------
+     *              |
+     *             BR1
+     *              |
+     *   -----------0------------
+     *            BBS1
+ */ + private Network createNetwork4() { + Network network = Network.create("test", "test"); + Substation s = network.newSubstation() + .setId("S") + .setCountry(Country.FR) + .add(); + VoltageLevel vl1 = s.newVoltageLevel() + .setId("VL1") + .setNominalV(400.0) + .setTopologyKind(TopologyKind.NODE_BREAKER) + .add(); + vl1.getNodeBreakerView().newBusbarSection() + .setId("BBS1") + .setNode(0) + .add(); + vl1.getNodeBreakerView().newBreaker() + .setId("BR1") + .setNode1(0) + .setNode2(1) + .setOpen(false) + .add(); + vl1.getNodeBreakerView().newBreaker() + .setId("BR2") + .setNode1(1) + .setNode2(2) + .setOpen(false) + .add(); + vl1.getNodeBreakerView().newBreaker() + .setId("BR3") + .setNode1(1) + .setNode2(2) + .setOpen(false) + .add(); + vl1.getNodeBreakerView().newBreaker() + .setId("BR4") + .setNode1(1) + .setNode2(3) + .setOpen(false) + .add(); + vl1.getNodeBreakerView().newBreaker() + .setId("BR5") + .setNode1(1) + .setNode2(4) + .setOpen(false) + .add(); + vl1.newLoad() + .setId("Load") + .setNode(2) + .setP0(1) + .setQ0(1) + .add(); + + VoltageLevel vl2 = s.newVoltageLevel() + .setId("VL2") + .setNominalV(400.0) + .setTopologyKind(TopologyKind.NODE_BREAKER) + .add(); + vl2.getNodeBreakerView().newBusbarSection() + .setId("BBS2") + .setNode(0) + .add(); + vl2.getNodeBreakerView().newBreaker() + .setId("BR6") + .setNode1(0) + .setNode2(1) + .setOpen(false) + .add(); + vl2.getNodeBreakerView().newBreaker() + .setId("BR7") + .setNode1(0) + .setNode2(2) + .setOpen(false) + .add(); + + s.newTwoWindingsTransformer() + .setId("2WT") + .setVoltageLevel1(vl1.getId()) + .setNode1(4) + .setVoltageLevel2(vl2.getId()) + .setNode2(2) + .setR(250) + .setX(100) + .setG(52) + .setB(12) + .setRatedU1(405) + .setRatedU2(405) + .add(); + network.newLine() + .setId("Line") + .setVoltageLevel1(vl1.getId()) + .setNode1(3) + .setVoltageLevel2(vl2.getId()) + .setNode2(1) + .setR(1.0) + .setX(1.0) + .setG1(0.0) + .setB1(0.0) + .setG2(0.0) + .setB2(0.0) + .add(); + return network; + } + @Test public void testDisconnect() { Network network = createNetwork(); @@ -142,4 +348,43 @@ public void testDisconnect2() { assertFalse(l.getTerminal().disconnect()); assertTrue(l.getTerminal().isConnected()); // because of D1 which is not openable } + + @Test + public void testDisconnect3() { + Network network = createNetwork3(); + Switch s = network.getSwitch("BR"); + assertFalse(s.isOpen()); + Load l1 = network.getLoad("L1"); + assertTrue(l1.getTerminal().isConnected()); + assertFalse(l1.getTerminal().disconnect()); + assertFalse(s.isOpen()); + assertTrue(l1.getTerminal().isConnected()); // because of D1 which is not openable + } + + @Test + public void testDisconnect4() { + Network network = createNetwork4(); + Switch s1 = network.getSwitch("BR1"); + Switch s2 = network.getSwitch("BR2"); + Switch s3 = network.getSwitch("BR3"); + Switch s4 = network.getSwitch("BR4"); + Switch s5 = network.getSwitch("BR5"); + + assertFalse(s1.isOpen()); + assertFalse(s2.isOpen()); + assertFalse(s3.isOpen()); + assertFalse(s4.isOpen()); + assertFalse(s5.isOpen()); + + Load l1 = network.getLoad("Load"); + assertTrue(l1.getTerminal().isConnected()); + assertTrue(l1.getTerminal().disconnect()); + assertTrue(l1.getTerminal().isConnected()); // because of D1 which is not openable + + assertTrue(s1.isOpen()); + assertFalse(s2.isOpen()); + assertFalse(s3.isOpen()); + assertTrue(s4.isOpen()); + assertTrue(s5.isOpen()); + } }