/* * Parameter.java * * Copyright (c) 2002-2015 Alexei Drummond, Andrew Rambaut and Marc Suchard * * This file is part of BEAST. * See the NOTICE file distributed with this work for additional * information regarding copyright ownership and licensing. * * BEAST 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 * of the License, or (at your option) any later version. * * BEAST 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 BEAST; if not, write to the * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, * Boston, MA 02110-1301 USA */ package dr.inference.model; import dr.inference.parallel.MPIServices; import dr.xml.Reportable; import org.w3c.dom.Document; import org.w3c.dom.Element; import java.util.*; /** * Represents a multi-dimensional continuous parameter. * * @author Alexei Drummond * @version $Id: Parameter.java,v 1.22 2005/06/08 11:23:25 alexei Exp $ */ public interface Parameter extends Statistic, Variable<Double> { /** * @param dim the index of the parameter dimension of interest * @return the parameter's scalar value in the given dimension */ double getParameterValue(int dim); /** * @return the parameter's values (may be modified, as this is a copy) */ double[] getParameterValues(); /** * sets the scalar value in the given dimension of this parameter * * @param dim the index of the dimension to set * @param value the value to set */ void setParameterValue(int dim, double value); /** * sets the scalar value in the given dimensin of this parameter to val, without firing any events * * @param dim the index of the dimension to set * @param value the value to set */ void setParameterValueQuietly(int dim, double value); /** * sets the scalar value in the given dimensin of this parameter to val, * and notifies that values in all dimension have been changed * * @param dim the index of the dimension to set * @param value the value to set */ void setParameterValueNotifyChangedAll(int dim, double value); /** * @return the name of this parameter */ String getParameterName(); /** * adds a parameter listener that is notified when this parameter changes. * * @param listener the listener */ void addParameterListener(VariableListener listener); /** * removes a parameter listener. * * @param listener the listener */ void removeParameterListener(VariableListener listener); /** * stores the state of this parameter for subsquent restore */ void storeParameterValues(); /** * restores the stored state of this parameter */ void restoreParameterValues(); /** * accepts the stored state of this parameter */ void acceptParameterValues(); /** * adopt the state of the source parameter * * @param source the parameter to adopt values from */ void adoptParameterValues(Parameter source); /** * @return true if values in all dimensions are within their bounds */ boolean isWithinBounds(); /** * Can be called before store is called. If it results in new * dimensions, then the value of the first dimension is copied into the new dimensions. * * @param dim new dimension */ void setDimension(int dim); /** * Adds new bounds to this parameter * * @param bounds to add */ void addBounds(Bounds<Double> bounds); /** * @return the intersection of all bounds added to this parameter */ Bounds<Double> getBounds(); /** * Adds an extra dimension at the given index * * @param index Index of the dimension to add * @param value value to save at end of new array */ public void addDimension(int index, double value); /** * Removes the specified dimension from parameter * * @param index Index of dimension to lose * @return the value of the dimension removed */ public double removeDimension(int index); public void fireParameterChangedEvent(); public void fireParameterChangedEvent(int index, Parameter.ChangeType type); boolean isUsed(); public final static Set<Parameter> FULL_PARAMETER_SET = new LinkedHashSet<Parameter>(); public final static Set<Parameter> CONNECTED_PARAMETER_SET = new LinkedHashSet<Parameter>(); /** * Abstract base class for parameters */ public abstract class Abstract extends Statistic.Abstract implements Parameter, Reportable { protected Abstract() { FULL_PARAMETER_SET.add(this); } protected Abstract(final String name) { super(name); FULL_PARAMETER_SET.add(this); } // ************************************************************** // MPI IMPLEMENTATION // ************************************************************** public void sendState(int toRank) { double[] value = getParameterValues(); MPIServices.sendDoubleArray(value, toRank); } public void receiveState(int fromRank) { final int length = getDimension(); double[] values = MPIServices.receiveDoubleArray(fromRank, length); for (int i = 0; i < length; i++) setParameterValueQuietly(i, values[i]); this.fireParameterChangedEvent(); } public int getDimension() { return 1; } /** * Fired when all dimensions of the parameter have changed */ public void fireParameterChangedEvent() { fireParameterChangedEvent(-1, Parameter.ChangeType.VALUE_CHANGED); } /** * Fired when a single dimension of the parameter has changed * * @param index which dimension changed * @param type the type of parameter change event */ public void fireParameterChangedEvent(int index, Parameter.ChangeType type) { if (listeners != null) { for (VariableListener listener : listeners) { listener.variableChangedEvent(this, index, type); } } } public final void addParameterListener(VariableListener listener) { if (listeners == null) { listeners = new ArrayList<VariableListener>(); } listeners.add(listener); } public final void removeParameterListener(VariableListener listener) { if (listeners != null) { listeners.remove(listener); } } public final String getStatisticName() { return getParameterName(); } public final double getStatisticValue(int dim) { return getParameterValue(dim); } @Override public String getDimensionName(int dim) { if (dimensionNames == null) { return super.getDimensionName(dim); } return dimensionNames[dim]; } public final void setDimensionNames(String[] names) { if (names != null && names.length != getDimension()) { throw new IllegalArgumentException("Length of dimension name array doesn't match the number of dimensions"); } dimensionNames = names; } public void setDimension(int dim) { throw new UnsupportedOperationException(); } /** * Defensively returns copy of parameter array. * * @return a copy of the parameter values */ public double[] getParameterValues() { double[] copyOfValues = new double[getDimension()]; for (int i = 0; i < copyOfValues.length; i++) { copyOfValues[i] = getParameterValue(i); } return copyOfValues; } public final void storeParameterValues() { if (isValid) { storeValues(); isValid = false; } } public final void restoreParameterValues() { if (!isValid) { restoreValues(); isValid = true; } } public final void acceptParameterValues() { if (!isValid) { acceptValues(); isValid = true; } } public final void adoptParameterValues(Parameter source) { adoptValues(source); isValid = true; } public boolean isWithinBounds() { Bounds<Double> bounds = getBounds(); for (int i = 0; i < getDimension(); i++) { final double value = getParameterValue(i); if (value < bounds.getLowerLimit(i) || value > bounds.getUpperLimit(i)) { return false; } } return true; } // -------------------------------------------------------------------- // IMPLEMENT VARIABLE // -------------------------------------------------------------------- /** * @return the name of this variable. */ public final String getVariableName() { return getParameterName(); } public final Double getValue(int index) { return getParameterValue(index); } public final void setValue(int index, Double value) { setParameterValue(index, value); } public Double[] getValues() { Double[] copyOfValues = new Double[getDimension()]; for (int i = 0; i < getDimension(); i++) { copyOfValues[i] = getValue(i); } return copyOfValues; } /** * @return the size of this variable - i.e. the length of the vector */ public int getSize() { return getDimension(); } /** * adds a parameter listener that is notified when this parameter changes. * * @param listener the listener */ public final void addVariableListener(VariableListener listener) { addParameterListener(listener); } /** * removes a parameter listener. * * @param listener the listener */ public final void removeVariableListener(VariableListener listener) { removeParameterListener(listener); } /** * stores the state of this parameter for subsquent restore */ public void storeVariableValues() { storeParameterValues(); } /** * restores the stored state of this parameter */ public void restoreVariableValues() { restoreParameterValues(); } /** * accepts the stored state of this parameter */ public void acceptVariableValues() { acceptParameterValues(); } public boolean isUsed() { return listeners != null && listeners.size() > 0; } // -------------------------------------------------------------------- protected abstract void storeValues(); protected abstract void restoreValues(); protected abstract void acceptValues(); protected abstract void adoptValues(Parameter source); public String toString() { StringBuffer buffer = new StringBuffer(String.valueOf(getParameterValue(0))); Bounds bounds = null; try { bounds = getBounds(); } catch (NullPointerException e) { // } final String id = getId(); if (id != null) buffer.append(", ").append(id); if (bounds != null) { buffer.append("=[").append(String.valueOf(bounds.getLowerLimit(0))); buffer.append(", ").append(String.valueOf(bounds.getUpperLimit(0))).append("]"); } for (int i = 1; i < getDimension(); i++) { buffer.append(", ").append(String.valueOf(getParameterValue(i))); if (bounds != null) { buffer.append("[").append(String.valueOf(bounds.getLowerLimit(i))); buffer.append(", ").append(String.valueOf(bounds.getUpperLimit(i))).append("]"); } } return buffer.toString(); } public String getReport() { StringBuilder sb = new StringBuilder(); Bounds bounds = null; try { bounds = getBounds(); } catch (NullPointerException e) { // Do nothing } for (int i = 0; i < getDimension(); ++i) { if (getDimensionName(i) != null) { sb.append(getDimensionName(i)).append("="); } sb.append(String.valueOf(getParameterValue(i))); if (bounds != null) { sb.append("[").append(String.valueOf(bounds.getLowerLimit(i))); sb.append(", ").append(String.valueOf(bounds.getUpperLimit(i))).append("]"); } if (i < getDimension() - 1) { sb.append(", "); } } return sb.toString(); } public Element createElement(Document document) { throw new IllegalArgumentException(); } private boolean isValid = true; private ArrayList<VariableListener> listeners; private String[] dimensionNames = null; } /** * A class that implements the Parameter interface. */ class Default extends Abstract { public Default(String id, int dimension) { this(dimension); setId(id); } public Default(String id) { this(1); // dimension setId(id); } public Default(int dimension) { this(dimension, 1.0); } public Default(double initialValue) { values = new double[1]; values[0] = initialValue; this.bounds = null; } /** * @param id a unique id for this parameter * @param initialValue the initial value for this parameter * @param lower the lower bound on this parameter * @param upper the upper bound on this parameter */ public Default(String id, double initialValue, double lower, double upper) { this(initialValue); setId(id); addBounds(new DefaultBounds(upper, lower, 1)); } public Default(int dimension, double initialValue) { values = new double[dimension]; for (int i = 0; i < dimension; i++) { values[i] = initialValue; } this.bounds = null; } public Default(String id, double[] values) { this(values); setId(id); } public Default(double[] values) { this.values = new double[values.length]; System.arraycopy(values, 0, this.values, 0, values.length); } public Default(String id, int dimension, double initialValue) { this(dimension, initialValue); setId(id); } public void addBounds(Bounds<Double> boundary) { if (bounds == null) { bounds = boundary; } else { if (!(bounds instanceof IntersectionBounds)) { IntersectionBounds newBounds = new IntersectionBounds(getDimension()); newBounds.addBounds(bounds); bounds = newBounds; } ((IntersectionBounds) bounds).addBounds(boundary); } // can't change dimension after bounds are added! //hasBeenStored = true; } //******************************************************************** // GETTERS //******************************************************************** public final int getDimension() { return values.length; } public final int getSize() { return getDimension(); } public final double getParameterValue(int i) { return values[i]; } /** * Defensively returns copy of parameter array. * * @return a copy of the parameter values */ public final double[] getParameterValues() { double[] copyOfValues = new double[values.length]; System.arraycopy(values, 0, copyOfValues, 0, copyOfValues.length); return copyOfValues; } /** * Do not write to the returned array directly!! * * @return the parameter values */ public final double[] inspectParameterValues() { return values; } public Bounds<Double> getBounds() { if (bounds == null) { throw new NullPointerException(getParameterName() + " parameter: Bounds not set"); } return bounds; } public String getParameterName() { return getId(); } //******************************************************************** // SETTERS //******************************************************************** /** * Can only be called before store is called. If it results in new * dimensions, then the value of the first dimension is copied into the new dimensions. */ public void setDimension(int dim) { final int oldDim = getDimension(); if (oldDim == dim) { return; } assert storedValues == null : "Can't change dimension after store has been called! storedValues=" + Arrays.toString(storedValues) + " bounds=" + bounds; double[] newValues = new double[dim]; // copy over new values, min in case new dim is smaller System.arraycopy(values, 0, newValues, 0, Math.min(oldDim, dim)); // fill new values with first item for (int i = oldDim; i < dim; i++) { newValues[i] = values[0]; } values = newValues; if (bounds != null) { //assert oldDim < dim : "Can't decrease dimension when bounds are set"; for (int k = 1; k < oldDim; ++k) { assert ((double) bounds.getLowerLimit(k) == bounds.getLowerLimit(0)) && ((double) bounds.getUpperLimit(k) == bounds.getUpperLimit(0)) : "Can't change dimension when bounds are not all equal"; } final double low = bounds.getLowerLimit(0); final double high = bounds.getUpperLimit(0); bounds = null; addBounds(low, high); } } /** * Adds an extra dimension to the end of values * * @param value value to save at end of new array */ public void addDimension(int index, double value) { assert bounds == null; final int n = values.length; double[] newValues = new double[n + 1]; System.arraycopy(values, 0, newValues, 0, index); newValues[index] = value; System.arraycopy(values, index, newValues, index + 1, n - index); values = newValues; fireParameterChangedEvent(index, Parameter.ChangeType.ADDED); } /** * Removes a single dimension from value array * * @param index Index of dimension to lose */ public double removeDimension(int index) { assert bounds == null; final int n = values.length; final double value = values[index]; final double[] newValues = new double[n - 1]; System.arraycopy(values, 0, newValues, 0, index); System.arraycopy(values, index, newValues, index - 1, n - index); values = newValues; fireParameterChangedEvent(index, Parameter.ChangeType.REMOVED); return value; } public void setParameterValue(int i, double val) { values[i] = val; fireParameterChangedEvent(i, Parameter.ChangeType.VALUE_CHANGED); } /** * Sets the value of the parameter without firing a changed event. * * @param dim the index of the parameter dimension * @param value the value to set */ public void setParameterValueQuietly(int dim, double value) { values[dim] = value; } /** * Sets the values of the parameter and notify that all values of the parameter have changed. * * @param i index of the value * @param val to value to set */ public void setParameterValueNotifyChangedAll(int i, double val) { values[i] = val; fireParameterChangedEvent(-1, Parameter.ChangeType.ALL_VALUES_CHANGED); } protected final void storeValues() { // no need to pay a price in a very common call for one-time rare usage //hasBeenStored = true; if (storedValues == null || storedValues.length != values.length) { storedValues = new double[values.length]; } System.arraycopy(values, 0, storedValues, 0, storedValues.length); } protected final void restoreValues() { //swap the arrays double[] temp = storedValues; storedValues = values; values = temp; //if (storedValues != null) { // System.arraycopy(storedValues, 0, values, 0, values.length); //} else throw new RuntimeException("restore called before store!"); } /** * Nothing to do */ protected final void acceptValues() { } protected final void adoptValues(Parameter source) { // todo bug ? bounds not adopted? if (getDimension() != source.getDimension()) { throw new RuntimeException("The two parameters don't have the same number of dimensions"); } for (int i = 0, n = getDimension(); i < n; i++) { values[i] = source.getParameterValue(i); } } private double[] values; private double[] storedValues; // same as !storedValues && !bounds //private boolean hasBeenStored = false; private Bounds<Double> bounds = null; public void addBounds(double lower, double upper) { addBounds(new DefaultBounds(upper, lower, getDimension())); } } class DefaultBounds implements Bounds<Double> { public DefaultBounds(double upper, double lower, int dimension) { this.uppers = new double[dimension]; this.lowers = new double[dimension]; for (int i = 0; i < dimension; i++) { uppers[i] = upper; lowers[i] = lower; } } // // public DefaultBounds(ArrayList<java.lang.Double> upperList, ArrayList<java.lang.Double> lowerList) { // // final int length = upperList.size(); // if (length != lowerList.size()) { // throw new IllegalArgumentException("upper and lower limits must be defined on the same number of dimensions."); // } // uppers = new double[length]; // lowers = new double[length]; // for (int i = 0; i < uppers.length; i++) { // uppers[i] = upperList.get(i); // lowers[i] = lowerList.get(i); // } // } public DefaultBounds(double[] uppers, double[] lowers) { if (uppers.length != lowers.length) { throw new IllegalArgumentException("upper and lower limits must be defined on the same number of dimensions."); } this.uppers = uppers; this.lowers = lowers; } public Double getUpperLimit(int i) { return uppers[i]; } public Double getLowerLimit(int i) { return lowers[i]; } public int getBoundsDimension() { return uppers.length; } public boolean isConstant() { return true; } private final double[] uppers, lowers; } }