1. 项目背景与核心挑战
实验室的白炽灯管嗡嗡作响,老张盯着CT扫描生成的岩心灰度图像,屏幕上那些扭曲的黑色纹路就像被揉皱的报纸——这是某油田实际取芯样本的微观孔隙结构。墙角两个标着"实验专用"的油罐已经见底,第三次驱替实验的数据曲线依然不符合预期。
问题的核心在于:传统手工建模的规则孔隙网络根本无法还原真实岩心的复杂结构。那些像毛细血管般交错连接的孔喉通道,其直径分布从纳米级到微米级不等,形状更是千奇百怪。我们尝试用AutoCAD重建时,要么丢失了关键的喉道连接,要么把表面粗糙度简化成了光滑曲面——这直接导致两相驱替模拟结果与物理实验出现高达40%的偏差。
2. 真实孔隙建模关键技术
2.1 CT扫描数据的预处理
从医院借来的医用CT扫描仪分辨率达到2μm,但原始DICOM格式的断层图像需要经过一系列预处理:
- 灰度阈值分割:使用ImageJ的Otsu算法自动区分固体骨架(亮色)和孔隙空间(暗色)
- 噪声消除:采用非局部均值滤波(Non-local Means Denoising)保留孔隙边缘细节
- 三维重建:通过Avizo软件的Marching Cubes算法生成STL格式的表面网格
关键提示:扫描时务必保持样本绝对静止,我们曾因样本轻微晃动导致连续三组数据出现"重影"伪影
2.2 COMSOL几何导入与修复
将STL文件导入COMSOL时,常见的几何问题包括:
- 微小孔隙被识别为噪点(<5体素)
- 相邻孔隙间的薄壁(<1μm)出现破损
- 曲面存在非流形边缘
修复代码示例:
model = ModelUtil.create('Model'); model.geom.create('geom1', 3); model.geom('geom1').feature.create('imp1', 'Import'); model.geom('geom1').feature('imp1').set('filename', 'core_sample.stl'); model.geom('geom1').feature('imp1').set('repair', 'on'); // 自动修复开关 model.geom('geom1').run; // 执行导入 model.geom('geom1').scale(1e-6); // 单位转换为米常见修复策略对比表:
| 问题类型 | 手动修复方法 | 自动修复效果 |
|---|---|---|
| 孤立噪点 | 逐个删除 | 效果较好 |
| 孔喉断裂 | 桥接操作 | 成功率约60% |
| 曲面破损 | 重新三角化 | 可能改变拓扑结构 |
3. 两相流物理场设置
3.1 材料参数定义
根据实验室实测数据定义两种原油属性:
// 重质原油(原始油藏) rho1 = 892 [kg/m^3]; mu1 = 0.032 [Pa·s]; // 轻质原油(驱替液) rho2 = 856 [kg/m^3]; mu2 = 0.028 [Pa·s]; // 界面张力 sigma = 0.025 [N/m];3.2 Level Set方法实现
采用相场法追踪两相界面时,控制方程包括:
Navier-Stokes方程: $$\rho(\frac{\partial \mathbf{u}}{\partial t} + \mathbf{u} \cdot \nabla \mathbf{u}) = -\nabla p + \mu \nabla^2 \mathbf{u} + \mathbf{F}_{st}$$
Level Set输运方程: $$\frac{\partial \phi}{\partial t} + \mathbf{u} \cdot \nabla \phi = \gamma \nabla \cdot (\epsilon \nabla \phi - \phi(1-\phi)\frac{\nabla \phi}{|\nabla \phi|})$$
COMSOL中的关键设置:
Physics.create("pf", "PhaseField"); Physics.feature("pfinit").set("c0", "0.5*(1-tanh((x-1e-4)/2e-5))"); // 初始界面 Physics.feature("pfeq1").set("Gamma", 0.032*[1,0.8]); // 各向异性参数4. 网格划分策略
4.1 边界层网格配置
在孔隙壁面处设置5层边界层网格:
- 第一层厚度:0.1μm
- 增长因子:1.5
- 总厚度:约1μm
实测表明:边界层少于3层时,毛细管力计算误差可达15%
4.2 主体区域离散化
采用自由四面体网格:
- 最大单元尺寸:孔隙平均直径的1/5
- 最小单元尺寸:喉道最窄处的1/10
- 曲率因子:0.3
- 狭窄区域细化等级:2
网格质量对比实验:
| 网格类型 | 计算时间 | 界面稳定性 | 内存占用 |
|---|---|---|---|
| 纯四面体 | 2.5h | 差(易发散) | 12GB |
| 边界层+四面体 | 3.8h | 优 | 18GB |
| 六面体主导 | 6.2h | 良 | 25GB |
5. 求解器配置技巧
5.1 瞬态求解参数
Study.create("std1", "Transient"); Study.feature("time").set("tlist", "range(0,5,100)"); // 0-5秒,100步 Study.feature("solver").set("stol", "1e-4"); // 相对容差5.2 稳定性控制
Peclet数监控: $$Pe = \frac{|\mathbf{u}|h}{2\epsilon}$$ 当局部Pe>2时切换为全耦合求解器
自动时间步进:
- 初始步长:1e-6s
- 最大步长:0.1s
- 步长增长因子:1.2
6. 后处理与结果分析
6.1 滞油区识别
使用流线积分器追踪粒子轨迹:
Results.create("pt", "ParticleTracing"); Results.feature("pt").set("solnum", "auto"); Results.feature("pt").set("tlist", "range(0,5,50)");6.2 驱替效率计算
定义采收率公式: $$\eta = \frac{V_{produced}}{V_{initial}} \times 100%$$
参数化扫描结果显示:
- 注采压差0.5MPa时:η=73%
- 注采压差1.0MPa时:η=85%
- 注采压差1.5MPa时:η=89%
7. 实战经验总结
几何修复陷阱:某次自动修复将相邻孔隙合并,导致模拟的驱替速度比实验快22%。后来我们开发了孔隙连通性检查脚本:
import networkx as nx def check_connectivity(stl_file): # 构建孔隙网络图 G = nx.Graph() # ...拓扑分析代码... return G.is_connected()服务器崩溃预防:长时间计算时务必设置自动保存:
Study.feature("time").set("savetime", "0.1"); // 每0.1秒保存 ModelUtil.saveState('autosave.mph');毛细管数临界值:当无量纲毛细管数$Ca=\frac{\mu u}{\sigma}>10^{-3}$时,滞油现象显著改善。我们最终采用的注采压差1.2MPa正好对应Ca=1.2×10⁻³。
实验室的空调突然发出嗡鸣,屏幕上的两相界面正以完美的指进形态向前推进。老张盯着后处理模块显示的89%采收率曲线,终于露出了三个月来的第一个笑容。我默默把冰棍棍扔进垃圾桶——这次模拟消耗了38小时CPU时间,但相比浪费的三罐原油,这代价简直微不足道。