MultipleSlackBusesTest.java

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

import com.powsybl.ieeecdf.converter.IeeeCdfNetworkFactory;
import com.powsybl.iidm.network.Bus;
import com.powsybl.iidm.network.Generator;
import com.powsybl.iidm.network.Line;
import com.powsybl.iidm.network.Load;
import com.powsybl.iidm.network.Network;
import com.powsybl.iidm.network.TwoWindingsTransformer;
import com.powsybl.iidm.network.test.EurostagTutorialExample1Factory;
import com.powsybl.loadflow.LoadFlow;
import com.powsybl.loadflow.LoadFlowParameters;
import com.powsybl.loadflow.LoadFlowResult;
import com.powsybl.math.matrix.DenseMatrixFactory;
import com.powsybl.openloadflow.OpenLoadFlowParameters;
import com.powsybl.openloadflow.OpenLoadFlowProvider;
import com.powsybl.openloadflow.ac.solver.NewtonRaphsonStoppingCriteriaType;
import com.powsybl.openloadflow.network.EurostagFactory;
import com.powsybl.openloadflow.network.SlackBusSelectionMode;
import com.powsybl.openloadflow.util.LoadFlowAssert;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

import java.util.Arrays;
import java.util.List;
import java.util.stream.IntStream;
import java.util.stream.Stream;

