1. 项目概述:当优化建模遇上地质封存
做能源优化这行的朋友,最近应该都注意到了GAMS和TotalEnergies在碳封存领域合作的消息。乍一看这可能只是条普通的企业新闻,但往深了想,这其实是数学优化软件与能源巨头在减碳路径上的一次关键耦合。GAMS全称General Algebraic Modeling System,在运筹学、能源系统规划、经济学建模领域摸爬滚打了几十年,而TotalEnergies作为欧洲能源巨头,旗下拥有大量油气田开发与CCS(碳捕集与封存)项目,这两家凑到一起,核心目标就一件事:把CO2往地下注入的整个过程,用数学优化模型算到最优。
碳封存优化这个事,很多外行听起来觉得简单——找个废油田,把二氧化碳打进去不就完了?但实际工程远不是这样。注入井的井位布置、注入速率、压力窗口、地层孔隙度、渗透率非均质性,以及多井同时注入时的干扰问题,每一样都是变量。更麻烦的是,CO2注入不是一次性完成,而是持续几十年的事,这期间地层压力会积累、气体会运移、可能还会与地层水反应生成碳酸盐。要在这个动态过程中找到一个兼顾安全性、经济性、封存效率的最优方案,靠人工经验和试错是行不通的。GAMS这类建模工具的价值,就在于把这些物理约束、工程约束、经济约束全部转成方程组,交给求解器去算,让决策建立在可量化、可复现的模型结果上。
这篇东西适合谁看?我觉得至少有四类人:一是做CCS项目前期规划的技术人员,想了解优化模型怎么参与注入方案设计;二是用GAMS做能源系统建模的研究生和工程师,想看看这个工具在碳封存场景下的具体打开方式;三是做油藏数值模拟的朋友,想知道怎么把模拟器输出变成优化器输入;四是单纯关注减碳技术路线的读者,想搞明白“优化”两个字在碳封存项目里到底优化了些什么。
2. 碳封存优化为什么要用GAMS,而不是直接写Python
先说一个经常被问的问题:现在Python这么万能,TensorFlow都能跑,为什么还要用GAMS这种“老古董”?
答案其实不复杂,主要是三个层面的考虑。
第一,约束建模的表达效率。碳封存优化本质上是一个大规模非线性约束优化问题,涉及注入井数量、注入速率、井底压力、破裂压力、累计注入量、长期稳定性评估等几十上百个约束条件。Python里你要用SciPy或Pyomo写这些约束,代码量大不说,查错也是个麻烦事。GAMS的设计哲学就是“数学表达式直接写”,约束条件和目标函数直接用接近数学教材的语法声明,模型结构一眼看去清清楚楚。对于工程审查和团队协作来说,这种透明性极其重要——甲方审你的模型,看的是你约束条件列得对不对,而不是一个个翻你的Python函数。
第二,求解器对接的便利性。GAMS自带一个求解器接口层,CONOPT、IPOPT、BARON、CPLEX这些主流的商业或开源求解器都能直接调用。做碳封存优化的人都知道,同一个模型,用CONOPT解可能遇到数值困难,换IPOPT就收敛了,这很常见。GAMS的机制是你只需要写一行option语句,求解器随便换,不需要改动模型主体。Python里如果用Pyomo也能实现,但要处理各个求解器的许可证、API接口差异、参数传递方式,工作量明显上了一个量级。
第三,大规模情景分析的支撑能力。碳封存优化逃不开敏感性和不确定性分析——注入速率上下浮动10%,成本曲线怎么变?地层渗透率降低20%,最优井位和注入方案怎么调整?GAMS里做这类情景分析很方便,通过参数映射和循环控制,可以批量跑几十个甚至上百个case,每次修改参数、调用求解器、输出结果都是自动化的。工程师要做的只是把跑完的结果收集起来做KPI对比,这个对项目前期的方案比选来说价值非常大。
当然,我不是说Python没用。实际上我自己的经验是:GAMS负责建模型和求解,Python负责前后处理和数据可视化,两者结合才是最高效的配置。GAMS用GAMS的建模语言写优化问题,结果导出成gdx文件或者csv,再用Python的pandas读进来画图、做对比分析。这个工作在实操中已经成为标准流程了。
3. 碳封存优化模型的核心数学结构
要理解GAMS在碳封存优化里具体干些什么,得先把它要解决的数学问题看懂。
3.1 目标函数:什么才算“最优”
碳封存项目的优化目标不是唯一的,根据项目阶段不同,目标函数也不同。
最常见的目标是最小化单位封存成本($/t CO2),计算公式拆开来看就是:
[ \min ; C_{total} = \sum_{i=1}^{N} \left( C_{cap,i} + C_{inj,i} \right) / Q_{total} ]
其中 ( C_{cap,i} ) 是第i口注入井的资本支出,包含钻井、完井、井口设备、管线连接等费用;( C_{inj,i} ) 是运营支出,主要是注入泵耗电、CO2输送、监测费用等;( Q_{total} ) 是封存总量。
有些项目更看重封存效率,这时目标函数会变成在给定井数条件下最大化累计注入量:
[ \max ; Q_{total} = \sum_{t=1}^{T} \sum_{i=1}^{N} q_{i,t} \Delta t ]
( q_{i,t} ) 是第i口井在第t个时间步的注入速率,( \Delta t ) 是时间步长。这两种目标在实际项目中往往要平衡——一味追求低成本可能牺牲注入效率,一味追求注入量又会让成本失控。所以GAMS模型里经常做成多目标优化,用加权法把两个目标合成一个:
[ \min ; \omega_1 C_{total} - \omega_2 Q_{total} ]
权重的取值就是工程决策的体现了,这个在后面讲参数设置时再展开。
3.2 约束条件:安全与物理的双重红线
约束条件是碳封存优化模型的核心,也是GAMS建模最花功夫的地方。
第一类是压力窗口约束。注入井的井底压力必须高于CO2达到超临界态所需的最小压力(一般对应7.38 MPa临界压力),同时绝对不能超过地层破裂压力,否则地层就裂了,CO2可能沿着裂缝快速逃逸。这个约束可以写成:
[ P_{min} \leq P_{i,t} \leq P_{frac} ]
( P_{frac} ) 是破裂压力,通常由测井资料和地应力实验获得。注意CO2的临界温度是31.1°C,在地下大多数深度都能满足,但压力必须够,所以注入泵的功率设计很大程度上就是受这个约束牵制的。
第二类是地层层位约束。井轨迹设计出来以后,完井段的射孔位置必须落在目标储层内,不能打到盖层里去。在建模里体现为对注入层位索引的取值范围约束,属于整数决策变量。
第三类是地面处理能力约束。CO2来源是捕获站,来源的压力和流量有一定波动范围,注入系统必须兼容这个范围。也就是 ( q_{i,t} ) 的上下限要和上游捕获装置的产气曲线对齐。
第四类是封存量约束。累计注入量不能超过地层的理论封存容量,这个容量由孔隙体积、CO2密度和不可动气饱和度决定:
[ \sum_{t=1}^{T} \sum_{i=1}^{N} q_{i,t} \Delta t \leq M_{total} = A h \phi (1 - S_w) \rho_{CO2} ]
这里 ( A ) 是储层面积,( h ) 是储层厚度,( \phi ) 是孔隙度,( S_w ) 是含水饱和度,( \rho_{CO2} ) 是CO2在地层条件下的密度。这个公式看起来简单,实际做的时候孔隙度和含水饱和度在平面上是非均匀的,所以要按网格分区输入,模型规模一下子就上去了。
3.3 为什么是全物理模拟+优化模型,两者怎么衔接
说到这里有个绕不开的问题:GAMS本身不解决CO2在地下的流动问题。如果用GAMS做注入速率优化,地层压力响应怎么计算?
这里其实有两条技术路线。一条是把油藏模拟器(比如CMG、Eclipse、TOUGH)当成全物理的“黑盒响应函数”,GAMS在优化迭代过程中反复调用模拟器,跑出压力分布后评估约束是否满足、目标函数是否收敛。这种耦合方式实现起来复杂、计算量大,但精度高。另一条是常见的做法——先离线用油藏模拟器生成代理模型(surrogate model),比如跑几十组不同注采方案,拟合出井底压力随注入量变化的响应方程,然后把代理模型嵌到GAMS的约束里。这样既保证了模型实用性,又避开了直接调模拟器搞耦合的工程灾难,实际项目里这是最稳妥的路径。
TotalEnergies在公开技术交流里也强调过类似思路:优化模型要做的是决策空间的快速搜索,而不是替代高精度模拟器。GAMS的价值不是自己算出地下流体的命运,而是在模拟器提供的响应关系基础上,帮你把几十口井、几十个时间步、几万个参数组合的决策空间搜索到接近最优的点。
4. 实操:在GAMS里搭建一个碳封存注入优化模型
光讲原理不干活,那是学院派干的事。这一节我把模型搭建的完整流程拆开来,每一步都给出可以照搬的代码和参数设置思路。
4.1 数据准备阶段
先别急着开GAMS,数据准备好没有是第一关口。一个典型的碳封存优化案例需要四类数据。
第一类是地质参数。储层深度、厚度、孔隙度分布、渗透率分布、含水饱和度、温度压力梯度、地层破裂压力。这些数据来自测井解释和地质建模,通常已经整理成Petrel或Eclipse的网格属性文件。实操里要做的不是直接导入,而是先按优化模型的网格分区要求做粗化——比如油藏模拟器用的是50x50的精细网格,优化模型不需要这么细,可以按工程分区聚合成6到9个区,每个区用平均孔隙度、平均渗透率代表。
第二类是流体物性参数。CO2在储层温压条件下的密度、粘度、溶解度,以及溶解CO2后卤水密度变化等。这些可以查公开数据库,比如NIST的流体物性查询,或者用BWRS状态方程直接估算。注意CO2密度和深度(压力)、温度直接相关,不同项目差别很大。
第三类是工程参数。备选井位坐标、注入泵功率上限、井口最大允许压力、单井最大日注入量、建设期与运营期成本参数等。
第四类是经济参数。钻井成本(按深度折算)、完井成本、每年运行维护费率、碳税或碳信用价格、折现率、项目寿命(一般按20到30年规划)。碳价这个参数对最优方案的影响极大,后面做敏感性分析时会专门讲。
4.2 GAMS模型代码结构解析
下面给一个简化的碳封存井位与注入优化模型框架。实际项目的模型规模比这个大得多,但结构上是一致的。
$title CCS Injection Optimization Model $ontext Simplified CO2 storage optimization model Well placement and injection rate allocation $offtext Sets i "potential injection wells" / w1*w6 / t "time periods" / t1*t10 / p "reservoir zones" / zone1*zone3 / Parameters depth(i) "well depth (m)" / w1 1800, w2 1850, w3 2100, w4 2200, w5 1950, w6 2000 / capInt(i) "injection capacity per well (kt/year)" / w1 500, w2 500, w3 450, w4 450, w5 550, w6 550 / cpCost(i) "capital cost per well (MUSD)" / w1 12.5, w2 13.0, w3 16.5, w4 17.0, w5 14.5, w6 15.0 / opCost "annual operating cost ratio" / 0.04 / Pfrac "fracture pressure (MPa)" / 28.5 / Pmin "minimum injection pressure (MPa)" / 8.5 / discount "discount rate" / 0.08 / duration "project life (years)" / 20 / carbonPrice "carbon credit price (USD/t)" / 35 / Scalars qmax "max total annual injection (kt/year)" / 2500 / nmax "max number of wells" / 4 / Variables x(i) "well selection binary" q(i,t) "injection rate (kt/year)"; Binary Variable x; Positive Variable q; Equations obj "minimize total cost" maxWells "limit number of wells" maxInj(i,t) "capacity constraint per well" minInj(i,t) "minimum injection rate if selected" totalInj "minimum total storage requirement" pressureCons "pressure upper bound constraint"; obj.. sum(i, cpCost(i)*x(i)) + sum((i,t), opCost*cpCost(i)*x(i) + carbonPrice*q(i,t)/1000) =E= z; maxWells.. sum(i, x(i)) =L= nmax; maxInj(i,t).. q(i,t) =L= capInt(i)*x(i); minInj(i,t).. q(i,t) =G= 100*x(i); totalInj.. sum((i,t), q(i,t)) =G= 20000; pressureCons.. sum(i, q(i,t)) =L= 2800; Model ccsOpt /all/; option minlp = dicopt; Solve ccsOpt using minlp minimizing z;几处关键点值得展开说说:
**Binary变量x(i)**是井位选择的开关变量。1表示建井,0表示不建。为什么要用整数变量?因为工程上不存在“建半口井”的概念,井要么建要么不建,这是一个典型的整数决策问题。加上x(i)后模型从LP/NLP变成了MIP/MINLP,求解难度明显提升,但对于实际项目来说,这个整数约束必须保留——工程师不可能根据0.7口井的优化结果去写可行性报告。
minInj(i,t)约束的含义是:如果选了这口井,它的年注入量至少要有100 kt。为什么不用0作为下限?这是为了避免数学上出现“选了井但注量为0”的无意义解。之前我就见过团队跑出来的方案显示某口井被选中但注入量为零,这就是规模病态的表现,这种约束就是避免这类问题的手段。类似的技巧在整数规划里叫big-M法约束,作用是建立连续变量与整数变量的逻辑关联。
pressureCons约束恒量了每期的总注入量上限。模型里这个值是拍出来的,但实际项目中它的来源就是前面提到的代理模型。具体做法是先用油藏模拟器跑几个不同注入总量下的情景,得到对应的井底压力,然后拟合一条“总注入量-最大井底压力”的响应曲线,把压力安全窗口换算成注入总量的可行区间,写进优化模型。这样既能保证优化结果工程上可行,又不需要在GAMS里直接解流体力学方程。
4.3 求解器选型与参数调优经验
这个模型是MINLP类型(因为有二进制变量x,同时约束里有一些非线性项),求解器选择就是决定成败的事了。
我习惯的配置是:先用DICOPT跑一遍外近似法,快速拿到一个可行的次优解,了解解空间的量级;然后用SBB做分支定界搜索,尝试进一步逼近全局最优;如果模型规模大或者非线性强,会考虑用外部求解器如BARON做全局优化,但这个很考验模型规模,通常只有中低维问题跑得动。
求解器参数方面,最常用的调整是:
option reslim,设定求解时间上限。碳封存项目的决策频率不高,前期规划阶段一个case跑半小时甚至一晚上都无所谓,所以可以放宽到3600秒甚至更高。但如果要做几百组敏感性分析,建议单个case控制在300秒以内,否则批次任务总时长会失控。option optcr,设定相对最优间隙。默认值是0.1,表示停止条件是最优解和最优界的差距小于10%。做方案比选阶段可以放宽到0.15甚至0.2,跑得快很多;到最终方案确认阶段,回到0.01甚至0,确保拿到的解接近真正的最优。option mip_cplex相关的参数,如果用的是CPLEX求解MIP部分,建议关闭前置启发式,或者至少保持默认。这个听起来反直觉,但有实际教训——某些情况下启发式拿到的初始解会误导分支方向,导致后期搜索效率变低。
另外有个实战小技巧:先跑一个去掉整数约束的连续松弛版本。这个版本直接LP/NLP求解,速度快得多,得到的目标函数值可以作为MIP问题的下界参考。如果后面整数解的目标值比松弛解高出太多(比如超过20%),就说明整数约束对方案的影响很大,可能要考虑增加备选井位来降低整数约束带来的自由度损失。
4.4 典型优化结果解读
我们以一个简化的案例来说明怎么读优化结果。假设给了6口备选井,编号w1到w6,经济参数和深度如上表,项目目标是20年累计封存2万吨CO2,但受碳价约束,既不能超标太多也不能不够(实际上CCS项目的补贴或碳信用收入通常在目标函数里以负成本形式体现)。
跑完DICOPT后的典型输出大约是这样的:
- 选中的井是w2、w4、w5,w1被排除的主要原因是它与w2的井位距离过近,同时投产会造成压力干扰,而约束maxWells限制了最多只能选4口。
- 各井注入量分配不是平均的,w5因为深度适中、渗透率条件好,承担了更大的注入份额;w4深度最浅但资本成本低,年注入量设在中位水平。
- 总成本分解出来,资本支出占比约35%,运行维护占25%,二氧化碳捕集压缩前段分配来的成本占40%。如果碳价从35美元涨到60美元,优化模型会倾向于选择更多井、更大注入量,因为封存收入增加抵消了额外建设成本。
这个结果说明了一件很重要的事:优化不是简单地“多注多得”,而是在边际成本和边际收益的交点上做取舍。碳价上行时封存变成了肥肉,项目会扩张;碳价低迷时,项目会收缩到刚好满足合约下限的规模。这就是为什么行业里常说,碳封存项目方案好不好,一半取决于地质条件,一半取决于碳价预期。
5. 常见问题与排查技巧实录
在实际碰这个题目的过程中,坑真的不少。这一节把我自己踩过的、以及同行交流中反复出现的几类问题整理成速查形式,希望对大家有实际帮助。
5.1 模型不收敛怎么办
症状:求解器报“infeasible(不可行)”,或者迭代次数打满都达不到收敛条件。
排查思路:首先检查约束是否自相矛盾。最常见的情况是maxWells限得太紧,但同时totalInj的封存量目标又定得太高——几个备选井的容量加起来都不够目标值,那就必然无解。把totalInj目标逐步降低,看模型到哪个值开始有可行解,能反推出现有井位方案的实际最大封存量。
其次要检查数值尺度。GAMS对数值尺度比较敏感——如果目标是6000,但约束数值在0.01量级,求解器的容差设置就会造成困扰。碳封存模型里深度是1800米,成本是12.5百万美元,注入量是500千吨/年,这些数值本身在同一量级,一般不严重,但要小心碳价和成本的乘积扩大到百万级后跟压力值混在一起。我习惯用集合p来统一做成本量纲归一化,比如所有金额除以1000统一成“百万美元”,所有注入量除以100统一成“十万吨”,能有效减少数值病态。
5.2 求解时间过长
症状:模型在MIP分支上卡住,intelligence不明显。
对策:先看问题规模。备选井数量从10口增到30口,二进制变量的组合空间是指数增长的,30口井意味着2的30次方种组合,暴力搜索必死。这时候必须加启发式约束剪枝,比如根据空间距离预设“如果选了井A则不选井B”的互斥关系,可以大幅缩小分支树。
另一个技巧是把时间维度粗化。如果是10年规划,可以先按年为单位跑一个粗模型,拿到井位选择的整数解,再把这个解固定下来,把时间维度细化到季度或月做连续变量的精细优化。这种“两阶段求解法”在工程实践里屡试不爽。
5.3 优化结果不符合工程直觉
症状:模型给出的最优解里,某个注入参数看起来“不合理”,比如让某口浅层井承担了大量注入,而工区东侧地质条件明显更好的井却没被选中。
排查方向:别急着怀疑求解器,先查参数输入。最容易出错的几个点:
- 渗透率数据在从Petrel导出时有没有做单位换算(毫达西换算成平方米)?
- 地层压力参数用的是初始静压还是考虑了注入后的增压?如果用初始值代替生产后的动态压力,压力约束全是错的。
- 经济参数里有没有重复计算成本项,比如钻井成本被同时算进资本支出和固定运营支出里?
我遇到过最离谱的一次,是地质模型里两口井的坐标单位弄反了(一个用米一个用千米),导致空间距离计算差了1000倍,模型自然选择了“距离很远”但实际上位置重叠的两口井。所以拿到结果第一件事,是把选中的井位投到地图上,用空间分布做一次人工验证,这个习惯能帮你省掉大量排查时间。
5.4 敏感性分析结果解读的陷阱
做碳价、渗透率、注入能力的单因素敏感性分析时,经常看到“关键参数从A变到B,方案从方案一跳到方案四,中间没有过渡方案”的现象。这不是模型出bug了,而是整数决策变量的特性——井位选择是离散的,参数微调可能不会改变井位方案,但到达临界点后方案会整体跳变。这个现象说明项目决策边界对某个参数非常敏感,反而值得重点关注:如果碳价波动10%就会导致整个最优方案推翻,说明项目抗风险能力差,需要留安全余量。
6. 写在后面:优化模型在碳封存项目中的位置
我在实际操作中的一个感受是:GAMS在碳封存项目里只是引擎,整个工作流的价值取决于前端数据质量和后端工程解读。数据没整明白,再牛的求解器也算不出能落地的方案;结果出来了没人能读懂约束卡在哪里、边界在哪套参数下容易被突破,优化就算白做了。
TotalEnergies这类大型能源公司与GAMS合作加速碳封存优化,背后传递的信号很明确:在成本压力越来越大的背景下,碳封存项目不能只靠传统油藏工程经验来选址布井,量化优化会成为标配工具。对于正在往这个方向靠的年轻工程师,我的建议是别急着学某个具体软件,先把目标函数、约束条件、整数变量、非线性这些运筹学的基本功打扎实,再去碰GAMS或者Python,你会发现工具上手比你想象中快得多。地质上多了解点沉积相、断裂系统、储层非均质性对注入方案的影响,跟优化建模结合起来的产出,远比单一技能栈值钱。
最后分享一个实操小经验:给领导汇报优化模型结果时,不要一上来就抛公式和代码。先把不优化和经验方案的对比成本曲线摆出来,再解释约束条件是怎么保证安全和工程可实施的,最后放一张最优井位部署图。这个顺序我试过很多次,通常能在十分钟内让非技术决策者听懂优化模型的业务价值。