diff --git a/sensitivity-analysis/src/main/java/com/powsybl/openrao/sensitivityanalysis/SystematicSensitivityResult.java b/sensitivity-analysis/src/main/java/com/powsybl/openrao/sensitivityanalysis/SystematicSensitivityResult.java index 3ba80b90d8..70d3ced8cf 100644 --- a/sensitivity-analysis/src/main/java/com/powsybl/openrao/sensitivityanalysis/SystematicSensitivityResult.java +++ b/sensitivity-analysis/src/main/java/com/powsybl/openrao/sensitivityanalysis/SystematicSensitivityResult.java @@ -340,12 +340,13 @@ private StateResult getCnecStateResult(Cnec cnec) { private StateResult getCnecStateResult(Cnec cnec, Instant instant) { Optional 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 possibleInstants = postContingencyResults.keySet().stream() .filter(instantOrder -> instantOrder <= cnec.getState().getInstant().getOrder() && instantOrder <= maxAdmissibleInstantOrder) .sorted(Comparator.reverseOrder()) + .filter(instantOrder -> postContingencyResults.get(instantOrder).containsKey(contingencyId)) .toList(); - String contingencyId = optionalContingency.get().getId(); 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 diff --git a/sensitivity-analysis/src/test/java/com/powsybl/openrao/sensitivityanalysis/SystematicSensitivityResultTest.java b/sensitivity-analysis/src/test/java/com/powsybl/openrao/sensitivityanalysis/SystematicSensitivityResultTest.java index dd43a0dedc..67bcef9722 100644 --- a/sensitivity-analysis/src/test/java/com/powsybl/openrao/sensitivityanalysis/SystematicSensitivityResultTest.java +++ b/sensitivity-analysis/src/test/java/com/powsybl/openrao/sensitivityanalysis/SystematicSensitivityResultTest.java @@ -11,6 +11,7 @@ import com.powsybl.iidm.network.TwoSides; import com.powsybl.openrao.data.cracapi.CracFactory; import com.powsybl.openrao.data.cracapi.State; +import com.powsybl.openrao.data.cracapi.cnec.FlowCnecAdder; import com.powsybl.openrao.data.cracapi.rangeaction.HvdcRangeAction; import com.powsybl.glsk.commons.ZonalData; import com.powsybl.openrao.data.cracapi.Crac; @@ -293,4 +294,82 @@ void testPartialContingencyFailures() { assertEquals(SystematicSensitivityResult.SensitivityComputationStatus.FAILURE, result.getStatus(contingencyState)); } + //This test simulates what happens after a second preventive, where some curative results are stored in outage results. + //We do this to avoid recomputing multiple times the same contingency while no remedial action was applied in later instants. + @Test + void testCurativeResultAtOutageInstant() { + setUpWith12Nodes(); + + FlowCnecAdder cnecAdder = crac.newFlowCnec() + .withId("cnec2stateOutageContingency2") + .withNetworkElement("FFR2AA1 DDE3AA1 1") + .withInstant(OUTAGE_INSTANT_ID) + .withContingency("Contingency FR1 FR2") + .withOptimized(true) + .withOperator("operator2") + .withReliabilityMargin(95.) + .withNominalVoltage(380.) + .withIMax(5000.); + Set.of(TwoSides.ONE, TwoSides.TWO).forEach(side -> + cnecAdder.newThreshold() + .withUnit(Unit.MEGAWATT) + .withSide(TwoSides.ONE) + .withMin(-1800.) + .withMax(1800.) + .add()); + cnecAdder.add(); + + outageInstantOrder = crac.getInstant(OUTAGE_INSTANT_ID).getOrder(); + int curativeInstantOrder = crac.getInstant(CURATIVE_INSTANT_ID).getOrder(); + + //run sensi on outage instant for outageCnec + FlowCnec outageCnec = crac.getFlowCnec("cnec2stateOutageContingency2"); + Contingency outageContingency = outageCnec.getState().getContingency().orElseThrow(); + SensitivityFactor outageSensitivityFactor = new SensitivityFactor( + SensitivityFunctionType.BRANCH_ACTIVE_POWER_1, + outageCnec.getNetworkElement().getId(), + SensitivityVariableType.TRANSFORMER_PHASE, + "BBE2AA1 BBE3AA1 1", + false, + new ContingencyContext(outageContingency.getId(), ContingencyContextType.SPECIFIC) + ); + SensitivityAnalysisResult sensitivityAnalysisResult = SensitivityAnalysis.find().run(network, + List.of(outageSensitivityFactor), + List.of(outageContingency), + new ArrayList<>(), + SensitivityAnalysisParameters.load()); + SystematicSensitivityResult result = new SystematicSensitivityResult().completeData(sensitivityAnalysisResult, outageInstantOrder); + + //run sensi on curative instant for curativeCnec + FlowCnec curativeCnec = crac.getFlowCnec("cnec1stateCurativeContingency1"); + Contingency curativeContingency = curativeCnec.getState().getContingency().orElseThrow(); + SensitivityFactor curativeSensitivityFactor = new SensitivityFactor( + SensitivityFunctionType.BRANCH_ACTIVE_POWER_1, + curativeCnec.getNetworkElement().getId(), + SensitivityVariableType.TRANSFORMER_PHASE, + "BBE2AA1 BBE3AA1 1", + false, + new ContingencyContext(curativeContingency.getId(), ContingencyContextType.SPECIFIC) + ); + sensitivityAnalysisResult = SensitivityAnalysis.find().run(network, + List.of(curativeSensitivityFactor), + List.of(curativeContingency), + new ArrayList<>(), + SensitivityAnalysisParameters.load()); + result.completeData(sensitivityAnalysisResult, curativeInstantOrder); + + //correct flows are available for both factors for all instants + //0 represents a missing value (this is due to the way load flow engines represent an open line, we need 0 as a default value) + assertEquals(-20, result.getReferenceFlow(outageCnec, TwoSides.ONE)); + assertEquals(-20, result.getReferenceFlow(outageCnec, TwoSides.ONE, crac.getInstant(OUTAGE_INSTANT_ID))); + assertEquals(-20, result.getReferenceFlow(outageCnec, TwoSides.ONE, crac.getInstant(AUTO_INSTANT_ID))); + assertEquals(-20, result.getReferenceFlow(outageCnec, TwoSides.ONE, crac.getInstant(CURATIVE_INSTANT_ID))); + + assertEquals(-20, result.getReferenceFlow(curativeCnec, TwoSides.ONE)); + assertEquals(0, result.getReferenceFlow(curativeCnec, TwoSides.ONE, crac.getInstant(OUTAGE_INSTANT_ID))); + assertEquals(0, result.getReferenceFlow(curativeCnec, TwoSides.ONE, crac.getInstant(AUTO_INSTANT_ID))); + assertEquals(-20, result.getReferenceFlow(curativeCnec, TwoSides.ONE, crac.getInstant(CURATIVE_INSTANT_ID))); + + } + }