// Refer to the paper: "Robust Point Cloud Based Reconstruction of Large-Scale Outdoor Scenes" in CVPR 2019
// Link: https://arxiv.org/abs/1905.09634

// This file is an implememntation of the Gaussian-Uniform mixture model as described in Sec. 5 of the paper.
// The Gaussian-Uniform mixture model degenerates to the approach in Choi et al. "Robust Reconstruction of Indoor Scenes" in CVPR 2017.
// This implementation adopts some techniques used by Choi et al.

#include <ceres/ceres.h>
#include <iostream>
#include <sophus/se3.hpp>

#include <vector>
#include <fstream>
#include <Eigen/Core>

#include <iomanip>
#include <algorithm>

#include "local_parameterization_se3.hpp"

#define NUM_ITERATION_EM 1000

#define EPSILON 6                             // upper bound of feature matching errors, used in Eq. (27)
#define M_HAT EPSILON * EPSILON               // mean error, used in Eq. (27)
#define P_HAT 0.9                             // trust level, used in Eq. (27)
#define THETA P_HAT / ( 1.0 - P_HAT) * M_HAT  // THETA is solved using Eq. (27)

#define THESHOLD_TO_BE_AN_INLIER_LOOP 0.8 * P_HAT    // theshold to prune outlier loop closures, used to generate results in Tab. 1 and 2

typedef Eigen::Matrix< double, 6, 6, Eigen::RowMajor > InformationMatrix;
typedef Eigen::Matrix< double, 6, 1> Vector6d;

using ceres::AutoDiffCostFunction;
using ceres::CostFunction;
using ceres::Problem;
using ceres::Solver;
using ceres::Solve;

struct FramedTransformation {
  int id1_;
  int id2_;
  int frame_;
  Eigen::Matrix4d transformation_;      // pose in matrix form
  Sophus::SE3d transformation_se3_;     // pose in se3 form

  FramedTransformation( int id1, int id2, int f, Eigen::Matrix4d t )
    : id1_( id1 ), id2_( id2 ), frame_( f ), transformation_( t )
  {
    Eigen::Quaterniond q;
    q = t.block<3,3>(0,0);
    transformation_se3_ = Sophus::SE3d(q, Sophus::SE3d::Point(t(0,3), t(1,3), t(2,3)));
  }

  FramedTransformation(int id1, int id2, int f, Sophus::SE3d t)
    : id1_( id1 ), id2_( id2 ), frame_( f ), transformation_se3_( t )
    {
      transformation_ = t.matrix();
    }
};

struct PCLTrajectory {
  std::vector< FramedTransformation > data_;
  int index_;

  void LoadFromFile(const char* filename ) {
    data_.clear();
    index_ = 0;
    int id1, id2, frame;
    Eigen::Matrix4d trans;
    FILE * f = fopen( filename, "r" );
    if ( f != NULL ) {
      char buffer[1024];
      while ( fgets( buffer, 1024, f ) != NULL ) {
        if ( strlen( buffer ) > 0 && buffer[ 0 ] != '#' ) {
          sscanf( buffer, "%d %d %d", &id1, &id2, &frame);
          fgets( buffer, 1024, f );
          sscanf( buffer, "%lf %lf %lf %lf", &trans(0,0), &trans(0,1), &trans(0,2), &trans(0,3) );
          fgets( buffer, 1024, f );
          sscanf( buffer, "%lf %lf %lf %lf", &trans(1,0), &trans(1,1), &trans(1,2), &trans(1,3) );
          fgets( buffer, 1024, f );
          sscanf( buffer, "%lf %lf %lf %lf", &trans(2,0), &trans(2,1), &trans(2,2), &trans(2,3) );
          fgets( buffer, 1024, f );
          sscanf( buffer, "%lf %lf %lf %lf", &trans(3,0), &trans(3,1), &trans(3,2), &trans(3,3) );
          data_.push_back( FramedTransformation( id1, id2, frame, trans ) );
        }
      }
      fclose( f );
    }
  }

  void SaveToFile(const char* filename ) {
    FILE * f = fopen( filename, "w" );
    for ( int i = 0; i < ( int )data_.size(); i++ ) {
      Sophus::SE3d trans_se3 = data_[ i ].transformation_se3_;
      Eigen::Matrix4d trans = trans_se3.matrix();
      fprintf( f, "%d\t%d\t%d\n", data_[ i ].id1_, data_[ i ].id2_, data_[ i ].frame_ );
      fprintf( f, "%.8f %.8f %.8f %.8f\n", trans(0,0), trans(0,1), trans(0,2), trans(0,3) );
      fprintf( f, "%.8f %.8f %.8f %.8f\n", trans(1,0), trans(1,1), trans(1,2), trans(1,3) );
      fprintf( f, "%.8f %.8f %.8f %.8f\n", trans(2,0), trans(2,1), trans(2,2), trans(2,3) );
      fprintf( f, "%.8f %.8f %.8f %.8f\n", trans(3,0), trans(3,1), trans(3,2), trans(3,3) );

    }
    fclose( f );
  }

};

struct FramedInformation {
  int id1_;
  int id2_;
  int frame_;
  InformationMatrix information_;

  FramedInformation( int id1, int id2, int f, InformationMatrix t )
    : id1_( id1 ), id2_( id2 ), frame_( f ), information_( t )
  {}
};

struct PCLInformation {
  std::vector< FramedInformation > data_;

  void LoadFromFile(const char*  filename ) {
    data_.clear();
    int id1, id2, frame;
    InformationMatrix info;
    FILE * f = fopen( filename, "r" );
    if ( f != NULL ) {
      char buffer[1024];
      while ( fgets( buffer, 1024, f ) != NULL ) {
        if ( strlen( buffer ) > 0 && buffer[ 0 ] != '#' ) {
          sscanf( buffer, "%d %d %d", &id1, &id2, &frame);
          fgets( buffer, 1024, f );
          sscanf( buffer, "%lf %lf %lf %lf %lf %lf", &info(0,0), &info(0,1), &info(0,2), &info(0,3), &info(0,4), &info(0,5) );
          fgets( buffer, 1024, f );
          sscanf( buffer, "%lf %lf %lf %lf %lf %lf", &info(1,0), &info(1,1), &info(1,2), &info(1,3), &info(1,4), &info(1,5) );
          fgets( buffer, 1024, f );
          sscanf( buffer, "%lf %lf %lf %lf %lf %lf", &info(2,0), &info(2,1), &info(2,2), &info(2,3), &info(2,4), &info(2,5) );
          fgets( buffer, 1024, f );
          sscanf( buffer, "%lf %lf %lf %lf %lf %lf", &info(3,0), &info(3,1), &info(3,2), &info(3,3), &info(3,4), &info(3,5) );
          fgets( buffer, 1024, f );
          sscanf( buffer, "%lf %lf %lf %lf %lf %lf", &info(4,0), &info(4,1), &info(4,2), &info(4,3), &info(4,4), &info(4,5) );
          fgets( buffer, 1024, f );
          sscanf( buffer, "%lf %lf %lf %lf %lf %lf", &info(5,0), &info(5,1), &info(5,2), &info(5,3), &info(5,4), &info(5,5) );
          data_.push_back( FramedInformation( id1, id2, frame, info ) );
        }
      }
      fclose( f );
    }
  }
};

struct GaussianFunctor {
  EIGEN_MAKE_ALIGNED_OPERATOR_NEW
  GaussianFunctor(Sophus::SE3d trans_next_to_current, InformationMatrix info, int id1, int id2)
  : trans_next_to_current_(trans_next_to_current), info_(info), id1_(id1), id2_(id2) {}

  template <typename T>
  bool operator()(T const* const sT_current, T const* const sT_next,
                  T* residual) const {

    Eigen::Map<Sophus::SE3<T> const> const T_current(sT_current);
    Eigen::Map<Sophus::SE3<T> const> const T_next(sT_next);

    Sophus::SE3<T> xi_7T = trans_next_to_current_
                          * T_next.inverse()
                          * T_current;

    // xi: local parameterization as in Choi et al.
    Eigen::Matrix<T,6,1> xi;
    xi << xi_7T.data()[4], xi_7T.data()[5], xi_7T.data()[6], xi_7T.data()[0], xi_7T.data()[1], xi_7T.data()[2];

    Eigen::Matrix<T,1,1> sq_error = xi.transpose() * info_ * xi;
    residual[0] = T( sqrt(sq_error(0,0)) );

    return true;
  }

private:
  const Sophus::SE3d trans_next_to_current_;
  const InformationMatrix info_;
  const int id1_, id2_;
};

class RobustPCLReconstruction_GaussianUniform {
public:

  // Odometry contraints
  PCLTrajectory odometry_log_;
  PCLInformation odometry_info_;

  // Loop closure contraints
  PCLTrajectory loop_log_;
  PCLInformation loop_info_;

  // Fragment poses
  PCLTrajectory fragment_poses_;
  PCLTrajectory last_fragment_poses_;   // used to check for EM convergence

  // P_ij table (Eq. 25)
  std::vector< std::vector<double> > P_;
  double average_P_;
  double last_average_P_;   // used to check for EM convergence

  RobustPCLReconstruction_GaussianUniform()
  {
    average_P_ = -1.;
    last_average_P_ = -1.;
  }

  ~RobustPCLReconstruction_GaussianUniform() {}

  void LoadOdometryLog(const char* filename) {
    odometry_log_.LoadFromFile(filename);
  }

  void LoadOdometryInfo(const char* filename) {
    odometry_info_.LoadFromFile(filename);
  }

  void LoadLoopLog(const char* filename) {
    loop_log_.LoadFromFile(filename);
  }

  void LoadLoopInfo(const char* filename) {
    loop_info_.LoadFromFile(filename);
  }

  void InitPosesFromFile(const char* filename) {
    fragment_poses_.LoadFromFile(filename);

    // Initialize P_ij table
    P_.resize(NumPoses());
    for (int i = 0; i < NumPoses(); i++) {
      P_[i].resize(NumPoses());
    }
  }

  // void InitPoses() {
  //   fragment_poses_.data_.clear();
  //   fragment_poses_.index_ = 0;
  //   Eigen::Matrix4d pose = Eigen::Matrix4d::Identity();
  //   fragment_poses_.data_.push_back( FramedTransformation( 0, 0, 1, pose ) );

  //   for( std::vector< FramedTransformation >::iterator it = odometry_log_.data_.begin();
  //     it != odometry_log_.data_.end(); it ++)
  //   {
  //     pose = pose * it->transformation_;
  //     fragment_poses_.data_.push_back( FramedTransformation( it->id2_, it->id2_, it->id2_ + 1, pose ) );
  //   }
  //   std::cout << "total data size: " << odometry_log_.data_.size() << std::endl;

  //   P_.resize(NumPoses());
  //   for (int i = 0; i < NumPoses(); i++) {
  //     P_[i].resize(NumPoses());
  //   }
  // }

  void SavePoses(const char* filename) {
    fragment_poses_.SaveToFile(filename);
  }

  void SaveLinks(const char* filename) {
    FILE * f = fopen( filename, "w" );

    for ( int i = 0; i < NumOdometryConstraints(); i++ ) {
      Sophus::SE3d trans_se3 = odometry_log_.data_[ i ].transformation_se3_;
      Eigen::Matrix4d trans = trans_se3.matrix();
      fprintf( f, "%d\t%d\t%d\n", odometry_log_.data_[ i ].id1_, odometry_log_.data_[ i ].id2_, odometry_log_.data_[ i ].frame_ );
      fprintf( f, "%.8f %.8f %.8f %.8f\n", trans(0,0), trans(0,1), trans(0,2), trans(0,3) );
      fprintf( f, "%.8f %.8f %.8f %.8f\n", trans(1,0), trans(1,1), trans(1,2), trans(1,3) );
      fprintf( f, "%.8f %.8f %.8f %.8f\n", trans(2,0), trans(2,1), trans(2,2), trans(2,3) );
      fprintf( f, "%.8f %.8f %.8f %.8f\n", trans(3,0), trans(3,1), trans(3,2), trans(3,3) );
    }

    for (int i = 0; i < NumLoopClosureConstraints(); i++ ) {
      Sophus::SE3d trans_se3 = loop_log_.data_[ i ].transformation_se3_;
      int id1 = loop_log_.data_[ i ].id1_;
      int id2 = loop_log_.data_[ i ].id2_;
      assert(id1 < id2);
      if (id1 != id2 - 1 && P_[id1][id2] > THESHOLD_TO_BE_AN_INLIER_LOOP) {
        Eigen::Matrix4d trans = trans_se3.matrix();
        fprintf( f, "%d\t%d\t%d\n", loop_log_.data_[ i ].id1_, loop_log_.data_[ i ].id2_, loop_log_.data_[ i ].frame_ );
        fprintf( f, "%.8f %.8f %.8f %.8f\n", trans(0,0), trans(0,1), trans(0,2), trans(0,3) );
        fprintf( f, "%.8f %.8f %.8f %.8f\n", trans(1,0), trans(1,1), trans(1,2), trans(1,3) );
        fprintf( f, "%.8f %.8f %.8f %.8f\n", trans(2,0), trans(2,1), trans(2,2), trans(2,3) );
        fprintf( f, "%.8f %.8f %.8f %.8f\n", trans(3,0), trans(3,1), trans(3,2), trans(3,3) );
      }
    }
    fclose( f );
  }

