LimitViolationDetectorTest.java

/**
 * Copyright (c) 2019, 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.security.detectors;

import com.powsybl.contingency.Contingency;
import com.powsybl.iidm.network.*;
import com.powsybl.iidm.network.test.EurostagTutorialExample1Factory;
import com.powsybl.security.LimitViolation;
import com.powsybl.security.LimitViolationType;
import com.powsybl.security.LimitViolations;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;

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

/**
 * @author Sylvain Leclerc {@literal <sylvain.leclerc at rte-france.com>}
 */
class LimitViolationDetectorTest {

    private Network network;
    private Branch line1;
    private Branch line2;
    private Branch transformer;
    private Contingency contingency1;
    private Contingency contingency2;
    private VoltageLevel voltageLevel1;
    private VoltageLevel genVoltageLevel;
    private VoltageLevel loadVoltageLevel;
    private VoltageAngleLimit voltageAngleLimit0;
    private VoltageAngleLimit voltageAngleLimit1;
    private VoltageAngleLimit voltageAngleLimit2;
    private List<LimitViolation> collectedViolations;

    private void doCheckCurrent(Contingency contingency, Branch branch, TwoSides side, Consumer<LimitViolation> consumer) {

        if (branch == line1) {
            consumer.accept(LimitViolations.current()
                    .subject(branch.getId())
                    .side(side)
                    .duration(1200)
                    .value(1500)
                    .limit(1200)
                    .build());
        }

        if (branch == line2 && side == TwoSides.TWO) {
            consumer.accept(LimitViolations.current()
                    .subject(branch.getId())
                    .duration(1200)
                    .side(side)
                    .value(1500)
                    .limit(1200)
                    .build());
        }

        if (contingency == contingency1
                && branch == transformer && side == TwoSides.ONE) {
            consumer.accept(LimitViolations.current()
                    .subject(branch.getId())
                    .duration(1200)
                    .side(side)
                    .value(1500)
                    .limit(1200)
                    .build());
        }
    }

    private void doCheckVoltage(Contingency contingency, Bus bus, Consumer<LimitViolation> consumer) {
        if (bus.getVoltageLevel() == voltageLevel1) {
            consumer.accept(LimitViolations.highVoltage()
                    .subject(bus.getVoltageLevel().getId())
                    .value(500)
                    .limit(420)
                    .build());
        }

        if (bus.getVoltageLevel() == genVoltageLevel) {
            consumer.accept(LimitViolations.lowVoltage()
                    .subject(bus.getVoltageLevel().getId())
                    .value(350)
                    .limit(380)
                    .build());
        }

        if (contingency == contingency1 && bus.getVoltageLevel() == loadVoltageLevel) {
            consumer.accept(LimitViolations.highVoltage()
                    .subject(bus.getVoltageLevel().getId())
                    .value(500)
                    .limit(420)
                    .build());
        }
    }

    private void doCheckVoltageAngle(Contingency contingency, VoltageAngleLimit voltageAngleLimit, Consumer<LimitViolation> consumer) {
        if (voltageAngleLimit == voltageAngleLimit0) {
            consumer.accept(LimitViolations.highVoltageAngle()
                .subject(voltageAngleLimit.getTerminalFrom().getConnectable().getId())
                .side(TwoSides.ONE)
                .value(0.30)
                .limit(0.25)
                .build());
        }
        if (contingency == contingency1 && voltageAngleLimit == voltageAngleLimit1) {
            consumer.accept(LimitViolations.lowVoltageAngle()
                .subject(voltageAngleLimit.getTerminalFrom().getConnectable().getId())
                .side(TwoSides.ONE)
                .value(-0.22)
                .limit(0.20)
                .build());
        }
    }

    private LimitViolationDetector contingencyBlindDetector() {
        return new AbstractContingencyBlindDetector() {
            @Override
            public void checkCurrent(Branch branch, TwoSides side, double currentValue, Consumer<LimitViolation> consumer) {
                doCheckCurrent(null, branch, side, consumer);
            }

            @Override
            public void checkCurrent(ThreeWindingsTransformer transformer, ThreeSides side, double currentValue, Consumer<LimitViolation> consumer) {
                throw new UnsupportedOperationException("Not used in this test!");
            }

            @Override
            public void checkVoltage(Bus bus, double voltageValue, Consumer<LimitViolation> consumer) {
                doCheckVoltage(null, bus, consumer);
            }

            @Override
            public void checkVoltageAngle(VoltageAngleLimit voltageAngleLimit, double voltageAngleDifference, Consumer<LimitViolation> consumer) {
                doCheckVoltageAngle(null, voltageAngleLimit, consumer);
            }

            @Override
            public void checkActivePower(Branch branch, TwoSides side, double currentValue, Consumer<LimitViolation> consumer) {
            }

            @Override
            public void checkApparentPower(Branch branch, TwoSides side, double currentValue, Consumer<LimitViolation> consumer) {
            }

            @Override
            public void checkActivePower(ThreeWindingsTransformer transformer, ThreeSides side, double currentValue, Consumer<LimitViolation> consumer) {
                throw new UnsupportedOperationException("Not used in this test!");
            }

            @Override
            public void checkApparentPower(ThreeWindingsTransformer transformer, ThreeSides side, double currentValue, Consumer<LimitViolation> consumer) {
                throw new UnsupportedOperationException("Not used in this test!");
            }
        };
    }

