dr.evolution.coalescent.DemographicFunction.java Source code

Java tutorial

Introduction

Here is the source code for dr.evolution.coalescent.DemographicFunction.java

Source

/*
 * DemographicFunction.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.evolution.coalescent;

import dr.evolution.util.Units;
import dr.math.Binomial;
import dr.math.MathUtils;
import org.apache.commons.math.FunctionEvaluationException;
import org.apache.commons.math.MaxIterationsExceededException;
import org.apache.commons.math.analysis.UnivariateRealFunction;
import org.apache.commons.math.analysis.integration.RombergIntegrator;

/**
 * This interface provides methods that describe a demographic function.
 *
 * Parts of this class were derived from C++ code provided by Oliver Pybus.
 *
 * @version $Id: DemographicFunction.java,v 1.12 2005/05/24 20:25:55 rambaut Exp $
 *
 * @author Andrew Rambaut
 * @author Alexei Drummond
 * @author Korbinian Strimmer
 */
public interface DemographicFunction extends UnivariateRealFunction, Units {

    /**
     * @param t time
     * @return value of the demographic function N(t) at time t
     */
    double getDemographic(double t);

    double getLogDemographic(double t);

    /**
     * @return value of demographic intensity function at time t (= integral 1/N(x) dx from 0 to t).
     * @param t time
     */
    double getIntensity(double t);

    /**
     * @return value of inverse demographic intensity function
     * (returns time, needed for simulation of coalescent intervals).
     */
    double getInverseIntensity(double x);

    /**
     * Calculates the integral 1/N(x) dx between start and finish.
      * @param start  point
      * @param finish point
      * @return integral value
      */
    double getIntegral(double start, double finish);

    /**
     * @return the number of arguments for this function.
     */
    int getNumArguments();

    /**
     * @return the name of the n'th argument of this function.
     */
    String getArgumentName(int n);

    /**
     * @return the value of the n'th argument of this function.
     */
    double getArgument(int n);

    /**
     * Sets the value of the nth argument of this function.
     */
    void setArgument(int n, double value);

    /**
     * @return the lower bound of the nth argument of this function.
     */
    double getLowerBound(int n);

    /**
     * Returns the upper bound of the nth argument of this function.
     */
    double getUpperBound(int n);

    /**
     * Returns a copy of this function.
     */
    //   DemographicFunction getCopy();

    /**
     * A threshold for underflow on calculation of likelihood of internode intervals.
     * Most demo functions could probably return 0.0 but (e.g.,) the Extended Skyline
     * needs a non zero value to prevent a numerical problem. 
     * @return
     */
    double getThreshold();

    public abstract class Abstract implements DemographicFunction {
        // private static final double LARGE_POSITIVE_NUMBER = 1.0e50;
        //        private static final double LARGE_NEGATIVE_NUMBER = -1.0e50;
        //        private static final double INTEGRATION_PRECISION = 1.0e-5;
        //        private static final double INTEGRATION_MAX_ITERATIONS = 50;

        RombergIntegrator numericalIntegrator = null;

        /**
        * Construct demographic model with default settings
        */
        public Abstract(Type units) {
            setUnits(units);
        }

        // general functions

        /**
         * Default implementation
         * @param t
         * @return log(demographic at t)
         */
        public double getLogDemographic(double t) {
            return Math.log(getDemographic(t));
        }

        public double getThreshold() {
            return 0;
        }

        /**
        * Calculates the integral 1/N(x) dx between start and finish.
        */
        public double getIntegral(double start, double finish) {
            return getIntensity(finish) - getIntensity(start);
        }

        /**
         * Returns the integral of 1/N(x) between start and finish, calling either the getAnalyticalIntegral or
         * getNumericalIntegral function as appropriate.
         */
        public double getNumericalIntegral(double start, double finish) {
            // AER 19th March 2008: I switched this to use the RombergIntegrator from
            // commons-math v1.2.

            if (start > finish) {
                throw new RuntimeException("NumericalIntegration start > finish");
            }

            if (start == finish) {
                return 0.0;
            }

            if (numericalIntegrator == null) {
                numericalIntegrator = new RombergIntegrator(this);
            }

            try {
                return numericalIntegrator.integrate(start, finish);
            } catch (MaxIterationsExceededException e) {
                throw new RuntimeException(e);
            } catch (FunctionEvaluationException e) {
                throw new RuntimeException(e);
            }

            //            double lastST = LARGE_NEGATIVE_NUMBER;
            //            double lastS = LARGE_NEGATIVE_NUMBER;
            //
            //            assert(finish > start);
            //
            //            for (int j = 1; j <= INTEGRATION_MAX_ITERATIONS; j++) {
            //                // iterate doTrapezoid() until answer obtained
            //
            //                double st = doTrapezoid(j, start, finish, lastST);
            //                double s = (4.0 * st - lastST) / 3.0;
            //
            //                // If answer is within desired accuracy then return
            //                if (Math.abs(s - lastS) < INTEGRATION_PRECISION * Math.abs(lastS)) {
            //                    return s;
            //                }
            //                lastS = s;
            //                lastST = st;
            //            }
            //
            //            throw new RuntimeException("Too many iterations in getNumericalIntegral");
        }