  void Expectation() {
    std::cout << std::endl << "This is the beginning of EM iteration " << fragment_poses_.index_ + 1 << std::endl
              << ">>>> Expectation Step <<<<" << std::endl;

    double sum_P = 0;   // used to compute average_P_ later
    std::vector<int> P_ij_counter(20);
    for (int ii = 0; ii < 20; ii ++)
      P_ij_counter[ii] = 0;

    std::cout << std::setw(5) << "id1"
              << std::setw(5) << "id2"
              << std::setw(10) << "#matches"
              << std::setw(15) << "xiLxi"
              << std::setw(15) << "B_ij"
              << std::setw(15) << "B_ij^2"
              << std::setw(15) << "expterm"
              << std::setw(5) << " --> "
              << std::setw(15) << "P_ij"
              << std::endl;

    // Compute P_ij table
    for (int i = 0; i < NumLoopClosureConstraints(); i ++) {

      FramedTransformation trans = loop_log_.data_[i];
      FramedInformation info = loop_info_.data_[i];

      assert(trans.id1_ == info.id1_ && trans.id2_ == info.id2_ && trans.id1_ < trans.id2_);
      int id1 = info.id1_;
      int id2 = info.id2_;

      // Compute the matrix form of xi
      Sophus::SE3d M_xi = trans.transformation_se3_
                          * fragment_poses_.data_[id2].transformation_se3_.inverse()
                          * fragment_poses_.data_[id1].transformation_se3_;

      // Extract the vector form of xi
      Vector6d xi;
      xi << M_xi.data()[4], M_xi.data()[5], M_xi.data()[6], M_xi.data()[0], M_xi.data()[1], M_xi.data()[2];

      // Compute B_ij (Eq. 23)
      // This approximiation technique is adopted from Choi et al.
      double xiLxi = xi.transpose() * info.information_ * xi;
      double B_ij = xiLxi / info.information_(0,0);

      // Compute P_ij (Eq. 25)
      // The exponential-of-log trick is used to avoid the numerical issues when B_ij is too large.
      double expterm = exp(2 * log(B_ij) - log (THETA));
      P_[id1][id2] = 1./ (1. + expterm);

      // Accumulate sum_P
      sum_P += P_[id1][id2];

      // Visualize intermediate steps
      std::cout << std::setw(5) << id1
                << std::setw(5) << id2
                << std::setw(10) << info.information_(0,0)
                << std::setw(15) << xiLxi
                << std::setw(15) << B_ij
                << std::setw(15) << B_ij * B_ij
                << std::setw(15) << expterm
                << std::setw(5) << " --> "
                << std::setw(15) << P_[id1][id2]
                << std::endl;

      // Use P_ij_counter to visualize the distribution of P_ij
      for (int ii = 0; ii < 20; ii ++)
        if (P_[id1][id2] > 0.05 * ii && P_[id1][id2] <= 0.05 * (ii+1))
          P_ij_counter[ii] ++;

    }

    // Save
    last_average_P_ = average_P_;
    last_fragment_poses_.index_ = fragment_poses_.index_;
    last_fragment_poses_.data_.clear();
    for (int i = 0; i < NumPoses(); i++)
      last_fragment_poses_.data_.push_back( FramedTransformation( fragment_poses_.data_[i].id1_,
                                                                  fragment_poses_.data_[i].id2_,
                                                                  fragment_poses_.data_[i].frame_,
                                                                  fragment_poses_.data_[i].transformation_se3_));
    // Update
    average_P_ = sum_P / NumLoopClosureConstraints();
    fragment_poses_.index_++;

    // Visualize the distribution of P_ij
    std::cout << "P_ij distributed in 20 bins :" << std::endl;
    for (int ii = 0; ii < 20 ; ii++)
      std::cout << "between"
                << std::setw(5) << 0.05 * ii
                << std::setw(5) << "and"
                << std::setw(5) << 0.05 * (ii+1)
                << std::setw(15) << P_ij_counter[ii]
                << std::endl;
  }

  void Maximization() {
    std::cout << std::endl << ">>>> Maximization Step <<<<" << std::endl;

    // Build the problem.
    ceres::Problem problem;

    // Specify local update rule for the parameters
    for (std::vector< FramedTransformation >::iterator it = fragment_poses_.data_.begin();
         it != fragment_poses_.data_.end(); it++ ) {
      problem.AddParameterBlock(it->transformation_se3_.data(), Sophus::SE3d::num_parameters,
                                new Sophus::test::LocalParameterizationSE3);
    }

    // Create and add cost functions. Derivatives will be evaluated via
    // automatic differentiation
    for (int i = 0; i < NumOdometryConstraints(); i++) {

      FramedTransformation trans = odometry_log_.data_[i];
      FramedInformation info = odometry_info_.data_[i];

      assert(trans.id1_ == info.id1_ && trans.id2_ == info.id2_ && trans.id1_ < trans.id2_);
      int id1 = info.id1_;
      int id2 = info.id2_;

      if (info.information_(0,0) <= 1)
        continue;

      ceres::CostFunction* cost_odometry =
          new ceres::AutoDiffCostFunction<GaussianFunctor, 1,
                                          Sophus::SE3d::num_parameters,
                                          Sophus::SE3d::num_parameters>(
              new GaussianFunctor(trans.transformation_se3_, info.information_, id1, id2));
      problem.AddResidualBlock(cost_odometry, NULL,
                               fragment_poses_.data_[id1].transformation_se3_.data(),
                               fragment_poses_.data_[id2].transformation_se3_.data());
    }

    for (int i = 0; i < NumLoopClosureConstraints(); i++) {

      FramedTransformation trans = loop_log_.data_[i];
      FramedInformation info = loop_info_.data_[i];

      assert(trans.id1_ == info.id1_ && trans.id2_ == info.id2_ && trans.id1_ < trans.id2_);
      int id1 = info.id1_;
      int id2 = info.id2_;

      ceres::CostFunction* cost_loop =
          new ceres::AutoDiffCostFunction<GaussianFunctor, 1,
                                          Sophus::SE3d::num_parameters,
                                          Sophus::SE3d::num_parameters>(
              new GaussianFunctor(trans.transformation_se3_, info.information_, id1, id2));
      problem.AddResidualBlock(cost_loop, new ceres::ScaledLoss(NULL, P_[id1][id2], ceres::TAKE_OWNERSHIP),
                               fragment_poses_.data_[id1].transformation_se3_.data(),
                               fragment_poses_.data_[id2].transformation_se3_.data());
    }

    // Set solver options
    ceres::Solver::Options options;
    // options.max_num_iterations = 1000;
    options.gradient_tolerance = 0.01 * Sophus::Constants<double>::epsilon();
    options.function_tolerance = 0.01 * Sophus::Constants<double>::epsilon();
    options.linear_solver_type = ceres::SPARSE_NORMAL_CHOLESKY;

    // Solve and report
    ceres::Solver::Summary summary;
    Solve(options, &problem, &summary);
    std::cout << summary.BriefReport() << std::endl;

    std::cout << "This is the end of EM iteration : " << fragment_poses_.index_ << std::endl;
  }

  int NumPoses() {
    return fragment_poses_.data_.size();
  }

  int NumOdometryConstraints() {
    return odometry_log_.data_.size();
  }

  int NumLoopClosureConstraints() {
    return loop_log_.data_.size();
  }

  bool IsConverged() {
    if (last_average_P_ < 0) {
      return false;         // not started yet
    }

    std::cout << "Checking for the convergence of average_P_ ...\t";
    if (fabs(average_P_ - last_average_P_) > 0.00001) {
      std::cout << "NOT coverged yet, as average_P_ was updated from " << last_average_P_ << " to " << average_P_ << std::endl;
      return false;
    }
    std::cout << "CONVERGED!" << std::endl;

    std::cout << "Checking for the convergence of poses ...\t";
    for (int i = 0; i < NumPoses(); i++)
    {
      Sophus::SE3d last_pose = last_fragment_poses_.data_[i].transformation_se3_;
      Sophus::SE3d current_pose = fragment_poses_.data_[i].transformation_se3_;

      double const mse = (last_pose.inverse() * current_pose).log().squaredNorm();
      bool const converged = mse < 10. * Sophus::Constants<double>::epsilon();

      if (!converged) {
        std::cout << "NOT converged yet." << std::endl;
        return false;
      }
    }
    std::cout << "CONVERGED!" << std::endl;
    return true;
  }
};

int main(int argc, char** argv) {
  if (argc != 8)
  {
    std::cout << "Please provide the following arguments: InputOdometryLog, InputOdometryInfo, InputLoopLog, InputLoopInfo, InputInitPoses, OutputFinalPoses, OutputKeptLoops" << std::endl;
    return 1;
  }
  RobustPCLReconstruction_GaussianUniform RobustPCLReconstruction_g;

  RobustPCLReconstruction_g.LoadOdometryLog(argv[1]);
  RobustPCLReconstruction_g.LoadOdometryInfo(argv[2]);
  RobustPCLReconstruction_g.LoadLoopLog(argv[3]);
  RobustPCLReconstruction_g.LoadLoopInfo(argv[4]);
  RobustPCLReconstruction_g.InitPosesFromFile(argv[5]);

  bool isConverged = false;
  for (int i = 0 ; i < NUM_ITERATION_EM; i ++) {
    RobustPCLReconstruction_g.Expectation();
    RobustPCLReconstruction_g.Maximization();
    if (RobustPCLReconstruction_g.IsConverged()) {
      std::cout << "Optimization ends since EM is converged" << std::endl << std::endl;
      isConverged = true;
      break;
    }
  }

  if (!isConverged)
    std::cout << "Optimization ends after " << NUM_ITERATION_EM << " EM iterations" << std::endl << std::endl;

  RobustPCLReconstruction_g.SavePoses(argv[6]);
  std::cout << "Final poses are saved to " << argv[6] << std::endl;

  RobustPCLReconstruction_g.SaveLinks(argv[7]);
  std::cout << "Good links are saved to " << argv[7] << std::endl;

  std::cout << "There are " << RobustPCLReconstruction_g.NumPoses() << " poses" << std::endl;
  std::cout << "There are " << RobustPCLReconstruction_g.NumOdometryConstraints() << " odometry constraints" << std::endl;
  std::cout << "There are " << RobustPCLReconstruction_g.NumLoopClosureConstraints() << " loops" << std::endl;

  std::cout << std::endl;

  return 0;
}


#ifndef Field_hpp
#define Field_hpp

/** @file src/libField/Field.hpp
 */

#include <ostream>
#include <type_traits>
#include <typeinfo>
#include <vector>
#include <array>

#include <boost/multi_array.hpp>

#include "CoordinateSystem.hpp"
#include "Utils.hpp"

/** @class Field
 * @brief A class for storing data in a field.
 * @author C.D. Clark III
 * @date 06/13/17
 *
 * A field is a quantity defined at every point in space. For example, the temperature
 * distribution throughout a solid, the pressure at every point in a room, or the density of a fluid.
 * A field may be 1-, 2-, or 3-dimensional, and is parameterized by a coordinate system.
 *
 * This class associates a coordinate system with a multi-dimensional array. Field elements are
 * allocated in a single, multi-dimensional array. And a CoordinateSystem is allocated for the coordinates.
 */
