简介:这套MATLAB代码面向多智能体网络一致性研究者与学习者,重点示范了分布式一致性协议的仿真实现。压缩包共5个文件,其中4个为.m脚本、1个为Simulink模型文件,整体约17KB,轻量易用。脚本覆盖智能体模型构建、一致性权重分配、网络拓扑切换等关键环节,可分别用于生成三维网络拓扑、设计加权共识矩阵、模拟动态拓扑变化;Simulink模型提供可视化仿真流程,便于观察各智能体状态随迭代逐步收敛的过程。已有213人学习浏览,适合作为课堂演示、课程设计或论文复现的起步代码。通过运行和修改这些示例,能够直观理解Laplacian共识算法、平均共识等经典协议的工作原理,比较不同初始条件与网络结构对收敛速度、稳定性的影响;同时,代码结构清晰、易于扩展,可灵活调整智能体规模与通信权重,为后续研究异构智能体协同、鲁棒一致性控制等方向提供实验基础。
1. 多智能体网络一致性研究在MATLAB里到底是什么:先跑通再谈理论
很多人第一次搜“多智能体网络一致性 matlab 代码”,想找的是一个能直接画出漂亮收敛曲线的脚本,结果搜出来的全是论文公式——拉普拉斯矩阵、代数连通度、线性矩阵不等式,看十分钟就劝退。这个研究方向用大白话讲就一句话:一群智能体,每个只知道自己和邻居的状态,靠局部交互让全网状态收敛到同一个值。它能解决无人机编队同步、分布式时钟同步、多机器人位置对齐这类实际问题,适合正在做毕业设计、写论文,或者想把协议往自己工程里迁的从业者。这篇笔记直接从最小可跑通的示例代码开始,把图怎么建、协议怎么写、参数怎么定、坑在哪里一次说清。
2. 从拉普拉斯矩阵到一阶一致性:最小可跑通的仿真脚本
2.1 图、邻接矩阵与拉普拉斯矩阵的MATLAB表示
图是所有协议的地基。一个无向图由 n 个节点和一组边组成,在 MATLAB 里最直接的表征是邻接矩阵 A:节点 i 和 j 之间有通信,A(i,j)=A(j,i)=1,否则为 0。度矩阵 D 是对角矩阵,对角线上放每个节点的邻居个数,即 sum(A,2)。拉普拉斯矩阵定义为 L = D - A,它在一阶一致性协议里的地位类似电路里的导纳矩阵:状态差通过它从高电势流向低电势。
% 构建5节点无向图:1-2-3-4-5-1 再加一条 2-4 边 n = 5; A = zeros(n); edges = [1 2; 2 3; 3 4; 4 5; 5 1; 2 4]; for k = 1:size(edges,1) i = edges(k,1); j = edges(k,2); A(i,j) = 1; A(j,i) = 1; % 无向图必须对称赋值 end D = diag(sum(A,2)); % 度矩阵:每行求和放到对角线 L = D - A; % 拉普拉斯矩阵这段代码就是从空邻接矩阵出发,把每条无向边填成对称的 1;sum(A,2) 对每行求和得到度,D 由 diag() 变成对角阵。唯一要改的参数是 n 和 edges,节点变多时只要在 edges 里追加新连接。注意 A(j,i)=1 不能漏,漏掉就变成有向图,一致性收敛性质会完全不同。
L 的性质直接决定仿真能不能跑出理想结果:L 是半正定矩阵,零特征值的个数等于网络中连通分量的个数。单个连通图只有一个零特征值,这就是全网收敛的前提。你在 MATLAB 里跑 eig(L) 会看到 0、正数和正数,那个 0 对应的是“所有状态相等”的方向。
2.2 一阶积分器模型与一致性协议的实现
模型写出来就是 x_i_dot = sum_j a_ij (x_j - x_i),每个节点被邻居朝平均位置拉。连续微分方程在 MATLAB 里做仿真必须离散化,这正是标题里“多智能体网络一致性研究”和 MATLAB 仿真之间最常见的 gap:离散时间系统在 MATLAB 里就是一个 for 循环,状态按步长 dt 做差分更新。
% 一阶积分器一致性:离散差分更新 x = rand(n,1)*10; % n×1 列向量,初值随机分布 K = 0.5; % 协议增益,论文里常写作 c 或 gamma dt = 0.1; % 离散化步长(秒) Nstep = 300; % 迭代步数 history = zeros(n, Nstep+1); history(:,1) = x; for t = 1:Nstep % L*x 等效于每个节点累加邻居差 sum_j a_ij*(x_j-x_i) x = x - dt * K * (L * x); history(:,t+1) = x; end plot(0:Nstep, history', 'LineWidth', 1) xlabel('迭代步 k'); ylabel('状态值 x_i');核心就一行:x = x - dtK(Lx)。右边的 Lx 是“朝向邻居平均状态移动”的修正量,左边的 x 是更新后的新状态。用矩阵乘法而不是双层 for 循环去算邻居累加,节点数上千时也能一次算完,这是 MATLAB 里最值得养成习惯的写法。
参数上,dt 和 K 的乘积直接决定稳定性。乘积越小越稳,但收敛也慢;设太大则振荡甚至发散。定量地讲,稳定条件要求 dt*K 小于 2/λ_max(L),λ_max 是 L 的最大特征值,用 eigs(L,1,'largestabs') 就能算出来。上面示例里 K=0.5、dt=0.1,乘积 0.05,远小于边界,所以曲线很平滑。
2.3 平均一致性与收敛速度:特征值和步长的配合
一阶积分器协议收敛到的终值,是所有节点初值的平均值 mean(x0),所以也叫平均一致性。终值不受 K 和 dt 影响,K 和 dt 只影响收敛速度和稳定性。收敛快慢在理论上由 L 的第二小特征值 λ2 决定,也叫代数连通度,它越大,网络里信息流通的“瓶颈”越宽,收敛越快。
实操中我分两步调参:先固定 K,用 e = eig(L) 取 e(2) 得到 λ2,把步长设成 dt = 0.1/λ2 作为起点;然后看收敛曲线,想更快就把 K 往大调,每次加 30% 左右,一旦看到振荡就退回上一个值。注意这里存在一个最优 K,超过之后收敛反而变慢,不要以为 K 越大越好。
还有个容易忽略的点:论文里的仿真图很多是连续时间微分方程直接解出来的,你在 MATLAB 里复现就会碰到离散化问题。离散化不仅多出一个 dt 要调,还让你能看到状态一步一步被邻居“拉”到一起的过程,这对理解协议本身反而有帮助。至于 dt 取太大导致的翻车现场,第 4 章专门讲。
3. 加权、领导跟随与切换拓扑:把一致性协议改向真实网络
单一权重、固定拓扑、所有节点平等的假设在工程里几乎不成立。这一章把协议往三个方向扩展:边带权重、领导者信号、拓扑切换。每个扩展都对应明确的代码改动点,不涉及新理论框架。
3.1 带权重的图与权重矩阵设计
带权重的场景很常见:通信质量不同,或者距离近的该多信一点。权重矩阵 W 代替 A,拉普拉斯矩阵改为 L_w = D_w - W,其中 D_w 是带权度矩阵,对角线放 sum(W,2)。
权重怎么给?常见做法是“距离越近权重越大”,直接把相对距离作为权重函数,比如 W(i,j) = 1/(1+d_ij^2)。先给节点随机位置,再按公式填权:
% 生成带权重的拉普拉斯矩阵 pos = randn(n,2); % 节点在二维平面上的位置 W = zeros(n); for i = 1:n for j = i+1:n d = norm(pos(i,:) - pos(j,:)); % 欧氏距离 W(i,j) = 1/(1 + d^2); % 距离越小权重越大 W(j,i) = W(i,j); % 保持对称 end end Dw = diag(sum(W,2)); Lw = Dw - W; % 带权拉普拉斯矩阵这个权重公式把“通信质量随距离衰减”映射成数值:距离无穷大时权重趋近 0,距离为 0 时权重为 1。使用的时候,直接把第 2 章的更新式里的 L 换成 Lw 即可,协议本身一行不用动。
参数上真正敏感的是对称性。对称权重保持平均一致性,收敛终值还是 mean(x0);非对称权重会让终值偏移,偏移量由左特征向量决定,第 4.4 节有对应排查方法。想让收敛更快就把分母里的 1 调小,比如 1/(1+0.1*d^2),权重整体变大,代数连通度变大,代价是远距离节点也有明显权重,不一定符合真实通信约束。
3.2 领导跟随一致性:给网络加一个“参考信号”
有一类问题叫领导跟随一致性,也叫牵制控制。它解决的不是让全网自己商量出平均值,而是让所有节点跟一个给定的参考信号。最常见做法是选一个或几个节点作为牵制节点,给它们额外加一项“拉向领导者”的力:
x_i(k+1) = x_i(k) + dt * [ sum_j a_ij(x_j - x_i) + b_i (x_L(k) - x_i(k)) ]
其中 b 是牵制向量,b_i=1 表示节点 i 能看到领导者,否则为 0。领导者状态 x_L 可以是常值,也可以是斜坡。下面是斜坡参考信号的例子:
% 领导跟随一致性:领导者按斜坡上升,节点1和5可接收其状态 b = [1; 0; 0; 0; 1]; % 牵制节点标记 x = rand(n,1)*2; % 跟随者初值 xL0 = 0; vL = 0.05; % 领导者初始状态和每步增量 dt = 0.1; Kc = 0.6; hist = zeros(n, Nstep+1); hist(:,1) = x; for t = 1:Nstep xL = xL0 + vL * (t-1); % 领导者当前参考值 neighbor_term = -L * x; % 邻居耦合项 pin_term = b .* (xL - x); % 牵制项,只有b=1的节点有 x = x + dt * (Kc * neighbor_term + pin_term); hist(:,t+1) = x; end这里用到向量化写法。b.*(xL-x) 只有 b=1 的节点会被赋值,标量 xL 与列向量相减在 MATLAB 里自动广播,即使 n 很大、牵制节点只有几个,也不会报错。
牵制节点的选择会影响收敛速度。直觉上选度数大的节点当领导者“传话筒”,信息扩散更快;选孤立节点当牵制节点,整个网络要绕一大圈才能感知参考信号。参数 vL 定义参考速度,vL 越大跟踪滞后越明显;Kc 控制内部耦合强度,牵制项通常不另设增益。这里有个给初学者挖的坑:一阶协议对常值参考能做到无稳态误差,对斜坡参考却会存在固定误差,原因和解决见第 4.5 节。
3.3 切换拓扑:在一个循环里换拉普拉斯矩阵
编队里通信中断是常态,拓扑随时间变化。做切换拓扑仿真的一种常见做法是:事先定义一个拉普拉斯矩阵序列,在循环里按时段选择当前矩阵。周期性切换是其中最简单的:
% 周期切换拓扑:三种拓扑每50步切换一次 L_list = {L1, L2, L3}; % 三个不同拓扑的拉普拉斯矩阵 period = 50; for t = 1:Nstep idx = mod(ceil(t / period) - 1, 3) + 1; % 当前是第几个拓扑 L_now = L_list{idx}; x = x - dt * K * (L_now * x); end这个写法的好处是只改一张索引表就能控制切换顺序和周期。只要每个时刻的图是连通的,或者所有时刻的图合并后连通的(联合连通),一致性依然能保证。
参数上注意 period 是迭代步数。周期太短,拓扑频繁切换,收敛会变慢,极端时看到状态来回摆动;周期太长则退化成固定拓扑。调试时我先跑三个固定拓扑各自收敛,再跑切换拓扑,对比收敛终值——切换不改变平均一致性的终值,只影响收敛速度。如果终值和固定拓扑跑了不一样,回去检查是不是切换过程里图断开了。
4. 一致性仿真的五个翻车点:避坑与排查
很多仿真跑不出预期结果,不是协议不对,而是离散化、矩阵形状、连通性这些细节在捣乱。这一章全部按“现象 → 原因 → 解决”来写,都是我实际调过的场景。
4.1 步长设置不当:协议照着论文抄,曲线却发散
现象:公式、增益、矩阵全对,仿真第一步状态就冲到 1e10,或者直接出现 NaN。
原因:连续时间协议离散化后的稳定条件大约是 dt*K < 2/λ_max(L)。很多论文根本不提离散化,直接画微分方程曲线,换成差分循环的人没注意步长上限,乘积一超就翻车。
解决:先用 e = eig(L) 算 e_max = max(e),把 dt 初始设为 1/(K*e_max),再往下减一半当安全值。记住增 K 和增 dt 对稳定性的影响是等价的,看曲线发散先查这个乘积,别去动协议结构。这是最典型的“靠玄学调参不如算一个特征值”的场景。
4.2 图不连通:曲线分片收敛,从不一致
现象:n 条曲线最终分成若干组,每组内部收敛,组与组之间各奔东西。
原因:拉普拉斯矩阵的零特征值个数大于 1。每个连通分量对应一个零特征值,分量之间没有信息交换,自然无法全网一致。很多情况下是邻接矩阵输入时漏了对称边,无向图变成有向图,照样分片收敛。
解决:算一下 rank(L),小于 n-1 就是有多个连通分量,给图补边就行。快速检查法是数 eig(L) 里小于 1e-10 的个数,这个数就是连通分量数。跑之前看一眼,省得后面对着曲线猜半天。
4.3 向量方向不一致:L*x 报维度错,或者更新完维度变成 n×n
现象:明明写的是 L*x,报错说维度不一致;或者不报错,但更新完结果里出现了矩阵,画图时冒出一堆奇怪的线。
原因:MATLAB 里 1×n 行向量和 n×1 列向量做减法会触发广播,结果变成 n×n 矩阵。初值如果写 x = rand(1,n),就埋了雷。
解决:所有状态统一用列向量,脚本开头写 x = x(:);xL 与 x 做减法时确认 xL 是标量。我在脚本里加一条断言 assert(size(L,2)==length(x)),一运行就能定位。这类问题不值得花时间盯,早断言早省事。
4.4 收敛终值不是初值平均:非对称权重导致的结果偏移
现象:所有曲线都收得很干净,但终值明显不等于 mean(x0),跟手算的平均值有固定偏差。
原因:平均一致性成立的前提是权重对称,也就是 L 的行和与列和同时为零。权重不对称时,协议收敛到加权平均值,加权系数由 L 的左零特征向量 w = null(L') 决定。
解决:检查权重矩阵是否对称。想保留非对称权重又想平均一致,就做对称化 W = (W+W')/2 再重构 L。想精确算终值,用 w'*x0/sum(w) 验证,别用 mean。真实工程里非对称权重往往来自单向通信,这时候终值偏离平均值是正常的,不代表算法错了。
4.5 领导跟随对斜坡信号存在稳态误差
现象:领导者是常值时,跟随者能精确收敛到参考值;领导者按斜坡变化,跟随者总是落后一个固定差值。
原因:一阶协议只有状态反馈,没有参考信号的导数(速度)信息,对斜坡输入只有比例跟踪,存在静态误差。这是协议结构决定的,不是参数问题。
解决:改成带参考速度项的协议:u_i = b_i*(xL - x_i) + b_i*vL,vL 是领导者的速度。实现时在更新式里加 b.vLdt 即可。常值参考时 vL=0,原协议是特例。这个改法要求领导者速度对牵制节点可见,如果速度不可测,就只能接受稳态误差,或改控制器结构加积分项。
5. 二阶系统与时滞:协议升级的两个关键改动
一阶积分器模型只是起点。真实被控对象往往要同时管位置和速度,比如无人机和移动机器人,这是二阶一致性。另一个绕不开的问题是通信时滞——智能体拿到邻居数据时,数据已经是 τ 步之前的状态了,这个延迟会影响稳定性。
5.1 二阶系统一致性:位置与速度双通道
二阶系统模型是 x_ddot_i = u_i,一致性协议要同时驱动位置差和速度差:
u_i = -Kp * sum_j a_ij (x_j - x_i) - Kd * sum_j a_ij (v_j - v_i)
离散化要分两步更新:先更新速度,再用新速度更新位置,不能一步到位。
% 二阶一致性:位置-速度双通道更新 Kp = 0.4; Kd = 0.9; dt = 0.05; x = rand(n,1)*5; v = zeros(n,1); hist_x = zeros(n, Nstep+1); hist_x(:,1) = x; for t = 1:Nstep % 邻居位置差 + 邻居速度差合成控制量 u = -Kp*(L*x) - Kd*(L*v); v = v + dt*u; % 先更新速度 x = x + dt*v; % 再更新位置 hist_x(:,t+1) = x; end整体就是欧拉积分,关键是 v 的更新用当前 u,x 的更新用更新后的新 v,这比完全显式欧拉更稳。把状态扩展成 [x; v] 的话,系统矩阵变成 [0 I; -KpL -KdL],一次 eig 就能算出所有特征值,稳定边界一目了然。
Kp 和 Kd 是配对才有效的两个参数。Kp 太小位置收敛慢;Kd 太小速度通道衰减不够,曲线会震荡;Kp 太大会让速度更新剧烈,直接把 v 打飞。经验起点是 Kp 取 0.2~0.5,Kd 取 0.8~1.2,再根据曲线微调。要严格判稳,把系统矩阵的特征值实部拉出来看,全部为负才是连续意义下稳定。
5.2 通信时滞:给仿真加一个环形缓存
通信延迟在真实系统里不可避免。MATLAB 仿真的优势在于时滞可以很自然地模拟:只需要用 τ 步之前的状态来计算当前控制量。这里用一个环形缓存存最近的状态,避免每步 shift 的开销:
% 固定时滞 tau:用历史状态计算一致性项 tau = 3; % 通信时滞(步) delay_buffer = repmat(x0, 1, tau+1); % 每列对应一个历史时刻 for t = 1:Nstep x_delay = delay_buffer(:, mod(t, tau+1) + 1); x = x - dt * K * (L * x_delay); % 用延迟状态更新 delay_buffer(:, mod(t+1, tau+1) + 1) = x; % 当前状态入缓存 endmod 索引实现环形覆盖,没有每步移动整块内存的开销。更新式里用的是 x_delay 而不是 x——邻居传来的数据总是有延迟的,这一步让仿真更贴近真实通信行为。
参数 tau 的单位是迭代步,换算成时间就是 tau*dt。一阶系统通常能容忍一定时滞,但 tau 超过临界值后系统会振荡甚至发散。严格判断临界值要用超越方程或多个特征值分析,工程上我直接扫描 tau 从 0 到 Nstep,画出收敛与发散边界曲线,直观且不用推导。时滞仿真里最忌“假装没有延迟”——闭环仿真一旦加入缓存,很多参数都要重新调。
5.3 参数整定的实用顺序:从一阶到二阶的递进
参数一多,同时乱调就会分不清是谁引起的震荡。我按这个顺序来:先跑一阶模型,把 K 和 dt 定下来;然后固定 K、dt,只调 Kd 让速度通道稳定;最后才动 Kp。每一步只动一个参数,并把曲线留档。下表是常用起点和调整方向:
| 参数 | 起点参考 | 调大后的效果 | 调小后的效果 |
|---|---|---|---|
| K(协议增益) | 0.3~0.6 | 收敛更快,超调变大,超过上限则发散 | 收敛变慢 |
| dt(步长) | 0.05~0.1 | 单步位移更大,容易不稳定 | 更稳但更慢 |
| Kd(速度阻尼) | 0.8~1.2 | 速度衰减快,抑制振荡 | 收敛慢、振荡明显 |
| Kp(位置增益) | 0.2~0.5 | 位置跟踪更快,速度超调大 | 跟踪慢,稳态误差变大 |
| tau(时滞步数) | 0~5 | 相位滞后增大,稳定裕度下降 | 无影响 |
验证标准始终一致:误差曲线换成半对数坐标后能看到向下的直线段,斜率对应收敛速率。这个脚本在第 6 章给出,作为所有调参动作的收尾检查。
6. 验证收敛正确性:从误差曲线到可复现脚本
6.1 一个一眼看清收敛快慢的误差脚本
一致性仿真不只看状态曲线束在一起,还要看误差是否按理论速率衰减。对平均一致性,误差定义为每个状态与终值的最大偏差:
% 一致性误差曲线:半对数坐标下应呈直线下降 avg = mean(x0); err = zeros(1, Nstep+1); for k = 1:Nstep+1 err(k) = max(abs(history(:,k) - avg)); end semilogy(0:Nstep, err) xlabel('迭代步 k'); ylabel('max |x_i - avg|');半对数坐标里,指数衰减画出来就是一条向下的直线,斜率与 λ2Kdt 对应。若曲线后半段出现平台,通常是浮点误差饱和;若中途上翘,说明系统已进入不稳定区,回查步长和增益。
6.2 一键复现:断言和参数记录
仿真代码最怕变成“黑匣子”——跑过就忘,别人拿到手里不知道参数为什么这么定。我现在每个脚本开头放三段:边表、参数表、断言。边表保证图结构可改,参数表用注释写明每个值的来源,断言把连通性、稳定性、维度检查都做掉:
% 一致性仿真模板:可复现代码的三件套 assert(rank(L) == n-1, '图不连通,检查邻接矩阵'); % 连通性 assert(dt*K < 2/max(eig(L)), '步长超出稳定边界'); % 稳定性 assert(isequal(size(L), [n n]), '拉普拉斯矩阵维度错误'); % 维度这三条断言在运行第一步拦住最常见的三类错误。跑通一遍不报错,再配上保存的 history 和参数表,过一个月回来也能快速还原当时怎么调的。
经验之谈:我见过很多次“当时能跑后来不能跑”的仿真,最后几乎都指向同一个问题——参数没记录,初值随机但没固定。让初值可复现的小习惯是 rng(1) 放脚本开头,随机初值每次都一样,对比实验才有可比性。这个方法不玄学,就是让输入可复现、输出可解释,省掉的不是调试时间,是返工时间。希望帮到你。
本文还有配套的精品资源,点击获取