DynawoParametersTest.java

/**
 * Copyright (c) 2020, 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/.
 */
package com.powsybl.dynawo;

import com.powsybl.commons.PowsyblException;
import com.powsybl.commons.config.InMemoryPlatformConfig;
import com.powsybl.commons.config.MapModuleConfig;
import com.powsybl.commons.extensions.Extension;
import com.powsybl.commons.test.AbstractSerDeTest;
import com.powsybl.dynamicsimulation.DynamicSimulationParameters;
import com.powsybl.dynamicsimulation.json.JsonDynamicSimulationParameters;
import com.powsybl.dynawo.DynawoSimulationParameters.LogLevel;
import com.powsybl.dynawo.DynawoSimulationParameters.SolverType;
import com.powsybl.dynawo.DynawoSimulationParameters.SpecificLog;
import com.powsybl.dynawo.commons.ExportMode;
import com.powsybl.dynawo.parameters.Parameter;
import com.powsybl.dynawo.parameters.ParameterType;
import com.powsybl.dynawo.xml.ParametersXml;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;

import static com.powsybl.dynawo.DynawoSimulationParameters.*;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.*;

/**
 * @author Marcos de Miguel {@literal <demiguelm at aia.es>}
 */
class DynawoParametersTest extends AbstractSerDeTest {

    public static final String USER_HOME = "/home/user/";

    private InMemoryPlatformConfig platformConfig;

    @BeforeEach
    @Override
    public void setUp() throws IOException {
        super.setUp();
        platformConfig = new InMemoryPlatformConfig(fileSystem);
    }

    private void copyFile(String name, String parametersFile) throws IOException {
        Path path = platformConfig.getConfigDir()
                .map(cd -> cd.resolve(fileSystem.getPath(parametersFile)))
                .orElse(fileSystem.getPath(parametersFile));
        Objects.requireNonNull(getClass().getResourceAsStream(name))
                .transferTo(Files.newOutputStream(path));
    }

    @Test
    void checkParameters() throws IOException {
        String networkParametersId = "networkParametersId";
        SolverType solverType = SolverType.IDA;
        String solverParametersId = "solverParametersId";
        boolean mergeLoads = true;
        boolean useModelSimplifiers = true;
        double precision = 1e-8;
        ExportMode timelinExportMode = ExportMode.XML;
        LogLevel logLevel = LogLevel.WARN;
        Set<SpecificLog> specificLogs = EnumSet.of(SpecificLog.MODELER, SpecificLog.EQUATIONS);
        String criteriaFileName = "criteria.crt";
        String additionalModelsFileName = "additionalModels.json";

        initPlatformConfig(networkParametersId, solverType, solverParametersId, mergeLoads, useModelSimplifiers,
                precision, timelinExportMode, logLevel, specificLogs, criteriaFileName, additionalModelsFileName);

        DynawoSimulationParameters parameters = DynawoSimulationParameters.load(platformConfig, fileSystem);

        checkModelParameters(parameters);
        checkNetworkParameters(parameters, networkParametersId);
        checkSolverParameters(parameters, solverParametersId, solverType);

        assertEquals(mergeLoads, parameters.isMergeLoads());
        assertEquals(useModelSimplifiers, parameters.isUseModelSimplifiers());
        assertEquals(precision, parameters.getPrecision());
        assertEquals(timelinExportMode, parameters.getTimelineExportMode());
        assertEquals(logLevel, parameters.getLogLevelFilter());
        assertEquals(specificLogs, parameters.getSpecificLogs());
        assertThat(parameters.getCriteriaFileName()).hasValue(criteriaFileName);
        assertThat(parameters.getCriteriaFilePath()).hasValue(fileSystem.getPath(USER_HOME + criteriaFileName));
        assertThat(parameters.getAdditionalModelsPath()).hasValue(fileSystem.getPath(USER_HOME + additionalModelsFileName));
    }

