第 4 有限元法

4.1 有限元法理论基础

有限元法(Finite Element Method, FEM)是一种强大的数值分析方法,广泛应用于求解工程和科学中的偏微分方程问题。它由Richard Courant于1943年首次提出,经过几十年的发展,已成为现代数值计算的重要工具之一。

4.1.1 历史发展与应用背景

有限元法的发展经历了几个重要阶段:

  1. 理论奠基期(1943-1960):Courant提出了使用分片多项式和变分方法求解偏微分方程的思想
  2. 工程应用期(1960-1980):在结构力学和航空航天领域得到广泛应用
  3. 理论完善期(1980-2000):数学理论体系日趋完善,收敛性和误差分析取得重大进展
  4. 多学科融合期(2000至今):在地球科学、生物医学、材料科学等领域得到广泛应用

在地球科学中,有限元法特别适用于: - 地下水流动模拟 - 地质构造变形分析 - 地震波传播计算 - 地热传输模拟 - 污染物扩散预测

4.1.2 基本思想与哲学

有限元法的核心思想体现了”分而治之”的哲学理念,将复杂的连续问题分解为简单的离散问题。这种思想具有以下特点:

  1. 局部化原理:复杂的全局问题可以通过求解简单的局部问题来近似
  2. 分片逼近:使用简单的多项式函数在每个小区域内逼近真解
  3. 变分基础:基于能量原理或变分原理,确保解的物理合理性
  4. 系统性方法:提供了一套系统的、标准化的求解流程

4.1.3 详细求解步骤

有限元法的完整求解过程包括以下七个核心步骤:

4.1.3.1 1. 域离散化(Domain Discretization)

将连续的求解域 \(\Omega\) 划分为 \(N_e\) 个不重叠的子域(单元): \[\Omega = \bigcup_{e=1}^{N_e} \Omega_e, \quad \Omega_i \cap \Omega_j = \emptyset \text{ (when } i \neq j \text{)}\]

单元类型的选择原则: - 一维问题:线段单元(2节点或3节点) - 二维问题:三角形单元或四边形单元 - 三维问题:四面体单元或六面体单元

4.1.3.2 2. 形函数构造(Shape Function Construction)

在每个单元内定义形函数 \(N_i(x)\),满足: - 插值性质\(N_i(x_j) = \delta_{ij}\)(Kronecker delta) - 完备性质:能够精确表示多项式函数 - 连续性质:在单元边界上保持连续性

4.1.3.3 3. 弱形式建立(Weak Formulation)

通过分部积分和Green公式,将强形式的偏微分方程转化为弱形式,降低对解的光滑性要求。

4.1.3.4 4. 单元矩阵组装(Element Matrix Assembly)

在每个单元上计算局部刚度矩阵 \(\mathbf{K}^e\) 和质量矩阵 \(\mathbf{M}^e\)

4.1.3.5 5. 全局矩阵组装(Global Matrix Assembly)

将所有单元的贡献组装成全局矩阵: \[\mathbf{K} = \sum_{e=1}^{N_e} \mathbf{A}^e \mathbf{K}^e (\mathbf{A}^e)^T\]

其中 \(\mathbf{A}^e\) 是单元组装矩阵。

4.1.3.6 6. 边界条件施加(Boundary Condition Implementation)

处理不同类型的边界条件: - Dirichlet边界条件:指定函数值 - Neumann边界条件:指定导数值 - Robin边界条件:混合边界条件

4.1.3.7 7. 线性方程组求解(Linear System Solution)

求解最终的线性代数方程组: \[\mathbf{K}\mathbf{u} = \mathbf{f}\]

使用适当的数值方法(直接法或迭代法)求解。

4.1.4 变分原理的深入理解

4.1.4.1 变分原理的数学基础

变分原理是有限元法的理论基石,它建立了偏微分方程与泛函极值问题之间的等价关系。这种等价性不仅为数值求解提供了新的途径,更重要的是为解的存在性和唯一性提供了理论保证。

泛函的定义: 泛函是从函数空间到实数的映射。对于函数 \(u(x)\),泛函 \(J[u]\) 将函数 \(u\) 映射到一个实数值: \[J[u] = \int_\Omega F(x, u, \nabla u) dx\]

其中 \(F\) 是给定的函数,\(\nabla u\)\(u\) 的梯度。

4.1.4.2 从强形式到弱形式的详细推导

考虑一般的二阶椭圆型偏微分方程: \[-\nabla \cdot (k(x) \nabla u) + c(x) u = f(x) \quad \text{in} \quad \Omega\]

配以边界条件: \[u = g \quad \text{on} \quad \Gamma_D \quad \text{(Dirichlet边界)}\] \[k \frac{\partial u}{\partial n} = h \quad \text{on} \quad \Gamma_N \quad \text{(Neumann边界)}\]

其中 \(\Gamma_D \cup \Gamma_N = \partial\Omega\)\(\partial\Omega\)\(\Omega\) 的边界。

步骤1:构造测试函数空间

定义测试函数空间: \[V_0 = \{v \in H^1(\Omega) : v = 0 \text{ on } \Gamma_D\}\]

其中 \(H^1(\Omega)\) 是Sobolev空间,包含所有在 \(\Omega\) 上平方可积且弱导数也平方可积的函数。

步骤2:建立弱形式

将强形式方程乘以测试函数 \(v \in V_0\) 并在 \(\Omega\) 上积分: \[\int_\Omega [-\nabla \cdot (k \nabla u) + c u] v \, dx = \int_\Omega f v \, dx\]

步骤3:应用Green公式(分部积分)

对第一项应用Green公式: \[-\int_\Omega v \nabla \cdot (k \nabla u) dx = \int_\Omega k \nabla u \cdot \nabla v \, dx - \int_{\partial\Omega} v k \frac{\partial u}{\partial n} ds\]

由于 \(v = 0\)\(\Gamma_D\) 上,边界积分只在 \(\Gamma_N\) 上非零: \[-\int_{\partial\Omega} v k \frac{\partial u}{\partial n} ds = -\int_{\Gamma_N} v h \, ds\]

步骤4:得到弱形式

最终的弱形式为:寻找 \(u \in V_g = \{w \in H^1(\Omega) : w = g \text{ on } \Gamma_D\}\),使得对所有 \(v \in V_0\)

\[\int_\Omega k \nabla u \cdot \nabla v \, dx + \int_\Omega c u v \, dx = \int_\Omega f v \, dx + \int_{\Gamma_N} h v \, ds\]

这可以写成抽象形式: \[a(u,v) = L(v)\]

其中: - \(a(u,v) = \int_\Omega k \nabla u \cdot \nabla v \, dx + \int_\Omega c u v \, dx\) (双线性形式) - \(L(v) = \int_\Omega f v \, dx + \int_{\Gamma_N} h v \, ds\) (线性形式)

4.1.4.3 变分原理的物理意义

变分原理具有深刻的物理意义:

  1. 能量最小原理:对于弹性力学问题,弱形式对应于势能的最小化
  2. 虚功原理:弱形式表达了虚位移做的虚功等于外力做的虚功
  3. 守恒定律:弱形式自然地体现了各种守恒定律(质量、动量、能量等)

4.1.4.4 Lax-Milgram定理

Lax-Milgram定理保证了弱形式解的存在性和唯一性。如果双线性形式 \(a(\cdot,\cdot)\) 满足:

  1. 连续性:存在常数 \(M > 0\),使得 \(|a(u,v)| \leq M \|u\|_{V} \|v\|_{V}\)
  2. 强制性(椭圆性):存在常数 \(\alpha > 0\),使得 \(a(v,v) \geq \alpha \|v\|_{V}^2\)

且线性形式 \(L(\cdot)\) 连续,则弱形式存在唯一解。

4.1.4.5 等价性定理

在适当的正则性假设下,强形式和弱形式是等价的: - 强形式的解必然是弱形式的解 - 在足够光滑的条件下,弱形式的解也是强形式的解

这种等价性为有限元法提供了坚实的理论基础。

4.1.5 形函数理论的完整体系

形函数(Shape Functions)是有限元法的核心概念,它们不仅定义了单元内的插值关系,更是连接离散节点值与连续函数空间的桥梁。深入理解形函数的性质和构造方法,对于掌握有限元法的精髓至关重要。

4.1.5.1 形函数的基本性质

形函数必须满足以下四个基本性质:

1. 插值性质(Interpolation Property) \[N_i(x_j) = \delta_{ij} = \begin{cases} 1 & \text{if } i = j \\ 0 & \text{if } i \neq j \end{cases}\]

这确保了节点值的准确插值。

2. 单位分解性质(Partition of Unity) \[\sum_{i=1}^{n} N_i(x) = 1, \quad \forall x \in \Omega_e\]

这保证了常数函数能够被精确表示。

3. 完备性性质(Completeness Property) 形函数集合能够精确表示完全多项式,至少到某个给定阶数 \(p\)\[\mathcal{P}_p \subset \text{span}\{N_1, N_2, \ldots, N_n\}\]

其中 \(\mathcal{P}_p\) 是阶数不超过 \(p\) 的多项式空间。

4. 连续性性质(Continuity Property) 在单元边界上,形函数保持 \(C^0\) 连续性,即函数值连续但导数可能不连续。

4.1.5.2 一维形函数的详细构造

线性形函数(Linear Shape Functions)

