Skip to content

[CGMES] Less internal connections created at import #3210

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 24 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
9f60314
Avoid creating internal connections when less than 2 terminals
flo-dup Nov 12, 2024
f6aafcb
Between 2 terminals create 1 internalConnection instead of 2
flo-dup Nov 13, 2024
0eda01d
Busbar sections placed at connectivity nodes
flo-dup Nov 13, 2024
17a6770
Only first busbar section placed at connectivity nodes
flo-dup Nov 13, 2024
1dbccf5
Cut iidmNodeForTerminal in two parts
flo-dup Nov 13, 2024
f6d064d
Corner case of two terminals with at least one from a bbs
flo-dup Nov 13, 2024
5bf28c3
Fix for busbarSectionFOrEveryConnectivityNode config
flo-dup Nov 14, 2024
61b1f55
Update 2 unit tests (only 2!!) due to changes
flo-dup Nov 14, 2024
471459f
Small refactor
flo-dup Nov 14, 2024
b917e9f
Merge branch 'main' into cgmes_less_internal_connections
flo-dup Feb 19, 2025
5ca972a
Remove color randomness in unit test
flo-dup Feb 20, 2025
722723e
Internal connections optimization only done if NEVER createFictSw(...)
flo-dup Feb 21, 2025
35111a8
Revert unit test changes, as the default behaviour is ALWAYS
flo-dup Feb 21, 2025
ceea5f8
Keep NEVER mode for one unit test
flo-dup Feb 21, 2025
7127106
Remove unused method
flo-dup Feb 27, 2025
0426971
Add unit test
flo-dup Mar 3, 2025
267b287
Rename variable
flo-dup Mar 3, 2025
74a3d9b
Explain further the particularity of bbs among other equipments
flo-dup Mar 3, 2025
f70e5bd
Clean
flo-dup Mar 3, 2025
4016812
Merge branch 'main' into cgmes_less_internal_connections
flo-dup Mar 3, 2025
643910a
Fix name in unit test
flo-dup Mar 3, 2025
24f3924
Merge branch 'main' into cgmes_less_internal_connections
flo-dup Mar 3, 2025
eb8daa3
Avoid creating internal connections even if not NEVER
flo-dup Mar 14, 2025
bdc4fe2
Merge remote-tracking branch 'origin/main' into cgmes_less_internal_c…
flo-dup Mar 14, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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().buildConnectivityNodeCgmesTerminalsMapping(t, context.config()));
cgmes.regulatingControls().forEach(p -> context.regulatingControlMapping().cacheRegulatingControls(p));
context.popReportNode();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,26 @@
package com.powsybl.cgmes.conversion;

