AbstractOperationalLimitsGroupsTest.java

/*
 * Copyright (c) 2023, 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.iidm.network.tck;

import com.powsybl.iidm.network.*;
import org.junit.jupiter.api.Test;

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

/**
 * @author Pauline Jean-Marie {@literal <pauline.jean-marie at artelys.com>}
 */
public abstract class AbstractOperationalLimitsGroupsTest {

    private static Network createNetworkWithLine() {
        Network network = Network.create("test", "test");
        Substation s1 = network.newSubstation().setId("S1").setCountry(Country.FR).add();
        VoltageLevel vl1 = s1.newVoltageLevel()
                .setId("VL1")
                .setNominalV(400.0)
                .setTopologyKind(TopologyKind.BUS_BREAKER)
                .add();
        vl1.getBusBreakerView().newBus().setId("B1").add();
        Substation s2 = network.newSubstation().setId("S2").setCountry(Country.FR).add();
        VoltageLevel vl2 = s2.newVoltageLevel()
                .setId("VL2")
                .setNominalV(400.0)
                .setTopologyKind(TopologyKind.BUS_BREAKER)
                .add();
        vl2.getBusBreakerView().newBus().setId("B2").add();
        network.newLine()
                .setId("L")
                .setVoltageLevel1("VL1")
                .setConnectableBus1("B1")
                .setBus1("B1")
                .setVoltageLevel2("VL2")
                .setConnectableBus2("B2")
                .setBus2("B2")
                .setR(1.0)
                .setX(1.0)
                .setG1(0.0)
                .setG2(0.0)
                .setB1(0.0)
                .setB2(0.0)
                .add();
        return network;
    }

    private static Network createNetworkWithOperationalLimitsGroupsOnLine() {
        Network network = createNetworkWithLine();
        Line l = network.getLine("L");
        l.newOperationalLimitsGroup1("1")
                .newCurrentLimits()
                .setPermanentLimit(900.0)
                .beginTemporaryLimit()
                .setName("20'")
                .setAcceptableDuration(20 * 60)
                .setValue(1100.0)
                .endTemporaryLimit()
                .beginTemporaryLimit()
                .setName("5'")
                .setAcceptableDuration(5 * 60)
                .setValue(1300.0)
                .endTemporaryLimit()
                .beginTemporaryLimit()
                .setName("1'")
                .setAcceptableDuration(60)
                .setValue(1500.0)
                .endTemporaryLimit()
                .add();
        l.newOperationalLimitsGroup1("2")
                .newActivePowerLimits()
                .setPermanentLimit(750.0)
                .beginTemporaryLimit()
                .setName("25'")
                .setAcceptableDuration(25 * 60)
                .setValue(1250.0)
                .endTemporaryLimit()
                .beginTemporaryLimit()
                .setName("7'")
                .setAcceptableDuration(7 * 60)
                .setValue(1350.0)
                .endTemporaryLimit()
                .beginTemporaryLimit()
                .setName("2'")
                .setAcceptableDuration(2 * 60)
                .setValue(1550.0)
                .endTemporaryLimit()
                .add();
        l.getOperationalLimitsGroup1("2")
                .ifPresent(olg -> olg.newCurrentLimits()
                        .setPermanentLimit(1000.0)
                        .beginTemporaryLimit()
                        .setName("20'")
                        .setAcceptableDuration(20 * 60)
                        .setValue(1200.0)
                        .endTemporaryLimit()
                        .beginTemporaryLimit()
                        .setName("5'")
                        .setAcceptableDuration(5 * 60)
                        .setValue(1400.0)
                        .endTemporaryLimit()
                        .beginTemporaryLimit()
                        .setName("1'")
                        .setAcceptableDuration(60)
                        .setValue(1600.0)
                        .endTemporaryLimit()
                        .add());
        l.getOperationalLimitsGroup1("2")
                .ifPresent(olg -> olg.newApparentPowerLimits()
                        .setPermanentLimit(890.0)
                        .add());
        l.newOperationalLimitsGroup1("3").newApparentPowerLimits().setPermanentLimit(850.0).add();
        l.getOperationalLimitsGroup1("3")
                .ifPresent(olg -> olg.newCurrentLimits()
                        .setPermanentLimit(1000.0)
                        .add());
        l.newOperationalLimitsGroup2("1").newCurrentLimits().setPermanentLimit(850.0).add();
        l.getOperationalLimitsGroup2("1")
                .flatMap(OperationalLimitsGroup::getCurrentLimits)
                .ifPresent(cl -> cl.setPermanentLimit(800.0));
        l.setSelectedOperationalLimitsGroup2("1");
        l.getOperationalLimitsGroup2("1")
                .ifPresent(olg -> olg.newApparentPowerLimits()
                        .setPermanentLimit(1100.0)
                        .add());
        l.getOperationalLimitsGroup2("1")
                .flatMap(OperationalLimitsGroup::getApparentPowerLimits)
                .ifPresent(apl -> apl.setPermanentLimit(900.0));
        return network;
    }

