#pragma optimize ("", off)

#include "tetmesher.h"

//#include <yixin_loader.h>
#include <Eigen/Core>
#include <igl/slim.h>
#include <igl/per_vertex_normals.h>
#include <igl/unproject_onto_mesh.h>
#include <igl/harmonic.h>
#include <igl/grad.h>
#include <igl/colormap.h>
#include <igl/facet_components.h>

#include <glm/gtx/component_wise.hpp>
#include <modules/base/algorithm/meshutils.h>
#include <modules/opengl/rendering/meshdrawergl.h>

//#include <utils/utils.h>
#include <modules/opengl/openglutils.h>
#include <modules/opengl/texture/textureutils.h>

#include <modules/segmentangling/util/defer.h>

#include <igl/copyleft/marching_cubes.h>
//#include <igl/components.h>
#include <igl/writeOFF.h>

//#include <TetWild.h>

#include <inviwo/core/datastructures/volume/volume.h>
#include <inviwo/core/datastructures/volume/volumeram.h>

#include "make_tet_mesh.h"
#include "make_signed_distance.h"
#include "read_obj.h"
#include "feature.h"
#include "trimesh.h"
#include <sstream>
#include <fstream>
#include <cstdio>
#include <cstring>
#include <ctime>

namespace inviwo {

TetMesher::TetMesher()
    : Processor()
    , _inport("volumeInport")
    , _triangleVertexOutport("_triangleVertexOutport")
    , _triangleIndexOutport("_triangleIndexOutport")
    , _vertexOutport("_vertexOutport")
    , _tetIndexOutport("_tetIndexOutport")
    //, _volumeFilename("_volumeFilename", "Volume Filename")
    , _componentCutoff("_componentCutoff", "Component Cutoff Ratio", 0.75f, 0.f, 1.f)
    , _action("_action", "Go")
    , _dilationOrEpsilonSize("_dilationSize", "Dilation Size")
    , _useGTet("_useGTet", "Use GTet? (Quartet else)")
{
    addPort(_inport);
    addPort(_triangleVertexOutport);
    addPort(_triangleIndexOutport);

    addPort(_vertexOutport);
    addPort(_tetIndexOutport);

    addProperty(_dilationOrEpsilonSize);
    addProperty(_useGTet);
    //addProperty(_volumeFilename);

    _action.onChange([this]() { action(); });
    addProperty(_action);

    _componentCutoff.onChange([this]() { updateFilter(); });
    addProperty(_componentCutoff);
}

void TetMesher::process() {
    if (_isFirstFrame) {
        _triangleVertexOutport.setData(std::make_shared<Eigen::MatrixXd>());
        _triangleIndexOutport.setData(std::make_shared<Eigen::MatrixXi>());
        _vertexOutport.setData(std::make_shared<Eigen::MatrixXd>());
        _tetIndexOutport.setData(std::make_shared<Eigen::MatrixXi>());

        _isFirstFrame = false;
    }
}

std::tuple<Eigen::MatrixXd, Eigen::MatrixXi> TetMesher::marchingCubes(const VolumeRAM& v) {
    Eigen::MatrixXd V;
    Eigen::MatrixXi F;

    //
    // Marching cubes
    //
    LogInfo("Compute marching cubes");
    Eigen::MatrixXd GP(
        (v.getDimensions().x + 2) * (v.getDimensions().y + 2) * (v.getDimensions().z + 2),
        3
    );
    Eigen::VectorXd SV(GP.rows());

    int readcount = 0;
    //int appendcount = 0;
    for (int zi = 0; zi < v.getDimensions().z + 2; zi++) {
        for (int yi = 0; yi < v.getDimensions().y + 2; yi++) {
            for (int xi = 0; xi < v.getDimensions().x + 2; xi++) {
                if (xi == 0 || yi == 0 || zi == 0 || xi == (v.getDimensions().x + 1) ||
                    yi == (v.getDimensions().y + 1) || zi == (v.getDimensions().z + 1))
                {
                    SV[readcount] = -1.0;
                }
                else {
                    // We subtract 1 to account for the boundary
                    SV[readcount] = v.getAsDouble({ xi - 1, yi - 1, zi - 1 });
                    //SV[readcount] = double(data[appendcount]);
                    //appendcount += 1;
                }
                GP.row(readcount) = Eigen::RowVector3d(xi, yi, zi);
                readcount += 1;
            }
        }
    }
    getProgressBar().updateProgress(0.3f);

    //delete data;

    //datfile.m_bb_min = Eigen::RowVector3d(1.0, 1.0, 1.0);
    //datfile.m_bb_max = Eigen::RowVector3d(datfile.w, datfile.h, datfile.d);

    //cout << "Running Marching Cubes..." << endl;
    igl::copyleft::marching_cubes(
        SV,
        GP,
        v.getDimensions().x + 2,
        v.getDimensions().y + 2,
        v.getDimensions().z + 2,
        V,
        F
    );

    LogInfo("Finished marching cubes");
    LogInfo("Marching cubes model has " << V.rows() << " vertices and " << F.rows() << " faces");
    getProgressBar().updateProgress(0.6f);

    return { V, F };
}

std::tuple<Eigen::VectorXi, std::vector<int>> TetMesher::findConnectedComponents(const Eigen::MatrixXd& V, const Eigen::MatrixXi& F) {
    //
    // remove_garbage_components
    //
    LogInfo("Computing connected components...");
    Eigen::VectorXi components;
    igl::facet_components(F, components);

    LogInfo("Counting connected components...");
    std::vector<int> component_count;
    component_count.resize(components.maxCoeff());
    for (int i = 0; i < V.rows(); i++) {
        component_count[components[i]] += 1;
    }
    LogInfo("The model has " << component_count.size() <<
        " connected components.");

    return { std::move(components), std::move(component_count) };
}

Eigen::MatrixXi TetMesher::filterConnectedComponents(const Eigen::MatrixXi& F,
        const Eigen::VectorXi& components, const std::vector<int>& componentsCount)
{
    Eigen::MatrixXi newF;

    LogInfo("Finding component with most vertices...");
    int max_component = -1;
    int max_component_count = 0;
    int min_component = -1;
    int min_component_count = std::numeric_limits<int>::max();

    for (int i = 0; i < componentsCount.size(); i++) {
        if (max_component_count < componentsCount[i]) {
            max_component = i;
            max_component_count = componentsCount[i];
        }

        if (min_component_count > componentsCount[i]) {
            min_component = i;
            min_component_count = componentsCount[i];
        }

    }
    LogInfo("Component " << max_component <<
        " has the most vertices with a count of " <<
        max_component_count);

    LogInfo("Component " << min_component <<
        " has the least vertices with a count of " <<
        min_component_count);

    int cutoffComponentCount = int(
        _componentCutoff * (float(max_component_count) - float(min_component_count)) + float(min_component_count)
    );

    LogInfo("Deleting components with count < " << cutoffComponentCount);
    newF.resize(F.rows(), 3);

    int fcount = 0;
    for (int i = 0; i < F.rows(); i++) {
        bool keep = true;
        for (int j = 0; j < 3; j++) {
            if (componentsCount[components[F(i, j)]] <= cutoffComponentCount) {
                keep = false;
                break;
            }
        }
        if (keep) {
            //nKeep++;
            newF.row(fcount++) = F.row(i);
        }
    }
    int nKeep = std::accumulate(
        componentsCount.begin(),
        componentsCount.end(),
        0,
        [cutoffComponentCount](int i, int j) {
        if (j <= cutoffComponentCount) {
            return i;
        }
        else {
            return i + 1;
        }
    }
    );

    LogInfo("Keeping " << nKeep << " out of " << componentsCount.size() << " components");

    LogInfo("Largest component of model has " << fcount << " faces and " <<
        newF.maxCoeff() << " vertices");
    newF.conservativeResize(fcount, 3);

    return newF;
}

void TetMesher::action() {
    std::shared_ptr<const Volume> vol = _inport.getData();

    getProgressBar().show();

    const VolumeRAM* v = vol->getRepresentation<VolumeRAM>();

    Eigen::MatrixXd V;
    std::tie(V, _F) = marchingCubes(*v);

    std::tie(_components, nComponents) = findConnectedComponents(V, _F);

    Eigen::MatrixXi newF = filterConnectedComponents(_F, _components, nComponents);

    //std::sort(component_count.begin(), component_count.end());
    //LogInfo("N Components:");
    //std::for_each(
    //    component_count.begin(),
    //    component_count.end(),
    //    [this](int i) { LogInfo(i); }
    //);

    //Eigen::VectorXd V2 = std::move(V.col(2));
    //V.col(2) = std::move(V.col(1));
    //V.col(1) = std::move(V2);

    std::shared_ptr<Eigen::MatrixXd> TV = std::make_shared<Eigen::MatrixXd>();
    std::shared_ptr<Eigen::MatrixXi> TT = std::make_shared<Eigen::MatrixXi>();
    *TV = V;
    *TT = newF;

    _triangleVertexOutport.setData(TV);
    _triangleIndexOutport.setData(TT);

    _vertexOutport.setData(std::make_shared<Eigen::MatrixXd>());
    _tetIndexOutport.setData(std::make_shared<Eigen::MatrixXi>());

    getProgressBar().updateProgress(1.f);

    //if (_useGTet) {
    //    dispatchPool([this, V, newF]() {
    //        //
    //        // gtet
    //        //
    //        args.is_quiet = true;
    //        args.max_pass = 5;
    //        args.filter_energy = 200;
    //        args.i_epsilon = _dilationOrEpsilonSize;
    //        args.i_dd = 100;
    //        args.i_ideal_edge_length = 10;

    //        std::shared_ptr<Eigen::MatrixXd> TV = std::make_shared<Eigen::MatrixXd>();
    //        std::shared_ptr<Eigen::MatrixXi> TT = std::make_shared<Eigen::MatrixXi>();
    //        TetWild::gtet(
    //            V,
    //            newF,
    //            *TV,
    //            *TT
    //        );

    //        dispatchFront([this, TV, TT]() {
    //            _vertexOutport.setData(TV);
    //            _tetIndexOutport.setData(TT);
    //        });
    //    });
    //}
    //else
    {
        // Use Quartet

        std::vector<Vec3f> surf_x;
        for (int i = 0; i < V.rows(); ++i) {
            surf_x.push_back(
                Vec3f(V(i, 0), V(i, 1), V(i, 2))
            );
        }

        std::vector<Vec3i> surf_tri;
        for (int i = 0; i < newF.rows(); ++i) {
            surf_tri.push_back(
                Vec3i(newF(i, 0), newF(i, 1), newF(i, 2))
            );
        }

        // Find bounding box
        Vec3f xmin = surf_x[0], xmax = surf_x[0];
        for (size_t i = 1; i<surf_x.size(); ++i)
            update_minmax(surf_x[i], xmin, xmax);

        // Build triangle mesh data structure
        TriMesh trimesh(surf_x, surf_tri);

        // Make the level set
        float dx = _dilationOrEpsilonSize.get() / 2.f;

        // Determining dimensions of voxel grid.
        // Round up to ensure voxel grid completely contains bounding box.
        // Also add padding of 2 grid points around the bounding box.
        // NOTE: We add 5 here so as to add 4 grid points of padding, as well as
        // 1 grid point at the maximal boundary of the bounding box
        // ie: (xmax-xmin)/dx + 1 grid points to cover one axis of the bounding box
        Vec3f origin = xmin - Vec3f(2 * dx);
        int ni = (int)std::ceil((xmax[0] - xmin[0]) / dx) + 5,
            nj = (int)std::ceil((xmax[1] - xmin[1]) / dx) + 5,
            nk = (int)std::ceil((xmax[2] - xmin[2]) / dx) + 5;

        SDF sdf(origin, dx, ni, nj, nk); // Initialize signed distance field.
        std::printf("making %dx%dx%d level set\n", ni, nj, nk);
        make_signed_distance(surf_tri, surf_x, sdf);

        // Then the tet mesh
        TetMesh mesh;

        bool optimize = false;
        make_tet_mesh(mesh, sdf, optimize, false, false); //231

        std::shared_ptr<Eigen::MatrixXd> TV = std::make_shared<Eigen::MatrixXd>();
        std::shared_ptr<Eigen::MatrixXi> TT = std::make_shared<Eigen::MatrixXi>();

        const std::vector<Vec3f>& v = mesh.verts();
        TV->resize(v.size(), 3);
        const std::vector<Vec4i>& t = mesh.tets();
        TT->resize(t.size(), 4);

        for (size_t i = 0; i < v.size(); ++i) {
            TV->row(i) = Eigen::RowVector3d(v[i][0], v[i][1], v[i][2]);
        }

        for (size_t i = 0; i < t.size(); ++i) {
            TT->row(i) = Eigen::RowVector4i(t[i][0], t[i][1], t[i][2], t[i][3]);
        }

        dispatchFront([this, TV, TT]() {
            _vertexOutport.setData(TV);
            _tetIndexOutport.setData(TT);
        });
    }
}

void TetMesher::updateFilter() {
    if (_F.rows() == 0 || _components.rows() == 0 || nComponents.empty()) {
        return;
    }
    Eigen::MatrixXi newF = filterConnectedComponents(_F, _components, nComponents);

    std::shared_ptr<Eigen::MatrixXi> TT = std::make_shared<Eigen::MatrixXi>();
    *TT = std::move(newF);
    _triangleIndexOutport.setData(TT);
}

//////////////////////////////////////////////////////////////////////////////////////////
//                                  Inviiiiiiwo
//////////////////////////////////////////////////////////////////////////////////////////

const ProcessorInfo TetMesher::processorInfo_ {
    "bock.tetmesher",  // Class identifier
    "TetMesher",            // Display name
    "Volume Operation",            // Category
    CodeState::Experimental,             // Code state
    Tags::GL                       // Tags
};

const ProcessorInfo TetMesher::getProcessorInfo() const {
    return processorInfo_;
}

}  // namespace

