SecurityTest.java

/**
 * Copyright (c) 2016-2017, 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;

import com.powsybl.commons.io.table.CsvTableFormatterFactory;
import com.powsybl.commons.io.table.TableFormatterConfig;
import com.powsybl.contingency.Contingency;
import com.powsybl.iidm.network.*;
import com.powsybl.iidm.network.test.EurostagTutorialExample1Factory;
import com.powsybl.iidm.network.test.ThreeWindingsTransformerNetworkFactory;
import com.powsybl.loadflow.LoadFlowResult;
import com.powsybl.security.limitreduction.SimpleLimitsComputer;
import com.powsybl.security.results.PostContingencyResult;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;

import java.io.StringWriter;
import java.util.*;

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

/**
 * @author Geoffroy Jamgotchian {@literal <geoffroy.jamgotchian at rte-france.com>}
 */
class SecurityTest {

    TableFormatterConfig formatterConfig;

    private final CsvTableFormatterFactory formatterFactory = new CsvTableFormatterFactory();

    private SecurityAnalysisResult result;
    private LimitViolation line1Violation;
    private LimitViolation line2Violation;
    private Network network;

    @BeforeEach
    void setUp() {
        formatterConfig = new TableFormatterConfig(Locale.US, ',', "inv", true, true);

        network = EurostagTutorialExample1Factory.createWithCurrentLimits();

        // create pre-contingency results, just one violation on line1
        line1Violation = new LimitViolation("NHV1_NHV2_1", LimitViolationType.CURRENT, null, Integer.MAX_VALUE, 1000.0, 0.95f, 1100.0, TwoSides.ONE);
        LimitViolationsResult preContingencyResult = new LimitViolationsResult(Collections.singletonList(line1Violation), Collections.singletonList("action1"));

        // create post-contingency results, still the line1 violation plus line2 violation
        Contingency contingency1 = Mockito.mock(Contingency.class);
        Mockito.when(contingency1.getId()).thenReturn("contingency1");
        line2Violation = new LimitViolation("NHV1_NHV2_2", LimitViolationType.CURRENT, null, Integer.MAX_VALUE, 900.0, 0.95f, 950.0, TwoSides.ONE);
        PostContingencyResult postContingencyResult = new PostContingencyResult(contingency1, PostContingencyComputationStatus.CONVERGED, Arrays.asList(line1Violation, line2Violation), Collections.singletonList("action2"));

        result = new SecurityAnalysisResult(preContingencyResult, LoadFlowResult.ComponentResult.Status.CONVERGED, Collections.singletonList(postContingencyResult));
    }

    @Test
    void printPreContingencyViolations() throws Exception {
        StringWriter writer = new StringWriter();
        try {
            Security.printPreContingencyViolations(result, network, writer, formatterFactory, formatterConfig, null);
        } finally {
            writer.close();
        }
        assertEquals(String.join(System.lineSeparator(),
                                 "Pre-contingency violations",
                                 "Action,Equipment (1),End,Country,Base voltage,Violation type,Violation name,Value,Limit,abs(value-limit),Loading rate %",
                                 "action1,,,,,,,,,,",
                                 ",NHV1_NHV2_1,VLHV1,FR,380,CURRENT,Permanent limit,1100.0000,950.0000,150.0000,110.00"),
                     writer.toString().trim());
    }

    @Test
    void printPostContingencyViolations() throws Exception {
        StringWriter writer = new StringWriter();
        try {
            Security.printPostContingencyViolations(result, network, writer, formatterFactory, formatterConfig, null, false);
        } finally {
            writer.close();
        }
        assertEquals(String.join(System.lineSeparator(),
                                 "Post-contingency limit violations",
                                 "Contingency,Status,Action,Equipment (2),End,Country,Base voltage,Violation type,Violation name,Value,Limit,abs(value-limit),Loading rate %",
                                 "contingency1,CONVERGED,,Equipment (2),,,,,,,,,",
                                 ",,action2,,,,,,,,,,",
                                 ",,,NHV1_NHV2_1,VLHV1,FR,380,CURRENT,Permanent limit,1100.0000,950.0000,150.0000,110.00",
                                 ",,,NHV1_NHV2_2,VLHV1,FR,380,CURRENT,Permanent limit,950.0000,855.0000,95.0000,105.56"),
                     writer.toString().trim());
    }

