DcLoadFlowTest.java

/*
 * Copyright (c) 2018-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.openloadflow.dc;

import com.powsybl.commons.report.ReportNode;
import com.powsybl.commons.test.PowsyblCoreTestReportResourceBundle;
import com.powsybl.computation.local.LocalComputationManager;
import com.powsybl.ieeecdf.converter.IeeeCdfNetworkFactory;
import com.powsybl.iidm.network.*;
import com.powsybl.iidm.network.test.EurostagTutorialExample1Factory;
import com.powsybl.iidm.network.test.PhaseShifterTestCaseFactory;
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.dc.equations.DcApproximationType;
import com.powsybl.openloadflow.network.*;
import com.powsybl.openloadflow.network.impl.LfNetworkList;
import com.powsybl.openloadflow.network.impl.Networks;
import com.powsybl.openloadflow.util.LoadFlowAssert;
import com.powsybl.openloadflow.util.PerUnit;
import com.powsybl.openloadflow.util.report.PowsyblOpenLoadFlowReportResourceBundle;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.CompletionException;

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

/**
 * @author Sylvain Leclerc {@literal <sylvain.leclerc at rte-france.com>}
 */
class DcLoadFlowTest {

    private LoadFlowParameters parameters;

    private OpenLoadFlowParameters parametersExt;

    private OpenLoadFlowProvider loadFlowProvider;

    private LoadFlow.Runner loadFlowRunner;

    @BeforeEach
    void setUp() {
        parameters = new LoadFlowParameters()
                .setDc(true);
        parametersExt = OpenLoadFlowParameters.create(parameters)
                .setSlackBusSelectionMode(SlackBusSelectionMode.FIRST);
        loadFlowProvider = new OpenLoadFlowProvider(new DenseMatrixFactory());
        loadFlowRunner = new LoadFlow.Runner(loadFlowProvider);
    }

    /**
     * Check behaviour of the load flow for simple manipulations on eurostag example 1 network.
     * - line opening
     * - load change
     */
    @Test
    void tuto1Test() {
        Network network = EurostagFactory.fix(EurostagTutorialExample1Factory.create());
        Line line1 = network.getLine("NHV1_NHV2_1");
        Line line2 = network.getLine("NHV1_NHV2_2");

        assertEquals(Double.NaN, line1.getTerminal1().getP(), 0);
        assertEquals(Double.NaN, line1.getTerminal2().getP(), 0);
        assertEquals(Double.NaN, line2.getTerminal1().getP(), 0);
        assertEquals(Double.NaN, line2.getTerminal2().getP(), 0);

        LoadFlowResult result = loadFlowRunner.run(network, parameters);
        assertTrue(result.isFullyConverged());
        assertEquals(1, result.getComponentResults().size());
        assertEquals(-7.0, result.getComponentResults().get(0).getDistributedActivePower(), 1e-3);

        assertEquals(300, line1.getTerminal1().getP(), 0.01);
        assertEquals(-300, line1.getTerminal2().getP(), 0.01);
        assertEquals(300, line2.getTerminal1().getP(), 0.01);
        assertEquals(-300, line2.getTerminal2().getP(), 0.01);

        network.getLine("NHV1_NHV2_1").getTerminal1().disconnect();

        loadFlowRunner.run(network, parameters);

        assertTrue(Double.isNaN(line1.getTerminal1().getP()));
        assertEquals(0, line1.getTerminal2().getP(), 0);
        assertEquals(600, line2.getTerminal1().getP(), 0.01);
        assertEquals(-600, line2.getTerminal2().getP(), 0.01);

        network.getLine("NHV1_NHV2_1").getTerminal1().connect();
        network.getLine("NHV1_NHV2_1").getTerminal2().disconnect();

        loadFlowRunner.run(network, parameters);

        assertEquals(0, line1.getTerminal1().getP(), 0);
        assertTrue(Double.isNaN(line1.getTerminal2().getP()));
        assertEquals(600, line2.getTerminal1().getP(), 0.01);
        assertEquals(-600, line2.getTerminal2().getP(), 0.01);

        network.getLine("NHV1_NHV2_1").getTerminal1().disconnect();
        network.getLoad("LOAD").setP0(450);

        result = loadFlowRunner.run(network, parameters);
        assertTrue(result.isFullyConverged());
        assertEquals(1, result.getComponentResults().size());
        assertEquals(-157.0, result.getComponentResults().get(0).getDistributedActivePower(), 1e-3);

        assertTrue(Double.isNaN(line1.getTerminal1().getP()));
        assertTrue(Double.isNaN(line1.getTerminal2().getP()));
        assertEquals(450, line2.getTerminal1().getP(), 0.01);
        assertEquals(-450, line2.getTerminal2().getP(), 0.01);
    }

    @ParameterizedTest(name = "distributedSlack={0}")
    @ValueSource(booleans = {true, false})
    void testSlackDistributionEnabledDisabledResults(boolean distributedSlack) {
        Network network = EurostagFactory.fix(EurostagTutorialExample1Factory.create());

        parameters.setDistributedSlack(distributedSlack);
        LoadFlowResult result = loadFlowRunner.run(network, parameters);
        assertTrue(result.isFullyConverged());
        var componentResults = result.getComponentResults();
        assertEquals(1, componentResults.size());
        assertEquals(1, componentResults.get(0).getSlackBusResults().size());
        assertEquals(distributedSlack ? -7.0 : 0.0, componentResults.get(0).getDistributedActivePower(), 1e-3);
        assertEquals(distributedSlack ? 0.0 : -7.0, componentResults.get(0).getSlackBusResults().get(0).getActivePowerMismatch(), 1e-3);
    }

    @Test
    void fourBusesTest() {
        Network network = FourBusNetworkFactory.create();

        loadFlowRunner.run(network, parameters);

        Line l14 = network.getLine("l14");
        Line l12 = network.getLine("l12");
        Line l23 = network.getLine("l23");
        Line l34 = network.getLine("l34");
        Line l13 = network.getLine("l13");

        assertEquals(0.25, l14.getTerminal1().getP(), 0.01);
        assertEquals(-0.25, l14.getTerminal2().getP(), 0.01);
        assertEquals(0.25, l12.getTerminal1().getP(), 0.01);
        assertEquals(-0.25, l12.getTerminal2().getP(), 0.01);
        assertEquals(1.25, l23.getTerminal1().getP(), 0.01);
        assertEquals(-1.25, l23.getTerminal2().getP(), 0.01);
        assertEquals(-1.25, l34.getTerminal1().getP(), 0.01);
        assertEquals(1.25, l34.getTerminal2().getP(), 0.01);
        assertEquals(1.5, l13.getTerminal1().getP(), 0.01);
        assertEquals(-1.5, l13.getTerminal2().getP(), 0.01);
    }

    @Test
    void phaseShifterTest() {
        Network network = PhaseShifterTestCaseFactory.create();
        Line l1 = network.getLine("L1");
        Line l2 = network.getLine("L2");
        TwoWindingsTransformer ps1 = network.getTwoWindingsTransformer("PS1");
        ps1.getPhaseTapChanger().getStep(0).setAlpha(5);
        ps1.getPhaseTapChanger().getStep(2).setAlpha(5);

        loadFlowRunner.run(network, parameters);

        assertEquals(50, l1.getTerminal1().getP(), 0.01);
        assertEquals(-50, l1.getTerminal2().getP(), 0.01);
        assertEquals(50, l2.getTerminal1().getP(), 0.01);
        assertEquals(-50, l2.getTerminal2().getP(), 0.01);
        assertEquals(50, ps1.getTerminal1().getP(), 0.01);
        assertEquals(-50, ps1.getTerminal2().getP(), 0.01);

        ps1.getPhaseTapChanger().setTapPosition(2);

        loadFlowRunner.run(network, parameters);

        assertEquals(18.5, l1.getTerminal1().getP(), 0.01);
        assertEquals(-18.5, l1.getTerminal2().getP(), 0.01);
        assertEquals(81.5, l2.getTerminal1().getP(), 0.01);
        assertEquals(-81.5, l2.getTerminal2().getP(), 0.01);
        assertEquals(81.5, ps1.getTerminal1().getP(), 0.01);
        assertEquals(-81.5, ps1.getTerminal2().getP(), 0.01);

        // check we have same result if we consider phase shift as a variable with a fixed value
        loadFlowProvider.setForcePhaseControlOffAndAddAngle1Var(true);

        loadFlowRunner.run(network, parameters);

        assertEquals(18.5, l1.getTerminal1().getP(), 0.01);
        assertEquals(-18.5, l1.getTerminal2().getP(), 0.01);
        assertEquals(81.5, l2.getTerminal1().getP(), 0.01);
        assertEquals(-81.5, l2.getTerminal2().getP(), 0.01);
        assertEquals(81.5, ps1.getTerminal1().getP(), 0.01);
        assertEquals(-81.5, ps1.getTerminal2().getP(), 0.01);
    }

