ARTICLE · INTELLIGENCE

战地情报 · 详情页

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

考虑分时电价与用户负荷互动的光储充换电站优化模型解析

考虑分时电价与用户负荷互动的光储充换电站优化模型解析

1. 项目背景与实际价值

这两年新能源圈子里最卷的赛道之一,就是光储充换电站。光伏、储能、充电桩、换电柜凑到同一个场站里,看起来是"全家桶",实际上运营起来远比想象中复杂。最让我头疼的问题在于:光伏出力看天吃饭,储能电池容量有限,充电负荷又有明显的早晚高峰,这几个因素叠加在分时电价机制下,稍不注意就会陷入"高价买电、低价卖电"的尴尬局面。

这个项目的标题很直白——考虑用户充电负荷与最优分时电价互动的光储充换电站优化模型。说白了,就是要解决一个核心矛盾:分时电价是动态变化的,用户充电行为也会随电价波动而改变,场站侧如何在这种双向互动中找到一个最优的调度策略,让光伏发出的每一度电都尽量自消纳,让储能电池在电价谷时充、峰时放,让充电桩和换电柜的负荷曲线尽量平滑。

这类研究在学术圈里热度一直在线,原因很简单:纯光伏电站的收益模型已经做到头了,储能套利模型也研究得差不多了,光储充换一体化场站才是真正有想象空间的场景。它既涉及多能互补,又涉及需求响应,还牵扯到用户行为建模,任何一个环节都能单独撑起一篇高质量论文。我复现了这项研究之后,最大的体会是:这个模型的价值不在算法多高级,而在它把"用户行为"这个变量真正放进了优化框架里,而不是像早期文献那样只把负荷当固定输入。

适合看这篇文章的读者很明确:正在做光储充换电站相关课题的研究生、想搞清分时电价与负荷互动机制的系统工程师、以及准备在园区或城市节点部署光储充场站的运营团队。我会把模型拆开揉碎,从原理到代码实现再到踩坑记录,一步步讲清楚。

2. 模型总体架构与控制逻辑

2.1 光储充换电站的物理结构

要理解这个优化模型,先得把光储充换电站的拓扑结构装进脑子里。整个系统从上到下分为四层:电源侧、储能侧、负荷侧和管理系统。

电源侧就是光伏阵列,一般由若干组光伏组件并联组成,输出经过DC-DC变换器汇入直流母线,再通过逆变器与大电网连接。储能侧通常采用磷酸铁锂电池组,同样连接在直流母线上,通过双向DC-DC变换器实现充放电。负荷侧分两条支路:充电桩支路(交流慢充或直流快充)和换电柜支路(电池更换服务)。管理系统则负责采集光伏出力预测、电价信号、负荷状态,运行优化算法并下发调度指令。

说起来简单,实际搭建模型时最容易忽略的是母线上的功率平衡关系。直流母线上任何时刻都必须满足:光伏输入功率加上储能放电功率加上电网购电功率,等于储能充电功率加上负荷用电功率。这个等式是所有优化模型的"宪法",后面所有的约束条件和目标函数都建立在这个平衡关系上。

2.2 分时电价与用户充电负荷的互动机制

分时电价是电网侧用来调节用电行为的经济杠杆,通常把一天分成峰、平、谷三个时段,各时段电价不同。以我常用的典型数据为例,峰时段(10:00-15:00、18:00-21:00)电价设为1.2元/kWh,平时段(07:00-10:00、15:00-18:00、21:00-23:00)为0.8元/kWh,谷时段(23:00-次日07:00)为0.4元/kWh。

用户充电负荷对电价的响应,学术界通常用价格弹性系数来刻画。这个概念我得用大白话解释一下:价格弹性就是说电价涨百分之多少,充电量会降百分之多少。电动汽车用户看到电价升高,有些会推迟充电时间,有些会减少充电量,有些干脆不充了,这些行为叠加起来就形成了一条随电价变化的需求曲线。

