ConnectionAndDisconnectionsTest.java

/*
 * Copyright (c) 2023, 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.modification;

import com.powsybl.commons.PowsyblException;
import com.powsybl.commons.report.PowsyblCoreReportResourceBundle;
import com.powsybl.commons.test.PowsyblCoreTestReportResourceBundle;
import com.powsybl.commons.report.ReportNode;
import com.powsybl.computation.ComputationManager;
import com.powsybl.computation.local.LocalComputationManager;
import com.powsybl.iidm.modification.topology.AbstractModificationTest;
import com.powsybl.iidm.modification.topology.CreateFeederBayBuilder;
import com.powsybl.iidm.modification.topology.DefaultNamingStrategy;
import com.powsybl.iidm.modification.topology.NamingStrategy;
import com.powsybl.iidm.network.*;
import com.powsybl.iidm.network.extensions.BusbarSectionPositionAdder;
import com.powsybl.iidm.network.test.HvdcTestNetwork;
import org.junit.jupiter.api.Test;

import java.io.IOException;
import java.time.ZonedDateTime;

import static com.powsybl.iidm.network.extensions.ConnectablePosition.Direction.BOTTOM;
import static org.junit.jupiter.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;

/**
 * @author Nicolas Rol {@literal <nicolas.rol at rte-france.com>}
 */
class ConnectionAndDisconnectionsTest extends AbstractModificationTest {

    public Network createNetwork() {
        // Initialisation
        Network network = Network.create("test", "test");
        network.setCaseDate(ZonedDateTime.parse("2023-12-13T10:05:55.570Z"));

        // Substations
        Substation s1 = network.newSubstation()
            .setId("S1")
            .setCountry(Country.FR)
            .add();
        VoltageLevel vl1 = s1.newVoltageLevel()
            .setId("VL1")
            .setNominalV(1.0)
            .setTopologyKind(TopologyKind.NODE_BREAKER)
            .add();
        VoltageLevel vl2 = s1.newVoltageLevel()
            .setId("VL2")
            .setNominalV(1.0)
            .setTopologyKind(TopologyKind.BUS_BREAKER)
            .add();
        VoltageLevel vl3 = s1.newVoltageLevel()
            .setId("VL3")
            .setNominalV(1.0)
            .setTopologyKind(TopologyKind.BUS_BREAKER)
            .add();

        // Busbar sections
        BusbarSection bbs11 = vl1.getNodeBreakerView()
            .newBusbarSection()
            .setId("BBS11")
            .setNode(0)
            .add();
        bbs11.newExtension(BusbarSectionPositionAdder.class)
            .withBusbarIndex(1)
            .withSectionIndex(1)
            .add();
        BusbarSection bbs21 = vl1.getNodeBreakerView()
            .newBusbarSection()
            .setId("BBS21")
            .setNode(1)
            .add();
        bbs21.newExtension(BusbarSectionPositionAdder.class)
            .withBusbarIndex(2)
            .withSectionIndex(1)
            .add();
        BusbarSection bbs12 = vl1.getNodeBreakerView()
            .newBusbarSection()
            .setId("BBS12")
            .setNode(2)
            .add();
        bbs12.newExtension(BusbarSectionPositionAdder.class)
            .withBusbarIndex(1)
            .withSectionIndex(2)
            .add();
        BusbarSection bbs22 = vl1.getNodeBreakerView()
            .newBusbarSection()
            .setId("BBS22")
            .setNode(3)
            .add();
        bbs22.newExtension(BusbarSectionPositionAdder.class)
            .withBusbarIndex(2)
            .withSectionIndex(2)
            .add();
        vl2.getBusBreakerView()
            .newBus()
            .setId("bus2A")
            .add();
        vl2.getBusBreakerView()
            .newBus()
            .setId("bus2B")
            .add();
        vl3.getBusBreakerView()
            .newBus()
            .setId("bus3A")
            .add();
        vl3.getBusBreakerView()
            .newBus()
            .setId("bus3B")
            .add();

        // Disconnectors for coupling
        vl1.getNodeBreakerView().newDisconnector()
            .setId("D_BBS11_BBS12")
            .setNode1(0)
            .setNode2(2)
            .setOpen(true)
            .add();
        vl1.getNodeBreakerView().newDisconnector()
            .setId("D_BBS21_BBS22")
            .setNode1(1)
            .setNode2(3)
            .setOpen(false)
            .add();

        // Line and transformer
        network.newLine()
            .setId("L1")
            .setName("LINE1")
            .setR(1.0)
            .setX(2.0)
            .setG1(3.0)
            .setG2(3.5)
            .setB1(4.0)
            .setB2(4.5)
            .setVoltageLevel1("VL1")
            .setVoltageLevel2("VL2")
            .setNode1(4)
            .setBus2("bus2A")
            .setConnectableBus2("bus2A")
            .add();
        network.newLine()
            .setId("L2")
            .setName("LINE2")
            .setR(1.0)
            .setX(2.0)
            .setG1(3.0)
            .setG2(3.5)
            .setB1(4.0)
            .setB2(4.5)
            .setVoltageLevel1("VL1")
            .setVoltageLevel2("VL3")
            .setNode1(5)
            .setBus2("bus3A")
            .setConnectableBus2("bus3A")
            .add();
        s1.newThreeWindingsTransformer()
            .setId("twt")
            .setName("TWT_NAME")
            .newLeg1()
            .setR(1.3)
            .setX(1.4)
            .setG(1.6)
            .setB(1.7)
            .setRatedU(1.1)
            .setRatedS(1.2)
            .setVoltageLevel("VL1")
            .setNode(6)
            .add()
            .newLeg2()
            .setR(2.03)
            .setX(2.04)
            .setG(0.0)
            .setB(0.0)
            .setRatedU(2.05)
            .setRatedS(2.06)
            .setVoltageLevel("VL2")
            .setBus("bus2B")
            .setConnectableBus("bus2B")
            .add()
            .newLeg3()
            .setR(3.3)
            .setX(3.4)
            .setG(0.0)
            .setB(0.0)
            .setRatedU(3.5)
            .setRatedS(3.6)
            .setVoltageLevel("VL3")
            .setBus("bus3B")
            .setConnectableBus("bus3B")
            .add()
            .add();

        // Breakers
        vl1.getNodeBreakerView().newBreaker()
            .setId("B_L1_1")
            .setNode1(4)
            .setNode2(7)
            .setOpen(false)
            .setFictitious(true)
            .add();
        vl1.getNodeBreakerView().newBreaker()
            .setId("B_L1_2")
            .setNode1(4)
            .setNode2(7)
            .setOpen(false)
            .add();
        vl1.getNodeBreakerView().newBreaker()
            .setId("B_L2")
            .setNode1(5)
            .setNode2(8)
            .setFictitious(true)
            .setOpen(true)
            .add();
        vl1.getNodeBreakerView().newBreaker()
            .setId("B_TWT")
            .setNode1(6)
            .setNode2(9)
            .setOpen(true)
            .setFictitious(true)
            .add();
        vl1.getNodeBreakerView().newBreaker()
            .setId("B0")
            .setNode1(7)
            .setNode2(17)
            .setOpen(false)
            .setFictitious(true)
            .add();
        vl1.getNodeBreakerView().newBreaker()
            .setId("B1")
            .setNode1(8)
            .setNode2(11)
            .setOpen(true)
            .add();
        vl1.getNodeBreakerView().newBreaker()
            .setId("B2")
            .setNode1(9)
            .setNode2(12)
            .setOpen(false)
            .setFictitious(true)
            .add();
        vl1.getNodeBreakerView().newBreaker()
            .setId("B3")
            .setNode1(7)
            .setNode2(8)
            .setOpen(false)
            .add();
        vl1.getNodeBreakerView().newBreaker()
            .setId("B4")
            .setNode1(8)
            .setNode2(9)
            .setOpen(false)
            .add();
        vl1.getNodeBreakerView().newBreaker()
            .setId("B5")
            .setNode1(17)
            .setNode2(10)
            .setOpen(false)
            .setFictitious(true)
            .add();

        // Disconnectors
        vl1.getNodeBreakerView().newDisconnector()
            .setId("D0")
            .setNode1(0)
            .setNode2(10)
            .setOpen(true)
            .add();
        vl1.getNodeBreakerView().newDisconnector()
            .setId("D1")
            .setNode1(1)
            .setNode2(10)
            .setOpen(false)
            .add();
        vl1.getNodeBreakerView().newDisconnector()
            .setId("D2")
            .setNode1(0)
            .setNode2(11)
            .setOpen(false)
            .add();
        vl1.getNodeBreakerView().newDisconnector()
            .setId("D3")
            .setNode1(1)
            .setNode2(11)
            .setOpen(true)
            .add();
        vl1.getNodeBreakerView().newDisconnector()
            .setId("D4")
            .setNode1(2)
            .setNode2(12)
            .setOpen(false)
            .add();
        vl1.getNodeBreakerView().newDisconnector()
            .setId("D5")
            .setNode1(3)
            .setNode2(12)
            .setOpen(true)
            .add();
        return network;
    }

