Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix ReferenceTerminals and SlackTerminal iIDM extension with merged networks and subnetworks #3076

Merged
merged 11 commits into from
Jun 21, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ default String getName() {
ReferenceTerminals addReferenceTerminal(Terminal terminal);

/**
* Deletes all defined reference terminals in the network for the current variant
* Deletes all defined reference terminals in the network and all its subnetworks for the current variant
olperr1 marked this conversation as resolved.
Show resolved Hide resolved
* @param network network whose reference terminals should be deleted
*/
static void reset(Network network) {
Expand All @@ -51,10 +51,13 @@ static void reset(Network network) {
.add();
}
ext.reset();
// reset also all subnetworks
network.getSubnetworks().forEach(ReferenceTerminals::reset);
}

/**
* Defines/add a terminal as reference in the network for the current variant
* Defines/add a terminal as reference in the network for the current variant.
* In case of a merged network with subnetwork, the extension is placed on the root/merged network.
olperr1 marked this conversation as resolved.
Show resolved Hide resolved
* @param terminal terminal to be added as reference terminal
*/
static void addTerminal(Terminal terminal) {
Expand All @@ -70,7 +73,10 @@ static void addTerminal(Terminal terminal) {
}

/**
* Gets the reference terminals in the network for the current variant
* Gets the reference terminals in the network for the current variant.
* <p> Note: This method returns only the terminal from the extension attached to the provided network.
* In case of a merged network with subnetworks, be careful whether you want the extension
* of the merged network or of a subnetwork.</p>
* @param network network to get reference terminals from
*/
static Set<Terminal> getTerminals(Network network) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,16 @@ public interface SlackTerminal extends Extension<VoltageLevel> {
String NAME = "slackTerminal";

/**
* Set the terminal of all SlackTerminal extensions from the given network to null. If the extension is empty,
* meaning that for each variant the terminal is null, this method automatically remove the extension.
* Set the terminal of all SlackTerminal extensions from the given network and all its subnetworks to null.
* If the extension is empty, meaning that for each variant the terminal is null, this method automatically
* remove the extension.
*
* @param network A network to cleanup
*/
static void reset(Network network) {
network.getVoltageLevels().forEach(vl -> reset(vl, null));
// reset also all subnetworks
network.getSubnetworks().forEach(sn -> sn.getVoltageLevels().forEach(vl -> reset(vl, null)));
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class ReferenceTerminalsImpl extends AbstractMultiVariantIdentifiableExtension<N

private final class ReferenceTerminalsListener extends DefaultNetworkListener {
@Override
public void beforeRemoval(Identifiable identifiable) {
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));
Expand All @@ -39,12 +39,21 @@ public ReferenceTerminalsImpl(Network network, Set<Terminal> terminals) {
Collections.nCopies(getVariantManagerHolder().getVariantManager().getVariantArraySize(), new LinkedHashSet<>()));
setReferenceTerminals(terminals);
this.referenceTerminalsListener = new ReferenceTerminalsListener();
network.addListener(this.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);
}
}

@Override
protected void cleanup() {
getExtendable().removeListener(this.referenceTerminalsListener);
// when extension removed from extendable, remove the listener. This will happen when merging and detaching.
getExtendable().getNetwork().removeListener(this.referenceTerminalsListener);
}

@Override
Expand Down Expand Up @@ -103,9 +112,19 @@ public void allocateVariantArrayElement(int[] indexes, int sourceIndex) {
}

private static void checkTerminalInNetwork(Terminal terminal, Network network) {
if (!terminal.getVoltageLevel().getNetwork().equals(network)) {
throw new PowsyblException("Terminal given is not in the right Network ("
+ terminal.getVoltageLevel().getNetwork().getId() + " instead of " + network.getId() + ")");
final boolean extendableIsRootNetwork = network.getNetwork().equals(network);
if (extendableIsRootNetwork) {
// it is all fine as long as the terminal belongs to the merged network
if (!terminal.getVoltageLevel().getNetwork().equals(network)) {
throw new PowsyblException("Terminal given is not in the right Network ("
+ terminal.getVoltageLevel().getNetwork().getId() + " instead of " + network.getId() + ")");
}
} else {
// subnetwork: the terminal must be in the subnetwork
if (!terminal.getVoltageLevel().getParentNetwork().equals(network)) {
throw new PowsyblException("Terminal given is not in the right Network ("
+ terminal.getVoltageLevel().getParentNetwork().getId() + " instead of " + network.getId() + ")");
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -184,9 +184,9 @@ public void testVariantsCloning() {
public void testWrongNetwork() {
Network other = EurostagTutorialExample1Factory.create();
Terminal terminal = other.getBusBreakerView().getBus("NHV1").getConnectedTerminals().iterator().next();
PowsyblException ex1 = assertThrows(PowsyblException.class, () -> network.newExtension(ReferenceTerminalsAdder.class)
.withTerminals(Set.of(terminal))
.add());
ReferenceTerminalsAdder referenceTerminalsAdder = network.newExtension(ReferenceTerminalsAdder.class)
.withTerminals(Set.of(terminal));
PowsyblException ex1 = assertThrows(PowsyblException.class, referenceTerminalsAdder::add);
assertEquals("Terminal given is not in the right Network (sim1 instead of fourSubstations)", ex1.getMessage());
network.newExtension(ReferenceTerminalsAdder.class)
.withTerminals(Set.of())
Expand All @@ -196,6 +196,77 @@ public void testWrongNetwork() {
assertEquals("Terminal given is not in the right Network (sim1 instead of fourSubstations)", ex2.getMessage());
}

@Test
public void testWithSubnetwork() {
Network merged = Network.merge(network, EurostagTutorialExample1Factory.create());
olperr1 marked this conversation as resolved.
Show resolved Hide resolved
Network subnetwork = merged.getSubnetwork("fourSubstations");
Network sim1subnetwork = merged.getSubnetwork("sim1");
gh1 = merged.getGenerator("GH1");
gh2 = merged.getGenerator("GH2");
Terminal gh1Terminal = gh1.getTerminal();
Terminal gh2Terminal = gh2.getTerminal();

// gh1 is in subnetwork and can be added to subnetwork extension
subnetwork.newExtension(ReferenceTerminalsAdder.class)
.withTerminals(Set.of(gh1Terminal))
.add();
ReferenceTerminals extSubnetwork = subnetwork.getExtension(ReferenceTerminals.class);
assertEquals(1, extSubnetwork.getReferenceTerminals().size());
assertTrue(extSubnetwork.getReferenceTerminals().contains(gh1Terminal));

// gh2 is in subnetwork and can be added to root network extension
merged.newExtension(ReferenceTerminalsAdder.class)
.withTerminals(Set.of(gh2Terminal))
.add();
ReferenceTerminals extMergedNetwork = merged.getExtension(ReferenceTerminals.class);
assertEquals(1, extMergedNetwork.getReferenceTerminals().size());
assertTrue(extMergedNetwork.getReferenceTerminals().contains(gh2Terminal));

// we can reset everything via this method
ReferenceTerminals.reset(merged);
assertTrue(ReferenceTerminals.getTerminals(merged).isEmpty());

// we can add easily to merged/root network via this method
ReferenceTerminals.addTerminal(gh1Terminal);
extMergedNetwork = merged.getExtension(ReferenceTerminals.class);
extSubnetwork = subnetwork.getExtension(ReferenceTerminals.class);
assertEquals(1, extMergedNetwork.getReferenceTerminals().size());
assertEquals(0, extSubnetwork.getReferenceTerminals().size()); // not added to subnetwork
// same as above, but using the more user-friendly static methods from ReferenceTerminals.
assertEquals(1, ReferenceTerminals.getTerminals(merged).size());
assertEquals(0, ReferenceTerminals.getTerminals(subnetwork).size());

// we can't add gh1 to sim1
sim1subnetwork.newExtension(ReferenceTerminalsAdder.class)
.withTerminals(Set.of())
.add();
ReferenceTerminals extSim1 = sim1subnetwork.getExtension(ReferenceTerminals.class);
PowsyblException ex = assertThrows(PowsyblException.class, () -> extSim1.addReferenceTerminal(gh1Terminal));
assertEquals("Terminal given is not in the right Network (fourSubstations instead of sim1)", ex.getMessage());
}

@Test
public void testListenersTransferOnMergeAndDetach() {
Network network1 = FourSubstationsNodeBreakerFactory.create();
Network network2 = EurostagTutorialExample1Factory.create();
ReferenceTerminals.addTerminal(network1.getGenerator("GH1").getTerminal());
ReferenceTerminals.addTerminal(network1.getGenerator("GH2").getTerminal());

Network merged = Network.merge(network1, network2);
network1 = merged.getSubnetwork("fourSubstations");

// check listener is now effective on merged network
assertEquals(2, ReferenceTerminals.getTerminals(network1).size());
merged.getGenerator("GH1").remove();
assertEquals(1, ReferenceTerminals.getTerminals(network1).size());

Network network1detached = network1.detach();
// check listener is now effective on detached network
assertEquals(1, ReferenceTerminals.getTerminals(network1detached).size());
network1detached.getGenerator("GH2").remove();
assertEquals(0, ReferenceTerminals.getTerminals(network1detached).size());
}

@Test
public void testRemoveEquipment() {
network.newExtension(ReferenceTerminalsAdder.class)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,6 @@ static Network createBusBreakerNetwork() {
.setQ0(50)
.add();

Substation s1 = network.newSubstation()
.setId("S1")
.setCountry(Country.FR)
.add();
VoltageLevel vl1 = s.newVoltageLevel()
.setId("VL1")
.setNominalV(400)
Expand Down Expand Up @@ -260,4 +256,32 @@ public void variantsResetTest() {
assertNull(vlhv1.getExtension(SlackTerminal.class));

}

@Test
public void testWithSubnetwork() {
Network network1 = createBusBreakerNetwork();
SlackTerminal.attach(network1.getBusBreakerView().getBus("B"));
Network network2 = EurostagTutorialExample1Factory.create();
SlackTerminal.attach(network2.getBusBreakerView().getBus("NHV1"));

Network merged = Network.merge(network1, network2);
network1 = merged.getSubnetwork("test");
network2 = merged.getSubnetwork("sim1");

// still there after merge
assertNotNull(merged.getVoltageLevel("VL").getExtension(SlackTerminal.class));
assertNotNull(merged.getVoltageLevel("VLHV1").getExtension(SlackTerminal.class));

// we can reset everything (including subnetworks)
SlackTerminal.reset(merged);
assertNull(merged.getVoltageLevel("VL").getExtension(SlackTerminal.class));
assertNull(merged.getVoltageLevel("VLHV1").getExtension(SlackTerminal.class));

// we can reset only a subnetwork
SlackTerminal.attach(network1.getBusBreakerView().getBus("B"));
SlackTerminal.attach(network2.getBusBreakerView().getBus("NHV1"));
SlackTerminal.reset(network1);
assertNull(merged.getVoltageLevel("VL").getExtension(SlackTerminal.class)); // reset
assertNotNull(merged.getVoltageLevel("VLHV1").getExtension(SlackTerminal.class)); // untouched
}
}