ReactiveCapabilityCurveImpl.java

/**
 * Copyright (c) 2016, All partners of the iTesla project (http://www.itesla-project.eu/consortium)
 * 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.impl;

import com.powsybl.iidm.network.ReactiveCapabilityCurve;
import com.powsybl.iidm.network.ReactiveLimitsKind;
import com.powsybl.iidm.network.util.ReactiveCapabilityCurveUtil;

import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.TreeMap;
import java.util.function.ToDoubleFunction;

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

    /**
     * <p>Comparator to handle the -0.0 == 0.0 case:</p>
     * <p>According to the JLS: "Positive zero and negative zero compare equal, so the result of the expression 0.0==-0.0 is true".
     * But the {@link Double#compareTo(Double)} method consider -0.0 lower than 0.0. Therefore, using the default
     * Double comparator causes a problem when the lower point's <code>p</code> is equal to 0.0 and the tested <code>p</code>
     * is -0.0.
     * </p>
     * <p>This comparator considers 0.0 and -0.0 as equal.</p>
     * <p>Note: it throws a {@link NullPointerException} when one of the Doubles are null,
     * similarly as the default Double comparator. But in our use case, this cannot happen.</p>
     */
    private static final Comparator<Double> COMPARATOR = (d1, d2) -> d1 - d2 == 0 ? 0 : Double.compare(d1, d2);

    private final String ownerDescription;

    static class PointImpl implements Point {

        private double p;

        private double minQ;

        private double maxQ;

        PointImpl(double p, double minQ, double maxQ) {
            this.p = p;
            this.minQ = minQ;
            this.maxQ = maxQ;
        }

        @Override
        public double getP() {
            return p;
        }

        @Override
        public double getMinQ() {
            return minQ;
        }

        @Override
        public double getMaxQ() {
            return maxQ;
        }

    }

    private final TreeMap<Double, Point> points;

    private static void checkPointsSize(TreeMap<Double, Point> points) {
        if (points.size() < 2) {
            throw new IllegalStateException("points size should be >= 2");
        }
    }

    ReactiveCapabilityCurveImpl(TreeMap<Double, Point> points, String ownerDescription) {
        checkPointsSize(points);
        this.points = new TreeMap<>(COMPARATOR);
        this.points.putAll(points);
        this.ownerDescription = ownerDescription;
    }

    @Override
    public Collection<Point> getPoints() {
        return Collections.unmodifiableCollection(points.values());
    }

    @Override
    public int getPointCount() {
        return points.size();
    }

    @Override
    public double getMinP() {
        return points.firstKey();
    }

    @Override
    public double getMaxP() {
        return points.lastKey();
    }

    @Override
    public ReactiveLimitsKind getKind() {
        return ReactiveLimitsKind.CURVE;
    }

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

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

    @Override
    public double getMinQ(double p, boolean extrapolateReactiveLimitSlope) {
        return getReactiveLimit(p, extrapolateReactiveLimitSlope, Point::getMinQ);
    }

    @Override
    public double getMaxQ(double p, boolean extrapolateReactiveLimitSlope) {
        return getReactiveLimit(p, extrapolateReactiveLimitSlope, Point::getMaxQ);
    }

    private double getReactiveLimit(double p, boolean extrapolateReactiveLimitSlope, ToDoubleFunction<Point> getMinOrMaxQ) {
        checkPointsSize(points);

        // First case : searched point is one of the points defining the curve
        Point pt = points.get(p);
        if (pt != null) {
            return getMinOrMaxQ.applyAsDouble(pt);
        }

        // Second case : searched point is between minP and maxP
        if (p >= this.getMinP() && p <= this.getMaxP()) {
            Point p1 = points.floorEntry(p).getValue();
            Point p2 = points.ceilingEntry(p).getValue();
            return getMinOrMaxQ.applyAsDouble(p1) + (getMinOrMaxQ.applyAsDouble(p2) - getMinOrMaxQ.applyAsDouble(p1)) / (p2.getP() - p1.getP()) * (p - p1.getP());
        }

        // Third case : searched point is outside minP and maxP
        if (extrapolateReactiveLimitSlope) {
            Point extrapolatedPoint = ReactiveCapabilityCurveUtil.extrapolateReactiveLimitsSlope(p, points, PointImpl::new, ownerDescription);
            return getMinOrMaxQ.applyAsDouble(extrapolatedPoint);
        } else {
            if (p < this.getMinP()) { // p < minP
                Point pMin = points.firstEntry().getValue();
                return getMinOrMaxQ.applyAsDouble(pMin);
            } else { // p > maxP
                Point pMax = points.lastEntry().getValue();
                return getMinOrMaxQ.applyAsDouble(pMax);
            }
        }
    }

}