diff --git a/commons/src/main/java/com/powsybl/commons/extensions/AbstractExtension.java b/commons/src/main/java/com/powsybl/commons/extensions/AbstractExtension.java index f091e829e3e..bf4ab14edb3 100644 --- a/commons/src/main/java/com/powsybl/commons/extensions/AbstractExtension.java +++ b/commons/src/main/java/com/powsybl/commons/extensions/AbstractExtension.java @@ -32,15 +32,11 @@ public void setExtendable(T extendable) { if (extendable != null && this.extendable != null && this.extendable != extendable) { throw new PowsyblException("Extension is already associated to the extendable " + this.extendable); } - if (extendable == null) { - cleanup(); - } this.extendable = extendable; } - /** - * Method called when the extension is removed from its holder. - * Can be used for e.g. resource cleanup. - */ - protected void cleanup() { } + @Override + public void cleanup() { + // nothing by default + } } diff --git a/commons/src/main/java/com/powsybl/commons/extensions/Extension.java b/commons/src/main/java/com/powsybl/commons/extensions/Extension.java index ce30898628b..c56f23591bd 100644 --- a/commons/src/main/java/com/powsybl/commons/extensions/Extension.java +++ b/commons/src/main/java/com/powsybl/commons/extensions/Extension.java @@ -33,4 +33,11 @@ public interface Extension { * @throws com.powsybl.commons.PowsyblException if this extension is already held. */ void setExtendable(T extendable); + + /** + * Method called just before the extension is removed from its holder. + * Can be used for e.g. resource cleanup. + */ + default void cleanup() { + } } diff --git a/contingency/contingency-api/src/test/java/com/powsybl/contingency/json/ContingencyJsonTest.java b/contingency/contingency-api/src/test/java/com/powsybl/contingency/json/ContingencyJsonTest.java index 0ead6904b44..ca19654a6cd 100644 --- a/contingency/contingency-api/src/test/java/com/powsybl/contingency/json/ContingencyJsonTest.java +++ b/contingency/contingency-api/src/test/java/com/powsybl/contingency/json/ContingencyJsonTest.java @@ -14,7 +14,7 @@ import com.fasterxml.jackson.databind.ObjectWriter; import com.fasterxml.jackson.databind.SerializerProvider; import com.google.auto.service.AutoService; -import com.powsybl.commons.extensions.Extension; +import com.powsybl.commons.extensions.AbstractExtension; import com.powsybl.commons.extensions.ExtensionJsonSerializer; import com.powsybl.commons.json.JsonUtil; import com.powsybl.commons.test.AbstractSerDeTest; @@ -249,7 +249,7 @@ void readJsonListContingenciesWithOptionalName() throws IOException { roundTripTest(contingencyList, ContingencyJsonTest::write, ContingencyJsonTest::readContingencyList, "/contingenciesWithOptionalName.json"); } - static class DummyExtension implements Extension { + static class DummyExtension extends AbstractExtension { private Contingency contingency; diff --git a/iidm/iidm-api/src/main/java/com/powsybl/iidm/network/Terminal.java b/iidm/iidm-api/src/main/java/com/powsybl/iidm/network/Terminal.java index 47ab9a2cfd3..8da19d0c3cc 100644 --- a/iidm/iidm-api/src/main/java/com/powsybl/iidm/network/Terminal.java +++ b/iidm/iidm-api/src/main/java/com/powsybl/iidm/network/Terminal.java @@ -11,6 +11,7 @@ import com.powsybl.math.graph.TraversalType; import com.powsybl.math.graph.TraverseResult; +import java.util.List; import java.util.Optional; import java.util.function.Predicate; @@ -252,4 +253,11 @@ static Terminal getTerminal(Identifiable identifiable, ThreeSides side) { } ThreeSides getSide(); + + /** + * Retrieves a list of objects that refer to the terminal. + * + * @return a list of referrer objects. + */ + List getReferrers(); } 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 756f6be24a2..4a98b6c6f07 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 @@ -70,7 +70,7 @@ public void remove() { network.getIndex().remove(this); for (TerminalExt terminal : terminals) { - terminal.removeAsRegulationPoint(); + terminal.getReferrerManager().notifyOfRemoval(); VoltageLevelExt vl = terminal.getVoltageLevel(); vl.getTopologyModel().detach(terminal); } diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AbstractIdentifiable.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AbstractIdentifiable.java index 837269c396b..6a546dd4046 100644 --- a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AbstractIdentifiable.java +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AbstractIdentifiable.java @@ -260,10 +260,17 @@ public void allocateVariantArrayElement(int[] indexes, int sourceIndex) { @Override public > boolean removeExtension(Class type) { + return removeExtension(type, true); + } + + public > boolean removeExtension(Class type, boolean cleanup) { E extension = getExtension(type); NetworkListenerList listeners = getNetwork().getListeners(); if (extension != null) { listeners.notifyExtensionBeforeRemoval(extension); + if (cleanup) { + extension.cleanup(); + } removeExtension(type, extension); listeners.notifyExtensionAfterRemoval(this, extension.getName()); return true; 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 new file mode 100644 index 00000000000..f7a91350642 --- /dev/null +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AbstractIidmExtension.java @@ -0,0 +1,28 @@ +/** + * 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; + +import com.powsybl.commons.extensions.AbstractExtension; +import com.powsybl.iidm.network.Identifiable; +import com.powsybl.iidm.network.Terminal; + +/** + * @author Geoffroy Jamgotchian {@literal } + */ +public abstract class AbstractIidmExtension> extends AbstractExtension implements Referrer { + + protected AbstractIidmExtension(I extendable) { + super(extendable); + } + + @Override + public void onReferencedRemoval(Terminal removedTerminal) { + // nothing by default + // this is the place for terminal reference cleanup + } +} diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AbstractMultiVariantIdentifiableExtension.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AbstractMultiVariantIdentifiableExtension.java index bbae95cfad4..aeb04fda519 100644 --- a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AbstractMultiVariantIdentifiableExtension.java +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AbstractMultiVariantIdentifiableExtension.java @@ -8,14 +8,13 @@ package com.powsybl.iidm.network.impl; import com.powsybl.commons.exceptions.UncheckedClassCastExceptionException; -import com.powsybl.commons.extensions.AbstractExtension; import com.powsybl.iidm.network.Identifiable; import com.powsybl.iidm.network.Network; /** * @author Florian Dupuy {@literal } */ -public abstract class AbstractMultiVariantIdentifiableExtension> extends AbstractExtension implements MultiVariantObject { +public abstract class AbstractMultiVariantIdentifiableExtension> extends AbstractIidmExtension implements MultiVariantObject { public AbstractMultiVariantIdentifiableExtension(T extendable) { super(extendable); diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AbstractNetwork.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AbstractNetwork.java index a73725bf9ec..8614a6a3c50 100644 --- a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AbstractNetwork.java +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AbstractNetwork.java @@ -104,7 +104,7 @@ protected static void transferExtensions(Network from, Network to, boolean ignor "an extension of this same class already exists in the destination network.", clazz.getName(), from.getId(), to.getId()); } else { - from.removeExtension((Class>) clazz); + ((AbstractIdentifiable) from).removeExtension((Class>) clazz, false); to.addExtension((Class>) clazz, (Extension) e); } }) diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AbstractTapChanger.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AbstractTapChanger.java index d46a9b1a830..84d370a7c74 100644 --- a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AbstractTapChanger.java +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AbstractTapChanger.java @@ -256,4 +256,8 @@ private void throwIncorrectTapPosition(int tapPosition, int highTapPosition) { + tapPosition + " [" + lowTapPosition + ", " + highTapPosition + "]"); } + + public void remove() { + regulatingPoint.remove(); + } } diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AbstractTerminal.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AbstractTerminal.java index 0f7d2f336e1..2e1d8ad0770 100644 --- a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AbstractTerminal.java +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/AbstractTerminal.java @@ -8,12 +8,11 @@ package com.powsybl.iidm.network.impl; 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.SwitchPredicates; import gnu.trove.list.array.TDoubleArrayList; -import java.util.ArrayList; import java.util.List; import java.util.function.Predicate; @@ -33,7 +32,7 @@ abstract class AbstractTerminal implements TerminalExt { protected VoltageLevelExt voltageLevel; - protected final List regulated = new ArrayList<>(); + protected final ReferrerManager referrerManager = new ReferrerManager<>(this); // attributes depending on the variant @@ -94,12 +93,6 @@ public void setVoltageLevel(VoltageLevelExt voltageLevel) { } } - @Override - public void removeAsRegulationPoint() { - regulated.forEach(RegulatingPoint::removeRegulatingTerminal); - regulated.clear(); - } - @Override public double getP() { if (removed) { @@ -250,12 +243,12 @@ public void remove() { } @Override - public void setAsRegulatingPoint(RegulatingPoint rp) { - regulated.add(rp); + public ReferrerManager getReferrerManager() { + return referrerManager; } @Override - public void removeRegulatingPoint(RegulatingPoint rp) { - regulated.remove(rp); + public List getReferrers() { + return referrerManager.getReferrers().stream().map(r -> (Object) r).toList(); } } 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 89d18793155..123f451ee98 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,31 +34,9 @@ public class AreaImpl extends AbstractIdentifiable implements Area { private final TDoubleArrayList interchangeTarget; - private final class AreaListener extends DefaultNetworkListener { - @Override - public void beforeRemoval(Identifiable identifiable) { - if (identifiable instanceof DanglingLine danglingLine) { - // if dangling line removed from network, remove its boundary from this extension - AreaImpl.this.removeAreaBoundary(danglingLine.getBoundary()); - } else if (identifiable instanceof Connectable connectable) { - // if connectable removed from network, remove its terminals from this extension - connectable.getTerminals().forEach(AreaImpl.this::removeAreaBoundary); - } - // When a VoltageLevel is removed, its areas' voltageLevels attributes are directly updated. This is not managed via this listener. - } - } + private final Referrer terminalReferrer = this::removeAreaBoundary; - /** - * Must be called whenever the Area is moved to a different root Network when merging and detaching. - * @param fromNetwork previous root network - * @param toNetwork new root network - */ - void moveListener(NetworkImpl fromNetwork, NetworkImpl toNetwork) { - fromNetwork.removeListener(this.areaListener); - toNetwork.addListener(this.areaListener); - } - - private final NetworkListener areaListener; + private final Referrer boundaryReferrer = this::removeAreaBoundary; AreaImpl(Ref ref, Ref subnetworkRef, String id, String name, boolean fictitious, String areaType, double interchangeTarget) { @@ -74,8 +52,6 @@ void moveListener(NetworkImpl fromNetwork, NetworkImpl toNetwork) { for (int i = 0; i < variantArraySize; i++) { this.interchangeTarget.add(interchangeTarget); } - this.areaListener = new AreaListener(); - getNetwork().addListener(this.areaListener); } @Override @@ -229,8 +205,14 @@ public Stream getAreaBoundaryStream() { protected void addAreaBoundary(AreaBoundaryImpl areaBoundary) { Optional terminal = areaBoundary.getTerminal(); Optional boundary = areaBoundary.getBoundary(); - boundary.ifPresent(b -> checkBoundaryNetwork(b.getDanglingLine().getParentNetwork(), "Boundary of DanglingLine" + b.getDanglingLine().getId())); - terminal.ifPresent(t -> checkBoundaryNetwork(t.getConnectable().getParentNetwork(), "Terminal of connectable " + t.getConnectable().getId())); + boundary.ifPresent(b -> { + checkBoundaryNetwork(b.getDanglingLine().getParentNetwork(), "Boundary of DanglingLine" + b.getDanglingLine().getId()); + ((DanglingLineBoundaryImplExt) b).getReferrerManager().register(boundaryReferrer); + }); + terminal.ifPresent(t -> { + checkBoundaryNetwork(t.getConnectable().getParentNetwork(), "Terminal of connectable " + t.getConnectable().getId()); + ((TerminalExt) t).getReferrerManager().register(terminalReferrer); + }); areaBoundaries.add(areaBoundary); } @@ -248,8 +230,11 @@ public void remove() { for (VoltageLevel voltageLevel : new HashSet<>(voltageLevels)) { voltageLevel.removeArea(this); } + for (AreaBoundary areaBoundary : areaBoundaries) { + areaBoundary.getTerminal().ifPresent(t -> ((TerminalExt) t).getReferrerManager().unregister(terminalReferrer)); + areaBoundary.getBoundary().ifPresent(b -> ((DanglingLineBoundaryImplExt) b).getReferrerManager().unregister(boundaryReferrer)); + } network.getListeners().notifyAfterRemoval(id); - network.removeListener(this.areaListener); removed = true; } diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/DanglingLineBoundaryImplExt.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/DanglingLineBoundaryImplExt.java new file mode 100644 index 00000000000..46a0e144960 --- /dev/null +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/DanglingLineBoundaryImplExt.java @@ -0,0 +1,32 @@ +/** + * 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; + +import com.powsybl.iidm.network.Boundary; +import com.powsybl.iidm.network.DanglingLine; +import com.powsybl.iidm.network.util.DanglingLineBoundaryImpl; + +/** + * @author Geoffroy Jamgotchian {@literal } + */ +public class DanglingLineBoundaryImplExt extends DanglingLineBoundaryImpl { + + protected final ReferrerManager referrerManager = new ReferrerManager<>(this); + + public DanglingLineBoundaryImplExt(DanglingLine parent) { + super(parent); + } + + public ReferrerManager getReferrerManager() { + return referrerManager; + } + + void remove() { + referrerManager.notifyOfRemoval(); + } +} diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/DanglingLineImpl.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/DanglingLineImpl.java index 567dfe44356..a92f8d6a8a9 100644 --- a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/DanglingLineImpl.java +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/DanglingLineImpl.java @@ -10,7 +10,6 @@ import com.powsybl.commons.util.trove.TBooleanArrayList; import com.powsybl.iidm.network.*; import com.powsybl.commons.ref.Ref; -import com.powsybl.iidm.network.util.DanglingLineBoundaryImpl; import gnu.trove.list.array.TDoubleArrayList; import java.util.Collection; @@ -259,7 +258,7 @@ void allocateVariantArrayElement(int[] indexes, int sourceIndex) { private final TDoubleArrayList q0; - private final DanglingLineBoundaryImpl boundary; + private final DanglingLineBoundaryImplExt boundary; DanglingLineImpl(Ref network, String id, String name, boolean fictitious, double p0, double q0, double r, double x, double g, double b, String pairingKey, GenerationImpl generation) { super(network, id, name, fictitious); @@ -277,7 +276,7 @@ void allocateVariantArrayElement(int[] indexes, int sourceIndex) { this.b = b; this.pairingKey = pairingKey; this.operationalLimitsGroups = new OperationalLimitsGroupsImpl(this, "limits"); - this.boundary = new DanglingLineBoundaryImpl(this); + this.boundary = new DanglingLineBoundaryImplExt(this); this.generation = generation != null ? generation.attach(this) : null; } @@ -309,6 +308,7 @@ public void remove() { throw new UnsupportedOperationException("Parent tie line " + tieLine.getId() + " should be removed before the child dangling line"); } super.remove(); + boundary.remove(); } void removeTieLine() { diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/GeneratorAdderImpl.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/GeneratorAdderImpl.java index 54afbc6133c..bac5fd0da46 100644 --- a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/GeneratorAdderImpl.java +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/GeneratorAdderImpl.java @@ -127,7 +127,7 @@ public GeneratorImpl add() { = new GeneratorImpl(getNetworkRef(), id, getName(), isFictitious(), energySource, minP, maxP, - voltageRegulatorOn, regulatingTerminal != null ? regulatingTerminal : terminal, + voltageRegulatorOn, regulatingTerminal, targetP, targetQ, targetV, ratedS, isCondenser); generator.addTerminal(terminal); diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/GeneratorImpl.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/GeneratorImpl.java index a5ba5df417f..93a5b337afa 100644 --- a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/GeneratorImpl.java +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/GeneratorImpl.java @@ -54,7 +54,7 @@ class GeneratorImpl extends AbstractConnectable implements Generator, this.reactiveLimits = new ReactiveLimitsHolderImpl(this, new MinMaxReactiveLimitsImpl(-Double.MAX_VALUE, Double.MAX_VALUE)); this.ratedS = ratedS; int variantArraySize = network.get().getVariantManager().getVariantArraySize(); - regulatingPoint = new RegulatingPoint(id, this::getTerminal, variantArraySize, voltageRegulatorOn, voltageRegulatorOn); + regulatingPoint = new RegulatingPoint(id, this::getTerminal, variantArraySize, voltageRegulatorOn, true); regulatingPoint.setRegulatingTerminal(regulatingTerminal); this.targetP = new TDoubleArrayList(variantArraySize); this.targetQ = new TDoubleArrayList(variantArraySize); @@ -129,7 +129,6 @@ public GeneratorImpl setVoltageRegulatorOn(boolean voltageRegulatorOn) { voltageRegulatorOn, targetV.get(variantIndex), targetQ.get(variantIndex), n.getMinValidationLevel(), n.getReportNodeContext().getReportNode()); boolean oldValue = regulatingPoint.setRegulating(variantIndex, voltageRegulatorOn); - regulatingPoint.setUseVoltageRegulation(voltageRegulatorOn); String variantId = network.get().getVariantManager().getVariantId(variantIndex); n.invalidateValidationLevel(); notifyUpdate("voltageRegulatorOn", variantId, oldValue, voltageRegulatorOn); diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/NetworkImpl.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/NetworkImpl.java index 344f3e246bf..31c25e9d926 100644 --- a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/NetworkImpl.java +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/NetworkImpl.java @@ -1015,11 +1015,6 @@ private void merge(Network other) { checkMergeability(otherNetwork); - otherNetwork.getAreaStream().forEach(a -> { - AreaImpl area = (AreaImpl) a; - area.moveListener(otherNetwork, this); - }); - // try to find dangling lines couples List lines = new ArrayList<>(); Map> dl1byPairingKey = new HashMap<>(); diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/PhaseTapChangerImpl.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/PhaseTapChangerImpl.java index b658fd1aaa3..7aa502c70bf 100644 --- a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/PhaseTapChangerImpl.java +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/PhaseTapChangerImpl.java @@ -131,7 +131,7 @@ public PhaseTapChangerImpl setRegulationTerminal(Terminal regulationTerminal) { @Override public void remove() { - regulatingPoint.remove(); + super.remove(); parent.setPhaseTapChanger(null); } diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/RatioTapChangerImpl.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/RatioTapChangerImpl.java index 98843670406..7fbf01b53d4 100644 --- a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/RatioTapChangerImpl.java +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/RatioTapChangerImpl.java @@ -180,7 +180,7 @@ public RatioTapChangerImpl setRegulationTerminal(Terminal regulationTerminal) { @Override public void remove() { - regulatingPoint.remove(); + super.remove(); parent.setRatioTapChanger(null); } 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 new file mode 100644 index 00000000000..e9e29e36c4d --- /dev/null +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/Referrer.java @@ -0,0 +1,23 @@ +/** + * 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; + +/** + * @author Geoffroy Jamgotchian {@literal } + */ +public interface Referrer { + + /** + * Called when a referenced object is removed because of a connectable removal. + * Implementations of this method should handle any required cleanup or updates + * necessary when the referenced object is no longer part of the network. + * + * @param removedReferenced The referenced that has been removed from the network. + */ + void onReferencedRemoval(T removedReferenced); +} diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/ReferrerManager.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/ReferrerManager.java new file mode 100644 index 00000000000..e29931e9783 --- /dev/null +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/ReferrerManager.java @@ -0,0 +1,44 @@ +/** + * 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; + +import java.util.List; +import java.util.Objects; +import java.util.concurrent.CopyOnWriteArrayList; + +/** + * @author Geoffroy Jamgotchian {@literal } + */ +public class ReferrerManager { + + private final T referenced; + + private final List> referrers = new CopyOnWriteArrayList<>(); + + public ReferrerManager(T referenced) { + this.referenced = Objects.requireNonNull(referenced); + } + + public List> getReferrers() { + return referrers; + } + + public void register(Referrer referrer) { + referrers.add(Objects.requireNonNull(referrer)); + } + + public void unregister(Referrer referrer) { + referrers.remove(Objects.requireNonNull(referrer)); + } + + public void notifyOfRemoval() { + for (Referrer referrer : referrers) { + referrer.onReferencedRemoval(referenced); + } + } +} 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 8ca037d0711..54a69e96ecd 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 @@ -10,24 +10,24 @@ import com.powsybl.commons.util.trove.TBooleanArrayList; import com.powsybl.iidm.network.Bus; import com.powsybl.iidm.network.StaticVarCompensator; +import com.powsybl.iidm.network.Terminal; import gnu.trove.list.array.TIntArrayList; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.Objects; import java.util.function.Supplier; /** * @author Miora Vedelago {@literal } */ -class RegulatingPoint implements MultiVariantObject { +class RegulatingPoint implements MultiVariantObject, Referrer { private static final Logger LOG = LoggerFactory.getLogger(RegulatingPoint.class); private final String regulatedEquipmentId; private final Supplier localTerminalSupplier; - private boolean useVoltageRegulation; - private TerminalExt regulatingTerminal = null; + private final boolean useVoltageRegulation; + private TerminalExt regulatingTerminal; // attributes depending on the variant @@ -58,26 +58,23 @@ class RegulatingPoint implements MultiVariantObject { void setRegulatingTerminal(TerminalExt regulatingTerminal) { if (this.regulatingTerminal != null) { - this.regulatingTerminal.removeRegulatingPoint(this); + this.regulatingTerminal.getReferrerManager().unregister(this); + this.regulatingTerminal = null; } - this.regulatingTerminal = regulatingTerminal != null ? regulatingTerminal : localTerminalSupplier.get(); - if (this.regulatingTerminal != null) { - this.regulatingTerminal.setAsRegulatingPoint(this); + if (regulatingTerminal != null) { + this.regulatingTerminal = regulatingTerminal; + this.regulatingTerminal.getReferrerManager().register(this); } } TerminalExt getRegulatingTerminal() { - return regulatingTerminal; + return regulatingTerminal != null ? regulatingTerminal : localTerminalSupplier.get(); } boolean setRegulating(int index, boolean regulating) { return this.regulating.set(index, regulating); } - void setUseVoltageRegulation(boolean useVoltageRegulation) { - this.useVoltageRegulation = useVoltageRegulation; - } - boolean isRegulating(int index) { return regulating.get(index); } @@ -90,35 +87,6 @@ int getRegulationMode(int index) { return regulationMode.get(index); } - void removeRegulatingTerminal() { - Objects.requireNonNull(regulatingTerminal); - TerminalExt localTerminal = localTerminalSupplier.get(); - if (localTerminal != null && useVoltageRegulation) { // if local voltage regulation, we keep the regulating status, and re-locate the regulation at the regulated equipment - Bus bus = regulatingTerminal.getBusView().getBus(); - Bus localBus = localTerminal.getBusView().getBus(); - if (bus != null && bus == localBus) { - LOG.warn("Connectable {} was a local voltage regulation point for {}. Regulation point is re-located at {}.", regulatingTerminal.getConnectable().getId(), - regulatedEquipmentId, regulatedEquipmentId); - regulatingTerminal = localTerminal; - return; - } - } - LOG.warn("Connectable {} was a regulation point for {}. Regulation is deactivated", regulatingTerminal.getConnectable().getId(), regulatedEquipmentId); - regulatingTerminal = localTerminal; - if (regulating != null) { - regulating.fill(0, regulating.size(), false); - } - if (regulationMode != null) { - regulationMode.fill(0, regulationMode.size(), StaticVarCompensator.RegulationMode.OFF.ordinal()); - } - } - - void remove() { - if (regulatingTerminal != null) { - regulatingTerminal.removeRegulatingPoint(this); - } - } - @Override public void extendVariantArraySize(int initVariantArraySize, int number, int sourceIndex) { if (regulating != null) { @@ -163,4 +131,37 @@ public void allocateVariantArrayElement(int[] indexes, int sourceIndex) { } } } + + void remove() { + if (regulatingTerminal != null) { + regulatingTerminal.getReferrerManager().unregister(this); + } + } + + @Override + public void onReferencedRemoval(Terminal removedTerminal) { + TerminalExt oldRegulatingTerminal = regulatingTerminal; + TerminalExt localTerminal = localTerminalSupplier.get(); + if (localTerminal != null && useVoltageRegulation) { // if local voltage regulation, we keep the regulating status, and re-locate the regulation at the regulated equipment + Bus bus = regulatingTerminal.getBusView().getBus(); + Bus localBus = localTerminal.getBusView().getBus(); + if (bus != null && bus == localBus) { + LOG.warn("Connectable {} was a local voltage regulation point for {}. Regulation point is re-located at {}.", regulatingTerminal.getConnectable().getId(), + regulatedEquipmentId, regulatedEquipmentId); + regulatingTerminal = localTerminal; + return; + } else { + regulatingTerminal = null; + } + } else { + regulatingTerminal = null; + } + LOG.warn("Connectable {} was a regulation point for {}. Regulation is deactivated", oldRegulatingTerminal.getConnectable().getId(), regulatedEquipmentId); + if (regulating != null) { + regulating.fill(0, regulating.size(), false); + } + if (regulationMode != null) { + regulationMode.fill(0, regulationMode.size(), StaticVarCompensator.RegulationMode.OFF.ordinal()); + } + } } diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/ShuntCompensatorAdderImpl.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/ShuntCompensatorAdderImpl.java index a83a21c13c4..dd059cab2dd 100644 --- a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/ShuntCompensatorAdderImpl.java +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/ShuntCompensatorAdderImpl.java @@ -220,8 +220,7 @@ public ShuntCompensatorImpl add() { network.getMinValidationLevel(), network.getReportNodeContext().getReportNode())); ShuntCompensatorImpl shunt = new ShuntCompensatorImpl(getNetworkRef(), - id, getName(), isFictitious(), modelBuilder.build(), sectionCount, - regulatingTerminal == null ? terminal : regulatingTerminal, + id, getName(), isFictitious(), modelBuilder.build(), sectionCount, regulatingTerminal, voltageRegulatorOn, targetV, targetDeadband); shunt.addTerminal(terminal); diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/StaticVarCompensatorAdderImpl.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/StaticVarCompensatorAdderImpl.java index af6a95bbb75..5e3a4a7566a 100644 --- a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/StaticVarCompensatorAdderImpl.java +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/StaticVarCompensatorAdderImpl.java @@ -88,8 +88,7 @@ public StaticVarCompensatorImpl add() { network.setValidationLevelIfGreaterThan(ValidationUtil.checkSvcRegulator(this, voltageSetpoint, reactivePowerSetpoint, regulationMode, network.getMinValidationLevel(), network.getReportNodeContext().getReportNode())); StaticVarCompensatorImpl svc = new StaticVarCompensatorImpl(id, name, isFictitious(), bMin, bMax, voltageSetpoint, reactivePowerSetpoint, - regulationMode, regulatingTerminal != null ? regulatingTerminal : terminal, - getNetworkRef()); + regulationMode, regulatingTerminal, getNetworkRef()); svc.addTerminal(terminal); voltageLevel.getTopologyModel().attach(terminal, false); network.getIndex().checkAndAdd(svc); diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/StaticVarCompensatorImpl.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/StaticVarCompensatorImpl.java index c5835250164..a99f31b2456 100644 --- a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/StaticVarCompensatorImpl.java +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/StaticVarCompensatorImpl.java @@ -136,7 +136,6 @@ public StaticVarCompensatorImpl setRegulationMode(RegulationMode regulationMode) int variantIndex = n.getVariantIndex(); int oldValueOrdinal = regulatingPoint.setRegulationMode(variantIndex, regulationMode != null ? regulationMode.ordinal() : -1); - regulatingPoint.setUseVoltageRegulation(regulationMode == RegulationMode.VOLTAGE); String variantId = n.getVariantManager().getVariantId(variantIndex); n.invalidateValidationLevel(); notifyUpdate("regulationMode", variantId, oldValueOrdinal == -1 ? null : RegulationMode.values()[oldValueOrdinal], regulationMode); diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/SubnetworkImpl.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/SubnetworkImpl.java index 04dad01f24b..52c0cfa5eb5 100644 --- a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/SubnetworkImpl.java +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/SubnetworkImpl.java @@ -837,11 +837,6 @@ public Network detach() { detachedNetwork.getVoltageAngleLimitsIndex().put(val.getId(), val); } - detachedNetwork.getAreaStream().forEach(a -> { - AreaImpl area = (AreaImpl) a; - area.moveListener(previousRootNetwork, detachedNetwork); - }); - // We don't control that regulating terminals and phase/ratio regulation terminals are in the same subnetwork // as their network elements (generators, PSTs, ...). It is unlikely that those terminals and their elements // are in different subnetworks but nothing prevents it. For now, we ignore this case, but it may be necessary diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/TerminalExt.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/TerminalExt.java index 43569f81dad..bbc76742b33 100644 --- a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/TerminalExt.java +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/TerminalExt.java @@ -18,7 +18,7 @@ * * @author Geoffroy Jamgotchian {@literal } */ -interface TerminalExt extends Terminal, MultiVariantObject { +public interface TerminalExt extends Terminal, MultiVariantObject { interface BusBreakerViewExt extends BusBreakerView { @@ -55,11 +55,7 @@ interface BusViewExt extends BusView { TopologyPoint getTopologyPoint(); - void removeAsRegulationPoint(); - void remove(); - void setAsRegulatingPoint(RegulatingPoint rp); - - void removeRegulatingPoint(RegulatingPoint rp); + ReferrerManager getReferrerManager(); } diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/TwoWindingsTransformerImpl.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/TwoWindingsTransformerImpl.java index 3b488a909ae..00a5b76e107 100644 --- a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/TwoWindingsTransformerImpl.java +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/TwoWindingsTransformerImpl.java @@ -285,4 +285,15 @@ public String getTapChangerAttribute() { protected String getTypeDescription() { return "2 windings transformer"; } + + @Override + public void remove() { + if (ratioTapChanger != null) { + ratioTapChanger.remove(); + } + if (phaseTapChanger != null) { + phaseTapChanger.remove(); + } + super.remove(); + } } diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/VscConverterStationAdderImpl.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/VscConverterStationAdderImpl.java index 3c361ab1cdf..a81738f74c6 100644 --- a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/VscConverterStationAdderImpl.java +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/VscConverterStationAdderImpl.java @@ -71,7 +71,7 @@ public VscConverterStationImpl add() { validate(); VscConverterStationImpl converterStation = new VscConverterStationImpl(id, name, isFictitious(), getLossFactor(), getNetworkRef(), - voltageRegulatorOn, reactivePowerSetpoint, voltageSetpoint, regulatingTerminal == null ? terminal : regulatingTerminal); + voltageRegulatorOn, reactivePowerSetpoint, voltageSetpoint, regulatingTerminal); converterStation.addTerminal(terminal); getVoltageLevel().getTopologyModel().attach(terminal, false); network.getIndex().checkAndAdd(converterStation); diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/VscConverterStationImpl.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/VscConverterStationImpl.java index c2a1704d4f4..166b28678c6 100644 --- a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/VscConverterStationImpl.java +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/VscConverterStationImpl.java @@ -36,7 +36,7 @@ class VscConverterStationImpl extends AbstractHvdcConverterStation implements ReferenceTerminals { - private final class ReferenceTerminalsListener extends DefaultNetworkListener { - @Override - public void beforeRemoval(Identifiable identifiable) { - if (identifiable instanceof Connectable connectable) { - // if connectable removed from network, remove its terminals from this extension - terminalsPerVariant.forEach(referenceTerminals -> connectable.getTerminals().forEach(referenceTerminals::remove)); - } - } - } - - private final NetworkListener referenceTerminalsListener; private final ArrayList> terminalsPerVariant; public ReferenceTerminalsImpl(Network network, Set terminals) { @@ -38,22 +29,51 @@ public ReferenceTerminalsImpl(Network network, Set terminals) { this.terminalsPerVariant = new ArrayList<>( Collections.nCopies(getVariantManagerHolder().getVariantManager().getVariantArraySize(), new LinkedHashSet<>())); setReferenceTerminals(terminals); - this.referenceTerminalsListener = new ReferenceTerminalsListener(); } - @Override - public void setExtendable(Network extendable) { - super.setExtendable(extendable); - if (extendable != null) { - // Add the listener, this will be done both extension creation, but also on extension transfer when merging and detaching. - extendable.getNetwork().addListener(this.referenceTerminalsListener); + private void unregisterReferencedTerminalIfNeeded(int variantIndex) { + // check there is no more same terminal referenced by any variant, unregister it + Set oldTerminals = terminalsPerVariant.get(variantIndex); + for (Terminal oldTerminal : oldTerminals) { + if (terminalsPerVariant.stream() + .flatMap(Collection::stream) + .filter(t -> t == oldTerminal) + .count() == 1) { + ((TerminalExt) oldTerminal).getReferrerManager().unregister(this); + } } } - @Override - protected void cleanup() { - // when extension removed from extendable, remove the listener. This will happen when merging and detaching. - getExtendable().getNetwork().removeListener(this.referenceTerminalsListener); + private void registerReferencedTerminalIfNeeded(Set terminals) { + // if terminal was not already referenced by another variant, register it + for (Terminal terminal : terminals) { + if (terminalsPerVariant.stream() + .flatMap(Collection::stream) + .noneMatch(t -> t == terminal)) { + ((TerminalExt) terminal).getReferrerManager().register(this); + } + } + } + + private void setTerminalsAndUpdateReferences(int variantIndex, Set terminals) { + unregisterReferencedTerminalIfNeeded(variantIndex); + registerReferencedTerminalIfNeeded(terminals); + terminalsPerVariant.set(variantIndex, new LinkedHashSet<>(terminals)); + } + + private void addTerminalsAndUpdateReferences(Set terminals) { + registerReferencedTerminalIfNeeded(terminals); + terminalsPerVariant.add(new LinkedHashSet<>(terminals)); + } + + private void updateTerminalsAndUpdateReferences(int variantIndex, Terminal terminal) { + registerReferencedTerminalIfNeeded(Set.of(terminal)); + terminalsPerVariant.get(variantIndex).add(terminal); + } + + private void removeTerminalsAndUpdateReferences(int variantIndex) { + unregisterReferencedTerminalIfNeeded(variantIndex); + terminalsPerVariant.remove(variantIndex); // remove elements from the top to avoid moves inside the array } @Override @@ -65,12 +85,12 @@ public Set getReferenceTerminals() { public void setReferenceTerminals(Set terminals) { Objects.requireNonNull(terminals); terminals.forEach(t -> checkTerminalInNetwork(t, getExtendable())); - terminalsPerVariant.set(getVariantIndex(), new LinkedHashSet<>(terminals)); + setTerminalsAndUpdateReferences(getVariantIndex(), terminals); } @Override public ReferenceTerminals reset() { - terminalsPerVariant.set(getVariantIndex(), new LinkedHashSet<>()); + setTerminalsAndUpdateReferences(getVariantIndex(), Collections.emptySet()); return this; } @@ -78,7 +98,7 @@ public ReferenceTerminals reset() { public ReferenceTerminals addReferenceTerminal(Terminal terminal) { Objects.requireNonNull(terminal); checkTerminalInNetwork(terminal, getExtendable()); - terminalsPerVariant.get(getVariantIndex()).add(terminal); + updateTerminalsAndUpdateReferences(getVariantIndex(), terminal); return this; } @@ -87,27 +107,27 @@ public void extendVariantArraySize(int initVariantArraySize, int number, int sou terminalsPerVariant.ensureCapacity(terminalsPerVariant.size() + number); Set sourceTerminals = terminalsPerVariant.get(sourceIndex); for (int i = 0; i < number; ++i) { - terminalsPerVariant.add(new LinkedHashSet<>(sourceTerminals)); + addTerminalsAndUpdateReferences(sourceTerminals); } } @Override public void reduceVariantArraySize(int number) { for (int i = 0; i < number; i++) { - terminalsPerVariant.remove(terminalsPerVariant.size() - 1); // remove elements from the top to avoid moves inside the array + removeTerminalsAndUpdateReferences(terminalsPerVariant.size() - 1); // remove elements from the top to avoid moves inside the array } } @Override public void deleteVariantArrayElement(int index) { - terminalsPerVariant.set(index, new LinkedHashSet<>()); + setTerminalsAndUpdateReferences(index, Collections.emptySet()); } @Override public void allocateVariantArrayElement(int[] indexes, int sourceIndex) { Set sourceTerminals = terminalsPerVariant.get(sourceIndex); for (int index : indexes) { - terminalsPerVariant.set(index, new LinkedHashSet<>(sourceTerminals)); + setTerminalsAndUpdateReferences(index, sourceTerminals); } } @@ -127,4 +147,20 @@ private static void checkTerminalInNetwork(Terminal terminal, Network network) { } } } + + @Override + public void onReferencedRemoval(Terminal removedTerminal) { + for (Set terminals : terminalsPerVariant) { + terminals.remove(removedTerminal); + } + } + + @Override + public void cleanup() { + for (Set terminals : terminalsPerVariant) { + for (Terminal terminal : terminals) { + ((TerminalExt) terminal).getReferrerManager().unregister(this); + } + } + } } diff --git a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/extensions/RemoteReactivePowerControlAdderImpl.java b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/extensions/RemoteReactivePowerControlAdderImpl.java index fe80f5e384b..cb840592d9a 100644 --- a/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/extensions/RemoteReactivePowerControlAdderImpl.java +++ b/iidm/iidm-impl/src/main/java/com/powsybl/iidm/network/impl/extensions/RemoteReactivePowerControlAdderImpl.java @@ -7,6 +7,7 @@ */ package com.powsybl.iidm.network.impl.extensions; +import com.powsybl.commons.PowsyblException; import com.powsybl.commons.extensions.AbstractExtensionAdder; import com.powsybl.iidm.network.Generator; import com.powsybl.iidm.network.Terminal; @@ -18,11 +19,11 @@ */ public class RemoteReactivePowerControlAdderImpl extends AbstractExtensionAdder implements RemoteReactivePowerControlAdder { - private double targetQ; + private double targetQ = Double.NaN; private Terminal regulatingTerminal; - private boolean enabled; + private boolean enabled = true; protected RemoteReactivePowerControlAdderImpl(final Generator extendable) { super(extendable); @@ -30,6 +31,12 @@ protected RemoteReactivePowerControlAdderImpl(final Generator extendable) { @Override protected RemoteReactivePowerControl createExtension(final Generator extendable) { + if (Double.isNaN(targetQ)) { + throw new PowsyblException("Reactive power target must be set"); + } + if (regulatingTerminal == null) { + throw new PowsyblException("Regulating terminal must be set"); + } return new RemoteReactivePowerControlImpl(extendable, targetQ, regulatingTerminal, enabled); } 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 d73f773d934..77b7335b3bf 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 @@ -7,34 +7,48 @@ */ package com.powsybl.iidm.network.impl.extensions; +import com.powsybl.commons.PowsyblException; import com.powsybl.commons.util.trove.TBooleanArrayList; import com.powsybl.iidm.network.Generator; import com.powsybl.iidm.network.Terminal; import com.powsybl.iidm.network.extensions.RemoteReactivePowerControl; import com.powsybl.iidm.network.impl.AbstractMultiVariantIdentifiableExtension; +import com.powsybl.iidm.network.impl.TerminalExt; import gnu.trove.list.array.TDoubleArrayList; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Objects; /** * @author Bertrand Rix {@literal } */ public class RemoteReactivePowerControlImpl extends AbstractMultiVariantIdentifiableExtension implements RemoteReactivePowerControl { - private TDoubleArrayList targetQ; + private static final Logger LOGGER = LoggerFactory.getLogger(RemoteReactivePowerControlImpl.class); + + private final TDoubleArrayList targetQ; private final Terminal regulatingTerminal; - private TBooleanArrayList enabled; + private final TBooleanArrayList enabled; public RemoteReactivePowerControlImpl(Generator generator, double targetQ, Terminal regulatingTerminal, boolean enabled) { super(generator); int variantArraySize = getVariantManagerHolder().getVariantManager().getVariantArraySize(); this.targetQ = new TDoubleArrayList(); - this.regulatingTerminal = regulatingTerminal; + this.regulatingTerminal = Objects.requireNonNull(regulatingTerminal); this.enabled = new TBooleanArrayList(variantArraySize); for (int i = 0; i < variantArraySize; i++) { this.targetQ.add(targetQ); this.enabled.add(enabled); } + if (regulatingTerminal.getVoltageLevel().getParentNetwork() != getExtendable().getParentNetwork()) { + throw new PowsyblException("Regulating terminal is not in the right Network (" + + regulatingTerminal.getVoltageLevel().getParentNetwork().getId() + " instead of " + + getExtendable().getParentNetwork().getId() + ")"); + } + ((TerminalExt) regulatingTerminal).getReferrerManager().register(this); } @Override @@ -92,4 +106,21 @@ public void allocateVariantArrayElement(int[] indexes, int sourceIndex) { enabled.set(index, enabled.get(sourceIndex)); } } + + @Override + public void onReferencedRemoval(Terminal removedTerminal) { + // we cannot set regulating terminal to null because otherwise extension won't be consistent anymore + // we cannot also as for voltage regulation fallback to a local terminal + // so we just remove the extension + LOGGER.warn("Remove 'RemoteReactivePowerControl' extension of generator '{}', because its regulating terminal has been removed", + getExtendable().getId()); + getExtendable().removeExtension(RemoteReactivePowerControl.class); + } + + @Override + public void cleanup() { + if (regulatingTerminal != null) { + ((TerminalExt) regulatingTerminal).getReferrerManager().unregister(this); + } + } } 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 17b41c7e93c..f33f56abbcf 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 @@ -12,6 +12,7 @@ import com.powsybl.iidm.network.VoltageLevel; import com.powsybl.iidm.network.extensions.SlackTerminal; import com.powsybl.iidm.network.impl.AbstractMultiVariantIdentifiableExtension; +import com.powsybl.iidm.network.impl.TerminalExt; import java.util.ArrayList; import java.util.Collections; @@ -31,6 +32,37 @@ public class SlackTerminalImpl extends AbstractMultiVariantIdentifiableExtension this.setTerminal(terminal); } + private void unregisterReferencedTerminalIfNeeded(int variantIndex) { + // check there is no more same terminal referenced by any variant, unregister it + Terminal oldTerminal = terminals.get(variantIndex); + if (oldTerminal != null && Collections.frequency(terminals, oldTerminal) == 1) { + ((TerminalExt) oldTerminal).getReferrerManager().unregister(this); + } + } + + private void registerReferencedTerminalIfNeeded(Terminal terminal) { + // if terminal was not already referenced by another variant, register it + if (terminal != null && !terminals.contains(terminal)) { + ((TerminalExt) terminal).getReferrerManager().register(this); + } + } + + private void setTerminalAndUpdateReferences(int variantIndex, Terminal terminal) { + unregisterReferencedTerminalIfNeeded(variantIndex); + registerReferencedTerminalIfNeeded(terminal); + terminals.set(variantIndex, terminal); + } + + private void addTerminalAndUpdateReferences(Terminal terminal) { + registerReferencedTerminalIfNeeded(terminal); + terminals.add(terminal); + } + + private void removeTerminalAndUpdateReferences(int variantIndex) { + unregisterReferencedTerminalIfNeeded(variantIndex); + terminals.remove(variantIndex); + } + @Override public Terminal getTerminal() { return terminals.get(getVariantIndex()); @@ -42,7 +74,7 @@ public SlackTerminal setTerminal(Terminal terminal) { throw new PowsyblException("Terminal given is not in the right VoltageLevel (" + terminal.getVoltageLevel().getId() + " instead of " + getExtendable().getId() + ")"); } - terminals.set(getVariantIndex(), terminal); + setTerminalAndUpdateReferences(getVariantIndex(), terminal); return this; } @@ -56,27 +88,44 @@ public void extendVariantArraySize(int initVariantArraySize, int number, int sou terminals.ensureCapacity(terminals.size() + number); Terminal sourceTerminal = terminals.get(sourceIndex); for (int i = 0; i < number; ++i) { - terminals.add(sourceTerminal); + addTerminalAndUpdateReferences(sourceTerminal); } } @Override public void reduceVariantArraySize(int number) { for (int i = 0; i < number; i++) { - terminals.remove(terminals.size() - 1); // remove elements from the top to avoid moves inside the array + removeTerminalAndUpdateReferences(terminals.size() - 1); // remove elements from the top to avoid moves inside the array } } @Override public void deleteVariantArrayElement(int index) { - terminals.set(index, null); + setTerminalAndUpdateReferences(index, null); } @Override public void allocateVariantArrayElement(int[] indexes, int sourceIndex) { Terminal terminalSource = terminals.get(sourceIndex); for (int index : indexes) { - terminals.set(index, terminalSource); + setTerminalAndUpdateReferences(index, terminalSource); + } + } + + @Override + public void onReferencedRemoval(Terminal removedTerminal) { + int variantIndex = terminals.indexOf(removedTerminal); + if (variantIndex != -1) { + terminals.set(variantIndex, null); + } + } + + @Override + public void cleanup() { + for (Terminal terminal : terminals) { + if (terminal != null) { + ((TerminalExt) terminal).getReferrerManager().unregister(this); + } } } } diff --git a/iidm/iidm-tck/src/test/java/com/powsybl/iidm/network/tck/AbstractTapChangerTest.java b/iidm/iidm-tck/src/test/java/com/powsybl/iidm/network/tck/AbstractTapChangerTest.java index 5f73b06b4c7..681bc8e710f 100644 --- a/iidm/iidm-tck/src/test/java/com/powsybl/iidm/network/tck/AbstractTapChangerTest.java +++ b/iidm/iidm-tck/src/test/java/com/powsybl/iidm/network/tck/AbstractTapChangerTest.java @@ -16,8 +16,6 @@ import java.util.List; import static org.junit.jupiter.api.Assertions.*; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; import static org.mockito.Mockito.*; public abstract class AbstractTapChangerTest { diff --git a/iidm/iidm-tck/src/test/java/com/powsybl/iidm/network/tck/extensions/AbstractReferenceTerminalsTest.java b/iidm/iidm-tck/src/test/java/com/powsybl/iidm/network/tck/extensions/AbstractReferenceTerminalsTest.java index 9e414ef43e4..3654f96fa9d 100644 --- a/iidm/iidm-tck/src/test/java/com/powsybl/iidm/network/tck/extensions/AbstractReferenceTerminalsTest.java +++ b/iidm/iidm-tck/src/test/java/com/powsybl/iidm/network/tck/extensions/AbstractReferenceTerminalsTest.java @@ -8,17 +8,18 @@ package com.powsybl.iidm.network.tck.extensions; import com.powsybl.commons.PowsyblException; -import com.powsybl.iidm.network.*; +import com.powsybl.iidm.network.Generator; +import com.powsybl.iidm.network.Network; +import com.powsybl.iidm.network.Terminal; +import com.powsybl.iidm.network.VariantManager; import com.powsybl.iidm.network.extensions.ReferenceTerminals; import com.powsybl.iidm.network.extensions.ReferenceTerminalsAdder; 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 org.mockito.Mockito; import java.util.Arrays; -import java.util.Collections; import java.util.List; import java.util.Set; @@ -91,6 +92,9 @@ public void testVariants() { .withTerminals(Set.of(gh1.getTerminal())) .add(); ReferenceTerminals ext = network.getExtension(ReferenceTerminals.class); + assertEquals(1, gh1.getTerminal().getReferrers().size()); + assertEquals(0, gh2.getTerminal().getReferrers().size()); + assertEquals(0, gh3.getTerminal().getReferrers().size()); // create variants String variant1 = "variant1"; @@ -101,9 +105,11 @@ public void testVariants() { // add gh2 to variant1 variantManager.setWorkingVariant(variant1); ext.addReferenceTerminal(gh2.getTerminal()); + assertEquals(1, gh2.getTerminal().getReferrers().size()); // add gh3 to variant2 variantManager.setWorkingVariant(variant2); ext.addReferenceTerminal(gh3.getTerminal()); + assertEquals(1, gh3.getTerminal().getReferrers().size()); // initial variant unmodified variantManager.setWorkingVariant(INITIAL_VARIANT_ID); @@ -123,6 +129,8 @@ public void testVariants() { // clear variant 1 variantManager.setWorkingVariant(variant1); ext.reset(); + assertEquals(0, gh2.getTerminal().getReferrers().size()); + assertEquals(1, gh3.getTerminal().getReferrers().size()); // check variant 1 empty assertEquals(0, ext.getReferenceTerminals().size()); @@ -281,31 +289,4 @@ public void testRemoveEquipment() { assertEquals(1, ext.getReferenceTerminals().size()); assertTrue(ext.getReferenceTerminals().contains(gh2.getTerminal())); } - - @Test - public void testCleanup() { - Network net = Mockito.spy(EurostagTutorialExample1Factory.create()); - - net.newExtension(ReferenceTerminalsAdder.class) - .withTerminals(Collections.emptySet()) - .add(); - // check listener added - Mockito.verify(net, Mockito.times(1)).addListener(Mockito.any()); - Mockito.verify(net, Mockito.times(0)).removeListener(Mockito.any()); - - // overwrite existing extension - net.newExtension(ReferenceTerminalsAdder.class) - .withTerminals(Collections.emptySet()) - .add(); - // check old listener removed and new listener added - Mockito.verify(net, Mockito.times(2)).addListener(Mockito.any()); - Mockito.verify(net, Mockito.times(1)).removeListener(Mockito.any()); - - // remove extension - net.removeExtension(ReferenceTerminals.class); - // check all clean - Mockito.verify(net, Mockito.times(2)).addListener(Mockito.any()); - Mockito.verify(net, Mockito.times(2)).removeListener(Mockito.any()); - } - } diff --git a/iidm/iidm-tck/src/test/java/com/powsybl/iidm/network/tck/extensions/AbstractRemoteReactivePowerControlTest.java b/iidm/iidm-tck/src/test/java/com/powsybl/iidm/network/tck/extensions/AbstractRemoteReactivePowerControlTest.java index 9760b527bc9..eb1f0177e3c 100644 --- a/iidm/iidm-tck/src/test/java/com/powsybl/iidm/network/tck/extensions/AbstractRemoteReactivePowerControlTest.java +++ b/iidm/iidm-tck/src/test/java/com/powsybl/iidm/network/tck/extensions/AbstractRemoteReactivePowerControlTest.java @@ -174,4 +174,42 @@ public void variantsCloneTest() { assertEquals("Variant index not set", e.getMessage()); } } + + @Test + public void adderTest() { + Network network = createNetwork(); + Generator g = network.getGenerator("g4"); + Line l = network.getLine("l34"); + var adder = g.newExtension(RemoteReactivePowerControlAdder.class) + .withTargetQ(200.0) + .withEnabled(true); + var e = assertThrows(PowsyblException.class, adder::add); + assertEquals("Regulating terminal must be set", e.getMessage()); + adder = g.newExtension(RemoteReactivePowerControlAdder.class) + .withRegulatingTerminal(l.getTerminal(TwoSides.ONE)) + .withEnabled(true); + e = assertThrows(PowsyblException.class, adder::add); + assertEquals("Reactive power target must be set", e.getMessage()); + var extension = g.newExtension(RemoteReactivePowerControlAdder.class) + .withTargetQ(200.0) + .withRegulatingTerminal(l.getTerminal(TwoSides.ONE)) + .add(); + assertTrue(extension.isEnabled()); + } + + @Test + public void terminalRemoveTest() { + Network network = createNetwork(); + Generator g = network.getGenerator("g4"); + Line l = network.getLine("l34"); + g.newExtension(RemoteReactivePowerControlAdder.class) + .withTargetQ(200.0) + .withRegulatingTerminal(l.getTerminal(TwoSides.ONE)) + .withEnabled(true) + .add(); + assertNotNull(g.getExtension(RemoteReactivePowerControl.class)); + l.remove(); + // extension has been removed because regulating terminal is invalid + assertNull(g.getExtension(RemoteReactivePowerControl.class)); + } } diff --git a/iidm/iidm-tck/src/test/java/com/powsybl/iidm/network/tck/extensions/AbstractSlackTerminalTest.java b/iidm/iidm-tck/src/test/java/com/powsybl/iidm/network/tck/extensions/AbstractSlackTerminalTest.java index 8d665ba4e3b..774fd2d71f7 100644 --- a/iidm/iidm-tck/src/test/java/com/powsybl/iidm/network/tck/extensions/AbstractSlackTerminalTest.java +++ b/iidm/iidm-tck/src/test/java/com/powsybl/iidm/network/tck/extensions/AbstractSlackTerminalTest.java @@ -206,26 +206,33 @@ public void variantsResetTest() { SlackTerminal stGen = vlgen.getExtension(SlackTerminal.class); assertNotNull(stGen); final Terminal tGen = stGen.getTerminal(); + assertEquals(1, tGen.getReferrers().size()); // Testing that only current variant was set variantManager.setWorkingVariant(INITIAL_VARIANT_ID); assertNull(stGen.getTerminal()); stGen.setTerminal(tGen); + assertEquals(1, tGen.getReferrers().size()); variantManager.setWorkingVariant(variant1); assertNull(stGen.getTerminal()); stGen.setTerminal(tGen); + assertEquals(1, tGen.getReferrers().size()); // Testing the empty property of the slackTerminal variantManager.setWorkingVariant(INITIAL_VARIANT_ID); assertFalse(stGen.setTerminal(null).isEmpty()); + assertEquals(1, tGen.getReferrers().size()); variantManager.setWorkingVariant(variant2); assertFalse(stGen.setTerminal(null).isEmpty()); + assertEquals(1, tGen.getReferrers().size()); variantManager.setWorkingVariant(variant1); assertTrue(stGen.setTerminal(null).isEmpty()); + assertEquals(0, tGen.getReferrers().size()); assertFalse(stGen.setTerminal(tGen).isEmpty()); + assertEquals(1, tGen.getReferrers().size()); // Testing the cleanIfEmpty boolean stGen.setTerminal(null, false); @@ -284,4 +291,17 @@ public void testWithSubnetwork() { assertNull(merged.getVoltageLevel("VL").getExtension(SlackTerminal.class)); // reset assertNotNull(merged.getVoltageLevel("VLHV1").getExtension(SlackTerminal.class)); // untouched } + + @Test + public void removeTerminalConnectableTest() { + Network network = EurostagTutorialExample1Factory.createWithMoreGenerators(); + var vlgen = network.getVoltageLevel("VLGEN"); + var gen2 = network.getGenerator("GEN2"); + var slackTerminal = vlgen.newExtension(SlackTerminalAdder.class) + .withTerminal(gen2.getTerminal()) + .add(); + assertNotNull(slackTerminal.getTerminal()); + gen2.remove(); + assertNull(slackTerminal.getTerminal()); + } } diff --git a/security-analysis/security-analysis-api/src/main/java/com/powsybl/security/extensions/ActivePowerExtension.java b/security-analysis/security-analysis-api/src/main/java/com/powsybl/security/extensions/ActivePowerExtension.java index 63189bc0eff..9375c5af722 100644 --- a/security-analysis/security-analysis-api/src/main/java/com/powsybl/security/extensions/ActivePowerExtension.java +++ b/security-analysis/security-analysis-api/src/main/java/com/powsybl/security/extensions/ActivePowerExtension.java @@ -7,13 +7,13 @@ */ package com.powsybl.security.extensions; -import com.powsybl.commons.extensions.Extension; +import com.powsybl.commons.extensions.AbstractExtension; import com.powsybl.security.LimitViolation; /** * @author Mathieu Bague {@literal } */ -public class ActivePowerExtension implements Extension { +public class ActivePowerExtension extends AbstractExtension { private LimitViolation limitViolation;