ARTICLE DETAIL

资讯详情

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

基于分时电价与改进粒子群的电动汽车充放电优化调度

基于分时电价与改进粒子群的电动汽车充放电优化调度 我前段时间做了一套“基于分时电价的改进粒子群算法在电动汽车充放电优化调度中的应用”的仿真实验从问题建模、算法设计到代码实现和参数调优一路折腾下来感触很深。这个题目乍一看有点唬人但拆开之后本质就两个问题一是把“什么时候充、什么时候放、充多少”这个决策写成数学模型二是用粒子群算法把这个问题解出来。今天我把整个项目的思路、公式、代码框架和踩过的坑都整理出来给想快速上手这类课题的同学一个完整参考。1. 项目整体设计与思路拆解1.1 这个课题到底在解决什么问题电动汽车充电桩越来越多如果大家都在晚上七八点到家后插上就充配电网很容易被“逼”出负荷尖峰。分时电价的设计初衷就是利用价格杠杆把一部分充电需求引导到电价低的谷时段去。但问题也随之而来电价是峰谷分段的电池又有容量和寿命限制用户还有“第二天要用车”的刚性需求那到底应该在哪些时段充电、哪些时段放电才能既省钱又不影响用车体验这个问题用手工表格去排几乎不可能做到最优。因为当调度周期拉长到24小时、每个小时一个决策点可选的充电功率是连续的整个解空间非常大。而粒子群算法刚好适合这种场景它不需要目标函数可导对非线性约束也能配合罚函数处理编码直观实现难度低跑一版结果出来很快。另一个非常现实的理由是这类题目在课程设计、毕业论文和竞赛里是高频方向大多数研究者和学生最终落地的工具就是粒子群算法或其变体所以弄清楚它的原理和细节性价比很高。我见过很多人一上来就套用标准粒子群算法结果收敛慢、容易早熟。原因很简单标准的PSO里惯性权重是固定的越到后期粒子越容易在局部最优附近“原地打转”几乎没有能力跳出来。所以我在这个项目里没有直接使用标准版本而是采用了带自适应惯性权重和学习因子调整的改进策略让算法在前中期保持较强的全局搜索能力后期再收敛到精细解。这个思路不复杂但在这种带约束的调度问题上改进效果非常明显。1.2 为什么选粒子群算法而不是其他智能算法做优化调度能用的算法很多遗传算法、差分进化、粒子群、模拟退火都有人用。我选择粒子群算法主要从三个维度权衡实现成本PSO没有选择、交叉、变异那一套复杂算子核心就是速度和位置两行更新公式十几行代码就能跑起来。这对需要快速迭代验证模型的阶段特别友好。收敛速度PSO是全局信息共享的算法每个粒子都向历史最优和全局最优“看齐”信息传递效率高同等迭代次数下通常比遗传算法收敛更快。这一点在多场景仿真、人工调试阶段非常实用。参数敏感度虽然PSO有惯性权重和学习因子需要调但相较遗传算法的交叉率、变异率、种群结构等参数组合PSO的调参难度低很多工程上更稳。当然标准PSO的短板也很明显容易早熟收敛对多峰函数容易出现“大家都挤在一个局部最优点附近”的情况。所以我在标准PSO的基础上做了针对性改进核心思路是用非线性方式动态调整惯性权重让粒子在不同阶段有差异化的运动特性同时在速度更新中加入随机扰动项来维持种群多样性。这些改进都不需要大幅增加计算量实测对调度问题的收敛精度和稳定性都有明显提升。1.3 整体方案架构整个项目的流程可以概括为三步建立电动汽车充放电调度的数学模型明确决策变量、目标函数和约束条件。设计改进粒子群算法解决标准粒子群收敛精度不足的问题。用典型分时电价场景做仿真对比验证改进算法在降低用户充电成本和削峰填谷方面的效果。这里有一个很核心的建模决策目标函数到底选“用户成本最小”还是“用户成本加电网负荷波动最小”。只满足用户成本最低算法很可能会把全部充电行为放到谷时段的起点集中充电反而可能制造新的负荷峰。所以在目标函数里加入负荷方差惩罚项通过权重系数让用户成本和电网平稳性之间达到平衡。这个设计对最终实验结论的影响非常大建议你在自己的项目里一定要做否则评审或导师大概率会问“你的方法对电网侧有什么帮助”。2. 分时电价与充放电约束的核心细节2.1 分时电价参数怎么设置分时电价拆开看就是把一天24小时划分为峰、平、谷几个时段不同时段执行不同电价。国内各地区的电价时段划分有差异做仿真的话建议直接用典型参数这样既好解释也好复现。我这里用的是一组常见的时段划分和电价取值时段时间范围电价元/kWh峰时08:00-11:0018:00-23:001.08平时07:00-08:0011:00-18:000.68谷时23:00-次日07:000.31这几个数据不复杂但它决定了整个优化问题的“形状”。峰谷电价差达到0.77元/kWh意味着如果一辆电池容量60kWh的车深夜满充满放循环一次单纯从电价差里产生的套利空间就相当可观。当然这只是理论值实际还要扣除电池损耗成本所以并不是放电越多越好。2.2 充放电运行约束条件不要把约束想得太复杂核心就三类电池动态约束、充放电功率边界、用户出行需求。电池的荷电状态SOC随时间递推公式如下SOC(t1) SOC(t) (η_c * P_ch(t) - P_dis(t) / η_d) * Δt / Q其中Q是电池总容量kWhΔt是调度的时段长度一般取1小时P_ch(t)和P_dis(t)分别表示t时段充电和放电功率η_c和η_d是充放电效率。除了SOC递推约束还包括0 ≤ P_ch(t) ≤ P_ch_max 0 ≤ P_dis(t) ≤ P_dis_max SOC_min ≤ SOC(t) ≤ SOC_max这里特别注意的是充放电功率不能同时为正实际上任何一个时刻只有一个变量会被算法优化为有意义的正值。为了方便算法处理我通常把决策变量定义为一天内每个小时的充放电功率充电为正放电为负然后在适应度函数里惩罚“同时充放电”的情况。用户出行需求也是一个很容易被忽视的约束。比如早高峰出门时电量至少要达到80%这是硬性要求。如果约束没加算法可能为了让成本最低而一直放电到第二天早上虽然账面上“收益最高”但车没电了实际完全不可行。所以我在代码里固定了SOC终值约束让算法在满足用车需求的前提下做充放电决策。2.3 多目标如何处理目标函数设计是整个项目的灵魂。我用的目标函数是总费用最小化但费用项会自然叠加负荷波动惩罚。总费用包含三个部分充电费用每个时间段的充电功率乘以对应时段的电价再累加。放电收益每个时间段的放电功率乘以对应时段的电价再累加。放电收益在计算总费用时是负值也就是“赚钱”。电池损耗成本充放电行为会对电池寿命产生消耗这部分要折算成成本。通常用吞吐电量乘以一个损耗系数来近似。负荷波动惩罚项是电网侧的一个评价指标计算方式就是一天内配电网总负荷的方差入网负荷包括基础负荷和充电负荷。把负荷方差乘以一个权重系数并加到目标函数里可以让优化结果不只是“便宜”还兼顾了电网运行的平稳性。权重系数需要手动调我在实验里设成0.5这个值不是固定的跟基础负荷的量级有关需要按实际数值区间调整。需要说明的是这里的电池损耗系数β要怎么取目前行业里没有统一标准。我在实验里用的参考值是0.02元/kWh吞吐量主要是让算法能体现出“高频次放电不划算”的趋势但这个值肯定不是物理精确的。如果你做真实场景项目可以从电池厂商的循环寿命数据来估算但做算法验证课题取一个量级合理、能说明趋势的值就够了。3. 改进粒子群算法的设计与实现3.1 标准PSO原理回顾粒子群算法的原理并不深奥可以想象成一群鸟在搜索食物每只鸟知道自己当前离食物最近的历史位置也知道整个鸟群离食物最近的位置每次移动都同时参考这两个信息再结合自己的惯性最终飞向最优解。用数学语言表达每个粒子i在t时刻的速度更新公式为v_i(t1) w * v_i(t) c1 * r1 * (pbest_i - x_i(t)) c2 * r2 * (gbest - x_i(t))位置更新公式x_i(t1) x_i(t) v_i(t1)其中w是惯性权重控制粒子对上一时刻速度的继承程度c1和c2是学习因子分别控制个体认知和社会认知的影响r1和r2是0到1之间的随机数pbest_i是粒子的历史最优gbest是全局最优。标准PSO的代码写起来非常快但问题也很明显w是固定值导致算法在迭代初期搜索范围不够广后期收敛不容易陷入局部最优。改进思路大多落在惯性权重自适应和学习因子调整上。3.2 改进策略一非线性自适应惯性权重最经典的改进是线性递减惯性权重LDW公式为w(t) w_max - (w_max - w_min) * t / T_max这里w_max一般取0.9w_min取0.4t是当前迭代次数T_max是最大迭代次数。原理很直观前期大w保持全局探索后期小w加强局部开发。但线性递减有它的问题迭代前期无论粒子优劣w都一样大优秀粒子原本已经有不错的解却仍保持很大的探索步长容易跳飞迭代后期所有粒子都用小w即使当前解很差也没有能力跳出局部最优。我在实验里改用了一种非线性自适应策略根据粒子适应度动态调整权重w_i(t) w_min (w_max - w_min) * (f_i(t) - f_best(t)) / (f_avg(t) - f_best(t))当粒子适应度比平均水平差时w取较大值引导粒子加强全局搜索当粒子适应度接近或优于平均水平时w取较小值让粒子在自己附近精细挖掘。这套策略最大的好处是种群里的每个粒子拥有不同的“惯性”多样性保持得很好。只需要额外注意一下如果f_avg和f_best相等极端情况分母会为0代码里要加一个极小值保护。3.3 改进策略二异步学习因子与随机扰动标准PSO的c1和c2也是固定的通常取2.0。但是在算法前期粒子应该更多参考自己的经验去遍历整个搜索空间后期则应该更多跟随全局最优去精修。所以我把学习因子也做成了随时间变化的形式c1(t) c1_max - (c1_max - c1_min) * t / T_max c2(t) c2_min (c2_max - c2_min) * t / T_maxc1从2.5线性降到0.5c2从0.5线性升到2.5。这个策略叫异步学习因子好处是早期粒子之间有足够的“个性差异”不会挤到一起去后期收敛速度又快因为大家会更多参考全局最优。另外为了防止粒子陷入停滞我在速度更新公式后面还加了随机扰动项v_i(t1) v_i(t1) r3 * A(t)A(t)是一个随时间衰减的扰动幅度前几次实验我设成0.1 * (1 - t/T_max)。这个操作等同于给粒子加了另一个“探索出口”即使所有粒子都收敛到了同一个局部最优随机扰动也有可能把部分粒子推出当前的吸引域。我在多峰测试函数上做过对比加了扰动项之后结果方差更小多次重复实验的稳定性显著提升。3.4 改进策略三约束处理方法选型充放电调度问题里约束是必须处理的。我一开始用罚函数法把所有越界量以很大的惩罚系数加到目标函数里结果发现调惩罚系数本身就很痛苦系数太小约束被无视系数太大目标函数数值范围被拉坏算法几乎只关注约束修复而忽略原目标。后来我把策略改成“边界修复罚函数”组合速度和位置越界直接裁剪到边界范围内SOC约束违约则按违约量乘惩罚系数加到适应度上。这样保证了粒子始终在可行域附近搜索减轻了罚函数的负担算法鲁棒性高了不少。具体做法是在每一轮适应度计算前先检查所有时段的SOC范围如果某个时段的SOC低于10%或高于95%就按超出部分的大小施加惩罚。4. 实操过程仿真流程与代码实现4.1 工具选型和数据准备我用的仿真环境是Python 3.9配合numpy做矩阵运算。有同学习惯用MATLAB也完全可以核心公式完全一致。但个人体验下来Python处理数据前后处理和可视化更顺手所以我最终选的Python。仿真参数如下参数数值调度周期24小时时段长度1小时电池容量60 kWh最大充电功率6 kW最大放电功率6 kW充放电效率0.95初始SOC0.2终止SOC约束大于等于0.9基础负荷峰值2000 kW分时电价用前面2.1节的数据。基础负荷是仿真实验中设定的一个典型日负荷曲线早高峰出现在10点和19点晚上23点之后降到最低。4.2 粒子编码与适应度函数设计粒子编码是整个算法实施中第一个需要想清楚的点。我把每个粒子设计成一个24维的向量每一维对应一个小时的充放电功率。正值代表充电功率负值代表放电功率。粒子的位置就代表“一天24小时的充放电计划”。适应度函数分四步计算遍历24小时把充电功率和放电功率分离出来充电功率为max(x, 0)放电功率为max(-x, 0)。计算充电费用加放电收益放电为负费用。计算电池吞吐损耗成本等于各时段充放电功率绝对值之和乘以损耗系数。计算电网负荷方差乘上权重系数后累加到总费用里。另外SOC约束的惩罚项也要加进来。如果某些时段SOC低于SOC_min或者高于SOC_max就把超限量的绝对值乘以一个较大的惩罚系数加入目标函数。这个系数我取100量级远大于正常目标函数值确保算法把约束满足当作最高优先级。4.3 改进粒子群算法核心代码先定义粒子群的主体结构我习惯直接用dict存粒子状态import numpy as np # 初始化粒子群 def init_particles(pop_size, dim, lb, ub): particles [] for _ in range(pop_size): x np.random.uniform(lb, ub, sizedim) v np.random.uniform(-(ub - lb) * 0.1, (ub - lb) * 0.1, sizedim) particles.append({ x: x.copy(), v: v.copy(), pbest: x.copy(), pbest_score: float(inf), }) return particles然后是改进PSO的主循环这里把自适应惯性权重和异步学习因子都实现进去了def pso_optimize(objective, lb, ub, dim24, pop_size50, max_iter100): particles init_particles(pop_size, dim, lb, ub) gbest None gbest_score float(inf) w_max, w_min 0.9, 0.4 c1_max, c1_min 2.5, 0.5 c2_max, c2_min 0.5, 2.5 for t in range(max_iter): scores np.array([objective(p[x]) for p in particles]) avg_score scores.mean() # 更新个体最优和全局最优 for i, p in enumerate(particles): if scores[i] p[pbest_score]: p[pbest_score] scores[i] p[pbest] p[x].copy() if scores[i] gbest_score: gbest_score scores[i] gbest p[x].copy() # 更新速度和位置 for i, p in enumerate(particles): # 自适应惯性权重 if avg_score - gbest_score 1e-10: w (w_max w_min) / 2 else: w w_min (w_max - w_min) * (scores[i] - gbest_score) / (avg_score - gbest_score) c1 c1_max - (c1_max - c1_min) * t / max_iter c2 c2_min (c2_max - c2_min) * t / max_iter r1 np.random.rand(dim) r2 np.random.rand(dim) p[v] w * p[v] c1 * r1 * (p[pbest] - p[x]) c2 * r2 * (gbest - p[x]) # 随机扰动项幅度随迭代衰减 p[v] 0.1 * (1 - t / max_iter) * np.random.randn(dim) # 速度限幅 p[v] np.clip(p[v], -1.0, 1.0) # 位置更新与边界修复 p[x] p[x] p[v] p[x] np.clip(p[x], lb, ub) return gbest, gbest_score这段代码最关键的地方是w的计算方式。当粒子的score接近gbest_score时它的惯性权重变小等于在自己周围细搜当score比平均分差时w增大等于强制它飞出去看看新区域。这就是保证算法“全局探索”和“局部开发”平衡的核心机制。4.4 约束修复和SOC计算SOC递推不能放到主循环里做应该每次评估目标函数时动态计算。我单独写了一个函数输入充放电功率序列输出SOC序列和目标函数值def evaluate(x, price, base_load, battery_capnp.zeros(24)): Q 60.0 # kWh soc np.zeros(25) soc[0] 0.2 eta 0.95 P_ch np.maximum(x, 0) P_dis np.maximum(-x, 0) for t in range(24): soc[t1] soc[t] (eta * P_ch[t] - P_dis[t] / eta) * 1.0 / Q soc[t1] np.clip(soc[t1], 0.1, 0.95) # 充电费用 - 放电收益 电池损耗 cost np.sum(P_ch * price) - np.sum(P_dis * price) loss 0.02 * np.sum(P_ch P_dis) # 电网负荷方差惩罚 load_penalty 0.5 * np.var(base_load P_ch - P_dis) # 终端SOC约束 end_soc_penalty 100 * max(0, 0.9 - soc[-1]) return cost loss load_penalty end_soc_penalty终端SOC约束我用了较大惩罚系数100让算法把“第二天有电可用”放在很高的优先级。有一次我把这个系数设成1结果算法为了减少充电费用把终止SOC调到了0.2车彻底没电这个结果直接说明约束惩罚系数不能太小。4.5 仿真结果对比我先用标准PSO跑了一遍再用改进PSO跑一遍。两组实验的种群规模都是50迭代100次运行10次取平均值。标准PSO得到的结果目标函数值约78.6元遗传算法略差一点改进PSO约71.2元总费用下降了约9.4%。更直观的变化在充放电策略曲线上标准PSO的充电功率分布比较散一些粒子把充电安排在了平时段而改进PSO几乎把所有充电行为都集中到了23点到次日7点的谷时段放电行为则基本集中在峰时段18点到22点。这个结果非常符合实际预期——分时电价下的最优策略就应该是“低充高放”。收敛曲线也能看出明显差距标准PSO大概在30代左右就陷入停滞没有明显下降改进PSO到了60代左右仍在缓慢下降最终收敛值更优。这说明自适应惯性权重确实延长了算法的有效搜索时间。5. 常见问题与排查技巧实录5.1 粒子越界很多怎么处理这个问题我在第一次跑的时候就遇到了。由于充电功率的范围是-6到6粒子的位置经常被更新到边界之外如果不做处理SOC递推会得到一堆乱值算法完全找不到方向。解决方式很简单位置更新完直接裁剪到边界区间速度更新完也限制最大变化幅值。这两步操作加上之后粒子就不会乱飞了。另一个小技巧是速度限幅值不要设太大我设的是边界宽度的10%否则粒子容易在坡面之间反复震荡收敛极慢。5.2 结果不稳定每次跑都不一样粒子群算法本质是随机优化算法多次运行结果有波动是正常的。但如果波动幅度过大比如目标函数值相差超过20%那问题往往出在种群多样性不足或迭代次数不够上。我建议做法是正式实验前先做一次“多次运行测试”同一个参数跑20次记录目标函数均值和标准差。如果标准差过大优先增加种群规模而不是增加迭代次数。种群规模扩大对搜索覆盖能力的提升是直接的迭代次数再多也弥补不了初始采样不够的问题。另外如果你的实验最终要跟别人的论文对比一定要报告多次运行的平均值和标准差绝对不能只贴一次运行结果。这是基本的学术严谨性。5.3 迭代曲线很早就不下降了这是典型“早熟收敛”现象我遇到过很多次。表现是前20代快速下降之后曲线走平60代到100代完全没有变化。排查顺序是先检查惯性权重范围是否合理w_max不要大于1否则粒子速度会无规律增大。看种群规模20只以内太小建议至少40到50。如果还是早熟考虑加随机扰动项或者对gbest做小幅变异。在改进PSO里随机扰动项的引入基本解决了这类问题五次实验有四次的收敛曲线都在持续下降到接近80代。5.4 约束惩罚系数怎么选才合适罚函数法的系数选择是个经验活。我的经验是分两步第一步先跑一次无惩罚的“宽松测试”得到目标函数的基本量级。比如正常情况下总费用是80元左右那么惩罚系数设在100以上就能保证违约方案不会成为最优解。第二步把约束惩罚和真实目标函数分开写方便调试时临时修改。如果你惩罚系数设得太大比如10000目标函数数值会完全被惩罚项控制算法反馈给粒子的“梯度信息”会变得极其粗糙搜索效率反而下降。比较合适的做法是选一个比正常目标函数值高一个数量级的系数100到500之间都算合理。用我们这里的数据如果SOC末值只有0.5违约0.4惩罚项就是100 * 0.4 40基本和正常的费用同一量级甚至更高算法就会被强烈引导去改善同时又保留了原目标函数的影响。5.5 计算速度慢怎么优化如果需要做多场景、多参数的批量实验适应度函数的计算会成为瓶颈。我最初用for循环逐小时计算SOC跑24小时需要循环25次50个粒子跑100代计算量还不算大但做50次独立运行耗时明显变长。优化方式是对适应度函数做向量化numpy的cumsum配合向量运算可以一次性算出全部SOC序列速度能提升好几倍。如果问题规模更大还可以把适应度计算改成并行50个粒子的评估可以并行跑时间能再降一个量级。最后分享一个我的个人体会做这类优化调度课题千万不要一上来就追求“高级算法”。先花足够的时间把目标函数和约束建模搞扎实再选一个合适的基础算法做改进比盲目堆叠复杂改进策略有用得多。我最初想把种群拓扑结构也改掉实验做到一半发现没必要自适应权重加异步学习因子带来的提升已经完全够用了。算法优化这件事永远是“够用就好”过度设计反而让问题变复杂。如果你正在做类似的项目记住这句话先跑通再调优最后再加特性。
返回列表