Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
5c4d49c
Support three windings transformer in terminals connection action.
obrix Jan 30, 2026
89a664b
Fix
obrix Jan 30, 2026
55d7092
Adjust warnings.
obrix Feb 25, 2026
f1ac12e
Suggestion
obrix Feb 25, 2026
87d0cb2
Revert "Suggestion"
obrix Feb 25, 2026
6c10575
Introduce branchesByOriginalId
obrix Feb 26, 2026
1424645
Clean
obrix Feb 26, 2026
4a29592
Try handling three windings transformer case in abstractLfBranchAction
obrix Mar 11, 2026
d53a866
Rename
obrix Mar 11, 2026
9a46e70
Add check on flow
obrix Mar 11, 2026
3b9e23a
Add precision on limitations
obrix Mar 11, 2026
475f4c2
Handle three windings transformer terminal connection action properly…
obrix Mar 13, 2026
b9c52c0
Clean
obrix Mar 13, 2026
8e34cfc
clean test
jeandemanged Mar 16, 2026
8d3788a
Merge branch 'main' into support_3wt_terminals_connection_action
SylvestreSakti Mar 23, 2026
848a6c9
Merge branch 'main' into support_3wt_terminals_connection_action
jeandemanged Mar 23, 2026
26ed753
Merge branch 'main' into support_3wt_terminals_connection_action
jeandemanged Mar 24, 2026
a21858d
Merge branch 'main' into support_3wt_terminals_connection_action
SylvestreSakti Mar 24, 2026
846054c
Merge branch 'main' into support_3wt_terminals_connection_action
jeandemanged Mar 24, 2026
ae313f0
Merge branch 'main' into support_3wt_terminals_connection_action
jeandemanged Mar 24, 2026
f91178e
Merge branch 'main' into support_3wt_terminals_connection_action
SylvestreSakti Mar 26, 2026
41f8e3b
clean
jeandemanged Mar 27, 2026
52d0a0b
clean
jeandemanged Mar 27, 2026
4051c65
Merge branch 'main' into support_3wt_terminals_connection_action
obrix Mar 27, 2026
63621e5
Clean
obrix Mar 27, 2026
cfae44f
Quick fix ?
obrix Mar 27, 2026
d55eff8
Also remove branch in the branchByOrginalIds index
obrix Mar 27, 2026
0fe3203
Fix
obrix Mar 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/security/inputs.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ With Open Load Flow only the following remedial actions are currently implemente

- `LoadAction`
- `SwitchAction`
- `TerminalsConnectionAction`
- `TerminalsConnectionAction` (Only supporting actions on branches and three windings transformers terminals)
- `PhaseTapChangerTapPositionAction`
- `RatioTapChangerTapPositionAction`
- `ShuntCompensatorPositionAction`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,7 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import java.util.stream.Stream;
Expand Down Expand Up @@ -133,36 +129,36 @@ static DenseMatrix calculateElementsStates(DcLoadFlowContext loadFlowContext, Co
return elementsStates;
}

