All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.powsybl.openrao.searchtreerao.linearoptimisation.algorithms.fillers.UnoptimizedCnecFiller Maven / Gradle / Ivy

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

import com.powsybl.openrao.commons.OpenRaoException;
import com.powsybl.openrao.data.cracapi.Identifiable;
import com.powsybl.openrao.data.cracapi.State;
import com.powsybl.openrao.data.cracapi.cnec.FlowCnec;
import com.powsybl.openrao.data.cracapi.cnec.Side;
import com.powsybl.openrao.data.cracapi.rangeaction.HvdcRangeAction;
import com.powsybl.openrao.data.cracapi.rangeaction.InjectionRangeAction;
import com.powsybl.openrao.data.cracapi.rangeaction.PstRangeAction;
import com.powsybl.openrao.data.cracapi.rangeaction.RangeAction;
import com.powsybl.openrao.raoapi.parameters.RangeActionsOptimizationParameters;
import com.powsybl.openrao.searchtreerao.commons.RaoUtil;
import com.powsybl.openrao.searchtreerao.commons.optimizationperimeters.OptimizationPerimeter;
import com.powsybl.openrao.searchtreerao.commons.parameters.UnoptimizedCnecParameters;
import com.powsybl.openrao.searchtreerao.linearoptimisation.algorithms.linearproblem.OpenRaoMPConstraint;
import com.powsybl.openrao.searchtreerao.linearoptimisation.algorithms.linearproblem.OpenRaoMPVariable;
import com.powsybl.openrao.searchtreerao.linearoptimisation.algorithms.linearproblem.LinearProblem;
import com.powsybl.openrao.searchtreerao.result.api.FlowResult;
import com.powsybl.openrao.searchtreerao.result.api.RangeActionActivationResult;
import com.powsybl.openrao.searchtreerao.result.api.SensitivityResult;

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

import static com.powsybl.openrao.commons.Unit.MEGAWATT;

/**
 * This filler adds variables and constraints allowing the RAO to ignore some
 * cnecs, if they should not be optimized. This can happen in the following cases :
 * * selectedRule = MARGIN_DECREASE : some operators' CNECs' margins will not be taken into account in the objective function,
 * unless they are worse than their pre-perimeter margins.
 * * selectedRule = PST_LIMITATION : some cnecs parametrized as in series with a pst will not be taken into account in the objective
 * function, as long as a there are enough setpoints on the pst left to absorb the margin deficit
 *
 * @author Peter Mitri {@literal }
 * @author Godelaine de Montmorillon {@literal }
 */
public class UnoptimizedCnecFiller implements ProblemFiller {
    public static final String VARIABLE_NOT_CREATED = "%s variable has not yet been created for Cnec %s (side %s)";
    public static final String OPTIMIZE_CNEC_BINARY = "Optimize cnec binary";
    private final OptimizationPerimeter optimizationContext;
    private final Set flowCnecs;
    private final FlowResult prePerimeterFlowResult;
    private final Set operatorsNotToOptimize;
    private final double highestThresholdValue;
    private final Map> flowCnecRangeActionMap;
    private final RangeActionsOptimizationParameters rangeActionParameters;
    private UnoptimizedCnecFillerRule selectedRule;

    public UnoptimizedCnecFiller(OptimizationPerimeter optimizationContext,
                                 Set flowCnecs,
                                 FlowResult prePerimeterFlowResult,
                                 UnoptimizedCnecParameters unoptimizedCnecParameters,
                                 RangeActionsOptimizationParameters rangeActionParameters) {
        this.optimizationContext = optimizationContext;
        this.flowCnecs = new TreeSet<>(Comparator.comparing(Identifiable::getId));
        this.flowCnecs.addAll(FillersUtil.getFlowCnecsNotNaNFlow(flowCnecs, prePerimeterFlowResult));
        this.prePerimeterFlowResult = prePerimeterFlowResult;
        this.operatorsNotToOptimize = unoptimizedCnecParameters.getOperatorsNotToOptimize();
        this.highestThresholdValue = RaoUtil.getLargestCnecThreshold(flowCnecs, MEGAWATT);
        this.flowCnecRangeActionMap = unoptimizedCnecParameters.getDoNotOptimizeCnecsSecuredByTheirPst();
        this.rangeActionParameters = rangeActionParameters;
    }