template<typename T, std::size_t N>
using arrayND = boost::multi_array<T, N, std::allocator<T>>;
template<typename T, std::size_t N>
using viewND = boost::detail::multi_array::multi_array_view<T, N>;

template<typename QUANT, size_t NUMDIMS, typename COORD = QUANT,
         template<typename, size_t> class ARRAYND = arrayND,
         template<typename> class ARRAY1D         = array1D>
class Field
{
 public:
  typedef ARRAYND<QUANT, NUMDIMS>                   array_type;
  typedef typename array_type::index                index_type;
  typedef CoordinateSystem<COORD, NUMDIMS, ARRAY1D> cs_type;

 protected:
  std::shared_ptr<array_type> d;
  std::shared_ptr<cs_type>    cs;

 protected:
  /**
   * @internal
   * Utility function for converting 1d index to an Nd
   * array of indices.
   */
  auto _1d2nd(size_t i) const
  {
    auto shape = d->shape();

    std::array<size_t, NUMDIMS> ind;
    int                         NN = shape[0];
    for (size_t j = 1; j < NUMDIMS; ++j) NN *= shape[j];
    for (size_t j = 0; j < NUMDIMS; ++j) {
      NN /= shape[j];
      ind[j] = i / NN;
      i -= ind[j] * NN;
    }
    return ind;
  }

 public:
#if SERIALIZATION_ENABLED
  template<class Archive>
  void serialize(Archive& ar, const unsigned int version)
  {
    ar& d;
    ar& cs;
  }
#endif

  /**
   * @brief Create an empty field with no elements allocated.
   *
   * Memory can be allocated later with Field::reset() method. The default constructor is provided to
   * support storing fields in some containers that require a default constructor.
   */
  Field()        = default;
  Field(Field&&) = default;
  ~Field() = default;

  Field(const Field& f) { reset(*f.cs, *f.d); }

  /**
   * @brief Create a new field and allocate memory for grid defined by dims.
   *
   * @param dims a list of sizes for each dimension.
   *
   * This constructor allows the caller to pass dimension sizes directly to the constructor.
   *
   * @code
   * libField<double,2> f(10,20);
   * @endcode
   *
   * This will create a field with 200 elements, 10 along the first dimension, 20 along the second.
   *
   */
  template<typename... Dims>
  Field(Dims... dims)
  {
    reset(dims...);
  }

  /**
   * @brief Create a new field and allocate memory for the grid defined sizes.
   *
   * @param sizes an array of integers specifying the size of the field along each dimension.
   */
  template<typename I>
  Field(std::array<I, NUMDIMS> sizes)
  {
    reset(sizes);
  }
  Field(std::shared_ptr<cs_type> cs_) { reset(cs_); }

  Field(cs_type& cs_, array_type& d_) { reset(cs_, d_); };

  /**
   * @brief Reallocate a field with new dimensions.
   *
   * @param dims The size of the new field along each dimension.
   */
  template<typename... Dims>
  void reset(Dims... dims)
  {
    cs = std::make_shared<cs_type>(dims...);
    std::array<size_t, NUMDIMS> sizes({static_cast<size_t>(dims)...});
    d = std::make_shared<array_type>(sizes);
  }

  /**
   * @brief Reallocate a field with new dimensions.
   *
   * @param sizes An array of the new field sizes along each dimension.
   */
  template<typename I>
  void reset(std::array<I, NUMDIMS> sizes)
  {
    cs = std::make_shared<cs_type>(sizes);
    d = std::make_shared<array_type>(sizes);
  }

  /**
   * @brief Reallocate a field from an existing coordinate system.
   *
   * @param cs_ a shared pointer to an existing coordinate system.
   *
   * New memory will be allocated for the field elements, but not for
   * the coordinate system.
   */
  void reset(std::shared_ptr<cs_type> cs_)
  {
    cs = cs_;

    std::vector<size_t> sizes(NUMDIMS);
    for (size_t i = 0; i < NUMDIMS; ++i) sizes[i] = cs->size(i);

    d = std::make_shared<array_type>(sizes);
  }

  /**
   * @brief Reallocate a field from an existing coordinate system and field elements.
   *
   * @param cs_ a shared pointer to an existing coordinate system.
   * @param d_ a shared pointer to an existing array of field elements.
   *
   * No new memory is allocated. References to existing field elements
   * and coordinate system will be used.
   */
  void reset(cs_type& cs_, array_type& d_)
  {
    d = std::make_shared<array_type>(d_);
    cs = std::make_shared<cs_type>(cs_.getAxes());
  };

  // ELEMENT ACCESS

  /**
   * @brief Return const reference to an element of field with given index.
   * @param i An array-like container of indices.
   *
   * Example:
   *
   * The elements of a 2-dimensional array can be accessed using a 2-element vector.
   * @code
   * Field<double,2> f(10,20);
   * ...
   * std::vector<int> ind;
   *
   * ind[0] = 2;
   * ind[1] = 4;
   *
   * double val = f(ind); // get the (2,4) element of the field.
   * @endcode
   *
   * Any container that provides a subscript operator (operator[](int)) can be used.
   */
  template<typename I,
           typename std::enable_if<IsIndexCont<I>::value, int>::type = 0>
  const auto& operator()(I i) const
  {
    return (*d)(i);
  }

  /**
   * @brief Return a reference to an element of field with given index.
   * @param i An array-like container of indices.
   *
   * Example:
   *
   * The elements of a 2-dimensional array can be accessed using a 2-element vector.
   * @code
   * Field<double,2> f(10,20);
   * ...
   * std::vector<int> ind;
   *
   * ind[0] = 2;
   * ind[1] = 4;
   *
   * f(ind) = 10; // set the (2,4) element of the field.
   * @endcode
   *
   * Any container that provides a subscript operator (operator[](int)) can be used.
   */
  template<typename I,
           typename std::enable_if<IsIndexCont<I>::value, int>::type = 0>
  auto& operator()(I i)
  {
    return (*d)(i);
  }

  /**
   * @brief Return a const reference to an element of field with given index.
   * @param i the index of the element along the first dimension.
   * @param args the indexes of the element along the remaining dimensions.
   *
   * This function is a variadic template that allow a natural access to the field elements using the operator()(), rather
   * than having to use an index container..
   *
   * Example:
   *
   * @code
   * Field<double,2> f(10,20);
   * ...
   *
   * double val = f(2,4); // get the (2,4) element of the field.
   * @endcode
   */
  template<typename I, typename... Args,
           typename std::enable_if<std::is_integral<I>::value, int>::type = 0>
  const auto& operator()(I i, Args... args) const
  {
    return (*d)(std::array<I, NUMDIMS>({i, args...}));
  }

  /**
   * @brief Return a reference to an element of field with given index.
   * @param i the index of the element along the first dimension.
   * @param args the indexes of the element along the remaining dimensions.
   *
   *
   * This function is a variadic template that allow a natural access to the field elements using the operator()(), rather
   * than having to use an index container..
   *
   * Example:
   *
   * @code
   * Field<double,2> f(10,20);
   * ...
   *
   * f(2,4) = 10; // set the (2,4) element of the field.
   * @endcode
   */
  template<typename I, typename... Args,
           typename std::enable_if<std::is_integral<I>::value, int>::type = 0>
  auto& operator()(I i, Args... args)
  {
    return (*d)(std::array<I, NUMDIMS>({i, args...}));
  }

  template<typename I>
  auto operator[](I i) const
  {
    return (*d)[i];
  }

  /**
   * @brief Return a shared pointer to the coordinate system used by the field.
   */
  auto        getCoordinateSystemPtr() { return cs; };

  /**
   * @brief Return a reference to the coordinate system used by the field.
   */
  auto&       getCoordinateSystem() { return *cs; };

  /**
   * @brief Return a reference to the i'th axis in the coordinate system used by the field.
   * @param i The index (zero-offset) of the axis to return.
   */
  auto&       getAxis(size_t i) { return cs->getAxis(i); }

  /**
   * @brief Return a const reference to the coordinate system used by the field.
   */
  const auto& getCoordinateSystem() const { return *cs; };
  /**
   * @brief Return a const reference to the i'th axis in the coordinate system used by the field.
   * @param i The index (zero-offset) of the axis to return.
   */
  const auto& getAxis(size_t i) const { return cs->getAxis(i); }

  /**
   * @brief Set the coordinates of the coordinate system.
   * @param args Coordinates specification. The arguments are passed directly to CoordinateSystem::set() of the coordinate system used by the field.
   *
   * Coordinate system coordinate are set using a range discretizer. See range_discretizers::UnitformImp<T> for example.
   *
   * Example:
   *
   * To configure a field over the range [-1,1] along the x direction and [0:4] along the y direction,
   * @code
   * Field<double,2> f(10,20);
   * f.setCoordinateSystem( Uniform(-1,1), Uniform(0,4) );
   * @endcode
   */
  template<typename... Args>
  auto setCoordinateSystem(Args... args)
  {
    cs->set(args...);
  }

  /**
   * @brief Return the coordinate element specified by args.
   * @param args The index of the coordinate to retrief. The arguments are passed directly to CoordinateSystem::getCoord() of the coordinate system used by the field.
   *
   * Example:
   *
   * @code
   * Field<double,2> f(10,20);
   * ...
   *
   * auto coord = f.getCoord(2,4); // get the coordinate for the field element at (2,4)
   * double x = coord[0]; // get the x coordinate;
   * double y = coord[1]; // get the x coordinate;
   * @endcode
   */
  template<typename... Args>
  auto getCoord(Args... args) const
  {
    return cs->getCoord(args...);
  }

  /**
   * @brief Returns index of stored coordinate that lower bounds the given coordinate.
   *
   * This function forwards the arguments to Coordinate::lower_bound() of the coordinate system used by the field.
   * Along with upper_bound, it is useful for finding a range that bounds the given coordinate.
   */
  template<typename... Args>
  auto lower_bound(Args... args) const
  {
    return cs->lower_bound(args...);
  }

  /**
   * @brief Returns index of stored coordinate that upper bounds the given coordinate.
   *
   * This function forwards the arguments to Coordinate::upper_bound() of the coordinate system used by the field.
   * Along with lower_bound, it is useful for finding a range that bounds the given coordinate.
   */
  template<typename... Args>
  auto upper_bound(Args... args) const
  {
    return cs->upper_bound(args...);
  }

  /**
   * @brief returns index of stored coordinate that is closest to the given coordinate.
   */
  template<typename... Args>
  auto nearest(Args... args) const
  {
    return cs->nearest(args...);
  }

  // data access
  auto        getDataPtr() { return d; };
  const auto& getData() const { return *d; };
  auto&       getData() { return *d; };

  const auto data() const { return d->data(); }
  auto       data() { return d->data(); }

  template<int NDims>
  const auto slice(
      const boost::detail::multi_array::index_gen<NUMDIMS, NDims>& ind) const
  {
    // get sliced data
    auto d_ = d->operator[](ind);
    // get sliced coordinate system
    auto cs_ = cs->slice(ind);

    return Field<QUANT, NDims, COORD, viewND, view1D>(cs_, d_);
  }

  template<int NDims>
  auto slice(const boost::detail::multi_array::index_gen<NUMDIMS, NDims>& ind)
  {
    // get sliced data
    auto d_ = d->operator[](ind);
    // get sliced coordinate system
    auto cs_ = cs->slice(ind);

    return Field<QUANT, NDims, COORD, viewND, view1D>(cs_, d_);
  }

  auto size() const { return d->num_elements(); }
  auto size(int i) const { return d->shape()[i]; }

  /**
   * @brief Set each element of a field using a callable that takes an array-like container of *coordinates* as an argument and returns the element's value.
   *
   * This function evaluates the callable f for each coordinate and sets the field value to value
   * returned by callable. Field may be evaluated in PARRALLEL. Callable should
   * NOT depend on the order of being called.
   *
   * @param f a callable object (function, funtor, lambda, std::function, etc.)
   * that accepts one argument and returns a value.
   *
   * Argument passed to callable f will be an array of coordinates.
   *
   */
  template<typename F>
  auto set_f(F f) -> decltype((*d)(0) = f(cs->getCoord(_1d2nd(0))), void())
  {
    auto N = d->num_elements();
#pragma omp parallel for
    for (size_t i = 0; i < N; ++i) {
      auto ind             = this->_1d2nd(i);
      d->  operator()(ind) = f(cs->getCoord(ind));
    }
  }

