ARTICLE DETAIL

资讯详情

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

基于Matlab的电动汽车有序充电调度优化:MILP与粒子群实现详解

基于Matlab的电动汽车有序充电调度优化:MILP与粒子群实现详解 傍晚六点半小区的充电桩跟前已经排了一溜车。我一同事就住那个小区他说每到这个点变压器就嗡嗡响物业群里隔三差五通知“充电桩功率受限请错峰充电”。他看了眼自己那台电车满电剩35%明天早上还要跑高速不充不行。电价呢这会儿正好是峰段1.2元一度充一晚上比夏天开空调还贵。这不光是钱的问题——大家都挤在这个点插枪电网侧负荷峰上加峰变压器说炸就炸。这就是“无序充电”的真实代价。而我们要做的有序充电策略优化简单说就是用数学模型给一堆电动车排一个“充电计划表”让每台车在满足出行需求的前提下尽量去电价低、电网负荷低的时段充电同时把配电网的功率红线顶住。今天这篇我会从问题本质、数学建模、Matlab代码实现到踩坑实录完整拆一遍这个课题。不管你是做毕业设计、写论文还是充电运营想落地一套调度逻辑这套框架都能直接改着用。整个仿真环境我基于Matlab做的核心用到了多时段动态电价、混合整数线性规划MILP和粒子群对比实验。读完你可以得到一个可复现的小规模算例以及一套遇到“无解”“收敛慢”“谷时挤爆”时候的排查思路。1. 有序充电到底在优化什么1.1 从无序到有序问题本质首先把“无序”描述清楚。所谓无序充电就是电动车用户回到家插枪就充充满为止不做任何时间上的调整。这个行为的直接后果有两个第一用户侧下班时段正好是晚高峰电价充一次电的花费比夜里贵出一倍还不止第二电网侧下班时段本来就有生活用电高峰再加上一堆充电枪同时拉功率配电变压器的负载率很容易冲到100%以上。我见过一个实际案例某小区变压器容量630kVA夏天晚高峰基础负荷大约380kW装了30台7kW慢充桩如果20台同时充那就是140kW叠加上去总负荷直接520kW变压器过载率超过80%。这就是必须做有序充电的硬理由——不是“钱多钱少”的问题是电网设备扛不扛得住的问题。有序充电的核心是把“充电负荷”在时间和空间上重新分配把一部分可平移的充电需求从峰时段挪到平、谷时段同时在变电站或台区功率约束下保证“同一时刻充电车辆的总功率不超过限值”。注意这里有个关键前提充电需求本身不是刚性的只要你设定了“离开时SOC达到某个值”那么在到达和离开之间存在一个时间窗窗口内的充电时段分配就是可以调的。做个类比你就懂了演唱会散场一万个人同时涌向唯一的出口那是无序管理员把观众按区域分批放行虽然有人要多等十分钟但所有人能安全出去场馆也不会踩踏。有序充电就是给电动汽车排队放行只不过排队的依据不是手臂上贴的号而是电价、SOC、离开始时间、变压器余量这些数据。1.2 多时段动态电价模型是怎么来的“多时段动态电价”这个词字面理解就是把一天分成多个时段每个时段给一个电价电价值会随着供需情况变化。这里面有两层静态分时电价和动态电价。静态分时电价你们很熟悉就是峰、平、谷三段比如早上8点到晚上10点峰段1.2元夜里谷段0.38元这个表是固定的各地区电网会发布。但动态电价不是死表它是基于日前负荷预测、新能源出力预测、发电成本算出来的时间粒度可以细到96个点每15分钟一个电价值从一个时段到另一个时段是变动的。多时段的意思就是离散化把连续的一天切成一排时间窗口然后在这个离散序列上做优化。到这里你可能会问直接用峰谷平三段不是挺好吗为什么要搞那么多时段因为时间粒度越细调度自由度越高。三段电价下所有车都会挤到同一个谷段充电谷时会不会又变成“第二个峰”这就是我之前跑仿真踩过的坑后面详细说。而动态电价序列有波动性和不确定性反而会逼着优化算法去权衡“这个谷是一般低”“那个谷是特别低”从而让充电负荷摊得更开。在Matlab仿真里我通常这样生成动态电价先设置基准峰谷差然后在每个时段上叠加一个服从正态分布的随机扰动再加一点时间相关性模拟“预测电价”。这样后面做敏感性分析时只要调整扰动方差就能看策略的鲁棒性。1.3 策略优化的三个典型目标做有序充电第一步不是写代码是先想清楚“优化什么”。我见过很多同学一上来就抄公式目标函数五花八门最后约束一加程序无论如何也不收敛。先把目标定清楚后面一切才顺。常见的三个目标第一个是最小化充电费用这个最直白。用户充电花的钱最少目标函数就是每个时段充电功率乘以该时段电价的累计和。适合以用户侧为主体的算例但有一个隐含问题如果电网不过问所有车会自觉跑到最便宜的谷段充电谷段负荷也会爆。第二个是最小化负荷峰谷差也就是削峰填谷。电网或充电站运营方关注这个希望充电负荷叠加基础负荷后全天曲线尽可能平缓。峰谷差小变压器利用率就高不用扩容。这个目标的数学形式通常写成最小化最大功率或者用一个松弛变量把“全天任意时刻总负荷”压到尽可能低。第三个是综合多目标比如用户费用、电网峰谷差、电池寿命、用户满意度充不满的惩罚一起加权。学术里很流行工程上也有用。不过我不建议第一个算例就上多目标权重怎么定都能吵半天。我的建议是毕设或者论文先做“最小充电费用功率约束”的版本跑通之后再加峰谷差目标用权重或者分层优化两个阶段处理。这样既有逻辑递进又能写清楚“为什么我的策略有效”。下面第二部分我就按这个思路把数学建模完整铺开。2. 数学模型搭建别着急写代码2.1 决策变量与参数定义建模之前先把要做决策的东西拎出来。在有序充电里最基本的决策是哪辆车在哪个时段充多少功率。如果功率是连续的决策变量是P(i,t)第i辆车第t个时段的充电功率kW如果只考虑“充/不充”决策变量是二进制u(i,t)1表示充0表示不充充电功率就固定为Pmax比如7kW。我建议小规模算例用二进制变量更直观。因为家用交流桩功率不可调要充就是7kW不充就是0你让它用5.3kW反而偏离实际。真正可连续调节的是V2G桩或直流快充但那些场景约束更复杂后面再扩展。除了决策变量还需要一组输入参数车辆总数 n时段总数 T以及每时段时长 dt小时电价序列 price(t)长度 T每辆车的电池容量 Cap(i)到达时的SOC soc0(i)离开时目标SOC socTarget(i)SOC上限 socMax(i)通常100%最大充电功率 Pmax(i)允许充电时间窗 [a(i), d(i)]到达时段到离开时段。这里有个容易出错的地方SOC一般用百分比但电量用kWh建模时要统一单位。电池容量50kWhSOC从30%充到90%需要充0.6×5030kWh。充电效率η也要提前定一般0.9~0.95数学上是SOC增量等于η×功率×时长÷容量。2.2 目标函数从省钱到削峰填谷最基础的充电费用目标函数写出来是这样min Σ_{i1..n} Σ_{t1..T} Pmax(i) × dt × price(t) × u(i,t)如果用了连续功率变量把Pmax(i)×u(i,t)换成 P(i,t) 就行。这个公式非常简单但足够说明问题算法会在“贵的时段少充”“便宜的时段多充”之间寻找平衡。如果你要把峰谷差也纳入优化常见做法是加一个辅助变量Z表示“全时段最大总负荷”目标函数变成min Σ费用 λ × Z同时加约束任意时段t基础负荷 baseLoad(t) 充电总功率 ≤ Z。λ是峰谷差惩罚系数调它就是在“省钱”和“削峰”之间找平衡。我自己的体会是lambda取电价均值的0.3到0.5倍削峰效果就比较明显又不至于让费用涨太多。具体数值可以扫描一下做张表敏感性分析很容易写进论文里。2.3 核心约束的建模细节约束条件比目标函数重要得多目标函数再好约束不自洽求解器直接给你报“无可行解”。第一个约束是充电时间窗u(i,t) 0如果t在到达时间之前或离开时间之后。这个必须硬性施加否则求解器为了省钱会把充电任务安排到车还没到的时候。第二个约束是目标SOC约束每辆车在充电时间窗内累计充电量必须达到目标所需电量。写成数学Σ_{t1..T} Pmax(i) × dt × η × u(i,t) ≥ (socTarget(i) - soc0(i)) × Cap(i)这个约束是线性的Matlab优化工具箱能直接处理。注意如果差值是负的到了甚至比目标还高这条约束自动满足说明这辆车根本不需要充电——这在代码里要容错别让它参与优化。第三个约束是电池SOC上下限。很多教材只约束最终SOC忽略了中间过程。比如一辆车SOC已经95%你在窗口中段给它充一小时直接过充这不现实。所以最好加累计约束任意时段结束累计充电量不超过 (capMax(i)-soc0(i))×Cap(i)。别小看这条它会把可行域收得很紧。第四个约束是配电变压器容量约束任意时段所有正在充电的车辆功率之和加上基础负荷不超过变压器可分配功率。因为一台7kW充电桩不是“想充就充”台区功率就那么多这条才是有序充电的“硬约束”。最后还要说明一下这类问题如果你用二进制变量本质是MILP连续功率版本是LP。LP更快但无法表达“要么不充要么满功率”的场景。我建议第一版就做MILP规模小个位数车辆、24个时段求解速度非常快后期再考虑换成启发式算法。2.4 为什么这个模型能实现“有序”把目标函数和约束放一块看有序的逻辑就清晰了时间窗约束保证了需求真实性目标SOC约束保证了用户不被饿死变压器功率约束防止了同时刻拥挤而目标函数里的电价项则引导充电负荷向低电价、低负荷时段流动。四者合在一起系统就会在可行域里自动找“最不挤、最便宜、又能把车充满”的方案。有一个特别值得强调的点单纯“费用最小化功率上限”有时会产生一个新的“谷时拥堵”。举例夜里3点电价最低所有车都想在这个时段充但配电网功率上限只有80kW一堆车排队到这个点还是会受限。这时候有两种解法一是把功率上限调低强行错开二是在目标里加峰谷差惩罚项让算法自己把负荷摊到次低价时段。后者更符合动态电价的初衷——电价波动本身就是负荷引导信号但电价也不能单独扛下所有责任。3. Matlab代码实现与求解流程3.1 两种求解路线怎么选动手写Matlab代码之前先说清楚求解路线。主流两条第一条是“基于优化工具箱的精确解法”。用linprog线性规划或intlinprog混合整数线性规划只要你的模型是线性目标线性约束就能用。优点收敛快、有全局最优性保证、不需要调算法参数。缺点规模大了之后整数变量一多求解时间会爆炸而且不好直接往模型里加非线性约束比如电池寿命衰减曲线。第二条是“基于智能算法的启发式解法”。比如粒子群PSO、遗传算法GA、模拟退火SA把充电计划编码成个体迭代搜索。优点能处理非线性、非凸、甚至黑箱约束适合大规模集群缺点没有最优性保证参数粒子数、惯性权重、交叉率要靠经验调而且容易陷入局部最优。我的建议是算例车辆少于20台、时段数24~96用intlinprog如果做几百台车的集群仿真或者要加入非常复杂的目标再用PSO/GA。而且PSO和GA的代码尽量自己写主框架Matlab自带的particleswarm虽然能用但加约束麻烦不如手写灵活。3.2 基于intlinprog的小规模复现案例我用Matlab的“问题式建模”problem-based approach给大家展示核心代码这个从R2017b开始都支持语法直观不用手拼约束矩阵。假设场景5辆电动车24个时段每小时1个电价序列给定每辆车的参数如下表车辆电池容量(kWh)初始SOC目标SOC充电功率(kW)到达时段离开时段15035%90%742026040%100%762434050%90%721044530%85%751855545%95%7316基础负荷baseLoad我就用一个典型曲线这里不贴全部数据变压器可分配给充电的总功率上限Plimit设为30kW。注意5台车×7kW如果同时充是35kW所以必须错峰。代码核心段T 24; dt 1; n 5; price [0.38 0.38 0.38 0.38 0.38 0.45 0.8 1.0 ... ]; % 按实际填入24个值 Cap [50 60 40 45 55]; soc0 [0.35 0.40 0.50 0.30 0.45]; socTarget [0.90 1.00 0.90 0.85 0.95]; Pmax 7 * ones(n,1); a [4 6 2 5 3]; d [20 24 10 18 16]; eta 0.9; % 决策变量u(i,t) 二进制1表示充电 u optimvar(u, n, T, Type, integer, LowerBound, 0, UpperBound, 1); % 目标最小化充电费用 priceMat repmat(price, n, 1); prob optimproblem(Objective, sum(sum(Pmax .* dt * u .* priceMat)), ... ObjectiveSense, min); % 约束1到达前/离开后不能充电直接通过上界矩阵处理 uUpper ones(n, T); for i 1:n uUpper(i, 1:a(i)-1) 0; uUpper(i, d(i)1:T) 0; end u.UpperBound uUpper; % 约束2目标SOC必须达到 prob.Constraints.socTarget ... (Pmax .* dt .* eta) .* sum(u, 2) (socTarget - soc0) .* Cap; % 约束3电池SOC不超过上限用累计电量上限 for i 1:n cumCap cumsum(u(i,:), 2); prob.Constraints.socMax(i) (Pmax(i) .* dt .* eta) .* cumCap ... (1 - soc0(i)) * Cap(i); end % 约束4台区充电总功率上限 prob.Constraints.powerLimit Pmax * u Plimit; % 1*T维 % 求解 [sol, fval] solve(prob); uOpt round(sol.u);运行之后会得到一个n×T的0/1矩阵把每一列求和乘上Pmax就是每个时段的充电功率曲线。把这条曲线和电价叠加画在一张图里你能很明显看到充电负荷大多落到了低电价时段且任何时刻总功率不超过30kW。这里有三个细节提醒。第一cumsum(u(i,:), 2)生成的上三角累积矩阵乘上“单位时段充电电量”本质上就是“截止时段t累计充了多少电”然后让它不超过剩余可充容量这个约束比只约束终点严格得多可以避免中间过充。第二Pmax * u是矩阵乘法得到1×T的向量正好表示每个时段所有车总功率。第三由于是MILP求解器可能有一些数值误差解出来u的值可能是0.999999所以最后必须round。3.3 基于粒子群算法的功率调度框架如果你要做大规模场景整数变量上千个intlinprog会相当吃力。这时候可以试试粒子群。我自己的习惯是用PSO去搜索“每辆车开始充电的时间点”。核心思路很简单每个粒子代表一组解内容是n辆车在时间窗内的充电起始时段。粒子位置x [s1, s2, ..., sn]其中si在[a(i), d(i)-ceil(needHours(i))]之间随机初始化。needHours是满足目标SOC所需最少充电小时数needHours(i) ceil((socTarget(i)-soc0(i))×Cap(i) / (Pmax(i)×dt×η))约束的处理用罚函数。比如某一辆车算完开始时间后连续充电needHours直到满足目标SOC然后判断总功率是否超过Plimit最后SOC是否超过100%如果违反就在适应度上加上很大的罚值。伪代码框架for iter 1:maxIter for p 1:popSize x particles(p, :); % 每辆车充电起始时刻 schedule zeros(n, T); for i 1:n start round(x(i)); dur needHours(i); schedule(i, start:startdur-1) 1; end power Pmax * schedule; % 每个时段总功率 over max(0, power - Plimit); fee sum(sum(Pmax .* dt .* schedule .* priceMat)); fitness(p) fee 5000 * sum(over); % 罚函数 end % 更新pbest、gbest ... end这种连续充电时长假设比“每时段可断充”更贴近慢充场景但限制也多如果窗口内连续充不完或者会超容量就无解。要做更自由的调度还是回到MILP。所以我把智能算法的定位放在“大规模近似解”和“给定模型校验用”。3.4 动态电价场景生成与对比验证动态电价的构造看起来简单其实要讲技巧。我用的是“基准分时电价随机扰动相关性平滑”的生成方式rng(2025); T 96; % 15分钟粒度 priceBase zeros(1,T); % 峰平谷时间表 peakIdx [17*41 : 21*4, 8*41 : 10*4]; flatIdx [10*41 : 14*4, 6*41 : 7*4]; valleyIdx setdiff(1:T, [peakIdx flatIdx]); priceBase(peakIdx) 1.1; priceBase(flatIdx) 0.6; priceBase(valleyIdx) 0.3; % 加扰动并平滑 noise 0.05 * randn(1,T); noise smoothdata(noise, gaussian, 8); price priceBase noise; price(price 0.2) 0.2;为什么要平滑真实电价不会从1.1瞬间跳到0.3相邻时段之间有连续性。如果你给它加一个白噪声这种没有时间相关性的扰动算法会“钻空子”把充电任务全都安排到一个极低并孤立的时间点上这在工程上根本不可行。所以必须平滑或者用AR模型生成。场景构造好之后建议做三组对比无序充电回家即插即充、有序充电仅考虑费用、有序充电费用削峰。统计指标选三个总电费、峰时段最大负荷、平均SOC完成度。我自己跑过的典型结果动态电价场景5车Plimit30kW大致是无序充电电费约66元峰值负荷52kW超限单纯费用优化的充电电费约43元节省35%左右但最大负荷稳定在30kW以下如果再加削峰权重费用会上升到47元左右换来的是负荷曲线更平坦全天最高负荷降到27kW上下。这个对比很能说明问题“钱”和“电网安全”之间确实有一个Pareto权衡写文章时用这张表很有说服力。4. 常见问题与排查技巧实录4.1 求解器报“无可行解”九成是约束写死这个问题太常见了我当初第一次跑通之前被卡了整整一个晚上。报错信息基本是“No feasible solution found. infeasible.”。原因通常是三类。第一类是目标SOC需求大于时间窗内最大可充量。比如一辆车到达时SOC 20%目标100%电池容量60kWh7kW桩需要充6.8小时而它的充电窗口只有5小时那肯定无解。这个在建模前就应该手动验证先算每辆车的needHours再对比窗口长度。第二类是变压器功率约束定得太苛刻比如Plimit设了15kW但5辆车每辆至少需要充一段时间窗口又重叠无论如何都满足不了。遇到这种情况要么放宽Plimit要么减少参与调度的车辆数量要么扩大时间窗。第三类是SOC上限约束写反了比如把某个时段之后的累计电量约束加到了全部时段导致本来可充电的时段也被锁死。我当时是把cumsum写成了sum(u(:,1:t))的循环版本结果t从1开始没问题到了t2就把前两个时段的累计都锁住了相当于把一个“爬坡上限”错误地当成了“总上限”。4.2 变量索引越界尤其是到达时段等于1如果到达时段是1代码里1:a(i)-1会变成1:0在Matlab里这个向量是空的倒不会越界但逻辑可能出问题。更麻烦的是离开时段等于T的情况d(i)1:T如果dT就变成T1:T结果又是空的看着没报错实际约束漏了。我现在的习惯是所有时间窗参数一律先做一次清洗a max(a, 1); d min(d, T);然后把“不允许充电”的部分单独用约束写而不是悄悄改上界矩阵。这样即使数据异常至少能通过约束数量发现问题。4.3 求解结果看起来合理但画图后露馅有时候fval降下来了SOC约束也都满足但你把充电负荷画出来一看——所有车都在同一个最便宜的时段充总功率顶在Plimit上限一动不动。这个结果虽然“可行”但在工程上极不健康因为它是“谷时拥堵”。排查思路先打印每辆车开始充电时段和结束充电时段看看是不是出现“扎堆”。如果扎堆说明目标函数里缺了峰谷差惩罚项或者Plimit设得太大在谷时段根本不形成约束。我通常这样处理把Plimit设成比“总充电需求/可利用窗口”稍微紧一点的值让算法不得不铺开充。另一个隐蔽问题是“最小化费用”可能导致一辆车中途多次启停比如0点充半小时、1点充半小时、2点半又充半小时虽然满足所有约束但频繁启停对电池和接触器都不友好。想避免这个需要引入“最小连续充电时长”约束或者把变量改成“充电开始时间”。4.4 算例验证与效率提升的实操建议模型跑通之后一定要做两个验证。第一个是“极端验证”把电价全部设成同一个常数有序充电的结果应该退化为“所有车尽量均匀分布且总费用最低”可以用来排查目标函数方向对不对。第二个是“手工验证”挑一辆车手动算它需要充几个小时在哪个时段充最省然后用代码输出对比。如果我调程序时能有一次通过手工计算对上的后面大规模算例的信心就会很足。效率方面MILP的求解时间对整数变量个数非常敏感。5辆车24时段的算例几乎瞬时完成改成100辆车96时段intlinprog可能会跑几分钟甚至更久。我的应对手段有三个一是把时间粒度从15分钟放宽到1小时前提是电价序列也跟着重采样二是把每辆车的时间窗先压窄不要给全天只给真实可用窗口能大幅缩小变量规模三是用“启发式初值intlinprog”先用粒子群跑一个粗略可行解作为x0传给intlinprog热启动往往能快很多倍。4.5 关于多目标权重和结果呈现的一句总结最后再给一个细节如果你最后用了“费用λ峰谷差”这种加权目标论文里一定要放λ灵敏度分析。λ0时费用最低但峰谷差大λ0.2、0.5、1.0费用会逐渐升高峰谷差逐渐减小。把这条曲线画出来评阅人一眼就能看出你理解了策略的权衡而不是只会套一个目标跑结果。我就是靠这张图把一个原本平平无奇的算例撑成了有点深度的对比实验。另外写代码不要一次性写完整版。先写2辆车、6个时段的迷你算例甚至可以直接手算验证跑通了再扩到5辆、24时段最后才上96时段的大场景。这种“从小模型到大模型”的开发习惯能帮你省掉至少两天的调试时间。我在这个课题上踩过的坑大部分都是因为贪快想一次写完结果被一堆维度不匹配、约束冲突的问题淹没。
返回列表