package beast.math.statistic; import java.io.PrintStream; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import beast.core.CalculationNode; import beast.core.Description; import beast.core.Function; import beast.core.Input; import beast.core.Loggable; import beast.core.parameter.Parameter; /** * A statistic based on evaluating simple expressions. * <p/> * The expressions are in RPN, so no parsing issues. whitespace separated. Variables (other statistics), * constants and operations. Currently just the basic four, but easy to extend. * * @author Joseph Heled in beast1, migrated to beast2 by Denise Kuehnert */ @Description("RPN calculator to evaluate simple expressions of parameters (Reverse Polish notation is a mathematical notation wherein every operator follows its operands)") public class RPNcalculator extends CalculationNode implements Loggable, Function { final public Input<String> strExpressionInput = new Input<>("expression", "Expressions needed for the calculations", Input.Validate.REQUIRED); final public Input<List<Parameter<?>>> parametersInput = new Input<>("parameter", "Parameters needed for the calculations", new ArrayList<>()); private RPNexpressionCalculator[] expressions; private List<String> names; private Map<String, Object[]> variables; RPNexpressionCalculator.GetVariable[] vars; int dim; @Override public void initAndValidate() { names = new ArrayList<>(); dim = parametersInput.get().get(0).getDimension(); int pdim; for (final Parameter<?> p : parametersInput.get()) { pdim = p.getDimension(); if (pdim != dim && dim != 1 && pdim != 1) { throw new IllegalArgumentException("error: all parameters have to have same length or be of dimension 1."); } if (pdim > dim) dim = pdim; expressions = new RPNexpressionCalculator[dim]; names.add(p.toString()); for (int i = 0; i < pdim; i++) { variables = new HashMap<>(); variables.put(p.getID(), p.getValues()); } } vars = new RPNexpressionCalculator.GetVariable[dim]; for (int i = 0; i < dim; i++) { final int index = i; vars[i] = new RPNexpressionCalculator.GetVariable() { @Override public double get(final String name) { final Object[] values = (variables.get(name)); if (values == null) { String ids = ""; for (final Parameter<?> p : parametersInput.get()) { ids += p.getID() +", "; } if (parametersInput.get().size() > 0) { ids = ids.substring(0, ids.length() - 2); } throw new RuntimeException("Something went wront with the RPNCalculator with id=" + getID() +".\n" + "There might be a typo on the expression.\n" + "It should only contain these: " + ids +"\n" + "but contains " + name); } if (values[0] instanceof Boolean) return ((Boolean) values[values.length > 1 ? index : 0] ? 1. : 0.); if (values[0] instanceof Integer) return (Integer) values[values.length > 1 ? index : 0]; return (Double) values[values.length > 1 ? index : 0]; } }; } String err; for (int i = 0; i < dim; i++) { expressions[i] = new RPNexpressionCalculator(strExpressionInput.get()); err = expressions[i].validate(); if (err != null) { throw new RuntimeException("Error in expression: " + err); } } } private void updateValues() { for (Parameter<?> p : parametersInput.get()) { for (int i = 0; i < p.getDimension(); i++) { variables.put(p.getID(), p.getValues()); } } } @Override public int getDimension() { return dim; } // todo: add dirty flag to avoid double calculation!!! @Override public double getArrayValue() { return getStatisticValue(0); } @Override public double getArrayValue(final int i) { return getStatisticValue(i); } // public String getDimensionName(final int dim) { // return names.get(dim); // } /** * @return the value of the expression */ public double getStatisticValue(final int i) { updateValues(); return expressions[i].evaluate(vars[i]); } @Override public void init(final PrintStream out) { if (dim == 1) out.print(this.getID() + "\t"); else for (int i = 0; i < dim; i++) out.print(this.getID() + "_" + i + "\t"); } @Override public void log(final int sample, final PrintStream out) { for (int i = 0; i < dim; i++) out.print(getStatisticValue(i) + "\t"); } @Override public void close(final PrintStream out) { // nothing to do } public List<String> getArguments() { final List<String> arguments = new ArrayList<>(); for (final Parameter<?> par : parametersInput.get()) { arguments.add(par.getID()); } return arguments; } }