最近在复现一个光储充换电站的联合优化模型,核心是把用户充电负荷、分时电价和光伏-储能-换电业务统一到一个框架里求解,用Matlab把整套代码完整跑通。这类模型在新能源配电网、充电基础设施规划、需求响应策略里都很有代表性,网上不少论文标题长得很像,但真正能落地的可复现代码并不好找。这篇文章把我踩过的坑和理顺的思路一起拆出来,给正在做充换电站调度优化、分时电价策略、光储协同研究的朋友一个可以直接开跑的参考方案。
我不打算只贴代码,那样没意义。咱们先从“这个模型到底在优化什么”讲起,把目标函数、约束条件、用户行为建模逻辑讲透,再一步步落到Matlab实现上。看完之后你至少能回答三个问题:最优分时电价为什么不能单独设计?储能和换电资源在模型里该怎么衔接?复现这类模型时最常见的求解报错怎么快速定位。
1. 模型想解决什么问题:充换电站的收益从哪来
1.1 充换电站的运营场景与收益结构
光储充换电站不是一个单一概念,它把四类设备绑在同一个配电母线上:屋顶或车棚光伏、储能电池系统、交流慢充桩和直流快充桩,外加换电仓库以及站内电池充电机。运营主体既卖电给充电用户,也提供换电服务,同时还能利用光伏自发电和储能错峰套利。
运营商的日收益来源可以拆成四块:
- 充电业务收入:用户充电电量乘以充电电价,再加上每度电的服务费。
- 换电业务收入:每次换电固定收费,换电次数由站点的服务对象决定。出租车、网约车是典型的高频换电客户。
- 光伏发电收益:光伏出力优先自用,多余电量上网结算。
- 储能间接收益:低谷时段从电网买便宜电存入电池,高峰时段放出供负荷使用,赚取峰谷价差。
支出侧主要是向电网购电的成本、储能循环老化的损耗成本、换电电池的充电损耗和人工运维成本。目标很清楚:在满足用户充电/换电需求的前提下,找到一种分时电价策略和设备调度策略,让日净收益最大。
1.2 为什么必须把“用户充电负荷”和“分时电价”绑在一起优化
很多人最开始会做成两步:先给定一个固定的分时电价,再优化储能充放电。这种做法不是不行,但它忽略了一个关键机制——用户不是木头人,充电负荷会随着电价变化而改变。
举个很直观的例子:如果固定晚高峰电价为1.2元/度,用户就会有一部分人推迟充电,或者干脆去附近更便宜的站。反过来,如果你把低谷电价降得足够低,原本早上才充的车可能晚上就插上桩。所以运营商的电价决策会直接影响负荷曲线,负荷曲线又反过来影响储能策略和购电计划,这是一个双向互动。
把用户充电负荷和分时电价放进同一个优化问题里,本质上是同时决策“价格信号”和“设备响应”。这比传统预测负荷然后调度设备的思路高一个维度,也更能反映真实运营逻辑。论文里常见的主从博弈、价格弹性矩阵、可转移负荷模型,都是在处理这层互动关系。
1.3 模型边界和简化假设
复现这类模型时,我建议先建立一套合理但不复杂的边界假设,否则模型复杂度过高,很难在普通电脑上求解。我这次复现采用的假设如下:
- 时间粒度为1小时,优化周期为24小时。这个粒度足够观察峰谷变化,也方便和分时电价时段对齐。
- 所有充电桩聚合为总负荷,不逐台细化功率分配,避免引入大量整数变量。
- 换电电池规格统一,每块电池充电所需时间固定为2小时,忽略电池健康状态差异。
- 光伏出力曲线作为已知输入,由典型日的日照数据换算得到,不做随机场景。
- 用户对电价的响应采用自弹性系数近似,暂不考虑用户对相邻时段电价差异的交叉弹性。
这些假设会让模型变成一个“双层联动、单层求解”的问题:把用户响应表达式直接嵌入电价决策变量,然后统一交给求解器处理。既保留互动行为,又不至于把自己困在复杂的博弈迭代里。
2. 核心建模:用户负荷、电价响应和光储换电的数学表达
2.1 用户充电负荷的蒙特卡洛建模
基础充电负荷是整个模型的起点。我常说一句话:调度优化能不能出靠谱结果,一半取决于基础负荷是怎么造出来的。用蒙特卡洛模拟单辆电动汽车的充电行为,再累加成站级负荷曲线,是复现这类模型的主流做法。
每辆车的模拟流程是这样的:
- 抽样电池容量:常见乘用车电池容量可设为40、60、80kWh三种规格,分别占40%、40%、20%。
- 抽样日行驶里程:用正态分布近似,均值40km、标准差15km,小于0则截断为0。
- 抽样到达时刻:私家车用户晚高峰到达站点的概率最高,到达时间近似服从均值18时、标准差2小时的正态分布。
- 计算充电需求量:充电能量 = 日行驶里程 × 百公里耗电(按15kWh/100km计算),再限制在电池容量与当前SOC之差以内。
- 分配充电功率和时长:慢充桩功率7kW,快充桩60kW。慢充车辆按到达时间依次累加功率到对应时段。
用代码表达就是下面这样,这段代码是真实可跑的,关键是理解随机抽样的逻辑。
function L_base = mc_charging_load(n_car, seed) % 用蒙特卡洛生成24小时基础充电负荷 rng(seed); % 保证可重复 L_base = zeros(24, 1); battery_cap = [40 60 80]; cap_prob = [0.4 0.4 0.2]; p_charge = 7; % 慢充功率,kW for i = 1:n_car cap = battery_cap(randsample(1:3, 1, true, cap_prob)); km = max(0, normrnd(40, 15)); energy_need = km * 0.15; % 每公里耗电0.15 kWh initial_soc = 0.2 + 0.3 * rand; % 初始SOC随机 max_charge = cap * (1 - initial_soc); energy_need = min(energy_need, max_charge); t_arrive = round(normrnd(18, 2)); t_arrive = min(max(t_arrive, 1), 24); duration = ceil(energy_need / p_charge); for t = t_arrive : min(24, t_arrive + duration - 1) L_base(t) = L_base(t) + p_charge; end end end这里有一个很多人容易忽略的细节:如果到达时刻加上充电时长超出了24时,超出部分应该截断,不要循环回第二天。否则会把深夜的负荷算高,导致分时电价优化出来的谷时段电价失真。我最初复现时在这个细节上吃过亏,后面在问题排查环节会详细说。
2.2 用户对分时电价的响应机制
有了基础负荷,接下来就要回答一个问题:电价变了,用户充电负荷怎么变?
我这次采用的自弹性模型比较简单直观:实际充电负荷等于基础负荷乘以一个与电价变化率相关的因子。
L_resp_t = L_base_t * (1 + epsilon * (p_t - p_ref) / p_ref)其中epsilon是价格弹性系数,取负值,比如-0.3。p_ref是参考电价,取0.7元/kWh。这个公式的含义是:某时段电价高于参考值时,该时段实际充电负荷相对基础负荷下降;电价低于参考值,负荷上升。弹性绝对值越大,用户对价格越敏感。
这里有一个需要谨慎处理的点:电价优化后某个时段的响应负荷可能被推到上限甚至变成负值,必须加约束限制。
L_resp >= 0 L_resp <= L_base + L_shift_upper同时还要保证用户总的充电能量不能因为价格信号而大幅缩水。因为现实中大多数用户“该充的电还是得充”,只是换个时间充而已。可以加一个总能量约束:
sum(L_resp) * dt >= 0.95 * sum(L_base) * dt这个约束的作用很大:它一方面保证了负荷响应不会导致总充电需求大幅流失,另一方面也间接限制了电价优化不能离谱。如果电价被推得过高,用户总负荷大幅下降,总能量约束就会拉回来。
2.3 光伏出力和储能系统的数学模型
光伏部分我用典型日出力曲线作为输入,单位是kW。模型层面不需要复杂的辐照度计算,直接把24小时的光伏预测功率传进平衡约束就行。关键在储能系统。
储能电池的核心状态是荷电状态SOC。用两个非负变量分别表示充电功率和放电功率,并加互斥约束,避免模型通过同时充放电“空转套利”形成的假收益。SOC递推公式为:
SOC(t+1) = SOC(t) + eta_ch * P_ch(t) * dt - P_dis(t) * dt / eta_dis充电效率和放电效率分别取0.95和0.95。SOC的上下限通常取0.1和0.9,防止过充过放;储能功率上限由电池PCS容量决定。充放电互斥用一个二进制变量实现:
isc = binvar(T,1); Constraints = [Constraints, 0 <= P_ch <= P_max * isc]; Constraints = [Constraints, 0 <= P_dis <= P_max * (1 - isc)];之所以强调互斥约束,是因为如果不加,线性规划求解器非常容易让P_ch和P_dis同时大于0,形成循环电流:一边充电一边放电,SOC不变但两个功率都在计成本或计收入,最后产生一个完全虚假的收益。这是复现储能优化模型时最常见的“幽灵收益”来源。
储能老化成本我用线性近似:每次充放电1kWh产生0.05元损耗,计入目标函数。这样模型会自动权衡套利收益和电池寿命损耗,不会出现为了赚几分钱价差就疯狂充放电的极端解。
2.4 换电资源调度的离散化约束
换电业务的建模比纯充电复杂,因为涉及电池的物理状态:车来换电,必须是仓库里有充满电的电池才能服务;换下来的空电池需要插到充电机上补电,充满后才能再次投入使用。
这里我采用一个可求解的库存转移模型:
- B_ready(t):t时段开始时满电电池库存,单位块。
- N_swap(t):t时段换电需求次数,来自历史数据或蒙特卡洛抽样。
- N_ch_start(t):t时段开始充电的空电池数量,受充电机数量限制。
- 每块电池充电耗时D个时段,所以t时段开始充电的电池在t+D时段完成充电。
约束写成递推形式:
B_ready(t+1) = B_ready(t) - N_swap(t) + N_ch_start(t-D) B_ready(t) >= N_swap(t)这个模型能同时抓住两个核心约束:满电电池库存不能断,充电机资源不能超。在Matlab/Yalmip里用intvar定义N_ch_start就可以,站点规模不大时求解速度完全可以接受。
换电服务收入同样计入目标函数,按次数收费。换电车辆的高随机性让站点天然存在备用需求,所以模型中必须保留一道硬约束:每个时段换电需求都必须被满足,如果实在无法满足,可以引入一个很大的惩罚系数做松弛,但我建议先不加惩罚,直接设置硬约束,更容易排查模型可行性问题。
2.5 目标函数设计:收益、成本与惩罚项
把前面所有模块串起来,目标函数定义为日净收益最大化:
最大化: sum_t [ p_t * L_resp_t % 充电电费收入 + c_service * L_resp_t % 充电服务费收入 + c_swap * N_swap_t % 换电服务收入 + p_fit * max(P_pv_t - P_use_t, 0)] % 光伏余电上网收入 - sum_t [ c_buy_t * P_buy_t ] % 购电成本 - sum_t [ c_aging * (P_ch_t + P_dis_t) ] % 储能老化成本同时必须加用户费用约束,防止电价被优化到一个不合理的极端水平。我用的约束是:优化后的用户总充电费用不超过固定电价方案的用户总费用乘以一个比例系数。比如限制在110%以内,这样电价可以在峰谷之间合理调整,但不能显著损害用户利益。
这个约束我在第一次跑模型时完全没加,结果求解器把高价时段的电价直接推到上限,同时低价时段几乎零电价,看起来收益巨大,实际上完全不符合市场逻辑。加了用户费用约束之后,电价优化结果才变得可解释。
3. Matlab复现要点与关键代码拆解
3.1 整体代码框架与文件规划
复现这类模型不要把所有代码塞进一个脚本。我建议按职责拆成下面这组文件,维护和调试都会轻松很多:
main.m % 主入口:参数读取、建模、求解、出图 data_case.m % 算例参数:容量、电价上下限、弹性系数 load_mc.m % 蒙特卡洛生成基础充电负荷 pv_data.m % 光伏出力曲线 build_model.m % 用Yalmip构建约束和目标 plot_result.m % 结果可视化主文件里只要做三件事:调用参数配置、调用模型构建、求解并保存结果。模型构建单独放一个函数的好处是,以后从单目标改成多目标、从确定性改成鲁棒优化,都不需要动主程序。
3.2 核心变量与Yalmip建模语法
Yalmip是Matlab环境下很方便的建模语言,它的核心思路是用sdpvar定义矩阵变量,用constraints拼接约束,最后调用外部求解器。
我这次的核心变量定义如下:
T = 24; p_price = sdpvar(T, 1); % 分时电价决策变量 L_resp = sdpvar(T, 1); % 响应后的充电负荷 P_ch = sdpvar(T, 1); % 储能充电功率 P_dis = sdpvar(T, 1); % 储能放电功率 SOC = sdpvar(T+1, 1); % 储能SOC B_ready = sdpvar(T+1, 1); % 换电满电库存 N_ch = intvar(T, 1); % 换电电池开始充电数量这里把L_resp也设为变量、而不是直接写成一个表达式,是为了方便加上下限和总能量约束。电价和负荷之间的响应关系作为等式约束写入:
L_resp == L_base .* (1 + epsilon * (p_price - p_ref) ./ p_ref);这条等式带了二次项p_price * L_resp,所以整个模型本质上是一个带有二次目标和二次约束的优化问题。Gurobi或CPLEX都能处理中小规模的二次规划,24时段变量规模完全没问题。
3.3 求解器选型与求解策略
求解器我用的是Gurobi,通过Yalmip调用。安装配置好之后,求解设置只需两行:
ops = sdpsettings('solver', 'gurobi', 'verbose', 2); optimize(Constraints, -Objective, ops);Yalmip默认求最小值,所以目标函数取负号。
需要注意一点:如果模型里有intvar变量,Gurobi会自动识别为混合整数模型,求解时间会显著增加。第一次复现时建议先不优化换电电池充电数量,改用连续变量近似,等模型跑通后再逐步把整数约束加回去。
关于分时电价的决策形式,我做了两种对比:一是把24个时段电价全部作为独立变量,自由度很高但结果容易抖动;二是把一天划分为峰、平、谷三个时段,同一时段内电价相同,只需3个变量。第二种更贴近现实中的分时电价表,结果也更稳定。我建议入门阶段用三段式,跑通后再放开成24时段独立电价。
3.4 稳定性细节:互斥约束、SOC终态和不可行解
有几个细节直接决定模型能不能一次求解成功,这里单独拿出来强调。
第一,储能SOC的终态约束。优化周期结束时SOC应该回到初始值附近,否则模型会把初始储存的电能全部放光,收益虚高。我用的约束是:
abs(SOC(25) - SOC(1)) <= 0.05 * E_bess这条约束代表一天运行结束后储能电量基本恢复,不占用“免费初始能量”。
第二,充放电互斥约束前面已经讲过,不再重复,但我要提醒:实现互斥的二进制变量isc只能取0或1,千万不要把isc定义为连续变量,否则两个方向的功率可以同时为正,模型直接失真。
第三,换电库存初始值要合理。满电初始库存设得过高,模型会通过释放库存赚取收益,最后无解或出现不合理的负库存。合理做法是设成一天中最差情况下的安全库存,比如18块。
4. 仿真算例与结果解读
4.1 模型参数与基础场景设置
为了说明模型效果,我设计了一个中等规模的站级算例,参数如下:
| 参数 | 数值 | 说明 |
|---|---|---|
| 光伏装机容量 | 800 kW | 屋顶和车棚光伏 |
| 储能容量 | 500 kWh | 额定功率250 kW |
| 慢充桩 | 120个 × 7 kW | 面向私家车 |
| 快充桩 | 20个 × 60 kW | 面向网约车 |
| 换电电池总数 | 30块 | 初始满电库存18块 |
| 换电高峰时段 | 7:00-9:00、16:00-19:00 | 出租车交接班特征 |
| 峰谷购电价 | 1.1 / 0.7 / 0.4 元/kWh | 典型商业电价 |
| 充电服务费 | 0.4 元/kWh | 固定值 |
| 换电服务费 | 20 元/次 | 固定值 |
| 用户价格弹性 | -0.3 | 自弹性系数 |
| 参考电价 | 0.7 元/kWh | 弹性公式基准值 |
基础充电负荷通过蒙特卡洛抽样150辆车得到,并加入换电仓库充电机的固定充电负荷,合成总基础负荷。
4.2 分时电价与用户负荷的互动结果
优化后的分时电价呈现非常典型的“低谷狠压、高峰稳增”形态:低谷时段电价从基准值0.7元降到0.45元左右,晚高峰时段升到1.05元左右,整体仍在约束范围内。用户总费用约束把电价限制在一个合理区间,所以峰谷价差没有被无限拉大。
用户负荷的响应方向也很清晰:原本集中在18点到20点的高峰充电负荷,有一部分转移到22点到次日凌晨2点。响应后的峰值负荷比基础负荷下降约18%,峰谷差缩小约22%。
这个结果说明电价信号确实起到了削峰填谷的作用。没必要追求把峰值削得很低,因为那意味着用户大量转移充电行为,可能会影响充电便利性,总能量约束也会限制这种转移。
4.3 储能套利、光伏自用和换电调度效果
在这套场景下,储能基本在凌晨低谷时段充电,在晚高峰时段放电。单日充放电量约430 kWh,按峰谷价差0.6元/kWh计算,储能套利贡献约258元,扣除老化成本后仍然正向收益。光伏的800 kW峰值出力出现在中午,除自用外约有30%光伏电量上网。
换电调度方面更有意思。模型会把换电电池的充电负荷尽量安排在低谷时段:夜间大部分空电池集中充电,白天只补充必要库存,保证早晚高峰的换电需求不断档。优化后换电仓库充电负荷在白天高峰时段下降了约40%,夜间上升约50%,整体实现了“夜间蓄能、白天供能”的运营节奏。
4.4 对比实验:固定电价与优化电价的收益差异
为了量化“电价互动”的价值,我用固定分时电价方案作为基准,和优化方案做了对比。同样一套储能和换电约束下,固定电价方案的日净收益设为100%,优化分时电价方案大约高出18%到22%。
收益提升的来源拆开来看,大约一半来自储能更精准的低谷充电、高峰放电,另一半来自电价峰谷形态引导用户负荷优化的购电成本下降。可见“电价当成决策变量”不是花架子,它确实带来了可量化的运营改善。
当然,这个比例和用户弹性系数强相关。弹性系数绝对值越大,电价互动的收益空间越大。如果你把弹性系数设为-0.1,优化空间会明显缩小;设为-0.5,收益提升比例可能超过30%。所以在汇报结果时一定要说明参数假设,否则数字之间没有可比性。
5. 复现中踩过的坑与排查清单
5.1 求解不收敛或目标无界的处理思路
我第一次跑通时遇到的问题是目标无界,求解器返回“unbounded”或“infeasible”。这类问题十有八九是缺约束。
目标无界最典型的原因是没有限制电价上限,求解器会把电价推向无穷大来“增加收益”。解决方法是加上电价上下限约束和用户总费用约束。另一个原因是没有SOC终态约束,储能初始电量被当成免费能量全部放空。
排查方法是一层层加约束:先固定电价,只优化储能,跑通后再放开电价;先不加换电模块,跑通充电负荷加储能,再加换电约束。这种“抽屉式排查”要比盯着模型硬想高效得多。
5.2 蒙特卡洛负荷数据波动大怎么办
蒙特卡洛模拟的随机性会让每次跑出来的基础负荷不一样。如果直接用单次抽样结果,优化结果可能偏差很大,也很难复现。
我用两个手段解决:第一,通过rng(seed)固定随机种子,保证每次抽样结果一致;第二,多次抽样取均值,比如抽50次负荷曲线取平均,得到一个相对平滑的基准场景。论文里如果做多场景分析,可以保留100个场景分别优化再统计期望,但日常复现性能验证用均值曲线就足够了。
5.3 换电约束导致模型不可行的补救方式
换电模块加入后,很容易出现模型不可行。原因往往是初始满电库存太少,前几个时段又有连续换电需求,库存被一次性抽干。
补救方式有两种。第一种是增加初始库存值,比如从18块调到22块;第二种是给换电需求增加松弛变量,在目标函数里加一个很大的惩罚系数,让模型在“必须换电”和“收益最大化”之间取得平衡。日常复现用第一种就好,物理意义也直观:站点本来就要准备足够的安全库存。
5.4 几个提高复现效率的小技巧
最后分享几个实操小技巧,都是我在复现过程中觉得很实用的经验。
第一,Yalmip调试时把verbose级别打开,留意求解器输出的mip gap和求解时间。如果求解时间超过几分钟,优先检查整数变量的规模,而不是盲目升级电脑。第二,代码里所有参数尽量集中在一个数据文件里,不要散落在各个函数中,否则换一组参数调参时会非常痛苦。第三,结果输出统一写成CSV文件,方便后续做对比表和画图,不要只在命令行打印几个数字。
关于换电电池的充电耗时,我这里用的是固定2小时,如果你需要更精确,可以把充电过程建模为功率积分形式,但那样会引入更多非线性项。我的建议是先跑通简化版,再逐步提高保真度。
个人体会是,这类模型的数学难度其实不大,真正的挑战全在物理建模和参数合理性上。用户弹性系数、初始库存、电价上下限、储能老化成本这些参数任何一个脱离实际,优化结果都可能看起来很漂亮但根本没法解释。所以复现完成后一定要返回来做参数敏感性分析,把每个关键参数扫一遍,看看结果变化是否符合直觉。如果弹性系数从-0.3改到-0.5,用户总负荷反而上升,那大概率是你的响应公式或约束写错了。
最后再分享一个小技巧:如果你要把这个模型扩展到园区综合能源、虚拟电厂或者更大规模的配电网节点,可以把这里的24小时单站模型直接作为子模块封装,对外只暴露电价输入和负荷输出。这样既保持了可扩展性,也避免了大模型调试困难的通病。希望这篇文章能帮你把这条路走顺。