#pragma optimize ("", on)


//---------------------------------------------------------------------------//
// Copyright (c) 2018-2021 Mikhail Komarov <nemo@nil.foundation>
// Copyright (c) 2020-2021 Nikita Kaskov <nbering@nil.foundation>
// Copyright (c) 2020-2021 Ilias Khairullin <ilias@nil.foundation>
//
// MIT License
//
// 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.
//---------------------------------------------------------------------------//
// @file This module implements two binding commitment schemes used in the Groth16
// aggregation.
// The first one is a commitment scheme that commits to a single vector $a$ of
// length n in the second base group $G_1$ (for example):
// * it requires a structured SRS $v_1$ of the form $(h,h^u,h^{u^2}, ...
// ,g^{h^{n-1}})$ with $h \in G_2$ being a random generator of $G_2$ and $u$ a
// random scalar (coming from a power of tau ceremony for example)
// * it requires a second structured SRS $v_2$ of the form $(h,h^v,h^{v^2},
// ...$ with $v$ being a random scalar different than u (coming from another
// power of tau ceremony for example)
// The Commitment is a tuple $(\prod_{i=0}^{n-1} e(a_i,v_{1,i}),
// \prod_{i=0}^{n-1} e(a_i,v_{2,i}))$
//
// The second one takes two vectors $a \in G_1^n$ and $b \in G_2^n$ and commits
// to them using a similar approach as above. It requires an additional SRS
// though:
// * $v_1$ and $v_2$ stay the same
// * An additional tuple $w_1 = (g^{u^n},g^{u^{n+1}},...g^{u^{2n-1}})$ and $w_2 =
// (g^{v^n},g^{v^{n+1},...,g^{v^{2n-1}})$ where $g$ is a random generator of
// $G_1$
// The commitment scheme returns a tuple:
// * $\prod_{i=0}^{n-1} e(a_i,v_{1,i})e(w_{1,i},b_i)$
// * $\prod_{i=0}^{n-1} e(a_i,v_{2,i})e(w_{2,i},b_i)$
//
// The second commitment scheme enables to save some KZG verification in the
// verifier of the Groth16 verification protocol since we pack two vectors in
// one commitment.

#ifndef CRYPTO3_R1CS_GG_PPZKSNARK_AGGREGATE_IPP2_COMMITMENT_HPP
#define CRYPTO3_R1CS_GG_PPZKSNARK_AGGREGATE_IPP2_COMMITMENT_HPP

#include <tuple>
#include <vector>
#include <type_traits>

#include <boost/assert.hpp>
#include <boost/iterator/zip_iterator.hpp>
#include <boost/accumulators/accumulators.hpp>

#include <nil/crypto3/algebra/type_traits.hpp>

#include <nil/crypto3/algebra/algorithms/pair.hpp>

namespace nil {
    namespace crypto3 {
        namespace zk {
            namespace snark {
                /// Both commitment outputs a pair of $F_q^k$ element.
                template<typename CurveType>
                using r1cs_gg_ppzksnark_ipp2_commitment_output =
                    std::pair<typename CurveType::gt_type::value_type, typename CurveType::gt_type::value_type>;

                /// Key is a generic commitment key that is instantiated with g and h as basis,
                /// and a and b as powers.
                template<typename GroupType>
                struct r1cs_gg_ppzksnark_ipp2_commitment_key {
                    typedef GroupType group_type;
                    typedef typename group_type::curve_type curve_type;
                    typedef typename curve_type::scalar_field_type field_type;

                    typedef typename group_type::value_type group_value_type;
                    typedef typename field_type::value_type field_value_type;

                    /// Exponent is a
                    std::vector<group_value_type> a;
                    /// Exponent is b
                    std::vector<group_value_type> b;

                    /// Returns true if commitment keys have the exact required length.
                    /// It is necessary for the IPP scheme to work that commitment
                    /// key have the exact same number of arguments as the number of proofs to
                    /// aggregate.
                    inline bool has_correct_len(std::size_t n) const {
                        return a.size() == n && n == b.size();
                    }

                    /// Returns both vectors scaled by the given vector entrywise.
                    /// In other words, it returns $\{v_i^{s_i}\}$
                    template<
                        typename InputIterator,
                        typename ValueType = typename std::iterator_traits<InputIterator>::value_type,
                        typename std::enable_if<std::is_same<field_value_type, ValueType>::value, bool>::type = true>
                    r1cs_gg_ppzksnark_ipp2_commitment_key<group_type> scale(InputIterator s_first,
                                                                            InputIterator s_last) const {
                        BOOST_ASSERT(has_correct_len(std::distance(s_first, s_last)));

                        r1cs_gg_ppzksnark_ipp2_commitment_key<group_type> result;
                        std::for_each(boost::make_zip_iterator(boost::make_tuple(s_first, a.begin(), b.begin())),
                                      boost::make_zip_iterator(boost::make_tuple(s_last, a.end(), b.end())),
                                      [&](const boost::tuple<const field_value_type &, const group_value_type &,
                                                             const group_value_type &> &t) {
                                          result.a.emplace_back(t.template get<1>() * t.template get<0>());
                                          result.b.emplace_back(t.template get<2>() * t.template get<0>());
                                      });

                        return result;
                    }

                    /// Returns the left and right commitment key part. It makes copy.
                    std::pair<r1cs_gg_ppzksnark_ipp2_commitment_key<group_type>,
                              r1cs_gg_ppzksnark_ipp2_commitment_key<group_type>>
                        split(std::size_t at) const {
                        BOOST_ASSERT(a.size() == b.size());
                        BOOST_ASSERT(at > 0 && at < a.size());

                        r1cs_gg_ppzksnark_ipp2_commitment_key<group_type> result_l;
                        r1cs_gg_ppzksnark_ipp2_commitment_key<group_type> result_r;

                        auto a_it = a.begin();
                        auto b_it = b.begin();
                        while (a_it != a.begin() + at && b_it != b.begin() + at) {
                            result_l.a.emplace_back(*a_it);
                            result_l.b.emplace_back(*b_it);
                            ++a_it;
                            ++b_it;
                        }
                        while (a_it != a.end() && b_it != b.end()) {
                            result_r.a.emplace_back(*a_it);
                            result_r.b.emplace_back(*b_it);
                            ++a_it;
                            ++b_it;
                        }

                        return std::make_pair(result_l, result_r);
                    }

                    /// Takes a left and right commitment key and returns a commitment
                    /// key $left \circ right^{scale} = (left_i*right_i^{scale} ...)$. This is
                    /// required step during GIPA recursion.
                    r1cs_gg_ppzksnark_ipp2_commitment_key<group_type>
                        compress(const r1cs_gg_ppzksnark_ipp2_commitment_key<group_type> &right,
                                 const field_value_type &scale) const {
                        BOOST_ASSERT(a.size() == right.a.size());
                        BOOST_ASSERT(b.size() == right.b.size());
                        BOOST_ASSERT(a.size() == b.size());

                        r1cs_gg_ppzksnark_ipp2_commitment_key<group_type> result;

                        std::for_each(
                            boost::make_zip_iterator(
                                boost::make_tuple(a.begin(), b.begin(), right.a.begin(), right.b.begin())),
                            boost::make_zip_iterator(boost::make_tuple(a.end(), b.end(), right.a.end(), right.b.end())),
                            [&](const boost::tuple<const group_value_type &, const group_value_type &,
                                                   const group_value_type &, const group_value_type &> &t) {
                                result.a.emplace_back(t.template get<0>() + t.template get<2>() * scale);
                                result.b.emplace_back(t.template get<1>() + t.template get<3>() * scale);
                            });

                        return result;
                    }

                    /// Returns the first values in the vector of v1 and v2 (respectively
                    /// w1 and w2). When commitment key is of size one, it's a proxy to get the
                    /// final values.
                    std::pair<group_value_type, group_value_type> first() const {
                        return std::make_pair(a.front(), b.front());
                    }
                };

                /// Commitment key used by the "single" commitment on G1 values as
                /// well as in the "pair" commitment.
                /// It contains $\{h^a^i\}_{i=1}^n$ and $\{h^b^i\}_{i=1}^n$
                template<typename CurveType>
                using r1cs_gg_ppzksnark_ipp2_vkey = r1cs_gg_ppzksnark_ipp2_commitment_key<typename CurveType::g2_type>;

