SwitchConversionTest.java
/**
* 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.cgmes.conversion.test;
import com.powsybl.cgmes.conversion.Conversion;
import com.powsybl.commons.test.AbstractSerDeTest;
import com.powsybl.iidm.network.*;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import static com.powsybl.cgmes.conversion.test.ConversionUtil.*;
import static org.junit.jupiter.api.Assertions.*;
/**
* @author Florian Dupuy {@literal <florian.dupuy at rte-france.com>}
* @author Romain Courtier {@literal <romain.courtier at rte-france.com>}
*/
class SwitchConversionTest extends AbstractSerDeTest {
private static final String DIR = "/issues/switches/";
@Test
void basicSwitchTest() {
// CGMES network:
// A Breaker with all optional fields defined, and a Disconnector with just the required fields defined.
// IIDM network:
// All fields have been correctly read.
Network network = readCgmesResources(DIR, "basic_switch.xml");
assertNotNull(network);
// Breaker has all optional fields defined.
Switch breaker = network.getSwitch("BR");
assertEquals(SwitchKind.BREAKER, breaker.getKind());
assertEquals("Breaker", breaker.getNameOrId());
assertTrue(breaker.isOpen());
assertTrue(breaker.isRetained());
// Disconnector has only the required fields defined.
Switch disconnector = network.getSwitch("DIS");
assertEquals(SwitchKind.DISCONNECTOR, disconnector.getKind());
assertEquals("DIS", disconnector.getNameOrId()); // Returns ID since name is undefined
assertFalse(disconnector.isOpen()); // default value if undefined is false
assertFalse(disconnector.isRetained()); // default value if undefined is false
}
@Test
void switchKindTest() throws IOException {
// CGMES network:
// A Breaker BR, Disconnector DIS, LoadBreakSwitch LBS (direct map to IIDM),
// Switch SW, ProtectedSwitch PSW, GroundDisconnector GRD, Jumper JUM (indirect map to IIDM).
// IIDM network:
// All Switch are imported.
// If the CGMES original class doesn't correspond to an IIDM kind, it is saved in a property.
Network network = readCgmesResources(DIR, "switch_kind.xml");
assertNotNull(network);
// Check that the switch kind is correct.
assertEquals(SwitchKind.BREAKER, network.getSwitch("BR").getKind());
assertEquals(SwitchKind.DISCONNECTOR, network.getSwitch("DIS").getKind());
assertEquals(SwitchKind.LOAD_BREAK_SWITCH, network.getSwitch("LBS").getKind());
assertEquals(SwitchKind.BREAKER, network.getSwitch("SW").getKind());
assertEquals(SwitchKind.BREAKER, network.getSwitch("PSW").getKind());
assertEquals(SwitchKind.DISCONNECTOR, network.getSwitch("GRD").getKind());
assertEquals(SwitchKind.DISCONNECTOR, network.getSwitch("JUM").getKind());
// For Switch, ProtectedSwitch, GroundDisconnector, Jumper (indirect mapping), the CGMES original class is stored.
assertNull(network.getSwitch("BR").getProperty(Conversion.PROPERTY_CGMES_ORIGINAL_CLASS));
assertNull(network.getSwitch("DIS").getProperty(Conversion.PROPERTY_CGMES_ORIGINAL_CLASS));
assertNull(network.getSwitch("LBS").getProperty(Conversion.PROPERTY_CGMES_ORIGINAL_CLASS));
assertEquals("Switch", network.getSwitch("SW").getProperty(Conversion.PROPERTY_CGMES_ORIGINAL_CLASS));
assertEquals("ProtectedSwitch", network.getSwitch("PSW").getProperty(Conversion.PROPERTY_CGMES_ORIGINAL_CLASS));
assertEquals("GroundDisconnector", network.getSwitch("GRD").getProperty(Conversion.PROPERTY_CGMES_ORIGINAL_CLASS));
assertEquals("Jumper", network.getSwitch("JUM").getProperty(Conversion.PROPERTY_CGMES_ORIGINAL_CLASS));
// Correct class is restored in CGMES EQ and SSH export.
String eqFile = writeCgmesProfile(network, "EQ", tmpDir);
String sshFile = writeCgmesProfile(network, "SSH", tmpDir);
assertTrue(containsObject(eqFile, sshFile, "Breaker", "BR"));
assertTrue(containsObject(eqFile, sshFile, "Disconnector", "DIS"));
assertTrue(containsObject(eqFile, sshFile, "LoadBreakSwitch", "LBS"));
assertTrue(containsObject(eqFile, sshFile, "Switch", "SW"));
assertTrue(containsObject(eqFile, sshFile, "ProtectedSwitch", "PSW"));
assertTrue(containsObject(eqFile, sshFile, "GroundDisconnector", "GRD"));
assertTrue(containsObject(eqFile, sshFile, "Jumper", "JUM"));
}
private boolean containsObject(String eqFile, String sshFile, String className, String rdfId) {
return eqFile.contains("<cim:" + className + " rdf:ID=\"_" + rdfId + "\">")
&& sshFile.contains("<cim:" + className + " rdf:about=\"#_" + rdfId + "\">");
}
@Test
void switchInBusBranchTest() throws IOException {
// CGMES network:
// A bus-branch network with a non-retained (doesn't make sense) Disconnector DIS.
// IIDM network:
// In bus breaker topology kind, all switches are Breaker and retained.
Network network = readCgmesResources(DIR, "switch_in_bus_branch_EQ.xml", "switch_in_bus_branch_TP.xml");
assertNotNull(network);
// The Switch is imported with kind breaker, and its original CGMES class is stored.
Switch disconnector = network.getSwitch("DIS");
assertEquals(SwitchKind.BREAKER, disconnector.getKind());
assertEquals("Disconnector", disconnector.getProperty(Conversion.PROPERTY_CGMES_ORIGINAL_CLASS));
// The Switch is retained in IIDM, even though it is not in the original CGMES file.
assertTrue(disconnector.isRetained());
// It is a retained Disconnector in the CGMES export.
String eqFile = writeCgmesProfile(network, "EQ", tmpDir);
String xmlDisconnector = getElement(eqFile, "Disconnector", "DIS");
assertTrue(xmlDisconnector.contains("<cim:Switch.retained>true</cim:Switch.retained>"));
}
@Test
void lineWithZeroImpedanceTest() {
// CGMES network:
// An ACLineSegment ACL with zero impedance between two nodes of the same voltage level.
// IIDM network:
// A branch with 0 impedance inside a VoltageLevel is converted to a Switch.
Network network = readCgmesResources(DIR, "line_with_0_impedance.xml");
assertNotNull(network);
// The line has been imported as a fictitious switch
assertNull(network.getLine("ACL"));
assertNotNull(network.getSwitch("ACL"));
assertTrue(network.getSwitch("ACL").isFictitious());
}
@Test
void fictitiousSwitchForDisconnectedTerminalTest() throws IOException {
// CGMES network:
// A Load, whose terminal T_LD is disconnected, attached to a bus.
// IIDM network:
// Fictitious Switch are created for disconnected terminals, but these shouldn't be exported back to CGMES.
Network network = readCgmesResources(DIR, "disconnected_terminal_EQ.xml", "disconnected_terminal_SSH.xml");
assertNotNull(network);
// A fictitious switch has been created for the disconnected terminal.
Switch fictitiousSwitch = network.getSwitch("T_LD_SW_fict");
assertNotNull(fictitiousSwitch);
assertTrue(fictitiousSwitch.isFictitious());
assertTrue(fictitiousSwitch.isOpen());
assertEquals("true", fictitiousSwitch.getProperty(Conversion.PROPERTY_IS_CREATED_FOR_DISCONNECTED_TERMINAL));
// The fictitious switch isn't present in the EQ export and the terminal is disconnected in the SSH.
String eqFile = writeCgmesProfile(network, "EQ", tmpDir);
String sshFile = writeCgmesProfile(network, "SSH", tmpDir);
assertFalse(eqFile.contains("<cim:Breaker rdf:ID="));
String xmlLoadTerminal = getElement(sshFile, "Terminal", "T_LD");
assertTrue(xmlLoadTerminal.contains("<cim:ACDCTerminal.connected>false</cim:ACDCTerminal.connected>"));
// If the fictitious switch gets closed, it still isn't exported but the terminal now gets connected.
fictitiousSwitch.setOpen(false);
eqFile = writeCgmesProfile(network, "EQ", tmpDir);
sshFile = writeCgmesProfile(network, "SSH", tmpDir);
assertFalse(eqFile.contains("<cim:Breaker rdf:ID="));
xmlLoadTerminal = getElement(sshFile, "Terminal", "T_LD");
assertTrue(xmlLoadTerminal.contains("<cim:ACDCTerminal.connected>true</cim:ACDCTerminal.connected>"));
}
@Test
void retainedSwitchTest() throws IOException {
// IIDM network:
// Two BusbarSections BBS_1 and BBS_2 connected by a COUPLER Switch.
// A feeder bay with two Disconnectors, 1 Breaker, 1 Load also can couple the two bars.
// CGMES export:
// A retained switch cannot have both its terminals associated to the same topological node.
Network network = readCgmesResources(DIR, "retained_switch.xml");
// Open one disconnector so that the 2 ends of the retained switch are on different buses/topological nodes.
network.getSwitch("DIS_1").setOpen(true);
VoltageLevel.BusBreakerView bbv = network.getVoltageLevel("VL").getBusBreakerView();
assertNotEquals(bbv.getBus1("COUPLER"), bbv.getBus2("COUPLER"));
// The retained switch can be exported as such.
String eqExport = writeCgmesProfile(network, "EQ", tmpDir);
String xmlCoupler = getElement(eqExport, "Breaker", "COUPLER");
assertTrue(xmlCoupler.contains("<cim:Switch.retained>true</cim:Switch.retained>"));
// Now close the disconnector so that the 2 ends of the retained switch are on the same bus/topological node.
network.getSwitch("DIS_1").setOpen(false);
assertEquals(bbv.getBus1("COUPLER"), bbv.getBus2("COUPLER"));
// The retained switch can't be exported as such.
eqExport = writeCgmesProfile(network, "EQ", tmpDir);
xmlCoupler = getElement(eqExport, "Breaker", "COUPLER");
assertTrue(xmlCoupler.contains("<cim:Switch.retained>false</cim:Switch.retained>"));
}
}