    public enum UnoptimizedCnecFillerRule {
        MARGIN_DECREASE,
        PST_LIMITATION
    }

    private void selectUnoptimizedCnecFillerRule() {
        if (Objects.nonNull(flowCnecRangeActionMap)) {
            selectedRule = UnoptimizedCnecFillerRule.PST_LIMITATION;
        } else {
            selectedRule = UnoptimizedCnecFillerRule.MARGIN_DECREASE;
        }
    }

    @Override
    public void fill(LinearProblem linearProblem, FlowResult flowResult, SensitivityResult sensitivityResult) {
        // define UnoptimizedCnecFillerRule
        selectUnoptimizedCnecFillerRule();

        // Get list of valid flow CNECs
        Set validFlowCnecs = getValidFlowCnecs(sensitivityResult);

        // build variables
        buildDontOptimizeCnecVariables(linearProblem, validFlowCnecs);

        // build constraints
        buildDontOptimizeCnecConstraints(linearProblem, validFlowCnecs, sensitivityResult);

        // update minimum margin objective function constraints
        updateMinimumMarginConstraints(linearProblem, validFlowCnecs);
    }

    @Override
    public void updateBetweenSensiIteration(LinearProblem linearProblem, FlowResult flowResult, SensitivityResult sensitivityResult, RangeActionActivationResult rangeActionActivationResult) {
        updateDontOptimizeCnecConstraints(linearProblem, getValidFlowCnecs(sensitivityResult), sensitivityResult);
    }

    @Override
    public void updateBetweenMipIteration(LinearProblem linearProblem, RangeActionActivationResult rangeActionActivationResult) {
        // nothing to do
    }

    /**
     * This method defines a binary variable that detects the decrease of the margin on the given CNEC compared to the preperimeter margin
     * The variable should be equal to 1 if there is a decrease
     */
    private void buildDontOptimizeCnecVariables(LinearProblem linearProblem, Set validFlowCnecs) {
        validFlowCnecs.forEach(cnec -> cnec.getMonitoredSides().forEach(side ->
            linearProblem.addOptimizeCnecBinaryVariable(cnec, side)
        ));
    }

    /**
     * Gathers flow cnecs that can be unoptimized depending on the ongoing UnoptimizedCnecFillerRule.
     */
    private Set getValidFlowCnecs(SensitivityResult sensitivityResult) {
        if (selectedRule.equals(UnoptimizedCnecFillerRule.MARGIN_DECREASE)) {
            return FillersUtil.getFlowCnecsComputationStatusOk(flowCnecs, sensitivityResult).stream()
                .filter(cnec -> operatorsNotToOptimize.contains(cnec.getOperator()))
                .collect(Collectors.toSet());
        } else {
            return FillersUtil.getFlowCnecsComputationStatusOk(optimizationContext.getFlowCnecs(), sensitivityResult).stream()
                .filter(flowCnecRangeActionMap::containsKey)
                .collect(Collectors.toSet());
        }
    }

    private void buildDontOptimizeCnecConstraints(LinearProblem linearProblem, Set validFlowCnecs, SensitivityResult sensitivityResult) {
        if (selectedRule.equals(UnoptimizedCnecFillerRule.MARGIN_DECREASE)) {
            buildDontOptimizeCnecConstraintsForTsosThatDoNotShareRas(linearProblem, validFlowCnecs);
        } else {
            buildDontOptimizeCnecConstraintsForCnecsInSeriesWithPsts(linearProblem, validFlowCnecs, sensitivityResult);
        }
    }

    private void updateDontOptimizeCnecConstraints(LinearProblem linearProblem, Set validFlowCnecs, SensitivityResult sensitivityResult) {
        if (selectedRule.equals(UnoptimizedCnecFillerRule.PST_LIMITATION)) {
            updateDontOptimizeCnecConstraintsForCnecsInSeriesWithPsts(linearProblem, validFlowCnecs, sensitivityResult);
        }
    }