                /// Commitment key used by the "pair" commitment. Note the sequence of
                /// powers starts at $n$ already.
                /// It contains $\{g^{a^{n+i}}\}_{i=1}^n$ and $\{g^{b^{n+i}}\}_{i=1}^n$
                template<typename CurveType>
                using r1cs_gg_ppzksnark_ipp2_wkey = r1cs_gg_ppzksnark_ipp2_commitment_key<typename CurveType::g1_type>;

                template<typename CurveType>
                struct r1cs_gg_ppzksnark_ipp2_commitment {
                    typedef CurveType curve_type;
                    typedef typename curve_type::pairing pairing;

                    typedef r1cs_gg_ppzksnark_ipp2_wkey<CurveType> wkey_type;
                    typedef r1cs_gg_ppzksnark_ipp2_vkey<CurveType> vkey_type;

                    typedef typename wkey_type::group_value_type g1_value_type;
                    typedef typename vkey_type::group_value_type g2_value_type;
                    typedef typename curve_type::gt_type::value_type gt_value_type;

                    typedef r1cs_gg_ppzksnark_ipp2_commitment_output<CurveType> output_type;

                    /// Commits to a tuple of G1 vector and G2 vector in the following way:
                    /// $T = \prod_{i=0}^n e(A_i, v_{1,i})e(B_i,w_{1,i})$
                    /// $U = \prod_{i=0}^n e(A_i, v_{2,i})e(B_i,w_{2,i})$
                    /// Output is $(T,U)$
                    template<typename InputG1Iterator, typename InputG2Iterator,
                             typename ValueType1 = typename std::iterator_traits<InputG1Iterator>::value_type,
                             typename ValueType2 = typename std::iterator_traits<InputG2Iterator>::value_type,
                             typename std::enable_if<std::is_same<g1_value_type, ValueType1>::value, bool>::type = true,
                             typename std::enable_if<std::is_same<g2_value_type, ValueType2>::value, bool>::type = true>
                    static output_type pair(const vkey_type &vkey, const wkey_type &wkey, InputG1Iterator a_first,
                                            InputG1Iterator a_last, InputG2Iterator b_first, InputG2Iterator b_last) {
                        BOOST_ASSERT(vkey.has_correct_len(std::distance(a_first, a_last)));
                        BOOST_ASSERT(wkey.has_correct_len(std::distance(b_first, b_last)));
                        BOOST_ASSERT(std::distance(a_first, a_last) == std::distance(b_first, b_last));

                        // (A * v)
                        gt_value_type t1 = gt_value_type::one();
                        std::for_each(boost::make_zip_iterator(boost::make_tuple(a_first, vkey.a.begin())),
                                      boost::make_zip_iterator(boost::make_tuple(a_last, vkey.a.end())),
                                      [&](const boost::tuple<const g1_value_type &, const g2_value_type &> &t) {
                                          t1 = t1 * algebra::pair<curve_type>(t.template get<0>(), t.template get<1>());
                                      });

                        // (B * v)
                        gt_value_type t2 = gt_value_type::one();
                        std::for_each(boost::make_zip_iterator(boost::make_tuple(wkey.a.begin(), b_first)),
                                      boost::make_zip_iterator(boost::make_tuple(wkey.a.end(), b_last)),
                                      [&](const boost::tuple<const g1_value_type &, const g2_value_type &> &t) {
                                          t2 = t2 * algebra::pair<curve_type>(t.template get<0>(), t.template get<1>());
                                      });

                        gt_value_type u1 = gt_value_type::one();
                        std::for_each(boost::make_zip_iterator(boost::make_tuple(a_first, vkey.b.begin())),
                                      boost::make_zip_iterator(boost::make_tuple(a_last, vkey.b.end())),
                                      [&](const boost::tuple<const g1_value_type &, const g2_value_type &> &t) {
                                          u1 = u1 * algebra::pair<curve_type>(t.template get<0>(), t.template get<1>());
                                      });

                        gt_value_type u2 = gt_value_type::one();
                        std::for_each(boost::make_zip_iterator(boost::make_tuple(wkey.b.begin(), b_first)),
                                      boost::make_zip_iterator(boost::make_tuple(wkey.b.end(), b_last)),
                                      [&](const boost::tuple<const g1_value_type &, const g2_value_type &> &t) {
                                          u2 = u2 * algebra::pair<curve_type>(t.template get<0>(), t.template get<1>());
                                      });

                        // (A * v)(w * B)
                        return std::make_pair(algebra::final_exponentiation<curve_type>(t1 * t2),
                                              algebra::final_exponentiation<curve_type>(u1 * u2));
                    }

                    /// Commits to a single vector of G1 elements in the following way:
                    /// $T = \prod_{i=0}^n e(A_i, v_{1,i})$
                    /// $U = \prod_{i=0}^n e(A_i, v_{2,i})$
                    /// Output is $(T,U)$
                    template<typename InputG1Iterator,
                             typename ValueType1 = typename std::iterator_traits<InputG1Iterator>::value_type,
                             typename std::enable_if<std::is_same<g1_value_type, ValueType1>::value, bool>::type = true>
                    static output_type single(const vkey_type &vkey, InputG1Iterator a_first, InputG1Iterator a_last) {
                        BOOST_ASSERT(vkey.has_correct_len(std::distance(a_first, a_last)));

                        gt_value_type t1 = gt_value_type::one();
                        std::for_each(boost::make_zip_iterator(boost::make_tuple(a_first, vkey.a.begin())),
                                      boost::make_zip_iterator(boost::make_tuple(a_last, vkey.a.end())),
                                      [&](const boost::tuple<const g1_value_type &, const g2_value_type &> &t) {
                                          t1 = t1 * algebra::pair<curve_type>(t.template get<0>(), t.template get<1>());
                                      });

                        gt_value_type u1 = gt_value_type::one();
                        std::for_each(boost::make_zip_iterator(boost::make_tuple(a_first, vkey.b.begin())),
                                      boost::make_zip_iterator(boost::make_tuple(a_last, vkey.b.end())),
                                      [&](const boost::tuple<const g1_value_type &, const g2_value_type &> &t) {
                                          u1 = u1 * algebra::pair<curve_type>(t.template get<0>(), t.template get<1>());
                                      });

                        return std::make_pair(algebra::final_exponentiation<curve_type>(t1),
                                              algebra::final_exponentiation<curve_type>(u1));
                    }
                };
            }    // namespace snark
        }        // namespace zk
    }            // namespace crypto3
}    // namespace nil

#endif    // CRYPTO3_R1CS_GG_PPZKSNARK_AGGREGATE_IPP2_COMMITMENT_HPP


/*
 * Copyright 2015 David A. Boyuka II
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/*
 * quantization.hpp
 *
 *  Created on: May 16, 2014
 *      Author: David A. Boyuka II
 */
#ifndef QUANTIZATION_HPP_
#define QUANTIZATION_HPP_

#include <memory>
#include <typeindex>

#include <boost/smart_ptr.hpp>
#include <boost/type_traits.hpp>

#include "pique/util/datatypes.hpp"

//
// Basic quantization and quantized key comparison
//
template<typename ValueT, typename QKeyT>
class Quantization {
public:
	typedef ValueT ValueType;
	typedef QKeyT QKeyType;
public:
	virtual ~Quantization() {}
	virtual QKeyType quantize(ValueType val) const = 0;

	QKeyType operator()(const ValueType &val) const { return this->quantize(val); }
};

template<typename QKeyT>
class QuantizedKeyCompare {
public:
	typedef QKeyT QKeyType;
public:
	virtual ~QuantizedKeyCompare() {}
	virtual bool compare(QKeyType key1, QKeyType key2) const = 0;
	bool operator()(const QKeyType &key1, const QKeyType &key2) const { return this->compare(key1, key2); }

	virtual QKeyType min_key() const = 0;
	virtual QKeyType max_key() const = 0;
};

//
// Integral quantized key specialization
//
template<typename ValueT>
class IntegralTypeQuantization : public Quantization<ValueT, uint64_t> {
public:
	typedef typename Quantization<ValueT, uint64_t>::ValueType ValueType;
	typedef typename Quantization<ValueT, uint64_t>::QKeyType QKeyType;
};

class IntegralTypeQuantizedKeyCompare : public QuantizedKeyCompare<uint64_t> {
public:
	typedef typename QuantizedKeyCompare<uint64_t>::QKeyType QKeyType;
};

//
// Sigbits-based value binning, integral quantized key specialization
//
template<typename ValueT>
class SigbitsQuantization : public IntegralTypeQuantization<ValueT> {
public:
	typedef typename IntegralTypeQuantization<ValueT>::ValueType ValueType;
	typedef typename IntegralTypeQuantization<ValueT>::QKeyType QKeyType;
public:
	SigbitsQuantization(int sigbits) : sigbits(sigbits) {}
	virtual ~SigbitsQuantization() {}

	virtual QKeyType quantize(ValueType val) const;
protected:
	int sigbits;
};

template<Datatypes::NumericSignednessType Signedness>
class SigbitsQuantizedKeyCompare : public IntegralTypeQuantizedKeyCompare {
public:
	typedef IntegralTypeQuantizedKeyCompare::QKeyType QKeyType;
public:
	SigbitsQuantizedKeyCompare(int sigbits) : sigbits(sigbits) {}
	virtual ~SigbitsQuantizedKeyCompare() {}

	virtual bool compare(uint64_t key1, uint64_t key2) const;
	virtual uint64_t min_key() const;
	virtual uint64_t max_key() const;
protected:
	int sigbits;
};

//
// Precision-based value binning

template<typename ValueT, typename EnableIf = typename boost::enable_if< boost::is_floating_point< ValueT > >::type >
class PrecisionQuantization : public Quantization< ValueT, ValueT >, public QuantizedKeyCompare< ValueT > {
public:
	using ValueType = ValueT;
	using QKeyType = ValueT;
public:
	PrecisionQuantization(int digits) : digits(digits) {}
	virtual ~PrecisionQuantization() {}

	using Quantization< ValueT, ValueT >::operator();
	using QuantizedKeyCompare< ValueT >::operator();

	virtual QKeyType quantize(ValueType val) const;
	virtual bool compare(ValueType key1, ValueType key2) const { return key1 < key2; }
	virtual ValueType min_key() const { return -std::numeric_limits< ValueType >::infinity(); }
	virtual ValueType max_key() const { return std::numeric_limits< ValueType >::infinity(); }
protected:
	int digits;
};

// Explicit bins-based value binning
template<typename ValueT>
class ExplicitBinsComparableQuantization : public Quantization< ValueT, ValueT >, public QuantizedKeyCompare< ValueT > {
public:
	using ValueType = ValueT;
	using QKeyType = ValueT;
public:
	ExplicitBinsComparableQuantization(std::vector< ValueType > bins_by_value, ValueType neg_infinity) :
		neg_infinity(neg_infinity),
		bins(std::move(bins_by_value))
	{}
	virtual ~ExplicitBinsComparableQuantization() {};