import com.powsybl.cgmes.model.CgmesTerminal;
import com.powsybl.commons.PowsyblException;
import com.powsybl.iidm.network.Switch;
import com.powsybl.iidm.network.SwitchKind;
import com.powsybl.iidm.network.VoltageLevel;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
* @author Luma Zamarreño {@literal <zamarrenolm at aia.es>}
*/
public class NodeMapping {

private final Map<String, Integer> cgmes2iidm = new HashMap<>(100);
private final Map<VoltageLevel, Integer> voltageLevelNumNodes = new HashMap<>(100);
private final Context context;

public NodeMapping(Context context) {
this.context = context;
cgmes2iidm = new HashMap<>(100);
voltageLevelNumNodes = new HashMap<>(100);
}

public int iidmNodeForTopologicalNode(String id, int associatedNode, VoltageLevel vl) {
Expand All @@ -43,51 +47,118 @@ public int iidmNodeForTopologicalNode(String id, int associatedNode, VoltageLeve
}

public int iidmNodeForTerminal(CgmesTerminal t, boolean isSwitchEnd, VoltageLevel vl) {
return iidmNodeForTerminal(t, isSwitchEnd, vl, true);
// 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.

var fictSwCreationMode = context.config().getCreateFictitiousSwitchesForDisconnectedTerminalsMode();
boolean bbsForEveryConnectivityNode = context.config().createBusbarSectionForEveryConnectivityNode();
if (!t.connected() && createFictitiousSwitch(fictSwCreationMode, isSwitchEnd)) {
createFictitiousSwitch(t, vl);
} else {
if (isSwitchEnd) {
// for switches the iidm node is the one from the connectivity node
return cgmes2iidm.get(t.connectivityNode());
} else if (bbsForEveryConnectivityNode) {
createInternalConnection(t, vl);
} else if (!cgmes2iidm.containsKey(t.id())) {
// Create internal connections but only if too many terminals on connectivity node
createInternalConnectionsIfNeeded(t.connectivityNode(), vl);
}
}

return cgmes2iidm.get(t.id());
}

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
private void createInternalConnectionsIfNeeded(String connectivityNode, VoltageLevel vl) {
List<CgmesTerminal> connectivityNodeTerminals = context.terminalMapping().getConnectivityNodeTerminals(connectivityNode);
switch (connectivityNodeTerminals.size()) {
case 0 -> throw new PowsyblException("Erroneous connectivity node terminals mapping");
case 1 ->
// Only one terminal: no need to create an internal connection
// Connectivity node and terminal share the same iidm node
oneIidmNodeForBothTerminalAndConnectivityNode(connectivityNodeTerminals.get(0), vl);
case 2 -> {
// There are two terminals on the same connectivity node. We need one (and only one!) internal connection
// between the two terminals as iidm can only handle one terminal per node.
// There's a special treatment for BusbarSections described below. Indeed, a bbs is a central equipment,
// to which bays/cells connect. We want to keep this physical centrality in iidm, especially for
// easy graph traversals and visualization. Note that there are no other central equipments of this type.
// - if there's one bbs, it is placed at the connectivity node: in iidm we want the node of the bbs terminal
// (and not the connectivity point!) to be where all feeders connect.
// - if there are two bbs on the same connectivity node (that is, two united bbs), this is only done for
// the first bbs of the list.
CgmesTerminal t0 = connectivityNodeTerminals.get(0);
CgmesTerminal t1 = connectivityNodeTerminals.get(1);
if (!isBusbarSectionTerminal(t0) && isBusbarSectionTerminal(t1)) {
oneIidmNodeForBothTerminalAndConnectivityNode(t1, vl);
createInternalConnection(t0, vl);
} else {
oneIidmNodeForBothTerminalAndConnectivityNode(t0, vl);
createInternalConnection(t1, vl);
}
}
default -> {
// We need the connectivity node as connecting point between terminals
// Similarly to previous case, there's a special treatment for BusbarSections described thereafter for
// the same mentioned reasons.
// - if there's only one bbs, it is placed at the connectivity node.
// - if there are several bbs (that is, several united bbs), this is only done for the first one
// encountered, the other ones are connected to the first one with internal connections.
boolean bbsEncountered = false;
for (CgmesTerminal t : connectivityNodeTerminals) {
if (!bbsEncountered && isBusbarSectionTerminal(t)) {
oneIidmNodeForBothTerminalAndConnectivityNode(t, vl);
bbsEncountered = true;
} 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
createInternalConnection(t, vl);
}
}
}
}
}

private void createInternalConnection(CgmesTerminal t, VoltageLevel vl) {
int iidmNodeForConnectivityNode = cgmes2iidm.computeIfAbsent(t.connectivityNode(), id -> newNode(vl));
int iidmNodeForTerminal = cgmes2iidm.computeIfAbsent(t.id(), id -> newNode(vl));
vl.getNodeBreakerView().newInternalConnection()
.setNode1(iidmNodeForConnectivityNode)
.setNode2(iidmNodeForTerminal)
.add();
}

// 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
// map this situation to IIDM.
// This behavior can be disabled through configuration.
private void 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
}

boolean connected = t.connected() && equipmentIsConnected;
private void createFictitiousSwitch(CgmesTerminal t, VoltageLevel vl) {
int iidmNodeForConductingEquipment = cgmes2iidm.computeIfAbsent(t.id(), id -> newNode(vl));
int iidmNodeForConnectivityNode = cgmes2iidm.computeIfAbsent(t.connectivityNode(), id -> newNode(vl));

if (connected || !createFictitiousSwitch(context.config().getCreateFictitiousSwitchesForDisconnectedTerminalsMode(), isSwitchEnd)) {
// TODO(Luma): do not add an internal connection if is has already been added?
vl.getNodeBreakerView().newInternalConnection()
// 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();
} else {
// 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");
}
sw.setProperty(Conversion.PROPERTY_IS_CREATED_FOR_DISCONNECTED_TERMINAL, "true");
}
}

return iidmNodeForConductingEquipment;
private static boolean isBusbarSectionTerminal(CgmesTerminal t) {
return t.conductingEquipmentType().equals("BusbarSection");
}