        /**
         * Performs the trapezoid rule.
         */
        //        private double doTrapezoid(int n, double low, double high, double lastS) {
        //
        //            double s;
        //
        //            if (n == 1) {
        //                // On the first iteration s is reset
        //                double demoLow = getDemographic(low); // Value of N(x) obtained here
        //                assert(demoLow > 0.0);
        //
        //                double demoHigh = getDemographic(high);
        //                assert(demoHigh > 0.0);
        //
        //                s = 0.5 * (high - low) * ( (1.0 / demoLow) + (1.0 / demoHigh) );
        //            } else {
        //                int it=1;
        //                for (int j = 1; j < n - 1; j++) {
        //                    it *= 2;
        //                }
        //
        //                double tnm = it;   // number of points
        //                double del = (high - low) / tnm;   // width of spacing between points
        //
        //                double x = low + 0.5 * del;
        //
        //                double sum = 0.0;
        //                for (int j = 1; j <= it; j++) {
        //                    double demoX = getDemographic(x); // Value of N(x) obtained here
        //                    assert(demoX > 0.0);
        //
        //                    sum += (1.0 / demoX);
        //                    x += del;
        //                }
        //                s =  0.5 * (lastS + (high - low) * sum / tnm);   // New s uses previous s value
        //            }
        //
        //            return s;
        //        }

        // **************************************************************
        // UnivariateRealFunction IMPLEMENTATION
        // **************************************************************

        /**
         * Return the intensity at a given time for numerical integration
         * @param x the time
         * @return the intensity
         */
        public double value(double x) {
            return 1.0 / getDemographic(x);
        }

        // **************************************************************
        // Units IMPLEMENTATION
        // **************************************************************

        /**
         * Units in which population size is measured.
         */
        private Type units;

        /**
         * sets units of measurement.
         *
         * @param u units
         */
        public void setUnits(Type u) {
            units = u;
        }

        /**
         * returns units of measurement.
         */
        public Type getUnits() {
            return units;
        }
    }

    public static class Utils {
        private static double getInterval(double U, DemographicFunction demographicFunction, int lineageCount,
                double timeOfLastCoalescent) {
            final double intensity = demographicFunction.getIntensity(timeOfLastCoalescent);
            final double tmp = -Math.log(U) / Binomial.choose2(lineageCount) + intensity;

            return demographicFunction.getInverseIntensity(tmp) - timeOfLastCoalescent;
        }

        private static double getInterval(double U, DemographicFunction demographicFunction, int lineageCount,
                double timeOfLastCoalescent, double earliestTimeOfFinalCoalescent) {
            if (timeOfLastCoalescent > earliestTimeOfFinalCoalescent) {
                throw new IllegalArgumentException(
                        "Given maximum height is smaller than given final coalescent time");
            }
            final double fullIntegral = demographicFunction.getIntegral(timeOfLastCoalescent,
                    earliestTimeOfFinalCoalescent);
            final double normalisation = 1 - Math.exp(-Binomial.choose2(lineageCount) * fullIntegral);
            final double intensity = demographicFunction.getIntensity(timeOfLastCoalescent);

            double tmp = -Math.log(1 - U * normalisation) / Binomial.choose2(lineageCount) + intensity;

            return demographicFunction.getInverseIntensity(tmp) - timeOfLastCoalescent;

        }

        /**
         * @return a random interval size selected from the Kingman prior of the demographic model.
         */
        public static double getSimulatedInterval(DemographicFunction demographicFunction, int lineageCount,
                double timeOfLastCoalescent) {
            final double U = MathUtils.nextDouble(); // create unit uniform random variate
            return getInterval(U, demographicFunction, lineageCount, timeOfLastCoalescent);
        }

        public static double getSimulatedInterval(DemographicFunction demographicFunction, int lineageCount,
                double timeOfLastCoalescent, double earliestTimeOfFirstCoalescent) {
            final double U = MathUtils.nextDouble();
            return getInterval(U, demographicFunction, lineageCount, timeOfLastCoalescent,
                    earliestTimeOfFirstCoalescent);
        }

        public static double getMedianInterval(DemographicFunction demographicFunction, int lineageCount,
                double timeOfLastCoalescent) {
            return getInterval(0.5, demographicFunction, lineageCount, timeOfLastCoalescent);
        }

        /**
         * This function tests the consistency of the
         * getIntensity and getInverseIntensity methods
         * of this demographic model. If the model is
         * inconsistent then a RuntimeException will be thrown.
         * @param demographicFunction the demographic model to test.
         * @param steps the number of steps between 0.0 and maxTime to test.
         * @param maxTime the maximum time to test.
         */
        public static void testConsistency(DemographicFunction demographicFunction, int steps, double maxTime) {

            double delta = maxTime / (double) steps;

            for (int i = 0; i <= steps; i++) {
                double time = (double) i * delta;
                double intensity = demographicFunction.getIntensity(time);
                double newTime = demographicFunction.getInverseIntensity(intensity);

                if (Math.abs(time - newTime) > 1e-12) {
                    throw new RuntimeException(
                            "Demographic model not consistent! error size = " + Math.abs(time - newTime));
                }
            }
        }
    }
}