AbstractOptimizationPerimeter.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.commons.optimizationperimeters;

import com.powsybl.openrao.data.crac.api.Identifiable;
import com.powsybl.openrao.data.crac.api.State;
import com.powsybl.openrao.data.crac.api.cnec.Cnec;
import com.powsybl.openrao.data.crac.api.cnec.FlowCnec;
import com.powsybl.openrao.data.crac.api.networkaction.NetworkAction;
import com.powsybl.openrao.data.crac.api.rangeaction.RangeAction;
import com.powsybl.openrao.data.crac.loopflowextension.LoopFlowThreshold;
import com.powsybl.openrao.raoapi.parameters.RaoParameters;
import com.powsybl.openrao.searchtreerao.result.api.RangeActionSetpointResult;
import com.powsybl.iidm.network.Network;

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

import static com.powsybl.openrao.commons.logs.OpenRaoLoggerProvider.BUSINESS_WARNS;

/**
 * @author Baptiste Seguinot {@literal <baptiste.seguinot at rte-france.com>}
 */
public abstract class AbstractOptimizationPerimeter implements OptimizationPerimeter {

    private final State mainOptimizationState;
    private final Set<State> monitoredStates;
    private final Set<FlowCnec> flowCnecs;
    private final Set<FlowCnec> optimizedFlowCnecs;
    private final Set<FlowCnec> monitoredFlowCnecs;
    private final Set<FlowCnec> loopFlowCnecs;
    private final Set<NetworkAction> availableNetworkActions;
    private final Map<State, Set<RangeAction<?>>> availableRangeActions;
    private static final double EPSILON = 1e-6;

    protected AbstractOptimizationPerimeter(State mainOptimizationState,
                                         Set<FlowCnec> flowCnecs,
                                         Set<FlowCnec> loopFlowCnecs,
                                         Set<NetworkAction> availableNetworkActions,
                                         Map<State, Set<RangeAction<?>>> availableRangeActions) {
        this.mainOptimizationState = mainOptimizationState;

        this.monitoredStates = flowCnecs.stream().map(FlowCnec::getState).collect(Collectors.toSet());

        this.flowCnecs = new TreeSet<>(Comparator.comparing(Identifiable::getId));
        this.flowCnecs.addAll(flowCnecs);

        this.optimizedFlowCnecs = new TreeSet<>(Comparator.comparing(Identifiable::getId));
        this.optimizedFlowCnecs.addAll(flowCnecs.stream().filter(Cnec::isOptimized).collect(Collectors.toSet()));

        this.monitoredFlowCnecs = new TreeSet<>(Comparator.comparing(Identifiable::getId));
        this.monitoredFlowCnecs.addAll(flowCnecs.stream().filter(Cnec::isMonitored).collect(Collectors.toSet()));

        this.loopFlowCnecs = new TreeSet<>(Comparator.comparing(Identifiable::getId));
        this.loopFlowCnecs.addAll(loopFlowCnecs);

        this.availableNetworkActions = new TreeSet<>(Comparator.comparing(Identifiable::getId));
        this.availableNetworkActions.addAll(availableNetworkActions);

        this.availableRangeActions = new TreeMap<>(Comparator.comparing(State::getId));
        availableRangeActions.forEach((state, rangeActions) -> {
            Set<RangeAction<?>> rangeActionSet = new TreeSet<>(Comparator.comparing(Identifiable::getId));
            rangeActionSet.addAll(availableRangeActions.get(state));
            this.availableRangeActions.put(state, rangeActionSet);
        });
    }

    @Override
    public State getMainOptimizationState() {
        return mainOptimizationState;
    }

    @Override
    public Set<State> getRangeActionOptimizationStates() {
        return availableRangeActions.keySet();
    }

    @Override
    public Set<State> getMonitoredStates() {
        return monitoredStates;
    }

    @Override
    public Set<FlowCnec> getFlowCnecs() {
        return flowCnecs;
    }

    @Override
    public Set<FlowCnec> getOptimizedFlowCnecs() {
        return optimizedFlowCnecs;
    }