    @Test
    void nonImpedantBranchTest() {
        Network network = PhaseShifterTestCaseFactory.create();
        network.getLine("L2").setX(0).setR(0);
        parameters.getExtension(OpenLoadFlowParameters.class).setLowImpedanceBranchMode(OpenLoadFlowParameters.LowImpedanceBranchMode.REPLACE_BY_MIN_IMPEDANCE_LINE);
        loadFlowRunner.run(network, parameters);
        assertEquals(66.6666, network.getLine("L2").getTerminal1().getP(), 0.01);
        assertEquals(33.3333, network.getLine("L1").getTerminal1().getP(), 0.01);

        parameters.getExtension(OpenLoadFlowParameters.class).setLowImpedanceBranchMode(OpenLoadFlowParameters.LowImpedanceBranchMode.REPLACE_BY_ZERO_IMPEDANCE_LINE);
        loadFlowRunner.run(network, parameters);
        assertEquals(66.6666, network.getLine("L2").getTerminal1().getP(), 0.01);
        assertEquals(33.3333, network.getLine("L1").getTerminal1().getP(), 0.01);
    }

    @Test
        void nonImpedantBranchAndPhaseShiftingTest() {
        Network network = PhaseShifterTestCaseFactory.create();
        network.getLine("L2").setX(0).setR(0);
        network.getTwoWindingsTransformer("PS1").getPhaseTapChanger().getStep(1).setAlpha(2);
        loadFlowRunner.run(network, parameters);

        assertEquals(16.5316, network.getLine("L1").getTerminal1().getP(), 0.01);
        assertEquals(83.4683, network.getLine("L2").getTerminal1().getP(), 0.01); // Temporary comment : P without fix = 133.87
        assertEquals(-83.4683, network.getTwoWindingsTransformer("PS1").getTerminal2().getP(), 0.01);

        // With e second zero impedance line and a second load
        VoltageLevel vl2 = network.getVoltageLevel("VL2");
        vl2.getBusBreakerView().newBus()
                .setId("B2Bis")
                .add();
        vl2.newLoad()
                .setId("LD2Bis")
                .setConnectableBus("B2Bis")
                .setBus("B2Bis")
                .setP0(100.0)
                .setQ0(50.0)
                .add();
        network.newLine()
                .setId("L2Bis")
                .setVoltageLevel1("VL3")
                .setConnectableBus1("B3")
                .setBus1("B3")
                .setVoltageLevel2("VL2")
                .setConnectableBus2("B2Bis")
                .setBus2("B2Bis")
                .setR(0.0)
                .setX(0.0)
                .setG1(0.0)
                .setB1(0.0)
                .setG2(0.0)
                .setB2(0.0)
                .add();
        network.getGenerator("G1").setMaxP(500);
        LoadFlowResult result = loadFlowRunner.run(network, parameters);
        assertTrue(result.isFullyConverged());
        assertEquals(49.86, network.getLine("L1").getTerminal1().getP(), 0.01);
        assertEquals(150.13, network.getTwoWindingsTransformer("PS1").getTerminal1().getP(), 0.01);
        assertEquals(0, network.getTwoWindingsTransformer("PS1").getTerminal2().getP() + network.getLine("L2").getTerminal1().getP() + network.getLine("L2Bis").getTerminal1().getP(), 0.01); // Temporary comment : P without fix = 133.87
        assertEquals(-200, network.getGenerator("G1").getTerminal().getP());
    }