import static com.powsybl.openloadflow.util.LoadFlowAssert.assertActivePowerEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

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

    private Network network;

    private LoadFlow.Runner loadFlowRunner;

    private LoadFlowParameters parameters;

    private OpenLoadFlowParameters parametersExt;
    Bus genBus;
    Load load;
    Generator generator;
    private Line line1;
    private Line line2;
    private TwoWindingsTransformer loadT2wt;

    @BeforeEach
    void setUp() {
        network = EurostagFactory.fix(EurostagTutorialExample1Factory.create());
        loadFlowRunner = new LoadFlow.Runner(new OpenLoadFlowProvider(new DenseMatrixFactory()));
        parameters = new LoadFlowParameters()
                .setUseReactiveLimits(false)
                .setDistributedSlack(false);
        parametersExt = OpenLoadFlowParameters.create(parameters)
                .setMaxSlackBusCount(2);
        genBus = network.getBusBreakerView().getBus("NGEN");
        load = network.getLoad("LOAD");
        generator = network.getGenerator("GEN");
        line1 = network.getLine("NHV1_NHV2_1");
        line2 = network.getLine("NHV1_NHV2_2");
        loadT2wt = network.getTwoWindingsTransformer("NHV2_NLOAD");
    }

    static Stream<Arguments> allModelAndStoppingCriteriaTypes() {
        Stream<Arguments> acStream = Arrays.stream(NewtonRaphsonStoppingCriteriaType.values()).map(a -> Arguments.of(true, a));
        Stream<Arguments> dcStream = Stream.of(Arguments.of(false, NewtonRaphsonStoppingCriteriaType.UNIFORM_CRITERIA));
        return Stream.concat(acStream, dcStream);
    }

    static Stream<Arguments> allModelAndSwitchingTypes() {
        return Stream.of(
                Arguments.of(true, true),
                Arguments.of(true, false),
                Arguments.of(false, true),
                Arguments.of(false, false)
        );
    }

    static Stream<Arguments> allModelTypesAndNbSlackbuses() {
        return Stream.concat(IntStream.range(1, 5).mapToObj(i -> Arguments.of(true, i)),
                IntStream.range(1, 5).mapToObj(i -> Arguments.of(false, i)));
    }

    static Stream<Arguments> allModelTypes() {
        return Stream.of(Arguments.of(true), Arguments.of(false));
    }

    @ParameterizedTest(name = "ac : {0}, NR stopping crit : {1}")
    @MethodSource("allModelAndStoppingCriteriaTypes")
    void multiSlackTest(boolean ac, NewtonRaphsonStoppingCriteriaType stoppingCriteria) {
        parameters.setDc(!ac);
        parametersExt.setNewtonRaphsonStoppingCriteriaType(stoppingCriteria);
        LoadFlowResult result = loadFlowRunner.run(network, parameters);
        assertTrue(result.isFullyConverged());
        LoadFlowResult.ComponentResult componentResult = result.getComponentResults().get(0);
        int expectedIterationCount = ac ? 3 : 0;
        assertEquals(expectedIterationCount, componentResult.getIterationCount());

        List<LoadFlowResult.SlackBusResult> slackBusResults = componentResult.getSlackBusResults();
        double expectedSlackBusMismatch = ac ? -0.7164 : -3.5;
        assertSlackBusResults(slackBusResults, expectedSlackBusMismatch, 2);

        if (ac) {
            assertActivePowerValues(302.807, 302.807, 600.868);
        } else {
            assertActivePowerValues(301.75, 301.75, 600);
        }

        parameters.setDistributedSlack(true);
        result = loadFlowRunner.run(network, parameters);
        assertTrue(result.isFullyConverged());
        componentResult = result.getComponentResults().get(0);
        slackBusResults = componentResult.getSlackBusResults();
        expectedIterationCount = ac ? 4 : 0;
        assertEquals(expectedIterationCount, componentResult.getIterationCount());
        expectedSlackBusMismatch = ac ? -0.005 : 0;
        assertSlackBusResults(slackBusResults, expectedSlackBusMismatch, 2);
    }

    @ParameterizedTest(name = "ac : {0}")
    @MethodSource("allModelTypes")
    void nonImpedantBranchTest(boolean ac) {
        parameters.setDc(!ac);
        network.getLine("NHV1_NHV2_1")
                .setR(0)
                .setX(0);
        LoadFlowResult result = loadFlowRunner.run(network, parameters);
        assertTrue(result.isFullyConverged());
        LoadFlowResult.ComponentResult componentResult = result.getComponentResults().get(0);
        int expectedIterationCount = ac ? 3 : 0;
        assertEquals(expectedIterationCount, componentResult.getIterationCount());
        List<LoadFlowResult.SlackBusResult> slackBusResults = componentResult.getSlackBusResults();
        double expectedSlackBusMismatch = ac ? -2.755 : -3.5;
        assertSlackBusResults(slackBusResults, expectedSlackBusMismatch, 2);

        if (ac) {
            assertActivePowerValues(603.567, 0.0, 600.812);
        } else {
            assertActivePowerValues(603.5, 0.0, 600);
        }

        parameters.setDistributedSlack(true);
        result = loadFlowRunner.run(network, parameters);
        assertTrue(result.isFullyConverged());
        componentResult = result.getComponentResults().get(0);
        slackBusResults = componentResult.getSlackBusResults();
        expectedIterationCount = ac ? 4 : 0;
        assertEquals(expectedIterationCount, componentResult.getIterationCount());
        expectedSlackBusMismatch = ac ? -0.005 : 0;
        assertSlackBusResults(slackBusResults, expectedSlackBusMismatch, 2);
    }

    @ParameterizedTest(name = "ac : {0}, nbSlackbuses : {1}")
    @MethodSource("allModelTypesAndNbSlackbuses")
    void differentNbSlackbusesTest(boolean ac, int nbSlackbuses) {
        network = IeeeCdfNetworkFactory.create14();
        parameters.setDc(!ac).setReadSlackBus(false).setDistributedSlack(true);
        parametersExt.setMaxSlackBusCount(nbSlackbuses) // Testing from 1 to 4 slack buses and expecting same global mismatch
                .setSlackBusPMaxMismatch(0.001)
                .setPlausibleActivePowerLimit(10000); // IEEE14 Network has generators with maxP = 9999 we want to keep for distribution
        LoadFlowResult result = loadFlowRunner.run(network, parameters);
        double distributedActivePower = result.getComponentResults().get(0).getDistributedActivePower();
        assertEquals(ac ? -0.006 : -13.4, distributedActivePower, 0.001);
        assertActivePowerEquals(ac ? -39.996 : -33.300, network.getGenerator("B2-G").getTerminal());
        assertActivePowerEquals(ac ? 156.886 : 153.453, network.getLine("L1-2-1").getTerminal1());
        assertActivePowerEquals(ac ? 56.131 : 54.768, network.getLine("L2-4-1").getTerminal1());
    }

    @ParameterizedTest(name = "ac : {0}, switchSlacks : {1}")
    @MethodSource("allModelAndSwitchingTypes")
    void loadOnSlackBusTest(boolean ac, boolean switchSlacks) {
        parameters.setDc(!ac);
        parametersExt.setSlackBusSelectionMode(SlackBusSelectionMode.NAME);
        if (switchSlacks) { //switching slack buses order (expecting the same result)
            parametersExt.setSlackBusesIds(List.of("VLHV2", "VLLOAD"));
        } else {
            parametersExt.setSlackBusesIds(List.of("VLLOAD", "VLHV2"));
        }

        LoadFlowResult result = loadFlowRunner.run(network, parameters);
        assertTrue(result.isFullyConverged());
        LoadFlowResult.ComponentResult componentResult = result.getComponentResults().get(0);
        int expectedIterationCount = ac ? 3 : 0;
        assertEquals(expectedIterationCount, componentResult.getIterationCount());

        List<LoadFlowResult.SlackBusResult> slackBusResults = componentResult.getSlackBusResults();
        assertEquals(switchSlacks ? List.of("VLHV2_0", "VLLOAD_0") : List.of("VLLOAD_0", "VLHV2_0"),
                slackBusResults.stream().map(LoadFlowResult.SlackBusResult::getId).toList());
        double expectedSlackBusMismatch = ac ? -0.711 : -3.5;
        assertSlackBusResults(slackBusResults, expectedSlackBusMismatch, 2);

        if (ac) {
            assertActivePowerValues(303.165, 303.165, 601.58);
        } else {
            assertActivePowerValues(303.5, 303.5, 603.5);
        }
    }

    void assertActivePowerValues(double line1P1, double line2P1, double loadT2wtP1) {
        assertActivePowerEquals(line1P1, line1.getTerminal1());
        assertActivePowerEquals(line2P1, line2.getTerminal1());
        assertActivePowerEquals(loadT2wtP1, loadT2wt.getTerminal1());
        assertActivePowerEquals(600, load.getTerminal());
        assertActivePowerEquals(-607, generator.getTerminal());
    }

    void assertSlackBusResults(List<LoadFlowResult.SlackBusResult> slackBusResults, double expectedMismatch, int slackBusCount) {
        assertEquals(slackBusCount, slackBusResults.size());
        for (LoadFlowResult.SlackBusResult slackBusResult : slackBusResults) {
            assertEquals(expectedMismatch, slackBusResult.getActivePowerMismatch(), LoadFlowAssert.DELTA_POWER);
        }
    }
}