SearchTreeParameters.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.openrao.searchtreerao.searchtree.parameters;

import com.powsybl.openrao.commons.Unit;
import com.powsybl.openrao.data.crac.api.Crac;
import com.powsybl.openrao.data.crac.api.Instant;
import com.powsybl.openrao.data.crac.api.RaUsageLimits;
import com.powsybl.openrao.data.crac.api.State;
import com.powsybl.openrao.data.crac.api.rangeaction.PstRangeAction;
import com.powsybl.openrao.data.crac.api.rangeaction.RangeAction;
import com.powsybl.openrao.raoapi.parameters.ObjectiveFunctionParameters;
import com.powsybl.openrao.raoapi.parameters.RangeActionsOptimizationParameters;
import com.powsybl.openrao.raoapi.parameters.extensions.*;
import com.powsybl.openrao.raoapi.parameters.extensions.SearchTreeRaoRangeActionsOptimizationParameters.LinearOptimizationSolver;
import com.powsybl.openrao.raoapi.parameters.RaoParameters;
import com.powsybl.openrao.raoapi.parameters.LoopFlowParameters;
import com.powsybl.openrao.raoapi.parameters.MnecParameters;
import com.powsybl.openrao.raoapi.parameters.extensions.SearchTreeRaoRelativeMarginsParameters;
import com.powsybl.openrao.searchtreerao.commons.parameters.*;
import com.powsybl.openrao.searchtreerao.result.api.OptimizationResult;
import com.powsybl.openrao.searchtreerao.result.api.PrePerimeterResult;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import static com.powsybl.openrao.raoapi.parameters.extensions.SearchTreeRaoRangeActionsOptimizationParameters.getLinearOptimizationSolver;
import static com.powsybl.openrao.raoapi.parameters.extensions.SearchTreeRaoRangeActionsOptimizationParameters.getMaxMipIterations;

/**
 * @author Baptiste Seguinot {@literal <joris.mancini at rte-france.com>}
 */
public class SearchTreeParameters {

    private final ObjectiveFunctionParameters.ObjectiveFunctionType objectiveFunction;
    private final Unit objectiveFunctionUnit;

    // required for the search tree algorithm
    private final TreeParameters treeParameters;
    private final NetworkActionParameters networkActionParameters;
    private final Map<Instant, RaUsageLimits> raLimitationParameters;

    // required for sub-module iterating linear optimizer
    private final RangeActionsOptimizationParameters rangeActionParameters;
    private final SearchTreeRaoRangeActionsOptimizationParameters rangeActionParametersExtension;

    private final MnecParameters mnecParameters;
    private final SearchTreeRaoMnecParameters mnecParametersExtension;

    private final SearchTreeRaoRelativeMarginsParameters maxMinRelativeMarginParameters;
    private final LoopFlowParameters loopFlowParameters;
    private final SearchTreeRaoLoopFlowParameters loopFlowParametersExtension;

    private final UnoptimizedCnecParameters unoptimizedCnecParameters;
    private final LinearOptimizationSolver solverParameters;
    private final SearchTreeRaoCostlyMinMarginParameters maxMinMarginsParameters;
    private final int maxNumberOfIterations;

