LoadflowProvider.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.openrao.sensitivityanalysis;

import com.powsybl.openrao.commons.OpenRaoException;
import com.powsybl.openrao.commons.Unit;
import com.powsybl.openrao.data.crac.api.cnec.FlowCnec;
import com.powsybl.iidm.network.TwoSides;
import com.powsybl.openrao.data.crac.api.rangeaction.HvdcRangeAction;
import com.powsybl.contingency.Contingency;
import com.powsybl.contingency.ContingencyContext;
import com.powsybl.contingency.ContingencyContextType;
import com.powsybl.iidm.network.*;
import com.powsybl.sensitivity.SensitivityFactor;
import com.powsybl.sensitivity.SensitivityFunctionType;
import com.powsybl.sensitivity.SensitivityVariableSet;
import com.powsybl.sensitivity.SensitivityVariableType;
import org.apache.commons.lang3.tuple.Pair;

import java.util.*;
import java.util.stream.Collectors;

/**
 * To run a systematic sensitivity analysis and evaluate the flows in all states at once,
 * hades requires sensitivities. We therefore extend RangeActionSensitivityProvider to use
 * some of its conversion methods, and use a random PST from the network to create "dummy"
 * sensitivities for each studied cnec.
 *
 * @author Philippe Edwards {@literal <philippe.edwards at rte-france.com>}
 */
public class LoadflowProvider extends AbstractSimpleSensitivityProvider {

    Pair<String, SensitivityVariableType> defaultSensitivityVariable;

    LoadflowProvider(Set<FlowCnec> cnecs, Set<Unit> units) {
        super(cnecs, units);
    }

    @Override
    public List<SensitivityFactor> getBasecaseFactors(Network network) {
        List<SensitivityFactor> factors = new ArrayList<>();
        if (afterContingencyOnly) {
            return factors;
        }
        Map<String, SensitivityVariableType> sensitivityVariables = new LinkedHashMap<>();
        addDefaultSensitivityVariable(network, sensitivityVariables);

        List<Pair<String, SensitivityFunctionType>> sensitivityFunctions = getSensitivityFunctions(network, null);

        //According to ContingencyContext doc, contingencyId should be null for preContingency context
        ContingencyContext preContingencyContext = new ContingencyContext(null, ContingencyContextType.NONE);
        sensitivityFunctions.forEach(function ->
            sensitivityVariables.forEach((key, value) -> factors.add(new SensitivityFactor(function.getValue(), function.getKey(), value, key, false, preContingencyContext)))
        );
        return factors;
    }

    @Override
    public List<SensitivityFactor> getContingencyFactors(Network network, List<Contingency> contingencies) {
        List<SensitivityFactor> factors = new ArrayList<>();
        for (Contingency contingency : contingencies) {
            String contingencyId = contingency.getId();
            Map<String, SensitivityVariableType> sensitivityVariables = new LinkedHashMap<>();
            addDefaultSensitivityVariable(network, sensitivityVariables);

            List<Pair<String, SensitivityFunctionType>> sensitivityFunctions = getSensitivityFunctions(network, contingencyId);

            ContingencyContext contingencyContext = new ContingencyContext(contingencyId, ContingencyContextType.SPECIFIC);
            sensitivityFunctions.forEach(function ->
                sensitivityVariables.forEach((key, value) -> factors.add(new SensitivityFactor(function.getValue(), function.getKey(), value, key, false, contingencyContext)))
            );
        }
        return factors;
    }

    @Override
    public List<SensitivityVariableSet> getVariableSets() {
        return new ArrayList<>();
    }

    private boolean willBeKeptInSensi(TwoWindingsTransformer twoWindingsTransformer) {
        return twoWindingsTransformer.getTerminal1().isConnected() && twoWindingsTransformer.getTerminal1().getBusBreakerView().getBus().isInMainSynchronousComponent() &&
            twoWindingsTransformer.getTerminal2().isConnected() && twoWindingsTransformer.getTerminal2().getBusBreakerView().getBus().isInMainSynchronousComponent() &&
            twoWindingsTransformer.getPhaseTapChanger() != null;
    }

    @Override
    public Map<String, HvdcRangeAction> getHvdcs() {
        return new HashMap<>();
    }

    private boolean willBeKeptInSensi(Generator gen) {
        return gen.getTerminal().isConnected() && gen.getTerminal().getBusBreakerView().getBus().isInMainSynchronousComponent();
    }

