/* Random data generator and reference result on CPU */

#pragma once
#include <iostream>
//#include <numa.h>
#include <vector>
#include <numeric>
#include <utility>
#include <iterator>
#include <algorithm>
#include <boost/range/irange.hpp>
#include <boost/range/algorithm_ext/push_back.hpp>

#define likely(x)      __builtin_expect(!!(x), 1)
#define unlikely(x)    __builtin_expect(!!(x), 0)

typedef double d_type;
typedef unsigned int i_type;

using namespace boost;

/*!brief Data structure to store values of a single feature across all examples */
struct feature {
    /*!brief feature data */
    std::vector<d_type> f_data;
    /*!brief example index of each feature */
    std::vector<i_type> f_index;
    /*!brief permuation for sorted feature values */
    std::vector<i_type> f_inv_perm;
    /*!brief ID of this feature */
    size_t feature_id;
    /*!brief number of values in this feature */
    size_t feature_size;
    feature (size_t feature_size = 0, size_t feature_id = 0) : f_data(feature_size), f_index(feature_size), feature_id(feature_id), feature_size(feature_size) {
    }
    inline void add(i_type example_idx, d_type feature_val) {
        feature_size++;
        // if feature_size is power of 2, reserve
        if (feature_size == 1) {
            f_data.reserve(64);
            f_index.reserve(64);
        }
        else if (feature_size >= 64 && !(feature_size & (feature_size - 1))) {
            f_data.reserve(feature_size * 2);
            f_index.reserve(feature_size * 2);
        }
        // push back avoids unnecessary initialization, as it calls the copy constructor
        f_data.push_back(feature_val);
        f_index.push_back(example_idx);
    }

    void sort_feature() {
        std::vector<i_type> perm;
        perm.reserve(f_index.size());
        /* find the permutation */
        boost::push_back(perm, boost::irange(0, int(f_index.size())));
        std::sort(perm.begin(), perm.end(), feature_comp(f_data));
        /* apply permutation to f_data and f_index */
        std::vector<d_type> d_tmp = f_data;
        for (size_t i = 0; i < f_data.size(); ++i) {
            f_data[i] = d_tmp[perm[i]];
        }
        std::vector<i_type> i_tmp = f_index;
        for (size_t i = 0; i < f_index.size(); ++i) {
            f_index[i] = i_tmp[perm[i]];
        }
        /* build the inverse of the permutation */
        f_inv_perm.resize(f_index.size());
        for (size_t i = 0; i < f_index.size(); ++i) {
            f_inv_perm[perm[i]] = i;
        }
    }

    /*!brief Given an example ID, find if it has the feature, and feature value */
    bool locate_example(i_type example_id, d_type& val) {
        // binary search to find this example_id
        i_type mid, high, low;
        size_t size = f_index.size();
        if (unlikely(!size)) {
            return false;
        }
        high = size;
        low = 0;
        mid = (high + low) / 2;
        while (low != mid) {
            i_type index = f_index[f_inv_perm[mid]];
            bool cmp_gt = (index > example_id);
            bool cmp_eq = (index == example_id);
            if (cmp_eq) {
                val = f_data[f_inv_perm[mid]];
                return true;
            }
            if (cmp_gt)
                high = mid;
            else
                low = mid;
            mid = (high + low) / 2;
        }
        val = f_data[f_inv_perm[mid]];
        return example_id == f_index[f_inv_perm[mid]];
    }

    /*!brief an index/feature pair supporting swap operation */
    struct f_pair {
        i_type* p_index;
        d_type* p_data;
        f_pair (i_type* p_index, d_type* p_data) : p_index(p_index), p_data(p_data) {}
        f_pair& operator=( const f_pair& rhs ) {
            printf("assigning from %d to %d\n", *(rhs.p_index), *p_index);
            *p_index = *(rhs.p_index);
            *p_data = *(rhs.p_data);
            return *this;
        }
        friend void swap(f_pair a, f_pair b) {
            printf("o%d->%d->", *(a.p_index), *(b.p_index));
            i_type i;
            d_type d;
            i = *(a.p_index);
            d = *(a.p_data);
            *(a.p_index) = *(b.p_index);
            *(a.p_data) = *(b.p_data);
            *(b.p_index) = i;
            *(b.p_data) = d;
            printf("%d\n", *(a.p_index));
        }
        inline friend bool operator< (const f_pair& lhs, const f_pair& rhs){ printf("comp %d (%f) - %d (%f)\n", *(lhs.p_index), *(lhs.p_data), *(rhs.p_index), *(rhs.p_data));return *(lhs.p_data) < *(rhs.p_data); }
        inline friend bool operator> (const f_pair& lhs, const f_pair& rhs){ return rhs < lhs; }
        inline friend bool operator<=(const f_pair& lhs, const f_pair& rhs){ return !(lhs > rhs); }
        inline friend bool operator>=(const f_pair& lhs, const f_pair& rhs){ return !(lhs < rhs); }
    };

    /*!brief an iterator for easily accessing feature data */
    class iterator : public std::iterator<std::random_access_iterator_tag, f_pair>
    {
        typedef std::iterator<std::random_access_iterator_tag, f_pair>::difference_type diffference_type;
        // Lifecycle:
        public:
            iterator() {}
            iterator(std::vector<i_type>::iterator index_iter, std::vector<d_type>::iterator feat_iter) : index_iter(index_iter), feat_iter(feat_iter) {}
            iterator(const iterator &rhs) : index_iter(rhs.index_iter), feat_iter(rhs.feat_iter) {}

        // Operators : misc
        public:
            inline iterator& operator+=(const int& rhs) {index_iter += rhs; feat_iter += rhs; return *this;}
            inline iterator& operator-=(const int& rhs) {index_iter -= rhs; feat_iter -= rhs; return *this;}
            inline f_pair operator*() {return f_pair(&*index_iter, &*feat_iter);}
            // inline f_pair operator->() {return f_pair(*index_iter, *feat_iter);}
            inline f_pair operator[](const difference_type& rhs) {return f_pair(&index_iter[rhs], &feat_iter[rhs]);}

        // Operators : arithmetic
        public:
            inline iterator& operator++() {++index_iter; ++feat_iter; return *this;}
            inline iterator& operator--() {--index_iter; --feat_iter; return *this;}
            inline iterator operator++(int) {iterator tmp(*this); ++index_iter; ++feat_iter; return tmp;}
            inline iterator operator--(int) {iterator tmp(*this); --index_iter; --feat_iter; return tmp;}
            inline difference_type operator-(const iterator& rhs) {return index_iter - rhs.index_iter;}
            inline iterator operator+(difference_type rhs) const {return iterator(index_iter + rhs, feat_iter + rhs);}
            inline iterator operator-(difference_type rhs) const {return iterator(index_iter - rhs, feat_iter - rhs);}
            // inline iterator operator+(const int& rhs) {return iterator(index_iter + rhs, feat_iter + rhs);}
            // inline iterator operator-(const int& rhs) {return iterator(index_iter - rhs, feat_iter - rhs);}
            // friend inline iterator operator+(const int& lhs, const iterator& rhs) {return iterator(lhs + rhs.index_iter, lhs + rhs.feat_iter);}
            // friend inline iterator operator-(const int& lhs, const iterator& rhs) {return iterator(lhs - rhs.index_iter, lhs - rhs.feat_iter);}

        // Operators : comparison
        public:
            inline bool operator==(const iterator& rhs) {return index_iter == rhs.index_iter;}
            inline bool operator!=(const iterator& rhs) {return index_iter != rhs.index_iter;}
            inline bool operator>(const iterator& rhs) {return index_iter > rhs.index_iter;}
            inline bool operator<(const iterator& rhs) {return index_iter < rhs.index_iter;}
            inline bool operator>=(const iterator& rhs) {return index_iter >= rhs.index_iter;}
            inline bool operator<=(const iterator& rhs) {return index_iter <= rhs.index_iter;}

        // Data members
        protected:
            std::vector<i_type>::iterator index_iter;
            std::vector<d_type>::iterator feat_iter;
    };

    iterator begin() {return iterator(f_index.begin(), f_data.begin());}
    iterator end() {return iterator(f_index.end(), f_data.end());}

    private:
    /*!brief comparison function for sort values of each feature */
        struct feature_comp {
            const std::vector<d_type>& vals;
            feature_comp (const std::vector<d_type>& vals) : vals(vals) {
            }
            bool operator()(i_type i1, i_type i2) {
                return vals[i1] < vals[i2];
            }
        };
};

/*!brief synthetic feature (generate a random feature) */
struct synth_feature : public feature {
    synth_feature (size_t feature_size, size_t feature_id) : feature(feature_size, feature_id) {
        // generate features with random numbers
        std::generate(f_data.begin(), f_data.end(), std::rand);
        // f_index is a list of 0,1,2,3,...
        // std::iota(f_index.begin(), f_index.end(), 0);
    }

};

/*!brief Data structure holding the entire training data */
struct example_data {
    /*!brief All feature data */
    std::vector<feature> features_data;
    /*!brief All feature data transpose */
    std::vector<feature> data_mat;
    /*!brief number of features*/
    size_t n_feat;
    /*!brief Label for each example*/
    std::vector<d_type> y;
    /*!brief Node ID for each example */
    std::vector<int> nid;
    /*!brief Gradient statistics */
    std::vector<d_type> grad;
    /*!brief summation of all gradients */
    d_type total_grad;
    /*!brief Hessian statistics */
    std::vector<d_type> hess;
    /*!brief summation of all hessians */
    d_type total_hess;
    example_data() : features_data(0), n_feat(0), y(0), grad(0), total_grad(0.0), hess(0), total_hess(0.0) {
        y.reserve(1024);
        features_data.reserve(1024);
    }
    void add_y(d_type y_val) {
        // if (fabs(y_val) < 1e-10)
        //     y_val = -1.0;
        y.push_back(y_val);
    }
    // void finalize(loss_func& loss) {
    void finalize(void) {
        /* resize to the real size */
        features_data.resize(n_feat);
        /* assigned feature ID to each feature */
        for (size_t i = 0; i < features_data.size(); ++i) {
           features_data[i].feature_id = i;
        }
        /* all examples assigned to node 1 (root node) */
        nid.resize(y.size());
        std::fill(nid.begin(), nid.end(), 1);
        /* initialize gradient and hessian statistics */
        /*
        grad.resize(y.size());
        hess.resize(y.size());
        for (size_t i = 0; i < y.size(); ++i) {
            grad[i] = loss.grad(y[i], 0.0);
            total_grad += grad[i];
            hess[i] = loss.hess(y[i], 0.0);
            total_hess += hess[i];
            #if DEBUG > 1
            std::cout << "index:" << i << ",label:" << y[i] << ",grad:" << grad[i] << ",hess:" << hess[i] << std::endl;
            #endif
        }
        */
    }
    inline void add_feature_val(i_type example_index, i_type feature_index, d_type feature_value) {
        /* if this feature is seen first time, add it to the map */
        if (n_feat <= feature_index) {
            n_feat = feature_index + 1; // assumes feature_index starts from 0
        }
        if (features_data.size() <= feature_index) {
            // features_data.resize(feature_index + 1);
            // use exponential growth
            features_data.resize(feature_index * 2 + 1);
        }
        features_data[feature_index].add(example_index, feature_value);
        if (data_mat.size() <= example_index) {
            // features_data.resize(feature_index + 1);
            // use exponential growth
            data_mat.resize(example_index+1);
        }
        data_mat[example_index].add(feature_index, feature_value);
    }
};


// Copyright Nick Thompson, 2017
// Use, modification and distribution are subject to the
// Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt
// or copy at http://www.boost.org/LICENSE_1_0.txt)

