#define OFDIS_INTERNAL
#include <Eigen/Core>
#include <Eigen/LU>
#include <Eigen/Dense>
#include "litiv/3rdparty/ofdis/fdf/image.h"
#include "litiv/3rdparty/ofdis/patch.hpp"

template<ofdis::FlowInputType eInput, ofdis::FlowOutputType eOutput>
ofdis::PatClass<eInput,eOutput>::PatClass(const camparam* cpt_in,
                                          const camparam* cpo_in,
                                          const optparam* op_in,
                                          const int patchid_in) :
        cpt(cpt_in),
        cpo(cpo_in),
        op(op_in),
        patchid(patchid_in) {
    pc = new patchstate<eOutput>();
    CreateStatusStruct(pc);
    tmp.resize(op->novals,1);
    dxx_tmp.resize(op->novals,1);
    dyy_tmp.resize(op->novals,1);
}

template<ofdis::FlowInputType eInput, ofdis::FlowOutputType eOutput>
void ofdis::PatClass<eInput,eOutput>::CreateStatusStruct(patchstate<eOutput>* psin) {
    // get reference / template patch
    psin->pdiff.resize(op->novals,1);
    psin->pweight.resize(op->novals,1);
}

template<ofdis::FlowInputType eInput, ofdis::FlowOutputType eOutput>
ofdis::PatClass<eInput,eOutput>::~PatClass() {
    delete pc;
}

template<ofdis::FlowInputType eInput, ofdis::FlowOutputType eOutput>
void ofdis::PatClass<eInput,eOutput>::InitializePatch(Eigen::Map<const Eigen::MatrixXf>* im_ao_in,
                                                      Eigen::Map<const Eigen::MatrixXf>* im_ao_dx_in,
                                                      Eigen::Map<const Eigen::MatrixXf>* im_ao_dy_in,
                                                      const Eigen::Vector2f pt_ref_in) {
    im_ao = im_ao_in;
    im_ao_dx = im_ao_dx_in;
    im_ao_dy = im_ao_dy_in;
    pt_ref = pt_ref_in;
    ResetPatch();
    getPatchStaticNNGrad(im_ao->data(), im_ao_dx->data(), im_ao_dy->data(), &pt_ref, &tmp, &dxx_tmp, &dyy_tmp);
    ComputeHessian();
}

template<ofdis::FlowInputType eInput, ofdis::FlowOutputType eOutput>
void ofdis::PatClass<eInput,eOutput>::ComputeHessian() {
    if(eOutput==ofdis::FlowOutput_OpticalFlow) {
        pc->Hes(0,0) = (dxx_tmp.array()*dxx_tmp.array()).sum();
        pc->Hes(0,1) = (dxx_tmp.array()*dyy_tmp.array()).sum();
        pc->Hes(1,1) = (dyy_tmp.array()*dyy_tmp.array()).sum();
        pc->Hes(1,0) = pc->Hes(0,1);
        if(pc->Hes.determinant()==0) {
            pc->Hes(0,0) += 1e-10;
            pc->Hes(1,1) += 1e-10;
        }
    }
    else {
        pc->Hes(0,0) = (dxx_tmp.array()*dxx_tmp.array()).sum();
        if(pc->Hes.sum()==0)
            pc->Hes(0,0) += 1e-10;
    }
}

template<ofdis::FlowInputType eInput, ofdis::FlowOutputType eOutput>
void ofdis::PatClass<eInput,eOutput>::SetTargetImage(Eigen::Map<const Eigen::MatrixXf>* im_bo_in,
                                                     Eigen::Map<const Eigen::MatrixXf>* im_bo_dx_in,
                                                     Eigen::Map<const Eigen::MatrixXf>* im_bo_dy_in) {
    im_bo = im_bo_in;
    im_bo_dx = im_bo_dx_in;
    im_bo_dy = im_bo_dy_in;
    ResetPatch();
}

template<ofdis::FlowInputType eInput, ofdis::FlowOutputType eOutput>
void ofdis::PatClass<eInput,eOutput>::ResetPatch() {
    pc->hasconverged=0;
    pc->hasoptstarted=0;
    pc->pt_st = pt_ref;
    pc->pt_iter = pt_ref;
    pc->p_in.setZero();
    pc->p_iter.setZero();
    pc->delta_p.setZero();
    pc->delta_p_sqnorm = 1e-10;
    pc->delta_p_sqnorm_init = 1e-10;
    pc->mares = 1e20;
    pc->mares_old = 1e20;
    pc->cnt=0;
    pc->invalid = false;
}

inline void paramtopt(Eigen::Vector2f& pt_iter, const Eigen::Vector2f& pt_ref, const Eigen::Vector2f& p_iter) {
    pt_iter = pt_ref + p_iter; // for optical flow the point displacement and the parameter vector are equivalent
}

inline void paramtopt(Eigen::Vector2f& pt_iter, const Eigen::Vector2f& pt_ref, const Eigen::Matrix<float,1,1>& p_iter) {
    pt_iter[0] = pt_ref[0] + p_iter[0];
}

template<ofdis::FlowInputType eInput, ofdis::FlowOutputType eOutput>
void ofdis::PatClass<eInput,eOutput>::OptimizeStart(const point_type& p_in_arg) {
    pc->p_in   = p_in_arg;
    pc->p_iter = p_in_arg;

    // convert from input parameters to 2D query location(s) for patches
    paramtopt(pc->pt_iter,pt_ref,pc->p_iter);

    // save starting location, only needed for outlier check
    pc->pt_st = pc->pt_iter;

    //Check if initial position is already invalid
    if(pc->pt_iter[0] < cpt->tmp_lb  || pc->pt_iter[1] < cpt->tmp_lb || // check if patch left valid image region
       pc->pt_iter[0] > cpt->tmp_ubw || pc->pt_iter[1] > cpt->tmp_ubh) {
        pc->hasconverged=1;
        pc->pdiff = tmp;
        pc->hasoptstarted=1;
    }
    else {
        pc->cnt=0; // reset iteration counter
        pc->delta_p_sqnorm = 1e-10;
        pc->delta_p_sqnorm_init = 1e-10;  // set to arbitrary low value, s.t. that loop condition is definitely true on first iteration
        pc->mares = 1e5;          // mean absolute residual
        pc->mares_old = 1e20; // for rate of change, keep mares from last iteration in here. Set high so that loop condition is definitely true on first iteration
        pc->hasconverged=0;

        OptimizeComputeErrImg();

        pc->hasoptstarted=1;
        pc->invalid = false;
    }
}

template<ofdis::FlowInputType eInput, ofdis::FlowOutputType eOutput>
void ofdis::PatClass<eInput,eOutput>::OptimizeIter(const point_type& p_in_arg, const bool untilconv) {
    if(!pc->hasoptstarted) {
        ResetPatch();
        OptimizeStart(p_in_arg);
    }
    int oldcnt=pc->cnt;
    // optimize patch until convergence, or do only one iteration if DIS visualization is used
    while(!(pc->hasconverged || (untilconv == false && (pc->cnt > oldcnt)))) {
        pc->cnt++;
        // projection onto sd_images
        if(eOutput==ofdis::FlowOutput_OpticalFlow) {
            pc->delta_p[0] = (dxx_tmp.array()*pc->pdiff.array()).sum();
            pc->delta_p[1] = (dyy_tmp.array()*pc->pdiff.array()).sum();
        }
        else
            pc->delta_p[0] = (dxx_tmp.array()*pc->pdiff.array()).sum();
        pc->delta_p = pc->Hes.llt().solve(pc->delta_p); // solve linear system
        pc->p_iter -= pc->delta_p; // update flow vector
        if(eOutput==ofdis::FlowOutput_StereoDepth) {
            if(cpt->camlr==0)
                pc->p_iter[0] = std::min(pc->p_iter[0],0.0f); // disparity in t can only be negative (in right image)
            else
                pc->p_iter[0] = std::max(pc->p_iter[0],0.0f); // ... positive (in left image)
        }
        // compute patch locations based on new parameter vector
        paramtopt(pc->pt_iter,pt_ref,pc->p_iter);
        // check if patch(es) moved too far from starting location, if yes, stop iteration and reset to starting location
        if((pc->pt_st - pc->pt_iter).norm() > op->outlierthresh || // check if query patch moved more than >padval from starting location -> most likely outlier
            pc->pt_iter[0] < cpt->tmp_lb  || pc->pt_iter[1] < cpt->tmp_lb || // check patch left valid image region
            pc->pt_iter[0] > cpt->tmp_ubw || pc->pt_iter[1] > cpt->tmp_ubh) {
            pc->p_iter = pc->p_in; // reset
            paramtopt(pc->pt_iter,pt_ref,pc->p_iter);
            pc->hasconverged=1;
            pc->hasoptstarted=1;
        }
        OptimizeComputeErrImg();
    }
}

template<ofdis::FlowInputType eInput, ofdis::FlowOutputType eOutput>
void ofdis::PatClass<eInput,eOutput>::LossComputeErrorImage(Eigen::Matrix<float,Eigen::Dynamic,1>* patdest,
                                            Eigen::Matrix<float,Eigen::Dynamic,1>* wdest,
                                            const Eigen::Matrix<float,Eigen::Dynamic,1>* patin,
                                            const Eigen::Matrix<float,Eigen::Dynamic,1>* tmpin) {
    v4sf * pd = (v4sf*) patdest->data(),
         * pa = (v4sf*) patin->data(),
         * te = (v4sf*) tmpin->data(),
         * pw = (v4sf*) wdest->data();

    if(op->costfct==0) { // L2 cost function
        for(int i=op->novals/4; i--; ++pd, ++pa, ++te, ++pw) {
          (*pd) = (*pa)-(*te);  // difference image
          (*pw) = __builtin_ia32_andnps(op->negzero,(*pd));
        }
    }
    else if(op->costfct==1) { // L1 cost function
        for(int i=op->novals/4; i--; ++pd, ++pa, ++te, ++pw) {
            (*pd) = (*pa)-(*te);   // difference image
            (*pd) = __builtin_ia32_orps( __builtin_ia32_andps(op->negzero,  (*pd) )  , __builtin_ia32_sqrtps (__builtin_ia32_andnps(op->negzero,  (*pd) )) );  // sign(pdiff) * sqrt(abs(pdiff))
            (*pw) = __builtin_ia32_andnps(op->negzero,  (*pd) );
        }
    }
    else if(op->costfct==2) { // Pseudo Huber cost function
        for (int i=op->novals/4; i--; ++pd, ++pa, ++te, ++pw) {
            (*pd) = (*pa)-(*te); // difference image
            (*pd) = __builtin_ia32_orps(
                        __builtin_ia32_andps(op->negzero,(*pd)),
                        __builtin_ia32_sqrtps(
                            // PSEUDO HUBER NORM
                            __builtin_ia32_mulps(
                                __builtin_ia32_sqrtps(
                                    op->ones+__builtin_ia32_divps(__builtin_ia32_mulps((*pd),(*pd)),
                                    op->normoutlier_tmpbsq)
                                )-op->ones,
                                op->normoutlier_tmp2bsq
                            )
                        )
                    ); // sign(pdiff) * sqrt( 2*b^2*( sqrt(1+abs(pdiff)^2/b^2)+1)  )) // <- looks like this without SSE instruction
            (*pw) = __builtin_ia32_andnps(op->negzero,(*pd));
        }
    }
}

template<ofdis::FlowInputType eInput, ofdis::FlowOutputType eOutput>
void ofdis::PatClass<eInput,eOutput>::OptimizeComputeErrImg() {
    getPatchStaticBil(im_bo->data(), &(pc->pt_iter), &(pc->pdiff));
    // Get photometric patch error
    LossComputeErrorImage(&pc->pdiff, &pc->pweight, &pc->pdiff, &tmp);
    // Compute step norm
    pc->delta_p_sqnorm = pc->delta_p.squaredNorm();
    if(pc->cnt==1)
        pc->delta_p_sqnorm_init = pc->delta_p_sqnorm;
    // Check early termination criterions
    pc->mares_old = pc->mares;
    pc->mares = pc->pweight.template lpNorm<1>() / (op->novals);
    if( ! ((pc->cnt < op->max_iter) & (pc->mares  > op->res_thresh) &
          ((pc->cnt < op->min_iter) | (pc->delta_p_sqnorm / pc->delta_p_sqnorm_init >= op->dp_thresh)) &
          ((pc->cnt < op->min_iter) | (pc->mares / pc->mares_old <= op->dr_thresh))) )
        pc->hasconverged=1;
}