    @Test
    public void testForOperationalLimitsGroupsOnLine() {
        Network network = createNetworkWithOperationalLimitsGroupsOnLine();
        Line l = network.getLine("L");

        // Set some values for limits on side 1
        l.getTerminal1().getBusBreakerView().getBus().setV(390.0);
        l.getTerminal1().setP(800.0).setQ(400.0); // i = 1324.0969
        assertFalse(Double.isNaN(l.getTerminal1().getI()));

        checkBehaveLikeNoLimitsWhenNoDefaultOneOnLine(l);

        checkDefaultLimitsOnLine(l);

        checkChangeOfDefaultLimitsOnLine(l);

        checkRemoveNonDefaultLimitsHasNoImpactOnLine(l);

        checkRemoveOnDefaultLimitsHasAnImpactOnLine(l);

        checkRemoveDefaultLimitsOnLine(l);

        // Set some values for limits on side 2
        l.getTerminal2().getBusBreakerView().getBus().setV(390.0);
        l.getTerminal2().setP(900.0).setQ(500.0); // i = 1524.1499
        assertFalse(Double.isNaN(l.getTerminal2().getI()));

        checkLimitsOnSecondHolder(l);

        checkBehaveLikeNoLimitsWhenCancelDefaultLimits(l);
    }

    private static void checkBehaveLikeNoLimitsWhenCancelDefaultLimits(Line l) {
        // cancel default limits, no group is the default limits, impact expected, no more limits
        l.cancelSelectedOperationalLimitsGroup2();
        assertFalse(l.checkPermanentLimit2(LimitType.CURRENT));
        assertFalse(l.checkPermanentLimit2(LimitType.ACTIVE_POWER));
        assertFalse(l.checkPermanentLimit2(LimitType.APPARENT_POWER));
    }

    private static void checkLimitsOnSecondHolder(Line l) {
        // A group by default is defined and fill, but no active power defined
        assertTrue(l.checkPermanentLimit2(LimitType.CURRENT));
        assertFalse(l.checkPermanentLimit2(LimitType.ACTIVE_POWER));
        assertTrue(l.checkPermanentLimit2(LimitType.APPARENT_POWER));
    }

    private static void checkRemoveDefaultLimitsOnLine(Line l) {
        // Remove default limits group, impact expected, no more limits
        l.removeOperationalLimitsGroup1("3");
        assertFalse(l.isOverloaded());
        assertEquals(2147483647L, l.getOverloadDuration());
        assertFalse(l.checkPermanentLimit1(LimitType.CURRENT));
        assertFalse(l.checkPermanentLimit1(LimitType.ACTIVE_POWER));
        assertFalse(l.checkPermanentLimit1(LimitType.APPARENT_POWER));
    }