    @Test
    void multiCcTest() {
        Network network = IeeeCdfNetworkFactory.create14();
        network.getVoltageLevel("VL12").newGenerator()
                .setId("gvl12")
                .setBus("B12")
                .setConnectableBus("B12")
                .setEnergySource(EnergySource.THERMAL)
                .setMinP(0)
                .setMaxP(1)
                .setTargetP(0)
                .setTargetQ(0)
                .setVoltageRegulatorOn(false)
                .add();
        for (Line l : List.of(network.getLine("L13-14-1"),
                network.getLine("L6-13-1"),
                network.getLine("L6-12-1"))) {
            l.getTerminal1().disconnect();
            l.getTerminal2().disconnect();
        }
        // bus 12 and 13 are out of main connected component
        parameters.setConnectedComponentMode(LoadFlowParameters.ConnectedComponentMode.ALL);
        loadFlowRunner.run(network, parameters);

        // check angle is zero for the 2 slack buses
        LoadFlowAssert.assertAngleEquals(0, network.getBusView().getBus("VL1_0"));
        LoadFlowAssert.assertAngleEquals(0, network.getBusView().getBus("VL12_0"));
    }

    @Test
    void lineWithDifferentNominalVoltageTest() {

        parameters.setDcUseTransformerRatio(true);
        Network network = FourBusNetworkFactory.create();

        loadFlowRunner.run(network, parameters);

        Line l14 = network.getLine("l14");
        Line l12 = network.getLine("l12");
        Line l23 = network.getLine("l23");
        Line l34 = network.getLine("l34");
        Line l13 = network.getLine("l13");

        assertEquals(0.25, l14.getTerminal1().getP(), 0.01);
        assertEquals(-0.25, l14.getTerminal2().getP(), 0.01);
        assertEquals(0.25, l12.getTerminal1().getP(), 0.01);
        assertEquals(-0.25, l12.getTerminal2().getP(), 0.01);
        assertEquals(1.25, l23.getTerminal1().getP(), 0.01);
        assertEquals(-1.25, l23.getTerminal2().getP(), 0.01);
        assertEquals(-1.25, l34.getTerminal1().getP(), 0.01);
        assertEquals(1.25, l34.getTerminal2().getP(), 0.01);
        assertEquals(1.5, l13.getTerminal1().getP(), 0.01);
        assertEquals(-1.5, l13.getTerminal2().getP(), 0.01);

        network.getBusBreakerView().getBus("b1").getVoltageLevel().setNominalV(2d);
        loadFlowRunner.run(network, parameters);
        assertEquals(0d, l14.getTerminal1().getP(), 0.01);
        assertEquals(0d, l14.getTerminal2().getP(), 0.01);
        assertEquals(0d, l12.getTerminal1().getP(), 0.01);
        assertEquals(0d, l12.getTerminal2().getP(), 0.01);
        assertEquals(1d, l23.getTerminal1().getP(), 0.01);
        assertEquals(-1d, l23.getTerminal2().getP(), 0.01);
        assertEquals(-1d, l34.getTerminal1().getP(), 0.01);
        assertEquals(1d, l34.getTerminal2().getP(), 0.01);
        assertEquals(2d, l13.getTerminal1().getP(), 0.01);
        assertEquals(-2d, l13.getTerminal2().getP(), 0.01);
    }

    @Test
    void shuntCompensatorActivePowerZero() {
        Network network = EurostagFactory.fix(EurostagTutorialExample1Factory.create());
        var sc = network.getVoltageLevel("VLLOAD").newShuntCompensator()
                .setId("SC")
                .setBus("NLOAD")
                .setSectionCount(1)
                .newLinearModel()
                .setBPerSection(0.111)
                .setMaximumSectionCount(1)
                .add()
                .add();
        loadFlowRunner.run(network, parameters);
        assertActivePowerEquals(0, sc.getTerminal());
    }

