SensitivityAnalysisToolTest.java

/**
 * Copyright (c) 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.sensitivity;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.powsybl.commons.test.TestUtil;
import com.powsybl.contingency.*;
import com.powsybl.contingency.contingency.list.ContingencyList;
import com.powsybl.contingency.contingency.list.DefaultContingencyList;
import com.powsybl.contingency.json.ContingencyJsonModule;
import com.powsybl.iidm.network.Network;
import com.powsybl.iidm.network.test.EurostagTutorialExample1Factory;
import com.powsybl.iidm.serde.NetworkSerDe;
import com.powsybl.sensitivity.json.SensitivityJsonModule;
import com.powsybl.tools.test.AbstractToolTest;
import com.powsybl.tools.Tool;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import java.util.List;

import static com.powsybl.sensitivity.SensitivityFunctionType.*;
import static com.powsybl.sensitivity.SensitivityVariableType.*;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

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

    private static final String COMMAND_NAME = "sensitivity-analysis";

    private final SensitivityAnalysisTool tool = new SensitivityAnalysisTool();

    private ObjectMapper objectMapper;

    @Override
    @BeforeEach
    public void setUp() throws Exception {
        super.setUp();

        // create network
        Network network = EurostagTutorialExample1Factory.create();
        NetworkSerDe.write(network, fileSystem.getPath("network.xiidm"));

        objectMapper = new ObjectMapper();
        objectMapper.registerModule(new SensitivityJsonModule())
                .registerModule(new ContingencyJsonModule());

        // create factors
        List<SensitivityFactor> factors = List.of(new SensitivityFactor(BRANCH_ACTIVE_POWER_1, "NHV1_NHV2_1", INJECTION_ACTIVE_POWER, "GEN", false, ContingencyContext.all()),
                                                  new SensitivityFactor(BRANCH_ACTIVE_POWER_2, "NHV1_NHV2_1", INJECTION_ACTIVE_POWER, "glsk", true, ContingencyContext.specificContingency("NHV1_NHV2_2")));
        try (Writer writer = Files.newBufferedWriter(fileSystem.getPath("factors.json"), StandardCharsets.UTF_8)) {
            objectMapper.writeValue(writer, factors);
        }

        // create contingencies
        ContingencyList contingencyList = new DefaultContingencyList("one contingency list", List.of(new Contingency("NHV1_NHV2_2", new BranchContingency("NHV1_NHV2_2"))));
        try (Writer writer = Files.newBufferedWriter(fileSystem.getPath("contingencies.json"), StandardCharsets.UTF_8)) {
            objectMapper.writeValue(writer, contingencyList);
        }

        List<SensitivityVariableSet> variableSets = Collections.emptyList();
        try (Writer writer = Files.newBufferedWriter(fileSystem.getPath("variableSets.json"), StandardCharsets.UTF_8)) {
            objectMapper.writeValue(writer, variableSets);
        }

        SensitivityAnalysisParameters parameters = new SensitivityAnalysisParameters();
        try (Writer writer = Files.newBufferedWriter(fileSystem.getPath("parameters.json"), StandardCharsets.UTF_8)) {
            objectMapper.writeValue(writer, parameters);
        }
    }

    @Override
    protected Iterable<Tool> getTools() {
        return Collections.singleton(tool);
    }

    @Override
    public void assertCommand() {
        assertCommand(tool.getCommand(), COMMAND_NAME, 10, 3);
        assertOption(tool.getCommand().getOptions(), "case-file", true, true);
        assertOption(tool.getCommand().getOptions(), "output-file", true, true);
        assertOption(tool.getCommand().getOptions(), "factors-file", true, true);
        assertOption(tool.getCommand().getOptions(), "contingencies-file", false, true);
        assertOption(tool.getCommand().getOptions(), "variable-sets-file", false, true);
        assertOption(tool.getCommand().getOptions(), "parameters-file", false, true);
        assertOption(tool.getCommand().getOptions(), "output-contingency-file", false, true);
    }

    @Test
    void runJsonOutput() throws IOException {
        String expectedOut = "Loading network 'network.xiidm'" + System.lineSeparator() +
                "Running analysis..." + System.lineSeparator();
        assertCommandSuccessfulMatch(new String[] {COMMAND_NAME,
            "--case-file", "network.xiidm",
            "--factors-file", "factors.json",
            "--contingencies-file", "contingencies.json",
            "--variable-sets-file", "variableSets.json",
            "--parameters-file", "parameters.json",
            "--output-file", "output.json"},
                expectedOut);

        assertTrue(Files.exists(fileSystem.getPath("output.json")));
        List<SensitivityValue> values;
        List<SensitivityAnalysisResult.SensitivityContingencyStatus> status;
        try (Reader reader = Files.newBufferedReader(fileSystem.getPath("output.json"))) {
            List<List<Object>> lists = objectMapper.readValue(reader, new TypeReference<>() { });
            values = objectMapper.convertValue(lists.get(0), new TypeReference<>() { });
            status = objectMapper.convertValue(lists.get(1), new TypeReference<>() { });
        }
        assertEquals(2, values.size());
        SensitivityValue value0 = values.get(0);
        assertEquals(0, value0.getFactorIndex());
        assertEquals(0, value0.getContingencyIndex());
        SensitivityValue value1 = values.get(1);
        assertEquals(1, value1.getFactorIndex());
        assertEquals(0, value1.getContingencyIndex());

        assertEquals(1, status.size());
        SensitivityAnalysisResult.SensitivityContingencyStatus status0 = status.get(0);
        assertEquals("NHV1_NHV2_2", status0.getContingencyId());
        assertEquals(SensitivityAnalysisResult.Status.SUCCESS, status0.getStatus());
    }

    @Test
    void runCsvOutput() throws IOException {
        String expectedOut = "Loading network 'network.xiidm'" + System.lineSeparator() +
                "Running analysis..." + System.lineSeparator();
        assertCommandSuccessfulMatch(new String[] {COMMAND_NAME,
            "--case-file", "network.xiidm",
            "--factors-file", "factors.json",
            "--contingencies-file", "contingencies.json",
            "--parameters-file", "parameters.json",
            "--output-file", "output.csv",
            "--output-contingency-file", "outputContingency.csv"},
                expectedOut);

        Path outputCsvFile = fileSystem.getPath("output.csv");
        assertTrue(Files.exists(outputCsvFile));
        String outputCsvRef = TestUtil.normalizeLineSeparator(String.join(System.lineSeparator(),
               "Sensitivity analysis result",
               "Contingency ID;Factor index;Function ref value;Sensitivity value",
               "NHV1_NHV2_2;0;0.00000;0.00000",
               "NHV1_NHV2_2;1;0.00000;0.00000")
                + System.lineSeparator());
        assertEquals(outputCsvRef, TestUtil.normalizeLineSeparator(Files.readString(outputCsvFile)));

        Path outputContingencyStatusCsvFile = fileSystem.getPath("outputContingency.csv");
        assertTrue(Files.exists(outputContingencyStatusCsvFile));
        String outputContingencyStatusCsvRef = TestUtil.normalizeLineSeparator(String.join(System.lineSeparator(),
                "Sensitivity analysis contingency status result",
                "Contingency ID;Contingency Status",
                "NHV1_NHV2_2;SUCCESS")
                + System.lineSeparator());
        assertEquals(outputContingencyStatusCsvRef, TestUtil.normalizeLineSeparator(Files.readString(outputContingencyStatusCsvFile)));

    }

    @Test
    void checkFailsWhenNetworkFileNotFound() {
        assertCommandErrorMatch(new String[] {COMMAND_NAME,
            "--case-file", "wrongFile.xiidm",
            "--factors-file", "factors.json",
            "--output-file", "output.csv"},
                "com.powsybl.commons.PowsyblException: File wrongFile.xiidm does not exist");
    }

    @Test
    void checkFailsWhenFactorsFileNotFound() {
        assertCommandErrorMatch(new String[] {COMMAND_NAME,
            "--case-file", "network.xiidm",
            "--factors-file", "wrongFile.json",
            "--output-file", "output.csv"},
                "java.nio.file.NoSuchFileException: wrongFile.json");
    }

    @Test
    void checkThrowsWhenOutputFileAndNoFormat() {
        assertCommandErrorMatch(new String[] {COMMAND_NAME,
            "--case-file", "network.xiidm",
            "--factors-file", "factors.json",
            "--output-file", "output.txt"},
                "Unsupported output format: output.txt");
    }

    @Test
    void checkThrowsWhenOutputFileAndContingencyDiffFormat() {
        assertCommandErrorMatch(new String[] {COMMAND_NAME,
            "--case-file", "network.xiidm",
            "--factors-file", "factors.json",
            "--output-file", "output.csv",
            "--output-contingency-file", "outputContingency.json"},
                "output-file and output-contingency-file files must have the same format (csv).");
    }

    @Test
    void runJsonOutputAutoContingencyOut() {
        String expectedOut = "Loading network 'network.xiidm'" + System.lineSeparator() +
                "Running analysis..." + System.lineSeparator();
        assertCommandSuccessfulMatch(new String[] {COMMAND_NAME,
            "--case-file", "network.xiidm",
            "--factors-file", "factors.json",
            "--contingencies-file", "contingencies.json",
            "--parameters-file", "parameters.json",
            "--output-file", "outputCustom.csv"},
                expectedOut);

        Path outputCsvFile = fileSystem.getPath("outputCustom.csv");
        assertTrue(Files.exists(outputCsvFile));
        Path outputContingencyCsvFile = fileSystem.getPath("outputCustom_contingency_status.csv");
        assertTrue(Files.exists(outputContingencyCsvFile));
    }

    @Test
    void runCommandWithSingleOutput() throws IOException {
        String expectedOut = "Loading network 'network.xiidm'" + System.lineSeparator() +
                "Running analysis..." + System.lineSeparator();
        assertCommandSuccessfulMatch(new String[] {COMMAND_NAME,
            "--case-file", "network.xiidm",
            "--factors-file", "factors.json",
            "--contingencies-file", "contingencies.json",
            "--variable-sets-file", "variableSets.json",
            "--parameters-file", "parameters.json",
            "--output-file", "output.json",
            "--single-output"},
                expectedOut);

        SensitivityAnalysisResult result;
        try (Reader reader = Files.newBufferedReader(fileSystem.getPath("output.json"))) {
            result = objectMapper.readValue(reader, new TypeReference<>() {
            });
        }
        assertEquals(2, result.getValues().size());
        SensitivityValue value0 = result.getValues().get(0);
        assertEquals(0, value0.getFactorIndex());
        assertEquals(0, value0.getContingencyIndex());
        SensitivityValue value1 = result.getValues().get(1);
        assertEquals(1, value1.getFactorIndex());
        assertEquals(0, value1.getContingencyIndex());

        assertEquals(1, result.getContingencyStatuses().size());
        SensitivityAnalysisResult.SensitivityContingencyStatus status0 = result.getContingencyStatuses().get(0);
        assertEquals("NHV1_NHV2_2", status0.getContingencyId());
        assertEquals(SensitivityAnalysisResult.Status.SUCCESS, status0.getStatus());

        assertEquals(2, result.getFactors().size());
        SensitivityFactor factor0 = result.getFactors().get(0);
        assertEquals("NHV1_NHV2_1", factor0.getFunctionId());
        assertEquals("GEN", factor0.getVariableId());
        SensitivityFactor factor1 = result.getFactors().get(1);
        assertEquals("NHV1_NHV2_1", factor1.getFunctionId());
        assertEquals("glsk", factor1.getVariableId());
    }

    @Test
    void checkThrowsSingleOutputCSV() {
        assertCommandErrorMatch(new String[] {COMMAND_NAME,
            "--case-file", "network.xiidm",
            "--factors-file", "factors.json",
            "--contingencies-file", "contingencies.json",
            "--variable-sets-file", "variableSets.json",
            "--parameters-file", "parameters.json",
            "--output-file", "output.csv",
            "--single-output"},
                "Unsupported single-output option does not support csv file as argument of output-file. Must be json.");
    }

    @Test
    void checkCommand() {
        assertEquals("sensitivity-analysis", tool.getCommand().getName());
        assertEquals("Computation", tool.getCommand().getTheme());
        assertEquals("Run sensitivity analysis", tool.getCommand().getDescription());
    }
}