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 22 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));
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 @@ -47,47 +51,121 @@ 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
// 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;
var fictSwCreationMode = context.config().getCreateFictitiousSwitchesForDisconnectedTerminalsMode();
boolean bbsForEveryConnectivityNode = context.config().createBusbarSectionForEveryConnectivityNode();
if (!bbsForEveryConnectivityNode && fictSwCreationMode == CgmesImport.FictitiousSwitchesCreationMode.NEVER) {
if (isSwitchEnd) {
// for switches the iidm node is the one from the connectivity node
return cgmes2iidm.get(t.connectivityNode());
} else if (!cgmes2iidm.containsKey(t.id())) {
// Create internal connections but only if too many terminals on connectivity node
createInternalConnectionsIfNeeded(t.connectivityNode(), vl);
}
} else {
boolean connected = t.connected() && equipmentIsConnected;
if (!connected && createFictitiousSwitch(fictSwCreationMode, isSwitchEnd)) {
createFictitiousSwitch(t, vl);
} else {
createInternalConnection(t, vl);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't have to systematically create a node for the terminal and an internal connection between the terminal's node and the connectivitynode's node. You could do it only if needed, that is if 2 or more connected non-switch equipments share that terminal's connectivity node.

}
}

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

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();
}

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
}

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 +188,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,16 @@ public void buildTopologicalNodeCgmesTerminalsMapping(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) {
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 +184,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());
}
}
Loading