ARTICLE DETAIL

资讯详情

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

电动汽车充电站规划:出行链、马尔科夫链与排队论实战

电动汽车充电站规划:出行链、马尔科夫链与排队论实战 简介这是一份面向电动汽车充电设施规划领域研究者与工程技术人员的专业参考文献围绕用户出行模拟展开充电需求分析与充电站选址定容研究。资源为单个PDF文档压缩包约847KB内容完整呈现了基于出行链与马尔科夫链构建的电动汽车时空分布模型并借助蒙特卡洛模拟预测充电需求进而以运营商投资运维成本和用户排队等待时间最小化为目标建立优化模型采用遗传算法求解。文中还系统梳理了充电站规划领域的多篇文献成果涵盖排队论、Voronoi图划分、碳排放约束等思路并给出算例验证。目前已有471人学习下载适合新能源汽车、汽车技术方向的学生与从业者参考可帮助读者理解充电负荷空间分布特性、掌握从需求预测到站址容量配置的完整建模流程与求解方法。1. 从一份充电站规划 PDF 说起出行链、马尔科夫链和排队论怎么串起来很多人拿到《基于用户出行模拟的电动汽车充电需求分析与充电站规划.pdf》这类文献第一反应是“公式太多翻两页就放下了”。但如果你正在做充电站选址、配电网扩容评估或者要给运营商出一版容量配置方案这份材料其实是一条完整的工程链路从用户每天怎么跑到车什么时候没电再到站里排多长的队、配几台桩、接进配电网会不会过载。它把出行链、马尔科夫链、蒙特卡洛模拟、M/M/C 排队论和遗传算法串成了一条可复现的流程而不是只丢一个目标函数。适合做规划仿真、写论文复现、给充电站项目做前期测算的人。下面我按“怎么把 PDF 里的模型跑起来”拆一遍重点讲参数怎么设、坑在哪。2. 出行链与马尔科夫链把用户一天的行车轨迹变成状态转移矩阵2.1 五类目的地与状态转移概率的工程含义PDF 把用户出行目的分成五类回家 H、工作 W、购物吃饭 SE、社交休闲 SR、其他事务 O。这五类对应马尔科夫链里的五个状态 E1 到 E5。核心假设是车辆下一个目的地只与当前目的地有关与更早去过哪里无关。这个假设在工程上是否成立对于通勤为主的城区基本成立对于网约车、物流车偏差会比较大因为它们的行程受订单驱动不完全是个人活动链。状态转移概率矩阵是 5×5 的 P 矩阵每一行代表当前状态每一列代表下一个状态。PDF 里没有给出具体的数值矩阵这是复现时第一个要补的地方。常见做法是引用 NHTS美国全国家庭出行调查或国内城市居民出行调查数据按出行目的统计转移频率。我一般会先用均匀分布跑通流程再用实际调查数据替换观察充电需求空间分布的变化。矩阵每行之和必须为 1这是马尔科夫链的硬约束。如果某一行之和偏离 1蒙特卡洛抽样时会出现概率累积误差导致某些目的地永远抽不到。建议在代码里加一行断言import numpy as np # 5x5 状态转移矩阵行当前状态(H,W,SE,SR,O)列下一状态 P np.array([ [0.00, 0.40, 0.20, 0.15, 0.25], # 从家出发 [0.45, 0.00, 0.20, 0.15, 0.20], # 从工作地出发 [0.50, 0.25, 0.00, 0.15, 0.10], # 从购物吃饭出发 [0.45, 0.20, 0.15, 0.00, 0.20], # 从社交休闲出发 [0.40, 0.25, 0.20, 0.15, 0.00], # 从其他事务出发 ]) # 每行必须归一化否则抽样会出问题 row_sums P.sum(axis1) assert np.allclose(row_sums, 1.0), f转移矩阵行和不为1: {row_sums} print(转移矩阵校验通过)这段代码的逻辑说明先定义矩阵再检查每行和是否为 1。参数说明矩阵中的 0 表示不允许自转移即车辆不会从当前目的地直接“转移”到同一个目的地必须去另一个地方。如果你允许自转移比如在家待着不出行那要把对角线改成非零值但那样出行链的行程数会变少充电需求也会跟着降。2.2 蒙特卡洛抽样从概率到具体行程有了转移矩阵下一步是用蒙特卡洛方法为每辆车抽出一条出行链。流程是先抽初始状态一般从 H 开始再根据当前状态那一行的概率分布抽下一个状态重复直到行程数达到设定值。PDF 里提到“抽取行程数量 N”这个 N 是每辆车的日出行次数常见取值是 3 到 6 次。N 越大充电需求的空间分布越分散因为车辆跑的地方多。def sample_trip_chain(P, start_state0, n_trips4): 抽一条出行链返回状态序列 states [start_state] current start_state for _ in range(n_trips - 1): # 按当前状态那一行的概率抽下一个状态 next_state np.random.choice(5, pP[current]) states.append(next_state) current next_state return states # 给 1000 辆车各抽一条出行链 np.random.seed(42) chains [sample_trip_chain(P, start_state0, n_trips4) for _ in range(1000)] print(前5辆车的出行链:, chains[:5])逻辑说明np.random.choice按概率 p 抽样p 就是转移矩阵的某一行。参数说明start_state0表示从家出发n_trips4表示一天 4 次行程。如果你要模拟不同出行强度的人群可以把 n_trips 设成随机变量比如从 3 到 6 均匀抽取这样更接近实际。2.3 Dijkstra 路径与耗电量计算抽完出行链还要知道每段行程走哪条路、耗多少电。PDF 用 Dijkstra 算法确定最优路径。工程上需要一张路网图节点是路口或目的地边是道路权重可以是距离或通行时间。耗电量按百公里电耗乘以路径长度估算。常见做法是城区路网用 0.15 kWh/km高速用 0.18 kWh/km。如果 PDF 没有给具体路网可以用网格图代替节点间距 1 km先跑通逻辑。import heapq def dijkstra(graph, start, end): 返回最短路径长度 dist {node: float(inf) for node in graph} dist[start] 0 pq [(0, start)] while pq: d, u heapq.heappop(pq) if u end: return d if d dist[u]: continue for v, w in graph[u]: if dist[u] w dist[v]: dist[v] dist[u] w heapq.heappush(pq, (dist[v], v)) return dist[end] # 示例5个节点的简单路网 graph { 0: [(1, 2.0), (2, 3.5)], 1: [(0, 2.0), (3, 1.5)], 2: [(0, 3.5), (3, 2.0), (4, 4.0)], 3: [(1, 1.5), (2, 2.0), (4, 1.0)], 4: [(2, 4.0), (3, 1.0)], } print(0到4最短距离:, dijkstra(graph, 0, 4))逻辑说明标准 Dijkstra 用优先队列找最短路。参数说明graph 的键是节点编号值是该节点连出去的边和权重。实际项目中路网可以从 OpenStreetMap 导出节点数可能上万这时候要考虑用 A* 或分层路网加速否则 1000 辆车每辆抽 4 段路计算量会很大。3. 充电需求计算SOC 阈值、排队论和配电网约束怎么落地3.1 SOC 低于 30% 才充电阈值设多少才合理PDF 假设电量小于 30% 时用户产生充电需求且充满。这个阈值直接影响充电需求的频次和空间分布。阈值设高了充电次数多充电站排队压力大设低了用户里程焦虑严重实际中可能低于 20% 就找桩了。我一般会做敏感性分析分别取 20%、25%、30%、35%看充电需求总量和峰值的变化。如果 PDF 的算例没有给 SOC 分布可以用正态分布 N(0.5, 0.15) 截断到 [0.1, 0.9] 来抽初始电量。def need_charge(soc, threshold0.3): 判断是否需要充电 return soc threshold def sample_initial_soc(n): 抽初始SOC截断正态 soc np.random.normal(0.5, 0.15, n) return np.clip(soc, 0.1, 0.9) socs sample_initial_soc(1000) need [need_charge(s) for s in socs] print(f需要充电的车辆比例: {sum(need)/len(need):.2%})逻辑说明np.clip把 SOC 限制在合理范围避免出现负数或超过 1。参数说明均值 0.5 表示用户平均有一半电标准差 0.15 表示分布比较集中。如果你要模拟极端场景可以把标准差调大比如 0.25这样低电量车辆更多充电需求更集中。3.2 M/M/C 排队论充电机数量与等待时间的量化关系PDF 用 M/M/C 排队论算等待时间。M/M/C 的假设是到达过程是泊松流服务时间服从指数分布C 个服务台并行。充电站里C 就是充电机数量服务率 μ 是每台充电机每小时能充满的车辆数。如果一辆车平均充 1 小时μ1如果快充半小时μ2。到达率 λ 是每小时来充电的车辆数从蒙特卡洛模拟的充电需求时空分布里统计得到。等待时间公式 PDF 里给了但实际写代码时我建议直接用排队论库或自己写数值计算避免公式抄错。关键参数是服务强度 ρ λ/(Cμ)。ρ 必须小于 1否则队列会无限增长。如果算出来 ρ≥1说明充电机不够要么加桩要么引导用户去别的站。import math def mmc_wait_time(lam, mu, c): M/M/C 平均等待时间 rho lam / (c * mu) if rho 1: return float(inf) # 系统不稳定 # 计算 P0 sum1 sum((lam/mu)**k / math.factorial(k) for k in range(c)) sum2 (lam/mu)**c / (math.factorial(c) * (1 - rho)) p0 1 / (sum1 sum2) # 平均等待时间 w (p0 * (lam/mu)**c * rho) / (math.factorial(c) * (1 - rho)**2 * lam) return w # 示例每小时来12辆车每台桩每小时服务2辆配8台桩 print(平均等待时间(小时):, mmc_wait_time(12, 2, 8))逻辑说明先算 ρ再算 P0最后算 W。参数说明lam 是到达率mu 是服务率c 是充电机数。如果 W 超过 10 分钟PDF 算例里的约束是低于 10 分钟就要调整 c 或引导需求。注意M/M/C 假设服务时间指数分布实际充电时间更接近正态分布所以算出来的等待时间偏保守实际可能短一些。3.3 配电网约束电压、电流和功率的校验PDF 给了节点电压幅值约束、馈线电流约束和功率约束。这些是硬约束规划方案如果不满足直接淘汰。工程上配电网潮流计算可以用前推回代法或直接调 MATPOWER。如果只是做规划筛选可以用简化校验充电站接入点的总负荷不超过变压器容量的 80%线路电流不超过额定值的 90%。def check_grid_constraints(v, v_min, v_max, i_line, i_max, p_total, p_max): 校验配电网约束 ok True if not (v_min v v_max): print(f电压越限: {v}) ok False if i_line i_max: print(f电流越限: {i_line} {i_max}) ok False if p_total p_max: print(f功率越限: {p_total} {p_max}) ok False return ok # 示例 print(约束校验:, check_grid_constraints(0.95, 0.93, 1.07, 200, 250, 800, 1000))逻辑说明逐项检查电压、电流、功率是否在范围内。参数说明v 是标幺值v_min 和 v_max 一般取 0.93 和 1.07i_line 是馈线电流i_max 是允许最大值p_total 是充电负荷加基础负荷p_max 是节点允许最大接入功率。如果校验不通过遗传算法里对应的染色体适应度要设成惩罚值让它被淘汰。4. 遗传算法求解编码、适应度函数和约束处理4.1 选址编码11 个备选站址里选 7 个PDF 算例有 11 个备选站址要选 5 到 8 个。遗传算法的染色体可以编码成二进制串长度 111 表示建站0 表示不建。但这样有个问题随机生成的染色体可能建站数量不在 5 到 8 之间。常见做法是加修复机制如果 1 的个数少于 5随机补 1多于 8随机去 1。或者直接用整数编码染色体是选中的站址编号列表长度固定为 7但这样搜索空间小一些。def repair_chromosome(chrom, min_stations5, max_stations8): 修复染色体保证建站数量在范围内 chrom chrom.copy() n chrom.sum() if n min_stations: # 随机补1 zeros np.where(chrom 0)[0] add np.random.choice(zeros, min_stations - n, replaceFalse) chrom[add] 1 elif n max_stations: # 随机去1 ones np.where(chrom 1)[0] remove np.random.choice(ones, n - max_stations, replaceFalse) chrom[remove] 0 return chrom # 示例 chrom np.array([1,0,1,0,0,1,0,0,1,0,0]) print(修复前:, chrom, 建站数:, chrom.sum()) print(修复后:, repair_chromosome(chrom), 建站数:, repair_chromosome(chrom).sum())逻辑说明先统计 1 的个数再根据上下限补或删。参数说明min_stations 和 max_stations 来自 PDF 的 5 到 8。注意修复后要重新计算适应度因为建站方案变了。4.2 适应度函数年建设运维成本加年等待时间成本PDF 的目标函数是年建设运维成本加年等待时间成本单位是万元。年建设成本是充电机数量的函数年运行维护成本按初期投资的百分比算年等待时间成本是用户等待时间乘以单位时间成本再乘以 365 天。遗传算法里适应度取目标函数的倒数或负值因为我们要最小化成本。def fitness(chrom, m_list, a, b, o, z, eta, W_list, u, n_list): 计算适应度chrom是二进制串m_list是各站充电机数 selected np.where(chrom 1)[0] if len(selected) 0: return -1e9 # 惩罚 # 年建设成本 C_cs 0 for g in selected: m m_list[g] C_cs (m * a b) * (o * (1o)**z) / ((1o)**z - 1) # 年运维成本 C_op eta * C_cs # 年等待时间成本 C_W 0 for g in selected: C_W 365 * u * W_list[g] * n_list[g] total C_cs C_op C_W return -total # 最小化成本适应度取负 # 示例参数 m_list [15,13,17,16,18,13,14,12,16,14,15] # 各站充电机数 W_list [0.11,0.12,0.10,0.11,0.10,0.11,0.12,0.13,0.11,0.12,0.11] # 等待时间(小时) n_list [50,45,55,48,60,42,47,40,52,46,49] # 充电需求数 print(适应度:, fitness(np.array([1,1,1,1,1,1,1,0,0,0,0]), m_list, 10, 100, 0.08, 10, 0.05, W_list, 50, n_list))逻辑说明先算建设成本再算运维成本再算等待时间成本最后取负作为适应度。参数说明a 是充电机单价b 是固定投资o 是折现率z 是运行年限eta 是运维比例u 是单位等待时间成本W_list 是各站平均等待时间n_list 是各站充电需求数。这些参数在 PDF 里没有全部给出需要根据实际项目填。4.3 选择、交叉、变异遗传算法参数怎么调遗传算法的参数包括种群大小、迭代次数、交叉率、变异率。PDF 没有给具体值常见做法是种群 50 到 100迭代 100 到 500交叉率 0.8变异率 0.05。如果算例规模小种群可以小一点如果备选站址多种群要大一些避免早熟收敛。def genetic_algorithm(pop_size, n_gen, chrom_len, fitness_func, pc0.8, pm0.05): 简单遗传算法框架 pop np.random.randint(0, 2, (pop_size, chrom_len)) pop np.array([repair_chromosome(c) for c in pop]) best None best_fit -1e9 for gen in range(n_gen): fits np.array([fitness_func(c) for c in pop]) # 选择 idx np.argsort(fits)[::-1] pop pop[idx] if fits[idx[0]] best_fit: best_fit fits[idx[0]] best pop[0].copy() # 交叉 new_pop [pop[0], pop[1]] while len(new_pop) pop_size: p1, p2 pop[np.random.choice(len(pop), 2, replaceFalse)] if np.random.rand() pc: point np.random.randint(1, chrom_len-1) c1 np.concatenate([p1[:point], p2[point:]]) c2 np.concatenate([p2[:point], p1[point:]]) else: c1, c2 p1.copy(), p2.copy() new_pop.extend([repair_chromosome(c1), repair_chromosome(c2)]) pop np.array(new_pop[:pop_size]) # 变异 for i in range(pop_size): if np.random.rand() pm: j np.random.randint(chrom_len) pop[i, j] 1 - pop[i, j] pop[i] repair_chromosome(pop[i]) return best, best_fit # 示例运行 best, best_fit genetic_algorithm(50, 100, 11, lambda c: fitness(c, m_list, 10, 100, 0.08, 10, 0.05, W_list, 50, n_list)) print(最优染色体:, best, 最优适应度:, best_fit)逻辑说明选择用排序保留精英交叉用单点交叉变异用位翻转。参数说明pc 是交叉率pm 是变异率。注意每次交叉和变异后都要修复染色体保证建站数量在 5 到 8 之间。如果迭代过程中最优适应度长时间不变可以增大变异率或重启种群。5. 避坑与排查复现这份 PDF 时最容易翻车的五个地方5.1 转移矩阵行和不为 1抽样结果全偏现象跑蒙特卡洛时某些目的地永远抽不到或者充电需求全挤在商业区。原因转移矩阵某一行之和不是 1概率累积误差导致抽样偏向。解决在代码里加断言每行和必须等于 1误差超过 1e-6 就报错。如果数据来自调查先做归一化。5.2 SOC 阈值设太低充电需求少得可怜现象算出来的充电站年等待时间成本很低但实际用户抱怨找不到桩。原因SOC 阈值设成 20% 甚至更低很多车一天都不用充电。解决做敏感性分析至少跑 20%、25%、30%、35% 四组看充电需求总量和峰值的变化。PDF 用 30%你可以从 30% 起步再上下调整。5.3 M/M/C 排队论 ρ≥1等待时间算出来是无穷大现象某充电站的等待时间返回 inf遗传算法直接淘汰该方案。原因到达率 λ 超过服务能力 Cμ系统不稳定。解决先检查 λ 和 μ 的单位是否一致比如 λ 是每小时车辆数μ 也必须是每小时服务车辆数。如果 ρ 确实大于 1要么加充电机要么把需求引导到相邻站。5.4 配电网约束校验太严所有方案都不通过现象遗传算法跑了几百代最优适应度一直是惩罚值。原因电压下限设成 0.95 甚至更高或者电流上限设得太小。解决参考实际配电网运行规程电压一般 0.93 到 1.07电流按导线载流量的 80% 到 90% 设。如果还是全不通过先放宽约束跑通流程再逐步收紧。5.5 遗传算法早熟收敛结果不如随机选现象迭代 50 代后最优解不变但手动检查发现明显更差的方案。原因种群多样性不足交叉和变异没起作用。解决增大种群到 100 以上提高变异率到 0.1或者用多种群并行。另外每次交叉变异后必须修复染色体否则建站数量越界会被惩罚好基因也留不下来。6. 进阶技巧用真实路网和充电数据校准你的仿真如果你已经把 PDF 的流程跑通下一步是用真实数据替换假设。路网可以从 OpenStreetMap 导出用 osmnx 库读取节点是路口边是道路权重用通行时间。充电需求可以拿运营商的历史订单数据统计每个站每小时的到达率 λ再反推 M/M/C 里的服务率 μ。这样算出来的等待时间更接近实际。import osmnx as ox import networkx as nx # 下载路网示例某城市中心区域 G ox.graph_from_place(深圳市福田区, network_typedrive) # 转成有向图边权重用通行时间 G ox.add_edge_speeds(G) G ox.add_edge_travel_times(G) # 找最短路径 orig list(G.nodes)[0] dest list(G.nodes)[100] route nx.shortest_path(G, orig, dest, weighttravel_time) print(路径节点数:, len(route))逻辑说明osmnx 自动下载路网并计算通行时间networkx 找最短路。参数说明network_typedrive只保留机动车道weighttravel_time用通行时间作权重。注意路网下载可能受网络影响如果失败可以先用示例路网跑通逻辑。另一个技巧是用充电订单数据校准 SOC 阈值。统计用户充电时的 SOC 分布如果大部分人在 25% 到 35% 之间充电那 30% 的阈值是合理的如果很多人低于 20% 才充说明里程焦虑不严重或者充电站不够方便。我一般会把订单数据按小时聚合看早高峰、晚高峰的到达率变化再调整蒙特卡洛里的出行时间分布。从那以后我每次复现这类规划模型都强制先跑一遍敏感性分析把 SOC 阈值、转移矩阵、充电机单价这三个参数各调三档看结果稳不稳定。不稳定就说明模型对参数太敏感实际工程里不能直接用。希望帮到你。本文还有配套的精品资源点击获取
返回列表