AbstractConvertTopologyTest.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.iidm.network.tck;

import com.powsybl.commons.PowsyblException;
import com.powsybl.iidm.network.*;
import com.powsybl.iidm.network.events.RemovalNetworkEvent;
import com.powsybl.iidm.network.events.UpdateNetworkEvent;
import com.powsybl.iidm.network.extensions.SlackTerminal;
import com.powsybl.iidm.network.test.EurostagTutorialExample1Factory;
import com.powsybl.iidm.network.test.FourSubstationsNodeBreakerFactory;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.util.Collections;
import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.*;

/**
 * @author Geoffroy Jamgotchian {@literal <geoffroy.jamgotchian at rte-france.com>}
 */
public abstract class AbstractConvertTopologyTest {

    private Network network;
    private VoltageLevel vl;
    private NetworkEventRecorder eventRecorder;

    @BeforeEach
    void setUp() {
        network = FourSubstationsNodeBreakerFactory.create();
        vl = network.getVoltageLevel("S1VL2");
        for (Switch sw : vl.getSwitches()) {
            sw.setRetained(sw.getId().equals("S1VL2_COUPLER"));
        }
        eventRecorder = new NetworkEventRecorder();
        network.addListener(eventRecorder);
    }

    @Test
    public void testBusBreakerToNodeBreaker() {
        Network busBreakerNetwork = EurostagTutorialExample1Factory.create();
        VoltageLevel vlgen = busBreakerNetwork.getVoltageLevel("VLGEN");
        var e = assertThrows(PowsyblException.class, () -> vlgen.convertToTopology(TopologyKind.NODE_BREAKER));
        assertEquals("Topology model conversion from bus/breaker to node/breaker not yet supported", e.getMessage());
    }

