#include <stdio.h>
#include <cfloat>
#include <list>
#include <vector>
#include <math.h>
#include <iostream>
#include <algorithm>
#include <numeric>
#include <random>
#include <boost/algorithm/clamp.hpp>
#include <boost/geometry.hpp>
#include <boost/geometry/geometries/geometries.hpp>
#include <boost/geometry/geometries/point_xy.hpp>
#include <boost/graph/adjacency_matrix.hpp>
#include <boost/graph/kruskal_min_spanning_tree.hpp>
#include "third_party/eigen3/unsupported/Eigen/CXX11/Tensor"
#include "tensorflow/core/framework/op.h"
#include "tensorflow/core/framework/op_kernel.h"
#include "tensorflow/core/framework/tensor_shape.h"
#include "tensorflow/core/framework/common_shape_fns.h"
#include "tensorflow/core/util/work_sharder.h"
#include <boost/geometry/io/wkt/write.hpp>
#include "bboxes.h"

using namespace boost;
using namespace tensorflow;
using namespace std;

typedef Eigen::ThreadPoolDevice CPUDevice;
typedef adjacency_matrix<undirectedS, no_property, property <edge_weight_t, float>> Graph;
typedef graph_traits <Graph>::edge_descriptor Edge;
typedef std::pair<int, int> E;
/*
 * 根据输入的boxes生成一个邻接矩阵
 * boxes: [N,4], ymin,xmin,ymax,xmax相对坐标
 * output:
 * matrix[N,N] 第i行,j列表示第i个点与第j个点之间的连接
 */
REGISTER_OP("AdjacentMatrixGeneratorByIou")
    .Attr("T: {float, double,int32}")
	.Attr("threshold:float")
    .Attr("keep_connect: bool")
    .Input("bboxes: T")
	.Output("matrix:int32")
	.SetShapeFn([](shape_inference::InferenceContext* c) {
		    auto box_nr = c->Dim(c->input(0),0);
			c->set_output(0, c->Matrix(box_nr,box_nr));
			return Status::OK();
			});

template <typename Device, typename T>
class AdjacentMatrixGeneratorByIouOp: public OpKernel {
    public:
        explicit AdjacentMatrixGeneratorByIouOp(OpKernelConstruction* context) : OpKernel(context) {
            OP_REQUIRES_OK(context, context->GetAttr("threshold", &threshold_));
            OP_REQUIRES_OK(context, context->GetAttr("keep_connect", &keep_connect_));
        }

        void Compute(OpKernelContext* context) override
        {
            const Tensor &_bboxes  = context->input(0);
            auto          bboxes   = _bboxes.template tensor<T,2>();

            OP_REQUIRES(context, _bboxes.dims() == 2, errors::InvalidArgument("bboxes data must be 2-dimensional"));
            const auto bboxes_nr    = _bboxes.dim_size(0);
            Eigen::Tensor<float,2,Eigen::RowMajor> dis_matrix(bboxes_nr,bboxes_nr);

            dis_matrix.setZero();

            if(keep_connect_) {
                for(auto i=0; i<bboxes_nr-1; ++i) {
                    const Eigen::Tensor<T,1,Eigen::RowMajor> box_data0 = bboxes.chip(i,0);

                    dis_matrix(i,i) = 0.;
                    for(auto j=i+1; j<bboxes_nr; ++j) {
                        const Eigen::Tensor<T,1,Eigen::RowMajor> box_data1 = bboxes.chip(j,0);
                        const auto dis = distance(box_data0,box_data1);
                        dis_matrix(i,j) = dis;
                        dis_matrix(j,i) = dis;
                    }
                }
            }

            auto res = make_graph(bboxes,dis_matrix);

            Tensor      *output_matrix = NULL;
            TensorShape  output_shape;
            const int    dims_2d[]     = {int(bboxes_nr),int(bboxes_nr)};

            TensorShapeUtils::MakeShape(dims_2d, 2, &output_shape);
            OP_REQUIRES_OK(context, context->allocate_output(0, output_shape, &output_matrix));

            auto output           = output_matrix->template tensor<int,2>();
            for(auto i=0; i<bboxes_nr; ++i) {
                output(i,i) = 0;
                for(auto j=i+1; j<bboxes_nr; ++j) {
                    output(i,j) = res(i,j);
                    output(j,i) = res(i,j);
                }
            }
        }
        template<typename _T,typename M>
            Eigen::Tensor<int,2,Eigen::RowMajor> make_graph(const _T& bboxes,const M& dis_m) {
                const auto data_nr = bboxes.dimension(0);
                Graph g(data_nr);
                Eigen::Tensor<int,2,Eigen::RowMajor> res(data_nr,data_nr);
                res.setZero();

                if(keep_connect_) {
                    auto weightmap = get(edge_weight,g);
                    for(auto i=0; i<data_nr; ++i) {
                        for(auto j=i+1; j<data_nr; ++j) {
                            Edge e;
                            bool inserted;
                            boost::tie(e,inserted) = add_edge(i,j,g);
                            weightmap[e] = dis_m(i,j);
                        }
                    }
                    std::vector < Edge > spanning_tree;
                    kruskal_minimum_spanning_tree(g, std::back_inserter(spanning_tree));
                    for(auto e:spanning_tree) {
                        auto si = source(e,g);
                        auto ti = target(e,g);
                        res(si,ti) = 1;
                        res(ti,si) = 1;
                    }
                }
                for(auto i=0; i<data_nr; ++i) {
                    const Eigen::Tensor<T,1,Eigen::RowMajor> box_data0 = bboxes.chip(i,0);
                    for(auto j=i+1; j<data_nr; ++j) {
                        const Eigen::Tensor<T,1,Eigen::RowMajor> box_data1 = bboxes.chip(j,0);
                        //if(bboxes_jaccardv1(box_data0,box_data1)>threshold_) {
                        if((bboxes_jaccard_of_box0v1(box_data0,box_data1)>threshold_) ||
                                (bboxes_jaccard_of_box0v1(box_data1,box_data0)>threshold_)) {
                            res(i,j) = 1;
                            res(j,i) = 1;
                        }
                    }
                }
                return res;
            }
        template<typename _T>
            inline float distance(const _T& box0, const _T& box1) {
                float cx0,cy0,cx1,cy1;
                tie(cx0,cy0) = get_cxy(box0);
                tie(cx1,cy1) = get_cxy(box1);
                const auto dx = (cx0-cx1);
                const auto dy = (cy0-cy1);
                return sqrt(dx*dx+dy*dy);
            }
        template<typename _T>
        static inline std::pair<float,float> get_cxy(const _T& box) {
            return make_pair((box(1)+box(3))/2.0f,(box(0)+box(2))/2.0f);
        }
    private:
        float threshold_ = 0.3f;
        bool keep_connect_ = false;
};
REGISTER_KERNEL_BUILDER(Name("AdjacentMatrixGeneratorByIou").Device(DEVICE_CPU).TypeConstraint<float>("T"), AdjacentMatrixGeneratorByIouOp<CPUDevice, float>);
REGISTER_KERNEL_BUILDER(Name("AdjacentMatrixGeneratorByIou").Device(DEVICE_CPU).TypeConstraint<double>("T"), AdjacentMatrixGeneratorByIouOp<CPUDevice, double>);

/*
 * 根据输入的boxes生成一个邻接矩阵
 * boxes: [N,4], ymin,xmin,ymax,xmax相对坐标
 * output:
 * matrix[N,N] 第i行,j列表示第i个点与第j个点之间的连接
 */
REGISTER_OP("AdjacentMatrixGeneratorByIouV2")
    .Attr("T: {float, double,int32}")
	.Attr("threshold:list(float)=[0.3,0.12]")
    .Attr("keep_connect: bool")
    .Input("bboxes: T")
    .Input("labels: int32")
    .Input("probs: T")
	.Output("matrix:int32")
	.SetShapeFn([](shape_inference::InferenceContext* c) {
		    auto box_nr = c->Dim(c->input(0),0);
			c->set_output(0, c->Matrix(box_nr,box_nr));
			return Status::OK();
			});

template <typename Device, typename T>
class AdjacentMatrixGeneratorByIouV2Op: public OpKernel {
    public:
        explicit AdjacentMatrixGeneratorByIouV2Op(OpKernelConstruction* context) : OpKernel(context) {
            OP_REQUIRES_OK(context, context->GetAttr("threshold", &threshold_));
            OP_REQUIRES_OK(context, context->GetAttr("keep_connect", &keep_connect_));
        }