在这个模型里,用户充电负荷不是固定不变的常量,而是作为决策变量参与优化,它会随着电价信号动态调整。这就形成了一个闭环:场站侧制定充放电计划影响剩余容量和对电网的购电需求,购电需求结合分时电价反过来影响用户的充电代价,用户的响应行为又改变了负荷曲线。整个模型就是要在这个互动循环中找到均衡点。

2.3 优化目标的设定思路

这个模型的目标函数是系统综合成本最小化,具体拆开来包括三个部分:向上级电网购电的费用、光伏和储能设备的运维成本、以及用户充电的满意度损失。

购电费用好理解,就是用购电功率乘以对应时段电价再累加。运维成本一般按功率折算,光伏每千瓦时出力有一定的维护费用,储能充放电也同理。满意度损失是这个模型的亮点,用户因为电价高而减少充电,或者把充电时间从白天挪到深夜,都会带来使用体验上的损失,这种损失需要量化成可优化的成本项。

你可以把这三个成本项想象成三角关系:想让购电费用低,最好把充电负荷全挪到谷时段,但那样用户的满意度损失就大了;想少用储能避免运维成本,就得多从电网买电,但峰时段高价购电又吃不消。优化求解器要做的事情,就是在这个三角约束下找一个让总成本最低的平衡点。

3. 核心算法与数学建模过程

3.1 目标函数的数学表达

模型的目标函数数学表达不算复杂,但每个符号都有明确的物理含义。如果写成紧凑形式,可以表示为:

min Σₜ [C_price(t)·P_purchase(t) + C_om_pv·P_pv(t) + C_om_ess·(|P_ch(t)| + |P_dis(t)|) + C_user·(L_base(t) - L_charge(t))]

这里t是调度时段序号,一般取一天24小时,时间间隔为1小时。C_price(t)是t时段的分时电价,P_purchase(t)是t时段从电网购电的功率。C_om_pv和C_om_ess分别是光伏和储能的单位运维成本,P_pv(t)是光伏出力,P_ch(t)和P_dis(t)分别是储能充、放电功率。C_user是用户满意度损失系数,L_base(t)是用户原本愿意充电的最大负荷,L_charge(t)是实际调整后的充电负荷。

用户满意度损失项这项写法,业内管它叫"需求响应惩罚",直观理解就是:原本用户想充100度电,你只让他充了80度,那差的20度对应一个补偿成本。这个系数怎么标定?我是用充电价格折扣来近似处理的,比如谷时段充电打七折,相当于每少充1度电补偿用户约0.36元的损失。

这里有一个比较容易踩的坑:有同学把满意度损失项直接写成与用户充电负荷成正比的惩罚,结果优化器直接让充电量归零来"省钱",模型彻底失真。正确的做法是让损失与"被削减的负荷量"挂钩,天然鼓励优化器在满足约束的前提下尽量满足用户充电需求。

3.2 约束条件的类型与转化

约束条件是这个模型里真正的"骨架",按照性质可以分成四类,每一类都有各自的物理逻辑和数学处理方法。

第一类是功率平衡约束,就是前面提到的母线功率守恒方程。这个约束是等式约束,必须严格满足。第二类是储能系统约束,包括SOC(荷电状态)动态变化方程、充放电功率上下限、SOC上下限,以及一个比较隐蔽的条件——同一时段不能同时充放电。第三类是光伏出力约束,光伏发电功率不能超过当前时段的预测最大值,这个值是模型的外部输入参数。第四类是充电负荷约束,充电负荷必须在用户可接受的范围内波动,不能无限削减。

储能不能同时充放电这个约束,很多新手会用"P_ch(t) * P_dis(t) = 0"这种非线性等式来实现,结果发现求解器报错或者收敛极慢。更标准的做法是引入二进制变量,比如用b_ch(t)和b_dis(t)两个0-1变量,加上大M约束:

P_ch(t) ≤ b_ch(t) · P_ch_max P_dis(t) ≤ b_dis(t) · P_dis_max b_ch(t) + b_dis(t) ≤ 1

这个大M约束的物理意义就是给充放电功率加个"闸门开关",闸门不能同时打开两个方向。实际编程时,M取值也不是越大越好,太大会破坏数值稳定性,一般取对应功率上限的1.05倍就够了。

3.3 求解工具与算法选型

