From edd3f0e8b0fbf14cc23d57491c661cc297de5183 Mon Sep 17 00:00:00 2001 From: Nicolas Rol Date: Fri, 8 Nov 2024 14:46:04 +0100 Subject: [PATCH] Allow use of bus-breaker voltage levels (#169) * add exception on Three Windings Transformers and Dangling Lines Signed-off-by: Nicolas Rol * remove dangling lines from branches (already managed as loads) + rewrite addElementToRetainedBreakersList to manage switches from BUS_BREAKER voltage levels + remove exception + add TODO on T3T Signed-off-by: Nicolas Rol * test exception on T3T Signed-off-by: Nicolas Rol * add test on MetrixNetwork Signed-off-by: Nicolas Rol * switch next to dangling lines should be managed as those next to loads Signed-off-by: Nicolas Rol * add more test for coverage Signed-off-by: Nicolas Rol * remove useless comment Signed-off-by: Nicolas Rol * revert fixes on dangling line to do it in another PR Signed-off-by: Nicolas Rol --------- Signed-off-by: Nicolas Rol Co-authored-by: jeandemanged --- .../metrix/integration/MetrixNetwork.java | 78 +++++++++++----- .../dataGenerator/MetrixInputData.java | 6 +- .../metrix/integration/MetrixInputTest.java | 13 +++ .../metrix/integration/MetrixNetworkTest.java | 91 ++++++++++++++++++- .../metrix/mapping/SimpleMappingData.groovy | 4 - 5 files changed, 159 insertions(+), 33 deletions(-) diff --git a/metrix-integration/src/main/java/com/powsybl/metrix/integration/MetrixNetwork.java b/metrix-integration/src/main/java/com/powsybl/metrix/integration/MetrixNetwork.java index 9cc86815..eb14adf3 100755 --- a/metrix-integration/src/main/java/com/powsybl/metrix/integration/MetrixNetwork.java +++ b/metrix-integration/src/main/java/com/powsybl/metrix/integration/MetrixNetwork.java @@ -68,6 +68,7 @@ public class MetrixNetwork { private final Map mappedSwitchMap = new HashMap<>(); protected MetrixNetwork(Network network) { + // TODO: switch T3T for 3xTWT in the network using a network modification this.network = Objects.requireNonNull(network); } @@ -612,35 +613,66 @@ private void createRetainedBreakersList(Set breakerList) { private void addElementToRetainedBreakersList(Switch sw) { String switchId = sw.getId(); - VoltageLevel.NodeBreakerView nodeBreakerView = sw.getVoltageLevel().getNodeBreakerView(); - Terminal terminal1 = nodeBreakerView.getTerminal1(switchId); - Terminal terminal2 = nodeBreakerView.getTerminal2(switchId); + VoltageLevel voltageLevel = sw.getVoltageLevel(); - if (terminal1 == null || terminal1.getConnectable().getType() == IdentifiableType.BUSBAR_SECTION) { - terminal1 = terminal2; + if (voltageLevel.getTopologyKind() == TopologyKind.NODE_BREAKER) { + // Get the terminals on both sides of the switch + VoltageLevel.NodeBreakerView nodeBreakerView = voltageLevel.getNodeBreakerView(); + Terminal terminal1 = nodeBreakerView.getTerminal1(switchId); + Terminal terminal2 = nodeBreakerView.getTerminal2(switchId); + + // Check on both sides of the switch to find a connectable that is not another switch nor a bus bar section + if (isSwitchNotConnectedToOtherSwitchOrBbs(terminal1)) { + addElementToRetainedBreakersList(sw, terminal1, true); + } else if (isSwitchNotConnectedToOtherSwitchOrBbs(terminal2)) { + addElementToRetainedBreakersList(sw, terminal2, true); + } else { + // Both sides have either a switch or a bus bar section : the switch is registered by itself + addElementToRetainedBreakersList(sw, switchId, true); + } + } else if (voltageLevel.getTopologyKind() == TopologyKind.BUS_BREAKER) { + // Get the terminals on both sides of the switch + VoltageLevel.BusBreakerView busBreakerView = voltageLevel.getBusBreakerView(); + List terminalsBus1 = busBreakerView.getBus1(switchId).getConnectedTerminalStream().toList(); + List terminalsBus2 = busBreakerView.getBus2(switchId).getConnectedTerminalStream().toList(); + + // Check on both sides of the switch to check if there is one and only one connectable + if (terminalsBus1.size() == 1) { + addElementToRetainedBreakersList(sw, terminalsBus1.get(0), false); + } else if (terminalsBus2.size() == 1) { + addElementToRetainedBreakersList(sw, terminalsBus2.get(0), false); + } else { + addElementToRetainedBreakersList(sw, switchId, false); + } } + } - if (terminal1 == null || terminal1.getConnectable().getType() == IdentifiableType.BUSBAR_SECTION) { - sw.setRetained(true); - mappedSwitchMap.put(switchId, switchId); - } else { - switch (terminal1.getConnectable().getType()) { - case LINE, TWO_WINDINGS_TRANSFORMER -> { - sw.setRetained(true); - mappedSwitchMap.put(switchId, terminal1.getConnectable().getId()); - } - case LOAD, GENERATOR -> { - sw.setRetained(true); - mappedSwitchMap.put(switchId, switchId); - } - case DANGLING_LINE, HVDC_CONVERTER_STATION, SHUNT_COMPENSATOR, STATIC_VAR_COMPENSATOR, THREE_WINDINGS_TRANSFORMER -> { - if (LOGGER.isWarnEnabled()) { - LOGGER.warn(String.format("Unsupported connectable type (%s) for switch '%s'", terminal1.getConnectable().getType(), switchId)); - } + private boolean isSwitchNotConnectedToOtherSwitchOrBbs(Terminal terminal) { + return terminal != null && terminal.getConnectable().getType() != IdentifiableType.BUSBAR_SECTION; + } + + private void addElementToRetainedBreakersList(Switch sw, Terminal terminal, boolean setRetained) { + String switchId = sw.getId(); + switch (terminal.getConnectable().getType()) { + // Since switches connected to lines and TWT are "replaced" by those connectables, no need to set them retained + case LINE, TWO_WINDINGS_TRANSFORMER -> addElementToRetainedBreakersList(sw, terminal.getConnectable().getId(), false); + case LOAD, GENERATOR -> addElementToRetainedBreakersList(sw, switchId, setRetained); + case DANGLING_LINE, HVDC_CONVERTER_STATION, SHUNT_COMPENSATOR, STATIC_VAR_COMPENSATOR, + THREE_WINDINGS_TRANSFORMER -> { + if (LOGGER.isWarnEnabled()) { + LOGGER.warn("Unsupported connectable type ({}) for switch '{}'", terminal.getConnectable().getType(), switchId); } - default -> throw new PowsyblException("Unexpected connectable type : " + terminal1.getConnectable().getType()); } + default -> + throw new PowsyblException("Unexpected connectable type : " + terminal.getConnectable().getType()); + } + } + + private void addElementToRetainedBreakersList(Switch sw, String id, boolean setRetained) { + if (setRetained) { + sw.setRetained(true); } + mappedSwitchMap.put(sw.getId(), id); } private void createOpenedBranchesList(Set openedBranches) { diff --git a/metrix-integration/src/main/java/com/powsybl/metrix/integration/dataGenerator/MetrixInputData.java b/metrix-integration/src/main/java/com/powsybl/metrix/integration/dataGenerator/MetrixInputData.java index dc993306..33dc4d57 100755 --- a/metrix-integration/src/main/java/com/powsybl/metrix/integration/dataGenerator/MetrixInputData.java +++ b/metrix-integration/src/main/java/com/powsybl/metrix/integration/dataGenerator/MetrixInputData.java @@ -184,9 +184,11 @@ private void createMetrixInputs() { trnbgrou = metrixNetwork.getGeneratorList().size(); - cqnbquad = metrixNetwork.getLineList().size() + metrixNetwork.getTwoWindingsTransformerList().size() + 3 * metrixNetwork.getThreeWindingsTransformerList().size() + metrixNetwork.getDanglingLineList().size() + metrixNetwork.getSwitchList().size(); + // Quadripoles are lines, transformers and switches + cqnbquad = metrixNetwork.getLineList().size() + metrixNetwork.getTwoWindingsTransformerList().size() + 3 * metrixNetwork.getThreeWindingsTransformerList().size() + metrixNetwork.getSwitchList().size(); dtnbtrde = metrixNetwork.getPhaseTapChangerList().size(); + // Loads are loads and dangling lines ecnbcons = metrixNetwork.getLoadList().size() + metrixNetwork.getDanglingLineList().size(); dcnblies = metrixNetwork.getHvdcLineList().size(); @@ -391,7 +393,7 @@ private void writeBranches(boolean constantLossFactor, MetrixDie die) { // Three Windings Transformers metrixNetwork.getThreeWindingsTransformerList().forEach(twt -> { - throw new UnsupportedOperationException("TODO"); + throw new PowsyblException("Three Windings Transformers are not yet supported in metrix"); }); // Switches diff --git a/metrix-integration/src/test/java/com/powsybl/metrix/integration/MetrixInputTest.java b/metrix-integration/src/test/java/com/powsybl/metrix/integration/MetrixInputTest.java index 09ec99b0..9f4e833d 100755 --- a/metrix-integration/src/test/java/com/powsybl/metrix/integration/MetrixInputTest.java +++ b/metrix-integration/src/test/java/com/powsybl/metrix/integration/MetrixInputTest.java @@ -14,8 +14,10 @@ import com.google.common.collect.Range; import com.google.common.jimfs.Configuration; import com.google.common.jimfs.Jimfs; +import com.powsybl.commons.PowsyblException; import com.powsybl.contingency.*; import com.powsybl.iidm.network.*; +import com.powsybl.iidm.network.test.ThreeWindingsTransformerNetworkFactory; import com.powsybl.iidm.serde.NetworkSerDe; import com.powsybl.metrix.integration.dataGenerator.MetrixInputData; import com.powsybl.metrix.integration.metrix.MetrixChunkParam; @@ -156,6 +158,17 @@ void metrixDefaultInputTest() throws IOException { new ByteArrayInputStream(actual.getBytes(StandardCharsets.UTF_8)))); } + @Test + void metrixInputDataWithT3TTest() throws IOException { + Network n = ThreeWindingsTransformerNetworkFactory.create(); + MetrixInputData metrixInputData = new MetrixInputData(MetrixNetwork.create(n), null, new MetrixParameters()); + try (StringWriter writer = new StringWriter()) { + assertThrows(PowsyblException.class, + () -> metrixInputData.writeJson(writer), + "Three Windings Transformers are not yet supported in metrix"); + } + } + @Test void metrixInputTest() throws IOException { Network n = NetworkSerDe.read(getClass().getResourceAsStream("/simpleNetwork.xml")); diff --git a/metrix-integration/src/test/java/com/powsybl/metrix/integration/MetrixNetworkTest.java b/metrix-integration/src/test/java/com/powsybl/metrix/integration/MetrixNetworkTest.java index 4320e23f..07894e44 100644 --- a/metrix-integration/src/test/java/com/powsybl/metrix/integration/MetrixNetworkTest.java +++ b/metrix-integration/src/test/java/com/powsybl/metrix/integration/MetrixNetworkTest.java @@ -7,6 +7,8 @@ import com.powsybl.contingency.Contingency; import com.powsybl.iidm.network.*; import com.powsybl.iidm.network.test.FourSubstationsNodeBreakerFactory; +import com.powsybl.iidm.serde.ExportOptions; +import com.powsybl.iidm.serde.NetworkSerDe; import com.powsybl.metrix.integration.contingency.Probability; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -21,6 +23,8 @@ import java.util.Set; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; /** * @author Nicolas Rol {@literal } @@ -44,8 +48,11 @@ void testNetworkElementsLists() { Network network = createNetwork(); // Set some switches as retained - Set mappedSwitches = Set.of("S1VL2_GH2_BREAKER", "S3VL1_LINES3S4_BREAKER", "S1VL1_LD1_BREAKER"); - List switchList = mappedSwitches.stream() + Set mappedSwitches = Set.of("S1VL2_GH2_BREAKER", "S3VL1_LINES3S4_BREAKER", "S1VL1_LD1_BREAKER", "S1VL3_DL_BREAKER", "S1VL2_BBS1_BBS3"); + + // Expected switch list in MetrixNetwork: switches next to branches (lines, two windings transformers) are not present + List switchList = Set.of("S1VL2_GH2_BREAKER", "S1VL1_LD1_BREAKER", "S1VL2_BBS1_BBS3") + .stream() .map(network::getSwitch).toList(); // Contingencies @@ -77,13 +84,79 @@ void testNetworkElementsLists() { assertThat(metrixNetwork.getHvdcLineList()).containsExactlyInAnyOrderElementsOf(network.getHvdcLines()); assertThat(metrixNetwork.getBusList()).containsExactlyInAnyOrderElementsOf(network.getBusBreakerView().getBuses()); assertThat(metrixNetwork.getContingencyList()).containsExactlyInAnyOrderElementsOf(List.of(a, b)); + + assertTrue(metrixNetwork.isMapped(network.getIdentifiable("S1VL2_GH2_BREAKER"))); + assertTrue(metrixNetwork.isMapped(network.getIdentifiable("DL"))); + assertTrue(metrixNetwork.isMapped(network.getIdentifiable("GH2"))); + assertTrue(metrixNetwork.isMapped(network.getIdentifiable("HVDC1"))); + assertTrue(metrixNetwork.isMapped(network.getIdentifiable("LINE_S2S3"))); + assertTrue(metrixNetwork.isMapped(network.getIdentifiable("LD5"))); + assertFalse(metrixNetwork.isMapped(network.getIdentifiable("S2VL1_BBS"))); + } + + @Test + void testNetworkBusBreakerElementsLists() { + // Mapped switches + Set mappedSwitches = Set.of("S1VL2_GH2_BREAKER", "S3VL1_LINES3S4_BREAKER", "S1VL1_LD1_BREAKER", + "S1VL3_DL_BREAKER", "S1VL2_BBS1_BBS3", "S1VL3_3WT_BREAKER"); + + // Network + Network network = createBusBreakerNetwork(mappedSwitches); + + // Expected switch list in MetrixNetwork: switches next to branches (lines, two windings transformers) are not present + List switchList = mappedSwitches.stream().map(network::getSwitch).toList(); + + // Contingencies + Contingency a = new Contingency("a", Collections.singletonList(new BranchContingency("LINE_S2S3"))); + Contingency b = new Contingency("b", Arrays.asList( + new BranchContingency("LINE_S2S3"), + new BranchContingency("LINE_S3S4"))); + + // Create a contingency provider + ContingenciesProvider contingenciesProvider = networkLocal -> { + a.addExtension(Probability.class, new Probability(0.002d, null)); + b.addExtension(Probability.class, new Probability(null, "variable_ts1")); + return Arrays.asList(a, b); + }; + + // Initialize the MetrixNetwork + MetrixNetwork metrixNetwork = MetrixNetwork.create(network, contingenciesProvider, mappedSwitches, new MetrixParameters(), (Path) null); + + // Check the lists + assertThat(metrixNetwork.getCountryList()).containsExactlyInAnyOrderElementsOf(Collections.singletonList("Undefined")); + assertThat(metrixNetwork.getLoadList()).containsExactlyInAnyOrderElementsOf(network.getLoads()); + assertThat(metrixNetwork.getGeneratorList()).containsExactlyInAnyOrderElementsOf(network.getGenerators()); + assertThat(metrixNetwork.getGeneratorTypeList()).containsExactlyInAnyOrderElementsOf(List.of("HYDRO", "THERMAL")); + assertThat(metrixNetwork.getLineList()).containsExactlyInAnyOrderElementsOf(network.getLines()); + assertThat(metrixNetwork.getTwoWindingsTransformerList()).containsExactlyInAnyOrderElementsOf(network.getTwoWindingsTransformers()); + assertThat(metrixNetwork.getThreeWindingsTransformerList()).containsExactlyInAnyOrderElementsOf(network.getThreeWindingsTransformers()); + assertThat(metrixNetwork.getDanglingLineList()).containsExactlyInAnyOrderElementsOf(network.getDanglingLines()); + assertThat(metrixNetwork.getSwitchList()).containsExactlyInAnyOrderElementsOf(switchList); + assertThat(metrixNetwork.getHvdcLineList()).containsExactlyInAnyOrderElementsOf(network.getHvdcLines()); + assertThat(metrixNetwork.getBusList()).containsExactlyInAnyOrderElementsOf(network.getBusBreakerView().getBuses()); + assertThat(metrixNetwork.getContingencyList()).containsExactlyInAnyOrderElementsOf(List.of(a, b)); + } + + private Network createBusBreakerNetwork(Set mappedSwitches) { + // Initial network + Network network = createNetwork(); + + // Set some switches as retained + List retainedSwitches = mappedSwitches.stream().map(network::getSwitch).toList(); + network.getSwitchStream() + .forEach(sw -> sw.setRetained(retainedSwitches.contains(sw))); + + // Export the network as BusBreaker + Path exportedFile = fileSystem.getPath("./network.xiidm"); + NetworkSerDe.write(network, new ExportOptions().setTopologyLevel(TopologyLevel.BUS_BREAKER), exportedFile); + return NetworkSerDe.read(exportedFile); } private Network createNetwork() { // Initial network Network network = FourSubstationsNodeBreakerFactory.create(); - // We add a substation, a ThreeWindingsTransformer and a DanglingLine + // We add a voltage level, a ThreeWindingsTransformer and a DanglingLine VoltageLevel s1vl3 = network.getSubstation("S1").newVoltageLevel() .setId("S1VL3") .setNominalV(225.0) @@ -137,7 +210,7 @@ private Network createNetwork() { // Dangling line createSwitch(s1vl3, "S1VL3_BBS_DL_DISCONNECTOR", SwitchKind.DISCONNECTOR, 0, 3); - createSwitch(s1vl3, "S1VL3_DL_BREAKER", SwitchKind.BREAKER, 3, 4); + createSwitch(s1vl3, "S1VL3_DL_BREAKER", SwitchKind.BREAKER, 4, 3); s1vl3.newDanglingLine() .setId("DL") .setR(10.0) @@ -149,6 +222,16 @@ private Network createNetwork() { .setNode(4) .add(); + // We add another bus bar section and link it to the others with a breaker + network.getVoltageLevel("S1VL2").getNodeBreakerView().newBusbarSection() + .setId("S1VL2_BBS3") + .setName("S1VL2_BBS3") + .setNode(90) + .add(); + createSwitch(network.getVoltageLevel("S1VL2"), "S1VL2_BBS1_DISCONNECTOR", SwitchKind.DISCONNECTOR, 0, 91); + createSwitch(network.getVoltageLevel("S1VL2"), "S1VL2_BBS3_DISCONNECTOR", SwitchKind.DISCONNECTOR, 92, 90); + createSwitch(network.getVoltageLevel("S1VL2"), "S1VL2_BBS1_BBS3", SwitchKind.BREAKER, 91, 92); + return network; } diff --git a/metrix-mapping/src/main/groovy/com/powsybl/metrix/mapping/SimpleMappingData.groovy b/metrix-mapping/src/main/groovy/com/powsybl/metrix/mapping/SimpleMappingData.groovy index 9308e8ba..81c8b70c 100644 --- a/metrix-mapping/src/main/groovy/com/powsybl/metrix/mapping/SimpleMappingData.groovy +++ b/metrix-mapping/src/main/groovy/com/powsybl/metrix/mapping/SimpleMappingData.groovy @@ -42,10 +42,6 @@ class SimpleMappingData extends FilteredData { // for each filtered equipment, compute the distribution key and add it to the config if (!filteredEquipments.isEmpty()) { - if (((Switch) filteredEquipments[0]).voltageLevel.topologyKind == TopologyKind.BUS_BREAKER) { - throw new TimeSeriesMappingException("Bus breaker topology not supported for switch mapping") - } - filteredEquipments.forEach({ Identifiable identifiable -> configLoader.addEquipmentMapping(breakerType, spec.timeSeriesName, identifiable.id, NumberDistributionKey.ONE, EquipmentVariable.OPEN) })