	using Quantization< ValueT, ValueT >::operator();
	using QuantizedKeyCompare< ValueT >::operator();

	virtual QKeyType quantize(ValueType val) const;
	virtual bool compare(ValueType key1, ValueType key2) const { return key1 < key2; }
	virtual ValueType min_key() const { return neg_infinity; }
	virtual ValueType max_key() const { return bins.back(); }

	const std::vector< ValueType > & get_bin_bounds() const { return bins; }
	ValueType get_neg_infinity() const { return neg_infinity; }

private:
	ValueType neg_infinity;
	std::vector< ValueType > bins; // only used if we're given a bin vector to keep; otherwise, empty
};

// Semantic value casting quantization
template<typename ValueT, typename BaseQuantizationT>
class CastingQuantization : public Quantization< ValueT, typename BaseQuantizationT::QKeyType > {
public:
	typedef BaseQuantizationT BaseQuantizationType;
	typedef ValueT ValueType;
	typedef typename BaseQuantizationType::ValueType IntermediateValueType;
	typedef typename BaseQuantizationType::QKeyType QKeyType;
public:
	CastingQuantization(BaseQuantizationType &&q) : q(q) {}
	virtual ~CastingQuantization() {}
	virtual QKeyType quantize(ValueType val) const;

private:
	BaseQuantizationType q;
};

template<typename InValueT>
class DynamicCastingSigbitsQuantization : IntegralTypeQuantization<InValueT> {
public:
	typedef InValueT ValueType;
	typedef typename IntegralTypeQuantization<InValueT>::QKeyType QKeyType;
public:
	DynamicCastingSigbitsQuantization(int sigbits, Datatypes::IndexableDatatypeID semantic_cast_to);
	virtual ~DynamicCastingSigbitsQuantization() {}

	virtual QKeyType quantize(ValueType val) const { return base->quantize(val); }

private:
	typedef Quantization< ValueType, QKeyType > GenericBaseQuantizationType;
	std::unique_ptr< GenericBaseQuantizationType > base;

	struct CastingSigbitsQuantizationCreateFn;
};

class DynamicSigbitsQuantizedKeyCompare : IntegralTypeQuantizedKeyCompare {
public:
	typedef typename IntegralTypeQuantizedKeyCompare::QKeyType QKeyType;
public:
	DynamicSigbitsQuantizedKeyCompare(int sigbits, Datatypes::NumericSignednessType original_datatype_signedness);
	DynamicSigbitsQuantizedKeyCompare(int sigbits, std::type_index original_datatype);
	virtual ~DynamicSigbitsQuantizedKeyCompare() {}

	virtual bool compare(uint64_t key1, uint64_t key2) const { return base->compare(key1, key2); }
	virtual uint64_t min_key() const { return base->min_key(); }
	virtual uint64_t max_key() const { return base->max_key(); }

private:
	typedef QuantizedKeyCompare< QKeyType > GenericBaseQuantizedKeyCompareType;
	static std::unique_ptr< GenericBaseQuantizedKeyCompareType > base_quantized_key_compare_helper(int sigbits, Datatypes::NumericSignednessType signedness);
	std::unique_ptr< GenericBaseQuantizedKeyCompareType > base;
};

#include "impl/quantization-impl.hpp"

#endif /* QUANTIZATION_HPP_ */


//          Copyright(C) Rishabh Arora 2017.
// Distributed under 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)

#include <iostream>
#include <string>
#include <boost/numeric/ublas/vector.hpp>
#include <type_traits>
#include "./data_frame_exceptions.hpp"

using namespace boost::numeric::ublas;

// T - the current ublas::vector<T> under consideration.
// TArgs... - rest of the data_frame arguments
template <class T = void, class ... TArgs>
class data_frame {
public:

	/// \default constructor:
	/// \params: {}
	data_frame() {}

	/// \params: {column_name, ublas::vector<T>}*
	/// \recursive function: calls on inner arguments.
	template <class F1, class F2, class ... FArgs>
	data_frame(F1 header, vector<F2> col, FArgs ... fargs) :
		header_(header), col_(col), data_(fargs...) {}

	/// \params: data_frame with exact same template arguments.
	data_frame(data_frame<T, TArgs...>& df) :
		col_(df.col_), header_(df.header_), data_(df.data_) {}

	/// \brief: returns the ublas::vector<T> on the basis of header.
	template < class T1 >
	const vector<T1>& column(const std::string header) {
		try {
			/// \header match return the current column.
			if (header == header_) {
				if (std::is_same<T, T1>::value) {
					return (vector<T1>&)col_;
				}
				throw column_type_mismatch();
			}

			/// \search in the inner data_frame
			return data_.template column <T1> (header);
		}

		catch (std::exception& e) {
			std::terminate();
		}
	}

	/// \brief: * '=' operator overload for the data_frame.
	/// 		* sets the current set of header_, col_ and calls the inner data_frame
	data_frame<T, TArgs...>& operator = (data_frame<T, TArgs...>& df) {
		col_ = df.col();
		header_ = df.header();
		data_ = df.data();
		return *this;
	}

	template < class T1 >
	void add_column(data_frame<T, TArgs..., T1>& df,
					const std::string& header,
					const vector<T1>& col) {
		df.col() = col_;
		df.header() = header_;
		data_.add_column(df.data(), header, col);
		return;
	}

	/// \should work!? FIX IT !!!!
	// No idea why it doesn't work!
	template < class ... TArgs1 >
	void erase_column(data_frame<TArgs1...>& df, const std::string& header) {
		if (header == header_) {
			data_.erase_column(df, header);
			return;
		}
		df.col() = col_;
		df.header() = header_;
		data_.erase_column(df.data(), header);
		return;
	}

	void print() {
		std::cout << "[" << header_ << "]" << ": ";
		for(size_t i = 0; i < col_.size(); ++i) std::cout << col_(i) << ' ';
		std::cout << std::endl;
		data_.print();
	}

	const bool end() const {
		return false;
	}
	vector<T>& col() {
		return col_;
	}
	const vector<T>& col() const {
		return col_;
	}
	data_frame<TArgs...>& data() {
		return data_;
	}
	const data_frame<TArgs...>& data() const {
		return data_;
	}
	std::string& header() {
		return header_;
	}
	const std::string& header() const {
		return header_;
	}

//private:
	std::string header_;
	vector<T> col_;
	data_frame<TArgs...> data_;
};

/// \the inner most data_frame class
template <>
class data_frame<> {

public:
	data_frame() {}

	template < class T >
	void add_column(data_frame<T>& df, const std::string header, const vector<T>& col) {
		df.header() = header;
		df.col() = col;
		return;
	}

	template <class ... TArgs>
	void erase_column(data_frame<TArgs...>& df, const std::string& header) {
		/* end the erase column operation */
		return;
	}

	data_frame<>& operator = (data_frame<>& df) {
		return *this;
	}

	template <class T1>
	const vector<T1>& column(const std::string header) {
		try {
			throw undefined_column_header();
		}
		catch (std::exception& e) {
			std::terminate();
		}
	}

	void print() {}

	const bool end() const {
		return true;
	}
};

int main() {
	vector<int> a(2);
	a(0) = 1;
	a(1) = 2;
	vector<float> b(2);
	b(0) = 1.1;
	b(1) = 2.1;
	vector<char> c(2);
	c(0) = 'a';
	c(1) = 'b';
	vector<std::string> d(2);
	d(0)= "rishabh";
	d(1) = "mridul";

	data_frame<int> df1("a", a);
	//data_frame<int, float, char> df2;
	data_frame<int, float> df2;
	df1.add_column(df2, "b", b);
	// std::cout << std::endl;
	// df1.print();
	// std::cout << std::endl;
	// df2.print();
	// data_frame<int, float, char> df3;
	// df2.add_column(df3, "c", c);
	// df3.print();
	df2.erase_column(df1, "b");
	return 0;
}


#include <boost/test/unit_test.hpp>
#include "algorithms/data_structures/array/container_with_most_water.hpp"

BOOST_AUTO_TEST_SUITE(TestContainerWithMostWater)

BOOST_AUTO_TEST_CASE(invalid_container)
{
    BOOST_CHECK(0 == Algo::DS::Array::ContainerWithMostWater::Search({}));
    BOOST_CHECK(0 == Algo::DS::Array::ContainerWithMostWater::Search({1}));
}

BOOST_AUTO_TEST_CASE(empty_container)
{
    BOOST_CHECK(0 == Algo::DS::Array::ContainerWithMostWater::Search({0, 1}));
    BOOST_CHECK(0 == Algo::DS::Array::ContainerWithMostWater::Search({0, 1, 0}));
}

BOOST_AUTO_TEST_CASE(valid_container)
{
    BOOST_CHECK(1 == Algo::DS::Array::ContainerWithMostWater::Search({0, 1, 2}));
    BOOST_CHECK(1 == Algo::DS::Array::ContainerWithMostWater::Search({2, 1, 0}));
    BOOST_CHECK(2 == Algo::DS::Array::ContainerWithMostWater::Search({1, 1, 2}));
    BOOST_CHECK(2 == Algo::DS::Array::ContainerWithMostWater::Search({2, 1, 1}));
    BOOST_CHECK(49 ==
            Algo::DS::Array::ContainerWithMostWater::Search({1,8,6,2,5,4,8,3,7}));
}

BOOST_AUTO_TEST_SUITE_END()


#include <nan.h>
#include <boost/coroutine/all.hpp>
#include <sstream>

using v8::FunctionTemplate;
using v8::Handle;
using v8::Object;
using v8::String;

NAN_METHOD(Fibo) {
    // Lambda from the Fibonacci example:
    boost::coroutines::asymmetric_coroutine<int>::pull_type source(
        [&](boost::coroutines::asymmetric_coroutine<int>::push_type& sink) {
            int first = 1, second = 1;
            sink(first);
            sink(second);
            for (int i = 0; i < 8; ++i)
            {
                int third = first + second;
                first = second;
                second = third;
                sink( third);
            }
        });

    // Write the results to a string stream:
    std::stringstream ss;

    bool first = true;
    for (auto i : source) {
        if (!first) {
            ss << " ";
        }
        else {
            first = false;
        }
        ss << i;
    }

    // Return it to node engine
    info.GetReturnValue().Set(Nan::New<String>(ss.str().c_str()).ToLocalChecked());
}

void InitAll(Handle<Object> exports) {
  // Method that returns the generated Fibonacci sequence as a string:
  exports->Set(Nan::New<String>("fibo").ToLocalChecked(),
    Nan::New<FunctionTemplate>(Fibo)->GetFunction());
}

NODE_MODULE(addon, InitAll)


#include <boost/thread/thread.hpp>
#include <boost/thread/tss.hpp>
#include <iostream>
#include <chrono>
#include <atomic>
#include <iomanip>
#include <vector>

#define LOOP 100000000
#define TARGET 50000000
#define BENCH_RUNS 5

using namespace std;

atomic<unsigned int> c;
vector<int> NTHREADS;

int inc(){
	  unsigned int oldValue = c.load();
    unsigned int newValue;

    do{
        if (oldValue < TARGET) newValue = oldValue + 1;
        else break;
    }
    while (!c.compare_exchange_weak(oldValue, newValue));

    return oldValue;
}

