#include <iostream>
#include <vector>
#include <cstdio>
#include <cstdlib>
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/boyer_myrvold_planar_test.hpp>

using std::vector;
using namespace boost;

//a class to hold the coordinates of the straight line embedding
struct coord_t
{
    std::size_t x;
    std::size_t y;
};

vector<int> check_k33{ 4060, 13242, 15481, 15751, 21878, 23221, 23371, 26323, 26413, 26862}; // all masks of k33 graph with 6 vertexes
vector<int> check_k5{ 5871, 11127, 19899, 29149, 32286, 32736};                             // all masks of k5 graph with 6 vertexes

int mask_to_int(std::string &mask) {
    int ans = 0;
    for (auto c : mask) {
        ans <<= 1;
        if (c == '1')
            ans += 1;
    }
    return ans;
}
bool hard_check_k5(std::string mask) {
    vector<int> deg(6, 0);
    int cur = 0;
    for (int i = 1; i < 6; i++) {
        for (int j = 0; j < i; j++) {
            if (mask[cur] == '1') {
                deg[i]++;
                deg[j]++;
            }
            cur++;
        }
    }

    int counter = 0;
    for(auto v : deg) {
        if (v == 4)
            counter++;
    }
    if (counter == 5) {
        return true;
    }  else
        return false;
}

bool chek_k5_or_k33(int mask) {
    for (int check_mask : check_k5) {
        if ((check_mask & mask) == check_mask)
            return true;
    }
    for (int check_mask : check_k33) {
        if ((check_mask & mask) == check_mask)
            return true;
    }
    return false;
}

void boost_foo(std::string mask, int size, bool &flag);

void solve() {
    int t;
    bool flag, flag1;
    std::string mask, tmp;
    std::getline(std::cin, tmp);
    t = atoi(tmp.c_str());
    for (int i = 0; i < t; ++i) {
        std::getline(std::cin, mask);
        size_t size = mask.size();
        boost_foo(mask, 6, flag);
        if (size < 10) { // less then 5 vertex
            //std::cout << "YES\n";
        } else if (size == 10) {  // when 5 vertex
            if (mask_to_int(mask) == 1023) { // 1111111111 -- K_{5}
                //std::cout << "NO\n";
            } else {
                //std::cout << "YES\n";
            }
        } else if (size == 15) { // when 6 vertex
            if (chek_k5_or_k33(mask_to_int(mask))) {
                flag1 = false;//std::cout << "NO\n";
            } else {
                flag1 = true;//std::cout << "YES\n";
            }
        }
        if (flag != flag1) {
            std::cout << "Error in mask: " << mask << "\n";
            std::cout << "Expected: ";
            if (flag)
                std::cout << "YES";
            else
                std::cout << "NO";
            std::cout << "\n";
        }
    }
}

void boost_foo(std::string mask, int size, bool &flag) {
    typedef adjacency_list<vecS,
            vecS,
            undirectedS,
            property<vertex_index_t, int>
    > graph;

    graph g(size);
    int k = 0;
    for (int i = 1; i < size; i++) {
        for (int j = 0; j < i; j++) {
            if (mask[k] == '1') {
                add_edge(i, j, g);
            }
            k++;
        }
    }

    if (boyer_myrvold_planarity_test(g))
        flag = true;//std::cout << "YES --- ";
    else
        flag = false;//std::cout << "NO --- ";
}
int main() {
    freopen("planaritycheck.in", "r", stdin);
    freopen("planaritycheck.out", "w", stdout);
    solve();

    return 0;
}


//==============================================================================
//         Copyright 2003 - 2011 LASMEA UMR 6602 CNRS/Univ. Clermont II
//         Copyright 2009 - 2011 LRI    UMR 8623 CNRS/Univ Paris Sud XI
//
//          Distributed under the Boost Software License, Version 1.0.
//                 See accompanying file LICENSE.txt or copy at
//                     http://www.boost.org/LICENSE_1_0.txt
//==============================================================================
#ifndef BOOST_SIMD_ARITHMETIC_FUNCTIONS_GENERIC_MEANOF_HPP_INCLUDED
#define BOOST_SIMD_ARITHMETIC_FUNCTIONS_GENERIC_MEANOF_HPP_INCLUDED

#include <boost/simd/arithmetic/functions/meanof.hpp>
#include <boost/simd/include/functions/simd/average.hpp>
#include <boost/simd/include/functions/simd/is_greater_equal.hpp>
#include <boost/simd/include/functions/simd/is_gez.hpp>
#include <boost/simd/include/functions/simd/is_finite.hpp>
#include <boost/simd/include/functions/simd/if_else.hpp>
#include <boost/simd/include/functions/simd/logical_and.hpp>
#include <boost/simd/include/functions/simd/plus.hpp>
#include <boost/simd/include/functions/simd/minus.hpp>
#include <boost/simd/include/functions/simd/shrai.hpp>
#include <boost/simd/include/functions/simd/shri.hpp>
#include <boost/simd/include/constants/half.hpp>

//no overflow average for floating numbers

namespace boost { namespace simd { namespace ext
{
  BOOST_SIMD_FUNCTOR_IMPLEMENTATION(boost::simd::tag::meanof_, tag::cpu_,
                                    (A0),
                                    (generic_< floating_<A0> >) (generic_< floating_<A0> >)
                                    )
  {
    typedef A0 result_type;
    BOOST_SIMD_FUNCTOR_CALL_REPEAT(2)
    {
      return
        if_else(logical_and(logical_and(is_gez(a0*a1),is_finite(a0)),is_finite(a1)),
                if_else(ge(a1, a0),
                        a0 + (a1-a0)*Half<result_type>(),
                        a1 + (a0-a1)*Half<result_type>()),
                average(a0, a1)
                );
    }
  };
  BOOST_SIMD_FUNCTOR_IMPLEMENTATION(boost::simd::tag::meanof_, tag::cpu_,
                                    (A0),
                                    (generic_< arithmetic_<A0> >) (generic_< arithmetic_<A0> >)
                                    )
  {
    typedef A0 result_type;
    BOOST_SIMD_FUNCTOR_CALL_REPEAT(2)
    {
      return
        if_else(is_gez(a0*a1),
                if_else(ge(a1, a0),
                        a0 + shri((a1-a0), 1),
                        a1 + shri((a0-a1), 1)),
                shrai(a0+a1, 1)
                );
    }
  };
} } }

#endif


/*! *****************************************************************************
 *   \file filterDataset_main.cpp
 *   \author moennen
 *   \brief
 *   \date 2018-09-07
 *   *****************************************************************************/

#include "utils/imgFileLst.h"
#include "utils/cv_utils.h"

#include <Eigen/Dense>

#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>

#include <boost/filesystem.hpp>

#include <limits>
#include <iostream>

using namespace std;
using namespace cv;
using namespace glm;
using namespace boost;
using namespace Eigen;

namespace
{
//------------------------------------------------------------------------------
//
void resizeToMax( Mat& img, const uvec2 sampleSz )
{
   const uvec2 imgSz( img.cols, img.rows );
   // random rescale
   const float ds = std::max( (float)sampleSz.y / imgSz.y, (float)sampleSz.x / imgSz.x );
   if ( ( ds > 0.0 ) && ( ds < 1.0 ) ) resize( img, img, Size(), ds, ds, CV_INTER_AREA );
}
};

const string keys =
    "{help h usage ? |         | print this message   }"
    "{@imgFileLst   |         | images list   }"
    "{@imgRootDir   |         | images root dir   }"
    "{@imgOutDir    |         | images output root dir   }"
    "{maxWidth      |640      | maximum width of the output image }"
    "{maxHeight     |480      | maximum heigth of the output image }"
    "{minError      |0.005    | minimum error }"
    "{maxError      |0.015    | maximum error }"
    "{show          |         |   }"
    "{write         |         | write images   }"
    "{stats         |         | compute error stats   }";

int main( int argc, char* argv[] )
{
   CommandLineParser parser( argc, argv, keys );
   if ( parser.has( "help" ) )
   {
      parser.printMessage();
      return ( 0 );
   }
   const filesystem::path outRootPath( parser.get<string>( "@imgOutDir" ) );

   const bool doShow = parser.get<bool>( "show" );
   const bool doWrite = parser.get<bool>( "write" );
   const bool doStats = parser.get<bool>( "stats" );
   const double minErr = parser.get<double>( "minError" );
   const double maxErr = parser.get<double>( "maxError" );
   const uvec2 maxSize( parser.get<int>( "maxWidth" ), parser.get<int>( "maxHeight" ) );
   double statMin = numeric_limits<double>::max();
   double statMax = numeric_limits<double>::min();
   double statMean = 0.0;
   size_t nValid = 0;

   // Create the list of image triplets + alpha
   ImgNFileLst imgLst(
       4,
       parser.get<string>( "@imgFileLst" ).c_str(),
       parser.get<string>( "@imgRootDir" ).c_str() );
   if ( imgLst.size() == 0 )
   {
      cerr << "Invalid dataset : " << parser.get<string>( "@imgFileLst" ) << endl;
      return -1;
   }

   for ( size_t i = 0; i < imgLst.size(); ++i )
   {
      Mat prevImg = cv_utils::imread32FC3( imgLst.filePath( i, 0 ) );
      resizeToMax(prevImg, maxSize);
      Mat nextImg = cv_utils::imread32FC3( imgLst.filePath( i, 2 ) );
      resizeToMax(nextImg, maxSize);

      const double merr = sqrt( norm( prevImg, nextImg ) / ( prevImg.cols * prevImg.rows * 3 ) );
      if ( ( merr > maxErr ) || ( merr < minErr ) )
      {
         continue;
      }

      const auto& data = imgLst[i];

      if ( doStats )
      {
         nValid++;
         statMin = std::min( statMin, merr );
         statMax = std::max( statMax, merr );
         statMean += merr;

         cout << merr << " -> " << statMean / nValid << "  [ " << statMin << " , " << statMax
              << "] " << (double)nValid / ( i + 1 ) << endl;
      }
      else
      {
         cout << data[0] << " " << data[1] << " " << data[2] << " " << data[3] << endl;
      }

      if ( doShow )
      {
         imshow( "InterpBlend", ( prevImg + nextImg ) * 0.5f );
         waitKey( 0 );
      }

      if (doWrite)
      {
        Mat currImg = cv_utils::imread32FC3( imgLst.filePath( i, 2 ) );
        resizeToMax(currImg, maxSize);

        imwrite( filesystem::path( outRootPath / data[0] ).string().c_str(), prevImg * 255.0 );
        imwrite( filesystem::path( outRootPath / data[1] ).string().c_str(), currImg * 255.0 );
        imwrite( filesystem::path( outRootPath / data[2] ).string().c_str(), nextImg * 255.0 );
      }
   }

   return ( 0 );
}


#include <MultiVariRNG.h>
#include <Eigen/Dense>

// EXAMPLE 1: WAYS TO USE MULTIVARIRNG

int main(){

    std::cout << "\n ---------------------------- Start Double \n";

    // USING NORMALLY WITH CONSTRUCTOR
    Eigen::Matrix3d covar = Eigen::MatrixXd::Ones(3,3);
    Eigen::Vector3d mean = Eigen::MatrixXd::Zero(3,1);
    MultiVariRNG<Eigen::Vector3d,Eigen::Matrix3d> mvrng(mean,covar);

    for(size_t i = 0; i<100; i++){
        std::cout << "\n" << mvrng.MVRNGenerate() << "\n" << std::endl;
    }

    std::cout << "\n ---------------------------- Start Float \n";

    // USING WITH FLOAT MATRIX
    Eigen::Matrix3f covarf = Eigen::MatrixXf::Ones(3,3);
    Eigen::Vector3f meanf = Eigen::MatrixXf::Zero(3,1);
    MultiVariRNG<Eigen::Vector3f,Eigen::Matrix3f> mvrngf(meanf,covarf);

    for(size_t i = 0; i<100; i++){
        std::cout << "\n" << mvrngf.MVRNGenerate() << "\n" << std::endl;
    }

    std::cout << "\n ---------------------------- Start 4dim \n";

    // USING WITH POINTER AND CONSTRUCTOR WITH "new"
    Eigen::Matrix4d covar2 = Eigen::MatrixXd::Ones(4,4);
    Eigen::Vector4d mean2 = Eigen::MatrixXd::Zero(4,1);

    MultiVariRNG<Eigen::Vector4d,Eigen::Matrix4d>* mvrng_ptr
     = new MultiVariRNG< Eigen::Vector4d,Eigen::Matrix4d >(mean2,covar2);

    for(size_t i = 0; i<100; i++){
        std::cout << "\n" << mvrng_ptr->MVRNGenerate() << "\n" << std::endl;
    }

    return 0;
}


//==================================================================================================
/*!
  @file

  @copyright 2015 NumScale SAS
  @copyright 2015 J.T. Lapreste

  Distributed under the Boost Software License, Version 1.0.
  (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
*/
//==================================================================================================
#ifndef BOOST_SIMD_ARCH_COMMON_SCALAR_FUNCTION_EXPOCVT_HPP_INCLUDED
#define BOOST_SIMD_ARCH_COMMON_SCALAR_FUNCTION_EXPOCVT_HPP_INCLUDED

#include <boost/simd/function/bitwise_cast.hpp>
#include <boost/simd/function/fma.hpp>
#include <boost/simd/function/is_flint.hpp>
#include <boost/simd/function/shr.hpp>
#include <boost/simd/function/shift_left.hpp>
#include <boost/simd/detail/constant/maxexponent.hpp>
#include <boost/simd/constant/two.hpp>
#include <boost/simd/constant/nbexponentbits.hpp>
#include <boost/simd/constant/nbmantissabits.hpp>
#include <boost/simd/detail/dispatch/function/overload.hpp>
#include <boost/simd/detail/dispatch/meta/as_integer.hpp>
#include <boost/config.hpp>
#include <cmath>

namespace boost { namespace simd { namespace ext
{
  namespace bd = boost::dispatch;
  namespace bs = boost::simd;