    private static void checkRemoveOnDefaultLimitsHasAnImpactOnLine(Line l) {
        // Remove limits inside default limits group, impact expected, no more current limits
        l.getOperationalLimitsGroup1("3").ifPresent(OperationalLimitsGroup::removeCurrentLimits);
        assertFalse(l.isOverloaded());
        assertEquals(2147483647L, l.getOverloadDuration());
        assertFalse(l.checkPermanentLimit1(LimitType.CURRENT));
        assertFalse(l.checkPermanentLimit1(LimitType.ACTIVE_POWER));
        assertTrue(l.checkPermanentLimit1(LimitType.APPARENT_POWER));
        assertFalse(l.getOperationalLimitsGroup1("3").map(OperationalLimitsGroup::isEmpty).orElse(true));
    }

    private static void checkRemoveNonDefaultLimitsHasNoImpactOnLine(Line l) {
        // Remove limits inside non default limits group, so no impact expected
        l.getOperationalLimitsGroup1("2").ifPresent(OperationalLimitsGroup::removeCurrentLimits);
        assertTrue(l.isOverloaded());
        assertEquals(2147483647L, l.getOverloadDuration());
        assertTrue(l.checkPermanentLimit1(LimitType.CURRENT));
        assertFalse(l.checkPermanentLimit1(LimitType.ACTIVE_POWER));
        assertTrue(l.checkPermanentLimit1(LimitType.APPARENT_POWER));
    }

    private static void checkChangeOfDefaultLimitsOnLine(Line l) {
        // Change default limits group, so active limits is changed, this one have no temporary limits
        l.setSelectedOperationalLimitsGroup1("3");
        assertTrue(l.isOverloaded());
        assertEquals(2147483647L, l.getOverloadDuration());
        assertTrue(l.checkPermanentLimit1(LimitType.CURRENT));
        assertNull(l.checkTemporaryLimits1(LimitType.CURRENT));
        assertFalse(l.checkPermanentLimit1(LimitType.ACTIVE_POWER));
        assertNull(l.checkTemporaryLimits1(LimitType.ACTIVE_POWER));
        assertTrue(l.checkPermanentLimit1(LimitType.APPARENT_POWER));
        assertNull(l.checkTemporaryLimits1(LimitType.APPARENT_POWER));
    }

    private static void checkDefaultLimitsOnLine(Line l) {
        // Set a default limits group, so we have an active limits
        l.setSelectedOperationalLimitsGroup1("2");
        assertTrue(l.isOverloaded());
        assertEquals(5 * 60L, l.getOverloadDuration());
        assertTrue(l.checkPermanentLimit1(LimitType.CURRENT));
        assertNotNull(l.checkTemporaryLimits1(LimitType.CURRENT));
        assertEquals(5 * 60L, l.checkTemporaryLimits1(LimitType.CURRENT).getTemporaryLimit().getAcceptableDuration());
        assertEquals(1200.0, l.checkTemporaryLimits1(LimitType.CURRENT).getPreviousLimit(), 0.0);
        assertTrue(l.checkPermanentLimit1(LimitType.ACTIVE_POWER));
        assertNotNull(l.checkTemporaryLimits1(LimitType.ACTIVE_POWER));
        assertEquals(25 * 60L, l.checkTemporaryLimits1(LimitType.ACTIVE_POWER)
                .getTemporaryLimit()
                .getAcceptableDuration());
        assertEquals(750.0, l.checkTemporaryLimits1(LimitType.ACTIVE_POWER).getPreviousLimit(), 0.0);
        assertTrue(l.checkPermanentLimit1(LimitType.APPARENT_POWER));
        assertNull(l.checkTemporaryLimits1(LimitType.APPARENT_POWER));
    }

