// Copyright (C) 2013  Davis E. King (davis@dlib.net)
// License: Boost Software License   See LICENSE.txt for the full license.

#include "opaque_types.h"
#include <dlib/python.h>
#include "testing_results.h"
#include <dlib/svm.h>
#include <chrono>

using namespace dlib;
using namespace std;

namespace py = pybind11;

typedef matrix<double,0,1> sample_type;
typedef std::vector<std::pair<unsigned long,double> > sparse_vect;

void np_to_cpp (
    const numpy_image<double>& x_,
    std::vector<matrix<double,0,1>>& samples
)
{
    auto x = make_image_view(x_);
    DLIB_CASSERT(x.nc() > 0);
    DLIB_CASSERT(x.nr() > 0);
    samples.resize(x.nr());
    for (long r = 0; r < x.nr(); ++r)
    {
        samples[r].set_size(x.nc());
        for (long c = 0; c < x.nc(); ++c)
        {
            samples[r](c) = x[r][c];
        }
    }
}

void np_to_cpp (
    const numpy_image<double>& x_,
    const py::array_t<double>& y,
    std::vector<matrix<double,0,1>>& samples,
    std::vector<double>& labels
)
{
    DLIB_CASSERT(y.ndim() == 1 && y.size() > 0);
    labels.assign(y.data(), y.data()+y.size());
    auto x = make_image_view(x_);
    DLIB_CASSERT(x.nr() == y.size(), "The x matrix must have as many rows as y has elements.");
    DLIB_CASSERT(x.nc() > 0);
    samples.resize(x.nr());
    for (long r = 0; r < x.nr(); ++r)
    {
        samples[r].set_size(x.nc());
        for (long c = 0; c < x.nc(); ++c)
        {
            samples[r](c) = x[r][c];
        }
    }
}

template <typename decision_function>
double predict (
    const decision_function& df,
    const typename decision_function::kernel_type::sample_type& samp
)
{
    typedef typename decision_function::kernel_type::sample_type T;
    if (df.basis_vectors.size() == 0)
    {
        return 0;
    }
    else if (is_matrix<T>::value && df.basis_vectors(0).size() != samp.size())
    {
        std::ostringstream sout;
        sout << "Input vector should have " << df.basis_vectors(0).size()
             << " dimensions, not " << samp.size() << ".";
        PyErr_SetString( PyExc_ValueError, sout.str().c_str() );
        throw py::error_already_set();
    }
    return df(samp);
}

inline matrix<double,0,1> np_to_mat(
    const py::array_t<double>& samp
)
{
    matrix<double,0,1> temp(samp.size());

    const auto data = samp.data();
    for (long i = 0; i < temp.size(); ++i)
        temp(i) = data[i];
    return temp;
}

template <typename decision_function>
double normalized_predict (
    const normalized_function<decision_function>& df,
    const typename decision_function::kernel_type::sample_type& samp
)
{
    typedef typename decision_function::kernel_type::sample_type T;
    if (df.function.basis_vectors.size() == 0)
    {
        return 0;
    }
    else if (is_matrix<T>::value && df.function.basis_vectors(0).size() != samp.size())
    {
        std::ostringstream sout;
        sout << "Input vector should have " << df.function.basis_vectors(0).size()
             << " dimensions, not " << samp.size() << ".";
        PyErr_SetString( PyExc_ValueError, sout.str().c_str() );
        throw py::error_already_set();
    }
    return df(samp);
}

template <typename decision_function>
std::vector<double> normalized_predict_vec (
    const normalized_function<decision_function>& df,
    const std::vector<typename decision_function::kernel_type::sample_type>& samps
)
{
    std::vector<double> out;
    out.reserve(samps.size());
    for (auto& x : samps)
        out.push_back(normalized_predict(df,x));
    return out;
}

template <typename decision_function>
py::array_t<double> normalized_predict_np_vec (
    const normalized_function<decision_function>& df,
    const numpy_image<double>& samps_
)
{
    auto samps = make_image_view(samps_);

    if (df.function.basis_vectors(0).size() != samps.nc())
    {
        std::ostringstream sout;
        sout << "Input vector should have " << df.function.basis_vectors(0).size()
             << " dimensions, not " << samps.nc() << ".";
        PyErr_SetString( PyExc_ValueError, sout.str().c_str() );
        throw py::error_already_set();
    }

    py::array_t<double, py::array::c_style> out((size_t)samps.nr());
    matrix<double,0,1> temp(samps.nc());
    auto data = out.mutable_data();
    for (long r = 0; r < samps.nr(); ++r)
    {
        for (long c = 0; c < samps.nc(); ++c)
            temp(c) = samps[r][c];
        *data++ = df(temp);
    }
    return out;
}

template <typename decision_function>
double normalized_predict_np (
    const normalized_function<decision_function>& df,
    const py::array_t<double>& samp
)
{
    typedef typename decision_function::kernel_type::sample_type T;
    if (df.function.basis_vectors.size() == 0)
    {
        return 0;
    }
    else if (is_matrix<T>::value && df.function.basis_vectors(0).size() != samp.size())
    {
        std::ostringstream sout;
        sout << "Input vector should have " << df.function.basis_vectors(0).size()
             << " dimensions, not " << samp.size() << ".";
        PyErr_SetString( PyExc_ValueError, sout.str().c_str() );
        throw py::error_already_set();
    }
    return df(np_to_mat(samp));
}

