/*
 * BinaryCovarionModelTest.java
 *
 * Copyright (c) 2002-2014 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 test.dr.evomodel.substmodel;

import dr.evolution.datatype.DataType;
import dr.evolution.datatype.TwoStateCovarion;
import dr.evolution.datatype.TwoStates;
import dr.evomodel.substmodel.BinaryCovarionModel;
import dr.evomodel.substmodel.FrequencyModel;
import dr.evomodel.substmodel.GeneralSubstitutionModel;
import dr.evomodel.substmodel.SubstitutionModelUtils;
import dr.inference.model.Parameter;
import dr.math.matrixAlgebra.IllegalDimension;
import dr.math.matrixAlgebra.Matrix;
import dr.math.matrixAlgebra.Vector;
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;

/**
 * BinaryCovarionModel Tester.
 *
 * @author Alexei Drummond
 * @version 1.0
 * @since <pre>08/26/2007</pre>
 */
public class BinaryCovarionModelTest extends TestCase {
    public BinaryCovarionModelTest(String name) {
        super(name);
    }

    @Override
	public void setUp() throws Exception {
        super.setUp();

        frequencies = new Parameter.Default(new double[]{0.5, 0.5});
        hiddenFrequencies = new Parameter.Default(new double[]{0.5, 0.5});
        alpha = new Parameter.Default(0.0);
        switchingRate = new Parameter.Default(1.0);

        model = new BinaryCovarionModel(TwoStateCovarion.INSTANCE, frequencies, hiddenFrequencies, alpha, switchingRate,
                BinaryCovarionModel.Version.VERSION1);
        dataType = model.getDataType();
    }

    @Override
	public void tearDown() throws Exception {
        super.tearDown();
    }

    /**
     * Tests that pi*Q = 0
     */
    public void testEquilibriumDistribution() {

        alpha.setParameterValue(0, 0.1);
        switchingRate.setParameterValue(0, 1.0);

        model.setupMatrix();

        double[] pi = model.getFrequencyModel().getFrequencies();

        try {
            Matrix m = new Matrix(model.getQ());
            Vector p = new Vector(pi);
            Vector y = m.product(p);

            assertEquals(0.0, y.norm(), 1e-14);
        } catch (IllegalDimension illegalDimension) {
        }
    }

    public void testTransitionProbabilitiesAgainstEqualBaseFreqsEqualRates() {

        // with alpha == 1, the transition probability should be the same as binary jukes cantor
        alpha.setParameterValue(0, 1.0);
        switchingRate.setParameterValue(0, 1.0);

        model.setupMatrix();

        double[] matrix = new double[16];

        double[] pi = model.getFrequencyModel().getFrequencies();

        for (double distance = 0.01; distance <= 1.005; distance += 0.01) {
            model.getTransitionProbabilities(distance, matrix);

            double pChange =
                    (matrix[1] + matrix[3]) * pi[0] +
                            (matrix[4] + matrix[6]) * pi[1] +
                            (matrix[9] + matrix[11]) * pi[2] +
                            (matrix[12] + matrix[14]) * pi[3];

            // analytical result for the probability of a mismatch in binary jukes cantor model
            double jc = 0.5 * (1 - Math.exp(-2.0 * distance));

            assertEquals(pChange, jc, 1e-14);
        }
    }

    public void testTransitionProbabilitiesUnequalBaseFreqsEqualRates() {

        // with alpha == 1, the transition probability should be the same as binary jukes cantor
        alpha.setParameterValue(0, 1.0);
        switchingRate.setParameterValue(0, 1.0);
        frequencies.setParameterValue(0, 0.25);
        frequencies.setParameterValue(1, 0.75);

        FrequencyModel freqModel = new FrequencyModel(TwoStates.INSTANCE, frequencies);

        GeneralSubstitutionModel modelToCompare = new GeneralSubstitutionModel(TwoStates.INSTANCE, freqModel, null, 0);

        model.setupMatrix();

        double[] matrix = new double[16];
        double[] m = new double[4];

        double[] pi = model.getFrequencyModel().getFrequencies();

        for (double distance = 0.01; distance <= 1.005; distance += 0.01) {
            model.getTransitionProbabilities(distance, matrix);

            modelToCompare.getTransitionProbabilities(distance, m);

            double pChange =
                    (matrix[1] + matrix[3]) * pi[0] +
                            (matrix[4] + matrix[6]) * pi[1] +
                            (matrix[9] + matrix[11]) * pi[2] +
                            (matrix[12] + matrix[14]) * pi[3];

            double pChange2 = m[1] * frequencies.getParameterValue(0) +
                    m[2] * frequencies.getParameterValue(1);

            System.out.println(distance + "\t" + pChange2 + "\t" + pChange);

            assertEquals(pChange2, pChange, 1e-14);
        }
    }