/*
 * This class performs exp-sinh quadrature on half infinite intervals.
 *
 * References:
 *
 * 1) Tanaka, Ken'ichiro, et al. "Function classes for double exponential integration formulas." Numerische Mathematik 111.4 (2009): 631-655.
 */

#ifndef BOOST_MATH_QUADRATURE_EXP_SINH_HPP
#define BOOST_MATH_QUADRATURE_EXP_SINH_HPP

#include <cmath>
#include <limits>
#include <memory>
#include <string>
#include <boost/math/quadrature/detail/exp_sinh_detail.hpp>

namespace boost{ namespace math{ namespace quadrature {

template<class Real, class Policy = policies::policy<> >
class exp_sinh
{
public:
   exp_sinh(size_t max_refinements = 9)
      : m_imp(std::make_shared<detail::exp_sinh_detail<Real, Policy>>(max_refinements)) {}

    template<class F>
    auto integrate(const F& f, Real a, Real b, Real tol = boost::math::tools::root_epsilon<Real>(), Real* error = nullptr, Real* L1 = nullptr, std::size_t* levels = nullptr)->decltype(std::declval<F>()(std::declval<Real>()))  const;
    template<class F>
    auto integrate(const F& f, Real tol = boost::math::tools::root_epsilon<Real>(), Real* error = nullptr, Real* L1 = nullptr, std::size_t* levels = nullptr)->decltype(std::declval<F>()(std::declval<Real>()))  const;

private:
    std::shared_ptr<detail::exp_sinh_detail<Real, Policy>> m_imp;
};

template<class Real, class Policy>
template<class F>
auto exp_sinh<Real, Policy>::integrate(const F& f, Real a, Real b, Real tolerance, Real* error, Real* L1, std::size_t* levels)->decltype(std::declval<F>()(std::declval<Real>()))  const
{
    typedef decltype(f(a)) K;
    static_assert(!std::is_integral<K>::value,
                  "The return type cannot be integral, it must be either a real or complex floating point type.");
    using std::abs;
    using boost::math::constants::half;
    using boost::math::quadrature::detail::exp_sinh_detail;

    static const char* function = "boost::math::quadrature::exp_sinh<%1%>::integrate";

    // Neither limit may be a NaN:
    if((boost::math::isnan)(a) || (boost::math::isnan)(b))
    {
       return static_cast<K>(policies::raise_domain_error(function, "NaN supplied as one limit of integration - sorry I don't know what to do", a, Policy()));
     }
    // Right limit is infinite:
    if ((boost::math::isfinite)(a) && (b >= boost::math::tools::max_value<Real>()))
    {
        // If a = 0, don't use an additional level of indirection:
        if (a == static_cast<Real>(0))
        {
            return m_imp->integrate(f, error, L1, function, tolerance, levels);
        }
        const auto u = [&](Real t)->K { return f(t + a); };
        return m_imp->integrate(u, error, L1, function, tolerance, levels);
    }

    if ((boost::math::isfinite)(b) && a <= -boost::math::tools::max_value<Real>())
    {
        const auto u = [&](Real t)->K { return f(b-t);};
        return m_imp->integrate(u, error, L1, function, tolerance, levels);
    }

    // Infinite limits:
    if ((a <= -boost::math::tools::max_value<Real>()) && (b >= boost::math::tools::max_value<Real>()))
    {
        return static_cast<K>(policies::raise_domain_error(function, "Use sinh_sinh quadrature for integration over the whole real line; exp_sinh is for half infinite integrals.", a, Policy()));
    }
    // If we get to here then both ends must necessarily be finite:
    return static_cast<K>(policies::raise_domain_error(function, "Use tanh_sinh quadrature for integration over finite domains; exp_sinh is for half infinite integrals.", a, Policy()));
}

template<class Real, class Policy>
template<class F>
auto exp_sinh<Real, Policy>::integrate(const F& f, Real tolerance, Real* error, Real* L1, std::size_t* levels)->decltype(std::declval<F>()(std::declval<Real>())) const
{
    static const char* function = "boost::math::quadrature::exp_sinh<%1%>::integrate";
    using std::abs;
    if (abs(tolerance) > 1) {
        std::string msg = std::string(__FILE__) + ":" + std::to_string(__LINE__) + ":" + std::string(function) + ": The tolerance provided is unusually large; did you confuse it with a domain bound?";
        throw std::domain_error(msg);
    }
    return m_imp->integrate(f, error, L1, function, tolerance, levels);
}

}}}
#endif


/*
 * seqgen.cpp
 *
 *  Created on: Jun 23, 2015
 *      Author: joe
 */

#include <iostream>
#include <string>
#include <vector>
#include <nlopt.hpp>
#include <armadillo>
#include <random>
#include <numeric>

using namespace std;
//using namespace arma;
using arma::randn;
using arma::mat;
using arma::expmat;

#include "seq_gen.h"
#include "utils.h"
#include "node.h"
#include "tree.h"
#include "tree_reader.h"
#include "tree_utils.h"

// TODO: do we want this order?
/*Default Rate Matrix looks like this, I don't know why but I always go A,T,C,G
 *
 *    A   T   C   G
 * A -1  .33 .33 .33
 * T .33 -1  .33 .33
 * G .33 .33  -1 .33
 * G .33 .33 .33  -1
 */

// this should enable easy changing of order, if desired

map <char, int> SequenceGenerator::nuc_map_ = {
   {'A', 0},
   {'C', 1},
   {'G', 2},
   {'T', 3}
};

string SequenceGenerator::nucleotides_ = "ACGT";
string SequenceGenerator::amino_acids_ = "ARNDCQEGHILKMFPSTWYV";

map <char, int> SequenceGenerator::aa_map_ = {

   {'A', 0},
   {'R', 1},
   {'N', 2},
   {'D', 3},
   {'C', 4},
   {'Q', 5},
   {'E', 6},
   {'G', 7},
   {'H', 8},
   {'I', 9},
   {'L', 10},
   {'K', 11},
   {'M', 12},
   {'F', 13},
   {'P', 14},
   {'S', 15},
   {'T', 16},
   {'W', 17},
   {'Y', 18},
   {'V', 19}
};

/*
map <char, int> SequenceGenerator::nucMap = {
   {'A', 0},
   {'T', 1},
   {'C', 2},
   {'G', 3}
};
string SequenceGenerator::nucleotides = "ATCG";
*/

SequenceGenerator::SequenceGenerator (int const &seqlength, vector <double> const& basefreq,
    vector < vector<double> >& rmatrix, Tree * tree, bool const& showancs,
    int const& nreps, int const& seed, float const& alpha, float const& pinvar,
    string const& ancseq, bool const& printpost, vector<double> const& multirates,
    vector <double> const& aabasefreq, bool const& is_dna):tree_(tree),
    seqlen_(seqlength), nreps_(nreps), seed_(seed), alpha_(alpha), pinvar_(pinvar),
    root_sequence_(ancseq), base_freqs_(basefreq), aa_freqs_(aabasefreq), rmatrix_(rmatrix),
    multi_rates_(multirates), show_ancs_(showancs), print_node_labels_(printpost),
    multi_model_(false), is_dna_(is_dna)  {
    /*
     for (unsigned int i = 0; i < rmatrix.size(); i++) {
        for (unsigned int j = 0; j < rmatrix.size(); j++) {
            cout << rmatrix[i][j] << " ";
        }
        cout << "\n";
    }*/
    initialize();
    if (is_dna_) {
        nstates_ = 4;
    } else {
        nstates_ = 20;
    }
    // Print out the nodes names
    if (print_node_labels_) {
        label_internal_nodes();
        print_node_labels();
        exit(0);
    }
    preorder_tree_traversal();
}

// set all values
void SequenceGenerator::initialize () {
    // set the number generator being used
    if (seed_ != -1) { // user provided seed
        generator_ = mt19937(seed_);
    } else {
        generator_ = mt19937(get_clock_seed());
    }

    // construct uniform distribution from which random numbers will be generated
    // this happens to be the default distribution, but good to be explicit
    uniformDistrib_ = uniform_real_distribution<float>(0.0, 1.0);

    // construct gamma distribution (if necessary)
    if (alpha_ != -1.0) {
        gammaDistrib_ = gamma_distribution<float>(alpha_, (1/alpha_));
    }

    if (show_ancs_) {
        label_internal_nodes();
    }
    if (root_sequence_.length() == 0) {
        root_sequence_ = generate_random_sequence();
    } else {
        check_valid_sequence();
        // if root sequence is provided, set length to this
        seqlen_ = root_sequence_.size();
    }
    // set site-specific rate (pinvar and gamma)
    site_rates_ = set_site_rates();

    if (multi_rates_.size() != 0) {
        multi_model_ = true;
    }
}

/* Use the P matrix probabilities and randomly draw numbers to see
 * if each individual state will undergo some type of change
 */
string SequenceGenerator::simulate_sequence (string const& anc,
    vector < vector <double> >& QMatrix, float const& brlength) {
    std::vector<double>::iterator low;
    vector < vector <double> > PMatrix(nstates_, vector <double>(nstates_, 0.0));
    //int ancChar = 0;
    string newstring = anc; // instead of building, set size and replace
    for (int i = 0; i < seqlen_; i++) {
        float RandNumb = get_uniform_random_deviate();
        int ancChar = 0;
        if (is_dna_) {
            ancChar = nuc_map_[anc[i]];
        } else {
            ancChar = aa_map_[anc[i]];
            //cout << ancChar << endl;
        }
        float brnew = brlength * site_rates_[i];
        PMatrix = calculate_p_matrix(QMatrix, brnew);
        for (int i = 0; i < nstates_; i++) {
            // this calculates a cumulative sum
            std::partial_sum(PMatrix[i].begin(), PMatrix[i].end(), PMatrix[i].begin(), plus<double>());
        }
        low = std::lower_bound (PMatrix[ancChar].begin(), PMatrix[ancChar].end(), RandNumb);

        if (is_dna_) {
            newstring[i] = nucleotides_[low - PMatrix[ancChar].begin()];
        } else {
            newstring[i] = amino_acids_[low - PMatrix[ancChar].begin()];
        }
    }
    //cout << newstring << endl;
    return newstring;
}

/*
 * Calculate the Q Matrix (Substitution rate matrix)
 */
vector < vector <double> > SequenceGenerator::calculate_q_matrix () {

    vector < vector <double> > bigpi(nstates_, vector <double>(nstates_, 1.0));
    vector < vector <double> > t(nstates_, vector <double>(nstates_, 0.0));

    double tscale = 0.0;

    // doing the same looping multiple times here. simplify?
    for (unsigned int i = 0; i < rmatrix_.size(); i++) {
        for (unsigned int j = 0; j < rmatrix_.size(); j++) {
            if (i != j) {
                if (is_dna_) {
                    bigpi[i][j] *= base_freqs_[i] * base_freqs_[j] * rmatrix_[i][j];
                } else {
                    bigpi[i][j] *= aa_freqs_[i] * aa_freqs_[j] * rmatrix_[i][j];
                }
                tscale += bigpi[i][j];
            } else {
                bigpi[i][j] = 0.0;
            }
        }
    }
    for (unsigned int i = 0; i < rmatrix_.size(); i++) {
        for (unsigned int j = 0; j < rmatrix_.size(); j++) {
            if (i != j) {
                bigpi[i][j] /= tscale;
            } else {
                // set the diagnols to zero *** are they not set to zero above?
                bigpi[i][j] = 0.0;
            }
        }
    }
    for (unsigned int i = 0; i < rmatrix_.size(); i++) {
        double diag = 0.0;
        for (unsigned int j = 0; j < rmatrix_.size(); j++) {
            if (i != j) {
                diag -= bigpi[i][j];
            }
        }
        bigpi[i][i] = diag;
    }
    //Divide and Transpose
    for (unsigned int i = 0; i < rmatrix_.size(); i++) {
        for (unsigned int j = 0; j < rmatrix_.size(); j++) {
            if (is_dna_) {
                bigpi[i][j] /= base_freqs_[i];
            } else {
                bigpi[i][j] /= aa_freqs_[i];
            }
        }
    }
    return bigpi;
}

