\title{
多物理和多领域PDE模拟的灵活框架
}




\begin{abstract}
物理学和工程学中的许多重要问题，如流体力学和连续体力学，都是用偏微分方程来建模的。这些问题通常不能直接解决，而必须进行数值逼近，这在数学和计算机科学层面都是一个具有挑战性的过程。

在这项工作中，我们提出了一套新颖的软件组件，方便创建多域偏微分方程问题的模拟程序。我们确定了与多个空间域及其附属物理问题的耦合有关的实施挑战，并开发了一个由明确定义的构件组成的数学框架，通过将单个物理构件（通常已经被应用科学家很好地理解）与描述这些子问题之间相互作用的额外构件相结合，可以用来组成一个多域问题。

我们在成熟的`DUNE`数值框架之上，介绍了这些数学概念的开源软件实现。这个实现包括两个主要部分：一个是将任何现有的`DUNE`网格细分为多个子域的机制，另一个是对高级偏微分方程工具箱求解器`PDELAB`的一系列扩展，使我们的数学框架的组成部分在其求解器中可用。我们的整体设计使应用层面的科学家能够重新使用来自单一物理模拟的现有代码块，并将它们结合起来解决新的多域问题。

这个新功能在很大程度上是基于`PDELAB`的乘积函数空间的递归树表示法；我们用一个新的$\mathrm{C}++$库来取代这些树的内部临时实现，用于静态定义、基于模板的对象树。由于多领域问题通常需要利用领域分解方法的结构化线性代数求解器，我们开发了一个数学框架，用于描述基于底层函数空间结构的偏微分方程问题组装过程中产生的向量和矩阵的结构。这个框架在`PDELAB`中实现；它基于我们的树库所提供的树形转换机制。

我们通过两个模型实例展示了我们的多领域仿真组件的通用性及其对开发者生产力的影响；通过描述我们的软件对几个外部研究项目的影响，展示了我们简化真实世界应用开发的最终目标。最后，我们衡量了我们的扩展对现有`DUNE`框架的性能影响，并讨论了我们为减少任何现有性能惩罚而实施的缓解措施。
\end{abstract}




\section{Introduction}

\subsection{Motivation and Scope}

今天，自然科学和工程领域的许多有趣的问题都涉及到对多个物理模型的同时检验，这些模型通过一组耦合条件进行交互，例如包括大气和土壤中的水的传输过程的气候模型或血管和周围细胞的生物模型。对于这些问题中的很大一部分，模型可以写成偏微分方程（PDEs）系统。除了一些微不足道的例子问题，一般来说，不可能得到这样一个系统的分析解。为了研究这些问题，基本的PDEs通过有限元方法（`FEM`）或类似方法进行离散化。最终，这导致了一个（可能是非线性的）代数方程系统，然后必须用数字来解决。

多重（物理学、领域、规模）模拟是理解这种复杂过程和/或能够模拟这些过程的重要工具。走向 "多 "可以由问题的结构驱动，也可以由需要结合不同复杂性的多个模型以减少模拟的整体计算成本驱动。

作为一个本质上结合了多种物理模型和多种领域的问题的一个很好的例子，请考虑流体结构相互作用领域（`FSI`），它关注的是可变形结构（如飞机）和周围流体之间的相互作用。它在现实世界中有重要的应用，例如在航空工程中，它被用来通过形状优化使飞机阻力最小化，以及识别飞机结构的薄弱点（结构失效分析）。  [Fig1.1]描述了模拟高超音速飞机周围7马赫气流的结果，显示了气流的速度信息和飞机结构的诱导应力。这类问题带来了各种各样的挑战，从结构变形引起的几何形状的移动到解决所产生的非线性代数方程组的巨大困难，对于这些问题有两种普遍的方法。一类方法是利用域分解方法（Steklov-Poincaré算子，参见[^108] [^123]）对系统进行分割，如Yang[^125]所做的那样，使得单个问题更容易解决。另一方面，这些子问题的求解器需要使用某种迭代方案进行耦合。在一般情况下，创建一个具有可预测收敛性的整体模拟是非常困难的。另一个学派试图通过创建一个单一的求解器来避免这些问题，如[^122] [^44]中提出的，作者认为对于复杂的设置，收敛性是不可能保证的。然而，对于那些完全耦合的问题公式，现有的求解器还不能扩展到现实的问题规模。

开发多域模拟的另一个原因来自于可以用多种方式建模的应用，每个模型在计算成本和精度之间提供不同的权衡。多域模拟使得在整个空间领域的不同区域选择不同的模型成为可能。例如，在研究$C O_{2}$注入地下水时，可以利用这种方法，如[Fig1.2]所示：最初，$\mathrm{CO}_{2}$只存在于一个小区域，模拟可以从只对该区域的油水相互作用进行建模而在其他地方使用更简单的纯水模型中获益。

纵观这两个多领域模型的典型应用，可以看出针对此类问题的模拟开发是一项非常复杂的任务，需要在模型开发（通常由应用科学家完成）、数学（创建稳定高效的离散化和求解器）和计算机科学（实施的复杂性和效率）这三个不同的领域有很高的熟练度。在我们的论文中，我们开发了一些软件工具，抽象出大部分与计算机科学有关的挑战，大大简化了多领域问题的仿真开发的实施部分，使数学家和应用科学家更容易开发这种仿真。

\subsection{Related Work}

PDEs的数值解法是一个在过去几十年里不断扩展的科学领域。传统上，方法开发和应用方面存在着非常明显的分歧，前者侧重于分析方面的改进，通常通过小型的、有目的的数值例子（通常在MATLAB这样的敏捷环境中以临时方式建立）来验证新的算法；后者通常由物理学家和工程师而不是数学家驱动，他们倾向于开发大型的、高度专业化的模拟代码，专注于他们的具体应用。这些代码往往被调整得非常好，但往往停留在过时的数学方法上，因为它们的单片设计使得重要的算法很难适应。

虽然不限于这个目标，科学计算试图通过开发高度灵活的算法和软件来弥合这一差距，使得快速开发需要大量控制仿真过程的原型应用成为可能，同时提供必要的基础设施，用替代离散化、附加求解器和支持并行计算等额外功能来扩展这一核心仿真。

一般来说，PDE模拟可分为两个不同的阶段：问题组装，包括计算描述离散化PDE模型所提出的代数问题的向量和矩阵，以及问题解决，即致力于解决该（潜在的非线性）代数问题。

\subsubsection{PDE Assembly Frameworks}

多年来，有限元(`FE`)问题组装的重复性催生了大量的软件包，在不同程度上使这一过程自动化。这些软件包的范围包括主要以网格为中心的方法（如ALBERTA [^113] [^112] [^2] , ALUGrid [^25] [^4] ）和更综合的方法（如 deal.II [^13] , UG [^14] [^17] [^117] , diffpack [^80][^36]），直至采用特定领域语言（DSL）作为指定模型方程的高级接口的高度集成框架（sundance [^83], freefem [^65] [^66] , FEniCS [^82] ）。本论文还基于一个成熟的数值框架，即分布式统一数值环境（`DUNE`），这是一个模块化的、严重依赖模板的C++模块集合，围绕着细粒度接口层的理念，为现有软件提供一个统一的前端，在编译时可以优化掉，以提供灵活性、通用性和性能的良好组合。

不幸的是，这些既定的仿真框架大多集中在解决一个共同领域的问题上。此外，在仿真中对这些子域进行建模的标准方法是为每个子域采用不同的网格，这主要是出于对子问题重用现有软件包的愿望。这种方法的一个主要缺点是在创建单个网格时需要大量的手工设置工作，特别是在具有复杂边界几何形状的界面问题上，界面的两边都需要尽可能地与界面的共同几何形状匹配。此外，任何耦合条件的计算，无论是在低维界面上还是在重叠的子域上，都需要底层网格离散化的符合性匹配，对于不同的网格来说，这往往需要非常昂贵的共同子网格的计算。能够处理程序间网格和`DOF`传输并协调弱耦合求解方案的软件包的例子包括SIERRA [^45] 和MpCCI [^89] 。最近的PreCICE库项目[^107] [^116]正试图在高性能计算（`HPC`）规模上耦合现有的单物理学黑盒代码。

在本论文中，我们将采取一种不同的方法。我们的框架承担了对包括所有子问题求解器在内的整个仿真堆栈的控制，以创建一个紧密集成的应用程序，这使我们能够研究单体和弱耦合的求解器，并回避必须匹配不透明的网格拓扑结构的问题。由于这种高集成度，我们的解决方案更适合于通常与模型和数值水平上的方法开发相关的探索性软件开发类型。

\subsubsection{Linear Algebra Libraries}

任何PDE模拟的第二个主要部分涉及到解决组装的线性或非线性代数系统，这个问题设置的特点与组装有明显的不同；特别是，它涉及到一套更受约束和定义明确的数据结构和算法（向量和矩阵以及线性/非线性求解器和预处理程序）。因此，在定制模拟中使用现有的线性代数（`LA`）库要容易得多，许多软件包都有专门的界面，允许用户在不同的`LA`包之间进行选择，并为当前的问题挑选最佳包。

这种更倾向于实现重用的趋势也导致了集中在少数大型软件项目上。这一领域最大的两项工作是PETSc [^12] [^11] [^10] 和Trilinos [^68] [^115] 。除了一些不同的稀疏矩阵实现外，这些软件包还包含各种线性求解器、高级预处理器（包括多网格预处理器）、非线性求解器和用于时间离散化的常微分方程（ `ODE` ）求解器，并通过消息传递接口（ `MPI` ）、开放式多处理（ `OpenMP` ）等为并行化提供支持。许多更专业的组件实际上是由外部开发的，PETSc大多捆绑外部项目，并将其包装在PETScified应用编程接口中（`API`），而Trilinos采取了一种更综合的方法，即基于包的整体框架，各个组件通常建立在一个提供基本数据结构的公共核心之上。然而，它的使用很复杂，因为它包含几个不同的（不兼容的）向量和矩阵实现。

除了这两个非常大的项目（都是由美国国家实验室资助的），还有两个相对知名的软件包也得到了相对广泛的使用，它们是Eigen [^60] 和矩阵模板库（MTL4） [^56] [^85] 。Eigen现在缺乏对基于`MPI`的大规模并行的支持，而MTL4则分为非并行化的开源核心和增加对并行计算支持的商业许可部分。这两个库都是用高级的、大量模板化的$\mathrm{C}++$编写的，这与PETSc（基于C）和$-$（在较小程度上）--Trilinos形成了鲜明的对比。它们采用运算符重载，为向量和矩阵的基本操作提供了非常自然的语法，并使用表达式模板和懒惰评估来有效地评估这些表达式。这些特点使得从其算法的数学描述中直接实现许多标准求解器变得非常容易，这部分地弥补了它们包含的求解器组件远少于这两个大框架的事实。最后是uBLAS库[^121]，它是`Boost`[^114]的一部分，但它最近没有看到任何发展，缺少内置求解器和对并行的支持。

`DUNE`（更具体地说，它的求解器工具箱`PDELAB`）提供了这些库中的几个接口，但对于这项工作，我们将使用它自己的内部线性代数包，称为迭代求解器模板库（`ISTL`）[^23][^22]代替；由于我们专注于问题组装阶段，这个库中包含的求解器完全足以满足我们的目的。

\subsection{Structure and Contribution}

本工作的主要贡献是在`DUNE`及其离散化模块`PDELAB`的基础上创建了一个软件框架，用于通过基于网格的离散化来解决多物理问题，特别是多域问题。

在简单介绍了偏微分方程及其使用一般有限元类型方法的解法之后，我们对构成我们实现基础的`DUNE`软件框架进行了高度概括，并描述了理解后面几章介绍的一些实现技术所需的一些高级C++编程技巧。

我们工作的主要部分是关于我们的软件框架。在第三章中，我们介绍了一个`DUNE`元网格，它为任何现有的`DUNE`网格实现增加了子域支持，这一特点构成了我们高水平多域仿真框架的基础。第四章是对多域问题的一般性介绍，以及对这个仿真框架的高层概述。在第五章中，我们介绍了一个用于函数空间树和相关对象的C++模板库，它是我们框架和底层`PDELAB`工具箱的核心。第6章是关于控制集合向量和矩阵的条目顺序，并提出了一个灵活的框架，通过注释函数空间来产生不同的顺序；这是使用高级线性求解器所需要的功能，通常在多域仿真中需要。在第七章中，我们解释了我们框架的一些内部实现。第八章包含了我们工作的最后一个主要部分，在那里我们展示了我们的实现在一对模型问题上的应用，以说明其加快应用开发的能力，并量化其与标准（非多域）`DUNE`相比的性能开销。此外，我们还展示了它对现实世界应用的影响，我们的软件被其他科学家用来开发和执行他们的多域模拟。我们描述了这些项目是如何从我们框架提供的基础设施中受益的。

最后，在第九章，我们提供了一个结论，并指出了我们的工作可以进一步扩展或改进的一些领域。



\section{Fundamentals}

在这一章中，我们对几个对理解我们的工作至关重要的主题做了一个简短的概述。特别是，我们介绍了PDEs及其使用有限元方法（`FEM`）的数值解法，重点是建立符号并概述了装配过程，即描述问题的向量和矩阵的计算。之后，我们继续介绍构成我们自己的软件实现基础的`DUNE`软件框架；最后是对高级$\mathrm{C}++$编程技术的简短总结，这是理解我们许多实现决策所需要的。

\subsection{Partial Differential Equations}

本节对PDEs及其数值解法做了一个非常紧凑的概述。它主要是为了介绍后面各章所需的术语和符号，既不详尽，也不具有数学上的严谨性；对于更全面的论述，我们可以参考[^16]，本介绍也主要基于此。

开放域PDE上的一般线性、标量二阶$\Omega \subset \mathbb{R}^{d}$可以写成

\begin{equation} 
L u=\nabla \cdot(\boldsymbol{D}(x) \nabla u)+\boldsymbol{b}(x) \cdot \nabla u+c(x) u=f \text { in } \Omega
\label{2.1}
\end{equation}

未知数 $u: \Omega \rightarrow \mathbb{F}$ ，其中 $\mathbb{F}$ 通常是 $\mathbb{R}$ 或 $\mathbb{C}$ ，空间参数场 $\boldsymbol{D}: \Omega \rightarrow \mathbb{F}^{d \times d}, \boldsymbol{b}: \Omega \rightarrow \mathbb{F}^{d}, c: \Omega \rightarrow \mathbb{F}$ 和右手 $f: \Omega \rightarrow \mathbb{F}^{d}$ 。虽然我们的软件框架也支持复数的计算，但为了简单起见，我们在下文中将假设$\mathbb{F}=\mathbb{R}$。

根据$\boldsymbol{D}(x)$和$\boldsymbol{b}(x)$的值，方程式\ref{2.1}被称为

如果$\boldsymbol{D}(x)$是正定或负定，则\textbf{elliptic}在$x$中。

如果\textbf{hyperbolic}的所有特征值均为非零，且除一个外所有特征值的符号相同，则$x$中的\textbf{hyperbolic}，或

\textbf{parabolic}中的$x$，如果正好有一个特征值为零，所有剩余的特征值都有相同的符号，矩阵$(\boldsymbol{D}(x), \boldsymbol{b}(x))$有全等级。

先验地讲，这种分类是一种局部属性。然而，在许多情况下，它对所有的$x \in \Omega$都是成立的，因此成为有关的PDE的全局属性。

这种分类的术语植根于二维环境，根据矩阵$\boldsymbol{D}$的值，二次形式$d_{11} x_{1}^{2}+2 d_{12} x_{1} x_{2}+d_{22} x_{2}^{2}$的水平集函数具有相应几何曲线的形状。

一个PDE不一定属于上述类别，因为这个分类并不详尽；此外，不同$\Omega$的方程特征可能不同。

静止的标量PDE的一个非常简单的例子是静止的对流扩散-反应方程。它描述了在扩散、对流和反应三个过程存在的情况下浓度的空间分布$u$，其公式为

\begin{equation} 
\begin{aligned}
-\nabla \cdot(\boldsymbol{D} \nabla u)+\boldsymbol{b} \cdot \nabla u+c u &=f \text { in } \Omega \subset \mathbb{R}^{d}, \\
u &=g \text { on } \Gamma_{D} \\
-\nabla u \cdot \boldsymbol{n} &=j \text { on } \Gamma_{N}
\end{aligned}
\label{2.2}
\end{equation}

具有对称的正定扩散张量$\boldsymbol{D} \in \mathbb{R}^{d \times d}$，对流速度$\boldsymbol{b} \in \mathbb{R}^{d}$和反应速率$c \in \mathbb{R}$。这里，$\boldsymbol{n}$表示单位外法向量，$g$规定了`Dirichlet`的边界条件和$j$的边界条件。一起，$\Gamma_{D}$和$\Gamma_{N}$形成了对$\Omega$边界的分割，使得$\Gamma_{D} \cap \Gamma_{N}=\emptyset$和$\partial \Omega=\Gamma_{D} \cup \Gamma_{N}$ 。如果$g=0$ ，`Dirichlet`的边界条件被称为同质性。

\subsubsection{ Weak Form }

寻找类似方程\ref{2.2}的二阶PDE的解$u$需要$u \in C^{2}(\Omega)$，即$u$在$\Omega$的各处均可连续两次微分。这是一个非常有局限性的限制，因为它排除了用分片线性函数来逼近$u$。为了部分克服这一限制，我们通过应用变分原理使原始方程变得平滑，也就是说，我们将方程乘以一个合适的测试函数并在域上进行积分。这个结果被称为PDE的弱表述。鉴于对流-扩散-反应方程\ref{2.2}在整个边界上具有同质`Dirichlet`条件，我们选择一个在边界上消失的检验函数$v$并在$\Omega$上进行积分，这就得到了

\begin{equation} 
-\int_{\Omega} \nabla \cdot(\boldsymbol{D} \nabla u) v \mathrm{~d} x+\int_{\Omega} \boldsymbol{b} \cdot \nabla u v+c u v \mathrm{~d} x=\int_{\Omega} f v \mathrm{~d} x
\label{2.3}
\end{equation}

对第一项进行分项积分，并放弃由此产生的边界积分，由于$v=0$对$\partial \Omega$的影响，边界积分消失了，我们可以得到

\begin{equation} 
\int_{\Omega}(\boldsymbol{D} \nabla u) \cdot \nabla v \mathrm{~d} x+\int_{\Omega} \boldsymbol{b} \cdot \nabla u v+c u v \mathrm{~d} x=\int_{\Omega} f v \mathrm{~d} x
\label{2.4}
\end{equation}

然后我们定义双线性和线性形式

\begin{equation}
a(u, v)=\int_{\Omega}(\boldsymbol{D} \nabla u) \cdot \nabla v+\boldsymbol{b} \cdot \nabla u v+c u v \mathrm{~d} x \quad \text { and } \quad l(v)=\int_{\Omega} f v \mathrm{~d} x
\end{equation}

利用这些形式，我们的问题可以写成弱的形式为

\begin{equation} 
\text { Find } u \in U: \quad a(u, v)=l(v) \quad \forall v \in V
\label{2.5}
\end{equation}

方程\ref{2.5}的解$u$被称为弱解。弱解不一定满足强公式\ref{2.2}；强公式的解必须在$C_{0}^{2}(\Omega)$中，而弱公式只需要第一导数。事实上，弱解几乎在任何地方都必须在$C^{1}$中，因为弱公式是基于弱微分的概念，它平滑了微分中的低维跳变。

`PDELAB`使用了方程\ref{2.5}的一个稍加修改的版本：我们引入了残差形式$r(u, v)=a(u, v)-l(v)$，得到了问题的残差表述。

\begin{equation} 
\text { Find } u \in U: \quad r(u, v)=0 \quad \forall v \in V
\label{2.6}
\end{equation}

正如我们稍后将看到的，这种提法的好处是，我们可以通过简单地放弃$r(u, v)$必须是$u$中的线性的要求，自然地将其扩展到非线性问题。

一般来说，分析空间$U$和检验空间$V$不一定完全相同。在这种非常普遍的情况下，上述的变分方法被称为`Petrov-Galerkin`方法。然而，现实世界中的大多数应用都是假设$U=V$，这被称为Galerkin方法。

`PDELAB`和本工作中提出的扩展框架都支持PetrovGalerkin方案，但为了简化符号，我们将把下面的讨论限制在具有相同的解析和测试空间的Galerkin方法上，因为具有不同的解析和测试空间并不从根本上改变我们在本论文中引入的多域方法的适用性。

\subsubsection{Existence and Uniqueness}

如果一个PDE问题有一个解，如果这个解是唯一的，如果这个解连续地依赖于数据（边界条件和参数函数），那么这个问题就被称为良好解决。虽然有多个定理为PDE问题的良好解决性提供了必要和/或充分条件，但我们只限于说明其中最著名的结果，它给出了一个相当容易验证的良好解决性的充分条件。

\textbf{Theorem} 2.1（Lax-Milgram）。设$V$为希尔伯特空间，$a \in \mathcal{L}(V \times V ; \mathbb{R})$和$l \in V^{\prime}$ 。那么，如果$a$是胁迫性的，即如果有一个$\alpha>0$，那么方程\ref{2.5}是良好解决的，即如果有一个$\alpha>0$使

\begin{equation}
a(u, u) \geq \alpha\|u\|^{2} \quad \forall u \in V
\end{equation}

并且如果以下先验估计成立。

\begin{equation}
\|u\|_{V} \leq \frac{1}{\alpha}\|l\|_{V^{\prime}} \quad \forall l \in V^{\prime}
\end{equation}

这个定理的直接证明可以在 [^16] 中找到。在这项工作的其余部分，我们一般会假设我们所研究的任何问题都满足Lax-Milgram或类似的良好处置标准，因此我们能够获得并解决该问题的离散化版本。

\subsubsection{ Discrete Problem }

只有数量非常有限的PDEs的分析解是已知的；通常情况下，只可能计算出一个数值的近似解。一般来说，解决PDE相当于找到一个函数$\psi: \Omega \rightarrow \mathbb{R}$，该函数对每个$x \in \Omega$都满足方程\ref{2.1}。这个问题不能直接用数字来解决，因为基础函数空间$V$是无限维的。因此，任何数值解法方案都必然涉及离散化过程，其最终目标是找到一个适合的有限维子空间$V^{h}$，以获得$V$的良好近似。

如果我们假设找到了一个合适的维度为$N$的有限维空间$V^{h}$（关于如何构建这样一个空间，参见第$2.2$节），我们可以为$V^{h}$构建一个基$\Phi=\left\{\varphi_{i}\right\}_{i=0, \ldots, N-1}$，并将任何函数$\psi \in V^{h}$表示为这些基函数的线性组合。

\begin{equation} 
\psi=\sum_{i=0}^{N-1} u_{i} \varphi_{i}, \quad u_{i} \in \mathbb{R}
\label{2.7}
\end{equation}

系数$u_{i}$通常被称为自由度（DOF），矢量$\boldsymbol{u}=\left(u_{0}, \ldots, u_{N-1}\right)$为`DOF`矢量。对于一个固定的基础$\Phi$，这个向量是函数$\psi$的唯一表示，可以作为一个浮点数的向量存储在计算机中；最终，任何PDE模拟的目标是获得解决方案的`DOF`向量$\boldsymbol{u}$。

从高层次的角度来看，残差形式的问题的离散化版本方程\ref{2.5}仍然可以用与连续情况相同的方式来写。

\begin{equation} 
\text { Find } \psi \in V^{h}: \quad r^{h}(\psi, \varphi)=0 \quad \forall \varphi \in V^{h}
\label{2.8}
\end{equation}

请注意，问题结构没有任何改变；函数空间$V$和残差形式$r(\cdot, \cdot)$只是被它们的离散对应物$V^{h}$和$r^{h}$取代。然后我们利用$r^{h}(\psi, \varphi)$是一个双线性形式的事实，因此足以用$V^{h}$的基函数$\varphi_{i}$来检验方程\ref{2.8}。用方程\ref{2.7}代替$\psi$，我们得到了有限维代数问题

\begin{equation} 
\text { Find } \boldsymbol{u} \in \mathbb{R}^{N}: \quad r^{h}\left(\sum_{j=0}^{N-1} u_{j} \varphi_{j}, \varphi_{i}\right)=0, \quad i=0, \ldots, N-1
\label{2.9}
\end{equation}

它用PDE矢量$\boldsymbol{u}$来描述`DOF`问题。为了进一步的符号方便，我们最后引入离散残差算子$\mathcal{R}^{h}$为

\begin{equation} 
\mathcal{R}^{h}: \mathbb{R}^{N} \rightarrow \mathbb{R}^{N},\left.\quad\left(\mathcal{R}^{h}(\boldsymbol{u})\right)\right|_{i}=r^{h}\left(\sum_{j=0}^{N-1} u_{j} \varphi_{j}, \varphi_{i}\right)
\label{2.10}
\end{equation}

然后可以将代数问题方程\ref{2.9}写为

\begin{equation} 
\text { Find } \boldsymbol{u} \in \mathbb{R}^{N}: \quad \mathcal{R}^{h}(\boldsymbol{u})=\emptyset
\label{2.11}
\end{equation}

\subsubsection{Systems of Equations}

大多数多物理问题都涉及到描述超过一个单一标量变量的模型。作为一个例子，考虑纳维-斯托克斯方程，它根据速度 $\boldsymbol{v}$ 和压力 $p$ 来描述流体的运动。

\begin{equation} 
\nabla \cdot(2 \mu \mathbb{D}(\boldsymbol{v})-p \mathrm{I})+\rho \boldsymbol{v} \cdot \nabla \boldsymbol{v} =f \text { in } \Omega 
\label{2.12a}
\end{equation}

\begin{equation} 
\nabla \cdot \boldsymbol{v} =0 \text { in } \Omega 
\label{2.12b}
\end{equation}

\begin{equation} 
\mathbb{T}(\boldsymbol{v}) \cdot \boldsymbol{n} =\boldsymbol{j}_{N} \text { on } \Gamma_{N} \subset \partial \Omega
\label{2.12c}
\end{equation}

这里，$\mu$表示流体的粘度，$\rho$表示流体的密度。  $f$是重力等外力，$\mathbb{D}=\frac{1}{2}\left(\nabla \boldsymbol{v}+(\nabla \boldsymbol{v})^{T}\right)$是对称变形张量，$\mathbb{T}=2 \mu \mathrm{D}(\boldsymbol{v})-p \mathrm{I}$是应力张量。

这个问题的结构比标量对流-扩散反应方程更复杂。

- 这个问题的函数空间$U$是矢量值的。有$d$速度分量$v_{i}$以及压力$p$，所以$U$有$d+1$分量。让我们首先考虑速度：为了更好地阅读，我们用总速度$\boldsymbol{v}$来写方程，因此想把$\boldsymbol{v}$看作一个单一的、矢量值的变量。相应的速度函数空间$\boldsymbol{V}$可以构造为每个速度分量的标量函数空间的笛卡尔乘积。

\begin{equation}
\boldsymbol{V}=V_{1} \times V_{2} \times \cdots \times V_{d}, \quad V_{i} \subseteq H^{1}(\Omega), i=1, \ldots, d
\end{equation}

为了定义整体解空间，我们重复这个构造过程，用一个额外的压力空间$P$定义$U=\boldsymbol{V} \times P$。这个例子暗示了多成分函数空间的一般构造原则。我们可以通过递归地将基本函数空间组合成更大的单元来构建它们，而不是将它们视为单一的、任意复杂的空间，从而形成一种计算机科学家会直接识别为树的结构。正如我们将在后面的章节中看到的，这个构造原则是`PDELAB`和我们的多域扩展的核心。

- 方程\ref{2.12a}和方程\ref{2.12b}给出了问题中$d+1$标量变量的$d+1$标量方程。我们对这些方程的处理方式与标量情况相同，用适当标量变量的测试空间的测试函数相乘，必要时对结果进行部分积分。之后，我们将所得的方程汇总成一个完整系统的单一残差$r$，即成为

\begin{equation}
\begin{aligned}
r((\boldsymbol{v}, p),(\boldsymbol{w}, q))=& \int_{\Omega}(p \mathbb{I}-2 \mu \mathbb{D}(\boldsymbol{v})) \cdot \nabla \boldsymbol{w} \mathrm{d} x+\int_{\Omega}(\rho \boldsymbol{v} \cdot \nabla \boldsymbol{v}) \cdot \boldsymbol{w} \mathrm{d} x \\
&-\int_{\Omega} \boldsymbol{f} \cdot \boldsymbol{w} \mathrm{d} x+\int_{\Gamma_{N}} \boldsymbol{j} \cdot \boldsymbol{w} \mathrm{d} s+\int_{\Omega}(\nabla \cdot \boldsymbol{v}) q \mathrm{~d} x
\end{aligned}
\end{equation}

注意，由于对流项$\rho \boldsymbol{v} \cdot \nabla \boldsymbol{v}$，残差形式不再是线性的$\boldsymbol{v}$。

最后，我们再次得出了一个形式为方程\ref{2.8}的问题。

\begin{equation}
\text { Find }(\boldsymbol{v}, p) \in \boldsymbol{V} \times P: \quad r((\boldsymbol{v}, p),(\boldsymbol{w}, q))=0 \quad \forall(\boldsymbol{w}, q) \in \boldsymbol{V} \times P
\end{equation}

我们可以看到，残差公式自然延伸到方程组。此外，它还允许统一处理线性和非线性问题（尽管非线性问题当然需要像牛顿方案那样的非线性求解器）。

\subsubsection{Instationary Problems}

到目前为止，我们只考虑了时间无关的方程，但现实世界的大部分应用都是关于不稳定的问题。作为一个例子，我们将对流-扩散-反应方程\ref{2.2}扩展到时间区间$\left(t_{0}, t_{0}+T\right)$的暂态模型。

\begin{equation} 
\begin{aligned}
\partial_{t} u-\nabla \cdot(\boldsymbol{D} \nabla u)+\boldsymbol{b} \cdot \nabla u+c u &=f \text { in } \Omega \times \Sigma=\left(t_{0}, t_{0}+T\right) \\
u &=g \text { on } \Gamma_{D} \\
-\nabla u \cdot \boldsymbol{n} &=j \text { on } \Gamma_{N} \\
u\left(\cdot, t_{0}\right) &=u_{0} \text { at } t=t_{0}
\end{aligned}
\label{2.13}
\end{equation}

这里的主要区别是实际中的时间导数 PDE 和初始条件 $u_{0}$ 。瞬时问题的解决通常是将问题分成空间和时间部分，然后将时间部分作为空间DOF的ODE系统来处理。该问题的弱形式是由

\begin{equation}
\begin{aligned}
\frac{d}{d t} \int_{\Omega} u v \mathrm{~d} x+\int_{\Omega}(\boldsymbol{D} \nabla u) \cdot \nabla v &+(\boldsymbol{b} \cdot \nabla u) v+c u v \mathrm{~d} x \\
&-\int_{\Omega} f v \mathrm{~d} x+\int_{\Gamma_{N}} j v \mathrm{~d} s=0 \quad \forall v \in V, t \in \Sigma
\end{aligned}
\end{equation}

我们引入了第二种残差形式$m(u, v ; t)=\int_{\Omega} u v \mathrm{~d} x$，并将问题在空间上离散化。然后，该问题可以被投射到我们既定的残差公式的一个略微扩展的版本中。

\begin{equation} 
\text { Find } u^{h}(\boldsymbol{x}, t) \in V^{h}: \frac{d}{d t} m^{h}\left(u^{h}, v^{h} ; t\right)+r^{h}\left(u^{h}, v^{h} ; t\right)=0 \quad \forall v^{h} \in V^{h}, t \in \Sigma
\label{2.14}
\end{equation}

这里，$m^{h}\left(u^{h}, v^{h} ; t\right)$代表问题的时间部分；注意，$r^{h}$从静止问题方程\ref{2.6}中延续下来，没有改变。将方程\ref{2.14}进行时间离散后，我们可以对问题应用`ODE`积分器。以隐式欧拉方法为例，我们用$t^{n-1}<t^{n}, n=1, \ldots, N$选取一串时间点$\left(t^{0}=t_{0}, t^{1}, \ldots, t^{N}=t_{0}+T\right)$，并设置$k^{n}=t^{n+1}-t^{n}$ 。用后向差分法对方程\ref{2.14}中的时间导数进行逼近，然后得到一序列完全离散的问题

\begin{equation}
\text { Find } \boldsymbol{u}^{n+1} \in \mathbb{R}^{N}: \frac{\mathcal{M}\left(\boldsymbol{u}^{n+1} ; t^{n+1}\right)-\mathcal{M}\left(\boldsymbol{u}^{n} ; t^{n}\right)}{t^{n+1}-t^{n}}+\mathcal{R}\left(\boldsymbol{u}^{n+1} ; t^{n+1}\right)=\emptyset
\end{equation}

其中$\mathcal{R}$的定义与方程\ref{2.10}相同，$\mathcal{M}$由$m^{h}\left(u^{h}, v^{h} ; t\right)$以等价方式构建。因此，我们必须在每个时间步骤中找到一个（非）线性代数问题的解决方案。这种方法自然延伸到高阶一步法以及显式时间积分器，但我们的介绍仅限于隐式欧拉方案的这个简单例子，并请读者参考[^63] [^64]，以了解ODE数值解的详细情况。

除了额外的时间依赖性，$\mathcal{R}$与静止问题的残差算子方程\ref{2.2}相同，虽然我们现在需要两个残差算子，但$\mathcal{M}$的结构与原始残差相同；这在实施层面将很重要，我们可以使用相同的软件基础设施来组装$\mathcal{R}$和$\mathcal{M}$ 。

\subsubsection{Solution of the Algebraic Problem}

给定一个形式为方程\ref{2.11}的代数问题，我们可以应用以下基于阻尼牛顿方法的求解算法。我们从一个初始猜测$\boldsymbol{u}^{0} \in U$开始，该猜测的选择方式应该是满足任何强加的`Dirichlet`边界条件。然后我们计算$\boldsymbol{r}^{0}=\mathcal{R}\left(\boldsymbol{u}^{0}\right)$并设定$k=0$ 。随后，重复以下步骤直到收敛（即$\left\|\boldsymbol{r}_{k}\right\|<\epsilon$ ）。

1.组建雅各布矩阵 $\boldsymbol{A}^{k}=\nabla \mathcal{R}\left(\boldsymbol{u}^{k}\right)$ 。

2.用一些线性求解器求解$\boldsymbol{A}^{k} \boldsymbol{z}^{k}=\boldsymbol{r}^{k}$。

3.用$\sigma \in(0,1]$更新$\boldsymbol{u}^{k+1}=\boldsymbol{u}^{k}-\sigma^{k} \boldsymbol{z}^{k}$ 。

4.计算新的残差 $\boldsymbol{r}^{k+1}=\mathcal{R}\left(\boldsymbol{u}^{k+1}\right)$ 。

5.设置 $k=k+1$ .

我们可以看到，我们需要一种方法来计算残差向量 $\mathcal{R}(\boldsymbol{u})$ 和雅各布矩阵 $\nabla \mathcal{R}(\boldsymbol{u})$ 。这两个量的计算被称为问题装配；我们的论文提出了一个软件框架，用于在第四章介绍的更复杂的多域问题的情况下进行这种装配。请注意，对于像对流-扩散-反应这样的线性问题，雅各布矩阵$\nabla \mathcal{R}(\boldsymbol{u})$是一个常数；因此，牛顿方法将在一次迭代中收敛，使这个解决程序对线性和非线性问题都可行。

\subsection{The Finite Element Method}

到目前为止，我们还没有对有限维空间$V$的性质做出任何假设。选择这个空间的方法有很多，但在这项工作中，我们专注于有限元（`FE`）方法，我们认为这意味着一般`Petrov-Galerkin`类型的方法，包括经典连续`FE`、有限体积和不连续Galerkin（`DG`）离散。

在所有这些方法中，空间域被网格离散化，解决方案被构建为一个分片多项式函数的总和，每个函数都有少量网格单元的支持。这种小的支持导致Jacobian$\nabla \mathcal{R}(\boldsymbol{u})$中的绝大多数项消失（因为涉及的测试和试验基础函数不重叠）。因此，由`FEM`离散化产生的线性系统往往是极其稀疏的。

\subsubsection{Tessellation of the Spatial Domain}

为了构建一个离散的函数空间，空间域$\Omega$必须被离散化。虽然有其他的可能性，但这通常是通过创建一个棋盘格（也叫网格或网格）来完成的。定义2.1（镶嵌法）。让$\Omega$成为$\mathbb{R}^{d}$中的一个域。那么一个镶嵌体是$a$有界域$T_{i}$的有限集合，称为单元（或元素），形成$\Omega$的不相交分区。

\begin{equation}
\bar{\Omega}=\bigcup_{i=1}^{N} \bar{T}_{i}, \quad T_{i} \cap T_{j}=\emptyset \forall i \neq j
\end{equation}

我们用$h(T)=\operatorname{diam} T=\max _{x, y \in \bar{T}}\|x-y\|$定义单元格$h(T)$的大小，用$h=\max _{T \in \mathcal{T}} h(T)$定义网格大小$h$ 。