private static boolean createFictitiousSwitch(CgmesImport.FictitiousSwitchesCreationMode mode, boolean isSwitchEnd) {
Expand All @@ -110,8 +181,4 @@ private int newNode(VoltageLevel vl) {
int numNodes = voltageLevelNumNodes.merge(vl, 1, Integer::sum);
return numNodes - 1;
}

private final Map<String, Integer> cgmes2iidm;
private final Map<VoltageLevel, Integer> voltageLevelNumNodes;
private final Context context;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<String, Terminal> terminals = new HashMap<>();
private final Map<String, Boundary> 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<String, Integer> terminalNumbers = new HashMap<>();
private final Map<String, List<String>> topologicalNodesMapping = new HashMap<>();
private final Map<String, List<CgmesTerminal>> connectivityNodeTerminalsMapping = new HashMap<>();
private final Map<String, String> cgmesTerminalsMapping = new HashMap<>();

public void add(String cgmesTerminal, Terminal iidmTerminal, int terminalNumber) {
if (terminals.containsKey(cgmesTerminal) || boundaries.containsKey(cgmesTerminal)) {
Expand Down Expand Up @@ -145,6 +146,22 @@ public void buildTopologicalNodeCgmesTerminalsMapping(CgmesTerminal t) {
}
}

public void buildConnectivityNodeCgmesTerminalsMapping(CgmesTerminal t, Conversion.Config config) {
// To avoid creating unnecessary internal connections, we need to compute the mapping of the connectivity nodes to the list
// of CgmesTerminals
// - this list should not contain switches
// - if CgmesImport.FictitiousSwitchesCreationMode.ALWAYS or CgmesImport.FictitiousSwitchesCreationMode.ALWAYS_EXCEPT_SWITCHES,
// this list should not contain the disconnected terminals
boolean onlyConnected = config.getCreateFictitiousSwitchesForDisconnectedTerminalsMode() != CgmesImport.FictitiousSwitchesCreationMode.NEVER;
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 && (!onlyConnected || t.connected())) {
connectivityNodeTerminalsMapping.computeIfAbsent(connectivityNode, k -> new ArrayList<>(1)).add(t);
}
}

public boolean areAssociated(String cgmesTerminalId, String topologicalNode) {
return topologicalNodesMapping.get(topologicalNode).contains(cgmesTerminalId);
}
Expand Down Expand Up @@ -173,11 +190,7 @@ public String findCgmesTerminalFromTopologicalNode(String topologicalNode) {
return topologicalNodesMapping.containsKey(topologicalNode) ? topologicalNodesMapping.get(topologicalNode).get(0) : null;
}

private final Map<String, Terminal> terminals;
private final Map<String, Boundary> 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<String, Integer> terminalNumbers;
private final Map<String, List<String>> topologicalNodesMapping;
private final Map<String, String> cgmesTerminalsMapping;
public List<CgmesTerminal> getConnectivityNodeTerminals(String id) {
return connectivityNodeTerminalsMapping.getOrDefault(id, List.of());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -381,20 +381,16 @@ private static Optional<EquivalentInjectionConversion> getEquivalentInjectionCon
}

int iidmNode() {
return iidmNode(1, true);
return iidmNode(1);
}

int iidmNode(int n) {
return iidmNode(n, true);
}

int iidmNode(int n, boolean equipmentIsConnected) {
if (!context.nodeBreaker()) {
throw new ConversionException("Can't request an iidmNode if conversion context is not node-breaker");
}
VoltageLevel vl = terminals[n - 1].voltageLevel;
CgmesTerminal t = terminals[n - 1].t;
return context.nodeMapping().iidmNodeForTerminal(t, type.equals("Switch"), vl, equipmentIsConnected);
return context.nodeMapping().iidmNodeForTerminal(t, type.equals("Switch"), vl);
}

String busId() {
Expand Down Expand Up @@ -616,24 +612,20 @@ public static void connect(Context context, BranchAdder<?, ?> adder,
}

public void connect(BranchAdder<?, ?> adder, boolean t1Connected, boolean t2Connected) {
connect(adder, t1Connected, t2Connected, true);
}

public void connect(BranchAdder<?, ?> adder, boolean t1Connected, boolean t2Connected, boolean branchIsClosed) {
if (context.nodeBreaker()) {
adder
.setVoltageLevel1(iidmVoltageLevelId(1))
.setVoltageLevel2(iidmVoltageLevelId(2))
.setNode1(iidmNode(1, branchIsClosed))
.setNode2(iidmNode(2, branchIsClosed));
.setNode1(iidmNode(1))
.setNode2(iidmNode(2));
} else {
String busId1 = busId(1);
String busId2 = busId(2);
adder
.setVoltageLevel1(iidmVoltageLevelId(1))
.setVoltageLevel2(iidmVoltageLevelId(2))
.setBus1(t1Connected && branchIsClosed ? busId1 : null)
.setBus2(t2Connected && branchIsClosed ? busId2 : null)
.setBus1(t1Connected ? busId1 : null)
.setBus2(t2Connected ? busId2 : null)
.setConnectableBus1(busId1)
.setConnectableBus2(busId2);
}
Expand Down
Loading
Loading