显式求解器负体积排查:Jacobian判据与TSSFAC调参

发布时间:2026/11/3 19:50:45
显式求解器负体积排查:Jacobian判据与TSSFAC调参

简介:在 LS-DYNA 非线性动力学仿真中,负体积报错是结构碰撞、冲击与成形分析里颇为常见的计算中断原因。这份文档面向已具备一定显式动力学基础、正被此类报错卡住的工程师与研究生,内容按「成因—对策」展开:先归纳材料参数与状态方程定义错误、人工时间步长偏大、模型约束不足、网格质量不佳等诱因,再给出调小 CONTROL_TIMESTEP 第二参数(0.9 改为 0.7)、体积修正、补足边界约束与优化网格等处置办法,并特别提示「先出现负体积、修改材料后又出现速度无穷大」这类连锁问题的排查顺序,便于对照实际报错逐项定位。压缩包共含 1 个 docx 文件,约 32KB,单文件结构紧凑,方便随时打开查阅或打印留档。目前已有 1221 人学习下载,说明该故障在显式动力学建模中较为普遍,其中的排查思路对同类问题具有借鉴意义。

1. 负体积:一次单元翻转为什么会让显式求解器直接停摆

负体积这个报错,见过的人都记得它的出场方式:算到某个时刻,终端刷出一行Negative volume in solid element #xxxxx,计算戛然而止,没有残局可收拾。多数人的第一反应是网格画得不好,回头改雅可比、局部加密,重算,还是崩在同一个位置。因为负体积多数时候不是几何质量问题,而是位移场在某一步之内把单元整个“翻”了过去,是个彻头彻尾的数值动力学事件。而显式求解器对这件事的容忍度是零:它不做收敛判断,只做稳定性判断,单元体积一旦跨过零,后面的每一步都在放大错误,而不是修正错误。搞懂这条链路,才知道把*CONTROL_TIMESTEP第二个参数从 0.9 调到 0.7 到底改了什么,也才知道什么时候该动它、什么时候动它没用。

2. Jacobian 判据:求解器是怎么“看见”负体积的

负体积不是一个物理概念,是一个几何量在数值上的符号翻转。要理解它,得先看求解器每个时间步里到底算了一遍什么,以及那个符号是从哪一行代码里冒出来的。

2.1 单元体积从节点坐标算起:一个行列式决定生死

对实体单元而言,体积是形函数雅可比矩阵在参考域上的积分:V = ∫ det(J) dξ。J 由节点坐标对参考坐标求偏导组装而成,det(J) 恒正意味着映射是保向的,节点顺序正确、单元没有自相交;det(J) 跨过零变负,映射就把参考域“翻面”了,单元在自己体内打了一个结。四面体是最直观的例子,四个点构成的有向体积就是一个标量三重积,符号直接对应节点绕向。

import numpy as np def tet_signed_volume(p0, p1, p2, p3): """返回四节点的有向体积。 正值:节点顺序正确,单元朝外; 负值:单元已翻转,求解器会报 negative volume。 """ a = np.asarray(p1) - np.asarray(p0) b = np.asarray(p2) - np.asarray(p0) c = np.asarray(p3) - np.asarray(p0) # 标量三重积除以 6 即为四面体有向体积 return float(np.dot(a, np.cross(b, c)) / 6.0) # 一个正常单元与一个被压翻的单元对照 good = tet_signed_volume([0,0,0], [1,0,0], [0,1,0], [0,0,1]) bad = tet_signed_volume([0,0,0], [0,1,0], [1,0,0], [0,0,1]) print(good, bad) # 0.1666... -0.1666...

上面两个四面体用的是同一组点,只是p1与p2调换了顺序,体积从 +1/6 变成 -1/6。这说明符号翻转有两种来源:一种是建模阶段节点顺序就写反了,另一种是求解过程中节点被推到了对面去。前者第一次计算就会崩,后者往往跑了几百步才崩,两者的排查路径完全不同,混在一起看就容易白忙。六面体单元没有这么简洁的闭式,求解器是在每个积分点上分别算 det(J),取最小值判断,所以六面体出现负体积时,报出的单元号可能只是“最严重的那一个”,同一片区域往往已经有一批单元临界了。

