CracValidator.java
/*
* Copyright (c) 2023, 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.data.crac.util;
import com.powsybl.openrao.data.crac.api.Crac;
import com.powsybl.openrao.data.crac.api.Instant;
import com.powsybl.openrao.data.crac.api.InstantKind;
import com.powsybl.openrao.data.crac.api.RemedialAction;
import com.powsybl.openrao.data.crac.api.State;
import com.powsybl.openrao.data.crac.api.usagerule.OnConstraint;
import com.powsybl.openrao.data.crac.api.usagerule.OnContingencyState;
import com.powsybl.openrao.data.crac.api.usagerule.OnFlowConstraintInCountry;
import com.powsybl.openrao.data.crac.api.usagerule.OnInstant;
import com.powsybl.openrao.data.crac.api.usagerule.UsageMethod;
import com.powsybl.openrao.data.crac.api.usagerule.UsageRule;
import com.powsybl.openrao.data.crac.api.cnec.FlowCnec;
import com.powsybl.openrao.data.crac.api.cnec.FlowCnecAdder;
import com.powsybl.iidm.network.TwoSides;
import com.powsybl.openrao.data.crac.api.threshold.BranchThresholdAdder;
import com.powsybl.iidm.network.Network;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
/**
* Misc features that clean up a CRAC to prepare it for the RAO
*
* @author Peter Mitri {@literal <peter.mitri at rte-france.com>}
*/
public final class CracValidator {
private CracValidator() {
// should not be used
}
public static List<String> validateCrac(Crac crac, Network network) {
return new ArrayList<>(addOutageCnecsForAutoCnecsWithoutRas(crac, network));
}
/**
* Since auto CNECs that have no RA associated cannot be secured by the RAO, this function duplicates these CNECs
* but on the OUTAGE instant.
* Beware that the CRAC is modified since extra CNECs are added.
*/
private static List<String> addOutageCnecsForAutoCnecsWithoutRas(Crac crac, Network network) {
List<String> report = new ArrayList<>();
if (!crac.getInstants(InstantKind.AUTO).isEmpty()) {
crac.getStates(crac.getInstant(InstantKind.AUTO))
.forEach(state -> duplicateCnecsWithNoUsefulRaOnOutageInstant(crac, network, state, report));
}
return report;
}
private static void duplicateCnecsWithNoUsefulRaOnOutageInstant(Crac crac, Network network, State state, List<String> report) {
if (hasNoRemedialAction(state, crac) || hasGlobalRemedialActions(state, crac)) {
// 1. Auto state has no RA => it will not constitute a perimeter
// => Auto CNECs will be optimized in preventive RAO, no need to duplicate them
// 2. If state has "global" RA (useful for all CNECs), nothing to do neither
return;
}
// Find CNECs with no useful RA and duplicate them on outage instant
Set<RemedialAction<?>> remedialActions = new HashSet<>();
remedialActions.addAll(crac.getPotentiallyAvailableRangeActions(state));
remedialActions.addAll(crac.getPotentiallyAvailableNetworkActions(state));
crac.getFlowCnecs(state).stream()
.filter(cnec -> shouldDuplicateAutoCnecInOutageState(remedialActions, cnec, network))
.forEach(cnec -> {
duplicateCnecOnOutageInstant(crac, cnec);
report.add(String.format("CNEC \"%s\" has no associated automaton. It will be cloned on the OUTAGE instant in order to be secured during preventive RAO.", cnec.getId()));
});
}
private static void duplicateCnecOnOutageInstant(Crac crac, FlowCnec cnec) {
Instant outageInstant = crac.getOutageInstant();
FlowCnecAdder adder = crac.newFlowCnec()
.withId(cnec.getId() + " - OUTAGE DUPLICATE")
.withNetworkElement(cnec.getNetworkElement().getId())
.withIMax(cnec.getIMax(TwoSides.ONE), TwoSides.ONE)
.withIMax(cnec.getIMax(TwoSides.TWO), TwoSides.TWO)
.withNominalVoltage(cnec.getNominalVoltage(TwoSides.ONE), TwoSides.ONE)
.withNominalVoltage(cnec.getNominalVoltage(TwoSides.TWO), TwoSides.TWO)
.withReliabilityMargin(cnec.getReliabilityMargin())
.withInstant(outageInstant.getId()).withContingency(cnec.getState().getContingency().orElseThrow().getId())
.withOptimized(cnec.isOptimized())
.withMonitored(cnec.isMonitored());
copyThresholds(cnec, adder);
adder.add();
}
private static boolean hasNoRemedialAction(State state, Crac crac) {
return crac.getPotentiallyAvailableRangeActions(state).isEmpty()
&& crac.getPotentiallyAvailableNetworkActions(state).isEmpty();
}
private static boolean hasGlobalRemedialActions(State state, Crac crac) {
return hasOnInstantOrOnStateUsageRules(crac.getRangeActions(state, UsageMethod.FORCED)) ||
hasOnInstantOrOnStateUsageRules(crac.getNetworkActions(state, UsageMethod.FORCED));
}
private static <T extends RemedialAction<?>> boolean hasOnInstantOrOnStateUsageRules(Set<T> remedialActionSet) {
return remedialActionSet.stream().anyMatch(rangeAction -> rangeAction.getUsageRules().stream().anyMatch(usageRule -> usageRule instanceof OnInstant || usageRule instanceof OnContingencyState));
}
private static void copyThresholds(FlowCnec cnec, FlowCnecAdder adder) {
cnec.getThresholds().forEach(tr -> {
BranchThresholdAdder trAdder = adder.newThreshold()
.withSide(tr.getSide())
.withUnit(tr.getUnit());
if (tr.limitsByMax()) {
trAdder.withMax(tr.max().orElseThrow());
}
if (tr.limitsByMin()) {
trAdder.withMin(tr.min().orElseThrow());
}
trAdder.add();
}
);
}
/**
* Indicates whether an auto FlowCNEC should be duplicated in the outage state or not.
* A FlowCNEC must be duplicated if no auto remedial action can act on it, leaving only the preventive remedial
* actions to possibly reduce the flow which means that the CNEC should be added to the preventive perimeter.
* <p/>
* This CNEC must however be kept in the auto instant because an overload on this line may be the triggering
* condition of auto remedial actions that can affect other FlowCNECs of the same state.
* <p/>
* If no auto remedial action affects the CNEC and the CNEC does not trigger any auto remedial action, there is no
* need to duplicate it because this means that no auto remedial action is available for this auto state at all.
* In this case, the StateTree algorithm will automatically include all the CNECs from the state to the preventive perimeter.
* @param remedialActions The set of remedial actions that may affect the CNEC
* @param flowCnec The FlowCNEC to possibly duplicate
* @param network The network
* @return Boolean value that indicates whether the CNEC should be duplicate in the outage state or not
*/
private static boolean shouldDuplicateAutoCnecInOutageState(Set<RemedialAction<?>> remedialActions, FlowCnec flowCnec, Network network) {
boolean raForOtherCnecs = false;
for (RemedialAction<?> remedialAction : remedialActions) {
for (UsageRule usageRule : remedialAction.getUsageRules()) {
if (usageRule instanceof OnInstant onInstant && onInstant.getInstant().equals(flowCnec.getState().getInstant())) {
return false;
} else if (usageRule instanceof OnContingencyState onContingencyState && onContingencyState.getState().equals(flowCnec.getState())) {
return false;
} else if (usageRule instanceof OnConstraint<?> onConstraint && onConstraint.getCnec() instanceof FlowCnec && onConstraint.getCnec().getState().equals(flowCnec.getState())) {
if (onConstraint.getCnec().equals(flowCnec)) {
return false;
} else {
raForOtherCnecs = true;
}
} else if (usageRule instanceof OnFlowConstraintInCountry onFlowConstraintInCountry
&& onFlowConstraintInCountry.getInstant().equals(flowCnec.getState().getInstant()) // TODO: why not comesBefore?
&& (onFlowConstraintInCountry.getContingency().isEmpty() || flowCnec.getState().getContingency().equals(onFlowConstraintInCountry.getContingency()))) {
if (flowCnec.getLocation(network).contains(Optional.of(onFlowConstraintInCountry.getCountry()))) {
return false;
} else {
raForOtherCnecs = true;
}
}
}
}
return raForOtherCnecs;
}
}