/* Calculate the P Matrix (Probability Matrix)
 * Changes to armadillos format then back I don't like the way could be more
 * efficient but yeah...
 */
vector < vector <double> > SequenceGenerator::calculate_p_matrix (vector < vector <double> > const& QMatrix,
    float br) {

    vector < vector <double> > Pmatrix(nstates_, vector <double>(nstates_, 0.0));
    mat A = randn<mat>(nstates_, nstates_);
    mat B = randn<mat>(nstates_, nstates_); // why not just copy A?
    int count = 0;
    //Q * t moved into Matrix form for armadillo
    for (unsigned int i = 0; i < QMatrix.size(); i++) {
        for (unsigned int j = 0; j < QMatrix.size(); j++) {
            A[count] = (QMatrix[i][j] * br);
            count++;
        }
    }
   //exponentiate the matrix
   B = expmat(A);
   //cout << B << endl;
   count = 0;
   //convert the matrix back to C++ vector
   for (unsigned int i = 0; i < Pmatrix.size(); i++) {
        for (unsigned int j = 0; j < Pmatrix.size(); j++) {
            Pmatrix[i][j] = B[count];
            count++;
        }
   }
   return Pmatrix;
}

/*
 * Pre-Order traversal works
 * Calculates the JC Matrix
 */
// TODO: how to name ancestor nodes (sequences)
//       - if we have this we can add to results (if desired))
void SequenceGenerator::preorder_tree_traversal () {

    double brlength = 0.0;
    int rate_count = 0;
    int check = 0;
    vector < vector <double> > QMatrix(nstates_, vector <double>(nstates_, 0.0));
    //vector < vector <double> > PMatrix(4, vector <double>(4, 0.0));
    // NOTE: this uses order: A,T,C,G
    if (multi_model_) {
        rmatrix_[0][2] = multi_rates_[0]; // A->C
        rmatrix_[2][0] = multi_rates_[0]; // C->A
        rmatrix_[0][3] = multi_rates_[1]; // A->G
        rmatrix_[3][0] = multi_rates_[1]; // G->A
        rmatrix_[0][1] = multi_rates_[2]; // A->T
        rmatrix_[1][0] = multi_rates_[2]; // T->A
        rmatrix_[2][3] = multi_rates_[3]; // C->G
        rmatrix_[3][2] = multi_rates_[3]; // G->C
        rmatrix_[1][2] = multi_rates_[4]; // C->T
        rmatrix_[2][1] = multi_rates_[4]; // T->C
        rmatrix_[1][3] = multi_rates_[5]; // G->T
        rmatrix_[3][1] = multi_rates_[5]; // T->G
        rmatrix_[0][0] = (multi_rates_[0]+multi_rates_[1]+multi_rates_[2]) * -1;
        rmatrix_[1][1] = (multi_rates_[2]+multi_rates_[4]+multi_rates_[5]) * -1;
        rmatrix_[2][2] = (multi_rates_[0]+multi_rates_[3]+multi_rates_[4]) * -1;
        rmatrix_[3][3] = (multi_rates_[1]+multi_rates_[3]+multi_rates_[5]) * -1;
        for (unsigned int i = 0; i < 6; i++) {
            multi_rates_.erase (multi_rates_.begin() + 0);
        }
        //QMatrix = calcQmatrix(rate_matrix);
        //QMatrix = calculate_q_matrix();
    }
    QMatrix = calculate_q_matrix();
    Node * root = tree_->getRoot();
    seqs_[root] = root_sequence_;
    ancq_[root] = QMatrix;

    if (show_ancs_) {
        string tname = root->getName();
        Sequence seq(tname, root_sequence_);
        res.push_back(seq);
    }

    // Pre-Order Traverse the tree
    for (int k = (tree_->getNodeCount() - 2); k >= 0; k--) {
        brlength = tree_->getNode(k)->getBL();
        /*
        for (unsigned int i = 0; i < QMatrix.size(); i++) {
            for (unsigned int j = 0; j < QMatrix.size(); j++) {
                cout << QMatrix[i][j] << " ";
            }
            cout << "\n";
        }
        cout << "\n";
        */
        if (multi_model_) {
            check = (int)round(multi_rates_[0]);
            //cout << check << " " << rate_count << endl;
            if (tree_->getNode(k)->isInternal() == true && multi_rates_.size() != 0) {
                if (check == rate_count) {
                    rmatrix_[0][2] = multi_rates_[1];
                    rmatrix_[2][0] = multi_rates_[1];
                    rmatrix_[0][3] = multi_rates_[2];
                    rmatrix_[3][0] = multi_rates_[2];
                    rmatrix_[0][1] = multi_rates_[3];
                    rmatrix_[1][0] = multi_rates_[3];
                    rmatrix_[2][3] = multi_rates_[4];
                    rmatrix_[3][2] = multi_rates_[4];
                    rmatrix_[1][2] = multi_rates_[5];
                    rmatrix_[2][1] = multi_rates_[5];
                    rmatrix_[1][3] = multi_rates_[6];
                    rmatrix_[3][1] = multi_rates_[6];
                    rmatrix_[0][0] = (multi_rates_[1]+multi_rates_[2]+multi_rates_[3]) * -1;
                    rmatrix_[1][1] = (multi_rates_[3]+multi_rates_[5]+multi_rates_[6]) * -1;
                    rmatrix_[2][2] = (multi_rates_[1]+multi_rates_[4]+multi_rates_[5]) * -1;
                    rmatrix_[3][3] = (multi_rates_[2]+multi_rates_[4]+multi_rates_[6]) * -1;

                    for (unsigned int i = 0; i < 7; i++) {
                        multi_rates_.erase(multi_rates_.begin() + 0);
                    }
                    //cout << "Size " << multi_rates_.size() << endl;
                    //cout << multi_rates_[0] << endl;
                    /*
                    for (unsigned int i = 0; i < multi_rates_.size(); i++) {
                        cout << multi_rates_[i] << endl;
                    }*/
                }
                rate_count++;
            }
        }
        QMatrix = calculate_q_matrix();
        //PMatrix = calculate_p_matrix(QMatrix, brlength);
        Node * dec = tree_->getNode(k);
        Node * parent = tree_->getNode(k)->getParent();
        //ancq[dec] = QMatrix;
        vector < vector <double> > Qparent = ancq_[parent];
        string ancSeq = seqs_[parent];
        string decSeq = simulate_sequence(ancSeq, Qparent, brlength);
        /*
        for (unsigned int i = 0; i < Qparent.size(); i++) {
            for (unsigned int j = 0; j < Qparent.size(); j++) {
                cout << Qparent[i][j] << " ";
            }
            cout << "\n";
        }
        cout << "\n";*/

        seqs_[dec] = decSeq;
        ancq_[dec] = QMatrix; // why store this?
        if (show_ancs_ && tree_->getNode(k)->isInternal() == true) {
            string tname = tree_->getNode(k)->getName();
            Sequence seq(tname, decSeq);
            res.push_back(seq);
        }
        // If its a tip print the name and the sequence
        if (tree_->getNode(k)->isInternal() != true) {
            string tname = tree_->getNode(k)->getName();
            Sequence seq(tname, decSeq);
            res.push_back(seq);
        }
    }
}

// this should probably be returned on its own
void SequenceGenerator::print_node_labels() {
    cout << getNewickString(tree_) << endl;
    //cout << tree_->getRoot()->getNewick(true) <<";" << endl;
}

void SequenceGenerator::label_internal_nodes() {

    int count = 1;
    string str = "Node";
    string nlabel = "";
    Node * root = tree_->getRoot();
    root->setName("Node_0");
    for (int k = (tree_->getNodeCount() - 2); k >= 0; k--) {
        if (tree_->getNode(k)->isInternal() == true) {
            //cout << k << endl;
            str = to_string(count);
            nlabel = "Node_" + str;
            tree_->getNode(k)->setName(nlabel);
            count++;
        }
    }
}

// involves both gamma and pinvar
vector <float> SequenceGenerator::set_site_rates () {
    vector <float> srates(seqlen_, 1.0);

    // invariable sites
    if (pinvar_ != 0.0) {
        int numsample = seqlen_ * pinvar_ + 0.5;
        // sample invariable sites
        vector <int> randsites = sample_without_replacement(seqlen_, numsample);
        // must be a more elegant way of doing this
        for (int i = 0; i < numsample; i++) {
            srates[randsites[i]] = 0.0;
        }
    }

    // gamma-distributed rate variation. could explore other distributions...
    if (alpha_ != -1.0) { // default i.e. no rate variation
        for (int i = 0; i < seqlen_; i++) {
            // want to skip over sites that are set to invariable
            if (srates[i] != 0.0) {
                srates[i] = get_gamma_random_deviate(alpha_);
            }
        }
    }
    return srates;
}

// initialized as string of length seqlength, all 'G'
string SequenceGenerator::generate_random_sequence () {

    string ancseq(seqlen_, 'G');
    if (is_dna_) {
        //string ancseq(seqlen, 'G');
        vector <double> cumsum(4);
        std::vector <double>::iterator low;
        // cumulative sum
        std::partial_sum(base_freqs_.begin(), base_freqs_.end(), cumsum.begin(), plus<double>());

        for (int i = 0; i < seqlen_; i++) {
            float RandNumb = get_uniform_random_deviate();
            low = std::lower_bound (cumsum.begin(), cumsum.end(), RandNumb);
            ancseq[i] = nucleotides_[low - cumsum.begin()];
        }
    } else {
        //string ancseq(seqlen, 'G');
        vector <double> cumsum(20);
        std::vector <double>::iterator low;
        // cumulative sum
        std::partial_sum(aa_freqs_.begin(), aa_freqs_.end(), cumsum.begin(), plus<double>());

        for (int i = 0; i < seqlen_; i++) {
            float RandNumb = get_uniform_random_deviate();
            low = std::lower_bound (cumsum.begin(), cumsum.end(), RandNumb);
            ancseq[i] = amino_acids_[low - cumsum.begin()];
        }
    }
    //cout << ancseq << endl;
    return ancseq;
}

// rates are in order: A<->C,A<->G,A<->T,C<->G,C<->T,G<->T
vector < vector <double> > SequenceGenerator::construct_rate_matrix (vector <double> const& rates) {

    // initialize
    vector < vector <double> > ratemat(nstates_, vector<double>(4, 0.33));

    // planning ahead here for potential non-reversible matrices
    if (rates.size() == 6) {
        ratemat[0][1] = rates[0];
        ratemat[1][0] = rates[0];
        ratemat[0][2] = rates[1];
        ratemat[2][0] = rates[1];
        ratemat[0][3] = rates[2];
        ratemat[3][0] = rates[2];
        ratemat[1][2] = rates[3];
        ratemat[2][1] = rates[3];
        ratemat[1][3] = rates[4];
        ratemat[3][1] = rates[4];
        ratemat[2][3] = rates[5];
        ratemat[3][2] = rates[5];

        ratemat[0][0] = (rates[0] + rates[1] + rates[2]) * -1;
        ratemat[1][1] = (rates[0] + rates[3] + rates[4]) * -1;
        ratemat[2][2] = (rates[1] + rates[3] + rates[5]) * -1;
        ratemat[3][3] = (rates[2] + rates[4] + rates[5]) * -1;

    } else {
        cout << "Er, we don't deal with " << rates.size() << " rates at the moment..." << endl;
        exit(0);
    }
    return ratemat;
}

// make sure sequence contains only valid nucleotide characters
void SequenceGenerator::check_valid_sequence () {
    // make sure uppercase
    root_sequence_ = string_to_upper(root_sequence_);
    if (is_dna_) {
        std::size_t found = root_sequence_.find_first_not_of(nucleotides_);
        if (found != std::string::npos) {
            cout << "Error: illegal character '" << root_sequence_[found] << "' at position "
                << found+1 << " (only A,C,G,T allowed). Maybe specify AA with -c? Exiting." << endl;
            exit(0);
        }
    } else {
        std::size_t found = root_sequence_.find_first_not_of(amino_acids_);
        if (found != std::string::npos) {
            cout << "Error: illegal character '" << root_sequence_[found] << "' at position "
                << found+1 << " (only AA chars allowed). Exiting." << endl;
            exit(0);
        }

    }
}

// not sure of a more elegant way to do this...
vector<Sequence> SequenceGenerator::get_sequences () {
    return res;
}

// call this whenever a random float is needed
float SequenceGenerator::get_uniform_random_deviate () {
    return uniformDistrib_(generator_);
}

float SequenceGenerator::get_gamma_random_deviate (float alpha) {
    return gammaDistrib_(generator_);
}

//SEQGEN::~SEQGEN() {
//    // TODO Auto-generated destructor stub
//}


/*
 * @file eigenverb_collection.cc
 */

#include <boost/foreach.hpp>
#include <usml/types/seq_data.h>
#include <usml/eigenverb/eigenverb_collection.h>
#include <netcdfcpp.h>

using namespace usml::types;
using namespace usml::eigenverb;
using namespace boost;

// meters/degree  60 nmiles/degree * 1852 meters/nmiles
double eigenverb_collection::latitude_scaler = (60.0 * 1852.0);

/**
 * Builds a box to insert in an rtree and to query the rtree
 */
box eigenverb_collection::build_box(eigenverb verb, float sigma) {
	double q;
	double latitude;
	double longitude;
	double delta_lat;
	double delta_long;

	q = max(verb.length, verb.width);
	latitude = verb.position.latitude();
	longitude = verb.position.longitude();
	delta_lat = (sigma * q) / latitude_scaler;
	delta_long = (sigma * q) / (latitude_scaler * cos(to_radians(latitude)));

	// create a box, first point bottom left, second point upper right
	box b(point(latitude - delta_lat, longitude - delta_long),
			point(latitude + delta_lat, longitude + delta_long));

	return b;
}

/**
 * Queries the RTree for this collection of eigenverbs at the interface and the
 * spatial box specified the rcv_eigenverb.
 * Results are return via the third parameter.
 */
void eigenverb_collection::query_rtree(size_t interface, eigenverb verb,
		std::vector<value_pair>& result_s) {
	read_lock_guard guard(_rtree_mutex);
	float scaling = 1.0;
	box query_box = build_box(verb, scaling);
	_rtrees[interface].query(bgi::within(query_box),
			std::back_inserter(result_s));
}
/**
 * Generates the rtrees for this collection of eigenverbs.
 */
void eigenverb_collection::generate_rtrees() {
	if (rtrees_ready) return;
	write_lock_guard guard(_rtree_mutex);

	// Use local pair to package in rtree
	std::list<value_pair> collection_pair;

	eigenverb verb;
	eigenverb_list::iterator iter;

	for (int n = 0; n < num_interfaces(); ++n) {

		for (iter = _collection[n].begin(); iter != _collection[n].end();
				++iter) {

			verb = *iter;

			collection_pair.push_back(
					std::make_pair(
							point(verb.position.latitude(),
									verb.position.longitude()), iter));
		}
		// Use Packed constructor of rtree for fastest insertion
		_rtrees[n] = rtree_type(collection_pair.begin(), collection_pair.end());
		collection_pair.clear();
	}
	rtrees_ready = true;
}

/**
 * Writes the eigenverbs for an individual interface to a netcdf file.
 */
void eigenverb_collection::write_netcdf(
		const char* filename, size_t interface_num) const
{
	NcFile* nc_file = new NcFile(filename, NcFile::Replace);
	const eigenverb_list& curr = _collection[interface_num];

	switch (interface_num) {
	case eigenverb::BOTTOM:
		nc_file->add_att("long_name", "bottom eigenverbs");
		break;
	case eigenverb::SURFACE:
		nc_file->add_att("long_name", "surface eigenverbs");
		break;
	case eigenverb::VOLUME_UPPER:
		nc_file->add_att("long_name", "upper volume eigenverbs");
		nc_file->add_att("layer", 1);
		break;
	case eigenverb::VOLUME_LOWER:
		nc_file->add_att("long_name", "lower volume eigenverbs");
		nc_file->add_att("layer", 1);
		break;
	default: {
		size_t layer = interface_num - eigenverb::VOLUME_UPPER;
		size_t side = layer % 2;
		layer = (layer / 2) + 1;
		std::ostringstream oss;
		oss << ((side) ? "lower" : "upper") << " volume " << layer
				<< " eigenverbs";
		nc_file->add_att("long_name", oss.str().c_str());
		nc_file->add_att("layer", (long) layer);
	}
		break;
	}

	if (curr.size() > 0) {

		// dimensions

		NcDim* eigenverb_dim = nc_file->add_dim("eigenverbs",
				(long) curr.size());
		NcDim* freq_dim = nc_file->add_dim("frequency",
				(long) curr.begin()->frequencies->size());

		// variables

		NcVar* time_var = nc_file->add_var("travel_time", ncDouble,
				eigenverb_dim);
		NcVar* freq_var = nc_file->add_var("frequency", ncDouble, freq_dim);
		NcVar* power_var = nc_file->add_var("power", ncDouble, eigenverb_dim,
				freq_dim);
		NcVar* length_var = nc_file->add_var("length", ncDouble, eigenverb_dim);
		NcVar* width_var = nc_file->add_var("width", ncDouble, eigenverb_dim);
		NcVar* lat_var = nc_file->add_var("latitude", ncDouble, eigenverb_dim);
		NcVar* lng_var = nc_file->add_var("longitude", ncDouble, eigenverb_dim);
		NcVar* alt_var = nc_file->add_var("altitude", ncDouble, eigenverb_dim);
		NcVar* direction_var = nc_file->add_var("direction", ncDouble,
				eigenverb_dim);
		NcVar* grazing_var = nc_file->add_var("grazing_angle", ncDouble,
				eigenverb_dim);
		NcVar* sound_speed_var = nc_file->add_var("sound_speed", ncDouble,
				eigenverb_dim);
		NcVar* de_index_var = nc_file->add_var("de_index", ncShort,
				eigenverb_dim);
		NcVar* az_index_var = nc_file->add_var("az_index", ncShort,
				eigenverb_dim);
		NcVar* source_de_var = nc_file->add_var("source_de", ncDouble,
				eigenverb_dim);
		NcVar* source_az_var = nc_file->add_var("source_az", ncDouble,
				eigenverb_dim);
		NcVar* surface_var = nc_file->add_var("surface", ncShort,
				eigenverb_dim);
		NcVar* bottom_var = nc_file->add_var("bottom", ncShort, eigenverb_dim);
		NcVar* caustic_var = nc_file->add_var("caustic", ncShort,
				eigenverb_dim);
		NcVar* upper_var = nc_file->add_var("upper", ncShort, eigenverb_dim);
		NcVar* lower_var = nc_file->add_var("lower", ncShort, eigenverb_dim);

		// units

		time_var->add_att("units", "seconds");
		freq_var->add_att("units", "hertz");
		power_var->add_att("units", "dB");
		length_var->add_att("units", "meters");
		width_var->add_att("units", "meters");
		lat_var->add_att("units", "degrees_north");
		lng_var->add_att("units", "degrees_east");
		alt_var->add_att("units", "meters");
		direction_var->add_att("units", "degrees_true");
		direction_var->add_att("positive", "clockwise");
		grazing_var->add_att("units", "degrees");
		grazing_var->add_att("positive", "up");
		sound_speed_var->add_att("units", "m/s");
		de_index_var->add_att("units", "count");
		az_index_var->add_att("units", "count");
		source_de_var->add_att("units", "degrees");
		source_de_var->add_att("positive", "up");
		source_az_var->add_att("units", "degrees_true");
		source_az_var->add_att("positive", "clockwise");
		surface_var->add_att("units", "count");
		bottom_var->add_att("units", "count");
		caustic_var->add_att("units", "count");
		upper_var->add_att("units", "count");
		lower_var->add_att("units", "count");

		// data

		freq_var->put(curr.begin()->frequencies->data().begin(),
				(long) curr.begin()->frequencies->size());
		int record = 0;    // current record
		BOOST_FOREACH( eigenverb verb, curr ) {

			// sets current index

			time_var->set_cur(record);
			power_var->set_cur(record);
			length_var->set_cur(record);
			width_var->set_cur(record);
			lat_var->set_cur(record);
			lng_var->set_cur(record);
			alt_var->set_cur(record);
			direction_var->set_cur(record);
			grazing_var->set_cur(record);
			sound_speed_var->set_cur(record);
			de_index_var->set_cur(record);
			az_index_var->set_cur(record);
			source_de_var->set_cur(record);
			source_az_var->set_cur(record);
			surface_var->set_cur(record);
			bottom_var->set_cur(record);
			caustic_var->set_cur(record);
			upper_var->set_cur(record);
			lower_var->set_cur(record);
			++record;

			// inserts data

			double v;
			long i;
			time_var->put(&verb.time, 1);

			vector<double> power = 10.0 * log10(max(verb.power, 1e-30));
			power_var->put(power.data().begin(), 1,
					(long) verb.frequencies->size());
			v = sqrt(verb.length2);
			length_var->put(&v, 1);
			v = sqrt(verb.width2);
			width_var->put(&v, 1);
			v = verb.position.latitude();
			lat_var->put(&v, 1);
			v = verb.position.longitude();
			lng_var->put(&v, 1);
			v = verb.position.altitude();
			alt_var->put(&v, 1);
			v = to_degrees(verb.direction);
			direction_var->put(&v, 1);
			v = to_degrees(verb.grazing);
			grazing_var->put(&v, 1);
			v = verb.sound_speed;
			sound_speed_var->put(&v, 1);
			i = (long) verb.de_index;
			de_index_var->put(&i, 1);
			i = (long) verb.az_index;
			az_index_var->put(&i, 1);
			v = to_degrees(verb.source_de);
			source_de_var->put(&v, 1);
			v = to_degrees(verb.source_az);
			source_az_var->put(&v, 1);
			i = (long) verb.surface;
			surface_var->put(&i, 1);
			i = (long) verb.bottom;
			bottom_var->put(&i, 1);
			i = (long) verb.caustic;
			caustic_var->put(&i, 1);
			i = (long) verb.upper;
			upper_var->put(&i, 1);
			i = (long) verb.lower;
			lower_var->put(&i, 1);
		}
	}

	// close file

	delete nc_file; // destructor frees all netCDF temp variables
}

/**
 * Reads the eigenverbs for a single interface from a netcdf file.
 */