    private static void checkBehaveLikeNoLimitsWhenNoDefaultOneOnLine(Line l) {
        // No limits group by default, so no limits, so everything is false or not-equals
        assertFalse(l.isOverloaded());
        assertNotEquals(5 * 60L, l.getOverloadDuration());
        assertFalse(l.checkPermanentLimit1(LimitType.CURRENT));
        assertNull(l.checkTemporaryLimits1(LimitType.CURRENT));
        assertFalse(l.checkPermanentLimit1(LimitType.ACTIVE_POWER));
        assertNull(l.checkTemporaryLimits1(LimitType.ACTIVE_POWER));
        assertFalse(l.checkPermanentLimit1(LimitType.APPARENT_POWER));
        assertNull(l.checkTemporaryLimits1(LimitType.APPARENT_POWER));
    }

    private static Network createNetworkWithTWT() {
        Network network = Network.create("test_3wt", "test");
        Substation s1 = network.newSubstation()
                .setId("S1")
                .setCountry(Country.FR)
                .add();
        VoltageLevel vl1 = s1.newVoltageLevel()
                .setId("VL1")
                .setNominalV(400.0)
                .setTopologyKind(TopologyKind.BUS_BREAKER)
                .add();
        vl1.getBusBreakerView().newBus()
                .setId("B1")
                .add();
        VoltageLevel vl2 = s1.newVoltageLevel()
                .setId("VL2")
                .setNominalV(400.0)
                .setTopologyKind(TopologyKind.BUS_BREAKER)
                .add();
        vl2.getBusBreakerView().newBus()
                .setId("B2")
                .add();
        VoltageLevel vl3 = s1.newVoltageLevel()
                .setId("VL3")
                .setNominalV(400.0)
                .setTopologyKind(TopologyKind.BUS_BREAKER)
                .add();
        vl3.getBusBreakerView().newBus()
                .setId("B3")
                .add();
        s1.newThreeWindingsTransformer()
                .setId("3WT")
                .setRatedU0(132.0)
                .newLeg1()
                .setR(1.0)
                .setX(1.0)
                .setG(0.0)
                .setB(0.0)
                .setRatedU(132.0)
                .setVoltageLevel("VL1")
                .setBus("B1")
                .add()
                .newLeg2() // not used for the test
                .setR(1.0)
                .setX(1.0)
                .setG(0.0)
                .setB(0.0)
                .setRatedU(132.0)
                .setVoltageLevel("VL2")
                .setBus("B2")
                .add()
                .newLeg3() // not used for the test
                .setR(1.0)
                .setX(1.0)
                .setG(0.0)
                .setB(0.0)
                .setRatedU(132.0)
                .setVoltageLevel("VL3")
                .setBus("B3")
                .add()
                .add();
        return network;
    }

    private static Network createNetworkWithOperationalLimitsGroupsOnTWTLeg3() {
        Network network = createNetworkWithTWT();
        ThreeWindingsTransformer.Leg l = network.getThreeWindingsTransformer("3WT").getLeg3();
        l.newOperationalLimitsGroup("1")
                .newCurrentLimits()
                .setPermanentLimit(900.0)
                .beginTemporaryLimit()
                .setName("20'")
                .setAcceptableDuration(20 * 60)
                .setValue(1100.0)
                .endTemporaryLimit()
                .beginTemporaryLimit()
                .setName("5'")
                .setAcceptableDuration(5 * 60)
                .setValue(1300.0)
                .endTemporaryLimit()
                .beginTemporaryLimit()
                .setName("1'")
                .setAcceptableDuration(60)
                .setValue(1500.0)
                .endTemporaryLimit()
                .add();
        l.newOperationalLimitsGroup("2")
                .newActivePowerLimits()
                .setPermanentLimit(750.0)
                .beginTemporaryLimit()
                .setName("25'")
                .setAcceptableDuration(25 * 60)
                .setValue(1250.0)
                .endTemporaryLimit()
                .beginTemporaryLimit()
                .setName("7'")
                .setAcceptableDuration(7 * 60)
                .setValue(1350.0)
                .endTemporaryLimit()
                .beginTemporaryLimit()
                .setName("2'")
                .setAcceptableDuration(2 * 60)
                .setValue(1550.0)
                .endTemporaryLimit()
                .add();
        l.getOperationalLimitsGroup("2")
                .ifPresent(olg -> olg.newCurrentLimits()
                        .setPermanentLimit(1000.0)
                        .beginTemporaryLimit()
                        .setName("20'")
                        .setAcceptableDuration(20 * 60)
                        .setValue(1200.0)
                        .endTemporaryLimit()
                        .beginTemporaryLimit()
                        .setName("5'")
                        .setAcceptableDuration(5 * 60)
                        .setValue(1400.0)
                        .endTemporaryLimit()
                        .beginTemporaryLimit()
                        .setName("1'")
                        .setAcceptableDuration(60)
                        .setValue(1600.0)
                        .endTemporaryLimit()
                        .add());
        l.getOperationalLimitsGroup("2")
                .ifPresent(olg -> olg.newApparentPowerLimits()
                        .setPermanentLimit(890.0)
                        .add());
        l.newOperationalLimitsGroup("3").newApparentPowerLimits().setPermanentLimit(850.0).add();
        l.getOperationalLimitsGroup("3")
                .ifPresent(olg -> olg.newCurrentLimits()
                        .setPermanentLimit(1000.0)
                        .add());
        return network;
    }