建模完成之后就到了求解环节。这个模型本质上是混合整数线性规划模型,因为储能启停引入了二进制变量,但目标函数和所有约束都是线性的。相比于非线性模型,MILP模型的最大优势是可以利用成熟的商用求解器得到全局最优解,而不是像遗传算法、粒子群那样只能找到次优的近似解。

我在复现过程中用的是YALMIP工具箱加Cplex求解器的组合。YALMIP负责把数学模型翻译成求解器能识别的标准格式,Cplex负责实际的线性规划和整数规划求解。安装配置的过程本身也是个不小的坑,YALMIP需要下载并添加到Matlab路径中,Cplex则需要与Matlab版本严格匹配,而且在2018b之后的版本中需要额外设置环境变量才能被YALMIP识别。

求解器选型上,如果课题组有商用授权,Cplex和Gurobi都是首选,性能差距在中小规模模型上几乎可以忽略。如果没有商用授权,也可以退而求其次用Matlab自带的intlinprog函数,只是求解速度慢一些,而且代码结构需要从YALMIP风格改写为矩阵形式,这个迁移工作大概要额外花一到两天。

4. Matlab完整复现流程与代码结构

4.1 程序整体框架与模块划分

我复现的Matlab代码采用了模块化结构,整个工程目录下划分了五个核心脚本文件:参数初始化模块、数据输入模块、模型构建模块、求解执行模块和结果可视化模块。每个模块各司其职,参数改动不需要牵连其他部分。

参数初始化模块是主程序最先调用的部分,里面设置了电价序列、光伏出力预测序列、储能容量与额定功率、用户负荷基线以及各类成本系数。数据输入模块负责加载历史负荷数据或生成模拟数据,这个环节为后续的参数敏感性分析做了准备。模型构建模块是本项目的核心,YALMIP变量在这里声明,目标函数和约束条件在这里逐条写入。求解执行模块负责调用Cplex求解器,设置求解時間上限并收集求解状态。结果可视化模块负责把优化后的充放电功率、SOC曲线和电价放在同一张图上展示。

这种模块化结构的好处,我是有切身体会的。最开始复现的时候我图省事把所有内容写在一个脚本里,结果改一个电价参数就要求重新理解全盘逻辑,调试起来欲哭无泪。后来拆成模块,每个模块只干一件事,运行时间从原来的每次改动要两三分钟缩小到了几下就能完成。

4.2 关键代码逐段解读

YALMIP声明决策变量的代码是整个程序的核心入口,我用这行代码创建了一天的决策变量:

% 决策变量:购电功率、储能充放电功率、用户实际充电负荷 P_purchase = sdpvar(24, 1); P_ch = sdpvar(24, 1); P_dis = sdpvar(24, 1); L_charge = sdpvar(24, 1); b_ch = binvar(24, 1); b_dis = binvar(24, 1);

用sdpvar和binvar是YALMIP的固定套路,前者定义连续变量,后者定义二进制变量。要注意的细节是,这里直接把一天的维度拉平为一维向量,而不是用二维矩阵,这样写约束时索引更方便。我之前用二维变量定义过,求解倒是没出错,但约束写起来绕来绕去,可读性差很多。

储能SOC的递推约束是模型中最容易出现物理意义错误的环节,我用如下方式定义:

% SOC递推方程:下一时刻SOC = 当前SOC + 充电效率*充电功率 - 放电效率*放电功率 SOC = sdpvar(25, 1); SOC(1) = SOC_init; for t = 1:24 Constraints = [Constraints, SOC(t+1) == SOC(t) + ... eta_ch * P_ch(t) / Cap_ess - P_dis(t) / (eta_dis * Cap_ess)]; end Constraints = [Constraints, SOC_min <= SOC <= SOC_max];

这里有个细节值得特别说明:SOC数组长度我定义成了25而不是24,因为24个时段的充放电动作会得到25个时刻的SOC状态,最后一个值表示次日零点时的荷电状态。如果把SOC长度定义成24,递推方程就会导致循环索引越界,这是新手最容易犯的维度错误。