对于一个一维领域$\Omega=(a, b)$，这相当于找到合适的点

\begin{equation}
a=x_{0}<x_{1}<\cdots<x_{N}=b
\end{equation}

并让

\begin{equation}
\mathcal{T}=\left\{\left(x_{i-1}, x_{i}\right): i=1, \ldots, N\right\}
\end{equation}

在更高的维度上，网格通常使用单体或立方体（矩形、六面体）构建。当$\Omega$不是多边形形状时，这就带来了额外的挑战。在这种情况下，嵌片只能是实际领域的一个近似值。对于复杂的几何形状和异质PDEs来说，找到一个好的网格是一个非常具有挑战性的问题，关于这个问题的全面概述可以在Ern和Guermond[^48]中找到；在下文中，我们将始终假设已经找到了适合我们问题的棋盘格$\mathcal{T}$。

\subsubsection{Finite Element Spaces}

随着时间的推移，不同的问题设置导致了各种各样的有限元空间的产生。在本节中，我们提供两个著名的`FE`族的例子。请注意，还有很多其他重要的空间，它们保证了弱解的不同属性，如Nedelec元素，它在$H_{\text {curl }}$中（即其梯度的切向分量是跨元素边界连续的）和Raviart-Thomas元素，它可用于必须构成$H_{\mathrm{div}}$的一部分的解（跨元素边界的梯度连续法向分量）。一个非常全面的概述可以在 [^82] 中找到。

$P_{k}$空间可能是最著名的连续$\mathrm{`FE`}$空间类型，即保证整体解决方案将在$\mathcal{C}^{0}$中的空间。它们需要一个简单的网格，其定义为

\begin{equation} 
P_{k}(\mathcal{T})=\left\{u \in C^{0}(\bar{\Omega}):\left.u\right|_{\bar{T}} \in \mathbb{P}_{k}^{d} \forall T \in \mathcal{T}\right\}
\label{2.15}
\end{equation}

其中$\mathbb{P}_{k}^{d}$表示$\mathbb{R}^{d}$中最多为$k$度的多项式的空间。通常情况下，它们由拉格朗日基表示。除了是正交的，这样的基还具有产生节点基的有益特性，这使得通过简单地评估节点上的数据来插值成为可能。$1 \mathrm{D} P_{1}$空间的一个例子显示在[Fig2.1]。它的构造可以以一种通用的方式扩展到更高的多项式阶数和维度（参见 [^16] ）。根据相关节点的位置，节点基础中的每个基础函数可以与一个独特的网格实体（顶点、单元、面、边等）相关联。在计算机实现中，相应的`DOF`是利用这种关系来存储的。

对于六面体网格也存在类似的空间系列；它们用$Q_{k}$表示，与$P_{k}$空间略有不同，它们的构造需要一个参考元素（参见[^16]）。

\textbf{Finite Volume spaces}属于一类不同的函数空间，称为破碎的Sobolev空间。与上面介绍的更 "标准 "的`FE`空间一样，这些空间由各个网格单元上的分片函数组成，但与之不同的是，它们不包含每个单元函数之间的任何内在耦合。  [Fig2.2]描述了这种空间的基础函数，用于一个简单的$1 \mathrm{D}$设置。如果每单元的解不是常数，而是多项式，则相应的空间被称为不连续的Galerkin（`DG`）空间。基于这些破碎空间的方法需要通过单元格-单元格交点的附加残差项来确保全局解的一致性；这些附加积分对解（和/或其导数）的跳跃进行惩罚，以加强解的全局连续性，但只是在弱意义上这样做。不连续空间和相应的方法非常适用于包含不连续特征的问题，如数据中的冲击或不连续现象。


\subsubsection{Reference Elements and Local Function Spaces}

`FE`基函数通常以网格单元$T$的参考元素$\hat{\Omega}_{T}$来定义，例如，$2 \mathrm{D}$中的单位正方形或单位三角形。对于每个网格单元$T$，网格管理器提供一个几何变换$\mu_{T}: \hat{\Omega}_{T} \rightarrow \Omega_{T}$，从参考元素映射到网格单元的实际几何。为了在位置$\boldsymbol{x}$评估一个标量函数$u^{h} \in U^{h}$，我们必须将世界空间坐标$\boldsymbol{x}$映射到每个网格单元的参考空间，然后我们可以在（特定单元）参考空间坐标$\hat{\boldsymbol{x}}$评估基础函数。考虑到这些转换，$u^{h}$的基数表示成为

\begin{equation}
u^{h}(\boldsymbol{x})=\sum_{T \in \mathcal{T}} \sum_{l=0}^{n(T)-1} \boldsymbol{u}_{g(T, l)} \hat{\varphi}_{T, l}\left(\mu_{T}^{-1}(\boldsymbol{x})\right)
\end{equation}

这里，$n(T)$表示在$T, \hat{\varphi}_{T, l}$上的基函数的数量，表示这些基函数中以参考元素$\hat{\Omega}_{T}$定义的第$l$个，$g(T, l)$将该基函数的局部索引$l$映射到全局系数向量$\boldsymbol{u}$中相应条目的索引。

如这个例子所示，与一个函数$u^{h}$（由其`DOF`向量$\boldsymbol{u})$和底层函数空间$U^{h}$表示）的交互通常是通过在网格单元上迭代并对与每个网格单元相关的基函数进行一些操作来完成的。我们可以通过引入一个只包含那些局部活跃的基函数的局部函数空间$U_{T}$和相应的限制系数空间$\boldsymbol{U}_{T}=\mathbb{R}^{n(T)}$来简化这些单元的操作。为了在全局和局部空间之间进行映射，我们引入一个限制算子 $R_{T}$ ，即

\begin{equation} 
R_{T}: \boldsymbol{U}=\mathbb{R}^{N} \rightarrow \boldsymbol{U}_{T}, \quad\left(R_{T}(\boldsymbol{u})\right)_{i}=(\boldsymbol{u})_{g(T, i)}, i=0, \ldots, n(T)-1
\label{2.16}
\end{equation}

\subsubsection{Evaluation of Element-wise Residual Contributions}

考虑到这种分割为单元格局部操作和网格迭代，我们可以以类似于基数表示的方式重写残差运算符$\mathcal{R}$式\ref{2.10}为

\begin{equation} 
\mathcal{R}(\boldsymbol{u})=\sum_{T \in \mathcal{T}} R_{T}^{T} \alpha_{T}^{\mathrm{vol}}\left(R_{T} \boldsymbol{u}\right)+\sum_{\tau \in \partial \mathcal{T}} R_{\tau}^{T} \alpha_{\tau}^{\mathrm{bnd}}\left(R_{\tau} \boldsymbol{u}\right)
\label{2.17}
\end{equation}



其中算子$\alpha_{T}^{\text {vol }}: \boldsymbol{U}_{T} \rightarrow \boldsymbol{U}_{T}$及其对应项$\alpha_{\tau}^{\text {bnd }}$分别编码实际PDE的弱形式，即把弱形式限制在当前单元$T$或边界交点$\tau$后包含的积分。连续`FE`方法只需要这里显示的体积和边界项；`DG`方案包含邻近网格单元之间的内部交点的额外$\alpha^{\text {skel }}$项。

回顾基本的平流-扩散-反应问题，其弱形式方程\ref{2.4}的体积积分部分对应于以下的体积积分核，为了简单起见，我们假定$\boldsymbol{b}=0$（即无对流）。

\begin{equation}
\begin{aligned}
\left(\alpha_{T}^{\mathrm{vol}}\left(u_{T}\right)\right)=& \int_{\hat{\Omega}_{T}}\left\{\left[\sum_{l=0}^{n(T)-1}\left(u_{T}\right)_{l}\left(\boldsymbol{D} \nabla \mu_{T}(\hat{\boldsymbol{x}})\right)^{-T} \nabla_{\hat{x}} \hat{\varphi}_{T, l}(\hat{\boldsymbol{x}})\right] \cdot \mu_{T}(\hat{\boldsymbol{x}})\right)^{-T} \nabla_{\hat{x}} \hat{\varphi}_{T, m}(\hat{\boldsymbol{x}}) \\
&\left.+c\left[\sum_{l=0}^{n(T)-1}\left(u_{T}\right)_{l} \hat{\varphi}_{T, l}(\hat{\boldsymbol{x}})\right] \hat{\varphi}_{T, m}(\hat{\boldsymbol{x}})-f \hat{\varphi}_{T, m}(\hat{\boldsymbol{x}})\right\} \operatorname{det} \nabla \mu_{T}(\hat{\boldsymbol{x}}) \mathrm{d} \hat{\boldsymbol{x}}
\end{aligned}
\end{equation}

通常情况下，这个积分将使用参考元素的正交规则进行数值计算。请注意，除了要求它是连续的之外，积分核对基础`FE`空间不做任何假设。只要数值积分阶数选择得足够高，这个内核对任意的$P_{k}$和$Q_{k}$空间都有效。

为了应用第2.1.6节所述的求解程序，我们还需要组装雅各布矩阵。这个过程与残差装配非常相似；它的主要区别在于，它采用了一组计算局部矩阵贡献的核子，而不是各种$\alpha$函数。关于详细的概述，我们参考了 [^101] 。

\subsubsection{Finite Element Assembly}

如上所示，一个PDE的离散化弱形式由网格单元上的积分和单元与其他单元/域边界的交点组成。为了数值计算这个残差（通常称为形式集合），我们必须为方程\ref{2.17}设计一个算法。这个过程是任何`FEM`框架的核心部分；因此，有大量的具有不同特征的实现，并基于弱式的不同变体[^10] [^11] [^12] [^68] [^115] 。由于我们的工作是基于 `PDELAB` 框架，我们快速重述其版本的算法，如 [^101] 中所述。

算法2.1给出了`PDELAB`中装配过程的高层概述：它直接对应于方程(2.17)，并以网格单元(第3行)和--在第二层--与每个网格单元相关的交点(第9行)的嵌套迭代方式实现。它唯一不同的是，它进一步将积分核分成取决于解$\alpha$的部分$\boldsymbol{u}$和常数部分$\lambda$，这是一个优化细节，使我们能够避免构建限制性的算法2.1--有限元剩余组合。特定问题的（用户提供的）构件用黑体字突出显示。


解$u_{T}$在某些情况下，通过数值微分加快雅各布矩阵的计算，我们可以跳过常数项。

我们论文中的大部分贡献都是由这个算法中的一些操作和被操作对象直接激发的。算法的布局已经表明，只有元素和交点的局部积分（指定为$\alpha$和$\lambda$的各种函数）是完全取决于问题的。算法的其余部分始终保持不变：它执行了一些操作，这些操作只用非常一般的术语描述，如 "限制试验空间"。同样，像函数空间和限制/延长算子这样的对象的确切性质也没有在算法中指定。  `PDELAB`包含了这个算法的一个非常通用的实现，我们扩展了这个实现以应对多领域模拟的具体挑战。

- 我们想要模拟的大多数多物理和多领域问题都是PDEs系统，因此相应的函数空间是积空间。为了提供一种机制来递归地组成这种函数空间，我们在第五章中介绍了一个新的不相关类型的树库。

- 复杂的结构化问题要求全局索引集的布局有很大的灵活性 $\mathcal{I}_{U^{h}}$ 。在实践中，这种布局是由局部到全局的地图决定的 $g$ 。在第6章中，我们提出了一个框架，该框架从底层函数树的树状结构中构建这种地图，并支持非常灵活的条目排序，以及通过多索引支持块状结构的容器。

- 在多域问题的情况下，算法比这里显示的更复杂；不同的积分必须在空间域的不同部分进行计算。在第三章中，我们介绍了一种机制，使我们有可能将现有的网格分割成子域来描述这些部分。最后，第四章描述了一个基于该机制的软件框架，该框架在很大程度上实现了将函数空间和残差形式的子问题特定项映射到这些子域的过程自动化。

\subsection{The DUNE Framework}

本论文介绍的软件实现是基于分布式统一数值环境（ `DUNE` ） [^18] [^19] ，这是一个用 $\mathrm{C}++$ 编写的用于开发基于网格的模拟的主要开源软件框架。它利用了一些先进的 $\mathrm{C}++$ 编程技术，并围绕以下设计理念。

\textbf{Flexibility} `DUNE`是基于一些详细的接口规范；任何建立在这些接口之上的程序都可以很容易地交换底层实现，而不需要改变用户代码。这方面最重要的例子是网格接口，它允许用户通过简单地改变少量的类型定义来切换例如从结构化网格到非结构化网格或从$2 \mathrm{D}$到$3 \mathrm{D}$计算。

\textbf{Extensibility} 一个`DUNE`项目是由独立的模块组成的。这些模块可以提供库和/或可执行程序；一个模块可以依赖任何数量的其他模块。通过明确地将框架的不同部分（基础设施、网格接口、线性代数......）分离成不同的模块，交换某些功能和尝试不同的模拟方法就变得更加容易。

\textbf{Efficiency} 当应用于现实世界的问题时，数值模拟需要大量的计算能力。因此，软件性能是最重要的，这个目标往往需要高度专业化的实现。  `DUNE`尽管其重点是灵活性，但还是设法实现了高效率。

为了实现这些目标，`DUNE`依赖于$\mathrm{C}++$的模板功能，它在编译时解决了所有的接口选择（网格实现、函数空间等）；结合广泛的函数内联，这使得$\mathrm{C}++$编译器能够为用户指定的参数集生成最佳代码。

\subsubsection{Components}

`DUNE`模块分为两个基本类别之一：核心模块，为创建基于网格的模拟提供基本功能，并且（大部分）是创建基于`DUNE`的程序所必需的；以及附加模块，提供额外的功能（大部分是更高的抽象级别）。

核心模块的开发过程要严格得多，由一个核心开发人员团队策划。这些模块有一个共同的、协调的发布时间表（目前的目标是每年发布一个主要版本），并提供一定的`API`稳定性（例如，当前的功能通常只在至少一个主要发布周期内被废弃后才被修改或删除）。目前，有5个核心模块。

\textbf{dune-common}包含所有`DUNE`模块使用的基本基础设施，如构建系统、密集向量和矩阵，以及对基于`MPI`的并行计算的支持。

\textbf{dune-geometry}为`FEM`模拟所需的几何基元和操作提供支持。这包括参考元素、几何变换和正交规则，以及支持即时创建参考元素细化的基础设施。

\textbf{dune-grid}包含了抽象的网格接口`API`以及一个独立的并行结构化的网格实现和一些预先存在的网格管理器的适配器，以及提供网格输入/输出（`I/O`）（网格工厂、文件解析器、Visualization ToolKit（ `VTK` ）输出）和提供建立在基本`API`之上的额外功能的额外基础设施组件。最后，这个模块包含一个元网格（在下一节中解释），可以用来修改现有`DUNE`网格的几何形状。

\textbf{dune-istl} ，迭代求解器模板库，是稀疏线性代数的向量、矩阵、算法和求解器的集合。 dune-localfunctions定义了有限元的`API` （插值、评估、`DOF` 映射），并包含许多常见元素的实现，如$P_{k}, Q_{k}$, `DG`, Brezzi-Douglas-Marini, Rannacher-Turek, Raviart-Thomas, Whitney和单项式基函数（正则和非正则）。

虽然核心模块提供了建立`FEM`仿真的所有必要组件，但这样做仍然需要用户为函数空间和`DOF`处理、约束条件处理、矩阵/残差装配和问题解决（并行和非线性求解器、时间步进等）等任务编写大量的基础设施。目前有三个不同的高级框架提供这种功能（至少是部分），它们目前正由`DUNE`核心团队的不同成员开发。

\textbf{dune-fem} [^34]为创建`FEM`模拟提供抽象（如函数空间、函数、线性运算符）。它的实现主要集中在支持局部适应性和非结构化网格上的负载平衡的并行计算。它可以从 [^41] 获得。

\textbf{dune-fufem}，根据其维护者（Gräser, Sack, and Sander [^57]和私人通信）的说法，主要关注的是易用性而不是性能（例如它对动态多态性的广泛使用，在大多数其他`DUNE`模块中基本没有）。但它并没有被广泛使用，而是源于柏林自由大学的内部开发工作。

\textbf{PDELab} [^15]是一个高度抽象的软件框架，用于解决基于PDE的问题，这些问题可以被投进残差公式。它提供了大量的空间和时间离散性，具有强大的组合机制的函数空间，用于多变量的问题，线性和非线性问题的求解器后端，可用于重叠和非重叠的并行网格分解，以及显式和隐式时间步长方法的时间积分器。它是根据GPL2和LGPL3许可的，有一个运行时间例外；进一步的信息见[^102]。  `PDELAB`已成功用于实现各种`FE`类型的离散化，包括连续`FEM`、有限体积法（`FVM`）、有限差分法（`FDM`）和`DG`方案。

还有越来越多的额外的网格实现，其中大部分是作为开源的`DUNE`模块提供。例子包括一个$2 \mathrm{D}$n-D网格[^42]，一个角点网格[^109]以及一个替代的结构化网格[^98][^43]和越来越多的元网格，这些都将在下一节描述。

与核心模块一样，上面提到的所有扩展模块都在开源许可证下免费提供。

\subsubsection{Grid Interface}

`DUNE`最重要的一个方面是它对网格的非常详细的数学定义和它的程序化界面。在下文中，我们将介绍该界面的一些关键要素，这些要素在下面的章节中很重要；完整的概述可以在 [^18] [^19] 中找到。

我们认为网格$\mathcal{T}$是一个实体的容器：单元、顶点、边等。这些实体可以以多种方式进行分组；在下文中，我们将使用代码维度和参考元素的标准。网格实体$c_{\tau}=d_{\mathcal{T}}-d_{\tau}$的维度$\tau^{()}$是一种方便的方式，可以以独立于维度的方式识别实体的功能：例如，维度为0的实体总是一个网格单元，与网格的维度无关。代码维度是创建独立于维度的代码的一个重要工具。参考元素（描述实体的几何形状）是一个更精细的标准，因为一个给定维度可以有多个参考元素，例如在$2 \mathrm{D}$中，单元格$(c=0)$可以进一步细分为三角形和四边形。这种分类例如在处理混合网格时很重要，即包含不同形状的单元的网格。一个函数空间必须为网格单元$T$返回不同的有限元，这取决于$T$是一个三角形还是一个四边形。

我们假设一个网格为每个二维$c$提供单独的实体集$E_{c}$ 。此外，让$E_{\mathrm{RE}}$为所有具有参考元素$\mathrm{RE}$的实体集。那么网格提供了一个双射图$\mathcal{I}_{\mathcal{T}}^{\mathrm{RE}}: E_{\mathrm{RE}} \rightarrow\left\{0, \ldots,\left|E_{\mathrm{RE}}\right|-1\right\}$ ，我们称之为索引图。在前进方向上评估这个索引图是非常便宜的，并且提供了将数据附加到网格实体的主要手段；通常，一个算法将把数据放在大小为$\left|E_{\mathrm{RE}}\right|$的连续数组中，并通过$\mathcal{I}_{\mathcal{T}}^{\mathrm{RE}}$查询数组中的正确偏移。

网格中的实体可以通过迭代器接口访问，允许迭代给定维度的所有实体。此外，还可以迭代单个网格单元与相邻单元和/或网格外部边界的所有交点。由于`DUNE`网格支持$h$悬空节点的adaptivity，这些交叉点不一定与网格边缘重合（即一个维数为1的实体）。

\textbf{Meta Grids}

网格接口足够精细，允许在现有的`DUNE`网格实现的基础上实现一个新的网格，创建一个所谓的元网格。元网格是一个强大的机制，可以增强或修改现有网格的功能，即时为各种不同的网格管理员提供新的功能，而不需要了解他们的实现，因为任何授权都是通过通用的网格接口进行的。作为本论文的一部分，我们开发了一个元网格，为现有的`DUNE`网格实现增加了对子域的支持（参见第三章）。

\subsubsection{PDELab}

`PDELAB` [102, 101]提供了一些高级抽象，以便在`DUNE`网格之上建立PDE求解器。作为这项工作的一部分而开发的多域仿真软件是作为`PDELAB`框架的一组扩展来实现的，因此重用了许多这些抽象以及相关术语。为此，我们对`PDELAB`模拟的典型构件以及它们的`API`进行了简要概述。

\textbf{Remark} 2.1.  `DUNE`将其所有的实现放在命名空间Dune::中，而`PDELAB` 的特定功能可以在命名空间`Dune::PDELab::`中找到。为了提高本节代码示例的可读性，我们省略了这两个命名空间的前缀，并假定它们已经通过适当的using指令被导入。

最基本的`PDELAB`概念之一是函数空间，它是由`GridView`（`DUNE`网格的只读视图）、FiniteElementMap（将特定的有限元与每个网格单元联系起来）、约束引擎（用于汇集受约束的DOF的信息）和矢量后端标签（用于选择和参数化用于`DOF`矢量的矢量实现）所定义。一般来说，一个函数空间是这样创建的。


请注意，由于库的静态类型和模板性质，`PDELAB`组件通常是由`typedef`来填写组件模板参数的组合，然后是新定义类型的实例化。

约束空间不直接存储其约束，而是使用外部约束容器来实现这一目的，该容器必须手动创建并填充实际约束。对于具有`Dirichlet`约束的连续Galerkin空间的常见情况，这可以通过以下方式实现


约束参数可以携带约束组件所需的额外信息；在这里，它们被用来确定某个位置的边界类型（`Dirichlet` vs. `Neumann`）。

用户编写的特定问题的代码主要是关于单元和交叉限制的残差和雅各布项的实现。这些都被封装在一个必须符合Localoperator概念的类中。由于这部分模拟是对残差形式的直接翻译，因此通常由模拟开发者提供，但`PDELAB`也包括一些常见问题的实现，例如泊松方程的算子。


为了保持`DUNE`在很大程度上与维度无关的性质，大多数局部算子将适用于任何网格维度，这使得它很容易，例如用一个简单的$2 \mathrm{D}$例子进行方法开发，然后在以后切换到全三维应用。它们通常也不需要特定的函数空间；上面显示的泊松算子将适用于任何类型的连续函数空间，只需要用户指定一个足够高的正交阶数。局部算子包含一些局部内核，这些内核被`PDELAB`用来设置稀疏矩阵模式并组装残差或其Jacobian（参见2.2.5节）。这些内核被实现为Localoperator的回调方法，例如用于$\alpha^{\text {vol }}$项。


注意必须设置的额外标志；它在编译时由`PDELAB`评估，并使其有可能通过针对活动内核集而优化全局装配（算法2.1）。如果用户不想提供雅各布系数的明确实现，`PDELAB`可以通过对残差进行数值微分来自动计算它。

用户提供的Localoperator只包含局部残差贡献的实现；正如第2.2.5节所解释的，装配算法的其余部分主要是与问题无关的性质，可以由框架以通用方式提供。在`PDELAB`中，这项工作是由网格运算器完成的，它将局部运算器应用于一对（可能是受限的）答案空间和测试函数空间。此外，它还处理雅各布矩阵的创建，这些矩阵通常以稀疏的格式存储。稀疏模式取决于网格拓扑结构和离散化方案；因此，矩阵的创建需要通过网格遍历来提取这些信息并设置模式。矩阵的确切格式（块状结构等）可以再次通过`Grid0perator`参数来选择。恢复我们上面的例子，我们可以通过以下方式定义网格运算器


使用网格算子，我们现在可以很容易地创建向量和矩阵，并计算残差和jacobian，以反馈给代数求解器。


`PDELAB`还包含解决组装代数问题的组件（顺序和并行线性求解器、预处理器、牛顿求解器），以及隐式和显式方法的时间积分器和将解决方案导出到`VTK`文件的工具，但由于我们的重点是问题组装，所以我们在此省略对该功能的描述。

\subsection{Advanced C++ Programming Techniques}

`DUNE`库是用$\mathrm{C}++ [^72][^73] ;$编写的，它们主要依靠语言的模板机制来创建抽象的组件和算法，可以很容易地以许多不同的方式组合。

\subsubsection{Template Programming}

模板编程在这方面比传统的面向对象的多态性要强大得多。模板允许用户组合任意的对象，而不在它们之间强加任何一种语言层面的关系；相反，对象只需要符合一组特定的要求（在$\mathrm{C}++)$中通常称为概念，就可以使用模板化的组件或算法。这种行为的一个很好的例子是作为$\mathrm{C}++$标准模板库（`STL`）一部分的容器。假设我们有一个代表离散函数空间的自定义类`GridFunctionSpace`，想用一个`std::vector`来存储这些空间的集合。为了做到这一点，我们为我们的`GridFunctionSpace`类型实例化了一个特殊版本的`std::vector`模板。

这将导致编译器生成一个特殊版本的`std::vector`，该版本只为存储`GridFunctionSpace`对象而量身定做，而不是其他。以同样的方式，我们可以创建自定义版本的容器来存储整数、双数或任何其他内置或用户定义的类型的对象。同时，`std::vector`对所含对象的类型提出了一些要求。例如，上面代码片段中的最后一个操作将gfs的副本添加到向量的末端，这个操作要求gfs是可复制的。$\mathrm{C}++$标准在$[\mathrm{C}++/ 23.2 .1 / 4]$和$[\mathrm{C}++/ 23.2 .3 / 3]$中列出了所有`std::vector`操作对存储类型的要求，这突出了这种方法的主要缺点。模板类和它的参数之间的契约是完全隐含的；对参数的要求不能作为代码的一部分来表达，而必须在其他地方进行记录。如果你试图将一个模板和一个不兼容的参数结合起来，编译器通常会产生一个看起来与实际问题完全无关的错误信息（例如，参数类上缺少一个方法定义）。由于缺乏适当的工具，模板编程表现出非常陡峭的学习曲线，需要对各个组件之间的隐含契约有深入的了解，这导致大多数程序员在程序中避免大量使用模板。然而，在科学软件的背景下，该技术近年来被广泛采用，主要是因为它为编译器提供了大量的类型信息，使其能够进行积极的优化，并产生在速度上可与手工调整的FORTRAN实现相媲美的代码，同时也更容易维护。在第5章中，我们已经量化了这些编译器优化的效果，特别是函数内联，对`PDELAB`的性能的影响。

\textbf{Template Specialization}

在某些情况下，理论上一个模板可以与一组给定的模板参数一起工作，但是默认版本的模板实现与给定的模板参数值不兼容或导致次优代码。在这种情况下，C++允许程序员为模板参数的特定值提供一个不同的实现。这是通过一种叫做模板专业化的机制实现的；大多数$\mathrm{C}++$程序员已经通过`std::vector` $<$ bool $>$的专业化实现接触到了它：一个布尔变量占用了1个字节（即8位）的内存，使得默认的向量实现对于大量的布尔集合非常浪费。出于这个原因，$\mathrm{C}++$标准要求对`std::vector` $<$ bool $>$进行专业化处理，压缩布尔数据，对每个存储的布尔值只使用一个比特。

如果我们要专门化的参数本身就是一个模板，那么模板的专门化就会变得更加复杂；在这种情况下，我们必须求助于部分专门化。


\textbf{Tag Dispatching}

虽然部分专业化是编写通用$\mathrm{C}++$代码的一个有价值的工具，但上面的例子突出了该技术的一个主要缺点。我们的部分专业化必须为`std::vector`的所有模板参数提供值。另一方面，许多程序员甚至不知道它的第二个参数，一个分配器，可以用来控制向量如何分配内存。在有许多模板类的大型$\mathrm{C}++$项目中，这种完全匹配模板参数签名的要求成为一个主要的维护负担：每当一个类的模板参数列表改变时，我们必须更新该模板类的所有部分特殊化；忘记一个特殊化会导致编译失败。

标签调度是一种减少这种努力的机制。它的工作原理是将模板类与一个简单的标签类型联系起来，这个标签类型标识了模板本身，而不是任何具体的实例。作为一个例子，考虑一下 `PDELAB` 中的网格函数空间。





有了这些标签，算法和数据结构现在可以使用标签而不是实际的模板类进行专业化，因此不必再担心它们的模板参数。为了使用标签，它必须作为模板参数出现；为此，依赖于标签调度的模板通常分两个阶段工作。在第一步中，标签被提取并提升为模板参数。例如，如果我们想写一个VTKExporter类，用于将一个网格函数写入`VTK`文件，我们可以将这个步骤嵌入该类的非专门声明中。


然后，不同类型的函数空间的专门化只在固定的标签类型上专门化，不对实际的函数空间对象的模板参数做任何假设，大大减少了这些组件之间的耦合。


\subsubsection{Template Meta Programming}

C++模板元程序（TMP）利用类型系统，特别是模板机制，生成在编译时执行的程序清单 $2.1 \longrightarrow$ 简单模板元程序来计算阶乘，而不是在运行时执行。这项技术是在$\mathrm{C}++$标准化委员会[^118]的一次会议上发现的，并在[^120]中得到充分发展。清单$2.1$显示了一个非常简单的`TMP`的例子，它计算了一个整数的阶乘。

一个`TMP`是通过实例化模板来调用的；然后模板参数在标准（运行时）算法中承担了函数参数的作用。模板元编程与命令式语言（如$\mathrm{C}$或 "普通"$\mathrm{C}++$）中的标准编程有很大区别。

- 首先，也是最重要的，正如已经说过的，程序是由编译器执行的。在优化的背景下，这是一个非常重要的特征，因为它允许我们将部分计算工作从执行阶段转移到编译阶段；如果程序被多次运行或在一次执行过程中多次进行计算，这就特别有效。

- 对`TMP`来说，只有$C++$类型和积分常数是可用的，因为模板参数必须属于这些类别之一。

- 模板元编程属于函数式程序的范畴；变量是不可变的（类型和积分常量都不能被修改），程序由一系列简单的表达式组成，不能有任何副作用。

在整个工作中，我们主要依靠TMP来创建抽象的、通用的算法，这些算法对未知类型的复杂的复合对象（例如函数空间的树）进行操作；与动态多态性相比，编译器可以在编译时解决所有的代码路径，从而可以将通用算法变成高度优化的代码。这种代码的运行速度通常几乎与传统的$\mathrm{C}$或FORTRAN代码一样快，这些代码已经针对一组特定的数据结构进行了手工优化。

\subsubsection{Improved Language Support in $\mathrm{C}++11$ }。

$\mathrm{C}++$中的模板元编程并不是被设计成语言的一部分的功能；正如上面已经确定的，该技术是由Unruh [^118] 在1995年偶然发现的。然而，它的威力很快被社区所认识，并成为深入研究的主题[^120] [^119] [^3]，甚至证明$\mathrm{C}++$中的模板机制形成了在编译器中执行的图灵完全语言$-$。

尽管模板元编程取得了成功，但这种技术显然受到了它 "滥用"$\mathrm{C}++$的影响，做了一些语言设计时没有考虑的事情。这种语言层面的支持的缺乏造成了一些特异现象，比如模板元函数实际上是在嵌套类型中 "返回 "其值的结构，或者不得不求助于晦涩的语言属性，比如Substitution Failure Is Not An Error（`SFINAE`），这是一个更强大（但也更难理解）的模板专业化的替代方案。综合来看，这些特异性使得`TMP`代码对于没有该领域经验的程序员来说非常难以理解。此外，在功能上也存在着重要的差距。这些硬性限制中最明显的可能是将算术限制在整数类型，以及模板参数列表必须是固定长度的要求。

由于模板元编程慢慢地开始被认为是$\mathrm{C}++$的优势之一，国际标准组织（`ISO`）标准化委员会在对该语言进行$\mathrm{C}++11$修订的工作中，增加了一些新的语言特性，简化了模板元程序的编写。在下文中，我们强调其中一些在本工作范围内特别重要的特性。

\textbf{Variadic Templates}

在$\mathrm{C}++03$中为不同类型的可变数量的元素（例如元组）实现容器时，主要的挑战之一是缺乏具有可变参数数量的模板。为了解决这个限制，实现者必须为数据结构可以接受的参数数量选择一个固定的上限，并以这个最大的参数数量来设计接口；然后可以通过将模板参数默认为一个特殊的标记类型来实现较短的变体，标志着一个空参数，并相应地对数据结构进行专业化。





虽然这种模式确实有效，但它有很大的缺点，因为它导致了大量的代码重复（记住，各个变体的实际主体都需要完全拼出来），而且必须小心翼翼地将修改和错误修复应用于所有的专业化。正如Gregor和Järvi所指出的 [^59] ，由此产生的代码很难维护，导致开发者求助于预处理器元编程 [^77] 。

相比之下，变体模板通过向语言添加模板参数包，允许任意数量的模板参数。有了这些包，我们的元组类型就变成了


而我们不再需要任何特殊化。此外，原始版本中所有未使用的模板参数会产生非常长的类型签名，这反过来又会给编译器带来严重的性能问题。在[^77]中介绍的例子中（TR1中的元组库），编译时间随着允许的最大参数数而呈指数级增长，作者将其与基于变体模板的重新实现进行了对比，后者完全避免了这些限制，同时性能优于原始版本，即使后者被限制在最大三个参数。

变体模板仍然是$\mathrm{C}++$工具箱中相当高级的工具；变体模板的参数包不能直接访问，而必须在算法中解包，通过部分专业化或函数重载的方式递归剥离参数包中的各个元素。例如，一个接受任意数量的参数并将每个参数打印到控制台的函数可以这样实现。


虽然这种编程风格需要一些时间来适应，但对于实现`TYPETREE`库的更多高级功能来说，变量模板已经是一个非常宝贵的工具。

\textbf{Type Deduction With auto and decltype}

$\mathrm{C}++03$缺乏一种机制来捕获函数或漏斗的返回类型，这使得创建类型无关的、可堆叠的漏斗变得复杂，这在表达式模板和类似结构的设计中非常重要。为了解决这个问题，我们设计了一个库级协议来获取这个信息。


然后，否定函数的用户将不得不调用result_of元函数来确定其返回类型，它需要知道这一类型以存储返回值。虽然这个协议是可行的，并且多年来一直被用于主要的框架中（例如`Boost` MPL [^61]），但它非常脆弱，因为程序员必须注意不要忘记result_of元函数，这在集成第三方代码时可能会有问题。C $++11$在新的`decltype`关键字和新的函数声明语法的帮助下，大大简化了这种类型的通用函数器的创建。使用这些特性（和r值引用以确保完美的转发，这是原始实现的另一个缺陷），上述例子变成了


在更平凡的层面上，新的auto关键字允许程序员省略新声明的变量的类型，如果它的类型可以从初始化表达式中推导出来。请看下面的例子。


一般来说，自动使用户代码中的绝大多数显式类型声明变得多余，大大提高了代码的可读性，特别是当这些类型必须从嵌套在其他变量中的类型定义中提取出来时。

虽然auto可以用来自动推断一个新声明的变量的类型，但它的对应物`decltype`使得将推断出的类型存储在`typedef`中或将其用作模板参数成为可能，这一特性在上面的例子中被使用，它构成了第五章中介绍的模板元函数的新型静态多态性的基础。


\section{Conforming Subdomains for DUNE Grids}

为了模拟像第一章中提到的那些多域问题（`FSI`，斯托克斯-`Darcy`耦合等），我们必须在我们的应用程序中为这些空间子域管理单独的网格。我们还需要一种有效的方法来计算这些子域之间的重叠和交叉点（正如我们以后所看到的，在我们整个多物理模型中的各个子问题通过这些交叉点的积分进行耦合）。对这个问题有两种基本的方法。

- 我们可以对每个子域进行单独离散。这样一来，我们就能够根据基本问题对每个网格进行优化调整。此外，我们有可能为子问题重新使用现有的软件包，即使这些软件包使用不兼容的数据格式。另一方面，为了计算这些不相关的网格之间的交叉点，以及在子域之间传输空间数据（如解决方案），这种方法引入了很多额外的复杂性。尽管有这些挑战，这个架构还是被这个领域所有成熟的软件框架所使用，例如[^20] [^45] [^47] [^89]，主要是由于提供的灵活性和能够用一个主要的黑盒子方法整合现有软件。

- 另外，我们可以从整个（多域）模拟的单一全局网格开始，然后通过在整个网格中标记适用的单元来指定子域。这主要是颠倒了其他策略的优点和缺点。仿真的所有部分都必须基于一个共同的网格数据结构，而在子问题重叠的区域，我们无法为每个子问题挑选出最合适的单独的魔方。这种方法的主要优点是大大降低了软件的复杂性（就子问题的耦合而言），并有可能在代数水平上研究松散耦合和单片求解器。

通过这篇论文，我们的目标是简化对各种问题类别的新数值解决方案的开发和研究。为此，我们选择了第二种策略，并创建了子域感知网格管理器`MultiDomainGrid`，构成了我们多域框架的基础。

在这一章中，我们描述了这个网格管理器的功能（作为一个`DUNE`模块实现），并概述了它的实现。特别是，我们强调了它的性能特点，并提出了一种通过模块化的后端引擎使网格适应特定问题的方法。本章的信息基于并扩展了我们之前在 [^96] 的工作。

\textbf{Remark} 3.1.实现本章介绍的网格管理器的$\mathrm{C}++$类位于命名空间Dune::mdgrid中。为了提高本章代码片段的可读性，我们将省略这个命名空间，并假设Dune和Dune::mdgrid这两个命名空间已经被导入了适当的using声明。