  BOOST_DISPATCH_OVERLOAD ( expocvt_
                          , (typename A0)
                          , bd::cpu_
                          , bd::scalar_<bd::floating_<A0> >
                          )
  {
    using result =  bd::as_integer_t<A0, unsigned>;
    BOOST_FORCEINLINE A0 operator() ( A0 a0) const BOOST_NOEXCEPT
    {
      BOOST_ASSERT_MSG(is_flint(a0), "parameter is not a flint");
//      return a0+Maxexponent<A0>();
      return std::ldexp(A0(1), result(a0));
    }
  };

} } }

#endif


//==============================================================================
//         Copyright 2003 - 2011 LASMEA UMR 6602 CNRS/Univ. Clermont II
//         Copyright 2009 - 2011 LRI    UMR 8623 CNRS/Univ Paris Sud XI
//
//          Distributed under the Boost Software License, Version 1.0.
//                 See accompanying file LICENSE.txt or copy at
//                     http://www.boost.org/LICENSE_1_0.txt
//==============================================================================
#ifndef BOOST_SIMD_ARITHMETIC_FUNCTIONS_SCALAR_FAST_RSQRT_HPP_INCLUDED
#define BOOST_SIMD_ARITHMETIC_FUNCTIONS_SCALAR_FAST_RSQRT_HPP_INCLUDED

#include <boost/simd/arithmetic/functions/fast_rsqrt.hpp>
#include <boost/simd/include/functions/scalar/bitwise_cast.hpp>
#include <boost/simd/include/functions/scalar/rsqrt.hpp>
#include <boost/dispatch/meta/as_integer.hpp>
#include <boost/dispatch/attributes.hpp>

namespace boost { namespace simd { namespace ext
{
  BOOST_DISPATCH_IMPLEMENT          ( fast_rsqrt_
                                    , tag::cpu_
                                    , (A0)
                                    , (scalar_< single_<A0> >)
                                    )
  {
    typedef A0 result_type;

    BOOST_FORCEINLINE result_type operator()(A0 a0) const
    {
      typedef typename dispatch::meta::as_integer<A0>::type i_t;

      // Quake III Arena RSQRT approximation
      i_t x = bitwise_cast<i_t>(a0);
      i_t y = 0x5f3759df - (x >> 1);

      // make negative values be NaN
      y |= x >> (sizeof(i_t)*CHAR_BIT-1);

      A0 x2 = a0 * 0.5f;
      A0 y2 = bitwise_cast<A0>(y);

      // Newton-Rhapson refinement steps: 2 NR steps for precision purpose
      y2    = y2 * ( 1.5f - ( x2 * y2 * y2 ) );
      return  y2 * ( 1.5f - ( x2 * y2 * y2 ) );
    }
  };

  BOOST_DISPATCH_IMPLEMENT          ( fast_rsqrt_
                                    , tag::cpu_
                                    , (A0)
                                    , (scalar_< floating_<A0> >)
                                    )
  {
    typedef A0 result_type;

    BOOST_FORCEINLINE result_type operator()(A0 a0) const
    {
      return simd::rsqrt(a0);
    }
  };
} } }

#endif


/* ---------------------------------------------------------------------
 *
 * Copyright (C) 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.
 *
 * ---------------------------------------------------------------------
 *
 * Authors: Fabian Castelli, Karlsruhe Institute of Technology (KIT)
 */

// 首先我们包括本教程所需的deal.II库的典型头文件。

#include <deal.II/base/function.h>
#include <deal.II/base/quadrature_lib.h>
#include <deal.II/base/timer.h>
#include <deal.II/base/vectorization.h>

#include <deal.II/dofs/dof_accessor.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/mapping_q_generic.h>

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

#include <deal.II/lac/affine_constraints.h>
#include <deal.II/lac/precondition.h>
#include <deal.II/lac/solver_cg.h>
#include <deal.II/lac/vector.h>

#include <deal.II/numerics/data_out.h>
#include <deal.II/numerics/vector_tools.h>

// 特别是，我们需要包括无矩阵框架的头文件。

#include <deal.II/matrix_free/fe_evaluation.h>
#include <deal.II/matrix_free/matrix_free.h>
#include <deal.II/matrix_free/operators.h>
#include <deal.II/matrix_free/tools.h>

// 由于我们要使用几何多网格预处理程序，所以我们还需要多级头文件。

#include <deal.II/multigrid/mg_coarse.h>
#include <deal.II/multigrid/mg_constrained_dofs.h>
#include <deal.II/multigrid/mg_matrix.h>
#include <deal.II/multigrid/mg_smoother.h>
#include <deal.II/multigrid/mg_tools.h>
#include <deal.II/multigrid/mg_transfer_matrix_free.h>
#include <deal.II/multigrid/multigrid.h>

// 最后是一些常用的C++头文件，用于输入和输出。

#include <fstream>
#include <iostream>

namespace Step66
{
  using namespace dealii;

//  @sect3{Matrix-free JacobianOperator}

// 在开始时，我们定义了雅各布系数的无矩阵算子。作为指导，我们遵循教程  step-37  和  step-48  ，其中广泛记录了  MatrixFreeOperators::Base  类的精确接口。

// 由于我们希望将雅各布（Jacobian）作为系统矩阵使用，并将其传递给线性求解器以及多级预处理类，我们从 MatrixFreeOperators::Base 类派生出 JacobianOperator 类，这样我们就有了正确的接口。我们需要从基类中覆盖的两个函数是 MatrixFreeOperators::Base::apply_add() 和 MatrixFreeOperators::Base::compute_diagonal() 函数。为了允许用浮动精度进行预处理，我们将数字类型定义为模板参数。

// 正如在介绍中提到的，我们需要在最后一个牛顿步骤 $u_h^n$ 中评估雅各布 $F'$ ，以便计算牛顿更新 $s_h^n$ 。为了获得最后一个牛顿步骤 $u_h^n$ 的信息，我们的做法与 step-37 基本相同，在使用无矩阵算子之前，我们将一个系数函数的值存储在一个表中 nonlinear_values 。我们在这里实现的不是一个函数  evaluate_coefficient()  ，而是一个函数  evaluate_newton_step()  。

// 作为 JacobianOperator 的额外私有成员函数，我们实现了 local_apply() 和 local_compute_diagonal() 函数。第一个是矩阵-向量应用的实际工作函数，我们在 apply_add() 函数中将其传递给 MatrixFree::cell_loop() 。后面一个是计算对角线的工作函数，我们把它传递给 MatrixFreeTools::compute_diagonal() 函数。

// 为了提高源代码的可读性，我们进一步为FEEvaluation对象定义了一个别名。

  template <int dim, int fe_degree, typename number>
  class JacobianOperator
    : public MatrixFreeOperators::
        Base<dim, LinearAlgebra::distributed::Vector<number>>
  {
  public:
    using value_type = number;

    using FECellIntegrator =
      FEEvaluation<dim, fe_degree, fe_degree + 1, 1, number>;

    JacobianOperator();

    virtual void clear() override;

    void evaluate_newton_step(
      const LinearAlgebra::distributed::Vector<number> &newton_step);

    virtual void compute_diagonal() override;

  private:
    virtual void apply_add(
      LinearAlgebra::distributed::Vector<number> &      dst,
      const LinearAlgebra::distributed::Vector<number> &src) const override;

    void
    local_apply(const MatrixFree<dim, number> &                   data,
                LinearAlgebra::distributed::Vector<number> &      dst,
                const LinearAlgebra::distributed::Vector<number> &src,
                const std::pair<unsigned int, unsigned int> &cell_range) const;

    void local_compute_diagonal(FECellIntegrator &integrator) const;

    Table<2, VectorizedArray<number>> nonlinear_values;
  };

//  JacobianOperator 的构造函数只是调用基类 MatrixFreeOperators::Base, 的构造函数，而基类本身就是派生于Subscriptor类。

  template <int dim, int fe_degree, typename number>
  JacobianOperator<dim, fe_degree, number>::JacobianOperator()
    : MatrixFreeOperators::Base<dim,
                                LinearAlgebra::distributed::Vector<number>>()
  {}

//  clear() 函数重置了保存非线性值的表格，并调用基类的 clear() 函数。

  template <int dim, int fe_degree, typename number>
  void JacobianOperator<dim, fe_degree, number>::clear()
  {
    nonlinear_values.reinit(0, 0);
    MatrixFreeOperators::Base<dim, LinearAlgebra::distributed::Vector<number>>::
      clear();
  }

//  @sect4{Evaluation of the old Newton step}

// 下面的  evaluate_newton_step()  函数是基于  step-37  的  evaluate_coefficient()  函数。然而，它并不评估一个函数对象，而是评估一个代表有限元函数的向量，即雅各布系数所需的最后一个牛顿步骤。因此，我们设置了一个FEEvaluation对象，用 FEEvaluation::read_dof_values_plain() 和 FEEvaluation::evaluate() 函数评估正交点的有限元函数。我们将有限元函数的评估值直接存储在 nonlinear_values 表中。

//这样做会很好，在 local_apply() 函数中我们可以使用存储在表中的值来应用矩阵-向量乘积。然而，我们也可以在这个阶段优化雅各布系数的实现。我们可以直接评估非线性函数 std::exp(newton_step[q]) 并将这些值存储在表中。这就跳过了在每次调用 vmult() 函数时对非线性的所有评估。

  template <int dim, int fe_degree, typename number>
  void JacobianOperator<dim, fe_degree, number>::evaluate_newton_step(
    const LinearAlgebra::distributed::Vector<number> &newton_step)
  {
    const unsigned int n_cells = this->data->n_cell_batches();
    FECellIntegrator   phi(*this->data);

    nonlinear_values.reinit(n_cells, phi.n_q_points);

    for (unsigned int cell = 0; cell < n_cells; ++cell)
      {
        phi.reinit(cell);
        phi.read_dof_values_plain(newton_step);
        phi.evaluate(EvaluationFlags::values);

        for (unsigned int q = 0; q < phi.n_q_points; ++q)
          {
            nonlinear_values(cell, q) = std::exp(phi.get_value(q));
          }
      }
  }

//  @sect4{Nonlinear matrix-free operator application}

// 现在在  local_apply()  函数中，实际上实现了系统矩阵的单元格动作，我们可以使用存储在表  nonlinear_values  中的最后一个牛顿步骤的信息。这个函数的其余部分与  step-37  中的基本相同。我们设置 FEEvaluation 对象，收集并评估输入向量的值和梯度  src  ，根据雅各布的形式提交值和梯度，最后调用  FEEvaluation::integrate_scatter()  进行单元积分，将局部贡献分配到全局向量   dst  。

  template <int dim, int fe_degree, typename number>
  void JacobianOperator<dim, fe_degree, number>::local_apply(
    const MatrixFree<dim, number> &                   data,
    LinearAlgebra::distributed::Vector<number> &      dst,
    const LinearAlgebra::distributed::Vector<number> &src,
    const std::pair<unsigned int, unsigned int> &     cell_range) const
  {
    FECellIntegrator phi(data);

    for (unsigned int cell = cell_range.first; cell < cell_range.second; ++cell)
      {
        AssertDimension(nonlinear_values.size(0),
                        phi.get_matrix_free().n_cell_batches());
        AssertDimension(nonlinear_values.size(1), phi.n_q_points);

        phi.reinit(cell);

        phi.gather_evaluate(src,
                            EvaluationFlags::values |
                              EvaluationFlags::gradients);

        for (unsigned int q = 0; q < phi.n_q_points; ++q)
          {
            phi.submit_value(-nonlinear_values(cell, q) * phi.get_value(q), q);
            phi.submit_gradient(phi.get_gradient(q), q);
          }

        phi.integrate_scatter(EvaluationFlags::values |
                                EvaluationFlags::gradients,
                              dst);
      }
  }

// 接下来我们使用 MatrixFree::cell_loop() 对所有单元进行实际循环，计算单元对矩阵-向量积的贡献。

  template <int dim, int fe_degree, typename number>
  void JacobianOperator<dim, fe_degree, number>::apply_add(
    LinearAlgebra::distributed::Vector<number> &      dst,
    const LinearAlgebra::distributed::Vector<number> &src) const
  {
    this->data->cell_loop(&JacobianOperator::local_apply, this, dst, src);
  }

//  @sect4{Diagonal of the JacobianOperator}

// 用于计算对角线的内部工作函数  local_compute_diagonal()  与上述工作函数  local_apply()  类似。然而，作为主要区别，我们不从输入向量中读取数值，也不将任何局部结果分配给输出向量。相反，唯一的输入参数是使用的FEEvaluation对象。

  template <int dim, int fe_degree, typename number>
  void JacobianOperator<dim, fe_degree, number>::local_compute_diagonal(
    FECellIntegrator &phi) const
  {
    AssertDimension(nonlinear_values.size(0),
                    phi.get_matrix_free().n_cell_batches());
    AssertDimension(nonlinear_values.size(1), phi.n_q_points);

    const unsigned int cell = phi.get_current_cell_index();

    phi.evaluate(EvaluationFlags::values | EvaluationFlags::gradients);

    for (unsigned int q = 0; q < phi.n_q_points; ++q)
      {
        phi.submit_value(-nonlinear_values(cell, q) * phi.get_value(q), q);
        phi.submit_gradient(phi.get_gradient(q), q);
      }

    phi.integrate(EvaluationFlags::values | EvaluationFlags::gradients);
  }

// 最后我们覆盖  MatrixFreeOperators::Base::compute_diagonal()  的基类的  JacobianOperator  的函数。虽然这个函数的名字表明只是计算对角线，但这个函数的作用更大。因为我们实际上只需要矩阵对角线元素的逆值，用于多网格预处理器的切比雪夫平滑器，我们计算对角线并存储逆值元素。因此我们首先初始化 inverse_diagonal_entries  。然后我们通过将工作函数 local_compute_diagonal() 传递给 MatrixFreeTools::compute_diagonal() 函数来计算对角线。最后，我们在对角线上循环，用手反转这些元素。注意，在这个循环过程中，我们捕捉受限的DOF，并手动将其设置为1。