    /**
     * This method defines, for each CNEC belonging to a TSO that does not share RAs in the given perimeter, a constraint
     * The constraint defines the behaviour of the binary variable optimize cnec
     * margin >= margin_preperimeter - optimize_cnec * bigM
     * => (1) -flow + optimize_cnec * bigM >= margin_preperimeter - maxFlow
     * and (2) flow + optimize_cnec * bigM >= margin_preperimeter + minFlow
     * bigM is computed to be equal to the maximum margin decrease possible, which is the amount that decreases the
     * cnec's margin to the initial worst margin
     */
    private void buildDontOptimizeCnecConstraintsForTsosThatDoNotShareRas(LinearProblem linearProblem, Set validFlowCnecs) {
        double worstMarginDecrease = 20 * highestThresholdValue;
        // No margin should be smaller than the worst margin computed above, otherwise it means the linear optimizer or
        // the search tree rao is degrading the situation
        // So we can use this to estimate the worst decrease possible of the margins on cnecs
        validFlowCnecs.forEach(cnec -> cnec.getMonitoredSides().forEach(side -> {
            double prePerimeterMargin = prePerimeterFlowResult.getMargin(cnec, side, MEGAWATT);

            OpenRaoMPVariable flowVariable = linearProblem.getFlowVariable(cnec, side);
            OpenRaoMPVariable optimizeCnecBinaryVariable = linearProblem.getOptimizeCnecBinaryVariable(cnec, side);

            Optional minFlow;
            Optional maxFlow;
            minFlow = cnec.getLowerBound(side, MEGAWATT);
            maxFlow = cnec.getUpperBound(side, MEGAWATT);

            if (minFlow.isPresent()) {
                OpenRaoMPConstraint decreaseMinmumThresholdMargin = linearProblem.addDontOptimizeCnecConstraint(
                    prePerimeterMargin + minFlow.get(),
                    LinearProblem.infinity(), cnec, side,
                    LinearProblem.MarginExtension.BELOW_THRESHOLD
                );
                decreaseMinmumThresholdMargin.setCoefficient(flowVariable, 1);
                decreaseMinmumThresholdMargin.setCoefficient(optimizeCnecBinaryVariable, worstMarginDecrease);
            }

            if (maxFlow.isPresent()) {
                OpenRaoMPConstraint decreaseMinmumThresholdMargin = linearProblem.addDontOptimizeCnecConstraint(
                    prePerimeterMargin - maxFlow.get(),
                    LinearProblem.infinity(), cnec, side,
                    LinearProblem.MarginExtension.ABOVE_THRESHOLD
                );
                decreaseMinmumThresholdMargin.setCoefficient(flowVariable, -1);
                decreaseMinmumThresholdMargin.setCoefficient(optimizeCnecBinaryVariable, worstMarginDecrease);
            }
        }));
    }

    private void defineDontOptimizeCnecConstraintsForCnecsInSeriesWithPsts(LinearProblem linearProblem, Set validFlowCnecs, SensitivityResult sensitivityResult, boolean buildConstraint) {
        validFlowCnecs.forEach(cnec -> cnec.getMonitoredSides()
            .forEach(side -> defineDontOptimizeCnecConstraintsForCnecInSeriesWithPsts(linearProblem, sensitivityResult, buildConstraint, cnec, side)));
    }