\subsection{Introduction}

`MultiDomainGrid` 是作为 `DUNE-GRID` 的附加模块开发的，可以在 `DUNE` 模块 `DUNE-MULTIDOMAINGRID` 中找到。它是自由软件，在与`DUNE`核心模块相同的许可证下提供（GNU通用公共许可证，有一个特殊的运行时间例外，进一步的细节见[^40] ）。除了`DUNE-GRID`和它的依赖关系外，还需要安装`Boost`C++库[^114]。特别是，该代码使用了`Boost`包MTL和Fusion。

下面的描述是基于版本 $2.3 .1$ 的库，可以从 [^93] 下载或直接从源代码库 [^94] 下载。它需要相应的2.3.1版本的`DUNE`核心模块。

\subsubsection{Overview}

`DUNE-MULTIDOMAINGRID`的功能和基本设计原理显示在 [Fig3.1] 中。该模块由两个合作的元网格实现，`MultiDomainGrid`和`SubDomainGrid`：`MultiDomainGrid`包装了一个现有的主机网格，并通过设置和访问子域的接口来扩展它。它还存储了管理子域所需的所有数据。各个子域以`SubDomainGrid`实例的形式暴露出来，它是非常轻量级的对象，结合了主机网格和相关`MultiDomainGrid`的信息。它们将一个子域呈现为一个普通的`DUNE`网格。在



为了区分不同的子域，它们被分配到$[0, N-1]$的集合中，其中$N$表示网格支持的最大子域数。

一个`MultiDomainGrid`保留了底层网格的所有能力，包括对$h$ -adaptivity和`MPI`并行的完全支持，如果这些是由主机网格提供的。

\subsubsection{Grid Creation}

作为元网格方法的典型，多域功能是通过将其包裹在一个`MultiDomainGrid`中添加到现有的网格中，随后在所有进一步的计算中取代主机网格。


这里，第二个模板参数是一个策略类，可以用来为不同的应用场景定制内部数据结构和算法。现在，可以在三种不同的策略中进行挑选，这在第3.2.2节中有详细解释。这里使用的策略是针对只有少量子域的情况而优化的，我们感兴趣的应用就是这样。支持的子域的最大数量由参数 `maxSubDomains` 控制。最后，可以禁用对分层网格结构的支持。如果一个模拟只使用最精细的投影，即LeafGridView（就像大多数没有$h$ -adaptivity的模拟一样），这种优化可以大大减少网格的内存需求。





在创建了`MultiDomainGrid`之后，就不应该再直接访问主机网格了。特别是，绕过元网格来执行任何一种修改宿主网格的操作，都很可能导致未定义的行为。

\subsubsection{Subdomain Setup}

子域是通过简单地将网格单元分配给它们而隐式创建的。为此，`MultiDomainGrid`提供了一个与现有的`DUNE`界面非常相似的界面，用于网格的适应。子域总是通过 `MultiDomainGrid` 来控制，它包含新的 `API` 。请注意，修改子域布局和细化网格是相互排斥的过程；当两者中的一个正在进行时，另一个不能启动。

`DUNE`网格实际上可以包含一个网格层次结构，并允许用户单独访问这些层次，以实现$h$-基于几何多网格的自适应代码和求解器。在`MultiDomainGrid`中，一个子域总是包括网格的所有层次；它是通过在叶子网格单元上标记属于子域的单元来定义的。之后，这一信息会在网格层次结构中向上传播，但不会再向下传播。由于这个原因，子域单元的细化子单元不一定形成父单元的完整分区。子域成员资格的跨级传播在[Fig3.2]中得到了证明，它显示了由叶子网格上的两组标记单元构建的两个子域网格。这个插图也解释了为什么有时不可能使一个单元的所有子域成为子域的一部分。由于最粗的网格只由一个单元组成，这样做会导致两个子域的增长，并最终包含整个主网格，使得在这个主网格上不可能创建任何非琐碎的子域。

只要没有其他网格修改（负载平衡、网格适应等）同时发生，子域就可以在任何时候根据仿真的需要改变其形状。回顾介绍中的两相/单相流例子，当注入的流体开始扩散时，这种能力使我们能够连续地扩展两相流区域。

子域标记过程的编程接口由少量的`MultiDomainGrid`方法组成。在我们开始标记任何单元格之前，必须通过调用`md_grid.beginSubDomainMarking()`来启动这个过程，它检查一些先决条件并设置必要的数据结构来同时管理新旧子域布局。之后，我们可以遍历叶子网格并修改子域结构。


这些方法的用法应该是不言自明的。一旦新的布局被完全建立起来，实际切换到该布局的过程分三个阶段进行。这允许用户备份存储在从子域中移除的单元格中的数据，并在单元格被新添加到子域中时提供初始数据。这个过程与`DUNE`网格界面处理网格适应的方式类似。

- `md_grid.preUpdateSubDomains()`将子域成员信息传播到所有子域和水平网格中。它还会根据新的子域布局重建索引图。在这一步之后，用户应该在旧的和新的子域状态之间做任何需要的数据投射。

- 随后对`md_grid.updateSubDomains()`的调用会激活新的子域布局，但会保留所有关于旧状态的信息，以便进一步的用户数据传输。

- 一旦所有数据传输完成，通过调用`md_grid.postUpdateSubDomains()`，旧布局的记账信息将被删除。

\subsubsection{Subdomain Usage}

如前所述，子域可以作为只读的`DUNE`网格对象被访问。它们支持完整的`DUNE`接口，但像网格适应或负载平衡这样的变异操作除外。请注意，`SubDomainGrids`不能被复制，因此必须通过引用从`MultiDomainGrid`中获得。

之后，这些`SubDomainGrids`可以像其他`DUNE`网格管理器一样使用。

\textbf{Inter-grid Data Transfer}

任何局限于单个子域的操作都可以使用相应的`SubDomainGrid`以完全透明的方式进行，我们在后面章节中介绍的更高层次的框架就是利用这一特点在子域上定义函数空间，并支持像解决方案输出这样的操作。然而，在某些情况下，有必要访问元网格层次结构的不同层次（例如，访问主机网格）。为此，两个元网格类都提供了元网格和宿主网格实体之间的转换方法，如清单3.1所示。对于交叉点也有一套相应的方法，为了简洁起见，我们在此略去。

然而，多域框架的表格组装基础设施采取了不同的方法。它几乎只在全局`MultiDomainGrid`上操作，直接查询网格实体所属的子域。这些信息被编码在一个`SubDomainSet`中。例如，给定叶子网格上的一个顶点对象顶点，其子域集的访问方式是


`SubDomainSet`是我们库中的一个中心数据结构（参见3.2节）。顾名思义，它存储了一组子域（由其索引标识），并包含一个全面的`API`来获取其状态信息并对该组进行修改。下面的代码片段演示了如何使用该接口的一些重要部分（我们只限于观察者接口，因为子域集不应该被用户修改）。


集合的实际实现取决于创建`MultiDomainGrid`时选择的策略；根据策略的不同，确切的实现也可能因为效率的原因而在不同的代码维度上有所不同。为了与`DUNE`对稳定、抽象接口的关注保持一致，确切的实现并不影响上面显示的公共`API`。

\subsubsection{Subdomain Interface Extraction}

计算子域相互作用的算法必须穿越这些子域之间的接口。我们使用SubDomainInterface类来表示这样一个接口的元素，它与标准的`DUNE`交叉点相似，但提供了两个额外的方法



访问两个特定子域之间的接口。给定两个子域 $s_{1}$ 和 $s_{2}$ ，这个迭代器将访问实体 $e_{i}$ 和 $e_{j}$ 之间的所有交点，其中 $e_{i}$ 包含在 $s_{1}$ 中，但不包含在 $s_{2}$ 中，而且 $e_{j}$ 包含在 $s_{2}$ 中，但不包含在 $s_{1}$ 中。这些迭代器可以用网格方法得到


注意，为了简洁起见，省略了水平视图迭代器的等效方法。

\textbf{Visiting all subdomain interfaces in the grid} .如果一个应用程序需要在多个子域接口上进行迭代，上述迭代器的效率并不高，因为每一对子域都需要对底层主机网格进行全面的遍历。另一方面，我们可以有效地计算出一个给定的网格交点所属的子域界面集合。给定两个网格单元$e_{1}$和$e_{2}$的交集，分别属于子域$S_{1}$和$S_{2}$的集合，让$D_{1}=S_{1} \backslash S_{2}$和$D_{2}=S_{2} \backslash S_{1}$ 。那么，所有子域交集的集合由张量积$D_{1} \times D_{2}$给出。正如第3.2.2节所概述的，每个实体的子域集合通常以积分类型的比特集的形式存储，使得这些集合操作非常高效。

基于这种算法的高效迭代方案，访问`MultiDomaingrid `的所有子域界面，在从网格方法得到的迭代器中得到实现


和它们的水平视图的对应物。这些迭代器访问`MultiDomainGrid`中包含的成对子域之间的所有接口，并且只需要一次主机网格遍历。如果一个单元格-单元格交集属于多个子域接口，迭代器将对每个接口返回一次，并对`subDomainInInside() ` and ` subDomainIn0utside()`取不同值。因此，以这样一种方式来表述应用级算法通常是非常有益的，即所有子域-子域相关的任务都可以使用这些迭代器来并行处理，特别是对于与完整域相比数量较少的子域，会产生性能上的优势。

\subsection{Implementation}

与`DUNE`中的元网格相关的一个主要问题是与底层网格周围的包装层相关的开销。虽然`DUNE`的网格界面大量使用了$\mathrm{C}++$的模板编程，并且其设计方式可以被一个好的编译器优化掉，但它在设计时并没有考虑到创建元网格的可能性。因此，在封装器中必须发生一定量的信息重复，使得 "堆叠 "几个元网格的成本很高。

请注意，`DUNE`核心库的下一个版本将包含对网格界面的修改，在很大程度上缓解了这个问题。

\subsubsection{Design}

`DUNE-MULTIDOMAINGRID`的设计和实现受到`DUNE-SUBGRID`[^58]模块的启发，但对那里所展示的概念进行了显著的扩展，因为该模块是为不同的目的（几何多网格应用）而建立的，并且只能跟踪主机网格中的单个子域。  然而，`DUNE-SUBGRID`是第一个`DUNE`元网格，证明了这个概念的可行性。网格接口可以通过转发用户的方法调用来代理宿主网格的所有不变的功能。这样一来



元网格实现者可以将他们的努力集中在他们想要添加到主机网格的额外功能上，并将这些功能与不同的`DUNE`网格实现的巨大差异相结合。

由于单个子域只是代表了主机网格中所包含的实体的一个子集，因此对于子域追踪来说，唯一需要改变的功能是网格界面中与主机网格的实体复合体有关的部分。这包括实体和交叉点迭代器、实体计数和索引图，但不包括ID图，因为主机网格生成的ID对每个子域来说仍然是唯一的，可以重复使用。实现任何这些功能都需要一个子域集地图$\sigma: E \rightarrow \mathcal{S}$，它将每个实体$e \in E$与它所属于的子域集$s \subseteq S$联系起来。这里，$S$是所有可能的子域的集合，$\mathcal{S}:=\mathcal{P}(S)$表示所有可能的子域组合的集合。在实现层面上，$\sigma$是通过为`MultiDomainGrid`的每个实体存储一个`SubDomainSet`的实例来实现。网格接口使得通过附加的索引集（参见第2.3.2节）将数据附加到`GridView`中的实体（基本上是水平网格或叶子投影）成为可能，这些索引集为每种类型的网格实体提供连续的索引范围。使用这些索引图，我们可以在平面数组中讲述每个实体的子域信息，允许有效的数据访问。

因此，子域成员信息与IndexSet组件紧密相连。因此，我们将这些信息作为`MultiDomainGrid`索引集的一部分来存储，它包裹了底层主机网格的索引集。此外，Indexset也是`DUNE`网格接口中少数几个可以很容易地用额外的用户可见的接口方法进行扩展的位置之一；大多数网格组件必须被包裹在一个门面层中，这个门面层只暴露官方规定的API，按照网格接口规范，这种限制很难绕过。虽然可以为子域设计一些不同的存储方案，但`MultiDomainGrid`实现的方案在[Fig3.3]中得到了描述。二维和几何类型的两个容器是由于网格界面的索引图是根据几何类型（基本上是网格实体的参考元素）定义的，即几何类型$g t$的实体的索引图$I_{g t}$是一个地图$E_{g t} \rightarrow\left[0,\left|E_{g t}\right|-1\right]$ 。因此，有必要为网格中包含的每个几何体类型创建一个不同的数据结构。这个数据结构是一个图元数组$(s, i)$，其中$s$表示实体所属的子域集合，$i$代表一个索引。$i$的确切含义取决于$s:$的cardinality，如果$|s|=1$，它代表实体在它所属的单个子域中的索引。否则，它指的是第二个数组中的一个条目，该数组包含一个$\operatorname{map} \lambda_{e}: s \rightarrow I$。

\subsubsection{Storage Backends}

上述存储策略背后的算法目前是固定的，但还是可以通过为几个地图和集合容器选择不同的实现来影响它。特别是，可以指定 [Fig3.3] 中深蓝色阴影的容器类型。现在，在创建 `MultiDomainGrid` 时有三种预定义的策略可以挑选。

- FewSubDomainsTraits。该策略针对常规的多物理场问题进行了优化。它允许多达64个子域，这些子域可以以任意方式重叠。有了这个策略，实体子域成员测试和子域实体索引的速度非常快，而且复杂度为$\mathcal{O}(1)$。64的硬性限制是由于子域集被存储为一个积分类型中的位掩码的事实。

- ArrayBasedTraits。一种为大量子域设计的替代策略。它对子域的最大数量没有任何内在的限制，但是限制了一个实体可以属于的子域的数量。子域成员测试和子域索引查询都需要对一个包含本地子域集合的排序数组进行单一的二进制搜索，因此复杂度为$\mathcal{O}\left(\log \left|s_{e}\right|\right)$ 。请注意，虽然这个策略允许任意多的子域，但用户仍然需要选择一个每个网格的最大值，这个最大值作为模板参数传递给traits类。网格需要知道这个数字，因为它必须为实体计数等子域特定的数据分配存储。

- DynamicSubDomainCountTraits。这个策略是ArrayBasedTraits的一个稍微灵活的版本，只要求用户在运行时指定最大的子域数量。然而，这种额外的灵活性是以小的性能损失为代价的。网格现在必须分配动态内存来存储每个子域的信息，相对于ArrayBasedTraits来说，它引入了一个额外的指针指示，它将这些信息存储在静态分配的数组中。

\subsubsection{Efficiency}

子域的实体和交叉点迭代器是在主机迭代器的基础上实现的。它们总是在完整的主机网格上进行迭代，并简单地跳过不包含在子域中的实体。此外，它们对位于`MultiDomaingrid `内部但位于子域外部边界的交叉点修改了交叉点类型。这种实现策略使子域的迭代时间与整个域的大小成线性关系，尽管有一个小常数。虽然这在对非常小的子域进行迭代时可能会产生一些开销，但这个问题大多可以通过将子域迭代凝聚到对底层`MultiDomainGrid`的一次传递中来避免。这种方法在`DUNE-MULTIDOMAIN`中采取，我们的`PDELAB`扩展模块基于`MultiDomainGrid`，其中只有网格`I/O`操作采用低效迭代模式。

网格适应是由实现透明地处理的。可以在`MultiDomainGrid`和任何`SubDomainGrid`上放置细化标记，但实际的网格转换只能由`MultiDomainGrid`发起，这个选择是为了明确强调只有一个主机网格，因此，细化一个子域也会影响其他重叠的子域。

为了评估`MultiDomainGrid`的运行时间和内存开销，我们从`PDELAB`中抽取了一个简单的例子程序，该程序使用`Dirichlet`和`Neumann`的混合边界在单位面积上求解$2 \mathrm{D}$中的泊松方程。这个程序被修改为直接在主机网格上、在`MultiDomainGrid`上或在横跨整个域的`SubDomainGrid`上运行。这样，所有三个程序变体都以相同的方式解决了完全相同的问题，这使得我们可以很好地评估分别通过包裹主机网格和使用定义在子域上的网格所带来的开销。我们使用结构化的（YaspGrid）和非结构化的（`ALUSimplexGrid `）主机网格运行该基准，以研究我们的模块在这两种类型的网格上是否表现出不同的行为。基准的网格是通过从覆盖单位面积的单个正方形（或者在`ALUSimplexGrid `的情况下是两个三角形）开始生成的，并迭代完善这些宏观网格。所有的结果都是通过在硬件配置B.1上运行模拟10次，并对单个运行得到的数字进行平均而得到的。

我们通过对几个标准的`PDELAB`操作进行计时来评估我们的网格的运行开销，这些操作都涉及到网格迭代，但每个网格单元的计算量不同，从非常快速的网格函数空间设置到通过数值不同的方式评估雅各布系数。比较的结果可以在 [Fig3.1] 中找到。这些结果清楚地显示了一个明显的性能开销，这可能会通过进一步优化包装器的实现而减少。特别是，基于`SubDomainGrid`的变体表现出不成比例的运行时间增长，这既与它被实现为堆叠在`MultiDomainGrid`之上的第二个元网格有关，也与迭代期间需要额外的子域成员检查有关。总的来说，对于简单和快速的操作来说，性能上的损失更为明显，这使得当前状态下的网格更适合于每个单元涉及到中等到大的计算工作量的数值方案。

`MultiDomainGrid`的额外内存需求在[Fig3.4]中进行了说明。程序的内存用量是在网格创建后（对于主机网格）和创建子域后（对于 `MultiDomainGrid` ）直接测量的。我们的网格在用于结构化网格之上时，必然会改变内存的使用特性（结构化网格使用相同的内存，与网格大小无关）。这是可以预期的--我们提供的子域不一定是结构化的，所以我们失去了被结构化主机网格利用的优化机会。另一方面，非结构化网格在内存需求方面的扩展行为保持不变（基本上与网格实体的数量成线性关系）。不幸的是，所需的额外内存总量仍然相当可观。为了缓解这个问题，可以通过两种方式降低`MultiDomainGrid`的存储要求。

\textbf{移除对未使用的维数的支持}. 例如，有限体积或非连续Galerkin离散化只需要代码维度为0的实体。在这种情况下，可以选择性地停用`SubDomainGrids`中未使用的实体（边、面）。试图在`SubDomainGrid`中访问这样的实体将导致编译时出错。`MultiDomaingrid `中的相应实体仍然可以被使用，但网格将不再跟踪其子域成员。注意，`SubDomainGrid`必须始终包含单元和顶点。这种优化可以通过在 `MultiDomainGrid` 的策略类上设置一个模板参数来启用。

\textbf{停用水平网格视图}。非自适应代码通常只访问一个网格的叶子网格视图。如果一个程序从不访问子域的层次网格视图，那么在创建 `MultiDomainGrid` 时可以在运行时移除对它们的支持。根据主机网格所包含的级别数量，这种优化可能会产生大量的内存使用量，但即使对于一个完全没有精炼的网格，我们也能够大约将内存需求减少一半。正如在[Fig3.4]中可以看到的那样，停用层级网格视图可以将非结构化网格的内存开销减少到一个基本可以忽略不计的程度。

这些优化主要影响模块的内存需求，并不真正影响正常网格操作中的运行时间性能。然而，它们确实减少了改变子域布局后重建子域信息的时间，因为需要跟踪和分配每个子域索引的实体较少。对模块运行效率的实质性改进需要对`DUNE`网格界面进行修改。不幸的是，其中一些修改不能以向后兼容的方式进行，但最近`DUNE`的开发者同意将其作为一个主要的新版本的一部分引入，将允许打破向后兼容。

在我们的多域框架中，我们能够回避大部分的性能问题。它们大多与`SubDomainGrids`有关，正如我们将在第七章中看到的，所有对性能至关重要的操作都直接在`MultiDomainGrid`之上实现；`SubDomainGrids`只用于少数任务，如 "I/O"，在那里性能不是一个问题。





\section{Mathematical Framework for General Multi Physics Problems}

在这一章中，我们为多域问题的定义定义了一个数学上的严格框架，并介绍了我们的软件库`DUNE-MULTIDOMAIN`，它通过扩展PDE求解器工具箱`PDELAB`实现了这个数学框架。在通过几个实例问题介绍了问题设置及其相关挑战之后，我们首先重申了`PDELAB`从组件空间递归合成复杂函数空间的基本原则，然后介绍了多领域的具体扩展，如支持只定义在整个模拟领域的一部分的函数空间和残差。在整个章节中，我们将提到最初的例子问题，并展示它们如何映射到我们框架的各个组成部分，在数学层面上以及通过代码实例展示我们实现的一般用法。

这个框架的目的是为描述离散残差（和`Jacobians`）提供一个精确的符号，我们希望在一组给定的相关网格上装配相关的离散函数空间。因此，我们的框架局限于对离散问题的描述。虽然完全有可能扩展其抽象以描述连续水平的问题，但所需的努力超出了这项工作的范围，最好放在更多侧重于数学分析的理论论文中。

我们的框架依赖于`DUNE-MULTIDOMAINGRID`对域的空间离散化和对子域的划分，所以偶尔会提到`MultiDomainGrid`的具体术语，如前一章介绍的子域集。

\textbf{Remark} 4.1.和前几章一样，我们在代码示例中部分省略了我们框架的类和函数的命名空间范围；具体来说，我们省略了 `Dune::` 和 `Dune::PDELab::prefixes` 。请注意，本章介绍的所有新功能都生活在命名空间`Dune::PDELab::MultiDomain`中，因此可以通过前缀`MultiDomain::`识别。

\subsection{Introduction}

`DUNE-MULTIDOMAIN`是作为`PDELAB`的附加模块开发的，与该软件目前的$2.0$发布分支兼容。此外，它依赖于`DUNE-MULTIDOMAINGRID`的子域信息，与它的$2.3$发布系列兼容。  `DUNE-MULTIDOMAIN`是自由软件，在与`DUNE`核心模块相同的许可证下提供（GNU通用公共许可证，有一个特殊的运行时间例外，进一步的细节见[^40]）。

下面的描述是基于版本 $2.0.1$ 的库，可以从 [^91] 下载或直接从源代码库 [^92] 下载。它需要 $2.3.1$ 版本的 `DUNE` 核心模块。

\subsection{Problems with Multiple Variables}

一个多物理问题通常涉及不止一个感兴趣的量--通常，我们对多个变量（如压力、浓度、速度......）感兴趣，因此解空间$U$将是矢量值的。为了我们的目的，我们假设$U$总是可以写成基本空间的张量乘积，因此对于一个函数$u=\left(u_{1}, u_{2}, \ldots, u_{n}\right) \in U$，空间可以写为

\begin{equation} 
U=U_{1} \times U_{2} \times \cdots \times U_{n}
\label{4.1}
\end{equation}

与$U_{i}=\mathcal{S}\left(\Omega_{i}\right)$相关开域上的一些Sobolev空间$\Omega_{i} \subset \mathbb{R}^{d}$ 。请注意，这个定义允许每个变量都有一个不同的空间域。此外，这些基本空间$U_{i}$中的每一个仍然可以是矢量或张量值的，例如对于$H($curl$)$或$H$（div）空间。

在下文中，我们将介绍一些典型的多领域问题，并讨论我们的装配框架需要什么样的功能来支持这些问题。之后，我们给出了框架的高层次概述；在许多地方，它的界面和设计选择将由回溯到例子问题的例子和代码片断来激发。

\subsubsection{Two Domain Poisson Problem}

我们从一个简单的两域泊松问题开始，在不重叠的子域上，界面上有`Dirichlet` - `Neumann` 耦合条件 $\Gamma_{C}$ ，外部边界上有同质 `Dirichlet` 边界条件，如 [Fig4.1] 所描述。



\begin{equation} 
\begin{aligned}
-\Delta u_{i} &=f \quad \text { in } \Omega_{i}, \quad i \in\{L, R\} \\
u_{i} &=0 \quad \text { on } \partial \Omega_{i} \backslash \Gamma_{C} \\
u_{L} &=u_{R} \quad \text { at } \Gamma_{C}, \\
\left(\nabla u_{L}\right) \cdot \boldsymbol{n} &=\left(\nabla U_{R}\right) \cdot \boldsymbol{n} \quad \text { at } \Gamma_{C}
\end{aligned}
\label{4.2}
\end{equation}

其中$f$表示一个源/汇项。请注意，这个问题等同于组合域$\Omega=\Omega_{L} \cup \Omega_{R} \cup \Gamma_{C}:$上的标准泊松问题。

\begin{equation} 
\begin{aligned}
-\Delta u &=f & & \text { in } \Omega \\
u &=0 & & \text { on } \partial \Omega
\end{aligned}
\label{4.3}
\end{equation}

这种非常简化的设置已经使我们能够提取出我们的装配框架必须支持的一些特征。

- 应该可以对两个领域使用不同的离散化方案，例如将连续Galerkin离散化与`DG`方案相结合。

- 我们希望能够支持在耦合界面上不符合要求的网格$\Gamma_{C}$ 。

- 这个问题显然分成了两个子问题，同时还有耦合项。因此，我们希望能够对整个问题应用一个全局的、单一的求解器，或者采用一个在两个子问题之间迭代的方案，例如`Dirichlet`-`Neumann`迭代。

如果我们假设每个子域采用连续的Galerkin方法，并对$\Omega_{L}$和$\Omega_{R}$分别采用网格$\mathcal{T}_{L}$和$\mathcal{T}_{R}$，则整个问题的离散残差$r$变为

\begin{equation} 
r\left(\left(u_{L}, u_{R}\right),\left(v_{L}, v_{R}\right)\right)=\sum_{T \in \mathcal{T}_{L}} \int_{T} \nabla u_{L} \cdot \nabla v_{L} \mathrm{~d} x+\sum_{T \in \mathcal{T}_{L}} \int_{T} f v_{L} \mathrm{~d} x 
\label{4.4a}
\end{equation}

\begin{equation} 
+\sum_{T \in \mathcal{T}_{R}} \int_{T} \nabla u_{R} \cdot \nabla v_{R} \mathrm{~d} x+\sum_{T \in \mathcal{T}_{R}} \int_{T} f v_{R} \mathrm{~d} x 
\label{4.4b}
\end{equation}

\begin{equation} 
+\sum_{\tau^{(c)} \in \mathcal{T}_{\Gamma_{C}}} \int_{\tau^{(c)}} [u][v] \mathrm{d} s 
\label{4.4c}
\end{equation}

\begin{equation} 
+\sum_{\tau^{(c)} \in \mathcal{T}_{\Gamma_{C}}} \int_{\tau^{(c)}} [\nabla u] \cdot[\nabla v] \mathrm{d} s
\label{4.4d}
\end{equation}

其中$[u]=u_{L}-u_{R}$表示一个函数在一个单元交点上的跳跃，$\{u\}=\frac{1}{2}\left(u_{L}+u_{R}\right)$表示平均值。这里，方程\ref{4.4a}和方程\ref{4.4b}对应于每个子域上的单个泊松问题（有相关的网格$\left.\mathcal{T}_{i}\right)$，而方程\ref{4.4c}和方程\ref{4.4d}提供这两个问题的耦合。注意额外的网格$\mathcal{T}_{\Gamma_{C}}:$ 一般来说，生成这个网格并计算其与子域网格的拓扑关系被证明是主要的实施挑战之一。在我们的设置中，我们依靠`SubDomainGrid`来提供这一关键信息，这使我们能够利用主机网格的已知全球拓扑结构。

\subsubsection{Stokes-`Darcy` Flow}

虽然上面介绍的简单的泊松-泊松问题作为理解多域模拟的基本数学和实施问题的载体效果很好，但现实世界的应用通常会更加复杂，因为它们在每个子域中都有不同的变量和方程。作为这样一个问题的例子，我们考虑自由流域和多孔介质中的固定耦合流动问题。  [Fig4.2]展示了这样一个问题的例子，以及使用我们的框架计算的解决方案。

这个问题中的自由流动是由标准的（Navier-）Stokes方程描述的，它需要单独的变量$\boldsymbol{v}$来描述速度和$p$来描述流体压力。在下文中，我们忽略了对流成分，只限于斯托克斯流，这使整个问题保持线性，如果我们只考虑蠕动流，这是一个可以接受的简化。对于这个例子，我们遵循 [^29] 中的表述。

\begin{equation}
\begin{aligned}
\nabla \cdot(2 \mu \mathbb{D}(\boldsymbol{v})-p \mathbb{I}) &=\boldsymbol{f}_{S} \text { in } \Omega_{S} \\
\nabla \cdot \boldsymbol{v} &=0 \text { in } \Omega_{S} \\
\boldsymbol{v} &=\boldsymbol{g}_{S, D} \text { on } \partial \Omega_{S} \backslash \Gamma_{C}
\end{aligned}
\end{equation}

这里，$\mu$是流体的粘度，$\boldsymbol{f}_{S}$表示可能的外力如重力，$\mathbb{D}=\frac{1}{2}\left(\nabla \boldsymbol{v}+(\nabla \boldsymbol{v})^{T}\right)$是对称变形张量，$\mathbb{T}=2 \mu \mathbb{D}(\boldsymbol{v})-p \mathbb{I}$是


应力张量。为了简单起见，我们假设外域边界的所有`Dirichlet`边界条件。

对于多孔介质，我们不解决孔隙尺度的问题，而是使用一个以水力压头$\phi=\frac{p}{\varrho g}+z$为单一主要变量的`Darcy`流动的放大、同质化模型。在这个模型中，流动总是发生在$\phi$的负梯度方向上，并与它的绝对值成正比。

\begin{equation}
\begin{aligned}
-\nabla \cdot(\boldsymbol{K} \nabla \phi) &=f_{D} \text { in } \Omega_{D} \\
\phi &=g_{D, D} \text { on } \partial \Omega_{D} \backslash \Gamma_{C}
\end{aligned}
\end{equation}

其中$\boldsymbol{K}$表示渗透率张量，$f_{D}$表示可能的源项。水力压头$\phi$大多与按密度归一化的流体压力相同，但考虑到了重力的影响。

这两个领域通常由Beavers和Joseph [^21] 在实验中得出的一组条件耦合。它们后来被Saffman [^111]和Jones [^75]简化，通常被称为Beavers-Joseph（-Saffman）条件，这取决于是使用完整条件还是简化条件。最近，Jäger和Mikelić[^74]提出了一个基于分析同质化框架的方程的理论动机推导。在我们的符号中，该方程为

\begin{equation} 
\boldsymbol{v}_{S} \cdot \boldsymbol{n}_{S D} =-(\boldsymbol{K} \nabla \phi) \cdot \boldsymbol{n}_{S D} 
\label{4.5a}
\end{equation}


\begin{equation} 
-\boldsymbol{n}_{S D}^{T} \mathbb{T}(\boldsymbol{v}) \boldsymbol{n}_{S D} =g(\phi-z) 
\label{4.5b}
\end{equation}

\begin{equation} 
-P_{\tau}\left(\mathbb{T}(\boldsymbol{v}) \boldsymbol{n}_{S D}\right) =\frac{\alpha \mu \sqrt{3}}{\sqrt{\operatorname{tr}(\mu \boldsymbol{K} / g)}} P_{\tau}(\boldsymbol{v}+\boldsymbol{K} \nabla \phi)
\label{4.5c}
\end{equation}

这里，$\boldsymbol{n}_{S D}$表示从斯托克斯指向`Darcy`域的界面法线，$P_{\tau}(\boldsymbol{x})=\boldsymbol{x}-\left(\boldsymbol{x}, \boldsymbol{n}_{S D}\right) \boldsymbol{n}_{S D}$是指向与$\boldsymbol{n}$正交的切向平面的投影算子。考虑到这一点，我们观察到方程\ref{4.5b}和方程\ref{4.5c}之和可以得到$\mathbb{T}(\boldsymbol{v}) \boldsymbol{n}_{S D}$，这是斯托克斯问题的自然边界条件。

在下文中，我们提出了基于Stokes部分的连续Galerkin离散化和多孔介质的`DG`方案的该问题的离散表述。这里，`DG`方法具有局部质量守恒的优点；此外，`DG`方案更适合于异质参数场，如[Fig4.2]中所示的渗透率的大跳跃，其中渗透率在多孔介质内部两个不同的阴影区域之间相差$10^{5}$倍。由于残差中存在大量的项，我们将其分成三个独立的部分，分别用于两个子域和耦合条件。这个模型中的斯托克斯残差$r_{S}$由以下公式给出

\begin{equation} 
\begin{aligned}
r_{S}((\boldsymbol{v}, p),(\boldsymbol{w}, q))=& \sum_{T \in \mathcal{T}_{S}} \int_{T}(p \mathbb{I}-2 \mu \mathbb{D}(\boldsymbol{v}) \cdot \nabla \boldsymbol{w} \mathrm{d} x\\
&+\sum_{T \in \mathcal{T}_{S}} \int_{T} \boldsymbol{f}_{S} \cdot \boldsymbol{w} \mathrm{d} x+\sum_{T \in \mathcal{T}_{S}} \int_{T}(\nabla \cdot \boldsymbol{v}) q \mathrm{~d} x
\end{aligned}
\label{4.6}
\end{equation}

对于多孔介质，我们采用了标准的对称内部罚金加尔金（SIPG）离散化。由于我们对基础分析不感兴趣，所以我们跳过这个方案的冗长推导，只说明由此产生的残差形式 $r_{D}$ ；进一步的信息见例如 [^49] 。

\begin{equation} 
r_{D}(\phi, \psi)= \sum_{T \in \mathcal{T}_{D}} \int_{T}(\boldsymbol{K} \nabla \phi) \cdot \nabla \psi \mathrm{d} x 
\label{4.7a}
\end{equation}

\begin{equation} 
+\sum_{\tau \in E\left(\mathcal{T}_{D}\right)} \int_{\tau}\left\{\left(\boldsymbol{K} \cdot \boldsymbol{n}_{\tau}\right) \cdot \nabla \phi\right\} \cdot[\psi] \mathrm{d} s 
\label{4.7b}
\end{equation}

\begin{equation} 
+\sum_{\tau \in E\left(\mathcal{T}_{D}\right)} \int_{\tau}[\phi] \cdot\left\{\left(\boldsymbol{K} \cdot \boldsymbol{n}_{\tau}\right) \cdot \nabla \psi\right\} \mathrm{d} s 
\label{4.7c}
\end{equation}

\begin{equation} 
+\sum_{\tau \in E\left(\mathcal{T}_{D}\right)} \int_{\tau} \frac{\alpha}{h_{\tau}}[\phi][\psi] \mathrm{d} s 
\label{4.7d}
\end{equation}

\begin{equation} 
-\sum_{\tau \in B\left(\mathcal{T}_{D}\right)} \int_{\tau}\left(\left(\boldsymbol{K} \cdot \boldsymbol{n}_{\tau}\right) \cdot \nabla \phi\right) \psi \mathrm{d} s 
\label{4.7e}
\end{equation}

\begin{equation} 
-\sum_{\tau \in B\left(\mathcal{T}_{D}\right)} \int_{\tau}(\phi-g)\left(\left(\boldsymbol{K} \cdot \boldsymbol{n}_{\tau}\right) \cdot \nabla \psi\right) \mathrm{d} s 
\label{4.7f}
\end{equation}

\begin{equation} 
+\sum_{\tau \in B\left(\mathcal{T}_{D}\right)} \int_{\tau} \frac{\alpha}{h_{\tau}}\left(\phi-g_{D, D}\right) \psi \mathrm{d} s
\label{4.7g}
\end{equation}

其中$E\left(\mathcal{T}_{D}\right)$表示`Darcy`子域内部的所有单元交点的集合，$B\left(\mathcal{T}_{D}\right)$是外部边界上的所有单元交点的集合，但不是在耦合界面上。  $\alpha$是一个与问题和离散化相关的惩罚项的比例参数，$h_{\tau}$是交叉点直径。这个例子还表明，`PDELAB`并不局限于 "标准 "连续`FE`方法，而是可以很容易地用于不符合要求的方法，如有限体积（FVs）或在这种情况下`DG` 。

最后，对于耦合残差$r_{C}$，我们将第一个条件方程\ref{4.5a}与来自斯托克斯域的测试函数相乘，其余两个条件方程\ref{4.5b}和方程\ref{4.5c}分别与来自斯托克斯域的速度测试函数的正常和切向部分相乘。

\begin{equation} 
r_{C}= r_{C}(((\boldsymbol{v}, p), \phi),((\boldsymbol{w}, q), \psi)) \\
= \sum_{\tau \in \mathcal{T}_{\Gamma C}} \int_{\tau}  (\boldsymbol{v} \cdot \boldsymbol{n}_{SD} ) \psi \mathrm{d} s 
\label{4.8a}
\end{equation}


\begin{equation} 
+\sum_{\tau \in \mathcal{T}_{\Gamma_{C}}} \int_{\tau} g(\phi-z) (\boldsymbol{w} \cdot \boldsymbol{n}_{S D} ) \mathrm{d} s 
\label{4.8b}
\end{equation}

\begin{equation} 
+\sum_{\tau \in \mathcal{T}_{\Gamma_{C}}} \int_{\tau} \frac{\alpha \mu \sqrt{3}}{\sqrt{\operatorname{tr}(\mu \boldsymbol{K} / g)}} P_{\tau}(\boldsymbol{v}+\boldsymbol{K} \nabla \phi) \cdot P_{\tau}(\boldsymbol{w}) \mathrm{d} s
\label{4.8c}
\end{equation}

耦合残差 $r_{C}$ 基本上为耦合界面上的每个子域规定了一个自然（ `Neumann` ）边界条件 $\Gamma_{C}$ 。

观察这个例子，我们可以发现我们的框架有一些额外的要求。

- 对于现实问题，耦合残差$r_{C}$不会是对称的，我们需要能够指定耦合界面的方向（如上面的$r_{C}$的参数顺序和法线$\boldsymbol{n}_{S D}$从斯托克斯指向`Darcy`域的方向所证明）。

- 对于现实问题和离散化，每个子问题的组装$\left(r_{S}\right)$ .和$\left.r_{D}\right)$往往已经是一个相当复杂的过程；应该可以从现有的模拟中重复使用单个子域的实现构件。