// Extract patch on integer position, and gradients, No Bilinear interpolation
template<ofdis::FlowInputType eInput, ofdis::FlowOutputType eOutput>
void ofdis::PatClass<eInput,eOutput>::getPatchStaticNNGrad(const float* img,
                                           const float* img_dx,
                                           const float* img_dy,
                                           const Eigen::Vector2f* mid_in,
                                           Eigen::Matrix<float,Eigen::Dynamic,1>* tmp_in_e,
                                           Eigen::Matrix<float,Eigen::Dynamic,1>* tmp_dx_in_e,
                                           Eigen::Matrix<float,Eigen::Dynamic,1>* tmp_dy_in_e) {
    float* tmp_in = tmp_in_e->data();
    float* tmp_dx_in = tmp_dx_in_e->data();
    float* tmp_dy_in = tmp_dy_in_e->data();
    Eigen::Vector2i pos;
    Eigen::Vector2i pos_it;
    pos[0] = round((*mid_in)[0])+cpt->imgpadding;
    pos[1] = round((*mid_in)[1])+cpt->imgpadding;
    int posxx = 0;
    int lb = -op->p_samp_s/2;
    int ub = op->p_samp_s/2-1;
    for(int j = lb; j<=ub; ++j) {
        for(int i = lb; i<=ub; ++i,++posxx) {
            pos_it[0] = pos[0]+i;
            pos_it[1] = pos[1]+j;
            int idx = pos_it[0]+pos_it[1]*cpt->tmp_w;
            if(eInput==ofdis::FlowInput_RGB) {
                idx *= 3;
                tmp_in[posxx] = img[idx];
                tmp_dx_in[posxx] = img_dx[idx];
                tmp_dy_in[posxx] = img_dy[idx];
                ++posxx;
                ++idx;
                tmp_in[posxx] = img[idx];
                tmp_dx_in[posxx] = img_dx[idx];
                tmp_dy_in[posxx] = img_dy[idx];
                ++posxx;
                ++idx;
                tmp_in[posxx] = img[idx];
                tmp_dx_in[posxx] = img_dx[idx];
                tmp_dy_in[posxx] = img_dy[idx];
            }
            else {
                tmp_in[posxx] = img[idx];
                tmp_dx_in[posxx] = img_dx[idx];
                tmp_dy_in[posxx] = img_dy[idx];
            }
        }
    }
    // PATCH NORMALIZATION
    if(op->patnorm>0)
        tmp_in_e->array() -= (tmp_in_e->sum()/op->novals);
}

// Extract patch on float position with bilinear interpolation, no gradients.
template<ofdis::FlowInputType eInput, ofdis::FlowOutputType eOutput>
void ofdis::PatClass<eInput,eOutput>::getPatchStaticBil(const float* img, const Eigen::Vector2f* mid_in, Eigen::Matrix<float,Eigen::Dynamic,1>* tmp_in_e) {
    float* tmp_in = tmp_in_e->data();
    Eigen::Vector2f resid;
    Eigen::Vector4f we; // bilinear weight vector
    Eigen::Vector4i pos;
    Eigen::Vector2i pos_it;
    // Compute the bilinear weight vector, for patch without orientation/scale change -> weight vector is constant for all pixels
    pos[0] = ceil((*mid_in)[0]+.00001f); // ensure rounding up to natural numbers
    pos[1] = ceil((*mid_in)[1]+.00001f);
    pos[2] = floor((*mid_in)[0]);
    pos[3] = floor((*mid_in)[1]);
    resid[0] = (*mid_in)[0]-(float)pos[2];
    resid[1] = (*mid_in)[1]-(float)pos[3];
    we[0] = resid[0]*resid[1];
    we[1] = (1-resid[0])*resid[1];
    we[2] = resid[0]*(1-resid[1]);
    we[3] = (1-resid[0])*(1-resid[1]);
    pos[0] += cpt->imgpadding;
    pos[1] += cpt->imgpadding;
    float* tmp_it = tmp_in;
    const float* img_a,* img_b,* img_c,* img_d,* img_e;
    if(eInput==ofdis::FlowInput_RGB)
        img_e = img+(pos[0]-op->p_samp_s/2)*3;
    else
        img_e = img+pos[0]-op->p_samp_s/2;
    int lb = -op->p_samp_s/2;
    int ub = op->p_samp_s/2-1;
    for(pos_it[1] = pos[1]+lb; pos_it[1]<=pos[1]+ub; ++pos_it[1]) {
        if(eInput==ofdis::FlowInput_RGB) {
            img_a = img_e+pos_it[1]*cpt->tmp_w*3;
            img_c = img_e+(pos_it[1]-1)*cpt->tmp_w*3;
            img_b = img_a-3;
            img_d = img_c-3;
        }
        else {
            img_a = img_e+pos_it[1]*cpt->tmp_w;
            img_c = img_e+(pos_it[1]-1)*cpt->tmp_w;
            img_b = img_a-1;
            img_d = img_c-1;
        }
        for(pos_it[0] = pos[0]+lb; pos_it[0]<=pos[0]+ub; ++pos_it[0],++tmp_it,++img_a,++img_b,++img_c,++img_d) {
            if(eInput==ofdis::FlowInput_RGB) {
                (*tmp_it) = we[0]*(*img_a)+we[1]*(*img_b)+we[2]*(*img_c)+we[3]*(*img_d);
                ++tmp_it;
                ++img_a;
                ++img_b;
                ++img_c;
                ++img_d;
                (*tmp_it) = we[0]*(*img_a)+we[1]*(*img_b)+we[2]*(*img_c)+we[3]*(*img_d);
                ++tmp_it;
                ++img_a;
                ++img_b;
                ++img_c;
                ++img_d;
                (*tmp_it) = we[0]*(*img_a)+we[1]*(*img_b)+we[2]*(*img_c)+we[3]*(*img_d);
            }
            else {
                (*tmp_it) = we[0]*(*img_a)+we[1]*(*img_b)+we[2]*(*img_c)+we[3]*(*img_d);
            }
        }
    }
    // PATCH NORMALIZATION
    if(op->patnorm>0) // Subtract Mean
        tmp_in_e->array() -= (tmp_in_e->sum()/op->novals);
}

template class ofdis::PatClass<ofdis::FlowInput_Grayscale,ofdis::FlowOutput_OpticalFlow>;
template class ofdis::PatClass<ofdis::FlowInput_Gradient,ofdis::FlowOutput_OpticalFlow>;
template class ofdis::PatClass<ofdis::FlowInput_RGB,ofdis::FlowOutput_OpticalFlow>;
template class ofdis::PatClass<ofdis::FlowInput_Grayscale,ofdis::FlowOutput_StereoDepth>;
template class ofdis::PatClass<ofdis::FlowInput_Gradient,ofdis::FlowOutput_StereoDepth>;
template class ofdis::PatClass<ofdis::FlowInput_RGB,ofdis::FlowOutput_StereoDepth>;


//
// Created by Gonzalo Lera Romero.
// Grupo de Optimizacion Combinatoria (GOC).
// Departamento de Computacion - Universidad de Buenos Aires.
//

#include "goc/graph/maxflow_mincut.h"

#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/graph_selectors.hpp>
#include <boost/graph/graph_traits.hpp>
#include <boost/graph/properties.hpp>
#include <boost/graph/boykov_kolmogorov_max_flow.hpp>

using namespace std;

namespace goc
{
namespace
{
typedef boost::adjacency_list<boost::vecS, boost::vecS, boost::directedS, boost::no_property, boost::property<boost::edge_index_t, std::size_t>> BoostDigraph;
typedef boost::graph_traits<BoostDigraph>::vertex_descriptor BoostVertex;
typedef BoostDigraph::edge_descriptor BoostArc;
}

pair<double, STCut> maxflow_mincut(const Digraph& D, const function<double(int i, int j)>& c, int s, int t)
{
	// n = number of vertices.
	int n = D.VertexCount();

	// Build boost network B to work with.
	BoostDigraph B;
	vector<BoostArc> reverse_arcs;
	vector<float> capacities;

	for (int i = 0; i < n; ++i)
	{
		for (int j: D.Successors(i))
		{
			boost::add_edge(i, j, boost::num_edges(B), B);
			capacities.push_back(c(i,j));
		}
	}

	// Add boost reverse arcs.
	vector<BoostArc> reverse_reverses; // reverse arcs of the reverse arcs.
	for (int i = 0; i < n; ++i)
	{
		for (int j: D.Successors(i))
		{
			auto reverse_arc = boost::edge(j,i,B);
			if (!reverse_arc.second) // If the arc was not in the network.
			{
				reverse_arc = boost::add_edge(j, i, boost::num_edges(B), B);
				capacities.push_back(0.0);
				reverse_reverses.push_back(boost::edge(i,j,B).first);
			}
			reverse_arcs.push_back(reverse_arc.first);
		}
	}
	for (auto e: reverse_reverses) reverse_arcs.push_back(e);

	vector<int> color(n);
	vector<float> residual_capacity(num_edges(B), 0);

	auto capacity_map = boost::make_iterator_property_map(&capacities[0], boost::get(boost::edge_index, B));
	auto residual_capacity_map = boost::make_iterator_property_map(&residual_capacity[0], boost::get(boost::edge_index, B));
	auto reverse_arc_map = boost::make_iterator_property_map(&reverse_arcs[0], boost::get(boost::edge_index, B));
	auto color_map = boost::make_iterator_property_map(&color[0], boost::get(boost::vertex_index, B));

	// Solve max-flow with boykov_kolmogorov algorithm.
	BoostVertex source = s;
	BoostVertex sink = t;
	double max_flow = boost::boykov_kolmogorov_max_flow(B, capacity_map, residual_capacity_map, reverse_arc_map,
														color_map, boost::get(boost::vertex_index, B), source, sink);

	// Get min-cut.
	STCut min_cut;
	for (int i = 0; i < n; ++i)
	{
		if (color[i] == boost::black_color) min_cut.S.push_back(i);
		else min_cut.T.push_back(i);
	}

	return {max_flow, min_cut};
}
} // namespace goc.


/**
    test.cpp
    Desc: tests for mvee class
    @author Chris Larson, Cornell University
    @date (2017)
    @version 1.0
**/

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <fstream>
#include <string>
#include <vector>
#include <exception>
#include <Eigen/Dense>
#include "boost/chrono.hpp"
#include "boost/filesystem.hpp"
#include "mvee/mvee.hpp"
#include "mvee/utils.hpp"

#define EPS 1e-4
#define LMC 1e-8

using namespace std;
using namespace Eigen;
using namespace mvee;

/*
	Ghost shell script that calls plot.py
	@param dfpath: path/to/fname.csv
	@param e: [a, b, x0, y0, phi]
*/
void ghostShellCall(string dfpath, string desc, vector<double> e)
{
	boost::filesystem::path BASEPATH(boost::filesystem::current_path());

    // string script_name = to_string(BASEPATH) + "/ghost.sh";
    string script_name = BASEPATH.string() + "/ghost.sh";
    ofstream gen_result(script_name);
    gen_result << "python3 test.py" << " " << desc << " " << dfpath << " "
    		   << to_string(e[0]) << "," << to_string(e[1]) << ","
    		   << to_string(e[2]) << "," << to_string(e[3]) << ","
    		   << to_string(e[4]) << "," << to_string(e[5]);
    gen_result.close();
    string shell_cmd = "sh " + script_name;
    system(shell_cmd.c_str());
    remove(shell_cmd.c_str());
    shell_cmd = "rm -f " + script_name;
	system(shell_cmd.c_str());
}

vector<double> getEllipse(Mvee& B)
{
	vector<double> ellipse {
		B.radii()[0],
		B.radii()[1],
		B.centroid()[0],
		B.centroid()[1],
		acos(B.pose()[0][0]),
		acos(B.pose()[0][0])
	};
	return ellipse;
}

int test_irregular()
{
	Mvee B;
	string dfbase = "data/irregular";
	for (int i=0; i<10; i++)
	{
		string dfpath = dfbase + "_" + to_string(i) + ".csv";
    	B.compute(dfpath, ',', EPS, LMC);
		vector<double> ellipse = getEllipse(B);
    	ghostShellCall(dfpath, "test_irregular_" + to_string(i), ellipse);
	}
    return 0;
}

int test_multimodal()
{
	Mvee B;
	string dfpath = "data/multimodal.csv";
	B.compute(dfpath, ',', EPS, LMC);
	vector<double> ellipse = getEllipse(B);
	ghostShellCall(dfpath, "test_multimodal", ellipse);
    return 0;
}

int test_multivariate()
{
	Mvee B;
	string dfpath = "data/multivariate.csv";
	MatrixXd X = readCSV(dfpath, ',');
	B.compute(X, 1e-8, LMC);
    vector<double> radii = B.radii();
	vector<double> centroid = B.centroid();
	vector<vector<double>> pose = B.pose();
	double t = B.time;
	long int iters = B.iters;
	string fname = boost::filesystem::current_path().string() + "/results/multivariate.csv";
	ofstream result(fname);
	result << "data_rows" << "," << X.rows() << "\n" <<
			  "data_cols" << "," << X.cols() << "\n" <<
			  "compute_time" << "," << t << "\n" <<
			  "compute_iters" << "," << iters;
	result.close();
    return 0;
}

int test_outliers()
{
	Mvee B;
	string dfpath = "data/outlier.csv";
	MatrixXd X = readCSV(dfpath, ',');
	vector<vector<double>> X_STD = toStdMat(X, [](double z) { return z; });
	B.compute(X_STD, EPS, LMC);
	vector<double> ellipse = getEllipse(B);
	ghostShellCall(dfpath, "test_outlier", ellipse);
    return 0;
}

int main(int argc, char **argv)
{
	boost::filesystem::path BASEPATH(boost::filesystem::current_path());

    /******* TEST DATA *******/
	string script_name = BASEPATH.string() + "/ghost.sh";
	ofstream gen_data(script_name);
	gen_data << "python3 data.py";
    gen_data.close();
    string shell_cmd = "sh " + script_name;
    system(shell_cmd.c_str());
    remove(shell_cmd.c_str());
    shell_cmd = "rm -f " + script_name;
	system(shell_cmd.c_str());

    /******* UNIT TESTS *******/
	int status;

	// Irregular data
	status = test_irregular();
	if (status == 0)
	{
		cout << "test_irregular passed!" << endl;
	}
	else
	{
		throw runtime_error("test_irregular failed!");
	}

	// Multimodal data
	status = test_multimodal();
	if (status == 0)
	{
		cout << "test_multimodal passed!" << endl;
	}
	else
	{
		throw runtime_error("test_multimodal failed!");
	}

	// Multivariate data
	status = test_multivariate();
	if (status == 0)
	{
		cout << "test_multivariate passed!" << endl;
	}
	else
	{
		throw runtime_error("test_multivariate failed!");
	}

	// Outlier data
	status = test_outliers();
	if (status == 0)
	{
		cout << "test_outliers passed!" << endl;
	}
	else
	{
		throw runtime_error("test_outliers failed!");
	}

	return 0;
}


// -----------------------------------------------------------------------------
// Fern © Geoneric
//
// This file is part of Geoneric Fern which is available under the terms of
// the GNU General Public License (GPL), version 2. If you do not want to
// be bound by the terms of the GPL, you may purchase a proprietary license
// from Geoneric (http://www.geoneric.eu/contact).
// -----------------------------------------------------------------------------
#define BOOST_TEST_MODULE fern expression_tree stream_visitor
#include <sstream>
#include <boost/test/unit_test.hpp>
#include "fern/expression_tree/plus.h"
#include "fern/expression_tree/slope.h"
#include "fern/expression_tree/times.h"
#include "fern/expression_tree/stream_visitor.h"

BOOST_AUTO_TEST_CASE(stream_visitor)
{
    namespace fet = fern::expression_tree;

    // Never visit an empty tree! Because of the never empty guarantee of
    // boost::variant, the root expression instance always has a value (the
    // first value in the variant).
    // {
    //     fet::Expression expression;
    //     std::stringstream stream;
    //     boost::apply_visitor(fet::StreamVisitor(stream), expression);
    //     BOOST_CHECK_EQUAL(stream.str(), "");
    // }

    // 4
    {
        fet::Constant<int32_t> constant(4);
        auto expression(constant);

        std::stringstream string_stream;
        fet::stream(expression, string_stream);
        BOOST_CHECK_EQUAL(string_stream.str(),
            "(int32_t(4))");
    }

    // 4 + 5
    {
        fet::Constant<int32_t> expression1(4);
        using Result1 = decltype(expression1)::result_type;

        fet::Constant<int64_t> expression2(5);
        using Result2 = decltype(expression2)::result_type;

        using Result3 = typename fet::Plus<Result1, Result2>::result_type;

        fet::Operation<Result3> expression3(
            "plus",
            fet::Implementation(
                fet::Plus<Result1, Result2>()),
            {
                expression1,
                expression2
            }
        );

        std::stringstream string_stream;
        fet::stream(expression3, string_stream);
        BOOST_CHECK_EQUAL(string_stream.str(),
            "(plus(int32_t(4), int64_t(5)))");
    }

    // slope((4 + 5) * 3.4)
    {
        // 4
        fet::Constant<int32_t> expression1(4);
        using Result1 = decltype(expression1)::result_type;

        // 5
        fet::Constant<int64_t> expression2(5);
        using Result2 = decltype(expression2)::result_type;

        // 4 + 5
        // TODO To allow for the compiler to perform compile time checks,
        //      type information must be available at compile time. This
        //      means, that all expression types must be able to present their
        //      result type. Operations must be able to tell what they will
        //      output.
        using Result3 = typename fet::Plus<Result1, Result2>::result_type;
        fet::Operation<Result3> expression3(
            "plus",
            fet::Implementation(
                fet::Plus<Result1, Result2>()),
            {
                expression1,
                expression2
            }
        );

        // 3.4
        fet::Constant<double> expression4(3.4);
        using Result4 = decltype(expression4)::result_type;
        using Result5 = typename fet::Times<Result3, Result4>::result_type;

        // (4 + 5) * 3.4
        fet::Operation<Result5> expression5(
            "times",
            fet::Implementation(
                fet::Times<Result3, Result4>()),
            {
                expression3,
                expression4
            }
        );

        // slope((4 + 5) * 3.4)
        using Result6 = typename fet::Slope<Result5>::result_type;
        fet::Operation<Result6> expression6(
            "slope",
            fet::Implementation(
                fet::Slope<Result5>()),
            {
                expression5
            }
        );

        std::stringstream string_stream;
        fet::stream(expression6, string_stream);
        BOOST_CHECK_EQUAL(string_stream.str(),
            "(slope(times(plus(int32_t(4), int64_t(5)), double(3.4))))");
    }
}


#include <map>
#include <string>
#include <iostream>
#include <iomanip>
#include <cmath>
#include <numeric>
#include <chrono>
#include <random>

#include <EigenRand/EigenRand>

class BenchmarkHelper
{
	std::map<std::string, double>& timing;
	std::map<std::string, std::pair<double, double> >& mean_var;
public:

	template<typename _EigenTy>
	class ScopeMeasure
	{
		BenchmarkHelper& bh;
		std::string name;
		_EigenTy& results;
		std::chrono::high_resolution_clock::time_point start;
	public:
		ScopeMeasure(BenchmarkHelper& _bh,
			const std::string& _name,
			_EigenTy& _results) :
			bh{ _bh }, name{ _name }, results{ _results },
			start{ std::chrono::high_resolution_clock::now() }
		{
		}

		~ScopeMeasure()
		{
			if (!name.empty())
			{
				bh.timing[name] = std::chrono::duration<double>(std::chrono::high_resolution_clock::now() - start).count();

				double mean = results.template cast<double>().sum() / results.size();
				double sqmean = results.template cast<double>().array().square().sum() / results.size();
				bh.mean_var[name] = std::make_pair(mean, sqmean - mean * mean);
			}
		}
	};

	BenchmarkHelper(std::map<std::string, double>& _timing,
		std::map<std::string, std::pair<double, double>>& _mean_var)
		: timing{ _timing }, mean_var{ _mean_var }
	{
	}

	template<typename _EigenTy>
	ScopeMeasure<_EigenTy> measure(const std::string& name, _EigenTy& results)
	{
		return ScopeMeasure<_EigenTy>(*this, name, results);
	}
};

template<typename Rng>
std::map<std::string, double> test_eigenrand(size_t size, const std::string& suffix, std::map<std::string, std::pair<double, double> >& results)
{
	std::map<std::string, double> ret;

	BenchmarkHelper bh{ ret, results };

	Rng urng;

	Eigen::ArrayXXi xi{ size, size };
	Eigen::ArrayXXf x{ size, size };
	Eigen::ArrayXXd xd{ size, size };

	{
		auto scope = bh.measure("randBits/int" + suffix, xi);
		xi = Eigen::Rand::randBitsLike(xi, urng);
	}

	{
		auto scope = bh.measure("randBits/int/gen" + suffix, xi);
		Eigen::Rand::RandbitsGen<int32_t> gen;
		xi = gen.generateLike(xi, urng);
	}

	{
		auto scope = bh.measure("uniformInt/int(0~6)" + suffix, xi);
		xi = Eigen::Rand::uniformIntLike(xi, urng, 0, 6);
	}

	{
		auto scope = bh.measure("uniformInt/int(0~6)/gen" + suffix, xi);
		Eigen::Rand::UniformIntGen<int32_t> gen{ 0, 6 };
		xi = gen.generateLike(xi, urng);
	}

	{
		auto scope = bh.measure("uniformInt/int(0~63)" + suffix, xi);
		xi = Eigen::Rand::uniformIntLike(xi, urng, 0, 63);
	}

	{
		auto scope = bh.measure("uniformInt/int(5~79)" + suffix, xi);
		xi = Eigen::Rand::uniformIntLike(xi, urng, 5, 79);
	}

	{
		auto scope = bh.measure("uniformInt/int(0~100k)" + suffix, xi);
		xi = Eigen::Rand::uniformIntLike(xi, urng, 0, 100000);
	}

	{
		auto scope = bh.measure("discreteF/int(s=5)" + suffix, xi);
		xi = Eigen::Rand::discreteFLike(xi, urng, { 1, 2, 3, 4, 5 });
	}

	{
		auto scope = bh.measure("discreteF/int(s=5)/gen" + suffix, xi);
		Eigen::Rand::DiscreteGen<int32_t> gen{ 1, 2, 3, 4, 5 };
		xi = gen.generateLike(xi, urng);
	}

	{
		auto scope = bh.measure("discreteF/int(s=8)" + suffix, xi);
		xi = Eigen::Rand::discreteFLike(xi, urng, { 8, 7, 6, 5, 4, 3, 2, 1 });
	}

	{
		std::vector<double> ws(50);
		std::iota(ws.begin(), ws.end(), 1);
		std::shuffle(ws.begin(), ws.end(), std::mt19937_64{});
		auto scope = bh.measure("discreteF/int(s=50)" + suffix, xi);
		xi = Eigen::Rand::discreteFLike(xi, urng, ws.begin(), ws.end());
	}

	{
		std::vector<double> ws(250);
		std::iota(ws.begin(), ws.end(), 1);
		std::shuffle(ws.begin(), ws.end(), std::mt19937_64{});
		auto scope = bh.measure("discreteF/int(s=250)" + suffix, xi);
		xi = Eigen::Rand::discreteFLike(xi, urng, ws.begin(), ws.end());
	}

	{
		auto scope = bh.measure("discreteD/int(s=5)" + suffix, xi);
		xi = Eigen::Rand::discreteDLike(xi, urng, { 1, 2, 3, 4, 5 });
	}

	{
		auto scope = bh.measure("discreteD/int(s=8)" + suffix, xi);
		xi = Eigen::Rand::discreteDLike(xi, urng, { 8, 7, 6, 5, 4, 3, 2, 1 });
	}

	{
		std::vector<double> ws(50);
		std::iota(ws.begin(), ws.end(), 1);
		std::shuffle(ws.begin(), ws.end(), std::mt19937_64{});
		auto scope = bh.measure("discreteD/int(s=50)" + suffix, xi);
		xi = Eigen::Rand::discreteDLike(xi, urng, ws.begin(), ws.end());
	}

	{
		std::vector<double> ws(250);
		std::iota(ws.begin(), ws.end(), 1);
		std::shuffle(ws.begin(), ws.end(), std::mt19937_64{});
		auto scope = bh.measure("discreteD/int(s=250)" + suffix, xi);
		xi = Eigen::Rand::discreteDLike(xi, urng, ws.begin(), ws.end());
	}

	{
		auto scope = bh.measure("discrete/int(s=5)" + suffix, xi);
		xi = Eigen::Rand::discreteLike(xi, urng, { 1, 2, 3, 4, 5 });
	}

	{
		auto scope = bh.measure("discrete/int(s=8)" + suffix, xi);
		xi = Eigen::Rand::discreteLike(xi, urng, { 8, 7, 6, 5, 4, 3, 2, 1 });
	}

	{
		std::vector<double> ws(50);
		std::iota(ws.begin(), ws.end(), 1);
		std::shuffle(ws.begin(), ws.end(), std::mt19937_64{});
		auto scope = bh.measure("discrete/int(s=50)" + suffix, xi);
		xi = Eigen::Rand::discreteLike(xi, urng, ws.begin(), ws.end());
	}

	{
		std::vector<double> ws(250);
		std::iota(ws.begin(), ws.end(), 1);
		std::shuffle(ws.begin(), ws.end(), std::mt19937_64{});
		auto scope = bh.measure("discrete/int(s=250)" + suffix, xi);
		xi = Eigen::Rand::discreteLike(xi, urng, ws.begin(), ws.end());
	}

	{
		auto scope = bh.measure("balanced" + suffix, x);
		x = Eigen::Rand::balancedLike(x, urng);
	}

	{
		auto scope = bh.measure("balanced/double" + suffix, xd);
		xd = Eigen::Rand::balancedLike(xd, urng);
	}

	{
		auto scope = bh.measure("balanced square" + suffix, x);
		x = Eigen::Rand::balancedLike(x, urng).square();
	}

	{
		auto scope = bh.measure("uniformReal" + suffix, x);
		x = Eigen::Rand::uniformRealLike(x, urng);
	}

	{
		auto scope = bh.measure("uniformReal/double" + suffix, xd);
		xd = Eigen::Rand::uniformRealLike(xd, urng);
	}

	{
		auto scope = bh.measure("uniformReal sqrt" + suffix, x);
		x = Eigen::Rand::uniformRealLike(x, urng).sqrt();
	}

	{
		auto scope = bh.measure("normal(0,1)" + suffix, x);
		x = Eigen::Rand::normalLike(x, urng);
	}

	{
		auto scope = bh.measure("normal(0,1) square" + suffix, x);
		x = Eigen::Rand::normalLike(x, urng).square();
	}

	{
		auto scope = bh.measure("normal(2,3)" + suffix, x);
		x = Eigen::Rand::normalLike(x, urng, 2, 3);
	}

	{
		auto scope = bh.measure("lognormal(0,1)" + suffix, x);
		x = Eigen::Rand::lognormalLike(x, urng);
	}

	{
		auto scope = bh.measure("exponential(1)" + suffix, x);
		x = Eigen::Rand::exponentialLike(x, urng);
	}

	{
		auto scope = bh.measure("gamma(1,2)" + suffix, x);
		x = Eigen::Rand::gammaLike(x, urng, 1, 2);
	}

	{
		auto scope = bh.measure("gamma(5,3)" + suffix, x);
		x = Eigen::Rand::gammaLike(x, urng, 5, 3);
	}

	{
		auto scope = bh.measure("gamma(5,3)/gen" + suffix, x);
		Eigen::Rand::GammaGen<float> gen{ 5, 3 };
		x = gen.generateLike(x, urng);
	}

	/*{
		auto scope = bh.measure("gamma(5,3)/double" + suffix, xd);
		xd = Eigen::Rand::gammaLike(xd, urng, 5, 3);
	}*/

	{
		auto scope = bh.measure("gamma(0.2,1)" + suffix, x);
		x = Eigen::Rand::gammaLike(x, urng, 0.2, 1);
	}

	{
		auto scope = bh.measure("gamma(10.5,1)" + suffix, x);
		x = Eigen::Rand::gammaLike(x, urng, 10.5, 1);
	}

	{
		auto scope = bh.measure("weibull(2,3)" + suffix, x);
		x = Eigen::Rand::weibullLike(x, urng, 2, 3);
	}

	{
		auto scope = bh.measure("extremeValue(0,1)" + suffix, x);
		x = Eigen::Rand::extremeValueLike(x, urng, 0, 1);
	}

	{
		auto scope = bh.measure("chiSquared(15)" + suffix, x);
		x = Eigen::Rand::chiSquaredLike(x, urng, 15);
	}

	{
		auto scope = bh.measure("cauchy" + suffix, x);
		x = Eigen::Rand::cauchyLike(x, urng);
	}

	{
		auto scope = bh.measure("studentT(1)" + suffix, x);
		x = Eigen::Rand::studentTLike(x, urng, 1);
	}

	{
		auto scope = bh.measure("studentT(5)" + suffix, x);
		x = Eigen::Rand::studentTLike(x, urng, 5);
	}

	{
		auto scope = bh.measure("studentT(20)" + suffix, x);
		x = Eigen::Rand::studentTLike(x, urng, 20);
	}

	{
		auto scope = bh.measure("fisherF(1,1)" + suffix, x);
		x = Eigen::Rand::fisherFLike(x, urng, 1, 1);
	}

	{
		auto scope = bh.measure("fisherF(5,1)" + suffix, x);
		x = Eigen::Rand::fisherFLike(x, urng, 5, 1);
	}

	{
		auto scope = bh.measure("fisherF(1,5)" + suffix, x);
		x = Eigen::Rand::fisherFLike(x, urng, 1, 5);
	}

	{
		auto scope = bh.measure("fisherF(5,5)" + suffix, x);
		x = Eigen::Rand::fisherFLike(x, urng, 5, 5);
	}

	{
		auto scope = bh.measure("poisson(1)" + suffix, xi);
		xi = Eigen::Rand::poissonLike(xi, urng, 1);
	}

	{
		auto scope = bh.measure("poisson(8)" + suffix, xi);
		xi = Eigen::Rand::poissonLike(xi, urng, 8);
	}

	{
		auto scope = bh.measure("poisson(16)" + suffix, xi);
		xi = Eigen::Rand::poissonLike(xi, urng, 16);
	}

	{
		auto scope = bh.measure("binomial(20,0.5)" + suffix, xi);
		xi = Eigen::Rand::binomialLike(xi, urng, 20, 0.5);
	}

	{
		auto scope = bh.measure("binomial(50,0.01)" + suffix, xi);
		xi = Eigen::Rand::binomialLike(xi, urng, 50, 0.01);
	}

	{
		auto scope = bh.measure("binomial(100,0.75)" + suffix, xi);
		xi = Eigen::Rand::binomialLike(xi, urng, 100, 0.75);
	}

	{
		auto scope = bh.measure("geometric(0.5)" + suffix, xi);
		xi = Eigen::Rand::geometricLike(xi, urng, 0.5);
	}

	{
		auto scope = bh.measure("negativeBinomial(10,0.5)" + suffix, xi);
		xi = Eigen::Rand::negativeBinomialLike(xi, urng, 10, 0.5);
	}

	{
		auto scope = bh.measure("negativeBinomial(20,0.25)" + suffix, xi);
		xi = Eigen::Rand::negativeBinomialLike(xi, urng, 20, 0.25);
	}

	{
		auto scope = bh.measure("negativeBinomial(30,0.75)" + suffix, xi);
		xi = Eigen::Rand::negativeBinomialLike(xi, urng, 30, 0.75);
	}
	return ret;
}

