LoadFlowParameters.java

/**
 * Copyright (c) 2016, All partners of the iTesla project (http://www.itesla-project.eu/consortium)
 * 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.loadflow;

import com.fasterxml.jackson.core.util.ByteArrayBuilder;
import com.google.common.collect.ImmutableMap;
import com.powsybl.commons.PowsyblException;
import com.powsybl.commons.config.PlatformConfig;
import com.powsybl.commons.extensions.AbstractExtendable;
import com.powsybl.commons.extensions.Extension;
import com.powsybl.commons.util.ServiceLoaderCache;
import com.powsybl.iidm.network.Country;
import com.powsybl.loadflow.json.JsonLoadFlowParameters;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.*;

/**
 * Parameters for loadflow computation.
 * Extensions may be added, for instance for implementation-specific parameters.
 *
 * @author Geoffroy Jamgotchian {@literal <geoffroy.jamgotchian at rte-france.com>}
 */
public class LoadFlowParameters extends AbstractExtendable<LoadFlowParameters> {

    public enum VoltageInitMode {
        UNIFORM_VALUES, // v=1pu, theta=0
        PREVIOUS_VALUES,
        DC_VALUES // preprocessing to compute DC angles
    }

    /**
     * BalanceType enum describes the various options for active power slack distribution
     */
    public enum BalanceType {
        /**
         * active power slack distribution on generators, proportional to generator targetP
         */
        PROPORTIONAL_TO_GENERATION_P,
        /**
         * active power slack distribution on generators, proportional to generator maxP
         */
        PROPORTIONAL_TO_GENERATION_P_MAX,
        /**
         * active power slack distribution on generators, proportional to generator maxP - targetP if active production is increased, and proportional to targetP - minP if decreased.
         */
        PROPORTIONAL_TO_GENERATION_REMAINING_MARGIN,
        /**
         * active power slack distribution on generators, proportional to participationFactor (see ActivePowerControl extension)
         */
        PROPORTIONAL_TO_GENERATION_PARTICIPATION_FACTOR,
        /**
         * active power slack distribution on all loads
         */
        PROPORTIONAL_TO_LOAD,
        /**
         * active power slack distribution on conforming loads (see LoadDetails extension)
         */
        PROPORTIONAL_TO_CONFORM_LOAD,
    }

    public enum ConnectedComponentMode {
        MAIN,
        ALL,
    }

    public static final Logger LOGGER = LoggerFactory.getLogger(LoadFlowParameters.class);

    // VERSION = 1.0 specificCompatibility
    // VERSION = 1.1 t2wtSplitShuntAdmittance
    // VERSION = 1.2 twtSplitShuntAdmittance,
    // VERSION = 1.3 simulShunt, read/write slack bus
    // VERSION = 1.4 dc, distributedSlack, balanceType
    // VERSION = 1.5 dcUseTransformerRatio, countriesToBalance, computedConnectedComponentScope
    // VERSION = 1.6 shuntCompensatorVoltageControlOn instead of simulShunt
    // VERSION = 1.7 hvdcAcEmulation
    // VERSION = 1.8 noGeneratorReactiveLimits -> useReactiveLimits
    // VERSION = 1.9 dcPowerFactor
    public static final String VERSION = "1.9";

    public static final VoltageInitMode DEFAULT_VOLTAGE_INIT_MODE = VoltageInitMode.UNIFORM_VALUES;
    public static final boolean DEFAULT_TRANSFORMER_VOLTAGE_CONTROL_ON = false;
    public static final boolean DEFAULT_USE_REACTIVE_LIMITS = true;
    public static final boolean DEFAULT_PHASE_SHIFTER_REGULATION_ON = false;
    public static final boolean DEFAULT_TWT_SPLIT_SHUNT_ADMITTANCE = false;
    public static final boolean DEFAULT_SHUNT_COMPENSATOR_VOLTAGE_CONTROL_ON = false;
    public static final boolean DEFAULT_READ_SLACK_BUS = true;
    public static final boolean DEFAULT_WRITE_SLACK_BUS = true;
    public static final boolean DEFAULT_DC = false;
    public static final boolean DEFAULT_DISTRIBUTED_SLACK = true;
    public static final BalanceType DEFAULT_BALANCE_TYPE = BalanceType.PROPORTIONAL_TO_GENERATION_P_MAX;
    public static final boolean DEFAULT_DC_USE_TRANSFORMER_RATIO_DEFAULT = true;
    public static final Set<Country> DEFAULT_COUNTRIES_TO_BALANCE = Collections.unmodifiableSet(EnumSet.noneOf(Country.class));
    public static final ConnectedComponentMode DEFAULT_CONNECTED_COMPONENT_MODE = ConnectedComponentMode.MAIN;
    public static final boolean DEFAULT_HVDC_AC_EMULATION_ON = true;
    public static final double DEFAULT_DC_POWER_FACTOR = 1d;

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