template <typename kernel_type>
void add_df (
    py::module& m,
    const std::string name
)
{
    typedef decision_function<kernel_type> df_type;
    py::class_<df_type>(m, name.c_str())
        .def("__call__", &predict<df_type>)
        .def_property_readonly("alpha", [](const df_type& df) {return df.alpha;})
        .def_property_readonly("b", [](const df_type& df) {return df.b;})
        .def_property_readonly("kernel_function", [](const df_type& df) {return df.kernel_function;})
        .def_property_readonly("basis_vectors", [](const df_type& df) {
            std::vector<matrix<double,0,1>> temp;
            for (long i = 0; i < df.basis_vectors.size(); ++i)
                temp.push_back(sparse_to_dense(df.basis_vectors(i)));
            return temp;
        })
        .def(py::pickle(&getstate<df_type>, &setstate<df_type>));
}

template <typename kernel_type>
void add_normalized_df (
    py::module& m,
    const std::string name
)
{
    using df_type = normalized_function<decision_function<kernel_type>>;

    py::class_<df_type>(m, name.c_str())
        .def("__call__", &normalized_predict<decision_function<kernel_type>>)
        .def("__call__", &normalized_predict_np<decision_function<kernel_type>>)
        .def("batch_predict", &normalized_predict_vec<decision_function<kernel_type>>)
        .def("batch_predict", &normalized_predict_np_vec<decision_function<kernel_type>>)
        .def_property_readonly("alpha", [](const df_type& df) {return df.function.alpha;})
        .def_property_readonly("b", [](const df_type& df) {return df.function.b;})
        .def_property_readonly("kernel_function", [](const df_type& df) {return df.function.kernel_function;})
        .def_property_readonly("basis_vectors", [](const df_type& df) {
            std::vector<matrix<double,0,1>> temp;
            for (long i = 0; i < df.function.basis_vectors.size(); ++i)
            temp.push_back(sparse_to_dense(df.function.basis_vectors(i)));
            return temp;
        })
    .def_property_readonly("means", [](const df_type& df) {return df.normalizer.means();},
        "Input vectors are normalized by the equation, (x-means)*invstd_devs, before being passed to the underlying RBF function.")
    .def_property_readonly("invstd_devs", [](const df_type& df) {return df.normalizer.std_devs();},
        "Input vectors are normalized by the equation, (x-means)*invstd_devs, before being passed to the underlying RBF function.")
    .def(py::pickle(&getstate<df_type>, &setstate<df_type>));
}

template <typename df_type>
typename df_type::sample_type get_weights(
    const df_type& df
)
{
    if (df.basis_vectors.size() == 0)
    {
        PyErr_SetString( PyExc_ValueError, "Decision function is empty." );
        throw py::error_already_set();
    }
    df_type temp = simplify_linear_decision_function(df);
    return temp.basis_vectors(0);
}

template <typename df_type>
typename df_type::scalar_type get_bias(
    const df_type& df
)
{
    if (df.basis_vectors.size() == 0)
    {
        PyErr_SetString( PyExc_ValueError, "Decision function is empty." );
        throw py::error_already_set();
    }
    return df.b;
}

template <typename df_type>
void set_bias(
    df_type& df,
    double b
)
{
    if (df.basis_vectors.size() == 0)
    {
        PyErr_SetString( PyExc_ValueError, "Decision function is empty." );
        throw py::error_already_set();
    }
    df.b = b;
}

template <typename kernel_type>
void add_linear_df (
    py::module &m,
    const std::string name
)
{
    typedef decision_function<kernel_type> df_type;
    py::class_<df_type>(m, name.c_str())
        .def("__call__", predict<df_type>)
        .def_property_readonly("weights", &get_weights<df_type>)
        .def_property("bias", get_bias<df_type>, set_bias<df_type>)
        .def(py::pickle(&getstate<df_type>, &setstate<df_type>));
}

// ----------------------------------------------------------------------------------------

std::string radial_basis_kernel__repr__(const radial_basis_kernel<sample_type>& item)
{
    std::ostringstream sout;
    sout << "radial_basis_kernel(gamma="<< item.gamma<<")";
    return sout.str();
}

std::string linear_kernel__repr__(const linear_kernel<sample_type>& item)
{
    std::ostringstream sout;
    sout << "linear_kernel()";
    return sout.str();
}

// ----------------------------------------------------------------------------------------

std::string binary_test__str__(const binary_test& item)
{
    std::ostringstream sout;
    sout << "class1_accuracy: "<< item.class1_accuracy << "  class2_accuracy: "<< item.class2_accuracy;
    return sout.str();
}
std::string binary_test__repr__(const binary_test& item) { return "< " + binary_test__str__(item) + " >";}

std::string regression_test__str__(const regression_test& item)
{
    std::ostringstream sout;
    sout << "mean_squared_error: "<< item.mean_squared_error << "  R_squared: "<< item.R_squared;
    sout << "  mean_average_error: "<< item.mean_average_error << "  mean_error_stddev: "<< item.mean_error_stddev;
    return sout.str();
}
std::string regression_test__repr__(const regression_test& item) { return "< " + regression_test__str__(item) + " >";}

std::string ranking_test__str__(const ranking_test& item)
{
    std::ostringstream sout;
    sout << "ranking_accuracy: "<< item.ranking_accuracy << "  mean_ap: "<< item.mean_ap;
    return sout.str();
}
std::string ranking_test__repr__(const ranking_test& item) { return "< " + ranking_test__str__(item) + " >";}