    @Test
    void checkDumpFileParameters() throws IOException {
        String folderProperty = USER_HOME + "dumpFiles";
        String fileProperty = "dumpFile.dmp";
        initDumpFilePlatformConfig(folderProperty, fileProperty);
        DynawoSimulationParameters parameters = DynawoSimulationParameters.load(platformConfig, fileSystem);
        DumpFileParameters dumpFileParameters = parameters.getDumpFileParameters();

        assertTrue(dumpFileParameters.exportDumpFile());
        assertTrue(dumpFileParameters.useDumpFile());
        assertEquals(folderProperty, dumpFileParameters.dumpFileFolder().toString());
        assertEquals(fileProperty, dumpFileParameters.dumpFile());
    }

    @Test
    void roundTripParametersSerializing() throws IOException {
        String networkParametersId = "networkParametersId";
        SolverType solverType = SolverType.IDA;
        String solverParametersId = "solverParametersId";
        boolean mergeLoads = false;
        initPlatformConfig(networkParametersId, solverType, solverParametersId, mergeLoads, false,
                1e-7, ExportMode.TXT, LogLevel.INFO, Set.of(SpecificLog.PARAMETERS, SpecificLog.VARIABLES),
                null, null);

        DynamicSimulationParameters dynamicSimulationParameters = new DynamicSimulationParameters()
                .setStartTime(0)
                .setStopTime(3600);
        DynawoSimulationParameters dynawoParameters = DynawoSimulationParameters.load(platformConfig);
        dynamicSimulationParameters.addExtension(DynawoSimulationParameters.class, dynawoParameters);
        roundTripTest(dynamicSimulationParameters, JsonDynamicSimulationParameters::write,
                JsonDynamicSimulationParameters::read, "/DynawoSimulationParameters.json");
    }

    private void initPlatformConfig(String networkParametersId, SolverType solverType, String solverParametersId,
                                    boolean mergeLoads, boolean useModelSimplifiers, double precision, ExportMode timelineExportMode,
                                    LogLevel logLevel, Set<SpecificLog> specificLogs, String criteriaFileName,
                                    String additionalModelsFileName) throws IOException {
        String parametersFile = USER_HOME + "parametersFile";
        String networkParametersFile = USER_HOME + "networkParametersFile";
        String solverParametersFile = USER_HOME + "solverParametersFile";
        String criteriaFile = criteriaFileName != null ? USER_HOME + criteriaFileName : null;
        String additionalModelsFile = additionalModelsFileName != null ? USER_HOME + additionalModelsFileName : null;

        MapModuleConfig moduleConfig = platformConfig.createModuleConfig(MODULE_SPECIFIC_PARAMETERS);
        moduleConfig.setStringProperty("parametersFile", parametersFile);
        moduleConfig.setStringProperty("network.parametersFile", networkParametersFile);
        moduleConfig.setStringProperty("solver.parametersFile", solverParametersFile);
        moduleConfig.setStringProperty("mergeLoads", String.valueOf(mergeLoads));
        moduleConfig.setStringProperty("network.parametersId", networkParametersId);
        moduleConfig.setStringProperty("solver.type", solverType.toString());
        moduleConfig.setStringProperty("solver.parametersId", solverParametersId);
        moduleConfig.setStringProperty("useModelSimplifiers", String.valueOf(useModelSimplifiers));
        moduleConfig.setStringProperty("precision", Double.toString(precision));
        moduleConfig.setStringProperty("timeline.exportMode", String.valueOf(timelineExportMode));
        moduleConfig.setStringProperty("log.levelFilter", logLevel.toString());
        moduleConfig.setStringListProperty("log.specificLogs", specificLogs.stream().map(SpecificLog::toString).toList());
        moduleConfig.setStringProperty("criteria.file", criteriaFile);
        moduleConfig.setStringProperty("additionalModelsFile", additionalModelsFile);

        createFiles(parametersFile, networkParametersFile, solverParametersFile, criteriaFile, additionalModelsFile);
    }