void work(){
	for(int i=0; i<LOOP; i++){
		if (inc() >= TARGET) break;
	}
}

void work2(){
 	for(int i=0; i<LOOP; i++){
 		if(c < TARGET) c++;
 		else break;
 	}
 }

void benchmark(){
	using namespace std::chrono;
    for(int k = 0; k < NTHREADS.size(); k++){
      std::list<double> times;
      std::list<double> counters;
      std::list<double> throughs;

      for(int i= 0; i< BENCH_RUNS; i++){
        steady_clock::time_point t1 = steady_clock::now();

        boost::thread_group threads;
        for (int a=0; a < NTHREADS[k]; a++){
          threads.create_thread(&work);
        }

        threads.join_all();

        steady_clock::time_point t2 = steady_clock::now();

        duration<double> ti = duration_cast<duration<double>>(t2 - t1);

        times.push_back(ti.count());
        counters.push_back(c);
        throughs.push_back(c/ti.count());

        cout << "VALUE: " << c << endl;
        c=0;
      }

      double sumTimes = 0;
      for(double t: times){
        sumTimes += t;
      }

      double finalTime = sumTimes/times.size();
      cout << fixed;

      double sumCounters = 0;
      for(double c: counters){
        sumCounters += c;
      }
      double finalCounter = (int)(sumCounters/counters.size());

      double sumTroughs = 0;
      for(double th: throughs){
        sumTroughs += th;
      }
      double finalThroughs = sumTroughs/throughs.size();

      //double overshoot = ((finalCounter-TARGET)*100)/TARGET;

      cout << (int)finalThroughs << "," << NTHREADS[k] << endl;
    }
}

int main(int argc, char** argv){
  for(int i=1; i<argc; i++){
    cout << argv[i] << endl;
    NTHREADS.push_back(atoi(argv[i]));
  }
	benchmark();
}


///////////////////////////////////////////////////////////////////////////////
// kernel::include.hpp                                                       //
//                                                                           //
//  Copyright 2009 Erwann Rogard. Distributed under 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_STATISTICS_DETAIL_KERNEL_INCLUDE_HPP_ER_2009
#define BOOST_STATISTICS_DETAIL_KERNEL_INCLUDE_HPP_ER_2009

#include <boost/statistics/detail/kernel/bandwidth/include.hpp>
#include <boost/statistics/detail/kernel/estimation/include.hpp>
#include <boost/statistics/detail/kernel/kernels/multivariate/include.hpp>
#include <boost/statistics/detail/kernel/kernels/scalar/include.hpp>

#endif


//
// Copyright (c) 2009 Rutger ter Borg
//
// Distributed under 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_NUMERIC_BINDINGS_BLAS_DETAIL_DEFAULT_ORDER_HPP
#define BOOST_NUMERIC_BINDINGS_BLAS_DETAIL_DEFAULT_ORDER_HPP

#include <boost/mpl/if.hpp>
#include <boost/numeric/bindings/is_row_major.hpp>
#include <boost/numeric/bindings/tag.hpp>
#include <boost/numeric/bindings/detail/property_map.hpp>

namespace boost {
namespace numeric {
namespace bindings {
namespace blas {
namespace detail {

template< typename T >
struct default_order {
    typedef typename mpl::if_<
        bindings::detail::is_same_at< T, tag::value_transform, tag::conjugate >,
        typename mpl::if_< is_row_major< T >, tag::column_major, tag::row_major >::type,
        tag::column_major
    >::type type;
};

} // namespace detail
} // namespace blas
} // namespace bindings
} // namespace numeric
} // namespace boost

#endif


// Copyright (c) 2006 Johan Rade
// Copyright (c) 2011 Paul A. Bristow - filename changes for boost-trunk.

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

//#ifdef _MSC_VER
//#   pragma warning(disable : 4511 4512 4702)
//#endif

#define BOOST_TEST_MAIN

#include <limits>
#include <locale>
#include <sstream>

#include <boost/archive/text_iarchive.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_wiarchive.hpp>
#include <boost/archive/text_woarchive.hpp>
#include <boost/archive/codecvt_null.hpp>
#include <boost/test/auto_unit_test.hpp>
#include <boost/math/special_functions/nonfinite_num_facets.hpp>
#include <boost/math/special_functions/sign.hpp>
#include <boost/math/special_functions/fpclassify.hpp>

#include "almost_equal.ipp"

namespace {

// The anonymous namespace resolves ambiguities on platforms
// with fpclassify etc functions at global scope.

using namespace boost::archive;

using namespace boost::math;
using boost::math::signbit;
using boost::math::changesign;
using boost::math::isnan;

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

void archive_basic_test();
void archive_put_trap_test();
void archive_get_trap_test();

BOOST_AUTO_TEST_CASE(archive_test)
{
    archive_basic_test();
    archive_put_trap_test();
    archive_get_trap_test();
}

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

template<class CharType, class OArchiveType, class IArchiveType, class ValType>
void archive_basic_test_impl();

void archive_basic_test()
{
    archive_basic_test_impl<char, text_oarchive, text_iarchive, float>();
    archive_basic_test_impl<char, text_oarchive, text_iarchive, double>();
    archive_basic_test_impl<
        char, text_oarchive, text_iarchive, long double>();
    archive_basic_test_impl<
        wchar_t, text_woarchive, text_wiarchive, float>();
    archive_basic_test_impl<
        wchar_t, text_woarchive, text_wiarchive, double>();
    archive_basic_test_impl<
        wchar_t, text_woarchive, text_wiarchive, long double>();
}

template<class CharType, class OArchiveType, class IArchiveType, class ValType>
void archive_basic_test_impl()
{
    if((std::numeric_limits<ValType>::has_infinity == 0) || (std::numeric_limits<ValType>::infinity() == 0))
      return;
    std::locale default_locale(std::locale::classic(),
        new boost::archive::codecvt_null<CharType>);
    std::locale tmp_locale(default_locale, new nonfinite_num_put<CharType>);
    std::locale my_locale(tmp_locale, new nonfinite_num_get<CharType>);

    std::basic_stringstream<CharType> ss;
    ss.imbue(my_locale);

    ValType a1 = static_cast<ValType>(0);
    ValType a2 = static_cast<ValType>(2307.35);
    ValType a3 = std::numeric_limits<ValType>::infinity();
    BOOST_CHECK((boost::math::isinf)(a3));
    ValType a4 = std::numeric_limits<ValType>::quiet_NaN();
    BOOST_CHECK((boost::math::isnan)(a4));
    ValType a5 = std::numeric_limits<ValType>::signaling_NaN();
    BOOST_CHECK((boost::math::isnan)(a5));
    ValType a6 = (changesign)(static_cast<ValType>(0));
    ValType a7 = static_cast<ValType>(-57.13);
    ValType a8 = -std::numeric_limits<ValType>::infinity();
    BOOST_CHECK((boost::math::isinf)(a8));
    ValType a9 = -std::numeric_limits<ValType>::quiet_NaN();
    BOOST_CHECK((boost::math::isnan)(a9));
    ValType a10 = -std::numeric_limits<ValType>::signaling_NaN();
    BOOST_CHECK((boost::math::isnan)(a10));

    {
        OArchiveType oa(ss, no_codecvt);
        oa & a1 & a2 & a3 & a4 & a5 & a6 & a7 & a8 & a9 & a10;
    }

    ValType b1, b2, b3, b4, b5, b6, b7, b8, b9, b10;

    {
        IArchiveType ia(ss, no_codecvt);
        ia & b1 & b2 & b3 & b4 & b5 & b6 & b7 & b8 & b9 & b10;
    }

    BOOST_CHECK(a1 == b1);
    BOOST_CHECK(almost_equal(a2, b2));
    BOOST_CHECK(a3 == b3);
    BOOST_CHECK((isnan)(b4));
    BOOST_CHECK(!(signbit)(b4));
    BOOST_CHECK((isnan)(b5));
    BOOST_CHECK(!(signbit)(b5));
    BOOST_CHECK(a6 == b6);
    BOOST_CHECK(almost_equal(a7, b7));
    BOOST_CHECK(a8 == b8);
    BOOST_CHECK((isnan)(b9));
    BOOST_CHECK((signbit)(b9));
    BOOST_CHECK((isnan)(b10));
    BOOST_CHECK((signbit)(b10));
}

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

template<class CharType, class OArchiveType, class IArchiveType, class ValType>
void archive_put_trap_test_impl();

void archive_put_trap_test()
{
    archive_put_trap_test_impl<char, text_oarchive, text_iarchive, float>();
    archive_put_trap_test_impl<char, text_oarchive, text_iarchive, double>();
    archive_put_trap_test_impl<
        char, text_oarchive, text_iarchive, long double>();
    archive_put_trap_test_impl<
        wchar_t, text_woarchive, text_wiarchive, float>();
    archive_put_trap_test_impl<
        wchar_t, text_woarchive, text_wiarchive, double>();
    archive_put_trap_test_impl<
        wchar_t, text_woarchive, text_wiarchive, long double>();
}

template<class CharType, class OArchiveType, class IArchiveType, class ValType>
void archive_put_trap_test_impl()
{
    if((std::numeric_limits<ValType>::has_infinity == 0) || (std::numeric_limits<ValType>::infinity() == 0))
      return;

    std::locale default_locale(std::locale::classic(),
        new boost::archive::codecvt_null<CharType>);
    std::locale new_locale(default_locale,
        new nonfinite_num_put<CharType>(trap_infinity));

    std::basic_stringstream<CharType> ss;
    ss.exceptions(std::ios_base::failbit | std::ios_base::badbit);
    ss.imbue(new_locale);

    ValType a = std::numeric_limits<ValType>::infinity();

    OArchiveType oa(ss, no_codecvt);

    try {
        oa & a;
    }
    catch(std::exception&) {
    ss.clear();
        return;
    }

    BOOST_CHECK(false);
}

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

template<class CharType, class OArchiveType, class IArchiveType, class ValType>
void archive_get_trap_test_impl();

void archive_get_trap_test()
{
    archive_get_trap_test_impl<char, text_oarchive, text_iarchive, float>();
    archive_get_trap_test_impl<char, text_oarchive, text_iarchive, double>();
    archive_get_trap_test_impl<
        char, text_oarchive, text_iarchive, long double>();
    archive_get_trap_test_impl<
        wchar_t, text_woarchive, text_wiarchive, float>();
    archive_get_trap_test_impl<
        wchar_t, text_woarchive, text_wiarchive, double>();
    archive_get_trap_test_impl<
        wchar_t, text_woarchive, text_wiarchive, long double>();
}

template<class CharType, class OArchiveType, class IArchiveType, class ValType>
void archive_get_trap_test_impl()
{
    if((std::numeric_limits<ValType>::has_infinity == 0) || (std::numeric_limits<ValType>::infinity() == 0))
     return;

    std::locale default_locale(std::locale::classic(),
        new boost::archive::codecvt_null<CharType>);
    std::locale tmp_locale(default_locale, new nonfinite_num_put<CharType>);
    std::locale my_locale(tmp_locale,
        new nonfinite_num_get<CharType>(trap_nan));

    std::basic_stringstream<CharType> ss;
    ss.exceptions(std::ios_base::failbit);
    ss.imbue(my_locale);

    ValType a = -std::numeric_limits<ValType>::quiet_NaN();

    {
        OArchiveType oa(ss, no_codecvt);
        oa & a;
    }

    ValType b;
    {
        IArchiveType ia(ss, no_codecvt);
        try {
            ia & b;
        }
        catch(std::exception&) {
            return;
        }
    }

    BOOST_CHECK(false);
}

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

}   // anonymous namespace


