/* ---------------------------------------------------------------------
 *
 * Copyright (C) 2018 - 2020 by the deal.II authors
 *
 * This file is part of the deal.II library.
 *
 * The deal.II library is free software; you can use it, redistribute
 * it, and/or modify it under the terms of the GNU Lesser General
 * Public License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * The full text of the license can be found in the file LICENSE.md at
 * the top level directory of deal.II.
 *
 * ---------------------------------------------------------------------
 *
 * Authors: Luca Heltai, Giovanni Alzetta,
 * International School for Advanced Studies, Trieste, 2018
 */

// @sect3{Include files}  其中大部分已经在其他地方介绍过了，我们只对新的部分进行评论。

#include <deal.II/base/logstream.h>
#include <deal.II/base/utilities.h>
#include <deal.II/base/timer.h>

// 参数接受器类是本教程程序的第一个新颖之处：一般来说，参数文件是用来在运行时引导程序的执行。虽然即使是简单的方法也能节省编译时间，因为同一个可执行文件可以用不同的参数设置来运行，但要同时处理数百个参数，同时保持不同程序之间的兼容性，会变得很困难。这就是ParameterAcceptor类证明有用的地方。

// 该类用于定义一个公共接口，供那些希望使用一个全局ParameterHandler来处理参数的类使用。该类提供了一个静态的ParameterHandler成员，即 ParameterAcceptor::prm, ，并实现了 "命令设计模式"（例如，见E. Gamma, R. Helm, R. Johnson, J. Vlissides, 设计模式。Elements of Reusable Object-Oriented Software, Addison-Wesley Professional, 1994. https:goo.gl/NYByc）。)

// ParameterAcceptor提供了一个全局订阅机制。每当一个从ParameterAcceptor派生出来的类的对象被构造出来时，一个指向该派生型对象的指针就会被注册，同时在参数文件中还有一个部分条目。这种注册表在调用单一函数 ParameterAcceptor::initialize("file.prm") 时被遍历，该函数反过来确保所有存储在全局注册表中的类都声明它们将使用的参数，在声明了这些参数后，它读取`file.prm`的内容来解析实际参数。

// 如果你为你想在代码中使用的每个参数调用方法 ParameterHandler::add_parameter ，你就不需要做其他事情了。如果你使用的是一个已经存在的类，它提供了`declare_parameters`和`parse_parameters`这两个函数，你仍然可以使用ParameterAcceptor，方法是将现有的类封装成ParameterAcceptorProxy类。

// 在这个例子中，我们将同时使用这两种策略，为deal.II类使用ParameterAcceptorProxy，并直接从ParameterAcceptor派生出我们自己的参数类。

#include <deal.II/base/parameter_acceptor.h>

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

// 另一个新的包含文件是包含 GridTools::Cache 类的文件。deal.II的结构和许多现代数值库一样，是按照有向无环图（DAG）组织的。DAG是一个具有拓扑顺序的有向图：每个节点在结构上代表一个对象，并通过一条（或多条）有向边与非根节点相连，从父节点到子节点。这种结构最重要的例子是Triangulation及其 Triangulation::cell_iterator 结构。从Triangulation（主节点），我们可以访问每个单元（三角形的子节点）。从单元本身，我们可以访问该单元的所有顶点。在这个简单的例子中，DAG结构可以表示为三种节点类型（三角结构、单元格迭代器和顶点），由从三角结构到单元格迭代器，以及从单元格迭代器到顶点的定向边连接。这有几个优点，但它本质上造成了 "不对称"，使某些操作变得很快，而它们的逆向操作却很慢：寻找一个单元的顶点的计算成本很低，可以通过简单地遍历DAG来完成，而寻找所有共享一个顶点的单元则需要进行非难事的计算，除非添加一个新的DAG数据结构来表示逆向搜索。

// 由于在有限元代码中通常不需要逆向操作，所以在GridTools中实现了这些操作，而没有使用与Triangulation相关的额外数据结构，这将使它们的速度更快。例如，这样的数据结构是一个从三角结构的顶点到所有共享这些顶点的单元的映射，这将减少回答前面问题所需的计算。

// 有些方法，例如 GridTools::find_active_cell_around_point, 大量使用了这些非标准操作。如果你需要多次调用这些方法，那么将这些数据结构存储在某个地方就变得很方便。  GridTools::Cache 正是这样做的，它让你可以访问以前计算过的对象，或者在飞行中计算它们（然后将它们存储在类中供以后使用），并确保每当三角测量被更新时，相关的数据结构也被重新计算。

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

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

// 在这个例子中，我们将使用一个参考域来描述一个嵌入的三角形，通过一个有限元矢量场进行变形。

// 接下来的两个包含文件包含了在这些情况下可以使用的两个类的定义。MappingQEulerian允许人们通过一个*位移*场来描述一个域，基于FESystem[FE_Q(p)^spacedim]有限元空间。第二种是比较通用的，允许你使用任意的矢量有限元空间，只要它们能提供*连续*的
//对你的领域的描述。在这种情况下，这种描述是通过实际的*变形*场，而不是*位移*场来完成的。

// 使用哪一个取决于用户想如何指定参考域，和/或实际配置。我们将提供这两种选择，并在本教程程序的结果部分做一些实验。

#include <deal.II/fe/mapping_q_eulerian.h>
#include <deal.II/fe/mapping_fe_field.h>

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

// 被解析的函数类是另一个新条目。它允许人们创建一个Function对象，从参数文件中的一个字符串开始，它被解析成一个对象，你可以在deal.II接受Function的任何地方使用（例如，用于插值、边界条件等）。

#include <deal.II/base/parsed_function.h>

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

// 这是本教程程序的最后一个新条目。NonMatching命名空间包含了一些方法，这些方法在对非匹配网格或与底层网格不对齐的曲线进行计算时很有用。

// 我们将在后面的 "setup_coupling "方法中详细讨论其用途。

#include <deal.II/non_matching/coupling.h>

#include <deal.II/lac/affine_constraints.h>
#include <deal.II/lac/sparse_matrix.h>
#include <deal.II/lac/vector.h>
#include <deal.II/lac/sparse_direct.h>
#include <deal.II/lac/solver_cg.h>
#include <deal.II/lac/precondition.h>
#include <deal.II/lac/linear_operator.h>
#include <deal.II/lac/linear_operator_tools.h>

#include <iostream>
#include <fstream>

namespace Step60
{
  using namespace dealii;
// @sect3{DistributedLagrangeProblem}

// 在DistributedLagrangeProblem中，我们需要两个参数来描述域 $\Gamma$ （`dim`）和域 $\Omega$ （`spacedim`）的尺寸。

// 这些参数将用于初始化一个Triangulation<dim,spacedim>（用于 $\Gamma$  ）和一个Triangulation<spacedim,spacedim>（用于 $\Omega$  ）。

// 与其他教程程序相比，一个新奇之处在于大量使用了  std::unique_ptr.  这些指针的行为与经典指针一样，其优点是可以进行自动的内部管理：一旦unique_ptr超出范围，所包含的对象就会被自动销毁，即使它是在一个容器内或者有一个异常。此外，它不允许有重复的指针，这可以防止所有权问题。我们这样做，是因为我们希望能够 i) 构建问题，ii) 读取参数，iii) 根据参数文件中指定的内容初始化所有对象。

// 我们在派生于ParameterAcceptor的内部类`Parameters`中构建我们问题的参数。`DistributedLagrangeProblem`类需要一个对`Parameters`对象的常量引用，因此不可能从DistributedLagrangeProblem类本身修改参数。

// 我们可以先初始化参数，然后将参数传递给DistributedLagrangeProblem，假设所有条目都设置为所需的值，但这有两个缺点。

// - 我们不应该对用户如何初始化一个不受我们直接控制的类做出假设。如果用户未能初始化该类，我们应该注意到并抛出一个异常。

// - 当我们构造参数时，并不是所有需要从参数文件中读取参数的对象都可以使用；对于复杂的程序，有多种物理现象，或者我们在一些外部类中重复使用现有的代码，往往是这种情况。我们通过将一些 "复杂 "的对象，如ParsedFunction对象，保留在`DistributedLagrangeProblem`内而不是`Parameters`内来模拟这种情况。

// 这里我们假设在构建时，构建我们问题的类还不能使用。解析参数文件是确保我们有所有的成分来建立我们的类，我们的设计是，如果解析失败，或者没有被执行，运行就会被中止。

  template <int dim, int spacedim = dim>
  class DistributedLagrangeProblem
  {
  public:

// `Parameters`类是由ParameterAcceptor派生的。这使得我们可以在其构造函数中使用 ParameterAcceptor::add_parameter() 方法。

// 这个函数的成员都是非常量的，但是`DistributedLagrangeProblem`类需要一个对`Parameters`对象的常量引用：这确保参数不会从`DistributedLagrangeProblem`类中被修改。

    class Parameters : public ParameterAcceptor
    {
    public:
      Parameters();

// 现在描述的参数都可以通过参数文件在外部设置：如果运行可执行文件时没有参数文件，程序将创建一个 "参数.prm "文件，并在这里定义默认值，然后中止，让用户有机会修改参数.prm文件。

// 嵌入网格的初始细化，对应于域 $\Omega$  。

      unsigned int initial_refinement = 4;

// 嵌入网格 $\Omega$ 和嵌入网格 $\Gamma$ 之间的交互是通过 $C$ 的计算来处理的，这涉及到 $\Omega$ 的所有单元与 $\Gamma$ 的部分重叠：这些单元的更高细化可能提高我们计算的质量。出于这个原因，我们定义了`delta_refinement'：如果它大于零，那么我们将空间网格中包含嵌入网格顶点的每个单元及其邻居标记出来，执行细化，并重复这个过程`delta_refinement'的次数。

      unsigned int delta_refinement = 3;

// 开始细化嵌入网格，对应于域  $\Gamma$  。

      unsigned int initial_embedded_refinement = 8;

// 边界id列表，我们在其中施加同质Dirichlet边界条件。在其余的边界id上（如果有的话），我们施加同质的诺伊曼边界条件。作为一个默认问题，我们在 $\partial \Omega$ 上设置了零迪里希特边界条件。
      std::list<types::boundary_id> homogeneous_dirichlet_ids{0, 1, 2, 3};

// 嵌入空间的有限元素程度。  $V_h(\Omega)$
      unsigned int embedding_space_finite_element_degree = 1;

// 嵌入空间的有限元素度。  $Q_h(\Gamma)$
      unsigned int embedded_space_finite_element_degree = 1;

// 用于描述嵌入域变形的空间的有限元度

      unsigned int embedded_configuration_finite_element_degree = 1;

// 用于积分耦合的正交公式的阶数

      unsigned int coupling_quadrature_order = 3;

// 如果设置为 "true"，则嵌入式配置函数被解释为位移函数。

      bool use_displacement = false;

// 在输出中使用的粗略程度

      unsigned int verbosity_level = 10;

// 用来记录我们是否被初始化的一个标志。

      bool initialized = false;
    };

    DistributedLagrangeProblem(const Parameters &parameters);

// 分布式拉格朗日问题的入口点

    void run();

  private:

// 包含实际参数的对象

    const Parameters &parameters;

// 下面的函数与其他所有的教程程序相似，不同的是，我们现在需要为两个不同系列的对象设置东西，即与*嵌入*网格有关的对象，以及与*嵌入*有关的对象。

    void setup_grids_and_dofs();

    void setup_embedding_dofs();

    void setup_embedded_dofs();

// 这里唯一的非常规函数是`setup_coupling()`方法，用于生成耦合矩阵的稀疏性模式  $C$  。

    void setup_coupling();

    void assemble_system();

    void solve();

    void output_results();

// 首先我们收集所有与嵌入空间几何学有关的对象

    std::unique_ptr<Triangulation<spacedim>> space_grid;
    std::unique_ptr<GridTools::Cache<spacedim, spacedim>>
                                             space_grid_tools_cache;
    std::unique_ptr<FiniteElement<spacedim>> space_fe;
    std::unique_ptr<DoFHandler<spacedim>>    space_dh;

// 然后是与嵌入网格有关的，与拉格朗日乘数`lambda`有关的DoFHandler

    std::unique_ptr<Triangulation<dim, spacedim>> embedded_grid;
    std::unique_ptr<FiniteElement<dim, spacedim>> embedded_fe;
    std::unique_ptr<DoFHandler<dim, spacedim>>    embedded_dh;

// 最后，需要对嵌入的三角形进行*变形的所有内容

    std::unique_ptr<FiniteElement<dim, spacedim>> embedded_configuration_fe;
    std::unique_ptr<DoFHandler<dim, spacedim>>    embedded_configuration_dh;
    Vector<double>                                embedded_configuration;

// ParameterAcceptorProxy类是一个 "透明 "的包装器，它同时来自ParameterAcceptor和作为其模板参数传递的类型。在构造时，参数被分成两部分：第一个参数是一个 std::string, ，转发给ParameterAcceptor类，并包含应该用于该类的部分名称，而所有其余的参数都转发给模板类型的构造器，在本例中，转发给 Functions::ParsedFunction 构造器。

// 这个类允许你结合ParameterAcceptor注册机制使用现有的类，只要这些类有`declare_parameters()`和`parse_parameters()`成员。

// 这里就是这种情况，使得利用 Functions::ParsedFunction 类变得相当容易：而不是要求用户在代码中为RHS、边界函数等创建新的Function对象。在这里，我们允许用户使用deal.II接口到muParser（http:muparser.beltoforion.de），在这里，函数的指定不是在编译时完成的，而是在运行时完成的，使用一个字符串被解析成一个实际的Function对象。

// 在这种情况下，`embedded_configuration_function`是一个向量值的Function，根据`parameters.use_displacement`的布尔值，可以被解释为*变形*或*位移*。分量的数量将在后面的结构中指定。

    ParameterAcceptorProxy<Functions::ParsedFunction<spacedim>>
      embedded_configuration_function;

    std::unique_ptr<Mapping<dim, spacedim>> embedded_mapping;

// 我们做同样的事情来指定函数的值  $g$  ，这就是我们希望我们的解决方案在嵌入空间中的值。在这种情况下，该函数是一个标量函数。

