/**
 * @file minibatch_sgd_test.cpp
 * @author Ryan Curtin
 *
 * Test file for minibatch SGD.
 *
 * mlpack is free software; you may redistribute it and/or modify it under the
 * terms of the 3-clause BSD license.  You should have received a copy of the
 * 3-clause BSD license along with mlpack.  If not, see
 * http://www.opensource.org/licenses/BSD-3-Clause for more information.
 */
#include <mlpack/core.hpp>
#include <mlpack/core/optimizers/sgd/sgd.hpp>
#include <mlpack/core/optimizers/minibatch_sgd/minibatch_sgd.hpp>
#include <mlpack/core/optimizers/lbfgs/test_functions.hpp>
#include <mlpack/core/optimizers/sgd/test_function.hpp>

#include <mlpack/methods/logistic_regression/logistic_regression.hpp>

#include <boost/test/unit_test.hpp>
#include "test_tools.hpp"

using namespace std;
using namespace arma;
using namespace mlpack;
using namespace mlpack::optimization;
using namespace mlpack::optimization::test;

using namespace mlpack::distribution;
using namespace mlpack::regression;

BOOST_AUTO_TEST_SUITE(MiniBatchSGDTest);

/**
 * If the batch size is 1, and we aren't shuffling, we should get the exact same
 * results as regular SGD.
 */
BOOST_AUTO_TEST_CASE(SGDSimilarityTest)
{
  SGDTestFunction f;
  StandardSGD s(0.0003, 100000, 1e-4, false);
  MiniBatchSGD ms(1, 0.0003, 100000, 1e-4, false);

  arma::mat sCoord = f.GetInitialPoint();
  arma::mat msCoord = f.GetInitialPoint();

  const double sResult = s.Optimize(f, sCoord);
  const double msResult = ms.Optimize(f, msCoord);

  BOOST_REQUIRE_CLOSE(sResult, msResult, 1e-2);
  BOOST_REQUIRE_CLOSE(sCoord[0], msCoord[0], 1e-2);
  BOOST_REQUIRE_CLOSE(sCoord[1], msCoord[1], 1e-2);
  BOOST_REQUIRE_CLOSE(sCoord[2], msCoord[2], 1e-2);
}

/*
BOOST_AUTO_TEST_CASE(SimpleSGDTestFunction)
{
  SGDTestFunction f;
  // Batch size of 3.
  MiniBatchSGD<SGDTestFunction> s(f, 3, 0.0005, 2000000, 1e-9, true);

  arma::mat coordinates = f.GetInitialPoint();
  double result = s.Optimize(coordinates);

  BOOST_REQUIRE_CLOSE(result, -1.0, 0.05);
  BOOST_REQUIRE_SMALL(coordinates[0], 1e-3);
  BOOST_REQUIRE_SMALL(coordinates[1], 1e-7);
  BOOST_REQUIRE_SMALL(coordinates[2], 1e-7);
}
*/

/**
 * Run mini-batch SGD on logistic regression and make sure the results are
 * acceptable.
 */
BOOST_AUTO_TEST_CASE(LogisticRegressionTest)
{
  // Generate a two-Gaussian dataset.
  GaussianDistribution g1(arma::vec("1.0 1.0 1.0"), arma::eye<arma::mat>(3, 3));
  GaussianDistribution g2(arma::vec("9.0 9.0 9.0"), arma::eye<arma::mat>(3, 3));

  arma::mat data(3, 500);
  arma::Row<size_t> responses(500);
  for (size_t i = 0; i < 250; ++i)
  {
    data.col(i) = g1.Random();
    responses[i] = 0;
  }
  for (size_t i = 250; i < 500; ++i)
  {
    data.col(i) = g2.Random();
    responses[i] = 1;
  }

  // Shuffle the dataset.
  arma::uvec indices = arma::shuffle(arma::linspace<arma::uvec>(0,
      data.n_cols - 1, data.n_cols));
  arma::mat shuffledData(3, 500);
  arma::Row<size_t> shuffledResponses(500);
  for (size_t i = 0; i < data.n_cols; ++i)
  {
    shuffledData.col(i) = data.col(indices[i]);
    shuffledResponses[i] = responses[indices[i]];
  }

  // Create a test set.
  arma::mat testData(3, 500);
  arma::Row<size_t> testResponses(500);
  for (size_t i = 0; i < 250; ++i)
  {
    testData.col(i) = g1.Random();
    testResponses[i] = 0;
  }
  for (size_t i = 250; i < 500; ++i)
  {
    testData.col(i) = g2.Random();
    testResponses[i] = 1;
  }

  // Now run mini-batch SGD with a couple of batch sizes.
  for (size_t batchSize = 5; batchSize < 50; batchSize += 5)
  {
    MiniBatchSGD mbsgd(batchSize);
    LogisticRegression<> lr(shuffledData, shuffledResponses, mbsgd, 0.5);

    // Ensure that the error is close to zero.
    const double acc = lr.ComputeAccuracy(data, responses);
    BOOST_REQUIRE_CLOSE(acc, 100.0, 0.3); // 0.3% error tolerance.

    const double testAcc = lr.ComputeAccuracy(testData, testResponses);
    BOOST_REQUIRE_CLOSE(testAcc, 100.0, 0.6); // 0.6% error tolerance.
  }
}

/**
 * Run mini-batch SGD on a simple test function and make sure the last batch
 * size is handled correctly.
 *
 * When using a batchsize that fulfilled the constraint:
 * (numFunctions % batchSize) == 1 we have to make sure that the last batch size
 * isn't zero.
 */
BOOST_AUTO_TEST_CASE(ZeroBatchSizeTest)
{
  // Create the generalized Rosenbrock function.
  GeneralizedRosenbrockFunction f(10);

  MiniBatchSGD s(f.NumFunctions() - 1, 0.01, 3);

  arma::mat coordinates = f.GetInitialPoint();
  s.Optimize(f, coordinates);

  const bool finite = coordinates.is_finite();
  BOOST_REQUIRE_EQUAL(finite, true);
}

BOOST_AUTO_TEST_SUITE_END();


/* ScaFES
 * Copyright (c) 2017-2018, ZIH, TU Dresden, Federal Republic of Germany.
 * For details, see the files COPYING and LICENSE in the base directory
 * of the package.
 */

/**
 *  @file HeatEqnFDMInitFile.hpp
 *
 *  @brief Implementation of n-dimensional heat equation problem on unit hybercube
 *         with init function and config file parser.
 */

#include "ScaFES.hpp"
#include "analyticalFunctions.hpp"

#include <boost/property_tree/ini_parser.hpp>

/*******************************************************************************
 ******************************************************************************/
/**
 * \class HeatEqnFDM
 *  @brief Class for discretized heat equation problem.
 *
 * \section heatEqnFDM 3D Heat Equation Problem on Unit Cube
 *
 *
 * \subsection mathdescr Mathematical Description
 * Given:
 *

 *
 Time interval \f[[t_S; t_E] \mbox{ with } 0 \le t_S < t_E,\f]

 *
 domain \f[\Omega := (0,1)^3,\f]

 *
 source \f[f: \bar{\Omega} \times (t_S;t_E] \to R, \quad
        f(x,t) := 0,\f]

 *
 boundary condition \f[g: \partial\Omega \times (t_S;t_E] \to R, \quad
         g(x,t) := 0,\f]

 *
 initial condition \f[\widetilde{y}: \bar{\Omega} \to R, \quad
        \widetilde{y}(x) :=  \prod_{i=0}^{d-1} x_i \cdot
                    (x_i - 1/2)^2 \cdot (1 - x_i),\f]

 *

 * Find \f[y: \bar{\Omega} \times [t_S; t_E] \to {R}\f] such that
 * \f{eqnarray*}{
 * \partial_t y - \Delta y & =&  f  \quad \mbox{in }
 *          \Omega \times (t_S;t_E], \\
 *                      y & =&  g \quad \mbox{on }
 *          \partial\Omega \times (t_S;t_E], \\
 *             y(\cdot,t_S) & =& \widetilde{y}  \quad \mbox{in } \bar{\Omega}.
 * \f}
 *
 * \subsection mathdiscretization Discretization of the problem
 * \subsubsection discrTD Discretization of the time interval and the domain
 * Let the time interval [t_S;t_E] be uniformly discretised with
 * \f[t_l := \{ t_S + l \cdot \tau\}_l \f]
 * with time step size tau > 0.
 *
 * Let the domain Omega be uniformly discretised with
 * \f[ x_{(i,j,k)} :=  \{ (i \cdot h_0, j \cdot h_1, k \cdot h_2) \}_{(i,j,k)}
 * \f]
 * with grid sizes h_p>0 for all p.
 *

 *
G_I: Set of all interior grid nodes,

 *
G_B: Set of all boundary grid nodes,

 *
G := G_I with G_B: Set of all grid nodes

 *

 *
 * \subsubsection discrHeatEqn Discretization of the heat equation
 * Define the following vectors:
 * \f{eqnarray*}{
 * Y^{(l)}_{(i,j,k)} &:=& y(x_{(i,j,k)},t_l), \\
 * F^{(l)}_{(i,j,k)} &:=& f(x_{(i,j,k)},t_l), \\
 * G^{(l)}_{(i,j,k)} &:=& g(x_{(i,j,k)},t_l), \\
 * \widetilde{Y}_{(i,j,k)} &:=& \widetilde{y}(x_{(i,j,k)}) \\
 * && \quad \mbox{for all } t_l \in \tau_h, x_{(i,j,k)} \in \Omega_h.
 * \f}
 * Discretise derivatives in space using the symmetric difference quotients
 * (Finite Difference Method with 7-point stencil) and in time using the
 * forward difference quotient (explicit Euler scheme):
 * Discretise in space (Finite Difference Method with 7-point stencil)
 * and in time (explicit Euler scheme):
 * \f{eqnarray*}{
 * Y^{(l+1)}_{(i,j,k)} &=&  Y^{(l)}_{(i,j,k)}
 *                   + \big( Y^{(l)}_{(i-1,j,k)} - 2 \cdot Y^{(l)}_{(i,j,k)}
 *               + Y^{(l)}_{(i+1,j,k)} \big) \cdot \tau / h_0^2   \\
 * &&
 *               \quad  \quad \quad  \quad  + \big(  Y^{(l)}_{(i,j-1,k)} - 2 \cdot Y^{(l)}_{(i,j,k)}
 *               + Y^{(l)}_{(i,j+1,k)} \big) \cdot \tau / h_1^2   \\
 * &&            \quad \quad  \quad \quad  + \big( Y^{(l)}_{(i,j,k-1)}  - 2 \cdot Y^{(l)}_{(i,j,k)}
 *               + Y^{(l)}_{(i,j,k+1)} \big) \cdot \tau / h_2^2  \\
 * &&  \quad \quad \quad\quad + \tau \cdot F^{(l)}_{(i,j,k)}  \quad\quad
 * \forall l, \, \forall {(i,j,k)} \in \cal{G}_I, \\
 * Y^{(l+1)}_{(i,j,k)} &=&  G^{(l+1)}_{(i,j,k)}
 *               \quad   \quad \forall l, \, \forall {(i,j,k)} \in \cal{G}_B, \\
 *            Y^{(0)}_{(i,j,k)} & =&  \widetilde{Y}_{(i,j,k)}\quad \quad
 * \forall {(i,j,k)} \in \cal{G}.
 * \f}
*/
template<typename CT, std::size_t DIM>
class HeatEqnFDM : public ScaFES::Problem<HeatEqnFDM<CT,DIM>, CT, DIM> {
   private:
   using PTree = boost::property_tree::ptree;
   const PTree ptree;

   public:
    /** Coefficient a. */
    const double A;

    /** Coefficient alpha. */
    const double ALPHA;

    /** Coefficient c. */
    const double C;

    /** Coefficient lambda. */
    const double LAMBDA;

    /** Coefficient rho. */
    const double RHO;

   public:
    /** All fields which are related to the underlying problem
     * are added in terms of an entry of the parameters of
     * type \c std::vector.
     * @param params Set of ScaFES parameters.
     * @param gg Global grid.
     * @param useLeapfrog Should the leap frog scheme be used?
     * @param nameDatafield Name of the fields.
     * @param stencilWidth Stencil width of the fields.
     * @param isKnownDf Is the data field are known or unknown one?
     * @param nLayers Number of layers at the global boundary.
     * @param defaultValue Default value of fields.
     * @param writeToFile How often should the data field be written to file.
     * @param computeError Should the Linf error between the numerical
     *                     and exact solution be computed?
     * @param geomparamsInit Initial guess of geometrical parameters.
     */
    HeatEqnFDM(ScaFES::Parameters const& params,
               ScaFES::GridGlobal<DIM> const& gg,
               bool useLeapfrog,
               std::vector<std::string> const& nameDatafield,
               std::vector<int> const& stencilWidth,
               std::vector<bool> const& isKnownDf,
               const PTree& ptree_,
               std::vector<int> const& nLayers = std::vector<int>(),
               std::vector<CT> const& defaultValue = std::vector<CT>(),
               std::vector<ScaFES::WriteHowOften> const& writeToFile
                 = std::vector<ScaFES::WriteHowOften>(),
               std::vector<bool> const& computeError = std::vector<bool>(),
               std::vector<CT> const& geomparamsInit = std::vector<CT>() )
        : ScaFES::Problem<HeatEqnFDM<CT, DIM>, CT, DIM>(params, gg, useLeapfrog,
                                                        nameDatafield, stencilWidth,
                                                        isKnownDf, nLayers,
                                                        defaultValue, writeToFile,
                                                        computeError, geomparamsInit),
        ptree(ptree_),
        A(ptree.get<CT>("Parameters.a")),
        ALPHA(ptree.get<CT>("Parameters.alpha")),
        C(ptree.get<CT>("Parameters.c")),
        LAMBDA(ptree.get<CT>("Parameters.lambda")),
        RHO(ptree.get<CT>("Parameters.rho"))
        { }

    /** Evaluates all fields at one given global inner grid node.
     *  @param vNew Set of all fields.
     *  @param idxNode Index of given grid node.
     */
    void evalInner(std::vector< ScaFES::DataField<CT, DIM> >& vNew,
                   ScaFES::Ntuple<int,DIM> const& idxNode,
                   int const& timestep) {
        ScaFES::Ntuple<double,DIM> x = this->coordinates(idxNode);
        double t = this->time(timestep);

        /* Vector for f. */
        vNew[0](idxNode) = this->RHO * this->C * timeLinSpaceLindTime<CT,DIM>(x);
        vNew[0](idxNode) -= this->LAMBDA * timeLinSpaceLinSumOfdSpace2ndOrder<CT,DIM>(x, t);
        /* Vector for g. */
        vNew[1](idxNode) = timeLinSpaceLinFunc<CT,DIM>(x, t);
        /* Vector for y. */
        vNew[2](idxNode) = timeLinSpaceLinFunc<CT,DIM>(x, t);
    }

