DynawoSimulationParameters.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.fasterxml.jackson.annotation.JsonGetter;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonSetter;
import com.powsybl.commons.PowsyblException;
import com.powsybl.commons.config.ModuleConfig;
import com.powsybl.commons.config.PlatformConfig;
import com.powsybl.commons.extensions.AbstractExtension;
import com.powsybl.commons.parameters.Parameter;
import com.powsybl.commons.parameters.ParameterType;
import com.powsybl.dynamicsimulation.DynamicSimulationParameters;
import com.powsybl.dynawo.commons.ExportMode;
import com.powsybl.dynawo.parameters.ParametersSet;
import com.powsybl.dynawo.xml.ParametersXml;

import java.io.InputStream;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static com.powsybl.dynawo.commons.ParametersUtils.*;

/**
 * @author Laurent Issertial {@literal <laurent.issertial at rte-france.com>}
 * @author Marcos de Miguel {@literal <demiguelm at aia.es>}
 * @author Florian Dupuy {@literal <florian.dupuy at rte-france.com>}
 */
@JsonIgnoreProperties(value = { "criteriaFileName" })
public class DynawoSimulationParameters extends AbstractExtension<DynamicSimulationParameters> {

    public static final String MODULE_SPECIFIC_PARAMETERS = "dynawo-simulation-default-parameters";

    public static final String DEFAULT_INPUT_PARAMETERS_FILE = "models.par";
    public static final String DEFAULT_INPUT_NETWORK_PARAMETERS_FILE = "network.par";
    public static final String DEFAULT_INPUT_SOLVER_PARAMETERS_FILE = "solvers.par";
    public static final SolverType DEFAULT_SOLVER_TYPE = SolverType.SIM;
    public static final String DEFAULT_NETWORK_PAR_ID = "1";
    public static final String DEFAULT_SOLVER_PAR_ID = "1";
    public static final boolean DEFAULT_MERGE_LOADS = false;
    public static final boolean DEFAULT_USE_MODEL_SIMPLIFIERS = false;
    public static final double DEFAULT_PRECISION = 1e-6;
    public static final ExportMode DEFAULT_TIMELINE_EXPORT_MODE = ExportMode.TXT;
    public static final LogLevel DEFAULT_LOG_LEVEL_FILTER = LogLevel.INFO;

    private static final String PARAMETERS_FILE = "parametersFile";
    private static final String NETWORK_PARAMETERS_FILE = "network.parametersFile";
    private static final String NETWORK_PARAMETERS_ID = "network.parametersId";
    private static final String SOLVER_PARAMETERS_FILE = "solver.parametersFile";
    private static final String SOLVER_PARAMETERS_ID = "solver.parametersId";
    private static final String SOLVER_TYPE = "solver.type";
    private static final String MERGE_LOADS = "mergeLoads";
    private static final String USE_MODEL_SIMPLIFIERS = "useModelSimplifiers";
    private static final String PRECISION_PROPERTY_NAME = "precision";
    private static final String TIMELINE_EXPORT_MODE = "timeline.exportMode";
    private static final String LOG_LEVEL_FILTER = "log.levelFilter";
    private static final String LOG_SPECIFIC_LOGS = "log.specificLogs";
    private static final String CRITERIA_FILE = "criteria.file";
    private static final String ADDITIONAL_MODELS_FILE = "additionalModelsFile";

    /**
     * Information about the solver to use in the simulation
     */
    public enum SolverType {
        /**
         * the simplified solver
         */
        SIM,
        /**
         * the IDA solver
         */
        IDA
    }

    public enum LogLevel {
        DEBUG,
        INFO,
        WARN,
        ERROR
    }

    public enum SpecificLog {

        NETWORK("network"),
        MODELER("modeler"),
        PARAMETERS("param"),
        VARIABLES("variables"),
        EQUATIONS("equations");

        private static final String DEFAULT_FILE_EXTENSION = ExportMode.TXT.getFileExtension();

        private final String fileName;

        SpecificLog(String fileName) {
            this.fileName = fileName;
        }

        public String getFileName() {
            return fileName + DEFAULT_FILE_EXTENSION;
        }
    }