- 单个子问题可能已经表现出复杂的内部结构，正如本例中的斯托克斯子问题所证明的那样。一般来说，我们不能指望所有我们想组合成多域模拟的组件以统一的方式布置这种结构（例如考虑斯托克斯函数空间，它可以写成$\boldsymbol{V} \times P$或$P \times \boldsymbol{V})$ 。因此，我们的框架需要一种机制来在这些不同的表示法之间进行数据的即时转换。

\subsubsection{Two Model Two-Phase Flow Problem}

除了具有不相交子域的 "经典 "多域拓扑结构，我们的框架也能够处理具有重叠域的问题。作为一个例子，考虑介绍中提到的$\mathrm{CO}_{2}$注入地下储层的问题，如注入井。在下文中，我们将考虑这个问题的一个大大简化的版本，如[Fig1.2]中所描述的。为了避免用完整的$\mathrm{CO}_{2} /$水模型的复杂性来混淆多领域的具体挑战，我们在下文中仅限于湿润相$w$（如水）和非湿润相$n$（如油）在多孔介质中的基本两相流模型，这是由`Darcy`两相流方程式描述的。

\begin{equation} 
\partial_{t}\left(\phi \rho_{\alpha} S_{\alpha}\right)-\nabla\left(\boldsymbol{K} \frac{k_{r, \alpha}}{\mu_{\alpha}} \rho_{\alpha}\left(\nabla p_{\alpha}-\rho_{\alpha} g\right)\right) =\rho_{\alpha} q_{\alpha} \quad \text { in } \Omega \times \Sigma, \quad \alpha \in\{w, n\}, 
\label{4.9a}
\end{equation}

\begin{equation} 
p_{\alpha} =g_{\alpha} \quad \text { on } \partial \Omega, \quad \alpha \in\{w, n\}, 
\label{4.9b}
\end{equation}

\begin{equation} 
p_{\alpha}\left(\cdot, t_{0}\right) =p_{\alpha, t_{0}}, 
\label{4.9c}
\end{equation}

\begin{equation} 
p_{n}-p_{w} =p_{c}\left(S_{w}\right) 
\label{4.9d}
\end{equation}

\begin{equation} 
S_{w}+S_{n} =1,
\label{4.9e}
\end{equation}

其中$\phi$表示孔隙度，$p_{c}$表示毛细管压力，$\boldsymbol{K}$表示绝对渗透率张量，$S_{\alpha} \in[0,1]$表示相的饱和度$\alpha$和其饱和度相关的相对渗透率。  $\rho_{\alpha}$是相的密度$\alpha, \mu_{\alpha}$其粘度，$q_{\alpha}$是源/汇项和$g$是重力加速度。这是一个由4个未知数$p_{w}, p_{n}, s_{w}$和$s_{n}$组成的方程组。为了解决这个问题，我们必须为毛细管压力和相对渗透率挑选一个模型。在下文中，我们选择Brooks和Corey的模型 [^24] ，他们通过实验得出了方程式

\begin{equation} 
p_{c} =p_{e} S_{w}^{-1 / \lambda} 
\label{4.10a}
\end{equation}

\begin{equation} 
k_{r, w} =S_{w}^{\frac{2+3 \lambda}{\lambda}} 
\label{4.10b}
\end{equation}

\begin{equation} 
k_{r, n} =S_{n}^{2}\left(1-\left(1-S_{n}\right)^{\frac{2+\lambda}{\lambda}}\right)
\label{4.10c}
\end{equation}

其中$p_{e}$表示进入压力，$\lambda$是孔径分布指数，$\gamma_{\alpha}$是经验性的、特定问题的参数。请注意，这个模型需要对$p_{c}$进行重新归一化，以考虑到消失的润湿相。然后我们使用构成关系方程\ref{4.9d}和方程\ref{4.9e}以及毛细管压力模型方程\ref{4.10a}来消除方程$S_{w}$中的润湿相饱和度和非润湿相压力$p_{n}$，从而得到

\begin{equation} 
\partial_{t}\left(\phi \rho_{w}\left(1-S_{n}\right)\right)-\nabla\left(\boldsymbol{K} \frac{k_{r, w}}{\mu_{w}} \rho_{w}\left(\nabla p_{w}-\rho_{w} g\right)\right) =\rho_{w} q_{w} 
\label{4.11a}
\end{equation}

\begin{equation} 
\partial_{t}\left(\phi \rho_{n} S_{n}\right)-\nabla\left(\boldsymbol{K} \frac{k_{r, n}}{\mu_{n}} \rho_{n}\left(\nabla\left(p_{w}+p_{e}\left(1-S_{n}\right)^{-1 / \lambda}\right)-\rho_{n} g\right)\right) =\rho_{n} q_{n} 
\label{4.11b}
\end{equation}

\begin{equation} 
p_{w} =g_{w} \text { on } \partial \Omega 
\label{4.11c}
\end{equation}

\begin{equation} 
S_{n} =g_{n} \text { on } \partial \Omega 
\label{4.11d}
\end{equation}

\begin{equation} 
p_{w}\left(\cdot, t_{0}\right) =p_{w, t_{0}} 
\label{4.11e}
\end{equation}

\begin{equation} 
S_{n}\left(\cdot, t_{0}\right) =S_{n, t_{0}}
\label{4.11f}
\end{equation}

这是多孔介质中两相流问题的标准压力-饱和度公式。由于$p_{c}$和$k_{r, \alpha}$的非线性，这个PDEs系统是高度非线性的，需要花费大量精力来解决。

相反，单相在多孔介质中的流动可以用`Darcy`模型来描述，该模型由以下内容给出

\begin{equation} 
\partial_{t}\left(\phi \rho_{w} p_{w}\right)-\nabla\left(\boldsymbol{K} \frac{\rho_{w}}{\mu_{w}}\left(\nabla p_{w}-\rho_{w} g\right)\right)=\rho_{w} q_{w}
\label{4.12}
\end{equation}

这是一个更简单的线性扩散型方程。

回顾一下[Fig4.3]中概述的情况，似乎只在实际含有第二相的井周围的小区域（与整个模拟域相比）解决完整的两相模型方程\ref{4.11a}是有利的，而在其余区域则改用更简单的单相模型方程\ref{4.12}。

通常情况下，这两种模型都会用局部保质离散化来实现，如FV或`DG` 。由此产生的残差与前面例子中的`Darcy`残差相似；因此我们在此不作明确说明。

这种情况揭示了对我们框架的几个新要求。

- 在以前的例子中，针对子问题的残差的积分域与基础变量的域相吻合。这一点在这里不再成立了。水压变量$p_{w}$存在于整个积分域$\Omega$上，但对应于方程\ref{4.12}的单相残差只定义在较小的区域$\Omega \backslash \Omega_{n, w}$。因此，我们需要能够将子问题残差的积分域限制在其相关变量域的一个子集上。



- 随着模拟时间的推进，$\Omega_{n, w}$的大小会发生变化，因此我们的框架需要应对模拟过程中修改的子域，并为不同子域布局之间的数据（解决方案、边界条件等）迁移提供支持。

\subsection{Hierarchical Construction of Composite Func- tion Spaces}

如2.1.4节所述，多变量问题的函数空间是每个变量的基本空间的代数乘积。

\begin{equation} 
U=U_{0} \times \cdots \times U_{n-1}
\end{equation}

这个过程我们可以重复多次，以递归地创建更复杂的、有结构的空间。在数学上，这种结构对应于使用小括号对代数乘积中的项进行分组，例如

\begin{equation} 
U=U_{1} \times U_{2} \times U_{3} \times U_{4}=\left(U_{1} \times U_{2} \times U_{3}\right) \times U_{4}
\end{equation}

并在观察相应的表达式树时变得更加清晰，这使得我们可以将$U$解释为函数空间树，如[Fig4.4]中所示。该图还展示了将嵌套张量积映射到树状结构时出现的一个重要效果。每一组函数空间由树的内部节点代表 $\left(U_{I}\right)$ .在这个例子中）。)这些人为引入的对象在我们将数学概念映射到软件时将非常有用，因为它们为整个函数空间的任意子树提供了一个手柄。这些部分可以被传递给各个子问题的运算器实现等构件，而不需要让这些构件意识到整体的问题结构，这是在更大的多领域背景下实现这些（单一物理学、单一领域）构件重用的关键要求。

数学树符号直接映射到 `PDELAB` 中的默认实现。例如，一个具有子GFSO、GFS1和GFS2的复合空间被创建为


这里，前两个模板参数被用来控制DOF到向量项的映射，进一步的信息见第六章。

从理论上讲，这种实现方式对所有类型的复合空间都是足够的，但是如果子空间的数量变得过大（例如在对有20或30个反应物的反应问题进行建模时），接口就会变得越来越难用。出于这个原因，`PDELAB`包含了第二种类型的复合空间，即`PowerGridFunctionSpace` 。


在这个例子中，我们创建了一个包含$N$类型ChildGFS的子空间的复合空间。

\subsubsection{Multi Domain Function Space}

作为标准`PDELAB`框架一部分的复合函数空间不支持多域问题；函数空间树中的所有叶子空间必须定义在同一个网格上（由`DUNE``GridView`表示）。

因此，我们需要一个新的复合空间的实现，可以结合不同子域上的子空间。在我们的实现中，这种多域支持是建立在`DUNE-MULTIDOMAINGRID`之上的：一个多域函数空间与底层的`MultiDomainGrid`合作，跟踪其子空间的域。这些子空间中的每一个都必须是一个常规的`PDELAB`函数空间（它本身允许是一个复合空间），并且必须定义在`GridView`的`MultiDomainGrid`或其`SubDomainGrids`中的一个。注意，不可能结合任意的网格视图：例如，如果一个子空间使用其子域的叶子网格视图，所有其他空间也必须定义在其各自子域的叶子网格视图上。

在数学符号中，我们可以通过以下方式描述一个多域函数空间

\textbf{Definition} 4.1（多域函数空间）。设$V=V_{0} \times \cdots \times V_{n-1}$是由函数$V_{i}$的基本函数空间$v_{i}$：$\Omega_{i} \rightarrow \mathbb{R}^{d}$的张量积形成的函数空间，$\Omega_{i} \subset \mathbb{R}^{d}$是整个模拟域$\Omega=\Omega_{0} \cap \cdots \cap \Omega_{n-1}$的子集。给出域$\left(\Omega_{0}, \ldots, \Omega_{n-1}\right)$的网格序列$\left(\mathcal{T}_{0}, \ldots, \mathcal{T}_{n-1}\right)$和对应的函数空间

\begin{equation} 
V_{i}^{h}:\left\{v \in V_{i}:\left.v\right|_{T} \in \mathcal{F}_{i} \forall T \in \mathcal{T}_{i}\right\}, \quad i=0, \ldots, n-1
\end{equation}

复合函数空间$V^{h}$的定义如下

\begin{equation} 
V^{h}=V_{0}^{h} \times \cdots \times V_{n-1}^{h}
\end{equation}

为简洁起见，我们引入支持网格序列的符号 $\mathfrak{T}=\left(\mathcal{T}_{0}, \ldots, \mathcal{T}_{n-1}\right)$ 。

翻译成C++，这个定义映射到一个`MultiDomainGridFunctionSpace`对象，它与上面显示的`CompositeGridFunctionSpace`的界面非常相似，但也需要`MultiDomainGrid`来管理子域关系。作为一个例子，考虑双域泊松例证方程\ref{4.2} ：给定一对标量函数空间对象LeftGFS和RightGFS，分别代表域的左部和右部，相应的多域函数空间的构造为


\subsubsection{Subproblem Subspaces}

由于多物理问题的本质，它们的组装需要我们在整体的解析空间和测试函数空间的子空间上操作。即使考虑到两个耦合泊松问题的基本例子，每个子问题都有单独的残差，每个子问题只在相关子域$\Omega_{L}$或$\Omega_{R}$的标量子空间上分别装配一个标准泊松残差。因此，我们需要一种机制来指定和构建来自全局空间的那些子问题子空间。在我们的框架内，我们支持由全局`MultiDomainGridFunctionSpace`的直接子集组成的子空间。为了选择这些子空间，我们引入了一个索引元组作为索引$\mathcal{I}=\left(i_{0}, \ldots, i_{N-1}\right), i_{k} \neq i_{l} \forall k \neq l$的序列，其中每个索引$i_{k} \in 0, \ldots,(N-1)$表示全球多域空间$V$的一个直接子节点。

\textbf{Definition} 4.2（子问题子空间）。给定一个索引元组$\mathcal{I}=\left(i_{0}, \ldots, i_{N-1}\right)$，指定一组子空间及其排序，我们通过以下方式定义子问题子空间$V_{\mathcal{I}}$

\begin{equation} 
V_{\mathcal{I}}=V_{i_{0}} \times \cdots \times V_{i_{N-1}}
\end{equation}

请注意，通过使用一个元组，它保留了子指数的顺序，我们可以根据它们在全局函数空间树中的典范顺序对子空间进行重新排序，如果我们想纳入两个现有的残差运算符，假设它们的变量有不同的顺序，这可能是必要的，正如Stokes- `Darcy`例子中所解释的那样。

一般来说，一个子空间的成分不一定都定义在同一个子域上；如果我们想评价一个定义在子空间上的函数，我们只能在所有成分的域的交点上这样做。这一限制由受限子空间明确指出，它将正常子空间的空间域简化为这个交点。

\textbf{Definition} 4.3（限制性子空间）。设$V_{\mathcal{I}}$为全局空间$V$的一个子空间，有$\mathcal{I}=\left(i_{0}, \ldots, i_{n-1}\right)$和每分量网格$\mathcal{T}_{V_{i_{k}}}$ 。那么限制性网格$\mathcal{T}_{\mathcal{I}}$由以下公式给出

\begin{equation} 
\mathcal{T}_{\mathcal{I}}=\mathcal{T}_{V_{i_{0}}} \cap \cdots \cap \mathcal{T}_{V_{i_{n-1}}}
\end{equation}

而由$V_{\mathcal{I}, \mathcal{T}_{\mathcal{I}}}$诱导的限制性子空间$V_{\mathcal{I}}$定义为

\begin{equation} 
V_{\mathcal{I}, \mathcal{T}_{\mathcal{I}}}=\left.V_{i_{0}}\right|_{\mathcal{T}_{\mathcal{I}}} \times \cdots \times\left. V_{i_{n-1}}\right|_{\mathcal{T}_{\mathcal{I}}}
\end{equation}

在`DUNE-MULTIDOMAIN`的实现层面上，没有等同于这些全局定义的子空间，因为这里描述的子空间只在问题组装时需要；在这种情况下，提供第2.2.3节中介绍的局部函数空间就足够了，当在子问题组装时需要，软件框架会自动合成（见下一节）。这个过程的技术细节将在第七章解释。

\subsection{Decomposition of Residuals Into Semantic Building Blocks}

如本章开头的例子所示，一个多物理问题的残余形式$\mathcal{R}$可以分解为对应于单个物理模型的独立形式的总和，并且往往只取决于问题变量的一个子集。在下文中，我们将识别和定义这些残余成分的两种特殊类型，我们称之为子问题和耦合，它们大致对应于单个物理问题和这些单个物理模型之间的成对相互作用。

\subsubsection{Subproblems: Single Physics Components}

几乎所有的多物理学模型都是从一些已经建立的单物理学模型开始的，这些单物理学模型是整个问题的一部分，然后通过纳入额外的多物理学相互作用而耦合起来。在我们的框架中，这些构件被称为子问题；对于许多多领域问题，它们不会从头开始实施，而是从现有的单一物理问题的模拟中提取。

为了指定这样一个子问题的残差$\mathcal{R}_{P}$，我们显然需要弱残差形式$r_{P}$及其相关的解析空间和检验空间，但这留下了空间域（基本上是网格单元的集合）的问题，$r_{S P}$应该被整合。最大的可能整合域是$r_{P}$中出现的变量的限制性子空间。然而，如果我们考虑我们的两相流例子，我们有简化的单流模型，它是以水压来定义的。该水压是在整个模拟域中定义的，但同时，单相流残余必须只在那些没有油浓度变量的部分进行组合。因此，我们需要一种机制来明确选择子问题的残差是否应该在一个给定的网格单元上进行组装。为此，我们引入一个网格谓词 $q: \mathcal{T} \rightarrow\{0,1\}$ 。在我们的框架中，这个谓词不是针对单元本身的网格实体，而是针对其子域集（参见第3.1.3节。为了方便，我们的框架包括两个默认的谓词实现。


正如它们的名字所暗示的，当以子域集$s_{P}$创建时，如果$s_{P}=s$或$s_{p} \subseteq s$，这些谓词分别与子域集$s$的单元匹配。

有了必要的组件，我们可以正式定义一个子问题，即

\textbf{Definition} $4.4$ （子问题）。设$U_{\mathcal{I}}$和$V_{\mathcal{I}}$为全局分析和检验空间$U$和$V$的一对限制性子空间，由网格$\mathfrak{T}=\left(\mathcal{T}_{1}, \ldots, \mathcal{T}_{n}\right)$上的索引元组$\mathcal{I}$诱发。此外，让$q_{P}: \mathcal{T}_{\mathcal{I}} \rightarrow\{0,1\}$为重叠网格$\mathcal{T}_{\mathcal{I}}$上的谓词，$\mathcal{T}_{P}=\left\{T \in \mathcal{T}_{\mathcal{I}}: q_{P}(T)=1\right\}$为该谓词选择的$\mathcal{T}_{\mathcal{I}}$的子集。最后，让

\begin{equation}
\mathcal{R}_{P}:\left.\left.U_{\mathcal{I}}\right|_{\mathcal{T}_{P}} \rightarrow V_{\mathcal{I}}\right|_{\mathcal{T}_{P}}
\end{equation}

一个在给定子空间上的离散残差形式。那么超问题$P$是由元组定义的

\begin{equation} 
P=\left(U, V, \mathcal{R}_{P}, q_{P}, \mathcal{I}_{P}\right)
\label{4.13}
\end{equation}

在我们的框架内，这个数学定义直接转化为相应的SubProblem对象。考虑到方程\ref{4.9a}的两相流子问题，这样的对象可以这样创建。





一个子问题对象除了提取存储的信息外，不支持任何操作；它的唯一目的是封装组装子问题剩余部分所需的所有信息 $\mathcal{R}_{P}$ 。

\subsubsection{Coupling Nonoverlapping Subproblems}

虽然第4.4.1节中定义的子问题能够封装多物理问题的单一物理构件，甚至可以用来模拟这些构件之间的某些类别的耦合，但它们不能代表模拟非重叠子问题之间表面相互作用的剩余形式。从形式组装的角度来看，这些表面耦合反而与`DG`方案的内部表面积分（在`PDELAB`中称为骨架积分）非常相似。它们集合在一组单元格的交点上，相邻的两个单元格有独立的局部函数空间。与普通骨架积分的主要区别在于，对于骨架积分来说，这两个局部空间属于同一个全局函数空间，而在耦合积分的情况下，耦合界面两边的函数空间通常没有关系。这使得我们无法在本地算子的标准骨架方法中实现这种类型的积分。取而代之的是，我们在局部运算符接口中添加了一个略微扩展的`alpha_coupling()`方法。


它的签名与现有的`alpha_skeleton()`方法几乎相同，为cell_intersection两侧的网格单元提供独立的函数空间、解和残差。它的不同之处仅在于允许每一侧的空间有不同的$\mathrm{C}++$类型。按照惯例，第一组参数（标记为spl_ ）与`DUNE`网格界面定义的交点内侧单元相关，而第二组参数则在外侧单元上。为了与标准的Localoperator`API`保持一致，这个术语的装配必须通过编译时的开关来启用。


还有一些额外的方法用于矩阵模式和Jacobian装配等，反映了股票`PDELAB`接口的功能，但为了简洁起见，我们在这里省略了这些。在清单A.2中可以找到一个实现这个接口的Localoperator类的完整例子，用于处理Stokes- `Darcy` 问题的耦合残差。

给定一个耦合算子，我们仍然需要定义其积分域和该域两侧的空间。为此，我们引入了第二类残差分量，称为耦合，它代表了两个子问题和它们的相关变量在一个一维流形（通常是耦合界面）上的成对互动。

\textbf{Definition} 4.5（耦合）。让$P_{1}=\left(U, V, \mathcal{I}_{1}, \mathcal{R}_{1}, \mathcal{T}_{1}\right)$和$P_{2}=\left(U, V, \mathcal{I}_{2}, \mathcal{R}_{2}, \mathcal{T}_{2}\right)$两个子问题具有不重叠的网格$\mathcal{T}_{1}$和$\mathcal{T}_{2}$，沿着耦合界面$\mathcal{T}_{C}=\mathcal{T}_{1} \cap \mathcal{T}_{2} \neq \emptyset$接触。此外，让$P_{1}$和$P_{2}$中的变量集是不相交的，即$\left\{i \in \mathcal{I}_{1}\right\} \cap\left\{i \in \mathcal{I}_{2}\right\}=\emptyset$ 。此外，让

\begin{equation} 
r_{C}:\left.\left(U_{\mathcal{I}_{1}} \times U_{\mathcal{I}_{2}}\right)\right|_{\mathcal{T}_{C}} \times\left.\left(V_{\mathcal{I}_{1}} \times V_{\mathcal{I}_{2}}\right)\right|_{\mathcal{T}_{C}} \rightarrow \mathbb{R}
\end{equation}

耦合界面$\mathcal{T}_{C}$上的剩余形式，描述了两个子问题$P_{1}$和$P_{2}$之间的耦合机制。然后，耦合$C$由三联体定义

\begin{equation} 
C=\left(P_{1}, P_{2}, \mathcal{R}_{C}\right)
\end{equation}

注意，耦合定义中的子问题的顺序必须与 $r_{C}$ 的参数顺序相同。

这个定义再次以一种非常直接的方式映射到$\mathrm{C}++$。在我们的框架中，上面定义的耦合是由一个耦合对象表示的，它捕捉了两个子问题和耦合残差的局部算子 $r_{C}$ 。考虑到耦合的Poisson例子，这个对象是通过以下方式创建的





给定这个耦合对象，我们的框架将确保总是以这样的方式调用耦合局部算子的回调方法，即把对应于左边子问题的函数空间作为第一个参数传递，并且把交点的法线从左边子问题指向右边子问题。

\subsubsection{Constraints Handling}

在实现一个标准的`FEM`问题时，约束条件主要限于外域边界和内部过程边界上的DOF，这两个边界都与（过程的局部）网格的外部边界相吻合。

这一假设对于多域问题来说并不成立，因为在多域问题中可能会出现多个内部子域边界；此外，这些边界的约束必须只适用于其相关的子问题子空间及其DOF。

为了组装约束，我们的多域框架包含了一个更有能力的替代默认的`PDELAB`约束组装器。正如第2.3.3节所解释的，默认的装配器在整个网格上迭代，并在叶子空间的约束引擎上调用装配方法（对约束有不同的方法，例如在域边界或内部处理器边界）。除了函数空间本身，装配器还接受一棵参数对象树，例如，在给定位置指定边界条件类型（`Dirichlet`或`Neumann`）。

我们在前面看到，大多数标准`PDELAB`操作可以通过应用于子问题而不是整个领域和函数空间来映射到多领域框架。这也适用于约束集合。我们通常想在子问题域的边界上添加约束，因为它对应于其剩余形式的边界。一个典型的多域问题将包含几个子问题，我们可能都想对其进行约束。为此，该框架构建了一个特定问题的约束集合器，它知道对每个子问题应用什么约束。就我们的Stokes- `Darcy`问题而言，它的构造是


在标准`PDELAB`中，当用户调用`PDELab::constraints()`时，这个装配器会自动创建，并在调用结束后销毁。在我们的例子中，这个对象一般会复杂得多（而且构建成本很高），所以我们把它的创建作为一个单独的、用户可控制的步骤。请注意，汇编器的确切类型是一个实现细节，取决于对constrainSubProblem的嵌套调用；用户应该总是在一个自动变量中捕获它。一旦约束集合器被创建，我们可以调用它来创建`multi_gfs`的约束图。


\subsubsection{Interpolation}

当在多域环境下进行内插时，我们再次面临这样的问题：我们不能指定一个单一的、具有所有变量值的矢量值函数，并进行全局内插。

和以前一样，我们通过子问题的方式来解决这个问题。我们不是将整个子域的单一全局函数内插到整个多域函数空间中，而是为每个子问题中的变量提供一个子问题和函数列表。例如，如果耦合泊松问题的初始解是由标量函数$g_{L}$和$g_{R}$给出的，我们可以通过调用以下函数来内插它们


请注意，我们必须指定我们是在试验空间还是在测试空间进行插值；由于子问题存储了两个空间的子空间，框架本身不能知道该选哪一个。

\subsubsection{Assembly}

在定义了多领域问题的构件后，我们现在要用这些构件来实际解决相应的代数系统。现在，我们专注于组装部分。

PDELab在`Grid0perator`中封装了这个全局组件，它实现了算法$2.1$中规定的通用`FEM`组件，并在定理和测试函数空间以及单元和交点局部残差上进行了参数化。默认实现是`PDELAB`的一部分，只支持定义在单一网格上的函数空间以及单一残差形式（参见第2.3.3节）。在我们的多域设置中，装配过程以及网格运算器更加复杂。除了默认版本外，它必须能够(1)应对只定义在部分网格上的子空间，(2)管理多个子问题和耦合的列表，并找出它们需要装配在全局网格的哪些部分，(3)按照每个子问题/耦合的规定准备限制的子空间。

尽管有这些变化，我们的多域网格算子的界面几乎与原始版本相同；唯一用户可见的变化是，传递给标准算子的单一本地算子被子域和耦合的列表所取代。例如，Poiss-Poisson例子的网格算子是通过以下方式创建的


请注意，该操作符只适用于`MultiDomainGridFunctionSpaces`；试图将其用于库存的`PDELAB`函数空间将导致编译错误。

一旦运算器被创建，它的使用方式就与库存的`Grid0perator`完全相同；最重要的是，它与现有的求解器基础设施兼容。因此，我们可以使用那些现有的组件，通过将其插入通用的`PDELAB`求解器组件之一，例如默认的`PDELAB`牛顿求解器，以单体方式解决问题。


另一方面，通过将多域网格算子与第六章中描述的灵活的`DOF`映射框架相结合，我们能够有效地创建块矩阵，用来实现松散耦合的求解器。这种耦合泊松问题的求解器的例子可以在第8.1节找到。

虽然本章只是对我们的多领域框架的界面做了一个高层次的概述，但第七章描述了其实现的一些有趣部分。然而，在能够深入了解这些细节之前，接下来的两章介绍了我们软件的两个基本构件，涉及函数空间树的处理和全局`DOF`排序的构建。


\section{Compile Time Polymorphic Trees and Associated Algorithms}

正如我们在前一章中所看到的，`PDELAB`将复合函数空间表示为一棵树，基本函数空间为叶子。此外，大多数与函数空间相关的对象也是以树的形式实现的（例如，表示对单个网格单元的限制的`LocalFunctionSpaces`，初始值的解析函数和第六章中介绍的`DOF`映射）。因此，`PDELAB`的主要部分包括对这些树的操作。在最初的`PDELAB`实现中，所有这些操作都是在代码中出现的地方以一种临时的方式实现。这种方法无法扩展到我们的`DUNE-MULTIDOMAIN`扩展模块中出现的更复杂的树结构；因此我们分析了我们框架的确切要求，并创建了专用的`TYPETREE`库，提供了这些树结构和相关算法的通用实现。

\subsection{Introduction}

`TYPETREE` 是一个大部分独立的库，可以独立于 `DUNE` 使用。它是自由软件，与`DUNE`的核心模块采用相同的许可证（GNU通用公共许可证，有一个特殊的运行时间例外，进一步的细节见[^40]）。由于它的构建系统目前是建立在`DUNE`构建系统之上的，它需要`DUNE` -CoMMON模块来构建，但在运行时不需要。

下面的描述是基于版本 $2.3 .1$ 的库，可以从 [^97] 下载或直接从源代码库 [^95] 下载。它需要相应的2.3.1版本的 `DUNE` -COMMON模块。

\subsection{Problem Setting and Design Considerations}

在创建我们自己的树库之前，我们首先根据其在`PDELAB`中的应用，查看了对这样一个库的要求，并提取了以下关键属性。

- `PDELAB`中的树在结构上是不可改变的，也就是说，不可能从现有的树节点上添加或删除子节点。

- 因此，这些树总是以自下而上的方式建立，从叶子节点开始，将这些节点组合成子树，然后递归地继续聚合这些子树。这种方法的一个很好的例子是`PDELAB`中建立函数空间的方式，我们在前一章中已经证明了这一点。

- 树内的用户有效载荷是异质的；通常，每个节点存储的用户数据具有独特的$\mathrm{C}++$类型。

- 对于一个给定的树来说，没有固定的程度（每个节点的子女数），所以除了杂乱无章的用户数据外，树的结构也是不统一的。

- 树的遍历对性能非常敏感，因为框架需要对每个访问的网格单元进行不同树的多次遍历。

- 在一些地方，`PDELAB`需要通过算法从旧的树中构建新的树（例如从全局空间的树中构建单元格限制的函数空间的树）。

树一直是计算机科学中一个重要的数据结构 [^78] ，因此我们最初试图在 `PDELAB` 中为树找到一个现有的库。令人惊讶的是，只有很少的$\mathrm{C}++$库提供了树的数据结构，那些库大多实现了某种版本的平衡二叉树，用于搜索应用[^27] [^71]，而少数更通用的实现，如[^103]，是面向易变性和自顶向下的构建，没有提供足够的性能。

如果我们仔细看看上面列出的属性，它们实际上可以归结为两个核心要求的结合。(1)树的异质性，同时(2)需要极快的网格遍历和在遍历过程中访问节点的特定行为和数据。在$\mathrm{C}++$中，调和这两个要求的唯一方法是通过模板和静态多态性来实现。虽然动态多态性的性能已经在编译器和硬件层面进行了广泛的优化[^39] [^28] [^1] [^106]，但相关的运行时间开销仍然使这种方法对我们的应用不可行。这主要是由于许多`DUNE`和`PDELAB`接口的颗粒度非常细。这些函数通常只执行几条机器指令来计算


表5.1 - 函数内联的性能影响 `PDELAB` 。基准测试是在硬件配置B.1下进行的。通过在标准优化标志后面加上"- fno-inline "来禁用内联。

他们的结果，因此要依靠函数内联来获得良好的性能。不幸的是，动态多态性给函数内联带来了不可逾越的障碍，这正是因为关于哪些代码要执行的决定被推迟到了运行时间。表$5.1$展示了启用或禁用内联对一些真实世界`PDELAB`应用程序的影响。正如这些数字所显示的，内联的典型性能优势是$\approx 6$的一个系数。

虽然我们无法找到一个静态类型的、模板化的$\mathrm{C}++$的树库，但简单的异质容器一直是该语言标准库的一部分。最突出的例子可能是`std::pair`，它可以存储两个不相关的对象；$\mathrm{C}++11$通过提供任意长度的图元扩展了这个概念。在标准库之外，作为`Boost`框架[^114]的一部分，`Boost`融合库的形式出现了更广泛和非常成功的异构通用容器和相关算法的集合。Guzman、Marsden和Schwinger [^62]开创了将另一个著名的 `Boost` 库 `Boost` MPL [^61] [^3] 的编译时 `TMP` 算法和容器与运行时行为相结合的方法，后者是 $\mathrm{C}++$ 标准 [^72] 的一部分。其中，`Boost` Fusion提供了著名的容器类别向量、列表和地图的版本，支持在一个容器中存储不相关的类型。在一些地方，我们库的内部设计受到了`Boost`Fusion开创的技术的很大启发，特别是它大量使用标记调度来驱动静态多态性。


\subsection{Tree Nodes }

在像树或链表这样的递归数据结构中，各个元素不仅包含用户数据；它们还必须存储数据结构的额外内务信息，例如链表中下一个元素的链接。对这个问题有两种基本方法。

侵入性容器直接在用户有效载荷中存储附加信息。为了做到这一点，用户数据类型必须适应容器。在 $\mathrm{C}++$ 中，这可以通过继承特定于容器的 mixin 来实现。


很明显，这种技术并不适合于通用的容器。对于每一种我们想要存储数据的容器（list, set, map, ...），我们都必须继承自一个特殊的mixin类。如果我们不能修改用户数据（例如，因为它来自外部库），这就有问题了。

另一方面，如果我们知道我们的数据基本面是一个链接列表，那么让该数据类型意识到列表结构就会非常有用。例如，许多函数空间的`PDELAB`算法都要遍历函数空间树。有了侵入性的数据结构，我们可以给用户数据类型（函数空间类）添加方法，执行这些遍历。

另一方面，非侵入式容器将容器数据和用户有效载荷分别存储在一个容器内部包装对象中。对于一个链表来说，这可以在容器级别实现为


在这里，我们颠倒了另一种方法的优点和缺点。有效载荷不需要意识到容器，因此我们不需要依赖来自存储数据的帮助。但是现在使用这种列表的用户代码现在必须知道额外的Node对象。这种方法对于一个通用的列表库来说效果更好。使用列表的代码自然会意识到数据结构是一个列表，而存储在列表中的数据可以对这一事实视而不见。

由于这些权衡，大多数实用程序库如$C++$`STL`都提供了非侵入性的数据结构。  `TYPETREE`没有遵循这个例子，它的树是侵入性的数据结构。我们用库构建的树（例如函数空间）从根本上说是树，所以我们并不真正关心通用性；在我们的案例中，侵入式方法所提供的更综合的用户级`API`的好处更为重要。

`TYPETREE`以一种非常灵活和可扩展的方式处理存储在树节点中的结构信息（即子节点列表）；正如我们将在后面看到的，它的设计是基于由孤立的、特定于节点的构件组成的算法，这些构件使用嵌入在每个节点中的标签进行查找。通常，在编写一个有效载荷节点（如叶子或复合函数空间）时，用户选择预定义的结构节点类型之一，这使得将对象放在`TYPETREE`树中成为可能。与库一起运送的默认节点被实现为混合基类，用户只需继承这些基类。它们负责存储节点的子节点，提供一个`API`来访问这些子节点，并添加上述的标签。在叶子节点的情况下（不需要存储任何孩子），这是非常直接的。


内部节点的混合函数更为有趣，因为它们需要包含关于其子节点的信息，并在下面几节中描述。清单5.1 $\longrightarrow$ `VariadicCompositeNode` 接口



\subsubsection{VariadicCompositeNode}

内部树节点的默认混合器，`VariadicCompositeNode`能够管理具有任意数量的异质子节点的内部节点。它的用法类似于C++的std::tuple：子节点的类型以模板参数列表的形式传递。关于 `API` 的 `VariadicCompositeNode` 的一个非常压缩的概述，见清单 5.1）。)

该实现依赖于变量模板，因此只在兼容C++11的编译器上可用。在内部，子代被存储在std::hared_ptrs的元组中；构造函数接受一个`std::shared_ptrs`的列表，它被简单地复制到内部元组中，或者接受一个引用列表。在这种情况下，我们没有关于这些引用的所有权的信息，因此子节点不会和 `VariadicCompositeNode` 一起被销毁。这样，用户可以在栈上创建所有的树节点，这在 `PDELAB` 中是一个非常常见的习惯，例如在构建函数空间树时。


\subsubsection{CompositeNode}