// ----------------------------------------------------------------------------------------

template <typename K>
binary_test  _normalized_test_binary_decision_function (
    const normalized_function<decision_function<K>>& dec_funct,
    const std::vector<typename K::sample_type>& x_test,
    const std::vector<double>& y_test
) { return binary_test(test_binary_decision_function(dec_funct, x_test, y_test)); }

template <typename K>
binary_test  _normalized_test_binary_decision_function_np (
    const normalized_function<decision_function<K>>& dec_funct,
    const numpy_image<double>& x_test_,
    const py::array_t<double>& y_test_
)
{
    std::vector<typename K::sample_type> x_test;
    std::vector<double> y_test;
    np_to_cpp(x_test_,y_test_, x_test,y_test);
    return binary_test(test_binary_decision_function(dec_funct, x_test, y_test));
}

template <typename K>
binary_test  _test_binary_decision_function (
    const decision_function<K>& dec_funct,
    const std::vector<typename K::sample_type>& x_test,
    const std::vector<double>& y_test
) { return binary_test(test_binary_decision_function(dec_funct, x_test, y_test)); }

template <typename K>
regression_test _test_regression_function (
    const decision_function<K>& reg_funct,
    const std::vector<typename K::sample_type>& x_test,
    const std::vector<double>& y_test
) { return regression_test(test_regression_function(reg_funct, x_test, y_test)); }

template < typename K >
ranking_test _test_ranking_function1 (
    const decision_function<K>& funct,
    const std::vector<ranking_pair<typename K::sample_type> >& samples
) { return ranking_test(test_ranking_function(funct, samples)); }

template < typename K >
ranking_test _test_ranking_function2 (
    const decision_function<K>& funct,
    const ranking_pair<typename K::sample_type>& sample
) { return ranking_test(test_ranking_function(funct, sample)); }

// ----------------------------------------------------------------------------------------