        void Compute(OpKernelContext* context) override
        {
            const Tensor &_bboxes  = context->input(0);
            const Tensor &_labels = context->input(1);
            const Tensor &_probs = context->input(2);
            auto          bboxes   = _bboxes.template tensor<T,2>();
            auto          labels = _labels.template tensor<int,1>();
            auto          probs = _probs.template tensor<T,2>();

            OP_REQUIRES(context, _bboxes.dims() == 2, errors::InvalidArgument("bboxes data must be 2-dimensional"));
            const auto bboxes_nr    = _bboxes.dim_size(0);
            Eigen::Tensor<float,2,Eigen::RowMajor> dis_matrix(bboxes_nr,bboxes_nr);

            dis_matrix.setZero();

            if(keep_connect_) {
                for(auto i=0; i<bboxes_nr-1; ++i) {
                    const Eigen::Tensor<T,1,Eigen::RowMajor> box_data0 = bboxes.chip(i,0);

                    dis_matrix(i,i) = 0.;
                    for(auto j=i+1; j<bboxes_nr; ++j) {
                        const Eigen::Tensor<T,1,Eigen::RowMajor> box_data1 = bboxes.chip(j,0);
                        const auto dis = distance(box_data0,box_data1);
                        dis_matrix(i,j) = dis;
                        dis_matrix(j,i) = dis;
                    }
                }
            }

            auto res = make_graph(bboxes,labels,probs,dis_matrix);

            Tensor      *output_matrix = NULL;
            TensorShape  output_shape;
            const int    dims_2d[]     = {int(bboxes_nr),int(bboxes_nr)};

            TensorShapeUtils::MakeShape(dims_2d, 2, &output_shape);
            OP_REQUIRES_OK(context, context->allocate_output(0, output_shape, &output_matrix));

            auto output           = output_matrix->template tensor<int,2>();
            for(auto i=0; i<bboxes_nr; ++i) {
                output(i,i) = 0;
                for(auto j=i+1; j<bboxes_nr; ++j) {
                    output(i,j) = res(i,j);
                    output(j,i) = res(i,j);
                }
            }
        }
        template<typename _T,typename _TL,typename _TP,typename M>
            Eigen::Tensor<int,2,Eigen::RowMajor> make_graph(const _T& bboxes,const _TL& labels,const _TP& probs,const M& dis_m) {
                const auto data_nr = bboxes.dimension(0);
                Graph g(data_nr);
                Eigen::Tensor<int,2,Eigen::RowMajor> res(data_nr,data_nr);
                res.setZero();

                if(keep_connect_) {
                    auto weightmap = get(edge_weight,g);
                    for(auto i=0; i<data_nr; ++i) {
                        for(auto j=i+1; j<data_nr; ++j) {
                            Edge e;
                            bool inserted;
                            boost::tie(e,inserted) = add_edge(i,j,g);
                            weightmap[e] = dis_m(i,j);
                        }
                    }
                    std::vector < Edge > spanning_tree;
                    kruskal_minimum_spanning_tree(g, std::back_inserter(spanning_tree));
                    for(auto e:spanning_tree) {
                        auto si = source(e,g);
                        auto ti = target(e,g);
                        res(si,ti) = 1;
                        res(ti,si) = 1;
                    }
                }
                for(auto i=0; i<data_nr; ++i) {
                    const Eigen::Tensor<T,1,Eigen::RowMajor> box_data0 = bboxes.chip(i,0);
                    for(auto j=i+1; j<data_nr; ++j) {
                        const Eigen::Tensor<T,1,Eigen::RowMajor> box_data1 = bboxes.chip(j,0);
                        const auto probs_corr = prob_correlation(probs,i,j);
                        if((labels(i) != labels(j)) &&
                            (probs_corr<threshold_[1]))
                            continue;
                        if((bboxes_jaccardv1(box_data0,box_data1)>threshold_[0])) {
                            res(i,j) = 1;
                            res(j,i) = 1;
                        }
                    }
                }
                return res;
            }
        template<typename _T>
            float prob_correlation(const _T& p0, int i,int j) {
                const auto nr = p0.dimension(1);
                float dis = 0.0f;
                for(auto k=0; k<nr; ++k) {
                    dis += (p0(i,k)*p0(j,k));
                }
                return dis;
            }
        template<typename _T>
            inline float distance(const _T& box0, const _T& box1) {
                float cx0,cy0,cx1,cy1;
                tie(cx0,cy0) = get_cxy(box0);
                tie(cx1,cy1) = get_cxy(box1);
                const auto dx = (cx0-cx1);
                const auto dy = (cy0-cy1);
                return sqrt(dx*dx+dy*dy);
            }
        template<typename _T>
        static inline std::pair<float,float> get_cxy(const _T& box) {
            return make_pair((box(1)+box(3))/2.0f,(box(0)+box(2))/2.0f);
        }
    private:
        vector<float> threshold_;
        bool keep_connect_ = false;
};
REGISTER_KERNEL_BUILDER(Name("AdjacentMatrixGeneratorByIouV2").Device(DEVICE_CPU).TypeConstraint<float>("T"), AdjacentMatrixGeneratorByIouV2Op<CPUDevice, float>);
REGISTER_KERNEL_BUILDER(Name("AdjacentMatrixGeneratorByIouV2").Device(DEVICE_CPU).TypeConstraint<double>("T"), AdjacentMatrixGeneratorByIouV2Op<CPUDevice, double>);

/*
 * 根据输入的boxes生成一个邻接矩阵
 * boxes: [N,4], ymin,xmin,ymax,xmax相对坐标
 * output:
 * matrix[N,N] 第i行,j列表示第i个点与第j个点之间的连接
 */
REGISTER_OP("AdjacentMatrixGeneratorByIouV3")
    .Attr("T: {float, double,int32}")
	.Attr("threshold:float")
    .Attr("keep_connect: bool=False")
    .Input("bboxes: T")
	.Output("matrix:int32")
	.SetShapeFn([](shape_inference::InferenceContext* c) {
		    auto box_nr = c->Dim(c->input(0),0);
			c->set_output(0, c->Matrix(box_nr,box_nr));
			return Status::OK();
			});

template <typename Device, typename T>
class AdjacentMatrixGeneratorByIouV3Op: public OpKernel {
    public:
        explicit AdjacentMatrixGeneratorByIouV3Op(OpKernelConstruction* context) : OpKernel(context) {
            OP_REQUIRES_OK(context, context->GetAttr("threshold", &threshold_));
            OP_REQUIRES_OK(context, context->GetAttr("keep_connect", &keep_connect_));
        }

        void Compute(OpKernelContext* context) override
        {
            const Tensor &_bboxes  = context->input(0);
            auto          bboxes   = _bboxes.template tensor<T,2>();

            OP_REQUIRES(context, _bboxes.dims() == 2, errors::InvalidArgument("bboxes data must be 2-dimensional"));
            const auto bboxes_nr    = _bboxes.dim_size(0);
            Eigen::Tensor<float,2,Eigen::RowMajor> dis_matrix(bboxes_nr,bboxes_nr);

            dis_matrix.setZero();

            if(keep_connect_) {
                for(auto i=0; i<bboxes_nr-1; ++i) {
                    const Eigen::Tensor<T,1,Eigen::RowMajor> box_data0 = bboxes.chip(i,0);

                    dis_matrix(i,i) = 0.;
                    for(auto j=i+1; j<bboxes_nr; ++j) {
                        const Eigen::Tensor<T,1,Eigen::RowMajor> box_data1 = bboxes.chip(j,0);
                        const auto dis = distance(box_data0,box_data1);
                        dis_matrix(i,j) = dis;
                        dis_matrix(j,i) = dis;
                    }
                }
            }

            auto res = make_graph(bboxes,dis_matrix);

            Tensor      *output_matrix = NULL;
            TensorShape  output_shape;
            const int    dims_2d[]     = {int(bboxes_nr),int(bboxes_nr)};

            TensorShapeUtils::MakeShape(dims_2d, 2, &output_shape);
            OP_REQUIRES_OK(context, context->allocate_output(0, output_shape, &output_matrix));

            auto output           = output_matrix->template tensor<int,2>();
            for(auto i=0; i<bboxes_nr; ++i) {
                output(i,i) = 0;
                for(auto j=i+1; j<bboxes_nr; ++j) {
                    output(i,j) = res(i,j);
                    output(j,i) = res(j,i);
                }
            }
        }
        template<typename _T,typename M>
            Eigen::Tensor<int,2,Eigen::RowMajor> make_graph(const _T& bboxes,const M& dis_m) {
                const auto data_nr = bboxes.dimension(0);
                Graph g(data_nr);
                Eigen::Tensor<int,2,Eigen::RowMajor> res(data_nr,data_nr);
                res.setZero();

                if(keep_connect_) {
                    auto weightmap = get(edge_weight,g);
                    for(auto i=0; i<data_nr; ++i) {
                        for(auto j=i+1; j<data_nr; ++j) {
                            Edge e;
                            bool inserted;
                            boost::tie(e,inserted) = add_edge(i,j,g);
                            weightmap[e] = dis_m(i,j);
                        }
                    }
                    std::vector < Edge > spanning_tree;
                    kruskal_minimum_spanning_tree(g, std::back_inserter(spanning_tree));
                    for(auto e:spanning_tree) {
                        auto si = source(e,g);
                        auto ti = target(e,g);
                        res(si,ti) = 1;
                        res(ti,si) = 1;
                    }
                }
                for(auto i=0; i<data_nr; ++i) {
                    const Eigen::Tensor<T,1,Eigen::RowMajor> box_data0 = bboxes.chip(i,0);
                    for(auto j=i+1; j<data_nr; ++j) {
                        const Eigen::Tensor<T,1,Eigen::RowMajor> box_data1 = bboxes.chip(j,0);
                        if((bboxes_jaccardv1(box_data0,box_data1)>threshold_)) {
                            res(i,j) = 1;
                            res(j,i) = 1;
                        }
                    }
                }
                return res;
            }
        template<typename _T>
            inline float distance(const _T& box0, const _T& box1) {
                float cx0,cy0,cx1,cy1;
                tie(cx0,cy0) = get_cxy(box0);
                tie(cx1,cy1) = get_cxy(box1);
                const auto dx = (cx0-cx1);
                const auto dy = (cy0-cy1);
                return sqrt(dx*dx+dy*dy);
            }
        template<typename _T>
        static inline std::pair<float,float> get_cxy(const _T& box) {
            return make_pair((box(1)+box(3))/2.0f,(box(0)+box(2))/2.0f);
        }
    private:
        float threshold_ = 0.3f;
        bool keep_connect_ = false;
};
REGISTER_KERNEL_BUILDER(Name("AdjacentMatrixGeneratorByIouV3").Device(DEVICE_CPU).TypeConstraint<float>("T"), AdjacentMatrixGeneratorByIouV3Op<CPUDevice, float>);
REGISTER_KERNEL_BUILDER(Name("AdjacentMatrixGeneratorByIouV3").Device(DEVICE_CPU).TypeConstraint<double>("T"), AdjacentMatrixGeneratorByIouV3Op<CPUDevice, double>);

/*
 * 根据输入的boxes生成一个邻接矩阵
 * boxes: [N,4], ymin,xmin,ymax,xmax相对坐标
 * output:
 * matrix[N,N] 第i行,j列表示第i个点与第j个点之间的连接
 */
REGISTER_OP("AdjacentMatrixGeneratorByIouV4")
    .Attr("T: {float, double,int32}")
	.Attr("threshold:float=0.3")
    .Attr("keep_connect: bool")
    .Input("bboxes: T")
    .Input("labels: int32")
	.Output("matrix:int32")
	.SetShapeFn([](shape_inference::InferenceContext* c) {
		    auto box_nr = c->Dim(c->input(0),0);
			c->set_output(0, c->Matrix(box_nr,box_nr));
			return Status::OK();
			});

template <typename Device, typename T>
class AdjacentMatrixGeneratorByIouV4Op: public OpKernel {
    public:
        explicit AdjacentMatrixGeneratorByIouV4Op(OpKernelConstruction* context) : OpKernel(context) {
            OP_REQUIRES_OK(context, context->GetAttr("threshold", &threshold_));
            OP_REQUIRES_OK(context, context->GetAttr("keep_connect", &keep_connect_));
        }