    /** Evaluates all fields at one given global border grid node.
     *  @param vNew Set of all fields.
     *  @param idxNode Index of given grid node.
     *  @param timestep Given time step.
     */
    void evalBorder(std::vector< ScaFES::DataField<CT, DIM> >& vNew,
                    ScaFES::Ntuple<int,DIM> const& idxNode,
                    int const& timestep) {
        this->evalInner(vNew, idxNode, timestep);
    }

    /** Initializes all unknown fields at one given global inner grid node.
     *  @param vNew Set of all unknown fields (return value).
     *  @param idxNode Index of given grid node.
     */
    template<typename TT>
    void initInner(std::vector< ScaFES::DataField<TT, DIM> >& vNew,
                   std::vector<TT> const& /*vOld*/,
                   ScaFES::Ntuple<int,DIM> const& idxNode,
                   int const& timestep) {
        ScaFES::Ntuple<double,DIM> x = this->coordinates(idxNode);
        double t_s = this->time(timestep);

        vNew[0](idxNode) = timeLinSpaceLinFunc<CT,DIM>(x, t_s);
    }

    /** Initializes all unknown fields at one given global border grid node.
     *  @param vNew Set of all unknown fields (return value).
     *  @param vOld Set of all given fields.
     *  @param idxNode Index of given grid node.
     *  @param timestep Given time step.
     */
    template<typename TT>
    void initBorder(std::vector< ScaFES::DataField<TT, DIM> >& vNew,
                    std::vector<TT> const& vOld,
                    ScaFES::Ntuple<int,DIM> const& idxNode,
                    int const& timestep) {
        this->template initInner<TT>(vNew, vOld, idxNode, timestep);
    }

    /** Updates all unknown fields at one given global inner grid node.
     *  @param vNew Set of all unknown fields at new time step (return value).
     *  @param vOld Set of all unknown fields at old time step.
     *  @param idxNode Index of given grid node.
     */
    template<typename TT>
    void updateInner(std::vector<ScaFES::DataField<TT,DIM>>& vNew,
                     std::vector<ScaFES::DataField<TT,DIM>> const& vOld,
                     ScaFES::Ntuple<int,DIM> const& idxNode,
                     int const& /*timestep*/) {
        vNew[0](idxNode) = vOld[0](idxNode);
        for (std::size_t pp = 0; pp < DIM; ++pp) {
            vNew[0](idxNode) += this->tau() * A * (
                     vOld[0](this->connect(idxNode, 2*pp))
                     + vOld[0](this->connect(idxNode, 2*pp+1))
                     - 2.0 * vOld[0](idxNode) )
                     / (this->gridsize(pp) * this->gridsize(pp));
        }
        vNew[0](idxNode) += this->tau() * (1.0/(RHO*C)) * this->knownDf(0, idxNode);
    }

    /** Updates all unknown fields at one given global border grid node.
     *  @param vNew Set of all unknown fields at new time step (return value).
     *  @param idxNode Index of given grid node.
     */
    template<typename TT>
    void updateBorder(std::vector<ScaFES::DataField<TT,DIM>>& vNew,
                      std::vector<ScaFES::DataField<TT,DIM>>const& /*vOld*/,
                      ScaFES::Ntuple<int,DIM> const& idxNode,
                      int const& /*timestep*/) {
        vNew[0](idxNode) = this->knownDf(1, idxNode);
    }

    /** Updates (2nd cycle) all unknown fields at one given global inner grid node.
     *  \remarks Only important if leap frog scheme is used.
     */
    template<typename TT>
    void updateInner2(std::vector<ScaFES::DataField<TT,DIM>>&,
                      std::vector<ScaFES::DataField<TT,DIM>> const&,
                      ScaFES::Ntuple<int,DIM> const&,
                      int const&) { }

    /** Updates (2nd cycle) all unknown fields at one given global border
     *  grid node.
     *  \remarks Only important if leap frog scheme is used.
     */
    template<typename TT>
    void updateBorder2(std::vector<ScaFES::DataField<TT,DIM>>&,
                       std::vector<ScaFES::DataField<TT,DIM>>const&,
                       ScaFES::Ntuple<int,DIM> const&,
                       int const&) { }
};


//
// Created by tom on 09/01/2021.
//

#include <sstream>
#include <fstream>
#include <iostream>

#include <Eigen/Dense>
#include "utils.h"

vector<string> utils::split(const string &s, char delim) {
    /*********************************************************
     *  Split a string by a delimiter into a vector of strings
     ********************************************************/
    stringstream stream(s);
    string item;
    vector<string> elems;
    while (getline(stream, item, delim)) {
        if (!item.empty()){
            elems.push_back(item);
        }
    }
    return elems;
}

Eigen::MatrixXd utils::symmetric_matrix_from_file(const string &filename){
    /*******************************************************
     *  Extract a symmetric matrix from a file, where the
     *  file is formatted as:
     *
     *  idx1  idx2 value
     ******************************************************/
    string line, item;
    ifstream input_file(filename);

    std::vector<long> idxs1;
    std::vector<long> idxs2;
    std::vector<double> values;

    // Iterate through the file line by line
    while (getline(input_file, line, '\n')) {

        // Ignore any blank lines
        if (line.empty()) {
            continue;
        }
        vector<string> items = utils::split(line, ' ');
        if (items.size() != 3){
            throw runtime_error("Overlap matrix file not correctly formatted."
                                "Expecting lines in the format: idx1  idx2 value");
        }
        idxs1.push_back(stol(items[0]));
        idxs2.push_back(stol(items[1]));
        values.push_back(stod(items[2]));

    }
    long max_idx1 = *max_element(begin(idxs1), end(idxs1));
    long min_idx1 = *min_element(begin(idxs1), end(idxs1));
    long size = max_idx1 - min_idx1 + 1;     // Allows for indexing from 0 or 1 in the file

    Eigen::MatrixXd matrix(size, size);
    for (int n = 0; n < values.size(); n++){

        long i = idxs1[n] - min_idx1;
        long j = idxs2[n] - min_idx1;

        matrix(i, j) = values[n];
        // And because the matrix is symmetric the flipped element can also be set
        matrix(j, i) = values[n];
    }

    return matrix;
}

Eigen::VectorXd utils::ee_from_file(const string &filename, const int &length){
    /*******************************************************
     *  Extract a 1D vector of values for 4 index electron
     *  repulsion integrals from a file
     *
     *  idx1  idx2 idx3 idx4  value
     ******************************************************/
    // Set up the vector we're going to populate
    int M = (length*(length+1))/2;
    int max_ijkl = (M*(M+1))/2;
    Eigen::VectorXd ee_vector(max_ijkl+1);

    string line;
    int i, j, k, l;
    double value;

    ifstream input_file(filename);

    // Iterate through the file line by line
    while (getline(input_file, line, '\n')) {

        // Ignore any blank lines
        if (line.empty()) {
            continue;
        }
        vector<string> items = utils::split(line, ' ');
        if (items.size() != 5){
            throw runtime_error("Unexpected line length");
        }

        // Indexes from 1
        i = stoi(items[0]) - 1;
        j = stoi(items[1]) - 1;
        k = stoi(items[2]) - 1;
        l = stoi(items[3]) - 1;

        value = stod(items[4]);

        if (i < j){
            swap(i, j);
        }
        if (k < l){
            swap(k, l);
        }

        int ij = i * (i + 1) / 2 + j;
        int kl = k * (k + 1) / 2 + l;

        if (ij < kl){
            swap(ij, kl);
        }
        int idx = ij*(ij+1)/2+kl;

        if (idx >= ee_vector.size()){
            throw runtime_error("Index not settable, exceeded the "
                                "size of the array");
        }
        ee_vector(idx) = value;
    }
    return ee_vector;
}


#include <gsl/gsl_integration.h>

#include <algorithm>

#include <Eigen/Dense>

#include "GaussLegendre2d.hh"
#include "TypesFunctions.hh"

using namespace Eigen;

void GaussLegendre2d::init() {
  size_t npoints = 0;
  for (size_t n: m_xorders) {
    npoints += n;
  }
  m_xpoints.resize(npoints);
  m_xweights.resize(npoints);
  size_t k = 0;
  for (size_t i = 0; i < m_xorders.size(); ++i) {
    size_t n = m_xorders[i];
    auto *t = gsl_integration_glfixed_table_alloc(n);

    double a = m_xedges[i];
    double b = m_xedges[i+1];

    for (size_t j = 0; j < t->n; ++j) {
      gsl_integration_glfixed_point(a, b, j, &m_xpoints[k], &m_xweights[k], t);
      k++;
    }
    gsl_integration_glfixed_table_free(t);
  }

  auto *t = gsl_integration_glfixed_table_alloc(m_yorder);
  m_ypoints.resize(t->n);
  m_yweights.resize(t->n);
  for (size_t i = 0; i < t->n; ++i) {
    gsl_integration_glfixed_point(m_ymin, m_ymax, i,
                                  &m_ypoints[i], &m_yweights[i], t);
  }
  gsl_integration_glfixed_table_free(t);

  transformation_("points")
    .output("x")
    .output("y")
    .output("xedges")
    .output("xhist")
    .types([](GaussLegendre2d *obj, TypesFunctionArgs& fargs) {
        auto& rets=fargs.rets;
        rets[0] = DataType().points().shape(obj->m_xpoints.size());
        rets[1] = DataType().points().shape(obj->m_ypoints.size());
        rets[2] = DataType().points().shape(obj->m_xedges.size());
        rets[3] = DataType().hist().edges(obj->m_xedges);
      })
    .func([](GaussLegendre2d *obj, FunctionArgs& fargs) {
        auto& rets=fargs.rets;
        rets[0].x = Eigen::Map<const Eigen::ArrayXd>(&obj->m_xpoints[0], obj->m_xpoints.size());
        rets[1].x = Eigen::Map<const Eigen::ArrayXd>(&obj->m_ypoints[0], obj->m_ypoints.size());
        rets[2].x = Eigen::Map<const Eigen::ArrayXd>(&obj->m_xedges[0], obj->m_xedges.size());
        rets.untaint();
        rets.freeze();
      })
    .finalize()
    ;
}

GaussLegendre2dHist::GaussLegendre2dHist(const GaussLegendre2d *base)
  : m_base(base)
{
  transformation_("hist")
    .input("f")
    .output("hist")
    .types(TypesFunctions::ifSame, [](GaussLegendre2dHist *obj, TypesFunctionArgs& fargs) {
        fargs.rets[0] = DataType().hist().bins(obj->m_base->m_xorders.size()).edges(obj->m_base->m_xedges);
      })
    .func([](GaussLegendre2dHist *obj, FunctionArgs& fargs) {
        auto& args=fargs.args;
        auto& rets=fargs.rets;
        size_t shape[2];
        shape[0] = obj->m_base->m_xpoints.size();
        shape[1] = obj->m_base->m_ypoints.size();
        for (size_t k = 0; k < rets.size(); ++k) {
          Map<const ArrayXXd, Aligned> pts(args[k].x.data(), shape[0], shape[1]);
          auto &xw = obj->m_base->m_xweights, &yw = obj->m_base->m_yweights;
          ArrayXd prod = (pts.rowwise()*yw.transpose()).rowwise().sum()*xw;
          auto *data = prod.data();
          for (size_t i = 0; i < obj->m_base->m_xorders.size(); ++i) {
            size_t n = obj->m_base->m_xorders[i];
            rets[k].x(i) = std::accumulate(data, data+n, 0.0);
            data += n;
          }
        }
      })
    ;
}


#include <Eigen/Eigenvalues>
#include <Eigen/Geometry>
#include <algorithm>
#include <cstring>
#include <elasty/constraint.hpp>
#include <elasty/fem.hpp>
#include <elasty/particle.hpp>

namespace
{
    inline Eigen::Matrix3d convertVecToCrossOp(const Eigen::Vector3d& vec)
    {
        Eigen::Matrix3d mat = Eigen::Matrix3d::Zero();

        mat(0, 1) = -vec(2);
        mat(0, 2) = +vec(1);
        mat(1, 0) = +vec(2);
        mat(1, 2) = -vec(0);
        mat(2, 0) = -vec(1);
        mat(2, 1) = +vec(0);

        return mat;
    };

    inline double calculateCotTheta(const Eigen::Vector3d& x, const Eigen::Vector3d& y)
    {
        const double scaled_cos_theta = x.dot(y);
        const double scaled_sin_theta = x.cross(y).norm();
        return scaled_cos_theta / scaled_sin_theta;
    }
} // namespace

elasty::BendingConstraint::BendingConstraint(const std::shared_ptr<Particle> p_0,
                                             const std::shared_ptr<Particle> p_1,
                                             const std::shared_ptr<Particle> p_2,
                                             const std::shared_ptr<Particle> p_3,
                                             const double                    stiffness,
                                             const double                    compliance,
                                             const double                    delta_time,
                                             const double                    dihedral_angle)
    : FixedNumAbstractConstraint(std::vector<std::shared_ptr<Particle>>{p_0, p_1, p_2, p_3},
                                 stiffness,
                                 compliance,
                                 delta_time),
      m_dihedral_angle(dihedral_angle)
{
}

double elasty::BendingConstraint::calculateValue()
{
    const Eigen::Vector3d& x_0 = m_particles[0]->p;
    const Eigen::Vector3d& x_1 = m_particles[1]->p;
    const Eigen::Vector3d& x_2 = m_particles[2]->p;
    const Eigen::Vector3d& x_3 = m_particles[3]->p;

    const Eigen::Vector3d p_10 = x_1 - x_0;
    const Eigen::Vector3d p_20 = x_2 - x_0;
    const Eigen::Vector3d p_30 = x_3 - x_0;

    const Eigen::Vector3d n_0 = p_10.cross(p_20).normalized();
    const Eigen::Vector3d n_1 = p_10.cross(p_30).normalized();

    const double current_dihedral_angle = std::acos(std::clamp(n_0.dot(n_1), -1.0, 1.0));

    assert(n_0.norm() > 0.0);
    assert(n_1.norm() > 0.0);
    assert(!std::isnan(current_dihedral_angle));

    return current_dihedral_angle - m_dihedral_angle;
}