    /**
     * Load parameters from a provided platform configuration.
     */
    public static LoadFlowParameters load(PlatformConfig platformConfig) {
        LoadFlowParameters parameters = new LoadFlowParameters(platformConfig);
        load(parameters, platformConfig);

        parameters.loadExtensions(platformConfig);

        return parameters;
    }

    protected static void load(LoadFlowParameters parameters) {
        load(parameters, PlatformConfig.defaultConfig());
    }

    protected static void load(LoadFlowParameters parameters, PlatformConfig platformConfig) {
        Objects.requireNonNull(parameters);
        Objects.requireNonNull(platformConfig);

        // Only the parameters present in platformConfig will be updated and no default value will be set for the absent parameters
        // (unlike what is done for the other parameters classes).
        // This is needed for the LoadFlowDefaultParametersLoader mechanism to work (else the default values defined
        // by the loader will be overwritten by the hardcoded ones).
        platformConfig.getOptionalModuleConfig("load-flow-default-parameters")
                .ifPresent(config -> {
                    config.getOptionalEnumProperty("voltageInitMode", VoltageInitMode.class).ifPresent(parameters::setVoltageInitMode);
                    config.getOptionalBooleanProperty("transformerVoltageControlOn").ifPresent(parameters::setTransformerVoltageControlOn);
                    config.getOptionalBooleanProperty("useReactiveLimits").ifPresentOrElse(parameters::setUseReactiveLimits,
                            () -> config.getOptionalBooleanProperty("noGeneratorReactiveLimits").ifPresent(value -> parameters.setUseReactiveLimits(!value)));
                    config.getOptionalBooleanProperty("phaseShifterRegulationOn").ifPresent(parameters::setPhaseShifterRegulationOn);
                    config.getOptionalBooleanProperty("twtSplitShuntAdmittance").ifPresentOrElse(parameters::setTwtSplitShuntAdmittance,
                            () -> config.getOptionalBooleanProperty("specificCompatibility").ifPresent(parameters::setTwtSplitShuntAdmittance));
                    config.getOptionalBooleanProperty("shuntCompensatorVoltageControlOn").ifPresentOrElse(parameters::setShuntCompensatorVoltageControlOn,
                            () -> config.getOptionalBooleanProperty("simulShunt").ifPresent(parameters::setShuntCompensatorVoltageControlOn));
                    config.getOptionalBooleanProperty("readSlackBus").ifPresent(parameters::setReadSlackBus);
                    config.getOptionalBooleanProperty("writeSlackBus").ifPresent(parameters::setWriteSlackBus);
                    config.getOptionalBooleanProperty("dc").ifPresent(parameters::setDc);
                    config.getOptionalBooleanProperty("distributedSlack").ifPresent(parameters::setDistributedSlack);
                    config.getOptionalEnumProperty("balanceType", BalanceType.class).ifPresent(parameters::setBalanceType);
                    config.getOptionalBooleanProperty("dcUseTransformerRatio").ifPresent(parameters::setDcUseTransformerRatio);
                    config.getOptionalEnumSetProperty("countriesToBalance", Country.class).ifPresent(parameters::setCountriesToBalance);
                    config.getOptionalEnumProperty("connectedComponentMode", ConnectedComponentMode.class).ifPresent(parameters::setConnectedComponentMode);
                    config.getOptionalBooleanProperty("hvdcAcEmulation").ifPresent(parameters::setHvdcAcEmulation);
                    config.getOptionalDoubleProperty("dcPowerFactor").ifPresent(parameters::setDcPowerFactor);
                });
    }

    private VoltageInitMode voltageInitMode = DEFAULT_VOLTAGE_INIT_MODE;

    private boolean transformerVoltageControlOn = DEFAULT_TRANSFORMER_VOLTAGE_CONTROL_ON;

    private boolean useReactiveLimits = DEFAULT_USE_REACTIVE_LIMITS;

