ReactiveCapabilityCurveUtil.java
/**
* Copyright (c) 2025, 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.iidm.network.util;
import com.powsybl.iidm.network.ReactiveCapabilityCurve;
import org.apache.commons.lang3.function.TriFunction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.TreeMap;
/**
* @author Sylvestre Prabakaran {@literal <sylvestre.prabakaran at rte-france.com>}
*/
public final class ReactiveCapabilityCurveUtil {
private static final Logger LOGGER = LoggerFactory.getLogger(ReactiveCapabilityCurveUtil.class);
private ReactiveCapabilityCurveUtil() {
}
/**
* Extrapolate reactive limits when p is outside [minP,maxP] using slopes of reactive limits at the crossed limit of p
* (Note that this method throws an exception if p is inside [minP, maxP])
* @param p Active power value to evaluate the reactive limits
* @param points TreeMap of all points defining the reactive capability curve mapped by their active power values
* @param valuesToReactiveCapabilityPoint TriFunction returning the used implementation of {@link ReactiveCapabilityCurve.Point} interface: <code>(p, minQ, maxQ) -> Point</code>
* @param ownerDescription Description of the ReactiveCapabilityCurve's owner (for logging purpose)
* @return A ReactiveCapabilityCurve.Point of the extrapolated limits at the requested value of p
*/
public static ReactiveCapabilityCurve.Point extrapolateReactiveLimitsSlope(double p, TreeMap<Double, ReactiveCapabilityCurve.Point> points, TriFunction<Double, Double, Double, ReactiveCapabilityCurve.Point> valuesToReactiveCapabilityPoint, String ownerDescription) {
double minQ;
double maxQ;
ReactiveCapabilityCurve.Point pBound;
ReactiveCapabilityCurve.Point pbis;
if (p < points.firstKey()) {
// Extrapolate reactive limits slope below min active power limit (pBound = min active power limit)
pBound = points.firstEntry().getValue();
pbis = points.higherEntry(points.firstKey()).getValue(); // p < pBound < pbis
} else if (p > points.lastKey()) {
// Extrapolate reactive limits slope above max active power limit (pBound = max active power limit)
pBound = points.lastEntry().getValue();
pbis = points.lowerEntry(points.lastKey()).getValue(); // pbis < pBound < 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 valuesToReactiveCapabilityPoint.apply(p, minQ, maxQ);
} else { // Corner case of intersecting reactive limits when extrapolated
double limitQ = (minQ + maxQ) / 2;
LOGGER.warn("Extrapolation of reactive capability curve for {} leads to minQ > maxQ, correcting to minQ = maxQ", ownerDescription); // This log message can be over flowing (if called at each iteration), apply filters in logback to avoid it
return valuesToReactiveCapabilityPoint.apply(p, limitQ, limitQ); // Returning the mean as limits minQ and maxQ
}
}
}