☰
基于动态电价的电动汽车有序充电策略:Matlab建模与优化
2026/10/11 5:52:54 网站建设 项目流程

我用Matlab做这个项目的时候,第一反应是"这不就是个线性规划嘛"。真正把数据跑起来才发现,电动汽车有序充电这件事,坑全藏在约束条件的组合里,就像小时候玩的华容道——单个块怎么走都很简单,但要所有块一起通关,就得动点脑筋。这篇文章我会把整个"基于多时段动态电价的电动汽车有序充电策略优化"项目的思路、建模、代码和调试过程完整拆开来讲,适合正在做配电网、电力市场或者电动汽车入网方向课题的同学参考,拿着就能改、就能跑。

1. 为什么电动汽车充电要"有序"

1.1 无序充电的代价有多大

先说我实验里最直观的一组数字。假设一个小区的配变容量是 800kVA,晚高峰基础负荷在 500kW 上下,本来余量很充足。可一旦接入 100 台 7kW 的交流慢充桩,大家都下班回家随手插上枪,17:00 到 19:00 这段时间直接叠出 1200kW 的尖峰,变压器立马过载。这不是我拍脑袋算的,是用蒙特卡洛模拟生成 100 辆车的到家时间、出发时间、初始电量之后,按"即插即充"策略跑出来的结果。很多文献里把这个叫"峰上加峰"——电网调度员最头疼的情况之一。

无序充电的代价不只是变压器寿命缩短。峰段电价贵,用户直接多掏钱;削峰填谷的调峰资源要额外调用,社会成本也跟着涨;电压越限的问题在小区域里尤其严重,末端用户可能发现自家灯光傍晚开始发暗。这些问题的根源在于:电动汽车既是负荷,又是可控资源,但"可控"这件事需要调度信号来激活。多时段动态电价,就是最天然的调度信号。

1.2 "有序"到底在优化什么

有序充电的英文是 Coordinated Charging 或者 Smart Charging,本质上是一种"削峰填谷"的负荷调度。它通过调整每辆车的充电时段和充电功率,让总充电负荷避开峰段、填进谷段,或者至少削掉尖峰。我在这套 Matlab 代码里实现了两种目标:一种是纯费用最低,用户省钱;另一种是负荷方差最小,电网侧友好。两种策略跑完一对比,你会发现用户省钱和电网消峰有时候是冲突的——这时候多目标函数就派上用场了,我后面会展开讲。

再往深里说,"有序"的另一个维度是约束。每辆车的电池容量、起始荷电状态(SOC)、目标电量、最大充电功率、接入电网的开始和离开时间都不相同,优化算法要从这些五花八门的约束里找出一个全局可行的功率分配方案。动态电价只是给了优化一个"价格梯度",真正让策略成立的是约束建模的准确性。这点我建议做课题的同学重点看第 2 章,约束条件写不严谨,求解器分分钟给你返回 Infeasible。

2. 数学模型:从物理世界到优化语言

2.1 多时段动态电价怎么离散化

电价是连续波动的,但实际按小时或 15 分钟出清。我建议用 (T) 个时段来离散,比如一天 24 小时、每 15 分钟一个点,就是 (T=96)。设 (p_t) 为第 (t) 个时段的电价(单位:元/kWh),那么用户的充电费用 (C) 可以写成:

[ C = \sum_{t=1}^{T} \sum_{i=1}^{N} p_t \cdot P_{i,t} \cdot \Delta t ]

其中 (P_{i,t}) 是第 (i) 辆车在时段 (t) 的充电功率(kW),(\Delta t) 是时段长度(h),(N) 是电动汽车数量。这个式子看起来简单,但它把所有成本都压到了"功率变量"上,后面线性规划的目标函数就是直接把它向量化。

动态电价和静态分时电价有个区别需要说清楚:分时电价是固定峰谷平时段,比如早上 7 点前谷价、10 到 15 点是平价、18 到 22 点是峰价;动态电价则是更接近实时电价(RTP)的序列,可以模拟光伏发电或风电大发时出现负电价等极端情况。做课题时你可以自己生成一条 96 点的电价曲线,也可以拿典型日的真实电价数据来跑。我代码里默认用的是 8 个典型价格带的平滑曲线,但结构上完全兼容输入任意长度 96 的数组。