    private Map<String, ParametersSet> modelsParameters = new LinkedHashMap<>();
    private ParametersSet networkParameters;
    private ParametersSet solverParameters;
    private SolverType solverType = DEFAULT_SOLVER_TYPE;
    private boolean mergeLoads = DEFAULT_MERGE_LOADS;
    private boolean useModelSimplifiers = DEFAULT_USE_MODEL_SIMPLIFIERS;
    private DumpFileParameters dumpFileParameters = DumpFileParameters.createDefaultDumpFileParameters();
    private double precision = DEFAULT_PRECISION;
    private ExportMode timelineExportMode = DEFAULT_TIMELINE_EXPORT_MODE;
    private LogLevel logLevelFilter = DEFAULT_LOG_LEVEL_FILTER;
    private EnumSet<SpecificLog> specificLogs = EnumSet.noneOf(SpecificLog.class);
    private Path criteriaFilePath = null;
    private Path additionalModelsPath = null;

    public static final List<Parameter> SPECIFIC_PARAMETERS = Stream.concat(Stream.of(
            new Parameter(PARAMETERS_FILE, ParameterType.STRING, "Main parameters file path", DEFAULT_INPUT_PARAMETERS_FILE),
            new Parameter(NETWORK_PARAMETERS_FILE, ParameterType.STRING, "Network parameters file path", DEFAULT_INPUT_NETWORK_PARAMETERS_FILE),
            new Parameter(NETWORK_PARAMETERS_ID, ParameterType.STRING, "Network parameters set id", DEFAULT_NETWORK_PAR_ID),
            new Parameter(SOLVER_PARAMETERS_FILE, ParameterType.STRING, "Solver parameters file path", DEFAULT_INPUT_SOLVER_PARAMETERS_FILE),
            new Parameter(SOLVER_PARAMETERS_ID, ParameterType.STRING, "Solver parameters set id", DEFAULT_SOLVER_PAR_ID),
            new Parameter(SOLVER_TYPE, ParameterType.STRING, "Solver used in the simulation", DEFAULT_SOLVER_TYPE.toString(), getEnumPossibleValues(SolverType.class)),
            new Parameter(MERGE_LOADS, ParameterType.BOOLEAN, "Merge loads connected to same bus", DEFAULT_MERGE_LOADS),
            new Parameter(USE_MODEL_SIMPLIFIERS, ParameterType.BOOLEAN, "Simplifiers used before macro connection computation", DEFAULT_USE_MODEL_SIMPLIFIERS),
            new Parameter(PRECISION_PROPERTY_NAME, ParameterType.DOUBLE, "Simulation step precision", DEFAULT_PRECISION),
            new Parameter(TIMELINE_EXPORT_MODE, ParameterType.STRING, "Timeline export file extension", DEFAULT_TIMELINE_EXPORT_MODE.toString(), getEnumPossibleValues(ExportMode.class)),
            new Parameter(LOG_LEVEL_FILTER, ParameterType.STRING, "Dynawo log level", DEFAULT_LOG_LEVEL_FILTER.toString(), getEnumPossibleValues(LogLevel.class)),
            new Parameter(LOG_SPECIFIC_LOGS, ParameterType.STRING, "List specific logs returned", null, getEnumPossibleValues(SpecificLog.class)),
            new Parameter(CRITERIA_FILE, ParameterType.STRING, "Simulation criteria file path", null),
            new Parameter(ADDITIONAL_MODELS_FILE, ParameterType.STRING, "Additional models file path", null)),
            DumpFileParameters.SPECIFIC_PARAMETERS.stream()).toList();

    /**
     * Loads parameters from the default platform configuration.
     */
    public static DynawoSimulationParameters load() {
        return load(PlatformConfig.defaultConfig());
    }

    /**
     * Load parameters from a provided platform configuration.
     */
    public static DynawoSimulationParameters load(PlatformConfig platformConfig) {
        return load(platformConfig, FileSystems.getDefault());
    }