    @Test
    void testDisabledNonImpedantBranch() {
        Network network = NodeBreakerNetworkFactory.create3Bars();
        Switch c1 = network.getSwitch("C1");
        c1.setOpen(true);

        parameters = new LoadFlowParameters()
                .setDc(true);
        loadFlowRunner.run(network, parameters);

        assertActivePowerEquals(400.0, network.getLine("L1").getTerminal1());
        assertActivePowerEquals(100.0, network.getLine("L2").getTerminal1());
        assertActivePowerEquals(100.0, network.getLine("L3").getTerminal1());

        LfNetworkParameters lfNetworkParameters = new LfNetworkParameters()
                .setLoadFlowModel(LoadFlowModel.DC)
                .setBreakers(true);
        DcLoadFlowParameters dcLoadFlowParameters = new DcLoadFlowParameters()
                .setNetworkParameters(lfNetworkParameters)
                .setMatrixFactory(new DenseMatrixFactory())
                .setDistributedSlack(true)
                .setBalanceType(parameters.getBalanceType())
                .setSetVToNan(false)
                .setMaxOuterLoopIterations(1);
        LfTopoConfig topoConfig = new LfTopoConfig();
        topoConfig.getSwitchesToClose().add(c1);
        try (LfNetworkList lfNetworks = Networks.load(network, lfNetworkParameters, topoConfig, ReportNode.NO_OP)) {
            LfNetwork largestNetwork = lfNetworks.getLargest().orElseThrow();
            largestNetwork.getBranchById("C1").setDisabled(true);
            try (DcLoadFlowContext context = new DcLoadFlowContext(largestNetwork, dcLoadFlowParameters)) {
                new DcLoadFlowEngine(context).run();
            }
            // should be the same as with previous LF
            assertEquals(400.0, largestNetwork.getBranchById("L1").getP1().eval() * PerUnit.SB, LoadFlowAssert.DELTA_POWER);
            assertEquals(100.0, largestNetwork.getBranchById("L2").getP1().eval() * PerUnit.SB, LoadFlowAssert.DELTA_POWER);
            assertEquals(100.0, largestNetwork.getBranchById("L3").getP1().eval() * PerUnit.SB, LoadFlowAssert.DELTA_POWER);
        }
    }

    @Test
    void outerLoopPhaseShifterTest() {
        Network network = PhaseShifterTestCaseFactory.create();
        Line l1 = network.getLine("L1");
        Line l2 = network.getLine("L2");
        TwoWindingsTransformer ps1 = network.getTwoWindingsTransformer("PS1");
        ps1.getPhaseTapChanger().getStep(0).setAlpha(-5);
        ps1.getPhaseTapChanger().getStep(2).setAlpha(5);
        ps1.getPhaseTapChanger().setTargetDeadband(10);
        ps1.getPhaseTapChanger().setRegulationMode(PhaseTapChanger.RegulationMode.ACTIVE_POWER_CONTROL);
        ps1.getPhaseTapChanger().setRegulating(true);

        parameters.setPhaseShifterRegulationOn(false);

        loadFlowRunner.run(network, parameters);

        assertEquals(50, l1.getTerminal1().getP(), 0.01);
        assertEquals(-50, l1.getTerminal2().getP(), 0.01);
        assertEquals(50, l2.getTerminal1().getP(), 0.01);
        assertEquals(-50, l2.getTerminal2().getP(), 0.01);
        assertEquals(50, ps1.getTerminal1().getP(), 0.01);
        assertEquals(-50, ps1.getTerminal2().getP(), 0.01);

        parameters.setPhaseShifterRegulationOn(true);
        ps1.getPhaseTapChanger().setRegulationValue(-80);

        loadFlowRunner.run(network, parameters);

        assertEquals(2, ps1.getPhaseTapChanger().getTapPosition());
        assertEquals(18.5, l1.getTerminal1().getP(), 0.01);
        assertEquals(-18.5, l1.getTerminal2().getP(), 0.01);
        assertEquals(81.5, l2.getTerminal1().getP(), 0.01);
        assertEquals(-81.5, l2.getTerminal2().getP(), 0.01);
        assertEquals(81.5, ps1.getTerminal1().getP(), 0.01);
        assertEquals(-81.5, ps1.getTerminal2().getP(), 0.01);

        ps1.getPhaseTapChanger().setRegulationTerminal(ps1.getTerminal1());
        ps1.getPhaseTapChanger().setTapPosition(0);
        ps1.getPhaseTapChanger().setRegulationValue(50);

        loadFlowRunner.run(network, parameters);

        assertEquals(1, ps1.getPhaseTapChanger().getTapPosition());
        assertEquals(50, l1.getTerminal1().getP(), 0.01);
        assertEquals(-50, l1.getTerminal2().getP(), 0.01);
        assertEquals(50, l2.getTerminal1().getP(), 0.01);
        assertEquals(-50, l2.getTerminal2().getP(), 0.01);
        assertEquals(50, ps1.getTerminal1().getP(), 0.01);
        assertEquals(-50, ps1.getTerminal2().getP(), 0.01);
    }