    public void testTransitionProbabilitiesUnequalBaseFreqsUnequalRateFreqsEqualRates() {

        // with alpha == 1, the transition probability should be the same as binary jukes cantor
        alpha.setParameterValue(0, 1.0);
        switchingRate.setParameterValue(0, 1.0);
        frequencies.setParameterValue(0, 0.25);
        frequencies.setParameterValue(1, 0.75);
        hiddenFrequencies.setParameterValue(0, 0.1);
        hiddenFrequencies.setParameterValue(1, 0.9);

        FrequencyModel freqModel = new FrequencyModel(TwoStates.INSTANCE, frequencies);

        GeneralSubstitutionModel modelToCompare = new GeneralSubstitutionModel(TwoStates.INSTANCE, freqModel, null, 0);

        model.setupMatrix();

        double[] matrix = new double[16];
        double[] m = new double[4];

        double[] pi = model.getFrequencyModel().getFrequencies();

        for (double distance = 0.01; distance <= 1.005; distance += 0.01) {
            model.getTransitionProbabilities(distance, matrix);

            modelToCompare.getTransitionProbabilities(distance, m);

            double pChange =
                    (matrix[1] + matrix[3]) * pi[0] +
                            (matrix[4] + matrix[6]) * pi[1] +
                            (matrix[9] + matrix[11]) * pi[2] +
                            (matrix[12] + matrix[14]) * pi[3];

            double pChange2 = m[1] * frequencies.getParameterValue(0) +
                    m[2] * frequencies.getParameterValue(1);

            //System.out.println(distance + "\t" + pChange2 + "\t" + pChange);

            assertEquals(pChange2, pChange, 1e-14);
        }
    }


    public void testTransitionProbabilitiesAgainstMatLab() {
        // test againt Matlab results for alpha = 0.5 and switching rate = 1.0
        // and visible state base frequencies = 0.25, 0.75

        alpha.setParameterValue(0, 0.5);
        switchingRate.setParameterValue(0, 1.0);
        frequencies.setParameterValue(0, 0.25);
        frequencies.setParameterValue(1, 0.75);

        model.setupMatrix();
        double[] matrix = new double[16];

        double[] pi = model.getFrequencyModel().getFrequencies();
        System.out.println(pi[0] + " " + pi[1] + " " + pi[2] + " " + pi[3]);

        System.out.println(SubstitutionModelUtils.toString(model.getQ(), dataType, 2));

        int index = 0;
        for (double distance = 0.01; distance <= 1.005; distance += 0.01) {
            model.getTransitionProbabilities(distance, matrix);


            double pChange =
                    (matrix[1] + matrix[3]) * pi[0] +
                            (matrix[4] + matrix[6]) * pi[1] +
                            (matrix[9] + matrix[11]) * pi[2] +
                            (matrix[12] + matrix[14]) * pi[3];


            double pChangeIndependent = TwoStateCovarionModelTest.matLabPChange[index];

            //System.out.println(distance + "\t" + pChange + "\t" + pChangeIndependent);
            assertEquals(pChange, pChangeIndependent, 1e-14);

            index += 1;
        }
    }

    public static Test suite() {
        return new TestSuite(BinaryCovarionModelTest.class);
    }

    BinaryCovarionModel model;
    DataType dataType;
    Parameter frequencies;
    Parameter hiddenFrequencies;
    Parameter switchingRate;
    Parameter alpha;
}