2.2 目标函数:单一目标还是多目标

我在这套代码里实现了三种目标,方便对比分析。

第一种是费用最小。线性规划形式是:

[ \min \quad \mathbf{c}^{\top} \mathbf{x} ]

(\mathbf{x}) 是所有 (P_{i,t}) 拼成的列向量,(\mathbf{c}) 是对应系数。这种策略会把充电尽可能挪到低价时段,极端情况下所有车都挤在凌晨 1 点到 5 点充电,虽然总费用低了,但会在谷段自动形成新的负荷尖峰——这个现象做仿真时最容易发现,也最容易被忽视。

第二种是负荷方差最小。目标函数写成:

[ \min \quad \sum_{t=1}^{T} \left( L_{t}^{\text{base}} + \sum_{i=1}^{N} P_{i,t} - L_{\text{avg}} \right)^{2} ]

这是二次规划(QP),Matlab 里用 quadprog 就能解。负荷方差小了,曲线就平了,对配变最友好。但代价是部分用户充电费用可能会比费用最优时高,因为他们可能被安排到电价相对高的时段完成充电。

第三种是多目标加权。把费用和方差归一化之后加起来:

[ \min \quad \omega_1 \cdot \frac{C}{C_{\max}} + \omega_2 \cdot \frac{V}{V_{\max}} ]

(\omega_1+\omega_2=1)。这个式子写起来容易,调权重才是真麻烦。我跑了几十组参数,经验是 (\omega_1=0.6, \omega_2=0.4) 左右能兼顾省钱和削峰,但如果你的基础负荷曲线峰谷差特别大,建议把 (\omega_2) 再往上提。

2.3 约束条件:每个都不能少

先把单个电动汽车的充电动态方程写出来:

[ SOC_{i,t+1} = SOC_{i,t} + \frac{\eta \cdot P_{i,t} \cdot \Delta t}{E_i} ]

(\eta) 是充电效率,一般取 0.9 到 0.95,(E_i) 是电池容量(kWh),我案例里设成 40kWh。SOC 上下限通常取 ([0.1, 0.9]),不过并非所有车都要充满,更重要的是离网时满足用户需求,即:

[ SOC_{i,T_{i,\text{dep}}} \ge SOC_{i,\text{target}} ]

这部分是保证"用户满意度"的底线,代码里一旦把这个约束写到目标里头去控制,问题往往很快就变成无解。

功率约束有两层意思:一是每时每刻不能超过充电桩容量 (P_{i,t} \le P_{\max});二是车辆接入电网之前和离开之后功率必须为 0:

[ P_{i,t} = 0, \quad t \notin [T_{i,\text{arr}}, T_{i,\text{dep}}] ]

可调度时段可以用稀疏矩阵做约束,也可以用决策变量的上下界和索引集实现,后面代码部分会讲。

还有一条容易漏的是配变容量约束:

[ L_{t}^{\text{base}} + \sum_{i=1}^{N} P_{i,t} \le S_{\text{trans}} ]

这个约束一旦加上去,解空间立马收缩,求解时间也可能成倍增加,但它是"有序充电"区别于"只是省钱充电"的关键。没有它,调出来的策略在真实世界里根本不敢用。

3. Matlab 代码实现全流程

3.1 工具箱选择:linprog 还是 YALMIP

Matlab 优化工具箱自带linprog处理线性规划、quadprog处理二次规划,做小规模算例完全够用,我在网上最常见的提问也是"怎么用 linprog 搭有序充电",所以这套代码主体我用纯linprog实现,方便你直接跑。比较复杂的模型扩展——比如加入整数变量来表示"充电是否连续"或者"是否在某个时刻开始充电"——那就得上intlinprog或者 YALMIP + 外部求解器了,YALMIP 的好处是建模直观,用sdpvar声明变量,optimize一句求解,坏了也好排查。