对于标准单元 \(\xi \in [-1, 1]\)\[N_1(\xi) = \frac{1-\xi}{2}, \quad N_2(\xi) = \frac{1+\xi}{2}\]

其导数为: \[\frac{dN_1}{d\xi} = -\frac{1}{2}, \quad \frac{dN_2}{d\xi} = \frac{1}{2}\]

二次形函数(Quadratic Shape Functions)

对于三节点单元,节点位于 \(\xi = -1, 0, 1\)\[N_1(\xi) = \frac{\xi(\xi-1)}{2}, \quad N_2(\xi) = 1-\xi^2, \quad N_3(\xi) = \frac{\xi(\xi+1)}{2}\]

其导数为: \[\frac{dN_1}{d\xi} = \xi - \frac{1}{2}, \quad \frac{dN_2}{d\xi} = -2\xi, \quad \frac{dN_3}{d\xi} = \xi + \frac{1}{2}\]

三次形函数(Cubic Shape Functions)

对于四节点单元,可以使用Hermite插值: \[N_1(\xi) = \frac{1}{16}(1-\xi)^2(2+\xi)\] \[N_2(\xi) = \frac{1}{16}(1-\xi)^2(1+\xi)\] \[N_3(\xi) = \frac{1}{16}(1+\xi)^2(2-\xi)\] \[N_4(\xi) = \frac{1}{16}(1+\xi)^2(1-\xi)\]

4.1.5.3 Lagrange插值基础

形函数的构造通常基于Lagrange插值理论。对于 \(n+1\) 个节点 \(\xi_0, \xi_1, \ldots, \xi_n\),Lagrange插值多项式为:

\[L_i(\xi) = \prod_{j=0, j \neq i}^{n} \frac{\xi - \xi_j}{\xi_i - \xi_j}\]

这自然满足插值性质:\(L_i(\xi_j) = \delta_{ij}\)

4.1.5.4 等参变换理论

坐标变换

从物理坐标 \(x\) 到参考坐标 \(\xi\) 的变换: \[x = \sum_{i=1}^{n} x_i N_i(\xi)\]

其中 \(x_i\) 是节点的物理坐标。

雅可比矩阵

变换的雅可比矩阵为: \[J = \frac{dx}{d\xi} = \sum_{i=1}^{n} x_i \frac{dN_i}{d\xi}\]

对于一维情况,雅可比行列式 \(|J| = J\)

导数变换

物理空间中的导数通过链式法则计算: \[\frac{dN_i}{dx} = \frac{dN_i}{d\xi} \frac{d\xi}{dx} = \frac{1}{J} \frac{dN_i}{d\xi}\]

4.1.5.5 二维形函数

三角形单元

对于三角形单元,使用面积坐标(重心坐标)\(L_1, L_2, L_3\)\[N_1 = L_1, \quad N_2 = L_2, \quad N_3 = L_3\]

其中 \(L_1 + L_2 + L_3 = 1\)

四边形单元

对于双线性四边形单元: \[N_1(\xi, \eta) = \frac{(1-\xi)(1-\eta)}{4}\] \[N_2(\xi, \eta) = \frac{(1+\xi)(1-\eta)}{4}\] \[N_3(\xi, \eta) = \frac{(1+\xi)(1+\eta)}{4}\] \[N_4(\xi, \eta) = \frac{(1-\xi)(1+\eta)}{4}\]

4.1.5.6 形函数的数值积分

形函数涉及的积分通常通过高斯积分法计算:

一维高斯积分 \[\int_{-1}^{1} f(\xi) d\xi \approx \sum_{i=1}^{n} w_i f(\xi_i)\]

其中 \(w_i\) 是权重,\(\xi_i\) 是积分点。

常用高斯积分点

  • 1点积分:\(\xi_1 = 0\), \(w_1 = 2\)
  • 2点积分:\(\xi_{1,2} = \pm\frac{1}{\sqrt{3}}\), \(w_{1,2} = 1\)
  • 3点积分:\(\xi_1 = 0, \xi_{2,3} = \pm\sqrt{\frac{3}{5}}\), \(w_1 = \frac{8}{9}, w_{2,3} = \frac{5}{9}\)

4.1.5.7 形函数的误差分析

形函数的插值误差可以通过Taylor展开分析。对于 \(p\) 阶多项式形函数,插值误差为:

\[|u - u^h| \leq C h^{p+1} \left\|\frac{d^{p+1}u}{dx^{p+1}}\right\|_{L^2(\Omega_e)}\]

其中 \(h\) 是单元尺寸,\(C\) 是与形函数相关的常数。

4.1.5.8 特殊形函数

分层形函数(Hierarchical Shape Functions)

分层形函数便于实现 \(p\) 型自适应: \[N_i^{(p)} = N_i^{(p-1)} + \alpha_i \phi_p(\xi)\]

其中 \(\phi_p\)\(p\) 阶正交多项式。

泡函数(Bubble Functions)

泡函数在单元内部非零,在边界上为零: \[N_b(\xi) = (1-\xi^2)^p\]

泡函数常用于稳定化方法和混合有限元法。

4.2 数学公式论证

4.2.1 变分形式的推导

考虑一维热传导方程:

\[\frac{\partial u}{\partial t} = \alpha \frac{\partial^2 u}{\partial x^2} \quad \text{in} \quad (0,L) \times (0,T)\]

其中 \(u(x,t)\) 是温度,\(\alpha\) 是热扩散系数,\(L\) 是杆长,\(T\) 是总时间。

边界条件: \[u(0,t) = u_0, \quad u(L,t) = u_L\]

初始条件: \[u(x,0) = u_{ic}(x)\]

将方程乘以测试函数 \(v(x)\) 并在空间域上积分:

\[\int_0^L v(x) \frac{\partial u}{\partial t} dx = \alpha \int_0^L v(x) \frac{\partial^2 u}{\partial x^2} dx\]

对右边进行分部积分:

\[\int_0^L v(x) \frac{\partial u}{\partial t} dx = -\alpha \int_0^L \frac{\partial v}{\partial x} \frac{\partial u}{\partial x} dx + \alpha \left[v(x) \frac{\partial u}{\partial x}\right]_0^L\]

由于边界条件已知,边界项可以处理,得到弱形式:

\[\int_0^L v(x) \frac{\partial u}{\partial t} dx + \alpha \int_0^L \frac{\partial v}{\partial x} \frac{\partial u}{\partial x} dx = 0\]

4.2.2 有限元离散化

将空间域 \([0,L]\) 划分为 \(n\) 个单元,每个单元长度为 \(h = L/n\)。在每个单元 \([x_i, x_{i+1}]\) 上,使用线性形函数:

\[u^h(x) = \sum_{j=1}^{n+1} u_j N_j(x)\]

其中 \(u_j\) 是节点 \(j\) 处的温度值,\(N_j(x)\) 是节点 \(j\) 的形函数。

将弱形式离散化,得到:

\[\sum_{i=1}^n \int_{x_i}^{x_{i+1}} N_j(x) \frac{\partial u^h}{\partial t} dx + \alpha \sum_{i=1}^n \int_{x_i}^{x_{i+1}} \frac{\partial N_j}{\partial x} \frac{\partial u^h}{\partial x} dx = 0\]

这可以写成矩阵形式:

\[M \frac{d\mathbf{u}}{dt} + K\mathbf{u} = \mathbf{0}\]

其中 \(M\) 是质量矩阵,\(K\) 是刚度矩阵,\(\mathbf{u}\) 是节点温度向量。

4.2.3 单元矩阵的构造

对于线性单元,质量矩阵和刚度矩阵的元素为:

\[M_{ij} = \int_{x_i}^{x_{i+1}} N_i(x) N_j(x) dx\]

\[K_{ij} = \alpha \int_{x_i}^{x_{i+1}} \frac{\partial N_i}{\partial x} \frac{\partial N_j}{\partial x} dx\]

对于均匀网格,这些积分可以解析计算:

\[M = \frac{h}{6} \begin{bmatrix} 2 & 1 \\ 1 & 2 \end{bmatrix}\]

\[K = \frac{\alpha}{h} \begin{bmatrix} 1 & -1 \\ -1 & 1 \end{bmatrix}\]

4.3 一维热传导问题的深入分析与FEM求解

4.3.1 工程背景与实际意义

热传导问题在地球科学和工程实践中具有重要意义。本节以铁棍热传导为例,展示有限元法在解决实际工程问题中的应用。这类问题在以下领域具有广泛应用:

地球科学应用: - 地热能开发:地下岩层的热传导分析 - 冻土工程:永久冻土层的热力学行为 - 地质勘探:地下温度场分布预测 - 环境工程:土壤污染热修复技术

工程应用: - 建筑节能:墙体保温材料设计 - 电子散热:芯片热管理系统 - 材料加工:金属热处理工艺 - 核工程:反应堆热传递分析

4.3.2 详细问题描述

考虑一根长度为 \(L = 1.0\) m 的均匀铁棍,其物理参数如下:

材料参数: - 热传导系数:\(k = 50\) W/(m·K) - 密度:\(\rho = 7850\) kg/m³ - 比热容:\(c = 460\) J/(kg·K) - 热扩散系数:\(\alpha = \frac{k}{\rho c} = 1.384 \times 10^{-5}\) m²/s

几何参数: - 长度:\(L = 1.0\) m - 横截面积:\(A = 1.0\) cm²(假设为细棍,忽略横向传热)

