// This file is part of the dune-hdd project:
//   http://users.dune-project.org/projects/dune-hdd
// Copyright holders: Felix Schindler
// License: BSD 2-Clause License (http://opensource.org/licenses/BSD-2-Clause)

#ifndef DUNE_HDD_EXAMPLES_LINEARELLIPTIC_OS2015_SISC_6_2_HH
#define DUNE_HDD_EXAMPLES_LINEARELLIPTIC_OS2015_SISC_6_2_HH

#define DUNE_HDD_LINEARELLIPTIC_TESTCASES_BASE_DISABLE_WARNING

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

#if HAVE_DUNE_FEM
# include <dune/fem/misc/mpimanager.hh>
#endif

#include <dune/stuff/common/exceptions.hh>
#include <dune/stuff/common/logging.hh>
#include <dune/stuff/common/memory.hh>
#include <dune/stuff/common/ranges.hh>
#include <dune/stuff/functions/expression.hh>

#include <dune/pymor/parameters/base.hh>

#include <dune/gdt/discretefunction/default.hh>
#include <dune/gdt/operators/projections.hh>
#include <dune/gdt/operators/prolongations.hh>
#include <dune/gdt/playground/products/elliptic-swipdg.hh>
#include <dune/gdt/playground/spaces/finitevolume/default.hh>

#include <dune/hdd/linearelliptic/testcases/OS2015.hh>
#include <dune/hdd/linearelliptic/discretizations/block-swipdg.hh>
#include <dune/hdd/linearelliptic/estimators/block-swipdg.hh>

namespace internal {

class Initializer
{
public:
  Initializer(const DUNE_STUFF_SSIZE_T info_log_levels,
              const DUNE_STUFF_SSIZE_T debug_log_levels,
              const bool enable_warnings,
              const bool enable_colors,
              const std::string info_color,
              const std::string debug_color,
              const std::string warn_color)
  {
    try {
      int argc = 0;
      char** argv = new char* [0];
      Dune::Fem::MPIManager::initialize(argc, argv);
    } catch (...) {}
    DSC::TimedLogger().create(info_log_levels,
                              debug_log_levels,
                              enable_warnings,
                              enable_colors,
                              info_color,
                              debug_color,
                              warn_color);
    DSC::TimedLogger().get("OS2015.initializer").info() << "creating grid and problem... " << std::endl;
  }
}; // class Initializer

template< class TestCaseType >
class Example
  : Initializer
{
public:
  typedef typename TestCaseType::GridType GridType;
  typedef double RangeFieldType;
  typedef Dune::HDD::LinearElliptic::Discretizations::BlockSWIPDG< GridType, RangeFieldType, 1 > DiscretizationType;
  typedef typename DiscretizationType::VectorType VectorType;
  typedef typename TestCaseType::ParametersMapType ParametersMapType;

private:
  typedef Dune::HDD::LinearElliptic::Estimators::BlockSWIPDG< typename DiscretizationType::AnsatzSpaceType,
                                                              VectorType,
                                                              typename DiscretizationType::ProblemType,
                                                              GridType > Estimator;

public:
  Example(const ParametersMapType& parameter_range,
          const std::string partitioning = "[1 1 1]",
          const DUNE_STUFF_SSIZE_T num_refinements = 0,
          const DUNE_STUFF_SSIZE_T oversampling_layers = 0,
          const std::vector< std::string > products = {},
          const bool with_reference = true,
          const DUNE_STUFF_SSIZE_T info_log_levels  = 0,
          const DUNE_STUFF_SSIZE_T debug_log_levels = -1,
          const bool enable_warnings = true,
          const bool enable_colors   = true,
          const std::string info_color  = DSC::TimedLogging::default_info_color(),
          const std::string debug_color = DSC::TimedLogging::default_debug_color(),
          const std::string warn_color  = DSC::TimedLogging::default_warning_color())
    : Initializer(info_log_levels,
                  debug_log_levels,
                  enable_warnings,
                  enable_colors,
                  info_color,
                  debug_color,
                  warn_color)
    , with_reference_(with_reference),
      parameter_range_(parameter_range)
    , test_case_(merge_parameters({{"mu",     Dune::Pymor::Parameter("mu", 1)},  // <- it does not matter which parameters we give to the
                                   {"mu_hat", Dune::Pymor::Parameter("mu", 1)},  //    test case here, since we use test_case_.problem()
                                   {"mu_bar", Dune::Pymor::Parameter("mu", 1)}}, //    (which is the parametric problem) anyway
                                  parameter_range_),
                 partitioning,
                 boost::numeric_cast< size_t >(num_refinements),
                 boost::numeric_cast< size_t >(oversampling_layers))
    , reference_test_case_(with_reference_
                           ? new TestCaseType(merge_parameters({{"mu",     Dune::Pymor::Parameter("mu", 1)},
                                                                {"mu_hat", Dune::Pymor::Parameter("mu", 1)},
                                                                {"mu_bar", Dune::Pymor::Parameter("mu", 1)}},
                                                               parameter_range_),
                                              partitioning,
                                              boost::numeric_cast< size_t >(num_refinements + 1))
                           : nullptr)
    , discretization_(*test_case_.reference_provider(),
                      test_case_.boundary_info(),
                      test_case_.problem(),
                      products)
    , reference_discretization_(with_reference_
                                ? new DiscretizationType(*reference_test_case_->reference_provider(),
                                                         reference_test_case_->boundary_info(),
                                                         reference_test_case_->problem(),
                                                         products)
                                : nullptr)
  {
    auto logger = DSC::TimedLogger().get("OS2015.example");
    logger.info() << "initializing discretization... " << std::flush;
    discretization_.init();
    if (with_reference_)
      reference_discretization_->init();
    logger.info() << "done (grid has " << discretization_.grid_view().indexSet().size(0)
                  << " elements, discretization has " << discretization_.ansatz_space().mapper().size() << " DoFs)"
                  << std::endl;
  } // ... Example(...)

  const TestCaseType& test_case() const
  {
    return test_case_;
  }

  DiscretizationType& discretization()
  {
    return discretization_;
  }

  DiscretizationType* discretization_and_return_ptr() const
  {
    return new DiscretizationType(discretization_);
  }

  void visualize(const std::string& filename_prefix) const
  {
    test_case_.reference_provider()->visualize(filename_prefix + ".grid", /*coupling=*/ false);
    test_case_.problem().visualize(test_case_.reference_provider()->leaf_view(),
                                   filename_prefix + ".problem",
                                   /*subsampling=*/ false);
  } // ... visualize(...)

  VectorType project(const std::string expression) const
  {
    using namespace Dune;
    typedef typename DiscretizationType::AnsatzSpaceType AnsatzSpaceType;
    typedef GDT::DiscreteFunction< AnsatzSpaceType, VectorType > DiscreteFunctionType;
    DiscreteFunctionType discrete_function(discretization_.ansatz_space());

    typedef Stuff::Functions::Expression< typename DiscreteFunctionType::EntityType,
                                          typename DiscreteFunctionType::DomainFieldType,
                                          DiscreteFunctionType::dimDomain,
                                          typename DiscreteFunctionType::RangeFieldType,
                                          DiscreteFunctionType::dimRange > ExpressionFunctionType;
    ExpressionFunctionType func("x", expression);

    GDT::Operators::Projection< typename DiscretizationType::GridViewType > projection(discretization_.grid_view());
    projection.apply(func, discrete_function);

    return discrete_function.vector();
  } // ... project(...)

  RangeFieldType compute_error(const VectorType& solution,
                               const std::string product_type = "",
                               const Dune::Pymor::Parameter mu = Dune::Pymor::Parameter(),
                               const Dune::Pymor::Parameter mu_product = Dune::Pymor::Parameter())
  {
    if (!with_reference_)
      DUNE_THROW(Dune::Stuff::Exceptions::you_are_using_this_wrong,
                 "Do not call compute error() if with_reference is false!");
    const std::string type = (product_type.empty() && reference_discretization_->available_products().size() == 1)
                             ? reference_discretization_->available_products()[0]
                             : product_type;
    // wrap solution into a discrete function
    using namespace Dune;
    typedef typename DiscretizationType::AnsatzSpaceType AnsatzSpaceType;
    typedef GDT::ConstDiscreteFunction< AnsatzSpaceType, VectorType > ConstDiscreteFunctionType;
    ConstDiscreteFunctionType coarse_solution(discretization_.ansatz_space(), solution);
    // prolong to reference grid view
    typedef GDT::DiscreteFunction< AnsatzSpaceType, VectorType > DiscreteFunctionType;
    DiscreteFunctionType fine_solution(reference_discretization_->ansatz_space());
    GDT::Operators::Prolongation< typename DiscretizationType::GridViewType >
        prolongation_operator(reference_discretization_->grid_view());
    prolongation_operator.apply(coarse_solution, fine_solution);
    // compute reference solution
    DiscreteFunctionType reference_solution(reference_discretization_->ansatz_space());
    reference_discretization_->solve(reference_solution.vector(), mu);
    // compute error
    const auto difference = reference_solution.vector() - fine_solution.vector();
    const auto product = reference_discretization_->get_product(type);
    return std::sqrt(product.apply2(difference, difference, mu_product));
  } // ... compute_error(...)

  VectorType* pb_project_global_to_oversampled(const VectorType& global_vector, const DUNE_STUFF_SSIZE_T subdomain) const
  {
    using namespace Dune;
    size_t ss = std::numeric_limits< size_t >::max();
    try {
      ss = boost::numeric_cast< size_t >(subdomain);
    } catch (boost::bad_numeric_cast& ee) {
      DUNE_THROW(Stuff::Exceptions::wrong_input_given,
                 "There was an error in boost converting " << subdomain << " to "
                 << Stuff::Common::Typename< size_t >::value() << ": \n\n" << ee.what());
    }
    const GDT::ConstDiscreteFunction< typename DiscretizationType::AnsatzSpaceType, VectorType >
        global_function(discretization_.ansatz_space(), global_vector);
    const auto oversampled_discretization = discretization_.get_oversampled_discretization(subdomain, "dirichlet");
    GDT::DiscreteFunction< typename DiscretizationType::OversampledDiscretizationType::AnsatzSpaceType, VectorType >
        oversampled_function(oversampled_discretization.ansatz_space());
    const GDT::Operators::Projection< typename DiscretizationType::OversampledDiscretizationType::GridViewType >
        projection_operator(oversampled_discretization.grid_view());
    projection_operator.apply(global_function, oversampled_function);
    return new VectorType(oversampled_function.vector());
  } // ... pb_project_global_to_oversampled(...)

  VectorType* pb_project_global_to_local(const VectorType& global_vector, const DUNE_STUFF_SSIZE_T subdomain) const
  {
    using namespace Dune;
    size_t ss = std::numeric_limits< size_t >::max();
    try {
      ss = boost::numeric_cast< size_t >(subdomain);
    } catch (boost::bad_numeric_cast& ee) {
      DUNE_THROW(Stuff::Exceptions::wrong_input_given,
                 "There was an error in boost converting " << subdomain << " to "
                 << Stuff::Common::Typename< size_t >::value() << ": \n\n" << ee.what());
    }
    const GDT::ConstDiscreteFunction< typename DiscretizationType::AnsatzSpaceType, VectorType >
        global_function(discretization_.ansatz_space(), global_vector);
    const auto local_discretization = discretization_.get_local_discretization(subdomain);
    GDT::DiscreteFunction< typename DiscretizationType::LocalDiscretizationType::AnsatzSpaceType, VectorType >
        local_function(local_discretization.ansatz_space());
    const GDT::Operators::Projection< typename DiscretizationType::LocalDiscretizationType::GridViewType >
        projection_operator(local_discretization.grid_view());
    projection_operator.apply(global_function, local_function);
    return new VectorType(local_function.vector());
  } // ... pb_project_global_to_local(...)

  VectorType* pb_project_oversampled_to_local(const VectorType& oversampled_vector, const DUNE_STUFF_SSIZE_T subdomain) const
  {
    using namespace Dune;
    size_t ss = std::numeric_limits< size_t >::max();
    try {
      ss = boost::numeric_cast< size_t >(subdomain);
    } catch (boost::bad_numeric_cast& ee) {
      DUNE_THROW(Stuff::Exceptions::wrong_input_given,
                 "There was an error in boost converting " << subdomain << " to "
                 << Stuff::Common::Typename< size_t >::value() << ": \n\n" << ee.what());
    }
    const auto oversampled_discretization = discretization_.get_oversampled_discretization(subdomain, "dirichlet");
    const GDT::ConstDiscreteFunction
        < typename DiscretizationType::OversampledDiscretizationType::AnsatzSpaceType, VectorType >
        oversampled_function(oversampled_discretization.ansatz_space(), oversampled_vector);
    const auto local_discretization = discretization_.get_local_discretization(subdomain);
    const auto& local_space = local_discretization.ansatz_space();
    GDT::DiscreteFunction< typename DiscretizationType::LocalDiscretizationType::AnsatzSpaceType, VectorType >
        local_function(local_space);
    const GDT::Operators::Projection< typename DiscretizationType::LocalDiscretizationType::GridViewType >
        projection_operator(local_space.grid_view());
    projection_operator.apply(oversampled_function, local_function);
    return new VectorType(local_function.vector());
  } // ... pb_project_oversampled_to_local(...)

  std::vector< std::string > available_estimators() const
  {
    return Estimator::available();
  }

  RangeFieldType estimate(const VectorType& vector,
                          const std::string type,
                          const Dune::Pymor::Parameter mu_hat = Dune::Pymor::Parameter(),
                          const Dune::Pymor::Parameter mu_bar = Dune::Pymor::Parameter(),
                          const Dune::Pymor::Parameter mu     = Dune::Pymor::Parameter())
  {
    return Estimator::estimate(discretization_.ansatz_space(),
                               vector,
                               discretization_.problem(),
                               type,
                               merge_parameters({{"mu_hat", mu_hat},
                                                 {"mu_bar", mu_bar},
                                                 {"mu",     mu}},
                                                parameter_range_));
  } // ... estimate(...)

  std::vector< std::string > available_local_estimators() const
  {
    return Estimator::available_local();
  }

  std::vector< RangeFieldType > estimate_local(const VectorType& vector,
                                               const std::string type,
                                               const Dune::Pymor::Parameter mu_hat = Dune::Pymor::Parameter(),
                                               const Dune::Pymor::Parameter mu_bar = Dune::Pymor::Parameter(),
                                               const Dune::Pymor::Parameter mu     = Dune::Pymor::Parameter())
  {
    return Estimator::estimate_local(discretization_.ansatz_space(),
                                     vector,
                                     discretization_.problem(),
                                     type,
                                     merge_parameters({{"mu_hat", mu_hat},
                                                       {"mu_bar", mu_bar},
                                                       {"mu",     mu}},
                                                      parameter_range_));
  } // ... estimate_local(...)

  VectorType solve_for_local_correction(const std::vector< VectorType >& local_vectors,
                                        const DUNE_STUFF_SSIZE_T subdomain,
                                        const Dune::Pymor::Parameter mu = Dune::Pymor::Parameter()) const
  {
    const size_t ss = boost::numeric_cast< size_t >(subdomain);
    return discretization_.solve_for_local_correction(local_vectors, ss, mu);
  }

  VectorType solve_oversampled(const DUNE_STUFF_SSIZE_T subdomain,
                               const std::string& boundary_value_type,
                               const VectorType& boundary_values,
                               const Dune::Pymor::Parameter mu = Dune::Pymor::Parameter()) const
  {
    using namespace Dune;
    DSC::Configuration boundary_cfg;
    if (boundary_value_type == "dirichlet")
      boundary_cfg = Stuff::Grid::BoundaryInfoConfigs::AllDirichlet::default_config();
    else if (boundary_value_type == "neumann")
      boundary_cfg = Stuff::Grid::BoundaryInfoConfigs::AllNeumann::default_config();
    else
      DUNE_THROW(Stuff::Exceptions::wrong_input_given, "Unknown boundary_value_type given: " << boundary_value_type);
    // we need this one temporarily for the space
    const auto tmp_oversampled_discretization = discretization_.get_oversampled_discretization(boost::numeric_cast< size_t >(subdomain),
                                                                                boundary_value_type);
    const auto oversampled_space = tmp_oversampled_discretization.ansatz_space();
    typedef typename DiscretizationType::OversampledDiscretizationType OversampledDiscretizationType;
    typedef typename HDD::LinearElliptic::Problems::ConvertToDefault< typename TestCaseType::ProblemType >::Type
                                                                       OversampledProblemType;
    typedef typename OversampledDiscretizationType::AnsatzSpaceType    OversampledAnsatzSpaceType;
    auto bv_function = std::make_shared< GDT::ConstDiscreteFunction< OversampledAnsatzSpaceType, VectorType > >(
          oversampled_space, boundary_values, "boundary_values");
    auto nonparametric_problem = test_case_.problem().with_mu(mu);
    const OversampledProblemType oversampled_problem(nonparametric_problem->diffusion_factor()->affine_part(),
                                                     nonparametric_problem->diffusion_tensor()->affine_part(),
                                                     nonparametric_problem->force()->affine_part(),
                                                     bv_function,
                                                     bv_function);
    OversampledDiscretizationType discretization(*test_case_.reference_provider(),
                                                 boundary_cfg,
                                                 oversampled_problem,
                                                 boost::numeric_cast< int >(subdomain));
    discretization.init();
    VectorType solution = discretization.create_vector();
    discretization.solve(solution);
    return solution;
  } // ... solve_oversampled(...)

  void visualize_on_coarse_grid(const std::vector< double >& vector,
                                const std::string& filename,
                                const std::string& name)
  {
    using namespace Dune;
    const auto ms_grid = discretization_.ansatz_space().ms_grid();
    const auto& grid_view = discretization_.grid_view();
    if (vector.size() != ms_grid->size())
      DUNE_THROW(Stuff::Exceptions::wrong_input_given,
                 "Given vector has wrong lenght!\n"
                 << "  expected: " << ms_grid->size() << "\n"
                 << "  actual:   " << vector.size() << "\n");
    if (filename.empty())
      DUNE_THROW(Stuff::Exceptions::wrong_input_given, "Given filename must not be empty!");
    if (name.empty())
      DUNE_THROW(Stuff::Exceptions::wrong_input_given, "Given name must not be empty!");
    GDT::Spaces::FiniteVolume::Default< typename std::remove_reference< decltype(grid_view) >::type, RangeFieldType, 1 >
        fv_space(grid_view);
    auto visualization = GDT::make_discrete_function< VectorType >(fv_space, name);
    for (const auto& entity : Stuff::Common::entityRange(grid_view))
      visualization.vector().set_entry(grid_view.indexSet().index(entity), vector[ms_grid->subdomainOf(entity)]);
    visualization.visualize(filename);
  } // ... visualize_on_coarse_grid(...)

  RangeFieldType alpha(const Dune::Pymor::Parameter& mu_1, const Dune::Pymor::Parameter& mu_2)
  {
    return test_case_.problem().diffusion_factor()->alpha(mu_1, mu_2);
  }

  RangeFieldType gamma(const Dune::Pymor::Parameter& mu_1, const Dune::Pymor::Parameter& mu_2)
  {
    return test_case_.problem().diffusion_factor()->gamma(mu_1, mu_2);
  }

private:
  static ParametersMapType merge_parameters(const ParametersMapType& first,
                                            const ParametersMapType& second)
  {
    ParametersMapType ret = first;
    for (const auto& element : second)
      ret.insert(element);
    return ret;
  }

  const bool with_reference_;
  const ParametersMapType parameter_range_;
  TestCaseType test_case_;
  std::unique_ptr< TestCaseType > reference_test_case_;
  DiscretizationType discretization_;
  std::unique_ptr< DiscretizationType > reference_discretization_;
}; // class Example

} // namespace internal