    ParameterAcceptorProxy<Functions::ParsedFunction<spacedim>>
      embedded_value_function;

// 与我们对 Functions::ParsedFunction 类所做的类似，我们对ReductionControl类重复同样的做法，允许我们为我们以后使用的Schur补码迭代求解器指定所有可能的停止标准。

    ParameterAcceptorProxy<ReductionControl> schur_solver_control;

// 接下来我们收集所有我们需要的SparsityPattern, SparseMatrix, 和Vector对象

    SparsityPattern stiffness_sparsity;
    SparsityPattern coupling_sparsity;

    SparseMatrix<double> stiffness_matrix;
    SparseMatrix<double> coupling_matrix;

    AffineConstraints<double> constraints;

    Vector<double> solution;
    Vector<double> rhs;

    Vector<double> lambda;
    Vector<double> embedded_rhs;
    Vector<double> embedded_value;

// TimerOutput类用来提供一些关于我们程序性能的统计数据。

    TimerOutput monitor;
  };
// @sect3{DistributedLagrangeProblem::Parameters}

// 在构造时，我们也初始化ParameterAcceptor类，在解析参数文件时，我们希望问题使用的部分名称。

// 参数文件可以被组织成section/subsection/etc.：这样做的好处是，当共享同一section/subsection/etc.时，定义的对象可以共享参数。ParameterAcceptor允许使用Unix约定的路径来指定部分名称。如果部分名称以斜线（"/"）开头，那么该部分将被解释为*绝对路径*，ParameterAcceptor为路径中的每个目录输入一个小节，使用它遇到的最后一个名称作为当前类的着陆小节。

// 例如，如果你使用`ParameterAcceptor("/first/second/third/My Class")`构建你的类，参数将被组织成如下样子。

//
// @code
//  # Example parameter file
//  subsection first
//    subsection second
//      subsection third
//        subsection My Class
//         ... # all the parameters
//        end
//      end
//    end
//  end
//  @endcode

// 在内部，存储在ParameterAcceptor中的*当前路径现在被认为是"/first/second/third/"，也就是说，当你指定一个绝对路径时，ParameterAcceptor将*当前的部分改为当前路径，即改为直到*最后一个"/"的部分名称的路径。

// 你现在可以使用相对路径（例如：`ParameterAcceptor("My Other Class")`）而不是绝对路径（例如：`ParameterAcceptor("/first/second/third/My Other Class")`）来构造从ParameterAcceptor派生的另一个类，获得。
// @code
//  # Example parameter file
//  subsection first
//    subsection second
//      subsection third
//        subsection My Class
//          ... # all the parameters
//        end
//        subsection My Other Class
//          ... # all the parameters of MyOtherClass
//        end
//      end
//    end
//  end
//  @endcode

// 如果部分名称*以斜线结尾，那么后续的类将把它解释为完整的路径：例如，与上面类似，如果我们有两个类，一个用`ParameterAcceptor("/first/second/third/My Class/")`初始化，另一个用`ParameterAcceptor("My Other Class")`，那么得到的参数文件将看起来像。

//
// @code
//  # Example parameter file
//  subsection first
//    subsection second
//      subsection third
//        subsection My Class
//          ... # all the parameters of MyClass
//          ... # notice My Class subsection does not end here
//          subsection My Other Class
//            ... # all the parameters of MyOtherClass
//          end # of subsection My Other Class
//        end # of subsection My Class
//      end
//    end
//  end
//  @endcode

// 我们将利用这一点，使我们的`Parameters`成为所有后续构建的类的*父类。由于大多数其他的类都是`DistributedLagrangeProblem`的成员，这就允许，例如，为两个不同的维度构造两个`DistributedLagrangeProblem`，而不会在这两个问题的参数上产生冲突。

  template <int dim, int spacedim>
  DistributedLagrangeProblem<dim, spacedim>::Parameters::Parameters()
    : ParameterAcceptor("/Distributed Lagrange<" +
                        Utilities::int_to_string(dim) + "," +
                        Utilities::int_to_string(spacedim) + ">/")
  {

//  ParameterAcceptor::add_parameter() 函数做了几件事。

// - 在构造时向ParameterAcceptor输入指定的分段

// - 调用 ParameterAcceptor::prm.add_parameter() 函数

// - 调用你可能附加到 ParameterAcceptor::declare_parameters_call_back 的任何信号。

// --离开分节

// 反过来， ParameterAcceptor::prm.add_parameter  。

// - 在参数处理程序中为给定的变量声明一个条目。

// - 取得该变量的当前值

// - 将其转换为一个字符串，作为参数文件的默认值使用

// - 在 ParameterAcceptor::prm 中附加一个*动作，当文件被解析时，或者当一个条目被设置时，它会更新传递给`add_parameter()`的变量的值，将其设置为输入文件中指定的值（当然，是在输入文件被解析并将文本表示转换为变量的类型后）。

    add_parameter("Initial embedding space refinement", initial_refinement);

    add_parameter("Initial embedded space refinement",
                  initial_embedded_refinement);

    add_parameter("Local refinements steps near embedded domain",
                  delta_refinement);

    add_parameter("Homogeneous Dirichlet boundary ids",
                  homogeneous_dirichlet_ids);

    add_parameter("Use displacement in embedded interface", use_displacement);

    add_parameter("Embedding space finite element degree",
                  embedding_space_finite_element_degree);

    add_parameter("Embedded configuration finite element degree",
                  embedded_configuration_finite_element_degree);

    add_parameter("Coupling quadrature order", coupling_quadrature_order);

    add_parameter("Verbosity level", verbosity_level);

// 一旦参数文件被解析，那么参数就可以使用了。将内部变量`initialized`设置为true。

    parse_parameters_call_back.connect([&]() -> void { initialized = true; });
  }

// 构造函数是非常标准的，除了前面解释的`ParameterAcceptorProxy`对象之外。

  template <int dim, int spacedim>
  DistributedLagrangeProblem<dim, spacedim>::DistributedLagrangeProblem(
    const Parameters &parameters)
    : parameters(parameters)
    , embedded_configuration_function("Embedded configuration", spacedim)
    , embedded_value_function("Embedded value")
    , schur_solver_control("Schur solver control")
    , monitor(std::cout, TimerOutput::summary, TimerOutput::cpu_and_wall_times)
  {

// 下面是一个为使用ParameterAcceptorProxy构建的ParameterAcceptor类设置默认值的方法。

// 在这种情况下，我们将嵌入式网格的默认变形设置为半径为 $R$ 、中心为 $(Cx, Cy)$ 的圆，我们将embedded_value_function的默认值设置为常数，并为SolverControl对象指定一些合理的值。

// 嵌入 $\Gamma$ 是最基本的：从 $C_{\alpha j}$ 的定义可以看出，如果 $\Gamma \not\subseteq \Omega$ ，矩阵 $C$ 的某些行将是零。这将是一个问题，因为舒尔补码法要求 $C$ 具有全列秩。

    embedded_configuration_function.declare_parameters_call_back.connect(
      []() -> void {
        ParameterAcceptor::prm.set("Function constants", "R=.3, Cx=.4, Cy=.4");

        ParameterAcceptor::prm.set("Function expression",
                                   "R*cos(2*pi*x)+Cx; R*sin(2*pi*x)+Cy");
      });

    embedded_value_function.declare_parameters_call_back.connect(
      []() -> void { ParameterAcceptor::prm.set("Function expression", "1"); });

    schur_solver_control.declare_parameters_call_back.connect([]() -> void {
      ParameterAcceptor::prm.set("Max steps", "1000");
      ParameterAcceptor::prm.set("Reduction", "1.e-12");
      ParameterAcceptor::prm.set("Tolerance", "1.e-12");
    });
  }
// @sect3{Set up}

//函数 `DistributedLagrangeProblem::setup_grids_and_dofs()` 是用来设置有限元空间的。注意 `std::make_unique` 是如何用来创建包裹在 `std::unique_ptr` 对象里面的对象的。

  template <int dim, int spacedim>
  void DistributedLagrangeProblem<dim, spacedim>::setup_grids_and_dofs()
  {
    TimerOutput::Scope timer_section(monitor, "Setup grids and dofs");

// 初始化  $\Omega$  : 构建三角形，并将其包装成一个  `std::unique_ptr`  对象

    space_grid = std::make_unique<Triangulation<spacedim>>();

// 接下来，我们使用 GridGenerator::hyper_cube(). 实际创建三角形，最后一个参数设置为true：这激活了着色（即为边界的不同部分分配不同的边界指标），我们用它来分配Dirichlet和Neumann条件。

    GridGenerator::hyper_cube(*space_grid, 0, 1, true);

// 一旦我们构建了一个三角形，我们就根据参数文件中的规格对其进行全局细化，并用它构建一个 GridTools::Cache 。

    space_grid->refine_global(parameters.initial_refinement);
    space_grid_tools_cache =
      std::make_unique<GridTools::Cache<spacedim, spacedim>>(*space_grid);

// 对嵌入式网格也是这样做的。由于嵌入式网格是变形的，我们首先需要设置变形映射。我们在下面几行中这样做。

    embedded_grid = std::make_unique<Triangulation<dim, spacedim>>();
    GridGenerator::hyper_cube(*embedded_grid);
    embedded_grid->refine_global(parameters.initial_embedded_refinement);

    embedded_configuration_fe = std::make_unique<FESystem<dim, spacedim>>(
      FE_Q<dim, spacedim>(
        parameters.embedded_configuration_finite_element_degree),
      spacedim);

    embedded_configuration_dh =
      std::make_unique<DoFHandler<dim, spacedim>>(*embedded_grid);

    embedded_configuration_dh->distribute_dofs(*embedded_configuration_fe);
    embedded_configuration.reinit(embedded_configuration_dh->n_dofs());

// 一旦我们为变形定义了一个有限维度的空间，我们就对参数文件中定义的 "嵌入_配置_函数 "进行插值。

    VectorTools::interpolate(*embedded_configuration_dh,
                             embedded_configuration_function,
                             embedded_configuration);

// 现在我们可以根据用户在参数文件中指定的内容来解释它：作为位移，在这种情况下，我们构建一个映射，将我们的配置有限元空间的每个支持点的位置在相应的配置矢量上*移开指定的数量，或者作为赦免位置。

// 在第一种情况下，MappingQEulerian类提供其服务，而在第二种情况下，我们将使用MappingFEField类。它们实际上是非常相似的。MappingQEulerian只适用于FE_Q有限元空间系统，其中位移矢量存储在FESystem的第一个`spacedim`分量中，并且在构造时作为参数给出的度数必须与第一个`spacedim`分量的度数一致。

// MappingFEField类稍显一般，因为它允许你在构造近似值时选择任意的FiniteElement类型。当然，根据你选择的FiniteElement的类型，一些选择可能（也可能没有）意义。MappingFEField实现了纯粹的等参量概念，例如，可以通过与FE_Bernstein有限元类结合，在deal.II中实现等参量分析代码。在这个例子中，我们将考虑到一个配置将是一个 "位移"，而另一个将是一个绝对的 "变形 "场，从而将这两者互换使用。

    if (parameters.use_displacement == true)
      embedded_mapping =
        std::make_unique<MappingQEulerian<dim, Vector<double>, spacedim>>(
          parameters.embedded_configuration_finite_element_degree,
          *embedded_configuration_dh,
          embedded_configuration);
    else
      embedded_mapping =
        std::make_unique<MappingFEField<dim, spacedim, Vector<double>>>(
          *embedded_configuration_dh, embedded_configuration);

    setup_embedded_dofs();

// 在本教程中，我们不仅对 $\Omega$ 进行全局细化，还允许根据 $\Gamma$ 的位置进行局部细化，根据`parameters.delta_refinement`的值，我们用来决定对 $\Omega$ 进行多少轮局部细化，对应于 $\Gamma$  的位置。

// 有了这个映射，现在就可以通过调用方法 DoFTools::map_dofs_to_support_points. 来查询与`embedded_dh`相关的所有支持点的位置了。

// 这个方法有两个变种。一种是不接受Mapping，另一种是接受Mapping。如果你使用第二种类型，就像我们在这种情况下所做的那样，支持点是通过指定的映射来计算的，它可以对它们进行相应操作。

// 这正是`embedded_mapping`的作用。

    std::vector<Point<spacedim>> support_points(embedded_dh->n_dofs());
    if (parameters.delta_refinement != 0)
      DoFTools::map_dofs_to_support_points(*embedded_mapping,
                                           *embedded_dh,
                                           support_points);

// 一旦我们有了嵌入有限元空间的支持点，我们就想确定嵌入空间的哪些单元包含哪些支持点，以便在有必要的地方，也就是嵌入网格的地方，获得完善嵌入网格的机会。这可以手动完成，在每个支持点上循环，然后为嵌入空间的每个单元调用方法 Mapping::transform_real_to_unit_cell ，直到我们找到一个返回单位参考单元中的点，或者可以用更智能的方式完成。

//  GridTools::find_active_cell_around_point 是一个可能的选择，它以更便宜的方式执行上述任务，首先确定嵌入三角的最接近目标点的顶点，然后只对那些共享找到的顶点的单元格调用 Mapping::transform_real_to_unit_cell 。

// 事实上，在GridTools命名空间中，有一些算法利用 GridTools::Cache 对象，可能还有KDTree对象来尽可能地加快这些操作。

// 利用最大速度的最简单方法是调用一个专门的方法， GridTools::compute_point_locations, ，该方法将在第一个点的搜索过程中存储大量有用的信息和数据结构，然后在随后的点中重复使用所有这些信息。

//  GridTools::compute_point_locations 返回一个元组，其中第一个元素是一个包含输入点的单元格向量，在这里是support_points。对于细化来说，这是我们唯一需要的信息，而这正是现在所发生的。

// 然而，当我们需要组装一个耦合矩阵时，我们还需要每个点的参考位置来评估嵌入空间的基础函数。由 GridTools::compute_point_locations 返回的元组的其他元素允许你重建，对于每个点，什么单元包含它，以及什么是给定点的参考单元的位置。由于这些信息最好被分组到单元格中，那么这就是算法返回的内容：一个元组，包含所有单元格中至少有一个点的向量，以及所有参考点的列表和它们在原始向量中的相应索引。

// 在下面的循环中，我们将忽略所有返回的对象，除了第一个，确定所有单元格至少包含一个嵌入空间的支持点。这允许一个简单的自适应细化策略：细化这些单元和它们的邻居。

// 注意，我们需要做一些理智的检查，在这个意义上，我们希望有一个嵌入网格，它在嵌入网格周围被很好地细化，但其中两个连续的支持点要么位于同一个单元，要么位于邻近的嵌入单元。

// 这只有在我们确保嵌入网格的最小单元尺寸仍然大于嵌入网格的最大单元尺寸时才有可能。由于用户可以修改细化水平，以及他们希望在嵌入网格周围进行的局部细化的数量，我们要确保所得到的网格满足我们的要求，如果不是这样，我们就用一个例外来放弃。

    for (unsigned int i = 0; i < parameters.delta_refinement; ++i)
      {
        const auto point_locations =
          GridTools::compute_point_locations(*space_grid_tools_cache,
                                             support_points);
        const auto &cells = std::get<0>(point_locations);
        for (auto &cell : cells)
          {
            cell->set_refine_flag();
            for (const auto face_no : cell->face_indices())
              if (!cell->at_boundary(face_no))
                cell->neighbor(face_no)->set_refine_flag();
          }
        space_grid->execute_coarsening_and_refinement();
      }

// 为了构造一个良好的耦合插值算子 $C$ ，对嵌入域和被嵌入域之间的网格的相对尺寸有一些限制。耦合算子 $C$ 和空间 $V$ 和 $Q$ 必须满足一个inf-sup条件，以使问题有一个解决方案。事实证明，只要空间 $V$ 和 $Q$ 之间相互兼容（例如，只要它们被选为引言中所述的空间），非匹配 $L^2$ 投影就满足这种inf-sup条件。

// 然而，*离散*的inf-sup条件也必须成立。这里没有出现复杂的情况，但事实证明，当非匹配网格的局部直径相距太远时，离散的inf-sup常数会恶化。特别是，事实证明，如果你选择一个相对于嵌入网格*细的嵌入网格，inf-sup常数的恶化程度要比你让嵌入网格更细的情况下大得多。

// 为了避免问题，在本教程中，如果用户选择的参数使嵌入网格的最大直径大于嵌入网格的最小直径，我们将抛出一个异常。

// 这个选择保证了嵌入网格的几乎每一个单元都不超过嵌入网格的两个单元，只有一些罕见的例外，这些例外在结果的inf-sup方面可以忽略不计。

    const double embedded_space_maximal_diameter =
      GridTools::maximal_cell_diameter(*embedded_grid, *embedded_mapping);
    double embedding_space_minimal_diameter =
      GridTools::minimal_cell_diameter(*space_grid);

    deallog << "Embedding minimal diameter: "
            << embedding_space_minimal_diameter
            << ", embedded maximal diameter: "
            << embedded_space_maximal_diameter << ", ratio: "
            << embedded_space_maximal_diameter /
                 embedding_space_minimal_diameter
            << std::endl;

    AssertThrow(embedded_space_maximal_diameter <
                  embedding_space_minimal_diameter,
                ExcMessage(
                  "The embedding grid is too refined (or the embedded grid "
                  "is too coarse). Adjust the parameters so that the minimal "
                  "grid size of the embedding grid is larger "
                  "than the maximal grid size of the embedded grid."));
// $\Omega$ 已经被完善，我们现在可以设置它的DoF了

    setup_embedding_dofs();
  }

// 我们现在设置 $\Omega$ 和 $\Gamma$ 的DoF：因为它们基本上是独立的（除了 $\Omega$ 的网格在 $\Gamma$ 周围更加精细），这个过程是标准的。

