
晚上六点社区里二十辆电动汽车同时插上充电枪每台都以最大功率开始充电——这个场景放在五年前没什么但在今天不少小区的配电变压器会在晚高峰里直接触发过载保护。问题出在“插上就充满”这个默认动作上电动汽车无序充电本质上是在电网最紧张的时间段又叠加了一个用电高峰。我这次要分享的是一个基于多时段动态电价的电动汽车有序充电策略优化项目用Matlab完成建模和求解目标是让充电行为从“无序抢电”变成“有序避峰”既降低用户充电费用又缓解配电网负荷压力。这篇文章适合正在做充电策略相关课题的学生、想落地有序充电功能的工程师以及刚接触优化建模、想看看线性规划在真实场景中怎么用的朋友。一小时内到达的充电需求是固定的但发生在哪个时段是灵活的这就是有序充电的核心逻辑。1. 多时段动态电价下的有序充电问题本质与建模前提1.1 “无序充电”为什么需要被优化先看一个最朴素的充电方式用户下班回家插上充电枪充电桩以额定功率直接充到电池满或用户拔枪为止。这个行为对单台车来说没有任何问题7kW的家用慢充一晚上充50度电充完睡觉第二天满电出发。但问题一规模化就暴露了。居民区电动汽车的充电行为高度同步。工作日大部分用户集中在傍晚六点到九点回家插枪时间高度重合。如果每台车都满功率充电一个100户的小区按10%的电动车渗透率算10台车同时以7kW充电就是70kW的附加负荷这个数值已经超过一台常见配电变压器的可用裕度。更麻烦的是这个负荷恰好叠加在晚高峰负荷曲线上——夏天傍晚空调还在运行照明、厨电负荷还没降下来充电负荷一上来变压器就容易被逼到过载边缘。无序充电带来的第二个问题是经济上的浪费。很多地区已经实行峰谷分时电价晚高峰时段的电价可能是深夜低谷电价的两到三倍。用户充入同样的电量由于充电时间没有规划可能在电价最高的时段充了相当一部分电。这个问题对单个用户来说可能只是一个月多几十块钱但在充电运营商的场景里一个拥有几百根桩的场站优化空间就非常可观了。所以有序充电解决的是这样一件事充电的总量不变、用户的充电需求不牺牲只改变充电功率在时间上的分布。什么时候充、以多大功率充由优化算法来决策。这个决策的依据就是多时段动态电价和电网侧的约束。1.2 动态电价的两种典型形态“多时段动态电价”这个名字听起来复杂实际落地主要有两种形态。第一种是分时电价即TOUTime of Use。电网把一天划分成峰、平、谷若干时段每个时段设定一个固定电价提前公布用户和充电运营商可以据此规划充电行为。比如常见的居民分时电价会是这样的时段类型电价元/kWh18:00 - 22:00峰1.1022:00 - 次日06:00谷0.4006:00 - 08:00平0.70第二种是实时电价即RTPReal-Time Pricing。电价每隔15分钟或1个小时根据电力市场供需情况更新一次更接近真实的发电边际成本。这种模式下充电策略需要滚动优化每到一个新时段用最新电价重新计算后续的充电计划。不管是TOU还是RTP落到数学建模上其实是一回事——输入一个长度为N的电价序列 price(1), price(2), ..., price(N)每个元素代表一个时段内充1千瓦时电需要付出的钱然后在这个序列的基础上做充电功率分配决策。本文后面的Matlab实现就是基于这种统一的“电价序列”视角。为了让结果可复现我采用TOU分时电价作为示例输入但代码结构完全可以直接替换成实时电价数据。1.3 这个模型的边界单台车、单时段、纯充电任何一个优化模型都有它的边界假设先把这个说清楚后面才不会误用。本文建立的模型是单台车在固定停车时段内的有序充电优化。假设这辆车傍晚到达充电桩接入后直到第二天早上才离开停车时段被离散成N个等长时段每个时段内充电功率恒定。目标是找一个功率序列使得在满足“离开时电量达到目标值”的前提下总充电费用最低。模型里不考虑V2G放电车辆向电网反向送电也不考虑电池老化成本更不涉及多台车之间的功率分配。这几个扩展方向我会在文章最后给出思路但作为核心推导先把单台车的线性规划模型吃透。单台模型是集群优化的基础模块——把一台车看成一个子问题多台车就是多个子问题耦合一个总功率约束理解了单台的求解逻辑扩展到集群只是工程量的问题不是思路的问题。2. 优化模型怎么搭决策变量、目标函数与约束条件2.1 场景参数与决策变量设计建模之前先把场景里的固定参数列清楚。假设车辆在傍晚18:00接入充电桩第二天早上08:00离开共14个小时。以1小时为一个时段时段数量N14。电池容量Cap60kWh接入时剩余电量SOC020%目标离开电量SOC_target90%也就是需要充入42kWh。充电桩最大功率Pmax7kW对应常见的家用交流慢充桩。决策变量是每个时段的充电功率记为P_tt1,2,...,N。单位是kW。P_t可以取0到Pmax之间的任意值表示这个小时内充电桩实际输出的平均功率。为什么是连续变量而不是“充/不充”的0-1变量因为在实际充电桩执行层面功率是可以连续调节的很多交流桩支持调功率直流桩更是自带功率分配能力。连续变量让模型成为一个线性规划求解高效全局最优后面会展开讲这个选型的理由。还有一个隐含变量是每个时段结束时的电池电量SOC_t。它不参与优化求解而是由P_t递推出来SOC_t SOC_{t-1} P_t * Δt / Cap其中Δt是时段长度本文取1小时。2.2 目标函数充电费用最小化用户最直观的诉求是少花钱。因此目标函数设定为整个停车时段内的总充电费用最小minimize ∑(P_t × price_t × Δt)其中price_t是第t个时段的电价。这个目标函数是线性的因为电量单价与充电功率、时段之间没有耦合关系。把price_t × Δt合并成系数f_t目标函数就变成标准的线性形式fx。这里有一个容易犯迷糊的地方目标函数为什么不是“充电量最大化”因为充电需求已经由约束条件锁死了必须充够42kWh多充反而浪费钱。所以目标函数只管“同样的充电量怎么分配时段能让花费最小”不是在“多充”和“少充”之间做权衡。这个设计逻辑在建模时要想清楚不然目标函数和约束会出现冲突。2.3 四类约束条件及其数学形式约束条件分四层从物理设备到电网侧逐层来看。第一层是充电功率的上下限。每个时段的充电功率不能为负也不能超过充电桩的额定功率0 ≤ P_t ≤ Pmax。写成矩阵形式就是变量的lb和ub边界。第二层是总充电量约束。整个停车时段内充入的总电量必须等于需求电量即∑(P_t × Δt) E_req Cap × (SOC_target - SOC0) / η其中η是充电效率本文算例先取1.0简化后面调试部分再说效率怎么处理。这是等式约束也是整个模型里与“用户需求”直接挂钩的约束。它保证了无论功率怎么安排离开时一定达到目标电量。第三层是电池SOC上限约束。任意时段结束时电池电量不能超过电池容量即SOC_t ≤ SOC_max。写成累计充电量的形式∑_{i1}^{t}(P_i × Δt) ≤ Cap × (SOC_max - SOC0)。这个约束的矩阵形式是下三角矩阵乘以功率向量。这里有个值得注意的细节在纯充电且功率非负的设定下SOC是单调递增的所以最终SOC不超过上限就能保证全程不超过上限也就是说这个约束在纯充电模型里通常是冗余的。但代码里我还是保留了它——原因后面调试部分细说。第四层是电网侧约束单台车模型里可以先不体现扩展到多台车时必须加入任意时段所有车辆的总充电功率不能超过配电变压器容量。这层约束在本文的单车算例里不激活但模型扩展章节会给出矩阵构造方法。2.4 为什么“线性规划刚刚好”很多第一次做充电优化的朋友会习惯性想到遗传算法、粒子群这类智能算法因为“优化”两个字听起来就很适合启发式搜索。但这个模型的真实结构是线性目标函数 线性约束 连续变量这是一个标准的线性规划问题用linprog求解是最合适的选择。线性规划的优点是理论成熟、求解极快、保证全局最优。上面这个14变量的LPlinprog求解时间在毫秒级。遗传算法等启发式算法在这个问题上没有优势它们适合的是非凸、非线性、不可导的目标函数比如引入电池老化成本的非线性项、或者有离散0-1决策变量需要整数规划的场景。每次看到有人用遗传算法去解一个标准LP我都替那些白白消耗的计算时间心疼。这不是说智能算法没必要学。如果模型改成“目标函数加入电池循环寿命损耗的二次项”那就变成了二次规划可以用quadprog如果再加入“充电桩是否开启”的0-1变量变成混合整数线性规划那就需要用intlinprog或分支定界法。什么时候用什么工具取决于模型结构而不是“优化”这个词本身。3. Matlab实现从参数表到linprog求解的完整链路3.1 参数初始化电价序列、车辆参数、时段划分Matlab代码的第一步是把所有输入参数写清楚。我习惯把所有参数集中放在脚本开头方便改参数跑不同场景。%% 基于多时段动态电价的电动汽车有序充电优化 % 场景18:00接入次日08:00离开共14个时段每时段1小时 clear; clc; %% 1. 时段与电价 dt 1; % 时段长度小时 N 14; % 停车时段数量 price [1.10*ones(1,4), ... % 18:00-22:00 峰时段 0.40*ones(1,8), ... % 22:00-06:00 谷时段 0.70*ones(1,2)]; % 06:00-08:00 平时段 %% 2. 车辆参数 Cap 60; % 电池容量kWh SOC0 0.20; % 接入时SOC SOC_target 0.90; % 离开时目标SOC Eta 1.0; % 充电效率简化取1.0 Pmax 7; % 最大充电功率kW E_req Cap * (SOC_target - SOC0) / Eta; % 需充入电量kWh这里把price向量直接写成三段对应峰谷平。如果做实时电价的滚动优化只需要把price改成从外部数据源读入的向量其他代码完全不用动。E_req的计算把SOC差换算成绝对电量这是后面等式约束的右侧常数。3.2 目标函数与约束条件的矩阵化linprog的标准形式是min fx约束是Ax ≤ b、Aeq x beq、lb ≤ x ≤ ub。核心工作就是把前面定义的优化模型翻译成这几个矩阵。%% 3. 构造线性规划 f price * dt; % 目标函数系数每时段充1kW的电费 lb zeros(N, 1); % 功率下限 ub Pmax * ones(N, 1); % 功率上限 Aeq ones(1, N) * dt; % 等式约束总充电量 beq E_req; Aineq tril(ones(N)) * dt; % 不等式约束任意时刻累计充电量 剩余容量上限 bineq Cap * (1 - SOC0) * ones(N, 1);逐个解释。f price * dt因为目标函数系数是“充电功率1kW持续一个时段产生的电费”电价按kWh计价所以乘上时长。lb和ub直接对应功率上下限。Aeq是长度为N的全1行向量乘以dt表示把14个时段的充电量累加beq是需充入电量。Aineq用下三角矩阵构造了一个“累计充电量”矩阵第i行表示前i个时段的累计充电量不超过bineq这个bineq就是电池里还能充进去的空间。很多教程会漏掉Aineq认为纯充电场景纯属冗余。我的建议是保留它。原因有两个一是它显式表达了电池SOC上限这个物理约束代码读起来语义完整二是将来扩展V2G或者双向充电时SOC不是单调递增的这个约束就会变成真正的活动约束代码结构已经留好了口子。3.3 求解、退出标志检查与结果输出调用linprog很简单但这里有一个必须养成的习惯检查exitflag。%% 4. 求解 options optimoptions(linprog, Display, off); [x, cost, exitflag] linprog(f, Aineq, bineq, Aeq, beq, lb, ub, options); if exitflag ~ 1 warning(linprog未找到最优解exitflag %d, exitflag); else fprintf(最优充电费用%.2f 元\n, cost); fprintf(总充电量%.2f kWh\n, sum(x) * dt); SOC SOC0 cumsum(x * dt) / Cap; % 各时段结束时SOC figure; subplot(2,1,1); stairs(0:N-1, [price, price(end)], LineWidth, 1.5); xlabel(停车时段); ylabel(电价元/kWh); grid on; subplot(2,1,2); stairs(0:N-1, [x, x(end)], LineWidth, 1.5); xlabel(停车时段); ylabel(充电功率kW); grid on; endexitflag等于1表示求解成功。如果不检查exitflag无解时x可能是空数组后面画图、算SOC全都会报错而且报错信息还不容易看懂。我在调试踩坑部分会专门展开这个。画图用了stairs而不是plot因为功率和电价都是时段常量阶梯图更符合物理含义。两条曲线画在上下子图里直观看出“电价高的时段功率是零、电价低的时段功率拉满”这个有序充电的核心规律。3.4 无序充电基线怎么仿真有有序充电的结果还不够得有一个参照系。无序充电的仿真逻辑很简单接入立即以最大功率充电直到充够目标电量就停止。%% 5. 无序充电基线 x_base zeros(N, 1); E_left E_req; for t 1:N if E_left 0 break; end x_base(t) min(Pmax, E_left / dt); E_left E_left - x_base(t) * dt; end cost_base f * x_base; fprintf(无序充电费用%.2f 元\n, cost_base);这个循环模拟的是“到家插枪满功率充电充满即停”的用户行为。Pmax7kW、E_req42kWh充满需要6个小时所以在18:00接入后连续充6个时段到24:00左右充满。把这组功率序列带入同一电价序列就能算出无序充电的费用。这个基线虽然简单但非常重要——没有它有序充电节省了多少就没有参照后面所有对比分析都无从谈起。4. 算例结果与敏感性分析省了多少电费、削了多少峰4.1 基准算例结果对比直接跑上面的代码无序充电和有序充电的结果如下。指标无序充电有序充电充满时刻24:00充6小时04:00谷时段充6小时充电费用36.40 元16.80 元峰时段充电量28 kWh0 kWh谷时段充电量14 kWh42 kWh费用节省比例—53.8%费用怎么算出来的无序充电从18:00开始满功率充6小时前4小时落在峰时段18:00-22:00每小时充7kWh共28kWh按1.10元计价产生30.80元后2小时落在谷时段22:00-24:00充14kWh按0.40元计价产生5.60元合计36.40元。有序充电把所有42kWh都安排在22:00开始的谷时段前6小时满功率充电就能满足需求42 × 0.40 16.80元。比无序充电整整省了19.60元。这个结果背后是“负荷平移”——同样的充电需求从电网晚高峰完全挪到了深夜低谷。作为对比试验我把这两组功率序列分别叠加到典型居民晚高峰负荷曲线上可以看到无序充电在18:00-22:00之间硬生生加了一个7kW的负荷平台而有序充电的负荷完全避开了这个区间。这就是削峰填谷的直观体现。变压器容量是按照“最大负荷”来配置的如果把充电负荷从高峰时段挪走就等于降低了最大负荷需求对配电网来说是实打实的容量释放。4.2 充电功率、电池容量、电价差对效果的影响固定算例只能说明一种情况。我把几个关键参数各跑了一遍结论很有意思。充电功率Pmax从3.5kW小功率慢充、7kW标准慢充到11kW快充有序充电的节省比例变化很大Pmax有序充电费用无序充电费用节省比例3.5 kW23.80 元26.60 元10.5%7 kW16.80 元36.40 元53.8%11 kW16.80 元46.20 元63.6%Pmax3.5kW时谷时段8小时最多只能充28kWh需求42kWh剩下的14kWh无论如何都要落在平段和峰段所以有序充电的优势被压缩到只有10.5%。Pmax11kW时无序充电4小时左右就能充满全部落在峰时段费用高达46.20元有序充电依然可以全部挪到谷时段节省比例反而更高了。这说明一个反直觉的规律充电功率越大有序充电的价值越突出。因为功率大意味着可以在更短的时间内完成同样的充电量也就有更大的“时间自由”去挑选电价便宜的时段。再看电池容量和电价差的影响。把电池换成100kWhSOC020%、目标90%需求70kWh。谷时段8小时×7kW56kWh不够还需14kWh分摊到平段和峰段。有序充电费用56×0.40 7×0.70 7×1.10 35.00元。无序充电70kWh需10小时充满峰段28kWh×1.10 谷段42kWh×0.40 30.80 16.80 47.60元。节省26.5%。可见电池容量越大、需求电量越高能灵活调度的空间越小有序充电的增益也会下降。电价差的影响更直观。如果谷电0.30元、峰电1.20元价差0.90元有序充电费用42×0.3012.60元无序充电37.80元节省66.7%如果谷电0.60元、峰电0.80元价差仅0.20元有序充电25.20元无序充电30.80元节省18.2%。价差越大有序充电的杠杆效应越明显。这也意味着推动峰谷电价差拉大的政策本身就在为有序充电创造经济激励。4.3 从仿真结果到实际充电桩执行仿真做完怎么把这个功率序列变成充电桩上的实际行为有序充电算法输出的是一个功率计划比如本文算例的结果是“22:00到04:00以7kW充电其他时段功率为零”。执行层面需要把这个计划下发到充电桩。现在的智能充电桩基本都支持远程设置充电功率和启停时间运营商平台拿到功率序列后按时间段下发指令即可。技术栈上可以做成一个简单的云端调度服务每天根据第二天的预测电价TOU电价可直接读取RTP电价需要预测加上用户设定的出发时间和目标SOC调用本文的优化函数算出功率计划再推送到充电桩执行。如果用户临时改变出发时间只需要触发一次重新计算。这里有一个工程实现上的问题仿真假设充电功率是连续可调的但部分交流桩只支持固定档位比如只有3.5kW和7kW两档。这种情况下需要把连续LP的解做档位化后处理或者干脆在建模阶段就把功率档位作为离散变量升级成MILP模型。具体选哪种方案取决于充电桩的硬件能力。5. 调试踩坑与工程化扩展建议5.1 linprog报错与结果异常的排查链路这个项目跑起来可能遇到的问题我踩过一遍整理成排查链路供参考。第一个问题是无解。表现为exitflag返回-2或-3。无解通常有三个原因一是E_req超出了物理极限比如Pmax3.5kW、N14时段最多只能充49kWh但E_req设成了60kWh那必然无解二是E_req超过了电池剩余容量比如SOC00.5、目标0.9、Cap60需求24kWh剩余容量是30kWh看起来没事但如果SOC_max写错成0.85需求24kWh超过剩余21kWh就无解了三是Aineq构造错了负号、转置、维度搞反约束方向反了也会让可行域为空。排查时先打印E_req和Pmax×N×dt对比再看SOC上限约束是否有解。第二个问题是结果“看起来不合理”。比如有序充电的功率序列竟然出现在峰时段。这种情况首先检查电价序列和时段索引是否对上了。我最初用真实电价数据时价格向量的第一个元素对应的是零点不是接入时刻结果优化结果全部错位。把电价序列和停车时段对齐是最容易忽略的一步。第三个坑是效率系数。Eta没有纳入E_req的计算。设置Eta0.9时电池需要42kWh电网侧实际要取电42/0.946.67kWh。如果E_req仍按42kWh算最终SOC会低于目标值。我在代码示例中先设Eta1.0是为了展示核心逻辑实际工程里这个值必须认真对待通常取0.85到0.95之间。第四个坑是linprog返回的x里偶尔会出现1e-9量级的微小值。这是求解器数值误差导致的不影响总费用但画SOC曲线时会在功率序列里看到一些莫名其妙的毛刺。处理办法很简单画图前加一句x(x 1e-6) 0。还有一点关于Aineq冗余约束的观察。在纯充电模型里SOC上限约束理论上不活跃。我一开始也怀疑是不是模型写错了为什么这个约束从来不起作用。后来想明白了充电功率非负时SOC单调上升终点满足上限则全程满足上限。但保留它仍然有意义——一旦扩展V2G双向充电SOC曲线不再单调这个约束立刻变成真正的约束。这种“给未来留接口”的写法在项目迭代中能省不少事。5.2 从单台车扩展到充电集群模型怎么改单台车模型是基础实际项目中更常见的是充电集群的有序调度。一个充电站几十根桩配电容量有限需要统一调度所有车辆的充电功率。数学上扩展的核心变化是决策变量从“一台车N个时段的功率”变成“M台车N个时段的功率”。假设第m台车的充电功率是x_m(t)目标函数变成所有车辆在所有时段的费用之和。每台车有自己独立的SOC初始值、目标值、Pmax、接入和离开时段。约束需要按车分块构造。集群模型增加一个全局约束任意时段t所有在线车辆充电功率之和不超过变压器可用容量P_trans即∑_{m1}^{M} x_m(t) ≤ P_trans。在Matlab里这个约束可以用kron或repmat构造稀疏矩阵。规模上100台车、24个时段总共2400个决策变量对这个规模的LP来说依然是秒级求解完全具备实时滚动调度的计算条件。实际操作中还有一个维度用户接入和离开时间是随机的。有的车早上开走有的车下午才插上。这就不能用一个统一的N时段矩阵而是每台车根据自己的停车窗口生成约束矩阵再拼装成一个大LP。好消息是linprog对稀疏矩阵支持很好这种分块装配的效率完全够用。5.3 再说几个可以继续做的方向基础模型跑通之后可以往几个方向延伸都是实际项目里被问得最多的。第一个方向是实时电价下的滚动优化。把固定电价序列换成实时更新的电价数据算法每到一个新时段就重新求解一次只执行当前时段的第一步然后滚动到下一时段。这种模型对电价预测误差有天然的鲁棒性——预测不准没关系每小时校正一次。实现上就是本文的代码包一个for循环加一个“更新电价、重新构造LP”的外壳。第二个方向是考虑电池老化成本。充电过程会加速电池循环寿命衰减如果把单位充电量对应的老化成本折算进目标函数就能在“省电费”和“保护电池”之间做权衡。这种模型往往是非线性的目标函数里会出现二次项需要换成quadprog或者fmincon。这个方向更适合动力电池梯次利用、换电模式等对电池寿命敏感的场景。第三个方向是纳入光伏出力和家庭储能。家里有屋顶光伏或储能电池时有序充电就变成了“光储充”联合调度光伏出力大的时段优先给车充电储能电池在谷时段充电、峰时段放电给车。多时段动态电价框架可以直接复用只需要增加光伏出力序列和储能SOC约束。第四个方向是需求响应。电网在极端时段会发出削峰指令如果充电运营商能在这些时段主动降低充电功率可以获得需求响应补贴。把补贴收益建模成目标函数的负项有序充电就从“被动避峰”变成了“主动响应”这是目前很多充电运营商探索的商业模式。以我的实操体会先把单台车模型跑通、理解透是一切扩展的前提。很多同学一上来就奔着多车集群甚至光储充联合调度去做结果被各种边界条件淹没连最优解都求不出来。不如先用本文这套代码把基础逻辑验证清楚再一层层往上加复杂度。这个项目的代码量和思路都非常适合作为充电优化方向的入门基石后续的扩展空间也足够大。