template< class GridImp >
class OS2015MultiscaleExample
  : public internal::Example< Dune::HDD::LinearElliptic::TestCases::OS2015::Multiscale< GridImp > >
{
  static_assert(GridImp::dimension == 2, "Only available in 2d!");
  typedef internal::Example< Dune::HDD::LinearElliptic::TestCases::OS2015::Multiscale< GridImp > > BaseType;

public:
  template< class... Args >
  OS2015MultiscaleExample(Args&& ...args)
    : BaseType({{"parameter_range_min", Dune::Pymor::Parameter("mu", 0.1)},
                {"parameter_range_max", Dune::Pymor::Parameter("mu", 1.0)}},
               std::forward< Args >(args)...)
  {}
}; // class OS2015MultiscaleExample

template< class GridImp >
class OS2015AcademicExample
  : public internal::Example< Dune::HDD::LinearElliptic::TestCases::OS2015::Academic< GridImp > >
{
  static_assert(GridImp::dimension == 2, "Only available in 2d!");
  typedef internal::Example
      < Dune::HDD::LinearElliptic::TestCases::OS2015::Academic< GridImp > > BaseType;

public:
  template< class... Args >
  OS2015AcademicExample(Args&& ...args)
    : BaseType({{"parameter_range_min", Dune::Pymor::Parameter("mu", 0.1)},
                {"parameter_range_max", Dune::Pymor::Parameter("mu", 1.0)}},
               std::forward< Args >(args)...)
  {}
}; // class OS2015AcademicExample

#endif // DUNE_HDD_EXAMPLES_LINEARELLIPTIC_OS2015_SISC_6_2_HH


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

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

#include "ibamr/IBInterpolantMethod.h"

#include "ibtk/HierarchyIntegrator.h"
#include "ibtk/IBTK_CHKERRQ.h"
#include "ibtk/IBTK_MPI.h"
#include "ibtk/LData.h"
#include "ibtk/LDataManager.h"
#include "ibtk/LEInteractor.h"
#include "ibtk/LInitStrategy.h"
#include "ibtk/LMesh.h"
#include "ibtk/LNode.h"
#include "ibtk/LSiloDataWriter.h"
#include "ibtk/ibtk_utilities.h"

#include "BasePatchHierarchy.h"
#include "BasePatchLevel.h"
#include "CartesianGridGeometry.h"
#include "CellVariable.h"
#include "CoarsenSchedule.h"
#include "GriddingAlgorithm.h"
#include "HierarchyCellDataOpsReal.h"
#include "HierarchySideDataOpsReal.h"
#include "IntVector.h"
#include "MultiblockDataTranslator.h"
#include "PatchHierarchy.h"
#include "PatchLevel.h"
#include "RefineAlgorithm.h"
#include "RefineOperator.h"
#include "SideVariable.h"
#include "Variable.h"
#include "VariableContext.h"
#include "VariableDatabase.h"
#include "tbox/Array.h"
#include "tbox/Database.h"
#include "tbox/MathUtilities.h"
#include "tbox/PIO.h"
#include "tbox/Pointer.h"
#include "tbox/RestartManager.h"
#include "tbox/Utilities.h"

#include "petscvec.h"
#include <petscsys.h>

#include "ibamr/namespaces.h" // IWYU pragma: keep

IBTK_DISABLE_EXTRA_WARNINGS
#include <boost/multi_array.hpp>
IBTK_ENABLE_EXTRA_WARNINGS

IBTK_DISABLE_EXTRA_WARNINGS
#include <Eigen/Core>
#include <Eigen/Geometry>
IBTK_ENABLE_EXTRA_WARNINGS

#include <algorithm>
#include <array>
#include <cmath>
#include <limits>
#include <sstream>
#include <string>
#include <utility>
#include <vector>

namespace SAMRAI
{
namespace xfer
{
template <int DIM>
class RefineSchedule;
} // namespace xfer
} // namespace SAMRAI

namespace IBTK
{
class RobinPhysBdryPatchStrategy;
} // namespace IBTK

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

