有限元算例-线弹性方程求解#

本节将引导您使用 FEALPy 完成二维线弹性方程的有限元求解,现定义如下数学模型:

\[\begin{split} \begin{cases} -\nabla \cdot \boldsymbol{\sigma}(\mathbf{u}) = \mathbf{f} & \text{在 } \Omega = [0,1]^2 \\ \mathbf{u} = \mathbf{g} & \text{在 } \partial \Omega \end{cases} \end{split}\]

其中应力张量 \(\boldsymbol{\sigma}(\mathbf{u})\) 由广义胡克定律给出:

\[ \boldsymbol{\sigma}(\mathbf{u}) = 2\mu \boldsymbol{\varepsilon}(\mathbf{u}) + \lambda \mathrm{tr}(\boldsymbol{\varepsilon}(\mathbf{u})) \mathbf{I}, \]

也可以写成弹性矩阵的形式:

\[ \boldsymbol{\sigma}(\boldsymbol{u}) = \boldsymbol{D}\boldsymbol{\varepsilon}(\boldsymbol{u}) \]

应变张量定义为:

\[ \boldsymbol{\varepsilon}(\mathbf{u}) = \frac{1}{2} \left( \nabla \mathbf{u} + (\nabla \mathbf{u})^T \right). \]

取材料参数为:杨氏模量 \(E = 1.0\),泊松比 \(\nu = 0.3\),并定义对应的拉梅常数:

\[ \mu = \frac{E}{2(1+\nu)}, \quad \lambda = \frac{E \nu}{(1+\nu)(1-2\nu)}. \]

其中定义真解为:

\[\begin{split} \mathbf{u}(x, y) = \begin{bmatrix} \sin(\pi x) \sin(\pi y) \\ 0 \end{bmatrix} \end{split}\]

对应的源项为:

\[\begin{split} \mathbf{f}(x, y) = \begin{bmatrix} \displaystyle \frac{22.5\pi^2}{13} \sin(\pi x) \sin(\pi y) \\ \displaystyle -\frac{12.5\pi^2}{13} \cos(\pi x) \cos(\pi y) \end{bmatrix} \end{split}\]

变分形式为:

\[ \int_\Omega \boldsymbol{\sigma}(\boldsymbol{u_h}) : \boldsymbol{\varepsilon}(\boldsymbol{v_h}) ~ \mathrm{d}\boldsymbol{x}  = \int_\Omega \boldsymbol{f}\cdot \boldsymbol{v_h} ~ \mathrm{d}\boldsymbol{x} +\int_{\partial\Omega} \boldsymbol{g}\cdot\boldsymbol{v_h} ~ \mathrm{d}\boldsymbol{s},\quad\forall\boldsymbol{v_h}\in V_h \]

1. 定义 PDE 模型#

本节首先定义 PDE 的数学模型及其相关参数、真解和边界条件。

import math
from fealpy.backend import backend_manager as bm
from fealpy.decorator import cartesian

# 定义域
def domain():
    return [0, 1, 0, 1]

# 杨氏模量
def E():
    return 1.0

# 泊松比
def nu():
    return 0.3

# 剪切模量
def mu():
    return E() / (2 * (1 + nu()))

# 拉梅常数
def lam():
    return E() * nu() / ((1 + nu()) * (1 - 2 * nu()))

# 真解
@cartesian
def solution(p):
    x, y = p[..., 0], p[..., 1]
    u1 = bm.sin(math.pi * x) * bm.sin(math.pi * y)
    u2 = bm.zeros_like(u1)
    return bm.stack([u1, u2], axis=-1)

# 源项
@cartesian
def source(p):
    x, y = p[..., 0], p[..., 1]
    π2 = math.pi ** 2
    f1 = (22.5 * π2 / 13.0) * bm.sin(math.pi * x) * bm.sin(math.pi * y)
    f2 = -(12.5 * π2 / 13.0) * bm.cos(math.pi * x) * bm.cos(math.pi * y)
    return bm.stack([f1, f2], axis=-1)

# dirichlet 边界条件
@cartesian
def dirichlet(p):
    return solution(p)

2. 参数配置与初始化#

本节将进行后端设置、日志工具导入、网格初始化等准备工作。

2.1 设置后端#

选择数值计算后端(如 numpy、cupy 等)及计算设备。

from fealpy.backend import backend_manager as bm

backend = 'numpy'
device = 'cpu'
bm.set_backend(backend)

2.2 导入日志工具#

用于记录各阶段的耗时与调试信息。

from fealpy.utils import timer
from fealpy import logger

logger.setLevel('WARNING')
tmr = timer()
next(tmr)

2.3 设置初始网格和加密次数#

初始化计算域的网格,并设置网格加密的迭代次数。

from fealpy.mesh import TriangleMesh

mesh = TriangleMesh.from_box(domain(), nx=4, ny=4)
maxit = 4

2.4 定义误差存储矩阵#

用于记录每次网格加密下的数值误差。