// See the appendix of the original paper by Muller et al. (2007) for details.
void elasty::BendingConstraint::calculateGrad(double* grad_C)
{
    const Eigen::Vector3d& x_0 = m_particles[0]->p;
    const Eigen::Vector3d& x_1 = m_particles[1]->p;
    const Eigen::Vector3d& x_2 = m_particles[2]->p;
    const Eigen::Vector3d& x_3 = m_particles[3]->p;

    // Assuming that p_0 = [ 0, 0, 0 ]^T without loss of generality
    const Eigen::Vector3d p_1 = x_1 - x_0;
    const Eigen::Vector3d p_2 = x_2 - x_0;
    const Eigen::Vector3d p_3 = x_3 - x_0;

    const Eigen::Vector3d p_1_cross_p_2 = p_1.cross(p_2);
    const Eigen::Vector3d p_1_cross_p_3 = p_1.cross(p_3);

    const Eigen::Vector3d n_0 = p_1_cross_p_2.normalized();
    const Eigen::Vector3d n_1 = p_1_cross_p_3.normalized();

    const double d = n_0.dot(n_1);

    // If the current dihedral angle is sufficiently small or large (i.e., zero or pi), return zeros.
    // This is only an ad-hoc solution for stability and it needs to be solved in a more theoretically grounded way.
    constexpr double epsilon = 1e-12;
    if (1.0 - d * d < epsilon)
    {
        std::fill(grad_C, grad_C + 12, 0.0);
        return;
    }

    const double common_coeff = -1.0 / std::sqrt(1.0 - d * d);

    auto calc_grad_of_normalized_cross_prod_wrt_p_a =
        [](const Eigen::Vector3d& p_a, const Eigen::Vector3d& p_b, const Eigen::Vector3d& n) -> Eigen::Matrix3d
    {
        return +(1.0 / p_a.cross(p_b).norm()) * (-convertVecToCrossOp(p_b) + n * (n.cross(p_b)).transpose());
    };

    auto calc_grad_of_normalized_cross_prod_wrt_p_b =
        [](const Eigen::Vector3d& p_a, const Eigen::Vector3d& p_b, const Eigen::Vector3d& n) -> Eigen::Matrix3d
    {
        return -(1.0 / p_a.cross(p_b).norm()) * (-convertVecToCrossOp(p_a) + n * (n.cross(p_a)).transpose());
    };

    const Eigen::Matrix3d partial_n_0_per_partial_p_1 = calc_grad_of_normalized_cross_prod_wrt_p_a(p_1, p_2, n_0);
    const Eigen::Matrix3d partial_n_1_per_partial_p_1 = calc_grad_of_normalized_cross_prod_wrt_p_a(p_1, p_3, n_1);
    const Eigen::Matrix3d partial_n_0_per_partial_p_2 = calc_grad_of_normalized_cross_prod_wrt_p_b(p_1, p_2, n_0);
    const Eigen::Matrix3d partial_n_1_per_partial_p_3 = calc_grad_of_normalized_cross_prod_wrt_p_b(p_1, p_3, n_1);

    const Eigen::Vector3d grad_C_wrt_p_1 =
        common_coeff * (partial_n_0_per_partial_p_1.transpose() * n_1 + partial_n_1_per_partial_p_1.transpose() * n_0);
    const Eigen::Vector3d grad_C_wrt_p_2 = common_coeff * partial_n_0_per_partial_p_2.transpose() * n_1;
    const Eigen::Vector3d grad_C_wrt_p_3 = common_coeff * partial_n_1_per_partial_p_3.transpose() * n_0;
    const Eigen::Vector3d grad_C_wrt_p_0 = -grad_C_wrt_p_1 - grad_C_wrt_p_2 - grad_C_wrt_p_3;

    std::memcpy(grad_C + (3 * 0), grad_C_wrt_p_0.data(), sizeof(double) * 3);
    std::memcpy(grad_C + (3 * 1), grad_C_wrt_p_1.data(), sizeof(double) * 3);
    std::memcpy(grad_C + (3 * 2), grad_C_wrt_p_2.data(), sizeof(double) * 3);
    std::memcpy(grad_C + (3 * 3), grad_C_wrt_p_3.data(), sizeof(double) * 3);
}

elasty::ContinuumTriangleConstraint::ContinuumTriangleConstraint(const std::shared_ptr<Particle> p_0,
                                                                 const std::shared_ptr<Particle> p_1,
                                                                 const std::shared_ptr<Particle> p_2,
                                                                 const double                    stiffness,
                                                                 const double                    compliance,
                                                                 const double                    delta_time,
                                                                 const double                    youngs_modulus,
                                                                 const double                    poisson_ratio)
    : FixedNumAbstractConstraint(std::vector<std::shared_ptr<Particle>>{p_0, p_1, p_2},
                                 stiffness,
                                 compliance,
                                 delta_time),
      m_first_lame(fem::calcFirstLame(youngs_modulus, poisson_ratio)),
      m_second_lame(fem::calcSecondLame(youngs_modulus, poisson_ratio))
{
    const Eigen::Vector3d& x_0 = m_particles[0]->x;
    const Eigen::Vector3d& x_1 = m_particles[1]->x;
    const Eigen::Vector3d& x_2 = m_particles[2]->x;

    // Calculate the two axes for defining material coordinates
    const Eigen::Vector3d r_1    = x_1 - x_0;
    const Eigen::Vector3d r_2    = x_2 - x_0;
    const Eigen::Vector3d cross  = r_1.cross(r_2);
    const Eigen::Vector3d axis_1 = r_1.normalized();
    const Eigen::Vector3d axis_2 = cross.cross(axis_1).normalized();

    // Calculate the rest positions in the material coordinates
    const Eigen::Vector2d mat_x_0(axis_1.dot(x_0), axis_2.dot(x_0));
    const Eigen::Vector2d mat_x_1(axis_1.dot(x_1), axis_2.dot(x_1));
    const Eigen::Vector2d mat_x_2(axis_1.dot(x_2), axis_2.dot(x_2));

    // Calculate the rest shape matrix
    Eigen::Matrix2d rest_D;
    rest_D.col(0) = mat_x_1 - mat_x_0;
    rest_D.col(1) = mat_x_2 - mat_x_0;

    // Calculate the inverse of the rest shape matrix
    assert(rest_D.determinant() > 0.0);
    m_rest_D_inv = rest_D.inverse();

    // Calculate the area of the rest configuration
    m_rest_area = 0.5 * cross.norm();
}

double elasty::ContinuumTriangleConstraint::calculateValue()
{
    const Eigen::Vector3d& x_0 = m_particles[0]->p;
    const Eigen::Vector3d& x_1 = m_particles[1]->p;
    const Eigen::Vector3d& x_2 = m_particles[2]->p;

    // Calculate the shape matrix
    Eigen::Matrix<double, 3, 2> D;
    D.col(0) = x_1 - x_0;
    D.col(1) = x_2 - x_0;

    // Calculate the deformation gradient (a 3-by-2 matrix)
    const auto F = D * m_rest_D_inv;

    // Calculate the strain energy density
    const double psi = fem::calcStVenantKirchhoffEnergyDensity(F, m_first_lame, m_second_lame);

    // Return the constraint value
    return m_rest_area * psi;
}

void elasty::ContinuumTriangleConstraint::calculateGrad(double* grad_C)
{
    const Eigen::Vector3d& x_0 = m_particles[0]->p;
    const Eigen::Vector3d& x_1 = m_particles[1]->p;
    const Eigen::Vector3d& x_2 = m_particles[2]->p;

    // Calculate the shape matrix
    Eigen::Matrix<double, 3, 2> D;
    D.col(0) = x_1 - x_0;
    D.col(1) = x_2 - x_0;

    // Calculate the deformation gradient (a 3-by-2 matrix)
    const auto F = D * m_rest_D_inv;

    // Calculate the first Piola-Kirchhoff stress tensor (a 3-by-2 matrix)
    const auto P = fem::calcStVenantKirchhoffPiolaStress(F, m_first_lame, m_second_lame);

    // Calculate the gradient of the constraint
    const Eigen::Matrix<double, 3, 2> grad_12 = m_rest_area * P * m_rest_D_inv.transpose();
    const Eigen::Vector3d             grad_0  = -grad_12.col(0) - grad_12.col(1);

    // Copy the results
    std::memcpy(grad_C + 0, grad_0.data(), sizeof(double) * 3);
    std::memcpy(grad_C + 3, grad_12.data(), sizeof(double) * 6);
}

elasty::DistanceConstraint::DistanceConstraint(const std::shared_ptr<Particle> p_0,
                                               const std::shared_ptr<Particle> p_1,
                                               const double                    stiffness,
                                               const double                    compliance,
                                               const double                    delta_time,
                                               const double                    d)
    : FixedNumAbstractConstraint(std::vector<std::shared_ptr<Particle>>{p_0, p_1}, stiffness, compliance, delta_time),
      m_d(d)
{
    assert(d >= 0.0);
}

double elasty::DistanceConstraint::calculateValue()
{
    const Eigen::Vector3d& x_0 = m_particles[0]->p;
    const Eigen::Vector3d& x_1 = m_particles[1]->p;

    return (x_0 - x_1).norm() - m_d;
}

void elasty::DistanceConstraint::calculateGrad(double* grad_C)
{
    const Eigen::Vector3d& x_0 = m_particles[0]->p;
    const Eigen::Vector3d& x_1 = m_particles[1]->p;

    const Eigen::Vector3d r = x_0 - x_1;

    const double dist = r.norm();

    constexpr double epsilon = 1e-24;

    // Calculate a normalized vector, where a random direction is selected when the points are degenerated
    const Eigen::Vector3d n = (dist < epsilon) ? Eigen::Vector3d::Random().normalized() : (1.0 / dist) * r;

    grad_C[0] = +n(0);
    grad_C[1] = +n(1);
    grad_C[2] = +n(2);
    grad_C[3] = -n(0);
    grad_C[4] = -n(1);
    grad_C[5] = -n(2);
}

elasty::EnvironmentalCollisionConstraint::EnvironmentalCollisionConstraint(const std::shared_ptr<Particle> p_0,
                                                                           const double                    stiffness,
                                                                           const double                    compliance,
                                                                           const double                    delta_time,
                                                                           const Eigen::Vector3d&          n,
                                                                           const double                    d)
    : FixedNumAbstractConstraint(std::vector<std::shared_ptr<Particle>>{p_0}, stiffness, compliance, delta_time),
      m_n(n),
      m_d(d)
{
}

double elasty::EnvironmentalCollisionConstraint::calculateValue()
{
    const Eigen::Vector3d& x = m_particles[0]->p;
    return m_n.transpose() * x - m_d;
}

void elasty::EnvironmentalCollisionConstraint::calculateGrad(double* grad_C)
{
    std::memcpy(grad_C, m_n.data(), sizeof(double) * 3);
}

elasty::FixedPointConstraint::FixedPointConstraint(const std::shared_ptr<Particle> p_0,
                                                   const double                    stiffness,
                                                   const double                    compliance,
                                                   const double                    delta_time,
                                                   const Eigen::Vector3d&          point)
    : FixedNumAbstractConstraint(std::vector<std::shared_ptr<Particle>>{p_0}, stiffness, compliance, delta_time),
      m_point(point)
{
}

double elasty::FixedPointConstraint::calculateValue()
{
    const Eigen::Vector3d& x = m_particles[0]->p;
    return (x - m_point).norm();
}

void elasty::FixedPointConstraint::calculateGrad(double* grad_C)
{
    const Eigen::Vector3d& x    = m_particles[0]->p;
    const Eigen::Vector3d  r    = x - m_point;
    const double           dist = r.norm();

    constexpr double epsilon = 1e-24;

    // Calculate a normalized vector, where a random direction is selected when the points are degenerated
    const Eigen::Vector3d n = (dist < epsilon) ? Eigen::Vector3d::Random().normalized() : (1.0 / dist) * r;

    std::memcpy(grad_C, n.data(), sizeof(double) * 3);
}

elasty::IsometricBendingConstraint::IsometricBendingConstraint(const std::shared_ptr<Particle> p_0,
                                                               const std::shared_ptr<Particle> p_1,
                                                               const std::shared_ptr<Particle> p_2,
                                                               const std::shared_ptr<Particle> p_3,
                                                               const double                    stiffness,
                                                               const double                    compliance,
                                                               const double                    delta_time)
    : FixedNumAbstractConstraint(std::vector<std::shared_ptr<Particle>>{p_0, p_1, p_2, p_3},
                                 stiffness,
                                 compliance,
                                 delta_time)
{
    const Eigen::Vector3d& x_0 = p_0->x;
    const Eigen::Vector3d& x_1 = p_1->x;
    const Eigen::Vector3d& x_2 = p_2->x;
    const Eigen::Vector3d& x_3 = p_3->x;

    const Eigen::Vector3d e0 = x_1 - x_0;
    const Eigen::Vector3d e1 = x_2 - x_1;
    const Eigen::Vector3d e2 = x_0 - x_2;
    const Eigen::Vector3d e3 = x_3 - x_0;
    const Eigen::Vector3d e4 = x_1 - x_3;

    const double cot_01 = calculateCotTheta(e0, -e1);
    const double cot_02 = calculateCotTheta(e0, -e2);
    const double cot_03 = calculateCotTheta(e0, e3);
    const double cot_04 = calculateCotTheta(e0, e4);

    const Eigen::Vector4d K = Eigen::Vector4d(cot_01 + cot_04, cot_02 + cot_03, -cot_01 - cot_02, -cot_03 - cot_04);

    const double A_0 = 0.5 * e0.cross(e1).norm();
    const double A_1 = 0.5 * e0.cross(e3).norm();

    m_Q = (3.0 / (A_0 + A_1)) * K * K.transpose();
}

double elasty::IsometricBendingConstraint::calculateValue()
{
    double sum = 0.0;
    for (unsigned int i = 0; i < 4; ++i)
    {
        for (unsigned int j = 0; j < 4; ++j)
        {
            sum += m_Q(i, j) * double(m_particles[i]->p.transpose() * m_particles[j]->p);
        }
    }
    return 0.5 * sum;
}

void elasty::IsometricBendingConstraint::calculateGrad(double* grad_C)
{
    for (unsigned int i = 0; i < 4; ++i)
    {
        Eigen::Vector3d sum = Eigen::Vector3d::Zero();
        for (unsigned int j = 0; j < 4; ++j)
        {
            sum += m_Q(i, j) * m_particles[j]->p;
        }
        std::memcpy(grad_C + (3 * i), sum.data(), sizeof(double) * 3);
    }
}

