AbstractTopologyTraverserTest.java

/**
 * Copyright (c) 2016, 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.iidm.network.*;
import com.powsybl.iidm.network.test.EurostagTutorialExample1Factory;
import com.powsybl.math.graph.TraverseResult;
import org.apache.commons.lang3.tuple.Pair;
import org.junit.jupiter.api.Test;

import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

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

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

    private Network createNodeBreakerNetwork() {
        Network network = Network.create("test", "test");
        Substation s1 = network.newSubstation()
                .setId("S1")
                .setCountry(Country.FR)
                .add();
        VoltageLevel vl1 = s1.newVoltageLevel()
                .setId("VL1")
                .setNominalV(400.0)
                .setTopologyKind(TopologyKind.NODE_BREAKER)
                .add();
        vl1.getNodeBreakerView().newBusbarSection()
                .setId("BBS1")
                .setNode(0)
                .add();
        vl1.newGenerator()
                .setId("G")
                .setNode(4)
                .setMaxP(100.0)
                .setMinP(50.0)
                .setTargetP(100.0)
                .setTargetV(400.0)
                .setVoltageRegulatorOn(true)
                .add();
        vl1.getNodeBreakerView().newInternalConnection()
                .setNode1(1)
                .setNode2(4)
                .add();
        vl1.getNodeBreakerView().newDisconnector()
                .setId("BR1")
                .setNode1(0)
                .setNode2(1)
                .setOpen(false)
                .add();
        vl1.getNodeBreakerView().newDisconnector()
                .setId("D1")
                .setNode1(0)
                .setNode2(2)
                .setOpen(false)
                .add();
        vl1.getNodeBreakerView().newBreaker()
                .setId("BR2")
                .setNode1(2)
                .setNode2(3)
                .setOpen(false)
                .add();

        Substation s2 = network.newSubstation()
                .setId("S2")
                .setCountry(Country.FR)
                .add();
        VoltageLevel vl2 = s2.newVoltageLevel()
                .setId("VL2")
                .setNominalV(400.0)
                .setTopologyKind(TopologyKind.NODE_BREAKER)
                .add();
        vl2.getNodeBreakerView().newBusbarSection()
                .setId("BBS2")
                .setNode(0)
                .add();
        vl2.newLoad()
                .setId("LD")
                .setNode(1)
                .setP0(1)
                .setQ0(1)
                .add();
        vl2.getNodeBreakerView().newDisconnector()
                .setId("BR3")
                .setNode1(0)
                .setNode2(1)
                .setOpen(false)
                .add();
        vl2.getNodeBreakerView().newDisconnector()
                .setId("D2")
                .setNode1(0)
                .setNode2(2)
                .setOpen(false)
                .add();
        vl2.getNodeBreakerView().newBreaker()
                .setId("BR4")
                .setNode1(2)
                .setNode2(3)
                .setOpen(false)
                .add();
        network.newLine()
                .setId("L1")
                .setVoltageLevel1("VL1")
                .setNode1(3)
                .setVoltageLevel2("VL2")
                .setNode2(3)
                .setR(1.0)
                .setX(1.0)
                .setG1(0.0)
                .setB1(0.0)
                .setG2(0.0)
                .setB2(0.0)
                .add();
        return network;
    }

    protected Network createMixedNodeBreakerBusBreakerNetwork() {
        Network network = createNodeBreakerNetwork();
        Substation s3 = network.newSubstation()
                .setId("S3")
                .setCountry(Country.FR)
                .add();
        VoltageLevel vl3 = s3.newVoltageLevel()
                .setId("VL3")
                .setNominalV(400.0)
                .setTopologyKind(TopologyKind.BUS_BREAKER)
                .add();
        vl3.getBusBreakerView().newBus()
                .setId("B1")
                .add();
        vl3.newLoad()
                .setId("LD2")
                .setConnectableBus("B1")
                .setBus("B1")
                .setP0(1.0)
                .setQ0(1.0)
                .add();
        network.getVoltageLevel("VL2").getNodeBreakerView().newBreaker()
                .setId("BR5")
                .setNode1(0)
                .setNode2(4)
                .setOpen(false)
                .add();
        network.newLine()
                .setId("L2")
                .setVoltageLevel1("VL2")
                .setNode1(4)
                .setVoltageLevel2("VL3")
                .setConnectableBus2("B1")
                .setBus2("B1")
                .setR(1.0)
                .setX(1.0)
                .setG1(0.0)
                .setB1(0.0)
                .setG2(0.0)
                .setB2(0.0)
                .add();
        return network;
    }

    @Test
    public void test1() {
        Network network = createNodeBreakerNetwork();
        Terminal start = network.getGenerator("G").getTerminal();
        Set<Pair<String, Integer>> visited = getVisitedSet(start, s -> TraverseResult.CONTINUE);
        assertEquals(Set.of(Pair.of("G", 0), Pair.of("BBS1", 0), Pair.of("L1", 0),
                        Pair.of("L1", 1), Pair.of("BBS2", 0), Pair.of("LD", 0)),
                visited);
    }

    @Test
    public void test2() {
        Network network = createNodeBreakerNetwork();
        Terminal start = network.getVoltageLevel("VL1").getNodeBreakerView().getBusbarSection("BBS1").getTerminal();
        Set<Pair<String, Integer>> visited = getVisitedSet(start, aSwitch ->
                !aSwitch.isOpen() && aSwitch.getKind() != SwitchKind.BREAKER ? TraverseResult.CONTINUE : TraverseResult.TERMINATE_PATH);
        assertEquals(Set.of(Pair.of("BBS1", 0), Pair.of("G", 0)), visited);
    }

    @Test
    public void test3() {
        Network network = createMixedNodeBreakerBusBreakerNetwork();
        Terminal start = network.getGenerator("G").getTerminal();
        Set<Pair<String, Integer>> visited1 = getVisitedSet(start, s -> TraverseResult.CONTINUE);
        assertEquals(Set.of(Pair.of("G", 0), Pair.of("BBS1", 0), Pair.of("L1", 0),
                        Pair.of("L1", 1), Pair.of("BBS2", 0), Pair.of("LD", 0), Pair.of("L2", 0),
                        Pair.of("L2", 1), Pair.of("LD2", 0)),
                visited1);

        Set<Pair<String, Integer>> visited2 = getVisitedSet(start, s -> TraverseResult.CONTINUE,
            t -> t.getConnectable().getId().equals("L2") ? TraverseResult.TERMINATE_PATH : TraverseResult.CONTINUE);
        assertEquals(Set.of(Pair.of("G", 0), Pair.of("BBS1", 0), Pair.of("L1", 0), Pair.of("L1", 1),
                    Pair.of("BBS2", 0), Pair.of("LD", 0), Pair.of("L2", 0)),
                visited2);

        Set<Pair<String, Integer>> visited3 = getVisitedSet(network.getLoad("LD2").getTerminal(), s -> TraverseResult.CONTINUE,
            t -> t.getConnectable().getId().equals("L2") ? TraverseResult.TERMINATE_PATH : TraverseResult.CONTINUE);
        assertEquals(Set.of(Pair.of("LD2", 0), Pair.of("L2", 1)), visited3);

    }

    @Test
    public void test4() {
        Network network = EurostagTutorialExample1Factory.create();
        Terminal start = network.getGenerator("GEN").getTerminal();
        Set<Pair<String, Integer>> visited = getVisitedSet(start, s -> TraverseResult.CONTINUE);
        assertEquals(Set.of(Pair.of("GEN", 0), Pair.of("NGEN_NHV1", 0), Pair.of("NGEN_NHV1", 1),
                        Pair.of("NHV1_NHV2_1", 0), Pair.of("NHV1_NHV2_2", 0), Pair.of("NHV1_NHV2_1", 1),
                        Pair.of("NHV1_NHV2_2", 1), Pair.of("NHV2_NLOAD", 0), Pair.of("NHV2_NLOAD", 1), Pair.of("LOAD", 0)),
                visited);
    }

    @Test
    public void test5() {
        Network network = EurostagTutorialExample1Factory.create();

        // Duplicate 2wt to go from VLGEN to VLHV1 even if traverser stops at one of them
        TwoWindingsTransformer transformer = network.getTwoWindingsTransformer("NGEN_NHV1");
        TwoWindingsTransformer duplicatedTransformer = network.getSubstation("P1")
                .newTwoWindingsTransformer()
                .setId("duplicate")
                .setVoltageLevel1("VLGEN").setBus1("NGEN")
                .setVoltageLevel2("VLHV1").setBus2("NHV1")
                .setRatedU1(transformer.getRatedU1())
                .setRatedU2(transformer.getRatedU2())
                .setR(transformer.getR())
                .setX(transformer.getX())
                .setG(transformer.getG())
                .setB(transformer.getB())
                .add();

        Terminal start = network.getGenerator("GEN").getTerminal();
        Set<Pair<String, Integer>> visited = getVisitedSet(start, s -> TraverseResult.CONTINUE,
            t -> t.getConnectable() == duplicatedTransformer && t.getVoltageLevel().getId().equals("VLGEN") ? TraverseResult.TERMINATE_PATH : TraverseResult.CONTINUE);
        assertEquals(Set.of(Pair.of("GEN", 0), Pair.of("NGEN_NHV1", 0), Pair.of("duplicate", 0), Pair.of("NGEN_NHV1", 1),
                        Pair.of("NHV1_NHV2_1", 0), Pair.of("NHV1_NHV2_2", 0), Pair.of("duplicate", 1), Pair.of("NHV1_NHV2_1", 1),
                        Pair.of("NHV1_NHV2_2", 1), Pair.of("NHV2_NLOAD", 0), Pair.of("NHV2_NLOAD", 1), Pair.of("LOAD", 0)),
                visited);
    }

    protected List<Pair<String, Integer>> getVisitedList(Terminal start, Function<Switch, TraverseResult> switchTest) {
        return getVisitedList(start, switchTest, t -> TraverseResult.CONTINUE);
    }

    protected List<Pair<String, Integer>> getVisitedList(Terminal start, Function<Switch, TraverseResult> switchTest, Function<Terminal, TraverseResult> terminalTest) {
        return getVisitedStream(start, switchTest, terminalTest).toList();
    }

    protected Set<Pair<String, Integer>> getVisitedSet(Terminal start, Function<Switch, TraverseResult> switchTest) {
        return getVisitedSet(start, switchTest, t -> TraverseResult.CONTINUE);
    }

    protected Set<Pair<String, Integer>> getVisitedSet(Terminal start, Function<Switch, TraverseResult> switchTest, Function<Terminal, TraverseResult> terminalTest) {
        return getVisitedStream(start, switchTest, terminalTest).collect(Collectors.toSet());
    }

    protected Stream<Pair<String, Integer>> getVisitedStream(Terminal start, Function<Switch, TraverseResult> switchTest, Function<Terminal, TraverseResult> terminalTest) {
        Set<Terminal> visited = new LinkedHashSet<>();
        start.traverse(new Terminal.TopologyTraverser() {
            @Override
            public TraverseResult traverse(Terminal terminal, boolean connected) {
                if (!visited.add(terminal)) {
                    fail("Traversing an already visited terminal");
                }
                return terminalTest.apply(terminal);
            }

            @Override
            public TraverseResult traverse(Switch aSwitch) {
                return switchTest.apply(aSwitch);
            }
        });
        return visited.stream().map(t -> Pair.of(t.getConnectable().getId(), indexOfTerminal(t)));
    }

    static Integer indexOfTerminal(Terminal t) {
        int index = t.getConnectable().getTerminals().indexOf(t);
        return index == -1 ? null : index;
    }

}