namespace IBAMR
{
/////////////////////////////// STATIC ///////////////////////////////////////

namespace
{
// Version of IBInterpolantMethod restart file data.
static const int IB_INTERPOLANT_METHOD_VERSION = 1;

inline void
set_rotation_matrix(const EigenAlignedVector<Eigen::Vector3d>& rot_vel,
                    const EigenAlignedVector<Eigen::Quaterniond>& q_old,
                    EigenAlignedVector<Eigen::Quaterniond>& q_new,
                    EigenAlignedVector<Eigen::Matrix3d>& rot_mat,
                    const double dt)
{
    unsigned n_structs = (unsigned)rot_mat.size();
    for (unsigned struct_no = 0; struct_no < n_structs; ++struct_no)
    {
        const double norm = rot_vel[struct_no].norm();
        if (!IBTK::abs_equal_eps(norm, 0.0))
        {
            Eigen::Vector3d rot_axis = rot_vel[struct_no] / norm;
            Eigen::Quaterniond q(Eigen::AngleAxisd(norm * dt, rot_axis));
            q_new[struct_no] = (q.normalized() * q_old[struct_no]).normalized();
        }
        else
        {
            q_new[struct_no] = q_old[struct_no];
        }

        rot_mat[struct_no] = q_new[struct_no].toRotationMatrix();
    }
    return;
} // set_rotation_matrix

} // namespace

/////////////////////////////// PUBLIC ///////////////////////////////////////

IBInterpolantMethod::IBInterpolantMethod(std::string object_name,
                                         Pointer<Database> input_db,
                                         int no_structures,
                                         bool register_for_restart)
    : d_num_rigid_parts(no_structures),
      d_object_name(std::move(object_name)),
      d_registered_for_restart(register_for_restart)
{
    // Register object with the restart manager.
    if (d_registered_for_restart)
    {
        RestartManager::getManager()->registerRestartItem(d_object_name, this);
    }

    // Set some default values.
    d_struct_lag_idx_range.resize(d_num_rigid_parts);
    d_quaternion_current.resize(d_num_rigid_parts, Eigen::Quaterniond::Identity());
    d_quaternion_new.resize(d_num_rigid_parts, Eigen::Quaterniond::Identity());
    d_center_of_mass_initial.resize(d_num_rigid_parts, Eigen::Vector3d::Zero());
    d_center_of_mass_current.resize(d_num_rigid_parts, Eigen::Vector3d::Zero());
    d_center_of_mass_new.resize(d_num_rigid_parts, Eigen::Vector3d::Zero());

    // Set some default values.
    d_ghosts = std::max(LEInteractor::getMinimumGhostWidth(d_interp_kernel_fcn),
                        LEInteractor::getMinimumGhostWidth(d_spread_kernel_fcn));

    // Initialize object with data read from the input and restart databases.
    bool from_restart = RestartManager::getManager()->isFromRestart();
    if (from_restart) getFromRestart();
    if (input_db) getFromInput(input_db, from_restart);

    // Check the choices for the kernel function.
    if (d_interp_kernel_fcn != d_spread_kernel_fcn)
    {
        pout << "WARNING: different kernel functions are being used for  "
                "interpolation and "
                "spreading.\n";
    }

    // Get the Lagrangian Data Manager.
    d_l_data_manager = LDataManager::getManager(d_object_name + "::LDataManager",
                                                d_interp_kernel_fcn,
                                                d_spread_kernel_fcn,
                                                d_error_if_points_leave_domain,
                                                d_ghosts,
                                                d_registered_for_restart);
    d_ghosts = d_l_data_manager->getGhostCellWidth();

    return;
} // IBInterpolantMethod

IBInterpolantMethod::~IBInterpolantMethod()
{
    if (d_registered_for_restart)
    {
        RestartManager::getManager()->unregisterRestartItem(d_object_name);
        d_registered_for_restart = false;
    }
    return;
} // ~IBInterpolantMethod

void
IBInterpolantMethod::registerEulerianVariables()
{
    const IntVector<NDIM> ib_ghosts = getMinimumGhostCellWidth();
    for (auto& q_pair : d_q_interp_idx)
    {
        const std::string& var_name = q_pair.first;
        q_pair.second = -1;
        registerVariable(q_pair.second, d_q_var[var_name], ib_ghosts, NULL /*d_ib_solver->getScratchContext()*/);
    }

    return;
} // registerEulerianVariables

void
IBInterpolantMethod::registerEulerianCommunicationAlgorithms()
{
    Pointer<RefineAlgorithm<NDIM> > ghost_fill_alg = new RefineAlgorithm<NDIM>();
    Pointer<RefineOperator<NDIM> > refine_op = NULL;

    for (const auto& q_pair : d_q_interp_idx)
    {
        const int q_interp_idx = q_pair.second;
        ghost_fill_alg->registerRefine(q_interp_idx, q_interp_idx, q_interp_idx, refine_op);
    }
    registerGhostfillRefineAlgorithm(d_object_name + "::ghost_fill_alg", ghost_fill_alg);

    return;
} // registerEulerianCommunicationAlgorithms

void
IBInterpolantMethod::registerVariableAndHierarchyIntegrator(const std::string& var_name,
                                                            const int var_depth,
                                                            Pointer<Variable<NDIM> > var,
                                                            Pointer<HierarchyIntegrator> hier_integrator)
{
#if !defined(NDEBUG)
    TBOX_ASSERT(d_q_var.find(var_name) == d_q_var.end());
    TBOX_ASSERT(d_q_depth.find(var_name) == d_q_depth.end());
    TBOX_ASSERT(d_q_hier_integrator.find(var_name) == d_q_hier_integrator.end());
#endif
    d_q_var[var_name] = var;
    d_q_depth[var_name] = var_depth;
    d_q_hier_integrator[var_name] = hier_integrator;
    d_q_interp_idx[var_name] = -1;
    d_Q_current_data[var_name] = {};
    d_Q_new_data[var_name] = {};

    return;
} // registerVariableAndHierarchyIntegrator

void
IBInterpolantMethod::registerLInitStrategy(Pointer<LInitStrategy> l_initializer)
{
#if !defined(NDEBUG)
    TBOX_ASSERT(l_initializer);
#endif
    d_l_initializer = l_initializer;
    d_l_data_manager->registerLInitStrategy(d_l_initializer);
    return;
} // registerLInitStrategy

void
IBInterpolantMethod::freeLInitStrategy()
{
    d_l_initializer.setNull();
    d_l_data_manager->freeLInitStrategy();
    return;
} // freeLInitStrategy

LDataManager*
IBInterpolantMethod::getLDataManager() const
{
    return d_l_data_manager;
} // getLDataManager

int
IBInterpolantMethod::getStructuresLevelNumber() const
{
    return d_hierarchy->getFinestLevelNumber();

} // getStructuresLevelNumber

int
IBInterpolantMethod::getStructureHandle(const int lag_idx) const
{
    for (unsigned struct_no = 0; struct_no < d_num_rigid_parts; ++struct_no)
    {
        const std::pair<int, int>& lag_idx_range = d_struct_lag_idx_range[struct_no];
        if (lag_idx_range.first <= lag_idx && lag_idx < lag_idx_range.second) return struct_no;
    }

    return -1;
} // getStructureHandle

void
IBInterpolantMethod::registerLSiloDataWriter(Pointer<LSiloDataWriter> silo_writer)
{
#if !defined(NDEBUG)
    TBOX_ASSERT(silo_writer);
#endif
    d_silo_writer = silo_writer;
    d_l_data_manager->registerLSiloDataWriter(d_silo_writer);
    return;
} // registerLSiloDataWriter

const IntVector<NDIM>&
IBInterpolantMethod::getMinimumGhostCellWidth() const
{
    return d_ghosts;
} // getMinimumGhostCellWidth

void
IBInterpolantMethod::setupTagBuffer(Array<int>& tag_buffer, Pointer<GriddingAlgorithm<NDIM> > gridding_alg) const
{
    const int finest_hier_ln = gridding_alg->getMaxLevels() - 1;
    const int tsize = tag_buffer.size();
    tag_buffer.resizeArray(finest_hier_ln);
    for (int i = tsize; i < finest_hier_ln; ++i) tag_buffer[i] = 0;
    const int gcw = d_ghosts.max();
    for (int tag_ln = 0; tag_ln < finest_hier_ln; ++tag_ln)
    {
        const int data_ln = tag_ln + 1;
        const int can_be_refined = data_ln < finest_hier_ln;
        if (!d_l_initializer->getLevelHasLagrangianData(data_ln, can_be_refined)) continue;
        tag_buffer[tag_ln] = std::max(tag_buffer[tag_ln], gcw);
    }
    for (int ln = finest_hier_ln - 2; ln >= 0; --ln)
    {
        tag_buffer[ln] =
            std::max(tag_buffer[ln], tag_buffer[ln + 1] / gridding_alg->getRatioToCoarserLevel(ln + 1).max() + 1);
    }
    return;
} // setupTagBuffer

void
IBInterpolantMethod::preprocessIntegrateData(double current_time, double new_time, int /*num_cycles*/)
{
    d_current_time = current_time;
    d_new_time = new_time;
    d_half_time = current_time + 0.5 * (new_time - current_time);

    int ierr;
    const int coarsest_ln = 0;
    const int finest_ln = d_hierarchy->getFinestLevelNumber();

    // Look-up or allocate Lagangian data.
    d_X_current_data.resize(finest_ln + 1);
    d_X_new_data.resize(finest_ln + 1);
    for (auto& Q_pair : d_Q_current_data)
    {
        const std::string& name = Q_pair.first;
        Q_pair.second.resize(finest_ln + 1);
        d_Q_new_data[name].resize(finest_ln + 1);
    }

    for (int ln = coarsest_ln; ln <= finest_ln; ++ln)
    {
        if (!d_l_data_manager->levelContainsLagrangianData(ln)) continue;
        d_X_current_data[ln] = d_l_data_manager->getLData(LDataManager::POSN_DATA_NAME, ln);
        d_X_new_data[ln] = d_l_data_manager->createLData("X_new", ln, NDIM);

        for (auto& Q_pair : d_Q_current_data)
        {
            const std::string& name = Q_pair.first;
            Q_pair.second[ln] = d_l_data_manager->getLData(name, ln);
            d_Q_new_data[name][ln] = d_l_data_manager->createLData(name + "_new", ln, d_q_depth[name]);
        }

        // Initialize X^{n+1} to equal X^{n}, and initialize Q^{n+1} to equal Q^{n}.
        ierr = VecCopy(d_X_current_data[ln]->getVec(), d_X_new_data[ln]->getVec());
        IBTK_CHKERRQ(ierr);
        for (auto& Q_pair : d_Q_current_data)
        {
            const std::string& name = Q_pair.first;
            ierr = VecCopy(Q_pair.second[ln]->getVec(), d_Q_new_data[name][ln]->getVec());
            IBTK_CHKERRQ(ierr);
        }
    }

    return;
} // preprocessIntegrateData

void
IBInterpolantMethod::postprocessIntegrateData(double /*current_time*/, double /*new_time*/, int /*num_cycles*/)
{
    int ierr;
    const int coarsest_ln = 0;
    const int finest_ln = d_hierarchy->getFinestLevelNumber();

    // Reset time-dependent Lagrangian data.
    for (int ln = coarsest_ln; ln <= finest_ln; ++ln)
    {
        if (!d_l_data_manager->levelContainsLagrangianData(ln)) continue;
        ierr = VecSwap(d_X_current_data[ln]->getVec(), d_X_new_data[ln]->getVec());
        IBTK_CHKERRQ(ierr);
        for (auto& Q_pair : d_Q_current_data)
        {
            const std::string& name = Q_pair.first;
            ierr = VecSwap(Q_pair.second[ln]->getVec(), d_Q_new_data[name][ln]->getVec());
            IBTK_CHKERRQ(ierr);
        }
    }

    // Deallocate Lagrangian scratch data.
    d_X_current_data.clear();
    d_X_new_data.clear();
    for (auto& Q_pair : d_Q_current_data)
    {
        const std::string& name = Q_pair.first;
        Q_pair.second.clear();
        d_Q_new_data[name].clear();
    }

    // New state becomes current state for the next timestep.
    d_center_of_mass_current = d_center_of_mass_new;
    d_quaternion_current = d_quaternion_new;

    // Reset the current time step interval.
    d_current_time = std::numeric_limits<double>::quiet_NaN();
    d_new_time = std::numeric_limits<double>::quiet_NaN();
    d_half_time = std::numeric_limits<double>::quiet_NaN();
    return;
} // postprocessIntegrateData

void
IBInterpolantMethod::interpolateVelocity(const int /*u_data_idx*/,
                                         const std::vector<Pointer<CoarsenSchedule<NDIM> > >& /*u_synch_scheds*/,
                                         const std::vector<Pointer<RefineSchedule<NDIM> > >& /*u_ghost_fill_scheds*/,
                                         const double /*data_time*/)
{
    TBOX_ERROR("IBInterpolantMethod::interpolateVelocity(). This method is not implemented." << std::endl);
    return;
} // interpolateVelocity

void
IBInterpolantMethod::interpolateQ(const double data_time)
{
    int finest_ln = d_hierarchy->getFinestLevelNumber();
    std::vector<Pointer<LData> >* X_data;
    getPositionData(&X_data, data_time);
    std::vector<Pointer<LData> >* Q_data;

    for (auto& Q_pair : d_Q_current_data)
    {
        const std::string& name = Q_pair.first;
        int q_data_idx = d_q_interp_idx[name];
        copyEulerianDataFromIntegrator(name, q_data_idx, data_time);
        getQData(name, &Q_data, data_time);
        d_l_data_manager->interp(q_data_idx,
                                 *Q_data,
                                 *X_data,
                                 std::vector<Pointer<CoarsenSchedule<NDIM> > >(finest_ln + 1, NULL),
                                 getGhostfillRefineSchedules(d_object_name + "::ghost_fill_alg"),
                                 data_time);
    }

    return;
} // interpolateQ

void
IBInterpolantMethod::interpolateQ()
{
    interpolateQ(d_current_time);
    interpolateQ(d_new_time);

    return;
} // interpolateQ

void
IBInterpolantMethod::forwardEulerStep(const double /*current_time*/, const double /*new_time*/)
{
    TBOX_ERROR("IBInterpolantMethod::forwardEulerStep(). This method is not implemented." << std::endl);
    return;
} // forwardEulerStep

void
IBInterpolantMethod::backwardEulerStep(const double /*current_time*/, const double /*new_time*/)
{
    TBOX_ERROR("IBInterpolantMethod::backwardEulerStep(). This method is not implemented." << std::endl);
    return;
} // backwardEulerStep

void
IBInterpolantMethod::midpointStep(const double /*current_time*/, const double /*new_time*/)
{
    TBOX_ERROR("IBInterpolantMethod::midpointStep(). This method is not implemented." << std::endl);
    return;
} // midpointStep

void
IBInterpolantMethod::trapezoidalStep(const double /*current_time*/, const double /*new_time*/)
{
    TBOX_ERROR("IBInterpolantMethod::trapezoidalStep(). This method is not implemented." << std::endl);
    return;
} // trapezoidalStep

void
IBInterpolantMethod::updateMeshPosition(double current_time,
                                        double new_time,
                                        const EigenAlignedVector<Eigen::Vector3d>& U,
                                        const EigenAlignedVector<Eigen::Vector3d>& W)
{
    const double dt = new_time - current_time;

    // Fill the rotation matrix of structures with rotation angle (W^n+1)*dt.
    EigenAlignedVector<Eigen::Matrix3d> rotation_mat(d_num_rigid_parts, Eigen::Matrix3d::Identity(3, 3));
    set_rotation_matrix(W, d_quaternion_current, d_quaternion_new, rotation_mat, dt);

    // Rotate the body with new rotational velocity about origin
    // and translate the body to newer position.
    const int coarsest_ln = 0;
    const int finest_ln = d_hierarchy->getFinestLevelNumber();
    for (int ln = coarsest_ln; ln <= finest_ln; ++ln)
    {
        if (!d_l_data_manager->levelContainsLagrangianData(ln)) continue;

        boost::multi_array_ref<double, 2>& X_new_array = *d_X_new_data[ln]->getLocalFormVecArray();
        const boost::multi_array_ref<double, 2>& X0_array =
            *(d_l_data_manager->getLData("X0", ln)->getLocalFormVecArray());
        const Pointer<LMesh> mesh = d_l_data_manager->getLMesh(ln);
        const std::vector<LNode*>& local_nodes = mesh->getLocalNodes();

        // Get structures on this level.
        const std::vector<int> structIDs = d_l_data_manager->getLagrangianStructureIDs(ln);
        const unsigned structs_on_this_ln = (unsigned)structIDs.size();
#if !defined(NDEBUG)
        TBOX_ASSERT(structs_on_this_ln == d_num_rigid_parts);
#endif

        for (const auto& node_idx : local_nodes)
        {
            const int lag_idx = node_idx->getLagrangianIndex();
            const int local_idx = node_idx->getLocalPETScIndex();
            double* const X_new = &X_new_array[local_idx][0];
            const double* const X0 = &X0_array[local_idx][0];
            Eigen::Vector3d dr = Eigen::Vector3d::Zero();

            int struct_handle = 0;
            if (structs_on_this_ln > 1) struct_handle = getStructureHandle(lag_idx);

            for (unsigned int d = 0; d < NDIM; ++d)
            {
                dr[d] = X0[d] - d_center_of_mass_initial[struct_handle][d];
            }

            // Rotate dr vector using the rotation matrix.
            const Eigen::Vector3d R_dr = rotation_mat[struct_handle] * dr;
            for (unsigned int d = 0; d < NDIM; ++d)
            {
                X_new[d] = d_center_of_mass_current[struct_handle][d] + R_dr[d] + dt * U[struct_handle][d];
            }
        }
        d_X_new_data[ln]->restoreArrays();
        d_l_data_manager->getLData("X0", ln)->restoreArrays();
    }

    // Compute the new center of mass.
    for (unsigned struct_no = 0; struct_no < d_num_rigid_parts; ++struct_no)
    {
        for (unsigned int d = 0; d < NDIM; ++d)
        {
            d_center_of_mass_new[struct_no][d] = d_center_of_mass_current[struct_no][d] + dt * U[struct_no][d];
        }
    }

    return;
} // updateMeshPosition

void
IBInterpolantMethod::computeLagrangianForce(double /*data_time*/)
{
    TBOX_ERROR("IBInterpolantMethod::computeLagrangianForce(). This method is not implemented." << std::endl);
    return;
} // computeLagrangianForce

void
IBInterpolantMethod::spreadForce(const int /*f_data_idx*/,
                                 RobinPhysBdryPatchStrategy* /*f_phys_bdry_op*/,
                                 const std::vector<Pointer<RefineSchedule<NDIM> > >& /*f_prolongation_scheds*/,
                                 const double /*data_time*/)
{
    TBOX_ERROR("IBInterpolantMethod::spreadForce(). This method is not implemented." << std::endl);
    return;
} // spreadForce

void
IBInterpolantMethod::spreadQ(double data_time)
{
    int finest_ln = d_hierarchy->getFinestLevelNumber();
    Pointer<PatchLevel<NDIM> > finest_level = d_hierarchy->getPatchLevel(finest_ln);
    const IntVector<NDIM>& ratio = finest_level->getRatio();
    Pointer<CartesianGridGeometry<NDIM> > grid_geom = d_hierarchy->getGridGeometry();
    const double* dx0 = grid_geom->getDx();
    std::array<double, NDIM> dx;
    PetscScalar vol = 1.0;
    for (unsigned i = 0; i < NDIM; ++i)
    {
        dx[i] = dx0[i] / ratio(i);
        vol *= dx[i];
    }

    std::vector<Pointer<LData> >* X_data;
    getPositionData(&X_data, data_time);
    std::vector<Pointer<LData> >* Q_data;

    for (auto& Q_pair : d_Q_current_data)
    {
        const std::string& name = Q_pair.first;
        int q_data_idx = d_q_interp_idx[name];
        zeroOutEulerianData(name, q_data_idx);

        getQData(name, &Q_data, data_time);
        Vec l_data_vec = (*Q_data)[finest_ln]->getVec();
        VecScale(l_data_vec, vol);
        d_l_data_manager->spread(q_data_idx, *Q_data, *X_data, (RobinPhysBdryPatchStrategy*)NULL);
        VecScale(l_data_vec, 1.0 / vol);
    }

    return;
} // spreadQ

void
IBInterpolantMethod::copyEulerianDataToIntegrator(double data_time)
{
    for (auto& Q_pair : d_Q_current_data)
    {
        const std::string& name = Q_pair.first;
        int q_data_idx = d_q_interp_idx[name];
        copyEulerianDataToIntegrator(name, q_data_idx, data_time);
    }
    return;
} // copyEulerianDataToIntegrator

void
IBInterpolantMethod::initializePatchHierarchy(
    Pointer<PatchHierarchy<NDIM> > hierarchy,
    Pointer<GriddingAlgorithm<NDIM> > gridding_alg,
    int /*u_data_idx*/,
    const std::vector<Pointer<CoarsenSchedule<NDIM> > >& /*u_synch_scheds*/,
    const std::vector<Pointer<RefineSchedule<NDIM> > >& /*u_ghost_fill_scheds*/,
    int /*integrator_step*/,
    double /*init_data_time*/,
    bool initial_time)
{
    // Cache pointers to the patch hierarchy and gridding algorithm.
    d_hierarchy = hierarchy;
    d_gridding_alg = gridding_alg;

    // Set structure index info.
    const int struct_ln = getStructuresLevelNumber();
    std::vector<int> structIDs = d_l_data_manager->getLagrangianStructureIDs(struct_ln);
    std::sort(structIDs.begin(), structIDs.end());
    const auto structs_on_this_ln = static_cast<unsigned>(structIDs.size());

    for (unsigned struct_no = 0; struct_no < structs_on_this_ln; ++struct_no)
    {
        d_struct_lag_idx_range[struct_no] =
            d_l_data_manager->getLagrangianStructureIndexRange(structIDs[struct_no], struct_ln);
    }

    // Initialize initial center of mass of structures.
    const int finest_ln = d_hierarchy->getFinestLevelNumber();
    std::vector<Pointer<LData> > X0_data_vec(finest_ln + 1, Pointer<LData>(NULL));
    X0_data_vec[finest_ln] = d_l_data_manager->getLData("X0", finest_ln);
    computeCenterOfMass(d_center_of_mass_initial, X0_data_vec);

    if (initial_time)
    {
        d_center_of_mass_current = d_center_of_mass_initial;
    }

    return;
} // initializePatchHierarchy

void
IBInterpolantMethod::addWorkloadEstimate(Pointer<PatchHierarchy<NDIM> > hierarchy, const int workload_data_idx)
{
    d_l_data_manager->addWorkloadEstimate(hierarchy, workload_data_idx);
    return;
} // addWorkloadEstimate

void IBInterpolantMethod::beginDataRedistribution(Pointer<PatchHierarchy<NDIM> > /*hierarchy*/,
                                                  Pointer<GriddingAlgorithm<NDIM> > /*gridding_alg*/)
{
    d_l_data_manager->beginDataRedistribution();
    return;
} // beginDataRedistribution

void IBInterpolantMethod::endDataRedistribution(Pointer<PatchHierarchy<NDIM> > /*hierarchy*/,
                                                Pointer<GriddingAlgorithm<NDIM> > /*gridding_alg*/)
{
    d_l_data_manager->endDataRedistribution();
    return;
} // endDataRedistribution

void
IBInterpolantMethod::initializeLevelData(Pointer<BasePatchHierarchy<NDIM> > hierarchy,
                                         int level_number,
                                         double init_data_time,
                                         bool can_be_refined,
                                         bool initial_time,
                                         Pointer<BasePatchLevel<NDIM> > old_level,
                                         bool allocate_data)
{
    const int finest_hier_level = hierarchy->getFinestLevelNumber();
    d_l_data_manager->setPatchHierarchy(hierarchy);
    d_l_data_manager->setPatchLevels(0, finest_hier_level);
    d_l_data_manager->initializeLevelData(
        hierarchy, level_number, init_data_time, can_be_refined, initial_time, old_level, allocate_data);
    if (initial_time && d_l_data_manager->levelContainsLagrangianData(level_number))
    {
        for (const auto& Q_pair : d_Q_current_data)
        {
            const std::string& Q_name = Q_pair.first;
            const int Q_depth = d_q_depth[Q_name];
            Pointer<LData> Q_data = d_l_data_manager->createLData(Q_name, level_number, Q_depth, /*manage_data*/ true);
        }
    }

    return;
} // initializeLevelData

void
IBInterpolantMethod::resetHierarchyConfiguration(Pointer<BasePatchHierarchy<NDIM> > hierarchy,
                                                 int coarsest_level,
                                                 int finest_level)
{
    const int finest_hier_level = hierarchy->getFinestLevelNumber();
    d_l_data_manager->setPatchHierarchy(hierarchy);
    d_l_data_manager->setPatchLevels(0, finest_hier_level);
    d_l_data_manager->resetHierarchyConfiguration(hierarchy, coarsest_level, finest_level);

    return;
} // resetHierarchyConfiguration

void
IBInterpolantMethod::applyGradientDetector(Pointer<BasePatchHierarchy<NDIM> > base_hierarchy,
                                           int level_number,
                                           double error_data_time,
                                           int tag_index,
                                           bool initial_time,
                                           bool uses_richardson_extrapolation_too)
{
    Pointer<PatchHierarchy<NDIM> > hierarchy = base_hierarchy;
#if !defined(NDEBUG)
    TBOX_ASSERT(hierarchy);
    TBOX_ASSERT((level_number >= 0) && (level_number <= hierarchy->getFinestLevelNumber()));
    TBOX_ASSERT(hierarchy->getPatchLevel(level_number));
#endif

    // Tag cells that contain Lagrangian nodes.
    d_l_data_manager->applyGradientDetector(
        hierarchy, level_number, error_data_time, tag_index, initial_time, uses_richardson_extrapolation_too);
    return;
} // applyGradientDetector

void
IBInterpolantMethod::putToDatabase(Pointer<Database> db)
{
    db->putInteger("IB_INTERPOLANT_METHOD_VERSION", IB_INTERPOLANT_METHOD_VERSION);
    db->putString("d_interp_kernel_fcn", d_interp_kernel_fcn);
    db->putString("d_spread_kernel_fcn", d_spread_kernel_fcn);
    db->putIntegerArray("d_ghosts", d_ghosts, NDIM);

    const int finest_hier_level = d_hierarchy->getFinestLevelNumber();
    db->putInteger("finest_hier_level", finest_hier_level);

    for (unsigned int struct_no = 0; struct_no < d_num_rigid_parts; ++struct_no)
    {
        std::ostringstream C, Q;
        C << "C_" << struct_no;
        Q << "Q_" << struct_no;

        double Q_coeffs[4] = { d_quaternion_current[struct_no].w(),
                               d_quaternion_current[struct_no].x(),
                               d_quaternion_current[struct_no].y(),
                               d_quaternion_current[struct_no].z() };

        db->putDoubleArray(C.str(), &d_center_of_mass_current[struct_no][0], 3);
        db->putDoubleArray(Q.str(), &Q_coeffs[0], 4);
    }

    return;
} // putToDatabase

/////////////////////////////// PROTECTED ////////////////////////////////////

void
IBInterpolantMethod::getPositionData(std::vector<Pointer<LData> >** X_data, double data_time)
{
    if (IBTK::rel_equal_eps(data_time, d_current_time))
    {
        *X_data = &d_X_current_data;
    }
    else if (IBTK::rel_equal_eps(data_time, d_new_time))
    {
        *X_data = &d_X_new_data;
    }
    else
    {
        TBOX_ERROR(
            "IBInterpolantMethod::getPositionData() Structure position inquired at times other than current and new. "
            "\n");
    }
    return;
} // getPositionData

void
IBInterpolantMethod::copyEulerianDataFromIntegrator(const std::string& var_name, int q_data_idx, double data_time)
{
    int q_hier_idx = -1;

    Pointer<HierarchyIntegrator> hier_integrator = d_q_hier_integrator[var_name];
    Pointer<Variable<NDIM> > var = d_q_var[var_name];

    VariableDatabase<NDIM>* var_db = VariableDatabase<NDIM>::getDatabase();
    if (IBTK::rel_equal_eps(data_time, d_current_time))
    {
        q_hier_idx = var_db->mapVariableAndContextToIndex(var, hier_integrator->getCurrentContext());
    }
    else if (IBTK::rel_equal_eps(data_time, d_new_time))
    {
        q_hier_idx = var_db->mapVariableAndContextToIndex(var, hier_integrator->getNewContext());
    }
    else
    {
        TBOX_ERROR(
            "IBInterpolantMethod::getEulerianDataForInterpolation() Interpolation not supported at times other than "
            "current and new.\n");
    }

    // Copy integrator data into q_data_idx
    Pointer<CellVariable<NDIM, double> > cc_var = var;
    Pointer<SideVariable<NDIM, double> > sc_var = var;
    if (cc_var)
    {
        HierarchyCellDataOpsReal<NDIM, double> hier_data_ops(d_hierarchy);
        hier_data_ops.copyData(q_data_idx, q_hier_idx);
    }

    if (sc_var)
    {
        HierarchySideDataOpsReal<NDIM, double> hier_data_ops(d_hierarchy);
        hier_data_ops.copyData(q_data_idx, q_hier_idx);
    }

    return;
} // copyEulerianDataFromIntegrator

void
IBInterpolantMethod::zeroOutEulerianData(const std::string& var_name, int q_data_idx)
{
    Pointer<Variable<NDIM> > var = d_q_var[var_name];

    Pointer<CellVariable<NDIM, double> > cc_var = var;
    Pointer<SideVariable<NDIM, double> > sc_var = var;
    if (cc_var)
    {
        HierarchyCellDataOpsReal<NDIM, double> hier_data_ops(d_hierarchy);
        hier_data_ops.setToScalar(q_data_idx, 0.0);
    }

    if (sc_var)
    {
        HierarchySideDataOpsReal<NDIM, double> hier_data_ops(d_hierarchy);
        hier_data_ops.setToScalar(q_data_idx, 0.0);
    }

    return;
} // zeroOutEulerianData

void
IBInterpolantMethod::copyEulerianDataToIntegrator(const std::string& var_name, int q_data_idx, double data_time)
{
    int q_hier_idx = -1;

    Pointer<HierarchyIntegrator> hier_integrator = d_q_hier_integrator[var_name];
    Pointer<Variable<NDIM> > var = d_q_var[var_name];

    VariableDatabase<NDIM>* var_db = VariableDatabase<NDIM>::getDatabase();
    if (IBTK::rel_equal_eps(data_time, d_current_time))
    {
        q_hier_idx = var_db->mapVariableAndContextToIndex(var, hier_integrator->getCurrentContext());
    }
    else if (IBTK::rel_equal_eps(data_time, d_new_time))
    {
        q_hier_idx = var_db->mapVariableAndContextToIndex(var, hier_integrator->getNewContext());
    }
    else
    {
        TBOX_ERROR(
            "IBInterpolantMethod::getEulerianDataForInterpolation() Interpolation not supported at times other than "
            "current and new.\n");
    }

    // Copy integrator data into q_data_idx
    Pointer<CellVariable<NDIM, double> > cc_var = var;
    Pointer<SideVariable<NDIM, double> > sc_var = var;
    if (cc_var)
    {
        HierarchyCellDataOpsReal<NDIM, double> hier_data_ops(d_hierarchy);
        hier_data_ops.copyData(q_hier_idx, q_data_idx);
    }

    if (sc_var)
    {
        HierarchySideDataOpsReal<NDIM, double> hier_data_ops(d_hierarchy);
        hier_data_ops.copyData(q_hier_idx, q_data_idx);
    }

    return;
} // copyEulerianDataToIntegrator

void
IBInterpolantMethod::getQData(const std::string& var_name, std::vector<Pointer<LData> >** Q_data, double data_time)
{
    if (IBTK::rel_equal_eps(data_time, d_current_time))
    {
        *Q_data = &d_Q_current_data[var_name];
    }
    else if (IBTK::rel_equal_eps(data_time, d_new_time))
    {
        *Q_data = &d_Q_new_data[var_name];
    }
    else
    {
        TBOX_ERROR("IBInterpolantMethod::getQData() Q data inquired at times other than current and new.\n");
    }
    return;
} // getQData

void
IBInterpolantMethod::computeCenterOfMass(EigenAlignedVector<Eigen::Vector3d>& center_of_mass,
                                         std::vector<SAMRAI::tbox::Pointer<IBTK::LData> >& X_data)
{
    const int coarsest_ln = 0;
    const int finest_ln = d_hierarchy->getFinestLevelNumber();

    // Zero out the COM vector.
    for (unsigned int struct_no = 0; struct_no < d_num_rigid_parts; ++struct_no)
    {
        center_of_mass[struct_no].setZero();
    }

    for (int ln = coarsest_ln; ln <= finest_ln; ++ln)
    {
        if (!d_l_data_manager->levelContainsLagrangianData(ln)) continue;

        const boost::multi_array_ref<double, 2>& X_data_array = *X_data[ln]->getLocalFormVecArray();
        const Pointer<LMesh> mesh = d_l_data_manager->getLMesh(ln);
        const std::vector<LNode*>& local_nodes = mesh->getLocalNodes();

        // Get structures on this level.
        const std::vector<int> structIDs = d_l_data_manager->getLagrangianStructureIDs(ln);
        const unsigned structs_on_this_ln = static_cast<unsigned>(structIDs.size());
#if !defined(NDEBUG)
        TBOX_ASSERT(structs_on_this_ln == d_num_rigid_parts);
#endif

        for (const auto& node_idx : local_nodes)
        {
            const int lag_idx = node_idx->getLagrangianIndex();
            const int local_idx = node_idx->getLocalPETScIndex();
            const double* const X = &X_data_array[local_idx][0];

            int struct_handle = 0;
            if (structs_on_this_ln > 1) struct_handle = getStructureHandle(lag_idx);

            for (unsigned int d = 0; d < NDIM; ++d) center_of_mass[struct_handle][d] += X[d];
        }

        for (unsigned struct_no = 0; struct_no < structs_on_this_ln; ++struct_no)
        {
            IBTK_MPI::sumReduction(&center_of_mass[struct_no][0], NDIM);
            const int total_nodes = getNumberOfNodes(struct_no);
            center_of_mass[struct_no] /= total_nodes;
        }

        X_data[ln]->restoreArrays();
    }
    return;
} // computeCenterOfMass

/////////////////////////////// PRIVATE //////////////////////////////////////

void
IBInterpolantMethod::getFromInput(Pointer<Database> db, bool is_from_restart)
{
    if (!is_from_restart)
    {
        if (db->isString("interp_kernel_fcn") && db->isString("spread_kernel_fcn"))
        {
            d_interp_kernel_fcn = db->getString("interp_kernel_fcn");
            d_spread_kernel_fcn = db->getString("spread_kernel_fcn");
        }
        if (db->isString("interp_delta_fcn") && db->isString("spread_delta_fcn"))
        {
            d_interp_kernel_fcn = db->getString("interp_delta_fcn");
            d_spread_kernel_fcn = db->getString("spread_delta_fcn");
        }
        else if (db->keyExists("delta_fcn"))
        {
            d_interp_kernel_fcn = db->getString("delta_fcn");
            d_spread_kernel_fcn = db->getString("delta_fcn");
        }
        else if (db->keyExists("kernel_fcn"))
        {
            d_interp_kernel_fcn = db->getString("kernel_fcn");
            d_spread_kernel_fcn = db->getString("kernel_fcn");
        }
        else if (db->keyExists("IB_delta_fcn"))
        {
            d_interp_kernel_fcn = db->getString("IB_delta_fcn");
            d_spread_kernel_fcn = db->getString("IB_delta_fcn");
        }
        else if (db->keyExists("IB_kernel_fcn"))
        {
            d_interp_kernel_fcn = db->getString("IB_kernel_fcn");
            d_spread_kernel_fcn = db->getString("IB_kernel_fcn");
        }

        if (db->isInteger("min_ghost_cell_width"))
        {
            d_ghosts = db->getInteger("min_ghost_cell_width");
        }
        else if (db->isDouble("min_ghost_cell_width"))
        {
            d_ghosts = static_cast<int>(std::ceil(db->getDouble("min_ghost_cell_width")));
        }
    }
    TBOX_ASSERT(LEInteractor::isKnownKernel(d_interp_kernel_fcn));
    TBOX_ASSERT(LEInteractor::isKnownKernel(d_spread_kernel_fcn));
    if (db->keyExists("error_if_points_leave_domain"))
        d_error_if_points_leave_domain = db->getBool("error_if_points_leave_domain");
    if (db->keyExists("do_log"))
        d_do_log = db->getBool("do_log");
    else if (db->keyExists("enable_logging"))
        d_do_log = db->getBool("enable_logging");
    return;
} // getFromInput

void
IBInterpolantMethod::getFromRestart()
{
    Pointer<Database> restart_db = RestartManager::getManager()->getRootDatabase();
    Pointer<Database> db;
    if (restart_db->isDatabase(d_object_name))
    {
        db = restart_db->getDatabase(d_object_name);
    }
    else
    {
        TBOX_ERROR(d_object_name << ":  Restart database corresponding to " << d_object_name
                                 << " not found in restart file." << std::endl);
    }
    int ver = db->getInteger("IB_INTERPOLANT_METHOD_VERSION");
    if (ver != IB_INTERPOLANT_METHOD_VERSION)
    {
        TBOX_ERROR(d_object_name << ":  Restart file version different than class version." << std::endl);
    }
    if (db->keyExists("d_interp_kernel_fcn"))
        d_interp_kernel_fcn = db->getString("d_interp_kernel_fcn");
    else if (db->keyExists("d_interp_delta_fcn"))
        d_interp_kernel_fcn = db->getString("d_interp_delta_fcn");
    if (db->keyExists("d_spread_kernel_fcn"))
        d_spread_kernel_fcn = db->getString("d_spread_kernel_fcn");
    else if (db->keyExists("d_spread_delta_fcn"))
        d_spread_kernel_fcn = db->getString("d_spread_delta_fcn");
    TBOX_ASSERT(LEInteractor::isKnownKernel(d_interp_kernel_fcn));
    TBOX_ASSERT(LEInteractor::isKnownKernel(d_spread_kernel_fcn));
    db->getIntegerArray("d_ghosts", d_ghosts, NDIM);

    for (unsigned int struct_no = 0; struct_no < d_num_rigid_parts; ++struct_no)
    {
        std::ostringstream C, Q;
        C << "C_" << struct_no;
        Q << "Q_" << struct_no;

        double Q_coeffs[4];
        db->getDoubleArray(C.str(), &d_center_of_mass_current[struct_no][0], 3);
        db->getDoubleArray(Q.str(), &Q_coeffs[0], 4);

        d_quaternion_current[struct_no].w() = Q_coeffs[0];
        d_quaternion_current[struct_no].x() = Q_coeffs[1];
        d_quaternion_current[struct_no].y() = Q_coeffs[2];
        d_quaternion_current[struct_no].z() = Q_coeffs[3];
        d_quaternion_current[struct_no].normalized();
    }

    return;
} // getFromRestart

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

} // namespace IBAMR

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


