1. 无序充电的账与静态分时电价的局限
1.1 无序充电的账其实很容易算
电动汽车有序充放电调度,算是我这段时间投入精力最多的一块工作。从最初的离线日调度,到最后落地成基于动态分时电价的实时滚动优化系统,中间推翻了不少看似“理所当然”的设计。先算一笔最朴素的账:一台家用交流桩7kW,下班后18点到22点集中充电,1000辆车同时段开工,相当于凭空给晚间负荷峰值叠加了7MW。这个数字对配电网意味着什么?一台10kV变压器的典型容量在630kVA到1000kVA左右,7MW已经接近好几条馈线的总负荷。换成120kW快充桩,几十辆车同时充电就能让一片区域的变压器直接过载。电网不是按平均负荷设计的,是按峰值负荷设计的,无序充电真正昂贵的地方在于:为了满足每天只持续一两个小时的尖峰,整条线路设备都得按这个峰值扩容,而扩容成本对应的资产利用率却低得可怜。
这个问题的本质,是电动汽车作为柔性负荷没有被“引导”起来。车辆接入电网的时间窗口很长,真正需要的充电时间往往只有三四个小时,中间有大量的调度空间。但如果没有价格信号或控制信号,每辆车都会选择“接入即充”,把所有灵活性白白浪费掉。这也是为什么这两年从学术界到电网公司都在推“有序充电”和“车网互动”——不是技术上的强制,而是经济上、控制上必须把这块弹性利用起来。
1.2 静态分时电价为什么不够用
静态分时电价是目前最普及的需求响应手段,居民侧峰谷电价就是典型代表:白天一个价、晚上一个价,逻辑上鼓励用户把充电挪到谷段。可实际用下来会发现三个问题。
第一,价格曲线是季度或月度级别的固定值,跟当天电网的真实运行状态没有直接关联。夏天晚上空调负荷本来就高,低谷时段可能根本不“低”;碰上光伏大发的中午,真正的低谷反而出现在午间,但静态电价完全感知不到这种日间波动。第二,所有用户都知道谷段便宜,于是谷段刚开始的半小时会出现“充电拥堵”——大量车辆同时启动,形成一个削不掉的二次尖峰,业内叫负荷反弹。这种反弹有时候比原来的晚高峰还难看。第三,静态电价没有给V2G(车辆到电网)反向放电留出合理的经济信号。峰段和谷段只差几毛钱,用户没有任何动力把车里的电放回电网,因为放电带来的电池损耗可能比赚到的差价还高。
静态电价本质上是一个“开环”的信号:它发布之后就再也不看电网里实际发生了什么。指望靠一个固定价格曲线去应对每天都不一样的负荷曲线、发电曲线,精度远远不够。
1.3 动态分时电价与实时优化之间的连带关系
动态分时电价把价格变成了一个随时间快速变化的信号,可以每15分钟或每小时更新一次,直接反映当前电网的供需紧张程度。它的本质是把“电网需要什么”翻译成“用户愿意做什么”。电网侧负荷吃紧时价格抬升,可再生能源大发时价格压低,用户侧调度系统拿到这个实时更新的价格之后,必须能快速重新决策:是继续充、暂停、还是反手放一点电。
这就引出了实时优化调度的必要性。如果电价24小时都不变,离线排一个固定充电计划就够了;但价格一变,昨天的“最优计划”今天就变成次优甚至错误。所以动态分时电价和实时优化是一对捆绑在一起的组合:前者提供持续更新的经济信号,后者负责把这个信号高效地翻译成每辆车的充放电动作。在我做的系统里,滚动时域优化是承载实时调度的核心框架,下一节先讲清楚优化模型本身是怎么设计的。
2. 优化模型怎么建:变量、目标与约束的逐项拆解
2.1 时域离散化与决策变量的选择
建模第一步是把时间切成小段。以24小时为周期、15分钟一个时间槽,一天就有96个决策点。之所以选15分钟,是因为动态分时电价在电网侧的更新频率通常也在5到15分钟这个量级,同时这个粒度能让混合整数线性规划(MILP)问题的规模控制在可解范围内。决策变量定义成每个EV在每个时间槽的充电功率P_ch(i,t)和放电功率P_dis(i,t)。
另外必须引入一个0-1二进制变量u(i,t),表示“该车在该时间槽是否处于充电状态”。这个变量的作用有两个:一是防止同一辆车既充电又放电,这在物理上不可能,但数学模型里如果只写功率边界,优化器很可能会玩“先充后放再充”的套利把戏,制造出物理上无意义的环流;二是让功率边界约束变成带开关的形式,而不是单纯的不等式。二进制变量也是后面求解时间的主要来源,后面会专门讲。
2.2 目标函数:成本、削峰、电池衰减怎么权衡
目标函数的常见选择有三个,实际项目中通常组合使用。最基础的是用户侧总成本最小化:充电电费减去放电收益,再加上电池衰减惩罚。写成公式就是:
min Σ_i Σ_t [ p(t)·P_ch(i,t)·Δt - p(t)·P_dis(i,t)·Δt·η_dis + c_deg·P_dis(i,t)·Δt ]
其中p(t)是动态电价,η_dis是放电效率,c_deg是每放出1kWh电对应的电池损耗成本,一般按电池更换成本除以全生命周期可放电总量来估算。如果站方或电网侧更关心负荷曲线形态,可以在目标里再加一个负荷方差项,让所有EV的总功率叠加基础负荷之后尽量平坦。方程形如 λ·Σ_t (P_base(t) + Σ_i (P_ch(i,t) - P_dis(i,t)) - P_avg)²。
需要注意,加上方差项之后问题变成带二次项的混合整数规划(MIQP),求解时间会明显上升。工程上我更喜欢用“削峰惩罚项”来近似——只对超过某个阈值的总功率加线性惩罚,比如总功率超过配变容量80%的部分按高价计入目标。这样做既保留了线性结构,求解快,效果也够用。
2.3 约束条件:SOC递推、功率边界与充放电互斥
约束条件这层最容易出问题。核心是SOC递推方程:
SOC(i,t+1) = SOC(i,t) + [η_ch·P_ch(i,t) - P_dis(i,t)/η_dis]·Δt / B_i
B_i是电池容量,单位kWh。这里特别要注意单位统一:功率是kW,时间是小时,Δt按小时折算(15分钟就是0.25),乘出来的能量才是kWh,否则SOC计算结果会差一个倍率,而且这种错误往往很难一眼看出来。
SOC要限制在安全窗口内,比如10%到90%,很多车厂实际允许的窗口更窄。同时每辆车在离开时间点的SOC必须达到用户设定的目标值——这个约束是用户满意度的底线,不能省。功率边界写成 0 ≤ P_ch(i,t) ≤ P_ch_max(i)·u(i,t),0 ≤ P_dis(i,t) ≤ P_dis_max(i)·(1-u(i,t)),u(i,t)∈{0,1},充放电互斥就通过这个二进制变量写进去了。
还有一个很多初稿会漏掉的约束:车辆在未接入充电桩的时间段内,充放电功率必须强制为0,SOC保持不变。不能因为模型里“方便”就把到达前和离开后的时间槽也放开。这个约束漏掉之后,优化结果里会出现“车还没到就已经充满”的荒谬现象,第一次跑出这种结果时我还以为是求解器出了问题,排查半天才发现是自己在建模时偷懒了。
2.4 动态电价是如何进入优化模型的
动态电价在模型里是一个随时间变化的外生输入序列p(t)。严格来说,实时系统里电价是“当前已知、未来预测”的,所以模型里用的其实是一段价格预测向量p_forecast,每个滚动周期都要刷新。这也是动态分时电价与静态电价在数学建模上的本质差别:静态电价是常数向量,一次定义全程不变;动态电价要求每个重优化周期都重新读入价格向量。
工程实现上,p_forecast可以来自日前市场出清价、负荷预测加电价映射函数,或者干脆是电网侧直接下发的实时价格信号。无论数据来源是什么,模型侧只关心它是不是一段长度为预测时域H的向量。这里插一句经验:价格预测精度直接影响调度质量,但也不需要追求绝对精确,滚动优化自带的反馈纠错能力能吸收很大一部分预测误差,这点后面灵敏度分析里会验证。
3. 实时调度为什么是滚动时域优化:算法框架与触发逻辑
3.1 离线日调度的三个硬伤
我一开始的做法是提前一天拿到明天的完整电价曲线,假设所有EV的到达、离开、初始SOC全部已知,一次性解出24小时的充放电计划。仿真结果确实漂亮,成本最优、曲线平滑,但放到现实里根本没法用。三个硬伤摆在面前。
第一,用户到达时间是随机的。前一秒还没到的车,后一秒突然出现在车位上,离线计划完全没有考虑这种扰动。第二,动态电价本身是预测值,实际执行时价格可能已经变了,按昨天的预测价格优化出的“最优”,在今天可能连次优都算不上。第三,充电过程受实际功率波动、电池温度、SOC估计误差影响,离线计划一旦开始执行,误差就会逐步累积,一步错步步错。
3.2 滚动时域优化的基本逻辑:先解窗口,只动一步
滚动时域控制,也叫模型预测控制(MPC),思路很直白:不要一次把24小时全部定死,而是每到一个决策时刻k,只把从k到k+H这个预测窗口内的优化问题解出来,然后只执行第一个时间槽的决策;时间推进一个槽之后,收集最新状态(当前SOC、新到达车辆、最新电价预测),重新优化。
这个“走走停停”的结构天然适合不确定性环境。每一次重优化都把新信息灌进模型,之前预测偏差带来的后果被持续纠正。用一句话概括:离线优化赌的是预测全对,滚动优化赌的是“错了也能很快改回来”。我在Matlab里跑对比实验时,同样的价格预测误差下,滚动优化的总成本只比完美信息下的理论最优高几个百分点,而离线开环优化在这个误差下可能直接崩溃。
3.3 重优化触发机制与预测输入
重优化触发有两种策略。时间触发最简单,每15分钟到点就重算一次,实现容易,行为可预测。事件触发则更贴合实际:检测到新EV接入、某辆车提前离开、电价发生非预期跳变等关键事件时立即插入一次重优化。工程上通常把两者结合,定时扫描作为底线,关键事件作为插入触发器。
预测输入方面,电价预测是最重要的一块。最简单的做法是零阶保持:假设未来若干槽的电价延续最新已知价格。这种做法在算法验证阶段完全够用,实际部署时再接入更复杂的预测模型。EV到达情况则可以用历史到达率分布,或者把“当前在站车辆+在途车辆预估”作为输入。这里有个取舍:预测做细致了肯定好,但预测模块本身也会引入新的误差源,初期版本建议先用简单的假设把主链路跑通。
3.4 MILP求解规模与实时性预算
每个滚动窗口内要解一个混合整数线性规划。不考虑车辆差异带来的稀疏性,决策变量总数大约是“车辆数×窗口长度×2个功率变量”,二进制变量是“车辆数×窗口长度”。假设50辆车、预测窗口32个槽(8小时),就是50×32×2=3200个连续变量和1600个二进制变量,约束也在几千条量级。
这个规模用主流商用MILP求解器(如Gurobi、CPLEX这类)配合默认参数,通常几秒内能拿到接近最优的解,完全满足15分钟一个控制周期的要求。但如果车辆数量涨到几百辆、窗口又拉满全天,求解时间会指数级恶化,这时候就得考虑用启发式算法,或者把二进制变量松弛掉,在最优性和实时性之间做取舍。这也是为什么很多实际系统最后做成两级调度:上层在慢时间尺度做群体优化,下层在快时间尺度做单桩跟踪控制,把一个大问题拆成两个好解的问题。
4. Matlab代码实现:从数据准备到滚动求解循环
4.1 代码模块划分与参数封装
Matlab代码我按四个模块组织:参数定义、数据生成、优化求解、结果分析。参数定义放EV群参数,包括电池容量B、最大充放电功率、初始SOC、目标SOC、接入和离开时间,以及电价曲线、预测窗口长度H、控制周期Δt。数据生成模块负责产生动态电价序列和EV到离时间,设计成支持从外部文件读取真实数据,方便替换。优化求解模块用YALMIP建模,封装成一个独立函数,输入是当前系统状态和价格预测,输出是下一个控制周期的决策。结果分析模块画SOC轨迹、功率曲线、费用对比图。
这种模块化看起来多花了一点时间,但调试阶段能省大量精力。我一开始把所有代码塞在一个脚本里,改一个参数就要从头跑一遍,后来拆开之后,每个模块可以单独验证,排查问题的速度明显提升。
4.2 YALMIP建模的核心代码细节
我用的建模工具是YALMIP工具箱,它在Matlab里最大的好处是建模语言接近数学公式,调试起来直观。核心代码结构大致如下:
% ===== 单次滚动窗口内的优化求解 ===== % 输入: SOC_now(M*1), p_forecast(H*1), active_flag(M*H), params % 输出: P_ch_opt(M*H), P_dis_opt(M*H) M = params.M; % 车辆总数 H = params.H; % 预测窗口长度 dt = params.dt; % 单时间槽时长(小时) B = params.battery; % 电池容量 kWh % 1. 申明优化变量 P_ch = sdpvar(M, H, 'full'); P_dis = sdpvar(M, H, 'full'); u = binvar(M, H, 'full'); SOC = sdpvar(M, H+1, 'full'); C = []; C = [C, SOC(:,1) == SOC_now]; % 当前SOC是实测值 % 2. 逐车逐槽构建约束 for i = 1:M for t = 1:H if active_flag(i,t) == 1 % 该车在该槽处于在网状态 C = [C, 0 <= P_ch(i,t) <= P_ch_max(i) * u(i,t)]; C = [C, 0 <= P_dis(i,t) <= P_dis_max(i) * (1-u(i,t))]; C = [C, SOC(i,t+1) == SOC(i,t) ... + (eta_ch*P_ch(i,t) - P_dis(i,t)/eta_dis)*dt/B(i)]; else C = [C, P_ch(i,t) == 0, P_dis(i,t) == 0, u(i,t) == 0]; C = [C, SOC(i,t+1) == SOC(i,t)]; % 离线时段SOC不变 end end C = [C, SOC(i,t_leave_idx(i)) >= SOC_target(i)]; % 离网电量达标 end % 3. 目标函数:充电电费 - 放电收益 + 电池衰减惩罚 obj = sum(sum(p_forecast .* P_ch * dt)) ... - sum(sum(p_forecast .* P_dis * dt * eta_dis)) ... + c_deg * sum(sum(P_dis * dt)); % 4. 求解 ops = sdpsettings('solver', solver_name, 'verbose', 0, ... 'mipgap', 0.01); diagnostics = optimize(C, obj, ops); P_ch_opt = value(P_ch); P_dis_opt = value(P_dis);几个细节值得强调。第一,SOC、P_ch、P_dis、u都是优化变量,SOC通过等式约束建立递推关系,初始值SOC_now是当前实测状态,这是滚动时域里连接上一轮和本轮的关键接口。第二,“不在网时段强制断电”的约束别偷懒省略,否则会出现前面说的“未到先充满”问题。第三,SOC目标约束里t_leave_idx(i)是车辆离开时刻在窗口内的索引,如果离开时间超出窗口,要单独做截断处理,否则索引越界报错。
4.3 滚动求解循环怎么写才稳定
滚动求解循环是整个系统的主干,结构可以概括为:初始化系统状态,然后循环执行“收集最新数据、构建当前窗口模型、求解、执行第一个控制动作、推进系统状态”五个步骤。伪代码大致如下:
for k = 1:N_total % 1. 获取当前全局时刻k的状态和最新价格预测 SOC_measured = get_current_soc(ev_status, k); p_forecast = get_price_forecast(k, k+H-1); active_flag = get_active_matrix(ev_status, k, H); % 2. 在窗口内构建并求解优化问题(复用4.2的建模逻辑) [P_ch_opt, P_dis_opt] = solve_window(... SOC_measured, p_forecast, active_flag, params); % 3. 只执行第一个控制周期的动作 apply_control(P_ch_opt(:,1), P_dis_opt(:,1), k); % 4. 利用仿真模型推进状态;真实系统则等待测量反馈 ev_status = simulate_step(ev_status, ... P_ch_opt(:,1), P_dis_opt(:,1), k); end这里每次调用求解时,窗口右端的价格预测必须按最新信息刷新,不能复用上一轮的旧数据,否则滚动优化的反馈纠错能力就丧失了一半。求解速度方面,如果单次求解超过十秒,就该检查窗口长度、变量预处理和求解器参数。一个实用的加速手段是热启动:把上一轮解作为本次迭代的初始可行解传给求解器,商用求解器一般都有对应的MIP start接口,实测能省不少时间。
4.4 结果可视化与费用统计口径
结果分析一般画三张图。第一张是所有EV的总功率曲线叠加电价曲线,可以直观看出充电负荷是否从高价段挪走、放电是否集中在电价尖峰段。第二张是典型EV的SOC轨迹,验证充放电过程是否始终满足约束。第三张是累积费用对比,把无序充电、静态分时、动态分时三种策略的曲线放在一起,这张图基本能支撑小论文或项目结题需要的核心结论。
费用统计有一个细节要特别提醒:目标函数里放电收益是按同一电价计算的负费用,但实际结算中双向电价规则可能不同,放电收益和充电电费往往不是同一个单价。仿真里怎么假设都行,但输出结果时要写清楚假设边界,否则评审或同事追问起来容易说不清楚。
5. 仿真算例对比与灵敏度分析
5.1 算例场景与参数设置
用一个简单算例说明效果,方便复现。假设某园区充电站有50台7kW交流桩,车辆随机到达,接入时间分布在下4点到晚10点,离开时间集中在早8点到9点。每台车电池容量60kWh,初始SOC在20%到50%之间,目标SOC设为90%,最大充电功率7kW,放电功率暂设为3.5kW(考虑实际V2G桩的功率等级),电池衰减成本按每kWh放电0.15元计入。
电价方面,静态分时用典型两段制:谷段0.35元/kWh(23:00到7:00),峰段1.1元/kWh。动态分时则在静态谷峰基础上叠加随机扰动,模拟电网供需波动,峰段最高到1.5元/kWh,谷段最低0.2元/kWh,扰动每15分钟更新一次。这个设置不算极端,但足以看出动态信号对调度结果的方向性影响。
5.2 三种策略对比:成本、峰值、峰谷差
三种策略分别是:无序充电(接入即充到目标电量)、静态分时电价下的启发式延迟充电(只延迟到谷段开始)、动态分时电价下的滚动时域优化。仿真一天的典型结果如下表:
| 策略 | 单日平均充电成本(元/车) | 峰值总功率(kW) | 峰谷差率 |
|---|---|---|---|
| 无序充电 | 38.6 | 315 | 78% |
| 静态分时+启发式延迟 | 24.2 | 285 | 71% |
| 动态分时+滚动优化 | 18.8 | 220 | 52% |
数据是示例值,不同参数下绝对值会有变化,但趋势很稳定:滚动优化比单纯延迟充电的成本再省两成左右,同时峰值负荷明显下降。原因不难理解:静态分时只看“谷段”一个区间,所有车都挤在23点开充,形成了新的负荷尖峰;动态分时加实时优化则会把充电功率在谷段内部继续摊平,并且在下午电价尖峰时让部分车辆小功率放电,既降了成本又削了峰。这个“削峰不靠口号”的效果,正是动态电价信号加优化算法的价值所在。
5.3 灵敏度分析:哪些参数最影响结论
我做了几组灵敏度测试,结论可以作为工程判断的参考。
第一,目标SOC从80%升到95%,成本上升约15%,但峰值功率反而略降。原因是要充得越满,可调度的自由度越小,系统被迫把充电窗口拉长摊平。这说明用户对电量的要求是影响削峰效果的重要约束,不能只看电价。
第二,电池衰减成本c_deg从0.05元/kWh升到0.3元/kWh,V2G放电量从每天约40kWh每车降到几乎为0。这说明衰减成本是V2G场景是否具有经济性的决定性因素。如果手头没有实测衰减数据,宁可把c_deg设高一点,避免仿真里出现“疯狂放电”这种不符合用户直觉的失真结果。
第三,价格预测误差从5%增大到30%,滚动优化相对静态启发式策略的收益空间缩小一半,但优势依然存在。这正是滚动时域结构带来的容错性。换成开环离线优化,同样的预测误差下收益可能直接消失。这个结论让我放心地把文章开头说的“动态电价与实时优化是一对组合”这句话坐实了。
6. 工程化落地的坑与几点经验
6.1 求解时间膨胀与数值病态的处理
求解时间是我踩的第一个坑。第一次跑全规模问题时,单次求解要两分多钟,一个15分钟控制周期根本跑不完。排查后发现两个原因。一是窗口末尾有大量不必要的二进制变量,很多车在窗口后半段已经离开,但循环里没有排除,白白增加了变量维度。二是约束里出现了很多数值很小的系数,比如0.0001量级,导致求解器数值条件很差,单纯收缩迭代次数暴涨。
解决办法很直接:建模前把每辆车的可调度窗口预处理出来,只对在网时段建立变量和约束;同时对能量类系数做量纲归一到kWh级别,避免极小数出现。这两个改动之后,求解时间从两分多钟降到了三四秒,几乎一个数量级的提升。
6.2 预测误差带来的连锁反应
动态电价预测误差是实时调度绕不开的话题。实际测试中我给价格预测加白噪声,发现两个有意思的现象。
一是有预测误差时,优化器倾向于“少动作”。因为动作是基于错误信息的,最保守的做法是尽量维持当前状态,这其实是MPC框架下鲁棒性的一种体现。二是当价格预测在某个槽位严重失真时,比如预测1.5元实际只有0.6元,系统会做出“在这个槽位尽量放电”的错误决策;如果恰好此时电池压在SOC下限附近,下一轮就失去了调度弹性,后面几小时都缓不过来。
缓解办法是在目标函数里加一个相邻时间槽功率变化惩罚项,抑制过于剧烈的开关动作。实测下来,这个惩罚项对预测误差的鲁棒性提升非常明显,代价是理论最优性损失不到1%,完全值得。
6.3 从仿真桩到真实充电桩的工程落差
最后说几个代码之外的问题,反而是在实际部署中更关键的。
第一,充电桩的控制接口不是想多快就多快。很多桩只支持按设定功率持续执行,修改指令前有握手和确认延时,控制周期不是越短越好,必须和硬件能力对齐。第二,车端SOC的估计和通信往往是整车厂的白盒接口,实测和BMS上报可能有3%到5%的偏差。SOC递推约束要留裕量,别卡着90%的边界去算,否则真实场景下可能还没到目标就提前保护性停充。第三,多车辆同时响应价格时,“均衡”是相对整个系统而言的,单个用户可能发现自己的账单并不比邻居便宜多少。做产品化时要把用户公平性也纳入模型,否则推广阶段会收到大量抱怨。
如果让我给准备做这套系统的开发者一个最直接的建议:先把“离线版能跑通、结果合理”这一步做到位,再加滚动时域和动态电价,不要一上来就把实时性、随机性、多目标全搅在一起。模型每加一层复杂度,排查问题的难度是成倍增长的,不是简单叠加。先把骨架立稳,再慢慢长肉——这套系统的价值,恰恰在于每一个环节都能独立验证、独立优化。