☰
Voronoi围捕算法在含电动汽车主动配电网规划中的Matlab实现
2026/10/10 4:16:04 网站建设 项目流程

干配电网规划仿真这几年,我最大的一个体会是:电动汽车把“负荷是静止的”这个老假设彻底打破了。一辆通勤电动车,白天在园区停车场充电、傍晚在商圈快充、深夜又回到小区慢充,落在电网图上就是一个肉眼可见在漂移的动态负荷。你按某一张负荷分布图做的变电站供电范围划分,过两个月可能就哪里都别扭。最近我在Matlab里完整复现了一套解决这个问题的算法——Voronoi图围捕算法,把它移植到电动汽车、主动配电网和电力系统规划的框架下来看,效果挺有意思。这篇东西就干一件事:把这套方法从原理到代码、再到规划场景里的实际套用法,掰开揉碎讲清楚。适合正在做充电设施布局、移动储能调度、配电网网格化规划的同行参考,也适合想用空间几何工具做仿真的研究生直接照着搭一个Demo。

1. 先说清楚:Voronoi围捕算法到底在解决配电网的什么问题

1.1 传统规划方法在“移动负荷”面前的三处失灵

做电网规划的人最习惯的工作方式,是先把区域里每个节点的负荷求出来,再按负荷大小和位置规划变电站、馈线、变压器容量。这套方法论成熟,但在电动汽车大量接入之后,至少有三个地方开始不好使。

第一,负荷不再固定在节点上。传统规划把一个街区、一个园区的负荷当成相对稳定的量,但充电负荷跟人的活动轨迹走,白天和晚上的高峰位置可能正好相反,更不用说节假日高速公路服务区那种脉冲式负荷。你拿季度的平均负荷数据去倒推站点容量,很可能刚好漏掉最需要扩容的位置。

第二,时间断面割裂。传统规划常按“最大负荷时刻”或者“典型日”来做,但电动车渗透率上来之后,峰谷差和负荷同时率变化剧烈,单一断面下的方案在另一时段几乎必然过配或欠配。白天园区配电容量吃紧,晚上却没几辆车;小区正好反过来。一张固定容量表很难同时满足这两种截然不同的空间分布。

第三,集中式决策跟不上动态变化。规划做完之后,运行中电动汽车的充电需求可能随机出现在任何一个位置,想靠一个中心节点实时统筹全局,通信和计算压力都很大。尤其是应急充电、移动储能调配这类场景,电网侧需要的是分布式、能靠局部信息自主判断的调度模式。

1.2 围捕算法的思路:把充电需求当成会跑的“猎物”

Voronoi围捕算法的出发点和这几处失灵正好对应。它的思路是:不把电动汽车充电需求当成贴死在地图上的负荷,而是当成一群“会跑的猎物”,让充电站、移动充电车、分布式储能这些资源像围捕者一样,在空间上主动逼近需求。

打个比方。你让几个猎人围一块野地,如果猎物位置固定,猎人只要事先分好片区、各自驻守就行。可猎物是动的,猎人就得实时根据自己和猎物的相对位置调整前进方向,同时还要保证大家不要一窝蜂扑到同一个方向。Voronoi图干的事情,就是给每个猎人划出一块“责任田”——理论上,猎人只需要盯住自己田里出现的目标,所有田合起来又恰好覆盖整个可活动范围,不重叠、不遗漏。

把这个比喻搬回配电网:每个移动充电车、每个储能舱就是猎人,某个区域出现的紧急充电需求就是猎物。谁离得近、谁的区域里出现了新请求,谁就去响应,整个过程可以完全靠局部信息跑起来,不需要一个“总指挥”实时告诉所有人该往哪走。这正是围捕算法在主动配电网里最被看重的地方——它天然就是分布式的。

1.3 为什么Voronoi图是干这件事最顺手的工具

当然,解决空间分配问题的几何工具不止Voronoi一种,你还可以用K-means聚类、用最短路径、用人工势场。但Voronoi图在配电网规划里尤其顺手,原因是它有三条其他方法很难同时给到的性质。

第一条,最近邻性质。任意空间点永远被划分到离它最近的那个种子点,也就是某个充电站或移动资源。对充电需求来说,“就近服务”天然就是最优目标,这就决定了Voronoi分区本身就是一种合理的服务范围划分,不需要额外加一层优化模型来强行凑出服务区。