/*=============================================================================
    Copyright (c) 2001-2007 Joel de Guzman
    Copyright (c) 2001-2009 Hartmut Kaiser

    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)
=============================================================================*/
///////////////////////////////////////////////////////////////////////////////
//
//  A calculator example demonstrating the grammar and semantic actions
//  using phoenix to do the actual expression evaluation. The parser is
//  essentially an "interpreter" that evaluates expressions on the fly.
//
//  Additionally this examples shows how to build and use a lexer based on
//  Ben Hansons Lexertl (http://www.benhanson.net/lexertl.html). This way the
//  parser matches the grammar against the tokens generated by the lexer
//  component and not against the input character stream.
//
//  Even if the benefits of using a lexer for this small calculator grammar may
//  not outweight the corresponding overhead, we provide this example because
//  it allows to concentrate on the essentials without having to understand
//  the semantics first.
//
//  [ JDG June 29, 2002 ]   spirit1
//  [ JDG March 5, 2007 ]   spirit2
//
///////////////////////////////////////////////////////////////////////////////

#include <boost/config/warning_disable.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/lex_lexer_lexertl.hpp>
#include <boost/spirit/include/phoenix_operator.hpp>

#include <iostream>
#include <string>

using namespace boost::spirit;
using namespace boost::spirit::qi;
using namespace boost::spirit::lex;
using namespace boost::spirit::ascii;
using namespace boost::spirit::arg_names;