我实测过,100 辆车、96 个时段,连续线性规划的决策变量有 (100\times96=9600) 个,linprog默认的内点法几秒钟就能解完。如果换成混合整数规划(比如加充电状态二进制变量),问题规模直接膨胀到上万个整数变量,内点法不够用了,得靠分支定界,这时候 Matlab 自带求解器会非常吃力。所以给的建议是:能建 LP 就建 LP,尽量别引入整数变量,除非你研究的就是"充电连续性"这类必须用整数的课题。

3.2 代码结构:数据产生和主求解器的分工

我的工程文件分了这几个部分:

  • load_data.m——读入基础负荷曲线、电价曲线、车辆参数;
  • generate_evs.m——用蒙特卡洛生成每辆车的到达时间、离开时间、初始 SOC、目标 SOC;
  • build_problem.m——把约束矩阵和目标系数拼起来;
  • run_optimization.m——调用 linprog 并统计结果;
  • plot_results.m——画总负荷曲线、各车 SOC 曲线、费用对比柱状图。

拆分的好处是后面做灵敏度分析时,只改generate_evs.m的参数就行,主求解器不用动。这个架构我刻意模仿了学术论文里"算例系统"的组织方式,因为后续你们写论文,图和数据基本都是从这几个模块产出的。

generate_evs.m里比较关键的是出行行为参数。到达时间我用正态分布取样,均值设在 18:00,标准差 1.5 小时;离开时间取正态分布,均值 7:30,标准差 1 小时;初始 SOC 用均匀分布 (U(0.2, 0.6)),目标 SOC 统一设 0.9。这些参数不算精确的真实统计数据,但做策略对比足够了。

3.3 核心建模代码:一次看懂约束矩阵怎么拼

直接把最核心的build_problem.m结构写出来,对照着看就清楚了。

N = 100; % 车辆总数 T = 96; % 调度时段数(15分钟间隔) dt = 0.25; % 时段长度,单位小时 Pmax = 7; % 充电桩最大功率,kW E = 40; % 电池容量,kWh eta = 0.92; % 充电效率 SOC_init = ...; % N行1列,初始SOC SOC_target = ...; % N行1列,离网目标SOC arrT = ...; depT = ...; % 入网/离网时段索引,N行1列 price = ...; % 1行T列,电价(元/kWh) baseLoad = ...; % 1行T列,基础负荷(kW) S_trans = 800; % 配变容量(kW) % 决策变量 x 的排列方式: 车1的全部时段,然后车2的全部时段,依此类推 % 因此 x((i-1)*T + t) 表示第i辆车在第t个时段的充电功率 nVar = N * T; % 目标函数系数 c_total = price * dt c = repmat(price, 1, N) * dt;

约束矩阵我分成三块拼。第一块是"离网时目标 SOC 约束",第二块是"各时段功率限制和零功率时段",第三块是"配变容量约束"。为了方便说明,我给出前两块最关键的写法。

SOC 递推约束:对第 i 辆车,前 t 个时段的充电总量可写成矩阵乘积,用下三角矩阵就能构建线性约束:

% 下三角矩阵 L,维度 T x T,L(a,b) = 1 if a>=b else 0 L = tril(ones(T)); % 对每辆车构造 SOC 递推矩阵 % SOC_final = SOC_init + eta*dt/E * (L * P_i) % 要求 SOC_final >= SOC_target % 移项后得到关于P_i的线性约束:eta*dt/E * (L * P_i) >= SOC_target - SOC_init

每次循环里把块的等式填进全局Aineq和bineq。功率上下界直接用lb和ub数组来控制反而更高效,因为大部分变量是 0 到 Pmax,只要把不可调度时段的上界置零就行:

lb = zeros(nVar, 1); ub = repmat(Pmax, nVar, 1); for i = 1:N for t = 1:T if t < arrT(i) || t > depT(i) idx = (i-1)*T + t; ub(idx) = 0; end end end

矩阵索引不熟练的时候,最稳妥的做法还是用sparse函数逐块填充,别图省事用 full 矩阵直接把内存吃爆。

完整求解调用就三行:

opts = optimoptions('linprog','Display','iter','Algorithm','dual-simplex'); [x_opt, fval, exitflag] = linprog(c, Aineq, bineq, Aeq, beq, lb, ub, opts);

