Skip to content
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

[CGMES] Less internal connections created at import #3210

Draft
wants to merge 9 commits into
base: main
Choose a base branch
from
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 @@ -14,6 +14,7 @@
import com.powsybl.iidm.network.VoltageLevel;

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

/**
Expand Down Expand Up @@ -47,49 +48,111 @@ 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));
boolean connected = t.connected() && equipmentIsConnected;

// 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;
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) {
if (context.config().createBusbarSectionForEveryConnectivityNode()) {
return createInternalConnection(t, vl);
}
List<CgmesTerminal> 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 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);
boolean terminalBbs = isBusbarSectionTerminal(t);
boolean otherTerminalBbs = isBusbarSectionTerminal(otherTerminal);
boolean firstTerminalAtConnectivityNode = !cgmes2iidm.containsKey(otherTerminal.id());
if (firstTerminalAtConnectivityNode && terminalBbs && otherTerminalBbs
|| firstTerminalAtConnectivityNode && !otherTerminalBbs
|| terminalBbs && !otherTerminalBbs) {
return oneIidmNodeForBothTerminalAndConnectivityNode(t, vl);
} else {
return createInternalConnection(t, vl);
}
} else if (connectivityNodeTerminals.size() > 2) {
// We need the connectivity node as connecting point between terminals
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.
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
return createInternalConnection(t, vl);
}
} else {
// only one terminal: connectivity node and terminal share the same iidm node
return oneIidmNodeForBothTerminalAndConnectivityNode(t, vl);
}
}

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

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

// 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 boolean isFirstBbsAtConnectivityNode(CgmesTerminal t, List<CgmesTerminal> 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;
Expand Down
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());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.*;
Expand Down Expand Up @@ -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(
Expand All @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
}