/*
* Copyright (C) 2013 Tim Vaughan <tgvaughan@gmail.com>.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
package beast.core.parameter;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.List;
import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.w3c.dom.Node;
import beast.core.Description;
import beast.core.Input;
import beast.core.StateNode;
import beast.core.util.Log;
/**
* @author Tim Vaughan <tgvaughan@gmail.com>
* @param <T> Type of parameters in list.
*/
@Description("State node representing a list of parameter objects, used for "
+ "model selection problems. The parameters involved are not instances "
+ "of Parameter.Base, but are instead instances of a local class "
+ "QuietParameter which is not itself a StateNode. All constituent "
+ "parameters must have identical dimensions and bounds.")
public abstract class GeneralParameterList<T> extends StateNode {
final public Input<List<Parameter.Base<T>>> initialParamsInput = new Input<>(
"initialParam",
"Parameter whose value will initially be in parameter list.",
new ArrayList<>());
final public Input<Integer> dimensionInput = new Input<>("dimension",
"Dimension of individual parameters in list. Default 1.", 1);
final public Input<Integer> minorDimensionInput = new Input<>("minordimension",
"Minor dimension of individual parameters in list. Default 1.", 1);
protected List<QuietParameter> pList, pListStored;
protected TreeSet<Integer> deallocatedKeys, deallocatedKeysStored;
protected int nextUnallocatedKey, nextUnallocatedKeyStored;
protected int dimension, minorDimension;
protected T lowerBound, upperBound;
public GeneralParameterList() { };
@Override
public void initAndValidate() {
pList = new ArrayList<>();
pListStored = new ArrayList<>();
deallocatedKeys = new TreeSet<>();
deallocatedKeysStored = new TreeSet<>();
nextUnallocatedKey = 0;
nextUnallocatedKeyStored = 0;
dimension = dimensionInput.get();
minorDimension = minorDimensionInput.get();
for (Parameter<?> param : initialParamsInput.get()) {
if (param.getDimension() != dimension)
throw new IllegalArgumentException("Parameter dimension does not equal"
+ " dimension specified in enclosing ParameterList.");
QuietParameter qParam = new QuietParameter(param);
allocateKey(qParam);
pList.add(qParam);
}
store();
setSomethingIsDirty(false);
}
/**
* Retrieve number of parameters in parameter list.
*
* @return size of parameter list.
*/
public int size() {
return pList.size();
}
/**
* Retrieve parameter from list.
*
* @param index index of parameter to retrieve
* @return parameter
*/
public QuietParameter get(int index) {
return pList.get(index);
}
/**
* Assign parameter to position in list.
*
* @param index
* @param param
*/
public void set(int index, QuietParameter param) {
startEditing(null);
pList.set(index, param);
}
/**
* Append parameter to end of list.
*
* @param param
*/
public void add(QuietParameter param) {
startEditing(null);
pList.add(param);
}
/**
* Insert parameter at position index in list, incrementing the index of
* all parameters already at and to the right of that position.
*
* @param index
* @param param
*/
public void add(int index, QuietParameter param) {
startEditing(null);
pList.add(index, param);
}
/**
* Remove parameter from list.
*
* @param param
*/
public void remove(QuietParameter param) {
startEditing(null);
deallocatedKeys.add(param.key);
pList.remove(param);
}
/**
* Remove parameter at index from list.
*
* @param index
*/
public void remove(int index) {
startEditing(null);
deallocatedKeys.add(pList.get(index).key);
pList.remove(index);
}
/**
* Create new parameter, without appending it to the list. This only
* makes sense if the parameter is eventually added to the list. This
* call does not itself affect the ParameterList's dirty status (it
* will be marked as dirty when/if add() is called).
*
* @return New parameter.
*/
public QuietParameter createNewParam() {
QuietParameter param = new QuietParameter();
allocateKey(param);
return param;
}
/**
* Create new parameter from existing Parameter, without appending it to
* the list. This only makes sense if the parameter is eventually added
* to the list. This call does not itself affect the ParameterList's
* dirty status (it will be marked as dirty when/if add() is called).
*
* @param otherParam
* @return New parameter.
*/
public QuietParameter createNewParam(Parameter<?> otherParam) {
QuietParameter param = new QuietParameter(otherParam);
allocateKey(param);
return param;
}
/**
* Create new parameter and append to list.
*
* @return New parameter.
*/
public QuietParameter addNewParam() {
startEditing(null);
QuietParameter param = new QuietParameter();
allocateKey(param);
pList.add(param);
return param;
}
/**
* Create new parameter from existing Parameter and append to list.
*
* @param otherParam
* @return New parameter.
*/
public QuietParameter addNewParam(Parameter<?> otherParam) {
startEditing(null);
QuietParameter param = new QuietParameter(otherParam);
allocateKey(param);
pList.add(param);
return param;
}
/**
* Assign unique ID to this parameter.
* @param param
*/
private void allocateKey(QuietParameter param) {
if (deallocatedKeys.size()>0) {
param.key = deallocatedKeys.first();
deallocatedKeys.remove(param.key);
} else {
param.key = nextUnallocatedKey;
nextUnallocatedKey += 1;
}
}
@Override
public StateNode copy() {
try {
@SuppressWarnings("unchecked")
GeneralParameterList<T> copy = (GeneralParameterList<T>) this.clone();
copy.initAndValidate();
copy.pList.clear();
for (QuietParameter param : pList) {
QuietParameter paramCopy = param.copy();
copy.pList.add(paramCopy);
}
copy.dimension = dimension;
copy.minorDimension = minorDimension;
copy.lowerBound = lowerBound;
copy.upperBound = upperBound;
copy.deallocatedKeys.addAll(deallocatedKeys);
copy.nextUnallocatedKey = nextUnallocatedKey;
return copy;
} catch (CloneNotSupportedException ex) {
Log.err(ex.getMessage());
}
return null;
}
@Override
public void assignTo(StateNode other) {
if (!(other instanceof GeneralParameterList))
throw new RuntimeException("Incompatible statenodes in assignTo "
+ "call.");
@SuppressWarnings("unchecked")
GeneralParameterList<T> otherParamList = (GeneralParameterList<T>)other;
otherParamList.pList.clear();
for (QuietParameter param : pList)
otherParamList.pList.add(param.copy());
otherParamList.dimension = dimension;
otherParamList.minorDimension = minorDimension;
otherParamList.lowerBound = lowerBound;
otherParamList.upperBound = upperBound;
otherParamList.deallocatedKeys = new TreeSet<>(deallocatedKeys);
otherParamList.nextUnallocatedKey = nextUnallocatedKey;
}
@SuppressWarnings("unchecked")
@Override
public void assignFrom(StateNode other) {
if (!(other instanceof GeneralParameterList))
throw new RuntimeException("Incompatible statenodes in assignFrom "
+ "call.");
GeneralParameterList<T> otherParamList = (GeneralParameterList<T>)other;
pList.clear();
for (Object paramObj : otherParamList.pList)
pList.add((QuietParameter) paramObj);
dimension = otherParamList.dimension;
minorDimension = otherParamList.minorDimension;
lowerBound = otherParamList.lowerBound;
upperBound = otherParamList.upperBound;
deallocatedKeys = new TreeSet<>(otherParamList.deallocatedKeys);
nextUnallocatedKey = otherParamList.nextUnallocatedKey;
}
@Override
public void assignFromFragile(StateNode other) {
assignFrom(other);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(String.format("Dimension: [%d, %d], Bounds: [%s,%s], ",
dimension,
minorDimension,
String.valueOf(lowerBound),
String.valueOf(upperBound)));
sb.append("AvailableKeys: [");
boolean first = true;
for (int key : deallocatedKeys) {
if (!first)
sb.append(",");
else
first = false;
sb.append(key);
}
sb.append("], ");
sb.append("NextKey: ").append(nextUnallocatedKey).append(", ");
sb.append("Parameters: [");
for (int i=0; i<pList.size(); i++) {
if (i>0)
sb.append(",");
sb.append(pList.get(i));
}
sb.append("], ");
sb.append("ParameterKeys: [");
for (int i=0; i<pList.size(); i++) {
if (i>0)
sb.append(",");
sb.append(pList.get(i).key);
}
sb.append("]");
return sb.toString();
}
@Override
public void fromXML(Node node) {
String str = node.getTextContent();
Pattern pattern = Pattern.compile("^"
+ " *Dimension: *\\[([^]]*)] *,"
+ " *Bounds: *\\[([^]]*)] *,"
+ " *AvailableKeys: *\\[([^]]*)] *,"
+ " *NextKey: *([^, ]*) *,"
+ " *Parameters: *\\[(.*)] *,"
+ " *ParameterKeys: *\\[(.*)] *$");
Matcher matcher = pattern.matcher(str);
if (!matcher.find())
throw new RuntimeException("Error parsing ParameterList state string.");
// Parse dimension strings
String [] dimStr = matcher.group(1).split(",");
dimension = Integer.parseInt(dimStr[0].trim());
minorDimension = Integer.parseInt(dimStr[1].trim());
// Parse dealocated key strings
deallocatedKeys.clear();
for (String keyStr : matcher.group(3).trim().split(",") ) {
if (keyStr.trim().length()>0)
deallocatedKeys.add(Integer.parseInt(keyStr));
}
// Parse next allocated key string
nextUnallocatedKey = Integer.parseInt(matcher.group(4));
// Prepare bounds and parameter value strings for parsing by methods in
// non-abstract classes (where type T is known).
String [] boundsStr = matcher.group(2).split(",");
List<String[]> parameterValueStrings = new ArrayList<>();
String parameterListString = matcher.group(5).trim();
pattern = Pattern.compile("\\[([^]]*)]");
Matcher parameterMatcher = pattern.matcher(parameterListString);
while(parameterMatcher.find())
parameterValueStrings.add(parameterMatcher.group(1).split(","));
// Parse key strings:
List<Integer> keys = new ArrayList<>();
for (String keyString : matcher.group(6).split(","))
keys.add(Integer.parseInt(keyString.trim()));
readStateFromString(boundsStr, parameterValueStrings, keys);
}
/**
* Reads upper and lower parameter element bounds and parameter values from
* strings and uses these to populate the corresponding GeneralParameterList
* fields.
*
* @param boundsStrings Two-element array containing lower and upper bounds.
* @param parameterValueStrings List of arrays of reps of parameter values
* @param keys List of keys to assign to parameters
*/
protected abstract void readStateFromString(String [] boundsStrings,
List<String[]> parameterValueStrings,
List<Integer> keys);
@Override
public int scale(double scale) {
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
@Override
protected void store() {
pListStored.clear();
for (QuietParameter param : pList)
pListStored.add(param.copy());
deallocatedKeysStored.clear();
deallocatedKeysStored.addAll(deallocatedKeys);
nextUnallocatedKeyStored = nextUnallocatedKey;
}
@Override
public void restore() {
pList.clear();
for (QuietParameter param: pListStored)
pList.add(param.copy());
deallocatedKeys.clear();
deallocatedKeys.addAll(deallocatedKeysStored);
nextUnallocatedKey = nextUnallocatedKeyStored;
hasStartedEditing = false;
}
@Override
public void setEverythingDirty(boolean isDirty) {
setSomethingIsDirty(isDirty);
}
/*
* The following methods are here because Functions are Loggable. This
* doesn't seem to make sense for ParameterLists though, so at the moment
* these methods just log the ParameterLists's size.
*/
@Override
public void init(PrintStream out) {
out.print(getID() + ".size\t");
}
@Override
public void log(int sample, PrintStream out) {
out.print(pList.size() + "\t");
}
@Override
public void close(PrintStream out) { }
/*
* The following methods are here because all StateNodes are Functions.
* They don't seem to make sense for ParameterLists though, so at the
* moment these methods just probe the ParameterList's size.
*/
@Override
public int getDimension() {
return 1;
}
@Override
public double getArrayValue() {
return pList.size();
}
@Override
public double getArrayValue(int i) {
if (i==0)
return pList.size();
else
return Double.NaN;
}
/**
* Jessie's QuietParameter. Objects of this class make sense
* only in the context of ParameterLists. They behave very much like
* Parameter<T>.Base objects, but are not StateNodes.
*/
public class QuietParameter implements Parameter<T> {
Object[] values;
int key = -1;
/**
* Construct a new QuietParameter.
*/
QuietParameter() {
values = new Object[dimension];
}
/**
* Create new QuietParameter from existing parameter.
*
* @param param
*/
QuietParameter(Parameter<?> param) {
if (param.getDimension() != dimension)
throw new IllegalArgumentException("Cannot construct "
+ "ParameterList parameter with a dimension not equal "
+ "to that specified in the enclosing list.");
values = new Object[dimension];
for (int i=0; i<param.getValues().length; i++) {
values[i] = param.getValue(i);
}
}
public int getKey() {
return key;
}
@SuppressWarnings("unchecked")
@Override
public T getValue(int i) {
return (T)values[i];
}
@SuppressWarnings("unchecked")
@Override
public T getValue() {
return (T)values[0];
}
@Override
public void setValue(int i, T value) {
startEditing(null); // ParameterList's startEditing()
values[i] = value;
}
@Override
public void setValue(T value) {
startEditing(null); // ParameterList's startEditing()
values[0] = value;
}
@Override
public T getLower() {
return lowerBound;
}
@Override
public void setLower(T lower) {
lowerBound = lower;
}
@Override
public T getUpper() {
return upperBound;
}
@Override
public void setUpper(T upper) {
upperBound = upper;
}
@SuppressWarnings("unchecked")
@Override
public T[] getValues() {
return (T[])values;
}
@Override
public String getID() {
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public int getMinorDimension1() {
return minorDimension;
}
@Override
public int getMinorDimension2() {
return dimension/minorDimension;
}
@SuppressWarnings("unchecked")
@Override
public T getMatrixValue(int i, int j) {
return (T)values[i*minorDimension+j];
}
@Override
public void swap(int i, int j) {
startEditing(null);
Object tmp = values[i];
values[i] = values[j];
values[j] = tmp;
}
@Override
public int getDimension() {
return values.length;
}
@Override
public double getArrayValue() {
return (Double)values[0];
}
@Override
public double getArrayValue(int i) {
return (Double)values[0];
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder("[");
for (int i=0; i<values.length; i++) {
if (i>0)
sb.append(",");
sb.append(values[i]);
}
sb.append("]");
return sb.toString();
}
/**
* @return deep copy of parameter.
*/
public QuietParameter copy() {
QuietParameter copy = new QuietParameter(this);
copy.key = this.key;
return copy;
}
}
}