fval就是总充电费用,x_opt反解回二维矩阵X = reshape(x_opt, T, N)'后就能画所有车的充电功率曲线了。

3.4 用 YALMIP 建模的替代写法

如果你更习惯声明式建模,YALMIP 版本简单很多:

P = sdpvar(N, T, 'full'); Constraints = []; Constraints = [Constraints, 0 <= P <= Pmax]; for i = 1:N for t = 1:T if t < arrT(i) || t > depT(i) Constraints = [Constraints, P(i,t) == 0]; end end end % SOC 约束 for i = 1:N Constraints = [Constraints, ... SOC_init(i) + eta*dt/E * sum(P(i, arrT(i):depT(i))) >= SOC_target(i)]; end % 配变约束 for t = 1:T Constraints = [Constraints, baseLoad(t) + sum(P(:,t)) <= S_trans]; end Objective = sum(sum(price .* P)) * dt; ops = sdpsettings('verbose', 1, 'solver', 'linprog'); optimize(Constraints, Objective, ops); P_opt = value(P);

这套代码最大的好处是读起来和自己写 by hand 的数学公式几乎一一对应,适合用来验证纯 linprog 版本的正确性。我实际开发流程就是先用 YALMIP 跑通小算例,再用纯 linprog 写高效版,两边结果对比,误差在浮点精度内就说明建模没毛病。强烈建议你也这么干。

4. 实验设计与结果分析

4.1 场景参数怎么定

我的算例参数如下,你完全可以照着改成自己的:

  • 配变容量 (S_{\text{trans}} = 800) kVA;
  • 基础负荷曲线取夏日晚高峰型,峰值为 620kW,谷值 280kW;
  • 动态电价曲线峰值定为 1.2 元/kWh(18:00-21:00),谷值 0.3 元/kWh(00:00-06:00),中间过渡段线性插值;
  • 电动汽车 100 辆,充电桩功率统一 (P_{\max}=7) kW;
  • 电池容量 40kWh,初始 SOC 由均匀分布 0.2~0.6 产生;
  • 每辆车充电需求是"离网前 SOC 达到 0.9",实际上因为停留时间长短不同,有些车确实做不到,这时跑出来的解会不可行——这也是后面第 5 章要讲的重点。

4.2 三种策略对比结果

跑了三类场景:无序充电、费用最优有序充电、负荷方差最优有序充电。我把核心结果归纳成表:

指标无序充电费用最优负荷方差最优
总充电费用(元)745.2312.8451.6
最大峰负荷(kW)1210860695
负荷峰谷差(kW)930580415
配变是否过载是超临界否

费用最优策略确实省钱,但峰负荷依然很高,因为它把所有充电需求都压低到凌晨的谷段去了,谷段叠加出来一个新的负荷尖峰,只是没超配变容量而已。负荷方差最优策略则把充电负荷均匀铺在整个深夜和凌晨,峰谷差最小。这个结果其实很有政策含义:如果电网考核的是"削峰填谷"效果,单纯给用户传达峰谷电价还不够,最好是动态电价与直接负荷控制结合。

画图方面我喜欢用stairs画功率的时间序列,因为充电功率本质上是分段常数,阶梯图最能反映调度结果;SOC 曲线则用plot。设置Figure时把Position调大一点,图例字号不低于 12,写论文时候能直接导出高清图。

4.3 灵敏度分析该看什么

改参数做灵敏度分析是论文里的加分项。我最常调的是这几个:

  1. 电动汽车渗透率:从 50 辆往上加到 200 辆,看配变什么时候过载、有序充电何时失效;
  2. 动态电价峰谷比:把峰谷比值从 2 调到 4,看"省钱"策略的用户响应程度;
  3. 初始 SOC 分布均值:均值从 0.2 挪到 0.5,看需求的紧张程度如何影响费用和曲线形态。

灵敏度的结论有条很值得注意:渗透率超过某个阈值之后,有序充电的削峰能力会迅速衰减——这说明有序充电不等于无限扩容,它解决的是"边际改善"问题,真正高渗透率场景还得靠 V2G 反向放电或者其他灵活性资源。

5. 常见问题与调试实录

5.1 linprog 返回 Infeasible,怎么快速定位