  template <int dim, int fe_degree, typename number>
  void JacobianOperator<dim, fe_degree, number>::compute_diagonal()
  {
    this->inverse_diagonal_entries.reset(
      new DiagonalMatrix<LinearAlgebra::distributed::Vector<number>>());
    LinearAlgebra::distributed::Vector<number> &inverse_diagonal =
      this->inverse_diagonal_entries->get_vector();
    this->data->initialize_dof_vector(inverse_diagonal);

    MatrixFreeTools::compute_diagonal(*this->data,
                                      inverse_diagonal,
                                      &JacobianOperator::local_compute_diagonal,
                                      this);

    for (auto &diagonal_element : inverse_diagonal)
      {
        diagonal_element = (std::abs(diagonal_element) > 1.0e-10) ?
                             (1.0 / diagonal_element) :
                             1.0;
      }
  }

//  @sect3{GelfandProblem class}

// 在实现了无矩阵运算符之后，我们现在可以为Gelfand problem定义求解器类。这个类是基于之前所有教程程序的共同结构，特别是它是基于 step-15 ，解决的也是一个非线性问题。由于我们使用的是无矩阵框架，所以我们不再需要assemble_system函数，相反，在每次调用 vmult() 函数时都会重建矩阵的信息。然而，对于牛顿方案的应用，我们需要组装线性化问题的右手边并计算残差。因此，我们实现了一个额外的函数 evaluate_residual() ，后来我们在 assemble_rhs() and the compute_residual() 函数中调用了它。最后，这里典型的 solve() 函数实现了牛顿方法，而线性化系统的解是在 compute_update() 函数中计算的。由于MatrixFree框架将拉格朗日有限元方法的多项式程度作为一个模板参数来处理，我们也将其作为问题求解器类的模板参数来声明。

  template <int dim, int fe_degree>
  class GelfandProblem
  {
  public:
    GelfandProblem();

    void run();

  private:
    void make_grid();

    void setup_system();

    void evaluate_residual(
      LinearAlgebra::distributed::Vector<double> &      dst,
      const LinearAlgebra::distributed::Vector<double> &src) const;

    void local_evaluate_residual(
      const MatrixFree<dim, double> &                   data,
      LinearAlgebra::distributed::Vector<double> &      dst,
      const LinearAlgebra::distributed::Vector<double> &src,
      const std::pair<unsigned int, unsigned int> &     cell_range) const;

    void assemble_rhs();

    double compute_residual(const double alpha);

    void compute_update();

    void solve();

    double compute_solution_norm() const;

    void output_results(const unsigned int cycle) const;

// 对于并行计算，我们定义了一个  parallel::distributed::Triangulation.  由于计算域在二维是一个圆，在三维是一个球，我们除了为边界单元分配SphericalManifold外，还为内部单元的映射分配了一个TransfiniteInterpolationManifold对象，它负责处理内部单元的映射。在这个例子中，我们使用了一个等参数的有限元方法，因此使用了MappingQGeneric类。注意，我们也可以创建一个MappingQ类的实例，并在构造函数调用中设置 use_mapping_q_on_all_cells 标志为 true  。关于MappingQ和MappingQGeneric连接的进一步细节，你可以阅读这些类的详细描述。

    parallel::distributed::Triangulation<dim> triangulation;
    const MappingQGeneric<dim>                mapping;

// 像往常一样，我们接着定义拉格朗日有限元FE_Q和一个DoFHandler。

    FE_Q<dim>       fe;
    DoFHandler<dim> dof_handler;

// 对于线性化的离散系统，我们定义一个AffineConstraints对象和 system_matrix  ，在本例中它被表示为一个无矩阵算子。

    AffineConstraints<double> constraints;
    using SystemMatrixType = JacobianOperator<dim, fe_degree, double>;
    SystemMatrixType system_matrix;

// 多级对象也是基于雅各布系数的无矩阵算子。由于我们需要用最后一个牛顿步骤来评估雅各布，所以我们也需要用最后一个牛顿步骤来评估预处理器的水平算子。因此，除了 mg_matrices 之外，我们还需要一个MGLevelObject来存储每一级的插值解向量。与 step-37 一样，我们对预处理程序使用浮点精度。此外，我们将MGTransferMatrixFree对象定义为一个类变量，因为我们只需要在三角形变化时设置一次，然后可以在每个牛顿步骤中再次使用它。

    MGConstrainedDoFs mg_constrained_dofs;
    using LevelMatrixType = JacobianOperator<dim, fe_degree, float>;
    MGLevelObject<LevelMatrixType>                           mg_matrices;
    MGLevelObject<LinearAlgebra::distributed::Vector<float>> mg_solution;
    MGTransferMatrixFree<dim, float>                         mg_transfer;

// 当然，我们还需要持有  solution  ,  newton_update and the system_rhs  的向量。这样，我们就可以一直将上一个牛顿步存储在解的向量中，只需添加更新就可以得到下一个牛顿步。

    LinearAlgebra::distributed::Vector<double> solution;
    LinearAlgebra::distributed::Vector<double> newton_update;
    LinearAlgebra::distributed::Vector<double> system_rhs;

// 最后我们有一个变量，用来表示线性求解器的迭代次数。

    unsigned int linear_iterations;

// 对于与MPI并行运行的程序中的输出，我们使用ConditionalOStream类来避免不同MPI等级对同一数据的多次输出。

    ConditionalOStream pcout;

// 最后，对于时间测量，我们使用一个TimerOutput对象，它在程序结束后将每个函数的耗时CPU和墙体时间打印在一个格式良好的表格中。

    TimerOutput computing_timer;
  };

//  GelfandProblem 的构造函数初始化了类的变量。特别是，我们为 parallel::distributed::Triangulation, 设置了多级支持，将映射度设为有限元度，初始化ConditionalOStream，并告诉TimerOutput，我们只想在需求时看到墙体时间。

  template <int dim, int fe_degree>
  GelfandProblem<dim, fe_degree>::GelfandProblem()
    : triangulation(MPI_COMM_WORLD,
                    Triangulation<dim>::limit_level_difference_at_vertices,
                    parallel::distributed::Triangulation<
                      dim>::construct_multigrid_hierarchy)
    , mapping(fe_degree)
    , fe(fe_degree)
    , dof_handler(triangulation)
    , pcout(std::cout, Utilities::MPI::this_mpi_process(MPI_COMM_WORLD) == 0)
    , computing_timer(MPI_COMM_WORLD,
                      pcout,
                      TimerOutput::never,
                      TimerOutput::wall_times)
  {}

//  @sect4{GelfandProblem::make_grid}

// 作为计算域，我们使用 dim  -维的单位球。我们按照TransfiniteInterpolationManifold类的说明，也为边界指定了一个SphericalManifold。最后，我们将初始网格细化为3

// -  dim 次全局。

  template <int dim, int fe_degree>
  void GelfandProblem<dim, fe_degree>::make_grid()
  {
    TimerOutput::Scope t(computing_timer, "make grid");

    SphericalManifold<dim>                boundary_manifold;
    TransfiniteInterpolationManifold<dim> inner_manifold;

    GridGenerator::hyper_ball(triangulation);

    triangulation.set_all_manifold_ids_on_boundary(0);

    triangulation.set_manifold(0, boundary_manifold);

    inner_manifold.initialize(triangulation);
    triangulation.set_manifold(1, inner_manifold);

    triangulation.refine_global(3 - dim);
  }

//  @sect4{GelfandProblem::setup_system}

//  setup_system() 函数与  step-37  中的函数基本相同。唯一的区别显然是时间测量只有一个 TimerOutput::Scope ，而不是单独测量每个部分，更重要的是对前一个牛顿步骤的内插解向量的MGLevelObject的初始化。另一个重要的变化是MGTransferMatrixFree对象的设置，我们可以在每个牛顿步骤中重复使用它，因为 triangulation 不会被改变。

// 注意我们如何在 JacobianOperator 和多网格预处理程序中两次使用同一个MatrixFree对象。

  template <int dim, int fe_degree>
  void GelfandProblem<dim, fe_degree>::setup_system()
  {
    TimerOutput::Scope t(computing_timer, "setup system");

    system_matrix.clear();
    mg_matrices.clear_elements();

    dof_handler.distribute_dofs(fe);
    dof_handler.distribute_mg_dofs();

    IndexSet locally_relevant_dofs;
    DoFTools::extract_locally_relevant_dofs(dof_handler, locally_relevant_dofs);

    constraints.clear();
    constraints.reinit(locally_relevant_dofs);
    DoFTools::make_hanging_node_constraints(dof_handler, constraints);
    VectorTools::interpolate_boundary_values(dof_handler,
                                             0,
                                             Functions::ZeroFunction<dim>(),
                                             constraints);
    constraints.close();

    {
      typename MatrixFree<dim, double>::AdditionalData additional_data;
      additional_data.tasks_parallel_scheme =
        MatrixFree<dim, double>::AdditionalData::partition_color;
      additional_data.mapping_update_flags =
        (update_values | update_gradients | update_JxW_values |
         update_quadrature_points);
      auto system_mf_storage = std::make_shared<MatrixFree<dim, double>>();
      system_mf_storage->reinit(mapping,
                                dof_handler,
                                constraints,
                                QGauss<1>(fe.degree + 1),
                                additional_data);

      system_matrix.initialize(system_mf_storage);
    }

    system_matrix.initialize_dof_vector(solution);
    system_matrix.initialize_dof_vector(newton_update);
    system_matrix.initialize_dof_vector(system_rhs);

    const unsigned int nlevels = triangulation.n_global_levels();
    mg_matrices.resize(0, nlevels - 1);
    mg_solution.resize(0, nlevels - 1);

    std::set<types::boundary_id> dirichlet_boundary;
    dirichlet_boundary.insert(0);
    mg_constrained_dofs.initialize(dof_handler);
    mg_constrained_dofs.make_zero_boundary_constraints(dof_handler,
                                                       dirichlet_boundary);

    mg_transfer.initialize_constraints(mg_constrained_dofs);
    mg_transfer.build(dof_handler);

    for (unsigned int level = 0; level < nlevels; ++level)
      {
        IndexSet relevant_dofs;
        DoFTools::extract_locally_relevant_level_dofs(dof_handler,
                                                      level,
                                                      relevant_dofs);

        AffineConstraints<double> level_constraints;
        level_constraints.reinit(relevant_dofs);
        level_constraints.add_lines(
          mg_constrained_dofs.get_boundary_indices(level));
        level_constraints.close();

        typename MatrixFree<dim, float>::AdditionalData additional_data;
        additional_data.tasks_parallel_scheme =
          MatrixFree<dim, float>::AdditionalData::partition_color;
        additional_data.mapping_update_flags =
          (update_values | update_gradients | update_JxW_values |
           update_quadrature_points);
        additional_data.mg_level = level;
        auto mg_mf_storage_level = std::make_shared<MatrixFree<dim, float>>();
        mg_mf_storage_level->reinit(mapping,
                                    dof_handler,
                                    level_constraints,
                                    QGauss<1>(fe.degree + 1),
                                    additional_data);

        mg_matrices[level].initialize(mg_mf_storage_level,
                                      mg_constrained_dofs,
                                      level);
        mg_matrices[level].initialize_dof_vector(mg_solution[level]);
      }
  }

//  @sect4{GelfandProblem::evaluate_residual}

// 接下来我们实现一个函数，该函数对给定的输入向量评估非线性离散残差（  $\texttt{dst} = F(\texttt{src})$  ）。这个函数随后被用于组装线性化系统的右手边，随后用于计算下一个牛顿步骤的残差，以检查我们是否已经达到了误差容忍度。由于这个函数不应该影响任何类别的变量，我们把它定义为一个常数函数。在内部，我们通过FEEvaluation类和类似于 MatrixFree::cell_loop(), 的 apply_add() function of the JacobianOperator 来利用快速有限元评估。

// 首先我们创建一个指向MatrixFree对象的指针，它被存储在  system_matrix  中。然后，我们将用于残差的单元评估的工作函数  local_evaluate_residual()  以及输入和输出向量传递给  MatrixFree::cell_loop().  此外，我们在循环中启用输出向量的清零，这比之前单独调用dst = 0.0更有效率。

// 注意，使用这种方法，我们不必关心MPI相关的数据交换，因为所有的记账工作都是由  MatrixFree::cell_loop().  完成的。
  template <int dim, int fe_degree>
  void GelfandProblem<dim, fe_degree>::evaluate_residual(
    LinearAlgebra::distributed::Vector<double> &      dst,
    const LinearAlgebra::distributed::Vector<double> &src) const
  {
    auto matrix_free = system_matrix.get_matrix_free();

    matrix_free->cell_loop(
      &GelfandProblem::local_evaluate_residual, this, dst, src, true);
  }

//  @sect4{GelfandProblem::local_evaluate_residual}

// 这是用于评估残差的内部工作函数。本质上它与  JacobianOperator  的  local_apply()  函数具有相同的结构，在给定的单元格集合  cell_range  上对输入向量  src  进行残差评估。与上述 local_apply() 函数不同的是，我们将 FEEvaluation::gather_evaluate() 函数分成 FEEvaluation::read_dof_values_plain() 和 FEEvaluation::evaluate(), ，因为输入向量可能有受限的DOF。

  template <int dim, int fe_degree>
  void GelfandProblem<dim, fe_degree>::local_evaluate_residual(
    const MatrixFree<dim, double> &                   data,
    LinearAlgebra::distributed::Vector<double> &      dst,
    const LinearAlgebra::distributed::Vector<double> &src,
    const std::pair<unsigned int, unsigned int> &     cell_range) const
  {
    FEEvaluation<dim, fe_degree, fe_degree + 1, 1, double> phi(data);

    for (unsigned int cell = cell_range.first; cell < cell_range.second; ++cell)
      {
        phi.reinit(cell);

        phi.read_dof_values_plain(src);
        phi.evaluate(EvaluationFlags::values | EvaluationFlags::gradients);

        for (unsigned int q = 0; q < phi.n_q_points; ++q)
          {
            phi.submit_value(-std::exp(phi.get_value(q)), q);
            phi.submit_gradient(phi.get_gradient(q), q);
          }

        phi.integrate_scatter(EvaluationFlags::values |
                                EvaluationFlags::gradients,
                              dst);
      }
  }

//  @sect4{GelfandProblem::assemble_rhs}

// 使用上述函数 evaluate_residual() 来评估非线性残差，组装线性化系统的右手边现在变得非常容易。我们只需调用 evaluate_residual() 函数并将结果乘以减一。

// 经验表明，使用FEEvaluation类要比使用FEValues和co的经典实现快得多。

