PstRangeActionAdderImplTest.java

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

package com.powsybl.openrao.data.crac.impl;

import com.powsybl.openrao.commons.OpenRaoException;
import com.powsybl.openrao.data.crac.api.InstantKind;
import com.powsybl.openrao.data.crac.api.networkaction.ActionType;
import com.powsybl.openrao.data.crac.api.rangeaction.PstRangeAction;
import com.powsybl.openrao.data.crac.api.rangeaction.PstRangeActionAdder;
import com.powsybl.openrao.data.crac.api.range.RangeType;
import com.powsybl.openrao.data.crac.api.rangeaction.VariationDirection;
import com.powsybl.openrao.data.crac.api.usagerule.UsageMethod;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

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

/**
 * @author Peter Mitri {@literal <peter.mitri at rte-france.com>}
 * @author Baptiste Seguinot {@literal <baptiste.seguinot at rte-france.com>}
 */
class PstRangeActionAdderImplTest {
    private static final String PREVENTIVE_INSTANT_ID = "preventive";
    private static final String OUTAGE_INSTANT_ID = "outage";
    private static final String AUTO_INSTANT_ID = "auto";

    private CracImpl crac;
    private String networkElementId;
    private Map<Integer, Double> validTapToAngleConversionMap;

    @BeforeEach
    public void setUp() {
        crac = new CracImpl("test-crac")
            .newInstant(PREVENTIVE_INSTANT_ID, InstantKind.PREVENTIVE)
            .newInstant(OUTAGE_INSTANT_ID, InstantKind.OUTAGE)
            .newInstant(AUTO_INSTANT_ID, InstantKind.AUTO);
        networkElementId = "BBE2AA1  BBE3AA1  1";
        validTapToAngleConversionMap = Map.of(-2, -20., -1, -10., 0, 0., 1, 10., 2, 20.);
    }

    @Test
    void testAdd() {
        PstRangeAction pstRangeAction = crac.newPstRangeAction()
            .withId("id1")
            .withOperator("BE")
            .withNetworkElement(networkElementId)
            .withGroupId("groupId1")
            .withActivationCost(0d)
            .withVariationCost(0d, VariationDirection.UP)
            .withVariationCost(0d, VariationDirection.DOWN)
            .newTapRange()
                .withMinTap(-10)
                .withMaxTap(10)
                .withRangeType(RangeType.ABSOLUTE)
                .add()
            .newOnInstantUsageRule()
                .withInstant(PREVENTIVE_INSTANT_ID)
                .withUsageMethod(UsageMethod.AVAILABLE)
                .add()
            .withInitialTap(1)
            .withTapToAngleConversionMap(validTapToAngleConversionMap)
            .add();

        assertEquals(1, crac.getRangeActions().size());
        assertEquals(networkElementId, pstRangeAction.getNetworkElement().getId());
        assertEquals("BE", pstRangeAction.getOperator());
        assertEquals(Optional.of(0d), pstRangeAction.getActivationCost());
        assertEquals(Optional.of(0d), pstRangeAction.getVariationCost(VariationDirection.UP));
        assertEquals(Optional.of(0d), pstRangeAction.getVariationCost(VariationDirection.DOWN));
        assertEquals(1, pstRangeAction.getRanges().size());
        assertEquals(1, pstRangeAction.getUsageRules().size());
        assertEquals(1, crac.getNetworkElements().size());
        assertNotNull(crac.getNetworkElement(networkElementId));
    }

    @Test
    void testAddAutoWithoutSpeed() {
        PstRangeActionAdder pstRangeActionAdder = crac.newPstRangeAction()
            .withId("id1")
            .withOperator("BE")
            .withNetworkElement(networkElementId)
            .withGroupId("groupId1")
            .newTapRange()
            .withMinTap(-10)
            .withMaxTap(10)
            .withRangeType(RangeType.ABSOLUTE)
            .add()
            .newOnInstantUsageRule()
            .withInstant(AUTO_INSTANT_ID)
            .withUsageMethod(UsageMethod.AVAILABLE)
            .add()
            .withInitialTap(1)
            .withTapToAngleConversionMap(validTapToAngleConversionMap);
        OpenRaoException exception = assertThrows(OpenRaoException.class, pstRangeActionAdder::add);
        assertEquals("Cannot create an AUTO Pst range action without speed defined", exception.getMessage());
    }