///////////////////////////////////////////////////////////////////////////////
//  Our token definition
//  This class is used to define all the tokens to be recognized by the lexer.
///////////////////////////////////////////////////////////////////////////////
template <typename Lexer>
struct calculator_tokens : lexer_def<Lexer>
{
    template <typename Self>
    void def (Self& self)
    {
        // unsigned integer token definition
        ui = "[1-9][0-9]*";

        // whitespace token definitions
        ws = "[ \\t\\f\\v]+";
        c_comment = "\\/\\*[^*]*\\*+([^/*][^*]*\\*+)*\\/";

        // build token set
        skipper = ws | c_comment;                   // += is allowed as well

        // associate the tokens and the token set with the lexer
        // default lexer state
        self = token_def<>('+') | '-' | '*' | '/' | '(' | ')';
        self += ui;                                 // still default state

        // The token_set 'skipper' get's assigned to a separate lexer state
        // which allows to use it separately from the main tokenization
        // (it is used as the skipper parser below)
        self("SKIPPER") = skipper;                  // lexer state "SKIPPER"
    }

    // This are the tokens to be recognized by the lexer.
    token_def<unsigned int> ui;   // matched tokens will have a unsigned int
    token_def<> ws, c_comment;    // attribute will not be used

    // This is the only token set explicitly defined by this lexer because it
    // needs to be accessible from the outside (used as skip parser below).
    typename Lexer::token_set skipper;
};

