ContingencyTrippingTest.java

/**
 * Copyright (c) 2020, 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.openloadflow.util.sa;

import com.powsybl.commons.PowsyblException;
import com.powsybl.iidm.network.*;
import com.powsybl.iidm.network.test.EurostagTutorialExample1Factory;
import com.powsybl.iidm.network.test.FictitiousSwitchFactory;
import com.powsybl.openloadflow.network.EurostagFactory;
import com.powsybl.openloadflow.network.NodeBreakerNetworkFactory;
import com.powsybl.openloadflow.network.impl.ContingencyTripping;
import org.junit.jupiter.api.Test;

import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;

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

/**
 * @author Florian Dupuy {@literal <florian.dupuy at rte-france.com>}
 */
class ContingencyTrippingTest {

    @Test
    void testLineTripping() {
        Network network = NodeBreakerNetworkFactory.create();

        Set<Switch> switchesToOpen = new HashSet<>();
        Set<Terminal> terminalsToDisconnect = new HashSet<>();
        ContingencyTripping.createBranchTripping(network, network.getBranch("L1")).traverse(switchesToOpen, terminalsToDisconnect);
        checkSwitches(switchesToOpen, "C");
        checkTerminalIds(terminalsToDisconnect, "BBS1", "L1");

        switchesToOpen.clear();
        terminalsToDisconnect.clear();
        ContingencyTripping.createBranchTripping(network, network.getBranch("L2")).traverse(switchesToOpen, terminalsToDisconnect);
        checkSwitches(switchesToOpen);
        checkTerminalIds(terminalsToDisconnect, "L2");
    }

    @Test
    void testBusbarSectionTripping() {
        Network network = NodeBreakerNetworkFactory.create();

        Set<Switch> switchesToOpen = new HashSet<>();
        Set<Terminal> terminalsToDisconnect = new HashSet<>();

        ContingencyTripping.createBusbarSectionMinimalTripping(network, network.getBusbarSection("BBS3"))
                .traverse(switchesToOpen, terminalsToDisconnect);
        checkSwitches(switchesToOpen, "B3", "B4");
        checkTerminalIds(terminalsToDisconnect, "LD", "BBS3");
    }

    @Test
    void testUnconnectedVoltageLevel() {
        Network network = NodeBreakerNetworkFactory.create();
        Exception unknownVl = assertThrows(PowsyblException.class,
            () -> ContingencyTripping.createBranchTripping(network, network.getBranch("L1"), "VL3"));
        assertEquals("VoltageLevel 'VL3' not connected to branch 'L1'", unknownVl.getMessage());
    }

    @Test
    void testVoltageLevelFilter() {
        Network network = NodeBreakerNetworkFactory.create();

        ContingencyTripping trippingTaskVl1 = ContingencyTripping.createBranchTripping(network, network.getBranch("L1"), "VL1");
        Set<Switch> switchesToOpen = new HashSet<>();
        Set<Terminal> terminalsToDisconnect = new HashSet<>();
        trippingTaskVl1.traverse(switchesToOpen, terminalsToDisconnect);
        checkSwitches(switchesToOpen, "C");
        checkTerminalIds(terminalsToDisconnect, "BBS1", "L1");

        ContingencyTripping trippingTaskVl2 = ContingencyTripping.createBranchTripping(network, network.getBranch("L1"), "VL2");
        switchesToOpen.clear();
        terminalsToDisconnect.clear();
        trippingTaskVl2.traverse(switchesToOpen, terminalsToDisconnect);
        checkSwitches(switchesToOpen);
        checkTerminalIds(terminalsToDisconnect, "L1");
    }

    @Test
    void testDisconnectorBeforeMultipleSwitches() {
        Network network = FictitiousSwitchFactory.create();

        Set<Switch> switchesToOpen = new HashSet<>();
        Set<Terminal> terminalsToDisconnect = new HashSet<>();
        ContingencyTripping.createBranchTripping(network, network.getBranch("CJ")).traverse(switchesToOpen, terminalsToDisconnect);
        checkSwitches(switchesToOpen);
        checkTerminalIds(terminalsToDisconnect, "D", "CI", "CJ");
    }