  template <int dim, int spacedim>
  void DistributedLagrangeProblem<dim, spacedim>::setup_embedding_dofs()
  {
    space_dh = std::make_unique<DoFHandler<spacedim>>(*space_grid);
    space_fe = std::make_unique<FE_Q<spacedim>>(
      parameters.embedding_space_finite_element_degree);
    space_dh->distribute_dofs(*space_fe);

    DoFTools::make_hanging_node_constraints(*space_dh, constraints);
    for (auto id : parameters.homogeneous_dirichlet_ids)
      {
        VectorTools::interpolate_boundary_values(
          *space_dh, id, Functions::ZeroFunction<spacedim>(), constraints);
      }
    constraints.close();

// 根据定义，刚度矩阵只涉及 $\Omega$ 的DoF。

    DynamicSparsityPattern dsp(space_dh->n_dofs(), space_dh->n_dofs());
    DoFTools::make_sparsity_pattern(*space_dh, dsp, constraints);
    stiffness_sparsity.copy_from(dsp);
    stiffness_matrix.reinit(stiffness_sparsity);
    solution.reinit(space_dh->n_dofs());
    rhs.reinit(space_dh->n_dofs());

    deallog << "Embedding dofs: " << space_dh->n_dofs() << std::endl;
  }

  template <int dim, int spacedim>
  void DistributedLagrangeProblem<dim, spacedim>::setup_embedded_dofs()
  {
    embedded_dh = std::make_unique<DoFHandler<dim, spacedim>>(*embedded_grid);
    embedded_fe = std::make_unique<FE_Q<dim, spacedim>>(
      parameters.embedded_space_finite_element_degree);
    embedded_dh->distribute_dofs(*embedded_fe);

// 根据定义，我们要解决的系统的rhs只涉及一个零向量和 $G$ ，它只用 $\Gamma$ 的DoF计算。

    lambda.reinit(embedded_dh->n_dofs());
    embedded_rhs.reinit(embedded_dh->n_dofs());
    embedded_value.reinit(embedded_dh->n_dofs());

    deallog << "Embedded dofs: " << embedded_dh->n_dofs() << std::endl;
  }

// 创建耦合稀疏模式是一个复杂的操作，但可以使用 NonMatching::create_coupling_sparsity_pattern, 轻松完成，它需要两个DoFHandler对象、耦合的正交点、一个DynamicSparsityPattern（然后需要像往常一样复制到稀疏模式中）、嵌入和嵌入三角形的组件掩码（我们留空）以及嵌入和嵌入三角形的映射关系。

  template <int dim, int spacedim>
  void DistributedLagrangeProblem<dim, spacedim>::setup_coupling()
  {
    TimerOutput::Scope timer_section(monitor, "Setup coupling");

    QGauss<dim> quad(parameters.coupling_quadrature_order);

    DynamicSparsityPattern dsp(space_dh->n_dofs(), embedded_dh->n_dofs());

    NonMatching::create_coupling_sparsity_pattern(*space_grid_tools_cache,
                                                  *space_dh,
                                                  *embedded_dh,
                                                  quad,
                                                  dsp,
                                                  AffineConstraints<double>(),
                                                  ComponentMask(),
                                                  ComponentMask(),
                                                  *embedded_mapping);
    coupling_sparsity.copy_from(dsp);
    coupling_matrix.reinit(coupling_sparsity);
  }
// @sect3{Assembly}

// 以下是创建矩阵的函数：如前所述，计算刚度矩阵和rhs是一个标准程序。

  template <int dim, int spacedim>
  void DistributedLagrangeProblem<dim, spacedim>::assemble_system()
  {
    {
      TimerOutput::Scope timer_section(monitor, "Assemble system");

//嵌入刚度矩阵  $K$  ，以及右手边  $G$  。

      MatrixTools::create_laplace_matrix(
        *space_dh,
        QGauss<spacedim>(2 * space_fe->degree + 1),
        stiffness_matrix,
        static_cast<const Function<spacedim> *>(nullptr),
        constraints);

      VectorTools::create_right_hand_side(*embedded_mapping,
                                          *embedded_dh,
                                          QGauss<dim>(2 * embedded_fe->degree +
                                                      1),
                                          embedded_value_function,
                                          embedded_rhs);
    }
    {
      TimerOutput::Scope timer_section(monitor, "Assemble coupling system");

// 为了计算耦合矩阵，我们使用 NonMatching::create_coupling_mass_matrix 工具，其工作原理与 NonMatching::create_coupling_sparsity_pattern. 类似。
      QGauss<dim> quad(parameters.coupling_quadrature_order);
      NonMatching::create_coupling_mass_matrix(*space_grid_tools_cache,
                                               *space_dh,
                                               *embedded_dh,
                                               quad,
                                               coupling_matrix,
                                               AffineConstraints<double>(),
                                               ComponentMask(),
                                               ComponentMask(),
                                               *embedded_mapping);

      VectorTools::interpolate(*embedded_mapping,
                               *embedded_dh,
                               embedded_value_function,
                               embedded_value);
    }
  }
// @sect3{Solve}

// 所有的部分都已经组装好了：我们用舒尔补数法解决这个系统

  template <int dim, int spacedim>
  void DistributedLagrangeProblem<dim, spacedim>::solve()
  {
    TimerOutput::Scope timer_section(monitor, "Solve system");

// 从创建反刚度矩阵开始

    SparseDirectUMFPACK K_inv_umfpack;
    K_inv_umfpack.initialize(stiffness_matrix);

//初始化运算符，如介绍中所述

    auto K  = linear_operator(stiffness_matrix);
    auto Ct = linear_operator(coupling_matrix);
    auto C  = transpose_operator(Ct);

    auto K_inv = linear_operator(K, K_inv_umfpack);

// 使用舒尔补数法

    auto                     S = C * K_inv * Ct;
    SolverCG<Vector<double>> solver_cg(schur_solver_control);
    auto S_inv = inverse_operator(S, solver_cg, PreconditionIdentity());

    lambda = S_inv * embedded_rhs;

    solution = K_inv * Ct * lambda;

    constraints.distribute(solution);
  }

// 下面的函数只是在两个独立的文件上生成标准结果输出，每个网格一个。

  template <int dim, int spacedim>
  void DistributedLagrangeProblem<dim, spacedim>::output_results()
  {
    TimerOutput::Scope timer_section(monitor, "Output results");

    DataOut<spacedim> embedding_out;

    std::ofstream embedding_out_file("embedding.vtu");

    embedding_out.attach_dof_handler(*space_dh);
    embedding_out.add_data_vector(solution, "solution");
    embedding_out.build_patches(
      parameters.embedding_space_finite_element_degree);
    embedding_out.write_vtu(embedding_out_file);

// 这两个输出例程之间的唯一区别是，在第二种情况下，我们想在当前配置上输出数据，而不是在参考配置上。这可以通过将实际的embedded_mapping传递给 DataOut::build_patches 函数来实现。该映射将负责在实际变形的配置上输出结果。

    DataOut<dim, spacedim> embedded_out;

    std::ofstream embedded_out_file("embedded.vtu");

    embedded_out.attach_dof_handler(*embedded_dh);
    embedded_out.add_data_vector(lambda, "lambda");
    embedded_out.add_data_vector(embedded_value, "g");
    embedded_out.build_patches(*embedded_mapping,
                               parameters.embedded_space_finite_element_degree);
    embedded_out.write_vtu(embedded_out_file);
  }

// 与所有其他教程程序类似，`run()`函数只是按照正确的顺序调用所有其他方法。没有什么特别需要注意的，只是在我们实际尝试运行我们的程序之前，我们检查是否完成了解析。

  template <int dim, int spacedim>
  void DistributedLagrangeProblem<dim, spacedim>::run()
  {
    AssertThrow(parameters.initialized, ExcNotInitialized());
    deallog.depth_console(parameters.verbosity_level);

    setup_grids_and_dofs();
    setup_coupling();
    assemble_system();
    solve();
    output_results();
  }
} // namespace Step60

int main(int argc, char **argv)
{
  try
    {
      using namespace dealii;
      using namespace Step60;

      const unsigned int dim = 1, spacedim = 2;

// 与其他教程程序中的情况不同，这里我们使用ParameterAcceptor风格的初始化，即首先构建所有对象，然后对静态方法 ParameterAcceptor::initialize 发出一次调用，以填充从ParameterAcceptor派生的类的所有参数。

// 我们检查用户是否在程序启动时指定了一个要使用的参数文件名。如果是，就尝试读取该参数文件，否则就尝试读取文件 "parameters.prm"。

// 如果指定的参数文件（隐式或显式）不存在， ParameterAcceptor::initialize 将为你创建一个，并退出程序。

      DistributedLagrangeProblem<dim, spacedim>::Parameters parameters;
      DistributedLagrangeProblem<dim, spacedim>             problem(parameters);

      std::string parameter_file;
      if (argc > 1)
        parameter_file = argv[1];
      else
        parameter_file = "parameters.prm";

      ParameterAcceptor::initialize(parameter_file, "used_parameters.prm");
      problem.run();
    }
  catch (std::exception &exc)
    {
      std::cerr << std::endl
                << std::endl
                << "----------------------------------------------------"
                << std::endl;
      std::cerr << "Exception on processing: " << std::endl
                << exc.what() << std::endl
                << "Aborting!" << std::endl
                << "----------------------------------------------------"
                << std::endl;
      return 1;
    }
  catch (...)
    {
      std::cerr << std::endl
                << std::endl
                << "----------------------------------------------------"
                << std::endl;
      std::cerr << "Unknown exception!" << std::endl
                << "Aborting!" << std::endl
                << "----------------------------------------------------"
                << std::endl;
      return 1;
    }
  return 0;
}


#ifndef CAFFE_NHWC_CONV_DW_LAYER_HPP_
#define CAFFE_NHWC_CONV_DW_LAYER_HPP_

#include <vector>
#include "caffe/blob.hpp"
#include "caffe/layer.hpp"
#include "caffe/proto/caffe.pb.h"
// we use Eigen to accelerate the DepthwiseConv2D computation
#include <Eigen/Dense>

/**
 * NOTE: 只支持通道完全分离的卷积，即:输入通道＝输出通道
 * 建议：通道数为４的倍数会加快运行速度
 */

