package beast.evolution.operators;
import java.text.DecimalFormat;
import beast.core.Description;
import beast.core.Input;
import beast.core.Input.Validate;
import beast.core.Operator;
import beast.core.parameter.RealParameter;
import beast.util.Randomizer;
@Description("A random walk operator that selects a random dimension of the real parameter and perturbs the value a " +
"random amount within +/- windowSize.")
public class RealRandomWalkOperator extends Operator {
final public Input<Double> windowSizeInput =
new Input<>("windowSize", "the size of the window both up and down when using uniform interval OR standard deviation when using Gaussian", Input.Validate.REQUIRED);
final public Input<RealParameter> parameterInput =
new Input<>("parameter", "the parameter to operate a random walk on.", Validate.REQUIRED);
final public Input<Boolean> useGaussianInput =
new Input<>("useGaussian", "Use Gaussian to move instead of uniform interval. Default false.", false);
double windowSize = 1;
boolean useGaussian;
@Override
public void initAndValidate() {
windowSize = windowSizeInput.get();
useGaussian = useGaussianInput.get();
}
/**
* override this for proposals,
* returns log of hastingRatio, or Double.NEGATIVE_INFINITY if proposal should not be accepted *
*/
@Override
public double proposal() {
RealParameter param = parameterInput.get(this);
int i = Randomizer.nextInt(param.getDimension());
double value = param.getValue(i);
double newValue = value;
if (useGaussian) {
newValue += Randomizer.nextGaussian() * windowSize;
} else {
newValue += Randomizer.nextDouble() * 2 * windowSize - windowSize;
}
if (newValue < param.getLower() || newValue > param.getUpper()) {
return Double.NEGATIVE_INFINITY;
}
if (newValue == value) {
// this saves calculating the posterior
return Double.NEGATIVE_INFINITY;
}
param.setValue(i, newValue);
return 0.0;
}
@Override
public double getCoercableParameterValue() {
return windowSize;
}
@Override
public void setCoercableParameterValue(double value) {
windowSize = value;
}
/**
* called after every invocation of this operator to see whether
* a parameter can be optimised for better acceptance hence faster
* mixing
*
* @param logAlpha difference in posterior between previous state & proposed state + hasting ratio
*/
@Override
public void optimize(double logAlpha) {
// must be overridden by operator implementation to have an effect
double delta = calcDelta(logAlpha);
delta += Math.log(windowSize);
windowSize = Math.exp(delta);
}
@Override
public final String getPerformanceSuggestion() {
double prob = m_nNrAccepted / (m_nNrAccepted + m_nNrRejected + 0.0);
double targetProb = getTargetAcceptanceProbability();
double ratio = prob / targetProb;
if (ratio > 2.0) ratio = 2.0;
if (ratio < 0.5) ratio = 0.5;
// new scale factor
double newWindowSize = windowSize * ratio;
DecimalFormat formatter = new DecimalFormat("#.###");
if (prob < 0.10) {
return "Try setting window size to about " + formatter.format(newWindowSize);
} else if (prob > 0.40) {
return "Try setting window size to about " + formatter.format(newWindowSize);
} else return "";
}
} // class RealRandomWalkOperator