errorMatrix = bm.zeros((1, maxit), dtype=bm.float64)

3. 有限元求解#

本节将依次完成有限元空间构建、矩阵组装、边界处理、线性系统求解、误差计算与网格加密。

流程包含:

  1. 构建 Lagrange 有限元空间

  2. 组装刚度矩阵 \(A\) 和载荷向量 \(F\)

  3. 处理 Dirichlet 边界条件

  4. 求解线性系统 \(A u_h = F\)

  5. 计算 \(L^2\) 误差 \(\|u - u_h\|_{L^2(\Omega)}\)

  6. 网格均匀加密

from fealpy.functionspace import functionspace
from fealpy.material import LinearElasticMaterial
from fealpy.fem import BilinearForm, LinearElasticityIntegrator
from fealpy.fem import LinearForm, ScalarSourceIntegrator
from fealpy.fem import DirichletBC
from fealpy.solver import cg

for i in range(maxit):
    GD = mesh.geo_dimension()
    space = functionspace(mesh, ('Lagrange', 1), shape=(GD, -1))
    tmr.send(f'第{i}次空间时间')
    uh = space.function()
    
    LEM = LinearElasticMaterial(name='E1nu025',
                                lame_lambda=lam(), shear_modulus=mu(),
                                hypo='plane_strain', device=device)
    LEI = LinearElasticityIntegrator(material=LEM, q=3)
    bform = BilinearForm(space)
    bform.add_integrator(LEI)
    A = bform.assembly()
    lform = LinearForm(space)
    lform.add_integrator(ScalarSourceIntegrator(source, q=3))
    F = lform.assembly()
    tmr.send(f'第{i}次矩阵组装时间')
    gdof = space.number_of_global_dofs()
    A, F = DirichletBC(space, gd = dirichlet).apply(A, F)
    tmr.send(f'第{i}次边界处理时间')
    uh[:] = cg(A, F)
    tmr.send(f'第{i}次求解器时间')
    errorMatrix[0, i] = mesh.error(solution, uh)
    if i < maxit-1:
        mesh.uniform_refine(n=1)
    tmr.send(f'第{i}次误差计算及网格加密时间')

4. 误差分析与收敛阶计算#

本节将输出每次网格加密下的误差,并计算收敛阶。

next(tmr)
print("最终误差",errorMatrix)
print("order : ", bm.log2(errorMatrix[0,:-1]/errorMatrix[0,1:]))
Timer received None and paused.
=================================================
   ID       Time        Proportion(%)    Label
-------------------------------------------------
    1    574.838 [ms]          91.366    第0次空间时间
    2     11.292 [ms]           1.795    第0次矩阵组装时间
    3      2.021 [ms]           0.321    第0次边界处理时间
    4      0.000 [us]           0.000    第0次求解器时间
    5      0.000 [us]           0.000    第0次误差计算及网格加密时间
    6      0.000 [us]           0.000    第1次空间时间
    7      3.089 [ms]           0.491    第1次矩阵组装时间
    8      0.000 [us]           0.000    第1次边界处理时间
    9    994.205 [us]           0.158    第1次求解器时间
   10      0.000 [us]           0.000    第1次误差计算及网格加密时间
   11      0.000 [us]           0.000    第2次空间时间
   12      4.987 [ms]           0.793    第2次矩阵组装时间
   13      1.008 [ms]           0.160    第2次边界处理时间
   14    998.259 [us]           0.159    第2次求解器时间
   15      1.988 [ms]           0.316    第2次误差计算及网格加密时间
   16      0.000 [us]           0.000    第3次空间时间
   17     19.967 [ms]           3.174    第3次矩阵组装时间
   18    980.377 [us]           0.156    第3次边界处理时间
   19      5.002 [ms]           0.795    第3次求解器时间
   20      1.997 [ms]           0.317    第3次误差计算及网格加密时间
=================================================
最终误差 [[0.0817831  0.02305386 0.00601597 0.00152303]]
order :  [1.82679432 1.93813903 1.98185488]

5. 结果可视化#

在单元重心处计算真解和数值解,并进行可视化比较。

注:由于真解 \(y\) 分量为 0,故仅展示 \(x\) 分量的可视化结果。

from matplotlib import pyplot as plt

bc = bm.array([[1/3, 1/3, 1/3]], dtype=bm.float64)
ps = mesh.bc_to_point(bc)
u = solution(ps)
ux = u[..., 0]
uh = uh(bc)
uhx = uh[..., 0]

fig, axes = plt.subplots(1, 2)
mesh.add_plot(axes[0], cellcolor=ux, linewidths=0)
axes[0].set_title('真解', fontname='Microsoft YaHei')
mesh.add_plot(axes[1], cellcolor=uhx, linewidths=0)
axes[1].set_title('数值解', fontname='Microsoft YaHei')
plt.show()
../_images/b0113e61ac0e1d36e5d9e3370951728947666c57fa0fdca9b8cb4dae0f135891.png