// Boost.Geometry - gis-projections (based on PROJ4)

// Copyright (c) 2008-2015 Barend Gehrels, Amsterdam, the Netherlands.

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

// 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)

// This file is converted from PROJ4, http://trac.osgeo.org/proj
// PROJ4 is originally written by Gerald Evenden (then of the USGS)
// PROJ4 is maintained by Frank Warmerdam
// PROJ4 is converted to Boost.Geometry by Barend Gehrels

// Last updated version of proj: 5.0.0

// Original copyright notice:

// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:

// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.

// The Natural Earth projection was designed by Tom Patterson, US National Park
// Service, in 2007, using Flex Projector. The shape of the original projection
// was defined at every 5 degrees and piece-wise cubic spline interpolation was
// used to compute the complete graticule.
// The code here uses polynomial functions instead of cubic splines and
// is therefore much simpler to program. The polynomial approximation was
// developed by Bojan Savric, in collaboration with Tom Patterson and Bernhard
// Jenny, Institute of Cartography, ETH Zurich. It slightly deviates from
// Patterson's original projection by adding additional curvature to meridians
// where they meet the horizontal pole line. This improvement is by intention
// and designed in collaboration with Tom Patterson.
// Port to PROJ.4 by Bernhard Jenny, 6 June 2011

#ifndef BOOST_GEOMETRY_PROJECTIONS_NATEARTH_HPP
#define BOOST_GEOMETRY_PROJECTIONS_NATEARTH_HPP

#include <boost/geometry/srs/projections/impl/base_static.hpp>
#include <boost/geometry/srs/projections/impl/base_dynamic.hpp>
#include <boost/geometry/srs/projections/impl/projects.hpp>
#include <boost/geometry/srs/projections/impl/factory_entry.hpp>

namespace boost { namespace geometry
{

namespace srs { namespace par4
{
    struct natearth {}; // Natural Earth

}} //namespace srs::par4

namespace projections
{
    #ifndef DOXYGEN_NO_DETAIL
    namespace detail { namespace natearth
    {

            static const double A0 = 0.8707;
            static const double A1 = -0.131979;
            static const double A2 = -0.013791;
            static const double A3 = 0.003971;
            static const double A4 = -0.001529;
            static const double B0 = 1.007226;
            static const double B1 = 0.015085;
            static const double B2 = -0.044475;
            static const double B3 = 0.028874;
            static const double B4 = -0.005916;
            static const double C0 = B0;
            static const double C1 = (3 * B1);
            static const double C2 = (7 * B2);
            static const double C3 = (9 * B3);
            static const double C4 = (11 * B4);
            static const double epsilon = 1e-11;

            template <typename T>
            inline T max_y() { return (0.8707 * 0.52 * detail::pi<T>()); }

            /* Not sure at all of the appropriate number for max_iter... */
            static const int max_iter = 100;

            // template class, using CRTP to implement forward/inverse
            template <typename T, typename Parameters>
            struct base_natearth_spheroid
                : public base_t_fi<base_natearth_spheroid<T, Parameters>, T, Parameters>
            {
                inline base_natearth_spheroid(const Parameters& par)
                    : base_t_fi<base_natearth_spheroid<T, Parameters>, T, Parameters>(*this, par)
                {}

                // FORWARD(s_forward)  spheroid
                // Project coordinates from geographic (lon, lat) to cartesian (x, y)
                inline void fwd(T& lp_lon, T& lp_lat, T& xy_x, T& xy_y) const
                {
                    T phi2, phi4;

                    phi2 = lp_lat * lp_lat;
                    phi4 = phi2 * phi2;
                    xy_x = lp_lon * (A0 + phi2 * (A1 + phi2 * (A2 + phi4 * phi2 * (A3 + phi2 * A4))));
                    xy_y = lp_lat * (B0 + phi2 * (B1 + phi4 * (B2 + B3 * phi2 + B4 * phi4)));
                }

                // INVERSE(s_inverse)  spheroid
                // Project coordinates from cartesian (x, y) to geographic (lon, lat)
                inline void inv(T& xy_x, T& xy_y, T& lp_lon, T& lp_lat) const
                {
                    static const T max_y = natearth::max_y<T>();

                    T yc, tol, y2, y4, f, fder;
                    int i;

                    /* make sure y is inside valid range */
                    if (xy_y > max_y) {
                        xy_y = max_y;
                    } else if (xy_y < -max_y) {
                        xy_y = -max_y;
                    }

                    /* latitude */
                    yc = xy_y;
                    for (i = max_iter; i ; --i) { /* Newton-Raphson */
                        y2 = yc * yc;
                        y4 = y2 * y2;
                        f = (yc * (B0 + y2 * (B1 + y4 * (B2 + B3 * y2 + B4 * y4)))) - xy_y;
                        fder = C0 + y2 * (C1 + y4 * (C2 + C3 * y2 + C4 * y4));
                        yc -= tol = f / fder;
                        if (fabs(tol) < epsilon) {
                            break;
                        }
                    }
                    if( i == 0 )
                        BOOST_THROW_EXCEPTION( projection_exception(error_non_convergent) );
                    lp_lat = yc;

                    /* longitude */
                    y2 = yc * yc;
                    lp_lon = xy_x / (A0 + y2 * (A1 + y2 * (A2 + y2 * y2 * y2 * (A3 + y2 * A4))));
                }

                static inline std::string get_name()
                {
                    return "natearth_spheroid";
                }

            };