  /**
   * @brief Set each element of a field using a callable that takes an array-like container of *coordinates* as an argument and returns the element's value.
   *
   * This function evaluates the callable f that returns an optional type (boost::optional or
   * std::optional) for each coordinate. If the optional is set, then the value of
   * of the field element is set. Otherwise, the field is left untouched. Field may be
   * evaluated in PARRALLEL. Callable should NOT depend on the order of being
   * called.
   *
   * @param f a callable object (function, funtor, lambda, std::function, etc.)
   * that accepts one argument and returns an optional value.
   *
   * Argument passed to callable f will be an array of coordinates.
   *
   */
  template<typename F>
  auto set_f(F f)
      -> decltype((bool)f(cs->getCoord(_1d2nd(0))),
                  (*d)(0) = f(cs->getCoord(_1d2nd(0))).value(), void())
  {
    auto N = d->num_elements();
#pragma omp parallel for
    for (size_t i = 0; i < N; ++i) {
      auto ind = this->_1d2nd(i);
      auto val = f(cs->getCoord(ind));
      if (val) d->operator()(ind) = val.value();
    }
  }

  /**
   * @brief Set each element of a field using a callable that takes an array-like container of *indices* and a pointer to a coordinate system as arguments and returns the element's value.
   *
   * Evaluates callable f for each indices and sets the field value to value
   * returned by callable. Callable is passed a container of indices and a
   * pointer to the coordinate system. Field may be evaluated in PARRALLEL.
   * Callable should NOT depend on the order of being called.
   *
   * @param f a callable object (function, funtor, lambda, std::function, etc.)
   * that accepts two arguments and returns a value.
   *
   * Arguments passed to callable f will be an array of indices, and a pointer
   * to the coordinate system.
   *
   */
  template<typename F>
  auto set_f(F f) -> decltype((*d)(0) = f(_1d2nd(0), cs), void())
  {
    auto N = d->num_elements();
#pragma omp parallel for
    for (size_t i = 0; i < N; ++i) {
      auto ind             = this->_1d2nd(i);
      d->  operator()(ind) = f(ind, cs);
    }
  }

  /**
   * @brief Set each element of a field using a callable that takes an array-like container of *indices* and a pointer to a coordinate system as arguments and returns the element's value.
   *
   * Evaluates callable f for each coordinate index. If the optional returned by
   * f is set, the field for the coordinate is set. Otherwise, the field is left
   * untouched. Callable is passed a container of indices and a pointer to the
   * coordinate system. Field may be evaluated in PARRALLEL. Callable should NOT
   * depend on the order of being called.
   *
   * @param f a callable object (function, funtor, lambda, std::function, etc.)
   * that accepts two arguments and returns a value.
   *
   * Arguments passed to callable f will be an array of indices, and a pointer
   * to the coordinate system.
   *
   */
  template<typename F>
  auto set_f(F f) -> decltype((bool)f(_1d2nd(0), cs),
                              (*d)(0) = f(_1d2nd(0), cs).value(), void())
  {
    auto N = d->num_elements();
#pragma omp parallel for
    for (size_t i = 0; i < N; ++i) {
      auto ind = this->_1d2nd(i);
      auto val = f(ind, cs);
      if (val) d->operator()(ind) = val.value();
    }
  }

  // NOTE: we wanted to combined set and set_f into a single function, but this
  // isn't possible in general. We cannot assume that the set_f version should
  // be called if a function is passed in, because the user may actually want to
  // store the functions in the field.

  /**
   * @brief Set all elements of a field to the value specified.
   * @param q the value to set each element to.
   *
   * The method will set element values in parallel using OpenMP.
   *
   */
  template<typename Q>
  auto set(Q q)
  {
    auto N = d->num_elements();
#pragma omp parallel for
    for (size_t i = 0; i < N; ++i) {
      auto ind             = this->_1d2nd(i);
      d->  operator()(ind) = std::move(q);
    }
  }

  // operator overloads

  friend std::ostream& operator<<(std::ostream& output, const Field& F)
  {
    auto N        = F.d->num_elements();
    auto last_ind = F._1d2nd(0);
    for (size_t i = 0; i < N; ++i) {
      auto ind = F._1d2nd(i);
      // we want to print out blank lines whenever an index gets reset
      for (size_t j = 0; j < NUMDIMS; ++j)
        if (ind[j] < last_ind[j]) output << "\n";

      for (size_t j = 0; j < NUMDIMS; ++j)
        output << F.cs->getAxis(j)[ind[j]] << " ";
      output << F.d->operator()(ind) << "\n";

      last_ind = ind;
    }
    return output;
  }

  /**
   * @brief Set the elements of a field to the value given.
   *
   * Field elements are set in parallel using OpenMP.
   */
  template<typename Q>
  Field& operator=(const Q& q)
  {
    auto N = d->num_elements();
#pragma omp parallel for
    for (size_t i = 0; i < N; ++i) {
      auto ind             = this->_1d2nd(i);
      d->  operator()(ind) = q;
    }
    return *this;
  }

  /**
   * @brief Add a constant value to each element in the field.
   * @param The value to add to each element.
   *
   * Field elements are updated in parallel using OpenMP.
   */
  template<typename Q>
  Field& operator+=(const Q& q)
  {
    auto N = d->num_elements();
#pragma omp parallel for
    for (size_t i = 0; i < N; ++i) {
      auto ind = this->_1d2nd(i);
      d->  operator()(ind) += q;
    }
    return *this;
  }

  /**
   * @brief Subtract a constant value to each element in the field.
   * @param The value to subtract from each element.
   *
   * Field elements are updated in parallel using OpenMP.
   */
  template<typename Q>
  Field& operator-=(const Q& q)
  {
    auto N = d->num_elements();
#pragma omp parallel for
    for (size_t i = 0; i < N; ++i) {
      auto ind = this->_1d2nd(i);
      d->  operator()(ind) -= q;
    }
    return *this;
  }

  /**
   * @brief Multiply each element in a field by a constant value.
   * @param The value to multiply each element by.
   *
   * Field elements are updated in parallel using OpenMP.
   */
  template<typename Q>
  Field& operator*=(const Q& q)
  {
    auto N = d->num_elements();
#pragma omp parallel for
    for (size_t i = 0; i < N; ++i) {
      auto ind = this->_1d2nd(i);
      d->  operator()(ind) *= q;
    }
    return *this;
  }

  /**
   * @brief Divide each element in a field by a constant value.
   * @param The value to divide each element by.
   *
   * Field elements are updated in parallel using OpenMP.
   */
  template<typename Q>
  Field& operator/=(const Q& q)
  {
    auto N = d->num_elements();
#pragma omp parallel for
    for (size_t i = 0; i < N; ++i) {
      auto ind = this->_1d2nd(i);
      d->  operator()(ind) /= q;
    }
    return *this;
  }

  Field& operator=(Field f)
  {
    d.swap(f.d);
    cs.swap(f.cs);
    return *this;
  }

  /**
   * @brief Add the element of a second field to each element of the field.
   * @param The field containing elements to be added to this field.
   *
   * Field elements are updated in parallel using OpenMP.
   *
   * Fields must be of the size.
   */
  Field& operator+=(const Field& f)
  {
    BOOST_ASSERT(f.size() == this->size());
    auto N = d->num_elements();
#pragma omp parallel for
    for (size_t i = 0; i < N; ++i) {
      auto ind = this->_1d2nd(i);
      d->  operator()(ind) += f(ind);
    }
    return *this;
  }

  /**
   * @brief Subtract the element of a second field from each element of the field.
   * @param The field containing elements to be subtracted from this field.
   *
   * Field elements are updated in parallel using OpenMP.
   *
   * Fields must be of the size.
   */
  Field& operator-=(const Field& f)
  {
    BOOST_ASSERT(f.size() == this->size());
    auto N = d->num_elements();
#pragma omp parallel for
    for (size_t i = 0; i < N; ++i) {
      auto ind = this->_1d2nd(i);
      d->  operator()(ind) -= f(ind);
    }
    return *this;
  }

  /**
   * @brief Multiply each element in the field by the corresponding element in a second field.
   * @param The field containing elements to be multiply by.
   *
   * Field elements are updated in parallel using OpenMP.
   *
   * Fields must be of the size.
   */
  Field& operator*=(const Field& f)
  {
    BOOST_ASSERT(f.size() == this->size());
    auto N = d->num_elements();
#pragma omp parallel for
    for (size_t i = 0; i < N; ++i) {
      auto ind = this->_1d2nd(i);
      d->  operator()(ind) *= f(ind);
    }
    return *this;
  }

  /**
   * @brief Divide each element in the field by the corresponding element in a second field.
   * @param The field containing elements to divide by.
   *
   * Field elements are updated in parallel using OpenMP.
   *
   * Fields must be of the size.
   */
  Field& operator/=(const Field& f)
  {
    BOOST_ASSERT(f.size() == this->size());
    auto N = d->num_elements();
#pragma omp parallel for
    for (size_t i = 0; i < N; ++i) {
      auto ind = this->_1d2nd(i);
      d->  operator()(ind) /= f(ind);
    }
    return *this;
  }
};

#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.
 *
 * ---------------------------------------------------------------------
 *
 * Author: Marc Fehling, Colorado State University, 2021
 *         Peter Munch, Technical University of Munich and Helmholtz-Zentrum
 *                      hereon, 2021
 *         Wolfgang Bangerth, Colorado State University, 2021
 */

// @sect3{Include files}

// 在以前的教程程序中，特别是在 step-27 和 step-40 中，已经使用和讨论了以下包含文件。

#include <deal.II/base/conditional_ostream.h>
#include <deal.II/base/index_set.h>
#include <deal.II/base/mpi.h>
#include <deal.II/base/quadrature_lib.h>
#include <deal.II/base/timer.h>

#include <deal.II/distributed/grid_refinement.h>
#include <deal.II/distributed/tria.h>

#include <deal.II/dofs/dof_handler.h>
#include <deal.II/dofs/dof_tools.h>

#include <deal.II/grid/grid_generator.h>

#include <deal.II/fe/fe_q.h>
#include <deal.II/fe/fe_series.h>

#include <deal.II/hp/fe_collection.h>
#include <deal.II/hp/refinement.h>

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

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

#include <algorithm>
#include <fstream>
#include <iostream>

// 为了实现负载平衡，我们将在单元格上分配单独的权重，为此我们将使用类  parallel::CellWeights.  。
#include <deal.II/distributed/cell_weights.h>

// 求解函数需要从直角坐标到极坐标的转换。 GeometricUtilities::Coordinates 命名空间提供了必要的工具。

#include <deal.II/base/function.h>
#include <deal.II/base/geometric_utilities.h>

// 以下包含的文件将启用MatrixFree功能。

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

// 我们将使用 LinearAlgebra::distributed::Vector 进行线性代数操作。

#include <deal.II/lac/la_parallel_vector.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_global_coarsening.h>
#include <deal.II/multigrid/multigrid.h>

namespace Step75
{
  using namespace dealii;
// @sect3{The Solution class template}

// 我们有一个分析性的方案可以使用。我们将用这个解来为问题的数值解施加边界条件。解决方案的表述需要转换为极坐标。为了从笛卡尔坐标转换到球面坐标，我们将使用 GeometricUtilities::Coordinates 命名空间的一个辅助函数。这个转换的前两个坐标对应于x-y面的极坐标。

  template <int dim>
  class Solution : public Function<dim>
  {
  public:
    Solution()
      : Function<dim>()
    {}

    virtual double value(const Point<dim> &p,
                         const unsigned int /*component*/) const override
    {
      const std::array<double, dim> p_sphere =
        GeometricUtilities::Coordinates::to_spherical(p);

      constexpr const double alpha = 2. / 3.;
      return std::pow(p_sphere[0], alpha) * std::sin(alpha * p_sphere[1]);
    }
  };

//  @sect3{Parameters}

// 在本教程中，我们将使用一个简化的参数集。这里也可以使用ParameterHandler类，但为了使本教程简短，我们决定使用简单的结构。所有这些参数的实际意图将在接下来的类中描述，在它们各自使用的位置。

// 下面的参数集控制着多网格机制的粗网格求解器、平滑器和网格间传输方案。我们用默认参数来填充它。

