LfActionTest.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.sa;

import com.powsybl.action.*;
import com.powsybl.commons.report.ReportNode;
import com.powsybl.commons.test.AbstractSerDeTest;
import com.powsybl.contingency.Contingency;
import com.powsybl.contingency.LoadContingency;
import com.powsybl.iidm.network.Generator;
import com.powsybl.iidm.network.Network;
import com.powsybl.iidm.network.ThreeSides;
import com.powsybl.iidm.network.extensions.HvdcAngleDroopActivePowerControlAdder;
import com.powsybl.loadflow.LoadFlowParameters;
import com.powsybl.math.matrix.DenseMatrixFactory;
import com.powsybl.openloadflow.OpenLoadFlowParameters;
import com.powsybl.openloadflow.ac.AcLoadFlowParameters;
import com.powsybl.openloadflow.graph.NaiveGraphConnectivityFactory;
import com.powsybl.openloadflow.network.*;
import com.powsybl.openloadflow.network.action.*;
import com.powsybl.openloadflow.network.impl.LfNetworkList;
import com.powsybl.openloadflow.network.impl.Networks;
import com.powsybl.openloadflow.network.impl.PropagatedContingency;
import com.powsybl.openloadflow.network.impl.PropagatedContingencyCreationParameters;
import com.powsybl.openloadflow.util.PerUnit;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.io.IOException;
import java.util.Collections;

import static org.junit.jupiter.api.Assertions.*;

/**
 * @author Anne Tilloy {@literal <anne.tilloy at rte-france.com>}
 * @author Jean-Luc Bouchot {@literal <jlbouchot at gmail.com>}
 */
class LfActionTest extends AbstractSerDeTest {

    @Override
    @BeforeEach
    public void setUp() throws IOException {
        super.setUp();
    }

    @Override
    @AfterEach
    public void tearDown() throws IOException {
        super.tearDown();
    }

    @Test
    void test() {
        Network network = NodeBreakerNetworkFactory.create();
        SwitchAction switchAction = new SwitchAction("switchAction", "C", true);
        var matrixFactory = new DenseMatrixFactory();
        LoadFlowParameters loadFlowParameters = new LoadFlowParameters();
        AcLoadFlowParameters acParameters = OpenLoadFlowParameters.createAcParameters(network,
                loadFlowParameters, new OpenLoadFlowParameters(), matrixFactory, new NaiveGraphConnectivityFactory<>(LfBus::getNum), true, false);
        LfTopoConfig topoConfig = new LfTopoConfig();
        LfNetworkParameters networkParameters = acParameters.getNetworkParameters();
        topoConfig.getSwitchesToOpen().add(network.getSwitch("C"));
        try (LfNetworkList lfNetworks = Networks.load(network, networkParameters, topoConfig, ReportNode.NO_OP)) {
            LfNetwork lfNetwork = lfNetworks.getLargest().orElseThrow();
            LfAction lfSwitchAction = LfActionUtils.createLfAction(switchAction, network, networkParameters.isBreakers(), lfNetwork);
            String loadId = "LOAD";
            Contingency contingency = new Contingency(loadId, new LoadContingency("LD"));
            PropagatedContingencyCreationParameters creationParameters = new PropagatedContingencyCreationParameters()
                    .setHvdcAcEmulation(false);
            PropagatedContingency propagatedContingency = PropagatedContingency.createList(network,
                    Collections.singletonList(contingency), new LfTopoConfig(), creationParameters).get(0);
            propagatedContingency.toLfContingency(lfNetwork).ifPresent(lfContingency -> {
                lfSwitchAction.apply(lfNetwork, lfContingency, networkParameters);
                assertTrue(lfNetwork.getBranchById("C").isDisabled());
                assertEquals("C", ((LfSwitchAction) lfSwitchAction).getDisabledBranch().getId());
                assertNull(((LfSwitchAction) lfSwitchAction).getEnabledBranch());
            });

            LfAction lfInvalidSwitchAction = LfActionUtils.createLfAction(new SwitchAction("switchAction", "S", true),
                network, networkParameters.isBreakers(), lfNetwork);
            LfAction lfInvalidTerminalsConnectionAction = LfActionUtils.createLfAction(new TerminalsConnectionAction("A line action", "x", true),
                network, networkParameters.isBreakers(), lfNetwork);
            LfAction lfInvalidPhaseTapChangerTapPositionAction = LfActionUtils.createLfAction(new PhaseTapChangerTapPositionAction("A phase tap change action", "y", false, 3),
                network, networkParameters.isBreakers(), lfNetwork);

            assertFalse(lfInvalidSwitchAction.apply(lfNetwork, null, networkParameters));
            assertFalse(lfInvalidTerminalsConnectionAction.apply(lfNetwork, null, networkParameters));
            assertFalse(lfInvalidPhaseTapChangerTapPositionAction.apply(lfNetwork, null, networkParameters));

            var lineAction = new TerminalsConnectionAction("A line action", "L1", ThreeSides.ONE, false);
            assertEquals("Terminals connection action: only open or close branch at both sides is supported yet.",
                assertThrows(UnsupportedOperationException.class, () -> new LfTerminalsConnectionAction("A line action", lineAction, lfNetwork)).getMessage());
        }
    }

