com.powsybl.openloadflow.network.PiModelArray Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of powsybl-open-loadflow Show documentation
Show all versions of powsybl-open-loadflow Show documentation
An open source loadflow based on PowSyBl
The newest version!
/**
* Copyright (c) 2020, 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 org.apache.commons.lang3.Range;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.function.ToDoubleFunction;
/**
* @author Geoffroy Jamgotchian {@literal }
*/
public class PiModelArray implements PiModel {
private final List models;
private final int lowTapPosition;
private int tapPositionIndex;
private double a1 = Double.NaN; // override a1 at current tap position if not NaN
private double r1 = Double.NaN; // override r1 at current tap position if not NaN
private double continuousR1 = Double.NaN;
private LfBranch branch;
private final double minR1;
private final double maxR1;
public PiModelArray(List models, int lowTapPosition, int tapPosition) {
this.models = Objects.requireNonNull(models);
this.lowTapPosition = lowTapPosition;
tapPositionIndex = tapPosition - lowTapPosition;
minR1 = this.models.stream().mapToDouble(PiModel::getMinR1).min().orElseThrow();
maxR1 = this.models.stream().mapToDouble(PiModel::getMaxR1).max().orElseThrow();
}
List getModels() {
return models;
}
private PiModel getModel() {
return models.get(tapPositionIndex);
}
public PiModel getModel(int tapPositionIndex) {
return models.get(tapPositionIndex);
}
@Override
public double getR() {
return getModel().getR();
}
@Override
public PiModel setR(double r) {
return getModel().setR(r);
}
@Override
public double getX() {
return getModel().getX();
}
@Override
public PiModel setX(double x) {
return getModel().setX(x);
}
@Override
public double getZ() {
return getModel().getZ();
}
@Override
public double getY() {
return getModel().getY();
}
@Override
public double getKsi() {
return getModel().getKsi();
}
@Override
public double getG1() {
return getModel().getG1();
}
@Override
public double getB1() {
return getModel().getB1();
}
@Override
public double getG2() {
return getModel().getG2();
}
@Override
public double getB2() {
return getModel().getB2();
}
public double getModifiedR1() {
return r1;
}
public double getModifiedA1() {
return a1;
}
@Override
public double getR1() {
return Double.isNaN(r1) ? getModel().getR1() : r1;
}
@Override
public double getContinuousR1() {
return continuousR1;
}
@Override
public double getA1() {
return Double.isNaN(a1) ? getModel().getA1() : a1;
}
@Override
public PiModelArray setA1(double a1) {
this.a1 = a1;
return this;
}
@Override
public PiModelArray setR1(double r1) {
this.r1 = r1;
return this;
}
interface TapPositionFinder {
int find(List models, int tapPositionIndex, ToDoubleFunction valueGetter,
Range positionIndexRange, int maxTapShift);
}
static class ClosestTapPositionFinder implements TapPositionFinder {
private final double targetValue;
ClosestTapPositionFinder(double targetValue) {
this.targetValue = targetValue;
}
@Override
public int find(List models, int tapPositionIndex, ToDoubleFunction valueGetter,
Range positionIndexRange, int maxTapShift) {
int closestTapPositionIndex = tapPositionIndex;
double smallestDistance = Math.abs(targetValue - valueGetter.applyAsDouble(models.get(tapPositionIndex)));
for (int i = positionIndexRange.getMinimum(); i <= positionIndexRange.getMaximum(); i++) {
if (Math.abs(i - tapPositionIndex) > maxTapShift) { // we are not allowed to go further than maxTapShift positions
continue;
}
double distance = Math.abs(targetValue - valueGetter.applyAsDouble(models.get(i)));
if (distance < smallestDistance) {
closestTapPositionIndex = i;
smallestDistance = distance;
}
}
return closestTapPositionIndex;
}
}
static class FirstTapPositionAboveFinder implements TapPositionFinder {
private final double valueShift;
FirstTapPositionAboveFinder(double valueShift) {
this.valueShift = valueShift;
}
private static boolean isDescending(int n1, int n2, double valueShift, List models, ToDoubleFunction valueGetter) {
return valueShift > 0 ? valueGetter.applyAsDouble(models.get(n1)) < valueGetter.applyAsDouble(models.get(n2))
: valueGetter.applyAsDouble(models.get(n1)) > valueGetter.applyAsDouble(models.get(n2));
}
static int nextTapPositionIndex(int i, double valueShift, List models, ToDoubleFunction valueGetter) {
if (valueShift == 0) {
return -1;
}
if (i == 0 && isDescending(i + 1, i, valueShift, models, valueGetter)) {
return -1;
}
if (i > 0 && isDescending(i, i - 1, valueShift, models, valueGetter)) {
return i - 1;
}
if (i < models.size() - 1 && isDescending(i, i + 1, valueShift, models, valueGetter)) {
return i + 1;
}
if (i == models.size() - 1 && isDescending(i, i - 1, valueShift, models, valueGetter)) {
return -1;
}
return -1;
}
@Override
public int find(List models, int tapPositionIndex, ToDoubleFunction valueGetter,
Range positionIndexRange, int maxTapShift) {
int currentTapPositionIndex = tapPositionIndex;
double remainingValueShift = valueShift;
int nextTapPositionIndex;
while ((nextTapPositionIndex = nextTapPositionIndex(currentTapPositionIndex, remainingValueShift, models, valueGetter)) != -1) {
if (!positionIndexRange.contains(nextTapPositionIndex)
|| Math.abs(nextTapPositionIndex - tapPositionIndex) > maxTapShift) {
break;
}
double value = valueGetter.applyAsDouble(models.get(currentTapPositionIndex));
double nextValue = valueGetter.applyAsDouble(models.get(nextTapPositionIndex));
currentTapPositionIndex = nextTapPositionIndex;
// stop when shift is not enough to go to next position
if (remainingValueShift < 0 && value + remainingValueShift > nextValue
|| remainingValueShift > 0 && value + remainingValueShift < nextValue) {
break;
}
remainingValueShift -= nextValue - value;
}
return currentTapPositionIndex;
}
}
private Optional updateTapPosition(ToDoubleFunction valueGetter, Range positionIndexRange,
int maxTapShift, TapPositionFinder finder) {
int oldPositionIndex = tapPositionIndex;
// find tap position with the closest value without exceeding the maximum of taps to switch.
tapPositionIndex = finder.find(models, tapPositionIndex, valueGetter, positionIndexRange, maxTapShift);
if (tapPositionIndex != oldPositionIndex) {
for (LfNetworkListener listener : branch.getNetwork().getListeners()) {
listener.onTapPositionChange(branch, lowTapPosition + oldPositionIndex, lowTapPosition + tapPositionIndex);
}
return Optional.of(tapPositionIndex - oldPositionIndex > 0 ? Direction.INCREASE : Direction.DECREASE);
}
return Optional.empty();
}
private Range getAllowedPositionIndexRange(AllowedDirection allowedDirection) {
switch (allowedDirection) {
case INCREASE:
return Range.of(tapPositionIndex, models.size() - 1);
case DECREASE:
return Range.of(0, tapPositionIndex);
case BOTH:
return Range.of(0, models.size() - 1);
default:
throw new IllegalStateException("Unknown direction: " + allowedDirection);
}
}
@Override
public void roundA1ToClosestTap() {
if (Double.isNaN(a1)) {
return; // nothing to do because a1 has not been modified
}
// find tap position with the closest a1 value
updateTapPosition(PiModel::getA1, getAllowedPositionIndexRange(AllowedDirection.BOTH), Integer.MAX_VALUE,
new ClosestTapPositionFinder(a1));
a1 = Double.NaN;
}
@Override
public void roundR1ToClosestTap() {
if (Double.isNaN(r1)) {
return; // nothing to do because r1 has not been modified
}
// find tap position with the closest r1 value
updateTapPosition(PiModel::getR1, getAllowedPositionIndexRange(AllowedDirection.BOTH), Integer.MAX_VALUE,
new ClosestTapPositionFinder(r1));
continuousR1 = r1;
r1 = Double.NaN;
}
@Override
public boolean shiftOneTapPositionToChangeA1(Direction direction) {
// an increase direction means that A1 should increase.
// a decrease direction means that A1 should decrease.
double currentA1 = getA1();
int oldTapPositionIndex = tapPositionIndex;
if (tapPositionIndex < models.size() - 1) {
double nextA1 = models.get(tapPositionIndex + 1).getA1(); // abs?
if (direction == Direction.INCREASE && nextA1 > currentA1
|| direction == Direction.DECREASE && nextA1 < currentA1) {
tapPositionIndex++;
}
}
if (tapPositionIndex > 0) {
double previousA1 = models.get(tapPositionIndex - 1).getA1(); // abs?
if (direction == Direction.INCREASE && previousA1 > currentA1
|| direction == Direction.DECREASE && previousA1 < currentA1) {
tapPositionIndex--;
}
}
if (tapPositionIndex != oldTapPositionIndex) {
a1 = Double.NaN;
for (LfNetworkListener listener : branch.getNetwork().getListeners()) {
listener.onTapPositionChange(branch, lowTapPosition + oldTapPositionIndex, lowTapPosition + tapPositionIndex);
}
return true;
}
return false;
}
@Override
public Optional updateTapPositionToReachNewR1(double deltaR1, int maxTapShift, AllowedDirection allowedDirection) {
double newR1 = getR1() + deltaR1;
Range positionIndexRange = getAllowedPositionIndexRange(allowedDirection);
Optional direction = updateTapPosition(PiModel::getR1, positionIndexRange, maxTapShift,
new ClosestTapPositionFinder(newR1));
if (direction.isPresent()) {
r1 = Double.NaN;
}
return direction;
}
@Override
public Optional updateTapPositionToExceedNewA1(double deltaA1, int maxTapShift, AllowedDirection allowedDirection) {
Range positionIndexRange = getAllowedPositionIndexRange(allowedDirection);
Optional direction = updateTapPosition(PiModel::getA1, positionIndexRange, maxTapShift,
new FirstTapPositionAboveFinder(deltaA1));
if (direction.isPresent()) {
a1 = Double.NaN;
}
return direction;
}
@Override
public Optional updateTapPositionToReachNewA1(double deltaA1, int maxTapShift, AllowedDirection allowedDirection) {
double newA1 = getA1() + deltaA1;
Range positionIndexRange = getAllowedPositionIndexRange(allowedDirection);
Optional direction = updateTapPosition(PiModel::getA1, positionIndexRange, maxTapShift,
new ClosestTapPositionFinder(newA1));
if (direction.isPresent()) {
a1 = Double.NaN;
}
return direction;
}
@Override
public boolean setMinZ(double minZ, LoadFlowModel loadFlowModel) {
boolean done = false;
for (PiModel model : models) {
done |= model.setMinZ(minZ, loadFlowModel);
}
return done;
}
@Override
public void setBranch(LfBranch branch) {
this.branch = Objects.requireNonNull(branch);
}
@Override
public int getTapPosition() {
return lowTapPosition + tapPositionIndex;
}
@Override
public PiModel setTapPosition(int tapPosition) {
Range tapPositionRange = getTapPositionRange();
if (!tapPositionRange.contains(tapPosition)) {
throw new IllegalArgumentException("Tap position " + tapPosition + " out of range " + tapPositionRange);
}
if (tapPosition - lowTapPosition != tapPositionIndex) {
int oldTapPositionIndex = tapPositionIndex;
tapPositionIndex = tapPosition - lowTapPosition;
r1 = Double.NaN;
continuousR1 = Double.NaN;
a1 = Double.NaN;
for (LfNetworkListener listener : branch.getNetwork().getListeners()) {
listener.onTapPositionChange(branch, lowTapPosition + oldTapPositionIndex, tapPosition);
}
}
return this;
}
@Override
public double getMinR1() {
return minR1;
}
@Override
public double getMaxR1() {
return maxR1;
}
@Override
public Range getTapPositionRange() {
return Range.of(lowTapPosition, lowTapPosition + models.size() - 1);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy