/*
* AbstractModel.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.MPISerializable;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import java.util.ArrayList;
import java.util.List;
/**
* A model that brings together a number of model components
*
* @author Alexei Drummond
* @author Andrew Rambaut
* @version $Id: AbstractModel.java,v 1.13 2006/08/17 15:30:08 rambaut Exp $
*/
public abstract class AbstractModel implements Model, ModelListener, VariableListener, StatisticList, MPISerializable {
/**
* @param name Model Name
*/
public AbstractModel(String name) {
this.name = name;
}
/**
* Adds a sub-model to this model. If the model is already in the
* list then it does nothing.
*/
public void addModel(Model model) {
Model.CONNECTED_MODEL_SET.add(model);
if (!models.contains(model)) {
models.add(model);
model.addModelListener(this);
}
}
public void removeModel(Model model) {
models.remove(model);
model.removeModelListener(this);
}
public int getModelCount() {
return models.size();
}
public final Model getModel(int i) {
return models.get(i);
}
public final void addVariable(Variable variable) {
if (variable instanceof Parameter) {
Parameter.CONNECTED_PARAMETER_SET.add((Parameter)variable);
}
if (!variables.contains(variable)) {
variables.add(variable);
variable.addVariableListener(this);
}
// parameters are also statistics
if (variable instanceof Statistic) addStatistic((Statistic) variable);
}
public final void removeVariable(Variable variable) {
variables.remove(variable);
variable.removeVariableListener(this);
// parameters are also statistics
if (variable instanceof Statistic) removeStatistic((Statistic) variable);
}
/**
* @param parameter
* @return true of the given parameter is contained in this model
*/
public final boolean hasVariable(Variable parameter) {
return variables.contains(parameter);
}
/**
* Adds a model listener.
*/
public void addModelListener(ModelListener listener) {
listenerHelper.addModelListener(listener);
}
/**
* remove a model listener.
*/
public void removeModelListener(ModelListener listener) {
listenerHelper.removeModelListener(listener);
}
public void addModelRestoreListener(ModelListener listener) {
listenerHelper.addModelRestoreListener(listener);
}
public boolean isUsed() {
return listenerHelper.getListenerCount() > 0;
}
/**
* Fires a model changed event.
*/
public void fireModelChanged() {
listenerHelper.fireModelChanged(this, this, -1);
}
public void fireModelChanged(Object object) {
listenerHelper.fireModelChanged(this, object, -1);
}
public void fireModelChanged(Object object, int index) {
listenerHelper.fireModelChanged(this, object, index);
}
public final int getVariableCount() {
return variables.size();
}
public final Variable getVariable(int i) {
return variables.get(i);
}
// **************************************************************
// MPI IMPLEMENTATION
// **************************************************************
public void sendState(int toRank) {
// Iterate through child models
for (Model model : models) {
((AbstractModel) model).sendState(toRank);
}
// Send current model parameters
for (Variable variable : variables) {
if (variable instanceof Parameter.Abstract) ((Parameter.Abstract) variable).sendState(toRank);
}
}
public void sendStateNoParameters(int toRank) {
// Iterate through child models
for (Model model : models) {
((AbstractModel) model).sendState(toRank);
}
}
public void receiveStateNoParameters(int fromRank) {
for (Model model : models) {
((AbstractModel) model).receiveState(fromRank);
}
}
public void receiveState(int fromRank) {
for (Model model : models) {
((AbstractModel) model).receiveState(fromRank);
}
// Send current model parameters
for (Variable variable : variables) {
if (variable instanceof Parameter.Abstract)
((Parameter.Abstract) variable).receiveState(fromRank);
}
}
// **************************************************************
// ModelListener IMPLEMENTATION
// **************************************************************
public final void modelChangedEvent(Model model, Object object, int index) {
// String message = " model: " + getModelName() + "/" + getId() + " component: " + model.getModelName();
// if (object != null) {
// message += " object: " + object;
// }
// if (index != -1) {
// message += " index: " + index;
// }
// System.out.println(message);
handleModelChangedEvent(model, object, index);
}
// do nothing by default
public void modelRestored(Model model) {
}
abstract protected void handleModelChangedEvent(Model model, Object object, int index);
// **************************************************************
// VariableListener IMPLEMENTATION
// **************************************************************
public final void variableChangedEvent(Variable variable, int index, Parameter.ChangeType type) {
handleVariableChangedEvent(variable, index, type);
// todo AR - I am not sure this is required and may be overruling modelChange events on parts of the
// model. If a parameter changes it should be handleVariableChangedEvent() job to fireModelChanged
// events
listenerHelper.fireModelChanged(this, variable, index);
}
/**
* This method is called whenever a parameter is changed.
* <p/>
* It is strongly recommended that the model component sets a "dirty" flag and does no
* further calculations. Recalculation is typically done when the model component is asked for
* some information that requires them. This mechanism is 'lazy' so that this method
* can be safely called multiple times with minimal computational cost.
*/
protected abstract void handleVariableChangedEvent(Variable variable, int index, Parameter.ChangeType type);
// **************************************************************
// Model IMPLEMENTATION
// **************************************************************
public final void storeModelState() {
if (isValidState) {
// System.out.println("STORE MODEL: " + getModelName() + "/" + getId() + "/" + getClass().getCanonicalName());
for (Model m : models) {
// System.out.println("\t" + m.getModelName() + "/" + m.getClass().getCanonicalName());
m.storeModelState();
}
for (Variable variable : variables) {
// System.out.println("\t" + variable.getVariableName() + "/" + variable.getClass().getCanonicalName());
variable.storeVariableValues();
}
storeState();
isValidState = false;
}
}
public final void restoreModelState() {
if (!isValidState) {
//System.out.println("RESTORE MODEL: " + getModelName() + "/" + getId());
for (Variable variable : variables) {
variable.restoreVariableValues();
}
for (Model m : models) {
m.restoreModelState();
}
restoreState();
isValidState = true;
listenerHelper.fireModelRestored(this);
}
}
public final void acceptModelState() {
if (!isValidState) {
//System.out.println("ACCEPT MODEL: " + getModelName() + "/" + getId());
for (Variable variable : variables) {
variable.acceptVariableValues();
}
for (Model m : models) {
m.acceptModelState();
}
acceptState();
isValidState = true;
}
}
public boolean isValidState() {
return isValidState;
}
public final String getModelName() {
return name;
}
/**
* Additional state information, outside of the sub-model is stored by this call.
*/
protected abstract void storeState();
/**
* After this call the model is guaranteed to have returned its extra state information to
* the values coinciding with the last storeState call.
* Sub-models are handled automatically and do not need to be considered in this method.
*/
protected abstract void restoreState();
/**
* This call specifies that the current state is accept. Most models will not need to do anything.
* Sub-models are handled automatically and do not need to be considered in this method.
*/
protected abstract void acceptState();
// **************************************************************
// StatisticList IMPLEMENTATION
// **************************************************************
public final void addStatistic(Statistic statistic) {
if (!statistics.contains(statistic)) {
statistics.add(statistic);
}
}
public final void removeStatistic(Statistic statistic) {
statistics.remove(statistic);
}
/**
* @return the number of statistics of this component.
*/
public int getStatisticCount() {
return statistics.size();
}
/**
* @return the ith statistic of the component
*/
public Statistic getStatistic(int i) {
return statistics.get(i);
}
public final Statistic getStatistic(String name) {
for (int i = 0; i < getStatisticCount(); i++) {
Statistic statistic = getStatistic(i);
if (name.equals(statistic.getStatisticName())) {
return statistic;
}
}
return null;
}
// **************************************************************
// Identifiable IMPLEMENTATION
// **************************************************************
private String id = null;
public void setId(String id) {
this.id = id;
}
public String getId() {
return id;
}
public String toString() {
if (id != null) {
return id;
} else if (name != null) {
return name;
}
return super.toString();
}
// **************************************************************
// XMLElement IMPLEMENTATION
// **************************************************************
public Element createElement(Document d) {
throw new RuntimeException("Not implemented!");
}
boolean isValidState = true;
protected Model.ListenerHelper listenerHelper = new Model.ListenerHelper();
private final ArrayList<Model> models = new ArrayList<Model>();
private final ArrayList<Variable> variables = new ArrayList<Variable>();
private final ArrayList<Statistic> statistics = new ArrayList<Statistic>();
private final String name;
}