`CompositeNode`被设计为默认复合节点类型（`VariadicCompositeNode`）的后备实现，用于缺乏变量模板列表$5.2-$`CompositeNode`后备兼容宏支持的编译器。在`TYPETREE`最初被纳入`PDELAB`的时候，有许多大型机器无法获得$\mathrm{C}++11$支持的编译器（最明显的是Jülich超级计算中心的JUGENE（`JSC`））。  `PDELAB`对这些机器的支持在几个研究项目的背景下是至关重要的，所以我们创建了替代方案`CompositeNode`，它使用一个固定的模板参数列表；因此，它被限制为最多10个子节点。如果用户提供的子节点少于10个，剩下的模板参数默认为一个特殊的标记类型，表示缺少一个子节点。总的来说，这使得模仿大多数用户可见的 `VariadicCompositeNode` 的部分成为可能。不幸的是，这对于坐在 `CompositeNode` 上面的有效载荷节点类型来说并不成立：虽然它内部不使用子节点的模板列表，但它必须复制它，所以在一个天真的实现中，有必要在库中提供所有复合有效载荷类型（网格函数空间、局部函数空间、网格函数...）的替代版本。这似乎有些过分，所以我们开发了一个解决这个问题的方法，即在整个`PDELAB`代码库中用`CompositeNode`自动替换`VariadicCompositeNode`的机制，该机制基本透明。它依赖于一些预处理器宏，将正确的代码插入到有效载荷数据结构的定义中；从清单$5.2$中可以看出，这主要涉及到对新类的模板签名、树节点混合器的类型以及将子节点转发给混合器类的构造器的参数签名的改变。  然后 `TYPETREE` 将自动检测编译器是否支持变量模板，并为这些宏选择适当的定义，以使用 `VariadicCompositeNode` 或 `CompositeNode` 。

在`TYPETREE`整合到`PDELAB`之前，它的复合函数空间总是使用模拟的变量模板参数；切换到 "真正的 "变量模板参数后，现代编译器的编译时间得到了巨大的改善（可达 2 倍）。由于编译时间对于像`DUNE`这样的模板密集型代码来说是相当长的，这一改进明显地提高了该框架对开发者的可用性。

\subsubsection{`PowerNode`}

理论上，上述的复合节点足以能够将任意的树映射到`TYPETREE`框架中，但是出于可用性和性能的考虑，从`PDELAB`开始，一个专门的内部节点只包含相同类型的子节点，成为`PDELAB`的一部分。`PowerNode`是由其子节点的类型和它们的数量来指定的，例如，这简化了在无维度代码中从标量空间构建向量空间的过程。此外，对于大量的子类型，`PowerNode`的类型签名将远远短于等价的`VariadicCompositeNode`，改善编译时间和编译器错误信息的可读性。最后，我们可以利用`PowerNode`的子类型共享这一事实，将这些子类型上的循环从编译时结构转换为常规的运行时循环，进一步减少编译器的负担（由于循环边界在编译时仍然是已知的，这并不排除编译器进行广泛的优化和循环解卷）。


\subsubsection{Classifying Tree Nodes}

由于子列表被编码到每个`TYPETREE`节点的类型签名中，很明显，所有用该库创建的树都是异构数据结构；因此，任何对这些树进行操作的算法都必须写成TMP。对于$\mathrm{C}++$中的任何一种异构数据结构来说都是如此，对异构数据容器中的每一项进行操作的标准技术包括将节点操作封装在一个通用的放矢中，然后通过通用的遍历算法应用到容器中。例如，为了将std::元组中的每个元素输出到控制台，我们可以写出这样的函数


并以一对重载模板函数的形式实现一个元组特定的迭代算法。


由于不可能以迭代方式编写模板代码，这是一个递归算法，使用积分模板参数i来控制迭代。给定一个元组变量my_tuple，它可以通过以下方式调用

对于C++ `STL`的用户来说，这是一个众所周知的成语，它包含一个建立在这种回调模式之上的算法库。Fusion `Boost`库也采用了这种方法，并为其异构容器包含了大量类似`STL`的算法。

编写像 "tuple_for_each() ` function above for ` TYPETREE "这样的通用算法会面临一个额外的挑战：像std::tuple或fusion::map这样的异构容器会存储异构的用户数据，但容器节点只有一个（模板化）的实现（参考第5.3节的列表例子）。在`TYPETREE`库中，情况更加复杂，因为有多个节点的实现，它们都需要不同的代码来迭代它们的子节点。因此，`TYPETREE`算法必须能够区分这些节点类型。这通常是通过对每个可能的节点类型的算法组件（如上面的for_each）进行（部分）专业化，或者使用某种形式的标签调度来实现，如第2.4.1节所述。在这种情况下，专业化是行不通的，因为混合类型隐藏在基类的列表中，所以`TYPETREE`使用标签调度。为了简单起见，每个节点混合器都通过一个叫做NodeTag的公共`typedef`明确地导出它的节点标签。

`TYPETREE`中的一些算法（尤其是树形变换）另外由节点的用户类型控制。为了一致性和避免部分模板专业化的脆弱性，用户类型也使用第二个标签类型进行分类，用户必须以 `ImplementationTag` 的名义导出。这两个标签加在一起，使得完全不同的用户有效载荷共享一个共同的`TYPETREE`节点类型成为可能（例如，`PDELAB`有一个`PowerGridFunctionSpace`和一个`VectorGridFunctionSpace`，两者都实现为`PowerNode` 以及相反的情况（同一类型的用户节点坐在不同的树形拓扑结构之上$-$，这使得实现代理节点非常容易，`DUNE-MULTIDOMAIN`在其子问题函数空间中大量使用这一功能）。

\subsection{Algorithms}

除了节点实现之外，`TYPETREE`还包含了一些在这些树上工作的算法。在下文中，我们将介绍这些算法中最重要的两种：树的遍历和树的变换。

`TYPETREE`中的所有算法都是通过调度到基于这两个标签的专门的、节点本地的构建块来工作的。由此产生的算法是高度模块化的，这确保了良好的可维护性。更重要的是，标签调度机制也使它们以一种极弱的方式被耦合，这使得扩展它们以适应新的节点类型变得非常容易--在编写所需的算法片段并将其与调度机制挂钩后，实现的现有部分不需要以任何方式进行修改以纳入新的节点类型。

\subsubsection{Tree Traversal}

由于树的分层结构，它的节点没有单一的规范的遍历顺序。一般来说，计算机科学区分了两种基本的遍历算法。

\textbf{Breadth-first}遍历包括以自上而下的顺序访问树的结点。它从根节点开始，继续访问根节点的所有直接子节点，然后按照这个顺序，迭代地继续访问根节点的$i$-级子节点。它通常是通过一个队列来实现的。每当算法遇到一个新的节点，它的子节点就会被推到队列的末端，算法通过从队列的前面弹出节点来推进，直到耗尽。


\textbf{Depth-first} 遍历是尽可能地沿着树的给定路径往下走，然后通过回溯的方式继续搜索下一个符合条件的路径。这相当于使用一个堆栈，其中元素被添加到数据结构的同一端，并从该端检索。

[Fig5.2]显示了广度优先和深度优先的例子树的遍历路径。有趣的是，对于我们的树来说，实现后一种算法是相对简单的，而广度优先的遍历则很难实现。这是因为遍历算法必须写成`TMP`，而深度优先遍历的基于堆栈的性质自然与模板元编程的递归编程风格相匹配；我们可以简单地使用我们程序的调用堆栈来存储算法的状态。另一方面，使用$\mathrm{C}++$模板手动实现异质数据的高效队列似乎是一项非常艰难的任务。

幸运的是，我们框架中的预期应用只需要深度优先的遍历，所以我们将自己限制在这种迭代上。如果我们仔细看一下这个算法，就会发现由于它的回溯特性，遍历轨迹会多次经过树的内部节点。根据算法何时在这些节点上停止，深度优先遍历可以进一步分类。

\textbf{Pre-order} 遍历在第一次遇到内部节点时就访问这些节点。这对需要在树上传播信息的算法很有用。

\textbf{In-order} 遍历对有序二叉树来说非常重要，它按照排序顺序访问节点。


\textbf{Post-order}当对内部节点的操作需要所有后代的结果时，即信息必须被移到树上时，必须使用遍历。

[Fig5.3]描述了三种变体中每一种变体访问示例树的节点的顺序。

为了将迭代算法与在树节点上进行的操作分开，计算机科学发展了访问者模式[^99] [^26]：一个通用算法遍历树，并通过调用其回调函数向访问者展示各个元素。在树的上下文中，一个访问者可以有多个回调函数，在不同的时间被调用以实现不同的遍历顺序。这允许单个访问者执行前、后顺序的联合遍历。清单 $5.3$ 显示了 `TYPETREE` 的访问者接口所提供的回调点。除了实际的节点，回调还通过额外的TreePath参数接收节点在树中的位置信息，该参数将从根到当前节点的路径编码为一个子索引的元组。虽然前、中、后回调在树形库中很常见，但我们的接口还包含两个额外的方法，有助于编写数据必须在树形层次结构中向上或向下移动的算法。数据结构的设计使得这一点很难手工完成，因为没有从子到父的链接。最后，访问者还包含一个模板元函数，为每个节点调用，以决定是否要继续遍历该节点的子节点（在上面的例子中，该函数是从基类中继承的）。这是一个重要的优化，因为树的遍历本身就是一个`TMP`，因此是完全未滚动的；不必要地遍历大量的深树层次，会大大增加基于`TYPETREE`的程序的编译时间。

实际的traveral算法$5.1$是基于一个模块化的框架，该框架基于编译时间调度到特定节点的迭代逻辑。根据当前节点的特点，这个逻辑可以采用运行时迭代（其优点是减少代码大小和编译时间，但只可能用于同质结构的节点，如`PowerNode`）或编译时递归。同时，这种模块化的方法确保了可扩展性，因为新的节点类型可以通过增加一个新的调度重载来适应。重要的是，增加这个重载不需要对现有的算法组件做任何改变。

\subsubsection{Simultaneous Traversal of Tree Pairs}

在`PDELAB`的背景下，经常需要遍历一棵树并应用一个需要存储在不同树中的数据的函数，例如插值函数或评估约束条件所需的数据（参见2.3.3节）。从本质上讲，我们需要并行地遍历两棵树，并向访问者展示匹配的树节点对。  `TYPETREE`包含一个扩展的迭代算法，可以实现这种使用场景。算法 $5.1-$ 树的遍历算法。  $N$是当前树节点，$V$是访问者，$p$是树路径。在这个算法中，我们展示了一个单一的`apply()`函数及其一般语义。派遣阶段使用组件注册表$\mathcal{D}$来查找为当前节点的节点类型$N$定制的`apply()`版本，并以针对节点的数据布局进行优化的方式实现应用功能。


一般来说，最好不要要求两棵树的结构完全相同，只要它们是兼容的，即两棵树中的相应节点要么有相同数量的孩子，要么至少有一个节点根本没有任何孩子。有了这个宽松的要求，就有可能在两棵树的位置相同的节点采用不同的节点类型的情况下进行迭代，并支持一棵树在某些内部节点被切断的情况。如果我们遇到这样的切断情况，就不再清楚应该如何进行遍历了。我们可以继续遍历 "更深 "的树，用该树中的额外节点和另一棵树中的单一节点的所有结果组合来调用访问者，这给我们留下了如何在访问者中区分这种特殊情况的问题。出于这个原因，我们转而选择简单地忽略较深树中的额外树节点，并以引入差异的两个相应节点调用访问者的叶子回调。如果访问者需要继续遍历，从传入的节点开始设置一个标准的单树遍历是很容易的。  [Fig5.4]演示了同时进行的树形遍历的语义。

\subsection{Tree Transformations}

`TYPETREE`的设计受到函数式编程的启发，函数式编程通常将数据视为不可改变的，并以从给定输入产生新数据的函数来表达程序 [^70] 。这种范式体现在`TYPETREE`的 "树 "部分：一旦一个节点被创建，就不可能再添加或删除它的子节点（默认的节点实现实际上允许用相同类型的另一个对象替换一个子节点，但这主要是一个实现细节，没有在我们的任何树中使用）。另一方面，包含在节点中的数据，即赋予树以实际意义的数据，可以在任何时候被改变。

在`PDELAB`的背景下，这种分裂的观点（不可变的树结构，可变的有效载荷数据）非常合适。虽然存储在函数空间树中的数据非常有望发生变化（可能的原因包括网格细化、$p$ -adaptivity或并行计算的负载平衡），但我们通常不希望在创建空间后增加或删除新的变量，所以在创建树后其结构被冻结。

另一方面，`PDELAB`定义了一些依赖于函数空间的数据结构，这些结构被存储在具有相同（或至少相似）形状的树中。早期版本的`PDELAB`要么使用一次性的特殊目的代码来构建这些依赖树（例如构建单元格限制空间的LocalFunctionSpace树），要么要求用户手动构建新的树。由于`TYPETREE`的树是以自下而上的方式构建的，这个过程通常包括完全解构旧的树到它的叶子，基于这些旧的叶子定义新的叶子节点，然后递归地组合新的叶子来创建新的树，如果由用户手动执行，这是一个非常冗长和容易出错的过程。

在`PDELAB`中的大多数时候，这个临时程序实际上是遵循一个一般的算法，它接受一个输入树，并根据一组特定的节点规则，返回一个新的树。在下文中，我们把这种算法称为转换 $\mathcal{F}$ 。在数学术语中，转换$\mathcal{F}: \mathcal{B} \rightarrow \mathcal{B}^{\prime}$是一个从一组树$\mathcal{B}$到另一组树$\mathcal{B}^{\prime}$的映射。这个定义允许广泛的转换，包括返回一个与原始形状不同的树，这个特点在我们的一些应用中很重要。

纵观`PDELAB`中的大多数转换，产生的树在形状上与输入的树相同（或至少非常相似）。我们发现，这种类型的转换通常可以写成两个部分的组合。

- 一个通用的算法，它只关注树的深度优先遍历，并按后序产生转化的树节点（这样，子节点在其父节点之前被转化，这是构建`TYPETREE`树的要求）。

- 和一组转换描述符，每种类型的用户数据都有一个，该算法用于转换每个单独的节点。

这种关注点的分离将整个转换过程解耦为两个大体上正交的部分，使其更容易维护和扩展这两个部分。想要定义一个新的转换的用户只需要描述他们每个用户节点类型的转换结果，而不需要考虑底层`TYPETREE`节点处理子节点的方式。同时，在向库中添加新的结构性`TYPETREE`节点类型后，该类型只需集成到通用的遍历算法中，然后将适用于在该算法之上实现的所有用户定义的转换。

执行转换是非常直接的，通过调用元函数TypeTree::TransformTree与起始树的根和标识转换的标签类型来实现。例如，获取一个函数空间树并创建其对应的排序树（参见第6章）的转换被称为gfs_to_ordering。给定一个根空间RootGFS，我们可以像这样创建和调用它。


算法$5.2-$ 树的转换。这里，$\mathcal{F}$表示一个转换，$\mathcal{B}$是源树，$d$是用户指定的$\mathcal{B}$根节点的转换描述符，可以通过对转换和树的双重调度来找到。


算法$5.2$中所示的通用遍历是以类似于树状遍历的方式实现的，因为它由松散耦合的构件集合组成，每种类型的`TYPETREE`节点都有一个，它们负责转换该单一节点，并通过调度`TYPETREE`节点标签进行查询。然而，该算法确实以节点特定的转换描述符的形式纳入了用户提供的额外组件。这些是通过对转换标签和当前节点的`ImplementationTag`的双重调度来查找的（参见第5.3.4节），负责转换当前节点内的用户数据。根据它们的类型，它们还可以控制通用算法是否会递归到当前节点的子节点，这个机制将在下一节中详细描述。

\subsubsection{Descriptor Structure}

上一节中描述的通用转换算法支持两种类型的每节点转换描述符。

一个给定节点$N$的非递归描述符会阻止通用算法递归到根在$N$的子树。相反，该算法希望描述符能通过一些其他方式来转换该子树。这种类型的描述符主要是在子树的形状必须作为转换的一部分而改变（例如切断子树），或者子树应该用不同的转换方式进行转换的情况下有用。在这种情况下，算法的通用部分只访问一个给定的子节点，并期望被它依次调用来转换可能的子节点。递归描述符导致通用算法继续向下递归树。它希望该算法能够处理其所有的子节点的转换，并要求将已转换的子节点作为节点本地转换的输入。

描述符必须提供两方面的功能。在编译时，它们必须能够计算出节点的转换类型。根据描述符是否是递归的，这项工作要么由一个简单的、固定的`typedef`（非递归描述符）来处理，要么由一个`TMP`来处理，该描述符期望被转换的子节点的类型作为输入（递归描述符）。同样，在运行时，它们被用来从原始节点和--如果是递归描述符--已经转化的子节点中构造一个转化的节点实例。

清单$5.4$显示了一个非递归转换描述符的一般结构。它展示了接口的一个不幸的特殊性，这是由于向后兼容造成的。在现有的`PDELAB`代码中，用户通常将全局和局部函数空间等对象创建为局部变量。同时，`TYPETREE`在动态分配的内存中存储所有的子程序（如`std::shared_ptr` ）。因此，我们需要两个不同版本的运行时转换代码。一个是接受堆栈对象（作为`const`引用）并在堆栈上创建转换后的节点，另一个是以`std::hared_ptr`的形式操作堆分配的对象。

在许多情况下，每个节点的转换确实是节点的局部，也就是说，它们的结果只取决于节点本身，而不取决于它的子节点。这种使用情况是由递归转换描述符处理的。这些描述符在调用本地转换之前依赖于通用的转换算法来转换当前节点的子节点。由于节点的类型取决于它的子节点，该算法通过调用描述符上的`TMP`来确定转换后的节点类型，并将转换后的子节点类型传递给它。同样，运行时的`transform() ` methods of the descriptor expect the algorithm to handle the creation of the child objects and pass the transformed nodes to the ` transform()`函数也是如此。不同的节点类型（`CompositeNode/PowerNode`）使用不同的约定来传递其子节点的信息，为此，递归描述符的`API`对每种节点类型都是不同的。有关详细信息，我们参考库的文档；在下文中，我们只展示清单 $5.5$ 中 VariadiccompositeNode 的例子。

\subsubsection{Transformation Descriptor Registry}

当执行树形转换时，`TYPETREE`依靠一个转换描述符注册表来查询每个节点的转换情况。这个注册表使用了一个基于函数的注册方案，这将在下一节详细描述。虽然这个注册表从未被显式地存储（它是一个编译时的概念，描述符是通过声明具有特定签名的函数来注册的），但还是有一个规范的`API`，它实现了查找转换描述符的双重调度机制。给定一个节点和一个`TreeTransform`，节点转换描述符可以通过以下方式查询


为了注册一个树形变换`descriptor_type`和实现标签`ImplementationTag`的特定组合的变换描述符`descriptor_type`，用户声明了一个重载版本的函数清单$5.5-$ 具有自动变换子节点的变换描述符。

在一个命名空间中，可以通过依赖参数的查找找到它（ `ADL`）。注意，这个函数只需要被声明，不需要定义。例如，在 `PDELAB` 中，LocalFunctionSpace是通过转换 `gfs_to_lfs` 从一个函数空间生成的。这个转换和`CompositeGridFunctionSpace`的转换描述符是通过在命名空间`Dune::PDELab:`中放置以下重载来注册的


具体的转换`gfs_to_lfs`是相当复杂的，转换描述符里面的逻辑需要额外的参数。在这个例子中，我们把这些参数打包到转换标签的一个模板参数中。这样，我们仍然可以为转换要求一个特定的外部类型，这样我们的注册就可以匹配所有带有这个标签的转换，而不考虑嵌套的参数，但注册的描述符永远不会被考虑用于其他类型的转换。

\subsection{Tag Dispatch With Polymorphic Meta Functions}

将标记排列成层次以实现编译时多态性的效用已经被认可了很久；$[\mathrm{C}++/ 24.4 .3]$定义的迭代器标记经常被引为典型的例子。在该条款中，标准还解释了这些标记如何通过重载标记类型上的函数调用来提供优化算法。


由于迭代器标记会自动被投到它的最派生的基类上，因为可以找到foo的重载，所以这种技术很容易容纳新的标记，只需将其放在标记层次中的适当位置，如果没有标记本身的函数重载，就会退回到给定标记的基类的实现。这就创造了一种与动态多态性非常相似的调度机制，它根据参数类型来选择执行的代码，但它发生在编译时，因此被称为静态多态性或编译时多态性。

在下文中，我们将介绍一种新的元函数类型，使这种类型的多态性在运行时代码之外还可以用于类型计算。

如第2.4.2节所示，C++元函数通常是作为模板结构实现的，其中函数的参数是作为模板参数传递的，而返回类型可以作为`typedef`中嵌套的`struct`被检索。然后元函数内的控制流通过（部分）专门化的 `struct` 发生。

为了复制本节开头的迭代器例子的多态属性，我们转而重载了一个函数签名，元函数参数现在作为实际函数参数的类型被传递，而元函数调用的结果被编码为函数的返回类型。这是可能的，因为$[\mathrm{C}++/ 8.3 .5 .3]$允许重载的函数有不同的返回类型，虽然这个特性在正常的$\mathrm{C}++$代码中可能会引起混淆，但它完全适合我们的应用。

让我们考虑一个非常简单的例子，它让我们基于一个标签类型注册一个描述符。如果我们假设元函数被称为dispatch，我们提供一个重载，将输入值 $X$ 与返回类型 $Y$ 连接起来。


注意，我们没有在参数列表中直接指定标记，而是使用了一个指针；这使得$X$在元函数被调用时是一个不完整的类型。正如前面所解释的，这个函数只需要被声明；可能的定义将永远不会被使用。

调用这个元函数比调用基于`struct`的元函数要稍微复杂一些。首先，我们需要一种方法来捕获`typedef`中的函数的返回类型。为此，我们使用$\mathrm{C}++11$关键字`decltype`，它返回其参数的结果类型（参见2.4.3节）。在它的帮助下，我们可以通过 $^{1}$ 调用元函数


对于缺乏 $\mathrm{C}++11$ 和 `decltype` 的旧编译器版本，可以使用非标准扩展_-typeof_-。除了一些与此无关的语义上的角落情况外，它们的行为是相同的。注意，为了创建对`dispatch() ` inside ` decltype`的虚假调用，我们需要制造一个`tag*`类型的参数。虽然我们可以通过写`static_cast < Tag *> (nullpt r)`来实现，但实用函数`declptr()`能更清楚地表达其含义。在上面的例子中，我们将真正的调度函数包裹在一个传统的元函数中，这个元函数叫做 invoke_dispatch，它提供了一个用户更熟悉的接口，并封装了对调度的稍微复杂的调用。

在这个例子中，整体结果看起来与 "传统的 "元函数非常相似，但是这两种方法实际上使用了不同的查找机制来将一般的元函数解析为适合其参数的正确版本。传统的元函数依赖于模板专业化，而我们的基于函数的元函数是基于函数重载解析的。这两种机制根据不同的规则集来匹配其参数。

- 一个实例化的模板通过首先搜索完整的专业化$[\mathrm{C}++/ 14.7 .3]$，逐步扩大搜索范围到部分模板的专业化$[\mathrm{C}++/ 14.5 .5 .2]$，最后回落到主模板定义。

- 由$[\mathrm{C}++/ 13.3]$定义的函数重载解析要复杂得多，特别是因为$[\mathrm{C}++/ 13.3 .2]$允许对函数参数进行隐式转换以找到一个可行的函数。由于编译器有多种方法来进行隐式转换，因此可能会有大量的候选函数通过对参数进行不同的转换得到。因此，$[\mathrm{C}++/ 13.3 .3]$中优先考虑这些候选函数的规则集要比类模板实例化规则复杂得多，特别是在存在函数模板甚至函数模板特殊化的情况下。

重要的是，模板实例化机制总是匹配非常具体的参数类型（或者，在部分专业化的情况下，可能是具有任意内部模板参数的具体模板）。由于$\mathrm{C}++$中模板的本质，负责寻找匹配的专业化的逻辑并不考虑类型之间的任何关系，例如继承或转换操作符 这种限制要求通过专业化的调度元函数明确注册每一个支持的标记。另一方面，函数重载解析知道类型关系，特别是如果有一个以基类为参数的函数重载，但没有该类型本身的重载，它将把一个类型投给它的一个基类。

在 `TYPETREE` 的上下文中，函数重载解析过程的这个属性使得创建一个类似于本节开头的迭代器示例的 ImplementationTags 的层次结构成为可能。虽然基标签必须为所有转换注册，但是如果派生标签的行为应该与这个特定转换的基标签不同，那么派生标签只需要提供更专门的转换描述符。这个功能被 `PDELAB` 用来实现 `VectorGridFunctionSpace` ，它是 `PowerGridFunctionSpace` 的一个特例，用于表示空间矢量的变量（例如速度）。这个`VectorGridFunctionSpace`的实现标签继承了`PowerGridFunctionSpace`的标签，因此能够重复使用其大部分的实现。它只重载了极少数的转换，例如将其值输出为`VTK`的矢量数据，而正常的`PowerGridFunctionSpace`将其子集输出为单独的标量数据。

函数重载的另一个优点是在放置注册声明的地方有额外的灵活性。

- 类模板的特化必须放在与主模板相同的范围内$[\mathrm{C}++/ 14.5 .5]$，这在元函数的上下文中是不方便的，因为特化的元函数的命名空间往往与带有用户类型的应用程序命名空间不同。

- 另一方面，只要函数调用没有限定特定的作用域，就会在更多的作用域中找到函数重载。除了调用地点的范围，`ADL`在寻找候选函数时还会考虑所有参数（包括常规参数和模板参数）的范围，这使得用户可以将注册了派生模式的函数声明直接放入应用命名空间。

\subsection{Applications}

`TYPETREE`最初是作为`PDELAB`的一个实用库编写的，在那里它对许多基于树的数据结构具有核心意义，主要是函数空间和几个派生对象，如订购对象的树。在下文中，我们强调了几个采用该库的一些更高级功能的例子。

\subsubsection{Proxy Nodes}

在`PDELAB`中，有几个场景涉及到在保留底层树的结构的同时，采用现有的树节点并扩展和/或修改其行为。在核心`PDELAB`中，子空间是这种模式最明显的应用：它们使得提取较大空间的子树并使该子树了解整个树结构成为可能。这种 "祖先 "知识通常在`TYPETREE`树中是不存在的，但在将子空间的局部DOF映射到全局空间时是必需的。同时，子空间的函数空间`API`的其余部分，包括其子树结构，可以从原始树节点中重用。为了简化这种`proxy objects`的实现，`TYPETREE`提供了一个`ProxyNode`，它存储了一个现有的节点并模仿其树节点行为（它将具有相同的NodeTag并提供相同的`API`，它只是转发到被代理的节点）。这种封装通常避免了为每个不同类型的树节点重新实现代理的用户特定部分的必要性。相反，大多数情况下，有一个单一的实现并继承`ProxyNode`就足够了，就像这个微不足道的例子。


这个代理既适用于叶子节点，也适用于复合节点或任何其他类型的 `TYPETREE` 节点。在内部，`ProxyNode`是一个接口类，提供所有节点类型共享的基本节点接口。然后，通过继承依赖于代理类型及其节点标签的混合类，注入树结构中的节点功能的额外功能。该库为默认的节点标签提供了该混合类的适当的专门化，其中包含用于静态和动态儿童访问的额外的`API`。

基于mixin的设计使得添加对额外节点标签的支持变得非常容易。例如，如果用户用节点标签FooNodeTag创建了一个新的节点类型，那么只需要提供部分特殊化即可


之后，我们上面的一般Proxy类将自动支持FooNode类型的`TYPETREE`节点。

\subsubsection{ Filtered Nodes }

第四章中介绍的子问题和耦合为相关的残差形式定义了一个子空间。这个子空间是以一个子指数的元组形式给出的。如果该元组只包含一个子指数，那么该子空间可以作为该子之上的`ProxyNode`来实现，但如果该空间结合了多个子，我们需要合成一个新的树节点，以正确的顺序呈现所选的子。为此，`TYPETREE`引入了另一个强大的工具，称为`FilteredCompositeNode`，它对内部节点的子节点应用了一个编译时过滤器。该库还提供了一些预定义的过滤器；为了根据子空间索引元组来选择和重新排序子节点，我们可以使用IndexFilter。例如，如果我们需要一个包含现有节点的子节点$(4,1,3,2,3)$的树形节点，过滤后的代理节点可以构造如下。


如这个例子所示，甚至有可能在被过滤的节点中复制一个子节点，但请记住，过滤器并没有真正复制节点；在上面的例子中，被过滤节点的第3个和第5个子节点在内存中指向同一个对象。

传递给`FilteredCompositeNode`的过滤器实际上是一个`TMP`，它在被过滤的节点实例化时被调用；在其调用期间，它可以访问底层的、未被过滤的节点及其子节点的类型。它返回一个索引对的元组$\left(i_{\text {new }}, i_{\text {old }}\right)$，封装在符合`FilterResult`概念的`struct`中；该概念的精确定义与类型名称等可以在库文档中找到。对于上面的例子，调用IndexFilter的结果将是$((0,4),(1,1),(2,3),(3,2),(4,3))$ 。

通过将过滤器实现为`TMP`而不是简单的索引列表，就有可能对几乎任意的标准进行过滤（例如，移除所有属于叶子节点的子节点，通过一些用户定义的属性对子节点进行排序，...）。另一方面，`FilteredCompositeNode`对过滤器的确切语义漠不关心；只有一个在所有类型的过滤器和内部节点中共享的单一实现。然而，请注意，过滤`PowerNode`会移除该节点类型的扩展接口，因为`FilteredCompositeNode`总是模仿`VariadicCompositeNode`，因为为动态子接口添加特殊支持对我们的应用来说是不需要的，而且似乎太特殊了，不值得增加实现的复杂性。


\section{Flexible Control of Vector and Matrix Layout}

在前面的章节中，我们一直在关注制定多物理问题以及组装其残差和`Jacobians`的错综复杂的问题，但直到现在我们还忽略了代表这些对象的向量和容器的实际布局。这种布局受我们列举基函数的方式所支配，而这种顺序又反过来影响我们用来解决由残差和雅各布形成的（线性或非线性）方程系统的数值求解器的行为。

这些方程系统带来的最重要的问题是其巨大的规模--在数学方法开发的庇护所之外，数百万的方程在这方面被认为是一个小问题，而大型`HPC`模拟目前在$10^{12}$DOFs数量级的问题上运作。这些巨大的问题规模使得我们不可能天真地将问题矩阵存储在密集的表示中--即使是一个有$10^{6}$个DOF的小问题，密集的双精度矩阵也需要$\approx 7.3 \mathrm{TiB}$的存储空间，这反过来又会使这样的问题在当前工作站硬件上的解决完全不可行。另一方面，典型的`FEM`矩阵是非常稀疏的，所以它们通常以利用这种稀疏性的格式来存储，例如块压缩行存储（`BCRS`）。

对于现实的问题规模，遇到的线性方程组（直接或在非线性系统的线性化之后）是用迭代求解器来解决的。迭代求解器经常表现出强烈依赖矩阵/向量结构的性能特征，这种结构是由问题组装算法创建的。具体来说，这种影响是通过三种不同但又相互依存的机制出现的。

- 修改DOF的空间内顺序可以控制集合矩阵的带宽等特征，这是一些迭代求解器/预处理器性能的重要因素。

- 当在一个共同的魔方上合并产品空间时，有多种方法，这些方法允许对整个问题的不同解释和解决技术。

- 与上一点有些关系，某些问题的特定`DOF`布局得益于使用块状矩阵/向量，将问题结构暴露给代数求解器并提高其性能。

在这一章中，我们提出了一种基于底层函数空间的树状结构生成函数空间的基函数枚举的迭代机制，并讨论了这种机制在 `PDELAB` 中的实现。由于索引映射是PDE汇编框架的核心功能，我们在这种情况下的实现并不是作为一个可选的附加功能，而是完全取代了整个框架中早期的、更有限的映射功能。因此，本章的讨论并不局限于多域问题，也适用于较简单的问题，如第2.1.4节中介绍的纳维-斯托克斯方程。

\subsection{Ordering Degrees of Freedom in Finite Element Bases}

一个离散的`FE`函数空间$V$是以其底层网格来定义的。它由有限数量的基函数$\left\{\varphi_{i}\right\}_{i=0, \ldots, n-1}$跨越，每个基函数与一个特定的网格实体相关联（注意，可能有多个基函数与一个实体相关联，例如对于$P_{k}$空间的$k>2$），该空间中的任何解决方案$\psi \in V$可以由一个$D O F$矢量$\boldsymbol{u} \in \mathbb{R}^{n}$表示，这意味着基函数的线性组合。

\begin{equation} 
\psi=\sum_{i=0}^{n-1} u_{i} \varphi_{i}
\end{equation}

为了将这个方程用于具体的函数空间，我们必须以某种方式唯一地列举出基函数。虽然这个问题对于像标量$Q_{1}$空间这样的简单情况来说似乎微不足道（每个网格顶点有一个基函数；只要在你的网格数据结构中迭代顶点并按迭代顺序对函数进行编号即可），但我们将看到，一旦我们遇到积空间或更复杂的有限元，就有许多选择。一般来说，我们不可能选择一种在所有情况下都是最佳的枚举方案；相反，我们需要能够使枚举适应问题、离散化和线性求解器。


\subsubsection{Merging Index Ranges in Product Spaces}

在组装一个有多个变量的问题时，有两种基本的方法可以将各个部分已经有序的基函数合并成一个全局的基。

- 我们可以简单地将子空间的完整基数连接起来，我们称之为词法顺序。这就为每个子空间创造了一个连续的基函数范围。

- 如果子空间有类似的结构（例如，两个子空间都是$Q_{1}$，或者两个子空间都是`DG`空间），我们可以根据一个简单的比例规则来交错使用子基。

[Fig6.1] 在 $2 \mathrm{D}$ 中通过两个 $Q_{1}$ 空间的乘积空间说明了这两种方法。在这种情况下，交错的方法效果很好，因为它将合并后的空间的基函数按其相关的网格实体分组。

对于具有lexicographically合并的乘积空间的问题，系统矩阵包含每个子空间的一个大对角线块，以及描述子空间对之间耦合行为的非对角线块。

\begin{equation} 
\left(\begin{array}{cc}
A_{1} & C_{12} \\
C_{21} & A_{2}
\end{array}\right)\left(\begin{array}{l}
u_{1} \\
u_{2}
\end{array}\right)=\left(\begin{array}{l}
f_{1} \\
f_{2}
\end{array}\right)
\end{equation}

另一方面，从交错合并中产生的矩阵模仿了对应于每个子问题$(A_{1}$ .和上面$A_{2}$的较小矩阵的结构。)每个矩阵条目不再是一个标量值，而是变成一个小的密集矩阵（在上面的例子中大小为$2 \times 2$）。  [Fig6.2]说明了$Q_{1} \times Q_{1}$空间的$2 \mathrm{D}$网格的$4 \times 4$单元的这个原理；注意右边的交错矩阵看起来像是左边单个块的放大版本。它还展示了矩阵、解决方案向量和右侧块结构之间的关联；解决方案的结构与矩阵的列块结构相同，而右侧则反映了其行块结构。

\subsubsection{Block Structured Vectors and Matrices}

虽然计算机实现向量和矩阵的数据结构不一定要考虑到这种块状结构，但使用块状感知的容器还是有好处。

- 许多先进的迭代求解器都是基于将系统矩阵分解为宏观块，如公式\ref{6.2}所示，并且不对全局矩阵进行操作，而是对这些块进行操作（例如块预处理和域分解方法，如`Dirichlet` - `Neumann` ）。将这些块存储为全局容器内可直接访问的对象，可以避免访问这些块时昂贵的复制操作。这在矩阵案例中特别重要，因为(a)矩阵包含的数据比向量多得多，(b)`FEM`矩阵非常稀疏，因此以压缩形式存储；从全局压缩的矩阵中提取一个块是一个非常昂贵的操作，涉及到内存和处理时间。

- 压缩矩阵需要存储填充的矩阵条目的位置。稀疏块矩阵只需要存储块的位置；这种技术可以大大减少稀疏模式的大小。  [Fig6.3]显示了使用`DG`离散化的这种优化的效果，每个网格单元的6个DOF被放置在一个大小为$6 \times 6$的单一矩阵块中；因此，图案大小被缩小了36倍。如果矩阵使用双精度和64位索引，这些节省的部分几乎将整个矩阵的大小减少了一半。

- 像块状雅可比或块状高斯-赛德尔这样的块状感知的预处理程序是其标量对应程序对块状结构矩阵的扩展。如果这种块状结构没有反映在内存中，实现这些类型的预处理程序就会变得更加困难，性能也会受到影响。

这里显示的例子是基于单层阻断，但当然也有可能出现嵌套的阻断结构，特别是在考虑具有高度结构化函数空间的问题时。

如上所述，在合并索引范围时自然会出现块结构。每当两个（或多个）索引范围合并时，我们可以决定是否也要在我们的数据结构中明确地表示这个块状结构。因此，这两个过程在本质上是相互联系的，并与内部函数空间节点相联系，索引范围必须被合并。由于这些原因，`PDELAB`将这种合并和阻塞信息附加到函数空间树的内部节点上，这是通过提供两个模板参数来实现的，这些模板参数指定了一个`OrderingTag`和一个`VectorBackend`，这种机制将在$6.5$节中详细解释。

\subsection{Structure-preserving `DOF` Indexing}