        void Compute(OpKernelContext* context) override
        {
            const Tensor &_bboxes  = context->input(0);
            const Tensor &_labels = context->input(1);
            auto          bboxes   = _bboxes.template tensor<T,2>();
            auto          labels = _labels.template tensor<int,1>();

            OP_REQUIRES(context, _bboxes.dims() == 2, errors::InvalidArgument("bboxes data must be 2-dimensional"));
            const auto bboxes_nr    = _bboxes.dim_size(0);
            Eigen::Tensor<float,2,Eigen::RowMajor> dis_matrix(bboxes_nr,bboxes_nr);

            dis_matrix.setZero();

            if(keep_connect_) {
                for(auto i=0; i<bboxes_nr-1; ++i) {
                    const Eigen::Tensor<T,1,Eigen::RowMajor> box_data0 = bboxes.chip(i,0);

                    dis_matrix(i,i) = 0.;
                    for(auto j=i+1; j<bboxes_nr; ++j) {
                        const Eigen::Tensor<T,1,Eigen::RowMajor> box_data1 = bboxes.chip(j,0);
                        const auto dis = distance(box_data0,box_data1);
                        dis_matrix(i,j) = dis;
                        dis_matrix(j,i) = dis;
                    }
                }
            }

            auto res = make_graph(bboxes,labels,dis_matrix);

            Tensor      *output_matrix = NULL;
            TensorShape  output_shape;
            const int    dims_2d[]     = {int(bboxes_nr),int(bboxes_nr)};

            TensorShapeUtils::MakeShape(dims_2d, 2, &output_shape);
            OP_REQUIRES_OK(context, context->allocate_output(0, output_shape, &output_matrix));

            auto output           = output_matrix->template tensor<int,2>();
            for(auto i=0; i<bboxes_nr; ++i) {
                output(i,i) = 0;
                for(auto j=i+1; j<bboxes_nr; ++j) {
                    output(i,j) = res(i,j);
                    output(j,i) = res(i,j);
                }
            }
        }
        template<typename _T,typename _TL,typename M>
            Eigen::Tensor<int,2,Eigen::RowMajor> make_graph(const _T& bboxes,const _TL& labels,const M& dis_m) {
                const auto data_nr = bboxes.dimension(0);
                Graph g(data_nr);
                Eigen::Tensor<int,2,Eigen::RowMajor> res(data_nr,data_nr);
                res.setZero();

                if(keep_connect_) {
                    auto weightmap = get(edge_weight,g);
                    for(auto i=0; i<data_nr; ++i) {
                        for(auto j=i+1; j<data_nr; ++j) {
                            Edge e;
                            bool inserted;
                            boost::tie(e,inserted) = add_edge(i,j,g);
                            weightmap[e] = dis_m(i,j);
                        }
                    }
                    std::vector < Edge > spanning_tree;
                    kruskal_minimum_spanning_tree(g, std::back_inserter(spanning_tree));
                    for(auto e:spanning_tree) {
                        auto si = source(e,g);
                        auto ti = target(e,g);
                        res(si,ti) = 1;
                        res(ti,si) = 1;
                    }
                }
                for(auto i=0; i<data_nr; ++i) {
                    const Eigen::Tensor<T,1,Eigen::RowMajor> box_data0 = bboxes.chip(i,0);
                    for(auto j=i+1; j<data_nr; ++j) {
                        const Eigen::Tensor<T,1,Eigen::RowMajor> box_data1 = bboxes.chip(j,0);
                        if((labels(i) != labels(j)))
                            continue;
                        if((bboxes_jaccardv1(box_data0,box_data1)>threshold_)) {
                            res(i,j) = 1;
                            res(j,i) = 1;
                        }
                    }
                }
                return res;
            }
        template<typename _T>
            inline float distance(const _T& box0, const _T& box1) {
                float cx0,cy0,cx1,cy1;
                tie(cx0,cy0) = get_cxy(box0);
                tie(cx1,cy1) = get_cxy(box1);
                const auto dx = (cx0-cx1);
                const auto dy = (cy0-cy1);
                return sqrt(dx*dx+dy*dy);
            }
        template<typename _T>
        static inline std::pair<float,float> get_cxy(const _T& box) {
            return make_pair((box(1)+box(3))/2.0f,(box(0)+box(2))/2.0f);
        }
    private:
        float threshold_=0.3f;
        bool keep_connect_ = false;
};
REGISTER_KERNEL_BUILDER(Name("AdjacentMatrixGeneratorByIouV4").Device(DEVICE_CPU).TypeConstraint<float>("T"), AdjacentMatrixGeneratorByIouV4Op<CPUDevice, float>);
REGISTER_KERNEL_BUILDER(Name("AdjacentMatrixGeneratorByIouV4").Device(DEVICE_CPU).TypeConstraint<double>("T"), AdjacentMatrixGeneratorByIouV4Op<CPUDevice, double>);


#ifndef INCLUDED_search_HackPack_hh
#define INCLUDED_search_HackPack_hh

#include "scheme/objective/storage/TwoBodyTable.hh"

	#include <random>
	#include <boost/foreach.hpp>

namespace scheme { namespace search {

template< typename Float >
inline
bool
pass_metropolis(
	Float const & temperature,
	Float const & deltaE,
	Float const & random_uniform
)
{
	if ( deltaE < 0 ) {
		return true;
	} else { //evaluate prob of substitution
		Float lnprob = deltaE / temperature;
		if ( lnprob < 10.0 ) {
			Float probability = std::exp(-lnprob);
			if ( probability > random_uniform ) return true;
		}
	}
	return false;
 }

struct HackPackOpts
{
	int   pack_n_iters = 1;
	float pack_iter_mult = 2.0;
	float hbond_weight = 2.0;
	float upweight_iface = 1.0;
	float upweight_multi_hbond = 0.0;
	float min_hb_quality_for_satisfaction = -0.6;
	bool  use_extra_rotamers = true;
	int   always_available_rotamers_level = 0;
	bool  packing_use_rif_rotamers = true;
	bool  add_native_scaffold_rots_when_packing = false;
	float rotamer_inclusion_threshold = -0.5;
	float rotamer_onebody_inclusion_threshold = 30.0;//5
	bool  init_with_best_1be_rots = true;
	float user_rotamer_bonus_constant = -2; //-2
	float user_rotamer_bonus_per_chi = -2; // 2
	bool  rescore_rots_before_insertion = true;		// this isn't a real flag, gets used in MyScoreBBActorVsRif
};
inline
std::ostream & operator<<( std::ostream & out, HackPackOpts const & hpo ){
	out << "HackPackOpts:"
		<< "\n  pack_iter_mult " << hpo.pack_iter_mult
		<< "\n  hbond_weight " << hpo.hbond_weight
		<< "\n  upweight_iface " << hpo.upweight_iface
		<< "\n  upweight_multi_hbond " << hpo.upweight_multi_hbond
		<< "\n  use_extra_rotamers " << hpo.use_extra_rotamers
		<< "\n  always_available_rotamers_level " << hpo.always_available_rotamers_level
		<< "\n  packing_use_rif_rotamers " << hpo.packing_use_rif_rotamers
		<< "\n  add_native_scaffold_rots_when_packing " << hpo.add_native_scaffold_rots_when_packing
		<< "\n  rotamer_inclusion_threshold " << hpo.rotamer_inclusion_threshold
		<< "\n  rotamer_onebody_inclusion_threshold " << hpo.rotamer_onebody_inclusion_threshold
		<< "\n  init_with_best_1be_rots " << hpo.init_with_best_1be_rots
		<< "\n  user_rotamer_bonus_constant " << hpo.user_rotamer_bonus_constant
		<< "\n  user_rotamer_bonus_per_chi" << hpo.user_rotamer_bonus_per_chi
		<< "\n  rescore_rots_before_insertion " << hpo.rescore_rots_before_insertion

	    << std::endl;
	return out;
}

struct HackPack
{
	typedef std::pair<int32_t,float> RotInfo;
	typedef std::pair< int32_t, std::vector< RotInfo > > RotInfos;
	int nres_; // total res currently stored
	std::vector< RotInfos > res_rots_; // iresapp + list of irottwob/onebody pairs
	std::vector< std::pair<int32_t,int32_t> > rot_list_; // list of ireslocal / irotlocal pairs
	std::vector< int32_t > current_rots_, trial_best_rots_, global_best_rots_; // current rotamer in local numbering
	std::mt19937 rng;
	shared_ptr<::scheme::objective::storage::TwoBodyTable<float>> twob_;
	float score_, trial_best_score_, global_best_score_;
	HackPackOpts opts_;
	int32_t default_rot_num_;
	HackPack(
		// ::scheme::objective::storage::TwoBodyTable<float> const & twob,
		HackPackOpts const & opts,
		int32_t default_rot_num,
		int seed_offset = 0 // mainly for threads
	)
		: nres_(0)
		, rng( time(0)+seed_offset )
		// , twob_( twob )
		, opts_(opts)
		, default_rot_num_( default_rot_num )
	{}

	void reinitialize(
		shared_ptr<::scheme::objective::storage::TwoBodyTable<float> > twob ){

		// Brian

		twob_ = twob;
		ALWAYS_ASSERT( twob_->nrot_ > 0 );
		ALWAYS_ASSERT( twob_->nres_ > 0 );
		ALWAYS_ASSERT( twob_->nrot_ < 99999 );
		ALWAYS_ASSERT( twob_->nres_ < 99999 );

		////////////////////

		// todo: always add native rotamer and ALA/GLY as appropriate
		// should hopefully not deallocate memory
		rot_list_.clear();
		BOOST_FOREACH( RotInfos & rotinfos, res_rots_ ){
			rotinfos.first = -1;
			rotinfos.second.clear();
		}
		nres_ = 0;
	}
	template< class Int >
	bool using_rotamer( Int const & ires, Int const & irotglobal )
	{
		ALWAYS_ASSERT( 0 <= ires && ires < twob_->all2sel_.shape()[0] );
		ALWAYS_ASSERT( 0 <= irotglobal && irotglobal < twob_->all2sel_.shape()[1] );
		return twob_->all2sel_[ires][irotglobal] >= 0;
	}
	template< class Int >
	void add_tmp_rot( int const & ires, Int const & irotglobal, float const & onebody_e, bool allow_high_energy=false )
	{
		// #pragma omp critical
		// {
		// 	std::cout << "================= add_tmp_rot " << ires << " " << irotglobal << " " << onebody_e << std::endl;
		// 	print_rot_info();
		// }
		if( onebody_e > 10.0 && ! allow_high_energy ){
			return;
		}
		ALWAYS_ASSERT( 0 <= ires && ires < twob_->all2sel_.shape()[0] );
		ALWAYS_ASSERT( 0 <= irotglobal && irotglobal < twob_->all2sel_.shape()[1] );
		int32_t irotlocal = twob_->all2sel_[ires][irotglobal];
		// std::cout << "irotlocal" << irotlocal << std::endl;
		if( irotlocal >= 0 ){
			if( nres_==0 || res_rots_.at(nres_-1).first != ires ){
				++nres_;
				if( res_rots_.size() < nres_ ) res_rots_.resize( nres_ );
				res_rots_.at(nres_-1).first = ires;
				// always allow ALA as an option:
				int alarot = twob_->all2sel_[ires][ default_rot_num_ ];
				if( alarot >= 0 ) {
					rot_list_.push_back( std::make_pair( nres_-1, res_rots_.at(nres_-1).second.size() ) );
					res_rots_.at(nres_-1).second.push_back( RotInfo( alarot, 0.0 ) );
				}
			}
			rot_list_.push_back( std::make_pair( nres_-1, res_rots_.at(nres_-1).second.size() ) );
			res_rots_.at(nres_-1).second.push_back( RotInfo( irotlocal, onebody_e ) );
		} else {
			// std::cout << "Error!!!: Rotamer not in twobody energies " << irotglobal << " " << ires << std::endl;
			// static bool missingrotwarn = true;
			// if( missingrotwarn ){
			// 	#ifdef USE_OPENMP
			// 	#pragma omp critical
			// 	#endif
			// 	{
			// 		std::cout << "WARNING: requested rotamer not in TwoBodyTable, probably has bad 1BE, subsequent warnings will be skipped: "
			// 		          << " irotglobal: " << irotglobal << std::endl;
			// 		missingrotwarn = false;
			// 		// std::exit(-1);
			// 	}
			// }
		}
	}

	float
	compute_energy_full(
		std::vector< int32_t > const & rots
	) const {
			// using namespace ObjexxFCL::format;
		// for( int i = 1; i < nres_; ++i ){
		// 		int   const iresglobal = res_rots_[i].first;
		// 		int   const irottwob   = res_rots_[i].second[ rots[i] ].first;
		// 		float const ionebody   = res_rots_[i].second[ rots[i] ].second;
		// 		int irotglobal = twob_->sel2all_[ iresglobal ][ irottwob ];
		// 		std::cout << "ONEBODY " << iresglobal << " " << rot_index_.resname(irotglobal) << irotglobal << " " << ionebody << std::endl;
		// 	}
		float score = 0.0;
		for( int ires = 0; ires < nres_; ++ires ){
				assert( 0 <= ires && ires < rots.size() );
			int32_t const irotlocal = rots.at(ires);
				assert( 0 <= ires && ires < res_rots_.size() );
				// std::cout << ires << " " << irotlocal << " " << res_rots_[ires].second.size() << std::endl;
				assert( 0 <= irotlocal && irotlocal < res_rots_.at(ires).second.size() );
			int32_t const iresglobal = res_rots_.at(ires).first;
			int32_t const irottwob   = res_rots_.at(ires).second.at( irotlocal ).first;
			float const ionebody     = res_rots_.at(ires).second.at( irotlocal ).second;
			score += ionebody;
			for( int jres = 0; jres < ires; ++jres ){
					assert( 0 <= jres && jres < rots.size() );
				int32_t const jrotlocal = rots.at(jres);
					assert( 0 <= jres && jres < res_rots_.size() );
					assert( 0 <= jrotlocal && jrotlocal < res_rots_.at(jres).second.size() );
				int32_t const jresglobal = res_rots_.at(jres).first;
				int32_t const jrottwob   = res_rots_.at(jres).second.at( jrotlocal ).first;
				float const jonebody     = res_rots_.at(jres).second.at( jrotlocal ).second;
				float const twobodye     = twob_->twobody_rotlocalnumbering( iresglobal, jresglobal, irottwob, jrottwob );
				score += twobodye;
					// int irotglobal = twob_->sel2all_[ iresglobal ][ irottwob ];
					// int jrotglobal = twob_->sel2all_[ jresglobal ][ jrottwob ];
					// std::cout << "TWOBODY "
					//           << I(2,ires) << "/" << I(2,jres) << " "
					//           << I(3,iresglobal) << "/" << I(3,jresglobal) << " "
					//           << rot_index_.resname(irotglobal) << I(3,irottwob) << "/"
					//           << rot_index_.resname(jrotglobal) << I(3,jrottwob) << " "
					//           << F(7,3,twobodye) << std::endl;
			}
		}
		return score;
	}
	float
	compute_energy_delta(
		std::vector< int32_t > const & rots,
		int32_t const & ilres,
		int32_t const & ilrotnew
	) const {
		// using namespace ObjexxFCL::format;
		float delta = 0;
		int32_t const ilrotold = rots.at(ilres);
		int32_t const iresglobal  = res_rots_.at(ilres).first;
		int32_t const irottwobold = res_rots_.at(ilres).second.at( rots.at(ilres) ).first;
		float   const ionebodyold = res_rots_.at(ilres).second.at( rots.at(ilres) ).second;
		int32_t const irottwobnew = res_rots_.at(ilres).second.at(  ilrotnew   ).first;
		float   const ionebodynew = res_rots_.at(ilres).second.at(  ilrotnew   ).second;
		delta -= ionebodyold;
		delta += ionebodynew;
		for( int j = 0; j < nres_; ++j ){
			if( j == ilres ) continue;
			int32_t const jresglobal = res_rots_.at(j).first;
			int32_t const jrottwob   = res_rots_.at(j).second.at( rots.at(j) ).first;
			float   const jonebody   = res_rots_.at(j).second.at( rots.at(j) ).second;
			float   const twobodyeold = twob_->twobody_rotlocalnumbering( iresglobal, jresglobal, irottwobold, jrottwob );
			float   const twobodyenew = twob_->twobody_rotlocalnumbering( iresglobal, jresglobal, irottwobnew, jrottwob );
			delta -= twobodyeold;
			delta += twobodyenew;
			// std::cout << "DELTA TWOB"
			//           << " ires "    << I(2,ilres   ) << "/" << I(3,iresglobal )
			//           << " jres "    << I(2,j       ) << "/" << I(3,jresglobal)
			//           << " irotold " << I(2,ilrotold) << "/" << I(3,irottwobold)
			//           << " irotnew " << I(2,ilrotnew) << "/" << I(3,irottwobnew)
			//           << " jrot "    << I(2,rots[j] ) << "/" << I(3,jrottwob)
			//           << " e " << F(7,3,twobodyeold)  << " " << F(7,3,twobodyenew)
			//           << std::endl;
		}
		if( -123460.0 > delta || delta > 123460.0 ){ // 10x energy cap per-rottable entry
			bool throwerr = false;
			#ifdef USE_OPENMP
			#pragma omp critical
			#endif
			{
				std::cout << "crazy energy delta, indicates a problem.... res (scene numbering) = " << res_rots_.at(ilres).first << " " << delta
					<< " 1body-old: " << ionebodyold << " 1body-new: " << ionebodynew << std::endl;
				static int errcount = 0;
				if( ++errcount > 10 ) throwerr = true;
			}
			if( throwerr ) throw std::logic_error("too many crazy energy deltas");
			return 9e9;
		}
		return delta;
	}
	int32_t randres()
	{
		std::uniform_int_distribution<> rand_idx(0,nres_-1);
		return rand_idx( rng );
	}
	int32_t randrot( int32_t const & ires )
	{
		assert( res_rots_.at(ires).second.size() > 0 );
		// if( res_rots_[ires].second.size() == 1 ) return 0;
		std::uniform_int_distribution<> rand_idx(0,res_rots_.at(ires).second.size()-1);
		return rand_idx( rng );
	}
	void randrot_not_current_uniform_res( int32_t & ires, int32_t & irot )
	{
		for( int k = 0; k < 1000; ++k ){
			ires = randres();
			if( res_rots_.at(ires).second.size() > 1 ) break;
		}
		ALWAYS_ASSERT( 0 <= ires && ires < nres_ );
		ALWAYS_ASSERT( res_rots_.at(ires).second.size() > 1 );

		for( int k = 0; k < 1000; ++k ){
			irot = randrot( ires );
			if( irot != current_rots_.at(ires) ) return;
		}
		ALWAYS_ASSERT( 0 <= irot && irot < res_rots_.at(ires).second.size() );
	}
	void randrot_not_current_uniform_rot( int32_t & ires, int32_t & irot )
	{
		std::uniform_int_distribution<> rand_idx(0,rot_list_.size()-1);
		for( int i = 0; i < 1000; ++i ){
			int const irand = rand_idx(rng);
			ires = rot_list_.at(irand).first;
			irot = rot_list_.at(irand).second;
			if( res_rots_.at(ires).second.size() > 1 && irot != current_rots_.at(ires) ) return;
		}
		std::cerr << "randrot_not_current_uniform_rot FAIL" << std::endl;
		std::exit(-1);
	}
	void random_substitution_test( float temperature ){
		std::uniform_real_distribution<float> runif(0,1);

		int32_t ires, irot;
		randrot_not_current_uniform_rot( ires, irot );

		float delta = compute_energy_delta( current_rots_, ires, irot );
		// {
		// 	// std::cout << "SUB: " << ires << " " << irot << " " << res_rots_[ires].first << std::endl;
		// 	// std::cout << "==================================== old ==========================================" << std::endl;
		// 	// for( int k = 0; k < current_rots_.size(); ++k ) std::cout << "currot " << k << " " << current_rots_[k] << std::endl;
		// 	float curfull = compute_energy_full( current_rots_ );
		// 	int32_t tmp = current_rots_[ires];
		// 	current_rots_[ires] = irot;
		// 	// std::cout << "==================================== new ==========================================" << std::endl;
		// 	// for( int k = 0; k < current_rots_.size(); ++k ) std::cout << "currot " << k << " " << current_rots_[k] << std::endl;
		// 	float newfull = compute_energy_full( current_rots_ );
		// 	current_rots_[ires] = tmp;
		// 	float test_delta = newfull-curfull;
		// 	// std::cout << delta << " " << test_delta << " " << curfull << " " << newfull << std::endl;
		// 	if( fabs(delta-test_delta) > 0.01 ){
		// 		#pragma omp critical
		// 		std::cout << "fabs(delta-test_delta) " << fabs(delta-test_delta) << " " << delta << " " << test_delta << std::endl;
		// 	}
		// }

		if( pass_metropolis( temperature, delta, runif(rng) ) ){
			current_rots_.at(ires) = irot;
			score_ += delta;
			if( score_ < trial_best_score_ ){
				trial_best_score_ = score_;
				trial_best_rots_ = current_rots_;
			}
		}
	}
	void recover_trial_best(){
		score_ = trial_best_score_;
		current_rots_ = trial_best_rots_;
	}
	void assign_random_rots(){
		current_rots_.resize( nres_ );
		for( int ires = 0; ires < nres_; ++ires ){
			current_rots_.at(ires) = randrot(ires);
			// std::cout << "starting rot " << ires << " " << current_rots_[ires] << std::endl;
			assert( 0 <= current_rots_.at(ires) && current_rots_.at(ires) < res_rots_.at(ires).second.size() );
		}
	}
	void assign_best_obe_rots(){
		current_rots_.resize( nres_ );
		for( int ilres = 0; ilres < nres_; ++ilres ){
			float best = 9e9;
			for( int ilrot = 0; ilrot < res_rots_.at(ilres).second.size(); ++ilrot ){
				float obe = res_rots_.at(ilres).second.at(ilrot).second;
				if( obe < best ){
					best = obe;
					current_rots_.at(ilres) = ilrot;
				}
			}
		}
	}
	void assign_initial_rots(){
		if( opts_.init_with_best_1be_rots ){
			assign_best_obe_rots();
		} else {
			assign_random_rots();
		}
	}
	void fill_result_rots( std::vector<std::pair<int32_t,int32_t> > & result_rots ){
		result_rots.clear();
		for( int i = 0; i < nres_; ++i ){
			int32_t iresglobal = res_rots_.at(i).first;
			int32_t irottwob   = res_rots_.at(i).second.at( global_best_rots_.at(i) ).first;
			ALWAYS_ASSERT( 0 <= iresglobal && iresglobal < twob_->sel2all_.shape()[0] );
			if( irottwob < 0 ){
				#ifdef USE_OPENMP
				#pragma omp critical
				#endif
				{
					std::cout << "local rot num: " << i << " iresglobal: " << iresglobal << " irottwob: " << irottwob << std::endl;
					print_rot_info();
					std::cerr << "debug...." << std::endl;
					std::exit(-1);
				}
			}
			ALWAYS_ASSERT( 0 <= irottwob   && irottwob   < twob_->sel2all_.shape()[1] );
			int32_t irotglobal = twob_->sel2all_[ iresglobal ][ irottwob ];
			result_rots.push_back( std::make_pair( iresglobal, irotglobal ) );
		}
	}
	float pack( std::vector<std::pair<int32_t,int32_t> > & result_rots )
	{
		assert( res_rots_.size() >= nres_ );
		for( int i = 0; i < nres_; ++i ){
			//std::cout << i << " " << res_rots_[i].second.size() << std::endl;
			assert( res_rots_.at(i).second.size() > 0 );
		}

		assign_initial_rots();

		uint64_t nchoices = 1;
		for( int ires = 0; ires < nres_; ++ires ){
			ALWAYS_ASSERT_MSG( res_rots_.at(ires).second.size() > 0, "no rotamers at designable position!" );
			nchoices *= res_rots_.at(ires).second.size();
			if( nchoices > 1000000000000000ull ) break;
		}
		if( nchoices == 1 ){
            global_best_rots_ = current_rots_;
			fill_result_rots( result_rots );
			score_ = compute_energy_full( current_rots_ );
			return score_;
		}

		int const ntrials = opts_.pack_n_iters;
		int const pack_iters = opts_.pack_iter_mult * rot_list_.size()+10;
		global_best_score_ = 9e9;
		for( int k = 0; k < ntrials; ++k ){
			if( k > 0 ) assign_initial_rots();
			score_ = compute_energy_full( current_rots_ );
			trial_best_score_ = score_;
			trial_best_rots_ = current_rots_;
			for( int i = 0; i < pack_iters; ++i ) random_substitution_test( 100.0  ); recover_trial_best();
			for( int i = 0; i < pack_iters; ++i ) random_substitution_test(  33.0  ); recover_trial_best();
			for( int i = 0; i < pack_iters; ++i ) random_substitution_test(  10.0  ); recover_trial_best();
			for( int i = 0; i < pack_iters; ++i ) random_substitution_test(   3.3  ); recover_trial_best();
			for( int i = 0; i < pack_iters; ++i ) random_substitution_test(   1.0  ); recover_trial_best();
			for( int i = 0; i < pack_iters; ++i ) random_substitution_test(   0.33 ); recover_trial_best();
			for( int i = 0; i < pack_iters; ++i ) random_substitution_test(   0.1  ); recover_trial_best();
			for( int i = 0; i < pack_iters; ++i ) random_substitution_test(   0.0  ); recover_trial_best();
			if( score_ < global_best_score_ ){
				global_best_score_ = score_;
				global_best_rots_ = current_rots_;
			}
		}

		fill_result_rots( result_rots );

		return score_;
	}