    @Test
    void testUnsupportedGeneratorAction() {
        Network network = NodeBreakerNetworkFactory.create();
        String genId = "G";
        GeneratorAction generatorAction = new GeneratorActionBuilder()
                .withId("genAction" + genId)
                .withGeneratorId(genId)
                .withTargetQ(100) // to be done soon
                .build();
        var matrixFactory = new DenseMatrixFactory();
        AcLoadFlowParameters acParameters = OpenLoadFlowParameters.createAcParameters(network,
                new LoadFlowParameters(), new OpenLoadFlowParameters(), matrixFactory, new NaiveGraphConnectivityFactory<>(LfBus::getNum), true, false);
        try (LfNetworkList lfNetworks = Networks.load(network, acParameters.getNetworkParameters(), new LfTopoConfig(), ReportNode.NO_OP)) {
            LfNetwork lfNetwork = lfNetworks.getLargest().orElseThrow();
            UnsupportedOperationException e = assertThrows(UnsupportedOperationException.class,
                () -> new LfGeneratorAction("Gen action", generatorAction, lfNetwork));
            assertEquals("Generator action on G : configuration not supported yet.", e.getMessage());
        }
    }

    @Test
    void testGeneratorActionWithRelativeActivePowerValue() {
        Network network = NodeBreakerNetworkFactory.create();
        String genId = "G";
        Generator generator = network.getGenerator(genId);
        final double deltaTargetP = 2d;
        final double oldTargetP = generator.getTargetP();
        final double newTargetP = oldTargetP + deltaTargetP;
        GeneratorAction generatorAction = new GeneratorActionBuilder()
                .withId("genAction_" + genId)
                .withGeneratorId(genId)
                .withActivePowerRelativeValue(true)
                .withActivePowerValue(deltaTargetP)
                .build();
        var matrixFactory = new DenseMatrixFactory();
        AcLoadFlowParameters acParameters = OpenLoadFlowParameters.createAcParameters(network,
                new LoadFlowParameters(), new OpenLoadFlowParameters(), matrixFactory, new NaiveGraphConnectivityFactory<>(LfBus::getNum), true, false);
        try (LfNetworkList lfNetworks = Networks.load(network, acParameters.getNetworkParameters(), new LfTopoConfig(), ReportNode.NO_OP)) {
            LfNetwork lfNetwork = lfNetworks.getLargest().orElseThrow();
            LfAction lfAction = LfActionUtils.createLfAction(generatorAction, network, acParameters.getNetworkParameters().isBreakers(), lfNetwork);
            lfAction.apply(lfNetwork, null, acParameters.getNetworkParameters());
            assertEquals(newTargetP / PerUnit.SB, lfNetwork.getGeneratorById(genId).getTargetP());
            assertEquals(genId, generatorAction.getGeneratorId());
            assertEquals(oldTargetP, network.getGenerator(genId).getTargetP());
        }
    }