///////////////////////////////////////////////////////////////////////////////
//  Our calculator grammar
//
//  The difference to the original example (calc3.cpp) is that we are
//  specifying a second template parameter referring to the lexer. Further, we
//  use a defined tokenset from above as the skip parser.
///////////////////////////////////////////////////////////////////////////////
template <typename Iterator, typename Lexer>
struct calculator : grammar<Iterator, int(), typename Lexer::token_set>
{
    template <typename TokenDef>
    calculator(TokenDef const& tok)
      : calculator::base_type(expression)
    {
        // grammar
        expression =
            term                            [_val = _1]
            >> *(   ('+' >> term            [_val += _1])
                |   ('-' >> term            [_val -= _1])
                )
            ;

        term =
            factor                          [_val = _1]
            >> *(   ('*' >> factor          [_val *= _1])
                |   ('/' >> factor          [_val /= _1])
                )
            ;

        factor =
            tok.ui                          [_val = _1]
            |   '(' >> expression           [_val = _1] >> ')'
            |   ('-' >> factor              [_val = -_1])
            |   ('+' >> factor              [_val = _1])
            ;
    }

    rule<Iterator, int(), typename Lexer::token_set> expression, term, factor;
};

///////////////////////////////////////////////////////////////////////////////
//  Main program
///////////////////////////////////////////////////////////////////////////////
int
main()
{
    std::cout << "/////////////////////////////////////////////////////////\n\n";
    std::cout << "Expression parser...\n\n";
    std::cout << "/////////////////////////////////////////////////////////\n\n";
    std::cout << "Type an expression...or [q or Q] to quit\n\n";

    // iterator type used to expose the underlying input stream
    typedef std::string::const_iterator base_iterator_type;

    // This is the lexer token type to use. The second template parameter lists
    // all attribute types used for token_def's during token definition (see
    // calculator_tokens<> above). Here we use the predefined lexertl token
    // type, but any compatible token type may be used.
    typedef lexertl_token<
        base_iterator_type, boost::mpl::vector<unsigned int>
    > token_type;

    // This is the lexer type to use to tokenize the input.
    // Here we use the lexertl based lexer engine.
    typedef lexertl_lexer<token_type> lexer_type;

    // This is the token definition type (derived from the given lexer type).
    typedef calculator_tokens<lexer_type> calculator_tokens;

    // this is the iterator type exposed by the lexer
    typedef lexer<calculator_tokens>::iterator_type iterator_type;

    // this is the type of the grammar to parse
    typedef calculator<iterator_type, lexer_type> calculator;

    // now we use the types defined above to create the lexer and grammar
    // object instances needed to invoke the parsing process
    calculator_tokens tokens;                       // Our token definition
    calculator calc(tokens);                        // Our grammar definition

    lexer<calculator_tokens> lex(tokens);           // Our lexer

    // get input line by line and feed the parser to evaluate the expressions
    // read in from the input
    std::string str;
    int result;
    while (std::getline(std::cin, str))
    {
        if (str.empty() || str[0] == 'q' || str[0] == 'Q')
            break;

        // At this point we generate the iterator pair used to expose the
        // tokenized input stream.
        iterator_type iter = lex.begin(str.begin(), str.end());
        iterator_type end = lex.end();

        // Parsing is done based on the the token stream, not the character
        // stream read from the input.
        // Note, how we use the token_set defined above as the skip parser.
        bool r = phrase_parse(iter, end, calc, result, tokens.skipper);

        if (r && iter == end)
        {
            std::cout << "-------------------------\n";
            std::cout << "Parsing succeeded\n";
            std::cout << "result = " << result << std::endl;
            std::cout << "-------------------------\n";
        }
        else
        {
            std::cout << "-------------------------\n";
            std::cout << "Parsing failed\n";
            std::cout << "-------------------------\n";
        }
    }

    std::cout << "Bye... :-) \n\n";
    return 0;
}


#include "domain/mesh/mesh_cartesian.hpp"

#include <algorithm>
#include <cmath>
#include <vector>

#include <deal.II/base/point.h>
#include <deal.II/grid/grid_generator.h>
#include <deal.II/base/tensor.h>
#include <deal.II/base/utilities.h>

#include "domain/mesh/factory.hpp"
#include "problem/parameter_types.hpp"