	void print_rot_info() const {
		// using namespace ObjexxFCL::format;

		std::cout << "====== res_rots_ =======" << std::endl;
		for( int ilres = 0; ilres < this->nres_; ++ilres ){
			int ires = this->res_rots_[ilres].first;
			// std::cout << I(3,ilres) << " res " << I(3,ires);
			std::cout << ilres << " res " << ires;
			BOOST_FOREACH( typename HackPack::RotInfo const & rinfo, this->res_rots_[ilres].second ){
				std::cout << " " << rinfo.first << "/" << rinfo.second;
			}
			std::cout << std::endl;
		}
		std::cout << "====== rot_list_ =======" << std::endl;
		for( int k = 0; k < this->rot_list_.size(); ++k ){
			int32_t ilres = this->rot_list_[k].first;
			int32_t ilrot = this->rot_list_[k].second;
			// std::cout << I(3,k) << " res: " << I(2,ilres) << "/" << I(3,this->res_rots_[ilres].first)
			//           << " rot: " << I(2,ilrot) << "/" << I(3,this->res_rots_[ilres].second[ilrot].first)
			//           << " score: " << this->res_rots_[ilres].second[ilrot].second << std::endl;
			std::cout << k << " res: " << ilres << "/" << this->res_rots_[ilres].first
			          << " rot: " << ilrot << "/" << this->res_rots_[ilres].second[ilrot].first
			          << " score: " << this->res_rots_[ilres].second[ilrot].second << std::endl;
		}
	}
};

} // namespace search
} // namespace scheme

#endif


#define BOOST_TEST_MODULE "test_3SPN2BasePairInteraction_potential"

#ifdef BOOST_TEST_DYN_LINK
#include <boost/test/unit_test.hpp>
#else
#include <boost/test/included/unit_test.hpp>
#endif

#include <mjolnir/forcefield/3SPN2/ThreeSPN2BasePairPotential.hpp>

using parameters_to_test_double = std::tuple<
    mjolnir::ThreeSPN2BasePairGlobalPotentialParameter<double>,
    mjolnir::ThreeSPN2CBasePairGlobalPotentialParameter<double>
>;

BOOST_AUTO_TEST_CASE_TEMPLATE(f_3SPN2_BasePairInteraction_double,
        ParameterSet, parameters_to_test_double)
{
    mjolnir::LoggerManager::set_default_logger(
            "test_3spn2_base_base_interaction_potential.log");

    using traits_type    = mjolnir::SimulatorTraits<double, mjolnir::UnlimitedBoundary>;
    using real_type      = traits_type::real_type;
    using potential_type = mjolnir::ThreeSPN2BasePairPotential<real_type>;
    using parameter_list_type = mjolnir::ThreeSPN2BasePairParameterList<traits_type>;
    using parameter_type = typename parameter_list_type::parameter_type;
    using base_kind      = mjolnir::parameter_3SPN2::base_kind  ;

    {
        using unit_type = mjolnir::unit::constants<real_type>;
        using phys_type = mjolnir::physics::constants<real_type>;
        const std::string energy = "kcal/mol";
        const std::string length = "angstrom";
        phys_type::set_kB(phys_type::kB() * (unit_type::J_to_cal() / 1000.0) *
                          unit_type::avogadro_constant());
        phys_type::set_eps0(phys_type::eps0() * (1000.0 / unit_type::J_to_cal()) /
                            unit_type::avogadro_constant());
        phys_type::set_energy_unit(energy);

        phys_type::set_eps0(phys_type::eps0() / unit_type::m_to_angstrom());

        phys_type::set_m_to_length(unit_type::m_to_angstrom());
        phys_type::set_length_to_m(unit_type::angstrom_to_m());

        phys_type::set_L_to_volume(1e-3 * std::pow(unit_type::m_to_angstrom(), 3));
        phys_type::set_volume_to_L(1e+3 * std::pow(unit_type::angstrom_to_m(), 3));

        phys_type::set_length_unit(length);
    }

    constexpr std::size_t N = 1000;
    constexpr real_type   h = 1e-6;
    constexpr real_type  pi = mjolnir::math::constants<real_type>::pi();

    parameter_list_type parameter_list(ParameterSet{}, {
            {1, parameter_type{base_kind::A, 0}},
            {3, parameter_type{base_kind::T, 2}}
        }, {},
        parameter_list_type::ignore_molecule_type("Nothing"),
        parameter_list_type::ignore_group_type({})
        );

    const real_type theta_0   = 0.5  * pi;
    const real_type theta_min = 0.01 * pi;
    const real_type theta_max = 0.99 * pi;
    const real_type dtheta    = (theta_max - theta_min) / N;

    const auto K         = parameter_list.K_BP();
    const auto pi_over_K = parameter_list.pi_over_K_BP();
    const potential_type potential;

    for(std::size_t i=0; i<N; ++i)
    {
        const real_type theta = theta_min + i * dtheta;
        const real_type f1  = potential.f(K, pi_over_K, theta + h, theta_0);
        const real_type f2  = potential.f(K, pi_over_K, theta - h, theta_0);
        const real_type df_numeric  = (f1 - f2) / (2 * h);
        const real_type df_analytic = potential.df(K, pi_over_K, theta, theta_0);

        if((f1 == 0.0 || f2 == 0.0) && !(f1 == 0.0 && f2 == 0.0))
        {
            // the numeric differentiation becomes unstable.
        }
        else
        {
            BOOST_TEST(df_numeric == df_analytic, boost::test_tools::tolerance(h));
        }
    }
}

