MergeTest.java
/**
* Copyright (c) 2019, 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.PowsyblException;
import com.powsybl.iidm.network.*;
import com.powsybl.iidm.network.test.NetworkTest1Factory;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.StringWriter;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
/**
* @author Luma Zamarre��o {@literal <zamarrenolm at aia.es>}
*/
class MergeTest {
@Test
void mergeNodeBreakerTestNPE() throws IOException {
Network n1 = createNetworkWithDanglingLine("1");
Network n2 = createNetworkWithDanglingLine("2");
logVoltageLevel("Network 1 first voltage level", n1.getVoltageLevels().iterator().next());
Network merge = Network.merge(n1, n2);
// If we try to get connected components directly on the merged network,
// A Null Pointer Exception happens in AbstractConnectable.notifyUpdate:
// There is a CalculatedBus that has a terminal that refers to the removed DanglingLine
// DanglingLine object has VoltageLevel == null,
// NPE comes from trying to getNetwork() using VoltageLevel to notify a change in connected components
checkConnectedComponents(merge);
}
@Test
void mergeNodeBreakerTestPass1() {
Network n1 = createNetworkWithDanglingLine("1");
Network n2 = createNetworkWithDanglingLine("2");
// The test passes if we do not log voltage level (exportTopology)
Network merge = Network.merge(n1, n2);
checkConnectedComponents(merge);
}
@Test
void mergeNodeBreakerTestPass2() throws IOException {
Network n1 = createNetworkWithDanglingLine("1");
Network n2 = createNetworkWithDanglingLine("2");
logVoltageLevel("Network 1 first voltage level", n1.getVoltageLevels().iterator().next());
// The test also passes if we "force" the connected component calculation before merge
checkConnectedComponents(n1);
Network merge = Network.merge(n1, n2);
checkConnectedComponents(n1);
}
private static void logVoltageLevel(String title, VoltageLevel vl) throws IOException {
LOG.info(title);
try (StringWriter w = new StringWriter()) {
vl.exportTopology(w);
LOG.info(w.toString());
}
}
private static void checkConnectedComponents(Network n) {
n.getBusView().getBuses().forEach(b -> assertEquals(0, b.getConnectedComponent().getNum()));
}
private static Network createNetworkWithDanglingLine(String nid) {
Network n = NetworkTest1Factory.create(nid);
VoltageLevel vl = n.getVoltageLevel(id("voltageLevel1", nid));
DanglingLine dl = vl.newDanglingLine()
.setId(id("danglingLineb", nid))
.setNode(6)
.setR(1.0)
.setX(0.1)
.setG(0.0)
.setB(0.001)
.setP0(10)
.setQ0(1)
// Same pairing key for dangling lines
.setPairingKey("X")
.add();
vl.getNodeBreakerView().newBreaker()
.setId(id("voltageLevel1BreakerDLb", nid))
.setRetained(false)
.setOpen(false)
.setNode1(n.getBusbarSection(id("voltageLevel1BusbarSection1", nid)).getTerminal().getNodeBreakerView().getNode())
.setNode2(dl.getTerminal().getNodeBreakerView().getNode())
.add();
return n;
}
@Test
void mergeTwoNetworksWithVoltageAngleLimits() {
Network network1 = createNodeBreakerWithVoltageAngleLimit("1");
Network network2 = createNodeBreakerWithVoltageAngleLimit("2");
List<VoltageAngleLimit> val1 = network1.getVoltageAngleLimitsStream().toList();
List<VoltageAngleLimit> val2 = network2.getVoltageAngleLimitsStream().toList();
List<VoltageAngleLimit> valMerge = new ArrayList<>();
valMerge.addAll(val1);
valMerge.addAll(val2);
Network merge = Network.merge(network1, network2);
assertTrue(voltageAngleLimitsAreEqual(valMerge, merge.getVoltageAngleLimitsStream().toList()));
network1 = merge.getSubnetwork(network1.getId()).detach();
assertTrue(voltageAngleLimitsAreEqual(val1, network1.getVoltageAngleLimitsStream().toList()));
assertEquals(1, merge.getVoltageAngleLimitsStream().count());
network2 = merge.getSubnetwork(network2.getId()).detach();
assertTrue(voltageAngleLimitsAreEqual(val2, network2.getVoltageAngleLimitsStream().toList()));
assertEquals(0, merge.getVoltageAngleLimitsStream().count());
}
@Test
void failMergeWithVoltageAngleLimits() {
Network network1 = createNodeBreakerWithVoltageAngleLimit("1", "duplicate");
Network network2 = createNodeBreakerWithVoltageAngleLimit("2", "duplicate");
PowsyblException e = assertThrows(PowsyblException.class, () -> Network.merge(network1, network2));
assertEquals("The following voltage angle limit(s) exist(s) in both networks: [duplicate]", e.getMessage());
}
@Test
void failDetachWithVoltageAngleLimits() {
Network network1 = createNodeBreakerWithVoltageAngleLimit("1");
Network network2 = createNodeBreakerWithVoltageAngleLimit("2");
Network merge = Network.merge(network1, network2);
merge.newVoltageAngleLimit()
.setId("valMerge")
.from(merge.getLine(id("Line-2-2", "1")).getTerminal1())
.to(merge.getLine(id("Line-2-2", "2")).getTerminal1())
.setHighLimit(0.25)
.add();
Network subnetwork1 = merge.getSubnetwork(network1.getId());
PowsyblException e = assertThrows(PowsyblException.class, subnetwork1::detach);
assertEquals("VoltageAngleLimits prevent the subnetwork to be detached: valMerge", e.getMessage());
}
@Test
void mergeTwoNetworksWithVoltageAngleLimitsFail() {
Network network1 = createNodeBreakerWithVoltageAngleLimit("1");
Network network2 = createNodeBreakerWithVoltageAngleLimit("2");
network1.newVoltageAngleLimit()
.setId("LimitCollision")
.from(network1.getLine(id("Line-2-2", "1")).getTerminal1())
.to(network1.getDanglingLine(id("Dl-3", "1")).getTerminal())
.setHighLimit(0.25)
.add();
network2.newVoltageAngleLimit()
.setId("LimitCollision")
.from(network2.getLine(id("Line-2-2", "2")).getTerminal1())
.to(network2.getDanglingLine(id("Dl-3", "2")).getTerminal())
.setHighLimit(0.25)
.add();
PowsyblException e = assertThrows(PowsyblException.class, () -> Network.merge(network1, network2));
assertEquals("The following voltage angle limit(s) exist(s) in both networks: [LimitCollision]", e.getMessage());
}
@Test
void mergeNetworksWithDifferentCaseDates() {
ZonedDateTime zonedDateTime1 = ZonedDateTime.of(1999, 12, 1, 10, 30, 0, 0, ZoneId.of("UTC"));
ZonedDateTime zonedDateTime2 = ZonedDateTime.of(2021, 10, 31, 11, 30, 0, 0, ZoneId.of("UTC"));
Network network1 = createNodeBreakerWithVoltageAngleLimit("1");
network1.setCaseDate(zonedDateTime1);
Network network2 = createNodeBreakerWithVoltageAngleLimit("2");
network2.setCaseDate(zonedDateTime2);
Network network3 = createNodeBreakerWithVoltageAngleLimit("3");
network3.setCaseDate(zonedDateTime1);
Network networkMerged = Network.merge(network1, network2, network3);
assertNotEquals(zonedDateTime1, networkMerged.getCaseDate());
assertNotEquals(zonedDateTime2, networkMerged.getCaseDate());
}
@Test
void mergeNetworksWithSameCaseDates() {
ZonedDateTime zonedDateTime1 = ZonedDateTime.of(1999, 12, 1, 10, 30, 0, 0, ZoneId.of("UTC"));
Network network1 = createNodeBreakerWithVoltageAngleLimit("1");
network1.setCaseDate(zonedDateTime1);
Network network2 = createNodeBreakerWithVoltageAngleLimit("2");
network2.setCaseDate(zonedDateTime1);
Network network3 = createNodeBreakerWithVoltageAngleLimit("3");
network3.setCaseDate(zonedDateTime1);
Network networkMerged = Network.merge(network1, network2, network3);
assertEquals(zonedDateTime1, networkMerged.getCaseDate());
}
private static boolean voltageAngleLimitsAreEqual(List<VoltageAngleLimit> expected, List<VoltageAngleLimit> actual) {
if (expected.size() != actual.size()) {
return false;
}
for (VoltageAngleLimit voltageAngleLimit : actual) {
if (!isContained(expected, voltageAngleLimit)) {
return false;
}
}
return true;
}
private static boolean isContained(List<VoltageAngleLimit> expected, VoltageAngleLimit actual) {
return expected.stream().filter(val -> val.getTerminalFrom().getConnectable().getId().equals(actual.getTerminalFrom().getConnectable().getId())
&& Terminal.getConnectableSide(val.getTerminalFrom()).equals(Terminal.getConnectableSide(actual.getTerminalFrom()))
&& val.getTerminalTo().getConnectable().getId().equals(actual.getTerminalTo().getConnectable().getId())
&& Terminal.getConnectableSide(val.getTerminalTo()).equals(Terminal.getConnectableSide(actual.getTerminalTo()))).count() == 1;
}
private static Network createNodeBreakerWithVoltageAngleLimit(String nid, String valId) {
return createNodeBreakerWithVoltageAngleLimit(NetworkFactory.findDefault(), nid, valId);
}
private static Network createNodeBreakerWithVoltageAngleLimit(String nid) {
return createNodeBreakerWithVoltageAngleLimit(nid, id("VoltageAngleLimit_Line-2-2_Dl-3", nid));
}
private static Network createNodeBreakerWithVoltageAngleLimit(NetworkFactory networkFactory, String nid, String valId) {
Network network = networkFactory.createNetwork(id("nodeBreakerWithVoltageAngleLimit", nid), "test");
double vn = 225.0;
// First substation
Substation s1 = network.newSubstation()
.setId(id("S1", nid))
.add();
VoltageLevel s1vl1 = s1.newVoltageLevel()
.setId(id("S1VL1", nid))
.setNominalV(vn)
.setLowVoltageLimit(vn * 0.9)
.setHighVoltageLimit(vn * 1.1)
.setTopologyKind(TopologyKind.NODE_BREAKER)
.add();
createBusbarSection(s1vl1, id("S1VL1_BBS0A", nid), id("S1VL1_BBS0A", nid), 0);
createInternalConnection(s1vl1, 0, 1);
createInternalConnection(s1vl1, 0, 2);
createGenerator(s1vl1, id("S1VL1-Generator", nid), vn, 80.0, 10.0, 1);
// Second substation
Substation s2 = network.newSubstation()
.setId(id("S2", nid))
.add();
VoltageLevel s2vl1 = s2.newVoltageLevel()
.setId(id("S2VL1", nid))
.setNominalV(vn)
.setLowVoltageLimit(vn * 0.9)
.setHighVoltageLimit(vn * 1.1)
.setTopologyKind(TopologyKind.NODE_BREAKER)
.add();
createBusbarSection(s2vl1, id("S2VL1_BBS0", nid), id("S2VL1_BBS0", nid), 0);
createInternalConnection(s2vl1, 0, 1);
createInternalConnection(s2vl1, 0, 2);
createInternalConnection(s2vl1, 0, 3);
createLoad(s2vl1, id("S2VL1-Load", nid), 45.0, 9.0, 1);
createDanglingLine(network, id("S2VL1", nid), id("Dl-3", nid), 70.0, 10.0, "pairingKey", 3);
// Line between both substations
createLine(network, id("S1VL1", nid), id("S2VL1", nid), id("Line-2-2", nid), 2, 2);
network.newVoltageAngleLimit()
.setId(valId)
.from(network.getLine(id("Line-2-2", nid)).getTerminal1())
.to(network.getDanglingLine(id("Dl-3", nid)).getTerminal())
.setHighLimit(0.25)
.add();
return network;
}
private static void createBusbarSection(VoltageLevel vl, String id, String name, int node) {
vl.getNodeBreakerView().newBusbarSection()
.setId(id)
.setName(name)
.setNode(node)
.add();
}
private static void createInternalConnection(VoltageLevel vl, int node1, int node2) {
vl.getNodeBreakerView().newInternalConnection()
.setNode1(node1)
.setNode2(node2)
.add();
}
private static void createLoad(VoltageLevel vl, String id, double p, double q, int node) {
Load load = vl.newLoad()
.setId(id)
.setLoadType(LoadType.UNDEFINED)
.setP0(p)
.setQ0(q)
.setNode(node)
.add();
load.getTerminal().setP(p).setQ(q);
}
private static void createGenerator(VoltageLevel vl, String id, double targetV, double p, double q, int node) {
Generator generator = vl.newGenerator()
.setId(id)
.setEnergySource(EnergySource.HYDRO)
.setMinP(-500.0)
.setMaxP(500.0)
.setVoltageRegulatorOn(true)
.setTargetP(p)
.setTargetV(targetV)
.setTargetQ(q)
.setNode(node)
.add();
generator.newMinMaxReactiveLimits()
.setMinQ(-500.0)
.setMaxQ(500.0)
.add();
generator.getTerminal().setP(-p).setQ(-q);
}
private static void createLine(Network network, String vl1id, String vl2id, String id, int node1, int node2) {
network.newLine()
.setId(id)
.setR(0.01)
.setX(2.0)
.setG1(0.0)
.setB1(0.0005)
.setG2(0.0)
.setB2(0.0005)
.setNode1(node1)
.setVoltageLevel1(vl1id)
.setNode2(node2)
.setVoltageLevel2(vl2id)
.add();
}
private static void createDanglingLine(Network network, String vlId, String id, double p0, double q0, String pairingKey, int node) {
network.getVoltageLevel(vlId).newDanglingLine()
.setId(id)
.setR(0.01)
.setX(2.0)
.setG(0.0)
.setB(0.0)
.setP0(p0)
.setQ0(q0)
.setPairingKey(pairingKey)
.setNode(node)
.setEnsureIdUnicity(false)
.add();
}
private static String id(String localId, String networkId) {
return NetworkTest1Factory.id(localId, networkId);
}
private static final Logger LOG = LoggerFactory.getLogger(MergeTest.class);
}