    @Test
    public void testNodeBreakerToBusBreaker() {
        var gh1 = network.getGenerator("GH1");
        var busesNbModel = vl.getBusBreakerView().getBusStream().toList();
        assertEquals(2, busesNbModel.size());
        assertThat(busesNbModel.stream().map(Identifiable::getId).toList()).containsExactlyInAnyOrder("S1VL2_0", "S1VL2_1");
        assertThat(busesNbModel.stream().filter(b -> b.getId().equals("S1VL2_0")).findFirst().orElseThrow()
            .getConnectedTerminalStream().map(t -> t.getConnectable().getId()).toList())
            .containsExactlyInAnyOrder("S1VL2_BBS1", "TWT", "GH1", "GH2", "GH3", "SHUNT");
        assertThat(busesNbModel.stream().filter(b -> b.getId().equals("S1VL2_1")).findFirst().orElseThrow()
            .getConnectedTerminalStream().map(t -> t.getConnectable().getId()).toList())
            .containsExactlyInAnyOrder("S1VL2_BBS2", "VSC1", "LD2", "LD3", "LD4", "LCC1");
        assertEquals("S1VL2_0", gh1.getRegulatingTerminal().getBusBreakerView().getBus().getId());
        vl.convertToTopology(TopologyKind.BUS_BREAKER);

        var busesBbModel = vl.getBusBreakerView().getBusStream().toList();
        assertEquals(2, busesBbModel.size());
        assertThat(busesBbModel.stream().map(Identifiable::getId).toList()).containsExactlyInAnyOrder("S1VL2_0", "S1VL2_1");
        // compare to initial node/breaker model, only difference is that there is no more busbar sections
        assertThat(busesBbModel.stream().filter(b -> b.getId().equals("S1VL2_0")).findFirst().orElseThrow()
            .getConnectedTerminalStream().map(t -> t.getConnectable().getId()).toList())
            .containsExactlyInAnyOrder("TWT", "GH1", "GH2", "GH3", "SHUNT");
        assertThat(busesBbModel.stream().filter(b -> b.getId().equals("S1VL2_1")).findFirst().orElseThrow()
            .getConnectedTerminalStream().map(t -> t.getConnectable().getId()).toList())
            .containsExactlyInAnyOrder("VSC1", "LD2", "LD3", "LD4", "LCC1");
        // only retained switches have been kept
        assertEquals(List.of("S1VL2_COUPLER"), vl.getBusBreakerView().getSwitchStream().map(Identifiable::getId).toList());
        assertEquals(List.of(new RemovalNetworkEvent("S1VL2_BBS1", false),
                             new RemovalNetworkEvent("S1VL2_BBS1", true),
                             new RemovalNetworkEvent("S1VL2_BBS2", false),
                             new RemovalNetworkEvent("S1VL2_BBS2", true),
                             new RemovalNetworkEvent("S1VL2_BBS1_TWT_DISCONNECTOR", false),
                             new RemovalNetworkEvent("S1VL2_BBS2_TWT_DISCONNECTOR", false),
                             new RemovalNetworkEvent("S1VL2_TWT_BREAKER", false),
                             new RemovalNetworkEvent("S1VL2_BBS1_VSC1_DISCONNECTOR", false),
                             new RemovalNetworkEvent("S1VL2_BBS2_VSC1_DISCONNECTOR", false),
                             new RemovalNetworkEvent("S1VL2_VSC1_BREAKER", false),
                             new RemovalNetworkEvent("S1VL2_BBS1_GH1_DISCONNECTOR", false),
                             new RemovalNetworkEvent("S1VL2_BBS1_GH2_DISCONNECTOR", false),
                             new RemovalNetworkEvent("S1VL2_BBS1_GH3_DISCONNECTOR", false),
                             new RemovalNetworkEvent("S1VL2_BBS2_GH1_DISCONNECTOR", false),
                             new RemovalNetworkEvent("S1VL2_BBS2_GH2_DISCONNECTOR", false),
                             new RemovalNetworkEvent("S1VL2_BBS2_GH3_DISCONNECTOR", false),
                             new RemovalNetworkEvent("S1VL2_GH1_BREAKER", false),
                             new RemovalNetworkEvent("S1VL2_GH2_BREAKER", false),
                             new RemovalNetworkEvent("S1VL2_GH3_BREAKER", false),
                             new RemovalNetworkEvent("S1VL2_BBS1_LD2_DISCONNECTOR", false),
                             new RemovalNetworkEvent("S1VL2_BBS1_LD3_DISCONNECTOR", false),
                             new RemovalNetworkEvent("S1VL2_BBS1_LD4_DISCONNECTOR", false),
                             new RemovalNetworkEvent("S1VL2_BBS2_LD2_DISCONNECTOR", false),
                             new RemovalNetworkEvent("S1VL2_BBS2_LD3_DISCONNECTOR", false),
                             new RemovalNetworkEvent("S1VL2_BBS2_LD4_DISCONNECTOR", false),
                             new RemovalNetworkEvent("S1VL2_LD2_BREAKER", false),
                             new RemovalNetworkEvent("S1VL2_LD3_BREAKER", false),
                             new RemovalNetworkEvent("S1VL2_LD4_BREAKER", false),
                             new RemovalNetworkEvent("S1VL2_BBS1_SHUNT_DISCONNECTOR", false),
                             new RemovalNetworkEvent("S1VL2_BBS2_SHUNT_DISCONNECTOR", false),
                             new RemovalNetworkEvent("S1VL2_SHUNT_BREAKER", false),
                             new RemovalNetworkEvent("S1VL2_BBS1_LCC1_DISCONNECTOR", false),
                             new RemovalNetworkEvent("S1VL2_BBS2_LCC1_DISCONNECTOR", false),
                             new RemovalNetworkEvent("S1VL2_LCC1_BREAKER", false),
                             new RemovalNetworkEvent("S1VL2_BBS1_COUPLER_DISCONNECTOR", false),
                             new RemovalNetworkEvent("S1VL2_BBS2_COUPLER_DISCONNECTOR", false),
                             new RemovalNetworkEvent("S1VL2_BBS1_TWT_DISCONNECTOR", true),
                             new RemovalNetworkEvent("S1VL2_BBS2_TWT_DISCONNECTOR", true),
                             new RemovalNetworkEvent("S1VL2_TWT_BREAKER", true),
                             new RemovalNetworkEvent("S1VL2_BBS1_VSC1_DISCONNECTOR", true),
                             new RemovalNetworkEvent("S1VL2_BBS2_VSC1_DISCONNECTOR", true),
                             new RemovalNetworkEvent("S1VL2_VSC1_BREAKER", true),
                             new RemovalNetworkEvent("S1VL2_BBS1_GH1_DISCONNECTOR", true),
                             new RemovalNetworkEvent("S1VL2_BBS1_GH2_DISCONNECTOR", true),
                             new RemovalNetworkEvent("S1VL2_BBS1_GH3_DISCONNECTOR", true),
                             new RemovalNetworkEvent("S1VL2_BBS2_GH1_DISCONNECTOR", true),
                             new RemovalNetworkEvent("S1VL2_BBS2_GH2_DISCONNECTOR", true),
                             new RemovalNetworkEvent("S1VL2_BBS2_GH3_DISCONNECTOR", true),
                             new RemovalNetworkEvent("S1VL2_GH1_BREAKER", true),
                             new RemovalNetworkEvent("S1VL2_GH2_BREAKER", true),
                             new RemovalNetworkEvent("S1VL2_GH3_BREAKER", true),
                             new RemovalNetworkEvent("S1VL2_BBS1_LD2_DISCONNECTOR", true),
                             new RemovalNetworkEvent("S1VL2_BBS1_LD3_DISCONNECTOR", true),
                             new RemovalNetworkEvent("S1VL2_BBS1_LD4_DISCONNECTOR", true),
                             new RemovalNetworkEvent("S1VL2_BBS2_LD2_DISCONNECTOR", true),
                             new RemovalNetworkEvent("S1VL2_BBS2_LD3_DISCONNECTOR", true),
                             new RemovalNetworkEvent("S1VL2_BBS2_LD4_DISCONNECTOR", true),
                             new RemovalNetworkEvent("S1VL2_LD2_BREAKER", true),
                             new RemovalNetworkEvent("S1VL2_LD3_BREAKER", true),
                             new RemovalNetworkEvent("S1VL2_LD4_BREAKER", true),
                             new RemovalNetworkEvent("S1VL2_BBS1_SHUNT_DISCONNECTOR", true),
                             new RemovalNetworkEvent("S1VL2_BBS2_SHUNT_DISCONNECTOR", true),
                             new RemovalNetworkEvent("S1VL2_SHUNT_BREAKER", true),
                             new RemovalNetworkEvent("S1VL2_BBS1_LCC1_DISCONNECTOR", true),
                             new RemovalNetworkEvent("S1VL2_BBS2_LCC1_DISCONNECTOR", true),
                             new RemovalNetworkEvent("S1VL2_LCC1_BREAKER", true),
                             new RemovalNetworkEvent("S1VL2_BBS1_COUPLER_DISCONNECTOR", true),
                             new RemovalNetworkEvent("S1VL2_BBS2_COUPLER_DISCONNECTOR", true),
                             new UpdateNetworkEvent("S1VL2", "topologyKind", null, TopologyKind.NODE_BREAKER, TopologyKind.BUS_BREAKER)),
                eventRecorder.getEvents());

        // check regulating terminal has been correctly updated
        assertEquals("S1VL2_0", gh1.getRegulatingTerminal().getBusBreakerView().getBus().getId());

        // check busbar sections have been removed
        assertNull(network.getBusbarSection("S1VL2_BBS1"));
        assertNull(network.getBusbarSection("S1VL2_BBS2"));
    }

