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] Extrapolation of getMinQ and getMaxQ for reactive capability curve #3250

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -70,4 +70,15 @@ public interface Point {
*/
double getMaxP();

/**
* Get the reactive power minimum value of the curve (with the possibility of extrapolating slope of reactive
* limits outside active limits)
*/
double getMinQ(double p, boolean extrapolateReactiveLimitSlope);

SylvestreSakti marked this conversation as resolved.
Show resolved Hide resolved
/**
* Get the reactive power maximum value of the curve (with the possibility of extrapolating slope of reactive
* limits outside active limits)
*/
double getMaxQ(double p, boolean extrapolateReactiveLimitSlope);
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,22 @@

import com.powsybl.iidm.network.ReactiveCapabilityCurve;
import com.powsybl.iidm.network.ReactiveLimitsKind;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.TreeMap;

/**
*
* @author Geoffroy Jamgotchian {@literal <geoffroy.jamgotchian at rte-france.com>}
*/
class ReactiveCapabilityCurveImpl implements ReactiveCapabilityCurve {

private static final Logger LOGGER = LoggerFactory.getLogger(ReactiveCapabilityCurveImpl.class);
private int warningCount = 0;

static class PointImpl implements Point {

private double p;
Expand Down Expand Up @@ -54,10 +58,42 @@ public double getMaxQ() {

private final TreeMap<Double, Point> points;

ReactiveCapabilityCurveImpl(TreeMap<Double, Point> points) {
private static void checkPointsSize(TreeMap<Double, Point> points) {
if (points.size() < 2) {
throw new IllegalStateException("Points size must be >= 2");
throw new IllegalStateException("points size should be >= 2");
}
vidaldid-rte marked this conversation as resolved.
Show resolved Hide resolved
}

private Point extrapolateReactiveLimitsSlope(double p, Point pBound) {
double minQ;
double maxQ;
Point pbis;
if (p < pBound.getP()) {
// Extrapolate reactive limits slope below min active power limit (bound_p = min active power limit)
pbis = points.higherEntry(points.firstKey()).getValue(); // p < bound_p < pbis
} else if (p > pBound.getP()) {
// Extrapolate reactive limits slope above max active power limit (bound_p = max active power limit)
pbis = points.lowerEntry(points.lastKey()).getValue(); // pbis < bound_p < p
} else {
throw new IllegalStateException();
}
double slopeMinQ = (pbis.getMinQ() - pBound.getMinQ()) / (pbis.getP() - pBound.getP());
double slopeMaxQ = (pbis.getMaxQ() - pBound.getMaxQ()) / (pbis.getP() - pBound.getP());
minQ = pBound.getMinQ() + slopeMinQ * (p - pBound.getP());
maxQ = pBound.getMaxQ() + slopeMaxQ * (p - pBound.getP());
if (minQ <= maxQ) {
return new PointImpl(p, minQ, maxQ);
} else { // Corner case of intersecting reactive limits when extrapolated
double limitQ = (minQ + maxQ) / 2;
if (warningCount++ % 100 == 0) { // Warn message only every 100 calls to avoid logging overflow
LOGGER.warn("PQ diagram extrapolation leads to minQ > maxQ ({} > {}) for P = {} => changing to minQ = maxQ = {}", minQ, maxQ, p, limitQ);
}
return new PointImpl(p, limitQ, limitQ); // Returning the mean as limits minQ and maxQ
}
}

ReactiveCapabilityCurveImpl(TreeMap<Double, Point> points) {
checkPointsSize(points);
this.points = points;
}

Expand Down Expand Up @@ -88,51 +124,63 @@ public ReactiveLimitsKind getKind() {

@Override
public double getMinQ(double p) {
if (points.size() < 2) {
throw new IllegalStateException("points size should be >= 2");
}
return getMinQ(p, false);
}

@Override
public double getMaxQ(double p) {
return getMaxQ(p, false);
}

@Override
public double getMinQ(double p, boolean extrapolateReactiveLimitSlope) {
checkPointsSize(points);
SylvestreSakti marked this conversation as resolved.
Show resolved Hide resolved

Point pt = points.get(p);
if (pt != null) {
return pt.getMinQ();
}

Map.Entry<Double, Point> e1 = points.floorEntry(p);
Map.Entry<Double, Point> e2 = points.ceilingEntry(p);
if (e1 == null && e2 != null) {
Point pMin = e2.getValue();
return extrapolateReactiveLimitSlope ? extrapolateReactiveLimitsSlope(p, pMin).getMinQ() : pMin.getMinQ();
} else if (e1 != null && e2 == null) {
Point pMax = e1.getValue();
return extrapolateReactiveLimitSlope ? extrapolateReactiveLimitsSlope(p, pMax).getMinQ() : pMax.getMinQ();
} else if (e1 != null && e2 != null) {
Point p1 = e1.getValue();
Point p2 = e2.getValue();
return p1.getMinQ() + (p2.getMinQ() - p1.getMinQ()) / (p2.getP() - p1.getP()) * (p - p1.getP());
} else {
Map.Entry<Double, Point> e1 = points.floorEntry(p);
Map.Entry<Double, Point> e2 = points.ceilingEntry(p);
if (e1 == null && e2 != null) {
return e2.getValue().getMinQ();
} else if (e1 != null && e2 == null) {
return e1.getValue().getMinQ();
} else if (e1 != null && e2 != null) {
Point p1 = e1.getValue();
Point p2 = e2.getValue();
return p1.getMinQ() + (p2.getMinQ() - p1.getMinQ()) / (p2.getP() - p1.getP()) * (p - p1.getP());
} else {
throw new IllegalStateException();
}
throw new IllegalStateException();
}
}

@Override
public double getMaxQ(double p) {
if (points.size() < 2) {
throw new IllegalStateException("points size should be >= 2");
}
public double getMaxQ(double p, boolean extrapolateReactiveLimitSlope) {
checkPointsSize(points);

Point pt = points.get(p);
if (pt != null) {
return pt.getMaxQ();
}

Map.Entry<Double, Point> e1 = points.floorEntry(p);
Map.Entry<Double, Point> e2 = points.ceilingEntry(p);
if (e1 == null && e2 != null) {
Point pMin = e2.getValue();
return extrapolateReactiveLimitSlope ? extrapolateReactiveLimitsSlope(p, pMin).getMaxQ() : pMin.getMaxQ();
} else if (e1 != null && e2 == null) {
Point pMax = e1.getValue();
return extrapolateReactiveLimitSlope ? extrapolateReactiveLimitsSlope(p, pMax).getMaxQ() : pMax.getMaxQ();
} else if (e1 != null && e2 != null) {
Point p1 = e1.getValue();
Point p2 = e2.getValue();
return p1.getMaxQ() + (p2.getMaxQ() - p1.getMaxQ()) / (p2.getP() - p1.getP()) * (p - p1.getP());
} else {
Map.Entry<Double, Point> e1 = points.floorEntry(p);
Map.Entry<Double, Point> e2 = points.ceilingEntry(p);
if (e1 == null && e2 != null) {
return e2.getValue().getMaxQ();
} else if (e1 != null && e2 == null) {
return e1.getValue().getMaxQ();
} else if (e1 != null && e2 != null) {
Point p1 = e1.getValue();
Point p2 = e2.getValue();
return p1.getMaxQ() + (p2.getMaxQ() - p1.getMaxQ()) / (p2.getP() - p1.getP()) * (p - p1.getP());
} else {
throw new IllegalStateException();
}
throw new IllegalStateException();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
import com.powsybl.iidm.network.ReactiveCapabilityCurve.Point;
import com.powsybl.iidm.network.impl.ReactiveCapabilityCurveImpl.PointImpl;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

import java.util.TreeMap;

Expand All @@ -29,7 +31,7 @@ private ReactiveCapabilityCurveImpl createCurve(Point... points) {
}

@Test
void testInterpolation() {
void testReactiveCapabilityCurve() {
ReactiveCapabilityCurveImpl curve = createCurve(new PointImpl(100.0, 200.0, 300.0),
new PointImpl(200.0, 300.0, 400.0));
// bounds test
Expand All @@ -51,4 +53,33 @@ void testInterpolation() {
assertEquals(400.0, curve.getMaxQ(1000.0), 0.0);
}

@ParameterizedTest
@ValueSource(booleans = {true, false})
void testReactiveCapabilityCurveWithReactiveLimitsExtrapolation(boolean extrapolate) {
ReactiveCapabilityCurveImpl curve = createCurve(new PointImpl(100.0, 200.0, 300.0),
new PointImpl(200.0, 300.0, 400.0),
new PointImpl(300.0, 300.0, 400.0),
new PointImpl(400.0, 310.0, 390.0));
// bounds test
assertEquals(200.0, curve.getMinQ(100.0, extrapolate), 0.0);
assertEquals(300.0, curve.getMaxQ(100.0, extrapolate), 0.0);
assertEquals(300.0, curve.getMinQ(200.0, extrapolate), 0.0);
assertEquals(400.0, curve.getMaxQ(200.0, extrapolate), 0.0);

// interpolation test
assertEquals(250.0, curve.getMinQ(150.0, extrapolate), 0.0);
assertEquals(350.0, curve.getMaxQ(150.0, extrapolate), 0.0);
assertEquals(210.0, curve.getMinQ(110.0, extrapolate), 0.0);
assertEquals(310.0, curve.getMaxQ(110.0, extrapolate), 0.0);

// out of bounds test
assertEquals(extrapolate ? 100.0 : 200.0, curve.getMinQ(0.0, extrapolate), 0.0);
assertEquals(extrapolate ? 200.0 : 300.0, curve.getMaxQ(0.0, extrapolate), 0.0);
assertEquals(extrapolate ? 320.0 : 310.0, curve.getMinQ(500.0, extrapolate), 0.0);
assertEquals(extrapolate ? 380.0 : 390.0, curve.getMaxQ(500.0, extrapolate), 0.0);

// intersecting reactive limits test
assertEquals(extrapolate ? 350.0 : 310.0, curve.getMinQ(1500.0, extrapolate), 0.0);
assertEquals(extrapolate ? 350.0 : 390.0, curve.getMaxQ(1500.0, extrapolate), 0.0);
}
}
Loading