相场断裂

把脆性断裂看作能量最小化问题。损伤场 \(\alpha \in [0, 1]\) 把裂纹弥散在长度 \(\ell\) 的范围内,于是裂纹在哪里萌生、沿哪条路径扩展、何时失稳扩展,都由同一个能量的最小化决定 [Francfort1998] [Bourdin2000]。脚本 examples/solid/phase_field_fracture/phase_field_fracture.py 在平面应变下运行单边缺口拉伸(SENT)基准算例 [Miehe2010]。

与 塑性(带各向同性硬化的 J2) 相比,新增的要素是 不等式约束:损伤不可愈合,\(\alpha_n \ge \alpha_{n-1}\)。因此每个载荷步都是一个带界约束的最小化问题。脚本用交替最小化求解它,两个子问题都使用 torch-sla 中的 vi_solve——一种带上下界的牛顿求解。

模型

位移 \(\mathbf{u}\) 与损伤场 \(\alpha\) 的能量为

\[\mathcal{E}(\mathbf{u}, \alpha) = \int_\Omega \Bigl[ g(\alpha)\, \psi_D(\boldsymbol{\varepsilon}) + \psi_R(\boldsymbol{\varepsilon}) \Bigr] \mathrm{d}\Omega + \frac{G_c}{c_w} \int_\Omega \Bigl[ \frac{w(\alpha)}{\ell} + \ell\, |\nabla\alpha|^2 \Bigr] \mathrm{d}\Omega, \qquad g(\alpha) = (1 - \alpha)^2 + g_0,\]

其中 \(\boldsymbol{\varepsilon} = \tfrac12(\nabla\mathbf{u} + \nabla\mathbf{u}^T)\),\(g_0\) 是一个小的残余刚度。

  • 裂纹模型。 AT1 取 \(w(\alpha) = \alpha\)、\(c_w = 8/3\),它有一个弹性阶段:应力低于临界值时不产生损伤 [Pham2011]。AT2 取 \(w(\alpha) = \alpha^2\)、\(c_w = 2\),在任意载荷下都会产生损伤。用 --model 选择。

  • 能量分解 [Amor2009]。只有受拉的体积部分和偏量部分会被退化,因此压缩不会产生裂纹:

    \[\psi_D = \tfrac{\kappa}{2} \langle \operatorname{tr}\boldsymbol{\varepsilon} \rangle_+^2 + \mu\, |\operatorname{dev}\boldsymbol{\varepsilon}|^2, \qquad \psi_R = \tfrac{\kappa}{2} \langle \operatorname{tr}\boldsymbol{\varepsilon} \rangle_-^2.\]

脚本只在 PhaseFieldModel 中写一次这两个能量密度。energy() 对它们积分,autograd 再把积分转换为残差 \(\partial\mathcal{E}/\partial\mathbf{u}\) 和 \(\partial\mathcal{E}/\partial\alpha\)。

以界约束表达不可逆性

在载荷步 \(n\),损伤在 \([\alpha_{n-1}, 1]\) 中求解。在这些界约束下的极小点逐节点满足

\[\alpha_i = \alpha_{n-1,i} \;\Rightarrow\; \partial_i \mathcal{E} \ge 0, \qquad \alpha_{n-1,i} < \alpha_i < 1 \;\Rightarrow\; \partial_i \mathcal{E} = 0, \qquad \alpha_i = 1 \;\Rightarrow\; \partial_i \mathcal{E} \le 0.\]

要么残差为零,要么该节点停在一个界上,且残差把它推向界外。这是一个变分不等式,正是 torch_sla.vi_solve(residual, x0, lower=..., upper=..., jacobian_fn=...) 所求解的问题。所用方法是降维空间的有效集牛顿法,即 PETSc 的 SNESVINEWTONRSLS 所用的算法 [Benson2006]。每个牛顿步先对不在界上的节点求解,再更新哪些节点停在界上。

给定值同样是界约束:lower == upper 即固定该分量。位移的狄利克雷边界条件就是这样施加的,因此位移求解不需要 Condenser。预制裂纹也可以用同样的方式给定,即在一条节点线上保持 \(\alpha = 1\)(alpha_fixed 参数)。下面的基准算例改为在网格中开一条狭缝,使缺口面从一开始就是自由的。

交替最小化

