ProblemFillerHelper.java

/*
 * Copyright (c) 2025, 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.linearoptimisation.algorithms;

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.extensions.SearchTreeRaoRangeActionsOptimizationParameters;
import com.powsybl.openrao.searchtreerao.commons.optimizationperimeters.CurativeOptimizationPerimeter;
import com.powsybl.openrao.searchtreerao.linearoptimisation.algorithms.fillers.ContinuousRangeActionGroupFiller;
import com.powsybl.openrao.searchtreerao.linearoptimisation.algorithms.fillers.CostCoreProblemFiller;
import com.powsybl.openrao.searchtreerao.linearoptimisation.algorithms.fillers.DiscretePstGroupFiller;
import com.powsybl.openrao.searchtreerao.linearoptimisation.algorithms.fillers.DiscretePstTapFiller;
import com.powsybl.openrao.searchtreerao.linearoptimisation.algorithms.fillers.MarginCoreProblemFiller;
import com.powsybl.openrao.searchtreerao.linearoptimisation.algorithms.fillers.MaxLoopFlowFiller;
import com.powsybl.openrao.searchtreerao.linearoptimisation.algorithms.fillers.MaxMinMarginFiller;
import com.powsybl.openrao.searchtreerao.linearoptimisation.algorithms.fillers.MaxMinRelativeMarginFiller;
import com.powsybl.openrao.searchtreerao.linearoptimisation.algorithms.fillers.MnecFiller;
import com.powsybl.openrao.searchtreerao.linearoptimisation.algorithms.fillers.ProblemFiller;
import com.powsybl.openrao.searchtreerao.linearoptimisation.algorithms.fillers.RaUsageLimitsFiller;
import com.powsybl.openrao.searchtreerao.linearoptimisation.algorithms.fillers.UnoptimizedCnecFiller;
import com.powsybl.openrao.searchtreerao.linearoptimisation.inputs.IteratingLinearOptimizerInput;
import com.powsybl.openrao.searchtreerao.linearoptimisation.parameters.IteratingLinearOptimizerParameters;

import java.time.OffsetDateTime;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.stream.Collectors;

import static com.powsybl.openrao.raoapi.parameters.extensions.SearchTreeRaoRangeActionsOptimizationParameters.getPstModel;

/**
 * @author Thomas Bouquet {@literal <thomas.bouquet at rte-france.com>}
 */
public final class ProblemFillerHelper {
    private ProblemFillerHelper() {
    }