    @Test
    void testPlannedDisconnection() throws IOException {
        // Network creation
        Network network = createNetwork();

        // Network modification
        PlannedDisconnection modification = new PlannedDisconnectionBuilder()
            .withIdentifiableId("L1")
            .build();
        modification.apply(network);
        writeXmlTest(network, "/network-planned-disconnection.xiidm");
    }

    @Test
    void testPlannedDisconnectionComplete() throws IOException {
        // Network creation
        Network network = createNetwork();

        // Network modification
        ReportNode reportNode = ReportNode.newRootReportNode()
                .withResourceBundles(PowsyblCoreTestReportResourceBundle.TEST_BASE_NAME, PowsyblCoreReportResourceBundle.BASE_NAME)
                .withMessageTemplate("reportPlannedDisconnectionComplete")
                .build();
        PlannedDisconnection modification = new PlannedDisconnectionBuilder()
            .withIdentifiableId("L1")
            .withFictitiousSwitchesOperable(true)
            .build();
        modification.apply(network, reportNode);
        writeXmlTest(network, "/network-disconnection-with-fictitious.xiidm");
        testReportNode(reportNode, "/reportNode/connectable-disconnected-planned.txt");
    }

    @Test
    void testPlannedDisconnectionNoDisconnection() throws IOException {
        // Network creation
        Network network = createNetwork();

        // Set a disconnector to fictitious
        network.getSwitch("D1").setFictitious(true);

        // Network modification
        ReportNode reportNode = ReportNode.newRootReportNode()
                .withResourceBundles(PowsyblCoreTestReportResourceBundle.TEST_BASE_NAME, PowsyblCoreReportResourceBundle.BASE_NAME)
                .withMessageTemplate("reportTestConnectionNoDisconnection")
                .build();
        PlannedDisconnection modification = new PlannedDisconnectionBuilder()
            .withIdentifiableId("L1")
            .withFictitiousSwitchesOperable(false)
            .build();
        modification.apply(network, reportNode);
        writeXmlTest(network, "/network-planned-disconnection-not-disconnected.xiidm");

        // Network modification
        PlannedDisconnection modificationSide1 = new PlannedDisconnectionBuilder()
            .withIdentifiableId("L1")
            .withFictitiousSwitchesOperable(false)
            .withSide(ThreeSides.ONE)
            .build();
        modificationSide1.apply(network, reportNode);
        writeXmlTest(network, "/network-planned-disconnection-not-disconnected.xiidm");
        testReportNode(reportNode, "/reportNode/connectable-not-disconnected-planned.txt");
    }