namespace bart::domain::mesh {

template <int dim>
MeshCartesian<dim>::MeshCartesian(const std::vector<double> spatial_max, const std::vector<int> n_cells,
                                  const std::string material_mapping)
    : MeshCartesian(spatial_max, n_cells) {
  ParseMaterialMap(material_mapping);
}

template <int dim>
MeshCartesian<dim>::MeshCartesian(const std::vector<double> spatial_max, const std::vector<int> n_cells) {
  const std::string error_string{"MeshCartesian constructor error: "};
  // Check lengths of spatial max and n_cells
  AssertThrow(spatial_max.size() == dim, dealii::ExcMessage(error_string + "incorrect spatial vector size"))
  AssertThrow(n_cells.size() == dim, dealii::ExcMessage(error_string + "incorrect number of cells vector size"))
  AssertThrow(std::all_of(spatial_max.cbegin(), spatial_max.cend(), [](const double val){ return val != 0; }),
              dealii::ExcMessage(error_string + "spatial max has 0 entry."))
  AssertThrow(std::all_of(n_cells.cbegin(), n_cells.cend(), [](const int val){ return val > 0; }),
              dealii::ExcMessage(error_string + "n_cells values must be >= 0"))

  std::copy(n_cells.begin(), n_cells.end(), n_cells_.begin());
  std::copy(spatial_max.begin(), spatial_max.end(), spatial_max_.begin());
  std::string description = "deal.II Cartesian Mesh, "+ std::to_string(dim) + "D, Size: {";

  auto int_comma_fold = [](std::string a, int b) { return std::move(a) + ", " + std::to_string(b); };
  auto double_comma_fold = [](std::string a, double b) { return std::move(a) + ", " + std::to_string(b); };

  std::string size_string = std::accumulate(std::next(spatial_max.begin()), spatial_max.end(),
                                            std::to_string(spatial_max.at(0)), double_comma_fold);
  std::string n_cells_string = std::accumulate(std::next(n_cells.begin()), n_cells.end(),
                                               std::to_string(n_cells.at(0)), int_comma_fold);
  description += size_string + "}, N_cells: {" + n_cells_string + "}";
  this->set_description(description, utility::DefaultImplementation(true));
}

template <int dim>
void MeshCartesian<dim>::FillTriangulation(dealii::Triangulation<dim> &to_fill) {
  dealii::Point<dim> diagonal;
  dealii::Point<dim> origin;

  for (int i = 0; i < dim; ++i)
    diagonal[i] = spatial_max_[i];

  std::vector<unsigned int> number_of_cells{n_cells_.begin(), n_cells_.end()};
  dealii::GridGenerator::subdivided_hyper_rectangle(to_fill, number_of_cells, origin, diagonal);
}

template <int dim>
void MeshCartesian<dim>::ParseMaterialMap(std::string material_mapping) {
  using dealii::Utilities::split_string_list;
  using dealii::Utilities::string_to_int;
  using StringVector = std::vector<std::string>;

  StringVector z_blocks = split_string_list(material_mapping, "\n\n");

  std::reverse(z_blocks.begin(), z_blocks.end());

  for (unsigned k = 0; k < z_blocks.size(); ++k) {
    StringVector y_line = split_string_list(z_blocks.at(k), "\n");
    std::reverse(y_line.begin(), y_line.end());
    for (unsigned j = 0; j < y_line.size(); ++j) {
      StringVector x_positions = split_string_list(y_line.at(j), " ");
      for (unsigned i = 0; i < x_positions.size(); ++i) {
        std::array<unsigned, 3> index{i, j, k};
        std::array<int, dim> location;
        for (int dir = 0; dir < dim; ++dir)
          location.at(dir) = index.at(dir);
        material_mapping_[location] = string_to_int(x_positions.at(i));
      }
      n_material_cells_.at(0) = x_positions.size();
    }
    if (dim > 1)
      n_material_cells_.at(1) = y_line.size();
  }

  if (dim > 2)
    n_material_cells_.at(2) = z_blocks.size();
}

template <int dim>
void MeshCartesian<dim>::FillMaterialID(dealii::Triangulation<dim> &to_fill) {
  for (auto cell = to_fill.begin_active(); cell != to_fill.end(); ++cell) {
    if (cell->is_locally_owned()) {
      int material_id = GetMaterialID(cell->center());
      cell->set_material_id(material_id);
    }
  }
}

template <int dim>
void MeshCartesian<dim>::FillBoundaryID(dealii::Triangulation<dim> &to_fill) {
  using Boundary = bart::problem::Boundary;
  int faces_per_cell = dealii::GeometryInfo<dim>::faces_per_cell;
  double zero_tol = 1.0e-14;

  for (auto cell = to_fill.begin_active(); cell != to_fill.end(); ++cell) {
    if (cell->is_locally_owned() && cell->at_boundary()) {
      for (int face_id = 0; face_id < faces_per_cell; ++face_id) {
        auto face = cell->face(face_id);
        if (face->at_boundary()) {
          dealii::Point<dim> face_center = face->center();
          switch (dim) {
            case 3: {
              if (std::fabs(face_center[2]) < zero_tol) {
                face->set_boundary_id(static_cast<int>(Boundary::kZMin));
                break;
              } else if (std::fabs(face_center[2] - spatial_max_.at(2)) < zero_tol) {
                face->set_boundary_id(static_cast<int>(Boundary::kZMax));
                break;
              }
              [[fallthrough]];
            }
            case 2: {
              if (std::fabs(face_center[1]) < zero_tol) {
                face->set_boundary_id(static_cast<int>(Boundary::kYMin));
                break;
              } else if (std::fabs(face_center[1] - spatial_max_[1]) < zero_tol) {
                face->set_boundary_id(static_cast<int>(Boundary::kYMax));
                break;
              }
              [[fallthrough]];
            }
              // Fall through to check x-direction
            case 1: {
              if (std::fabs(face_center[0]) < zero_tol) {
                face->set_boundary_id(static_cast<int>(Boundary::kXMin));
                break;
              } else if (std::fabs(face_center[0] - spatial_max_[0]) < zero_tol) {
                face->set_boundary_id(static_cast<int>(Boundary::kXMax));
                break;
              }
              [[fallthrough]];
            }
            default: {
              AssertThrow(false,
                          dealii::ExcMessage("The location of a boundary could "
                                             "not be determined."))
            }
          }
        }
      }
    }
  }
}

template <int dim>
int MeshCartesian<dim>::GetMaterialID(dealii::Point<dim> location) {
  std::array<double, dim> array_location;
  for (int i = 0; i < dim; ++i)
    array_location[i] = location[i];
  return GetMaterialID(array_location);
}

template <int dim>
int MeshCartesian<dim>::GetMaterialID(std::array<double, dim> location) {
  std::array<int, dim> relative_location;

  for (int i = 0; i < dim; ++i) {
    double cell_size = spatial_max_.at(i)/n_material_cells_.at(i);
    double cell_location = location.at(i) / cell_size;
    int cell_index = std::floor(cell_location);

    if (static_cast<double>(cell_index) == cell_location && cell_index != 0) {
      relative_location.at(i) = cell_index - 1;
    } else {
      relative_location.at(i) = cell_index;
    }
  }

  return material_mapping_[relative_location];
}

template <int dim>
bool MeshCartesian<dim>::is_registered_ =
    MeshIFactory<dim, const std::vector<double>, const std::vector<int>, const std::string>::get()
    .RegisterConstructor(
        MeshName::kCartesian,
        [] (const std::vector<double> spatial_max,
            const std::vector<int> n_cells,
            const std::string material_mapping) {
          std::unique_ptr<MeshI<dim>> return_ptr =
              std::make_unique<MeshCartesian<dim>>(spatial_max, n_cells, material_mapping);
          return return_ptr; }
    );

template class MeshCartesian<1>;
template class MeshCartesian<2>;
template class MeshCartesian<3>;

} // } // namespace bart::domain::mesh


// -*- C++ -*-

// The MIT License (MIT)
//
// Copyright (c) 2021 Alexander Samoilov
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

#include <boost/program_options.hpp>
#include "PoissonProblem.hpp"

struct program_options
{
    bool verbose                    = {false};
    size_t M                        = {32};    // grid size along `x` direction
    size_t N                        = {32};    // grid size along `y` direction
    double x_min                    = {-1.0};  // minimum `x` value
    double x_max                    = { 1.0};  // maximum `x` value
    double y_min                    = {-1.0};  // minimum `y` value
    double y_max                    = { 1.0};  // maximum `y` value
    std::string asy_name            = {""};    // dump streamlines to vector `.asy` file if name is given
};

program_options parse_command_line(int argc, char** argv)
{
    namespace po = boost::program_options;
    po::options_description desc("allowed options");
    desc.add_options()
            ("help",                   "describe arguments")
            ("verbose",                "be verbose")
            ("M",                      po::value<size_t>(),      "`M`     -- grid size along `x` direction")
            ("N",                      po::value<size_t>(),      "`N`     -- grid size along `y` direction")
            ("x_min",                  po::value<double>(),      "`x_min` -- minimum `x` value")
            ("x_max",                  po::value<double>(),      "`x_max` -- maximum `x` value")
            ("y_min",                  po::value<double>(),      "`y_min` -- minimum `y` value")
            ("y_max",                  po::value<double>(),      "`y_max` -- maximum `y` value")
            ("asy_name",               po::value<std::string>(), "dump streamlines to vector `.asy` file if name is given");
    try
    {
        po::variables_map vm;
        po::store(po::parse_command_line(argc, argv, desc), vm);
        po::notify(vm);
        program_options popt;

        if (vm.count("help")) {
            std::cout << desc << std::endl;
            std::exit(0);
        }

        popt.verbose = vm.count("verbose");

        if (vm.count("M")) {
            popt.M = vm["M"].as<size_t>();
        }

        if (vm.count("N")) {
            popt.N = vm["N"].as<size_t>();
        }

        if (vm.count("x_min")) {
            popt.x_min = vm["x_min"].as<double>();
        }

        if (vm.count("x_max")) {
            popt.x_max = vm["x_max"].as<double>();
        }

        if (vm.count("y_min")) {
            popt.y_min = vm["y_min"].as<double>();
        }

        if (vm.count("y_max")) {
            popt.y_max = vm["y_max"].as<double>();
        }

        return popt;
    }
    catch(const std::exception& e)
    {
        std::cerr << e.what() << std::endl;
        std::cout << desc << std::endl;
        std::exit(-1);
    }
}

int main(int argc, char **argv)
{
    program_options po = parse_command_line(argc, argv);

    if (po.verbose) {
        std::cout << "problem parameters: M: " << po.M << " N: " << po.N
                  << " x_min: " << po.x_min << " x_max: " << po.x_max
                  << " y_min: " << po.y_min << " y_max: " << po.y_max
                  << std::endl;
    }

    PoissonProblem problem(po.M, po.N, po.x_min, po.x_max, po.y_min, po.y_max, po.verbose);

    problem.solve();

    problem.compute_residual();

    return EXIT_SUCCESS;
}


/***
第三节 完美转发
（1）完美转发的概念和步骤演绎
a)直接调用：  funcLast();
b)转发： 通过funcMiddle()间接调用funcLast。funcMiddle相当于一个跳板函数。如果有参数，那么参数也需要通过funcMiddle中转传递给funcLast()
c)完美转发：const,左值，右值。实参的属性完全不丢失，原原本本的通过funcMiddle转发给funcLast，这种转发就是完美转发。
万能引用：实参的所有信息都会传递到万能引用当中去从而让编译器推导出来函数模板最终的形参类型 (引用折叠)。
完美转发：就是让程序员可以书写接受任意实参的函数模板（funcMiddle_Temp），并将其转发到目标函数（funcLast2)，目标函数会接收到与
转发函数（funcMiddle_Temp）所接收的完全相同（当然包括类型相同比如保持参数的左值、右值特性）的参数。
要实现完美转发，就要用到std::forward了。

（2）std::forward ：C++11中专门为转发而存在的函数。这个函数要么返回一个左值，要么返回一个右值。
万能引用类型才是forward能够发挥作用的重要条件。
理解：
(a)实参原来是个左值j，到了形参中还是左值t2。forward能够转化回原来该实参的左值或者右值性。所以，forward之后还是个左值。
(b)实参原来是个右值20，到了形参中变成了左值t1。forward能够转化回原来该实参的左值或者右值性。所以，forward之后还是个右值。
forward这个函数有强制把左值转换成右值的能力。所以：forward这个函数只对原来是个右值这种情况有用。
forward的能力：保持原始实参的左值性或者右值性
总结：完美转发：比较好的解决了参数转发的问题。

（3）普通参数的完美转发:auto &&
***/

#include <iostream>

//#include <boost/type_index.hpp>
using namespace std;
//#pragma warning(disable : 4996)

//函数模板
//template <typename T>
//void myfunc(T  tmprv)
//{
//	cout << "--------------------------------begin----------------" << endl;
//	using boost::typeindex::type_id_with_cvr;
//	cout << "T=" << type_id_with_cvr<T>().pretty_name() << endl; //显示T的类型
//	cout << "tmprv=" << type_id_with_cvr<decltype(tmprv)>().pretty_name() << endl; //显示tmprv的类型
//	cout << "--------------------------------end------------------" << endl;
//}

