/**
* Copyright (C) 2009 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.analytics.math.minimization;
import org.apache.commons.lang.Validate;
import com.opengamma.analytics.math.TrigonometricFunctionUtils;
/**
* If a model parameter $x$ is constrained to be between two values $a \geq x
* \geq b$, the function to transform it to an unconstrained variable is $y$ is
* given by
* $$
* \begin{align*}
* y &= \tanh^{-1}\left(\frac{x - m}{s}\right)\\
* m &= \frac{a + b}{2}\\
* s &= \frac{b - a}{2}
* \end{align*}
* $$
* with the inverse transform
* $$
* \begin{align*}
* x &= s\tanh(y) + m\\
* \end{align*}
* $$
*/
public class DoubleRangeLimitTransform implements ParameterLimitsTransform {
private static final double TANH_MAX = 25.0;
private final double _lower;
private final double _upper;
private final double _scale;
private final double _mid;
/**
* @param lower Lower limit
* @param upper Upper limit
* @throws IllegalArgumentException If the upper limit is not greater than the lower limit
*/
public DoubleRangeLimitTransform(final double lower, final double upper) {
Validate.isTrue(upper > lower, "upper limit must be greater than lower");
_lower = lower;
_upper = upper;
_mid = (lower + upper) / 2;
_scale = (upper - lower) / 2;
}
/**
* If $y > 25$, this returns $b$. If $y < -25$ returns $a$.
* {@inheritDoc}
*/
@Override
public double inverseTransform(final double y) {
if (y > TANH_MAX) {
return _upper;
} else if (y < -TANH_MAX) {
return _lower;
}
return _mid + _scale * TrigonometricFunctionUtils.tanh(y);
}
/**
* {@inheritDoc}
* @throws IllegalArgumentException If $x > b$ or $x < a$
*/
@Override
public double transform(final double x) {
Validate.isTrue(x <= _upper && x >= _lower, "parameter out of range");
if (x == _upper) {
return TANH_MAX;
} else if (x == _lower) {
return -TANH_MAX;
}
return TrigonometricFunctionUtils.atanh((x - _mid) / _scale);
}
/**
* If $|y| > 25$, this returns 0.
* {@inheritDoc}
*/
@Override
public double inverseTransformGradient(final double y) {
if (y > TANH_MAX || y < -TANH_MAX) {
return 0.0;
}
final double p = 2 * y;
final double ep = Math.exp(p);
final double epp1 = ep + 1;
return _scale * 4 * ep / (epp1 * epp1);
}
/**
* {@inheritDoc}
* @throws IllegalArgumentException If $x > b$ or $x < a$
*/
@Override
public double transformGradient(final double x) {
Validate.isTrue(x <= _upper && x >= _lower, "parameter out of range");
final double t = (x - _mid) / _scale;
return 1 / (_scale * (1 - t * t));
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
long temp;
temp = Double.doubleToLongBits(_lower);
result = prime * result + (int) (temp ^ (temp >>> 32));
temp = Double.doubleToLongBits(_upper);
result = prime * result + (int) (temp ^ (temp >>> 32));
return result;
}
@Override
public boolean equals(final Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final DoubleRangeLimitTransform other = (DoubleRangeLimitTransform) obj;
if (Double.doubleToLongBits(_lower) != Double.doubleToLongBits(other._lower)) {
return false;
}
return Double.doubleToLongBits(_upper) == Double.doubleToLongBits(other._upper);
}
}