  struct MultigridParameters
  {
    struct
    {
      std::string  type            = "cg_with_amg";
      unsigned int maxiter         = 10000;
      double       abstol          = 1e-20;
      double       reltol          = 1e-4;
      unsigned int smoother_sweeps = 1;
      unsigned int n_cycles        = 1;
      std::string  smoother_type   = "ILU";
    } coarse_solver;

    struct
    {
      std::string  type                = "chebyshev";
      double       smoothing_range     = 20;
      unsigned int degree              = 5;
      unsigned int eig_cg_n_iterations = 20;
    } smoother;

    struct
    {
      MGTransferGlobalCoarseningTools::PolynomialCoarseningSequenceType
        p_sequence = MGTransferGlobalCoarseningTools::
          PolynomialCoarseningSequenceType::decrease_by_one;
      bool perform_h_transfer = true;
    } transfer;
  };

// 这是该问题类的一般参数结构。你会发现这个结构分为几个类别，包括一般的运行时参数、级别限制、细化和粗化分数，以及单元加权的参数。它还包含一个上述结构的实例，用于多网格参数，这些参数将被传递给多网格算法。

  struct Parameters
  {
    unsigned int n_cycles         = 8;
    double       tolerance_factor = 1e-12;

    MultigridParameters mg_data;

    unsigned int min_h_level            = 5;
    unsigned int max_h_level            = 12;
    unsigned int min_p_degree           = 2;
    unsigned int max_p_degree           = 6;
    unsigned int max_p_level_difference = 1;

    double refine_fraction    = 0.3;
    double coarsen_fraction   = 0.03;
    double p_refine_fraction  = 0.9;
    double p_coarsen_fraction = 0.9;

    double weighting_factor   = 1e6;
    double weighting_exponent = 1.;
  };

//  @sect3{Matrix-free Laplace operator}

// 这是一个无矩阵的拉普拉斯算子的实现，基本上将接管其他教程中的`assemble_system()`函数的部分。所有成员函数的含义将在后面的定义中解释。

// 我们将使用FEEvaluation类来评估正交点的解向量并进行积分。与其他教程不同的是，模板参数`度数`被设置为  $-1$  ，`一维正交数`被设置为  $0$  。在这种情况下，FEEvaluation会动态地选择正确的多项式度数和正交点的数量。在这里，我们为FEEvaluation引入一个带有正确模板参数的别名，这样我们以后就不用担心这些参数了。

  template <int dim, typename number>
  class LaplaceOperator : public Subscriptor
  {
  public:
    using VectorType = LinearAlgebra::distributed::Vector<number>;

    using FECellIntegrator = FEEvaluation<dim, -1, 0, 1, number>;

    LaplaceOperator() = default;

    LaplaceOperator(const hp::MappingCollection<dim> &mapping,
                    const DoFHandler<dim> &           dof_handler,
                    const hp::QCollection<dim> &      quad,
                    const AffineConstraints<number> & constraints,
                    VectorType &                      system_rhs);

    void reinit(const hp::MappingCollection<dim> &mapping,
                const DoFHandler<dim> &           dof_handler,
                const hp::QCollection<dim> &      quad,
                const AffineConstraints<number> & constraints,
                VectorType &                      system_rhs);

    types::global_dof_index m() const;

    number el(unsigned int, unsigned int) const;

    void initialize_dof_vector(VectorType &vec) const;

    void vmult(VectorType &dst, const VectorType &src) const;

    void Tvmult(VectorType &dst, const VectorType &src) const;

    const TrilinosWrappers::SparseMatrix &get_system_matrix() const;

    void compute_inverse_diagonal(VectorType &diagonal) const;

  private:
    void do_cell_integral_local(FECellIntegrator &integrator) const;

    void do_cell_integral_global(FECellIntegrator &integrator,
                                 VectorType &      dst,
                                 const VectorType &src) const;

    void do_cell_integral_range(
      const MatrixFree<dim, number> &              matrix_free,
      VectorType &                                 dst,
      const VectorType &                           src,
      const std::pair<unsigned int, unsigned int> &range) const;

    MatrixFree<dim, number> matrix_free;

// 为了用AMG预处理程序解决最粗层次的方程系统，我们需要一个最粗层次的实际系统矩阵。为此，我们提供了一种机制，可以选择从无矩阵公式中计算出一个矩阵，为此我们引入了一个专门的SparseMatrix对象。在默认情况下，这个矩阵保持为空。一旦`get_system_matrix()`被调用，这个矩阵就会被填充（懒惰分配）。由于这是一个 "const "函数，我们需要在这里使用 "mutable "关键字。我们还需要一个约束对象来构建矩阵。

    AffineConstraints<number>              constraints;
    mutable TrilinosWrappers::SparseMatrix system_matrix;
  };

// 下面的部分包含了初始化和重新初始化该类的函数。特别是，这些函数初始化了内部的MatrixFree实例。为了简单起见，我们还计算了系统右侧的向量。

  template <int dim, typename number>
  LaplaceOperator<dim, number>::LaplaceOperator(
    const hp::MappingCollection<dim> &mapping,
    const DoFHandler<dim> &           dof_handler,
    const hp::QCollection<dim> &      quad,
    const AffineConstraints<number> & constraints,
    VectorType &                      system_rhs)
  {
    this->reinit(mapping, dof_handler, quad, constraints, system_rhs);
  }

  template <int dim, typename number>
  void LaplaceOperator<dim, number>::reinit(
    const hp::MappingCollection<dim> &mapping,
    const DoFHandler<dim> &           dof_handler,
    const hp::QCollection<dim> &      quad,
    const AffineConstraints<number> & constraints,
    VectorType &                      system_rhs)
  {

// 清除内部数据结构（在操作者被重复使用的情况下）。

    this->system_matrix.clear();

// 复制约束条件，因为以后在计算系统矩阵时可能需要它们。

    this->constraints.copy_from(constraints);

// 设置MatrixFree。在正交点，我们只需要评估解的梯度，并用形状函数的梯度进行测试，所以我们只需要设置标志`update_gradients`。

    typename MatrixFree<dim, number>::AdditionalData data;
    data.mapping_update_flags = update_gradients;

    matrix_free.reinit(mapping, dof_handler, constraints, quad, data);

// 计算右手边的向量。为此，我们设置了第二个MatrixFree实例，它使用一个修改过的AffineConstraints，不包含由于Dirichlet-边界条件的约束。这个修改过的算子被应用于一个只设置了迪里希特值的向量。其结果是负的右手边向量。

    {
      AffineConstraints<number> constraints_without_dbc;

      IndexSet locally_relevant_dofs;
      DoFTools::extract_locally_relevant_dofs(dof_handler,
                                              locally_relevant_dofs);
      constraints_without_dbc.reinit(locally_relevant_dofs);

      DoFTools::make_hanging_node_constraints(dof_handler,
                                              constraints_without_dbc);
      constraints_without_dbc.close();

      VectorType b, x;

      this->initialize_dof_vector(system_rhs);

      MatrixFree<dim, number> matrix_free;
      matrix_free.reinit(
        mapping, dof_handler, constraints_without_dbc, quad, data);

      matrix_free.initialize_dof_vector(b);
      matrix_free.initialize_dof_vector(x);

      constraints.distribute(x);

      matrix_free.cell_loop(&LaplaceOperator::do_cell_integral_range,
                            this,
                            b,
                            x);

      constraints.set_zero(b);

      system_rhs -= b;
    }
  }

// 以下函数是多网格算法隐含需要的，包括平滑器。

// 由于我们没有矩阵，所以要向DoFHandler查询自由度的数量。

  template <int dim, typename number>
  types::global_dof_index LaplaceOperator<dim, number>::m() const
  {
    return matrix_free.get_dof_handler().n_dofs();
  }

// 访问矩阵中的一个特定元素。这个函数既不需要也没有实现，但是，在编译程序时需要它。

  template <int dim, typename number>
  number LaplaceOperator<dim, number>::el(unsigned int, unsigned int) const
  {
    Assert(false, ExcNotImplemented());
    return 0;
  }

// 初始化给定的向量。我们只是把这个任务委托给同名的MatrixFree函数。

  template <int dim, typename number>
  void
  LaplaceOperator<dim, number>::initialize_dof_vector(VectorType &vec) const
  {
    matrix_free.initialize_dof_vector(vec);
  }

// 在MatrixFree的帮助下，通过在所有单元中循环进行运算评估，并评估单元积分的效果（参见。`do_cell_integral_local()`和`do_cell_integral_global()`）。)

  template <int dim, typename number>
  void LaplaceOperator<dim, number>::vmult(VectorType &      dst,
                                           const VectorType &src) const
  {
    this->matrix_free.cell_loop(
      &LaplaceOperator::do_cell_integral_range, this, dst, src, true);
  }

// 执行转置的运算符评估。由于我们考虑的是对称的 "矩阵"，这个函数可以简单地将其任务委托给vmult()。

  template <int dim, typename number>
  void LaplaceOperator<dim, number>::Tvmult(VectorType &      dst,
                                            const VectorType &src) const
  {
    this->vmult(dst, src);
  }

// 由于我们没有一个系统矩阵，我们不能循环计算矩阵的对角线项。相反，我们通过对单位基向量进行一连串的运算符评估来计算对角线。为此，我们使用了MatrixFreeTools命名空间中的一个优化函数。之后再手动进行反转。

  template <int dim, typename number>
  void LaplaceOperator<dim, number>::compute_inverse_diagonal(
    VectorType &diagonal) const
  {
    MatrixFreeTools::compute_diagonal(matrix_free,
                                      diagonal,
                                      &LaplaceOperator::do_cell_integral_local,
                                      this);

    for (auto &i : diagonal)
      i = (std::abs(i) > 1.0e-10) ? (1.0 / i) : 1.0;
  }

// 在无矩阵的情况下，在这个类的初始化过程中没有设置系统矩阵。因此，如果需要的话，它必须在这里被计算出来。由于矩阵在本教程中只对线性元素进行计算（在粗略的网格上），这一点是可以接受的。矩阵的条目是通过运算符的评估序列得到的。为此，使用了优化函数 MatrixFreeTools::compute_matrix() 。矩阵只有在尚未设置的情况下才会被计算（懒惰分配）。

  template <int dim, typename number>
  const TrilinosWrappers::SparseMatrix &
  LaplaceOperator<dim, number>::get_system_matrix() const
  {
    if (system_matrix.m() == 0 && system_matrix.n() == 0)
      {
        const auto &dof_handler = this->matrix_free.get_dof_handler();

        TrilinosWrappers::SparsityPattern dsp(
          dof_handler.locally_owned_dofs(),
          dof_handler.get_triangulation().get_communicator());

        DoFTools::make_sparsity_pattern(dof_handler, dsp, this->constraints);

        dsp.compress();
        system_matrix.reinit(dsp);

        MatrixFreeTools::compute_matrix(
          matrix_free,
          constraints,
          system_matrix,
          &LaplaceOperator::do_cell_integral_local,
          this);
      }

    return this->system_matrix;
  }

// 对一个单元格批处理进行单元格积分，不需要收集和分散数值。MatrixFreeTools函数需要这个函数，因为这些函数直接对FEEvaluation的缓冲区进行操作。

  template <int dim, typename number>
  void LaplaceOperator<dim, number>::do_cell_integral_local(
    FECellIntegrator &integrator) const
  {
    integrator.evaluate(EvaluationFlags::gradients);

    for (unsigned int q = 0; q < integrator.n_q_points; ++q)
      integrator.submit_gradient(integrator.get_gradient(q), q);

    integrator.integrate(EvaluationFlags::gradients);
  }

// 与上述相同，但可以访问全局向量。

  template <int dim, typename number>
  void LaplaceOperator<dim, number>::do_cell_integral_global(
    FECellIntegrator &integrator,
    VectorType &      dst,
    const VectorType &src) const
  {
    integrator.gather_evaluate(src, EvaluationFlags::gradients);

    for (unsigned int q = 0; q < integrator.n_q_points; ++q)
      integrator.submit_gradient(integrator.get_gradient(q), q);

    integrator.integrate_scatter(EvaluationFlags::gradients, dst);
  }

// 这个函数在一个单元格批次范围内的所有单元格批次上循环，并调用上述函数。

