尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

分段损耗与阶梯碳价下的储能优化:MILP建模与Python实战

分段损耗与阶梯碳价下的储能优化:MILP建模与Python实战 1. 这个模型到底在解决什么从一次储能投资测算的翻车说起先说个我自己的经历。去年帮朋友校核一个工商业储能项目的收益测算对方给过来的Excel模型逻辑很完整峰谷价差套利、循环次数、衰减率、IRR全都有唯独对储能系统的损耗处理极其天真——充放电效率全年统一取92%损耗按固定比例折算。我拿实际运行数据一套发现真实场景里PCS变流器在低负载率工况下效率可能跌到85%以下电池模组在不同SOC区间内阻差异也大再加上夏季高温散热辅机功耗飙升全年下来系统综合效率比92%低了三四个点。别小看这几个点在峰谷价差不到0.7元/kWh的地区直接能把一个本子算平的项目打到亏损。这个翻车让我意识到一个问题储能优化模型要落地损耗不能只当常数处理。而如果损耗项做精细了顺带就该把需求侧响应、阶梯碳价这些以前被简化的因素一起纳入否则模型算出的调度指令和真实经济运行逻辑还是两张皮。这篇文章就围绕我自己整理的一套模型展开基于分段损耗与需求侧响应的多源协同阶梯碳价储能优化附Python代码实现。适合正在做储能调度、微网优化、碳交易成本核算方向的同学参考也适合刚入门优化建模的读者拿来当一份完整的MILP混合整数线性规划实战案例。2. 模型搭建分段损耗、阶梯碳价、需求侧响应的数学化2.1 从单效率常数到分段损耗三条损耗曲线的处理逻辑储能系统损耗主要分三块电池本体损耗与充放电功率、SOC相关、PCS变流损耗与负载率相关、辅助系统损耗大致恒定制冷、BMS、照明等。大多数教科书模型把这三块合并成一个固定效率比如充90%、放92%求解是省事但做工程测算误差很大。我采用的方案是分段线性化处理将充放电功率区间划分为若干段每一段内损耗率近似线性。设储能额定功率为 P_rated充电功率区间划分为 K 段第 k 段的充电功率上界为 P_k对应损耗系数为 η_k则实际充电功率从电网/电源侧吸收的功率P_in 和存储到电池的功率 P_bat 之间满足P_in P_bat / η_k, 当 P_bat 落在第 k 段区间同理放电时 P_out P_bat * η_kP_bat 为电池释放的功率P_out 为实际送出的功率。为什么用分段而不用二次曲线因为二次曲线进模型后带非线性项求解要动用NLP非线性规划求解器而分段线性化只需要引入一组 SOS2特殊有序集合或二进制变量整个问题仍然是MILP开源求解器就能处理。对很多实际应用来说三段损耗区间低负载段、中负载段、高负载段已经能逼近真实效率曲线误差控制在1.5%以内。2.2 阶梯碳价的线性化处理阶梯碳价是碳排放配额成本递增的机制免费配额内不产生碳成本超出部分按区间分档计价档位越高单价越贵。这在数学上是一个分段常数函数也是非线性的但通过引入二进制变量可以精确线性化。设总碳排放量为 C_total免费配额为 C_free阶梯区间为 [C_free, C_limit1)、[C_limit1, C_limit2)、[C_limit2, ∞)对应碳价为 p1、p2、p3且 p1 p2 p3。用一个二进制变量 y1、y2 表示当前所处的阶梯区间再引入连续变量 C_slab1、C_slab2、C_slab3 表示落入各档位的排放量则碳成本 C_cost 表示为C_cost p1 * C_slab1 p2 * C_slab2 p3 * C_slab3约束条件包括C_total C_free C_slab1 C_slab2 C_slab3C_slab1 ≤ (C_limit1 - C_free) * y1C_slab2 ≤ (C_limit2 - C_limit1) * y2C_slab3 ≤ M * (1 - y1 - y2) 之类的大M约束这套建模技巧的核心思想是跳出非线性的坑用0-1变量把分段结构拆解开。在实际项目中碳价阶梯档位一般3到5档就够了再细分只会增加求解时间而对结果改进有限。2.3 需求侧响应的灵活性建模需求侧响应DR在这个模型里不是简单的负荷平移而是拆成三类可平移负荷、可削减负荷、可转移负荷。可平移负荷比如工厂的某条生产线整个生产时段可以整体往前或往后挪2小时挪动后内部功率曲线不变。可削减负荷比如空调温度上调1度、照明减半这部分负荷可以在指定时段被削减削减量有上限削减后不需要补偿。可转移负荷比如储能充电本身就可以看成一种可转移负荷但这里特指用户侧的灵活性资源在不同时段之间分配总用电量总量守恒。DR建模的核心是激励价格和约束边界。对可削减负荷目标函数里要加入削减补偿成本补偿单价高于正常电价才合理对可平移负荷引入整数变量表示平移后的启动时刻启动时刻有窗口限制。这部分约束写得越真实调度结果越有参考价值。2.4 多源协同框架与目标函数设计所谓多源协同是指发电侧火电、风电、光伏、储能系统、用户负荷三者联合调度储能作为时间转移的枢纽。目标函数是所有成本最小化min 总成本 购电成本 碳成本 DR补偿成本 储能运行维护成本 - 售电收益约束包括功率平衡约束发电出力 储能放电 购电 负荷 - DR削减 储能充电 售电储能SOC递推约束SOC(t1) SOC(t) (P_bat_charge * η_c - P_bat_discharge / η_d) * Δt / E_ratedSOC上下限约束、充放电功率约束、充放电互斥约束备用容量约束如果要求系统留有一定旋转备用火电爬坡约束、出力上下限约束风电/光伏出力预测值上限约束这套耦合约束下来模型规模对中小型微网而言大约几百到几千个变量求解时间在几秒到几十秒之间完全可接受。3. 求解方案选型为什么最终落在MILP而不是启发式3.1 动态规划到哪一步就不合适了早期看到不少储能优化文章用动态规划DP求解状态变量是SOC动作是充放电功率用价值函数迭代。对单储能、单一电价场景DP清楚直观、能处理非线性损耗问题不大。但一旦引入多台机组、需求侧响应可平移时段、阶梯碳价多档位状态空间就会急速膨胀。以一个24时段、SOC离散成50个状态、3台火电各有10个出力档位、可平移负荷有4个启动窗口的模型为例状态数轻松超过百万量级DP每一步都要遍历动作空间计算量和内存都吃不消而且对约束改动非常敏感加一个约束就要重新设计递推方程。启发式算法遗传算法、粒子群等反过来走向另一个极端它们擅长处理非线性甚至黑箱目标函数但给不出最优性证明多次运行结果还有随机波动。对电力调度这类需要严密物理约束的问题启发式解的合规性验证麻烦而且很难向业主方解释为什么这个解是最优的。3.2 为什么MILP是当前最优解MILP混合整数线性规划同时处理了连续变量功率、SOC、碳排量和整数变量机组启停、充放电互斥、阶梯档位选择、负荷平移时刻而且现代求解器Gurobi、CPLEX、HiGHS等对MILP的branch-and-cut算法已经非常成熟中小规模模型求全局最优解并不困难。核心理念是把非线性结构通过线性化技巧转化为线性约束整数变量。模型一旦建好求解器负责搜空间我们只需要把约束写对、把边界参数写合理。相比启发式MILP还有一个巨大优势可以输出对偶变量影子价格这对分析节点电价、储能套利空间、碳价敏感性非常有价值。3.3 Python求解器选型开源还是商业我自己的习惯是个人学习、论文复现、几十个节点的微网规模用开源求解器工业级项目、变量上万、时限严格直接用商业求解器。求解器类型License特点HiGHS开源MIT对纯LP和MILP性能已相当不错PuLP/CVXPY可直接调用CBC开源Eclipse经典开源MILP求解器性能中规中矩Gurobi商业收费行业标杆求解速度快有学术LicenseCPLEX商业收费IBM出品与Gurobi同级适合企业部署scipy.optimize.milp开源BSDSciPy 1.9内置接口简单适合轻量场景这篇文章的示例代码以PuLP调用HiGHS求解器为例一个原因是PuLP建模语法直观另一个原因是HiGHS求解器在Windows/Linux下安装零依赖读者拿到代码装个pulp库就能跑通不涉及商业化收费问题。4. 代码实现从参数定义到结果输出4.1 整体工程结构我的项目目录结构大概是这样的storage_carbon_dr/ ├── data/ │ ├── load_profile.csv # 负荷曲线96点 │ ├── pv_wind_profile.csv # 新能源出力曲线96点 │ ├── tou_price.csv # 分时电价 │ └── carbon_params.json # 碳配额与阶梯碳价参数 ├── src/ │ ├── model.py # 优化模型主逻辑 │ ├── data_loader.py # 数据读取与预处理 │ ├── loss_curve.py # 分段损耗参数生成 │ └── postprocess.py # 结果分析可视化 ├── run.py # 主运行入口 └── requirements.txt这个结构不算重但把数据、模型、后处理拆开了换一组数据、换一套碳价参数都不需要改模型主体代码对后续做敏感性分析很友好。4.2 核心代码逐段解读创建模型对象定义时间集合与关键变量import pulp import numpy as np T 96 # 一天96个时段15分钟粒度 model pulp.LpProblem(Storage_Carbon_DR, pulp.LpMinimize) # 储能变量 P_ch pulp.LpVariable.dicts(P_ch, range(T), lowBound0) # 充电功率 P_dis pulp.LpVariable.dicts(P_dis, range(T), lowBound0) # 放电功率 SOC pulp.LpVariable.dicts(SOC, range(T1), lowBound0.2, upBound0.9) # SOC上下限 # 火电出力变量 P_thermal pulp.LpVariable.dicts(P_thermal, range(T), lowBound0) # 阶梯碳价相关变量 C_slab1 pulp.LpVariable.dicts(C_slab1, range(T), lowBound0) C_slab2 pulp.LpVariable.dicts(C_slab2, range(T), lowBound0) C_slab3 pulp.LpVariable.dicts(C_slab3, range(T), lowBound0) # 需求侧响应变量 DR_cut pulp.LpVariable.dicts(DR_cut, range(T), lowBound0) # 可削减量变量定义的关键点SOC范围一般设在0.2到0.9之间不要设0-1因为锂离子电池深度放电会加速衰减模型里预留这个范围也更贴近工程实际不至于算出把电池放空的激进调度方案。分段损耗的参数构建# 三段式充电损耗低负载率段85%中负载率段90%高负载率段88% # 这里为了演示简化为按功率区间划分 seg_boundaries [0.0, 0.3, 0.7, 1.0] # 归一化功率区间边界 seg_efficiency [0.85, 0.90, 0.88] def get_charge_efficiency(p_norm): for i in range(len(seg_boundaries) - 1): if seg_boundaries[i] p_norm seg_boundaries[i1]: return seg_efficiency[i] return seg_efficiency[-1]实际代码里不会每次调用这个函数而是把分段损耗转成线性约束加到模型里用二进制变量激活对应的损耗区间。这是个容易犯迷糊的地方我给出的简化版是为了让读者理解分段的思想完整线性化约束版本贴在文末。目标函数与核心约束# 目标函数购电成本 碳成本 DR补偿 储能运维 - 售电收益 model pulp.lpSum( buy_price[t] * P_buy[t] carbon_price * (C_slab1[t] C_slab2[t] C_slab3[t]) DR_price * DR_cut[t] om_cost * (P_ch[t] P_dis[t]) for t in range(T) ) - pulp.lpSum(sell_price[t] * P_sell[t] for t in range(T)), Total_Cost # 功率平衡约束 for t in range(T): model (P_pv[t] P_wind[t] P_thermal[t] P_dis[t] P_buy[t] load[t] - DR_cut[t] P_ch[t] P_sell[t]), fPower_Balance_{t} # SOC递推约束 for t in range(T): model (SOC[t1] SOC[t] (P_ch[t] * eta_charge - P_dis[t] / eta_discharge) * delta_t / E_rated, fSOC_Dynamics_{t}) # 充放电互斥约束引入0-1变量 for t in range(T): model P_ch[t] M * u_ch[t] model P_dis[t] M * (1 - u_ch[t])功率平衡约束是整个模型的骨架所有电源、储能、负荷、DR都必须在这一条等式里对账。如果发现模型无解优先检查这一条约束两侧的单位和方向。4.3 算例设计一个小型微网的完整输入输出为了验证模型效果我设计了一个典型的工业园区微网算例光伏装机2MW、风电装机0.5MW、储能容量2MWh/1MW、一台3MW燃气轮机、负荷峰值约3.2MW峰谷电价采用一般工商业分时电价碳配额按机组实际排放的80%发放阶梯碳价设为超出配额部分每吨60元/90元/120元三档。时段负荷(MW)光伏(MW)风电(MW)电价(元/kWh)00:00-08:001.2-1.800.15-0.30.3208:00-12:002.0-2.80.4-1.20.2-0.350.7212:00-17:002.4-3.20.8-2.00.25-0.40.5217:00-22:002.8-3.200.2-0.31.1222:00-24:001.5-2.000.15-0.250.42运行求解后典型输出结果包括各时段储能SOC曲线、充放电计划、火电出力计划、DR削减量、购售电计划、总成本及碳成本明细。这部分后处理代码用Matplotlib输出曲线图我习惯把SOC、电价、充放电功率画在同一张双y轴图里方便直接看出储能的套利逻辑。import matplotlib.pyplot as plt fig, ax1 plt.subplots(figsize(10, 6)) ax1.plot(soc_result, labelSOC, colorblue, linewidth2) ax1.set_ylabel(SOC) ax2 ax1.twinx() ax2.bar(range(T), p_ch_result, labelCharge, alpha0.6) ax2.bar(range(T), p_dis_result, labelDischarge, alpha0.6) ax2.set_ylabel(Power (MW)) plt.legend(locupper left) plt.show()5. 实测中的坑和结果解读5.1 改进模型比原模型省了多少用同一组数据跑了两个版本A版是固定效率单一碳价的传统模型B版是本文的分段损耗阶梯碳价DR模型。结果差异很明显指标A版传统模型B版本文模型差异总运行成本万元/日12.3611.52-6.8%碳成本万元/日1.281.05-18.0%储能日循环次数1.01.2525%系统综合效率92%88.6%-3.4%DR削减成本00.12新增表面上B版效率变低了但这是真实效率的客观反映。A版因为过度乐观估计效率实际执行时根本到不了预期的充放电量省下来的成本是纸面成本。B版在效率和真实转化之间做了取舍反而因为调度计划更贴合物理实际碳成本降得更明显、总成本更低。这个结论和我实际运维观测吻合低估损耗的项目测算时很爽运行时必然打折扣。5.2 分段损耗区间的选择粒度与求解速度的权衡分段损耗区间的划分对求解效率影响非常大。我把充电效率曲线从单段一直细分到10段做测试发现段数从1增加到3时目标函数值变化显著从3段增加到5段目标函数值变化在1%-2%左右从5段增加到10段目标函数值变化不足0.5%但求解时间增加了近5倍。所以实际项目中损耗区间划分3到5段是性价比最高的选择。另外要提醒一个容易踩坑的细节如果某段区间边界设得非常窄比如低负载段只有0到5%的功率区间求解器在这个区间内几乎选不中连续解会导致二进制变量偏好异常结果出现抖动。处理办法是把低负载段合并进第二段让每段至少覆盖15%-20%的出力范围。5.3 阶梯碳价的边界陷阱阶梯碳价的非线性来源在于跨档。如果当天碳排放量刚好压在两个档位的交界处模型对碳排量的小扰动会非常敏感可能出现为了省碳而大幅调整出力的激进决策。我在代码里加了碳价的连续性修正把阶梯函数转换成等价的线性函数加整数补偿项这样在边界处目标函数是连续的不容易出现病态跳变。另外需要特别注意的是免费配额的处理。很多模型没考虑免费配额直接对全部碳排放收费这在低碳价场景下会显著高估碳成本、压低火电出力导致调度结果偏保守。正确做法是把免费配额设成一个变量在约束中先抵扣再算阶梯这样才能反映真实碳市场机制。5.4 需求侧响应的激励价格不该拍脑袋定DR削减补偿单价如果定得太低模型永远不会调用DR资源定得太高模型会过度削减负荷牺牲用户体验来省钱。我自己的经验是用缺电成本或停电损失作为价格上限的参考值。比如工业用户停电损失约在每度电20-80元之间但那是极端情况下的失负荷价值日常DR削减属于舒适性调节补偿价格参考实时电价的1.2到1.5倍比较合理既能让模型在高峰时段主动调用DR又不会造成过度削减。6. 从复现到进阶的几个扩展方向模型跑通后可以往三个方向演进。第一个是随机优化。光伏和风电出力都是预测值实际偏差会让确定性调度方案失效。简单做法是用场景法生成若干组新能源出力场景每个场景一个约束副本目标函数变成所有场景的期望成本。代价是模型规模乘以场景数N倍对求解器内存要求高一些。更高级的做法是两阶段鲁棒优化第一阶段定储能、火电的开停机决策第二阶段在不确定集内寻优目标是最坏情况下的最优方案适合对系统可靠性要求高的用户。第二个是多时间尺度前瞻。现在模型只看单日96时段属于日间经济调度。可以把边界条件拉长到一周甚至一个月用滚动优化的方式逐日推进。储能电池的寿命衰减通常用循环次数和放电深度DOD衡量日间模型如果只盯单日成本可能在电价高、碳价高的日子把电池用得很狠牺牲长期寿命。加入全生命周期约束后电池的日均充放电量会受到抑制总成本虽然单日小幅上升但摊到电池更换成本上反而划算。第三个是考虑储能老化与更换成本。储能不是一次性投入的资产模型里可以把电池容量衰减率表达为与循环次数、DOD相关的线性函数再折算成每次充放电的老化成本加进目标函数的运维项。这样调度方案会自动减少不必要的浅充浅放更贴近商业储能的实际运营逻辑。以上扩展方向我在另一个项目里分别做过验证随机优化版本改造成本最高但结果稳健性提升最明显生命周期版本改造最简单只需加一个约束和一个成本系数建议优先做。7. 如何用这套代码套你自己的算例如果你手头有自己的负荷、电价、新能源出力数据改造流程非常直接。数据格式上先把所有时序数据统一成相同的分辨率。我遇到过不少案例负荷数据是15分钟粒度、电价是30分钟粒度、新能源出力是1小时粒度混在一起处理时接口最容易出bug。归根到底要按最小粒度对齐或者统一差值到同一粒度。1小时模型精度够用15分钟模型更精细但数据量翻四倍跑之前先想清楚需求。参数标定上储能损耗曲线最好直接向设备厂商要效率测试报告不同厂家、不同温度环境下的效率差异很大。如果拿不到实测数据可以参考统一标准里的储能系统效率测试方法用50%额定功率下的效率作为中段值再设定低负载率下降3-5个百分点、高负载率下下降1-2个百分点构造三段损耗曲线。碳价参数直接查当地碳市场最新公告即可注意配额发放比例每年在收紧模型里建议把免费配额比例做成可配置参数方便做政策敏感性分析。需求侧响应的可平移负荷参数需要和用户实际沟通比如工厂里哪些产线能挪、能挪多久这些约束写进模型之前必须经过现场验证。我在一个项目里遇到过模型建议把高耗能产线挪到夜间但工厂夜班人力成本远高于电费节省实际根本不可行。所以DR参数宁可少设不可乱设。运行结束后重点看三张图SOC曲线是否在合理区间震荡、储能充放电是否集中在峰谷时段、碳成本占运行成本的比例是否合理。如果SOC曲线频繁触顶触底说明储能容量配置相对于电价差来说偏大可以适当调小容量或增加循环次数上限如果碳成本占比异常高说明火电出力被过度调用需要回头检查新能源出力和负荷预测是否匹配有误。这套代码从最初我自己算储能项目投资收益的校验工具慢慢扩展成现在这个包含分段损耗、阶梯碳价、需求侧响应的多源协同优化框架中间迭代了很多版。每次改动模型都要重新跑历史数据做对比验证这步一定不能省。纯粹的数学建模和落到实处的工程建模差距往往就在这些细节里。
返回列表