namespace caffe {

template <typename Dtype>
class ConvolutionDepthwiseNHWCLayer : public Layer<Dtype> {
 public:
  explicit ConvolutionDepthwiseNHWCLayer(const LayerParameter& param)
      : Layer<Dtype>(param) {}
  virtual void LayerSetUp(const vector<Blob<Dtype>*>& bottom,
      const vector<Blob<Dtype>*>& top);
  virtual void Reshape(const vector<Blob<Dtype>*>& bottom,
      const vector<Blob<Dtype>*>& top);
  virtual inline int ExactNumBottomBlobs() const { return 1; }
  virtual inline int ExactNumTopBlobs() const { return 1; }
  virtual inline const char* type() const { return "ConvolutionDepthwiseNHWC"; }
 protected:
  virtual void Forward_cpu(const vector<Blob<Dtype>*>& bottom,
      const vector<Blob<Dtype>*>& top);
  virtual void Backward_cpu(const vector<Blob<Dtype>*>& top,
      const vector<bool>& propagate_down, const vector<Blob<Dtype>*>& bottom);
  virtual void Forward_gpu(const vector<Blob<Dtype>*>& bottom,
      const vector<Blob<Dtype>*>& top);
  virtual void Backward_gpu(const vector<Blob<Dtype>*>& top,
      const vector<bool>& propagate_down, const vector<Blob<Dtype>*>& bottom);

  // NOTE: it's only CPU implemention, GPU use CUDA Kernels to accelerate the computation.
  void FilterPadKernel(const Dtype* filter, Dtype* padded_filter, const int channels, const int filter_spatial_dim);
  void InputCopyKernel(const Dtype* input, Dtype* input_buffer, const int channels, const int height, const int width,
                       const int out_r, const int out_c, const int stride_h, const int stride_w, const int pad_h,
                       const int pad_w, const int kernel_h, const int kernel_w);
  void DepthwiseConv2DKernel(const Dtype* filter, const Dtype* input_buffer, Dtype* output,
                             const int channels, const int out_h, const int out_w, const int top_height,
                             const int top_width, const int filter_spatial_dim, const int padded_size);
  bool has_bias_;
  unsigned int kernel_h_;
  unsigned int kernel_w_;
  unsigned int stride_h_;
  unsigned int stride_w_;
  unsigned int pad_h_;
  unsigned int pad_w_;
  unsigned int dilation_h_;
  unsigned int dilation_w_;
  unsigned int num_output_;
  unsigned int channels_;
  // for input copy & filter pad
  Blob<Dtype> padded_filter_;
  Blob<Dtype> padded_input_buf_;
  // for bias operation
  Blob<Dtype> bias_multiplier_;
  // weight operation
  Blob<Dtype> weight_buffer_;
  Blob<Dtype> weight_multiplier_;
};

}

#endif


///////////////////////////////////////////////////////////////////////////////
// distribution::survival::models::common::importance_sampling::manager.hpp  //
//                                                                           //
//  Copyright 2009 Erwann Rogard. Distributed under the Boost                //
//  Software License, Version 1.0. (See accompanying file                    //
//  LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)         //
///////////////////////////////////////////////////////////////////////////////
#ifndef BOOST_STATISTICS_DETAIL_DISTRIBUTION_SURVIVAL_MODELS_IMPORTANCE_SAMPLING_MANAGER_HPP_ER_2009
#define BOOST_STATISTICS_DETAIL_DISTRIBUTION_SURVIVAL_MODELS_IMPORTANCE_SAMPLING_MANAGER_HPP_ER_2009
#include <algorithm>
#include <vector>
#include <string>
#include <boost/format.hpp>
#include <boost/range.hpp>
#include <boost/math/tools/precision.hpp>
#include <boost/statistics/detail/distribution/survival/models/common/importance_sampling/importance_sample.hpp>
#include <boost/statistics/detail/distribution/survival/models/common/importance_sampling/log_ratio_pdf_generate.hpp>
#include <boost/statistics/detail/distribution/survival/models/common/importance_sampling/log_ratio_pdf_statistics.hpp>

namespace boost {
namespace statistics{
namespace detail{
namespace distribution{
namespace survival {

    template<typename TagPar,typename TagLpdf,typename T = double>
	class importance_sampling_manager{
    	typedef std::string str_;
        typedef boost::format format_;
    	typedef std::vector<T> vec_val_;
        typedef importance_sampling::prepare_weights<T> pw_;
		typedef survival::log_ratio_pdf_statistics<T> stats_;

        public:

		typedef TagPar 	tag_par_;
        typedef TagLpdf tag_lpdf_;

		template<typename T1>
		importance_sampling_manager(const T1& max_log):pw(max_log){}

        template<
        	typename ItT,typename N_t,
        	typename ItD,typename It_p_lpdf,
            typename Pr,typename M,
            typename U
        >
        void refresh(
        	ItT it_targets,
            N_t n_t,
        	ItD b_d,
            ItD e_d,
            It_p_lpdf b_p_lpdf,
            It_p_lpdf e_p_lpdf,
            const Pr& prior,
            const M& model,
            U& urng
        )
        {
            vec_w.clear();
            generate_log_ratio_pdf<tag_par_,tag_lpdf_>(
                std::back_inserter(vec_w),
                prior, model,
                b_d, e_d,
        		b_p_lpdf, e_p_lpdf
            );
            this->pw(boost::begin(this->vec_w),boost::end(this->vec_w));
            survival::importance_sample<tag_par_>(
                it_targets,
                n_t,
                b_p_lpdf,
                e_p_lpdf,
                this->vec_w,
                urng
            );
        }

		template<typename T1>
		const stats_&  update_stats(const T1& eps)const{
            this->stats(
            	boost::begin(this->vec_w),
                boost::end(this->vec_w),
                eps
            );
            return this->stats;
        }

        template<typename T1>
		str_ description(const T1& eps)const{
			static const str_ str = "pws = %1%, ess = %2%, pc_lt_eps = %3%";
            format_ f(str);
            this->update_stats(eps);
            f% this->pw % this->stats.ess() % this->stats.percent_lt_eps();
            return f.str();
		}

		str_ description()const{
        	static const T eps = boost::math::tools::epsilon<T>();
        	return this->description(eps);
        }

        private:
    	vec_val_ vec_w; // importance weights
		mutable pw_ pw;
        mutable stats_ stats;

    };

}// survival
}// distribution
}// detail
}// statistics
}// boost

#endif


/*    Copyright (c) 2010-2019, Delft University of Technology
 *    All rigths reserved
 *
 *    This file is part of the Tudat. Redistribution and use in source and
 *    binary forms, with or without modification, are permitted exclusively
 *    under the terms of the Modified BSD license. You should have received
 *    a copy of the license with this file. If not, please or visit:
 *    http://tudat.tudelft.nl/LICENSE.
 */
#define BOOST_TEST_DYN_LINK
#define BOOST_TEST_MAIN

#include <boost/test/tools/floating_point_comparison.hpp>
#include <boost/test/unit_test.hpp>

#include "tudat/simulation/simulation.h"
#include "tudat/math/basic/leastSquaresEstimation.h"
#include "tudat/io/basicInputOutput.h"

