TopologyExportCornerCasesTest.java

package com.powsybl.cgmes.conversion.test.export;

import com.powsybl.cgmes.conversion.CgmesExport;
import com.powsybl.cgmes.conversion.CgmesImport;
import com.powsybl.commons.test.AbstractSerDeTest;
import com.powsybl.commons.datasource.ZipArchiveDataSource;
import com.powsybl.computation.local.LocalComputationManager;
import com.powsybl.iidm.network.*;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;

import java.util.Properties;

import static org.junit.jupiter.api.Assertions.*;

class TopologyExportCornerCasesTest extends AbstractSerDeTest {

    @Test
    void testExportSwitchesBusBreaker() {
        test(createSwitchesBBNetwork(), true, true,
                new String[] {"voltageLevel1_0", "voltageLevel1_1", "voltageLevel1_2", "voltageLevel1_3", "voltageLevel1_4"});
    }

    @Test
    void testExportParallelSwitchesNodeBreaker() {
        test(createParallelSwitchesNBNetwork(), true, true,
                new String[] {"voltageLevel1_0"});
    }

    @Test
    void testExportSwitchesNodeBreaker() {
        test(createSwitchesNBNetwork(), true, true,
                new String[] {"voltageLevel1_0", "voltageLevel1_2", "voltageLevel1_8"});
    }

    @Test
    void testExportGeneratorDisconnectedTransformerBusBreaker() {
        // The calculated BusView from bus-breaker iidm and from node-breaker iidm is different
        // The condition for a valid bus in the BusView for bus-breaker and node-breaker is slightly different
        // So we end up with different bus-view buses
        test(createGeneratorDisconnectedTransformerBBNetwork(), false, false,
                new String[] {"voltageLevel1_0", "voltageLevel2_0", "voltageLevel2_1"});
    }

    @Test
    void testExportGeneratorTransformerNodeBreaker() {
        test(createGeneratorTransformerNBNetwork(), true, true,
                new String[] {"voltageLevel1_0", "voltageLevel2_0"});
    }

    @Disabled("Mismatch in bus view bus definition from node/breaker and bus/breaker topologies")
    // FIXME(Luma): consider adding busbar section to exported EQ when we save a bus/breaker topology as node/breaker
    @Test
    void testExportDisconnectedLoadBusBreaker() {
        test(createDisconnectedLoadBBNetwork(), false, true,
                new String[] {"voltageLevel1_0", "voltageLevel1_1"});
    }

    @Test
    void testExportDisconnectedLoadNodeBreaker() {
        test(createDisconnectedLoadNBNetwork(), false, true,
                new String[] {"voltageLevel1_0", "voltageLevel1_1", "voltageLevel1_3"});
    }

    private void test(Network network,
                      boolean checkAllTerminalsConnected,
                      boolean checkSameNumberOfBusViewBuses,
                      String[] expectedBusBreakerViewBuses) {
        String name = network.getId();

        // Some terminals may show as disconnected even if everything is connected but the bus is not valid
        // We perform the check to verify that the networks where we want everything connected have valid buses
        // and do not introduce additional noise in the validation
        if (checkAllTerminalsConnected) {
            checkAllTerminalsConnected(network, name);
        }

        // Export as node-breaker
        Properties params = new Properties();
        params.put(CgmesExport.TOPOLOGY_KIND, "NODE_BREAKER");
        ZipArchiveDataSource zip = new ZipArchiveDataSource(tmpDir.resolve("."), name);
        new CgmesExport().export(network, params, zip);
        Properties importParams = new Properties();
        importParams.put(CgmesImport.IMPORT_CGM_WITH_SUBNETWORKS, "false");
        Network networkFromCgmes = Network.read(tmpDir.resolve(name + ".zip"), LocalComputationManager.getDefault(), ImportConfig.CACHE.get(), importParams);
        if (checkAllTerminalsConnected) {
            checkAllTerminalsConnected(network, name + "_from_CGMES");
        }

        // Original network and re-imported must have the same number of buses in the bus view
        // Additional buses in the bus-breaker view may have been introduced because some terminals disconnected
        if (checkSameNumberOfBusViewBuses) {
            assertEquals(
                    network.getBusView().getBusStream().count(),
                    networkFromCgmes.getBusView().getBusStream().count());
        }

        // And the list of buses should be the expected one
        assertArrayEquals(
                expectedBusBreakerViewBuses,
                networkFromCgmes.getBusBreakerView().getBusStream().map(Bus::getId).sorted().toArray());
    }