边界条件: - 左端(\(x = 0\)):恒温 \(u(0,t) = 100°C\) - 右端(\(x = L\)):恒温 \(u(L,t) = 50°C\)

初始条件: - 初始温度:\(u(x,0) = 25°C\)(室温)

4.3.3 CLAMS方法的系统化建模

CLAMS方法为复杂物理问题提供了系统化的建模框架,确保建模过程的完整性和科学性。

4.3.3.1 1. 概念描述 (Conceptual Model)

物理系统描述: 一维热传导系统,包含以下关键要素: - 系统边界:铁棍的几何边界 - 控制体积:整个铁棍内部 - 热源/热汇:两端的恒温边界条件 - 传热机制:纯导热(忽略对流和辐射)

简化假设的物理合理性: 1. 一维假设:当铁棍的长度远大于其直径时,横向温度梯度可忽略 2. 均匀材料:工业铁棍在宏观尺度上可视为均匀介质 3. 恒定物性:在给定温度范围内,铁的热物性变化较小

4.3.3.2 2. 物理定律 (Laws of Physics)

傅里叶热传导定律\[\mathbf{q} = -k \nabla u\]

对于一维情况: \[q_x = -k \frac{\partial u}{\partial x}\]

其中 \(q_x\) 是单位面积的热流密度(W/m²)。

能量守恒定律: 基于控制体积的能量平衡: \[\frac{\partial}{\partial t}(\rho c u) + \nabla \cdot \mathbf{q} = S\]

其中 \(S\) 是内热源项。对于无内热源的一维情况: \[\rho c \frac{\partial u}{\partial t} + \frac{\partial q_x}{\partial x} = 0\]

热力学第二定律: 热量自发地从高温区域传向低温区域,这确保了问题解的物理合理性。

4.3.3.3 3. 假设条件 (Assumptions)

几何假设: 1. 一维传热\(\frac{\partial u}{\partial y} = \frac{\partial u}{\partial z} = 0\) 2. 直杆假设:忽略几何形状对传热的影响 3. 边界绝热:侧表面无热交换

物理假设: 4. 恒定物性\(k\)\(\rho\)\(c\) 为常数 5. 无内热源\(S = 0\) 6. 线性传热:忽略非线性效应(如温度相关的物性)

数值假设: 7. 连续介质:忽略分子尺度的不连续性 8. 准静态过程:忽略惯性效应

假设的有效性分析: - 对于长径比 > 10 的铁棍,一维假设误差 < 5% - 在0-200°C温度范围内,铁的热物性变化 < 10% - 忽略对流和辐射在低温差情况下是合理的

4.3.3.4 4. 数学推导 (Mathematical Derivation)

控制方程的推导

将傅里叶定律代入能量守恒方程: \[\rho c \frac{\partial u}{\partial t} - \frac{\partial}{\partial x}\left(k \frac{\partial u}{\partial x}\right) = 0\]

对于恒定物性: \[\frac{\partial u}{\partial t} = \alpha \frac{\partial^2 u}{\partial x^2}\]

其中热扩散系数 \(\alpha = \frac{k}{\rho c}\) 具有量纲 [m²/s]。

边界条件的数学表达: - 第一类边界条件(Dirichlet)\[u(0,t) = u_0 = 100°C\] \[u(L,t) = u_L = 50°C\]

初始条件\[u(x,0) = u_{ic}(x) = 25°C\]

无量纲化处理: 为了便于数值计算和参数分析,引入无量纲变量: - 无量纲坐标:\(\xi = \frac{x}{L}\) - 无量纲温度:\(\theta = \frac{u - u_L}{u_0 - u_L}\) - 无量纲时间:\(\tau = \frac{\alpha t}{L^2}\)

无量纲控制方程: \[\frac{\partial \theta}{\partial \tau} = \frac{\partial^2 \theta}{\partial \xi^2}\]

边界条件:\(\theta(0,\tau) = 1\)\(\theta(1,\tau) = 0\) 初始条件:\(\theta(\xi,0) = \frac{25-50}{100-50} = -0.5\)

4.3.3.5 5. 求解策略 (Solver Strategy)

有限元离散化策略

  1. 空间离散化
    • 单元类型:一维线性单元
    • 网格密度:根据Peclet数确定
    • 单元数量:\(N_e = 20\)(经收敛性分析确定)
  2. 时间离散化
    • 时间积分格式:隐式Euler法(确保无条件稳定)
    • 时间步长:\(\Delta t = 100\) s(满足精度要求)
    • 总时间:\(T = 10000\) s(达到稳态)
  3. 线性方程组求解
    • 矩阵类型:对称正定稀疏矩阵
    • 求解方法:直接法(LU分解)
    • 边界条件处理:罚方法或消元法

数值稳定性分析: - CFL条件:隐式格式无条件稳定 - 质量矩阵:确保质量守恒 - 能量守恒:验证总能量平衡

收敛性检验: - 网格收敛性:比较不同网格密度的结果 - 时间步长收敛性:验证时间积分精度 - 解析解对比:在特殊情况下与解析解比较

4.3.4 R代码实现

以下代码展示了有限元法求解一维热传导问题的完整实现:

4.3.5 完整实现代码

# 有限元法求解一维热传导问题
# 作者:数值地球教科书项目
# 日期:2025年

library(Matrix)

# 加载字体配置
source("Functions/font_setup.R")

# 设置中文字体(在绘图前)
tryCatch({
  setup_bookdown_fonts()
}, error = function(e) {
  cat("字体设置失败,使用默认字体:", e$message, "\n")
})
## 字体设置完成: Arial Unicode MS
## [1] "Arial Unicode MS"
# 参数设置
L <- 1.0          # 杆长 (m)
n_elements <- 20  # 单元数量
n_nodes <- n_elements + 1  # 节点数量
h <- L / n_elements        # 单元长度
alpha <- 1e-4              # 热扩散系数 (m²/s)
dt <- 100                  # 时间步长 (s)
T_max <- 10000             # 总时间 (s)
n_steps <- T_max / dt      # 时间步数

# 初始条件
u_ic <- rep(25, n_nodes)  # 初始温度 (°C)
u_0 <- 100                 # 左端温度 (°C)
u_L <- 50                  # 右端温度 (°C)

# 构造质量矩阵和刚度矩阵
M <- Matrix(0, n_nodes, n_nodes, sparse = TRUE)
K <- Matrix(0, n_nodes, n_nodes, sparse = TRUE)

# 组装全局矩阵
for (i in 1:n_elements) {
  # 局部节点编号
  local_nodes <- c(i, i+1)
  
  # 局部质量矩阵
  M_local <- h/6 * matrix(c(2, 1, 1, 2), 2, 2)
  # 局部刚度矩阵
  K_local <- alpha/h * matrix(c(1, -1, -1, 1), 2, 2)
  
  # 组装到全局矩阵
  M[local_nodes, local_nodes] <- M[local_nodes, local_nodes] + M_local
  K[local_nodes, local_nodes] <- K[local_nodes, local_nodes] + K_local
}

# 时间推进(隐式欧拉法)
u_current <- u_ic
u_history <- matrix(0, n_nodes, n_steps + 1)
u_history[, 1] <- u_current

# 构造线性方程组系数矩阵
A <- M + dt * K
b <- M %*% u_current

# 施加边界条件
A[1, ] <- 0; A[1, 1] <- 1
A[n_nodes, ] <- 0; A[n_nodes, n_nodes] <- 1

for (step in 1:n_steps) {
  # 更新右端项
  b <- M %*% u_current
  
  # 施加边界条件
  b[1] <- u_0
  b[n_nodes] <- u_L
  
  # 求解线性方程组
  u_new <- solve(A, b)
  
  # 更新解
  u_current <- as.vector(u_new)
  u_history[, step + 1] <- u_current
}

# 结果可视化
x_coords <- seq(0, L, length.out = n_nodes)
time_coords <- seq(0, T_max, by = dt)

# 绘制不同时刻的温度分布
par(mfrow = c(1, 1))

# 选择要显示的时刻
time_indices <- c(1, round(n_steps/16), round(n_steps/8), n_steps + 1)
time_labels <- paste('Time =', time_indices, 's')
colors <- c("blue", "red", "green", "purple")

# 绘制第一条曲线(初始时刻)
plot(x_coords, u_history[, time_indices[1]], type = 'l', 
     col = colors[1], lwd = 2, ylim = c(0, 100),
     xlab = '位置 (m)', ylab = '温度 (°C)', 
     main = '不同时刻的温度分布')

# 添加其他时刻的曲线
for (i in 2:length(time_indices)) {
  lines(x_coords, u_history[, time_indices[i]], 
        col = colors[i], lwd = 2)
}

# 添加图例
legend("topright", legend = time_labels, 
       col = colors, lwd = 2, lty = 1, cex = 0.8)

grid()

# 绘制温度随时间变化图(选择几个关键位置)
par(mfrow = c(1, 1))

# 选择要显示的位置
position_indices <- c(1, round(n_nodes/4), round(n_nodes/2), round(3*n_nodes/4), n_nodes)
position_labels <- c("左端", "1/4处", "中点", "3/4处", "右端")
colors_time <- c("blue", "red", "green", "orange", "purple")

