diff --git a/iidm/iidm-api/src/main/java/com/powsybl/iidm/network/VoltageLevel.java b/iidm/iidm-api/src/main/java/com/powsybl/iidm/network/VoltageLevel.java index 053007a2326..f22d1d884d9 100644 --- a/iidm/iidm-api/src/main/java/com/powsybl/iidm/network/VoltageLevel.java +++ b/iidm/iidm-api/src/main/java/com/powsybl/iidm/network/VoltageLevel.java @@ -1355,6 +1355,15 @@ default void remove() { */ BusView getBusView(); + /** + * Convert the topology model to another one. Notice that when converting from a + * node/breaker model to a bus/breaker model, we definitely lost some information as + * we are converting to a simpler topology model. + * + * @param newTopologyKind the new topology model kind + */ + void convertToTopology(TopologyKind newTopologyKind); + /** * Print an ASCII representation of the topology on the standard ouput. */ diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AbstractConnectable.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AbstractConnectable.java index 4a98b6c6f07..22032cb9d09 100644 --- a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AbstractConnectable.java +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AbstractConnectable.java @@ -132,7 +132,7 @@ public void allocateVariantArrayElement(int[] indexes, int sourceIndex) { } } - protected void move(TerminalExt oldTerminal, TopologyPoint oldTopologyPoint, int node, String voltageLevelId) { + protected void move(TerminalExt oldTerminal, int node, String voltageLevelId) { VoltageLevelExt voltageLevel = getNetwork().getVoltageLevel(voltageLevelId); if (voltageLevel == null) { throw new PowsyblException("Voltage level '" + voltageLevelId + "' not found"); @@ -152,10 +152,10 @@ protected void move(TerminalExt oldTerminal, TopologyPoint oldTopologyPoint, int .build(); // detach the terminal from its previous voltage level - attachTerminal(oldTerminal, oldTopologyPoint, voltageLevel, terminalExt); + replaceTerminal(oldTerminal, voltageLevel.getTopologyModel(), terminalExt, true); } - protected void move(TerminalExt oldTerminal, TopologyPoint oldTopologyPoint, String busId, boolean connected) { + protected void move(TerminalExt oldTerminal, String busId, boolean connected) { Bus bus = getNetwork().getBusBreakerView().getBus(busId); if (bus == null) { throw new PowsyblException("Bus '" + busId + "' not found"); @@ -175,22 +175,43 @@ protected void move(TerminalExt oldTerminal, TopologyPoint oldTopologyPoint, Str .build(); // detach the terminal from its previous voltage level - attachTerminal(oldTerminal, oldTopologyPoint, (VoltageLevelExt) bus.getVoltageLevel(), terminalExt); + replaceTerminal(oldTerminal, ((VoltageLevelExt) bus.getVoltageLevel()).getTopologyModel(), terminalExt, true); } - private void attachTerminal(TerminalExt oldTerminal, TopologyPoint oldTopologyPoint, VoltageLevelExt voltageLevel, TerminalExt terminalExt) { + void replaceTerminal(TerminalExt oldTerminal, TopologyPoint oldTopologyPoint, TerminalExt newTerminalExt, boolean notify) { + Objects.requireNonNull(oldTerminal); + Objects.requireNonNull(newTerminalExt); + int iSide = terminals.indexOf(oldTerminal); + if (iSide == -1) { + throw new PowsyblException("Terminal to replace not found"); + } + terminals.set(iSide, newTerminalExt); + + if (notify) { + notifyUpdate("terminal" + (iSide + 1), oldTopologyPoint, newTerminalExt.getTopologyPoint()); + } + } + + void replaceTerminal(TerminalExt oldTerminal, TopologyModel newTopologyModel, TerminalExt newTerminalExt, boolean notify) { + Objects.requireNonNull(oldTerminal); + Objects.requireNonNull(newTopologyModel); + Objects.requireNonNull(newTerminalExt); + // first, attach new terminal to connectable and to voltage level of destination, to ensure that the new terminal is valid - terminalExt.setConnectable(this); - voltageLevel.getTopologyModel().attach(terminalExt, false); + newTerminalExt.setConnectable(this); + newTopologyModel.attach(newTerminalExt, false); // then we can detach the old terminal, as we now know that the new terminal is valid + TopologyPoint oldTopologyPoint = oldTerminal.getTopologyPoint(); oldTerminal.getVoltageLevel().getTopologyModel().detach(oldTerminal); // replace the old terminal by the new terminal in the connectable - int iSide = terminals.indexOf(oldTerminal); - terminals.set(iSide, terminalExt); + replaceTerminal(oldTerminal, oldTopologyPoint, newTerminalExt, notify); - notifyUpdate("terminal" + (iSide + 1), oldTopologyPoint, terminalExt.getTopologyPoint()); + // also update terminal referrers + for (Referrer referrer : oldTerminal.getReferrerManager().getReferrers()) { + referrer.onReferencedReplacement(oldTerminal, newTerminalExt); + } } @Override diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AbstractIidmExtension.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AbstractIidmExtension.java index f7a91350642..6e0bdf483c0 100644 --- a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AbstractIidmExtension.java +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AbstractIidmExtension.java @@ -25,4 +25,9 @@ public void onReferencedRemoval(Terminal removedTerminal) { // nothing by default // this is the place for terminal reference cleanup } + + @Override + public void onReferencedReplacement(Terminal oldReferenced, Terminal newReferenced) { + // nothing by default + } } diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AreaBoundaryImpl.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AreaBoundaryImpl.java index 277bd3e6e7b..dc8fd5b1494 100644 --- a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AreaBoundaryImpl.java +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AreaBoundaryImpl.java @@ -17,13 +17,13 @@ public class AreaBoundaryImpl implements AreaBoundary { - final Area area; + private final Area area; - final Terminal terminal; + private Terminal terminal; - final Boundary boundary; + private final Boundary boundary; - final boolean ac; + private final boolean ac; AreaBoundaryImpl(Area area, Terminal terminal, boolean ac) { this.area = Objects.requireNonNull(area); @@ -68,4 +68,12 @@ public double getP() { public double getQ() { return boundary != null ? boundary.getQ() : terminal.getQ(); } + + void replaceTerminal(Terminal oldTerminal, Terminal newTerminal) { + Objects.requireNonNull(oldTerminal); + Objects.requireNonNull(newTerminal); + if (terminal == oldTerminal) { + terminal = newTerminal; + } + } } diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AreaImpl.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AreaImpl.java index 123f451ee98..4de343b8956 100644 --- a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AreaImpl.java +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AreaImpl.java @@ -34,9 +34,31 @@ public class AreaImpl extends AbstractIdentifiable implements Area { private final TDoubleArrayList interchangeTarget; - private final Referrer terminalReferrer = this::removeAreaBoundary; + private final Referrer terminalReferrer = new Referrer<>() { + @Override + public void onReferencedRemoval(Terminal terminal) { + removeAreaBoundary(terminal); + } + + @Override + public void onReferencedReplacement(Terminal oldTerminal, Terminal newTerminal) { + for (AreaBoundary areaBoundary : areaBoundaries) { + ((AreaBoundaryImpl) areaBoundary).replaceTerminal(oldTerminal, newTerminal); + } + } + }; - private final Referrer boundaryReferrer = this::removeAreaBoundary; + private final Referrer boundaryReferrer = new Referrer<>() { + @Override + public void onReferencedRemoval(Boundary boundary) { + removeAreaBoundary(boundary); + } + + @Override + public void onReferencedReplacement(Boundary oldBoundary, Boundary newBoundary) { + // cannot happen + } + }; AreaImpl(Ref ref, Ref subnetworkRef, String id, String name, boolean fictitious, String areaType, double interchangeTarget) { diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/BusBreakerTopologyModel.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/BusBreakerTopologyModel.java index 95d1a89dd97..884335d11de 100644 --- a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/BusBreakerTopologyModel.java +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/BusBreakerTopologyModel.java @@ -100,7 +100,8 @@ public Switch add() { } SwitchImpl aSwitch = new SwitchImpl(voltageLevel, id, getName(), isFictitious(), SwitchKind.BREAKER, open, true); - addSwitch(aSwitch, busId1, busId2); + getNetwork().getIndex().checkAndAdd(aSwitch); + addSwitchToTopology(aSwitch, busId1, busId2); getNetwork().getListeners().notifyCreation(aSwitch); return aSwitch; } @@ -797,10 +798,9 @@ private void removeAllBuses() { removedBusesIds.forEach(id -> network.getListeners().notifyAfterRemoval(id)); } - private void addSwitch(SwitchImpl aSwitch, String busId1, String busId2) { + void addSwitchToTopology(SwitchImpl aSwitch, String busId1, String busId2) { int v1 = getVertex(busId1, true); int v2 = getVertex(busId2, true); - getNetwork().getIndex().checkAndAdd(aSwitch); int e = graph.addEdge(v1, v2, aSwitch); switches.put(aSwitch.getId(), e); } diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/BusTerminal.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/BusTerminal.java index 383023ceea8..15509ef1642 100644 --- a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/BusTerminal.java +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/BusTerminal.java @@ -41,7 +41,7 @@ public void moveConnectable(int node, String voltageLevelId) { if (removed) { throw new PowsyblException(UNMODIFIABLE_REMOVED_EQUIPMENT + connectable.id); } - getConnectable().move(BusTerminal.this, getTopologyPoint(), node, voltageLevelId); + getConnectable().move(BusTerminal.this, node, voltageLevelId); } }; @@ -87,7 +87,7 @@ public void moveConnectable(String busId, boolean connected) { if (removed) { throw new PowsyblException(UNMODIFIABLE_REMOVED_EQUIPMENT + connectable.id); } - getConnectable().move(BusTerminal.this, getTopologyPoint(), busId, connected); + getConnectable().move(BusTerminal.this, busId, connected); } }; diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/NodeBreakerTopologyModel.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/NodeBreakerTopologyModel.java index e58a76de39c..d9e1b2c9567 100644 --- a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/NodeBreakerTopologyModel.java +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/NodeBreakerTopologyModel.java @@ -626,6 +626,16 @@ CalculatedBusTopology getCalculatedBusTopology() { return variants.get().calculatedBusTopology; } + void removeSwitchFromTopology(String switchId, boolean notify) { + Integer e = switches.get(switchId); + if (e == null) { + throw new PowsyblException("Switch '" + switchId + + "' not found in voltage level '" + voltageLevel.getId() + "'"); + } + graph.removeEdge(e, notify); + graph.removeIsolatedVertices(notify); + } + private final VoltageLevelExt.NodeBreakerViewExt nodeBreakerView = new VoltageLevelExt.NodeBreakerViewExt() { private final TIntObjectMap fictitiousP0ByNode = TCollections.synchronizedMap(new TIntObjectHashMap<>()); @@ -873,13 +883,7 @@ public int getSwitchCount() { @Override public void removeSwitch(String switchId) { - Integer e = switches.get(switchId); - if (e == null) { - throw new PowsyblException("Switch '" + switchId - + "' not found in voltage level '" + voltageLevel.getId() + "'"); - } - graph.removeEdge(e); - graph.removeIsolatedVertices(); + NodeBreakerTopologyModel.this.removeSwitchFromTopology(switchId, true); } @Override diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/NodeTerminal.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/NodeTerminal.java index e1acd93912c..da48badb779 100644 --- a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/NodeTerminal.java +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/NodeTerminal.java @@ -53,7 +53,7 @@ public void moveConnectable(int node, String voltageLevelId) { if (removed) { throw new PowsyblException(UNMODIFIABLE_REMOVED_EQUIPMENT + connectable.id); } - getConnectable().move(NodeTerminal.this, getTopologyPoint(), node, voltageLevelId); + getConnectable().move(NodeTerminal.this, node, voltageLevelId); } }; @@ -89,7 +89,7 @@ public void moveConnectable(String busId, boolean connected) { if (removed) { throw new PowsyblException(UNMODIFIABLE_REMOVED_EQUIPMENT + connectable.id); } - getConnectable().move(NodeTerminal.this, getTopologyPoint(), busId, connected); + getConnectable().move(NodeTerminal.this, busId, connected); } }; diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/Referrer.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/Referrer.java index e9e29e36c4d..88e9bd0003c 100644 --- a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/Referrer.java +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/Referrer.java @@ -20,4 +20,14 @@ public interface Referrer { * @param removedReferenced The referenced that has been removed from the network. */ void onReferencedRemoval(T removedReferenced); + + /** + * Called when a referenced object is replaced with another one. Implementations of this method + * should handle any required updates or transfers necessary when the referenced object is + * replaced. + * + * @param oldReferenced The original referenced object that is being replaced. + * @param newReferenced The new referenced object that is taking the place of the old one. + */ + void onReferencedReplacement(T oldReferenced, T newReferenced); } diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/RegulatingPoint.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/RegulatingPoint.java index 54a69e96ecd..0301d5a0fef 100644 --- a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/RegulatingPoint.java +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/RegulatingPoint.java @@ -164,4 +164,11 @@ public void onReferencedRemoval(Terminal removedTerminal) { regulationMode.fill(0, regulationMode.size(), StaticVarCompensator.RegulationMode.OFF.ordinal()); } } + + @Override + public void onReferencedReplacement(Terminal oldReferenced, Terminal newReferenced) { + if (regulatingTerminal == oldReferenced) { + regulatingTerminal = (TerminalExt) newReferenced; + } + } } diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/VoltageLevelImpl.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/VoltageLevelImpl.java index 7fc379bc153..88e2c5b3936 100644 --- a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/VoltageLevelImpl.java +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/VoltageLevelImpl.java @@ -10,8 +10,8 @@ import com.google.common.collect.Lists; import com.google.common.primitives.Ints; import com.powsybl.commons.PowsyblException; -import com.powsybl.iidm.network.*; import com.powsybl.commons.ref.Ref; +import com.powsybl.iidm.network.*; import com.powsybl.iidm.network.util.ShortIdDictionary; import java.io.IOException; @@ -39,7 +39,7 @@ class VoltageLevelImpl extends AbstractIdentifiable implements Vol private double highVoltageLimit; - private final AbstractTopologyModel topologyModel; + private AbstractTopologyModel topologyModel; /** Areas associated to this VoltageLevel, with at most one area for each area type */ private final Set areas = new LinkedHashSet<>(); @@ -618,4 +618,83 @@ public void allocateVariantArrayElement(int[] indexes, int sourceIndex) { super.allocateVariantArrayElement(indexes, sourceIndex); topologyModel.allocateVariantArrayElement(indexes, sourceIndex); } + + private void convertToBusBreakerModel() { + BusBreakerTopologyModel newTopologyModel = new BusBreakerTopologyModel(this); + + // remove busbar sections because not needed in a bus/breaker topology + for (BusbarSection bbs : topologyModel.getNodeBreakerView().getBusbarSections()) { + bbs.remove(); + } + + for (Bus bus : topologyModel.getBusBreakerView().getBuses()) { + // no notification, this is just a mutation of a calculation bus to a configured bus + ConfiguredBusImpl configuredBus = new ConfiguredBusImpl(bus.getId(), bus.getOptionalName().orElse(null), bus.isFictitious(), this); + newTopologyModel.addBus(configuredBus); + } + + // transfer retained switches + for (Switch sw : topologyModel.getBusBreakerView().getSwitchStream().toList()) { + String busId1 = topologyModel.getBusBreakerView().getBus1(sw.getId()).getId(); + String busId2 = topologyModel.getBusBreakerView().getBus2(sw.getId()).getId(); + // no notification, this is just a transfer + ((NodeBreakerTopologyModel) topologyModel).removeSwitchFromTopology(sw.getId(), false); + newTopologyModel.addSwitchToTopology((SwitchImpl) sw, busId1, busId2); + } + + // reconnect all connectable to new topology model + // first store all bus/breaker topological infos associated to this terminal because we will start moving + // terminal from old mode to new one, it will modify the old topology model + record TopologyModelInfos(TerminalExt terminal, String connectableBusId, boolean connected) { + } + List oldTopologyModelInfos = new ArrayList<>(); + for (Terminal oldTerminal : topologyModel.getTerminals()) { + Bus connectableBus = oldTerminal.getBusBreakerView().getConnectableBus(); + String connectableBusId = connectableBus == null ? null : connectableBus.getId(); + boolean connected = oldTerminal.isConnected(); + oldTopologyModelInfos.add(new TopologyModelInfos((TerminalExt) oldTerminal, connectableBusId, connected)); + } + + for (var infos : oldTopologyModelInfos) { + TerminalExt oldTerminalExt = infos.terminal(); + + // if there is no way to find a connectable bus, remove the connectable + // an alternative would be to connect them all to a new trash configured bus + if (infos.connectableBusId() == null) { + // here keep the removal notification + oldTerminalExt.getConnectable().remove(); + continue; + } + + AbstractConnectable connectable = oldTerminalExt.getConnectable(); + + // create the new terminal with new type + TerminalExt newTerminalExt = new TerminalBuilder(networkRef, this, oldTerminalExt.getSide()) + .setBus(infos.connected ? infos.connectableBusId() : null) + .setConnectableBus(infos.connectableBusId()) + .build(); + + connectable.replaceTerminal(oldTerminalExt, newTopologyModel, newTerminalExt, false); + } + + // also here keep the notification for remaining switches removal + topologyModel.removeTopology(); + + TopologyKind oldTopologyKind = topologyModel.getTopologyKind(); + topologyModel = newTopologyModel; + + notifyUpdate("topologyKind", oldTopologyKind, TopologyKind.BUS_BREAKER); + } + + @Override + public void convertToTopology(TopologyKind newTopologyKind) { + Objects.requireNonNull(newTopologyKind); + if (newTopologyKind != topologyModel.getTopologyKind()) { + if (newTopologyKind == TopologyKind.NODE_BREAKER) { + throw new PowsyblException("Topology model conversion from bus/breaker to node/breaker not yet supported"); + } else if (newTopologyKind == TopologyKind.BUS_BREAKER) { + convertToBusBreakerModel(); + } + } + } } diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/extensions/RemoteReactivePowerControlImpl.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/extensions/RemoteReactivePowerControlImpl.java index cd88cec163e..f3c7a3c5663 100644 --- a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/extensions/RemoteReactivePowerControlImpl.java +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/extensions/RemoteReactivePowerControlImpl.java @@ -132,6 +132,13 @@ public void onReferencedRemoval(Terminal removedTerminal) { getExtendable().removeExtension(RemoteReactivePowerControl.class); } + @Override + public void onReferencedReplacement(Terminal oldReferenced, Terminal newReferenced) { + if (regulatingTerminal == oldReferenced) { + regulatingTerminal = newReferenced; + } + } + @Override public void cleanup() { if (regulatingTerminal != null) { diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/extensions/SlackTerminalImpl.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/extensions/SlackTerminalImpl.java index f33f56abbcf..e072f714070 100644 --- a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/extensions/SlackTerminalImpl.java +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/extensions/SlackTerminalImpl.java @@ -120,6 +120,11 @@ public void onReferencedRemoval(Terminal removedTerminal) { } } + @Override + public void onReferencedReplacement(Terminal oldReferenced, Terminal newReferenced) { + terminals.replaceAll(t -> t == oldReferenced ? newReferenced : t); + } + @Override public void cleanup() { for (Terminal terminal : terminals) { diff --git a/iidm/iidm-impl/src/test/java/com/powsybl/iidm/network/impl/tck/ConvertTopologyTest.java b/iidm/iidm-impl/src/test/java/com/powsybl/iidm/network/impl/tck/ConvertTopologyTest.java new file mode 100644 index 00000000000..5f78e6ecdb3 --- /dev/null +++ b/iidm/iidm-impl/src/test/java/com/powsybl/iidm/network/impl/tck/ConvertTopologyTest.java @@ -0,0 +1,16 @@ +/** + * Copyright (c) 2024, 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/. + * SPDX-License-Identifier: MPL-2.0 + */ +package com.powsybl.iidm.network.impl.tck; + +import com.powsybl.iidm.network.tck.AbstractConvertTopologyTest; + +/** + * @author Geoffroy Jamgotchian {@literal } + */ +class ConvertTopologyTest extends AbstractConvertTopologyTest { +} diff --git a/iidm/iidm-tck/src/test/java/com/powsybl/iidm/network/tck/AbstractConvertTopologyTest.java b/iidm/iidm-tck/src/test/java/com/powsybl/iidm/network/tck/AbstractConvertTopologyTest.java new file mode 100644 index 00000000000..637f15e5197 --- /dev/null +++ b/iidm/iidm-tck/src/test/java/com/powsybl/iidm/network/tck/AbstractConvertTopologyTest.java @@ -0,0 +1,202 @@ +/** + * Copyright (c) 2024, 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/. + * SPDX-License-Identifier: MPL-2.0 + */ +package com.powsybl.iidm.network.tck; + +import com.powsybl.commons.PowsyblException; +import com.powsybl.iidm.network.*; +import com.powsybl.iidm.network.events.RemovalNetworkEvent; +import com.powsybl.iidm.network.events.UpdateNetworkEvent; +import com.powsybl.iidm.network.extensions.SlackTerminal; +import com.powsybl.iidm.network.test.EurostagTutorialExample1Factory; +import com.powsybl.iidm.network.test.FourSubstationsNodeBreakerFactory; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.Collections; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * @author Geoffroy Jamgotchian {@literal } + */ +public abstract class AbstractConvertTopologyTest { + + private Network network; + private VoltageLevel vl; + private NetworkEventRecorder eventRecorder; + + @BeforeEach + void setUp() { + network = FourSubstationsNodeBreakerFactory.create(); + vl = network.getVoltageLevel("S1VL2"); + for (Switch sw : vl.getSwitches()) { + sw.setRetained(sw.getId().equals("S1VL2_COUPLER")); + } + eventRecorder = new NetworkEventRecorder(); + network.addListener(eventRecorder); + } + + @Test + void testBusBreakerToNodeBreaker() { + Network busBreakerNetwork = EurostagTutorialExample1Factory.create(); + VoltageLevel vlgen = busBreakerNetwork.getVoltageLevel("VLGEN"); + var e = assertThrows(PowsyblException.class, () -> vlgen.convertToTopology(TopologyKind.NODE_BREAKER)); + assertEquals("Topology model conversion from bus/breaker to node/breaker not yet supported", e.getMessage()); + } + + @Test + void testNodeBreakerToBusBreaker() { + var gh1 = network.getGenerator("GH1"); + var busesNbModel = vl.getBusBreakerView().getBusStream().toList(); + assertEquals(2, busesNbModel.size()); + assertEquals(List.of("S1VL2_0", "S1VL2_1"), busesNbModel.stream().map(Identifiable::getId).toList()); + assertEquals(List.of("S1VL2_BBS1", "TWT", "GH1", "GH2", "GH3", "SHUNT"), busesNbModel.get(0).getConnectedTerminalStream().map(t -> t.getConnectable().getId()).toList()); + assertEquals(List.of("S1VL2_BBS2", "VSC1", "LD2", "LD3", "LD4", "LCC1"), busesNbModel.get(1).getConnectedTerminalStream().map(t -> t.getConnectable().getId()).toList()); + assertEquals("S1VL2_0", gh1.getRegulatingTerminal().getBusBreakerView().getBus().getId()); + vl.convertToTopology(TopologyKind.BUS_BREAKER); + + var busesBbModel = vl.getBusBreakerView().getBusStream().toList(); + assertEquals(2, busesBbModel.size()); + assertEquals(List.of("S1VL2_0", "S1VL2_1"), busesNbModel.stream().map(Identifiable::getId).toList()); + // compare to initial node/breaker model, only difference is that there is no more busbar sections + assertEquals(List.of("TWT", "GH1", "GH2", "GH3", "SHUNT"), busesBbModel.get(0).getConnectedTerminalStream().map(t -> t.getConnectable().getId()).toList()); + assertEquals(List.of("VSC1", "LD2", "LD3", "LD4", "LCC1"), busesBbModel.get(1).getConnectedTerminalStream().map(t -> t.getConnectable().getId()).toList()); + // only retained switches have been kept + assertEquals(List.of("S1VL2_COUPLER"), vl.getBusBreakerView().getSwitchStream().map(Identifiable::getId).toList()); + assertEquals(List.of(new RemovalNetworkEvent("S1VL2_BBS1", false), + new RemovalNetworkEvent("S1VL2_BBS1", true), + new RemovalNetworkEvent("S1VL2_BBS2", false), + new RemovalNetworkEvent("S1VL2_BBS2", true), + new RemovalNetworkEvent("S1VL2_BBS1_TWT_DISCONNECTOR", false), + new RemovalNetworkEvent("S1VL2_BBS2_TWT_DISCONNECTOR", false), + new RemovalNetworkEvent("S1VL2_TWT_BREAKER", false), + new RemovalNetworkEvent("S1VL2_BBS1_VSC1_DISCONNECTOR", false), + new RemovalNetworkEvent("S1VL2_BBS2_VSC1_DISCONNECTOR", false), + new RemovalNetworkEvent("S1VL2_VSC1_BREAKER", false), + new RemovalNetworkEvent("S1VL2_BBS1_GH1_DISCONNECTOR", false), + new RemovalNetworkEvent("S1VL2_BBS1_GH2_DISCONNECTOR", false), + new RemovalNetworkEvent("S1VL2_BBS1_GH3_DISCONNECTOR", false), + new RemovalNetworkEvent("S1VL2_BBS2_GH1_DISCONNECTOR", false), + new RemovalNetworkEvent("S1VL2_BBS2_GH2_DISCONNECTOR", false), + new RemovalNetworkEvent("S1VL2_BBS2_GH3_DISCONNECTOR", false), + new RemovalNetworkEvent("S1VL2_GH1_BREAKER", false), + new RemovalNetworkEvent("S1VL2_GH2_BREAKER", false), + new RemovalNetworkEvent("S1VL2_GH3_BREAKER", false), + new RemovalNetworkEvent("S1VL2_BBS1_LD2_DISCONNECTOR", false), + new RemovalNetworkEvent("S1VL2_BBS1_LD3_DISCONNECTOR", false), + new RemovalNetworkEvent("S1VL2_BBS1_LD4_DISCONNECTOR", false), + new RemovalNetworkEvent("S1VL2_BBS2_LD2_DISCONNECTOR", false), + new RemovalNetworkEvent("S1VL2_BBS2_LD3_DISCONNECTOR", false), + new RemovalNetworkEvent("S1VL2_BBS2_LD4_DISCONNECTOR", false), + new RemovalNetworkEvent("S1VL2_LD2_BREAKER", false), + new RemovalNetworkEvent("S1VL2_LD3_BREAKER", false), + new RemovalNetworkEvent("S1VL2_LD4_BREAKER", false), + new RemovalNetworkEvent("S1VL2_BBS1_SHUNT_DISCONNECTOR", false), + new RemovalNetworkEvent("S1VL2_BBS2_SHUNT_DISCONNECTOR", false), + new RemovalNetworkEvent("S1VL2_SHUNT_BREAKER", false), + new RemovalNetworkEvent("S1VL2_BBS1_LCC1_DISCONNECTOR", false), + new RemovalNetworkEvent("S1VL2_BBS2_LCC1_DISCONNECTOR", false), + new RemovalNetworkEvent("S1VL2_LCC1_BREAKER", false), + new RemovalNetworkEvent("S1VL2_BBS1_COUPLER_DISCONNECTOR", false), + new RemovalNetworkEvent("S1VL2_BBS2_COUPLER_DISCONNECTOR", false), + new RemovalNetworkEvent("S1VL2_BBS1_TWT_DISCONNECTOR", true), + new RemovalNetworkEvent("S1VL2_BBS2_TWT_DISCONNECTOR", true), + new RemovalNetworkEvent("S1VL2_TWT_BREAKER", true), + new RemovalNetworkEvent("S1VL2_BBS1_VSC1_DISCONNECTOR", true), + new RemovalNetworkEvent("S1VL2_BBS2_VSC1_DISCONNECTOR", true), + new RemovalNetworkEvent("S1VL2_VSC1_BREAKER", true), + new RemovalNetworkEvent("S1VL2_BBS1_GH1_DISCONNECTOR", true), + new RemovalNetworkEvent("S1VL2_BBS1_GH2_DISCONNECTOR", true), + new RemovalNetworkEvent("S1VL2_BBS1_GH3_DISCONNECTOR", true), + new RemovalNetworkEvent("S1VL2_BBS2_GH1_DISCONNECTOR", true), + new RemovalNetworkEvent("S1VL2_BBS2_GH2_DISCONNECTOR", true), + new RemovalNetworkEvent("S1VL2_BBS2_GH3_DISCONNECTOR", true), + new RemovalNetworkEvent("S1VL2_GH1_BREAKER", true), + new RemovalNetworkEvent("S1VL2_GH2_BREAKER", true), + new RemovalNetworkEvent("S1VL2_GH3_BREAKER", true), + new RemovalNetworkEvent("S1VL2_BBS1_LD2_DISCONNECTOR", true), + new RemovalNetworkEvent("S1VL2_BBS1_LD3_DISCONNECTOR", true), + new RemovalNetworkEvent("S1VL2_BBS1_LD4_DISCONNECTOR", true), + new RemovalNetworkEvent("S1VL2_BBS2_LD2_DISCONNECTOR", true), + new RemovalNetworkEvent("S1VL2_BBS2_LD3_DISCONNECTOR", true), + new RemovalNetworkEvent("S1VL2_BBS2_LD4_DISCONNECTOR", true), + new RemovalNetworkEvent("S1VL2_LD2_BREAKER", true), + new RemovalNetworkEvent("S1VL2_LD3_BREAKER", true), + new RemovalNetworkEvent("S1VL2_LD4_BREAKER", true), + new RemovalNetworkEvent("S1VL2_BBS1_SHUNT_DISCONNECTOR", true), + new RemovalNetworkEvent("S1VL2_BBS2_SHUNT_DISCONNECTOR", true), + new RemovalNetworkEvent("S1VL2_SHUNT_BREAKER", true), + new RemovalNetworkEvent("S1VL2_BBS1_LCC1_DISCONNECTOR", true), + new RemovalNetworkEvent("S1VL2_BBS2_LCC1_DISCONNECTOR", true), + new RemovalNetworkEvent("S1VL2_LCC1_BREAKER", true), + new RemovalNetworkEvent("S1VL2_BBS1_COUPLER_DISCONNECTOR", true), + new RemovalNetworkEvent("S1VL2_BBS2_COUPLER_DISCONNECTOR", true), + new UpdateNetworkEvent("S1VL2", "topologyKind", null, TopologyKind.NODE_BREAKER, TopologyKind.BUS_BREAKER)), + eventRecorder.getEvents()); + + // check regulating terminal has been correctly updated + assertEquals("S1VL2_0", gh1.getRegulatingTerminal().getBusBreakerView().getBus().getId()); + + // check busbar sections have been removed + assertNull(network.getBusbarSection("S1VL2_BBS1")); + assertNull(network.getBusbarSection("S1VL2_BBS2")); + } + + @Test + void testNodeBreakerToBusBreakerOneElementDisconnected() { + var gh2 = network.getGenerator("GH2"); + gh2.disconnect(); + assertEquals(List.of(new UpdateNetworkEvent("S1VL2_GH2_BREAKER", "open", "InitialState", false, true)), eventRecorder.getEvents()); + eventRecorder.reset(); + var busesNbModel = vl.getBusBreakerView().getBusStream().toList(); + assertEquals(3, busesNbModel.size()); + assertEquals(List.of("S1VL2_0", "S1VL2_1", "S1VL2_9"), busesNbModel.stream().map(Identifiable::getId).toList()); + assertEquals(List.of("S1VL2_BBS1", "TWT", "GH1", "GH3", "SHUNT"), busesNbModel.get(0).getConnectedTerminalStream().map(t -> t.getConnectable().getId()).toList()); + assertEquals(List.of("S1VL2_BBS2", "VSC1", "LD2", "LD3", "LD4", "LCC1"), busesNbModel.get(1).getConnectedTerminalStream().map(t -> t.getConnectable().getId()).toList()); + assertEquals(List.of("GH2"), busesNbModel.get(2).getConnectedTerminalStream().map(t -> t.getConnectable().getId()).toList()); + vl.convertToTopology(TopologyKind.BUS_BREAKER); + assertEquals(69, eventRecorder.getEvents().size()); + + var busesBbModel = vl.getBusBreakerView().getBusStream().toList(); + assertEquals(3, busesBbModel.size()); + assertEquals(List.of("S1VL2_0", "S1VL2_1", "S1VL2_9"), busesNbModel.stream().map(Identifiable::getId).toList()); + // compare to initial node/breaker model, only difference is that there is no more busbar sections + assertEquals(List.of("TWT", "GH1", "GH3", "SHUNT"), busesBbModel.get(0).getConnectedTerminalStream().map(t -> t.getConnectable().getId()).toList()); + assertEquals(List.of("VSC1", "LD2", "LD3", "LD4", "LCC1"), busesBbModel.get(1).getConnectedTerminalStream().map(t -> t.getConnectable().getId()).toList()); + assertEquals(Collections.emptyList(), busesBbModel.get(2).getConnectedTerminalStream().map(t -> t.getConnectable().getId()).toList()); + assertEquals("S1VL2_9", gh2.getTerminal().getBusBreakerView().getConnectableBus().getId()); + assertFalse(gh2.getTerminal().isConnected()); + // only retained switches have been kept + assertEquals(List.of("S1VL2_COUPLER"), vl.getBusBreakerView().getSwitchStream().map(Identifiable::getId).toList()); + } + + @Test + void testNodeBreakerToBusBreakerWithArea() { + var gh2 = network.getGenerator("GH2"); + network.newArea() + .setId("area1") + .setAreaType("fake") + .addAreaBoundary(gh2.getTerminal(), true) + .add(); + var boundary = network.getArea("area1").getAreaBoundaryStream().findFirst().orElseThrow(); + assertEquals(gh2.getTerminal(), boundary.getTerminal().orElse(null)); + vl.convertToTopology(TopologyKind.BUS_BREAKER); + assertEquals(gh2.getTerminal(), boundary.getTerminal().orElse(null)); + } + + @Test + void testWithSlackTerminalExtension() { + var gh2 = network.getGenerator("GH2"); + SlackTerminal.reset(gh2.getTerminal().getVoltageLevel(), gh2.getTerminal()); + var slackTerminal = gh2.getTerminal().getVoltageLevel().getExtension(SlackTerminal.class); + assertEquals(gh2.getTerminal(), slackTerminal.getTerminal()); + vl.convertToTopology(TopologyKind.BUS_BREAKER); + assertEquals(gh2.getTerminal(), slackTerminal.getTerminal()); + } +}