第二条,覆盖完整、互不重叠。每个胞无缝隙地铺满整个规划区域,不会出现两个站都不管的地带。这对工程上“保供电范围无盲区”的需求是硬性的,规划评审时只要拿分区图出来,覆盖问题一目了然。

第三条,局部扰动只会引起局部变化。一个种子点挪了位置,受影响的只是它和相邻种子之间那几条边,远处细胞纹丝不动。这个性质在动态规划里非常友好——电动车负荷一变,我只需重新计算局部边界,不需要把整张分区图推倒重来。

2. 算法原理拆解:Voronoi划分与围捕控制律怎么搭

2.1 Voronoi图的数学定义与三条关键性质

形式化一点。给定平面上一组种子点集合 S = {s1, s2, ..., sn},Voronoi图把平面划分成n个胞 Vi,使得:

Vi = { x ∈ R² | ||x - si|| ≤ ||x - sj||, 对任意 j ≠ i }

意思就是,胞 Vi 里任意位置到种子 si 的距离,都不大于到其他任何种子的距离。胞与胞之间共享的边,是相邻种子连线的中垂线;三个胞交汇的点,是三个种子外接圆的圆心。在Matlab里生成它只用一行基础函数:对点集调用 voronoin,或者先建 delaunayTriangulation 再取对偶图。后者在工程上更常用,因为Delaunay三角剖分自带“最大最小角”的优质网格特性,和Voronoi互为对偶,很多计算可以共用一套三角网格。

前几年我读文献时一直分不清这两个概念的关系,后来记住一句话就通了:把Delaunay三角剖分每个三角形的外接圆圆心连起来,就得到Voronoi图的边。反过来理解,Voronoi顶点就是某三个种子的外接圆圆心。这两个结构配合使用,既能做空间分区,又能做最近邻搜索和网格剖分,一套数据结构吃透整个流程。

2.2 围捕过程的“划分—逼近—合围”三阶段

一篇完整的围捕仿真,不管论文里写得多么花哨,骨架就三个阶段。

第一阶段是划分。按围捕者当前位置生成Voronoi图,每个围捕者获得自己的责任区域。对配电网规划来说,这一步对应的是“根据现有站点位置,把服务范围切成片”。第二阶段是逼近。每个围捕者朝目标方向运动,但运动的优先级和幅度受自己所在胞的限制。比如目标出现在多个胞的公共边界附近,那么多个围捕者都会同时向目标逼近,多方向合围的立体感就出来了——这正好对应多个移动资源同时响应一处充电高峰。第三阶段是合围与收敛。围捕者不断靠近目标,同时通过相互之间的排斥或角度约束保持队形,直到某个判定条件成立,比如最小距离小于捕获半径,或者最大角度缺口小于给定阈值。

在规划场景里,“捕获”不一定真指抓到,而是指资源已经覆盖需求点、响应时间达标、容量足以消纳该点的充电负荷。算法跑完,你得到的不是一条“谁追到了谁”的记录,而是一组“哪个资源在什么时刻覆盖了哪个需求点”的时空轨迹。

2.3 控制律设计:向心项与分散项的参数关系

控制律是算法的发动机。我复现时用的是最常见也是最好调的“向心项+分散项”组合,公式长这样:

u_i = k_p * ( (pT - p_i) / (||pT - p_i|| + ε) ) + k_d * Σ_{j∈N(i)} ( (p_i - p_j) / (||p_i - p_j|| + ε) )

向量 u_i 是第 i 个围捕者本轮的速度指令。前半部分叫向心项,把围捕者拉向目标 pT,k_p 是追赶增益;后半部分叫分散项,把挤在一起的围捕者互相推开,k_d 是分散增益,N(i) 是第 i 个围捕者的邻居集合。邻居关系由Voronoi相邻判定得到,这也是为什么控制律和Voronoi图始终绑在一起。

三个参数之间的配合有讲究。k_p 太大,围捕者会在目标附近反复震荡、冲过头;k_d 太大,围捕者还没靠近目标就互相推走,队形散开、收敛不到捕获半径。我调参的经验是:先固定 k_p,在自己期望的 v_max 附近做几轮二分,找到目标附近不明显震荡的上限;再慢慢加 k_d,观察围捕者相对目标的角度是否趋于均匀。角度均匀化以后,包围效果才能真正撑起来,单纯一堆车追着目标屁股跑那不叫围捕,叫追击。

