SecurityAnalysisTest.java

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

import com.google.common.jimfs.Configuration;
import com.google.common.jimfs.Jimfs;
import com.powsybl.action.SwitchAction;
import com.powsybl.commons.config.InMemoryPlatformConfig;
import com.powsybl.commons.config.PlatformConfig;
import com.powsybl.commons.report.ReportNode;
import com.powsybl.computation.ComputationManager;
import com.powsybl.computation.ComputationResourcesStatus;
import com.powsybl.contingency.*;
import com.powsybl.iidm.modification.AbstractNetworkModification;
import com.powsybl.iidm.modification.topology.NamingStrategy;
import com.powsybl.iidm.network.Bus;
import com.powsybl.iidm.network.Network;
import com.powsybl.iidm.network.VariantManagerConstants;
import com.powsybl.iidm.network.test.EurostagTutorialExample1Factory;
import com.powsybl.loadflow.LoadFlowResult;
import com.powsybl.security.*;
import com.powsybl.security.condition.AnyViolationCondition;
import com.powsybl.security.extensions.ActivePowerExtension;
import com.powsybl.security.extensions.CurrentExtension;
import com.powsybl.security.impl.interceptors.SecurityAnalysisInterceptorMock;
import com.powsybl.security.interceptors.SecurityAnalysisInterceptor;
import com.powsybl.security.monitor.StateMonitor;
import com.powsybl.security.results.BranchResult;
import com.powsybl.security.results.BusResult;
import com.powsybl.security.results.PostContingencyResult;
import com.powsybl.security.strategy.OperatorStrategy;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;

import java.io.IOException;
import java.nio.file.FileSystem;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Executor;

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

/**
 *
 * @author Massimo Ferraro {@literal <massimo.ferraro@techrain.eu>}
 * @author Teofil Calin BANC {@literal <teofil-calin.banc at rte-france.com>}
 */
class SecurityAnalysisTest {

    private static final class SecurityAnalysisModificationTest extends AbstractNetworkModification {

        @Override
        public String getName() {
            return "SecurityAnalysisModificationTest";
        }

        @Override
        public void apply(Network network, NamingStrategy namingStrategy, boolean throwException, ComputationManager computationManager, ReportNode reportNode) {
            network.getLine("NHV1_NHV2_2").getTerminal1().disconnect();
            network.getLine("NHV1_NHV2_2").getTerminal2().disconnect();
            network.getLine("NHV1_NHV2_1").getTerminal2().setP(600.0);
            ((Bus) network.getIdentifiable("NHV2")).setV(380.0).setAngle(-0.10);
        }
    }

    private FileSystem fileSystem;

    private PlatformConfig platformConfig;

    @BeforeEach
    void setUp() {
        fileSystem = Jimfs.newFileSystem(Configuration.unix());
        platformConfig = new InMemoryPlatformConfig(fileSystem);
    }

    @AfterEach
    void tearDown() throws IOException {
        fileSystem.close();
    }