    @Test
    void testSwitchBeforeOpenedDisconnector() {
        Network network = FictitiousSwitchFactory.create();

        // Close switches to traverse the voltage level further
        network.getSwitch("BB").setOpen(false);
        network.getSwitch("AZ").setOpen(false);

        // First without opening disconnector
        ContingencyTripping lbt1 = ContingencyTripping.createBranchTripping(network, network.getBranch("CJ"));
        Set<Switch> switchesToOpen = new HashSet<>();
        Set<Terminal> terminalsToDisconnect = new HashSet<>();
        lbt1.traverse(switchesToOpen, terminalsToDisconnect);
        checkSwitches(switchesToOpen, "BL", "BJ");
        checkTerminalIds(terminalsToDisconnect, "D", "CE", "CF", "CG", "CH", "CI", "P", "CJ");

        // Then with the opened disconnector
        network.getSwitch("AH").setOpen(true);
        switchesToOpen.clear();
        terminalsToDisconnect.clear();
        lbt1.traverse(switchesToOpen, terminalsToDisconnect);
        checkSwitches(switchesToOpen, "BL");
        checkTerminalIds(terminalsToDisconnect, "D", "CE", "CF", "CG", "CH", "CI", "P", "CJ");
    }

    @Test
    void testOpenedSwitches() {
        // Testing disconnector and breaker opened just after contingency
        Network network = FictitiousSwitchFactory.create();
        network.getSwitch("L").setOpen(true); // breaker at C side of line CJ
        network.getSwitch("BF").setOpen(true);  // disconnector at N side of line CJ

        Set<Switch> switchesToOpen = new HashSet<>();
        Set<Terminal> terminalsToDisconnect = new HashSet<>();
        ContingencyTripping.createBranchTripping(network, network.getBranch("CJ")).traverse(switchesToOpen, terminalsToDisconnect);
        checkSwitches(switchesToOpen);
        checkTerminalIds(terminalsToDisconnect, "CJ");
    }

    @Test
    void testEndNodeAfterSwitch() {
        Network network = FictitiousSwitchFactory.create();

        // Close switches to traverse the voltage level until encountering generator/loads
        network.getSwitch("BB").setOpen(false); // BB fictitious breaker
        network.getSwitch("AZ").setOpen(false); // AZ disconnector to BBS1
        network.getSwitch("AV").setOpen(false); // AV disconnector to generator CD

        // Adding breakers between two loads to simulate the case of a branching of two switches at an end node
        network.getVoltageLevel("N").getNodeBreakerView().newSwitch()
            .setId("ZW")
            .setName("ZX")
            .setKind(SwitchKind.BREAKER)
            .setRetained(false)
            .setOpen(true)
            .setFictitious(true)
            .setNode1(22)
            .setNode2(16)
            .add();
        network.getVoltageLevel("N").getNodeBreakerView().newSwitch()
            .setId("ZY")
            .setName("ZZ")
            .setKind(SwitchKind.BREAKER)
            .setRetained(false)
            .setOpen(false)
            .setFictitious(true)
            .setNode1(20)
            .setNode2(18)
            .add();

        ContingencyTripping lbt1 = ContingencyTripping.createBranchTripping(network, network.getBranch("CJ"));
        Set<Switch> switchesToOpen = new HashSet<>();
        Set<Terminal> terminalsToDisconnect = new HashSet<>();
        lbt1.traverse(switchesToOpen, terminalsToDisconnect);
        checkSwitches(switchesToOpen, "BJ", "BL", "BV", "BX");
        checkTerminalIds(terminalsToDisconnect, "D", "CD", "CE", "CH", "CI", "P", "O", "CJ");

        // Adding an internal connection and open the ZW switch
        network.getSwitch("ZY").setOpen(true);
        network.getVoltageLevel("N").getNodeBreakerView().newInternalConnection()
            .setNode1(20)
            .setNode2(18)
            .add();
        switchesToOpen.clear();
        terminalsToDisconnect.clear();
        lbt1.traverse(switchesToOpen, terminalsToDisconnect);
        checkSwitches(switchesToOpen, "BJ", "BL", "BV", "BX");
        checkTerminalIds(terminalsToDisconnect, "D", "CD", "CE", "CH", "CI", "P", "O", "CJ");
    }

    @Test
    void testInternalConnection() {
        Network network = FictitiousSwitchFactory.create();

        // Adding internal connections
        network.getVoltageLevel("N").getNodeBreakerView().newInternalConnection()
            .setNode1(3)
            .setNode2(9)
            .add();
        network.getVoltageLevel("C").getNodeBreakerView().newInternalConnection()
            .setNode1(2)
            .setNode2(3)
            .add();

        Set<Switch> switchesToOpen = new HashSet<>();
        Set<Terminal> terminalsToDisconnect = new HashSet<>();
        ContingencyTripping.createBranchTripping(network, network.getBranch("CJ")).traverse(switchesToOpen, terminalsToDisconnect);
        checkSwitches(switchesToOpen, "BL");
        checkTerminalIds(terminalsToDisconnect, "D", "CE", "CI", "CJ");
    }