    @Test
    void testUnplannedDisconnection() throws IOException {
        // Network creation
        Network network = createNetwork();

        // Network modification
        ReportNode reportNode = ReportNode.newRootReportNode()
                .withResourceBundles(PowsyblCoreTestReportResourceBundle.TEST_BASE_NAME, PowsyblCoreReportResourceBundle.BASE_NAME)
                .withMessageTemplate("reportTestConnectionDisconnection")
                .build();
        UnplannedDisconnection modification = new UnplannedDisconnectionBuilder()
            .withIdentifiableId("L1")
            .withFictitiousSwitchesOperable(true)
            .build();
        modification.apply(network, reportNode);
        writeXmlTest(network, "/network-disconnection-with-fictitious.xiidm");
        testReportNode(reportNode, "/reportNode/connectable-disconnected-unplanned.txt");
    }

    @Test
    void testUnplannedDisconnectionNoDisconnection() throws IOException {
        // Network creation
        Network network = createNetwork();

        // Network modification
        ReportNode reportNode = ReportNode.newRootReportNode()
                .withResourceBundles(PowsyblCoreTestReportResourceBundle.TEST_BASE_NAME, PowsyblCoreReportResourceBundle.BASE_NAME)
                .withMessageTemplate("reportTestConnectionNoDisconnection")
                .build();
        UnplannedDisconnection modification = new UnplannedDisconnectionBuilder()
            .withIdentifiableId("L1")
            .withFictitiousSwitchesOperable(false)
            .build();
        modification.apply(network, reportNode);
        writeXmlTest(network, "/network-unplanned-disconnection-not-disconnected.xiidm");

        // Network modification
        UnplannedDisconnection modificationSide1 = new UnplannedDisconnectionBuilder()
            .withIdentifiableId("L1")
            .withFictitiousSwitchesOperable(false)
            .withSide(ThreeSides.ONE)
            .build();
        modificationSide1.apply(network, reportNode);
        writeXmlTest(network, "/network-unplanned-disconnection-not-disconnected.xiidm");
        testReportNode(reportNode, "/reportNode/connectable-not-disconnected-unplanned.txt");
    }

    @Test
    void testConnection() throws IOException {
        // Network creation
        Network network = createNetwork();

        // Network modification
        ReportNode reportNode = ReportNode.newRootReportNode()
                .withResourceBundles(PowsyblCoreTestReportResourceBundle.TEST_BASE_NAME, PowsyblCoreReportResourceBundle.BASE_NAME)
                .withMessageTemplate("reportTestConnection")
                .build();
        ConnectableConnection modification = new ConnectableConnectionBuilder()
            .withIdentifiableId("L2")
            .withFictitiousSwitchesOperable(true)
            .withOnlyBreakersOperable(false)
            .build();
        modification.apply(network, reportNode);
        writeXmlTest(network, "/network-connectable-connection.xiidm");
        testReportNode(reportNode, "/reportNode/connectable-connected.txt");
    }

    @Test
    void testConnectionNoConnection() throws IOException {
        // Network creation
        Network network = createNetwork();

        // Network modification
        ReportNode reportNode = ReportNode.newRootReportNode()
                .withResourceBundles(PowsyblCoreTestReportResourceBundle.TEST_BASE_NAME, PowsyblCoreReportResourceBundle.BASE_NAME)
                .withMessageTemplate("reportTestConnectionNoConnection")
                .build();
        ConnectableConnection modification = new ConnectableConnectionBuilder()
            .withIdentifiableId("L2")
            .withFictitiousSwitchesOperable(false)
            .withOnlyBreakersOperable(true)
            .build();
        modification.apply(network, reportNode);
        writeXmlTest(network, "/network-unplanned-disconnection-not-disconnected.xiidm");

        // Connection on one side
        ConnectableConnection modificationSide1 = new ConnectableConnectionBuilder()
            .withIdentifiableId("L2")
            .withFictitiousSwitchesOperable(false)
            .withOnlyBreakersOperable(true)
            .withSide(ThreeSides.ONE)
            .build();
        modificationSide1.apply(network, reportNode);
        writeXmlTest(network, "/network-unplanned-disconnection-not-disconnected.xiidm");
        testReportNode(reportNode, "/reportNode/connectable-not-connected.txt");
    }