提示:报错只给一个单元号,是因为求解器在第一次检测到 det(J) ≤ 0 时就终止了;那个单元是当时最坏的,不是唯一有问题的。把该单元号前后两三层单元一起圈出来看,命中率会高得多。

2.2 中心差分的稳定性前提:为什么显式算法对翻转零容忍

显式求解的核心是三行更新:由节点内力与外力算加速度a = M⁻¹(F_ext − F_int),由加速度推半步速度,再由速度推位移。整个过程没有矩阵求解、没有迭代,代价极低,但代价是稳定性完全依赖时间步小于临界值,且内力必须是位移的光滑函数。

内力那一项F_int = ∫ Bᵀσ dV里的 B 矩阵含有 J 的逆。J 接近零时,J⁻¹ 迅速放大;J 变号之后,B 不仅幅值失控,方向也反了——本该抵抗变形的内力变成了助长变形的推力。此时无论时间步多小,下一步的位移都会更大,形成正反馈。这就是显式算法对负体积零容忍的根源:它没有“回头修正”的机制,只有“继续往前推”的机制,错误一旦进入状态量,就永久留在了解的历史里。

对比隐式算法会更清楚:隐式每步要解一个非线性方程组,若某步出现单元翻转,残差会急剧变大,牛顿迭代不收敛,求解器可以直接拒绝接受这一步并缩小增量。隐式有“后悔药”,显式没有。所以负体积在显式里是终止条件,在隐式里只是一个难收敛的增量。

2.3 负体积之后的速度爆炸:能量去了哪里

项目正文里那句“先出现负体积,修改材料后出现速度无穷大问题”,描述的是同一条链路上的两个阶段。单元翻转之后,det(J)变负,单元体积 V 变负,特征长度L_e也跟着变成一个没有物理意义的量(常常是负值或 NaN)。而时间步的计算式是Δt = L_e / c,c 是材料声速。于是下一步的 Δt 要么为负、要么为 NaN,求解器要么直接终止,要么继续用上一步的时间步硬推。

如果此时恰好因为改了材料参数,让单元的刚度或质量变得更“极端”,情况会更糟:质量很小的节点上作用着一个方向反了的内力,加速度的量级可以轻松达到 10 的十几次方,速度在一步之内就冲到无法解释的数量级,终端上表现为 velocity 无穷大、能量异常、总能量暴涨若干数量级。很多人以为是材料参数填错了才导致速度爆炸,其实顺序是反的——是单元先翻,速度才炸的。改材料只是把触发点提前或推后了几十步。

定位这类问题,看能量曲线比看速度峰值更有用。沙漏能、内能、动能三者随时间的变化曲线里,如果内能在某一步出现一个垂直台阶,那一步就是翻转发生的时刻,把该时刻的 d3plot 打开,找到体积为负或接近零的单元,比从头猜参数快得多。

3. 本构与状态方程:参数错在哪一步会把单元“压穿”

材料参数不会直接产生负体积,但它们决定了单元在同样的应变下产生多大的应力、也决定了时间步有多小。参数错一个数量级,等于把模型的稳定性裕度整个抹掉。

3.1 密度与声速:时间步长的隐形源头

显式时间步的表达式是Δt = L_e · sqrt(ρ / (λ + 2μ)),其中 λ、μ 是拉梅常数,由弹性模量 E 和泊松比 ν 换算:λ = Eν / ((1+ν)(1−2ν)),μ = E / (2(1+ν))。三个参数里,密度最小、也最容易被忽略,但它直接决定声速,声速又直接决定时间步。密度填错一个数量级,时间步就差三倍多;模量填错一个数量级,时间步差三倍多。两者叠加,时间步可能比正确值小两个数量级——此时计算慢得离谱但不会崩;也可能比正确值大一个数量级——此时直接冲过稳定极限,表现为算几步就翻单元。