            // Natural Earth
            template <typename Parameters>
            inline void setup_natearth(Parameters& par)
            {
                par.es = 0;
            }

    }} // namespace detail::natearth
    #endif // doxygen

    /*!
        \brief Natural Earth projection
        \ingroup projections
        \tparam Geographic latlong point type
        \tparam Cartesian xy point type
        \tparam Parameters parameter type
        \par Projection characteristics
         - Pseudocylindrical
         - Spheroid
        \par Example
        \image html ex_natearth.gif
    */
    template <typename T, typename Parameters>
    struct natearth_spheroid : public detail::natearth::base_natearth_spheroid<T, Parameters>
    {
        inline natearth_spheroid(const Parameters& par) : detail::natearth::base_natearth_spheroid<T, Parameters>(par)
        {
            detail::natearth::setup_natearth(this->m_par);
        }
    };

    #ifndef DOXYGEN_NO_DETAIL
    namespace detail
    {

        // Static projection
        BOOST_GEOMETRY_PROJECTIONS_DETAIL_STATIC_PROJECTION(srs::par4::natearth, natearth_spheroid, natearth_spheroid)

        // Factory entry(s)
        template <typename T, typename Parameters>
        class natearth_entry : public detail::factory_entry<T, Parameters>
        {
            public :
                virtual base_v<T, Parameters>* create_new(const Parameters& par) const
                {
                    return new base_v_fi<natearth_spheroid<T, Parameters>, T, Parameters>(par);
                }
        };

        template <typename T, typename Parameters>
        inline void natearth_init(detail::base_factory<T, Parameters>& factory)
        {
            factory.add_to_factory("natearth", new natearth_entry<T, Parameters>);
        }

    } // namespace detail
    #endif // doxygen

} // namespace projections

}} // namespace boost::geometry

#endif // BOOST_GEOMETRY_PROJECTIONS_NATEARTH_HPP


#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


// 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


#include "mesh_generator.h"

#include <fstream>
#include <climits>

#include <deal.II/grid/tria.h>
#include <deal.II/grid/manifold_lib.h>
#include <deal.II/grid/grid_generator.h>
#include <deal.II/grid/grid_tools.h>
#include <deal.II/grid/tria_accessor.h>
#include <deal.II/grid/tria_iterator.h>
#include <deal.II/grid/grid_in.h>

// note that prm here cannot be const because InitializeRelativePositionToIDMap
// invokes dealii::ParameterHandler::enter_subsection not marked const
template <int dim>
MeshGenerator<dim>::MeshGenerator (dealii::ParameterHandler &prm)
    : is_mesh_generated_(prm.get_bool("is mesh generated by deal.II")),
      have_reflective_bc_(prm.get_bool("have reflective boundary")),
      global_refinements_(prm.get_integer("uniform refinements")) {
  if (!is_mesh_generated_) {
    mesh_filename_ = prm.get ("mesh file name");
  } else {
    is_mesh_pin_resolved_ = prm.get_bool ("is mesh pin-resolved");
    if (is_mesh_pin_resolved_) {
      rod_radius_ = prm.get_double ("fuel rod radius");
      rod_type_ = prm.get ("fuel rod triangulation type");
    }
    ProcessCoordinateInformation (prm);
    InitializeRelativePositionToIDMap (prm);
    PreprocessReflectiveBC (prm);
  }
}

template <int dim>
MeshGenerator<dim>::~MeshGenerator () {}

template <int dim>
void MeshGenerator<dim>::MakeGrid (dealii::Triangulation<dim> &tria) {
  if (is_mesh_generated_) {
    GenerateInitialGrid (tria);
    InitializeMaterialID (tria);
    SetupBoundaryIDs (tria);
    if (global_refinements_>0)
      GlobalRefine (tria);
  } else {
    dealii::GridIn<dim> gi;
    gi.attach_triangulation (tria);
    std::ifstream f(mesh_filename_);
    gi.read_msh (f);
  }
}