namespace tudat
{

namespace unit_tests
{

using namespace tudat::spice_interface;
using namespace tudat::simulation_setup;
using namespace tudat::gravitation;
using namespace tudat::basic_astrodynamics;
using namespace tudat::simulation_setup;
using namespace tudat::propagators;
using namespace tudat::numerical_integrators;

BOOST_AUTO_TEST_SUITE( test_mutual_spherical_harmonic_gravity )

double computeSemiMajorAxisRateDueToTideRaisedOnPlanet(
        const double k2QRatio, const double satelliteWrtPlanetMassRatio, const double referenceRadiusWrtSemiMajorAxisRatio,
        const double semiMajorAxis, const double meanMotion )
{
    return  3.0 * k2QRatio * satelliteWrtPlanetMassRatio * std::pow( referenceRadiusWrtSemiMajorAxisRatio, 5.0 ) *
            semiMajorAxis * meanMotion;
}

double computeEccentricityRateDueToTideRaisedOnPlanet(
        const double k2QRatio, const double satelliteWrtPlanetMassRatio, const double referenceRadiusWrtSemiMajorAxisRatio,
        const double eccentricity, const double meanMotion )
{
    return 57.0 / 8.0 * k2QRatio * satelliteWrtPlanetMassRatio * std::pow( referenceRadiusWrtSemiMajorAxisRatio, 5.0 ) *
            eccentricity * meanMotion;
}

std::pair< double, double > computeKeplerElementRatesDueToDissipation(
        const SystemOfBodies& bodies, const std::string& satelliteToPropagate, const bool usePlanetDissipation,
        const double k2LoveNumber, const double tidalTimeLag, const double initialTime, const double finalTime,
        double& meanMotion, Eigen::Vector6d& intialKeplerElements  )
{
    // Define propagator settings variables.
    SelectedAccelerationMap accelerationMap;
    std::vector< std::string > bodiesToPropagate;
    std::vector< std::string > centralBodies;

    bodiesToPropagate.push_back( satelliteToPropagate );
    centralBodies.push_back( "Jupiter" );

    // Define propagation settings.
    const double fixedStepSize = 450.0;
    std::shared_ptr< IntegratorSettings< > > integratorSettings =
            std::make_shared< RungeKuttaVariableStepSizeSettings< > >
            ( 0.0, fixedStepSize,
              RungeKuttaCoefficients::rungeKuttaFehlberg78, fixedStepSize, fixedStepSize, 1.0, 1.0);

    std::map< double, Eigen::VectorXd > integrationResultWithDissipation;
    std::map< double, Eigen::VectorXd > integrationResultWithDissipationKepler;

    std::map< double, double > semiMajorAxes, eccentricities;
    {
        std::map< std::string, std::vector< std::shared_ptr< AccelerationSettings > > > accelerationsOfIo;
        accelerationsOfIo[ "Jupiter" ].push_back( std::make_shared< AccelerationSettings >(
                                                      basic_astrodynamics::point_mass_gravity ) );
        accelerationsOfIo[ "Jupiter" ].push_back( std::make_shared< DirectTidalDissipationAccelerationSettings >(
                                                      k2LoveNumber, tidalTimeLag, false, usePlanetDissipation ) );
        accelerationMap[ satelliteToPropagate ] = accelerationsOfIo;
        basic_astrodynamics::AccelerationMap accelerationModelMap = createAccelerationModelsMap(
                    bodies, accelerationMap, bodiesToPropagate, centralBodies );

        // Save dependent variables
        std::vector< std::shared_ptr< SingleDependentVariableSaveSettings > > dependentVariablesToSave;
        if( usePlanetDissipation )
        {
            dependentVariablesToSave.push_back(
                        std::make_shared< SingleAccelerationDependentVariableSaveSettings >(
                            direct_tidal_dissipation_in_central_body_acceleration, satelliteToPropagate, "Jupiter" ) );
        }
        else
        {
            dependentVariablesToSave.push_back(
                        std::make_shared< SingleAccelerationDependentVariableSaveSettings >(
                            direct_tidal_dissipation_in_orbiting_body_acceleration, satelliteToPropagate, "Jupiter" ) );
        }

        std::shared_ptr< DependentVariableSaveSettings > dependentVariableSaveSettings =
                std::make_shared< DependentVariableSaveSettings >( dependentVariablesToSave, 0 ) ;

        std::shared_ptr< TranslationalStatePropagatorSettings< double > > propagatorSettings =
                std::make_shared< TranslationalStatePropagatorSettings< double > >
                ( centralBodies, accelerationModelMap, bodiesToPropagate, getInitialStatesOfBodies(
                      bodiesToPropagate, centralBodies, bodies, initialTime ), finalTime, cowell,
                  dependentVariableSaveSettings );

        ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        ///////////////////////             PROPAGATE ORBIT            ////////////////////////////////////////////////////////
        ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        // Create simulation object and propagate dynamics.
        SingleArcDynamicsSimulator< > dynamicsSimulator(
                    bodies, integratorSettings, propagatorSettings, true, false, false );
        integrationResultWithDissipation = dynamicsSimulator.getEquationsOfMotionNumericalSolution( );

        for( std::map< double, Eigen::VectorXd >::const_iterator mapIterator = integrationResultWithDissipation.begin( );
             mapIterator != integrationResultWithDissipation.end( ); mapIterator++ )
        {
            integrationResultWithDissipationKepler[ mapIterator->first ] =
                    orbital_element_conversions::convertCartesianToKeplerianElements< double >(
                        mapIterator->second, getBodyGravitationalParameter( "Jupiter" ) +
                        getBodyGravitationalParameter( satelliteToPropagate ) );
            semiMajorAxes[ mapIterator->first ] = integrationResultWithDissipationKepler[ mapIterator->first ]( 0 ) -
                    integrationResultWithDissipationKepler.begin( )->second( 0 );
            eccentricities[ mapIterator->first ] = integrationResultWithDissipationKepler[ mapIterator->first ]( 1 ) -
                    integrationResultWithDissipationKepler.begin( )->second( 1 );

        }
    }

    //    input_output::writeDataMapToTextFile( integrationResultWithDissipationKepler,
    //                                          "keplerElements_"  + std::to_string( usePlanetDissipation ) +
    //                                          satelliteToPropagate + ".dat" );

    std::vector< double > semiMajorAxisFit = linear_algebra::getLeastSquaresPolynomialFit(
                semiMajorAxes, { 0, 1 } );
    std::vector< double > eccentricityFit = linear_algebra::getLeastSquaresPolynomialFit(
                eccentricities, { 0, 1 } );

    intialKeplerElements = integrationResultWithDissipationKepler.begin( )->second;
    meanMotion = basic_astrodynamics::computeKeplerMeanMotion(
                intialKeplerElements( 0 ), getBodyGravitationalParameter( "Jupiter" ) +
                getBodyGravitationalParameter( satelliteToPropagate ) );

    return std::make_pair( semiMajorAxisFit.at( 1 ), eccentricityFit.at( 1 ) );
}

BOOST_AUTO_TEST_CASE( testTidalDissipationInPlanetAndSatellite )
{
    // Load spice kernels.
    spice_interface::loadStandardSpiceKernels( );

    // Create list of bodies to create.
    std::vector< std::string > bodyNames;
    bodyNames.push_back( "Jupiter" );
    bodyNames.push_back( "Io" );
    bodyNames.push_back( "Europa" );
    bodyNames.push_back( "Ganymede" );
    //bodyNames.push_back( "Callisto" );
    bodyNames.push_back( "Sun" );

    // Specify initial time
    double initialTime = 0.0;
    double finalTime = 1.0 * physical_constants::JULIAN_YEAR;

    // Get body settings.
    BodyListSettings bodySettings =
            getDefaultBodySettings( bodyNames, initialTime - 86400.0, finalTime + 86400.0 );

    std::vector< std::string > galileanSatellites = { "Io", "Europa", "Ganymede" };

    Eigen::MatrixXd cosineCoefficients = Eigen::MatrixXd::Zero( 3, 3 );
    cosineCoefficients( 0, 0 ) = 1.0;
    Eigen::MatrixXd sineCoefficients = Eigen::MatrixXd::Zero( 3, 3 );
    bodySettings.at( "Jupiter" )->gravityFieldSettings = std::make_shared< SphericalHarmonicsGravityFieldSettings >
            ( getBodyGravitationalParameter( "Jupiter" ), getAverageRadius( "Jupiter" ),
              cosineCoefficients, sineCoefficients, "IAU_Jupiter" );
    bodySettings.at( "Jupiter" )->rotationModelSettings = std::make_shared< SimpleRotationModelSettings >(
                "ECLIPJ2000", "IAU_Jupiter", Eigen::Quaterniond( Eigen::Matrix3d::Identity( ) ),
                0.0, 2.0 * mathematical_constants::PI / ( 9.925 * 3600.0 ) );

    for( unsigned int i = 0; i < galileanSatellites.size( ); i++ )
    {
        bodySettings.at( galileanSatellites.at( i ) )->gravityFieldSettings = std::make_shared< SphericalHarmonicsGravityFieldSettings >
                ( getBodyGravitationalParameter( galileanSatellites.at( i ) ), getAverageRadius( galileanSatellites.at( i ) ),
                  cosineCoefficients, sineCoefficients, "IAU_" + galileanSatellites.at( i )  );
    }

    bodySettings.at( "Io" )->ephemerisSettings = std::make_shared< KeplerEphemerisSettings >(
                ( Eigen::Vector6d( ) << 1.0 * 421.8E6, 1.0 * 0.004, 0.0, 0.0, 0.0, 0.0 ).finished( ), 0.0,
                getBodyGravitationalParameter( "Jupiter" ) + getBodyGravitationalParameter( "Io" ), "Jupiter", "ECLIPJ2000" );
    bodySettings.at( "Europa" )->ephemerisSettings = std::make_shared< KeplerEphemerisSettings >(
                ( Eigen::Vector6d( ) << 671.1E6, 0.009, 0.0, 0.0, 0.0, 0.0 ).finished( ), 0.0,
                getBodyGravitationalParameter( "Jupiter" ) + getBodyGravitationalParameter( "Europa" ), "Jupiter", "ECLIPJ2000" );
    bodySettings.at( "Ganymede" )->ephemerisSettings = std::make_shared< KeplerEphemerisSettings >(
                ( Eigen::Vector6d( ) << 1070.400E6, 0.0013, 0.0, 0.0, 0.0, 0.0 ).finished( ), 0.0,
                getBodyGravitationalParameter( "Jupiter" ) + getBodyGravitationalParameter( "Ganymede" ), "Jupiter", "ECLIPJ2000" );
    //    bodySettings[ "Callisto" ]->ephemerisSettings = std::make_shared< KeplerEphemerisSettings >(
    //                ( Eigen::Vector6d( ) << 1882.700E6, 0.0074, 0.0, 0.0, 0.0, 0.0 ).finished( ), 0.0,
    //                getBodyGravitationalParameter( "Jupiter" ) + getBodyGravitationalParameter( "Callisto" ), "Jupiter", "ECLIPJ2000" );

    // Create bodies needed in simulation
    SystemOfBodies bodies = createSystemOfBodies( bodySettings );

    // Define propagation settings.
    double jupiterLoveNumber = 0.1;
    double jupiterTimeLag = 100.0;

    for( unsigned int i = 0; i < galileanSatellites.size( ); i++ )
    {
        Eigen::Vector6d intialKeplerElements;
        double meanMotion;
        std::pair< double, double > elementRates = computeKeplerElementRatesDueToDissipation(
                    bodies, galileanSatellites.at( i ), true, jupiterLoveNumber, jupiterTimeLag, initialTime, finalTime, meanMotion, intialKeplerElements );

        double orbitalPeriod = 2.0 * mathematical_constants::PI / meanMotion;
        double jupiterForcingTime = 9.925 * 3600.0 * orbitalPeriod / ( 2.0 * std::fabs( orbitalPeriod - 9.925 * 3600.0 ) );
        double jupiterQualityFactor = 1.0 / std::sin( 2.0 * mathematical_constants::PI * jupiterTimeLag / ( jupiterForcingTime ) );

        double theoreticalSemiMajorAxisRateFromJupiterTide = computeSemiMajorAxisRateDueToTideRaisedOnPlanet(
                    jupiterLoveNumber / jupiterQualityFactor, getBodyGravitationalParameter( galileanSatellites.at( i ) ) /
                    getBodyGravitationalParameter( "Jupiter" ), getAverageRadius( "Jupiter" ) / intialKeplerElements( 0 ),
                    intialKeplerElements( 0 ), meanMotion );
        double theoreticaEccentricityRateFromJupiterTide = computeEccentricityRateDueToTideRaisedOnPlanet(
                    jupiterLoveNumber / jupiterQualityFactor, getBodyGravitationalParameter( galileanSatellites.at( i ) ) /
                    getBodyGravitationalParameter( "Jupiter" ), getAverageRadius( "Jupiter" ) / intialKeplerElements( 0 ),
                    intialKeplerElements( 1 ), meanMotion );

        BOOST_CHECK_CLOSE_FRACTION( elementRates.first, theoreticalSemiMajorAxisRateFromJupiterTide, 2.0E-3 );
        BOOST_CHECK_CLOSE_FRACTION( elementRates.second, theoreticaEccentricityRateFromJupiterTide, 1.0E-1 );
    }

    std::map< double, Eigen::VectorXd > integrationResultWithDissipationInIo;
    std::map< double, Eigen::VectorXd > integrationResultWithDissipationInIoKepler;

    double satelliteLoveNumber = 1.0;
    double satelliteTimeLag = 1000.0;

    for( unsigned int i = 0; i < galileanSatellites.size( ); i++ )
    {
        Eigen::Vector6d intialKeplerElements;
        double meanMotion;
        std::pair< double, double > elementRates = computeKeplerElementRatesDueToDissipation(
                    bodies, galileanSatellites.at( i ), false, satelliteLoveNumber, satelliteTimeLag, initialTime, finalTime, meanMotion, intialKeplerElements );

        double orbitalPeriod = 2.0 * mathematical_constants::PI / meanMotion;
        double satelliteQualityFactor = 1.0 / std::sin( 2.0 * mathematical_constants::PI * satelliteTimeLag / orbitalPeriod );

        double theoreticalSemiMajorAxisRateFromIoTide =  -21.0 * satelliteLoveNumber / satelliteQualityFactor * getBodyGravitationalParameter( "Jupiter" ) /
                getBodyGravitationalParameter( galileanSatellites.at( i ) ) * std::pow( getAverageRadius( galileanSatellites.at( i ) ) / intialKeplerElements( 0 ), 5.0 ) *
                intialKeplerElements( 0 ) * meanMotion * intialKeplerElements( 1 ) * intialKeplerElements( 1 );
        double theoreticaEccentricityRateFromIoTide =  - 21.0 / 2.0 * satelliteLoveNumber / satelliteQualityFactor * getBodyGravitationalParameter( "Jupiter" ) /
                getBodyGravitationalParameter( galileanSatellites.at( i ) ) * std::pow( getAverageRadius( galileanSatellites.at( i ) ) / intialKeplerElements( 0 ), 5.0 ) *
                intialKeplerElements( 1 ) * meanMotion;

        // Increase tolerance for more distance moons
        double toleranceMultiplier = 1.0;
        if( i == 1 )
        {
            toleranceMultiplier = 5.0;
        }
        else if( i == 2 )
        {
            toleranceMultiplier = 20.0;
        }

        BOOST_CHECK_CLOSE_FRACTION( elementRates.first, theoreticalSemiMajorAxisRateFromIoTide, toleranceMultiplier * 1.0E-3 );
        BOOST_CHECK_CLOSE_FRACTION( elementRates.second, theoreticaEccentricityRateFromIoTide, toleranceMultiplier * 1.0E-3 );

        // Artificially increase time lag to make effect observable over integration tiem of 1 year.
        satelliteTimeLag *= 5.0;
    }

}

BOOST_AUTO_TEST_SUITE_END( )

}

}


#include <base/cgal_typedefs.h>
#include <IO/fileIO.h>
#include <processing/pointSetProcessing.h>
#include <processing/meshProcessing.h>

#include <CGAL/Polygon_mesh_processing/connected_components.h>

#include <CGAL/Surface_mesh.h>
#include <CGAL/Surface_mesh_approximation/approximate_triangle_mesh.h>
#include <CGAL/Polygon_mesh_processing/orient_polygon_soup.h>
#include <CGAL/Polygon_mesh_processing/polygon_soup_to_polygon_mesh.h>
#include <CGAL/Polygon_mesh_processing/orientation.h>
#include <CGAL/polygon_mesh_processing.h>

namespace VSA = CGAL::Surface_mesh_approximation;
namespace PMP = CGAL::Polygon_mesh_processing;
using namespace std;

////////////////////////////////////////////////////////////
////////////////////// Triangulation ///////////////////////
////////////////////////////////////////////////////////////
void makeDelaunayWithInfo(dataHolder& data)
{
    auto start = chrono::high_resolution_clock::now();

    cout << "\nDelaunay triangulation..." << endl;
    cout << "\t-" <<  data.points.size()  << " input points" << endl;

    assert(data.points.size() == data.infos.size());

    // make the triangulation
    data.Dt.insert( boost::make_zip_iterator(boost::make_tuple(data.points.begin(), data.infos.begin() )),
             boost::make_zip_iterator(boost::make_tuple(data.points.end(), data.infos.end() ) )  );
//    Delaunay Dt( boost::make_zip_iterator(boost::make_tuple(data.points.begin(), data.infos.begin() )),
//    boost::make_zip_iterator(boost::make_tuple(data.points.end(), data.infos.end() ) )  );
//    data.Dt = Dt;

//    if(options.insert_sensor){
//        for(int i = 0; i < data.points.size(); i++)
//            data.Dt.insert(data.infos[i].sensor_positions[0]);
//    }

    auto stop = chrono::high_resolution_clock::now();
    auto duration = chrono::duration_cast<chrono::seconds>(stop - start);
    cout << "\t-" << data.Dt.number_of_vertices() << " output points" << endl;
    cout << "\t-" << data.Dt.number_of_finite_facets() << " finite facets" << endl;
    cout << "\t-" << data.Dt.number_of_finite_cells() << "(+" << data.Dt.number_of_cells()-data.Dt.number_of_finite_cells() << ")" << " cells" << endl;
    cout << "\t-done in " << duration.count() << "s" << endl;

}
// "Multi-view reconstruction preserving weakly-supported surfaces"
// and https://github.com/CGAL/cgal/tree/master/Triangulation_3/examples/Triangulation_3
// meaning, only insert point in triangulation, if no point in d-distance is already in there
// by looking for nearest neighbour in triangulation, which can be done efficiently with CGAL
// and checking if this nearest neighbour is further away then d-distance. if not increase a_vis of this neighbour by one.
void makeAdaptiveDelaunayWithInfo(dataHolder& data, double epsilon)
{
    auto start = chrono::high_resolution_clock::now();
    cout << "\nAdaptive Delaunay triangulation..." << endl;
    cout << "\t-" <<  data.points.size()  << " input points" << endl;

    if(data.scale_factor > 0.0){
        cout << "\t-scaling to unit cube is activate, so epislon will be scaled with scaling factor " << data.scale_factor << endl;
        epsilon *= data.scale_factor;
    }
    cout << "\t-with " << epsilon << " spacing" << endl;

    epsilon*=epsilon;

    // make the triangulation
//    Delaunay Dt;
    Vertex_handle v;
    v = data.Dt.insert(data.points[0]);
    v->info() = data.infos[0];
    v = data.Dt.insert(data.points[1]);
    v->info() = data.infos[1];
    v = data.Dt.insert(data.points[2]);
    v->info() = data.infos[2];
    assert( data.Dt.is_valid() );
    Cell_handle c;
    Vertex_handle current_vertex;
    Vertex_handle closest_vertex;
    Vertex_handle new_vertex;
    double dist;
    double new_dist;
    for(int i = 4; i < data.points.size(); i++){
        c=data.Dt.locate(data.points[i]);
        // TODO: define the distance to the four triangles of this cell, not to the four points!!
        dist=10e10;
        for(int ci = 0; ci < 4; ci++){
            current_vertex = c->vertex(ci);
            if(!data.Dt.is_vertex(current_vertex))
                continue;
            new_dist=CGAL::squared_distance(current_vertex->point(), data.points[i]);
            if(new_dist<dist){
                dist=new_dist;
                closest_vertex = current_vertex;
            }
        }
        if(dist>epsilon){
            new_vertex = data.Dt.insert(data.points[i]);
            new_vertex->info() = data.infos[i];
        }
        else{
            // TODO: this could be activate again, once sensor_positions is a set (controlled by the sensor index),
            // to avoid inserting the same sensor again and again for the same vertex.
            closest_vertex->info().sensor_positions.insert(closest_vertex->info().sensor_positions.end(),
                        data.infos[i].sensor_positions.begin(), data.infos[i].sensor_positions.end());

            closest_vertex->info().alpha+=1; // increase the number of points this vertex represents
//            closest_vertex->info().number_of_rays+=1;
        }
    }

    // remove duplicate sensors

//    auto end = v.end();
//    for (auto it = v.begin(); it != end; ++it) {
//        end = std::remove(it + 1, end, *it);
//    }

//    v.erase(end, v.end());

    auto stop = chrono::high_resolution_clock::now();
    auto duration = chrono::duration_cast<std::chrono::seconds>(stop - start);

    cout << "\t-" << data.Dt.number_of_vertices() << " output points" << endl;
    cout << "\t-" << data.Dt.number_of_finite_facets() << " finite facets" << endl;
    cout << "\t-" << data.Dt.number_of_finite_cells() << "(+" << data.Dt.number_of_cells()-data.Dt.number_of_finite_cells() << ")" << " cells" << endl;
    cout << "\t-done in " << duration.count() << "s" << endl;
}

