AbstractLfBranch.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.iidm.network.*;
import com.powsybl.openloadflow.network.*;
import com.powsybl.openloadflow.sa.LimitReductionManager;
import com.powsybl.openloadflow.util.Evaluable;
import com.powsybl.openloadflow.util.PerUnit;
import net.jafama.FastMath;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
import java.util.function.Supplier;
/**
* @author Geoffroy Jamgotchian {@literal <geoffroy.jamgotchian at rte-france.com>}
*/
public abstract class AbstractLfBranch extends AbstractElement implements LfBranch {
private static final Logger LOGGER = LoggerFactory.getLogger(AbstractLfBranch.class);
protected final LfBus bus1;
protected final LfBus bus2;
private List<LfLimit> currentLimits1;
private List<LfLimit> activePowerLimits1;
private List<LfLimit> apparentPowerLimits1;
private List<LfLimit> currentLimits2;
private List<LfLimit> activePowerLimits2;
private List<LfLimit> apparentPowerLimits2;
protected final PiModel piModel;
protected TransformerPhaseControl phaseControl;
protected boolean phaseControlEnabled = false;
protected TransformerVoltageControl voltageControl;
protected boolean voltageControlEnabled = false;
protected TransformerReactivePowerControl transformerReactivePowerControl;
static class ZeroImpedanceContext {
boolean spanningTreeEdge = false;
boolean zeroImpedance = false;
}
protected final Map<LoadFlowModel, ZeroImpedanceContext> zeroImpedanceContextByModel = new EnumMap<>(LoadFlowModel.class);
protected Evaluable a1;
private GeneratorReactivePowerControl generatorReactivePowerControl;
protected LfAsymLine asymLine;
private static final String LIMIT_TYPE_UNSUPPORTED_TEMPLATE = "Getting %s limits is not supported.";
protected AbstractLfBranch(LfNetwork network, LfBus bus1, LfBus bus2, PiModel piModel, LfNetworkParameters parameters) {
super(network);
this.bus1 = bus1;
this.bus2 = bus2;
this.piModel = Objects.requireNonNull(piModel);
this.piModel.setBranch(this);
for (LoadFlowModel loadFlowModel : LoadFlowModel.values()) {
zeroImpedanceContextByModel.put(loadFlowModel, new ZeroImpedanceContext());
}
if (!parameters.isMinImpedance()) {
for (LoadFlowModel loadFlowModel : LoadFlowModel.values()) {
zeroImpedanceContextByModel.get(loadFlowModel).zeroImpedance = isZeroImpedanceBranch(piModel, loadFlowModel, parameters.getLowImpedanceThreshold());
}
}
}
@Override
public Optional<ThreeSides> getOriginalSide() {
return Optional.empty();
}
/**
* Create the list of LfLimits from a LoadingLimits and a list of reductions.
* The resulting list will contain the permanent limit
*/
protected static List<LfLimit> createSortedLimitsList(LoadingLimits loadingLimits, LfBus bus, double[] limitReductions) {
List<LfLimit> sortedLimits = new ArrayList<>(3);
if (loadingLimits != null) {
double toPerUnit = getScaleForLimitType(loadingLimits.getLimitType(), bus);
int i = 0;
for (LoadingLimits.TemporaryLimit temporaryLimit : loadingLimits.getTemporaryLimits()) {
if (temporaryLimit.getAcceptableDuration() != 0) {
// it is not useful to add a limit with acceptable duration equal to zero as the only value plausible
// for this limit is infinity.
// https://javadoc.io/doc/com.powsybl/powsybl-core/latest/com/powsybl/iidm/network/CurrentLimits.html
double reduction = limitReductions.length == 0 ? 1d : limitReductions[i + 1]; // Temporary limit's reductions are stored starting from index 1 in `limitReductions`
double originalValuePerUnit = temporaryLimit.getValue() * toPerUnit;
sortedLimits.add(0, LfLimit.createTemporaryLimit(temporaryLimit.getName(), temporaryLimit.getAcceptableDuration(),
originalValuePerUnit, reduction));
}
i++;
}
double reduction = limitReductions.length == 0 ? 1d : limitReductions[0];
sortedLimits.add(LfLimit.createPermanentLimit(loadingLimits.getPermanentLimit() * toPerUnit, reduction));
}
if (sortedLimits.size() > 1) {
// we only make that fix if there is more than a permanent limit attached to the branch.
for (int i = sortedLimits.size() - 1; i > 0; i--) {
// From the permanent limit to the most serious temporary limit.
sortedLimits.get(i).setAcceptableDuration(sortedLimits.get(i - 1).getAcceptableDuration());
}
sortedLimits.get(0).setAcceptableDuration(0);
}
return sortedLimits;
}
@Override
public ElementType getType() {
return ElementType.BRANCH;
}
@Override
public LfBus getBus1() {
return bus1;
}
@Override
public LfBus getBus2() {
return bus2;
}
private List<LfLimit> getLimits1(LimitType type) {
switch (type) {
case ACTIVE_POWER -> {
return activePowerLimits1;
}
case APPARENT_POWER -> {
return apparentPowerLimits1;
}
case CURRENT -> {
return currentLimits1;
}
default -> throw new UnsupportedOperationException(String.format(LIMIT_TYPE_UNSUPPORTED_TEMPLATE, type.name()));
}
}
private void setLimits1(LimitType type, List<LfLimit> limits) {
switch (type) {
case ACTIVE_POWER -> activePowerLimits1 = limits;
case APPARENT_POWER -> apparentPowerLimits1 = limits;
case CURRENT -> currentLimits1 = limits;
default -> throw new UnsupportedOperationException(String.format(LIMIT_TYPE_UNSUPPORTED_TEMPLATE, type.name()));
}
}
public <T extends LoadingLimits> List<LfLimit> getLimits1(LimitType type, Supplier<Optional<T>> loadingLimitsSupplier, LimitReductionManager limitReductionManager) {
var limits = getLimits1(type);
if (limits == null) {
// It is possible to apply the reductions here since the only supported ContingencyContext for LimitReduction is ALL.
var loadingLimits = loadingLimitsSupplier.get().orElse(null);
limits = createSortedLimitsList(loadingLimits, bus1,
getLimitReductions(TwoSides.ONE, limitReductionManager, loadingLimits));
setLimits1(type, limits);
}
return limits;
}
private List<LfLimit> getLimits2(LimitType type) {
switch (type) {
case ACTIVE_POWER -> {
return activePowerLimits2;
}
case APPARENT_POWER -> {
return apparentPowerLimits2;
}
case CURRENT -> {
return currentLimits2;
}
default -> throw new UnsupportedOperationException(String.format(LIMIT_TYPE_UNSUPPORTED_TEMPLATE, type.name()));
}
}
private void setLimits2(LimitType type, List<LfLimit> limits) {
switch (type) {
case ACTIVE_POWER -> activePowerLimits2 = limits;
case APPARENT_POWER -> apparentPowerLimits2 = limits;
case CURRENT -> currentLimits2 = limits;
default -> throw new UnsupportedOperationException(String.format(LIMIT_TYPE_UNSUPPORTED_TEMPLATE, type.name()));
}
}
public <T extends LoadingLimits> List<LfLimit> getLimits2(LimitType type, Supplier<Optional<T>> loadingLimitsSupplier, LimitReductionManager limitReductionManager) {
var limits = getLimits2(type);
if (limits == null) {
// It is possible to apply the reductions here since the only supported ContingencyContext for LimitReduction is ALL.
var loadingLimits = loadingLimitsSupplier.get().orElse(null);
limits = createSortedLimitsList(loadingLimits, bus2,
getLimitReductions(TwoSides.TWO, limitReductionManager, loadingLimits));
setLimits2(type, limits);
}
return limits;
}
@Override
public PiModel getPiModel() {
return piModel;
}
@Override
public Optional<TransformerPhaseControl> getPhaseControl() {
return Optional.ofNullable(phaseControl);
}
@Override
public void setPhaseControl(TransformerPhaseControl phaseControl) {
this.phaseControl = phaseControl;
}
@Override
public boolean isPhaseController() {
return phaseControl != null && phaseControl.getControllerBranch() == this;
}
@Override
public boolean isPhaseControlled() {
return phaseControl != null && phaseControl.getControlledBranch() == this;
}
@Override
public boolean isPhaseControlEnabled() {
return phaseControlEnabled;
}
@Override
public void setPhaseControlEnabled(boolean phaseControlEnabled) {
if (this.phaseControlEnabled != phaseControlEnabled) {
this.phaseControlEnabled = phaseControlEnabled;
for (LfNetworkListener listener : network.getListeners()) {
listener.onTransformerPhaseControlChange(this, phaseControlEnabled);
}
}
}
protected void updateTapPosition(PhaseTapChanger ptc) {
int tapPosition = Transformers.findTapPosition(ptc, Math.toDegrees(getPiModel().getA1()));
ptc.setTapPosition(tapPosition);
}
protected void updateTapPosition(RatioTapChanger rtc, double ptcRho, double rho) {
Transformers.findTapPosition(rtc, ptcRho, rho).ifPresent(rtc::setTapPosition);
}
protected static double getScaleForLimitType(LimitType type, LfBus bus) {
switch (type) {
case ACTIVE_POWER,
APPARENT_POWER:
return 1.0 / PerUnit.SB;
case CURRENT:
return 1.0 / PerUnit.ib(bus.getNominalV());
case VOLTAGE:
default:
throw new UnsupportedOperationException(String.format("Getting scale for limit type %s is not supported.", type));
}
}
@Override
public Optional<TransformerVoltageControl> getVoltageControl() {
return Optional.ofNullable(voltageControl);
}
@Override
public boolean isVoltageController() {
return voltageControl != null;
}
@Override
public void setVoltageControl(TransformerVoltageControl transformerVoltageControl) {
this.voltageControl = transformerVoltageControl;
}
@Override
public boolean isVoltageControlEnabled() {
return voltageControlEnabled;
}
public void setVoltageControlEnabled(boolean voltageControlEnabled) {
if (this.voltageControlEnabled != voltageControlEnabled) {
this.voltageControlEnabled = voltageControlEnabled;
for (LfNetworkListener listener : network.getListeners()) {
listener.onTransformerVoltageControlChange(this, voltageControlEnabled);
}
}
}
@Override
public Optional<TransformerReactivePowerControl> getTransformerReactivePowerControl() {
return Optional.ofNullable(transformerReactivePowerControl);
}
@Override
public void setTransformerReactivePowerControl(TransformerReactivePowerControl transformerReactivePowerControl) {
this.transformerReactivePowerControl = transformerReactivePowerControl;
}
@Override
public boolean isTransformerReactivePowerController() {
return transformerReactivePowerControl != null && transformerReactivePowerControl.getControllerBranch() == this;
}
@Override
public boolean isTransformerReactivePowerControlled() {
return transformerReactivePowerControl != null && transformerReactivePowerControl.getControlledBranch() == this;
}
public double computeApparentPower1() {
double p = getP1().eval();
double q = getQ1().eval();
return FastMath.sqrt(p * p + q * q);
}
@Override
public double computeApparentPower2() {
double p = getP2().eval();
double q = getQ2().eval();
return FastMath.sqrt(p * p + q * q);
}
@Override
public boolean isZeroImpedance(LoadFlowModel loadFlowModel) {
return zeroImpedanceContextByModel.get(loadFlowModel).zeroImpedance;
}
@Override
public void setSpanningTreeEdge(LoadFlowModel loadFlowModel, boolean spanningTreeEdge) {
ZeroImpedanceContext context = zeroImpedanceContextByModel.get(loadFlowModel);
if (spanningTreeEdge != context.spanningTreeEdge) {
context.spanningTreeEdge = spanningTreeEdge;
for (LfNetworkListener listener : network.getListeners()) {
listener.onZeroImpedanceNetworkSpanningTreeChange(this, loadFlowModel, spanningTreeEdge);
}
}
}
@Override
public boolean isSpanningTreeEdge(LoadFlowModel loadFlowModel) {
network.updateZeroImpedanceCache(loadFlowModel);
return zeroImpedanceContextByModel.get(loadFlowModel).spanningTreeEdge;
}
@Override
public Evaluable getA1() {
return a1;
}
@Override
public void setA1(Evaluable a1) {
this.a1 = a1;
}
public Optional<GeneratorReactivePowerControl> getGeneratorReactivePowerControl() {
return Optional.ofNullable(generatorReactivePowerControl);
}
@Override
public void setGeneratorReactivePowerControl(GeneratorReactivePowerControl pGeneratorReactivePowerControl) {
this.generatorReactivePowerControl = Objects.requireNonNull(pGeneratorReactivePowerControl);
}
@Override
public boolean isConnectedAtBothSides() {
return isConnectedSide1() && isConnectedSide2();
}
@Override
public void setMinZ(double lowImpedanceThreshold) {
for (LoadFlowModel loadFlowModel : List.of(LoadFlowModel.AC, LoadFlowModel.DC)) {
if (piModel.setMinZ(lowImpedanceThreshold, loadFlowModel) ||
LoadFlowModel.DC.equals(loadFlowModel) && isZeroImpedance(loadFlowModel)) {
// Note: For DC load flow model, the min impedance has already been set by AC load flow model but
// the zero impedance field must still be updated.
LOGGER.trace("Branch {} has a low impedance in {}, set to min {}", getId(), loadFlowModel, lowImpedanceThreshold);
zeroImpedanceContextByModel.get(loadFlowModel).zeroImpedance = false;
}
}
}
private static boolean isZeroImpedanceBranch(PiModel piModel, LoadFlowModel loadFlowModel, double lowImpedanceThreshold) {
if (loadFlowModel == LoadFlowModel.DC) {
return FastMath.abs(piModel.getX()) < lowImpedanceThreshold;
} else {
return piModel.getZ() < lowImpedanceThreshold;
}
}
private void updateZeroImpedanceNetworks(boolean disabled, LoadFlowModel loadFlowModel) {
if (isZeroImpedance(loadFlowModel)) {
LfZeroImpedanceNetwork zn1 = bus1.getZeroImpedanceNetwork(loadFlowModel);
LfZeroImpedanceNetwork zn2 = bus2.getZeroImpedanceNetwork(loadFlowModel);
if (zn1 != null && zn2 != null) {
if (disabled) {
if (zn1 == zn2) {
// zero impedance network split (maybe)
zn1.removeBranchAndTryToSplit(this);
} else {
throw new IllegalStateException("Should not happen");
}
} else {
if (zn1 != zn2) {
// zero impedance network merge
LfZeroImpedanceNetwork.addBranchAndMerge(zn1, zn2, this);
} else {
// we need to add the branch again to zero impedance graph and update spanning tree as
// this branch might become part of spanning tree (and was not before because disabled)
zn1.addBranch(this);
}
}
}
}
}
@Override
public void setDisabled(boolean disabled) {
if (disabled != this.disabled) {
this.disabled = disabled;
notifyDisable();
if (bus1 != null && bus2 != null) {
updateZeroImpedanceNetworks(disabled, LoadFlowModel.AC);
updateZeroImpedanceNetworks(disabled, LoadFlowModel.DC);
}
}
}
@Override
public LfAsymLine getAsymLine() {
return asymLine;
}
@Override
public void setAsymLine(LfAsymLine asymLine) {
this.asymLine = asymLine;
}
@Override
public boolean isAsymmetric() {
if (asymLine != null) {
return asymLine.getAdmittanceMatrix().isCoupled();
}
return false;
}
}