    public static List<ProblemFiller> getProblemFillers(IteratingLinearOptimizerInput input, IteratingLinearOptimizerParameters parameters, OffsetDateTime timestamp) {
        List<ProblemFiller> problemFillers = new ArrayList<>();

        // Core problem filler
        if (parameters.getObjectiveFunction().costOptimization()) {
            // TODO : mutualize arguments using only SearchTreeRaoRangeActionsOptimizationParameters extension
            CostCoreProblemFiller costCoreProblemFiller = new CostCoreProblemFiller(
                input.optimizationPerimeter(),
                input.prePerimeterSetpoints(),
                parameters.getRangeActionParameters(),
                parameters.getRangeActionParametersExtension(),
                parameters.getObjectiveFunctionUnit(),
                parameters.getRaRangeShrinking(),
                getPstModel(parameters.getRangeActionParametersExtension()),
                timestamp
            );
            problemFillers.add(costCoreProblemFiller);
        } else {
            MarginCoreProblemFiller marginCoreProblemFiller = new MarginCoreProblemFiller(
                input.optimizationPerimeter(),
                input.prePerimeterSetpoints(),
                parameters.getRangeActionParameters(),
                parameters.getRangeActionParametersExtension(),
                parameters.getObjectiveFunctionUnit(),
                parameters.getRaRangeShrinking(),
                getPstModel(parameters.getRangeActionParametersExtension()),
                timestamp
            );
            problemFillers.add(marginCoreProblemFiller);
        }

        // max.min margin, or max.min relative margin
        if (parameters.getObjectiveFunction().relativePositiveMargins()) {
            MaxMinRelativeMarginFiller maxMinRelativeMarginFiller = new MaxMinRelativeMarginFiller(
                input.optimizationPerimeter().getOptimizedFlowCnecs(),
                input.preOptimizationFlowResult(),
                parameters.getObjectiveFunctionUnit(),
                parameters.getMinMarginParameters(),
                parameters.getMaxMinRelativeMarginParameters(),
                timestamp
            );
            problemFillers.add(maxMinRelativeMarginFiller);
        } else {
            MaxMinMarginFiller maxMinMarginFiller = new MaxMinMarginFiller(
                input.optimizationPerimeter().getOptimizedFlowCnecs(),
                parameters.getObjectiveFunctionUnit(),
                parameters.getObjectiveFunction().costOptimization(),
                parameters.getMinMarginParameters(),
                timestamp
            );
            problemFillers.add(maxMinMarginFiller);
        }

        // MNEC
        if (parameters.isRaoWithMnecLimitation()) {
            MnecFiller mnecFiller = new MnecFiller(
                input.initialFlowResult(),
                input.optimizationPerimeter().getMonitoredFlowCnecs(),
                parameters.getObjectiveFunctionUnit(),
                parameters.getMnecParametersExtension().getViolationCost(),
                parameters.getMnecParameters().getAcceptableMarginDecrease(),
                parameters.getMnecParametersExtension().getConstraintAdjustmentCoefficient(),
                timestamp
            );
            problemFillers.add(mnecFiller);
        }

        // loop-flow limitation
        if (parameters.isRaoWithLoopFlowLimitation()) {
            MaxLoopFlowFiller maxLoopFlowFiller = new MaxLoopFlowFiller(
                input.optimizationPerimeter().getLoopFlowCnecs(),
                input.initialFlowResult(),
                parameters.getLoopFlowParameters(),
                parameters.getLoopFlowParametersExtension(),
                timestamp
            );
            problemFillers.add(maxLoopFlowFiller);
        }

        // unoptimized CNECs for TSOs without curative RA
        if (!Objects.isNull(parameters.getUnoptimizedCnecParameters())
            && !Objects.isNull(parameters.getUnoptimizedCnecParameters().getOperatorsNotToOptimize())
            && input.optimizationPerimeter() instanceof CurativeOptimizationPerimeter) {
            UnoptimizedCnecFiller unoptimizedCnecFiller = new UnoptimizedCnecFiller(
                input.optimizationPerimeter().getFlowCnecs(),
                input.prePerimeterFlowResult(),
                parameters.getUnoptimizedCnecParameters(),
                timestamp
            );
            problemFillers.add(unoptimizedCnecFiller);
        }

        // MIP optimization vs. CONTINUOUS optimization
        SearchTreeRaoRangeActionsOptimizationParameters.PstModel pstModel = getPstModel(parameters.getRangeActionParametersExtension());
        if (SearchTreeRaoRangeActionsOptimizationParameters.PstModel.APPROXIMATED_INTEGERS.equals(pstModel)) {
            Map<State, Set<PstRangeAction>> pstRangeActions = copyOnlyPstRangeActions(input.optimizationPerimeter().getRangeActionsPerState());
            Map<State, Set<RangeAction<?>>> otherRa = copyWithoutPstRangeActions(input.optimizationPerimeter().getRangeActionsPerState());
            DiscretePstTapFiller discretePstTapFiller = new DiscretePstTapFiller(
                input.optimizationPerimeter(),
                pstRangeActions,
                input.prePerimeterSetpoints(),
                parameters.getRangeActionParameters(),
                parameters.getObjectiveFunction().costOptimization(),
                timestamp
            );
            problemFillers.add(discretePstTapFiller);
            DiscretePstGroupFiller discretePstGroupFiller = new DiscretePstGroupFiller(
                input.optimizationPerimeter().getMainOptimizationState(),
                pstRangeActions,
                timestamp
            );
            problemFillers.add(discretePstGroupFiller);
            ContinuousRangeActionGroupFiller continuousRangeActionGroupFiller = new ContinuousRangeActionGroupFiller(otherRa, timestamp);
            problemFillers.add(continuousRangeActionGroupFiller);
        } else if (SearchTreeRaoRangeActionsOptimizationParameters.PstModel.CONTINUOUS.equals(pstModel)) {
            ContinuousRangeActionGroupFiller continuousRangeActionGroupFiller = new ContinuousRangeActionGroupFiller(input.optimizationPerimeter().getRangeActionsPerState(), timestamp);
            problemFillers.add(continuousRangeActionGroupFiller);
        }

        // RA limitation
        if (parameters.getRaLimitationParameters() != null
            && input.optimizationPerimeter().getRangeActionOptimizationStates().stream()
            .anyMatch(state -> parameters.getRaLimitationParameters().areRangeActionLimitedForState(state))) {
            RaUsageLimitsFiller raUsageLimitsFiller = new RaUsageLimitsFiller(
                input.optimizationPerimeter().getRangeActionsPerState(),
                input.prePerimeterSetpoints(),
                parameters.getRaLimitationParameters(),
                getPstModel(parameters.getRangeActionParametersExtension()) == SearchTreeRaoRangeActionsOptimizationParameters.PstModel.APPROXIMATED_INTEGERS,
                input.network(),
                parameters.getObjectiveFunction().costOptimization(),
                timestamp
            );
            problemFillers.add(raUsageLimitsFiller);
        }
        return problemFillers;
    }

    private static Map<State, Set<RangeAction<?>>> copyWithoutPstRangeActions(Map<State, Set<RangeAction<?>>> inRangeActions) {
        Map<State, Set<RangeAction<?>>> outRangeActions = new HashMap<>();
        inRangeActions.forEach((state, rangeActions) -> {
            if (rangeActions.stream().anyMatch(ra -> !(ra instanceof PstRangeAction))) {
                outRangeActions.put(state, rangeActions.stream().filter(ra -> !(ra instanceof PstRangeAction)).collect(Collectors.toCollection(
                    () -> new TreeSet<>(Comparator.comparing(RangeAction::getId))
                )));
            }
        });
        return outRangeActions;
    }

    private static Map<State, Set<PstRangeAction>> copyOnlyPstRangeActions(Map<State, Set<RangeAction<?>>> inRangeActions) {
        Map<State, Set<PstRangeAction>> outRangeActions = new TreeMap<>(Comparator.comparing(State::getId));
        inRangeActions.forEach((state, rangeActions) -> {
            if (rangeActions.stream().anyMatch(PstRangeAction.class::isInstance)) {
                outRangeActions.put(state, rangeActions.stream().filter(PstRangeAction.class::isInstance).map(PstRangeAction.class::cast).collect(Collectors.toCollection(
                    () -> new TreeSet<>(Comparator.comparing(PstRangeAction::getId))
                )));
            }
        });
        return outRangeActions;
    }
}