void indexDelaunay(dataHolder& data, runningOptions options){
    // cells
    if(options.make_global_cell_idx){
        int gcidx = 0;
        Delaunay::All_cells_iterator aci;
        for(aci = data.Dt.all_cells_begin(); aci != data.Dt.all_cells_end(); aci++)
            aci->info().global_idx = gcidx++;
    }
    if(options.make_finite_cell_idx){
        int fcidx = 0;
        Delaunay::Finite_cells_iterator fci;
        for(fci = data.Dt.finite_cells_begin(); fci != data.Dt.finite_cells_end(); fci++)
            fci->info().finite_idx = fcidx++;
    }
    // vertex
    if(options.make_global_vertex_idx){
        int gvidx = 0;
        Delaunay::All_vertices_iterator avi;
        for(avi = data.Dt.all_vertices_begin(); avi != data.Dt.all_vertices_end(); avi++)
            avi->info().global_idx = gvidx++;
    }
    if(options.make_finite_vertex_idx){
        int fvidx = 0;
        Delaunay::Finite_vertices_iterator fvi;
        for(fvi = data.Dt.finite_vertices_begin(); fvi != data.Dt.finite_vertices_end(); fvi++)
            fvi->info().finite_idx = fvidx++;
    }
}

////////////////////////////////////////////////////////////
//////////////////////// scan object ////////////////
////////////////////////////////////////////////////////////
#include <CGAL/Random.h>
#include <random>
typedef CGAL::Side_of_triangle_mesh<SurfaceMesh, EPICK> Point_inside;
typedef CGAL::AABB_face_graph_triangle_primitive<SurfaceMesh> Primitive;
typedef CGAL::AABB_traits<EPICK, Primitive> AABB_Traits;
typedef CGAL::AABB_tree<AABB_Traits> Tree;
typedef Tree::Point_and_primitive_id Point_and_primitive_id;
typedef boost::optional<Tree::Intersection_and_primitive_id<Ray>::Type> Ray_intersection;
bool scanObjectClosed(dataHolder& data, runningOptions& options){

    auto start = std::chrono::high_resolution_clock::now();

    cout << "\nScan closed ground truth surface: " << endl;
    cout << "\t-with " << options.number_of_points_to_scan << " points " << endl;
    cout << "\t-with Gaussian noise with mean 0.0 and std " << options.noise_std << endl;
    cout << "\t-with " << options.percentage_of_outliers * 100 << "% outliers" << endl;
    cout << "\t-and " << options.number_of_cameras << " cameras" << endl;

    // get all the triangles from the ground truth polyhedron model
    if(!data.gt_poly.is_valid()){
        cout << "GROUND TRUTH IS NOT VALID!\n" << endl;
        return 1;
    }
    if(!data.gt_poly.is_closed()){
        cout << "GROUND TRUTH IS NOT CLOSED!\n" << endl;
        return 1;
    }

    if(!data.gt_poly.is_pure_triangle())
        CGAL::Polygon_mesh_processing::triangulate_faces(data.gt_poly);

    SurfaceMesh smesh;
    CGAL::copy_face_graph(data.gt_poly, smesh);

    Tree tree(faces(smesh).first, faces(smesh).second, smesh);
//    Tree tree(faces(data.gt_poly).first, faces(data.gt_poly).second, data.gt_poly);
    tree.accelerate_distance_queries();

    // make the synthetic rays
    // this gets an axis aligned bounding box
    Bbox bb = CGAL::Polygon_mesh_processing::bbox(data.gt_poly);
    Point bb_center = Point((bb.xmin()+bb.xmax())/2,
                            (bb.ymin()+bb.ymax())/2,
                            (bb.zmin()+bb.zmax())/2);

    double radius = sqrt(pow(bb.xmin()-bb_center.x(),2)+
                     pow(bb.ymin()-bb_center.y(),2)+
                     pow(bb.zmin()-bb_center.z(),2));

    CGAL::Random random(42);

    CGAL::Random_points_on_sphere_3<Point> points_on_sphere3(3.5*radius, random); // for cameras
    CGAL::Random_points_on_sphere_3<Point> points_on_sphere2(2.5*radius, random); // for cameras
    CGAL::Random_points_on_sphere_3<Point> points_on_sphere1(radius, random); // for aims
    vector<Point> cameras, aims;

    int k = options.number_of_cameras % 2;
    CGAL::cpp11::copy_n(points_on_sphere3, options.number_of_cameras/2 + k, std::back_inserter(cameras));
    CGAL::cpp11::copy_n(points_on_sphere2, options.number_of_cameras/2, std::back_inserter(cameras));
    for(int i = 0; i < cameras.size(); i++){
        cameras[i] = Point(cameras[i].x() + bb_center.x(),
                        cameras[i].y() + bb_center.y(),
                        cameras[i].z() + bb_center.z());
        data.sensor_map.insert(pair<int,Point>(i,cameras[i]));
    }

    CGAL::cpp11::copy_n(points_on_sphere1, options.number_of_points_to_scan, std::back_inserter(aims));

    // for noise/outliers
    // TODO: add more noise to the sensors (e.g. mimic wrong ray). without adding noise to the surface
    // this could simply be done by choosing a different camera in the very last step
    default_random_engine rand_gen;
    rand_gen.seed(42);
//    uniform_real_distribution<double> dn(-radius/100,radius/100); // 1 cm noise
    normal_distribution<double> rnoise(0.0, options.noise_std); // Gaussian noise with mean 0.0 and std noise_std

    // the following two randoms are for choosing a random camera and a random aim to shoot at, or simply put, for producing a random ray
    std::uniform_int_distribution<int> rcamera(0, options.number_of_cameras-1);
    std::uniform_int_distribution<int> rpoint(0, options.number_of_points_to_scan-1);
    // scan the polyhedron by finding "first" intersections of all triangles and rays
    int n_outliers = options.number_of_points_to_scan * options.percentage_of_outliers;
    double rn1, rn2, rn3;
    while(data.points.size() < (options.number_of_points_to_scan - n_outliers)){

        // randomly choose a camera and a point from the generated cameras and points
        int sensor_idx = rcamera(rand_gen);
        Point c = cameras[sensor_idx];
        Point a = aims[rpoint(rand_gen)];
        Ray ray(c,
                Point(bb_center.x()+a.x(), bb_center.y()+a.y(), bb_center.z()+a.z()));

        Point closest_intersection_point;

        Ray_intersection intersection = tree.first_intersection(ray);
        if(intersection){
          if(boost::get<Point>(&(intersection->first))){
            const Point* p = boost::get<Point>(&(intersection->first) );
            auto fd = intersection->second;
            Vector gt_normal = PMP::compute_face_normal(fd,smesh);

            rn1 = rnoise(rand_gen);
            rn2 = rnoise(rand_gen);
            rn3 = rnoise(rand_gen);
            closest_intersection_point=Point(p->x()+rn1,
                                             p->y()+rn2,
                                             p->z()+rn3);

            data.points.push_back(closest_intersection_point);
            vertex_info vinf;
            vinf.gt_normal = gt_normal;
            vinf.normal = gt_normal;
            vinf.sensor_positions.push_back(ray.source());
            vinf.sensor_vec = ray.source() - closest_intersection_point;
            vinf.sensor_idx = sensor_idx;
            // TODO: so, e.g. here just say v_inf.sensor_idx = sensor_idx + 1 for an outlier
            vinf.outlier = 0;
            data.infos.push_back(vinf);
          }
        }
    }

    vector<Point> outliers;
    if(data.points.size() < options.number_of_points_to_scan){
        CGAL::Random_points_in_sphere_3<Point> points_in_sphere(radius*3, random);
        CGAL::cpp11::copy_n(points_in_sphere, options.number_of_points_to_scan, std::back_inserter(outliers));
    }
    // add outliers
    while(data.points.size() < options.number_of_points_to_scan){

        // randomly choose a camera and an outlier point from the generated cameras and outlier points
        int sensor_idx = rcamera(rand_gen);
        Point c = cameras[sensor_idx];
        Point o = outliers[rpoint(rand_gen)];
        Point out = Point(bb_center.x()+o.x(), bb_center.y()+o.y(), bb_center.z()+o.z());
        Ray ray(Point(bb_center.x()+c.x(), bb_center.y()+c.y(), bb_center.z()+c.z()),
                out);

        data.points.push_back(out);
        vertex_info vinf;
        vinf.gt_normal = CGAL::NULL_VECTOR;
        vinf.normal = CGAL::NULL_VECTOR;
        vinf.sensor_positions.push_back(ray.source());
        vinf.sensor_vec = ray.source() - out;
        vinf.sensor_idx = sensor_idx;
        vinf.outlier = 1;
        data.infos.push_back(vinf);
    }
    data.has_sensor = true;
    data.has_normal = true;
    data.has_gt_normal = true;

    auto stop = chrono::high_resolution_clock::now();
    auto duration = chrono::duration_cast<chrono::seconds>(stop - start);
    cout << "\t-done after " << duration.count() << "s" << endl;

    return 0;

}

bool scanObjectOpen(dataHolder& data, runningOptions& options){

    auto start = std::chrono::high_resolution_clock::now();

    cout << "\nScan closed ground truth surface: " << endl;
    cout << "\t-with " << options.number_of_points_to_scan << " points " << endl;
    cout << "\t-with Gaussian noise with mean 0.0 and std " << options.noise_std << endl;
    cout << "\t-with " << options.percentage_of_outliers * 100 << "% outliers" << endl;
    cout << "\t-and " << options.number_of_cameras << " cameras" << endl;

    // get all the triangles from the ground truth polyhedron model
    if(!data.gt_poly.is_valid()){
        cout << "GROUND TRUTH IS NOT VALID!\n" << endl;
        return 1;
    }
    if(!data.gt_poly.is_closed()){
        cout << "GROUND TRUTH IS NOT CLOSED!\n" << endl;
        return 1;
    }

    if(!data.gt_poly.is_pure_triangle())
        CGAL::Polygon_mesh_processing::triangulate_faces(data.gt_poly);

    SurfaceMesh smesh;
    CGAL::copy_face_graph(data.gt_poly, smesh);

    Tree tree(faces(smesh).first, faces(smesh).second, smesh);
    tree.accelerate_distance_queries();

    // make the synthetic rays
    // this gets an axis aligned bounding box
    Bbox bb = CGAL::Polygon_mesh_processing::bbox(data.gt_poly);
    Point bb_center = Point((bb.xmin()+bb.xmax())/2,
                            (bb.ymin()+bb.ymax())/2,
                            (bb.zmin()+bb.zmax())/2);

    double radius = sqrt(pow(bb.xmin()-bb_center.x(),2)+
                     pow(bb.ymin()-bb_center.y(),2)+
                     pow(bb.zmin()-bb_center.z(),2));

    CGAL::Random random(42);

    CGAL::Random_points_in_sphere_3<Point> points_in_sphere3(1.0*radius, random); // for cameras
    CGAL::Random_points_in_triangle_mesh_3<Polyhedron> points_in_gt(data.gt_poly,random); // for aims
    vector<Point> temp_cameras, cameras, aims;

    CGAL::cpp11::copy_n(points_in_sphere3, 10000, std::back_inserter(temp_cameras));
    // Initialize the point-in-polyhedron tester
    const Point_inside inside_tester(tree);
    int i = 0;
    while(cameras.size() < options.number_of_cameras){
        if(inside_tester(temp_cameras[i]) == CGAL::ON_UNBOUNDED_SIDE){
            Point cam = Point(temp_cameras[i].x() + bb_center.x(),
                              temp_cameras[i].y() + bb_center.y(),
                              temp_cameras[i].z() + bb_center.z());
            cameras.push_back(cam);
            data.sensor_map.insert(pair<int,Point>(i,cam));
        }
        i++;
    }

    CGAL::cpp11::copy_n(points_in_gt, options.number_of_points_to_scan, std::back_inserter(aims));

    // for noise/outliers
    // TODO: add more noise to the sensors (e.g. mimic wrong ray). without adding noise to the surface
    // this could simply be done by choosing a different camera in the very last step
    default_random_engine rand_gen;
    rand_gen.seed(42);
//    uniform_real_distribution<double> dn(-radius/100,radius/100); // 1 cm noise
    normal_distribution<double> rnoise(0.0, options.noise_std); // Gaussian noise with mean 0.0 and std noise_std

    // the following two randoms are for choosing a random camera and a random aim to shoot at, or simply put, for producing a random ray
    std::uniform_int_distribution<int> rcamera(0, options.number_of_cameras-1);
    std::uniform_int_distribution<int> rpoint(0, options.number_of_points_to_scan-1);
    // scan the polyhedron by finding "first" intersections of all triangles and rays
    int n_outliers = options.number_of_points_to_scan * options.percentage_of_outliers;
    double rn1, rn2, rn3;
    while(data.points.size() < (options.number_of_points_to_scan - n_outliers)){

        // randomly choose a camera and a point from the generated cameras and points
        int sensor_idx = rcamera(rand_gen);
        Point c = cameras[sensor_idx];
        Point a = aims[rpoint(rand_gen)];
        Ray ray(c,
                Point(bb_center.x()+a.x(), bb_center.y()+a.y(), bb_center.z()+a.z()));

        Point closest_intersection_point;

        Ray_intersection intersection = tree.first_intersection(ray);
        if(intersection){
          if(boost::get<Point>(&(intersection->first))){
            const Point* p = boost::get<Point>(&(intersection->first) );
            auto fd = intersection->second;
            Vector gt_normal = PMP::compute_face_normal(fd,smesh);

            rn1 = rnoise(rand_gen);
            rn2 = rnoise(rand_gen);
            rn3 = rnoise(rand_gen);
            closest_intersection_point=Point(p->x()+rn1,
                                             p->y()+rn2,
                                             p->z()+rn3);

            data.points.push_back(closest_intersection_point);
            vertex_info vinf;
            vinf.gt_normal = gt_normal;
            vinf.normal = gt_normal;
            vinf.sensor_positions.push_back(ray.source());
            vinf.sensor_vec = ray.source() - closest_intersection_point;
            vinf.sensor_idx = sensor_idx;
            // TODO: so, e.g. here just say v_inf.sensor_idx = sensor_idx + 1 for an outlier
            vinf.outlier = 0;
            data.infos.push_back(vinf);
          }
        }
    }

    vector<Point> outliers;
    if(data.points.size() < options.number_of_points_to_scan){
        CGAL::Random_points_in_sphere_3<Point> points_in_sphere(radius*3, random);
        CGAL::cpp11::copy_n(points_in_sphere, options.number_of_points_to_scan, std::back_inserter(outliers));
    }
    // add outliers
    while(data.points.size() < options.number_of_points_to_scan){

        // randomly choose a camera and an outlier point from the generated cameras and outlier points
        int sensor_idx = rcamera(rand_gen);
        Point c = cameras[sensor_idx];
        Point o = outliers[rpoint(rand_gen)];
        Point out = Point(bb_center.x()+o.x(), bb_center.y()+o.y(), bb_center.z()+o.z());
        Ray ray(Point(bb_center.x()+c.x(), bb_center.y()+c.y(), bb_center.z()+c.z()),
                out);

        data.points.push_back(out);
        vertex_info vinf;
        vinf.gt_normal = CGAL::NULL_VECTOR;
        vinf.normal = CGAL::NULL_VECTOR;
        vinf.sensor_positions.push_back(ray.source());
        vinf.sensor_vec = ray.source() - out;
        vinf.sensor_idx = sensor_idx;
        vinf.outlier = 1;
        data.infos.push_back(vinf);
    }
    data.has_sensor = true;
    data.has_normal = true;
    data.has_gt_normal = true;

    auto stop = chrono::high_resolution_clock::now();
    auto duration = chrono::duration_cast<chrono::seconds>(stop - start);
    cout << "\t-done after " << duration.count() << "s" << endl;

    return 0;

}