    private void defineDontOptimizeCnecConstraintsForCnecInSeriesWithPsts(LinearProblem linearProblem, SensitivityResult sensitivityResult, boolean buildConstraint, FlowCnec cnec, Side side) {
        // Flow variable
        OpenRaoMPVariable flowVariable = linearProblem.getFlowVariable(cnec, side);

        // Optimize cnec binary variable
        OpenRaoMPVariable optimizeCnecBinaryVariable = linearProblem.getOptimizeCnecBinaryVariable(cnec, side);

        State state = getLastStateWithRangeActionAvailableForCnec(cnec);
        if (Objects.isNull(state)) {
            return;
        }
        OpenRaoMPVariable setPointVariable = linearProblem.getRangeActionSetpointVariable(flowCnecRangeActionMap.get(cnec), state);

        double maxSetpoint = setPointVariable.ub();
        double minSetpoint = setPointVariable.lb();
        double sensitivity = zeroIfSensitivityBelowThreshold(
            flowCnecRangeActionMap.get(cnec), sensitivityResult.getSensitivityValue(cnec, side, flowCnecRangeActionMap.get(cnec), MEGAWATT));
        Optional minFlow = cnec.getLowerBound(side, MEGAWATT);
        Optional maxFlow = cnec.getUpperBound(side, MEGAWATT);

        double bigM = 20 * highestThresholdValue;
        if (minFlow.isPresent()) {
            OpenRaoMPConstraint extendSetpointBounds;
            if (buildConstraint) {
                extendSetpointBounds = linearProblem.addDontOptimizeCnecConstraint(
                    -LinearProblem.infinity(),
                    LinearProblem.infinity(), cnec,
                    side,
                    LinearProblem.MarginExtension.BELOW_THRESHOLD);
                extendSetpointBounds.setCoefficient(flowVariable, 1);
            } else {
                extendSetpointBounds = linearProblem.getDontOptimizeCnecConstraint(cnec, side, LinearProblem.MarginExtension.BELOW_THRESHOLD);
            }
            extendSetpointBounds.setCoefficient(setPointVariable, -sensitivity);
            extendSetpointBounds.setCoefficient(optimizeCnecBinaryVariable, bigM);
            double lb = minFlow.get();
            if (sensitivity >= 0) {
                lb += -maxSetpoint * sensitivity;
            } else {
                lb += -minSetpoint * sensitivity;
            }
            extendSetpointBounds.setLb(lb);
        }
        if (maxFlow.isPresent()) {
            OpenRaoMPConstraint extendSetpointBounds;
            if (buildConstraint) {
                extendSetpointBounds = linearProblem.addDontOptimizeCnecConstraint(
                    -LinearProblem.infinity(),
                    LinearProblem.infinity(), cnec, side,
                    LinearProblem.MarginExtension.ABOVE_THRESHOLD);
                extendSetpointBounds.setCoefficient(flowVariable, -1);
            } else {
                extendSetpointBounds = linearProblem.getDontOptimizeCnecConstraint(cnec, side, LinearProblem.MarginExtension.ABOVE_THRESHOLD);
            }
            extendSetpointBounds.setCoefficient(setPointVariable, sensitivity);
            extendSetpointBounds.setCoefficient(optimizeCnecBinaryVariable, bigM);
            double lb = -maxFlow.get();
            if (sensitivity >= 0) {
                lb += minSetpoint * sensitivity;
            } else {
                lb += maxSetpoint * sensitivity;
            }
            extendSetpointBounds.setLb(lb);
        }
    }

    /**
     * This method defines, for each CNEC parameterized as in series with a pst in the given perimeter, a constraint
     * The constraint defines the behaviour of the binary variable optimize cnec
     * As long as a there are enough setpoints on the pst left to absorb the margin deficit, the cnec should not
     * participate in the definition of the minimum margin (i.e. the binary variable optimize cnec is 0)
     * When sensitivity >= 0 :
     * (1) setpoint <= maxSetpoint - -(maxFlow - flow)/sensitivity + bigM * optimize_cnec
     * (2) setpoint >= minSetpoint + -(flow - minFlow)/sensitivity - bigM * optimize_cnec
     * * When sensitivity < 0 :
     * (1) setpoint <= maxSetpoint - -(flow - minFlow)/-sensitivity + bigM * optimize_cnec
     * (2) setpoint >= minSetpoint + -(maxFlow - flow)/-sensitivity - bigM * optimize_cnec
     * bigM is computed to allow the PST to reach its bounds with optimize_cnec set to 1 :
     * bigM = maxSetpoint - minSetpoint
     */
    private void buildDontOptimizeCnecConstraintsForCnecsInSeriesWithPsts(LinearProblem linearProblem, Set validFlowCnecs, SensitivityResult sensitivityResult) {
        defineDontOptimizeCnecConstraintsForCnecsInSeriesWithPsts(linearProblem, validFlowCnecs, sensitivityResult, true);
    }

    private void updateDontOptimizeCnecConstraintsForCnecsInSeriesWithPsts(LinearProblem linearProblem, Set validFlowCnecs, SensitivityResult sensitivityResult) {
        defineDontOptimizeCnecConstraintsForCnecsInSeriesWithPsts(linearProblem, validFlowCnecs, sensitivityResult, false);
    }