    @Test
    void testTieLine() {
        Network network = createNetwork();

        // Add tie line
        DanglingLine nhv1xnode1 = network.getVoltageLevel("VL2").newDanglingLine()
            .setId("NHV1_XNODE1")
            .setP0(0.0)
            .setQ0(0.0)
            .setR(1.5)
            .setX(20.0)
            .setG(1E-6)
            .setB(386E-6 / 2)
            .setBus("bus2A")
            .setPairingKey("XNODE1")
            .add();
        DanglingLine xnode1nhv2 = network.getVoltageLevel("VL3").newDanglingLine()
            .setId("XNODE1_NHV2")
            .setP0(0.0)
            .setQ0(0.0)
            .setR(1.5)
            .setX(13.0)
            .setG(2E-6)
            .setB(386E-6 / 2)
            .setBus("bus3A")
            .setPairingKey("XNODE1")
            .add();
        TieLine tieLine = network.newTieLine()
            .setId("NHV1_NHV2_1")
            .setDanglingLine1(nhv1xnode1.getId())
            .setDanglingLine2(xnode1nhv2.getId())
            .add();

        // Disconnection
        assertTieLineConnection(tieLine, true, true);
        UnplannedDisconnection disconnection = new UnplannedDisconnectionBuilder()
            .withIdentifiableId("NHV1_NHV2_1")
            .withFictitiousSwitchesOperable(false)
            .build();
        disconnection.apply(network);
        assertTieLineConnection(tieLine, false, false);

        // Connection
        ConnectableConnection connection = new ConnectableConnectionBuilder()
            .withIdentifiableId("NHV1_NHV2_1")
            .withFictitiousSwitchesOperable(false)
            .withOnlyBreakersOperable(true)
            .build();
        connection.apply(network);
        assertTieLineConnection(tieLine, true, true);

        // Disconnection on one side
        UnplannedDisconnection disconnectionSide1 = new UnplannedDisconnectionBuilder()
            .withIdentifiableId("NHV1_NHV2_1")
            .withFictitiousSwitchesOperable(false)
            .withSide(ThreeSides.ONE)
            .build();
        disconnectionSide1.apply(network);
        assertTieLineConnection(tieLine, false, true);

        // Connection on the same side
        ConnectableConnection connectionSide1 = new ConnectableConnectionBuilder()
            .withIdentifiableId("NHV1_NHV2_1")
            .withFictitiousSwitchesOperable(false)
            .withOnlyBreakersOperable(true)
            .withSide(ThreeSides.ONE)
            .build();
        connectionSide1.apply(network);
        assertTieLineConnection(tieLine, true, true);
    }

    @Test
    void testHvdcLine() {
        Network network = HvdcTestNetwork.createLcc();
        HvdcLine hvdcLine = network.getHvdcLine("L");

        // Disconnection
        assertHvdcLineConnection(hvdcLine, true, true);
        UnplannedDisconnection disconnection = new UnplannedDisconnectionBuilder()
            .withIdentifiableId("L")
            .withFictitiousSwitchesOperable(false)
            .build();
        disconnection.apply(network);
        assertHvdcLineConnection(hvdcLine, false, false);

        // Connection
        ConnectableConnection connection = new ConnectableConnectionBuilder()
            .withIdentifiableId("L")
            .withFictitiousSwitchesOperable(false)
            .withOnlyBreakersOperable(true)
            .build();
        connection.apply(network);
        assertHvdcLineConnection(hvdcLine, true, true);

        // Disconnection on one side
        UnplannedDisconnection disconnectionSide2 = new UnplannedDisconnectionBuilder()
            .withIdentifiableId("L")
            .withFictitiousSwitchesOperable(false)
            .withSide(ThreeSides.TWO)
            .build();
        disconnectionSide2.apply(network);
        assertHvdcLineConnection(hvdcLine, true, false);

        // Connection on the same side
        ConnectableConnection connectionSide2 = new ConnectableConnectionBuilder()
            .withIdentifiableId("L")
            .withFictitiousSwitchesOperable(false)
            .withOnlyBreakersOperable(true)
            .withSide(ThreeSides.TWO)
            .build();
        connectionSide2.apply(network);
        assertHvdcLineConnection(hvdcLine, true, true);
    }

    private void assertTieLineConnection(TieLine tieLine, boolean expectedConnectionOnSide1, boolean expectedConnectionOnSide2) {
        assertEquals(expectedConnectionOnSide1, tieLine.getDanglingLine1().getTerminal().isConnected());
        assertEquals(expectedConnectionOnSide2, tieLine.getDanglingLine2().getTerminal().isConnected());
    }

    private void assertHvdcLineConnection(HvdcLine hvdcLine, boolean expectedConnectionOnSide1, boolean expectedConnectionOnSide2) {
        assertEquals(expectedConnectionOnSide1, hvdcLine.getConverterStation1().getTerminal().isConnected());
        assertEquals(expectedConnectionOnSide2, hvdcLine.getConverterStation2().getTerminal().isConnected());
    }

