CreateVoltageLevelSectionsTest.java

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

import com.powsybl.commons.PowsyblException;
import com.powsybl.commons.report.ReportNode;
import com.powsybl.iidm.modification.NetworkModificationImpact;
import com.powsybl.iidm.network.BusbarSection;
import com.powsybl.iidm.network.Country;
import com.powsybl.iidm.network.Network;
import com.powsybl.iidm.network.Substation;
import com.powsybl.iidm.network.SwitchKind;
import com.powsybl.iidm.network.TopologyKind;
import com.powsybl.iidm.network.VoltageLevel;
import com.powsybl.iidm.network.extensions.BusbarSectionPositionAdder;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

import java.io.IOException;
import java.time.ZonedDateTime;
import java.util.stream.Stream;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

/**
 * @author Franck Lecuyer {@literal <franck.lecuyer_externe at rte-france.com>}
 */
class CreateVoltageLevelSectionsTest extends AbstractModificationTest {

    private void createBusbarSection(VoltageLevel vl, String id, int node, int busbarIndex, int sectionIndex) {
        BusbarSection bbs = vl.getNodeBreakerView()
            .newBusbarSection()
            .setId(id)
            .setNode(node)
            .add();
        if (busbarIndex != -1 && sectionIndex != -1) {
            bbs.newExtension(BusbarSectionPositionAdder.class)
                .withBusbarIndex(busbarIndex)
                .withSectionIndex(sectionIndex)
                .add();
        }
    }

    private void createDisconnector(VoltageLevel vl, String id, int node1, int node2) {
        vl.getNodeBreakerView().newDisconnector()
            .setId(id)
            .setNode1(node1)
            .setNode2(node2)
            .setOpen(false)
            .add();
    }

    private void createBreaker(VoltageLevel vl, String id, int node1, int node2) {
        vl.getNodeBreakerView().newBreaker()
            .setId(id)
            .setNode1(node1)
            .setNode2(node2)
            .setOpen(false)
            .add();
    }

    private void createGenerator(VoltageLevel vl, String id, int node) {
        vl.newGenerator()
            .setId(id)
            .setNode(node)
            .setMaxP(100)
            .setMinP(0)
            .setTargetP(100)
            .setTargetV(400)
            .setVoltageRegulatorOn(true)
            .add();
    }

    private 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();

        // Busbar sections
        createBusbarSection(vl1, "BBS11", 0, 1, 1);
        createBusbarSection(vl1, "BBS21", 1, 2, 1);
        createBusbarSection(vl1, "BBS31", 2, 3, 1);
        createBusbarSection(vl1, "BBS12", 3, 1, 2);
        createBusbarSection(vl1, "BBS22", 4, 2, 2);
        createBusbarSection(vl1, "BBS32", 5, 3, 2);
        createBusbarSection(vl1, "BBS13", 6, 1, 3);
        createBusbarSection(vl1, "BBS23", 7, 2, 3);
        createBusbarSection(vl1, "BBS33", 8, 3, 3);

        // Disconnectors and/or disconnectors/breakers/disconnectors for coupling between busbar sections
        createDisconnector(vl1, "D_BBS11_BBS12", 0, 3);
        createDisconnector(vl1, "D_BBS12_BBS13_1", 3, 20);
        createBreaker(vl1, "B_BBS12_BBS13", 20, 21);
        createDisconnector(vl1, "D_BBS12_BBS13_2", 21, 6);

        createDisconnector(vl1, "D_BBS21_BBS22", 1, 4);
        createDisconnector(vl1, "D_BBS22_BBS23_1", 4, 22);
        createBreaker(vl1, "B_BBS22_BBS23", 22, 23);
        createDisconnector(vl1, "D_BBS22_BBS23_2", 23, 7);

        createDisconnector(vl1, "D_BBS31_BBS32", 2, 5);
        createDisconnector(vl1, "D_BBS32_BBS33_1", 5, 24);
        createBreaker(vl1, "B_BBS32_BBS33", 24, 25);
        createDisconnector(vl1, "D_BBS32_BBS33_2", 25, 8);

        // Add some equipments connected to the busbar sections
        createDisconnector(vl1, "D_11_GEN_1", 0, 30);
        createDisconnector(vl1, "D_21_GEN_1", 1, 30);
        createDisconnector(vl1, "D_31_GEN_1", 2, 30);
        createBreaker(vl1, "B_GEN_1", 30, 31);
        createGenerator(vl1, "GEN_1", 31);

        createDisconnector(vl1, "D_12_GEN_2", 3, 40);
        createDisconnector(vl1, "D_22_GEN_2", 4, 40);
        createDisconnector(vl1, "D_32_GEN_2", 5, 40);
        createBreaker(vl1, "B_GEN_2", 40, 41);
        createGenerator(vl1, "GEN_2", 41);

        createDisconnector(vl1, "D_13_GEN_3", 6, 50);
        createDisconnector(vl1, "D_23_GEN_3", 7, 50);
        createDisconnector(vl1, "D_33_GEN_3", 8, 50);
        createBreaker(vl1, "B_GEN_3", 50, 51);
        createGenerator(vl1, "GEN_3", 51);