每个载荷步在两个场之间交替求解,直到二者相容:

  1. 位移,固定 \(\alpha\)。\(\mathcal{E}\) 关于 \(\mathbf{u}\) 是凸的,但只是分片二次的,因为能量分解随 \(\operatorname{tr}\boldsymbol{\varepsilon}\) 的符号切换。带线搜索的牛顿法只需几步。

  2. 损伤,固定 \(\mathbf{u}\)。对 AT1 和 AT2,\(\mathcal{E}\) 关于 \(\alpha\) 是二次的,因此这是一个带界约束的二次规划。它的 Hessian 不依赖于 \(\alpha\),只需装配一次。

  3. 停止:用新的损伤计算出的、未约束自由度上的位移残差小于反力的 \(10^{-4}\) 倍时停止。

    def solve_step(self, u_bc):
        """One load step with the Dirichlet values ``u_bc`` (full dof vector);
        returns the number of staggered iterations."""
        inf = torch.full_like(self.u, math.inf)
        u_lower = torch.where(self.u_fixed, u_bc, -inf)
        u_upper = torch.where(self.u_fixed, u_bc, inf)
        a_lower = torch.where(self.alpha_fixed, self.alpha_value, self.alpha)  # irreversibility
        a_upper = torch.where(self.alpha_fixed, self.alpha_value, torch.ones_like(self.alpha))
        scale = 1e-12
        for it in range(1, self.max_staggered + 1):
            alpha = self.alpha
            self.u = vi_solve(lambda u: self.residual_u(u, alpha), self.u, lower=u_lower, upper=u_upper,
                              jacobian_fn=lambda u: self.tangent_u(u, alpha), line_search=True,
                              tol=1e-9, atol=1e-11, linear_solver=self.linear_solver)
            u = self.u
            hessian = self.hessian_alpha(u)  # constant during the alpha solve
            alpha_new = vi_solve(lambda a: self.residual_alpha(a, u), alpha, lower=a_lower, upper=a_upper,
                                 jacobian_fn=lambda a: hessian, tol=1e-9, atol=1e-11,
                                 linear_solver=self.linear_solver)
            r = self.residual_u(u, alpha_new)
            free = float(torch.linalg.vector_norm(r[~self.u_fixed]))
            scale = max(scale, float(torch.linalg.vector_norm(r[self.u_fixed])))
            if free <= self.tol * scale:
                self.alpha = alpha_new
                return it
            if self.relaxation != 1.0:
                alpha_new = (alpha + self.relaxation * (alpha_new - alpha)).clamp(a_lower, a_upper)
            self.alpha = alpha_new
        raise RuntimeError(f"staggered scheme did not converge in {self.max_staggered} iterations")

两个雅可比矩阵都是双线性形式。与 TensorMesh 的所有形式一样,forward 描述一个求积点和一对基函数。点数据 disp 和 alpha 以 graddisp 和 alpha 的形式传入:

class DisplacementTangent(ElementAssembler):
    """d2E/du2 with the split's tangent C = g C_0 + (1 - g) kappa [tr eps < 0] I x I."""

    def __post_init__(self, model: PhaseFieldModel):
        self.model = model

    def forward(self, gradu, gradv, graddisp, alpha):
        m = self.model
        g = m.g(alpha)
        compressed = (graddisp.diagonal().sum() < 0).to(gradu.dtype)
        lam = g * m.lam + (1 - g) * m.kappa * compressed
        I = torch.eye(gradu.shape[0], dtype=gradu.dtype, device=gradu.device)
        return lam * torch.outer(gradu, gradv) + g * m.mu * ((gradu * gradv).sum() * I + torch.outer(gradv, gradu))
class PhaseFieldHessian(ElementAssembler):
    """d2E/dalpha2, which does not depend on alpha for AT1 and AT2 (g'' = 2)."""

    def __post_init__(self, model: PhaseFieldModel):
        self.model = model

    def forward(self, u, v, gradu, gradv, graddisp):
        m = self.model
        reaction = 2 * m.psi_D(m.strain(graddisp)) + m.Gc / (m.cw * m.ell) * m.ddw
        return reaction * u * v + 2 * m.Gc * m.ell / m.cw * (gradu * gradv).sum()

单边缺口拉伸

  • 几何:单位正方形 \([0, 1]^2\) mm,沿 \(y = 0.5\) 从左边缘到中心开有一条狭缝。狭缝上方的单元拥有其节点的独立副本,因此两侧缝面可以自由分开。

  • 材料:\(E = 210\) kN/mm²,\(\nu = 0.3\),\(G_c = 2.7 \times 10^{-3}\) kN/mm,\(\ell = 0.015\) mm,AT1,\(g_0 = 10^{-6}\)。

  • 加载:底边固支;顶边 \(u_x = 0\)、\(u_y = \delta\),增量为 \(10^{-4}\) mm。

  • 网格:用 NumPy 生成的对角线交错(union-jack)三角形网格。相邻单元的对角线像棋盘一样交替,因此网格关于裂纹线镜像对称,不偏向任何裂纹方向。在裂纹路径附近的条带内 \(h = \ell / 2\),其上下更粗(约 6,400 个节点)。

峰值载荷时狭缝尖端的损伤、断裂后的试样以及载荷-位移曲线

图 63 phase_field_fracture.py 的输出。左:峰值载荷时狭缝尖端(白线)附近的损伤。中:断裂后的试样,位移经过放大。右:顶边上的反力。

在 \(\delta \approx 3.9 \times 10^{-3}\) mm(\(F \approx 0.55\) kN)之前,响应是线弹性的,此时狭缝尖端才首次出现损伤:AT1 有弹性阶段。随后尖端的过程区不断长大,而载荷继续上升,在 \(\delta = 6.2 \times 10^{-3}\) mm 时达到 0.87 kN 的峰值。在下一个载荷步中,裂纹笔直地贯穿韧带,反力降为零。在位移控制下这种扩展是不稳定的,因此在单个载荷步内完成。

