有限元学习 时变动力学问题之FEniCS求解 bxplosion 2024-07-01 2024-07-05 FEniCS系列第五讲
【本文仅是视频中Markdown文件内容,不包括视频中讲解】
如果你希望和我一样Win10上安装FEniCS,建议采用Win10 + WSL2 +
Ubuntu。
时变动力学问题之FEniCS求解
FEniCS系列讲座
第1讲 有限元法求解偏微分方程之FEniCS入门讲解
第2讲 混合形式泊松方程之FEniCS求解
第3讲 非线性变分问题之FEniCS求解
第4讲 从变分原理到变分方程之FEniCS求解【上一讲】
本讲要点
瞬态变分方程
广义α方法(the generalized-α method)。
时间依赖的表达式定义
如何区分不同的边界并标记之
需要反复迭代时,使用solve函数效率低下,解决方法。
动力学方程瞬间的变分方程
弹性动力学方程
\[
\nabla\cdot\textcolor{red}{\sigma}+\rho b = \rho \ddot{u} \\ \quad \\
\sigma = \lambda\mathrm{tr}(\epsilon)I+2\mu\epsilon \\
\epsilon=\frac{\nabla u+(\nabla u)^T}{2} \\
\lambda=\frac{E\nu}{(1+\nu)(1-2\nu)},\mu=\frac{E}{2(1+\nu)}
\]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 E = 1000.0 nu = 0.3 mu = Constant(E / (2.0 *(1.0 + nu))) lmbda = Constant(E*nu / ((1.0 + nu)*(1.0 - 2.0 *nu))) rho = Constant(1.0 ) def epsilon (u_ ): return sym(grad(u_)) def sigma (u_ ): return 2.0 *mu*epsilon(u_) + lmbda*tr(epsilon(u_))*Identity(len (u_))
在任意“冻结“的瞬间,弹性动力学方程,可以按以前的套路,改写成变分方程(待求量在左边,已知量在右边)
\[
\int_\Omega\rho\ddot{u}\cdot v
dx-\int_\Omega\textcolor{red}{(\nabla\cdot\sigma)\cdot v}
dx=\int_\Omega\rho b\cdot v dx
\]
\[
(\nabla\cdot\sigma)\cdot v=\frac{\partial \sigma_{ij}}{\partial
x_i}v_j=\frac{\partial}{\partial
x_i}(\sigma_{ij}v_j)-\textcolor{red}{\sigma_{ij}\frac{\partial
v_j}{\partial x_i}}=\nabla \cdot(\sigma\cdot v)-\sigma \cdot \nabla v
\]
\[
\sigma \cdot \nabla v=\sigma \cdot (\nabla v)^T=\sigma(u) \cdot
\textcolor{red}{\epsilon(v)}
\]
\[
\int_\Omega\rho\ddot{u}\cdot v dx+\int_\Omega\sigma(u) \cdot \epsilon(v)
dx=\int_\Omega\rho b\cdot v
dx+\int_{\partial\Omega}(\textcolor{red}{\sigma\cdot n})\cdot v ds
\]
\[
\sigma\cdot n|_{\partial\Omega}=p
\]
为了后面编程方便,不妨引入几个记号(函数):
\[
m(u,v)=\int_\Omega\rho u\cdot v dx\\ k(u,v)=\int_\Omega\sigma(u) \cdot
\epsilon(v) dx\\ f(v)=\int_\Omega\rho b\cdot v
dx+\int_{\partial\Omega}\textcolor{red}{p}\cdot v ds
\]
于是变分方程可以简单写成:
\[
m(\ddot{u},v)+k(u,v)=f(v)
\]
必要的话,我们可能还需要加一个耗散项\(\textcolor{red}{c(\dot{u},v)}\) :
\[
c(u,v)=\eta_m m(u,v)+\eta_k k(u,v)
\]
最后有
\[
\boxed{m(\ddot{u},v)+c(\dot{u},v)+k(u,v)=f(v)}
\]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 def m (u_,v_ ): return rho*inner(u_, v_)*dx def k (u_,v_ ): return inner(sigma(u_), epsilon(v_))*dx eta_m = Constant(0. ) eta_k = Constant(0. ) def c (u_,v_ ): return eta_m*m(u_,v_) + eta_k*k(u_,v_) def f (v_ ): return rho*dot(b,v_)*dx+dot(p,v_)*ds
特定瞬间的应变,速度和加速度
已知上时刻 的应变\(u_0\) ,速度\(v_0\) 和加速度\(a_0\) ,可以根据前面的瞬态变分方程求解出当前时刻的应变\(u\) 。
进而可以通过有限差分法 近似算出当前时刻的对应值。
为了更精确计算,不妨将应变\(u\) 用泰勒级数展开,然后保留头3项:
\[
u=u_0+(\Delta t) v_0+\frac{1}{2}(\Delta t)^2 \textcolor{red}{a}
\]
根据这个式,可估算出当前时刻加速度
\[
a=\frac{2}{(\Delta t)^2}[u-u_0-(\Delta t)v_0]
\]
类似地,速度\(v\) 用泰勒级数展开,保留头2项:
\[
v=v_0+(\Delta t)\textcolor{red}{a}
\]
当前时刻应变(求变分方程的解),加速度(如前估算),速度(如前估算),这3个量都有了。
为了数值稳定性,\(u\) 的展开式中的\(a\) 用“平均”值替代
\[
a\leftarrow (1-2\beta)a_0+2\beta \textcolor{red}{a}
\]
得到当前时刻新的加速度估计式 :
\[
\boxed{a=\frac{1}{\beta(\Delta t)^2}[u-u_0-(\Delta
t)v_0]-\frac{1-2\beta}{2\beta}a_0}
\]
对速度\(v\) 估计式中的加速度,进一步用“平均”值替代
\[
a\leftarrow (1-\gamma)a_0+\gamma a
\]
得到当前时刻新的速度估计式:
\[
\boxed{v=v_0+(\Delta t)[(1-\gamma)a_0+\gamma a]}
\]
这两个估计式,就是我们最终要的速度和加速度更新式,写成函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 def update_a (u_, u_old_, v_old_, a_old_, ufl=True ): if ufl: dt_ = dt beta_ = beta else : dt_ = float (dt) beta_ = float (beta) return (u_-u_old_-dt_*v_old_)/beta_/dt_**2 - (1 -2 *beta_)/2 /beta_*a_old_ def update_v (a_, u_old_, v_old_, a_old_, ufl=True ): if ufl: dt_ = dt gamma_ = gamma else : dt_ = float (dt) gamma_ = float (gamma) return v_old_ + dt_*((1 -gamma_)*a_old_ + gamma_*a_) def update_fields (u_, u_old_, v_old_, a_old_ ): u_vec, u0_vec = u_.vector(), u_old_.vector() v0_vec, a0_vec = v_old_.vector(), a_old_.vector() a_vec = update_a(u_vec, u0_vec, v0_vec, a0_vec, ufl=False ) v_vec = update_v(a_vec, u0_vec, v0_vec, a0_vec, ufl=False ) v_old_.vector()[:], a_old_.vector()[:] = v_vec, a_vec u_old_.vector()[:] = u_.vector()
对于要进入变分方程的\(\ddot{u},\dot{u},u\) 也要用某种形式的用“平均”值替代:
\[
a\leftarrow (1-\alpha_m)a+\alpha_m a_0\\ v\leftarrow
(1-\alpha_f)v+\alpha_f v_0 \\ u\leftarrow (1-\alpha_f)u+\alpha_f u_0
\]
写成代码为:
1 2 3 4 def avg (x_old, x_new, alpha ): return (1 -alpha)*x_new + alpha*x_old
这个方法就是著名的广义α方法 (the generalized-α
method)。
注意,这个方法有4个待选定的参数:\(\alpha_m,\alpha_f,\beta,\gamma\) 。
一个可以保证确保无条件稳定性 的热门选择是:
\[
\alpha_m,\alpha_f\le 1\\ \gamma=\frac{1}{2}+\alpha_m-\alpha_f\\
\beta=\frac{1}{4}\left(\gamma+\frac{1}{2}\right)^2
\]
1 2 3 4 5 6 alpha_m = Constant(0.2 ) alpha_f = Constant(0.4 ) gamma = Constant(0.5 +alpha_f-alpha_m) beta = Constant((gamma+0.5 )**2 /4. )
输入函数,域,边界条件,初始条件
\(\Omega=[0,1]\times[0,0.1]\times[0,0.04]\) 【一个长条形】
\(t\in[0,T]\) 【演示时长T=8s】
初始时刻:无应变,无速度,无加速度
任意时刻,左侧面\(0\times[0,0.1]\times[0,0.04]\) ,固定无应变
左侧面则是\(1\times[0,0.1]\times[0,0.04]\)
\([0,T/10]\) 时间段,右侧面受到\(y\) 轴方向的线性增长的面元牵引力。
\([T/10,T]\) 时间段,右侧面不受到任何面元牵引力。
\(b=0\) 【不受体元外力】
FEniCS代码实现
第一步:创建网格定义函数空间
1 2 3 4 5 6 from dolfin import *import numpy as npmesh = BoxMesh(Point(0. , 0. , 0. ), Point(1. , 0.1 , 0.04 ), 60 , 10 , 5 ) V = VectorFunctionSpace(mesh, "CG" , 1 )
第二步:定义已提供的表达式和参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 E = 1000.0 nu = 0.3 mu = Constant(E / (2.0 *(1.0 + nu))) lmbda = Constant(E*nu / ((1.0 + nu)*(1.0 - 2.0 *nu))) rho = Constant(1.0 ) eta_m = Constant(0. ) eta_k = Constant(0. ) alpha_m = Constant(0.2 ) alpha_f = Constant(0.4 ) gamma = Constant(0.5 +alpha_f-alpha_m) beta = Constant((gamma+0.5 )**2 /4. ) T = 8.0 Nsteps = 100 dt = Constant(T/Nsteps) time = np.linspace(0 , T, Nsteps+1 ) p0 = 1. cutoff_Tc = T/10 p = Expression(("0" , "t <= tc ? p0*t/tc : 0" , "0" ), t=0 , tc=cutoff_Tc, p0=p0, degree=0 ) b = Constant((0.0 , 0.0 , 0.0 )) u0 = Constant((0.0 , 0.0 , 0.0 ))
第三步:创建和应用基本边界条件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 def left (x, on_boundary ): return near(x[0 ], 0. ) and on_boundary def right (x, on_boundary ): return near(x[0 ], 1. ) and on_boundary boundary_subdomains = MeshFunction("size_t" , mesh, mesh.topology().dim() - 1 ) boundary_subdomains.set_all(0 ) force_boundary = AutoSubDomain(right) force_boundary.mark(boundary_subdomains, 3 ) dss = ds(subdomain_data=boundary_subdomains) bc = DirichletBC(V, u0, left)
第四步:定义变分问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 def epsilon (u_ ): return sym(grad(u_)) def sigma (u_ ): return 2.0 *mu*epsilon(u_) + lmbda*tr(epsilon(u_))*Identity(len (u_)) def m (u_,v_ ): return rho*inner(u_, v_)*dx def k (u_,v_ ): return inner(sigma(u_), epsilon(v_))*dx def c (u_,v_ ): return eta_m*m(u_,v_) + eta_k*k(u_,v_) def f (v_ ): return rho*dot(b,v_)*dx+dot(p,v_)*dss(3 ) def update_a (u_, u_old_, v_old_, a_old_, ufl=True ): if ufl: dt_ = dt beta_ = beta else : dt_ = float (dt) beta_ = float (beta) return (u_-u_old_-dt_*v_old_)/beta_/dt_**2 - (1 -2 *beta_)/2 /beta_*a_old_ def update_v (a_, u_old_, v_old_, a_old_, ufl=True ): if ufl: dt_ = dt gamma_ = gamma else : dt_ = float (dt) gamma_ = float (gamma) return v_old_ + dt_*((1 -gamma_)*a_old_ + gamma_*a_) def update_fields (u_, u_old_, v_old_, a_old_ ): u_vec, u0_vec = u_.vector(), u_old_.vector() v0_vec, a0_vec = v_old_.vector(), a_old_.vector() a_vec = update_a(u_vec, u0_vec, v0_vec, a0_vec, ufl=False ) v_vec = update_v(a_vec, u0_vec, v0_vec, a0_vec, ufl=False ) v_old_.vector()[:], a_old_.vector()[:] = v_vec, a_vec u_old_.vector()[:] = u_.vector() def avg (x_old, x_new, alpha ): return (1 -alpha)*x_new + alpha*x_old u = TrialFunction(V) v = TestFunction(V) u_ = Function(V) u_old = Function(V) v_old = Function(V) a_old = Function(V) a_new = update_a(u, u_old, v_old, a_old, ufl=True ) v_new = update_v(a_new, u_old, v_old, a_old, ufl=True ) F = m(avg(a_old, a_new, alpha_m),v)+c(avg(v_old, v_new, alpha_f),v)+k(avg(u_old, u, alpha_f),v) - f(v) a = lhs(F) L = rhs(F)
第五步:循环迭代求解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 vtkfile = File("elastodynamics.pvd" ) K, res = assemble_system(a, L, bc) solver = LUSolver(K, "mumps" ) solver.parameters["symmetric" ] = True for (i, dt) in enumerate (np.diff(time)): t = time[i+1 ] print ("Time: " , t) p.t = t-float (alpha_f*dt) res = assemble(L) bc.apply(res) solver.solve(K, u_.vector(), res) vtkfile << (u_, t) update_fields(u_, u_old, v_old, a_old)
END