SystematicSensitivityResultTest.java
/*
* Copyright (c) 2020, 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.*;
import com.powsybl.openrao.commons.Unit;
import com.powsybl.iidm.network.TwoSides;
import com.powsybl.openrao.data.crac.api.CracFactory;
import com.powsybl.openrao.data.crac.api.State;
import com.powsybl.openrao.data.crac.api.cnec.FlowCnecAdder;
import com.powsybl.openrao.data.crac.api.rangeaction.HvdcRangeAction;
import com.powsybl.glsk.commons.ZonalData;
import com.powsybl.openrao.data.crac.api.Crac;
import com.powsybl.openrao.data.crac.api.InstantKind;
import com.powsybl.openrao.data.crac.api.cnec.FlowCnec;
import com.powsybl.openrao.data.crac.api.rangeaction.RangeAction;
import com.powsybl.openrao.data.crac.impl.utils.CommonCracCreation;
import com.powsybl.openrao.data.crac.impl.utils.NetworkImportsUtil;
import com.powsybl.glsk.ucte.UcteGlskDocument;
import com.powsybl.iidm.network.HvdcLine;
import com.powsybl.iidm.network.Network;
import com.powsybl.sensitivity.*;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import java.time.Instant;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.*;
/**
* @author Sebastien Murgey {@literal <sebastien.murgey at rte-france.com>}
*/
class SystematicSensitivityResultTest {
private static final double EPSILON = 1e-2;
private static final String PREVENTIVE_INSTANT_ID = "preventive";
private static final String OUTAGE_INSTANT_ID = "outage";
private static final String AUTO_INSTANT_ID = "auto";
private static final String CURATIVE_INSTANT_ID = "curative";
private Network network;
private FlowCnec nStateCnec;
private FlowCnec contingencyCnec;
private RangeAction<?> rangeAction;
private HvdcRangeAction hvdcRangeAction;
private SensitivityVariableSet linearGlsk;
private Crac crac;
private RangeActionSensitivityProvider rangeActionSensitivityProvider;
private PtdfSensitivityProvider ptdfSensitivityProvider;
private int outageInstantOrder;
public void setUpWith12Nodes() {
network = NetworkImportsUtil.import12NodesNetwork();
crac = CommonCracCreation.createWithPreventivePstRange(Set.of(TwoSides.ONE, TwoSides.TWO));
outageInstantOrder = crac.getInstant(CURATIVE_INSTANT_ID).getOrder();
ZonalData<SensitivityVariableSet> glskProvider = UcteGlskDocument.importGlsk(getClass().getResourceAsStream("/glsk_proportional_12nodes.xml"))
.getZonalGlsks(network, Instant.parse("2016-07-28T22:30:00Z"));
// Ra Provider
rangeActionSensitivityProvider = new RangeActionSensitivityProvider(crac.getRangeActions(), crac.getFlowCnecs(), Stream.of(Unit.MEGAWATT, Unit.AMPERE).collect(Collectors.toSet()));
// Ptdf Provider
ptdfSensitivityProvider = new PtdfSensitivityProvider(glskProvider, crac.getFlowCnecs(), Collections.singleton(Unit.MEGAWATT));
nStateCnec = crac.getFlowCnec("cnec1basecase");
rangeAction = crac.getRangeAction("pst");
contingencyCnec = crac.getFlowCnec("cnec1stateCurativeContingency1");
linearGlsk = glskProvider.getData("10YFR-RTE------C");
}
@Test
void testPostTreatIntensities() {
setUpWith12Nodes();
// When
SensitivityAnalysisResult sensitivityAnalysisResult = SensitivityAnalysis.find().run(network,
rangeActionSensitivityProvider.getAllFactors(network),
ptdfSensitivityProvider.getContingencies(network),
new ArrayList<>(),
SensitivityAnalysisParameters.load());
SystematicSensitivityResult result = new SystematicSensitivityResult().completeData(sensitivityAnalysisResult, outageInstantOrder);
// Before postTreating intensities
assertEquals(-20, result.getReferenceFlow(contingencyCnec, TwoSides.ONE), EPSILON);
assertEquals(200, result.getReferenceIntensity(contingencyCnec, TwoSides.ONE), EPSILON);
assertEquals(25, result.getReferenceFlow(contingencyCnec, TwoSides.TWO), EPSILON);
assertEquals(205, result.getReferenceIntensity(contingencyCnec, TwoSides.TWO), EPSILON);
// After postTreating intensities
result.postTreatIntensities();
assertEquals(-20, result.getReferenceFlow(contingencyCnec, TwoSides.ONE), EPSILON);
assertEquals(-200, result.getReferenceIntensity(contingencyCnec, TwoSides.ONE), EPSILON);
assertEquals(25, result.getReferenceFlow(contingencyCnec, TwoSides.TWO), EPSILON);
assertEquals(205, result.getReferenceIntensity(contingencyCnec, TwoSides.TWO), EPSILON);
}
@Test
void testPstResultManipulation() {
setUpWith12Nodes();
// When
SensitivityAnalysisResult sensitivityAnalysisResult = SensitivityAnalysis.find().run(network,
rangeActionSensitivityProvider.getAllFactors(network),
rangeActionSensitivityProvider.getContingencies(network),
new ArrayList<>(),
SensitivityAnalysisParameters.load());
SystematicSensitivityResult result = new SystematicSensitivityResult().completeData(sensitivityAnalysisResult, outageInstantOrder).postTreatIntensities();
// Then
assertTrue(result.isSuccess());
assertEquals(SystematicSensitivityResult.SensitivityComputationStatus.SUCCESS, result.getStatus());
// in basecase
assertEquals(10, result.getReferenceFlow(nStateCnec, TwoSides.ONE), EPSILON);
assertEquals(25, result.getReferenceIntensity(nStateCnec, TwoSides.ONE), EPSILON);
assertEquals(0.5, result.getSensitivityOnFlow(rangeAction, nStateCnec, TwoSides.ONE), EPSILON);
assertEquals(-15, result.getReferenceFlow(nStateCnec, TwoSides.TWO), EPSILON);
assertEquals(-30, result.getReferenceIntensity(nStateCnec, TwoSides.TWO), EPSILON);
assertEquals(-0.55, result.getSensitivityOnFlow(rangeAction, nStateCnec, TwoSides.TWO), EPSILON);
// after contingency
assertEquals(-20, result.getReferenceFlow(contingencyCnec, TwoSides.ONE), EPSILON);
assertEquals(-200, result.getReferenceIntensity(contingencyCnec, TwoSides.ONE), EPSILON);
assertEquals(-5, result.getSensitivityOnFlow(rangeAction, contingencyCnec, TwoSides.ONE), EPSILON);
assertEquals(25, result.getReferenceFlow(contingencyCnec, TwoSides.TWO), EPSILON);
assertEquals(205, result.getReferenceIntensity(contingencyCnec, TwoSides.TWO), EPSILON);
assertEquals(5.5, result.getSensitivityOnFlow(rangeAction, contingencyCnec, TwoSides.TWO), EPSILON);
}
@Test
void testPtdfResultManipulation() {
setUpWith12Nodes();
// When
SensitivityAnalysisResult sensitivityAnalysisResult = SensitivityAnalysis.find().run(network,
ptdfSensitivityProvider.getAllFactors(network),
ptdfSensitivityProvider.getContingencies(network),
new ArrayList<>(),
SensitivityAnalysisParameters.load());
SystematicSensitivityResult result = new SystematicSensitivityResult().completeData(sensitivityAnalysisResult, outageInstantOrder).postTreatIntensities();
// Then
assertTrue(result.isSuccess());
// in basecase
assertEquals(10, result.getReferenceFlow(nStateCnec, TwoSides.ONE), EPSILON);
assertEquals(0.140, result.getSensitivityOnFlow(linearGlsk, nStateCnec, TwoSides.ONE), EPSILON);
assertEquals(-15, result.getReferenceFlow(nStateCnec, TwoSides.TWO), EPSILON);
assertEquals(-0.19, result.getSensitivityOnFlow(linearGlsk, nStateCnec, TwoSides.TWO), EPSILON);
// after contingency
assertEquals(-20, result.getReferenceFlow(contingencyCnec, TwoSides.ONE), EPSILON);
assertEquals(6, result.getSensitivityOnFlow(linearGlsk, contingencyCnec, TwoSides.ONE), EPSILON);
assertEquals(25, result.getReferenceFlow(contingencyCnec, TwoSides.TWO), EPSILON);
assertEquals(-6.5, result.getSensitivityOnFlow(linearGlsk, contingencyCnec, TwoSides.TWO), EPSILON);
}
@Test
void testFailureSensiResult() {
setUpWith12Nodes();
// When
SensitivityAnalysisResult sensitivityAnalysisResult = Mockito.mock(SensitivityAnalysisResult.class);
SystematicSensitivityResult result = new SystematicSensitivityResult().completeData(sensitivityAnalysisResult, outageInstantOrder).postTreatIntensities();
// Then
assertFalse(result.isSuccess());
assertEquals(SystematicSensitivityResult.SensitivityComputationStatus.FAILURE, result.getStatus());
}
private void setUpForHvdc() {
crac = CracFactory.findDefault().create("test-crac")
.newInstant(PREVENTIVE_INSTANT_ID, InstantKind.PREVENTIVE)
.newInstant(OUTAGE_INSTANT_ID, InstantKind.OUTAGE)
.newInstant(AUTO_INSTANT_ID, InstantKind.AUTO)
.newInstant(CURATIVE_INSTANT_ID, InstantKind.CURATIVE);
crac.newContingency()
.withId("co")
.withContingencyElement("NNL2AA11 BBE3AA11 1", ContingencyElementType.LINE)
.add();
nStateCnec = crac.newFlowCnec()
.withId("cnec-prev")
.withNetworkElement("BBE1AA11 FFR5AA11 1")
.withInstant(PREVENTIVE_INSTANT_ID)
.newThreshold().withMax(1000.).withUnit(Unit.MEGAWATT).withSide(TwoSides.ONE).add()
.add();
contingencyCnec = crac.newFlowCnec()
.withId("cnec-cur")
.withNetworkElement("BBE1AA11 FFR5AA11 1")
.withContingency("co")
.withInstant(OUTAGE_INSTANT_ID)
.newThreshold().withMax(1000.).withUnit(Unit.MEGAWATT).withSide(TwoSides.TWO).add()
.add();
hvdcRangeAction = crac.newHvdcRangeAction()
.withId("hvdc-ra")
.withNetworkElement("BBE2AA11 FFR3AA11 1")
.newRange().withMin(-1000.).withMax(1000.).add()
.add();
network = Network.read("TestCase16NodesWithHvdc.xiidm", getClass().getResourceAsStream("/TestCase16NodesWithHvdc.xiidm"));
rangeActionSensitivityProvider = new RangeActionSensitivityProvider(crac.getRangeActions(), crac.getFlowCnecs(), Stream.of(Unit.MEGAWATT, Unit.AMPERE).collect(Collectors.toSet()));
}
@Test
void testPostTreatHvdcNoEffect() {
setUpForHvdc();
Map<String, HvdcRangeAction> hvdcs = Map.of(hvdcRangeAction.getNetworkElement().getId(), hvdcRangeAction);
SensitivityAnalysisResult sensitivityAnalysisResult = SensitivityAnalysis.find().run(network,
rangeActionSensitivityProvider.getAllFactors(network),
rangeActionSensitivityProvider.getContingencies(network),
new ArrayList<>(),
SensitivityAnalysisParameters.load());
SystematicSensitivityResult result = new SystematicSensitivityResult()
.completeData(sensitivityAnalysisResult, outageInstantOrder)
.postTreatIntensities()
.postTreatHvdcs(network, hvdcs);
assertEquals(30., result.getReferenceFlow(nStateCnec, TwoSides.ONE), EPSILON);
assertEquals(40., result.getReferenceIntensity(nStateCnec, TwoSides.ONE), EPSILON);
assertEquals(0.34, result.getSensitivityOnFlow(hvdcRangeAction, nStateCnec, TwoSides.ONE), EPSILON);
assertEquals(26., result.getReferenceFlow(contingencyCnec, TwoSides.TWO), EPSILON);
assertEquals(-31., result.getReferenceIntensity(contingencyCnec, TwoSides.TWO), EPSILON);
assertEquals(-7.5, result.getSensitivityOnFlow(hvdcRangeAction, contingencyCnec, TwoSides.TWO), EPSILON);
}
@Test
void testPostTreatHvdcInvert() {
setUpForHvdc();
Map<String, HvdcRangeAction> hvdcs = Map.of(hvdcRangeAction.getNetworkElement().getId(), hvdcRangeAction);
network.getHvdcLine("BBE2AA11 FFR3AA11 1").setConvertersMode(HvdcLine.ConvertersMode.SIDE_1_INVERTER_SIDE_2_RECTIFIER);
SensitivityAnalysisResult sensitivityAnalysisResult = SensitivityAnalysis.find().run(network,
rangeActionSensitivityProvider.getAllFactors(network),
rangeActionSensitivityProvider.getContingencies(network),
new ArrayList<>(),
SensitivityAnalysisParameters.load());
SystematicSensitivityResult result = new SystematicSensitivityResult()
.completeData(sensitivityAnalysisResult, outageInstantOrder)
.postTreatIntensities()
.postTreatHvdcs(network, hvdcs);
assertEquals(30., result.getReferenceFlow(nStateCnec, TwoSides.ONE), EPSILON);
assertEquals(40., result.getReferenceIntensity(nStateCnec, TwoSides.ONE), EPSILON);
assertEquals(-0.34, result.getSensitivityOnFlow(hvdcRangeAction, nStateCnec, TwoSides.ONE), EPSILON);
assertEquals(26., result.getReferenceFlow(contingencyCnec, TwoSides.TWO), EPSILON);
assertEquals(-31., result.getReferenceIntensity(contingencyCnec, TwoSides.TWO), EPSILON);
assertEquals(7.5, result.getSensitivityOnFlow(hvdcRangeAction, contingencyCnec, TwoSides.TWO), EPSILON);
}
@Test
// Test case where N succeeds and all N-1 fail
void testPartialContingencyFailures() {
setUpWith12Nodes();
// The contingency points to an element that does not exist in the network => n-1 status should be set to failure
Contingency contingency = new Contingency("wrong_contingency", new BranchContingency("fake_branch"));
State contingencyState = Mockito.mock(State.class);
Mockito.when(contingencyState.getContingency()).thenReturn(Optional.of(contingency));
Mockito.when(contingencyState.getInstant()).thenReturn(crac.getOutageInstant());
SensitivityFactor sensitivityFactor1 = new SensitivityFactor(
SensitivityFunctionType.BRANCH_ACTIVE_POWER_1,
"BBE2AA1 FFR3AA1 1",
SensitivityVariableType.TRANSFORMER_PHASE,
"BBE2AA1 BBE3AA1 1",
false,
new ContingencyContext(contingency.getId(), ContingencyContextType.SPECIFIC)
);
SensitivityFactor sensitivityFactor2 = new SensitivityFactor(
SensitivityFunctionType.BRANCH_ACTIVE_POWER_1,
"BBE2AA1 FFR3AA1 1",
SensitivityVariableType.TRANSFORMER_PHASE,
"BBE2AA1 BBE3AA1 1",
false,
new ContingencyContext(null, ContingencyContextType.NONE)
);
SensitivityAnalysisResult sensitivityAnalysisResult = SensitivityAnalysis.find().run(network,
List.of(sensitivityFactor1, sensitivityFactor2),
List.of(contingency),
new ArrayList<>(),
SensitivityAnalysisParameters.load());
SystematicSensitivityResult result = new SystematicSensitivityResult().completeData(sensitivityAnalysisResult, outageInstantOrder).postTreatIntensities();
// N is in SUCCESS, N-1 in failure
assertTrue(result.isSuccess());
assertEquals(SystematicSensitivityResult.SensitivityComputationStatus.PARTIAL_FAILURE, result.getStatus());
assertEquals(SystematicSensitivityResult.SensitivityComputationStatus.SUCCESS, result.getStatus(crac.getPreventiveState()));
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)));
}
@Test
void testCompleteDataWithFailingPerimeterIn2P() {
setUpWith12Nodes();
// A first call to complete data with a sensitivity analysis N and N-1 succeed.
// Simulate the first call to completeData of the sensi computation pre 2P.
SensitivityAnalysisResult sensitivityAnalysisResult = SensitivityAnalysis.find().run(network,
rangeActionSensitivityProvider.getAllFactors(network),
rangeActionSensitivityProvider.getContingencies(network),
new ArrayList<>(),
SensitivityAnalysisParameters.load());
SystematicSensitivityResult result = new SystematicSensitivityResult().completeData(sensitivityAnalysisResult, outageInstantOrder);
assertEquals(SystematicSensitivityResult.SensitivityComputationStatus.SUCCESS, result.getStatus());
// Simulate a second call to completeData during sensitivity computation pre 2P on a state with RAs that failed (a N-1 state fails).
Contingency contingency = new Contingency("contingency", new BranchContingency("branch"));
SensitivityFactor sensitivityFactor1 = new SensitivityFactor(
SensitivityFunctionType.BRANCH_ACTIVE_POWER_1,
"BBE2AA1 FFR3AA1 1",
SensitivityVariableType.TRANSFORMER_PHASE,
"BBE2AA1 BBE3AA1 1",
false,
new ContingencyContext(contingency.getId(), ContingencyContextType.SPECIFIC)
);
sensitivityAnalysisResult = new SensitivityAnalysisResult(
List.of(sensitivityFactor1),
List.of(new SensitivityAnalysisResult.SensitivityContingencyStatus(contingency.getId(), SensitivityAnalysisResult.Status.FAILURE)),
List.of()
);
result.completeData(sensitivityAnalysisResult, outageInstantOrder);
// success in n and a n-1 fails -> PARTIAL_FAILURE
assertEquals(SystematicSensitivityResult.SensitivityComputationStatus.PARTIAL_FAILURE, result.getStatus());
}
@Test
void testFailingCurativePerimeter() {
setUpWith12Nodes();
// Simulate a second call to completeData during sensitivity computation pre 2P on a state with RAs that failed (a N-1 state fails).
Contingency contingency = new Contingency("contingency", new BranchContingency("branch"));
SensitivityFactor sensitivityFactor1 = new SensitivityFactor(
SensitivityFunctionType.BRANCH_ACTIVE_POWER_1,
"BBE2AA1 FFR3AA1 1",
SensitivityVariableType.TRANSFORMER_PHASE,
"BBE2AA1 BBE3AA1 1",
false,
new ContingencyContext(contingency.getId(), ContingencyContextType.SPECIFIC)
);
SensitivityAnalysisResult sensitivityAnalysisResult = new SensitivityAnalysisResult(
List.of(sensitivityFactor1),
List.of(new SensitivityAnalysisResult.SensitivityContingencyStatus(contingency.getId(), SensitivityAnalysisResult.Status.FAILURE)),
List.of()
);
SystematicSensitivityResult result = new SystematicSensitivityResult();
result.completeData(sensitivityAnalysisResult, outageInstantOrder);
assertEquals(SystematicSensitivityResult.SensitivityComputationStatus.FAILURE, result.getStatus());
}
}