LfBranchImpl.java
/**
* Copyright (c) 2019, 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.impl;
import com.powsybl.commons.PowsyblException;
import com.powsybl.iidm.network.*;
import com.powsybl.iidm.network.extensions.LineFortescue;
import com.powsybl.openloadflow.network.*;
import com.powsybl.openloadflow.sa.LimitReductionManager;
import com.powsybl.openloadflow.util.PerUnit;
import com.powsybl.security.results.BranchResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
/**
* @author Geoffroy Jamgotchian {@literal <geoffroy.jamgotchian at rte-france.com>}
*/
public class LfBranchImpl extends AbstractImpedantLfBranch {
private static final Logger LOGGER = LoggerFactory.getLogger(LfBranchImpl.class);
private final Ref<Branch<?>> branchRef;
protected LfBranchImpl(LfNetwork network, LfBus bus1, LfBus bus2, PiModel piModel, Branch<?> branch, LfNetworkParameters parameters) {
super(network, bus1, bus2, piModel, parameters);
this.branchRef = Ref.create(branch, parameters.isCacheEnabled());
}
private static void createLineAsym(Line line, double zb, PiModel piModel, LfBranchImpl lfBranch) {
var extension = line.getExtension(LineFortescue.class);
if (extension != null) {
boolean openPhaseA = extension.isOpenPhaseA();
boolean openPhaseB = extension.isOpenPhaseB();
boolean openPhaseC = extension.isOpenPhaseC();
double rz = extension.getRz();
double xz = extension.getXz();
SimplePiModel piZeroComponent = new SimplePiModel()
.setR(rz / zb)
.setX(xz / zb);
SimplePiModel piPositiveComponent = new SimplePiModel()
.setR(piModel.getR())
.setX(piModel.getX());
SimplePiModel piNegativeComponent = new SimplePiModel()
.setR(piModel.getR())
.setX(piModel.getX());
lfBranch.setAsymLine(new LfAsymLine(piZeroComponent, piPositiveComponent, piNegativeComponent,
openPhaseA, openPhaseB, openPhaseC));
}
}
private static LfBranchImpl createLine(Line line, LfNetwork network, LfBus bus1, LfBus bus2, LfNetworkParameters parameters) {
double r1;
double r;
double x;
double g1;
double b1;
double g2;
double b2;
double zb;
if (parameters.getLinePerUnitMode() == LinePerUnitMode.RATIO) {
double nominalV2 = line.getTerminal2().getVoltageLevel().getNominalV();
zb = PerUnit.zb(nominalV2);
r1 = 1 / Transformers.getRatioPerUnitBase(line);
r = line.getR() / zb;
x = line.getX() / zb;
g1 = line.getG1() * zb;
g2 = line.getG2() * zb;
b1 = line.getB1() * zb;
b2 = line.getB2() * zb;
} else { // IMPEDANCE
r1 = 1;
double zSquare = line.getR() * line.getR() + line.getX() * line.getX();
if (zSquare == 0) {
r = 0;
x = 0;
g1 = 0;
b1 = 0;
g2 = 0;
b2 = 0;
zb = 0;
} else {
double nominalV1 = line.getTerminal1().getVoltageLevel().getNominalV();
double nominalV2 = line.getTerminal2().getVoltageLevel().getNominalV();
double g = line.getR() / zSquare;
double b = -line.getX() / zSquare;
zb = nominalV1 * nominalV2 / PerUnit.SB;
r = line.getR() / zb;
x = line.getX() / zb;
g1 = (line.getG1() * nominalV1 * nominalV1 + g * nominalV1 * (nominalV1 - nominalV2)) / PerUnit.SB;
b1 = (line.getB1() * nominalV1 * nominalV1 + b * nominalV1 * (nominalV1 - nominalV2)) / PerUnit.SB;
g2 = (line.getG2() * nominalV2 * nominalV2 + g * nominalV2 * (nominalV2 - nominalV1)) / PerUnit.SB;
b2 = (line.getB2() * nominalV2 * nominalV2 + b * nominalV2 * (nominalV2 - nominalV1)) / PerUnit.SB;
}
}
PiModel piModel = new SimplePiModel()
.setR1(r1)
.setR(r)
.setX(x)
.setG1(g1)
.setG2(g2)
.setB1(b1)
.setB2(b2);
LfBranchImpl lfBranch = new LfBranchImpl(network, bus1, bus2, piModel, line, parameters);
if (parameters.isAsymmetrical()) {
createLineAsym(line, zb, piModel, lfBranch);
}
return lfBranch;
}
private static LfBranchImpl createTransformer(TwoWindingsTransformer twt, LfNetwork network, LfBus bus1, LfBus bus2,
boolean retainPtc, boolean retainRtc, LfNetworkParameters parameters) {
PiModel piModel = null;
double baseRatio = Transformers.getRatioPerUnitBase(twt);
double nominalV2 = twt.getTerminal2().getVoltageLevel().getNominalV();
double zb = PerUnit.zb(nominalV2);
PhaseTapChanger ptc = twt.getPhaseTapChanger();
if (ptc != null
&& (ptc.isRegulating()
&& ptc.getRegulationMode() != PhaseTapChanger.RegulationMode.FIXED_TAP || retainPtc)) {
// we have a phase control, whatever we also have a voltage control or not, we create a pi model array
// based on phase taps mixed with voltage current tap
Integer rtcPosition = Transformers.getCurrentPosition(twt.getRatioTapChanger());
List<PiModel> models = new ArrayList<>();
for (int ptcPosition = ptc.getLowTapPosition(); ptcPosition <= ptc.getHighTapPosition(); ptcPosition++) {
Transformers.TapCharacteristics tapCharacteristics = Transformers.getTapCharacteristics(twt, rtcPosition, ptcPosition);
models.add(Transformers.createPiModel(tapCharacteristics, zb, baseRatio, parameters.isTwtSplitShuntAdmittance()));
}
piModel = new PiModelArray(models, ptc.getLowTapPosition(), ptc.getTapPosition());
}
RatioTapChanger rtc = twt.getRatioTapChanger();
if (rtc != null && (rtc.isRegulating() && rtc.hasLoadTapChangingCapabilities() || retainRtc)) {
if (piModel == null) {
// we have a voltage control, we create a pi model array based on voltage taps mixed with phase current
// tap
Integer ptcPosition = Transformers.getCurrentPosition(twt.getPhaseTapChanger());
List<PiModel> models = new ArrayList<>();
for (int rtcPosition = rtc.getLowTapPosition(); rtcPosition <= rtc.getHighTapPosition(); rtcPosition++) {
Transformers.TapCharacteristics tapCharacteristics = Transformers.getTapCharacteristics(twt, rtcPosition, ptcPosition);
models.add(Transformers.createPiModel(tapCharacteristics, zb, baseRatio, parameters.isTwtSplitShuntAdmittance()));
}
piModel = new PiModelArray(models, rtc.getLowTapPosition(), rtc.getTapPosition());
} else {
throw new PowsyblException("Voltage and phase control on same branch '" + twt.getId() + "' is not yet supported");
}
}
if (piModel == null) {
// we don't have any phase or voltage control, we create a simple pi model (single tap) based on phase current
// tap and voltage current tap
Transformers.TapCharacteristics tapCharacteristics = Transformers.getTapCharacteristics(twt);
piModel = Transformers.createPiModel(tapCharacteristics, zb, baseRatio, parameters.isTwtSplitShuntAdmittance());
}
return new LfBranchImpl(network, bus1, bus2, piModel, twt, parameters);
}
public static LfBranchImpl create(Branch<?> branch, LfNetwork network, LfBus bus1, LfBus bus2, LfTopoConfig topoConfig,
LfNetworkParameters parameters) {
Objects.requireNonNull(branch);
Objects.requireNonNull(network);
Objects.requireNonNull(topoConfig);
Objects.requireNonNull(parameters);
LfBranchImpl lfBranch;
if (branch instanceof Line line) {
lfBranch = createLine(line, network, bus1, bus2, parameters);
} else if (branch instanceof TwoWindingsTransformer twt) {
lfBranch = createTransformer(twt, network, bus1, bus2, topoConfig.isRetainedPtc(twt.getId()),
topoConfig.isRetainedRtc(twt.getId()), parameters);
} else {
throw new PowsyblException("Unsupported type of branch for flow equations of branch: " + branch.getId());
}
if (bus1 != null && topoConfig.getBranchIdsOpenableSide1().contains(lfBranch.getId())) {
lfBranch.setDisconnectionAllowedSide1(true);
}
if (bus2 != null && topoConfig.getBranchIdsOpenableSide2().contains(lfBranch.getId())) {
lfBranch.setDisconnectionAllowedSide2(true);
}
return lfBranch;
}
private Branch<?> getBranch() {
return branchRef.get();
}
@Override
public String getId() {
return getBranch().getId();
}
@Override
public BranchType getBranchType() {
return getBranch() instanceof Line ? BranchType.LINE : BranchType.TRANSFO_2;
}
@Override
public boolean hasPhaseControllerCapability() {
var branch = getBranch();
return branch.getType() == IdentifiableType.TWO_WINDINGS_TRANSFORMER
&& ((TwoWindingsTransformer) branch).getPhaseTapChanger() != null;
}
@Override
public List<BranchResult> createBranchResult(double preContingencyBranchP1, double preContingencyBranchOfContingencyP1, boolean createExtension) {
var branch = getBranch();
double flowTransfer = Double.NaN;
if (!Double.isNaN(preContingencyBranchP1) && !Double.isNaN(preContingencyBranchOfContingencyP1)) {
flowTransfer = (p1.eval() * PerUnit.SB - preContingencyBranchP1) / preContingencyBranchOfContingencyP1;
}
double currentScale1 = PerUnit.ib(branch.getTerminal1().getVoltageLevel().getNominalV());
double currentScale2 = PerUnit.ib(branch.getTerminal2().getVoltageLevel().getNominalV());
var branchResult = new BranchResult(getId(), p1.eval() * PerUnit.SB, q1.eval() * PerUnit.SB, currentScale1 * i1.eval(),
p2.eval() * PerUnit.SB, q2.eval() * PerUnit.SB, currentScale2 * i2.eval(), flowTransfer);
if (createExtension) {
branchResult.addExtension(OlfBranchResult.class, new OlfBranchResult(piModel.getR1(), piModel.getContinuousR1(),
getV1() * branch.getTerminal1().getVoltageLevel().getNominalV(),
getV2() * branch.getTerminal2().getVoltageLevel().getNominalV(),
Math.toDegrees(getAngle1()),
Math.toDegrees(getAngle2())));
}
return List.of(branchResult);
}
@Override
public List<LfLimit> getLimits1(final LimitType type, LimitReductionManager limitReductionManager) {
switch (type) {
case ACTIVE_POWER:
return getLimits1(type, () -> getBranch().getActivePowerLimits1(), limitReductionManager);
case APPARENT_POWER:
return getLimits1(type, () -> getBranch().getApparentPowerLimits1(), limitReductionManager);
case CURRENT:
return getLimits1(type, () -> getBranch().getCurrentLimits1(), limitReductionManager);
case VOLTAGE:
default:
throw new UnsupportedOperationException(String.format("Getting %s limits is not supported.", type.name()));
}
}
@Override
public List<LfLimit> getLimits2(final LimitType type, LimitReductionManager limitReductionManager) {
switch (type) {
case ACTIVE_POWER:
return getLimits2(type, () -> getBranch().getActivePowerLimits2(), limitReductionManager);
case APPARENT_POWER:
return getLimits2(type, () -> getBranch().getApparentPowerLimits2(), limitReductionManager);
case CURRENT:
return getLimits2(type, () -> getBranch().getCurrentLimits2(), limitReductionManager);
case VOLTAGE:
default:
throw new UnsupportedOperationException(String.format("Getting %s limits is not supported.", type.name()));
}
}
/**
* <p>Create the list of the limit reductions to use for each limit:
* <ul>
* <li>the value for the permanent limit is stored at index 0, values for the temporary limits are stored from index 1;</li>
* <li>if no reduction is detected for a limit, the corresponding value is set to 1;</li>
* <li>if several LimitReductions are applicable for the same limit, the last one is used;</li>
* </ul></p>
* <p>This method may return an empty list when no reduction apply.</p>
*/
@Override
public double[] getLimitReductions(TwoSides side, LimitReductionManager limitReductionManager, LoadingLimits limits) {
if (limits == null) {
return new double[] {};
}
if (limits.getLimitType() != LimitType.CURRENT) {
return new double[] {};
}
if (limitReductionManager == null || limitReductionManager.isEmpty()) {
return new double[] {};
}
// Initialize the array of the reductions with 1s
double[] limitReductions = new double[limits.getTemporaryLimits().size() + 1];
Arrays.fill(limitReductions, 1.);
double nominalV = branchRef.get().getTerminal(side).getVoltageLevel().getNominalV();
for (LimitReductionManager.TerminalLimitReduction terminalLimitReduction : limitReductionManager.getTerminalLimitReductions()) {
if (terminalLimitReduction.nominalV().contains(nominalV)) {
if (terminalLimitReduction.isPermanent()) {
limitReductions[0] = terminalLimitReduction.reduction();
}
if (terminalLimitReduction.acceptableDuration() != null) {
int i = 1; // temporary limit's reductions will be stored starting from index 1
for (LoadingLimits.TemporaryLimit temporaryLimit : limits.getTemporaryLimits()) {
if (terminalLimitReduction.acceptableDuration().contains(temporaryLimit.getAcceptableDuration())) {
limitReductions[i] = terminalLimitReduction.reduction();
}
i++;
}
}
}
}
return limitReductions;
}
@Override
public void updateState(LfNetworkStateUpdateParameters parameters, LfNetworkUpdateReport updateReport) {
var branch = getBranch();
if (isDisabled()) {
updateFlows(Double.NaN, Double.NaN, Double.NaN, Double.NaN);
} else {
updateFlows(p1.eval(), q1.eval(), p2.eval(), q2.eval());
}
// in case of automation system simulation we might need to update IIDM terminals connection status
if (parameters.isSimulateAutomationSystems()) {
boolean connectedSide1Before = branch.getTerminal1().isConnected();
boolean connectedSide2Before = branch.getTerminal2().isConnected();
boolean connectedSide1After = !isDisabled() && isConnectedSide1();
boolean connectedSide2After = !isDisabled() && isConnectedSide2();
if (connectedSide1Before && !connectedSide1After) {
LOGGER.warn("Disconnect terminal 1 of branch '{}'", branch.getId());
branch.getTerminal1().disconnect();
updateReport.disconnectedBranchSide1Count++;
}
if (!connectedSide1Before && connectedSide1After) {
LOGGER.warn("Connect terminal 1 of branch '{}'", branch.getId());
branch.getTerminal1().connect();
updateReport.connectedBranchSide1Count++;
}
if (connectedSide2Before && !connectedSide2After) {
LOGGER.warn("Disconnect terminal 2 of branch '{}'", branch.getId());
branch.getTerminal2().disconnect();
updateReport.disconnectedBranchSide2Count++;
}
if (!connectedSide2Before && connectedSide2After) {
LOGGER.warn("Connect terminal 2 of branch '{}'", branch.getId());
branch.getTerminal2().connect();
updateReport.connectedBranchSide2Count++;
}
}
if (parameters.isPhaseShifterRegulationOn() && isPhaseController()) {
// it means there is a regulating phase tap changer located on that branch
updateTapPosition(((TwoWindingsTransformer) branch).getPhaseTapChanger());
}
if (parameters.isTransformerVoltageControlOn() && isVoltageController()
|| parameters.isTransformerReactivePowerControlOn() && isTransformerReactivePowerController()) { // it means there is a regulating ratio tap changer
TwoWindingsTransformer twt = (TwoWindingsTransformer) branch;
RatioTapChanger rtc = twt.getRatioTapChanger();
double baseRatio = Transformers.getRatioPerUnitBase(twt);
double rho = getPiModel().getR1() * twt.getRatedU1() / twt.getRatedU2() * baseRatio;
double ptcRho = twt.getPhaseTapChanger() != null ? twt.getPhaseTapChanger().getCurrentStep().getRho() : 1;
updateTapPosition(rtc, ptcRho, rho);
}
}
@Override
public void updateFlows(double p1, double q1, double p2, double q2) {
var branch = getBranch();
branch.getTerminal1().setP(p1 * PerUnit.SB)
.setQ(q1 * PerUnit.SB);
branch.getTerminal2().setP(p2 * PerUnit.SB)
.setQ(q2 * PerUnit.SB);
}
}