写在前面:
新开个人公众号一枚,长期驻守。更新随心,忙时沉淀,闲时落笔。往后会陆续分享个人科研点滴,承蒙厚爱,欢迎大家关注指教✨
还在受限于软件自带的常规渗流模块?教你用Python偏微分方程求解器联动离散元内核,降维打击传统管涌、突水与水力压裂模拟,重塑顶刊级力学模型。
顶刊里的流固耦合,为什么那么难复现?
在岩土工程与岩石力学领域,无论是冲击AG还是 CG,RMRE,TUST,IJRMMS等国际顶刊。只要涉及管涌、断层突水、尾矿坝溃决或水力压裂,流固耦合的深度机理分析都是审稿人眼里的加分项。
但许多科研工作者在面对多场耦合时,常常遭遇瓶颈:
1.黑盒效应:内置流体模块通常是封装好的,一旦想要自定义复杂的流体(如非牛顿流体、变粘度泥石流)或非线性渗透率演化,往往无从下手。
2.物理场单向妥协:常规方法很难实现完美的双向实时反馈(如:孔隙率改变、渗透率突变、局部水压重分布、颗粒拖曳力剧增)。
3.计算效率低下:面对数以十万计的网格和颗粒,传统的循环赋值方法会让计算时间呈指数级膨胀。
今天,我们将展示一套“PFC 6.0 + FiPy PDE 求解器”的顶级联合求解工作流,带你彻底打开多孔介质渗流力学的黑匣子!
这套工作流的核心在于物理场分离求解。我们引入了基于Python的强大开源有限体积法求解器FiPy,让它与PFC的力学内核进行双向奔赴。
1. 偏微分方程(PDE)的绝对自由度
我们不再依赖软件的默认渗流算法,而是在 FiPy 中直接构建达西渗流的控制方程∇∙(M∇P)=0。
这意味你拥有了上帝视角。只需修改方程参数,你就能精准定义任何形式的流体行为、设定极其复杂的动态水压边界,甚至引入化学溶蚀项。
2. 完美的物理量双向映射
1.固相-液相:利用经典的 Kozeny-Carman 公式,让网格渗透率与离散元颗粒的实时孔隙率强绑定。当岩土体发生剪切扩容或挤压密实时,流场会瞬间感知并改变水流路径。
2.液相-固相:FiPy 求解出的高精度压力梯度,将被精确重构为网格中心速度,实时转化为作用在颗粒上的流体拖曳力(Drag Force)。
3. Numpy 矩阵级超算提速
这套框架摒弃了传统的单体循环计算。所有空间网格的压力、速度和孔隙率数据,全部被封装进Numpy数组,通过底层的高速数据通道(CFD Array)直接灌入计算内核。用纯数学矩阵的思维进行批量吞吐,计算速度实现跨越式提升!
初始模型
渗流0.2s(真实时间)
渗流0.4s(真实时间)
渗流0.6s(真实时间)
典型应用场景
掌握了这套基于Python的联合求解架构,你相当于拥有了一个完全定制化的多物理场引擎,可轻松降维打击以下复杂课题:
·内部侵蚀与管涌(Piping/Erosion):精准捕捉细颗粒在动态水力梯度下的流失过程与骨架失稳机制。
·孔压驱动与水力压裂(Hydraulic Fracturing):模拟注水试验中,孔隙水压在微裂隙中的积聚与动态劈裂扩展。
·复杂地质体的渗流突变:还原具有正交节理(如柱状玄武岩)或软弱夹层的各向异性岩体在开挖卸荷下的流场演化。
部分代码如下:model new model domain extent -3 22 -2 5 -3 14 condition destroygeometry set 'slope'geometry import 'slope.stl'geometry rotate axis 1 0 0 angle 90geometry translate 0 3 0wall generate box 0 20 0 3 0 12ball distribute porosity 0.15 radius 0.15 box 0 20 0 3 0 12cmat default model linear type ball-ball method deform emod 1e8 kratio 1.0 property fric 0.5cmat default model linear type ball-facet method deform emod 10e8 kratio 1.0 property fric 0.5ball attribute density 2500 damp 0.7wall import from-geometry 'slope' skip-errors id 100ball delete range geometry-space 'slope' count odd notmodel cycle 1000 calm 10model solve ratio-average 1e-3model save 'slope1'
model res 'slope1'contact model linearpbond range contact type 'ball-ball'contact method bond gap 0.01contact method deform emod 1.0e9 krat 1.5contact method pb_deform emod 1.0e9 krat 1.5contact property pb_ten 1.5e6 pb_coh 1.5e6 pb_fa 0.0contact property dp_nratio 0.5contact property fric 0.5 range contact type 'ball-ball'ball attribute displacement multiply 0.0contact property lin_force 0.0 0.0 0.0 lin_mode 1ball attribute force-contact multiply 0.0 moment-contact multiply 0.0 model gravity 10model cycle 1model solve ratio-average 1e-5model save 'slope2'
import numpy as npimport pylab as pltimport fipy as fpimport itasca as itfrom itasca import ballarray as bafrom itasca import cfdarray as cafrom itasca import elementfrom functools import reduceclass DarcyFlowSolution(object): def __init__(self): self.mesh = fp.Grid3D(nx=12, ny=2, nz=9, dx=1.5, dy=1.5, dz=1.5) self.pressure = fp.CellVariable(mesh=self.mesh, name='pressure', value=0.0) self.mobility = fp.CellVariable(mesh=self.mesh, name='mobility', value=0.0) self.pressure.equation = (fp.DiffusionTerm(coeff=self.mobility) == 0.0) self.mu = 1e-2 # dynamic viscosity self.inlet_mask = None self.outlet_mask = None # create the FiPy grid into the PFC CFD module ca.create_mesh(self.mesh.vertexCoords.T, self.mesh._cellVertexIDs.T[:,(0,2,3,1,4,6,7,5)].astype(np.int64)) if it.ball.count() == 0: self.grain_size = 5e4 else: self.grain_size = 2*ba.radius().mean() it.command(""" model configure cfd element cfd attribute density 1e3 element cfd attribute viscosity {} cfd porosity polyhedron cfd interval 1 """.format(self.mu)) def set_pressure(self, value, where): """Dirichlet boundary condition. value is a pressure in Pa and where is a mask on the element faces.""" print("setting pressure to {} on {} faces".format(value, where.sum())) self.pressure.constrain(value, where) def set_inflow_rate(self, flow_rate): """ Set inflow rate in m^3/s. Flow is in the positive y direction and is specfified on the mesh faces given by the inlet_mask. """ assert self.inlet_mask.sum() assert self.outlet_mask.sum() print("setting inflow on %i faces" % (self.inlet_mask.sum())) print("setting outflow on %i faces" % (self.outlet_mask.sum())) self.flow_rate = flow_rate self.inlet_area = (self.mesh.scaledFaceAreas*self.inlet_mask).sum() self.outlet_area = (self.mesh.scaledFaceAreas*self.outlet_mask).sum() self.Uin = flow_rate/self.inlet_area inlet_mobility = (self.mobility.getFaceValue() * \ self.inlet_mask).sum()/(self.inlet_mask.sum()+0.0) self.pressure.faceGrad.constrain( ((0,),(-self.Uin/inlet_mobility,),(0,),), self.inlet_mask)