eigenverb_list eigenverb_collection::read_netcdf(const char* filename) {
	// return data
	eigenverb_list eigenverbs;
	// Open file
	NcFile* nc_file = new NcFile(filename, NcFile::ReadOnly);

	if (!nc_file->is_valid()) {
		cout << "Could not open file!" << endl;
		return eigenverbs;
	}

	// dimensions

	NcDim* eigenverb_dim = nc_file->get_dim("eigenverbs");
	long num_eigenverbs = eigenverb_dim->size();
	NcDim* freq_dim = nc_file->get_dim("frequency");
	long num_freq = freq_dim->size();

	// variables

	NcVar* time_var = nc_file->get_var("travel_time");
	NcVar* freq_var = nc_file->get_var("frequency");
	NcVar* power_var = nc_file->get_var("power");
	NcVar* length_var = nc_file->get_var("length");
	NcVar* width_var = nc_file->get_var("width");
	NcVar* lat_var = nc_file->get_var("latitude");
	NcVar* lng_var = nc_file->get_var("longitude");
	NcVar* alt_var = nc_file->get_var("altitude");
	NcVar* direction_var = nc_file->get_var("direction");
	NcVar* grazing_var = nc_file->get_var("grazing_angle");
	NcVar* sound_speed_var = nc_file->get_var("sound_speed");
	NcVar* de_index_var = nc_file->get_var("de_index");
	NcVar* az_index_var = nc_file->get_var("az_index");
	NcVar* source_de_var = nc_file->get_var("source_de");
	NcVar* source_az_var = nc_file->get_var("source_az");
	NcVar* surface_var = nc_file->get_var("surface");
	NcVar* bottom_var = nc_file->get_var("bottom");
	NcVar* caustic_var = nc_file->get_var("caustic");
	NcVar* upper_var = nc_file->get_var("upper");
	NcVar* lower_var = nc_file->get_var("lower");

	double* freq_data = new double[num_freq];
	double* freq_accessor = freq_data;
	seq_vector* frequencies = NULL;

	// Get frequencies just once
	for (int rec = 0; rec < num_freq; ++rec) {
		freq_var->set_cur(rec);
		freq_var->get(freq_accessor++, 1, num_freq);
	}
	frequencies = new seq_data(freq_data, num_freq);

	// data storage
	eigenverb verb;
	verb.power.resize(num_freq);
	double* data = new double[num_freq];

	// current record
	for (int rec = 0; rec < num_eigenverbs; ++rec) {
		// sets current index
		time_var->set_cur(rec);
		power_var->set_cur(rec, 0);
		length_var->set_cur(rec);
		width_var->set_cur(rec);
		lat_var->set_cur(rec);
		lng_var->set_cur(rec);
		alt_var->set_cur(rec);
		direction_var->set_cur(rec);
		grazing_var->set_cur(rec);
		sound_speed_var->set_cur(rec);
		de_index_var->set_cur(rec);
		az_index_var->set_cur(rec);
		source_de_var->set_cur(rec);
		source_az_var->set_cur(rec);
		surface_var->set_cur(rec);
		bottom_var->set_cur(rec);
		caustic_var->set_cur(rec);
		upper_var->set_cur(rec);
		lower_var->set_cur(rec);
		++rec;

		// translate units

		vector<double> power; //  = 10.0*log10(max(verb.power, 1e-30));

		// get data

		double v;
		long i;
		time_var->get(&v, 1);
		verb.time = v;
		verb.frequencies = frequencies;

		power_var->get(data, 1, num_freq);
		double* power_accessor = data;
		double tmp;
		BOOST_FOREACH (double& d, verb.power) {
			tmp = *power_accessor++; // Get dB
			d = pow(10.0, (tmp / 10.0)); // convert to linear intensity
		}

		length_var->get(&v, 1);
		verb.length = v;
		verb.length2 = v * v;

		width_var->get(&v, 1);
		verb.width = v;
		verb.width2 = v * v;

		lat_var->get(&v, 1);
		verb.position.latitude(v);
		lng_var->get(&v, 1);
		verb.position.longitude(v);
		alt_var->get(&v, 1);
		verb.position.altitude(v);
		direction_var->get(&v, 1);
		verb.direction = to_radians(v);
		grazing_var->get(&v, 1);
		verb.grazing = to_radians(v);
		sound_speed_var->get(&v, 1);
		verb.sound_speed = v;
		de_index_var->get(&i, 1);
		verb.de_index = i;
		az_index_var->get(&i, 1);
		verb.az_index = i;
		source_de_var->get(&v, 1);
		verb.source_de = to_radians(v);
		source_az_var->get(&v, 1);
		verb.source_az = to_radians(v);
		surface_var->get(&i, 1);
		verb.surface = i;
		bottom_var->get(&i, 1);
		verb.bottom = i;
		caustic_var->get(&i, 1);
		verb.caustic = i;
		upper_var->get(&i, 1);
		verb.upper = i;
		lower_var->get(&i, 1);
		verb.lower = i;

		eigenverbs.push_back(verb);
	}

	delete nc_file;

	delete[] data;
	delete[] freq_data;
	delete frequencies;

	return eigenverbs;
}


//          Copyright Sergey Tsynikin 2020.
// Distributed under the Boost Software License, Version 1.0.
//    (See accompanying file LICENSE_1_0.txt or copy at
//          https://www.boost.org/LICENSE_1_0.txt)

#define BOOST_TEST_MODULE tf_gd_lib_test_module

#include "UnitSpline.h"
#include "UnitTableFunctions.h"
#include "UnitGradDescent.h"

#include <boost/test/unit_test.hpp>

#include <cstdlib>
#include <cmath>
#include <tuple>

#include <iostream>

//#include <fstream>

using namespace std;
using namespace tf_gd_lib;

bool CmpFunc(double a, double b, double eps)
{
	return (fabs(a - b) < eps);
}
//---------------------------------------------------------------------------

BOOST_AUTO_TEST_SUITE(tf_gd_lib_test_suite)

BOOST_AUTO_TEST_CASE(tf_gd_lib_test_boost_test)
{
	cout << "------------------> TEST TEST TEST <------------------" << endl;
    BOOST_CHECK(1 > 0);
}
//---------------------------------------------------------------------------

BOOST_AUTO_TEST_CASE(tf_gd_lib_test_tabulated_function_test)
{
	TableFunction tf;
	tf.CreateDemoFunction(201, -1, 0.01);

	const double e = 0.005; // accuracy for linear interpolation/extrapolation testing

	cout << "tf(-1.003) = " << tf(-1.003) << endl;
	BOOST_CHECK( CmpFunc(tf(-1.003),  0.5689446, e) ); // left extrapolation

	cout << "tf(1.004) = " << tf(1.004) << endl;
	BOOST_CHECK( CmpFunc(tf (1.004), -0.5771398, e) ); // right extrapolation

	cout << "tf(0.117) = " << tf(0.117) << endl;
	BOOST_CHECK( CmpFunc(tf( 0.117),  0.9207505, e) ); // interpolation

	cout << "tf(0) = " << tf(0) << endl;
	BOOST_CHECK( CmpFunc(tf(0), 0, 0.00000000001) );               // center point

	tf.ClearAll();
	tf.CreateNewFunction(5, "Four_points_function");

	BOOST_CHECK( tf.Size() == 5 );

	// create some unordered data with duplicates
	tf.SetPoint(0, -1, 0.5);
	tf.SetPointByNumber(1, make_tuple(1, -0.125));
	tf.SetPoint(2,  0, 1.5);
	tf.SetPoint(3, -1, 0.75); // it's duplicate of Point[0] by x
	tf.SetPointByNumber(4, make_tuple(2, 0.25));

	tf.Sort();
	tf.KillDuplicates();
	tf.CalcStat();

	cout << "size = " << tf.Size() << endl;
	BOOST_CHECK( tf.Size() == 4 ); // one duplicate was removed

	cout << "x\ty" << endl;
	for (size_t i = 0; i < tf.Size(); ++i)
	{
		auto point = tf[i];
		cout << get<0>(point) << "\t" << get<1>(point) << endl;
	}

	BOOST_CHECK( tf.GetName() == "Four_points_function" );

	// check stats
	BOOST_CHECK( tf.GetMinX() == -1 );
	BOOST_CHECK( tf.GetMaxX() == 2 );
	BOOST_CHECK( tf.GetMinY() == -0.125 );
	BOOST_CHECK( tf.GetMaxY() == 1.5);

	BOOST_CHECK( tf.Get_x_ForMinY() == 1);
	BOOST_CHECK( tf.Get_x_ForMaxY() == 0);
	BOOST_CHECK( tf.Get_i_ForMinY() == 2); // considering that sort was done
	BOOST_CHECK( tf.Get_i_ForMaxY() == 1); // considering that sort was done

}
//---------------------------------------------------------------------------

BOOST_AUTO_TEST_CASE(tf_gd_lib_test_tf_load_and_spline_test)
{
	TableFunction tf;
	BOOST_CHECK( tf.LoadFromFile("test_data.txt") );

	BOOST_CHECK( tf.BuildSpline() );

	const double e = 0.001; // 1e-3
	BOOST_CHECK( CmpFunc(tf.Spline(-0.185), 1.930, e) ); // at some random point
	BOOST_CHECK( CmpFunc(tf.Spline(0.707), -0.335, e) ); // at some random point
	BOOST_CHECK( CmpFunc(tf.Spline(0.5), -0.250, e) );   // at existing point

	BOOST_CHECK( tf.Spline(-0.185) != tf(-0.185) ); // at some random point
	BOOST_CHECK( tf.Spline(0.707)  != tf( 0.707) ); // at some random point

	BOOST_CHECK( tf.Spline(0.5) == tf(0.5) );   // at existing point
}
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------

double user_target_function(const std::vector<double>& p)
{
	double r1 = sqrt((p[0] - 0) * (p[0] - 0) + (p[1] - 1000) * (p[1] - 1000));
	double r2 = sqrt((p[0] - 0) * (p[0] - 0) + (p[1] - 0) * (p[1] - 0));
	double r3 = sqrt((p[2] - p[0]) * (p[2] - p[0]) + (p[3] - p[1]) * (p[3] - p[1]));
	double r4 = sqrt((1000 - p[2]) * (1000 - p[2]) + (1000 - p[3]) * (1000 - p[3]));
	double r5 = sqrt((1000 - p[2]) * (1000 - p[2]) + (0 - p[3]) * (0 - p[3]));

	return r1 + r2 + r3 + r4 + r5;
}

//   *                *(1000,1000)
//    \              /
//     \            /
//      \          /
//       *--------*        //  Point1(p[0], p[1]) ,  Point2(p[2], p[3])
//      /          \
//     /            \
//    /              \
//   *(0,0)           *