    @Test
    void testIdentifiableNotFoundException() {
        Network network = createNetwork();
        UnplannedDisconnection disconnection = new UnplannedDisconnectionBuilder()
            .withIdentifiableId("ELEMENT_NOT_PRESENT")
            .withFictitiousSwitchesOperable(false)
            .build();

        NamingStrategy namingStrategy = new DefaultNamingStrategy();
        ComputationManager computationManager = LocalComputationManager.getDefault();
        assertDoesNotThrow(() -> disconnection.apply(network, namingStrategy, false, computationManager, ReportNode.NO_OP));
        PowsyblException disconnectionException = assertThrows(PowsyblException.class, () -> disconnection.apply(network, true, ReportNode.NO_OP));
        assertEquals("Identifiable 'ELEMENT_NOT_PRESENT' not found", disconnectionException.getMessage());

        ConnectableConnection connection = new ConnectableConnectionBuilder()
            .withIdentifiableId("ELEMENT_NOT_PRESENT")
            .withFictitiousSwitchesOperable(false)
            .withOnlyBreakersOperable(true)
            .build();
        assertDoesNotThrow(() -> connection.apply(network, namingStrategy, false, computationManager, ReportNode.NO_OP));
        PowsyblException connectionException = assertThrows(PowsyblException.class, () -> connection.apply(network, true, ReportNode.NO_OP));
        assertEquals("Identifiable 'ELEMENT_NOT_PRESENT' not found", connectionException.getMessage());
    }

    @Test
    void testMethodNotImplemented() {
        Network network = createNetwork();
        UnplannedDisconnection disconnection = new UnplannedDisconnectionBuilder()
            .withIdentifiableId("S1")
            .withFictitiousSwitchesOperable(false)
            .build();

        NamingStrategy namingStrategy = new DefaultNamingStrategy();
        ComputationManager computationManager = LocalComputationManager.getDefault();
        assertDoesNotThrow(() -> disconnection.apply(network, namingStrategy, false, computationManager, ReportNode.NO_OP));
        PowsyblException disconnectionException = assertThrows(PowsyblException.class, () -> disconnection.apply(network, true, ReportNode.NO_OP));
        assertEquals("Disconnection not implemented for identifiable 'S1'", disconnectionException.getMessage());

        ConnectableConnection connection = new ConnectableConnectionBuilder()
            .withIdentifiableId("S1")
            .withFictitiousSwitchesOperable(false)
            .withOnlyBreakersOperable(true)
            .build();

        assertDoesNotThrow(() -> connection.apply(network, namingStrategy, false, computationManager, ReportNode.NO_OP));
        PowsyblException connectionException = assertThrows(PowsyblException.class, () -> connection.apply(network, true, ReportNode.NO_OP));
        assertEquals("Connection not implemented for identifiable 'S1'", connectionException.getMessage());
    }

    @Test
    void testGetName() {
        AbstractNetworkModification networkModification = new ConnectableConnection("ID", false, false, ThreeSides.ONE);
        assertEquals("ConnectableConnection", networkModification.getName());

        networkModification = new UnplannedDisconnection("ID", false, ThreeSides.ONE);
        assertEquals("UnplannedDisconnection", networkModification.getName());

        networkModification = new PlannedDisconnection("ID", false, ThreeSides.ONE);
        assertEquals("PlannedDisconnection", networkModification.getName());
    }