    private void initDumpFilePlatformConfig(String folderProperty, String fileProperty) throws IOException {
        String folderName = USER_HOME + "dumpFiles";
        String fileName = "dumpFile.dmp";
        MapModuleConfig moduleConfig = (MapModuleConfig) platformConfig.getOptionalModuleConfig(MODULE_SPECIFIC_PARAMETERS)
                .orElseGet(() -> platformConfig.createModuleConfig(MODULE_SPECIFIC_PARAMETERS));
        moduleConfig.setStringProperty("dump.export", Boolean.toString(true));
        moduleConfig.setStringProperty("dump.useAsInput", Boolean.toString(true));
        moduleConfig.setStringProperty("dump.exportFolder", folderProperty);
        moduleConfig.setStringProperty("dump.fileName", fileProperty);

        createDumpFiles(folderName, fileName);
    }

    @Test
    void checkDefaultParameters() throws IOException {
        Files.createDirectories(fileSystem.getPath(USER_HOME));
        copyFile("/parametersSet/models.par", DEFAULT_INPUT_PARAMETERS_FILE);
        copyFile("/parametersSet/network.par", DEFAULT_INPUT_NETWORK_PARAMETERS_FILE);
        copyFile("/parametersSet/solvers.par", DEFAULT_INPUT_SOLVER_PARAMETERS_FILE);
        MapModuleConfig moduleConfig = platformConfig.createModuleConfig(MODULE_SPECIFIC_PARAMETERS);
        moduleConfig.setStringProperty("parametersFile", "/work/inmemory/models.par");
        moduleConfig.setStringProperty("network.parametersFile", "/work/inmemory/network.par");
        moduleConfig.setStringProperty("solver.parametersFile", "/work/inmemory/solvers.par");

        DynawoSimulationParameters parameters = DynawoSimulationParameters.load(platformConfig, fileSystem);
        checkModelParameters(parameters);

        assertEquals(DEFAULT_NETWORK_PAR_ID, parameters.getNetworkParameters().getId());
        assertTrue(parameters.getNetworkParameters().getParameters().isEmpty());
        assertTrue(parameters.getNetworkParameters().getReferences().isEmpty());

        assertEquals(DEFAULT_SOLVER_TYPE, parameters.getSolverType());
        assertEquals(DEFAULT_SOLVER_PAR_ID, parameters.getSolverParameters().getId());
        assertEquals("1", parameters.getSolverParameters().getId());

        assertEquals(DEFAULT_MERGE_LOADS, parameters.isMergeLoads());
        assertEquals(DEFAULT_USE_MODEL_SIMPLIFIERS, parameters.isUseModelSimplifiers());
        assertEquals(DEFAULT_TIMELINE_EXPORT_MODE, parameters.getTimelineExportMode());
        assertTrue(parameters.getCriteriaFilePath().isEmpty());
        assertTrue(parameters.getAdditionalModelsPath().isEmpty());
    }

    @Test
    void checkDefaultDumpParameters() {
        DynawoSimulationParameters parameters = load(platformConfig, fileSystem);
        DumpFileParameters dumpFileParameters = parameters.getDumpFileParameters();
        assertEquals(DumpFileParameters.DEFAULT_EXPORT_DUMP, dumpFileParameters.exportDumpFile());
        assertEquals(DumpFileParameters.DEFAULT_USE_DUMP, dumpFileParameters.useDumpFile());
        assertEquals(DumpFileParameters.DEFAULT_DUMP_FOLDER, dumpFileParameters.dumpFileFolder());
        assertEquals(DumpFileParameters.DEFAULT_DUMP_NAME, dumpFileParameters.dumpFile());
    }

    @Test
    void checkException() throws IOException {
        Files.createDirectories(fileSystem.getPath(USER_HOME));
        copyFile("/parametersSet/solversMissingDefault.par", DEFAULT_INPUT_SOLVER_PARAMETERS_FILE);
        MapModuleConfig moduleConfig = platformConfig.createModuleConfig(MODULE_SPECIFIC_PARAMETERS);
        moduleConfig.setStringProperty("solver.parametersFile", "/work/inmemory/solvers.par");

        PowsyblException e1 = assertThrows(PowsyblException.class, () -> load(platformConfig, fileSystem));
        assertEquals("Could not find parameters set with id='1' in file '/work/inmemory/solvers.par'", e1.getMessage());

        try (InputStream is = getClass().getResourceAsStream("/parametersSet/solvers.par")) {
            PowsyblException e2 = assertThrows(PowsyblException.class, () -> ParametersXml.load(is, "2"));
            assertEquals("Could not find parameters set with id='2' in given input stream", e2.getMessage());
        }
    }