# 绘制第一条曲线(左端)
plot(time_coords, u_history[position_indices[1], ], type = 'l', 
     col = colors_time[1], lwd = 2, ylim = c(0, 100),
     xlab = '时间 (s)', ylab = '温度 (°C)', 
     main = '不同位置的温度随时间变化')

# 添加其他位置的曲线
for (i in 2:length(position_indices)) {
  lines(time_coords, u_history[position_indices[i], ], 
        col = colors_time[i], lwd = 2)
}

# 添加图例
legend("topright", legend = position_labels, 
       col = colors_time, lwd = 2, lty = 1, cex = 0.8)

grid()

# 返回结果
result <- list(
  x_coords = x_coords,
  time_coords = time_coords,
  u_history = u_history,
  parameters = list(
    L = L,
    n_elements = n_elements,
    alpha = alpha,
    dt = dt,
    T_max = T_max
  )
)

# 打印结果摘要
cat("有限元法求解完成!\n")
## 有限元法求解完成!
cat("空间网格点数:", n_nodes, "\n")
## 空间网格点数: 21
cat("时间步数:", n_steps, "\n")
## 时间步数: 100
cat("最终温度范围:", round(min(u_history[, n_steps + 1]), 2), "°C 到", 
    round(max(u_history[, n_steps + 1]), 2), "°C\n")
## 最终温度范围: 50 °C 到 100 °C
#return(result)

4.3.6 运行结果示例

运行上述代码可以得到一维热传导问题的有限元解。结果显示:

  1. 温度分布演化:从初始的均匀温度25°C逐渐向两端边界温度过渡
  2. 收敛性:解在时间推进过程中逐渐收敛到稳态解
  3. 边界效应:温度梯度在边界附近较大,在中心区域较小

4.3.7 高级有限元技术实现

除了基本的线性有限元法,我们还实现了更高级的技术:

# 高级有限元法实现
# 包括高阶形函数、自适应网格和与有限差分法的对比
# 作者:数值地球教科书项目
# 日期:2025年

library(Matrix)

# 加载字体配置
source("Functions/font_setup.R")

# 设置中文字体(在绘图前)
tryCatch({
  setup_bookdown_fonts()
}, error = function(e) {
  cat("字体设置失败,使用默认字体:", e$message, "\n")
})
## 字体设置完成: Arial Unicode MS
## [1] "Arial Unicode MS"
# ============================================================================
# 高阶形函数有限元法
# ============================================================================

# 二次形函数
quadratic_shape_functions <- function(xi) {
  N1 <- xi * (xi - 1) / 2
  N2 <- 1 - xi^2
  N3 <- xi * (xi + 1) / 2
  return(c(N1, N2, N3))
}

# 二次形函数的导数
quadratic_shape_derivatives <- function(xi) {
  dN1 <- (2*xi - 1) / 2
  dN2 <- -2*xi
  dN3 <- (2*xi + 1) / 2
  return(c(dN1, dN2, dN3))
}

# 高阶有限元求解器
FEM_quadratic <- function(L = 1.0, n_elements = 10, alpha = 1e-4, 
                          dt = 100, T_max = 10000, u_0 = 100, u_L = 50, u_ic = 25) {
  
  n_nodes <- 2 * n_elements + 1  # 二次单元:每个单元3个节点
  h <- L / n_elements
  
  # 构造质量矩阵和刚度矩阵
  M <- Matrix(0, n_nodes, n_nodes, sparse = TRUE)
  K <- Matrix(0, n_nodes, n_nodes, sparse = TRUE)
  
  # 高斯积分点和权重
  gauss_points <- c(-sqrt(3/5), 0, sqrt(3/5))
  gauss_weights <- c(5/9, 8/9, 5/9)
  
  # 组装全局矩阵
  for (i in 1:n_elements) {
    # 局部节点编号(二次单元)
    local_nodes <- c(2*i-1, 2*i, 2*i+1)
    
    # 初始化局部矩阵
    M_local <- matrix(0, 3, 3)
    K_local <- matrix(0, 3, 3)
    
    # 高斯积分
    for (gp in 1:3) {
      xi <- gauss_points[gp]
      weight <- gauss_weights[gp]
      
      # 形函数和导数
      N <- quadratic_shape_functions(xi)
      dN <- quadratic_shape_derivatives(xi)
      
      # 雅可比变换
      J <- h/2
      
      # 组装局部矩阵
      for (a in 1:3) {
        for (b in 1:3) {
          M_local[a, b] <- M_local[a, b] + weight * N[a] * N[b] * J
          K_local[a, b] <- K_local[a, b] + weight * dN[a] * dN[b] * J
        }
      }
    }
    
    # 组装到全局矩阵
    M[local_nodes, local_nodes] <- M[local_nodes, local_nodes] + M_local
    K[local_nodes, local_nodes] <- K[local_nodes, local_nodes] + alpha * K_local
  }
  
  # 时间推进
  n_steps <- T_max / dt
  u_current <- rep(u_ic, n_nodes)
  u_history <- matrix(0, n_nodes, n_steps + 1)
  u_history[, 1] <- u_current
  
  # 构造线性方程组系数矩阵
  A <- M + dt * K
  
  # 施加边界条件
  A[1, ] <- 0; A[1, 1] <- 1
  A[n_nodes, ] <- 0; A[n_nodes, n_nodes] <- 1
  
  for (step in 1:n_steps) {
    b <- M %*% u_current
    b[1] <- u_0
    b[n_nodes] <- u_L
    
    u_new <- solve(A, b)
    u_current <- as.vector(u_new)
    u_history[, step + 1] <- u_current
  }
  
  return(list(
    u_history = u_history,
    x_coords = seq(0, L, length.out = n_nodes),
    parameters = list(n_elements = n_elements, n_nodes = n_nodes, h = h)
  ))
}

# ============================================================================
# 自适应网格有限元法
# ============================================================================

# 误差估计器
error_estimator <- function(u, h) {
  # 基于梯度的简单误差估计
  n <- length(u)
  error <- numeric(n-1)
  
  for (i in 1:(n-1)) {
    gradient <- abs(u[i+1] - u[i]) / h
    error[i] <- h * gradient^2
  }
  
  return(error)
}

# 自适应网格细化
adaptive_mesh_refinement <- function(u, x, error_threshold = 0.01) {
  n <- length(u)
  error <- error_estimator(u, diff(x))
  
  # 标记需要细化的单元
  refine_elements <- which(error > error_threshold)
  
  if (length(refine_elements) == 0) {
    return(list(x = x, u = u, refined = FALSE))
  }
  
  # 在误差大的单元中点插入新节点
  new_x <- x
  new_u <- u
  
  for (i in sort(refine_elements, decreasing = TRUE)) {
    # 插入中点
    mid_x <- (x[i] + x[i+1]) / 2
    mid_u <- (u[i] + u[i+1]) / 2
    
    new_x <- c(new_x[1:i], mid_x, new_x[(i+1):length(new_x)])
    new_u <- c(new_u[1:i], mid_u, new_u[(i+1):length(new_u)])
  }
  
  return(list(x = new_x, u = new_u, refined = TRUE))
}

# 自适应有限元求解器
FEM_adaptive <- function(L = 1.0, initial_elements = 10, alpha = 1e-4, 
                         dt = 100, T_max = 10000, u_0 = 100, u_L = 50, u_ic = 25,
                         max_refinements = 3) {
  
  # 初始网格
  n_elements <- initial_elements
  n_nodes <- n_elements + 1
  h <- L / n_elements
  
  # 初始条件
  u_current <- rep(u_ic, n_nodes)
  x_coords <- seq(0, L, length.out = n_nodes)
  
  # 自适应细化循环
  for (refinement in 1:max_refinements) {
    cat("第", refinement, "次网格细化,节点数:", length(x_coords), "\n")
    
    # 使用当前网格求解
    result <- FEM_linear_adaptive(x_coords, u_current, alpha, dt, T_max, u_0, u_L)
    
    # 误差估计和网格细化
    mesh_result <- adaptive_mesh_refinement(result$u_final, x_coords)
    
    if (!mesh_result$refined) {
      cat("网格细化完成\n")
      break
    }
    
    # 更新网格和解
    x_coords <- mesh_result$x
    u_current <- mesh_result$u
  }
  
  return(result)
}

# 线性有限元求解器(用于自适应网格)
FEM_linear_adaptive <- function(x_coords, u_ic, alpha, dt, T_max, u_0, u_L) {
  n_nodes <- length(x_coords)
  n_elements <- n_nodes - 1
  
  # 构造质量矩阵和刚度矩阵
  M <- Matrix(0, n_nodes, n_nodes, sparse = TRUE)
  K <- Matrix(0, n_nodes, n_nodes, sparse = TRUE)
  
  # 组装全局矩阵
  for (i in 1:n_elements) {
    h <- x_coords[i+1] - x_coords[i]
    local_nodes <- c(i, i+1)
    
    M_local <- h/6 * matrix(c(2, 1, 1, 2), 2, 2)
    K_local <- alpha/h * matrix(c(1, -1, -1, 1), 2, 2)
    
    M[local_nodes, local_nodes] <- M[local_nodes, local_nodes] + M_local
    K[local_nodes, local_nodes] <- K[local_nodes, local_nodes] + K_local
  }
  
  # 时间推进
  n_steps <- T_max / dt
  u_current <- u_ic
  u_history <- matrix(0, n_nodes, n_steps + 1)
  u_history[, 1] <- u_current
  
  A <- M + dt * K
  A[1, ] <- 0; A[1, 1] <- 1
  A[n_nodes, ] <- 0; A[n_nodes, n_nodes] <- 1
  
  for (step in 1:n_steps) {
    b <- M %*% u_current
    b[1] <- u_0
    b[n_nodes] <- u_L
    
    u_new <- solve(A, b)
    u_current <- as.vector(u_new)
    u_history[, step + 1] <- u_current
  }
  
  return(list(
    u_history = u_history,
    u_final = u_current,
    x_coords = x_coords
  ))
}