    private static void checkAllTerminalsConnected(Network network, String name) {
        for (Connectable<?> c : network.getConnectables()) {
            for (Terminal t : c.getTerminals()) {
                if (!t.isConnected()) {
                    fail("Terminal is disconnected in equipment " + c.getId() + " in network " + name);
                }
            }
        }
    }

    private static Network createGeneratorDisconnectedTransformerBBNetwork() {
        Network network = createBaseNetwork("disconnected_generator_transformer_bb", TopologyKind.BUS_BREAKER);
        VoltageLevel.BusBreakerView topology1 = network.getVoltageLevel("voltageLevel1").getBusBreakerView();
        topology1.newBus()
                .setId("voltageLevel1Bus1")
                .add();
        Substation substation1 = network.getSubstation("substation1");
        VoltageLevel voltageLevel2 = substation1.newVoltageLevel()
                .setId("voltageLevel2")
                .setNominalV(15)
                .setTopologyKind(TopologyKind.BUS_BREAKER)
                .add();
        VoltageLevel.BusBreakerView topology2 = network.getVoltageLevel("voltageLevel2").getBusBreakerView();
        topology2.newBus()
                .setId("voltageLevel2Bus1")
                .add();
        Generator generator1 = createGeneratorAdder(voltageLevel2, "generator1")
                .setConnectableBus("voltageLevel2Bus1")
                .add();
        createReactiveCapabilityCurve(generator1);
        // We already said the generator1 is not connected to its bus by only setting its ConnectableBus, but anyway:
        generator1.getTerminal().disconnect();
        createTwoWindingsTransformerAdder(substation1, "twoWindingsTransformer1", "voltageLevel1", "voltageLevel2")
                .setBus1("voltageLevel1Bus1")
                .setBus2("voltageLevel2Bus1")
                .add();
        return network;
    }

    private static Network createDisconnectedLoadBBNetwork() {
        Network network = createBaseNetwork("disconnected_load_bb", TopologyKind.BUS_BREAKER);

        VoltageLevel.BusBreakerView topology1 = network.getVoltageLevel("voltageLevel1").getBusBreakerView();
        Bus voltageLevel1Bus1 = topology1.newBus()
                .setId("voltageLevel1Bus1")
                .add();
        createLoadAdder(network.getVoltageLevel("voltageLevel1"), "load1")
                .setBus(voltageLevel1Bus1.getId())
                .add();
        Load load2 = createLoadAdder(network.getVoltageLevel("voltageLevel1"), "load2")
                .setConnectableBus(voltageLevel1Bus1.getId())
                .add();
        load2.getTerminal().disconnect();

        return network;
    }

