OpenLoadFlowParametersTest.java

/*
 * Copyright (c) 2020-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;

import com.google.common.jimfs.Configuration;
import com.google.common.jimfs.Jimfs;
import com.powsybl.commons.PowsyblException;
import com.powsybl.commons.config.InMemoryPlatformConfig;
import com.powsybl.commons.config.MapModuleConfig;
import com.powsybl.commons.config.PlatformConfig;
import com.powsybl.commons.config.YamlModuleConfigRepository;
import com.powsybl.commons.parameters.Parameter;
import com.powsybl.commons.parameters.ParameterType;
import com.powsybl.iidm.network.Network;
import com.powsybl.iidm.network.extensions.SlackTerminal;
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.graph.EvenShiloachGraphDecrementalConnectivityFactory;
import com.powsybl.openloadflow.lf.outerloop.OuterLoop;
import com.powsybl.openloadflow.lf.outerloop.config.ExplicitAcOuterLoopConfig;
import com.powsybl.openloadflow.network.*;
import com.powsybl.openloadflow.network.impl.Networks;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.io.IOException;
import java.nio.file.FileSystem;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

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

/**
 * @author J��r��my Labous {@literal <jlabous at silicom.fr>}
 */
class OpenLoadFlowParametersTest {

    private InMemoryPlatformConfig platformConfig;

    private FileSystem fileSystem;

    public static final double DELTA_MISMATCH = 1E-4d;

    @BeforeEach
    public void setUp() {
        fileSystem = Jimfs.newFileSystem(Configuration.unix());
        platformConfig = new InMemoryPlatformConfig(fileSystem);

        MapModuleConfig lfModuleConfig = platformConfig.createModuleConfig("load-flow-default-parameters");
        lfModuleConfig.setStringProperty("voltageInitMode", LoadFlowParameters.VoltageInitMode.DC_VALUES.toString());
        lfModuleConfig.setStringProperty("transformerVoltageControlOn", Boolean.toString(true));
        lfModuleConfig.setStringProperty("balanceType", LoadFlowParameters.BalanceType.PROPORTIONAL_TO_LOAD.toString());
        lfModuleConfig.setStringProperty("dc", Boolean.toString(true));
    }

    @AfterEach
    public void tearDown() throws IOException {
        fileSystem.close();
    }

    @Test
    void testConfig() {
        MapModuleConfig olfModuleConfig = platformConfig.createModuleConfig("open-loadflow-default-parameters");
        olfModuleConfig.setStringProperty("slackBusSelectionMode", "FIRST");

        LoadFlowParameters parameters = LoadFlowParameters.load(platformConfig);

        assertEquals(LoadFlowParameters.VoltageInitMode.DC_VALUES, parameters.getVoltageInitMode());
        assertTrue(parameters.isTransformerVoltageControlOn());
        assertEquals(LoadFlowParameters.BalanceType.PROPORTIONAL_TO_LOAD, parameters.getBalanceType());
        assertTrue(parameters.isDc());
        assertTrue(parameters.isDistributedSlack());

        OpenLoadFlowParameters olfParameters = parameters.getExtension(OpenLoadFlowParameters.class);
        assertEquals(SlackBusSelectionMode.FIRST, olfParameters.getSlackBusSelectionMode());
        assertEquals(OpenLoadFlowParameters.SLACK_DISTRIBUTION_FAILURE_BEHAVIOR_DEFAULT_VALUE, olfParameters.getSlackDistributionFailureBehavior());
        assertTrue(olfParameters.isVoltageRemoteControl());
        assertEquals(OpenLoadFlowParameters.LOW_IMPEDANCE_BRANCH_MODE_DEFAULT_VALUE, olfParameters.getLowImpedanceBranchMode());
        assertEquals(LfNetworkParameters.MIN_PLAUSIBLE_TARGET_VOLTAGE_DEFAULT_VALUE, olfParameters.getMinPlausibleTargetVoltage());
        assertEquals(LfNetworkParameters.MAX_PLAUSIBLE_TARGET_VOLTAGE_DEFAULT_VALUE, olfParameters.getMaxPlausibleTargetVoltage());
        assertEquals(LfNetworkParameters.SLACK_BUS_COUNTRY_FILTER_DEFAULT_VALUE, olfParameters.getSlackBusCountryFilter());
    }

    @Test
    void testDefaultOpenLoadflowConfig() {
        LoadFlowParameters parameters = LoadFlowParameters.load(platformConfig);

        assertEquals(LoadFlowParameters.VoltageInitMode.DC_VALUES, parameters.getVoltageInitMode());
        assertTrue(parameters.isTransformerVoltageControlOn());
        assertEquals(LoadFlowParameters.BalanceType.PROPORTIONAL_TO_LOAD, parameters.getBalanceType());
        assertTrue(parameters.isDc());
        assertTrue(parameters.isDistributedSlack());

        OpenLoadFlowParameters olfParameters = parameters.getExtension(OpenLoadFlowParameters.class);
        assertEquals(OpenLoadFlowParameters.SLACK_BUS_SELECTION_MODE_DEFAULT_VALUE, olfParameters.getSlackBusSelectionMode());
        assertEquals(OpenLoadFlowParameters.VOLTAGE_REMOTE_CONTROL_DEFAULT_VALUE, olfParameters.isVoltageRemoteControl());
        assertEquals(OpenLoadFlowParameters.LOW_IMPEDANCE_BRANCH_MODE_DEFAULT_VALUE, olfParameters.getLowImpedanceBranchMode());
        assertEquals(OpenLoadFlowParameters.SLACK_DISTRIBUTION_FAILURE_BEHAVIOR_DEFAULT_VALUE, olfParameters.getSlackDistributionFailureBehavior());
        assertEquals(OpenLoadFlowParameters.SLACK_BUS_P_MAX_MISMATCH_DEFAULT_VALUE, olfParameters.getSlackBusPMaxMismatch(), 0.0);
        assertEquals(OpenLoadFlowParameters.GENERATOR_REACTIVE_POWER_REMOTE_CONTROL_DEFAULT_VALUE, olfParameters.isGeneratorReactivePowerRemoteControl());
    }

