diff --git a/diagram-test/src/main/java/com/powsybl/diagram/test/Networks.java b/diagram-test/src/main/java/com/powsybl/diagram/test/Networks.java index 250e638cb..c95315641 100644 --- a/diagram-test/src/main/java/com/powsybl/diagram/test/Networks.java +++ b/diagram-test/src/main/java/com/powsybl/diagram/test/Networks.java @@ -1724,6 +1724,10 @@ public static VoltageLevel createVoltageLevel(Substation s, String id, String na .add(); } + public static void createSwitch(VoltageLevel vl, String id, SwitchKind kind, boolean retained, boolean open, boolean fictitious, int node1, int node2) { + createSwitch(vl, id, id, kind, retained, open, fictitious, node1, node2); + } + public static void createSwitch(VoltageLevel vl, String id, String name, SwitchKind kind, boolean retained, boolean open, boolean fictitious, int node1, int node2) { vl.getNodeBreakerView().newSwitch() .setId(id) @@ -1744,6 +1748,10 @@ public static void createInternalConnection(VoltageLevel vl, int node1, int node .add(); } + public static void createBusBarSection(VoltageLevel vl, String id, int node, int busbarIndex, int sectionIndex) { + createBusBarSection(vl, id, id, node, busbarIndex, sectionIndex); + } + public static void createBusBarSection(VoltageLevel vl, String id, String name, int node, int busbarIndex, int sectionIndex) { BusbarSection bbs = vl.getNodeBreakerView().newBusbarSection() .setId(id) @@ -1756,6 +1764,11 @@ public static void createBusBarSection(VoltageLevel vl, String id, String name, .add(); } + public static void createLoad(VoltageLevel vl, String id, Integer feederOrder, + ConnectablePosition.Direction direction, int node, double p0, double q0) { + createLoad(vl, id, id, id, feederOrder, direction, node, p0, q0); + } + public static void createLoad(VoltageLevel vl, String id, String name, String feederName, Integer feederOrder, ConnectablePosition.Direction direction, int node, double p0, double q0) { Load load = vl.newLoad() @@ -1768,6 +1781,13 @@ public static void createLoad(VoltageLevel vl, String id, String name, String fe addFeederPosition(load, feederName, feederOrder, direction); } + public static void createGenerator(VoltageLevel vl, String id, Integer feederOrder, + ConnectablePosition.Direction direction, int node, + double minP, double maxP, boolean voltageRegulator, + double targetP, double targetQ) { + createGenerator(vl, id, id, id, feederOrder, direction, node, minP, maxP, voltageRegulator, targetP, targetQ); + } + public static void createGenerator(VoltageLevel vl, String id, String name, String feederName, Integer feederOrder, ConnectablePosition.Direction direction, int node, double minP, double maxP, boolean voltageRegulator, @@ -2264,6 +2284,87 @@ public static Network createNetworkWithComplexInternCellDifferentSubsections() { return network; } + /** + *
+     *     vl1  vl2
+     *     |    |
+     * L1  |    |  L2
+     *     |    |
+     *     --*---  Fictitious busbar section
+     *       |
+     *   L3  |
+     *       |
+     *       vl3
+     *
+     * 
+ */ + public static Network createTeePointNetwork() { + Network network = Network.create("testCase1", "test"); + VoltageLevel vl = network.newVoltageLevel() + .setId("vl") + .setNominalV(50) + .setTopologyKind(TopologyKind.NODE_BREAKER) + .add(); + + VoltageLevel vl1 = network.newVoltageLevel() + .setId("vl1") + .setNominalV(10) + .setTopologyKind(TopologyKind.NODE_BREAKER) + .add(); + + VoltageLevel vl2 = network.newVoltageLevel() + .setId("vl2") + .setNominalV(30) + .setTopologyKind(TopologyKind.NODE_BREAKER) + .add(); + + VoltageLevel vl3 = network.newVoltageLevel() + .setId("vl3") + .setNominalV(10) + .setTopologyKind(TopologyKind.NODE_BREAKER) + .add(); + + createInternalConnection(vl, 1, 0); + createInternalConnection(vl, 2, 0); + createInternalConnection(vl, 3, 0); + + createLine(network, "L1", "L1", 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, + 1, 10, vl.getId(), vl1.getId(), + "L1", 0, ConnectablePosition.Direction.TOP, + "L1", 1, ConnectablePosition.Direction.TOP); + + createLine(network, "L2", "L2", 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, + 2, 20, vl.getId(), vl2.getId(), + "L2", 1, ConnectablePosition.Direction.BOTTOM, + "L2", 0, ConnectablePosition.Direction.TOP); + + createLine(network, "L3", "L3", 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, + 3, 30, vl.getId(), vl3.getId(), + "L3", 2, ConnectablePosition.Direction.TOP, + "L3", 0, ConnectablePosition.Direction.TOP); + + return network; + } + + public static Network createDanglingConnectablesNetwork() { + Network network = Network.create("testDLoad", "testDLoad"); + VoltageLevel vl = network.newVoltageLevel() + .setId("vl") + .setNominalV(50) + .setTopologyKind(TopologyKind.NODE_BREAKER) + .add(); + createBusBarSection(vl, "bbs1", 0, 1, 1); + createBusBarSection(vl, "bbs2", 1, 1, 2); + createSwitch(vl, "d", SwitchKind.DISCONNECTOR, true, false, false, 0, 2); + createSwitch(vl, "d12", SwitchKind.DISCONNECTOR, true, false, false, 0, 1); + createSwitch(vl, "ddl2", SwitchKind.DISCONNECTOR, true, false, false, 4, 5); + createLoad(vl, "load", 1, ConnectablePosition.Direction.TOP, 2, 0, 0); + createLoad(vl, "dLoad1", 2, ConnectablePosition.Direction.BOTTOM, 3, 0, 0); + createLoad(vl, "dLoad2", 0, ConnectablePosition.Direction.TOP, 4, 10, 0); + createGenerator(vl, "dGen", null, ConnectablePosition.Direction.TOP, 5, 50, 100, false, 100, 400); + return network; + } + public static void createLine(Bus bus1, Bus bus2) { String id = String.format("%s - %s", bus1.getVoltageLevel().getSubstation().orElseThrow().getId(), diff --git a/single-line-diagram/single-line-diagram-core/src/main/java/com/powsybl/sld/layout/GraphRefiner.java b/single-line-diagram/single-line-diagram-core/src/main/java/com/powsybl/sld/layout/GraphRefiner.java index dbadf1aac..d69730c08 100644 --- a/single-line-diagram/single-line-diagram-core/src/main/java/com/powsybl/sld/layout/GraphRefiner.java +++ b/single-line-diagram/single-line-diagram-core/src/main/java/com/powsybl/sld/layout/GraphRefiner.java @@ -6,7 +6,6 @@ */ package com.powsybl.sld.layout; -import com.powsybl.commons.PowsyblException; import com.powsybl.sld.model.graphs.NodeFactory; import com.powsybl.sld.model.graphs.VoltageLevelGraph; import com.powsybl.sld.model.nodes.BusNode; @@ -78,20 +77,29 @@ private void handleConnectedComponents(VoltageLevelGraph graph) { .map(setNodes -> setNodes.stream().map(Node::getId).collect(Collectors.toSet())) .forEach(strings -> LOGGER.warn(" - {}", strings)); } - connectedSets.forEach(s -> ensureOneBusInConnectedComponent(graph, s)); + // Add a fictitious bus for all connected components without any bus + connectedSets.stream() + .filter(s -> s.stream().noneMatch(node -> node.getType() == Node.NodeType.BUS)) + .forEach(s -> addFictitiousBusInConnectedComponent(graph, s)); } - private void ensureOneBusInConnectedComponent(VoltageLevelGraph graph, Set nodes) { - if (nodes.stream().anyMatch(node -> node.getType() == Node.NodeType.BUS)) { - return; - } - Node biggestFn = nodes.stream() - .filter(node -> node.getType() == Node.NodeType.INTERNAL) - .min(Comparator.comparingInt(node -> node.getAdjacentEdges().size()) - .reversed() - .thenComparing(Node::getId)) // for stable fictitious node selection, also sort on id - .orElseThrow(() -> new PowsyblException("Empty node set")); - graph.substituteNode(biggestFn, NodeFactory.createFictitiousBusNode(graph, biggestFn.getId() + "FictitiousBus")); + private void addFictitiousBusInConnectedComponent(VoltageLevelGraph graph, Set nodes) { + // Replace the most meshed fictitious node by a fictitious BusNode. + // If no fictitious node, insert/add a fictitious BusNode at the most meshed node of the set. + Comparator mostMeshedComparator = Comparator.comparingInt(node -> node.getAdjacentEdges().size()).reversed().thenComparing(Node::getId); // for stable fictitious node selection, also sort on id + int sectionIndex = 1 + graph.getNodeBuses().stream().mapToInt(BusNode::getSectionIndex).max().orElse(0); + nodes.stream().filter(node -> node.getType() == Node.NodeType.INTERNAL) + .min(mostMeshedComparator) + .ifPresentOrElse( + mostMeshedFictitiousNode -> { + BusNode busNode = NodeFactory.createFictitiousBusNode(graph, mostMeshedFictitiousNode.getId() + "_FictitiousBus", 1, sectionIndex); + graph.substituteNode(mostMeshedFictitiousNode, busNode); + }, + () -> { + Node mostMeshedNode = nodes.stream().min(mostMeshedComparator).orElseThrow(); // always non-empty set + BusNode busNode = NodeFactory.createFictitiousBusNode(graph, mostMeshedNode.getId() + "_FictitiousBus", 1, sectionIndex); + graph.insertNodeNextTo(busNode, mostMeshedNode); + }); } private Predicate getNodesOnBusPredicate(VoltageLevelGraph graph, List componentsOnBusbars) { diff --git a/single-line-diagram/single-line-diagram-core/src/main/java/com/powsybl/sld/model/graphs/NodeFactory.java b/single-line-diagram/single-line-diagram-core/src/main/java/com/powsybl/sld/model/graphs/NodeFactory.java index 61116ee52..2e14ea1b0 100644 --- a/single-line-diagram/single-line-diagram-core/src/main/java/com/powsybl/sld/model/graphs/NodeFactory.java +++ b/single-line-diagram/single-line-diagram-core/src/main/java/com/powsybl/sld/model/graphs/NodeFactory.java @@ -56,9 +56,9 @@ public static BusNode createBusNode(VoltageLevelGraph graph, String id, String n return bn; } - public static BusNode createFictitiousBusNode(VoltageLevelGraph graph, String id) { + public static BusNode createFictitiousBusNode(VoltageLevelGraph graph, String id, int busbarIndex, int sectionIndex) { BusNode bn = new BusNode(id, null, true); - bn.setBusBarIndexSectionIndex(1, 1); + bn.setBusBarIndexSectionIndex(busbarIndex, sectionIndex); graph.addNode(bn); return bn; } diff --git a/single-line-diagram/single-line-diagram-core/src/main/java/com/powsybl/sld/model/graphs/VoltageLevelGraph.java b/single-line-diagram/single-line-diagram-core/src/main/java/com/powsybl/sld/model/graphs/VoltageLevelGraph.java index 4a8919278..485d675eb 100644 --- a/single-line-diagram/single-line-diagram-core/src/main/java/com/powsybl/sld/model/graphs/VoltageLevelGraph.java +++ b/single-line-diagram/single-line-diagram-core/src/main/java/com/powsybl/sld/model/graphs/VoltageLevelGraph.java @@ -730,6 +730,15 @@ private void substituteInternalMiddle2wtByEquipmentNode(FeederNode feederNode) { } } + public void insertNodeNextTo(Node nodeToInsert, Node adjacentNode) { + List neighbours = adjacentNode.getAdjacentNodes(); + if (neighbours.isEmpty()) { + addEdge(nodeToInsert, adjacentNode); + } else { + insertNode(adjacentNode, nodeToInsert, neighbours.get(0)); + } + } + private record GroundDisconnection(List nodes, FeederNode ground, SwitchNode disconnector, Node forkNode) { } } diff --git a/single-line-diagram/single-line-diagram-core/src/test/java/com/powsybl/sld/AbstractTestCase.java b/single-line-diagram/single-line-diagram-core/src/test/java/com/powsybl/sld/AbstractTestCase.java index 7828644e5..dfd6994ad 100644 --- a/single-line-diagram/single-line-diagram-core/src/test/java/com/powsybl/sld/AbstractTestCase.java +++ b/single-line-diagram/single-line-diagram-core/src/test/java/com/powsybl/sld/AbstractTestCase.java @@ -25,15 +25,12 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.Objects; -import java.util.regex.Pattern; /** * @author Benoit Jeanson {@literal } */ public abstract class AbstractTestCase { - private static final Pattern SVG_FIX_PATTERN = Pattern.compile(">\\s*(<\\!\\[CDATA\\[.*?]]>)\\s* - * vl1 vl2 - * | | - * L1 | | L2 - * | | - * --*--- Fictitious busbar section - * | - * L3 | - * | - * vl3 - * - * - * * @author Thomas Adam {@literal } */ class TestCaseFictitiousBus extends AbstractTestCaseIidm { - private VoltageLevel vl1; - private VoltageLevel vl2; - private VoltageLevel vl3; - - @BeforeEach - public void setUp() { - network = Network.create("testCase1", "test"); - graphBuilder = new NetworkGraphBuilder(network); - substation = null; - vl = network.newVoltageLevel() - .setId("vl") - .setNominalV(50) - .setTopologyKind(TopologyKind.NODE_BREAKER) - .add(); - - vl1 = network.newVoltageLevel() - .setId("vl1") - .setNominalV(10) - .setTopologyKind(TopologyKind.NODE_BREAKER) - .add(); - - vl2 = network.newVoltageLevel() - .setId("vl2") - .setNominalV(30) - .setTopologyKind(TopologyKind.NODE_BREAKER) - .add(); - - vl3 = network.newVoltageLevel() - .setId("vl3") - .setNominalV(10) - .setTopologyKind(TopologyKind.NODE_BREAKER) - .add(); - - Networks.createInternalConnection(vl, 1, 0); - Networks.createInternalConnection(vl, 2, 0); - Networks.createInternalConnection(vl, 3, 0); - - Networks.createLine(network, "L1", "L1", 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, - 1, 10, vl.getId(), vl1.getId(), - "L1", 0, ConnectablePosition.Direction.TOP, - "L1", 1, ConnectablePosition.Direction.TOP); - - Networks.createLine(network, "L2", "L2", 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, - 2, 20, vl.getId(), vl2.getId(), - "L2", 1, ConnectablePosition.Direction.BOTTOM, - "L2", 0, ConnectablePosition.Direction.TOP); - - Networks.createLine(network, "L3", "L3", 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, - 3, 30, vl.getId(), vl3.getId(), - "L3", 2, ConnectablePosition.Direction.TOP, - "L3", 0, ConnectablePosition.Direction.TOP); - + @Override + public void setUp() throws IOException { + // no common setup } @Test void testBasic() { + network = Networks.createTeePointNetwork(); + // build graph - VoltageLevelGraph g = graphBuilder.buildVoltageLevelGraph(vl.getId()); + VoltageLevelGraph g = new NetworkGraphBuilder(network).buildVoltageLevelGraph("vl"); // Run layout voltageLevelGraphLayout(g); @@ -107,8 +44,10 @@ void testBasic() { @Test void testTopological() { + network = Networks.createTeePointNetwork(); + // build graph - VoltageLevelGraph g = graphBuilder.buildVoltageLevelGraph(vl.getId()); + VoltageLevelGraph g = new NetworkGraphBuilder(network).buildVoltageLevelGraph("vl"); // Run layout voltageLevelGraphLayout(g); @@ -117,4 +56,19 @@ void testTopological() { assertEquals(toString("/TestCaseFictitiousBusTopological.svg"), toSVG(g, "/TestCaseFictitiousBusTopological.svg", componentLibrary, layoutParameters, svgParameters, getDefaultDiagramLabelProvider(), new TopologicalStyleProvider(network))); } + + @Test + void testDanglingConnectables() { + network = Networks.createDanglingConnectablesNetwork(); + + // build graph + VoltageLevelGraph g = new NetworkGraphBuilder(network).buildVoltageLevelGraph("vl"); + + // Run layout + voltageLevelGraphLayout(g); + + // write Json and compare to reference + assertEquals(toString("/TestCaseFictitiousBusDanglingConnectables.svg"), + toSVG(g, "/TestCaseFictitiousBusDanglingConnectables.svg", componentLibrary, layoutParameters, svgParameters, getDefaultDiagramLabelProvider(), new TopologicalStyleProvider(network))); + } } diff --git a/single-line-diagram/single-line-diagram-core/src/test/resources/TestCaseFictitiousBus.svg b/single-line-diagram/single-line-diagram-core/src/test/resources/TestCaseFictitiousBus.svg index 84c8fc7c2..1396ea15b 100644 --- a/single-line-diagram/single-line-diagram-core/src/test/resources/TestCaseFictitiousBus.svg +++ b/single-line-diagram/single-line-diagram-core/src/test/resources/TestCaseFictitiousBus.svg @@ -151,14 +151,14 @@ ]]> - + - + - + @@ -174,10 +174,10 @@ - + - + @@ -188,10 +188,10 @@ - + - + @@ -207,10 +207,10 @@ - + - + @@ -221,10 +221,10 @@ - + - + @@ -240,10 +240,10 @@ - + - + diff --git a/single-line-diagram/single-line-diagram-core/src/test/resources/TestCaseFictitiousBusDanglingConnectables.svg b/single-line-diagram/single-line-diagram-core/src/test/resources/TestCaseFictitiousBusDanglingConnectables.svg new file mode 100644 index 000000000..a9ca6f9de --- /dev/null +++ b/single-line-diagram/single-line-diagram-core/src/test/resources/TestCaseFictitiousBusDanglingConnectables.svg @@ -0,0 +1,340 @@ + + + + + + + + bbs1 + + + + bbs2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + load + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + dLoad1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + dGen + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + dLoad2 + + + + diff --git a/single-line-diagram/single-line-diagram-core/src/test/resources/TestCaseFictitiousBusTopological.svg b/single-line-diagram/single-line-diagram-core/src/test/resources/TestCaseFictitiousBusTopological.svg index 6e28f7506..39d32ca6e 100644 --- a/single-line-diagram/single-line-diagram-core/src/test/resources/TestCaseFictitiousBusTopological.svg +++ b/single-line-diagram/single-line-diagram-core/src/test/resources/TestCaseFictitiousBusTopological.svg @@ -146,14 +146,14 @@ ]]> - + - + - + @@ -169,10 +169,10 @@ - + - + @@ -183,10 +183,10 @@ - + - + @@ -202,10 +202,10 @@ - + - + @@ -216,10 +216,10 @@ - + - + @@ -235,10 +235,10 @@ - + - +