弹性阶段每步只需一到两次交错迭代,过程区增长的步最多约三十次。裂纹扩展的那一步需要 138 次,整个计算共约 320 次。使用 --model at2 时,损伤从第一步就开始增长,响应在峰值之前就已软化(\(\delta = 5.9 \times 10^{-3}\) mm 时为 0.73 kN),但裂纹同样在一个载荷步内贯穿。

验证

tests/assemble/test_phase_field_fracture_example.py 检查以下内容:

  • Hessian:将装配得到的 Hessian 与 autograd 残差的中心差分进行比较。

  • 均匀解:AT1 下单轴拉伸的条带在临界应变 \(\varepsilon_c = \sqrt{3 G_c (1 - \nu^2) / (8 E \ell)}\) 以下保持无损伤。超过临界应变后,损伤为 \(\alpha = 1 - (\varepsilon_c / \varepsilon)^2\),反力为 \(g(\alpha)\, E \varepsilon / (1 - \nu^2)\) 乘以宽度。

  • 不可逆性:损伤从不减小,卸载步之后也是如此。

  • 给定损伤:通过 alpha_fixed 保持在 \(\alpha = 1\) 的节点严格保持该值。

  • 粗网格 SENT 算例:裂纹从狭缝尖端出发,沿 \(y = 0.5\) 笔直扩展到右边缘。它耗散的能量为 \(G_c\) 乘以裂纹长度,相差一个与网格有关的有效韧性系数(见下文)。

运行它

cd examples/solid/phase_field_fracture
python phase_field_fracture.py                       # AT1, ell = 0.015 mm
python phase_field_fracture.py --model at2
python phase_field_fracture.py --ell 0.04 --h 0.02   # coarse and quick

默认算例在一个 CPU 节点上约需一分钟(AT2 约一分半),粗网格算例只需几秒。--device cuda 在 GPU 上装配,此时牛顿方程组在 torch-sla 支持 cuDSS 时用 cuDSS 求解,否则在主机上用 SciPy 求解。

说明

  • 分辨率。 \(h\) 必须能分辨 \(\ell\)(这里 \(h = \ell/2\))。此时耗散能会比 \(G_c\) 乘以裂纹长度多出一个与网格有关的系数,因为完全断裂的带至少有一个单元宽。这里 0.5 mm 的裂纹耗散 \(0.61\, G_c\),系数为 1.22。

  • 长度尺度是材料参数。 对 AT1,\(\ell\) 还决定了强度 \(\sigma_c = \sqrt{3 G_c E' / (8 \ell)}\),平面应变下 \(E' = E / (1 - \nu^2)\) [Tanne2018]。改变 \(\ell\) 不仅改变裂纹的光滑程度,也改变裂纹何时萌生。

  • 求解器。 交替最小化稳健,但在裂纹扩展时收敛较慢。对损伤更新做超松弛 [Farrell2017] 有帮助:这里 --relaxation 1.6 得到相同的结果,迭代次数少 15–20 %;当裂纹在许多载荷步中逐步扩展时节省更多。复合牛顿法和整体(monolithic)牛顿法所需的迭代还可以更少,它们同样用 vi_solve 施加界约束。

下一步

[Francfort1998]

G. A. Francfort and J.-J. Marigo, Revisiting brittle fracture as an energy minimization problem, Journal of the Mechanics and Physics of Solids, 46(8):1319–1342, 1998.

[Bourdin2000]

B. Bourdin, G. A. Francfort and J.-J. Marigo, Numerical experiments in revisited brittle fracture, Journal of the Mechanics and Physics of Solids, 48(4):797–826, 2000.

[Miehe2010]

C. Miehe, M. Hofacker and F. Welschinger, A phase field model for rate-independent crack propagation: Robust algorithmic implementation based on operator splits, Computer Methods in Applied Mechanics and Engineering, 199(45–48):2765–2778, 2010.

[Pham2011]

K. Pham, H. Amor, J.-J. Marigo and C. Maurini, Gradient damage models and their use to approximate brittle fracture, International Journal of Damage Mechanics, 20(4):618–652, 2011.

[Amor2009]

H. Amor, J.-J. Marigo and C. Maurini, Regularized formulation of the variational brittle fracture with unilateral contact: Numerical experiments, Journal of the Mechanics and Physics of Solids, 57(8):1209–1229, 2009.

[Benson2006]

S. J. Benson and T. S. Munson, Flexible complementarity solvers for large-scale applications, Optimization Methods and Software, 21(1):155–168, 2006.

[Tanne2018]

E. Tanné, T. Li, B. Bourdin, J.-J. Marigo and C. Maurini, Crack nucleation in variational phase-field models of brittle fracture, Journal of the Mechanics and Physics of Solids, 110:80–99, 2018.

[Farrell2017]

P. E. Farrell and C. Maurini, Linear and nonlinear solvers for variational phase-field models of brittle fracture, International Journal for Numerical Methods in Engineering, 109(5):648–667, 2017.