static Map<LfAction, ComputedElement> createActionElementsIndexByLfAction(Map<String, LfAction> lfActionById, EquationSystem<DcVariableType, DcEquationType> equationSystem) {
Map<LfAction, ComputedElement> computedElements = lfActionById.values().stream()
.flatMap(lfAction -> {
ComputedElement element = switch (lfAction.getType()) {
case SwitchAction.NAME, TerminalsConnectionAction.NAME -> {
AbstractLfBranchAction<?> lfBranchAction = (AbstractLfBranchAction<?>) lfAction;
if (lfBranchAction.getEnabledBranch() != null) {
yield ComputedSwitchBranchElement.create(lfBranchAction.getEnabledBranch(), true, equationSystem);
} else if (lfBranchAction.getDisabledBranch() != null) {
yield ComputedSwitchBranchElement.create(lfBranchAction.getDisabledBranch(), false, equationSystem);
}
yield null;
static Map<LfAction, List<ComputedElement>> createActionElementsIndexByLfAction(Map<String, LfAction> lfActionById, EquationSystem<DcVariableType, DcEquationType> equationSystem) {
Map<LfAction, List<ComputedElement>> computedElements = lfActionById.values().stream()
.flatMap(lfAction -> {
List<ComputedElement> elements = new ArrayList<>();
switch (lfAction.getType()) {
case SwitchAction.NAME, TerminalsConnectionAction.NAME -> {
AbstractLfBranchAction<?> lfBranchAction = (AbstractLfBranchAction<?>) lfAction;
if (!lfBranchAction.getEnabledBranches().isEmpty()) {
elements.addAll(lfBranchAction.getEnabledBranches().stream().map(b -> ComputedSwitchBranchElement.create(b, true, equationSystem)).toList());
} else if (!lfBranchAction.getDisabledBranches().isEmpty()) {
elements.addAll(lfBranchAction.getDisabledBranches().stream().map(b -> ComputedSwitchBranchElement.create(b, false, equationSystem)).toList());
}
case PhaseTapChangerTapPositionAction.NAME ->
new ComputedTapPositionChangeElement(((AbstractLfTapChangerAction<?>) lfAction).getChange(), equationSystem);
default -> throw new IllegalStateException("Only tap position change and branch enabling/disabling are supported in WoodburyDcSecurityAnalysis");
};
if (element == null) {
return Stream.empty();
}
return Stream.of(Map.entry(lfAction, element));
})
.filter(e -> e.getValue().getLfBranchEquation() != null)
.collect(Collectors.toMap(
Map.Entry::getKey,
Map.Entry::getValue,
(existing, replacement) -> existing,
LinkedHashMap::new
));
ComputedElement.setComputedElementIndexes(computedElements.values());
case PhaseTapChangerTapPositionAction.NAME ->
elements.add(new ComputedTapPositionChangeElement(((AbstractLfTapChangerAction<?>) lfAction).getChange(), equationSystem));
default -> throw new IllegalStateException("Only tap position change and branch enabling/disabling are supported in WoodburyDcSecurityAnalysis");
}
if (elements.isEmpty()) {
return Stream.empty();
}
return Stream.of(Map.entry(lfAction, elements));
})
.filter(e -> e.getValue().stream().filter(b -> b.getLfBranchEquation() == null).findAny().isEmpty())
.collect(Collectors.toMap(
Map.Entry::getKey,
Map.Entry::getValue,
(existing, replacement) -> existing,
LinkedHashMap::new
));
ComputedElement.setComputedElementIndexes(computedElements.values().stream().flatMap(Collection::stream).toList());
return computedElements;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ private static void detectPotentialConnectivityBreak(LfNetwork lfNetwork, DenseM
*/
private static boolean isConnectivityPotentiallyModifiedByContingencyAndOperatorStrategy(LfNetwork lfNetwork, States states, PropagatedContingency contingency,
Map<String, ComputedContingencyElement> contingencyElementByBranch, List<LfAction> operatorStrategyLfActions,
Map<LfAction, ComputedElement> actionElementByBranch, EquationSystem<DcVariableType, DcEquationType> equationSystem) {
Map<LfAction, List<ComputedElement>> actionElementByBranch, EquationSystem<DcVariableType, DcEquationType> equationSystem) {
List<ComputedContingencyElement> contingencyElements = contingency.getBranchIdsToOpen().keySet().stream()
.map(contingencyElementByBranch::get)
.collect(Collectors.toList());
Expand All @@ -162,8 +162,10 @@ private static boolean isConnectivityPotentiallyModifiedByContingencyAndOperator
// it is not necessary to consider them to ensure that there is no loss of connectivity.
List<ComputedElement> actionElements = operatorStrategyLfActions.stream()
.map(actionElementByBranch::get)
.filter(Objects::nonNull)
.flatMap(Collection::stream)
.filter(actionElement -> actionElement instanceof ComputedSwitchBranchElement computedSwitchBranchElement && !computedSwitchBranchElement.isEnabled())
.collect(Collectors.toList());
.toList();
return isGroupOfElementsBreakingConnectivity(lfNetwork, states.contingencyStates(), contingencyElements, states.actionStates(), actionElements, equationSystem);
}

Expand Down Expand Up @@ -200,14 +202,14 @@ private static boolean isGroupOfElementsBreakingConnectivity(LfNetwork lfNetwork
*/
private static Optional<ConnectivityAnalysisResult> computeConnectivityAnalysisResult(LfNetwork lfNetwork,
PropagatedContingency contingency, Map<String, ComputedContingencyElement> contingencyElementByBranch,
LfOperatorStrategy operatorStrategy, Map<LfAction, ComputedElement> actionElementByBranch) {
LfOperatorStrategy operatorStrategy, Map<LfAction, List<ComputedElement>> actionElementByBranch) {
GraphConnectivity<LfBus, LfBranch> connectivity = lfNetwork.getConnectivity();

// concatenate all computed elements, to apply them on the connectivity
List<LfAction> lfActions = operatorStrategy == null ? Collections.emptyList() : operatorStrategy.getActions().stream().filter(LfAction::isValid).toList();
List<ComputedElement> modifyingConnectivityCandidates = Stream.concat(
contingency.getBranchIdsToOpen().keySet().stream().map(contingencyElementByBranch::get),
lfActions.stream().map(actionElementByBranch::get)
lfActions.stream().map(actionElementByBranch::get).flatMap(Collection::stream)
).sorted(Comparator.comparing(element -> element.getLfBranch().getId())).toList();

// we confirm the breaking of connectivity by network connectivity
Expand Down Expand Up @@ -353,7 +355,7 @@ public static ConnectivityBreakAnalysisResults run(DcLoadFlowContext loadFlowCon
*/
public static ConnectivityAnalysisResult processPostContingencyAndPostOperatorStrategyConnectivityAnalysisResult(DcLoadFlowContext loadFlowContext, ConnectivityAnalysisResult postContingencyConnectivityAnalysisResult,
Map<String, ComputedContingencyElement> contingencyElementByBranch, DenseMatrix contingenciesStates,
LfOperatorStrategy operatorStrategy, Map<LfAction, ComputedElement> actionElementsIndexByLfAction, DenseMatrix actionsStates) {
LfOperatorStrategy operatorStrategy, Map<LfAction, List<ComputedElement>> actionElementsIndexByLfAction, DenseMatrix actionsStates) {
// if there is no topological action, no need to process anything as the connectivity has not changed from post contingency result
boolean hasAnyTopologicalAction = operatorStrategy.getActions().stream().anyMatch(lfAction -> lfAction instanceof AbstractLfBranchAction<?>);
if (!hasAnyTopologicalAction) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,9 +131,9 @@ public static double[] runDcLoadFlowWithModifiedTargetVector(DcLoadFlowContext l
// set transformer phase shift to 0 for disabled phase tap changers by actions
lfActions.stream()
.filter(AbstractLfBranchAction.class::isInstance)
.map(lfAction -> ((AbstractLfBranchAction<?>) lfAction).getDisabledBranch())
.map(lfAction -> ((AbstractLfBranchAction<?>) lfAction).getDisabledBranches())
.filter(Objects::nonNull)
.flatMap(lfBranch -> loadFlowContext.getEquationSystem().getEquation(lfBranch.getNum(), DcEquationType.BRANCH_TARGET_ALPHA1).stream())
.flatMap(lfBranches -> lfBranches.stream().flatMap(b -> loadFlowContext.getEquationSystem().getEquation(b.getNum(), DcEquationType.BRANCH_TARGET_ALPHA1).stream()))
.map(Equation::getColumn)
.forEach(column -> targetVectorArray[column] = 0);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,7 @@ public void removeBranch(String branchId) {
throw new PowsyblException("Branch " + branchId + " not found in network " + this);
}
branches.remove(branch);
branch.getOriginalIds().forEach(branchesByOriginalId::remove);
invalidateSlackAndReference();
if (connectivity != null) {
connectivity.removeEdge(branch);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@
import com.powsybl.openloadflow.graph.GraphConnectivity;
import com.powsybl.openloadflow.network.*;

import java.util.HashSet;
import java.util.Set;
import java.util.*;

/**
* @author Bertrand Rix {@literal <bertrand.rix at artelys.com>}
Expand All @@ -22,36 +21,38 @@
*/
public abstract class AbstractLfBranchAction<A extends Action> extends AbstractLfAction<A> {

private LfBranch disabledBranch = null; // switch to open
private final List<LfBranch> disabledBranch = new ArrayList<>(); // switch to open

private LfBranch enabledBranch = null; // switch to close
private final List<LfBranch> enabledBranch = new ArrayList<>(); // switch to close

AbstractLfBranchAction(A action, LfNetwork lfNetwork) {
super(action);
findEnabledDisabledBranches(lfNetwork);
}

protected void setDisabledBranch(LfBranch disabledBranch) {
this.disabledBranch = disabledBranch;
protected void addDisabledBranch(LfBranch disabledBranch) {
Objects.requireNonNull(disabledBranch);
this.disabledBranch.add(disabledBranch);
}

protected void setEnabledBranch(LfBranch enabledBranch) {
this.enabledBranch = enabledBranch;
protected void addEnabledBranch(LfBranch enabledBranch) {
Objects.requireNonNull(enabledBranch);
this.enabledBranch.add(enabledBranch);
}

public LfBranch getDisabledBranch() {
public List<LfBranch> getDisabledBranches() {
return this.disabledBranch;
}

public LfBranch getEnabledBranch() {
public List<LfBranch> getEnabledBranches() {
return this.enabledBranch;
}

abstract void findEnabledDisabledBranches(LfNetwork lfNetwork);

@Override
public boolean isValid() {
return disabledBranch != null || enabledBranch != null;
return !disabledBranch.isEmpty() || !enabledBranch.isEmpty();
}

/**
Expand Down Expand Up @@ -86,18 +87,17 @@ public boolean apply(LfNetwork network, LfContingency contingency, LfNetworkPara
* Optimized apply on an existing connectivity (to apply several branch actions at the same time)
*/
public boolean applyOnConnectivity(GraphConnectivity<LfBus, LfBranch> connectivity) {
boolean found = disabledBranch != null || enabledBranch != null;
updateConnectivity(connectivity);
return found;
return isValid();
}

private void updateConnectivity(GraphConnectivity<LfBus, LfBranch> connectivity) {
if (disabledBranch != null && disabledBranch.getBus1() != null && disabledBranch.getBus2() != null) {
connectivity.removeEdge(disabledBranch);
}
if (enabledBranch != null) {
connectivity.addEdge(enabledBranch.getBus1(), enabledBranch.getBus2(), enabledBranch);
}
disabledBranch.forEach(branch -> {
if (branch.getBus1() != null && branch.getBus2() != null) {
connectivity.removeEdge(branch);
}
});
enabledBranch.forEach(branch -> connectivity.addEdge(branch.getBus1(), branch.getBus2(), branch));
}

public static void updateBusesAndBranchStatus(GraphConnectivity<LfBus, LfBranch> connectivity) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,9 @@ public static void checkValidity(Network network, List<Action> actions) {

case TerminalsConnectionAction.NAME: {
TerminalsConnectionAction terminalsConnectionAction = (TerminalsConnectionAction) action;
if (network.getBranch(terminalsConnectionAction.getElementId()) == null) {
throw new PowsyblException("Branch '" + terminalsConnectionAction.getElementId() + NOT_FOUND);
if (network.getBranch(terminalsConnectionAction.getElementId()) == null &&
network.getThreeWindingsTransformer(terminalsConnectionAction.getElementId()) == null) {
throw new PowsyblException("Branch or three windings transformer '" + terminalsConnectionAction.getElementId() + NOT_FOUND);
}
break;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@ void findEnabledDisabledBranches(LfNetwork lfNetwork) {
LfBranch branch = lfNetwork.getBranchById(action.getSwitchId());
if (branch != null) {
if (action.isOpen()) {
setDisabledBranch(branch);
addDisabledBranch(branch);
} else {
setEnabledBranch(branch);
addEnabledBranch(branch);
}
} else {
LOGGER.warn("Switch action {}: branch matching switch id {} not found", action.getId(), action.getSwitchId());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.List;

/**
* @author Bertrand Rix {@literal <bertrand.rix at artelys.com>}
* @author Anne Tilloy {@literal <anne.tilloy at rte-france.com>}
Expand All @@ -32,15 +34,23 @@ public LfTerminalsConnectionAction(TerminalsConnectionAction action, LfNetwork l

@Override
void findEnabledDisabledBranches(LfNetwork lfNetwork) {
LfBranch branch = lfNetwork.getBranchById(action.getElementId());
if (branch != null && branch.getBus1() != null && branch.getBus2() != null) {
List<LfBranch> branches = lfNetwork.getBranchesByOriginalId(action.getElementId());
if (branches != null) {
branches.forEach(b -> applyEnabledDisabled(b, action));
} else {
LOGGER.warn("TerminalsConnectionAction action {}: branch or three windings transformer matching element id {} not found", action.getId(), action.getElementId());
}
}

void applyEnabledDisabled(LfBranch branch, TerminalsConnectionAction action) {
if (branch.getBus1() != null && branch.getBus2() != null) {
if (action.isOpen()) {
setDisabledBranch(branch);
addDisabledBranch(branch);
} else {
setEnabledBranch(branch);
addEnabledBranch(branch);
}
} else {
LOGGER.warn("TerminalsConnectionAction action {}: branch matching element id {} not found", action.getId(), action.getElementId());
LOGGER.warn("TerminalsConnectionAction action {}: branch matching element id {} has one missing bus", action.getId(), action.getElementId());
}
}
}
Loading
Loading