// ---------------------------------------------------------------------
//
// Copyright (c) 2018 - 2021 by the IBAMR developers
// All rights reserved.
//
// This file is part of IBAMR.
//
// IBAMR is free software and is distributed under the 3-clause BSD
// license. The full text of the license can be found in the file
// COPYRIGHT at the top level directory of IBAMR.
//
// ---------------------------------------------------------------------

/////////////////////////////// INCLUDES /////////////////////////////////////

#include "ibtk/FECache.h"
#include "ibtk/QuadratureCache.h"
#include "ibtk/libmesh_utilities.h"

#include "tbox/Utilities.h"

#if LIBMESH_VERSION_LESS_THAN(1, 2, 0)
#include "libmesh/mesh_tools.h"
#else
#include "libmesh/bounding_box.h"
#endif
#include "libmesh/dof_map.h"
#include "libmesh/elem.h"
#include "libmesh/enum_elem_type.h"
#include "libmesh/enum_order.h"
#include "libmesh/enum_quadrature_type.h"
#include "libmesh/explicit_system.h"
#include "libmesh/fem_context.h"
#include "libmesh/id_types.h"
#include "libmesh/libmesh_config.h"
#include "libmesh/mesh_base.h"
#include "libmesh/node.h"
#include "libmesh/numeric_vector.h"
#include "libmesh/parallel.h"
#include "libmesh/petsc_vector.h"
#include "libmesh/point.h"
#include "libmesh/quadrature.h"
#include "libmesh/system.h"
#include "libmesh/type_vector.h"
#include "libmesh/variant_filter_iterator.h"

#include <petscsys.h>

IBTK_DISABLE_EXTRA_WARNINGS
#include <boost/multi_array.hpp>
IBTK_ENABLE_EXTRA_WARNINGS

#include <mpi.h>

#include <algorithm>
#include <cmath>
#include <fstream>
#include <limits>
#include <memory>
#include <sstream>
#include <string>
#include <tuple>
#include <vector>

/////////////////////////////// NAMESPACE ////////////////////////////////////