elasty::ShapeMatchingConstraint::ShapeMatchingConstraint(const std::vector<std::shared_ptr<Particle>>& particles,
                                                         const double                                  stiffness,
                                                         const double                                  compliance,
                                                         const double                                  delta_time)
    : VariableNumConstraint(particles, stiffness, compliance, delta_time)
{
    // Calculate the initial center of mass and the total mass
    Eigen::Vector3d x_0_cm = Eigen::Vector3d::Zero();
    m_total_mass           = 0.0;
    for (int i = 0; i < m_particles.size(); ++i)
    {
        m_total_mass += m_particles[i]->m;
        x_0_cm += m_particles[i]->m * m_particles[i]->p;
    }
    x_0_cm /= m_total_mass;

    // Calculate q
    m_q.resize(m_particles.size());
    for (int i = 0; i < m_particles.size(); ++i)
    {
        m_q[i] = m_particles[i]->x - x_0_cm;
    }
}

double elasty::ShapeMatchingConstraint::calculateValue()
{
    throw std::runtime_error("ShapeMatchingConstraint does not directly provide its cost value or the gradient.");
}

void elasty::ShapeMatchingConstraint::calculateGrad(double* grad_C)
{
    throw std::runtime_error("ShapeMatchingConstraint does not directly provide its cost value or the gradient.");
}

void elasty::ShapeMatchingConstraint::projectParticles(const AlgorithmType type)
{
    // Calculate the current center of mass
    Eigen::Vector3d x_cm = Eigen::Vector3d::Zero();
    for (int i = 0; i < m_particles.size(); ++i)
    {
        x_cm += m_particles[i]->m * m_particles[i]->p;
    }
    x_cm /= m_total_mass;

    // Calculate A_pq
    Eigen::Matrix3d A_pq = Eigen::Matrix3d::Zero();
    for (int i = 0; i < m_particles.size(); ++i)
    {
        A_pq += m_particles[i]->m * (m_particles[i]->p - x_cm) * m_q[i].transpose();
    }

    // Calculate the rotation matrix
    const auto            ATA          = A_pq.transpose() * A_pq;
    const auto            eigen_solver = Eigen::SelfAdjointEigenSolver<Eigen::Matrix3d>(ATA);
    const auto            S_inv        = eigen_solver.operatorInverseSqrt();
    const Eigen::Matrix3d R            = A_pq * S_inv;

    assert(R.determinant() > 0);

    // Update the particle positions
    for (int i = 0; i < m_particles.size(); ++i)
    {
        // Calculate the goal position
        const Eigen::Vector3d g = R * m_q[i] + x_cm;

        // Move the particle
        m_particles[i]->p = m_stiffness * g + (1.0 - m_stiffness) * m_particles[i]->p;
    }
}


#include "rbfcore.h"
#include "utility.h"
#include "Solver.h"
#include <armadillo>
#include <fstream>
#include <limits>
#include <iomanip>
#include <ctime>
#include <chrono>
#include<algorithm>

double sigma = 2.0;
double inv_sigma_squarex2 = 1/(2 * pow(sigma, 2));
double Gaussian_Kernel(const double x_square){

    return exp(-x_square*inv_sigma_squarex2);

}

double Gaussian_Kernel_2p(const double *p1, const double *p2){

    return Gaussian_Kernel(MyUtility::vecSquareDist(p1,p2));

}

double Gaussian_PKernel_Dirichlet_2p(const double *p1, const double *p2){

    double d2 = MyUtility::vecSquareDist(p1,p2);
    return (6*sigma*sigma-d2)*sqrt(Gaussian_Kernel(d2));

}

double Gaussian_PKernel_Bending_2p(const double *p1, const double *p2){

    double d2 = MyUtility::vecSquareDist(p1,p2);
    double d4 = d2*d2;
    double sigma2 = sigma * sigma;
    double sigma4 = sigma2 * sigma2;
    return (60*sigma4-20*sigma2*d2+d4)*sqrt(Gaussian_Kernel(d2));

}

double XCube_Kernel(const double x){

    return pow(x,3);
}

double XCube_Kernel_2p(const double *p1, const double *p2){

    return XCube_Kernel(MyUtility::_VerticesDistance(p1,p2));

}

void XCube_Gradient_Kernel_2p(const double *p1, const double *p2, double *G){

    double len_dist  = MyUtility::_VerticesDistance(p1,p2);
    for(int i=0;i<3;++i)G[i] = 3*len_dist*(p1[i]-p2[i]);
    return;

}

double XCube_GradientDot_Kernel_2p(const double *p1, const double *p2, const double *p3){

    double G[3];
    XCube_Gradient_Kernel_2p(p1,p2,G);
    return MyUtility::dot(p3,G);

}

void XCube_Hessian_Kernel_2p(const double *p1, const double *p2, double *H){

    double diff[3];
    for(int i=0;i<3;++i)diff[i] = p1[i] - p2[i];
    double len_dist  = sqrt(MyUtility::len(diff));

    if(len_dist<1e-8){
        for(int i=0;i<9;++i)H[i] = 0;
    }else{
        for(int i=0;i<3;++i)for(int j=0;j<3;++j)
            if(i==j)H[i*3+j] = 3 * pow(diff[i],2) / len_dist + 3 * len_dist;
            else H[i*3+j] = 3 * diff[i] * diff[j] / len_dist;
    }

    return;

}

void XCube_HessianDot_Kernel_2p(const double *p1, const double *p2, const double *p3, vector<double>&dotout){

    double H[9];
    XCube_Gradient_Kernel_2p(p1,p2,H);
    dotout.resize(3);
    for(int i=0;i<3;++i){
        dotout[i] = 0;
        for(int j=0;j<3;++j){
            dotout[i] += H[i*3+j] * p3[j];
        }
    }

}

RBF_Core::RBF_Core(){

    Kernal_Function = Gaussian_Kernel;
    Kernal_Function_2p = Gaussian_Kernel_2p;
    P_Function_2p = Gaussian_PKernel_Dirichlet_2p;

    isHermite = false;

    mp_RBF_INITMETHOD.insert(make_pair(GT_NORMAL,"GT_NORMAL"));
    mp_RBF_INITMETHOD.insert(make_pair(GlobalEigen,"GlobalEigen"));
    mp_RBF_INITMETHOD.insert(make_pair(GlobalEigenWithMST,"GlobalEigenWithMST"));
    mp_RBF_INITMETHOD.insert(make_pair(GlobalEigenWithGT,"GlobalEigenWithGT"));
    mp_RBF_INITMETHOD.insert(make_pair(LocalEigen,"LocalEigen"));
    mp_RBF_INITMETHOD.insert(make_pair(IterativeEigen,"IterativeEigen"));
    mp_RBF_INITMETHOD.insert(make_pair(ClusterEigen,"ClusterEigen"));

    mp_RBF_METHOD.insert(make_pair(Variational,"Variational"));
    mp_RBF_METHOD.insert(make_pair(Variational_P,"Variation_P"));
    mp_RBF_METHOD.insert(make_pair(LS,"LS"));
    mp_RBF_METHOD.insert(make_pair(LSinterp,"LSinterp"));
    mp_RBF_METHOD.insert(make_pair(Interp,"Interp"));
    mp_RBF_METHOD.insert(make_pair(RayleighQuotients,"Rayleigh"));
    mp_RBF_METHOD.insert(make_pair(RayleighQuotients_P,"Rayleigh_P"));
    mp_RBF_METHOD.insert(make_pair(RayleighQuotients_I,"Rayleigh_I"));
    mp_RBF_METHOD.insert(make_pair(Hermite,"Hermite"));
    mp_RBF_METHOD.insert(make_pair(Hermite_UnitNorm,"UnitNorm"));
    mp_RBF_METHOD.insert(make_pair(Hermite_UnitNormal,"UnitNormal"));
    mp_RBF_METHOD.insert(make_pair(Hermite_Tangent_UnitNorm,"T_UnitNorm"));
    mp_RBF_METHOD.insert(make_pair(Hermite_Tangent_UnitNormal,"T_UnitNormal"));

    mp_RBF_Kernal.insert(make_pair(XCube,"TriH"));
    mp_RBF_Kernal.insert(make_pair(ThinSpline,"ThinSpline"));
    mp_RBF_Kernal.insert(make_pair(XLinear,"XLinear"));
    mp_RBF_Kernal.insert(make_pair(Gaussian,"Gaussian"));

}
RBF_Core::RBF_Core(RBF_Kernal kernal){
    isHermite = false;
    Init(kernal);
}

void RBF_Core::Init(RBF_Kernal kernal){

    this->kernal = kernal;
    switch(kernal){
    case Gaussian:
        Kernal_Function = Gaussian_Kernel;
        Kernal_Function_2p = Gaussian_Kernel_2p;
        P_Function_2p = Gaussian_PKernel_Dirichlet_2p;
        break;

    case XCube:
        Kernal_Function = XCube_Kernel;
        Kernal_Function_2p = XCube_Kernel_2p;
        Kernal_Gradient_Function_2p = XCube_Gradient_Kernel_2p;
        Kernal_Hessian_Function_2p = XCube_Hessian_Kernel_2p;
        break;

    default:
        break;

    }

}

void RBF_Core::SetSigma(double x){
    sigma = x;
    inv_sigma_squarex2 = 1/(2 * pow(sigma, 2));
}

double RBF_Core::Dist_Function(const double x, const double y, const double z){

	return -1;

}

inline double RBF_Core::Dist_Function(const double *p){

    n_evacalls++;
    double *p_pts = pts.data();
    static arma::vec kern(npt), kb;
    if(isHermite){
        kern.set_size(npt*4);
        double G[3];
        for(int i=0;i<npt;++i)kern(i) = Kernal_Function_2p(p_pts+i*3, p);
        for(int i=0;i<npt;++i){
            Kernal_Gradient_Function_2p(p,p_pts+i*3,G);
            //for(int j=0;j<3;++j)kern(npt+i*3+j) = -G[j];
            for(int j=0;j<3;++j)kern(npt+i+j*npt) = G[j];
        }
    }else{
        kern.set_size(npt);
        for(int i=0;i<npt;++i)kern(i) = Kernal_Function_2p(p_pts+i*3, p);
    }

    double loc_part = dot(kern,a);

    if(polyDeg==1){
        kb.set_size(4);
        for(int i=0;i<3;++i)kb(i+1) = p[i];
        kb(0) = 1;
    }else if(polyDeg==2){
        vector<double>buf(4,1);
        int ind = 0;
        kb.set_size(10);
        for(int j=0;j<3;++j)buf[j+1] = p[j];
        for(int j=0;j<4;++j)for(int k=j;k<4;++k)kb(ind++) = buf[j] * buf[k];
    }
    double poly_part = dot(kb,b);

    if(0){
        cout<<"dist: "<<p[0]<<' '<<p[1]<<' '<<p[2]<<' '<<p_pts[3]<<' '<<p_pts[4]<<' '<<p_pts[5]<<' '<<
              Kernal_Function_2p(p,p_pts+3)<<endl;
        for(int i=0;i<npt;++i)cout<<kern(i)<<' ';
        for(int i=0;i<bsize;++i)cout<<kb(i)<<' ';
        cout<<endl;
    }

    double re = loc_part + poly_part;
    return re;

}
static RBF_Core * s_hrbf;
double RBF_Core::Dist_Function(const R3Pt &in_pt){
    return s_hrbf->Dist_Function(&(in_pt[0]));
}

//FT RBF_Core::Dist_Function(const Point_3 in_pt){

//    return s_hrbf->Dist_Function(&(in_pt.x()));
//}

void RBF_Core::SetThis(){

    s_hrbf = this;
}

void RBF_Core::Write_Surface(string fname){

    //writeObjFile(fname,finalMesh_v,finalMesh_fv);

    writePLYFile_VF(fname,finalMesh_v,finalMesh_fv);
}

/**********************************************************/

void RBF_Core::Record(RBF_METHOD method, RBF_Kernal kernal, Solution_Struct &rsol, double time){

    npoints.push_back(npt);

    record_initmethod.push_back(mp_RBF_INITMETHOD[curInitMethod]);
    record_method.push_back(mp_RBF_METHOD[method]);
    record_kernal.push_back(mp_RBF_Kernal[kernal]);
    record_initenergy.push_back(rsol.init_energy);
    record_energy.push_back(rsol.energy);
    record_time.push_back(time);

    setup_timev.push_back(setup_time);
    init_timev.push_back(init_time);
    solve_timev.push_back(solve_time);
    callfunc_timev.push_back(callfunc_time);
    invM_timev.push_back(invM_time);
    setK_timev.push_back(setK_time);

}

void RBF_Core::Record(){

    //cout<<"record"<<endl;
    npoints.push_back(npt);

    record_initmethod.push_back(mp_RBF_INITMETHOD[curInitMethod]);
    record_method.push_back(mp_RBF_METHOD[curMethod]);
    //record_kernal.push_back(mp_RBF_Kernal[kernal]);
    record_initenergy.push_back(sol.init_energy);
    record_energy.push_back(sol.energy);
    //cout<<"record"<<endl;

//    setup_timev.push_back(setup_time);
//    init_timev.push_back(init_time);
//    solve_timev.push_back(solve_time);
//    callfunc_timev.push_back(callfunc_time);
//    invM_timev.push_back(invM_time);
//    setK_timev.push_back(setK_time);
   // cout<<"record end"<<endl;
}

void RBF_Core::AddPartition(string pname){

    record_partition.push_back(record_method.size());
    record_partition_name.push_back(pname);
}

void RBF_Core::Print_Record(){

    cout<<"Method\t\t Kernal\t\t Energy\t\t Time"<<endl;
    cout<<std::setprecision(8)<<endl;
    if(record_partition.size()==0){
        for(int i=0;i<record_method.size();++i){
            cout<<record_method[i]<<"\t\t"<<record_kernal[i]<<"\t\t"<<record_energy[i]<<"\t\t"<<record_time[i]<<endl;
        }
        for(int i=0;i<setup_timev.size();++i){
            cout<<setup_timev[i]<<"\t\t"<<init_timev[i]<<"\t\t"<<solve_timev[i]<<"\t\t"<<callfunc_timev[i]<<"\t\t"<<invM_timev[i]<<"\t\t"<<setK_timev[i]<<endl;
        }
    }else{
        for(int j=0;j<record_partition.size();++j){
            cout<<record_partition_name[j]<<endl;
            for(int i=j==0?0:record_partition[j-1];i<record_partition[j];++i){
                cout<<record_method[i]<<"\t\t"<<record_kernal[i]<<"\t\t"<<record_energy[i]<<"\t\t"<<record_time[i]<<endl;
            }
            for(int i=j==0?0:record_partition[j-1];i<record_partition[j];++i){
                cout<<setup_timev[i]<<"\t\t"<<init_timev[i]<<"\t\t"<<solve_timev[i]<<"\t\t"<<callfunc_timev[i]<<endl;
            }
        }
    }

}