    @Test
    void multipleOuterLoopsTest() {
        Network network = MultiAreaNetworkFactory.createTwoAreasWithPhaseShifter();
        Line l1 = network.getLine("L1");
        Line l2 = network.getLine("L2");
        Area a1 = network.getArea("A1");
        Area a2 = network.getArea("A2");
        TwoWindingsTransformer ps1 = network.getTwoWindingsTransformer("PS1");

        parameters.setPhaseShifterRegulationOn(true);
        parametersExt.setAreaInterchangeControl(true);
        parametersExt.setAreaInterchangePMaxMismatch(1);

        var result = loadFlowRunner.run(network, parameters);

        assertTrue(result.isFullyConverged());
        assertEquals(0.0, result.getComponentResults().get(0).getSlackBusResults().get(0).getActivePowerMismatch(), 1e-3);
        assertEquals(a1.getInterchangeTarget().orElseThrow(), a1.getInterchange(), 1);
        assertEquals(a2.getInterchangeTarget().orElseThrow(), a2.getInterchange(), 1);

        assertEquals(17.86, l1.getTerminal1().getP(), 0.01);
        assertEquals(-17.86, l1.getTerminal2().getP(), 0.01);
        assertEquals(80.87, l2.getTerminal1().getP(), 0.01);
        assertEquals(-80.87, l2.getTerminal2().getP(), 0.01);
        assertEquals(80.87, ps1.getTerminal1().getP(), 0.01);
        assertEquals(-80.87, ps1.getTerminal2().getP(), 0.01);
    }

    @Test
    void outerLoopFailedTest() {
        Network network = MultiAreaNetworkFactory.createOneAreaBase();

        Generator g1 = network.getGenerator("g1");
        g1.setMinP(99); // makes the power distribution fail

        parameters.setPhaseShifterRegulationOn(true);
        parametersExt.setAreaInterchangeControl(true);
        parametersExt.setAreaInterchangePMaxMismatch(1)
                .setSlackDistributionFailureBehavior(OpenLoadFlowParameters.SlackDistributionFailureBehavior.FAIL);

        var result = loadFlowRunner.run(network, parameters);
        assertFalse(result.isFullyConverged());

        assertEquals(LoadFlowResult.ComponentResult.Status.FAILED, result.getComponentResults().get(0).getStatus());
        assertEquals("Outer loop failed: Failed to distribute interchange active power mismatch", result.getComponentResults().get(0).getStatusText());
    }