    @Test
    void testHasImpactCannotBeApplied() {
        Network network = createNetwork();

        // Unknown element
        UnplannedDisconnection disconnection = new UnplannedDisconnectionBuilder()
            .withIdentifiableId("ELEMENT_NOT_PRESENT")
            .withFictitiousSwitchesOperable(false)
            .build();
        assertEquals(NetworkModificationImpact.CANNOT_BE_APPLIED, disconnection.hasImpactOnNetwork(network));
        ConnectableConnection connectionUnknownElement = new ConnectableConnectionBuilder()
            .withIdentifiableId("ELEMENT_NOT_PRESENT")
            .withFictitiousSwitchesOperable(false)
            .build();
        assertEquals(NetworkModificationImpact.CANNOT_BE_APPLIED, connectionUnknownElement.hasImpactOnNetwork(network));

        // Not a connectable, nor a TieLine nor a HvdcLine
        UnplannedDisconnection voltageLevelDisconnection = new UnplannedDisconnectionBuilder()
            .withIdentifiableId("VL1")
            .build();
        assertEquals(NetworkModificationImpact.CANNOT_BE_APPLIED, voltageLevelDisconnection.hasImpactOnNetwork(network));
        ConnectableConnection connectionVoltageLevel = new ConnectableConnectionBuilder()
            .withIdentifiableId("VL1")
            .build();
        assertEquals(NetworkModificationImpact.CANNOT_BE_APPLIED, connectionVoltageLevel.hasImpactOnNetwork(network));

        // Wrong ThreeSide on Connectable with 2 sides
        PlannedDisconnection disconnectionConnectable = new PlannedDisconnectionBuilder()
            .withIdentifiableId("L1")
            .withSide(ThreeSides.THREE)
            .build();
        ConnectableConnection connectionConnectable = new ConnectableConnectionBuilder()
            .withIdentifiableId("L1")
            .withSide(ThreeSides.THREE)
            .build();
        assertImpactOfConnectDisconnect(network,
            connectionConnectable, NetworkModificationImpact.CANNOT_BE_APPLIED,
            disconnectionConnectable, NetworkModificationImpact.CANNOT_BE_APPLIED);

        // Add a load on the network
        LoadAdder loadAdder = network.getVoltageLevel("VL1").newLoad()
            .setId("LD1")
            .setLoadType(LoadType.UNDEFINED)
            .setP0(80)
            .setQ0(10);
        NetworkModification modification = new CreateFeederBayBuilder()
            .withInjectionAdder(loadAdder)
            .withBusOrBusbarSectionId("BBS11")
            .withInjectionPositionOrder(15)
            .withInjectionFeederName("newLoadFeeder")
            .withInjectionDirection(BOTTOM)
            .build();
        modification.apply(network);

        // Wrong ThreeSide on Connectable with 1 side
        disconnectionConnectable = new PlannedDisconnectionBuilder()
            .withIdentifiableId("LD1")
            .withSide(ThreeSides.TWO)
            .build();
        connectionConnectable = new ConnectableConnectionBuilder()
            .withIdentifiableId("LD1")
            .withSide(ThreeSides.TWO)
            .build();
        assertImpactOfConnectDisconnect(network,
            connectionConnectable, NetworkModificationImpact.CANNOT_BE_APPLIED,
            disconnectionConnectable, NetworkModificationImpact.CANNOT_BE_APPLIED);

        // Add tie line
        addTieLine(network);

        // Wrong ThreeSide on Tie Line
        disconnectionConnectable = new PlannedDisconnectionBuilder()
            .withIdentifiableId("NHV1_NHV2_1")
            .withSide(ThreeSides.THREE)
            .build();
        connectionConnectable = new ConnectableConnectionBuilder()
            .withIdentifiableId("NHV1_NHV2_1")
            .withSide(ThreeSides.THREE)
            .build();
        assertImpactOfConnectDisconnect(network,
            connectionConnectable, NetworkModificationImpact.CANNOT_BE_APPLIED,
            disconnectionConnectable, NetworkModificationImpact.CANNOT_BE_APPLIED);

        // Wrong ThreeSide on Hvdc Line
        Network networkHvdc = HvdcTestNetwork.createLcc();
        disconnectionConnectable = new PlannedDisconnectionBuilder()
            .withIdentifiableId("L")
            .withSide(ThreeSides.THREE)
            .build();
        connectionConnectable = new ConnectableConnectionBuilder()
            .withIdentifiableId("L")
            .withSide(ThreeSides.THREE)
            .build();
        assertImpactOfConnectDisconnect(networkHvdc,
            connectionConnectable, NetworkModificationImpact.CANNOT_BE_APPLIED,
            disconnectionConnectable, NetworkModificationImpact.CANNOT_BE_APPLIED);
    }

    @Test
    void testHasImpactConnectable() {
        Network network = createNetwork();
        Line line = network.getLine("L1");

        // Connection and disconnection on both sides
        PlannedDisconnection disconnectionConnectable = new PlannedDisconnectionBuilder()
            .withIdentifiableId("L1")
            .build();
        ConnectableConnection connectionConnectable = new ConnectableConnectionBuilder()
            .withIdentifiableId("L1")
            .build();
        // Both sides are connected
        assertImpactOfConnectDisconnect(network,
            connectionConnectable, NetworkModificationImpact.NO_IMPACT_ON_NETWORK,
            disconnectionConnectable, NetworkModificationImpact.HAS_IMPACT_ON_NETWORK);
        // Only one side connected
        line.getTerminal1().disconnect();
        assertImpactOfConnectDisconnect(network,
            connectionConnectable, NetworkModificationImpact.HAS_IMPACT_ON_NETWORK,
            disconnectionConnectable, NetworkModificationImpact.HAS_IMPACT_ON_NETWORK);
        // Both sides disconnected
        line.getTerminal2().disconnect();
        assertImpactOfConnectDisconnect(network,
            connectionConnectable, NetworkModificationImpact.HAS_IMPACT_ON_NETWORK,
            disconnectionConnectable, NetworkModificationImpact.NO_IMPACT_ON_NETWORK);

        // Connection and disconnection on side 1
        PlannedDisconnection disconnectionConnectableSide1 = new PlannedDisconnectionBuilder()
            .withIdentifiableId("L1")
            .withSide(ThreeSides.ONE)
            .build();
        ConnectableConnection connectionConnectableSide1 = new ConnectableConnectionBuilder()
            .withIdentifiableId("L1")
            .withSide(ThreeSides.ONE)
            .build();
        // Side 1 is disconnected
        assertImpactOfConnectDisconnect(network,
            connectionConnectableSide1, NetworkModificationImpact.HAS_IMPACT_ON_NETWORK,
            disconnectionConnectableSide1, NetworkModificationImpact.NO_IMPACT_ON_NETWORK);

        // Connection and disconnection on side 2
        PlannedDisconnection disconnectionConnectableSide2 = new PlannedDisconnectionBuilder()
            .withIdentifiableId("L1")
            .withSide(ThreeSides.TWO)
            .build();
        ConnectableConnection connectionConnectableSide2 = new ConnectableConnectionBuilder()
            .withIdentifiableId("L1")
            .withSide(ThreeSides.TWO)
            .build();
        // Side 2 is disconnected
        assertImpactOfConnectDisconnect(network,
            connectionConnectableSide2, NetworkModificationImpact.HAS_IMPACT_ON_NETWORK,
            disconnectionConnectableSide2, NetworkModificationImpact.NO_IMPACT_ON_NETWORK);

        // Add a load on the network
        LoadAdder loadAdder = network.getVoltageLevel("VL1").newLoad()
            .setId("LD1")
            .setLoadType(LoadType.UNDEFINED)
            .setP0(80)
            .setQ0(10);
        NetworkModification modification = new CreateFeederBayBuilder()
            .withInjectionAdder(loadAdder)
            .withBusOrBusbarSectionId("BBS11")
            .withInjectionPositionOrder(15)
            .withInjectionFeederName("newLoadFeeder")
            .withInjectionDirection(BOTTOM)
            .build();
        modification.apply(network);

        // Connection/Disconnection on Connectable with 1 side
        disconnectionConnectable = new PlannedDisconnectionBuilder()
            .withIdentifiableId("LD1")
            .withSide(ThreeSides.ONE)
            .build();
        connectionConnectable = new ConnectableConnectionBuilder()
            .withIdentifiableId("LD1")
            .withSide(ThreeSides.ONE)
            .build();
        assertImpactOfConnectDisconnect(network,
            connectionConnectable, NetworkModificationImpact.NO_IMPACT_ON_NETWORK,
            disconnectionConnectable, NetworkModificationImpact.HAS_IMPACT_ON_NETWORK);

        // Connection/Disconnection on Connectable with 3 sides
        disconnectionConnectable = new PlannedDisconnectionBuilder()
            .withIdentifiableId("twt")
            .withSide(ThreeSides.THREE)
            .build();
        connectionConnectable = new ConnectableConnectionBuilder()
            .withIdentifiableId("twt")
            .withSide(ThreeSides.THREE)
            .build();
        assertImpactOfConnectDisconnect(network,
            connectionConnectable, NetworkModificationImpact.NO_IMPACT_ON_NETWORK,
            disconnectionConnectable, NetworkModificationImpact.HAS_IMPACT_ON_NETWORK);
    }