std::map<std::string, double> test_nullary(size_t size, const std::string& suffix, std::map<std::string, std::pair<double, double> >& results)
{
	std::map<std::string, double> ret;

	BenchmarkHelper bh{ ret, results };

	std::mt19937_64 urng;

	Eigen::ArrayXXi xi{ size, size };
	Eigen::ArrayXXf x{ size, size };
	Eigen::ArrayXXd xd{ size, size };

	{
		auto scope = bh.measure("randBits/int" + suffix, xi);
		xi = Eigen::ArrayXXi::NullaryExpr(size, size, [&]() { return urng(); });
	}

	{
		auto scope = bh.measure("uniformInt/int(0~6)" + suffix, xi);
		std::uniform_int_distribution<> dist{ 0, 6 };
		xi = Eigen::ArrayXXi::NullaryExpr(size, size, [&]() { return dist(urng); });
	}

	{
		auto scope = bh.measure("uniformInt/int(0~63)" + suffix, xi);
		std::uniform_int_distribution<> dist{ 0, 63 };
		xi = Eigen::ArrayXXi::NullaryExpr(size, size, [&]() { return dist(urng); });
	}

	{
		auto scope = bh.measure("uniformInt/int(5~79)" + suffix, xi);
		std::uniform_int_distribution<> dist{ 5, 79 };
		xi = Eigen::ArrayXXi::NullaryExpr(size, size, [&]() { return dist(urng); });
	}

	{
		auto scope = bh.measure("uniformInt/int(0~100k)" + suffix, xi);
		std::uniform_int_distribution<> dist{ 0, 100000 };
		xi = Eigen::ArrayXXi::NullaryExpr(size, size, [&]() { return dist(urng); });
	}

	{
		auto scope = bh.measure("discreteD/int(s=5)" + suffix, xi);
		std::discrete_distribution<> dist{ {1, 2, 3, 4, 5} };
		xi = Eigen::ArrayXXi::NullaryExpr(size, size, [&]() { return dist(urng); });
	}

	{
		auto scope = bh.measure("discreteD/int(s=8)" + suffix, xi);
		std::discrete_distribution<> dist{ {8, 7, 6, 5, 4, 3, 2, 1} };
		xi = Eigen::ArrayXXi::NullaryExpr(size, size, [&]() { return dist(urng); });
	}

	{
		std::vector<double> ws(50);
		std::iota(ws.begin(), ws.end(), 1);
		std::shuffle(ws.begin(), ws.end(), std::mt19937_64{});
		auto scope = bh.measure("discreteD/int(s=50)" + suffix, xi);
		std::discrete_distribution<> dist{ ws.begin(), ws.end() };
		xi = Eigen::ArrayXXi::NullaryExpr(size, size, [&]() { return dist(urng); });
	}

	{
		std::vector<double> ws(250);
		std::iota(ws.begin(), ws.end(), 1);
		std::shuffle(ws.begin(), ws.end(), std::mt19937_64{});
		auto scope = bh.measure("discreteD/int(s=250)" + suffix, xi);
		std::discrete_distribution<> dist{ ws.begin(), ws.end() };
		xi = Eigen::ArrayXXi::NullaryExpr(size, size, [&]() { return dist(urng); });
	}

	{
		auto scope = bh.measure("uniformReal" + suffix, x);
		x = Eigen::ArrayXXf::NullaryExpr(size, size, [&]() { return std::generate_canonical<float, 32>(urng); });
	}

	{
		auto scope = bh.measure("uniformReal sqrt" + suffix, x);
		x = Eigen::ArrayXXf::NullaryExpr(size, size, [&]() { return std::generate_canonical<float, 32>(urng); }).sqrt();
	}

	{
		auto scope = bh.measure("normal(0,1)" + suffix, x);
		std::normal_distribution<float> dist;
		x = Eigen::ArrayXXf::NullaryExpr(size, size, [&]() { return dist(urng); });
	}

	{
		auto scope = bh.measure("normal(0,1) square" + suffix, x);
		std::normal_distribution<float> dist;
		x = Eigen::ArrayXXf::NullaryExpr(size, size, [&]() { return dist(urng); }).square();
	}

	{
		auto scope = bh.measure("normal(2,3)" + suffix, x);
		std::normal_distribution<float> dist{ 2, 3 };
		x = Eigen::ArrayXXf::NullaryExpr(size, size, [&]() { return dist(urng); });
	}

	{
		auto scope = bh.measure("lognormal(0,1)" + suffix, x);
		std::lognormal_distribution<float> dist;
		x = Eigen::ArrayXXf::NullaryExpr(size, size, [&]() { return dist(urng); });
	}

	{
		auto scope = bh.measure("exponential(1)" + suffix, x);
		std::exponential_distribution<float> dist;
		x = Eigen::ArrayXXf::NullaryExpr(size, size, [&]() { return dist(urng); });
	}

	{
		auto scope = bh.measure("gamma(1,2)" + suffix, x);
		std::gamma_distribution<float> dist{ 1, 2 };
		x = Eigen::ArrayXXf::NullaryExpr(size, size, [&]() { return dist(urng); });
	}

	{
		auto scope = bh.measure("gamma(0.2,1)" + suffix, x);
		std::gamma_distribution<float> dist{ 0.2, 1 };
		x = Eigen::ArrayXXf::NullaryExpr(size, size, [&]() { return dist(urng); });
	}

	{
		auto scope = bh.measure("gamma(5,3)" + suffix, x);
		std::gamma_distribution<float> dist{ 5, 3 };
		x = Eigen::ArrayXXf::NullaryExpr(size, size, [&]() { return dist(urng); });
	}

	{
		auto scope = bh.measure("gamma(5,3)/double" + suffix, xd);
		std::gamma_distribution<double> dist{ 5, 3 };
		xd = Eigen::ArrayXXd::NullaryExpr(size, size, [&]() { return dist(urng); });
	}

	{
		auto scope = bh.measure("gamma(10.5,1)" + suffix, x);
		std::gamma_distribution<float> dist{ 10.5, 1 };
		x = Eigen::ArrayXXf::NullaryExpr(size, size, [&]() { return dist(urng); });
	}

	{
		auto scope = bh.measure("weibull(2,3)" + suffix, x);
		std::weibull_distribution<float> dist{ 2, 3 };
		x = Eigen::ArrayXXf::NullaryExpr(size, size, [&]() { return dist(urng); });
	}

	{
		auto scope = bh.measure("extremeValue(0,1)" + suffix, x);
		std::extreme_value_distribution<float> dist{ 0, 1 };
		x = Eigen::ArrayXXf::NullaryExpr(size, size, [&]() { return dist(urng); });
	}

	{
		auto scope = bh.measure("chiSquared(15)" + suffix, x);
		std::chi_squared_distribution<float> dist{ 15 };
		x = Eigen::ArrayXXf::NullaryExpr(size, size, [&]() { return dist(urng); });
	}

	{
		auto scope = bh.measure("cauchy" + suffix, x);
		std::cauchy_distribution<float> dist;
		x = Eigen::ArrayXXf::NullaryExpr(size, size, [&]() { return dist(urng); });
	}

	{
		auto scope = bh.measure("studentT(1)" + suffix, x);
		std::student_t_distribution<float> dist{ 1 };
		x = Eigen::ArrayXXf::NullaryExpr(size, size, [&]() { return dist(urng); });
	}

	{
		auto scope = bh.measure("studentT(5)" + suffix, x);
		std::student_t_distribution<float> dist{ 5 };
		x = Eigen::ArrayXXf::NullaryExpr(size, size, [&]() { return dist(urng); });
	}

	{
		auto scope = bh.measure("studentT(20)" + suffix, x);
		std::student_t_distribution<float> dist{ 20 };
		x = Eigen::ArrayXXf::NullaryExpr(size, size, [&]() { return dist(urng); });
	}

	{
		auto scope = bh.measure("fisherF(1,1)" + suffix, x);
		std::fisher_f_distribution<float> dist{ 1, 1 };
		x = Eigen::ArrayXXf::NullaryExpr(size, size, [&]() { return dist(urng); });
	}

	{
		auto scope = bh.measure("fisherF(5,1)" + suffix, x);
		std::fisher_f_distribution<float> dist{ 5, 1 };
		x = Eigen::ArrayXXf::NullaryExpr(size, size, [&]() { return dist(urng); });
	}

	{
		auto scope = bh.measure("fisherF(1,5)" + suffix, x);
		std::fisher_f_distribution<float> dist{ 1, 5 };
		x = Eigen::ArrayXXf::NullaryExpr(size, size, [&]() { return dist(urng); });
	}

	{
		auto scope = bh.measure("fisherF(5,5)" + suffix, x);
		std::fisher_f_distribution<float> dist{ 5, 5 };
		x = Eigen::ArrayXXf::NullaryExpr(size, size, [&]() { return dist(urng); });
	}

	{
		auto scope = bh.measure("poisson(1)" + suffix, xi);
		std::poisson_distribution<> dist{ 1 };
		xi = Eigen::ArrayXXi::NullaryExpr(size, size, [&]() { return dist(urng); });
	}

	{
		auto scope = bh.measure("poisson(8)" + suffix, xi);
		std::poisson_distribution<> dist{ 8 };
		xi = Eigen::ArrayXXi::NullaryExpr(size, size, [&]() { return dist(urng); });
	}

	{
		auto scope = bh.measure("poisson(16)" + suffix, xi);
		std::poisson_distribution<> dist{ 16 };
		xi = Eigen::ArrayXXi::NullaryExpr(size, size, [&]() { return dist(urng); });
	}

	{
		auto scope = bh.measure("binomial(20,0.5)" + suffix, xi);
		std::binomial_distribution<> dist{ 20, 0.5 };
		xi = Eigen::ArrayXXi::NullaryExpr(size, size, [&]() { return dist(urng); });
	}

	{
		auto scope = bh.measure("binomial(50,0.01)" + suffix, xi);
		std::binomial_distribution<> dist{ 50, 0.01 };
		xi = Eigen::ArrayXXi::NullaryExpr(size, size, [&]() { return dist(urng); });
	}

	{
		auto scope = bh.measure("binomial(100,0.75)" + suffix, xi);
		std::binomial_distribution<> dist{ 100, 0.75 };
		xi = Eigen::ArrayXXi::NullaryExpr(size, size, [&]() { return dist(urng); });
	}

	{
		auto scope = bh.measure("geometric(0.5)" + suffix, xi);
		std::geometric_distribution<> dist{ 0.5 };
		xi = Eigen::ArrayXXi::NullaryExpr(size, size, [&]() { return dist(urng); });
	}

	{
		auto scope = bh.measure("negativeBinomial(10,0.5)" + suffix, xi);
		std::negative_binomial_distribution<> dist{ 10, 0.5 };
		xi = Eigen::ArrayXXi::NullaryExpr(size, size, [&]() { return dist(urng); });
	}

	{
		auto scope = bh.measure("negativeBinomial(20,0.25)" + suffix, xi);
		std::negative_binomial_distribution<> dist{ 20, 0.25 };
		xi = Eigen::ArrayXXi::NullaryExpr(size, size, [&]() { return dist(urng); });
	}

	{
		auto scope = bh.measure("negativeBinomial(30,0.75)" + suffix, xi);
		std::negative_binomial_distribution<> dist{ 30, 0.75 };
		xi = Eigen::ArrayXXi::NullaryExpr(size, size, [&]() { return dist(urng); });
	}
	return ret;
}