void setup_auto_train_rbf_classifier (py::module& m)
{
    m.def("auto_train_rbf_classifier", [](
        const std::vector<matrix<double,0,1>>& x,
        const std::vector<double>& y,
        double max_runtime_seconds,
        bool be_verbose
    ) { return auto_train_rbf_classifier(x,y,std::chrono::microseconds((uint64_t)(max_runtime_seconds*1e6)),be_verbose); },
        py::arg("x"), py::arg("y"), py::arg("max_runtime_seconds"), py::arg("be_verbose")=true,
"requires \n\
    - y contains at least 6 examples of each class.  Moreover, every element in y \n\
      is either +1 or -1. \n\
    - max_runtime_seconds >= 0 \n\
    - len(x) == len(y) \n\
    - all the vectors in x have the same dimension. \n\
ensures \n\
    - This routine trains a radial basis function SVM on the given binary \n\
      classification training data.  It uses the svm_c_trainer to do this.  It also \n\
      uses find_max_global() and 6-fold cross-validation to automatically determine \n\
      the best settings of the SVM's hyper parameters. \n\
    - Note that we interpret y[i] as the label for the vector x[i].  Therefore, the \n\
      returned function, df, should generally satisfy sign(df(x[i])) == y[i] as \n\
      often as possible. \n\
    - The hyperparameter search will run for about max_runtime and will print \n\
      messages to the screen as it runs if be_verbose==true."
    /*!
        requires
            - y contains at least 6 examples of each class.  Moreover, every element in y
              is either +1 or -1.
            - max_runtime_seconds >= 0
            - len(x) == len(y)
            - all the vectors in x have the same dimension.
        ensures
            - This routine trains a radial basis function SVM on the given binary
              classification training data.  It uses the svm_c_trainer to do this.  It also
              uses find_max_global() and 6-fold cross-validation to automatically determine
              the best settings of the SVM's hyper parameters.
            - Note that we interpret y[i] as the label for the vector x[i].  Therefore, the
              returned function, df, should generally satisfy sign(df(x[i])) == y[i] as
              often as possible.
            - The hyperparameter search will run for about max_runtime and will print
              messages to the screen as it runs if be_verbose==true.
    !*/
    );

    m.def("auto_train_rbf_classifier", [](
        const numpy_image<double>& x_,
        const py::array_t<double>& y_,
        double max_runtime_seconds,
        bool be_verbose
    ) {
        std::vector<matrix<double,0,1>> x;
        std::vector<double> y;
        np_to_cpp(x_,y_, x, y);
        return auto_train_rbf_classifier(x,y,std::chrono::microseconds((uint64_t)(max_runtime_seconds*1e6)),be_verbose); },
        py::arg("x"), py::arg("y"), py::arg("max_runtime_seconds"), py::arg("be_verbose")=true,
"requires \n\
    - y contains at least 6 examples of each class.  Moreover, every element in y \n\
      is either +1 or -1. \n\
    - max_runtime_seconds >= 0 \n\
    - len(x.shape(0)) == len(y) \n\
    - x.shape(1) > 0 \n\
ensures \n\
    - This routine trains a radial basis function SVM on the given binary \n\
      classification training data.  It uses the svm_c_trainer to do this.  It also \n\
      uses find_max_global() and 6-fold cross-validation to automatically determine \n\
      the best settings of the SVM's hyper parameters. \n\
    - Note that we interpret y[i] as the label for the vector x[i].  Therefore, the \n\
      returned function, df, should generally satisfy sign(df(x[i])) == y[i] as \n\
      often as possible. \n\
    - The hyperparameter search will run for about max_runtime and will print \n\
      messages to the screen as it runs if be_verbose==true."
    /*!
        requires
            - y contains at least 6 examples of each class.  Moreover, every element in y
              is either +1 or -1.
            - max_runtime_seconds >= 0
            - len(x.shape(0)) == len(y)
            - x.shape(1) > 0
        ensures
            - This routine trains a radial basis function SVM on the given binary
              classification training data.  It uses the svm_c_trainer to do this.  It also
              uses find_max_global() and 6-fold cross-validation to automatically determine
              the best settings of the SVM's hyper parameters.
            - Note that we interpret y[i] as the label for the vector x[i].  Therefore, the
              returned function, df, should generally satisfy sign(df(x[i])) == y[i] as
              often as possible.
            - The hyperparameter search will run for about max_runtime and will print
              messages to the screen as it runs if be_verbose==true.
    !*/
    );

    m.def("reduce", [](const normalized_function<decision_function<radial_basis_kernel<matrix<double,0,1>>>>& df,
            const std::vector<matrix<double,0,1>>& x,
            long num_bv,
            double eps)
        {
            auto out = df;
            // null_trainer doesn't use y so we can leave it empty.
            std::vector<double> y;
            out.function = reduced2(null_trainer(df.function),num_bv,eps).train(x,y);
            return out;
        }, py::arg("df"), py::arg("x"), py::arg("num_basis_vectors"), py::arg("eps")=1e-3
        );

    m.def("reduce", [](const normalized_function<decision_function<radial_basis_kernel<matrix<double,0,1>>>>& df,
            const numpy_image<double>& x_,
            long num_bv,
            double eps)
        {
            std::vector<matrix<double,0,1>> x;
            np_to_cpp(x_, x);
            // null_trainer doesn't use y so we can leave it empty.
            std::vector<double> y;
            auto out = df;
            out.function = reduced2(null_trainer(df.function),num_bv,eps).train(x,y);
            return out;
        }, py::arg("df"), py::arg("x"), py::arg("num_basis_vectors"), py::arg("eps")=1e-3,
"requires \n\
    - eps > 0 \n\
    - num_bv > 0 \n\
ensures \n\
    - This routine takes a learned radial basis function and tries to find a \n\
      new RBF function with num_basis_vectors basis vectors that approximates \n\
      the given df() as closely as possible.  In particular, it finds a \n\
      function new_df() such that new_df(x[i])==df(x[i]) as often as possible. \n\
    - This is accomplished using a reduced set method that begins by using a \n\
      projection, in kernel space, onto a random set of num_basis_vectors \n\
      vectors in x.  Then, L-BFGS is used to further optimize new_df() to match \n\
      df().  The eps parameter controls how long L-BFGS will run, smaller \n\
      values of eps possibly giving better solutions but taking longer to \n\
      execute."
        /*!
            requires
                - eps > 0
                - num_bv > 0
            ensures
                - This routine takes a learned radial basis function and tries to find a
                  new RBF function with num_basis_vectors basis vectors that approximates
                  the given df() as closely as possible.  In particular, it finds a
                  function new_df() such that new_df(x[i])==df(x[i]) as often as possible.
                - This is accomplished using a reduced set method that begins by using a
                  projection, in kernel space, onto a random set of num_basis_vectors
                  vectors in x.  Then, L-BFGS is used to further optimize new_df() to match
                  df().  The eps parameter controls how long L-BFGS will run, smaller
                  values of eps possibly giving better solutions but taking longer to
                  execute.
        !*/
        );
}

// ----------------------------------------------------------------------------------------

