FlowDecompositionComputer.java

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

import com.powsybl.commons.PowsyblException;
import com.powsybl.flow_decomposition.glsk_provider.AutoGlskProvider;
import com.powsybl.flow_decomposition.partitioners.DirectSensitivityPartitioner;
import com.powsybl.flow_decomposition.partitioners.MatrixBasedPartitioner;
import com.powsybl.flow_decomposition.rescaler.*;
import com.powsybl.iidm.network.*;
import com.powsybl.loadflow.LoadFlow;
import com.powsybl.loadflow.LoadFlowParameters;
import com.powsybl.sensitivity.SensitivityAnalysis;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;

/**
 * @author Sebastien Murgey {@literal <sebastien.murgey at rte-france.com>}
 * @author Hugo Schindler {@literal <hugo.schindler at rte-france.com>}
 * @author Caio Luke {@literal <caio.luke at artelys.com>}
 */
public class FlowDecompositionComputer {

    private static final Logger LOGGER = LoggerFactory.getLogger(FlowDecompositionComputer.class);
    static final String DEFAULT_LOAD_FLOW_PROVIDER = "OpenLoadFlow";
    static final String DEFAULT_SENSITIVITY_ANALYSIS_PROVIDER = "OpenLoadFlow";
    public static final LoadFlowParameters.ConnectedComponentMode MAIN_CONNECTED_COMPONENT = LoadFlowParameters.ConnectedComponentMode.MAIN;
    private final LoadFlowParameters loadFlowParameters;
    private final FlowDecompositionParameters parameters;
    private final LoadFlowRunningService loadFlowRunningService;
    private final SensitivityAnalysis.Runner sensitivityAnalysisRunner;
    private final LossesCompensator lossesCompensator;
    private final DecomposedFlowRescaler decomposedFlowRescaler;
    private final FlowDecompositionObserverList observers;

    public FlowDecompositionComputer() {
        this(new FlowDecompositionParameters());
    }

    public FlowDecompositionComputer(FlowDecompositionParameters flowDecompositionParameters,
                                     LoadFlowParameters loadFlowParameters,
                                     String loadFlowProvider, String sensitivityAnalysisProvider) {
        this.parameters = flowDecompositionParameters;
        this.loadFlowParameters = loadFlowParameters.copy();
        if (!MAIN_CONNECTED_COMPONENT.equals(this.loadFlowParameters.getConnectedComponentMode())) {
            LOGGER.warn("Flow decomposition is currently available only on the main synchronous component. Changing connected component mode from {} to MAIN.",
                    this.loadFlowParameters.getConnectedComponentMode());
            this.loadFlowParameters.setConnectedComponentMode(MAIN_CONNECTED_COMPONENT);
        }
        this.loadFlowRunningService = new LoadFlowRunningService(LoadFlow.find(loadFlowProvider));
        this.sensitivityAnalysisRunner = SensitivityAnalysis.find(sensitivityAnalysisProvider);
        this.lossesCompensator = parameters.isLossesCompensationEnabled() ? new LossesCompensator(parameters) : null;
        this.decomposedFlowRescaler = getDecomposedFlowRescaler();
        this.observers = new FlowDecompositionObserverList();
    }

    public FlowDecompositionComputer(FlowDecompositionParameters flowDecompositionParameters,
                                     LoadFlowParameters loadFlowParameters) {
        this(flowDecompositionParameters, loadFlowParameters,
            DEFAULT_LOAD_FLOW_PROVIDER, DEFAULT_SENSITIVITY_ANALYSIS_PROVIDER);
    }

    public FlowDecompositionComputer(FlowDecompositionParameters flowDecompositionParameters) {
        this(flowDecompositionParameters, LoadFlowParameters.load());
    }

    public FlowDecompositionResults run(XnecProvider xnecProvider, Network network) {
        return run(xnecProvider, new AutoGlskProvider(), network);
    }