void RBF_Core::Print_TimerRecord(string fname){

    ofstream fout(fname);
    fout<<setprecision(5);
    if(!fout.fail()){
        for(int i=0;i<setup_timev.size();++i){
            fout<<npoints[i]<<'\t'<<setup_timev[i]<<"\t"<<init_timev[i]<<"\t"<<solve_timev[i]<<"\t"<<callfunc_timev[i]<<"\t"<<invM_timev[i]<<"\t"<<setK_timev[i]<<endl;
        }
    }
    fout.close();

}

void RBF_Core::Print_TimerRecord_Single(string fname){

    ofstream fout(fname);
    fout<<setprecision(5);
    if(!fout.fail()){
        fout<<"number of points: "<<npt<<endl
           <<"setup_time (Compute H): "<<setup_time<<" s"<<endl
          <<"init_time (Optimize g/Eigen): "<<init_time<<" s"<<endl
         <<"solve_time (Optimize g/LBFGS): "<<solve_time<<" s"<<endl
        <<"surfacing_time: "<<surf_time<<" s"<<endl;
    }
    fout.close();
}

void RBF_Core::Clear_TimerRecord(){
    npoints.clear();
    setup_timev.clear();
    init_timev.clear();
    solve_timev.clear();
    callfunc_timev.clear();
    invM_timev.clear();
    setK_timev.clear();

}


#include <NTL/FFT.h>

#include <NTL/new.h>

NTL_START_IMPL

FFTPrimeInfo *FFTTables = 0;
Vec<FFTPrimeInfo> FFTTables_store;

long *FFTPrime = 0;
Vec<long> FFTPrime_store;

double *FFTPrimeInv = 0;
Vec<double> FFTPrimeInv_store;

// We separate the pointer from the Vec, to ensure
// portability: global initialization of C++ objects
// can be problematic.

long NumFFTPrimes = 0;

static
long IsFFTPrime(long n, long& w)
{
   long  m, x, y, z;
   long j, k;

   if (n % 3 == 0) return 0;

   if (n % 5 == 0) return 0;

   if (n % 7 == 0) return 0;

   m = n - 1;
   k = 0;
   while ((m & 1) == 0) {
      m = m >> 1;
      k++;
   }

   for (;;) {
      x = RandomBnd(n);

      if (x == 0) continue;
      z = PowerMod(x, m, n);
      if (z == 1) continue;

      x = z;
      j = 0;
      do {
         y = z;
         z = MulMod(y, y, n);
         j++;
      } while (j != k && z != 1);

      if (z != 1 || y !=  n-1) return 0;

      if (j == k)
         break;
   }

   /* x^{2^k} = 1 mod n, x^{2^{k-1}} = -1 mod n */

   long TrialBound;

   TrialBound = m >> k;
   if (TrialBound > 0) {
      if (!ProbPrime(n, 5)) return 0;

      /* we have to do trial division by special numbers */

      TrialBound = SqrRoot(TrialBound);

      long a, b;

      for (a = 1; a <= TrialBound; a++) {
         b = (a << k) + 1;
         if (n % b == 0) return 0;
      }
   }

   /* n is an FFT prime */

   for (j = NTL_FFTMaxRoot; j < k; j++)
      x = MulMod(x, x, n);

   w = x;
   return 1;
}

static
void NextFFTPrime(long& q, long& w)
{
   static long m = NTL_FFTMaxRootBnd + 1;
   static long k = 0;

   long t, cand;

   for (;;) {
      if (k == 0) {
         m--;
         if (m < 5) Error("ran out of FFT primes");
         k = 1L << (NTL_SP_NBITS-m-2);
      }

      k--;

      cand = (1L << (NTL_SP_NBITS-1)) + (k << (m+1)) + (1L << m) + 1;

      if (!IsFFTPrime(cand, t)) continue;
      q = cand;
      w = t;
      return;
   }
}

long CalcMaxRoot(long p)
{
   p = p-1;
   long k = 0;
   while ((p & 1) == 0) {
      p = p >> 1;
      k++;
   }

   if (k > NTL_FFTMaxRoot)
      return NTL_FFTMaxRoot;
   else
      return k;
}

void UseFFTPrime(long index)
{
   long numprimes = FFTTables_store.length();

   if (index < 0 || index > numprimes)
      Error("invalid FFT prime index");

   if (index < numprimes) return;

   // index == numprimes

   long q, w;

   NextFFTPrime(q, w);

   double qinv = 1/((double) q);

   long mr = CalcMaxRoot(q);

   FFTTables_store.SetLength(numprimes+1);
   FFTTables = FFTTables_store.elts();

   FFTPrimeInfo& info = FFTTables[numprimes];

   info.q = q;
   info.qinv = qinv;

   info.RootTable.SetLength(mr+1);
   info.RootInvTable.SetLength(mr+1);
   info.TwoInvTable.SetLength(mr+1);
   info.TwoInvPreconTable.SetLength(mr+1);

   long *rt = &info.RootTable[0];
   long *rit = &info.RootInvTable[0];
   long *tit = &info.TwoInvTable[0];
   mulmod_precon_t *tipt = &info.TwoInvPreconTable[0];

   long j;
   long t;

   rt[mr] = w;
   for (j = mr-1; j >= 0; j--)
      rt[j] = MulMod(rt[j+1], rt[j+1], q);

   rit[mr] = InvMod(w, q);
   for (j = mr-1; j >= 0; j--)
      rit[j] = MulMod(rit[j+1], rit[j+1], q);

   t = InvMod(2, q);
   tit[0] = 1;
   for (j = 1; j <= mr; j++)
      tit[j] = MulMod(tit[j-1], t, q);

   for (j = 0; j <= mr; j++)
      tipt[j] = PrepMulModPrecon(tit[j], q, qinv);

   // initialize data structures for the legacy inteface

   NumFFTPrimes = FFTTables_store.length();

   FFTPrime_store.SetLength(NumFFTPrimes);
   FFTPrime = FFTPrime_store.elts();
   FFTPrime[NumFFTPrimes-1] = q;

   FFTPrimeInv_store.SetLength(NumFFTPrimes);
   FFTPrimeInv = FFTPrimeInv_store.elts();
   FFTPrimeInv[NumFFTPrimes-1] = qinv;
}

/*
 * Our FFT is based on the routine in Cormen, Leiserson, Rivest, and Stein.
 * For very large inputs, it should be relatively cache friendly.
 * The inner loop has been unrolled and pipelined, to exploit any
 * low-level parallelism in the machine.
 *
 * This version now allows input to alias output.
 */

static
long RevInc(long a, long k)
{
   long j, m;

   j = k;
   m = 1L << (k-1);

   while (j && (m & a)) {
      a ^= m;
      m >>= 1;
      j--;
   }
   if (j) a ^= m;
   return a;
}

static Vec<long> brc_mem[NTL_FFTMaxRoot+1];

static
void BitReverseCopy(long *A, const long *a, long k)
{
   long n = 1L << k;
   long* rev;
   long i, j;

   rev = brc_mem[k].elts();
   if (!rev) {
      brc_mem[k].SetLength(n);
      rev = brc_mem[k].elts();
      for (i = 0, j = 0; i < n; i++, j = RevInc(j, k))
         rev[i] = j;
   }

   for (i = 0; i < n; i++)
      A[rev[i]] = a[i];
}

static
void BitReverseCopy(unsigned long *A, const long *a, long k)
{
   long n = 1L << k;
   long* rev;
   long i, j;

   rev = brc_mem[k].elts();
   if (!rev) {
      brc_mem[k].SetLength(n);
      rev = brc_mem[k].elts();
      for (i = 0, j = 0; i < n; i++, j = RevInc(j, k))
         rev[i] = j;
   }

   for (i = 0; i < n; i++)
      A[rev[i]] = a[i];
}

void FFT(long* A, const long* a, long k, long q, const long* root)
// performs a 2^k-point convolution modulo q

{
   if (k <= 1) {
      if (k == 0) {
	 A[0] = a[0];
	 return;
      }
      if (k == 1) {
	 long a0 = AddMod(a[0], a[1], q);
	 long a1 = SubMod(a[0], a[1], q);
         A[0] = a0;
         A[1] = a1;
	 return;
      }
   }

   // assume k > 1

   static Vec<long> wtab_store;
   static Vec<mulmod_precon_t> wqinvtab_store;
   static Vec<long> AA_store;

   wtab_store.SetLength(1L << (k-2));
   wqinvtab_store.SetLength(1L << (k-2));
   AA_store.SetLength(1L << k);

   long * NTL_RESTRICT wtab = wtab_store.elts();
   mulmod_precon_t * NTL_RESTRICT wqinvtab = wqinvtab_store.elts();
   long *AA = AA_store.elts();

   double qinv = 1/((double) q);

   wtab[0] = 1;
   wqinvtab[0] = PrepMulModPrecon(1, q, qinv);

   BitReverseCopy(AA, a, k);

   long n = 1L << k;

   long s, m, m_half, m_fourth, i, j, t, u, t1, u1, tt, tt1;

   long w;
   mulmod_precon_t wqinv;

   // s = 1

   for (i = 0; i < n; i += 2) {
      t = AA[i + 1];
      u = AA[i];
      AA[i] = AddMod(u, t, q);
      AA[i+1] = SubMod(u, t, q);
   }

   for (s = 2; s < k; s++) {
      m = 1L << s;
      m_half = 1L << (s-1);
      m_fourth = 1L << (s-2);

      w = root[s];
      wqinv = PrepMulModPrecon(w, q, qinv);

      // prepare wtab...

      if (s == 2) {
         wtab[1] = MulModPrecon(wtab[0], w, q, wqinv);
         wqinvtab[1] = PrepMulModPrecon(wtab[1], q, qinv);
      }
      else {
         // some software pipelining

         i = m_half-1; j = m_fourth-1;
         wtab[i-1] = wtab[j];
         wqinvtab[i-1] = wqinvtab[j];
         wtab[i] = MulModPrecon(wtab[i-1], w, q, wqinv);

         i -= 2; j --;

         for (; i >= 0; i -= 2, j --) {
            long wp2 = wtab[i+2];
            long wm1 = wtab[j];
            wqinvtab[i+2] = PrepMulModPrecon(wp2, q, qinv);
            wtab[i-1] = wm1;
            wqinvtab[i-1] = wqinvtab[j];
            wtab[i] = MulModPrecon(wm1, w, q, wqinv);
         }

         wqinvtab[1] = PrepMulModPrecon(wtab[1], q, qinv);
      }

      for (i = 0; i < n; i+= m) {

         long * NTL_RESTRICT AA0 = &AA[i];
         long * NTL_RESTRICT AA1 = &AA[i + m_half];

         t = AA1[0];
         u = AA0[0];
         t1 = MulModPrecon(AA1[1], w, q, wqinv);
         u1 = AA0[1];

         for (j = 0; j < m_half-2; j += 2) {
            long a02 = AA0[j+2];
            long a03 = AA0[j+3];
            long a12 = AA1[j+2];
            long a13 = AA1[j+3];
            long w2 = wtab[j+2];
            long w3 = wtab[j+3];
            mulmod_precon_t wqi2 = wqinvtab[j+2];
            mulmod_precon_t wqi3 = wqinvtab[j+3];

            tt = MulModPrecon(a12, w2, q, wqi2);
            long b00 = AddMod(u, t, q);
            long b10 = SubMod(u, t, q);
            t = tt;
            u = a02;

            tt1 = MulModPrecon(a13, w3, q, wqi3);
            long b01 = AddMod(u1, t1, q);
            long b11 = SubMod(u1, t1, q);
            t1 = tt1;
            u1 = a03;

            AA0[j] = b00;
            AA1[j] = b10;
            AA0[j+1] = b01;
            AA1[j+1] = b11;
         }

         AA0[j] = AddMod(u, t, q);
         AA1[j] = SubMod(u, t, q);
         AA0[j + 1] = AddMod(u1, t1, q);
         AA1[j + 1] = SubMod(u1, t1, q);
      }
   }

   // s == k...special case

   m = 1L << s;
   m_half = 1L << (s-1);
   m_fourth = 1L << (s-2);

   w = root[s];
   wqinv = PrepMulModPrecon(w, q, qinv);

   // j = 0, 1

   t = AA[m_half];
   u = AA[0];
   t1 = MulModPrecon(AA[1+ m_half], w, q, wqinv);
   u1 = AA[1];

   A[0] = AddMod(u, t, q);
   A[m_half] = SubMod(u, t, q);
   A[1] = AddMod(u1, t1, q);
   A[1 + m_half] = SubMod(u1, t1, q);

   for (j = 2; j < m_half; j += 2) {
      t = MulModPrecon(AA[j + m_half], wtab[j >> 1], q, wqinvtab[j >> 1]);
      u = AA[j];
      t1 = MulModPrecon(AA[j + 1+ m_half], wtab[j >> 1], q,
                        wqinvtab[j >> 1]);
      t1 = MulModPrecon(t1, w, q, wqinv);
      u1 = AA[j + 1];

      A[j] = AddMod(u, t, q);
      A[j + m_half] = SubMod(u, t, q);
      A[j + 1] = AddMod(u1, t1, q);
      A[j + 1 + m_half] = SubMod(u1, t1, q);

   }
}

#if (!defined(NTL_FFT_LAZYMUL) || defined(NTL_SINGLE_MUL) || \
     (!defined(NTL_SPMM_ULL) && !defined(NTL_SPMM_ASM)))

// FFT with precomputed tables

#define NTL_PIPELINE (1)

