diff --git a/src/main/java/org/gridsuite/network/map/dto/definition/extension/BusbarSectionFinderTraverser.java b/src/main/java/org/gridsuite/network/map/dto/definition/extension/BusbarSectionFinderTraverser.java index 2faa6c7d..d42bd84d 100644 --- a/src/main/java/org/gridsuite/network/map/dto/definition/extension/BusbarSectionFinderTraverser.java +++ b/src/main/java/org/gridsuite/network/map/dto/definition/extension/BusbarSectionFinderTraverser.java @@ -6,42 +6,97 @@ */ package org.gridsuite.network.map.dto.definition.extension; -import com.powsybl.iidm.network.IdentifiableType; +import com.powsybl.iidm.network.BusbarSection; import com.powsybl.iidm.network.Switch; import com.powsybl.iidm.network.Terminal; +import com.powsybl.iidm.network.VoltageLevel; +import com.powsybl.math.graph.TraversalType; import com.powsybl.math.graph.TraverseResult; +import java.util.*; + /** - * @author Slimane Amar + * @author Ghazwa Rehili */ -public class BusbarSectionFinderTraverser implements Terminal.TopologyTraverser { +// TODO : code to move to powsybl-core or powsybl-network-store +public final class BusbarSectionFinderTraverser { + + private BusbarSectionFinderTraverser() { + throw new UnsupportedOperationException(); + } - private final boolean onlyConnectedBbs; + public record SwitchInfo(String id, boolean isOpen) { } - private String firstTraversedBbsId; + public record BusbarSectionResult(String busbarSectionId, int depth, SwitchInfo lastSwitch, boolean allClosedSwitch) { } - public BusbarSectionFinderTraverser(boolean onlyConnectedBbs) { - this.onlyConnectedBbs = onlyConnectedBbs; + public static String findBusbarSectionId(Terminal terminal) { + BusbarSectionResult result = getBusbarSectionResult(terminal); + return result != null ? result.busbarSectionId() : terminal.getVoltageLevel().getNodeBreakerView().getBusbarSections().iterator().next().getId(); } - @Override - public TraverseResult traverse(Terminal terminal, boolean connected) { - if (terminal.getConnectable().getType() == IdentifiableType.BUSBAR_SECTION) { - firstTraversedBbsId = terminal.getConnectable().getId(); - return TraverseResult.TERMINATE_TRAVERSER; + public static BusbarSectionResult getBusbarSectionResult(Terminal terminal) { + int startNode = terminal.getNodeBreakerView().getNode(); + List allResults = searchAllBusbars(terminal.getVoltageLevel(), startNode); + if (allResults.isEmpty()) { + return null; } - return TraverseResult.CONTINUE; + return selectBestBusbar(allResults); } - @Override - public TraverseResult traverse(Switch aSwitch) { - if (onlyConnectedBbs && aSwitch.isOpen()) { - return TraverseResult.TERMINATE_PATH; + private static BusbarSectionResult selectBestBusbar(List results) { + List withAllClosedSwitch = results.stream().filter(r -> r.allClosedSwitch).toList(); + if (!withAllClosedSwitch.isEmpty()) { + return withAllClosedSwitch.stream().min(Comparator.comparingInt(BusbarSectionResult::depth) + .thenComparing(BusbarSectionResult::busbarSectionId)).orElse(null); } - return TraverseResult.CONTINUE; + List withClosedSwitch = results.stream().filter(r -> r.lastSwitch() != null && !r.lastSwitch().isOpen()).toList(); + if (!withClosedSwitch.isEmpty()) { + return withClosedSwitch.stream().min(Comparator.comparingInt(BusbarSectionResult::depth) + .thenComparing(BusbarSectionResult::busbarSectionId)).orElse(null); + } + List withOpenSwitch = results.stream().filter(r -> r.lastSwitch() != null && r.lastSwitch().isOpen()).toList(); + if (!withOpenSwitch.isEmpty()) { + return withOpenSwitch.stream().min(Comparator.comparingInt(BusbarSectionResult::depth) + .thenComparing(BusbarSectionResult::busbarSectionId)).orElse(null); + } + return results.getFirst(); } - public String getFirstTraversedBbsId() { - return firstTraversedBbsId; + private static List searchAllBusbars(VoltageLevel voltageLevel, int startNode) { + List results = new ArrayList<>(); + record NodeState(int depth, boolean allClosed) { } + Map visitedNodes = new HashMap<>(); + visitedNodes.put(startNode, new NodeState(0, true)); + voltageLevel.getNodeBreakerView().getTerminal(startNode).traverse(new Terminal.TopologyTraverser() { + SwitchInfo lastSwitch = null; + @Override + public TraverseResult traverse(Terminal terminal, boolean connected) { + if (terminal.getVoltageLevel() != voltageLevel) { + return TraverseResult.TERMINATE_PATH; + } + NodeState currentNodeState = visitedNodes.get(terminal.getNodeBreakerView().getNode()); + if (terminal.getConnectable() instanceof BusbarSection busbarSection) { + if (currentNodeState != null) { + results.add(new BusbarSectionResult(busbarSection.getId(), currentNodeState.depth, lastSwitch, currentNodeState.allClosed)); + } + return TraverseResult.TERMINATE_PATH; + } + return TraverseResult.CONTINUE; + } + + @Override + public TraverseResult traverse(Switch aSwitch) { + int node1 = voltageLevel.getNodeBreakerView().getNode1(aSwitch.getId()); + int node2 = voltageLevel.getNodeBreakerView().getNode2(aSwitch.getId()); + int sourceNode = visitedNodes.containsKey(node1) ? node1 : node2; + int targetNode = visitedNodes.containsKey(node1) ? node2 : node1; + NodeState sourceState = visitedNodes.get(sourceNode); + NodeState newState = new NodeState(sourceState.depth + 1, sourceState.allClosed && !aSwitch.isOpen()); + visitedNodes.put(targetNode, newState); + lastSwitch = new SwitchInfo(aSwitch.getId(), aSwitch.isOpen()); + return TraverseResult.CONTINUE; + } + }, TraversalType.BREADTH_FIRST); + return results; } } diff --git a/src/main/java/org/gridsuite/network/map/dto/utils/ElementUtils.java b/src/main/java/org/gridsuite/network/map/dto/utils/ElementUtils.java index 31232bf7..76cb9795 100644 --- a/src/main/java/org/gridsuite/network/map/dto/utils/ElementUtils.java +++ b/src/main/java/org/gridsuite/network/map/dto/utils/ElementUtils.java @@ -8,7 +8,6 @@ import com.powsybl.iidm.network.*; import com.powsybl.iidm.network.extensions.ConnectablePosition; -import com.powsybl.math.graph.TraversalType; import org.gridsuite.network.map.dto.common.ReactiveCapabilityCurveMapData; import org.gridsuite.network.map.dto.common.TapChangerData; import org.gridsuite.network.map.dto.common.TapChangerStepData; @@ -92,9 +91,8 @@ public static String getBusOrBusbarSection(Terminal terminal) { return terminal.getBusBreakerView().getConnectableBus().getId(); } } else { - final BusbarSectionFinderTraverser connectedBusbarSectionFinder = new BusbarSectionFinderTraverser(terminal.isConnected()); - terminal.traverse(connectedBusbarSectionFinder, TraversalType.BREADTH_FIRST); - return connectedBusbarSectionFinder.getFirstTraversedBbsId(); + // NODE_BREAKER: explore all paths and choose the busbar with the closed disconnector + return BusbarSectionFinderTraverser.findBusbarSectionId(terminal); } } diff --git a/src/test/java/org/gridsuite/network/map/NetworkMapControllerTest.java b/src/test/java/org/gridsuite/network/map/NetworkMapControllerTest.java index 734fdb7a..09597cb9 100644 --- a/src/test/java/org/gridsuite/network/map/NetworkMapControllerTest.java +++ b/src/test/java/org/gridsuite/network/map/NetworkMapControllerTest.java @@ -54,7 +54,7 @@ */ @AutoConfigureMockMvc @SpringBootTest -class NetworkMapControllerTest { +public class NetworkMapControllerTest { private static final UUID NETWORK_UUID = UUID.fromString("7928181c-7977-4592-ba19-88027e4254e4"); private static final UUID NETWORK_2_UUID = UUID.fromString("9828181c-7977-4592-ba19-8976e4254e"); @@ -1268,7 +1268,7 @@ static void tearDown() { Mockito.validateMockitoUsage(); } - private static void createSwitch(VoltageLevel vl, String id, SwitchKind kind, boolean open, int node1, int node2) { + public static void createSwitch(VoltageLevel vl, String id, SwitchKind kind, boolean open, int node1, int node2) { vl.getNodeBreakerView().newSwitch() .setId(id) .setName(id) diff --git a/src/test/java/org/gridsuite/network/map/mapper/BusbarSectionFinderTraverserTest.java b/src/test/java/org/gridsuite/network/map/mapper/BusbarSectionFinderTraverserTest.java new file mode 100644 index 00000000..1dde6692 --- /dev/null +++ b/src/test/java/org/gridsuite/network/map/mapper/BusbarSectionFinderTraverserTest.java @@ -0,0 +1,248 @@ +package org.gridsuite.network.map.mapper; + +import com.powsybl.iidm.network.*; +import com.powsybl.iidm.network.extensions.BusbarSectionPositionAdder; +import org.gridsuite.network.map.dto.definition.extension.BusbarSectionFinderTraverser; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +import static org.gridsuite.network.map.NetworkMapControllerTest.createSwitch; +import static org.junit.jupiter.api.Assertions.*; + +/** + * @author Ghazwa Rehili + */ + +@SpringBootTest +class BusbarSectionFinderTraverserTest { + + private Network network; + + @BeforeEach + void setUp() { + network = NetworkFactory.findDefault().createNetwork("sim1", "test"); + Substation s1 = network.newSubstation() + .setId("S1") + .setCountry(Country.FR) + .setTso("RTE") + .setGeographicalTags("A") + .add(); + VoltageLevel vl1 = s1.newVoltageLevel() + .setId("VL1") + .setNominalV(24.0) + .setTopologyKind(TopologyKind.NODE_BREAKER) + .add(); + vl1.getNodeBreakerView().newBusbarSection() + .setId("BBS1") + .setName("BBS1") + .setNode(1) + .add(); + + VoltageLevel vl2 = s1.newVoltageLevel() + .setId("VLGEN2") + .setNominalV(24.0) + .setTopologyKind(TopologyKind.NODE_BREAKER) + .add(); + vl2.getNodeBreakerView().newBusbarSection() + .setId("BBS1_1") + .setName("BBS1_1") + .setNode(1) + .add(); + vl2.getNodeBreakerView().newBusbarSection() + .setId("BBS2_1") + .setName("BBS2_1") + .setNode(2) + .add(); + vl2.getNodeBreakerView().newBusbarSection() + .setId("BBS1_2") + .setName("BBS1_2") + .setNode(3) + .add(); + vl2.getNodeBreakerView().newBusbarSection() + .setId("BBS2_2") + .setName("BBS2_2") + .setNode(4) + .add(); + + vl2.getNodeBreakerView() + .getBusbarSection("BBS1_1") + .newExtension(BusbarSectionPositionAdder.class) + .withBusbarIndex(1) + .withSectionIndex(1) + .add(); + vl2.getNodeBreakerView() + .getBusbarSection("BBS1_2") + .newExtension(BusbarSectionPositionAdder.class) + .withBusbarIndex(1) + .withSectionIndex(2) + .add(); + vl2.getNodeBreakerView() + .getBusbarSection("BBS2_1") + .newExtension(BusbarSectionPositionAdder.class) + .withBusbarIndex(2) + .withSectionIndex(1) + .add(); + vl2.getNodeBreakerView() + .getBusbarSection("BBS2_2") + .newExtension(BusbarSectionPositionAdder.class) + .withBusbarIndex(2) + .withSectionIndex(2) + .add(); + + //Fork topology + createSwitch(vl2, "DISC_BBS1.1_BBS1.2", SwitchKind.DISCONNECTOR, false, 1, 3); + createSwitch(vl2, "DISC_BBS2.1_BBS2.2", SwitchKind.DISCONNECTOR, true, 2, 4); + createSwitch(vl2, "DISC_BBS1_2", SwitchKind.DISCONNECTOR, true, 3, 5); + createSwitch(vl2, "DISC_BBS2_2", SwitchKind.DISCONNECTOR, true, 4, 5); + createSwitch(vl2, "BRK_FORK", SwitchKind.BREAKER, true, 5, 6); + createSwitch(vl2, "DISC_LINE_1_2", SwitchKind.DISCONNECTOR, false, 6, 7); + createSwitch(vl2, "DISC_LINE_2_2", SwitchKind.DISCONNECTOR, false, 6, 8); + createSwitch(vl2, "BRK_LINE_1_2", SwitchKind.BREAKER, false, 7, 10); + createSwitch(vl2, "BRK_LINE_2_2", SwitchKind.BREAKER, false, 8, 9); + network.newLine() + .setId("LINE_1_2") + .setName("LINE_1_2") + .setVoltageLevel1("VLGEN2") + .setNode1(10) + .setVoltageLevel2("VL1") + .setNode2(11) + .setR(2.0) + .setX(25.0) + .setG1(0.0) + .setB1(300E-6 / 2) + .setG2(0.0) + .setB2(300E-6 / 2) + .add(); + + network.newLine() + .setId("LINE_2_2") + .setName("LINE_2_2") + .setVoltageLevel1("VLGEN2") + .setNode1(9) + .setVoltageLevel2("VL1") + .setNode2(21) + .setR(2.0) + .setX(25.0) + .setG1(0.0) + .setB1(300E-6 / 2) + .setG2(0.0) + .setB2(300E-6 / 2) + .add(); + + // BYPASS topology + createSwitch(vl2, "DISC_BBS1_1", SwitchKind.DISCONNECTOR, true, 1, 15); + createSwitch(vl2, "DISC_BBS2_1", SwitchKind.DISCONNECTOR, true, 2, 18); + createSwitch(vl2, "DISC_BYPASS", SwitchKind.DISCONNECTOR, true, 15, 18); + createSwitch(vl2, "DISC_LINE_1_1", SwitchKind.DISCONNECTOR, true, 15, 11); + createSwitch(vl2, "DISC_LINE_2_1", SwitchKind.DISCONNECTOR, true, 18, 21); + createSwitch(vl2, "BRK_LINE_1_1", SwitchKind.BREAKER, true, 11, 12); + createSwitch(vl2, "BRK_LINE_2_1", SwitchKind.BREAKER, true, 21, 23); + network.newLine() + .setId("LINE_1_1") + .setName("LINE_1_1") + .setVoltageLevel1("VLGEN2") + .setNode1(12) + .setVoltageLevel2("VL1") + .setNode2(3) + .setR(2.0) + .setX(25.0) + .setG1(0.0) + .setB1(300E-6 / 2) + .setG2(0.0) + .setB2(300E-6 / 2) + .add(); + + network.newLine() + .setId("LINE_2_1") + .setName("LINE_2_1") + .setVoltageLevel1("VLGEN2") + .setNode1(23) + .setVoltageLevel2("VL1") + .setNode2(41) + .setR(2.0) + .setX(25.0) + .setG1(0.0) + .setB1(300E-6 / 2) + .setG2(0.0) + .setB2(300E-6 / 2) + .add(); + + network.getVariantManager().setWorkingVariant(VariantManagerConstants.INITIAL_VARIANT_ID); + } + + @Test + void testWithOpenSwitch() { + Line line12 = network.getLine("LINE_1_2"); + Line line22 = network.getLine("LINE_2_2"); + BusbarSectionFinderTraverser.BusbarSectionResult result12 = BusbarSectionFinderTraverser.getBusbarSectionResult(line12.getTerminal1()); + BusbarSectionFinderTraverser.BusbarSectionResult result22 = BusbarSectionFinderTraverser.getBusbarSectionResult(line22.getTerminal1()); + assertNotNull(result12); + assertEquals("BBS1_2", result12.busbarSectionId()); + assertNotNull(result22); + assertEquals("BBS1_2", result22.busbarSectionId()); + assertEquals(4, result12.depth()); + assertEquals(4, result22.depth()); + assertEquals("DISC_BBS1_2", result12.lastSwitch().id()); + assertEquals("DISC_BBS1_2", result22.lastSwitch().id()); + assertTrue(result12.lastSwitch().isOpen()); + assertTrue(result22.lastSwitch().isOpen()); + assertFalse(result12.allClosedSwitch()); + assertFalse(result22.allClosedSwitch()); + + } + + @Test + void testWithClosedLastSwitch() { + network.getSwitch("DISC_BBS1_1").setOpen(false); + network.getSwitch("DISC_BBS2_2").setOpen(false); + Line line11 = network.getLine("LINE_1_1"); + Line line21 = network.getLine("LINE_2_1"); + Line line12 = network.getLine("LINE_1_2"); + BusbarSectionFinderTraverser.BusbarSectionResult result11 = BusbarSectionFinderTraverser.getBusbarSectionResult(line11.getTerminal1()); + BusbarSectionFinderTraverser.BusbarSectionResult result21 = BusbarSectionFinderTraverser.getBusbarSectionResult(line21.getTerminal1()); + BusbarSectionFinderTraverser.BusbarSectionResult result12 = BusbarSectionFinderTraverser.getBusbarSectionResult(line12.getTerminal1()); + assertNotNull(result11); + assertEquals("BBS1_1", result11.busbarSectionId()); + assertEquals(3, result11.depth()); + assertEquals("DISC_BBS1_1", result11.lastSwitch().id()); + assertFalse(result11.lastSwitch().isOpen()); + assertFalse(result11.allClosedSwitch()); + assertNotNull(result21); + assertEquals("BBS1_1", result21.busbarSectionId()); + assertEquals(4, result21.depth()); + assertEquals("DISC_BBS1_1", result21.lastSwitch().id()); + assertFalse(result21.lastSwitch().isOpen()); + assertFalse(result21.allClosedSwitch()); + assertNotNull(result12); + assertEquals("BBS2_2", result12.busbarSectionId()); + assertEquals(4, result12.depth()); + assertEquals("DISC_BBS2_2", result12.lastSwitch().id()); + assertFalse(result12.lastSwitch().isOpen()); + assertFalse(result12.allClosedSwitch()); + } + + @Test + void testWithAllClosedSwitch() { + network.getSwitch("BRK_LINE_1_1").setOpen(false); + network.getSwitch("DISC_LINE_1_1").setOpen(false); + network.getSwitch("DISC_BBS1_1").setOpen(false); + network.getSwitch("DISC_BBS2_1").setOpen(false); + network.getSwitch("BRK_LINE_2_1").setOpen(false); + network.getSwitch("DISC_LINE_2_1").setOpen(false); + Line line11 = network.getLine("LINE_1_1"); + Line line21 = network.getLine("LINE_2_1"); + BusbarSectionFinderTraverser.BusbarSectionResult result11 = BusbarSectionFinderTraverser.getBusbarSectionResult(line11.getTerminal1()); + BusbarSectionFinderTraverser.BusbarSectionResult result21 = BusbarSectionFinderTraverser.getBusbarSectionResult(line21.getTerminal1()); + assertNotNull(result11); + assertEquals("BBS1_1", result11.busbarSectionId()); + assertEquals(3, result11.depth()); + assertTrue(result11.allClosedSwitch()); + assertEquals("DISC_BBS1_1", result11.lastSwitch().id()); + assertNotNull(result21); + assertEquals("BBS2_1", result21.busbarSectionId()); + assertEquals(3, result21.depth()); + assertEquals("DISC_BBS2_1", result21.lastSwitch().id()); + assertTrue(result21.allClosedSwitch()); + } +} diff --git a/src/test/resources/substations-form-data.json b/src/test/resources/substations-form-data.json index 8aae64fe..345e9493 100644 --- a/src/test/resources/substations-form-data.json +++ b/src/test/resources/substations-form-data.json @@ -129,7 +129,7 @@ ], "SHUNT_NON_LINEAR": [ { - "busbarSectionId": null, + "busbarSectionId": "NGEN4", "connectablePositionInfos": { "connectionDirection": null }, diff --git a/src/test/resources/voltage-level-form-data.json b/src/test/resources/voltage-level-form-data.json index 6f4837ff..6d10ba78 100644 --- a/src/test/resources/voltage-level-form-data.json +++ b/src/test/resources/voltage-level-form-data.json @@ -34,7 +34,7 @@ ], "SHUNT_NON_LINEAR": [ { - "busbarSectionId": null, + "busbarSectionId": "NGEN4", "connectablePositionInfos": { "connectionDirection": null }, diff --git a/src/test/resources/voltage-levels-form-data.json b/src/test/resources/voltage-levels-form-data.json index c65df911..1b3f7e25 100644 --- a/src/test/resources/voltage-levels-form-data.json +++ b/src/test/resources/voltage-levels-form-data.json @@ -95,7 +95,7 @@ ], "SHUNT_NON_LINEAR": [ { - "busbarSectionId": null, + "busbarSectionId": "NGEN4", "connectablePositionInfos": { "connectionDirection": null },