BOOST_AUTO_TEST_CASE(tf_gd_lib_test_gd_user_target_function)
{
	GradDescent gd;

	gd.SetIsUseUserTargetFunction(true);
	gd.SetUseUserTargetFunction(user_target_function);

	gd.SetAlpha(0.25);   // value for momentum
	gd.SetEps(0.0001); // value to derivative
	gd.SetEta_FirstJump(10);
	gd.SetEta_k_inc(1.08);
	gd.SetEta_k_dec(2.0);
	gd.SetMin_Eta(1e-9);       // min descent rate (will be multiplied by FirstJump before get started)
	gd.SetFinDifMethod(false); // using a plain central derivative
	gd.SetMaxIters(10000);     // iteration limit
	gd.SetMaxTime(3);          // time limit (seconds)

	const int param_count = 4;
	vector<double> params(param_count);
	vector<double> min_constrains(param_count);
	vector<double> max_constrains(param_count);
	vector<double> rel_constrains(param_count); // relative constrains in %
	vector<bool> type_constrains(param_count);  // set 'false' to use absolute constrains, 'true' - for relative

	//params[0] = 900;
	//params[1] = 999;
	//params[2] = 1;
	//params[3] = 100;

	srand(time(NULL));
	params[0] = 1 + rand() % 1000;
	params[1] = 1 + rand() % 1000;
	params[2] = 1 + rand() % 1000;
	params[3] = 1 + rand() % 1000;

	min_constrains[0] = 0;      max_constrains[0] = 1000;
	min_constrains[1] = 0;      max_constrains[1] = 1000;
	min_constrains[2] = 0;      max_constrains[2] = 1000;
	min_constrains[3] = 0;      max_constrains[3] = 1000;

	rel_constrains[0] = rel_constrains[1] = rel_constrains[2] = rel_constrains[3] = 0; // not used
	type_constrains[0] = type_constrains[1] = type_constrains[2] = type_constrains[3] = false; // use absolute constrains

	gd.SetParams(params);
	gd.SetMinConstrains(min_constrains);
	gd.SetMaxConstrains(max_constrains);
	gd.SetRelConstrains(rel_constrains);
	gd.SetTypeConstrains(type_constrains);

	gd.SetCallBackFreq(25);  // call callback function every 25 iterations

	std::ios_base::fmtflags ff;
	ff = std::cout.flags();
	ff |= std::cout.fixed;
	std::cout.flags(ff);
	cout.precision(10);

	gd.SetCallback([&gd]() {
		cout << "Last Cost = " << gd.GetLastCost() << endl;
		});

	GradErrorType res = gd.Go();

	cout << "gd_user_target_function result: ";
	switch (res)
	{
	case GradErrorType::Success:
		cout << "Success" << endl;
		break;
	case GradErrorType::VectorSizesNotTheSame:
		cout << "VectorSizesNotTheSame" << endl;
		break;
	case GradErrorType::CanceledByUser:
		cout << "CanceledByUser" << endl;
		break;
	case GradErrorType::TimeOut:
		cout << "TimeOut" << endl;
		break;
	case GradErrorType::ItersOverflow:
		cout << "ItersOverflow" << endl;
		break;
	}

	cout << "gd.GetLastCost() = " << gd.GetLastCost() << endl;

	params = gd.GetParams();

	cout << "Found Params: " << endl;
	for (auto p : params)
		cout << p << endl;

	BOOST_CHECK(CmpFunc(params[0], 288.67513, 0.0001));   // almost the same as expected
	BOOST_CHECK(CmpFunc(params[1], 500.00000, 0.0001));   // almost the same as expected
	BOOST_CHECK(CmpFunc(params[2], 711.32486, 0.0001));   // almost the same as expected
	BOOST_CHECK(CmpFunc(params[3], 500.00000, 0.0001));   // almost the same as expected
}
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------

double linear_experimental(double x)
{
	return 1.23 * x - 0.123;
}

double linear_predict(double x, const std::vector<double> &p)
{
	return p[0] * x + p[1];
}

BOOST_AUTO_TEST_CASE(tf_gd_lib_test_gd_linear_test)
{
	GradDescent gd;

	TableFunction experimental;   // Generate some linear experimental data
	experimental.CreateDemoFunction(101, -10, 0.25, linear_experimental, "Linear_experimental");

	gd.SetSrcFunction(experimental);
	gd.SetDstFunction(linear_predict);

	gd.SetAlpha(0.5);   // value for momentum
	gd.SetEps(0.00001); // value to derivative
	gd.SetEta_FirstJump(10);
	gd.SetEta_k_inc(1.08);
	gd.SetEta_k_dec(2.0);
	gd.SetMin_Eta(1e-8);       // min descent rate (will be multiplied by FirstJump before get started)
	gd.SetFinDifMethod(false); // using a plain central derivative
	gd.SetMaxIters(10000);     // iteration limit
	gd.SetMaxTime(3);          // time limit (seconds)

	const int param_count = 2;
	vector<double> params(param_count);
	vector<double> min_constrains(param_count);
	vector<double> max_constrains(param_count);
	vector<double> rel_constrains(param_count); // relative constrains in %
	vector<bool> type_constrains(param_count);  // set 'false' to use absolute constrains, 'true' - for relative

	params[0] = params[1] = 0; // start parameters
	min_constrains[0] = min_constrains[1] = -1000;
	max_constrains[0] = max_constrains[1] =  1000;
	rel_constrains[0] = rel_constrains[1] = 0;
	type_constrains[0] = type_constrains[1] = false; // use absolute constrains

	gd.SetParams(params);
	gd.SetMinConstrains(min_constrains);
	gd.SetMaxConstrains(max_constrains);
	gd.SetRelConstrains(rel_constrains);
	gd.SetTypeConstrains(type_constrains);

	GradErrorType res = gd.Go();

	cout << "gd_linear result: ";
	switch (res)
	{
	case GradErrorType::Success:
		cout << "Success" << endl;
		break;
	case GradErrorType::VectorSizesNotTheSame:
		cout << "VectorSizesNotTheSame" << endl;
		break;
	case GradErrorType::CanceledByUser:
		cout << "CanceledByUser" << endl;
		break;
	case GradErrorType::TimeOut:
		cout << "TimeOut" << endl;
		break;
	case GradErrorType::ItersOverflow:
		cout << "ItersOverflow" << endl;
		break;
	}

	cout << "gd.GetLastCost() = " << gd.GetLastCost() << endl;

	params = gd.GetParams();

	BOOST_CHECK(CmpFunc(gd.GetLastCost(), 0, 1e-16));

	cout << "Found Params: " << endl;
	for (auto p : params)
		cout << p << endl;

	BOOST_CHECK(CmpFunc(params[0],  1.23,  1e-12)); // almost the same as in SrcFunction
	BOOST_CHECK(CmpFunc(params[1], -0.123, 1e-12)); // almost the same as in SrcFunction
}
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------

double polynominal_experimental(double x)
{
	double noise = rand() / (double)RAND_MAX / 25.0; // add some noise
	return 0.5*x*x*x - 1.1*x*x + 0.75*x - 1.5 + noise;
}

double polynominal_predict(double x, const std::vector<double>& p)
{
	return p[0] * x * x * x + p[1] * x * x + p[2] * x + p[3];
}

BOOST_AUTO_TEST_CASE(tf_gd_lib_test_gd_polynominal_test)
{
	GradDescent gd;

	TableFunction experimental; // Generate some polynominal experimental data
	experimental.CreateDemoFunction(101, -10, 0.25, polynominal_experimental, "polynominal_experimental");

	gd.SetSrcFunction(experimental);
	gd.SetDstFunction(polynominal_predict);

	gd.SetAlpha(0.4);   // value for momentum
	gd.SetEps(0.000001); // value to derivative
	gd.SetEta_FirstJump(10);
	gd.SetEta_k_inc(1.1);
	gd.SetEta_k_dec(2.0);
	gd.SetMin_Eta(1e-10);      // min descent rate (will be multiplied by FirstJump before get started)
	gd.SetFinDifMethod(true);  // using a method of finite differences
	gd.SetMaxIters(5000);      // iteration limit
	gd.SetMaxTime(3);          // time limit (seconds)

	const int param_count = 4;
	vector<double> params(param_count, 0);             // all start parameters are 0
	vector<double> min_constrains(param_count, -10);   // all min_constrains are -10
	vector<double> max_constrains(param_count,  10);   // all max constrains are  10
	vector<double> rel_constrains(param_count, 0);     // relative constrains in %, aren't used
	vector<bool> type_constrains(param_count, false);  // 'false' - to use absolute constrains

	gd.SetParams(params);
	gd.SetMinConstrains(min_constrains);
	gd.SetMaxConstrains(max_constrains);
	gd.SetRelConstrains(rel_constrains);
	gd.SetTypeConstrains(type_constrains);

	GradErrorType res = gd.Go();

	cout << "gd_polynominal result: ";
	switch (res)
	{
	case GradErrorType::Success:
		cout << "Success" << endl;
		break;
	case GradErrorType::VectorSizesNotTheSame:
		cout << "VectorSizesNotTheSame" << endl;
		break;
	case GradErrorType::CanceledByUser:
		cout << "CanceledByUser" << endl;
		break;
	case GradErrorType::TimeOut:
		cout << "TimeOut" << endl;
		break;
	case GradErrorType::ItersOverflow:
		cout << "ItersOverflow" << endl;
		break;
	}

	cout << "gd.GetLastCost() = " << gd.GetLastCost() << endl;

	params = gd.GetParams();

	cout << "Found Params: " << endl;
	for (auto p : params)
		cout << p << endl;

	BOOST_CHECK(CmpFunc(params[0],  0.5,  0.001)); // close to parameters of SrcFunction
	BOOST_CHECK(CmpFunc(params[1], -1.1,  0.001)); // close to parameters of SrcFunction
	BOOST_CHECK(CmpFunc(params[2],  0.75, 0.001)); // close to parameters of SrcFunction
	BOOST_CHECK(CmpFunc(params[3], -1.5,  0.05));  // close to parameters of SrcFunction
}
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------

double damped_oscillations_experimental(double x)
{
	double noise = rand() / (double)RAND_MAX / 250.0; // add some noise
	return 3.0 * sin(0.25 * x + 0.5) * exp(-0.02 * x) + 10.0 + noise;
}

double damped_oscillations_predict(double x, const std::vector<double>& p)
{
	return p[0] * sin(p[1] * x + p[2]) * exp(-p[3] * x) + p[4];
}

BOOST_AUTO_TEST_CASE(tf_gd_lib_test_gd_damped_oscillations_test)
{
	GradDescent gd;

	TableFunction experimental; // Generate some damped_oscillations experimental data
	experimental.CreateDemoFunction(101, -20, 1.0, damped_oscillations_experimental, "damped_oscillations_experimental");

	gd.SetSrcFunction(experimental);
	gd.SetDstFunction(damped_oscillations_predict);

	gd.SetAlpha(0.45);    // value for momentum
	gd.SetEps(0.000001);  // value to derivative
	gd.SetEta_FirstJump(10);
	gd.SetEta_k_inc(1.09);
	gd.SetEta_k_dec(2.0);
	gd.SetMin_Eta(1e-11);       // min descent rate (will be multiplied by FirstJump before get started)
	gd.SetFinDifMethod(false);  // using a plain central derivative
	gd.SetMaxIters(1000);       // iteration limit
	gd.SetMaxTime(3);           // time limit (seconds)

	const int param_count = 5;
	vector<double> params(param_count);
	vector<double> min_constrains(param_count);
	vector<double> max_constrains(param_count);
	vector<double> rel_constrains(param_count, 0);
	vector<bool> type_constrains(param_count, false);

	params[0] = 2.5; params[1] = 0.27; params[2] = 0; params[3] = 0.01; params[4] = 15;

	min_constrains[0] = 1;       max_constrains[0] = 3;
	min_constrains[1] = 0;       max_constrains[1] = 0;    // won't be use
	min_constrains[2] = -3.15;   max_constrains[2] = 3.15;
	min_constrains[3] = 0.001;   max_constrains[3] = 0.05;
	min_constrains[4] = 0;       max_constrains[4] = 30;

	rel_constrains[1] = 15;      // 15 % by params[1]
	type_constrains[1] = true;

	gd.SetParams(params);
	gd.SetMinConstrains(min_constrains);
	gd.SetMaxConstrains(max_constrains);
	gd.SetRelConstrains(rel_constrains);
	gd.SetTypeConstrains(type_constrains);

	gd.SetCallBackFreq(50);  // call callback function every 50 iterations

	gd.SetCallback([&gd]() {
		cout << "Last Cost = " << gd.GetLastCost() << endl;
		});

	GradErrorType res = gd.Go();

	cout << "gd_damped_oscillations result: ";
	switch (res)
	{
	case GradErrorType::Success:
		cout << "Success" << endl;
		break;
	case GradErrorType::VectorSizesNotTheSame:
		cout << "VectorSizesNotTheSame" << endl;
		break;
	case GradErrorType::CanceledByUser:
		cout << "CanceledByUser" << endl;
		break;
	case GradErrorType::TimeOut:
		cout << "TimeOut" << endl;
		break;
	case GradErrorType::ItersOverflow:
		cout << "ItersOverflow" << endl;
		break;
	}

	cout << "gd.GetLastCost() = " << gd.GetLastCost() << endl;

	params = gd.GetParams();

	cout << "Found Params: " << endl;
	for (auto p : params)
		cout << p << endl;

	BOOST_CHECK(CmpFunc(params[0], 3.0,  0.001)); // close to parameters of SrcFunction
	BOOST_CHECK(CmpFunc(params[1], 0.25, 0.0001));  // close to parameters of SrcFunction
	BOOST_CHECK(CmpFunc(params[2], 0.5,  0.0005));  // close to parameters of SrcFunction
	BOOST_CHECK(CmpFunc(params[3], 0.02, 0.0001));  // close to parameters of SrcFunction
	BOOST_CHECK(CmpFunc(params[4], 10.0, 0.005));   // close to parameters of SrcFunction
}
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------