    @Test
    void run() {
        Network network = EurostagTutorialExample1Factory.create();
        ((Bus) network.getIdentifiable("NHV1")).setV(380.0).setAngle(0.25);
        ((Bus) network.getIdentifiable("NHV2")).setV(380.0).setAngle(0.20);
        network.getLine("NHV1_NHV2_1").getTerminal1().setP(560.0).setQ(550.0);
        network.getLine("NHV1_NHV2_1").getTerminal2().setP(560.0).setQ(550.0);
        network.getLine("NHV1_NHV2_1").newCurrentLimits1().setPermanentLimit(1500.0).add();
        network.getLine("NHV1_NHV2_1").newCurrentLimits2()
                .setPermanentLimit(1200.0)
                .beginTemporaryLimit()
                .setName("10'")
                .setAcceptableDuration(10 * 60)
                .setValue(1300.0)
                .endTemporaryLimit()
                .add();
        network.newVoltageAngleLimit()
            .setId("VoltageAngleLimit_NHV1_NHV2_1")
            .from(network.getLine("NHV1_NHV2_1").getTerminal1())
            .to(network.getLine("NHV1_NHV2_1").getTerminal2())
            .setLowLimit(-0.25)
            .setHighLimit(0.25)
            .add();

        Contingency contingency = Contingency.builder("NHV1_NHV2_2_contingency")
                                             .addBranch("NHV1_NHV2_2")
                                             .build();
        Contingency contingencyMock = Mockito.spy(contingency);

        Mockito.when(contingencyMock.toModification()).thenReturn(new SecurityAnalysisModificationTest());
        ContingenciesProvider contingenciesProvider = n -> Collections.singletonList(contingencyMock);
        SecurityAnalysisInterceptorMock interceptorMock = new SecurityAnalysisInterceptorMock();

        SecurityAnalysisResult result = SecurityAnalysis.run(network,
                VariantManagerConstants.INITIAL_VARIANT_ID,
                contingenciesProvider,
                createSecurityAnalysisRunParameters(interceptorMock))
                .getResult();

        assertSame(LoadFlowResult.ComponentResult.Status.CONVERGED, result.getPreContingencyResult().getStatus());
        assertEquals(0, result.getPreContingencyLimitViolationsResult().getLimitViolations().size());
        PostContingencyResult postcontingencyResult = result.getPostContingencyResults().get(0);
        assertSame(PostContingencyComputationStatus.CONVERGED, postcontingencyResult.getStatus());
        assertEquals(2, postcontingencyResult.getLimitViolationsResult().getLimitViolations().size());
        LimitViolation violation = postcontingencyResult.getLimitViolationsResult().getLimitViolations().get(0);
        assertEquals(LimitViolationType.CURRENT, violation.getLimitType());
        assertEquals("NHV1_NHV2_1", violation.getSubjectId());

        ActivePowerExtension extension1 = violation.getExtension(ActivePowerExtension.class);
        assertNotNull(extension1);
        assertEquals(560.0, extension1.getPreContingencyValue(), 0.0);
        assertEquals(600.0, extension1.getPostContingencyValue(), 0.0);

        CurrentExtension extension2 = violation.getExtension(CurrentExtension.class);
        assertNotNull(extension2);
        assertEquals(1192.5631358010583, extension2.getPreContingencyValue(), 0.0);

        LimitViolation violation1 = postcontingencyResult.getLimitViolationsResult().getLimitViolations().get(1);
        assertEquals(LimitViolationType.LOW_VOLTAGE_ANGLE, violation1.getLimitType());
        assertEquals("VoltageAngleLimit_NHV1_NHV2_1", violation1.getSubjectId());
        assertNull(violation1.getSide());

        assertEquals(1, interceptorMock.getOnPostContingencyResultCount());
        assertEquals(1, interceptorMock.getOnPreContingencyResultCount());
        assertEquals(1, interceptorMock.getOnSecurityAnalysisResultCount());
    }

    @Test
    void runWithoutContingency() {
        Network network = EurostagTutorialExample1Factory.create();
        ContingenciesProvider contingenciesProvider = Mockito.mock(ContingenciesProvider.class);
        Mockito.when(contingenciesProvider.getContingencies(network)).thenReturn(Collections.emptyList());
        SecurityAnalysisInterceptorMock interceptorMock = new SecurityAnalysisInterceptorMock();

        SecurityAnalysisResult result = SecurityAnalysis.run(network,
                VariantManagerConstants.INITIAL_VARIANT_ID,
                contingenciesProvider,
                createSecurityAnalysisRunParameters(interceptorMock))
                .getResult();

        assertSame(LoadFlowResult.ComponentResult.Status.CONVERGED, result.getPreContingencyResult().getStatus());
        assertEquals(0, result.getPreContingencyLimitViolationsResult().getLimitViolations().size());
        assertEquals(0, result.getPostContingencyResults().size());

        assertEquals(0, interceptorMock.getOnPostContingencyResultCount());
        assertEquals(1, interceptorMock.getOnPreContingencyResultCount());
        assertEquals(1, interceptorMock.getOnSecurityAnalysisResultCount());
    }

    private SecurityAnalysisRunParameters createSecurityAnalysisRunParameters(SecurityAnalysisInterceptor interceptor) {
        return new SecurityAnalysisRunParameters()
                .setSecurityAnalysisParameters(SecurityAnalysisParameters.load(platformConfig))
                .setComputationManager(createMockComputationManager())
                .addInterceptor(interceptor)
                .addOperatorStrategy(new OperatorStrategy("operatorStrategy", ContingencyContext.specificContingency("c1"), new AnyViolationCondition(), Collections.singletonList("action1")))
                .addAction(new SwitchAction("action1", "switchId", true));
    }

    private static ComputationManager createMockComputationManager() {
        ComputationManager computationManager = Mockito.mock(ComputationManager.class);
        Executor executor = Runnable::run;
        Mockito.when(computationManager.getExecutor()).thenReturn(executor);
        ComputationResourcesStatus computationResourcesStatus = Mockito.mock(ComputationResourcesStatus.class);
        Mockito.when(computationResourcesStatus.getAvailableCores()).thenReturn(4);
        Mockito.when(computationManager.getResourcesStatus()).thenReturn(computationResourcesStatus);
        return computationManager;
    }