  template <int dim, typename number>
  void LaplaceOperator<dim, number>::do_cell_integral_range(
    const MatrixFree<dim, number> &              matrix_free,
    VectorType &                                 dst,
    const VectorType &                           src,
    const std::pair<unsigned int, unsigned int> &range) const
  {
    FECellIntegrator integrator(matrix_free, range);

    for (unsigned cell = range.first; cell < range.second; ++cell)
      {
        integrator.reinit(cell);

        do_cell_integral_global(integrator, dst, src);
      }
  }

//  @sect3{Solver and preconditioner}
// @sect4{Conjugate-gradient solver with multigrid preconditioner}

// 这个函数用一连串提供的多网格对象来解决方程组。它的目的是为了尽可能的通用，因此有许多模板参数。

  template <typename VectorType,
            int dim,
            typename SystemMatrixType,
            typename LevelMatrixType,
            typename MGTransferType>
  static void
  mg_solve(SolverControl &            solver_control,
           VectorType &               dst,
           const VectorType &         src,
           const MultigridParameters &mg_data,
           const DoFHandler<dim> &    dof,
           const SystemMatrixType &   fine_matrix,
           const MGLevelObject<std::unique_ptr<LevelMatrixType>> &mg_matrices,
           const MGTransferType &                                 mg_transfer)
  {
    AssertThrow(mg_data.coarse_solver.type == "cg_with_amg",
                ExcNotImplemented());
    AssertThrow(mg_data.smoother.type == "chebyshev", ExcNotImplemented());

    const unsigned int min_level = mg_matrices.min_level();
    const unsigned int max_level = mg_matrices.max_level();

    using SmootherPreconditionerType = DiagonalMatrix<VectorType>;
    using SmootherType               = PreconditionChebyshev<LevelMatrixType,
                                               VectorType,
                                               SmootherPreconditionerType>;
    using PreconditionerType = PreconditionMG<dim, VectorType, MGTransferType>;

// 我们在这里初始化电平运算符和切比雪夫平滑器。

    mg::Matrix<VectorType> mg_matrix(mg_matrices);

    MGLevelObject<typename SmootherType::AdditionalData> smoother_data(
      min_level, max_level);

    for (unsigned int level = min_level; level <= max_level; level++)
      {
        smoother_data[level].preconditioner =
          std::make_shared<SmootherPreconditionerType>();
        mg_matrices[level]->compute_inverse_diagonal(
          smoother_data[level].preconditioner->get_vector());
        smoother_data[level].smoothing_range = mg_data.smoother.smoothing_range;
        smoother_data[level].degree          = mg_data.smoother.degree;
        smoother_data[level].eig_cg_n_iterations =
          mg_data.smoother.eig_cg_n_iterations;
      }

    MGSmootherPrecondition<LevelMatrixType, SmootherType, VectorType>
      mg_smoother;
    mg_smoother.initialize(mg_matrices, smoother_data);

// 接下来，我们初始化粗略网格求解器。我们使用共轭梯度法和AMG作为预处理程序。

    ReductionControl coarse_grid_solver_control(mg_data.coarse_solver.maxiter,
                                                mg_data.coarse_solver.abstol,
                                                mg_data.coarse_solver.reltol,
                                                false,
                                                false);
    SolverCG<VectorType> coarse_grid_solver(coarse_grid_solver_control);

    std::unique_ptr<MGCoarseGridBase<VectorType>> mg_coarse;

    TrilinosWrappers::PreconditionAMG                 precondition_amg;
    TrilinosWrappers::PreconditionAMG::AdditionalData amg_data;
    amg_data.smoother_sweeps = mg_data.coarse_solver.smoother_sweeps;
    amg_data.n_cycles        = mg_data.coarse_solver.n_cycles;
    amg_data.smoother_type   = mg_data.coarse_solver.smoother_type.c_str();

    precondition_amg.initialize(mg_matrices[min_level]->get_system_matrix(),
                                amg_data);

    mg_coarse =
      std::make_unique<MGCoarseGridIterativeSolver<VectorType,
                                                   SolverCG<VectorType>,
                                                   LevelMatrixType,
                                                   decltype(precondition_amg)>>(
        coarse_grid_solver, *mg_matrices[min_level], precondition_amg);

// 最后，我们创建Multigrid对象，将其转换为预处理程序，并在共轭梯度求解器中使用它来解决线性方程组。

    Multigrid<VectorType> mg(
      mg_matrix, *mg_coarse, mg_transfer, mg_smoother, mg_smoother);

    PreconditionerType preconditioner(dof, mg, mg_transfer);

    SolverCG<VectorType>(solver_control)
      .solve(fine_matrix, dst, src, preconditioner);
  }

//  @sect4{Hybrid polynomial/geometric-global-coarsening multigrid preconditioner}

// 上述函数处理给定的多网格对象序列的实际解决方案。这个函数创建了实际的多重网格层次，特别是运算符，以及作为MGTransferGlobalCoarsening对象的转移运算符。

  template <typename VectorType, typename OperatorType, int dim>
  void solve_with_gmg(SolverControl &                  solver_control,
                      const OperatorType &             system_matrix,
                      VectorType &                     dst,
                      const VectorType &               src,
                      const MultigridParameters &      mg_data,
                      const hp::MappingCollection<dim> mapping_collection,
                      const DoFHandler<dim> &          dof_handler,
                      const hp::QCollection<dim> &     quadrature_collection)
  {

// 为每个多网格层次创建一个DoFHandler和操作符，以及，创建转移操作符。为了能够设置运算符，我们需要一组DoFHandler，通过p或h的全局粗化来创建。

// 如果没有要求h-transfer，我们为`emplace_back()`函数提供一个空的删除器，因为我们的DoFHandler的Triangulation是一个外部字段，其析构器在其他地方被调用。

    MGLevelObject<DoFHandler<dim>>                     dof_handlers;
    MGLevelObject<std::unique_ptr<OperatorType>>       operators;
    MGLevelObject<MGTwoLevelTransfer<dim, VectorType>> transfers;

    std::vector<std::shared_ptr<const Triangulation<dim>>>
      coarse_grid_triangulations;
    if (mg_data.transfer.perform_h_transfer)
      coarse_grid_triangulations =
        MGTransferGlobalCoarseningTools::create_geometric_coarsening_sequence(
          dof_handler.get_triangulation());
    else
      coarse_grid_triangulations.emplace_back(
        const_cast<Triangulation<dim> *>(&(dof_handler.get_triangulation())),
        [](auto &) {});

// 确定多栅格操作的总层数，并为所有层数分配足够的内存。

    const unsigned int n_h_levels = coarse_grid_triangulations.size() - 1;

    const auto get_max_active_fe_degree = [&](const auto &dof_handler) {
      unsigned int max = 0;

      for (auto &cell : dof_handler.active_cell_iterators())
        if (cell->is_locally_owned())
          max =
            std::max(max, dof_handler.get_fe(cell->active_fe_index()).degree);

      return Utilities::MPI::max(max, MPI_COMM_WORLD);
    };

    const unsigned int n_p_levels =
      MGTransferGlobalCoarseningTools::create_polynomial_coarsening_sequence(
        get_max_active_fe_degree(dof_handler), mg_data.transfer.p_sequence)
        .size();

    std::map<unsigned int, unsigned int> fe_index_for_degree;
    for (unsigned int i = 0; i < dof_handler.get_fe_collection().size(); ++i)
      {
        const unsigned int degree = dof_handler.get_fe(i).degree;
        Assert(fe_index_for_degree.find(degree) == fe_index_for_degree.end(),
               ExcMessage("FECollection does not contain unique degrees."));
        fe_index_for_degree[degree] = i;
      }

    unsigned int minlevel   = 0;
    unsigned int minlevel_p = n_h_levels;
    unsigned int maxlevel   = n_h_levels + n_p_levels - 1;

    dof_handlers.resize(minlevel, maxlevel);
    operators.resize(minlevel, maxlevel);
    transfers.resize(minlevel, maxlevel);

// 从最小（最粗）到最大（最细）级别的循环，并相应地设置DoFHandler。我们从h层开始，在这里我们分布在越来越细的网格上的线性元素。

    for (unsigned int l = 0; l < n_h_levels; ++l)
      {
        dof_handlers[l].reinit(*coarse_grid_triangulations[l]);
        dof_handlers[l].distribute_dofs(dof_handler.get_fe_collection());
      }

// 在我们达到最细的网格后，我们将调整每一层的多项式度数。我们反向迭代我们的数据结构，从包含所有活动FE指数信息的最细网格开始。然后我们逐级降低每个单元的多项式度数。

    for (unsigned int i = 0, l = maxlevel; i < n_p_levels; ++i, --l)
      {
        dof_handlers[l].reinit(dof_handler.get_triangulation());

        if (l == maxlevel) // finest level
          {
            auto &dof_handler_mg = dof_handlers[l];

            auto cell_other = dof_handler.begin_active();
            for (auto &cell : dof_handler_mg.active_cell_iterators())
              {
                if (cell->is_locally_owned())
                  cell->set_active_fe_index(cell_other->active_fe_index());
                cell_other++;
              }
          }
        else // coarse level
          {
            auto &dof_handler_fine   = dof_handlers[l + 1];
            auto &dof_handler_coarse = dof_handlers[l + 0];

            auto cell_other = dof_handler_fine.begin_active();
            for (auto &cell : dof_handler_coarse.active_cell_iterators())
              {
                if (cell->is_locally_owned())
                  {
                    const unsigned int next_degree =
                      MGTransferGlobalCoarseningTools::
                        create_next_polynomial_coarsening_degree(
                          cell_other->get_fe().degree,
                          mg_data.transfer.p_sequence);
                    Assert(fe_index_for_degree.find(next_degree) !=
                             fe_index_for_degree.end(),
                           ExcMessage("Next polynomial degree in sequence "
                                      "does not exist in FECollection."));

                    cell->set_active_fe_index(fe_index_for_degree[next_degree]);
                  }
                cell_other++;
              }
          }

        dof_handlers[l].distribute_dofs(dof_handler.get_fe_collection());
      }

// 接下来，我们将在每个多重网格层面上创建所有额外需要的数据结构。这涉及到确定具有同质Dirichlet边界条件的约束，并像在活动层上一样建立运算器。

    MGLevelObject<AffineConstraints<typename VectorType::value_type>>
      constraints(minlevel, maxlevel);

    for (unsigned int level = minlevel; level <= maxlevel; ++level)
      {
        const auto &dof_handler = dof_handlers[level];
        auto &      constraint  = constraints[level];

        IndexSet locally_relevant_dofs;
        DoFTools::extract_locally_relevant_dofs(dof_handler,
                                                locally_relevant_dofs);
        constraint.reinit(locally_relevant_dofs);

        DoFTools::make_hanging_node_constraints(dof_handler, constraint);
        VectorTools::interpolate_boundary_values(mapping_collection,
                                                 dof_handler,
                                                 0,
                                                 Functions::ZeroFunction<dim>(),
                                                 constraint);
        constraint.close();

        VectorType dummy;

        operators[level] = std::make_unique<OperatorType>(mapping_collection,
                                                          dof_handler,
                                                          quadrature_collection,
                                                          constraint,
                                                          dummy);
      }

//根据多网格求解器类的需要，在单个算子中设置网格间算子和收集转移算子。

    for (unsigned int level = minlevel; level < minlevel_p; ++level)
      transfers[level + 1].reinit_geometric_transfer(dof_handlers[level + 1],
                                                     dof_handlers[level],
                                                     constraints[level + 1],
                                                     constraints[level]);

    for (unsigned int level = minlevel_p; level < maxlevel; ++level)
      transfers[level + 1].reinit_polynomial_transfer(dof_handlers[level + 1],
                                                      dof_handlers[level],
                                                      constraints[level + 1],
                                                      constraints[level]);

    MGTransferGlobalCoarsening<dim, VectorType> transfer(
      transfers, [&](const auto l, auto &vec) {
        operators[l]->initialize_dof_vector(vec);
      });

// 最后，继续用多网格法解决问题。

    mg_solve(solver_control,
             dst,
             src,
             mg_data,
             dof_handler,
             system_matrix,
             operators,
             transfer);
  }

//  @sect3{The LaplaceProblem class template}

// 现在，我们将最后声明这个程序的主类，它在随后的精炼函数空间上求解拉普拉斯方程。它的结构看起来很熟悉，因为它与  step-27  和  step-40  的主类类似。基本上只增加了两个。

// - 持有系统矩阵的SparseMatrix对象已经被MatrixFree公式中的LaplaceOperator类对象所取代。

// - 加入了一个 parallel::CellWeights, 的对象，它将帮助我们实现负载平衡。

