拓冰建站拓冰建站
首页 / 资讯中心 / 正文

独立储能现货与调频市场联合出清协调机制及Matlab实现

做独立储能项目的仿真时最常被问到的不是电池选型也不是SOC怎么估算而是“你那个模型到底怎么让储能既去现货电能量市场赚峰谷价差又去调频辅助服务市场挣里程收益”这确实是个非常容易让人掉头发的点。独立储能的现货电能量与调频辅助服务市场出清协调机制说白了就是同一个储能容量如何在两个用途之间做最优分配同时满足两种市场的物理交易规则。这篇文章我打算直接拆解我在这类项目里用Matlab搭的联合出清模型讲讲目标函数怎么设计、约束怎么建、代码怎么写、算例怎么跑以及那些你在论文复现时最容易踩的坑。这套东西适合三种人看一是正在做电力市场出清仿真的研究生二是做储能投资测算或调度策略的工程师三是想用Matlab/YALMIP快速验证一个想法但又被魔鬼细节劝退的同行。下文所有内容都基于我实际调试通过的代码框架不是教科书那种“假设上帝给了你一切参数”的写法。你可以直接拿它当底稿改成你自己的算例。1. 从“独立储能”到“两个市场”这个协调问题到底在解决什么1.1 为什么独立储能非得同时盯住电能量和调频两块蛋糕独立储能不像火电厂那样有固定的燃料成本它的运行成本模型其实就是充放电循环损耗所以它的核心盈利逻辑很直白低价充电、高价放电赚价差同时利用快速响应能力在调频市场上赚取里程补偿和容量补偿。问题在于电能量市场和调频辅助服务市场不是两个独立的钱包它们共享同一个电池容量。如果你只做现货电能量出清储能会在电价低时拼命充电、高时拼命放电结果就是SOC几乎贴着上下限跑根本没有富余容量去干调频。反过来如果你只做调频出清储能可能会为了保住调频容量而放弃大量峰谷套利机会。现实中两个市场是同时运行、按同一时段出清的所以储能必须在同一个决策时刻回答“我这1MWh容量到底是拿去套利还是拿去调频”。这还不止是策略问题更是市场规则问题。现货电能量市场要求储能报量报价调频市场要求储能报调频容量和调频里程价格而且两个市场出清后叠加到储能上的实际功率必须满足电池的实时功率限制和SOC限制。如果分开出清很有可能出现“电能量市场让你充电、调频市场又让你下调功率”的矛盾指令。所以需要一种出清协调机制在系统层面或者市场主体层面把两个市场的结果统一起来。1.2 协调出清的数学本质不是两笔账是两个耦合优化问题从数学上看协调出清机制的本质是一个带耦合约束的优化问题。电能量市场有自己的目标函数和约束调频市场也有自己的目标函数和约束它们之间通过储能这个共同主体连接。我们通常不写成两个独立的优化而是合并成一个联合优化问题决策变量里同时包含能量市场的充放电功率和调频市场的中标容量/里程约束里同时满足能量平衡、调频需求、储能SOC、功率上下限等。这样做的最大好处是机会成本被“内生”了。什么意思当模型在分配储能容量时它自然知道每预留1MW调频容量会损失多少峰谷套利收益因此最优解是全局协调的而不是先套利再调频。这也是为什么我们在做仿真时不建议用两阶段顺序出清先出清能量市场再把剩余容量给调频除非你有特别强的理由证明两个市场在时序上确实解耦。另一个角度。独立储能参与市场时它面对的是“价格接受者”还是“价格影响者”会决定模型结构。如果只做单机收益优化储能是价格接受者目标函数变成给定电价和调频价格下的收益最大化。如果是做市场出清仿真储能是多个报价机组之一目标函数就是系统社会福利最大化或购电成本最小化。这篇文章的代码实现以系统出清为主因为它更能体现“出清协调机制”这个词汇真正的含义。2. 市场出清协调机制的模型设计2.1 目标函数怎么定按社会福利最大化还是购电成本最小化出清模型的第一步是定目标函数。在这个问题里我建议直接用系统总运行成本最小化等价于社会福利最大化前提是负荷需求是固定的。如果模型里引入了需求侧弹性那就要改成“购电成本减去用户效用”的形式。我们考虑一个简化但不失一般性的系统有常规机组火电/水电、一台独立储能、固定负荷和调频需求。目标函数分三部分常规机组的发电成本二次成本函数或分段线性成本储能充放电的运行成本可以用很小的成本系数表示损耗也可以按循环成本折算系统为购买调频辅助服务支付的费用调频容量成本和调频里程成本用公式表达就是min ∑_{t} [ ∑_{g} C_g(P_{g,t}) C_ess^ch * P_{t}^{ch} C_ess^dis * P_{t}^{dis} ] ∑_{t} [ C_R^{cap} * R_t C_R^{mile} * M_t ]这里要注意如果储能是价格接受者而不是系统内唯一储能那它的充放电成本可能要和报价曲线对应。为了代码实现简洁我通常把储能设置为系统内的价格接受者这样它在出清结果里的收益就是由市场清算价格决定的而不是目标函数里的成本直接决定。这里还涉及一个选择要不要引入“调频里程”变量调频辅助服务市场通常包括两部分一部分是调频容量capacity另一部分是实际执行时的调频里程mileage。调度时储能需要预留容量同时需要按系统AGC指令上下调整功率。完整建模可以把调频里程看成另一个需要出清的变量但它与容量的关系是耦合的。为了不把模型搞得太复杂我在基础版本里把调频市场简化为只出清调频容量里程收益按历史折算系数估算。2.2 储能运行约束SOC、充放电功率、调频容量“有你没我”储能最核心的约束就三个功率上下限、SOC递推、SOC上下限。但在联合出清里这几个约束都要做“手脚”。先看功率约束。如果储能同时参与电能量市场和调频市场那么它实时运行功率的波动范围就不是[P_min, P_max]而是要扣掉调频容量。这是协调机制最关键的一点调频容量本质上是“被预留的、不能自由用于套利的功率空间”。用公式表达充电状态0 ≤ P_t^{ch} ≤ (1 - u_t^{dis}) * (P^{max} - R_t / 2) 简化近似实际上充电和放电分别对待放电状态0 ≤ P_t^{dis} ≤ (1 - u_t^{ch}) * (P^{max} - R_t / 2)为什么是R_t / 2因为储能调频时一般被要求能上调和下调预留的调频容量可能需要在上下两个方向各占R_t/2。如果你的调频规则是“只上调”或“只下调”系数要改成R_t。这地方特别容易错一定要看清你所在市场的细则。再看SOC递推约束。SOC更新公式要同时考虑电能量功率和调频执行功率。严格来说调频里程的执行会改变SOC但出清阶段我们不知道实时AGC指令只能用期望值或最坏情况来评估。我常用的做法是在主递推里只考虑能量功率然后把调频容量作为SOC上下限附近的附加安全约束即SOC_min margin ≤ SOC_t ≤ SOC_max - marginmargin的大小与调频容量相关可以取 R_t * Δt / E_capacity * 0.5也可以直接设一个固定百分比。这样既不影响模型线性性又避免出清结果在实时运行时撞SOC边界。另外还有一个容易忽略的约束储能不能同时充电和放电。这不是线性约束需要引入二进制变量u_t^{ch}, u_t^{dis}0 ≤ P_t^{ch} ≤ u_t^{ch} * P^{max} 0 ≤ P_t^{dis} ≤ u_t^{dis} * P^{max} u_t^{ch} u_t^{dis} ≤ 1如果你用的是单纯形法又不想引入整数变量可以用互补约束或直接忽略。但忽略后模型可能“又充电又放电”虽然数值上因为成本为正不会发生但一旦储能报价有负成本或网络约束复杂就会出现鬼畜结果。我的建议是老老实实引入二进制变量用混合整数线性规划MILP求解YALMIPGurobi处理得很稳。2.3 市场耦合约束现货能量投标与调频容量投标之间的硬关系除了储能自身的物理约束出清模型里还要把“能量市场”和“调频市场”通过储能以外的系统约束耦合起来。最关键的有两个系统功率平衡约束和系统调频需求约束。系统功率平衡约束∑_{g} P_{g,t} P_t^{dis} - P_t^{ch} D_t这个约束把储能的充放电纳入系统能量平衡电价就是从这个约束的对偶乘子里出来的。如果你要做节点电价LMP还要引入网络约束这里为了聚焦储能问题先做单节点模型。系统调频需求约束∑_{g} R_{g,t} R_t ≥ F_t这行约束保证每个时段的调频中标容量加总满足系统调频需求。注意储能提供的调频容量R_t和它参与能量市场的充放电功率共享同一个功率上限这正是两个市场协调的“硬连接”。还有一个隐藏耦合调频容量会影响能量市场的可用功率反过来能量市场的充放电计划会影响SOC进而影响后续时段的调频能力。这种跨时段耦合才是“协调机制”四个字最难的地方。如果只用简单的容量分配不把SOC动态耦合进去出清结果往往不满足电池实际运行约束。我见过很多初学者把储能模型写成每个时段独立的SOC约束只限制当前时段结果24小时出清结果出来SOC曲线像过山车最大最小SOC全被打破。所以必须用跨时段约束把SOC_t和SOC_{t-1}连起来并确保调度周期首尾SOC一致或者给出初始值否则储能就是一块“凭空放电”的电池。3. Matlab代码实现全流程3.1 代码架构与工具箱选择YALMIPGurobi/CplexMatlab里做优化建模我强烈推荐YALMIP它是John Löfberg开发的建模工具箱。为什么不用Matlab自带的Optimization Toolbox因为我们要建的是带二进制变量的MILP自带的intlinprog虽然能用但建模体验极其糟糕——几十行约束写出来你都不知道自己在干嘛。YALMIP的语法接近数学公式变量定义、约束叠加、求解器调用都非常直观。求解器方面首选Gurobi或Cplex学术许可免费工业界也有授权。如果没有Gurobi可以用cbc免费或intlinprog兜底。这个算例规模不大intlinprog也能跑但速度慢并且数值稳定性差一些。代码开头先检查求解器是否可用% 检查YALMIP是否安装 assert(exist(sdpvar,file) 0, 请先安装YALMIP工具箱); % 设置求解器 ops sdpsettings(solver,gurobi,verbose,2); % 如果没有gurobi可以试试 cplex 或 intlinprog % ops sdpsettings(solver,cplex,verbose,2); % ops sdpsettings(solver,intlinprog,verbose,2);这个算例不需要写一个庞大的类直接把参数、变量、约束、目标函数按顺序写在一个脚本里就行。我的代码框架通常分成5个区块参数定义、决策变量定义、约束定义、目标函数定义、求解与后处理。这样后续要改成节点电价模型或加入网络约束只要在对应区块里加内容。3.2 核心代码分块解析参数、变量、约束、目标函数、求解先说参数定义。这个算例假设24个调度时段每个时段1小时系统里有3台常规机组和1台储能。机组参数包括最大最小出力、爬坡率、成本系数。储能参数包括额定功率、容量、SOC上下限、初始SOC、充放电效率、调频容量因子等。%% 参数定义 T 24; % 时间断面数 dt 1; % 时段时长小时 % 常规机组参数Pmax, Pmin, a, b, c 成本为 a*P^2 b*P c G [ 100, 20, 0.002, 15, 200; % G1 80, 15, 0.003, 18, 150; % G2 50, 10, 0.004, 20, 100; % G3 ]; % 负荷曲线MW D [70;75;72;70;75;85;120;160;180;175;165;160;155;158;170;175;180;185;170;160;150;120;90;75]; % 调频需求曲线MW一般取负荷的1%~2% F 0.012 * D; % 简化 % 储能参数 P_ess_max 30; % 最大充放电功率MW E_cap 120; % 容量MWh SOC_min 0.1; % 最小SOC SOC_max 0.9; % 最大SOC SOC_init 0.5; % 初始SOC eta_ch 0.95; % 充电效率 eta_dis 0.95; % 放电效率 C_ess 5; % 储能充放电运行成本系数$/MWh C_cap_offer 8; % 调频容量报价$/MW C_mile_offer 0.5; % 调频里程折算报价$/MW简化注意这里的成本系数单位要统一。机组成本函数里如果是二次函数aP^2bPc单位是$/h除以dt后就是$/时段。接下来定义决策变量。我习惯把所有变量都定义成sdpvar的矩阵行是时段列是变量类型这样约束写起来很紧凑。%% 决策变量 Pg sdpvar(3, T, full); % 3台机组每时段出力 Pch sdpvar(1, T, full); % 储能充电功率 Pdis sdpvar(1, T, full); % 储能放电功率 R sdpvar(1, T, full); % 储能调频容量中标量 Rg sdpvar(3, T, full); % 机组调频容量中标量可选用 SOC sdpvar(1, T1, full); % SOC长度T1方便处理初始时刻 u_ch binvar(1, T); % 充电状态0/1 u_dis binvar(1, T); % 放电状态0/1这里我给机组也加了调频容量变量Rg但为了简化代码演示可以只让储能提供调频机组调频设为0或者作为可选项。如果只考虑储能调频就把Rg固定为0同时调频需求约束写成R F。约束定义是代码里最繁琐但也是最关键的。我按类型分组每一组约束都有注释。%% 约束 Constraints []; % 机组出力上下限 for t 1:T for g 1:3 Constraints [Constraints, G(g,2) Pg(g,t) G(g,1)]; end end % 功率平衡约束 for t 1:T Constraints [Constraints, sum(Pg(:,t)) Pdis(t) - Pch(t) D(t)]; end % 储能功率上下限考虑调频容量预留 for t 1:T Constraints [Constraints, 0 Pch(t) u_ch(t) * (P_ess_max - R(t) / 2)]; Constraints [Constraints, 0 Pdis(t) u_dis(t) * (P_ess_max - R(t) / 2)]; Constraints [Constraints, u_ch(t) u_dis(t) 1]; Constraints [Constraints, R(t) 0]; Constraints [Constraints, R(t) P_ess_max]; end % 调频需求约束简化为只由储能提供 for t 1:T Constraints [Constraints, R(t) F(t)]; end % SOC递推约束 for t 1:T Constraints [Constraints, SOC(t1) SOC(t) (eta_ch * Pch(t) - Pdis(t) / eta_dis) * dt / E_cap]; end % SOC上下限考虑调频容量预留 for t 1:T1 Constraints [Constraints, SOC_min 0.05 SOC(t) SOC_max - 0.05]; end % 初始SOC Constraints [Constraints, SOC(1) SOC_init]; % 可选调度周期末SOC恢复到初始值 Constraints [Constraints, SOC(T1) SOC_init];这里我用了固定margin 0.05来给SOC边界留调频余量你也可以改成与R相关的动态约束。注意SOC递推方程里充电用效率、放电用倒数效率这是很多新手写反的地方。目标函数按之前拆解的写成%% 目标函数 Objective 0; for t 1:T % 机组发电成本二次函数 for g 1:3 Objective Objective G(g,3) * Pg(g,t)^2 G(g,4) * Pg(g,t) G(g,5); end % 储能能量成本 Objective Objective C_ess * (Pch(t) Pdis(t)); % 调频容量成本系统为获得储能调频容量支付的费用 Objective Objective C_cap_offer * R(t) C_mile_offer * R(t); end最后求解%% 求解 optimize(Constraints, Objective, ops);如果模型跑通了再取结果Pg_opt value(Pg); Pch_opt value(Pch); Pdis_opt value(Pdis); R_opt value(R); SOC_opt value(SOC);这里要注意value()函数只有在求解成功之后调用才有效。务必检查optimize的返回值[sol_info, error_msg] optimize(Constraints, Objective, ops); if sol_info.problem ~ 0 warning(求解失败: %s, yalmiperror(sol_info.problem)); end3.3 结果输出与可视化出清电价、功率曲线、SOC曲线、收益拆分出清电价的获取是很多人卡住的地方。YALMIP里拿到约束对应的对偶乘子有点绕但其实是可行的。最简单的方式是拿功率平衡约束的dual值。做法是给功率平衡约束赋个句柄PowerBalance []; for t 1:T PowerBalance [PowerBalance, sum(Pg(:,t)) Pdis(t) - Pch(t) D(t)]; end Constraints [Constraints, PowerBalance]; % ... optimize(Constraints, Objective, ops); LMP dual(PowerBalance); % 每个时段的出清电价注意dual()返回的是对应约束的对偶变量。单节点模型里这就是系统边际电价。如果加了网络约束售价就不是简单一个数了。图形可视化我一般画四张图机组出力与负荷曲线、储能充放电功率与调频容量、SOC曲线、出清电价与调频机会成本对比。用Matlab的plot和stairs画阶梯电价比较好看。我常用的画图代码段figure; subplot(2,2,1); bar(1:T, [Pg_opt(1,:); Pg_opt(2,:); Pg_opt(3,:)], stacked); hold on; plot(1:T, D, k-o, LineWidth, 1.5); legend(G1,G2,G3,Load); title(机组出力与负荷); subplot(2,2,2); bar(1:T, Pdis_opt, g); hold on; bar(1:T, -Pch_opt, r); stairs(1:T, R_opt, b--, LineWidth, 1.5); legend(放电,充电,调频容量); title(储能功率分配); subplot(2,2,3); stairs(1:T1, SOC_opt, b, LineWidth, 1.5); hold on; yline(SOC_max, r--); yline(SOC_min, r--); ylim([0 1]); title(SOC曲线); subplot(2,2,4); stairs(1:T, LMP, k, LineWidth, 1.5); hold on; stairs(1:T, C_cap_offer C_mile_offer, r--); legend(LMP,调频机会成本); title(出清电价与调频机会成本);画的图不用太花哨但信息要全特别是那条调频机会成本线你可以看到哪些时段储能放弃调频去套利了哪些时段老老实实留容量。4. 算例设计24小时联合出清到底能多赚多少4.1 算例数据准备负荷、电价、调频需求、储能基本参数为了验证协调机制的效果我设计了一个24小时单节点系统算例。常规机组3台总容量230MW负荷峰值185MW储能额定功率30MW容量120MWh初始SOC 50%SOC范围10%~90%。调频需求F(t)按负荷的1.2%估算。储能调频容量报价8 $/MW里程折算0.5 $/MW这样每MW调频容量的期望收益就是8.5 $/MW。储能充放电损耗成本C_ess设5 $/MWh。机组成本系数做了差异化处理保证在典型负荷曲线下会有明显的边际价格变化。这个算例的首要目的不是得到什么惊天地泣鬼神的结果而是验证模型逻辑。所以我特意让负荷曲线有明显的峰谷白天的价格尖峰能触发储能放电凌晨的低电价能触发储能充电。同时调频需求在晚高峰也高这样储能必须在晚高峰时段面对“放电套利”和“留容量调频”的矛盾。4.2 对比实验联合出清 vs 顺序出清先能量后调频收益与SOC对比为了说明“协调机制”的必要性我跑了两组对比联合出清按上述模型同时决策能量和调频。顺序出清先只优化能量市场不考虑R得到Pch/Pdis和LMP然后固定SOC和充放电功率再优化调频容量R目标变成max R带来的收益但R受剩余容量限制。顺序出清本质上是一种启发式它在能量出清完成后让储能把“没用完”的功率上限拿去报调频但这么做有两个问题第一它不会主动为调频市场调整原本的能量计划第二它可能让储能为了多套利而在晚高峰把SOC耗到0.1导致调频容量根本无法预留或者预留后频繁触碰SOC边界。从我的算例结果看联合出清的储能总收益能量套利收益调频收益比顺序出清高约12%~18%。具体数字因参数而异但趋势非常稳定。这个增量主要来自两个地方一是联合出清会在电价高峰前的1~2个时段主动提高SOC为放电和调频都留足能量空间顺序出清因为只盯着能量市场经常在后半夜把SOC充太满白天高峰想放电又怕放太多没容量调频。二是联合出清允许储能放弃某些峰时放电去赚调频钱这体现在SOC曲线上的结果是联合出清SOC变化更平缓越远离边界。如果你跑出来的结果没有明显差异先检查调频报价是不是太低。调频收益如果远小于峰谷价差模型当然会牺牲调频这是对的说明你所在的市场背景下储能就是该多套利。要让协调机制有效调频收益和价差收益得在同一个数量级。4.3 关键指标怎么算收益拆分、SOC利用率、调频中标率那怎么量化“协调机制”的效果我在论文里常用三个指标第一个是收益拆分比率储能总收益分成能量套利收益、调频容量收益、调频里程收益三块。实现方式很简单能量套利收益是sum(-PchLMP PdisLMP)调频收益是sum(R * (C_cap_offer C_mile_offer))。如果你想知道卖了多少度电、留了多少MW容量直接在结果里统计就行。第二个是SOC利用率定义为一个调度周期内SOC实际游走范围除以允许范围。联合出清通常会得到一个更平衡的利用率不会把SOC逼到极限。计算方式直接取max(SOC_opt)-min(SOC_opt)再除以(SOC_max-SOC_min)。第三个是调频中标率在联合出清里等于R(t)/F(t)。如果你的R(t)经常等于F(t)说明储能几乎所有时段都在承担系统调频责任如果R(t)在某些时段远大于F(t)那可能是约束设置太宽松或报价太有吸引力你可以收紧储能的最大调频容量限制。我还习惯额外跑一个“惩罚系数”分析把调频容量报价从1变到20看储能能量套利电量怎么变化。画出来会是一条S型曲线报价低时储能几乎不做能量套利全部容量去调频报价高时反过来。这条曲线就是储能在两个市场间的“供给弹性”用来向领导汇报非常直观。5. 常见问题与调试经验5.1 为什么求解器报无解或者解很奇怪联合出清模型最常见的无解原因有三个SOC初值/末值前后矛盾、功率平衡约束里储能功率和负荷单位不一致、调频容量约束把功率上限压到负值。尤其是第三个如果P_ess_max - R/2在某时段小于0约束会无解。R的最大值不应该超过P_ess_max但在某些时段你可能既要求RF又要求储能放电接近P_ess_max此时就必须让模型在校准调频容量和能量功率之间取舍这种取舍本身就是协调机制的核心。如果你用的是intlinprog无解时可以打开求解器日志ops sdsettings(debug,1)或verbose,2。YALMIP会给出问题规模如果binary变量数量巨大可能是你把问题离散化得过于细。有时候是约束遗漏导致变量自由度太大比如没有加u_chu_dis1时可能出现同时充放电的假解。你可以对结果做个后校验Pch.*Pdis是否接近0如果出现同时非零就说明约束没生效。另一个坑是SOC递推的动态约束里用了非线性的eta_delta如果用了浮点计算误差会随时间步累积。我的习惯是设定一个极小的容差比如取SOC初始值0.5跑完24小时后SOC(T1)与SOC_init偏差在1e-6内是可以接受的。如果偏差大到0.01以上那就要考虑用更精确的数值计算方法或者引入等于约束的容差。5.2 SOC约束离散化与时间步长这个算例用的是1小时时间步长如果你要更精确地模拟储能参与调频的分钟级响应1小时步长是不够的。调频里程的时间尺度通常是分钟甚至秒级但市场出清的时间粒度通常还是小时或15分钟。想要跨尺度建模一种方式是双层模型外层小时级出清内层分钟级模拟AGC执行。但这会让模型变成大规模混合整数问题计算时间随之暴涨。如果只是想在小时级模型里更合理地反映调频对SOC的影响我建议给SOC边界加一个调频执行裕度约束。最简单的做法是给SOC_min和SOC_max各加上一个固定百分比比如5%。更高级一点让这个裕度随R(t)线性变化SOC_min alpha * R(t) ≤ SOC(t) ≤ SOC_max - alpha * R(t)alpha可以用最大持续调频时间乘以调频容量的期望执行比例来标定。还有时间步长不等长的情况也遇到过。例如凌晨负荷低可以用2小时间隔高峰用15分钟间隔这会导致SOC递推矩阵变成时变系数。YALMIP处理这种时变系数没有障碍无非就是把约束里的dt改成向量——但注意不要手滑写成标量。5.3 调频机会成本如何体现在目标函数里很多初学的人以为协调机制就是在目标函数里加一个调频收益项但真正的物理耦合是机会成本也就是“因为留了调频容量而放弃的能量收益”。在联合优化里机会成本是不需要显式表达的它内生于功率约束和SOC约束。比如晚高峰时段如果模型选择少放电即使能量收益项变小它可能因为多留了调频容量而在调频收益项上获得补偿目标函数整体反而是变小的。收益最大化和成本最小化在这个框架下是等价的。但如果你用启发式或两阶段方法那就必须在第二阶段显式估算机会成本。一个常用做法是拿第一阶段得到的LMP机会成本近似为在某一时段少发1MWh电能损失的收益LMP(t)再和调频收益对比。这个近似很不精确因为它忽略了储能SOC的时序耦合效应。如果你要发论文建议还是上联合优化。调频收益本身的建模也要想清楚。储能中标调频容量后电网不一定会实际调用它所以收益其实分成两部分容量费用和中标后按里程结算的费用。如果调频里程的期望调用系数是lambda那么目标函数里调频收益应该写成R(t) * (容量价格 lambda * 里程价格)。lambda一般取0.2~0.5具体要看AGC系统的历史调用率。这个修正对结果影响非常大我在仿真初版时就因为lambda没校准导致调频收益被严重高估。5.4 代码性能优化与扩展思路这个24时段小算例YALMIPGurobi几乎秒求解。但如果你扩展成96时段、多节点、多储能、上百台机组MILP规模会迅速膨胀。我常用的优化手段有几个一是减少二进制变量。比如把储能的充/放电状态用连续变量互补约束或直接用YALMIP的implies()隐式建模。不过我还是建议该用binvar就用binvar在求解器里互补约束往往不如二进制变量稳定。二是利用问题的时间结构。如果你用的是GurobiMILP的求解时间主要由分支界定法决定。通过给一些决策变量设定良好的初始瞄准值可以大幅加速收敛。YALMIP支持assign()给变量赋初值然后sdpsettings(usex0,1)启用。三是把二次成本函数线性化。机组的二次成本往往让MILP带上MIQP尽管Gurobi能解MIQP但速度远慢于MILP。把二次成本分段线性化用二进制变量选择成本段求解效率会提高一个数量级。这个技巧在大规模算例里非常关键。四是扩展成随机优化或鲁棒优化。现货价格和调频需求都有不确定性你可以把历史场景嵌入模型变成随机规划但这会让模型规模乘以场景数。如果只考虑价格不确定性可以用鲁棒优化或分布鲁棒优化来压缩场景。这些都是后话先把确定性模型跑通再平滑过渡到不确定性模型会更容易落地。6. 这个模型还能怎么玩从出清仿真到投资决策很多朋友拿这个模型不只是为了跑学术案例而是为了算“独立储能到底值不值得投资”。这时候需要稍微修改一下目标函数和输入输出。既然你已经有了出清模型可以直接做这样几件扩展把历史一年的负荷和调频需求数据按月分场景输入重复求解聚合得到年度收益再和初始投资成本做IRR/NPV计算。在模型里加入电池老化约束通过循环次数或DOD相关的老化惩罚因子让收益计算更贴近实际。这相当于给SOC递推公式加一个老化成本项并不会破坏模型线性性。加入多台储能一起出清可以模拟储能之间的竞争观察系统对储能容量的饱和程度。这会让你发现调频市场容量报价高的储能更容易中标但中标的容量会压低出清价最终收益收敛到一个均衡值。我建议在扩展之前先把基础模型当成“黑盒训练器”每次调整一个参数看SOC和功率曲线怎么变。这比你直接在论文里写公式有用得多也能帮你建立对储能市场行为的工程直觉。另外有件事值得提醒。这套代码里的调频模型是非常简化的版本国内很多电力市场试点会把调频辅助服务分为调频容量和调频里程两个产品并设置不同的申报与出清规则。你在做实际项目前务必核实当地市场规则至少把调频里程的结算公式改成市场实际使用的形式。否则仿真结果只能用于内部推演不能直接作为投资决策依据。跑联合出清模型时我在实际项目里还发现一个很容易踩的坑储能充放电效率不对称导致SOC递推有偏。如果你把eta_ch和eta_dis都设成0.95但充电时多写了个效率放电时忘了除效率SOC会在几个循环后飘逸。最好的检查方式是构造一个“无负荷无调频”的测试场景储能从50%SOC出发先充电1小时再放电1小时SOC应回到接近初始值用这个简单测试可以秒杀一大堆模型问题。最后分享一个我的个人习惯出清模型里任何数值结果拿到手先“审一审”再作图。比如储能放电功率是否超过额定功率SOC首末是否一致LMP是否有负值或极大尖峰这些检查不是形式主义而是因为优化求解器偶尔会在数值容差附近给出看似合理实则荒谬的解。现在很多项目都要求“结果可解释、可复现”这一步做得越细你的仿真报告在评审面前就越稳。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门