颗粒离散元软件PFC裂隙文件详解及使用注意事项
1. 文件的总体作用
这个 fracture.p3fis 文件主要用于颗粒离散元 BPM 模型中的裂隙追踪。它的核心思想是:当颗粒之间的粘结发生破坏时,程序自动捕捉这个断裂事件,并在对应的位置生成一条裂隙。

在 BPM,也就是 Bonded Particle Model 中,颗粒之间通过粘结连接在一起,用来模拟岩石、混凝土、煤体等材料的连续性。当外部加载逐渐增大时,颗粒间的粘结会因为拉伸或剪切作用发生断裂。每一次粘结断裂,都可以看作材料内部产生了一条微裂纹。这个文件的作用就是把这些微裂纹记录下来,并且根据破坏模式区分为拉伸裂纹和剪切裂纹。
因此,这个文件并不是用来控制颗粒如何受力、如何破坏的,而是一个后处理和实时追踪脚本。它负责把模型计算过程中发生的粘结断裂事件转化为可视化、可统计的裂隙对象。通过它,可以观察裂纹的产生位置、扩展过程、裂纹数量变化,以及碎块的形成情况。
整个文件主要由两个函数组成。一个是 track_init,用于初始化裂隙追踪系统;另一个是 add_crack,用于在粘结断裂时生成裂隙。程序运行时,一般先调用 track_init,然后在模型计算过程中,只要发生 bond_break 事件,就会自动调用 add_crack 来生成裂隙。
2. 初始化函数的作用
track_init 是整个裂隙追踪系统的初始化函数。它通常需要在模型正式加载或计算之前调用。这个函数主要做四件事:清除旧裂隙、清除旧碎块信息、注册 fragment 计算对象,以及绑定粘结断裂回调函数。
首先,它会删除模型中已经存在的 fracture 对象。如果之前已经进行过一次计算,模型里可能残留旧的裂隙数据。如果不清除这些旧裂隙,新的计算结果会和旧结果混在一起,导致裂纹数量、裂纹分布和后处理结果不准确。因此,在重新开始裂隙追踪之前,必须先清空已有 fracture。
其次,它会清除已有 fragment 信息。fragment 是 PFC 中用于识别碎块的功能。当颗粒间的连接关系发生变化时,fragment 可以根据颗粒之间是否仍然相互连接,把颗粒系统划分成不同的碎块。对于研究岩样破裂、块体剥落、颗粒团聚体解体等问题,这个功能非常重要。
然后,程序会注册 ball-ball 类型的 fragment 识别。也就是说,程序会根据颗粒与颗粒之间的连接关系来判断哪些颗粒属于同一个碎块。对于 BPM 模型来说,颗粒之间的粘结断裂会改变整体连通性,因此用 ball-ball 作为 fragment 追踪对象是合理的。
最后,初始化函数会把 add_crack 这个函数绑定到 bond_break 事件上。也就是说,从这一刻开始,只要模型中发生粘结断裂,程序就会自动执行 add_crack 函数。这里还先移除了已有的 add_crack 回调,再重新添加,是为了防止重复绑定。如果多次运行初始化函数而不移除旧回调,同一次断裂事件可能会触发多次裂隙生成,造成裂纹重复记录。
初始化函数中还定义了一些全局变量,例如裂纹累计数量、裂纹总数、开始追踪的时间、上一次 fragment 计算时间,以及模型计算域的边界范围。这些变量会在后续裂隙生成和碎块更新过程中使用。
3. 裂隙生成的基本过程
add_crack 是这个文件中最核心的函数。它在颗粒间粘结断裂时被自动调用。每次调用时,程序会从断裂事件中读取两个关键信息:一个是发生断裂的接触,另一个是断裂模式。
发生断裂的接触包含了断裂位置、接触方向,以及接触两端的颗粒。程序首先获取接触点的位置,并把这个位置作为新裂隙的中心位置。然后获取接触法向方向。对于二维 BPM 模型来说,粘结断裂产生的裂纹通常被表示为一条线段,这条线段的方向一般取为垂直于接触法向的方向。也就是说,如果两个颗粒中心连线方向代表接触法向,那么裂隙方向就是与这条中心连线垂直的方向。
接下来,程序会获取接触两端的两个颗粒,并读取它们的半径。这里采用两个颗粒中较小的半径作为裂隙的半长。因此,生成的裂隙总长度大约是较小颗粒半径的两倍。这种做法比较简单,也比较常见,因为它可以使裂隙尺寸与局部颗粒尺度相匹配。不过需要注意,如果模型中的平行粘结半径并不等于颗粒半径,那么这里生成的裂隙长度只是一个近似值。
确定裂隙中心、方向和长度之后,程序会计算裂隙的两个端点。也就是说,它以接触点为中心,沿裂隙方向向两侧各延伸一个半长,形成一条二维线段裂隙。随后,这条线段会被创建为一个 fracture 对象,并加入对应的 DFN 裂隙网络中。
这个过程可以概括为:一次粘结断裂事件,对应生成一条微裂隙;裂隙中心位于断裂接触点;裂隙方向垂直于接触法向;裂隙长度与局部颗粒半径有关。

4. 拉伸裂隙和剪切裂隙的区分
这个文件不仅记录裂隙,还会根据粘结破坏模式对裂隙进行分类。断裂事件传入的 mode 变量用于表示破坏类型。通常情况下,mode 等于 1 表示拉伸破坏,mode 等于 2 表示剪切破坏。

如果是拉伸破坏,程序会把该裂隙放入名为 crack_tension 的 DFN 中;如果是剪切破坏,则会放入名为 crack_shear 的 DFN 中。DFN 可以理解为一个裂隙集合,或者一个裂隙网络。通过把不同破坏类型的裂隙放入不同 DFN,后续就可以分别统计和显示拉伸裂纹与剪切裂纹。


这种分类对于分析岩石类材料的破坏机制非常有用。例如,在单轴压缩试验中,初期可能以拉伸裂纹为主,随着加载继续进行,剪切裂纹逐渐增多,并最终形成贯通破裂带。通过这个文件生成的 crack_tension 和 crack_shear,可以直观看到两类裂纹在空间上的分布差异,也可以统计两类裂纹数量随时间或应变的演化关系。
程序在创建裂隙后,还会给每条裂隙记录一个 age 属性。这个属性的值是当前的机械时间,也就是这条裂隙产生的时间。这样在后处理中,不仅可以知道裂隙在哪里,还可以知道它是什么时候产生的。通过裂隙年龄,可以进一步分析裂纹扩展顺序,例如哪些区域先破坏、哪些裂纹后期才产生、主裂纹是如何逐渐贯通的。
此外,程序还把产生这条裂隙的两个颗粒保存在 fracture 的额外变量中。这样做的目的是为了后续更新裂隙位置。因为颗粒在计算过程中会继续运动,如果裂隙仍然要和原来的颗粒对保持关联,就需要记住这条裂隙对应的是哪两个颗粒。
5. fragment 碎块计算与裂隙位置更新
除了生成裂隙,这个文件还涉及 fragment 碎块计算。fragment 的作用是根据颗粒之间的连接状态,判断模型中形成了哪些独立碎块。在 BPM 模型中,颗粒之间的粘结不断断裂,原本完整的试样会逐渐被分割成不同的块体。fragment 计算就是用来识别这些块体的。

不过,fragment 计算通常比较耗时。如果每发生一条裂隙就立刻重新计算一次 fragment,会严重影响计算效率。因此,这个文件采用了累计触发的方式。程序用 crack_accum 记录自上一次 fragment 计算以来新增的裂隙数量。当新增裂隙数量超过 50 条时,才执行一次 fragment compute。
这里需要注意,代码中使用的是“超过 50 条”才触发,所以严格来说是在第 51 条新增裂隙出现时执行 fragment 更新。如果希望正好每 50 条裂隙更新一次,可以把判断条件改成“大于等于 50”。不过当前写法并不会影响整体逻辑,只是更新频率略有差异。
程序中还使用了 frag_time 来记录上一次 fragment 计算的机械时间。这样可以避免在同一个时间点重复执行 fragment 计算。因为在一个计算步内,可能会有多条粘结同时或几乎同时断裂。如果没有时间判断,程序可能会在同一个机械时间内多次进行碎块识别,导致效率下降。
执行 fragment 计算之后,程序还会遍历已有的拉伸裂隙和剪切裂隙,并尝试更新它们的位置。更新方式是读取裂隙对应的两个颗粒,然后计算这两个颗粒当前位置的中点,并把这个中点作为裂隙的新中心位置。这样,裂隙可以在一定程度上跟随颗粒运动。
不过,这里更新的只是裂隙的中心位置,并没有重新计算裂隙方向和长度。也就是说,裂隙一旦生成,它的方向和长度基本保持不变;后续只是根据对应两个颗粒的位置变化,调整裂隙中心。这种处理方式适合大多数裂纹可视化和裂纹统计场景,但如果颗粒发生很大转动,或者希望裂纹方向也随颗粒运动动态更新,则需要进一步修改脚本。
在更新裂隙位置之前,程序还会进行边界检查。只有当裂隙更新后仍然完全位于模型计算域内部时,才会真正更新它的位置。如果裂隙靠近边界,或者更新后可能超出计算域,程序就不会移动这条裂隙。这可以避免裂隙显示到不合理的位置,也可以防止部分几何异常。
6. 使用意义、适用范围与注意事项
这个文件的主要意义在于把 BPM 模型中的微观粘结断裂转化为宏观可观察的裂隙演化过程。通过它,可以实现裂纹可视化、裂纹数量统计、拉伸和剪切裂纹分类、裂纹扩展路径分析,以及碎块形成过程分析。
在岩石力学、煤岩破裂、混凝土损伤、颗粒胶结体破坏等模拟中,这类脚本非常常用。因为单纯观察颗粒运动或接触力变化,往往不容易直观判断裂纹是如何产生和扩展的。而通过 fracture 和 DFN,可以把每一次粘结断裂显示成一条裂隙,从而更接近实验中观察到的裂纹图像。
不过,使用这个文件时有几个地方需要注意。
第一,这个脚本明显是二维模型使用的。它通过把接触法向旋转 90° 来确定裂隙方向,并用两个端点创建线段裂隙。这种方法适合 PFC2D 或二维颗粒模型。如果是三维模型,裂隙通常应表示为圆盘或平面片状结构,而不能简单用二维线段描述。
第二,裂隙长度采用两个颗粒中较小的半径来确定。这种方法简单稳定,但不一定完全等于真实粘结半径。如果你的模型中平行粘结半径经过特殊设置,例如使用了粘结半径放大系数,那么裂隙长度可能需要根据平行粘结半径重新定义。
第三,文件开头注释中提到的是 bond_change 事件,但实际绑定的是 bond_break 事件。也就是说,当前代码真正监听的是粘结断裂事件,而不是一般意义上的粘结状态变化。实际运行时应以代码中的 bond_break 为准。同时要注意的是pb的mode = entries(2),fj的mode= entries(3)。
第四,文件开头和结尾的文件名注释不一致。开头写的是 fracture.p2fis,结尾写的是 fracture.p3fis。这通常只是注释没有同步修改,不影响程序运行,但建议统一,避免后续管理脚本时混淆。
第五,fragment 不是实时每条裂纹都更新,而是累计超过一定裂纹数量后更新一次。这是一种效率和精度之间的折中。如果模型较小,想要更精细地追踪碎块变化,可以降低触发阈值;如果模型很大,裂纹很多,为了提高计算效率,可以适当提高这个阈值。
总的来说,这个 fracture.p3fis 文件可以理解为一个“裂纹记录器”和“碎块追踪器”。它把颗粒间的粘结破坏转化为可视化裂隙,并将裂隙按拉伸和剪切两类分别保存,同时周期性更新 fragment 信息,用于分析试样从局部损伤到裂纹扩展再到整体破坏的全过程。
7. 裂隙文件详细注释
; fname: fracture.p3fis
; 文件名:fracture.p3fis,表示这是一个用于三维裂隙追踪的 FISH 文件
;
; 空注释行,用于分隔文件头部说明
; Simple environment to track fragmentation in a BPM.
; 该文件用于在 BPM 颗粒粘结模型中追踪破碎和裂隙演化
; Track LinearPBond model “bond_change” events and turn them into fractures.
; 该文件用于追踪线性平行粘结模型中的粘结变化或断裂事件,并将其转化为裂隙
; Use Fragment logic and Ball Result logic to record fragemnt ids
; 使用 fragment 逻辑和 ball result 逻辑记录碎块编号,其中 fragemnt 应为 fragment
;
; 空注释行
;=============================================================================
; 分隔线,用于区分文件说明和正式程序
fish define add_crack(entries)
; 定义 FISH 函数 add_crack,该函数会在粘结断裂事件发生时被调用
local contact = entries(1)
; 从事件传入参数 entries 中读取第 1 个量,即发生断裂的接触对象
local mode = entries(2)
; 从事件传入参数 entries 中读取第 2 个量,即断裂模式,通常 1 表示拉伸破坏,2 表示剪切破坏
local frac_pos = contact.pos(contact)
; 获取断裂接触的位置,并将其作为新裂隙圆盘的中心位置
local norm = contact.normal(contact)
; 获取断裂接触的法向方向,用于确定裂隙圆盘的空间姿态
local dfn_label = ‘crack’
; 定义裂隙 DFN 名称的基础字符串,后面会根据破坏类型变为 crack_tension 或 crack_shear
local frac_size
; 定义局部变量 frac_size,用于存储裂隙圆盘的尺寸
local bp1 = contact.end1(contact)
; 获取发生断裂接触一端的颗粒对象,即第一个 ball
local bp2 = contact.end2(contact)
; 获取发生断裂接触另一端的颗粒对象,即第二个 ball
local ret = math.min(ball.radius(bp1),ball.radius(bp2))
; 取两个颗粒半径中的较小值,作为裂隙尺寸的参考值
frac_size = ret
; 将较小的颗粒半径赋给 frac_size,用作裂隙圆盘的尺寸参数
local arg = array.create(5)
; 创建一个长度为 5 的数组,用于存放创建圆盘裂隙所需的参数
arg(1) = ‘disk’
; 设置第 1 个参数为 disk,表示创建三维圆盘状裂隙
arg(2) = frac_pos
; 设置第 2 个参数为裂隙圆盘中心位置
arg(3) = frac_size
; 设置第 3 个参数为裂隙圆盘尺寸,通常可理解为裂隙圆盘的直径或尺度参数
arg(4) = math.dip.from.normal(norm)/math.degrad
; 根据接触法向计算裂隙圆盘的倾角 dip,并将弧度转换为角度
arg(5) = math.ddir.from.normal(norm)/math.degrad
; 根据接触法向计算裂隙圆盘的倾向 dip direction,并将弧度转换为角度
if arg(5) < 0.0
; 判断裂隙倾向角是否小于 0 度
arg(5) = 360.0+arg(5)
; 如果倾向角为负值,则加 360 度,使其转换到 0 到 360 度范围内
end_if
; 结束倾向角修正判断
crack_num = crack_num + 1
; 裂隙总数加 1,用于统计当前已经生成的裂隙总数量
if mode = 1 then
; 如果断裂模式 mode 等于 1,说明该粘结发生拉伸破坏
; failed in tension
; 注释:该粘结以拉伸方式破坏
crack_tension_num += 1
; 拉伸裂隙数量加 1
dfn_label = dfn_label + ‘_tension’
; 将 DFN 名称改为 crack_tension,用于存储拉伸裂隙
else if mode = 2 then
; 如果断裂模式 mode 等于 2,说明该粘结发生剪切破坏
; failed in shear
; 注释:该粘结以剪切方式破坏
crack_shear_num += 1
; 剪切裂隙数量加 1
dfn_label = dfn_label + ‘_shear’
; 将 DFN 名称改为 crack_shear,用于存储剪切裂隙
endif
; 结束断裂模式判断
global dfn = dfn.find(dfn_label)
; 查找是否已经存在名为 dfn_label 的 DFN,并将其赋给全局变量 dfn
if dfn = null then
; 如果没有找到对应的 DFN,说明该类型裂隙集合尚未创建
dfn = dfn.create(dfn_label)
; 创建一个新的 DFN,例如 crack_tension 或 crack_shear
endif
; 结束 DFN 查找与创建判断
local fnew = fracture.create(dfn,arg)
; 根据前面设置的 disk 参数,在对应 DFN 中创建一个新的圆盘裂隙
fracture.prop(fnew,’age’) = mech.time.total
; 给新裂隙添加 age 属性,记录该裂隙产生时的总机械时间
fracture.extra(fnew,1) = bp1
; 将产生该裂隙的第一个颗粒对象存入裂隙的 extra(1) 中
fracture.extra(fnew,2) = bp2
; 将产生该裂隙的第二个颗粒对象存入裂隙的 extra(2) 中
crack_accum += 1
; 自上一次 fragment 计算以来的新增裂隙数量加 1
if crack_accum > 50
; 如果累计新增裂隙数量超过 50 条,则准备重新计算 fragment
if frag_time < mech.time.total
; 判断当前机械时间是否大于上一次 fragment 计算时间,避免同一时间步重复计算
frag_time = mech.time.total
; 将上一次 fragment 计算时间更新为当前机械时间
crack_accum = 0
; 将累计裂隙数清零,重新开始统计下一轮 fragment 更新间隔
command
; 进入 PFC 命令块,用于执行 PFC 内置命令
fragment compute
; 重新计算 fragment,根据当前颗粒连接关系识别碎块
endcommand
; 结束 PFC 命令块
; go through and update the fracture positions
; 注释:遍历已有裂隙,并更新裂隙位置
loop for (local i = 0, i < 2, i = i + 1)
; 循环两次,分别处理拉伸裂隙 DFN 和剪切裂隙 DFN
local name = ‘crack_tension’
; 默认本轮循环处理 crack_tension,即拉伸裂隙集合
if i = 1
; 如果循环变量 i 等于 1,则说明需要处理剪切裂隙集合
name = ‘crack_shear’
; 将当前处理的 DFN 名称改为 crack_shear
endif
; 结束 DFN 名称选择判断
dfn = dfn.find(name)
; 查找当前要处理的 DFN
if dfn # null
; 如果该 DFN 存在,则继续遍历其中的裂隙
loop foreach local frac dfn.fracturelist(dfn)
; 遍历当前 DFN 中的每一条裂隙
local ball1 = fracture.extra(frac,1)
; 读取该裂隙 extra(1) 中保存的第一个颗粒
local ball2 = fracture.extra(frac,2)
; 读取该裂隙 extra(2) 中保存的第二个颗粒
if ball1 # null
; 判断第一个颗粒是否仍然存在
if ball2 # null
; 判断第二个颗粒是否仍然存在
local len=fracture.diameter(frac)/2.0
; 获取裂隙圆盘直径的一半,作为后续边界检查使用的半径
local pos=(ball.pos(ball1)+ball.pos(ball2))/2.0
; 计算两个颗粒当前位置的中点,并将其作为裂隙新的中心位置
if comp.x(pos)-len > xmin
; 判断裂隙更新后在 x 方向负侧是否仍位于模型域内部
if comp.x(pos)+len < xmax
; 判断裂隙更新后在 x 方向正侧是否仍位于模型域内部
if comp.y(pos)-len > ymin
; 判断裂隙更新后在 y 方向负侧是否仍位于模型域内部
if comp.y(pos)+len < ymax
; 判断裂隙更新后在 y 方向正侧是否仍位于模型域内部
if comp.z(pos)-len > zmin
; 判断裂隙更新后在 z 方向负侧是否仍位于模型域内部
if comp.z(pos)+len < zmax
; 判断裂隙更新后在 z 方向正侧是否仍位于模型域内部
fracture.pos(frac)= pos
; 如果裂隙圆盘仍完全位于计算域内部,则更新裂隙中心位置
end_if
; 结束 z 方向正侧边界判断
end_if
; 结束 z 方向负侧边界判断
endif
; 结束 y 方向正侧边界判断
endif
; 结束 y 方向负侧边界判断
endif
; 结束 x 方向正侧边界判断
endif
; 结束 x 方向负侧边界判断
endif
; 结束第二个颗粒是否存在的判断
endif
; 结束第一个颗粒是否存在的判断
endloop
; 结束对当前 DFN 中所有裂隙的遍历
endif
; 结束当前 DFN 是否存在的判断
endloop
; 结束拉伸裂隙和剪切裂隙两个 DFN 的循环
endif
; 结束当前机械时间是否允许 fragment 计算的判断
endif
; 结束累计裂隙数量是否超过 50 的判断
end
; 结束 add_crack 函数
fish define track_init
; 定义初始化函数 track_init,用于在正式计算前初始化裂隙追踪系统
command
; 进入 PFC 命令块
fracture delete
; 删除模型中已有的 fracture 裂隙,避免旧裂隙影响新计算结果
model results clear-map
; 清除模型结果映射,避免旧的结果映射数据影响后续记录
fragment clear
; 清除已有的 fragment 碎块信息
fragment register ball-ball
; 注册 ball-ball 类型的 fragment 识别,根据颗粒间连接关系划分碎块
endcommand
; 结束 PFC 命令块
; activate fishcalls
; 注释:激活 FISH 回调函数
command
; 进入 PFC 命令块
fish callback remove @add_crack event bond_break
; 移除已有的 add_crack 断键回调,防止重复绑定
fish callback add @add_crack event bond_break
; 将 add_crack 函数绑定到 bond_break 事件,使粘结断裂时自动生成裂隙
endcommand
; 结束 PFC 命令块
; reset global variables
; 注释:重置全局变量
global crack_accum = 0
; 初始化累计裂隙数量为 0,用于控制 fragment compute 的触发频率
global crack_num = 0
; 初始化裂隙总数为 0
global track_time0 = mech.time.total
; 记录开始追踪裂隙时的总机械时间
global frag_time = mech.time.total
; 初始化上一次 fragment 计算时间为当前机械时间
global xmin = domain.min.x()
; 记录模型计算域在 x 方向的最小边界
global ymin = domain.min.y()
; 记录模型计算域在 y 方向的最小边界
global xmax = domain.max.x()
; 记录模型计算域在 x 方向的最大边界
global ymax = domain.max.y()
; 记录模型计算域在 y 方向的最大边界
global zmin = domain.min.z()
; 记录模型计算域在 z 方向的最小边界
global zmax = domain.max.z()
; 记录模型计算域在 z 方向的最大边界
end
; 结束 track_init 初始化函数
;=============================================================================
; 文件结束前的分隔线
; eof: fracture.p3fis
; 文件结束标记,表示 fracture.p3fis 文件到此结束
如有其他需要,欢迎关注我的咸鱼号:pfc小姐姐

夜雨聆风