namespace _nmsp1
{
	//void funcLast(int v1, int v2)
	void funcLast(int v1, int& v2)
	{
		++v2; //改变v2的值，让其自增1
		cout << v1 + v2 << endl;
	}

	void funcLast2(int&& v1, int& v2)
	{
		cout << v1 << endl;
		cout << v2 << endl;
	}

	//函数模板（跳板函数）：把收到的参数以及这些参数相对应的类型不变的转发给其他函数（完美转发）
	template<typename F, typename T1,typename T2>
	//void funcMiddle_Temp(F f, T1 t1, T2 t2) //f:函数指针类型void(*)(int,int)，而funcLast是函数类型void(int,int)
	void funcMiddle_Temp(F f, T1&& t1, T2&& t2)
	{
		//f(t1, t2);
		f(
			std::forward<T1>(t1), //T1 = int
			std::forward<T2>(t2)  //T2 = int &
		);
	}

}
namespace _nmsp2
{
	void printInfo(int& t)
	{
		cout << "printInfo()参数类型为左值引用" << endl;
	}

	void printInfo(int&& t)
	{
		cout << "printInfo()参数类型为右值引用" << endl;
	}

	void printInfo(const int& t)
	{
		cout << "printInfo()参数类型为const 左值引用" << endl;
	}

	template <typename T>
	void TestF(T&& t)
	{
		printInfo(std::forward<T>(t));
	}

}
namespace _nmsp3
{
	int getData()
	{
		return 3;
	}
	void funcLast3(int v1)
	{
		cout << "v1=" << v1 << endl;
	}

	void funcMiddle_Temp2()
	{
		auto&& result = getData(); //getData返回的是右值，所以auto = int ,result = int &&（右值引用）
		//....对result做各种运算。
		funcLast3(
			std::forward<decltype(result)>(result)
		);
	}

}

int main()
{

	/*int i = 50;
	_nmsp1::funcLast(41, i); //92*/

	/*
	int j = 70;
	_nmsp1::funcMiddle_Temp(_nmsp1::funcLast, 20, j); //91
	*/

	/*
	int i = 50;
	_nmsp1::funcLast(41, i); //直接调用，92,执行完i=51
	*/

	/*
	int j = 70;
	_nmsp1::funcMiddle_Temp(_nmsp1::funcLast, 20, j); //91,执行完本函数，j = 71？70呢？ = 70
	                                                   //当前情况下j被funcMiddle_Temp推断成了int而不是int&
	                                                    //void funcMiddle_Temp(void(*f)(int,int &),int t1,int t2){...}
														*/

	/*
	int j = 70;
	_nmsp1::funcMiddle_Temp(_nmsp1::funcLast, 20, j);  //91,T1=int, t1 = int &&，      T2 = int & , t2 = int &
		                                                  //j = 71;

	/*
	int j = 70;
	//_nmsp1::funcLast2(20, j); //20,70
	_nmsp1::funcMiddle_Temp(_nmsp1::funcLast2, 20, j);
	                                                 //20->t1(int &&)，但是t1本身是左值。
													 */
	/*int&& abc = 1;
	abc = 15;*/

	/*
	_nmsp2::TestF(1); //printInfo()参数类型为右值引用
	int i = 5;
	_nmsp2::TestF(i);  //printInfo()参数类型为左值引用
	_nmsp2::TestF(std::move(i)); //printInfo()参数类型为右值引用     ----std::move能够将左值转换成右值。
	const int j = 8;
	_nmsp2::TestF(j); //printInfo()参数类型为const 左值引用   ------j是个const左值
	_nmsp2::TestF(int(12)); //printInfo()参数类型为右值引用 ----int(12)是个临时对象，是个右值。
	int&& tempvalue = 16;
	_nmsp2::TestF(tempvalue);//printInfo()参数类型为左值引用
	*/

	//_nmsp3::funcLast3(_nmsp3::getData());

	_nmsp3::funcMiddle_Temp2();

	return 0;
}


/***************************************************************************
 * Copyright (C) 2012, Naomasa Matsubayashi
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ***************************************************************************/

#ifndef HERMIT_MPINT_HPP
#define HERMIT_MPINT_HPP

#include <boost/cstdint.hpp>
#include <stdexcept>
#include <string>
#include <boost/lexical_cast.hpp>
#include <boost/type_traits.hpp>
#include <boost/utility/enable_if.hpp>
#include <boost/mpl/less_equal.hpp>
#include <boost/mpl/comparison.hpp>
#include <tommath.h>

#define MPINT_SAFE_CALL( expr ) \
{ \
  int mp_op_result = expr; \
  if( mp_op_result != MP_OKAY ) \
  throw std::runtime_error( mp_error_to_string( mp_op_result ) ); \
}

namespace hermit {
  boost::optional< std::string > is_hex( const std::string &temp );
  boost::optional< std::string > is_dec( const std::string temp );
  boost::optional< std::string > is_oct( const std::string temp );

  class mpint {
    public:
      mpint() {
        MPINT_SAFE_CALL( mp_init( &value ) );
        mp_zero( &value );
      }
      mpint( const mpint &src_ ) {
        MPINT_SAFE_CALL( mp_init_copy( &value, const_cast< mp_int* >( &src_.value ) ) );
      }
      mpint &operator=( const mpint &src_ ) {
        MPINT_SAFE_CALL( mp_init_copy( &value, const_cast< mp_int* >( &src_.value ) ) );
        return *this;
      }
      template< typename T >
        mpint( T value_,
            typename boost::enable_if<
            typename boost::is_integral<
            typename boost::remove_cv< T >::type
            >::type
            >::type* = NULL
            ) {
          MPINT_SAFE_CALL( mp_init( &value ) );
          mp_zero( &value );
          from_string( boost::lexical_cast< std::string >( value_ ), 10 );
        }
      mpint( const std::string &value_ );
      ~mpint() {
        mp_clear( &value );
      }
      void swap( mpint &target ) {
        mp_exch( &value, &target.value );
      }
      void shrink_to_fit() {
        MPINT_SAFE_CALL( mp_shrink( &value ) );
      }
      void reserve( int size_ ) {
        MPINT_SAFE_CALL( mp_grow( &value, size_ ) );
      }
      void clear() {
        mp_zero( &value );
      }
      int size() const {
        return mp_count_bits( const_cast< mp_int* >( &value ) );
      }
      operator uint64_t() const {
        static const mpint mask( "0xFFFFFFFFFFFFFFFF" );
        return boost::lexical_cast< uint64_t >( *this & mask );
      }
      operator int64_t() const {
        static const mpint mask( "0x7FFFFFFFFFFFFFFF" );
        return boost::lexical_cast< int64_t >( *this & mask );
      }
      operator uint32_t() const {
        static const mpint mask( "0xFFFFFFFF" );
        return boost::lexical_cast< uint32_t >( *this & mask );
      }
      operator int32_t() const {
        static const mpint mask( "0x7FFFFFFF" );
        return boost::lexical_cast< int32_t >( *this & mask );
      }
      operator uint16_t() const {
        static const mpint mask( "0xFFFF" );
        return boost::lexical_cast< uint16_t >( *this & mask );
      }
      operator int16_t() const {
        static const mpint mask( "0x7FFF" );
        return boost::lexical_cast< int16_t >( *this & mask );
      }
      operator uint8_t() const {
        static const mpint mask( "0xFF" );
        return boost::lexical_cast< uint8_t >( *this & mask );
      }
      operator int8_t() const {
        static const mpint mask( "0x7F" );
        return boost::lexical_cast< int8_t >( *this & mask );
      }
      mpint &operator>>=( int right ) {
        *this = *this >> right;
        return *this;
      }
      mpint &operator<<=( int right ) {
        *this = *this << right;
        return *this;
      }
      mpint operator>>( int right ) const {
        mpint result;
        mpint remainder;
        MPINT_SAFE_CALL( mp_div_2d( const_cast< mp_int* >( &value ), right, &result.value, &remainder.value ) )
          return result;
      }
      mpint operator<<( int right ) const {
        mpint result;
        MPINT_SAFE_CALL( mp_mul_2d( const_cast< mp_int* >( &value ), right, &result.value ) )
          return result;
      }
      mpint operator^( const mpint &right ) const {
        mpint result;
        MPINT_SAFE_CALL( mp_xor( const_cast< mp_int* >( &value ), const_cast< mp_int* >( &right.value ), &result.value ) );
        return result;
      }
      mpint &operator^=( const mpint &right ) {
        *this = *this ^ right;
        return *this;
      }
      mpint operator|( const mpint &right ) const {
        mpint result;
        MPINT_SAFE_CALL( mp_or( const_cast< mp_int* >( &value ), const_cast< mp_int* >( &right.value ), &result.value ) );
        return result;
      }
      mpint &operator|=( const mpint &right ) {
        *this = *this | right;
        return *this;
      }
      mpint operator&( const mpint &right ) const {
        mpint result;
        MPINT_SAFE_CALL( mp_and( const_cast< mp_int* >( &value ), const_cast< mp_int* >( &right.value ), &result.value ) );
        return result;
      }
      mpint &operator&=( const mpint &right ) {
        *this = *this + right;
        return *this;
      }
      mpint operator-() {
        mpint result;
        MPINT_SAFE_CALL( mp_neg( &result.value, &value ) );
        return result;
      }
      mpint operator+( const mpint &right ) const {
        mpint result;
        MPINT_SAFE_CALL( mp_add( const_cast< mp_int* >( &value ), const_cast< mp_int* >( &right.value ), &result.value ) );
        return result;
      }
      mpint &operator+=( const mpint &right ) {
        *this = *this + right;
        return *this;
      }
      mpint operator++() {
        mpint temp = *this;
        *this += static_cast<mpint>( 1ul );
        return temp;
      }
      mpint &operator++( int ) {
        *this += static_cast<mpint>( 1ul );
        return *this;
      }
      mpint operator-( const mpint &right ) const {
        mpint result;
        MPINT_SAFE_CALL( mp_sub( const_cast< mp_int* >( &value ), const_cast< mp_int* >( &right.value ), &result.value ) );
        return result;
      }
      mpint &operator-=( const mpint &right ) {
        *this = *this - right;
        return *this;
      }
      mpint operator--() {
        mpint temp = *this;
        *this -= static_cast<mpint>( 1ul );
        return temp;
      }
      mpint &operator--( int ) {
        *this -= static_cast<mpint>( 1ul );
        return *this;
      }
      mpint operator*( const mpint &right ) const {
        mpint result;
        MPINT_SAFE_CALL( mp_mul( const_cast< mp_int* >( &value ), const_cast< mp_int* >( &right.value ), &result.value ) );
        return result;
      }
      mpint &operator*=( const mpint &right ) {
        *this = *this * right;
        return *this;
      }
      mpint operator/( const mpint &right ) const {
        mpint result;
        mpint remainder;
        MPINT_SAFE_CALL( mp_div( const_cast< mp_int* >( &value ), const_cast< mp_int* >( &right.value ), &result.value, &remainder.value ) );
        return result;
      }
      mpint &operator/=( const mpint &right ) {
        *this = *this / right;
        return *this;
      }
      mpint operator%( const mpint &right ) const {
        mpint result;
        MPINT_SAFE_CALL( mp_mod( const_cast< mp_int* >( &value ), const_cast< mp_int* >( &right.value ), &result.value ) );
        return result;
      }
      mpint &operator%=( const mpint &right ) {
        *this = *this % right;
        return *this;
      }
      std::string to_string( int radix ) const;
      void from_string( const std::string &str, int radix ) {
        MPINT_SAFE_CALL( mp_read_radix( const_cast< mp_int* >( &value ), str.c_str(), radix ) );
      }

      const mp_int &get_raw() const {
        return value;
      }
    private:
      mp_int value;
  };

  std::ostream &operator<<( std::ostream &stream, const mpint &mp_value );
  std::istream &operator>>( std::istream &stream, mpint &mp_value );
  mpint sqrt( const mpint &value );
  void swap( mpint &left, mpint &right );

}

#endif