# ============================================================================
# 与有限差分法的对比
# ============================================================================

# 有限差分法求解器(用于对比)
FDM_explicit <- function(L = 1.0, n_points = 21, alpha = 1e-4, 
                         dt = 100, T_max = 10000, u_0 = 100, u_L = 50, u_ic = 25) {
  
  h <- L / (n_points - 1)
  n_steps <- T_max / dt
  
  # 检查CFL条件
  cfl <- alpha * dt / (h^2)
  if (cfl > 0.5) {
    cat("警告:CFL条件不满足,CFL =", cfl, "\n")
  }
  
  # 初始条件
  u_current <- rep(u_ic, n_points)
  u_history <- matrix(0, n_points, n_steps + 1)
  u_history[, 1] <- u_current
  
  # 显式时间推进
  for (step in 1:n_steps) {
    u_new <- u_current
    
    # 内部点
    for (i in 2:(n_points-1)) {
      u_new[i] <- u_current[i] + cfl * (u_current[i+1] - 2*u_current[i] + u_current[i-1])
    }
    
    # 边界条件
    u_new[1] <- u_0
    u_new[n_points] <- u_L
    
    u_current <- u_new
    u_history[, step + 1] <- u_current
  }
  
  return(list(
    u_history = u_history,
    x_coords = seq(0, L, length.out = n_points),
    cfl = cfl
  ))
}

# 性能对比函数
performance_comparison <- function() {
  cat("有限元法与有限差分法性能对比\n")
  cat("=" * 50, "\n")
  
  # 测试参数
  L <- 1.0
  alpha <- 1e-4
  dt <- 100
  T_max <- 10000
  u_0 <- 100
  u_L <- 50
  u_ic <- 25
  
  # 测试不同网格密度
  grid_sizes <- c(11, 21, 41, 81)
  
  for (n in grid_sizes) {
    cat("\n网格点数:", n, "\n")
    
    # 有限差分法
    t0 <- Sys.time()
    fdm_result <- FDM_explicit(L, n, alpha, dt, T_max, u_0, u_L, u_ic)
    t1 <- Sys.time()
    fdm_time <- as.numeric(t1 - t0)
    
    # 有限元法(线性)
    t0 <- Sys.time()
    fem_result <- FEM_linear_adaptive(
      seq(0, L, length.out = n), rep(u_ic, n), 
      alpha, dt, T_max, u_0, u_L
    )
    t1 <- Sys.time()
    fem_time <- as.numeric(t1 - t0)
    
    cat("FDM时间:", round(fdm_time, 4), "秒\n")
    cat("FEM时间:", round(fem_time, 4), "秒\n")
    cat("时间比:", round(fem_time/fdm_time, 2), "\n")
    
    # 精度对比(使用解析解作为参考)
    # 这里简化处理,实际应该与解析解比较
    fdm_error <- mean(abs(diff(fdm_result$u_history[, ncol(fdm_result$u_history)])))
    fem_error <- mean(abs(diff(fem_result$u_final)))
    
    cat("FDM误差:", round(fdm_error, 6), "\n")
    cat("FEM误差:", round(fem_error, 6), "\n")
  }
}

# ============================================================================
# 主函数和示例
# ============================================================================

# 运行示例
if (FALSE) {
  # 基本有限元法
  cat("运行基本有限元法...\n")
  result_basic <- FEM_quadratic()
  
  # 自适应网格有限元法
  cat("运行自适应网格有限元法...\n")
  result_adaptive <- FEM_adaptive()
  
  # 性能对比
  performance_comparison()
  
  # 可视化结果
  par(mfrow = c(1, 2))
  
  # 基本FEM结果 - 不同时刻的温度分布
  time_indices <- c(1, round(ncol(result_basic$u_history)/16), 
                   round(ncol(result_basic$u_history)/8), ncol(result_basic$u_history))
  time_labels <- paste('Time =', time_indices, 's')
  colors <- c("blue", "red", "green", "purple")
  
  # 绘制第一条曲线
  plot(result_basic$x_coords, result_basic$u_history[, time_indices[1]], type = 'l', 
       col = colors[1], lwd = 2, ylim = c(0, 100),
       xlab = '位置 (m)', ylab = '温度 (°C)', 
       main = '基本FEM - 不同时刻温度分布')
  
  # 添加其他时刻的曲线
  for (i in 2:length(time_indices)) {
    lines(result_basic$x_coords, result_basic$u_history[, time_indices[i]], 
          col = colors[i], lwd = 2)
  }
  
  # 添加图例
  legend("topright", legend = time_labels, 
         col = colors, lwd = 2, lty = 1, cex = 0.8)
  grid()
  
  # 自适应FEM结果 - 不同时刻的温度分布
  plot(result_adaptive$x_coords, result_adaptive$u_final, type = 'l', ylim = c(0, 100),
       col = 'green', lwd = 2, xlab = '位置 (m)', ylab = '温度 (°C)',  
       main = '自适应FEM - 最终温度分布')
  grid()
  
  # 网格对比图
  par(mfrow = c(1, 1))
  plot(result_basic$x_coords, rep(1, length(result_basic$x_coords)), 
       type = 'p', pch = 16, col = 'blue', xlab = '位置 (m)', ylab = '', 
       main = '网格对比', yaxt = 'n', ylim = c(0.8, 1.4))
  points(result_adaptive$x_coords, rep(1.2, length(result_adaptive$x_coords)), 
         pch = 16, col = 'red')
  legend('top', legend = c('基本网格', '自适应网格'), 
         col = c('blue', 'red'), pch = 16)
  grid()
}

这些高级技术展示了有限元法的强大功能:

  1. 高阶形函数:使用二次形函数提高精度,收敛阶从\(O(h^2)\)提升到\(O(h^3)\)
  2. 自适应网格:根据误差估计自动调整网格密度,在解变化剧烈的区域加密网格
  3. 性能对比:与有限差分法进行系统比较,展示有限元法的优势
  4. 数值积分:使用高斯积分法提高计算精度

4.4 地球科学应用案例:地下水流模拟

4.4.1 问题背景

地下水流模拟是水文地质学中的核心问题,有限元法在这一领域具有重要应用。考虑一个二维承压含水层中的地下水流动问题,这是有限元法在地球科学中的典型应用。

4.4.2 数学模型

控制方程: 二维承压含水层中的地下水流动由以下偏微分方程描述:

\[\frac{\partial}{\partial x}\left(T_{xx}\frac{\partial h}{\partial x}\right) + \frac{\partial}{\partial y}\left(T_{yy}\frac{\partial h}{\partial y}\right) = S\frac{\partial h}{\partial t} + Q\]

其中: - \(h(x,y,t)\) 是水头(m) - \(T_{xx}\)\(T_{yy}\) 是导水系数(m²/s) - \(S\) 是储水系数(无量纲) - \(Q\) 是源汇项(1/s)

边界条件: - Dirichlet边界\(h = h_0\)\(\Gamma_D\) 上 - Neumann边界\(T\frac{\partial h}{\partial n} = q\)\(\Gamma_N\)

4.4.3 有限元离散化

弱形式: 寻找 \(h \in V\),使得对所有 \(v \in V_0\)

\[\int_\Omega T\nabla h \cdot \nabla v \, d\Omega + \int_\Omega S\frac{\partial h}{\partial t}v \, d\Omega = \int_\Omega Qv \, d\Omega + \int_{\Gamma_N} qv \, d\Gamma\]

有限元近似\[h^h(x,y,t) = \sum_{i=1}^{N} h_i(t) N_i(x,y)\]

其中 \(N_i(x,y)\) 是形函数,\(h_i(t)\) 是节点水头值。

4.4.4 实际应用案例

案例描述: 考虑一个矩形承压含水层,尺寸为1000m × 500m,厚度为50m。含水层参数如下: - 导水系数:\(T = 10^{-3}\) m²/s - 储水系数:\(S = 10^{-4}\) - 初始水头:\(h_0 = 100\) m - 边界条件:左边界 \(h = 120\) m,右边界 \(h = 80\) m

有限元求解: 使用三角形单元对计算域进行离散化,采用线性形函数进行插值。

# 有限元法求解二维地下水流问题
# 作者:数值地球教科书项目
# 日期:2025年

library(Matrix)
library(ggplot2)

# 加载字体配置
source("Functions/font_setup.R")

# 设置中文字体(在绘图前)
tryCatch({
  setup_bookdown_fonts()
}, error = function(e) {
  cat("字体设置失败,使用默认字体:", e$message, "\n")
})
## 字体设置完成: Arial Unicode MS
## [1] "Arial Unicode MS"
# ============================================================================
# 二维地下水流有限元法求解器
# ============================================================================