    @Test
    void printPostContingencyViolationsWithPreContingencyViolationsFiltering() throws Exception {
        StringWriter writer = new StringWriter();
        try {
            Security.printPostContingencyViolations(result, network, writer, formatterFactory, formatterConfig, null, true);
        } finally {
            writer.close();
        }
        assertEquals(String.join(System.lineSeparator(),
                                 "Post-contingency limit violations",
                                 "Contingency,Status,Action,Equipment (1),End,Country,Base voltage,Violation type,Violation name,Value,Limit,abs(value-limit),Loading rate %",
                                 "contingency1,CONVERGED,,Equipment (1),,,,,,,,,",
                                 ",,action2,,,,,,,,,,",
                                 ",,,NHV1_NHV2_2,VLHV1,FR,380,CURRENT,Permanent limit,950.0000,855.0000,95.0000,105.56"),
                     writer.toString().trim());
    }

    @Test
    void printLimitsViolations() {
        assertEquals("+---------------+-------+---------+--------------+----------------+-----------------+-----------+----------+------------------+----------------+\n" +
                     "| Equipment (2) | End   | Country | Base voltage | Violation type | Violation name  | Value     | Limit    | abs(value-limit) | Loading rate % |\n" +
                     "+---------------+-------+---------+--------------+----------------+-----------------+-----------+----------+------------------+----------------+\n" +
                     "| NHV1_NHV2_1   | VLHV1 | FR      |          380 | CURRENT        | Permanent limit | 1100.0000 | 950.0000 |         150.0000 |         110.00 |\n" +
                     "| NHV1_NHV2_2   | VLHV1 | FR      |          380 | CURRENT        | Permanent limit |  950.0000 | 855.0000 |          95.0000 |         105.56 |\n" +
                     "+---------------+-------+---------+--------------+----------------+-----------------+-----------+----------+------------------+----------------+",
                     Security.printLimitsViolations(Arrays.asList(line1Violation, line2Violation), network, new LimitViolationFilter(), formatterConfig));
    }

    @Test
    void checkLimits() {
        List<LimitViolation> violations = Security.checkLimits(network);
        assertViolations(violations);

        violations = Security.checkLimits(network, 1);
        assertViolations(violations);
    }

    @Test
    void checkLimits05() {
        Line line = network.getLine(EurostagTutorialExample1Factory.NHV1_NHV2_1);
        line.getCurrentLimits1().ifPresent(l -> l.setPermanentLimit(2000));

        List<LimitViolation> violations = Security.checkLimits(network);
        assertTrue(getCurrentLimitViolationOnLine1Side1(violations).isEmpty());

        violations = Security.checkLimits(network, 0.5);
        assertViolation05(violations);

        violations = Security.checkLimits(network, new SimpleLimitsComputer(0.5));
        assertViolation05(violations);
    }

    private static Optional<LimitViolation> getCurrentLimitViolationOnLine1Side1(List<LimitViolation> violations) {
        return violations.stream().filter(l -> EurostagTutorialExample1Factory.NHV1_NHV2_1.equals(l.getSubjectId()) &&
                l.getSide() == ThreeSides.ONE && l.getLimitType() == LimitViolationType.CURRENT).findFirst();
    }

    private static void assertViolation05(List<LimitViolation> violations) {
        Optional<LimitViolation> violation = getCurrentLimitViolationOnLine1Side1(violations);
        assertTrue(violation.isPresent());
        assertEquals(0.5, violation.get().getLimitReduction(), 0.001d);
        assertEquals(2000, violation.get().getLimit(), 0.001d);
        assertEquals(1192, violation.get().getValue(), 1d);
    }