        return network;
    }

    @ParameterizedTest
    @MethodSource("parametersOK")
    void testInsertBetweenTwoSectionsWithDisconnectors(String referenceBusbarSectionId,
                                                       boolean createTheBusbarSectionsAfterTheReferenceBusbarSection,
                                                       boolean allBusbars,
                                                       SwitchKind leftSwitchKind,
                                                       SwitchKind rightSwitchKind,
                                                       String resourceFile) throws IOException {
        // Network creation
        Network network = createNetwork();

        // Network modification
        CreateVoltageLevelSections modification = new CreateVoltageLevelSectionsBuilder()
            .withReferenceBusbarSectionId(referenceBusbarSectionId)
            .withCreateTheBusbarSectionsAfterTheReferenceBusbarSection(createTheBusbarSectionsAfterTheReferenceBusbarSection)
            .withAllBusbars(allBusbars)
            .withLeftSwitchKind(leftSwitchKind)
            .withRightSwitchKind(rightSwitchKind)
            .withSwitchPrefixId("VL1")
            .withBusbarSectionPrefixId("VL1")
            .build();
        modification.apply(network);
        writeXmlTest(network, resourceFile);
    }

    private static Stream<Arguments> parametersOK() {
        return Stream.of(
            Arguments.of("BBS12", true, true, SwitchKind.DISCONNECTOR, SwitchKind.DISCONNECTOR, "/create-vl-sections-insert-between-2-sections-with-disconnectors.xiidm"),
            Arguments.of("BBS11", true, true, SwitchKind.BREAKER, SwitchKind.BREAKER, "/create-vl-sections-insert-between-2-sections-with-breakers.xiidm"),
            Arguments.of("BBS11", true, true, SwitchKind.DISCONNECTOR, SwitchKind.BREAKER, "/create-vl-sections-insert-between-2-sections-with-disconnectors-on-left-side-and-breakers-on-right-side.xiidm"),
            Arguments.of("BBS12", true, false, SwitchKind.DISCONNECTOR, SwitchKind.DISCONNECTOR, "/create-vl-sections-insert-between-2-sections-on-only-one-busbar-with-disconnectors.xiidm"),
            Arguments.of("BBS21", false, true, SwitchKind.BREAKER, SwitchKind.BREAKER, "/create-vl-sections-insert-before-first-section-with-breakers.xiidm"),
            Arguments.of("BBS13", true, true, SwitchKind.DISCONNECTOR, SwitchKind.DISCONNECTOR, "/create-vl-sections-insert-after-last-section-with-disconnectors.xiidm")
        );
    }

    @ParameterizedTest
    @MethodSource("parametersNOK")
    void testWithException(String referenceBusbarSectionId, boolean withMissingBbsPosition, String exceptionMessage) {
        // Network creation
        Network network = createNetwork();
        if (withMissingBbsPosition) {
            VoltageLevel vl1 = network.getVoltageLevel("VL1");
            createBusbarSection(vl1, "BBS41", 60, -1, -1);
        }

        // Network modification
        CreateVoltageLevelSections modification = new CreateVoltageLevelSectionsBuilder()
            .withReferenceBusbarSectionId(referenceBusbarSectionId)
            .withCreateTheBusbarSectionsAfterTheReferenceBusbarSection(true)
            .withAllBusbars(true)
            .withLeftSwitchKind(SwitchKind.DISCONNECTOR)
            .withRightSwitchKind(SwitchKind.DISCONNECTOR)
            .withSwitchPrefixId("VL1")
            .withBusbarSectionPrefixId("VL1")
            .build();
        PowsyblException exception = assertThrows(PowsyblException.class, () -> modification.apply(network, true, ReportNode.NO_OP));
        assertEquals(exceptionMessage, exception.getMessage());
    }

    private static Stream<Arguments> parametersNOK() {
        return Stream.of(
            Arguments.of("unknown_bbs", false, "Busbar section unknown_bbs not found"),
            Arguments.of("GEN_1", false, "Busbar section GEN_1 not found"),
            Arguments.of("BBS12", true, "Some busbar sections have no position in voltage level (VL1)")
        );
    }

    @Test
    void testImpactOnNetwork() {
        // Network creation
        Network network = createNetwork();

        // Network modification
        CreateVoltageLevelSections modification1 = new CreateVoltageLevelSectionsBuilder()
            .withReferenceBusbarSectionId("BBS11")
            .withCreateTheBusbarSectionsAfterTheReferenceBusbarSection(true)
            .withAllBusbars(true)
            .withLeftSwitchKind(SwitchKind.DISCONNECTOR)
            .withLeftSwitchFictitious(false)
            .withRightSwitchKind(SwitchKind.DISCONNECTOR)
            .withRightSwitchFictitious(false)
            .withSwitchPrefixId("VL1")
            .withBusbarSectionPrefixId("VL1")
            .build();
        assertEquals(NetworkModificationImpact.HAS_IMPACT_ON_NETWORK, modification1.hasImpactOnNetwork(network));

        CreateVoltageLevelSections modification2 = new CreateVoltageLevelSectionsBuilder()
            .withReferenceBusbarSectionId("BBS1")
            .withCreateTheBusbarSectionsAfterTheReferenceBusbarSection(true)
            .withAllBusbars(true)
            .withLeftSwitchKind(SwitchKind.DISCONNECTOR)
            .withLeftSwitchFictitious(false)
            .withRightSwitchKind(SwitchKind.DISCONNECTOR)
            .withRightSwitchFictitious(false)
            .withSwitchPrefixId("VL1")
            .withBusbarSectionPrefixId("VL1")
            .build();
        assertEquals(NetworkModificationImpact.CANNOT_BE_APPLIED, modification2.hasImpactOnNetwork(network));
    }
}