    private LimitViolationDetector contingencyBasedDetector() {
        return new AbstractLimitViolationDetector() {
            @Override
            public void checkCurrent(Contingency contingency, Branch branch, TwoSides side, double currentValue, Consumer<LimitViolation> consumer) {
                doCheckCurrent(contingency, branch, side, consumer);
            }

            @Override
            public void checkVoltage(Contingency contingency, Bus bus, double voltageValue, Consumer<LimitViolation> consumer) {
                doCheckVoltage(contingency, bus, consumer);
            }

            @Override
            public void checkVoltageAngle(Contingency contingency, VoltageAngleLimit voltageAngleLimit, double voltageAngleDifference, Consumer<LimitViolation> consumer) {
                doCheckVoltageAngle(contingency, voltageAngleLimit, consumer);
            }

            @Override
            public void checkActivePower(Branch branch, TwoSides side, double currentValue, Consumer<LimitViolation> consumer) {
            }

            @Override
            public void checkApparentPower(Branch branch, TwoSides side, double currentValue, Consumer<LimitViolation> consumer) {
            }

            @Override
            public void checkActivePower(ThreeWindingsTransformer transformer, ThreeSides side, double currentValue, Consumer<LimitViolation> consumer) {
                throw new UnsupportedOperationException("Not used in this test!");
            }

            @Override
            public void checkApparentPower(ThreeWindingsTransformer transformer, ThreeSides side, double currentValue, Consumer<LimitViolation> consumer) {
                throw new UnsupportedOperationException("Not used in this test!");
            }

            @Override
            public void checkPermanentLimit(Branch<?> branch, TwoSides side, double limitReductionValue, double value, Consumer<LimitViolation> consumer, LimitType type) {

            }
        };
    }

    @BeforeEach
    void setUp() {
        network = EurostagTutorialExample1Factory.createWithVoltageAngleLimit();
        line1 = network.getBranch("NHV1_NHV2_1");
        line2 = network.getBranch("NHV1_NHV2_2");
        transformer = network.getBranch("NGEN_NHV1");
        voltageLevel1 = network.getVoltageLevel("VLHV1");
        genVoltageLevel = network.getVoltageLevel("VLGEN");
        loadVoltageLevel = network.getVoltageLevel("VLLOAD");
        voltageAngleLimit0 = network.getVoltageAngleLimitsStream().toList().get(0);
        voltageAngleLimit1 = network.getVoltageAngleLimitsStream().toList().get(1);
        voltageAngleLimit2 = network.getVoltageAngleLimitsStream().toList().get(2);
        contingency1 = new Contingency("cont1");
        contingency2 = new Contingency("cont2");

        collectedViolations = new ArrayList<>();
    }

    @Test
    void networkHas6Violations() {
        contingencyBasedDetector().checkAll(network, collectedViolations::add);
        assertEquals(6, collectedViolations.size());
        assertEquals(3, collectedViolations.stream().filter(l -> l.getLimitType() == LimitViolationType.CURRENT).count());
        assertEquals(1, collectedViolations.stream().filter(l -> l.getLimitType() == LimitViolationType.LOW_VOLTAGE).count());
        assertEquals(1, collectedViolations.stream().filter(l -> l.getLimitType() == LimitViolationType.HIGH_VOLTAGE).count());
        assertEquals(3, collectedViolations.stream().filter(l -> l.getSubjectId().equals("NHV1_NHV2_1")).count());
        assertEquals(1, collectedViolations.stream().filter(l -> l.getLimitType() == LimitViolationType.HIGH_VOLTAGE_ANGLE).count());
    }

    @Test
    void branch1Has2Violations() {
        contingencyBasedDetector().checkCurrent(network.getLine("NHV1_NHV2_1"), collectedViolations::add);
        assertEquals(2, collectedViolations.size());
    }

    @Test
    void branch2Has1Violation() {
        contingencyBasedDetector().checkCurrent(network.getLine("NHV1_NHV2_2"), collectedViolations::add);
        assertEquals(1, collectedViolations.size());
    }

    @Test
    void voltageLevel1Has1Violation() {
        contingencyBasedDetector().checkVoltage(network.getVoltageLevel("VLHV1"), collectedViolations::add);
        assertEquals(1, collectedViolations.size());
        assertSame(LimitViolationType.HIGH_VOLTAGE, collectedViolations.get(0).getLimitType());
    }