最常见的翻车场景是单位制不统一。钢的弹性模量写成 210000(MPa·mm 制),密度却填了 7850(这是 kg/m³ 的数),量级差了三倍多,声速被算成不合理的值,时间步就跟着错。反过来,模量按 GPa 填了 210,密度按 t/mm³ 填了 7.85e-9,也是一样的结果。判断方法很直接:把声速算出来,钢材大约 5000 m/s 量级,换算到你用的单位制下看数量级对不对;不对就是单位制混了,别急着调 TSSFAC。

3.2 状态方程和体积模量的耦合陷阱

对泡沫、橡胶、混凝土、炸药这类材料,本构模型之外还要挂状态方程描述静水压力与体积应变的关系。状态方程是一个p = f(ρ, e)的映射,它的斜率就是体积模量。如果状态方程给的斜率在某个密度区间变成负值(曲线写得不对、参数拟合时外插过头、或者压力截断设成了允许拉伸),材料就会表现出“越压越软”,体积应变持续增大而压力不增,单元被越压越扁,直到翻转。

这里有个隐蔽点:很多状态方程只在压缩区标定,拉伸区靠一条截断线兜底。如果模型在冲击过程中局部出现拉伸(反射波、层裂、接触弹开),单元进入未标定的拉伸区,压力变成不合物理的负值,体积模量符号翻转,负体积就在那里发生。所以看到一个位置反复出负体积、而该位置又不是撞击面时,优先怀疑材料在拉伸侧的行为,而不是网格。

同理,塑性模型里的失效应变、粘弹性模型里的松弛时间,一旦设得让材料在应变硬化段之后迅速软化,也会走同一条路:应力下降、变形继续、单元被压穿。参数本身没错,错在“参数和这个载荷工况不匹配”。

3.3 一份可复现的材料参数自检脚本

与其在求解器里改一轮跑一轮,不如先在脚本里把量级算清楚。下面这段代码把声速、临界时间步、以及给定 TSSFAC 下的实际时间步一次算出来,几个常用单位制都能对照。

import math def check_material(E, nu, rho, L_e, tssfac, unit_note=""): """ E : 弹性模量,单位与 rho、L_e 保持一致 nu : 泊松比 rho : 密度 L_e : 单元特征长度(最小单元的最短边或体积/面积估算) tssfac : *CONTROL_TIMESTEP 第二个参数 """ lam = E * nu / ((1 + nu) * (1 - 2 * nu)) # 拉梅第一常数 mu = E / (2 * (1 + nu)) # 剪切模量 c = math.sqrt((lam + 2 * mu) / rho) # 膨胀波声速 dt_crit = L_e / c # 临界时间步 dt_used = dt_crit * tssfac # 实际使用的时间步 print(f"[{unit_note}] 声速={c:.4g}, 临界步长={dt_crit:.4g}, " f"TSSFAC={tssfac} 时实际步长={dt_used:.4g}") return c, dt_crit, dt_used # 单位制:mm - ms - kg(对应力单位 kPa 量级需自行换算,这里只做量级对照) check_material(E=210e3, nu=0.3, rho=7.85e-6, L_e=2.0, tssfac=0.9, unit_note="mm-ms-kg") check_material(E=210e3, nu=0.3, rho=7.85e-6, L_e=2.0, tssfac=0.7, unit_note="mm-ms-kg")

代码里的关键不是公式本身,而是“先算再改”的顺序。把rho换成你实际输入的值,如果算出的声速和你预期差了一个数量级以上,问题就在单位制或参数录入,不在时间步设置;如果声速正常但临界步长小得离谱,问题在L_e,也就是网格里存在极小单元,该去查网格而不是查材料。这两条分支一分,排查范围立刻收窄一半。

4. TSSFAC 0.9 改 0.7:时间步长安全系数动了什么

