PFC单轴压缩模拟:按裂纹数触发截图与应力位移云图同步导出
简介PFC单轴压缩数值模拟代码采用离散元方法构建非均质岩石模型集成声发射监测、裂纹数实时截图以及应力场与位移场云图同步输出功能适用于岩土工程、岩石力学与材料科学领域研究者开展单轴压缩破坏机制分析、裂纹演化规律追踪及强度变形特性研究。压缩包共11个文件以6份docx格式说明文档为主体系统介绍了非均质模型构建方法、声发射分析流程与云图数据处理思路4个html页面提供可视化代码展示1张jpg图片呈现示例结果整体体积仅3.5MB便于快速查阅与二次开发。已有255人学习下载。通过该资源可获取完整的PFC二次开发代码框架、非均质模型参数设置方案、声发射特征提取思路以及裂纹数与应力、位移云图联动输出的关键实现方法能大幅缩短离散元模拟建模与后处理程序的编写时间。1. PFC单轴压缩代码把试件破坏过程变成一套可回放的数据我最近在调一套PFC单轴压缩代码模型是带声发射监听的非均质试件核心功能是“根据裂纹数截图同时输出应力和位移云图数据”。为什么要折腾这个因为只看最终破坏形态几乎所有试件都是斜剪断或压碎真正的区别全在过程中——裂纹从哪个区域先冒出来、应力怎么集中、位移怎么重分布。这套代码解决的就是把这个动态过程按“裂纹数阈值”切成若干关键帧每一步截图都配同一时刻的应力场、位移场数据。适合用PFC做岩土破裂机理、非均质材料数值试验以及想让数值AE和室内声发射数据对标的工程师和研究生。2. 模型搭建与技术选型非均质参数场和声发射数据流怎么组织2.1 技术栈与文件组织PFC 6.0 命令流 Python 后处理PFCParticle Flow Code是颗粒流离散元平台搞岩土破裂模拟的人基本都绕不开它。单轴压缩这种试件尺度的问题用连续元模型处理裂纹萌生和扩展很别扭离散元天生就是颗粒和粘结的集合裂纹可以从任意接触处产生所以特别适合这类细观试验。这套资源我拿到的目录结构是下面这样的pfc_ucs_nonhom/ ├── data/ │ ├── snapshots/ # 裂纹演化截图输出目录 │ ├── ae_event.csv # 声发射事件记录 │ └── field/ │ ├── stress_step_0500.csv # 某时刻应力场 │ └── disp_step_0500.csv # 某时刻位移场 ├── scripts/ │ ├── 01_generate_sample.fish # 生成非均质试件 │ ├── 02_assign_property.fish # Weibull 赋参数 │ ├── 03_run_compression.py # 主循环裂纹监听截图 │ └── post_process.py # 云图后处理 └── README.md主体计算脚本用FISH命令流Python只做循环控制、回调监听和文件输出。为什么这么分工FISH在PFC里执行效率最高适合写颗粒遍历、接触遍历这类高密度计算Python适合写逻辑判断判断什么时候触发截图、什么时候写文件比纯FISH的字符串拼文件舒服得多。PFC 6.0之后的PyPFC接口把这两者打通这也是这套项目能实现“按裂纹数自动截图导数据”的基础。数据流是单向的建模 → 赋非均质参数 → 稳压平衡 → 加载 → 每步循环里更新应力应变 → 监听裂纹 → 达到阈值时导出当前场数据和截图 → 后处理画云图。这个顺序基本不用动动的话后面模块的输入会接不上。第一次拿项目先打开PFC控制台依次敲一遍两个.fish文件确认试件生成、压密、赋非均质参数三步都正常再往上跑主脚本。2.2 非均质模型用Weibull分布给每个接触赋不同的强度非均质模型的核心不是“颗粒颜色不一样”而是细观力学参数空间分布的随机性。常见做法是让平行粘结的抗拉强度、粘结力、粘结刚度服从Weibull分布用均质度系数m控制离散程度。m越大材料越均匀m越小颗粒尺度上的强度方差越大试件里越容易出现天然“弱面”。下面这段FISH函数是我从这套资源里抽出来的、给每个接触赋抗拉强度的核心逻辑fish define weibull_pb_ten local i 0 loop foreach local ct in contact.list i i 1 local u math.random.uniform local pb_ten global.m_scale * ( -math.ln(u) ) ^ (1.0 / global.m) contact.prop(ct, pb_ten) pb_ten contact.prop(ct, pb_coh) pb_ten * global.coh_ratio end_loop end这段代码干的事情很直接遍历当前所有接触取一个[0,1)均匀随机数u通过Weibull逆变换得到该接触的抗拉强度再按固定比例算出粘聚力。这里global.m是均质度系数控制强度离散程度global.m_scale是尺度参数实际控制平均抗拉强度类岩石材料一般给8到20 MPaglobal.coh_ratio是粘聚力与抗拉强度的比值通常取5到10比值越高试件越倾向先发生拉伸破坏而不是剪切破坏。这套参数里最敏感的是m值我实际调参的参考范围如下参数含义典型取值对结果的影响global.m均质度系数3~10越大越均匀越小弱面越突出global.m_scale平均抗拉强度8~20 MPa决定峰值强度量级global.coh_ratio粘聚力/抗拉强度比5~10控制剪切与拉伸破坏比重global.snapshot_gap截图裂纹间隔总裂纹的1%~2%控制截图密度和数据量赋完参数之后最好抽样验证一下分布形态。做法很简单单独跑一遍遍历把每50个接触的pb_ten打印出来画直方图看是不是符合预期的Weibull形状。m3时直方图会明显出现低强度长尾m10时基本集中在中值附近。如果分布不对后面破坏模式全是虚的。这里有个容易踩坑的点必须在所有接触生成完之后再赋参数而且要放在压密完成之后。若在压密前就赋好强度颗粒重排会导致接触不断增删你费的劲白干还会出现一批强度极低的“接触幽灵”影响初始平衡。2.3 声发射监听把裂纹增量变成AE事件流PFC里没有现成的“声发射”对象但破裂产生裂纹这件事是能统计的。室内声发射探头测到的是弹性波数值模型里能拿到的代理变量是裂纹的生成时刻、位置和尺寸。大多数做离散元AE的人都以裂纹长度作为事件能量的代理变量。下面这段函数是这套资源里监听裂纹增量的核心fish define crack_listener local n_new crack.list.size - global.ae_count loop local i (1, n_new) local tally global.ae_count i local c crack.list(tally) global.ae_step(tally) global.step global.ae_x(tally) crack.pos(c)-x(global) global.ae_y(tally) crack.pos(c)-y(global) global.ae_type(tally) crack.type(c) global.ae_energy(tally) crack.length(c) end_loop global.ae_count crack.list.size end这个函数不需要轮询所有接触只比较crack.list.size和上次记录值global.ae_count把新增裂纹追加到全局数组里。每次裂纹产生记录当前step、二维坐标、裂纹类型剪切/拉伸以及用裂纹长度近似的事件能量。global.ae_step、global.ae_x这些数组要在主脚本开头用array(200000)初始化长度按预估裂纹数上限设数组索引从1开始。调用方式很灵活放在主循环里每步主动调用或者挂进PFC的fish callback注册成step回调。每调用一次它就拉取一次裂纹增量性能开销可以忽略。需要说明的是PFC的裂纹没有直接的“能量”输出字段crack.length是裂纹尺寸学术论文里常规做法是把它作为AE能量的相对指标。如果只关心累计事件数每条裂纹算一个事件就行如果要做能量统计建议对裂纹长度加权后再累计。这也是后面剪枝优化的基础——事件量大了以后不是所有微裂纹都值得保留。3. 核心链路裂纹计数触发截图与应力/位移云图同步导出3.1 加载与应力应变计算别让加载速率毁了你的破坏模式单轴压缩试验机是刚性压头数值模型里主流做法是让上下墙以恒定速度相向运动同时把加载速率控制在准静态范围。PFC模型的惯性力影响很大加载速率过快会导致裂纹分布明显发散破坏模式从剪切破坏变成劈裂破坏结果直接失真。下面是加载与应力计算的主要骨架fish define get_stress_strain local wal_b wall.find(bottom) local wal_t wall.find(top) global.strain (wall.pos.y(wal_b) - global.h0) / global.h0 global.stress wall.force.contact(wal_t)-y(global) / (global.wid * 1.0) endglobal.h0是试件初始高度global.wid是试件宽度二维模型默认厚度取1截面积就是宽度×1。用顶墙接触力除以截面积得到名义应力位移变化量除以原始高度得到名义应变。这套代码没有每步遍历所有颗粒直接读墙体反力开销很小。在实际跑的时候我一般还会加一个伺服控制让墙速按峰值应力动态调整避免在应变软化段把试件“拍碎”。伺服控制的常见做法是每隔几步读一次当前应力如果应力增速超过设定值就把墙速降一半如果增速低于阈值恢复原速。这样峰后曲线不会出现应力的剧烈跌落破坏过程更容易被截到。3.2 裂纹数截图触发阈值自动判定的那一刻项目名里的“根据裂纹数截图”实现上是这样一个循环判断主循环每推进若干步调用一次check_snapshot计算当前裂纹总数与上一次截图时的裂纹总数之差当差值越过设定阈值global.snapshot_gap时触发截图和场数据导出。fish define check_snapshot local target global.snapshot_index * global.snapshot_gap if global.ae_count target global.snapshot_index global.snapshot_index 1 local fname snap_ string(global.snapshot_index) .png command plot delete crack_plot plot create crack_plot plot add crack plot export png file [fname] end_command export_mea_field(string(global.snapshot_index)) end_if end这段逻辑把“截图”和“导数据”绑定在同一个触发点所以应力云图数据和位移云图数据总是和裂纹图拍到的是同一个加载时刻。global.snapshot_gap代表每新增多少条裂纹截一次图一般设为预估总裂纹数的1%到2%。如果设成固定值比如50条破裂前期可能很久截不到图后期又会连拍好几张这就是很多项目导出的云图时间点不连续的根本原因。每次截图前先把同名plot删掉再创建是为了避免PFC里重复创建同名plot对象直接报错。3.3 云图数据导出应力和位移字段怎么落到csv导出应力云图数据我不会让PFC直接画云图而是把场数据写成csv再用Python或Paraview后处理。这样做的理由是“画图”和“数据”分离PFC的plot导出的是位图后处理脚本可以随时换配色、换渲染方式数据本身保留原始粒度。下面这段FISH生成测量圆应力场文件是该资源里导出应力数据的主函数fish define export_mea_field local fname data/field/stress_step_ arg(1) .csv local f file.open(fname, w) file.write(f, x,y,sxx,syy,sxy,dp\n) loop foreach local m in measure.list local sx measure.stress(m)-xx(global) local sy measure.stress(m)-yy(global) local sxy measure.stress(m)-xy(global) file.write(f, string(measure.pos.x(m)) , string(measure.pos.y(m)) , string(sx) , string(sy) , string(sxy) , string(measure.dp(m)) \n) end_loop file.close(f) end测量圆在建模阶段就按规则网格布置在试件范围内每个测量圆输出中心坐标、三向应力分量和损伤参数。用measure.list做循环特点是写文件量小、不涉及庞大颗粒数据。位移场数据同理遍历ball写出ball.displacement(b)-x(global)和ball.displacement(b)-y(global)。两套csv是同时刻的后处理的时候按文件名里的step编号做对齐就能把应力集中和位移重分布画成组合云图。到这里“按裂纹数截图 同步导出应力/位移场”这条主链路就闭环了。实际项目里这个流程跑下来坑比想象多得多下一章列几条我实测踩过的。4. 避坑经验非均质单轴压缩最容易翻车的5个地方4.1 颗粒一生成就“炸开”平衡阶段就没守住现象跑01_generate_sample.fish时模型平衡到一半颗粒速度持续增大试件整体膨胀甚至飞出边界。原因要么ball distribute随机投放的重叠率过高颗粒间初始接触力太大要么非均质参数里给了一部分极低强度接触在压密还没完成之前这些弱接触就先断了相当于试件还没“凝固”就产生了裂纹。解决先生成颗粒关闭粘结只保留线性接触用墙压密到目标孔隙率压密结束后重置颗粒速度再赋非均质平行粘结参数。顺序不能倒先压密、后赋强度。4.2 AE数对不上室内试验把每条裂纹直接当成一次撞击现象数值AE累计曲线和室内声发射的累计撞击曲线趋势相近但绝对数量差一个数量级。原因室内AE系统里一次撞击可能包含大量微破裂PFC里一条裂纹就是一个事件两者统计粒度不对等。解决用能量代理做剪枝。把裂纹长度低于某阈值的微破裂合并到邻近高能量事件中或按事件聚类后再对比。这套思路我在第5章展开了。4.3 硬盘被云图数据塞满全场数据每个step都导出现象跑完一个中等规模模型输出目录多了几十个GB的csv和png。原因主循环里每step都调用导出函数颗粒数和测量圆数量一多IO开销大文件量失控。解决只在裂纹数跨越设定阈值时导出并且导出的字段只保留需要的分量。先低分辨率试跑一次确认输出总量再跑正式工况。4.4 同参数不同随机种子破坏模式完全不一样现象同样的Weibull参数只改随机种子一个结果是单斜面剪切破坏另一个变成中间劈裂。原因非均质模型的随机种子决定了弱面分布小样本下破坏模式受局部弱面控制单次模拟结果不可靠。解决一个工况至少跑5个随机种子对裂纹分布、峰值强度取包络和方差。论文里如果只给一条曲线大概率是只跑了一个种子。4.5 plot导出png全黑图里什么对象都没加现象plot export png成功生成了文件打开后是纯黑或白底空图。原因plot对象创建后没add任何数据项或者当前view范围不对裂纹和颗粒全在视野外。解决在截图前先用plot add crack、plot add ball等显式指定要绘制的内容然后plot pan把视野缩放到试件范围。我曾栽过一次后来把“创建-添加-缩放-导出”四步固定成模板再没黑过屏。5. 二次开发方向给AE事件流加一层剪枝让输出数据量砍半5.1 能量阈值剪枝 空间合并破裂最剧烈的阶段一个step内可能产生几十条裂纹全部写入ae_event.csv会导致事件序列冗长后处理时又卡又占空间。我做的第一处改造是给AE事件流加剪枝先按能量阈值过滤微裂纹再按空间距离把相邻事件合并成一个宏观AE事件。import csv def prune_ae(events, e_th0.5, d_th1e-3): events.sort(keylambda e: e[step]) kept [] for ev in events: if ev[energy] e_th: continue if kept: last kept[-1] if ev[step] - last[step] 20: dist ((ev[x] - last[x])**2 (ev[y] - last[y])**2)**0.5 if dist d_th: last[energy] ev[energy] continue kept.append(ev) return kepte_th是能量阈值低于该值的微裂纹视为噪声d_th是空间合并半径在20个step内出现且距离小于该值的裂纹会被合并到前一个事件中能量累加。这样得到的AE序列在累计曲线上与原始序列保持一致但事件数量可以降到原来的30%到50%画累计AE曲线时性能会好很多。5.2 云图输出的自适应“剪枝”按破裂速率调整导出频率第二处剪枝用在云图导出频率上破裂速率低时拉大输出间隔破裂速率高时加密输出避免大量完全相似的中期云图占据空间。把3.2章节里的固定snapshot_gap改成动态值if crack_rate high_rate: gap 50 elif crack_rate low_rate: gap 100 else: gap 200判断条件放在check_snapshot里破裂后期裂纹成群爆发时加密截图峰前平稳阶段则稀疏采样。这样文件量通常能再降一半而且关键时刻的云图密度不变。验证剪枝是否合理的标准很简单拿剪枝前后的累计AE事件曲线对比误差在5%以内且破坏模式截图的关键阶段没有跳变就说明阈值设得合适。以前我总担心多记数据以后能复用结果每次跑完都在清理输出文件。从那以后我每次新建模型都会先定“哪些数据必须存、哪些数据算完就弃”像AE事件和云图输出这类的剪枝阈值先用小模型试跑标定一遍再上正式工况。希望帮到你。本文还有配套的精品资源点击获取