    private boolean phaseShifterRegulationOn = DEFAULT_PHASE_SHIFTER_REGULATION_ON;

    private boolean twtSplitShuntAdmittance = DEFAULT_TWT_SPLIT_SHUNT_ADMITTANCE;

    private boolean shuntCompensatorVoltageControlOn = DEFAULT_SHUNT_COMPENSATOR_VOLTAGE_CONTROL_ON;

    private boolean readSlackBus = DEFAULT_READ_SLACK_BUS;

    private boolean writeSlackBus = DEFAULT_WRITE_SLACK_BUS;

    private boolean dc = DEFAULT_DC;

    private boolean distributedSlack = DEFAULT_DISTRIBUTED_SLACK;

    private BalanceType balanceType = DEFAULT_BALANCE_TYPE;

    private boolean dcUseTransformerRatio = DEFAULT_DC_USE_TRANSFORMER_RATIO_DEFAULT;

    private Set<Country> countriesToBalance = DEFAULT_COUNTRIES_TO_BALANCE;

    private ConnectedComponentMode connectedComponentMode = DEFAULT_CONNECTED_COMPONENT_MODE;

    private boolean hvdcAcEmulation = DEFAULT_HVDC_AC_EMULATION_ON;

    private double dcPowerFactor = DEFAULT_DC_POWER_FACTOR;

    public LoadFlowParameters() {
        this(PlatformConfig.defaultConfig());
    }

    protected LoadFlowParameters(PlatformConfig config) {
        this(ServiceLoader.load(LoadFlowDefaultParametersLoader.class)
                .stream()
                .map(ServiceLoader.Provider::get)
                .toList(), config);
    }

    public LoadFlowParameters(List<LoadFlowDefaultParametersLoader> defaultParametersLoaders) {
        this(defaultParametersLoaders, PlatformConfig.defaultConfig());
    }

    public LoadFlowParameters(List<LoadFlowDefaultParametersLoader> defaultParametersLoaders, PlatformConfig config) {
        // Check default-parameters-loader config parameter
        String selectedLoaderName;
        Optional<LoadFlowDefaultParametersLoader> selectedLoader = Optional.empty();
        selectedLoaderName = config.getOptionalModuleConfig("load-flow")
                .flatMap(moduleConfig -> moduleConfig.getOptionalStringProperty("default-parameters-loader"))
                .orElse(null);
        if (selectedLoaderName != null) {
            selectedLoader = defaultParametersLoaders.stream()
                    .filter(loader -> loader.getSourceName().equals(selectedLoaderName))
                    .findFirst();
            if (selectedLoader.isEmpty()) {
                LOGGER.warn("Parameter 'default-parameters-loader' of module 'load-flow' ({}) does not match any" +
                        " LoadFlowDefaultParametersLoader found in the classpath", selectedLoaderName);
            }
        } else {
            int numberOfLoadersFound = Objects.requireNonNull(defaultParametersLoaders).size();
            if (numberOfLoadersFound > 1) {
                List<String> names = defaultParametersLoaders.stream()
                        .map(LoadFlowDefaultParametersLoader::getSourceName).toList();
                String message = String.format("Multiple default loadflow parameter loader classes have been found in the class path : %s." +
                                " Specify which one to use with the 'default-parameters-loader' parameter of 'load-flow' module " +
                                "of Powsybl configuration.", names);
                throw new PowsyblException(message);
            } else if (numberOfLoadersFound == 1) {
                selectedLoader = defaultParametersLoaders.stream().findFirst();
            }
        }

        if (selectedLoader.isPresent()) {
            JsonLoadFlowParameters.update(this, selectedLoader.get().loadDefaultParametersFromFile());
            LOGGER.debug("Default loadflow configuration has been updated using the reference from parameters loader '{}'",
                    selectedLoader.get().getSourceName());
        }
    }