template <int dim>
void MeshGenerator<dim>::FuelRod2D (dealii::Triangulation<2> &tria,
    const dealii::Point<2> &p) {
  double r = (rod_type_=="simple") ? rod_radius_ : (0.6*rod_radius_);
  const double a=1. / (std::sqrt(2.0)) * r;
  std::vector<dealii::Point<2>> v={
      p + dealii::Point<2>(0, 0)*r,
      p + dealii::Point<2>(0, 1)*r,
      p + dealii::Point<2>(1, 1)*a,
      p + dealii::Point<2>(1, 0)*r,
      p + dealii::Point<2>(1, -1)*a,
      p + dealii::Point<2>(0, -1)*r,
      p + dealii::Point<2>(-1, -1)*a,
      p + dealii::Point<2>(-1, 0)*r,
      p + dealii::Point<2>(-1, 1)*a
  };
  // vertices per cell for the circle
  std::vector<std::vector<dealii::Point<2>>> ps={
      {v[8], v[0], v[1], v[2]},
      {v[0], v[4], v[2], v[3]},
      {v[6], v[5], v[0], v[4]},
      {v[7], v[6], v[8], v[0]}
  };
  // generate a quater of the circle
  dealii::GridGenerator::general_cell(tria, ps[0]);
  // generate the rest of the circle
  for (int i=1; i<4; ++i) {
    dealii::Triangulation<2> t;
    dealii::GridGenerator::general_cell (t, ps[i]);
    dealii::GridGenerator::merge_triangulations (tria, t, tria);
  }

  // for composite type rod, a ring should be generated outside the inner circle
  if (rod_type_=="composite") {
    dealii::Triangulation<2> t;
    dealii::GridGenerator::hyper_shell (t, p, r, rod_radius_, 8);
    dealii::GridGenerator::merge_triangulations (tria, t, tria);
  }
}

template <int dim>
void MeshGenerator<dim>::FuelPin2DGrid (dealii::Triangulation<2> &tria,
    const dealii::Point<2> &center_xy) {
  // generate a fuel rod at origin
  FuelRod2D (tria, dealii::Point<2>());

  // generate the rest part of a 2D pin
  dealii::Triangulation<2> tria_moderator;
  dealii::GridGenerator::hyper_cube_with_cylindrical_hole (tria_moderator,
      rod_radius_, 0.5*cell_size_all_dir_[0]);

  // merge these two parts to get a pin
  dealii::GridGenerator::merge_triangulations (tria, tria_moderator, tria);

  // shift the triangulation to the desired center
  dealii::GridTools::shift (dealii::Tensor<1,2>(center_xy), tria);
}

template <int dim>
void MeshGenerator<dim>::NonFuelPin2DGrid (dealii::Triangulation<2> &tria,
    const dealii::Point<2> &center_xy) {
  // generate 4 squares around origin with same side length of rod_radius_
  // diags are the lower_left-upper_right diagonal points of squares
  double r = 0.5 * cell_size_all_dir_[0];
  std::vector<std::pair<dealii::Point<2>, dealii::Point<2>>> diags = {
      {dealii::Point<2>(-r, 0), dealii::Point<2>(0, r)},
      {dealii::Point<2>(0,0), dealii::Point<2>(r,r)},
      {dealii::Point<2>(-r, -r), dealii::Point<2>(0, 0)},
      {dealii::Point<2>(0,-r), dealii::Point<2>(r,0)}
  };
  // generate the first subcell
  dealii::GridGenerator::hyper_rectangle (tria,
      diags[0].first, diags[0].second);
  // generate the rest and merge together
  for (int i=1; i<4; ++i) {
    dealii::Triangulation<2> t;
    dealii::GridGenerator::hyper_rectangle (t, diags[i].first, diags[i].second);
    dealii::GridGenerator::merge_triangulations (tria, t, tria);
  }

  // shift the triangulation to the destination
  dealii::GridTools::shift (center_xy, tria);
}

template <>
void MeshGenerator<1>::GenerateInitialUnstructGrid (
    dealii::Triangulation<1> &) {}

template <>
void MeshGenerator<2>::GenerateInitialUnstructGrid (
    dealii::Triangulation<2> &tria) {
  // generate two basic pin models at lower left corner of the x-y plane
  dealii::Triangulation<2> t_fuel, t_moderator;
  double length = cell_size_all_dir_[0];
  FuelPin2DGrid (t_fuel, dealii::Point<2>(0.5*length, 0.5*length));
  NonFuelPin2DGrid (t_moderator, dealii::Point<2>(0.5*length, 0.5*length));

  // create a local triangulation and modify it for the first time
  dealii::Triangulation<2> t_loc;

  // modify the rest of the domain
  for (int ix=0; ix<ncell_per_dir_[0]; ++ix)
    for (int iy=0; iy<ncell_per_dir_[1]; ++iy) {
      std::vector<int> rel_pos = {ix, iy};
      auto fuel_id = relative_position_to_fuel_id_[rel_pos];
      // the first cell
      if (ix==0 && iy==0) {
        if (fuel_id<0)
          t_loc.copy_triangulation (t_moderator);
        else
          t_loc.copy_triangulation (t_fuel);
      } else {
        dealii::Triangulation<2> t_tmp;
        if (fuel_id<0)
          t_tmp.copy_triangulation (t_moderator);
        else
          t_tmp.copy_triangulation (t_fuel);

        dealii::GridTools::shift (
            dealii::Tensor<1, 2>(dealii::Point<2>(ix*length, iy*length)), t_tmp);
        dealii::GridGenerator::merge_triangulations (t_loc, t_tmp, t_loc);
      }
    }
  tria.copy_triangulation (t_loc);
}

