
搞电力系统规划的人手里没一套能跑的优化模型很多工作都只能停在拍脑袋层面。抽水蓄能更是重资产里的重资产规划期动辄十年起步几十亿上百亿的投资规模装机容量定多少、库容修多大、机组配几台每一项都直接影响项目是长期印钞还是年年贴钱。最近我把一套用CPLEX求解的抽水蓄能容量优化配置程序完整整理了一遍从模型搭建、数据准备到求解器调用、结果后处理都捋顺了学习价值和使用价值都能称得上宝藏二字。这篇文章就拆开讲讲这套程序到底解决了什么问题、CPLEX在中间扮演什么角色以及想拿它学习或做二次开发应该从哪里下手。1. 容量优化配置要算的账远不止大坝修多高1.1 抽水蓄能电站规划里的三类核心决策抽水蓄能的基本原理大家都不陌生负荷低谷时把水抽到上库储存负荷高峰时放水发电本质上是把电能转化为水的势能再转回来。但真正设计一个电站时问题远不止建个上下库这么简单。至少有三层决策需要同时考虑。第一层是容量决策。这包括发电装机容量MW、抽水装机容量MW、上下库的调节库容m³以及机组台数台。这些参数决定了电站块头有多大也决定了投资规模。第二层是运行决策。在给定的容量参数下需要在每个时段决定发电功率、抽水功率、库容状态、机组启停状态以及是否允许同时抽水和发电。运行策略直接决定电站能否赚到钱。第三层是收益核算。电站收益来自峰谷电价套利、调频备用辅助服务、促进新能源消纳带来的整体效益等同时要扣除抽水电费、运维费用和投资成本。这一层把前两层的决策折算成经济指标是最终方案取舍的依据。容量优化配置程序的核心就是把这三层决策放进一个统一的数学模型里让求解器帮我们找到最优容量组合最优运行策略最大净收益的完整方案。1.2 为什么这问题又复杂又难解从数学角度看容量配置问题属于典型的混合整数规划而且往往是大规模混合整数规划。难解的原因至少有四个。第一时间尺度跨度极大。容量决策是年度尺度的而运行策略是小时尺度的。为了准确评估容量方案的经济性通常要模拟一年8760个小时的运行过程这意味着问题的变量数直接放大到数万个量级。第二存在大量离散变量。机组台数、抽发状态切换、启停动作这些都是0-1整数变量。离散变量让问题失去了光滑可导的性质没法用常规的梯度类算法求解。第三物理过程存在非线性。库水位变化会影响水头水头反过来影响发电功率与流量的关系有些模型还涉及库容-水头的耦合项这会产生双线性项进一步抬高求解难度。第四不确定性因素多。来水量、风电光伏出力、负荷曲线、现货电价每一样都是随机变量。如果要在优化模型里显式考虑不确定性问题规模还会再翻几倍。正因为如此这类问题很依赖成熟的商用求解器。CPLEX在这里承担的角色就是对大规模混合整数线性规划/二次约束规划做分支切割求解它决定了这个程序能不能在可接受的时间内收敛到高质量方案。1.3 一套能够学习自用的程序应该长什么样我整理这套程序的初衷很朴素当学习模板时它应该让一个初学者看懂抽蓄容量优化模型长什么样、CPLEX怎么接入、结果怎么解读当自用工具时它应该让一个有项目经验的人只改数据文件和少量约束就能适配不同的项目场景。所以它的结构刻意做了模块化拆分包括数据输入模块、模型构建模块、求解配置模块、结果输出模块。每个模块都独立成文件模型部分的每个公式和代码行一一对应。这样做的直接好处是想学就逐行读想用就改参数想扩展就在对应模块里加逻辑彼此不干扰。2. CPLEX在容量配置问题里的位置求解器不是万能的但没它也不行2.1 CPLEX能解什么LP、MIP、MIQCP的基本边界CPLEX是IBM ILOG Optimization Studio的核心求解器是运筹优化领域的老牌商用软件。它支持的模型类型包括线性规划LP、二次规划QP、混合整数线性规划MILP、混合整数二次规划MIQP以及混合整数二次约束规划MIQCP。了解这个边界很重要。抽水蓄能容量优化配置程序如果做了充分的线性化近似最终模型就是一个MILP如果保留水头与流量的双线性项或者用二次函数近似效率曲线模型就会变成MIQCP/MIQP。CPLEX对这两种情况都支持这也是我坚持选它而不是某些开源求解器的主要原因。另外CPLEX提供了多语言接口常用的有OPL建模语言、Python API包括docplex和cplex这两个包、C API、Java API还支持MATLAB环境下的调用。这意味着不管你是Python党、MATLAB党还是写底层C的都能在同一套模型逻辑下使用。2.2 为什么容量配置这类问题绕不开混合整数规划回到抽蓄问题本身。机组台数是整数这没什么争议更关键的是抽发互斥逻辑——同一个水库在同一时段不能既抽水又发电抽水蓄能机组虽然有少数可以实现抽发同时运行但常规设计都是互斥的。这个约束天然需要0-1状态变量来建模。一旦引入了0-1变量整个问题就脱离了纯粹的凸优化范畴。分支定界、割平面、启发式这些算法开始登场。CPLEX在混合整数规划上积累了几十年的算法实现对大规模整数变量复杂约束的场景处理得非常成熟尤其是它对强对称性模型比如两台同型号机组之间有专门的对称性破缺处理这对我们这类机组配置模型非常实用。我自己也试过用开源求解器跑同样的模型小规模算例确实没问题但一旦把8760个时段、多个候选机组加上去求解时间差距就不是一倍两倍的问题了。在工程实际中项目周期不等人一个能在几分钟内给出1%以内最优性差距解的求解器价值完全对得起它的价格。学术环境下申请到免费许可后CPLEX几乎就是最优选择。2.3 安装与许可先把工具链处理好再谈建模很多新手卡在第一步不是模型难而是CPLEX装不上。这里把最顺的流程说一遍。第一步访问IBM官网的Academic Initiative页面用学校邮箱注册学术账号。学术许可针对在校师生是完全免费的授权期限通常一年到期续期就行。申请通过后会收到一个下载链接和许可证文件通常是一个带有cplex.studio.license信息的文件。第二步下载安装对应操作系统的ILOG CPLEX Optimization Studio。安装时注意选64位版本Python接口的位数必须和你的Python环境一致否则import cplex的时候直接报错。第三步激活许可证。在安装目录下用命令行执行python安装脚本python setup.py install老版本需要这样做新版本会有安装向导或者在IDE里指定license文件路径。装完后打开终端输入python -c import cplex; print(cplex.version)能打印出版本号就说明安装成功。第四步安装Python接口。如果用的是新版CPLEX Studiopip install cplex可以直接装上与官方版本匹配的Python包更推荐的其实是安装docplex它的建模语法更接近人类思维底层再调用CPLEX求解引擎。两条路都能走但建议新手直接选docplex理由在后文建模部分说。这里重点提醒Python、CPLEX Studio、docplex三者的版本要相互匹配。我之前遇到过Python 3.11配老版CPLEX 12.9的情况docplex能装但求解时报许可证错误最后升级CPLEX到12.10才解决。这类问题在Stack Overflow上非常常见排查优先级很高。3. 程序核心模块从数据到模型再到结果一条线串起来3.1 数据文件设计哪些是输入哪些是假设一套好用程序的数据设计直接决定后续二次开发的工作量。我在整理这套程序时把输入数据分为四类。第一类是基础参数包括规划周期年数、年贴现率、抽水效率、发电效率、库容上下限系数、初始库容比例等。这些是模型的基本物理与经济参数。第二类是时序数据包括典型日负荷曲线或全年8760小时负荷曲线、风电光伏出力系数、分时电价曲线。时序数据通常放在Excel或CSV文件中按时间顺序排列程序启动时直接读取。第三类是候选方案数据例如候选装机容量列表、候选库容列表、单位投资成本、单位运维成本。这些数据决定了模型在多大的设计空间里搜索。第四类是约束假设比如是否需要保证日调节周期要求每天结束与开始库容一致、是否允许弃风弃光、最小出力比例是多少。约束假设不同模型结果可能天差地别所以我会单独放在一个配置文件里避免每次改约束都去翻代码。3.2 目标函数全生命周期净收益的数学化目标函数是这套程序最核心的部分。抽蓄容量优化中常用的目标是全生命周期净收益最大化通俗讲就是在电站寿命期内赚到的钱减去花掉的钱贴到现在值多少钱。用公式表达就是max 收益 Σ [发电收入(t) - 抽水购电成本(t) - 运维成本(t)] - 年化投资成本其中发电收入 发电功率 × 该时段电价抽水购电成本 抽水功率 × 该时段电价。注意抽水功率消耗的电量还要扣除抽水效率因为电站从电网吸收的电只有一部分真正转化为水的势能。年化投资成本是容量决策变量的函数例如年化投资成本 (发电装机容量 × 单位发电投资 抽水装机容量 × 单位抽水投资 库容 × 单位库容投资 机组台数 × 单台机组投资) × 年化系数年化系数通过折现公式由寿命期和贴现率计算得到。这笔钱在运行收益面前必须是沉重的否则求解器会倾向把容量建到天上。很多初学者发现最优解总是取容量上限往往就是年化系数算错了或者单位的数量级不一致。3.3 约束条件水量平衡、库容、功率和抽发联动容量配置模型最关键的约束归纳起来就是四组。这四组约束缺一不可少了任何一个模型都可能给出物理上完全荒谬的方案。第一组是水量平衡约束。库容变化等于上游来水加上抽水入库量减去发电放水量和蒸发渗漏损失v(t1) v(t) q_in(t) q_pump(t) - q_discharge(t)这是整个水系统的守恒方程是模型物理可行的基础。第二组是库容上下限约束。库容必须时刻保持在死库容和最大库容之间这个约束让修多大多用多大变成现实约束也是容量优化的直接体现v_min ≤ v(t) ≤ v_max第三组是功率上下限约束。发电功率受限于发电装机容量和放水能力抽水功率受限于抽水装机容量和抽水扬程条件同时发电放水量和抽水入库量本身还要与水头、效率耦合。简化处理时常用线性关系表达p_gen(t) ≤ P_gen_maxp_pump(t) ≤ P_pump_max第四组是抽发互斥约束。用两个0-1变量表示发电状态和抽水状态并强行约束它们不同时为1u_gen(t) u_pump(t) ≤ 1此外还有日调节平衡约束即典型日运行结束后库容必须回到初始库容这对日调节电站是硬性要求。如果做周调节或长时间尺度模拟这个约束要相应调整到周期末。这套程序默认给出日调节模型同时预留了周期长度参数改成周调节只需要把24小时参数改成168小时逻辑不用大改。3.4 求解设置与输出不只要能算还要算得稳模型建完只是第一步求解设置决定了能不能稳定收敛。程序里我设置了几个关键参数MIP gap最优性差距容忍度默认设1%。即求解器找到的解与最优解的理论差距在1%以内就接受。工程上1%已经足够设置过小会大幅拉长求解时间。Time limit时间上限默认设600秒防止模型在极端复杂时无休止地跑下去。MIP emphasis设成平衡模式兼顾可行解的寻找速度与最优性证明速度。求解线程数设成并行充分发挥多核性能。输出模块会把三样东西写出来最优容量方案装机、库容、机组数、逐时段运行计划发电功率、抽水功率、库容变化、经济指标总收益、投资、回收年限。这些结果直接落到Excel或CSV里方便后续绘图和汇报。4. 一个小型算例风电场配两座抽蓄库容的配置验证4.1 算例基础参数理论讲再多不如跑一个能复现的小例子。这套程序配套了一个小型算例某风电场额定容量100MW现拟配套建设一座抽水蓄能电站目标是通过峰谷电价套利和减少弃风提升风电整体收益。基础参数如下表参数数值说明风电额定容量100 MW模型外给定规划周期30 年电站寿命年贴现率8%投资成本折现发电效率0.85水能到电能抽水效率0.90电能到水能综合效率0.765往返循环效率峰段电价0.8 元/kWh10:00-12:00、18:00-20:00平段电价0.5 元/kWh其余白天时段谷段电价0.25 元/kWh23:00-06:00发电机组候选20, 40, 60, 80, 100 MW离散候选库容候选50, 100, 150, 200 万m³离散候选发电机组单位投资2500 元/kW折算到年化库容单位投资35 元/m³折算到年化日运行模拟用24个时段每个时段1小时。风电出力按典型弱风日数据取目的在于考察电站对弃风的缓解能力。4.2 模型结果与业务解读模型跑完最优方案是发电装机50MW、抽水装机50MW模型默认发电与抽水装机容量绑定可分别设置、库容100万m³。全年运行时电站日循环策略非常清晰夜间谷段抽水早高峰和晚高峰放水发电午间平段视风电出力情况小功率补发。经济指标上项目年化收益约1250万元扣除年化投资成本约580万元净年收益约670万元。如果按静态估算约7年左右收回增量投资考虑到抽蓄电站寿命通常可达50年以上这个项目在经济上可行。这个算例的价值在于它能直观告诉学习者容量配置的结果不是孤立堆参数而是运行策略和投资成本之间反复博弈后的平衡点。库容从50万m³增加到100万m³时调节能力显著提升但从100万m³加到200万m³收益增量急剧减小因为每天只需要调峰6到8个小时多余库容等于闲置资金。想让求解器自己领悟这一点就得靠之前说的年化投资约束起作用。4.3 验证环节最容易忽略的物理一致性检查求解结果出来不能直接用必须做物理一致性检查。我自己习惯做三件事。第一件事核对水量平衡。把输出结果里的逐时段库容变化、抽水量、放水量重新代入水量平衡公式误差不能超过0.1%。模型内部有约束保证这一点但数据后处理时如果做了插值或重采样可能会引入误差。第二件事核对功率对应关系。检查发电时段是否有发电流量数据抽水时段是否有抽水入库流量数据水电转换关系是否符合效率设定。曾经遇到过一次结果里发电功率为40MW但按放水量和效率反算只能支撑35MW的情况最后查出是效率单位写错——把kWh/m³和MW·h/m³混用了。第三件事核对经济指标。把收益、成本拆分进行验算确认峰段发电量乘以峰段电价确实等于收益项谷段抽水电量乘以谷段电价确实等于购电成本项。这一步能拦住90%以上的单位错误。5. 从跑通到自用一套可执行的二次开发路线5.1 阶段一看懂数据、看懂结果不急着改代码拿到这套程序第一件事不是立刻改代码而是把现有算例从头到尾跑通三遍。第一遍不输出任何修改只看运行日志理解求解过程感受MIP gap从100%逐步下降到1%以内的过程。第二遍打开输入数据文件把每个表格和程序读取代码对照读一遍搞清楚每个字段在模型中的作用。第三遍盯着结果文件把每个输出与模型里的约束条件和目标函数对应上。我见过很多人上来就删掉一半约束结果模型不可行又来抱怨程序有问题。实际上模型本身很挑剔每个约束都是某种物理或经济逻辑的数学表达。理解之后再动手效率会高得多。5.2 阶段二参数敏感性与维度扩展跑通默认算例后最值得做的练习就是参数敏感性分析。做法非常简单保持模型不变循环修改某一参数例如电价峰谷比从3倍改到5倍观察最优容量如何变化。固定其他参数把峰谷电价差从0.5元/kWh逐步提高到0.7元/kWh你会发现最优装机容量从30MW逐步爬升到80MW。这个趋势在物理上很好解释电价差越大调节能力越值钱电站越值得装大。用这套程序做敏感性分析不需要改任何代码只要批量生成输入文件再运行即可。想更进一步可以把模型从日调节扩展成周调节。修改周期参数从24到168同时把输入数据从典型日24个时段扩展为连续168个时段。这里要留意的是约束条件里的日平衡必须改成周期平衡否则上下库一周下来的水量无法自洽。扩展之后的求解时间会明显上升这时正好练习如何通过收紧MIP gap和设定时间限制来控制求解规模。5.3 阶段三周调节、随机风光出力与不确定性建模再往深走一步就到了不确定性建模。现实中的风电出力不可能是一条固定的典型日曲线它每天都在波动。处理不确定性的常见做法是场景法从历史数据中抽取几十个典型场景把每个场景都跑一遍运行模拟以期望收益作为目标函数。两阶段随机优化的思路是第一阶段做容量决策不随场景变化第二阶段做运行决策每个场景独立响应。模型表达式会变成max 收益 Σ_scenario weight(s) × 运行收益(s) - 年化投资成本约束里容量变量对所有场景共享运行变量按场景分别定义。这是一个标准的随机规划结构CPLEX求解时依然高效。唯一要注意的是场景数不能太多否则变量膨胀会让求解时间失控。常用做法是先做场景缩减把50个场景聚成5到8个典型场景再用这8个场景跑优化。5.4 阶段四把模型封装成自己的工具当模型逻辑基本稳定后就该考虑自用了。我会建议把整个程序封装成一个Python函数输入是一堆参数和DataFrame输出是容量方案和运行结果。封装之后可以做很多延伸。比如写一个循环脚本自动扫描几十组参数组合生成容量配置对比报告或者接入遗传算法做外层搜索让优化算法和CPLEX嵌套处理更大规模的问题。还可以把结果自动生成图表和Word报告直接给领导汇报。到了这个阶段这套程序才算真正变成你的工具而不只是跑演示算例的模板。我自己就是靠这个思路把同一套抽蓄容量配置逻辑应用到了多个不同项目中每次只改数据文件模型部分长期保持稳定。6. 调试这类混合整数规划模型时我踩过的坑和通用解法6.1 模型不可行先查矛盾的来源别急着改求解器遇到模型不可行infeasible的报错我的第一反应永远不是怀疑CPLEX出问题了而是怀疑某个约束之间打架了。抽蓄容量配置模型中最常见的冲突来源有三个。第一个是库容上下限冲突。如果死库容设得过高而初始库容落在死库容之下模型直接不可行。第二个是日调节平衡约束与发电收益目标冲突。每天被迫回到初始库容而库容上限又太小导致夜里想抽水白天想发电都施展不开。第三个是功率上下限与水量平衡的配合问题尤其是抽水入库流量极端大而库容极小一个时段的抽水量就能让库容越上限。排查时我最常用的手段是逐条松开约束。先把日平衡约束去掉看模型是否可行不可行就再松库容上限。用这种二分定位法通常几分钟就能锁定矛盾的约束组合。CPLEX也提供了冲突分析工具在docplex里调用get_conflict方法可以自动给出互相矛盾的最小约束集合建议每个学习者都试一遍。6.2 求解超时0-1变量膨胀和Big-M选取的影响求解超时是混合整数规划模型最常见的问题原因一半出在模型规模一半出在建模质量。规模方面0-1变量是求解器的大敌。抽蓄模型里每个时段两个状态变量24小时就是48个0-1变量这不算多但全年8760小时就变成17520个0-1变量求解难度指数级上升。应对方法一是在保证精度前提下用更粗的时间分辨率二是把已经在结果中确定的机组状态固定下来缩小搜索空间。建模质量方面最大坑是Big-M系数选取不当。比如某条约束里用了1000000作为M值而其他变量量级只有1000CPLEX在数值计算时会出现严重的数值病态问题——表现为求解过程反复振荡、gap无法下降甚至给出违反约束的解。我自己遇到过最典型的问题是库容-功率耦合约束里的M取太大导致模型解出的库容过程有明显毛刺。解决办法就是把M尽量收紧取该约束物理含义下的最小可行值通常用所有变量上限之和再加上一个安全余量。双线性项的处理是这个模型里另一个容易让新手懵的地方。抽水蓄能的水头随库容变化发电功率与发电流量、水头之间存在乘积项。如果坚持精确建模模型就变成MIQCP。CPLEX能处理这类问题但求解代价高。工程上我更推荐做线性化处理当库容变化幅度不大时把平均水头视为常数乘积项直接消失模型退化为MILP求解速度大幅提升。如果问题规模很大又必须考虑水头变化McCormick松弛是一个经典等价替换方案把双线性项的上界和下界用线性约束包络起来代价是目标函数变松MIP gap的收敛会更慢。6.3 量纲归一化和单位统一最不起眼却最致命的坑这一类坑如果没人提醒新手能连续踩一个月。我在整理这套程序时特意把量纲核验做成了一页检查清单功率统一用MW能量用MWh电价统一用元/MWh或元/kWh但记得换算时差1000倍。库容用m³流量用m³/h时间单位统一为小时。效率统一无量纲比例不要出现0.85和85%混用。年化系数要正确换算LIC r(1r)^N / ((1r)^N - 1)其中r是年贴现率N是寿命期年数。一旦单位混了最典型的症状就是模型结果看着离谱但搜不出语法错误。比如投资容量选了上限收益却为负又比如库容过程每天剧烈震荡。遇到这类情况第一件事就是逐项核验单位这比分析模型约束更快。6.4 数值稳定性量纲归一化是关键最后说一个高阶但非常实用的建议建模之前对数据进行归一化处理。把发电功率除以基准容量库容除以基准库容电价除以基准电价让所有决策变量的数值落在0到5的合理区间内CPLEX的数值求解会稳定很多。归一化在数学上不改变最优解但直接影响求解器的算法内部容差判定。我对比过同一模型归一化前后的表现归一化后求解时间平均缩短约30%数值警告几乎消失。这套程序里所有输入数据都在数据模块中先做归一化模型求解后再反归一化回真实量纲输出结果。这个设计虽然多写了几个函数但长期使用下来收益巨大。另一个稳定性技巧是给求解器提供好的初始可行解。用启发式方法比如按谷段抽满、峰段放光的简单规则生成一组初始运行方案作为MIP start传入CPLEX可以显著加快MIP gap的下降速度。尤其在大规模算例中这个技巧几乎是必备的。我前前后后用这套程序跑过上百个算例最大的体会是容量优化这类模型瓶颈往往不在求解器性能而在建模者对物理过程的理解程度。公式里的每个符号都对应真实世界里的水、电、钱理解得越透模型建得越干净求解也越快。如果你正准备学抽水蓄能优化或者手头有个容量配置的活要干不妨先拿这套程序把一个算例彻底啃透再谈扩展。最后建议你从把日调节模型改成周调节模型这个小目标开始这一步做通了你对这整套东西的掌握程度会有一个质的提升。