double two_gaussian_distribution_experimental(double x)
{
	double noise = rand() / (double)RAND_MAX / 1.5; // add some noise
	return 7500 * exp(-(x - 8600) * (x - 8600) / (2.0 * 80 * 80)) + // the first gaussian distribution
		   2250 * exp(-(x - 8900) * (x - 8900) / (2.0 * 85 * 85)) + // the second gaussian distribution
		   -0.1 * x + 1200 +                                        // linear background
		   noise;
}

double two_gaussian_distribution_predict(double x, const std::vector<double>& p)
{
	return p[0] * exp(-(x - p[1]) * (x - p[1]) / (2.0 * p[2] * p[2])) + // the first gaussian distribution
		   p[3] * exp(-(x - p[4]) * (x - p[4]) / (2.0 * p[5] * p[5])) + // the second gaussian distribution
		   p[6] * x + p[7];                                             // linear background
}

BOOST_AUTO_TEST_CASE(tf_gd_lib_test_gd_two_gaussian_distribution_test)
{
	GradDescent gd;

	TableFunction experimental; // Generate some two_gaussian_distribution experimental data
	experimental.CreateDemoFunction(101, 8200, 12, two_gaussian_distribution_experimental, "two_gaussian_distribution_experimental");

	gd.SetSrcFunction(experimental);
	gd.SetDstFunction(two_gaussian_distribution_predict);

	gd.SetAlpha(0.55);    // value for momentum
	gd.SetEps(0.00001);   // value to derivative
	gd.SetEta_FirstJump(10);
	gd.SetEta_k_inc(1.2);
	gd.SetEta_k_dec(2.0);
	gd.SetMin_Eta(1e-10);       // min descent rate (will be multiplied by FirstJump before get started)
	gd.SetFinDifMethod(false);  // using a plain central derivative
	gd.SetMaxIters(10000);      // iteration limit
	gd.SetMaxTime(10);          // time limit (seconds)

	const int param_count = 8;
	vector<double> params(param_count);
	vector<double> min_constrains(param_count);
	vector<double> max_constrains(param_count);
	vector<double> rel_constrains(param_count, 0);
	vector<bool> type_constrains(param_count, false);

	params[0] = 7000; params[1] = 8700; params[2] = 75; // the first gaussian distribution
	params[3] = 2400; params[4] = 8850; params[5] = 90; // the second gaussian distribution
	params[6] = 0; params[7] = 1000;                    // linear background

	min_constrains[0] = 5000;       max_constrains[0] = 10000;
	min_constrains[1] = 8500;       max_constrains[1] = 8800;
	min_constrains[2] = 60;         max_constrains[2] = 100;
	min_constrains[3] = 1500;       max_constrains[3] = 4200;
	min_constrains[4] = 8800;       max_constrains[4] = 9000;
	min_constrains[5] = 30;         max_constrains[5] = 100;
	min_constrains[6] = -1;         max_constrains[6] = 1;
	min_constrains[7] = -3000;      max_constrains[7] = 3000;

	gd.SetParams(params);
	gd.SetMinConstrains(min_constrains);
	gd.SetMaxConstrains(max_constrains);
	gd.SetRelConstrains(rel_constrains);
	gd.SetTypeConstrains(type_constrains);

	gd.SetCallBackFreq(200);  // call callback function every 200 iterations

	gd.SetCallback([&gd]() {
		cout << "Last Cost = " << gd.GetLastCost() << endl;
		});

	GradErrorType res = gd.Go();

	cout << "gd_two_gaussian_distribution result: ";
	switch (res)
	{
	case GradErrorType::Success:
		cout << "Success" << endl;
		break;
	case GradErrorType::VectorSizesNotTheSame:
		cout << "VectorSizesNotTheSame" << endl;
		break;
	case GradErrorType::CanceledByUser:
		cout << "CanceledByUser" << endl;
		break;
	case GradErrorType::TimeOut:
		cout << "TimeOut" << endl;
		break;
	case GradErrorType::ItersOverflow:
		cout << "ItersOverflow" << endl;
		break;
	}

	cout << "gd.GetLastCost() = " << gd.GetLastCost() << endl;

	params = gd.GetParams();

	cout << "Found Params: " << endl;
	for (auto p : params)
		cout << p << endl;

	BOOST_CHECK(CmpFunc(params[0], 7500, 0.5));   // close to parameters of SrcFunction
	BOOST_CHECK(CmpFunc(params[1], 8600, 0.05));  // close to parameters of SrcFunction
	BOOST_CHECK(CmpFunc(params[2], 80,   0.05));  // close to parameters of SrcFunction
	BOOST_CHECK(CmpFunc(params[3], 2250, 0.3));   // close to parameters of SrcFunction
	BOOST_CHECK(CmpFunc(params[4], 8900, 0.05));  // close to parameters of SrcFunction
	BOOST_CHECK(CmpFunc(params[5], 85,   0.05));  // close to parameters of SrcFunction
	BOOST_CHECK(CmpFunc(params[6], -0.1, 0.08));  // close to parameters of SrcFunction
	BOOST_CHECK(CmpFunc(params[7], 1200, 10));    // close to parameters of SrcFunction
}
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------

double mix_experimental(double x)
{
	double noise = rand() / (double)RAND_MAX / 100.0; // add some noise
	return -0.1 * x * x * x -0.6 * x * x + 4.0 * x -200.0 +            // polynominal
		   10.0 * sin(1.5 * x + 1.0) * exp(-0.1 * x) +                 // damped oscillations
		   40 * exp(-(x - -2.0) * (x - -2.0) / (2.0 * 2.0 * 2.0)) + // gaussian distribution
		   noise;
}

double mix_predict(double x, const std::vector<double>& p)
{
	return p[0] * x * x * x + p[1] * x * x + p[2] * x + p[3] +           // polynominal
		   p[4] * sin(p[5] * x + p[6]) * exp(-p[7] * x) +                // damped oscillations
		   p[8] * exp(-(x - p[9]) * (x - p[9]) / (2.0 * p[10] * p[10])); // gaussian distribution
}

BOOST_AUTO_TEST_CASE(tf_gd_lib_test_gd_mix_test)
{
	GradDescent gd;

	TableFunction experimental; // Generate some mix_distribution experimental data
	experimental.CreateDemoFunction(101, -10, 0.18, mix_experimental, "mix_experimental");

	gd.SetSrcFunction(experimental);
	gd.SetDstFunction(mix_predict);

	gd.SetAlpha(0.55);    // value for momentum
	gd.SetEps(0.00001);   // value to derivative
	gd.SetEta_FirstJump(10);
	gd.SetEta_k_inc(1.15);
	gd.SetEta_k_dec(2.0);
	gd.SetMin_Eta(1e-12);       // min descent rate (will be multiplied by FirstJump before get started)
	gd.SetFinDifMethod(false);  // using a plain central derivative
	gd.SetMaxIters(10000);      // iteration limit
	gd.SetMaxTime(10);          // time limit (seconds)

	const int param_count = 11;
	vector<double> params(param_count);
	vector<double> min_constrains(param_count);
	vector<double> max_constrains(param_count);
	vector<double> rel_constrains(param_count, 0);
	vector<bool> type_constrains(param_count, false);

	// -0.1 * x * x * x - 0.6 * x * x + 4.0 * x - 200.0 +        // polynominal
	//	10.0 * sin(1.5 * x + 1.0) * exp(-0.1 * x) +              // damped oscillations
	//	40 * exp(-(x - -2.0) * (x - -2.0) / (2.0 * 2.0 * 2.0))   // gaussian distribution

	params[0] = 0;   params[1] = 0;     params[2] = 0;    params[3] = -235;  // polynominal
	params[4] = 7;   params[5] = 1.4;   params[6] = 1.7;  params[7] = 0.3;   // damped oscillations
	params[8] = 60;  params[9] = -3.5;  params[10] = 5;                      // gaussian distribution

	min_constrains[0] = -10;       max_constrains[0] = 10;
	min_constrains[1] = -10;       max_constrains[1] = 10;
	min_constrains[2] = -10;       max_constrains[2] = 10;
	min_constrains[3] = -300;      max_constrains[3] = -100;
	min_constrains[4] = 5;         max_constrains[4] = 20;
	min_constrains[5] = 1.3;       max_constrains[5] = 1.7;
	min_constrains[6] = -6;        max_constrains[6] = 6;
	min_constrains[7] = 0.001;     max_constrains[7] = 0.5;
	min_constrains[8] = 20;        max_constrains[8] = 100;
	min_constrains[9] = -5;        max_constrains[9] = 2;
	min_constrains[10] = 1;        max_constrains[10] = 10;

	gd.SetParams(params);
	gd.SetMinConstrains(min_constrains);
	gd.SetMaxConstrains(max_constrains);
	gd.SetRelConstrains(rel_constrains);
	gd.SetTypeConstrains(type_constrains);

	gd.SetCallBackFreq(200);  // call callback function every 200 iterations

	gd.SetCallback([&gd]() {
		cout << "Last Cost = " << gd.GetLastCost() << endl;
		});

	GradErrorType res = gd.Go();

	cout << "mix result: ";
	switch (res)
	{
	case GradErrorType::Success:
		cout << "Success" << endl;
		break;
	case GradErrorType::VectorSizesNotTheSame:
		cout << "VectorSizesNotTheSame" << endl;
		break;
	case GradErrorType::CanceledByUser:
		cout << "CanceledByUser" << endl;
		break;
	case GradErrorType::TimeOut:
		cout << "TimeOut" << endl;
		break;
	case GradErrorType::ItersOverflow:
		cout << "ItersOverflow" << endl;
		break;
	}

	cout << "gd.GetLastCost() = " << gd.GetLastCost() << endl;

	params = gd.GetParams();

	cout << "Found Params: " << endl;
	for (auto p : params)
		cout << p << endl;

// -0.1 * x * x * x - 0.6 * x * x + 4.0 * x - 200.0 +          // polynominal
//	10.0 * sin(1.5 * x + 1.0) * exp(-0.1 * x) +                // damped oscillations
//	40 * exp(-(x - -2.0) * (x - -2.0) / (2.0 * 2.0 * 2.0))     // gaussian distribution

	BOOST_CHECK(CmpFunc(params[0], -0.1, 0.03));   // close to parameters of SrcFunction
	BOOST_CHECK(CmpFunc(params[1], -0.6, 0.05));  // close to parameters of SrcFunction
	BOOST_CHECK(CmpFunc(params[2],  4.0, 0.05));  // close to parameters of SrcFunction
	BOOST_CHECK(CmpFunc(params[3], -200, 0.03));   // close to parameters of SrcFunction
	BOOST_CHECK(CmpFunc(params[4], 10.0, 0.05));  // close to parameters of SrcFunction
	BOOST_CHECK(CmpFunc(params[5],  1.5, 0.05));  // close to parameters of SrcFunction
	BOOST_CHECK(CmpFunc(params[6],  1.0, 0.05));  // close to parameters of SrcFunction
	BOOST_CHECK(CmpFunc(params[7],  0.1, 0.05));     // close to parameters of SrcFunction
	BOOST_CHECK(CmpFunc(params[8],  40,  0.05));  // close to parameters of SrcFunction
	BOOST_CHECK(CmpFunc(params[9], -2.0, 0.05));  // close to parameters of SrcFunction
	BOOST_CHECK(CmpFunc(params[10], 2.0, 0.05));     // close to parameters of SrcFunction
}
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------