    /**
     * This method finds the most recent state amongst the states in optimizationContext previous to cnec's state
     * for which the pst range action in series with cnec was available.
     **/
    private State getLastStateWithRangeActionAvailableForCnec(FlowCnec cnec) {
        List statesBeforeCnec = FillersUtil.getPreviousStates(cnec.getState(), optimizationContext).stream()
            .sorted((s1, s2) -> Integer.compare(s2.getInstant().getOrder(), s1.getInstant().getOrder())) // start with curative state
            .toList();

        Optional lastState = statesBeforeCnec.stream().filter(state ->
                optimizationContext.getRangeActionsPerState().get(state).contains(flowCnecRangeActionMap.get(cnec)))
            .findFirst();
        // Range action (referenced for "cnec" in flowCnecPstRangeActionMap) can be unavailable for cnec
        return lastState.orElse(null);
    }

    /**
     * For CNECs with binary variable optimize_cnecs set to 0, deactivate their participation in the definition of the minimum margin
     * Do this by adding (1 - optimize_cnecs) * bigM to the right side of the inequality
     * bigM is computed as 2 times the largest absolute threshold between all CNECs
     * Of course this can be restrictive as CNECs can have hypothetically infinite margins if they are monitored in one direction only
     * But we'll suppose for now that the minimum margin can never be greater than 1 * the largest threshold
     */
    private void updateMinimumMarginConstraints(LinearProblem linearProblem, Set validFlowCnecs) {
        double bigM = 2 * highestThresholdValue;
        validFlowCnecs.forEach(cnec -> cnec.getMonitoredSides().forEach(side -> {
            OpenRaoMPVariable optimizeCnecBinaryVariable = linearProblem.getOptimizeCnecBinaryVariable(cnec, side);
            try {
                updateMinimumMarginConstraint(
                    linearProblem.getMinimumMarginConstraint(cnec, side, LinearProblem.MarginExtension.BELOW_THRESHOLD),
                    optimizeCnecBinaryVariable,
                    bigM
                );
            } catch (OpenRaoException ignored) {
            }
            try {
                updateMinimumMarginConstraint(
                    linearProblem.getMinimumMarginConstraint(cnec, side, LinearProblem.MarginExtension.ABOVE_THRESHOLD),
                    optimizeCnecBinaryVariable,
                    bigM
                );
            } catch (OpenRaoException ignored) {
            }
            try {
                updateMinimumMarginConstraint(
                    linearProblem.getMinimumRelativeMarginConstraint(cnec, side, LinearProblem.MarginExtension.BELOW_THRESHOLD),
                    optimizeCnecBinaryVariable,
                    bigM
                );
            } catch (OpenRaoException ignored) {
            }
            try {
                updateMinimumMarginConstraint(
                    linearProblem.getMinimumRelativeMarginConstraint(cnec, side, LinearProblem.MarginExtension.ABOVE_THRESHOLD),
                    optimizeCnecBinaryVariable,
                    bigM
                );
            } catch (OpenRaoException ignored) {
            }
        }));
    }

    /**
     * Add a big coefficient to the minimum margin definition constraint, allowing it to be relaxed if the
     * binary variable is equal to 1
     */
    private void updateMinimumMarginConstraint(OpenRaoMPConstraint constraint, OpenRaoMPVariable optimizeCnecBinaryVariable, double bigM) {
        constraint.setCoefficient(optimizeCnecBinaryVariable, bigM);
        constraint.setUb(constraint.ub() + bigM);
    }

    /**
     * Replace small sensitivity values with zero to avoid numerical issues
     */
    private double zeroIfSensitivityBelowThreshold(RangeAction rangeAction, double sensitivity) {
        double threshold;
        if (rangeAction instanceof PstRangeAction) {
            threshold = rangeActionParameters.getPstSensitivityThreshold();
        } else if (rangeAction instanceof HvdcRangeAction) {
            threshold = rangeActionParameters.getHvdcSensitivityThreshold();
        } else if (rangeAction instanceof InjectionRangeAction) {
            threshold = rangeActionParameters.getInjectionRaSensitivityThreshold();
        } else {
            throw new OpenRaoException("Type of RangeAction not yet handled by the LinearRao.");
        }
        return Math.abs(sensitivity) >= threshold ? sensitivity : 0;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy