塑性(带各向同性硬化的 J2)¶
一个平面应变塑性问题:一根 \(1.0 \times 0.2\) m 的钢条被拉伸 10 % 然后卸载,表现出特征性的永久(塑性)变形——这是 悬臂梁 中的弹性求解器无法捕捉的。脚本 examples/solid/plasticity_strip/plasticity_strip.py 采用带线性各向同性硬化的 J2 流动理论,以 变分本构更新 的方式积分——本构响应被编码为一个算法势,位移更新是一次 L-BFGS 过程,历史变量在每个收敛步推进一次。
本构模型是维度无关的,因此同一个 J2Plasticity 在三维中也能原封不动地运行——参见本页末尾的 Going to 3D。
问题¶
小应变弹塑性。柯西应力是应变的 弹性 部分所对应的弹性响应:
其屈服面为
演化方程 \(\dot{\boldsymbol{\varepsilon}}^p = \dot\gamma\, \mathbf{n}\),\(\dot\alpha = \sqrt{\tfrac{2}{3}}\, \dot\gamma\)。材料:\(E = 200\) GPa,\(\nu = 0.3\),\(\sigma_y = 250\) MPa,\(H = 1\) GPa。
边界条件:
\(x = 0\):滚动支座(\(u_x = 0\)),
角点 \((0, 0)\):完全铰接(\(u_x = u_y = 0\)),
\(x = 1\):规定的 \(u_x\) 从 0 逐步增加到 0.10 m(10 步),然后再降回 0(再 10 步)。
变分本构更新¶
脚本没有为每个求积点实现显式的“径向返回”映射,而是定义了一个 算法势 \(\Pi^\text{alg}(\boldsymbol{\varepsilon}, \boldsymbol{\varepsilon}^p_n, \alpha_n)\),它在塑性变量上的驻值条件可还原出标准的返回映射方程。于是每一步的位移更新就是对积分后的势的一次最小化:
由 autograd 提供梯度(残差),并通过 L-BFGS 隐式的海森近似提供一个等效切线刚度。收敛后,历史变量 \((\boldsymbol{\varepsilon}^p, \alpha)\) 被一次性推进到新的收敛值。
该势在 J2Plasticity 中实现,它是 tensormesh.assemble 中自带的一个内置类。从用户角度看,它和其他任何自定义装配器没有区别:
from tensormesh.assemble import J2Plasticity
from tensormesh.material import IsotropicMaterial
steel = IsotropicMaterial("Steel_Hardening",
E=200e9, nu=0.3, rho=7850,
sigma_y=250e6, H=1e9)
model = J2Plasticity.from_mesh(mesh, material=steel)
u = torch.zeros_like(mesh.points, requires_grad=True)
optimizer = optim.LBFGS([u], lr=1.0, max_iter=50,
history_size=50,
line_search_fn="strong_wolfe")
for step in range(1, n_total_steps + 1):
target_disp = piecewise_load_schedule(step)
def closure():
optimizer.zero_grad()
u_active = apply_bcs(u, target_disp)
element_data = {
"eps_p_n": {et: h["eps_p"] for et, h in model.history.items()},
"alpha_n": {et: h["alpha"] for et, h in model.history.items()},
}
E = model.energy(point_data={"displacement": u_active},
element_data=element_data)
E.backward()
return E
optimizer.step(closure)
model.advance_history(...) # commit eps_p, alpha
历史变量¶
塑性应变张量 \(\boldsymbol{\varepsilon}^p\) 和等效塑性应变 \(\alpha\) 按 逐单元/逐求积点 存储,通过 model.history 访问。它们以 element_data(逐求积点、逐单元的张量)的形式传入闭包,TensorMesh 会像分发 point_data 那样,将其作为关键字参数分发给底层的 forward 方法(参见 形式)。
在每个载荷步的 L-BFGS 收敛后,脚本调用 model.advance_history(u_active),它根据当前位移计算收敛后的 \(\boldsymbol{\varepsilon}^p, \alpha\),并覆盖已存储的历史。
输出¶
脚本会渲染一段多面板动画,输出为 plasticity_strip.mp4:
变形后的网格,按等效塑性应变 \(\alpha\) 着色,
受载边缘处的力-位移曲线,贯穿整个加载/卸载循环。
力-位移曲线是塑性的典型标志:线弹性加载直至屈服,超过 \(\sigma_y\) 后进入硬化段,随后是线弹性卸载段,在零载荷处回到一个非零的残余位移。
``plasticity_strip.py`` 的输出:钢条在规定的伸长下变形(左面板,按等效塑性应变 \(\alpha\) 着色),同时受载边缘处的力-位移曲线(右面板)描绘出加载/卸载循环——弹性加载至屈服、硬化段,然后弹性卸载回到零载荷处的永久残余位移。
扩展到三维¶
J2Plasticity 是维度无关的,因此同样的本构模型、变分更新模式和历史变量记账都能原封不动地搬到三维。脚本 examples/solid/plasticity_3d/plasticity_3d.py 在一个 \(0.5 \times 0.5 \times 0.5\) m 的钢制立方体上运行 J2 流动,在 \(x\) 方向上经 50 个加载步拉伸 40 %,再经 50 步卸载。与二维钢条相比,只有 驱动脚本 不同:
mesh = gen_cube(chara_length=0.04,
left=0, right=0.5, bottom=0, top=0.5,
front=0, back=0.5)
steel = IsotropicMaterial("Steel_Hardening",
E=200e9, nu=0.3, sigma_y=250e6, H=1e9)
model = J2Plasticity.from_mesh(mesh, material=steel)
# …same closure / advance_history loop as the 2D strip, 3D BCs…
真正改变的是规模,而不是模型:
三维网格。
gen_cube生成一个四面体立方体,自由度约为 5–10 k 量级(相比之下二维钢条约为 3 k);每次 L-BFGS 迭代都要在多得多的求积点上积分。更多载荷步。 50 步加载 + 50 步卸载 = 100 步,达到 40 % 的应变——足以让硬化段占据主导。在 40 % 名义应变下,小应变假设已被(字面意义上地)拉伸到极限:该示例是 J2 模型一个有用的回归测试,但不要在远离弹性区的情况下信任应力的绝对数值。
输出。
plasticity_3d_combined.mp4展示了按 冯·米塞斯应力 着色的变形立方体,以及受载面处的力-位移曲线。
三维运行的瓶颈在 L-BFGS——大部分实际耗时都花在每次迭代的能量计算和 autograd 反向传播上。把优化迁移到 GPU(mesh.to("cuda")、u.to("cuda"))能显著提速;当显存吃紧时,装配器的 batch_size 参数可对逐求积点的被积项分块处理(参见 批量化工作流)。
``plasticity_3d.py`` 的输出:钢制立方体被拉伸至 40 % 应变(左面板,按冯·米塞斯应力着色),以及在 50 步加载/50 步卸载循环中受载面处的力-位移曲线(右面板)——这是上面钢条动画的三维对应版本。
cd examples/solid/plasticity_3d
python plasticity_3d.py # writes plasticity_3d_combined.mp4
运行示例¶
cd examples/solid/plasticity_strip
python plasticity_strip.py # writes plasticity_strip.mp4
控制台输出会逐步报告力、位移和最大塑性应变。