  template <int dim>
  class LaplaceProblem
  {
  public:
    LaplaceProblem(const Parameters &parameters);

    void run();

  private:
    void initialize_grid();
    void setup_system();
    void print_diagnostics();
    void solve_system();
    void compute_indicators();
    void adapt_resolution();
    void output_results(const unsigned int cycle);

    MPI_Comm mpi_communicator;

    const Parameters prm;

    parallel::distributed::Triangulation<dim> triangulation;
    DoFHandler<dim>                           dof_handler;

    hp::MappingCollection<dim> mapping_collection;
    hp::FECollection<dim>      fe_collection;
    hp::QCollection<dim>       quadrature_collection;
    hp::QCollection<dim - 1>   face_quadrature_collection;

    IndexSet locally_owned_dofs;
    IndexSet locally_relevant_dofs;

    AffineConstraints<double> constraints;

    LaplaceOperator<dim, double>               laplace_operator;
    LinearAlgebra::distributed::Vector<double> locally_relevant_solution;
    LinearAlgebra::distributed::Vector<double> system_rhs;

    std::unique_ptr<FESeries::Legendre<dim>>    legendre;
    std::unique_ptr<parallel::CellWeights<dim>> cell_weights;

    Vector<float> estimated_error_per_cell;
    Vector<float> hp_decision_indicators;

    ConditionalOStream pcout;
    TimerOutput        computing_timer;
  };

//  @sect3{The LaplaceProblem class implementation}
// @sect4{Constructor}

// 构造函数以一个初始化器列表开始，该列表看起来与  step-40  的列表相似。我们再次准备好ConditionalOStream对象，只允许第一个进程在控制台输出任何东西，并正确初始化计算计时器。

  template <int dim>
  LaplaceProblem<dim>::LaplaceProblem(const Parameters &parameters)
    : mpi_communicator(MPI_COMM_WORLD)
    , prm(parameters)
    , triangulation(mpi_communicator)
    , dof_handler(triangulation)
    , pcout(std::cout,
            (Utilities::MPI::this_mpi_process(mpi_communicator) == 0))
    , computing_timer(mpi_communicator,
                      pcout,
                      TimerOutput::summary,
                      TimerOutput::wall_times)
  {
    Assert(prm.min_h_level <= prm.max_h_level,
           ExcMessage(
             "Triangulation level limits have been incorrectly set up."));
    Assert(prm.min_p_degree <= prm.max_p_degree,
           ExcMessage("FECollection degrees have been incorrectly set up."));

// 我们需要在构造函数的实际主体中为hp-functionality准备数据结构，并在参数结构的指定范围内为每个度数创建相应的对象。由于我们只处理非扭曲的矩形单元，在这种情况下，一个线性映射对象就足够了。

// 在参数结构中，我们为函数空间以合理的分辨率运行的层级提供范围。多网格算法需要在最粗的层次上使用线性元素。所以我们从最低的多项式度数开始，用连续的高度数填充集合，直到达到用户指定的最大值。

    mapping_collection.push_back(MappingQ1<dim>());

    for (unsigned int degree = 1; degree <= prm.max_p_degree; ++degree)
      {
        fe_collection.push_back(FE_Q<dim>(degree));
        quadrature_collection.push_back(QGauss<dim>(degree + 1));
        face_quadrature_collection.push_back(QGauss<dim - 1>(degree + 1));
      }

// 由于我们的FECollection包含的有限元比我们想用于求解的有限元近似值要多，我们想限制活动FE指数可以操作的范围。为此，FECollection类允许注册一个层次结构，在p-精简和p-粗化的情况下，分别决定后续的和前面的有限元。 hp::Refinement 命名空间中的所有函数都会参考这个层次结构来确定未来的FE指数。我们将注册这样一个层次结构，它只对建议范围内的多项式程度的有限元起作用  [min_p_degree, max_p_degree]  。

    const unsigned int min_fe_index = prm.min_p_degree - 1;
    fe_collection.set_hierarchy(

   /*下一个_index=  */
      [](const typename hp::FECollection<dim> &fe_collection,
         const unsigned int                    fe_index) -> unsigned int {
        return ((fe_index + 1) < fe_collection.size()) ? fe_index + 1 :
                                                         fe_index;
      },
    /*上一页_index=  */
      [min_fe_index](const typename hp::FECollection<dim> &,
                     const unsigned int fe_index) -> unsigned int {
        Assert(fe_index >= min_fe_index,
               ExcMessage("Finite element is not part of hierarchy!"));
        return (fe_index > min_fe_index) ? fe_index - 1 : fe_index;
      });

// 我们以默认配置初始化 FESeries::Legendre 对象，以便进行平滑度估计。

    legendre = std::make_unique<FESeries::Legendre<dim>>(
      SmoothnessEstimator::Legendre::default_fe_series(fe_collection));

// 接下来的部分会很棘手。在执行细化的过程中，有几个hp-算法需要干扰三角形对象上的实际细化过程。我们通过将几个函数连接到 Triangulation::Signals: 信号，在实际细化过程中的不同阶段被调用，并触发所有连接的函数来做到这一点。我们需要这个功能来实现负载平衡和限制相邻单元的多项式度数。

// 对于前者，我们希望给每个单元分配一个权重，这个权重与它未来的有限元的自由度数成正比。该库提供了一个类 parallel::CellWeights ，允许在细化过程中的正确位置轻松地附加单个权重，即在所有细化和粗化标志被正确设置为hp-adaptation之后，以及在即将发生的负载平衡的重新划分之前。可以注册一些函数，这些函数将以  $a (n_\text{dofs})^b$  提供的一对参数的形式附加权重  $(a,b)$  。我们在下文中注册了这样一个函数。每个单元在创建时将被赋予一个恒定的权重，这个值是1000（见  Triangulation::Signals::cell_weight).  ）。

// 为了实现负载平衡，像我们使用的高效求解器应该与拥有的自由度数量成线性比例。此外，为了增加我们想要附加的权重的影响，确保单个权重将超过这个基础权重的数量级。我们相应地设置单元加权的参数。大的加权系数为 $10^6$ ，指数为 $1$  。

    cell_weights = std::make_unique<parallel::CellWeights<dim>>(
      dof_handler,
      parallel::CellWeights<dim>::ndofs_weighting(
        {prm.weighting_factor, prm.weighting_exponent}));

// 在h-adaptive应用中，我们通过限制相邻单元的细化水平的差异为1来确保2:1的网格平衡。通过下面代码片段中的第二个调用，我们将确保相邻单元的p级数也是如此：未来有限元的级数不允许相差超过指定的差值。函数 hp::Refinement::limit_p_level_difference 可以处理这个问题，但需要与并行环境中的一个非常特殊的信号相连。问题是，我们需要知道网格的实际细化情况，以便相应地设置未来的FE指数。由于我们要求p4est神谕进行细化，我们需要确保Triangulation已经先用神谕的适应标志进行了更新。 parallel::distributed::TemporarilyMatchRefineFlags 的实例化在其生命期内正是如此。因此，我们将在限制p级差之前创建这个类的对象，并将相应的lambda函数连接到信号 Triangulation::Signals::post_p4est_refinement, 上，该信号将在神谕被完善之后，但在三角法被完善之前被触发。此外，我们指定这个函数将被连接到信号的前面，以确保修改在连接到同一信号的任何其他函数之前进行。

    triangulation.signals.post_p4est_refinement.connect(
      [&, min_fe_index]() {
        const parallel::distributed::TemporarilyMatchRefineFlags<dim>
          refine_modifier(triangulation);
        hp::Refinement::limit_p_level_difference(dof_handler,
                                                 prm.max_p_level_difference,
                                                 /*包含=  */ min_fe_index);
      },
      boost::signals2::at_front);
  }

//  @sect4{LaplaceProblem::initialize_grid}

// 对于L型域，我们可以使用 GridGenerator::hyper_L() 这个函数，如 step-50 中所演示的。然而在二维的情况下，该函数只去除第一象限，而在我们的方案中我们需要去除第四象限。因此，我们将使用一个不同的函数 GridGenerator::subdivided_hyper_L() ，它给我们更多的选择来创建网格。此外，我们在制定该函数时，也会生成一个三维网格：二维L型域基本上会在正Z方向上拉长1。

// 我们首先假装建立一个  GridGenerator::subdivided_hyper_rectangle().  我们需要提供的参数是左下角和右上角的点对象，以及基本网格在每个方向的重复次数。我们为前两个维度提供这些参数，对更高的第三维度单独处理。

// 为了创建一个L型域，我们需要去除多余的单元。为此，我们相应地指定 cells_to_remove 。我们希望从负方向的每一个单元格中移除一个单元格，但从正的x方向移除一个。

// 最后，我们提供与所提供的最小网格细化水平相对应的初始细化数。此外，我们相应地设置初始活动FE指数。

  template <int dim>
  void LaplaceProblem<dim>::initialize_grid()
  {
    TimerOutput::Scope t(computing_timer, "initialize grid");

    std::vector<unsigned int> repetitions(dim);
    Point<dim>                bottom_left, top_right;
    for (unsigned int d = 0; d < dim; ++d)
      if (d < 2)
        {
          repetitions[d] = 2;
          bottom_left[d] = -1.;
          top_right[d]   = 1.;
        }
      else
        {
          repetitions[d] = 1;
          bottom_left[d] = 0.;
          top_right[d]   = 1.;
        }

    std::vector<int> cells_to_remove(dim, 1);
    cells_to_remove[0] = -1;

    GridGenerator::subdivided_hyper_L(
      triangulation, repetitions, bottom_left, top_right, cells_to_remove);

    triangulation.refine_global(prm.min_h_level);

    const unsigned int min_fe_index = prm.min_p_degree - 1;
    for (const auto &cell : dof_handler.active_cell_iterators())
      if (cell->is_locally_owned())
        cell->set_active_fe_index(min_fe_index);
  }

//  @sect4{LaplaceProblem::setup_system}

// 这个函数看起来和 step-40 的函数完全一样，但是你会注意到没有系统矩阵以及围绕它的脚手架。相反，我们将在这里初始化 laplace_operator 中的MatrixFree公式。对于边界条件，我们将使用本教程前面介绍的Solution类。

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

    dof_handler.distribute_dofs(fe_collection);

    locally_owned_dofs = dof_handler.locally_owned_dofs();
    DoFTools::extract_locally_relevant_dofs(dof_handler, locally_relevant_dofs);

    locally_relevant_solution.reinit(locally_owned_dofs,
                                     locally_relevant_dofs,
                                     mpi_communicator);
    system_rhs.reinit(locally_owned_dofs, mpi_communicator);

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

    laplace_operator.reinit(mapping_collection,
                            dof_handler,
                            quadrature_collection,
                            constraints,
                            system_rhs);
  }

//  @sect4{LaplaceProblem::print_diagnostics}

// 这是一个打印关于方程组及其划分的额外诊断的函数。除了通常的全局活动单元数和自由度外，我们还输出它们的局部等价物。为了规范输出，我们将用 Utilities::MPI::gather 操作将局部数量传达给第一个进程，然后由该进程输出所有信息。本地量的输出只限于前8个进程，以避免终端的杂乱。