    private static Network createSwitchesBBNetwork() {
        Network network = createBaseNetwork("switches_network_bb", TopologyKind.BUS_BREAKER);

        VoltageLevel.BusBreakerView topology1 = network.getVoltageLevel("voltageLevel1").getBusBreakerView();
        Bus voltageLevel1Bus1 = topology1.newBus()
                .setId("voltageLevel1Bus1")
                .add();
        Bus busSwitch1 = topology1.newBus()
                .setId("BusSwitch1")
                .add();
        topology1.newSwitch()
                .setId("voltageLevel1Switch1")
                .setOpen(false)
                .setBus1(voltageLevel1Bus1.getId())
                .setBus2(busSwitch1.getId())
                .add();
        Bus busSwitch2 = topology1.newBus()
                .setId("BusSwitch2")
                .add();
        topology1.newSwitch()
                .setId("voltageLevel1Switch2")
                .setOpen(false)
                .setBus1(busSwitch1.getId())
                .setBus2(busSwitch2.getId())
                .add();
        Bus busSwitch3 = topology1.newBus()
                .setId("BusSwitch3")
                .add();
        topology1.newSwitch()
                .setId("voltageLevel1Switch3")
                .setOpen(false)
                .setBus1(busSwitch2.getId())
                .setBus2(busSwitch3.getId())
                .add();
        Bus voltageLevel1Bus2 = topology1.newBus()
                .setId("voltageLevel1Bus2")
                .add();
        topology1.newSwitch()
                .setId("voltageLevel1Switch4")
                .setOpen(false)
                .setBus1(busSwitch3.getId())
                .setBus2(voltageLevel1Bus2.getId())
                .add();
        return network;
    }

    private static Network createGeneratorTransformerNBNetwork() {
        Network network = createBaseNetwork("generator_transformer_nb", TopologyKind.NODE_BREAKER);
        VoltageLevel.NodeBreakerView topology1 = network.getVoltageLevel("voltageLevel1").getNodeBreakerView();
        BusbarSection voltageLevel1Bus1 = topology1.newBusbarSection()
                .setId("voltageLevel1Bus1")
                .setNode(0)
                .add();
        Substation substation1 = network.getSubstation("substation1");
        VoltageLevel voltageLevel2 = substation1.newVoltageLevel()
                .setId("voltageLevel2")
                .setNominalV(15)
                .setTopologyKind(TopologyKind.NODE_BREAKER)
                .add();

        TwoWindingsTransformer twoWindingsTransformer1 = createTwoWindingsTransformerAdder(substation1, "twoWindingsTransformer1", "voltageLevel1", "voltageLevel2")
                .setNode1(1)
                .setNode2(1)
                .add();
        topology1.newInternalConnection()
                .setNode1(voltageLevel1Bus1.getTerminal().getNodeBreakerView().getNode())
                .setNode2(twoWindingsTransformer1.getTerminal1().getNodeBreakerView().getNode())
                .add();

        VoltageLevel.NodeBreakerView topology2 = network.getVoltageLevel("voltageLevel2").getNodeBreakerView();
        Generator generator1 = createGeneratorAdder(voltageLevel2, "generator1")
                .setNode(0)
                .add();
        createReactiveCapabilityCurve(generator1);
        topology2.newInternalConnection()
                .setNode1(generator1.getTerminal().getNodeBreakerView().getNode())
                .setNode2(twoWindingsTransformer1.getTerminal2().getNodeBreakerView().getNode())
                .add();

        return network;
    }

    private static Network createDisconnectedLoadNBNetwork() {
        Network network = createBaseNetwork("disconnected_load_nb", TopologyKind.NODE_BREAKER);

        VoltageLevel.NodeBreakerView topology1 = network.getVoltageLevel("voltageLevel1").getNodeBreakerView();
        BusbarSection voltageLevel1Bus1 = topology1.newBusbarSection()
                .setId("voltageLevel1Bus1")
                .setNode(0)
                .add();
        Load load1 = createLoadAdder(network.getVoltageLevel("voltageLevel1"), "load1")
                .setNode(1)
                .add();
        topology1.newInternalConnection()
                .setNode1(load1.getTerminal().getNodeBreakerView().getNode())
                .setNode2(voltageLevel1Bus1.getTerminal().getNodeBreakerView().getNode())
                .add();
        Load load2 = createLoadAdder(network.getVoltageLevel("voltageLevel1"), "load2")
                .setNode(2)
                .add();
        topology1.newSwitch()
                .setId("load2Switch")
                .setNode1(load2.getTerminal().getNodeBreakerView().getNode())
                .setNode2(voltageLevel1Bus1.getTerminal().getNodeBreakerView().getNode())
                .setOpen(true)
                .setKind(SwitchKind.BREAKER)
                .add();

        return network;
    }