把*CONTROL_TIMESTEP的第二个参数从 0.9 调到 0.7,是流传最广的一招。它确实经常有效,但有效的理由和大多数人想的不一样,代价也常被低估。

4.1 临界时间步的推导:单元特征长度除以声速

显式中心差分的稳定条件是Δt ≤ 2/ω_max,ω_max 是离散系统最高阶模态的圆频率。对实体单元做量纲分析可以把它化成更实用的形式:Δt_crit = L_e / c,其中L_e是单元特征长度(六面体常取体积除以最大面面积,四面体取最短高),c 是膨胀波声速。求解器内部对每个单元算一遍,取全局最小值作为当前步的临界步长,再乘一个安全系数 TSSFAC 得到实际使用的时间步。

安全系数存在的意义,是兜住推导过程中的近似:2/ω_max的解析上界在非均匀网格、畸变单元、材料非线性下并不严格成立,取 0.9 是给这些误差留余量。这个数不是物理常数,是工程经验的产物。老版本的一些求解器默认取 0.9,也有取 0.67 的,取值的差异本身就说明它是一个可以调的量。

*CONTROL_TIMESTEP卡片大致长这样,第二个字段就是 TSSFAC:

$# DTINIT TSSFAC ISDO TSLIMT DT2MS LCTM ERODE MS1ST 0.0 0.7 0.0 0.0 0.0 0.0 0.0 0.0

DTINIT为 0 表示由求解器自动计算初始步长;TSLIMT是最小步长门限,步长掉到它以下就终止,可以拿它当“崩之前先停住”的保险;DT2MS是质量缩放的目标步长,后面会专门说它的副作用。

4.2 为什么缩小安全系数能压住负体积

缩小 TSSFAC 并不会让单元“更结实”,它做的是另一件事:让每一步的位移增量变小。单元从“接近翻转”到“真正翻转”之间,通常还隔着若干步。步长越小,这段窗口里被切出的步数越多,单元在翻转前的畸变状态被记录得越细,同时内力的反馈也更及时——本该在更早一步就被“顶回去”的变形,不会因为一步跨得太大而直接越过去。

换个说法:负体积经常是“一步跨过头”造成的。单元在这一步开始时 det(J) 还是 +0.02,一步之后变成 −0.05,中间那个物理上应该被弹性抗力拦住的瞬间,被离散化直接跳过了。把步长从 0.9 降到 0.7,位移增量减少 22%,很多临界单元就恰好被拦在了零的另一侧。

这也解释了为什么它对某些负体积无效:如果单元翻转是因为节点顺序写反、初始穿透、或者材料参数错到刚度已经失真,那么无论步长多小,翻转都必然发生,只是发生得更晚一点。这类情况调 TSSFAC 只会让你多等几个小时,然后看到同样的报错。判断方法是看报错时间:如果调小 TSSFAC 后崩溃时刻明显推后但最终仍崩,说明是渐进失稳,调步长有意义;如果崩溃时刻几乎没变,问题在模型本身。

4.3 代价核算:计算时间、质量缩放与结果可信度

安全系数直接乘以步数。以特征长度 2 mm、声速 5000 m/s 的钢件为例,临界步长约 4.0e-7 s,物理时长 1 ms 的工况在几种取值下的开销如下:

TSSFAC实际时间步1 ms 所需步数相对耗时
0.93.60e-7 s27781.00
0.83.20e-7 s31251.13
0.72.80e-7 s35711.29
0.62.40e-7 s41671.50

从 0.9 到 0.7 是 29% 的额外机时,这个代价多数项目能接受;再往下走到 0.6 就是 50%,而且收益递减——真正翻得厉害的单元,0.6 也救不回来。

另一条路是质量缩放,把DT2MS设成目标步长,让求解器给时间步过小的单元加质量。它能显著提升效率,但注入的附加质量会影响惯性效应,对冲击、碰撞这类以惯性为主导的问题,结果偏差可能超出可接受范围。常见的折中是DT2MS取负值,只对小于目标步长的单元加质量,把附加质量控制在局部、可控的范围内,并在计算后用*DATABASE_GLSTAT检查总质量增加百分比——超过 1%~2% 就该重新审视网格,而不是继续加质量。

注意:质量缩放和 TSSFAC 是两件事,不能互相替代。前者改变的是模型(加质量、改动力学),后者改变的是离散精度(不改模型)。先确认网格里没有极小单元,再谈调哪个。

5. 负体积排查:五条从现象倒推根因的踩坑记录

负体积的排查之所以让人头疼,是因为同一个报错背后可能是五类完全不同的原因。下面这几条按“现象 → 原因 → 解决”整理,都是实际调试里反复遇到的组合。

5.1 现象一:第一步就报负体积,一个时间步都没走完

现象很干脆:提交计算,还没来得及输出第一帧,直接报negative volume并终止。

原因基本只有一种——模型初始化时单元就已经是翻的。可能是网格导入时节点顺序被打乱,可能是建模工具导出时把退化单元写成了零厚度壳/体,也可能是两个零件初始穿透严重,在求解器里被强行推开。初始化检查(initial penetration check)阶段如果没报错,说明几何上没穿透,那就一定是拓扑或节点顺序问题。

解决办法分两步走:先关掉所有接触和载荷,只留约束,让模型空跑一步,看是否仍报错;如果仍报,用前处理器重新检查单元雅可比,把 det(J) 最小的前 100 个单元列出来逐一排查。这一步不要靠猜,靠工具给出的排序列表,通常就是那么几个单元。修完之后,把初始时间步打印出来对照上一节的脚本,确认量级合理再继续。

5.2 现象二:改完材料后不报负体积了,但速度无穷大

这是项目正文里描述的那条路径:先出现负体积,修改材料参数后负体积消失了,取而代之的是某个节点的速度跑到 10 的十几次方。

原因不是材料改错了,而是改材料把失稳的形式换了。原来的材料参数让单元刚度偏大、在翻转前就被终止;改小刚度后,单元能撑更久,但内力反馈变弱,某一步里位移跨得更大,速度一步就爆了。本质上都是同一个单元、同一片区域在失稳,只是终点的表现不同。

解决时不要盯着速度看,要看能量。打开*DATABASE_GLSTAT输出,检查总能量是否守恒、沙漏能占比是否超过 5%、内能曲线有没有垂直台阶。找到台阶对应的时刻,输出该时刻的 d3plot,定位速度异常节点所在的单元,看它是不是落在了接触面、约束边界或材料分界面上。80% 的这类问题出在这三个位置。

5.3 现象三:网格检查没问题,接触一开就翻

现象是:不带接触算得好好的,一加接触,十几个微秒后局部单元翻转。

原因通常是接触刚度或穿透处理不当。罚函数接触会在从节点穿透主面时施加一个与穿透深度成正比的力,如果罚因子SLSFAC取大、或者主从面网格尺寸严重不匹配,穿透一旦发生,反力会在一步内把从节点弹飞到对面去,那个位置的单元就被扯翻。

解决顺序是:先降低罚因子试算,看报错是否消失,以确认是接触刚度问题;确认后不要停在“调小罚因子”这一步,因为降低罚因子等于允许更多穿透,物理上是妥协。更该做的是把主从面的网格尺寸差控制在两倍以内,把接触厚度参数按实际壳厚或单元尺寸设置,必要时改用自动面面接触中的 soft 选项处理薄壁与厚实体混合的场景。表面上看是多花了半天改接触设置,实际上省掉了后面结果不可信的重算。

5.4 现象四:加质量缩放后时间步正常了,结果却更差

现象很有迷惑性:加了DT2MS之后不再报负体积,计算也跑完了,但变形形态明显不对,回弹量偏大,或者撞击部位的能量吸收对不上。