// 此外，我们想打印数值离散化中的多项式度数的频率。由于这些信息只存储在本地，我们将计算本地拥有的单元上的有限元，随后通过 Utilities::MPI::sum. 进行交流。
  template <int dim>
  void LaplaceProblem<dim>::print_diagnostics()
  {
    const unsigned int first_n_processes =
      std::min<unsigned int>(8,
                             Utilities::MPI::n_mpi_processes(mpi_communicator));
    const bool output_cropped =
      first_n_processes < Utilities::MPI::n_mpi_processes(mpi_communicator);

    {
      pcout << "   Number of active cells:       "
            << triangulation.n_global_active_cells() << std::endl
            << "     by partition:              ";

      std::vector<unsigned int> n_active_cells_per_subdomain =
        Utilities::MPI::gather(mpi_communicator,
                               triangulation.n_locally_owned_active_cells());
      for (unsigned int i = 0; i < first_n_processes; ++i)
        pcout << ' ' << n_active_cells_per_subdomain[i];
      if (output_cropped)
        pcout << " ...";
      pcout << std::endl;
    }

    {
      pcout << "   Number of degrees of freedom: " << dof_handler.n_dofs()
            << std::endl
            << "     by partition:              ";

      std::vector<types::global_dof_index> n_dofs_per_subdomain =
        Utilities::MPI::gather(mpi_communicator,
                               dof_handler.n_locally_owned_dofs());
      for (unsigned int i = 0; i < first_n_processes; ++i)
        pcout << ' ' << n_dofs_per_subdomain[i];
      if (output_cropped)
        pcout << " ...";
      pcout << std::endl;
    }

    {
      std::vector<types::global_dof_index> n_constraints_per_subdomain =
        Utilities::MPI::gather(mpi_communicator, constraints.n_constraints());

      pcout << "   Number of constraints:        "
            << std::accumulate(n_constraints_per_subdomain.begin(),
                               n_constraints_per_subdomain.end(),
                               0)
            << std::endl
            << "     by partition:              ";
      for (unsigned int i = 0; i < first_n_processes; ++i)
        pcout << ' ' << n_constraints_per_subdomain[i];
      if (output_cropped)
        pcout << " ...";
      pcout << std::endl;
    }

    {
      std::vector<unsigned int> n_fe_indices(fe_collection.size(), 0);
      for (const auto &cell : dof_handler.active_cell_iterators())
        if (cell->is_locally_owned())
          n_fe_indices[cell->active_fe_index()]++;

      Utilities::MPI::sum(n_fe_indices, mpi_communicator, n_fe_indices);

      pcout << "   Frequencies of poly. degrees:";
      for (unsigned int i = 0; i < fe_collection.size(); ++i)
        if (n_fe_indices[i] > 0)
          pcout << ' ' << fe_collection[i].degree << ":" << n_fe_indices[i];
      pcout << std::endl;
    }
  }

//  @sect4{LaplaceProblem::solve_system}

// 围绕解决方案的脚手架与  step-40  的类似。我们准备一个符合MatrixFree要求的向量，并收集本地相关的自由度，我们解决了方程系统。解决方法是通过前面介绍的函数进行的。

  template <int dim>
  void LaplaceProblem<dim>::solve_system()
  {
    TimerOutput::Scope t(computing_timer, "solve system");

    LinearAlgebra::distributed::Vector<double> completely_distributed_solution;
    laplace_operator.initialize_dof_vector(completely_distributed_solution);

    SolverControl solver_control(system_rhs.size(),
                                 prm.tolerance_factor * system_rhs.l2_norm());

    solve_with_gmg(solver_control,
                   laplace_operator,
                   completely_distributed_solution,
                   system_rhs,
                   prm.mg_data,
                   mapping_collection,
                   dof_handler,
                   quadrature_collection);

    pcout << "   Solved in " << solver_control.last_step() << " iterations."
          << std::endl;

    constraints.distribute(completely_distributed_solution);

    locally_relevant_solution.copy_locally_owned_data_from(
      completely_distributed_solution);
    locally_relevant_solution.update_ghost_values();
  }

//  @sect4{LaplaceProblem::compute_indicators}

// 这个函数只包含其他教程中典型的 refine_grid 函数的一部分，在这个意义上是新的。在这里，我们将只计算与实际细化网格相适应的所有指标。我们这样做的目的是将所有的指标写到文件系统中，以便为以后储存。

// 由于我们处理的是一个椭圆问题，我们将再次利用KellyErrorEstimator，但有一点不同。修改底层面积分的缩放系数，使其取决于相邻元素的实际多项式程度，这对hp-adaptive应用是有利的  @cite davydov2017hp  。我们可以通过指定你所注意到的附加参数中的最后一个参数来做到这一点。其他的实际上只是默认的。

// 为了hp-adaptation的目的，我们将用教程介绍中的策略来计算平滑度估计，并使用 SmoothnessEstimator::Legendre. 中的实现 在参数结构中，我们将最小多项式度数设置为2，因为似乎平滑度估计算法在处理线性元素时有问题。

  template <int dim>
  void LaplaceProblem<dim>::compute_indicators()
  {
    TimerOutput::Scope t(computing_timer, "compute indicators");

    estimated_error_per_cell.grow_or_shrink(triangulation.n_active_cells());
    KellyErrorEstimator<dim>::estimate(
      dof_handler,
      face_quadrature_collection,
      std::map<types::boundary_id, const Function<dim> *>(),
      locally_relevant_solution,
      estimated_error_per_cell,
      /*component_mask=  */
      ComponentMask(),
      /*coefficients=  */
      nullptr,
      /*n_threads=*/
      numbers::invalid_unsigned_int,
      /*subdomain_id=*/
      numbers::invalid_subdomain_id,
      /*material_id=*/
      numbers::invalid_material_id,
      /*策略=  */
      KellyErrorEstimator<dim>::Strategy::face_diameter_over_twice_max_degree);

    hp_decision_indicators.grow_or_shrink(triangulation.n_active_cells());
    SmoothnessEstimator::Legendre::coefficient_decay(*legendre,
                                                     dof_handler,
                                                     locally_relevant_solution,
                                                     hp_decision_indicators);
  }

//  @sect4{LaplaceProblem::adapt_resolution}

// 有了之前计算出的指标，我们最终将标记所有单元进行适应，同时在这个函数中执行细化。和以前的教程一样，我们将使用 "固定数字 "策略，但现在是针对hp-adaptation。

  template <int dim>
  void LaplaceProblem<dim>::adapt_resolution()
  {
    TimerOutput::Scope t(computing_timer, "adapt resolution");

// 首先，我们将根据每个单元的误差估计值来设置细化和粗化标志。这里没有什么新东西。

// 我们将使用在其他deal.II教程中阐述过的一般细化和粗化比例：使用固定数字策略，我们将标记所有单元中的30%进行细化，3%进行粗化，如参数结构中提供的。

    parallel::distributed::GridRefinement::refine_and_coarsen_fixed_number(
      triangulation,
      estimated_error_per_cell,
      prm.refine_fraction,
      prm.coarsen_fraction);

// 接下来，我们将对hp-adaptation进行所有调整。我们想细化和粗化那些在上一步中被标记的单元，但需要决定是通过调整网格分辨率还是调整多项式程度来实现。

// 下一个函数调用根据之前计算的平滑度指标设置未来的FE指数，作为p-adaptation指标。这些指数将只设置在那些分配了细化或粗化标志的单元上。

// 对于p-adaptation分数，我们将采取一个有根据的猜测。由于我们只期望在我们的方案中出现一个单一的奇点，即在域的原点，而在其他任何地方都有一个平滑的解决方案，所以我们希望强烈倾向于使用p-adaptation而不是h-adaptation。这反映在我们对p-精简和p-粗化都选择了90%的分数。

    hp::Refinement::p_adaptivity_fixed_number(dof_handler,
                                              hp_decision_indicators,
                                              prm.p_refine_fraction,
                                              prm.p_coarsen_fraction);

// 在这个阶段，我们既有未来的FE指数，也有经典的细化和粗化标志，后者将由 Triangulation::execute_coarsening_and_refinement() 解释为h-适应性。我们希望只对细胞施加一种适应，这就是下一个函数将为我们解决的问题。简而言之，在分配有两种类型指标的单元格上，我们将倾向于p-适应的那一种，并删除h-适应的那一种。

    hp::Refinement::choose_p_over_h(dof_handler);

// 设置完所有指标后，我们将删除那些超过参数结构中提供的水平范围的指定限制的指标。由于提供的有限元数量有限，这种限制自然会出现在p-adaptation中。此外，我们在构造函数中为p-adaptation注册了一个自定义层次结构。现在，我们需要像  step-31  中那样，在h-adaptive的上下文中手动完成。

// 我们将遍历指定的最小和最大层次上的所有单元格，并删除相应的标志。作为一种选择，我们也可以通过相应地设置未来的FE指数来标记这些单元的p适应性，而不是简单地清除细化和粗化的标志。

    Assert(triangulation.n_levels() >= prm.min_h_level + 1 &&
             triangulation.n_levels() <= prm.max_h_level + 1,
           ExcInternalError());

    if (triangulation.n_levels() > prm.max_h_level)
      for (const auto &cell :
           triangulation.active_cell_iterators_on_level(prm.max_h_level))
        cell->clear_refine_flag();

    for (const auto &cell :
         triangulation.active_cell_iterators_on_level(prm.min_h_level))
      cell->clear_coarsen_flag();

// 最后，我们就剩下执行粗化和细化了。在这里，不仅网格会被更新，而且所有以前的未来FE指数也会变得活跃。

// 记得我们在构造函数中为三角化信号附加了函数，将在这个函数调用中被触发。所以会有更多的事情发生：加权重新分区将被执行以确保负载平衡，以及我们将限制相邻单元之间的p级差。

    triangulation.execute_coarsening_and_refinement();
  }

//  @sect4{LaplaceProblem::output_results}

// 在并行应用中向文件系统写入结果的工作方式与  step-40  中完全相同。除了我们在整个教程中准备的数据容器外，我们还想写出网格上每个有限元的多项式程度，以及每个单元所属的子域。我们在这个函数的范围内为此准备必要的容器。

  template <int dim>
  void LaplaceProblem<dim>::output_results(const unsigned int cycle)
  {
    TimerOutput::Scope t(computing_timer, "output results");

    Vector<float> fe_degrees(triangulation.n_active_cells());
    for (const auto &cell : dof_handler.active_cell_iterators())
      if (cell->is_locally_owned())
        fe_degrees(cell->active_cell_index()) = cell->get_fe().degree;

    Vector<float> subdomain(triangulation.n_active_cells());
    for (auto &subd : subdomain)
      subd = triangulation.locally_owned_subdomain();

    DataOut<dim> data_out;
    data_out.attach_dof_handler(dof_handler);
    data_out.add_data_vector(locally_relevant_solution, "solution");
    data_out.add_data_vector(fe_degrees, "fe_degree");
    data_out.add_data_vector(subdomain, "subdomain");
    data_out.add_data_vector(estimated_error_per_cell, "error");
    data_out.add_data_vector(hp_decision_indicators, "hp_indicator");
    data_out.build_patches(mapping_collection);

    data_out.write_vtu_with_pvtu_record(
      "./", "solution", cycle, mpi_communicator, 2, 1);
  }

//  @sect4{LaplaceProblem::run}

// 实际的运行函数看起来又和  step-40  非常相似。唯一增加的是实际循环之前的括号内的部分。在这里，我们将预先计算Legendre变换矩阵。一般来说，每当需要某个矩阵时，这些矩阵将通过懒惰分配的方式进行实时计算。然而，出于计时的目的，我们希望在实际的时间测量开始之前，一次性地计算它们。因此，我们将把它们的计算指定为自己的范围。

  template <int dim>
  void LaplaceProblem<dim>::run()
  {
    pcout << "Running with Trilinos on "
          << Utilities::MPI::n_mpi_processes(mpi_communicator)
          << " MPI rank(s)..." << std::endl;

    {
      pcout << "Calculating transformation matrices..." << std::endl;
      TimerOutput::Scope t(computing_timer, "calculate transformation");
      legendre->precalculate_all_transformation_matrices();
    }

    for (unsigned int cycle = 0; cycle < prm.n_cycles; ++cycle)
      {
        pcout << "Cycle " << cycle << ':' << std::endl;

        if (cycle == 0)
          initialize_grid();
        else
          adapt_resolution();

        setup_system();

        print_diagnostics();

        solve_system();

        compute_indicators();

        if (Utilities::MPI::n_mpi_processes(mpi_communicator) <= 32)
          output_results(cycle);

        computing_timer.print_summary();
        computing_timer.reset();

        pcout << std::endl;
      }
  }
} // namespace Step75

//  @sect4{main()}

// 最后一个函数是 main 函数，它将最终创建并运行一个LaplaceOperator实例。它的结构与其他大多数教程程序相似。

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

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

      Parameters        prm;
      LaplaceProblem<2> laplace_problem(prm);
      laplace_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;
}