    public static DynawoSimulationParameters load(PlatformConfig platformConfig, FileSystem fileSystem) {
        DynawoSimulationParameters parameters = new DynawoSimulationParameters();
        Optional<ModuleConfig> config = platformConfig.getOptionalModuleConfig(MODULE_SPECIFIC_PARAMETERS);
        config.ifPresent(c -> {
            c.getOptionalStringProperty(PARAMETERS_FILE).ifPresent(f -> {
                Path path = resolveFilePath(f, platformConfig, fileSystem);
                if (Files.exists(path)) {
                    parameters.setModelsParameters(ParametersXml.load(path));
                }
            });
            c.getOptionalStringProperty(NETWORK_PARAMETERS_FILE).ifPresent(f -> {
                Path path = resolveFilePath(f, platformConfig, fileSystem);
                if (Files.exists(path)) {
                    parameters.setNetworkParameters(ParametersXml.load(path,
                            c.getOptionalStringProperty(NETWORK_PARAMETERS_ID).orElse(DEFAULT_NETWORK_PAR_ID)));
                }
            });
            c.getOptionalStringProperty(SOLVER_PARAMETERS_FILE).ifPresent(f -> {
                Path path = resolveFilePath(f, platformConfig, fileSystem);
                if (Files.exists(path)) {
                    parameters.setSolverParameters(ParametersXml.load(path,
                            c.getOptionalStringProperty(SOLVER_PARAMETERS_ID).orElse(DEFAULT_SOLVER_PAR_ID)));
                }
            });
            parameters.setDumpFileParameters(DumpFileParameters.createDumpFileParametersFromConfig(c, f -> resolveFilePath(f, platformConfig, fileSystem)));
            c.getOptionalEnumProperty(SOLVER_TYPE, SolverType.class).ifPresent(parameters::setSolverType);
            c.getOptionalBooleanProperty(MERGE_LOADS).ifPresent(parameters::setMergeLoads);
            c.getOptionalBooleanProperty(USE_MODEL_SIMPLIFIERS).ifPresent(parameters::setUseModelSimplifiers);
            c.getOptionalDoubleProperty(PRECISION_PROPERTY_NAME).ifPresent(parameters::setPrecision);
            c.getOptionalEnumProperty(TIMELINE_EXPORT_MODE, ExportMode.class).ifPresent(parameters::setTimelineExportMode);
            c.getOptionalEnumProperty(LOG_LEVEL_FILTER, LogLevel.class).ifPresent(parameters::setLogLevelFilter);
            c.getOptionalEnumSetProperty(LOG_SPECIFIC_LOGS, SpecificLog.class).ifPresent(parameters::setSpecificLogs);
            c.getOptionalStringProperty(CRITERIA_FILE).ifPresent(cf -> parameters.setCriteriaFilePath(resolveFilePath(cf, platformConfig, fileSystem)));
            c.getOptionalStringProperty(ADDITIONAL_MODELS_FILE).ifPresent(am -> parameters.setAdditionalModelsPath(resolveFilePath(am, platformConfig, fileSystem)));
        });
        return parameters;
    }

    private static Path resolveFilePath(String fileName, PlatformConfig platformConfig, FileSystem fileSystem) {
        return platformConfig.getConfigDir().map(configDir -> configDir.resolve(fileName)).orElse(fileSystem.getPath(fileName));
    }

    public static DynawoSimulationParameters load(Map<String, String> properties) {
        return load(properties, FileSystems.getDefault());
    }

    public static DynawoSimulationParameters load(Map<String, String> properties, FileSystem fileSystem) {
        DynawoSimulationParameters parameters = new DynawoSimulationParameters();
        parameters.update(properties, fileSystem);
        return parameters;
    }

    public static DynawoSimulationParameters load(DynamicSimulationParameters parameters) {
        DynawoSimulationParameters dynawoSimulationParameters = parameters.getExtension(DynawoSimulationParameters.class);
        if (dynawoSimulationParameters == null) {
            dynawoSimulationParameters = DynawoSimulationParameters.load();
        }
        return dynawoSimulationParameters;
    }

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

    public void update(Map<String, String> properties) {
        update(properties, FileSystems.getDefault());
    }

