VoltageInterval.java
/**
* Copyright (c) 2024, 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.criteria;
import org.apache.commons.lang3.DoubleRange;
import java.util.Optional;
/**
* @author Olivier Perrin {@literal <olivier.perrin at rte-france.com>}
*/
public final class VoltageInterval {
private final Double nominalVoltageLowBound;
private final Double nominalVoltageHighBound;
private final boolean lowClosed;
private final boolean highClosed;
/**
* Create a new {@link VoltageInterval} to filter network elements
* which nominal voltages are inside a given interval.
*
* @param lowBound lower bound of the acceptable interval. It may be <code>null</code>, if the interval has no lower bound.
* @param highBound upper bound of the acceptable interval. It may be <code>null</code>, if the interval has no upper bound.
* @param lowClosed <code>true</code> if <code>lowBound</code> is part of the interval, <code>false</code> otherwise.
* @param highClosed <code>true</code> if <code>highBound</code> is part of the interval, <code>false</code> otherwise.
*/
private VoltageInterval(Double lowBound, Double highBound, boolean lowClosed, boolean highClosed) {
this.nominalVoltageLowBound = lowBound;
this.nominalVoltageHighBound = highBound;
this.lowClosed = lowClosed;
this.highClosed = highClosed;
}
public static class Builder {
private Double nominalVoltageLowBound = null;
private Double nominalVoltageHighBound = null;
private boolean lowClosed = true;
private boolean highClosed = true;
/**
* Define the lower bound of the interval.
*
* @param value value of the lower bound.
* @param closed <code>true</code> if the bound is part of the interval, <code>false</code> otherwise.
* @return the current builder
*/
public Builder setLowBound(double value, boolean closed) {
checkValue(value);
checkBounds(value, nominalVoltageHighBound, closed, highClosed);
this.nominalVoltageLowBound = value;
this.lowClosed = closed;
return this;
}
/**
* Define the upper bound of the interval.
*
* @param value value of the upper bound.
* @param closed <code>true</code> if the bound is part of the interval, <code>false</code> otherwise.
* @return the current builder
*/
public Builder setHighBound(double value, boolean closed) {
checkValue(value);
checkBounds(nominalVoltageLowBound, value, lowClosed, closed);
this.nominalVoltageHighBound = value;
this.highClosed = closed;
return this;
}
protected static void checkValue(double value) {
if (Double.isNaN(value) || Double.isInfinite(value) || value < 0) {
throw new IllegalArgumentException("Invalid interval bound value (must be >= 0 and not infinite).");
}
}
protected static void checkBounds(Double low, Double high, boolean closedLow, boolean closedHigh) {
if (low != null && high != null && low > high) {
throw new IllegalArgumentException("Invalid interval bounds values (nominalVoltageLowBound must be <= nominalVoltageHighBound).");
}
double l = low != null ? low : 0;
double h = high != null ? high : Double.MAX_VALUE;
if (l == h && (!closedLow || !closedHigh)) {
throw new IllegalArgumentException("Invalid interval: it should not be empty");
}
}
public VoltageInterval build() {
if (nominalVoltageLowBound == null && nominalVoltageHighBound == null) {
throw new IllegalArgumentException("Invalid interval: at least one bound must be defined.");
}
return new VoltageInterval(nominalVoltageLowBound, nominalVoltageHighBound, lowClosed, highClosed);
}
}
/**
* Return a builder to create an {@link VoltageInterval}.
*
* @return a builder
*/
public static Builder builder() {
return new Builder();
}
/**
* <p>Convenient method to easily create a {@link VoltageInterval} with only a lower bound.</p>
* @param value the lower bound of the interval to create (it corresponds to the <code>nominalVoltageLowBound</code> attribute of the interval)
* @param closed is the bound included in the interval (it corresponds to the <code>lowClosed</code> attribute of the interval)
* @return an interval
*/
public static VoltageInterval greaterThan(double value, boolean closed) {
return VoltageInterval.builder()
.setLowBound(value, closed)
.build();
}
/**
* <p>Convenient method to easily create a {@link VoltageInterval} with only a upper bound.</p>
* @param value the upper bound of the interval to create (it corresponds to the <code>nominalVoltageHighBound</code> attribute of the interval)
* @param closed is the bound included in the interval (it corresponds to the <code>highClosed</code> attribute of the interval)
* @return an interval
*/
public static VoltageInterval lowerThan(double value, boolean closed) {
return VoltageInterval.builder()
.setHighBound(value, closed)
.build();
}
/**
* <p>Convenient method to easily create a {@link VoltageInterval} with only a upper bound.</p>
* @param lowBound the lower bound of the interval to create (it corresponds to the <code>nominalVoltageLowBound</code> attribute of the interval)
* @param lowClosed is the bound included in the interval (it corresponds to the <code>lowClosed</code> attribute of the interval)
* @param highBound the upper bound of the interval to create (it corresponds to the <code>nominalVoltageHighBound</code> attribute of the interval)
* @param highClosed is the bound included in the interval (it corresponds to the <code>highClosed</code> attribute of the interval)
* @return an interval
*/
public static VoltageInterval between(double lowBound, double highBound, boolean lowClosed, boolean highClosed) {
return VoltageInterval.builder()
.setLowBound(lowBound, lowClosed)
.setHighBound(highBound, highClosed)
.build();
}
/**
* <p>Check if a value is inside the interval.</p>
* <p>It returns <code>false</code> if the given value is null.</p>
*
* @param value the value to test
* @return <code>true</code> if the value is inside the interval, <code>false</code> otherwise.
*/
public boolean checkIsBetweenBound(Double value) {
if (value == null || value < 0) {
return false;
}
boolean lowBoundOk = nominalVoltageLowBound == null || value > nominalVoltageLowBound
|| lowClosed && value.equals(nominalVoltageLowBound);
boolean highBoundOk = nominalVoltageHighBound == null || value < nominalVoltageHighBound
|| highClosed && value.equals(nominalVoltageHighBound);
return lowBoundOk && highBoundOk;
}
/**
* Get the lower bound of the interval.
*
* @return lower bound of the acceptable interval, or <code>Optional.empty()</code> if the interval has no lower bound.
*/
public Optional<Double> getNominalVoltageLowBound() {
return Optional.ofNullable(nominalVoltageLowBound);
}
/**
* Get the upper bound of the interval.
*
* @return upper bound of the acceptable interval, or <code>Optional.empty()</code> if the interval has no upper bound.
*/
public Optional<Double> getNominalVoltageHighBound() {
return Optional.ofNullable(nominalVoltageHighBound);
}
/**
* Is the interval closed on the lower side?
*
* @return <code>true</code> if <code>lowBound</code> is part of the interval, <code>false</code> otherwise.
*/
public boolean isLowClosed() {
return lowClosed;
}
/**
* Is the interval closed on the upper side?
*
* @return <code>true</code> if <code>highBound</code> is part of the interval, <code>false</code> otherwise.
*/
public boolean isHighClosed() {
return highClosed;
}
/**
* <p>Return a {@link DoubleRange} representation of the interval.</p>
*
* @return the interval as a {@link DoubleRange}
*/
public DoubleRange asRange() {
double min = 0;
if (nominalVoltageLowBound != null) {
min = nominalVoltageLowBound;
if (!lowClosed) {
min = min + Math.ulp(min);
}
}
double max = Double.MAX_VALUE;
if (nominalVoltageHighBound != null) {
max = nominalVoltageHighBound;
if (!highClosed) {
max = max - Math.ulp(max);
}
}
return DoubleRange.of(min, max);
}
}