    private static Network createParallelSwitchesNBNetwork() {
        Network network = createBaseNetwork("parallel_switches_nb", TopologyKind.NODE_BREAKER);

        // We create a load at node 3 to avoid bbs terminals being considered disconnected.
        // If there are only bbs the calculated bus in the busview is not valid,
        // and the terminals of bbs are considered disconnected.
        // By adding a "feeder" element to the graph the bus is valid and the bbs terminals connected.
        network.getVoltageLevel("voltageLevel1").newLoad()
                .setId("load1")
                .setNode(3)
                .setP0(0)
                .setQ0(0)
                .add();

        VoltageLevel.NodeBreakerView topology1 = network.getVoltageLevel("voltageLevel1").getNodeBreakerView();
        BusbarSection voltageLevel1Bus1 = topology1.newBusbarSection()
                .setId("voltageLevel1Bus1")
                .setNode(0)
                .add();
        BusbarSection voltageLevel1Bus2 = topology1.newBusbarSection()
                .setId("voltageLevel1Bus2")
                .setNode(1)
                .add();
        topology1.newDisconnector()
                .setId("voltageLevel1Switch11")
                .setRetained(false)
                .setOpen(false)
                .setNode1(voltageLevel1Bus1.getTerminal().getNodeBreakerView().getNode())
                .setNode2(2)
                .add();
        topology1.newBreaker()
                .setId("voltageLevel1Switch21")
                .setRetained(true)
                .setOpen(false)
                .setNode1(2)
                .setNode2(3)
                .add();
        topology1.newDisconnector()
                .setId("voltageLevel1Switch31")
                .setRetained(false)
                .setOpen(false)
                .setNode1(3)
                .setNode2(voltageLevel1Bus2.getTerminal().getNodeBreakerView().getNode())
                .add();
        topology1.newDisconnector()
                .setId("voltageLevel1Switch12")
                .setRetained(false)
                .setOpen(false)
                .setNode1(voltageLevel1Bus1.getTerminal().getNodeBreakerView().getNode())
                .setNode2(4)
                .add();
        topology1.newBreaker()
                .setId("voltageLevel1Switch22")
                .setRetained(false)
                .setOpen(false)
                .setNode1(4)
                .setNode2(5)
                .add();
        topology1.newDisconnector()
                .setId("voltageLevel1Switch32")
                .setRetained(false)
                .setOpen(false)
                .setNode1(5)
                .setNode2(voltageLevel1Bus2.getTerminal().getNodeBreakerView().getNode())
                .add();

        return network;
    }