    public SearchTreeParameters(ObjectiveFunctionParameters.ObjectiveFunctionType objectiveFunction,
                                Unit objectiveFunctionUnit, TreeParameters treeParameters,
                                NetworkActionParameters networkActionParameters,
                                Map<Instant, RaUsageLimits> raLimitationParameters,
                                RangeActionsOptimizationParameters rangeActionParameters,
                                SearchTreeRaoRangeActionsOptimizationParameters rangeActionParametersExtension,
                                MnecParameters mnecParameters,
                                SearchTreeRaoMnecParameters mnecParametersExtension,
                                SearchTreeRaoRelativeMarginsParameters maxMinRelativeMarginParameters,
                                LoopFlowParameters loopFlowParameters,
                                SearchTreeRaoLoopFlowParameters loopFlowParametersExtension,
                                UnoptimizedCnecParameters unoptimizedCnecParameters,
                                LinearOptimizationSolver solverParameters,
                                SearchTreeRaoCostlyMinMarginParameters maxMinMarginParameters,
                                int maxNumberOfIterations) {
        this.objectiveFunction = objectiveFunction;
        this.objectiveFunctionUnit = objectiveFunctionUnit;
        this.treeParameters = treeParameters;
        this.networkActionParameters = networkActionParameters;
        this.raLimitationParameters = raLimitationParameters;
        this.rangeActionParameters = rangeActionParameters;
        this.rangeActionParametersExtension = rangeActionParametersExtension;
        this.mnecParameters = mnecParameters;
        this.mnecParametersExtension = mnecParametersExtension;
        this.maxMinRelativeMarginParameters = maxMinRelativeMarginParameters;
        this.loopFlowParameters = loopFlowParameters;
        this.loopFlowParametersExtension = loopFlowParametersExtension;
        this.unoptimizedCnecParameters = unoptimizedCnecParameters;
        this.solverParameters = solverParameters;
        this.maxMinMarginsParameters = maxMinMarginParameters;
        this.maxNumberOfIterations = maxNumberOfIterations;
    }

    public ObjectiveFunctionParameters.ObjectiveFunctionType getObjectiveFunction() {
        return objectiveFunction;
    }

    public Unit getObjectiveFunctionUnit() {
        return objectiveFunctionUnit;
    }

    public TreeParameters getTreeParameters() {
        return treeParameters;
    }

    public NetworkActionParameters getNetworkActionParameters() {
        return networkActionParameters;
    }

    public Map<Instant, RaUsageLimits> getRaLimitationParameters() {
        return raLimitationParameters;
    }

    public RangeActionsOptimizationParameters getRangeActionParameters() {
        return rangeActionParameters;
    }

    public SearchTreeRaoRangeActionsOptimizationParameters getRangeActionParametersExtension() {
        return rangeActionParametersExtension;
    }

    public MnecParameters getMnecParameters() {
        return mnecParameters;
    }

    public SearchTreeRaoMnecParameters getMnecParametersExtension() {
        return mnecParametersExtension;
    }

    public SearchTreeRaoRelativeMarginsParameters getMaxMinRelativeMarginParameters() {
        return maxMinRelativeMarginParameters;
    }

    public SearchTreeRaoCostlyMinMarginParameters getMaxMinMarginsParameters() {
        return maxMinMarginsParameters;
    }

    public LoopFlowParameters getLoopFlowParameters() {
        return loopFlowParameters;
    }

    public SearchTreeRaoLoopFlowParameters getLoopFlowParametersExtension() {
        return loopFlowParametersExtension;
    }

    public UnoptimizedCnecParameters getUnoptimizedCnecParameters() {
        return unoptimizedCnecParameters;
    }

    public LinearOptimizationSolver getSolverParameters() {
        return solverParameters;
    }

    public int getMaxNumberOfIterations() {
        return maxNumberOfIterations;
    }