void bind_decision_functions(py::module &m)
{
    add_linear_df<linear_kernel<sample_type> >(m, "_decision_function_linear");
    add_linear_df<sparse_linear_kernel<sparse_vect> >(m, "_decision_function_sparse_linear");

    add_df<histogram_intersection_kernel<sample_type> >(m, "_decision_function_histogram_intersection");
    add_df<sparse_histogram_intersection_kernel<sparse_vect> >(m, "_decision_function_sparse_histogram_intersection");

    add_df<polynomial_kernel<sample_type> >(m, "_decision_function_polynomial");
    add_df<sparse_polynomial_kernel<sparse_vect> >(m, "_decision_function_sparse_polynomial");

    py::class_<radial_basis_kernel<sample_type>>(m, "_radial_basis_kernel")
        .def("__repr__", radial_basis_kernel__repr__)
        .def_property_readonly("gamma", [](const radial_basis_kernel<sample_type>& k){return k.gamma; });

    py::class_<linear_kernel<sample_type>>(m, "_linear_kernel")
        .def("__repr__", linear_kernel__repr__);

    add_df<radial_basis_kernel<sample_type> >(m, "_decision_function_radial_basis");
    add_df<sparse_radial_basis_kernel<sparse_vect> >(m, "_decision_function_sparse_radial_basis");
    add_normalized_df<radial_basis_kernel<sample_type>>(m, "_normalized_decision_function_radial_basis");

    setup_auto_train_rbf_classifier(m);

    add_df<sigmoid_kernel<sample_type> >(m, "_decision_function_sigmoid");
    add_df<sparse_sigmoid_kernel<sparse_vect> >(m, "_decision_function_sparse_sigmoid");

    m.def("test_binary_decision_function", _normalized_test_binary_decision_function<radial_basis_kernel<sample_type> >,
        py::arg("function"), py::arg("samples"), py::arg("labels"));
    m.def("test_binary_decision_function", _normalized_test_binary_decision_function_np<radial_basis_kernel<sample_type> >,
        py::arg("function"), py::arg("samples"), py::arg("labels"));

    m.def("test_binary_decision_function", _test_binary_decision_function<linear_kernel<sample_type> >,
        py::arg("function"), py::arg("samples"), py::arg("labels"));
    m.def("test_binary_decision_function", _test_binary_decision_function<sparse_linear_kernel<sparse_vect> >,
        py::arg("function"), py::arg("samples"), py::arg("labels"));
    m.def("test_binary_decision_function", _test_binary_decision_function<radial_basis_kernel<sample_type> >,
        py::arg("function"), py::arg("samples"), py::arg("labels"));
    m.def("test_binary_decision_function", _test_binary_decision_function<sparse_radial_basis_kernel<sparse_vect> >,
        py::arg("function"), py::arg("samples"), py::arg("labels"));
    m.def("test_binary_decision_function", _test_binary_decision_function<polynomial_kernel<sample_type> >,
        py::arg("function"), py::arg("samples"), py::arg("labels"));
    m.def("test_binary_decision_function", _test_binary_decision_function<sparse_polynomial_kernel<sparse_vect> >,
        py::arg("function"), py::arg("samples"), py::arg("labels"));
    m.def("test_binary_decision_function", _test_binary_decision_function<histogram_intersection_kernel<sample_type> >,
        py::arg("function"), py::arg("samples"), py::arg("labels"));
    m.def("test_binary_decision_function", _test_binary_decision_function<sparse_histogram_intersection_kernel<sparse_vect> >,
        py::arg("function"), py::arg("samples"), py::arg("labels"));
    m.def("test_binary_decision_function", _test_binary_decision_function<sigmoid_kernel<sample_type> >,
        py::arg("function"), py::arg("samples"), py::arg("labels"));
    m.def("test_binary_decision_function", _test_binary_decision_function<sparse_sigmoid_kernel<sparse_vect> >,
        py::arg("function"), py::arg("samples"), py::arg("labels"));

    m.def("test_regression_function", _test_regression_function<linear_kernel<sample_type> >,
        py::arg("function"), py::arg("samples"), py::arg("targets"));
    m.def("test_regression_function", _test_regression_function<sparse_linear_kernel<sparse_vect> >,
        py::arg("function"), py::arg("samples"), py::arg("targets"));
    m.def("test_regression_function", _test_regression_function<radial_basis_kernel<sample_type> >,
        py::arg("function"), py::arg("samples"), py::arg("targets"));
    m.def("test_regression_function", _test_regression_function<sparse_radial_basis_kernel<sparse_vect> >,
        py::arg("function"), py::arg("samples"), py::arg("targets"));
    m.def("test_regression_function", _test_regression_function<histogram_intersection_kernel<sample_type> >,
        py::arg("function"), py::arg("samples"), py::arg("targets"));
    m.def("test_regression_function", _test_regression_function<sparse_histogram_intersection_kernel<sparse_vect> >,
        py::arg("function"), py::arg("samples"), py::arg("targets"));
    m.def("test_regression_function", _test_regression_function<sigmoid_kernel<sample_type> >,
        py::arg("function"), py::arg("samples"), py::arg("targets"));
    m.def("test_regression_function", _test_regression_function<sparse_sigmoid_kernel<sparse_vect> >,
        py::arg("function"), py::arg("samples"), py::arg("targets"));
    m.def("test_regression_function", _test_regression_function<polynomial_kernel<sample_type> >,
        py::arg("function"), py::arg("samples"), py::arg("targets"));
    m.def("test_regression_function", _test_regression_function<sparse_polynomial_kernel<sparse_vect> >,
        py::arg("function"), py::arg("samples"), py::arg("targets"));

    m.def("test_ranking_function", _test_ranking_function1<linear_kernel<sample_type> >,
        py::arg("function"), py::arg("samples"));
    m.def("test_ranking_function", _test_ranking_function1<sparse_linear_kernel<sparse_vect> >,
        py::arg("function"), py::arg("samples"));
    m.def("test_ranking_function", _test_ranking_function2<linear_kernel<sample_type> >,
        py::arg("function"), py::arg("sample"));
    m.def("test_ranking_function", _test_ranking_function2<sparse_linear_kernel<sparse_vect> >,
        py::arg("function"), py::arg("sample"));

    py::class_<binary_test>(m, "_binary_test")
        .def("__str__", binary_test__str__)
        .def("__repr__", binary_test__repr__)
        .def_readwrite("class1_accuracy", &binary_test::class1_accuracy,
            "A value between 0 and 1, measures accuracy on the +1 class.")
        .def_readwrite("class2_accuracy", &binary_test::class2_accuracy,
            "A value between 0 and 1, measures accuracy on the -1 class.");

    py::class_<ranking_test>(m, "_ranking_test")
        .def("__str__", ranking_test__str__)
        .def("__repr__", ranking_test__repr__)
        .def_readwrite("ranking_accuracy", &ranking_test::ranking_accuracy,
            "A value between 0 and 1, measures the fraction of times a relevant sample was ordered before a non-relevant sample.")
        .def_readwrite("mean_ap", &ranking_test::mean_ap,
            "A value between 0 and 1, measures the mean average precision of the ranking.");

    py::class_<regression_test>(m, "_regression_test")
        .def("__str__", regression_test__str__)
        .def("__repr__", regression_test__repr__)
        .def_readwrite("mean_average_error", &regression_test::mean_average_error,
            "The mean average error of a regression function on a dataset.")
        .def_readwrite("mean_error_stddev", &regression_test::mean_error_stddev,
            "The standard deviation of the absolute value of the error of a regression function on a dataset.")
        .def_readwrite("mean_squared_error", &regression_test::mean_squared_error,
            "The mean squared error of a regression function on a dataset.")
        .def_readwrite("R_squared", &regression_test::R_squared,
            "A value between 0 and 1, measures the squared correlation between the output of a \n"
            "regression function and the target values.");
}


