VoltageCnecAdderImplTest.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/.
 */

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

import com.powsybl.openrao.commons.OpenRaoException;
import com.powsybl.openrao.commons.Unit;
import com.powsybl.contingency.Contingency;
import com.powsybl.openrao.data.crac.api.Instant;
import com.powsybl.openrao.data.crac.api.InstantKind;
import com.powsybl.openrao.data.crac.api.cnec.VoltageCnec;
import com.powsybl.openrao.data.crac.api.cnec.VoltageCnecAdder;
import com.powsybl.openrao.data.crac.api.threshold.ThresholdAdder;
import com.powsybl.openrao.data.crac.api.threshold.VoltageThresholdAdder;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.util.Optional;

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

/**
 * @author Philippe Edwards {@literal <philippe.edwards at rte-france.com>}
 */
class VoltageCnecAdderImplTest {
    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 static final String CURATIVE_INSTANT_ID = "curative";
    private static final double DOUBLE_TOLERANCE = 1e-6;

    private CracImpl crac;
    private String contingency1Id = "condId1";
    private Contingency contingency1;
    private VoltageCnec cnec1;
    private VoltageCnec cnec2;
    private Instant preventiveInstant;
    private Instant outageInstant;

    @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)
            .newInstant(CURATIVE_INSTANT_ID, InstantKind.CURATIVE);
        preventiveInstant = crac.getInstant(PREVENTIVE_INSTANT_ID);
        outageInstant = crac.getInstant(OUTAGE_INSTANT_ID);
        contingency1 = crac.newContingency().withId(contingency1Id).add();
    }

    private void createVoltageCnecs() {
        cnec1 = crac.newVoltageCnec()
                .withId("cnecId1")
                .withName("cnecName1")
                .withInstant(OUTAGE_INSTANT_ID)
                .withContingency(contingency1Id)
                .withOperator("cnec1Operator")
                .withNetworkElement("neId1", "neName1")
                .newThreshold().withUnit(Unit.KILOVOLT).withMax(1000.0).withMin(-1000.0).add()
                .add();
        cnec2 = crac.newVoltageCnec()
                .withId("cnecId2")
                .withInstant(PREVENTIVE_INSTANT_ID)
                .withOperator("cnec2Operator")
                .withNetworkElement("neId2")
                .newThreshold().withUnit(Unit.KILOVOLT).withMax(500.0).add()
                .add();
    }

    @Test
    void testCheckCnecs() {
        createVoltageCnecs();
        assertEquals(2, crac.getVoltageCnecs().size());

        // Verify 1st cnec content
        assertEquals(cnec1, crac.getVoltageCnec("cnecId1"));
        assertEquals("cnecName1", cnec1.getName());
        assertEquals(contingency1, cnec1.getState().getContingency().orElseThrow());
        assertEquals(outageInstant, cnec1.getState().getInstant());
        assertEquals("cnec1Operator", cnec1.getOperator());
        assertEquals("neName1", cnec1.getNetworkElement().getName());
        assertEquals(1000.0, cnec1.getUpperBound(Unit.KILOVOLT).orElseThrow(), DOUBLE_TOLERANCE);
        assertEquals(-1000.0, cnec1.getLowerBound(Unit.KILOVOLT).orElseThrow(), DOUBLE_TOLERANCE);

        // Verify 2nd cnec content
        assertEquals(cnec2, crac.getVoltageCnec("cnecId2"));
        assertEquals("cnecId2", cnec2.getName());
        assertEquals(preventiveInstant, cnec2.getState().getInstant());
        assertEquals("cnec2Operator", cnec2.getOperator());
        assertEquals(Optional.empty(), cnec2.getState().getContingency());
        assertEquals("neId2", cnec2.getNetworkElement().getName());
        assertEquals(500.0, cnec2.getUpperBound(Unit.KILOVOLT).orElseThrow(), DOUBLE_TOLERANCE);
        assertFalse(cnec2.getLowerBound(Unit.KILOVOLT).isPresent());

    }

    @Test
    void testAdd() {
        createVoltageCnecs();
        // Verify that network elements were created
        crac.newVoltageCnec()
            .withId("cnecId3")
            .withInstant(PREVENTIVE_INSTANT_ID)
            .withOperator("cnec2Operator")
            .withNetworkElement("neId2") // same as cnec2
            .newThreshold().withUnit(Unit.KILOVOLT).withMax(500.0).add()
            .add();
        assertEquals(2, crac.getNetworkElements().size());
        assertNotNull(crac.getNetworkElement("neId1"));
        assertNotNull(crac.getNetworkElement("neId2"));

        // Verify states were created
        assertEquals(2, crac.getStates().size());
        assertNotNull(crac.getPreventiveState());
        assertNotNull(crac.getState(contingency1Id, outageInstant));
    }

    @Test
    void testReliabilityMarginHandling() {
        double maxValue = 100.0;
        double reliabilityMargin = 5.0;
        VoltageCnec cnec = crac.newVoltageCnec().withId("Cnec ID")
            .withInstant(OUTAGE_INSTANT_ID)
            .withContingency(contingency1Id)
            .withNetworkElement("neId")
            .newThreshold().withUnit(Unit.KILOVOLT).withMax(maxValue).withMin(-maxValue).add()
            .withReliabilityMargin(reliabilityMargin)
            .add();
        assertEquals(maxValue - reliabilityMargin, cnec.getUpperBound(Unit.KILOVOLT).orElseThrow(OpenRaoException::new), DOUBLE_TOLERANCE);
        assertEquals(reliabilityMargin - maxValue, cnec.getLowerBound(Unit.KILOVOLT).orElseThrow(OpenRaoException::new), DOUBLE_TOLERANCE);
    }

    @Test
    void testNotOptimizedMonitored() {
        VoltageCnec cnec = crac.newVoltageCnec().withId("Cnec ID")
            .withInstant(OUTAGE_INSTANT_ID)
            .withContingency(contingency1Id)
            .withNetworkElement("neId")
            .newThreshold().withUnit(Unit.KILOVOLT).withMax(100.0).withMin(-100.0).add()
            .withMonitored()
            .add();
        assertFalse(cnec.isOptimized());
        assertTrue(cnec.isMonitored());
    }

    @Test
    void testOptimizedNotMonitored() {
        VoltageCnecAdder voltageCnecAdder = crac.newVoltageCnec().withId("Cnec ID")
            .withInstant(OUTAGE_INSTANT_ID)
            .withContingency(contingency1Id)
            .withNetworkElement("neId")
            .newThreshold().withUnit(Unit.KILOVOLT).withMax(100.0).withMin(-100.0).add()
            .withOptimized();
        OpenRaoException exception = assertThrows(OpenRaoException.class, voltageCnecAdder::add);
        assertEquals("Error while adding cnec Cnec ID : Open RAO does not allow the optimization of VoltageCnecs.", exception.getMessage());
    }

    @Test
    void testNotOptimizedNotMonitored() {
        VoltageCnec cnec = crac.newVoltageCnec().withId("Cnec ID")
            .withInstant(OUTAGE_INSTANT_ID)
            .withContingency(contingency1Id)
            .withNetworkElement("neId")
            .newThreshold().withUnit(Unit.KILOVOLT).withMax(100.0).withMin(-100.0).add()
            .add();
        assertFalse(cnec.isOptimized());
        assertFalse(cnec.isMonitored());
    }

    @Test
    void testNotOptimizedNotMonitored2() {
        VoltageCnec cnec = crac.newVoltageCnec().withId("Cnec ID")
            .withInstant(OUTAGE_INSTANT_ID)
            .withContingency(contingency1Id)
            .withNetworkElement("neId")
            .newThreshold().withUnit(Unit.KILOVOLT).withMax(100.0).withMin(-100.0).add()
            .withOptimized(false)
            .withMonitored(false)
            .add();
        assertFalse(cnec.isOptimized());
        assertFalse(cnec.isMonitored());
    }

    @Test
    void testNullParentFail() {
        assertThrows(NullPointerException.class, () -> new VoltageCnecAdderImpl(null));
    }

    @Test
    void testNoIdFail() {
        VoltageCnecAdder voltageCnecAdder = crac.newVoltageCnec()
            .withName("cnecName")
            .withInstant(OUTAGE_INSTANT_ID)
            .withContingency(contingency1Id)
            .withNetworkElement("neId")
            .newThreshold().withUnit(Unit.KILOVOLT).withMax(100.0).withMin(-100.0).add();
        OpenRaoException exception = assertThrows(OpenRaoException.class, voltageCnecAdder::add);
        assertEquals("Cannot add a VoltageCnec object with no specified id. Please use withId()", exception.getMessage());
    }

    @Test
    void testNoStateInstantFail() {
        VoltageCnecAdder voltageCnecAdder = crac.newVoltageCnec()
            .withId("cnecId")
            .withContingency(contingency1Id)
            .withNetworkElement("neId")
            .newThreshold().withUnit(Unit.KILOVOLT).withMax(100.0).withMin(-100.0).add();
        OpenRaoException exception = assertThrows(OpenRaoException.class, voltageCnecAdder::add);
        assertEquals("Cannot add Cnec without a instant. Please use withInstant() with a non null value", exception.getMessage());
    }

    @Test
    void testNoNetworkElementFail() {
        VoltageCnecAdder voltageCnecAdder = crac.newVoltageCnec()
            .withId("cnecId")
            .withInstant(OUTAGE_INSTANT_ID)
            .withContingency(contingency1Id)
            .newThreshold().withUnit(Unit.KILOVOLT).withMax(100.0).withMin(-100.0).add();
        OpenRaoException exception = assertThrows(OpenRaoException.class, voltageCnecAdder::add);
        assertEquals("Cannot add Cnec without a network element. Please use withNetworkElement()", exception.getMessage());
    }

    @Test
    void testNoThresholdFail() {
        VoltageCnecAdder voltageCnecAdder = crac.newVoltageCnec()
            .withId("cnecId")
            .withInstant(OUTAGE_INSTANT_ID)
            .withContingency(contingency1Id)
            .withNetworkElement("neId");
        OpenRaoException exception = assertThrows(OpenRaoException.class, voltageCnecAdder::add);
        assertEquals("Cannot add an VoltageCnec without a threshold. Please use newThreshold", exception.getMessage());
    }

    @Test
    void testAddTwiceError() {
        crac.newVoltageCnec()
            .withId("cnecId")
            .withInstant(OUTAGE_INSTANT_ID)
            .withContingency(contingency1Id)
            .withNetworkElement("neId")
            .newThreshold().withUnit(Unit.KILOVOLT).withMax(100.0).withMin(-100.0).add()
            .add();
        VoltageCnecAdder voltageCnecAdder = crac.newVoltageCnec()
            .withId("cnecId")
            .withInstant(OUTAGE_INSTANT_ID)
            .withContingency(contingency1Id)
            .withNetworkElement("neId")
            .newThreshold().withUnit(Unit.KILOVOLT).withMax(100.0).withMin(-100.0).add();
        OpenRaoException exception = assertThrows(OpenRaoException.class, voltageCnecAdder::add);
        assertEquals("Cannot add a cnec with an already existing ID - cnecId.", exception.getMessage());
    }

    @Test
    void testAddPreventiveCnecWithContingencyError() {
        VoltageCnecAdder voltageCnecAdder = crac.newVoltageCnec()
            .withId("cnecId")
            .withInstant(PREVENTIVE_INSTANT_ID)
            .withContingency(contingency1Id)
            .withNetworkElement("neId")
            .newThreshold().withUnit(Unit.KILOVOLT).withMax(100.0).withMin(-100.0).add();
        OpenRaoException exception = assertThrows(OpenRaoException.class, voltageCnecAdder::add);
        assertEquals("You cannot define a contingency for a preventive cnec.", exception.getMessage());
    }

    @Test
    void testAddOutageCnecWithNoContingencyError() {
        VoltageCnecAdder voltageCnecAdder = crac.newVoltageCnec()
            .withId("cnecId")
            .withInstant(OUTAGE_INSTANT_ID)
            .withNetworkElement("neId")
            .newThreshold().withUnit(Unit.KILOVOLT).withMax(100.0).withMin(-100.0).add();
        OpenRaoException exception = assertThrows(OpenRaoException.class, voltageCnecAdder::add);
        assertEquals("You must define a contingency for a non-preventive cnec.", exception.getMessage());
    }

    @Test
    void testAddAutoCnecWithNoContingencyError() {
        VoltageCnecAdder voltageCnecAdder = crac.newVoltageCnec()
            .withId("cnecId")
            .withInstant(AUTO_INSTANT_ID)
            .withNetworkElement("neId")
            .newThreshold().withUnit(Unit.KILOVOLT).withMax(100.0).withMin(-100.0).add();
        OpenRaoException exception = assertThrows(OpenRaoException.class, voltageCnecAdder::add);
        assertEquals("You must define a contingency for a non-preventive cnec.", exception.getMessage());
    }

    @Test
    void testAddCurativeCnecWithNoContingencyError() {
        VoltageCnecAdder voltageCnecAdder = crac.newVoltageCnec()
            .withId("cnecId")
            .withInstant(CURATIVE_INSTANT_ID)
            .withNetworkElement("neId")
            .newThreshold().withUnit(Unit.KILOVOLT).withMax(100.0).withMin(-100.0).add();
        OpenRaoException exception = assertThrows(OpenRaoException.class, voltageCnecAdder::add);
        assertEquals("You must define a contingency for a non-preventive cnec.", exception.getMessage());
    }

    @Test
    void testAddCurativeCnecWithAbsentContingencyError() {
        VoltageCnecAdder voltageCnecAdder = crac.newVoltageCnec()
            .withId("cnecId")
            .withInstant(CURATIVE_INSTANT_ID)
            .withContingency("absent-from-crac")
            .withNetworkElement("neId")
            .newThreshold().withUnit(Unit.KILOVOLT).withMax(100.0).withMin(-100.0).add();
        OpenRaoException exception = assertThrows(OpenRaoException.class, voltageCnecAdder::add);
        assertEquals("Contingency absent-from-crac of Cnec cnecId does not exist in the crac. Use crac.newContingency() first.", exception.getMessage());
    }

    @Test
    void testThresholdWithUnitAmpere() {
        ThresholdAdder<VoltageThresholdAdder> thresholdAdder = crac.newVoltageCnec()
            .withId("cnecId")
            .withInstant(OUTAGE_INSTANT_ID)
            .withContingency(contingency1Id)
            .withNetworkElement("neId")
            .newThreshold();
        OpenRaoException exception = assertThrows(OpenRaoException.class, () -> thresholdAdder.withUnit(Unit.AMPERE));
        assertEquals("A Unit is not suited to measure a VOLTAGE value.", exception.getMessage());
    }
}