static
void PrecompFFTMultipliers(long k, long q, const long *root, FFTMultipliers& tab)
{
   if (k < 1) Error("PrecompFFTMultipliers: bad input");

   if (k <= tab.MaxK) return;

   tab.wtab_precomp.SetLength(k+1);
   tab.wqinvtab_precomp.SetLength(k+1);

   double qinv = 1/((double) q);

   if (tab.MaxK == -1) {
      tab.wtab_precomp[1].SetLength(1);
      tab.wqinvtab_precomp[1].SetLength(1);
      tab.wtab_precomp[1][0] = 1;
      tab.wqinvtab_precomp[1][0] = PrepMulModPrecon(1, q, qinv);
      tab.MaxK = 1;
   }

   for (long s = tab.MaxK+1; s <= k; s++) {
      tab.wtab_precomp[s].SetLength(1L << (s-1));
      tab.wqinvtab_precomp[s].SetLength(1L << (s-1));

      long m = 1L << s;
      long m_half = 1L << (s-1);
      long m_fourth = 1L << (s-2);

      long *wtab_last = tab.wtab_precomp[s-1].elts();
      mulmod_precon_t *wqinvtab_last = tab.wqinvtab_precomp[s-1].elts();

      long *wtab = tab.wtab_precomp[s].elts();
      mulmod_precon_t *wqinvtab = tab.wqinvtab_precomp[s].elts();

      for (long i = 0; i < m_fourth; i++) {
         wtab[i] = wtab_last[i];
         wqinvtab[i] = wqinvtab_last[i];
      }

      long w = root[s];
      mulmod_precon_t wqinv = PrepMulModPrecon(w, q, qinv);

      // prepare wtab...

      if (s == 2) {
         wtab[1] = MulModPrecon(wtab[0], w, q, wqinv);
         wqinvtab[1] = PrepMulModPrecon(wtab[1], q, qinv);
      }
      else {
         // some software pipelining
         long i, j;

         i = m_half-1; j = m_fourth-1;
         wtab[i-1] = wtab[j];
         wqinvtab[i-1] = wqinvtab[j];
         wtab[i] = MulModPrecon(wtab[i-1], w, q, wqinv);

         i -= 2; j --;

         for (; i >= 0; i -= 2, j --) {
            long wp2 = wtab[i+2];
            long wm1 = wtab[j];
            wqinvtab[i+2] = PrepMulModPrecon(wp2, q, qinv);
            wtab[i-1] = wm1;
            wqinvtab[i-1] = wqinvtab[j];
            wtab[i] = MulModPrecon(wm1, w, q, wqinv);
         }

         wqinvtab[1] = PrepMulModPrecon(wtab[1], q, qinv);
      }
   }

   tab.MaxK = k;
}

void FFT(long* A, const long* a, long k, long q, const long* root, FFTMultipliers& tab)
// performs a 2^k-point convolution modulo q

{
   if (k <= 1) {
      if (k == 0) {
	 A[0] = a[0];
	 return;
      }
      if (k == 1) {
	 long a0 = AddMod(a[0], a[1], q);
	 long a1 = SubMod(a[0], a[1], q);
         A[0] = a0;
         A[1] = a1;
	 return;
      }
   }

   // assume k > 1

   if (k > tab.MaxK) PrecompFFTMultipliers(k, q, root, tab);

   static Vec<long> AA_store;
   AA_store.SetLength(1L << k);
   long *AA = AA_store.elts();

   BitReverseCopy(AA, a, k);

   long n = 1L << k;

   long s, m, m_half, m_fourth, i, j, t, u, t1, u1, tt, tt1;

   // s = 1

   for (i = 0; i < n; i += 2) {
      t = AA[i + 1];
      u = AA[i];
      AA[i] = AddMod(u, t, q);
      AA[i+1] = SubMod(u, t, q);
   }

   for (s = 2; s < k; s++) {
      m = 1L << s;
      m_half = 1L << (s-1);
      m_fourth = 1L << (s-2);

      const long* wtab = tab.wtab_precomp[s].elts();
      const mulmod_precon_t *wqinvtab = tab.wqinvtab_precomp[s].elts();

      for (i = 0; i < n; i+= m) {

         long *AA0 = &AA[i];
         long *AA1 = &AA[i + m_half];

#if (NTL_PIPELINE)

// pipelining: seems to be faster

         t = AA1[0];
         u = AA0[0];
         t1 = MulModPrecon(AA1[1], wtab[1], q, wqinvtab[1]);
         u1 = AA0[1];

         for (j = 0; j < m_half-2; j += 2) {
            long a02 = AA0[j+2];
            long a03 = AA0[j+3];
            long a12 = AA1[j+2];
            long a13 = AA1[j+3];
            long w2 = wtab[j+2];
            long w3 = wtab[j+3];
            mulmod_precon_t wqi2 = wqinvtab[j+2];
            mulmod_precon_t wqi3 = wqinvtab[j+3];

            tt = MulModPrecon(a12, w2, q, wqi2);
            long b00 = AddMod(u, t, q);
            long b10 = SubMod(u, t, q);

            tt1 = MulModPrecon(a13, w3, q, wqi3);
            long b01 = AddMod(u1, t1, q);
            long b11 = SubMod(u1, t1, q);

            AA0[j] = b00;
            AA1[j] = b10;
            AA0[j+1] = b01;
            AA1[j+1] = b11;

            t = tt;
            u = a02;
            t1 = tt1;
            u1 = a03;
         }

         AA0[j] = AddMod(u, t, q);
         AA1[j] = SubMod(u, t, q);
         AA0[j + 1] = AddMod(u1, t1, q);
         AA1[j + 1] = SubMod(u1, t1, q);
      }
#else
         for (j = 0; j < m_half; j += 2) {
            const long a00 = AA0[j];
            const long a01 = AA0[j+1];
            const long a10 = AA1[j];
            const long a11 = AA1[j+1];

            const long w0 = wtab[j];
            const long w1 = wtab[j+1];
            const mulmod_precon_t wqi0 = wqinvtab[j];
            const mulmod_precon_t wqi1 = wqinvtab[j+1];

            const long tt = MulModPrecon(a10, w0, q, wqi0);
            const long uu = a00;
            const long b00 = AddMod(uu, tt, q);
            const long b10 = SubMod(uu, tt, q);

            const long tt1 = MulModPrecon(a11, w1, q, wqi1);
            const long uu1 = a01;
            const long b01 = AddMod(uu1, tt1, q);
            const long b11 = SubMod(uu1, tt1, q);

            AA0[j] = b00;
            AA0[j+1] = b01;
            AA1[j] = b10;
            AA1[j+1] = b11;
         }
      }
#endif
   }

   // s == k, special case
   {
      m = 1L << s;
      m_half = 1L << (s-1);
      m_fourth = 1L << (s-2);

      const long* wtab = tab.wtab_precomp[s].elts();
      const mulmod_precon_t *wqinvtab = tab.wqinvtab_precomp[s].elts();

      for (i = 0; i < n; i+= m) {

         long *AA0 = &AA[i];
         long *AA1 = &AA[i + m_half];
         long *A0 = &A[i];
         long *A1 = &A[i + m_half];

#if (NTL_PIPELINE)

// pipelining: seems to be faster

         t = AA1[0];
         u = AA0[0];
         t1 = MulModPrecon(AA1[1], wtab[1], q, wqinvtab[1]);
         u1 = AA0[1];

         for (j = 0; j < m_half-2; j += 2) {
            long a02 = AA0[j+2];
            long a03 = AA0[j+3];
            long a12 = AA1[j+2];
            long a13 = AA1[j+3];
            long w2 = wtab[j+2];
            long w3 = wtab[j+3];
            mulmod_precon_t wqi2 = wqinvtab[j+2];
            mulmod_precon_t wqi3 = wqinvtab[j+3];

            tt = MulModPrecon(a12, w2, q, wqi2);
            long b00 = AddMod(u, t, q);
            long b10 = SubMod(u, t, q);

            tt1 = MulModPrecon(a13, w3, q, wqi3);
            long b01 = AddMod(u1, t1, q);
            long b11 = SubMod(u1, t1, q);

            A0[j] = b00;
            A1[j] = b10;
            A0[j+1] = b01;
            A1[j+1] = b11;

            t = tt;
            u = a02;
            t1 = tt1;
            u1 = a03;
         }

         A0[j] = AddMod(u, t, q);
         A1[j] = SubMod(u, t, q);
         A0[j + 1] = AddMod(u1, t1, q);
         A1[j + 1] = SubMod(u1, t1, q);
      }
#else
         for (j = 0; j < m_half; j += 2) {
            const long a00 = AA0[j];
            const long a01 = AA0[j+1];
            const long a10 = AA1[j];
            const long a11 = AA1[j+1];

            const long w0 = wtab[j];
            const long w1 = wtab[j+1];
            const mulmod_precon_t wqi0 = wqinvtab[j];
            const mulmod_precon_t wqi1 = wqinvtab[j+1];

            const long tt = MulModPrecon(a10, w0, q, wqi0);
            const long uu = a00;
            const long b00 = AddMod(uu, tt, q);
            const long b10 = SubMod(uu, tt, q);

            const long tt1 = MulModPrecon(a11, w1, q, wqi1);
            const long uu1 = a01;
            const long b01 = AddMod(uu1, tt1, q);
            const long b11 = SubMod(uu1, tt1, q);

            A0[j] = b00;
            A0[j+1] = b01;
            A1[j] = b10;
            A1[j+1] = b11;
         }
      }
#endif
   }

}

#else

// FFT with precomputed tables and David Harvey's lazy multiplication
// strategy.

static inline
unsigned long LazyPrepMulModPrecon(long b, long n, double ninv)
{
   unsigned long q, r;

   q = (long) ( (((double) b) * NTL_SP_FBOUND) * ninv );
   r = (((unsigned long) b) << NTL_SP_NBITS ) - q * ((unsigned long) n);

   if (r >> (NTL_BITS_PER_LONG-1)) {
      q--;
      r += n;
   }
   else if (((long) r) >= n) {
      q++;
      r -=n;
   }

   unsigned long res = q << (NTL_BITS_PER_LONG - NTL_SP_NBITS);
   long qq, rr;

   rr = MulDivRem(qq, (long) r, 4, n, 4*ninv);

   res = res + (qq << (NTL_BITS_PER_LONG - NTL_SP_NBITS-2));

   return res;
}

static inline
unsigned long LazyMulModPrecon(unsigned long a, unsigned long b,
                               unsigned long n, unsigned long bninv)
{
   unsigned long q = MulHiUL(a, bninv);
   unsigned long res = a*b - q*n;
   return res;
}

static inline
unsigned long LazyReduce(unsigned long a, unsigned long q)
{
  unsigned long res;
#if (NTL_ARITH_RIGHT_SHIFT && defined(NTL_AVOID_BRANCHING) && !defined(NTL_CLEAN_INT))
  res = a - q;
  res  += (((long) res) >> (NTL_BITS_PER_LONG-1)) & q;
#elif (defined(NTL_AVOID_BRANCHING))
  res = a - q;
  res  += (-(res >> (NTL_BITS_PER_LONG-1))) & q;
#else
  if (a >= q)
    res = a - q;
  else
    res = a;
#endif

  return res;
}

static
void LazyPrecompFFTMultipliers(long k, long q, const long *root, FFTMultipliers& tab)
{
   if (k < 1) Error("LazyPrecompFFTMultipliers: bad input");

   if (k <= tab.MaxK) return;

   tab.wtab_precomp.SetLength(k+1);
   tab.wqinvtab_precomp.SetLength(k+1);

   double qinv = 1/((double) q);

   if (tab.MaxK == -1) {
      tab.wtab_precomp[1].SetLength(1);
      tab.wqinvtab_precomp[1].SetLength(1);
      tab.wtab_precomp[1][0] = 1;
      tab.wqinvtab_precomp[1][0] = LazyPrepMulModPrecon(1, q, qinv);
      tab.MaxK = 1;
   }

   for (long s = tab.MaxK+1; s <= k; s++) {
      tab.wtab_precomp[s].SetLength(1L << (s-1));
      tab.wqinvtab_precomp[s].SetLength(1L << (s-1));

      long m = 1L << s;
      long m_half = 1L << (s-1);
      long m_fourth = 1L << (s-2);

      long *wtab_last = tab.wtab_precomp[s-1].elts();
      mulmod_precon_t *wqinvtab_last = tab.wqinvtab_precomp[s-1].elts();

      long *wtab = tab.wtab_precomp[s].elts();
      mulmod_precon_t *wqinvtab = tab.wqinvtab_precomp[s].elts();

      for (long i = 0; i < m_fourth; i++) {
         wtab[i] = wtab_last[i];
         wqinvtab[i] = wqinvtab_last[i];
      }

      long w = root[s];
      mulmod_precon_t wqinv = LazyPrepMulModPrecon(w, q, qinv);

      // prepare wtab...

      if (s == 2) {
         wtab[1] = LazyReduce(LazyMulModPrecon(wtab[0], w, q, wqinv), q);
         wqinvtab[1] = LazyPrepMulModPrecon(wtab[1], q, qinv);
      }
      else {
         // some software pipelining
         long i, j;

         i = m_half-1; j = m_fourth-1;
         wtab[i-1] = wtab[j];
         wqinvtab[i-1] = wqinvtab[j];
         wtab[i] = LazyReduce(LazyMulModPrecon(wtab[i-1], w, q, wqinv), q);

         i -= 2; j --;

         for (; i >= 0; i -= 2, j --) {
            long wp2 = wtab[i+2];
            long wm1 = wtab[j];
            wqinvtab[i+2] = LazyPrepMulModPrecon(wp2, q, qinv);
            wtab[i-1] = wm1;
            wqinvtab[i-1] = wqinvtab[j];
            wtab[i] = LazyReduce(LazyMulModPrecon(wm1, w, q, wqinv), q);
         }

         wqinvtab[1] = LazyPrepMulModPrecon(wtab[1], q, qinv);
      }
   }

   tab.MaxK = k;
}

void FFT(long* A, const long* a, long k, long q, const long* root, FFTMultipliers& tab)

// performs a 2^k-point convolution modulo q

