package beast.core.parameter;
import java.io.PrintStream;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import beast.core.Description;
import beast.core.Function;
import beast.core.Input;
import beast.core.StateNode;
public interface Parameter<T> extends Function {
public T getValue(int i);
public T getValue();
public void setValue(int i, T value);
public void setValue(T value);
public T getLower();
public void setLower(final T lower);
public T getUpper();
public void setUpper(final T upper);
public T[] getValues();
public String getID();
public int getMinorDimension1();
public int getMinorDimension2();
public T getMatrixValue(int i, int j);
/**
* swap values of element i and j
*
* @param i
* @param j
*/
public void swap(int i, int j);
@Description("A parameter represents a value in the state space that can be changed "
+ "by operators.")
public abstract class Base<T> extends StateNode implements Parameter<T> {
/**
* value is a required input since it is very hard to ensure any
* internal consistency when no value is specified. When another class
* wants to set the dimension, say, this will make it the responsibility
* of the other class to maintain internal consistency of the parameter.
*/
final public Input<List<T>> valuesInput = new Input<>("value", "start value(s) for this parameter. If multiple values are specified, they should be separated by whitespace.", new ArrayList<>(), beast.core.Input.Validate.REQUIRED, getMax().getClass());
public final Input<java.lang.Integer> dimensionInput =
new Input<>("dimension", "dimension of the parameter (default 1, i.e scalar)", 1);
public final Input<Integer> minorDimensionInput = new Input<>("minordimension", "minor-dimension when the parameter is interpreted as a matrix (default 1)", 1);
/**
* constructors *
*/
public Base() {
}
public Base(final T[] values) {
this.values = values.clone();
this.storedValues = values.clone();
m_fUpper = getMax();
m_fLower = getMin();
m_bIsDirty = new boolean[values.length];
for (T value : values) {
valuesInput.get().add(value);
}
}
@SuppressWarnings("unchecked")
@Override
public void initAndValidate() {
T[] valuesString = valuesInput.get().toArray((T[]) Array.newInstance(getMax().getClass(), 0));
int dimension = Math.max(dimensionInput.get(), valuesString.length);
dimensionInput.setValue(dimension, this);
values = (T[]) Array.newInstance(getMax().getClass(), dimension);
storedValues = (T[]) Array.newInstance(getMax().getClass(), dimension);
for (int i = 0; i < values.length; i++) {
values[i] = valuesString[i % valuesString.length];
}
m_bIsDirty = new boolean[dimensionInput.get()];
minorDimension = minorDimensionInput.get();
if (minorDimension > 0 && dimensionInput.get() % minorDimension > 0) {
throw new IllegalArgumentException("Dimension must be divisible by stride");
}
this.storedValues = values.clone();
}
/**
* upper & lower bound These are located before the inputs (instead of
* after the inputs, as usual) so that valuesInput can determines the
* class
*/
protected T m_fUpper;
protected T m_fLower;
abstract T getMax();
abstract T getMin();
/**
* the actual values of this parameter
*/
protected T[] values;
protected T[] storedValues;
/**
* sub-dimension when parameter is considered a matrix
*/
protected int minorDimension = 1;
/**
* isDirty flags for individual elements in high dimensional parameters
*/
protected boolean[] m_bIsDirty;
/**
* last element to be changed *
*/
protected int m_nLastDirty;
/**
* @param index dimension to check
* @return true if the param-th element has changed
*/
public boolean isDirty(final int index) {
return m_bIsDirty[index];
}
/**
* Returns index of entry that was changed last. Useful if it is known
* only a single value has changed in the array. *
*/
public int getLastDirty() {
return m_nLastDirty;
}
@Override
public void setEverythingDirty(final boolean isDirty) {
setSomethingIsDirty(isDirty);
Arrays.fill(m_bIsDirty, isDirty);
}
/*
* various setters & getters *
*/
@Override
public int getDimension() {
return values.length;
}
/**
* Change the dimension of a parameter
* <p/>
* This should only be called from initAndValidate() when a parent
* beastObject can easily calculate the dimension of a parameter, but it is
* awkward to do this by hand.
* <p/>
* Values are sourced from the original parameter values.
*
* @param dimension
*/
@SuppressWarnings("unchecked")
public void setDimension(final int dimension) {
if (getDimension() != dimension) {
final T[] values2 = (T[]) Array.newInstance(getMax().getClass(), dimension);
for (int i = 0; i < dimension; i++) {
values2[i] = values[i % getDimension()];
}
values = values2;
//storedValues = (T[]) Array.newInstance(m_fUpper.getClass(), dimension);
}
m_bIsDirty = new boolean[dimension];
try {
dimensionInput.setValue(dimension, this);
} catch (Exception e) {
// ignore
}
}
public void setMinorDimension(final int dimension) {
minorDimension = dimension;
if (minorDimension > 0 && dimensionInput.get() % minorDimension > 0) {
throw new IllegalArgumentException("Dimension must be divisible by stride");
}
}
@Override
public T getValue() {
return values[0];
}
@Override
public T getLower() {
return m_fLower;
}
@Override
public void setLower(final T lower) {
m_fLower = lower;
}
@Override
public T getUpper() {
return m_fUpper;
}
@Override
public void setUpper(final T upper) {
m_fUpper = upper;
}
@Override
public T getValue(final int param) {
return values[param];
}
@Override
public T[] getValues() {
return Arrays.copyOf(values, values.length);
}
public void setBounds(final T lower, final T upper) {
m_fLower = lower;
m_fUpper = upper;
}
@Override
public void setValue(final T value) {
startEditing(null);
values[0] = value;
m_bIsDirty[0] = true;
m_nLastDirty = 0;
}
@Override
public void setValue(final int param, final T value) {
startEditing(null);
values[param] = value;
m_bIsDirty[param] = true;
m_nLastDirty = param;
}
@Override
public void swap(final int left, final int right) {
startEditing(null);
final T tmp = values[left];
values[left] = values[right];
values[right] = tmp;
m_bIsDirty[left] = true;
m_bIsDirty[right] = true;
}
/**
* Note that changing toString means fromXML needs to be changed as
* well, since it parses the output of toString back into a parameter.
*/
@Override
public String toString() {
final StringBuilder buf = new StringBuilder();
buf.append(getID()).append("[").append(values.length);
if (minorDimension > 0) {
buf.append(" ").append(minorDimension);
}
buf.append("] ");
buf.append("(").append(m_fLower).append(",").append(m_fUpper).append("): ");
for (final T value : values) {
buf.append(value).append(" ");
}
return buf.toString();
}
@Override
public Base<T> copy() {
try {
@SuppressWarnings("unchecked")
final Parameter.Base<T> copy = (Parameter.Base<T>) this.clone();
copy.values = values.clone();//new Boolean[values.length];
copy.m_bIsDirty = new boolean[values.length];
return copy;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
@Override
public void assignTo(final StateNode other) {
@SuppressWarnings("unchecked")
final Parameter.Base<T> copy = (Parameter.Base<T>) other;
copy.setID(getID());
copy.index = index;
copy.values = values.clone();
//System.arraycopy(values, 0, copy.values, 0, values.length);
copy.m_fLower = m_fLower;
copy.m_fUpper = m_fUpper;
copy.m_bIsDirty = new boolean[values.length];
}
@Override
public void assignFrom(final StateNode other) {
@SuppressWarnings("unchecked")
final Parameter.Base<T> source = (Parameter.Base<T>) other;
setID(source.getID());
values = source.values.clone();
storedValues = source.storedValues.clone();
System.arraycopy(source.values, 0, values, 0, values.length);
m_fLower = source.m_fLower;
m_fUpper = source.m_fUpper;
m_bIsDirty = new boolean[source.values.length];
}
@Override
public void assignFromFragile(final StateNode other) {
@SuppressWarnings("unchecked")
final Parameter.Base<T> source = (Parameter.Base<T>) other;
System.arraycopy(source.values, 0, values, 0, Math.min(values.length, source.getDimension()));
Arrays.fill(m_bIsDirty, false);
}
/**
* Loggable interface implementation follows (partly, the actual logging
* of values happens in derived classes) *
*/
@Override
public void init(final PrintStream out) {
final int valueCount = getDimension();
if (valueCount == 1) {
out.print(getID() + "\t");
} else {
for (int value = 0; value < valueCount; value++) {
out.print(getID() + (value + 1) + "\t");
}
}
}
@Override
public void close(final PrintStream out) {
// nothing to do
}
/**
* StateNode implementation *
*/
@Override
public void fromXML(final Node node) {
final NamedNodeMap atts = node.getAttributes();
setID(atts.getNamedItem("id").getNodeValue());
final String str = node.getTextContent();
Pattern pattern = Pattern.compile(".*\\[(.*) (.*)\\].*\\((.*),(.*)\\): (.*) ");
Matcher matcher = pattern.matcher(str);
if (matcher.matches()) {
final String dimension = matcher.group(1);
final String stride = matcher.group(2);
final String lower = matcher.group(3);
final String upper = matcher.group(4);
final String valuesAsString = matcher.group(5);
final String[] values = valuesAsString.split(" ");
minorDimension = Integer.parseInt(stride);
fromXML(Integer.parseInt(dimension), lower, upper, values);
} else {
pattern = Pattern.compile(".*\\[(.*)\\].*\\((.*),(.*)\\): (.*) ");
matcher = pattern.matcher(str);
if (matcher.matches()) {
final String dimension = matcher.group(1);
final String lower = matcher.group(2);
final String upper = matcher.group(3);
final String valuesAsString = matcher.group(4);
final String[] values = valuesAsString.split(" ");
minorDimension = 0;
fromXML(Integer.parseInt(dimension), lower, upper, values);
} else {
throw new RuntimeException("parameter could not be parsed");
}
}
}
/**
* Restore a saved parameter from string representation. This cannot be
* a template method since it requires creation of an array of T...
*
* @param dimension parameter dimension
* @param lower lower bound
* @param upper upper bound
* @param values values
*/
abstract void fromXML(int dimension, String lower, String upper, String[] values);
/**
* matrix implementation *
*/
@Override
public int getMinorDimension1() {
return minorDimension;
}
@Override
public int getMinorDimension2() {
return getDimension() / minorDimension;
}
@Override
public T getMatrixValue(final int i, final int j) {
return values[i * minorDimension + j];
}
public void setMatrixValue(final int i, final int j, final T value) {
setValue(i * minorDimension + j, value);
}
public void getMatrixValues1(final int i, final T[] row) {
assert (row.length == minorDimension);
System.arraycopy(values, i * minorDimension, row, 0, minorDimension);
}
public void getMatrixValues1(final int i, final double[] row) {
assert (row.length == minorDimension);
for (int j = 0; j < minorDimension; j++) {
row[j] = getArrayValue(i * minorDimension + j);
}
}
public void getMatrixValues2(final int j, final T[] col) {
assert (col.length == getMinorDimension2());
for (int i = 0; i < getMinorDimension2(); i++) {
col[i] = values[i * minorDimension + j];
}
}
public void getMatrixValues2(final int j, final double[] col) {
assert (col.length == getMinorDimension2());
for (int i = 0; i < getMinorDimension2(); i++) {
col[i] = getArrayValue(i * minorDimension + j);
}
}
@SuppressWarnings("unchecked")
@Override
protected void store() {
if (storedValues.length != values.length) {
storedValues = (T[]) Array.newInstance(m_fUpper.getClass(), values.length);
}
System.arraycopy(values, 0, storedValues, 0, values.length);
}
@Override
public void restore() {
final T[] tmp = storedValues;
storedValues = values;
values = tmp;
hasStartedEditing = false;
if (m_bIsDirty.length != values.length) {
m_bIsDirty = new boolean[values.length];
}
}
} // class Parameter
}