资讯动态

电动汽车充放电调度MATLAB+YALMIP复现:从MILP建模到求解器选型

发布时间:2026/10/19 6:29:11 来源:尧图企业网站定制
第一次拿到一篇带充放电调度公式的高水平文章时心里想的是公式读懂了剩下不就是套 MATLAB 吗。真打开代码编辑器之后你才发现光是把“电动汽车充放电最优调度”这几个字变成能跑的源代码中间隔着目标函数怎么改、约束怎么排、用哪个求解器、为什么结果和论文差出十万八千里这道坎。这篇文章就专门讲清楚这件事。我以一套典型的电动汽车充放电调度模型为例从建模逻辑一路走到 MATLAB YALMIP 的复现代码并把复现中涉及的求解器选型、单位换算、无解排查、整数变量拖慢性能这些坑一并交代清楚。适合正在复现论文的研究生、做有序充电和 V2G 方案的工程师以及想搞懂充放电调度到底怎么落地的入门读者。1. 充放电调度问题在电力系统里的真实定位1.1 为什么从“有序充电”要进化到“充放电”早期研究里电动汽车调度主要做“有序充电”也就是控制车辆什么时候开始充电、充多久尽量避免大家下班回家同时插枪把台区变压器顶到过载。这个阶段优化的是时间和功率分配本质是“单方向控制”。到了 V2G 场景车辆不仅能从电网取电还能在特定时段把电池里的电放回电网。这样一来电动汽车从一个单纯的“用电负荷”变成了“可移动储能单元”。园区配电系统的逻辑就变了白天光伏出力高的时候让车多充电傍晚基础负荷爬坡、电价进入高峰时段时让一部分车辆反向放电支撑一下变压器不过载。这个思路听着美好但建模复杂度直接从“有序充电”上了一个大台阶因为充放电状态需要成对出现目标和约束都要做双向描述。有人会问既然 V2G 有电池损耗为什么还要做充放电调度我复现这类文章时最直接的感受是文章想解决的核心矛盾不是“要不要放电”而是“在什么电价信号、什么负荷约束下放电才能让车主和电网双方都划得来”。这个权衡就是目标函数里电池损耗项存在的意义。1.2 复现前先给原问题定性是 MILP、MINLP 还是 LP拿到一篇充放电调度文章第一步不是写代码而是判断问题类型。绝大多数高水平的确定性调度文章会把问题建成混合整数线性规划MILP连续变量是充放电功率和电池荷电状态SOC整数变量是充放电状态或车辆接入状态。如果文章里加了电池循环寿命的非线性模型、温度影响、或者充电功率连续可调但充电效率随功率变化的曲线问题会变成混合整数非线性规划MINLP。MINLP 复现成本高很多需要分段线性化或者直接上非线性求解器MATLAB 这边可选的成熟工具比 MILP 少。我的建议是第一版复现先把非线性部分按文章给的工作点线性化验证整体调度逻辑跑得通再逐步加回非线性细节。还有一类文章做随机优化或鲁棒优化比如考虑光伏出力和出行需求的不确定性。这类问题往往拆成两阶段或多场景模型代码结构会比确定性模型复杂一倍。如果你只是想先跑通充放电调度逻辑建议从确定性模型入手。我下面展开的就是确定性 MILP 版本这也是大多数复现代码的起点。1.3 这篇博文复现的典型问题设定为了保证后面代码有具体对象我做了如下假设某园区停车场有 10 辆可参与 V2G 的电动汽车调度周期取 24 小时步长 1 小时。车辆早上到达园区傍晚离开。园区配电变压器有容量上限同时采用典型的分时电价。优化的目标是在满足每辆车出行电量需求的前提下最小化“总购电费用 电池放电损耗成本”。调度周期内允许车辆向电网放电赚取峰谷价差。这个设定基本涵盖了充放电调度的所有核心要素时间耦合的 SOC 递推、双向功率变量、整数状态变量、变压器容量约束、分时电价下的经济性目标。后面所有代码都是围绕这套设定展开的。2. 从论文公式到建模语言先处理目标函数再处理约束2.1 目标函数到底在最小化什么复现文章时最容易犯的错是看到目标函数公式里有三四个求和项就急着照抄。实际上每个求和项背后都有明确的物理含义和单位必须先在纸上拆开。典型充放电调度目标函数长这样min Σ_t price(t) * Δt * (Σ_i Pc(i,t) - Σ_i Pd(i,t)) α * Σ_i Σ_t (Pc(i,t) Pd(i,t)) * Δt第一项是购电费用。注意这里写的是Σ Pc - Σ Pd含义是停车场作为一个整体同一时刻既有车在充电也有车在放电放到电网的电量会在电费账单里抵消一部分购电。第二项是电池放电损耗。放电会加速电池循环老化文章里经常用一个等效损耗单价α乘以放电电量来刻画。有些文章还会加一个“用户满意度惩罚项”例如调度结束时 SOC 没达到用户期望值的惩罚。你在复现前要确认原文到底包含几项目标少了任意一项调度结果都会偏离原文。还有个很容易被人忽略的问题目标函数里到底要不要给“充电”也加损耗系数如果原文写的电池老化模型是“充放电循环总吞吐量”的函数那充电和放电都应该计入老化成本如果原文只考虑放电深度对寿命的影响那只有放电算钱。这个细节直接影响最优解我的建议是先按原文公式来再通过调α观察结果趋势是否合理。2.2 SOC 递推方程是最容易抄错的SOC 递推是整个模型的“时间耦合骨架”公式本身不长但效率系数放在哪里经常让复现者踩坑。正确的写法是SOC(i,t1) SOC(i,t) (η_ch * Pc(i,t) - Pd(i,t) / η_dis) * Δt / Cap(i)这里η_ch是充电效率充电时实际进入电池的能量是η_ch * Pc * Δt放电时电池释放的能量经过逆变器损耗后真正送到电网的功率是η_dis * Pd所以反过来要从电网侧功率Pd推算电池侧消耗需要用Pd / η_dis。我用一个数值例子说明为什么不能记反电池容量 60 kWh电网侧放电功率 3.5 kW放电效率 0.95持续 1 小时。电池侧实际消耗的能量是3.5 / 0.95 ≈ 3.68 kWhSOC 下降约 6.14 个百分点。如果你图省事写成SOC - η_dis * PdSOC 只下降 5.53 个百分点误差看起来不大但累计 24 小时、10 辆车之后最后的 SOC 满足情况会出现系统性偏差甚至让约束不可行。2.3 约束条件转成优化器能处理的形式这个模型里常用的约束包括下面这些我按在 YALMIP 里的写法整理SOC 上下限约束SOC_min ≤ SOC(i,t) ≤ SOC_max。这里双向不等式在 YALMIP 里可以直接连写比逐个拆开要快。充放电功率上限0 ≤ Pc(i,t) ≤ Pmax0 ≤ Pd(i,t) ≤ Pmax。注意功率是电网侧功率还是电池侧功率前后必须统一。我后面代码里统一采用“电网侧功率”作为决策变量。充放电状态互斥约束一个车在同一个时段不能既充电又放电否则求解器会在电价波动时玩“左手倒右手”刷收益。这个约束用二元变量u(i,t)实现u1允许充电、禁止放电u0允许放电、禁止充电。车辆可用时段约束只有车停在停车场并接入充电桩时才有功率。这个约束在复现时特别容易被漏掉一旦漏掉求解器会让车在行驶时段也充电结果看起来费用极低实际上完全是错的。变压器容量约束整个停车场同一时刻的净功率必须落在[-station_limit, station_limit]范围内。注意下限为负数因为公交放电时停车场是向电网送电的。调度结束时的最低 SOC 约束保证车辆离开时的电量能满足出行需求这是约束里最影响可行性的一个。2.4 一个可直接照抄的小算例参数为了让读者能直接对照代码我把参数固定下来不用随机数生成保证复现结果可重复。参数取值说明调度时段 T24单位 1 小时车辆数 N10均接入同一个园区单车最大功率 Pmax7 kW常见交流充电桩上限电池容量 Cap60 kWh假设车型一致SOC 初始值0.5到达园区时的电量SOC 下限 / 上限0.2 / 0.9文献中常见保护区间充电效率0.95交流转直流损耗放电效率0.95逆变器效率放电损耗单价 α0.02 元/kWh折算的等效损耗系数分时电价我采用一个典型的日曲线0:00-7:00 为 0.5 元/kWh8:00-11:00 和 18:00-22:00 为 1.2 元/kWh其余时段为 0.8 元/kWh。这个曲线足够制造充放电价差能让调度结果明显呈现出“低谷充电、高峰放电”的形态。3. 复现环境与求解器怎么选先让模型能“跑得动”3.1 YALMIP、CVX、MATPOWER 该选哪个第一次复现调度类文章的人经常在 MATLAB 建模工具上犹豫。我的结论很直接调度优化首选 YALMIP。YALMIP 是一个建模层你把优化变量和目标约束用接近数学公式的方式写出来它负责转换成求解器的标准输入格式。它的好处是支持二元变量和整数变量天然适配 MILP目标函数和约束可以用循环方式追加和论文公式结构一一对应换求解器只需改一个参数不用重写模型。CVX 也适合做凸优化但对整数变量的支持没有 YALMIP 那么顺手。MATPOWER 是电力系统潮流分析工具里面也有最优潮流模块但它解决的是电网拓扑层面的潮流优化不是电动汽车集群充放电调度这种“时间序列决策问题”。如果你要复现的文章是纯调度、不涉及交流潮流YALMIP 就够了。3.2 求解器选型首先要看“有没有整数变量”有了 YALMIP 之后还需要一个底层求解器。这里我给出一个非常粗的判断方式求解器是否支持整数变量适用规模使用建议MATLAB 自带 linprog否小规模 LP只用来快速验证连续模型MATLAB 自带 intlinprog是小中型 MILP第一版复现首选不用额外安装商业 MILP 求解器是中大型 MILP大规模或需要商业性能时使用开源 SCIP是中规模 MILP不想用商业工具时的可行替代如果你只有 10 辆车、24 个时段整车模型大概有 240 个二元变量和几百个连续变量MATLAB 自带求解器完全能扛住。如果你的文章要求复现 500 辆车、滚动 96 个时段那就别等了直接上商业求解器。我实际复现时还有个经验先用自带求解器把模型逻辑跑通再换成商业求解器提速对比结果。这样能避免“模型本身写错却怀疑是求解器不给力”的排查方向。3.3 运行前的环境检查清单我每次新建一个复现工程先花五分钟做三件事确认 MATLAB 版本能解析binvar这类变量确认 YALMIP 已在路径里命令行输入which optimize能返回路径确认需要用的求解器文件没有和 MATLAB 内置函数冲突。很多次有人跑代码报“未定义函数或变量 binvar”原因是 YALMIP 文件夹根本没加入 MATLAB 路径。这个坑和代码本身无关但足以让新手怀疑自己模型写错。4. 核心代码拆解从数据表到调度曲线的完整链路4.1 数据准备电价曲线和车辆可用时段数据准备的代码不长但决定了后面所有逻辑的排版。我把电价、初始 SOC、车辆可用时段全部用手动矩阵定义保证复现时可重复。clear; clc; T 24; % 调度时段数 N 10; % 车辆数 dt 1; % 时间步长小时 % 分时电价单位元/kWh price zeros(1,T); price(1:7) 0.5; % 低谷 price(8:11) 1.2; % 早高峰 price(12:17) 0.8; % 平段 price(18:22) 1.2; % 晚高峰 price(23:24) 0.5; % 夜间低谷 % 车辆参数 Cap 60; % 电池容量 kWh Pmax 7; % 充放电功率上限 kW SOC0 0.5; SOCmin 0.2; SOCmax 0.9; SOCend 0.5; % 离开园区时的最低要求 eta_ch 0.95; eta_dis 0.95; alpha 0.02; % 放电损耗等效成本 元/kWh station_limit 40; % 停车场净功率上限 kW车辆可用时段我用一个逻辑矩阵来表示。1 表示该车在该时段接入停车场0 表示车辆在外行驶或未接入。% 车辆可用时段矩阵10辆车 x 24小时 % 简化模型1-5号车白天接入8-17点6-10号车傍晚接入18点至次日7点 available zeros(N,T); available(1:5, 8:17) 1; available(6:10, 18:24) 1; available(6:10, 1:7) 1;这里需要说明真实复现时可用时段应该来自车辆出行数据或随机场景生成但我故意用这种“分组接入”的方式是为了让训练出来的调度结果能看出明显的“时间错峰”效果。第一组车白天在园区第二组车夜间在园区两组车共同覆盖 24 小时求解器必须靠充放电配合来压负荷。4.2 目标函数和约束的 YALMIP 写法下面这段是整套代码的核心。变量类型有三个充电功率矩阵、放电功率矩阵、SOC 矩阵以及一个二元变量矩阵u。% 决策变量 Pc sdpvar(N,T,full); % 充电功率kW Pd sdpvar(N,T,full); % 放电功率kW SOC sdpvar(N,T1,full); % SOC 矩阵多一列用于初始值 u binvar(N,T,full); % 1充电0放电或空闲 % 目标函数 obj sum(sum(repmat(price,N,1) .* Pc * dt)) ... % 购电费用 - sum(sum(repmat(price,N,1) .* Pd * dt)) ... % 放电收益 alpha * sum(sum((Pc Pd) * dt)); % 电池损耗 % 约束 Cons []; % 初始 SOC 和结束 SOC 约束 Cons [Cons, SOC(:,1) SOC0 * ones(N,1)]; Cons [Cons, SOC(:,T1) SOCend * ones(N,1)]; % SOC 递推约束 for i 1:N for t 1:T Cons [Cons, SOC(i,t1) SOC(i,t) ... (eta_ch * Pc(i,t) - Pd(i,t)/eta_dis) * dt / Cap]; end end % SOC 上下限 Cons [Cons, SOCmin SOC SOCmax]; % 功率非负与上下限 Cons [Cons, Pc 0, Pd 0]; Cons [Cons, Pc Pmax * u]; Cons [Cons, Pd Pmax * (1-u)]; % 车辆可用时段约束 Cons [Cons, Pc available .* Pmax]; Cons [Cons, Pd available .* Pmax]; % 变压器容量约束净功率在 [-40, 40] kW net_power sum(Pc - Pd, 1); Cons [Cons, -station_limit net_power station_limit];这里有几个容易理解偏差的地方我展开说一下。第一Pc Pmax * u和Pd Pmax * (1-u)这对约束实现了充放电互斥。注意它并没有强制u1时必须有充电功率也没有强制u0时必须有放电功率所以车辆完全可以在u1但充电功率为 0 的情况下“空闲”。这是模型设计的细节既排除了同时充放电又保留了空闲自由度。第二可用时段约束和互斥约束是并存的。available是逻辑矩阵求解器只能通过它判断功率必须为 0。由于 0 乘以任何数都是 0这个约束对连续变量是线性有效的。第三SOCmin SOC SOCmax这种写法在 YALMIP 里会展开成矩阵不等式的双向约束很多人第一次用会担心写错。实际上 YALMIP 支持这种紧凑写法而且可读性比逐个拆开好很多。4.3 求解与结果验证模型搭好之后调用求解器。第一版我们用 MATLAB 内置求解器只需要在optimize里指定。options sdpsettings(solver,intlinprog,verbose,2); result optimize(Cons, obj, options); if result.problem 0 Pc_opt value(Pc); Pd_opt value(Pd); SOC_opt value(SOC); fprintf(最优总费用: %.2f 元\n, value(obj)); % 每辆车最终SOC校验 final_soc SOC_opt(:, end); fprintf(最小末端SOC: %.3f\n, min(final_soc)); % 画图 figure; subplot(2,1,1); bar(1:T, [sum(Pc_opt,1); -sum(Pd_opt,1)], stacked); xlabel(时段); ylabel(功率/kW); legend({充电功率,放电功率}); title(停车场净功率组成); grid on; subplot(2,1,2); plot(1:T1, mean(SOC_opt,1), o-, LineWidth, 2); xlabel(时段); ylabel(平均SOC); title(平均SOC变化曲线); grid on; else disp(求解失败请参阅 result.info); end跑出来的典型结果大概是0:00-7:00 第二组车辆大量充电电价最低且闲时功率充足白天 8:00-11:00 第一组车辆部分充电、部分放电用低谷存下的电支撑早高峰12:00-17:00 平段时期最保守功率基本贴着可用但不超容量18:00-22:00 晚高峰电价最高第一组车已经离开第二组车刚接入会少量放电赚取峰谷价差同时避免和基础负荷叠加过载。你拿到结果后不要直接进论文对比先做三件事校验检查每辆车每个时段是否满足Pc * Pd 0验证没有同时充放电检查任意时段净功率绝对值是否小于等于 40 kW检查末端 SOC 是否大于等于 0.5。这三个校验都通过说明代码的物理逻辑基本没问题。4.4 如果结果不合理先查这四个地方我复现时遇到最多的情况是“优化结果存在但明显不合理”比如深夜所有车都不充电、高峰时段疯狂放电。这时候第一个怀疑对象是电价序列和price向量的顺序是否对应很多文章给出的电价从 0 点还是 7 点开始并不说明一错就是整体错位。第二个怀疑对象是效率系数方向也就是 2.2 节说的那个η_ch和/η_dis的位置。第三个是可用时段矩阵你可以在代码里加一句sum(available)如果某个车全天不可用它的 SOC 会一直停在初始值。第四个是变压器容量上下限。如果-station_limit net_power station_limit里的下限写成了 0那就是禁止向电网放电整个 V2G 收益空间直接没了结果必然很“保守”。5. 复现时最容易翻车的几个细节5.1 论文符号和代码变量之间没有“映射表”很多高水平文章的公式用P_c,t、P_d,t、SOC_t看起来很简单但实际代码里可能会出现Pc、Pch、Pd、Pdis好几个相似名称。我见过身边同学复现到一半把放电功率误当成充电功率写进 SOC 递推最后结果出现 SOC 持续上升这种明显违反能量守恒的情况。我的办法是开工前先建一张三列映射表第一列是论文符号第二列是代码变量名第三列是单位和物理含义。例如P^{ch}_{i,t}对应Pc(i,t)单位 kW含义是“从电网侧测量的充电功率”。有了这张表复现到后面就不会反复回去翻正文符号表。5.2 单位口径不一致kW、kWh 混用这个问题在线性规划里非常隐蔽。优化目标里电价单位是元/kWh功率单位是 kW时间步长是小时所以能量永远是P * dt。但有些文章目标函数里的求和项直接写∑ price_t * P_t省略了Δt如果你的dt不是 1 而是 0.5 小时照抄公式就会差一倍费用。SOC 递推方程里更是重灾区。P * dt / Cap计算的是“这个时段充了多少电量占电池容量的比例”如果你的功率单位是 kW、时间单位是小时、容量单位是 kWh那一切正常如果有人把功率单位想成 kWh那整个等式就完全错位了。我建议在代码注释里把每个变量的单位标在变量名后面例如Pc % kW防止时间一久自己都忘了。5.3 目标函数量级差距过大导致小项被“无视”有时候你发现求解结果里没有任何放电哪怕峰谷价差看起来很大。原因很可能是目标函数里电池损耗系数α设置得过大或者购电成本因为“总功率”计算方式的关系量级很大两类项之间差了三个数量级。求解器在容差范围内会优先优化大项小项的影响被淹没。解决方式有两种一是把损耗系数α和电价放在同一个单位体系下做量级对齐二是在复现论文时先用论文参数跑一遍观察损耗项占目标函数的比例。我通常会在求解后打印购电费用和损耗费用各是多少一旦发现损耗费用占比不到总费用的 0.1%先怀疑单位错了再怀疑系数数量级错了。5.4 整数变量太多求解慢到怀疑人生充放电调度模型里二元变量数量等于N * T看起来不多但如果你复现的参数是 500 辆车、96 个时段二元变量就逼近 5 万个。加上我还加了互斥约束和可用时段约束整张约束矩阵的规模会迅速膨胀普通笔记本电脑跑起来非常吃力。我的处理策略是按文章的实验规模分步走先跑 10 辆车验证模型正确性再跑 50 辆车观察趋势最后直接改参数跑全文规模。如果文章规模实在大就考虑“聚合建模”把同一类型车辆聚合成一个等效机组只对“每组车辆的总功率”做优化。聚合模型牺牲了一些每车差异化的细节但调度层面的大趋势依然准确。5.5 无解时的系统排查法最让人头疼的不是结果差而是求解器直接返回 infeasible。我会按下面顺序排查先检查 SOC 结束约束是否和 SOC 上下限冲突。比如初始 SOC 是 0.5最低要求是 0.5但车辆可用时段只有 1 个小时且最大功率无法补电那无解是必然的。这种情况先放宽 SOC_end 到 0.4试探求解确认其他约束是否可行。然后检查互斥约束和可用时段约束是否联合导致功率为零时段太多。比如某辆车可用时段只有 1 小时但u矩阵把这个小时设置成了只能放电同时结束 SOC 又要求升高那自然无解。排查手段是把可用时段矩阵画成热力图肉眼扫一遍就能发现“哪些车在哪些时段根本没有自由度”。最后检查变压器容量约束。如果车有很多且每辆都强制要充满station_limit设太小时优化器无论如何都满足不了净功率上限。这时候我会用“逐步放宽 station_limit”的方法观察问题从无解变成有解的临界值。这个临界值本身就非常有工程价值可以直接写进文章分析里因为它就是“该园区最多能同时支持多少辆车参与 V2G”的量化答案。我自己现在复现任何一篇调度类文章第一件事依然是先建一张公式符号和代码变量之间的映射表再来做模型验证。这个习惯让我从“对着公式手忙脚乱”变成“半小时就能把新模型移植到已有代码框架里”。如果你也被电动汽车充放电调度文章复现卡住别急着从头写代码先把目标函数里每一项的物理含义、单位、效率方向确认清楚再动 YALMIP。模型对了求解器只是最后一步而已。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