    @Test
    void outerLoopMaxTotalIterationTest() throws IOException {
        Network network = MultiAreaNetworkFactory.createTwoAreasWithPhaseShifter();
        parameters.setPhaseShifterRegulationOn(true);
        parametersExt.setAreaInterchangeControl(true);

        // For this case, AIC outer loop needs 3 iterations to be stable, phase control needs 1.
        parametersExt.setAreaInterchangePMaxMismatch(1)
                .setMaxOuterLoopIterations(1);

        ReportNode reportNodeWithLimit1 = ReportNode.newRootReportNode()
                .withResourceBundles(PowsyblOpenLoadFlowReportResourceBundle.BASE_NAME, PowsyblCoreTestReportResourceBundle.TEST_BASE_NAME)
                .withMessageTemplate("test")
                .build();

        var result = loadFlowRunner.run(network, network.getVariantManager().getWorkingVariantId(), LocalComputationManager.getDefault(), parameters, reportNodeWithLimit1);
        assertFalse(result.isFullyConverged());
        assertEquals(LoadFlowResult.ComponentResult.Status.MAX_ITERATION_REACHED, result.getComponentResults().get(0).getStatus());
        assertEquals("Reached outer loop max iterations limit. Last outer loop name: IncrementalPhaseControl", result.getComponentResults().get(0).getStatusText());

        parametersExt.setMaxOuterLoopIterations(3);
        result = loadFlowRunner.run(network, parameters);
        assertFalse(result.isFullyConverged());
        assertEquals(LoadFlowResult.ComponentResult.Status.MAX_ITERATION_REACHED, result.getComponentResults().get(0).getStatus());
        assertEquals("Reached outer loop max iterations limit. Last outer loop name: AreaInterchangeControl", result.getComponentResults().get(0).getStatusText());

        // Test the report
        String expected = """
                + test
                   + Load flow on network 'phaseShifterTestCase'
                      + Network CC0 SC0
                         + Network info
                            Network has 3 buses and 3 branches
                            Network balance: active generation=140 MW, active load=140 MW, reactive generation=0 MVar, reactive load=55 MVar
                            Angle reference bus: VL1_0
                            Slack bus: VL1_0
                         Slack bus active power (-0 MW) distributed in 0 distribution iteration(s)
                         + Outer loop IncrementalPhaseControl
                            Outer loop unsuccessful with status: UNSTABLE
                         Maximum number of outerloop iterations reached: 1
                         DC load flow completed (solverSuccess=true, outerloopStatus=UNSTABLE)
                """;

        assertReportEquals(new ByteArrayInputStream(expected.getBytes()), reportNodeWithLimit1);
    }

    @Test
    void testDcApproxIgnoreG() {
        Network network = EurostagFactory.fix(EurostagTutorialExample1Factory.create());
        Line line1 = network.getLine("NHV1_NHV2_1");
        // to get asymmetric flows
        line1.setR(line1.getR() * 1.1);
        line1.setX(line1.getX() * 1.05);
        Line line2 = network.getLine("NHV1_NHV2_2");

        loadFlowRunner.run(network, parameters);

        assertEquals(292.682, line1.getTerminal1().getP(), 0.01);
        assertEquals(-292.682, line1.getTerminal2().getP(), 0.01);
        assertEquals(307.317, line2.getTerminal1().getP(), 0.01);
        assertEquals(-307.317, line2.getTerminal2().getP(), 0.01);

        parametersExt.setDcApproximationType(DcApproximationType.IGNORE_G);
        loadFlowRunner.run(network, parameters);

        assertEquals(292.563, line1.getTerminal1().getP(), 0.01);
        assertEquals(-292.563, line1.getTerminal2().getP(), 0.01);
        assertEquals(307.436, line2.getTerminal1().getP(), 0.01);
        assertEquals(-307.436, line2.getTerminal2().getP(), 0.01);
    }

    @Test
    void testDcResidualMismatchRemaining() {
        Network network = IeeeCdfNetworkFactory.create9();
        network.getGenerator("B1-G").setTargetP(67.99); // Setting target P to have an initially almost balanced network
        ReportNode reportNode = ReportNode.newRootReportNode().withMessageTemplate("test").build();
        System.out.println(network.getGenerator("B1-G").getId() + " has P = " + network.getGenerator("B1-G").getTargetP());
        loadFlowRunner.run(network, network.getVariantManager().getWorkingVariantId(), LocalComputationManager.getDefault(), parameters, reportNode);
        // DC loadflow succeeds but the initial residual mismatch still remains
        assertReportContains("Remaining residual slack bus active power mismatch after active power distribution, [-+]?0\\.\\d* MW remains", reportNode);
    }