    @Test
    void dumpFilesFolderNotFound() throws IOException {
        String folderProperty = USER_HOME + "wrongFolder";
        String fileProperty = "dumpFile.dmp";
        initDumpFilePlatformConfig(folderProperty, fileProperty);
        PowsyblException e = assertThrows(PowsyblException.class, () -> load(platformConfig, fileSystem));
        assertEquals("Folder /home/user/wrongFolder set in 'dumpFileFolder' property cannot be found", e.getMessage());
    }

    @Test
    void dumpFileNotFound() throws IOException {
        String folderProperty = USER_HOME + "dumpFiles";
        String fileProperty = "wrongFile.dmp";
        initDumpFilePlatformConfig(folderProperty, fileProperty);
        PowsyblException e = assertThrows(PowsyblException.class, () -> load(platformConfig, fileSystem));
        assertEquals("File wrongFile.dmp set in 'dumpFile' property cannot be found", e.getMessage());
    }

    @Test
    void testGetParametersMap() throws IOException {
        String networkParametersId = "networkParametersId";
        SolverType solverType = SolverType.IDA;
        String solverParametersId = "solverParametersId";
        boolean mergeLoads = true;
        boolean useModelSimplifiers = true;
        double precision = 1e-8;
        ExportMode timelinExportMode = ExportMode.XML;
        LogLevel logLevel = LogLevel.WARN;
        Set<SpecificLog> specificLogs = EnumSet.of(SpecificLog.MODELER, SpecificLog.EQUATIONS);
        String criteriaFileName = "criteria.crt";
        String dumpFolder = USER_HOME + "dumpFiles";
        String dumpFile = "dumpFile.dmp";
        String additionalModelsFileName = "additionalModels.json";

        initPlatformConfig(networkParametersId, solverType, solverParametersId, mergeLoads, useModelSimplifiers, precision, timelinExportMode, logLevel, specificLogs, criteriaFileName, additionalModelsFileName);
        initDumpFilePlatformConfig(dumpFolder, dumpFile);
        Map<String, String> expectedProperties = Map.ofEntries(
                Map.entry("modelParameters",
                        "{test=test,{boolean=Parameter[name=boolean, type=BOOL, value=true], string=Parameter[name=string, type=STRING, value=aString]},[]}"),
                Map.entry("networkParameters",
                        "networkParametersId,{load_Tp=Parameter[name=load_Tp, type=DOUBLE, value=90], load_isControllable=Parameter[name=load_isControllable, type=BOOL, value=false]},[]"),
                Map.entry("solverParameters",
                        "solverParametersId,{order=Parameter[name=order, type=INT, value=1], absAccuracy=Parameter[name=absAccuracy, type=DOUBLE, value=1e-4]},[]"),
                Map.entry("solver.type", "IDA"),
                Map.entry("mergeLoads", "true"),
                Map.entry("useModelSimplifiers", "true"),
                Map.entry("precision", "1.0E-8"),
                Map.entry("timeline.exportMode", "XML"),
                Map.entry("log.levelFilter", "WARN"),
                Map.entry("log.specificLogs", "MODELER,EQUATIONS"),
                Map.entry("criteria.file", "/home/user/criteria.crt"),
                Map.entry("additionalModelsFile", "/home/user/additionalModels.json"),
                Map.entry("dump.export", "true"),
                Map.entry("dump.exportFolder", "/home/user/dumpFiles"),
                Map.entry("dump.useAsInput", "true"),
                Map.entry("dump.fileName", "dumpFile.dmp"));

        Map<String, String> properties = DynawoSimulationParameters.load(platformConfig, fileSystem)
                .createMapFromParameters();
        assertThat(properties).containsExactlyInAnyOrderEntriesOf(expectedProperties);
    }