    @Test
    void testHasImpactTieLine() {
        Network network = createNetwork();
        addTieLine(network);
        TieLine tieLine = network.getTieLine("NHV1_NHV2_1");

        // Connection/Disconnection on both sides
        ConnectableConnection connectionTieLine = new ConnectableConnectionBuilder()
            .withIdentifiableId("NHV1_NHV2_1")
            .withFictitiousSwitchesOperable(false)
            .withOnlyBreakersOperable(true)
            .build();
        UnplannedDisconnection disconnectionTieLine = new UnplannedDisconnectionBuilder()
            .withIdentifiableId("NHV1_NHV2_1")
            .withFictitiousSwitchesOperable(true)
            .build();
        // Both sides are connected
        assertImpactOfConnectDisconnect(network,
            connectionTieLine, NetworkModificationImpact.NO_IMPACT_ON_NETWORK,
            disconnectionTieLine, NetworkModificationImpact.HAS_IMPACT_ON_NETWORK);
        // One side is disconnected
        tieLine.getTerminal2().disconnect();
        assertImpactOfConnectDisconnect(network,
            connectionTieLine, NetworkModificationImpact.HAS_IMPACT_ON_NETWORK,
            disconnectionTieLine, NetworkModificationImpact.HAS_IMPACT_ON_NETWORK);
        // Both sides are disconnected
        tieLine.getTerminal1().disconnect();
        assertImpactOfConnectDisconnect(network,
            connectionTieLine, NetworkModificationImpact.HAS_IMPACT_ON_NETWORK,
            disconnectionTieLine, NetworkModificationImpact.NO_IMPACT_ON_NETWORK);
        // Only the other side is disconnected
        tieLine.getTerminal2().connect();
        assertImpactOfConnectDisconnect(network,
            connectionTieLine, NetworkModificationImpact.HAS_IMPACT_ON_NETWORK,
            disconnectionTieLine, NetworkModificationImpact.HAS_IMPACT_ON_NETWORK);

        // Connection/Disconnection on side 1
        connectionTieLine = new ConnectableConnectionBuilder()
            .withIdentifiableId("NHV1_NHV2_1")
            .withFictitiousSwitchesOperable(false)
            .withOnlyBreakersOperable(true)
            .withSide(ThreeSides.ONE)
            .build();
        disconnectionTieLine = new UnplannedDisconnectionBuilder()
            .withIdentifiableId("NHV1_NHV2_1")
            .withFictitiousSwitchesOperable(true)
            .withSide(ThreeSides.ONE)
            .build();
        // Both sides are connected
        assertImpactOfConnectDisconnect(network,
            connectionTieLine, NetworkModificationImpact.HAS_IMPACT_ON_NETWORK,
            disconnectionTieLine, NetworkModificationImpact.NO_IMPACT_ON_NETWORK);

        // Connection/Disconnection on side 2
        connectionTieLine = new ConnectableConnectionBuilder()
            .withIdentifiableId("NHV1_NHV2_1")
            .withFictitiousSwitchesOperable(true)
            .withOnlyBreakersOperable(false)
            .withSide(ThreeSides.TWO)
            .build();
        disconnectionTieLine = new UnplannedDisconnectionBuilder()
            .withIdentifiableId("NHV1_NHV2_1")
            .withFictitiousSwitchesOperable(true)
            .withSide(ThreeSides.TWO)
            .build();
        // Both sides are connected
        assertImpactOfConnectDisconnect(network,
            connectionTieLine, NetworkModificationImpact.NO_IMPACT_ON_NETWORK,
            disconnectionTieLine, NetworkModificationImpact.HAS_IMPACT_ON_NETWORK);
    }

