From c112b6a2e55582cbad30ed0150b3dfa30be023b7 Mon Sep 17 00:00:00 2001 From: Rehili Ghazwa Date: Mon, 15 Sep 2025 13:16:23 +0200 Subject: [PATCH 01/15] enhance BusbarSectionFinderTraverser to handle all cases --- .../BusbarSectionFinderTraverser.java | 68 +++++++++++++++---- .../network/map/dto/utils/ElementUtils.java | 7 +- 2 files changed, 59 insertions(+), 16 deletions(-) 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..0391051a 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 @@ -8,40 +8,82 @@ import com.powsybl.iidm.network.IdentifiableType; import com.powsybl.iidm.network.Switch; +import com.powsybl.iidm.network.SwitchKind; import com.powsybl.iidm.network.Terminal; import com.powsybl.math.graph.TraverseResult; +import lombok.Getter; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; /** * @author Slimane Amar */ public class BusbarSectionFinderTraverser implements Terminal.TopologyTraverser { - private final boolean onlyConnectedBbs; - - private String firstTraversedBbsId; - - public BusbarSectionFinderTraverser(boolean onlyConnectedBbs) { - this.onlyConnectedBbs = onlyConnectedBbs; - } + private final List busbarCandidates = new ArrayList<>(); + private final Set visitedTerminals = new HashSet<>(); + private static final int MAX_VISITED = 50; @Override public TraverseResult traverse(Terminal terminal, boolean connected) { - if (terminal.getConnectable().getType() == IdentifiableType.BUSBAR_SECTION) { - firstTraversedBbsId = terminal.getConnectable().getId(); + String terminalId = terminal.getConnectable().getId(); + if (visitedTerminals.contains(terminalId)) { + return TraverseResult.TERMINATE_PATH; + } + visitedTerminals.add(terminalId); + if (visitedTerminals.size() > MAX_VISITED) { return TraverseResult.TERMINATE_TRAVERSER; } + + // If a busbar section is found, add it as a candidate + if (terminal.getConnectable().getType() == IdentifiableType.BUSBAR_SECTION) { + busbarCandidates.add(new BusbarCandidate(terminalId, connected)); + // CONTINUE to explore other paths to other busbars + return TraverseResult.CONTINUE; + } return TraverseResult.CONTINUE; } @Override public TraverseResult traverse(Switch aSwitch) { - if (onlyConnectedBbs && aSwitch.isOpen()) { - return TraverseResult.TERMINATE_PATH; + if (visitedTerminals.size() > MAX_VISITED) { + return TraverseResult.TERMINATE_TRAVERSER; + } + + // KEY: Open disconnectors end this path but not the overall traversal + // They block access to this busbar but not to the others + if (aSwitch.isOpen() && aSwitch.getKind() == SwitchKind.DISCONNECTOR) { + return TraverseResult.TERMINATE_PATH; // Ends this path, not the whole traversal } return TraverseResult.CONTINUE; } - public String getFirstTraversedBbsId() { - return firstTraversedBbsId; + public String getBusbarWithClosedDisconnector() { + // Search for a connected busbar (disconnector closed) + for (BusbarCandidate candidate : busbarCandidates) { + if (candidate.isConnected()) { + return candidate.getId(); + } + } + + // If none is connected, return the first one found (fallback) + if (!busbarCandidates.isEmpty()) { + return busbarCandidates.getFirst().getId(); + } + return null; + } + + @Getter + private static class BusbarCandidate { + private final String id; + private final boolean connected; + + public BusbarCandidate(String id, boolean connected) { + this.id = id; + this.connected = connected; + } } } 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 acb483b2..8e788687 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 @@ -47,9 +47,10 @@ 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 + BusbarSectionFinderTraverser finder = new BusbarSectionFinderTraverser(); + terminal.traverse(finder, TraversalType.BREADTH_FIRST); + return finder.getBusbarWithClosedDisconnector(); } } From bd1c1c57275b5604929ddc099da798319135a425 Mon Sep 17 00:00:00 2001 From: Rehili Ghazwa Date: Mon, 22 Sep 2025 17:28:07 +0200 Subject: [PATCH 02/15] add TU for fork topo --- .../BusbarSectionFinderTraverser.java | 45 ++-- .../network/map/dto/utils/ElementUtils.java | 5 +- .../network/map/NetworkMapControllerTest.java | 198 ++++++++++++++++++ 3 files changed, 228 insertions(+), 20 deletions(-) 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 0391051a..1f5c7902 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 @@ -11,7 +11,6 @@ import com.powsybl.iidm.network.SwitchKind; import com.powsybl.iidm.network.Terminal; import com.powsybl.math.graph.TraverseResult; -import lombok.Getter; import java.util.ArrayList; import java.util.HashSet; @@ -26,6 +25,11 @@ public class BusbarSectionFinderTraverser implements Terminal.TopologyTraverser private final List busbarCandidates = new ArrayList<>(); private final Set visitedTerminals = new HashSet<>(); private static final int MAX_VISITED = 50; + private final boolean allowTraversalThroughOpenDisconnectors; + + public BusbarSectionFinderTraverser(boolean allowTraversalThroughOpenDisconnectors) { + this.allowTraversalThroughOpenDisconnectors = allowTraversalThroughOpenDisconnectors; + } @Override public TraverseResult traverse(Terminal terminal, boolean connected) { @@ -56,7 +60,10 @@ public TraverseResult traverse(Switch aSwitch) { // KEY: Open disconnectors end this path but not the overall traversal // They block access to this busbar but not to the others if (aSwitch.isOpen() && aSwitch.getKind() == SwitchKind.DISCONNECTOR) { - return TraverseResult.TERMINATE_PATH; // Ends this path, not the whole traversal + // Use the parameter to control behavior + return allowTraversalThroughOpenDisconnectors ? + TraverseResult.CONTINUE : + TraverseResult.TERMINATE_PATH; } return TraverseResult.CONTINUE; } @@ -64,26 +71,32 @@ public TraverseResult traverse(Switch aSwitch) { public String getBusbarWithClosedDisconnector() { // Search for a connected busbar (disconnector closed) for (BusbarCandidate candidate : busbarCandidates) { - if (candidate.isConnected()) { - return candidate.getId(); + if (candidate.connected()) { + return candidate.id(); } } - // If none is connected, return the first one found (fallback) - if (!busbarCandidates.isEmpty()) { - return busbarCandidates.getFirst().getId(); - } - return null; + // Return first busbar found or null if none + return !busbarCandidates.isEmpty() ? busbarCandidates.getFirst().id() : null; } - @Getter - private static class BusbarCandidate { - private final String id; - private final boolean connected; + // Utility method with automatic fallback + public static String findBusbar(Terminal startTerminal) { + // Attempt 1: normal behavior (blocks on open disconnectors) + var traverser1 = new BusbarSectionFinderTraverser(false); + startTerminal.traverse(traverser1); + String result = traverser1.getBusbarWithClosedDisconnector(); - public BusbarCandidate(String id, boolean connected) { - this.id = id; - this.connected = connected; + if (result != null) { + return result; } + + // Attempt 2: if null, retry allowing traversal through open disconnectors + var traverser2 = new BusbarSectionFinderTraverser(true); + startTerminal.traverse(traverser2); + return traverser2.getBusbarWithClosedDisconnector(); + } + + private record BusbarCandidate(String id, boolean connected) { } } 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 8e788687..5b4b6fed 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 @@ -7,7 +7,6 @@ package org.gridsuite.network.map.dto.utils; import com.powsybl.iidm.network.*; -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; @@ -48,9 +47,7 @@ public static String getBusOrBusbarSection(Terminal terminal) { } } else { // NODE_BREAKER: explore all paths and choose the busbar with the closed disconnector - BusbarSectionFinderTraverser finder = new BusbarSectionFinderTraverser(); - terminal.traverse(finder, TraversalType.BREADTH_FIRST); - return finder.getBusbarWithClosedDisconnector(); + return BusbarSectionFinderTraverser.findBusbar(terminal); } } diff --git a/src/test/java/org/gridsuite/network/map/NetworkMapControllerTest.java b/src/test/java/org/gridsuite/network/map/NetworkMapControllerTest.java index 9e9c2117..87960e03 100644 --- a/src/test/java/org/gridsuite/network/map/NetworkMapControllerTest.java +++ b/src/test/java/org/gridsuite/network/map/NetworkMapControllerTest.java @@ -1188,6 +1188,204 @@ void setUp() { .withDirection(ConnectablePosition.Direction.TOP).add() .add(); + /** VLGEN7 - Fork topology + * + * BUS1 ═══════X════════ BUS2 ═══════════════/════ BUS3 + * (node0) (node5) (node7) + * | | | + * Disconnector1 Disconnector2 | + * | | | + * | | Disconnector3 + * [open = true] [open = false] [open = false] + * | | | + * |____________________| ═══════════/════════ BUS4 + * | | + * | | + * fork point Disconnector4 + * (node8) [open = false] + * | | + * ┌───┴───┐ LINE9 + * | | (→VLGEN9) + * LINE7 LINE8 + * (→VLGEN4) (→VLGEN8) + */ + + VoltageLevel vlgen7 = network.newVoltageLevel() + .setId("VLGEN7") + .setName("Fork Distribution Point") + .setNominalV(24.0) + .setHighVoltageLimit(30.0) + .setLowVoltageLimit(20.0) + .setTopologyKind(TopologyKind.NODE_BREAKER) + .add(); + + // Create busbarSections + vlgen7.getNodeBreakerView().newBusbarSection() + .setId("BUS1_NGEN7") + .setName("Primary Busbar VLGEN7") + .setNode(0) + .add(); + + vlgen7.getNodeBreakerView().newBusbarSection() + .setId("BUS2_NGEN7") + .setName("Secondary Busbar VLGEN7") + .setNode(5) + .add(); + + vlgen7.getNodeBreakerView().newBusbarSection() + .setId("BUS3_NGEN7") + .setName("Third Busbar VLGEN7") + .setNode(9) + .add(); + vlgen7.getNodeBreakerView().newBusbarSection() + .setId("BUS4_NGEN7") + .setName("Fourth Busbar VLGEN7") + .setNode(13) + .add(); + + createSwitch(vlgen7, "SECT_BUS1", SwitchKind.DISCONNECTOR, true, 0, 6); // OPEN + createSwitch(vlgen7, "SECT_BUS2", SwitchKind.DISCONNECTOR, false, 5, 7); // CLOSED + createSwitch(vlgen7, "SECT_BUS3", SwitchKind.DISCONNECTOR, false, 9, 10); // CLOSED + createSwitch(vlgen7, "SECT_BUS4", SwitchKind.DISCONNECTOR, false, 13, 14); // CLOSED + + createSwitch(vlgen7, "FORK_SW1", SwitchKind.DISCONNECTOR, false, 6, 8); // BUS1 to fork (CLOSED via node 6) + createSwitch(vlgen7, "FORK_SW2", SwitchKind.DISCONNECTOR, false, 7, 8); // BUS2 to fork (CLOSED via node 7) + + // LINE7 connection from fork + createSwitch(vlgen7, "DISC_LINE7", SwitchKind.DISCONNECTOR, false, 8, 1); + createSwitch(vlgen7, "BRKR_LINE7", SwitchKind.BREAKER, false, 1, 2); + + // LINE8 connection from fork + createSwitch(vlgen7, "DISC_LINE8", SwitchKind.DISCONNECTOR, false, 8, 3); + createSwitch(vlgen7, "BRKR_LINE8", SwitchKind.BREAKER, false, 3, 4); + + // LINE9 connection from BUS4 (Fixed connection point) + createSwitch(vlgen7, "DISC_LINE9", SwitchKind.DISCONNECTOR, false, 14, 12); // Connect from node 14 + createSwitch(vlgen7, "BRKR_LINE9", SwitchKind.BREAKER, false, 12, 11); + + // Assuming VLGEN4 exists, create its switches + createSwitch(vlgen4, "DISC_VLGEN4", SwitchKind.DISCONNECTOR, false, 0, 10); + createSwitch(vlgen4, "BRKR_VLGEN4", SwitchKind.BREAKER, false, 10, 15); + + // LINE7 - Fork Branch 1 + network.newLine() + .setId("LINE7_FORK") + .setName("Fork Branch 1 - Primary Line") + .setVoltageLevel1("VLGEN4") + .setNode1(15) // Fixed node reference + .setVoltageLevel2("VLGEN7") + .setNode2(2) + .setR(3.0) + .setX(33.0) + .setG1(0.0) + .setB1(386E-6 / 2) + .setG2(0.0) + .setB2(386E-6 / 2) + .add(); + + Line line7 = network.getLine("LINE7_FORK"); + line7.newExtension(ConnectablePositionAdder.class) + .newFeeder1() + .withName("LINE7_VLGEN4_Side") + .withOrder(5) + .withDirection(ConnectablePosition.Direction.BOTTOM) + .add() + .newFeeder2() + .withName("LINE7_VLGEN7_Fork_Side") + .withOrder(1) + .withDirection(ConnectablePosition.Direction.TOP) + .add() + .add(); + + // VLGEN8 - Fork destination 2 + VoltageLevel vlgen8 = network.newVoltageLevel() + .setId("VLGEN8") + .setName("Fork Destination Point 2") + .setNominalV(24.0) + .setHighVoltageLimit(30.0) + .setLowVoltageLimit(20.0) + .setTopologyKind(TopologyKind.NODE_BREAKER) + .add(); + createSwitch(vlgen8, "DISC_VLGEN8", SwitchKind.DISCONNECTOR, false, 0, 1); + createSwitch(vlgen8, "BRKR_VLGEN8", SwitchKind.BREAKER, false, 1, 2); + + // LINE8 - Fork Branch 2 + network.newLine() + .setId("LINE8_FORK") + .setName("Fork Branch 2 - Secondary Line") + .setVoltageLevel1("VLGEN7") + .setNode1(4) + .setVoltageLevel2("VLGEN8") + .setNode2(2) + .setR(2.5) + .setX(28.0) + .setG1(0.0) + .setB1(320E-6 / 2) + .setG2(0.0) + .setB2(320E-6 / 2) + .add(); + Line line8 = network.getLine("LINE8_FORK"); + line8.newExtension(ConnectablePositionAdder.class) + .newFeeder1() + .withName("LINE8_VLGEN7_Fork_Side") + .withOrder(2) + .withDirection(ConnectablePosition.Direction.BOTTOM) + .add() + .newFeeder2() + .withName("LINE8_VLGEN8_Side") + .withOrder(1) + .withDirection(ConnectablePosition.Direction.TOP) + .add() + .add(); + +// VLGEN9 - Independent line destination + VoltageLevel vlgen9 = network.newVoltageLevel() + .setId("VLGEN9") + .setName("Independent Line Destination") + .setNominalV(24.0) + .setHighVoltageLimit(30.0) + .setLowVoltageLimit(20.0) + .setTopologyKind(TopologyKind.NODE_BREAKER) + .add(); + + vlgen9.getNodeBreakerView().newBusbarSection() + .setId("BUS_NGEN9") + .setName("Main Busbar VLGEN9") + .setNode(0) + .add(); + + createSwitch(vlgen9, "DISC_VLGEN9", SwitchKind.DISCONNECTOR, false, 0, 1); + createSwitch(vlgen9, "BRKR_VLGEN9", SwitchKind.BREAKER, false, 1, 2); + + // LINE9 - Independent line from BUS4 + network.newLine() + .setId("LINE9_INDEPENDENT") + .setName("Independent Line from BUS4") + .setVoltageLevel1("VLGEN7") + .setNode1(11) + .setVoltageLevel2("VLGEN9") + .setNode2(2) + .setR(2.0) + .setX(25.0) + .setG1(0.0) + .setB1(300E-6 / 2) + .setG2(0.0) + .setB2(300E-6 / 2) + .add(); + Line line9 = network.getLine("LINE9_INDEPENDENT"); + line9.newExtension(ConnectablePositionAdder.class) + .newFeeder1() + .withName("LINE9_VLGEN7_Side") + .withOrder(3) + .withDirection(ConnectablePosition.Direction.BOTTOM) + .add() + .newFeeder2() + .withName("LINE9_VLGEN9_Side") + .withOrder(1) + .withDirection(ConnectablePosition.Direction.BOTTOM) + .add() + .add(); + network.getVariantManager().setWorkingVariant(VariantManagerConstants.INITIAL_VARIANT_ID); // Add new variant 2 From dab0cf83fb2ce639031275995f298a98a0d3f2b8 Mon Sep 17 00:00:00 2001 From: Rehili Ghazwa Date: Tue, 23 Sep 2025 12:52:43 +0200 Subject: [PATCH 03/15] enhance coverage code --- .../network/map/NetworkMapControllerTest.java | 78 ++++---- src/test/resources/all-data-in-variant.json | 187 ++++++++++++++++++ .../resources/all-data-without-optionals.json | 177 +++++++++++++++++ src/test/resources/all-data.json | 177 +++++++++++++++++ .../resources/bus-bar-section-form-data.json | 20 ++ .../resources/bus-bar-section-list-data.json | 20 ++ ...bus-bar-section-operating-status-data.json | 20 ++ src/test/resources/lines-form-data.json | 82 ++++++++ src/test/resources/lines-list-data.json | 12 ++ src/test/resources/lines-map-data.json | 29 +++ .../lines-operating-status-data.json | 29 +++ .../resources/switches-data-in-variant.json | 8 + src/test/resources/switches-data.json | 8 + .../resources/voltage-levels-form-data.json | 42 ++++ .../resources/voltage-levels-list-data.json | 12 ++ .../resources/voltage-levels-map-data.json | 15 ++ 16 files changed, 877 insertions(+), 39 deletions(-) diff --git a/src/test/java/org/gridsuite/network/map/NetworkMapControllerTest.java b/src/test/java/org/gridsuite/network/map/NetworkMapControllerTest.java index 87960e03..76974add 100644 --- a/src/test/java/org/gridsuite/network/map/NetworkMapControllerTest.java +++ b/src/test/java/org/gridsuite/network/map/NetworkMapControllerTest.java @@ -1157,37 +1157,6 @@ void setUp() { .withDirection(ConnectablePosition.Direction.TOP).add() .add(); - // Add new variant - network.getVariantManager().cloneVariant(VariantManagerConstants.INITIAL_VARIANT_ID, VARIANT_ID); - network.getVariantManager().setWorkingVariant(VARIANT_ID); - - // Disconnect shunt compensator "SHUNT_VLNB" in variant VARIANT_ID to test SJB retrieval even if equipment disconnected - network.getSwitch("VL4_SHUNT_BREAKER").setOpen(true); - - // Create a shunt compensator only in variant VARIANT_ID - ShuntCompensator shunt3 = vlgen3.newShuntCompensator() - .setId("SHUNT3") - .setName("SHUNT3") - .newLinearModel() - .setMaximumSectionCount(3) - .setBPerSection(1) - .setGPerSection(2) - .add() - .setSectionCount(2) - .setTargetV(225) - .setVoltageRegulatorOn(true) - .setTargetDeadband(10) - .setConnectableBus("NGEN3") - .setBus("NGEN3") - .add(); - shunt3.getTerminal().setQ(90); - shunt3.newExtension(ConnectablePositionAdder.class) - .newFeeder() - .withName("feederName") - .withOrder(0) - .withDirection(ConnectablePosition.Direction.TOP).add() - .add(); - /** VLGEN7 - Fork topology * * BUS1 ═══════X════════ BUS2 ═══════════════/════ BUS3 @@ -1404,6 +1373,37 @@ void setUp() { .setTopologyKind(TopologyKind.BUS_BREAKER) .add(); + // Add new variant + network.getVariantManager().cloneVariant(VariantManagerConstants.INITIAL_VARIANT_ID, VARIANT_ID); + network.getVariantManager().setWorkingVariant(VARIANT_ID); + + // Disconnect shunt compensator "SHUNT_VLNB" in variant VARIANT_ID to test SJB retrieval even if equipment disconnected + network.getSwitch("VL4_SHUNT_BREAKER").setOpen(true); + + // Create a shunt compensator only in variant VARIANT_ID + ShuntCompensator shunt3 = vlgen3.newShuntCompensator() + .setId("SHUNT3") + .setName("SHUNT3") + .newLinearModel() + .setMaximumSectionCount(3) + .setBPerSection(1) + .setGPerSection(2) + .add() + .setSectionCount(2) + .setTargetV(225) + .setVoltageRegulatorOn(true) + .setTargetDeadband(10) + .setConnectableBus("NGEN3") + .setBus("NGEN3") + .add(); + shunt3.getTerminal().setQ(90); + shunt3.newExtension(ConnectablePositionAdder.class) + .newFeeder() + .withName("feederName") + .withOrder(0) + .withDirection(ConnectablePosition.Direction.TOP).add() + .add(); + network.getVariantManager().setWorkingVariant(VariantManagerConstants.INITIAL_VARIANT_ID); Network network2 = NetworkTest1Factory.create(NETWORK_2_UUID.toString()); @@ -1920,10 +1920,10 @@ void shouldReturnLinesOperatingStatusData() throws Exception { @Test void shouldReturnLinesIds() throws Exception { - succeedingTestForElementsIds(NETWORK_UUID, null, List.of("NHV1_NHV2_1", "NHV1_NHV2_2", "LINE3", "LINE4").toString(), ElementType.LINE, null, null); - succeedingTestForElementsIds(NETWORK_UUID, null, List.of("NHV1_NHV2_1", "NHV1_NHV2_2", "LINE3", "LINE4").toString(), ElementType.LINE, null, List.of(24.0, 380.0)); - succeedingTestForElementsIds(NETWORK_UUID, VARIANT_ID, List.of("NHV1_NHV2_1", "NHV1_NHV2_2", "LINE3", "LINE4").toString(), ElementType.LINE, null, null); - succeedingTestForElementsIds(NETWORK_UUID, VARIANT_ID, List.of("NHV1_NHV2_1", "NHV1_NHV2_2", "LINE3", "LINE4").toString(), ElementType.LINE, null, List.of(24.0, 380.0)); + succeedingTestForElementsIds(NETWORK_UUID, null, List.of("NHV1_NHV2_1", "NHV1_NHV2_2", "LINE3", "LINE4", "LINE7_FORK", "LINE8_FORK", "LINE9_INDEPENDENT").toString(), ElementType.LINE, null, null); + succeedingTestForElementsIds(NETWORK_UUID, null, List.of("NHV1_NHV2_1", "NHV1_NHV2_2", "LINE3", "LINE4", "LINE7_FORK", "LINE8_FORK", "LINE9_INDEPENDENT").toString(), ElementType.LINE, null, List.of(24.0, 380.0)); + succeedingTestForElementsIds(NETWORK_UUID, VARIANT_ID, List.of("NHV1_NHV2_1", "NHV1_NHV2_2", "LINE3", "LINE4", "LINE7_FORK", "LINE8_FORK", "LINE9_INDEPENDENT").toString(), ElementType.LINE, null, null); + succeedingTestForElementsIds(NETWORK_UUID, VARIANT_ID, List.of("NHV1_NHV2_1", "NHV1_NHV2_2", "LINE3", "LINE4", "LINE7_FORK", "LINE8_FORK", "LINE9_INDEPENDENT").toString(), ElementType.LINE, null, List.of(24.0, 380.0)); succeedingTestForElementsIds(NETWORK_UUID, VARIANT_ID, List.of("NHV1_NHV2_1", "NHV1_NHV2_2", "LINE3").toString(), ElementType.LINE, List.of("P1"), null); succeedingTestForElementsIds(NETWORK_UUID, VARIANT_ID, List.of("NHV1_NHV2_1", "NHV1_NHV2_2", "LINE3").toString(), ElementType.LINE, List.of("P1"), List.of(24.0, 380.0)); succeedingTestForElementsIds(NETWORK_UUID, VARIANT_ID, List.of().toString(), ElementType.LINE, List.of("P1"), List.of(225.0)); @@ -2442,10 +2442,10 @@ void shouldReturnVotlageLevelsMapData() throws Exception { @Test void shouldReturnVoltageLevelsIds() throws Exception { - succeedingTestForElementsIds(NETWORK_UUID, null, List.of("VL", "VLGEN", "VLHV1", "VLHV2", "VLLOAD", "VLNEW2", "VLGEN3", "VLGEN4", "VLGEN5", "VLGEN6").toString(), ElementType.VOLTAGE_LEVEL, null, null); - succeedingTestForElementsIds(NETWORK_UUID, null, List.of("VL", "VLGEN", "VLHV1", "VLHV2", "VLLOAD", "VLNEW2", "VLGEN3", "VLGEN4", "VLGEN5", "VLGEN6").toString(), ElementType.VOLTAGE_LEVEL, null, List.of(24.0, 150.0, 225.0, 380.0)); - succeedingTestForElementsIds(NETWORK_UUID, VARIANT_ID, List.of("VL", "VLGEN", "VLHV1", "VLHV2", "VLLOAD", "VLNEW2", "VLGEN3", "VLGEN4", "VLGEN5", "VLGEN6").toString(), ElementType.VOLTAGE_LEVEL, null, null); - succeedingTestForElementsIds(NETWORK_UUID, VARIANT_ID, List.of("VL", "VLGEN", "VLHV1", "VLHV2", "VLLOAD", "VLNEW2", "VLGEN3", "VLGEN4", "VLGEN5", "VLGEN6").toString(), ElementType.VOLTAGE_LEVEL, null, List.of(24.0, 150.0, 225.0, 380.0)); + succeedingTestForElementsIds(NETWORK_UUID, null, List.of("VL", "VLGEN", "VLHV1", "VLHV2", "VLLOAD", "VLNEW2", "VLGEN3", "VLGEN4", "VLGEN5", "VLGEN6", "VLGEN7", "VLGEN8", "VLGEN9").toString(), ElementType.VOLTAGE_LEVEL, null, null); + succeedingTestForElementsIds(NETWORK_UUID, null, List.of("VL", "VLGEN", "VLHV1", "VLHV2", "VLLOAD", "VLNEW2", "VLGEN3", "VLGEN4", "VLGEN5", "VLGEN6", "VLGEN7", "VLGEN8", "VLGEN9").toString(), ElementType.VOLTAGE_LEVEL, null, List.of(24.0, 150.0, 225.0, 380.0)); + succeedingTestForElementsIds(NETWORK_UUID, VARIANT_ID, List.of("VL", "VLGEN", "VLHV1", "VLHV2", "VLLOAD", "VLNEW2", "VLGEN3", "VLGEN4", "VLGEN5", "VLGEN6", "VLGEN7", "VLGEN8", "VLGEN9").toString(), ElementType.VOLTAGE_LEVEL, null, null); + succeedingTestForElementsIds(NETWORK_UUID, VARIANT_ID, List.of("VL", "VLGEN", "VLHV1", "VLHV2", "VLLOAD", "VLNEW2", "VLGEN3", "VLGEN4", "VLGEN5", "VLGEN6", "VLGEN7", "VLGEN8", "VLGEN9").toString(), ElementType.VOLTAGE_LEVEL, null, List.of(24.0, 150.0, 225.0, 380.0)); succeedingTestForElementsIds(NETWORK_UUID, VARIANT_ID, List.of("VLGEN", "VLHV1", "VLHV2", "VLLOAD", "VLNEW2", "VLGEN3", "VLGEN4", "VLGEN5", "VLGEN6").toString(), ElementType.VOLTAGE_LEVEL, List.of("P1", "P2", "P3", "P4", "P5", "P6"), null); succeedingTestForElementsIds(NETWORK_UUID, VARIANT_ID, List.of("VLGEN", "VLHV1", "VLHV2", "VLLOAD", "VLNEW2", "VLGEN3", "VLGEN4", "VLGEN5", "VLGEN6").toString(), ElementType.VOLTAGE_LEVEL, List.of("P1", "P2", "P3", "P4", "P5", "P6"), List.of(24.0, 150.0, 225.0, 380.0)); } diff --git a/src/test/resources/all-data-in-variant.json b/src/test/resources/all-data-in-variant.json index e732bc20..7833f69d 100644 --- a/src/test/resources/all-data-in-variant.json +++ b/src/test/resources/all-data-in-variant.json @@ -219,6 +219,27 @@ "ipMin": 0.0, "ipMax": 100.0 } + }, + { + "id": "VLGEN7", + "name": "Fork Distribution Point", + "nominalV": 24.0, + "lowVoltageLimit": 20.0, + "highVoltageLimit": 30.0 + }, + { + "id": "VLGEN8", + "name": "Fork Destination Point 2", + "nominalV": 24.0, + "lowVoltageLimit": 20.0, + "highVoltageLimit": 30.0 + }, + { + "id": "VLGEN9", + "name": "Independent Line Destination", + "nominalV": 24.0, + "lowVoltageLimit": 20.0, + "highVoltageLimit": 30.0 } ], "lines": [ @@ -588,6 +609,64 @@ "b1": 1.93E-4, "g2": 0.0, "b2": 1.93E-4 + }, + { + "id": "LINE7_FORK", + "name": "Fork Branch 1 - Primary Line", + "type": "LINE", + "voltageLevelId1": "VLGEN4", + "voltageLevelId2": "VLGEN7", + "voltageLevelName2": "Fork Distribution Point", + "nominalVoltage1": 24.0, + "nominalVoltage2": 24.0, + "substationId1": "P4", + "country1": "FR", + "terminal1Connected": true, + "terminal2Connected": true, + "r": 3.0, + "x": 33.0, + "g1": 0.0, + "b1": 1.93E-4, + "g2": 0.0, + "b2": 1.93E-4 + }, + { + "id": "LINE8_FORK", + "name": "Fork Branch 2 - Secondary Line", + "type": "LINE", + "voltageLevelId1": "VLGEN7", + "voltageLevelId2": "VLGEN8", + "voltageLevelName1": "Fork Distribution Point", + "voltageLevelName2": "Fork Destination Point 2", + "nominalVoltage1": 24.0, + "nominalVoltage2": 24.0, + "terminal1Connected": true, + "terminal2Connected": false, + "r": 2.5, + "x": 28.0, + "g1": 0.0, + "b1": 1.6E-4, + "g2": 0.0, + "b2": 1.6E-4 + }, + { + "id": "LINE9_INDEPENDENT", + "name": "Independent Line from BUS4", + "type": "LINE", + "voltageLevelId1": "VLGEN7", + "voltageLevelId2": "VLGEN9", + "voltageLevelName1": "Fork Distribution Point", + "voltageLevelName2": "Independent Line Destination", + "nominalVoltage1": 24.0, + "nominalVoltage2": 24.0, + "terminal1Connected": true, + "terminal2Connected": true, + "r": 2.0, + "x": 25.0, + "g1": 0.0, + "b1": 1.5E-4, + "g2": 0.0, + "b2": 1.5E-4 } ], "hvdcLines": [ @@ -2480,6 +2559,43 @@ "voltageLevelId": "VLGEN6", "nominalVoltage": 24.0, "country": "FR" + }, + { + "id": "VLGEN4_0", + "v": "NaN", + "angle": "NaN", + "synchronousComponentNum": 1, + "connectedComponentNum": 1, + "voltageLevelId": "VLGEN4", + "nominalVoltage": 24.0, + "country": "FR" + }, + { + "id": "VLGEN7_11", + "v": "NaN", + "angle": "NaN", + "synchronousComponentNum": 2, + "connectedComponentNum": 2, + "voltageLevelId": "VLGEN7", + "nominalVoltage": 24.0 + }, + { + "id": "VLGEN7_1", + "v": "NaN", + "angle": "NaN", + "synchronousComponentNum": 1, + "connectedComponentNum": 1, + "voltageLevelId": "VLGEN7", + "nominalVoltage": 24.0 + }, + { + "id": "VLGEN9_0", + "v": "NaN", + "angle": "NaN", + "synchronousComponentNum": 2, + "connectedComponentNum": 2, + "voltageLevelId": "VLGEN9", + "nominalVoltage": 24.0 } ], "busbarSections": [ @@ -2510,6 +2626,31 @@ "id": "NGEN5_2_2", "name": "NGEN5_2_2", "voltageLevelId": "VLGEN5" + }, + { + "id": "BUS1_NGEN7", + "name": "Primary Busbar VLGEN7", + "voltageLevelId": "VLGEN7" + }, + { + "id": "BUS2_NGEN7", + "name": "Secondary Busbar VLGEN7", + "voltageLevelId": "VLGEN7" + }, + { + "id": "BUS3_NGEN7", + "name": "Third Busbar VLGEN7", + "voltageLevelId": "VLGEN7" + }, + { + "id": "BUS4_NGEN7", + "name": "Fourth Busbar VLGEN7", + "voltageLevelId": "VLGEN7" + }, + { + "id": "BUS_NGEN9", + "name": "Main Busbar VLGEN9", + "voltageLevelId": "VLGEN9" } ], "branches": [ @@ -3096,6 +3237,52 @@ "value": 53.0, "validity": false } + }, + { + "id": "LINE7_FORK", + "name": "Fork Branch 1 - Primary Line", + "type": "LINE", + "voltageLevelId1": "VLGEN4", + "voltageLevelId2": "VLGEN7", + "voltageLevelName2": "Fork Distribution Point", + "nominalVoltage1": 24.0, + "nominalVoltage2": 24.0, + "substationId1": "P4", + "country1": "FR", + "terminal1Connected": true, + "terminal2Connected": true, + "r": 3.0, + "x": 33.0 + }, + { + "id": "LINE8_FORK", + "name": "Fork Branch 2 - Secondary Line", + "type": "LINE", + "voltageLevelId1": "VLGEN7", + "voltageLevelId2": "VLGEN8", + "voltageLevelName1": "Fork Distribution Point", + "voltageLevelName2": "Fork Destination Point 2", + "nominalVoltage1": 24.0, + "nominalVoltage2": 24.0, + "terminal1Connected": true, + "terminal2Connected": false, + "r": 2.5, + "x": 28.0 + }, + { + "id": "LINE9_INDEPENDENT", + "name": "Independent Line from BUS4", + "type": "LINE", + "voltageLevelId1": "VLGEN7", + "voltageLevelId2": "VLGEN9", + "voltageLevelName1": "Fork Distribution Point", + "voltageLevelName2": "Independent Line Destination", + "nominalVoltage1": 24.0, + "nominalVoltage2": 24.0, + "terminal1Connected": true, + "terminal2Connected": true, + "r": 2.0, + "x": 25.0 } ] } \ No newline at end of file diff --git a/src/test/resources/all-data-without-optionals.json b/src/test/resources/all-data-without-optionals.json index fba77e8b..6dd26784 100644 --- a/src/test/resources/all-data-without-optionals.json +++ b/src/test/resources/all-data-without-optionals.json @@ -219,6 +219,27 @@ "ipMin": 0.0, "ipMax": 100.0 } + }, + { + "id": "VLGEN7", + "name": "Fork Distribution Point", + "nominalV": 24.0, + "lowVoltageLimit": 20.0, + "highVoltageLimit": 30.0 + }, + { + "id": "VLGEN8", + "name": "Fork Destination Point 2", + "nominalV": 24.0, + "lowVoltageLimit": 20.0, + "highVoltageLimit": 30.0 + }, + { + "id": "VLGEN9", + "name": "Independent Line Destination", + "nominalV": 24.0, + "lowVoltageLimit": 20.0, + "highVoltageLimit": 30.0 } ], "lines": [ @@ -448,6 +469,64 @@ "b1": 1.93E-4, "g2": 0.0, "b2": 1.93E-4 + }, + { + "id": "LINE7_FORK", + "name": "Fork Branch 1 - Primary Line", + "type": "LINE", + "voltageLevelId1": "VLGEN4", + "voltageLevelId2": "VLGEN7", + "voltageLevelName2": "Fork Distribution Point", + "nominalVoltage1": 24.0, + "nominalVoltage2": 24.0, + "substationId1": "P4", + "country1": "FR", + "terminal1Connected": true, + "terminal2Connected": true, + "r": 3.0, + "x": 33.0, + "g1": 0.0, + "b1": 1.93E-4, + "g2": 0.0, + "b2": 1.93E-4 + }, + { + "id": "LINE8_FORK", + "name": "Fork Branch 2 - Secondary Line", + "type": "LINE", + "voltageLevelId1": "VLGEN7", + "voltageLevelId2": "VLGEN8", + "voltageLevelName1": "Fork Distribution Point", + "voltageLevelName2": "Fork Destination Point 2", + "nominalVoltage1": 24.0, + "nominalVoltage2": 24.0, + "terminal1Connected": true, + "terminal2Connected": false, + "r": 2.5, + "x": 28.0, + "g1": 0.0, + "b1": 1.6E-4, + "g2": 0.0, + "b2": 1.6E-4 + }, + { + "id": "LINE9_INDEPENDENT", + "name": "Independent Line from BUS4", + "type": "LINE", + "voltageLevelId1": "VLGEN7", + "voltageLevelId2": "VLGEN9", + "voltageLevelName1": "Fork Distribution Point", + "voltageLevelName2": "Independent Line Destination", + "nominalVoltage1": 24.0, + "nominalVoltage2": 24.0, + "terminal1Connected": true, + "terminal2Connected": true, + "r": 2.0, + "x": 25.0, + "g1": 0.0, + "b1": 1.5E-4, + "g2": 0.0, + "b2": 1.5E-4 } ], "hvdcLines": [ @@ -2256,6 +2335,33 @@ "voltageLevelId": "VLGEN4", "nominalVoltage": 24.0, "country": "FR" + }, + { + "id": "VLGEN7_1", + "v": "NaN", + "angle": "NaN", + "synchronousComponentNum": 1, + "connectedComponentNum": 1, + "voltageLevelId": "VLGEN7", + "nominalVoltage": 24.0 + }, + { + "id": "VLGEN7_11", + "v": "NaN", + "angle": "NaN", + "synchronousComponentNum": 2, + "connectedComponentNum": 2, + "voltageLevelId": "VLGEN7", + "nominalVoltage": 24.0 + }, + { + "id": "VLGEN9_0", + "v": "NaN", + "angle": "NaN", + "synchronousComponentNum": 2, + "connectedComponentNum": 2, + "voltageLevelId": "VLGEN9", + "nominalVoltage": 24.0 } ], "busbarSections": [ @@ -2286,6 +2392,31 @@ "id": "NGEN5_2_2", "name": "NGEN5_2_2", "voltageLevelId": "VLGEN5" + }, + { + "id": "BUS1_NGEN7", + "name": "Primary Busbar VLGEN7", + "voltageLevelId": "VLGEN7" + }, + { + "id": "BUS2_NGEN7", + "name": "Secondary Busbar VLGEN7", + "voltageLevelId": "VLGEN7" + }, + { + "id": "BUS3_NGEN7", + "name": "Third Busbar VLGEN7", + "voltageLevelId": "VLGEN7" + }, + { + "id": "BUS4_NGEN7", + "name": "Fourth Busbar VLGEN7", + "voltageLevelId": "VLGEN7" + }, + { + "id": "BUS_NGEN9", + "name": "Main Busbar VLGEN9", + "voltageLevelId": "VLGEN9" } ], "branches": [ @@ -2663,6 +2794,52 @@ "value": 53.0, "validity": false } + }, + { + "id": "LINE7_FORK", + "name": "Fork Branch 1 - Primary Line", + "type": "LINE", + "voltageLevelId1": "VLGEN4", + "voltageLevelId2": "VLGEN7", + "voltageLevelName2": "Fork Distribution Point", + "nominalVoltage1": 24.0, + "nominalVoltage2": 24.0, + "substationId1": "P4", + "country1": "FR", + "terminal1Connected": true, + "terminal2Connected": true, + "r": 3.0, + "x": 33.0 + }, + { + "id": "LINE8_FORK", + "name": "Fork Branch 2 - Secondary Line", + "type": "LINE", + "voltageLevelId1": "VLGEN7", + "voltageLevelId2": "VLGEN8", + "voltageLevelName1": "Fork Distribution Point", + "voltageLevelName2": "Fork Destination Point 2", + "nominalVoltage1": 24.0, + "nominalVoltage2": 24.0, + "terminal1Connected": true, + "terminal2Connected": false, + "r": 2.5, + "x": 28.0 + }, + { + "id": "LINE9_INDEPENDENT", + "name": "Independent Line from BUS4", + "type": "LINE", + "voltageLevelId1": "VLGEN7", + "voltageLevelId2": "VLGEN9", + "voltageLevelName1": "Fork Distribution Point", + "voltageLevelName2": "Independent Line Destination", + "nominalVoltage1": 24.0, + "nominalVoltage2": 24.0, + "terminal1Connected": true, + "terminal2Connected": true, + "r": 2.0, + "x": 25.0 } ] } \ No newline at end of file diff --git a/src/test/resources/all-data.json b/src/test/resources/all-data.json index 194766cc..f38c3ba0 100644 --- a/src/test/resources/all-data.json +++ b/src/test/resources/all-data.json @@ -219,6 +219,27 @@ "ipMin": 0.0, "ipMax": 100.0 } + }, + { + "id": "VLGEN7", + "name": "Fork Distribution Point", + "nominalV": 24.0, + "lowVoltageLimit": 20.0, + "highVoltageLimit": 30.0 + }, + { + "id": "VLGEN8", + "name": "Fork Destination Point 2", + "nominalV": 24.0, + "lowVoltageLimit": 20.0, + "highVoltageLimit": 30.0 + }, + { + "id": "VLGEN9", + "name": "Independent Line Destination", + "nominalV": 24.0, + "lowVoltageLimit": 20.0, + "highVoltageLimit": 30.0 } ], "lines": [ @@ -588,6 +609,64 @@ "b1": 1.93E-4, "g2": 0.0, "b2": 1.93E-4 + }, + { + "id": "LINE7_FORK", + "name": "Fork Branch 1 - Primary Line", + "type": "LINE", + "voltageLevelId1": "VLGEN4", + "voltageLevelId2": "VLGEN7", + "voltageLevelName2": "Fork Distribution Point", + "nominalVoltage1": 24.0, + "nominalVoltage2": 24.0, + "substationId1": "P4", + "country1": "FR", + "terminal1Connected": true, + "terminal2Connected": true, + "r": 3.0, + "x": 33.0, + "g1": 0.0, + "b1": 1.93E-4, + "g2": 0.0, + "b2": 1.93E-4 + }, + { + "id": "LINE8_FORK", + "name": "Fork Branch 2 - Secondary Line", + "type": "LINE", + "voltageLevelId1": "VLGEN7", + "voltageLevelId2": "VLGEN8", + "voltageLevelName1": "Fork Distribution Point", + "voltageLevelName2": "Fork Destination Point 2", + "nominalVoltage1": 24.0, + "nominalVoltage2": 24.0, + "terminal1Connected": true, + "terminal2Connected": false, + "r": 2.5, + "x": 28.0, + "g1": 0.0, + "b1": 1.6E-4, + "g2": 0.0, + "b2": 1.6E-4 + }, + { + "id": "LINE9_INDEPENDENT", + "name": "Independent Line from BUS4", + "type": "LINE", + "voltageLevelId1": "VLGEN7", + "voltageLevelId2": "VLGEN9", + "voltageLevelName1": "Fork Distribution Point", + "voltageLevelName2": "Independent Line Destination", + "nominalVoltage1": 24.0, + "nominalVoltage2": 24.0, + "terminal1Connected": true, + "terminal2Connected": true, + "r": 2.0, + "x": 25.0, + "g1": 0.0, + "b1": 1.5E-4, + "g2": 0.0, + "b2": 1.5E-4 } ], "hvdcLines": [ @@ -2468,6 +2547,33 @@ "voltageLevelId": "VLGEN4", "nominalVoltage": 24.0, "country": "FR" + }, + { + "id": "VLGEN7_1", + "v": "NaN", + "angle": "NaN", + "synchronousComponentNum": 1, + "connectedComponentNum": 1, + "voltageLevelId": "VLGEN7", + "nominalVoltage": 24.0 + }, + { + "id": "VLGEN7_11", + "v": "NaN", + "angle": "NaN", + "synchronousComponentNum": 2, + "connectedComponentNum": 2, + "voltageLevelId": "VLGEN7", + "nominalVoltage": 24.0 + }, + { + "id": "VLGEN9_0", + "v": "NaN", + "angle": "NaN", + "synchronousComponentNum": 2, + "connectedComponentNum": 2, + "voltageLevelId": "VLGEN9", + "nominalVoltage": 24.0 } ], "busbarSections": [ @@ -2498,6 +2604,31 @@ "id": "NGEN5_2_2", "name": "NGEN5_2_2", "voltageLevelId": "VLGEN5" + }, + { + "id": "BUS1_NGEN7", + "name": "Primary Busbar VLGEN7", + "voltageLevelId": "VLGEN7" + }, + { + "id": "BUS2_NGEN7", + "name": "Secondary Busbar VLGEN7", + "voltageLevelId": "VLGEN7" + }, + { + "id": "BUS3_NGEN7", + "name": "Third Busbar VLGEN7", + "voltageLevelId": "VLGEN7" + }, + { + "id": "BUS4_NGEN7", + "name": "Fourth Busbar VLGEN7", + "voltageLevelId": "VLGEN7" + }, + { + "id": "BUS_NGEN9", + "name": "Main Busbar VLGEN9", + "voltageLevelId": "VLGEN9" } ], "branches": [ @@ -3084,6 +3215,52 @@ "value": 53.0, "validity": false } + }, + { + "id": "LINE7_FORK", + "name": "Fork Branch 1 - Primary Line", + "type": "LINE", + "voltageLevelId1": "VLGEN4", + "voltageLevelId2": "VLGEN7", + "voltageLevelName2": "Fork Distribution Point", + "nominalVoltage1": 24.0, + "nominalVoltage2": 24.0, + "substationId1": "P4", + "country1": "FR", + "terminal1Connected": true, + "terminal2Connected": true, + "r": 3.0, + "x": 33.0 + }, + { + "id": "LINE8_FORK", + "name": "Fork Branch 2 - Secondary Line", + "type": "LINE", + "voltageLevelId1": "VLGEN7", + "voltageLevelId2": "VLGEN8", + "voltageLevelName1": "Fork Distribution Point", + "voltageLevelName2": "Fork Destination Point 2", + "nominalVoltage1": 24.0, + "nominalVoltage2": 24.0, + "terminal1Connected": true, + "terminal2Connected": false, + "r": 2.5, + "x": 28.0 + }, + { + "id": "LINE9_INDEPENDENT", + "name": "Independent Line from BUS4", + "type": "LINE", + "voltageLevelId1": "VLGEN7", + "voltageLevelId2": "VLGEN9", + "voltageLevelName1": "Fork Distribution Point", + "voltageLevelName2": "Independent Line Destination", + "nominalVoltage1": 24.0, + "nominalVoltage2": 24.0, + "terminal1Connected": true, + "terminal2Connected": true, + "r": 2.0, + "x": 25.0 } ] } \ No newline at end of file diff --git a/src/test/resources/bus-bar-section-form-data.json b/src/test/resources/bus-bar-section-form-data.json index 5a9a8efa..73f8cbe4 100644 --- a/src/test/resources/bus-bar-section-form-data.json +++ b/src/test/resources/bus-bar-section-form-data.json @@ -22,5 +22,25 @@ "name": "NGEN5_2_2", "vertPos": 2, "horizPos": 2 + }, + { + "id":"BUS1_NGEN7", + "name":"Primary Busbar VLGEN7" + }, + { + "id":"BUS2_NGEN7", + "name":"Secondary Busbar VLGEN7" + }, + { + "id":"BUS3_NGEN7", + "name":"Third Busbar VLGEN7" + }, + { + "id":"BUS4_NGEN7", + "name":"Fourth Busbar VLGEN7" + }, + { + "id":"BUS_NGEN9", + "name":"Main Busbar VLGEN9" } ] \ No newline at end of file diff --git a/src/test/resources/bus-bar-section-list-data.json b/src/test/resources/bus-bar-section-list-data.json index ce64cf17..370725da 100644 --- a/src/test/resources/bus-bar-section-list-data.json +++ b/src/test/resources/bus-bar-section-list-data.json @@ -14,5 +14,25 @@ { "id": "NGEN5_2_2", "name": "NGEN5_2_2" + }, + { + "id":"BUS1_NGEN7", + "name":"Primary Busbar VLGEN7" + }, + { + "id":"BUS2_NGEN7", + "name":"Secondary Busbar VLGEN7" + }, + { + "id":"BUS3_NGEN7", + "name":"Third Busbar VLGEN7" + }, + { + "id":"BUS4_NGEN7", + "name":"Fourth Busbar VLGEN7" + }, + { + "id":"BUS_NGEN9", + "name":"Main Busbar VLGEN9" } ] \ No newline at end of file diff --git a/src/test/resources/bus-bar-section-operating-status-data.json b/src/test/resources/bus-bar-section-operating-status-data.json index 77ffd3f6..ccc32409 100644 --- a/src/test/resources/bus-bar-section-operating-status-data.json +++ b/src/test/resources/bus-bar-section-operating-status-data.json @@ -15,5 +15,25 @@ { "id": "NGEN5_2_2", "name": "NGEN5_2_2" + }, + { + "id":"BUS1_NGEN7", + "name":"Primary Busbar VLGEN7" + }, + { + "id":"BUS2_NGEN7", + "name":"Secondary Busbar VLGEN7" + }, + { + "id":"BUS3_NGEN7", + "name":"Third Busbar VLGEN7" + }, + { + "id":"BUS4_NGEN7", + "name":"Fourth Busbar VLGEN7" + }, + { + "id":"BUS_NGEN9", + "name":"Main Busbar VLGEN9" } ] \ No newline at end of file diff --git a/src/test/resources/lines-form-data.json b/src/test/resources/lines-form-data.json index 8f43e350..04fa4cf5 100644 --- a/src/test/resources/lines-form-data.json +++ b/src/test/resources/lines-form-data.json @@ -240,5 +240,87 @@ }, "busOrBusbarSectionId1": "NGEN6", "busOrBusbarSectionId2": "NGEN3" + }, + { + "id": "LINE7_FORK", + "name": "Fork Branch 1 - Primary Line", + "voltageLevelId1": "VLGEN4", + "voltageLevelId2": "VLGEN7", + "voltageLevelName2": "Fork Distribution Point", + "terminal1Connected": true, + "terminal2Connected": true, + "r": 3.0, + "x": 33.0, + "g1": 0.0, + "b1": 1.93e-4, + "g2": 0.0, + "b2": 1.93e-4, + "connectablePosition1": { + "connectionDirection": "BOTTOM", + "connectionPosition": 5, + "connectionName": "LINE7_VLGEN4_Side" + }, + "connectablePosition2": { + "connectionDirection": "TOP", + "connectionPosition": 1, + "connectionName": "LINE7_VLGEN7_Fork_Side" + }, + "busOrBusbarSectionId1": "NGEN4", + "busOrBusbarSectionId2": "BUS2_NGEN7" + }, + { + "id": "LINE8_FORK", + "name": "Fork Branch 2 - Secondary Line", + "voltageLevelId1": "VLGEN7", + "voltageLevelName1": "Fork Distribution Point", + "voltageLevelId2": "VLGEN8", + "voltageLevelName2": "Fork Destination Point 2", + "terminal1Connected": true, + "terminal2Connected": false, + "r": 2.5, + "x": 28.0, + "g1": 0.0, + "b1": 1.6e-4, + "g2": 0.0, + "b2": 1.6e-4, + "connectablePosition1": { + "connectionDirection": "BOTTOM", + "connectionPosition": 2, + "connectionName": "LINE8_VLGEN7_Fork_Side" + }, + "connectablePosition2": { + "connectionDirection": "TOP", + "connectionPosition": 1, + "connectionName": "LINE8_VLGEN8_Side" + }, + "busOrBusbarSectionId1": "BUS2_NGEN7" + }, + { + "id": "LINE9_INDEPENDENT", + "name": "Independent Line from BUS4", + "voltageLevelId1": "VLGEN7", + "voltageLevelName1": "Fork Distribution Point", + "voltageLevelId2": "VLGEN9", + "voltageLevelName2": "Independent Line Destination", + "terminal1Connected": true, + "terminal2Connected": true, + "r": 2.0, + "x": 25.0, + "g1": 0.0, + "b1": 1.5e-4, + "g2": 0.0, + "b2": 1.5e-4, + "connectablePosition1": { + "connectionDirection": "BOTTOM", + "connectionPosition": 3, + "connectionName": "LINE9_VLGEN7_Side" + }, + "connectablePosition2": { + "connectionDirection": "BOTTOM", + "connectionPosition": 1, + "connectionName": "LINE9_VLGEN9_Side" + }, + "busOrBusbarSectionId1": "BUS4_NGEN7", + "busOrBusbarSectionId2": "BUS_NGEN9" } ] \ No newline at end of file diff --git a/src/test/resources/lines-list-data.json b/src/test/resources/lines-list-data.json index 22dbe3b8..f8f529f9 100644 --- a/src/test/resources/lines-list-data.json +++ b/src/test/resources/lines-list-data.json @@ -10,5 +10,17 @@ }, { "id": "LINE4" + }, + { + "id":"LINE7_FORK", + "name":"Fork Branch 1 - Primary Line" + }, + { + "id":"LINE8_FORK", + "name":"Fork Branch 2 - Secondary Line" + }, + { + "id":"LINE9_INDEPENDENT", + "name":"Independent Line from BUS4" } ] diff --git a/src/test/resources/lines-map-data.json b/src/test/resources/lines-map-data.json index 28836e21..088a7482 100644 --- a/src/test/resources/lines-map-data.json +++ b/src/test/resources/lines-map-data.json @@ -32,5 +32,34 @@ "voltageLevelId2": "VLGEN3", "terminal1Connected": true, "terminal2Connected": true + }, + { + "id":"LINE7_FORK", + "name":"Fork Branch 1 - Primary Line", + "voltageLevelId1":"VLGEN4", + "voltageLevelId2":"VLGEN7", + "voltageLevelName2":"Fork Distribution Point", + "terminal1Connected":true, + "terminal2Connected":true + }, + { + "id":"LINE8_FORK", + "name":"Fork Branch 2 - Secondary Line", + "voltageLevelId1":"VLGEN7", + "voltageLevelName1":"Fork Distribution Point", + "voltageLevelId2":"VLGEN8", + "voltageLevelName2":"Fork Destination Point 2", + "terminal1Connected":true, + "terminal2Connected":false + }, + { + "id":"LINE9_INDEPENDENT", + "name":"Independent Line from BUS4", + "voltageLevelId1":"VLGEN7", + "voltageLevelName1":"Fork Distribution Point", + "voltageLevelId2":"VLGEN9", + "voltageLevelName2":"Independent Line Destination", + "terminal1Connected":true, + "terminal2Connected":true } ] \ No newline at end of file diff --git a/src/test/resources/lines-operating-status-data.json b/src/test/resources/lines-operating-status-data.json index 0d655cd7..5a7fb7fa 100644 --- a/src/test/resources/lines-operating-status-data.json +++ b/src/test/resources/lines-operating-status-data.json @@ -28,5 +28,34 @@ "voltageLevelId2": "VLGEN3", "terminal1Connected": true, "terminal2Connected": true + }, + { + "id":"LINE7_FORK", + "name":"Fork Branch 1 - Primary Line", + "voltageLevelId1":"VLGEN4", + "voltageLevelId2":"VLGEN7", + "voltageLevelName2":"Fork Distribution Point", + "terminal1Connected":true, + "terminal2Connected":true + }, + { + "id":"LINE8_FORK", + "name":"Fork Branch 2 - Secondary Line", + "voltageLevelId1":"VLGEN7", + "voltageLevelName1":"Fork Distribution Point", + "voltageLevelId2":"VLGEN8", + "voltageLevelName2":"Fork Destination Point 2", + "terminal1Connected":true, + "terminal2Connected":false + }, + { + "id":"LINE9_INDEPENDENT", + "name":"Independent Line from BUS4", + "voltageLevelId1":"VLGEN7", + "voltageLevelName1":"Fork Distribution Point", + "voltageLevelId2":"VLGEN9", + "voltageLevelName2":"Independent Line Destination", + "terminal1Connected":true, + "terminal2Connected":true } ] diff --git a/src/test/resources/switches-data-in-variant.json b/src/test/resources/switches-data-in-variant.json index e6a9eb9a..752f3aef 100644 --- a/src/test/resources/switches-data-in-variant.json +++ b/src/test/resources/switches-data-in-variant.json @@ -6,5 +6,13 @@ { "id": "VL4_SHUNT_BREAKER", "open": true + }, + { + "id":"DISC_VLGEN4", + "open":false + }, + { + "id":"BRKR_VLGEN4", + "open":false } ] \ No newline at end of file diff --git a/src/test/resources/switches-data.json b/src/test/resources/switches-data.json index 2848d225..5e048478 100644 --- a/src/test/resources/switches-data.json +++ b/src/test/resources/switches-data.json @@ -6,5 +6,13 @@ { "id": "VL4_SHUNT_BREAKER", "open": false + }, + { + "id":"DISC_VLGEN4", + "open":false + }, + { + "id":"BRKR_VLGEN4", + "open":false } ] \ No newline at end of file diff --git a/src/test/resources/voltage-levels-form-data.json b/src/test/resources/voltage-levels-form-data.json index f9b3b33b..d2c1ab2b 100644 --- a/src/test/resources/voltage-levels-form-data.json +++ b/src/test/resources/voltage-levels-form-data.json @@ -94,5 +94,47 @@ "topologyKind": "BUS_BREAKER", "substationId": "P6", "nominalV": 24.0 + }, + { + "id": "VLGEN7", + "name": "Fork Distribution Point", + "topologyKind": "NODE_BREAKER", + "nominalV": 24.0, + "lowVoltageLimit": 20.0, + "highVoltageLimit": 30.0, + "busbarCount": 1, + "sectionCount": 1, + "switchKinds": [], + "isRetrievedBusbarSections": false, + "isBusbarSectionPositionFound": false, + "busBarSectionInfos": {} + }, + { + "id": "VLGEN8", + "name": "Fork Destination Point 2", + "topologyKind": "NODE_BREAKER", + "nominalV": 24.0, + "lowVoltageLimit": 20.0, + "highVoltageLimit": 30.0, + "busbarCount": 1, + "sectionCount": 1, + "switchKinds": [], + "isRetrievedBusbarSections": true, + "isBusbarSectionPositionFound": true, + "busBarSectionInfos": {} + }, + { + "id": "VLGEN9", + "name": "Independent Line Destination", + "topologyKind": "NODE_BREAKER", + "nominalV": 24.0, + "lowVoltageLimit": 20.0, + "highVoltageLimit": 30.0, + "busbarCount": 1, + "sectionCount": 1, + "switchKinds": [], + "isRetrievedBusbarSections": false, + "isBusbarSectionPositionFound": false, + "busBarSectionInfos": {} } ] \ No newline at end of file diff --git a/src/test/resources/voltage-levels-list-data.json b/src/test/resources/voltage-levels-list-data.json index 6705164a..7c3759e8 100644 --- a/src/test/resources/voltage-levels-list-data.json +++ b/src/test/resources/voltage-levels-list-data.json @@ -30,5 +30,17 @@ }, { "id": "VLGEN6" + }, + { + "id":"VLGEN7", + "name":"Fork Distribution Point" + }, + { + "id":"VLGEN8", + "name":"Fork Destination Point 2" + }, + { + "id":"VLGEN9", + "name":"Independent Line Destination" } ] diff --git a/src/test/resources/voltage-levels-map-data.json b/src/test/resources/voltage-levels-map-data.json index 8f77d945..1b7928c7 100644 --- a/src/test/resources/voltage-levels-map-data.json +++ b/src/test/resources/voltage-levels-map-data.json @@ -49,5 +49,20 @@ "id": "VLGEN5", "substationId": "P5", "nominalV": 24.0 + }, + { + "id":"VLGEN7", + "name":"Fork Distribution Point", + "nominalV":24.0 + }, + { + "id":"VLGEN8", + "name":"Fork Destination Point 2", + "nominalV":24.0 + }, + { + "id":"VLGEN9", + "name":"Independent Line Destination", + "nominalV":24.0 } ] From 78f18fd417d00540b4a72ccf5ab374e6b1d70d2a Mon Sep 17 00:00:00 2001 From: Rehili Ghazwa Date: Tue, 23 Sep 2025 13:03:57 +0200 Subject: [PATCH 04/15] enhance TU --- src/test/resources/all-data-without-optionals.json | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/test/resources/all-data-without-optionals.json b/src/test/resources/all-data-without-optionals.json index 169ec78f..14e1ffdd 100644 --- a/src/test/resources/all-data-without-optionals.json +++ b/src/test/resources/all-data-without-optionals.json @@ -2324,8 +2324,6 @@ "id": "VLGEN7_1", "v": "NaN", "angle": "NaN", - "synchronousComponentNum": 1, - "connectedComponentNum": 1, "voltageLevelId": "VLGEN7", "nominalVoltage": 24.0 }, @@ -2333,8 +2331,6 @@ "id": "VLGEN7_11", "v": "NaN", "angle": "NaN", - "synchronousComponentNum": 2, - "connectedComponentNum": 2, "voltageLevelId": "VLGEN7", "nominalVoltage": 24.0 }, @@ -2342,8 +2338,6 @@ "id": "VLGEN9_0", "v": "NaN", "angle": "NaN", - "synchronousComponentNum": 2, - "connectedComponentNum": 2, "voltageLevelId": "VLGEN9", "nominalVoltage": 24.0 } From efc0ece187911875175a65f72c4740608a56d69c Mon Sep 17 00:00:00 2001 From: Rehili Ghazwa Date: Mon, 29 Sep 2025 16:26:07 +0200 Subject: [PATCH 05/15] breadth-first busbasrsection id search algorithm --- .../BusbarSectionFinderTraverser.java | 190 ++++++++++++------ .../network/map/dto/utils/ElementUtils.java | 2 +- 2 files changed, 126 insertions(+), 66 deletions(-) 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 1f5c7902..d64694bf 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 @@ -7,96 +7,156 @@ package org.gridsuite.network.map.dto.definition.extension; import com.powsybl.iidm.network.IdentifiableType; -import com.powsybl.iidm.network.Switch; import com.powsybl.iidm.network.SwitchKind; import com.powsybl.iidm.network.Terminal; -import com.powsybl.math.graph.TraverseResult; +import com.powsybl.iidm.network.VoltageLevel; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; +import java.util.*; /** * @author Slimane Amar */ -public class BusbarSectionFinderTraverser implements Terminal.TopologyTraverser { +public final class BusbarSectionFinderTraverser { - private final List busbarCandidates = new ArrayList<>(); - private final Set visitedTerminals = new HashSet<>(); - private static final int MAX_VISITED = 50; - private final boolean allowTraversalThroughOpenDisconnectors; - - public BusbarSectionFinderTraverser(boolean allowTraversalThroughOpenDisconnectors) { - this.allowTraversalThroughOpenDisconnectors = allowTraversalThroughOpenDisconnectors; + /** + * Private constructor to prevent instantiation of this utility class. + */ + private BusbarSectionFinderTraverser() { + throw new UnsupportedOperationException(); } - @Override - public TraverseResult traverse(Terminal terminal, boolean connected) { - String terminalId = terminal.getConnectable().getId(); - if (visitedTerminals.contains(terminalId)) { - return TraverseResult.TERMINATE_PATH; - } - visitedTerminals.add(terminalId); - if (visitedTerminals.size() > MAX_VISITED) { - return TraverseResult.TERMINATE_TRAVERSER; + /** + * Finds the best busbar section connected to the given terminal. + * Uses a breadth-first search algorithm to explore all possible paths. + * + * @param terminal the starting terminal + * @return the best busbar result according to selection criteria, or null if none found + */ + public static BusbarResult findBestBusbar(Terminal terminal) { + VoltageLevel.NodeBreakerView view = terminal.getVoltageLevel().getNodeBreakerView(); + int startNode = terminal.getNodeBreakerView().getNode(); + List allResults = searchAllBusbars(view, startNode); + if (allResults.isEmpty()) { + return null; } + return selectBestBusbar(allResults); + } - // If a busbar section is found, add it as a candidate - if (terminal.getConnectable().getType() == IdentifiableType.BUSBAR_SECTION) { - busbarCandidates.add(new BusbarCandidate(terminalId, connected)); - // CONTINUE to explore other paths to other busbars - return TraverseResult.CONTINUE; + /** + * Selects the best busbar from a list of candidates using a priority-based approach: + * Priority 1: Busbar with closed last switch (minimum depth, then minimum switches before last) + * Priority 2: Busbar with open last switch (minimum depth, then minimum switches before last) + * Priority 3: Busbar without switch (direct connection, minimum depth) + * + * @param results list of all found busbar results + * @return the best busbar according to selection criteria + */ + private static BusbarResult selectBestBusbar(List results) { + // Priority 1: Search for busbar with closed last switch + List withClosedSwitch = results.stream().filter(r -> r.lastSwitch() != null && !r.lastSwitch().isOpen()).toList(); + if (!withClosedSwitch.isEmpty()) { + BusbarResult best = withClosedSwitch.stream().min(Comparator.comparingInt(BusbarResult::depth) + .thenComparingInt(BusbarResult::switchesBeforeLast)) + .get(); + return best; } - return TraverseResult.CONTINUE; - } - @Override - public TraverseResult traverse(Switch aSwitch) { - if (visitedTerminals.size() > MAX_VISITED) { - return TraverseResult.TERMINATE_TRAVERSER; + // Priority 2: Search for busbar with open last switch + List withOpenSwitch = results.stream().filter(r -> r.lastSwitch() != null && r.lastSwitch().isOpen()).toList(); + if (!withOpenSwitch.isEmpty()) { + BusbarResult best = withOpenSwitch.stream().min(Comparator.comparingInt(BusbarResult::depth) + .thenComparingInt(BusbarResult::switchesBeforeLast)) + .get(); + + return best; } - // KEY: Open disconnectors end this path but not the overall traversal - // They block access to this busbar but not to the others - if (aSwitch.isOpen() && aSwitch.getKind() == SwitchKind.DISCONNECTOR) { - // Use the parameter to control behavior - return allowTraversalThroughOpenDisconnectors ? - TraverseResult.CONTINUE : - TraverseResult.TERMINATE_PATH; + // Priority 3: Busbars without switch (direct connection) + List withoutSwitch = results.stream().filter(r -> r.lastSwitch() == null).toList(); + if (!withoutSwitch.isEmpty()) { + BusbarResult best = withoutSwitch.stream().min(Comparator.comparingInt(BusbarResult::depth)).get(); + return best; } - return TraverseResult.CONTINUE; + + // Fallback: select first busbar + return results.getFirst(); } - public String getBusbarWithClosedDisconnector() { - // Search for a connected busbar (disconnector closed) - for (BusbarCandidate candidate : busbarCandidates) { - if (candidate.connected()) { - return candidate.id(); + /** + * Searches all accessible busbars from a starting node using breadth-first search. + * Explores the node-breaker topology through switches. + * + * @param view the node-breaker view of the voltage level + * @param startNode the starting node index + * @return list of all busbar results found + */ + private static List searchAllBusbars(VoltageLevel.NodeBreakerView view, int startNode) { + List results = new ArrayList<>(); + Set visited = new HashSet<>(); + Queue queue = new LinkedList<>(); + queue.offer(new NodePath(startNode, new ArrayList<>(), null)); + while (!queue.isEmpty()) { + NodePath current = queue.poll(); + if (visited.contains(current.node())) { + continue; + } + visited.add(current.node()); + // Check if current node is a busbar section + Optional nodeTerminal = view.getOptionalTerminal(current.node()); + if (nodeTerminal.isPresent()) { + Terminal term = nodeTerminal.get(); + if (term.getConnectable().getType() == IdentifiableType.BUSBAR_SECTION) { + String busbarId = term.getConnectable().getId(); + int depth = current.pathSwitches().size(); + SwitchInfo lastSwitch = current.lastSwitch(); + // Calculate number of switches BEFORE the last one + int switchesBeforeLast = lastSwitch != null ? (depth - 1) : 0; + results.add(new BusbarResult(busbarId, depth, switchesBeforeLast, lastSwitch, null)); + continue; // Don't explore beyond busbar + } } - } - // Return first busbar found or null if none - return !busbarCandidates.isEmpty() ? busbarCandidates.getFirst().id() : null; + // Explore adjacent nodes through switches + view.getSwitchStream().forEach(sw -> { + int node1 = view.getNode1(sw.getId()); + int node2 = view.getNode2(sw.getId()); + if (node1 == current.node() || node2 == current.node()) { + int nextNode = (node1 == current.node()) ? node2 : node1; + if (!visited.contains(nextNode)) { + List newPathSwitches = new ArrayList<>(current.pathSwitches()); + SwitchInfo switchInfo = new SwitchInfo(sw.getId(), sw.getKind(), sw.isOpen(), node1, node2); + newPathSwitches.add(switchInfo); + queue.offer(new NodePath(nextNode, newPathSwitches, switchInfo)); + } + } + }); + } + return results; } - // Utility method with automatic fallback - public static String findBusbar(Terminal startTerminal) { - // Attempt 1: normal behavior (blocks on open disconnectors) - var traverser1 = new BusbarSectionFinderTraverser(false); - startTerminal.traverse(traverser1); - String result = traverser1.getBusbarWithClosedDisconnector(); + /** + * Internal record to track the path during graph traversal. + */ + private record NodePath(int node, List pathSwitches, SwitchInfo lastSwitch) { } - if (result != null) { - return result; - } + /** + * Record containing information about a switch in the topology. + */ + public record SwitchInfo(String id, SwitchKind kind, boolean isOpen, int node1, int node2) { } - // Attempt 2: if null, retry allowing traversal through open disconnectors - var traverser2 = new BusbarSectionFinderTraverser(true); - startTerminal.traverse(traverser2); - return traverser2.getBusbarWithClosedDisconnector(); - } + /** + * Record containing the result of a busbar search with selection metadata. + */ + public record BusbarResult(String busbarId, int depth, int switchesBeforeLast, SwitchInfo lastSwitch, String selectionReason) { } - private record BusbarCandidate(String id, boolean connected) { + /** + * Convenience method to get only the busbar ID. + * + * @param terminal the starting terminal + * @return the busbar ID or null if none found + */ + public static String findBusbarId(Terminal terminal) { + BusbarResult result = findBestBusbar(terminal); + return result != null ? result.busbarId() : null; } } 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 5b4b6fed..09b6cc5e 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 @@ -47,7 +47,7 @@ public static String getBusOrBusbarSection(Terminal terminal) { } } else { // NODE_BREAKER: explore all paths and choose the busbar with the closed disconnector - return BusbarSectionFinderTraverser.findBusbar(terminal); + return BusbarSectionFinderTraverser.findBusbarId(terminal); } } From ca84d42cfebaf6d5aa52156f9bbb1d0acc36798d Mon Sep 17 00:00:00 2001 From: Rehili Ghazwa Date: Tue, 30 Sep 2025 09:18:24 +0200 Subject: [PATCH 06/15] add TU --- .../BusbarSectionFinderTraverser.java | 14 +- .../network/map/NetworkMapControllerTest.java | 200 --------- .../BusbarSectionFinderTraverserTest.java | 419 ++++++++++++++++++ 3 files changed, 424 insertions(+), 209 deletions(-) create mode 100644 src/test/java/org/gridsuite/network/map/mapper/BusbarSectionFinderTraverserTest.java 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 d64694bf..5c03f27d 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 @@ -55,27 +55,23 @@ private static BusbarResult selectBestBusbar(List results) { // Priority 1: Search for busbar with closed last switch List withClosedSwitch = results.stream().filter(r -> r.lastSwitch() != null && !r.lastSwitch().isOpen()).toList(); if (!withClosedSwitch.isEmpty()) { - BusbarResult best = withClosedSwitch.stream().min(Comparator.comparingInt(BusbarResult::depth) + return withClosedSwitch.stream().min(Comparator.comparingInt(BusbarResult::depth) .thenComparingInt(BusbarResult::switchesBeforeLast)) .get(); - return best; } // Priority 2: Search for busbar with open last switch List withOpenSwitch = results.stream().filter(r -> r.lastSwitch() != null && r.lastSwitch().isOpen()).toList(); if (!withOpenSwitch.isEmpty()) { - BusbarResult best = withOpenSwitch.stream().min(Comparator.comparingInt(BusbarResult::depth) + return withOpenSwitch.stream().min(Comparator.comparingInt(BusbarResult::depth) .thenComparingInt(BusbarResult::switchesBeforeLast)) .get(); - - return best; } // Priority 3: Busbars without switch (direct connection) List withoutSwitch = results.stream().filter(r -> r.lastSwitch() == null).toList(); if (!withoutSwitch.isEmpty()) { - BusbarResult best = withoutSwitch.stream().min(Comparator.comparingInt(BusbarResult::depth)).get(); - return best; + return withoutSwitch.stream().min(Comparator.comparingInt(BusbarResult::depth)).get(); } // Fallback: select first busbar @@ -111,7 +107,7 @@ private static List searchAllBusbars(VoltageLevel.NodeBreakerView SwitchInfo lastSwitch = current.lastSwitch(); // Calculate number of switches BEFORE the last one int switchesBeforeLast = lastSwitch != null ? (depth - 1) : 0; - results.add(new BusbarResult(busbarId, depth, switchesBeforeLast, lastSwitch, null)); + results.add(new BusbarResult(busbarId, depth, switchesBeforeLast, lastSwitch)); continue; // Don't explore beyond busbar } } @@ -147,7 +143,7 @@ public record SwitchInfo(String id, SwitchKind kind, boolean isOpen, int node1, /** * Record containing the result of a busbar search with selection metadata. */ - public record BusbarResult(String busbarId, int depth, int switchesBeforeLast, SwitchInfo lastSwitch, String selectionReason) { } + public record BusbarResult(String busbarId, int depth, int switchesBeforeLast, SwitchInfo lastSwitch) { } /** * Convenience method to get only the busbar ID. diff --git a/src/test/java/org/gridsuite/network/map/NetworkMapControllerTest.java b/src/test/java/org/gridsuite/network/map/NetworkMapControllerTest.java index 2dae0680..d6867218 100644 --- a/src/test/java/org/gridsuite/network/map/NetworkMapControllerTest.java +++ b/src/test/java/org/gridsuite/network/map/NetworkMapControllerTest.java @@ -1158,206 +1158,6 @@ void setUp() { .withDirection(ConnectablePosition.Direction.TOP).add() .add(); - /** VLGEN7 - Fork topology - * - * BUS1 ═══════X════════ BUS2 ═══════════════/════ BUS3 - * (node0) (node5) (node7) - * | | | - * Disconnector1 Disconnector2 | - * | | | - * | | Disconnector3 - * [open = true] [open = false] [open = false] - * | | | - * |____________________| ═══════════/════════ BUS4 - * | | - * | | - * fork point Disconnector4 - * (node8) [open = false] - * | | - * ┌───┴───┐ LINE9 - * | | (→VLGEN9) - * LINE7 LINE8 - * (→VLGEN4) (→VLGEN8) - */ - - VoltageLevel vlgen7 = network.newVoltageLevel() - .setId("VLGEN7") - .setName("Fork Distribution Point") - .setNominalV(24.0) - .setHighVoltageLimit(30.0) - .setLowVoltageLimit(20.0) - .setTopologyKind(TopologyKind.NODE_BREAKER) - .add(); - - // Create busbarSections - vlgen7.getNodeBreakerView().newBusbarSection() - .setId("BUS1_NGEN7") - .setName("Primary Busbar VLGEN7") - .setNode(0) - .add(); - - vlgen7.getNodeBreakerView().newBusbarSection() - .setId("BUS2_NGEN7") - .setName("Secondary Busbar VLGEN7") - .setNode(5) - .add(); - - vlgen7.getNodeBreakerView().newBusbarSection() - .setId("BUS3_NGEN7") - .setName("Third Busbar VLGEN7") - .setNode(9) - .add(); - vlgen7.getNodeBreakerView().newBusbarSection() - .setId("BUS4_NGEN7") - .setName("Fourth Busbar VLGEN7") - .setNode(13) - .add(); - - createSwitch(vlgen7, "SECT_BUS1", SwitchKind.DISCONNECTOR, true, 0, 6); // OPEN - createSwitch(vlgen7, "SECT_BUS2", SwitchKind.DISCONNECTOR, false, 5, 7); // CLOSED - createSwitch(vlgen7, "SECT_BUS3", SwitchKind.DISCONNECTOR, false, 9, 10); // CLOSED - createSwitch(vlgen7, "SECT_BUS4", SwitchKind.DISCONNECTOR, false, 13, 14); // CLOSED - - createSwitch(vlgen7, "FORK_SW1", SwitchKind.DISCONNECTOR, false, 6, 8); // BUS1 to fork (CLOSED via node 6) - createSwitch(vlgen7, "FORK_SW2", SwitchKind.DISCONNECTOR, false, 7, 8); // BUS2 to fork (CLOSED via node 7) - - // LINE7 connection from fork - createSwitch(vlgen7, "DISC_LINE7", SwitchKind.DISCONNECTOR, false, 8, 1); - createSwitch(vlgen7, "BRKR_LINE7", SwitchKind.BREAKER, false, 1, 2); - - // LINE8 connection from fork - createSwitch(vlgen7, "DISC_LINE8", SwitchKind.DISCONNECTOR, false, 8, 3); - createSwitch(vlgen7, "BRKR_LINE8", SwitchKind.BREAKER, false, 3, 4); - - // LINE9 connection from BUS4 (Fixed connection point) - createSwitch(vlgen7, "DISC_LINE9", SwitchKind.DISCONNECTOR, false, 14, 12); // Connect from node 14 - createSwitch(vlgen7, "BRKR_LINE9", SwitchKind.BREAKER, false, 12, 11); - - // Assuming VLGEN4 exists, create its switches - createSwitch(vlgen4, "DISC_VLGEN4", SwitchKind.DISCONNECTOR, false, 0, 10); - createSwitch(vlgen4, "BRKR_VLGEN4", SwitchKind.BREAKER, false, 10, 15); - - // LINE7 - Fork Branch 1 - network.newLine() - .setId("LINE7_FORK") - .setName("Fork Branch 1 - Primary Line") - .setVoltageLevel1("VLGEN4") - .setNode1(15) // Fixed node reference - .setVoltageLevel2("VLGEN7") - .setNode2(2) - .setR(3.0) - .setX(33.0) - .setG1(0.0) - .setB1(386E-6 / 2) - .setG2(0.0) - .setB2(386E-6 / 2) - .add(); - - Line line7 = network.getLine("LINE7_FORK"); - line7.newExtension(ConnectablePositionAdder.class) - .newFeeder1() - .withName("LINE7_VLGEN4_Side") - .withOrder(5) - .withDirection(ConnectablePosition.Direction.BOTTOM) - .add() - .newFeeder2() - .withName("LINE7_VLGEN7_Fork_Side") - .withOrder(1) - .withDirection(ConnectablePosition.Direction.TOP) - .add() - .add(); - - // VLGEN8 - Fork destination 2 - VoltageLevel vlgen8 = network.newVoltageLevel() - .setId("VLGEN8") - .setName("Fork Destination Point 2") - .setNominalV(24.0) - .setHighVoltageLimit(30.0) - .setLowVoltageLimit(20.0) - .setTopologyKind(TopologyKind.NODE_BREAKER) - .add(); - createSwitch(vlgen8, "DISC_VLGEN8", SwitchKind.DISCONNECTOR, false, 0, 1); - createSwitch(vlgen8, "BRKR_VLGEN8", SwitchKind.BREAKER, false, 1, 2); - - // LINE8 - Fork Branch 2 - network.newLine() - .setId("LINE8_FORK") - .setName("Fork Branch 2 - Secondary Line") - .setVoltageLevel1("VLGEN7") - .setNode1(4) - .setVoltageLevel2("VLGEN8") - .setNode2(2) - .setR(2.5) - .setX(28.0) - .setG1(0.0) - .setB1(320E-6 / 2) - .setG2(0.0) - .setB2(320E-6 / 2) - .add(); - Line line8 = network.getLine("LINE8_FORK"); - line8.newExtension(ConnectablePositionAdder.class) - .newFeeder1() - .withName("LINE8_VLGEN7_Fork_Side") - .withOrder(2) - .withDirection(ConnectablePosition.Direction.BOTTOM) - .add() - .newFeeder2() - .withName("LINE8_VLGEN8_Side") - .withOrder(1) - .withDirection(ConnectablePosition.Direction.TOP) - .add() - .add(); - -// VLGEN9 - Independent line destination - VoltageLevel vlgen9 = network.newVoltageLevel() - .setId("VLGEN9") - .setName("Independent Line Destination") - .setNominalV(24.0) - .setHighVoltageLimit(30.0) - .setLowVoltageLimit(20.0) - .setTopologyKind(TopologyKind.NODE_BREAKER) - .add(); - - vlgen9.getNodeBreakerView().newBusbarSection() - .setId("BUS_NGEN9") - .setName("Main Busbar VLGEN9") - .setNode(0) - .add(); - - createSwitch(vlgen9, "DISC_VLGEN9", SwitchKind.DISCONNECTOR, false, 0, 1); - createSwitch(vlgen9, "BRKR_VLGEN9", SwitchKind.BREAKER, false, 1, 2); - - // LINE9 - Independent line from BUS4 - network.newLine() - .setId("LINE9_INDEPENDENT") - .setName("Independent Line from BUS4") - .setVoltageLevel1("VLGEN7") - .setNode1(11) - .setVoltageLevel2("VLGEN9") - .setNode2(2) - .setR(2.0) - .setX(25.0) - .setG1(0.0) - .setB1(300E-6 / 2) - .setG2(0.0) - .setB2(300E-6 / 2) - .add(); - Line line9 = network.getLine("LINE9_INDEPENDENT"); - line9.newExtension(ConnectablePositionAdder.class) - .newFeeder1() - .withName("LINE9_VLGEN7_Side") - .withOrder(3) - .withDirection(ConnectablePosition.Direction.BOTTOM) - .add() - .newFeeder2() - .withName("LINE9_VLGEN9_Side") - .withOrder(1) - .withDirection(ConnectablePosition.Direction.BOTTOM) - .add() - .add(); - - network.getVariantManager().setWorkingVariant(VariantManagerConstants.INITIAL_VARIANT_ID); - // Add new variant 2 network.getVariantManager().cloneVariant(VariantManagerConstants.INITIAL_VARIANT_ID, VARIANT_ID_2); network.getVariantManager().setWorkingVariant(VARIANT_ID_2); 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..b7579176 --- /dev/null +++ b/src/test/java/org/gridsuite/network/map/mapper/BusbarSectionFinderTraverserTest.java @@ -0,0 +1,419 @@ +package org.gridsuite.network.map.mapper; + +import com.powsybl.iidm.network.*; +import com.powsybl.iidm.network.extensions.*; +import com.powsybl.iidm.network.test.EurostagTutorialExample1Factory; +import com.powsybl.network.store.iidm.impl.NetworkFactoryImpl; +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.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * @author Slimane Amar + */ +@AutoConfigureMockMvc +@SpringBootTest +public class BusbarSectionFinderTraverserTest { + + private Network network; + + public static void createSwitch(VoltageLevel vl, String id, SwitchKind kind, boolean open, int node1, int node2) { + vl.getNodeBreakerView().newSwitch() + .setId(id) + .setName(id) + .setKind(kind) + .setRetained(kind.equals(SwitchKind.BREAKER)) + .setOpen(open) + .setFictitious(false) + .setNode1(node1) + .setNode2(node2) + .add(); + } + + @BeforeEach + void setUp() { + network = EurostagTutorialExample1Factory.createWithMoreGenerators(new NetworkFactoryImpl()); + /** VLGEN7 - Fork topology + * + * BUS1 ═══════X════════ BUS2 ═══════════════/════ BUS3 + * (node0) (node5) (node7) + * | | | + * Disconnector1 Disconnector2 | + * | | | + * | | Disconnector3 + * [open = true] [open = false] [open = false] + * | | | + * |____________________| ═══════════/════════ BUS4 + * | | + * | | + * fork point Disconnector4 + * (node8) [open = false] + * | | + * ┌───┴───┐ LINE9 + * | | (→VLGEN9) + * LINE7 LINE8 + * (→VLGEN4) (→VLGEN8) + */ + + Substation p4 = network.newSubstation() + .setId("P4") + .setCountry(Country.FR) + .setTso("RTE") + .setGeographicalTags("A") + .add(); + VoltageLevel vlgen4 = p4.newVoltageLevel() + .setId("VLGEN4") + .setNominalV(24.0) + .setTopologyKind(TopologyKind.NODE_BREAKER) + .add(); + BusbarSection bbs4 = vlgen4.getNodeBreakerView().newBusbarSection() + .setId("NGEN4") + .setName("NGEN4") + .setNode(0) + .add(); + bbs4.newExtension(MeasurementsAdder.class).add(); + Measurements bbs4Measurements = bbs4.getExtension(Measurements.class); + bbs4Measurements.newMeasurement().setType(Measurement.Type.VOLTAGE).setValid(true).setValue(385.).add(); + bbs4Measurements.newMeasurement().setType(Measurement.Type.ANGLE).setValid(false).setValue(0.5).add(); + + vlgen4.getNodeBreakerView() + .getBusbarSection("NGEN4") + .newExtension(BusbarSectionPositionAdder.class) + .withBusbarIndex(1) + .withSectionIndex(2) + .add(); + + VoltageLevel vlgen7 = network.newVoltageLevel() + .setId("VLGEN7") + .setName("Fork Distribution Point") + .setNominalV(24.0) + .setHighVoltageLimit(30.0) + .setLowVoltageLimit(20.0) + .setTopologyKind(TopologyKind.NODE_BREAKER) + .add(); + + // Create busbarSections + vlgen7.getNodeBreakerView().newBusbarSection() + .setId("BUS1_NGEN7") + .setName("Primary Busbar VLGEN7") + .setNode(0) + .add(); + + vlgen7.getNodeBreakerView().newBusbarSection() + .setId("BUS2_NGEN7") + .setName("Secondary Busbar VLGEN7") + .setNode(5) + .add(); + + vlgen7.getNodeBreakerView().newBusbarSection() + .setId("BUS3_NGEN7") + .setName("Third Busbar VLGEN7") + .setNode(9) + .add(); + vlgen7.getNodeBreakerView().newBusbarSection() + .setId("BUS4_NGEN7") + .setName("Fourth Busbar VLGEN7") + .setNode(13) + .add(); + + createSwitch(vlgen7, "SECT_BUS1", SwitchKind.DISCONNECTOR, true, 0, 6); // OPEN + createSwitch(vlgen7, "SECT_BUS2", SwitchKind.DISCONNECTOR, false, 5, 7); // CLOSED + createSwitch(vlgen7, "SECT_BUS3", SwitchKind.DISCONNECTOR, false, 9, 10); // CLOSED + createSwitch(vlgen7, "SECT_BUS4", SwitchKind.DISCONNECTOR, false, 13, 14); // CLOSED + + createSwitch(vlgen7, "FORK_SW1", SwitchKind.DISCONNECTOR, false, 6, 8); // BUS1 to fork (CLOSED via node 6) + createSwitch(vlgen7, "FORK_SW2", SwitchKind.DISCONNECTOR, false, 7, 8); // BUS2 to fork (CLOSED via node 7) + + // LINE7 connection from fork + createSwitch(vlgen7, "DISC_LINE7", SwitchKind.DISCONNECTOR, false, 8, 1); + createSwitch(vlgen7, "BRKR_LINE7", SwitchKind.BREAKER, false, 1, 2); + + // LINE8 connection from fork + createSwitch(vlgen7, "DISC_LINE8", SwitchKind.DISCONNECTOR, false, 8, 3); + createSwitch(vlgen7, "BRKR_LINE8", SwitchKind.BREAKER, false, 3, 4); + + // LINE9 connection from BUS4 (Fixed connection point) + createSwitch(vlgen7, "DISC_LINE9", SwitchKind.DISCONNECTOR, false, 14, 12); // Connect from node 14 + createSwitch(vlgen7, "BRKR_LINE9", SwitchKind.BREAKER, false, 12, 11); + + // Assuming VLGEN4 exists, create its switches + createSwitch(vlgen4, "DISC_VLGEN4", SwitchKind.DISCONNECTOR, false, 0, 10); + createSwitch(vlgen4, "BRKR_VLGEN4", SwitchKind.BREAKER, false, 10, 15); + + // LINE7 - Fork Branch 1 + network.newLine() + .setId("LINE7_FORK") + .setName("Fork Branch 1 - Primary Line") + .setVoltageLevel1("VLGEN4") + .setNode1(15) // Fixed node reference + .setVoltageLevel2("VLGEN7") + .setNode2(2) + .setR(3.0) + .setX(33.0) + .setG1(0.0) + .setB1(386E-6 / 2) + .setG2(0.0) + .setB2(386E-6 / 2) + .add(); + + Line line7 = network.getLine("LINE7_FORK"); + line7.newExtension(ConnectablePositionAdder.class) + .newFeeder1() + .withName("LINE7_VLGEN4_Side") + .withOrder(5) + .withDirection(ConnectablePosition.Direction.BOTTOM) + .add() + .newFeeder2() + .withName("LINE7_VLGEN7_Fork_Side") + .withOrder(1) + .withDirection(ConnectablePosition.Direction.TOP) + .add() + .add(); + + // VLGEN8 - Fork destination 2 + VoltageLevel vlgen8 = network.newVoltageLevel() + .setId("VLGEN8") + .setName("Fork Destination Point 2") + .setNominalV(24.0) + .setHighVoltageLimit(30.0) + .setLowVoltageLimit(20.0) + .setTopologyKind(TopologyKind.NODE_BREAKER) + .add(); + createSwitch(vlgen8, "DISC_VLGEN8", SwitchKind.DISCONNECTOR, false, 0, 1); + createSwitch(vlgen8, "BRKR_VLGEN8", SwitchKind.BREAKER, false, 1, 2); + + // LINE8 - Fork Branch 2 + network.newLine() + .setId("LINE8_FORK") + .setName("Fork Branch 2 - Secondary Line") + .setVoltageLevel1("VLGEN7") + .setNode1(4) + .setVoltageLevel2("VLGEN8") + .setNode2(2) + .setR(2.5) + .setX(28.0) + .setG1(0.0) + .setB1(320E-6 / 2) + .setG2(0.0) + .setB2(320E-6 / 2) + .add(); + Line line8 = network.getLine("LINE8_FORK"); + line8.newExtension(ConnectablePositionAdder.class) + .newFeeder1() + .withName("LINE8_VLGEN7_Fork_Side") + .withOrder(2) + .withDirection(ConnectablePosition.Direction.BOTTOM) + .add() + .newFeeder2() + .withName("LINE8_VLGEN8_Side") + .withOrder(1) + .withDirection(ConnectablePosition.Direction.TOP) + .add() + .add(); + +// VLGEN9 - Independent line destination + VoltageLevel vlgen9 = network.newVoltageLevel() + .setId("VLGEN9") + .setName("Independent Line Destination") + .setNominalV(24.0) + .setHighVoltageLimit(30.0) + .setLowVoltageLimit(20.0) + .setTopologyKind(TopologyKind.NODE_BREAKER) + .add(); + + vlgen9.getNodeBreakerView().newBusbarSection() + .setId("BUS_NGEN9") + .setName("Main Busbar VLGEN9") + .setNode(0) + .add(); + + createSwitch(vlgen9, "DISC_VLGEN9", SwitchKind.DISCONNECTOR, false, 0, 1); + createSwitch(vlgen9, "BRKR_VLGEN9", SwitchKind.BREAKER, false, 1, 2); + + // LINE9 - Independent line from BUS4 + network.newLine() + .setId("LINE9_INDEPENDENT") + .setName("Independent Line from BUS4") + .setVoltageLevel1("VLGEN7") + .setNode1(11) + .setVoltageLevel2("VLGEN9") + .setNode2(2) + .setR(2.0) + .setX(25.0) + .setG1(0.0) + .setB1(300E-6 / 2) + .setG2(0.0) + .setB2(300E-6 / 2) + .add(); + Line line9 = network.getLine("LINE9_INDEPENDENT"); + line9.newExtension(ConnectablePositionAdder.class) + .newFeeder1() + .withName("LINE9_VLGEN7_Side") + .withOrder(3) + .withDirection(ConnectablePosition.Direction.BOTTOM) + .add() + .newFeeder2() + .withName("LINE9_VLGEN9_Side") + .withOrder(1) + .withDirection(ConnectablePosition.Direction.BOTTOM) + .add() + .add(); + + network.getVariantManager().setWorkingVariant(VariantManagerConstants.INITIAL_VARIANT_ID); + } + + @Test + void testLine7FindsBus2ViaFork() { + Line line7 = network.getLine("LINE7_FORK"); + Terminal terminal = line7.getTerminal2(); // VLGEN7 side + BusbarSectionFinderTraverser.BusbarResult result = BusbarSectionFinderTraverser.findBestBusbar(terminal); + assertNotNull(result); + assertEquals("BUS2_NGEN7", result.busbarId()); + assertEquals(4, result.depth()); + assertNotNull(result.lastSwitch()); + assertEquals("SECT_BUS2", result.lastSwitch().id()); + assertFalse(result.lastSwitch().isOpen()); + } + + @Test + void testLine8FindsBus2ViaFork() { + Line line8 = network.getLine("LINE8_FORK"); + Terminal terminal = line8.getTerminal1(); // VLGEN7 side + BusbarSectionFinderTraverser.BusbarResult result = BusbarSectionFinderTraverser.findBestBusbar(terminal); + assertNotNull(result); + assertEquals("BUS2_NGEN7", result.busbarId()); + assertEquals(4, result.depth()); + assertNotNull(result.lastSwitch()); + assertEquals("SECT_BUS2", result.lastSwitch().id()); + assertFalse(result.lastSwitch().isOpen()); + } + + @Test + void testLine9FindsBus4DirectPath() { + Line line9 = network.getLine("LINE9_INDEPENDENT"); + Terminal terminal = line9.getTerminal1(); // VLGEN7 side + BusbarSectionFinderTraverser.BusbarResult result = BusbarSectionFinderTraverser.findBestBusbar(terminal); + assertNotNull(result); + assertEquals("BUS4_NGEN7", result.busbarId()); + assertEquals(3, result.depth()); + assertNotNull(result.lastSwitch()); + assertEquals("SECT_BUS4", result.lastSwitch().id()); + assertFalse(result.lastSwitch().isOpen()); + } + + @Test + void testBus1AccessibleThroughOpenSwitch() { + Line line7 = network.getLine("LINE7_FORK"); + Terminal terminal = line7.getTerminal2(); + BusbarSectionFinderTraverser.BusbarResult result = BusbarSectionFinderTraverser.findBestBusbar(terminal); + // Should prefer BUS2 (closed) over BUS1 (open) + assertNotNull(result); + assertEquals("BUS2_NGEN7", result.busbarId()); + } + + @Test + void testFindsBus1WhenBus2SwitchOpen() { + VoltageLevel vlgen7 = network.getVoltageLevel("VLGEN7"); + Switch sectBus2 = vlgen7.getNodeBreakerView().getSwitch("SECT_BUS2"); + sectBus2.setOpen(true); // Open the switch to BUS2 + Line line7 = network.getLine("LINE7_FORK"); + Terminal terminal = line7.getTerminal2(); + BusbarSectionFinderTraverser.BusbarResult result = BusbarSectionFinderTraverser.findBestBusbar(terminal); + assertNotNull(result); + assertEquals("BUS1_NGEN7", result.busbarId()); + assertEquals(4, result.depth()); + assertNotNull(result.lastSwitch()); + assertTrue(result.lastSwitch().isOpen()); + } + + @Test + void testBus3AccessibleWhenBus4Open() { + VoltageLevel vlgen7 = network.getVoltageLevel("VLGEN7"); + Switch sectBus4 = vlgen7.getNodeBreakerView().getSwitch("SECT_BUS4"); + sectBus4.setOpen(true); // Open BUS4 connection + Line line9 = network.getLine("LINE9_INDEPENDENT"); + Terminal terminal = line9.getTerminal1(); + BusbarSectionFinderTraverser.BusbarResult result = BusbarSectionFinderTraverser.findBestBusbar(terminal); + assertNotNull(result); + assertEquals("BUS4_NGEN7", result.busbarId()); + } + + @Test + void testFindBusbarIdConvenienceMethod() { + Line line7 = network.getLine("LINE7_FORK"); + Terminal terminal = line7.getTerminal2(); + String busbarId = BusbarSectionFinderTraverser.findBusbarId(terminal); + assertNotNull(busbarId); + assertEquals("BUS2_NGEN7", busbarId); + } + + @Test + void testReturnsNullWhenNoBusbarAccessible() { + VoltageLevel vlgen7 = network.getVoltageLevel("VLGEN7"); + // Open all switches connecting busbars + vlgen7.getNodeBreakerView().getSwitch("SECT_BUS1").setOpen(true); + vlgen7.getNodeBreakerView().getSwitch("SECT_BUS2").setOpen(true); + vlgen7.getNodeBreakerView().getSwitch("SECT_BUS3").setOpen(true); + vlgen7.getNodeBreakerView().getSwitch("SECT_BUS4").setOpen(true); + vlgen7.getNodeBreakerView().getSwitch("FORK_SW1").setOpen(true); + vlgen7.getNodeBreakerView().getSwitch("FORK_SW2").setOpen(true); + Line line7 = network.getLine("LINE7_FORK"); + Terminal terminal = line7.getTerminal2(); + BusbarSectionFinderTraverser.BusbarResult result = BusbarSectionFinderTraverser.findBestBusbar(terminal); + assertNotNull(result); + assertEquals("BUS1_NGEN7", result.busbarId()); + assertEquals(4, result.depth()); + assertEquals(3, result.switchesBeforeLast()); + assertNotNull(result.lastSwitch()); + assertEquals("SECT_BUS1", result.lastSwitch().id()); + assertEquals(SwitchKind.DISCONNECTOR, result.lastSwitch().kind()); + assertTrue(result.lastSwitch().isOpen()); + assertEquals(0, result.lastSwitch().node1()); + assertEquals(6, result.lastSwitch().node2()); + } + + @Test + void testPrefersShortestClosedPath() { + Line line7 = network.getLine("LINE7_FORK"); + Terminal terminal = line7.getTerminal2(); + BusbarSectionFinderTraverser.BusbarResult result = BusbarSectionFinderTraverser.findBestBusbar(terminal); + // BUS2 should be preferred (3 switches) over potential longer paths + assertNotNull(result); + assertEquals("BUS2_NGEN7", result.busbarId()); + assertEquals(4, result.depth()); + } + + @Test + void testForkTopologySharesBusbar() { + Line line7 = network.getLine("LINE7_FORK"); + Line line8 = network.getLine("LINE8_FORK"); + String busbar7 = BusbarSectionFinderTraverser.findBusbarId(line7.getTerminal2()); + String busbar8 = BusbarSectionFinderTraverser.findBusbarId(line8.getTerminal1()); + assertEquals(busbar7, busbar8); + assertEquals("BUS2_NGEN7", busbar7); + } + + @Test + void testSwitchesBeforeLastCount() { + Line line7 = network.getLine("LINE7_FORK"); + Terminal terminal = line7.getTerminal2(); + BusbarSectionFinderTraverser.BusbarResult result = BusbarSectionFinderTraverser.findBestBusbar(terminal); + assertNotNull(result); + assertEquals(4, result.depth()); + assertEquals(3, result.switchesBeforeLast()); + } + + @Test + void testHandlesMixedSwitchTypes() { + Line line7 = network.getLine("LINE7_FORK"); + Terminal terminal = line7.getTerminal2(); + BusbarSectionFinderTraverser.BusbarResult result = BusbarSectionFinderTraverser.findBestBusbar(terminal); + assertNotNull(result); + // Path contains both breakers and disconnectors + assertTrue(result.depth() > 0); + } +} From b03d9153ceacbbae955f333c638db0bc09231337 Mon Sep 17 00:00:00 2001 From: Rehili Ghazwa Date: Tue, 30 Sep 2025 10:31:24 +0200 Subject: [PATCH 07/15] unused import --- .../java/org/gridsuite/network/map/dto/utils/ElementUtils.java | 1 - 1 file changed, 1 deletion(-) 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 9bac9bff..069da7c4 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; From a1482d3a0ce56f5a7272bba0c482644a08f950d2 Mon Sep 17 00:00:00 2001 From: Rehili Ghazwa Date: Tue, 30 Sep 2025 10:57:02 +0200 Subject: [PATCH 08/15] fix sonar issues --- .../BusbarSectionFinderTraverser.java | 131 ++++++++++-------- .../network/map/dto/utils/ElementUtils.java | 2 +- .../BusbarSectionFinderTraverserTest.java | 46 +++--- 3 files changed, 100 insertions(+), 79 deletions(-) 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 5c03f27d..0b16b1a5 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,16 +6,14 @@ */ package org.gridsuite.network.map.dto.definition.extension; -import com.powsybl.iidm.network.IdentifiableType; -import com.powsybl.iidm.network.SwitchKind; -import com.powsybl.iidm.network.Terminal; -import com.powsybl.iidm.network.VoltageLevel; +import com.powsybl.iidm.network.*; import java.util.*; /** * @author Slimane Amar */ +// TODO : to remove when this class is available in network-store public final class BusbarSectionFinderTraverser { /** @@ -32,10 +30,10 @@ private BusbarSectionFinderTraverser() { * @param terminal the starting terminal * @return the best busbar result according to selection criteria, or null if none found */ - public static BusbarResult findBestBusbar(Terminal terminal) { + public static BusbarSectionResult findBestBusbar(Terminal terminal) { VoltageLevel.NodeBreakerView view = terminal.getVoltageLevel().getNodeBreakerView(); int startNode = terminal.getNodeBreakerView().getNode(); - List allResults = searchAllBusbars(view, startNode); + List allResults = searchAllBusbars(view, startNode); if (allResults.isEmpty()) { return null; } @@ -51,27 +49,23 @@ public static BusbarResult findBestBusbar(Terminal terminal) { * @param results list of all found busbar results * @return the best busbar according to selection criteria */ - private static BusbarResult selectBestBusbar(List results) { + private static BusbarSectionResult selectBestBusbar(List results) { // Priority 1: Search for busbar with closed last switch - List withClosedSwitch = results.stream().filter(r -> r.lastSwitch() != null && !r.lastSwitch().isOpen()).toList(); + List withClosedSwitch = results.stream().filter(r -> r.lastSwitch() != null && !r.lastSwitch().isOpen()).toList(); if (!withClosedSwitch.isEmpty()) { - return withClosedSwitch.stream().min(Comparator.comparingInt(BusbarResult::depth) - .thenComparingInt(BusbarResult::switchesBeforeLast)) - .get(); + return withClosedSwitch.stream().min(Comparator.comparingInt(BusbarSectionResult::depth).thenComparingInt(BusbarSectionResult::switchesBeforeLast)).orElse(null); } // Priority 2: Search for busbar with open last switch - List withOpenSwitch = results.stream().filter(r -> r.lastSwitch() != null && r.lastSwitch().isOpen()).toList(); + List withOpenSwitch = results.stream().filter(r -> r.lastSwitch() != null && r.lastSwitch().isOpen()).toList(); if (!withOpenSwitch.isEmpty()) { - return withOpenSwitch.stream().min(Comparator.comparingInt(BusbarResult::depth) - .thenComparingInt(BusbarResult::switchesBeforeLast)) - .get(); + return withOpenSwitch.stream().min(Comparator.comparingInt(BusbarSectionResult::depth).thenComparingInt(BusbarSectionResult::switchesBeforeLast)).orElse(null); } - // Priority 3: Busbars without switch (direct connection) - List withoutSwitch = results.stream().filter(r -> r.lastSwitch() == null).toList(); + // Priority 3: Busbars without switch direct connection + List withoutSwitch = results.stream().filter(r -> r.lastSwitch() == null).toList(); if (!withoutSwitch.isEmpty()) { - return withoutSwitch.stream().min(Comparator.comparingInt(BusbarResult::depth)).get(); + return withoutSwitch.stream().min(Comparator.comparingInt(BusbarSectionResult::depth)).orElse(null); } // Fallback: select first busbar @@ -86,50 +80,77 @@ private static BusbarResult selectBestBusbar(List results) { * @param startNode the starting node index * @return list of all busbar results found */ - private static List searchAllBusbars(VoltageLevel.NodeBreakerView view, int startNode) { - List results = new ArrayList<>(); + private static List searchAllBusbars(VoltageLevel.NodeBreakerView view, int startNode) { + List results = new ArrayList<>(); Set visited = new HashSet<>(); Queue queue = new LinkedList<>(); queue.offer(new NodePath(startNode, new ArrayList<>(), null)); while (!queue.isEmpty()) { - NodePath current = queue.poll(); - if (visited.contains(current.node())) { + NodePath currentNodePath = queue.poll(); + if (!hasNotBeenVisited(currentNodePath.node(), visited)) { continue; } - visited.add(current.node()); - // Check if current node is a busbar section - Optional nodeTerminal = view.getOptionalTerminal(current.node()); - if (nodeTerminal.isPresent()) { - Terminal term = nodeTerminal.get(); - if (term.getConnectable().getType() == IdentifiableType.BUSBAR_SECTION) { - String busbarId = term.getConnectable().getId(); - int depth = current.pathSwitches().size(); - SwitchInfo lastSwitch = current.lastSwitch(); - // Calculate number of switches BEFORE the last one - int switchesBeforeLast = lastSwitch != null ? (depth - 1) : 0; - results.add(new BusbarResult(busbarId, depth, switchesBeforeLast, lastSwitch)); - continue; // Don't explore beyond busbar - } + visited.add(currentNodePath.node()); + Optional busbarSectionResult = tryCreateBusbarResult(view, currentNodePath); + if (busbarSectionResult.isPresent()) { + results.add(busbarSectionResult.get()); + } else { + exploreAdjacentNodes(view, currentNodePath, visited, queue); } - - // Explore adjacent nodes through switches - view.getSwitchStream().forEach(sw -> { - int node1 = view.getNode1(sw.getId()); - int node2 = view.getNode2(sw.getId()); - if (node1 == current.node() || node2 == current.node()) { - int nextNode = (node1 == current.node()) ? node2 : node1; - if (!visited.contains(nextNode)) { - List newPathSwitches = new ArrayList<>(current.pathSwitches()); - SwitchInfo switchInfo = new SwitchInfo(sw.getId(), sw.getKind(), sw.isOpen(), node1, node2); - newPathSwitches.add(switchInfo); - queue.offer(new NodePath(nextNode, newPathSwitches, switchInfo)); - } - } - }); } return results; } + private static boolean hasNotBeenVisited(int node, Set visited) { + return !visited.contains(node); + } + + private static Optional tryCreateBusbarResult(VoltageLevel.NodeBreakerView view, NodePath currentNodePath) { + Optional nodeTerminal = view.getOptionalTerminal(currentNodePath.node()); + if (nodeTerminal.isEmpty()) { + return Optional.empty(); + } + Terminal term = nodeTerminal.get(); + // Check if current node is a busbar section + if (term.getConnectable().getType() == IdentifiableType.BUSBAR_SECTION) { + String busbarSectionId = term.getConnectable().getId(); + int depth = currentNodePath.pathSwitches().size(); + SwitchInfo lastSwitch = currentNodePath.lastSwitch(); + int switchesBeforeLast = lastSwitch != null ? (depth - 1) : 0; + return Optional.of(new BusbarSectionResult(busbarSectionId, depth, switchesBeforeLast, lastSwitch)); + } + return Optional.empty(); + } + + private static void exploreAdjacentNodes(VoltageLevel.NodeBreakerView view, NodePath currentNodePath, Set visited, Queue queue) { + view.getSwitchStream().forEach(sw -> { + int node1 = view.getNode1(sw.getId()); + int node2 = view.getNode2(sw.getId()); + Optional nextNode = getNextNodeIfAdjacent(currentNodePath.node(), node1, node2); + if (nextNode.isPresent() && !visited.contains(nextNode.get())) { + NodePath newPath = createNodePath(currentNodePath, sw, node1, node2, nextNode.get()); + queue.offer(newPath); + } + }); + } + + private static Optional getNextNodeIfAdjacent(int currentNode, int node1, int node2) { + if (node1 == currentNode) { + return Optional.of(node2); + } + if (node2 == currentNode) { + return Optional.of(node1); + } + return Optional.empty(); + } + + private static NodePath createNodePath(NodePath currentNodePath, Switch sw, int node1, int node2, int nextNode) { + List newPathSwitches = new ArrayList<>(currentNodePath.pathSwitches()); + SwitchInfo switchInfo = new SwitchInfo(sw.getId(), sw.getKind(), sw.isOpen(), node1, node2); + newPathSwitches.add(switchInfo); + return new NodePath(nextNode, newPathSwitches, switchInfo); + } + /** * Internal record to track the path during graph traversal. */ @@ -143,7 +164,7 @@ public record SwitchInfo(String id, SwitchKind kind, boolean isOpen, int node1, /** * Record containing the result of a busbar search with selection metadata. */ - public record BusbarResult(String busbarId, int depth, int switchesBeforeLast, SwitchInfo lastSwitch) { } + public record BusbarSectionResult(String busbarSectionId, int depth, int switchesBeforeLast, SwitchInfo lastSwitch) { } /** * Convenience method to get only the busbar ID. @@ -151,8 +172,8 @@ public record BusbarResult(String busbarId, int depth, int switchesBeforeLast, S * @param terminal the starting terminal * @return the busbar ID or null if none found */ - public static String findBusbarId(Terminal terminal) { - BusbarResult result = findBestBusbar(terminal); - return result != null ? result.busbarId() : null; + public static String findBusbarSectionId(Terminal terminal) { + BusbarSectionResult result = findBestBusbar(terminal); + return result != null ? result.busbarSectionId() : null; } } 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 069da7c4..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 @@ -92,7 +92,7 @@ public static String getBusOrBusbarSection(Terminal terminal) { } } else { // NODE_BREAKER: explore all paths and choose the busbar with the closed disconnector - return BusbarSectionFinderTraverser.findBusbarId(terminal); + return BusbarSectionFinderTraverser.findBusbarSectionId(terminal); } } diff --git a/src/test/java/org/gridsuite/network/map/mapper/BusbarSectionFinderTraverserTest.java b/src/test/java/org/gridsuite/network/map/mapper/BusbarSectionFinderTraverserTest.java index b7579176..e2f348a8 100644 --- a/src/test/java/org/gridsuite/network/map/mapper/BusbarSectionFinderTraverserTest.java +++ b/src/test/java/org/gridsuite/network/map/mapper/BusbarSectionFinderTraverserTest.java @@ -215,7 +215,7 @@ void setUp() { .add() .add(); -// VLGEN9 - Independent line destination + // VLGEN9 - Independent line destination VoltageLevel vlgen9 = network.newVoltageLevel() .setId("VLGEN9") .setName("Independent Line Destination") @@ -270,9 +270,9 @@ void setUp() { void testLine7FindsBus2ViaFork() { Line line7 = network.getLine("LINE7_FORK"); Terminal terminal = line7.getTerminal2(); // VLGEN7 side - BusbarSectionFinderTraverser.BusbarResult result = BusbarSectionFinderTraverser.findBestBusbar(terminal); + BusbarSectionFinderTraverser.BusbarSectionResult result = BusbarSectionFinderTraverser.findBestBusbar(terminal); assertNotNull(result); - assertEquals("BUS2_NGEN7", result.busbarId()); + assertEquals("BUS2_NGEN7", result.busbarSectionId()); assertEquals(4, result.depth()); assertNotNull(result.lastSwitch()); assertEquals("SECT_BUS2", result.lastSwitch().id()); @@ -283,9 +283,9 @@ void testLine7FindsBus2ViaFork() { void testLine8FindsBus2ViaFork() { Line line8 = network.getLine("LINE8_FORK"); Terminal terminal = line8.getTerminal1(); // VLGEN7 side - BusbarSectionFinderTraverser.BusbarResult result = BusbarSectionFinderTraverser.findBestBusbar(terminal); + BusbarSectionFinderTraverser.BusbarSectionResult result = BusbarSectionFinderTraverser.findBestBusbar(terminal); assertNotNull(result); - assertEquals("BUS2_NGEN7", result.busbarId()); + assertEquals("BUS2_NGEN7", result.busbarSectionId()); assertEquals(4, result.depth()); assertNotNull(result.lastSwitch()); assertEquals("SECT_BUS2", result.lastSwitch().id()); @@ -296,9 +296,9 @@ void testLine8FindsBus2ViaFork() { void testLine9FindsBus4DirectPath() { Line line9 = network.getLine("LINE9_INDEPENDENT"); Terminal terminal = line9.getTerminal1(); // VLGEN7 side - BusbarSectionFinderTraverser.BusbarResult result = BusbarSectionFinderTraverser.findBestBusbar(terminal); + BusbarSectionFinderTraverser.BusbarSectionResult result = BusbarSectionFinderTraverser.findBestBusbar(terminal); assertNotNull(result); - assertEquals("BUS4_NGEN7", result.busbarId()); + assertEquals("BUS4_NGEN7", result.busbarSectionId()); assertEquals(3, result.depth()); assertNotNull(result.lastSwitch()); assertEquals("SECT_BUS4", result.lastSwitch().id()); @@ -309,10 +309,10 @@ void testLine9FindsBus4DirectPath() { void testBus1AccessibleThroughOpenSwitch() { Line line7 = network.getLine("LINE7_FORK"); Terminal terminal = line7.getTerminal2(); - BusbarSectionFinderTraverser.BusbarResult result = BusbarSectionFinderTraverser.findBestBusbar(terminal); + BusbarSectionFinderTraverser.BusbarSectionResult result = BusbarSectionFinderTraverser.findBestBusbar(terminal); // Should prefer BUS2 (closed) over BUS1 (open) assertNotNull(result); - assertEquals("BUS2_NGEN7", result.busbarId()); + assertEquals("BUS2_NGEN7", result.busbarSectionId()); } @Test @@ -322,9 +322,9 @@ void testFindsBus1WhenBus2SwitchOpen() { sectBus2.setOpen(true); // Open the switch to BUS2 Line line7 = network.getLine("LINE7_FORK"); Terminal terminal = line7.getTerminal2(); - BusbarSectionFinderTraverser.BusbarResult result = BusbarSectionFinderTraverser.findBestBusbar(terminal); + BusbarSectionFinderTraverser.BusbarSectionResult result = BusbarSectionFinderTraverser.findBestBusbar(terminal); assertNotNull(result); - assertEquals("BUS1_NGEN7", result.busbarId()); + assertEquals("BUS1_NGEN7", result.busbarSectionId()); assertEquals(4, result.depth()); assertNotNull(result.lastSwitch()); assertTrue(result.lastSwitch().isOpen()); @@ -337,16 +337,16 @@ void testBus3AccessibleWhenBus4Open() { sectBus4.setOpen(true); // Open BUS4 connection Line line9 = network.getLine("LINE9_INDEPENDENT"); Terminal terminal = line9.getTerminal1(); - BusbarSectionFinderTraverser.BusbarResult result = BusbarSectionFinderTraverser.findBestBusbar(terminal); + BusbarSectionFinderTraverser.BusbarSectionResult result = BusbarSectionFinderTraverser.findBestBusbar(terminal); assertNotNull(result); - assertEquals("BUS4_NGEN7", result.busbarId()); + assertEquals("BUS4_NGEN7", result.busbarSectionId()); } @Test - void testFindBusbarIdConvenienceMethod() { + void testFindBusbarSectionIdConvenienceMethod() { Line line7 = network.getLine("LINE7_FORK"); Terminal terminal = line7.getTerminal2(); - String busbarId = BusbarSectionFinderTraverser.findBusbarId(terminal); + String busbarId = BusbarSectionFinderTraverser.findBusbarSectionId(terminal); assertNotNull(busbarId); assertEquals("BUS2_NGEN7", busbarId); } @@ -363,9 +363,9 @@ void testReturnsNullWhenNoBusbarAccessible() { vlgen7.getNodeBreakerView().getSwitch("FORK_SW2").setOpen(true); Line line7 = network.getLine("LINE7_FORK"); Terminal terminal = line7.getTerminal2(); - BusbarSectionFinderTraverser.BusbarResult result = BusbarSectionFinderTraverser.findBestBusbar(terminal); + BusbarSectionFinderTraverser.BusbarSectionResult result = BusbarSectionFinderTraverser.findBestBusbar(terminal); assertNotNull(result); - assertEquals("BUS1_NGEN7", result.busbarId()); + assertEquals("BUS1_NGEN7", result.busbarSectionId()); assertEquals(4, result.depth()); assertEquals(3, result.switchesBeforeLast()); assertNotNull(result.lastSwitch()); @@ -380,10 +380,10 @@ void testReturnsNullWhenNoBusbarAccessible() { void testPrefersShortestClosedPath() { Line line7 = network.getLine("LINE7_FORK"); Terminal terminal = line7.getTerminal2(); - BusbarSectionFinderTraverser.BusbarResult result = BusbarSectionFinderTraverser.findBestBusbar(terminal); + BusbarSectionFinderTraverser.BusbarSectionResult result = BusbarSectionFinderTraverser.findBestBusbar(terminal); // BUS2 should be preferred (3 switches) over potential longer paths assertNotNull(result); - assertEquals("BUS2_NGEN7", result.busbarId()); + assertEquals("BUS2_NGEN7", result.busbarSectionId()); assertEquals(4, result.depth()); } @@ -391,8 +391,8 @@ void testPrefersShortestClosedPath() { void testForkTopologySharesBusbar() { Line line7 = network.getLine("LINE7_FORK"); Line line8 = network.getLine("LINE8_FORK"); - String busbar7 = BusbarSectionFinderTraverser.findBusbarId(line7.getTerminal2()); - String busbar8 = BusbarSectionFinderTraverser.findBusbarId(line8.getTerminal1()); + String busbar7 = BusbarSectionFinderTraverser.findBusbarSectionId(line7.getTerminal2()); + String busbar8 = BusbarSectionFinderTraverser.findBusbarSectionId(line8.getTerminal1()); assertEquals(busbar7, busbar8); assertEquals("BUS2_NGEN7", busbar7); } @@ -401,7 +401,7 @@ void testForkTopologySharesBusbar() { void testSwitchesBeforeLastCount() { Line line7 = network.getLine("LINE7_FORK"); Terminal terminal = line7.getTerminal2(); - BusbarSectionFinderTraverser.BusbarResult result = BusbarSectionFinderTraverser.findBestBusbar(terminal); + BusbarSectionFinderTraverser.BusbarSectionResult result = BusbarSectionFinderTraverser.findBestBusbar(terminal); assertNotNull(result); assertEquals(4, result.depth()); assertEquals(3, result.switchesBeforeLast()); @@ -411,7 +411,7 @@ void testSwitchesBeforeLastCount() { void testHandlesMixedSwitchTypes() { Line line7 = network.getLine("LINE7_FORK"); Terminal terminal = line7.getTerminal2(); - BusbarSectionFinderTraverser.BusbarResult result = BusbarSectionFinderTraverser.findBestBusbar(terminal); + BusbarSectionFinderTraverser.BusbarSectionResult result = BusbarSectionFinderTraverser.findBestBusbar(terminal); assertNotNull(result); // Path contains both breakers and disconnectors assertTrue(result.depth() > 0); From 6bcd0d2201721a25092b6cabfdee5a6791d0e4f9 Mon Sep 17 00:00:00 2001 From: Rehili Ghazwa Date: Tue, 30 Sep 2025 13:49:37 +0200 Subject: [PATCH 09/15] enhance TU to add bypass topo --- .../BusbarSectionFinderTraverserTest.java | 118 +++++++++++++----- 1 file changed, 87 insertions(+), 31 deletions(-) diff --git a/src/test/java/org/gridsuite/network/map/mapper/BusbarSectionFinderTraverserTest.java b/src/test/java/org/gridsuite/network/map/mapper/BusbarSectionFinderTraverserTest.java index e2f348a8..8f054c20 100644 --- a/src/test/java/org/gridsuite/network/map/mapper/BusbarSectionFinderTraverserTest.java +++ b/src/test/java/org/gridsuite/network/map/mapper/BusbarSectionFinderTraverserTest.java @@ -7,7 +7,6 @@ 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.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import static org.junit.jupiter.api.Assertions.*; @@ -15,7 +14,7 @@ /** * @author Slimane Amar */ -@AutoConfigureMockMvc + @SpringBootTest public class BusbarSectionFinderTraverserTest { @@ -37,8 +36,8 @@ public static void createSwitch(VoltageLevel vl, String id, SwitchKind kind, boo @BeforeEach void setUp() { network = EurostagTutorialExample1Factory.createWithMoreGenerators(new NetworkFactoryImpl()); - /** VLGEN7 - Fork topology - * + /* + * VLGEN7 - Fork topology with bypass * BUS1 ═══════X════════ BUS2 ═══════════════/════ BUS3 * (node0) (node5) (node7) * | | | @@ -53,12 +52,49 @@ void setUp() { * fork point Disconnector4 * (node8) [open = false] * | | - * ┌───┴───┐ LINE9 - * | | (→VLGEN9) - * LINE7 LINE8 - * (→VLGEN4) (→VLGEN8) + * ┌───┴───┐ ┌──────┴──────┐ + * | | | | + * LINE7 LINE8 Breaker10 Disconnector7 + * (→VLGEN4) (→VLGEN8) [open = true] [open = false] + * | | + * Disconnector5 | + * [open = false] | + * | | + * |_____________| + * | + * bypass point + * (node11) + * | + * Disconnector6 + * [open = false] + * | + * LINE9 + * (→VLGEN9) + * + * TOPOLOGY SUMMARY: + * ================ + * + * VLGEN7 Node Mapping: + * - Node 0: BUS1 (Primary Busbar) + * - Node 5: BUS2 (Secondary Busbar) + * - Node 9: BUS3 (Third Busbar) + * - Node 13: BUS4 (Fourth Busbar) + * - Node 8: Fork point (connects BUS1 & BUS2 to LINE7 & LINE8) + * - Node 11: Bypass point (convergence for breaker and bypass paths to LINE9) + * + * Fork Configuration: + * - BUS1 is DISCONNECTED (SECT_BUS1 open) + * - BUS2 feeds the fork through SECT_BUS2 (closed) → FORK_SW2 (closed) + * - Fork splits to LINE7 and LINE8 + * + * Bypass Configuration: + * - BUS4 connects to LINE9 via two parallel paths: + * Path 1: BRKR10 (OPEN) → DISC5 → Node 11 + * Path 2: DISC7 (CLOSED) → DISC_BYPASS_CONV → Node 11 (Bypass active) + * - With BRKR10 open and DISC7 closed, LINE9 operates via bypass */ + // ============ VLGEN4 - Source voltage level ============ Substation p4 = network.newSubstation() .setId("P4") .setCountry(Country.FR) @@ -86,7 +122,10 @@ void setUp() { .withBusbarIndex(1) .withSectionIndex(2) .add(); + createSwitch(vlgen4, "DISC_VLGEN4", SwitchKind.DISCONNECTOR, false, 0, 10); + createSwitch(vlgen4, "BRKR_VLGEN4", SwitchKind.BREAKER, false, 10, 15); + // ============ VLGEN7 - Main voltage level with fork and bypass topology ============ VoltageLevel vlgen7 = network.newVoltageLevel() .setId("VLGEN7") .setName("Fork Distribution Point") @@ -96,7 +135,7 @@ void setUp() { .setTopologyKind(TopologyKind.NODE_BREAKER) .add(); - // Create busbarSections + // Create 4 busbars vlgen7.getNodeBreakerView().newBusbarSection() .setId("BUS1_NGEN7") .setName("Primary Busbar VLGEN7") @@ -120,36 +159,47 @@ void setUp() { .setNode(13) .add(); - createSwitch(vlgen7, "SECT_BUS1", SwitchKind.DISCONNECTOR, true, 0, 6); // OPEN - createSwitch(vlgen7, "SECT_BUS2", SwitchKind.DISCONNECTOR, false, 5, 7); // CLOSED - createSwitch(vlgen7, "SECT_BUS3", SwitchKind.DISCONNECTOR, false, 9, 10); // CLOSED - createSwitch(vlgen7, "SECT_BUS4", SwitchKind.DISCONNECTOR, false, 13, 14); // CLOSED + // Bus coupling disconnectors (SECT) + createSwitch(vlgen7, "SECT_BUS1", SwitchKind.DISCONNECTOR, true, 0, 6); // OPEN - BUS1 disconnected + createSwitch(vlgen7, "SECT_BUS2", SwitchKind.DISCONNECTOR, false, 5, 7); // CLOSED - BUS2 connected + createSwitch(vlgen7, "SECT_BUS3", SwitchKind.DISCONNECTOR, false, 9, 10); // CLOSED - BUS3 connected + createSwitch(vlgen7, "SECT_BUS4", SwitchKind.DISCONNECTOR, false, 13, 14); // CLOSED - BUS4 connected - createSwitch(vlgen7, "FORK_SW1", SwitchKind.DISCONNECTOR, false, 6, 8); // BUS1 to fork (CLOSED via node 6) - createSwitch(vlgen7, "FORK_SW2", SwitchKind.DISCONNECTOR, false, 7, 8); // BUS2 to fork (CLOSED via node 7) + // Fork connections - BUS1 and BUS2 to fork point (node 8) + createSwitch(vlgen7, "FORK_SW1", SwitchKind.DISCONNECTOR, false, 6, 8); // BUS1 to fork + createSwitch(vlgen7, "FORK_SW2", SwitchKind.DISCONNECTOR, false, 7, 8); // BUS2 to fork - // LINE7 connection from fork + // LINE7 connection from fork point (node 8) createSwitch(vlgen7, "DISC_LINE7", SwitchKind.DISCONNECTOR, false, 8, 1); createSwitch(vlgen7, "BRKR_LINE7", SwitchKind.BREAKER, false, 1, 2); - // LINE8 connection from fork + // LINE8 connection from fork point (node 8) createSwitch(vlgen7, "DISC_LINE8", SwitchKind.DISCONNECTOR, false, 8, 3); createSwitch(vlgen7, "BRKR_LINE8", SwitchKind.BREAKER, false, 3, 4); - // LINE9 connection from BUS4 (Fixed connection point) - createSwitch(vlgen7, "DISC_LINE9", SwitchKind.DISCONNECTOR, false, 14, 12); // Connect from node 14 - createSwitch(vlgen7, "BRKR_LINE9", SwitchKind.BREAKER, false, 12, 11); + // ============ BYPASS TOPOLOGY - BUS4 with bypass for LINE9 ============ + // BUS3 to BUS4 coupler section (already created above: SECT_BUS3) - // Assuming VLGEN4 exists, create its switches - createSwitch(vlgen4, "DISC_VLGEN4", SwitchKind.DISCONNECTOR, false, 0, 10); - createSwitch(vlgen4, "BRKR_VLGEN4", SwitchKind.BREAKER, false, 10, 15); + // Path 1: Breaker10 (OPEN) - Main breaker path + createSwitch(vlgen7, "BRKR10", SwitchKind.BREAKER, true, 14, 15); // OPEN - Main breaker + createSwitch(vlgen7, "DISC5", SwitchKind.DISCONNECTOR, false, 15, 11); // CLOSED - After breaker + + // Path 2: Disconnector7 (CLOSED) - Bypass path + createSwitch(vlgen7, "DISC7", SwitchKind.DISCONNECTOR, false, 14, 16); // CLOSED - Bypass disconnector + + // Convergence to bypass point (node 11) + createSwitch(vlgen7, "DISC_BYPASS_CONV", SwitchKind.DISCONNECTOR, false, 16, 11); // Bypass convergence - // LINE7 - Fork Branch 1 + // LINE9 connection from bypass point (node 11) + createSwitch(vlgen7, "DISC6", SwitchKind.DISCONNECTOR, false, 11, 12); // CLOSED - Before LINE9 + createSwitch(vlgen7, "BRKR_LINE9", SwitchKind.BREAKER, false, 12, 17); // LINE9 breaker + + // ============ LINE7 - Fork Branch 1 (VLGEN4 → VLGEN7) ============ network.newLine() .setId("LINE7_FORK") .setName("Fork Branch 1 - Primary Line") .setVoltageLevel1("VLGEN4") - .setNode1(15) // Fixed node reference + .setNode1(15) .setVoltageLevel2("VLGEN7") .setNode2(2) .setR(3.0) @@ -174,7 +224,7 @@ void setUp() { .add() .add(); - // VLGEN8 - Fork destination 2 + // ============ VLGEN8 - Fork destination 2 ============ VoltageLevel vlgen8 = network.newVoltageLevel() .setId("VLGEN8") .setName("Fork Destination Point 2") @@ -183,10 +233,16 @@ void setUp() { .setLowVoltageLimit(20.0) .setTopologyKind(TopologyKind.NODE_BREAKER) .add(); + vlgen8.getNodeBreakerView().newBusbarSection() + .setId("BUS_NGEN8") + .setName("Main Busbar VLGEN8") + .setNode(0) + .add(); + createSwitch(vlgen8, "DISC_VLGEN8", SwitchKind.DISCONNECTOR, false, 0, 1); createSwitch(vlgen8, "BRKR_VLGEN8", SwitchKind.BREAKER, false, 1, 2); - // LINE8 - Fork Branch 2 + // ============ LINE8 - Fork Branch 2 (VLGEN7 → VLGEN8) ============ network.newLine() .setId("LINE8_FORK") .setName("Fork Branch 2 - Secondary Line") @@ -215,7 +271,7 @@ void setUp() { .add() .add(); - // VLGEN9 - Independent line destination + // ============ VLGEN9 - Independent line destination ============ VoltageLevel vlgen9 = network.newVoltageLevel() .setId("VLGEN9") .setName("Independent Line Destination") @@ -234,12 +290,12 @@ void setUp() { createSwitch(vlgen9, "DISC_VLGEN9", SwitchKind.DISCONNECTOR, false, 0, 1); createSwitch(vlgen9, "BRKR_VLGEN9", SwitchKind.BREAKER, false, 1, 2); - // LINE9 - Independent line from BUS4 + // ============ LINE9 - Independent line from BUS4 with bypass ============ network.newLine() .setId("LINE9_INDEPENDENT") .setName("Independent Line from BUS4") .setVoltageLevel1("VLGEN7") - .setNode1(11) + .setNode1(17) // Connected via bypass topology .setVoltageLevel2("VLGEN9") .setNode2(2) .setR(2.0) @@ -299,7 +355,7 @@ void testLine9FindsBus4DirectPath() { BusbarSectionFinderTraverser.BusbarSectionResult result = BusbarSectionFinderTraverser.findBestBusbar(terminal); assertNotNull(result); assertEquals("BUS4_NGEN7", result.busbarSectionId()); - assertEquals(3, result.depth()); + assertEquals(5, result.depth()); assertNotNull(result.lastSwitch()); assertEquals("SECT_BUS4", result.lastSwitch().id()); assertFalse(result.lastSwitch().isOpen()); From 6b5a7f16e1f5a650b1cbc52cef53551c075e9496 Mon Sep 17 00:00:00 2001 From: Rehili Ghazwa Date: Tue, 30 Sep 2025 14:17:47 +0200 Subject: [PATCH 10/15] enhance TU with mixed topo kind --- .../BusbarSectionFinderTraverserTest.java | 172 ++++++++++-------- 1 file changed, 101 insertions(+), 71 deletions(-) diff --git a/src/test/java/org/gridsuite/network/map/mapper/BusbarSectionFinderTraverserTest.java b/src/test/java/org/gridsuite/network/map/mapper/BusbarSectionFinderTraverserTest.java index 8f054c20..553a8790 100644 --- a/src/test/java/org/gridsuite/network/map/mapper/BusbarSectionFinderTraverserTest.java +++ b/src/test/java/org/gridsuite/network/map/mapper/BusbarSectionFinderTraverserTest.java @@ -322,133 +322,154 @@ void setUp() { network.getVariantManager().setWorkingVariant(VariantManagerConstants.INITIAL_VARIANT_ID); } + /* ============================================ + * FORK topology tests + * ============================================ + */ + @Test - void testLine7FindsBus2ViaFork() { + void testForkTopologyFindsBus2() { + // LINE7 and LINE8 share the same fork point (node 8) + // Both must find BUS2 because SECT_BUS2 is closed Line line7 = network.getLine("LINE7_FORK"); - Terminal terminal = line7.getTerminal2(); // VLGEN7 side - BusbarSectionFinderTraverser.BusbarSectionResult result = BusbarSectionFinderTraverser.findBestBusbar(terminal); - assertNotNull(result); + Line line8 = network.getLine("LINE8_FORK"); + BusbarSectionFinderTraverser.BusbarSectionResult result7 = BusbarSectionFinderTraverser.findBestBusbar(line7.getTerminal2()); + BusbarSectionFinderTraverser.BusbarSectionResult result8 = BusbarSectionFinderTraverser.findBestBusbar(line8.getTerminal1()); + // Both lines must find the same busbar + assertNotNull(result7); + assertEquals("BUS2_NGEN7", result7.busbarSectionId()); + assertNotNull(result8); + assertEquals("BUS2_NGEN7", result8.busbarSectionId()); + // Check depth and last switch + assertEquals(4, result7.depth()); + assertEquals(4, result8.depth()); + assertEquals("SECT_BUS2", result7.lastSwitch().id()); + assertFalse(result7.lastSwitch().isOpen()); + } + + @Test + void testForkPreferencesClosedOverOpen() { + Line line7 = network.getLine("LINE7_FORK"); + BusbarSectionFinderTraverser.BusbarSectionResult result = BusbarSectionFinderTraverser.findBestBusbar(line7.getTerminal2()); + // Must prefer BUS2 with closed switch rather than BUS1 with open switch assertEquals("BUS2_NGEN7", result.busbarSectionId()); - assertEquals(4, result.depth()); - assertNotNull(result.lastSwitch()); - assertEquals("SECT_BUS2", result.lastSwitch().id()); assertFalse(result.lastSwitch().isOpen()); } @Test - void testLine8FindsBus2ViaFork() { - Line line8 = network.getLine("LINE8_FORK"); - Terminal terminal = line8.getTerminal1(); // VLGEN7 side - BusbarSectionFinderTraverser.BusbarSectionResult result = BusbarSectionFinderTraverser.findBestBusbar(terminal); + void testForkFallbackToBus1WhenBus2Open() { + VoltageLevel vlgen7 = network.getVoltageLevel("VLGEN7"); + // Open SECT_BUS2 to disconnect BUS2 + vlgen7.getNodeBreakerView().getSwitch("SECT_BUS2").setOpen(true); + Line line7 = network.getLine("LINE7_FORK"); + BusbarSectionFinderTraverser.BusbarSectionResult result = BusbarSectionFinderTraverser.findBestBusbar(line7.getTerminal2()); + // Must find BUS1 (with open switch) assertNotNull(result); - assertEquals("BUS2_NGEN7", result.busbarSectionId()); - assertEquals(4, result.depth()); - assertNotNull(result.lastSwitch()); - assertEquals("SECT_BUS2", result.lastSwitch().id()); - assertFalse(result.lastSwitch().isOpen()); + assertEquals("BUS1_NGEN7", result.busbarSectionId()); + assertEquals("SECT_BUS1", result.lastSwitch().id()); + assertTrue(result.lastSwitch().isOpen()); } + /* ============================================ + * BYPASS topology tests + * ============================================ + */ + @Test - void testLine9FindsBus4DirectPath() { + void testBypassTopologyActivePath() { Line line9 = network.getLine("LINE9_INDEPENDENT"); - Terminal terminal = line9.getTerminal1(); // VLGEN7 side - BusbarSectionFinderTraverser.BusbarSectionResult result = BusbarSectionFinderTraverser.findBestBusbar(terminal); + BusbarSectionFinderTraverser.BusbarSectionResult result = BusbarSectionFinderTraverser.findBestBusbar(line9.getTerminal1()); + // Must find BUS4 via the bypass path assertNotNull(result); assertEquals("BUS4_NGEN7", result.busbarSectionId()); - assertEquals(5, result.depth()); + // Expected depth for bypass path + assertTrue(result.depth() >= 4); + // Last switch must be closed (not the open breaker) assertNotNull(result.lastSwitch()); - assertEquals("SECT_BUS4", result.lastSwitch().id()); assertFalse(result.lastSwitch().isOpen()); } @Test - void testBus1AccessibleThroughOpenSwitch() { - Line line7 = network.getLine("LINE7_FORK"); - Terminal terminal = line7.getTerminal2(); - BusbarSectionFinderTraverser.BusbarSectionResult result = BusbarSectionFinderTraverser.findBestBusbar(terminal); - // Should prefer BUS2 (closed) over BUS1 (open) - assertNotNull(result); - assertEquals("BUS2_NGEN7", result.busbarSectionId()); - } - - @Test - void testFindsBus1WhenBus2SwitchOpen() { + void testBypassSwitchesToMainPathWhenBreakerCloses() { VoltageLevel vlgen7 = network.getVoltageLevel("VLGEN7"); - Switch sectBus2 = vlgen7.getNodeBreakerView().getSwitch("SECT_BUS2"); - sectBus2.setOpen(true); // Open the switch to BUS2 - Line line7 = network.getLine("LINE7_FORK"); - Terminal terminal = line7.getTerminal2(); - BusbarSectionFinderTraverser.BusbarSectionResult result = BusbarSectionFinderTraverser.findBestBusbar(terminal); + // Close BRKR10 and open bypass DISC7 + vlgen7.getNodeBreakerView().getSwitch("BRKR10").setOpen(false); + vlgen7.getNodeBreakerView().getSwitch("DISC7").setOpen(true); + Line line9 = network.getLine("LINE9_INDEPENDENT"); + BusbarSectionFinderTraverser.BusbarSectionResult result = BusbarSectionFinderTraverser.findBestBusbar(line9.getTerminal1()); + // Must still find BUS4 assertNotNull(result); - assertEquals("BUS1_NGEN7", result.busbarSectionId()); - assertEquals(4, result.depth()); - assertNotNull(result.lastSwitch()); - assertTrue(result.lastSwitch().isOpen()); + assertEquals("BUS4_NGEN7", result.busbarSectionId()); + // Path must now use the closed breaker + assertFalse(result.lastSwitch().isOpen()); } @Test - void testBus3AccessibleWhenBus4Open() { + void testBypassFallbackToBus3() { VoltageLevel vlgen7 = network.getVoltageLevel("VLGEN7"); - Switch sectBus4 = vlgen7.getNodeBreakerView().getSwitch("SECT_BUS4"); - sectBus4.setOpen(true); // Open BUS4 connection + // Completely isolate BUS4 + vlgen7.getNodeBreakerView().getSwitch("SECT_BUS4").setOpen(true); Line line9 = network.getLine("LINE9_INDEPENDENT"); - Terminal terminal = line9.getTerminal1(); - BusbarSectionFinderTraverser.BusbarSectionResult result = BusbarSectionFinderTraverser.findBestBusbar(terminal); + BusbarSectionFinderTraverser.BusbarSectionResult result = BusbarSectionFinderTraverser.findBestBusbar(line9.getTerminal1()); + // Should still find BUS4 because bypass is still connected to it assertNotNull(result); assertEquals("BUS4_NGEN7", result.busbarSectionId()); } + /* ============================================ + * Selection priority tests + * ============================================ + */ + @Test - void testFindBusbarSectionIdConvenienceMethod() { + void testPrioritizesShortestClosedPath() { Line line7 = network.getLine("LINE7_FORK"); - Terminal terminal = line7.getTerminal2(); - String busbarId = BusbarSectionFinderTraverser.findBusbarSectionId(terminal); - assertNotNull(busbarId); - assertEquals("BUS2_NGEN7", busbarId); + BusbarSectionFinderTraverser.BusbarSectionResult result = BusbarSectionFinderTraverser.findBestBusbar(line7.getTerminal2()); + // BUS2 is the closest with closed switch + assertNotNull(result); + assertEquals("BUS2_NGEN7", result.busbarSectionId()); + assertEquals(4, result.depth()); + assertEquals(3, result.switchesBeforeLast()); } @Test - void testReturnsNullWhenNoBusbarAccessible() { + void testSelectionPriorityOrder() { VoltageLevel vlgen7 = network.getVoltageLevel("VLGEN7"); - // Open all switches connecting busbars + // Open all coupling disconnectors vlgen7.getNodeBreakerView().getSwitch("SECT_BUS1").setOpen(true); vlgen7.getNodeBreakerView().getSwitch("SECT_BUS2").setOpen(true); vlgen7.getNodeBreakerView().getSwitch("SECT_BUS3").setOpen(true); vlgen7.getNodeBreakerView().getSwitch("SECT_BUS4").setOpen(true); - vlgen7.getNodeBreakerView().getSwitch("FORK_SW1").setOpen(true); - vlgen7.getNodeBreakerView().getSwitch("FORK_SW2").setOpen(true); + // But keep fork connections Line line7 = network.getLine("LINE7_FORK"); - Terminal terminal = line7.getTerminal2(); - BusbarSectionFinderTraverser.BusbarSectionResult result = BusbarSectionFinderTraverser.findBestBusbar(terminal); + BusbarSectionFinderTraverser.BusbarSectionResult result = BusbarSectionFinderTraverser.findBestBusbar(line7.getTerminal2()); + // Must find a busbar with open switch priority 2 assertNotNull(result); - assertEquals("BUS1_NGEN7", result.busbarSectionId()); - assertEquals(4, result.depth()); - assertEquals(3, result.switchesBeforeLast()); assertNotNull(result.lastSwitch()); - assertEquals("SECT_BUS1", result.lastSwitch().id()); - assertEquals(SwitchKind.DISCONNECTOR, result.lastSwitch().kind()); assertTrue(result.lastSwitch().isOpen()); - assertEquals(0, result.lastSwitch().node1()); - assertEquals(6, result.lastSwitch().node2()); } @Test - void testPrefersShortestClosedPath() { + void testReturnsResultEvenWithNoClosedPaths() { + VoltageLevel vlgen7 = network.getVoltageLevel("VLGEN7"); + // Open all coupling switches but keep fork switches + vlgen7.getNodeBreakerView().getSwitch("SECT_BUS1").setOpen(true); + vlgen7.getNodeBreakerView().getSwitch("SECT_BUS2").setOpen(true); Line line7 = network.getLine("LINE7_FORK"); - Terminal terminal = line7.getTerminal2(); - BusbarSectionFinderTraverser.BusbarSectionResult result = BusbarSectionFinderTraverser.findBestBusbar(terminal); - // BUS2 should be preferred (3 switches) over potential longer paths + BusbarSectionFinderTraverser.BusbarSectionResult result = BusbarSectionFinderTraverser.findBestBusbar(line7.getTerminal2()); + // Must return a result (busbar accessible via open switch) assertNotNull(result); - assertEquals("BUS2_NGEN7", result.busbarSectionId()); - assertEquals(4, result.depth()); + assertTrue(result.depth() > 0); } @Test - void testForkTopologySharesBusbar() { + void testForkLinesShareSameBusbar() { Line line7 = network.getLine("LINE7_FORK"); Line line8 = network.getLine("LINE8_FORK"); String busbar7 = BusbarSectionFinderTraverser.findBusbarSectionId(line7.getTerminal2()); String busbar8 = BusbarSectionFinderTraverser.findBusbarSectionId(line8.getTerminal1()); + // Both fork lines must find the same busbar + assertNotNull(busbar7); assertEquals(busbar7, busbar8); assertEquals("BUS2_NGEN7", busbar7); } @@ -472,4 +493,13 @@ void testHandlesMixedSwitchTypes() { // Path contains both breakers and disconnectors assertTrue(result.depth() > 0); } + + @Test + void testSwitchesBeforeLastCountAccuracy() { + Line line7 = network.getLine("LINE7_FORK"); + BusbarSectionFinderTraverser.BusbarSectionResult result = BusbarSectionFinderTraverser.findBestBusbar(line7.getTerminal2()); + assertNotNull(result); + // With depth=4, we must have 3 switches before the last one + assertEquals(result.depth() - 1, result.switchesBeforeLast()); + } } From a48c699984fdd2e932d3f0e59d0fdea187359967 Mon Sep 17 00:00:00 2001 From: Rehili Ghazwa Date: Tue, 30 Sep 2025 17:23:46 +0200 Subject: [PATCH 11/15] EtienneH code review remarks --- .../BusbarSectionFinderTraverser.java | 97 +++++-------------- .../BusbarSectionFinderTraverserTest.java | 30 +++--- 2 files changed, 36 insertions(+), 91 deletions(-) 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 0b16b1a5..5ba52c5c 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 @@ -11,26 +11,27 @@ import java.util.*; /** - * @author Slimane Amar + * @author Ghazwa Rehili */ // TODO : to remove when this class is available in network-store public final class BusbarSectionFinderTraverser { - /** - * Private constructor to prevent instantiation of this utility class. - */ private BusbarSectionFinderTraverser() { throw new UnsupportedOperationException(); } - /** - * Finds the best busbar section connected to the given terminal. - * Uses a breadth-first search algorithm to explore all possible paths. - * - * @param terminal the starting terminal - * @return the best busbar result according to selection criteria, or null if none found - */ - public static BusbarSectionResult findBestBusbar(Terminal terminal) { + private record NodePath(int node, List pathSwitches, SwitchInfo lastSwitch) { } + + public record SwitchInfo(String id, SwitchKind kind, boolean isOpen) { } + + public record BusbarSectionResult(String busbarSectionId, int depth, SwitchInfo lastSwitch) { } + + public static String findBusbarSectionId(Terminal terminal) { + BusbarSectionResult result = getBusbarSectionResult(terminal); + return result != null ? result.busbarSectionId() : null; + } + + public static BusbarSectionResult getBusbarSectionResult(Terminal terminal) { VoltageLevel.NodeBreakerView view = terminal.getVoltageLevel().getNodeBreakerView(); int startNode = terminal.getNodeBreakerView().getNode(); List allResults = searchAllBusbars(view, startNode); @@ -40,46 +41,22 @@ public static BusbarSectionResult findBestBusbar(Terminal terminal) { return selectBestBusbar(allResults); } - /** - * Selects the best busbar from a list of candidates using a priority-based approach: - * Priority 1: Busbar with closed last switch (minimum depth, then minimum switches before last) - * Priority 2: Busbar with open last switch (minimum depth, then minimum switches before last) - * Priority 3: Busbar without switch (direct connection, minimum depth) - * - * @param results list of all found busbar results - * @return the best busbar according to selection criteria - */ private static BusbarSectionResult selectBestBusbar(List results) { - // Priority 1: Search for busbar with closed last switch + List withoutSwitch = results.stream().filter(r -> r.lastSwitch() == null).toList(); + if (!withoutSwitch.isEmpty()) { + return withoutSwitch.stream().min(Comparator.comparingInt(BusbarSectionResult::depth)).orElse(null); + } List withClosedSwitch = results.stream().filter(r -> r.lastSwitch() != null && !r.lastSwitch().isOpen()).toList(); if (!withClosedSwitch.isEmpty()) { - return withClosedSwitch.stream().min(Comparator.comparingInt(BusbarSectionResult::depth).thenComparingInt(BusbarSectionResult::switchesBeforeLast)).orElse(null); + return withClosedSwitch.stream().min(Comparator.comparingInt(BusbarSectionResult::depth)).orElse(null); } - - // Priority 2: Search for busbar with open last switch List withOpenSwitch = results.stream().filter(r -> r.lastSwitch() != null && r.lastSwitch().isOpen()).toList(); if (!withOpenSwitch.isEmpty()) { - return withOpenSwitch.stream().min(Comparator.comparingInt(BusbarSectionResult::depth).thenComparingInt(BusbarSectionResult::switchesBeforeLast)).orElse(null); + return withOpenSwitch.stream().min(Comparator.comparingInt(BusbarSectionResult::depth)).orElse(null); } - - // Priority 3: Busbars without switch direct connection - List withoutSwitch = results.stream().filter(r -> r.lastSwitch() == null).toList(); - if (!withoutSwitch.isEmpty()) { - return withoutSwitch.stream().min(Comparator.comparingInt(BusbarSectionResult::depth)).orElse(null); - } - - // Fallback: select first busbar return results.getFirst(); } - /** - * Searches all accessible busbars from a starting node using breadth-first search. - * Explores the node-breaker topology through switches. - * - * @param view the node-breaker view of the voltage level - * @param startNode the starting node index - * @return list of all busbar results found - */ private static List searchAllBusbars(VoltageLevel.NodeBreakerView view, int startNode) { List results = new ArrayList<>(); Set visited = new HashSet<>(); @@ -111,13 +88,11 @@ private static Optional tryCreateBusbarResult(VoltageLevel. return Optional.empty(); } Terminal term = nodeTerminal.get(); - // Check if current node is a busbar section if (term.getConnectable().getType() == IdentifiableType.BUSBAR_SECTION) { String busbarSectionId = term.getConnectable().getId(); int depth = currentNodePath.pathSwitches().size(); SwitchInfo lastSwitch = currentNodePath.lastSwitch(); - int switchesBeforeLast = lastSwitch != null ? (depth - 1) : 0; - return Optional.of(new BusbarSectionResult(busbarSectionId, depth, switchesBeforeLast, lastSwitch)); + return Optional.of(new BusbarSectionResult(busbarSectionId, depth, lastSwitch)); } return Optional.empty(); } @@ -128,7 +103,7 @@ private static void exploreAdjacentNodes(VoltageLevel.NodeBreakerView view, Node int node2 = view.getNode2(sw.getId()); Optional nextNode = getNextNodeIfAdjacent(currentNodePath.node(), node1, node2); if (nextNode.isPresent() && !visited.contains(nextNode.get())) { - NodePath newPath = createNodePath(currentNodePath, sw, node1, node2, nextNode.get()); + NodePath newPath = createNodePath(currentNodePath, sw, nextNode.get()); queue.offer(newPath); } }); @@ -144,36 +119,10 @@ private static Optional getNextNodeIfAdjacent(int currentNode, int node return Optional.empty(); } - private static NodePath createNodePath(NodePath currentNodePath, Switch sw, int node1, int node2, int nextNode) { + private static NodePath createNodePath(NodePath currentNodePath, Switch sw, int nextNode) { List newPathSwitches = new ArrayList<>(currentNodePath.pathSwitches()); - SwitchInfo switchInfo = new SwitchInfo(sw.getId(), sw.getKind(), sw.isOpen(), node1, node2); + SwitchInfo switchInfo = new SwitchInfo(sw.getId(), sw.getKind(), sw.isOpen()); newPathSwitches.add(switchInfo); return new NodePath(nextNode, newPathSwitches, switchInfo); } - - /** - * Internal record to track the path during graph traversal. - */ - private record NodePath(int node, List pathSwitches, SwitchInfo lastSwitch) { } - - /** - * Record containing information about a switch in the topology. - */ - public record SwitchInfo(String id, SwitchKind kind, boolean isOpen, int node1, int node2) { } - - /** - * Record containing the result of a busbar search with selection metadata. - */ - public record BusbarSectionResult(String busbarSectionId, int depth, int switchesBeforeLast, SwitchInfo lastSwitch) { } - - /** - * Convenience method to get only the busbar ID. - * - * @param terminal the starting terminal - * @return the busbar ID or null if none found - */ - public static String findBusbarSectionId(Terminal terminal) { - BusbarSectionResult result = findBestBusbar(terminal); - return result != null ? result.busbarSectionId() : null; - } } diff --git a/src/test/java/org/gridsuite/network/map/mapper/BusbarSectionFinderTraverserTest.java b/src/test/java/org/gridsuite/network/map/mapper/BusbarSectionFinderTraverserTest.java index 553a8790..88b63cd0 100644 --- a/src/test/java/org/gridsuite/network/map/mapper/BusbarSectionFinderTraverserTest.java +++ b/src/test/java/org/gridsuite/network/map/mapper/BusbarSectionFinderTraverserTest.java @@ -333,8 +333,8 @@ void testForkTopologyFindsBus2() { // Both must find BUS2 because SECT_BUS2 is closed Line line7 = network.getLine("LINE7_FORK"); Line line8 = network.getLine("LINE8_FORK"); - BusbarSectionFinderTraverser.BusbarSectionResult result7 = BusbarSectionFinderTraverser.findBestBusbar(line7.getTerminal2()); - BusbarSectionFinderTraverser.BusbarSectionResult result8 = BusbarSectionFinderTraverser.findBestBusbar(line8.getTerminal1()); + BusbarSectionFinderTraverser.BusbarSectionResult result7 = BusbarSectionFinderTraverser.getBusbarSectionResult(line7.getTerminal2()); + BusbarSectionFinderTraverser.BusbarSectionResult result8 = BusbarSectionFinderTraverser.getBusbarSectionResult(line8.getTerminal1()); // Both lines must find the same busbar assertNotNull(result7); assertEquals("BUS2_NGEN7", result7.busbarSectionId()); @@ -350,7 +350,7 @@ void testForkTopologyFindsBus2() { @Test void testForkPreferencesClosedOverOpen() { Line line7 = network.getLine("LINE7_FORK"); - BusbarSectionFinderTraverser.BusbarSectionResult result = BusbarSectionFinderTraverser.findBestBusbar(line7.getTerminal2()); + BusbarSectionFinderTraverser.BusbarSectionResult result = BusbarSectionFinderTraverser.getBusbarSectionResult(line7.getTerminal2()); // Must prefer BUS2 with closed switch rather than BUS1 with open switch assertEquals("BUS2_NGEN7", result.busbarSectionId()); assertFalse(result.lastSwitch().isOpen()); @@ -362,7 +362,7 @@ void testForkFallbackToBus1WhenBus2Open() { // Open SECT_BUS2 to disconnect BUS2 vlgen7.getNodeBreakerView().getSwitch("SECT_BUS2").setOpen(true); Line line7 = network.getLine("LINE7_FORK"); - BusbarSectionFinderTraverser.BusbarSectionResult result = BusbarSectionFinderTraverser.findBestBusbar(line7.getTerminal2()); + BusbarSectionFinderTraverser.BusbarSectionResult result = BusbarSectionFinderTraverser.getBusbarSectionResult(line7.getTerminal2()); // Must find BUS1 (with open switch) assertNotNull(result); assertEquals("BUS1_NGEN7", result.busbarSectionId()); @@ -378,7 +378,7 @@ void testForkFallbackToBus1WhenBus2Open() { @Test void testBypassTopologyActivePath() { Line line9 = network.getLine("LINE9_INDEPENDENT"); - BusbarSectionFinderTraverser.BusbarSectionResult result = BusbarSectionFinderTraverser.findBestBusbar(line9.getTerminal1()); + BusbarSectionFinderTraverser.BusbarSectionResult result = BusbarSectionFinderTraverser.getBusbarSectionResult(line9.getTerminal1()); // Must find BUS4 via the bypass path assertNotNull(result); assertEquals("BUS4_NGEN7", result.busbarSectionId()); @@ -396,7 +396,7 @@ void testBypassSwitchesToMainPathWhenBreakerCloses() { vlgen7.getNodeBreakerView().getSwitch("BRKR10").setOpen(false); vlgen7.getNodeBreakerView().getSwitch("DISC7").setOpen(true); Line line9 = network.getLine("LINE9_INDEPENDENT"); - BusbarSectionFinderTraverser.BusbarSectionResult result = BusbarSectionFinderTraverser.findBestBusbar(line9.getTerminal1()); + BusbarSectionFinderTraverser.BusbarSectionResult result = BusbarSectionFinderTraverser.getBusbarSectionResult(line9.getTerminal1()); // Must still find BUS4 assertNotNull(result); assertEquals("BUS4_NGEN7", result.busbarSectionId()); @@ -410,7 +410,7 @@ void testBypassFallbackToBus3() { // Completely isolate BUS4 vlgen7.getNodeBreakerView().getSwitch("SECT_BUS4").setOpen(true); Line line9 = network.getLine("LINE9_INDEPENDENT"); - BusbarSectionFinderTraverser.BusbarSectionResult result = BusbarSectionFinderTraverser.findBestBusbar(line9.getTerminal1()); + BusbarSectionFinderTraverser.BusbarSectionResult result = BusbarSectionFinderTraverser.getBusbarSectionResult(line9.getTerminal1()); // Should still find BUS4 because bypass is still connected to it assertNotNull(result); assertEquals("BUS4_NGEN7", result.busbarSectionId()); @@ -424,12 +424,11 @@ void testBypassFallbackToBus3() { @Test void testPrioritizesShortestClosedPath() { Line line7 = network.getLine("LINE7_FORK"); - BusbarSectionFinderTraverser.BusbarSectionResult result = BusbarSectionFinderTraverser.findBestBusbar(line7.getTerminal2()); + BusbarSectionFinderTraverser.BusbarSectionResult result = BusbarSectionFinderTraverser.getBusbarSectionResult(line7.getTerminal2()); // BUS2 is the closest with closed switch assertNotNull(result); assertEquals("BUS2_NGEN7", result.busbarSectionId()); assertEquals(4, result.depth()); - assertEquals(3, result.switchesBeforeLast()); } @Test @@ -442,7 +441,7 @@ void testSelectionPriorityOrder() { vlgen7.getNodeBreakerView().getSwitch("SECT_BUS4").setOpen(true); // But keep fork connections Line line7 = network.getLine("LINE7_FORK"); - BusbarSectionFinderTraverser.BusbarSectionResult result = BusbarSectionFinderTraverser.findBestBusbar(line7.getTerminal2()); + BusbarSectionFinderTraverser.BusbarSectionResult result = BusbarSectionFinderTraverser.getBusbarSectionResult(line7.getTerminal2()); // Must find a busbar with open switch priority 2 assertNotNull(result); assertNotNull(result.lastSwitch()); @@ -456,7 +455,7 @@ void testReturnsResultEvenWithNoClosedPaths() { vlgen7.getNodeBreakerView().getSwitch("SECT_BUS1").setOpen(true); vlgen7.getNodeBreakerView().getSwitch("SECT_BUS2").setOpen(true); Line line7 = network.getLine("LINE7_FORK"); - BusbarSectionFinderTraverser.BusbarSectionResult result = BusbarSectionFinderTraverser.findBestBusbar(line7.getTerminal2()); + BusbarSectionFinderTraverser.BusbarSectionResult result = BusbarSectionFinderTraverser.getBusbarSectionResult(line7.getTerminal2()); // Must return a result (busbar accessible via open switch) assertNotNull(result); assertTrue(result.depth() > 0); @@ -478,17 +477,16 @@ void testForkLinesShareSameBusbar() { void testSwitchesBeforeLastCount() { Line line7 = network.getLine("LINE7_FORK"); Terminal terminal = line7.getTerminal2(); - BusbarSectionFinderTraverser.BusbarSectionResult result = BusbarSectionFinderTraverser.findBestBusbar(terminal); + BusbarSectionFinderTraverser.BusbarSectionResult result = BusbarSectionFinderTraverser.getBusbarSectionResult(terminal); assertNotNull(result); assertEquals(4, result.depth()); - assertEquals(3, result.switchesBeforeLast()); } @Test void testHandlesMixedSwitchTypes() { Line line7 = network.getLine("LINE7_FORK"); Terminal terminal = line7.getTerminal2(); - BusbarSectionFinderTraverser.BusbarSectionResult result = BusbarSectionFinderTraverser.findBestBusbar(terminal); + BusbarSectionFinderTraverser.BusbarSectionResult result = BusbarSectionFinderTraverser.getBusbarSectionResult(terminal); assertNotNull(result); // Path contains both breakers and disconnectors assertTrue(result.depth() > 0); @@ -497,9 +495,7 @@ void testHandlesMixedSwitchTypes() { @Test void testSwitchesBeforeLastCountAccuracy() { Line line7 = network.getLine("LINE7_FORK"); - BusbarSectionFinderTraverser.BusbarSectionResult result = BusbarSectionFinderTraverser.findBestBusbar(line7.getTerminal2()); + BusbarSectionFinderTraverser.BusbarSectionResult result = BusbarSectionFinderTraverser.getBusbarSectionResult(line7.getTerminal2()); assertNotNull(result); - // With depth=4, we must have 3 switches before the last one - assertEquals(result.depth() - 1, result.switchesBeforeLast()); } } From 8bf25d48c468dc8c3e36bf6e5dc60951abdac330 Mon Sep 17 00:00:00 2001 From: Rehili Ghazwa Date: Tue, 30 Sep 2025 17:31:52 +0200 Subject: [PATCH 12/15] add author --- .../network/map/mapper/BusbarSectionFinderTraverserTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/org/gridsuite/network/map/mapper/BusbarSectionFinderTraverserTest.java b/src/test/java/org/gridsuite/network/map/mapper/BusbarSectionFinderTraverserTest.java index 88b63cd0..7f4d1aee 100644 --- a/src/test/java/org/gridsuite/network/map/mapper/BusbarSectionFinderTraverserTest.java +++ b/src/test/java/org/gridsuite/network/map/mapper/BusbarSectionFinderTraverserTest.java @@ -12,7 +12,7 @@ import static org.junit.jupiter.api.Assertions.*; /** - * @author Slimane Amar + * @author Ghazwa Rehili */ @SpringBootTest From 999f53c5b6316ce079280c0edf67207d84af3603 Mon Sep 17 00:00:00 2001 From: Rehili Ghazwa Date: Wed, 1 Oct 2025 13:01:09 +0200 Subject: [PATCH 13/15] etienneH code review remarks part 2 --- .../BusbarSectionFinderTraverser.java | 71 +++++++++++-------- src/test/resources/substations-form-data.json | 2 +- .../resources/voltage-level-form-data.json | 2 +- .../resources/voltage-levels-form-data.json | 2 +- 4 files changed, 46 insertions(+), 31 deletions(-) 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 5ba52c5c..06e764db 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 @@ -7,6 +7,7 @@ package org.gridsuite.network.map.dto.definition.extension; import com.powsybl.iidm.network.*; +import com.powsybl.iidm.network.extensions.BusbarSectionPosition; import java.util.*; @@ -20,15 +21,15 @@ private BusbarSectionFinderTraverser() { throw new UnsupportedOperationException(); } - private record NodePath(int node, List pathSwitches, SwitchInfo lastSwitch) { } + private record NodePath(int startNode, List traversedSwitches, SwitchInfo lastSwitch) { } public record SwitchInfo(String id, SwitchKind kind, boolean isOpen) { } - public record BusbarSectionResult(String busbarSectionId, int depth, SwitchInfo lastSwitch) { } + public record BusbarSectionResult(String busbarSectionId, int depth, SwitchInfo lastSwitch, int busbarIndex, int sectionIndex) { } public static String findBusbarSectionId(Terminal terminal) { BusbarSectionResult result = getBusbarSectionResult(terminal); - return result != null ? result.busbarSectionId() : null; + return result != null ? result.busbarSectionId() : terminal.getVoltageLevel().getNodeBreakerView().getBusbarSections().iterator().next().getId(); } public static BusbarSectionResult getBusbarSectionResult(Terminal terminal) { @@ -44,15 +45,21 @@ public static BusbarSectionResult getBusbarSectionResult(Terminal terminal) { private static BusbarSectionResult selectBestBusbar(List results) { List withoutSwitch = results.stream().filter(r -> r.lastSwitch() == null).toList(); if (!withoutSwitch.isEmpty()) { - return withoutSwitch.stream().min(Comparator.comparingInt(BusbarSectionResult::depth)).orElse(null); + return withoutSwitch.stream().min(Comparator.comparingInt(BusbarSectionResult::depth) + .thenComparingInt(BusbarSectionResult::busbarIndex) + .thenComparingInt(BusbarSectionResult::sectionIndex)).orElse(null); } List withClosedSwitch = results.stream().filter(r -> r.lastSwitch() != null && !r.lastSwitch().isOpen()).toList(); if (!withClosedSwitch.isEmpty()) { - return withClosedSwitch.stream().min(Comparator.comparingInt(BusbarSectionResult::depth)).orElse(null); + return withClosedSwitch.stream().min(Comparator.comparingInt(BusbarSectionResult::depth) + .thenComparingInt(BusbarSectionResult::busbarIndex) + .thenComparingInt(BusbarSectionResult::sectionIndex)).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)).orElse(null); + return withOpenSwitch.stream().min(Comparator.comparingInt(BusbarSectionResult::depth) + .thenComparingInt(BusbarSectionResult::busbarIndex) + .thenComparingInt(BusbarSectionResult::sectionIndex)).orElse(null); } return results.getFirst(); } @@ -60,51 +67,59 @@ private static BusbarSectionResult selectBestBusbar(List re private static List searchAllBusbars(VoltageLevel.NodeBreakerView view, int startNode) { List results = new ArrayList<>(); Set visited = new HashSet<>(); - Queue queue = new LinkedList<>(); - queue.offer(new NodePath(startNode, new ArrayList<>(), null)); - while (!queue.isEmpty()) { - NodePath currentNodePath = queue.poll(); - if (!hasNotBeenVisited(currentNodePath.node(), visited)) { + Queue nodePathsToVisit = new LinkedList<>(); + nodePathsToVisit.offer(new NodePath(startNode, new ArrayList<>(), null)); + while (!nodePathsToVisit.isEmpty()) { + NodePath currentNodePath = nodePathsToVisit.poll(); + if (hasBeenVisited(currentNodePath.startNode(), visited)) { continue; } - visited.add(currentNodePath.node()); - Optional busbarSectionResult = tryCreateBusbarResult(view, currentNodePath); + visited.add(currentNodePath.startNode()); + Optional busbarSectionResult = findBusbarSectionAtNode(view, currentNodePath); if (busbarSectionResult.isPresent()) { results.add(busbarSectionResult.get()); } else { - exploreAdjacentNodes(view, currentNodePath, visited, queue); + exploreAdjacentNodes(view, currentNodePath, visited, nodePathsToVisit); } } return results; } - private static boolean hasNotBeenVisited(int node, Set visited) { - return !visited.contains(node); + private static boolean hasBeenVisited(int node, Set visited) { + return visited.contains(node); } - private static Optional tryCreateBusbarResult(VoltageLevel.NodeBreakerView view, NodePath currentNodePath) { - Optional nodeTerminal = view.getOptionalTerminal(currentNodePath.node()); + private static Optional findBusbarSectionAtNode(VoltageLevel.NodeBreakerView view, NodePath currentNodePath) { + Optional nodeTerminal = view.getOptionalTerminal(currentNodePath.startNode()); if (nodeTerminal.isEmpty()) { return Optional.empty(); } - Terminal term = nodeTerminal.get(); - if (term.getConnectable().getType() == IdentifiableType.BUSBAR_SECTION) { - String busbarSectionId = term.getConnectable().getId(); - int depth = currentNodePath.pathSwitches().size(); + Terminal terminal = nodeTerminal.get(); + if (terminal.getConnectable().getType() == IdentifiableType.BUSBAR_SECTION) { + String busbarSectionId = terminal.getConnectable().getId(); + int depth = currentNodePath.traversedSwitches().size(); SwitchInfo lastSwitch = currentNodePath.lastSwitch(); - return Optional.of(new BusbarSectionResult(busbarSectionId, depth, lastSwitch)); + BusbarSection busbarSection = (BusbarSection) terminal.getConnectable(); + int busbarIndex = 1; + int sectionIndex = 1; + var busbarSectionPosition = busbarSection.getExtension(BusbarSectionPosition.class); + if (busbarSectionPosition != null) { + busbarIndex = busbarSectionPosition.getBusbarIndex(); + sectionIndex = busbarSectionPosition.getSectionIndex(); + } + return Optional.of(new BusbarSectionResult(busbarSectionId, depth, lastSwitch, busbarIndex, sectionIndex)); } return Optional.empty(); } - private static void exploreAdjacentNodes(VoltageLevel.NodeBreakerView view, NodePath currentNodePath, Set visited, Queue queue) { + private static void exploreAdjacentNodes(VoltageLevel.NodeBreakerView view, NodePath currentNodePath, Set visited, Queue nodePathsToVisit) { view.getSwitchStream().forEach(sw -> { int node1 = view.getNode1(sw.getId()); int node2 = view.getNode2(sw.getId()); - Optional nextNode = getNextNodeIfAdjacent(currentNodePath.node(), node1, node2); + Optional nextNode = getNextNodeIfAdjacent(currentNodePath.startNode(), node1, node2); if (nextNode.isPresent() && !visited.contains(nextNode.get())) { - NodePath newPath = createNodePath(currentNodePath, sw, nextNode.get()); - queue.offer(newPath); + NodePath newNodePath = createNodePath(currentNodePath, sw, nextNode.get()); + nodePathsToVisit.offer(newNodePath); } }); } @@ -120,7 +135,7 @@ private static Optional getNextNodeIfAdjacent(int currentNode, int node } private static NodePath createNodePath(NodePath currentNodePath, Switch sw, int nextNode) { - List newPathSwitches = new ArrayList<>(currentNodePath.pathSwitches()); + List newPathSwitches = new ArrayList<>(currentNodePath.traversedSwitches()); SwitchInfo switchInfo = new SwitchInfo(sw.getId(), sw.getKind(), sw.isOpen()); newPathSwitches.add(switchInfo); return new NodePath(nextNode, newPathSwitches, switchInfo); 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 }, From 28dc2d2e9f809fd349c84914ab9da852432ef684 Mon Sep 17 00:00:00 2001 From: Rehili Ghazwa Date: Wed, 1 Oct 2025 13:19:11 +0200 Subject: [PATCH 14/15] clean code --- .../definition/extension/BusbarSectionFinderTraverser.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 06e764db..c8afa156 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 @@ -23,7 +23,7 @@ private BusbarSectionFinderTraverser() { private record NodePath(int startNode, List traversedSwitches, SwitchInfo lastSwitch) { } - public record SwitchInfo(String id, SwitchKind kind, boolean isOpen) { } + public record SwitchInfo(String id, boolean isOpen) { } public record BusbarSectionResult(String busbarSectionId, int depth, SwitchInfo lastSwitch, int busbarIndex, int sectionIndex) { } @@ -136,7 +136,7 @@ private static Optional getNextNodeIfAdjacent(int currentNode, int node private static NodePath createNodePath(NodePath currentNodePath, Switch sw, int nextNode) { List newPathSwitches = new ArrayList<>(currentNodePath.traversedSwitches()); - SwitchInfo switchInfo = new SwitchInfo(sw.getId(), sw.getKind(), sw.isOpen()); + SwitchInfo switchInfo = new SwitchInfo(sw.getId(), sw.isOpen()); newPathSwitches.add(switchInfo); return new NodePath(nextNode, newPathSwitches, switchInfo); } From dfae5ecb7b93468142edea747716bbb1082bb7ec Mon Sep 17 00:00:00 2001 From: Rehili Ghazwa Date: Wed, 1 Oct 2025 15:12:45 +0200 Subject: [PATCH 15/15] add comments --- .../dto/definition/extension/BusbarSectionFinderTraverser.java | 3 +++ 1 file changed, 3 insertions(+) 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 c8afa156..dde16e39 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 @@ -43,18 +43,21 @@ public static BusbarSectionResult getBusbarSectionResult(Terminal terminal) { } private static BusbarSectionResult selectBestBusbar(List results) { + // Priority 1: Busbars without switch direct connection List withoutSwitch = results.stream().filter(r -> r.lastSwitch() == null).toList(); if (!withoutSwitch.isEmpty()) { return withoutSwitch.stream().min(Comparator.comparingInt(BusbarSectionResult::depth) .thenComparingInt(BusbarSectionResult::busbarIndex) .thenComparingInt(BusbarSectionResult::sectionIndex)).orElse(null); } + // Priority 2: Search for busbar with closed last switch List withClosedSwitch = results.stream().filter(r -> r.lastSwitch() != null && !r.lastSwitch().isOpen()).toList(); if (!withClosedSwitch.isEmpty()) { return withClosedSwitch.stream().min(Comparator.comparingInt(BusbarSectionResult::depth) .thenComparingInt(BusbarSectionResult::busbarIndex) .thenComparingInt(BusbarSectionResult::sectionIndex)).orElse(null); } + // Priority 3: Search for busbar with open last switch List withOpenSwitch = results.stream().filter(r -> r.lastSwitch() != null && r.lastSwitch().isOpen()).toList(); if (!withOpenSwitch.isEmpty()) { return withOpenSwitch.stream().min(Comparator.comparingInt(BusbarSectionResult::depth)