    @Override
    public Set<FlowCnec> getMonitoredFlowCnecs() {
        return monitoredFlowCnecs;
    }

    @Override
    public Set<FlowCnec> getLoopFlowCnecs() {
        return loopFlowCnecs;
    }

    @Override
    public Set<NetworkAction> getNetworkActions() {
        return availableNetworkActions;
    }

    @Override
    public Map<State, Set<RangeAction<?>>> getRangeActionsPerState() {
        return availableRangeActions;
    }

    @Override
    public Set<RangeAction<?>> getRangeActions() {
        return availableRangeActions.values().stream().flatMap(Collection::stream).collect(Collectors.toSet());
    }

    public static Set<FlowCnec> getLoopFlowCnecs(Set<FlowCnec> flowCnecs, RaoParameters raoParameters, Network network) {
        Optional<com.powsybl.openrao.raoapi.parameters.LoopFlowParameters> loopFlowParametersOptional = raoParameters.getLoopFlowParameters();
        if (loopFlowParametersOptional.isPresent()) {
            if (!loopFlowParametersOptional.get().getCountries().isEmpty()) {
                // loopFlow limited, and set of country for which loop-flow are monitored is defined
                return flowCnecs.stream()
                    .filter(cnec -> !Objects.isNull(cnec.getExtension(LoopFlowThreshold.class)) &&
                        cnec.getLocation(network).stream().anyMatch(country -> country.isPresent() && loopFlowParametersOptional.get().getCountries().contains(country.get())))
                    .collect(Collectors.toSet());
            } else {
                // loopFlow limited, but no set of country defined
                return flowCnecs.stream()
                    .filter(cnec -> !Objects.isNull(cnec.getExtension(LoopFlowThreshold.class)))
                    .collect(Collectors.toSet());
            }
        } else {
            //no loopFLow limitation
            return Collections.emptySet();
        }
    }

    static boolean doesPrePerimeterSetpointRespectRange(RangeAction<?> rangeAction, RangeActionSetpointResult prePerimeterSetpoints) {
        double preperimeterSetPoint = prePerimeterSetpoints.getSetpoint(rangeAction);
        double minSetPoint = rangeAction.getMinAdmissibleSetpoint(preperimeterSetPoint);
        double maxSetPoint = rangeAction.getMaxAdmissibleSetpoint(preperimeterSetPoint);

        if (preperimeterSetPoint < minSetPoint - EPSILON || preperimeterSetPoint > maxSetPoint + EPSILON) {
            BUSINESS_WARNS.warn("Range action {} has an initial setpoint of {} that does not respect its allowed range [{} {}]. It will be filtered out of the linear problem.",
                rangeAction.getId(), preperimeterSetPoint, minSetPoint, maxSetPoint);
            return false;
        } else {
            return true;
        }
    }

    /**
     * If aligned range actions' initial setpoint are different, this function filters them out
     */
    static void removeAlignedRangeActionsWithDifferentInitialSetpoints(Set<RangeAction<?>> rangeActions, RangeActionSetpointResult prePerimeterSetPoints) {
        Set<String> groups = rangeActions.stream().map(RangeAction::getGroupId)
            .filter(Optional::isPresent).map(Optional::get).collect(Collectors.toSet());
        for (String group : groups) {
            Set<RangeAction<?>> groupRangeActions = rangeActions.stream().filter(rangeAction -> rangeAction.getGroupId().isPresent() && rangeAction.getGroupId().get().equals(group)).collect(Collectors.toSet());
            double preperimeterSetPoint = prePerimeterSetPoints.getSetpoint(groupRangeActions.iterator().next());
            if (groupRangeActions.stream().anyMatch(rangeAction -> Math.abs(prePerimeterSetPoints.getSetpoint(rangeAction) - preperimeterSetPoint) > EPSILON)) {
                BUSINESS_WARNS.warn("Range actions of group {} do not have the same prePerimeter setpoint. They will be filtered out of the linear problem.", group);
                rangeActions.removeAll(groupRangeActions);
            }
        }
    }
}