    @Test
    void testHvdcAction() {
        // the hvc line is operated in AC emulation before applying the action. A change in P0 and droop is not supported yet.
        Network network = HvdcNetworkFactory.createWithHvdcInAcEmulation();
        network.getHvdcLine("hvdc34").newExtension(HvdcAngleDroopActivePowerControlAdder.class)
                .withDroop(180)
                .withP0(0.f)
                .withEnabled(true)
                .add();
        HvdcAction hvdcAction = new HvdcActionBuilder()
                .withId("action")
                .withHvdcId("hvdc34")
                .withAcEmulationEnabled(true)
                .withP0(200.0)
                .withDroop(90.0)
                .build();
        UnsupportedOperationException e = assertThrows(UnsupportedOperationException.class, () -> new LfHvdcAction("action", hvdcAction));
        assertEquals("Hvdc action: enabling ac emulation mode through an action is not supported yet.", e.getMessage());
    }

    @Test
    void testHvdcAction2() {
        // This action is valid but AC emulation disabling has not been explicitly set, not supported.
        Network network = HvdcNetworkFactory.createWithHvdcInAcEmulation();
        HvdcAction hvdcAction2 = new HvdcActionBuilder()
            .withId("action")
            .withHvdcId("hvdc34")
            .withP0(200.0)
            .withDroop(90.0)
            .build();
        var matrixFactory = new DenseMatrixFactory();
        AcLoadFlowParameters acParameters = OpenLoadFlowParameters.createAcParameters(network,
            new LoadFlowParameters(), new OpenLoadFlowParameters(), matrixFactory, new NaiveGraphConnectivityFactory<>(LfBus::getNum), true, false);
        try (LfNetworkList lfNetworks = Networks.load(network, acParameters.getNetworkParameters(), new LfTopoConfig(), ReportNode.NO_OP)) {
            LfNetwork lfNetwork = lfNetworks.getLargest().orElseThrow();
            LfAction lfAction = LfActionUtils.createLfAction(hvdcAction2, network, acParameters.getNetworkParameters().isBreakers(), lfNetwork);
            assertFalse(lfAction.apply(lfNetwork, null, acParameters.getNetworkParameters()));
        }
    }

    @Test
    void testLfAreaInterchangeTargetAction() {

        AreaInterchangeTargetAction targetAction = new AreaInterchangeTargetActionBuilder()
            .withId("action")
            .withAreaId("a1")
            .withTarget(20.0)
            .build();

        AreaInterchangeTargetAction invalidTargetAction = new AreaInterchangeTargetActionBuilder()
            .withId("action")
            .withAreaId("DUMMMY")
            .withTarget(20.0)
            .build();

        Network network = MultiAreaNetworkFactory.createTwoAreasWithXNode();

        var matrixFactory = new DenseMatrixFactory();

        // With area interchange target control enabled
        AcLoadFlowParameters acParameters = OpenLoadFlowParameters.createAcParameters(network,
            new LoadFlowParameters(), new OpenLoadFlowParameters(), matrixFactory, new NaiveGraphConnectivityFactory<>(LfBus::getNum), true, false);
        acParameters.getNetworkParameters().setAreaInterchangeControl(true);
        try (LfNetworkList lfNetworks = Networks.load(network, acParameters.getNetworkParameters(), new LfTopoConfig(), ReportNode.NO_OP)) {
            LfNetwork lfNetwork = lfNetworks.getLargest().orElseThrow();

            LfAction lfAreaTargetAction = LfActionUtils.createLfAction(targetAction, network, acParameters.getNetworkParameters().isBreakers(), lfNetwork);
            assertTrue(lfAreaTargetAction.apply(lfNetwork, null, acParameters.getNetworkParameters()));

            LfAction lfAreaTargetAction2 = LfActionUtils.createLfAction(invalidTargetAction, network, acParameters.getNetworkParameters().isBreakers(), lfNetwork);
            assertFalse(lfAreaTargetAction2.apply(lfNetwork, null, acParameters.getNetworkParameters()));
        }

        // With area interchange target control disabled
        acParameters.getNetworkParameters().setAreaInterchangeControl(false);
        try (LfNetworkList lfNetworks = Networks.load(network, acParameters.getNetworkParameters(), new LfTopoConfig(), ReportNode.NO_OP)) {
            LfNetwork lfNetwork = lfNetworks.getLargest().orElseThrow();
            acParameters.getNetworkParameters().setAreaInterchangeControl(false);

            LfAction lfAreaTargetAction = LfActionUtils.createLfAction(targetAction, network, acParameters.getNetworkParameters().isBreakers(), lfNetwork);
            assertFalse(lfAreaTargetAction.apply(lfNetwork, null, acParameters.getNetworkParameters()));
        }
    }
}