BOOST_AUTO_TEST_CASE_TEMPLATE(U_rep_3SPN2_BaseBaseInteraction_double,
        ParameterSet, parameters_to_test_double)
{
    mjolnir::LoggerManager::set_default_logger(
            "test_3spn2_base_base_interaction_potential.log");

    using traits_type    = mjolnir::SimulatorTraits<double, mjolnir::UnlimitedBoundary>;
    using real_type      = traits_type::real_type;
    using potential_type = mjolnir::ThreeSPN2BasePairPotential<real_type>;
    using parameter_list_type = mjolnir::ThreeSPN2BasePairParameterList<traits_type>;
    using parameter_type = typename parameter_list_type::parameter_type;
    using base_kind      = mjolnir::parameter_3SPN2::base_kind;
    using base_pair_kind = mjolnir::parameter_3SPN2::base_pair_kind;
    {
        using unit_type = mjolnir::unit::constants<real_type>;
        using phys_type = mjolnir::physics::constants<real_type>;
        const std::string energy = "kcal/mol";
        const std::string length = "angstrom";
        phys_type::set_kB(phys_type::kB() * (unit_type::J_to_cal() / 1000.0) *
                          unit_type::avogadro_constant());
        phys_type::set_eps0(phys_type::eps0() * (1000.0 / unit_type::J_to_cal()) /
                            unit_type::avogadro_constant());
        phys_type::set_energy_unit(energy);

        phys_type::set_eps0(phys_type::eps0() / unit_type::m_to_angstrom());

        phys_type::set_m_to_length(unit_type::m_to_angstrom());
        phys_type::set_length_to_m(unit_type::angstrom_to_m());

        phys_type::set_L_to_volume(1e-3 * std::pow(unit_type::m_to_angstrom(), 3));
        phys_type::set_volume_to_L(1e+3 * std::pow(unit_type::angstrom_to_m(), 3));

        phys_type::set_length_unit(length);
    }

    constexpr std::size_t N = 1000;
    constexpr real_type   h = 1e-6;

    parameter_list_type parameter_list(ParameterSet{}, {
            {1, parameter_type{base_kind::A, 0}},
            {3, parameter_type{base_kind::T, 2}}
        }, {},
        parameter_list_type::ignore_molecule_type("Nothing"),
        parameter_list_type::ignore_group_type({})
        );

    const real_type r_min =  3.0;
    const real_type r_max = 12.0;
    const real_type dr    = (r_max - r_min) / N;

    const auto alpha   = parameter_list.alpha  (base_pair_kind::AT);
    const auto epsilon = parameter_list.epsilon(base_pair_kind::AT);
    const auto r0      = parameter_list.r0     (base_pair_kind::AT);

    potential_type potential;

    for(std::size_t i=0; i<N; ++i)
    {
        const auto r         = r_min + i * dr;
        const real_type pot1 = potential.U_rep(epsilon, alpha, r + h, r0);
        const real_type pot2 = potential.U_rep(epsilon, alpha, r - h, r0);
        const real_type df_numeric  = (pot1 - pot2) / (2 * h);
        const real_type df_analytic = potential.dU_rep(epsilon, alpha, r, r0);

        if(pot1 != 0.0 && pot2 == 0.0)
        {
            // the numeric differentiation becomes unstable.
        }
        else if(df_analytic == 0.0)
        {
            // derivative of repulsive part of morse potential takes zero at
            // r > r_cutoff.
            BOOST_TEST(potential.U_rep(epsilon, alpha, r, r0) == 0.0,
                       boost::test_tools::tolerance(h));
        }
        else
        {
            BOOST_TEST(df_numeric == df_analytic, boost::test_tools::tolerance(h));
        }
    }
}

BOOST_AUTO_TEST_CASE_TEMPLATE(U_attr_3SPN2_BaseBaseInteraction_double,
        ParameterSet, parameters_to_test_double)
{
    mjolnir::LoggerManager::set_default_logger(
            "test_3spn2_base_base_interaction_potential.log");

    using traits_type    = mjolnir::SimulatorTraits<double, mjolnir::UnlimitedBoundary>;
    using real_type      = traits_type::real_type;
    using potential_type = mjolnir::ThreeSPN2BasePairPotential<real_type>;
    using parameter_list_type = mjolnir::ThreeSPN2BasePairParameterList<traits_type>;
    using parameter_type = typename parameter_list_type::parameter_type;
    using base_kind      = mjolnir::parameter_3SPN2::base_kind;
    using base_pair_kind   = mjolnir::parameter_3SPN2::base_pair_kind;
    {
        using unit_type = mjolnir::unit::constants<real_type>;
        using phys_type = mjolnir::physics::constants<real_type>;
        const std::string energy = "kcal/mol";
        const std::string length = "angstrom";
        phys_type::set_kB(phys_type::kB() * (unit_type::J_to_cal() / 1000.0) *
                          unit_type::avogadro_constant());
        phys_type::set_eps0(phys_type::eps0() * (1000.0 / unit_type::J_to_cal()) /
                            unit_type::avogadro_constant());
        phys_type::set_energy_unit(energy);

        phys_type::set_eps0(phys_type::eps0() / unit_type::m_to_angstrom());

        phys_type::set_m_to_length(unit_type::m_to_angstrom());
        phys_type::set_length_to_m(unit_type::angstrom_to_m());

        phys_type::set_L_to_volume(1e-3 * std::pow(unit_type::m_to_angstrom(), 3));
        phys_type::set_volume_to_L(1e+3 * std::pow(unit_type::angstrom_to_m(), 3));

        phys_type::set_length_unit(length);
    }

    constexpr std::size_t N = 1000;
    constexpr real_type   h = 1e-6;

    parameter_list_type parameter_list(ParameterSet{}, {
            {1, parameter_type{base_kind::A, 0}},
            {3, parameter_type{base_kind::T, 2}}
        }, {},
        parameter_list_type::ignore_molecule_type("Nothing"),
        parameter_list_type::ignore_group_type({})
        );

    const real_type r_min =  3.0; // avoiding the long tail around cutoff;
    const real_type r_max = 12.0; // not take numerical error around zero into account
    const real_type dr    = (r_max - r_min) / N;

    const auto alpha   = parameter_list.alpha  (base_pair_kind::AT);
    const auto epsilon = parameter_list.epsilon(base_pair_kind::AT);
    const auto r0      = parameter_list.r0     (base_pair_kind::AT);

    potential_type potential;

    for(std::size_t i=0; i<N; ++i)
    {
        const auto r         = r_min + i * dr;
        const real_type pot1 = potential.U_attr(epsilon, alpha, r + h, r0);
        const real_type pot2 = potential.U_attr(epsilon, alpha, r - h, r0);
        const real_type df_numeric  = (pot1 - pot2) / (2 * h);
        const real_type df_analytic = potential.dU_attr(epsilon, alpha, r, r0);

        if(pot1 != 0.0 && pot2 == 0.0)
        {
            // the numeric differentiation becomes unstable.
        }
        else if(df_analytic == 0.0)
        {
            // dU == 0 means that r is in `U == -epsilon` part.
            BOOST_TEST(potential.U_attr(epsilon, alpha, r, r0) == -epsilon,
                       boost::test_tools::tolerance(h));
        }
        else
        {
            BOOST_TEST(df_numeric == df_analytic, boost::test_tools::tolerance(1e-5));
        }
    }
}

BOOST_AUTO_TEST_CASE_TEMPLATE(U_dU_attr_3SPN2_BaseBaseInteraction_double,
        ParameterSet, parameters_to_test_double)
{
    mjolnir::LoggerManager::set_default_logger(
            "test_3spn2_base_base_interaction_potential.log");

    using traits_type    = mjolnir::SimulatorTraits<double, mjolnir::UnlimitedBoundary>;
    using real_type      = traits_type::real_type;
    using potential_type = mjolnir::ThreeSPN2BasePairPotential<real_type>;
    using parameter_list_type = mjolnir::ThreeSPN2BasePairParameterList<traits_type>;
    using parameter_type = typename parameter_list_type::parameter_type;
    using base_kind      = mjolnir::parameter_3SPN2::base_kind;
    using base_pair_kind   = mjolnir::parameter_3SPN2::base_pair_kind  ;
    {
        using unit_type = mjolnir::unit::constants<real_type>;
        using phys_type = mjolnir::physics::constants<real_type>;
        const std::string energy = "kcal/mol";
        const std::string length = "angstrom";
        phys_type::set_kB(phys_type::kB() * (unit_type::J_to_cal() / 1000.0) *
                          unit_type::avogadro_constant());
        phys_type::set_eps0(phys_type::eps0() * (1000.0 / unit_type::J_to_cal()) /
                            unit_type::avogadro_constant());
        phys_type::set_energy_unit(energy);

        phys_type::set_eps0(phys_type::eps0() / unit_type::m_to_angstrom());

        phys_type::set_m_to_length(unit_type::m_to_angstrom());
        phys_type::set_length_to_m(unit_type::angstrom_to_m());

        phys_type::set_L_to_volume(1e-3 * std::pow(unit_type::m_to_angstrom(), 3));
        phys_type::set_volume_to_L(1e+3 * std::pow(unit_type::angstrom_to_m(), 3));

        phys_type::set_length_unit(length);
    }

    constexpr std::size_t N = 1000;
    constexpr real_type   h = 1e-8;

    parameter_list_type parameter_list(ParameterSet{}, {
            {1, parameter_type{base_kind::A, 0}},
            {3, parameter_type{base_kind::T, 2}}
        }, {},
        parameter_list_type::ignore_molecule_type("Nothing"),
        parameter_list_type::ignore_group_type({})
        );

    potential_type potential;

    for(const auto bp : {base_pair_kind::AT, base_pair_kind::TA,
                         base_pair_kind::GC, base_pair_kind::CG})
    {
        const real_type r_min =  3.0;
        const real_type r_max = 18.0;
        const real_type dr    = (r_max - r_min) / N;

        const auto alpha   = parameter_list.alpha  (bp);
        const auto epsilon = parameter_list.epsilon(bp);
        const auto r0      = parameter_list.r0     (bp);

        for(std::size_t i=0; i<N; ++i)
        {
            const auto r         = r_min + i * dr;
            const auto U_attr    = potential.U_attr   (epsilon, alpha, r, r0);
            const auto dU_attr   = potential.dU_attr  (epsilon, alpha, r, r0);
            const auto U_dU_attr = potential.U_dU_attr(epsilon, alpha, r, r0);

            BOOST_TEST(U_dU_attr.first  == U_attr,  boost::test_tools::tolerance(h));
            BOOST_TEST(U_dU_attr.second == dU_attr, boost::test_tools::tolerance(h));
        }
    }
}

BOOST_AUTO_TEST_CASE_TEMPLATE(bp_kind_3SPN2_BaseBaseInteraction,
        ParameterSet, parameters_to_test_double)
{
    mjolnir::LoggerManager::set_default_logger(
            "test_3spn2_base_base_interaction_potential.log");

    using traits_type      = mjolnir::SimulatorTraits<double, mjolnir::UnlimitedBoundary>;
    using parameter_list_type = mjolnir::ThreeSPN2BasePairParameterList<traits_type>;
    using base_kind        = mjolnir::parameter_3SPN2::base_kind;
    using base_pair_kind   = mjolnir::parameter_3SPN2::base_pair_kind;

    parameter_list_type parameter_list(ParameterSet{}, {}, {},
        parameter_list_type::ignore_molecule_type("Nothing"),
        parameter_list_type::ignore_group_type({})
        );

    BOOST_TEST(base_pair_kind::AT == parameter_list.bp_kind(base_kind::A, base_kind::T));
    BOOST_TEST(base_pair_kind::TA == parameter_list.bp_kind(base_kind::T, base_kind::A));
    BOOST_TEST(base_pair_kind::GC == parameter_list.bp_kind(base_kind::G, base_kind::C));
    BOOST_TEST(base_pair_kind::CG == parameter_list.bp_kind(base_kind::C, base_kind::G));
}


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

// Copyright (c) 2007-2012 Barend Gehrels, Amsterdam, the Netherlands.
// Copyright (c) 2008-2012 Bruno Lalande, Paris, France.
// Copyright (c) 2009-2012 Mateusz Loskot, London, UK.
// Copyright (c) 2017 Adam Wulkiewicz, Lodz, Poland.

// This file was modified by Oracle on 2017-2021.
// Modifications copyright (c) 2017-2021 Oracle and/or its affiliates.
// Contributed and/or modified by Adam Wulkiewicz, on behalf of Oracle

// Parts of Boost.Geometry are redesigned from Geodan's Geographic Library
// (geolib/GGL), copyright (c) 1995-2010 Geodan, Amsterdam, the Netherlands.

// Use, modification and distribution is 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)

#ifndef BOOST_GEOMETRY_ALGORITHMS_AREA_HPP
#define BOOST_GEOMETRY_ALGORITHMS_AREA_HPP

#include <boost/core/ignore_unused.hpp>
#include <boost/range/begin.hpp>
#include <boost/range/end.hpp>
#include <boost/range/size.hpp>
#include <boost/range/value_type.hpp>

#include <boost/geometry/core/closure.hpp>
#include <boost/geometry/core/exterior_ring.hpp>
#include <boost/geometry/core/interior_rings.hpp>
#include <boost/geometry/core/point_order.hpp>
#include <boost/geometry/core/point_type.hpp>
#include <boost/geometry/core/ring_type.hpp>
#include <boost/geometry/core/tags.hpp>
#include <boost/geometry/core/visit.hpp>

#include <boost/geometry/algorithms/detail/calculate_null.hpp>
#include <boost/geometry/algorithms/detail/calculate_sum.hpp>
// #include <boost/geometry/algorithms/detail/throw_on_empty_input.hpp>
#include <boost/geometry/algorithms/detail/multi_sum.hpp>
#include <boost/geometry/algorithms/detail/visit.hpp>

#include <boost/geometry/algorithms/area_result.hpp>
#include <boost/geometry/algorithms/default_area_result.hpp>

#include <boost/geometry/geometries/adapted/boost_variant.hpp> // For backward compatibility
#include <boost/geometry/geometries/concepts/check.hpp>

#include <boost/geometry/strategies/area/services.hpp>
#include <boost/geometry/strategies/area/cartesian.hpp>
#include <boost/geometry/strategies/area/geographic.hpp>
#include <boost/geometry/strategies/area/spherical.hpp>
#include <boost/geometry/strategies/concepts/area_concept.hpp>
#include <boost/geometry/strategies/default_strategy.hpp>

#include <boost/geometry/util/math.hpp>

#include <boost/geometry/views/detail/closed_clockwise_view.hpp>

namespace boost { namespace geometry
{

#ifndef DOXYGEN_NO_DETAIL
namespace detail { namespace area
{

struct box_area
{
    template <typename Box, typename Strategies>
    static inline typename coordinate_type<Box>::type
    apply(Box const& box, Strategies const& strategies)
    {
        // Currently only works for 2D Cartesian boxes
        assert_dimension<Box, 2>();

        return strategies.area(box).apply(box);
    }
};

struct ring_area
{
    template <typename Ring, typename Strategies>
    static inline typename area_result<Ring, Strategies>::type
    apply(Ring const& ring, Strategies const& strategies)
    {
        using strategy_type = decltype(strategies.area(ring));

        BOOST_CONCEPT_ASSERT( (geometry::concepts::AreaStrategy<Ring, strategy_type>) );
        assert_dimension<Ring, 2>();

        // Ignore warning (because using static method sometimes) on strategy
        boost::ignore_unused(strategies);

        // An open ring has at least three points,
        // A closed ring has at least four points,
        // if not, there is no (zero) area
        if (boost::size(ring) < detail::minimum_ring_size<Ring>::value)
        {
            return typename area_result<Ring, Strategies>::type();
        }

        detail::closed_clockwise_view<Ring const> const view(ring);
        auto it = boost::begin(view);
        auto const end = boost::end(view);

        strategy_type const strategy = strategies.area(ring);
        typename strategy_type::template state<Ring> state;

        for (auto previous = it++; it != end; ++previous, ++it)
        {
            strategy.apply(*previous, *it, state);
        }

        return strategy.result(state);
    }
};

}} // namespace detail::area

#endif // DOXYGEN_NO_DETAIL

#ifndef DOXYGEN_NO_DISPATCH
namespace dispatch
{

template
<
    typename Geometry,
    typename Tag = typename tag<Geometry>::type
>
struct area : detail::calculate_null
{
    template <typename Strategy>
    static inline typename area_result<Geometry, Strategy>::type
        apply(Geometry const& geometry, Strategy const& strategy)
    {
        return calculate_null::apply
            <
                typename area_result<Geometry, Strategy>::type
            >(geometry, strategy);
    }
};

template <typename Geometry>
struct area<Geometry, box_tag> : detail::area::box_area
{};

template <typename Ring>
struct area<Ring, ring_tag>
    : detail::area::ring_area
{};

template <typename Polygon>
struct area<Polygon, polygon_tag> : detail::calculate_polygon_sum
{
    template <typename Strategy>
    static inline typename area_result<Polygon, Strategy>::type
        apply(Polygon const& polygon, Strategy const& strategy)
    {
        return calculate_polygon_sum::apply
            <
                typename area_result<Polygon, Strategy>::type,
                detail::area::ring_area
            >(polygon, strategy);
    }
};

template <typename MultiGeometry>
struct area<MultiGeometry, multi_polygon_tag> : detail::multi_sum
{
    template <typename Strategy>
    static inline typename area_result<MultiGeometry, Strategy>::type
    apply(MultiGeometry const& multi, Strategy const& strategy)
    {
        return multi_sum::apply
               <
                   typename area_result<MultiGeometry, Strategy>::type,
                   area<typename boost::range_value<MultiGeometry>::type>
               >(multi, strategy);
    }
};

} // namespace dispatch
#endif // DOXYGEN_NO_DISPATCH

namespace resolve_strategy
{

template
<
    typename Strategy,
    bool IsUmbrella = strategies::detail::is_umbrella_strategy<Strategy>::value
>
struct area
{
    template <typename Geometry>
    static inline typename area_result<Geometry, Strategy>::type
    apply(Geometry const& geometry, Strategy const& strategy)
    {
        return dispatch::area<Geometry>::apply(geometry, strategy);
    }
};

template <typename Strategy>
struct area<Strategy, false>
{
    template <typename Geometry>
    static auto apply(Geometry const& geometry, Strategy const& strategy)
    {
        using strategies::area::services::strategy_converter;
        return dispatch::area
            <
                Geometry
            >::apply(geometry, strategy_converter<Strategy>::get(strategy));
    }
};

template <>
struct area<default_strategy, false>
{
    template <typename Geometry>
    static inline typename area_result<Geometry>::type
    apply(Geometry const& geometry, default_strategy)
    {
        typedef typename strategies::area::services::default_strategy
            <
                Geometry
            >::type strategy_type;

        return dispatch::area<Geometry>::apply(geometry, strategy_type());
    }
};

} // namespace resolve_strategy

namespace resolve_dynamic
{

template <typename Geometry, typename Tag = typename geometry::tag<Geometry>::type>
struct area
{
    template <typename Strategy>
    static inline typename area_result<Geometry, Strategy>::type
        apply(Geometry const& geometry, Strategy const& strategy)
    {
        return resolve_strategy::area<Strategy>::apply(geometry, strategy);
    }
};

template <typename Geometry>
struct area<Geometry, dynamic_geometry_tag>
{
    template <typename Strategy>
    static inline typename area_result<Geometry, Strategy>::type
        apply(Geometry const& geometry, Strategy const& strategy)
    {
        typename area_result<Geometry, Strategy>::type result = 0;
        traits::visit<Geometry>::apply([&](auto const& g)
        {
            result = area<util::remove_cref_t<decltype(g)>>::apply(g, strategy);
        }, geometry);
        return result;
    }
};

template <typename Geometry>
struct area<Geometry, geometry_collection_tag>
{
    template <typename Strategy>
    static inline typename area_result<Geometry, Strategy>::type
        apply(Geometry const& geometry, Strategy const& strategy)
    {
        typename area_result<Geometry, Strategy>::type result = 0;
        detail::visit_breadth_first([&](auto const& g)
        {
            result += area<util::remove_cref_t<decltype(g)>>::apply(g, strategy);
            return true;
        }, geometry);
        return result;
    }
};

} // namespace resolve_dynamic

/*!
\brief \brief_calc{area}
\ingroup area
\details \details_calc{area}. \details_default_strategy

The area algorithm calculates the surface area of all geometries having a surface, namely
box, polygon, ring, multipolygon. The units are the square of the units used for the points
defining the surface. If subject geometry is defined in meters, then area is calculated
in square meters.

The area calculation can be done in all three common coordinate systems, Cartesian, Spherical
and Geographic as well.

\tparam Geometry \tparam_geometry
\param geometry \param_geometry
\return \return_calc{area}

\qbk{[include reference/algorithms/area.qbk]}
\qbk{[heading Examples]}
\qbk{[area] [area_output]}
*/
template <typename Geometry>
inline typename area_result<Geometry>::type
area(Geometry const& geometry)
{
    concepts::check<Geometry const>();

    // detail::throw_on_empty_input(geometry);

    return resolve_dynamic::area<Geometry>::apply(geometry, default_strategy());
}

/*!
\brief \brief_calc{area} \brief_strategy
\ingroup area
\details \details_calc{area} \brief_strategy. \details_strategy_reasons
\tparam Geometry \tparam_geometry
\tparam Strategy \tparam_strategy{Area}
\param geometry \param_geometry
\param strategy \param_strategy{area}
\return \return_calc{area}

\qbk{distinguish,with strategy}

\qbk{
[include reference/algorithms/area.qbk]

[heading Available Strategies]
\* [link geometry.reference.strategies.strategy_area_cartesian Cartesian]
\* [link geometry.reference.strategies.strategy_area_spherical Spherical]
\* [link geometry.reference.strategies.strategy_area_geographic Geographic]

[heading Example]
[area_with_strategy]
[area_with_strategy_output]
}
 */
template <typename Geometry, typename Strategy>
inline typename area_result<Geometry, Strategy>::type
area(Geometry const& geometry, Strategy const& strategy)
{
    concepts::check<Geometry const>();

    // detail::throw_on_empty_input(geometry);

    return resolve_dynamic::area<Geometry>::apply(geometry, strategy);
}

}} // namespace boost::geometry

