/*******************************************************************************
* Copyright 2014 Analog Devices, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
********************************************************************************/
package com.analog.lyric.dimple.model.domains;
import java.util.Arrays;
import org.eclipse.jdt.annotation.Nullable;
import com.google.common.math.DoubleMath;
import com.google.common.primitives.Doubles;
/**
* A discrete domain defined by a range of doubles separated by a constant positive interval.
* <p>
* @see DiscreteDomain#range(double, double)
* @see DiscreteDomain#range(double, double, double)
*/
public class DoubleRangeDomain extends DoubleDiscreteDomain
{
private static final long serialVersionUID = 1L;
private final double _lowerBound;
private final double _upperBound;
private final double _interval;
private final int _size;
private final double _tolerance;
private final boolean _hasIntCompatibleValues;
/*--------------
* Construction
*/
private DoubleRangeDomain(double lowerBound, double upperBound, double interval, double tolerance, int size)
{
super(computeHashCode(lowerBound, upperBound, interval, tolerance));
if (interval <= 0.0)
{
throw new IllegalArgumentException(
String.format("Non-positive interval '%g' for double range domain", interval));
}
if (tolerance < 0.0)
{
throw new IllegalArgumentException(
String.format("Negative tolerance '%g' for double range domain", tolerance));
}
if (tolerance >= interval/2)
{
throw new IllegalArgumentException(
String.format("Tolerance '%g' is too large for interval '%g' in double range domain",
tolerance, interval));
}
if (upperBound < lowerBound)
{
throw new IllegalArgumentException(
String.format("Bad double range [%g,%g]: upper bound lower than lower bound", lowerBound, upperBound));
}
_lowerBound = lowerBound;
_upperBound = upperBound;
_size = size;
_interval = interval;
_tolerance = tolerance;
_hasIntCompatibleValues = isIntCompatibleValue(lowerBound) && isIntCompatibleValue(interval) &&
isIntCompatibleValue(upperBound);
}
static DoubleRangeDomain create(double lowerBound, double upperBound, double interval, double tolerance)
{
if (!Doubles.isFinite(tolerance))
{
tolerance = defaultToleranceForInterval(interval);
}
int size = 1 + (int)((tolerance + upperBound - lowerBound) / interval);
return intern(new DoubleRangeDomain(lowerBound, upperBound, interval, tolerance, size));
}
private static int computeHashCode(double ... values)
{
return Arrays.hashCode(values);
}
/*----------------
* Object methods
*/
@Override
public boolean equals(@Nullable Object that)
{
if (this == that)
{
return true;
}
if (that instanceof DoubleRangeDomain)
{
DoubleRangeDomain thatRange = (DoubleRangeDomain)that;
return
_lowerBound == thatRange._lowerBound &&
_upperBound == thatRange._upperBound &&
_interval == thatRange._interval &&
_tolerance == thatRange._tolerance;
}
return false;
}
/*----------------
* Domain methods
*/
@Override
public final boolean hasIntCompatibleValues()
{
return _hasIntCompatibleValues;
}
/*------------------------
* DiscreteDomain methods
*/
@Override
public final int size()
{
return _size;
}
/**
* Same as {@link #getIndex(Object)} but taking a double.
*/
@Override
public int getIndex(double value)
{
int index = (int)Math.rint((value - _lowerBound) / _interval);
return index < _size && DoubleMath.fuzzyEquals(_lowerBound + index * _interval, value, _tolerance) ? index : - 1;
}
/*---------------------------
* DoubleRangeDomain methods
*/
/**
* Same as {@link #getElement(int)} but returning an unboxed double.
*/
@Override
public double getDoubleElement(int i)
{
assertIndexInBounds(i, _size);
return _lowerBound + i * _interval;
}
/**
* The interval separating consecutive elements of the domain.
* <p>
* Guaranteed to be positive.
*/
public double getInterval()
{
return _interval;
}
/**
* Returns the lower bound for the domain which is also the same as the first element.
* <p>
* @see #getUpperBound()
*/
public double getLowerBound()
{
return _lowerBound;
}
public double getUpperBound()
{
return _upperBound;
}
/**
* The tolerance is the maximum difference allowed between a value and a domain element
* for it to be consider to be the same element.
* <p>
* The tolerance must be positive and less than half the value of {@link #getInterval()} and is typically
* much smaller than that.
*/
public double getTolerance()
{
return _tolerance;
}
static public double defaultToleranceForInterval(double interval)
{
return Math.abs(interval * 1e-6);
}
}