    @Test
    void loadMapDynawoParameters() throws IOException {
        String networkParametersId = "networkParametersId";
        SolverType solverType = SolverType.IDA;
        String solverParametersId = "solverParametersId";
        boolean mergeLoads = true;
        boolean useModelSimplifiers = true;
        double precision = 1e-8;
        ExportMode timelinExportMode = ExportMode.XML;
        LogLevel logLevel = LogLevel.WARN;
        Set<SpecificLog> specificLogs = EnumSet.of(SpecificLog.MODELER, SpecificLog.EQUATIONS);
        String criteriaFileName = "criteria.crt";
        String additionalModelsFileName = "additionalModels.json";
        String dumpFolder = USER_HOME + "dumpFiles";
        String dumpFile = "dumpFile.dmp";
        boolean useDumpFile = true;
        boolean exportDumpFile = true;

        String parametersFile = USER_HOME + "parametersFile";
        String networkParametersFile = USER_HOME + "networkParametersFile";
        String solverParametersFile = USER_HOME + "solverParametersFile";
        String criteriaFile = USER_HOME + criteriaFileName;
        String additionalModelsFile = USER_HOME + "additionalModels.json";

        Map<String, String> properties = new HashMap<>();
        properties.put("parametersFile", parametersFile);
        properties.put("network.parametersFile", networkParametersFile);
        properties.put("network.parametersId", networkParametersId);
        properties.put("solver.parametersFile", solverParametersFile);
        properties.put("solver.parametersId", solverParametersId);
        properties.put("solver.type", solverType.toString());
        properties.put("mergeLoads", Boolean.toString(mergeLoads));
        properties.put("useModelSimplifiers", Boolean.toString(useModelSimplifiers));
        properties.put("precision", Double.toString(precision));
        properties.put("timeline.exportMode", timelinExportMode.toString());
        properties.put("log.levelFilter", logLevel.toString());
        properties.put("log.specificLogs", "MODELER, EQUATIONS");
        properties.put("criteria.file", criteriaFile);
        properties.put("additionalModelsFile", additionalModelsFile);
        properties.put("dump.export", Boolean.toString(exportDumpFile));
        properties.put("dump.exportFolder", dumpFolder);
        properties.put("dump.useAsInput", Boolean.toString(useDumpFile));
        properties.put("dump.fileName", dumpFile);

        createFiles(parametersFile, networkParametersFile, solverParametersFile, criteriaFile, additionalModelsFile);
        createDumpFiles(dumpFolder, dumpFile);

        DynawoSimulationParameters parameters = DynawoSimulationParameters.load(properties, fileSystem);
        checkModelParameters(parameters);
        checkNetworkParameters(parameters, networkParametersId);
        checkSolverParameters(parameters, solverParametersId, solverType);
        assertEquals(mergeLoads, parameters.isMergeLoads());
        assertEquals(useModelSimplifiers, parameters.isUseModelSimplifiers());
        assertEquals(precision, parameters.getPrecision());
        assertEquals(timelinExportMode, parameters.getTimelineExportMode());
        assertEquals(logLevel, parameters.getLogLevelFilter());
        assertThat(parameters.getSpecificLogs()).containsExactlyInAnyOrderElementsOf(specificLogs);
        assertThat(parameters.getCriteriaFileName()).hasValue(criteriaFileName);
        assertThat(parameters.getCriteriaFilePath()).hasValue(fileSystem.getPath(USER_HOME + criteriaFileName));
        assertThat(parameters.getAdditionalModelsPath()).hasValue(fileSystem.getPath(USER_HOME + additionalModelsFileName));
        DumpFileParameters dumpParameters = parameters.getDumpFileParameters();
        assertEquals(exportDumpFile, dumpParameters.exportDumpFile());
        assertEquals(useDumpFile, dumpParameters.useDumpFile());
        assertEquals(dumpFolder, dumpParameters.dumpFileFolder().toString());
        assertEquals(dumpFile, dumpParameters.dumpFile());
    }

