ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

储能调峰调频联合优化模型:基于MATLAB+CVX的凸优化实现

储能调峰调频联合优化模型:基于MATLAB+CVX的凸优化实现 简介面向电力系统研究人员与电气工程学生的储能调峰调频模型MATLABCVX代码包解决储能参与电网调峰调频的建模与优化调度问题。包内提供完整的储能动态模型、电力系统频率响应模型、调峰调频控制策略以及凸优化求解框架可利用CVX工具箱在满足充放电功率、容量与电网稳定约束下计算最优调度方案并输出仿真结果。压缩包共12个文件以Matlab脚本为主辅以数据文件、文本说明文档与PDF论文整体大小179.8MB目录结构清晰。已有209人学习下载适合希望快速上手储能调频建模、优化算法实现及论文复现的研究者。通过该代码包读者可获得从负荷/调频信号数据预处理、不同控制策略对比到结果可视化的完整实现流程有效降低储能系统优化调度的入门门槛。1. 储能调峰调频模型为什么用 MATLAB CVX 建模一个储能电站想同时吃下调峰和调频两笔收入难点不在电池本体而在怎么把同一块电池的功率合理拆给两类服务。调峰按 15 分钟到 1 小时的步长安排能量计划调频要求秒级到分钟级的功率响应两者共用 PCS 容量、共用 SOC 状态最终收敛成一个带状态递推的优化问题。CVX 的价值在于把建模和求解分开你用接近自然语言的约束描述模型它负责把问题翻译成 Gurobi、MOSEK、SDPT3 能直接求解的标准形式改约束的成本从重新推导降到改一行代码。围绕「096」这套储能调峰调频模型下面把目标函数怎么写、SOC 递推怎么约束、CVX 参数怎么设、不可行怎么定位讲透。适合手头有储能调度代码想往凸优化迁移的工程师也适合刚接触 CVX 的研究生照着代码一行行跑通。2. 调峰调频联合优化模型决策变量、目标函数与 SOC 约束拆分调峰调频模型的建模顺序我一般按「先定时间尺度、再写目标函数、最后补约束」三步走。时间尺度没定对后面的约束全是乱的目标函数没定对求解器会给你一个数学上最优、工程上没法用的解。2.1 决策变量与两类服务的时间尺度储能参与调峰交易对象是能量步长通常取 15 分钟或 1 小时参与调频交易对象是容量和里程响应时间在秒级到分钟级。CVX 模型里没法逐秒模拟 AGC 动作所以常见的做法是只预留调频容量、不模拟实时功率跟踪。这样一套模型里有四组核心决策变量P_ch(s)、P_dis(s)第 s 个时段的调峰充电/放电功率单位 MW非负R_up(s)、R_dn(s)第 s 个时段可提供的上调/下调调频备用容量单位 MW代表「AGC 召唤时能立刻腾出的功率」soc(s)第 s 个时段末的荷电状态标幺值 pu相对额定容量归一化。这里有个新手必踩的坑调频备用不是实际充放电功率它只占 PCS 容量不直接消耗电池能量。R_up、R_dn只进功率约束不进 SOC 递推方程。如果自己想加入「备用被调用后的能量补偿」那是另一层不确定性建模别和容量预留混在一组式子里写。2.2 目标函数峰谷套利加调频容量费目标函数写成最大化净收益由三项组成峰谷价差套利收益、调频容量补偿收益、循环寿命损耗成本。用数学式表达就是max Σ price_peak(s) · (P_dis(s) − P_ch(s)) · Δt Σ price_reg(s) · (R_up(s) R_dn(s)) · Δt − Σ c_cycle · (P_ch(s) P_dis(s)) · Δt第一项里price_peak 是分时电价只有电价高的时段放电、低的时段充电这一项才为正第二项里price_reg 是调频容量出清价格乘以备用容量后得到容量费第三项是电池衰减折算c_cycle 的单位是元/MWh让模型不会为了芝麻大的价差频繁充放。三个价格都按调度周期内逐时段给出模型才能被驱动出「谷充峰放、峰段让出上调备用」的行为。提示CVX 的 maximize 只接受凹目标函数。上面三项全是线性的凹性天然成立。如果后续想加放电深度惩罚这类带 soc 平方的项要确认整条表达式仍保持凹性否则要用等价凸形式改写。2.3 约束体系SOC 递推、PCS 容量共享与备用联动约束是这套模型真正有信息量的部分按三组写。第一组是 SOC 递推把相邻时段的荷电状态串起来soc(1) soc0 (P_ch(1)·η_ch − P_dis(1)/η_dis) · Δt / E_rated soc(s) soc(s−1) (P_ch(s)·η_ch − P_dis(s)/η_dis) · Δt / E_ratedη_ch 和 η_dis 分别是充放电效率。注意量纲功率 MW、时间小时、容量 MWh三者配齐才能得到无单位的 SOC 标幺值。放电要除效率是因为电池内部实际放出的能量大于送到电网的能量损耗也算在电池头上。第二组是功率容量共享约束P_ch(s) R_dn(s) ≤ P_rated P_dis(s) R_up(s) ≤ P_rated含义是充电功率和下调备用共享同一块 PCS 容量。假设 PCS 额定 2.5 MW低谷时段以 2 MW 充电同时手里还握着 0.8 MW 下调备用物理上不可能——AGC 召唤下调时电池要么停止充电要么反向放电2 0.8 已经超过 2.5模型直接把这个组合判成不可行。这组约束是调峰和调频真正「打架」的地方也是结果里备用和功率此消彼长的根源。第三组是 SOC 边界约束给上下沿各留一段裕度soc_min s_reserve_dn ≤ soc(s) ≤ soc_max − s_reserve_up原因很直接SOC 已经到 0.9 时还宣称能上调继续放电实际已经没有能量空间执行 AGC 指令所以边界必须内缩。最后是充放电互斥问题。P_ch(s) · P_dis(s) 0是非凸约束CVX 不能直接写。在电价恒为正、损耗为正的前提下目标函数会天然排斥同充同放——同时充放等于一进一出都白付损耗。所以这个约束可以不写只有在出现负电价且损耗很小时才需要引入 binary 变量做互斥。3. CVX 实现储能调峰调频96 时段模型代码与参数设置模型数学形式定下来之后CVX 的实现就变得很机械。这一章给出完整可运行的 96 时段模型并逐个参数说明影响。3.1 CVX 安装、路径配置与求解器选择CVX 不是 MATLAB 内置工具箱需要单独下载解压在 MATLAB 里 cd 到 CVX 目录执行cvx_setup完成路径配置。很多人问 CVX 安装完显示 Undefined function原因基本都是安装目录没加入 MATLAB 搜索路径或者解压后移动过文件夹没重新 setup。验证装没装好直接跑cvx_begin和cvx_end包一个最小 LP看cvx_status是否返回 Solved。CVX 默认内置 SDPT3 和 SeDuMi对 96 时段的线性规划模型够用但速度一般。我通常额外装 MOSEK下载后把 mosek 目录加入 path执行cvx_solver mosek切换同规模模型求解时间能差一个数量级。做储能日前调度这种反复调参的场景求解器快一倍调试体验完全不一样。3.2 核心代码CVX 的完整储能调峰调频模型%% 096 储能调峰调频模型 - MATLAB CVX % 调度周期 24 小时, 步长 15 分钟, 共 T 96 时段 % 输入: price_peak(96x1) 分时电价, price_reg(96x1) 调频容量价格 % 输出: P_ch, P_dis, R_up, R_dn, soc T 96; % 时段数 delta_t 0.25; % 步长, 小时 E_rated 10; % 电池额定容量, MWh P_rated 2.5; % PCS 额定功率, MW eta_ch 0.95; % 充电效率 eta_dis 0.95; % 放电效率 soc_min 0.1; soc_max 0.9; soc0 0.5; % 初始 SOC, 周期末也回到该值 % 分时电价示例: 谷-峰-平三段, 按小时给出后展开成 15 分钟 hourly_price [0.35*ones(6,1); 0.65*ones(4,1); 0.35*ones(2,1); ... 0.65*ones(4,1); 0.35*ones(8,1)]; % 24 小时 price_peak repelem(hourly_price, 4); % 展开为 96 时段 price_reg 0.08 * ones(T,1); % 调频容量价格, 元/MW c_cycle 0.05; % 循环损耗成本, 元/MWh cvx_begin quiet variables P_ch(T) P_dis(T) R_up(T) R_dn(T) variable soc(T) maximize( sum((price_peak .* (P_dis - P_ch)) * delta_t) ... sum((price_reg .* (R_up R_dn)) * delta_t) ... - c_cycle * sum(P_ch P_dis) * delta_t ) subject to % SOC 递推约束 soc(1) soc0 (P_ch(1)*eta_ch - P_dis(1)/eta_dis)*delta_t/E_rated; for k 2:T soc(k) soc(k-1) (P_ch(k)*eta_ch - P_dis(k)/eta_dis)*delta_t/E_rated; end soc(T) soc0; % 周期末 SOC 回到初始值 % 功率与调频备用共享 PCS 容量 P_ch R_dn P_rated; P_dis R_up P_rated; % SOC 上下沿留调频裕度 soc soc_min 0.05; soc soc_max - 0.05; % 非负约束 P_ch 0; P_dis 0; R_up 0; R_dn 0; cvx_end if strcmp(cvx_status, Solved) fprintf(求解成功, 目标值 %.4f\n, cvx_optval); end代码逻辑按 CVX 的建模语法拆开看cvx_begin quiet里的 quiet 抑制迭代日志调试时可去掉variables一行声明四个长度为 T 的向量变量variable soc(T)单独声明是因为它和前者类型相同但语义不同分开写更清楚。目标函数里sum(price_peak .* (P_dis - P_ch)) * delta_t是逐时段点乘再求和点乘要求两个向量维度严格一致96 对 96。SOC 递推用 for 循环生成 T 条等式约束这是 CVX 允许的写法本质是批量声明约束不是动态规划求解。soc(T) soc0强制日调度结束时电池回到初始状态保证模型可以逐日滚动。3.3 参数怎么改必调参数对结果的影响参数典型值调大调小eta_ch / eta_dis0.92~0.98套利空间变大模型更敢充放损耗高低买高卖难回本soc_min / soc_max0.1 / 0.9可调度能量多备用裕度变小备用裕度足充放电窗口受限c_cycle0.02~0.1 元/MWh充放电次数减少偏向调频容量费可频繁充放套利行为更激进price_reg市场出清价备用容量抢占功率套利收缩调频收益低模型专注峰谷套利这些参数里最容易出问题的是 soc_min/soc_max 与备用裕度的搭配。边界内缩 0.05 是经验值如果调频里程收益很高可以内缩到 0.02把更多 SOC 空间让给能量套利如果电池健康管理要求严格内缩到 0.10 更稳妥。c_cycle 的取值可以查电池厂家的循环寿命曲线折算成每 MWh 吞吐量的成本不要拍脑袋。4. 求解结果分析CVX 状态判读与不可行模型排查CVX 返回 Solved 不等于结果可以直接用。拿到结果后先做三个检查再谈收益拆解和报错处理。4.1 求解成功后的三个合法性检查第一SOC 曲线是否平滑。正常的调峰调频解SOC 应该是一天一到两个完整的谷充峰放循环。如果 SOC 出现高频抖动多半是 price_reg 给得过高模型为了容量费频繁改变功率方向这种解在工程上没法执行。第二功率与 SOC 是否联动。低谷时段 P_ch 应该明显大于高峰时段如果出现低谷不充电、高峰猛放电但 SOC 曲线对不上检查 SOC 递推里 eta 的方向是否写反。第三看cvx_status的精度标记。CVX 可能返回Solved/Inaccurate表示解存在但数值精度不达标需要调精度或换求解器不能直接当最优解用。4.2 收益拆解与结果绘图% 收益拆解: 调峰、调频、损耗三部分分开看 income_peak sum((price_peak .* (P_dis - P_ch)) * delta_t); income_reg sum((price_reg .* (R_up R_dn)) * delta_t); cost_cycle c_cycle * sum(P_ch P_dis) * delta_t; fprintf(调峰收益 %.2f 元, 调频收益 %.2f 元, 循环损耗 %.2f 元\n, ... income_peak, income_reg, cost_cycle); figure; yyaxis left; plot(1:T, P_dis, g, 1:T, P_ch, b); ylabel(功率 MW); yyaxis right; plot(1:T, soc, r); ylabel(SOC pu); xlabel(时段);这段代码把总收益拆成三个分项调试时比单看一个cvx_optval有用得多。比如调频收益占了 70% 但 P_dis 曲线几乎不动说明 price_reg 设置偏高模型在「卖容量」而不是「做能量」反过来调峰收益占比过高而 R_up 全程贴下限说明调频价格没竞争力储能实际是在裸跑峰谷套利。4.3 CVX 常见报错与排查对照表现象常见原因解决办法Disciplined convex programming error写了非凸表达式如 P_ch.*P_dis拆成线性约束或引入辅助变量Infeasible problemSOC 递推与 soc(T)soc0 冲突放宽 SOC 边界或先去掉末端约束Dimensions mismatchfor 循环索引越界、向量维度不齐用 size() 逐个检查变量维度An empty problem变量没进入目标或约束检查变量引用是否拼写错误求解耗时数分钟默认 SDPT3 太慢切 MOSEK或把 T 降到 48模型不可行时我按三步缩小范围先注释掉soc(T) soc0如果变可行说明末端 SOC 要求太紧再把 soc_min/soc_max 放宽到 0.05/0.95如果变可行说明 SOC 边界与备用裕度冲突最后把R_up R_dn相关约束全部去掉如果变可行问题出在调频备用与 PCS 容量共享的组合上。三步之后问题基本定位到具体某组约束而不是对着整个模型瞎猜。注意模型不可行时 CVX 不会给出对偶值靠 dual 变量回溯行不通逻辑排查比数值排查更高效。5. 滚动时域验证与灵敏度调参把模型变成可用策略单次求解 96 时段只是开环计划实际储能调度必须走滚动时域闭环并且通过灵敏度分析确定自己的报价策略。5.1 滚动时域闭环实际运行中每 15 分钟重新求解一次只执行第一个时段的 P_ch(1)、P_dis(1)到下一轮把实测 SOC 赋给 soc0 再求解。这样预测误差对调峰计划的影响被限制在单个时段内。实现上把 3.2 节的代码包成一个函数[P_opt, soc_pred] bess_schedule(price_fc, price_reg_fc, soc_now)主循环里每轮更新价格预测和当前 SOC。注意滚动时域下末端约束要从soc(T) soc0改成soc(T) soc_targetsoc_target 由当前实际电量和日末目标推算否则每轮都强制回到 0.5前段时段的解会偏差很大。5.2 灵敏度扫描找调频报价临界点调频价格是市场变量储能运营商改不了但能通过扫描确定自己的参与阈值。做法是让 price_reg 从 0.02 扫到 0.30 元/MW循环调用 bess_schedule记录每个价格下模型输出的 R_up R_dn 总量和调峰收益。把总备用容量对 price_reg 画一条曲线曲线上会有一个明显的拐点拐点左侧模型几乎不预留调频容量拐点右侧备用容量趋于饱和这个拐点价格就是「从纯峰谷套利」切换到「套利调频混合」的报价临界值。实际报价时只要市场出清价高于拐点就上修自己的备用申报量低于拐点就退守纯调峰。5.3 精度设置与初始 SOC 的实战处理96 时段的 LP 模型在 MOSEK 下默认精度求解通常 1 秒以内。如果遇到奇异结果用cvx_precision best重解一次对比目标值偏差在 1e-4 以内说明默认精度完全够用。不要一上来就把精度调到最高模型规模上去之后求解时间会指数级上升。最后把 soc0 和 soc_target 都设在实际运行容易达到的值比如 0.5滚动时域下每轮搜到的都是可行邻域内的解收敛速度比边界值快得多。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进