//bool scanObjectOpen(dataHolder& data, runningOptions& options){

//    auto start = std::chrono::high_resolution_clock::now();

//    cout << "\nScan open ground truth surface: " << endl;
//    cout << "\t-with " << options.number_of_points_to_scan << " points " << endl;
//    cout << "\t-with Gaussian noise with mean 0.0 and std " << options.noise_std << endl;
//    cout << "\t-with " << options.percentage_of_outliers * 100 << "% outliers" << endl;
//    cout << "\t-and " << options.number_of_cameras << " cameras" << endl;

//    // get all the triangles from the ground truth polyhedron model
//    if(!data.gt_poly.is_valid()){
//        cout << "GROUND TRUTH IS NOT VALID!\n" << endl;
//        return 1;
//    }
//    if(!data.gt_poly.is_closed()){
//        cout << "GROUND TRUTH IS NOT CLOSED!\n" << endl;
//        return 1;
//    }

//    CGAL::Polygon_mesh_processing::remove_degenerate_faces(data.gt_poly);

//    if(!data.gt_poly.is_pure_triangle())
//        CGAL::Polygon_mesh_processing::triangulate_faces(data.gt_poly);

//    SurfaceMesh smesh;
//    CGAL::copy_face_graph(data.gt_poly, smesh);

//    Tree tree(faces(smesh).first, faces(smesh).second, smesh);
//    tree.accelerate_distance_queries();

//    // make the synthetic rays

//    CGAL::Random random(42);

//    // make the synthetic rays
//    // this gets an axis aligned bounding box
//    Bbox bb = CGAL::Polygon_mesh_processing::bbox(data.gt_poly);
//    Point bb_center = Point((bb.xmin()+bb.xmax())/2,
//                            (bb.ymin()+bb.ymax())/2,
//                            (bb.zmin()+bb.zmax())/2);

//    double radius = sqrt(pow(bb.xmin()-bb_center.x(),2)+
//                     pow(bb.ymin()-bb_center.y(),2)+
//                     pow(bb.zmin()-bb_center.z(),2));

//    CGAL::Random_points_on_sphere_3<Point> points_on_sphere1(1.5*radius, random); // for cameras
//    CGAL::Random_points_on_sphere_3<Point> points_on_sphere2(2.5*radius, random); // for cameras
//    CGAL::Random_points_on_sphere_3<Point> points_on_sphere3(3.5*radius, random); // for cameras
//    CGAL::Random_points_in_triangle_mesh_3<Polyhedron> points_in_gt(data.gt_poly,random); // for aims

//    vector<Point> temp_cameras, cameras, aims;
//    CGAL::cpp11::copy_n(points_in_gt, options.number_of_points_to_scan, std::back_inserter(aims));

////    if(data.sampled_sensors.empty()){
//    CGAL::cpp11::copy_n(points_on_sphere3, 200, std::back_inserter(temp_cameras));
//    CGAL::cpp11::copy_n(points_on_sphere2, 400, std::back_inserter(temp_cameras));
//    CGAL::cpp11::copy_n(points_on_sphere1, 400, std::back_inserter(temp_cameras));
//    std::uniform_int_distribution<int> tcamera(0, 999);

//    default_random_engine rand_gen;
//    rand_gen.seed(42);

//    int i = 0;
//    const Point_inside inside_tester(tree);
//    while(cameras.size()< options.number_of_cameras){
//        int rindx=tcamera(rand_gen);
//        Point c = Point(temp_cameras[rindx].x() + bb_center.x(),
//                        temp_cameras[rindx].y() + bb_center.y(),
//                        temp_cameras[rindx].z() + bb_center.z());
//        if(c.y() > bb_center.y() && inside_tester(c) == CGAL::ON_UNBOUNDED_SIDE){
//            cameras.push_back(c);
//            data.sensor_map.insert(pair<int,Point>(i++,c));
//        }
//    }

//    // for noise/outliers
//    // TODO: add more noise to the sensors (e.g. mimic wrong ray). without adding noise to the surface
//    // this could simply be done by choosing a different camera in the very last step

////    uniform_real_distribution<double> dn(-radius/100,radius/100); // 1 cm noise
//    normal_distribution<double> rnoise(0.0, options.noise_std); // Gaussian noise with mean 0.0 and std noise_std

//    // the following two randoms are for choosing a random camera and a random aim to shoot at, or simply put, for producing a random ray
//    std::uniform_int_distribution<int> rcamera(0, options.number_of_cameras-1);
//    std::uniform_int_distribution<int> rpoint(0, options.number_of_points_to_scan-1);
//    // scan the polyhedron by finding "first" intersections of all triangles and rays
//    int n_outliers = options.number_of_points_to_scan * options.percentage_of_outliers;
//    double rn1, rn2, rn3;
//    while(data.points.size() < (options.number_of_points_to_scan - n_outliers)){

//        // randomly choose a camera and a point from the generated cameras and points
//        int sensor_idx = rcamera(rand_gen);
//        Point c = cameras[sensor_idx];
//        Point a = aims[rpoint(rand_gen)];
//        Ray ray(c,a);

//        Point closest_intersection_point;

//        Ray_intersection intersection = tree.first_intersection(ray);
//        if(intersection){
//          if(boost::get<Point>(&(intersection->first))){
//            const Point* p = boost::get<Point>(&(intersection->first) );

//            auto fd = intersection->second;
//            Vector gt_normal = PMP::compute_face_normal(fd,smesh);

//            rn1 = rnoise(rand_gen);
//            rn2 = rnoise(rand_gen);
//            rn3 = rnoise(rand_gen);
//            closest_intersection_point=Point(p->x()+rn1,p->y()+rn2,p->z()+rn3);
//            data.points.push_back(closest_intersection_point);
//            vertex_info vinf;
//            vinf.gt_normal = gt_normal;
//            vinf.normal = gt_normal;
//            vinf.sensor_positions.push_back(ray.source());
//            vinf.sensor_vec = ray.source() - closest_intersection_point;
//            vinf.sensor_idx = sensor_idx;
//            // TODO: so, e.g. here just say v_inf.sensor_idx = sensor_idx + 1 for an outlier
//            vinf.outlier = 0;
//            data.infos.push_back(vinf);
//          }
//        }
//    }

//    vector<Point> outliers;
////    if(data.points.size() < options.number_of_points_to_scan){
////        CGAL::Random_points_in_sphere_3<Point> points_in_sphere(radius*3, random);
////        CGAL::cpp11::copy_n(points_in_sphere, options.number_of_points_to_scan, std::back_inserter(outliers));
////    }
////    if(data.points.size() < options.number_of_points_to_scan){
////        getOrientedBoundingBox(data.gt_poly,data.bb_array,data.bb_surface_mesh);
////        CGAL::Random_points_in_triangle_mesh_3<SurfaceMesh> points_in_bb(data.bb_surface_mesh,random); // for aims
////        CGAL::cpp11::copy_n(points_in_bb, options.number_of_points_to_scan, std::back_inserter(outliers));
////    }
//    if(data.points.size() < options.number_of_points_to_scan){
//        std::uniform_real_distribution<double> oxr(bb.xmin(),bb.xmax());
//        std::uniform_real_distribution<double> oyr(bb.ymin(),bb.ymax());
//        std::uniform_real_distribution<double> ozr(bb.zmin(),bb.zmax());
//        for(int i = 0; i < int(options.number_of_points_to_scan/7); i++){
//            outliers.push_back(Point(oxr(rand_gen)+rn1,oyr(rand_gen),ozr(rand_gen)+rn3));
//            outliers.push_back(Point(oxr(rand_gen)+rn2,oyr(rand_gen),ozr(rand_gen)));
//            outliers.push_back(Point(oxr(rand_gen)+rn3,oyr(rand_gen),ozr(rand_gen)+rn1));
//            outliers.push_back(Point(oxr(rand_gen)+rn3,oyr(rand_gen)+rn1,ozr(rand_gen)));
//            outliers.push_back(Point(oxr(rand_gen)+rn3,oyr(rand_gen)+rn2,ozr(rand_gen)));
//        }
//    }
//    std::uniform_int_distribution<int> routlier(0, outliers.size()-1);
//    // add outliers
//    double vx,vy,vz, vl;
//    while(data.points.size() < options.number_of_points_to_scan){

//        // randomly choose a camera and an outlier point from the generated cameras and outlier points
//        int sensor_idx = rcamera(rand_gen);
//        Point c = cameras[sensor_idx];
//        Point o = outliers[routlier(rand_gen)];
//        Ray ray(c,o);

//        vx = rnoise(rand_gen);
//        vy = rnoise(rand_gen);
//        vz = rnoise(rand_gen);
//        vl = sqrt(vx*vx+vy*vy+vz*vz);

//        Vector rand_normal = Vector(vx/vl,vy/vl,vz/vl);

//        data.points.push_back(o);
//        vertex_info vinf;
//        vinf.gt_normal = rand_normal;
//        vinf.normal = rand_normal;
//        vinf.sensor_positions.push_back(ray.source());
//        vinf.sensor_vec = ray.source() - o;
//        vinf.sensor_idx = sensor_idx;
//        vinf.outlier = 1;
//        data.infos.push_back(vinf);
//    }
//    data.has_sensor = true;
//    data.has_normal = true;
//    data.has_gt_normal = true;

//    auto stop = chrono::high_resolution_clock::now();
//    auto duration = chrono::duration_cast<chrono::seconds>(stop - start);
//    cout << "\t-done after " << duration.count() << "s" << endl;

//    return 0;

//}

////////////////////////////////////////////////////////////
/////////////////// preprocessing functions ////////////////
////////////////////////////////////////////////////////////
#include <CGAL/Polygon_mesh_processing/repair.h>
void preprocessSensorMesh(vector<Point>& points,
                          vector<vertex_info>& infos,
                          vector<std::vector<int>>& polys){

        auto start = chrono::high_resolution_clock::now();

        assert(points.size() == infos.size());
        map<Point, vertex_info> pim;
        for(int i = 0; i < points.size(); i++){
            pim[points[i]] = infos[i];
        }

        Polyhedron sensor_mesh;
        PMP::orient_polygon_soup(points, polys);
        PMP::polygon_soup_to_polygon_mesh(points, polys, sensor_mesh);
        PMP::remove_degenerate_faces(sensor_mesh);

        vector<Point> new_points;
        vector<vertex_info> new_infos;
        Polyhedron::Vertex_iterator svi;
        int id = 0;
        for(svi = sensor_mesh.vertices_begin(); svi != sensor_mesh.vertices_end(); svi++){
            new_points.push_back(svi->point());
            vertex_info info = pim.find(svi->point())->second;
            new_infos.push_back(info);
            svi->id() = id++;
        }
        Polyhedron::Facet_iterator sfi;
        id = 0;
        for(sfi = sensor_mesh.facets_begin(); sfi != sensor_mesh.facets_end(); sfi++){
            sfi->id() = id++;
        }
//        Polyhedron::Edge_iterator sei;
//        id = 0;
//        for(sei = sensor_mesh.edges_begin(); sei != sensor_mesh.edges_end(); sei++){
//            sei->id() = id++;
//        }
        vector<std::vector<int>> new_polys;
        for(sfi = sensor_mesh.facets_begin(); sfi != sensor_mesh.facets_end(); sfi++){
            Polyhedron::Halfedge_around_facet_circulator circ = sfi->facet_begin();
            std::vector<int> ids;
            do{ids.push_back(circ->vertex()->id());}
            while ( ++circ != sfi->facet_begin());
            new_polys.push_back(ids);
        }

        points = new_points;
        infos = new_infos;
        polys = new_polys;

        auto stop = chrono::high_resolution_clock::now();
        auto duration = chrono::duration_cast<chrono::seconds>(stop - start);
        cout << "Mesh preprocessing done in " << duration.count() << "s" << endl;
}