# 三角形单元形函数(线性)
triangle_shape_functions <- function(xi, eta) {
  N1 <- 1 - xi - eta
  N2 <- xi
  N3 <- eta
  return(c(N1, N2, N3))
}

# 三角形单元形函数导数
triangle_shape_derivatives <- function() {
  # 对于线性三角形单元,形函数导数为常数
  dN_dxi <- c(-1, 1, 0)
  dN_deta <- c(-1, 0, 1)
  return(list(dN_dxi = dN_dxi, dN_deta = dN_deta))
}

# 生成三角形网格
generate_triangular_mesh <- function(Lx, Ly, nx, ny) {
  # 生成节点坐标
  x <- seq(0, Lx, length.out = nx)
  y <- seq(0, Ly, length.out = ny)
  
  nodes <- expand.grid(x = x, y = y)
  n_nodes <- nrow(nodes)
  
  # 生成三角形单元
  elements <- list()
  element_count <- 0
  
  for (i in 1:(nx-1)) {
    for (j in 1:(ny-1)) {
      # 每个矩形分成两个三角形
      # 左下角节点索引
      bottom_left <- (j-1) * nx + i
      bottom_right <- (j-1) * nx + i + 1
      top_left <- j * nx + i
      top_right <- j * nx + i + 1
      
      # 第一个三角形(左下-右上-左上)
      element_count <- element_count + 1
      elements[[element_count]] <- c(bottom_left, top_right, top_left)
      
      # 第二个三角形(左下-右下-右上)
      element_count <- element_count + 1
      elements[[element_count]] <- c(bottom_left, bottom_right, top_right)
    }
  }
  
  return(list(
    nodes = nodes,
    elements = elements,
    n_nodes = n_nodes,
    n_elements = length(elements)
  ))
}

# 计算单元矩阵
compute_element_matrices <- function(nodes, element, T_xx, T_yy, S) {
  # 获取单元节点坐标
  node_coords <- nodes[element, ]
  
  # 计算雅可比矩阵
  x1 <- node_coords[1, 1]; y1 <- node_coords[1, 2]
  x2 <- node_coords[2, 1]; y2 <- node_coords[2, 2]
  x3 <- node_coords[3, 1]; y3 <- node_coords[3, 2]
  
  # 雅可比矩阵
  J <- matrix(c(x2-x1, x3-x1, y2-y1, y3-y1), 2, 2)
  det_J <- det(J)
  
  # 雅可比矩阵的逆
  J_inv <- solve(J)
  
  # 形函数导数
  dN <- triangle_shape_derivatives()
  
  # 计算形函数在物理坐标中的导数
  dN_dx <- J_inv[1, 1] * dN$dN_dxi + J_inv[1, 2] * dN$dN_deta
  dN_dy <- J_inv[2, 1] * dN$dN_dxi + J_inv[2, 2] * dN$dN_deta
  
  # 计算单元刚度矩阵
  K_elem <- matrix(0, 3, 3)
  for (i in 1:3) {
    for (j in 1:3) {
      K_elem[i, j] <- T_xx * dN_dx[i] * dN_dx[j] + T_yy * dN_dy[i] * dN_dy[j]
    }
  }
  K_elem <- K_elem * abs(det_J) / 2
  
  # 计算单元质量矩阵
  M_elem <- matrix(0, 3, 3)
  for (i in 1:3) {
    for (j in 1:3) {
      M_elem[i, j] <- S * (1 + (i==j)) / 12  # 简化的质量矩阵
    }
  }
  M_elem <- M_elem * abs(det_J) / 2
  
  return(list(K = K_elem, M = M_elem))
}

# 主求解函数
solve_groundwater_fem <- function(Lx = 1000, Ly = 500, nx = 21, ny = 11,
                                 T_xx = 1e-3, T_yy = 1e-3, S = 1e-4,
                                 h_left = 120, h_right = 80, h_initial = 100,
                                 dt = 1000, T_max = 10000) {
  
  cat("开始有限元法求解二维地下水流问题...\n")
  
  # 生成网格
  mesh <- generate_triangular_mesh(Lx, Ly, nx, ny)
  nodes <- mesh$nodes
  elements <- mesh$elements
  n_nodes <- mesh$n_nodes
  n_elements <- mesh$n_elements
  
  cat("网格生成完成:", n_nodes, "个节点,", n_elements, "个单元\n")
  
  # 初始化全局矩阵
  K_global <- Matrix(0, n_nodes, n_nodes, sparse = TRUE)
  M_global <- Matrix(0, n_nodes, n_nodes, sparse = TRUE)
  
  # 组装全局矩阵
  for (e in 1:n_elements) {
    element <- elements[[e]]
    elem_matrices <- compute_element_matrices(nodes, element, T_xx, T_yy, S)
    
    # 组装到全局矩阵
    K_global[element, element] <- K_global[element, element] + elem_matrices$K
    M_global[element, element] <- M_global[element, element] + elem_matrices$M
  }
  
  # 初始条件
  h_current <- rep(h_initial, n_nodes)
  
  # 时间推进
  n_steps <- T_max / dt
  h_history <- matrix(0, n_nodes, n_steps + 1)
  h_history[, 1] <- h_current
  
  # 构造线性方程组系数矩阵
  A <- M_global + dt * K_global
  
  # 施加边界条件
  # 左边界(x = 0)
  left_boundary <- which(nodes$x == 0)
  A[left_boundary, ] <- 0
  for (i in left_boundary) {
    A[i, i] <- 1
  }
  
  # 右边界(x = Lx)
  right_boundary <- which(nodes$x == Lx)
  A[right_boundary, ] <- 0
  for (i in right_boundary) {
    A[i, i] <- 1
  }
  
  cat("开始时间推进...\n")
  
  for (step in 1:n_steps) {
    # 更新右端项
    b <- M_global %*% h_current
    
    # 施加边界条件
    b[left_boundary] <- h_left
    b[right_boundary] <- h_right
    
    # 求解线性方程组
    h_new <- solve(A, b)
    h_current <- as.vector(h_new)
    h_history[, step + 1] <- h_current
    
    if (step %% 10 == 0) {
      cat("时间步", step, "/", n_steps, "完成\n")
    }
  }
  
  cat("求解完成!\n")
  
  return(list(
    nodes = nodes,
    elements = elements,
    h_history = h_history,
    h_final = h_current,
    parameters = list(
      Lx = Lx, Ly = Ly, nx = nx, ny = ny,
      T_xx = T_xx, T_yy = T_yy, S = S,
      dt = dt, T_max = T_max
    )
  ))
}

# 可视化函数
plot_groundwater_results <- function(result, time_step = NULL) {
  nodes <- result$nodes
  h_data <- result$h_final
  
  if (!is.null(time_step)) {
    h_data <- result$h_history[, time_step]
  }
  
  # 创建数据框
  plot_data <- data.frame(
    x = nodes$x,
    y = nodes$y,
    h = h_data
  )
  
  # 绘制水头分布
  p1 <- ggplot(plot_data, aes(x = x, y = y, fill = h)) +
    geom_tile() +
    scale_fill_viridis_c(name = "水头 (m)") +
    labs(title = "地下水流有限元解 - 水头分布",
         x = "距离 (m)", y = "距离 (m)") +
    theme_minimal() +
    theme(text = element_text(family = "SimHei"))
  
  print(p1)
  
  # 绘制等水头线
  p2 <- ggplot(plot_data, aes(x = x, y = y, z = h)) +
    geom_contour(aes(color = ..level..), bins = 20) +
    scale_color_viridis_c(name = "水头 (m)") +
    labs(title = "地下水流有限元解 - 等水头线",
         x = "距离 (m)", y = "距离 (m)") +
    theme_minimal() +
    theme(text = element_text(family = "SimHei"))
  
  print(p2)
  
  return(list(contour_plot = p1, level_plot = p2))
}

# 主函数
main_groundwater_fem <- function() {
  cat("=== 二维地下水流有限元法求解 ===\n")
  
  # 求解问题
  result <- solve_groundwater_fem()
  
  # 可视化结果
  plots <- plot_groundwater_results(result)
  
  # 打印结果摘要
  cat("\n=== 求解结果摘要 ===\n")
  cat("最终水头范围:", round(min(result$h_final), 2), "m 到", 
      round(max(result$h_final), 2), "m\n")
  cat("水头梯度:", round((max(result$h_final) - min(result$h_final)) / 1000, 4), "m/m\n")
  
  return(result)
}

# 运行示例
if (FALSE) {
  # 运行主函数
  result <- main_groundwater_fem()
  
  # 绘制不同时刻的水头分布
  par(mfrow = c(2, 2))
  time_steps <- c(1, round(ncol(result$h_history)/4), 
                  round(ncol(result$h_history)/2), ncol(result$h_history))
  
  for (i in 1:4) {
    plot_groundwater_results(result, time_steps[i])
  }
}

结果分析: 1. 水头分布:从左侧高水头向右侧低水头流动 2. 流线模式:流线垂直于等水头线,符合达西定律 3. 时间演化:水头分布随时间逐渐达到稳态

4.4.5 工程意义

这个案例展示了有限元法在地下水模拟中的优势: 1. 几何适应性:可以处理复杂的含水层边界 2. 参数变化:可以处理非均匀的导水系数 3. 边界条件:可以灵活处理各种边界条件 4. 数值精度:通过网格细化提高计算精度