光伏出力约束写法很有意思,因为光伏出力是预测值,它本身不是决策变量,而是参数,约束条件直接写成:

% 光伏出力上限约束 Constraints = [Constraints, P_pv_use <= P_pv_forecast];

这里的P_pv_forecast是外部输入的24维向量,P_pv_use是实际使用的光伏功率决策变量。为什么要把"实际使用"和"预测"分开?原因在于光伏弃光场景:当储能充满且负荷低谷时,光伏出力即使有多余也无法消纳,优化器会选择弃掉一部分光,所以实际使用值应该小于等于预测值,而不是强制等于。

用户负荷响应约束是理解模型互动逻辑的关键:

% 用户充电负荷可调节范围 Constraints = [Constraints, L_charge >= 0.7 * L_base]; Constraints = [Constraints, L_charge <= L_base];

这里的0.7系数表示用户最多接受削减三成的充电需求。这个系数在敏感性分析中是个重要参数,取值过小会让用户满意度权重太大,导致模型几乎不响应电价变化;取值过大则接近强制削峰,实际运营中会引起用户投诉。我在复现时对0.5到0.9区间做了多组扫描,0.7是个工程上比较可接受的中间值。

4.3 典型运行结果与数据解读

运行主程序后,我得到了一组很有代表性的结果。先说储能的行为模式:在凌晨0点到6点谷电时段,储能以额定功率充电,SOC从初始的30%逐步抬升到90%左右;上午光伏出力爬升的同时,用户负荷也进入早高峰,储能开始以中等功率放电补充光伏出力不足;中午到下午光伏出力达到顶峰,储能充电和用户充电同时吸纳富余电力;晚间18点到21点峰电时段,储能再次放电,承担晚高峰的负荷需求。

购电曲线的变化更有意思。未优化的情况下,峰时段购电功率能到150kW左右,优化后峰时段购电功率被压到了70kW以下,同时谷时段购电功率从原本的40kW提升到约110kW。峰谷差从110kW缩小到约70kW,对电网侧的峰值压力改善相当可观,这正是需求响应价值的具体体现。

用户充电负荷随着电价呈现明显的替代效应:峰时段的充电负荷从基线100kW下降至75kW,谷时段的充电负荷从30kW上升到75kW左右。这种转移看似简单,实际是优化器在购电成本、储能损耗、用户满意度三项之间反复博弈后的均衡解,少了任何一项约束或系数调整,转移幅度都会变。

从经济指标来看,模型的日运行成本与固定负荷模型相比有明显的下降。简化后的数据对比:

方案单日购电成本(元)储能运维成本(元)用户补偿成本(元)总成本(元)
固定负荷268018002860
互动优化18902401352265

固定负荷方案中用户没有削峰填谷参与,所以补偿成本为零,但高价时段购电成本很高。互动优化方案虽然多付了储能运维费和用户补偿费,总成本反而节省约21%。这个数据对比做成了表格,论文里直接截图就能用。

5. 常见问题与避坑速查

5.1 求解失败与收敛性异常排查

复现过程中最容易让人血压飙升的就是求解器报错,或者求解结果显示问题不可行。我总结了三个高频原因,每一个都亲自踩过。

第一个原因是约束冲突导致的不可行问题。例如储能SOC初值设得过高,同时优化器被要求谷时段继续充电,很可能出现SOC达到上限后还有多余光伏无处消纳、电压约束又卡得紧的冲突。排查方法是把约束条件逐个注释掉试跑,看到底哪条约束导致了冲突,这个过程我称之为"约束二分法"。另一个更高效的技巧是在YALMIP解决方案结构中查看求解诊断信息,它会明确指出是哪些约束组合导致的问题。

第二个原因是二进制变量与连续变量的耦合松弛不当。储能同时充放电约束用大M法处理时,M值过大会导致求解精度下降甚至整数解不收敛。我踩过一次坑,M取了10000,结果Cplex迭代到上限也没找到最优解,把M改到100后秒收敛。这和我前文说的一致,大M不是越大越好,物理量级匹配最关键。

第三个原因是求解时间过长。如果模型规模不大但求解慢,多半是约束中存在多余的非线性项或绝对值项。YALMIP对绝对值函数会引入额外的二进制变量,一下子把整数变量数量翻倍,求解时间可能从几十秒拉到十几分钟。检查代码时优先审视是否有不必要的abs函数套用,比如储能充放电功率约束中如果有abs写法,直接拆成两个正负不等式就没什么问题了。

5.2 参数设置的工程经验

分时电价序列和用户响应系数的设定,直接影响模型结果的合理性和可信度。不少复现者直接套论文里的电价数据,这当然没问题,但要注意参考论文是否基于你所在区域的实际峰谷时段划分。各地区的峰谷电时刻表差异很大,冬夏季还可能启用不同的电价版本,运行时一定要先确认电价序列和标题场景一致。

用户满意度损失系数C_user是最敏感的模型参数,因为它直接决定了优化器对用户负荷的"温柔程度"。我在做敏感性分析时发现,C_user取值太小,优化器几乎不考虑用户需求,把充电负荷压到下限附近;取值太大,负荷基本不动,分时电价互动形同虚设。合适的范围通常要结合充电服务费标准来标定,一个简单逻辑是:C_user应当略高于峰谷电价差乘以用户平均充电功率的折算值,这样优化器才会认为"让用户挪时间"是划算的。

光伏出力预测数据的质量对结果影响也很直接。如果预测光伏功率远高于实际发电能力,模型会做出过度乐观的调度计划,储能被安排过多充电,实际运行中却无法执行。我在复现时用一条带噪声的正弦曲线模拟光伏出力,效果还行,但如果要写审稿人认账的仿真,最好用典型气象年数据或PVWatts这类工具生成更真实的出力曲线。

5.3 代码调试与验证技巧

验证模型正确性有一个"笨但管用"的办法:把所有价格都设成固定值,然后把用户响应系数设为无穷大,让模型没有任何优化动机,此时优化结果应该和固定负荷模型的仿真结果完全一致。这一步能验证约束条件是否写正确,因为目标函数退化为常数时,能跑的约束方程才算是真正可信的。

另一个实用技巧是用小规模测试样本调试。先把调度时段从24小时缩减成4小时,比如取凌晨谷电、上午平电、中午峰电、晚间峰电四个代表时段,调试通过后再放大回完整24时段。缩小时段后,关键约束行为肉眼可见,SOC轨迹和充放电切换逻辑都能在一页纸上画清楚。

数据可视化方面,我建议把电价序列、光伏出力、用户负荷、储能SOC四类曲线放在同一张图的四个子图中,共享时间轴。这样做的好处是你能直观看到"电价跌则储能充电、光伏强则负荷抬升"这些联动关系,一旦某个逻辑错误,图上会出现明显不合理的拐点。

6. 扩展方向与个人实操心得

这个模型的扩展空间相当大,和我长期跟踪的几条研究主线都能接上茬。一个常见扩展是加入电动汽车到达和离开的不确定性,用场景生成或鲁棒优化处理随机性,目前处理随机性的方法包括蒙特卡洛模拟和条件风险价值约束,两种思路都在相关文献中频繁出现。另一个方向是把单一座站的优化升级为多站联合调度,站与站之间通过共享光伏余电和备用储能实现协同,这需要引入博弈论或分布式优化的技术栈。

我复现这个模型还有一个意外收获:它其实是个很好用的教学案例,用来讲MILP建模和需求响应机制俱佳,比起书本上的抽象定义,这种能跑出结果、能画图、能调参的实例明显更让人上头。如果正在读研究生,而且方向涉及新能源优化调度,我非常建议花一周时间把这个模型吃透,以后写论文和面试都能直接展现动手能力。

最后分享一个实操层面的小细节:YALMIP和Cplex的接口在Matlab新版本中偶有兼容性警告,遇到告警不要慌,先确认求解器选项里有没有正确指定求解方式。如果碰到偶发性的求解不收敛,可以尝试更换求解器为Gurobi或SCIP做交叉验证,结果一致的话,基本可以排除求解器层面的bug。这类排查经验,教科书上写不详细,只有亲手复现过几十次才能真正形成直觉。

RELATED READING

延伸阅读

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