    @Test
    void testInternalConnectionEndingAtSwitches() {
        Network network = FictitiousSwitchFactory.create();

        network.getSwitch("L").setFictitious(false);
        network.getSwitch("BB").setFictitious(false);

        VoltageLevel.NodeBreakerView c = network.getVoltageLevel("C").getNodeBreakerView();
        c.newInternalConnection().setNode1(4).setNode2(5).add();
        c.newInternalConnection().setNode1(5).setNode2(6).add();
        c.newBreaker().setId("ZZ").setNode1(6).setNode2(0).add();

        Set<Switch> switchesToOpen = new HashSet<>();
        Set<Terminal> terminalsToDisconnect = new HashSet<>();
        ContingencyTripping.createBranchTripping(network, network.getBranch("CJ")).traverse(switchesToOpen, terminalsToDisconnect);
        assertTrue(switchesToOpen.isEmpty());
        checkTerminalIds(terminalsToDisconnect, "CJ");

        c.newInternalConnection().setNode1(5).setNode2(7).add();
        Switch b = c.newBreaker().setId("ZY").setNode1(7).setNode2(0).add();

        terminalsToDisconnect.clear();
        ContingencyTripping.createBranchTripping(network, network.getBranch("CJ")).traverse(switchesToOpen, terminalsToDisconnect);
        assertTrue(switchesToOpen.isEmpty());
        checkTerminalIds(terminalsToDisconnect, "CJ");

        b.setFictitious(true);
        terminalsToDisconnect.clear();
        ContingencyTripping.createBranchTripping(network, network.getBranch("CJ")).traverse(switchesToOpen, terminalsToDisconnect);
        checkSwitches(switchesToOpen, "L", "ZZ");
        checkTerminalIds(terminalsToDisconnect, "D", "CI", "CJ");
    }

    @Test
    void testStopAtStartEdges() {
        Network network = FictitiousSwitchFactory.create();

        // Adding edges after CJ line
        network.getVoltageLevel("N").getNodeBreakerView().newBreaker()
            .setId("ZY")
            .setName("ZZ")
            .setRetained(false)
            .setOpen(false)
            .setFictitious(false)
            .setNode1(5)
            .setNode2(9)
            .add();
        network.getVoltageLevel("C").getNodeBreakerView().newInternalConnection()
            .setNode1(1)
            .setNode2(4)
            .add();

        ContingencyTripping lbt1 = ContingencyTripping.createBranchTripping(network, network.getBranch("CJ"));
        Set<Switch> switchesToOpen = new HashSet<>();
        Set<Terminal> terminalsToDisconnect = new HashSet<>();
        lbt1.traverse(switchesToOpen, terminalsToDisconnect);
        checkSwitches(switchesToOpen, "ZY");
        checkTerminalIds(terminalsToDisconnect, "D", "CI", "CJ");

        network.getSwitch("ZY").setOpen(true);
        switchesToOpen.clear();
        terminalsToDisconnect.clear();
        lbt1.traverse(switchesToOpen, terminalsToDisconnect);
        checkSwitches(switchesToOpen);
        checkTerminalIds(terminalsToDisconnect, "D", "CI", "CJ");
    }

    @Test
    void testEurostagNetwork() {
        Network network = EurostagFactory.fix(EurostagTutorialExample1Factory.create());

        Set<Switch> switchesToOpen = new HashSet<>();
        Set<Terminal> terminalsToDisconnect = new HashSet<>();

        ContingencyTripping trippingTask = ContingencyTripping.createBranchTripping(network, network.getBranch("NHV1_NHV2_1"));
        trippingTask.traverse(switchesToOpen, terminalsToDisconnect);

        assertTrue(switchesToOpen.isEmpty());
        assertEquals(2, terminalsToDisconnect.size());
        checkTerminalStrings(terminalsToDisconnect, "BusTerminal[NHV1]", "BusTerminal[NHV2]");

        Line line = network.getLine("NHV1_NHV2_1");
        line.getTerminal1().disconnect();

        terminalsToDisconnect.clear();
        trippingTask.traverse(switchesToOpen, terminalsToDisconnect);

        assertTrue(switchesToOpen.isEmpty());
        assertEquals(1, terminalsToDisconnect.size());
        checkTerminalStrings(terminalsToDisconnect, "BusTerminal[NHV2]");
    }

    private static void checkSwitches(Set<Switch> switches, String... sId) {
        assertEquals(new HashSet<>(Arrays.asList(sId)), switches.stream().map(Switch::getId).collect(Collectors.toSet()));
    }

    private static void checkTerminalIds(Set<Terminal> terminals, String... tId) {
        assertEquals(new HashSet<>(Arrays.asList(tId)), terminals.stream().map(t -> t.getConnectable().getId()).collect(Collectors.toSet()));
    }

    private static void checkTerminalStrings(Set<Terminal> terminals, String... strings) {
        assertEquals(new HashSet<>(Arrays.asList(strings)), terminals.stream().map(Object::toString).collect(Collectors.toSet()));
    }

}