    @Test
    void testStateMonitors() {
        Network network = EurostagTutorialExample1Factory.create();
        ((Bus) network.getIdentifiable("NHV1")).setV(380.0);
        ((Bus) network.getIdentifiable("NHV1")).setAngle(0.0);
        ((Bus) network.getIdentifiable("NHV2")).setV(380.0);
        ((Bus) network.getIdentifiable("NHV2")).setAngle(0.0);
        network.getLine("NHV1_NHV2_1").getTerminal1().setP(560.0).setQ(550.0);
        network.getLine("NHV1_NHV2_1").getTerminal2().setP(560.0).setQ(550.0);
        network.getLine("NHV1_NHV2_1").newCurrentLimits1().setPermanentLimit(1500.0).add();
        network.getLine("NHV1_NHV2_1").newCurrentLimits2()
            .setPermanentLimit(1200.0)
            .beginTemporaryLimit()
            .setName("10'")
            .setAcceptableDuration(10 * 60)
            .setValue(1300.0)
            .endTemporaryLimit()
            .add();
        network.getLine("NHV1_NHV2_2").getTerminal1().setP(600.0).setQ(500.0);
        network.getLine("NHV1_NHV2_2").getTerminal2().setP(600.0).setQ(500.0);
        network.getLine("NHV1_NHV2_2").newCurrentLimits1().setPermanentLimit(1500.0).add();
        network.getLine("NHV1_NHV2_2").newCurrentLimits2()
            .setPermanentLimit(1200.0)
            .beginTemporaryLimit()
            .setName("10'")
            .setAcceptableDuration(10 * 60)
            .setValue(1300.0)
            .endTemporaryLimit()
            .add();
        ComputationManager computationManager = createMockComputationManager();
        // Testing all contingencies at once
        ContingenciesProvider contingenciesProvider = n -> n.getBranchStream()
            .map(b -> new Contingency(b.getId(), new BranchContingency(b.getId())))
            .toList();

        SecurityAnalysisParameters saParameters = new SecurityAnalysisParameters();

        LimitViolationFilter filter = new LimitViolationFilter();

        List<StateMonitor> monitors = new ArrayList<>();
        monitors.add(new StateMonitor(new ContingencyContext("NHV1_NHV2_1", ContingencyContextType.SPECIFIC),
            Collections.singleton("NHV1_NHV2_2"), Collections.singleton("VLHV2"), Collections.emptySet()));
        // this monitor will be filtered because the id of the branch of the state Monitor and the id of the branch of the contingency are the same
        monitors.add(new StateMonitor(new ContingencyContext("NHV1_NHV2_2", ContingencyContextType.SPECIFIC),
            Collections.singleton("NHV1_NHV2_2"), Collections.emptySet(), Collections.emptySet()));
        monitors.add(new StateMonitor(new ContingencyContext(null, ContingencyContextType.NONE),
                Set.of("NHV1_NHV2_1", "NOT_EXISTING_BRANCH"), Set.of("VLHV1", "NOT_EXISTING_VOLTAGE_LEVEL"), Collections.singleton("NOT_EXISTING_T3W"))); // ignore IDs of non existing equipment

        DefaultSecurityAnalysis defaultSecurityAnalysis = new DefaultSecurityAnalysis(network, filter, computationManager, monitors, ReportNode.NO_OP);
        SecurityAnalysisReport report = defaultSecurityAnalysis.run(network.getVariantManager().getWorkingVariantId(), saParameters, contingenciesProvider).join();
        SecurityAnalysisResult result = report.getResult();
        Assertions.assertThat(result.getPreContingencyResult().getNetworkResult().getBusResults()).containsExactly(new BusResult("VLHV1", "VLHV1_0", 380.0, 0.0));
        Assertions.assertThat(result.getPreContingencyResult().getNetworkResult().getBusResult("VLHV1_0")).isEqualToComparingOnlyGivenFields(new BusResult("VLHV1", "VLHV1_0", 380.0, 0.0));
        Assertions.assertThat(result.getPreContingencyResult().getNetworkResult().getBranchResults()).containsExactly(new BranchResult("NHV1_NHV2_1", 560.0, 550.0, 1192.5631358010583, 560.0, 550.0, 1192.5631358010583, 0.0));
        Assertions.assertThat(result.getPreContingencyResult().getNetworkResult().getBranchResult("NHV1_NHV2_1")).isEqualToComparingOnlyGivenFields(new BranchResult("NHV1_NHV2_1", 560.0, 550.0, 1192.5631358010583, 560.0, 550.0, 1192.5631358010583, 0.0));
        Assertions.assertThat(result.getPostContingencyResults().get(0).getNetworkResult().getBranchResults()).containsExactly(new BranchResult("NHV1_NHV2_2", 600.0, 500.0, 1186.6446717954987, 600.0, 500.0, 1186.6446717954987, 0.0));
        Assertions.assertThat(result.getPostContingencyResults().get(0).getNetworkResult().getBusResults()).containsExactly(new BusResult("VLHV2", "VLHV2_0", 380.0, 0.0));
    }
}