namespace IBTK
{
/////////////////////////////// STATIC ///////////////////////////////////////

/////////////////////////////// PUBLIC ///////////////////////////////////////
void
setup_system_vectors(libMesh::EquationSystems* equation_systems,
                     const std::vector<std::string>& system_names,
                     const std::vector<std::string>& vector_names,
                     const bool from_restart)
{
    for (const std::string& system_name : system_names)
    {
        TBOX_ASSERT(equation_systems->has_system(system_name));
        libMesh::System& system = equation_systems->get_system(system_name);
        for (const std::string& vector_name : vector_names)
        {
            setup_system_vector(system, vector_name, from_restart);
            if (vector_name == "RHS Vector")
            {
                auto* explicit_system = dynamic_cast<libMesh::ExplicitSystem*>(&system);
                if (!explicit_system)
                {
                    TBOX_ERROR(
                        "You are attempting to add a RHS vector to a libMesh system that does not have one (i.e., it "
                        "does not inherit from ExplicitSystem).");
                }
                else
                {
                    explicit_system->rhs = &system.get_vector("RHS Vector");
                }
            }
        }
    }
}

void
setup_system_vector(libMesh::System& system, const std::string& vector_name, const bool from_restart)
{
    std::unique_ptr<libMesh::NumericVector<double> > clone_vector;
    if (from_restart)
    {
        libMesh::NumericVector<double>* current = system.request_vector(vector_name);
        if (current != nullptr)
        {
            clone_vector = current->clone();
        }
    }
    system.remove_vector(vector_name);
    system.add_vector(vector_name, /*projections*/ true, /*type*/ libMesh::GHOSTED);

    if (clone_vector != nullptr)
    {
        const auto& parallel_vector = dynamic_cast<const libMesh::PetscVector<double>&>(*clone_vector);
        auto& ghosted_vector = dynamic_cast<libMesh::PetscVector<double>&>(system.get_vector(vector_name));
        TBOX_ASSERT(parallel_vector.size() == ghosted_vector.size());
        TBOX_ASSERT(parallel_vector.local_size() == ghosted_vector.local_size());
        ghosted_vector = parallel_vector;
        ghosted_vector.close();
    }
}

void
apply_transposed_constraint_matrix(const libMesh::DofMap& dof_map, libMesh::PetscVector<double>& rhs)
{
    std::vector<libMesh::dof_id_type> dofs;
    std::vector<double> values_to_add;
    // loop over constraints and do the action of C^T b
    for (auto it = dof_map.constraint_rows_begin(); it != dof_map.constraint_rows_end(); ++it)
    {
        const std::pair<libMesh::dof_id_type, libMesh::DofConstraintRow>& constraint = *it;
        const libMesh::dof_id_type constrained_dof = constraint.first;
        // only resolve constraints if the DoF is locally owned
        if (dof_map.first_dof() <= constrained_dof && constrained_dof < dof_map.end_dof())
        {
            for (const std::pair<const libMesh::dof_id_type, double>& pair : constraint.second)
            {
                dofs.push_back(pair.first);
                values_to_add.push_back(pair.second * rhs(constrained_dof));
            }
        }
    }

    // If we use ghosted RHSs then we cannot use VecSetValues because we are
    // using a replacement for VecStash. Hence do some somewhat ugly
    // transformations in that case to sum into ghost regions:
    if (rhs.type() == libMesh::GHOSTED)
    {
        // Calling operator() above puts the vector in read-only mode, which
        // has to be restored before we can write to it
        rhs.restore_array();
        // At this point rhs contains the full set of ghost data (which we
        // needed for constraint resolution). However, we now want to reuse
        // the ghost region to sum the values from constraint resolution onto
        // their owning processes. Hence we must clear the ghost data and then
        // do another parallel reduction.
        const PetscInt local_size_without_ghosts = rhs.local_size();
        Vec loc_vec = nullptr;
        // we also need the size of the ghost region, which is surprisingly
        // difficult to get
        int ierr = VecGhostGetLocalForm(rhs.vec(), &loc_vec);
        IBTK_CHKERRQ(ierr);
        PetscInt local_size_with_ghosts = -1;
        ierr = VecGetSize(loc_vec, &local_size_with_ghosts);
        IBTK_CHKERRQ(ierr);
        ierr = VecGhostRestoreLocalForm(rhs.vec(), &loc_vec);
        IBTK_CHKERRQ(ierr);

        double* const vec_array = rhs.get_array();
        std::fill(vec_array + local_size_without_ghosts, vec_array + local_size_with_ghosts, 0.0);
        for (std::size_t i = 0; i < dofs.size(); ++i)
        {
            const PetscInt index = rhs.map_global_to_local_index(dofs[i]);
            vec_array[index] += values_to_add[i];
        }
        rhs.restore_array();
    }
    else
    {
        rhs.add_vector(values_to_add.data(), dofs);
    }
}

quadrature_key_type
getQuadratureKey(const libMesh::QuadratureType quad_type,
                 libMesh::Order order,
                 const bool use_adaptive_quadrature,
                 const double point_density,
                 const bool allow_rules_with_negative_weights,
                 const libMesh::Elem* const elem,
                 const boost::multi_array<double, 2>& X_node,
                 const double dx_min)
{
    const libMesh::ElemType elem_type = elem->type();
#ifndef NDEBUG
    TBOX_ASSERT(elem->p_level() == 0); // higher levels are not implemented
#endif
    if (use_adaptive_quadrature)
    {
        const double hmax = get_max_edge_length(elem, X_node);
        int npts = int(std::ceil(point_density * hmax / dx_min));
        if (npts < 3)
        {
            if (elem->default_order() == libMesh::FIRST)
                npts = 2;
            else
                npts = 3;
        }
        switch (quad_type)
        {
        case libMesh::QGAUSS:
            order = static_cast<libMesh::Order>(std::min(2 * npts - 1, static_cast<int>(libMesh::FORTYTHIRD)));
            break;
        case libMesh::QGRID:
            order = static_cast<libMesh::Order>(npts);
            break;
        default:
            TBOX_ERROR("IBTK::getQuadratureKey():\n"
                       << "  adaptive quadrature rules are available only for quad_type = QGAUSS "
                          "or QGRID\n");
        }
    }

    return std::make_tuple(elem_type, quad_type, order, allow_rules_with_negative_weights);
}

void
write_elem_partitioning(const std::string& file_name, const libMesh::System& position_system)
{
    const int current_rank = position_system.comm().rank();
    const unsigned int position_system_n = position_system.number();
    const libMesh::NumericVector<double>& local_position = *position_system.solution.get();
    const libMesh::MeshBase& mesh = position_system.get_mesh();
    const unsigned int spacedim = mesh.spatial_dimension();
    // TODO: there is something wrong with the way we set up the ghost data in
    // the position vectors: not all locally owned nodes are, in fact, locally
    // available. Get around this by localizing first. Since all processes
    // write to the same file this isn't the worst bottleneck in this
    // function, anyway.
    std::vector<double> position(local_position.size());
    local_position.localize(position);
    std::stringstream current_processor_output;

    const auto end_elem = mesh.local_elements_end();
    for (auto elem = mesh.local_elements_begin(); elem != end_elem; ++elem)
    {
        const unsigned int n_nodes = (*elem)->n_nodes();
        libMesh::Point center;
        // TODO: this is a bit crude: if we use isoparametric elements (e.g.,
        // Tri6) then this is not a very accurate representation of the center
        // of the element. We should replace this with something more accurate.
        for (unsigned int node_n = 0; node_n < n_nodes; ++node_n)
        {
            const libMesh::Node& node = (*elem)->node_ref(node_n);
            TBOX_ASSERT(node.n_vars(position_system_n) == spacedim);
            for (unsigned int d = 0; d < spacedim; ++d)
            {
                center(d) += position[node.dof_number(position_system_n, d, 0)];
            }
        }
        center *= 1.0 / n_nodes;

        for (unsigned int d = 0; d < spacedim; ++d)
        {
            current_processor_output << center(d) << ',';
        }
        if (spacedim == 2)
        {
            current_processor_output << 0.0 << ',';
        }
        current_processor_output << current_rank << '\n';
    }

    // clear the file before we append to it
    if (current_rank == 0)
    {
        std::remove(file_name.c_str());
    }
    const int n_processes = position_system.comm().size();
    for (int rank = 0; rank < n_processes; ++rank)
    {
        if (rank == current_rank)
        {
            std::ofstream out(file_name, std::ios_base::app);
            if (rank == 0)
            {
                out << "x,y,z,r\n";
            }
            out << current_processor_output.rdbuf();
        }
        position_system.comm().barrier();
    }
}

void
write_node_partitioning(const std::string& file_name, const libMesh::System& position_system)
{
    const int current_rank = position_system.comm().rank();
    const unsigned int position_system_n = position_system.number();
    const libMesh::NumericVector<double>& local_position = *position_system.solution.get();
    const libMesh::MeshBase& mesh = position_system.get_mesh();
    const unsigned int spacedim = mesh.spatial_dimension();

    // TODO: there is something wrong with the way we set up the ghost data in
    // the position vectors: not all locally owned nodes are, in fact,
    // locally available. Get around this by localizing first. Since all
    // processes write to the same file this isn't the worst bottleneck in
    // this function, anyway.
    std::vector<double> position(local_position.size());
    local_position.localize(position);
    std::stringstream current_processor_output;

    const auto end_node = mesh.local_nodes_end();
    for (auto node_it = mesh.local_nodes_begin(); node_it != end_node; ++node_it)
    {
        const libMesh::Node* const node = *node_it;
        if (node->n_vars(position_system_n))
        {
            TBOX_ASSERT(node->n_vars(position_system_n) == spacedim);
            for (unsigned int d = 0; d < spacedim; ++d)
            {
                current_processor_output << position[node->dof_number(position_system_n, d, 0)] << ',';
            }
            if (spacedim == 2)
            {
                current_processor_output << 0.0 << ',';
            }
            current_processor_output << current_rank << '\n';
        }
    }

    // clear the file before we append to it
    if (current_rank == 0)
    {
        std::remove(file_name.c_str());
    }
    const int n_processes = position_system.comm().size();
    for (int rank = 0; rank < n_processes; ++rank)
    {
        if (rank == current_rank)
        {
            std::ofstream out(file_name, std::ios_base::app);
            if (rank == 0)
            {
                out << "x,y,z,r\n";
            }
            out << current_processor_output.rdbuf();
        }
        position_system.comm().barrier();
    }
}

std::vector<libMeshWrappers::BoundingBox>
get_local_element_bounding_boxes(const libMesh::MeshBase& mesh,
                                 const libMesh::System& X_system,
                                 const libMesh::QuadratureType quad_type,
                                 const libMesh::Order quad_order,
                                 const bool use_adaptive_quadrature,
                                 const double point_density,
                                 bool allow_rules_with_negative_weights,
                                 const double patch_dx_min)
{
    const unsigned int dim = mesh.mesh_dimension();
    const unsigned int spacedim = mesh.spatial_dimension();
    TBOX_ASSERT(spacedim == NDIM);
    const unsigned int X_sys_num = X_system.number();
    auto X_ghost_vec_ptr = X_system.current_local_solution->zero_clone();
    auto& X_ghost_vec = dynamic_cast<libMesh::PetscVector<double>&>(*X_ghost_vec_ptr);
    X_ghost_vec = *X_system.solution;

    std::vector<libMeshWrappers::BoundingBox> bboxes;

    std::vector<std::vector<libMesh::dof_id_type> > dof_indices(NDIM);
    boost::multi_array<double, 2> X_node;
    QuadratureCache quad_cache(dim);
    FECache fe_cache(dim, X_system.get_dof_map().variable_type(0), update_phi);
    using quad_key_type = quadrature_key_type;
    const auto el_begin = mesh.local_elements_begin();
    const auto el_end = mesh.local_elements_end();
    for (auto el_it = el_begin; el_it != el_end; ++el_it)
    {
        // 0. Set up bounding box
        bboxes.emplace_back();
        auto& box = bboxes.back();
        libMesh::Point& lower_bound = box.first;
        libMesh::Point& upper_bound = box.second;
        for (unsigned int d = 0; d < LIBMESH_DIM; ++d)
        {
            lower_bound(d) = std::numeric_limits<double>::max();
            upper_bound(d) = -std::numeric_limits<double>::max();
        }

        // As mentioned in the documentation of this function we do not set up
        // bounding boxes for inactive elements
        if (!(*el_it)->active()) continue;

        // 1. extract node locations
        const libMesh::Elem* const elem = *el_it;
        const unsigned int n_nodes = elem->n_nodes();
        for (unsigned int d = 0; d < NDIM; ++d) dof_indices[d].clear();

        for (unsigned int k = 0; k < n_nodes; ++k)
        {
            const libMesh::Node* const node = elem->node_ptr(k);
            for (unsigned int d = 0; d < NDIM; ++d)
            {
                TBOX_ASSERT(node->n_dofs(X_sys_num, d) == 1);
                dof_indices[d].push_back(node->dof_number(X_sys_num, d, 0));
            }
        }
        get_values_for_interpolation(X_node, X_ghost_vec, dof_indices);

        // 2. compute mapped quadrature points in the deformed configuration
        const quad_key_type key = getQuadratureKey(quad_type,
                                                   quad_order,
                                                   use_adaptive_quadrature,
                                                   point_density,
                                                   allow_rules_with_negative_weights,
                                                   elem,
                                                   X_node,
                                                   patch_dx_min);
        libMesh::QBase& quadrature = quad_cache[key];
        libMesh::FEBase& fe = fe_cache(key, elem);
        const std::vector<std::vector<double> >& phi_X = fe.get_phi();
        const std::vector<libMesh::Point>& q_points = quadrature.get_points();
        for (unsigned int qp = 0; qp < q_points.size(); ++qp)
        {
            libMesh::Point mapped_point;
            for (unsigned int k = 0; k < n_nodes; ++k)
            {
                for (unsigned int d = 0; d < NDIM; ++d)
                {
                    mapped_point(d) += phi_X[k][qp] * X_node[k][d];
                }
            }
            for (unsigned int d = 0; d < NDIM; ++d)
            {
                lower_bound(d) = std::min(lower_bound(d), mapped_point(d));
                upper_bound(d) = std::max(upper_bound(d), mapped_point(d));
            }

            // fill extra dimension with 0.0, which is libMesh's convention
            for (unsigned int d = NDIM; d < LIBMESH_DIM; ++d)
            {
                lower_bound(d) = 0.0;
                upper_bound(d) = 0.0;
            }
        }
    }

    return bboxes;
} // get_local_element_bounding_boxes

std::vector<libMeshWrappers::BoundingBox>
get_local_element_bounding_boxes(const libMesh::MeshBase& mesh, const libMesh::System& X_system)
{
    const unsigned int spacedim = mesh.spatial_dimension();
    TBOX_ASSERT(spacedim == NDIM);
    const unsigned int X_sys_num = X_system.number();
    auto X_ghost_vec_ptr = X_system.current_local_solution->zero_clone();
    auto& X_ghost_vec = dynamic_cast<libMesh::PetscVector<double>&>(*X_ghost_vec_ptr);
    X_ghost_vec = *X_system.solution;

    std::vector<libMeshWrappers::BoundingBox> bboxes;
    bboxes.reserve(mesh.n_local_elem());

    std::vector<libMesh::dof_id_type> dof_indices;
    std::vector<double> X_node;
    const auto el_begin = mesh.local_elements_begin();
    const auto el_end = mesh.local_elements_end();
    for (auto el_it = el_begin; el_it != el_end; ++el_it)
    {
        const libMesh::Elem* const elem = *el_it;
        const unsigned int n_nodes = elem->n_nodes();
        bboxes.emplace_back();
        auto& box = bboxes.back();
        libMesh::Point& lower_bound = box.first;
        libMesh::Point& upper_bound = box.second;
        for (unsigned int d = 0; d < LIBMESH_DIM; ++d)
        {
            lower_bound(d) = std::numeric_limits<double>::max();
            upper_bound(d) = -std::numeric_limits<double>::max();
        }

        // As mentioned in the documentation of this function we do not set up
        // bounding boxes for inactive elements
        if (!(*el_it)->active()) continue;

        dof_indices.clear();
        for (unsigned int k = 0; k < n_nodes; ++k)
        {
            const libMesh::Node* const node = elem->node_ptr(k);
            for (unsigned int d = 0; d < NDIM; ++d)
            {
                TBOX_ASSERT(node->n_dofs(X_sys_num, d) == 1);
                dof_indices.push_back(node->dof_number(X_sys_num, d, 0));
            }
        }

        X_node.resize(dof_indices.size());
        X_ghost_vec.get(dof_indices, X_node.data());
        for (unsigned int k = 0; k < n_nodes; ++k)
        {
            for (unsigned int d = 0; d < NDIM; ++d)
            {
                const double& X = X_node[k * NDIM + d];
                lower_bound(d) = std::min(lower_bound(d), X);
                upper_bound(d) = std::max(upper_bound(d), X);
            }
        }

        // fill extra dimension with 0.0, which is libMesh's convention
        for (unsigned int d = NDIM; d < LIBMESH_DIM; ++d)
        {
            lower_bound(d) = 0.0;
            upper_bound(d) = 0.0;
        }
    }

    return bboxes;
} // get_local_element_bounding_boxes

std::vector<libMeshWrappers::BoundingBox>
get_global_element_bounding_boxes(const libMesh::MeshBase& mesh,
                                  const std::vector<libMeshWrappers::BoundingBox>& local_bboxes)
{
    const std::size_t n_elem = mesh.n_elem();
    std::vector<double> flattened_bboxes(2 * LIBMESH_DIM * n_elem);
    std::size_t elem_n = 0;
    const auto el_begin = mesh.local_elements_begin();
    const auto el_end = mesh.local_elements_end();
    for (auto el_it = el_begin; el_it != el_end; ++el_it)
    {
        const auto id = (*el_it)->id();
        TBOX_ASSERT((2 * id + 2) * LIBMESH_DIM - 1 < flattened_bboxes.size());
        for (unsigned int d = 0; d < LIBMESH_DIM; ++d)
        {
            flattened_bboxes[2 * id * LIBMESH_DIM + d] = local_bboxes[elem_n].first(d);
            flattened_bboxes[(2 * id + 1) * LIBMESH_DIM + d] = local_bboxes[elem_n].second(d);
        }
        ++elem_n;
    }
    const int ierr = MPI_Allreduce(
        MPI_IN_PLACE, flattened_bboxes.data(), flattened_bboxes.size(), MPI_DOUBLE, MPI_SUM, mesh.comm().get());
    TBOX_ASSERT(ierr == 0);

    std::vector<libMeshWrappers::BoundingBox> global_bboxes(n_elem);
    for (unsigned int e = 0; e < n_elem; ++e)
    {
        for (unsigned int d = 0; d < LIBMESH_DIM; ++d)
        {
            global_bboxes[e].first(d) = flattened_bboxes[2 * e * LIBMESH_DIM + d];
            global_bboxes[e].second(d) = flattened_bboxes[(2 * e + 1) * LIBMESH_DIM + d];
        }
    }
    return global_bboxes;
} // get_local_element_bounding_boxes

std::vector<libMeshWrappers::BoundingBox>
get_global_element_bounding_boxes(const libMesh::MeshBase& mesh, const libMesh::System& X_system)
{
    static_assert(NDIM <= LIBMESH_DIM,
                  "NDIM should be no more than LIBMESH_DIM for this function to "
                  "work correctly.");
    return get_global_element_bounding_boxes(mesh, get_local_element_bounding_boxes(mesh, X_system));
} // get_global_element_bounding_boxes
//////////////////////////////////////////////////////////////////////////////

} // namespace IBTK

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