    @Test
    void testAddAutoWithSpeed() {
        PstRangeAction pstRangeAction = crac.newPstRangeAction()
                .withId("id1")
                .withOperator("BE")
                .withNetworkElement(networkElementId)
                .withGroupId("groupId1")
                .withSpeed(123)
                .newTapRange()
                .withMinTap(-10)
                .withMaxTap(10)
                .withRangeType(RangeType.ABSOLUTE)
                .add()
                .newOnInstantUsageRule()
                .withInstant(AUTO_INSTANT_ID)
                .withUsageMethod(UsageMethod.AVAILABLE)
                .add()
                .withInitialTap(1)
                .withTapToAngleConversionMap(validTapToAngleConversionMap)
                .add();

        assertEquals(123, pstRangeAction.getSpeed().get().intValue());
        assertTrue(pstRangeAction.getActivationCost().isEmpty());
        assertTrue(pstRangeAction.getVariationCost(VariationDirection.UP).isEmpty());
        assertTrue(pstRangeAction.getVariationCost(VariationDirection.DOWN).isEmpty());
    }

    @Test
    void testAddWithoutGroupId() {
        PstRangeAction pstRangeAction = crac.newPstRangeAction()
            .withId("id1")
            .withOperator("BE")
            .withNetworkElement(networkElementId)
            .newTapRange()
                .withMinTap(-10)
                .withMaxTap(10)
                .withRangeType(RangeType.ABSOLUTE)
                .add()
            .newOnInstantUsageRule()
                .withInstant(PREVENTIVE_INSTANT_ID)
                .withUsageMethod(UsageMethod.AVAILABLE)
                .add()
            .withInitialTap(0)
            .withTapToAngleConversionMap(validTapToAngleConversionMap)
            .add();

        assertEquals(1, crac.getRangeActions().size());
        assertEquals(networkElementId, pstRangeAction.getNetworkElement().getId());
        assertEquals("BE", pstRangeAction.getOperator());
        assertEquals(1, pstRangeAction.getRanges().size());
        assertEquals(1, pstRangeAction.getUsageRules().size());
    }

    @Test
    void testAddWithoutRangeAndUsageRule() {
        /*
        This behaviour is considered admissible:
            - without range, the default range will be defined by the min/max value of the network
            - without usage rule, the remedial action will never be available

        This test should however return two warnings
         */
        PstRangeAction pstRangeAction = crac.newPstRangeAction()
            .withId("id1")
            .withOperator("BE")
            .withNetworkElement(networkElementId)
            .withInitialTap(2)
            .withTapToAngleConversionMap(validTapToAngleConversionMap)
            .add();

        assertEquals(1, crac.getRangeActions().size());
        assertEquals(networkElementId, pstRangeAction.getNetworkElement().getId());
        assertEquals("BE", pstRangeAction.getOperator());
        assertEquals(0, pstRangeAction.getRanges().size());
        assertEquals(0, pstRangeAction.getUsageRules().size());
    }

    @Test
    void testAddWithoutOperator() {
        PstRangeAction pstRangeAction = crac.newPstRangeAction()
            .withId("id1")
            .withNetworkElement(networkElementId)
            .newTapRange()
                .withMinTap(-10)
                .withMaxTap(10)
                .withRangeType(RangeType.ABSOLUTE)
                .add()
            .newOnInstantUsageRule()
                .withInstant(PREVENTIVE_INSTANT_ID)
                .withUsageMethod(UsageMethod.AVAILABLE)
                .add()
            .withInitialTap(-2)
            .withTapToAngleConversionMap(validTapToAngleConversionMap)
            .add();

        assertEquals(1, crac.getRangeActions().size());
        assertEquals(networkElementId, pstRangeAction.getNetworkElement().getId());
        assertNull(pstRangeAction.getOperator());
        assertEquals(1, pstRangeAction.getRanges().size());
        assertEquals(1, pstRangeAction.getUsageRules().size());
    }

    @Test
    void testNoIdFail() {
        PstRangeActionAdder pstRangeActionAdder = crac.newPstRangeAction()
            .withOperator("BE")
            .withNetworkElement(networkElementId)
            .withInitialTap(1)
            .withTapToAngleConversionMap(validTapToAngleConversionMap);
        OpenRaoException exception = assertThrows(OpenRaoException.class, pstRangeActionAdder::add);
        assertEquals("Cannot add a PstRangeAction object with no specified id. Please use withId()", exception.getMessage());
    }

    @Test
    void testNoNetworkElementFail() {
        PstRangeActionAdder pstRangeActionAdder = crac.newPstRangeAction()
            .withId("id1")
            .withOperator("BE")
            .withInitialTap(1)
            .withTapToAngleConversionMap(validTapToAngleConversionMap);
        OpenRaoException exception = assertThrows(OpenRaoException.class, pstRangeActionAdder::add);
        assertEquals("Cannot add PstRangeAction without a network element. Please use withNetworkElement() with a non null value", exception.getMessage());
    }