    @Test
    public void testNodeBreakerToBusBreakerOneElementDisconnected() {
        var gh2 = network.getGenerator("GH2");
        gh2.disconnect();
        assertEquals(List.of(new UpdateNetworkEvent("S1VL2_GH2_BREAKER", "open", "InitialState", false, true)), eventRecorder.getEvents());
        eventRecorder.reset();
        var busesNbModel = vl.getBusBreakerView().getBusStream().toList();
        assertEquals(3, busesNbModel.size());
        assertThat(busesNbModel.stream().map(Identifiable::getId).toList()).containsExactlyInAnyOrder("S1VL2_0", "S1VL2_1", "S1VL2_9");
        assertThat(busesNbModel.stream().filter(b -> b.getId().equals("S1VL2_0")).findFirst().orElseThrow()
            .getConnectedTerminalStream().map(t -> t.getConnectable().getId()).toList())
            .containsExactlyInAnyOrder("S1VL2_BBS1", "TWT", "GH1", "GH3", "SHUNT");
        assertThat(busesNbModel.stream().filter(b -> b.getId().equals("S1VL2_1")).findFirst().orElseThrow()
            .getConnectedTerminalStream().map(t -> t.getConnectable().getId()).toList())
            .containsExactlyInAnyOrder("S1VL2_BBS2", "VSC1", "LD2", "LD3", "LD4", "LCC1");
        assertEquals(List.of("GH2"),
            busesNbModel.stream().filter(b -> b.getId().equals("S1VL2_9")).findFirst().orElseThrow()
                .getConnectedTerminalStream().map(t -> t.getConnectable().getId()).toList());
        vl.convertToTopology(TopologyKind.BUS_BREAKER);
        assertEquals(69, eventRecorder.getEvents().size());

        var busesBbModel = vl.getBusBreakerView().getBusStream().toList();
        assertEquals(3, busesBbModel.size());
        assertThat(busesBbModel.stream().map(Identifiable::getId).toList()).containsExactlyInAnyOrder("S1VL2_0", "S1VL2_1", "S1VL2_9");
        // compare to initial node/breaker model, only difference is that there is no more busbar sections
        assertThat(busesBbModel.stream().filter(b -> b.getId().equals("S1VL2_0")).findFirst().orElseThrow()
            .getConnectedTerminalStream().map(t -> t.getConnectable().getId()).toList())
            .containsExactlyInAnyOrder("TWT", "GH1", "GH3", "SHUNT");
        assertThat(busesBbModel.stream().filter(b -> b.getId().equals("S1VL2_1")).findFirst().orElseThrow()
            .getConnectedTerminalStream().map(t -> t.getConnectable().getId()).toList())
            .containsExactlyInAnyOrder("VSC1", "LD2", "LD3", "LD4", "LCC1");
        assertEquals(Collections.emptyList(),
            busesBbModel.stream().filter(b -> b.getId().equals("S1VL2_9")).findFirst().orElseThrow()
                .getConnectedTerminalStream().map(t -> t.getConnectable().getId()).toList());
        assertEquals("S1VL2_9", gh2.getTerminal().getBusBreakerView().getConnectableBus().getId());
        assertFalse(gh2.getTerminal().isConnected());
        // only retained switches have been kept
        assertEquals(List.of("S1VL2_COUPLER"), vl.getBusBreakerView().getSwitchStream().map(Identifiable::getId).toList());
    }

    @Test
    public void testNodeBreakerToBusBreakerWithArea() {
        var gh2 = network.getGenerator("GH2");
        network.newArea()
                .setId("area1")
                .setAreaType("fake")
                .addAreaBoundary(gh2.getTerminal(), true)
                .add();
        var boundary = network.getArea("area1").getAreaBoundaryStream().findFirst().orElseThrow();
        assertEquals(gh2.getTerminal(), boundary.getTerminal().orElse(null));
        vl.convertToTopology(TopologyKind.BUS_BREAKER);
        assertEquals(gh2.getTerminal(), boundary.getTerminal().orElse(null));
    }

    @Test
    public void testWithSlackTerminalExtension() {
        var gh2 = network.getGenerator("GH2");
        SlackTerminal.reset(gh2.getTerminal().getVoltageLevel(), gh2.getTerminal());
        var slackTerminal = gh2.getTerminal().getVoltageLevel().getExtension(SlackTerminal.class);
        assertEquals(gh2.getTerminal(), slackTerminal.getTerminal());
        vl.convertToTopology(TopologyKind.BUS_BREAKER);
        assertEquals(gh2.getTerminal(), slackTerminal.getTerminal());
    }
}