    public void setRaLimitationsForSecondPreventive(RaUsageLimits raUsageLimits, Set<RangeAction<?>> rangeActionSet, Instant preventiveInstant) {
        if (rangeActionSet.isEmpty()) {
            return;
        }
        Set<String> tsoCount = new HashSet<>();
        int raCount = 0;
        Map<String, Integer> currentPstPerTsoLimits = raUsageLimits.getMaxPstPerTso();
        Map<String, Integer> currentRaPerTsoLimits = raUsageLimits.getMaxRaPerTso();
        Map<String, Integer> currentTopoPerTsoLimits = raUsageLimits.getMaxTopoPerTso();
        for (var rangeAction : rangeActionSet) {
            String tso = rangeAction.getOperator();
            tsoCount.add(tso);
            raCount += 1;
            currentRaPerTsoLimits.computeIfPresent(tso, (key, currentLimit) -> Math.max(0, currentLimit - 1));
            currentPstPerTsoLimits.computeIfPresent(tso, (key, currentLimit) -> Math.max(0, currentLimit - 1));
        }
        raUsageLimits.setMaxRa(Math.max(0, raUsageLimits.getMaxRa() - raCount));
        raUsageLimits.setMaxTso(Math.max(0, raUsageLimits.getMaxTso() - tsoCount.size()));
        currentTopoPerTsoLimits.forEach((tso, raLimits) -> currentTopoPerTsoLimits.put(tso, Math.min(raLimits, currentRaPerTsoLimits.getOrDefault(tso, Integer.MAX_VALUE))));
        currentPstPerTsoLimits.forEach((tso, raLimits) -> currentPstPerTsoLimits.put(tso, Math.min(raLimits, currentRaPerTsoLimits.getOrDefault(tso, Integer.MAX_VALUE))));
        raUsageLimits.setMaxPstPerTso(currentPstPerTsoLimits);
        raUsageLimits.setMaxTopoPerTso(currentTopoPerTsoLimits);
        raUsageLimits.setMaxRaPerTso(currentRaPerTsoLimits);
        this.raLimitationParameters.put(preventiveInstant, raUsageLimits);
    }

    public void decreaseRemedialActionUsageLimits(Map<State, OptimizationResult> resultsPerOptimizationState, Map<State, PrePerimeterResult> prePerimeterResultPerPerimeter) {
        resultsPerOptimizationState.forEach((optimizedState, result) ->
            raLimitationParameters.keySet().forEach(
                otherInstant -> {
                    // Cumulative behaviour of constraints only applies to instants of the same kind
                    if (!otherInstant.comesBefore(optimizedState.getInstant()) && optimizedState.getInstant().getKind().equals(otherInstant.getKind())) {
                        RaUsageLimits raUsageLimits = raLimitationParameters.get(otherInstant);
                        int decreasedMaxRa = decreaseMaxRemedialAction(raUsageLimits, optimizedState, result);
                        Map<String, Integer> decreasedMaxRaPerTso = decreaseMaxRemedialActionPerTso(raUsageLimits, optimizedState, result);
                        Map<String, Integer> decreasedMaxTopoPerTso = decreaseMaxTopoPerTso(raUsageLimits, result, decreasedMaxRaPerTso);
                        Map<String, Integer> decreasedMaxPstPerTso = decreaseMaxPstPerTso(raUsageLimits, optimizedState, result, decreasedMaxRaPerTso);
                        int decreasedMaxTso = decreaseMaxTso(raUsageLimits, optimizedState, result);
                        Map<String, Integer> decreasedMaxElementaryActionsPerTso = decreaseMaxElementaryActionsPerTso(raUsageLimits, optimizedState, result, prePerimeterResultPerPerimeter.get(optimizedState));

                        RaUsageLimits decreasedRaUsageLimits = new RaUsageLimits();
                        decreasedRaUsageLimits.setMaxRa(decreasedMaxRa);
                        decreasedRaUsageLimits.setMaxRaPerTso(decreasedMaxRaPerTso);
                        decreasedRaUsageLimits.setMaxTopoPerTso(decreasedMaxTopoPerTso);
                        decreasedRaUsageLimits.setMaxPstPerTso(decreasedMaxPstPerTso);
                        raUsageLimits.getMaxTsoExclusion().forEach(decreasedRaUsageLimits::addTsoToExclude);
                        getTsoWithActivatedRemedialActionsDuringState(optimizedState, result).forEach(decreasedRaUsageLimits::addTsoToExclude);
                        decreasedRaUsageLimits.setMaxTso(decreasedMaxTso);
                        decreasedRaUsageLimits.setMaxElementaryActionsPerTso(decreasedMaxElementaryActionsPerTso);

                        raLimitationParameters.put(otherInstant, decreasedRaUsageLimits);
                    }
                }
            )
        );
    }

    private static int decreaseMaxRemedialAction(RaUsageLimits raUsageLimits, State optimizedState, OptimizationResult result) {
        return raUsageLimits.getMaxRa() - result.getActivatedNetworkActions().size() - result.getActivatedRangeActions(optimizedState).size();
    }