{
   if (k <= 1) {
      if (k == 0) {
	 A[0] = a[0];
	 return;
      }
      if (k == 1) {
	 long a0 = AddMod(a[0], a[1], q);
	 long a1 = SubMod(a[0], a[1], q);
         A[0] = a0;
         A[1] = a1;
	 return;
      }
   }

   // assume k > 1

   if (k > tab.MaxK) LazyPrecompFFTMultipliers(k, q, root, tab);

   static Vec<unsigned long> AA_store;
   AA_store.SetLength(1L << k);
   unsigned long *AA = AA_store.elts();

   BitReverseCopy(AA, a, k);

   long n = 1L << k;

   /* we work with redundant representations, in the range [0, 4q) */

   long s, m, m_half, m_fourth, i, j;
   unsigned long t, u, t1, u1;

   long two_q = 2 * q;

   // s = 1

   for (i = 0; i < n; i += 2) {
      t = AA[i + 1];
      u = AA[i];
      AA[i] = u + t;
      AA[i+1] = u - t + q;
   }

   // s = 2

   {
      const long * NTL_RESTRICT wtab = tab.wtab_precomp[2].elts();
      const mulmod_precon_t * NTL_RESTRICT wqinvtab = tab.wqinvtab_precomp[2].elts();

      for (i = 0; i < n; i += 4) {

         unsigned long * NTL_RESTRICT AA0 = &AA[i];
         unsigned long * NTL_RESTRICT AA1 = &AA[i + 2];

         {
            const long w1 = wtab[0];
            const mulmod_precon_t wqi1 = wqinvtab[0];
            const unsigned long a11 = AA1[0];
            const unsigned long a01 = AA0[0];

            const unsigned long tt1 = LazyMulModPrecon(a11, w1, q, wqi1);
            const unsigned long uu1 = LazyReduce(a01, two_q);
            const unsigned long b01 = uu1 + tt1;
            const unsigned long b11 = uu1 - tt1 + two_q;

            AA0[0] = b01;
            AA1[0] = b11;
         }
         {
            const long w1 = wtab[1];
            const mulmod_precon_t wqi1 = wqinvtab[1];
            const unsigned long a11 = AA1[1];
            const unsigned long a01 = AA0[1];

            const unsigned long tt1 = LazyMulModPrecon(a11, w1, q, wqi1);
            const unsigned long uu1 = LazyReduce(a01, two_q);
            const unsigned long b01 = uu1 + tt1;
            const unsigned long b11 = uu1 - tt1 + two_q;

            AA0[1] = b01;
            AA1[1] = b11;
         }
      }
   }

   //  s = 3..k

   for (s = 3; s <= k; s++) {
      m = 1L << s;
      m_half = 1L << (s-1);
      m_fourth = 1L << (s-2);

      const long* NTL_RESTRICT wtab = tab.wtab_precomp[s].elts();
      const mulmod_precon_t * NTL_RESTRICT wqinvtab = tab.wqinvtab_precomp[s].elts();

      for (i = 0; i < n; i += m) {

         unsigned long * NTL_RESTRICT AA0 = &AA[i];
         unsigned long * NTL_RESTRICT AA1 = &AA[i + m_half];

         for (j = 0; j < m_half; j += 4) {
            {
               const long w1 = wtab[j+0];
               const mulmod_precon_t wqi1 = wqinvtab[j+0];
               const unsigned long a11 = AA1[j+0];
               const unsigned long a01 = AA0[j+0];

               const unsigned long tt1 = LazyMulModPrecon(a11, w1, q, wqi1);
               const unsigned long uu1 = LazyReduce(a01, two_q);
               const unsigned long b01 = uu1 + tt1;
               const unsigned long b11 = uu1 - tt1 + two_q;

               AA0[j+0] = b01;
               AA1[j+0] = b11;
            }
            {
               const long w1 = wtab[j+1];
               const mulmod_precon_t wqi1 = wqinvtab[j+1];
               const unsigned long a11 = AA1[j+1];
               const unsigned long a01 = AA0[j+1];

               const unsigned long tt1 = LazyMulModPrecon(a11, w1, q, wqi1);
               const unsigned long uu1 = LazyReduce(a01, two_q);
               const unsigned long b01 = uu1 + tt1;
               const unsigned long b11 = uu1 - tt1 + two_q;

               AA0[j+1] = b01;
               AA1[j+1] = b11;
            }
            {
               const long w1 = wtab[j+2];
               const mulmod_precon_t wqi1 = wqinvtab[j+2];
               const unsigned long a11 = AA1[j+2];
               const unsigned long a01 = AA0[j+2];

               const unsigned long tt1 = LazyMulModPrecon(a11, w1, q, wqi1);
               const unsigned long uu1 = LazyReduce(a01, two_q);
               const unsigned long b01 = uu1 + tt1;
               const unsigned long b11 = uu1 - tt1 + two_q;

               AA0[j+2] = b01;
               AA1[j+2] = b11;
            }
            {
               const long w1 = wtab[j+3];
               const mulmod_precon_t wqi1 = wqinvtab[j+3];
               const unsigned long a11 = AA1[j+3];
               const unsigned long a01 = AA0[j+3];

               const unsigned long tt1 = LazyMulModPrecon(a11, w1, q, wqi1);
               const unsigned long uu1 = LazyReduce(a01, two_q);
               const unsigned long b01 = uu1 + tt1;
               const unsigned long b11 = uu1 - tt1 + two_q;

               AA0[j+3] = b01;
               AA1[j+3] = b11;
            }
         }
      }
   }

   /* need to reduce redundant representations */

   for (i = 0; i < n; i++) {
      unsigned long tmp = LazyReduce(AA[i], two_q);
      A[i] = LazyReduce(tmp, q);
   }
}

#endif

NTL_END_IMPL


#ifndef ROSE499_TYPES_HPP
#define ROSE499_TYPES_HPP

#include <Eigen/Core>
#include <iostream>

namespace SimulatorTypes
{
    typedef long double ValueType;
    typedef Eigen::Matrix<ValueType, Eigen::Dynamic, Eigen::Dynamic> MatrixXT;
    typedef Eigen::Matrix<ValueType, Eigen::Dynamic, 1> VectorXT;
    typedef Eigen::Matrix<ValueType, 2, 1> Vector2T;
};

#endif


/* test_non_central_chi_squared_distribution.cpp
 *
 * Copyright Steven Watanabe 2011
 * Copyright Thijs van den Berg 2014
 * 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)
 *
 * $Id$
 *
 */

#include <boost/random/non_central_chi_squared_distribution.hpp>
#include <limits>

#define BOOST_RANDOM_DISTRIBUTION boost::random::non_central_chi_squared_distribution<>
#define BOOST_RANDOM_ARG1 k
#define BOOST_RANDOM_ARG2 lambda
#define BOOST_RANDOM_ARG1_DEFAULT 1.0
#define BOOST_RANDOM_ARG2_DEFAULT 1.0
#define BOOST_RANDOM_ARG1_VALUE 4.0
#define BOOST_RANDOM_ARG2_VALUE 42.0

#define BOOST_RANDOM_DIST0_MIN 0
#define BOOST_RANDOM_DIST0_MAX (std::numeric_limits<double>::infinity)()
#define BOOST_RANDOM_DIST1_MIN 0
#define BOOST_RANDOM_DIST1_MAX (std::numeric_limits<double>::infinity)()
#define BOOST_RANDOM_DIST2_MIN 0
#define BOOST_RANDOM_DIST2_MAX (std::numeric_limits<double>::infinity)()

#define BOOST_RANDOM_TEST1_PARAMS
#define BOOST_RANDOM_TEST1_MIN 0.0
#define BOOST_RANDOM_TEST1_MAX 100.0

#define BOOST_RANDOM_TEST2_PARAMS (10000.0)
#define BOOST_RANDOM_TEST2_MIN 100.0

#include "test_distribution.ipp"


#include <boost/test/unit_test.hpp>

#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp>

#include <statistics.hpp>
#include <date_parser.hpp>

/*
 * XXX al -
 *
 * Unfortunately, Boost.Test does not have a direct support for parametrized test
 * fixture, as does GoogleTests. This would have allowed us to
 * write a generic set of test for the possible multiple implementation of the
 * statistics engine.
 */
namespace
{
	struct fixture {
		fixture() :
			records(this->generic_map)
		{
			this->records.emplace(this->parser("2018-01-01").second, 1.0);
			this->records.emplace(this->parser("2018-01-02").second, 2.0);
			this->records.emplace(this->parser("2018-01-03").second, 3.0);
			this->records.emplace(this->parser("2018-01-04").second, 4.0);
			this->records.emplace(this->parser("2018-01-05").second, 5.0);
		}

		bpi::date_parser parser;

		bpi::records::map generic_map;
		bpi::records::map_t& records;
	};
}

BOOST_FIXTURE_TEST_SUITE( statistics, fixture )

BOOST_AUTO_TEST_CASE( statistics )
{
#if 0
{
    "lowest": {
        "price": "1",
        "date": "2018-01-01"
    },
    "highest": {
        "price": "5",
        "date": "2018-01-05"
    },
    "stddev": "1.58113883008418966598",
    "average": "3",
    "median": "3",
    "sample_size": "5"
}
#endif

	bpi::statistics::engine engine(this->records);

	auto results = engine.run();

	BOOST_CHECK(results.get<int>("sample_size") == 5);

	BOOST_CHECK_CLOSE(results.get<double>("stddev"), 1.581, 0.01);
	BOOST_CHECK_CLOSE(results.get<double>("average"), 3.0, 0.01);
	BOOST_CHECK_CLOSE(results.get<double>("median"), 3.0, 0.01);

	auto lowest = results.get_child("lowest");
	BOOST_CHECK_CLOSE(lowest.get<double>("price"), 1.0, 0.01);
	BOOST_CHECK(lowest.get<std::string>("date") == "2018-01-01");

	auto highest = results.get_child("highest");
	BOOST_CHECK_CLOSE(highest.get<double>("price"), 5.0, 0.01);
	BOOST_CHECK(highest.get<std::string>("date") == "2018-01-05");
}

BOOST_AUTO_TEST_CASE( median )
{
	// Even number of records
	this->records.emplace(this->parser("2018-01-06").second, 6.0);

	bpi::statistics::engine engine(this->records);

	auto results = engine.run();

	BOOST_CHECK_CLOSE(results.get<double>("median"), 3.5, 0.01);
}

BOOST_AUTO_TEST_SUITE_END()


#pragma once

#include <Discregrid/common.hpp>
#include <Eigen/Dense>

class GaussQuadrature
{
public:

    using Integrand = std::function<Discregrid::Real(Discregrid::Vector3r const&)>;
    using Domain = Discregrid::AlignedBox3r;

    static Discregrid::Real integrate(Integrand integrand, Domain const& domain, unsigned int p);
};


/* ---------------------------------------------------------------------
 *
 * Copyright (C) 2001 - 2021 by the deal.II authors
 *
 * This file is part of the deal.II library.
 *
 * The deal.II library is free software; you can use it, redistribute
 * it, and/or modify it under the terms of the GNU Lesser General
 * Public License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * The full text of the license can be found in the file LICENSE.md at
 * the top level directory of deal.II.
 *
 * ---------------------------------------------------------------------
 *
 * Author: Wolfgang Bangerth, University of Heidelberg, 2001
 */

// 像往常一样，程序以一个相当长的包含文件列表开始，你现在可能已经习惯了。

#include <deal.II/base/quadrature_lib.h>
#include <deal.II/base/function.h>
#include <deal.II/base/logstream.h>
#include <deal.II/base/table_handler.h>
#include <deal.II/lac/vector.h>
#include <deal.II/lac/sparse_matrix.h>
#include <deal.II/lac/solver_cg.h>
#include <deal.II/lac/precondition.h>
#include <deal.II/lac/affine_constraints.h>
#include <deal.II/grid/tria.h>
#include <deal.II/grid/grid_generator.h>
#include <deal.II/dofs/dof_handler.h>
#include <deal.II/dofs/dof_tools.h>
#include <deal.II/fe/fe_q.h>
#include <deal.II/fe/fe_values.h>
#include <deal.II/fe/mapping_q.h>
#include <deal.II/numerics/vector_tools.h>
#include <deal.II/numerics/matrix_tools.h>
#include <deal.II/numerics/data_out.h>

// 只有这一条是新的：它声明了一个动态稀疏模式（DynamicSparsityPattern）类，我们将在下面进一步使用和解释。

#include <deal.II/lac/dynamic_sparsity_pattern.h>

// 我们将使用C++标准库中的 std::find 算法，所以我们必须包括以下文件来声明它。

#include <algorithm>
#include <iostream>
#include <iomanip>
#include <cmath>

// 最后一步和以前所有的程序一样。

namespace Step11
{
  using namespace dealii;

// 然后我们声明一个表示拉普拉斯问题解决方案的类。由于这个例子程序是基于 step-5 ，这个类看起来相当相同，唯一的结构区别是函数 assemble_system now calls solve 本身，因此被称为 assemble_and_solve ，而且输出函数被删除，因为解函数非常无聊，不值得查看。

// 其他唯一值得注意的变化是，构造函数取一个值，代表以后要使用的映射的多项式程度，而且它还有一个成员变量，正好代表这个映射。一般来说，这个变量在实际应用中会出现在声明或使用有限元的相同地方。

  template <int dim>
  class LaplaceProblem
  {
  public:
    LaplaceProblem(const unsigned int mapping_degree);
    void run();

  private:
    void setup_system();
    void assemble_and_solve();
    void solve();
    void write_high_order_mesh(const unsigned cycle);

    Triangulation<dim> triangulation;
    FE_Q<dim>          fe;
    DoFHandler<dim>    dof_handler;
    MappingQ<dim>      mapping;

    SparsityPattern           sparsity_pattern;
    SparseMatrix<double>      system_matrix;
    AffineConstraints<double> mean_value_constraints;

    Vector<double> solution;
    Vector<double> system_rhs;

    TableHandler output_table;
  };

// 构建这样一个对象，通过初始化变量。这里，我们使用线性有限元（ fe 变量的参数表示多项式的度数），以及给定阶数的映射。将我们要做的事情打印到屏幕上。

  template <int dim>
  LaplaceProblem<dim>::LaplaceProblem(const unsigned int mapping_degree)
    : fe(1)
    , dof_handler(triangulation)
    , mapping(mapping_degree)
  {
    std::cout << "Using mapping with degree " << mapping_degree << ":"
              << std::endl
              << "============================" << std::endl;
  }

// 第一个任务是为这个问题设置变量。这包括生成一个有效的 DoFHandler 对象，以及矩阵的稀疏模式，和代表边界上自由度平均值为零的约束条件的对象。