//////////////////////////////////////////////////////////////////////////////////
// random::poisson_ex::poisson_devroye::detail::q_function::self_checking.hpp   //
//                                                                              //
//                                                                              //
//  (C) Copyright 2010 Erwann Rogard                                            //
//  Use, modification and distribution are subject to the                       //
//  Boost Software License, Version 1.0. (See accompanying file                 //
//  LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)            //
//////////////////////////////////////////////////////////////////////////////////
#ifndef BOOST_RANDOM_POISSON_EXT_DEVROYE_DETAIL_Q_FUNCTION_SELF_CHECKING_ER_2010
#define BOOST_RANDOM_POISSON_EXT_DEVROYE_DETAIL_Q_FUNCTION_SELF_CHECKING_ER_2010
#include <cmath>
#include <string>
#include <boost/format.hpp>
#include <boost/mpl/bool.hpp>
#include <boost/random/poisson_ext/devroye/q_function/using_factorial.hpp>
#include <boost/random/poisson_ext/devroye/q_function/using_sum.hpp>
#include <boost/random/poisson_ext/devroye/q_function/lemma1.hpp>
#include <boost/random/poisson_ext/devroye/q_function/lemma2.hpp>
#include <boost/random/poisson_ext/devroye/q_function/bounds_tol.hpp>

namespace boost{
namespace random{
namespace poisson{
namespace devroye{
namespace q{

    template<typename Q = q::using_sum>
    struct self_checking{
        self_checking(){}
    };

    template<typename T,typename Int,typename P,typename IntT,typename Q>
    T fun(const Int& i_mean,const Int& i_y,const P& p,
        const IntT& converter,self_checking<Q>/*method*/)
    {
        typedef std::string s_;
        typedef boost::format f_;
        s_ exceptions;
        T tol = BOOST_RANDOM_POISSON_DEVROY_BOUNDS_TOL;
        T q;
        bool do_raise0 = false;
        try{
            q = fun<T>(i_mean,i_y,p,converter,Q());
        }catch (std::exception& e) {
            do_raise0 = true;
            exceptions += e.what();
        }

        bool do_raise1
            = lemma1::do_raise_error(q,tol,exceptions,i_mean,i_y,converter);
        bool do_raise2_1
            = lemma2::do_raise_error1(q,tol,exceptions,i_mean,i_y,converter);
        bool do_raise2_2
            = lemma2::do_raise_error2(q,tol,exceptions,i_mean,i_y,converter);
        bool do_raise2_3
            = lemma2::do_raise_error3(q,tol,exceptions,i_mean,i_y,converter);

        if(do_raise0 || do_raise1 || do_raise2_1 || do_raise2_2 || do_raise2_3){
            using namespace boost::math::policies;
            f_ f("(%1%,%2%)"); f % i_mean % i_y;
            const s_ s = "devroye::q::fun<%1%>" + f.str();
            exceptions = "= %1%, error(s) : " + exceptions;
            return raise_evaluation_error(s.c_str(), exceptions.c_str(), q, p);
        }
        return q;
    }

}// q
}// devroye
}// poisson
}// random
}// boost

#endif


#include <boost/accumulators/statistics/pot_quantile.hpp>


//
// Copyright © 2017 Arm Ltd. All rights reserved.
// SPDX-License-Identifier: MIT
//

#pragma once

#include "RefWorkloadUtils.hpp"
#include "TensorBufferArrayView.hpp"

#include <armnn/Tensor.hpp>

#include <DataLayoutIndexed.hpp>

#include <boost/assert.hpp>
#include <boost/numeric/conversion/cast.hpp>

#include <DataLayoutIndexed.hpp>

#include <cmath>
#include <limits>

namespace armnn
{

/// Performs multiplication of an integer with a multiplier which is less than one,
/// using quantized integer arithmetic which is consistent with AndroidNN's CPU executor.
struct QuantizedMultiplierSmallerThanOne
{
public:
    /// Constructs a QuantizedMultiplierSmallerThanOne which will multiply by the given multiplier.
    /// This stores the appropriate integer quantities (derived from the given multiplier) for later use.
    /// The implementation of this function is adapted from Android NN's QuantizeMultiplierSmallerThanOne().
    QuantizedMultiplierSmallerThanOne(float multiplier);

