diff --git a/docs/security/inputs.md b/docs/security/inputs.md index ece9e22ee2..2c5704345c 100644 --- a/docs/security/inputs.md +++ b/docs/security/inputs.md @@ -14,5 +14,6 @@ With Open Load Flow only the following remedial actions are currently implemente - `ShuntCompensatorPositionAction` - `GeneratorAction` - `HvdcAction` +- `AreaInterchangeTargetAction` Note: Some limitations in the use of these actions exist, please read the documentation about the [security analysis specific parameters](parameters). diff --git a/src/main/java/com/powsybl/openloadflow/NetworkCache.java b/src/main/java/com/powsybl/openloadflow/NetworkCache.java index b2de5fcb6e..d6f7198b0c 100644 --- a/src/main/java/com/powsybl/openloadflow/NetworkCache.java +++ b/src/main/java/com/powsybl/openloadflow/NetworkCache.java @@ -18,6 +18,7 @@ import com.powsybl.openloadflow.ac.AcLoadFlowResult; import com.powsybl.openloadflow.ac.solver.AcSolverStatus; import com.powsybl.openloadflow.network.*; +import com.powsybl.openloadflow.network.action.AbstractLfBranchAction; import com.powsybl.openloadflow.network.impl.AbstractLfGenerator; import com.powsybl.openloadflow.network.impl.LfLegBranch; import com.powsybl.openloadflow.network.util.PreviousValueVoltageInitializer; @@ -239,7 +240,7 @@ private static void updateSwitch(boolean open, LfNetwork lfNetwork, LfBranch lfB } else { connectivity.addEdge(lfBranch.getBus1(), lfBranch.getBus2(), lfBranch); } - LfAction.updateBusesAndBranchStatus(connectivity); + AbstractLfBranchAction.updateBusesAndBranchStatus(connectivity); } finally { connectivity.undoTemporaryChanges(); } diff --git a/src/main/java/com/powsybl/openloadflow/ac/outerloop/AutomationSystemOuterLoop.java b/src/main/java/com/powsybl/openloadflow/ac/outerloop/AutomationSystemOuterLoop.java index 39e94dd0aa..49a0b23b2f 100644 --- a/src/main/java/com/powsybl/openloadflow/ac/outerloop/AutomationSystemOuterLoop.java +++ b/src/main/java/com/powsybl/openloadflow/ac/outerloop/AutomationSystemOuterLoop.java @@ -14,6 +14,7 @@ import com.powsybl.openloadflow.lf.outerloop.OuterLoopResult; import com.powsybl.openloadflow.lf.outerloop.OuterLoopStatus; import com.powsybl.openloadflow.network.*; +import com.powsybl.openloadflow.network.action.AbstractLfBranchAction; import com.powsybl.openloadflow.util.PerUnit; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -81,7 +82,7 @@ public OuterLoopResult check(AcOuterLoopContext context, ReportNode reportNode) GraphConnectivity connectivity = network.getConnectivity(); branchesToOpen.forEach(connectivity::removeEdge); branchesToClose.forEach(branch -> connectivity.addEdge(branch.getBus1(), branch.getBus2(), branch)); - LfAction.updateBusesAndBranchStatus(connectivity); + AbstractLfBranchAction.updateBusesAndBranchStatus(connectivity); network.getConnectivity().undoTemporaryChanges(); // we have now to really change the network connectivity. branchesToOpen.forEach(connectivity::removeEdge); diff --git a/src/main/java/com/powsybl/openloadflow/dc/fastdc/WoodburyEngine.java b/src/main/java/com/powsybl/openloadflow/dc/fastdc/WoodburyEngine.java index bc090d1e21..74267f9c57 100644 --- a/src/main/java/com/powsybl/openloadflow/dc/fastdc/WoodburyEngine.java +++ b/src/main/java/com/powsybl/openloadflow/dc/fastdc/WoodburyEngine.java @@ -19,6 +19,8 @@ import com.powsybl.openloadflow.dc.equations.DcEquationType; import com.powsybl.openloadflow.equations.Equation; import com.powsybl.openloadflow.network.*; +import com.powsybl.openloadflow.network.action.AbstractLfTapChangerAction; +import com.powsybl.openloadflow.network.action.LfAction; import java.util.*; @@ -112,7 +114,8 @@ public static double[] runDcLoadFlowWithModifiedTargetVector(DcLoadFlowContext l if (!pstActions.isEmpty()) { // set transformer phase shift to new shifting value pstActions.stream() - .map(LfAction::getTapPositionChange) + .filter(AbstractLfTapChangerAction.class::isInstance) + .map(action -> ((AbstractLfTapChangerAction) action).getChange()) .filter(Objects::nonNull) .forEach(tapPositionChange -> { LfBranch lfBranch = tapPositionChange.getBranch(); diff --git a/src/main/java/com/powsybl/openloadflow/network/LfAction.java b/src/main/java/com/powsybl/openloadflow/network/LfAction.java deleted file mode 100644 index f6f55330b6..0000000000 --- a/src/main/java/com/powsybl/openloadflow/network/LfAction.java +++ /dev/null @@ -1,363 +0,0 @@ -/** - * Copyright (c) 2022, 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/. - * SPDX-License-Identifier: MPL-2.0 - */ -package com.powsybl.openloadflow.network; - -import com.powsybl.action.*; -import com.powsybl.iidm.network.Bus; -import com.powsybl.iidm.network.Load; -import com.powsybl.iidm.network.Network; -import com.powsybl.iidm.network.Terminal; -import com.powsybl.openloadflow.graph.GraphConnectivity; -import com.powsybl.openloadflow.network.impl.LfLegBranch; -import com.powsybl.openloadflow.network.impl.LfShuntImpl; -import com.powsybl.openloadflow.network.impl.Networks; -import com.powsybl.openloadflow.util.PerUnit; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.*; - -/** - * @author Anne Tilloy {@literal } - * @author Jean-Luc Bouchot (Artelys) {@literal } - */ -public final class LfAction { - - private static final Logger LOGGER = LoggerFactory.getLogger(LfAction.class); - - private record LoadShift(String loadId, LfLoad lfLoad, PowerShift powerShift) { - } - - private record GeneratorChange(LfGenerator generator, double activePowerValue, boolean isRelative) { - } - - private record SectionChange(LfShunt shunt, String controllerId, int value) { - } - - private final String id; - - private final LfBranch disabledBranch; // switch to open - - private final LfBranch enabledBranch; // switch to close - - private final TapPositionChange tapPositionChange; - - private final LoadShift loadShift; - - private final GeneratorChange generatorChange; - - private final LfHvdc hvdc; - - private final SectionChange sectionChange; - - private LfAction(String id, LfBranch disabledBranch, LfBranch enabledBranch, TapPositionChange tapPositionChange, - LoadShift loadShift, GeneratorChange generatorChange, LfHvdc hvdc, SectionChange sectionChange) { - this.id = Objects.requireNonNull(id); - this.disabledBranch = disabledBranch; - this.enabledBranch = enabledBranch; - this.tapPositionChange = tapPositionChange; - this.loadShift = loadShift; - this.generatorChange = generatorChange; - this.hvdc = hvdc; - this.sectionChange = sectionChange; - } - - public static Optional create(Action action, LfNetwork lfNetwork, Network network, boolean breakers) { - Objects.requireNonNull(action); - Objects.requireNonNull(network); - switch (action.getType()) { - case SwitchAction.NAME: - return create((SwitchAction) action, lfNetwork); - - case TerminalsConnectionAction.NAME: - return create((TerminalsConnectionAction) action, lfNetwork); - - case PhaseTapChangerTapPositionAction.NAME: - return create((PhaseTapChangerTapPositionAction) action, lfNetwork); - - case RatioTapChangerTapPositionAction.NAME: - return create((RatioTapChangerTapPositionAction) action, lfNetwork); - - case LoadAction.NAME: - return create((LoadAction) action, lfNetwork, network, breakers); - - case GeneratorAction.NAME: - return create((GeneratorAction) action, lfNetwork); - - case HvdcAction.NAME: - return create((HvdcAction) action, lfNetwork); - - case ShuntCompensatorPositionAction.NAME: - return create((ShuntCompensatorPositionAction) action, lfNetwork); - - default: - throw new UnsupportedOperationException("Unsupported action type: " + action.getType()); - } - } - - private static Optional create(ShuntCompensatorPositionAction action, LfNetwork lfNetwork) { - LfShunt shunt = lfNetwork.getShuntById(action.getShuntCompensatorId()); - if (shunt instanceof LfShuntImpl) { // no svc here - if (shunt.getVoltageControl().isPresent()) { - LOGGER.warn("Shunt compensator position action: voltage control is present on the shunt, section could be overridden."); - } - var sectionChange = new SectionChange(shunt, action.getShuntCompensatorId(), action.getSectionCount()); - return Optional.of(new LfAction(action.getId(), null, null, null, null, null, null, sectionChange)); - } - return Optional.empty(); // could be in another component - } - - private static Optional create(HvdcAction action, LfNetwork lfNetwork) { - // as a first approach, we only support an action that switches an hvdc operated in AC emulation into an active power - // set point operation mode. - LfHvdc lfHvdc = lfNetwork.getHvdcById(action.getHvdcId()); - Optional acEmulationEnabled = action.isAcEmulationEnabled(); - if (lfHvdc != null && acEmulationEnabled.isPresent()) { - if (acEmulationEnabled.get().equals(Boolean.TRUE)) { // the operation mode remains AC emulation. - throw new UnsupportedOperationException("Hvdc action: line is already in AC emulation, not supported yet."); - } else { // the operation mode changes from AC emulation to fixed active power set point. - return Optional.of(new LfAction(action.getId(), null, null, null, null, null, lfHvdc, null)); - } - } - LOGGER.warn("Hvdc action {}: not supported", action.getId()); - return Optional.empty(); // could be in another component or not operated in AC emulation - } - - private static Optional create(LoadAction action, LfNetwork lfNetwork, Network network, boolean breakers) { - Load load = network.getLoad(action.getLoadId()); - Terminal terminal = load.getTerminal(); - Bus bus = Networks.getBus(terminal, breakers); - if (bus != null) { - LfLoad lfLoad = lfNetwork.getLoadById(load.getId()); - if (lfLoad != null) { - PowerShift powerShift = PowerShift.createPowerShift(load, action); - return Optional.of(new LfAction(action.getId(), null, null, null, new LoadShift(load.getId(), lfLoad, powerShift), null, null, null)); - } - } - return Optional.empty(); // could be in another component or in contingency. - } - - private static Optional create(PhaseTapChangerTapPositionAction action, LfNetwork lfNetwork) { - String branchId = action.getSide().map(side -> LfLegBranch.getId(side, action.getTransformerId())).orElseGet(action::getTransformerId); - LfBranch branch = lfNetwork.getBranchById(branchId); - if (branch != null && branch.getPhaseControl().isPresent()) { - LOGGER.warn("Phase tap changer tap position action: phase control is present on the tap changer, tap position could be overriden."); - } - return create(branch, action.getId(), action.isRelativeValue(), action.getTapPosition(), lfNetwork); - } - - private static Optional create(RatioTapChangerTapPositionAction action, LfNetwork lfNetwork) { - String branchId = action.getSide().map(side -> LfLegBranch.getId(side, action.getTransformerId())).orElseGet(action::getTransformerId); - LfBranch branch = lfNetwork.getBranchById(branchId); - if (branch != null && branch.getVoltageControl().isPresent()) { - LOGGER.warn("Ratio tap changer tap position action: voltage control is present on the tap changer, tap position could be overriden."); - } - return create(branch, action.getId(), action.isRelativeValue(), action.getTapPosition(), lfNetwork); - } - - private static Optional create(LfBranch branch, String actionId, boolean isRelative, int tapPosition, LfNetwork lfNetwork) { - if (branch != null) { - if (branch.getPiModel() instanceof SimplePiModel) { - throw new UnsupportedOperationException("Tap position action: only one tap in branch " + branch.getId()); - } else { - var tapPositionChange = new TapPositionChange(branch, tapPosition, isRelative); - return Optional.of(new LfAction(actionId, null, null, tapPositionChange, null, null, null, null)); - } - } - return Optional.empty(); // could be in another component - } - - private static Optional create(TerminalsConnectionAction action, LfNetwork lfNetwork) { - LfBranch branch = lfNetwork.getBranchById(action.getElementId()); - if (branch != null && branch.getBus1() != null && branch.getBus2() != null) { - if (action.getSide().isEmpty()) { - if (action.isOpen()) { - return Optional.of(new LfAction(action.getId(), branch, null, null, null, null, null, null)); - } else { - return Optional.of(new LfAction(action.getId(), null, branch, null, null, null, null, null)); - } - } else { - 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 - } - - private static Optional create(SwitchAction action, LfNetwork lfNetwork) { - LfBranch branch = lfNetwork.getBranchById(action.getSwitchId()); - if (branch != null) { - LfBranch disabledBranch = null; - LfBranch enabledBranch = null; - if (action.isOpen()) { - disabledBranch = branch; - } else { - enabledBranch = branch; - } - return Optional.of(new LfAction(action.getId(), disabledBranch, enabledBranch, null, null, null, null, null)); - } - return Optional.empty(); // could be in another component - } - - private static Optional create(GeneratorAction action, LfNetwork lfNetwork) { - LfGenerator generator = lfNetwork.getGeneratorById(action.getGeneratorId()); - if (generator != null) { - OptionalDouble activePowerValue = action.getActivePowerValue(); - Optional relativeValue = action.isActivePowerRelativeValue(); - if (relativeValue.isPresent() && activePowerValue.isPresent()) { - var generatorChange = new GeneratorChange(generator, activePowerValue.getAsDouble() / PerUnit.SB, relativeValue.get()); - return Optional.of(new LfAction(action.getId(), null, null, null, null, generatorChange, null, null)); - } else { - throw new UnsupportedOperationException("Generator action on " + action.getGeneratorId() + " : configuration not supported yet."); - } - } - return Optional.empty(); - } - - public String getId() { - return id; - } - - public LfBranch getDisabledBranch() { - return disabledBranch; - } - - public LfBranch getEnabledBranch() { - return enabledBranch; - } - - public TapPositionChange getTapPositionChange() { - return tapPositionChange; - } - - public static void apply(List actions, LfNetwork network, LfContingency contingency, LfNetworkParameters networkParameters) { - Objects.requireNonNull(actions); - Objects.requireNonNull(network); - - // first process connectivity part of actions - updateConnectivity(actions, network, contingency); - - // then process remaining changes of actions - for (LfAction action : actions) { - action.apply(networkParameters); - } - } - - private static void updateConnectivity(List actions, LfNetwork network, LfContingency contingency) { - GraphConnectivity connectivity = network.getConnectivity(); - - // re-update connectivity according to post contingency state (revert after LfContingency apply) - connectivity.startTemporaryChanges(); - contingency.getDisabledNetwork().getBranches().forEach(connectivity::removeEdge); - - // update connectivity according to post action state - connectivity.startTemporaryChanges(); - for (LfAction action : actions) { - action.updateConnectivity(connectivity); - } - - updateBusesAndBranchStatus(connectivity); - - // reset connectivity to discard post contingency connectivity and post action connectivity - connectivity.undoTemporaryChanges(); - connectivity.undoTemporaryChanges(); - } - - public static void updateBusesAndBranchStatus(GraphConnectivity connectivity) { - // disable buses and branches that won't be part of the main connected component - Set removedBuses = connectivity.getVerticesRemovedFromMainComponent(); - removedBuses.forEach(bus -> bus.setDisabled(true)); - Set removedBranches = new HashSet<>(connectivity.getEdgesRemovedFromMainComponent()); - // we should manage branches open at one side. - for (LfBus bus : removedBuses) { - bus.getBranches().stream().filter(b -> !b.isConnectedAtBothSides()).forEach(removedBranches::add); - } - removedBranches.forEach(branch -> branch.setDisabled(true)); - - // enable buses and branches that will be part of the main connected component - Set addedBuses = connectivity.getVerticesAddedToMainComponent(); - addedBuses.forEach(bus -> bus.setDisabled(false)); - Set addedBranches = new HashSet<>(connectivity.getEdgesAddedToMainComponent()); - // we should manage branches open at one side. - for (LfBus bus : addedBuses) { - bus.getBranches().stream().filter(b -> !b.isConnectedAtBothSides()).forEach(addedBranches::add); - } - addedBranches.forEach(branch -> branch.setDisabled(false)); - } - - public void updateConnectivity(GraphConnectivity connectivity) { - if (disabledBranch != null && disabledBranch.getBus1() != null && disabledBranch.getBus2() != null) { - connectivity.removeEdge(disabledBranch); - } - if (enabledBranch != null) { - connectivity.addEdge(enabledBranch.getBus1(), enabledBranch.getBus2(), enabledBranch); - } - } - - private void applyTapPositionChange() { - LfBranch branch = tapPositionChange.getBranch(); - int newTapPosition = tapPositionChange.getNewTapPosition(); - branch.getPiModel().setTapPosition(newTapPosition); - } - - private void applyLoadShift() { - String loadId = loadShift.loadId(); - LfLoad lfLoad = loadShift.lfLoad(); - if (!lfLoad.isOriginalLoadDisabled(loadId)) { - PowerShift shift = loadShift.powerShift(); - lfLoad.setTargetP(lfLoad.getTargetP() + shift.getActive()); - lfLoad.setTargetQ(lfLoad.getTargetQ() + shift.getReactive()); - lfLoad.setAbsVariableTargetP(lfLoad.getAbsVariableTargetP() + Math.signum(shift.getActive()) * Math.abs(shift.getVariableActive())); - } - } - - private void applyGeneratorChange(LfNetworkParameters networkParameters) { - LfGenerator generator = generatorChange.generator(); - if (!generator.isDisabled()) { - double newTargetP = generatorChange.isRelative() ? generator.getTargetP() + generatorChange.activePowerValue() : generatorChange.activePowerValue(); - generator.setTargetP(newTargetP); - generator.setInitialTargetP(newTargetP); - generator.reApplyActivePowerControlChecks(networkParameters, null); - } - } - - private void applyHvdcAction() { - hvdc.setAcEmulation(false); - hvdc.setDisabled(true); // for equations only, but should be hidden - hvdc.getConverterStation1().setTargetP(-hvdc.getP1().eval()); // override - hvdc.getConverterStation2().setTargetP(-hvdc.getP2().eval()); // override - } - - private void applySectionChange() { - LfShunt shunt = sectionChange.shunt(); - shunt.getControllers().stream().filter(controller -> controller.getId().equals(sectionChange.controllerId())).findAny() - .ifPresentOrElse(controller -> controller.updateSectionB(sectionChange.value()), - () -> LOGGER.warn("No section change: shunt {} not present", sectionChange.controllerId)); - } - - public void apply(LfNetworkParameters networkParameters) { - if (tapPositionChange != null) { - applyTapPositionChange(); - } - - if (loadShift != null) { - applyLoadShift(); - } - - if (generatorChange != null) { - applyGeneratorChange(networkParameters); - } - - if (hvdc != null) { - applyHvdcAction(); - } - - if (sectionChange != null) { - applySectionChange(); - } - } -} diff --git a/src/main/java/com/powsybl/openloadflow/network/action/AbstractLfAction.java b/src/main/java/com/powsybl/openloadflow/network/action/AbstractLfAction.java new file mode 100644 index 0000000000..a548f12b56 --- /dev/null +++ b/src/main/java/com/powsybl/openloadflow/network/action/AbstractLfAction.java @@ -0,0 +1,30 @@ +/** + * Copyright (c) 2025, Coreso SA (https://www.coreso.eu/) and TSCNET Services GmbH (https://www.tscnet.eu/) + * 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/. + * SPDX-License-Identifier: MPL-2.0 + */ +package com.powsybl.openloadflow.network.action; + +import com.powsybl.action.Action; + +/** + * @author Bertrand Rix {@literal } + */ +public abstract class AbstractLfAction implements LfAction { + + protected final String id; + + protected final A action; + + AbstractLfAction(String id, A action) { + this.id = id; + this.action = action; + } + + @Override + public String getId() { + return id; + } +} diff --git a/src/main/java/com/powsybl/openloadflow/network/action/AbstractLfBranchAction.java b/src/main/java/com/powsybl/openloadflow/network/action/AbstractLfBranchAction.java new file mode 100644 index 0000000000..b0238df9a0 --- /dev/null +++ b/src/main/java/com/powsybl/openloadflow/network/action/AbstractLfBranchAction.java @@ -0,0 +1,119 @@ +/** + * Copyright (c) 2025, Coreso SA (https://www.coreso.eu/) and TSCNET Services GmbH (https://www.tscnet.eu/) + * Copyright (c) 2022, 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/. + * SPDX-License-Identifier: MPL-2.0 + */ +package com.powsybl.openloadflow.network.action; + +import com.powsybl.action.Action; +import com.powsybl.openloadflow.graph.GraphConnectivity; +import com.powsybl.openloadflow.network.*; + +import java.util.HashSet; +import java.util.Set; + +/** + * @author Bertrand Rix {@literal } + * @author Anne Tilloy {@literal } + * @author Jean-Luc Bouchot {@literal } + */ +public abstract class AbstractLfBranchAction extends AbstractLfAction { + + private LfBranch disabledBranch = null; // switch to open + + private LfBranch enabledBranch = null; // switch to close + + AbstractLfBranchAction(String id, A action) { + super(id, action); + } + + protected void setDisabledBranch(LfBranch disabledBranch) { + this.disabledBranch = disabledBranch; + } + + protected void setEnabledBranch(LfBranch enabledBranch) { + this.enabledBranch = enabledBranch; + } + + public LfBranch getDisabledBranch() { + return this.disabledBranch; + } + + public LfBranch getEnabledBranch() { + return this.enabledBranch; + } + + abstract boolean findEnabledDisabledBranches(LfNetwork lfNetwork); + + /** + * Standalone apply + */ + @Override + public boolean apply(LfNetwork network, LfContingency contingency, LfNetworkParameters networkParameters) { + boolean found = findEnabledDisabledBranches(network); + if (!found) { + return false; + } + GraphConnectivity connectivity = network.getConnectivity(); + + // re-update connectivity according to post contingency state (revert after LfContingency apply) + connectivity.startTemporaryChanges(); + if (contingency != null) { + contingency.getDisabledNetwork().getBranches().forEach(connectivity::removeEdge); + } + + // update connectivity according to post action state + connectivity.startTemporaryChanges(); + + updateConnectivity(connectivity); + updateBusesAndBranchStatus(connectivity); + + // reset connectivity to discard post contingency connectivity and post action connectivity + connectivity.undoTemporaryChanges(); + connectivity.undoTemporaryChanges(); + return true; + } + + /** + * Optimized apply on an existing connectivity (to apply several branch actions at the same time) + */ + public boolean applyOnConnectivity(LfNetwork network, GraphConnectivity connectivity) { + boolean found = findEnabledDisabledBranches(network); + updateConnectivity(connectivity); + return found; + } + + private void updateConnectivity(GraphConnectivity connectivity) { + if (disabledBranch != null && disabledBranch.getBus1() != null && disabledBranch.getBus2() != null) { + connectivity.removeEdge(disabledBranch); + } + if (enabledBranch != null) { + connectivity.addEdge(enabledBranch.getBus1(), enabledBranch.getBus2(), enabledBranch); + } + } + + public static void updateBusesAndBranchStatus(GraphConnectivity connectivity) { + // disable buses and branches that won't be part of the main connected component + Set removedBuses = connectivity.getVerticesRemovedFromMainComponent(); + removedBuses.forEach(bus -> bus.setDisabled(true)); + Set removedBranches = new HashSet<>(connectivity.getEdgesRemovedFromMainComponent()); + // we should manage branches open at one side. + for (LfBus bus : removedBuses) { + bus.getBranches().stream().filter(b -> !b.isConnectedAtBothSides()).forEach(removedBranches::add); + } + removedBranches.forEach(branch -> branch.setDisabled(true)); + + // enable buses and branches that will be part of the main connected component + Set addedBuses = connectivity.getVerticesAddedToMainComponent(); + addedBuses.forEach(bus -> bus.setDisabled(false)); + Set addedBranches = new HashSet<>(connectivity.getEdgesAddedToMainComponent()); + // we should manage branches open at one side. + for (LfBus bus : addedBuses) { + bus.getBranches().stream().filter(b -> !b.isConnectedAtBothSides()).forEach(addedBranches::add); + } + addedBranches.forEach(branch -> branch.setDisabled(false)); + } +} diff --git a/src/main/java/com/powsybl/openloadflow/network/action/AbstractLfTapChangerAction.java b/src/main/java/com/powsybl/openloadflow/network/action/AbstractLfTapChangerAction.java new file mode 100644 index 0000000000..e885b8eaff --- /dev/null +++ b/src/main/java/com/powsybl/openloadflow/network/action/AbstractLfTapChangerAction.java @@ -0,0 +1,41 @@ +/** + * Copyright (c) 2025, Coreso SA (https://www.coreso.eu/) and TSCNET Services GmbH (https://www.tscnet.eu/) + * Copyright (c) 2022, 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/. + * SPDX-License-Identifier: MPL-2.0 + */ +package com.powsybl.openloadflow.network.action; + +import com.powsybl.action.AbstractTapChangerTapPositionAction; +import com.powsybl.openloadflow.network.*; +import com.powsybl.openloadflow.network.impl.LfLegBranch; + +/** + * @author Bertrand Rix {@literal } + * @author Anne Tilloy {@literal } + * @author Jean-Luc Bouchot {@literal } + */ +public abstract class AbstractLfTapChangerAction extends AbstractLfAction { + + protected TapPositionChange change; + + protected LfBranch branch; + + AbstractLfTapChangerAction(String id, A action, LfNetwork network) { + super(id, action); + String branchId = action.getSide().map(side -> LfLegBranch.getId(side, action.getTransformerId())).orElseGet(action::getTransformerId); + this.branch = network.getBranchById(branchId); + if (this.branch != null) { + if (branch.getPiModel() instanceof SimplePiModel) { + throw new UnsupportedOperationException("Tap position action: only one tap in branch " + branch.getId()); + } + this.change = new TapPositionChange(branch, action.getTapPosition(), action.isRelativeValue()); + } + } + + public TapPositionChange getChange() { + return change; + } +} diff --git a/src/main/java/com/powsybl/openloadflow/network/action/LfAction.java b/src/main/java/com/powsybl/openloadflow/network/action/LfAction.java new file mode 100644 index 0000000000..b4c014243c --- /dev/null +++ b/src/main/java/com/powsybl/openloadflow/network/action/LfAction.java @@ -0,0 +1,20 @@ +/** + * Copyright (c) 2025, Coreso SA (https://www.coreso.eu/) and TSCNET Services GmbH (https://www.tscnet.eu/) + * 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/. + * SPDX-License-Identifier: MPL-2.0 + */ +package com.powsybl.openloadflow.network.action; + +import com.powsybl.openloadflow.network.*; + +/** + * @author Bertrand Rix {@literal } + */ +public interface LfAction { + + String getId(); + + boolean apply(LfNetwork network, LfContingency contingency, LfNetworkParameters networkParameters); +} diff --git a/src/main/java/com/powsybl/openloadflow/network/action/LfActionUtils.java b/src/main/java/com/powsybl/openloadflow/network/action/LfActionUtils.java new file mode 100644 index 0000000000..180c76e03b --- /dev/null +++ b/src/main/java/com/powsybl/openloadflow/network/action/LfActionUtils.java @@ -0,0 +1,109 @@ +/** + * Copyright (c) 2025, Coreso SA (https://www.coreso.eu/) and TSCNET Services GmbH (https://www.tscnet.eu/) + * Copyright (c) 2022, 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/. + * SPDX-License-Identifier: MPL-2.0 + */ +package com.powsybl.openloadflow.network.action; + +import com.powsybl.action.*; +import com.powsybl.commons.report.ReportNode; +import com.powsybl.iidm.network.Network; +import com.powsybl.openloadflow.graph.GraphConnectivity; +import com.powsybl.openloadflow.network.*; + +import java.util.*; + +import static com.powsybl.openloadflow.network.action.AbstractLfBranchAction.updateBusesAndBranchStatus; + +/** + * @author Bertrand Rix {@literal } + * @author Anne Tilloy {@literal } + * @author Jean-Luc Bouchot {@literal } + */ +public final class LfActionUtils { + + private static final String ACTION_ID = "actionId"; + + private static final String CONTINGENCY_ID = "contingencyId"; + + private LfActionUtils() { + } + + public static LfAction createLfAction(Action action, Network network, boolean breakers, LfNetwork lfNetwork) { + Objects.requireNonNull(action); + Objects.requireNonNull(network); + return switch (action.getType()) { + case SwitchAction.NAME -> new LfSwitchAction(action.getId(), (SwitchAction) action); + case TerminalsConnectionAction.NAME -> + new LfTerminalsConnectionAction(action.getId(), (TerminalsConnectionAction) action); + case PhaseTapChangerTapPositionAction.NAME -> + new LfPhaseTapChangerAction(action.getId(), (PhaseTapChangerTapPositionAction) action, lfNetwork); + case RatioTapChangerTapPositionAction.NAME -> + new LfRatioTapChangerAction(action.getId(), (RatioTapChangerTapPositionAction) action, lfNetwork); + case LoadAction.NAME -> + new LfLoadAction(action.getId(), (LoadAction) action, network, breakers); + case GeneratorAction.NAME -> new LfGeneratorAction(action.getId(), (GeneratorAction) action, lfNetwork); + case HvdcAction.NAME -> new LfHvdcAction(action.getId(), (HvdcAction) action); + case ShuntCompensatorPositionAction.NAME -> + new LfShuntCompensatorPositionAction(action.getId(), (ShuntCompensatorPositionAction) action); + case AreaInterchangeTargetAction.NAME -> + new LfAreaInterchangeTargetAction(action.getId(), (AreaInterchangeTargetAction) action); + default -> throw new UnsupportedOperationException("Unsupported action type: " + action.getType()); + }; + } + + public static void applyListOfActions(List actions, LfNetwork network, LfContingency contingency, LfNetworkParameters networkParameters, ReportNode node) { + Objects.requireNonNull(actions); + Objects.requireNonNull(network); + + // first apply action modifying connectivity + List branchActions = actions.stream() + .filter(action -> action instanceof AbstractLfBranchAction) + .toList(); + updateConnectivity(branchActions, network, contingency, node); + + // then process remaining changes of actions + actions.stream() + .filter(action -> !(action instanceof AbstractLfBranchAction)) + .forEach(action -> { + if (!action.apply(network, contingency, networkParameters)) { + reportActionApplicationFailure(action.getId(), contingency.getId(), node); + } + }); + } + + private static void updateConnectivity(List branchActions, LfNetwork network, LfContingency contingency, ReportNode node) { + GraphConnectivity connectivity = network.getConnectivity(); + + // re-update connectivity according to post contingency state (revert after LfContingency apply) + connectivity.startTemporaryChanges(); + contingency.getDisabledNetwork().getBranches().forEach(connectivity::removeEdge); + + // update connectivity according to post action state + connectivity.startTemporaryChanges(); + + branchActions.forEach(action -> { + if (!((AbstractLfBranchAction) action).applyOnConnectivity(network, connectivity)) { + reportActionApplicationFailure(action.getId(), contingency.getId(), node); + } + }); + + updateBusesAndBranchStatus(connectivity); + + // reset connectivity to discard post contingency connectivity and post action connectivity + connectivity.undoTemporaryChanges(); + connectivity.undoTemporaryChanges(); + } + + private static void reportActionApplicationFailure(String actionId, String contingencyId, ReportNode node) { + node.newReportNode() + .withMessageTemplate("LfActionUtils", "Action '${actionId}': may not have been applied successfully on contingency '${contingencyId}'") + .withUntypedValue(ACTION_ID, actionId) + .withUntypedValue(CONTINGENCY_ID, contingencyId) + .add(); + } + +} diff --git a/src/main/java/com/powsybl/openloadflow/network/action/LfAreaInterchangeTargetAction.java b/src/main/java/com/powsybl/openloadflow/network/action/LfAreaInterchangeTargetAction.java new file mode 100644 index 0000000000..4ef627f945 --- /dev/null +++ b/src/main/java/com/powsybl/openloadflow/network/action/LfAreaInterchangeTargetAction.java @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2025, Coreso SA (https://www.coreso.eu/) and TSCNET Services GmbH (https://www.tscnet.eu/) + * 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/. + * SPDX-License-Identifier: MPL-2.0 + */ +package com.powsybl.openloadflow.network.action; + +import com.powsybl.action.AreaInterchangeTargetAction; +import com.powsybl.openloadflow.network.*; +import com.powsybl.openloadflow.util.PerUnit; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Bertrand Rix {@literal } + */ +public class LfAreaInterchangeTargetAction extends AbstractLfAction { + + private static final Logger LOGGER = LoggerFactory.getLogger(LfAreaInterchangeTargetAction.class); + + public LfAreaInterchangeTargetAction(String id, AreaInterchangeTargetAction action) { + super(id, action); + } + + @Override + public boolean apply(LfNetwork network, LfContingency contingency, LfNetworkParameters networkParameters) { + if (!networkParameters.isAreaInterchangeControl()) { + LOGGER.warn("AreaInterchangeTargetAction action {}: area interchange control is disabled", action.getId()); + } + LfArea area = network.getAreaById(action.getAreaId()); + if (area != null) { + area.setInterchangeTarget(action.getInterchangeTarget() / PerUnit.SB); + return true; + } + return false; + } +} diff --git a/src/main/java/com/powsybl/openloadflow/network/action/LfGeneratorAction.java b/src/main/java/com/powsybl/openloadflow/network/action/LfGeneratorAction.java new file mode 100644 index 0000000000..0a8e0857ea --- /dev/null +++ b/src/main/java/com/powsybl/openloadflow/network/action/LfGeneratorAction.java @@ -0,0 +1,55 @@ +/** + * Copyright (c) 2025, Coreso SA (https://www.coreso.eu/) and TSCNET Services GmbH (https://www.tscnet.eu/) + * Copyright (c) 2022, 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/. + * SPDX-License-Identifier: MPL-2.0 + */ +package com.powsybl.openloadflow.network.action; + +import com.powsybl.action.GeneratorAction; +import com.powsybl.openloadflow.network.*; +import com.powsybl.openloadflow.util.PerUnit; + +import java.util.Optional; +import java.util.OptionalDouble; + +/** + * @author Bertrand Rix {@literal } + * @author Anne Tilloy {@literal } + * @author Jean-Luc Bouchot {@literal } + */ +public class LfGeneratorAction extends AbstractLfAction { + + private record GeneratorChange(double change, boolean isRelative) { } + + private GeneratorChange generatorChange; + + public LfGeneratorAction(String id, GeneratorAction action, LfNetwork lfNetwork) { + super(id, action); + LfGenerator generator = lfNetwork.getGeneratorById(action.getGeneratorId()); + if (generator != null) { + OptionalDouble activePowerValue = action.getActivePowerValue(); + Optional relativeValue = action.isActivePowerRelativeValue(); + if (relativeValue.isEmpty() || activePowerValue.isEmpty()) { + throw new UnsupportedOperationException("Generator action on " + action.getGeneratorId() + " : configuration not supported yet."); + } else { + generatorChange = new GeneratorChange(activePowerValue.getAsDouble() / PerUnit.SB, relativeValue.get()); + } + } + } + + @Override + public boolean apply(LfNetwork network, LfContingency contingency, LfNetworkParameters networkParameters) { + LfGenerator generator = network.getGeneratorById(action.getGeneratorId()); + if (generator != null && !generator.isDisabled()) { + double newTargetP = generatorChange.isRelative() ? generator.getTargetP() + generatorChange.change() : generatorChange.change(); + generator.setTargetP(newTargetP); + generator.setInitialTargetP(newTargetP); + generator.reApplyActivePowerControlChecks(networkParameters, null); + return true; + } + return false; + } +} diff --git a/src/main/java/com/powsybl/openloadflow/network/action/LfHvdcAction.java b/src/main/java/com/powsybl/openloadflow/network/action/LfHvdcAction.java new file mode 100644 index 0000000000..7b3bb21917 --- /dev/null +++ b/src/main/java/com/powsybl/openloadflow/network/action/LfHvdcAction.java @@ -0,0 +1,56 @@ +/** + * Copyright (c) 2025, Coreso SA (https://www.coreso.eu/) and TSCNET Services GmbH (https://www.tscnet.eu/) + * Copyright (c) 2022, 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/. + * SPDX-License-Identifier: MPL-2.0 + */ +package com.powsybl.openloadflow.network.action; + +import com.powsybl.action.HvdcAction; +import com.powsybl.openloadflow.network.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Optional; + +/** + * @author Bertrand Rix {@literal } + * @author Anne Tilloy {@literal } + * @author Jean-Luc Bouchot {@literal } + */ +public class LfHvdcAction extends AbstractLfAction { + + private static final Logger LOGGER = LoggerFactory.getLogger(LfHvdcAction.class); + + public LfHvdcAction(String id, HvdcAction action) { + super(id, action); + Optional acEmulationEnabled = action.isAcEmulationEnabled(); + // As a first approach, we only support an action that switches an hvdc operated in AC emulation + // into an active power set point operation mode. + if (acEmulationEnabled.isPresent() && acEmulationEnabled.get().equals(Boolean.TRUE)) { + throw new UnsupportedOperationException("Hvdc action: enabling ac emulation mode through an action is not supported yet."); + } + } + + @Override + public boolean apply(LfNetwork network, LfContingency contingency, LfNetworkParameters networkParameters) { + LfHvdc lfHvdc = network.getHvdcById(action.getHvdcId()); + if (lfHvdc != null) { + if (action.isAcEmulationEnabled().isEmpty()) { + LOGGER.warn("Hvdc action {}: only explicitly disabling ac emulation is supported.", action.getId()); + return false; + } + // the operation mode changes from AC emulation to fixed active power set point. + lfHvdc.setAcEmulation(false); + lfHvdc.setDisabled(true); // for equations only, but should be hidden + lfHvdc.getConverterStation1().setTargetP(-lfHvdc.getP1().eval()); // override + lfHvdc.getConverterStation2().setTargetP(-lfHvdc.getP2().eval()); // override + return true; + } else { + LOGGER.warn("Hvdc action {}: hvdc line not found", action.getId()); + } + return false; + } +} diff --git a/src/main/java/com/powsybl/openloadflow/network/action/LfLoadAction.java b/src/main/java/com/powsybl/openloadflow/network/action/LfLoadAction.java new file mode 100644 index 0000000000..62ba0334ab --- /dev/null +++ b/src/main/java/com/powsybl/openloadflow/network/action/LfLoadAction.java @@ -0,0 +1,55 @@ +/** + * Copyright (c) 2025, Coreso SA (https://www.coreso.eu/) and TSCNET Services GmbH (https://www.tscnet.eu/) + * Copyright (c) 2022, 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/. + * SPDX-License-Identifier: MPL-2.0 + */ +package com.powsybl.openloadflow.network.action; + +import com.powsybl.action.LoadAction; +import com.powsybl.iidm.network.Bus; +import com.powsybl.iidm.network.Load; +import com.powsybl.iidm.network.Network; +import com.powsybl.iidm.network.Terminal; +import com.powsybl.openloadflow.network.*; +import com.powsybl.openloadflow.network.impl.Networks; + +/** + * @author Bertrand Rix {@literal } + * @author Anne Tilloy {@literal } + * @author Jean-Luc Bouchot {@literal } + */ +public class LfLoadAction extends AbstractLfAction { + + private final Network network; + + private final boolean breakers; + + public LfLoadAction(String id, LoadAction action, Network network, boolean breakers) { + super(id, action); + this.network = network; + this.breakers = breakers; + } + + @Override + public boolean apply(LfNetwork lfNetwork, LfContingency contingency, LfNetworkParameters networkParameters) { + Load load = network.getLoad(action.getLoadId()); + Terminal terminal = load.getTerminal(); + Bus bus = Networks.getBus(terminal, breakers); + if (bus != null) { + LfLoad lfLoad = lfNetwork.getLoadById(load.getId()); + if (lfLoad != null) { + PowerShift powerShift = PowerShift.createPowerShift(load, action); + if (!lfLoad.isOriginalLoadDisabled(load.getId())) { + lfLoad.setTargetP(lfLoad.getTargetP() + powerShift.getActive()); + lfLoad.setTargetQ(lfLoad.getTargetQ() + powerShift.getReactive()); + lfLoad.setAbsVariableTargetP(lfLoad.getAbsVariableTargetP() + Math.signum(powerShift.getActive()) * Math.abs(powerShift.getVariableActive())); + return true; + } + } + } + return false; + } +} diff --git a/src/main/java/com/powsybl/openloadflow/network/action/LfPhaseTapChangerAction.java b/src/main/java/com/powsybl/openloadflow/network/action/LfPhaseTapChangerAction.java new file mode 100644 index 0000000000..3361706d71 --- /dev/null +++ b/src/main/java/com/powsybl/openloadflow/network/action/LfPhaseTapChangerAction.java @@ -0,0 +1,40 @@ +/** + * Copyright (c) 2025, Coreso SA (https://www.coreso.eu/) and TSCNET Services GmbH (https://www.tscnet.eu/) + * Copyright (c) 2022, 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/. + * SPDX-License-Identifier: MPL-2.0 + */ +package com.powsybl.openloadflow.network.action; + +import com.powsybl.action.PhaseTapChangerTapPositionAction; +import com.powsybl.openloadflow.network.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Bertrand Rix {@literal } + * @author Anne Tilloy {@literal } + * @author Jean-Luc Bouchot {@literal } + */ +public class LfPhaseTapChangerAction extends AbstractLfTapChangerAction { + + private static final Logger LOGGER = LoggerFactory.getLogger(LfPhaseTapChangerAction.class); + + public LfPhaseTapChangerAction(String id, PhaseTapChangerTapPositionAction action, LfNetwork network) { + super(id, action, network); + } + + @Override + public boolean apply(LfNetwork network, LfContingency contingency, LfNetworkParameters networkParameters) { + if (branch != null) { + if (branch.getPhaseControl().isPresent()) { + LOGGER.warn("Phase tap changer tap position action: phase control is present on the tap changer, tap position could be overriden."); + } + branch.getPiModel().setTapPosition(this.change.getNewTapPosition()); + return true; + } + return false; + } +} diff --git a/src/main/java/com/powsybl/openloadflow/network/action/LfRatioTapChangerAction.java b/src/main/java/com/powsybl/openloadflow/network/action/LfRatioTapChangerAction.java new file mode 100644 index 0000000000..442fd631bb --- /dev/null +++ b/src/main/java/com/powsybl/openloadflow/network/action/LfRatioTapChangerAction.java @@ -0,0 +1,40 @@ +/** + * Copyright (c) 2025, Coreso SA (https://www.coreso.eu/) and TSCNET Services GmbH (https://www.tscnet.eu/) + * Copyright (c) 2022, 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/. + * SPDX-License-Identifier: MPL-2.0 + */ +package com.powsybl.openloadflow.network.action; + +import com.powsybl.action.RatioTapChangerTapPositionAction; +import com.powsybl.openloadflow.network.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Bertrand Rix {@literal } + * @author Anne Tilloy {@literal } + * @author Jean-Luc Bouchot {@literal } + */ +public class LfRatioTapChangerAction extends AbstractLfTapChangerAction { + + private static final Logger LOGGER = LoggerFactory.getLogger(LfRatioTapChangerAction.class); + + public LfRatioTapChangerAction(String id, RatioTapChangerTapPositionAction action, LfNetwork network) { + super(id, action, network); + } + + @Override + public boolean apply(LfNetwork network, LfContingency contingency, LfNetworkParameters networkParameters) { + if (this.branch != null) { + if (branch.getVoltageControl().isPresent()) { + LOGGER.warn("Ratio tap changer tap position action: voltage control is present on the tap changer, tap position could be overriden."); + } + branch.getPiModel().setTapPosition(this.change.getNewTapPosition()); + return true; + } + return false; + } +} diff --git a/src/main/java/com/powsybl/openloadflow/network/action/LfShuntCompensatorPositionAction.java b/src/main/java/com/powsybl/openloadflow/network/action/LfShuntCompensatorPositionAction.java new file mode 100644 index 0000000000..df34dbe423 --- /dev/null +++ b/src/main/java/com/powsybl/openloadflow/network/action/LfShuntCompensatorPositionAction.java @@ -0,0 +1,51 @@ +/** + * Copyright (c) 2025, Coreso SA (https://www.coreso.eu/) and TSCNET Services GmbH (https://www.tscnet.eu/) + * Copyright (c) 2022, 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/. + * SPDX-License-Identifier: MPL-2.0 + */ +package com.powsybl.openloadflow.network.action; + +import com.powsybl.action.ShuntCompensatorPositionAction; +import com.powsybl.openloadflow.network.*; +import com.powsybl.openloadflow.network.impl.LfShuntImpl; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Optional; + +/** + * @author Bertrand Rix {@literal } + * @author Anne Tilloy {@literal } + * @author Jean-Luc Bouchot {@literal } + */ +public class LfShuntCompensatorPositionAction extends AbstractLfAction { + + private static final Logger LOGGER = LoggerFactory.getLogger(LfShuntCompensatorPositionAction.class); + + public LfShuntCompensatorPositionAction(String id, ShuntCompensatorPositionAction action) { + super(id, action); + } + + @Override + public boolean apply(LfNetwork network, LfContingency contingency, LfNetworkParameters networkParameters) { + LfShunt shunt = network.getShuntById(action.getShuntCompensatorId()); + if (shunt instanceof LfShuntImpl) { // no svc here + if (shunt.getVoltageControl().isPresent()) { + LOGGER.warn("Shunt compensator position action: voltage control is present on the shunt, section could be overridden."); + } + Optional controllerOpt = shunt.getControllers().stream() + .filter(controller -> controller.getId().equals(action.getShuntCompensatorId())).findAny(); + if (controllerOpt.isPresent()) { + controllerOpt.get().updateSectionB(action.getSectionCount()); + return true; + } else { + LOGGER.warn("No section change: shunt {} not present", action.getShuntCompensatorId()); + return false; + } + } + return false; + } +} diff --git a/src/main/java/com/powsybl/openloadflow/network/action/LfSwitchAction.java b/src/main/java/com/powsybl/openloadflow/network/action/LfSwitchAction.java new file mode 100644 index 0000000000..d3c0c95dc8 --- /dev/null +++ b/src/main/java/com/powsybl/openloadflow/network/action/LfSwitchAction.java @@ -0,0 +1,45 @@ +/** + * Copyright (c) 2025, Coreso SA (https://www.coreso.eu/) and TSCNET Services GmbH (https://www.tscnet.eu/) + * Copyright (c) 2022, 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/. + * SPDX-License-Identifier: MPL-2.0 + */ +package com.powsybl.openloadflow.network.action; + +import com.powsybl.action.SwitchAction; +import com.powsybl.openloadflow.network.LfBranch; +import com.powsybl.openloadflow.network.LfNetwork; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Bertrand Rix {@literal } + * @author Anne Tilloy {@literal } + * @author Jean-Luc Bouchot {@literal } + */ +public class LfSwitchAction extends AbstractLfBranchAction { + + private static final Logger LOGGER = LoggerFactory.getLogger(LfSwitchAction.class); + + public LfSwitchAction(String id, SwitchAction action) { + super(id, action); + } + + @Override + boolean findEnabledDisabledBranches(LfNetwork lfNetwork) { + LfBranch branch = lfNetwork.getBranchById(action.getSwitchId()); + if (branch != null) { + if (action.isOpen()) { + setDisabledBranch(branch); + } else { + setEnabledBranch(branch); + } + return true; + } else { + LOGGER.warn("Switch action {}: branch matching switch id {} not found", action.getId(), action.getSwitchId()); + return false; + } + } +} diff --git a/src/main/java/com/powsybl/openloadflow/network/action/LfTerminalsConnectionAction.java b/src/main/java/com/powsybl/openloadflow/network/action/LfTerminalsConnectionAction.java new file mode 100644 index 0000000000..024d49128d --- /dev/null +++ b/src/main/java/com/powsybl/openloadflow/network/action/LfTerminalsConnectionAction.java @@ -0,0 +1,48 @@ +/** + * Copyright (c) 2025, Coreso SA (https://www.coreso.eu/) and TSCNET Services GmbH (https://www.tscnet.eu/) + * Copyright (c) 2022, 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/. + * SPDX-License-Identifier: MPL-2.0 + */ +package com.powsybl.openloadflow.network.action; + +import com.powsybl.action.TerminalsConnectionAction; +import com.powsybl.openloadflow.network.LfBranch; +import com.powsybl.openloadflow.network.LfNetwork; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Bertrand Rix {@literal } + * @author Anne Tilloy {@literal } + * @author Jean-Luc Bouchot {@literal } + */ +public class LfTerminalsConnectionAction extends AbstractLfBranchAction { + + private static final Logger LOGGER = LoggerFactory.getLogger(LfTerminalsConnectionAction.class); + + public LfTerminalsConnectionAction(String id, TerminalsConnectionAction action) { + super(id, action); + if (action.getSide().isPresent()) { + throw new UnsupportedOperationException("Terminals connection action: only open or close branch at both sides is supported yet."); + } + } + + @Override + boolean findEnabledDisabledBranches(LfNetwork lfNetwork) { + LfBranch branch = lfNetwork.getBranchById(action.getElementId()); + if (branch != null && branch.getBus1() != null && branch.getBus2() != null) { + if (action.isOpen()) { + setDisabledBranch(branch); + } else { + setEnabledBranch(branch); + } + return true; + } else { + LOGGER.warn("TerminalsConnectionAction action {}: branch matching element id {} not found", action.getId(), action.getElementId()); + return false; + } + } +} diff --git a/src/main/java/com/powsybl/openloadflow/sa/AbstractSecurityAnalysis.java b/src/main/java/com/powsybl/openloadflow/sa/AbstractSecurityAnalysis.java index acfb5dc351..70a6b48b12 100644 --- a/src/main/java/com/powsybl/openloadflow/sa/AbstractSecurityAnalysis.java +++ b/src/main/java/com/powsybl/openloadflow/sa/AbstractSecurityAnalysis.java @@ -26,6 +26,8 @@ import com.powsybl.openloadflow.lf.LoadFlowContext; import com.powsybl.openloadflow.lf.LoadFlowEngine; import com.powsybl.openloadflow.network.*; +import com.powsybl.openloadflow.network.action.LfAction; +import com.powsybl.openloadflow.network.action.LfActionUtils; import com.powsybl.openloadflow.network.impl.*; import com.powsybl.openloadflow.network.util.ActivePowerDistribution; import com.powsybl.openloadflow.util.Lists2; @@ -469,6 +471,14 @@ protected static void checkActions(Network network, List actions) { break; } + case AreaInterchangeTargetAction.NAME: { + AreaInterchangeTargetAction areaInterchangeAction = (AreaInterchangeTargetAction) action; + if (network.getArea(areaInterchangeAction.getAreaId()) == null) { + throw new PowsyblException("Area '" + areaInterchangeAction.getAreaId() + "' not found"); + } + break; + } + default: throw new UnsupportedOperationException("Unsupported action type: " + action.getType()); } @@ -477,8 +487,7 @@ protected static void checkActions(Network network, List actions) { protected static Map createLfActions(LfNetwork lfNetwork, Set actions, Network network, LfNetworkParameters parameters) { return actions.stream() - .map(action -> LfAction.create(action, lfNetwork, network, parameters.isBreakers())) - .flatMap(Optional::stream) + .map(action -> LfActionUtils.createLfAction(action, network, parameters.isBreakers(), lfNetwork)) .collect(Collectors.toMap(LfAction::getId, Function.identity())); } @@ -869,7 +878,7 @@ protected OperatorStrategyResult runActionSimulation(LfNetwork network, C contex .filter(Objects::nonNull) .toList(); - LfAction.apply(operatorStrategyLfActions, network, contingency, networkParameters); + LfActionUtils.applyListOfActions(operatorStrategyLfActions, network, contingency, networkParameters, reportNode); Stopwatch stopwatch = Stopwatch.createStarted(); diff --git a/src/main/java/com/powsybl/openloadflow/sa/WoodburyDcSecurityAnalysis.java b/src/main/java/com/powsybl/openloadflow/sa/WoodburyDcSecurityAnalysis.java index 1119ddb8f0..8513f217b7 100644 --- a/src/main/java/com/powsybl/openloadflow/sa/WoodburyDcSecurityAnalysis.java +++ b/src/main/java/com/powsybl/openloadflow/sa/WoodburyDcSecurityAnalysis.java @@ -26,6 +26,7 @@ import com.powsybl.openloadflow.equations.EquationSystem; import com.powsybl.openloadflow.graph.GraphConnectivityFactory; import com.powsybl.openloadflow.network.*; +import com.powsybl.openloadflow.network.action.*; import com.powsybl.openloadflow.network.impl.Networks; import com.powsybl.openloadflow.network.impl.PropagatedContingency; import com.powsybl.openloadflow.dc.fastdc.ComputedContingencyElement; @@ -105,7 +106,8 @@ private double[] calculatePostContingencyAndOperatorStrategyStates(DcLoadFlowCon .map(contingencyElementByBranch::get) .collect(Collectors.toList()); List actionElements = operatorStrategyLfActions.stream() - .map(lfAction -> lfAction.getTapPositionChange().getBranch().getId()) + .filter(AbstractLfTapChangerAction.class::isInstance) + .map(lfAction -> ((AbstractLfTapChangerAction) lfAction).getChange().getBranch().getId()) .map(tapPositionChangeElementByBranch::get) .collect(Collectors.toList()); @@ -264,7 +266,7 @@ private OperatorStrategyResult computeOperatorStrategyResultFromPostContingencyA // apply modifications to compute results lfContingency.apply(loadFlowContext.getParameters().getBalanceType()); - LfAction.apply(operatorStrategyLfActions, lfNetwork, lfContingency, loadFlowContext.getParameters().getNetworkParameters()); + LfActionUtils.applyListOfActions(operatorStrategyLfActions, lfNetwork, lfContingency, loadFlowContext.getParameters().getNetworkParameters(), reportNode); // update network result var postActionsNetworkResult = new PreContingencyNetworkResult(lfNetwork, monitorIndex, createResultExtension); @@ -351,8 +353,8 @@ private void addPostContingencyAndOperatorStrategyResults(DcLoadFlowContext cont private static Map createTapPositionChangeElementsIndexByBranchId(Map lfActionById, EquationSystem equationSystem) { Map computedTapPositionChangeElements = lfActionById.values().stream() - .filter(lfAction -> lfAction.getTapPositionChange() != null) - .map(lfAction -> new ComputedTapPositionChangeElement(lfAction.getTapPositionChange(), equationSystem)) + .filter(AbstractLfTapChangerAction.class::isInstance) + .map(lfAction -> new ComputedTapPositionChangeElement(((AbstractLfTapChangerAction) lfAction).getChange(), equationSystem)) .filter(computedTapPositionChangeElement -> computedTapPositionChangeElement.getLfBranchEquation() != null) .collect(Collectors.toMap( computedTapPositionChangeElement -> computedTapPositionChangeElement.getLfBranch().getId(), diff --git a/src/test/java/com/powsybl/openloadflow/sa/LfActionTest.java b/src/test/java/com/powsybl/openloadflow/sa/LfActionTest.java index da9cf927e8..0c902e97d8 100644 --- a/src/test/java/com/powsybl/openloadflow/sa/LfActionTest.java +++ b/src/test/java/com/powsybl/openloadflow/sa/LfActionTest.java @@ -22,6 +22,7 @@ import com.powsybl.openloadflow.ac.AcLoadFlowParameters; import com.powsybl.openloadflow.graph.NaiveGraphConnectivityFactory; import com.powsybl.openloadflow.network.*; +import com.powsybl.openloadflow.network.action.*; import com.powsybl.openloadflow.network.impl.LfNetworkList; import com.powsybl.openloadflow.network.impl.Networks; import com.powsybl.openloadflow.network.impl.PropagatedContingency; @@ -33,7 +34,6 @@ import java.io.IOException; import java.util.Collections; -import java.util.List; import static org.junit.jupiter.api.Assertions.*; @@ -64,10 +64,11 @@ void test() { AcLoadFlowParameters acParameters = OpenLoadFlowParameters.createAcParameters(network, loadFlowParameters, new OpenLoadFlowParameters(), matrixFactory, new NaiveGraphConnectivityFactory<>(LfBus::getNum), true, false); LfTopoConfig topoConfig = new LfTopoConfig(); + LfNetworkParameters networkParameters = acParameters.getNetworkParameters(); topoConfig.getSwitchesToOpen().add(network.getSwitch("C")); - try (LfNetworkList lfNetworks = Networks.load(network, acParameters.getNetworkParameters(), topoConfig, ReportNode.NO_OP)) { + try (LfNetworkList lfNetworks = Networks.load(network, networkParameters, topoConfig, ReportNode.NO_OP)) { LfNetwork lfNetwork = lfNetworks.getLargest().orElseThrow(); - LfAction lfAction = LfAction.create(switchAction, lfNetwork, network, acParameters.getNetworkParameters().isBreakers()).orElseThrow(); + LfAction lfSwitchAction = LfActionUtils.createLfAction(switchAction, network, networkParameters.isBreakers(), lfNetwork); String loadId = "LOAD"; Contingency contingency = new Contingency(loadId, new LoadContingency("LD")); PropagatedContingencyCreationParameters creationParameters = new PropagatedContingencyCreationParameters() @@ -75,17 +76,26 @@ void test() { PropagatedContingency propagatedContingency = PropagatedContingency.createList(network, Collections.singletonList(contingency), new LfTopoConfig(), creationParameters).get(0); propagatedContingency.toLfContingency(lfNetwork).ifPresent(lfContingency -> { - LfAction.apply(List.of(lfAction), lfNetwork, lfContingency, acParameters.getNetworkParameters()); + lfSwitchAction.apply(lfNetwork, lfContingency, networkParameters); assertTrue(lfNetwork.getBranchById("C").isDisabled()); - assertEquals("C", lfAction.getDisabledBranch().getId()); - assertNull(lfAction.getEnabledBranch()); + assertEquals("C", ((LfSwitchAction) lfSwitchAction).getDisabledBranch().getId()); + assertNull(((LfSwitchAction) lfSwitchAction).getEnabledBranch()); }); - assertTrue(LfAction.create(new SwitchAction("switchAction", "S", true), lfNetwork, network, acParameters.getNetworkParameters().isBreakers()).isEmpty()); - 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()); + LfAction lfInvalidSwitchAction = LfActionUtils.createLfAction(new SwitchAction("switchAction", "S", true), + network, networkParameters.isBreakers(), lfNetwork); + LfAction lfInvalidTerminalsConnectionAction = LfActionUtils.createLfAction(new TerminalsConnectionAction("A line action", "x", true), + network, networkParameters.isBreakers(), lfNetwork); + LfAction lfInvalidPhaseTapChangerTapPositionAction = LfActionUtils.createLfAction(new PhaseTapChangerTapPositionAction("A phase tap change action", "y", false, 3), + network, networkParameters.isBreakers(), lfNetwork); + + assertFalse(lfInvalidSwitchAction.apply(lfNetwork, null, networkParameters)); + assertFalse(lfInvalidTerminalsConnectionAction.apply(lfNetwork, null, networkParameters)); + assertFalse(lfInvalidPhaseTapChangerTapPositionAction.apply(lfNetwork, null, networkParameters)); + var lineAction = new TerminalsConnectionAction("A line action", "L1", ThreeSides.ONE, false); - 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()); + assertEquals("Terminals connection action: only open or close branch at both sides is supported yet.", + assertThrows(UnsupportedOperationException.class, () -> new LfTerminalsConnectionAction("A line action", lineAction)).getMessage()); } } @@ -103,7 +113,8 @@ void testUnsupportedGeneratorAction() { new LoadFlowParameters(), new OpenLoadFlowParameters(), matrixFactory, new NaiveGraphConnectivityFactory<>(LfBus::getNum), true, false); try (LfNetworkList lfNetworks = Networks.load(network, acParameters.getNetworkParameters(), new LfTopoConfig(), ReportNode.NO_OP)) { LfNetwork lfNetwork = lfNetworks.getLargest().orElseThrow(); - UnsupportedOperationException e = assertThrows(UnsupportedOperationException.class, () -> LfAction.create(generatorAction, lfNetwork, network, acParameters.getNetworkParameters().isBreakers())); + UnsupportedOperationException e = assertThrows(UnsupportedOperationException.class, + () -> new LfGeneratorAction("Gen action", generatorAction, lfNetwork)); assertEquals("Generator action on G : configuration not supported yet.", e.getMessage()); } } @@ -127,8 +138,8 @@ void testGeneratorActionWithRelativeActivePowerValue() { new LoadFlowParameters(), new OpenLoadFlowParameters(), matrixFactory, new NaiveGraphConnectivityFactory<>(LfBus::getNum), true, false); try (LfNetworkList lfNetworks = Networks.load(network, acParameters.getNetworkParameters(), new LfTopoConfig(), ReportNode.NO_OP)) { LfNetwork lfNetwork = lfNetworks.getLargest().orElseThrow(); - LfAction lfAction = LfAction.create(generatorAction, lfNetwork, network, acParameters.getNetworkParameters().isBreakers()).orElseThrow(); - lfAction.apply(acParameters.getNetworkParameters()); + LfAction lfAction = LfActionUtils.createLfAction(generatorAction, network, acParameters.getNetworkParameters().isBreakers(), lfNetwork); + lfAction.apply(lfNetwork, null, acParameters.getNetworkParameters()); assertEquals(newTargetP / PerUnit.SB, lfNetwork.getGeneratorById(genId).getTargetP()); assertEquals(genId, generatorAction.getGeneratorId()); assertEquals(oldTargetP, network.getGenerator(genId).getTargetP()); @@ -151,33 +162,71 @@ void testHvdcAction() { .withP0(200.0) .withDroop(90.0) .build(); + UnsupportedOperationException e = assertThrows(UnsupportedOperationException.class, () -> new LfHvdcAction("action", hvdcAction)); + assertEquals("Hvdc action: enabling ac emulation mode through an action is not supported yet.", e.getMessage()); + } + + @Test + void testHvdcAction2() { + // This action is valid but AC emulation disabling has not been explicitly set, not supported. + Network network = HvdcNetworkFactory.createWithHvdcInAcEmulation(); + HvdcAction hvdcAction2 = new HvdcActionBuilder() + .withId("action") + .withHvdcId("hvdc34") + .withP0(200.0) + .withDroop(90.0) + .build(); var matrixFactory = new DenseMatrixFactory(); AcLoadFlowParameters acParameters = OpenLoadFlowParameters.createAcParameters(network, - new LoadFlowParameters(), new OpenLoadFlowParameters(), matrixFactory, new NaiveGraphConnectivityFactory<>(LfBus::getNum), true, false); + new LoadFlowParameters(), new OpenLoadFlowParameters(), matrixFactory, new NaiveGraphConnectivityFactory<>(LfBus::getNum), true, false); try (LfNetworkList lfNetworks = Networks.load(network, acParameters.getNetworkParameters(), new LfTopoConfig(), ReportNode.NO_OP)) { LfNetwork lfNetwork = lfNetworks.getLargest().orElseThrow(); - UnsupportedOperationException e = assertThrows(UnsupportedOperationException.class, () -> LfAction.create(hvdcAction, lfNetwork, network, acParameters.getNetworkParameters().isBreakers())); - assertEquals("Hvdc action: line is already in AC emulation, not supported yet.", e.getMessage()); + LfAction lfAction = LfActionUtils.createLfAction(hvdcAction2, network, acParameters.getNetworkParameters().isBreakers(), lfNetwork); + assertFalse(lfAction.apply(lfNetwork, null, acParameters.getNetworkParameters())); } } @Test - void testHvdcAction2() { - // the hvc line is in active power setpoint mode before applying the action. Not supported yet. - Network network = HvdcNetworkFactory.createVsc(); - HvdcAction hvdcAction = new HvdcActionBuilder() - .withId("action") - .withHvdcId("hvdc23") - .withAcEmulationEnabled(true) - .withP0(200.0) - .withDroop(90.0) - .build(); + void testLfAreaInterchangeTargetAction() { + + AreaInterchangeTargetAction targetAction = new AreaInterchangeTargetActionBuilder() + .withId("action") + .withAreaId("a1") + .withTarget(20.0) + .build(); + + AreaInterchangeTargetAction invalidTargetAction = new AreaInterchangeTargetActionBuilder() + .withId("action") + .withAreaId("DUMMMY") + .withTarget(20.0) + .build(); + + Network network = MultiAreaNetworkFactory.createTwoAreasWithXNode(); + var matrixFactory = new DenseMatrixFactory(); + + // With area interchange target control enabled AcLoadFlowParameters acParameters = OpenLoadFlowParameters.createAcParameters(network, - new LoadFlowParameters(), new OpenLoadFlowParameters(), matrixFactory, new NaiveGraphConnectivityFactory<>(LfBus::getNum), true, false); + new LoadFlowParameters(), new OpenLoadFlowParameters(), matrixFactory, new NaiveGraphConnectivityFactory<>(LfBus::getNum), true, false); + acParameters.getNetworkParameters().setAreaInterchangeControl(true); try (LfNetworkList lfNetworks = Networks.load(network, acParameters.getNetworkParameters(), new LfTopoConfig(), ReportNode.NO_OP)) { LfNetwork lfNetwork = lfNetworks.getLargest().orElseThrow(); - assertTrue(LfAction.create(hvdcAction, lfNetwork, network, acParameters.getNetworkParameters().isBreakers()).isEmpty()); + + LfAction lfAreaTargetAction = LfActionUtils.createLfAction(targetAction, network, acParameters.getNetworkParameters().isBreakers(), lfNetwork); + assertTrue(lfAreaTargetAction.apply(lfNetwork, null, acParameters.getNetworkParameters())); + + LfAction lfAreaTargetAction2 = LfActionUtils.createLfAction(invalidTargetAction, network, acParameters.getNetworkParameters().isBreakers(), lfNetwork); + assertFalse(lfAreaTargetAction2.apply(lfNetwork, null, acParameters.getNetworkParameters())); + } + + // With area interchange target control disabled + acParameters.getNetworkParameters().setAreaInterchangeControl(false); + try (LfNetworkList lfNetworks = Networks.load(network, acParameters.getNetworkParameters(), new LfTopoConfig(), ReportNode.NO_OP)) { + LfNetwork lfNetwork = lfNetworks.getLargest().orElseThrow(); + acParameters.getNetworkParameters().setAreaInterchangeControl(false); + + LfAction lfAreaTargetAction = LfActionUtils.createLfAction(targetAction, network, acParameters.getNetworkParameters().isBreakers(), lfNetwork); + assertFalse(lfAreaTargetAction.apply(lfNetwork, null, acParameters.getNetworkParameters())); } } } diff --git a/src/test/java/com/powsybl/openloadflow/sa/OpenSecurityAnalysisWithActionsTest.java b/src/test/java/com/powsybl/openloadflow/sa/OpenSecurityAnalysisWithActionsTest.java index 8aa47c6eb1..0459721307 100644 --- a/src/test/java/com/powsybl/openloadflow/sa/OpenSecurityAnalysisWithActionsTest.java +++ b/src/test/java/com/powsybl/openloadflow/sa/OpenSecurityAnalysisWithActionsTest.java @@ -1677,4 +1677,61 @@ void testOperatorStrategyNoMoreBusVoltageControlled() throws IOException { assertReportEquals("/saReportOperatorStrategyNoVoltageControl.txt", reportNode); } + + @Test + void testAreaInterchangeTargetAction() { + + Network network = MultiAreaNetworkFactory.createTwoAreasWithTwoXNodes(); + + Area area1 = network.getArea("a1"); + Area area2 = network.getArea("a2"); + area1.setInterchangeTarget(-15.0); + area2.setInterchangeTarget(15.0); + + Contingency lineContingency = new Contingency("l23_A1_1", new BranchContingency("l23_A1_1")); + List contingencies = List.of(lineContingency); + AreaInterchangeTargetAction actionArea1 = new AreaInterchangeTargetActionBuilder() + .withId("ActionArea1") + .withAreaId("a1") + .withTarget(-10.0) + .build(); + + AreaInterchangeTargetAction actionArea2 = new AreaInterchangeTargetActionBuilder() + .withId("ActionArea2") + .withAreaId("a2") + .withTarget(10.0) + .build(); + + List actions = List.of(actionArea1, actionArea2); + List operatorStrategies = List.of(new OperatorStrategy("strategy1", + ContingencyContext.specificContingency(lineContingency.getId()), new TrueCondition(), List.of(actionArea1.getId(), actionArea2.getId()))); + ReportNode reportNode = ReportNode.newRootReportNode() + .withMessageTemplate("testSaReport", "Test report of security analysis") + .build(); + + double areaInterchangePMaxMismatch = 1e-3; + + SecurityAnalysisParameters parameters = new SecurityAnalysisParameters(); + OpenLoadFlowParameters.create(parameters.getLoadFlowParameters()) + .setAreaInterchangeControl(true) + .setSlackBusPMaxMismatch(1e-3) + .setAreaInterchangePMaxMismatch(areaInterchangePMaxMismatch); + + List monitors = createAllBranchesMonitors(network); + SecurityAnalysisResult result = runSecurityAnalysis(network, contingencies, monitors, parameters, operatorStrategies, actions, reportNode); + + // Respect of targets in the base case (at 15.0) + assertEquals(15.0, result.getPreContingencyResult().getNetworkResult().getBranchResult("l23_A1").getP1(), areaInterchangePMaxMismatch); + assertEquals(-15.0, result.getPreContingencyResult().getNetworkResult().getBranchResult("l23_A2").getP2(), areaInterchangePMaxMismatch); + + // Respect of targets in post contingency state (at 15.0) + assertEquals(15.0, result.getPostContingencyResults().get(0).getNetworkResult().getBranchResult("l23_A1").getP1(), areaInterchangePMaxMismatch); + assertEquals(-15.0, result.getPostContingencyResults().get(0).getNetworkResult().getBranchResult("l23_A2").getP2(), areaInterchangePMaxMismatch); + + // Respect of targets after remedial actions (now at 10.0) + assertNotNull(result.getOperatorStrategyResults()); + assertEquals(10.0, result.getOperatorStrategyResults().get(0).getNetworkResult().getBranchResult("l23_A1").getP1(), areaInterchangePMaxMismatch); + assertEquals(-10.0, result.getOperatorStrategyResults().get(0).getNetworkResult().getBranchResult("l23_A2").getP2(), areaInterchangePMaxMismatch); + + } }