控制项作用过大的后果过小的后果
向心项 k_p将资源拉向需求点目标附近震荡、轨迹来回摆收敛慢,仿真时间内合不了围
分散项 k_d保持队形、避免扎堆围捕者互相推开、包围圈发散围捕者聚成一团,包围角度缺口大
速度上限 v_max模拟真实车辆/储能移动能力加速度失真、控制无效响应慢、跟不上动态目标

3. Matlab环境下的完整复现流程

3.1 仿真环境与参数预设

先交代环境。我用的Matlab R2021b,纯基础工具箱就能跑,不需要额外安装任何专业化组件,voronoi相关函数都内置。对规划算例来说,只要电脑能跑二维矩阵运算就完全够用。参数设置如下表,这些数值不是拍脑袋,而是结合典型配电网园区尺度和移动充电车速度设计的:园区范围约200米见方,充电车最高运行速度约8米/秒,捕获半径5米对应实际中“车已经到达充电工位”的物理距离。你完全可以根据自己场景改,但量级不要差太远,否则调参过程会很痛苦。

参数取值含义
N6围捕者(移动资源)数量
P0随机分布围捕者初始位置,覆盖园区各方位
pT0[25; 25]目标需求点初始位置
目标速度0.5 m/s模拟充电需求的缓慢漂移
T30 s仿真时长
dt0.01 s仿真步长,兼顾精度与计算量
rc5 m捕获半径,判断资源是否到位
kp / kd8 / 2.5控制增益,按调参流程确定
vmax8 m/s移动资源最大运行速度

3.2 生成Voronoi分区并处理无穷边界

直接用 voronoin 生成分区属于“三分钟能画出来、三天画不干净”的典型问题。我先给标准动作:

