摘要: 本文详细讲解 FLAC3D 官方 PPV(峰值质点速度)算例,展示如何通过 Python 回调函数在动力计算中实时追踪每个网格点的最大速度矢量,并将 Python 自定义数据持久化保存到 FLAC3D 存档文件中。文末附改良版包络云图计算脚本,可直接用于时程分析,适合从事动力分析、爆破振动、地震响应的岩土工程师阅读。回调频率并非越高越好,过密会拖慢计算。(点赞 + 转发 + 关注本公众号,后台回复「PPV包络」即可获取完整改良版,直接用于时程分析包络云图)
字数: 约 3200 字 | 预计阅读时间: 8 分钟
在岩土动力工程中,峰值质点速度(Peak Particle Velocity, PPV) 是评价振动效应最核心的指标之一。无论是爆破振动安全评估、地震响应分析,还是动力机器基础设计,PPV 都是判断结构是否安全的关键依据。
FLAC3D 本身并不直接输出 PPV 云图——动力分析完成后,我们通常只能看到某个时刻的速度场,而整个时程过程中的最大值需要额外记录。
这个算例的特别之处在于:

算例建立了一个 100 m × 100 m × 100 m 的立方体弹性模型,中心区域挖去一个 10 m × 10 m × 20 m 的长方体孔洞,用来模拟地下开挖空间。在模型一角施加集中应力作为震源,应力波在模型内传播,记录所有网格点在整个时程中的速度峰值。
模型几何特征:
zone create brick size 100 100 100
zone delete range position-x 40 50 position-y 40 50 position-z 40 60
model config dynamic
zone cmodel assign elastic
zone property density 2950 young 12e9 poisson 0.25
zone dynamic damping rayleigh 0.05 150
zone face apply quiet-normal range position-x 0 position-x 100 ...
position-y 0 position-y 100 position-z 0 union
zone face apply quiet-strike range position-x 0 position-x 100 ...
position-y 0 position-y 100 position-z 0 union
zone face apply quiet-dip range position-x 0 position-x 100 ...
position-y 0 position-y 100 position-z 0 union
union 关键字合并 6 个面为一个 rangezone initialize stress xx 10e6 yy 10e6 zz 10e6 ...
range position-x 80 81 position-y 80 81 position-z 80 81
PPV 定义:每个网格点在整个动力时程中经历的速度矢量模的最大值。
PPV_i = max( |v_i(t)| ) 对所有时间步 t
实现方式:通过 it.set_callback 注册一个 Python 函数,在每个力学计算步自动调用,用 np.maximum 逐元素取最大。
import itasca as it
import numpy as np
from itasca import gridpointarray as gpa
ppv =Nonedefstore_ppv(*args):global ppv
if ppv isNone: ppv = np.zeros(it.gridpoint.count()) ppv = np.maximum(np.linalg.norm(gpa.vel(), axis=1), ppv)it.set_callback("store_ppv",0.1)关键解读:
gpa.vel() 返回所有网格点速度的 (N, 3) numpy 数组np.linalg.norm(..., axis=1) 批量计算每个点的速度矢量模np.maximum 逐元素与历史最大值比较并更新默认情况下,Python 变量不会随 FLAC3D 保存文件一起存储。执行 model save 后再 model restore,Python 里的 ppv 数组就没了——这对于大型动力分析来说非常致命。
利用 FLAC3D 的 gridpoint extra 变量作为桥梁,在保存时写入、恢复时读回。
defppv_save(*args):"""保存时触发:将 ppv 写入网格点 extra 变量"""global ppv
print("saving PPV")if ppv.any(): gpa.set_extra(1, ppv)defppv_restore(*args):"""恢复时触发:从 extra 变量读回 ppv"""global ppv
print("restoring PPV")try: ppv = gpa.extra(1)print("max ppv during restore", ppv.max())except:passdefppv_new(*args):"""新建模型时触发:重置 ppv"""global ppv
print("resetting PPV") ppv =None# 注册三个事件回调it.set_callback("ppv_save","save")it.set_callback("ppv_restore","restore")it.set_callback("ppv_new","new")三个回调事件的时机:
save | model save | |
restore | model restore | |
new | model new |
💡 另一种方案:也可以用
it.fish函数将 Python 值转为 FISH 变量保存,适合标量或简单数组。但对于大规模网格数据(如百万级 PPV 数组),用 extra 变量更直接高效。
计算完成后,用 numpy + matplotlib 分析 PPV 随震源距离的衰减关系。
将网格点分为三类:
load_point =(80.5,80.5,80.5)distance = np.linalg.norm(gpa.pos()- load_point, axis=1)gpos = gpa.pos()gx, gy, gz = gpos.T
# 地表点(顶面)top_mask = gz ==100.0# 外表面点exterior_mask =reduce(np.logical_or,(gx==0, gx==100, gy==0, gy==100, gz==0, gz==100))# 边界网格点(关联单元数 ≠ 8)boundary_gridpoint_mask = np.array([len(v)!=8for v in gpa.zones()])# 开挖面 = 非外表面 + 边界点excavation_surface_mask = np.logical_and( np.logical_not(exterior_mask), boundary_gridpoint_mask)# 内部点interior_mask = np.logical_not(boundary_gridpoint_mask)import pylab as plt
plt.loglog(distance[interior_mask], ppv[interior_mask],"o", color="#e15759", markeredgewidth=0)plt.loglog(distance[excavation_surface_mask], ppv[excavation_surface_mask],"o", color="#f28e2b", markeredgewidth=0)plt.loglog(distance[top_mask], ppv[top_mask],"o", color="#4e79a7", markeredgewidth=0)plt.legend(("Interior","Excavation Surface","Ground Surface"))plt.xlabel("Distance from Source [m]")plt.ylabel("PPV [m/s]")plt.show()关键发现:
ppv.f3dat(主数据文件):
;-----------------------------------------------------------
; PPV - Peak Particle Velocity Recording
;-----------------------------------------------------------
model new
call 'ppv.py'
model config dynamic
; --- model geometry ---
zone create brick size 100 100 100
zone delete range position-x 40 50 position-y 40 50 position-z 40 60
zone face skin
; --- material properties ---
zone cmodel assign elastic
zone property density 2950 young 12e9 poisson 0.25
zone dynamic damping rayleigh 0.05 150
; --- quiet boundaries ---
zone face apply quiet-normal range position-x 0 position-x 100 ...
position-y 0 position-y 100 position-z 0 union
zone face apply quiet-strike range position-x 0 position-x 100 ...
position-y 0 position-y 100 position-z 0 union
zone face apply quiet-dip range position-x 0 position-x 100 ...
position-y 0 position-y 100 position-z 0 union
; --- source loading ---
zone initialize stress xx 10e6 yy 10e6 zz 10e6 ...
range position-x 80 81 position-y 80 81 position-z 80 81
; --- solve ---
model solve time-total 0.08
model save 'after_loading'
ppv.py(Python 脚本):
import itasca as it
import numpy as np
np.set_printoptions(threshold=20)from itasca import gridpointarray as gpa
ppv =Nonedefstore_ppv(*args):global ppv
if ppv isNone: ppv = np.zeros(it.gridpoint.count()) ppv = np.maximum(np.linalg.norm(gpa.vel(), axis=1), ppv)defppv_save(*args):global ppv
print("saving PPV")if ppv.any(): gpa.set_extra(1, ppv)defppv_restore(*args):global ppv
print("restoring PPV")try: ppv = gpa.extra(1)print("max ppv during restore", ppv.max())except:passdefppv_new(*args):global ppv
print("resetting PPV") ppv =Nonedeftransit_time(): z = it.zone.find(1) K, G, rho = z.prop("bulk"), z.prop("shear"), z.density() vp = np.sqrt((K +4.0/3.0*G)/rho) d = np.sqrt(3*100**2)return d/vp
it.set_callback("store_ppv",0.1)it.set_callback("ppv_save","save")it.set_callback("ppv_restore","restore")it.set_callback("ppv_new","new")官方算例只实现了速度的峰值记录,但在实际工程中,我们经常需要对位移、应力、加速度等多个物理量做时程包络分析。为此我做了一个改良版本:
🔔 获取方式:点赞 + 在看 + 关注本公众号,后台回复「PPV包络」即可获取完整改良版脚本(含速度包络、位移包络、应力包络全套代码,带详细注释,拿来即用)。
it.set_callback 注册自定义函数介入计算流程np.maximum + np.linalg.norm 逐点追踪速度峰值model new 时务必重置 Python 变量,否则新旧模型数据混杂