    @Test
    public void testForOperationalLimitsGroupsOn3WTLeg3() {
        Network network = createNetworkWithOperationalLimitsGroupsOnTWTLeg3();
        ThreeWindingsTransformer t = network.getThreeWindingsTransformer("3WT");
        ThreeWindingsTransformer.Leg l = t.getLeg3();

        // Set some values for limits on side 1
        l.getTerminal().getBusBreakerView().getBus().setV(390.0);
        l.getTerminal().setP(800.0).setQ(400.0); // i = 1324.0969
        assertFalse(Double.isNaN(l.getTerminal().getI()));

        checkBehaveLikeNoLimitsWhenNoDefaultOneOnTWT(t);

        checkDefaultLimitsOnTWT(l, t);

        checkChangeOfDefaultLimitsOnTWT(l, t);

        checkRemoveNonDefaultLimitsHasNoImpactOnTWT(l, t);

        checkRemoveOnDefaultLimitsHasAnImpactOnTWT(l, t);

        checkRemoveDefaultLimitsOnTWT(l, t);
    }

    private static void checkRemoveDefaultLimitsOnTWT(ThreeWindingsTransformer.Leg l, ThreeWindingsTransformer t) {
        // Remove default limits group, impact expected, no more limits
        l.removeOperationalLimitsGroup("3");
        assertFalse(t.isOverloaded());
        assertEquals(2147483647L, t.getOverloadDuration());
        assertFalse(t.checkPermanentLimit3(LimitType.CURRENT));
        assertFalse(t.checkPermanentLimit3(LimitType.ACTIVE_POWER));
        assertFalse(t.checkPermanentLimit3(LimitType.APPARENT_POWER));
    }

    private static void checkRemoveOnDefaultLimitsHasAnImpactOnTWT(ThreeWindingsTransformer.Leg l, ThreeWindingsTransformer t) {
        // Remove limits inside default limits group, impact expected, no more current limits
        l.getOperationalLimitsGroup("3").ifPresent(OperationalLimitsGroup::removeCurrentLimits);
        assertFalse(t.isOverloaded());
        assertEquals(2147483647L, t.getOverloadDuration());
        assertFalse(t.checkPermanentLimit3(LimitType.CURRENT));
        assertFalse(t.checkPermanentLimit3(LimitType.ACTIVE_POWER));
        assertTrue(t.checkPermanentLimit3(LimitType.APPARENT_POWER));
        assertFalse(l.getOperationalLimitsGroup("3").map(OperationalLimitsGroup::isEmpty).orElse(true));
    }