在上一节中我们已经确定，通常有多种方法来列举一个函数空间的基函数，列举和块结构的选择取决于手头的问题。由于这个原因，应该可以为一个给定的函数空间挑选不同的排序策略。为了做到这一点，我们将函数空间作为基函数容器的概念与排序的概念分开，后者是控制空间的`DOF`向量的布局（顺序和块结构）的一个新组件（参见公式\ref{6.1}）。

这个布局组件应该能够接受一个函数空间的无序基函数集，并通过应用一组合并和阻塞操作来生成一个枚举。基本上，它是一个从基函数集到整数范围$0, \ldots, N$的映射，其中$N$是基函数的数量。在未来，我们将用$\mathcal{M}$来表示这种排序。为了实现这个地图，我们需要一种方法来唯一地识别每个基函数。要做到这一点，回顾一下有限元基将每个基函数$\varphi$与特定的网格实体$\tau$联系起来；因此，标量函数空间的基函数可以通过一对$(k, \tau)$来识别，其中$k$是一个整数，用于列举附属于同一网格实体的多个基函数$\tau$ 。

当处理复合空间（以树形表示）时，我们需要进一步明确各个叶子空间的基础函数，我们通过扩展一个叶子空间唯一的标识符$(\tau, k)$，将该叶子空间的树形路径附加到函数空间树的根部来实现这一点。

\textbf{Definition} 6.1 ( `DOFIndex` )。设$V$为复合函数空间，具有相关的结构树$\mathcal{B}$，底层网状结构$\mathcal{T}$和基数$\Phi=\left\{\varphi_{i}\right\}_{i \in 0, \ldots, N-1}$。此外，让$V_{j}$为$V$的叶子空间，其树状路径$j=\left(j_{1}, \ldots, j_{n}\right)$在$\mathcal{B}$中。最后，让$\varphi \in \Phi$与网格实体$\tau$相关的$k$的第-个基函数。那么 `DOFIndex` $\mathcal{D}(\varphi)$ 的 $\varphi$ 被定义为元组

\begin{equation} 
\left.\mathcal{D}(\varphi)=\left(k, \mathcal{G}(\tau), j_{n}, \ldots, j_{1}\right)\right)
\end{equation}

其中$\mathcal{G}$表示对$\tau$的身份进行适当的编码，以使$\mathcal{G}(\tau) \in \mathbb{N}^{n_{g}}$与一些固定的$n_{g}$的整数组成一个元组 。

\textbf{Remark} 6.1.在我们`PDELAB`的实现中，$\mathcal{G}$将一个网格实体映射到一对索引，一个是实体的几何类型，一个是它在当前索引集中的索引（注意，`DUNE`提供了单独的索引范围给



不同的几何类型，参考$2.3 .2$节和[^18] [^19]中关于实体枚举的进一步信息）。然而，在下面的讨论中，我们将假设 $\mathcal{G}$ 将所有网格实体映射到一个单一的、连续的索引范围 $(0, \ldots,|\mathcal{T}|-1)$ 。我们不写 $\mathcal{G}(\tau)$ ，而是写 $\tau_{k}$ 来表示索引为 $k$ 的网格实体。

请注意，对于网格实体$T$上的给定基函数`DOFIndex`，可以在不了解全局函数空间大小的情况下简单构建。同时，它编码了大量关于基函数相对于函数空间结构的位置信息，使得它成为索引变换$\mathcal{M}$的理想输入值。`DOFIndex`将有限元空间的基函数排列成一棵树，反映了空间的层次结构，如[Fig6.4]所示：函数空间的叶子由每个网格实体的一个节点扩展；各个基函数形成树的新叶子，成为其相关网格实体的子女。

考虑到网格实体的固定遍历顺序，这棵树直接引出了一个可能的基函数枚举。它可以通过对树进行深度优先的遍历，并在遇到基函数时按升序进行编号来获得。请注意，一般来说，我们对存储这个全局枚举不感兴趣；典型的`FEM`装配操作在任何时候都只涉及少量的基函数。因此，我们需要一种有效的方法来计算任意基函数`DOFIndex`$\mathcal{D}(\varphi)$的$\varphi$的枚举值，并根据其`DOFIndex`的表示方法来计算。

\subsection{Using Multi Indices for Vector and Matrix Access}

到目前为止，我们假设排序图$\mathcal{M}$的输出将产生一个平坦的连续排序，即它映射到一个连续的、基于零的范围 $(0, \ldots, N)$ 。如果仿真数据存储在块状结构的向量和矩阵中，这样的平面索引方案会带来很大的开销，因为我们在访问这样的容器中的条目之前，必须以某种方式重建块状索引。这涉及到计算一个块编号 $i_{b}$ 和一个块内索引 $i_{s}$ 。在这种情况下，一个更有效的寻址方案将使用一个地图 $\mathcal{M}$ ，直接创建一个两部分的索引，将这两个整数封装在一个元组中 $\left(i_{b}, i_{s}\right)$ 。如果有多层次的阻塞，这个元组的长度就会相应增加。  [Fig6.5]对一个具有一级阻塞和固定块大小的简单向量说明了这个想法。在这种情况下，仍然可以使用简单的数学运算来恢复块号和块内索引。然而，如果块的大小是可变的，这就不再可能了，必须找到不同的方法来识别正确的块，例如，在带有块偏移的查找表中进行二进制搜索。



在第6.1.1节中，我们已经表明，向量和矩阵的块状结构是由对函数空间树的节点的操作而产生的。由于`DOFIndex`本质上是对这种树状结构的编码，它包含了所有必要的信息，可以设计出非常有效的算法，直接将Dof Index转换为像上面例子中的多索引。这就是`DOF`排序框架在`PDELAB`中采取的方法：排序图$\mathcal{M}$的输出将是根据数据结构的选定块结构定制的多索引。

\subsection{Nodal Operations for `DOF` Numbering Gen- eration}

正如我们之前所看到的，由排序$\mathcal{M}$产生的基函数$\varphi$的（多）索引是以迭代方式构建的，从与$\varphi$相关的叶子空间开始，然后沿着函数空间树向上走，在每个节点进行必要的合并（如果需要，也可以进行阻断）操作。

因此，可以将整个排序图$\mathcal{M}$分解为一系列与`DOFIndex`的个别条目相关的节点操作$M_{i}$。在下文中，我们假设这种操作可能只取决于输出元组的当前尾部条目（即容器索引）和`DOFIndex`树中当前节点的子索引。例如，如果传入的容器索引是$\left(i_{1}, i_{2}\right)$，即一个长度为2的元组，而当前位置的`DOFIndex`条目是$j$，那么节点操作$M$可以采取两种形式之一。

\begin{equation} 
\begin{aligned}
&M^{f}\left(\left(i_{1}, i_{2}\right), j\right)=\left(i_{1}, m_{f}\left(i_{2}, j\right)\right) \\
&M^{b}\left(\left(i_{1}, i_{2}\right), j\right)=\left(i_{1}, i_{2}, m_{b}\left(i_{2}, j\right)\right)
\end{aligned}
\end{equation}

\begin{equation} 
\begin{aligned}
&\text { (flat transformation) } \\
&\text { (block transformation) }
\end{aligned}
\end{equation}

由于我们只对容器索引的最后一个条目感兴趣，我们将省略前面的条目，以后只写$(\ldots, i)$。

在`DOFIndex`树的叶子节点，我们只考虑以下琐碎的转换。给定一个初始的、空的容器索引`tuple() ` and a ` DOFIndex` $\left(k, \mathcal{G}\left(\tau_{l}^{(c)}\right), j_{n}, \ldots, j_{1}\right)$ ，转换 $M_{0}$ 由 $M_{0}((), k)=(k)$ 给出。其余的节点类型更为复杂；相应的转换将在以下章节中介绍。

\subsubsection{ Grid Entity Nodes }

网格实体节点的合并操作可以理解为交织方法的一个特例。一般来说，我们总是希望与单个网格实体相关的所有基础函数被组合在一起，因为这些基础函数通常会相互耦合。因此，我们通过以下方式定义网格实体转换

\begin{equation} 
M_{\mathcal{T}}^{f}\left((\ldots, i), \tau_{l}\right)=\left(\ldots, i+\sum_{j=0}^{l-1} s_{j}\right)
\end{equation}

其中$s_{j}$表示与网格实体$\tau_{j}$相关的基函数的数量。等价地，封锁的版本 $M_{0}^{b}$ 创建了一个长度为2的多指数，并通过以下方式给出

\begin{equation} 
M_{\mathcal{T}}^{b}\left((\ldots, i), \tau_{l}\right)=(\ldots, i, l)
\end{equation}



\subsubsection{Lexicographic Merging}

词典式合并按其相关的子空间对基函数进行分组，然后根据当前树节点中的子空间的顺序对这些块进行排序。给定一个具有直接子空间$V$的复合空间$\left(V_{j}\right)_{j=0, N-1}$，将索引$(\ldots, i)$从子空间$V_{j}$映射到$V$的索引范围的变换$M_{L}^{f}$是这样给出的

\begin{equation} 
M_{L}^{f}((\ldots, i), j)=\left(\ldots,\left(i+\sum_{k=0}^{j-1}\left|V_{k}\right|\right)\right)
\label{6.5}
\end{equation}

其中$|V|$表示$V$的维度（也就是基的大小）。这种策略对子空间的内部结构或子空间之间的关系不做任何假设，因此可以适用于任何类型的多成分空间。这种通用性和易实施性的结合，使得词法合并成为几乎所有现代`FEM`框架[^32][^15][^81]所实施的标准算法。

这个转换的封锁版本$M_{L}^{b}$同样非常简单。它只是将子索引$j$附加到现有的多索引上，这就产生了

\begin{equation} 
M_{L}^{b}((\ldots, i), j)=(\ldots, i, j)
\label{6.6}
\end{equation}

\subsubsection{Proportional Interleaving}

像第4.2.3节中的多组分运输例子这样的问题，可以从将每个组分的浓度值分组为小的perentity向量中大大受益，然后我们可以使用块感知的预处理程序直接反演。  [Fig6.2] (b)显示了一个由两个相同大小的空间按比例交织而成的矩阵的例子。

为了给有直接子空间$V$的复合空间$\left(V_{j}\right)_{j=0, N-1}$创建这种映射，每个子空间$V_{j}$必须与一个整数倍数$f_{j} \in \mathbb{N}$相关联，以便$\left|V_{j}\right| /\left|V_{k}\right|=f_{j} / f_{k}, j, k=0, \ldots, N-1$ 。例如，考虑一个空间$V_{1}$，它给每个顶点附加2个函数，给每个边附加4个函数。同时$V_{2}$为每个顶点附加1个函数，为每条边附加2个函数，得出的固定比率为$2: 1$ 。假设固定的倍率$f_{j}$，比例合并图$M_{P}$由以下公式给出

\begin{equation} 
M_{P}^{f}((\ldots, i), j)=\left(\ldots,\left(i \bmod f_{j}+\sum_{k=0}^{j-1} f_{k}+\left\lfloor\frac{i}{f_{j}}\right\rfloor \sum_{k=0}^{N-1} f_{k}\right)\right)
\label{6.7}
\end{equation}

而封锁的版本是由

\begin{equation} 
\left.M_{P}^{b}((\ldots, i), j)\right)=\left(\ldots,\left(i \bmod f_{j}+\sum_{k=0}^{j-1} f_{k}\right),\left[\frac{i}{f_{j}}\right\rfloor\right)
\label{6.8}
\end{equation}

请注意，与早期的转换不同，在这种情况下，封锁的版本$M_{P}^{b}$不仅向输出索引追加了一个新条目，而且还修改了现有索引的最后一个条目。



\subsubsection{Grouping Composite Spaces by Grid Entity}

如果没有一个固定的子空间大小的整数比例，上一节描述的静态合并程序将不再起作用。以这种方式交织来自子空间的值的主要原因是按网格实体聚类基础函数，以便例如创建一个共同的对角线矩阵块，然后可用于像雅可比块那样的预处理程序。在某些情况下，即使没有固定的尺寸比，这种实体聚类也是有益的，例如对于$p$-适应性`DG`乘积空间，所有的自由度都与单元相关；$p$-适应性只是导致不同数量的基函数被分配到每个单元中。借助于产品空间的实体聚类，我们可以保留这些空间的典型块状结构，尽管块的大小不同。  [Fig6.6]通过一个不太有用的例子来说明这一原则，即一个$Q_{1}$和一个$Q_{2}$空间的不相容组合，但展示了该原则的普遍适用性

回顾一下[Fig6.4]中的基函数树，我们可以看到，在其中，叶子空间中的所有基函数都是按其网格实体分组的。如果我们想在树的更高层次上进行这种分组，我们不能通过引入一种新的节点操作来实现；相反，我们必须重组树，将网格实体节点向树的根部进一步移动。这种树的修改结果显示在[Fig6.7]中，它描述了一棵由[Fig6.4]中的实体节点向上移动到第三层树而得到的树。在我们的实现中，输入树的这种复杂修改是由一个特殊的`TYPETREE`转换来处理的。



\subsection{`DOF` Ordering Library}

作为我们在`PDELAB`中促进块状求解器方案实现的工作的一部分，特别是对于多物理问题的模拟，我们基于上一节介绍的概念为`PDELAB`创建了一个新的`DOF`排序库。在下文中，我们将介绍该库的界面并强调其实现的相关部分。

这个实现的代码包含在`PDELAB`中，并在与该库的其余部分相同的开放源码许可下提供（参见$2.3)$节。

\subsubsection{Mesh Entity Processing}

执行$6.4$节中描述的每个节点的转换需要将网格实体信息$\mathcal{G}\left(\tau_{i}\right)$移动到`DOFIndex`元组的不同位置。为了简化这一过程，`DOFIndex`的实际存储布局并不将$\mathcal{G}\left(\tau_{i}\right)$作为索引元组的一部分来存储；而是为网格索引信息包含一个单独的数据结构。

一个典型的`FEM`网格将包含大量的网格实体；因此为每个实体创建一个单独的树节点是不可行的。我们的实现是创建一个单一的对象，从额外的数据结构中提取网格实体信息，并使用它来执行所有网格实体的合并操作。注意，这在我们的理论概念和实体节点的所有子节点的实际实现之间产生了语义上的不匹配。现在只有一个单独的子树来处理所有网格实体的子树，而不是为每个网格实体提供单独的子树。我们的实现通过引入第二种类型的树节点，即本地排序来解决这个问题。这些与标准排序不同的是，它们能意识到网格实体，并为每个实体存储单独的合并信息。

\subsubsection{Ordering Tree Creation}

正如我们之前所看到的，具有将`DOFIndex`映射到`ContainerIndex`的节点变换的树，与用户创建的函数空间树密切相关。因此，我们可以使用`TYPETREE`转换（参见$5.5$节），从用户提供的函数空间树中构建订购对象树。为了确定每个节点的索引映射策略，用户必须给每个函数空间节点加上附加标签。有两种选择可以做。

合并 对于内部节点，必须选择一种合并策略。目前，该库支持词典式、比例式和按实体式合并。在下面的例子中，这些将分别用$\mathrm{L}, \mathrm{P}$和$\mathrm{E}$作为符号。

阻断 用户必须决定是否应该在当前级别阻断`ContainerIndex`，即是否应该在其末端附加一个新的块级别，我们用$\mathrm{B}$表示阻断，用$\mathrm{F}$表示平坦。

然后通过一个`TYPETREE`转换来构建排序对象树，该转换遍历函数空间树并根据这些注释对每个节点进行转换。这个转换并没有下降到网格处理节点之外；与这些节点相关的子树是通过一个嵌套的转换来创建的，该转换创建了一个本地排序树（见上文）。这些排序有一个略微不同的编程界面，除了 `DOFIndex` 之外，还总是接收网格实体信息。

\textbf{Choosing a Merging Strategy}

合并策略是通过向函数空间的模板参数列表传递一个`OrderingTag`来选择的。对于叶子空间，用户通常可以省略这个标签，因为`DefaultLeaforderingTag`的默认值几乎总是正确的。

相反，复合空间要求用户明确地传递一个描述合并策略的标签。在词法合并和实体封锁排序的情况下，只需在模板参数列表中指定这个标签即可。


相比之下，比例交错排序还需要被告知每个子空间的乘数$f_{i}$（参见$\left.6.4 .3\right)$节），这些乘数作为运行时信息存储，因此需要一个标签类的实例。通常，这个实例可以在函数空间的构造器调用中就地创建。


在这里，我们使用了C++11的std::initializer_list来提供乘数列表，这就避免了在构造函数调用中第二次拼出标记类型。

为了证明这些注释的效果，考虑一下第4.2.2节中`Stokes-Darcy`例子的函数空间树。  [Fig6.8]在左手边描述了该树的两个版本，每个版本都有不同的合并和阻塞注释。在另一边，你可以看到由这些注释诱导出来的各自的排序树。此外，这个例子还显示了额外的节点$M_{\mathcal{T}}$，这些节点被插入到订购树中以处理网格实体信息。

\subsubsection{Automated Construction of Linear Algebra Contain- ers from Annotated Function Spaces}

`PDELAB`的原始后端架构总是使用单层的`DOF`阻塞，由`BCRS`模块中的矩阵实现支持。由此产生的设置是一个固定的向量和矩阵结构，全局不变的块大小是唯一的参数。

由于`DOF`映射组件（当时是`GridFunctionSpace`树的一部分）和线性代数后端之间缺乏整合，旧的实现要求用户在多个地方指定块大小。在这些地方对块大小的指定不一致会导致编译失败或运行时错误的内存访问，导致程序崩溃或更糟的是$-$提供错误的结果，因为值没有被写入正确的向量和矩阵条目。



另一方面，我们可以从较低层次的构建块中推断出关于嵌套向量和矩阵结构以及相关块大小的所有信息。解析空间和测试空间向量的块结构可以从底层函数空间的每个节点的块注释中构建出来，矩阵的块结构可以通过对这两个向量的张量积得到：例如，在一个程序中，解析空间使用块大小为6的向量，测试空间使用块大小为3的向量，相应的矩阵必须由大小为 $3 \times 6$ 的块组成。

作为我们对`DOF`的映射基础设施`PDELAB`的修改的一部分，我们创建了一套TMP，在编译时实现了这个功能；有了这个功能，用户现在只需要在创建函数空间时指定所需的块结构；然后网格函数空间可以用来自动创建和初始化正确结构的向量；`Grid0perator`为矩阵提供了类似的功能，大大减少了其模拟设置不一致的可能性。

向量和矩阵的确切结构由下一节介绍的后端标签决定。

\textbf{Choosing a Vector Backend and a Block Structure}

`VectorBackend`的函数空间模板参数有双重作用，既选择用于`DOF`向量的后端库，又选择其块结构。目前，`PDELAB`只提供了一个叫ISTL `VectorBackend`的单一向量后端标签。在其默认形式下，ISTL `VectorBackend<>`（所有模板参数都是默认的），阻断在当前级别被禁用。如果用户想创建区块，有必要区分两种不同的情况。

- 当创建大量的小块时（例如用于交错合并），这些块通常被存储在一个静态分配的固定大小的块向量中（一个`BlockVector<FieldVector<double, block_size>`）。这里，块的大小必须在编译时知道，并作为一个额外的模板参数传递。


请注意，由于块的大小是作为模板参数给出的，相关的向量不支持可变大小的块，不适合用于例如$p$-适应性`DG`离散。这是acISTL中底层向量和矩阵实现的一个限制，它没有对可变大小的密集块的有效支持。

- 与lexicographic合并相关的大型宏块被存储为一个嵌套的块向量，其类型为

这个向量中的块是动态分配的，因此大小可能不同。这种类型的向量是通过在后端标签中指定dynamic_blocking来创建的。


当用户为一个函数空间创建一个`DOF`矢量时，`TMP`会遍历底层空间树，检查矢量后端标签并自动构建矢量类型。该算法还检查树状分支之间的不一致（例如不同的静态块大小），如果无法推导出向量类型，则会引发编译时错误。然后，本地`ISTL`矢量被包装在符合`PDELAB`后端接口的`PDELAB`容器中。这个包装器负责在 `PDELAB` 接口（例如用多索引 `ContainerIndex` 的入口访问）和 `ISTL` `API` 之间进行调解。

系统矩阵的结构也是以类似的方式推导出来的，它结合了安塞斯和测试空间向量的块结构，分别决定了列块和行块。尽管如此，用户在创建 `Grid0perator` 时必须指定一个矩阵后端描述符。这个描述符不影响矩阵类型（因为它是由解和残差向量推导出来的），但目前用于在两种不同的方法之间切换，以构建所产生的`ISTL` `BCRS`矩阵的稀疏模式。

\subsubsection{Arbitrary Index Permutations}

稀疏线性问题中变量的顺序对稀疏直接求解器[^35] [^52] [^100]的性能有很大影响；例如，减少矩阵带宽以最小化直接求解器产生的额外填充是很重要的。为此，最著名的技术是`Cuthill-McKee`算法[^33]及其反向变体[^53]，其基础是根据系统矩阵的广度优先遍历来重新排列行和列。另一个重要的优化围绕着重新排序DOF来创建一个三角矩阵，可以使用简单的前向或后向替换来解决，这是一些用于多孔介质中特定传输问题的上行方案`FVM`的可行技术。

虽然可以在组装代数问题后的后处理步骤中执行所需的索引互换，但这种方法涉及到复制矩阵和向量，因此在运行时间和所需内存方面产生了很大的开销。另一方面，我们可以通过给排序树中的现有节点增加一个排列组合步骤，将排列组合直接集成到索引映射中。这样的排列组合可以通过将原始的排序标签包裹在装饰器标签 ordering::Permuted 中来请求，如下例第3行所示。







由此产生的排序标记包裹了原始标记；它在其构造函数中期望有一个原始标记的实例（参见第7行）。在排序树被初始化之后， permutation最初将每个索引映射到它自己。它以`std::vector`的形式存储在排序标记内部，用户可以很容易地访问和改变。

在实现层面上，排列组合是由一个额外的排列组合节点$M_{P}$来执行的，该节点被注入到属于被装饰的排列标签的节点的正上方的排列树中。这些新节点与第6.4节中介绍的节点不同：它们不对应于原始`DOFIndex`树上的映射操作，因此必须不消耗`DOFIndex`元组的一个条目；相反，它们只对容器索引的尾部条目进行烫平。给定一个permutation $P: \mathbb{N} \rightarrow \mathbb{N}$ ，相关的转换$M_{P}$就这样被赋予了

\begin{equation} 
M_{P}((\ldots, i))=(\ldots, P(i))
\end{equation}

作为一个例子，在[Fig6.9]中，一个函数树的一些节点被装饰上了包络标签；你可以在相关的排序树中看到额外的包络节点$M_{P}$。

在内部，装饰器`ordering::Permuted`是在`TMP`库的基础上实现的，该库负责将额外的节点注入树中，并包含挂钩到生成排序树的`TYPETREE`转换的基础设施。使用这个库，也可以将多个装饰器堆叠在一起。

\subsubsection{Algorithm}


排序树的中心任务是将给定的`DOFIndex`$\mathcal{D}$映射到其相应的容器索引。在`PDELAB`中的每个向量或矩阵访问都要进行这一操作；因此其性能非常重要。另一方面，设计一个性能良好的这种映射算法的实现是令人惊讶的棘手。这种困难大部分来自于基于模板的排序树之间的根本不匹配，它只能以一种必须在编译时固定的方式进行遍历，而存储在$\mathcal{D}$中的索引是运行时信息。解决这个问题的一个明显的方法是恢复到动态多态性。如果Ordering树中的所有节点都继承自一个带有虚拟函数`mapIndex(const DOFIndex &, ContainerIndex &)`的基类，我们可以在运行时查找`DOFIndex`中指定的子节点，并通过虚拟函数调用将其分派给它。然而，由于我们之前在非内联函数调用的成本方面的经验（参见表5.1），并且由于最初的实验表明这种解决方案对性能有很大的影响，我们反而将两种最常见的节点类型（lexicographic和interleaved）的实现移到一个共同的基类中，重新启用了大量的编译器优化。这种方法产生了递归算法6.1。对于默认实现不支持的节点类型，该算法允许排序提供一个委托对象，在运行时通过虚拟函数调用使用动态调度，并由网格实体排序和包络装饰器使用 $(\mathrm{cf}$ .行 $2-5)$ 。然而，请注意，对于标量函数空间的常见场景，我们避免使用动态代码路径，因为网格实体位于排序树的根部。

\subsection{ Impact on Overall Assembly Framework}

与现有的PDE装配框架相比，本章提出的几个设计选择是不寻常的，特别是计算`DOF`顺序的专用软件组件的想法，为识别分层离散函数空间中的`DOF`与`LA`容器中的条目而引入单独的数据结构，以及为块矩阵和向量使用多索引。对DOF集的 "收集/分散 "操作（通常与网格实体相关）在`FEM`框架的装配算法中起着核心作用（参见第2.2.5节；因此，我们描述的大量扩展的`DOF`处理架构需要进一步修改和优化整个`PDELAB` 。

幸运的是，正如[^15]中所列出的，`PDELAB`旨在尽可能地隔离用户与全局函数空间和指数的直接交互；理想情况下，用户永远不必与`DOFIndex`或`MultiIndex`对象直接交互。

另一方面，我们不得不在一些地方修改框架代码，以保留旧的解决方案的性能，这在计算上是比较便宜的。在下文中，我们强调了一些被优化的重要区域。有了这些优化，新的框架在几乎所有的函数空间上都优于旧的实现；唯一的例外是非常简单的布局，比如标量`FVM`空间；对于这些特殊情况，我们实现了一个非常基本的替代排序基础设施，可以用一组不同的排序标签来选择。有兴趣的读者可以参考`API`文件中的`PDELAB`以了解更多细节。

\subsubsection{Constraints Storage}

函数空间可以因为各种原因而受到约束，包括基本的边界条件、并行计算中的过程边界以及具有不符合$h$-细化的符合空间。通常情况下，空间中受约束的DOF的部分会相当小；出于效率的考虑，它们因此被存储在稀疏的数据结构中。当`PDELAB`被构思时，$\mathrm{C}++$中唯一可用的稀疏数据结构是std::map，它提供了一个键值存储，被实现为一个平衡的红黑树；因此在大小为$N$的地图中查找一个键需要进行$\mathcal{O}(\log N)$元素比较。

C++11提供了一个更快的替代方案，即哈希图的实现std::unordered_map。哈希图提供了一个平均$\mathcal{O}(1)$的查找时间，因此更适合我们的多查询情况。同时，它们的最坏情况下的复杂度为$\mathcal{O}(N)$，并且依赖于一个哈希函数，该函数应该为所有键生成均匀分布的哈希值。

我们要散列的对象是可变长度的非负整数序列（`DOFIndex`和`MultiIndex`对象）。对于每个单独的整数，身份函数可以作为一个完美的散列（参见[^79]），因此我们只需要一个结合两个散列的函数$h: \mathbb{N} \times \mathbb{N} \rightarrow \mathbb{N}$。事实证明，为这一目的找到一个好的函数比预期的要难，因为，除了与网格实体有关的索引条目外，其余的元组条目通常都是非常小的整数，因此只包含非常小的熵。

在用简单的哈希函数进行了一些失败的实验后，例如 `Boost` [^114] ，我们意识到哈希表只有用非常强大的哈希梳理函数才是可行的，因为当桶的数量增长超过 $\approx 1000$ 时，这些函数会崩溃。幸运的是，随着解释型语言的日益普及，以及它们对变量查找的依赖，催生了许多优秀的哈希函数，它们对当前的中央处理器（CPU）进行了很好的优化。这个领域中一些比较突出的例子是FNV[^51]、SIPHASH[5, 6]以及CITYHASH[^31]和MURMURHASH[^90]系列的函数。这些算法主要是对不透明的字节流进行散列，通常是相当长的字节流，但是CITYHASH也提供了以下实现，用于组合两个64位的散列。


这个函数即使在具有多达$1 \times 10^{8}$个DOF和不同矩阵排序和阻塞方案的大型复合空间上也能产生非常好的桶分布。当考虑到这个哈希函数的全部64位输出时，我们无法在两个`DOFIndex`或`ContainerIndex`对象之间产生一个哈希碰撞。同时，该函数是无分支的，避免了除法或模数计算等昂贵的操作。在英特尔酷睿i7$4960 \mathrm{HQ}$的单核上，每秒钟可以进行大约$2.4 \cdot 10^{8}$次哈希组合。

通过使用`combine_hashes()`为我们的索引图元实现哈希计算，并随后将约束容器和所有类似的数据结构从基于树的地图切换到哈希表，我们能够完全减轻更复杂的索引所带来的开销。

\subsubsection{Index Caching and Optimized Batch Mapping}

正如前几节所述，我们已经投入了大量的精力来加快从`DOFIndex`到`ContainerIndex`的转换速度。然而，这种转换在`DOF`的映射过程中引入了一个额外的步骤，这需要额外的计算能力，更重要的是需要内存带宽，后者甚至被这样一个事实所放大，即一个`DOFIndex`需要比一个简单的平面索引的单一整数多得多的存储。

另一方面，大多数矩阵/向量入口访问模式涉及到这些值的多次关联读取或写入；在矩阵组装的情况下，相同的指数甚至可以重复用于不同的容器（测试和分析空间的向量和矩阵）。为了减少对这种重要使用场景的性能影响，我们实现了一个缓存，预先计算并存储与LocalFunctionSpace相关的容器指数。此外，在一个单一的操作中映射所有这些值使我们有可能使用一个更有效的算法，如下所述，加快了转换过程本身。

在大多数情况下，算法不需要访问单个的DOF；访问模式通常对应于与特定网格实体或单元格本地函数空间相关的所有值的收集/散射操作。本地函数空间是`PDELAB`中的核心组件之一，几乎所有用户和离散函数空间之间的交互都需要它，而实体上的访问非常罕见，目前只用于`DUNE`网格的基于实体的`MPI`通信设施。

当然，在这些情况下可以简单地执行`DOF`的映射，方法是天真地在源索引上循环，并一次一次地转换这些索引，但是这种方法由于性能原因是有问题的，因为它导致了非常混乱的内存访问模式，并且通常需要在每个`DOF`中至少调用一个虚拟函数（参见算法6.1）。另一方面，为一个给定的单元或网格实体构建所有DOF的集合已经需要对函数空间树进行完整的遍历。通过重新组织算法$6.1$来批量映射相关DOF的完整集合，可以提高该过程的效率，并通过将`DOF`枚举的每叶尺寸与嵌入LocalFunctionSpace和Ordering树中的函数空间结构知识相结合，避免虚拟函数调用。由此产生的算法包括以深度优先顺序对Ordering树的单一遍历；我们在一个名为`LFSIndexCache`的新组件中实现了它，并修改了`PDELAB`中的所有相关地方以使用这个缓存。

除了加快对ContainerIndices的访问，缓存还能解决和存储本地约束结构。  `PDELAB`有一个非常通用的方法来处理函数空间约束。为了处理像悬挂节点细化这样的情况，受约束的DOF可以创建对任意其他DOF的贡献；特别是，这些其他条目不一定是 `LFSIndexCache` 中包含的当前本地函数空间的一部分。如第6.6.1节所述，`PDELAB`在一个包含转换后的`DOFIndex`值的哈希图中存储约束。在矩阵组装过程中，每写一个矩阵条目都需要在测试和分析空间的约束图中分别查找该行和列的约束状态。如果找到一个约束，该条目要么被忽略，要么被累积到约束中指定的不同矩阵条目上（后者发生在悬挂节点约束的情况下）。考虑到本地测试空间和Ansatz空间的大小分别为$N$和$M$，因此编写相关的矩阵块需要进行$M \times N$的地图查询。通过缓存约束信息，查找次数减少到$M+N$次，这在高阶离散化方案中产生了巨大的性能提升，因为每个单元有大量的DOF。

\subsubsection{Optimized Handling of Grid Information}

网格实体转换方程 \ref{6.3} 需要与每个网格实体相关的基函数数量 $s_{k}$ 。有大量的网格实体，所以存储这些信息有很大的内存成本；此外，频繁的查找大大增加了缓存压力。另一方面，大多数`FE`空间实际上将相同数量的基函数与每个固定类型的网格实体联系起来。例如考虑第2.2.2节中讨论的空间。例如，一个$P_{1}$空间为每个顶点关联一个基函数，而对任何其他类型的网格实体不关联基函数，而FV空间对每个网格单元正好使用一个基函数。如果我们知道这个数字对所有网格实体来说是一个常数$s$，我们就可以将方程\ref{6.3}简化为

\begin{equation} 
M_{\mathcal{T}}^{f}\left((\ldots, i), \tau_{l}\right)=(\ldots, i+l s)
\end{equation}

并因此可以将网格实体转换的存储复杂性从$\mathcal{O}(\mid \mathcal{T})$降低到$\mathcal{O}(1)$ 。虽然这个方程看起来很有限制性，要求所有类型的网格实体的基函数数量恒定，但请记住，正如前面所解释的，`PDELAB`实际上为每个几何体类型维护了单独的索引和大小信息，使得这个优化适用于更多的`FE`基。该实现使用一个特殊的接口到基函数定义，以查询基是否具有这种恒定大小的属性。如果它有，我们甚至不需要遍历网格来找出每个网格实体的基函数的数量。如果这个信息不是先验的（例如，因为基础支持$p$-adaptivity），排序大小信息的更新过程会遍历网格并记录每个实体的大小。同时，它跟踪是否真的存在每个实体的差异。每个没有任何差异的几何体类型在网格遍历后会自动切换回恒定尺寸的实现，释放相关的每实体信息。因此，绝大多数的`PDELAB`程序都会自动受益于这种优化。


\section{Implementation Details}

在这一章中，我们强调了一些关于 `DUNE-MULTIDOMAIN` 的实现的技术细节。一般来说，`PDELAB`的这些多域扩展的软件设计是由两个主要目标驱动的。

对现有`API`的最小扩展 整体设计的一个重要目标是`API`尽可能地重用现有的`PDELAB`基础设施，使现有的用户代码（如本地运算器内核）得到重用，使`PDELAB`用户更容易理解和编写DUNEMULTIDOMAIN代码。

利用软件的模块化来实现代码重用 我们利用现有的`PDELAB`基础设施来尽量减少代码的重复，并能够从核心组件的改进中获益。这方面最重要的例子可以在对实例问题的支持中找到（参见第7.5节）。

在最高层面上，`DUNE-MULTIDOMAIN`的用户界面是基于描述第四章中定义的多领域问题的构件的数学实体与$\mathrm{C}++$对象的直接映射。然而，在对性能敏感的残差和雅各布集合过程中，以有效的方式处理这些组件确实带来了一些软件设计上的挑战。在下文中，我们将强调其中的一些挑战以及我们的一些解决方案，这些解决方案允许我们重新使用标准`PDELAB`的大部分功能，这些功能可以在多域背景下不变地使用。


\subsection{Multi Domain Space Composition}

在目前的实现中，`MultiDomainGridFunctionSpace`受到了一些限制。它只支持其直接子代的DOFs的词法合并，虽然它可以包含由存量`PDELAB`函数空间组成的任意复杂的复合空间，但`MultiDomainGridFunctionSpace`本身必须位于函数空间层次结构的根部，这使得它无法嵌套这些空间。

在内部，两个空间都是基于相同的实现。唯一的区别来自于在`MultiDomainGrid`上直接定义的子空间和那些只存在于`SubDomainGrid`上的子空间之间的语义不匹配。正如第3.1.4节所解释的，每个`SubDomainGrid`为其限制的网格实体集提供其连续的编号。因此，一个网格实体将在多个子域中携带不同的索引。此外，$\mathrm{C}++$严格的静态类型检查使得定义在`MultiDomainGrid`上的子空间不可能对`SubDomainGrid`实体对象进行操作，反之亦然。为此，遍历`MultiDomainGridFunctionSpace`的`TYPETREE`访问者对其回调使用了一个标签调度机制（参见第2.4.1节），这使得他们能够区分不同的情况；当遇到定义在`SubDomainGrid`上的子空间时]上定义的子空间时，原始的`MultiDomainGrid`网格单元对象首先被转换，如第3.1.4节所解释的那样，然后被传递给标准的`PDELAB`，多域无关的访问者版本，它将处理对当前子树的遍历。这个转换过程目前不能以可靠的方式在函数空间树的更深处进行，这就是对`MultiDomainGridFunctionSpace`嵌套的限制。

\subsection{Subproblem and Coupling Definition}

根据定义 $4.4$ ，一个子问题 $P$ 是由其残差 $\mathcal{R}_{P}$ 、底层网格上的谓词函数和表示 $\mathcal{R}_{P}$ 中引用的子空间的索引列表 $\mathcal{I}_{P}$ 的组合给出。鉴于现有的$\mathrm{C}++$对象用于整个多域函数空间、谓词和算子（残差），这个定义直接延续到实现中，在第四章给出的代码例子中可以看到。

此外，我们还提供了一个增强版，它用子空间的$\mathrm{C}++$类型取代了子空间的数字索引，改善了可读性和编译器的静态错误检查，并使子空间列表与整个多域函数空间中的实际空间顺序无关。如果有多个子空间具有相同的$\mathrm{C}++$类型，则该增强版本不可用，因为这使得编译器无法将子空间类型映射到唯一的子空间对象。