#endif // BOOST_GEOMETRY_ALGORITHMS_AREA_HPP


//==================================================================================================
/*!
  @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_DIVIDES_HPP_INCLUDED
#define BOOST_SIMD_ARCH_COMMON_SIMD_FUNCTION_DIVIDES_HPP_INCLUDED

#include <boost/simd/detail/overload.hpp>
#include <boost/simd/constant/one.hpp>
#include <boost/simd/constant/valmax.hpp>
#include <boost/simd/constant/valmin.hpp>
#include <boost/simd/function/bitwise_or.hpp>
#include <boost/simd/function/bitwise_xor.hpp>
#include <boost/simd/function/divides.hpp>
#include <boost/simd/function/genmask.hpp>
#include <boost/simd/function/if_else.hpp>
#include <boost/simd/function/if_zero_else_one.hpp>
#include <boost/simd/function/inc.hpp>
#include <boost/simd/function/is_eqz.hpp>
#include <boost/simd/function/is_nez.hpp>
#include <boost/simd/function/logical_and.hpp>
#include <boost/simd/function/plus.hpp>
#include <boost/simd/function/shift_right.hpp>
#include <boost/simd/detail/dispatch/meta/scalar_of.hpp>

namespace boost { namespace simd { namespace ext
{
   namespace bd = boost::dispatch;
   namespace bs = boost::simd;
   BOOST_DISPATCH_OVERLOAD(divides_
                          , (typename A0, typename X)
                          , bd::cpu_
                          , bs::saturated_tag
                          , bs::pack_<bd::floating_<A0>, X>
                          , bs::pack_<bd::floating_<A0>, X>
                          )
   {
      BOOST_FORCEINLINE A0 operator()(const saturated_tag &
                                     , const A0& a0, const A0& a1) const BOOST_NOEXCEPT
      {
        return a0/a1;
      }
   };

   BOOST_DISPATCH_OVERLOAD(divides_
                          , (typename A0, typename X)
                          , bd::cpu_
                          , bs::saturated_tag
                          , bs::pack_<bd::uint_<A0>, X>
                          , bs::pack_<bd::uint_<A0>, X>
                          )
   {
      BOOST_FORCEINLINE A0 operator()(const saturated_tag &
                                     , const A0& a0, const A0& a1) const BOOST_NOEXCEPT
      {
        auto iseqza1 = is_eqz(a1);
        const A0 aa1 = if_else(iseqza1, One<A0>(), a1);
        const A0 aa0 = if_else(iseqza1, genmask(a0), a0);
        return aa0/aa1;
      }
   };

   BOOST_DISPATCH_OVERLOAD(divides_
                          , (typename A0, typename X)
                          , bd::cpu_
                          , bs::saturated_tag
                          , bs::pack_<bd::int_<A0>, X>
                          , bs::pack_<bd::int_<A0>, X>
                          )
   {
      BOOST_FORCEINLINE A0 operator()(const saturated_tag &
                                     , const A0& a0, const A0& a1) const BOOST_NOEXCEPT
      {
        using sA0 = bd::scalar_of_t<A0>;
        auto iseqza1 = is_eqz(a1);
        // replace valmin/-1 by (valmin+1)/-1
        A0 x = a0 + if_zero_else_one(inc(a1) | (a0 + Valmin<A0>()));
        // negative -> valmin
        // positive -> valmax
        const A0 x2 = bitwise_xor(Valmax<A0>(), shift_right(x, sizeof(sA0)*CHAR_BIT-1));
        x = if_else(logical_and(iseqza1, is_nez(x)), x2, x);
        const A0 y = if_else(iseqza1, One<A0>(), a1);
        return x/y;
      }
   };
  namespace bd = boost::dispatch;
  BOOST_DISPATCH_OVERLOAD ( divides_
                          , (typename T)
                          , bd::cpu_
                          , boost::simd::fast_tag
                          , bd::generic_<bd::unspecified_<T>>
                          , bd::generic_<bd::unspecified_<T>>
                          )
  {
     BOOST_FORCEINLINE T operator()(const fast_tag &, T const& a, T const& b
                                  ) const BOOST_NOEXCEPT
    {
      return divides(a, b);
    }
  };

  BOOST_DISPATCH_OVERLOAD ( divides_
                          , (typename T)
                          , bd::cpu_
                          , boost::simd::fast_tag
                          , bd::generic_<bd::floating_<T>>
                          , bd::generic_<bd::floating_<T>>
                          )
  {
    BOOST_FORCEINLINE T operator()(const fast_tag &, T const& a, T const& b
                                  ) const BOOST_NOEXCEPT
    {
      return a*rec(b); // this is temporary as fast_ is meant to disappear
    }
  };
   BOOST_DISPATCH_OVERLOAD ( divides_
                          , (typename T)
                          ,  bd::cpu_
                          ,  bs::saturated_tag
                          ,  bd::generic_<bd::fundamental_<T>>
                          ,  bd::generic_<bd::fundamental_<T>>
                          )
  {
    BOOST_FORCEINLINE T operator()(const saturated_tag &, const T& a, const T& b) const BOOST_NOEXCEPT
    {
      return saturated_(divides)(a, b);
    }
  };

} } }

#endif


#include <boost/math/constants/constants.hpp>

using namespace std;

int main() {
    double my_pi1 = boost::math::constants::pi<double>();
    return 0;
}


#include <cctbx/boost_python/flex_fwd.h>

#include <cctbx/maptbx/grid_tags.h>
#include <boost/python/class.hpp>
#include <boost/python/args.hpp>
#include <boost/python/return_value_policy.hpp>
#include <boost/python/copy_const_reference.hpp>
#include <boost/python/return_internal_reference.hpp>

namespace cctbx { namespace maptbx {
namespace {

  struct grid_tags_wrappers
  {
    typedef grid_tags<> w_t;

    static void
    wrap()
    {
      using namespace boost::python;
      typedef return_value_policy<copy_const_reference> ccr;
      typedef return_internal_reference<> rir;
      class_<w_t>("grid_tags", no_init)
        .def(init<af::int3 const&>((arg("dim"))))
        .def("is_valid", &w_t::is_valid)
        .def("tag_array", &w_t::tag_array)
        .def("build", &w_t::build,
          (arg("space_group_type"), arg("symmetry_flags")))
        .def("space_group_type", &w_t::space_group_type, rir())
        .def("symmetry_flags", &w_t::symmetry_flags, ccr())
        .def("grid_ss_continuous", &w_t::grid_ss_continuous, ccr())
        .def("n_grid_misses", &w_t::n_grid_misses)
        .def("n_independent", &w_t::n_independent)
        .def("n_dependent", &w_t::n_dependent)
        .def("dependent_correlation",
          (scitbx::math::linear_correlation<>(w_t::*)(
            af::const_ref<float, af::c_grid_padded<3> > const&,
            double) const)
              &w_t::dependent_correlation, (
                arg("data"),
                arg("epsilon")=1e-15))
        .def("dependent_correlation",
          (scitbx::math::linear_correlation<>(w_t::*)(
            af::const_ref<double, af::c_grid_padded<3> > const&,
            double) const)
              &w_t::dependent_correlation, (
                arg("data"),
                arg("epsilon")=1e-15))
        .def("verify",
          (bool(w_t::*)(
            af::const_ref<float, af::c_grid_padded<3> > const&,
            double) const)
              &w_t::verify, (
                arg("data"), arg("min_correlation")=0.99))
        .def("verify",
          (bool(w_t::*)(
            af::const_ref<double, af::c_grid_padded<3> > const&,
            double) const)
              &w_t::verify, (
                arg("data"), arg("min_correlation")=0.99))
        .def("sum_sym_equiv_points",
          (void(w_t::*)(af::ref<float, c_grid_padded_p1<3> > const&) const)
            &w_t::sum_sym_equiv_points,
              (arg("data")))
        .def("sum_sym_equiv_points",
          (void(w_t::*)(af::ref<double, c_grid_padded_p1<3> > const&) const)
            &w_t::sum_sym_equiv_points,
              (arg("data")))
        .def("apply_symmetry_to_mask",
          (std::size_t(w_t::*)(af::ref<int, af::c_grid<3> > const&) const)
            &w_t::apply_symmetry_to_mask,
              (arg("data")))
      ;
    }
  };

} // namespace <anoymous>

namespace boost_python {

  void wrap_grid_tags()
  {
    grid_tags_wrappers::wrap();
  }

}}} // namespace cctbx::maptbx::boost_python