std::map<std::string, double> test_old(size_t size, const std::string& suffix, std::map<std::string, std::pair<double, double> >& results)
{
	std::map<std::string, double> ret;

	BenchmarkHelper bh{ ret, results };

	Eigen::ArrayXXf x{ size, size };
	Eigen::ArrayXXd xd{ size, size };

	{
		auto scope = bh.measure("balanced" + suffix, x);
		x = Eigen::ArrayXXf::Random(size, size);
	}

	{
		auto scope = bh.measure("balanced/double" + suffix, xd);
		xd = Eigen::ArrayXXd::Random(size, size);
	}

	{
		auto scope = bh.measure("balanced square" + suffix, x);
		x = Eigen::ArrayXXf::Random(size, size).square();
	}
	return ret;
}

template<typename Rng>
std::map<std::string, double> test_rng(Rng&& rng, size_t size, const std::string& suffix, std::map<std::string, std::pair<double, double> >& results)
{
	std::map<std::string, double> ret;
	BenchmarkHelper bh{ ret, results };

	Eigen::Array<uint64_t, -1, -1> x{ size, size };

	{
		auto scope = bh.measure(suffix, x);
		for (size_t i = 0; i < size * size; ++i)
		{
			x.data()[i] = rng();
		}
	}
	return ret;
}

int main(int argc, char** argv)
{
	size_t size = 1000, repeat = 20;

	if (argc > 1) size = std::stoi(argv[1]);
	if (argc > 2) repeat = std::stoi(argv[2]);

	std::cout << "[Benchmark] Generating Random Matrix " << size << "x" << size
		<< " " << repeat << " times" << std::endl;

	std::map<std::string, double> time, timeSq;
	std::map<std::string, std::pair<double, double> > results;

	for (size_t i = 0; i < repeat; ++i)
	{
		for (auto& p : test_rng(std::mt19937{}, size, "rng\tmt19937", results))
		{
			time[p.first] += p.second;
			timeSq[p.first] += p.second * p.second;
		}

		for (auto& p : test_rng(Eigen::Rand::makeUniversalRng<uint32_t>(Eigen::Rand::Vmt19937_64{}), size, "rng\tvmt19937_64/32", results))
		{
			time[p.first] += p.second;
			timeSq[p.first] += p.second * p.second;
		}

		for (auto& p : test_rng(Eigen::Rand::P8_mt19937_64<uint32_t>{}, size, "rng\tP8_mt19937_64/32", results))
		{
			time[p.first] += p.second;
			timeSq[p.first] += p.second * p.second;
		}

		for (auto& p : test_rng(std::mt19937_64{}, size, "rng\tmt19937_64", results))
		{
			time[p.first] += p.second;
			timeSq[p.first] += p.second * p.second;
		}

		for (auto& p : test_rng(Eigen::Rand::makeUniversalRng<uint64_t>(Eigen::Rand::Vmt19937_64{}), size, "rng\tvmt19937_64", results))
		{
			time[p.first] += p.second;
			timeSq[p.first] += p.second * p.second;
		}

		for (auto& p : test_rng(Eigen::Rand::P8_mt19937_64<uint64_t>{}, size, "rng\tP8_mt19937_64", results))
		{
			time[p.first] += p.second;
			timeSq[p.first] += p.second * p.second;
		}

		for (auto& p : test_old(size, "\t:Old", results))
		{
			time[p.first] += p.second;
			timeSq[p.first] += p.second * p.second;
		}

		for (auto& p : test_nullary(size, "\t:NullaryExpr", results))
		{
			time[p.first] += p.second;
			timeSq[p.first] += p.second * p.second;
		}

		for (auto& p : test_eigenrand<std::mt19937_64>(size, "\t:ERand", results))
		{
			time[p.first] += p.second;
			timeSq[p.first] += p.second * p.second;
		}

#if defined(EIGEN_VECTORIZE_SSE2) || defined(EIGEN_VECTORIZE_AVX)
		for (auto& p : test_eigenrand<Eigen::Rand::Vmt19937_64>(size, "\t:ERand+vRNG", results))
		{
			time[p.first] += p.second;
			timeSq[p.first] += p.second * p.second;
		}
#endif

	}

	std::cout << "[Average Time] Mean (Stdev)" << std::endl;
	for (auto& p : time)
	{
		double mean = p.second / repeat;
		double var = (timeSq[p.first] / repeat) - mean * mean;
		size_t sp = p.first.find('\t');
		std::cout << std::left << std::setw(28) << p.first.substr(0, sp);
		std::cout << std::setw(14) << p.first.substr(sp + 1);
		std::cout << ": " << mean * 1000 << " (" << std::sqrt(var) * 1000 << ")" << std::endl;
	}

	std::cout << std::endl << "[Statistics] Mean (Stdev)" << std::endl;
	for (auto& p : results)
	{
		size_t sp = p.first.find('\t');
		std::cout << std::left << std::setw(28) << p.first.substr(0, sp);
		std::cout << std::setw(14) << p.first.substr(sp + 1);
		std::cout << ": " << p.second.first << " (" << std::sqrt(p.second.second) << ")" << std::endl;
	}
	std::cout << std::endl;
	return 0;
}


//==================================================================================================
/*!
  @file

  @copyright 2016 NumScale SAS

  Distributed under the Boost Software License, Version 1.0.
  (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
*/
//==================================================================================================
#ifndef BOOST_SIMD_ARCH_COMMON_SIMD_FUNCTION_FNMS_HPP_INCLUDED
#define BOOST_SIMD_ARCH_COMMON_SIMD_FUNCTION_FNMS_HPP_INCLUDED

#include <boost/simd/function/fms.hpp>
#include <boost/simd/function/minus.hpp>
#include <boost/simd/function/multiplies.hpp>
#include <boost/simd/function/unary_minus.hpp>
#include <boost/simd/detail/dispatch/function/overload.hpp>
#include <boost/config.hpp>

namespace boost { namespace simd { namespace ext
{
  namespace bd = boost::dispatch;
  BOOST_DISPATCH_OVERLOAD_IF ( fnms_
                          , (typename A0, typename X)
                          , (detail::is_native<X>)
                          , bd::cpu_
                          , bs::pack_< bd::unspecified_<A0>, X>
                          , bs::pack_< bd::unspecified_<A0>, X>
                          , bs::pack_< bd::unspecified_<A0>, X>
                                    )
  {
    BOOST_FORCEINLINE
    A0 operator() ( A0 const& a0, A0 const& a1, A0 const& a2) const BOOST_NOEXCEPT
    {
     return a2-multiplies(a0, a1);
    }
  };

   BOOST_DISPATCH_OVERLOAD_IF ( fnms_
                           , (typename A0, typename X)
                           , (detail::is_native<X>)
                           , bd::cpu_
                           , bs::pedantic_tag
                           , bs::pack_< bd::unspecified_<A0>, X>
                           , bs::pack_< bd::unspecified_<A0>, X>
                           , bs::pack_< bd::unspecified_<A0>, X>
                           )
  {
    BOOST_FORCEINLINE
    A0 operator() ( A0 const& a0, A0 const& a1, A0 const& a2) const BOOST_NOEXCEPT
    {
      return -pedantic_(fms)(a0, a1, a2);
    }
  };

} } }

#endif


/*
 * HmmTest.cpp
 *
 * Author: Anthony San Lucas
 * Email:  sanlucas@gmail.com
 */

#define BOOST_TEST_MODULE HmmTest
#include <boost/test/included/unit_test.hpp>
#include <string>
#include "DataStructures.h"
#include "Hmm.h"

BOOST_AUTO_TEST_SUITE(HmmTest)

BOOST_AUTO_TEST_CASE(loadProbsAndObservationsTest) {

	// tolerance level of differences between
	// floating point numbers when testing for equality
	double tolerance = 0.00001;

	// initialize toy HMM
	std::string initState = "_initial";
	std::string state1 = "S1";
	std::string state2 = "S2";
	std::string emit1 = "A";
	std::string emit2 = "C";
	std::string emit3 = "G";
	std::string emit4 = "T";

	std::string obsType = "phase_concordance";
	Hmm hmm(2, obsType);
	hmm.setInitState(initState);
//	std::cout << hmm.getInitState() << std::endl;

	// load transition probabilities
	hmm.getTransProbs().load(initState, state1, 0.5, hmm.getStrIdMap());
	hmm.getTransProbs().load(initState, state2, 0.5, hmm.getStrIdMap());
	hmm.getTransProbs().load(state1, state1, 0.8, hmm.getStrIdMap());
	hmm.getTransProbs().load(state1, state2, 0.2, hmm.getStrIdMap());
	hmm.getTransProbs().load(state2, state1, 0.2, hmm.getStrIdMap());
	hmm.getTransProbs().load(state2, state2, 0.8, hmm.getStrIdMap());

	// check transition probs
	BOOST_CHECK_CLOSE_FRACTION(0.5, exp(hmm.getTransProbs().get(hmm.getId(state1), hmm.getId(initState))), tolerance);
	BOOST_CHECK_CLOSE_FRACTION(0.5, exp(hmm.getTransProbs().get(hmm.getId(state2), hmm.getId(initState))), tolerance);
	BOOST_CHECK_CLOSE_FRACTION(0.8, exp(hmm.getTransProbs().get(hmm.getId(state1), hmm.getId(state1))), tolerance);
	BOOST_CHECK_CLOSE_FRACTION(0.2, exp(hmm.getTransProbs().get(hmm.getId(state2), hmm.getId(state1))), tolerance);
	BOOST_CHECK_CLOSE_FRACTION(0.2, exp(hmm.getTransProbs().get(hmm.getId(state1), hmm.getId(state2))), tolerance);
	BOOST_CHECK_CLOSE_FRACTION(0.8, exp(hmm.getTransProbs().get(hmm.getId(state2), hmm.getId(state2))), tolerance);

	// load emission probabilities
	hmm.getEmitProbs().load(state1, emit1, 0.4, hmm.getStrIdMap());
	hmm.getEmitProbs().load(state1, emit2, 0.1, hmm.getStrIdMap());
	hmm.getEmitProbs().load(state1, emit3, 0.4, hmm.getStrIdMap());
	hmm.getEmitProbs().load(state1, emit4, 0.1, hmm.getStrIdMap());
	hmm.getEmitProbs().load(state2, emit1, 0.1, hmm.getStrIdMap());
	hmm.getEmitProbs().load(state2, emit2, 0.4, hmm.getStrIdMap());
	hmm.getEmitProbs().load(state2, emit3, 0.1, hmm.getStrIdMap());
	hmm.getEmitProbs().load(state2, emit4, 0.4, hmm.getStrIdMap());

	// check emission probs
	BOOST_CHECK_CLOSE_FRACTION(0.4, exp(hmm.getEmitProbs().get(hmm.getId(emit1), hmm.getId(state1))), tolerance);
	BOOST_CHECK_CLOSE_FRACTION(0.1, exp(hmm.getEmitProbs().get(hmm.getId(emit2), hmm.getId(state1))), tolerance);
	BOOST_CHECK_CLOSE_FRACTION(0.4, exp(hmm.getEmitProbs().get(hmm.getId(emit3), hmm.getId(state1))), tolerance);
	BOOST_CHECK_CLOSE_FRACTION(0.1, exp(hmm.getEmitProbs().get(hmm.getId(emit4), hmm.getId(state1))), tolerance);
	BOOST_CHECK_CLOSE_FRACTION(0.1, exp(hmm.getEmitProbs().get(hmm.getId(emit1), hmm.getId(state2))), tolerance);
	BOOST_CHECK_CLOSE_FRACTION(0.4, exp(hmm.getEmitProbs().get(hmm.getId(emit2), hmm.getId(state2))), tolerance);
	BOOST_CHECK_CLOSE_FRACTION(0.1, exp(hmm.getEmitProbs().get(hmm.getId(emit3), hmm.getId(state2))), tolerance);
	BOOST_CHECK_CLOSE_FRACTION(0.4, exp(hmm.getEmitProbs().get(hmm.getId(emit4), hmm.getId(state2))), tolerance);

//	hmm.saveProbs(); // prints transition and emission probabilities

	// add some observations - in this case each observation is a character
	std::string observations = "CGTCAG";
	for (int i = 0; i < observations.length(); i++) {
		hmm.addObservation(boost::lexical_cast<std::string>(observations[i]));
		BOOST_REQUIRE_EQUAL(observations[i], hmm.getObservation(i+1)[0]);
	}
}