#include "modules/neuralNetwork/layer/output/output.hpp"
#include "modules/neuralNetwork/neuralNetwork.hpp"

#ifdef _KORALI_USE_CUDNN
  #include "auxiliar/cudaUtils.hpp"
#endif

#ifdef _KORALI_USE_ONEDNN
  #include "auxiliar/dnnUtils.hpp"
using namespace dnnl;
#endif

#include <Eigen/Dense>
using namespace Eigen;

namespace korali
{
namespace neuralNetwork
{
namespace layer
{
;

void Output::initialize()
{
  if (_index != _nn->_layers.size() - 1)
    KORALI_LOG_ERROR("Output layers can only be placed at the last position in the NN\n");

  // The node count for this layer should be the same as the previous layer
  _outputChannels = _prevLayer->_outputChannels;

  // Checking Layer size
  if (_outputChannels == 0) KORALI_LOG_ERROR("Node count for layer (%lu) should be larger than zero.\n", _index);

  // Check output scaling configuration
  if (_scale.empty() == false)
    if (_scale.size() != _outputChannels)
      KORALI_LOG_ERROR("Wrong number of output scaling factors passed to the neural network. Expected: %lu, provided: %lu.\n", _outputChannels, _scale.size());

  // Check output shifting configuration
  if (_shift.empty() == false)
    if (_shift.size() != _outputChannels)
      KORALI_LOG_ERROR("Wrong number of output shift factors passed to the neural network. Expected: %lu, provided: %lu.\n", _outputChannels, _shift.size());

  // Initializing transformation vector to know what operations to apply, without the need of constant string comparison
  _transformationVector.resize(_outputChannels);

  // If no transformation mask is specified, use identity exclusively
  if (_transformationMask.size() == 0)
    for (size_t i = 0; i < _outputChannels; i++)
      _transformationVector[i] = t_identity;

  // If incorrect size of mask is specified, produce error
  if (_transformationMask.size() != 0 && _transformationMask.size() != _outputChannels)
    KORALI_LOG_ERROR("Wrong size of transformation mask specified: %lu, expected: %lu.\n", _transformationMask.size(), _outputChannels);

  // If transformation mask is specified, storing the correct transformations
  if (_transformationMask.size() == _outputChannels)
    for (size_t i = 0; i < _outputChannels; i++)
    {
      bool isRecognized = false;
      if (_transformationMask[i] == "Identity")
      {
        _transformationVector[i] = t_identity;
        isRecognized = true;
      }
      if (_transformationMask[i] == "Absolute")
      {
        _transformationVector[i] = t_absolute;
        isRecognized = true;
      }
      if (_transformationMask[i] == "Softplus")
      {
        _transformationVector[i] = t_softplus;
        isRecognized = true;
      }
      if (_transformationMask[i] == "Tanh")
      {
        _transformationVector[i] = t_tanh;
        isRecognized = true;
      }
      if (_transformationMask[i] == "Sigmoid")
      {
        _transformationVector[i] = t_sigmoid;
        isRecognized = true;
      }
      if (isRecognized == false) KORALI_LOG_ERROR("Wrong transformation mask specified: %s for output variable %lu.\n", _transformationMask[i].c_str(), i);
    }
}

void Output::createForwardPipeline()
{
  size_t N = _batchSize;
  size_t IC = _prevLayer->_outputChannels;
  size_t OC = _outputChannels;

  if (IC != OC) KORALI_LOG_ERROR("Output layers node count (%lu) is different than its previous layer's (%lu)\n", OC, IC);

  // Calling base layer function
  Layer::createForwardPipeline();

  // Allocating storage for pre-processing data
  _srcOutputValues = (float *)malloc(N * OC * sizeof(float));
}

void Output::createBackwardPipeline()
{
  size_t N = _batchSize;
  size_t IC = _prevLayer->_outputChannels;
  size_t OC = _outputChannels;

  if (IC != OC) KORALI_LOG_ERROR("Output layers node count (%lu) is different than its previous layer's (%lu)\n", OC, IC);

  // Calling base layer function
  Layer::createBackwardPipeline();

  // Allocating storage for pre-processing data
  _dstOutputGradients = (float *)malloc(N * OC * sizeof(float));
}

void Output::forwardData(const size_t t)
{
  size_t N = _batchSize;
  size_t OC = _outputChannels;

  // Copying previous layer's output to this layer's output
  if (_nn->_engine == "Korali")
  {
    memcpy(_srcOutputValues, _prevLayer->_outputValues, N * OC * sizeof(float));
  }

#ifdef _KORALI_USE_ONEDNN
  if (_nn->_engine == "OneDNN")
  {
    read_from_dnnl_memory(_srcOutputValues, _prevLayer->_outputMem[t]);
  }
#endif

#ifdef _KORALI_USE_CUDNN
  if (_nn->_engine == "CuDNN")
  {
    cudaErrCheck(cudaMemcpy(_srcOutputValues, _prevLayer->_outputTensor[t], N * OC * sizeof(float), cudaMemcpyDeviceToHost));
  }
#endif

  // Performing postprocessing
#pragma omp parallel for
  for (size_t i = 0; i < N; i++)
    for (size_t j = 0; j < OC; j++)
    {
      auto x = _srcOutputValues[i * OC + j];

      // Apply selected transformation now
      if (_transformationVector[j] == t_absolute) x = std::fabs(x);
      if (_transformationVector[j] == t_sigmoid) x = 1. / (1. + std::exp(-x));
      if (_transformationVector[j] == t_softplus) x = 0.5 * (x + std::sqrt(1. + x * x));
      if (_transformationVector[j] == t_tanh) x = std::tanh(x);

      // If we  use scaling, then apply the scaling factors now
      if (_scale.size() > 0) x *= _scale[j];

      // If we  use shifting, then apply the shifting now
      if (_shift.size() > 0) x += _shift[j];

      // Saving result to NN's output directly
      size_t pos = (t * N * OC) + (i * OC) + j;
      _pipeline->_rawOutputValues[pos] = x;
    }
}

void Output::backwardData(const size_t t)
{
  const size_t N = _batchSize;
  const size_t OC = _outputChannels;

  if (_nn->_mode == "Inference")
    KORALI_LOG_ERROR("Requesting Layer backward data propagation but NN was configured for inference only.\n");

// Performing gradient pre-processing
#pragma omp parallel for
  for (size_t i = 0; i < N; i++)
    for (size_t j = 0; j < OC; j++)
    {
      // Getting forward propagation output and passed gradients
      size_t pos = (t * N * OC) + (i * OC) + j;
      float x = _pipeline->_rawOutputValues[pos];
      float g = _pipeline->_rawOutputGradients[pos];

      // If we use shift, then apply the inverse shift to the forward output
      if (_shift.size() > 0)
        x = x - _shift[j];

      // If we use scaling, then apply the inverse scaling factors gradient now
      if (_scale.size() > 0)
      {
        x = x / _scale[j];
        g = g * _scale[j];
      }

      // Apply backward operation on the selected transformations now
      if (_transformationVector[j] == t_absolute)
        if (std::signbit(x) != std::signbit(_srcOutputValues[i * OC + j]))
          g = -g;

      if (_transformationVector[j] == t_tanh)
        g = g * (1.0f - x * x);

      if (_transformationVector[j] == t_softplus)
      {
        float nnx = x - 0.25 / x;
        g = g * 0.5 * (1. + nnx / std::sqrt(nnx * nnx + 1));
      }

      if (_transformationVector[j] == t_sigmoid)
        g = g * x * (1. - x);

      _dstOutputGradients[i * OC + j] = g;
    }

  if (_nn->_engine == "Korali")
  {
    memcpy(_prevLayer->_outputGradient, _dstOutputGradients, N * OC * sizeof(float));
  }

#ifdef _KORALI_USE_ONEDNN
  if (_nn->_engine == "OneDNN")
  {
    write_to_dnnl_memory(_dstOutputGradients, _prevLayer->_outputGradientMem[t]);
  }
#endif

#ifdef _KORALI_USE_CUDNN
  if (_nn->_engine == "CuDNN")
  {
    cudaErrCheck(cudaMemcpy(_prevLayer->_outputGradientTensor[t], _dstOutputGradients, N * OC * sizeof(float), cudaMemcpyHostToDevice));
  }
#endif
}

void Output::setConfiguration(knlohmann::json& js)
{
 if (isDefined(js, "Results"))  eraseValue(js, "Results");

 if (isDefined(js, "Transformation Mask"))
 {
 try { _transformationMask = js["Transformation Mask"].get<std::vector<std::string>>();
} catch (const std::exception& e)
 { KORALI_LOG_ERROR(" + Object: [ output ] \n + Key:    ['Transformation Mask']\n%s", e.what()); }
   eraseValue(js, "Transformation Mask");
 }
  else   KORALI_LOG_ERROR(" + No value provided for mandatory setting: ['Transformation Mask'] required by output.\n");

 if (isDefined(js, "Scale"))
 {
 try { _scale = js["Scale"].get<std::vector<float>>();
} catch (const std::exception& e)
 { KORALI_LOG_ERROR(" + Object: [ output ] \n + Key:    ['Scale']\n%s", e.what()); }
   eraseValue(js, "Scale");
 }
  else   KORALI_LOG_ERROR(" + No value provided for mandatory setting: ['Scale'] required by output.\n");

 if (isDefined(js, "Shift"))
 {
 try { _shift = js["Shift"].get<std::vector<float>>();
} catch (const std::exception& e)
 { KORALI_LOG_ERROR(" + Object: [ output ] \n + Key:    ['Shift']\n%s", e.what()); }
   eraseValue(js, "Shift");
 }
  else   KORALI_LOG_ERROR(" + No value provided for mandatory setting: ['Shift'] required by output.\n");

 Layer::setConfiguration(js);
 _type = "layer/output";
 if(isDefined(js, "Type")) eraseValue(js, "Type");
 if(isEmpty(js) == false) KORALI_LOG_ERROR(" + Unrecognized settings for Korali module: output: \n%s\n", js.dump(2).c_str());
}

void Output::getConfiguration(knlohmann::json& js)
{

 js["Type"] = _type;
   js["Transformation Mask"] = _transformationMask;
   js["Scale"] = _scale;
   js["Shift"] = _shift;
 Layer::getConfiguration(js);
}

void Output::applyModuleDefaults(knlohmann::json& js)
{

 std::string defaultString = "{\"Scale\": [], \"Shift\": [], \"Transformation Mask\": []}";
 knlohmann::json defaultJs = knlohmann::json::parse(defaultString);
 mergeJson(js, defaultJs);
 Layer::applyModuleDefaults(js);
}

void Output::applyVariableDefaults()
{

 Layer::applyVariableDefaults();
}

;

} //layer
} //neuralNetwork
} //korali
;