简介:面向多智能体系统与网络一致性研究的MATLAB代码包,适合自动化、控制工程等领域的研究生和科研人员,用于开展一致性协议设计、动态网络建模与仿真验证。压缩包共5个文件,含4个m脚本和1个Simulink模型(mdl),代码涵盖智能体动力学模型、一致性协议与切换网络/权重等典型模块,Simulink模型可直观搭建和观察多智能体协同过程。包体仅约17KB,结构轻量,便于快速阅读和二次开发。资源已有212人学习,可作为多智能体网络一致性课题的入门参考。学习者可借此掌握Laplacian共识、平均共识等常见算法的MATLAB实现思路,理解不同网络拓扑和切换条件对收敛性能的影响,并能在模型基础上扩展研究异构智能体、动态网络与鲁棒控制等问题,为后续理论分析和实验对比提供可运行的代码起点。
1. 多智能体网络一致性研究的MATLAB代码,先读懂再跑通
拿到一组多智能体网络一致性研究的MATLAB代码,很多人习惯先点运行、看曲线重合就收工。但一致性研究里真正拉开差距的,不是“最后一致了没有”,而是Laplacian矩阵怎么构造、权重按什么规则给、拓扑切换时网络是否始终保持联合连通、仿真步长是否踩在稳定性边界内。这组代码正好覆盖了这几个关键点:square_3D.m处理三维空间中的位置一致性,switching.m研究动态拓扑下的一致性收敛,weight.m负责从邻接矩阵生成权重矩阵和Laplacian矩阵,model.m提供连续时间的系统模型,untitled.mdl则是Simulink层面的图形化仿真层。如果你正在复现多智能体一致性相关论文,或者要把静态一致性算法扩展到编队控制、分布式观测器,拆开这套代码比直接跑通它更有价值。下一章先从最核心的weight.m说起,因为后面所有仿真都要依赖它生成的矩阵。
2. 从Laplacian到权重矩阵:一致性协议背后的线性代数
2.1 一致性协议在连续时间下其实只有一个方程
多智能体网络一致性的标准形式并不复杂。对第i个智能体,状态x_i的更新只依赖邻居集合N_i和通信权重a_ij:
dx_i/dt = sum_{j in N_i} a_ij (x_j - x_i)
写成矩阵形式就是dx/dt = -L x,其中L是图的Laplacian矩阵。这个方程的物理意义很直接:每个智能体都在被邻居“拉向”平均状态。只要网络连通,最终所有状态都会收敛到初始状态的加权平均;如果权重是对称且随机的,则收敛到算术平均。多智能体一致性的所有变体,包括编队、包围、分布式优化,基本都是在这个标准方程上叠加了参考项或目标项。所以先理解L的结构,再看具体代码才不会迷路。
2.2 weight.m 的常规实现:邻接矩阵、度矩阵、Laplacian
在MATLAB代码里,weight.m通常是整个仿真链路中最容易被低估的文件。它的输入一般是邻接矩阵Adj,输出则至少包含权重矩阵A和Laplacian矩阵L。一种常见做法是写成下面的独立函数:
% weight.m - 构建一致性仿真所需的权重矩阵和Laplacian矩阵 function [A, D, L] = weight(Adj) % Adj: 邻接矩阵,Adj(i,j) > 0 表示智能体 i 与 j 之间有通信边 if nargin < 1 % 默认使用一个 4 节点的四边形拓扑,便于快速测试 Adj = [0 1 0 1; 1 0 1 0; 0 1 0 1; 1 0 1 0]; end A = Adj; % 权重矩阵,这里先直接继承邻接矩阵 Deg = sum(A, 2); % 按行求和,得到每个节点的度 D = diag(Deg); % 度矩阵,对角阵 L = D - A; % 组合拉普拉斯矩阵 end这段代码的逻辑分四步:第一步读取邻接矩阵;第二步按行求和得到每个智能体邻居数或边权总和;第三步把度放到对角阵上;第四步用D - A生成Laplacian。参数上需要注意的是,如果Adj里保存的不是0/1而是实际通信强度,那么sum(A,2)得到的就是加权度,L变成了加权Laplacian,这种形式在分布式控制中同样常见。实际写代码时经常有人直接把A = Adj,然后忘记检查对角线是否为零。自环会污染Laplacian的零特征值,导致矩阵性质偏离理论假设。
2.3 权重选型决定收敛速度:0-1权重与Metropolis权重
weight.m里直接使用0/1邻接矩阵是最省事的做法,但收敛速度往往不是最优的。对于无向连通图,可以选用Metropolis权重,它只依赖局部度数信息,却能让收敛速度接近全局最优:
w_ij = 1 / ( max(d_i, d_j) + 1 )
对角线权重为w_ii = 1 - sum_{j in N_i} w_ij。这种权重的好处是行和始终为1,离散迭代时稳定性边界更宽,且不需要中央节点掌握全局拓扑。如果weight.m要做成更通用的版本,可以在文件里加一个mode参数,按需返回0/1权重或Metropolis权重。下面的表总结了几种常见选择,方便后面调参时对照:
| 权重类型 | 公式 | 适用场景 | 注意点 |
|---|---|---|---|
| 0/1邻接 | A = Adj | 理论验证、拓扑结构研究 | 收敛速度受最大度影响明显 |
| Metropolis权重 | w_ij = 1/(max(d_i,d_j)+1) | 大规模网络、分布式实现 | 需要邻居度信息,行和恒为1 |
| 平均一致性权重 | w_ij = 1/n | 完全图或小规模系统 | 依赖全局节点数,不满足分布式约束 |
| 随机游走Laplacian | L_rw = I - D^{-1}A | 有向图相关研究 | 收敛值不再是算术平均 |
实际项目中我发现很多问题不是协议不对,而是权重矩阵选得太随意。比如稀疏图中用0/1权重,离散步长稍微调大一点就发散;换用Metropolis权重后,同样的步长还能稳定收敛。因此建议在weight.m里保留两种权重生成路径,做参数实验时结构更清晰。
3. 拆解四个核心文件:switching、square_3D、model与Simulink模型
3.1 switching.m:随机切换拓扑一致性仿真的实现
switching.m针对的是动态网络场景,即智能体之间的通信边会随时间变化。这种模型常用于无人机编队在存在通信干扰或节点移动时的分析。常见的随机切换实现思路是:每个时间步以概率p生成无向边,再运行一步离散时间一致性。一个可运行的骨架如下:
% switching.m - 随机切换拓扑下的一致性仿真 n = 6; % 智能体数量 x0 = randn(n, 1); % 初始状态,随机但不失一般性 x = x0; p = 0.7; % 每条边存在的概率 dt = 0.02; % 离散步长,需要满足稳定性条件 T = 300; % 总迭代步数 r = zeros(T, 1); for k = 1:T Adj = triu(rand(n) < p, 1); % 只采样上三角,避免重复判断同一条边 Adj = Adj + Adj.'; % 对称化,得到无向图 [A, ~, L] = weight(Adj); % 复用 weight.m 生成 Laplacian x = x - dt * L * x; % 离散时间共识协议 r(k) = max(x) - min(x); % 用状态差衡量一致性程度 end plot(r); xlabel('迭代步数'); ylabel('max(x)-min(x)');这段代码的关键参数有三个:p控制网络密度,dt必须满足离散稳定性条件,T要足够观察收敛过程。原理解释如下:rand(n) < p生成逻辑矩阵,triu(...,1)取出严格上三角部分,加转置后得到对称邻接矩阵。这样每次迭代拓扑都可能不同,研究的是随机切换到任意图时的平均收敛行为。另一个实用细节是,当p较小时,网络可能出现瞬时孤立节点,此时一次迭代相当于部分智能体没有通信。需要确认切换序列的联合连通性,否则后续所有统计指标都不具备理论意义。
3.2 square_3D.m:三维空间中的编队一致性
square_3D.m的字面意思是“三维方形”,实际用途是把二维方形拓扑拓展到三维坐标一致性。每个智能体的状态不再是单一标量,而是三维位置坐标。由于拉普拉斯矩阵只作用在智能体维度,不作用在坐标维度,因此可以把所有智能体的坐标组织成n x 3矩阵,直接用L左乘即可,三个坐标通道相互解耦。
% square_3D.m - 三维 square 拓扑下的位置一致性 pos0 = [0 0 0; 1 0 0; 1 1 0; 0 1 0] + randn(4,3) * 0.05; Adj = [0 1 0 1; 1 0 1 0; 0 1 0 1; 1 0 1 0]; [A, D, L] = weight(Adj); dt = 0.05; T = 200; pos = pos0; for k = 1:T pos = pos - dt * L * pos; % 三轴同时更新,等价于对每列执行一致性协议 end plot3(pos0(:,1), pos0(:,2), pos0(:,3), 'o', ... pos(:,1), pos(:,2), pos(:,3), 'x'); legend('初始位置', '一致后位置');这里pos - dt * L * pos本质上是(I - dt*L)*pos,相当于三个独立的标量一致性子系统并联。参数dt取0.05是保守值,因为square拓扑最大度是2,满足dt < 1/d_max。如果把pos换成机器人或无人机的期望位置,再叠加一个参考队形,就能从一致性变成编队控制,这个细节在最后一章会展开。
3.3 model.m 与 untitled.mdl 的协作关系
model.m通常是连续时间模型的右端函数,供ode45或Simulink调用。最简形式是:
% model.m - 连续时间一致性动态模型 function dx = model(t, x, L) dx = -L * x; end调用它时只需要构造好拓扑和初始状态:
[t, x] = ode45(@(t, x) model(t, x, L), [0 20], x0);这种方式适合需要精确模拟连续系统、分析特征值和收敛速率的研究。而untitled.mdl是基于Simulink的图形化模型,常见做法是在模型里放一个“MATLAB Function”模块,或者用S-Function调用model.m,再用Integrator模块积分dx/dt。Simulink的优势是直观,可以在Scope里实时观察每个智能体状态。
下表整理了这套代码各个文件的角色,方便对照自己的仿真需求:
| 文件名 | 主要作用 | 输入 | 输出/结果 |
|---|---|---|---|
weight.m | 生成权重矩阵和Laplacian | 邻接矩阵 | A、L、D |
square_3D.m | 三维拓扑下的位置一致性验证 | 初始坐标、拓扑 | 一致性收敛结果 |
switching.m | 动态切换拓扑的一致性仿真 | 边概率p、步长dt | 一致性误差曲线 |
model.m | 连续时间模型右端函数 | 时间t、状态x | 状态导数dx |
untitled.mdl | Simulink图形化一致性模型 | 外部输入/初始值 | 可视化状态曲线 |
如果打开untitled.mdl时遇到MATLAB提示“模型是旧版或受保护格式”,不要直接保存覆盖。先另存为新版模型,或者在旧版本MATLAB中打开检查模块层级。新版Simulink对老模型一般能自动升级,但升级后模型求解器配置可能变化,需要重新确认步长和求解器类型。
4. 参数调不收敛?从特征值到仿真排错
4.1 收敛速度由第二小特征值决定,不是看最终数值
多智能体一致性的收敛速度由Laplacian的第二小特征值lambda_2决定,也叫Fiedler特征值。网络连通时lambda_2 > 0,且值越大收敛越快;网络不连通时lambda_2 = 0,系统会分裂成多个子群,各自收敛到不同的值。实际MATLAB计算时,由于浮点误差,连通的图也可能算出接近零的特征值,因此需要设置阈值过滤。
% 计算给定拓扑的Laplacian特征值,并提取第二小特征值 [A, D, L] = weight(Adj); e = eig(L); e = sort(e); lambda2 = e(2); % 若网络连通,通常 lambda2 > 1e-8 if lambda2 < 1e-8 warning('拓扑可能不连通,请检查邻接矩阵'); end为什么这里的lambda_2那么重要?因为连续时间共识的解为x(t) = exp(-Lt)x(0),沿着除全1向量以外的特征方向,衰减速率由相应特征值的实部决定,最慢的非零模式就是lambda_2。离散时间迭代x(k+1) = (I - dt L) x(k)的谱半径条件为dt不大于2/lambda_max,工程上常用dt < 1/max(D)作为保守上限。如果你发现收敛曲线几乎不降,第一步就是算一下lambda_2是不是太小;如果lambda_2正常而曲线发散,问题多半出在dt。
4.2 常见故障表与修复方式
实际跑这套代码时,最常见的现象可以归纳成下面这张表:
| 现象 | 可能原因 | 检查方法 | 修复方式 |
|---|---|---|---|
| 状态曲线发散 | dt超出稳定性边界 | 计算max(abs(eig(I-dt*L)))是否大于1 | 调小dt,或改用Metropolis权重 |
| 多个智能体各自成群收敛 | 拓扑不连通 | 用graph(Adj)查看连通分量 | 增加边概率p,或检查邻接矩阵是否正确 |
| 收敛到非一致值 | 有向图或行和不为1 | 检查sum(A,2)是否等于sum(A,1) | 对称化权重矩阵 |
| 残差曲线出现锯齿 | 切换拓扑中出现了孤立节点 | 打印每次迭代的度矩阵 | 添加连通性约束或拒绝意外拓扑 |
| Simulink仿真时间极慢 | 求解器用了变步长且容许误差过小 | 查看求解器配置 | 改为固定步长,步长设为dt |
这里的检查方法比直接看运行结果更有用。比如“收敛到非一致值”这个问题,很多人反复调dt都无效,原因就是Adj构造时没有对称化,或者权重矩阵没有归一化。用sum(A,2) == sum(A,1).'这样的判断,能在一分钟内排除绝大多数权重型错误。
4.3 把切换拓扑改造成可重复的参数扫描脚本
switching.m里每次运行都会生成不同随机拓扑,因此无法直接对比不同参数的效果。我一般会把随机种子固定,并把切换过程封装成循环,对多个p值批量仿真。下面这个脚本可以作为改造模板:
% 切换拓扑一致性:不同边概率下的收敛速度对比 rng(42); x0 = randn(6, 1); ps = 0.3:0.1:0.9; res = []; dt = 0.02; T = 500; for pi = 1:length(ps) p = ps(pi); x = x0; r = zeros(T, 1); for k = 1:T Adj = triu(rand(6) < p, 1); Adj = Adj + Adj.'; [A, ~, L] = weight(Adj); x = x - dt * L * x; r(k) = max(x) - min(x); end res(:, pi) = r; end semilogy(res); xlabel('迭代步数'); ylabel('max(x)-min(x)'); legend(cellstr(num2str(ps', 'p=%.1f')));脚本中的rng(42)让随机拓扑序列可复现,这是实验对比的前提。semilogy能把指数收敛画成直线,便于观察收敛速率变化。这里要注意,p从0.3到0.9变化时,随机图不连通的概率会显著不同,因此最终收敛速度的差异不只是权重问题,还包含拓扑连通性因素。理论上这类随机切换一致性需要满足联合连通性,仿真结果才稳定。
5. 从仿真到应用:把一致性代码改造成无人机编队和观测器
5.1 封装成不依赖工作区的通用入口
做研究时脚本越短越好,做项目时函数越独立越好。可以把前面几章的内容封装成一个独立的共识仿真函数,输入邻接矩阵、初始状态、增益和仿真时长,输出完整状态轨迹和时间轴:
function [t, x] = run_consensus(Adj, x0, c, T) n = size(Adj, 1); [A, ~, L] = weight(Adj); d_max = max(sum(A, 2)); dt = 0.9 / d_max; % 稳定性边界内留余量 Nt = ceil(T / dt); x = zeros(Nt, n); x(1, :) = x0(:).'; for k = 1:Nt-1 x(k+1, :) = x(k, :) - c * dt * (L * x(k, :)')'; end t = (0:Nt-1) * dt; end这个函数的参数说明很关键:c是协议增益,理论取值范围是0 < c < 2/lambda_max,实际取1.0附近比较安全。dt不是固定值,而是根据最大度自动计算,避免换拓扑后忘记调整步长。返回值x是Nt x n矩阵,每一行是一个时间切片,方便后续做动画或统计。
5.2 从一致到编队:用参考轨迹偏置拉普拉斯项
一致性让所有智能体收敛到同一个位置,再加上参考目标就变成编队控制。假设每个智能体的期望相对位置是ref,把共识误差改成pos - ref即可:
% 编队一致性:所有智能体收敛到以 ref 为偏移的队形 ref = [0 0 0; 1 0 0; 1 1 0; 0 1 0]; % 目标队形 pos = pos0; for k = 1:T pos = pos - dt * L * (pos - ref); % 每列独立执行编队共识 end这里的原理很直观:让智能体之间的位置差收敛到目标队形差,而不是收敛到零。代码中的L * (pos - ref)相当于把“当前位置相对于期望位置的偏差”作为驱动项。这个技巧在无人机编队和机器人集群里非常常见,且不需要改动Laplacian结构,只需要在源码上做一个减法。
5.3 用semilogy和graph对象验证收敛过程
最后给一个判断系统是否真正指数收敛的验证技巧。连续时间一致性理论上是负指数衰减,因此在semilogy坐标下,残差max(x)-min(x)应近似为直线。如果曲线快速下降后变成水平线,说明数值精度已经到底,再迭代也没有意义;如果曲线拐弯或者出现波动,说明拓扑切换引入了额外扰动,或者步长过大。此时用graph对象全局观察拓扑会非常直观:
g = graph(Adj); lambda2 = eigs(L, 2, 'smallestreal'); plot(g, 'Layout', 'force'); title(sprintf('lambda_2 = %.3f', lambda2));eigs(L,2,'smallestreal')是计算最小特征值的另一种方式,在大规模网络上比eig更高效。把图绘制出来,再对照特征值,就能定位“不收敛是因为拓扑断裂”还是“收敛速度被瓶颈节点拖慢”。调试到这里,一致性代码基本上就脱离“例程”阶段,可以当作分布式控制项目的基础模块继续扩展了。
本文还有配套的精品资源,点击获取