diff --git a/src/main/java/org/gridsuite/modification/ModificationType.java b/src/main/java/org/gridsuite/modification/ModificationType.java index 11350a3d..73b651fd 100644 --- a/src/main/java/org/gridsuite/modification/ModificationType.java +++ b/src/main/java/org/gridsuite/modification/ModificationType.java @@ -62,7 +62,8 @@ public enum ModificationType { CREATE_COUPLING_DEVICE(PreloadingStrategy.NONE), CREATE_VOLTAGE_LEVEL_TOPOLOGY(PreloadingStrategy.NONE), LIMIT_SETS_TABULAR_MODIFICATION(PreloadingStrategy.COLLECTION), - CREATE_VOLTAGE_LEVEL_SECTION(PreloadingStrategy.NONE); + CREATE_VOLTAGE_LEVEL_SECTION(PreloadingStrategy.NONE), + MOVE_VOLTAGE_LEVEL_FEEDER_BAYS(PreloadingStrategy.NONE); private final PreloadingStrategy strategy; diff --git a/src/main/java/org/gridsuite/modification/NetworkModificationException.java b/src/main/java/org/gridsuite/modification/NetworkModificationException.java index a66d1d62..c7264529 100644 --- a/src/main/java/org/gridsuite/modification/NetworkModificationException.java +++ b/src/main/java/org/gridsuite/modification/NetworkModificationException.java @@ -133,7 +133,8 @@ public enum Type { MODIFY_VOLTAGE_LEVEL_TOPOLOGY_ERROR(HttpStatus.INTERNAL_SERVER_ERROR), CREATE_COUPLING_DEVICE_ERROR(HttpStatus.INTERNAL_SERVER_ERROR), CREATE_VOLTAGE_LEVEL_TOPOLOGY_ERROR(HttpStatus.INTERNAL_SERVER_ERROR), - CREATE_VOLTAGE_LEVEL_SECTION_ERROR(HttpStatus.INTERNAL_SERVER_ERROR); + CREATE_VOLTAGE_LEVEL_SECTION_ERROR(HttpStatus.INTERNAL_SERVER_ERROR), + MOVE_VOLTAGE_LEVEL_FEEDER_BAYS_ERROR(HttpStatus.INTERNAL_SERVER_ERROR),; public final HttpStatus status; private final String message; diff --git a/src/main/java/org/gridsuite/modification/dto/ConnectablePositionModificationInfos.java b/src/main/java/org/gridsuite/modification/dto/ConnectablePositionModificationInfos.java new file mode 100644 index 00000000..f8c12610 --- /dev/null +++ b/src/main/java/org/gridsuite/modification/dto/ConnectablePositionModificationInfos.java @@ -0,0 +1,44 @@ +/** + * Copyright (c) 2025, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.gridsuite.modification.dto; + +import com.powsybl.iidm.network.extensions.ConnectablePosition; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; +import lombok.experimental.SuperBuilder; + +/** + * @author Etienne Lesot + */ +@SuperBuilder +@NoArgsConstructor +@Getter +@Setter +@ToString(callSuper = true) +@Schema(description = "Connectable position modification") +public class ConnectablePositionModificationInfos { + @Schema(description = "Connectable id") + private String equipmentId; + + @Schema(description = "busbar section id") + private String busbarSectionId; + + @Schema(description = "connection side") + private String connectionSide; + + @Schema(description = "connection position") + private Integer connectionPosition; + + @Schema(description = "connection name") + private String connectionName; + + @Schema(description = "connection direction") + private ConnectablePosition.Direction connectionDirection; +} diff --git a/src/main/java/org/gridsuite/modification/dto/ModificationInfos.java b/src/main/java/org/gridsuite/modification/dto/ModificationInfos.java index d514e226..aa19fbe8 100644 --- a/src/main/java/org/gridsuite/modification/dto/ModificationInfos.java +++ b/src/main/java/org/gridsuite/modification/dto/ModificationInfos.java @@ -83,6 +83,7 @@ @JsonSubTypes.Type(value = CreateVoltageLevelTopologyInfos.class), @JsonSubTypes.Type(value = LimitSetsTabularModificationInfos.class), @JsonSubTypes.Type(value = CreateVoltageLevelSectionInfos.class), + @JsonSubTypes.Type(value = MoveVoltageLevelFeederBaysInfos.class), }) @SuperBuilder @NoArgsConstructor diff --git a/src/main/java/org/gridsuite/modification/dto/MoveVoltageLevelFeederBaysInfos.java b/src/main/java/org/gridsuite/modification/dto/MoveVoltageLevelFeederBaysInfos.java new file mode 100644 index 00000000..8bfe4244 --- /dev/null +++ b/src/main/java/org/gridsuite/modification/dto/MoveVoltageLevelFeederBaysInfos.java @@ -0,0 +1,58 @@ +/** + * Copyright (c) 2025, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.gridsuite.modification.dto; + +import com.fasterxml.jackson.annotation.JsonTypeName; +import com.powsybl.commons.report.ReportNode; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.experimental.SuperBuilder; +import org.gridsuite.modification.dto.annotation.ModificationErrorTypeName; +import org.gridsuite.modification.modifications.AbstractModification; +import org.gridsuite.modification.modifications.MoveVoltageLevelFeederBays; + +import java.util.List; +import java.util.Map; + +/** + * @author Etienne Lesot + */ +@SuperBuilder +@NoArgsConstructor +@Getter +@Setter +@Schema(description = "Move voltage level feeder bays") +@JsonTypeName("MOVE_VOLTAGE_LEVEL_FEEDER_BAYS") +@ModificationErrorTypeName("MOVE_VOLTAGE_LEVEL_FEEDER_BAYS_ERROR") +public class MoveVoltageLevelFeederBaysInfos extends ModificationInfos { + + @Schema(description = "Voltage level id") + private String voltageLevelId; + + @Schema(description = "Feeder bays list") + private List feederBaysAttributeList; + + @Override + public AbstractModification toModification() { + return new MoveVoltageLevelFeederBays(this); + } + + @Override + public ReportNode createSubReportNode(ReportNode reportNode) { + return reportNode.newReportNode() + .withMessageTemplate("network.modification.MOVE_VOLTAGE_LEVEL_FEEDER_BAYS") + .withUntypedValue("voltageLevelId", getVoltageLevelId()) + .add(); + } + + @Override + public Map getMapMessageValues() { + return Map.of("voltageLevelId", getVoltageLevelId()); + } +} diff --git a/src/main/java/org/gridsuite/modification/modifications/BusbarSectionFinderTraverser.java b/src/main/java/org/gridsuite/modification/modifications/BusbarSectionFinderTraverser.java index 78043b3c..fe341fb8 100644 --- a/src/main/java/org/gridsuite/modification/modifications/BusbarSectionFinderTraverser.java +++ b/src/main/java/org/gridsuite/modification/modifications/BusbarSectionFinderTraverser.java @@ -8,8 +8,15 @@ 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 @@ -17,33 +24,67 @@ // FIXME : to remove when this class is available in network-store 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/modification/modifications/MoveVoltageLevelFeederBays.java b/src/main/java/org/gridsuite/modification/modifications/MoveVoltageLevelFeederBays.java new file mode 100644 index 00000000..2d009c44 --- /dev/null +++ b/src/main/java/org/gridsuite/modification/modifications/MoveVoltageLevelFeederBays.java @@ -0,0 +1,165 @@ +/** + * Copyright (c) 2025, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.gridsuite.modification.modifications; + +import com.powsybl.commons.report.ReportNode; +import com.powsybl.iidm.network.*; +import com.powsybl.iidm.network.extensions.ConnectablePosition; +import com.powsybl.iidm.network.extensions.ConnectablePositionAdder; +import org.gridsuite.modification.ModificationType; +import org.gridsuite.modification.NetworkModificationException; +import org.gridsuite.modification.dto.*; +import org.gridsuite.modification.utils.ModificationUtils; + +import static org.gridsuite.modification.NetworkModificationException.Type.MOVE_VOLTAGE_LEVEL_FEEDER_BAYS_ERROR; + +/** + * @author Etienne Lesot + */ +public class MoveVoltageLevelFeederBays extends AbstractModification { + private static final String VOLTAGE_LEVEL_NOT_FOUND = "Voltage level %s is not found"; + private static final String CONNECTABLE_NOT_FOUND = "Connectable %s not found"; + private static final String BUSBAR_NOT_FOUND = "Bus or busbar section %s where connectable %s is supposed to be is not found in voltage level %s"; + private static final String UNSUPPORTED_CONNECTABLE = "ConnectablePositionModification is not implemented for %s"; + private static final String INVALID_CONNECTION_SIDE = "Invalid connection side: %s for branch %s"; + + private final MoveVoltageLevelFeederBaysInfos modificationInfos; + + public MoveVoltageLevelFeederBays(MoveVoltageLevelFeederBaysInfos modificationInfos) { + this.modificationInfos = modificationInfos; + } + + @Override + public void check(Network network) throws NetworkModificationException { + VoltageLevel voltageLevel = checkVoltageLevelOrThrow(network, modificationInfos.getVoltageLevelId()); + for (ConnectablePositionModificationInfos info : modificationInfos.getFeederBaysAttributeList()) { + checkBusOrBusbarSection(voltageLevel, info); + checkConnectable(network, info); + } + } + + private VoltageLevel checkVoltageLevelOrThrow(Network network, String voltageLevelId) { + VoltageLevel voltageLevel = network.getVoltageLevel(voltageLevelId); + if (voltageLevel == null) { + throw new NetworkModificationException(MOVE_VOLTAGE_LEVEL_FEEDER_BAYS_ERROR, String.format(VOLTAGE_LEVEL_NOT_FOUND, voltageLevelId)); + } + return voltageLevel; + } + + private void checkBusOrBusbarSection(VoltageLevel voltageLevel, ConnectablePositionModificationInfos info) { + boolean busOrBusbarSectionExists = voltageLevel.getTopologyKind().equals(TopologyKind.NODE_BREAKER) + ? voltageLevel.getNodeBreakerView().getBusbarSection(info.getBusbarSectionId()) != null + : voltageLevel.getBusBreakerView().getBus(info.getBusbarSectionId()) != null; + if (!busOrBusbarSectionExists) { + throw new NetworkModificationException(MOVE_VOLTAGE_LEVEL_FEEDER_BAYS_ERROR, String.format(BUSBAR_NOT_FOUND, + info.getBusbarSectionId(), info.getEquipmentId(), modificationInfos.getVoltageLevelId())); + } + } + + private void checkConnectable(Network network, ConnectablePositionModificationInfos info) { + Connectable connectable = network.getConnectable(info.getEquipmentId()); + if (connectable == null) { + throw new NetworkModificationException(MOVE_VOLTAGE_LEVEL_FEEDER_BAYS_ERROR, String.format(CONNECTABLE_NOT_FOUND, info.getEquipmentId())); + } + if (!(connectable instanceof Injection) && !(connectable instanceof Branch)) { + throw new NetworkModificationException(MOVE_VOLTAGE_LEVEL_FEEDER_BAYS_ERROR, String.format(UNSUPPORTED_CONNECTABLE, connectable.getClass())); + } + } + + @Override + public void apply(Network network, ReportNode subReportNode) { + for (ConnectablePositionModificationInfos info : modificationInfos.getFeederBaysAttributeList()) { + Connectable connectable = network.getConnectable(info.getEquipmentId()); + switch (connectable) { + case Injection injection -> modifyInjectionConnectablePosition(network, injection, info, subReportNode); + case Branch branch -> modifyBranchConnectablePosition(network, branch, info, subReportNode); + default -> throw new NetworkModificationException(MOVE_VOLTAGE_LEVEL_FEEDER_BAYS_ERROR, String.format(UNSUPPORTED_CONNECTABLE, connectable.getClass())); + } + } + } + + @Override + public String getName() { + return ModificationType.MOVE_VOLTAGE_LEVEL_FEEDER_BAYS.name(); + } + + private void modifyInjectionConnectablePosition(Network network, Injection injection, ConnectablePositionModificationInfos info, ReportNode subReportNode) { + ConnectablePosition connectablePosition = injection.getExtension(ConnectablePosition.class); + ConnectablePositionAdder connectablePositionAdder = injection.newExtension(ConnectablePositionAdder.class); + InjectionModificationInfos injectionModificationInfos = buildInjectionModificationInfos(info); + ModificationUtils.getInstance().modifyInjectionConnectivityAttributes(connectablePosition, connectablePositionAdder, injection, injectionModificationInfos, subReportNode); + moveVoltageLevelBusOrBusbarSection(network, injection, info, subReportNode); + } + + private void modifyBranchConnectablePosition(Network network, Branch branch, ConnectablePositionModificationInfos info, ReportNode subReportNode) { + ConnectablePosition connectablePosition = branch.getExtension(ConnectablePosition.class); + ConnectablePositionAdder connectablePositionAdder = branch.newExtension(ConnectablePositionAdder.class); + BranchModificationInfos branchModificationInfos = buildBranchModificationInfos(info); + ModificationUtils.getInstance().modifyBranchConnectivityAttributes(connectablePosition, connectablePositionAdder, branch, branchModificationInfos, subReportNode); + moveVoltageLevelBusOrBusbarSection(network, (Connectable) branch, info, subReportNode); + } + + private InjectionModificationInfos buildInjectionModificationInfos(ConnectablePositionModificationInfos info) { + InjectionModificationInfos injectionInfos = new InjectionModificationInfos(); + injectionInfos.setEquipmentId(info.getEquipmentId()); + injectionInfos.setConnectionPosition(new AttributeModification<>(info.getConnectionPosition(), OperationType.SET)); + injectionInfos.setConnectionName(new AttributeModification<>(info.getConnectionName(), OperationType.SET)); + injectionInfos.setConnectionDirection(new AttributeModification<>(info.getConnectionDirection(), OperationType.SET)); + return injectionInfos; + } + + private BranchModificationInfos buildBranchModificationInfos(ConnectablePositionModificationInfos info) { + BranchModificationInfos branchInfos = new BranchModificationInfos(); + branchInfos.setEquipmentId(info.getEquipmentId()); + + ThreeSides connectionSide = ThreeSides.valueOf(info.getConnectionSide()); + switch (connectionSide) { + case ONE -> { + branchInfos.setConnectionPosition1(new AttributeModification<>(info.getConnectionPosition(), OperationType.SET)); + branchInfos.setConnectionName1(new AttributeModification<>(info.getConnectionName(), OperationType.SET)); + branchInfos.setConnectionDirection1(new AttributeModification<>(info.getConnectionDirection(), OperationType.SET)); + } + case TWO -> { + branchInfos.setConnectionPosition2(new AttributeModification<>(info.getConnectionPosition(), OperationType.SET)); + branchInfos.setConnectionName2(new AttributeModification<>(info.getConnectionName(), OperationType.SET)); + branchInfos.setConnectionDirection2(new AttributeModification<>(info.getConnectionDirection(), OperationType.SET)); + } + default -> throw new NetworkModificationException(MOVE_VOLTAGE_LEVEL_FEEDER_BAYS_ERROR, String.format(INVALID_CONNECTION_SIDE, info.getConnectionSide(), branchInfos.getEquipmentId())); + } + return branchInfos; + } + + private void moveVoltageLevelBusOrBusbarSection(Network network, Connectable connectable, ConnectablePositionModificationInfos info, ReportNode subReportNode) { + Terminal terminal = getTerminal(network, info); + String currentBusbarId = ModificationUtils.getInstance().getBusOrBusbarSection(terminal); + String targetBusbarId = info.getBusbarSectionId(); + if (!currentBusbarId.equals(targetBusbarId)) { + ModificationUtils.getInstance().modifyVoltageLevelBusOrBusBarSectionAttributes( + connectable, terminal, + new AttributeModification<>(modificationInfos.getVoltageLevelId(), OperationType.SET), + new AttributeModification<>(targetBusbarId, OperationType.SET), + subReportNode); + } + } + + public Terminal getTerminal(Network network, ConnectablePositionModificationInfos info) { + Connectable connectable = network.getConnectable(info.getEquipmentId()); + return switch (connectable) { + case Injection injection -> injection.getTerminal(); + case Branch branch -> getTerminalFromBranch(branch, info); + default -> throw new NetworkModificationException(MOVE_VOLTAGE_LEVEL_FEEDER_BAYS_ERROR, String.format(UNSUPPORTED_CONNECTABLE, connectable.getClass())); + }; + } + + private Terminal getTerminalFromBranch(Branch branch, ConnectablePositionModificationInfos info) { + return switch (ThreeSides.valueOf(info.getConnectionSide())) { + case ONE -> branch.getTerminal1(); + case TWO -> branch.getTerminal2(); + default -> throw new NetworkModificationException(MOVE_VOLTAGE_LEVEL_FEEDER_BAYS_ERROR, String.format(INVALID_CONNECTION_SIDE, info.getConnectionSide(), branch.getId())); + }; + } +} diff --git a/src/main/java/org/gridsuite/modification/utils/ModificationUtils.java b/src/main/java/org/gridsuite/modification/utils/ModificationUtils.java index c2d2bb53..513f4a68 100644 --- a/src/main/java/org/gridsuite/modification/utils/ModificationUtils.java +++ b/src/main/java/org/gridsuite/modification/utils/ModificationUtils.java @@ -723,7 +723,7 @@ public ReportNode modifyBranchConnectivityAttributes(ConnectablePosition conn return reportModifications(connectivityReports, reports, "network.modification.ConnectivityModified"); } - private void processConnectivityPosition(ConnectablePosition connectablePosition, + public void processConnectivityPosition(ConnectablePosition connectablePosition, ConnectablePositionAdder connectablePositionAdder, BasicEquipmentModificationInfos modificationInfos, Network network, @@ -950,9 +950,9 @@ public String getBusOrBusbarSection(Terminal terminal) { } private String getBusbarSectionId(Terminal terminal) { - BusbarSectionFinderTraverser connectedBusbarSectionFinder = new BusbarSectionFinderTraverser(terminal.isConnected()); - terminal.traverse(connectedBusbarSectionFinder, TraversalType.BREADTH_FIRST); - return connectedBusbarSectionFinder.getFirstTraversedBbsId(); + BusbarSectionFinderTraverser finder = new BusbarSectionFinderTraverser(); + terminal.traverse(finder, TraversalType.BREADTH_FIRST); + return finder.getBusbarWithClosedDisconnector(); } private int getPosition(AttributeModification connectionPosition, diff --git a/src/main/resources/org/gridsuite/modification/reports.properties b/src/main/resources/org/gridsuite/modification/reports.properties index e19efc49..ee73f729 100644 --- a/src/main/resources/org/gridsuite/modification/reports.properties +++ b/src/main/resources/org/gridsuite/modification/reports.properties @@ -84,6 +84,7 @@ network.modification.TotalGeneratorUnchangedTargetP = ${nbUnchangedGenerator} el network.modification.TotalOutwardHvdcFlow = The HVDC balance is : ${hvdcBalance} MW network.modification.TwoWindingsTransformerProperties = Properties network.modification.VOLTAGE_LEVEL_TOPOLOGY_MODIFICATION = Voltage Level topology modification ${voltageLevelId} +network.modification.MOVE_VOLTAGE_LEVEL_FEEDER_BAYS = Move voltage level feeder bays modification ${voltageLevelId} network.modification.VlProperties = Properties network.modification.VoltageRegulationCreated = Voltage regulation network.modification.VoltageRegulationOn = ${status} diff --git a/src/test/java/org/gridsuite/modification/modifications/GeneratorModificationTest.java b/src/test/java/org/gridsuite/modification/modifications/GeneratorModificationTest.java index 9128efea..ae2dc770 100644 --- a/src/test/java/org/gridsuite/modification/modifications/GeneratorModificationTest.java +++ b/src/test/java/org/gridsuite/modification/modifications/GeneratorModificationTest.java @@ -285,9 +285,9 @@ void testMoveFeederBay() { Generator generator = getNetwork().getGenerator("idGenerator"); Terminal terminal = generator.getTerminal(); assertEquals("v2", terminal.getVoltageLevel().getId()); - BusbarSectionFinderTraverser connectedBusbarSectionFinder = new BusbarSectionFinderTraverser(terminal.isConnected()); + BusbarSectionFinderTraverser connectedBusbarSectionFinder = new BusbarSectionFinderTraverser(); generator.getTerminal().traverse(connectedBusbarSectionFinder, TraversalType.BREADTH_FIRST); - assertEquals("1A", connectedBusbarSectionFinder.getFirstTraversedBbsId()); + assertEquals("1A", connectedBusbarSectionFinder.getBusbarWithClosedDisconnector()); // Change voltageLevel and busbar section generatorModificationInfos.setVoltageLevelId(new AttributeModification<>("v1", OperationType.SET)); @@ -296,8 +296,8 @@ void testMoveFeederBay() { generator = getNetwork().getGenerator("idGenerator"); terminal = generator.getTerminal(); assertEquals("v1", terminal.getVoltageLevel().getId()); - connectedBusbarSectionFinder = new BusbarSectionFinderTraverser(terminal.isConnected()); + connectedBusbarSectionFinder = new BusbarSectionFinderTraverser(); terminal.traverse(connectedBusbarSectionFinder, TraversalType.BREADTH_FIRST); - assertEquals("1.1", connectedBusbarSectionFinder.getFirstTraversedBbsId()); + assertEquals("1.1", connectedBusbarSectionFinder.getBusbarWithClosedDisconnector()); } } diff --git a/src/test/java/org/gridsuite/modification/modifications/MoveVoltageLevelFeederBaysTest.java b/src/test/java/org/gridsuite/modification/modifications/MoveVoltageLevelFeederBaysTest.java new file mode 100644 index 00000000..440cc1a4 --- /dev/null +++ b/src/test/java/org/gridsuite/modification/modifications/MoveVoltageLevelFeederBaysTest.java @@ -0,0 +1,294 @@ +/** + * Copyright (c) 2025, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.gridsuite.modification.modifications; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.powsybl.commons.report.ReportNode; +import com.powsybl.iidm.network.*; +import com.powsybl.iidm.network.extensions.ConnectablePosition; +import org.gridsuite.modification.NetworkModificationException; +import org.gridsuite.modification.dto.ConnectablePositionModificationInfos; +import org.gridsuite.modification.dto.ModificationInfos; +import org.gridsuite.modification.dto.MoveVoltageLevelFeederBaysInfos; +import org.gridsuite.modification.utils.ModificationUtils; +import org.gridsuite.modification.utils.NetworkCreation; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import static org.gridsuite.modification.ModificationType.MOVE_VOLTAGE_LEVEL_FEEDER_BAYS; +import static org.gridsuite.modification.utils.NetworkUtil.createBusBarSection; +import static org.gridsuite.modification.utils.TestUtils.assertLogMessage; +import static org.junit.jupiter.api.Assertions.*; + +/** + * @author Etienne Lesot + */ +class MoveVoltageLevelFeederBaysTest extends AbstractNetworkModificationTest { + + @Override + protected Network createNetwork(UUID networkUuid) { + Network network = NetworkCreation.create(networkUuid, true); + createBusBarSection(network.getVoltageLevel("v3"), "3B", "3B", 20); + createBusBarSection(network.getVoltageLevel("v1"), "1.2", "1.2", 20); + return network; + } + + @Override + protected ModificationInfos buildModification() { + List connectablePositionModificationInfos = new ArrayList<>(); + connectablePositionModificationInfos.add(ConnectablePositionModificationInfos.builder().equipmentId("v3load") + .busbarSectionId("3B") + .connectionSide(null) + .connectionName("v3loadrename") + .connectionPosition(4) + .connectionDirection(ConnectablePosition.Direction.TOP) + .build()); + connectablePositionModificationInfos.add(ConnectablePositionModificationInfos.builder() + .equipmentId("line2") + .busbarSectionId("3B") + .connectionSide(ThreeSides.TWO.toString()) + .connectionName("line2NameV3") + .connectionPosition(10) + .connectionDirection(ConnectablePosition.Direction.BOTTOM) + .build()); + return MoveVoltageLevelFeederBaysInfos.builder() + .voltageLevelId("v3") + .feederBaysAttributeList(connectablePositionModificationInfos) + .build(); + } + + @Override + protected void assertAfterNetworkModificationApplication() { + // v3load + Load v3load = getNetwork().getLoad("v3load"); + assertNotNull(v3load); + String busbarId = ModificationUtils.getInstance().getBusOrBusbarSection(v3load.getTerminal()); + assertEquals("3B", busbarId); + ConnectablePosition connectablePosition = v3load.getExtension(ConnectablePosition.class); + assertNotNull(connectablePosition); + ConnectablePosition.Feeder feeder = connectablePosition.getFeeder(); + assertNotNull(feeder); + assertTrue(feeder.getName().isPresent()); + assertEquals("v3loadrename", feeder.getName().get()); + assertTrue(feeder.getOrder().isPresent()); + assertEquals(4, feeder.getOrder().get()); + assertEquals(ConnectablePosition.Direction.TOP, feeder.getDirection()); + // line2 + Line line2 = getNetwork().getLine("line2"); + assertNotNull(line2); + String line2BusbarId = ModificationUtils.getInstance().getBusOrBusbarSection(line2.getTerminal2()); + assertEquals("3B", line2BusbarId); + ConnectablePosition lineConnectablePosition = line2.getExtension(ConnectablePosition.class); + assertNotNull(lineConnectablePosition); + ConnectablePosition.Feeder feeder2 = lineConnectablePosition.getFeeder2(); + assertNotNull(feeder2); + assertTrue(feeder2.getName().isPresent()); + assertEquals("line2NameV3", feeder2.getName().get()); + assertTrue(feeder2.getOrder().isPresent()); + assertEquals(10, feeder2.getOrder().get()); + assertEquals(ConnectablePosition.Direction.BOTTOM, feeder2.getDirection()); + } + + @Override + protected void checkModification() { + Network network = getNetwork(); + testConnectableNotInjectionOrBranch(network); + testConnectableNotFound(network); + testVoltageLevelNotFound(network); + testBusBarNotFound(network); + } + + private void testVoltageLevelNotFound(Network network) { + MoveVoltageLevelFeederBaysInfos moveVoltageLevelFeederBaysInfos = MoveVoltageLevelFeederBaysInfos.builder() + .voltageLevelId("notFound") + .build(); + MoveVoltageLevelFeederBays moveVoltageLevelFeederBays = (MoveVoltageLevelFeederBays) moveVoltageLevelFeederBaysInfos.toModification(); + String message = assertThrows(NetworkModificationException.class, () -> moveVoltageLevelFeederBays.check(network)).getMessage(); + assertEquals("MOVE_VOLTAGE_LEVEL_FEEDER_BAYS_ERROR : Voltage level notFound is not found", message); + } + + private void testBusBarNotFound(Network network) { + List connectablePositionModificationInfos = new ArrayList<>(); + connectablePositionModificationInfos.add(ConnectablePositionModificationInfos.builder() + .equipmentId("v3load") + .busbarSectionId("notFound") + .connectionSide("3B") + .connectionName("v3loadrename") + .connectionPosition(4) + .connectionDirection(ConnectablePosition.Direction.TOP) + .build()); + MoveVoltageLevelFeederBaysInfos moveVoltageLevelFeederBaysInfos = MoveVoltageLevelFeederBaysInfos.builder() + .voltageLevelId("v3") + .feederBaysAttributeList(connectablePositionModificationInfos) + .build(); + MoveVoltageLevelFeederBays moveVoltageLevelFeederBays = (MoveVoltageLevelFeederBays) moveVoltageLevelFeederBaysInfos.toModification(); + String message = assertThrows(NetworkModificationException.class, () -> moveVoltageLevelFeederBays.check(network)).getMessage(); + assertEquals("MOVE_VOLTAGE_LEVEL_FEEDER_BAYS_ERROR : Bus or busbar section notFound where connectable v3load is supposed to be is not found in voltage level v3", message); + } + + private void testConnectableNotFound(Network network) { + List connectablePositionModificationInfos = new ArrayList<>(); + connectablePositionModificationInfos.add(ConnectablePositionModificationInfos.builder() + .equipmentId("notFound") + .busbarSectionId("3A") + .connectionSide(null) + .connectionName("notFoundMoved") + .connectionPosition(4) + .connectionDirection(ConnectablePosition.Direction.TOP) + .build()); + MoveVoltageLevelFeederBaysInfos moveVoltageLevelFeederBaysInfos = MoveVoltageLevelFeederBaysInfos.builder() + .voltageLevelId("v3") + .feederBaysAttributeList(connectablePositionModificationInfos) + .build(); + MoveVoltageLevelFeederBays moveVoltageLevelFeederBays = (MoveVoltageLevelFeederBays) moveVoltageLevelFeederBaysInfos.toModification(); + String message = assertThrows(NetworkModificationException.class, () -> moveVoltageLevelFeederBays.check(network)).getMessage(); + assertEquals("MOVE_VOLTAGE_LEVEL_FEEDER_BAYS_ERROR : Connectable notFound not found", message); + } + + private void testConnectableNotInjectionOrBranch(Network network) { + List connectablePositionModificationInfos = new ArrayList<>(); + connectablePositionModificationInfos.add(ConnectablePositionModificationInfos.builder() + .equipmentId("trf6") + .busbarSectionId("1A") + .connectionSide("ONE") + .connectionName("trf6Moved") + .connectionPosition(4) + .connectionDirection(ConnectablePosition.Direction.TOP) + .build()); + MoveVoltageLevelFeederBaysInfos moveVoltageLevelFeederBaysInfos = MoveVoltageLevelFeederBaysInfos.builder() + .voltageLevelId("v2") + .feederBaysAttributeList(connectablePositionModificationInfos) + .build(); + MoveVoltageLevelFeederBays moveVoltageLevelFeederBays = (MoveVoltageLevelFeederBays) moveVoltageLevelFeederBaysInfos.toModification(); + String message = assertThrows(NetworkModificationException.class, () -> moveVoltageLevelFeederBays.check(network)).getMessage(); + assertEquals("MOVE_VOLTAGE_LEVEL_FEEDER_BAYS_ERROR : ConnectablePositionModification is not implemented for class com.powsybl.network.store.iidm.impl.ThreeWindingsTransformerImpl", message); + } + + @Override + protected void testCreationModificationMessage(ModificationInfos modificationInfos) throws Exception { + assertEquals("MOVE_VOLTAGE_LEVEL_FEEDER_BAYS", modificationInfos.getMessageType()); + Map createdValues = mapper.readValue(modificationInfos.getMessageValues(), new TypeReference<>() { }); + assertEquals("v3", createdValues.get("voltageLevelId")); + } + + @Test + void testGetTerminal() { + Network network = getNetwork(); + // is not an injection or a branch + ConnectablePositionModificationInfos transformerInfo = ConnectablePositionModificationInfos.builder() + .equipmentId("trf6") + .busbarSectionId("1A") + .connectionSide("ONE") + .connectionName("trf6Moved") + .connectionPosition(4) + .connectionDirection(ConnectablePosition.Direction.TOP) + .build(); + MoveVoltageLevelFeederBaysInfos moveVoltageLevelFeederBaysInfos = MoveVoltageLevelFeederBaysInfos.builder() + .voltageLevelId("v2") + .feederBaysAttributeList(List.of(transformerInfo)) + .build(); + MoveVoltageLevelFeederBays moveVoltageLevelFeederBays = (MoveVoltageLevelFeederBays) moveVoltageLevelFeederBaysInfos.toModification(); + assertEquals("MOVE_VOLTAGE_LEVEL_FEEDER_BAYS", moveVoltageLevelFeederBays.getName()); + String message = assertThrows(NetworkModificationException.class, () -> moveVoltageLevelFeederBays.getTerminal(network, transformerInfo)).getMessage(); + assertEquals("MOVE_VOLTAGE_LEVEL_FEEDER_BAYS_ERROR : ConnectablePositionModification is not implemented for class com.powsybl.network.store.iidm.impl.ThreeWindingsTransformerImpl", message); + ConnectablePositionModificationInfos invalidSideInfo = ConnectablePositionModificationInfos.builder() + .equipmentId("line1") + .busbarSectionId("random") + .connectionSide("THREE") + .connectionName("line1") + .connectionPosition(4) + .connectionDirection(ConnectablePosition.Direction.TOP) + .build(); + message = assertThrows(NetworkModificationException.class, () -> moveVoltageLevelFeederBays.getTerminal(network, invalidSideInfo)).getMessage(); + assertEquals("MOVE_VOLTAGE_LEVEL_FEEDER_BAYS_ERROR : Invalid connection side: THREE for branch line1", message); + ConnectablePositionModificationInfos injectionInfo = ConnectablePositionModificationInfos.builder() + .equipmentId("v3Battery") + .busbarSectionId("3A") + .connectionSide(null) + .connectionName("v3Battery") + .connectionPosition(4) + .connectionDirection(ConnectablePosition.Direction.TOP) + .build(); + Terminal terminal = moveVoltageLevelFeederBays.getTerminal(network, injectionInfo); + assertEquals("v3", terminal.getVoltageLevel().getId()); + assertEquals("v3Battery", terminal.getConnectable().getId()); + // branch side 2 with no error + ConnectablePositionModificationInfos branchSide2Info = ConnectablePositionModificationInfos.builder() + .equipmentId("line1") + .busbarSectionId("1A") + .connectionSide("TWO") + .connectionName("line1") + .connectionPosition(4) + .connectionDirection(ConnectablePosition.Direction.TOP) + .build(); + terminal = moveVoltageLevelFeederBays.getTerminal(network, branchSide2Info); + assertEquals("v4", terminal.getVoltageLevel().getId()); + assertEquals("line1", terminal.getConnectable().getId()); + assertEquals(TwoSides.TWO, network.getLine("line1").getSide(terminal)); + // branch side 1 with no error + ConnectablePositionModificationInfos branchSide1Info = ConnectablePositionModificationInfos.builder() + .equipmentId("line2") + .busbarSectionId("1.1") + .connectionSide("ONE") + .connectionName("line2") + .connectionPosition(21) + .connectionDirection(ConnectablePosition.Direction.BOTTOM) + .build(); + terminal = moveVoltageLevelFeederBays.getTerminal(network, branchSide1Info); + assertEquals("v1", terminal.getVoltageLevel().getId()); + assertEquals("line2", terminal.getConnectable().getId()); + assertEquals(TwoSides.ONE, network.getLine("line2").getSide(terminal)); + } + + @Test + void testCreateSubReportNode() { + ReportNode reportNode = ReportNode.newRootReportNode() + .withAllResourceBundlesFromClasspath() + .withMessageTemplate("test") + .build(); + MoveVoltageLevelFeederBaysInfos modification = (MoveVoltageLevelFeederBaysInfos) buildModification(); + MoveVoltageLevelFeederBays moveVoltageLevelFeederBays = (MoveVoltageLevelFeederBays) modification.toModification(); + assertEquals(MOVE_VOLTAGE_LEVEL_FEEDER_BAYS.toString(), moveVoltageLevelFeederBays.getName()); + modification.createSubReportNode(reportNode); + assertLogMessage("Move voltage level feeder bays modification v3", "network.modification.MOVE_VOLTAGE_LEVEL_FEEDER_BAYS", reportNode); + } + + @Test + void testConnectablePositionModificationOnSide1() { + Network network = getNetwork(); + ConnectablePositionModificationInfos connectablePositionModificationInfos = ConnectablePositionModificationInfos.builder() + .equipmentId("line2") + .busbarSectionId("1.1") + .connectionSide("TWO") + .connectionName("line2test") + .connectionPosition(21) + .connectionDirection(ConnectablePosition.Direction.BOTTOM) + .build(); + MoveVoltageLevelFeederBays moveVoltageLevelFeederBays = new MoveVoltageLevelFeederBays(MoveVoltageLevelFeederBaysInfos.builder() + .voltageLevelId("v1") + .feederBaysAttributeList(List.of(connectablePositionModificationInfos)) + .build()); + moveVoltageLevelFeederBays.apply(network); + Line line = network.getLine("line2"); + assertNotNull(line); + String line2BusbarId = ModificationUtils.getInstance().getBusOrBusbarSection(line.getTerminal1()); + assertEquals("1.1", line2BusbarId); + ConnectablePosition lineConnectablePosition = line.getExtension(ConnectablePosition.class); + assertNotNull(lineConnectablePosition); + ConnectablePosition.Feeder feeder1 = lineConnectablePosition.getFeeder1(); + assertNotNull(feeder1); + assertTrue(feeder1.getName().isPresent()); + assertEquals("cn1line2", feeder1.getName().get()); + assertTrue(feeder1.getOrder().isPresent()); + assertEquals(2, feeder1.getOrder().get()); + assertEquals(ConnectablePosition.Direction.TOP, feeder1.getDirection()); + } +} diff --git a/src/test/java/org/gridsuite/modification/modifications/TwoWindingsTransformerModificationTest.java b/src/test/java/org/gridsuite/modification/modifications/TwoWindingsTransformerModificationTest.java index e160a0b6..02c6fcb9 100644 --- a/src/test/java/org/gridsuite/modification/modifications/TwoWindingsTransformerModificationTest.java +++ b/src/test/java/org/gridsuite/modification/modifications/TwoWindingsTransformerModificationTest.java @@ -982,12 +982,12 @@ void testMoveFeederBay() { Terminal terminal2 = twoWindingsTransformer.getTerminal2(); assertEquals("v1", twoWindingsTransformer.getTerminal1().getVoltageLevel().getId()); assertEquals("v2", terminal2.getVoltageLevel().getId()); - BusbarSectionFinderTraverser connectedBusbarSectionFinder1 = new BusbarSectionFinderTraverser(terminal1.isConnected()); + BusbarSectionFinderTraverser connectedBusbarSectionFinder1 = new BusbarSectionFinderTraverser(); terminal1.traverse(connectedBusbarSectionFinder1, TraversalType.BREADTH_FIRST); - assertEquals("1.1", connectedBusbarSectionFinder1.getFirstTraversedBbsId()); - BusbarSectionFinderTraverser connectedBusbarSectionFinder2 = new BusbarSectionFinderTraverser(terminal2.isConnected()); + assertEquals("1.1", connectedBusbarSectionFinder1.getBusbarWithClosedDisconnector()); + BusbarSectionFinderTraverser connectedBusbarSectionFinder2 = new BusbarSectionFinderTraverser(); terminal2.traverse(connectedBusbarSectionFinder2, TraversalType.BREADTH_FIRST); - assertEquals("1A", connectedBusbarSectionFinder2.getFirstTraversedBbsId()); + assertEquals("1A", connectedBusbarSectionFinder2.getBusbarWithClosedDisconnector()); } }