谓词对象的精确语义取决于提供空间多域离散化的基础引擎。

总的来说，我们能够在理论框架的抽象数学对象和相应的实现构件之间提供一个非常直接的转换路径。此外，所产生的$\mathrm{C}++$对象是非常轻量级的，并带有最小的依赖性$-$，对底层多域离散技术的任何细节都没有假设，将仿真设置的高层部分与负责处理空间离散的部分解耦。

\subsection{ Synthesizing Tailored Subspaces for Residual Components}

实现层面的一个重要挑战是整合现有的`PDELAB`局部算子，用于组装子问题残差。这些实现方式期望它们所操作的局部函数空间相对于所包含的叶子空间拥有特定的结构，不能用于整个多域函数空间。另一方面，所有的子问题都需要指定其相关残差所定义的变量集（参见定义4.4）。利用这些信息，我们能够在`MultiDomainGridFunctionSpace`周围自动生成一个薄的包装对象，模拟子问题所需的函数空间结构，并自动将任何`DOF`的访问转化为较大的`MultiDomainGridFunctionSpace`所定义的指数。





清单$7.1$中所示的子问题希望是整个多域函数空间的第四和第二子空间（指数为零）。这个例子展示了子问题特定子空间所需的两个核心能力。

过滤子问题通常不是在整个多域函数空间上定义的；通常这甚至不可能，因为不是所有的子空间都会在子问题的空间域上定义。因此，合成的子问题子空间必须从全局空间中过滤掉所有不适用的子空间。

重新排序 正如前面所解释的，支持重用现有的汇编内核是非常理想的。一般来说，我们不能假设所有这些内核都是根据函数空间组件的共同排序来编写的，所以针对子问题的子空间必须支持子空间的即时重排序。

在较低的实现层面上，合成的子空间的`API`取决于具体的子空间集。如果子问题只需要一个子空间，我们的合成对象的界面必须反映原始标量函数空间的界面，以允许透明的使用。另一方面，选择多个子空间需要即时创建一个新的LocalFunctionSpace节点来聚合所选的子空间。在我们的实现中，这个合成的节点总是反映了一个标准 `PDELAB` `CompositeGridFunctionSpace` 的界面。

这些合成空间的创建主要是通过`TYPETREE`库中的几项支持性基础设施来实现的，特别是单组件情况下的代理节点（第5.7.1节）和如果子问题在多个子空间上运行时的过滤节点（第5.7.2节）。

通过只在局部构建这些针对子问题的子空间，我们最大限度地减少了相关的开销，因为只有少量的基函数与每个网格单元相关，我们必须为其存储整体空间和子空间之间的映射信息。此外，局部基函数的数量并不取决于整个网格的整体空间大小，这就避免了大型模拟的潜在可扩展性问题。

\subsection{Efficient Creation of Statically Typed Visitor Captures}

在装配过程中，基础设施代码必须遍历子问题和联接的列表（在下文中称为装配参与者或只是参与者），并在当前网格单元/交叉口上调用它们各自的装配内核。由于参与者被存储在深度为1的`TYPETREE`树中，这种迭代是通过对该树应用访问者来进行的。根据当前的装配操作（残差或雅各布）和内核的类型（体积、边界、耦合等），这些参与方的特定内核需要访问不同数量和类型的信息。表$7.4$包含了为雅各布式装配而调用的内核所需的不同种类的信息列表。所有包含这些信息的对象都必须存储在穿越装配参与者的访问者里面，同时还有必须让装配参与者访问的精确类型信息。

看一下表7.4，我们可以看到，这需要大量不同的访问者实现，它们都只因存储信息的类型而不同。请记住，不可能使用一个包含所有数据的单一实现，因为这些数据不会在所有情况下都可用。例如，在计算单元格积分时，不可能在访问者中存储一个交集。

为了避免一次又一次地重新实现访问者的数据存储部分，我们设计了一个方案，从构建时传递给它的信息中自动构建一个自定义访问者，并保留所有存储类型的静态信息。我们通过定义成对的数据isting$7.2-$来实现这一点。

一个单元格对象容器的数据包装器和所有需要的对象类型的容器生成函数。一个用于捕获网格单元的例子显示在清单 $7.2$ 中。

实际的实现使用了这些包装器的几种变体，它们针对不同的捕获场景，例如，通过引用捕获与通过值捕获，以及只读捕获与可写捕获。由于这些变体的数量很少，我们能够通过少量的预处理器宏来生成所有需要的包装器，从而避免大量的代码重复。

在第二步中，我们将这些包装对象传递给实际访问者的生成器，该生成器继承了所有这些对象，反过来使它们的访问器类型定义和方法在访问者中可用。同时，这种技术避免了任何意外的数据重复传递，因为编译器会因为同一方法/类型名称在多个基类中可用而引入重载歧义而产生错误。为了避免正确性和性能问题，包装器被设计成不可复制的，所以构造过程依赖于$\mathrm{C}++11$完美的转发和移动构造来初始化存储捕获数据的访问者基。此外，该实现还需要在变量模板上支持任意数量的数据包装器。清单 $7.3-$ 用于调用参与者特定的本地操作方法的向量


然后，通过用两个元函数对通用访问者模板进行参数化，选择要调用的实际函数，随后在参数化的访问者上调用静态成员函数，将上面介绍的`wrap_*()`调用的结果反馈给它，从而建立实际的访问者。在`alpha_volume()`方法的例子中，访问者是这样创建的。


由`add_data() ` implements the ` TYPETREE`访问者接口返回的访问者，然后由本地装配器应用于装配参与者的树。它为每个参与者调用functors::alpha_volume functor，它解压存储在访问者中的数据，并以解压后的信息在本地运算器上调用`alpha_volume()`（参见清单 $7.3$ ）。

\subsection{Time Dependent Problems}

`PDELAB`的特点是基于线法的时间积分器的有效实现。正如我们在第2.1.5节中所看到的，这些时间积分器需要对残差的时间和空间部分进行单独访问。

为了避免完全重新实现与那些单独的残差和雅各布贡献的装配和正确加权累积相关的程序逻辑，`PDELAB`包含了一个基于回调的通用装配器，用于单步时间积分方法。这个装配器使用两个独立的固定`Grid0perator`s，分别用于残差和雅各布的空间和时间贡献（参见第2.1.5节。它的工作方式是从这两个固定装配器中提取适当的回调引擎（用于模式装配、残差或雅各布装配），并将其包裹在一个新的回调引擎中，在不同的时间点对这两个组件进行必要的评估，累积其结果并根据时间整合方案的要求对其进行加权。这些回调引擎只在单个网格单元的水平上运行，因此不需要意识到整个网格结构。为了穿越网格（或者在我们的多领域设置中的多个网格），静止的网格操作者提供了一个特殊的组件，叫做全局装配器。这个组件接受一个回调引擎并遍历网格，设置所需的局部函数空间，处理全局向量和矩阵，并为每个单元和交叉点调用回调引擎的适当回调函数，只提供局部信息。然后，固定的网格操作者使用其中一个固定操作者的全局装配器，将其新构建的回调引擎应用于网格，而不需要任何关于如何执行网格遍历和/或`DOF`处理的进一步知识。

这种在股票`PDELAB`层面上的关注点分离使我们有可能重新使用默认的OneStep`Grid0perator`来组装多域实例问题，这有两个好处，一是避免了我们多域扩展中的代码重复，二是为用户提供了组装实例问题的统一方法，而不考虑空间问题结构。

\subsection{Performance Characteristics}

在我们的实现中，我们利用了这样一个事实：整个多域问题总是定义在一个`MultiDomaingrid `上。因此，我们可以通过迭代这个共同的、底层的网格来进行问题的组装。然后，我们用额外的逻辑来扩展现有的`PDELAB`装配器，通过评估相关的谓词来检查当前的集成域（在我们的例子中，单元格和单元格交叉点）是否属于每个定义的子问题和耦合，这引入了$\mathcal{O}\left(N_{S}+N_{C}\right)$的开销，其中$N_{S}$和$N_{C}$ 表示子问题和耦合组件的数量。另一方面，这些谓词的评估通常会非常便宜，因为它只涉及两组`SubDomainGrid`指数的比较。

另一个更重要的开销来源是构建针对子问题的子空间，这需要在整个局部函数空间和针对子问题的子空间的局部基础函数指数之间建立映射，以及与使用元网格和这些元网格之间的网格对象的必要转换有关的性能惩罚。

正如我们在下一章中所看到的，使用`DUNE-MULTIDOMAIN`相对于虚构的`PDELAB`模拟，确实带来了一定的性能损失，但正如这些数字所显示的，我们认为其程度完全可以接受，因为我们还必须考虑到我们正在解决的实际问题更加复杂。





\section{Applications}

创建一个像本论文中提出的软件框架涉及到大量的工作，但就其本身而言，其结果并不十分有用。为了获得成功，它必须被用来建立真正的应用，而在它的帮助下，实现这些应用应该变得非常容易。

`DUNE-MULTIDOMAINGRID`通过了这两道关卡。在本章的第一部分，我们展示了该框架如何帮助建立第四章中两个例子问题的模拟，使我们能够重用大量的现有构件，并尽量减少新代码的数量。此外，我们通过与使用标准`PDELAB`的等效单域问题仿真相比较，研究了该框架在耦合泊松问题上的性能。

自其早期开发阶段以来，我们的框架已被其他研究人员用作构建多领域应用的工具。在本章的第二部分，我们介绍了一篇博士论文的简短概述，该论文主要依靠`DUNE-MULTIDOMAIN`进行数值研究。

请注意，除了这个例子，还有大量基于`DUNE-MULTIDOMAIN`的外部研究项目，包括模拟物质通过毛细血管壁的运输 [^8] [^7] ，模拟双相多孔介质和单相自由流动与蒸发的耦合系统 [^87] [^88] 以及模拟固态激光共振器 [^124] 。此外，还有一个正在进行的博士项目是基于第4.2.3节中提出的两相/单相多孔介质耦合模型的。

\subsection{Poisson Problem on Two Subdomains}

我们首先看一下第四章中的简单模型例子方程\ref{4.2}，它描述了单位广场左右两半的两个耦合泊松问题。

\begin{equation} 
\begin{aligned}
-\Delta u_{i} &=f & & \text { in } \Omega_{i}, \quad i \in\{L, R\}, \\
u_{i} &=g & & \text { on } \Gamma_{i, D} \\
\nabla u_{i} \cdot \boldsymbol{n} &=j & & \text { on } \Gamma_{i, N} \\
u_{L} &=u_{R} & & \text { on } \Gamma_{C} \\
\left(\nabla u_{L}\right) \cdot \boldsymbol{n} &=\left(\nabla U_{R}\right) \cdot \boldsymbol{n} & & \text { on } \Gamma_{C}
\end{aligned}
\end{equation}

这个版本的问题稍显一般，因为它允许`Dirichlet`和`Neumann`两种边界条件。这个例子也可以作为一个 "正常 "的PDE问题，在整个领域$\Omega$上有一个单一的变量$u$，我们用它来衡量我们的框架相对于股票`PDELAB`中的标准实现的开销。

通过细化单元格上的初始网格，用`DUNE-GRID`中的结构化网格YaspGrid对该域进行离散。细化后，网格被包裹在`MultiDomainGrid`中，域的左半部和右半部被分配到不同的子域$\Omega_{L}$和$\Omega_{R}$ 。为了证明我们的框架对符合和不符合离散化的适用性，我们随后对这些域上的子问题使用了几种不同的连续和不连续离散化的方法。

在研究实际的多域问题之前，我们通过比较库存`PDELAB`和我们对单域问题的扩展的装配性能，研究了多域装配器的额外基础设施所带来的性能开销。为此，我们首先创建了一个标准的`PDELAB`函数空间和`Grid0perator`直接在`MultiDomainGrid`之上，并测量了最重要的装配操作的运行时间，特别是约束条件评估、矩阵模式构建以及残差和雅各布评估。

之后，我们为整个域$\Omega$创建了一个带有单一子空间的`MultiDomainGridFunctionSpace`，并使用它来定义一个子问题，将单一子空间与库存`PDELAB`版本中的本地算子相结合。通过将该子问题反馈给多域`Grid0perator`，我们可以复制标准`PDELAB`的设置，而无需子域扩展。

然后，我们使用$Q_{1}$和$Q_{2}$空间以及符合和不符合离散性的网格，在262144个单元上测量了两个版本的性能。结果显示在表8.1中。在大多数情况下，从默认的`PDELAB`版本到多域感知装配程序会增加运行时间约$10 \%$，在我们看来这是合理的，因为框架需要对每个网格单元和交叉点进行额外的子域成员资格检查。正如我们在第三章中所看到的，这些检查的开销对于像本例中使用的YaspGrid这样的快速、结构化的主机网格尤其明显。


另一个重要的问题是，我们能够在多域模拟中重用多少单一的物理学代码。这个例子是通过修改现有的`PDELAB`程序并简单地将标准的`PDELAB`组件替换为第四章中介绍的多域意识的对应组件而创建的，这对于一个对`PDELAB`有相当了解的开发者来说是一个非常简单的过程。唯一的主要变化是在 `MultiDomainGrid` 中设置子域布局，复制 `VTK` 的输出代码，为每个子域编写单独的文件，最重要的是，本地耦合运算符的实现，你可以在清单A.1中找到它。总的来说，对问题的相关修改（包括新的算子）大约有200行代码，而且这些代码对于一个在标准`PDELAB`方面有经验的开发者来说很容易理解。另一方面，我们能够重新利用现有的Poisson问题的连续Galerkin离散化的实现，以及`DG`离散化的高级SIPG方案，这需要大量的工作来重新实现。

\subsubsection{Iterative Solution With `Dirichlet`-`Neumann` Scheme}

为了证明我们对弱耦合求解方案的支持，我们还实现了一个耦合泊松-泊松问题的版本，该版本使用`Dirichlet` - `Neumann` 固定点迭代，在每个子域上交替求解，使用另一个子域上的解来提供耦合界面上的`Dirichlet` 或 `Neumann` 边界数据。这个方案的详细数学描述可以在 [^123] 中找到。在下文中，我们提供了如何在`DUNE-MULTIDOMAIN`中实现这一方案的概要，以证明我们的框架为高级多域解决方案的发展提供了大量的支持。

作为一个起点，我们假设已经按照上一节的解释设置了带有单片机的模拟。我们将重复使用为该问题创建的函数空间和子问题。然而，我们必须为 `MultiDomainGridFunctionSpace` 使用不同的矢量后端标签。


有了这个标签，`PDELAB`将为每个子问题创建具有独立块的向量和矩阵。

\begin{equation} 
\left(\begin{array}{cc}
A_{L} & 0 \\
0 & A_{R}
\end{array}\right)\left(\begin{array}{l}
u_{L} \\
u_{R}
\end{array}\right)=\left(\begin{array}{l}
f_{L} \\
f_{R}
\end{array}\right)
\end{equation}

由于我们的问题只通过边界条件（即右手边）进行耦合，矩阵的非对角线块是空的。有了这个矩阵结构，我们就可以直接访问各个块，例如将$A_{L}$传递给`ISTL`求解器，而不必复制任何数据。同时，`DUNE-MULTIDOMAIN`保留了所有关于全局问题结构的信息，并在耦合界面的每一侧识别相应的DOF。

作为第一步，我们需要创建一个局部算子，计算`Neumann`和`Dirichlet`耦合项的剩余贡献。为了这个例子的目的，我们决定在耦合界面上以弱的方式应用`Neumann`和`Dirichlet`边界条件；局部算子的实现可以在清单A.3中找到。它基于用于对流-扩散-对流问题的默认`PDELAB``DG`算子，通过将骨架积分改为耦合项并调整它们来组装`Neumann`或`Dirichlet`边界条件，使用另一个子域的解作为边界数据。所有其他的积分核被简单地删除。我们创建了这个算子的两个实例，每个子域都有一个。





有了这些局部运算符在手，我们就可以创建两个耦合描述符。在这种情况下，我们决定通过`Dirichlet`条件将左边的子问题耦合到右边的子问题。


这里，我们省略了第二个耦合；它是第一个耦合的精确镜像，除了耦合的本地算子不同（`Neumann`而不是`Dirichlet`）。

在创建`Grid0perator`之前，我们首先要构建一个矩阵后台，可以存储每个矩阵块每行的非零条目数的单独数字。




我们再次省略第二个`Grid0perator`的代码。这两个网格算子中的每一个都只将贡献集合到属于其相应子问题的残差和雅各布矩阵的部分。

为了完成我们的模拟，我们现在需要为该问题设置一个线性求解器后端。为此，我们使用自定义的ISTL_SEQ_Subblock_Backend，它接受包含两个子问题的全局系统矩阵，但只对矩阵的一个块行进行操作，对应于一个子问题。




随着所有组件的到位，我们只剩下实现实际的定点迭代，现在它变成了一个非常短的循环，直接对应于方案的数学描述。



在这里，我们假设在循环外计算了一个合适的起始缺陷$r_{0}$；$\alpha \in(0,1)$是一个阻尼参数。在 `DUNE-MULTIDOMAINGRID` 的测试目录中可以找到这个例子的完全工作实现。

与单片机求解器类似，我们能够为这个实现重用大量的构件；子问题和它们的局部运算符不必从单片机多域问题中改变。同时，我们能够完全改变线性代数容器的块状结构，只需在其他不变的函数空间上改变一个模板参数。最后，虽然这个问题涉及更多的网格算子和求解器，但它们直接源于我们需要解决`Dirichlet`-`Neumann`迭代的两个独立问题；此外，只要用户注意使用特殊的线性求解器后端，所有这些组件的构造都非常简单明了。这个例子中唯一真正的新代码是用于耦合的本地算子，即使在这里，我们也设法从以下事实中获益：`DUNE-MULTIDOMAIN`中该算子的界面与标准的`DG`骨架残差非常相似，允许我们从现有的`DG`算子实现中复制和粘贴大部分的代码。

我们通过与上一节中的单体求解器进行比较来评估这个弱耦合求解器的性能。我们选择了连续$Q_{2}$空间和$\mathrm{`DG`}_{2}$空间的组合来证明我们的`Dirichlet`-`Neumann`实现的通用性，它在方程层面而不是线性代数层面应用耦合边界条件。我们以$10^{-10}$的目标缺陷减少来解决这个问题；`Dirichlet`-`Neumann`求解器以不同的阻尼参数来运行迭代方案，我们将子域求解的精度降低到$10^{-4}$以提高其性能。结果可以在表8.2中找到。正如在这样一个简单的设置中所预期的那样，单片机的求解器大大超过了迭代方案，因为它对两个子域之间的耦合效应拥有更多的信息。保持在


请注意，`Dirichlet`-`Neumann`方案只列出了外部迭代的次数；这些迭代中的每一次都涉及两个线性求解，每个子域一个，尽管其精度低于单片机方法中的线性求解器。

看了这个例子，我们得出结论，我们的框架已经成功地实现了它的目标，即大大简化了多领域模拟的开发，同时为开发者提供了解决方案的灵活性，并设法提供有竞争力的性能。

\subsection{Coupling of Free Flow and Porous Media}

虽然耦合泊松问题可以作为框架性能和实施影响的一个很好的验证例子，但它并不是一个真正的多领域问题，该框架将被应用科学家使用。为此，我们还实现了第4.2.2节中描述的斯托克斯-`Darcy`问题的模拟。

地下水在多孔介质中流动的耦合系统和嵌入的自由流动通道对于很好地理解岩溶含水层非常重要。岩溶含水层是常见的饮用水源，因此，广泛了解这些水库内的传输过程对确保水质至关重要，例如，准确预测污染物的扩散，使这个例子与现实世界的应用更加相关。

对于我们的实验，我们使用了方程 \ref{4.6} - \ref{4.8a} 中的公式，它是基于 [^29] [^49] 的方法。我们使用标准的Taylor-Hood元素$\left(P_{2} / P_{1}\right)$对自由流域进行离散，并在多孔介质中采用了3阶元素的SIPG方案。请注意，我们避免在多孔介质中采用混合公式，从而产生了一个将两个领域的变量真正不同的问题。早期对两个领域中的至少一个领域采用不连续Galerkin方法的例子可以在 [^110] [^55] [^54] [^76] 中找到。Chidyagwai和Rivière [^30]对CG和`DG`离散化的不同组合在各自子域中的综合比较可以找到。  [Fig8.1]展示了一个用我们的代码进行的模拟实例。

请注意，为了本论文的目的，我们主要是从系统组装的角度对Stokes-Darcy问题感兴趣。出于这个原因，我们将自己限制在用直接求解器库SuperLU [^35]来解决所产生的线性代数问题。另一方面，也有非常有趣的领域分解方法来解决这类问题，例如 [^37] [^38] [^9] ，作者提出用Steklov-Poincaré类型的算子对两个子域的外部和内部自由度进行嵌套迭代。实施这种方法和其他先进的求解器可能会成为一个很好的验证平台，以便将来在求解器层面对库进行补充。

与泊松例子一样，我们不必从头开始为各个子问题创建局部算子，因为`PDELAB`已经包含了适当的实现，我们能够以最小的努力适应多域环境。因此，我们只需要为Beavers-Joseph-Saffman条件方程\ref{4.8a}编写一个算子，它可以在清单A.2中找到，包含185行代码，以及问题设置和驱动程序，它包含大约240行代码，包括网格设置和解决方案输出。这个例子的完整源代码可以在`DUNE-MULTIDOMAIN`的测试目录中找到，文件stokesdarcy2.cc。

在DUNEMULTIDOMAIN和基于`DUNE`的多孔介质框架$DuMu^x$[^50]的基础上，对两相多孔介质和单相自由流与蒸发的耦合系统进行了更详尽的工作，可参见[^87][^88]。

\subsection{Signal Transport in Neurons}

详细的生物物理模型和模拟是提高我们对复杂的生物过程的理解和验证这些过程的现有的、更粗略的功能模型的重要工具。计算能力的稳步提高和模型开发水平的进步，推动了更复杂的模拟的趋势，将多个物理过程结合起来；同时，生物模拟往往涉及复杂的几何形状。综合来看，这些挑战为我们的多领域软件框架形成了完美的应用场景。在下文中，我们将介绍`DUNE-MULTIDOMAIN`是如何在Pods, Schönke, and Bastian [^105]和Pods [^104]最近进行的神经元信号传播模拟中成功用于克服这些技术挑战的。

他们的工作重点是动作电位沿轴突（一种连接两个神经元的纤维，作为通信渠道）的传播。一般情况在[Fig8.2]中描述：一个信号在一个神经元中启动，然后沿轴突传播到第二个神经元，在那里通过释放特殊的神经递质分子触发信号。轴突是一种中空的、充满液体的纤维，嵌入细胞外液中。它的外壳是一层膜，将其两侧的离子溶液产生的电荷分开，但也包含特殊的离子通道，在某些情况下会允许离子穿过膜。沿着轴突传输的信号以电压脉冲的形式出现；它通过静电电位、膜两侧的离子浓度和膜离子通道的不同激活机制之间的复杂互动沿轴突传播。对这些相互作用特别感兴趣的是直接靠近膜的薄层，称为`Debye layer` [^86]，它在电位和离子浓度方面都表现出强烈的梯度（见[Fig8.2]），必须在膜界面的正常方向上以非常高的分辨率进行解析。

在作者采用的模型中，离子浓度和由此产生的电动势的演变是由电扩散的泊松-恩斯特-普朗克方程[^84]描述的，该方程使用恩斯特-普朗克方程对离子运动进行建模

\begin{equation} 
\frac{\partial n_{i}}{\partial t}+\nabla \cdot \boldsymbol{F}_{i}=0
\end{equation}

其中$\boldsymbol{F}_{i}$表示离子种类$i$的离子通量，定义如下

\begin{equation} 
\boldsymbol{F}_{i}=-\boldsymbol{D}_{i}\left(\nabla n_{i}+z_{i} n_{i} \nabla \phi\right)
\end{equation}





这些通量又通过离子浓度与无量纲电势$\phi$相耦合。电势本身由泊松式方程描述

\begin{equation} 
\nabla \cdot(\epsilon \nabla \phi)=-\frac{e^{2} n^{*}}{\epsilon_{0} k_{B} T} \sum_{i} z_{i} n_{i}
\end{equation}

这里，$n_{i}$是离子浓度相对于比例浓度$n^{*}=N_{A}$（阿伏伽德罗常数）的相对浓度，而$z_{i}$表示离子物种的价态$i$和$D_{i}$其位置相关的扩散系数；$T$是温度，$k_{B}$是玻尔兹曼常数，$\epsilon$是相对电容，它也可能取决于$x$ 。

这些方程与描述基于Hodgkin-Huxley模型的离子通道活动的ODE系统相耦合 [^69] 。

\subsubsection{General Simulation Setup}

如[Fig8.3]所述，模拟域由两个不同的子域组成：带有离子浓度和电动势的细胞内/外液体（这是一个由多个非连接部分组成的子域的例子）和细胞膜，它被建模为一个非常薄的（厚度为1个网格单元）层，只携带电动势。膜两边的离子浓度通过`Neumann`型边界条件耦合，该边界条件来自霍奇金-赫胥黎模型的`ODE`系统。

静电势和离子浓度都用标准的$Q_{1}$有限元离散，所产生的非线性系统--除了耦合膜两边的ODEs--用隐式欧拉时隙方案和牛顿求解器以完全耦合的方式求解。然而，这个方案的实际实现是相当复杂的：它从结构化网格实现YaspGrid的一个实例开始，该实例被包裹在一个元网格中，用一个张量乘积的几何形状取代原始网格的均匀几何形状，在`Debye layer`范围内对膜的法线方向提供一个非常精细的分辨率。然后这个网格又被包裹在一个`MultiDomainGrid`中，该网格将问题的两个子域划分开来。

对于局部算子，作者能够重新使用现有的通用`Dune::PDELab ::ConvectionDiffusionFEM`实现膜上电势方程。相关的子问题是以这样一种方式设置的，即只向该算子提供具有电动势的子空间。然后，作者开发了这个通用算子的扩展版本，对全部变量（电势和离子浓度）进行操作，并纳入了Nernst-Planck方程的附加残差。

这些局部运算符被封装在适当的SubProblems中。在框架层面上缺乏广泛支持的主要领域是实现与他们的嵌入式`ODE`求解器的耦合边界条件；仿真的这一部分需要作者付出大量的实现努力。同时，他们指出，这种类型的耦合是一个非常特殊的情况，将很难以通用方式支持。

用于残差的空间和时间部分的`Grid0perator`s是常规的MultiDomain:。`Grid0perator`s，而实例问题的总体汇编（OneStep `Grid0perator`）、时间积分器和每个时间步长发生的代数问题的求解器都是由标准的`PDELAB`组件构建的，再次证明了我们的库和基础`PDELAB`工具箱中的抽象所促进的大量代码的重用。由于我们的多域扩展重用了时间集成基础设施和`PDELAB`中的线性求解器，作者不必学习第二套API来实现与单域问题基本相同的功能。


作者最初与嵌入牛顿求解器内的线性求解器的稳定性和性能进行了斗争。他们能够通过将所有子空间的DOF按实体进行全局分组来解决这个问题（用结构$\left.\left(\phi, n_{1}, \ldots, n_{N}\right)\right)$创建块，并在他们的向量和矩阵中反映所产生的块结构。这一变化极大地改善了他们的系统矩阵的结构（参见[Fig8.4]），重要的是，允许他们使用ILU或AMG的块感知版本作为他们的线性求解器的预处理。这些预处理程序然后对连接到每个网格顶点的小对角线矩阵块进行精确反演，大大改善了收敛率，使作者能够将他们的数值实验扩展到更大的问题规模。

\subsubsection{Parallel Computations}

虽然我们在本工作中省略了对这一功能的讨论，但DUNEMULTIDOMAIN支持基于`MPI`的并行性，以便根据`MultiDomainGrid`包裹的主机网格的领域分解能力模拟大型问题。神经元传播模拟的作者成功地采用了这一支持，使用重叠的`Schwarz`分解来并行化他们的应用，这使得他们能够通过在10个处理器上并行运行问题，将默认参数下的单次模拟运行时间从$\approx 19.2 \mathrm{~h}$减少到$\approx 2.52 \mathrm{~h}$。这相当于加快了$7.58$，这表明我们的框架没有为创建并行程序引入任何基本障碍。同时，并行化大部分是自动的；它是通过简单地指示网格在可用的处理器上进行负载平衡，以及切换到`PDELAB`的标准并行求解器后端之一实现的。该库和我们的扩展随后自动处理了通信和应用重叠`Schwarz`方案所需的修正。

作者发现，只在$x$方向上划分领域是有益的；这样一来，任何处理器的边界将始终与膜正交，如[Fig8.5]所示。这种设置避免了处理器边界与膜边界重合的情况，这将使离子通道的`ODE`求解器的实现严重复杂化，可能需要手动`MPI`通信来跨越处理器边界传输该求解器的输入和输出数据。此外，将并行分区限制在$x$方向也排除了由于$y$方向的高网格各向异性而可能引起的数值问题，因为`Debye layer`及其在该方向的细网格分辨率。



总之，我们可以说，我们的软件框架极大地促进了这些模拟的实施，减少了达成有效模拟所需的时间和精力，使作者能够转而专注于模型和方法的开发。特别是，他们能够扩展他们的模型，将髓鞘化的影响描述到他们的模型中：真正的轴突部分被包裹在一个叫做髓鞘的保护层中，这个保护层对细胞外电场有很大影响。他们的扩展模型能够正确模拟这种行为的变化。

最后，这个例子很有意思，它将两个元网格堆叠在一起。在他们的工作中，作者承认能够以这种方式扩展现有网格的功能的便利性，以及与`PDELAB`和`DUNE-MULTIDOMAIN`堆栈的其他部分的高度集成（例如，其无缝集成到对并行模拟的支持）。同时，他们注意到包装程序带来的大量性能开销，他们估计，对于他们的应用来说，堆栈中的每个元网格都有$\approx 30 \%$。这一经验强调了改进`DUNE`网格接口以减少这种性能影响的重要性。





\section{Conclusion}

开发偏微分方程的有限元模拟是一项具有挑战性的任务，需要数学、计算机科学和应用（模型）知识的混合，这种独特的专业知识组合不是所有涉及PDEs的研究者都能做到的。科学计算通过开发强大的求解器工具箱减轻了一些实施的负担，这些工具箱为关键的概念如网格、函数空间和积分形式提供了高水平的抽象，并与强大的线性代数库对接以解决由此产生的代数问题。然而，现有的框架几乎只关注单域问题。

多物理学，特别是多域问题代表了复杂度的另一个台阶；它们涉及多个域（以及网格），以及多个变量和这些变量子集上的方程。在这项工作中，我们创建了一个灵活的数学框架，描述了多域问题的各个组成部分及其相互作用，并提出了这些概念的软件实现（第四章）。这个框架的重点是灵活性和试验不同离散化和求解器的能力。为此，我们支持各种`Petrov-Galerkin`方法（包括连续的和不连续的），可以任意组合。

同时，由于问题规模巨大，运行时间的性能对于`FEM`的模拟是至关重要的。出于这个原因（为了与底层的`DUNE`框架保持一致），我们的实现在很大程度上依赖于$\mathrm{C}++$模板、静态多态性和函数内联。不幸的是，基于这些概念的基础架构代码往往会变得非常笨重，而且难以维护。出于这个原因，第五章引入了一个新的模板库，称为`TYPETREE`，用于处理异质对象的树。这个库构成了`PDELAB`和我们的多域扩展的复合函数空间实现的基础；它对通用树转换的支持被用来自动推导出一些像局部函数空间一样的依赖对象树。我们的扩展在很大程度上利用了这些转换的模块化来扩展标准`PDELAB`的功能，而不需要修补其源代码，例如引入额外的函数空间树节点类型，然后由默认的树转换自动支持。

在我们能够定义多个域的函数空间之前，我们需要将这些域离散化的网格，以及将这些网格相互联系起来的方法。为此，第三章介绍了一种机制，它扩展了现有的网格管理器，支持现有网格内的多个子域。这种方法使得在`DUNE`框架支持的所有网格管理器之上使用我们的框架成为可能，并且可以根据应用需求（结构化与非结构化、简单与六面体、顺序与平行等）选择基础网格。使用单一的 "主网格 "并将其分割成子域，大大简化了快速应用开发，因为它允许我们通过主网格在子域之间传输数据，从而避免了不相关的网格之间高度复杂的数据传输问题。

虽然我们的工作主要集中在问题的组装上，即计算残差向量和雅各布矩阵，但我们也开发了一个通用的概念，通过第六章中描述的`DOF`排序库来控制这些向量和矩阵的结构。对于多域问题有各种各样的解决方法，这些方法通常取决于DOF的特定列举和/或向量和矩阵的特殊块结构。用户可以通过简单地注释其函数空间树的节点来创建几乎任意的`DOF`命令和块结构。我们的实现再次依赖于`TYPETREE`库，在编译时捕获尽可能多的信息，使编译器能够生成高度优化的代码。由于`DOF`映射过程在问题组装过程中具有非常关键的性能，我们还提出并实施了一些优化和缓存机制。

这项工作的最终目标是通过简化问题的设置来加速多域模拟的发展，重要的是，利用现有的实现代码来实现整个问题的单一物理组件。在第7章中，我们描述了一些技术，这些技术有利于重复使用现有的针对子问题的集成内核，而不要求这些内核知道更大的多域问题结构。这基本上涉及到重新安排和过滤仿真数据，以适应现有代码所期望的问题结构。重要的是，我们已经展示了如何以最小的运行时间和内存开销来实现这种数据处理。

最后，在第8章中，我们通过将我们的框架应用于一些模型问题来证明我们方法的可行性，同时调查了我们的实施工作（以及可能的代码重用）和我们的实施所带来的性能开销。性能开销是存在的，但事实证明，由于组装多域与基本PDE问题所需的额外工作，性能开销在预期范围之内。我们通过强调一些依靠我们的软件在方法（数学）和建模（应用）层面上获得新的结果的外部研究成果，表明该实现还可以扩展到这些模型问题之外。

总之，我们的论文和相关的软件库大大简化了多领域问题仿真开发的技术环节，这使得应用科学家有可能专注于他们的核心兴趣，即模型和方法的开发，并提高他们的生产力。提供这种基础设施的重要性是不可低估的，我们的工作在发展阶段已经吸引了大量的合作和用户，这就是证明。

如前所述，我们的工作主要集中在PDE模拟的装配阶段；我们框架的一个直接扩展是改进对求解阶段的支持，为利用问题结构的求解器提供一些预包装组件，例如`Dirichlet` - `Neumann`迭代，如第8章所示的手动实现的例子，更好地支持具有异质大小的块结构的问题，或者提供一个块预处理框架，该框架仍然用单片机解决完全耦合的问题，但对每个子问题应用不同的、特定问题的预处理程序。

我们还将自己限制在对平行模拟的基本支持上。多域模拟中的各个子问题一般不会在其计算复杂度方面得到平衡；实现高效并行多域模拟的一个非常重要的步骤是支持为每个子问题附加适当的权重，并在负载平衡时考虑这些权重。然而，这需要在`DUNE`项目中进行协调，因为负载平衡是由网格管理器处理的；因此，加权的负载平衡将需要对典型的`DUNE`网格`API`进行修改。

另一个需要`DUNE`社区支持的问题是`MultiDomainGrid`的性能；正如第三章所概述的，网格接口中的一些设计决定给主机网格带来了巨大的性能开销。在这个领域，我们已经和其他核心的`DUNE`开发者一起开发了一个解决方案。由于它需要一些不兼容的接口变化，它将与`DUNE`$3.0$一起引入，后者将于2015年发布。

最后，将`DUNE-MULTIDOMAIN`与其他两个网格级解决方案`DUNE-GRIDGLUE` [^20] [^47]和`DUNE-UDG` [^46] [^67]的多域问题进一步整合，这将是有益的。




\section{Acknowledgments}

首先，我非常幸运地拥有彼得-巴斯蒂安教授作为我的导师。他让我自由地以我喜欢的方式开展工作，但当我遇到困难时，他总是能及时赶到；他的经验和知识对于我的研究项目中的许多基本设计决策取得正确的平衡是非常宝贵的。我还想对他在这些年里对我的鼓励和信任表示感谢。我还要感谢我的共同审查人Thomas Ertl教授博士，他不仅审查了这篇论文，而且在我在斯图加特期间在他的计算机可视化小组为我提供了一个学术 "家"。如果没有这种热情，那段时间会相当孤独。最后，我还要感谢Christian Engwer教授，我与他就`DOF`的错综复杂的重新排序问题进行了长期的、非常有益的讨论。

我也非常感谢Hans-Petter Langtangen教授，他让我有机会在奥斯陆的SIMULA研究实验室呆了一个夏天，并通过观察其他软件项目如何处理有限元来扩展我的视野。在此，我还要感谢SIMTECH卓越集群，他们不仅支付了我在斯图加特期间的工资，而且还资助了我在挪威的逗留。

