Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Add switch actions in Fast DC Security Analysis #1153

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/**
* Copyright (c) 2024, 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.dc.fastdc;

import com.powsybl.openloadflow.dc.equations.ClosedBranchSide1DcFlowEquationTerm;
import com.powsybl.openloadflow.dc.equations.DcEquationType;
import com.powsybl.openloadflow.dc.equations.DcVariableType;
import com.powsybl.openloadflow.equations.EquationSystem;
import com.powsybl.openloadflow.network.ElementType;
import com.powsybl.openloadflow.network.LfBranch;

/**
* @author Pierre Arvy {@literal <[email protected]>}
*/
public final class ComputedSwitchBranchElement extends ComputedElement {

private final boolean enabled;

public ComputedSwitchBranchElement(LfBranch lfBranch, boolean enabled, EquationSystem<DcVariableType, DcEquationType> equationSystem) {
super(lfBranch, equationSystem.getEquationTerm(ElementType.BRANCH, lfBranch.getNum(), ClosedBranchSide1DcFlowEquationTerm.class));
this.enabled = enabled;
}

public boolean isEnabled() {
return enabled;
}
}
144 changes: 89 additions & 55 deletions src/main/java/com/powsybl/openloadflow/dc/fastdc/WoodburyEngine.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,26 +38,26 @@ public class WoodburyEngine {

private final DenseMatrix contingenciesStates;

private final List<ComputedTapPositionChangeElement> tapPositionChangeElements;
private final List<ComputedElement> actionElements;

private final DenseMatrix tapPositionChangeStates;
private final DenseMatrix actionsStates;

public WoodburyEngine(DcEquationSystemCreationParameters creationParameters, List<ComputedContingencyElement> contingencyElements,
DenseMatrix contingenciesStates) {
this.creationParameters = Objects.requireNonNull(creationParameters);
this.contingencyElements = Objects.requireNonNull(contingencyElements);
this.contingenciesStates = Objects.requireNonNull(contingenciesStates);
this.tapPositionChangeElements = Collections.emptyList();
this.tapPositionChangeStates = DenseMatrix.EMPTY;
this.actionElements = Collections.emptyList();
this.actionsStates = DenseMatrix.EMPTY;
}

public WoodburyEngine(DcEquationSystemCreationParameters creationParameters, List<ComputedContingencyElement> contingencyElements,
DenseMatrix contingenciesStates, List<ComputedTapPositionChangeElement> tapPositionChangeElements, DenseMatrix tapPositionChangeStates) {
DenseMatrix contingenciesStates, List<ComputedElement> actionElements, DenseMatrix actionsStates) {
this.creationParameters = Objects.requireNonNull(creationParameters);
this.contingencyElements = Objects.requireNonNull(contingencyElements);
this.contingenciesStates = Objects.requireNonNull(contingenciesStates);
this.tapPositionChangeElements = Objects.requireNonNull(tapPositionChangeElements);
this.tapPositionChangeStates = Objects.requireNonNull(tapPositionChangeStates);
this.actionElements = Objects.requireNonNull(actionElements);
this.actionsStates = Objects.requireNonNull(actionsStates);
}

/**
Expand All @@ -72,7 +72,7 @@ public static double[] runDcLoadFlowWithModifiedTargetVector(DcLoadFlowContext l
* A simplified version of DcLoadFlowEngine that supports on the fly bus and branch disabling, and pst actions.
* Note that it does not update the state vector and the network at the end (because we don't need it to just evaluate a few equations).
*/
public static double[] runDcLoadFlowWithModifiedTargetVector(DcLoadFlowContext loadFlowContext, DisabledNetwork disabledNetwork, ReportNode reportNode, List<LfAction> pstActions) {
public static double[] runDcLoadFlowWithModifiedTargetVector(DcLoadFlowContext loadFlowContext, DisabledNetwork disabledNetwork, ReportNode reportNode, List<LfAction> lfActions) {
Collection<LfBus> remainingBuses;
if (disabledNetwork.getBuses().isEmpty()) {
remainingBuses = loadFlowContext.getNetwork().getBuses();
Expand Down Expand Up @@ -109,9 +109,17 @@ public static double[] runDcLoadFlowWithModifiedTargetVector(DcLoadFlowContext l
.forEach(column -> targetVectorArray[column] = 0);
}

if (!pstActions.isEmpty()) {
if (!lfActions.isEmpty()) {
// set transformer phase shift to 0 for disconnected phase tap changers
lfActions.stream()
.map(LfAction::getDisabledBranch)
.filter(Objects::nonNull)
.flatMap(lfBranch -> loadFlowContext.getEquationSystem().getEquation(lfBranch.getNum(), DcEquationType.BRANCH_TARGET_ALPHA1).stream())
.map(Equation::getColumn)
.forEach(column -> targetVectorArray[column] = 0);

// set transformer phase shift to new shifting value
pstActions.stream()
lfActions.stream()
.map(LfAction::getTapPositionChange)
.filter(Objects::nonNull)
.forEach(tapPositionChange -> {
Expand Down Expand Up @@ -141,99 +149,125 @@ private double calculatePower(PiModel piModel) {
return AbstractClosedBranchDcFlowEquationTerm.computePower(creationParameters.isUseTransformerRatio(), creationParameters.getDcApproximationType(), piModel);
}

// TODO : refactor
double getRhsValue(DenseMatrix states, ClosedBranchSide1DcFlowEquationTerm p1, int columnState, ComputedElement computedElement) {
double newAlpha = 0;
if (computedElement instanceof ComputedTapPositionChangeElement) {
TapPositionChange tapPositionChange = ((ComputedTapPositionChangeElement) computedElement).getTapPositionChange();
PiModel newPiModel = tapPositionChange.getNewPiModel();
newAlpha = newPiModel.getA1();
} else if (computedElement instanceof ComputedSwitchBranchElement && ((ComputedSwitchBranchElement) computedElement).isEnabled()) {
newAlpha = computedElement.getLfBranch().getPiModel().getA1();
}
return states.get(p1.getPh1Var().getRow(), columnState) - states.get(p1.getPh2Var().getRow(), columnState) + newAlpha;
}

// TODO : refactor
double getMatrixValue(LfBranch lfBranch, ClosedBranchSide1DcFlowEquationTerm p1, ComputedElement element, boolean onDiagonal) {
if (element instanceof ComputedContingencyElement) {
double deltaX = onDiagonal ? 1d / calculatePower(lfBranch) : 0d;
return deltaX - (contingenciesStates.get(p1.getPh1Var().getRow(), element.getComputedElementIndex())
- contingenciesStates.get(p1.getPh2Var().getRow(), element.getComputedElementIndex()));
} else {
double deltaX = 0;
if (onDiagonal) {
double oldPower = 0d;
double newPower = 0d;
if (element instanceof ComputedTapPositionChangeElement) {
TapPositionChange tapPositionChange = ((ComputedTapPositionChangeElement) element).getTapPositionChange();
PiModel newPiModel = tapPositionChange.getNewPiModel();
oldPower = calculatePower(lfBranch);
newPower = calculatePower(newPiModel);
} else if (element instanceof ComputedSwitchBranchElement) {
if (((ComputedSwitchBranchElement) element).isEnabled()) {
newPower = calculatePower(lfBranch);
} else {
oldPower = calculatePower(lfBranch);
}
}
deltaX = 1d / (oldPower - newPower);
}
return deltaX - (actionsStates.get(p1.getPh1Var().getRow(), element.getComputedElementIndex())
- actionsStates.get(p1.getPh2Var().getRow(), element.getComputedElementIndex()));
}
}

/**
* Compute the flow transfer factors needed to calculate the post-contingency state values.
*/
private void setAlphas(DenseMatrix states, int columnState) {
if (contingencyElements.size() == 1 && tapPositionChangeElements.isEmpty()) {
if (contingencyElements.size() == 1 && actionElements.isEmpty()) {
ComputedContingencyElement element = contingencyElements.iterator().next();
LfBranch lfBranch = element.getLfBranch();
ClosedBranchSide1DcFlowEquationTerm p1 = element.getLfBranchEquation();

// we solve a*alpha = b
double a = 1d / calculatePower(lfBranch) - (contingenciesStates.get(p1.getPh1Var().getRow(), element.getComputedElementIndex())
- contingenciesStates.get(p1.getPh2Var().getRow(), element.getComputedElementIndex()));
double b = states.get(p1.getPh1Var().getRow(), columnState) - states.get(p1.getPh2Var().getRow(), columnState);
double a = getMatrixValue(lfBranch, p1, element, true);
double b = getRhsValue(states, p1, columnState, element);
element.setAlphaForWoodburyComputation(b / a);
} else if (contingencyElements.isEmpty() && tapPositionChangeElements.size() == 1) {
ComputedTapPositionChangeElement element = tapPositionChangeElements.iterator().next();
} else if (contingencyElements.isEmpty() && actionElements.size() == 1) {
// TODO : case when there is only one switching action, for now, works only when there is only one action on pst
ComputedElement element = actionElements.iterator().next();
LfBranch lfBranch = element.getLfBranch();
ClosedBranchSide1DcFlowEquationTerm p1 = element.getLfBranchEquation();

// we solve a*alpha = b
PiModel newPiModel = element.getTapPositionChange().getNewPiModel();
double deltaX = 1d / (calculatePower(lfBranch) - calculatePower(newPiModel));
double a = deltaX - (tapPositionChangeStates.get(p1.getPh1Var().getRow(), element.getComputedElementIndex())
- tapPositionChangeStates.get(p1.getPh2Var().getRow(), element.getComputedElementIndex()));

double newAlpha = newPiModel.getA1();
double b = states.get(p1.getPh1Var().getRow(), columnState) - states.get(p1.getPh2Var().getRow(), columnState) + newAlpha;
double a = getMatrixValue(lfBranch, p1, element, true);
double b = getRhsValue(states, p1, columnState, element);
element.setAlphaForWoodburyComputation(b / a);
} else {
// set local indexes of computed elements to use them in small matrix computation
ComputedElement.setLocalIndexes(contingencyElements);
ComputedElement.setLocalIndexes(tapPositionChangeElements);
int size = contingencyElements.size() + tapPositionChangeElements.size();
ComputedElement.setLocalIndexes(actionElements);
int size = contingencyElements.size() + actionElements.size();
DenseMatrix rhs = new DenseMatrix(size, 1);
DenseMatrix matrix = new DenseMatrix(size, size);

for (ComputedContingencyElement contingencyElement : contingencyElements) {
int i = contingencyElement.getLocalIndex();
LfBranch lfBranch = contingencyElement.getLfBranch();
ClosedBranchSide1DcFlowEquationTerm p1 = contingencyElement.getLfBranchEquation();
rhs.set(i, 0, states.get(p1.getPh1Var().getRow(), columnState) - states.get(p1.getPh2Var().getRow(), columnState));
rhs.set(i, 0, getRhsValue(states, p1, columnState, contingencyElement));

// loop on contingencies to fill top-left quadrant of the matrix
for (ComputedContingencyElement contingencyElement2 : contingencyElements) {
int j = contingencyElement2.getLocalIndex();
// if on the diagonal of the matrix, add variation of reactance
double deltaX = (i == j) ? 1d / calculatePower(lfBranch) : 0d;
double value = deltaX - (contingenciesStates.get(p1.getPh1Var().getRow(), contingencyElement2.getComputedElementIndex())
- contingenciesStates.get(p1.getPh2Var().getRow(), contingencyElement2.getComputedElementIndex()));
double value = getMatrixValue(lfBranch, p1, contingencyElement2, i == j);
matrix.set(i, j, value);
}

// loop on actions to fill top-right quadrant of the matrix
for (ComputedTapPositionChangeElement tapPositionChangeElement : tapPositionChangeElements) {
int j = contingencyElements.size() + tapPositionChangeElement.getLocalIndex();
double value = -(tapPositionChangeStates.get(p1.getPh1Var().getRow(), tapPositionChangeElement.getComputedElementIndex())
- tapPositionChangeStates.get(p1.getPh2Var().getRow(), tapPositionChangeElement.getComputedElementIndex()));
for (ComputedElement actionElement : actionElements) {
int j = contingencyElements.size() + actionElement.getLocalIndex();
double value = getMatrixValue(lfBranch, p1, actionElement, false);
matrix.set(i, j, value);
}
}

for (ComputedTapPositionChangeElement tapPositionChangeElement : tapPositionChangeElements) {
int i = contingencyElements.size() + tapPositionChangeElement.getLocalIndex();
LfBranch lfBranch = tapPositionChangeElement.getLfBranch();
ClosedBranchSide1DcFlowEquationTerm p1 = tapPositionChangeElement.getLfBranchEquation();

PiModel newPiModel = tapPositionChangeElement.getTapPositionChange().getNewPiModel();
double newAlpha = newPiModel.getA1();
rhs.set(i, 0, states.get(p1.getPh1Var().getRow(), columnState) - states.get(p1.getPh2Var().getRow(), columnState) + newAlpha);
for (ComputedElement actionElement : actionElements) {
int i = contingencyElements.size() + actionElement.getLocalIndex();
LfBranch lfBranch = actionElement.getLfBranch();
ClosedBranchSide1DcFlowEquationTerm p1 = actionElement.getLfBranchEquation();
rhs.set(i, 0, getRhsValue(states, p1, columnState, actionElement));

// loop on contingencies to fill bottom-left quadrant of the matrix
for (ComputedContingencyElement contingencyElement : contingencyElements) {
int j = contingencyElement.getLocalIndex();
double value = -(contingenciesStates.get(p1.getPh1Var().getRow(), contingencyElement.getComputedElementIndex())
- contingenciesStates.get(p1.getPh2Var().getRow(), contingencyElement.getComputedElementIndex()));
double value = getMatrixValue(lfBranch, p1, contingencyElement, false);
matrix.set(i, j, value);
}

// loop on actions to fill bottom-right quadrant of the matrix
for (ComputedTapPositionChangeElement tapPositionChangeElement2 : tapPositionChangeElements) {
int j = contingencyElements.size() + tapPositionChangeElement2.getLocalIndex();
// if on the diagonal of the matrix, add variation of reactance
double deltaX = (i == j) ? 1d / (calculatePower(lfBranch) - calculatePower(newPiModel)) : 0d;
double value = deltaX - (tapPositionChangeStates.get(p1.getPh1Var().getRow(), tapPositionChangeElement2.getComputedElementIndex())
- tapPositionChangeStates.get(p1.getPh2Var().getRow(), tapPositionChangeElement2.getComputedElementIndex()));
for (ComputedElement actionElement2 : actionElements) {
int j = contingencyElements.size() + actionElement2.getLocalIndex();
double value = getMatrixValue(lfBranch, p1, actionElement2, i == j);
matrix.set(i, j, value);
}
}
try (LUDecomposition lu = matrix.decomposeLU()) {
lu.solve(rhs); // rhs now contains state matrix
}
contingencyElements.forEach(element -> element.setAlphaForWoodburyComputation(rhs.get(element.getLocalIndex(), 0)));
tapPositionChangeElements.forEach(element -> element.setAlphaForWoodburyComputation(rhs.get(contingencyElements.size() + element.getLocalIndex(), 0)));
actionElements.forEach(element -> element.setAlphaForWoodburyComputation(rhs.get(contingencyElements.size() + element.getLocalIndex(), 0)));
}
}

Expand Down Expand Up @@ -268,9 +302,9 @@ public void toPostContingencyAndOperatorStrategyStates(double[] preContingencySt
postContingencyAndOperatorStrategyValue += contingencyElement.getAlphaForWoodburyComputation()
* contingenciesStates.get(rowIndex, contingencyElement.getComputedElementIndex());
}
for (ComputedTapPositionChangeElement tapPositionChangeElement : tapPositionChangeElements) {
postContingencyAndOperatorStrategyValue += tapPositionChangeElement.getAlphaForWoodburyComputation()
* tapPositionChangeStates.get(rowIndex, tapPositionChangeElement.getComputedElementIndex());
for (ComputedElement actionElement : actionElements) {
postContingencyAndOperatorStrategyValue += actionElement.getAlphaForWoodburyComputation()
* actionsStates.get(rowIndex, actionElement.getComputedElementIndex());
}
preContingencyStates[rowIndex] = postContingencyAndOperatorStrategyValue;
}
Expand Down
Loading
Loading