    @Test
    void testInvalidOpenLoadflowConfig() {
        MapModuleConfig olfModuleConfig = platformConfig.createModuleConfig("open-loadflow-default-parameters");
        olfModuleConfig.setStringProperty("slackBusSelectionMode", "Invalid");

        IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> LoadFlowParameters.load(platformConfig));
        assertEquals("No enum constant com.powsybl.openloadflow.network.SlackBusSelectionMode.Invalid", exception.getMessage());
    }

    @Test
    void testInvalidOpenLoadflowConfigNewtonRaphson() {
        MapModuleConfig olfModuleConfig = platformConfig.createModuleConfig("open-loadflow-default-parameters");
        olfModuleConfig.setStringProperty("newtonRaphsonStoppingCriteriaType", "Invalid");

        IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> LoadFlowParameters.load(platformConfig));
        assertEquals("No enum constant com.powsybl.openloadflow.ac.solver.NewtonRaphsonStoppingCriteriaType.Invalid", exception.getMessage());

        OpenLoadFlowParameters openLoadFlowParameters = OpenLoadFlowParameters.create(new LoadFlowParameters());
        assertThrows(IllegalArgumentException.class, () -> openLoadFlowParameters.setMaxAngleMismatch(-1));
        assertThrows(IllegalArgumentException.class, () -> openLoadFlowParameters.setMaxVoltageMismatch(-1));
        assertThrows(IllegalArgumentException.class, () -> openLoadFlowParameters.setMaxRatioMismatch(-1));
        assertThrows(IllegalArgumentException.class, () -> openLoadFlowParameters.setMaxActivePowerMismatch(-1));
        assertThrows(IllegalArgumentException.class, () -> openLoadFlowParameters.setMaxReactivePowerMismatch(-1));
    }

    @Test
    void testConfigSpecificParameters() {
        MapModuleConfig olfModuleConfig = platformConfig.createModuleConfig(MODULE_SPECIFIC_PARAMETERS);
        olfModuleConfig.setStringProperty("slackBusSelectionMode", "FIRST");

        List<Parameter> olfParameters = new OpenLoadFlowProvider().getSpecificParameters(platformConfig);

        assertEquals(SlackBusSelectionMode.FIRST.toString(), olfParameters.get(0).getDefaultValue());
    }

    @Test
    void testFirstSlackBusSelector() throws IOException {
        Path cfgDir = Files.createDirectory(fileSystem.getPath("config"));
        Path cfgFile = cfgDir.resolve("configFirstSlackBusSelector.yml");

        Files.copy(getClass().getResourceAsStream("/configFirstSlackBusSelector.yml"), cfgFile);
        PlatformConfig platformConfig = new PlatformConfig(new YamlModuleConfigRepository(cfgFile), cfgDir);

        LoadFlowParameters parameters = LoadFlowParameters.load(platformConfig);
        OpenLoadFlowParameters olfParameters = parameters.getExtension(OpenLoadFlowParameters.class);
        assertEquals(SlackBusSelectionMode.FIRST, olfParameters.getSlackBusSelectionMode());
    }

    @Test
    void testMostMeshedSlackBusSelector() throws IOException {
        Path cfgDir = Files.createDirectory(fileSystem.getPath("config"));
        Path cfgFile = cfgDir.resolve("configMostMeshedSlackBusSelector.yml");

        Files.copy(getClass().getResourceAsStream("/configMostMeshedSlackBusSelector.yml"), cfgFile);
        PlatformConfig platformConfig = new PlatformConfig(new YamlModuleConfigRepository(cfgFile), cfgDir);

        LoadFlowParameters parameters = LoadFlowParameters.load(platformConfig);
        OpenLoadFlowParameters olfParameters = parameters.getExtension(OpenLoadFlowParameters.class);
        assertEquals(SlackBusSelectionMode.MOST_MESHED, olfParameters.getSlackBusSelectionMode());
    }

    @Test
    void testNameSlackBusSelector() throws IOException {
        Path cfgDir = Files.createDirectory(fileSystem.getPath("config"));
        Path cfgFile = cfgDir.resolve("configNameSlackBusSelector.yml");

        Files.copy(getClass().getResourceAsStream("/configNameSlackBusSelector.yml"), cfgFile);
        PlatformConfig platformConfig = new PlatformConfig(new YamlModuleConfigRepository(cfgFile), cfgDir);

        LoadFlowParameters parameters = LoadFlowParameters.load(platformConfig);
        OpenLoadFlowParameters olfParameters = parameters.getExtension(OpenLoadFlowParameters.class);
        assertEquals(SlackBusSelectionMode.NAME, olfParameters.getSlackBusSelectionMode());
        SlackBusSelector slackBusSelector = SlackBusSelector.fromMode(olfParameters.getSlackBusSelectionMode(), olfParameters.getSlackBusesIds(), olfParameters.getPlausibleActivePowerLimit(),
                olfParameters.getMostMeshedSlackBusSelectorMaxNominalVoltagePercentile(), Collections.emptySet());
        List<LfNetwork> lfNetworks = Networks.load(EurostagFactory.fix(EurostagTutorialExample1Factory.create()), slackBusSelector);
        LfNetwork lfNetwork = lfNetworks.get(0);
        assertEquals("VLHV1_0", lfNetwork.getSlackBus().getId()); // fallback to automatic method.
    }

    @Test
    void testMaxIterationReached() {
        LoadFlowParameters parameters = LoadFlowParameters.load();
        parameters.setWriteSlackBus(true);
        Network network = EurostagFactory.fix(EurostagTutorialExample1Factory.create());
        LoadFlow.Runner loadFlowRunner = new LoadFlow.Runner(new OpenLoadFlowProvider(new DenseMatrixFactory()));

        // Change the nominal voltage to have a target V distant enough but still plausible (in [0.8 1.2] in Pu), so that the NR diverges
        network.getVoltageLevel("VLGEN").setNominalV(100);
        network.getGenerator("GEN").setTargetV(120);

        LoadFlowResult result = loadFlowRunner.run(network, parameters);
        assertEquals(LoadFlowResult.ComponentResult.Status.MAX_ITERATION_REACHED, result.getComponentResults().get(0).getStatus());
    }

    @Test
    void testIsWriteSlackBus() {
        LoadFlowParameters parameters = LoadFlowParameters.load();
        parameters.setWriteSlackBus(true);
        Network network = EurostagFactory.fix(EurostagTutorialExample1Factory.create());
        LoadFlow.Runner loadFlowRunner = new LoadFlow.Runner(new OpenLoadFlowProvider(new DenseMatrixFactory()));
        LoadFlowResult result = loadFlowRunner.run(network, parameters);
        assertEquals(network.getVoltageLevel("VLHV1").getExtension(SlackTerminal.class).getTerminal().getBusView().getBus().getId(),
                result.getComponentResults().get(0).getSlackBusResults().get(0).getId());
    }

    @Test
    void testSetSlackBusPMaxMismatch() {
        LoadFlowParameters parameters = LoadFlowParameters.load();
        Network network = EurostagFactory.fix(EurostagTutorialExample1Factory.create());
        LoadFlow.Runner loadFlowRunner = new LoadFlow.Runner(new OpenLoadFlowProvider(new DenseMatrixFactory()));
        LoadFlowResult result = loadFlowRunner.run(network, parameters);
        assertEquals(-0.004, result.getComponentResults().get(0).getSlackBusResults().get(0).getActivePowerMismatch(), DELTA_MISMATCH);

        parameters.getExtension(OpenLoadFlowParameters.class).setSlackBusPMaxMismatch(0.0001);
        LoadFlow.Runner loadFlowRunner2 = new LoadFlow.Runner(new OpenLoadFlowProvider(new DenseMatrixFactory()));
        LoadFlowResult result2 = loadFlowRunner2.run(network, parameters);
        assertEquals(-1.8703e-5, result2.getComponentResults().get(0).getSlackBusResults().get(0).getActivePowerMismatch(), DELTA_MISMATCH);
    }

    @Test
    void testPlausibleTargetVoltage() {
        LoadFlowParameters parameters = LoadFlowParameters.load();
        Network network = EurostagFactory.fix(EurostagTutorialExample1Factory.create());
        network.getGenerator("GEN").setTargetV(30.0);
        LoadFlow.Runner loadFlowRunner = new LoadFlow.Runner(new OpenLoadFlowProvider(new DenseMatrixFactory()));
        loadFlowRunner.run(network, parameters);
        assertTrue(Double.isNaN(network.getGenerator("GEN").getRegulatingTerminal().getBusView().getBus().getV())); // no calculation
        parameters.getExtension(OpenLoadFlowParameters.class).setMaxPlausibleTargetVoltage(1.3);
        loadFlowRunner.run(network, parameters);
        assertEquals(30.0, network.getGenerator("GEN").getRegulatingTerminal().getBusView().getBus().getV(), DELTA_MISMATCH);
    }

    @Test
    void testLowImpedanceThreshold() throws IOException {
        Path cfgDir = Files.createDirectory(fileSystem.getPath("config"));
        Path cfgFile = cfgDir.resolve("configLowImpedanceThreshold.yml");

        Files.copy(getClass().getResourceAsStream("/configLowImpedanceThreshold.yml"), cfgFile);
        PlatformConfig platformConfig = new PlatformConfig(new YamlModuleConfigRepository(cfgFile), cfgDir);

        LoadFlowParameters parameters = LoadFlowParameters.load(platformConfig);
        OpenLoadFlowParameters olfParameters = parameters.getExtension(OpenLoadFlowParameters.class);
        assertEquals(1.0E-2, olfParameters.getLowImpedanceThreshold());
    }

    @Test
    void testAlwaysUpdateNetwork() {
        LoadFlowParameters parameters = new LoadFlowParameters()
                .setTransformerVoltageControlOn(true)
                .setDistributedSlack(false);

        OpenLoadFlowParameters olfParameters = OpenLoadFlowParameters.create(parameters)
                .setSlackBusSelectionMode(SlackBusSelectionMode.FIRST)
                .setMaxNewtonRaphsonIterations(2); // Force final status of following run to be MAX_ITERATION_REACHED
        assertFalse(olfParameters.isAlwaysUpdateNetwork()); // Default value of alwaysUpdateNetwork

        // Check the network is not updated if alwaysUpdateNetwork = false and final status = MAX_ITERATION_REACHED
        Network network = EurostagFactory.fix(EurostagTutorialExample1Factory.create());
        var nload = network.getBusBreakerView().getBus("NLOAD");
        LoadFlow.Runner loadFlowRunner = new LoadFlow.Runner(new OpenLoadFlowProvider(new DenseMatrixFactory()));
        loadFlowRunner.run(network, parameters);
        assertTrue(Double.isNaN(nload.getV()));

        // Check the network is updated if alwaysUpdateNetwork = true and final status = MAX_ITERATION_REACHED
        olfParameters.setAlwaysUpdateNetwork(true);
        assertTrue(olfParameters.isAlwaysUpdateNetwork());

        loadFlowRunner.run(network, parameters);
        assertVoltageEquals(158, nload);
    }

    @Test
    void testUpdateParametersFromPlatformConfig() {
        LoadFlowParameters parameters = new LoadFlowParameters();
        OpenLoadFlowParameters olfParameters = OpenLoadFlowParameters.create(parameters);

        assertEquals(SlackBusSelectionMode.MOST_MESHED, olfParameters.getSlackBusSelectionMode());
        assertEquals(SlackDistributionFailureBehavior.LEAVE_ON_SLACK_BUS, olfParameters.getSlackDistributionFailureBehavior());

        MapModuleConfig olfModuleConfig = platformConfig.createModuleConfig(MODULE_SPECIFIC_PARAMETERS);
        olfModuleConfig.setStringProperty(SLACK_BUS_SELECTION_MODE_PARAM_NAME, SlackBusSelectionMode.FIRST.toString());
        olfParameters.update(platformConfig);

        assertEquals(SlackBusSelectionMode.FIRST, olfParameters.getSlackBusSelectionMode());
        assertEquals(SlackDistributionFailureBehavior.LEAVE_ON_SLACK_BUS, olfParameters.getSlackDistributionFailureBehavior());
    }

    @Test
    void testUpdateParameters() {
        Map<String, String> parametersMap = new HashMap<>();
        parametersMap.put("slackBusSelectionMode", "FIRST");
        parametersMap.put("voltageRemoteControl", "true");
        parametersMap.put("reactivePowerRemoteControl", "false");
        OpenLoadFlowParameters parameters = OpenLoadFlowParameters.load(parametersMap);
        assertEquals(SlackBusSelectionMode.FIRST, parameters.getSlackBusSelectionMode());
        assertTrue(parameters.isVoltageRemoteControl());
        assertFalse(parameters.isGeneratorReactivePowerRemoteControl());
        Map<String, String> updateParametersMap = new HashMap<>();
        updateParametersMap.put("slackBusSelectionMode", "MOST_MESHED");
        updateParametersMap.put("voltageRemoteControl", "false");
        updateParametersMap.put("maxNewtonRaphsonIterations", "10");
        parameters.update(updateParametersMap);
        assertEquals(SlackBusSelectionMode.MOST_MESHED, parameters.getSlackBusSelectionMode());
        assertFalse(parameters.isVoltageRemoteControl());
        assertEquals(10, parameters.getMaxNewtonRaphsonIterations());
        assertFalse(parameters.isGeneratorReactivePowerRemoteControl());
    }

    @Test
    void testParametersWithConfigAndLoader() {
        OLFDefaultParametersLoaderMock loader = new OLFDefaultParametersLoaderMock("test");

        MapModuleConfig moduleConfig = platformConfig.createModuleConfig("open-loadflow-default-parameters");
        moduleConfig.setStringProperty("maxOuterLoopIterations", "50");

        LoadFlowParameters parameters = new LoadFlowParameters(List.of(loader), platformConfig);
        OpenLoadFlowParameters olfParameters = parameters.getExtensionByName("open-load-flow-parameters");
        assertNotNull(olfParameters);
        assertEquals(SlackDistributionFailureBehavior.FAIL, olfParameters.getSlackDistributionFailureBehavior());
        assertEquals(30, olfParameters.getMaxOuterLoopIterations());

        olfParameters.update(platformConfig);
        assertEquals(SlackDistributionFailureBehavior.FAIL, olfParameters.getSlackDistributionFailureBehavior());
        assertEquals(50, olfParameters.getMaxOuterLoopIterations());

    }

    @Test
    void updateEmptyStringListParametersIssue() {
        Map<String, String> updateParametersMap = new HashMap<>();
        updateParametersMap.put("reportedFeatures", "");
        OpenLoadFlowParameters parameters = new OpenLoadFlowParameters();
        assertTrue(parameters.getReportedFeatures().isEmpty());
        parameters.update(updateParametersMap);
        assertTrue(parameters.getReportedFeatures().isEmpty());
    }

    @Test
    void testCompareParameters() {
        assertTrue(OpenLoadFlowParameters.equals(new LoadFlowParameters(), new LoadFlowParameters()));
        assertFalse(OpenLoadFlowParameters.equals(new LoadFlowParameters(), new LoadFlowParameters().setDc(true)));
        var p1 = new LoadFlowParameters();
        var p2 = new LoadFlowParameters();
        var pe1 = OpenLoadFlowParameters.create(p1);
        OpenLoadFlowParameters.create(p2);
        assertTrue(OpenLoadFlowParameters.equals(p1, p2));
        assertFalse(OpenLoadFlowParameters.equals(p1, new LoadFlowParameters()));
        assertFalse(OpenLoadFlowParameters.equals(new LoadFlowParameters(), p2));
        pe1.setMinRealisticVoltage(0.3);
        assertFalse(OpenLoadFlowParameters.equals(p1, p2));
    }

    @Test
    void testEqualsCloneAndUpdate() {
        OpenLoadFlowProvider provider = new OpenLoadFlowProvider();
        provider.getSpecificParameters().forEach(sp -> {
            var p1 = new LoadFlowParameters();
            var p2 = new LoadFlowParameters();
            var pe1 = OpenLoadFlowParameters.create(p1);
            var pe2 = OpenLoadFlowParameters.create(p2);
            final String newVal1;
            final String newVal2;
            assertTrue(OpenLoadFlowParameters.equals(p1, p2));
            if (sp.getType() == ParameterType.BOOLEAN) {
                newVal1 = "true";
                newVal2 = "false";
            } else if (sp.getType() == ParameterType.INTEGER || sp.getType() == ParameterType.DOUBLE) {
                newVal1 = "3";
                newVal2 = "4";
            } else if (sp.getType() == ParameterType.STRING) {
                if (sp.getPossibleValues() == null) {
                    // e.g. debugDir
                    newVal1 = "Foo";
                    newVal2 = "Bar";
                } else {
                    newVal1 = sp.getPossibleValues().get(0).toString();
                    newVal2 = sp.getPossibleValues().get(1).toString();
                }
            } else if (sp.getType() == ParameterType.STRING_LIST) {
                if (sp.getPossibleValues() == null) {
                    // e.g. slackBusesIds
                    newVal1 = "Foo,Bar";
                    newVal2 = "Foo,Bar,Baz";
                } else {
                    // e.g. slackBusCountryFilter
                    newVal1 = sp.getPossibleValues().get(0).toString();
                    newVal2 = sp.getPossibleValues().get(0).toString() + "," + sp.getPossibleValues().get(1).toString();
                }
            } else {
                throw new IllegalStateException("Unexpected ParameterType");
            }
            pe1.update(Map.of(sp.getName(), newVal1));
            pe2.update(Map.of(sp.getName(), newVal2));
            // should not equal
            assertFalse(OpenLoadFlowParameters.equals(p1, p2), "Parameter is not handled in equals: " + sp.getName());
            // at least one of the two won't have a default value by accident, so below should catch everything
            var p1c = OpenLoadFlowParameters.clone(p1);
            assertTrue(OpenLoadFlowParameters.equals(p1, p1c), "Parameter is not handled in clone: " + sp.getName());
            var p2c = OpenLoadFlowParameters.clone(p2);
            assertTrue(OpenLoadFlowParameters.equals(p2, p2c), "Parameter is not handled in clone: " + sp.getName());

            // Test update from PlatformConfig
            InMemoryPlatformConfig config1 = new InMemoryPlatformConfig(fileSystem);
            MapModuleConfig lfModuleConfig1 = config1.createModuleConfig("open-loadflow-default-parameters");
            InMemoryPlatformConfig config2 = new InMemoryPlatformConfig(fileSystem);
            MapModuleConfig lfModuleConfig2 = config2.createModuleConfig("open-loadflow-default-parameters");
            lfModuleConfig1.setStringProperty(sp.getName(), newVal1);
            lfModuleConfig2.setStringProperty(sp.getName(), newVal2);
            LoadFlowParameters lfu1 = new LoadFlowParameters();
            LoadFlowParameters lfu2 = new LoadFlowParameters();
            OpenLoadFlowParameters.create(lfu1).update(config1);
            OpenLoadFlowParameters.create(lfu2).update(config2);

            // should not equal
            assertFalse(OpenLoadFlowParameters.equals(lfu1, lfu2), "Parameter is not handled in update(platformConfig): " + sp.getName());

        });
    }

    @Test
    void testCloneParameters() {
        var p = new LoadFlowParameters();
        assertTrue(OpenLoadFlowParameters.equals(p, OpenLoadFlowParameters.clone(p)));
        var pe = OpenLoadFlowParameters.create(p);
        assertTrue(OpenLoadFlowParameters.equals(p, OpenLoadFlowParameters.clone(p)));
        pe.setMaxNewtonRaphsonIterations(20);
        assertTrue(OpenLoadFlowParameters.equals(p, OpenLoadFlowParameters.clone(p)));
        assertFalse(OpenLoadFlowParameters.equals(new LoadFlowParameters(), OpenLoadFlowParameters.clone(p)));
    }

    @Test
    void testToString() {
        OpenLoadFlowParameters parameters = new OpenLoadFlowParameters();
        assertEquals("OpenLoadFlowParameters(slackBusSelectionMode=MOST_MESHED, slackBusesIds=[], slackDistributionFailureBehavior=LEAVE_ON_SLACK_BUS, voltageRemoteControl=true, lowImpedanceBranchMode=REPLACE_BY_ZERO_IMPEDANCE_LINE, loadPowerFactorConstant=false, plausibleActivePowerLimit=5000.0, newtonRaphsonStoppingCriteriaType=UNIFORM_CRITERIA, slackBusPMaxMismatch=1.0, maxActivePowerMismatch=0.01, maxReactivePowerMismatch=0.01, maxVoltageMismatch=1.0E-4, maxAngleMismatch=1.0E-5, maxRatioMismatch=1.0E-5, maxSusceptanceMismatch=1.0E-4, voltagePerReactivePowerControl=false, generatorReactivePowerRemoteControl=false, transformerReactivePowerControl=false, maxNewtonRaphsonIterations=15, maxOuterLoopIterations=20, newtonRaphsonConvEpsPerEq=1.0E-4, voltageInitModeOverride=NONE, transformerVoltageControlMode=WITH_GENERATOR_VOLTAGE_CONTROL, shuntVoltageControlMode=WITH_GENERATOR_VOLTAGE_CONTROL, minPlausibleTargetVoltage=0.8, maxPlausibleTargetVoltage=1.2, minRealisticVoltage=0.5, maxRealisticVoltage=2.0, minNominalVoltageRealisticVoltageCheck=0.0, reactiveRangeCheckMode=MAX, lowImpedanceThreshold=1.0E-8, networkCacheEnabled=false, svcVoltageMonitoring=true, stateVectorScalingMode=NONE, maxSlackBusCount=1, debugDir=null, incrementalTransformerRatioTapControlOuterLoopMaxTapShift=3, secondaryVoltageControl=false, reactiveLimitsMaxPqPvSwitch=3, phaseShifterControlMode=CONTINUOUS_WITH_DISCRETISATION, alwaysUpdateNetwork=false, mostMeshedSlackBusSelectorMaxNominalVoltagePercentile=95.0, reportedFeatures=[], slackBusCountryFilter=[], actionableSwitchesIds=[], actionableTransformersIds=[], asymmetrical=false, minNominalVoltageTargetVoltageCheck=20.0, reactivePowerDispatchMode=Q_EQUAL_PROPORTION, outerLoopNames=null, useActiveLimits=true, disableVoltageControlOfGeneratorsOutsideActivePowerLimits=false, lineSearchStateVectorScalingMaxIteration=10, lineSearchStateVectorScalingStepFold=1.3333333333333333, maxVoltageChangeStateVectorScalingMaxDv=0.1, maxVoltageChangeStateVectorScalingMaxDphi=0.17453292519943295, linePerUnitMode=IMPEDANCE, useLoadModel=false, dcApproximationType=IGNORE_R, simulateAutomationSystems=false, acSolverType=NEWTON_RAPHSON, maxNewtonKrylovIterations=100, newtonKrylovLineSearch=false, referenceBusSelectionMode=FIRST_SLACK, writeReferenceTerminals=true, voltageTargetPriorities=[GENERATOR, TRANSFORMER, SHUNT], transformerVoltageControlUseInitialTapPosition=false, generatorVoltageControlMinNominalVoltage=-1.0, fictitiousGeneratorVoltageControlCheckMode=FORCED, areaInterchangeControl=false, areaInterchangeControlAreaType=ControlArea, areaInterchangePMaxMismatch=2.0, voltageRemoteControlRobustMode=true, forceTargetQInReactiveLimits=false, disableInconsistentVoltageControls=false, extrapolateReactiveLimits=false)",
                parameters.toString());
    }

    @Test
    void testExplicitOuterLoopsParameterAc() {
        LoadFlowParameters parameters = new LoadFlowParameters()
                .setShuntCompensatorVoltageControlOn(true);
        OpenLoadFlowParameters parametersExt = new OpenLoadFlowParameters()
                .setSecondaryVoltageControl(true);

        assertEquals(List.of("DistributedSlack", "SecondaryVoltageControl", "VoltageMonitoring", "ReactiveLimits", "ShuntVoltageControl"), OpenLoadFlowParameters.createAcOuterLoops(parameters, parametersExt).stream().map(OuterLoop::getType).toList());

        parametersExt.setOuterLoopNames(List.of("ReactiveLimits", "SecondaryVoltageControl"));
        assertEquals(List.of("ReactiveLimits", "SecondaryVoltageControl"), OpenLoadFlowParameters.createAcOuterLoops(parameters, parametersExt).stream().map(OuterLoop::getType).toList());

        parametersExt.setOuterLoopNames(ExplicitAcOuterLoopConfig.NAMES);
        PowsyblException e = assertThrows(PowsyblException.class, () -> OpenLoadFlowParameters.createAcOuterLoops(parameters, parametersExt));
        assertEquals("Multiple (2) outer loops with same type: ShuntVoltageControl", e.getMessage());

        parametersExt.setOuterLoopNames(List.of("ReactiveLimits", "Foo"));
        e = assertThrows(PowsyblException.class, () -> OpenLoadFlowParameters.createAcOuterLoops(parameters, parametersExt));
        assertEquals("Unknown outer loop 'Foo' for AC load flow", e.getMessage());

        assertEquals("Ordered explicit list of outer loop names, supported outer loops are for AC : [IncrementalPhaseControl, DistributedSlack, IncrementalShuntVoltageControl, IncrementalTransformerVoltageControl, VoltageMonitoring, PhaseControl, ReactiveLimits, SecondaryVoltageControl, ShuntVoltageControl, SimpleTransformerVoltageControl, TransformerVoltageControl, AutomationSystem, IncrementalTransformerReactivePowerControl, AreaInterchangeControl], and for DC : [IncrementalPhaseControl, AreaInterchangeControl]",
                     OpenLoadFlowParameters.SPECIFIC_PARAMETERS.stream().filter(p -> p.getName().equals(OpenLoadFlowParameters.OUTER_LOOP_NAMES_PARAM_NAME)).findFirst().orElseThrow().getDescription());
    }

    @Test
    void testExplicitOuterLoopsParameterDc() {
        LoadFlowParameters parameters = new LoadFlowParameters()
                .setPhaseShifterRegulationOn(true);
        OpenLoadFlowParameters parametersExt = new OpenLoadFlowParameters()
                .setAreaInterchangeControl(true);

        assertEquals(List.of("IncrementalPhaseControl", "AreaInterchangeControl"), OpenLoadFlowParameters.createDcOuterLoops(parameters, parametersExt).stream().map(OuterLoop::getName).toList());

        parametersExt.setOuterLoopNames(List.of("IncrementalPhaseControl"));
        assertEquals(List.of("IncrementalPhaseControl"), OpenLoadFlowParameters.createDcOuterLoops(parameters, parametersExt).stream().map(OuterLoop::getName).toList());

        parametersExt.setOuterLoopNames(List.of("IncrementalPhaseControl", "DistributedSlack"));
        PowsyblException e = assertThrows(PowsyblException.class, () -> OpenLoadFlowParameters.createDcOuterLoops(parameters, parametersExt));
        assertEquals("Unknown outer loop 'DistributedSlack' for DC load flow", e.getMessage());
    }

    @Test
    void testSlackDistributionOuterLoops() {
        LoadFlowParameters parameters = new LoadFlowParameters()
                .setDistributedSlack(true);
        OpenLoadFlowParameters parametersExt = new OpenLoadFlowParameters();

        assertEquals(List.of("DistributedSlack", "VoltageMonitoring", "ReactiveLimits"), OpenLoadFlowParameters.createAcOuterLoops(parameters, parametersExt).stream().map(OuterLoop::getType).toList());

        parametersExt.setAreaInterchangeControl(true);
        assertEquals(List.of("AreaInterchangeControl", "VoltageMonitoring", "ReactiveLimits"), OpenLoadFlowParameters.createAcOuterLoops(parameters, parametersExt).stream().map(OuterLoop::getType).toList());

        parametersExt.setOuterLoopNames(List.of("DistributedSlack", "AreaInterchangeControl"));
        assertEquals(List.of("AreaInterchangeControl"), OpenLoadFlowParameters.createAcOuterLoops(parameters, parametersExt).stream().map(OuterLoop::getType).toList());

        parametersExt.setOuterLoopNames(List.of("DistributedSlack"));
        assertEquals(List.of("DistributedSlack"), OpenLoadFlowParameters.createAcOuterLoops(parameters, parametersExt).stream().map(OuterLoop::getType).toList());
    }

    @Test
    void testVoltageTargetPrioritiesParameter() {
        LoadFlowParameters parameters = new LoadFlowParameters();
        OpenLoadFlowParameters parametersExt = OpenLoadFlowParameters.create(parameters);
        List<String> voltageTargetPrioritiesList = List.of("GENERATOR", "Foo");
        Throwable e = assertThrows(PowsyblException.class, () -> parametersExt.setVoltageTargetPriorities(voltageTargetPrioritiesList));
        assertEquals("Unknown Voltage Control Type: Foo", e.getMessage());

        parametersExt.setVoltageTargetPriorities(List.of("SHUNT"));
        LfNetworkParameters lfNetworkParameters = OpenLoadFlowParameters.getNetworkParameters(parameters, parametersExt, new FirstSlackBusSelector(), new EvenShiloachGraphDecrementalConnectivityFactory<>(), false);
        assertEquals(0, lfNetworkParameters.getVoltageTargetPriority(VoltageControl.Type.SHUNT)); // user-provided
        assertEquals(1, lfNetworkParameters.getVoltageTargetPriority(VoltageControl.Type.GENERATOR)); // filled from default
        assertEquals(2, lfNetworkParameters.getVoltageTargetPriority(VoltageControl.Type.TRANSFORMER)); // filled from default
    }

    @Test
    void testOlfIntegerParametersChecker() {
        LoadFlowParameters parameters = new LoadFlowParameters();
        OpenLoadFlowParameters olfParameters = OpenLoadFlowParameters.create(parameters);

        // for integer parameters
        IllegalArgumentException e = assertThrows(IllegalArgumentException.class, () -> olfParameters.setMaxNewtonRaphsonIterations(0));
        assertEquals("Invalid value for parameter maxNewtonRaphsonIterations: 0", e.getMessage());

        e = assertThrows(IllegalArgumentException.class, () -> olfParameters.setMaxOuterLoopIterations(0));
        assertEquals("Invalid value for parameter maxOuterLoopIterations: 0", e.getMessage());

        e = assertThrows(IllegalArgumentException.class, () -> olfParameters.setIncrementalTransformerRatioTapControlOuterLoopMaxTapShift(0));
        assertEquals("Invalid value for parameter incrementalTransformerRatioTapControlOuterLoopMaxTapShift: 0", e.getMessage());

        e = assertThrows(IllegalArgumentException.class, () -> olfParameters.setReactiveLimitsMaxPqPvSwitch(-1));
        assertEquals("Invalid value for parameter reactiveLimitsMaxPqPvSwitch: -1", e.getMessage());

        e = assertThrows(IllegalArgumentException.class, () -> olfParameters.setLineSearchStateVectorScalingMaxIteration(0));
        assertEquals("Invalid value for parameter lineSearchStateVectorScalingMaxIteration: 0", e.getMessage());

        e = assertThrows(IllegalArgumentException.class, () -> olfParameters.setMaxNewtonKrylovIterations(0));
        assertEquals("Invalid value for parameter maxNewtonKrylovIterations: 0", e.getMessage());
    }

    @Test
    void testOlfDoubleParametersChecker() {
        LoadFlowParameters parameters = new LoadFlowParameters();
        OpenLoadFlowParameters olfParameters = OpenLoadFlowParameters.create(parameters);

        // for double parameters
        IllegalArgumentException e = assertThrows(IllegalArgumentException.class, () -> olfParameters.setSlackBusPMaxMismatch(-1.0));
        assertEquals("Invalid value for parameter slackBusPMaxMismatch: -1.0", e.getMessage());

        e = assertThrows(IllegalArgumentException.class, () -> olfParameters.setNewtonRaphsonConvEpsPerEq(0));
        assertEquals("Invalid value for parameter newtonRaphsonConvEpsPerEq: 0.0", e.getMessage());

        e = assertThrows(IllegalArgumentException.class, () -> olfParameters.setMaxActivePowerMismatch(0));
        assertEquals("Invalid value for parameter maxActivePowerMismatch: 0.0", e.getMessage());

        e = assertThrows(IllegalArgumentException.class, () -> olfParameters.setMaxReactivePowerMismatch(0));
        assertEquals("Invalid value for parameter maxReactivePowerMismatch: 0.0", e.getMessage());

        e = assertThrows(IllegalArgumentException.class, () -> olfParameters.setMaxVoltageMismatch(0));
        assertEquals("Invalid value for parameter maxVoltageMismatch: 0.0", e.getMessage());

        e = assertThrows(IllegalArgumentException.class, () -> olfParameters.setMaxAngleMismatch(0));
        assertEquals("Invalid value for parameter maxAngleMismatch: 0.0", e.getMessage());

        e = assertThrows(IllegalArgumentException.class, () -> olfParameters.setMaxRatioMismatch(0));
        assertEquals("Invalid value for parameter maxRatioMismatch: 0.0", e.getMessage());

        e = assertThrows(IllegalArgumentException.class, () -> olfParameters.setMaxSusceptanceMismatch(0));
        assertEquals("Invalid value for parameter maxSusceptanceMismatch: 0.0", e.getMessage());

        e = assertThrows(IllegalArgumentException.class, () -> olfParameters.setMinPlausibleTargetVoltage(-1.0));
        assertEquals("Invalid value for parameter minPlausibleTargetVoltage: -1.0", e.getMessage());

        e = assertThrows(IllegalArgumentException.class, () -> olfParameters.setMaxPlausibleTargetVoltage(-1.0));
        assertEquals("Invalid value for parameter maxPlausibleTargetVoltage: -1.0", e.getMessage());

        e = assertThrows(IllegalArgumentException.class, () -> olfParameters.setMinNominalVoltageTargetVoltageCheck(-1.0));
        assertEquals("Invalid value for parameter minNominalVoltageTargetVoltageCheck: -1.0", e.getMessage());

        e = assertThrows(IllegalArgumentException.class, () -> olfParameters.setMinRealisticVoltage(-1.0));
        assertEquals("Invalid value for parameter minRealisticVoltage: -1.0", e.getMessage());

        e = assertThrows(IllegalArgumentException.class, () -> olfParameters.setMaxRealisticVoltage(-1.0));
        assertEquals("Invalid value for parameter maxRealisticVoltage: -1.0", e.getMessage());

        e = assertThrows(IllegalArgumentException.class, () -> olfParameters.setLowImpedanceThreshold(0.0));
        assertEquals("Invalid value for parameter lowImpedanceThreshold: 0.0", e.getMessage());

        e = assertThrows(IllegalArgumentException.class, () -> olfParameters.setMostMeshedSlackBusSelectorMaxNominalVoltagePercentile(-1.0));
        assertEquals("Invalid value for parameter mostMeshedSlackBusSelectorMaxNominalVoltagePercentile: -1.0", e.getMessage());

        e = assertThrows(IllegalArgumentException.class, () -> olfParameters.setMostMeshedSlackBusSelectorMaxNominalVoltagePercentile(101.0));
        assertEquals("Invalid value for parameter mostMeshedSlackBusSelectorMaxNominalVoltagePercentile: 101.0", e.getMessage());

        e = assertThrows(IllegalArgumentException.class, () -> olfParameters.setLineSearchStateVectorScalingStepFold(1.0));
        assertEquals("Invalid value for parameter lineSearchStateVectorScalingStepFold: 1.0", e.getMessage());

        e = assertThrows(IllegalArgumentException.class, () -> olfParameters.setMaxVoltageChangeStateVectorScalingMaxDv(0.0));
        assertEquals("Invalid value for parameter maxVoltageChangeStateVectorScalingMaxDv: 0.0", e.getMessage());

        e = assertThrows(IllegalArgumentException.class, () -> olfParameters.setMaxVoltageChangeStateVectorScalingMaxDphi(0.0));
        assertEquals("Invalid value for parameter maxVoltageChangeStateVectorScalingMaxDphi: 0.0", e.getMessage());
    }
}