    public FlowDecompositionResults run(XnecProvider xnecProvider, GlskProvider glskProvider, Network network) {
        observers.runStart();
        try {
            NetworkStateManager networkStateManager = new NetworkStateManager(network, xnecProvider);

            LoadFlowRunningService.Result loadFlowServiceAcResult = runAcLoadFlow(network);

            Map<Country, Map<String, Double>> glsks = glskProvider.getGlsk(network);
            observers.computedGlsk(glsks);

            Map<Country, Double> netPositions = getZonesNetPosition(network);
            observers.computedNetPositions(netPositions);

            FlowDecompositionResults flowDecompositionResults = new FlowDecompositionResults(network);
            decomposeFlowForNState(network,
                    flowDecompositionResults,
                    xnecProvider.getNetworkElements(network),
                    netPositions,
                    glsks,
                    loadFlowServiceAcResult);
            xnecProvider.getNetworkElementsPerContingency(network)
                    .forEach((contingencyId, xnecs) -> decomposeFlowForContingencyState(network,
                            flowDecompositionResults,
                            networkStateManager,
                            contingencyId,
                            xnecs,
                            netPositions,
                            glsks));
            networkStateManager.deleteAllContingencyVariants();
            return flowDecompositionResults;
        } finally {
            observers.runDone();
        }
    }

    private void decomposeFlowForNState(Network network,
                                        FlowDecompositionResults flowDecompositionResults,
                                        Set<Branch> xnecs,
                                        Map<Country, Double> netPositions,
                                        Map<Country, Map<String, Double>> glsks,
                                        LoadFlowRunningService.Result loadFlowServiceAcResult) {
        if (!xnecs.isEmpty()) {
            observers.computingBaseCase();
            FlowDecompositionResults.PerStateBuilder flowDecompositionResultsBuilder = flowDecompositionResults.getBuilder(xnecs);
            decomposeFlowForState(network, xnecs, flowDecompositionResultsBuilder, netPositions, glsks, loadFlowServiceAcResult);
        }
    }

    private void decomposeFlowForContingencyState(Network network,
                                                  FlowDecompositionResults flowDecompositionResults,
                                                  NetworkStateManager networkStateManager,
                                                  String contingencyId,
                                                  Set<Branch> xnecList,
                                                  Map<Country, Double> netPositions,
                                                  Map<Country, Map<String, Double>> glsks) {
        if (!xnecList.isEmpty()) {
            observers.computingContingency(contingencyId);
            networkStateManager.setNetworkVariant(contingencyId);
            LoadFlowRunningService.Result loadFlowServiceAcResult = runAcLoadFlow(network);
            FlowDecompositionResults.PerStateBuilder flowDecompositionResultsBuilder = flowDecompositionResults.getBuilder(contingencyId, xnecList);
            decomposeFlowForState(network, xnecList, flowDecompositionResultsBuilder, netPositions, glsks, loadFlowServiceAcResult);
        }
    }

    private void decomposeFlowForState(Network network,
                                       Set<Branch> xnecs,
                                       FlowDecompositionResults.PerStateBuilder flowDecompositionResultsBuilder,
                                       Map<Country, Double> netPositions,
                                       Map<Country, Map<String, Double>> glsks,
                                       LoadFlowRunningService.Result loadFlowServiceAcResult) {
        // AC load flow
        saveAcLoadFlowResults(flowDecompositionResultsBuilder, network, xnecs, loadFlowServiceAcResult);

        // Losses compensation
        compensateLosses(network);

        // DC load flow
        LoadFlowRunningService.Result loadFlowServiceDcResult = runDcLoadFlow(network);
        saveDcLoadFlowResults(flowDecompositionResultsBuilder, network, xnecs, loadFlowServiceDcResult);

        Map<String, FlowPartition> flowPartitions = getFlowPartitioner().computeFlowPartitions(network, xnecs, netPositions, glsks);
        flowDecompositionResultsBuilder.saveFlowPartitions(flowPartitions);

        // Add the observers to keep the decomposed flows before rescaling
        flowDecompositionResultsBuilder.addObserversList(observers);
        flowDecompositionResultsBuilder.build(decomposedFlowRescaler, network);
    }

    private FlowPartitioner getFlowPartitioner() {
        return switch (parameters.getFlowPartitioner()) {
            case MATRIX_BASED ->
                    new MatrixBasedPartitioner(loadFlowParameters, parameters, sensitivityAnalysisRunner, observers);
            case DIRECT_SENSITIVITY_BASED ->
                    new DirectSensitivityPartitioner(loadFlowParameters, sensitivityAnalysisRunner, observers);
            default ->
                    throw new PowsyblException("FlowPartitioner not defined for mode: " + parameters.getFlowPartitioner());
        };
    }

    public void addObserver(FlowDecompositionObserver observer) {
        this.observers.addObserver(observer);
    }