template <>
void MeshGenerator<3>::GenerateInitialUnstructGrid (
    dealii::Triangulation<3> &tria) {
  // generate two basic pin models at lower left corner of the x-y plane
  dealii::Triangulation<2> t_fuel, t_moderator;
  double length = cell_size_all_dir_[0];
  FuelPin2DGrid (t_fuel, dealii::Point<2>(0.5*length, 0.5*length));
  NonFuelPin2DGrid (t_moderator, dealii::Point<2>(0.5*length, 0.5*length));

  // create a local triangulation and modify it for the first time
  dealii::Triangulation<2> t_loc;
  // modify the rest of the domain
  for (int ix=0; ix<ncell_per_dir_[0]; ++ix) {
    for (int iy=0; iy<ncell_per_dir_[1]; ++iy) {
      int fuel_id = -1;
      for (int iz=0; iz<ncell_per_dir_[2]; ++iz) {
        std::vector<int> rel_pos = {ix, iy, iz};
        if (relative_position_to_fuel_id_[rel_pos]>=0) {
          fuel_id = 1;
          curved_blocks.insert(std::vector<int>{ix, iy});
          break;
        }
      }
      // the first cell
      if (ix==0 && iy==0) {
        if (fuel_id<0)
          t_loc.copy_triangulation (t_moderator);
        else
          t_loc.copy_triangulation (t_fuel);
      } else {
        dealii::Triangulation<2> t_tmp;
        if (fuel_id<0)
          t_tmp.copy_triangulation (t_moderator);
        else
          t_tmp.copy_triangulation (t_fuel);

        dealii::GridTools::shift (
            dealii::Tensor<1, 2>(dealii::Point<2>(ix*length, iy*length)), t_tmp);
        dealii::GridGenerator::merge_triangulations (t_loc, t_tmp, t_loc);
      }
    }
  }
  // extrude 2d to 3d
  dealii::Triangulation<3> t_3d;
  dealii::GridGenerator::extrude_triangulation (
      t_loc, ncell_per_dir_[2]+1, axis_max_values_[2], t_3d);
  tria.copy_triangulation (t_3d);
}

template <int dim>
void MeshGenerator<dim>::GenerateInitialGrid (dealii::Triangulation<dim> &tria) {
  if (is_mesh_pin_resolved_) {
    AssertThrow(dim>1,
        dealii::ExcMessage("unstructured mesh is only valid for multi-D"))
    GenerateInitialUnstructGrid (tria);
  } else {
  // Note that this function is only suitable to
  // generate a hyper rectangle, which is a line
  // in 1D, a rectangle in 2D and a rectangular
  // cuboid in 3D

  // Construction of such a rectangle requires
  dealii::Point<dim> origin;
  dealii::Point<dim> diagonal;
  switch (dim) {
    case 1: {
      diagonal[0] = axis_max_values_[0];
      break;
    }

    case 2: {
      diagonal[0] = axis_max_values_[0];
      diagonal[1] = axis_max_values_[1];
      break;
    }

    case 3: {
      diagonal[0] = axis_max_values_[0];
      diagonal[1] = axis_max_values_[1];
      diagonal[2] = axis_max_values_[2];
      break;
    }

    default:
      break;
  }
  std::vector<unsigned int> tmp(ncell_per_dir_.begin(), ncell_per_dir_.end());
  dealii::GridGenerator::subdivided_hyper_rectangle (tria, tmp,
      origin, diagonal);
  }
}

template <int dim>
void MeshGenerator<dim>::InitializeMaterialID (
    dealii::Triangulation<dim> &tria) {
  AssertThrow (is_mesh_generated_==true,
      dealii::ExcMessage("mesh read in have to have boundary ids associated"));
  for (typename dealii::Triangulation<dim>::active_cell_iterator
       cell=tria.begin_active(); cell!=tria.end(); ++cell)
    if (cell->is_locally_owned()) {
      dealii::Point<dim> center(cell->center());
      std::vector<int> relative_position;
      GetCellRelativePosition(center, relative_position);
      int material_id = relative_position_to_id_[relative_position];

      if (is_mesh_pin_resolved_) {
        AssertThrow(dim>1,
            dealii::ExcMessage("unstructured mesh is not supported in 1D"));
        int fuel_id = relative_position_to_fuel_id_[relative_position];
        if (fuel_id<0) {
          // if it's not fuel pin, set material id
          cell->set_material_id(material_id);
        } else {
          AssertThrow (material_id!=fuel_id,
              dealii::ExcMessage("fuel rod must have different id than moderator"));
          // get pin center on xy plane
          double ctr_x, ctr_y;
          ctr_x = (0.5+relative_position[0])*cell_size_all_dir_[0];
          ctr_y = (0.5+relative_position[1])*cell_size_all_dir_[1];
          dealii::Point<dim> pin_center;
          pin_center[0]=ctr_x, pin_center[1]=ctr_y;
          if (dim==3) pin_center[2] = center[2];

          if (center.distance(pin_center)<rod_radius_) {
            // within pin, set id to fuel id
            cell->set_material_id(fuel_id);
          } else {
            // outside pin, set id to material id
            cell->set_material_id(material_id);
          }
        }
      } else {
        // structured mesh, directly set id
        cell->set_material_id (material_id);
      }
    }
}

template <int dim>
void MeshGenerator<dim>::GetCellRelativePosition (
    const dealii::Point<dim> &center,
    std::vector<int> &relative_position) {
  if (dim>=1) {
    relative_position.push_back(
        static_cast<int> (center[0] / cell_size_all_dir_[0]));
    if (dim>=2) {
      relative_position.push_back(
          static_cast<int> (center[1] / cell_size_all_dir_[1]));
      if (dim==3)
        relative_position.push_back(
            static_cast<int> (center[2] / cell_size_all_dir_[2]));
    }
  }
}

template <int dim>
void MeshGenerator<dim>::SetupBoundaryIDs
(dealii::Triangulation<dim> &tria)
{
  AssertThrow (is_mesh_generated_==true,
      dealii::ExcMessage("mesh read in have to have boundary ids associated"));
  AssertThrow (axis_max_values_.size()==dim,
      dealii::ExcMessage("number of entries axis max values should be dimension"));

  for (typename dealii::Triangulation<dim>::active_cell_iterator
       cell=tria.begin_active(); cell!=tria.end(); ++cell)
    if (cell->is_locally_owned())
      for (unsigned int fn=0;
           fn<dealii::GeometryInfo<dim>::faces_per_cell; ++fn)
        if (cell->at_boundary(fn)) {
          dealii::Point<dim> ct = cell->face(fn)->center();
          // x-axis boundaries
          if (std::fabs(ct[0])<1.0e-14)
            cell->face(fn)->set_boundary_id (0);
          else if (std::fabs(ct[0]-axis_max_values_[0])<1.0e-14)
            cell->face(fn)->set_boundary_id (1);

          // 2D and 3D boundaries
          if (dim>1) {
            // y-axis boundaries
            if (std::fabs(ct[1])<1.0e-14)
              cell->face(fn)->set_boundary_id (2);
            else if (std::fabs(ct[1]-axis_max_values_[1])<1.0e-14)
              cell->face(fn)->set_boundary_id (3);

            // z-axis boundaries for 3D only
            if (dim==3) {
              if (std::fabs(ct[2])<1.0e-14)
                cell->face(fn)->set_boundary_id (4);
              else if (std::fabs(ct[2]-axis_max_values_[2])<1.0e-14)
                cell->face(fn)->set_boundary_id (5);
            }
          }
        }
}

template <int dim>
void MeshGenerator<dim>::ProcessCoordinateInformation (
    const dealii::ParameterHandler &prm) {
  // max values for all axis
  std::vector<std::string> strings = dealii::Utilities::split_string_list (
      prm.get ("x, y, z max values of boundary locations"));
  AssertThrow (strings.size()>=dim,
      dealii::ExcMessage("Number of axis max values must be no less than dimension"));
  for (int i=0; i<dim; ++i)
    axis_max_values_.push_back (std::atof (strings[i].c_str()));

  // read in number of cells and get cell sizes along axes
  strings = dealii::Utilities::split_string_list (
      prm.get ("number of cells for x, y, z directions"));
  AssertThrow (strings.size()>=dim,
               dealii::ExcMessage ("Entries for numbers of cells must be no less than dimension"));
  std::vector<int> cells_per_dir;
  std::vector<std::vector<double> > spacings;
  for (int d=0; d<dim; ++d)
  {
    ncell_per_dir_.push_back (std::atoi (strings[d].c_str ()));
    cell_size_all_dir_.push_back (axis_max_values_[d]/ncell_per_dir_[d]);
  }
}

template <int dim>
void MeshGenerator<dim>::PreprocessReflectiveBC (
    const dealii::ParameterHandler &prm) {
  if (have_reflective_bc_) {
    std::map<std::string, int> bd_names_to_id {{"xmin",0}, {"xmax",1},
        {"ymin",2}, {"ymax",3}, {"zmin",4}, {"zmax",5}};
    std::vector<std::string> strings = dealii::Utilities::split_string_list (
        prm.get ("reflective boundary names"));
    AssertThrow (strings.size()>0,
        dealii::ExcMessage("reflective boundary names have to be entered"));
    std::set<int> tmp;
    for (unsigned int i=0; i<strings.size (); ++i) {
      AssertThrow(bd_names_to_id.find(strings[i])!=bd_names_to_id.end(),
          dealii::ExcMessage("Invalid reflective boundary name: use xmin, xmax, etc."));
      tmp.insert (bd_names_to_id[strings[i]]);
    }
    auto it = tmp.begin ();
    std::ostringstream os;
    os << "No valid reflective boundary name for " << dim << "D";
    AssertThrow(*it<2*dim,
                dealii::ExcMessage(os.str()));
    for (int i=0; i<2*dim; ++i) {
      if (tmp.count (i))
        is_reflective_bc_[i] = true;
      else
        is_reflective_bc_[i] = false;
    }
  }
}

template <int dim>
void MeshGenerator<dim>::InitializeRelativePositionToIDMap (
    dealii::ParameterHandler &prm) {
  prm.enter_subsection ("material ID map");
  {
    int ncell_z = dim==3?ncell_per_dir_[2]:1;
    int ncell_y = dim>=2?ncell_per_dir_[1]:1;
    int ncell_x = ncell_per_dir_[0];
    std::string id_fname = prm.get ("material id file name");
    std::ifstream in (id_fname);
    std::string line;
    int ct = 0;
    if (in.is_open ()) {
      while (std::getline (in, line)) {
        int y = ct % ncell_y;
        int z = ct / ncell_y;
        std::vector<std::string> strings =
            dealii::Utilities::split_string_list (line, ' ');
        AssertThrow (static_cast<int>(strings.size()) == ncell_x,
            dealii::ExcMessage("Entries of material ID per row must be ncell_x"));
        for (int x=0; x<ncell_x; ++x) {
          std::vector<int> tmp = {x};
          if (dim>1) tmp.push_back (y);
          if (dim>2) tmp.push_back (z);
          relative_position_to_id_[tmp] = std::atoi (strings[x].c_str());
        }
        ct += 1;
      }
      AssertThrow (ct==ncell_y*ncell_z,
          dealii::ExcMessage("Number of y, z ID entries are not correct"));
      in.close ();
    }
    if (is_mesh_pin_resolved_) {
      std::string fuel_id_name = prm.get ("fuel pin material id file name");
      std::ifstream in_fuel (fuel_id_name);
      ct = 0;
      if (in_fuel.is_open ()) {
        while (std::getline (in_fuel, line)) {
          int y = ct % ncell_y;
          int z = ct / ncell_y;
          std::vector<std::string> strings =
              dealii::Utilities::split_string_list (line, ' ');
          AssertThrow (static_cast<int>(strings.size())==ncell_x,
              dealii::ExcMessage("Entries of material ID per row must be ncell_x"));
          for (int x=0; x<ncell_x; ++x) {
            std::vector<int> tmp = {x};
            if (dim>1) tmp.push_back (y);
            if (dim>2) tmp.push_back (z);
            // Note: material has to be stored using Hash table to BST
            relative_position_to_fuel_id_[tmp] = std::atoi (strings[x].c_str());
          }
          ct += 1;
        }
        AssertThrow (ct==ncell_y*ncell_z,
            dealii::ExcMessage("Number of y, z fuel ID entries are not correct"));
        in_fuel.close ();
      }
    }
  }
  prm.leave_subsection ();
}