    private static Network createSwitchesNBNetwork() {
        Network network = createBaseNetwork("switches_nb", TopologyKind.NODE_BREAKER);

        VoltageLevel.NodeBreakerView topology1 = network.getVoltageLevel("voltageLevel1").getNodeBreakerView();

        // We create a load at node 3 to avoid bbs terminals being considered disconnected.
        // If there are only bbs the calculated bus in the busview is not valid,
        // and the terminals of bbs are considered disconnected.
        // By adding a "feeder" element to the graph the bus is valid and the bbs terminals connected.
        network.getVoltageLevel("voltageLevel1").newLoad()
                .setId("load1")
                .setNode(3)
                .setP0(0)
                .setQ0(0)
                .add();

        BusbarSection voltageLevel1Bus1 = topology1.newBusbarSection()
                .setId("voltageLevel1Bus1")
                .setNode(0)
                .add();
        BusbarSection voltageLevel1Bus2 = topology1.newBusbarSection()
                .setId("voltageLevel1Bus2")
                .setNode(1)
                .add();
        topology1.newDisconnector()
                .setId("voltageLevel1Switch1")
                .setRetained(false)
                .setOpen(false)
                .setNode1(voltageLevel1Bus1.getTerminal().getNodeBreakerView().getNode())
                .setNode2(2)
                .add();
        topology1.newBreaker()
                .setId("voltageLevel1Switch2")
                .setRetained(true)
                .setOpen(false)
                .setNode1(2)
                .setNode2(3)
                .add();
        topology1.newDisconnector()
                .setId("voltageLevel1Switch3")
                .setRetained(false)
                .setOpen(false)
                .setNode1(3)
                .setNode2(4)
                .add();
        topology1.newDisconnector()
                .setId("voltageLevel1Switch4")
                .setRetained(false)
                .setOpen(false)
                .setNode1(4)
                .setNode2(5)
                .add();
        topology1.newBreaker()
                .setId("voltageLevel1Switch5")
                .setRetained(false)
                .setOpen(false)
                .setNode1(5)
                .setNode2(6)
                .add();
        topology1.newDisconnector()
                .setId("voltageLevel1Switch6")
                .setRetained(false)
                .setOpen(false)
                .setNode1(6)
                .setNode2(7)
                .add();
        topology1.newDisconnector()
                .setId("voltageLevel1Switch7")
                .setRetained(false)
                .setOpen(false)
                .setNode1(7)
                .setNode2(8)
                .add();
        topology1.newBreaker()
                .setId("voltageLevel1Switch8")
                .setRetained(true)
                .setOpen(false)
                .setNode1(8)
                .setNode2(9)
                .add();
        topology1.newDisconnector()
                .setId("voltageLevel1Switch9")
                .setRetained(false)
                .setOpen(false)
                .setNode1(9)
                .setNode2(voltageLevel1Bus2.getTerminal().getNodeBreakerView().getNode())
                .add();

        return network;
    }

    private static Network createBaseNetwork(String id, TopologyKind topologyKind) {
        Network network = NetworkFactory.findDefault().createNetwork(id, "iidm");

        Substation substation1 = network.newSubstation()
                .setId("substation1")
                .setCountry(Country.FR)
                .setTso("TSO1")
                .setGeographicalTags("region1")
                .add();
        substation1.newVoltageLevel()
                .setId("voltageLevel1")
                .setNominalV(400)
                .setTopologyKind(topologyKind)
                .add();

        return network;
    }

    private static LoadAdder createLoadAdder(VoltageLevel voltageLevel, String name) {
        return voltageLevel.newLoad()
                .setId(name)
                .setP0(10)
                .setQ0(5);
    }

    private static GeneratorAdder createGeneratorAdder(VoltageLevel voltageLevel, String name) {
        return voltageLevel.newGenerator()
                .setId(name)
                .setEnergySource(EnergySource.NUCLEAR)
                .setMinP(200.0)
                .setMaxP(900.0)
                .setVoltageRegulatorOn(true)
                .setTargetP(900.0)
                .setTargetV(380.0);
    }

    private static TwoWindingsTransformerAdder createTwoWindingsTransformerAdder(Substation substation, String name, String voltageLevel1, String voltageLevel2) {
        return substation.newTwoWindingsTransformer()
                .setId(name)
                .setR(0.10368000715971)
                .setX(8.2943999999999996)
                .setB(-2.0576486349455101e-05)
                .setG(6.8962194745836297e-06)
                .setRatedS(300)
                .setRatedU1(405)
                .setRatedU2(17)
                .setVoltageLevel1(voltageLevel1)
                .setVoltageLevel2(voltageLevel2);
    }

    private static void createReactiveCapabilityCurve(Generator generator) {
        generator.newReactiveCapabilityCurve()
                .beginPoint().setP(200.0).setMinQ(300.0).setMaxQ(500.0).endPoint()
                .beginPoint().setP(900.0).setMinQ(300.0).setMaxQ(500.0).endPoint()
                .add();
    }
}