////////////////////////////////////////////////////////////
/////////////////// postprocessing functions ////////////////
////////////////////////////////////////////////////////////
void edgeCollapse(dataHolder& data, meshProcessingOptions mo)
{

    auto start = chrono::high_resolution_clock::now();

    cout << "\nEdge collapse..." << endl;
    cout << "\t-keep " << mo.keep_edges*100 << "\% of edges" << endl;
    cout << "\t-before: " << data.smesh.number_of_halfedges()/2 << endl;

    // This is a stop predicate (defines when the algorithm terminates).
    SMS::Count_ratio_stop_predicate<Polyhedron> stop_criterion(mo.keep_edges);

    // This the actual call to the simplification algorithm.
    // The surface mesh and stop conditions are mandatory arguments.
    // The index maps are needed because the vertices and edges
    // of this surface mesh lack an "id()" field.
    int r = SMS::edge_collapse(data.smesh,stop_criterion);

    cout << "\t-after " << (data.smesh.number_of_halfedges()/2) << endl;
    auto stop = chrono::high_resolution_clock::now();
    auto duration = chrono::duration_cast<chrono::seconds>(stop - start);
    cout << "\t-done in " << duration.count() << "s" << endl;

}

void variationalShapeApproximation(dataHolder& data, meshProcessingOptions mo){

    auto start = chrono::high_resolution_clock::now();

    cout << "\nVariational Shape Approximation..." << endl;

    // output indexed triangles
    vector<Point> anchors;
    vector<array<size_t,3>> triangles; // triplets of indices
    // free function interface with named parameters
    bool is_manifold = VSA::approximate_triangle_mesh(data.smesh,
      CGAL::parameters::seeding_method(VSA::HIERARCHICAL). // hierarchical seeding
      max_number_of_proxies(mo.max_number_of_proxies). // seeding with maximum number of proxies
      number_of_iterations(30). // number of clustering iterations after seeding
      anchors(back_inserter(anchors)). // anchor vertices
      triangles(back_inserter(triangles))); // indexed triangles
//    cout << "\t-anchor vertices: " << anchors.size() << endl;
//    cout << "\t-triangles: " << triangles.size() << endl;

    // convert from soup to surface mesh
//    PMP::orient_polygon_soup(anchors, triangles);
//    SurfaceMesh output;

//    data.smesh.collect_garbage();
//    data.smesh.clear();
//    data.smesh = output;
    data.remaining_points.clear();
    data.remaining_facets.clear();
    data.remaining_points = anchors;
    data.remaining_facets = triangles;

    auto stop = chrono::high_resolution_clock::now();
    auto duration = chrono::duration_cast<chrono::seconds>(stop - start);
    cout << "\t-done in " << duration.count() << "s" << endl;

    createSurfaceMesh(data, mo);
}

void removeLargeFaces(dataHolder& data, int factor){
    // get the area of the full mesh:
    double full_area = PMP::area(data.poly);
    int n_facets = data.poly.size_of_facets();
    cout << "\t-area of the mesh: " << full_area << endl;
    cout << "\t-number of facets of the mesh: " << n_facets << endl;
    set<Polyhedron::Facet_iterator> fs_to_remove;
    for (Polyhedron::Facet_iterator f_it = data.poly.facets_begin(); f_it != data.poly.facets_end(); ++f_it)
    {
        // bool remove_this_facet = (test if f_it should be removed)
        double face_area = PMP::face_area(f_it, data.poly);
        if(face_area > full_area * 10/n_facets)
        {
            fs_to_remove.insert(f_it);
        }
    }
    cout << "\t-number of facets that will be removed due to their size: " << fs_to_remove.size() << endl;
    for (auto fi = fs_to_remove.begin(); fi != fs_to_remove.end();  ++fi)
    {
        Polyhedron::Halfedge_around_facet_circulator f_circ = (*fi)->facet_begin();
        data.poly.erase_facet(f_circ);
    }

}

void fillHoles(SurfaceMesh& mesh){

    typedef boost::graph_traits<SurfaceMesh>::halfedge_descriptor   halfedge_descriptor;
    typedef boost::graph_traits<SurfaceMesh>::face_descriptor       face_descriptor;
    typedef boost::graph_traits<SurfaceMesh>::vertex_descriptor     vertex_descriptor;

    unsigned int nb_holes = 0;
    unsigned int unfilled_holes = 0;
    std::vector<halfedge_descriptor> border_cycles;
    // collect one halfedge per boundary cycle
    CGAL::Polygon_mesh_processing::extract_boundary_cycles(mesh, std::back_inserter(border_cycles));
    for(halfedge_descriptor h : border_cycles)
    {

      std::vector<face_descriptor>  patch_facets;
      std::vector<vertex_descriptor> patch_vertices;
      bool success = std::get<0>(
        CGAL::Polygon_mesh_processing::triangulate_refine_and_fair_hole(
                  mesh,
                  h,
                  std::back_inserter(patch_facets),
                  std::back_inserter(patch_vertices)) );
      if(!success)
          unfilled_holes++;
      else
          nb_holes++;
//      std::cout << "* Number of facets in constructed patch: " << patch_facets.size() << std::endl;
//      std::cout << "  Number of vertices in constructed patch: " << patch_vertices.size() << std::endl;
//      std::cout << "  Is fairing successful: " << success << std::endl;
    }
    std::cout << "\t-number of filled holes: " << nb_holes << std::endl;
    if(unfilled_holes)
        std::cout << "\t-number of unfilled holes: " << nb_holes << std::endl;

}
void createSurfaceMesh(dataHolder& data, meshProcessingOptions& options)
{
    auto start = std::chrono::high_resolution_clock::now();
    cout << "\nCreate a manifold and closed mesh..." << endl;

    data.smesh.collect_garbage();
    data.smesh.clear();
    data.smesh.collect_garbage();

    if(data.remaining_points.size() < 1){
        data.remaining_points = data.points;
        data.remaining_facets = data.facets;
    }

    cout << "\t-is manifold: " << PMP::is_polygon_soup_a_polygon_mesh(data.remaining_facets) << endl;

    if(options.try_to_make_manifold){
        cout << "\t-try to make manifold" << endl;
        bool manifold = PMP::orient_polygon_soup(data.remaining_points, data.remaining_facets);
        cout << "\t-has been made manifold: " << manifold << endl;
    }
    PMP::polygon_soup_to_polygon_mesh(data.remaining_points, data.remaining_facets, data.smesh);

    SurfaceMesh::Property_map<face_descriptor, std::size_t> fccmap =  data.smesh.add_property_map<face_descriptor, std::size_t>("f:CC").first;
    size_t components = CGAL::Polygon_mesh_processing::connected_components(data.smesh, fccmap);

    cout << "\t-number of components: " << components << endl;
    int erased_components = 0;
    if(options.number_of_components_to_keep > 0){
        erased_components = CGAL::Polygon_mesh_processing::keep_largest_connected_components(data.smesh, options.number_of_components_to_keep);
        cout << "\t-erased components: " << erased_components << endl;
    }

    bool closed = CGAL::is_closed(data.smesh);
    cout << "\t-is closed: " << closed << endl;

    if(options.try_to_close && !closed){
        cout << "\t-try to close holes" << endl;
        fillHoles(data.smesh);
        cout << "\t-has been closed: " << CGAL::is_closed(data.smesh) << endl;
    }
    // reorient
//    PMP::orient(data.smesh);
    bool oriented = PMP::is_outward_oriented(data.smesh);
    cout << "\t-is outward oriented: " << oriented << endl;

    cout << "\t-number of vertices: " << data.smesh.number_of_vertices() << endl;
    cout << "\t-number of edges: " << data.smesh.number_of_edges() << endl;
    cout << "\t-number of faces: " << data.smesh.number_of_faces() << endl;

    auto stop = chrono::high_resolution_clock::now();
    auto duration = chrono::duration_cast<chrono::seconds>(stop - start);
//    cout << "\t-Created duplicate vertices to generated polygon surface? " << oriented << endl;
//    cout << "\t-0 = additional vertices added for orientation and for ensuring manifoldness." << endl;
//    cout << "\t-Number of components of surface mesh: " << components << endl;
//    if(options.number_of_components_to_keep > 0)
//        cout << "\t-Number of erased components from surface mesh: " << erased_components << endl;
    cout << "\t-done in " << duration.count() << "s" << endl;

    // remove the CC property map again
    data.smesh.remove_property_map(fccmap);

}

#ifdef OpenMVS
// fix with PI from here: https://github.com/cdcseacave/openMVS/issues/643
// otherwise nameclash with some CGAL headers
#pragma push_macro("PI")
#undef PI
#include "MVS.h"
#pragma pop_macro("PI")
#include <boost/filesystem.hpp>
using namespace MVS;

int omvsCleanMesh(dirHolder& dir, dataHolder& data){

    auto start = std::chrono::high_resolution_clock::now();
    cout << "\nOMVS clean mesh..." << endl;
    cout << "\t-from densify file " << dir.path+"openMVS/densify_file.mvs" << endl;
    cout << "\t-from mesh " << dir.path+dir.write_file+dir.suffix+".ply" << endl;

    Scene scene(0);
    scene.Load(dir.path+"openMVS/densify_file.mvs");

    if(!scene.mesh.Load(dir.path+dir.write_file+dir.suffix+".ply")){
            cout << "ERROR in loading the mesh file" << endl;
            return 1;
    };

    int nItersFixNonManifold = 4; // also hardcoded to 4 in omvs code
    for (unsigned i=0; i<nItersFixNonManifold; ++i)
        if (!scene.mesh.FixNonManifold())
            break;

//    dir.suffix = "_nm";
//    scene.mesh.Save(dir.path+dir.write_file+dir.suffix+".ply");

    // clean the mesh
    scene.mesh.Clean(1.f, 20.f, true, 30.f, 2.f, false);
    scene.mesh.Clean(1.f, 0.f, true, 30.f, 0, false); // extra cleaning trying to close more holes
    scene.mesh.Clean(1.f, 0.f, false, 0, 0, true); // extra cleaning to remove non-manifold problems created by closing holes

    dir.suffix = "_cleaned";
    scene.mesh.Save(dir.path+dir.write_file+dir.suffix+".ply");

    auto stop = chrono::high_resolution_clock::now();
    auto duration = chrono::duration_cast<chrono::seconds>(stop - start);
    cout << "\t-saved to " << dir.path+dir.write_file+dir.suffix+".ply" << endl;
    cout << "\t-done after " << duration.count() << "s" << endl;

    return 0;
}

#endif

////////////////////////////////////////////////////////////
/////////////////// info functions ////////////////
////////////////////////////////////////////////////////////

Point getBoundingBoxCentroid(std::array<Point, 8>& obb_points, double z_offset){

    double mx = 0, my = 0, mz = 0;
    for(const auto p : obb_points){
        mx+=p.x();
        my+=p.y();
        mz+=p.z();
    }
    return Point(mx/8,my/8,mz/8+z_offset);

}

#include <CGAL/optimal_bounding_box.h>
void getOrientedBoundingBox(Polyhedron& poly, std::array<Point, 8>& obb_points, SurfaceMesh& obb){

    CGAL::oriented_bounding_box(poly,obb_points);
    CGAL::make_hexahedron(obb_points[0], obb_points[1], obb_points[2], obb_points[3],
                          obb_points[4], obb_points[5], obb_points[6], obb_points[7], obb);
    CGAL::Polygon_mesh_processing::triangulate_faces(obb);

}

void calculateMeanTriangleArea(dataHolder& data){

    int n_facets = data.Dt.number_of_finite_facets();
    cout << "\nCalculate mean triangle area..." << endl;

    Delaunay::Finite_facets_iterator ffi;
    for (ffi = data.Dt.finite_facets_begin(); ffi != data.Dt.finite_facets_end(); ffi++)
        data.mean_triangle_area+=sqrt(data.Dt.triangle(*ffi).squared_area());
    data.mean_triangle_area = data.mean_triangle_area / n_facets;
    cout << "\t-mean triangle area: " << data.mean_triangle_area << endl;

}

void calculateMeanCellVolume(dataHolder& data){

    int n_cells = data.Dt.number_of_finite_cells();
    cout << "\nCalculate mean cell volume..." << endl;

    Delaunay::Finite_cells_iterator ffi;
    for(ffi = data.Dt.finite_cells_begin(); ffi != data.Dt.finite_cells_end(); ffi++){
//        Tetrahedron tet = ;
        data.mean_cell_volume+=data.Dt.tetrahedron(ffi).volume();
    }

    data.mean_cell_volume = data.mean_cell_volume / n_cells;
    cout << "\t-mean cell volume: " << data.mean_cell_volume << endl;

};

void createVertexSet(dataHolder& data){

    auto start = chrono::high_resolution_clock::now();
    cout << "\nCreate vertex set with properties..." << endl;
    // init point set
    data.point_set.clear();
    data.point_set.reserve(data.smesh.number_of_vertices());
    Point_set::Property_map<Vector> normals = data.point_set.add_normal_map().first;
    Point_set::Property_map<SurfaceMesh::Vertex_index> vindex = data.point_set.add_property_map<SurfaceMesh::Vertex_index>("v:index").first;

    SurfaceMesh::Property_map<vertex_descriptor,Vector> vnormals;
    vnormals = data.smesh.add_property_map<vertex_descriptor, Vector>("v:normals", CGAL::NULL_VECTOR).first;
    PMP::compute_vertex_normals(data.smesh, vnormals);

    // fill point_set with the points from mesh vertices
    for(auto it = data.smesh.vertices_begin (); it < data.smesh.vertices_end (); ++it){
        // point-normal-vertex_handle map
        Point_set::iterator pit = data.point_set.insert(data.smesh.point(*it));
        normals[*pit] = vnormals[*it];
        vindex[*pit] = *it;
    }
    auto stop = chrono::high_resolution_clock::now();
    auto duration = chrono::duration_cast<chrono::seconds>(stop - start);
    cout << "\t-done in " << duration.count() << "s" << endl;

}