template <>
void MeshGenerator<2>::SetManifoldsAndRefine (dealii::Triangulation<2> &tria) {
  // declare manifold as cylinder surfaces around axis parallel to z-axis
  std::map<std::vector<int>, dealii::SphericalManifold<2>*> rod_surfaces;
  //std::vector<dealii::SphericalManifold<2>*> rod_surfaces;
  for (int x=0; x<ncell_per_dir_[0]; ++x)
    for (int y=0; y<ncell_per_dir_[1]; ++y) {
      std::vector<int> t = {x, y};
      auto fuel_id = relative_position_to_fuel_id_[t];
      if (fuel_id>=0) {

        dealii::Point<2> ctr((0.5+x)*cell_size_all_dir_[0],
                             (0.5+y)*cell_size_all_dir_[1]);
        rod_surfaces[t] = new dealii::SphericalManifold<2> (ctr);
        tria.set_manifold (10+x+y*ncell_per_dir_[0], *rod_surfaces[t]);
      }
    }

  for (typename dealii::Triangulation<2>::active_cell_iterator
       cell=tria.begin_active(); cell!=tria.end(); ++cell)
    if (cell->is_locally_owned()) {
      std::vector<int> relative_position;
      GetCellRelativePosition (cell->center(), relative_position);
      auto fuel_id = relative_position_to_fuel_id_[relative_position];

      // only set manifolds for rod cells having cladding surface
      if (fuel_id>=0) {
        // fuel pin center on x-y plane
        dealii::Point<2> pin_ctr_xy (
            (0.5+relative_position[0])*cell_size_all_dir_[0],
            (0.5+relative_position[1])*cell_size_all_dir_[1]);
        dealii::Point<2> cell_ctr_xy (cell->center()[0], cell->center()[1]);
        if (cell_ctr_xy.distance(pin_ctr_xy)<rod_radius_) {
          int x=relative_position[0], y=relative_position[1];
          for (unsigned  vn=0;
               vn<dealii::GeometryInfo<2>::vertices_per_cell; ++vn) {
            dealii::Point<2> vertex_xy(cell->vertex(vn)[0], cell->vertex(vn)[1]);
            double dist = vertex_xy.distance(pin_ctr_xy);
            if (std::fabs(dist-rod_radius_)<1.0e-14) {
              cell->set_all_manifold_ids (10+x+y*ncell_per_dir_[0]);
              break;
            }
          }
        }

      }
    }
  // perform the refinement
  tria.refine_global (global_refinements_);

  // reset the manifolds to avoid error from deal.II design defect of manifold
  tria.reset_manifold (INT_MAX);
}

template <>
void MeshGenerator<3>::SetManifoldsAndRefine (dealii::Triangulation<3> &tria) {
  // declare manifold as cylinder surfaces around axis parallel to z-axis
  std::map<std::vector<int>, dealii::CylindricalManifold<3>*> curved_surfaces;
  for (int ix=0; ix<ncell_per_dir_[0]; ++ix)
    for (int iy=0; iy<ncell_per_dir_[1]; ++iy) {
      std::vector<int> rel_pos = {ix, iy};
      if (curved_blocks.find(rel_pos)!=curved_blocks.end()) {
        dealii::Point<3> pt_on_axis ((0.5+ix)*cell_size_all_dir_[0],
            (0.5+iy)*cell_size_all_dir_[1] ,0);
        curved_surfaces[rel_pos] =
            new dealii::CylindricalManifold<3> (dealii::Point<3>(0,0,1.),
                pt_on_axis);
        tria.set_manifold (10+ix+iy*ncell_per_dir_[0],
            *curved_surfaces[rel_pos]);
      }
    }

  for (typename dealii::Triangulation<3>::active_cell_iterator
       cell=tria.begin_active(); cell!=tria.end(); ++cell)
    if (cell->is_locally_owned()) {
      //bool dist = cell->center().distance(cell->barycenter())>1.0e-15
      if (cell->center().distance(cell->barycenter())>1.0e-15) {
        std::vector<int> rel_pos;
        GetCellRelativePosition (cell->center(), rel_pos);
        dealii::Point<3> pin_ctr((0.5+rel_pos[0])*cell_size_all_dir_[0],
            (0.5+rel_pos[1])*cell_size_all_dir_[1],
            cell->center()[2]);
        if (pin_ctr.distance(cell->center())<rod_radius_)
          cell->set_all_manifold_ids(10+rel_pos[0]+rel_pos[1]*ncell_per_dir_[0]);
      }
    }

  // perform the refinement
  tria.refine_global (global_refinements_);
  // reset the manifolds to avoid error from deal.II design defect of manifold
  tria.reset_manifold (INT_MAX);
}

template <>
void MeshGenerator<1>::GlobalRefine (dealii::Triangulation<1>& tria) {
  tria.refine_global (global_refinements_);
}

template <>
void MeshGenerator<2>::GlobalRefine (dealii::Triangulation<2>& tria) {
  if (is_mesh_pin_resolved_)
    // set manifolds for fuel rod surfaces and refine
    SetManifoldsAndRefine (tria);
  else
    tria.refine_global (global_refinements_);
}

template <>
void MeshGenerator<3>::GlobalRefine (dealii::Triangulation<3>& tria) {
  if (is_mesh_pin_resolved_)
    // set manifolds for fuel rod surfaces and refine
    SetManifoldsAndRefine (tria);
  else
    tria.refine_global (global_refinements_);
}

template <int dim>
std::unordered_map<int, bool>
MeshGenerator<dim>::GetReflectiveBCMap () {
  return is_reflective_bc_;
}

template <int dim>
int MeshGenerator<dim>::GetUniformRefinement () {
  return global_refinements_;
}

template class MeshGenerator<1>;
template class MeshGenerator<2>;
template class MeshGenerator<3>;


// MIT License
//
// Copyright (c) 2019 Jelle Spijker
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//

