十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

电动汽车集群有序充电优化:Matlab+Yalmip+Gurobi实战

电动汽车集群有序充电优化:Matlab+Yalmip+Gurobi实战 先说结论这套组合如果你准备拿来做电动汽车集群优化选型上大概率不会后悔。我前前后后做了快两年的电动汽车集群有序充电项目规模不大五十辆车左右、24小时调度周期、时间步长取1小时工具就是Matlab和Yalmip求解器用的Gurobi。做完之后最大的感受是集群优化听起来门槛很高但选对建模工具之后核心难度其实集中在数学模型的约束设计上写代码反而是最省时间的一步。这篇文章不打算给你堆理论也不放那种“经典再看公式”的路数就把我实际建模型、写代码、调求解器、踩坑排错的过程完整拆给你看。项目场景是小区级充电站一帮人傍晚下班回家插上充电枪就走如果放任不管七八点会出现一个充电高峰配变过载、线路发热、电价还最贵。我们要做的就是给这群车安排一个充电计划让每辆车在离开前都能充到目标电量同时把集群总成本、负荷峰值控制住。适合正在做EV有序充电、虚拟电厂、微电网调度、或者用Matlab做电力系统优化课题的同学参考。1. 先把问题拆清楚集群优化到底在优化什么1.1 从单台车到一群车难点变了单台车充电没什么好优化的插上枪、以最大功率充充到就走。但一旦车多起来问题就变了。想象一个小区的配电变压器容量是120kW下班后有二十辆、三十辆车同时插上每辆车7kW峰值轻松冲到150kW以上变压器直接过载。这时候每一台车单独决策根本不行必须放在一起统筹。我习惯把集群优化理解成“在时间和功率两个维度上做资源分配”。时间维度上每辆车不是全天都在它有到达时间和离开时间只有在这个窗口内才能充电功率维度上所有车共用一个配变容量上限谁在哪个时段用多少功率需要整体协调。进一步说还有电价变化如果在晚高峰电价1.18元/kWh的时候把所有车都塞满费用肯定比凌晨0.38元/kWh充电高出很多。所以电动汽车集群优化本质上就是一个带时间耦合约束的资源分配问题。每辆车的SOC就像“水位”充电功率就是“注水速率”而配变容量和离开时间就是“水管的直径和截止时间”。模型算出来的就是每个时刻给每一辆车注多少水才能既不溢出来、又赶在截止时间前注满目标水位。1.2 为什么是Matlab Yalmip而不是其他组合你先别管Yalmip是什么先记住一句话它是Matlab里的一层“建模语言”。没有它你要手写约束矩阵A、b、Aeq、beq每加一个约束都要反复检查行和列对不对极其痛苦。Yalmip允许你直接用数学符号写公式比如 x(i,t) 就是一个sdpvar变量你的约束写成 s(:,t1) s(:,t) eta * x(:,t) * dt / Cap 即可它负责在底层把你的公式翻译成求解器能吃的标准形式。有人会问为什么不用PythonPython生态里确实有Pyomo、PuLP、Gurobi的Python接口也很好用。但很多电力系统课题、课程设计、论文复现都在Matlab环境里后面还要接Simulink做仿真跟风电光伏、储能、配电网模型对接起来Matlab的矩阵原生支持确实方便。Yalmip和CVX我也比较过CVX更适合凸优化、写起来也简洁但Yalmip对整数变量、逻辑约束、非光滑函数这类复杂调度场景的支持更宽松比如binvar、implies这些用法。电动汽车集群优化这种带时序递推、整数决策、多约束叠加的问题用Yalmip明显更顺手。还有一点很关键Yalmip是免费的Gurobi有学术许可Matlab虽然贵但大多数研究生和工程师手头都有。整套工具链的获取门槛非常低。2. 环境准备与求解器选型附实测配置)2.1 Matlab Yalmip Gurobi 的环境搭配先说版本匹配这件事。Yalmip本身一直在更新Gurobi的Matlab接口每个版本支持的范围也不一样。我实测比较稳的组合是MATLAB R2021b Yalmip R2021 Gurobi 9.5再早的Matlab版本配新版Gurobi容易报启动失败。安装流程不算复杂但我见过太多人在这一步卡住所以按步骤拆开说。第一步装Yalmip。网上直接搜Yalmip官方主页或者GitHub仓库下载zip压缩包解压到任意目录比如D:\toolbox\yalmip。然后在Matlab里执行addpath(genpath(D:\toolbox\yalmip))接着savepath保存到路径列表。验证是否成功就运行yalmiptest能弹出一堆求解器状态列表就说明路径没问题。第二步装Gurobi。到官网注册学术账号申请免费学术许可证下载对应系统的安装包。安装完以后关键一步是配置环境变量。Windows下要把Gurobi安装目录下的bin文件夹加到系统PATH里否则Matlab找不到动态链接库。Linux下还要在.bashrc里设置GUROBI_HOME和GRB_LICENSE_FILE。之后在Matlab里切到Gurobi安装目录的matlab子文件夹运行gurobi_setup它会自动添加接口路径。第三步验证连接。在Matlab里跑一个最简单的线性规划然后指定Gurobi求解器比如x sdpvar(2, 1); Constraints [x 0, x(1) 2*x(2) 1]; Objective x(1) x(2); ops sdpsettings(solver, gurobi, verbose, 0); optimize(Constraints, Objective, ops);如果这一步能顺利跑出结果说明环境已经通了。很多同学卡在“明明装了Gurobi但Yalmip说找不到求解器”十有八九是环境变量没配好或者gurobi_setup没执行。这里提醒一下装完Gurobi以后一定要重新启动Matlab让动态库加载生效否则照样报No suitable solver。2.2 求解器选择背后的逻辑再深入说求解器。Yalmip只是建模语言真正解算的是底层求解器。对于电动汽车集群这种问题我强烈建议用商业求解器Gurobi和CPLEX二选一学术许可都免费。原因很实在免费求解器在小规模问题上你感受不到差距一旦车辆数量到几十上百、时间步长细化到15分钟问题规模迅速膨胀免费求解器的求解时间和稳定性会带来明显差距。我这里把常见求解器的情况整理成一个表方便你对比求解器支持问题类型获取方式性能/稳定性适合场景intlinprogMILP/MIQPMatlab自带中小规模还行简单验证quadprogQP/QCPMatlab自带小规模稳定凸二次规划GurobiLP/QP/MILP/MIQP学术许可免费大规模最优、稳定强烈推荐CPLEXLP/QP/MILP/MIQP学术许可免费老牌、稳定跟Gurobi类似SCIPMILP开源可用但偏慢开源备选Ipopt大规模NLP开源不支持整数非线性问题选Gurobi还有一个实际好处如果你的模型后来要加入二进制变量比如“某台车在某个时段是否充电”问题会从LP变成MILP。MILP的求解复杂度是指数级的对求解器性能非常敏感。Gurobi的分支定界算法实现得很成熟默认参数就能跑出不错的结果。当然如果只是线性规划Matlab自带的linprog也够用但既然Yalmip的调度能力都摆在这了没必要省这一步。3. 数学模型把调度需求翻译成约束3.1 决策变量、参数先到位我习惯在建模之前先把“场”摆好决策变量是什么、参数有哪些、单位是什么、维度是什么。这个习惯帮我避免了很多低级错误。核心决策变量是两个x(i,t) 表示第i辆车在第t个时段的充电功率s(i,t) 表示第i辆车在第t个时段的电池SOC。两个变量的维度都是N×TN是车辆数T是调度时段数。如果是24小时、1小时一个步长T就是24如果步长15分钟T就是96。这里有个很容易忽略的细节SOC作为变量而不是参数因为它是动态递推出来的状态Δt时刻的SOC依赖于上一时刻的SOC和期间的充电量。主要参数包括电池容量CapkWh、最大充电功率PmaxkW、充电效率η一般取0.9~0.95、初始SOC0、离网目标SOC_need、到达时段arrive、离开时段depart、分时电价price以及最关键的配变容量上限P_totalkW。单位这块我吃过亏功率用kW、时间用h乘起来正好是kWh除以电池容量后SOC增量才是无量纲的。如果时间步长是15分钟dt0.25这个换算就容易乱。3.2 目标函数怎么设计目标函数取决于项目想干什么没有统一答案。最常见的两个方向是经济性最优和电网友好性最优。经济性目标就是让整个集群的充电费用最低公式写出来是minimize sum( sum( price(t) * x(i,t) ) ) * dt也就是每个时段的电价乘以该时段所有车的充电功率汇总到整个调度周期。这个目标函数是线性的求解器最爱。电网友好性目标通常是让集群总功率曲线尽量平缓常见做法是最小化负荷方差或者最小化峰值功率。最小化峰值功率的写法是引入一个辅助变量P_peak然后约束每个时段的总功率小于等于它再最小化P_peak。这个技巧叫epigraph变换用Yalmip可以直接写。但要注意如果你直接写min max(...)Yalmip会引入额外的处理问题性质可能改变能用辅助变量就用辅助变量。实际项目里纯用经济性目标会存在一个副作用电价低谷集中在凌晨模型可能把所有充电都挪到凌晨但车凌晨还在不在桩上是个问题。所以后来我把目标函数改成了经济成本加峰值惩罚的加权组合峰值每超一个值就加惩罚项这样计算出来的调度方案不会把负荷峰顶完全削平但也不会为了省钱把所有车都塞到深夜。3.3 约束条件里的细节决定成败很多新手把建模重点放在目标函数上但其实电动汽车集群优化的难点几乎全在约束。第一个约束是SOC递推方程s(i, t1) s(i, t) η · x(i, t) · dt / Cap这个方程把所有时间步串起来了是模型的核心。它的物理含义很好理解电池就像一个水桶充电功率乘时间就是注入的能量乘以效率是因为充电过程有损耗除以容量是把能量换算成SOC百分比。正是这条约束把原来独立的每个时段决策变成了一串有时间耦合关系的决策链。第二个约束是功率边界。充电功率要介于0和Pmax之间SOC要介于SOC_min和SOC_max之间。很多人会忽略SOC下限但为了电池寿命实际项目中通常会限制最低放电深度。如果考虑V2G反向放电充电功率的下界会变成负数这时需要引入两个非负变量分别表示充电功率和放电功率避免目标函数里出现“放电当负成本”的计算错误。第三个约束是时间窗约束。车没到之前不能充车离开之后也不能充。在模型里的表达方式就是强制车在到达前和离开后对应时段的充电功率为0。这组约束看似简单但如果你不做模型会自动把凌晨时段的充电任务提前到白天没车的时候结果完全不合逻辑。第四个约束是集群总功率约束sum(i, x(i,t)) P_total, 对所有 t这条约束就是“集群”二字的题眼。配变容量是所有车共享的它迫使模型必须统筹安排充电顺序。我实测过的效果是没有这条约束所有车同时充峰值154kW加了这条约束峰值被牢牢压到120kW以内同时电费还降了一截。还有一个容易忽略的约束是离网电量需求。每辆车离开时必须达到目标SOC写成 s(i, depart(i)) SOC_need(i)。这一步是硬约束如果不满足用户第二天开车没电整个方案就是废的。为了让它有解建模前要检查每辆车的可充电窗口时长是否足够窗口太短的要么降低目标电量要么限制车辆参与调度。4. Yalmip建模实操与核心代码4.1 建模三句话变量、约束、目标Yalmip建模的思路我总结成三句话用sdpvar定义变量用累加的方式写约束最后用optimize求解。这个顺序不能乱而且最好在定义变量之前就把模型里的所有参数准备好。用sdpvar定义变量时有个细节要特别注意默认情况下sdpvar(N, T)创建的是一个方阵或一般矩阵但如果你定义的是二维决策变量最好写成sdpvar(N, T, full)明确告诉Yalmip这是一个全矩阵。否则遇到某些求解器或者后续操作可能因为默认的对称性假设而出错。写约束时我强烈建议不要一行写一条然后不停拼接而是用循环批量生成。以SOC递推约束为例循环t从1到T-1每轮生成一条等式约束并拼到Constraints里。这种方式可读性好调试时也容易定位是哪一条约束出的问题。循环次数不多性能影响可以忽略。目标函数直接在Objective变量上累加。当目标函数和约束都写完后调用optimize(Constraints, Objective, ops)即可。注意optimize的返回值有个sol结构体其中sol.problem等于0表示求解成功非0就是有异常第一次跑通之后这个判断建议务必写上。4.2 一个能直接跑的Matlab示例下面给一个我简化过的完整示例。这个版本把车辆数设成了10辆保证任何一台普通笔记本都能在几秒内跑完。你可以先跑通再逐步加大车辆数。% 电动汽车集群有序充电 - Yalmip Gurobi 教学示例 clear; clc; rng(1); %% 基础参数 N 10; % 车辆数 T 24; % 调度时段数小时 dt 1; % 时间步长 [h] Cap 60; % 电池容量 [kWh] Pmax 7; % 最大充电功率 [kW]家用交流桩 eta 0.92; % 充电效率 P_total 80; % 配变可用容量 [kW] % 分时电价 [元/kWh]按典型峰谷平三段设置 price [0.38*ones(1,7), 0.72*ones(1,5), 1.18*ones(1,4), ... 0.72*ones(1,3), 1.18*ones(1,3), 0.72*ones(1,2)]; price price(1:T); %% 车辆参数简化生成保证可行 SOC0 0.2 0.3*rand(N, 1); % 初始SOC [0.2, 0.5] SOC_need 0.85 0.05*rand(N, 1); % 离网目标SOC [0.85, 0.9] arrive randi([9, 17], N, 1); % 到达时段 depart arrive 5 randi([0, 2], N, 1); % 离开始时段留足窗口 depart min(depart, T); %% 构建Yalmip模型 x sdpvar(N, T, full); % 充电功率 [kW] s sdpvar(N, T, full); % SOC [0,1] Constraints []; % 1) SOC 递推约束 for t 1:T-1 Constraints [Constraints, s(:, t1) s(:, t) eta * x(:, t) * dt / Cap]; end % 2) 功率边界 Constraints [Constraints, 0 x Pmax]; % 3) SOC边界 Constraints [Constraints, 0 s 1]; % 4) 时间窗约束 离网电量约束 for i 1:N % 到达前不能充电 for t 1:arrive(i) Constraints [Constraints, x(i, t) 0]; end % 离开后不能充电 for t depart(i):T Constraints [Constraints, x(i, t) 0]; end % 离开时SOC必须达到目标 Constraints [Constraints, s(i, depart(i)) SOC_need(i)]; end % 5) 集群总功率约束 Constraints [Constraints, sum(x, 1) P_total]; %% 目标函数总充电费用最小 Objective sum(sum(price .* x)) * dt; %% 求解 ops sdpsettings(solver, gurobi, verbose, 0); sol optimize(Constraints, Objective, ops); if sol.problem 0 fprintf(求解成功总充电费用 %.2f 元\n, value(Objective)); x_opt value(x); s_opt value(s); else disp(sol.info); return; end %% 结果可视化 figure; subplot(3,1,1); stairs(1:T, sum(x_opt, 1), LineWidth, 1.5); hold on; yline(P_total, r--, P_{total}, LineWidth, 1); xlabel(时段); ylabel(总功率/kW); title(集群总充电功率); grid on; subplot(3,1,2); plot(1:T, x_opt(1:3, :), LineWidth, 1.2); xlabel(时段); ylabel(充电功率/kW); title(前3辆车的充电功率); grid on; subplot(3,1,3); plot(1:T, s_opt(1:3, :), LineWidth, 1.2); xlabel(时段); ylabel(SOC); title(前3辆车的SOC轨迹); grid on;这个示例里我特意把车辆离开始时间设成到达后至少5小时目的是让每辆车都具备达到目标SOC的条件。如果窗口太短模型就会报infeasible这是新手最容易碰到的坑。4.3 结果怎么分析和调整跑完以后我习惯先看三个东西总功率曲线有没有顶到P_total、每辆车离开时SOC是否都达标、费用相比“无序充电”省了多少。无序充电就是所有车到达即以最大功率充到满或者充到离网写一个对比脚本也很简单按到达时间顺序累加功率然后乘电价算费用。我测试的这组参数下无序充电的峰值大概会到112kW左右明显超过80kW的配变上限有序之后峰值被压到80kW以内总费用大概下降15%到20%。这个数字会因为参数不同而波动但方向一定是对的。如果跑出来的调度方案里某辆车充电时间非常碎片化或者某辆车出现“充一会、停一会、再充一会”的情况这通常是目标函数里缺少最小功率约束导致的。实际充电桩可能有最小启动功率比如低于1.4kW就充不进去。你可以加一条约束要么x(i,t)0要么x(i,t) x_min。这种“或”关系需要二进制变量用Yalmip的binvar加implies来实现模型会从LP变成MILP求解时间会变长但更贴近实际。另外强烈建议把SOC轨迹画出来看一遍。如果某辆车在离开前很久就充到了目标但后面时段保持不动说明调度偏保守可以适当提高该车的Pmax或者延后充电窗口把资源留给更需要的车。如果某辆车SOC一路贴着上限跑说明它可调度空间极小这类车在真实系统里应该标记为“不可控充电”不参与优化。5. 实测问题与排查技巧5.1 常见报错速查表这半年被问得最多的问题基本集中在求解器调用和模型不可行这两类。我整理了一份速查表每条都是自己踩过的现象可能原因排查方法No suitable solver foundYalmip路径没配好或指定求解器没装/没激活先跑yalmiptest检查确认gurobi_setup已执行Gurobi启动失败或崩溃版本和Matlab不兼容或环境变量没配重启Matlab检查PATH对照版本列表infeasible / problem1模型约束过强无可行解逐个注释约束定位用sol.info查看详细告警求解时间过长有整数变量、约束过密、规模过大减少车辆/时段去掉binvar尝试提高MIPGapNaN出现在结果中量纲不统一数值差异过大归一化功率和容量避免10^6级别的参数混算sdpvar维度报错忘了加full或矩阵维度对不上检查sdpvar(N,T,full)用size()确认维度Infeasible这个问题值得单独说。模型不可行时Yalmip会返回problem1但不会告诉你哪条约束有问题。我的做法是“约束二分法”先只保留SOC递推和边界约束跑通后再逐步加时间窗、加离网电量、加总功率约束每加一组就跑一次一旦从可行变不可行就是刚加进去的那组约束出了问题。这个方法虽然原始但定位速度比盲猜快得多。还有一个我特别想提醒的问题Gurobi的崩溃不一定是代码错可能是Gurobi版本和Matlab位数不匹配。Matlab必须是64位Gurobi也得是64位否则启动时直接崩。这一点在新手环境配置时出现的频率非常高。5.2 三个值得养成的建模习惯第一个习惯是量纲归一化。我见过有人把电池容量写成60000Wh功率写成7000W算出来的SOC增量数值很小求解器在数值上很容易出现病态。建议统一用kW和kWhSOC直接用0到1之间的比例这样各变量量级都在10的几次方以内Gurobi的数字处理会舒服很多。第二个习惯是尽量保持模型线性。电动汽车调度本质上是个线性问题功率、SOC、费用全是线性关系。如果你在约束里写了min、max、abs这类函数Yalmip虽然能接受但底层可能要引入整数变量或者做非光滑处理模型的求解难度会急剧上升。能化成线性约束的就化成线性约束。比如前面说的峰值最小化用一个辅助变量加一组不等式永远比直接写min(max(...))要好。这样既高效又稳健。第三个习惯是给变量指定好的初值。Yalmip支持assign这个命令可以在求解前给变量一个合理的初始解特别是MILP问题好的初值能大幅缩短分支定界的时间。比如用无序充电的结果作为初值虽然它可能不满足总功率约束但给求解器提供了一个不错的起点在实际测试里求解时间能缩短20%到30%。6. 从示例到工程扩展方向与我的体会6.1 可以往哪些方向扩展这套模型只是一个起点实际项目里加入V2G之后电池可以在高电价时段放电、低电价时段充电模型从“充电调度”变成“充放电调度”目标函数从纯费用最小变成“充电费用 - 放电收益”约束里要加入放电功率下界和SOC放电深度限制。用Yalmip实现并不难定义两个非负变量x_ch和x_dis分别建模充电和放电功率然后在目标函数里用不同的价格系数。这里有一个重要的细节同一个时段不能既充又放需要加一个二进制变量来互斥模型会变成MILP。另外可以做多目标优化比如把“用户充电费用最小”和“配变负荷波动最小”同时纳入考虑用加权系数把两个目标合成一个。加权系数怎么选是个值得钻研的问题太偏向费用会把充电全挪到凌晨太偏向负荷平抑则费用可能比无序充电还贵。我通常的做法是扫一组不同的权重画Pareto前沿然后让决策者根据实际偏好挑一个点。还有一个很实际的方向是考虑SOC预测的不确定性。实际项目里车辆到达时的SOC并不准确电池容量也会老化衰减如果模型里用的参数和真实值有偏差算出来的方案可能不可行。解决办法一是做滚动时域优化每15分钟重新求解一次二是用鲁棒优化或者随机规划把不确定参数用区间或者场景集表示。滚动优化在工程里落地最多因为实现简单而且对预测误差的容忍度很高。6.2 个人体会与建议最后说点项目之外的感受。做这类优化问题模型跑通只是完成了30%剩下70%的时间基本花在数据清洗、参数标定、结果合理性检查上。比如你拿到手的车辆SOC初始值往往是不准的电价可能是预测值不是实际值充电桩的实际功率也可能达不到标称最大值。模型算得再精细输入数据错了结果就是错的。所以后来我做项目时养成一个习惯在建模之前先整理一份干净的输入数据表每个字段的单位、范围、来源都写清楚再写一个可行性检查脚本把每辆车的到达时间、离开时间、充电需求时长画成一张甘特图。这样能提前发现哪些车的调度窗口根本不足以充到目标SOC避免把不可行问题丢给求解器。另一个体会是不要一开始就追求大规模。我见过不少同学上来就搞几百辆车、96个时段结果模型规模太大求解时间动辄几分钟甚至十几分钟改个约束重新求解一次整个人都崩溃了。建议先把10辆车、24时段的模型跑通确认逻辑没问题再逐步扩大到30辆、50辆。速度慢了不要急着上高性能机器先检查模型里有没有多余的整数变量、有没有不必要的耦合约束很多时候把这些优化掉比换机器管用得多。如果你正准备进入这个方向我建议就照上面这个路径走一遍装环境、抄模型、跑示例、改参数、看结果一步都别省。等你亲手把一个“无序充电导致配变过载”的算例通过有序调度压到容量以内再看到总电费降下来那一刻你就会理解为什么我说Matlab加Yalmip这对组合确实有点奇妙。
返回列表