BOOST_AUTO_TEST_CASE(viterbiTest) {

	// tolerance level of differences between
	// floating point numbers when testing for equality
	double tolerance = 0.00001;

	// initialize toy HMM
	std::string initState = "_initial";
	std::string state1 = "S1";
	std::string state2 = "S2";
	std::string emit1 = "A";
	std::string emit2 = "C";
	std::string emit3 = "G";
	std::string emit4 = "T";

	std::string obsType = "phase_concordance";
	Hmm hmm(2, obsType);
	hmm.setInitState(initState);
//	std::cout << hmm.getInitState() << std::endl;

	// load transition probabilities
	hmm.getTransProbs().load(initState, state1, 0.5, hmm.getStrIdMap());
	hmm.getTransProbs().load(initState, state2, 0.5, hmm.getStrIdMap());
	hmm.getTransProbs().load(state1, state1, 0.8, hmm.getStrIdMap());
	hmm.getTransProbs().load(state1, state2, 0.2, hmm.getStrIdMap());
	hmm.getTransProbs().load(state2, state1, 0.2, hmm.getStrIdMap());
	hmm.getTransProbs().load(state2, state2, 0.8, hmm.getStrIdMap());

	// load emission probabilities
	hmm.getEmitProbs().load(state1, emit1, 0.4, hmm.getStrIdMap());
	hmm.getEmitProbs().load(state1, emit2, 0.1, hmm.getStrIdMap());
	hmm.getEmitProbs().load(state1, emit3, 0.4, hmm.getStrIdMap());
	hmm.getEmitProbs().load(state1, emit4, 0.1, hmm.getStrIdMap());
	hmm.getEmitProbs().load(state2, emit1, 0.1, hmm.getStrIdMap());
	hmm.getEmitProbs().load(state2, emit2, 0.4, hmm.getStrIdMap());
	hmm.getEmitProbs().load(state2, emit3, 0.1, hmm.getStrIdMap());
	hmm.getEmitProbs().load(state2, emit4, 0.4, hmm.getStrIdMap());

	//	hmm.saveProbs(); // prints transition and emission probabilities

	// add some observations - in this case each observation is a character
	std::string observations = "CGTCAG";
	for (int i = 0; i < observations.length(); i++) {
		hmm.addObservation(boost::lexical_cast<std::string>(observations[i]));
	}

	// TEST VITERBI
	std::string predictedPath;
	std::vector<Transition*> path;
	double jointProb = hmm.viterbi(path);
	double viterbiProb = exp(jointProb - hmm.calcLogProbObs());
	for (unsigned int i = 0; i < path.size(); i++) {
		Transition* trans = path[i];
		if (trans==0) continue;
		predictedPath.append(boost::lexical_cast<std::string>(trans->to->getState()));
	}

	BOOST_REQUIRE_EQUAL(predictedPath, "222211");
	BOOST_CHECK_CLOSE_FRACTION(viterbiProb, 0.269585, tolerance);
}

BOOST_AUTO_TEST_CASE(forwardBackwardTest) {

	// tolerance level of differences between
	// floating point numbers when testing for equality
	double tolerance = 0.00001;

	// initialize toy HMM
	std::string initState = "_initial";
	std::string state1 = "S1";
	std::string state2 = "S2";
	std::string emit1 = "A";
	std::string emit2 = "C";
	std::string emit3 = "G";
	std::string emit4 = "T";

	std::string obsType = "phase_concordance";
	Hmm hmm(2, obsType);
	hmm.setInitState(initState);
//	std::cout << hmm.getInitState() << std::endl;

	// load transition probabilities
	hmm.getTransProbs().load(initState, state1, 0.5, hmm.getStrIdMap());
	hmm.getTransProbs().load(initState, state2, 0.5, hmm.getStrIdMap());
	hmm.getTransProbs().load(state1, state1, 0.8, hmm.getStrIdMap());
	hmm.getTransProbs().load(state1, state2, 0.2, hmm.getStrIdMap());
	hmm.getTransProbs().load(state2, state1, 0.2, hmm.getStrIdMap());
	hmm.getTransProbs().load(state2, state2, 0.8, hmm.getStrIdMap());

	// load emission probabilities
	hmm.getEmitProbs().load(state1, emit1, 0.4, hmm.getStrIdMap());
	hmm.getEmitProbs().load(state1, emit2, 0.1, hmm.getStrIdMap());
	hmm.getEmitProbs().load(state1, emit3, 0.4, hmm.getStrIdMap());
	hmm.getEmitProbs().load(state1, emit4, 0.1, hmm.getStrIdMap());
	hmm.getEmitProbs().load(state2, emit1, 0.1, hmm.getStrIdMap());
	hmm.getEmitProbs().load(state2, emit2, 0.4, hmm.getStrIdMap());
	hmm.getEmitProbs().load(state2, emit3, 0.1, hmm.getStrIdMap());
	hmm.getEmitProbs().load(state2, emit4, 0.4, hmm.getStrIdMap());

	//	hmm.saveProbs(); // prints transition and emission probabilities

	// add some observations - in this case each observation is a character
	std::string observations = "CGTCAG";
	for (int i = 0; i < observations.length(); i++) {
		hmm.addObservation(boost::lexical_cast<std::string>(observations[i]));
	}
//	std::cout << hmm.getObservation(3L);

	// TEST FORWARD-BACKWARD
	hmm.forwardBackward();

//	hmm.forward();
//	hmm.backward();
	// expected values for alphas, betas and posteriors to test for

	std::map<unsigned int, std::map<std::string, double> > expectedAlphas;
	expectedAlphas[1]["S1"] = 0.2;
	expectedAlphas[1]["S2"] = 0.8;
	expectedAlphas[2]["S1"] = 0.653061;
	expectedAlphas[2]["S2"] = 0.346939;
	expectedAlphas[3]["S1"] = 0.266055;
	expectedAlphas[3]["S2"] = 0.733945;
	expectedAlphas[4]["S1"] = 0.123116;
	expectedAlphas[4]["S2"] = 0.876884;
	expectedAlphas[5]["S1"] = 0.601379;
	expectedAlphas[5]["S2"] = 0.398621;
	expectedAlphas[6]["S1"] = 0.836281;
	expectedAlphas[6]["S2"] = 0.163719;

	std::map<unsigned int, std::map<std::string, double> > expectedBetas;
	expectedBetas[1]["S1"] = 5.12546;
	expectedBetas[1]["S2"] = 3.71863;
	expectedBetas[2]["S1"] = 3.4965;
	expectedBetas[2]["S2"] = 8.1242;
	expectedBetas[3]["S1"] = 3.8297;
	expectedBetas[3]["S2"] = 4.73673;
	expectedBetas[4]["S1"] = 7.84657;
	expectedBetas[4]["S2"] = 2.80234;
	expectedBetas[5]["S1"] = 6.95804;
	expectedBetas[5]["S2"] = 3.27437;
	expectedBetas[6]["S1"] = 3.72789;
	expectedBetas[6]["S2"] = 3.72789;

	std::map<unsigned int, double > expectedScaleFactors;
	expectedScaleFactors[1] = 4;
	expectedScaleFactors[2] = 5.10204;
	expectedScaleFactors[3] = 4.49541;
	expectedScaleFactors[4] = 3.42337;
	expectedScaleFactors[5] = 5.48966;
	expectedScaleFactors[6] = 3.72789;

	std::map<unsigned int, std::map<std::string, double> > expectedPosteriors;
	expectedPosteriors[1]["S1"] = 0.256273;
	expectedPosteriors[1]["S2"] = 0.743727;
	expectedPosteriors[2]["S1"] = 0.447552;
	expectedPosteriors[2]["S2"] = 0.552448;
	expectedPosteriors[3]["S1"] = 0.226656;
	expectedPosteriors[3]["S2"] = 0.773344;
	expectedPosteriors[4]["S1"] = 0.282188;
	expectedPosteriors[4]["S2"] = 0.717812;
	expectedPosteriors[5]["S1"] = 0.762238;
	expectedPosteriors[5]["S2"] = 0.237762;
	expectedPosteriors[6]["S1"] = 0.836281;
	expectedPosteriors[6]["S2"] = 0.163719;

	for (unsigned int i = 1; i < hmm.getLatentStatesZ().size(); i++) {
		LatentStateZ* zi = hmm.getLatentStatesZ()[i];
		double scaleFactor = zi->getScaleFactor();
		BOOST_CHECK_CLOSE_FRACTION(scaleFactor, expectedScaleFactors[i], tolerance);
		for (LatentStateZ::iterator it = zi->begin(); it != zi->end(); it++) {
			PossibleState *ps = *it;
			std::string obs = hmm.getStr((*it)->getInTrans()[0]->obs);
			std::string state = hmm.getStr(ps->getState());
			double alpha = exp(ps->getLogAlpha());
			double beta = exp(ps->getLogBeta());
			double posterior =	exp(ps->getLogPosterior());

			BOOST_CHECK_CLOSE_FRACTION(alpha, expectedAlphas[i][state], tolerance);
			BOOST_CHECK_CLOSE_FRACTION(beta, expectedBetas[i][state], tolerance);
			BOOST_CHECK_CLOSE_FRACTION(posterior, expectedPosteriors[i][state], tolerance);

			std::cout << i << "\t" 	<< obs << "\t"
									<< state << "\t"
									<< alpha << "\t"
									<< beta << "\t"
									<< scaleFactor << "\t"
									<< posterior << "\t"
									<< std::endl;
		}
	}

}