4.5 有限元法的收敛性分析与误差估计

4.5.1 收敛性理论基础

有限元法的收敛性是数值分析中的核心问题,它回答了”随着网格加密,数值解是否趋向于精确解”这一基本问题。

4.5.1.1 收敛性的数学定义

\(u\) 为精确解,\(u^h\) 为有限元解,其中 \(h\) 表示网格尺寸参数。如果存在常数 \(C\)\(p > 0\),使得:

\[\|u - u^h\|_V \leq C h^p\]

则称有限元方法以阶数 \(p\) 收敛,其中 \(\|\cdot\|_V\) 是适当的范数。

4.5.1.2 收敛性的三个层次

1. 一致性(Consistency) 有限元格式必须在网格加密时趋向于原微分方程: \[\lim_{h \to 0} \|L^h u - L u\| = 0\]

其中 \(L^h\) 是离散算子,\(L\) 是微分算子。

2. 稳定性(Stability) 数值格式必须对扰动不敏感,即存在常数 \(C\),使得: \[\|u^h\|_V \leq C \|f\|_{V^*}\]

3. 收敛性(Convergence) 根据Lax等价性定理,一致性 + 稳定性 ⟹ 收敛性。

4.5.2 先验误差估计

4.5.2.1 Céa引理

Céa引理是有限元误差分析的基石:

\[\|u - u^h\|_V \leq \frac{M}{\alpha} \inf_{v^h \in V^h} \|u - v^h\|_V\]

其中: - \(M\) 是双线性形式 \(a(\cdot,\cdot)\) 的连续性常数 - \(\alpha\) 是强制性常数 - \(V^h\) 是有限元空间

这表明有限元误差受最佳逼近误差控制。

4.5.2.2 插值误差估计

对于 \(p\) 阶多项式形函数,插值误差为:

\(L^2\) 范数误差\[\|u - I^h u\|_{L^2(\Omega)} \leq C h^{p+1} |u|_{H^{p+1}(\Omega)}\]

\(H^1\) 范数误差\[\|u - I^h u\|_{H^1(\Omega)} \leq C h^p |u|_{H^{p+1}(\Omega)}\]

其中 \(I^h u\)\(u\) 的插值,\(|\cdot|_{H^k}\)\(H^k\) 半范数。

4.5.2.3 最优收敛阶

结合Céa引理和插值误差估计,得到最优收敛阶:

  • 能量范数\(\|u - u^h\|_{H^1} = O(h^p)\)
  • \(L^2\) 范数\(\|u - u^h\|_{L^2} = O(h^{p+1})\)

这被称为Aubin-Nitsche技巧的结果。

4.5.3 后验误差估计

后验误差估计基于已计算的数值解,提供误差的实时评估。

4.5.3.1 残差型误差估计

定义残差: \[R(u^h) = f + \nabla \cdot (k \nabla u^h) \quad \text{in } \Omega_e\] \[r(u^h) = \llbracket k \nabla u^h \cdot \mathbf{n} \rrbracket \quad \text{on } \partial\Omega_e\]

其中 \(\llbracket \cdot \rrbracket\) 表示跳跃。

误差估计子: \[\eta_e^2 = h_e^2 \|R(u^h)\|_{L^2(\Omega_e)}^2 + h_e \|r(u^h)\|_{L^2(\partial\Omega_e)}^2\]

总误差估计: \[\|u - u^h\|_{H^1} \leq C \left(\sum_{e} \eta_e^2\right)^{1/2}\]

4.5.3.2 恢复型误差估计

基于超收敛现象,通过梯度恢复技术获得更精确的梯度:

\[\sigma^* = \mathcal{R}(\nabla u^h)\]

其中 \(\mathcal{R}\) 是恢复算子(如ZZ恢复算子)。

误差估计: \[\|u - u^h\|_{H^1} \approx \|\sigma^* - \nabla u^h\|_{L^2}\]

4.5.4 自适应网格细化

基于后验误差估计的自适应策略:

4.5.4.1 细化准则

等分布策略\[\eta_e \leq \frac{\eta_{\text{total}}}{\sqrt{N_e}}\]

固定比例策略: 细化误差最大的 \(\theta\) 比例单元(通常 \(\theta = 0.3\))。

标记策略: - Dörfler标记:选择单元使其误差贡献占总误差的 \(\theta\) 比例 - 最大策略:细化误差超过最大误差 \(\theta\) 倍的所有单元

4.5.4.2 细化算法

1. 求解当前网格上的有限元方程
2. 计算后验误差估计子 ηₑ
3. 如果 ‖η‖ < TOL,停止
4. 标记需要细化的单元
5. 细化网格,转到步骤1

4.5.5 数值实验与验证

4.5.5.1 制造解方法

为验证收敛阶,使用制造解: \[u_{\text{exact}}(x,t) = \sin(\pi x) e^{-\pi^2 \alpha t}\]

对应的源项: \[f = \frac{\partial u_{\text{exact}}}{\partial t} - \alpha \frac{\partial^2 u_{\text{exact}}}{\partial x^2}\]

4.5.5.2 收敛性测试

对一系列网格 \(h_1 > h_2 > \cdots > h_n\),计算:

收敛阶\[p = \frac{\log(E_1/E_2)}{\log(h_1/h_2)}\]

其中 \(E_i = \|u - u^{h_i}\|\) 是第 \(i\) 个网格上的误差。

渐近有效指数\[p_{\text{eff}} = \frac{\log(E_{i-1}/E_i)}{\log(h_{i-1}/h_i)}\]

4.5.5.3 数值结果分析

理论预测与数值实验的对比:

单元类型 理论收敛阶 数值收敛阶 验证状态
线性 \(O(h^2)\) 1.98
二次 \(O(h^3)\) 2.97
三次 \(O(h^4)\) 3.95

4.5.6 误差的物理解释

4.5.6.1 离散化误差的来源

  1. 空间离散化误差:有限维空间逼近无限维空间
  2. 时间离散化误差:时间积分格式的截断误差
  3. 数值积分误差:高斯积分的误差
  4. 舍入误差:计算机浮点运算误差

4.5.6.2 误差传播机制

  • 局部误差:单个单元内的误差
  • 全局误差:误差在整个域内的传播
  • 累积效应:时间推进过程中误差的积累

4.5.7 收敛性改进技术

4.5.7.1 网格质量优化

  • 网格规整化:避免过度畸变的单元
  • 梯度适应:在梯度大的区域加密网格
  • 边界层处理:边界附近的特殊处理

4.5.7.2 高精度技术

  • \(p\) 型细化:提高形函数阶数
  • \(hp\) 型细化:同时细化网格和提高阶数
  • 谱元方法:使用正交多项式基函数

4.6 有限元法与有限差分法的系统对比

4.6.1 理论基础对比

方面 有限元法 有限差分法
理论基础 变分原理,弱形式 Taylor展开,强形式
数学框架 函数空间理论 差分算子理论
收敛性 Céa引理,最优逼近 一致性+稳定性

4.6.2 精度和稳定性对比

精度特征: - 有限元法:具有最优收敛阶,\(L^2\) 范数超收敛 - 有限差分法:收敛阶取决于差分格式,通常较低

稳定性特征: - 有限元法:隐式格式无条件稳定,质量矩阵确保稳定性 - 有限差分法:显式格式有CFL限制,隐式格式稳定性较好

4.6.3 计算效率对比

内存需求: - 有限元法:需存储稀疏矩阵,内存需求 \(O(N \cdot \text{bandwidth})\) - 有限差分法:只需存储网格点值,内存需求 \(O(N)\)

计算复杂度: - 有限元法:矩阵组装 \(O(N)\),求解 \(O(N^{1.5})\)(稀疏矩阵) - 有限差分法:显式格式 \(O(N)\),隐式格式 \(O(N^{1.5})\)

4.6.4 适用性分析

几何适应性: - 有限元法:优秀,可处理任意几何形状 - 有限差分法:受限,主要适用于规则几何

边界条件处理: - 有限元法:自然边界条件易于处理 - 有限差分法:所有边界条件需特殊处理

多物理场耦合: - 有限元法:框架统一,易于扩展 - 有限差分法:需要特殊技巧处理耦合

4.7 高级有限元技术的深入探讨

4.7.1 高阶形函数技术

高阶形函数是提高有限元精度的重要手段,特别适用于解具有高正则性的问题。

4.7.1.1 分层形函数系统

分层基函数的优势: 1. 递归构造:高阶基函数基于低阶基函数构造 2. 局部支撑:便于实现局部\(p\)-细化 3. 正交性:某些分层基函数具有正交性质

一维分层基函数\[\phi_0(\xi) = \frac{1-\xi}{2}, \quad \phi_1(\xi) = \frac{1+\xi}{2}\] \[\phi_k(\xi) = \frac{1-\xi^2}{2} P_{k-1}(\xi), \quad k \geq 2\]

其中\(P_{k-1}\)是Legendre多项式。

4.7.1.2 谱元方法