    @Test
    void testHasImpactHvdcLine() {
        Network network = HvdcTestNetwork.createLcc();

        // Connection/Disconnection on both sides
        UnplannedDisconnection disconnectionHvdcLine = new UnplannedDisconnectionBuilder()
            .withIdentifiableId("L")
            .withFictitiousSwitchesOperable(false)
            .build();
        ConnectableConnection connectionHvdcLine = new ConnectableConnectionBuilder()
            .withIdentifiableId("L")
            .withFictitiousSwitchesOperable(false)
            .withOnlyBreakersOperable(true)
            .build();
        // Both sides are connected
        assertImpactOfConnectDisconnect(network,
            connectionHvdcLine, NetworkModificationImpact.NO_IMPACT_ON_NETWORK,
            disconnectionHvdcLine, NetworkModificationImpact.HAS_IMPACT_ON_NETWORK);
        // One side is disconnected
        network.getHvdcLine("L").getConverterStation2().getTerminal().disconnect();
        assertImpactOfConnectDisconnect(network,
            connectionHvdcLine, NetworkModificationImpact.HAS_IMPACT_ON_NETWORK,
            disconnectionHvdcLine, NetworkModificationImpact.HAS_IMPACT_ON_NETWORK);
        // Both sides are disconnected
        network.getHvdcLine("L").getConverterStation1().getTerminal().disconnect();
        assertImpactOfConnectDisconnect(network,
            connectionHvdcLine, NetworkModificationImpact.HAS_IMPACT_ON_NETWORK,
            disconnectionHvdcLine, NetworkModificationImpact.NO_IMPACT_ON_NETWORK);
        // Only the other side is disconnected
        network.getHvdcLine("L").getConverterStation2().getTerminal().connect();
        assertImpactOfConnectDisconnect(network,
            connectionHvdcLine, NetworkModificationImpact.HAS_IMPACT_ON_NETWORK,
            disconnectionHvdcLine, NetworkModificationImpact.HAS_IMPACT_ON_NETWORK);

        // Connection/Disconnection on side 1
        disconnectionHvdcLine = new UnplannedDisconnectionBuilder()
            .withIdentifiableId("L")
            .withFictitiousSwitchesOperable(false)
            .withSide(ThreeSides.ONE)
            .build();
        connectionHvdcLine = new ConnectableConnectionBuilder()
            .withIdentifiableId("L")
            .withFictitiousSwitchesOperable(false)
            .withOnlyBreakersOperable(true)
            .withSide(ThreeSides.ONE)
            .build();
        // Both sides are connected
        assertImpactOfConnectDisconnect(network,
            connectionHvdcLine, NetworkModificationImpact.HAS_IMPACT_ON_NETWORK,
            disconnectionHvdcLine, NetworkModificationImpact.NO_IMPACT_ON_NETWORK);

        // Connection/Disconnection on side 2
        disconnectionHvdcLine = new UnplannedDisconnectionBuilder()
            .withIdentifiableId("L")
            .withFictitiousSwitchesOperable(false)
            .withSide(ThreeSides.TWO)
            .build();
        connectionHvdcLine = new ConnectableConnectionBuilder()
            .withIdentifiableId("L")
            .withFictitiousSwitchesOperable(false)
            .withOnlyBreakersOperable(true)
            .withSide(ThreeSides.TWO)
            .build();
        // Both sides are connected
        assertImpactOfConnectDisconnect(network,
            connectionHvdcLine, NetworkModificationImpact.NO_IMPACT_ON_NETWORK,
            disconnectionHvdcLine, NetworkModificationImpact.HAS_IMPACT_ON_NETWORK);
    }

    private void assertImpactOfConnectDisconnect(Network network,
                                                 ConnectableConnection connection, NetworkModificationImpact connectionImpact,
                                                 AbstractDisconnection disconnection, NetworkModificationImpact disconnectionImpact) {
        assertEquals(connectionImpact, connection.hasImpactOnNetwork(network));
        assertEquals(disconnectionImpact, disconnection.hasImpactOnNetwork(network));
    }

    private void addTieLine(Network network) {
        // Add tie line
        DanglingLine nhv1xnode1 = network.getVoltageLevel("VL2").newDanglingLine()
            .setId("NHV1_XNODE1")
            .setP0(0.0)
            .setQ0(0.0)
            .setR(1.5)
            .setX(20.0)
            .setG(1E-6)
            .setB(386E-6 / 2)
            .setBus("bus2A")
            .setPairingKey("XNODE1")
            .add();
        DanglingLine xnode1nhv2 = network.getVoltageLevel("VL3").newDanglingLine()
            .setId("XNODE1_NHV2")
            .setP0(0.0)
            .setQ0(0.0)
            .setR(1.5)
            .setX(13.0)
            .setG(2E-6)
            .setB(386E-6 / 2)
            .setBus("bus3A")
            .setPairingKey("XNODE1")
            .add();
        network.newTieLine()
            .setId("NHV1_NHV2_1")
            .setDanglingLine1(nhv1xnode1.getId())
            .setDanglingLine2(xnode1nhv2.getId())
            .add();
    }
}