    private static Map<String, Integer> decreaseMaxTopoPerTso(RaUsageLimits raUsageLimits, OptimizationResult result, Map<String, Integer> decreasedMaxRaPerTso) {
        Map<String, Integer> decreasedMaxTopoPerTso = new HashMap<>();
        raUsageLimits.getMaxTopoPerTso().forEach((key, value) -> decreasedMaxTopoPerTso.put(key, Math.min(value - (int) result.getActivatedNetworkActions().stream().filter(networkAction -> key.equals(networkAction.getOperator())).count(), decreasedMaxRaPerTso.get(key))));
        return decreasedMaxTopoPerTso;
    }

    private static Map<String, Integer> decreaseMaxPstPerTso(RaUsageLimits raUsageLimits, State optimizedState, OptimizationResult result, Map<String, Integer> decreasedMaxRaPerTso) {
        Map<String, Integer> decreasedMaxPstPerTso = new HashMap<>();
        raUsageLimits.getMaxPstPerTso().forEach((key, value) -> decreasedMaxPstPerTso.put(key, Math.min(value - (int) result.getActivatedRangeActions(optimizedState).stream().filter(rangeAction -> key.equals(rangeAction.getOperator())).count(), decreasedMaxRaPerTso.get(key))));
        return decreasedMaxPstPerTso;
    }

    private static Set<String> getTsoWithActivatedRemedialActionsDuringState(State optimizedState, OptimizationResult result) {
        Set<String> tsos = new HashSet<>();
        result.getActivatedNetworkActions().forEach(networkAction -> tsos.add(networkAction.getOperator()));
        result.getActivatedRangeActions(optimizedState).forEach(rangeAction -> tsos.add(rangeAction.getOperator()));
        return tsos;
    }

    private static int decreaseMaxTso(RaUsageLimits raUsageLimits, State optimizedState, OptimizationResult result) {
        Set<String> newTsos = new HashSet<>(getTsoWithActivatedRemedialActionsDuringState(optimizedState, result));
        raUsageLimits.getMaxTsoExclusion().forEach(newTsos::remove);
        return raUsageLimits.getMaxTso() - newTsos.size();
    }

    private static Map<String, Integer> decreaseMaxRemedialActionPerTso(RaUsageLimits raUsageLimits, State optimizedState, OptimizationResult result) {
        Map<String, Integer> decreasedMaxRaPerTso = new HashMap<>();
        raUsageLimits.getMaxRaPerTso().forEach((key, value) -> decreasedMaxRaPerTso.put(key, value - (int) result.getActivatedNetworkActions().stream().filter(networkAction -> key.equals(networkAction.getOperator())).count() - (int) result.getActivatedRangeActions(optimizedState).stream().filter(networkAction -> key.equals(networkAction.getOperator())).count()));
        return decreasedMaxRaPerTso;
    }

    private static Map<String, Integer> decreaseMaxElementaryActionsPerTso(RaUsageLimits raUsageLimits, State optimizedState, OptimizationResult result, PrePerimeterResult prePerimeterResult) {
        Map<String, Integer> decreasedMaxElementaryActionsPerTso = new HashMap<>();
        raUsageLimits.getMaxElementaryActionsPerTso().forEach((tso, eaLimit) -> decreasedMaxElementaryActionsPerTso.put(tso, Math.max(0, eaLimit - computeActivatedElementaryActionsForTso(tso, optimizedState, result, prePerimeterResult))));
        return decreasedMaxElementaryActionsPerTso;
    }

    private static int computeActivatedElementaryActionsForTso(String tso, State optimizedState, OptimizationResult result, PrePerimeterResult prePerimeterResult) {
        return computeActivatedElementaryNetworkActionsForTso(tso, result) + computeTotalTapsMovedForTso(tso, optimizedState, result, prePerimeterResult);
    }