原因是质量缩放改变了惯性。对一个极小单元加上几十倍的附加质量,它周围的单元在动力学上就被“拖住”了,波传到这里会反射、迟滞,局部响应完全失真。更麻烦的是这种失真不会报错,只会安静地给出一个看起来很正常的错误结果。

解决办法是先量化再接受。计算结束后检查总质量增加比例,超过 2% 就不要直接用;把加质量的单元数、最大附加质量倍数打印出来,如果集中在某几个单元,回头去修那几个单元的网格比加质量划算得多。真正需要质量缩放来救的,往往是“网格里存在一两个数量级偏小的单元”,而这类单元通常可以在前处理阶段通过局部重划或退化处理消掉。

5.5 现象五:删掉坏单元后算完了,但结果不能用

现象是用了单元删除(ERODE或失效准则)之后,计算顺利跑到底,可一看结果,本该传力的位置直接断开了,载荷传递路径断了,整体刚度明显偏低。

原因是单元删除是一种“止损”,不是“修正”。翻转的单元被删掉,等于在结构上挖了一个洞,而这个洞往往正好出现在最需要传力的高应力区。删得越多,结果越偏离物理。

解决办法是明确区分两种场景:如果单元删除是为了模拟真实的材料失效(比如冲剪破坏、层裂),那它是物理建模的一部分,需要靠失效应变标定来保证删除时机正确;如果是为了让计算“能跑完”,那它就是在掩盖问题,最终结果不可引用。判断标准很简单——删掉的单元是否落在预期的失效区域。不在,就不能接受。

提示:把ERODE打开后一定要输出失效单元的数量随时间变化。如果失效单元从一开始就零星出现、并迅速蔓延,说明删的是数值坏的单元而不是物理失效的单元,这时候该回到前面几节重新查,而不是继续往下算。

6. 修正与取舍:删单元、降步长、补约束,三条路各自要付什么账

负体积的三种主流处理方式,本质是三种不同的交易。

调小 TSSFAC 是拿机时换精度,不改模型,代价可控,适合“网格尚可、只是局部临界”的工况,从 0.9 到 0.7 大约多花三成时间。它的局限在于只能推后崩溃时刻,不能消除根因;如果调完只是晚崩了几十步,说明问题在别处。

加质量缩放是拿物理真实性换效率。它让时间步变大、步数变少,但注入的附加质量会污染惯性主导的响应。用它之前先问一句:这个模型的结果对局部惯性敏感吗?如果是冲击、侵彻、高速碰撞,答案通常是敏感,那就老老实实修网格。

单元删除是拿结构完整性换“能算完”。它在模拟真实失效时是必要的,在掩盖数值问题时是有害的。区别只在于删除位置是否符合物理预期,这一点靠经验判断不够,得靠能量曲线和失效区域云图交叉验证。

还有一条常被忽略的路:补约束。项目正文里提到“计算不收敛多是模型没约束好”,这话在显式里要换个说法——显式没有收敛判断,但未约束的自由度会让刚体位移一直累积,一旦和接触、和相邻零件发生干涉,就会在局部制造出远超物理的变形。显式模型虽然靠惯性就能算,但该固定的方向还是得固定,尤其是薄壁件、装配体里那些只靠接触“搭着”的零件。检查方法是看约束反力是否合理、看模型在无载荷时是否漂移。

我的习惯是每次模型跑挂之后,先别动参数,把时间步、能量曲线、报错单元号这三样东西摊在一起看:时间步是不是量级不对,能量有没有台阶,报错单元落在哪个区域。这三条能定位掉八成的问题,剩下的才轮到调 TSSFAC。有一年我在一个薄壁吸能结构上反复崩了两周,各种参数试遍,最后发现是一个 0.02 mm 的退化单元把全局时间步压到了 1e-9 量级,删掉那个单元、重新划分局部网格之后,0.9 的安全系数一次跑通。从那以后我每次拿到新网格,都强制先跑一遍最小单元尺寸统计和声速计算,量级对了再提交计算。希望帮到你。

本文还有配套的精品资源,点击获取