  template <int dim, int fe_degree>
  void GelfandProblem<dim, fe_degree>::assemble_rhs()
  {
    TimerOutput::Scope t(computing_timer, "assemble right hand side");

    evaluate_residual(system_rhs, solution);

    system_rhs *= -1.0;
  }

//  @sect4{GelfandProblem::compute_residual}

// 根据 step-15 ，下面的函数在 $u_h^n + \alpha s_h^n$ 函数的帮助下计算出解的非线性残差的规范。如果我们使用牛顿方法的自适应版本，牛顿步长 $\alpha$ 就变得很重要。例如，我们将计算不同步长的残差并比较残差。然而，对于我们的问题，使用 $\alpha=1$ 的完整牛顿步长是我们能做的最好的。如果我们没有好的初始值，牛顿方法的自适应版本就变得有趣了。请注意，在理论上，牛顿方法是以二次方顺序收敛的，但只有当我们有一个合适的初始值时才会收敛。对于不合适的初始值，牛顿方法甚至在二次方程下也会发散。一个常见的方法是使用阻尼版本 $\alpha<1$ ，直到牛顿步骤足够好，可以进行完整的牛顿步骤。这在  step-15  中也有讨论。

  template <int dim, int fe_degree>
  double GelfandProblem<dim, fe_degree>::compute_residual(const double alpha)
  {
    TimerOutput::Scope t(computing_timer, "compute residual");

    LinearAlgebra::distributed::Vector<double> residual;
    LinearAlgebra::distributed::Vector<double> evaluation_point;

    system_matrix.initialize_dof_vector(residual);
    system_matrix.initialize_dof_vector(evaluation_point);

    evaluation_point = solution;
    if (alpha > 1e-12)
      {
        evaluation_point.add(alpha, newton_update);
      }

    evaluate_residual(residual, evaluation_point);

    return residual.l2_norm();
  }

//  @sect4{GelfandProblem::compute_update}

// 为了计算每个牛顿步骤中的牛顿更新，我们用CG算法和一个几何多网格预处理程序来解决线性系统。为此，我们首先像在  step-37  中那样，用切比雪夫平滑器设置PreconditionMG对象。

  template <int dim, int fe_degree>
  void GelfandProblem<dim, fe_degree>::compute_update()
  {
    TimerOutput::Scope t(computing_timer, "compute update");

// 我们记得，雅各布系数取决于存储在解决方案向量中的最后一个牛顿步骤。所以我们更新牛顿步骤的鬼魂值，并将其传递给 JacobianOperator 来存储信息。

    solution.update_ghost_values();

    system_matrix.evaluate_newton_step(solution);

// 接下来我们还要将最后一个牛顿步骤传递给多级运算符。因此，我们需要将牛顿步骤插值到三角形的所有层面。这是用 MGTransferMatrixFree::interpolate_to_mg(). 来完成的。
    mg_transfer.interpolate_to_mg(dof_handler, mg_solution, solution);

// 现在我们可以设置预处理程序了。我们定义平滑器并将牛顿步的内插向量传递给多级运算器。

    using SmootherType =
      PreconditionChebyshev<LevelMatrixType,
                            LinearAlgebra::distributed::Vector<float>>;
    mg::SmootherRelaxation<SmootherType,
                           LinearAlgebra::distributed::Vector<float>>
                                                         mg_smoother;
    MGLevelObject<typename SmootherType::AdditionalData> smoother_data;
    smoother_data.resize(0, triangulation.n_global_levels() - 1);
    for (unsigned int level = 0; level < triangulation.n_global_levels();
         ++level)
      {
        if (level > 0)
          {
            smoother_data[level].smoothing_range     = 15.;
            smoother_data[level].degree              = 4;
            smoother_data[level].eig_cg_n_iterations = 10;
          }
        else
          {
            smoother_data[0].smoothing_range = 1e-3;
            smoother_data[0].degree          = numbers::invalid_unsigned_int;
            smoother_data[0].eig_cg_n_iterations = mg_matrices[0].m();
          }

        mg_matrices[level].evaluate_newton_step(mg_solution[level]);
        mg_matrices[level].compute_diagonal();

        smoother_data[level].preconditioner =
          mg_matrices[level].get_matrix_diagonal_inverse();
      }
    mg_smoother.initialize(mg_matrices, smoother_data);

    MGCoarseGridApplySmoother<LinearAlgebra::distributed::Vector<float>>
      mg_coarse;
    mg_coarse.initialize(mg_smoother);

    mg::Matrix<LinearAlgebra::distributed::Vector<float>> mg_matrix(
      mg_matrices);

    MGLevelObject<MatrixFreeOperators::MGInterfaceOperator<LevelMatrixType>>
      mg_interface_matrices;
    mg_interface_matrices.resize(0, triangulation.n_global_levels() - 1);
    for (unsigned int level = 0; level < triangulation.n_global_levels();
         ++level)
      {
        mg_interface_matrices[level].initialize(mg_matrices[level]);
      }
    mg::Matrix<LinearAlgebra::distributed::Vector<float>> mg_interface(
      mg_interface_matrices);

    Multigrid<LinearAlgebra::distributed::Vector<float>> mg(
      mg_matrix, mg_coarse, mg_transfer, mg_smoother, mg_smoother);
    mg.set_edge_matrices(mg_interface, mg_interface);

    PreconditionMG<dim,
                   LinearAlgebra::distributed::Vector<float>,
                   MGTransferMatrixFree<dim, float>>
      preconditioner(dof_handler, mg, mg_transfer);

// 最后我们设置了SolverControl和SolverCG来解决当前牛顿更新的线性化问题。实现SolverCG或SolverGMRES的一个重要事实是，持有线性系统解决方案的向量（这里是 newton_update  ）可以用来传递一个起始值。为了使迭代求解器总是以零向量开始，我们在调用 SolverCG::solve(). 之前明确地重置了 newton_update ，然后我们分配了存储在 constraints 中的Dirichlet边界条件，并为以后的输出存储了迭代的步数。

    SolverControl solver_control(100, 1.e-12);
    SolverCG<LinearAlgebra::distributed::Vector<double>> cg(solver_control);

    newton_update = 0.0;

    cg.solve(system_matrix, newton_update, system_rhs, preconditioner);

    constraints.distribute(newton_update);

    linear_iterations = solver_control.last_step();

// 然后，为了记账，我们将幽灵值清零。

    solution.zero_out_ghost_values();
  }

//  @sect4{GelfandProblem::solve}

// 现在我们实现非线性问题的实际牛顿求解器。

  template <int dim, int fe_degree>
  void GelfandProblem<dim, fe_degree>::solve()
  {
    TimerOutput::Scope t(computing_timer, "solve");

// 我们定义了牛顿步骤的最大数量和收敛标准的公差。通常情况下，如果有好的起始值，牛顿方法在三到六步内就能收敛，所以最大的十步应该是完全足够的。作为公差，我们使用 $\|F(u^n_h)\|<\text{TOL}_f = 10^{-12}$ 作为残差的规范， $\|s_h^n\| < \text{TOL}_x = 10^{-10}$ 作为牛顿更新的规范。这似乎有点过头了，但我们将看到，对于我们的例子，我们将在几步之后达到这些公差。

    const unsigned int itmax = 10;
    const double       TOLf  = 1e-12;
    const double       TOLx  = 1e-10;

    Timer solver_timer;
    solver_timer.start();

// 现在我们开始实际的牛顿迭代。

    for (unsigned int newton_step = 1; newton_step <= itmax; ++newton_step)
      {

// 我们将线性化问题的右侧集合起来，计算牛顿更新。

        assemble_rhs();
        compute_update();

// 然后，我们计算误差，即牛顿更新的规范和残差。注意，在这一点上，我们可以通过改变compute_residual函数的输入参数 $\alpha$ 来加入牛顿方法的步长控制。然而，在这里我们只是使用 $\alpha$ 等于1来进行普通的牛顿迭代。

        const double ERRx = newton_update.l2_norm();
        const double ERRf = compute_residual(1.0);

// 接下来我们通过将牛顿更新添加到当前的牛顿步骤中来推进牛顿步骤。

        solution.add(1.0, newton_update);

// 一个简短的输出将告知我们当前的牛顿步数。

        pcout << "   Nstep " << newton_step << ", errf = " << ERRf
              << ", errx = " << ERRx << ", it = " << linear_iterations
              << std::endl;

// 在每个牛顿步骤之后，我们检查收敛标准。如果其中至少有一个得到满足，我们就完成了，并结束循环。如果我们在牛顿迭代的最大数量之后还没有找到一个满意的解决方案，我们就会通知用户这个缺点。

        if (ERRf < TOLf || ERRx < TOLx)
          {
            solver_timer.stop();

            pcout << "Convergence step " << newton_step << " value " << ERRf
                  << " (used wall time: " << solver_timer.wall_time() << " s)"
                  << std::endl;

            break;
          }
        else if (newton_step == itmax)
          {
            solver_timer.stop();
            pcout << "WARNING: No convergence of Newton's method after "
                  << newton_step << " steps." << std::endl;

            break;
          }
      }
  }

//  @sect4{GelfandProblem::compute_solution_norm}

// 解的H1-seminorm的计算可以用与 step-59 相同的方法进行。我们更新幽灵值并使用函数  VectorTools::integrate_difference().  最后我们收集所有MPI行列的所有计算，并返回规范。

  template <int dim, int fe_degree>
  double GelfandProblem<dim, fe_degree>::compute_solution_norm() const
  {
    solution.update_ghost_values();

    Vector<float> norm_per_cell(triangulation.n_active_cells());

    VectorTools::integrate_difference(mapping,
                                      dof_handler,
                                      solution,
                                      Functions::ZeroFunction<dim>(),
                                      norm_per_cell,
                                      QGauss<dim>(fe.degree + 2),
                                      VectorTools::H1_seminorm);

    solution.zero_out_ghost_values();

    return VectorTools::compute_global_error(triangulation,
                                             norm_per_cell,
                                             VectorTools::H1_seminorm);
  }

//  @sect4{GelfandProblem::output_results}

// 我们通过调用  DataOut::write_vtu_with_pvtu_record()  函数，以与  step-37  中相同的方式，一次性生成 vtu 格式的图形输出文件和 pvtu 主文件。此外，与  step-40  一样，我们查询每个单元的  types::subdomain_id  并将三角形在MPI行列中的分布写进输出文件。最后，我们通过调用 DataOut::build_patches(). 生成解决方案的补丁。然而，由于我们的计算域有一个弯曲的边界，我们另外传递 mapping 和有限元度作为细分的数量。但这仍然不足以正确表示解决方案，例如在ParaView中，因为我们将TransfiniteInterpolationManifold附在内部单元上，这导致内部的单元是弯曲的。因此，我们将 DataOut::curved_inner_cells 选项作为第三个参数，这样，内部单元也会使用相应的流形描述来构建补丁。

// 注意，我们可以用标志 DataOutBase::VtkFlags::write_higher_order_cells. 来处理高阶元素，但是由于对ParaView以前版本的兼容性有限，而且VisIt也不支持，所以我们把这个选项留给未来的版本。

  template <int dim, int fe_degree>
  void
  GelfandProblem<dim, fe_degree>::output_results(const unsigned int cycle) const
  {
    if (triangulation.n_global_active_cells() > 1e6)
      return;

    solution.update_ghost_values();

    DataOut<dim> data_out;
    data_out.attach_dof_handler(dof_handler);
    data_out.add_data_vector(solution, "solution");

    Vector<float> subdomain(triangulation.n_active_cells());
    for (unsigned int i = 0; i < subdomain.size(); ++i)
      {
        subdomain(i) = triangulation.locally_owned_subdomain();
      }
    data_out.add_data_vector(subdomain, "subdomain");

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

    DataOutBase::VtkFlags flags;
    flags.compression_level = DataOutBase::VtkFlags::best_speed;
    data_out.set_flags(flags);
    data_out.write_vtu_with_pvtu_record(
      "./", "solution_" + std::to_string(dim) + "d", cycle, MPI_COMM_WORLD, 3);

    solution.zero_out_ghost_values();
  }

//  @sect4{GelfandProblem::run}

// Gelfand
 problem的求解器类的最后一个缺失的函数是运行函数。在开始的时候，我们打印关于系统规格和我们使用的有限元空间的信息。该问题在一个连续细化的网格上被多次求解。

  template <int dim, int fe_degree>
  void GelfandProblem<dim, fe_degree>::run()
  {
    {
      const unsigned int n_ranks =
        Utilities::MPI::n_mpi_processes(MPI_COMM_WORLD);
      const unsigned int n_vect_doubles = VectorizedArray<double>::size();
      const unsigned int n_vect_bits    = 8 * sizeof(double) * n_vect_doubles;

      std::string DAT_header = "START DATE: " + Utilities::System::get_date() +
                               ", TIME: " + Utilities::System::get_time();
      std::string MPI_header = "Running with " + std::to_string(n_ranks) +
                               " MPI process" + (n_ranks > 1 ? "es" : "");
      std::string VEC_header =
        "Vectorization over " + std::to_string(n_vect_doubles) +
        " doubles = " + std::to_string(n_vect_bits) + " bits (" +
        Utilities::System::get_current_vectorization_level() +
        "), VECTORIZATION_LEVEL=" +
        std::to_string(DEAL_II_COMPILER_VECTORIZATION_LEVEL);
      std::string SOL_header = "Finite element space: " + fe.get_name();

      pcout << std::string(80, '=') << std::endl;
      pcout << DAT_header << std::endl;
      pcout << std::string(80, '-') << std::endl;

      pcout << MPI_header << std::endl;
      pcout << VEC_header << std::endl;
      pcout << SOL_header << std::endl;

      pcout << std::string(80, '=') << std::endl;
    }

    for (unsigned int cycle = 0; cycle < 9 - dim; ++cycle)
      {
        pcout << std::string(80, '-') << std::endl;
        pcout << "Cycle " << cycle << std::endl;
        pcout << std::string(80, '-') << std::endl;

// 实际解决问题的第一项任务是生成或完善三角图。

        if (cycle == 0)
          {
            make_grid();
          }
        else
          {
            triangulation.refine_global(1);
          }

// 现在我们建立了系统并解决这个问题。这些步骤都伴随着时间测量和文本输出。

        Timer timer;

        pcout << "Set up system..." << std::endl;
        setup_system();

        pcout << "   Triangulation: " << triangulation.n_global_active_cells()
              << " cells" << std::endl;
        pcout << "   DoFHandler:    " << dof_handler.n_dofs() << " DoFs"
              << std::endl;
        pcout << std::endl;

        pcout << "Solve using Newton's method..." << std::endl;
        solve();
        pcout << std::endl;

        timer.stop();
        pcout << "Time for setup+solve (CPU/Wall) " << timer.cpu_time() << "/"
              << timer.wall_time() << " s" << std::endl;
        pcout << std::endl;

// 在问题被解决后，我们计算出解决方案的法线，并生成图形输出文件。

        pcout << "Output results..." << std::endl;
        const double norm = compute_solution_norm();
        output_results(cycle);

        pcout << "  H1 seminorm: " << norm << std::endl;
        pcout << std::endl;

// 最后在每个周期后，我们打印计时信息。

        computing_timer.print_summary();
        computing_timer.reset();
      }
  }
} // namespace Step66