    @Test
    void testIdNotUnique() {
        crac.newNetworkAction()
            .withId("sameId")
            .withOperator("BE")
            .newTerminalsConnectionAction().withActionType(ActionType.OPEN).withNetworkElement("action-elementId").add()
            .add();

        PstRangeActionAdder adder = crac.newPstRangeAction()
            .withId("sameId")
            .withOperator("BE")
            .withNetworkElement("networkElementId")
            .withInitialTap(1)
            .withTapToAngleConversionMap(validTapToAngleConversionMap);
        OpenRaoException exception = assertThrows(OpenRaoException.class, adder::add);
        assertEquals("A remedial action with id sameId already exists", exception.getMessage());
    }

    @Test
    void testNoInitialTap() {
        PstRangeActionAdder pstRangeActionAdder = crac.newPstRangeAction()
            .withId("id1")
            .withNetworkElement(networkElementId)
            .withOperator("BE")
            .withTapToAngleConversionMap(validTapToAngleConversionMap);
        OpenRaoException exception = assertThrows(OpenRaoException.class, pstRangeActionAdder::add);
        assertEquals("Cannot add PstRangeAction without a initial tap. Please use withInitialTap() with a non null value", exception.getMessage());
    }

    @Test
    void testNoTapToAngleConversionMap() {
        PstRangeActionAdder pstRangeActionAdder = crac.newPstRangeAction()
            .withId("id1")
            .withNetworkElement(networkElementId)
            .withOperator("BE")
            .withInitialTap(0);
        OpenRaoException exception = assertThrows(OpenRaoException.class, pstRangeActionAdder::add);
        assertEquals("Cannot add PstRangeAction without a tap to angle conversion map. Please use withTapToAngleConversionMap() with a non null value", exception.getMessage());
    }

    @Test
    void testEmptyTapToAngleConversionMap() {
        PstRangeActionAdder pstRangeActionAdder = crac.newPstRangeAction()
            .withId("id1")
            .withNetworkElement(networkElementId)
            .withOperator("BE")
            .withInitialTap(0)
            .withTapToAngleConversionMap(new HashMap<>());
        OpenRaoException exception = assertThrows(OpenRaoException.class, pstRangeActionAdder::add);
        assertEquals("TapToAngleConversionMap of PST id1 should at least contain 2 entries.", exception.getMessage());
    }

    @Test
    void testIncompleteTapToAngleConversionMap() {
        PstRangeActionAdder pstRangeActionAdder = crac.newPstRangeAction()
            .withId("id1")
            .withNetworkElement(networkElementId)
            .withOperator("BE")
            .withInitialTap(0)
            .withTapToAngleConversionMap(Map.of(-2, -20., 2, 20.));
        OpenRaoException exception = assertThrows(OpenRaoException.class, pstRangeActionAdder::add);
        assertEquals("TapToAngleConversionMap of PST id1 should contain all the consecutive taps between -2 and 2", exception.getMessage());
    }

    @Test
    void testNotMonotonousTapToAngleConversionMap() {
        PstRangeActionAdder pstRangeActionAdder = crac.newPstRangeAction()
            .withId("id1")
            .withNetworkElement(networkElementId)
            .withOperator("BE")
            .withInitialTap(0)
            .withTapToAngleConversionMap(Map.of(-2, -20., -1, -15., 0, 0., 1, -10., 2, 20.));
        OpenRaoException exception = assertThrows(OpenRaoException.class, pstRangeActionAdder::add);
        assertEquals("TapToAngleConversionMap of PST id1 should be increasing or decreasing", exception.getMessage());
    }

    @Test
    void testInitialTapNotInMap() {
        PstRangeActionAdder pstRangeActionAdder = crac.newPstRangeAction()
            .withId("id1")
            .withNetworkElement(networkElementId)
            .withOperator("BE")
            .withInitialTap(10)
            .withTapToAngleConversionMap(validTapToAngleConversionMap);
        OpenRaoException exception = assertThrows(OpenRaoException.class, pstRangeActionAdder::add);
        assertEquals("initialTap of PST id1 must be included into its tapToAngleConversionMap", exception.getMessage());
    }

    @Test
    void testPraRelativeToPreviousInstantRange() {
        PstRangeAction pstRangeAction = crac.newPstRangeAction()
            .withId("id1")
            .withNetworkElement(networkElementId)
            .newTapRange()
            .withMinTap(-10).withMaxTap(10).withRangeType(RangeType.RELATIVE_TO_PREVIOUS_INSTANT).add()
            .newOnInstantUsageRule().withInstant(PREVENTIVE_INSTANT_ID).withUsageMethod(UsageMethod.AVAILABLE).add()
            .withInitialTap(-2)
            .withTapToAngleConversionMap(validTapToAngleConversionMap)
            .add();
        assertTrue(pstRangeAction.getRanges().isEmpty());
    }
}