    private static void assertViolationsForThreeWindingsTransformer(List<LimitViolation> violations) {
        assertEquals(3, violations.size());
        violations.forEach(violation -> {
            assertEquals("3WT", violation.getSubjectId());
            assertEquals(LimitViolationType.CURRENT, violation.getLimitType());
        });
    }

    @Test
    void checkLimitsWithThreeWindingsTransformer() {
        Network otherNetwork = ThreeWindingsTransformerNetworkFactory.createWithCurrentLimitsAndTerminalsPAndQ();
        List<LimitViolation> violations = Security.checkLimits(otherNetwork);
        assertViolationsForThreeWindingsTransformer(violations);

        violations = Security.checkLimits(otherNetwork, 1);
        assertViolationsForThreeWindingsTransformer(violations);
    }

    @Test
    void checkLimitsDC() {
        var eBadLimit = assertThrows(IllegalArgumentException.class, () -> Security.checkLimitsDc(network, 0, 0.95));
        assertEquals("Bad limit reduction 0.0", eBadLimit.getMessage());

        var eLowCosPhi = assertThrows(IllegalArgumentException.class, () -> Security.checkLimitsDc(network, 0.7f, -0.1));
        assertEquals("Invalid DC power factor -0.1", eLowCosPhi.getMessage());

        var eHighCosPhi = assertThrows(IllegalArgumentException.class, () -> Security.checkLimitsDc(network, 0.7f, 1.2));
        assertEquals("Invalid DC power factor 1.2", eHighCosPhi.getMessage());

        List<LimitViolation> violations = Security.checkLimitsDc(network, 1, 0.95);
        assertCurrentViolations(violations);
    }

    @Test
    void checkLimitsDCOnThreeWindingsTransformer() {
        Network otherNetwork = ThreeWindingsTransformerNetworkFactory.createWithCurrentLimitsAndTerminalsPAndQ();
        var eBadLimit = assertThrows(IllegalArgumentException.class, () -> Security.checkLimitsDc(otherNetwork, 0, 0.95));
        assertEquals("Bad limit reduction 0.0", eBadLimit.getMessage());

        var eLowCosPhi = assertThrows(IllegalArgumentException.class, () -> Security.checkLimitsDc(otherNetwork, 0.7f, -0.1));
        assertEquals("Invalid DC power factor -0.1", eLowCosPhi.getMessage());

        var eHighCosPhi = assertThrows(IllegalArgumentException.class, () -> Security.checkLimitsDc(otherNetwork, 0.7f, 1.2));
        assertEquals("Invalid DC power factor 1.2", eHighCosPhi.getMessage());

        List<LimitViolation> violations = Security.checkLimitsDc(otherNetwork, 1, 0.95);
        assertEquals(3, violations.size());
        violations.forEach(violation -> {
            assertEquals("3WT", violation.getSubjectId());
            assertEquals(LimitViolationType.CURRENT, violation.getLimitType());
        });
    }

    private static void assertViolations(List<LimitViolation> violations) {
        assertEquals(5, violations.size());
        violations.forEach(violation -> {
            assertTrue(Arrays.asList("VLHV1", "NHV1_NHV2_1", "NHV1_NHV2_2").contains(violation.getSubjectId()));
            if ("VLHV1".equals(violation.getSubjectId())) {
                assertEquals(LimitViolationType.LOW_VOLTAGE, violation.getLimitType());
            } else {
                assertEquals(LimitViolationType.CURRENT, violation.getLimitType());
            }
        });
    }

    private static void assertCurrentViolations(List<LimitViolation> violations) {
        assertEquals(4, violations.size());
        violations.forEach(violation -> {
            assertTrue(Arrays.asList("VLHV1", "NHV1_NHV2_1", "NHV1_NHV2_2").contains(violation.getSubjectId()));
            assertEquals(LimitViolationType.CURRENT, violation.getLimitType());
        });
    }
}