    private static int computeActivatedElementaryNetworkActionsForTso(String tso, OptimizationResult result) {
        return result.getActivatedNetworkActions().stream().filter(networkAction -> tso.equals(networkAction.getOperator())).mapToInt(networkAction -> networkAction.getElementaryActions().size()).sum();
    }

    private static int computeTotalTapsMovedForTso(String tso, State optimizedState, OptimizationResult result, PrePerimeterResult prePerimeterResult) {
        return result.getActivatedRangeActions(optimizedState).stream().filter(rangeAction -> tso.equals(rangeAction.getOperator())).filter(PstRangeAction.class::isInstance).map(PstRangeAction.class::cast).mapToInt(pstRangeAction -> computeTapsMoved(pstRangeAction, optimizedState, result, prePerimeterResult)).sum();
    }

    private static int computeTapsMoved(PstRangeAction pstRangeAction, State optimizedState, OptimizationResult result, PrePerimeterResult prePerimeterResult) {
        return Math.abs(result.getOptimizedTap(pstRangeAction, optimizedState) - prePerimeterResult.getTap(pstRangeAction));
    }

    public static SearchTreeParametersBuilder create() {
        return new SearchTreeParametersBuilder();
    }

    public static class SearchTreeParametersBuilder {
        private ObjectiveFunctionParameters.ObjectiveFunctionType objectiveFunction;
        private Unit objectiveFunctionUnit;
        private TreeParameters treeParameters;
        private NetworkActionParameters networkActionParameters;
        private Map<Instant, RaUsageLimits> raLimitationParameters;
        private RangeActionsOptimizationParameters rangeActionParameters;
        private SearchTreeRaoRangeActionsOptimizationParameters rangeActionParametersExtension;
        private MnecParameters mnecParameters;
        private SearchTreeRaoMnecParameters mnecParametersExtension;

        private SearchTreeRaoRelativeMarginsParameters maxMinRelativeMarginParameters;
        private LoopFlowParameters loopFlowParameters;
        private SearchTreeRaoLoopFlowParameters loopFlowParametersExtension;
        private UnoptimizedCnecParameters unoptimizedCnecParameters;
        private LinearOptimizationSolver solverParameters;
        private SearchTreeRaoCostlyMinMarginParameters maxMinMarginsParameters;
        private int maxNumberOfIterations;

        public SearchTreeParametersBuilder withConstantParametersOverAllRao(RaoParameters raoParameters, Crac crac) {
            this.objectiveFunction = raoParameters.getObjectiveFunctionParameters().getType();
            this.objectiveFunctionUnit = raoParameters.getObjectiveFunctionParameters().getUnit();
            this.networkActionParameters = NetworkActionParameters.buildFromRaoParameters(raoParameters, crac);
            this.raLimitationParameters = new HashMap<>(crac.getRaUsageLimitsPerInstant());
            this.rangeActionParameters = raoParameters.getRangeActionsOptimizationParameters();
            if (raoParameters.hasExtension(OpenRaoSearchTreeParameters.class)) {
                this.rangeActionParametersExtension = raoParameters.getExtension(OpenRaoSearchTreeParameters.class).getRangeActionsOptimizationParameters();
            }
            this.mnecParameters = raoParameters.getMnecParameters().orElse(null);
            if (raoParameters.hasExtension(OpenRaoSearchTreeParameters.class)) {
                this.mnecParametersExtension = raoParameters.getExtension(OpenRaoSearchTreeParameters.class).getMnecParameters().orElse(null);
            }
            if (raoParameters.hasExtension(OpenRaoSearchTreeParameters.class)) {
                this.maxMinRelativeMarginParameters = raoParameters.getExtension(OpenRaoSearchTreeParameters.class).getRelativeMarginsParameters().orElse(null);
            }
            if (raoParameters.hasExtension(OpenRaoSearchTreeParameters.class)) {
                this.maxMinMarginsParameters = raoParameters.getExtension(OpenRaoSearchTreeParameters.class).getMinMarginsParameters().orElse(null);
            }
            this.loopFlowParameters = raoParameters.getLoopFlowParameters().orElse(null);
            if (raoParameters.hasExtension(OpenRaoSearchTreeParameters.class)) {
                this.loopFlowParametersExtension = raoParameters.getExtension(OpenRaoSearchTreeParameters.class).getLoopFlowParameters().orElse(null);
            }
            this.solverParameters = getLinearOptimizationSolver(raoParameters);
            this.maxNumberOfIterations = getMaxMipIterations(raoParameters);
            return this;
        }