//  @sect3{The main function}

// 作为使用MPI并行运行的典型程序，我们设置了MPI框架，并通过限制线程数为1来禁用共享内存并行化。最后，为了运行Gelfand problem的求解器，我们创建一个 GelfandProblem 类的对象并调用运行函数。例如，我们用四阶拉格朗日有限元在二维和三维中各解决一次问题。

int main(int argc, char *argv[])
{
  try
    {
      using namespace Step66;

      Utilities::MPI::MPI_InitFinalize mpi_init(argc, argv, 1);

      {
        GelfandProblem<2, 4> gelfand_problem;
        gelfand_problem.run();
      }

      {
        GelfandProblem<3, 4> gelfand_problem;
        gelfand_problem.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;
}


#include "mtf/AM/SSDBase.h"
#include "mtf/Utilities/miscUtils.h"
#include "mtf/Utilities/spiUtils.h"

#include <boost/random/random_device.hpp>
#include <boost/random/seed_seq.hpp>

_MTF_BEGIN_NAMESPACE

SSDBase::SSDBase(const AMParams *am_params, const int _n_channels) :
AppearanceModel(am_params, _n_channels), ilm(nullptr), likelihood_alpha(1),
I_diff(0, 0){
	if(am_params){
		ilm = am_params->ilm;
		likelihood_alpha = am_params->likelihood_alpha;
	}
	if(ilm){
		ilm->setPixHessType(ILMPixHessT::Constant);
		ilm_d2f_dIt_type = ilm->getPixHessType();
		state_size = ilm->getStateSize();
		if(ilm_d2f_dIt_type == ILMPixHessT::General){
			d2f_dIt2.resize(n_pix, n_pix);
		}
	}
	pix_norm_mult = 1;
	pix_norm_add = 0;
}

void SSDBase::initializeSimilarity(){
	if(is_initialized.similarity)
		return;

	df_dI0.resize(patch_size);
	new (&I_diff) VectorXdM(df_dI0.data(), patch_size);
	I_diff.fill(0);
	f = 0;
	if(ilm){
		It_orig.resize(patch_size);
		It_orig = It;
		p_am.resize(state_size);
		ilm->initialize(p_am.data());
	}
	is_initialized.similarity = true;
}

void SSDBase::initializeGrad(){
	if(is_initialized.grad)
		return;

	df_dIt.resize(patch_size);
	df_dIt = df_dI0;

	if(ilm){
		df_dpam.resize(state_size);
		df_dgt.resize(patch_size);
		df_dg0.resize(patch_size);
		df_dg0 = df_dI0;
		df_dgt = df_dIt;
		ilm->cmptParamJacobian(df_dpam.data(), df_dgt.data(), It_orig.data(), p_am.data());
		ilm->cmptPixJacobian(df_dIt.data(), df_dgt.data(), It_orig.data(), p_am.data());
	}
	is_initialized.grad = true;
}
double SSDBase::getLikelihood() const{
	/**
	since SSD can be numerically too large for exponential function to work,
	we take the square root of the per pixel SSD instead;
	it is further normalized by dividing with the range of pixel values to avoid
	very small numbers that may lead to loss of information due to the limited precision
	of floating point operations;
	*/
	return exp(-likelihood_alpha * sqrt(-f / (static_cast<double>(patch_size))));
}

void SSDBase::updateSimilarity(bool prereq_only){
	if(ilm){
		//! I_t actually refers to g(I_t, p_am) to keep the illumination model transparent
		//! and avoid any unnecessary runtime costs if it is not used
		It_orig = It;
		ilm->apply(It.data(), It_orig.data(), p_am.data());
	}
	I_diff = It - I0;
	if(prereq_only){ return; }
#ifndef DISABLE_SPI
	if(spi_mask){
		VectorXd masked_err_vec = Map<const VectorXb>(spi_mask, n_pix).select(I_diff, 0);
		f = -masked_err_vec.squaredNorm() / 2;
	} else{
#endif
		f = -I_diff.squaredNorm() / 2;
#ifndef DISABLE_SPI
	}
#endif
	//utils::printMatrix(p_am.transpose(), "p_am");
	//printf("f: %f\n", f);
}
void SSDBase::updateState(const VectorXd& state_update){
	if(ilm){
		assert(state_update.size() == state_size);
		VectorXd p_am_old = p_am;
		ilm->update(p_am.data(), p_am_old.data(), state_update.data());
		//utils::printMatrix(state_update.transpose(), "state_update");
		//utils::printMatrix(p_am_old.transpose(), "p_am before update");
		//utils::printMatrix(p_am.transpose(), "p_am after update");
	}
}
void SSDBase::invertState(VectorXd& inv_p, const VectorXd& p){
	if(ilm){
		assert(inv_p.size() == state_size && p.size() == state_size);
		ilm->invert(inv_p.data(), p.data());
	}
}

// curr_grad is same as negative of init_grad
void SSDBase::updateCurrGrad(){
	df_dIt = -df_dI0;
	if(ilm){
		df_dgt = df_dIt;
		ilm->cmptPixJacobian(df_dIt.data(), df_dgt.data(), It_orig.data(), p_am.data());
	}
}

void SSDBase::cmptInitJacobian(RowVectorXd &df_dp,
	const MatrixXd &dI0_dpssm){
	assert(df_dp.size() == state_size + dI0_dpssm.cols());
	assert(dI0_dpssm.rows() == patch_size);
	if(ilm){
		df_dp.head(dI0_dpssm.cols()).noalias() = df_dI0 * dI0_dpssm;
		ilm->cmptParamJacobian(df_dpam.data(), df_dg0.data(),
			I0.data(), p_am.data());
		df_dp.tail(state_size) = df_dpam;
	} else{
#ifndef DISABLE_SPI
		if(spi_mask){
			getJacobian(df_dp, spi_mask, df_dI0, dI0_dpssm);
		} else{
#endif
			df_dp.noalias() = df_dI0 * dI0_dpssm;
#ifndef DISABLE_SPI
		}
#endif
	}
}
void SSDBase::cmptCurrJacobian(RowVectorXd &df_dp,
	const MatrixXd &dIt_dpssm){
	assert(df_dp.size() == state_size + dIt_dpssm.cols());
	assert(dIt_dpssm.rows() == patch_size);

	if(ilm){
		df_dp.head(dIt_dpssm.cols()).noalias() = df_dIt * dIt_dpssm;
		ilm->cmptParamJacobian(df_dpam.data(), df_dgt.data(),
			It_orig.data(), p_am.data());
		df_dp.tail(state_size) = df_dpam;
	} else{
#ifndef DISABLE_SPI
		if(spi_mask){
			getJacobian(df_dp, spi_mask, df_dIt, dIt_dpssm);
		} else{
#endif
			//printf("df_dp: %ld x %ld\n", df_dp.rows(), df_dp.cols());
			//printf("df_dIt: %ld x %ld\n", df_dIt.rows(), df_dIt.cols());
			//printf("dIt_dpssm: %ld x %ld\n", dIt_dpssm.rows(), dIt_dpssm.cols());
			df_dp.noalias() = df_dIt * dIt_dpssm;
#ifndef DISABLE_SPI
		}
#endif
	}
}
void SSDBase::cmptDifferenceOfJacobians(RowVectorXd &df_dp_diff,
	const MatrixXd &dI0_dpssm, const MatrixXd &dIt_dpssm){
	assert(df_dp_diff.size() == state_size + dIt_dpssm.cols());
	assert(dI0_dpssm.cols() == dIt_dpssm.cols());
	assert(dIt_dpssm.rows() == patch_size && dI0_dpssm.rows() == patch_size);
	if(ilm){
		df_dp_diff.head(dIt_dpssm.cols()).noalias() = df_dIt * (dI0_dpssm + dIt_dpssm);
		VectorXd df_dpam_0(state_size), df_dpam_t(state_size);
		ilm->cmptParamJacobian(df_dpam_0.data(), df_dg0.data(), I0.data(), p_am.data());
		ilm->cmptParamJacobian(df_dpam_t.data(), df_dgt.data(), It_orig.data(), p_am.data());
		df_dp_diff.tail(state_size) = df_dpam_t - df_dpam_0;
	} else{
#ifndef DISABLE_SPI
		if(spi_mask){
			getDifferenceOfJacobians(df_dp_diff, spi_mask, dI0_dpssm, dIt_dpssm);
		} else{
#endif
			df_dp_diff.noalias() = df_dIt * (dI0_dpssm + dIt_dpssm);
#ifndef DISABLE_SPI
		}
#endif
	}
}

void SSDBase::cmptILMHessian(MatrixXd &d2f_dp2, const MatrixXd &dI_dpssm,
	const double* I, const double* df_dg){
	int ssm_state_size = static_cast<int>(dI_dpssm.cols());

	assert(d2f_dp2.rows() == state_size + ssm_state_size);
	assert(d2f_dp2.cols() == state_size + ssm_state_size);

	MatrixXd d2f_dpam2(state_size, state_size);
	ilm->cmptParamHessian(d2f_dpam2.data(), nullptr, df_dg, I, p_am.data());
	d2f_dp2.bottomRightCorner(state_size, state_size) = -d2f_dpam2;
	switch(ilm_d2f_dIt_type){
	case  ILMPixHessT::Constant:
	{
		double d2f_dIt2_const;
		if(df_dg){
			ilm->cmptPixHessian(&d2f_dIt2_const, nullptr, df_dg, I, p_am.data());
		} else{
			ilm->cmptPixHessian(&d2f_dIt2_const, nullptr, I, p_am.data());
		}

		d2f_dp2.topLeftCorner(ssm_state_size, ssm_state_size).noalias() = -d2f_dIt2_const * dI_dpssm.transpose() * dI_dpssm;
		break;
	}
	case  ILMPixHessT::Diagonal:
	{
		VectorXd d2f_dIt2_diag(patch_size);
		if(df_dg){
			ilm->cmptPixHessian(d2f_dIt2_diag.data(), nullptr, df_dg, I, p_am.data());
		} else{
			ilm->cmptPixHessian(d2f_dIt2_diag.data(), nullptr, I, p_am.data());
		}
		d2f_dp2.topLeftCorner(ssm_state_size, ssm_state_size).noalias() =
			-(dI_dpssm.array().colwise() * d2f_dIt2_diag.array()).matrix().transpose() * dI_dpssm;
		break;
	}
	case  ILMPixHessT::General:
	{
		if(df_dg){
			ilm->cmptPixHessian(d2f_dIt2.data(), nullptr, df_dg, I, p_am.data());
		} else{
			ilm->cmptPixHessian(d2f_dIt2.data(), nullptr, I, p_am.data());
		}
		d2f_dp2.topLeftCorner(ssm_state_size, ssm_state_size).noalias() = -dI_dpssm.transpose() * d2f_dIt2 * dI_dpssm;
		break;
	}
	default:
		throw utils::InvalidArgument(
			cv::format("SSDBase :: ILM has invalid hessian type provided: %d", ilm_d2f_dIt_type));
	}
	if(df_dg){
		ilm->cmptCrossHessian(d2f_dpam_dIt.data(), nullptr, df_dg, I, p_am.data());
	} else{
		ilm->cmptCrossHessian(d2f_dpam_dIt.data(), nullptr, I, p_am.data());
	}
	d2f_dp2.topRightCorner(ssm_state_size, state_size).transpose() =
		d2f_dp2.bottomLeftCorner(state_size, ssm_state_size) = -d2f_dpam_dIt*dI_dpssm;
}

void SSDBase::cmptInitHessian(MatrixXd &d2f_dp2, const MatrixXd &dI0_dpssm){
	assert(d2f_dp2.rows() == dI0_dpssm.cols() + state_size && d2f_dp2.rows() == d2f_dp2.cols());
	assert(dI0_dpssm.rows() == patch_size);
	if(ilm){
		cmptILMHessian(d2f_dp2, dI0_dpssm, I0.data());
	} else{
#ifndef DISABLE_SPI
		if(spi_mask){
			getHessian(d2f_dp2, spi_mask, dI0_dpssm);
		} else{
#endif
			d2f_dp2.noalias() = -dI0_dpssm.transpose() * dI0_dpssm;
#ifndef DISABLE_SPI
		}
#endif
	}
}
void SSDBase::cmptCurrHessian(MatrixXd &d2f_dp2,
	const MatrixXd &dIt_dpssm){
	assert(d2f_dp2.rows() == dIt_dpssm.cols() + state_size && d2f_dp2.rows() == d2f_dp2.cols());
	assert(dIt_dpssm.rows() == patch_size);
	if(ilm){
		cmptILMHessian(d2f_dp2, dIt_dpssm, It_orig.data());
	} else{
#ifndef DISABLE_SPI
		if(spi_mask){
			getHessian(d2f_dp2, spi_mask, dIt_dpssm);
		} else{
#endif
			d2f_dp2.noalias() = -dIt_dpssm.transpose() * dIt_dpssm;
#ifndef DISABLE_SPI
		}
#endif
	}
}
//! analogous to cmptDifferenceOfJacobians except for computing the difference between the current and initial Hessians
void SSDBase::cmptSumOfHessians(MatrixXd &d2f_dp2_sum,
	const MatrixXd &dI0_dpssm,	const MatrixXd &dIt_dpssm){
	assert(d2f_dp2_sum.cols() == d2f_dp2_sum.rows());
	assert(dI0_dpssm.rows() == patch_size && dIt_dpssm.rows() == patch_size);

	if(ilm){
		MatrixXd d2f_dp2t(d2f_dp2_sum.rows(), d2f_dp2_sum.cols());
		cmptILMHessian(d2f_dp2t, dIt_dpssm, It_orig.data());
		MatrixXd d2f_dp20(d2f_dp2_sum.rows(), d2f_dp2_sum.cols());
		cmptILMHessian(d2f_dp20, dI0_dpssm, I0.data());
		d2f_dp2_sum.noalias() = d2f_dp2t + d2f_dp20;
	} else{
#ifndef DISABLE_SPI
		if(spi_mask){
			getSumOfHessians(d2f_dp2_sum, spi_mask,
				dI0_dpssm, dIt_dpssm);
		} else{
#endif
			d2f_dp2_sum.noalias() = -(dI0_dpssm.transpose() * dI0_dpssm
				+ dIt_dpssm.transpose() * dIt_dpssm);
#ifndef DISABLE_SPI
		}
#endif
	}
}

void SSDBase::cmptInitHessian(MatrixXd &d2f_dp2, const MatrixXd &dI0_dpssm,
	const MatrixXd &d2I0_dpssm2){
	int ssm_state_size = static_cast<int>(d2f_dp2.rows());
	assert(d2f_dp2.cols() == ssm_state_size + state_size);
	assert(d2I0_dpssm2.rows() == ssm_state_size * ssm_state_size && d2I0_dpssm2.cols() == n_channels*n_pix);

	assert(d2f_dp2.rows() == dI0_dpssm.cols() + state_size && d2f_dp2.rows() == d2f_dp2.cols());
	assert(dI0_dpssm.rows() == patch_size);
	if(ilm){
		cmptILMHessian(d2f_dp2, dI0_dpssm, I0.data(), df_dg0.data());
	} else{
#ifndef DISABLE_SPI
		if(spi_mask){
			getHessian(d2f_dp2, spi_mask, dI0_dpssm);
		} else{
#endif
			d2f_dp2.noalias() = -dI0_dpssm.transpose() * dI0_dpssm;
#ifndef DISABLE_SPI
		}
#endif
	}
	int ch_pix_id = 0;
	for(unsigned int pix_id = 0; pix_id < n_pix; ++pix_id){
		spi_pt_check_mc(spi_mask, pix_id, ch_pix_id);
		for(unsigned int channel_id = 0; channel_id < n_channels; ++channel_id){
			d2f_dp2 += Map<const MatrixXd>(d2I0_dpssm2.col(ch_pix_id).data(), ssm_state_size, ssm_state_size) * df_dI0(ch_pix_id);
			++ch_pix_id;
		}
	}
}

void SSDBase::cmptCurrHessian(MatrixXd &d2f_dp2, const MatrixXd &dIt_dpssm,
	const MatrixXd &d2It_dpssm2){
	int ssm_state_size = static_cast<int>(d2f_dp2.rows());

	assert(d2f_dp2.cols() == ssm_state_size + state_size);
	assert(d2It_dpssm2.rows() == ssm_state_size * ssm_state_size && d2It_dpssm2.cols() == n_channels * n_pix);

	assert(d2f_dp2.rows() == dIt_dpssm.cols() + state_size && d2f_dp2.rows() == d2f_dp2.cols());
	assert(dIt_dpssm.rows() == patch_size);
	if(ilm){
		cmptILMHessian(d2f_dp2, dIt_dpssm, It_orig.data(), df_dgt.data());
	} else{
#ifndef DISABLE_SPI
		if(spi_mask){
			getHessian(d2f_dp2, spi_mask, dIt_dpssm);
		} else{
#endif
			d2f_dp2.noalias() = -dIt_dpssm.transpose() * dIt_dpssm;
#ifndef DISABLE_SPI
		}
#endif
	}
	int ch_pix_id = 0;
	for(unsigned int pix_id = 0; pix_id < n_pix; ++pix_id){
		spi_pt_check_mc(spi_mask, pix_id, ch_pix_id);
		for(unsigned int channel_id = 0; channel_id < n_channels; ++channel_id){
			d2f_dp2 += Map<const MatrixXd>(d2It_dpssm2.col(ch_pix_id).data(), ssm_state_size, ssm_state_size) * df_dIt(ch_pix_id);
			++ch_pix_id;
		}
	}
}

void SSDBase::cmptSumOfHessians(MatrixXd &d2f_dp2_sum,
	const MatrixXd &dI0_dpssm, const MatrixXd &dIt_dpssm,
	const MatrixXd &d2I0_dpssm2, const MatrixXd &d2It_dpssm2){

	int ssm_state_size = static_cast<int>(d2f_dp2_sum.rows());
	assert(d2f_dp2_sum.cols() == ssm_state_size);
	assert(d2I0_dpssm2.rows() == ssm_state_size * ssm_state_size && d2I0_dpssm2.cols() == n_channels * n_pix);
	assert(d2It_dpssm2.rows() == ssm_state_size * ssm_state_size && d2It_dpssm2.cols() == n_channels * n_pix);

	if(ilm){
		MatrixXd d2f_dp2t(d2f_dp2_sum.rows(), d2f_dp2_sum.cols());
		cmptILMHessian(d2f_dp2t, dIt_dpssm, It_orig.data(), df_dgt.data());
		MatrixXd d2f_dp20(d2f_dp2_sum.rows(), d2f_dp2_sum.cols());
		cmptILMHessian(d2f_dp20, dI0_dpssm, I0.data(), df_dgt.data());
		d2f_dp2_sum.noalias() = d2f_dp2t + d2f_dp20;
	} else{
#ifndef DISABLE_SPI
		if(spi_mask){
			getSumOfHessians(d2f_dp2_sum, spi_mask,
				dI0_dpssm, dIt_dpssm);
		} else{
#endif
			d2f_dp2_sum.noalias() = -(dI0_dpssm.transpose() * dI0_dpssm
				+ dIt_dpssm.transpose() * dIt_dpssm);
#ifndef DISABLE_SPI
		}
#endif
	}

	int ch_pix_id = 0;
	for(unsigned int pix_id = 0; pix_id < n_pix; ++pix_id){
		spi_pt_check_mc(spi_mask, pix_id, ch_pix_id);
		for(unsigned int channel_id = 0; channel_id < n_channels; ++channel_id){
			d2f_dp2_sum += df_dI0(pix_id)*
				(Map<const MatrixXd>(d2I0_dpssm2.col(ch_pix_id).data(), ssm_state_size, ssm_state_size)
				+ Map<const MatrixXd>(d2It_dpssm2.col(ch_pix_id).data(), ssm_state_size, ssm_state_size));
			++ch_pix_id;
		}
	}
}

void SSDBase::getJacobian(RowVectorXd &jacobian, const bool *pix_mask,
	const RowVectorXd &df_dI, const MatrixXd &dI_dpssm){
	assert(dI_dpssm.rows() == patch_size && dI_dpssm.rows() == jacobian.size());
	jacobian.setZero();
	int ch_pix_id = 0;
	for(unsigned int pix_id = 0; pix_id < n_pix; ++pix_id){
		spi_check_mc(pix_mask, pix_id, ch_pix_id);
		for(unsigned int channel_id = 0; channel_id < n_channels; ++channel_id){
			jacobian += df_dI[ch_pix_id] * dI_dpssm.row(ch_pix_id);
			++ch_pix_id;
		}

	}
}

void SSDBase::getDifferenceOfJacobians(RowVectorXd &diff_of_jacobians, const bool *pix_mask,
	const MatrixXd &dI0_dpssm, const MatrixXd &dIt_dpssm){
	assert(dI0_dpssm.rows() == n_channels * n_pix && dIt_dpssm.rows() == n_channels * n_pix);
	assert(dI0_dpssm.rows() == diff_of_jacobians.size());

	diff_of_jacobians.setZero();
	int ch_pix_id = 0;
	for(unsigned int pix_id = 0; pix_id < n_pix; ++pix_id){
		spi_check_mc(pix_mask, pix_id, ch_pix_id);
		for(unsigned int channel_id = 0; channel_id < n_channels; ++channel_id){
			diff_of_jacobians += df_dIt[ch_pix_id] *
				(dI0_dpssm.row(ch_pix_id) + dIt_dpssm.row(ch_pix_id));
			++ch_pix_id;
		}

	}
}

void SSDBase::getHessian(MatrixXd &d2f_dp2, const bool *pix_mask, const MatrixXd &dI_dpssm){
	assert(dI_dpssm.rows() == n_channels * n_pix);

	d2f_dp2.setZero();
	int ch_pix_id = 0;
	for(unsigned int pix_id = 0; pix_id < n_pix; ++pix_id){
		spi_check_mc(pix_mask, pix_id, ch_pix_id);
		for(unsigned int channel_id = 0; channel_id < n_channels; ++channel_id){
			d2f_dp2 -= dI_dpssm.row(ch_pix_id).transpose()*dI_dpssm.row(ch_pix_id);
			++ch_pix_id;
		}
	}
}

void SSDBase::getSumOfHessians(MatrixXd &d2f_dp2, const bool *pix_mask,
	const MatrixXd &dI0_dpssm, const MatrixXd &dIt_dpssm){
	assert(dI0_dpssm.rows() == n_channels * n_pix && dIt_dpssm.rows() == n_channels * n_pix);
	assert(d2f_dp2.rows() == d2f_dp2.cols() && d2f_dp2.rows() == dI0_dpssm.cols());

	d2f_dp2.setZero();
	unsigned int ch_pix_id = 0;
	for(unsigned int pix_id = 0; pix_id < n_pix; ++pix_id){
		spi_check_mc(pix_mask, pix_id, ch_pix_id);
		for(unsigned int channel_id = 0; channel_id < n_channels; ++channel_id){
			d2f_dp2 -= dI0_dpssm.row(ch_pix_id).transpose()*dI0_dpssm.row(ch_pix_id)
				+ dIt_dpssm.row(ch_pix_id).transpose()*dIt_dpssm.row(ch_pix_id);
			++ch_pix_id;
		}
	}
}

// -------------------------------------------------------------------------- //
// --------------------------- Stochastic Sampler --------------------------- //
// -------------------------------------------------------------------------- //

void SSDBase::initializeSampler(const VectorXd &_state_sigma,
	const VectorXd &_state_mean){
	if(!ilm){ return; }

	VectorXd state_sigma(state_size), state_mean(state_size);
	ilm->parseSamplerSigma(state_sigma, _state_sigma);
	ilm->parseSamplerMean(state_mean, _state_mean);

	printf("Initializing %s sampler with sigma: ",
		ilm ? (name + "/" + ilm->name).c_str() : name.c_str());
	utils::printMatrix(state_sigma.transpose(), nullptr, "%e");

	state_perturbation.resize(state_size);
	rand_gen.resize(state_size);
	rand_dist.resize(state_size);

	boost::random_device r;
	for(int state_id = 0; state_id < state_size; state_id++) {
		boost::random::seed_seq seed{ r(), r(), r(), r(), r(), r(), r(), r() };
		rand_gen[state_id] = SampleGenT(seed);
		rand_dist[state_id] = SampleDistT(state_mean[state_id], state_sigma[state_id]);
	}
	is_initialized.sampler = true;
}

void SSDBase::setSampler(const VectorXd &_state_sigma,
	const VectorXd &_state_mean){
	if(!ilm){ return; }
	VectorXd state_sigma(state_size), state_mean(state_size);
	ilm->parseSamplerSigma(state_sigma, _state_sigma);
	ilm->parseSamplerMean(state_mean, _state_mean);
	for(int state_id = 0; state_id < state_size; state_id++){
		rand_dist[state_id].param(DistParamT(state_mean[state_id], state_sigma[state_id]));
	}
}

void SSDBase::setSamplerMean(const VectorXd &_state_mean){
	if(!ilm){ return; }
	VectorXd state_mean(state_size);
	ilm->parseSamplerMean(state_mean, _state_mean);
	for(int state_id = 0; state_id < state_size; state_id++){
		double state_sigma = rand_dist[state_id].sigma();
		rand_dist[state_id].param(DistParamT(state_mean[state_id], state_sigma));
	}
}
void SSDBase::setSamplerSigma(const VectorXd &_state_sigma){
	VectorXd state_sigma(state_size);
	ilm->parseSamplerSigma(state_sigma, _state_sigma);
	for(int state_id = 0; state_id < state_size; state_id++){
		double mean = rand_dist[state_id].mean();
		rand_dist[state_id].param(DistParamT(mean, state_sigma[state_id]));
	}
}

void SSDBase::getSamplerSigma(VectorXd &std){
	if(!ilm){ return; }
	assert(std.size() == state_size);
	for(int state_id = 0; state_id < state_size; state_id++){
		std(state_id) = rand_dist[state_id].sigma();
	}
}
void SSDBase::getSamplerMean(VectorXd &mean){
	if(!ilm){ return; }
	assert(mean.size() == state_size);
	for(int state_id = 0; state_id < state_size; state_id++){
		mean(state_id) = rand_dist[state_id].mean();
	}
}

void SSDBase::generatePerturbation(VectorXd &perturbation){
	if(!ilm){ return; }
	assert(perturbation.size() == state_size);
	for(int state_id = 0; state_id < state_size; state_id++){
		perturbation(state_id) = rand_dist[state_id](rand_gen[state_id]);
	}
}

/**
* Squared Euclidean distance functor, optimized version
*/
/**
*  Compute the squared Euclidean distance between two vectors.
*
*	This is highly optimized, with loop unrolling, as it is one
*	of the most expensive inner loops.
*
*	The computation of squared root at the end is omitted for
*	efficiency.
*/
double SSDBaseDist::operator()(const double* a, const double* b,
	size_t size, double worst_dist) const{
	double result = 0;
	double diff0, diff1, diff2, diff3;
	const double* last = a + size;
	const double* lastgroup = last - 3;

	/* Process 4 items with each loop for efficiency. */
	while(a < lastgroup){
		diff0 = (a[0] - b[0]);
		diff1 = (a[1] - b[1]);
		diff2 = (a[2] - b[2]);
		diff3 = (a[3] - b[3]);
		result += diff0 * diff0 + diff1 * diff1 + diff2 * diff2 + diff3 * diff3;
		a += 4;
		b += 4;

		if((worst_dist > 0) && (result > worst_dist)){
			return result;
		}
	}
	/* Process last 0-3 pixels.  Not needed for standard vector lengths. */
	while(a < last){
		diff0 = (*a++ - *b++);
		result += diff0 * diff0;
	}
	return result;
}

_MTF_END_NAMESPACE


#pragma once
#include <memory>
#include <boost/serialization/vector.hpp>
#include <boost/serialization/shared_ptr.hpp>
#include "optimizer/NeuralNetworkOptimizer.hpp"
#include "layer/LayerModel.hpp"
#include "layer/FullyConnected.hpp"
#include "layer/Convolution1D.hpp"
#include "layer/Convolution2D.hpp"
#include "StatisticAnalysis.hpp"
#include "layer/BaseLayer.hpp"
#include "layer/Recurrence.hpp"
#include "layer/GruLayer.hpp"
#include "layer/LocallyConnected1D.hpp"
#include "layer/LocallyConnected2D.hpp"

namespace snn {
    struct NeuralNetworkOptimizerModel;
}

namespace snn::internal
{
    class NeuralNetwork : public StatisticAnalysis
    {
    private:
        static bool isTheFirst; // TODO: remplace by seed
        static void initialize();

        bool outputNan = false;

        void backpropagationAlgorithm(const std::vector<float>& inputs, const std::vector<float>& desired,
                                      bool temporalReset);
        std::vector<float> calculateError(const std::vector<float>& outputs, const std::vector<float>& desired) const;

        friend class boost::serialization::access;
        template <class Archive>
        void serialize(Archive& ar, unsigned version);

    protected:
        std::vector<float> output(const std::vector<float>& inputs, bool temporalReset);
        std::vector<float> outputForBackpropagation(const std::vector<float>& inputs, bool temporalReset); // Because Dropout is different during training and inference

        void evaluateOnceForRegression(const std::vector<float>& inputs,
                                       const std::vector<float>& desired,
                                       float precision,
                                       bool temporalReset);
        void evaluateOnceForMultipleClassification(const std::vector<float>& inputs,
                                                   const std::vector<float>& desired,
                                                   float separator,
                                                   bool temporalReset);
        void evaluateOnceForClassification(const std::vector<float>& inputs,
                                           int classNumber,
                                           const float separator,
                                           bool temporalReset);

    public:
        NeuralNetwork() = default; // use restricted to Boost library only
        NeuralNetwork(std::vector<LayerModel>& architecture, NeuralNetworkOptimizerModel optimizer);
        NeuralNetwork(const NeuralNetwork& neuralNetwork);
        virtual ~NeuralNetwork() = default;

        [[nodiscard]] bool hasNan() const;
        [[nodiscard]] int getNumberOfLayers() const;
        [[nodiscard]] int getNumberOfInputs() const;
        [[nodiscard]] int getNumberOfOutputs() const;
        [[nodiscard]] int getNumberOfNeurons() const;
        [[nodiscard]] int getNumberOfParameters() const;

        std::shared_ptr<NeuralNetworkOptimizer> optimizer = nullptr;

        std::vector<std::unique_ptr<BaseLayer>> layers{};

        [[nodiscard]] int isValid() const;

        void trainOnce(const std::vector<float>& inputs, const std::vector<float>& desired, bool temporalReset = false);

        bool operator==(const NeuralNetwork& neuralNetwork) const;
        bool operator!=(const NeuralNetwork& neuralNetwork) const;
    };

    template <class Archive>
    void NeuralNetwork::serialize(Archive& ar, unsigned version)
    {
        if (isTheFirst)
            this->initialize();
        boost::serialization::void_cast_register<NeuralNetwork, StatisticAnalysis>();
        ar & boost::serialization::base_object<StatisticAnalysis>(*this);
        ar.template register_type<FullyConnected>();
        ar.template register_type<Recurrence>();
        ar.template register_type<GruLayer>();
        ar.template register_type<Convolution1D>();
        ar.template register_type<Convolution2D>();
        ar.template register_type<LocallyConnected1D>();
        ar.template register_type<LocallyConnected2D>();
        ar & layers;
        ar.template register_type<StochasticGradientDescent>();
        ar & this->optimizer;
    }
}


#define BOOST_TEST_MODULE "test_implicit_membrane_potential"

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

#include <mjolnir/forcefield/external/ImplicitMembranePotential.hpp>

BOOST_AUTO_TEST_CASE(ImplicitMembranePotential_double)
{
    using real_type = double;
    constexpr static std::size_t N = 10000;
    constexpr static real_type h   = 1e-6;
    constexpr static real_type tol = 1e-5;

    const real_type thickness = 10.0;
    const real_type magnitude = 1.0;
    const real_type bend = 1.5;
    const std::vector<std::pair<std::size_t, real_type>> hydrophobicities{
        {0, 1.}, {1, 0.}
    };

    mjolnir::ImplicitMembranePotential<real_type>
        im(thickness, magnitude, bend, 4.0, hydrophobicities);

    const real_type cutoff_length = im.max_cutoff_length();
    const real_type z_min = -1 * cutoff_length;
    const real_type z_max = cutoff_length;
    const real_type dz = (z_max - z_min) / N;

    real_type z = z_min;
    for(std::size_t i = 0; i < N; ++i)
    {
        const real_type pot1 = im.potential(0, z + h);
        const real_type pot2 = im.potential(0, z - h);
        const real_type dpot = (pot1 - pot2) / (2 * h);
        const real_type deri = im.derivative(0, z);

        if(std::abs(z) > h)
        {
            if(std::abs(deri) > tol)
            {
                BOOST_TEST(dpot == deri, boost::test_tools::tolerance(tol));
            }
            else
            {
                BOOST_TEST(deri == 0.0, boost::test_tools::tolerance(tol));
            }
        }

        const real_type pot0 = im.potential(1, z);
        const real_type deri0 = im.derivative(1, z);

        BOOST_TEST(pot0  == 0.0, boost::test_tools::tolerance(tol));
        BOOST_TEST(deri0 == 0.0, boost::test_tools::tolerance(tol));

        z += dz;
    }
}


//  (C) Copyright John Maddock 2006.
//  Use, modification and distribution are subject to the
//  Boost Software License, Version 1.0. (See accompanying file
//  LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)

//
// This is not a complete header file, it is included by gamma.hpp
// after it has defined it's definitions.  This inverts the incomplete
// gamma functions P and Q on the first parameter "a" using a generic
// root finding algorithm (TOMS Algorithm 748).
//

#ifndef BOOST_MATH_SP_DETAIL_GAMMA_INVA
#define BOOST_MATH_SP_DETAIL_GAMMA_INVA

#ifdef _MSC_VER
#pragma once
#endif

#include <boost/math/tools/toms748_solve.hpp>
#include <boost/cstdint.hpp>

namespace boost{ namespace math{ namespace detail{

template <class T, class Policy>
struct gamma_inva_t
{
   gamma_inva_t(T z_, T p_, bool invert_) : z(z_), p(p_), invert(invert_) {}
   T operator()(T a)
   {
      return invert ? p - boost::math::gamma_q(a, z, Policy()) : boost::math::gamma_p(a, z, Policy()) - p;
   }
private:
   T z, p;
   bool invert;
};

template <class T, class Policy>
T inverse_poisson_cornish_fisher(T lambda, T p, T q, const Policy& pol)
{
   BOOST_MATH_STD_USING
   // mean:
   T m = lambda;
   // standard deviation:
   T sigma = sqrt(lambda);
   // skewness
   T sk = 1 / sigma;
   // kurtosis:
   // T k = 1/lambda;
   // Get the inverse of a std normal distribution:
   T x = boost::math::erfc_inv(p > q ? 2 * q : 2 * p, pol) * constants::root_two<T>();
   // Set the sign:
   if(p < 0.5)
      x = -x;
   T x2 = x * x;
   // w is correction term due to skewness
   T w = x + sk * (x2 - 1) / 6;
   /*
   // Add on correction due to kurtosis.
   // Disabled for now, seems to make things worse?
   //
   if(lambda >= 10)
      w += k * x * (x2 - 3) / 24 + sk * sk * x * (2 * x2 - 5) / -36;
   */
   w = m + sigma * w;
   return w > tools::min_value<T>() ? w : tools::min_value<T>();
}

template <class T, class Policy>
T gamma_inva_imp(const T& z, const T& p, const T& q, const Policy& pol)
{
   BOOST_MATH_STD_USING  // for ADL of std lib math functions
   //
   // Special cases first:
   //
   if(p == 0)
   {
      return tools::max_value<T>();
   }
   if(q == 0)
   {
      return tools::min_value<T>();
   }
   //
   // Function object, this is the functor whose root
   // we have to solve:
   //
   gamma_inva_t<T, Policy> f(z, (p < q) ? p : q, (p < q) ? false : true);
   //
   // Tolerance: full precision.
   //
   tools::eps_tolerance<T> tol(policies::digits<T, Policy>());
   //
   // Now figure out a starting guess for what a may be,
   // we'll start out with a value that'll put p or q
   // right bang in the middle of their range, the functions
   // are quite sensitive so we should need too many steps
   // to bracket the root from there:
   //
   T guess;
   T factor = 8;
   if(z >= 1)
   {
      //
      // We can use the relationship between the incomplete
      // gamma function and the poisson distribution to
      // calculate an approximate inverse, for large z
      // this is actually pretty accurate, but it fails badly
      // when z is very small.  Also set our step-factor according
      // to how accurate we think the result is likely to be:
      //
      guess = 1 + inverse_poisson_cornish_fisher(z, q, p, pol);
      if(z > 5)
      {
         if(z > 1000)
            factor = 1.01f;
         else if(z > 50)
            factor = 1.1f;
         else if(guess > 10)
            factor = 1.25f;
         else
            factor = 2;
         if(guess < 1.1)
            factor = 8;
      }
   }
   else if(z > 0.5)
   {
      guess = z * 1.2f;
   }
   else
   {
      guess = -0.4f / log(z);
   }
   //
   // Max iterations permitted:
   //
   boost::uintmax_t max_iter = policies::get_max_root_iterations<Policy>();
   //
   // Use our generic derivative-free root finding procedure.
   // We could use Newton steps here, taking the PDF of the
   // Poisson distribution as our derivative, but that's
   // even worse performance-wise than the generic method :-(
   //
   std::pair<T, T> r = bracket_and_solve_root(f, guess, factor, false, tol, max_iter, pol);
   if(max_iter >= policies::get_max_root_iterations<Policy>())
      policies::raise_evaluation_error<T>("boost::math::gamma_p_inva<%1%>(%1%, %1%)", "Unable to locate the root within a reasonable number of iterations, closest approximation so far was %1%", r.first, pol);
   return (r.first + r.second) / 2;
}

} // namespace detail

template <class T1, class T2, class Policy>
inline typename tools::promote_args<T1, T2>::type
   gamma_p_inva(T1 x, T2 p, const Policy& pol)
{
   typedef typename tools::promote_args<T1, T2>::type result_type;
   typedef typename policies::evaluation<result_type, Policy>::type value_type;
   typedef typename policies::normalise<
      Policy,
      policies::promote_float<false>,
      policies::promote_double<false>,
      policies::discrete_quantile<>,
      policies::assert_undefined<> >::type forwarding_policy;

   if(p == 0)
   {
      return tools::max_value<result_type>();
   }
   if(p == 1)
   {
      return tools::min_value<result_type>();
   }

   return policies::checked_narrowing_cast<result_type, forwarding_policy>(
      detail::gamma_inva_imp(
         static_cast<value_type>(x),
         static_cast<value_type>(p),
         1 - static_cast<value_type>(p),
         pol), "boost::math::gamma_p_inva<%1%>(%1%, %1%)");
}

template <class T1, class T2, class Policy>
inline typename tools::promote_args<T1, T2>::type
   gamma_q_inva(T1 x, T2 q, const Policy& pol)
{
   typedef typename tools::promote_args<T1, T2>::type result_type;
   typedef typename policies::evaluation<result_type, Policy>::type value_type;
   typedef typename policies::normalise<
      Policy,
      policies::promote_float<false>,
      policies::promote_double<false>,
      policies::discrete_quantile<>,
      policies::assert_undefined<> >::type forwarding_policy;

   if(q == 1)
   {
      return tools::max_value<result_type>();
   }
   if(q == 0)
   {
      return tools::min_value<result_type>();
   }

   return policies::checked_narrowing_cast<result_type, forwarding_policy>(
      detail::gamma_inva_imp(
         static_cast<value_type>(x),
         1 - static_cast<value_type>(q),
         static_cast<value_type>(q),
         pol), "boost::math::gamma_q_inva<%1%>(%1%, %1%)");
}

template <class T1, class T2>
inline typename tools::promote_args<T1, T2>::type
   gamma_p_inva(T1 x, T2 p)
{
   return boost::math::gamma_p_inva(x, p, policies::policy<>());
}

template <class T1, class T2>
inline typename tools::promote_args<T1, T2>::type
   gamma_q_inva(T1 x, T2 q)
{
   return boost::math::gamma_q_inva(x, q, policies::policy<>());
}

} // namespace math
} // namespace boost

#endif // BOOST_MATH_SP_DETAIL_GAMMA_INVA


/* Copyright (c) 2017, United States Government, as represented by the
 * Administrator of the National Aeronautics and Space Administration.
 *
 * All rights reserved.
 *
 * The Astrobee platform is licensed under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with the
 * License. You may obtain a copy of the License at
 *
 *     http:  //  www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */

// This code uses CGAL that is licensed under the GPL. This implements
// a standalone tool, not connect to the rest of Astrobee.

#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/Surface_mesh.h>
#include <CGAL/Polygon_mesh_processing/connected_components.h>
#include <boost/function_output_iterator.hpp>
#include <boost/property_map/property_map.hpp>
#include <CGAL/IO/PLY.h>
#include <CGAL/Polygon_mesh_processing/IO/polygon_mesh_io.h>

#include <iostream>
#include <fstream>
#include <map>

typedef CGAL::Exact_predicates_inexact_constructions_kernel Kernel;
typedef Kernel::Point_3                                     Point;
typedef Kernel::Compare_dihedral_angle_3                    Compare_dihedral_angle_3;
typedef CGAL::Surface_mesh<Point>                           Mesh;

namespace PMP = CGAL::Polygon_mesh_processing;

template <typename G>
struct Constraint : public boost::put_get_helper<bool,Constraint<G> > {
  typedef typename boost::graph_traits<G>::edge_descriptor edge_descriptor;
  typedef boost::readable_property_map_tag      category;
  typedef bool                                  value_type;
  typedef bool                                  reference;
  typedef edge_descriptor                       key_type;
  Constraint()
    :g_(NULL)
  {}
  Constraint(G& g, double bound)
    : g_(&g), bound_(bound)
  {}
  bool operator[](edge_descriptor e) const
  {
    const G& g = *g_;
    return compare_(g.point(source(e, g)),
                    g.point(target(e, g)),
                    g.point(target(next(halfedge(e, g), g), g)),
                    g.point(target(next(opposite(halfedge(e, g), g), g), g)),
                   bound_) == CGAL::SMALLER;
  }
  const G* g_;
  Compare_dihedral_angle_3 compare_;
  double bound_;
};
template <typename PM>
struct Put_true {
  Put_true(const PM pm)
    :pm(pm)
  {}
  template <typename T>
  void operator()(const T& t)
  {
    put(pm, t, true);
  }
  PM pm;
};

int main(int argc, char* argv[]) {

  if (argc < 5) {
    std::cout << "Usage: " << argv[0]
              << "  num_min_faces_in_component num_components_to_keep input.ply output.ply\n";

    return 1;
  }

  int num_min_faces_in_component = atoi(argv[1]);
  int num_components_to_keep     = atoi(argv[2]);
  const char* input_file         = argv[3];
  const char* output_file        = argv[4];

  std::cout << "Reading mesh:       " << input_file << std::endl;
  std::cout << "Max num hole edges: " << num_min_faces_in_component << "\n";
  std::cout << "Max hole diameter:  " << num_components_to_keep << "\n";

  if (std::string(input_file) == "" || std::string(output_file) == "") {
    std::cout << "The input and/or output mesh was not specified." << std::endl;
    return 1;
  }

  std::cout << "Reading: " << input_file << std::endl;

  Mesh mesh;
  if(!PMP::IO::read_polygon_mesh(input_file, mesh)) {
    std::cerr << "Invalid input." << std::endl;
    return 1;
  }

  typedef boost::graph_traits<Mesh>::face_descriptor face_descriptor;
  const double bound = std::cos(0.75 * CGAL_PI);
  std::vector<face_descriptor> cc;
  face_descriptor fd = *faces(mesh).first;
  PMP::connected_component(fd,
      mesh,
      std::back_inserter(cc));

  // Instead of writing the faces into a container, you can set a face property to true
  typedef Mesh::Property_map<face_descriptor, bool> F_select_map;
  F_select_map fselect_map =
    mesh.add_property_map<face_descriptor, bool>("f:select", false).first;
  PMP::connected_component(fd,
      mesh,
      boost::make_function_output_iterator(Put_true<F_select_map>(fselect_map)));

  Mesh::Property_map<face_descriptor, std::size_t> fccmap =
    mesh.add_property_map<face_descriptor, std::size_t>("f:CC").first;
  std::size_t num = PMP::connected_components(mesh,
      fccmap,
      PMP::parameters::edge_is_constrained_map(Constraint<Mesh>(mesh, bound)));
  std::cout << "The mesh has " << num << " connected components.\n";

  typedef std::map<std::size_t/*index of CC*/, unsigned int/*nb*/> Components_size;
  Components_size nb_per_cc;
  for(face_descriptor f : faces(mesh)){
    nb_per_cc[ fccmap[f] ]++;
  }

  //for(const Components_size::value_type& cc : nb_per_cc){
  //   std::cout << "\t CC #" << cc.first
  //            << " is made of " << cc.second << " faces" << std::endl;
  // }

  // Keep only components with at least this many faces
  if (num_min_faces_in_component > 0) {
    PMP::keep_large_connected_components
      (mesh, num_min_faces_in_component,
       PMP::parameters::edge_is_constrained_map(Constraint<Mesh>(mesh, bound)));

    // Without this the mesh will be invalid
    mesh.collect_garbage();
  }

  if (num_components_to_keep > 0) {
    PMP::keep_largest_connected_components
      (mesh, num_components_to_keep,
       PMP::parameters::edge_is_constrained_map(Constraint<Mesh>(mesh, bound)));

    // Without this the mesh will be invalid
    mesh.collect_garbage();
  }

  std::cout << "Writing output mesh: " << output_file << std::endl;
  CGAL::IO::write_PLY(output_file, mesh,
                      CGAL::parameters::stream_precision(17).use_binary_mode(false));

  return 0;
}


///////////////////////////////////////////////////////////////////////////////
// importance_sampling::sampler.hpp                                          //
//                                                                           //
//  Copyright 2009 Erwann Rogard. Distributed under the Boost                //
//  Software License, Version 1.0. (See accompanying file                    //
//  LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)         //
///////////////////////////////////////////////////////////////////////////////
#ifndef BOOST_STATISTICS_DETAIL_IMPORTANCE_SAMPLING_SAMPLER_HPP_ER_2009
#define BOOST_STATISTICS_DETAIL_IMPORTANCE_SAMPLING_SAMPLER_HPP_ER_2009
#include <vector>
#include <boost/mpl/not.hpp>
#include <boost/mpl/assert.hpp>
#include <boost/call_traits.hpp>
#include <boost/type_traits.hpp>
#include <boost/utility.hpp>
#include <boost/range.hpp>
#include <boost/random/variate_generator.hpp>
#include <boost/random/uniform_real.hpp>
// TODO #include <boost/random/discrete_distributionhpp> when becomes avail
#include <boost/random/discrete_distribution_sw_2009.hpp>

namespace boost{
namespace statistics{
namespace detail{
namespace importance_sampling{

// Samples by SIR given a set of proposal values and their unnormalized weights
//
// Models RandomDistribution
//
// R1:  type of a range values
// W:   type of each weight
template<typename R1,typename W>
class sampler{
    typedef typename remove_reference<R1>::type         const_values_;
    typedef typename remove_cv<const_values_>::type     values_t;
    typedef typename range_size<values_t>::type         size_;
    typedef is_reference<R1>                            is_ref_;
    public:
    typedef typename range_value<const_values_>::type   result_type;
    private:
    typedef random::discrete_distribution<size_,W>      discr_dist_t;
    public:
    typedef typename discr_dist_t::input_type           input_type;

    sampler(){}
    template<typename R0>
    sampler(
        const R0& unnormalized_weights,
        typename call_traits<R1>::param_type values
    ):discr_dist_(
        boost::begin(unnormalized_weights),
        boost::end(unnormalized_weights)
    ),values_(values){
        BOOST_ASSERT(
            boost::size(unnormalized_weights) == boost::size(this->values())
        );
    }

    sampler(const sampler& that)
        :discr_dist_(that.discr_dist_),values_(that.values_){}

    sampler& operator=(const sampler& that)
    {
        if(&that!=this){
            discr_dist_ = that.discr_dist_;
            values_ = that.values_;
        }
        return (*this);
    }

    template<typename U>
    result_type operator()(U& urng)const
    {
        typedef typename discr_dist_t::result_type k_t;
        k_t k = discr_dist_(urng);
        BOOST_ASSERT( k < boost::size(this->values()) );
        return (*boost::next(boost::begin(this->values()),k));
    }
    const discr_dist_t& discrete_distribution()const
    {
        return this->discr_dist_;
    }

    // TODO os/is

    typename call_traits<R1>::const_reference values()const{
        return this->values_;
    }

    private:
    discr_dist_t discr_dist_;
    typename call_traits<R1>::value_type values_;
};

    template<typename R0,typename R1>
    sampler<
        R0,
        typename remove_cv<
            typename remove_reference<
                typename range_value<R0>::type
            >::type
        >::type
    >
    make_sampler(
        const R0& unnormalized_weights,
        typename call_traits<R1>::param_type values
    )
    {
        typedef sampler<
            R0,
            typename remove_cv<
                typename remove_reference<
                    typename range_value<R0>::type
                >::type
            >::type
        > result_;
        return result_(unnormalized_weights,values);
    }

}// importance_sampling
}// detail
}// statistics
}// boost

#endif


#include <boost/cstdint.hpp>
#include <boost/mpl/int.hpp>
#include <boost/mpl/comparison.hpp>
#include <boost/mpl/assert.hpp>
#include <nt2/sdk/aligned/next_power_of_2.hpp>

using boost::mpl::int_;
using boost::mpl::equal_to;
using nt2::meta::next_power_of_2_c;

int main()
{
  BOOST_MPL_ASSERT(( equal_to<next_power_of_2_c<0>::type,int_<0> >::type ));
  BOOST_MPL_ASSERT(( equal_to<next_power_of_2_c<1>::type,int_<1> >::type ));
  BOOST_MPL_ASSERT(( equal_to<next_power_of_2_c<2>::type,int_<2> >::type ));
  BOOST_MPL_ASSERT(( equal_to<next_power_of_2_c<2>::type,int_<2> >::type ));
  BOOST_MPL_ASSERT(( equal_to<next_power_of_2_c<3>::type,int_<4> >::type ));
  BOOST_MPL_ASSERT(( equal_to<next_power_of_2_c<1055>::type,int_<2048> >::type ));
}


/**
 * @file dense_multiplication.cpp
 * @brief Benchmarks the multiplication of 2 random square matrices of increasing size
 *
 * @author Matthew Powelson
 * @date April 1, 2020
 * @version TODO
 * @bug No known bugs
 *
 * @copyright Copyright (c) 2020, Southwest Research Institute
 *
 * @par License
 * Software License Agreement (Apache License)
 * @par
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * http://www.apache.org/licenses/LICENSE-2.0
 * @par
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <benchmark/benchmark.h>

#include <arrayfire.h>
#include <Eigen/Eigen>
#include <torch/torch.h>

auto BM_PYTORCH_MM = [](benchmark::State& state, int size, torch::Device device) {
  torch::TensorOptions options =
      torch::TensorOptions().dtype(torch::kFloat64).layout(torch::kStrided).device(device).requires_grad(true);

  torch::manual_seed(0);
  torch::Tensor tensor0 = torch::rand({ size, size }, options);
  torch::manual_seed(1);
  torch::Tensor tensor1 = torch::rand({ size, size }, options);
  torch::Tensor result = torch::rand({ size, size }, options);
  for (auto _ : state)
  {
    benchmark::DoNotOptimize(result = torch::mm(tensor0, tensor1));
  }
};

auto BM_EIGEN_MM = [](benchmark::State& state, int size) {
  Eigen::MatrixXd matrix0 = Eigen::MatrixXd::Random(size, size);
  Eigen::MatrixXd matrix1 = Eigen::MatrixXd::Random(size, size);
  Eigen::MatrixXd result = Eigen::MatrixXd::Random(size, size);
  for (auto _ : state)
  {
    benchmark::DoNotOptimize(result = matrix0 * matrix1);
  }
};

auto BM_ARRAYFIRE_MM = [](benchmark::State& state, int size, auto device) {
  af::setBackend(device);

  af::setSeed(0);
  af::array array0 = af::randu(size, size);
  af::setSeed(1);
  af::array array1 = af::randu(size, size);

  af::array result = af::randu(size, size);
  for (auto _ : state)
  {
    benchmark::DoNotOptimize(result = af::matmul(array0, array1));
  }
};

int main(int argc, char** argv)
{
  for (auto& test_input : { 2, 3, 4, 8, 16, 32, 64, 128, 256, 512, 1028, 2056})
  {
    std::string name = "BM_PYTORCH_CPU_MM_Size_" + std::to_string(test_input);
    benchmark::RegisterBenchmark(name.c_str(), BM_PYTORCH_MM, test_input, torch::kCPU)
        ->UseRealTime()
        ->Unit(benchmark::TimeUnit::kMicrosecond);
  }
  for (auto& test_input : { 2, 3, 4, 8, 16, 32, 64, 128, 256, 512, 1028, 2056})
  {
    std::string name = "BM_PYTORCH_GPU_MM_Size_" + std::to_string(test_input);
    benchmark::RegisterBenchmark(name.c_str(), BM_PYTORCH_MM, test_input, torch::kCUDA)
        ->UseRealTime()
        ->Unit(benchmark::TimeUnit::kMicrosecond);
  }
  for (auto& test_input : { 2, 3, 4, 8, 16, 32, 64, 128, 256, 512, 1028, 2056})
  {
    std::string name = "BM_EIGEN_MM_Size_" + std::to_string(test_input);
    benchmark::RegisterBenchmark(name.c_str(), BM_EIGEN_MM, test_input)
        ->UseRealTime()
        ->Unit(benchmark::TimeUnit::kMicrosecond);
  }
  for (auto& test_input : { 2, 3, 4, 8, 16, 32, 64, 128, 256, 512, 1028, 2056})
  {
    std::string name = "BM_ARRAYFIRE_CPU_MM_Size_" + std::to_string(test_input);
    benchmark::RegisterBenchmark(name.c_str(), BM_ARRAYFIRE_MM, test_input, AF_BACKEND_CPU)
        ->UseRealTime()
        ->Unit(benchmark::TimeUnit::kMicrosecond);
  }
  for (auto& test_input : { 2, 3, 4, 8, 16, 32, 64, 128, 256, 512, 1028, 2056})
  {
    std::string name = "BM_ARRAYFIRE_CUDA_MM_Size_" + std::to_string(test_input);
    benchmark::RegisterBenchmark(name.c_str(), BM_ARRAYFIRE_MM, test_input, AF_BACKEND_CUDA)
        ->UseRealTime()
        ->Unit(benchmark::TimeUnit::kMicrosecond);
  }
  for (auto& test_input : { 2, 3, 4, 8, 16, 32, 64, 128, 256, 512, 1028, 2056})
  {
    std::string name = "BM_ARRAYFIRE_OPENCL_MM_Size_" + std::to_string(test_input);
    benchmark::RegisterBenchmark(name.c_str(), BM_ARRAYFIRE_MM, test_input, AF_BACKEND_OPENCL)
        ->UseRealTime()
        ->Unit(benchmark::TimeUnit::kMicrosecond);
  }
  benchmark::Initialize(&argc, argv);
  benchmark::RunSpecifiedBenchmarks();
}