diff --git a/src/main/java/com/powsybl/openloadflow/network/LfAction.java b/src/main/java/com/powsybl/openloadflow/network/LfAction.java index 6a60c99028..6e218495fd 100644 --- a/src/main/java/com/powsybl/openloadflow/network/LfAction.java +++ b/src/main/java/com/powsybl/openloadflow/network/LfAction.java @@ -212,10 +212,14 @@ private static Optional create(LfBranch branch, String actionId, boole private static Optional create(TerminalsConnectionAction action, LfNetwork lfNetwork) { LfBranch branch = lfNetwork.getBranchById(action.getElementId()); if (branch != null) { - if (action.getSide().isEmpty() && action.isOpen()) { - return Optional.of(new LfAction(action.getId(), branch, null, null, null, null, null)); + if (action.getSide().isEmpty()) { + if (action.isOpen()) { + return Optional.of(new LfAction(action.getId(), branch, null, null, null, null, null)); + } else { + return Optional.of(new LfAction(action.getId(), null, branch, null, null, null, null)); + } } else { - throw new UnsupportedOperationException("Line connection action: only open line at both sides is supported yet."); + throw new UnsupportedOperationException("Terminals connection action: only open or close branch at both sides is supported yet."); } } return Optional.empty(); // could be in another component diff --git a/src/main/java/com/powsybl/openloadflow/network/LfTopoConfig.java b/src/main/java/com/powsybl/openloadflow/network/LfTopoConfig.java index e47bf17a9e..d1bc1af757 100644 --- a/src/main/java/com/powsybl/openloadflow/network/LfTopoConfig.java +++ b/src/main/java/com/powsybl/openloadflow/network/LfTopoConfig.java @@ -31,6 +31,8 @@ public class LfTopoConfig { private final Set branchIdsOpenableSide2 = new HashSet<>(); + private final Set branchIdsToClose = new HashSet<>(); + public LfTopoConfig() { switchesToOpen = new HashSet<>(); switchesToClose = new HashSet<>(); @@ -83,4 +85,8 @@ public Set getBranchIdsOpenableSide1() { public Set getBranchIdsOpenableSide2() { return branchIdsOpenableSide2; } + + public Set getBranchIdsToClose() { + return branchIdsToClose; + } } diff --git a/src/main/java/com/powsybl/openloadflow/network/impl/Networks.java b/src/main/java/com/powsybl/openloadflow/network/impl/Networks.java index 2f09bd9b6f..86efd9cf86 100644 --- a/src/main/java/com/powsybl/openloadflow/network/impl/Networks.java +++ b/src/main/java/com/powsybl/openloadflow/network/impl/Networks.java @@ -124,15 +124,23 @@ private static void retainAndCloseNecessarySwitches(Network network, LfTopoConfi .forEach(sw -> sw.setRetained(true)); topoConfig.getSwitchesToClose().forEach(sw -> sw.setOpen(false)); // in order to be present in the network. + topoConfig.getBranchIdsToClose().stream().map(network::getBranch).forEach(branch -> { + branch.getTerminal1().connect(); + branch.getTerminal2().connect(); + }); // in order to be present in the network. } - private static void restoreInitialTopology(LfNetwork network, Set allSwitchesToClose) { + private static void restoreInitialTopology(LfNetwork network, Set allSwitchesToClose, Set branchIdsToClose) { var connectivity = network.getConnectivity(); connectivity.startTemporaryChanges(); allSwitchesToClose.stream().map(Identifiable::getId).forEach(id -> { LfBranch branch = network.getBranchById(id); connectivity.removeEdge(branch); }); + branchIdsToClose.stream().forEach(id -> { + LfBranch branch = network.getBranchById(id); + connectivity.removeEdge(branch); + }); Set removedBuses = connectivity.getVerticesRemovedFromMainComponent(); removedBuses.forEach(bus -> bus.setDisabled(true)); Set removedBranches = new HashSet<>(connectivity.getEdgesRemovedFromMainComponent()); @@ -182,10 +190,10 @@ public static LfNetworkList load(Network network, LfNetworkParameters networkPar } else { modifiedTopoConfig = topoConfig; } - if (!modifiedTopoConfig.isBreaker()) { + if (!modifiedTopoConfig.isBreaker() && modifiedTopoConfig.getBranchIdsToClose().isEmpty()) { return new LfNetworkList(load(network, topoConfig, networkParameters, reporter)); } else { - if (!networkParameters.isBreakers()) { + if (!networkParameters.isBreakers() && modifiedTopoConfig.isBreaker()) { throw new PowsyblException("LF networks have to be built from bus/breaker view"); } @@ -201,10 +209,10 @@ public static LfNetworkList load(Network network, LfNetworkParameters networkPar List lfNetworks = load(network, topoConfig, networkParameters, reporter); - if (!modifiedTopoConfig.getSwitchesToClose().isEmpty()) { + if (!(modifiedTopoConfig.getSwitchesToClose().isEmpty() && modifiedTopoConfig.getBranchIdsToClose().isEmpty())) { for (LfNetwork lfNetwork : lfNetworks) { // disable all buses and branches not connected to main component (because of switch to close) - restoreInitialTopology(lfNetwork, modifiedTopoConfig.getSwitchesToClose()); + restoreInitialTopology(lfNetwork, modifiedTopoConfig.getSwitchesToClose(), modifiedTopoConfig.getBranchIdsToClose()); } } diff --git a/src/main/java/com/powsybl/openloadflow/sa/AbstractSecurityAnalysis.java b/src/main/java/com/powsybl/openloadflow/sa/AbstractSecurityAnalysis.java index a1cb518969..273ed1a04a 100644 --- a/src/main/java/com/powsybl/openloadflow/sa/AbstractSecurityAnalysis.java +++ b/src/main/java/com/powsybl/openloadflow/sa/AbstractSecurityAnalysis.java @@ -13,8 +13,7 @@ import com.powsybl.computation.ComputationManager; import com.powsybl.contingency.ContingenciesProvider; import com.powsybl.contingency.Contingency; -import com.powsybl.iidm.network.Network; -import com.powsybl.iidm.network.Switch; +import com.powsybl.iidm.network.*; import com.powsybl.loadflow.LoadFlowParameters; import com.powsybl.loadflow.LoadFlowResult; import com.powsybl.math.matrix.MatrixFactory; @@ -125,6 +124,10 @@ SecurityAnalysisReport runSync(SecurityAnalysisParameters securityAnalysisParame findAllPtcToOperate(actions, topoConfig); findAllRtcToOperate(actions, topoConfig); + // try to find branches (lines and two windings transformers). + // tie lines and three windings transformers missing. + findAllBranchesToClose(network, actions, topoConfig); + // load contingencies List contingencies = contingenciesProvider.getContingencies(network); // try to find all switches impacted by at least one contingency and for each contingency the branches impacted @@ -340,6 +343,21 @@ protected static void findAllRtcToOperate(List actions, LfTopoConfig top } } + protected static void findAllBranchesToClose(Network network, List actions, LfTopoConfig topoConfig) { + // only branches open at both side or open at one side are visible in the LfNetwork. + for (Action action : actions) { + if (TerminalsConnectionAction.NAME.equals(action.getType())) { + TerminalsConnectionAction terminalsConnectionAction = (TerminalsConnectionAction) action; + if (terminalsConnectionAction.getSide().isEmpty() && !terminalsConnectionAction.isOpen()) { + Branch branch = network.getBranch(terminalsConnectionAction.getElementId()); + if (branch != null && !(branch instanceof TieLine)) { + topoConfig.getBranchIdsToClose().add(terminalsConnectionAction.getElementId()); + } + } + } + } + } + protected static void distributedMismatch(LfNetwork network, double mismatch, LoadFlowParameters loadFlowParameters, OpenLoadFlowParameters openLoadFlowParameters) { if (loadFlowParameters.isDistributedSlack() && Math.abs(mismatch) > 0) { diff --git a/src/test/java/com/powsybl/openloadflow/sa/LfActionTest.java b/src/test/java/com/powsybl/openloadflow/sa/LfActionTest.java index bb90616a0d..ca6d0a13d5 100644 --- a/src/test/java/com/powsybl/openloadflow/sa/LfActionTest.java +++ b/src/test/java/com/powsybl/openloadflow/sa/LfActionTest.java @@ -83,7 +83,7 @@ void test() { assertTrue(LfAction.create(new TerminalsConnectionAction("A line action", "x", true), lfNetwork, network, acParameters.getNetworkParameters().isBreakers()).isEmpty()); assertTrue(LfAction.create(new PhaseTapChangerTapPositionAction("A phase tap change action", "y", false, 3), lfNetwork, network, acParameters.getNetworkParameters().isBreakers()).isEmpty()); var lineAction = new TerminalsConnectionAction("A line action", "L1", ThreeSides.ONE, false); - assertEquals("Line connection action: only open line at both sides is supported yet.", assertThrows(UnsupportedOperationException.class, () -> LfAction.create(lineAction, lfNetwork, network, acParameters.getNetworkParameters().isBreakers())).getMessage()); + assertEquals("Terminals connection action: only open or close branch at both sides is supported yet.", assertThrows(UnsupportedOperationException.class, () -> LfAction.create(lineAction, lfNetwork, network, acParameters.getNetworkParameters().isBreakers())).getMessage()); } } diff --git a/src/test/java/com/powsybl/openloadflow/sa/OpenSecurityAnalysisWithActionsTest.java b/src/test/java/com/powsybl/openloadflow/sa/OpenSecurityAnalysisWithActionsTest.java index df13a2f2d0..519ae8d1a8 100644 --- a/src/test/java/com/powsybl/openloadflow/sa/OpenSecurityAnalysisWithActionsTest.java +++ b/src/test/java/com/powsybl/openloadflow/sa/OpenSecurityAnalysisWithActionsTest.java @@ -1257,4 +1257,43 @@ void testVSCLossSetpoint() { assertEquals(-0.0, getPostContingencyResult(result, "contingency").getNetworkResult().getBranchResult("l34").getP1(), LoadFlowAssert.DELTA_POWER); assertEquals(-200.0, getOperatorStrategyResult(result, "strategy").getNetworkResult().getBranchResult("l34").getP1(), LoadFlowAssert.DELTA_POWER); } + + @Test + void testTerminalsConnectionAction() { + Network network = FourBusNetworkFactory.create(); + network.getLine("l23").getTerminal1().disconnect(); + network.getLine("l23").getTerminal2().disconnect(); + List contingencies = Stream.of("l14") + .map(id -> new Contingency(id, new BranchContingency(id))) + .collect(Collectors.toList()); + + List actions = List.of(new TerminalsConnectionAction("closeLine", "l23", false)); + List operatorStrategies = List.of(new OperatorStrategy("strategyL1", ContingencyContext.specificContingency("l14"), new TrueCondition(), List.of("closeLine"))); + List monitors = createAllBranchesMonitors(network); + + LoadFlowParameters parameters = new LoadFlowParameters(); + parameters.setDistributedSlack(true); + parameters.setDc(true); + SecurityAnalysisParameters securityAnalysisParameters = new SecurityAnalysisParameters(); + securityAnalysisParameters.setLoadFlowParameters(parameters); + + SecurityAnalysisResult result = runSecurityAnalysis(network, contingencies, monitors, securityAnalysisParameters, + operatorStrategies, actions, Reporter.NO_OP); + OperatorStrategyResult dcStrategyResult = getOperatorStrategyResult(result, "strategyL1"); + + assertEquals(0.333, dcStrategyResult.getNetworkResult().getBranchResult("l12").getP1(), LoadFlowAssert.DELTA_POWER); + assertEquals(1.333, dcStrategyResult.getNetworkResult().getBranchResult("l23").getP1(), LoadFlowAssert.DELTA_POWER); + assertEquals(-1.0, dcStrategyResult.getNetworkResult().getBranchResult("l34").getP1(), LoadFlowAssert.DELTA_POWER); + + parameters.setDc(false); + SecurityAnalysisParameters securityAnalysisParametersAc = new SecurityAnalysisParameters(); + securityAnalysisParametersAc.setLoadFlowParameters(parameters); + SecurityAnalysisResult resultAc = runSecurityAnalysis(network, contingencies, monitors, securityAnalysisParametersAc, + operatorStrategies, actions, Reporter.NO_OP); + OperatorStrategyResult acStrategyResult = getOperatorStrategyResult(resultAc, "strategyL1"); + + assertEquals(0.336, acStrategyResult.getNetworkResult().getBranchResult("l12").getP1(), LoadFlowAssert.DELTA_POWER); + assertEquals(1.332, acStrategyResult.getNetworkResult().getBranchResult("l23").getP1(), LoadFlowAssert.DELTA_POWER); + assertEquals(-1.0, acStrategyResult.getNetworkResult().getBranchResult("l34").getP1(), LoadFlowAssert.DELTA_POWER); + } }