//BOOST_AUTO_TEST_CASE(estimateProbsTest) {
//
//	// tolerance level of differences between
//	// floating point numbers when testing for equality
//	double tolerance = 0.00001;
//
//	// initialize toy HMM
//	std::string initState = "_initial";
//	std::string state1 = "S1";
//	std::string state2 = "S2";
//	std::string emit1 = "A";
//	std::string emit2 = "C";
//	std::string emit3 = "G";
//	std::string emit4 = "T";
//
//	Hmm hmm;
//	hmm.setInitState(initState);
//	std::cout << hmm.getInitState() << std::endl;
//
//	// load transition probabilities
//	hmm.getTransProbs().load(initState, state1, 0.5, hmm.getStrIdMap());
//	hmm.getTransProbs().load(initState, state2, 0.5, hmm.getStrIdMap());
//	hmm.getTransProbs().load(state1, state1, 0.8, hmm.getStrIdMap());
//	hmm.getTransProbs().load(state1, state2, 0.2, hmm.getStrIdMap());
//	hmm.getTransProbs().load(state2, state1, 0.2, hmm.getStrIdMap());
//	hmm.getTransProbs().load(state2, state2, 0.8, hmm.getStrIdMap());
//
//	// load emission probabilities
//	hmm.getEmitProbs().load(state1, emit1, 0.4, hmm.getStrIdMap());
//	hmm.getEmitProbs().load(state1, emit2, 0.1, hmm.getStrIdMap());
//	hmm.getEmitProbs().load(state1, emit3, 0.4, hmm.getStrIdMap());
//	hmm.getEmitProbs().load(state1, emit4, 0.1, hmm.getStrIdMap());
//	hmm.getEmitProbs().load(state2, emit1, 0.1, hmm.getStrIdMap());
//	hmm.getEmitProbs().load(state2, emit2, 0.4, hmm.getStrIdMap());
//	hmm.getEmitProbs().load(state2, emit3, 0.1, hmm.getStrIdMap());
//	hmm.getEmitProbs().load(state2, emit4, 0.4, hmm.getStrIdMap());
//
//	hmm.saveProbs(); // prints transition and emission probabilities
//
//	// add some observations - in this case each observation is a character
//	std::string observations = "CGTCAG";
//	std::vector< std::vector<std::string>* > observedSequences;
//	std::vector<std::string> observedSequence;
//	for (unsigned int i = 0; i < observations.size(); i++) {
//		std::string switchStr = boost::lexical_cast<std::string>(observations[i]);
//		observedSequence.push_back(switchStr);
//	}
//	observedSequences.push_back(&observedSequence);
//
//	// TEST PARAMETER ESTIMATION
//	for (unsigned int i=0; i<observedSequences.size(); i++) {
//		std::vector<std::string>& seq = *observedSequences[i];
//		for (unsigned int j=0; j<seq.size(); j++) {
//			hmm.addObservation(seq[j]);
//		}
//	}
//
//	hmm.estimateProbs();
//	hmm.saveProbs();
//
//}

//BOOST_AUTO_TEST_CASE(baumWelchTest) {
//
//	// tolerance level of differences between
//	// floating point numbers when testing for equality
//	double tolerance = 0.00001;
//
//	// initialize toy HMM
//	std::string initState = "_initial";
//	std::string state1 = "S1";
//	std::string state2 = "S2";
//	std::string emit1 = "A";
//	std::string emit2 = "C";
//	std::string emit3 = "G";
//	std::string emit4 = "T";
//
//	Hmm hmm;
//	hmm.setInitState(initState);
//	std::cout << hmm.getInitState() << std::endl;
//
//	// load transition probabilities
//	hmm.getTransProbs().load(initState, state1, 0.5, hmm.getStrIdMap());
//	hmm.getTransProbs().load(initState, state2, 0.5, hmm.getStrIdMap());
//	hmm.getTransProbs().load(state1, state1, 0.8, hmm.getStrIdMap());
//	hmm.getTransProbs().load(state1, state2, 0.2, hmm.getStrIdMap());
//	hmm.getTransProbs().load(state2, state1, 0.2, hmm.getStrIdMap());
//	hmm.getTransProbs().load(state2, state2, 0.8, hmm.getStrIdMap());
//
//	// load emission probabilities
//	hmm.getEmitProbs().load(state1, emit1, 0.4, hmm.getStrIdMap());
//	hmm.getEmitProbs().load(state1, emit2, 0.1, hmm.getStrIdMap());
//	hmm.getEmitProbs().load(state1, emit3, 0.4, hmm.getStrIdMap());
//	hmm.getEmitProbs().load(state1, emit4, 0.1, hmm.getStrIdMap());
//	hmm.getEmitProbs().load(state2, emit1, 0.1, hmm.getStrIdMap());
//	hmm.getEmitProbs().load(state2, emit2, 0.4, hmm.getStrIdMap());
//	hmm.getEmitProbs().load(state2, emit3, 0.1, hmm.getStrIdMap());
//	hmm.getEmitProbs().load(state2, emit4, 0.4, hmm.getStrIdMap());
//
//	hmm.saveProbs(); // prints transition and emission probabilities
//
//	// add some observations - in this case each observation is a character
//	std::string observations = "CGTCAG";
//	std::vector< std::vector<std::string>* > observedSequences;
//	std::vector<std::string> observedSequence;
//	for (unsigned int i = 0; i < observations.size(); i++) {
//		std::string switchStr = boost::lexical_cast<std::string>(observations[i]);
//		observedSequence.push_back(switchStr);
//	}
//
//	// TEST BAUM-WELCH
//	int iterations = 2;
//	observedSequences.push_back(&observedSequence);
//	hmm.baumWelch(observedSequences, iterations);
//
//}

BOOST_AUTO_TEST_SUITE_END()


// Software License for MTL
//
// Copyright (c) 2007 The Trustees of Indiana University.
//               2008 Dresden University of Technology and the Trustees of Indiana University.
//               2010 SimuNova UG (haftungsbeschränkt), www.simunova.com.
// All rights reserved.
// Authors: Peter Gottschling and Andrew Lumsdaine
//
// This file is part of the Matrix Template Library
//
// See also license.mtl.txt in the distribution.

#ifndef ITL_PC_MASSLUMPING_INCLUDE
#define ITL_PC_MASSLUMPING_INCLUDE

#include <boost/numeric/linear_algebra/inverse.hpp>

#include <boost/numeric/mtl/vector/dense_vector.hpp>
#include <boost/numeric/mtl/utility/exception.hpp>
#include <boost/numeric/mtl/utility/tag.hpp>
#include <boost/numeric/mtl/detail/base_cursor.hpp>
#include <boost/numeric/mtl/concept/collection.hpp>
#include <boost/numeric/mtl/operation/resource.hpp>
#include <boost/numeric/mtl/interface/vpt.hpp>
#include <boost/numeric/itl/pc/solver.hpp>

namespace itl
{
  namespace pc
  {

    /// Diagonal Preconditioner
    template <typename Matrix, typename Value= typename mtl::Collection<Matrix>::value_type>
    class masslumping
    {
    public:
      typedef Value                                         value_type;
      typedef typename mtl::Collection<Matrix>::size_type   size_type;
      typedef masslumping                                   self;

      /// Constructor takes matrix reference
      explicit masslumping(const Matrix& A) : inv_diag(num_rows(A))
      {
        // 	mtl::vampir_trace<5050> tracer;
        MTL_THROW_IF(num_rows(A) != num_cols(A), mtl::matrix_not_square());
        using namespace mtl;
        using namespace mtl::tag;
        using mtl::traits::range_generator;
        using math::reciprocal;

        typedef typename range_generator<tag::row, Matrix>::type c_type;
        typedef typename range_generator<tag::nz, c_type>::type  ic_type;

        typename mtl::traits::row<Matrix>::type row(A);
        typename mtl::traits::const_value<Matrix>::type value(A);

        value_type row_sum;
        c_type cursor(begin<tag::row>(A));
        for (c_type cend(end<tag::row>(A)); cursor != cend; ++cursor)
        {
          row_sum = math::zero(value_type());
          ic_type icursor(begin<tag::nz>(cursor));
          size_type r = row(icursor);

          for (ic_type icend(end<tag::nz>(cursor)); icursor != icend; ++icursor)
            row_sum += value(*icursor);

          inv_diag[r]= reciprocal(row_sum);
        }
      }

      /// Member function solve, better use free function solve
      template <typename Vector>
      Vector solve(const Vector& x) const
      {
        Vector y(resource(x));
        solve(x, y);
        return y;
      }

      template <typename VectorIn, typename VectorOut>
      void solve(const VectorIn& x, VectorOut& y) const
      {
        mtl::vampir_trace<5051> tracer;
        y.checked_change_resource(x);
        MTL_THROW_IF(size(x) != size(inv_diag), mtl::incompatible_size());
        for (size_type i= 0; i < size(inv_diag); ++i)
          y[i]= inv_diag[i] * x[i];
      }

      /// Member function for solving adjoint problem, better use free function adjoint_solve
      template <typename Vector>
      Vector adjoint_solve(const Vector& x) const
      {
        Vector y(resource(x));
        adjoint_solve(x, y);
        return y;
      }

      template <typename VectorIn, typename VectorOut>
      void adjoint_solve(const VectorIn& x, VectorOut& y) const
      {
        using mtl::conj;
        y.checked_change_resource(x);
        MTL_THROW_IF(size(x) != size(inv_diag), mtl::incompatible_size());
        for (size_type i= 0; i < size(inv_diag); ++i)
          y[i]= conj(inv_diag[i]) * x[i];
      }

    protected:
      mtl::vector::dense_vector<value_type>    inv_diag;
    };

    /// Solve approximately a sparse system in terms of inverse lumped diagonal
    template <typename Matrix, typename Vector>
    solver<masslumping<Matrix>, Vector, false>
    inline solve(const masslumping<Matrix>& P, const Vector& x)
    {
      return solver<masslumping<Matrix>, Vector, false>(P, x);
    }

    /// Solve approximately the adjoint of a sparse system in terms of inverse lumped diagonal
    template <typename Matrix, typename Vector>
    solver<masslumping<Matrix>, Vector, true>
    inline adjoint_solve(const masslumping<Matrix>& P, const Vector& x)
    {
      return solver<masslumping<Matrix>, Vector, true>(P, x);
    }

  }
} // namespace itl::pc

#endif // ITL_PC_MASSLUMPING_INCLUDE


// Boost.Geometry (aka GGL, Generic Geometry Library)
// Unit Test

// Copyright (c) 2017, Oracle and/or its affiliates.

// Contributed and/or modified by Vissarion Fysikopoulos, on behalf of Oracle

// Licensed under the Boost Software License version 1.0.
// http://www.boost.org/users/license.html

#include <iostream>

#ifndef BOOST_TEST_MODULE
#define BOOST_TEST_MODULE test_distance_geographic_point_box
#endif

#include <boost/range.hpp>
#include <boost/type_traits/is_same.hpp>

#include <boost/test/included/unit_test.hpp>
#include <boost/geometry/util/condition.hpp>
#include <boost/geometry/strategies/strategies.hpp>

#include "test_distance_geo_common.hpp"

typedef bg::cs::geographic<bg::degree> cs_type;
typedef bg::model::point<double, 2, cs_type> point_type;
typedef bg::model::multi_point<point_type> multi_point_type;
typedef bg::model::segment<point_type> segment_type;
typedef bg::model::box<point_type> box_type;

namespace services = bg::strategy::distance::services;
typedef bg::default_distance_result<point_type>::type return_type;

typedef bg::srs::spheroid<double> stype;

// Strategies for point-point distance

typedef bg::strategy::distance::andoyer<stype> andoyer_pp;
typedef bg::strategy::distance::thomas<stype> thomas_pp;
typedef bg::strategy::distance::vincenty<stype> vincenty_pp;

// Strategies for point-segment distance

typedef bg::strategy::distance::geographic_cross_track<bg::strategy::andoyer, stype, double>
        andoyer_ps;

typedef bg::strategy::distance::geographic_cross_track<bg::strategy::thomas, stype, double>
        thomas_ps;

typedef bg::strategy::distance::geographic_cross_track<bg::strategy::vincenty, stype, double>
        vincenty_ps;

// Strategies for point-box distance

typedef bg::strategy::distance::geographic_cross_track_point_box
        <
            bg::strategy::andoyer,
            bg::srs::spheroid<double>,
            double
        > andoyer_pb;

typedef bg::strategy::distance::geographic_cross_track_point_box
        <
            bg::strategy::thomas,
            bg::srs::spheroid<double>,
            double
        > thomas_pb;

typedef bg::strategy::distance::geographic_cross_track_point_box
        <
            bg::strategy::vincenty,
            bg::srs::spheroid<double>,
            double
        > vincenty_pb;

//===========================================================================

template <typename Strategy>
inline bg::default_distance_result<point_type>::type
pp_distance(std::string const& wkt1,
            std::string const& wkt2,
            Strategy const& strategy)
{
    point_type p1, p2;
    bg::read_wkt(wkt1, p1);
    bg::read_wkt(wkt2, p2);
    return bg::distance(p1, p2, strategy);
}

template <typename Strategy>
inline bg::default_distance_result<point_type>::type
ps_distance(std::string const& wkt1,
            std::string const& wkt2,
            Strategy const& strategy)
{
    point_type p;
    segment_type s;
    bg::read_wkt(wkt1, p);
    bg::read_wkt(wkt2, s);
    return bg::distance(p, s, strategy);
}

//===========================================================================
// Cases for relative location of a point wrt to a box
//
//           |         |
//           |    3    |
//           |         |
//           +---------+
//           |         |
//       1   |    5    |   2
//           |         |
//           +---------+
//           |         |
//           |    4    |
//           |         |
//
// and also the following cases
//
//           |         |
//           A         B
//           |         |
//           +----C----+
//           |         |
//           D         E
//           |         |
//           +----F----+
//           |         |
//           G         H
//           |         |
//
// and finally we have the corners
//
//           |         |
//           |         |
//           |         |
//           a---------b
//           |         |
//           |         |
//           |         |
//           c---------d
//           |         |
//           |         |
//           |         |
//
// for each relative position we also have to test the shifted point
// (this is due to the fact that boxes have longitudes in the
// range [-180, 540)
//===========================================================================