    protected LoadFlowParameters(LoadFlowParameters other) {
        Objects.requireNonNull(other);
        voltageInitMode = other.voltageInitMode;
        transformerVoltageControlOn = other.transformerVoltageControlOn;
        useReactiveLimits = other.useReactiveLimits;
        phaseShifterRegulationOn = other.phaseShifterRegulationOn;
        twtSplitShuntAdmittance = other.twtSplitShuntAdmittance;
        shuntCompensatorVoltageControlOn = other.shuntCompensatorVoltageControlOn;
        readSlackBus = other.readSlackBus;
        writeSlackBus = other.writeSlackBus;
        dc = other.dc;
        distributedSlack = other.distributedSlack;
        balanceType = other.balanceType;
        dcUseTransformerRatio = other.dcUseTransformerRatio;
        countriesToBalance = other.countriesToBalance;
        connectedComponentMode = other.connectedComponentMode;
        hvdcAcEmulation = other.hvdcAcEmulation;
        dcPowerFactor = other.dcPowerFactor;
    }

    public VoltageInitMode getVoltageInitMode() {
        return voltageInitMode;
    }

    public LoadFlowParameters setVoltageInitMode(VoltageInitMode voltageInitMode) {
        this.voltageInitMode = Objects.requireNonNull(voltageInitMode);
        return this;
    }

    public boolean isTransformerVoltageControlOn() {
        return transformerVoltageControlOn;
    }

    public LoadFlowParameters setTransformerVoltageControlOn(boolean transformerVoltageControlOn) {
        this.transformerVoltageControlOn = transformerVoltageControlOn;
        return this;
    }

    public boolean isUseReactiveLimits() {
        return useReactiveLimits;
    }

    public LoadFlowParameters setUseReactiveLimits(boolean useReactiveLimits) {
        this.useReactiveLimits = useReactiveLimits;
        return this;
    }

    /**
     * @deprecated Use {@link #isUseReactiveLimits} instead.
     */
    @Deprecated(since = "5.1.0")
    public boolean isNoGeneratorReactiveLimits() {
        return !useReactiveLimits;
    }

    /**
     * @deprecated Use {@link #setNoGeneratorReactiveLimits} instead.
     */
    @Deprecated(since = "5.1.0")
    public LoadFlowParameters setNoGeneratorReactiveLimits(boolean noGeneratorReactiveLimits) {
        this.useReactiveLimits = !noGeneratorReactiveLimits;
        return this;
    }

    public boolean isPhaseShifterRegulationOn() {
        return phaseShifterRegulationOn;
    }

    public LoadFlowParameters setPhaseShifterRegulationOn(boolean phaseShifterRegulationOn) {
        this.phaseShifterRegulationOn = phaseShifterRegulationOn;
        return this;
    }

    public boolean isTwtSplitShuntAdmittance() {
        return twtSplitShuntAdmittance;
    }

    public LoadFlowParameters setTwtSplitShuntAdmittance(boolean twtSplitShuntAdmittance) {
        this.twtSplitShuntAdmittance = twtSplitShuntAdmittance;
        return this;
    }

    /**
     * @deprecated Use {@link #isShuntCompensatorVoltageControlOn()} instead.
     */
    @Deprecated(since = "4.7.0")
    public boolean isSimulShunt() {
        return isShuntCompensatorVoltageControlOn();
    }

    public boolean isShuntCompensatorVoltageControlOn() {
        return shuntCompensatorVoltageControlOn;
    }

    /**
     * @deprecated Use {@link #setShuntCompensatorVoltageControlOn(boolean)} instead.
     */
    @Deprecated(since = "4.7.0")
    public LoadFlowParameters setSimulShunt(boolean simulShunt) {
        return setShuntCompensatorVoltageControlOn(simulShunt);
    }

    public LoadFlowParameters setShuntCompensatorVoltageControlOn(boolean shuntCompensatorVoltageControlOn) {
        this.shuntCompensatorVoltageControlOn = shuntCompensatorVoltageControlOn;
        return this;
    }

    public boolean isReadSlackBus() {
        return readSlackBus;
    }

    public LoadFlowParameters setReadSlackBus(boolean readSlackBus) {
        this.readSlackBus = readSlackBus;
        return this;
    }

    public boolean isWriteSlackBus() {
        return writeSlackBus;
    }

    public LoadFlowParameters setWriteSlackBus(boolean writeSlackBus) {
        this.writeSlackBus = writeSlackBus;
        return this;
    }

    public boolean isDc() {
        return dc;
    }

    public LoadFlowParameters setDc(boolean dc) {
        this.dc = dc;
        return this;
    }

    public boolean isDistributedSlack() {
        return distributedSlack;
    }

    public LoadFlowParameters setDistributedSlack(boolean distributedSlack) {
        this.distributedSlack = distributedSlack;
        return this;
    }