    public void update(Map<String, String> properties, FileSystem fileSystem) {
        Objects.requireNonNull(properties);
        Optional.ofNullable(properties.get(PARAMETERS_FILE)).ifPresent(prop -> {
            Path path = fileSystem.getPath(prop);
            if (Files.exists(path)) {
                setModelsParameters(ParametersXml.load(path));
            }
        });
        Optional.ofNullable(properties.get(NETWORK_PARAMETERS_FILE)).ifPresent(prop -> {
            Path path = fileSystem.getPath(prop);
            if (Files.exists(path)) {
                setNetworkParameters(ParametersXml.load(path,
                        Optional.ofNullable(properties.get(NETWORK_PARAMETERS_ID)).orElse(DEFAULT_NETWORK_PAR_ID)));
            }
        });
        Optional.ofNullable(properties.get(SOLVER_PARAMETERS_FILE)).ifPresent(prop -> {
            Path path = fileSystem.getPath(prop);
            if (Files.exists(path)) {
                setSolverParameters(ParametersXml.load(path,
                        Optional.ofNullable(properties.get(SOLVER_PARAMETERS_ID)).orElse(DEFAULT_SOLVER_PAR_ID)));
            }
        });
        Optional.ofNullable(properties.get(SOLVER_TYPE)).ifPresent(prop -> setSolverType(SolverType.valueOf(prop)));
        Optional.ofNullable(properties.get(MERGE_LOADS)).ifPresent(prop -> setMergeLoads(Boolean.parseBoolean(prop)));
        Optional.ofNullable(properties.get(USE_MODEL_SIMPLIFIERS)).ifPresent(prop -> setUseModelSimplifiers(Boolean.parseBoolean(prop)));
        Optional.ofNullable(properties.get(PRECISION_PROPERTY_NAME)).ifPresent(prop -> setPrecision(Double.parseDouble(prop)));
        Optional.ofNullable(properties.get(TIMELINE_EXPORT_MODE)).ifPresent(prop -> setTimelineExportMode(ExportMode.valueOf(prop)));
        Optional.ofNullable(properties.get(LOG_LEVEL_FILTER)).ifPresent(prop -> setLogLevelFilter(LogLevel.valueOf(prop)));
        Optional.ofNullable(properties.get(LOG_SPECIFIC_LOGS)).ifPresent(prop ->
                setSpecificLogs(Stream.of(prop.split(PROPERTY_LIST_DELIMITER)).map(o -> SpecificLog.valueOf(o.trim())).collect(Collectors.toSet())));
        Optional.ofNullable(properties.get(CRITERIA_FILE)).ifPresent(prop -> setCriteriaFilePath(prop, fileSystem));
        Optional.ofNullable(properties.get(ADDITIONAL_MODELS_FILE)).ifPresent(prop -> setAdditionalModelsPath(prop, fileSystem));
        dumpFileParameters = DumpFileParameters.updateDumpFileParametersFromPropertiesMap(properties, dumpFileParameters, fileSystem::getPath);
    }

    public Map<String, String> createMapFromParameters() {
        Map<String, String> properties = new HashMap<>();
        addNotNullEntry("modelParameters", modelsParameters, properties::put);
        addNotNullEntry("networkParameters", networkParameters, properties::put);
        addNotNullEntry("solverParameters", solverParameters, properties::put);
        addNotNullEntry(SOLVER_TYPE, solverType, properties::put);
        addNotNullEntry(MERGE_LOADS, mergeLoads, properties::put);
        addNotNullEntry(USE_MODEL_SIMPLIFIERS, useModelSimplifiers, properties::put);
        addNotNullEntry(PRECISION_PROPERTY_NAME, precision, properties::put);
        addNotNullEntry(TIMELINE_EXPORT_MODE, timelineExportMode, properties::put);
        addNotNullEntry(LOG_LEVEL_FILTER, logLevelFilter, properties::put);
        if (!specificLogs.isEmpty()) {
            properties.put(LOG_SPECIFIC_LOGS, String.join(PROPERTY_LIST_DELIMITER, specificLogs.stream().map(SpecificLog::name).toList()));
        }
        addNotNullEntry(CRITERIA_FILE, criteriaFilePath, properties::put);
        addNotNullEntry(ADDITIONAL_MODELS_FILE, additionalModelsPath, properties::put);
        dumpFileParameters.addParametersToMap((k, v) -> addNotNullEntry(k, v, properties::put));
        return properties;
    }

    public void addModelParameters(ParametersSet parameterSet) {
        modelsParameters.put(parameterSet.getId(), parameterSet);
    }

    public ParametersSet getModelParameters(String parameterSetId) {
        ParametersSet parametersSet = modelsParameters.get(parameterSetId);
        if (parametersSet == null) {
            throw new PowsyblException("Model parameter set " + parameterSetId + " not found");
        }
        return parametersSet;
    }

    @JsonGetter("modelsParameters")
    public Collection<ParametersSet> getModelParameters() {
        return modelsParameters.values();
    }

    @JsonSetter("modelsParameters")
    public DynawoSimulationParameters setModelsParameters(Collection<ParametersSet> parametersSets) {
        modelsParameters = new LinkedHashMap<>();
        parametersSets.forEach(parametersSet -> modelsParameters.put(parametersSet.getId(), parametersSet));
        return this;
    }

    public DynawoSimulationParameters setModelsParameters(InputStream inputStream) {
        setModelsParameters(ParametersXml.load(inputStream));
        return this;
    }

    public DynawoSimulationParameters setNetworkParameters(ParametersSet networkParameters) {
        this.networkParameters = Objects.requireNonNull(networkParameters);
        return this;
    }