template <typename Strategy_pp, typename Strategy_ps, typename Strategy_pb>
void test_distance_point_box(Strategy_pp const& strategy_pp,
                             Strategy_ps const& strategy_ps,
                             Strategy_pb const& strategy_pb)
{

#ifdef BOOST_GEOMETRY_TEST_DEBUG
    std::cout << std::endl;
    std::cout << "point/box distance tests" << std::endl;
#endif
    typedef test_distance_of_geometries<point_type, box_type> tester;

    std::string const box1 = "BOX(10 10,20 20)";

    // case 1
    tester::apply("pb1-1a", "POINT(5 25)", box1,
                  pp_distance("POINT(5 25)", "POINT(10 20)", strategy_pp),
                  strategy_pb);

    // case 1
    tester::apply("pb1-1b", "POINT(3 12)", box1,
                  ps_distance("POINT(3 12)", "SEGMENT(10 10,10 20)", strategy_ps),
                  strategy_pb);

    // case 1
    tester::apply("pb1-1c", "POINT(3 17)", box1,
                  ps_distance("POINT(3 17)", "SEGMENT(10 10,10 20)", strategy_ps),
                  strategy_pb);

    // case 1
    tester::apply("pb1-1d", "POINT(5 4)", box1,
                  pp_distance("POINT(5 4)", "POINT(10 10)", strategy_pp),
                  strategy_pb);

    // case 1
    tester::apply("pb1-1e", "POINT(-100 20)", box1,
                  pp_distance("POINT(-100 20)", "POINT(10 20)", strategy_pp),
                  strategy_pb);

    // case 1
    tester::apply("pb1-1g", "POINT(-100 10)", box1,
                  ps_distance("POINT(-100 10)", "SEGMENT(10 10,10 20)", strategy_ps),
                  strategy_pb);

    // case 2
    tester::apply("pb1-2a", "POINT(31 25)", box1,
                  pp_distance("POINT(31 25)", "POINT(20 20)", strategy_pp),
                  strategy_pb);

    // case 2
    tester::apply("pb1-2b", "POINT(23 17)", box1,
                  ps_distance("POINT(23 17)", "SEGMENT(20 10,20 20)", strategy_ps),
                  strategy_pb);

    // case 2
    tester::apply("pb1-2c", "POINT(29 3)", box1,
                  pp_distance("POINT(29 3)", "POINT(20 10)", strategy_pp),
                  strategy_pb);

    // case 2
    tester::apply("pb1-2d", "POINT(131 65)", box1,
                  pp_distance("POINT(131 65)", "POINT(20 20)", strategy_pp),
                  strategy_pb);

    // case 2
    tester::apply("pb1-2e", "POINT(110 10)", box1,
                  ps_distance("POINT(110 10)", "SEGMENT(20 10,20 20)", strategy_ps),
                  strategy_pb);

    // case 2
    tester::apply("pb1-2f", "POINT(150 20)", box1,
                  pp_distance("POINT(150 20)", "POINT(20 20)", strategy_pp),
                  strategy_pb);

    // case 3
    tester::apply("pb1-3a", "POINT(11 25)", box1,
                  pp_distance("POINT(11 25)", "POINT(11 20)", strategy_pp),
                  strategy_pb);

    // case 3
    tester::apply("pb1-3b", "POINT(15 25)", box1,
                  pp_distance("POINT(15 25)", "POINT(15 20)", strategy_pp),
                  strategy_pb);

    // case 3
    tester::apply("pb1-3c", "POINT(18 25)", box1,
                  pp_distance("POINT(18 25)", "POINT(18 20)", strategy_pp),
                  strategy_pb);

    // case 4
    tester::apply("pb1-4a", "POINT(13 4)", box1,
                  pp_distance("POINT(13 4)", "POINT(13 10)", strategy_pp),
                  strategy_pb);

    // case 4
    tester::apply("pb1-4b", "POINT(19 4)", box1,
                  pp_distance("POINT(19 4)", "POINT(19 10)", strategy_pp),
                  strategy_pb);

    // case 5
    tester::apply("pb1-5", "POINT(15 14)", box1, 0, strategy_pb);

    // case A
    tester::apply("pb1-A", "POINT(10 28)", box1,
                  pp_distance("POINT(10 28)", "POINT(10 20)", strategy_pp),
                  strategy_pb);

    // case B
    tester::apply("pb1-B", "POINT(20 28)", box1,
                  pp_distance("POINT(20 28)", "POINT(20 20)", strategy_pp),
                  strategy_pb);

    // case C
    tester::apply("pb1-C", "POINT(14 20)", box1, 0, strategy_pb);

    // case D
    tester::apply("pb1-D", "POINT(10 17)", box1, 0, strategy_pb);

    // case E
    tester::apply("pb1-E", "POINT(20 11)", box1, 0, strategy_pb);

    // case F
    tester::apply("pb1-F", "POINT(19 10)", box1, 0, strategy_pb);

    // case G
    tester::apply("pb1-G", "POINT(10 -40)", box1,
                  pp_distance("POINT(10 -40)", "POINT(10 10)", strategy_pp),
                  strategy_pb);

    // case H
    tester::apply("pb1-H", "POINT(20 -50)", box1,
                  pp_distance("POINT(20 -50)", "POINT(20 10)", strategy_pp),
                  strategy_pb);

    // case a
    tester::apply("pb1-a", "POINT(10 20)", box1, 0, strategy_pb);
    // case b
    tester::apply("pb1-b", "POINT(20 20)", box1, 0, strategy_pb);
    // case c
    tester::apply("pb1-c", "POINT(10 10)", box1, 0, strategy_pb);
    // case d
    tester::apply("pb1-d", "POINT(20 10)", box1, 0, strategy_pb);

    std::string const box2 = "BOX(170 -60,400 80)";

    // case 1 - point is closer to western meridian
    tester::apply("pb2-1a", "POINT(160 0)", box2,
                  ps_distance("POINT(160 0)", "SEGMENT(170 -60,170 80)", strategy_ps),
                  strategy_pb);

    // case 1 - point is closer to eastern meridian
    tester::apply("pb2-1b", "POINT(50 0)", box2,
                  ps_distance("POINT(50 0)", "SEGMENT(40 -60,40 80)", strategy_ps),
                  strategy_pb);

    // case 3 - equivalent point POINT(390 85) is above the box
    tester::apply("pb2-3", "POINT(30 85)", box2,
                  pp_distance("POINT(30 85)", "POINT(30 80)", strategy_pp),
                  strategy_pb);

    // case 4 - equivalent point POINT(390 -75) is below the box
    tester::apply("pb2-4", "POINT(30 -75)", box2,
                  pp_distance("POINT(30 -75)", "POINT(30 -60)", strategy_pp),
                  strategy_pb);

    // case 5 - equivalent point POINT(390 0) is inside box
    tester::apply("pb2-5", "POINT(30 0)", box2, 0, strategy_pb);

    std::string const box3 = "BOX(-150 -50,-40 70)";

    // case 1 - point is closer to western meridian
    tester::apply("pb3-1a", "POINT(-170 10)", box3,
                  ps_distance("POINT(-170 10)", "SEGMENT(-150 -50,-150 70)", strategy_ps),
                  strategy_pb);

    // case 2 - point is closer to eastern meridian
    tester::apply("pb3-2a", "POINT(5 10)", box3,
                  ps_distance("POINT(5 10)", "SEGMENT(-40 -50,-40 70)", strategy_ps),
                  strategy_pb);

    // case 2 - point is closer to western meridian
    tester::apply("pb3-2a", "POINT(160 10)", box3,
                  ps_distance("POINT(160 10)", "SEGMENT(-150 -50,-150 70)", strategy_ps),
                  strategy_pb);

    // case 2 - point is at equal distance from eastern and western meridian
    tester::apply("pb3-2c1", "POINT(85 20)", box3,
                  ps_distance("POINT(85 20)", "SEGMENT(-150 -50,-150 70)", strategy_ps),
                  strategy_pb);

    // case 2 - point is at equal distance from eastern and western meridian
    tester::apply("pb3-2c2", "POINT(85 20)", box3,
                  ps_distance("POINT(85 20)", "SEGMENT(-40 -50,-40 70)", strategy_ps),
                  strategy_pb);

    // box that is symmetric wrt the prime meridian
    std::string const box4 = "BOX(-75 -45,75 65)";

    // case 1 - point is closer to western meridian
    tester::apply("pb4-1a", "POINT(-100 10)", box4,
                  ps_distance("POINT(-100 10)", "SEGMENT(-75 -45,-75 65)", strategy_ps),
                  strategy_pb);

    // case 2 - point is closer to eastern meridian
    tester::apply("pb4-2a", "POINT(90 15)", box4,
                  ps_distance("POINT(90 15)", "SEGMENT(75 -45,75 65)", strategy_ps),
                  strategy_pb);

    // case 2 - point is at equal distance from eastern and western meridian
    tester::apply("pb4-2c1", "POINT(-180 20)", box4,
                  ps_distance("POINT(-180 20)", "SEGMENT(-75 -45,-75 65)", strategy_ps),
                  strategy_pb);

    // case 2 - point is at equal distance from eastern and western meridian
    tester::apply("pb4-2c2", "POINT(-180 20)", box4,
                  ps_distance("POINT(-180 20)", "SEGMENT(75 -45,75 65)", strategy_ps),
                  strategy_pb);
}

template <typename Strategy_pp, typename Strategy_ps, typename Strategy_pb>
void test_distance_point_deg_box(Strategy_pp const& strategy_pp,
                                 Strategy_ps const& strategy_ps,
                                 Strategy_pb const& strategy_pb)
{

#ifdef BOOST_GEOMETRY_TEST_DEBUG
    std::cout << std::endl;
    std::cout << "point/box distance tests" << std::endl;
#endif
    typedef test_distance_of_geometries<point_type, box_type> tester;

    //box degenerates to a meridian segment
    std::string const box1 = "BOX(0 10,0 20)";

    tester::apply("pbd1", "POINT(1 10)", box1,
                  ps_distance("POINT(1 10)", "SEGMENT(0 10, 0 20)", strategy_ps),
                  strategy_pb);
    tester::apply("pbd2", "POINT(1 5)", box1,
                  ps_distance("POINT(1 5)", "SEGMENT(0 10, 0 20)", strategy_ps),
                  strategy_pb);
    tester::apply("pbd3", "POINT(1 15)", box1,
                  ps_distance("POINT(1 15)", "SEGMENT(0 10, 0 20)", strategy_ps),
                  strategy_pb);
    tester::apply("pbd4", "POINT(1 25)", box1,
                  ps_distance("POINT(1 25)", "SEGMENT(0 10, 0 20)", strategy_ps),
                  strategy_pb);

    //box degenerates to a horizontal line; that is not a geodesic segment
    std::string const box2 = "BOX(10 10,20 10)";

    tester::apply("pbd5", "POINT(15 15)", box2,
                  pp_distance("POINT(15 15)", "POINT(15 10)", strategy_pp),
                  strategy_pb);
    tester::apply("pbd6", "POINT(5 15)", box2,
                  pp_distance("POINT(5 15)", "POINT(10 10)", strategy_pp),
                  strategy_pb);
    tester::apply("pbd7", "POINT(25 15)", box2,
                  pp_distance("POINT(25 15)", "POINT(20 10)", strategy_pp),
                  strategy_pb);

    //box degenerates to a point
    std::string const box3 = "BOX(0 10,0 10)";

    tester::apply("pbd8", "POINT(1 11)", box3,
                  pp_distance("POINT(1 11)", "POINT(0 10)", strategy_pp),
                  strategy_pb);
}

template <typename Strategy_pp, typename Strategy_ps, typename Strategy_pb>
void test_distance_multipoint_box(Strategy_pp const& strategy_pp,
                                  Strategy_ps const& strategy_ps,
                                  Strategy_pb const& strategy_pb)
{

#ifdef BOOST_GEOMETRY_TEST_DEBUG
    std::cout << std::endl;
    std::cout << "multipoint/box distance tests" << std::endl;
#endif
    typedef test_distance_of_geometries<multi_point_type, box_type> tester;

    std::string const box1 = "BOX(10 10,20 20)";

    tester::apply("mpb1-1a", "MULTIPOINT(5 25,25 26)", box1,
                  pp_distance("POINT(5 25)", "POINT(10 20)", strategy_pp),
                  strategy_pb);

    tester::apply("mpb1-2e", "MULTIPOINT(110 10,110 9,110 0)", box1,
                  ps_distance("POINT(110 10)", "SEGMENT(20 10,20 20)", strategy_ps),
                  strategy_pb);
}

//===========================================================================
//===========================================================================
//===========================================================================

BOOST_AUTO_TEST_CASE( test_all_point_segment )
{
    test_distance_point_box(vincenty_pp(), vincenty_ps(), vincenty_pb());
    test_distance_point_box(thomas_pp(), thomas_ps(), thomas_pb());
    test_distance_point_box(andoyer_pp(), andoyer_ps(), andoyer_pb());

    test_distance_point_deg_box(vincenty_pp(), vincenty_ps(), vincenty_pb());
    test_distance_point_deg_box(thomas_pp(), thomas_ps(), thomas_pb());
    test_distance_point_deg_box(andoyer_pp(), andoyer_ps(), andoyer_pb());

    test_distance_multipoint_box(vincenty_pp(), vincenty_ps(), vincenty_pb());
    test_distance_multipoint_box(thomas_pp(), thomas_ps(), thomas_pb());
    test_distance_multipoint_box(andoyer_pp(), andoyer_ps(), andoyer_pb());
}