    /// The implementation of this function is adapted from Android NN's MultiplyByQuantizedMultiplierSmallerThanOne().
    int32_t operator*(int32_t rhs) const;

private:
    /// The implementation of this function is adapted from gemmlowp's SaturatingRoundingDoublingHighMul().
    static int32_t SaturatingRoundingDoublingHighMul(int32_t a, int32_t b);

    /// The implementation of this function is adapted from gemmlowp's RoundingDivideByPOT().
    static int32_t RoundingDivideByPOT(int32_t x, int exponent);

    int32_t m_Multiplier;
    int32_t m_RightShift;
};

/// An implementation shared by normal and depthwise convolution.
template<typename ConvData, typename InputType, typename BiasType, typename AccumulatorType>
static void ConvImpl(ConvData data,
                     const InputType* inputData,
                     float inputScale,
                     int32_t inputOffset,
                     const InputType* filterData,
                     float filterScale,
                     int32_t filterOffset,
                     const BiasType* biasData,
                     float outputScale,
                     int32_t outputOffset,
                     const TensorInfo& filterInfo,
                     bool depthwise = false)
{
    if (data.m_Parameters.m_BiasEnabled && !biasData)
    {
        throw InvalidArgumentException("Bias is enabled but the bias data is invalid");
    }

    const TensorInfo& inputInfo  = GetTensorInfo(data.m_Inputs[0]);
    const TensorInfo& outputInfo = GetTensorInfo(data.m_Outputs[0]);

    TensorBufferArrayView<InputType> output(outputInfo.GetShape(),
                                            GetOutputTensorData<InputType>(0, data),
                                            data.m_Parameters.m_DataLayout);

    const armnnUtils::DataLayoutIndexed dataLayoutIndexed(data.m_Parameters.m_DataLayout);

    const unsigned int channelsIndex = dataLayoutIndexed.GetChannelsIndex();
    const unsigned int heightIndex   = dataLayoutIndexed.GetHeightIndex();
    const unsigned int widthIndex    = dataLayoutIndexed.GetWidthIndex();

    unsigned int depthMultiplier = depthwise ? filterInfo.GetShape()[0] : 1;
    unsigned int inputChannels   = depthwise ? filterInfo.GetShape()[1] : filterInfo.GetShape()[channelsIndex];
    unsigned int outputChannels  = depthwise ? inputChannels * depthMultiplier : filterInfo.GetShape()[0];

    unsigned int batchSize    = outputInfo.GetShape()[0];
    unsigned int outputHeight = outputInfo.GetShape()[heightIndex];
    unsigned int outputWidth  = outputInfo.GetShape()[widthIndex];
    unsigned int inputHeight  = inputInfo.GetShape()[heightIndex];
    unsigned int inputWidth   = inputInfo.GetShape()[widthIndex];

    unsigned int filterHeight = depthwise ? filterInfo.GetShape()[2] : filterInfo.GetShape()[heightIndex];
    unsigned int filterWidth  = depthwise ? filterInfo.GetShape()[3] : filterInfo.GetShape()[widthIndex];

    unsigned int paddingTop  = data.m_Parameters.m_PadTop;
    unsigned int paddingLeft = data.m_Parameters.m_PadLeft;
    unsigned int xStride     = data.m_Parameters.m_StrideX;
    unsigned int yStride     = data.m_Parameters.m_StrideY;

    // The world's least efficient convolution.
    for (unsigned int batchIdx = 0; batchIdx < batchSize; batchIdx++)
    {
        for (unsigned int cOutput = 0; cOutput < outputChannels; cOutput++)
        {
            for (unsigned int yOutput = 0; yOutput < outputHeight; yOutput++)
            {
                for (unsigned int xOutput = 0; xOutput < outputWidth; xOutput++)
                {
                    // This loop goes over each output element.
                    AccumulatorType sum = AccumulatorType();

                    // For depthwise, each output channel corresponds to exactly one input channel.
                    // For normal, must loop over each input channel.
                    for (unsigned int cInput = 0; cInput < (depthwise ? 1 : inputChannels); cInput++)
                    {
                        unsigned int depthwiseMultiplierIdx = 0;
                        if (depthwise)
                        {
                            cInput = cOutput / depthMultiplier;
                            depthwiseMultiplierIdx = cOutput % depthMultiplier;
                        }

                        for (unsigned int yFilter = 0; yFilter < filterHeight; yFilter++)
                        {
                            for (unsigned int xFilter = 0; xFilter < filterWidth; xFilter++)
                            {
                                // This loop goes over each input element for each output element.

                                unsigned int filterIndex = 0;

                                // Since dimensionality of kernel depends on depthwiseness, so does index.
                                if (depthwise)
                                {
                                    filterIndex = depthwiseMultiplierIdx * filterWidth * filterHeight * inputChannels +
                                                  cInput * filterWidth * filterHeight +
                                                  yFilter * filterWidth +
                                                  xFilter;
                                }
                                else
                                {
                                    if (data.m_Parameters.m_DataLayout == DataLayout::NHWC)
                                    {
                                        filterIndex = cOutput * filterHeight * filterWidth * inputChannels +
                                                      yFilter * filterWidth * inputChannels +
                                                      xFilter * inputChannels +
                                                      cInput;
                                    }
                                    else
                                    {
                                        filterIndex = cOutput * filterWidth * filterHeight * inputChannels +
                                                      cInput  * filterWidth * filterHeight +
                                                      yFilter * filterWidth +
                                                      xFilter;
                                    }
                                }

                                AccumulatorType filterValue = filterData[filterIndex] -
                                    boost::numeric_cast<AccumulatorType>(filterOffset);

                                unsigned int yInput = yOutput * yStride + yFilter;
                                unsigned int xInput = xOutput * xStride + xFilter;

                                AccumulatorType inputValue;

                                // Check if we're in the padding.
                                if (yInput < paddingTop || yInput >= inputHeight + paddingTop ||
                                    xInput < paddingLeft || xInput >= inputWidth + paddingLeft )
                                {
                                    inputValue = AccumulatorType();
                                }
                                else
                                {
                                    unsigned int inputIndex;

                                    if (data.m_Parameters.m_DataLayout == DataLayout::NHWC)
                                    {
                                        inputIndex = batchIdx * inputHeight * inputWidth  * inputChannels +
                                                     (yInput - paddingTop) * inputWidth * inputChannels +
                                                     (xInput - paddingLeft) * inputChannels +
                                                     cInput;

                                    }
                                    else
                                    {
                                        inputIndex = batchIdx * inputWidth * inputHeight * inputChannels +
                                                     inputWidth * inputHeight * cInput +
                                                     inputWidth * (yInput - paddingTop) +
                                                     xInput - paddingLeft;
                                    }

                                    inputValue = inputData[inputIndex] -
                                                    boost::numeric_cast<AccumulatorType>(inputOffset);

                                }
                                sum += filterValue * inputValue;
                            }
                        }
                    }

                    if (data.m_Parameters.m_BiasEnabled)
                    {
                        sum += biasData[cOutput];
                    }

                    if (outputScale != 0.0f)
                    {
                        float multiplier = (inputScale * filterScale) / outputScale;
                        // Apply the multiplier to sum, but do so using some quantized arithmetic which is consistent
                        // with the AndroidNN CPU implementation. This should be (roughly) equivalent to:
                        //  sum = std::round(multiplier * sum + outputOffset);
                        sum = boost::numeric_cast<AccumulatorType>(
                                QuantizedMultiplierSmallerThanOne(multiplier) * boost::numeric_cast<int32_t>(sum))
                            + boost::numeric_cast<AccumulatorType>(outputOffset);
                        sum = std::min<AccumulatorType>(std::max<AccumulatorType>(sum, 0), 255);
                    }

                    output.Get(batchIdx, cOutput, yOutput, xOutput) = boost::numeric_cast<InputType>(sum);
                }
            }
        }
    }
}

} //namespace armnn


