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*", Pattern.DOTALL);
-
protected boolean debugJsonFiles = false;
protected boolean debugSvgFiles = false;
protected boolean overrideTestReferences = false;
diff --git a/single-line-diagram/single-line-diagram-core/src/test/java/com/powsybl/sld/iidm/TestCaseFictitiousBus.java b/single-line-diagram/single-line-diagram-core/src/test/java/com/powsybl/sld/iidm/TestCaseFictitiousBus.java
index 48bff6a29..5aa1b51f8 100644
--- a/single-line-diagram/single-line-diagram-core/src/test/java/com/powsybl/sld/iidm/TestCaseFictitiousBus.java
+++ b/single-line-diagram/single-line-diagram-core/src/test/java/com/powsybl/sld/iidm/TestCaseFictitiousBus.java
@@ -8,94 +8,31 @@
package com.powsybl.sld.iidm;
import com.powsybl.diagram.test.Networks;
-import com.powsybl.iidm.network.Network;
-import com.powsybl.iidm.network.TopologyKind;
-import com.powsybl.iidm.network.VoltageLevel;
-import com.powsybl.iidm.network.extensions.ConnectablePosition;
import com.powsybl.sld.builders.NetworkGraphBuilder;
import com.powsybl.sld.model.graphs.VoltageLevelGraph;
import com.powsybl.sld.svg.styles.iidm.TopologicalStyleProvider;
-import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
+import java.io.IOException;
+
import static org.junit.jupiter.api.Assertions.assertEquals;
/**
- *
- * 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 @@
+
+
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 @@
—
-
+
-
+