    public DynawoSimulationParameters setNetworkParameters(InputStream inputStream, String parameterSetId) {
        this.networkParameters = ParametersXml.load(inputStream, parameterSetId);
        return this;
    }

    public ParametersSet getNetworkParameters() {
        return networkParameters;
    }

    public DynawoSimulationParameters setSolverParameters(ParametersSet solverParameters) {
        this.solverParameters = Objects.requireNonNull(solverParameters);
        return this;
    }

    public DynawoSimulationParameters setSolverParameters(InputStream inputStream, String parameterSetId) {
        this.solverParameters = ParametersXml.load(inputStream, parameterSetId);
        return this;
    }

    public ParametersSet getSolverParameters() {
        return solverParameters;
    }

    public DynawoSimulationParameters setSolverType(SolverType solverType) {
        this.solverType = solverType;
        return this;
    }

    public SolverType getSolverType() {
        return solverType;
    }

    public boolean isMergeLoads() {
        return mergeLoads;
    }

    public DynawoSimulationParameters setMergeLoads(boolean mergeLoads) {
        this.mergeLoads = mergeLoads;
        return this;
    }

    public boolean isUseModelSimplifiers() {
        return useModelSimplifiers;
    }

    public DynawoSimulationParameters setUseModelSimplifiers(boolean useModelSimplifiers) {
        this.useModelSimplifiers = useModelSimplifiers;
        return this;
    }

    public DumpFileParameters getDumpFileParameters() {
        return dumpFileParameters;
    }

    public DynawoSimulationParameters setDumpFileParameters(DumpFileParameters dumpFileParameters) {
        this.dumpFileParameters = dumpFileParameters;
        return this;
    }

    public double getPrecision() {
        return precision;
    }

    public DynawoSimulationParameters setPrecision(double precision) {
        this.precision = precision;
        return this;
    }

    public ExportMode getTimelineExportMode() {
        return timelineExportMode;
    }

    public DynawoSimulationParameters setTimelineExportMode(ExportMode timelineExportMode) {
        this.timelineExportMode = timelineExportMode;
        return this;
    }

    public LogLevel getLogLevelFilter() {
        return logLevelFilter;
    }

    public DynawoSimulationParameters setLogLevelFilter(LogLevel logLevelFilter) {
        this.logLevelFilter = logLevelFilter;
        return this;
    }

    public Set<SpecificLog> getSpecificLogs() {
        return specificLogs;
    }

    public DynawoSimulationParameters setSpecificLogs(Set<SpecificLog> specificLogs) {
        if (specificLogs.isEmpty()) {
            this.specificLogs = EnumSet.noneOf(SpecificLog.class);
        } else {
            this.specificLogs = EnumSet.copyOf(specificLogs);
        }
        return this;
    }

    public DynawoSimulationParameters addSpecificLog(SpecificLog specificLog) {
        specificLogs.add(specificLog);
        return this;
    }

    public Optional<Path> getCriteriaFilePath() {
        return Optional.ofNullable(criteriaFilePath);
    }

    public Optional<String> getCriteriaFileName() {
        return getCriteriaFilePath().map(c -> c.getFileName().toString());
    }

    public DynawoSimulationParameters setCriteriaFilePath(Path criteriaFilePath) {
        this.criteriaFilePath = criteriaFilePath;
        return this;
    }

    private void setCriteriaFilePath(String criteriaPathName, FileSystem fileSystem) {
        Path criteriaPath = criteriaPathName != null ? fileSystem.getPath(criteriaPathName) : null;
        if (criteriaPath == null || !Files.exists(criteriaPath)) {
            throw new PowsyblException("File " + criteriaPathName + " set in 'criteria.file' property cannot be found");
        }
        setCriteriaFilePath(criteriaPath);
    }

    public Optional<Path> getAdditionalModelsPath() {
        return Optional.ofNullable(additionalModelsPath);
    }

    public DynawoSimulationParameters setAdditionalModelsPath(Path additionalModelsPath) {
        this.additionalModelsPath = additionalModelsPath;
        return this;
    }

    private void setAdditionalModelsPath(String additionalModelsPathName, FileSystem fileSystem) {
        Path path = additionalModelsPathName != null ? fileSystem.getPath(additionalModelsPathName) : null;
        if (path == null || !Files.exists(path)) {
            throw new PowsyblException("File " + additionalModelsPathName + " set in 'additionalModelsFile' property cannot be found");
        }
        setAdditionalModelsPath(path);
    }
}