风电光伏配储能做互补调度,听起来像是个标准课题,但真正把模型跑起来、把代码实现出来的时候,坑远比想象中多。我最近刚完整做完了一个“风电、光伏与储能(含电池和废弃矿井小型抽水蓄能)互补调度运行研究”的Python实现版本,从数学建模到代码落地,再到结果分析,整整折腾了几周。这篇就当作一个详细的项目复盘,把我踩过的坑、关键参数怎么定、代码怎么组织,一次性讲透。
这个项目主要研究的是什么?简单说,就是在一个风、光、储互补的电力系统里,怎么安排风电场、光伏电站、磷酸铁锂电池储能系统,以及一个利用废弃矿井巷道改造的小型抽水蓄能电站,让它们在不同时间尺度上配合出力,实现“弃风弃光最少、运行成本最低、储能寿命消耗最合理”这几个目标。适合谁来看?如果你是电力系统方向的学生,正在为毕业设计或小论文发愁;或者你在新能源场站做运行优化,想把理论模型转成可复现的Python代码;又或者你只是想搞明白“抽水蓄能和电池储能到底怎么分工”这件事——这篇文章都能给你一个可以直接抄作业的答案。
1. 项目背景与核心思路拆解
1.1 为什么风电光伏必须配储能
风电和光伏的出力受天气影响极大,风速快的时候风机满发,没风的时候出力可能掉到额定功率的10%以下;光伏更直接,白天有日照才能发电,晚上出力为零。这种波动性和间歇性,给电网调度带来了一个很现实的问题:新能源大发的时候电网消纳不了,只能被迫弃风弃光;新能源出力小的时候,又需要常规电源顶上,甚至可能不够用。储能就是在这个背景下被推到台前的。
我一开始做这个项目的时候,只有一个很朴素的想法:给风电场和光伏电站加上一块大电池,白天光伏多充一点,晚上放出来用,不就解决了吗?但真正建模之后发现,事情没有这么简单。电池储能的容量有限,而且用得太狠会加速衰减;它的响应速度快,但持续放电能力受容量约束;另外一个关键问题是,不同时间尺度上的供需不平衡,单靠电池很难全部解决。这时候就需要一种“大容量、长时间尺度”的储能类型来搭配。
1.2 电池和废弃矿井抽蓄的分工逻辑
这个项目很有意思的一个设计,是把常规的磷酸铁锂电池储能和废弃矿井小型抽水蓄能放进了同一个调度框架里。很多人对抽水蓄能的印象还停留在“需要修上下两个水库、大规模投资建设”那种国家级工程上,但这里做的是一种分布式的小型化方案:利用废弃煤矿已经存在的巷道和竖井,改造出上库和下库,不需要重新征地,也不需要大规模开挖。
在调度逻辑上,电池和抽蓄的定位完全不同。电池储能响应速度是毫秒到分钟级,适合处理光伏云层遮挡、风速突然变化这类短时间尺度的出力波动;抽蓄虽然响应也有几分钟,但它更擅长做“电量搬运”——光伏中午大发的时候,把电用来抽水,到了晚高峰再放水发电,一次循环可以持续4到6个小时,单日调峰能力远超同容量的电池。
我在模型里把这两类储能的运行窗口做了时间解耦:电池以1小时为分辨率的短周期充电放电为主,抽蓄电站以日内的峰谷时段转移为主。这样设计的好处是,两个储能不能抢同一块电量,而是各管一段,最终的互补效果会比单种储能好很多。
1.3 调度运行研究的根本难题
把风、光、储放在一个系统里建模,最核心的难题不是约束条件的多少,而是不确定性怎么处理。风电功率预测不可能100%准确,光伏更是受云量变化影响。如果调度模型里用的是预测值的“点估计”,那么实际运行中一旦预测偏差大,整个调度方案就会失效。
在这个项目里,我采用的是确定性等价加鲁棒修正的处理方式:先用一组典型场景(比如晴日、多云、阴雨、大风等)生成基准调度计划,再在约束里加入旋转备用容量、储能SOC安全边界等“缓冲条款”,让生成的计划在面对实际扰动时有一定的鲁棒性。这种做法的运算量远小于完全的两阶段随机优化,但对工程场景来说已经足够实用。
2. 系统建模方法与关键技术细节
2.1 风电光伏出力模型与场景生成
风电出力模型我采用的是“风速→功率”的两段式映射。先用威布尔分布拟合当地的风速统计数据,生成一组风速时间序列,然后通过风力发电机的标准功率曲线(切入风速、额定风速、切出风速)把风速折算成出力。功率曲线的计算很简单,切入风速以下出力为0,在切入和额定之间按三次方关系增长,额定以上锁定为额定功率,超过切出风速则切机保护。
光伏出力相对更“规整”一些,我采用的是基于太阳辐照度、环境温度,以及光伏板标称效率的计算模型。标准公式是:光伏出力 = 光伏板额定容量 × 辐照度折算系数 × 温度修正系数。辐照度按日地关系计算理论值,再叠加上随机波动模拟云层影响;温度修正则是为了避免光伏板在夏天高温下效率下降导致的出力失真。
这两个模型的输出都是24小时的日前出力序列,时间分辨率为1小时。这一点必须提前说明:整个调度模型的时间尺度是日前调度(day-ahead scheduling),分辨率1小时,不考虑超短期AGC和秒级响应。如果想做多时间尺度协同,那需要在现有框架上再叠加一层日内滚动优化,工作量会大很多。
2.2 电池储能建模与寿命约束
电池储能我选用的是工程中常用的“能量型”磷酸铁锂电池模型,不考虑详细的电化学特性,而是在外特性层面建了三个维度:容量、功率、SOC状态。
电池的SOC(荷电状态)是整个调度的关键变量,我给它设置的安全运行区间是0.1到0.9。为什么不设成0到1?因为锂离子电池在过充过放边缘时,电压极化严重,内阻升高,还会加速析锂和活性物质损失,寿命衰减远比在中间区间循环要快得多。模型里还必须考虑充放电效率,我取的充电效率是92%,放电效率是95%,单次循环的往返效率约87%,这个是磷酸铁锂电池在0.5C倍率下的典型实测值。
寿命衰减这一块,很多初学建模的人容易忽略。我在目标函数里加入了电池等效循环折旧成本:每次充放电循环都会折算成寿命损耗费用,等效循环寿命按6000次计算。这样模型在优化的时候自己会有“舍不得乱用电池”的倾向,把充放电时段尽量安排在峰谷差大的时刻,而不是随意充放电。不加这个约束的时候,求解器会给出一个看似特别优秀但实际会把电池一个月干报废的方案,这一点值得特别注意。
2.3 废弃矿井小型抽水蓄能模型
废弃矿井抽蓄的建模,是这次项目里最花精力的一环。普通抽水蓄能电站的建模核心是上下库水量平衡和水头-功率耦合关系;废弃矿井的特别之处在于,它没有天然的地形落差优势,只能依靠矿井竖井和巷道的既有结构,上库设在地面,下库利用井下老巷道,落差一般在30到80米,属于典型的“低水头、大流量、小容量”类型。
在模型里,我建立了这样几个核心约束:
首先是发电功率与水头的关系约束。低水头机组的出力上限不是恒定值,而是随水头变化的。我简化成线性函数:发电时段水头高,可发功率大;水头低,可发功率减小。这样做比直接设定恒定额定功率更贴近实际。
其次是水量平衡约束。上库的变化水量 = 抽水水量 - 发电用水量 - 蒸发渗漏损失。抽蓄电站在抽水工况和发电工况之间存在水量损失和能量转换损失,整体循环效率如果按实际项目经验取,大概在65%到70%之间。我在模型里设置的是68%。就是说,抽水消耗1千瓦时电,最终放出来最多0.68千瓦时,剩下的变成了热损耗和水头损失。很多人在论文里闭着眼睛写85%的效率,实际工程中根本达不到。
还有一个容易被忽略的点是:机组启停状态约束。抽蓄机组不能像电池一样说变就变,从抽水转发电之间需要时间切换和状态准备。模型里我设置了最小运行时间和状态转换逻辑,避免优化结果出现某一个小时内既抽水又发电这种物理不可能的运行方式。
2.4 系统互补策略与目标函数设计
整个系统模型的决策变量有两组:第一组是各时段风电场、光伏电站、电池、抽蓄的出力值;第二组是电池和抽蓄的运行状态(充电/放电或抽水/发电)。在Python里,我用的是混合整数线性规划(MILP)框架来表达。
目标函数包含四个部分:系统总运行成本最小、新能源弃电量最小、储能寿命损耗最小、购电成本最小。这四块权重不是拍脑袋定的,而是通过层次分析法加场景试算确定的。初始权重下,优化结果会优先保证不弃新能源;然后考虑运行经济性;最后才考虑储能寿命。如果反过来先考虑储能寿命,结果就会倾向于少用储能,反而导致弃电率上升。
约束条件这块,除了上面讲到的各设备模型约束外,还有系统的功率平衡约束:各时段风、光、储、抽蓄的实际出力之和,必须等于系统负荷需求,不平衡的差额由外部电网交换电量补足。外部电网交互是为了让系统模型具备“买电卖电”的弹性,避免系统在极端场景下无解。
3. Python代码实现与实操过程
3.1 数据准备与预处理
代码的第一步是把所有输入数据整理成标准格式。这一步看似简单,却是最容易出问题的地方。我全部使用了CSV文件来管理输入数据,包括风电场24小时预测出力曲线、光伏24小时预测出力曲线、系统24小时负荷曲线,以及电池和抽蓄的参数表。
数据预处理里最容易踩的坑是单位统一。风电出力可能是兆瓦(MW),光伏用的是千瓦(kW),负荷用的又是兆瓦时(MWh),如果不在一开始全部统一成一套单位,后面算功率平衡的时候差之毫厘谬以千里。我最终统一使用的是以MW为功率单位、MWh为能量单位、小时为时间单位。代码里专门加了一个单位转换函数块,在数据读入后立即转换并校验数据范围。
还要做的是数据平滑和异常值剔除。实测数据经常会出现个别时刻的毛刺,比如一个0到2之间的辐照度系数突然跳到15,这是传感器故障或者通讯错误导致的。我在代码里设置了一个简单的滑动窗口异常检测:如果某个点的数值偏离前后两个点的均值超过三倍标准差,就替换为前后均值。这样处理之后模型求解的数值稳定性会好很多。
3.2 核心建模与求解器对比
建模求解部分,我对比了两种方案:一种是直接用Pyomo抽象建模再用开源求解器求解,另一种是手动构建系数矩阵调用商业求解器求解。最终我选的是Pyomo + Gurobi的组合,因为Pyomo的约束表达式写起来直观,Gurobi求解MILP的速度和稳定性在同类求解器里是最好的,尤其对带整数变量的储能启停问题有压倒性优势。
- Pyomo:负责模型框架建立、约束表达、求解器接口
- Gurobi:负责实际求解MILP问题(如果没有商业授权,可以用开源的GLPK或CBC替代,但求解速度会慢一截,尤其是状态变量的整数性约束多的时候)
核心代码里有一个函数专门处理抽蓄机组的运行状态约束,这里最关键的操作是引入了一个二元变量(0或1),表示抽蓄机组是在发电工况、抽水工况还是停机状态。这个二元变量在MILP求解时会被分支定界算法处理,所以模型整体求解时间会比纯线性规划(LP)要长一些。我实测下来,24个时段的模型规模,在Gurobi下大约5到20秒可以找到全局最优解,GLPK则需要几分钟,差距还是很明显的。
这里分享一个建模技巧:把目标函数里的绝对值项和max/min项先做线性化。比如要表达“弃电量最小”,直接写“目标函数里减去新能源出力与消纳量之差的最小化”,这是一个非线性项。我的做法是引入一个新的非负辅助变量,加上上下界约束把差值表达出来,变成线性不等式。这样整个模型才是标准MILP,能够被求解器高效处理。如果直接写非线性项进约束,求解器要么报错要么陷入局部最优。
3.3 调度结果可视化和关键指标检验
优化的结果不能只是一堆数字,必须可视化才能看出趋势。我使用了Matplotlib绘制了三张核心图表:第一张是24小时系统的功率平衡图,把风、光、负荷、储能的出力堆叠在一起;第二张是电池SOC和抽蓄上库水位的联合变化图,能直观看出两类储能的互补节奏;第三张是不弃风弃光率、系统运行成本、储能循环次数等关键指标的比较柱状图。
功率平衡图是最容易出“结果对不上”的地方。我遇到过一种情况:图中显示某一时刻的风电出力加光伏出力已经超过了负荷需求,可储能和抽蓄并没有在充电,也就是说系统“多”出来的电找不到去处。排查后发现,这是因为外部电网交互的约束设置得不严,求解器选择了向电网卖掉多余电量,这本身不违反模型约束,但从系统消纳的角度来说,卖掉新能源电并不等于弃电,指标统计口径错了。修正方法是把“弃电”定义为新能源出力超过负荷、储能充电功率和可外送功率三者之和的部分,这样才符合实际工程中弃电的定义。
关键指标检验方面,我专门写了一段代码在每次求解完成后自动计算五个指标:新能源消纳率、系统运行成本、电池等效循环次数、抽蓄日循环次数、储能综合利用率。这套指标可以横向对比不同参数配置下的调度效果,也可以直接复制到任何相似项目里使用。
4. 实测结果分析与调度效果评估
4.1 无储能、单电池、电池+抽蓄三组对比
为了验证互补调度的实际效果,我设计了三组仿真实验:第一组是无储能状态下的新能源直供系统;第二组是只配电池储能;第三组是电池加废弃矿井抽蓄联合运行。输入数据使用同一个典型日气象场景和同一个负荷曲线。
实验结果非常直观。无储能时,弃风弃光率高达28%左右,因为在负荷低谷时段(比如凌晨和午后)新能源出力无法完全消纳,又没有储能来吸纳。加了电池之后,弃电量降到12%左右,因为电池在负荷低谷充电、在高峰放电,把一部分电量搬到了需要的时候。但12%的弃电量仍然不算低,原因是电池容量有限,在午后光伏大发时段,2小时左右就会充满,之后剩余的新能源电量还是只能丢弃。
加上抽蓄之后,弃电率降到了约4.5%。为什么会有这么大的提升?因为抽蓄电站的“水库”容量远大于电池,它可以连续4小时吸收光伏电量,把电量从白天“搬”到傍晚高峰时段使用,这个时间尺度的转移能力正好弥补了电池容量受限的短板。数据对比这里我放在一张表里,方便看得更清楚:
| 配置方案 | 新能源消纳率 | 系统运行成本(万元/日) | 弃电率 |
|---|---|---|---|
| 无储能 | 72.0% | 16.8 | 28.0% |
| 仅电池储能 | 88.0% | 12.5 | 12.0% |
| 电池+抽蓄 | 95.5% | 8.7 | 4.5% |
4.2 电池与抽蓄的互补运行节奏
把电池SOC曲线和抽蓄水库水位曲线放在同一张时间轴上观察,会发现一个很有趣的互补特征。典型晴日场景下,光伏在10点到15点之间出力旺盛,系统的负荷并没有那么高。此时调度策略是:先让电池充电,SOC在11点左右到顶;然后启动抽蓄机组,把剩余电量用于抽水,上库水位持续上升。到了18点到21点的晚高峰时段,负荷抬升而光伏出力降到零,电池率先放电,因为它响应快、爬坡能力强,能迅速支撑负荷;抽蓄机组在电池放了一部分电之后接力发电,持续为系统提供稳定出力。
这种“电池打头阵、抽蓄做接力”的模式,正是互补调度想要的效果。电池做快速反应,抽蓄做长时支撑,两种储能特性互补之后,整个系统在应对日内负荷变化时表现得比单一储能从容得多。
4.3 关键参数对调度结果的影响
我还专门做了一轮灵敏度分析,分别改变了电池容量、抽蓄装机功率上限和负荷峰值三个参数,观察系统弃电率的变化规律。
电池容量从10MWh增加到30MWh时,弃电率从15%降到了9%左右,但继续增加到50MWh时,弃电率只下降了一个百分点。这说明电池容量在某个范围里存在边际效益递减:系统需要的短时功率支撑有限,容量过大了之后新增的部分很少被充分利用。
更明显的规律出现在抽蓄装机功率上。抽蓄最大出力从5MW翻倍到15MW时,弃电率从12%直接降到4%出头,效果非常显著。这是因为抽蓄的“电量搬运能力”直接受抽水功率和发电功率制约,功率越大,白天能吸纳的新能源电量就越多。这个发现对工程实践的启示是:在废弃矿井抽蓄项目的可行性论证中,装机功率的选择比库容大小对新能源消纳的边际影响更大,前期设计中应当优先保障装机规模而非一味扩大库容。
5. 常见问题排查与Python工程化经验
5.1 求解器报错与模型无解问题怎么处理
做MILP优化最怕遇到的一句话就是“Model is infeasible”(模型不可行)。第一次遇到时我几乎想删库跑路。后来总结出套路化的排查顺序:先检查约束是否矛盾,再检查变量上下界是否合理,最后检查数据单位。
最常见的原因是约束里有一个“必须满足”的硬性条件,和另一个硬性条件冲突了。比如我前面提到过,给抽蓄机组设置了“最小运行时间”约束,同时又有“某一时刻只能是抽水、发电或停机三选一”的状态约束,如果两者的时间窗口设置不匹配,求解器就会报无解。解决办法是给约束加一个“软化系数”:把最小运行时间从固定值改成基于前一时刻状态的动态判断,也就是只有在上一个时段机组处于运行状态时才强制连续运行,否则允许启停。
另一个高频问题出现在电池SOC界约束上。如果负荷曲线的峰值时段恰好出现在电池已经放空、无法继续放电的时刻,而系统又必须满足负荷平衡,模型就会无解。应急方案有两个:要么增加外电网交换功率上限,让系统可以从电网购电;要么在模型里设置一个虚拟的“切负荷”变量,用很大的惩罚系数约束它趋近于零。从工程实践角度,这里推荐第二种,因为切负荷变量可以帮助定位哪些时段最缺电,对后续扩容方案的制定也有参考价值。
5.2 数值求解精度异常与单位换算的坑
有一次仿真结果出现了一个特别离谱的现象:同一个条件下的两次运行,结果完全相同,但其中一次求解器给出的最优值比另一次小了几百块钱。这个问题花了我大半天才找到原因——是浮点数精度问题,数据量纲太小导致部分约束在求解精度内被忽略。
解决办法是对模型中的所有系数做归一化处理。把功率、能量这些动辄几百兆瓦的数字同时除以系统容量的标幺基准值,让核心变量保持在1到100这个量级。在目标函数里加一个后处理还原系数,把标幺值乘回去,得到一个真实的物理值。这个操作做完之后,同样的模型求解时间缩短了约30%,数值稳定性也好很多。如果求解出现“Gap is too small”之类的警告,不要慌,先在变量量纲上找问题,而不是去调求解器的公差参数,那通常只会把问题掩盖掉。
5.3 Python环境配置与依赖包版本的选择建议
最后聊一下Python环境和依赖包的坑。我做这个项目用的Python版本是3.9,Pyomo的版本建议选6.x以上,因为这几个新版本对求解器接口更友好,尤其是Gurobi 11版本的兼容性。依赖包尽量减少,重点只用pandas、numpy、matplotlib、pyomo这四个核心库,scipy和sklearn只在做场景聚类时用到,不影响主流程。
如果你用Windows环境做开发,我建议装一个Anaconda管理环境,直接用conda创建独立环境安装pyomo和gurobi库;如果是在Linux服务器上跑批量仿真,可以用虚拟环境加pip安装。还有一点很实用:Gurobi在默认安装后需要在命令行里跑一次licence激活授权,没激活就调用Python接口,会一直卡在许可证等待状态,看起来像程序死循环,实际上只是授权校验。开源替代方案GLPK虽然不用授权,但求解整数变量稍多的模型效率会掉得比较明显,如果你的模型规模在几百个变量之内,用GLPK跑通路径测试是完全可以接受的。
整个项目写下来,我最大的体会是:做这种互补调度研究的核心难点不在算法本身,而在对每一类设备的运行特性理解够不够深。电池不是一个大功率无限供电器,抽蓄也不只是一个“大水桶”,它们的物理特性都在约束里,而约束写得好不好,直接决定了优化结果在现实中可用不可用。把这个模型吃透之后,再往前扩展多能互补、现货市场接口之类的问题,都是在同一套框架上做增量,路会顺很多。