先说结论:rblock 是 PFC 里的刚体块,自己不变形。所以两个块之间的力,全靠它们"重叠的那一小块区域"来传。PFC 在这块区域上只取一个点——重叠区的形心——在这一点上算出力 + 力矩。一旦胶结,这个点的位置就钉死在抹胶那一刻,像两块石头之间粘了一层胶。
下面把这条链路一层层拆开,每一步我都跑了小算例验证。
用 PFC 做过岩石、砌体、节理岩体的人,大概都碰过同一个尴尬:球堆出来的东西,太"软"了。
一颗一颗的球,接触是"点",球能自由滚动。你堆一摞球,稍微一推就散。真实岩体里的岩块不是球——它有棱有角,块与块之间是面贴着面,靠这个面摩擦、咬合、传力。
PFC 从 6.0 开始内置了 rigid block(刚体块,简称 rblock) 模块,专门解决这件事。它的定义很干脆:
所以问题立刻变成了:既然块自己不变形,那力只能从"块与块之间"传过去。这个"之间"到底是怎么算的?
这是全文最要紧的一张图。
图 1 rblock 接触的五步链路:两个块 → GJK/EPA 求重叠 → 取重叠区形心 → 接触本构 → 在这一点上算出力和力矩。
拆开说:
第 1 步,检测是否碰上。 两个凸多边形是否重叠,PFC 用的是图形学里的经典算法 GJK(Gilbert–Johnson–Keerthi),背后是"闵可夫斯基差"(Minkowski difference)那套东西:把 A 的所有顶点减掉 B 的所有顶点,得到的点集如果包住了原点,两块就重叠了。
第 2 步,算重叠区域。 光知道"重叠了"不够,还要知道重叠了多少、在哪。重叠浅的时候 GJK 直接给出"穿透向量";重叠深了,再上 EPA 算法(Expanding Polytope)把穿透向量精确求出来。
第 3 步,也是最关键的一步——把接触点放在哪。
这里有个漂亮的理论结果。PFC 文档直接引了 Feng 等人 2012 年的结论:
要让两个刚体块在只有一个接触的情况下保持静止,接触点的位置必须放在重叠区域的形心上。
道理其实很朴素。如果接触点不在形心,那么法向力作用在这个偏心的点上,就会对块体产生一个额外的力矩,块就自己转起来了。放在形心,力"正对着"重叠区的中心,才不会凭空生出一个转动力矩。
第 4、5 步,在这一点上挂上接触本构模型(linear、softbond、flatjoint……),算出力和力矩。
口说无凭。我做了个直接的验证:用两个正方形块,摆出六种不同的重叠姿态(面贴面、深压、斜 30°、菱形角顶、倾斜 15°、角对角),在 Python 里独立算一遍重叠多边形的形心,再和 PFC 报出来的接触位置对比。
图 2 六种重叠姿态下,PFC 的接触点(★)与独立计算的重叠区形心(○)的对比。上排 full 模式,下排 reduced 模式。左下角是位置误差。
结果非常干净:
| 1.6×10⁻² | ||
full 模式的误差是 10⁻¹⁷ 量级——这就是双精度浮点的机器精度,等于"完全一致"。接触点确实就在重叠区形心。
上表里有个例外值得单说:reduced 模式在斜边、倾斜的情况下,接触点会明显偏开(误差到了 10⁻² 量级,图 2 下排能肉眼看出 ★ 和 ○ 分家了)。
因为 PFC 给了两套求解策略:
怎么选:块数少、精度要求高 → 用默认的 full。块数上万、只关心整体响应 → 换 reduced,能省不少时间。切换命令:rblock contact-resolution reduced。
看到"只用一个接触点",第一反应多半是:一个点怎么撑得住?不会倒吗?
把块堆和球堆摆在一起看就清楚了。
图 3 球堆:接触点是"点",永远落在两球心的连线上。球可以绕接触点自由滚动,所以一推就散。
图 4 块堆:接触点是"面"(橙色带),接触点落在重叠区形心。块有棱角、转不动,接触还能传力矩,所以稳。
差别不在"一个点还是多个点",而在于:
同样是"面-面接触",离散元界其实有两种做法。
图 5 PFC 的做法:一对块之间只有一个接触点。
图 6 UDEC / 3DEC 的做法:沿接触面布一串子接触,逐个算。
PFC 选前者,理由写得很直白:省内存、省时间。文档原话说,一对接触对象之间维护大量子接触会带来"可观的内存和计算开销"。PFC 里还有一条更硬的规则:一个 rblock 和另一个 piece 之间,永远只存在一个接触(这跟 clump 很不一样——clump 的每个 pebble 都算一个 piece,一个 clump 和别的对象之间可以有好几个接触)。
代价是:既然只给一个点,那接触检测和求解就必须做得非常稳健,位置必须算准。这就是前面 GJK/EPA 那一整套算法存在的原因。
这是 rblock 和"球"最本质的差别。我搭了个悬臂:左边一块固定,右边一块跟它面贴面,在右块质心施加向下的力 F。
图 7 (a) 装置;(b) 右块下沉量;© 接触传递的力矩。
对照两组:
同一个几何、同一个力,胶结与否差了三个数量级。这说明:接触点本身能不能传力矩,取决于接触模型,而胶结给了它这个能力。
这是 rblock 最有意思、也最容易踩坑的地方。
先看一个最简单的实验:两块胶结好,然后直接拉开。
图 8 两块胶结后直接拉伸:线性弹性上升 → 到达强度 → 脆性断裂,力瞬间归零。
曲线本身很标准:弹性段笔直,到点就断。但真正特别的是"胶"的位置是怎么处理的。
普通(未胶结)接触,接触点是每一时刻重新算的——块动了,重叠区变了,接触点跟着变。
胶结之后不是这样。 文档原话:
胶结激活时,胶结那一刻的接触位置被当作初始位置,此后只根据接触点处的相对速度增量更新。可以这样理解:胶结材料在概念上粘在胶结那一刻的块体表面上。
换句话说,胶结位置被"钉"住了。你把两块往两边拉,那层"胶"就跟着被拉长,力从胶里传过去;拉到超过强度,胶断了,接触退回普通状态。
这里藏着一个非常实际的坑:
必须让块体先重叠,才能胶结。因为胶结要求接触存在,而 rblock 的接触又要求重叠(精确相切经常检测不到)。所以标准做法是先 rblock scale relative 1.01 或 rblock dilate 制造一点重叠,再胶结。而且此时必须把 lin_mode 设为 1(法向力增量累加),否则初始的那点重叠会直接算出一大坨初始接触力,模型一上来就炸。
块数一多,接触数会爆炸。原因很朴素:两个多边形只要有角碰角、边碰边,都算接触,但这些接触对整体力学几乎没有贡献。
PFC 给的对策是圆化(erode / dilate):把块的核心形状缩小一点,再在外面裹一层圆角。这样一来,顶点-顶点、边-边那种"擦一下"的接触就自动消失或变得不活跃了。
我建了个 1098 块的点阵,扫了一遍圆化半径:
图 9 (a) 接触数随圆化半径的变化;(b) 无效接触占比。
不圆化:6263 个接触。轻轻圆化一下:1603 个。 三分之二的接触是白算的。
官方 BBM 算例里的数字更极端:7700 个块,面-面接触只有约 13000 个,而顶点-顶点和边-边接触超过 225000 个。文档说得也直接——这些接触"对结果贡献可以忽略,但会把计算时间拉爆"。
配套的还有 contact inhibit:把未胶结的、间隙为正的接触直接挂起,跳过它们的力-位移计算,进一步提速。
把上面这些零件拼起来,就是 BBM(Bonded Block Model,胶结块体模型) 的标准流程:
拿它做一个花岗岩式的巴西劈裂(Voronoi 晶粒 + 晶界胶结),效果是这样:
图 10 加载前:122 个 Voronoi 晶粒,4 种矿物。
图 11 加载后:晶界键断裂(红点为断键,共 92 个)。
图 12 侧向位移场(放大 15 倍):左右两半向相反方向分离,是典型的巴西劈裂形态。
整个过程里,每一块晶粒都是刚体、自己不变形;所有变形和破坏,都发生在晶粒之间的那层"胶"上。这就是 BBM 的世界观。
跑下来最容易栽的几个地方,一并列在这:
Unable to determine a valid timestep。加载时只 fix 加载端。contact cmat default 之后必须给 method deformability emod ... kratio ...,否则接触刚度为零,同样是 timestep 报错。model clean all 之后 contact.num 会是 0,得 model cycle 1 才建出来。rblock property 给的是块表面的属性,用来填充接触模型属性,和 rblock attribute(位置、速度这类)是两回事。一句话回顾:rblock 之间靠重叠区传力,PFC 在重叠区形心上取唯一一个接触点,在那里算出力和力矩;胶结把这个点钉死成一层"胶";圆化则把大量没用的角-角接触提前剔掉。理解了这三件事,rblock 的行为基本就能预判了。
VibePFC 社区 · 一起把 AI 玩进 PFC
我成立了一个 VibePFC 社群,专门聊 AI 怎么落地到 PFC / 数值模拟——让大模型读懂模型、自己改脚本、自动排查"看不懂"的问题。收一个 50 元的门槛费。
想进群的同学,加 QQ 763388012,备注"vibePFC"即可。
rblock contact-resolution 命令