SystematicSensitivityResult.java
/*
* Copyright (c) 2019, 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.sensitivityanalysis;
import com.powsybl.contingency.Contingency;
import com.powsybl.openrao.data.crac.api.Instant;
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.iidm.network.TwoSides;
import com.powsybl.openrao.data.crac.api.rangeaction.HvdcRangeAction;
import com.powsybl.openrao.data.crac.api.rangeaction.RangeAction;
import com.powsybl.openrao.sensitivityanalysis.rasensihandler.RangeActionSensiHandler;
import com.powsybl.iidm.network.HvdcLine;
import com.powsybl.iidm.network.Network;
import com.powsybl.sensitivity.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
/**
* @author Pengbo Wang {@literal <pengbo.wang at rte-international.com>}
*/
public class SystematicSensitivityResult {
private static class StateResult {
private SensitivityComputationStatus status = SensitivityComputationStatus.SUCCESS;
private final Map<String, Map<TwoSides, Double>> referenceFlows = new HashMap<>();
private final Map<String, Map<TwoSides, Double>> referenceIntensities = new HashMap<>();
private final Map<String, Map<String, Map<TwoSides, Double>>> flowSensitivities = new HashMap<>();
private final Map<String, Map<String, Map<TwoSides, Double>>> intensitySensitivities = new HashMap<>();
private SensitivityComputationStatus getSensitivityComputationStatus() {
return status;
}
private Map<String, Map<TwoSides, Double>> getReferenceFlows() {
return referenceFlows;
}
private Map<String, Map<TwoSides, Double>> getReferenceIntensities() {
return referenceIntensities;
}
private Map<String, Map<String, Map<TwoSides, Double>>> getFlowSensitivities() {
return flowSensitivities;
}
private Map<String, Map<String, Map<TwoSides, Double>>> getIntensitySensitivities() {
return intensitySensitivities;
}
private boolean isEmpty() {
return referenceFlows.isEmpty() && referenceIntensities.isEmpty() && flowSensitivities.isEmpty() && intensitySensitivities.isEmpty();
}
}
public enum SensitivityComputationStatus {
SUCCESS,
PARTIAL_FAILURE,
FAILURE
}
private SensitivityComputationStatus status;
private final StateResult nStateResult = new StateResult();
private final Map<Integer, Map<String, StateResult>> postContingencyResults = new HashMap<>();
private final Map<Cnec<?>, StateResult> memoizedStateResultPerCnec = new ConcurrentHashMap<>();
public SystematicSensitivityResult() {
this.status = SensitivityComputationStatus.SUCCESS;
}
public SystematicSensitivityResult(SensitivityComputationStatus status) {
this.status = status;
}
public SystematicSensitivityResult completeData(SensitivityAnalysisResult results, Integer instantOrder) {
postContingencyResults.putIfAbsent(instantOrder, new HashMap<>());
// if a failing perimeter was already run, then the status would be set to PARTIAL_FAILURE
boolean anyContingencyFailure = this.status == SensitivityComputationStatus.PARTIAL_FAILURE;
// status set to failure initially, and set to success if we find at least one non NaN value
this.status = SensitivityComputationStatus.FAILURE;
if (results == null) {
return this;
}
results.getPreContingencyValues().forEach(sensitivityValue -> fillIndividualValue(sensitivityValue, nStateResult, results.getFactors(), SensitivityAnalysisResult.Status.SUCCESS));
for (SensitivityAnalysisResult.SensitivityContingencyStatus contingencyStatus : results.getContingencyStatuses()) {
if (contingencyStatus.getStatus() == SensitivityAnalysisResult.Status.FAILURE) {
anyContingencyFailure = true;
}
StateResult contingencyStateResult = new StateResult();
contingencyStateResult.status = contingencyStatus.getStatus().equals(SensitivityAnalysisResult.Status.FAILURE) ? SensitivityComputationStatus.FAILURE : SensitivityComputationStatus.SUCCESS;
results.getValues(contingencyStatus.getContingencyId()).forEach(sensitivityValue ->
fillIndividualValue(sensitivityValue, contingencyStateResult, results.getFactors(), contingencyStatus.getStatus())
);
postContingencyResults.get(instantOrder).put(contingencyStatus.getContingencyId(), contingencyStateResult);
}
if (!results.getPreContingencyValues().isEmpty()) {
nStateResult.status = this.status;
}
if (nStateResult.status != SensitivityComputationStatus.FAILURE && anyContingencyFailure && !nStateResult.isEmpty()) {
this.status = SensitivityComputationStatus.PARTIAL_FAILURE;
}
return this;
}
public SystematicSensitivityResult postTreatIntensities() {
postTreatIntensitiesOnState(nStateResult);
postContingencyResults.values().forEach(map -> map.values().forEach(this::postTreatIntensitiesOnState));
return this;
}
/**
* Sensitivity providers return absolute values for intensities
* In case flows are negative, we shall replace this value by its opposite
*/
private void postTreatIntensitiesOnState(StateResult stateResult) {
stateResult.getReferenceFlows()
.forEach((neId, sideAndFlow) -> {
if (stateResult.getReferenceIntensities().containsKey(neId)) {
sideAndFlow.forEach((side, flow) -> {
if (flow < 0) {
stateResult.getReferenceIntensities().get(neId).put(side, -stateResult.getReferenceIntensities().get(neId).get(side));
}
});
}
if (stateResult.getIntensitySensitivities().containsKey(neId)) {
sideAndFlow.forEach((side, flow) -> {
if (flow < 0) {
Map<String, Map<TwoSides, Double>> sensitivities = stateResult.getIntensitySensitivities().get(neId);
sensitivities.forEach((actionId, sideToSensi) -> sensitivities.get(actionId).put(side, -sideToSensi.get(side)));
}
});
}
});
}
public SystematicSensitivityResult postTreatHvdcs(Network network, Map<String, HvdcRangeAction> hvdcRangeActions) {
postTreatHvdcsOnState(network, hvdcRangeActions, nStateResult);
postContingencyResults.values().forEach(stringStateResultMap ->
stringStateResultMap.values().forEach(stateResult -> postTreatHvdcsOnState(network, hvdcRangeActions, stateResult))
);
return this;
}
private void postTreatHvdcsOnState(Network network, Map<String, HvdcRangeAction> hvdcRangeActions, StateResult stateResult) {
hvdcRangeActions.forEach((networkElementId, hvdcRangeAction) -> {
HvdcLine hvdcLine = network.getHvdcLine(networkElementId);
if (hvdcLine.getConvertersMode() == HvdcLine.ConvertersMode.SIDE_1_INVERTER_SIDE_2_RECTIFIER) {
stateResult.getFlowSensitivities().forEach((cnecId, cnecFlowSensis) -> {
if (cnecFlowSensis.containsKey(networkElementId)) {
cnecFlowSensis.put(networkElementId, invertMapValues(cnecFlowSensis.get(networkElementId)));
}
});
stateResult.getIntensitySensitivities().forEach((cnecId, cnecIntensitySensis) -> {
if (cnecIntensitySensis.containsKey(networkElementId)) {
cnecIntensitySensis.put(networkElementId, invertMapValues(cnecIntensitySensis.get(networkElementId)));
}
});
}
});
}
private Map<TwoSides, Double> invertMapValues(Map<TwoSides, Double> map) {
Map<TwoSides, Double> invertedMap = new EnumMap<>(TwoSides.class);
map.forEach((key, value) -> invertedMap.put(key, -value));
return invertedMap;
}
private void fillIndividualValue(SensitivityValue value, StateResult stateResult, List<SensitivityFactor> factors, SensitivityAnalysisResult.Status status) {
double reference = status.equals(SensitivityAnalysisResult.Status.FAILURE) ? Double.NaN : value.getFunctionReference();
double sensitivity = status.equals(SensitivityAnalysisResult.Status.FAILURE) ? Double.NaN : value.getValue();
SensitivityFactor factor = factors.get(value.getFactorIndex());
if (!Double.isNaN(reference) && !Double.isNaN(sensitivity)) {
this.status = SensitivityComputationStatus.SUCCESS;
}
if (Double.isNaN(reference) && status != SensitivityAnalysisResult.Status.FAILURE) {
reference = 0;
sensitivity = 0;
}
TwoSides side = null;
double activePowerCoefficient = 0;
if (factor.getFunctionType().equals(SensitivityFunctionType.BRANCH_ACTIVE_POWER_1) || factor.getFunctionType().equals(SensitivityFunctionType.BRANCH_CURRENT_1)) {
side = TwoSides.ONE;
activePowerCoefficient = 1;
} else if (factor.getFunctionType().equals(SensitivityFunctionType.BRANCH_ACTIVE_POWER_2) || factor.getFunctionType().equals(SensitivityFunctionType.BRANCH_CURRENT_2)) {
side = TwoSides.TWO;
activePowerCoefficient = -1; // Open RAO always considers flows as seen from Side 1. Sensitivity providers invert side flows.
}
if (factor.getFunctionType().equals(SensitivityFunctionType.BRANCH_ACTIVE_POWER_1) || factor.getFunctionType().equals(SensitivityFunctionType.BRANCH_ACTIVE_POWER_2)) {
stateResult.getReferenceFlows()
.computeIfAbsent(factor.getFunctionId(), k -> new EnumMap<>(TwoSides.class))
.putIfAbsent(side, reference * activePowerCoefficient);
stateResult.getFlowSensitivities()
.computeIfAbsent(factor.getFunctionId(), k -> new HashMap<>())
.computeIfAbsent(factor.getVariableId(), k -> new EnumMap<>(TwoSides.class))
.putIfAbsent(side, sensitivity * activePowerCoefficient);
} else if (factor.getFunctionType().equals(SensitivityFunctionType.BRANCH_CURRENT_1) || factor.getFunctionType().equals(SensitivityFunctionType.BRANCH_CURRENT_2)) {
stateResult.getReferenceIntensities()
.computeIfAbsent(factor.getFunctionId(), k -> new EnumMap<>(TwoSides.class))
.putIfAbsent(side, reference);
}
}
public boolean isSuccess() {
return status != SensitivityComputationStatus.FAILURE;
}
public SensitivityComputationStatus getStatus() {
return status;
}
public SensitivityComputationStatus getStatus(State state) {
if (status == SensitivityComputationStatus.FAILURE) {
return status;
}
Optional<Contingency> optionalContingency = state.getContingency();
if (optionalContingency.isPresent()) {
List<Integer> possibleInstants = postContingencyResults.keySet().stream()
.filter(instantOrder -> instantOrder <= state.getInstant().getOrder())
.sorted(Comparator.reverseOrder())
.toList();
for (Integer instantOrder : possibleInstants) {
// Use latest sensi computed on state
if (postContingencyResults.get(instantOrder).containsKey(optionalContingency.get().getId())) {
return postContingencyResults.get(instantOrder).get(optionalContingency.get().getId()).getSensitivityComputationStatus();
}
}
return SensitivityComputationStatus.FAILURE;
} else {
return nStateResult.getSensitivityComputationStatus();
}
}
public void setStatus(SensitivityComputationStatus status) {
this.status = status;
}
public Set<String> getContingencies() {
return postContingencyResults.values().stream().flatMap(contingencyResult -> contingencyResult.keySet().stream()).collect(Collectors.toSet());
}
public double getReferenceFlow(FlowCnec cnec, TwoSides side) {
StateResult stateResult = getCnecStateResult(cnec);
if (stateResult == null ||
!stateResult.getReferenceFlows().containsKey(cnec.getNetworkElement().getId()) ||
!stateResult.getReferenceFlows().get(cnec.getNetworkElement().getId()).containsKey(side)) {
return 0.0;
}
return stateResult.getReferenceFlows().get(cnec.getNetworkElement().getId()).get(side);
}
public double getReferenceFlow(FlowCnec cnec, TwoSides side, Instant instant) {
StateResult stateResult = getCnecStateResult(cnec, instant);
if (stateResult == null ||
!stateResult.getReferenceFlows().containsKey(cnec.getNetworkElement().getId()) ||
!stateResult.getReferenceFlows().get(cnec.getNetworkElement().getId()).containsKey(side)) {
return 0.0;
}
return stateResult.getReferenceFlows().get(cnec.getNetworkElement().getId()).get(side);
}
public double getReferenceIntensity(FlowCnec cnec, TwoSides side) {
StateResult stateResult = getCnecStateResult(cnec);
if (stateResult == null ||
!stateResult.getReferenceIntensities().containsKey(cnec.getNetworkElement().getId()) ||
!stateResult.getReferenceIntensities().get(cnec.getNetworkElement().getId()).containsKey(side)) {
return 0.0;
}
return stateResult.getReferenceIntensities().get(cnec.getNetworkElement().getId()).get(side);
}
public double getReferenceIntensity(FlowCnec cnec, TwoSides side, Instant instant) {
StateResult stateResult = getCnecStateResult(cnec, instant);
if (stateResult == null ||
!stateResult.getReferenceIntensities().containsKey(cnec.getNetworkElement().getId()) ||
!stateResult.getReferenceIntensities().get(cnec.getNetworkElement().getId()).containsKey(side)) {
return 0.0;
}
return stateResult.getReferenceIntensities().get(cnec.getNetworkElement().getId()).get(side);
}
public double getSensitivityOnFlow(RangeAction<?> rangeAction, FlowCnec cnec, TwoSides side) {
return RangeActionSensiHandler.get(rangeAction).getSensitivityOnFlow(cnec, side, this);
}
public double getSensitivityOnFlow(SensitivityVariableSet glsk, FlowCnec cnec, TwoSides side) {
return getSensitivityOnFlow(glsk.getId(), cnec, side);
}
public double getSensitivityOnFlow(String variableId, FlowCnec cnec, TwoSides side) {
StateResult stateResult = getCnecStateResult(cnec);
if (stateResult == null ||
!stateResult.getFlowSensitivities().containsKey(cnec.getNetworkElement().getId()) ||
!stateResult.getFlowSensitivities().get(cnec.getNetworkElement().getId()).containsKey(variableId) ||
!stateResult.getFlowSensitivities().get(cnec.getNetworkElement().getId()).get(variableId).containsKey(side)) {
return 0.0;
}
return stateResult.getFlowSensitivities().get(cnec.getNetworkElement().getId()).get(variableId).get(side);
}
public double getSensitivityOnFlow(String variableId, FlowCnec cnec, TwoSides side, Instant instant) {
StateResult stateResult = getCnecStateResult(cnec, instant);
if (stateResult == null ||
!stateResult.getFlowSensitivities().containsKey(cnec.getNetworkElement().getId()) ||
!stateResult.getFlowSensitivities().get(cnec.getNetworkElement().getId()).containsKey(variableId) ||
!stateResult.getFlowSensitivities().get(cnec.getNetworkElement().getId()).get(variableId).containsKey(side)) {
return 0.0;
}
return stateResult.getFlowSensitivities().get(cnec.getNetworkElement().getId()).get(variableId).get(side);
}
private StateResult getCnecStateResult(Cnec<?> cnec) {
if (memoizedStateResultPerCnec.containsKey(cnec)) {
return memoizedStateResultPerCnec.get(cnec);
}
Optional<Contingency> optionalContingency = cnec.getState().getContingency();
if (optionalContingency.isPresent()) {
List<Integer> possibleInstants = postContingencyResults.keySet().stream()
.filter(instantOrder -> instantOrder <= cnec.getState().getInstant().getOrder())
.sorted(Comparator.reverseOrder())
.toList();
for (Integer instantOrder : possibleInstants) {
// Use latest sensi computed on the cnec's contingency amidst the last instants before cnec state.
String contingencyId = optionalContingency.get().getId();
if (postContingencyResults.get(instantOrder).containsKey(contingencyId)) {
memoizedStateResultPerCnec.put(cnec, postContingencyResults.get(instantOrder).get(contingencyId));
return memoizedStateResultPerCnec.get(cnec);
}
}
return null;
} else {
return nStateResult;
}
}
private StateResult getCnecStateResult(Cnec<?> cnec, Instant instant) {
Optional<Contingency> optionalContingency = cnec.getState().getContingency();
if (optionalContingency.isPresent()) {
String contingencyId = optionalContingency.get().getId();
int maxAdmissibleInstantOrder = instant == null ? 1 : Math.max(1, instant.getOrder()); // when dealing with post-contingency CNECs, a null instant refers to the outage instant
List<Integer> possibleInstants = postContingencyResults.keySet().stream()
.filter(instantOrder -> instantOrder <= cnec.getState().getInstant().getOrder() && instantOrder <= maxAdmissibleInstantOrder)
.sorted(Comparator.reverseOrder())
.filter(instantOrder -> postContingencyResults.get(instantOrder).containsKey(contingencyId))
.toList();
return possibleInstants.isEmpty() ? null : postContingencyResults.get(possibleInstants.get(0)).get(contingencyId);
} else {
return nStateResult; // when dealing with preventive CNECs, a null instant refers to the initial instant
}
}
}