    @Test
    void testDcSlackDistributionFailureBehavior() {
        Network network = IeeeCdfNetworkFactory.create57();
        parameters.setBalanceType(LoadFlowParameters.BalanceType.PROPORTIONAL_TO_GENERATION_P);
        Generator referenceGenerator = network.getGenerator("B1-G");

        parametersExt.setSlackDistributionFailureBehavior(OpenLoadFlowParameters.SlackDistributionFailureBehavior.LEAVE_ON_SLACK_BUS);
        ReportNode reportNode = ReportNode.newRootReportNode().withMessageTemplate("test").build();
        var result = loadFlowRunner.run(network, network.getVariantManager().getWorkingVariantId(), LocalComputationManager.getDefault(), parameters, reportNode);
        assertTrue(result.isFullyConverged());
        assertEquals(1, result.getComponentResults().size());
        assertEquals(LoadFlowResult.ComponentResult.Status.CONVERGED, result.getComponentResults().get(0).getStatus());
        assertEquals(321.9, result.getComponentResults().get(0).getSlackBusResults().get(0).getActivePowerMismatch(), 0.01);
        assertEquals(0, result.getComponentResults().get(0).getDistributedActivePower(), 0.01);
        assertReportContains("Failed to distribute slack bus active power mismatch, [-+]?321\\.\\d* MW remains", reportNode);

        parametersExt.setSlackDistributionFailureBehavior(OpenLoadFlowParameters.SlackDistributionFailureBehavior.FAIL);
        reportNode = ReportNode.newRootReportNode().withMessageTemplate("test").build();
        result = loadFlowRunner.run(network, network.getVariantManager().getWorkingVariantId(), LocalComputationManager.getDefault(), parameters, reportNode);
        assertFalse(result.isFullyConverged());
        assertEquals(1, result.getComponentResults().size());
        assertEquals(LoadFlowResult.ComponentResult.Status.FAILED, result.getComponentResults().get(0).getStatus());
        assertEquals("Outer loop failed: Failed to distribute slack bus active power mismatch, 321.90 MW remains", result.getComponentResults().get(0).getStatusText());
        assertEquals(321.9, result.getComponentResults().get(0).getSlackBusResults().get(0).getActivePowerMismatch(), 0.01);
        assertEquals(0, result.getComponentResults().get(0).getDistributedActivePower(), 0.01);
        assertReportContains("Failed to distribute slack bus active power mismatch, [-+]?321\\.\\d* MW remains", reportNode);

        parametersExt.setSlackDistributionFailureBehavior(OpenLoadFlowParameters.SlackDistributionFailureBehavior.THROW);
        CompletionException e = assertThrows(CompletionException.class, () -> loadFlowRunner.run(network, parameters));
        assertEquals("Failed to distribute slack bus active power mismatch, 321.90 MW remains", e.getCause().getMessage());

        parametersExt.setSlackDistributionFailureBehavior(OpenLoadFlowParameters.SlackDistributionFailureBehavior.DISTRIBUTE_ON_REFERENCE_GENERATOR);
        parametersExt.setReferenceBusSelectionMode(ReferenceBusSelectionMode.GENERATOR_REFERENCE_PRIORITY);
        reportNode = ReportNode.newRootReportNode().withMessageTemplate("test").build();
        result = loadFlowRunner.run(network, network.getVariantManager().getWorkingVariantId(), LocalComputationManager.getDefault(), parameters, reportNode);
        assertEquals(0, result.getComponentResults().get(0).getSlackBusResults().get(0).getActivePowerMismatch(), 0.01);
        assertEquals(321.9, result.getComponentResults().get(0).getDistributedActivePower(), 0.01);
        assertActivePowerEquals(-450.8, referenceGenerator.getTerminal()); // -128.9 - 321.9 = -450.8
        assertReportContains("Slack bus active power \\([-+]?321\\.\\d* MW\\) distributed in 1 distribution iteration\\(s\\)", reportNode);
    }
}