    private static void checkRemoveNonDefaultLimitsHasNoImpactOnTWT(ThreeWindingsTransformer.Leg l, ThreeWindingsTransformer t) {
        // Remove limits inside non default limits group, so no impact expected
        l.getOperationalLimitsGroup("2").ifPresent(OperationalLimitsGroup::removeCurrentLimits);
        assertTrue(t.isOverloaded());
        assertEquals(2147483647L, t.getOverloadDuration());
        assertTrue(t.checkPermanentLimit3(LimitType.CURRENT));
        assertFalse(t.checkPermanentLimit3(LimitType.ACTIVE_POWER));
        assertTrue(t.checkPermanentLimit3(LimitType.APPARENT_POWER));
    }

    private static void checkChangeOfDefaultLimitsOnTWT(ThreeWindingsTransformer.Leg l, ThreeWindingsTransformer t) {
        // Change default limits group, so active limits is changed, this one have no temporary limits
        l.setSelectedOperationalLimitsGroup("3");
        assertTrue(t.isOverloaded());
        assertEquals(2147483647L, t.getOverloadDuration());
        assertTrue(t.checkPermanentLimit3(LimitType.CURRENT));
        assertNull(t.checkTemporaryLimits1(LimitType.CURRENT));
        assertFalse(t.checkPermanentLimit3(LimitType.ACTIVE_POWER));
        assertNull(t.checkTemporaryLimits1(LimitType.ACTIVE_POWER));
        assertTrue(t.checkPermanentLimit3(LimitType.APPARENT_POWER));
        assertNull(t.checkTemporaryLimits1(LimitType.APPARENT_POWER));
    }

    private static void checkDefaultLimitsOnTWT(ThreeWindingsTransformer.Leg l, ThreeWindingsTransformer t) {
        // Set a default limits group, so we have an active limits
        l.setSelectedOperationalLimitsGroup("2");
        assertTrue(t.isOverloaded());
        assertEquals(5 * 60L, t.getOverloadDuration());
        assertTrue(t.checkPermanentLimit3(LimitType.CURRENT));
        assertNotNull(t.checkTemporaryLimits3(LimitType.CURRENT));
        assertEquals(5 * 60L, t.checkTemporaryLimits3(LimitType.CURRENT).getTemporaryLimit().getAcceptableDuration());
        assertEquals(1200.0, t.checkTemporaryLimits3(LimitType.CURRENT).getPreviousLimit(), 0.0);
        assertTrue(t.checkPermanentLimit3(LimitType.ACTIVE_POWER));
        assertNotNull(t.checkTemporaryLimits3(LimitType.ACTIVE_POWER));
        assertEquals(25 * 60L, t.checkTemporaryLimits3(LimitType.ACTIVE_POWER)
                .getTemporaryLimit()
                .getAcceptableDuration());
        assertEquals(750.0, t.checkTemporaryLimits3(LimitType.ACTIVE_POWER).getPreviousLimit(), 0.0);
        assertTrue(t.checkPermanentLimit3(LimitType.APPARENT_POWER));
        assertNull(t.checkTemporaryLimits3(LimitType.APPARENT_POWER));
    }

    private static void checkBehaveLikeNoLimitsWhenNoDefaultOneOnTWT(ThreeWindingsTransformer t) {
        // No limits group by default, so no limits, so everything is false or not-equals
        assertFalse(t.isOverloaded());
        assertNotEquals(5 * 60L, t.getOverloadDuration());
        assertFalse(t.checkPermanentLimit3(LimitType.CURRENT));
        assertNull(t.checkTemporaryLimits3(LimitType.CURRENT));
        assertFalse(t.checkPermanentLimit3(LimitType.ACTIVE_POWER));
        assertNull(t.checkTemporaryLimits3(LimitType.ACTIVE_POWER));
        assertFalse(t.checkPermanentLimit3(LimitType.APPARENT_POWER));
        assertNull(t.checkTemporaryLimits3(LimitType.APPARENT_POWER));
    }
}