    public void removeObserver(FlowDecompositionObserver observer) {
        this.observers.removeObserver(observer);
    }

    private LoadFlowRunningService.Result runAcLoadFlow(Network network) {
        return loadFlowRunningService.runAcLoadflow(network, loadFlowParameters, parameters.isDcFallbackEnabledAfterAcDivergence());
    }

    private void saveAcLoadFlowResults(FlowDecompositionResults.PerStateBuilder flowDecompositionResultsBuilder, Network network, Set<Branch> xnecList, LoadFlowRunningService.Result loadFlowServiceAcResult) {
        saveAcReferenceFlows(flowDecompositionResultsBuilder, xnecList, loadFlowServiceAcResult.fallbackHasBeenActivated());
        saveAcCurrents(flowDecompositionResultsBuilder, xnecList, loadFlowServiceAcResult.fallbackHasBeenActivated());
        observers.computedAcLoadFlowResults(network, loadFlowServiceAcResult);
    }

    private void saveAcReferenceFlows(FlowDecompositionResults.PerStateBuilder flowDecompositionResultsBuilder, Set<Branch> xnecList, boolean fallbackHasBeenActivated) {
        Map<String, Double> acTerminal1ReferenceFlows = FlowComputerUtils.calculateAcTerminalReferenceFlows(xnecList, fallbackHasBeenActivated, TwoSides.ONE);
        Map<String, Double> acTerminal2ReferenceFlows = FlowComputerUtils.calculateAcTerminalReferenceFlows(xnecList, fallbackHasBeenActivated, TwoSides.TWO);
        flowDecompositionResultsBuilder.saveAcTerminal1ReferenceFlow(acTerminal1ReferenceFlows);
        flowDecompositionResultsBuilder.saveAcTerminal2ReferenceFlow(acTerminal2ReferenceFlows);
    }

    private void saveAcCurrents(FlowDecompositionResults.PerStateBuilder flowDecompositionResultBuilder, Set<Branch> xnecList, boolean fallbackHasBeenActivated) {
        Map<String, Double> acTerminal1Currents = FlowComputerUtils.calculateAcTerminalCurrents(xnecList, fallbackHasBeenActivated, TwoSides.ONE);
        Map<String, Double> acTerminal2Currents = FlowComputerUtils.calculateAcTerminalCurrents(xnecList, fallbackHasBeenActivated, TwoSides.TWO);
        flowDecompositionResultBuilder.saveAcCurrentTerminal1(acTerminal1Currents);
        flowDecompositionResultBuilder.saveAcCurrentTerminal2(acTerminal2Currents);
    }

    private Map<Country, Double> getZonesNetPosition(Network network) {
        NetPositionComputer netPositionComputer = new NetPositionComputer();
        return netPositionComputer.run(network);
    }

    private void compensateLosses(Network network) {
        if (parameters.isLossesCompensationEnabled()) {
            lossesCompensator.run(network);
        }
    }

    private DecomposedFlowRescaler getDecomposedFlowRescaler() {
        return switch (parameters.getRescaleMode()) {
            case NONE -> new DecomposedFlowRescalerNoOp();
            case ACER_METHODOLOGY -> new DecomposedFlowRescalerAcerMethodology();
            case PROPORTIONAL -> new DecomposedFlowRescalerProportional(parameters.getProportionalRescalerMinFlowTolerance());
            case MAX_CURRENT_OVERLOAD -> new DecomposedFlowRescalerMaxCurrentOverload(parameters.getProportionalRescalerMinFlowTolerance());
            default -> throw new PowsyblException("DecomposedFlowRescaler not defined for mode: " + parameters.getRescaleMode());
        };
    }

    private LoadFlowRunningService.Result runDcLoadFlow(Network network) {
        return loadFlowRunningService.runDcLoadflow(network, loadFlowParameters);
    }

    private void saveDcLoadFlowResults(FlowDecompositionResults.PerStateBuilder flowDecompositionResultBuilder, Network network, Set<Branch> xnecList, LoadFlowRunningService.Result loadFlowServiceDcResult) {
        flowDecompositionResultBuilder.saveDcReferenceFlow(FlowComputerUtils.getTerminalReferenceFlow(xnecList, TwoSides.ONE));
        observers.computedDcLoadFlowResults(network, loadFlowServiceDcResult);
    }

    protected LoadFlowParameters getLoadFlowParameters() {
        return this.loadFlowParameters;
    }
}