最后，非常感谢我的软件的所有用户，他们不仅为我提供了宝贵的意见，而且看到他们如何在软件的帮助下完成迷人的科学工作，也让我感觉到我在过去几年里所做的工作是有真正意义的。

在我做这篇论文的过程中，我不是参加了一个，而是参加了两个由优秀同事组成的工作小组。感谢斯图加特的VIS小组的每一个人，特别是Markus Üffinger、Höferlin兄弟、Harald Sanftmann、Martin Falk、Michael Wörner和其他在座的人，他们就生命、宇宙和其他一切在数到三时还没有消失的东西进行了多次讨论--我怀念午餐后的咖啡聚会我还要特别感谢马丁提供的强大的IATEX模板。一年前，我搬到了海德堡，成为巴斯蒂安教授小组的一个 "正式 "成员。我很感谢Pavel Hron（和他那热情的室友）、Jurgis Pods、Rebecca `Neumann` 、Dominic Kempf和AG Wissenschaftliches Rechnen的所有其他成员，感谢你们所有愉快的讨论、在一起的时间、共享的咖啡、去商店的旅程和你们的帮助。

我要感谢我的父母汉内洛尔和迈克尔，感谢他们在我的学习和这篇论文的过程中给予的持续和永不动摇的支持，感谢我所有的室友卡门、塔尼娅、莱尼、丹妮拉和阿斯特里德，感谢他们在过去几年中的耐心，因为这篇论文的工作，我的心情常常变得阴沉。最后，我要感谢克里斯蒂娜，感谢她的热情和精神支持，感谢她的校对，感谢安雅，感谢她再次告诉我，生活不仅仅是义务，并给了我对自己的信心，我需要这种信心来度过过去几个月的压力。




[Fig1.1]: https://cdn.mathpix.com/snip/images/iNf91buap1oP_SoG2c3ZIs6h9Im_rtIkMwKWfZ4oq9s.original.fullsize.png
[Fig1.2]: https://cdn.mathpix.com/snip/images/SfroP-TdJRDDgSzF8DtNJ76q3-ziGqZVtIsOtihWWiM.original.fullsize.png
[Fig2.1]: https://cdn.mathpix.com/snip/images/v6Y2UH4z61Im3B7t5h7zhFNj9woF-oXYsx2IGio3pcA.original.fullsize.png
[Fig2.2]: https://cdn.mathpix.com/snip/images/dQ7AXVRj_vlNxeEOOUghCmaJhV3thuf_CXlARSYK6R4.original.fullsize.png
[Fig3.1]: https://cdn.mathpix.com/snip/images/zMzjBW761kgkXfWev45h69QRAsDyKk1LkJOzmiAoR8w.original.fullsize.png
[Fig3.2]: https://cdn.mathpix.com/snip/images/8aZ_H091sqG0e_grK-z_wBkDHsgIPL2MLhL6463wzKo.original.fullsize.png
[Fig3.3]: https://cdn.mathpix.com/snip/images/YOgt6qWRocyBoUpFLB1bi2dxr2QMPuhvulnJylNkIRg.original.fullsize.png
[Fig3.4]: https://cdn.mathpix.com/snip/images/OSG8CzW8WAZzjzilhpF0cObbsmBDNDql4y0oeN2nsu8.original.fullsize.png
[Fig4.1]: https://cdn.mathpix.com/snip/images/44upE4kEjVNTSLIK7nBFgWmkx1xfX8Bq4UdMafAwf6w.original.fullsize.png
[Fig4.2]: https://cdn.mathpix.com/snip/images/ei4oHfZCQMnVI8F6mErk7G83M3eFOqMRAPjEJmTcOK0.original.fullsize.png
[Fig4.3]: https://cdn.mathpix.com/snip/images/UkxDb7IALRObmOIHxhGe5DCz8BfjiosLYdDcDQttl-Y.original.fullsize.png
[Fig4.4]: https://cdn.mathpix.com/snip/images/nBbPQNIOdhEAo1CUyUl48da9ETLqOOcNzmPrl4CK060.original.fullsize.png
[Fig5.1]: https://cdn.mathpix.com/snip/images/v7iX0YNFUkpreeNs_ZvuT_pqsTxUZeT2hEUOtq5G7d0.original.fullsize.png
[Fig5.2]: https://cdn.mathpix.com/snip/images/HChBf36oeaWygPY13GMUDGUig_hLGnR8rTGzn2Z-kvo.original.fullsize.png
[Fig5.3]: https://cdn.mathpix.com/snip/images/JCgqXe2WwxD61ZkGorcIb75b5Uedmfe3HjZQMgx786E.original.fullsize.png
[Fig5.4]: https://cdn.mathpix.com/snip/images/NNx6omHiAMmm9-y3k38trUFJyjtoUsieK2L1hfH2xag.original.fullsize.png
[Fig6.1]: https://cdn.mathpix.com/snip/images/bR13VQMWkChEe0Sq1Gnle3wieg4Z_5nu8wRkbz30A3w.original.fullsize.png
[Fig6.2]: https://cdn.mathpix.com/snip/images/6VMpR6FHcuZ790sUyW3e7SNtukq00JfK_0w1Kk00mV0.original.fullsize.png
[Fig6.3]: https://cdn.mathpix.com/snip/images/PO4Hqw3W6Y0M6s_MZlFoyB3-E1dpe9hYMiEDWhVSASw.original.fullsize.png
[Fig6.4]: https://cdn.mathpix.com/snip/images/NkH7XEjDEsteyVqw_CcRPL9uR9rEzXkDa2UhmjIT4iA.original.fullsize.png
[Fig6.5]: https://cdn.mathpix.com/snip/images/6oEjeqyiAiY7gggzaQd6sAqTu03GOwa24aSggiiAXwQ.original.fullsize.png
[Fig6.6]: https://cdn.mathpix.com/snip/images/tCZoQxjB9JD5Bg8ax1j44K3yj8kQok4tJm1jTvG8VVg.original.fullsize.png
[Fig6.7]: https://cdn.mathpix.com/snip/images/01rOzanpuerYrbTtAcIt4tOnbKRO2FLKtQitevSFCcU.original.fullsize.png
[Fig6.8]: https://cdn.mathpix.com/snip/images/rnGXpWdRGW1td4x81XxBy326p09OZ8lM62gPXAQkvtg.original.fullsize.png
[Fig6.9]: https://cdn.mathpix.com/snip/images/rhZNWyjkj7lZvWBoDDnG5s04n8rjs8L-4p3uLix1QXg.original.fullsize.png
[Fig8.1]: https://cdn.mathpix.com/snip/images/yYRNpEXghk21MQMddUAJdc-pxyXaakLV3kc9QPD_LmI.original.fullsize.png
[Fig8.2]: https://cdn.mathpix.com/snip/images/c5GZlFmNBKsuoAUwCEFz7qfUygEIThu7v2RhfLUTBdI.original.fullsize.png
[Fig8.3]: https://cdn.mathpix.com/snip/images/zYzRBlOaZKBbucfDiMJuqnqvI3XqTj9EbhObhzSvGRA.original.fullsize.png
[Fig8.4]: https://cdn.mathpix.com/snip/images/QwiWq49U_IrInN3uh9AspIqM_8k1WyVX8dfKT3BolGE.original.fullsize.png
[Fig8.5]: https://cdn.mathpix.com/snip/images/M77hrbwCS-R9Yhw7M_bH1W8mB094lah1WJcPeiiHAYM.original.fullsize.png


\section{Bibliography}

[^1]: G. Aigner and U. Hölzle. "Eliminating virtual function calls in $\mathrm{C}++$ programs". In: ECOOP '96-Object-Oriented Programming. Ed. by P. Cointe. Vol. 1098. Lecture Notes in Computer Science. Springer Berlin Heidelberg, 1996, pp. 142-166. DOI: 10.1007/BFb0053060. 

[^2]: ALBERTA web site. URL: http://www alberta-fem.de (visited on $06 / 28 / 2013$ ).

[^3]: A. Alexandrescu. Modern $C++$ Design: Generic Programming and Design Patterns Applied. 1st ed. C++ In-Depth Series. Addison-Wesley Professional, 2001. 

[^4]: ALUGrid web site. URL: http://aam.mathematik.uni- freiburg. de/IAM/Research/ alugrid/ (visited on $06 / 28 / 2013) .

[^5]: J.P. Aumasson and D. J. Bernstein. "SipHash: A Fast Short-Input PRF". In: Progress in Cryptology - INDOCRYPT 2012. Ed. by S. Galbraith and M. Nandi. Vol. 7668 . Lecture Notes in Computer Science. Springer Berlin Heidelberg, 2012, pp. $489-508$ . DOI: 10.1007/978-3-642-34931-7_28. 

[^6]: J.P. Aumasson and D. J. Bernstein. SipHash Website. URL: https://131002. net/siphash/ (visited on $06 / 16 / 2013$ . 

[^7]: $\mathrm{K}$ . Baber. "Coupling free flow and flow in porous media in biological and technical applications: From a simple to a complex interface description". $\mathrm{PhD}$ thesis. Universität Stuttgart, 2014. 

[^8]: $\mathrm{K}$ . Baber, $\mathrm{K}$ . Mosthaf, B. Flemisch, R. Helmig, S. Müthing, and B. Wohlmuth. "Numerical scheme for coupling two-phase compositional porous-media flow and one-phase compositional free flow". In: IMA Journal of Applied Mathematics $77.6$ (2012), pp. 887-909. DOI: 10.1093/imamat/hxs048. eprint: http: //imamat.oxfordjournals. org/content $/ 77 / 6 / 887$ . full.pdf+html. $-

[^9]: L. Badea, M. Discacciati, and A. Quarteroni. "Numerical analysis of the Navier-Stokes/Darcy coupling". In: Numerische Mathematik 115 (2 2010). $10.1007 / \mathrm{s} 00211-009-0279-6$ , pp. 195-227. URL: http: //dx.doi.org/10.1007/ s00211-009-0279-6. 

[^10]: S. Balay, J. Brown, K. Buschelman, V. Eijkhout, W. D. Gropp, D. Kaushik, M. G. Knepley, L. C. McInnes, B. F. Smith, and H. Zhang. PETSc Users Manual. Tech. rep. ANL-95/11 - Revision 3.2. Argonne National Laboratory, 2011 .

[^11]: S. Balay, J. Brown, K. Buschelman, W. D. Gropp, D. Kaushik, M. G. Knepley, L. C. McInnes, B. F. Smith, and H. Zhang. PETSc Web page. 2011. URL: http: //www.mcs. anl.gov/petsc (visited on $05 / 26 / 2013$) .  

[^12]: S. Balay, W. D. Gropp, L. C. McInnes, and B. F. Smith. "Efficient Management of Parallelism in Object Oriented Numerical Software Libraries". In: Modern Software Tools in Scientific Computing. Ed. by E. Arge, A. M. Bruaset, and H. P. Langtangen. Birkhäuser Press, 1997, pp. 163-202.

[^13]: W. Bangerth, R. Hartmann, and G. Kanschat. deal.II Differential Equations Analysis Library, Technical Reference. http://www.dealii.org. 

[^14]: P. Bastian, K. Birken, K. Johannsen, S. Lang, N. Neuß, H. Rentz-Reichert, and C. Wieners. "UG - A flexible software toolbox for solving partial differential equations". In: Computing and Visualization in Science $1.1$ $(1997)$ , pp. $27-40$ . DOI: $10.1007 / \mathrm{s} 007910050003 .

[^15]: P. Bastian, F. Heimann, and S. Marnach. "Generic implementation of finite element methods in the Distributed and Unified Numerics Environment (`DUNE`)". In: Kybernetika $46.2(2010)$ , pp. 294-315 .

[^16]: P. Bastian. Lecture Notes on Scientific Computing with Partial Differential Equations. 2014. URL: http://conan.iwr.uni-heidelberg.de/teaching/numerik2_ ss2014/num2. pdf (visited on $08 / 23 / 2014$ ). [10,16]

[^17]: P. Bastian, K. Birken, K. Johannsen, S. Lang, V. Reichenberger, C. Wieners, G. Wittum, and C. Wrobel. "A Parallel Software-Platform for Solving Problems of Partial Differential Equations using Unstructured Grids and Adaptive Multigrid Methods". In: High Performance Computing in Science and Engineering '98. Ed. by E. Krause and W. Jäger. Springer Berlin Heidelberg, 1999 , pp. 326-339. DOI: $10.1007 / 978-3-642-58600-2_{-} 31 .

[^18]: P. Bastian, M. Blatt, A. Dedner, C. Engwer, R. Klöfkorn, R. Kornhuber, M. Ohlberger, and O. Sander. "A Generic Grid Interface for Parallel and Adaptive Scientific Computing. Part I: Abstract Framework". In: Computing $82.2-3(2008)$ , pp. $103-119 .

[^19]: P. Bastian, M. Blatt, A. Dedner, C. Engwer, R. Klöfkorn, R. Kornhuber, M. Ohlberger, and O. Sander. "A Generic Grid Interface for Parallel and Adaptive Scientific Computing. Part II: Implementation and Tests in `DUNE`". In: Computing $82.2-3(2008)$ , pp. $121-138 .

[^20]: P. Bastian, G. Buse, and O. Sander. "Infrastructure for the Coupling of Dune Grids". In: Proceedings of ENUMATH 2009. 2010, pp. 107-114.  [35, $147]

[^21]: G. S. Beavers and D. D. Joseph. "Boundary conditions at a naturally permeable wall". In: J . Fluid Mech $30.1(1967)$ , pp. $197-207 .

[^22]: M. Blatt and P. Bastian. "On the generic parallelisation of iterative solvers for the finite element method". In: Int. J. Comput. Sci. Eng. $4.1$ (Nov. 2008 ), pp. $56-69$ . DOI: 10.1504/IJCSE.2008.021112.   

[^23]: M. Blatt and P. Bastian. "The Iterative Solver Template Library". In: Applied Parallel Computing. State of the Art in Scientific Computing. Ed. by B. Kågström, E. Elmroth, J. Dongarra, and $\mathrm{J}$ . Waśniewski. Vol. 4699 . Lecture Notes in Computer Science. Springer Berlin Heidelberg, 2007, pp. $666-675$ . DOI: $10.1007 / 9^78-:3-540-75755-9_{-} 82 .

[^24]: R. H. Brooks and A. T. Corey. "Hydraulic properties of porous media". In: Hydrology Paper 3 (1964). Fort Collins: Colorado State University, pp. 27 110 .

[^25]: A. Burri, A. Dedner, R. Klöfkorn, and M. Ohlberger. "An efficient implementation of an adaptive and parallel grid in `DUNE`". In: Computational Science and High Performance Computing II. Ed. by E. Krause, $Y$ . Shokin, M. Resch, and N. Shokina. Notes on Numerical Fluid Mechanics and Multidisciplinary Design 91. Springer Berlin Heidelberg, 2006, pp. 67-82. DOI: $10.1007 / 3-540-31768-6.7 .

[^26]: F. Büttner, O. Radfelder, A. Lindow, and M. Gogolla. "Digging into the visitor pattern". In: Proc. of International Conference on Software Engineering 8 Knowledge Engineering (SEKE). Citeseer. 2004. 

[^27]: $C++B$ -tree Library. URL: https://code.google.com/p/cpp-btree/ (visited on $11 / 10 / 2014) .

[^28]: B. Calder and D. Grunwald. "Reducing indirect function call overhead in C++ programs". In: Proceedings of the 21st $A C M \operatorname{SIGPLAN-SIGACT}$ symposium on Principles of programming languages. ACM. 1994, pp. 397 408. 

[^29]: Y. Cao, M. Gunzburger, X. Hu, F. Hua, X. Wang, and W. Zhao. "Finite Element Approximations for Stokes-Darcy Flow with Beavers-Joseph Interface Conditions". In: SIAM Journal on Numerical Analysis $47.6(2010)$ , pp. $4239-4256 .

[^30]: P. Chidyagwai and B. Rivière. "Numerical modelling of coupled surface and subsurface flow systems". In: Advances in Water Resources $33.1(2010)$ , pp. $92-105 .

[^31]: CityHash website. URL: http: / / code.google.com/p/cityhash/ (visited on $06 / 16 / 2013) .

[^32]: COMSOL Multiphysics. COMSOL Inc. 2013. URL: http://www.comsol.com. 

[^33]: E. Cuthill and J. McKee. "Reducing the bandwidth of sparse symmetric matrices". In: Proceedings of the 196924 th national conference. ACM '69. $\mathrm{ACM}, 1969$ , pp. $157-172$ . DOI: $10.1145 / 800195.805928 .

[^34]: A. Dedner, R. Klöfkorn, M. Nolte, and M. Ohlberger. A generic interface for parallel and adaptive scientific computing: Abstraction principles and the `DUNE`-`FEM` module. Preprint No. 3. Mathematisches Institut, Universität Freiburg. 2009.   

[^35]: J. W. Demmel, S. C. Eisenstat, J. R. Gilbert, X. S. Li, and J. W. H. Liu. "A supernodal approach to sparse partial pivoting". In: SIAM Journal on Matrix Analysis and Applications $20.3(1999)$ , pp. $720-755 .

[^36]: Diffpack web site. URL: http://www.diffpack.com (visited on $06 / 29 / 2013$ ). 

[^37]: M. Discacciati, E. Miglio, and A. Quarteroni. "Mathematical and numerical models for coupling surface and groundwater flows". In: Applied Numerical Mathematics 43.1-2 (2002), pp. 57-74. DOI: DOI: $10.1016 / \mathrm{SO} 168-9274(02) 00125$ 3. 

[^38]: M. Discacciati and A. Quarteroni. "Convergence analysis of a subdomain iterative method for the finite element approximation of the coupling of Stokes and Darcy equations". In: Computing and Visualization in Science 6 (22004) .10 .1007 / \mathrm{s} 00791-003-0113-0$ , pp. $93-103$ . URL: http://dx.doi.org/10. $1007 / \mathrm{s} 00791-003-0113-0 .

[^39]: $\mathrm{K}$ . Driesen and U. Hölzle. "The direct cost of virtual function calls in $\mathrm{C}++$ ". In: Proceedings of the 11th ACM SIGPLAN conference on Object-oriented programming, systems, languages, and applications. OOPSLA '96. San Jose, California, USA: ACM, 1996, pp. 306-323. DOI: $10.1145 / 236337.236369$ . 

[^40]: `DUNE` download and licence page. URL: http: //www. dune-project. org / down oad.html (visited on $05 / 31 / 2013) .

[^41]: `DUNE`-`FEM` web site. URL: http://dune.mathematik.uni-freiburg. de (visited on $06 / 29 / 2013) .

[^42]: dune-foamgrid web site. URL: http://users. dune-project.org/projects/dunefoamgrid (visited on $09 / 16 / 2014$ . 

[^43]: dune-spgrid web site. URL: http: //dune.mathematik. uni- freiburg. de/grids/ dune-spgrid/ (visited on $09 / 16 / 2014$ ). 

[^44]: T. Dunne, R. Rannacher, and T. Richter. "Fundamental Trends in FluidStructure Interaction". In: Contemporary Challenges in Mathematical Fluid Dynamics and Its Applications 1 . World Scientific Publishing, 2010. Chap. Numerical Simulation of Fluid-Structure Interaction Based on Monolithic Variational Formulations, pp. 1-77. 

[^45]: H. Edwards. "Managing complexity in massively parallel, adaptive, multiphysics applications". In: Engineering with Computers 22 (3 2006), pp. 135 155 .

[^46]: C. Engwer and F. Heimann. "Dune-UDG: A Cut-Cell Framework for Unfitted Discontinuous Galerkin Methods". In: Advances in `DUNE`. Ed. by A. Dedner, B. Flemisch, and R. Klöfkorn. Springer Berlin Heidelberg, 2012, pp. 89-100. DOI: $10.1007 / 978-3-642-28589-9_{-} 7 . 

[^47]: C. Engwer and S. Müthing. "Concepts for flexible parallel multi-domain simulations". In: Domain Decomposition Methods in Science and Engineering 22. Lecture Notes in Computational Science and Engineering. Springer Berlin Heidelberg, to appear.

[^48]: A. Ern and J.L. Guermond. Theory and Practice of Finite Elements. Applied Mathematical Sciences 159 . Springer-Verlag New York, 2004. 

[^49]: A. Ern, A. F. Stephansen, and P. Zunino. "A discontinuous Galerkin method with weighted averages for advection-diffusion equations with locally small and anisotropic diffusivity". In: IMA Journal of Numerical Analysis $29.2$ (2009), pp. 235-256. DOI: 10. $1093 /$ imanum / drm050. eprint: http: / / imajna. oxfordjournals.org/content/29/2/235. full.pdf+html. 

[^50]: \quad$ B. Flemisch, M. Darcis, $K$ . Erbertseder, B. Faigle, A. Lauser, K. Mosthaf, $S$ . Müthing, P. Nuske, A. Tatomir, M. Wolff, et al. "DuMux: `DUNE` for Multi\{Phase, Component, Scale, Physics, ... $\}$ Flow and Transport in Porous Media". In: Advances in Water Resources $34.9(2011)$ , pp. 1102-1112. DOI: $10.1016 / j$ .advwat res $ .2011 .03 .007 .

[^51]: G. Fowler, L. C. Noll, and P. Vo. FNV hash algorithm. 1991. URL: http: //www. is the . com/chongo/tech/comp/fnv/index.html (visited on $06 / 16 / 2013$ . 

[^52]: A. George, J. R. Gilbert, and J. W. Liu, eds. Graph theory and sparse matrix computation. Vol. 56. Springer Verlag, 1993.

[^53]: A. George and J. W. Liu. Computer Solution of Large Sparse Positive Definite Systems. Prentice Hall Professional Technical Reference, $1981 .

[^54]: V. Girault and B. Rivière. "`DG` Approximation of Coupled Navier-Stokes and Darcy Equations by Beaver-Joseph-Saffman Interface Condition". In: SIAM J. Numer. Anal. $47.3(2009)$ , pp. 2052-2089. DOI: DOI: $10.1137 / 070686081 .

[^55]: V. Girault, S. Shuyu, M. F. Wheeler, and I. Yotov. "Coupling Discontinuous Galerking and mixed finite element discretizations using mortar finite elements." In: SIAM Journal on Numerical Analysis $46.2(2008)$ , pp. $949-979$ . URL: http: //www . redi- bw. de/db/ebsco. php/search. ebscohost. com/login . aspx? direct=true\&db=aph\&AN=31380724\&site=ehost-live. 

[^56]: P. Gottschling, D. S. Wise, and M. D. Adams. "Representation-transparent matrix algorithms with scalable performance". In: Proceedings of the 21 st annual international conference on Supercomputing. ICS '07. Seattle, Washington: $\mathrm{ACM}, 2007$ , pp. $116-125$ . DOI: $10.1145 / 1274971.1274989 .

[^57]: C. Gräser, U. Sack, and O. Sander. dune-fufem on `DUNE` web site. URL: http: //www. dune-project.org/discmodule.html (visited on $09 / 16 / 2014$ . 

[^58]: C. Gräser and O. Sander. "The dune-subgrid module and Some Applications". In: Computing $8.4(2009)$ , pp. $269-290 .  

[^59]: D. Gregor and J. Järvi. "Variadic templates for C++". In: Proceedings of the 2007 ACM symposium on Applied computing. SAC '07. Seoul, Korea: $\mathrm{ACM}, 2007$ , pp. $1101-1108$ . DOI: $10.1145 / 1244002.1244243 .

[^60]: G. Guennebaud, B. Jacob, et al. Eigen v3. 2010. URL: http://eigen.tuxfamily. org. 

[^61]: A. Gurtovoy and D. Abrahams. The Boost MPL Library. 2002-2004. URL: http://www.boost.org/libs/mpl/. 

[^62]: J. de Guzman, D. Marsden, and T. Schwinger. The Boost Fusion Library. 2001-2012. URL: http://www.boost.org/libs/fusion/.

[^63]: E. Hairer and G. Wanner. Solving Ordinary Differential Equations II: Stiff and Differential-Algebraic Problems. Springer Series in Computational Mathematics 14. Springer Berlin Heidelberg, 2010. 

[^64]: E. Hairer, G. Wanner, and S. P. Nørsett. Solving Ordinary Differential Equations I: Nonstiff Problems. Springer Series in Computational Mathematics 8. Springer Berlin Heidelberg, 2011. 

[^65]: F. Hecht. "C++ Tools to construct our user-level language". In: ESAIM: Mathematical Modelling and Numerical Analysis $36.5$ (Aug. 2002), pp. 809 836 . DOI: $10.1051 / \mathrm{m} 2 \mathrm{an}: 2002034$ .

[^66]: F. Hecht. Freefem++ manual. 3rd ed. Version 3.22. June 2013. URL: http: $/ / \mathrm{www}$ . freefem.org/ff $++/ \mathrm{ftp} /$ freefem+doc.pdf. $-$

[^67]: F. Heimann, C. Engwer, O. Ippisch, and P. Bastian. "An unfitted interior penalty discontinuous Galerkin method for incompressible Navier-Stokes two-phase flow". In: International Journal for Numerical Methods in Fluids $71.3(2013)$ , pp. $269-293$ . DOI: $10.1002 / f l d .3653 .

[^68]: M. A. Heroux, R. A. Bartlett, V. E. Howle, R. J. Hoekstra, J. J. Hu, T. G. Kolda, R. B. Lehoucq, K. R. Long, R. P. Pawlowski, E. T. Phipps, et al. "An overview of the Trilinos project". In: ACM Trans. Math. Softw. $31.3$ $(2005)$ , pp. $397-423$ . DOI: http://doi.acm.org/10.1145/1089014. $1089021 .[5,$ ,

[^69]: A. L. Hodgkin and A. F. Huxley. "A quantitative description of membrane current and its application to conduction and excitation in nerve". In: The Journal of Physiology $117.4(1952)$ , pp. $500-544 .

[^70]: P. Hudak. "Conception, evolution, and application of functional programming languages". In: ACM Comput. Surv. 21.3 (Sept. 1989), pp. 359-411. DOI: $10.1145 / 72551.72554 .

[^71]: A. Inc. Adobe Forest Tree Library. URL: http: //stlab. adobe. com/group_ forest_ related.html (visited on $11 / 10 / 2014) .

[^72]: International Organization for Standardization. `ISO`/IEC 14882:2003 Programming Language $C++$ . 

[^73]: Oct. 2003. International Organization for Standardization. `ISO`/IEC 14882:2011 Programming Language $C++$ . Sept. 2011 .

[^74]: W. Jäger and A. Mikelić. "Modeling Effective Interface Laws for Transport Phenomena Between an Unconfined Fluid and a Porous Medium Using Homogenization". In: Transport in Porous Media $78.3(2009)$ , pp. $489-508$ . 

[^75]: I. P. Jones. "Low Reynolds number flow past a porous spherical shell". In: Cambridge Philosophical Society, Proceedings. Vol. 73. Cambridge Univ Press. 1973, pp. $231-238 .

[^76]: G. Kanschat and B. Rivière. "A strongly conservative finite element method for the coupling of Stokes and Darcy flow". In: Journal of Computational Physics $229.17(2010)$ , pp. $5933-5943 .

[^77]: V. Karvonen and P. Mensonides. The Boost Preprocesser Library. 2001. URL: http://www.boost.org/libs/preprocessor/. 

[^78]: D. E. Knuth. The Art of Computer Programming: Fundamental Algorithms. 2nd ed. Vol. 1. Redwood City, CA, USA: Addison-Wesley, 1997.

[^79]: D. E. Knuth. The Art of Computer Programming: Sorting and Searching. $2 \mathrm{nd}$ ed. Vol. 3. Redwood City, CA, USA: Addison-Wesley, 1998. 

[^80]: H. P. Langtangen. Computational Partial Differential Equations: Numerical Methods and Diffpack Programming. Lecture Notes in Computational Science and Engineering 2. Springer-Verlag New York, $2003$ .

[^81]: A. Logg. "Automating the Finite Element Method". In: Arch. Comput. Methods Eng. $14.2(2007)$ , pp. $93-138 .

[^82]: A. Logg, K.A. Mardal, and G. Wells. Automated solution of Differential Equations by the Finite Element Method. Springer, 2012.

[^83]: K. Long, R. Kirby, and B. van Bloemen Waanders. "Unified Embedded Parallel Finite Element Computations via Software-Based Fréchet Differentiation". In: SIAM Journal on Scientific Computing $32.6(2010)$ , pp. 3323-3351. DOI: $10.1137 / 09076920 \mathrm{x}$ . eprint: http: //epubs. siam.org/doi/pdf/10.1137/09076920X. $-$

[^84]: B. Lu, M. J. Holst, J. A. McCammon, and Y. Zhou. "Poisson-NernstPlanck equations for simulating biomolecular diffusion-reaction processes I: Finite element solutions". In: Journal of Computational Physics $229.19$ $(2010)$ , pp. $6979-6994$ . DOI: $10.1016 / j \cdot j c p .2010 .05 .035 .

[^85]: A. Lumsdaine, J. Siek, L.Q. Lee, and P. Gottschling. Matrix Template Library web site. 2006. URL: http://osl.iu.edu/research/mtl/. 

[^86]: Y. Mori. "From three-dimensional electrophysiology to the cable model: an asymptotic study". In: arXiv preprint arXiv:0901.3914 (2009).  

[^87]: $\mathrm{K}$ . Mosthaf, $\mathrm{K}$ . Baber, B. Flemischh, R. Helmig, A. Leijnse, I. Rybak, and B. Wohlmuth. "A coupling concept for two-phase compositional porous-medium and single-phase compositional free flow". In: Water Resources Research $47.10(2011) $ . DOI: $10.1029 / 2011$ WRO10685. 

[^88]: K. Mosthaf. "Modeling and analysis of coupled porous-medium and free flow with application to evaporation processes". PhD thesis. Universität Stuttgart, 2014. 

[^89]: MpCCI Website. Fraunhofer-Institut für Algorithmen und Wissenschaftliches Rechnen SCAI. URL: http: //www.mpcci.de (visited on $06 / 08 / 2013$ ). 

[^90]: MurmurHash Website. URL: http: / / code. google. com / p / smhasher / wiki / MurmurHash3 (visited on $06 / 16 / 2013) .

[^91]: S. Müthing. dune-multidomain 2.0.1. Dec. 2014. DOI: 10.5281/zenodo. $13193 .

[^92]: S. Müthing. dune-multidomain web site. URL: https://github.com/smuething/ dune-multidomain (visited on $05 / 31 / 2013$ ). 

[^93]: S. Müthing. dune-multidomaingrid 2.3.1. Nov. 2014. DOI: 10.5281/zenodo. 12887. 

[^94]: S. Müthing. dune-multidomaingrid web site. URL: https: / / github. com/ smuething/dune-multidomaingrid (visited on $09 / 16 / 2014$ . 

[^95]: S. Müthing. TypeTree web site. URL: https: //github. com/smuething / dune typetree (visited on $10 / 18 / 2014) .

[^96]: S. Müthing and P. Bastian. "Dune-Multidomaingrid: A Metagrid Approach to Subdomain Modeling". In: Advances in `DUNE`. Ed. by A. Dedner, B. Flemisch, and R. Klöfkorn. Springer Berlin Heidelberg, 2012, pp. 59-73. DOI: $10.1007 / 978-3-642-28589-9_{-} 5 .

[^97]: S. Müthing, M. Blatt, D. Kempf, B. Skaflestad, A. Buhr, and A. Burchardt. Type Tree 2.3.1. Nov. 2014. DOI: 10.5281/zenodo. $10304 .

[^98]: M. Nolte. "Efficient Numerical Approximation of the Effective Hamiltonian". PhD thesis. Albert-Ludwigs-Universität Freiburg, 2011. 

[^99]: J. Palsberg and C. Jay. "The essence of the Visitor pattern". In: The Twenty-Second Annual International Computer Software and Applications Conference. 1998, pp. 9-15. DOI: 10.1109/CMPSAC.1998.716629. 

[^100]: S. Parter. "The Use of Linear Graphs in Gauss Elimination". In: SIAM Review $3.2(1961)$ , pp. 119-130. URL: http://www.jstor.org/stable/2027387. 

[^101]: PDELab Team. PDELab Version 2.0.0 Howto. URL: http://www. dune-project. org/pdelab/pdelab-howto- 2.0.0.pdf (visited on $10 / 17 / 2014$ ). 

[^102]: PDELab web site. URL: http: //www. dune - project. org/pdelab/ (visited on $09 / 16 / 2014) .  

[^103]: K. Peeters. tree.hh: an `STL`-like $C++$ tree class. URL: http: //tree.phisci.com (visited on $11 / 09 / 2014) .

[^104]: J. Pods. "Electrodiffusion Models of Axon and Extracellular Space Using the Poisson-Nernst-Planck Equations". PhD thesis. Universität Heidelberg, $2014 .

[^105]: J. Pods, J. Schönke, and P. Bastian. "Electrodiffusion Models of Neurons and Extracellular Space Using the Poisson-Nernst-Planck Equations - Numerical Simulation of the Intra- and Extracellular Potential for an Axon Model". In: Biophysical Journal $105.1(2013)$ , pp. $242-254 .

[^106]: S. Porat, D. Bernstein, Y. Fedorov, J. Rodrigue, and E. Yahav. "Compiler optimization of $C++$ virtual function calls". In: Proceedings of the 2 nd conference on USENIX Conference on Object-Oriented Technologies (COOTS) - Volume 2. COOTS'96. Toronto, Ontario, Canada: USENIX Association, 1996, pp. 1-1. URL: http: //d . acm.org/citation. cfm?id=1268049 1268050 .

[^107]: PreCICE web site. URL: http: //www5 . in . tum . de/ wiki/index. php / PreCICE_ Webpage (visited on $11 / 14 / 2014$ . 
[^108]: A. Quarteroni and A. Valli. Domain Decomposition Methods for Partial Differential Equations. Oxford University Press, Oxford, 1999. 

[^109]: A. Rasmussen. dune-cornerpoint web site. URL: https://github.com/OPM/dunecornerpoint (visited on $09 / 16 / 2014) .

[^110]: B. Rivière and I. Yotov. "Locally Conservative Couplings of Stokes and Darcy Flows". In: SIAM Journal on Numerical Analysis $42.5(2005)$ , pp. $1959-1977 .

[^111]: P. Saffman. "On the boundary condition at the interface of a porous medium". In: Stud. Appl. Math. $1(1971)$ , pp. $77-84 .

[^112]: A. Schmidt and K. G. Siebert. Design of Adaptive Finite Element Software. Lecture Notes in Computational Science and Engineering 42. Springer, 2004 . $-$

[^113]: A. Schmidt, K. G. Siebert, D. Köster, and C.J. Heine. ALBERTA 3.0: Technical Manual. Mar. 2012. URL: http: //www. mathematik. uni - stuttgart. de / fak8 / ians / lehrstuhl / nmh / down oads / alberta/alberta - man.pdf (visited on $06 / 28 / 2013) $ .

[^114]: The Boost $C++$ libraries. URL: http://www.boost.org. 

[^115]: Trilinos Website. URL: http://trilinos.sandia.gov/index.html. 

[^116]: B. Uekermann, H.J. Bungartz, B. Gatzhammer, and M. Mehl. "A Parallel, Black-Box Coupling for Fluid-Structure Interaction". In: Computational Methods for Coupled Problems in Science and Engineering, COUPLED PROBLEMS 2013. Ed. by S. Idelsohn, M. Papadrakakis, and B. Schrefler. $2013$ . 172 Bibliography

[^117]: UG web site. URL: http: // atlas.gcsc. uni- frankfurt. de / ug/ (visited on $06 / 29 / 2013) .

[^118]: E. Unruh. "Prime number computation". ANSI X3J16-94-0075/`ISO` WG21$462.1994 .

[^119]: D. Vandevoorde and N. M. Josuttis. $C++$ Templates - The Complete Guide. Addison-Wesley, $2002 .

[^120]: T. L. Veldhuizen. "Using C++ Template Metaprograms". In: $C++$ Report $7.4(1995)$ , pp. $36-43 .

[^121]: J. Walter, M. Koch, G. Winkler, and D. Bellot. The uBLAS Library. 20002010. URL: http://www.boost.org/libs/numeric/ublas/. 

[^122]: T. Wick. "Coupling of fully Eulerian and arbitrary Lagrangian-Eulerian methods for fluid-structure interaction computations". In: Computational Mechanics $52.5$ (2013), pp. 1113-1124. DOI: $10.1007 / 500466-013-0866-3 .

[^123]: B. Wohlmuth. Discretization Techniques and Iterative Solvers Based on Domain Decomposition. Lectures Notes in Computational Science and Engineering 17. Springer, Heidelberg, 2001. 

[^124]: M. Wohlmuth. "Modeling and Simulation of Solid-State Laser Resonators Using a Dynamic Multimode Analysis (DMA)". PhD thesis. Universität Erlangen-Nürnberg, $2012 .
[^125]: H. Yang. "Partitioned solvers for the fluid-structure interaction problems with a nearly incompressible elasticity model". In: Computing and Visualization in Science $14.5(2011)$ , pp. $227-247$ . DOI: $10.1007 / \mathrm{s} 00791-012-0177-9 .