    @Test
    void loadAndUploadFromExtendable() {
        DynawoSimulationProvider provider = new DynawoSimulationProvider();
        Optional<Extension<DynamicSimulationParameters>> specificParameters = provider.loadSpecificParameters(Map.of("log.specificLogs", "EQUATIONS"));
        assertThat(specificParameters).isPresent();
        DynawoSimulationParameters parameters = (DynawoSimulationParameters) specificParameters.get();
        assertThat(parameters.getSpecificLogs()).containsExactly(SpecificLog.EQUATIONS);
        parameters.addSpecificLog(SpecificLog.PARAMETERS);
        provider.updateSpecificParameters(parameters, Map.of("useModelSimplifiers", "True"));
        assertThat(parameters.getSpecificLogs()).containsExactly(SpecificLog.PARAMETERS, SpecificLog.EQUATIONS);
        assertTrue(parameters.isUseModelSimplifiers());
    }

    private void createFiles(String parametersFile, String networkParametersFile, String solverParametersFile, String criteriaFile, String additionalModelsFile) throws IOException {
        Files.createDirectories(fileSystem.getPath(USER_HOME));
        copyFile("/parametersSet/models.par", parametersFile);
        copyFile("/parametersSet/network.par", networkParametersFile);
        copyFile("/parametersSet/solvers.par", solverParametersFile);
        if (criteriaFile != null) {
            copyFile("/criteria.crt", criteriaFile);
        }
        if (additionalModelsFile != null) {
            copyFile("/additionalModels.json", additionalModelsFile);
        }
    }

    private void createDumpFiles(String folderName, String fileName) throws IOException {
        Files.createDirectories(fileSystem.getPath(folderName));
        Files.createFile(fileSystem.getPath(folderName, fileName));
    }

    private static void checkModelParameters(DynawoSimulationParameters dynawoSimulationParameters) {
        Parameter booleanParameter = dynawoSimulationParameters.getModelParameters("test").getParameters().get("boolean");
        assertEquals("true", booleanParameter.value());
        assertEquals("boolean", booleanParameter.name());
        assertEquals(ParameterType.BOOL, booleanParameter.type());
        Parameter stringParameter = dynawoSimulationParameters.getModelParameters("test").getParameters().get("string");
        assertEquals("aString", stringParameter.value());
        assertEquals("string", stringParameter.name());
        assertEquals(ParameterType.STRING, stringParameter.type());
    }

    private static void checkNetworkParameters(DynawoSimulationParameters parameters, String networkParametersId) {
        assertEquals(networkParametersId, parameters.getNetworkParameters().getId());
        Map<String, Parameter> networkParameters = parameters.getNetworkParameters().getParameters();
        Parameter loadTp = networkParameters.get("load_Tp");
        assertEquals("90", loadTp.value());
        assertEquals("load_Tp", loadTp.name());
        assertEquals(ParameterType.DOUBLE, loadTp.type());
        Parameter loadControllable = networkParameters.get("load_isControllable");
        assertEquals("false", loadControllable.value());
        assertEquals("load_isControllable", loadControllable.name());
        assertEquals(ParameterType.BOOL, loadControllable.type());
    }

    private static void checkSolverParameters(DynawoSimulationParameters parameters, String solverParametersId, SolverType solverType) {
        Map<String, Parameter> solverParameters = parameters.getSolverParameters().getParameters();
        assertEquals(solverParametersId, parameters.getSolverParameters().getId());
        assertEquals(solverType, parameters.getSolverType());
        Parameter order = solverParameters.get("order");
        assertEquals("1", order.value());
        assertEquals("order", order.name());
        assertEquals(ParameterType.INT, order.type());
        Parameter absAccuracy = solverParameters.get("absAccuracy");
        assertEquals("1e-4", absAccuracy.value());
        assertEquals("absAccuracy", absAccuracy.name());
        assertEquals(ParameterType.DOUBLE, absAccuracy.type());
    }
}