BOOST_AUTO_TEST_SUITE_END()


/*
(c) 2014 Glen Joseph Fernandes
<glenjofe -at- gmail.com>

Distributed under the Boost Software
License, Version 1.0.
http://boost.org/LICENSE_1_0.txt
*/
#ifndef BOOST_ALIGN_DETAIL_IS_ALIGNMENT_HPP
#define BOOST_ALIGN_DETAIL_IS_ALIGNMENT_HPP

#include <boost/config.hpp>
#include <cstddef>

namespace mars_boost {}
namespace boost = mars_boost;
namespace mars_boost {
namespace alignment {
namespace detail {

BOOST_CONSTEXPR inline bool is_alignment(std::size_t value)
BOOST_NOEXCEPT {
  return (value > 0) && ((value & (value - 1)) == 0);
}

} /* .detail */
} /* .alignment */
} /* .boost */

#endif


#define BOOST_TEST_MODULE direct_CUDD_Distributed
#include <metaSMT/DirectSolver_Context.hpp>
#include <metaSMT/BitBlast.hpp>
#include <metaSMT/frontend/QF_BV.hpp>
#include <metaSMT/backend/CUDD_Distributed.hpp>
#include <boost/random/uniform_int.hpp>
#include <boost/random/mersenne_twister.hpp>
#include <boost/test/floating_point_comparison.hpp>

using namespace metaSMT::solver;
using namespace metaSMT;

struct Solver_Fixture {
  Solver_Fixture()
  : gen( time(NULL) )
  , rnd ( random_bit(gen) )
  {  }
  //typedef DirectSolver_Context< Group < CUDD_Distributed > > ContextType;
  typedef DirectSolver_Context< BitBlast<CUDD_Distributed > > ContextType;
  //typedef DirectSolver_Context< CUDD_Distributed > ContextType;
  ContextType ctx ;

  struct random_bit {
    random_bit( boost::mt19937 & gen ) : gen(gen), rnd(0, 1) {}

    bool operator() () {
      return rnd(gen);
    }

    boost::mt19937 & gen;
    boost::uniform_int<unsigned short> rnd;
  };

  boost::mt19937 gen;
  boost::function0<bool> rnd;

};

#include "test_solver.cpp"
#include "test_QF_BV.cpp"
// #include "test_Array.cpp"
//#include "test_group.cpp"
//#include "test_unsat.cpp"

static const unsigned repeat = 10000;
BOOST_FIXTURE_TEST_SUITE(dist, Solver_Fixture )

BOOST_AUTO_TEST_CASE( test1 )
{
   predicate x = new_variable();
   predicate y = new_variable();
   predicate z = new_variable();

   assumption( ctx, Ite(x, y, z));
   BOOST_CHECK( solve(ctx) );
}

BOOST_AUTO_TEST_CASE( dist_Or )
{
  predicate x = new_variable();
  predicate y = new_variable();

  assertion( ctx, Or(x, y));

  std::map<unsigned, unsigned> counter;
  for(unsigned i = 0; i < repeat; ++i)
  {
    BOOST_REQUIRE( solve(ctx) );
    unsigned xv = read_value( ctx, x ).randX(rnd);
    unsigned yv = read_value( ctx, y ).randX(rnd);
    counter [xv+2*yv]++;
  }

  for(std::map<unsigned,unsigned>::iterator iter = counter.begin(); iter != counter.end(); ++iter)
  {
    std::cout << iter->first << " " << iter->second << std::endl;
  }
  std::cout << std::endl;
  double expected = repeat/3.0;
  double tolerance = 5;
  BOOST_CHECK_EQUAL( counter[0], 0u);
  BOOST_CHECK_CLOSE((double)counter[1],expected, tolerance);
  BOOST_CHECK_CLOSE((double)counter[2],expected, tolerance);
  BOOST_CHECK_CLOSE((double)counter[3],expected, tolerance);
}

BOOST_AUTO_TEST_CASE( dist_And )
{
  predicate x = new_variable();
  predicate y = new_variable();

  assertion( ctx, And(x, y));

  std::map<unsigned, unsigned> counter;
  for(unsigned i = 0; i < repeat; ++i)
  {
    BOOST_REQUIRE( solve(ctx) );
    unsigned xv = read_value( ctx, x ).randX(rnd);
    unsigned yv = read_value( ctx, y ).randX(rnd);
    counter [xv+2*yv]++;
  }

  for(std::map<unsigned,unsigned>::iterator iter = counter.begin(); iter != counter.end(); ++iter)
  {
    std::cout << iter->first << " " << iter->second << std::endl;
  }
  std::cout << std::endl;
  BOOST_CHECK_EQUAL( counter[0], 0u);
  BOOST_CHECK_EQUAL( counter[1], 0u);
  BOOST_CHECK_EQUAL( counter[2], 0u);
  BOOST_CHECK_EQUAL( counter[3], repeat);
}

BOOST_AUTO_TEST_CASE( dist_Xor)
{
  predicate x = new_variable();
  predicate y = new_variable();

  assertion( ctx, Xor(x, y));

  std::map<unsigned, unsigned> counter;
  for(unsigned i = 0; i < repeat; ++i)
  {
    BOOST_REQUIRE( solve(ctx) );
    unsigned xv = read_value( ctx, x ).randX(rnd);
    unsigned yv = read_value( ctx, y ).randX(rnd);
    counter [xv+2*yv]++;
  }

  for(std::map<unsigned,unsigned>::iterator iter = counter.begin(); iter != counter.end(); ++iter)
  {
    std::cout << iter->first << " " << iter->second << std::endl;
  }
  std::cout << std::endl;
  double expected = repeat/2.0;
  double tolerance = 5;
  BOOST_CHECK_EQUAL( counter[0], 0u);
  BOOST_CHECK_EQUAL( counter[3], 0u);
  BOOST_CHECK_CLOSE((double)counter[1],expected, tolerance);
  BOOST_CHECK_CLOSE((double)counter[2],expected, tolerance);
}

BOOST_AUTO_TEST_CASE( dist_Nor )
{
  predicate x = new_variable();
  predicate y = new_variable();

  assertion( ctx, Nor(x, y));

  std::map<unsigned, unsigned> counter;
  for(unsigned i = 0; i < repeat; ++i)
  {
    BOOST_REQUIRE( solve(ctx) );
    unsigned xv = read_value( ctx, x ).randX(rnd);
    unsigned yv = read_value( ctx, y ).randX(rnd);
    counter [xv+2*yv]++;
  }

  for(std::map<unsigned,unsigned>::iterator iter = counter.begin(); iter != counter.end(); ++iter)
  {
    std::cout << iter->first << " " << iter->second << std::endl;
  }
  std::cout << std::endl;
  BOOST_CHECK_EQUAL(counter[1],0u);
  BOOST_CHECK_EQUAL(counter[2],0u);
  BOOST_CHECK_EQUAL(counter[3],0u);
  BOOST_CHECK_EQUAL(counter[0],repeat);
}

BOOST_AUTO_TEST_CASE( dist_Nand )
{
  predicate x = new_variable();
  predicate y = new_variable();

  assertion( ctx, Nand(x, y));

  std::map<unsigned, unsigned> counter;
  for(unsigned i = 0; i < repeat; ++i)
  {
    BOOST_REQUIRE( solve(ctx) );
    unsigned xv = read_value( ctx, x ).randX(rnd);
    unsigned yv = read_value( ctx, y ).randX(rnd);
    counter [xv+2*yv]++;
    //std::cout << read_value(ctx,x) << " " << read_value(ctx,y) << std::endl;
  }

  for(std::map<unsigned,unsigned>::iterator iter = counter.begin(); iter != counter.end(); ++iter)
  {
    std::cout << iter->first << " " << iter->second << std::endl;
  }
  std::cout << std::endl;
  BOOST_CHECK_NE(counter[0],0u);
  BOOST_CHECK_NE(counter[1],0u);
  BOOST_CHECK_NE(counter[2],0u);
  BOOST_CHECK_EQUAL(counter[3],0u);
}

BOOST_AUTO_TEST_CASE( dist_Xnor )
{
  predicate x = new_variable();
  predicate y = new_variable();

  assertion( ctx, Xnor(x, y));

  std::map<unsigned, unsigned> counter;
  for(unsigned i = 0; i < repeat; ++i)
  {
    BOOST_REQUIRE( solve(ctx) );
    unsigned xv = read_value( ctx, x ).randX(rnd);
    unsigned yv = read_value( ctx, y ).randX(rnd);
    counter [xv+2*yv]++;
  }

  for(std::map<unsigned,unsigned>::iterator iter = counter.begin(); iter != counter.end(); ++iter)
  {
    std::cout << iter->first << " " << iter->second << std::endl;
  }
  std::cout << std::endl;
  double expected = repeat/2.0;
  double tolerance = 5;
  BOOST_CHECK_EQUAL( counter[1], 0u);
  BOOST_CHECK_EQUAL( counter[2], 0u);
  BOOST_CHECK_CLOSE((double)counter[0],expected, tolerance);
  BOOST_CHECK_CLOSE((double)counter[3],expected, tolerance);

}

BOOST_AUTO_TEST_CASE( dist_aLT16 )
{
  using namespace metaSMT::logic::QF_BV;
  bitvector a = new_bitvector(8);

  assertion( ctx, bvult(a, bvuint(16,8)));

  std::map<unsigned, unsigned> counter;
  for(unsigned i = 0; i < repeat; ++i)
  {
    BOOST_REQUIRE( solve(ctx) );
    //std::cout << read_value( ctx, a ) << std::endl;
    unsigned av = read_value( ctx, a ).randX(rnd);
    counter [av]++;
  }

  for(std::map<unsigned,unsigned>::iterator iter = counter.begin(); iter != counter.end(); ++iter)
  {
    std::cout << iter->first << " " << iter->second << std::endl;
  }
  std::cout << std::endl;
  double expected = repeat/16.0;
  double tolerance = 15;
  BOOST_CHECK_CLOSE((double)counter[0],expected, tolerance);
  BOOST_CHECK_CLOSE((double)counter[1],expected, tolerance);
  BOOST_CHECK_CLOSE((double)counter[2],expected, tolerance);
  BOOST_CHECK_CLOSE((double)counter[3],expected, tolerance);
  BOOST_CHECK_CLOSE((double)counter[4],expected, tolerance);
  BOOST_CHECK_CLOSE((double)counter[5],expected, tolerance);
  BOOST_CHECK_CLOSE((double)counter[6],expected, tolerance);
  BOOST_CHECK_CLOSE((double)counter[7],expected, tolerance);
  BOOST_CHECK_CLOSE((double)counter[8],expected, tolerance);
  BOOST_CHECK_CLOSE((double)counter[9],expected, tolerance);
  BOOST_CHECK_CLOSE((double)counter[10],expected,tolerance);
  BOOST_CHECK_CLOSE((double)counter[11],expected,tolerance);
  BOOST_CHECK_CLOSE((double)counter[12],expected,tolerance);
  BOOST_CHECK_CLOSE((double)counter[13],expected,tolerance);
  BOOST_CHECK_CLOSE((double)counter[14],expected,tolerance);
  BOOST_CHECK_CLOSE((double)counter[15],expected,tolerance);
}

BOOST_AUTO_TEST_SUITE_END() //dist

// vim: ft=cpp:ts=2:sw=2:et