2. 峰谷分时电价:为什么它是引导充电行为的“指挥棒”
4. 常见问题与排查技巧实录
纯电动车代替燃油车是大方向,但充电这件事,一直没被说透。
一台家用电动车,慢充功率7kW,快充功率可能冲到60kW以上。如果车主都集中在晚上七八点回家插枪,一个小区的配电变压器瞬间就被“击穿”了。电网侧的峰谷差越来越大,变压器寿命缩短,甚至局部停电。这不是科幻片,是很多城市已经出现的现实。与此同时,车主自己的体验也不好——晚高峰充电,电价最贵,充满一台60度电的车,比凌晨充电多花三四十块。
我从2022年开始陆陆续续做电动汽车充电负荷优化相关的研究,中间换过好几版方案,最后稳定在“峰谷分时电价”加“NSGA-II多目标遗传算法”这套组合上。今天把整个项目的思路、模型搭建和Matlab实现完整拆开讲一遍,含代码逻辑、参数设置和我在实操中踩过的坑。
1. 内容整体设计与思路拆解
1.1 核心需求解析:充电负荷优化到底在优化什么
先问一个基本问题:所谓“电动汽车充电负荷优化”,优化的对象是什么?很多人第一反应是“让充电速度更快”,其实不对。真实场景里,充电负荷优化通常包含三个层面:
第一,用户侧的成本优化。同样是充满一辆车,选择在峰段充、平段充还是谷段充,电费差距是实打实的。对车主来说,省钱是最直接的驱动力。
第二,电网侧的负荷曲线优化。大量电动汽车同时接入,会导致局部配电网负荷陡增。优化目标之一是削峰填谷,让充电负荷尽量往负荷低谷期转移,缓解变压器压力。
第三,充电设施利用率的优化。对充电站运营商来说,希望充电桩尽可能连续工作、不要长时间闲置,同时避免集中排队。
这三个目标存在天然冲突——用户想省钱,电网想平稳,运营商想高效。单目标优化根本没法平衡,所以必须用多目标优化的思路。这就是这个项目选择NSGA-II的底层逻辑:它不需要把多个目标人为加权成一个,而是直接生成一组帕累托最优解,让决策者根据实际偏好做二次选择。
1.2 为什么选NSGA-II:多目标进化算法的时代选择
NSGA-II全称是Nondominated Sorting Genetic Algorithm II,即带精英策略的非支配排序遗传算法,2002年由Deb等人提出。即使在今天,它仍然是处理多目标优化问题最稳健的算法之一,尤其在工程研究领域地位稳固。
它解决的核心问题有三个:
第一个是非支配排序。把所有解按“支配关系”分成不同层级,第一层级的解就是当前最优的帕累托前端。举个例子,方案A充电费用比方案B低,同时电网负荷波动也比方案B小,那A支配B,B可以直接淘汰。如果A在某些指标上好、在某些指标上差,它们就互不支配,一起进入候选集。
第二个是拥挤度距离。同一层级的解之间,通过计算相邻解的密度来保持多样性。这个机制的目的是防止所有解都挤在某个局部区域,让解集在目标空间均匀铺开。
第三个是精英保留策略。父代和子代合并后一起筛选,保证优秀个体不会在迭代过程中丢失。这个特性让NSGA-II比第一代NSGA收敛更快、稳定性更好。
我用遗传算法做过多目标优化,也试过SPEA2、MOEA/D这些算法,相比之下NSGA-II的优势是“皮实”——参数不敏感,对目标函数的形态要求低,就算目标函数存在非线性、不连续甚至噪声,它也能稳定输出一组分布合理的备选解。如果你刚入门多目标优化,NSGA-II是最不容易翻车的起点。
1.3 峰谷分时电价的作用机制:价格信号如何“指挥”充电行为
这个方案里,峰谷分时电价不是简单的计价规则,它承担着“调度信号”的角色。核心逻辑是:通过不同时段电价的差异,引导车主主动把充电行为从高峰时段平移到低谷时段。
国内普遍执行的分时电价大概可以分成三类时段:峰段(通常10:00-15:00、18:00-21:00)、平段(7:00-10:00、15:00-18:00、21:00-23:00)、谷段(23:00-次日7:00)。峰谷价差在三四倍左右,具体数值因地区而异。
在仿真模型里,这个信号会作用于目标函数,具体体现在充电费用计算中。用户充电费用最低的目标,本质上就是在寻找“什么时候充、充多久”的最优解,而电价就是这个寻优过程中最重要的加权因子。
需要特别说明的是,峰谷分时电价不是适合所有场景的完美方案。有的地区峰谷价差不够大,用户根本没有动力调整充电时间;有的地区谷段电价过低,可能反而导致谷段负荷骤增。所以这个项目做的是“引导下的优化”,而不是“强制调度”——在电价信号的激励下,让用户自愿、主动地配合电网调节。
思考一下。
2. 核心细节解析与实操要点
2.1 多目标模型的数学表达
先把目标函数完整列出来。
目标函数一:用户充电费用最小化。
这里要注意几个细节:充电功率是分段变化的,不是恒定值。锂电池在低电量时充电功率高,电量超过80%后会逐步降流。但为了简化模型,我在这个项目里采用了恒功率假设——这是前期研究比较标准的简化方式,等模型跑通了再引入更精细的电池充电曲线。另外,起始充电时间是一个决策变量,它直接决定用户落在哪个电价时段。
目标函数二:电网负荷波动最小化。
这个目标用负荷方差来表示。具体做法是把一天分成24个时段(也可以分成48个或96个时段,精度越高计算量越大,需要权衡),统计叠加电动汽车充电负荷后的总负荷曲线,计算每个时段负荷与平均负荷的偏差平方和。方差越小,说明电网负荷曲线越平稳。
目标函数三:充电站资源利用率最大化。
这个目标用“有效充电时长占比”来衡量。一天有24小时,充电桩如果能持续高效工作,利用率就高。但这里有个矛盾:如果所有车都挤在谷段充电,电网是平了,但充电站的峰谷压力巨大——谷段充电桩全满,车主排队;峰段桩全空,资源浪费。所以这个目标与目标二存在潜在冲突,正好让NSGA-II发挥作用。
约束条件方面,本项目设置了四类:
- 充电电量约束:单台车充电需求有上下限,不能充不满也不能过充
- 充电功率约束:单台车充电功率不超过充电桩额定功率
- 变压器容量约束:小区或区域总负荷不超过配变容量
- 充电时间约束:车主设置的充电截止时间必须满足,不能无限制延迟
稍等,这里需要补充。
关于决策变量,实际研究中常见有两种设计方式:一种是直接设置每辆车的起始充电时间,另一种是为每辆车设置“是否充电”的二进制编码再配合起始时间。我在项目里选的是第一种——连续变量的实数编码,因为NSGA-II的模拟二进制交叉和多项式变异算子本身就适合实数编码处理,收敛效率比二进制编码高不少。加上一个“裕量”处理,在充电截止时间前留出缓冲,避免优化结果在现实中根本没法执行。
2.2 流程设计:从初始种群到帕累托前端
整体流程概述如下:
第一步,初始化种群。种群规模我设置了100,每辆车独立随机生成起始充电时间,范围从下午5点到次日早上8点。这个时间窗的设计有讲究——太窄会错过可行的最优解,太宽会让搜索空间过大、收敛变慢。
第二步,计算三个目标函数值。这里有个工程细节:目标函数的计算不是简单套公式,需要先模拟充电过程,然后才能算费用和负荷方差。如果直接套公式而不考虑充电时序,模型会失真。
第三步,非支配排序。先找出种群中所有不被任何其他个体支配的解,标记为第一层;然后移除这一层,在剩余个体中再找第二层,以此类推。
第四步,计算拥挤度。同一个层级内部,按每个目标函数排序,相邻解之间的目标空间距离就是拥挤度。拥挤度越大,说明该区域解越稀疏,越应该保留,以保持解的多样性。
第五步,选择、交叉、变异。用锦标赛选择法选出进入交配池的个体,交叉概率设为0.9,变异概率设为0.1。SBX交叉的分布指数设为20,多项式变异的分布指数设为20——这两个参数决定了子代与父代的相似程度,数值越大,子代越接近父代,搜索越偏向局部。
第六步,精英保留。父代和子代合并成一个规模为2N的池子,按非支配层级和拥挤度排序,从前往后选N个进入下一代。
第七步,迭代循环。跑到第200代终止,输出最终的帕累托解集。我实测200代在多数算例下已经收敛,300代以上收益很小但耗时明显增加。
2.3 Matlab代码架构
整个项目按模块化方式组织,主程序和子函数分离,方便后续调整参数或替换算法。核心文件如下:
main.m % 主程序入口:参数设置、算法循环、结果输出 nsga2_optimize.m % NSGA-II主循环:选择、交叉、变异、精英保留 non_dominated_sort.m % 非支配排序 crowding_distance.m % 拥挤度计算 init_population.m % 初始化种群 calc_objectives.m % 计算三个目标函数 calc_charging_load.m % 模拟充电过程,生成日负荷曲线 calc_cost.m % 根据电价时段计算充电费用 calc_volatility.m % 计算电网负荷波动方差 calc_utilization.m % 计算充电站利用率 plot_results.m % 结果可视化这里要强调一下calc_charging_load的建模逻辑。它是整个模型的核心,决定了优化结果的准确性。具体实现时,先获取种群中每个个体的起始充电时间,然后按照充电功率和充电需求时长,从起始时间开始逐时段累加充电负荷。每辆车充电结束时间如果超过车主设定的截止时间,该个体就会被判定为违反约束。
2.4 计算充电费用和负荷波动的实现细节
用电费用计算有几种做法。最简单的做法是:总费用等于充电功率、充电时长、电价三者相乘。
更精细的做法是考虑分时段电价:如果充电过程跨越多个电价时段,就需要分段计算。比如下午6点开始充电,先按峰段电价计费,到了晚上10点切换到平段电价,晚上11点再切换到谷段电价。这个分段计算逻辑要用循环判断电量落在哪些时段内。
注意,充电倍率和效率也影响实际费用。同一辆60度电的车,用7kW慢充和60kW快充,实际充满时间不同、线损不同,单位电能的电价也不同——快充服务费往往更高。如果项目需要更精确,可以加入充电效率系数和充电服务费参数。
我说下我最常遇到的实现返回:
负荷波动方差的计算要先把“原始电网负荷”(不含电动汽车)和“电动汽车充电负荷”叠加。目标函数的本质是让叠加后的总负荷曲线尽可能平稳,不希望电力系统因为一群电动车充电而出现新的尖峰。
前几版代码有个低级错误:计算负荷方差时,用峰值减谷值代替方差,导致优化结果把部分充电负荷平移到清晨4点到6点,虽然峰谷差小了,但那个时段的负荷波动反而大了。改用方差指标后表现正常。这个问题在复盘时很有价值——目标函数的定义方式会深刻影响结果形态。
2.5 参数选择的经验值
多目标优化最头疼的是参数敏感性。这里给出我用下来比较稳定的组合,针对类似的电动汽车充电优化场景可以直接参考。
种群规模100对应8辆车和24时段,这个规模是合适的。如果仿真区域车辆数量到百台级别,种群规模建议200到300,否则收敛速度会变慢,解集分布也不够均匀。
迭代代数200代。在仿真初期可以先用50代快速跑通流程,再逐步增加到100、200,观察帕累托前端的形状变化。前端形状不再明显变化,就说明基本收敛。
交叉概率0.9,变异概率0.1,分布指数均设为20。交叉概率高,保证种群充分探索解空间;变异概率低,保持种群稳定。
我用过一套参数,将交叉概率设为0.7、变异概率设为0.3,结果帕累托前端分布不均匀,部分区域的解明显缺失。回看数据,变异概率过高会导致优秀基因被频繁破坏。
需要提醒的是,这些参数只是起点。不同问题的最优参数需要自己调。我在项目里做了一个简单实验:固定其他参数,只把种群规模从50调到200,对比帕累托前端的超体积指标。结果很明显——100以上收益开始递减,50明显不足。
3. 实操过程与核心环节实现
3.1 数据准备与场景设定
我以某个典型居民小区场景为例:8辆电动汽车,统一慢充7kW,配变容量200kVA,小区基础负荷曲线采用典型夏季工作日的配变负荷数据。
电价参数参照某地实际峰谷电价(示意值,非精确数据并保留必要模糊):
- 峰段(8:00-11:00、18:00-23:00):1.0元/kWh(含服务费)
- 平段(11:00-18:00):0.6元/kWh
- 谷段(23:00-次日8:00):0.3元/kWh
每辆车的电池容量、起始SOC和截止时间随机生成。SOC服从0.2到0.8的均匀分布,充电截止时间设定在次日早上7点之前——这个设定模拟的是“回家充电、次日出发”的真实通勤场景。
3.2 主程序代码框架演示
主循环的核心代码如下:
%% 参数初始化 popsize = 100; % 种群规模 maxgen = 200; % 最大迭代代数 nvar = 8; % 决策变量个数,即电动汽车数量 lb = 17; % 起始充电时间下限:下午5点 ub = 32; % 起始充电时间上限:次日早上8点(用24+8表示) % 电价参数 price_peak = 1.0; price_flat = 0.6; price_valley = 0.3; %% 第一步:初始化种群 population = init_population(popsize, nvar, lb, ub); %% 第二步:NSGA-II主循环 for gen = 1:maxgen % 计算目标函数值 [cost, volatility, utilization] = calc_objectives(population, ...); % 非支配排序 [fronts, rank] = non_dominated_sort(cost, volatility, utilization); % 拥挤度计算 distance = crowding_distance(cost, volatility, utilization, fronts); % 选择、交叉、变异生成子代 offspring = genetic_operators(population, fronts, distance, ...); % 合并+精英保留 combined = [population; offspring]; [fronts, rank] = non_dominated_sort([combined_cost], ...); distance = crowding_distance(...); population = select_elite(combined, fronts, distance, popsize); % 可视化当前帕累托前端(每50代输出一次) if mod(gen, 50) == 0 plot_current_pareto(cost, volatility, utilization, gen); end end这段代码的精髓在第12行附近的变量处理——每次迭代时,子代的目标函数值需要与父代合并后重新排序,不能单独排序再拼接,否则精英保留就失去意义。这个细节我当年搞错过一次,结果帕累托前端出现“倒退”——上一代已经找到的好解,下一代竟然丢了。原因是精英保留策略写错了,父代的优秀解没有参与下一轮竞争。后来改成合并再排序才正常。
3.3 非支配排序与拥挤度计算的Matlab实现
非支配排序本身不复杂,但效率上有讲究。朴素的双重循环逐个比较每对个体,复杂度是O(MN^2)——当种群规模达到200、目标函数达到3个时,200代迭代需要几百秒,勉强能接受。如果想提速,可以用“支配计数+被支配集合”的方式,把复杂度降到约O(MNlogN)。
拥挤度计算要特别注意边界个体:对每个目标单独排序后,目标值最大和最小的个体,拥挤度直接设为无穷大,保证边界解永远保留。其他个体拥挤度取相邻两点在各目标上归一化距离之和。归一化很关键,因为三个目标函数的量纲完全不一致——费用是元级别,方差是千瓦平方级别,利用率是百分数级别。如果不归一化,量纲大的目标会主导拥挤度计算,解集会偏向某一侧。
3.4 结果可视化与解释
跑完200代之后,把得到的帕累托前端画成三维散点图,三个坐标轴分别是充电费用、电网负荷波动、充电站利用率。你会看到一个类似“三维曲面”的分布——其实是一个不规则的解集合。
作图时把帕累托前端上每一个解都标出来,然后用不同颜色区分非支配层级。从图上能直观看到,方案A充电费用低但负荷波动大,方案B负荷平稳但费用偏高,方案C两者兼顾但充电站利用率低——每个解都对应一种不同的权衡,就看决策者倾向哪边。
我习惯再用TOPSIS方法从帕累托解集中挑出一个推荐方案。TOPSIS的基本思路是:构造理想解(各目标最优值)和负理想解(各目标最差值),然后计算每个帕累托解到理想解和负理想解的距离,选一个离理想解最近、离负理想解最远的解。权重需要根据实际偏好设定——如果电网侧更关心削峰填谷,负荷波动权重调高;如果用户侧更关心省钱,费用权重调高。这个二次决策过程解决的是“帕累托解集有了,但到底选哪个”的问题。
用TOPSIS选出的折中解,在我这个项目里对应的充电方案是:大部分车辆在晚上10点半到次日凌晨5点之间陆续启动充电,充电费用相比无引导的原始方案减少了约23%,电网负荷波动降低了约31%,充电站利用率基本不变。这三组数据是模型的直接输出,能直观体现分时电价加NSGA-II组合的优化效果。
3.5 对比实验:无引导与有引导有多大差别
有对比才有说服力。我在同样场景下做了几组对照实验:
无任何引导,车回家就充。结果是晚高峰7点到9点出现非常明显的充电负荷尖峰,变压器负荷率超过90%,逼近危险线。用户充电费用是全天高价位,平均每辆车充电花费比谷段充电贵40%以上。
有分时电价无优化,车主自行选择。结果看用户运气,有人愿意等到谷段再充,有人嫌麻烦直接充了。整体曲线比无引导平缓,但仍存在明显的小高峰,电网侧收益不稳定。
分时电价加NSGA-II优化。充电负荷大部分转移到谷段,叠加后的总负荷曲线与基础负荷曲线基本同趋势,方差最小,变压器压力显著降低。
最后一组的核心价值在于:它不仅仅是“省电费”,更是通过价格杠杆和市场机制自动形成了削峰填谷的行为,而不是硬性停止充电之类的强制手段。
4. 常见问题与排查技巧实录
4.1 帕累托前端分布不均怎么办
这是多目标优化最常见的头号问题。解全部挤在某块区域,前端铺不满,意味着决策者在某些权衡偏好下没有可选方案。
排查顺序从前往后:
检查种群规模。如果只有30到50个个体,前端铺不满很正常,加大到100以上。
检查迭代是否收敛。前期迭代时前端还在快速演化,200代都跑不满就不要急着分析结果。
检查拥挤度计算的归一化。我踩过一次坑,三个目标的值域差异太大,费用在几十元量级,波动方差在几千量级,归一化没做好,算法几乎忽略了费用维度,前端被压成一条线。
检查变异概率。变异概率过低会把搜索困在局部区域,前端只覆盖局部;过高会破坏优秀解,前端变得稀疏。从0.1开始调,往上下各试两档。
检查目标函数的定义是否合理。有时候目标函数定义本身造成目标区间太窄,比如负荷波动方差的值域本身就远小于其他目标,需要检查目标函数表达式或加权重平衡。
简单说一下,如果问题本身确实存在帕累托前端边界不够完整的特性,也不是非解决不可——先用TOPSIS或理想点法挑出一个满意解即可,不必追求前端所有区域都均匀覆盖。
4.2 约束条件处理不当导致大量不可行解
前期我把约束条件直接放进目标函数里,采用罚函数法,结果总会出现一批严重违反约束的个体挤在某些区域。罚函数法的核心问题在于惩罚系数的设定:系数太小,约束形同虚设;系数太大,可行域边缘的解全被排除,丢失边界信息。
后来我改用“约束支配”策略,处理起来干净得多。具体规则是:任何可行解支配任何不可行解;两个可行解之间按非支配关系比较;两个不可行解之间,违反约束程度小的解占优。这个策略在NSGA-II里天然适配,不需要额外调惩罚系数。
另外,在初始化种群时,我会做一个预处理,保证起始充电时间加充电时长不超过车主的截止时间,从源头减少大量不可行解。这不是严谨的约束修复,但效果很好,大幅降低无效计算。
4.3 Matlab程序运行异常与性能问题
Matlab初学者常见问题是时间维度的编号混乱。在这个项目里,一天24小时被拆成24个时段,但充电可以跨天——晚上11点充到凌晨2点,时段编号就面临越界问题。需要引入模运算,或者把时间轴扩展到48个时段再折叠。
性能方面最大的瓶颈是目标函数评估。每代100个个体、每个个体都要模拟8辆车的充电过程并计算三个目标,200代下来总评估次数是2万次。如果每次评估都写成循环嵌套,跑一次完整实验要10多分钟。我的优化方式是向量化——把个体内多辆车的充电起始时间、SOC、截止时间等参数并成一个矩阵,一次性计算所有车的充电负荷曲线,速度能提升几十倍。对于大规模仿真,还可以考虑并行计算,直接改循环为parfor。
常见问题速查表
| 现象 | 可能原因 | 排查与解决建议 |
|---|---|---|
| 帕累托前端挤在一小片区域 | 种群规模太小或变异概率过低 | 种群加到100+,变异概率从0.1向上调 |
| 目标曲线震荡,前后代解不稳定 | 交叉概率过高,后代偏离父代过远 | 交叉概率降到0.8左右,增大分布指数 |
| 解集大量不可行 | 约束处理方式不当,惩罚系数不合理 | 改用约束支配法,初始化时预处理可行解 |
| 算法收敛太慢 | 初始种群质量差或搜索空间过大 | 用启发式规则生成部分初始解(如按谷段电价时段预分配) |
| 费用目标值异常大 | 分段计费逻辑出错 | 检查充电时间跨时段时,电量是否被重复计费 |
| 负荷波动方差几乎不变 | 目标函数计算的是“峰谷差”而非“方差” | 改用每个时段与平均负荷的偏差平方和 |
4.4 一个容易忽略的坑:结果的后处理
NSGA-II输出的是一组互不支配的解,项目交付时不能只说“我们拿到了100个解让用户自己选”——这在实际工程里等于没做。决策者需要一个可执行的充电方案。我通常用TOPSIS做二次决策,额外再做一步敏感度分析:变换TOPSIS的权重,观察推荐方案是否发生剧烈变化。如果权重稍微调一调,推荐方案就从“谷段充电”跳成“峰段充电”,说明模型对权重过于敏感,需要回看目标函数或约束定义是否有问题。
真实的项目交付中,光有曲线和图表还不够。要把推荐方案的充电计划表导出来:每辆车几点开始充电、预计充多少度电、花费多少钱、对小区总负荷曲线的影响是什么。更重要的是,这份方案要被用户理解和接受——如果模型建议凌晨3点充电,但用户习惯就是晚上回家就插枪,再优化的方案也落不了地。
现代充电桩和车联网系统已经支持APP预约充电,只要把优化结果以充电预约指令的形式下发,就能实现自动执行。从算法到落地的最后一公里,虽然还有不少工程细节要打磨,但方向是通的。
我个人在实际操作中的体会是:多目标优化用在电动汽车充电场景,技术门槛不高,真正难的是目标定义、场景抽象和结果的工程落地。把这三个环节想清楚,NSGA-II只是工具箱里的一个顺手工具。下一步可以考虑把光伏出力、储能系统引入模型,做风光储充联合优化——那又是另一个值得细聊的话题了。