#include "../include/fluids/Liquid.h"

#include <boost/units/cmath.hpp>
#include <fluids/Liquid.h>

namespace Fluids {

Liquid::Liquid() :
    m_static_pressure(new quantity<si::pressure>(1.e5 * si::pascals)),
    m_speed(new quantity<si::velocity>(0. * si::meters_per_second)),
    m_height(new quantity<si::length>(0. * si::meter)),
    m_density(new quantity<si::mass_density>(1000. * si::kilogram_per_cubic_meter)),
    m_dynamic_viscosity(new quantity<si::dynamic_viscosity>(1.15e-3 * si::pascals * si::seconds)),
    m_dynamic_pressure(new quantity<si::pressure>(0. * si::pascal)),
    m_potential_pressure(new quantity<si::pressure>(0. * si::pascal)),
    m_bernoulli(new quantity<si::pressure>(0. * si::pascal)) {

}

Liquid::Liquid(const Liquid &other) :
    m_static_pressure(new quantity<si::pressure>(*other.m_static_pressure)),
    m_speed(new quantity<si::velocity>(*other.m_speed)),
    m_height(new quantity<si::length>(*other.m_height)),
    m_density(new quantity<si::mass_density>(*other.m_density)),
    m_dynamic_viscosity(new quantity<si::dynamic_viscosity>(*other.m_dynamic_viscosity)),
    m_dynamic_pressure(new quantity<si::pressure>(*other.m_dynamic_pressure)),
    m_potential_pressure(new quantity<si::pressure>(*other.m_potential_pressure)),
    m_bernoulli(new quantity<si::pressure>(*other.m_bernoulli)) {

}

Liquid &Liquid::operator=(const Liquid &other) {
  if (this == &other)
    return *this;
  m_static_pressure = std::make_shared<quantity<si::pressure>>(*other.m_static_pressure);
  m_speed = std::make_shared<quantity<si::velocity>>(*other.m_speed);
  m_height = std::make_shared<quantity<si::length>>(*other.m_height);
  m_density = std::make_shared<quantity<si::mass_density>>(*other.m_density);
  m_dynamic_viscosity = std::make_shared<quantity<si::dynamic_viscosity>>(*other.m_dynamic_viscosity);
  m_dynamic_pressure = std::make_shared<quantity<si::pressure>>(*other.m_dynamic_pressure);
  m_potential_pressure = std::make_shared<quantity<si::pressure>>(*other.m_potential_pressure);
  m_bernoulli = std::make_shared<quantity<si::pressure>>(*other.m_bernoulli);
  return *this;
}

const std::shared_ptr<quantity<si::mass_density>> &Liquid::Get_Density() const {
  return m_density;
}

void Liquid::Set_Density(const std::shared_ptr<quantity<si::mass_density>> &density) {
  Liquid::m_density = density;
}

const std::shared_ptr<quantity<si::length>> &Liquid::Get_Height() const {
  return m_height;
}

void Liquid::Set_Height(const std::shared_ptr<quantity<si::length>> &height) {
  Liquid::m_height = height;
}

const std::shared_ptr<quantity<si::pressure>> &Liquid::Get_Dynamic_pressure() const {
  *m_dynamic_pressure = 0.5 * *this->Get_Density() * abs(*this->Get_Speed()) * *this->Get_Speed();
  return m_dynamic_pressure;
}

const std::shared_ptr<quantity<si::pressure>> &Liquid::Get_Potential_pressure() const {
  *m_potential_pressure = si::constants::g * *this->Get_Height() * *this->Get_Density();
  return m_potential_pressure;
}

const std::shared_ptr<quantity<si::pressure>> &Liquid::Get_Bernoulli() const {
  *m_bernoulli = *this->Get_Static_pressure() + *this->Get_Dynamic_pressure() + *this->Get_Potential_pressure();
  return m_bernoulli;
}

const std::shared_ptr<quantity<si::dynamic_viscosity>> &Liquid::Get_Dynamic_viscosity() const {
  return m_dynamic_viscosity;
}

void Liquid::Set_Dynamic_viscosity(const std::shared_ptr<quantity<si::dynamic_viscosity>> &dynamic_viscosity) {
  Liquid::m_dynamic_viscosity = dynamic_viscosity;
}

const std::shared_ptr<quantity<si::velocity>> &Liquid::Get_Speed() const {
  return m_speed;
}

void Liquid::Set_Speed(const std::shared_ptr<quantity<si::velocity>> &speed) {
  Liquid::m_speed = speed;
}

const std::shared_ptr<quantity<si::pressure>> &Liquid::Get_Static_pressure() const {
  return m_static_pressure;
}

void Liquid::Set_Static_pressure(const std::shared_ptr<quantity<si::pressure>> &static_pressure) {
  Liquid::m_static_pressure = static_pressure;
}
}