Programming BEAST without Java

14 April 2014 by Remco Bouckaert

If you want to log say a simple function of a parameter or set of parameters, programming a logger in Java is often overkill. A much simpler way is to use some of the scripting functionality that in BEAST. There are a number of options;

  • RPNCalculator which takes expressions in reverse polish notation
  • ExpParser in the MASTER? package, you can use which takes arithmetic expressions
  • Script in the BEASTLabs package, you can use complex arithmetic expressions as well as functions
  • RPNCalculator

    RPNCalculator is the simplest and most primitive of the lot. It takes as input a set of Functions and an expression in reverse Polish notation (RPN). RPN is stack based, and takes arguments first, and whenever an operator is pushed on the stack, it uses the top most positions to execute the operator. So “2 3 +” returns 5, “2 2 2 / /” return 0.5 as does “2 2 2 * /”. Variable names are resolved by the IDs of the arguments. Below, a complete XML fragment

    <RPNcalculator spec="beast.math.statistic.RPNcalculator" id="Yminus1TimesX" expression="x y 1 - *"> <!-- (y-1)*x -->
    	<parameter idref="x"/>
    	<parameter idref="y"/>  
    </RPNcalculator>

    ExpCalculator

    ExpCalculator can be found in the feast package and allows simple expressions to used as a Function. For example, to calculate the Eucledian distance to the point (20,50), you could use the following:

    <parameter id="x" >20</parameter>
    <parameter id="y" >50</parameter>
    
    <parameter id="R" spec='feast.ExpCalculator' expression="sqrt((x-20)^2+(y-50)^2)">
    	<parameter idref="x"/>
    	<parameter idref="y"/>
    </parameter>

    There is also a ExpCalculatorDistribution that you can use as a distribution where you can specify an expression to represent to log-density of the distribution. For example — ignoring constants — a normal distribtion could be specified like so:

    <distribution spec="feast.ExpCalculatorDistribution" id="target" expression="exp(-R^2/2)" parameter='@R'/>

    which can be used in for example the prior. For more information, see the feast package.

    Script

    To use Script you need to install the BEASTLab package (see the BEAST website for how to install packages).

    With beast.util.Script you can now run complex mathematical expressions like

    3 * sin(a[0]) + log(a[1]) * b 

    where a is a Function with 2 dimensions and b a single values Function. Parameters and Distributions are Functions, so you can use these for your expressions. Since Script is also a Function you can use the result of a Script in another Script.

    Script has an input named x, and every Function and the variable names in the expression must match the ID of the input-value. A complete XML fragment for logging the above expression with two parameters a and b could look something like this:

    <parameter id='a'>1.0 2.0</parameter>
    <parameter id='b'>3.0</parameter>
    
    <log spec='beast.util.Script' expression='3 * sin(a[0]) + log(a[1]) * b'>
    	<x idref='a'/>
    	<x idref='b'/>
    </log>

    With Script, you can define complex and recursive functions, for example factorial, like so:

    function fac(x) {
            if (x <= 1) {return 1;}
            return x * fac(x-1);
    }
    
    function f(a) {return fac(a);}

    Note that if you specify a scripts instead of an expression, the engine always
    calls function f with arguments of the inputs x in order of appearance. The function specification goes as text inside the element, unlike an expression, which goes inside the expression attribute. An XML fragment logging the factorial of parameter a could look something like this:

    <parameter id='a'>5.0</parameter>
    
    <log spec='beast.util.Script'>
    	<x idref='a'/>
    
    	function fac(x) {
    		    if (x &lt;= 1) {return 1;}
    		    return x * fac(x-1);
    	}
    
    	function f(a) {return fac(a);}
    
    </log>

    Note that because the text is XML, any XML character (especially ‘<' and '&') need to be escaped. The <= in the above script must replaced by &lt;=. To prevent this, you can wrap the text in a CDATA block, of which the content is not interpreted as XML but taken as is.

    <parameter id='a'>5.0</parameter>
    
    <log spec='beast.util.Script' x='@a'>
    <![CDATA[
    	function fac(x) {
    		    if (x <= 1) {return 1;}
    		    return x * fac(x-1);
    	}
    
    	function f(a) {return fac(a);}
    ]]>
    </log>

    Script syntax

    By default, the Script uses JavaScript syntax for coding expressions and functions. For expressions, the Math scope is used, so any Math-function is available, so the following functions are available: abs(), acos(), asin(), atan(), atan2(), ceil(), cos(), exp(), floor(), log(), max(), min(), pow(), random(), round(), sin(), sqrt(), tan(). A disadvantage is that debugging is awkward to put it mildly. If it does not compile, the ScriptEngine does not offer much clues to where things go wrong.

    You can specify other script engines by setting the engine attribute to one of JavaScript, python, jruby, or groovy and provided the script engine is available in your class path, you can use other syntax for specifying a script (though not for expressions). For example, to use python, you need to include jython in the class path, and set engine='python' on the Script element. A factorial logger could be done like this in python:

    <log spec='beast.util.Script' x='@a' engine='python'>
    <![CDATA[
    def f(n):
        if n <= 1:
            return 1
        else:
            return n * f(n-1)
    ]]>
    </log>

    So far, these scripts are rather simple, and can effectively only useable for logging of advanced information. In a later blog, we will look how to use scripts in other situations. But for now happy scripting!