    @Test
    void voltageLevelGenHas1Violation() {
        contingencyBasedDetector().checkVoltage(network.getVoltageLevel("VLGEN"), collectedViolations::add);
        assertEquals(1, collectedViolations.size());
        assertSame(LimitViolationType.LOW_VOLTAGE, collectedViolations.get(0).getLimitType());
    }

    @Test
    void voltageAngleLimit0Has1Violation() {
        contingencyBasedDetector().checkVoltageAngle(network.getVoltageAngleLimitsStream().toList().get(0), collectedViolations::add);
        assertEquals(1, collectedViolations.size());
        assertSame(LimitViolationType.HIGH_VOLTAGE_ANGLE, collectedViolations.get(0).getLimitType());
    }

    @Test
    void networkHas9ViolationsOnContingency1() {
        contingencyBasedDetector().checkAll(contingency1, network, collectedViolations::add);
        assertEquals(9, collectedViolations.size());
        assertEquals(4, collectedViolations.stream().filter(l -> l.getLimitType() == LimitViolationType.CURRENT).count());
        assertEquals(1, collectedViolations.stream().filter(l -> l.getLimitType() == LimitViolationType.LOW_VOLTAGE).count());
        assertEquals(2, collectedViolations.stream().filter(l -> l.getLimitType() == LimitViolationType.HIGH_VOLTAGE).count());
        assertEquals(1, collectedViolations.stream().filter(l -> l.getLimitType() == LimitViolationType.HIGH_VOLTAGE_ANGLE).count());
        assertEquals(1, collectedViolations.stream().filter(l -> l.getLimitType() == LimitViolationType.LOW_VOLTAGE_ANGLE).count());
    }

    @Test
    void networkHas5ViolationsOnContingency2() {
        contingencyBasedDetector().checkAll(contingency2, network, collectedViolations::add);
        assertEquals(6, collectedViolations.size());
        assertEquals(3, collectedViolations.stream().filter(l -> l.getLimitType() == LimitViolationType.CURRENT).count());
        assertEquals(1, collectedViolations.stream().filter(l -> l.getLimitType() == LimitViolationType.LOW_VOLTAGE).count());
        assertEquals(1, collectedViolations.stream().filter(l -> l.getLimitType() == LimitViolationType.HIGH_VOLTAGE).count());
        assertEquals(1, collectedViolations.stream().filter(l -> l.getLimitType() == LimitViolationType.HIGH_VOLTAGE_ANGLE).count());
    }

    @Test
    void networkHas5ViolationsOnContingency1WithContingencyBlindDetector() {
        contingencyBlindDetector().checkAll(contingency1, network, collectedViolations::add);
        assertEquals(6, collectedViolations.size());
        assertEquals(3, collectedViolations.stream().filter(l -> l.getLimitType() == LimitViolationType.CURRENT).count());
        assertEquals(1, collectedViolations.stream().filter(l -> l.getLimitType() == LimitViolationType.LOW_VOLTAGE).count());
        assertEquals(1, collectedViolations.stream().filter(l -> l.getLimitType() == LimitViolationType.HIGH_VOLTAGE).count());
        assertEquals(1, collectedViolations.stream().filter(l -> l.getLimitType() == LimitViolationType.HIGH_VOLTAGE_ANGLE).count());
    }

    @Test
    void transformerHas1ViolationOnContingency1() {
        contingencyBasedDetector().checkCurrent(contingency1, transformer, collectedViolations::add);
        assertEquals(1, collectedViolations.size());
    }

    @Test
    void transformerHasNoViolationOnContingency1WithContingencyBlindDetector() {
        LimitViolationDetector detector = contingencyBlindDetector();
        detector.checkCurrent(contingency1, transformer, collectedViolations::add);
        detector.checkCurrent(contingency1, transformer, TwoSides.ONE, collectedViolations::add);
        detector.checkCurrent(contingency1, transformer, TwoSides.ONE, 5000, collectedViolations::add);
        assertEquals(0, collectedViolations.size());
    }

    @Test
    void loadVoltageLevelHasNoViolationOnContingency1WithContingencyBlindDetector() {
        LimitViolationDetector detector = contingencyBlindDetector();
        detector.checkVoltage(contingency1, loadVoltageLevel, collectedViolations::add);
        Bus loadBus = loadVoltageLevel.getBusView().getBusStream().findFirst().orElseThrow(IllegalStateException::new);
        detector.checkVoltage(contingency1, loadBus, collectedViolations::add);
        detector.checkVoltage(contingency1, loadBus, 500, collectedViolations::add);
        assertEquals(0, collectedViolations.size());
    }

    @Test
    void voltageAngleLimit1Has1ViolationOnContingency1() {
        contingencyBasedDetector().checkVoltageAngle(contingency1, voltageAngleLimit1, collectedViolations::add);
        assertEquals(1, collectedViolations.size());
    }

    @Test
    void voltageAngleLimit2HasNoViolationOnContingency1() {
        contingencyBasedDetector().checkVoltageAngle(contingency1, voltageAngleLimit2, collectedViolations::add);
        assertEquals(0, collectedViolations.size());
    }
}