    void addDefaultSensitivityVariable(Network network, Map<String, SensitivityVariableType> sensitivityVariables) {
        if (!Objects.isNull(defaultSensitivityVariable)) {
            sensitivityVariables.put(defaultSensitivityVariable.getLeft(), defaultSensitivityVariable.getRight());
            return;
        }

        // First try to get a PST angle
        Optional<TwoWindingsTransformer> optionalPst = network.getTwoWindingsTransformerStream()
            .filter(this::willBeKeptInSensi)
            .findAny();

        if (optionalPst.isPresent()) {
            sensitivityVariables.put(optionalPst.get().getId(), SensitivityVariableType.TRANSFORMER_PHASE);
            defaultSensitivityVariable = Pair.of(optionalPst.get().getId(), SensitivityVariableType.TRANSFORMER_PHASE);
            return;
        }

        // If no one found, pick a Generator injection
        Optional<Generator> optionalGen = network.getGeneratorStream()
            .filter(this::willBeKeptInSensi)
            .findAny();

        if (optionalGen.isPresent()) {
            sensitivityVariables.put(optionalGen.get().getId(), SensitivityVariableType.INJECTION_ACTIVE_POWER);
            defaultSensitivityVariable = Pair.of(optionalGen.get().getId(), SensitivityVariableType.INJECTION_ACTIVE_POWER);
            return;
        }
        throw new OpenRaoException(String.format("Unable to create sensitivity factors. Did not find any varying element in network '%s'.", network.getId()));
    }

    List<Pair<String, SensitivityFunctionType>> getSensitivityFunctions(Network network, String contingencyId) {
        Set<FlowCnec> flowCnecs;
        if (Objects.isNull(contingencyId)) {
            flowCnecs = cnecsPerContingencyId.getOrDefault(null, new ArrayList<>()).stream()
                .filter(cnec -> cnec.isConnected(network))
                .collect(Collectors.toSet());
        } else {
            flowCnecs = cnecsPerContingencyId.getOrDefault(contingencyId, new ArrayList<>()).stream()
                .filter(cnec -> cnec.isConnected(network))
                .collect(Collectors.toSet());
        }

        Map<String, Set<TwoSides>> networkElementsAndSides = new HashMap<>();
        flowCnecs.forEach(flowCnec ->
            networkElementsAndSides.computeIfAbsent(flowCnec.getNetworkElement().getId(), k -> new HashSet<>()).addAll(flowCnec.getMonitoredSides())
        );

        List<Pair<String, SensitivityFunctionType>> sensitivityFunctions = new ArrayList<>();
        networkElementsAndSides.forEach((neId, sides) -> sensitivityFunctions.addAll(cnecToSensitivityFunctions(network, neId, sides)));
        return sensitivityFunctions;
    }

    private List<Pair<String, SensitivityFunctionType>> cnecToSensitivityFunctions(Network network, String networkElementId, Set<TwoSides> sides) {
        Identifiable<?> networkIdentifiable = network.getIdentifiable(networkElementId);
        if (networkIdentifiable instanceof Branch || networkIdentifiable instanceof DanglingLine) {
            return getSensitivityFunctionTypes(sides).stream().map(functionType -> Pair.of(networkElementId, functionType)).toList();
        } else {
            throw new OpenRaoException("Unable to create sensitivity function for " + networkElementId);
        }
    }

    private Set<SensitivityFunctionType> getSensitivityFunctionTypes(Set<TwoSides> sides) {
        Set<SensitivityFunctionType> sensitivityFunctionTypes = new HashSet<>();
        if (factorsInMegawatt && sides.contains(TwoSides.ONE)) {
            sensitivityFunctionTypes.add(SensitivityFunctionType.BRANCH_ACTIVE_POWER_1);
        }
        if (factorsInMegawatt && sides.contains(TwoSides.TWO)) {
            sensitivityFunctionTypes.add(SensitivityFunctionType.BRANCH_ACTIVE_POWER_2);
        }
        if (factorsInAmpere && sides.contains(TwoSides.ONE)) {
            sensitivityFunctionTypes.add(SensitivityFunctionType.BRANCH_CURRENT_1);
        }
        if (factorsInAmpere && sides.contains(TwoSides.TWO)) {
            sensitivityFunctionTypes.add(SensitivityFunctionType.BRANCH_CURRENT_2);
        }
        return sensitivityFunctionTypes;
    }

}