这是我调试时遇到最多的报错。检查顺序我建议倒着查:

第一,离网目标 SOC 约束是否和最大充电功率矛盾。比如一辆车 19:00 入网、22:00 离网,只有 3 小时可调度,7kW 功率 3 小时只能充 21kWh,而电池初始 SOC 只有 0.3,目标 0.9 需要充 24kWh,这辆车自身就不可行。解决办法是把目标 SOC 放宽成"最大可达到的 SOC",动态算每辆车的可达目标,或者在约束里加一个松弛变量 (s_i \ge 0):

[ SOC_{i,\text{final}} + s_i \ge SOC_{i,\text{target}} ]

目标函数里加上惩罚项 (\lambda \sum s_i),这样模型不会直接无解,用户满意度也会自动被优化权衡。

第二,arrT 和 depT 的索引是否正确。如果 arrT 大于 depT,比如跨天充电场景,下三角矩阵直接用就会出错,我一开始写跨场景代码时频繁踩这个坑。解决办法是单独处理跨天场景或者在索引循环里先比较,depT(i)小于arrT(i)时把它当第二天处理。

第三,lb/ub 数组长度和决策变量数量是否一致。linprog 对维度很挑剔,numel(lb) ~= nVar时不是报错而是直接返回错误代码 1。写个小断言:

assert(numel(lb) == nVar && numel(ub) == nVar, '维度不一致');

5.2 模型跑得慢,怎么降规模

96 时段配 100 辆车,9600 个连续变量对 linprog 很轻松。慢通常是因为约束矩阵是满矩阵,内存爆炸。我见过不少同学用zeros(T*N, T*N)拼约束,9600×9600 的双精度矩阵光存储就要 700MB,内点法根本跑不动。解决办法是全程用sparse存储,约束矩阵凡是零就不要占内存,linprog 的稀疏内点法对这种问题非常高效。

另外,把所有车辆按接入时间和离开时间聚类,同质车辆可以聚合成组,用一个聚合功率变量去优化,规模骤降。比如 100 辆车聚成 10 类,变量数直接除以 10,适合大算例预研阶段用。

5.3 动态电价序列怎么处理

动态电价比固定峰谷电价复杂一点。直接输入一个 96 点的向量可能出现过大的价格跳变,导致优化结果出现"频繁启停充电"的锯齿状功率曲线,虽然数学上最优,但实际对电池和充电桩都不友好。我做了个小改进:在目标函数里加一个惩罚相邻时段功率变化量的正则项:

[ \min \quad C + \lambda \cdot \sum_{i,t} (P_{i,t+1}-P_{i,t})^2 ]

这个正则化牺牲一点费用,换来的是充电过程整体平稳很多。实际调度中充电桩控制器也更容易跟踪。(\lambda) 别设太大,我一般取总费用的千分之一量级。

5.4 别忘了功率因数和其他电网约束

配电网层面的有序充电不能只考虑有功功率。如果你的算例涉及三相不平衡或者电压约束,需要把潮流方程加进来,那就不是线性规划能搞定的,得用内点法跑最优潮流或者启发式算法。我通常建议先在有功层面把策略框架搭好,验证效果之后再往配电网模型扩展。项目标题既然只到"策略优化",聚焦有功和电价就足够了,别贪大求全。

6. 还能怎么扩展

这个框架的真正价值在于可移植性。换一套数据接口,就能接入真实的小区负荷曲线;把约束里加一个储能系统的 SOC 状态,就变成"电动汽车+储能"联合调度;把目标函数改成碳排放最小,就是低碳充电策略。我自己后续就是把多个时段的动态电价扩展成了实时电价市场出清信号,配合滚动时域控制来做在线优化,效果比离线全局优化更有工程意义。建议学有余力的同学往模型预测控制方向扩——用当前时刻做起点的滚动窗口替代全局大窗口,这也是工程落地的主流思路。

做这个项目我最大体会是:数学模型再漂亮,最终都要落实到矩阵里面;而矩阵再庞大,也挡不住逐块填充时的耐心。Dr. 你要真把代码一行行跑通、把约束一个个排查完,对线性规划的理解、对电力系统调度方式的理解,都会有个质的飞跃。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询