    public LoadFlowParameters setBalanceType(BalanceType balanceType) {
        this.balanceType = Objects.requireNonNull(balanceType);
        return this;
    }

    public BalanceType getBalanceType() {
        return balanceType;
    }

    public LoadFlowParameters setDcUseTransformerRatio(boolean dcUseTransformerRatio) {
        this.dcUseTransformerRatio = dcUseTransformerRatio;
        return this;
    }

    public boolean isDcUseTransformerRatio() {
        return dcUseTransformerRatio;
    }

    public LoadFlowParameters setCountriesToBalance(Set<Country> countriesToBalance) {
        this.countriesToBalance = Collections.unmodifiableSet(new HashSet<>(Objects.requireNonNull(countriesToBalance)));
        return this;
    }

    public Set<Country> getCountriesToBalance() {
        return Collections.unmodifiableSet(countriesToBalance);
    }

    public ConnectedComponentMode getConnectedComponentMode() {
        return connectedComponentMode;
    }

    public LoadFlowParameters setConnectedComponentMode(ConnectedComponentMode connectedComponentMode) {
        this.connectedComponentMode = connectedComponentMode;
        return this;
    }

    public boolean isHvdcAcEmulation() {
        return hvdcAcEmulation;
    }

    public LoadFlowParameters setHvdcAcEmulation(boolean hvdcAcEmulation) {
        this.hvdcAcEmulation = hvdcAcEmulation;
        return this;
    }

    public double getDcPowerFactor() {
        return dcPowerFactor;
    }

    public LoadFlowParameters setDcPowerFactor(double dcPowerFactor) {
        if (dcPowerFactor <= 0 || dcPowerFactor > 1) {
            throw new IllegalArgumentException("Invalid DC power factor: " + dcPowerFactor);
        }
        this.dcPowerFactor = dcPowerFactor;
        return this;
    }

    public Map<String, Object> toMap() {
        return ImmutableMap.<String, Object>builder()
                .put("voltageInitMode", voltageInitMode)
                .put("transformerVoltageControlOn", transformerVoltageControlOn)
                .put("useReactiveLimits", useReactiveLimits)
                .put("phaseShifterRegulationOn", phaseShifterRegulationOn)
                .put("twtSplitShuntAdmittance", twtSplitShuntAdmittance)
                .put("shuntCompensatorVoltageControlOn", shuntCompensatorVoltageControlOn)
                .put("readSlackBus", readSlackBus)
                .put("writeSlackBus", writeSlackBus)
                .put("dc", dc)
                .put("distributedSlack", distributedSlack)
                .put("balanceType", balanceType)
                .put("dcUseTransformerRatio", dcUseTransformerRatio)
                .put("countriesToBalance", countriesToBalance)
                .put("computedConnectedComponentScope", connectedComponentMode)
                .put("hvdcAcEmulation", hvdcAcEmulation)
                .put("dcPowerFactor", dcPowerFactor)
                .build();
    }

    /**
     * This copy methods uses json serializer mechanism to rebuild all extensions in the this parameters.
     * If an extension's serializer not found via {@code @AutoService}, the extension would be lost in copied.
     *
     * @return a new copied instance and with original's extensions found based-on json serializer.
     */
    public LoadFlowParameters copy() {
        byte[] bytes = writeInMemory();
        try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes)) {
            return JsonLoadFlowParameters.read(bais);
        } catch (IOException e) {
            throw new UncheckedIOException(e);
        }
    }

    private byte[] writeInMemory() {
        try (ByteArrayBuilder byteArrayBuilder = new ByteArrayBuilder()) {
            JsonLoadFlowParameters.write(this, byteArrayBuilder);
            return byteArrayBuilder.toByteArray();
        }
    }

    @Override
    public String toString() {
        return toMap().toString();
    }

    protected void loadExtensions(PlatformConfig platformConfig) {
        for (LoadFlowProvider provider : new ServiceLoaderCache<>(LoadFlowProvider.class).getServices()) {
            provider.getSpecificParametersClass().ifPresent(clazz -> {
                Optional<Extension<LoadFlowParameters>> extension = Optional.ofNullable(getExtension(clazz));
                extension.ifPresentOrElse(ext -> provider.updateSpecificParameters(ext, platformConfig),
                        () -> provider.loadSpecificParameters(platformConfig)
                                .ifPresent(ext -> addExtension((Class) ext.getClass(), ext)));
            });
        }
    }
}