  template <int dim>
  void LaplaceProblem<dim>::setup_system()
  {

// 第一个任务很简单：生成一个自由度的枚举，并将解和右手向量初始化为正确的大小。

    dof_handler.distribute_dofs(fe);
    solution.reinit(dof_handler.n_dofs());
    system_rhs.reinit(dof_handler.n_dofs());

// 下一个任务是构建代表约束的对象，即边界上自由度的平均值应该是零。为此，我们首先需要一个实际在边界上的节点的列表。 DoFTools 命名空间有一个函数可以返回一个IndexSet对象，该对象包含所有在边界上的自由度的指数。

// 一旦我们有了这个索引集，我们想知道哪个是对应于边界上的自由度的第一个索引。我们需要这个，因为我们想通过边界上所有其他自由度的值来约束边界上的一个节点。使用IndexSet类很容易得到这个 "第一个 "自由度的索引。

    const IndexSet boundary_dofs = DoFTools::extract_boundary_dofs(dof_handler);

    const types::global_dof_index first_boundary_dof =
      boundary_dofs.nth_index_in_set(0);

// 然后生成一个只有这一个约束的约束对象。首先清除所有以前的内容（这些内容可能来自以前在更粗的网格上的计算），然后添加这一行，将 first_boundary_dof 约束到其他边界DoF的总和，每一个权重为-1。最后，关闭约束对象，也就是说，对它做一些内部记录，以便更快地处理后面的内容。

    mean_value_constraints.clear();
    mean_value_constraints.add_line(first_boundary_dof);
    for (types::global_dof_index i : boundary_dofs)
      if (i != first_boundary_dof)
        mean_value_constraints.add_entry(first_boundary_dof, i, -1);
    mean_value_constraints.close();

// 下一个任务是生成一个稀疏模式。这的确是一个棘手的任务。通常情况下，我们只需调用 DoFTools::make_sparsity_pattern 并使用悬挂节点约束来浓缩结果。我们在这里没有悬挂节点约束（因为我们在这个例子中只进行全局细化），但是我们在边界上有这个全局约束。在这种情况下，这带来了一个严重的问题： SparsityPattern 类希望我们事先说明每行的最大条目数，可以是所有行的，也可以是每行单独的。在库中有一些函数可以告诉你这个数字，如果你只有悬空的节点约束的话（即 DoFHandler::max_couplings_between_dofs), ，但这对现在的情况来说是怎样的？困难的出现是因为消除约束的自由度需要在矩阵中增加一些条目，而这些条目的位置并不那么容易确定。因此，如果我们在这里给出每行的最大条目数，我们就会有一个问题。

// 由于这可能非常困难，以至于无法给出合理的答案，只能分配合理的内存量，所以有一个DynamicSparsityPattern类，它可以帮助我们解决这个问题。它不要求我们事先知道行可以有多少个条目，而是允许任何长度。因此，在你对行的长度没有很好的估计的情况下，它明显更灵活，但是代价是建立这样一个模式也比建立一个你事先有信息的模式要昂贵得多。尽管如此，由于我们在这里没有其他选择，我们将建立这样一个对象，用矩阵的尺寸初始化它，并调用另一个函数 DoFTools::make_sparsity_pattern 来获得由于微分算子引起的稀疏模式，然后用约束对象浓缩它，在稀疏模式中增加那些消除约束所需的位置。

    DynamicSparsityPattern dsp(dof_handler.n_dofs(), dof_handler.n_dofs());
    DoFTools::make_sparsity_pattern(dof_handler, dsp);
    mean_value_constraints.condense(dsp);

// 最后，一旦我们有了完整的模式，我们就可以从中初始化一个 SparsityPattern 类型的对象，并反过来用它初始化矩阵。请注意，这实际上是必要的，因为与 SparsityPattern 类相比，DynamicSparsityPattern的效率非常低，因为它必须使用更灵活的数据结构，所以我们不可能将稀疏矩阵类建立在它的基础上，而是需要一个 SparsityPattern 类型的对象，我们通过复制中间对象产生这个对象。

// 作为进一步的附带说明，你会注意到我们在这里没有明确的  compress  稀疏模式。当然，这是由于 copy_from 函数从一开始就生成了一个压缩对象，你不能再向其添加新的条目。因此， compress 的调用是隐含在 copy_from 的调用中的。

    sparsity_pattern.copy_from(dsp);
    system_matrix.reinit(sparsity_pattern);
  }

// 下一个函数接着组装线性方程组，对其进行求解，并对解进行评估。这样就有了三个动作，我们将把它们放到八个真实的语句中（不包括变量的声明，以及临时向量的处理）。因此，这个函数是为非常懒惰的人准备的。尽管如此，所调用的函数是相当强大的，通过它们，这个函数使用了整个库的大量内容。但让我们来看看每一个步骤。

  template <int dim>
  void LaplaceProblem<dim>::assemble_and_solve()
  {

// 首先，我们要把矩阵和右手边的内容组合起来。在之前的所有例子中，我们已经研究了如何手动完成这一工作的各种方法。然而，由于拉普拉斯矩阵和简单的右手边在应用中出现的频率很高，库中提供的函数实际上是为你做这件事的，也就是说，它们在所有单元格上进行循环，设置局部的矩阵和向量，并将它们放在一起，得到最终结果。

// 以下是两个最常用的函数：创建拉普拉斯矩阵和创建来自体或边界力的右侧向量。它们需要映射对象、代表自由度和使用中的有限元的 DoFHandler 对象、要使用的正交公式以及输出对象。创建右手向量的函数还必须接受一个描述（连续）右手向量函数的函数对象。

// 让我们来看看矩阵和体力的集成方式。

    const unsigned int gauss_degree =
      std::max(static_cast<unsigned int>(
                 std::ceil(1. * (mapping.get_degree() + 1) / 2)),
               2U);
    MatrixTools::create_laplace_matrix(mapping,
                                       dof_handler,
                                       QGauss<dim>(gauss_degree),
                                       system_matrix);
    VectorTools::create_right_hand_side(mapping,
                                        dof_handler,
                                        QGauss<dim>(gauss_degree),
                                        Functions::ConstantFunction<dim>(-2),
                                        system_rhs);

// 这很简单，对吗？

// 不过，有两点需要注意。首先，这些函数在很多情况下都会用到。也许你想为一个矢量值有限元创建一个拉普拉斯或质量矩阵；或者你想使用默认的Q1映射；或者你想用拉普拉斯算子的一个系数来装配矩阵。由于这个原因，在 MatrixCreator 和 MatrixTools 命名空间中有相当多的这些函数的变种。每当你需要这些函数的一个与上面调用的略有不同的版本时，当然值得看一下文档，并检查一些东西是否适合你的需要。

// 第二点是关于我们使用的正交公式：我们想对双线性形状函数进行积分，所以我们知道我们至少要使用二阶高斯正交公式。另一方面，我们希望正交规则至少有边界近似的阶数。因为有 $r$ 点的高斯规则的阶数是 $2r -1$  ，而使用 $p$ 度的多项式的边界近似的阶数是 $p+1$ ，我们知道 $2r \geq p$  。由于r必须是一个整数，并且（如上所述）必须至少是 $2$ ，这就弥补了上述公式计算 gauss_degree 。

// 由于对右侧向量的体力贡献的生成是如此简单，我们对边界力也要重新做一遍：分配一个合适大小的向量并调用合适的函数。边界函数有常量值，所以我们可以从库中快速生成一个对象，我们使用与上面相同的正交公式，但这次的维度较低，因为我们现在是在面上而不是在单元上积分。

    Vector<double> tmp(system_rhs.size());
    VectorTools::create_boundary_right_hand_side(
      mapping,
      dof_handler,
      QGauss<dim - 1>(gauss_degree),
      Functions::ConstantFunction<dim>(1),
      tmp);

// 然后将边界的贡献与域内部的贡献相加。

    system_rhs += tmp;

// 在组装右手边时，我们必须使用两个不同的矢量对象，然后将它们加在一起。我们不得不这样做的原因是， VectorTools::create_right_hand_side 和 VectorTools::create_boundary_right_hand_side 函数首先清除输出向量，而不是将它们的结果与之前的内容相加。这可以合理地称为库在起步阶段的设计缺陷，但不幸的是，事情现在已经是这样了，很难改变这种无声地破坏现有代码的事情，所以我们不得不接受。

// 现在，线性系统已经建立起来了，所以我们可以从矩阵和右手向量中消除我们约束到边界上其他DoF的一个自由度的均值约束，并解决这个系统。之后，再次分配约束，在这种情况下，这意味着将被约束的自由度设置为适当的值

    mean_value_constraints.condense(system_matrix);
    mean_value_constraints.condense(system_rhs);

    solve();
    mean_value_constraints.distribute(solution);

// 最后，评估我们得到的解决方案。正如在介绍中所说，我们对解决方案的H1半正态感兴趣。在这里，我们在库中也有一个函数可以做到这一点，尽管是以一种稍微不明显的方式： VectorTools::integrate_difference 函数整合了一个有限元函数和一个连续函数之间的差值的规范。因此，如果我们想要一个有限元场的规范，我们只需将连续函数设为零。请注意，这个函数，就像库中的许多其他函数一样，至少有两个版本，一个是以映射为参数的（我们在这里使用），另一个是我们在以前的例子中使用的隐含的 MappingQ1  。 还要注意的是，我们采用的是高一级的正交公式，以避免出现超融合效应，即在某些点上的解特别接近精确解（我们不知道这里是否会出现这种情况，但有已知的案例，我们只是想确认一下）。

    Vector<float> norm_per_cell(triangulation.n_active_cells());
    VectorTools::integrate_difference(mapping,
                                      dof_handler,
                                      solution,
                                      Functions::ZeroFunction<dim>(),
                                      norm_per_cell,
                                      QGauss<dim>(gauss_degree + 1),
                                      VectorTools::H1_seminorm);

// 然后，刚刚调用的函数将其结果作为一个值的向量返回，每个值表示一个单元格上的法线。为了得到全局法线，我们要做以下工作。

    const double norm =
      VectorTools::compute_global_error(triangulation,
                                        norm_per_cell,
                                        VectorTools::H1_seminorm);

// 最后一项任务--生成输出。

    output_table.add_value("cells", triangulation.n_active_cells());
    output_table.add_value("|u|_1", norm);
    output_table.add_value("error",
                           std::fabs(norm - std::sqrt(3.14159265358 / 2)));
  }

// 下面这个解线性方程组的函数是从 step-5 中复制过来的，在那里有详细的解释。

  template <int dim>
  void LaplaceProblem<dim>::solve()
  {
    SolverControl            solver_control(1000, 1e-12);
    SolverCG<Vector<double>> cg(solver_control);

    PreconditionSSOR<SparseMatrix<double>> preconditioner;
    preconditioner.initialize(system_matrix, 1.2);

    cg.solve(system_matrix, solution, system_rhs, preconditioner);
  }

// 接下来，我们把解决方案以及材料ID写到一个VTU文件中。这与其他许多教程程序中的做法相似。这个教程程序中提出的新内容是，我们要确保写到文件中用于可视化的数据实际上是deal.II内部使用的数据的忠实代表。这是因为大多数可视化数据格式只用顶点坐标表示单元，但没有办法表示deal.II中使用高阶映射时的曲线边界--换句话说，你在可视化工具中看到的东西实际上不是你正在计算的东西。顺带一提，在使用高阶形状函数时也是如此。大多数可视化工具只呈现双线性/三线性的表示。这在 DataOut::build_patches().) 中有详细的讨论。

// 所以我们需要确保高阶表示被写入文件中。我们需要考虑两个特别的话题。首先，我们通过 DataOutBase::VtkFlags 告诉DataOut对象，我们打算将元素的细分解释为高阶拉格朗日多项式，而不是双线性斑块的集合。最近的可视化程序，如ParaView 5.5版或更新版，然后可以呈现高阶解决方案（更多细节见wiki
//  page）。其次，我们需要确保映射被传递给 DataOut::build_patches() 方法。最后，DataOut类默认只打印boundary单元的曲面，所以我们需要确保通过映射将内部单元也打印成曲面。

  template <int dim>
  void LaplaceProblem<dim>::write_high_order_mesh(const unsigned cycle)
  {
    DataOut<dim> data_out;

    DataOutBase::VtkFlags flags;
    flags.write_higher_order_cells = true;
    data_out.set_flags(flags);

    data_out.attach_dof_handler(dof_handler);
    data_out.add_data_vector(solution, "solution");

    data_out.build_patches(mapping,
                           mapping.get_degree(),
                           DataOut<dim>::curved_inner_cells);

    std::ofstream file("solution-c=" + std::to_string(cycle) +
                       ".p=" + std::to_string(mapping.get_degree()) + ".vtu");

    data_out.write_vtu(file);
  }

// 最后是控制要执行的不同步骤的主要函数。它的内容相当简单，生成一个圆的三角形，给它关联一个边界，然后在随后的更细的网格上做几个循环。请注意，我们将网格细化放到了循环头中；这对测试程序来说可能是件好事，但对实际应用来说，你应该考虑到这意味着网格是在循环最后一次执行后被细化的，因为增量子句（三部分循环头的最后一部分）是在比较部分（第二部分）之前执行的，如果网格已经相当细化了，这可能是相当昂贵的。在这种情况下，你应该安排代码，使网格在最后一次循环运行后不再被进一步细化（或者你应该在每次运行的开始就这样做，除了第一次）。

  template <int dim>
  void LaplaceProblem<dim>::run()
  {
    GridGenerator::hyper_ball(triangulation);

    for (unsigned int cycle = 0; cycle < 6; ++cycle)
      {
        setup_system();
        assemble_and_solve();
        write_high_order_mesh(cycle);

        triangulation.refine_global();
      }

// 在所有的数据生成之后，将结果的表格写到屏幕上。

    output_table.set_precision("|u|_1", 6);
    output_table.set_precision("error", 6);
    output_table.write_text(std::cout);
    std::cout << std::endl;
  }
} // namespace Step11

// 最后是主函数。它的结构与前面几个例子中使用的结构相同，所以可能不需要更多解释。

int main()
{
  try
    {
      std::cout.precision(5);

// 这是主循环，用线性到立方的映射做计算。注意，由于我们只需要一次 LaplaceProblem@<2@> 类型的对象，我们甚至不给它命名，而是创建一个未命名的这样的对象，并调用它的 run 函数，随后它又立即被销毁。

      for (unsigned int mapping_degree = 1; mapping_degree <= 3;
           ++mapping_degree)
        Step11::LaplaceProblem<2>(mapping_degree).run();
    }
  catch (std::exception &exc)
    {
      std::cerr << std::endl
                << std::endl
                << "----------------------------------------------------"
                << std::endl;
      std::cerr << "Exception on processing: " << std::endl
                << exc.what() << std::endl
                << "Aborting!" << std::endl
                << "----------------------------------------------------"
                << std::endl;
      return 1;
    }
  catch (...)
    {
      std::cerr << std::endl
                << std::endl
                << "----------------------------------------------------"
                << std::endl;
      std::cerr << "Unknown exception!" << std::endl
                << "Aborting!" << std::endl
                << "----------------------------------------------------"
                << std::endl;
      return 1;
    };

  return 0;
}