P = [10 20 35 18 28 40; ... 35 15 10 30 20 42]; % 6个围捕者初始位置 [V, C] = voronoin(P');

返回的 V 是所有顶点坐标,C 是每个胞的顶点索引。麻烦就在:对于落在凸包边缘的胞,Voronoi边会一直延伸到无穷远,voronoin 用 V(1,:) = [Inf, Inf] 表示这个无穷点,直接拿去画图,patch就会画出一些匪夷所思的超大三角形。

处理办法我给了两种。工程上偷懒但足够稳的一种是网格离散:把规划区域切细网格,逐点计算每个网格属于哪个种子,再用染色填充。这个办法天然免疫无穷边界问题。核心代码不长:

xgrid = linspace(0, 50, 250); ygrid = linspace(0, 50, 250); [XX, YY] = meshgrid(xgrid, ygrid); D = zeros(numel(XX), N); for k = 1:N D(:, k) = sqrt((XX(:) - P(1,k)).^2 + (YY(:) - P(2,k)).^2); end [~, idx] = min(D, [], 2); regionMap = reshape(idx, size(XX)); imagesc(xgrid, ygrid, regionMap); axis xy; hold on;

如果你想保留真正的Voronoi多边形、必须做裁剪,那就要自己处理包围盒。这种场景适合要给最终成果出矢量图的情况,做法是把 C 中的 Inf 点替换为包围盒边界上对应方向的交点,核心是解一条过种子点和无穷方向的中垂线方程。代码略长,原理不复杂,很多网上开源的ClipVoronoi函数可以直接参考。建议初学版本用网格离散,省心、结果直观、还能顺便叠加负荷密度云图。

3.3 主体仿真循环与控制律落地

仿真主体用一个时间循环驱动。每个时间步做四件事:更新目标位置、计算围捕者控制律、积分更新位置、记录评价指标。核心代码如下:

dt = 0.01; T = 30; t = 0:dt:T; N = 6; P = P0; pT = pT0; kp = 8; kd = 2.5; vmax = 8; rc = 5; histDist = zeros(length(t), N); frame = 1; for t = 0:dt:T % 目标小幅随机游走 pT = pT + 0.5 * [0.02 * (rand-0.5); 0.02 * (rand-0.5)]; u = zeros(size(P)); for k = 1:N d = pT - P(:,k); nd = norm(d) + 1e-6; u(:,k) = u(:,k) + kp * d / nd; for j = 1:N if j ~= k djk = P(:,k) - P(:,j); ndjk = norm(djk) + 1e-6; if ndjk < 6 u(:,k) = u(:,k) + kd * djk / ndjk; end end end if norm(u(:,k)) > vmax u(:,k) = u(:,k) / norm(u(:,k)) * vmax; end end P = P + u * dt; histDist(frame, :) = sqrt(sum((P - pT).^2, 1)); if min(sqrt(sum((P - pT).^2, 1))) < rc disp('捕获成功'); break; end frame = frame + 1; end

这段代码故意写成教学版本,没有用任何向量化技巧,方便逐行对照控制律公式。实际仿真如果 N 上到50、时间步再细,建议把内存预分配、距离矩阵计算都向量化,不然会等到怀疑人生。上面代码里 sqrt(sum(...)) 是为了兼容旧版本Matlab;如果你用 R2019b 以后版本,直接 vecnorm(P - pT) 更方便。

3.4 三个评价指标怎么算

仿真不能只看动画,还要盯指标。我做复现时至少盯三个。

第一个是平均距离与最小距离。平均距离反映整个围捕群体的逼近速度,最小距离直接对应捕获判据。绘制两条曲线,能看到收敛过程是否光滑,如果曲线反复波动,多半是增益调得不对。第二个是角度覆盖缺口。对每个时刻,计算所有围捕者相对目标点的方位角,排序后取相邻夹角的最大值,这个值就是“包围圈最大缺口”。最大缺口在仿真中持续下降、并收敛到一个比较小的角度,说明围捕者确实是在分散包围而不是一窝蜂扎堆。计算代码:

angles = sort(atan2(P(2,:)-pT(2), P(1,:)-pT(1))); gaps = diff([angles, angles(1)+2*pi]); maxGap = max(gaps);

第三个是区域面积均衡度。统计每个Voronoi胞面积,用变异系数衡量。这个指标在配电网规划里意义很大——服务分区面积均衡,一般意味着各站负荷不会出现一半挤爆一半闲置。面积的分布在Matlab里可以用 polyarea 算每个胞的多边形面积,网格离散版本则直接统计每个分类的网格数乘以单格面积。

4. 落到业务场景:电动车充电与配电网规划的三种套法

4.1 充电站服务范围划分与容量校核

纯算法跑通了,接下来就是往真实业务上靠。第一个也是最自然的应用,是充电站服务范围划分。做法是把充电站位置当成Voronoi种子,把区域离散成网格,逐网格按最近距离归类到某个站的服务区。然后,把网格上的预测充电需求密度,比如从手机信令、车辆运行轨迹数据折算出来的千瓦数,叠加到分区图上,累加得到每个站的责任负荷。这一步做完,规划表上最缺的“站-区对应关系”就落地了。

注意别直接用等权Voronoi,因为每个站的充电桩数量和功率不一样。我习惯给不同站加权重,用加权距离 argmin( ||x - p_j|| / w_j ) 代替纯距离,w_j 取站点最大同时充电功率。这实际上得到了一个加权Voronoi,在大站和小站混布的场景下,得到的服务范围会更符合直觉:大站吸引更大区域,小站只承担周边小片。容量校核就直接了:某分区责任负荷若超过站点容量×同时率,就在规划界面高亮报红,提示需要扩容或新增站点。这一步把几何算法和电网容量计算完整咬合在一起。

4.2 移动充电车的动态围捕式调度

第二个场景升级一点,加入“动态”二字。移动充电车的好处是位置能变,但调度逻辑比固定站复杂得多。我的仿真做法是这样:给定若干台移动充电车在规划区内的初始位置,整个时段内随机生成多个紧急充电需求点,把每个需求点当作一个“目标猎物”。每个时间步,各充电车按当前Voronoi分区判断自己该管哪一个需求点,优先解决落在自己胞内的最紧迫请求;完成后,Voronoi图按最新位置和剩余需求重新划分。整个流程自动滚下去,就是对“围捕动态需求”的模拟。

实验做下来,我发现这个方案最出效果的一点是:当同时出现多个需求点时,围捕式调度的平均响应时间比“固定区域责任制”要快10%以上。原因也很朴素,固定分区时跨界的请求只能等隔壁处理,而动态Voronoi会实时把边界让给有余力的充电车,整体利用率自然更高。如果你要复现这个对比,记得保持相同的随机种子,否则需求点分布不一致,对比结果没有说服力。

4.3 分布式光伏与储能的网格化布局

最后一个是规划层面更宏观的用法:分布式光储的网格化布局。在主动配电网里,最理想的运行状态是“源荷就在一个区内消化”,不把功率穿过长距离馈线往回送。Voronoi图的用途,是把一个园区或镇域划分成若干源荷自平衡的网格。

具体步骤是:先以候选光储站点为种子生成分区;然后统计每个分区内的负荷量和光伏可接入容量,计算消纳率:

消纳率 = 分区内就地消耗的光伏电量 / 分区内光伏总发电量

如果某分区负荷太小、光太多,消纳率就低,需要往该分区移入更多负荷或者减少装机;反过来,负荷太重、光不足,就在该分区补光伏。通过反复挪动种子位置、重新划分,最终让各分区的消纳率落在一个均衡区间。这个过程我自己就是在Matlab里用网格离散的加权Voronoi加一层启发式搜索完成的。整个规划期的源网荷格局能画成一张很清晰的彩色分区图,汇报时说服力比一大堆表格强得多。

5. 复现过程中踩过的坑与排查记录

5.1 无穷顶点导致的分区画图崩溃

先说最常见也最容易劝退新手的。用 voronoin 得到的边缘胞包含 Inf 点,直接 patch 大概率得到一个覆盖全图的巨型多边形,看起来就像程序崩了。我遇到过不少次,甚至一开始还以为是边界点算错了,排查半天发现就是 Inf 没清理。

处理建议再次强调:如果只是为了看分区和叠加负荷,直接网格离散染色的方式做可视化,一分钟出图,不会遇到任何边界问题。只有当你确实需要输出真正的多边形拓扑,比如导给地理信息系统做进一步分析,再考虑写裁剪函数。裁剪时注意用种子的位置向量来控制无穷方向,不然替换出来的点可能完全不对。

5.2 区域频繁重划引发的调度抖动

动态场景里非常隐蔽的一个坑:目标点正好落在两个胞的公共边界附近时,由于数值误差,它可能每个时间步在“属于A”和“属于B”之间反复横跳,调度指令就会高频抖动。放在规划里影响不大,但在移动充电车实时调度仿真里就会导致车辆忽左忽右、路线图看起来十分诡异。

解决思路是引入滞回带。当目标与当前归属种子的距离,比其他种子距离小出某个阈值,比如边界距离的5%时,就维持当前归属,不轻易切换。这个思路和电机控制里防止继电器抖动的滞回比较是同一套路,成本极低、效果立竿见影。改完之后再回看仿真轨迹,车辆不再出现频繁换向的锯齿纹路。

5.3 除零、计算效率和控制增益的隐蔽问题

剩下三个问题经常一起出现,就放一起说,它们分别坑在了不同环节。

除零问题最简单:当围捕者越过目标点或正好重合时,距离为0,归一化方向向量会变成 NaN,之后整条仿真曲线全部废掉。在所有 norm 计算里加 1e-6 是标准做法,别嫌丑,管用。

效率问题:每时间步重建 Delaunay 三角剖分,N 在50以下感受不明显;N 到200、仿真时长再拉长,就卡得让人烦躁。规划类场景完全没必要每步重剖——只有当种子点或目标移动超过一定距离阈值时才重新分区,其他时间使用上一轮分区,能提速非常多。实时调度类仿真则建议降低可视化刷新频率,控制算法本身很轻,瓶颈几乎都在画图。

控制增益调参:我发现在做30秒短时仿真时,kp 取 vmax / 初始平均距离 的1.5到2倍通常是个不错的起点;kd 从 kp 的1/4开始往上加,每次只加20%,观察角度缺口曲线是否出现抖动,出现就回退。按这个套路,大部分场景半小时能调出一组能用的参数。

最后说点复现之外的感受。这套算法真正值得留在你工具箱里的地方,不是Voronoi图这个年代久远的几何概念,而是它用一个极轻的分布式规则,同时解决了空间覆盖和动态追击两类问题——这恰恰是主动配电网在“源随荷动”道路上最需要的性格。

但也得泼一盆冷水:纯几何结果离能直接指导工程还有距离。电压约束、容量上限、充电时间窗、道路网络这些现实限制,一个都不能少。我现在的做法是三步走:先跑纯几何算例验证算法本身,再把每个分区映射到潮流节点上,加上容量和电压约束重新迭代,最后才进业务场景验证。照着这条路走下来,踩坑最少、改起来也最快。希望对正在复现这套算法的你有帮助。

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

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

立即咨询