        public SearchTreeParametersBuilder with0bjectiveFunction(ObjectiveFunctionParameters.ObjectiveFunctionType objectiveFunction) {
            this.objectiveFunction = objectiveFunction;
            return this;
        }

        public SearchTreeParametersBuilder with0bjectiveFunctionUnit(Unit objectiveFunctionUnit) {
            this.objectiveFunctionUnit = objectiveFunctionUnit;
            return this;
        }

        public SearchTreeParametersBuilder withTreeParameters(TreeParameters treeParameters) {
            this.treeParameters = treeParameters;
            return this;
        }

        public SearchTreeParametersBuilder withNetworkActionParameters(NetworkActionParameters networkActionParameters) {
            this.networkActionParameters = networkActionParameters;
            return this;
        }

        public SearchTreeParametersBuilder withGlobalRemedialActionLimitationParameters(Map<Instant, RaUsageLimits> raLimitationParameters) {
            this.raLimitationParameters = new HashMap<>(raLimitationParameters);
            return this;
        }

        public SearchTreeParametersBuilder withRangeActionParameters(RangeActionsOptimizationParameters rangeActionParameters) {
            this.rangeActionParameters = rangeActionParameters;
            return this;
        }

        public SearchTreeParametersBuilder withRangeActionParametersExtension(SearchTreeRaoRangeActionsOptimizationParameters rangeActionParametersExtension) {
            this.rangeActionParametersExtension = rangeActionParametersExtension;
            return this;
        }

        public SearchTreeParametersBuilder withMnecParameters(MnecParameters mnecParameters) {
            this.mnecParameters = mnecParameters;
            return this;
        }

        public SearchTreeParametersBuilder withMaxMinRelativeMarginParameters(SearchTreeRaoRelativeMarginsParameters maxMinRelativeMarginParameters) {
            this.maxMinRelativeMarginParameters = maxMinRelativeMarginParameters;
            return this;
        }

        public SearchTreeParametersBuilder withLoopFlowParameters(LoopFlowParameters loopFlowParameters) {
            this.loopFlowParameters = loopFlowParameters;
            return this;
        }

        public SearchTreeParametersBuilder withUnoptimizedCnecParameters(UnoptimizedCnecParameters unoptimizedCnecParameters) {
            this.unoptimizedCnecParameters = unoptimizedCnecParameters;
            return this;
        }

        public SearchTreeParametersBuilder withSolverParameters(LinearOptimizationSolver solverParameters) {
            this.solverParameters = solverParameters;
            return this;
        }

        public SearchTreeParametersBuilder withMaxNumberOfIterations(int maxNumberOfIterations) {
            this.maxNumberOfIterations = maxNumberOfIterations;
            return this;
        }

        public SearchTreeParametersBuilder withMaxMinMarginsParameters(SearchTreeRaoCostlyMinMarginParameters maxMinMarginsParameters) {
            this.maxMinMarginsParameters = maxMinMarginsParameters;
            return this;
        }

        public SearchTreeParameters build() {
            return new SearchTreeParameters(
                objectiveFunction,
                objectiveFunctionUnit,
                treeParameters,
                networkActionParameters,
                raLimitationParameters,
                rangeActionParameters,
                rangeActionParametersExtension,
                mnecParameters,
                mnecParametersExtension,
                maxMinRelativeMarginParameters,
                loopFlowParameters,
                loopFlowParametersExtension,
                unoptimizedCnecParameters,
                solverParameters,
                maxMinMarginsParameters,
                maxNumberOfIterations);
        }
    }
}