多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

虚拟电厂多时间尺度调度与储能衰减耦合建模实战

虚拟电厂多时间尺度调度与储能衰减耦合建模实战 最近做电网优化的同行应该都有同感虚拟电厂VPP这个词已经不只是PPT里的概念了。把分布式光伏、风电、储能和一堆可控负荷聚合成一个“虚拟发电主体”统一参与调度和交易已经是很多实际项目在推的方向。我这次就按一篇SCI论文的思路复现了虚拟电厂的多时间尺度调度模型核心难点在于把储能容量衰减和调度决策耦合在一起而不是事后校核一下寿命。先说结论这个模型拆开看并不复杂但真正把它跑通、跑稳并且让储能衰减这个因素真的影响调度结果中间有不少细节坑。这篇文章我会从研究问题拆解、数学模型、储能衰减建模、灵活性资源建模、Matlab实现、结果分析到排错经验完整讲一遍我实际复现的过程和踩过的坑。适合正在做虚拟电厂、储能调度、需求响应相关课题的研究生也适合想从论文公式落到代码的工程师参考。1. 先拆研究问题虚拟电厂到底在优化什么1.1 多时间尺度调度分得越细越贴近实际虚拟电厂调度最忌讳的就是“一个模型解决所有问题”。现实中的预测精度、用户响应速度、市场出清粒度都不一样所以主流做法是把调度决策拆成多个时间尺度。我在复现中用的是三层结构日前调度24小时1小时间隔、日内滚动调度未来4小时15分钟间隔、实时调整未来15分钟到1小时5分钟间隔。每层做的事不一样决策变量也有差异。这个思路的本质逻辑是尺度越长决策越“粗”但越全局化尺度越短决策越“细”但越局部化。拿生活类比就是长途旅行提前订好站票还是卧铺这是日前决策出门前根据路况决定走哪条路这是日内决策眼前遇到堵车临时变道这是实时决策。三个层面的目标不同不能揉成一团。多尺度调度还有个关键点层与层之间要传递“边界条件”。日内模型要接收日前定下来的机组启停状态实时模型要跟踪日内给出的储能SOC参考轨迹。如果各层之间变量脱节后面跑出来的结果全是乱的这一点后面代码实现时还会再提。1.2 储能容量衰减为什么必须进优化模型很多初版模型把储能当成一个“理想电池”容量恒定效率恒定随便充放。实际上锂电池用久了容量会下降内阻会增大这个衰减速度和充放电次数、放电深度、工作温度强相关。如果把衰减排除在优化目标之外调度策略会倾向于频繁地深充深放因为从运行成本看这样最便宜。但实际运行中电池会提前退役更换一块储能电池的成本可能抵得上好几年的运行收益。这就是“调度上最优、全生命周期上不优”的典型陷阱。所以这次复现里我把储能衰减写进了目标函数作为一项惩罚成本参与优化。这样调度器在做充放电决策时会主动权衡“现在多赚一点电费差价”和“电池寿命损耗”之间的关系出来的策略明显更保守、也更接近工程实际。2. 调度框架与数学模型拆解目标函数和约束是怎么搭起来的2.1 时间尺度分层与决策变量设计先说变量设计。同一套物理设备在不同尺度下决策变量不完全相同。日前层决策变量包括各分布式机组的启停状态0/1变量各时段机组出力储能各时段充放电功率可平移负荷的启动时段可削减负荷的削减比例日内层部分机组启停在日前已经锁定日内只调整出力大小、储能功率和可削减负荷比例可平移负荷通常不再动。实时层基本只做小幅度平衡修正一般通过调整储能功率和少量快速响应负荷来完成。一个常见的工程做法是把模型写成“统一形式”只不过不同尺度下某些变量被固定或忽略。这样维护一套代码就能跑三个尺度很方便。我复现时也是这么干的用Yalmip建模时把变量矩阵的维度统一不同层只用掩码mask控制决策变量的自由度和目标的取舍。2.2 目标函数运行成本与衰减成本怎么拧在一起目标函数我拆成四个部分分布式机组发电成本包括燃料成本和启停成本从主网购电的成本用户负荷参与响应的补偿成本储能寿命衰减折算成本前三个比较常规重点在第四项。衰减成本我用的折算思路是把储能全生命周期更换成本按容量衰减量分摊到每次调度决策。具体到代码里如果模型简化一点可以给每次充放电功率乘一个单位衰减代价系数如果精细化一点就要根据放电深度查衰减曲线再转成约束或惩罚项。目标函数写成标准形式大致是min sum(机组出力成本 启停成本 购电成本 需求响应补偿 储能衰减惩罚)值得说明的是不同量纲的目标项直接相加会出问题。比如购电成本一天几十万但储能衰减折算下来可能只有几百块那衰减项就形同虚设。所以我在复现时对衰减成本做了权重调整或者干脆用价格系数把它放大到和运行成本一个量级。具体系数怎么定要看原文的参数设定我这里是按常见参数范围试出来的。2.3 约束条件哪些墙不能推倒约束是调度模型的主体少了任何一个解都会飞。我梳理一下常用约束功率平衡约束电源出力 储能放电 购电 新能源出力 负荷 储能充电 售电机组出力上下限约束和爬坡约束储能SOC动态约束、充放电功率约束、充放电互斥约束储能容量衰减约束可用容量随调度周期递减可平移负荷的时间窗约束和总用电量约束可削减负荷的最大削减量约束用户舒适度约束比如室内温度不能超出设定范围其中储能SOC动态约束是连接各个时间断面的核心形式上是SOC(t1) SOC(t) Pch(t) * eta_ch * dt / Cap - Pdis(t) / eta_dis * dt / Cap;这个方程本身很简单但如果时间尺度变了dt就要跟着变容量Cap如果考虑衰减也要动态更新否则SOC会漂移出边界。3. 储能容量衰减建模三种做法和我的选择3.1 线性经验模型复现最省事第一种最简单电池每完成一个等效全循环容量损失一个固定百分比。这个模型假设衰减率和循环次数线性相关。公式可以写成SOH(k) SOH0 - alpha * N_cycle(k);其中alpha是每个等效循环的容量衰减率N_cycle是累计等效循环次数。这个模型的优点是线性好嵌入优化问题。缺点是太粗没有考虑放电深度的影响。不过我发现很多SCI论文在算例部分用的就是这种简化毕竟重点不在电池机理而在调度框架。所以这次复现也优先用线性模型和原文献保持一致。3.2 基于放电深度的循环老化模型更贴近实际第二种思路稍微精细一些不同放电深度DOD下电池可循环次数不同。放电越深可循环次数越少。工程上通常用实验数据拟合出一条DOD-循环寿命曲线。这种建模方式下衰减成本不是简单的线性函数而是和每次充放电的DOD挂钩。放进优化模型会引入非线性我复现时选择把它分段线性化再作为约束加入。效果比线性模型更真实但建模工作量明显增加求解时间也变长。3.3 日历老化循环老化半经验模型精度高但求解吃力第三种是学术上最“好看”的做法把容量衰减拆成两部分日历老化电池放着不用也会衰减跟时间、温度、SOC有关通常用与根号时间相关的函数描述循环老化每次充放电循环带来的容量损失和放电深度、倍率有关这类模型精度高参数多放进去做优化基本是MINLP问题求解难度飙升。我在做灵敏度分析时单独实现了这个模型但没有放进主调度循环而是作为离线校验工具用来对比线性模型有没有扭曲调度结论。如果你只想要一个能跑通、能出图、能写论文的结果线性模型加DOD惩罚通常就够了。3.4 衰减模型嵌入优化的三种位置衰减模型不是孤立存在的嵌入方式直接决定求解难度位置一目标函数惩罚项。衰减越快惩罚越大调度器会主动减少深充深放。这个方式最常用。位置二约束条件限制可用容量。每个调度周期根据累计放电量更新容量上限实现“能用多少”的动态变化。位置三储能功率上限约束。衰减严重后限制最大充放电功率。我实际推荐“位置一 位置二”组合目标里加惩罚约束里动态更新容量。这样既不破坏线性结构又能让衰减真实影响可行域。4. 发电侧与多用户负荷灵活性建模把“可调”变成“可算”4.1 发电侧灵活性资源建模发电侧灵活性主要来自两类资源一类是可控机组比如小型燃气轮机、柴油机另一类是新能源比如光伏和风电。新能源本身不可控但可以通过预测区间描述其不确定性。我复现时对新能源的处理方法是日内层每15分钟更新一次预测值不再做随机规划而是把预测值当作确定性参数。这个处理是常见简化如果要做鲁棒优化或场景随机优化代码量会翻倍。对大多数复现项目来说先跑通确定性模型再往不确定性扩展路线更稳。可控机组建模时有个细节爬坡约束千万不要漏。很多初版模型的调度结果里机组出力跳变非常夸张就是因为忘了爬坡限制。爬坡约束的表达式是P_g(t1) - P_g(t) ramp_up; P_g(t) - P_g(t1) ramp_down;4.2 多用户负荷的灵活性分类用户负荷的灵活性不能一刀切。我按响应方式分成三类可平移负荷比如工业流水线、电动汽车充电用电时段可以平移但总用电量不变可削减负荷比如空调、照明可以在高峰时段削减一部分功率用户舒适度会受影响可转移负荷介于两者之间部分电量可移到其他时段每一类负荷都需要自己的约束条件。可平移负荷需要定义“最晚开始时间窗口”和“持续时长”可削减负荷需要定义最大削减比例和补偿价格。4.3 聚合多用户负荷的潜力评估多用户负荷单独建模会导致变量爆炸。比如100个用户的空调都单独建模变量数量直接失控。所以我在代码里做了聚合处理。把同一类型、同一响应特性的负荷聚合成一个“灵活性群”用一个聚合功率范围描述。比如50台空调聚合后总可削减功率是单台的50倍响应时间常数取平均值。这样既保留了灵活性又不至于撑爆求解器。下面是我常用的一个资源参数表资源类型时间尺度响应速度调节容量成本特性光伏日前/日内不可控预测值零边际成本燃气轮机日前/实时分钟级额定的20%-100%燃料成本储能全尺度毫秒级额定功率衰减成本可平移负荷日前小时级按需分配补偿价格可削减负荷日内/实时分钟级最大容量的30%阶梯补偿价格这个表是在我复现过程中调试得最多的部分聚合参数的合理性直接决定调度结果是否可信。5. Matlab代码实现与求解细节从公式到能跑的代码5.1 工程文件怎么组织我复现时的目录结构是这样的VPP_MultiTimescale/ ├── data/ │ ├── load_profile.csv │ ├── pv_profile.csv │ ├── price_profile.csv │ └── battery_params.m ├── models/ │ ├── day_ahead_model.m │ ├── intraday_model.m │ └── realtime_model.m ├── solvers/ │ └── run_simulation.m ├── utils/ │ ├── build_soc_constraints.m │ ├── build_degradation_cost.m │ └── plot_results.m └── results/ ├── day_ahead_schedule.mat └── comparison_figures/第一次做复现时我图省事把所有代码塞进一个脚本里结果后期改参数改到崩溃。后来拆成函数模块每条约束对应一个函数调试效率明显提升。如果你也在做复现强烈建议从一开始就按模块写。5.2 Yalmip建模的核心代码片段我建模用的是Yalmip搭配商业求解器下面这段是我模型里储能部分的核心代码%% 储能变量定义 E sdpvar(T1, 1); % 储能能量状态SOC水平 Pch sdpvar(T, 1); % 充电功率 Pdis sdpvar(T, 1); % 放电功率 Xch binvar(T, 1); % 充电状态 Xdis binvar(T, 1); % 放电状态 %% 约束充放电互斥 Constraints [Constraints, Xch Xdis 1]; Constraints [Constraints, 0 Pch Pch_max * Xch]; Constraints [Constraints, 0 Pdis Pdis_max * Xdis]; %% 约束SOC动态 Constraints [Constraints, E(t1) E(t) Pch(t)*eta_ch*dt - Pdis(t)/eta_dis*dt]; %% 约束容量衰减后的SOC上下限 Constraints [Constraints, E_min(t) E(t1) E_max(t)];其中E_max(t)会随着衰减模型动态更新。这里有一个大坑SOC表示方式不一致会导致结果完全不对。有的文献用“能量状态单位MWh”有的用“荷电状态百分比 0-1”换算错了优化器给出的策略就会离大谱。5.3 求解器选择与参数调优我测试下来这类混合整数线性规划MILP问题用常用的商业求解器都能解但参数设置会影响求解速度和稳定性。求解时间限制设多少合适日前模型建议120秒日内模型60秒实时模型10秒。超出时间限制取当前最优可行解就行。MIP gap设多少我一般设1%工程上够用画出的图也说得过去求解时间能降不少。求解器线程数要不要限默认就好跑单组算例没必要折腾。有一个很容易踩的坑连续变量和二进制变量乘在一起误写成非线性项。Yalmip虽然能识别部分二次约束但性能会骤降。所以充放电互斥、机组启停这类逻辑一定要用二进制变量显式建模不要用大M方式糊弄过去。5.4 衰减成本嵌入目标函数的代码写法我用的衰减模型是线性化折算。假设每MWh的吞吐量对应一个衰减成本系数degrad_cost那么目标函数里加一项TotalCost TotalCost degrad_cost * sum(Pch Pdis) * dt;如果要做DOD级别的精细化衰减可以在循环里实时计算DOD再把对应的衰减成本累加进去。但要注意DOD和功率不是线性关系直接写进目标函数会变成非线性需要分段线性化处理。提示目标函数里的成本和约束里的量纲必须保持一致。我复现时因为价格单位用了元/MWh但储能容量单位是MWh功率单位MW盯了半天才发现dt换算漏了一处。6. 结果分析考虑衰减和不考虑衰减调度策略差在哪6.1 典型对比结果我跑了一个标准算例3台分布式机组、一座1MW/2MWh储能电站、50个聚合用户负荷和光伏曲线用经典开源数据。两种场景对比下来结果差异很明显指标不考虑衰减考虑衰减总运行成本万元/日12.813.4储能等效循环次数2.31.5储能容量衰减率%/日0.0720.045高峰时段储能放电占比68%52%可削减负荷调用比例23%18%看这个表考虑衰减后调度器明显“手软”了储能使用更克制总运行成本升高了5%左右但电池寿命损耗大幅下降。这就是衰减惩罚起的作用。6.2 画图建议结果可视化直接决定论文的观感。我常用的画图套路储能充放电功率用阶梯图stairs画能清楚看出时段启停机组出力用堆叠面积图能看出各机组承担基荷还是峰荷SOC曲线用plot画坐标轴范围设成0-110%避免截断看着像跑出边界对比图要放在同一张图里用不同颜色和线型方便一眼看出差异代码片段示例figure; subplot(2,1,1); stairs(t, Pch_opt, r, LineWidth, 1.5); hold on; stairs(t, -Pdis_opt, b, LineWidth, 1.5); legend(充电, 放电); xlabel(时间/h); ylabel(功率/MW); subplot(2,1,2); plot(t, SOC_opt*100, g, LineWidth, 1.5); xlabel(时间/h); ylabel(SOC/%); ylim([0, 110]);关于结果得“讲圆”比如考虑衰减后储能高峰放电占比下降是因为深放电的衰减惩罚大于峰谷套利收益调度器宁可多调用可削减负荷也不愿意牺牲电池寿命。这个结论就是论文的亮点。7. 踩坑记录与排查经验复现过程中的那些坑7.1 求解器报“无界解Unbounded”这个错误十有八九是约束漏了。我先查功率平衡约束再查SOC动态约束最后查机组出力上下限。最常见的原因是储能SOC的初值和终值约束没加导致能量凭空产生。我的习惯是给SOC加一个初始值和终值相等的约束也就是一天运行结束后SOC回到初始水平。这样既符合实际运行要求也能解决无界问题。7.2 求解时间爆炸如果模型变量不多但求解时间非常长大概率是二进制变量和连续变量的大M项引入了数值病态。我遇到一次变量才几千个求解器却跑了一个小时。排查方法先把所有二进制变量固定成确定值跑连续优化如果连续优化很快说明整数变量是瓶颈。接着逐个检查二进制约束看是否有多余的互斥约束导致分支树过深。7.3 SOC初值敏感策略漂移SOC初始值设0.5还是0.8调度结果完全不同。这在多场景对比时很烦。我的办法是先把SOC初值标定成统一基准或者在所有场景下都做“末端回位”约束消除初值差异对结论的干扰。7.4 容量衰减参数标定不准衰减参数alpha如果取得太大调度器会完全不敢用储能取得太小衰减约束形同虚设。我的经验是用电池厂商给的循环寿命参数按“20%容量衰减对应多少循环”反推alpha的合理区间再在这个区间做灵敏度分析。下面是一个速查表整理了我遇到的高频问题问题现象可能原因排查方法无界解功率平衡或SOC约束缺失逐条检查约束先补SOC初末约束求解时间过长大M值过大或冗余整数变量固定整数变量测连续求解缩小M值储能从不放电衰减成本系数过大查衰减系数和峰谷价差做对比负荷削减比例异常补偿价格设太高检查响应补偿价格是否超过购电价日内和日前结果冲突层间参数传递错误检查日前递交给日内的边界变量是否一致7.5 一个特别隐蔽的坑循环里更新容量导致历史依赖如果衰减约束里用了“累计放电量”这个状态量那模型就有了时间耦合并带有记忆效应。我在调试中发现如果不小心把累计放电量定义成因变量而某个时段无法求解后续所有时段的容量上限都会被污染。解决办法是把累计量定义为显式状态变量并加入每个时段的状态转移约束。这和SOC的处理方式一样只不过状态变量多了一个。8. 从复现到改进下一步能往哪个方向走这次跑通的基础版本是确定性多时间尺度调度代码框架搭好后往几个方向扩展都比较方便。第一个方向是多场景随机优化新能源和负荷的预测误差用多个场景描述把确定性模型改成两阶段或多阶段随机规划。主要改动是把约束复制到场景维度目标函数变成期望值。第二个方向是考虑备用容量和旋转备用约束把虚拟电厂参与辅助服务市场的能力建模进来。这个方向工程价值高因为单纯能量调度的利润空间很薄参与调频和备用市场才是大块收益。第三个方向是分布式求解多智能体架构下各资源主体独立优化通过一致性约束协调适合超大规模用户侧资源聚合。这个方向代码量会明显增加但研究价值也高。我个人在实际复现中的体会是决定一篇调度类文章质量的往往不是模型多复杂而是约束是否完备、参数设置是否合理、结果分析是否讲出了因果链条。把基础模型跑透、把每个结果的现象解释清楚再慢慢加复杂度路会比一开始就堆模型稳得多。最后再分享一个小技巧复现SCI论文时把论文里的每一张图当作验收标准。画出的图能和原图趋势对上模型基本就对了对不上就要回到约束和参数里去查。这个方法我用了很多年比反复读公式更高效。
返回列表