#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>;


/* curve25519.cpp - a thin layer around libsodium low-level interfaces
 *
 * Copyright (C) 2021, LWE-PVSS
 *
 * 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 <algorithm> // define std::min
#include <stdexcept>

#include <NTL/ZZ.h>
#include "scalar25519.hpp"
#include "point25519.hpp"

namespace CRV25519 {

size_t Point::counter = 0;
size_t Point::timer = 0;
Point Point::basePoint;
Point Point::identityPoint = Point::init(); // forcing a run of the init function

Point Point::init() {
    static bool firstTime = true; // ensure that init only happens once
    // FIXME: not thread safe
    if (!firstTime) // return a dummy point
        return Point();
    firstTime = false;

    int ret = sodium_init();
    if (ret != 0) {
        throw std::runtime_error("libsodium failed to initialize, #errno="+std::to_string(ret));
    }

    // initialize the point base
    Scalar one = Scalar().setInteger(1);
    ret = crypto_scalarmult_ed25519_base_noclamp(basePoint.bytes, one.bytes);
    if (ret != 0) {
        throw std::runtime_error("failed to initialize basePoint, #errno="+std::to_string(ret));
    }

    // The modulus of the main group: 2^{252} + 27742317777372353535851937790883648493
    // NTL::ZZ_p::init((NTL::to_ZZ(1L)<<252) + NTL::conv<NTL::ZZ>("27742317777372353535851937790883648493"));

    return basePoint - basePoint; // this will be assigned to the global identityPoint
}

// A helper function, naive implementation
Scalar innerProduct(const Scalar* a, const Scalar* b, size_t n) {
    Scalar ret;
    for (size_t i=0; i<n; i++) {
        ret += a[i] * b[i];
    }
    return ret;
}

// Computes *this modulo |other|. Both arguments are interpreted as
// signed integers in the range [-P/2,P/2), the result is set in the
// range [-|other|/2, |other|/2), then mapped to Z_P.
Scalar& Scalar::operator%=(const Scalar& other) {
    static NTL::ZZ P = (NTL::to_ZZ(1L)<<252) + NTL::conv<NTL::ZZ>("27742317777372353535851937790883648493");
    NTL::ZZ x = NTL::ZZFromBytes(bytes, crypto_core_ed25519_SCALARBYTES);
    NTL::ZZ y = NTL::ZZFromBytes(other.bytes, crypto_core_ed25519_SCALARBYTES);
    if (x >= P/2) x -= P; // map to the range [-P/2, P/2)
    if (y >= P/2) {
        y = abs(y-P); // map to the range [-P/2, P/2), then take absolute value
    }
    x %= y;        // in [0,y-1]
    if (x > y/2) {
        x = P+x-y; // map to [-y/2,y/2), then map to an element in Z_P
    }
    BytesFromZZ(bytes, x, crypto_core_ed25519_SCALARBYTES);
    return *this;
}

} /* end of namespace CRV25519 */