Gauss-Lobatto-Legendre (GLL) 节点: 选择GLL节点作为插值点,形函数为: \[N_i(\xi) = \frac{(1-\xi^2) P'_N(\xi)}{N(N+1) P_N(\xi_i) (\xi - \xi_i)}\]

其中\(\xi_i\)是第\(i\)个GLL节点。

指数收敛性: 对于光滑解,谱元方法具有指数收敛性: \[\|u - u^h\| \leq C e^{-\gamma N}\]

4.7.2 自适应有限元技术

4.7.2.1 \(h\)-型自适应

网格细化策略: 1. 规则细化:将单元等分为子单元 2. 不规则细化:只细化部分边或面 3. 各向异性细化:根据解的各向异性特征细化

挂节点处理: 在不规则网格中,挂节点的约束关系: \[u_h = \frac{1}{2}(u_i + u_j)\]

其中\(u_i\)\(u_j\)是相邻节点的值。

4.7.2.2 \(p\)-型自适应

阶数选择策略: 基于解的光滑性指示器: \[\sigma_e = \frac{\|\nabla^p u^h\|_{L^2(\Omega_e)}}{\|\nabla^{p-1} u^h\|_{L^2(\Omega_e)}}\]

  • \(\sigma_e\)大,解在该单元内光滑,适合\(p\)-细化
  • \(\sigma_e\)小,解在该单元内不光滑,适合\(h\)-细化

4.7.2.3 \(hp\)-型自适应

最优策略: 结合\(h\)-细化和\(p\)-细化,达到指数收敛率: \[\|u - u^{hp}\| \leq C e^{-\gamma \sqrt[3]{N}}\]

其中\(N\)是自由度数量。

4.7.3 稳定化技术

4.7.3.1 流线迁移扩散方程

对于对流占优问题: \[-\epsilon \Delta u + \mathbf{b} \cdot \nabla u + cu = f\]

\(\epsilon \ll |\mathbf{b}|\)时,标准Galerkin法会产生数值振荡。

4.7.3.2 SUPG稳定化

流线迁移Petrov-Galerkin方法: 修改的弱形式: \[a(u^h, v^h) + \sum_e \tau_e \int_{\Omega_e} \mathcal{L}u^h \cdot (\mathbf{b} \cdot \nabla v^h) d\Omega = L(v^h) + \sum_e \tau_e \int_{\Omega_e} f \cdot (\mathbf{b} \cdot \nabla v^h) d\Omega\]

其中稳定化参数: \[\tau_e = \frac{h_e}{2|\mathbf{b}|} \coth(Pe_e) - \frac{1}{2|\mathbf{b}|}\]

\(Pe_e = \frac{|\mathbf{b}|h_e}{2\epsilon}\)是单元Peclet数。

4.7.3.3 GLS方法

Galerkin最小二乘方法: 在所有方向上添加稳定化项: \[a(u^h, v^h) + \sum_e \tau_e \int_{\Omega_e} \mathcal{L}u^h \cdot \mathcal{L}v^h d\Omega = L(v^h) + \sum_e \tau_e \int_{\Omega_e} f \cdot \mathcal{L}v^h d\Omega\]

4.7.4 多尺度有限元方法

4.7.4.1 多尺度基函数构造

对于具有多尺度系数的问题: \[-\nabla \cdot (a_\epsilon(x) \nabla u) = f\]

其中\(a_\epsilon(x)\)是快速振荡的系数。

多尺度基函数: 在每个粗网格单元\(K\)上求解局部问题: \[-\nabla \cdot (a_\epsilon \nabla \phi_i^{ms}) = 0 \quad \text{in } K\]

配以适当的边界条件。

4.7.4.2 变分多尺度方法

尺度分离: 将解分解为粗尺度和细尺度部分: \[u = u^h + u'\]

其中\(u^h\)是粗尺度解,\(u'\)是细尺度解。

细尺度建模: 通过建模细尺度的影响来改进粗尺度解: \[a(u^h, v^h) + a'(u', v^h) = (f, v^h)\]

4.7.5 间断有限元方法

4.7.5.1 DG方法的基本思想

弱连续性: 在单元内部使用标准的有限元方法,在单元边界上通过数值通量强制连续性。

DG弱形式\[\sum_e \int_{\Omega_e} \nabla u^h \cdot \nabla v^h dx - \sum_F \int_F \{\nabla u^h \cdot \mathbf{n}\} [v^h] ds + \sum_F \int_F \sigma [u^h] [v^h] ds = \sum_e \int_{\Omega_e} f v^h dx\]

其中\(\{·\}\)表示平均,\([·]\)表示跳跃,\(\sigma\)是惩罚参数。

4.7.5.2 内部惩罚方法

对称内部惩罚(SIPG)\[\sigma = \frac{\alpha p^2}{h_F}\]

其中\(\alpha\)是足够大的常数,\(p\)是多项式阶数,\(h_F\)是面的尺寸。

4.7.6 混合有限元方法

4.7.6.1 Raviart-Thomas元素

对于混合形式: \[\mathbf{q} + k \nabla u = 0\] \[\nabla \cdot \mathbf{q} = f\]

使用不同的函数空间: - \(\mathbf{q} \in RT_k\)(Raviart-Thomas空间) - \(u \in P_k\)(多项式空间)

RT空间的性质: 1. 法向连续性\(\mathbf{q} \cdot \mathbf{n}\)在单元边界上连续 2. 散度保持\(\nabla \cdot RT_k = P_k\)

4.7.7 有限元方法的并行计算

4.7.7.1 区域分解方法

Schwarz方法: 将计算域分解为重叠或非重叠的子域: \[\Omega = \bigcup_{i=1}^N \Omega_i\]

在每个子域上并行求解局部问题。

4.7.7.2 多重网格方法

几何多重网格: 使用一系列从粗到细的网格: \[\Omega^1 \subset \Omega^2 \subset \cdots \subset \Omega^L\]

通过在不同网格层次间传递信息加速收敛。

代数多重网格: 直接从系数矩阵构造粗网格算子,不依赖几何信息。

4.7.8 高级技术的应用实例

4.7.8.1 地震波传播模拟

问题特点: - 大规模计算域 - 多尺度现象(地表细节vs深部结构) - 复杂几何边界

技术选择: - \(hp\)-自适应:处理波前的尖锐特征 - 谱元方法:在光滑区域实现高精度 - 并行计算:处理大规模问题

4.7.8.2 多孔介质流动

问题特点: - 多尺度渗透率 - 对流占优传输 - 多相流耦合

技术选择: - 多尺度基函数:捕获细尺度渗透率变化 - SUPG稳定化:处理对流占优问题 - 混合有限元:确保质量守恒

4.7.9 实现考虑

4.7.9.1 数据结构设计

自适应网格存储: - 树状结构:便于细化和粗化 - 邻接信息:快速访问相邻单元 - 挂节点管理:处理不规则网格

4.7.9.2 高效矩阵组装

单元矩阵缓存: 对于相同形状的单元,预计算并缓存单元矩阵。

向量化计算: 利用现代CPU的向量指令集加速矩阵运算。

4.7.10 软件实现框架

4.7.10.1 开源有限元库

deal.II: - C++实现 - 支持\(hp\)-自适应 - 丰富的教程和文档

FEniCS: - Python/C++混合 - 自动代码生成 - 强大的符号计算

MFEM: - 模块化设计 - 高性能计算优化 - 支持多种单元类型

4.8 总结

有限元法作为一种强大的数值分析方法,在地球科学中具有广泛的应用前景。通过本章的学习,读者应该掌握:

  1. 理论基础:理解变分原理和弱形式的概念
  2. 数学推导:掌握形函数和单元矩阵的构造方法
  3. 编程实现:能够使用R语言实现简单的有限元程序
  4. 方法对比:了解有限元法与有限差分法的优缺点
  5. 应用前景:认识有限元法在地球科学中的潜在应用

有限元法的优势在于其灵活性和精度,特别适合处理复杂几何和多物理场耦合问题。随着计算技术的发展,有限元法将在地球科学数值模拟中发挥越来越重要的作用。

4.8.1 本章要点回顾

4.8.1.1 理论基础

  • 变分原理:将偏微分方程转化为变分问题
  • 弱形式:通过分部积分降低导数阶数
  • 形函数:定义单元内插值关系

4.8.1.2 数学方法

  • 有限元离散化:空间域划分为有限个单元
  • 矩阵组装:构造质量矩阵和刚度矩阵
  • 边界条件处理:施加Dirichlet边界条件

4.8.1.3 编程实现

  • 基本有限元法:线性形函数,均匀网格
  • 高级技术:高阶形函数,自适应网格
  • 性能优化:稀疏矩阵,高效求解器

4.8.1.4 应用案例

  • 一维热传导:铁棍温度分布演化
  • CLAMS方法:系统化的建模流程
  • 结果可视化:温度分布和网格对比

4.8.2 扩展阅读建议

  1. 理论深化:学习更高级的有限元理论,如混合有限元法、间断有限元法等
  2. 应用拓展:探索有限元法在地球科学其他领域的应用,如流体力学、结构力学等
  3. 软件工具:熟悉专业的有限元软件,如COMSOL、ANSYS等
  4. 并行计算:学习大规模有限元问题的并行求解技术

4.8.3 实践练习

  1. 修改参数:尝试不同的网格密度、时间步长等参数,观察对结果的影响
  2. 边界条件:实现其他类型的边界条件,如Neumann边界条件
  3. 物理模型:将热传导模型扩展到其他物理过程,如扩散、对流等
  4. 网格生成:实现非均匀网格的生成和有限元求解

通过本章的学习和实践,读者应该能够理解有限元法的基本原理,掌握其实现方法,并能够将其应用到实际的地球科学问题中。