From 9f6031464c7dde61c1eb9976b9b5af25a2f7af98 Mon Sep 17 00:00:00 2001 From: Florian Dupuy Date: Tue, 12 Nov 2024 22:51:13 +0100 Subject: [PATCH 1/9] Avoid creating internal connections when less than 2 terminals Signed-off-by: Florian Dupuy --- .../powsybl/cgmes/conversion/Conversion.java | 1 + .../powsybl/cgmes/conversion/NodeMapping.java | 43 +++++++++++-------- .../cgmes/conversion/TerminalMapping.java | 35 +++++++++------ 3 files changed, 46 insertions(+), 33 deletions(-) diff --git a/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/Conversion.java b/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/Conversion.java index f41b2311f89..67bd70288f5 100644 --- a/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/Conversion.java +++ b/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/Conversion.java @@ -170,6 +170,7 @@ public Network convert(ReportNode reportNode) { cgmes.baseVoltages().forEach(bv -> bvAdder.addBaseVoltage(bv.getId("BaseVoltage"), bv.asDouble("nominalVoltage"), isBoundaryBaseVoltage(bv.getLocal("graph")))); bvAdder.add(); cgmes.computedTerminals().forEach(t -> context.terminalMapping().buildTopologicalNodeCgmesTerminalsMapping(t)); + cgmes.computedTerminals().forEach(t -> context.terminalMapping().countConnectivityNodeCgmesTerminals(t)); cgmes.regulatingControls().forEach(p -> context.regulatingControlMapping().cacheRegulatingControls(p)); context.popReportNode(); diff --git a/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/NodeMapping.java b/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/NodeMapping.java index e5e7778b736..8801b597405 100644 --- a/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/NodeMapping.java +++ b/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/NodeMapping.java @@ -47,28 +47,33 @@ public int iidmNodeForTerminal(CgmesTerminal t, boolean isSwitchEnd, VoltageLeve } public int iidmNodeForTerminal(CgmesTerminal t, boolean isSwitchEnd, VoltageLevel vl, boolean equipmentIsConnected) { - int iidmNodeForConductingEquipment = cgmes2iidm.computeIfAbsent(t.id(), id -> newNode(vl)); - // Add internal connection from terminal to connectivity node - int iidmNodeForConnectivityNode = cgmes2iidm.computeIfAbsent(t.connectivityNode(), id -> newNode(vl)); - - // For node-breaker models we create an internal connection between - // the terminal and its connectivity node - // Because there are some node-breaker models that, - // in addition to the information of opened switches also set - // the terminal.connected property to false, - // we have decided to create fictitious switches to precisely + boolean connected = t.connected() && equipmentIsConnected; + + // Because there are some node-breaker models that, in addition to the information of opened switches also set + // the terminal.connected property to false, we have decided to create fictitious switches to precisely // map this situation to IIDM. // This behavior can be disabled through configuration. - boolean connected = t.connected() && equipmentIsConnected; - if (connected || !createFictitiousSwitch(context.config().getCreateFictitiousSwitchesForDisconnectedTerminalsMode(), isSwitchEnd)) { - // TODO(Luma): do not add an internal connection if is has already been added? - vl.getNodeBreakerView().newInternalConnection() - .setNode1(iidmNodeForConductingEquipment) - .setNode2(iidmNodeForConnectivityNode) - .add(); + if (context.terminalMapping().getConnectivityNodesCount(t.connectivityNode()) > 1) { + // Add internal connection from terminal to connectivity node as iidm can only handle one terminal per node + int iidmNodeForConductingEquipment = cgmes2iidm.computeIfAbsent(t.id(), id -> newNode(vl)); + int iidmNodeForConnectivityNode = cgmes2iidm.computeIfAbsent(t.connectivityNode(), id -> newNode(vl)); + + // For node-breaker models we create an internal connection between the terminal and its connectivity node + vl.getNodeBreakerView().newInternalConnection() + .setNode1(iidmNodeForConductingEquipment) + .setNode2(iidmNodeForConnectivityNode) + .add(); + + return iidmNodeForConductingEquipment; + } else { + return cgmes2iidm.computeIfAbsent(t.connectivityNode(), id -> newNode(vl)); + } } else { + int iidmNodeForConductingEquipment = cgmes2iidm.computeIfAbsent(t.id(), id -> newNode(vl)); + int iidmNodeForConnectivityNode = cgmes2iidm.computeIfAbsent(t.connectivityNode(), id -> newNode(vl)); + // Only add fictitious switches for disconnected terminals if not already added // Use the id and name of terminal String switchId = t.id() + "_SW_fict"; @@ -85,9 +90,9 @@ public int iidmNodeForTerminal(CgmesTerminal t, boolean isSwitchEnd, VoltageLeve .add(); sw.setProperty(Conversion.PROPERTY_IS_CREATED_FOR_DISCONNECTED_TERMINAL, "true"); } - } - return iidmNodeForConductingEquipment; + return iidmNodeForConductingEquipment; + } } private static boolean createFictitiousSwitch(CgmesImport.FictitiousSwitchesCreationMode mode, boolean isSwitchEnd) { diff --git a/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/TerminalMapping.java b/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/TerminalMapping.java index 9574d21e605..ce891031764 100644 --- a/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/TerminalMapping.java +++ b/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/TerminalMapping.java @@ -26,13 +26,14 @@ */ public class TerminalMapping { - public TerminalMapping() { - boundaries = new HashMap<>(); - terminals = new HashMap<>(); - terminalNumbers = new HashMap<>(); - topologicalNodesMapping = new HashMap<>(); - cgmesTerminalsMapping = new HashMap<>(); - } + private final Map terminals = new HashMap<>(); + private final Map boundaries = new HashMap<>(); + // This is a somewhat dirty way of storing the side for the CGMES terminal + // (only mapped when the terminal is connected to a branch) + private final Map terminalNumbers = new HashMap<>(); + private final Map> topologicalNodesMapping = new HashMap<>(); + private final Map connectivityNodesCounting = new HashMap<>(); + private final Map cgmesTerminalsMapping = new HashMap<>(); public void add(String cgmesTerminal, Terminal iidmTerminal, int terminalNumber) { if (terminals.containsKey(cgmesTerminal) || boundaries.containsKey(cgmesTerminal)) { @@ -145,6 +146,16 @@ public void buildTopologicalNodeCgmesTerminalsMapping(CgmesTerminal t) { } } + public void countConnectivityNodeCgmesTerminals(CgmesTerminal t) { + if (CgmesNames.SWITCH_TYPES.contains(t.conductingEquipmentType())) { + return; // switches do not have terminals in iidm, so we should not count them + } + String connectivityNode = t.connectivityNode(); + if (connectivityNode != null) { + connectivityNodesCounting.merge(connectivityNode, 1, Integer::sum); + } + } + public boolean areAssociated(String cgmesTerminalId, String topologicalNode) { return topologicalNodesMapping.get(topologicalNode).contains(cgmesTerminalId); } @@ -173,11 +184,7 @@ public String findCgmesTerminalFromTopologicalNode(String topologicalNode) { return topologicalNodesMapping.containsKey(topologicalNode) ? topologicalNodesMapping.get(topologicalNode).get(0) : null; } - private final Map terminals; - private final Map boundaries; - // This is a somewhat dirty way of storing the side for the CGMES terminal - // (only mapped when the terminal is connected to a branch) - private final Map terminalNumbers; - private final Map> topologicalNodesMapping; - private final Map cgmesTerminalsMapping; + public int getConnectivityNodesCount(String id) { + return connectivityNodesCounting.getOrDefault(id, 0); + } } From f6aafcbfd6fa619a9671df414f24c8a4b85d8700 Mon Sep 17 00:00:00 2001 From: Florian Dupuy Date: Wed, 13 Nov 2024 09:57:03 +0100 Subject: [PATCH 2/9] Between 2 terminals create 1 internalConnection instead of 2 Signed-off-by: Florian Dupuy --- .../powsybl/cgmes/conversion/Conversion.java | 2 +- .../powsybl/cgmes/conversion/NodeMapping.java | 27 ++++++++++++------- .../cgmes/conversion/TerminalMapping.java | 10 +++---- 3 files changed, 23 insertions(+), 16 deletions(-) diff --git a/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/Conversion.java b/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/Conversion.java index 67bd70288f5..f206969c526 100644 --- a/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/Conversion.java +++ b/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/Conversion.java @@ -170,7 +170,7 @@ public Network convert(ReportNode reportNode) { cgmes.baseVoltages().forEach(bv -> bvAdder.addBaseVoltage(bv.getId("BaseVoltage"), bv.asDouble("nominalVoltage"), isBoundaryBaseVoltage(bv.getLocal("graph")))); bvAdder.add(); cgmes.computedTerminals().forEach(t -> context.terminalMapping().buildTopologicalNodeCgmesTerminalsMapping(t)); - cgmes.computedTerminals().forEach(t -> context.terminalMapping().countConnectivityNodeCgmesTerminals(t)); + cgmes.computedTerminals().forEach(t -> context.terminalMapping().buildConnectivityNodeCgmesTerminalsMapping(t)); cgmes.regulatingControls().forEach(p -> context.regulatingControlMapping().cacheRegulatingControls(p)); context.popReportNode(); diff --git a/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/NodeMapping.java b/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/NodeMapping.java index 8801b597405..ee5c0e1032d 100644 --- a/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/NodeMapping.java +++ b/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/NodeMapping.java @@ -54,24 +54,31 @@ public int iidmNodeForTerminal(CgmesTerminal t, boolean isSwitchEnd, VoltageLeve // map this situation to IIDM. // This behavior can be disabled through configuration. + int iidmNodeForConductingEquipment = cgmes2iidm.computeIfAbsent(t.id(), id -> newNode(vl)); if (connected || !createFictitiousSwitch(context.config().getCreateFictitiousSwitchesForDisconnectedTerminalsMode(), isSwitchEnd)) { - if (context.terminalMapping().getConnectivityNodesCount(t.connectivityNode()) > 1) { - // Add internal connection from terminal to connectivity node as iidm can only handle one terminal per node - int iidmNodeForConductingEquipment = cgmes2iidm.computeIfAbsent(t.id(), id -> newNode(vl)); + var connectivityNodeTerminals = context.terminalMapping().getConnectivityNodeTerminals(t.connectivityNode()); + if (connectivityNodeTerminals.size() == 2) { + // Add internal connection between two terminals as iidm can only handle one terminal per node + String otherTerminalId = t.id().equals(connectivityNodeTerminals.get(0)) ? connectivityNodeTerminals.get(1) : connectivityNodeTerminals.get(0); + if (!cgmes2iidm.containsKey(otherTerminalId)) { + int iidmNodeForOtherConductingEquipment = newNode(vl); + cgmes2iidm.put(otherTerminalId, iidmNodeForOtherConductingEquipment); + vl.getNodeBreakerView().newInternalConnection() + .setNode1(iidmNodeForConductingEquipment) + .setNode2(iidmNodeForOtherConductingEquipment) + .add(); + } + } else if (connectivityNodeTerminals.size() > 1) { int iidmNodeForConnectivityNode = cgmes2iidm.computeIfAbsent(t.connectivityNode(), id -> newNode(vl)); + // Add internal connection from terminal to connectivity node as iidm can only handle one terminal per node // For node-breaker models we create an internal connection between the terminal and its connectivity node vl.getNodeBreakerView().newInternalConnection() .setNode1(iidmNodeForConductingEquipment) .setNode2(iidmNodeForConnectivityNode) .add(); - - return iidmNodeForConductingEquipment; - } else { - return cgmes2iidm.computeIfAbsent(t.connectivityNode(), id -> newNode(vl)); } } else { - int iidmNodeForConductingEquipment = cgmes2iidm.computeIfAbsent(t.id(), id -> newNode(vl)); int iidmNodeForConnectivityNode = cgmes2iidm.computeIfAbsent(t.connectivityNode(), id -> newNode(vl)); // Only add fictitious switches for disconnected terminals if not already added @@ -90,9 +97,9 @@ public int iidmNodeForTerminal(CgmesTerminal t, boolean isSwitchEnd, VoltageLeve .add(); sw.setProperty(Conversion.PROPERTY_IS_CREATED_FOR_DISCONNECTED_TERMINAL, "true"); } - - return iidmNodeForConductingEquipment; } + + return iidmNodeForConductingEquipment; } private static boolean createFictitiousSwitch(CgmesImport.FictitiousSwitchesCreationMode mode, boolean isSwitchEnd) { diff --git a/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/TerminalMapping.java b/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/TerminalMapping.java index ce891031764..125ea34accb 100644 --- a/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/TerminalMapping.java +++ b/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/TerminalMapping.java @@ -32,7 +32,7 @@ public class TerminalMapping { // (only mapped when the terminal is connected to a branch) private final Map terminalNumbers = new HashMap<>(); private final Map> topologicalNodesMapping = new HashMap<>(); - private final Map connectivityNodesCounting = new HashMap<>(); + private final Map> connectivityNodesCounting = new HashMap<>(); private final Map cgmesTerminalsMapping = new HashMap<>(); public void add(String cgmesTerminal, Terminal iidmTerminal, int terminalNumber) { @@ -146,13 +146,13 @@ public void buildTopologicalNodeCgmesTerminalsMapping(CgmesTerminal t) { } } - public void countConnectivityNodeCgmesTerminals(CgmesTerminal t) { + public void buildConnectivityNodeCgmesTerminalsMapping(CgmesTerminal t) { if (CgmesNames.SWITCH_TYPES.contains(t.conductingEquipmentType())) { return; // switches do not have terminals in iidm, so we should not count them } String connectivityNode = t.connectivityNode(); if (connectivityNode != null) { - connectivityNodesCounting.merge(connectivityNode, 1, Integer::sum); + connectivityNodesCounting.computeIfAbsent(connectivityNode, k -> new ArrayList<>(1)).add(t.id()); } } @@ -184,7 +184,7 @@ public String findCgmesTerminalFromTopologicalNode(String topologicalNode) { return topologicalNodesMapping.containsKey(topologicalNode) ? topologicalNodesMapping.get(topologicalNode).get(0) : null; } - public int getConnectivityNodesCount(String id) { - return connectivityNodesCounting.getOrDefault(id, 0); + public List getConnectivityNodeTerminals(String id) { + return connectivityNodesCounting.getOrDefault(id, List.of()); } } From 0eda01d30742a0a05ddee1d5defa1854baad4cf3 Mon Sep 17 00:00:00 2001 From: Florian Dupuy Date: Wed, 13 Nov 2024 10:52:28 +0100 Subject: [PATCH 3/9] Busbar sections placed at connectivity nodes Signed-off-by: Florian Dupuy --- .../powsybl/cgmes/conversion/NodeMapping.java | 52 +++++++++++++------ 1 file changed, 37 insertions(+), 15 deletions(-) diff --git a/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/NodeMapping.java b/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/NodeMapping.java index ee5c0e1032d..8513d8ae843 100644 --- a/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/NodeMapping.java +++ b/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/NodeMapping.java @@ -14,6 +14,7 @@ import com.powsybl.iidm.network.VoltageLevel; import java.util.HashMap; +import java.util.List; import java.util.Map; /** @@ -54,32 +55,54 @@ public int iidmNodeForTerminal(CgmesTerminal t, boolean isSwitchEnd, VoltageLeve // map this situation to IIDM. // This behavior can be disabled through configuration. - int iidmNodeForConductingEquipment = cgmes2iidm.computeIfAbsent(t.id(), id -> newNode(vl)); + int iidmNodeForConductingEquipment; + int iidmNodeForConnectivityNode; if (connected || !createFictitiousSwitch(context.config().getCreateFictitiousSwitchesForDisconnectedTerminalsMode(), isSwitchEnd)) { - var connectivityNodeTerminals = context.terminalMapping().getConnectivityNodeTerminals(t.connectivityNode()); + List connectivityNodeTerminals = context.terminalMapping().getConnectivityNodeTerminals(t.connectivityNode()); if (connectivityNodeTerminals.size() == 2) { - // Add internal connection between two terminals as iidm can only handle one terminal per node + // Add one internal connection between the two terminals as iidm can only handle one terminal per node. + // We only create internal connection if the other side hasn't yet. String otherTerminalId = t.id().equals(connectivityNodeTerminals.get(0)) ? connectivityNodeTerminals.get(1) : connectivityNodeTerminals.get(0); if (!cgmes2iidm.containsKey(otherTerminalId)) { int iidmNodeForOtherConductingEquipment = newNode(vl); + iidmNodeForConnectivityNode = cgmes2iidm.computeIfAbsent(t.connectivityNode(), id -> newNode(vl)); + iidmNodeForConductingEquipment = iidmNodeForConnectivityNode; // connectivity node and terminal share the same iidm node + cgmes2iidm.put(t.id(), iidmNodeForConductingEquipment); cgmes2iidm.put(otherTerminalId, iidmNodeForOtherConductingEquipment); vl.getNodeBreakerView().newInternalConnection() - .setNode1(iidmNodeForConductingEquipment) + .setNode1(iidmNodeForConnectivityNode) .setNode2(iidmNodeForOtherConductingEquipment) .add(); + } else { + iidmNodeForConductingEquipment = cgmes2iidm.computeIfAbsent(t.id(), id -> newNode(vl)); } - } else if (connectivityNodeTerminals.size() > 1) { - int iidmNodeForConnectivityNode = cgmes2iidm.computeIfAbsent(t.connectivityNode(), id -> newNode(vl)); - - // Add internal connection from terminal to connectivity node as iidm can only handle one terminal per node - // For node-breaker models we create an internal connection between the terminal and its connectivity node - vl.getNodeBreakerView().newInternalConnection() - .setNode1(iidmNodeForConductingEquipment) - .setNode2(iidmNodeForConnectivityNode) - .add(); + } else if (connectivityNodeTerminals.size() > 2) { + // We need the connectivity node as connecting point between terminals + iidmNodeForConnectivityNode = cgmes2iidm.computeIfAbsent(t.connectivityNode(), id -> newNode(vl)); + if (t.conductingEquipmentType().equals("BusbarSection")) { + // If there's one busbar, it is placed at the connectivity node: in iidm we want the busbar (and not + // the connectivity point!) to be where all feeders connect. + // FIXME: don't do it if more than one busbar (does it make sense?) + iidmNodeForConductingEquipment = iidmNodeForConnectivityNode; // current terminal is placed at the connectivityNode + cgmes2iidm.put(t.id(), iidmNodeForConductingEquipment); + } else { + // Add internal connection from terminal to connectivity node as iidm can only handle one terminal per node + // For node-breaker models we create an internal connection between the terminal and its connectivity node + iidmNodeForConductingEquipment = cgmes2iidm.computeIfAbsent(t.id(), id -> newNode(vl)); + vl.getNodeBreakerView().newInternalConnection() + .setNode1(iidmNodeForConductingEquipment) + .setNode2(iidmNodeForConnectivityNode) + .add(); + } + } else { + // only one terminal: connectivity node and terminal share the same iidm node + iidmNodeForConnectivityNode = cgmes2iidm.computeIfAbsent(t.connectivityNode(), id -> newNode(vl)); + iidmNodeForConductingEquipment = iidmNodeForConnectivityNode; + cgmes2iidm.put(t.id(), iidmNodeForConductingEquipment); } } else { - int iidmNodeForConnectivityNode = cgmes2iidm.computeIfAbsent(t.connectivityNode(), id -> newNode(vl)); + iidmNodeForConductingEquipment = cgmes2iidm.computeIfAbsent(t.id(), id -> newNode(vl)); + iidmNodeForConnectivityNode = cgmes2iidm.computeIfAbsent(t.connectivityNode(), id -> newNode(vl)); // Only add fictitious switches for disconnected terminals if not already added // Use the id and name of terminal @@ -98,7 +121,6 @@ public int iidmNodeForTerminal(CgmesTerminal t, boolean isSwitchEnd, VoltageLeve sw.setProperty(Conversion.PROPERTY_IS_CREATED_FOR_DISCONNECTED_TERMINAL, "true"); } } - return iidmNodeForConductingEquipment; } From 17a67703af93ee36761e87c1cc66bc31cd56e06d Mon Sep 17 00:00:00 2001 From: Florian Dupuy Date: Wed, 13 Nov 2024 15:14:51 +0100 Subject: [PATCH 4/9] Only first busbar section placed at connectivity nodes Signed-off-by: Florian Dupuy --- .../powsybl/cgmes/conversion/NodeMapping.java | 24 ++++++++++++++----- .../cgmes/conversion/TerminalMapping.java | 8 +++---- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/NodeMapping.java b/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/NodeMapping.java index 8513d8ae843..5be897163c9 100644 --- a/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/NodeMapping.java +++ b/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/NodeMapping.java @@ -58,17 +58,17 @@ public int iidmNodeForTerminal(CgmesTerminal t, boolean isSwitchEnd, VoltageLeve int iidmNodeForConductingEquipment; int iidmNodeForConnectivityNode; if (connected || !createFictitiousSwitch(context.config().getCreateFictitiousSwitchesForDisconnectedTerminalsMode(), isSwitchEnd)) { - List connectivityNodeTerminals = context.terminalMapping().getConnectivityNodeTerminals(t.connectivityNode()); + List connectivityNodeTerminals = context.terminalMapping().getConnectivityNodeTerminals(t.connectivityNode()); if (connectivityNodeTerminals.size() == 2) { // Add one internal connection between the two terminals as iidm can only handle one terminal per node. // We only create internal connection if the other side hasn't yet. - String otherTerminalId = t.id().equals(connectivityNodeTerminals.get(0)) ? connectivityNodeTerminals.get(1) : connectivityNodeTerminals.get(0); - if (!cgmes2iidm.containsKey(otherTerminalId)) { + CgmesTerminal otherTerminal = t == connectivityNodeTerminals.get(0) ? connectivityNodeTerminals.get(1) : connectivityNodeTerminals.get(0); + if (!cgmes2iidm.containsKey(otherTerminal.id())) { int iidmNodeForOtherConductingEquipment = newNode(vl); iidmNodeForConnectivityNode = cgmes2iidm.computeIfAbsent(t.connectivityNode(), id -> newNode(vl)); iidmNodeForConductingEquipment = iidmNodeForConnectivityNode; // connectivity node and terminal share the same iidm node cgmes2iidm.put(t.id(), iidmNodeForConductingEquipment); - cgmes2iidm.put(otherTerminalId, iidmNodeForOtherConductingEquipment); + cgmes2iidm.put(otherTerminal.id(), iidmNodeForOtherConductingEquipment); vl.getNodeBreakerView().newInternalConnection() .setNode1(iidmNodeForConnectivityNode) .setNode2(iidmNodeForOtherConductingEquipment) @@ -79,10 +79,11 @@ public int iidmNodeForTerminal(CgmesTerminal t, boolean isSwitchEnd, VoltageLeve } else if (connectivityNodeTerminals.size() > 2) { // We need the connectivity node as connecting point between terminals iidmNodeForConnectivityNode = cgmes2iidm.computeIfAbsent(t.connectivityNode(), id -> newNode(vl)); - if (t.conductingEquipmentType().equals("BusbarSection")) { + if (isBusbarSectionTerminal(t) && isFirstBbsAtConnectivityNode(t, connectivityNodeTerminals)) { // If there's one busbar, it is placed at the connectivity node: in iidm we want the busbar (and not // the connectivity point!) to be where all feeders connect. - // FIXME: don't do it if more than one busbar (does it make sense?) + // If there are several busbars, this is only done for the first one encountered, the other ones + // will be connected to the first one with internal connections. iidmNodeForConductingEquipment = iidmNodeForConnectivityNode; // current terminal is placed at the connectivityNode cgmes2iidm.put(t.id(), iidmNodeForConductingEquipment); } else { @@ -124,6 +125,17 @@ public int iidmNodeForTerminal(CgmesTerminal t, boolean isSwitchEnd, VoltageLeve return iidmNodeForConductingEquipment; } + private boolean isFirstBbsAtConnectivityNode(CgmesTerminal t, List connectivityNodeTerminals) { + return connectivityNodeTerminals.stream() + .filter(t1 -> t != t1) + .filter(NodeMapping::isBusbarSectionTerminal) + .noneMatch(t1 -> cgmes2iidm.containsKey(t1.id())); + } + + private static boolean isBusbarSectionTerminal(CgmesTerminal t) { + return t.conductingEquipmentType().equals("BusbarSection"); + } + private static boolean createFictitiousSwitch(CgmesImport.FictitiousSwitchesCreationMode mode, boolean isSwitchEnd) { return switch (mode) { case ALWAYS -> true; diff --git a/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/TerminalMapping.java b/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/TerminalMapping.java index 125ea34accb..582060ff93e 100644 --- a/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/TerminalMapping.java +++ b/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/TerminalMapping.java @@ -32,7 +32,7 @@ public class TerminalMapping { // (only mapped when the terminal is connected to a branch) private final Map terminalNumbers = new HashMap<>(); private final Map> topologicalNodesMapping = new HashMap<>(); - private final Map> connectivityNodesCounting = new HashMap<>(); + private final Map> connectivityNodeTerminalsMapping = new HashMap<>(); private final Map cgmesTerminalsMapping = new HashMap<>(); public void add(String cgmesTerminal, Terminal iidmTerminal, int terminalNumber) { @@ -152,7 +152,7 @@ public void buildConnectivityNodeCgmesTerminalsMapping(CgmesTerminal t) { } String connectivityNode = t.connectivityNode(); if (connectivityNode != null) { - connectivityNodesCounting.computeIfAbsent(connectivityNode, k -> new ArrayList<>(1)).add(t.id()); + connectivityNodeTerminalsMapping.computeIfAbsent(connectivityNode, k -> new ArrayList<>(1)).add(t); } } @@ -184,7 +184,7 @@ public String findCgmesTerminalFromTopologicalNode(String topologicalNode) { return topologicalNodesMapping.containsKey(topologicalNode) ? topologicalNodesMapping.get(topologicalNode).get(0) : null; } - public List getConnectivityNodeTerminals(String id) { - return connectivityNodesCounting.getOrDefault(id, List.of()); + public List getConnectivityNodeTerminals(String id) { + return connectivityNodeTerminalsMapping.getOrDefault(id, List.of()); } } From 1dbccf56d3d557d7f377466cae19aa7219ca9761 Mon Sep 17 00:00:00 2001 From: Florian Dupuy Date: Wed, 13 Nov 2024 15:27:39 +0100 Subject: [PATCH 5/9] Cut iidmNodeForTerminal in two parts Signed-off-by: Florian Dupuy --- .../powsybl/cgmes/conversion/NodeMapping.java | 122 ++++++++++-------- 1 file changed, 66 insertions(+), 56 deletions(-) diff --git a/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/NodeMapping.java b/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/NodeMapping.java index 5be897163c9..dae7cc7995f 100644 --- a/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/NodeMapping.java +++ b/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/NodeMapping.java @@ -55,72 +55,82 @@ public int iidmNodeForTerminal(CgmesTerminal t, boolean isSwitchEnd, VoltageLeve // map this situation to IIDM. // This behavior can be disabled through configuration. + if (!connected && createFictitiousSwitch(context.config().getCreateFictitiousSwitchesForDisconnectedTerminalsMode(), isSwitchEnd)) { + return createFictitiousSwitch(t, vl); + } else { + // Create internal connection but only if too many terminals on connectivity node + return createInternalConnectionIfNeeded(t, vl); + } + } + + private int createInternalConnectionIfNeeded(CgmesTerminal t, VoltageLevel vl) { int iidmNodeForConductingEquipment; int iidmNodeForConnectivityNode; - if (connected || !createFictitiousSwitch(context.config().getCreateFictitiousSwitchesForDisconnectedTerminalsMode(), isSwitchEnd)) { - List connectivityNodeTerminals = context.terminalMapping().getConnectivityNodeTerminals(t.connectivityNode()); - if (connectivityNodeTerminals.size() == 2) { - // Add one internal connection between the two terminals as iidm can only handle one terminal per node. - // We only create internal connection if the other side hasn't yet. - CgmesTerminal otherTerminal = t == connectivityNodeTerminals.get(0) ? connectivityNodeTerminals.get(1) : connectivityNodeTerminals.get(0); - if (!cgmes2iidm.containsKey(otherTerminal.id())) { - int iidmNodeForOtherConductingEquipment = newNode(vl); - iidmNodeForConnectivityNode = cgmes2iidm.computeIfAbsent(t.connectivityNode(), id -> newNode(vl)); - iidmNodeForConductingEquipment = iidmNodeForConnectivityNode; // connectivity node and terminal share the same iidm node - cgmes2iidm.put(t.id(), iidmNodeForConductingEquipment); - cgmes2iidm.put(otherTerminal.id(), iidmNodeForOtherConductingEquipment); - vl.getNodeBreakerView().newInternalConnection() - .setNode1(iidmNodeForConnectivityNode) - .setNode2(iidmNodeForOtherConductingEquipment) - .add(); - } else { - iidmNodeForConductingEquipment = cgmes2iidm.computeIfAbsent(t.id(), id -> newNode(vl)); - } - } else if (connectivityNodeTerminals.size() > 2) { - // We need the connectivity node as connecting point between terminals + List connectivityNodeTerminals = context.terminalMapping().getConnectivityNodeTerminals(t.connectivityNode()); + if (connectivityNodeTerminals.size() == 2) { + // Add one internal connection between the two terminals as iidm can only handle one terminal per node. + // We only create internal connection if the other side hasn't yet. + CgmesTerminal otherTerminal = t == connectivityNodeTerminals.get(0) ? connectivityNodeTerminals.get(1) : connectivityNodeTerminals.get(0); + if (!cgmes2iidm.containsKey(otherTerminal.id())) { + int iidmNodeForOtherConductingEquipment = newNode(vl); iidmNodeForConnectivityNode = cgmes2iidm.computeIfAbsent(t.connectivityNode(), id -> newNode(vl)); - if (isBusbarSectionTerminal(t) && isFirstBbsAtConnectivityNode(t, connectivityNodeTerminals)) { - // If there's one busbar, it is placed at the connectivity node: in iidm we want the busbar (and not - // the connectivity point!) to be where all feeders connect. - // If there are several busbars, this is only done for the first one encountered, the other ones - // will be connected to the first one with internal connections. - iidmNodeForConductingEquipment = iidmNodeForConnectivityNode; // current terminal is placed at the connectivityNode - cgmes2iidm.put(t.id(), iidmNodeForConductingEquipment); - } else { - // Add internal connection from terminal to connectivity node as iidm can only handle one terminal per node - // For node-breaker models we create an internal connection between the terminal and its connectivity node - iidmNodeForConductingEquipment = cgmes2iidm.computeIfAbsent(t.id(), id -> newNode(vl)); - vl.getNodeBreakerView().newInternalConnection() - .setNode1(iidmNodeForConductingEquipment) - .setNode2(iidmNodeForConnectivityNode) - .add(); - } - } else { - // only one terminal: connectivity node and terminal share the same iidm node - iidmNodeForConnectivityNode = cgmes2iidm.computeIfAbsent(t.connectivityNode(), id -> newNode(vl)); - iidmNodeForConductingEquipment = iidmNodeForConnectivityNode; + iidmNodeForConductingEquipment = iidmNodeForConnectivityNode; // connectivity node and terminal share the same iidm node cgmes2iidm.put(t.id(), iidmNodeForConductingEquipment); + cgmes2iidm.put(otherTerminal.id(), iidmNodeForOtherConductingEquipment); + vl.getNodeBreakerView().newInternalConnection() + .setNode1(iidmNodeForConnectivityNode) + .setNode2(iidmNodeForOtherConductingEquipment) + .add(); + } else { + iidmNodeForConductingEquipment = cgmes2iidm.computeIfAbsent(t.id(), id -> newNode(vl)); } - } else { - iidmNodeForConductingEquipment = cgmes2iidm.computeIfAbsent(t.id(), id -> newNode(vl)); + } else if (connectivityNodeTerminals.size() > 2) { + // We need the connectivity node as connecting point between terminals iidmNodeForConnectivityNode = cgmes2iidm.computeIfAbsent(t.connectivityNode(), id -> newNode(vl)); - - // Only add fictitious switches for disconnected terminals if not already added - // Use the id and name of terminal - String switchId = t.id() + "_SW_fict"; - if (vl.getNetwork().getSwitch(switchId) == null) { - Switch sw = vl.getNodeBreakerView().newSwitch() - .setFictitious(true) - .setId(switchId) - .setName(t.name()) + if (isBusbarSectionTerminal(t) && isFirstBbsAtConnectivityNode(t, connectivityNodeTerminals)) { + // If there's one busbar, it is placed at the connectivity node: in iidm we want the busbar (and not + // the connectivity point!) to be where all feeders connect. + // If there are several busbars, this is only done for the first one encountered, the other ones + // will be connected to the first one with internal connections. + iidmNodeForConductingEquipment = iidmNodeForConnectivityNode; // current terminal is placed at the connectivityNode + cgmes2iidm.put(t.id(), iidmNodeForConductingEquipment); + } else { + // Add internal connection from terminal to connectivity node as iidm can only handle one terminal per node + // For node-breaker models we create an internal connection between the terminal and its connectivity node + iidmNodeForConductingEquipment = cgmes2iidm.computeIfAbsent(t.id(), id -> newNode(vl)); + vl.getNodeBreakerView().newInternalConnection() .setNode1(iidmNodeForConductingEquipment) .setNode2(iidmNodeForConnectivityNode) - .setOpen(true) - .setKind(SwitchKind.BREAKER) - .setEnsureIdUnicity(context.config().isEnsureIdAliasUnicity()) .add(); - sw.setProperty(Conversion.PROPERTY_IS_CREATED_FOR_DISCONNECTED_TERMINAL, "true"); } + } else { + // only one terminal: connectivity node and terminal share the same iidm node + iidmNodeForConnectivityNode = cgmes2iidm.computeIfAbsent(t.connectivityNode(), id -> newNode(vl)); + iidmNodeForConductingEquipment = iidmNodeForConnectivityNode; + cgmes2iidm.put(t.id(), iidmNodeForConductingEquipment); + } + return iidmNodeForConductingEquipment; + } + + private int createFictitiousSwitch(CgmesTerminal t, VoltageLevel vl) { + int iidmNodeForConductingEquipment = cgmes2iidm.computeIfAbsent(t.id(), id -> newNode(vl)); + int iidmNodeForConnectivityNode = cgmes2iidm.computeIfAbsent(t.connectivityNode(), id -> newNode(vl)); + + // Only add fictitious switches for disconnected terminals if not already added + // Use the id and name of terminal + String switchId = t.id() + "_SW_fict"; + if (vl.getNetwork().getSwitch(switchId) == null) { + Switch sw = vl.getNodeBreakerView().newSwitch() + .setFictitious(true) + .setId(switchId) + .setName(t.name()) + .setNode1(iidmNodeForConductingEquipment) + .setNode2(iidmNodeForConnectivityNode) + .setOpen(true) + .setKind(SwitchKind.BREAKER) + .setEnsureIdUnicity(context.config().isEnsureIdAliasUnicity()) + .add(); + sw.setProperty(Conversion.PROPERTY_IS_CREATED_FOR_DISCONNECTED_TERMINAL, "true"); } return iidmNodeForConductingEquipment; } From f6d064d8f73103adc42a9c3ee254195eb1dff0a8 Mon Sep 17 00:00:00 2001 From: Florian Dupuy Date: Wed, 13 Nov 2024 16:44:25 +0100 Subject: [PATCH 6/9] Corner case of two terminals with at least one from a bbs Signed-off-by: Florian Dupuy --- .../powsybl/cgmes/conversion/NodeMapping.java | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/NodeMapping.java b/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/NodeMapping.java index dae7cc7995f..acdd4c241a0 100644 --- a/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/NodeMapping.java +++ b/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/NodeMapping.java @@ -69,20 +69,25 @@ private int createInternalConnectionIfNeeded(CgmesTerminal t, VoltageLevel vl) { List connectivityNodeTerminals = context.terminalMapping().getConnectivityNodeTerminals(t.connectivityNode()); if (connectivityNodeTerminals.size() == 2) { // Add one internal connection between the two terminals as iidm can only handle one terminal per node. - // We only create internal connection if the other side hasn't yet. + // We only create an internal connection + // - at busbarSection side if there's only one busbarSection terminal + // - at first terminal encountered otherwise CgmesTerminal otherTerminal = t == connectivityNodeTerminals.get(0) ? connectivityNodeTerminals.get(1) : connectivityNodeTerminals.get(0); - if (!cgmes2iidm.containsKey(otherTerminal.id())) { - int iidmNodeForOtherConductingEquipment = newNode(vl); - iidmNodeForConnectivityNode = cgmes2iidm.computeIfAbsent(t.connectivityNode(), id -> newNode(vl)); + boolean terminalBbs = isBusbarSectionTerminal(t); + boolean otherTerminalBbs = isBusbarSectionTerminal(otherTerminal); + boolean firstTerminalAtConnectivityNode = !cgmes2iidm.containsKey(otherTerminal.id()); + iidmNodeForConnectivityNode = cgmes2iidm.computeIfAbsent(t.connectivityNode(), id -> newNode(vl)); + if (firstTerminalAtConnectivityNode && terminalBbs && otherTerminalBbs + || firstTerminalAtConnectivityNode && !otherTerminalBbs + || terminalBbs && !otherTerminalBbs) { iidmNodeForConductingEquipment = iidmNodeForConnectivityNode; // connectivity node and terminal share the same iidm node cgmes2iidm.put(t.id(), iidmNodeForConductingEquipment); - cgmes2iidm.put(otherTerminal.id(), iidmNodeForOtherConductingEquipment); + } else { + iidmNodeForConductingEquipment = cgmes2iidm.computeIfAbsent(t.id(), id -> newNode(vl)); vl.getNodeBreakerView().newInternalConnection() .setNode1(iidmNodeForConnectivityNode) - .setNode2(iidmNodeForOtherConductingEquipment) + .setNode2(iidmNodeForConductingEquipment) .add(); - } else { - iidmNodeForConductingEquipment = cgmes2iidm.computeIfAbsent(t.id(), id -> newNode(vl)); } } else if (connectivityNodeTerminals.size() > 2) { // We need the connectivity node as connecting point between terminals From 5bf28c376145a154bf2bd0307f900a47ff4224a2 Mon Sep 17 00:00:00 2001 From: Florian Dupuy Date: Thu, 14 Nov 2024 14:24:44 +0100 Subject: [PATCH 7/9] Fix for busbarSectionFOrEveryConnectivityNode config Signed-off-by: Florian Dupuy --- .../com/powsybl/cgmes/conversion/NodeMapping.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/NodeMapping.java b/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/NodeMapping.java index acdd4c241a0..16a2795f310 100644 --- a/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/NodeMapping.java +++ b/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/NodeMapping.java @@ -64,6 +64,9 @@ public int iidmNodeForTerminal(CgmesTerminal t, boolean isSwitchEnd, VoltageLeve } private int createInternalConnectionIfNeeded(CgmesTerminal t, VoltageLevel vl) { + if (context.config().createBusbarSectionForEveryConnectivityNode()) { + return createInternalConnection(t, vl); + } int iidmNodeForConductingEquipment; int iidmNodeForConnectivityNode; List connectivityNodeTerminals = context.terminalMapping().getConnectivityNodeTerminals(t.connectivityNode()); @@ -117,6 +120,16 @@ private int createInternalConnectionIfNeeded(CgmesTerminal t, VoltageLevel vl) { return iidmNodeForConductingEquipment; } + private int createInternalConnection(CgmesTerminal t, VoltageLevel vl) { + int iidmNodeForConnectivityNode = cgmes2iidm.computeIfAbsent(t.connectivityNode(), id -> newNode(vl)); + int iidmNodeForConductingEquipment = cgmes2iidm.computeIfAbsent(t.id(), id -> newNode(vl)); + vl.getNodeBreakerView().newInternalConnection() + .setNode1(iidmNodeForConnectivityNode) + .setNode2(iidmNodeForConductingEquipment) + .add(); + return iidmNodeForConductingEquipment; + } + private int createFictitiousSwitch(CgmesTerminal t, VoltageLevel vl) { int iidmNodeForConductingEquipment = cgmes2iidm.computeIfAbsent(t.id(), id -> newNode(vl)); int iidmNodeForConnectivityNode = cgmes2iidm.computeIfAbsent(t.connectivityNode(), id -> newNode(vl)); From 61b1f558610feef06fc054d0b0088f558e4441b2 Mon Sep 17 00:00:00 2001 From: Florian Dupuy Date: Thu, 14 Nov 2024 14:25:24 +0100 Subject: [PATCH 8/9] Update 2 unit tests (only 2!!) due to changes Signed-off-by: Florian Dupuy --- .../conversion/test/GroundConversionTest.java | 46 ++++++------------- .../export/TopologyExportCornerCasesTest.java | 6 ++- .../resources/groundConversionRemoveGraph.dot | 14 ++++++ 3 files changed, 33 insertions(+), 33 deletions(-) create mode 100644 cgmes/cgmes-conversion/src/test/resources/groundConversionRemoveGraph.dot diff --git a/cgmes/cgmes-conversion/src/test/java/com/powsybl/cgmes/conversion/test/GroundConversionTest.java b/cgmes/cgmes-conversion/src/test/java/com/powsybl/cgmes/conversion/test/GroundConversionTest.java index 744e6e0ec62..43728a291a4 100644 --- a/cgmes/cgmes-conversion/src/test/java/com/powsybl/cgmes/conversion/test/GroundConversionTest.java +++ b/cgmes/cgmes-conversion/src/test/java/com/powsybl/cgmes/conversion/test/GroundConversionTest.java @@ -8,17 +8,20 @@ package com.powsybl.cgmes.conversion.test; +import com.google.common.io.ByteStreams; import com.powsybl.cgmes.conversion.CgmesImport; import com.powsybl.commons.datasource.ResourceDataSource; import com.powsybl.commons.datasource.ResourceSet; import com.powsybl.commons.test.AbstractSerDeTest; +import com.powsybl.commons.test.ComparisonUtils; import com.powsybl.iidm.network.Ground; import com.powsybl.iidm.network.Network; import org.junit.jupiter.api.Test; import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; +import java.io.StringWriter; +import java.nio.charset.StandardCharsets; +import java.util.Objects; import java.util.Properties; import static org.junit.jupiter.api.Assertions.*; @@ -51,7 +54,7 @@ void groundConversionTest() { } @Test - void groundConversionRemoveTest() { + void groundConversionRemoveTest() throws IOException { Properties importParams = new Properties(); importParams.put(CgmesImport.POST_PROCESSORS, "RemoveGrounds"); Network network = Network.read( @@ -63,36 +66,17 @@ void groundConversionRemoveTest() { // Check also the exported GraphViz // Some edges have been removed, ensure it is exported properly String actual = graphVizClean(graphViz(network, "S")); - String expected = graphVizClean(""" - digraph G { - \tnode [shape=box]; - \tcompound=true; - \tn0 [label="0",shape="ellipse",style="filled",fillcolor="#8F7AF3"]; - \tn2 [label="5\\lBUSBAR_SECTION\\lAX\\lEF",shape="ellipse",style="filled",fillcolor="#8F7AF3"]; - \tn3 [label="8\\lGENERATOR\\lZX\\lZY",shape="ellipse",style="filled",fillcolor="#8F7AF3"]; - \tn2 -> n0 []; - \tn3 -> n0 []; - \tsubgraph cluster_c1 { - \t\t// scope=1392570698 - \t\tcluster_c1 [label="",shape=point,style=invis]; - \t\tpencolor="transparent"; - \t\tn0; - \t\tn2; - \t\tn3; - \t} - } - """); - assertEquals(expected, actual); + + String rawExpected = new String(ByteStreams.toByteArray(Objects.requireNonNull( + getClass().getResourceAsStream("/groundConversionRemoveGraph.dot"))), StandardCharsets.UTF_8); + String expected = graphVizClean(rawExpected); + ComparisonUtils.assertTxtEquals(expected, actual); } - private String graphViz(Network network, String voltageLevelId) { - try { - Path gv = tmpDir.resolve(voltageLevelId + ".gv"); - network.getVoltageLevel(voltageLevelId).exportTopology(gv); - return Files.readString(gv); - } catch (IOException e) { - throw new RuntimeException(e); - } + private String graphViz(Network network, String voltageLevelId) throws IOException { + StringWriter writer = new StringWriter(); + network.getVoltageLevel(voltageLevelId).exportTopology(writer); + return writer.toString(); } private String graphVizClean(String gv) { diff --git a/cgmes/cgmes-conversion/src/test/java/com/powsybl/cgmes/conversion/test/export/TopologyExportCornerCasesTest.java b/cgmes/cgmes-conversion/src/test/java/com/powsybl/cgmes/conversion/test/export/TopologyExportCornerCasesTest.java index 4bb61a53619..77d8d7fb3df 100644 --- a/cgmes/cgmes-conversion/src/test/java/com/powsybl/cgmes/conversion/test/export/TopologyExportCornerCasesTest.java +++ b/cgmes/cgmes-conversion/src/test/java/com/powsybl/cgmes/conversion/test/export/TopologyExportCornerCasesTest.java @@ -39,7 +39,7 @@ void testExportGeneratorDisconnectedTransformerBusBreaker() { // The condition for a valid bus in the BusView for bus-breaker and node-breaker is slightly different // So we end up with different bus-view buses test(createGeneratorDisconnectedTransformerBBNetwork(), false, false, - new String[] {"voltageLevel1_0", "voltageLevel2_0", "voltageLevel2_1"}); + new String[] {"voltageLevel2_0", "voltageLevel2_1"}); // FIXME: no bus in busbreaker view for the voltage levell 1: no edges! } @Test @@ -59,7 +59,7 @@ void testExportDisconnectedLoadBusBreaker() { @Test void testExportDisconnectedLoadNodeBreaker() { test(createDisconnectedLoadNBNetwork(), false, true, - new String[] {"voltageLevel1_0", "voltageLevel1_1", "voltageLevel1_3"}); + new String[] {"voltageLevel1_0", "voltageLevel1_1", "voltageLevel1_2"}); } private void test(Network network, @@ -95,6 +95,8 @@ private void test(Network network, networkFromCgmes.getBusView().getBusStream().count()); } + networkFromCgmes.getVoltageLevel("voltageLevel1").getBusBreakerView().getBuses(); + // And the list of buses should be the expected one assertArrayEquals( expectedBusBreakerViewBuses, diff --git a/cgmes/cgmes-conversion/src/test/resources/groundConversionRemoveGraph.dot b/cgmes/cgmes-conversion/src/test/resources/groundConversionRemoveGraph.dot new file mode 100644 index 00000000000..7d98304d688 --- /dev/null +++ b/cgmes/cgmes-conversion/src/test/resources/groundConversionRemoveGraph.dot @@ -0,0 +1,14 @@ +digraph G { + node [shape=box]; + compound=true; + n0 [label="0\lBUSBAR_SECTION\lAX\lEF",shape="ellipse",style="filled",fillcolor="---"]; + n2 [label="5\lGENERATOR\lZX\lZY",shape="ellipse",style="filled",fillcolor="---"]; + n0 -> n2 []; + subgraph cluster_c1 { + // + cluster_c1 [label="",shape=point,style=invis]; + pencolor="transparent"; + n0; + n2; + } +} From 471459f0c99e6b8163adbc79b5e5b10cb7e64e7e Mon Sep 17 00:00:00 2001 From: Florian Dupuy Date: Thu, 14 Nov 2024 14:37:39 +0100 Subject: [PATCH 9/9] Small refactor Signed-off-by: Florian Dupuy --- .../powsybl/cgmes/conversion/NodeMapping.java | 33 +++++++------------ 1 file changed, 11 insertions(+), 22 deletions(-) diff --git a/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/NodeMapping.java b/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/NodeMapping.java index 16a2795f310..1d43bffbbd3 100644 --- a/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/NodeMapping.java +++ b/cgmes/cgmes-conversion/src/main/java/com/powsybl/cgmes/conversion/NodeMapping.java @@ -67,8 +67,6 @@ private int createInternalConnectionIfNeeded(CgmesTerminal t, VoltageLevel vl) { if (context.config().createBusbarSectionForEveryConnectivityNode()) { return createInternalConnection(t, vl); } - int iidmNodeForConductingEquipment; - int iidmNodeForConnectivityNode; List connectivityNodeTerminals = context.terminalMapping().getConnectivityNodeTerminals(t.connectivityNode()); if (connectivityNodeTerminals.size() == 2) { // Add one internal connection between the two terminals as iidm can only handle one terminal per node. @@ -79,45 +77,30 @@ private int createInternalConnectionIfNeeded(CgmesTerminal t, VoltageLevel vl) { boolean terminalBbs = isBusbarSectionTerminal(t); boolean otherTerminalBbs = isBusbarSectionTerminal(otherTerminal); boolean firstTerminalAtConnectivityNode = !cgmes2iidm.containsKey(otherTerminal.id()); - iidmNodeForConnectivityNode = cgmes2iidm.computeIfAbsent(t.connectivityNode(), id -> newNode(vl)); if (firstTerminalAtConnectivityNode && terminalBbs && otherTerminalBbs || firstTerminalAtConnectivityNode && !otherTerminalBbs || terminalBbs && !otherTerminalBbs) { - iidmNodeForConductingEquipment = iidmNodeForConnectivityNode; // connectivity node and terminal share the same iidm node - cgmes2iidm.put(t.id(), iidmNodeForConductingEquipment); + return oneIidmNodeForBothTerminalAndConnectivityNode(t, vl); } else { - iidmNodeForConductingEquipment = cgmes2iidm.computeIfAbsent(t.id(), id -> newNode(vl)); - vl.getNodeBreakerView().newInternalConnection() - .setNode1(iidmNodeForConnectivityNode) - .setNode2(iidmNodeForConductingEquipment) - .add(); + return createInternalConnection(t, vl); } } else if (connectivityNodeTerminals.size() > 2) { // We need the connectivity node as connecting point between terminals - iidmNodeForConnectivityNode = cgmes2iidm.computeIfAbsent(t.connectivityNode(), id -> newNode(vl)); if (isBusbarSectionTerminal(t) && isFirstBbsAtConnectivityNode(t, connectivityNodeTerminals)) { // If there's one busbar, it is placed at the connectivity node: in iidm we want the busbar (and not // the connectivity point!) to be where all feeders connect. // If there are several busbars, this is only done for the first one encountered, the other ones // will be connected to the first one with internal connections. - iidmNodeForConductingEquipment = iidmNodeForConnectivityNode; // current terminal is placed at the connectivityNode - cgmes2iidm.put(t.id(), iidmNodeForConductingEquipment); + return oneIidmNodeForBothTerminalAndConnectivityNode(t, vl); } else { // Add internal connection from terminal to connectivity node as iidm can only handle one terminal per node // For node-breaker models we create an internal connection between the terminal and its connectivity node - iidmNodeForConductingEquipment = cgmes2iidm.computeIfAbsent(t.id(), id -> newNode(vl)); - vl.getNodeBreakerView().newInternalConnection() - .setNode1(iidmNodeForConductingEquipment) - .setNode2(iidmNodeForConnectivityNode) - .add(); + return createInternalConnection(t, vl); } } else { // only one terminal: connectivity node and terminal share the same iidm node - iidmNodeForConnectivityNode = cgmes2iidm.computeIfAbsent(t.connectivityNode(), id -> newNode(vl)); - iidmNodeForConductingEquipment = iidmNodeForConnectivityNode; - cgmes2iidm.put(t.id(), iidmNodeForConductingEquipment); + return oneIidmNodeForBothTerminalAndConnectivityNode(t, vl); } - return iidmNodeForConductingEquipment; } private int createInternalConnection(CgmesTerminal t, VoltageLevel vl) { @@ -130,6 +113,12 @@ private int createInternalConnection(CgmesTerminal t, VoltageLevel vl) { return iidmNodeForConductingEquipment; } + private int oneIidmNodeForBothTerminalAndConnectivityNode(CgmesTerminal t, VoltageLevel vl) { + int iidmNodeForConnectivityNode = cgmes2iidm.computeIfAbsent(t.connectivityNode(), id -> newNode(vl)); + cgmes2iidm.put(t.id(), iidmNodeForConnectivityNode); // connectivity node and terminal share the same iidm node + return iidmNodeForConnectivityNode; + } + private int createFictitiousSwitch(CgmesTerminal t, VoltageLevel vl) { int iidmNodeForConductingEquipment = cgmes2iidm.computeIfAbsent(t.id(), id -> newNode(vl)); int iidmNodeForConnectivityNode = cgmes2iidm.computeIfAbsent(t.connectivityNode(), id -> newNode(vl));