第 1 章 多机器人系统全景——架构、拓扑与建模¶
本章性质:✅ 全方向共享——多机系统的分类、通信建模与架构选择,是后续所有方向(分布式 MPC、协同搬运、异构协同、多智能体强化学习)的共同地基。 读者画像:已掌握单体腿足 MPC(OCS2/Crocoddyl)、单体机械臂规控、单智能体强化学习(PPO/SAC)与 ROS 2 基础的算法工程师。 在 Part 中的位置:本章是 Part 1(多机协作基础)的第一章。读完本章,你会建立起一张"看待任意多机系统"的认知地图——任何一个多机场景,都能被你拆解为"什么架构 × 什么交互 × 什么信息流",并据此判断该用哪类方法。
前置自测¶
📋 答不出 ≥ 2 题 → 先回顾单体规控主线的线性代数与图论基础,以及单体 MPC 的优化结构,再来读本章。本章不重复这些前置,但会大量复用它们。
- 一个对称矩阵的**特征值分解**给出什么?什么是矩阵的**谱半径(spectral radius)**?为什么特征值能刻画一个线性动态系统的稳定性与收敛速度?
- 什么叫**连通图(connected graph)?什么是图的**生成树(spanning tree)?如果一张图不连通,会发生什么?
- ROS 2 里的**命名空间(namespace)**是做什么用的?
tf2的坐标变换是怎么从一个坐标系链式地查到另一个坐标系的? - 单体 MPC 把一个机器人的轨迹优化写成一个 QP(二次规划)。这个 QP 的**决策变量**大致由哪些量构成?它的规模随预测步数 \(T\) 怎么增长?
- 一个稠密线性方程组 \(Ax=b\)(\(A\) 是 \(m\times m\) 矩阵)用直接法求解,计算量大约是多少?为什么"计算量是变量数的三次方"这件事,在多机系统里会变成一个核心矛盾?
本章目标¶
学完本章后,你应该能够:
- **画出**任意给定多机场景的三维分类标签——架构(集中/分布/去中心)× 交互(协作/竞争/混合)× 信息(全局/局部/邻居),并说明每一维的工程含义。
- **构造**一个给定通信拓扑的邻接矩阵 \(A\)、度矩阵 \(D\) 和 Laplacian 矩阵 \(L=D-A\),并计算其代数连通性 \(\lambda_2\)。
- **用 \(\lambda_2\) 解释**共识收敛速度与编队稳定性——为什么"图越连通,协调越快"。
- **区分**同构(homogeneous)与异构(heterogeneous)多机系统在建模上的根本差异,并说清为什么异构系统不能直接用参数共享的强化学习策略。
- **画出**面向具体场景的"架构选择决策树",在集中式 / 分布式 / 去中心化之间做出有依据的取舍。
- **跑起来**一个 ROS 2 多命名空间的多机骨架,验证话题隔离与跨命名空间通信。
本章知识导航¶
一句话定位:本章回答"多个机器人要协同,信息和决策该如何组织"这个最顶层的问题,并给出描述任意多机系统所需的两套数学语言——图论(描述"谁能和谁通信")与**架构分类**(描述"谁来做决策")。
本章三条主线及其递进关系:
| 主线 | 解决的问题 | 关键工具 | 对应小节 |
|---|---|---|---|
| 架构分类 | 决策与信息在系统中如何组织? | 集中/分布/去中心三分法 + 决策树 | §1.1 |
| 拓扑建模 | "谁能和谁通信"如何用数学表示? | 图 \(G=(V,E)\)、Laplacian \(L\)、\(\lambda_2\) | §1.2 |
| 系统建模 | 同构与异构系统的联合动力学怎么写? | 联合状态/控制、耦合项 | §1.3 |
| 工程落地 | 多机系统在 ROS 2 里怎么搭? | 多命名空间、tf2 多坐标系、DDS 发现 |
§1.4 |
推荐阅读路径:§1.1 建立全局视角(必读)→ §1.2 是后续所有分布式方法的数学根(必读,且需动手算)→ §1.3 为同构/异构两条技术线分流(必读)→ §1.4 把前三节落到代码(编程方向必做,纯理论读者可速读)。
前置知识桥接¶
本章站在三块前置知识之上,这里各用两三句话重新激活它们——你不需要翻回旧资料就能跟上:
- 单体 MPC 的优化结构:在单体腿足控制里,你把一个机器人在未来 \(T\) 步的轨迹优化写成一个 QP,决策变量是各时刻的状态与控制 \((x_t, u_t)\),约束是动力学和接触。本章会把"\(N\) 个这样的 QP 如何协调"作为多机的核心问题——好消息是,单体那个 QP 在多机里会原封不动地变成"每个 agent 的子问题"。
- 图与线性代数:你在 SLAM 的位姿图里见过"用图表示变量间的约束关系",也熟悉特征值分解。本章的通信拓扑同样是一张图,而它的 Laplacian 矩阵的**第二小特征值** \(\lambda_2\) 将直接决定多机协调的快慢——这与位姿图里 Hessian 矩阵的谱性质决定收敛速度,是同一种数学直觉。
- ROS 2 基础:你会用节点、话题、
tf2。本章只在一个增量维度上扩展:把"一个机器人一套话题"扩成"多个机器人各占一个命名空间",其余概念照旧。
如果跳过本章会怎样¶
- 场景一(架构选错,系统跑不动):你直接照搬单体经验,给 8 台四足写了一个集中式联合优化,想一次性算出所有机器人的动作。结果联合 QP 的变量数随机器人数三次方膨胀,求解器在第 5 台机器人加入时就超时——这一节的复杂度分析会让你提前预见这个坑,并知道该换分布式。
- 场景二(拓扑不连通,协调悄悄失败):你给一队机器人配了"最近邻通信",运动中两台跑远后链路断开,整张通信图分裂成两块。你的共识算法不再收敛,但程序不会报错——编队慢慢散架,你却找不到原因。§1.2 的代数连通性 \(\lambda_2\) 正是用来量化并监控这种"悄悄失联"的。
预计阅读时间¶
| 模式 | 时长 | 适合 |
|---|---|---|
| 精读(含动手算 \(\lambda_2\)、跑 ROS 2 骨架) | 10–14 小时 | 第一次系统学多机的工程师 |
| 速读(读懂架构三分法与图论建模,跳过代码) | 3–4 小时 | 已有多机背景,查漏补缺 |
| 速查(只看架构决策树 + 拓扑对照表 + 故障排查) | 30–40 分钟 | 做架构决策时回查 |
§1.1 多机系统的三大架构 ⭐⭐¶
这一节解决什么问题:多个机器人要一起完成一件事,"谁来做决策、决策所需的信息从哪里来"——这就是**架构(architecture)**。本节给出三种答案(集中式、分布式、去中心化),剖析各自的代价,并教你怎么选。
动机:两台四足合抬一张桌子¶
先看一个具体到不能再具体的问题。两台 Unitree Go2 四足机器人,要合抬一张刚性的桌子,从客厅穿过门洞搬到阳台。
在单体时代,一台机器人的控制器只需为它自己负责:规划自己的质心轨迹、算自己的足端力,跟踪就完事了。现在情况变了——两台机器人**共享同一个刚性负载**。假设左边的机器人 A 决定向左平移半步躲避门框,而右边的机器人 B 对此一无所知,仍按原计划直行。会发生什么?桌子是刚性的,它不允许两端朝不同方向运动。于是两台机器人通过桌子互相"较劲":桌子被扭动,要么在某台机器人的足端打滑,要么把抓得更紧的那台机器人直接拽得失去平衡摔倒。
这里冒出了一个单体控制里从不存在的新问题:两台机器人的决策必须相互协调,否则物理耦合会让它们互相破坏。那么,协调由谁来组织?信息从哪里流向哪里?这正是"架构"要回答的。
如果照搬单体思路会怎样¶
最朴素的想法是:让每台机器人各跑各的 MPC,互不通信,各自跟踪一条事先分好的目标轨迹。这相当于把两个单体控制器并排放在一起。
这样做会怎样?问题出在"事先分好的轨迹"几乎不可能精确一致。真实世界里两台机器人的执行总有微小偏差——电机响应差一点、地面摩擦不一样、估计的负载质心略有出入。这些偏差在刚性负载上会被放大成**内力(internal force)**:两台机器人对桌子施加的力,有一部分并不推动桌子运动,而是互相抵消、在桌子内部"对拉"。轻则白白消耗能量、磨损关节,重则内力把负载姿态带偏、超出某台机器人的平衡边界导致摔倒。更糟的是,没有通信,任何一台机器人都察觉不到对方正在偏离——错误无人纠正,只会累积。
这说明了一件根本的事:多机协同的难点不在"让每台机器人单独走得好",而在"让它们的决策在物理耦合下保持一致"。一致性从哪里来,就是架构的分野。
历史:从 ALLIANCE 到协调分类学¶
"多机器人系统该如何组织"是个有年头的问题。1998 年 Parker 提出 ALLIANCE 架构,这是容错多机协调的奠基工作之一——它用基于行为(behavior-based)的机制让一组机器人自适应地分工,某台失效时其它机器人能接管它的任务。ALLIANCE 第一次系统地回答了"多机如何分工与容错"。
2004 年,Farinelli、Iocchi 与 Nardi 在 IEEE Trans. on Systems, Man, and Cybernetics 上给多机系统提出了一套被广泛引用的**分类学(taxonomy)。它比"三个维度"要细——整套分类被组织成**两组**正交维度:一组刻画"机器人之间如何相互对待"(**协调维度,coordination dimensions),一组刻画"系统物理与硬件层面长什么样"(系统维度,system dimensions)。把这两组列全,你会更清楚本节聚焦的"组织"维度在整张地图里的位置:
| 维度组 | 维度 | 取值谱 | 工程含义 |
|---|---|---|---|
| 协调维度 | 协作(cooperation) | 协作 ↔ 非协作 | agent 是否为同一团队目标行动(本 Part 几乎全是协作) |
| 知识(knowledge) | 知道队友存在 ↔ 不知道(aware ↔ unaware) | agent 是否显式建模其他 agent 的存在 | |
| 协调(coordination) | 强协调 ↔ 弱协调 ↔ 无协调 | 是否依赖一套显式协调协议(如共识、拍卖)来对齐行为 | |
| 组织(organization) | 集中 ↔ 分布 ↔(去中心) | 决策权与信息如何分布——本节聚焦此维 | |
| 系统维度 | 通信(communication) | 直接(显式消息)↔ 间接(经环境/stigmergy) | 信息以何种媒介传递(对应 §1.1 三架构的"协调媒介") |
| 团队构成(team composition) | 同构 ↔ 异构 | 机器人是否相同——正是 §1.3 的主题 | |
| 系统架构(system architecture) | 中央处理 ↔ 分布式处理 | 计算资源在物理上如何摆放 | |
| 团队规模(team size) | 单体 / 双体 / 多体(small)/ 集群(large/swarm) | 规模量级——决定 §1.6 末尾"多机 vs 群体"的分野 |
这张表本身就是一个**系统性分类**的范例:它告诉你"描述一个多机系统该问哪八个问题"。注意一个容易被初学者忽略的点——协调维度里的"组织"(集中/分布/去中心)和系统维度里的"系统架构"(中央/分布式处理)是两回事:前者是**逻辑上**谁做决策,后者是**物理上**计算摆在哪。一个逻辑上分布式的算法,完全可以临时跑在一台中央服务器上(物理集中、逻辑分布);反过来,逻辑集中式的联合优化也可以用一个 GPU 集群并行求解(物理分布、逻辑集中)。本节(以及本课程)说"集中/分布/去中心"时,指的始终是**逻辑组织维度**——谁拥有决策所需的全局信息、谁来拍板。
我们本节聚焦其中最工程化、对后续方法选择最有指导意义的一维——组织维度,按"决策与信息的组织方式"把多机系统分成三类。其余几维不会消失,它们会在后续小节各自登场:通信维度对应 §1.1 末尾要点破的"分布式(直接通信) vs 去中心化(间接/经环境)";团队构成维度就是 §1.3 的同构/异构;团队规模维度则在 §1.6 末尾引出"多机器人系统"与"群体机器人(swarm)"的分野。换句话说,本章的四节(加小结)正好把 Farinelli 八维里最具工程指导性的几维逐一展开——这也是为什么这套分类学值得在开篇就建立。
理论:集中式、分布式、去中心化¶
集中式(Centralized)。一个中央规划器掌握所有 agent 的状态,把整个团队的协同写成**一个**联合优化问题,一次性算出每个 agent 的动作再下发。
数学上,这是一个把所有机器人拼在一起的联合 QP/NLP。如果第 \(i\) 台机器人的状态维度是 \(n_i\)、控制维度是 \(m_i\),预测步数为 \(T\),那么联合优化的决策变量总数约为
求解这个 QP 的主流方法(如内点法)每次迭代都要解一个 KKT 线性系统,其规模与决策变量数同阶。**稠密**情况下,解一个 \(m\times m\) 线性系统的计算量是 \(O(m^3)\)。于是联合 QP 的单次迭代成本随机器人数 \(N\) 大致按 \(O(N^3)\) 增长——在耦合更稠密、约束更多时,最坏可到 \(O(N^6)\) 量级。这个"三次方乃至六次方"的增长,正是集中式架构最致命的软肋。
- 优势:全局最优。因为是单一优化问题,它能找到所有机器人在联合动作空间里的协调最优解(帕累托或纳什意义下),不会出现"局部各自最优、整体打架"的情况。
- 劣势:计算量随 \(N\) 爆炸;中央节点是单点故障(它一挂,整队瘫痪);所有状态要汇聚到中央、所有命令要从中央下发,通信形成瓶颈。
- 适用:机器人数少(\(N\le 4\))、通信可靠、对协调精度要求高的场合,例如工厂里多台机械臂协同精密装配。代表工作:De Vincenti & Coros 在 RSS 2023 提出的集中式 SQP,它把协同 loco-manipulation 写成 robot/payload/gait-agnostic 的单一优化,协调至多两台机器人加一个被操作物体。
分布式(Distributed)。每个 agent 只求解**自己的**局部优化问题,然后与邻居交换信息、反复迭代,直到大家的解相互一致。
- 优势:可扩展。每台机器人解的是单体规模的子问题(计算量与 \(N\) 几乎无关),通信复杂度正比于通信边数 \(O(|E|)\) 而非 \(O(N^2)\);没有单点故障,某台失效其余仍能继续。
- 劣势:需要多轮通信迭代才能收敛(每个控制周期内要来回通信若干次);在非凸问题上,收敛性没有理论保证。
- 适用:机器人数中等(\(N=2\text{–}10\))、通信可靠的编队与协同搬运。代表工作:Kim 与 Akbari Hamed 在 IEEE T-RO 2023 提出的分布式 MPC,用两台 A1 协同行走与搬运,通过"一步通信延迟 + 一致性协议(agreement protocol)"协调,性能逼近集中式而计算量大幅下降。
去中心化(Decentralized)。每个 agent 完全独立决策,没有显式通信,只通过环境(典型如共享负载的接触力)隐式地相互影响。
- 优势:零通信需求;鲁棒性极强(没有通信链路可断,没有中央节点可挂)。
- 劣势:没有任何全局优化保证;协调完全依赖隐式信号,设计难度高。
- 适用:机器人数大、通信不可靠或不可行的场合。代表工作:Pandit 等人 2025 年的 decPLM——一组"四足(Unitree Go2)+ 机械臂(D1)"机器人对不可抓取的物体做 pinch-lift-move(夹-抬-移),靠夹持接触而非刚性连接。它在 IsaacLab 里用 MAPPO 加大幅域随机化训练,仅用两台训练却能泛化到 2–10 台,机器人之间不通信,靠一个精心设计的奖励(constellation reward,统一位置与姿态对齐)诱导它们表现得"像被刚性连接在一起",并已在真机上由 2、3 台 Go2 复现。
把三者并排对照:
| 维度 | 集中式 | 分布式 | 去中心化 |
|---|---|---|---|
| 决策主体 | 单一中央规划器 | 每个 agent 解局部问题 + 协调 | 每个 agent 完全独立 |
| 通信 | 全状态汇聚 + 命令下发 | 与邻居多轮交换 | 无显式通信 |
| 计算复杂度 | \(O(N^3)\!\sim\!O(N^6)\) | 各 agent \(\approx O(1)\)(并行) | 各 agent \(\approx O(1)\) |
| 最优性 | 全局最优 | 接近最优(凸时可证) | 无保证 |
| 单点故障 | 有(致命) | 无 | 无 |
| 适用规模 | \(N\le 4\) | \(N=2\text{–}10\) | \(N\) 大 |
| 代表 | De Vincenti & Coros (RSS 2023) | Kim & Hamed (T-RO 2023) | decPLM (2025) |
那么实际工程中怎么选?下面这棵决策树给出一个可操作的判断流程:
机器人数 N > 10?
├── 是 → 去中心化(集中/分布式的计算或通信已不可行)
└── 否 →
需要全局最优 / 高协调精度?
├── 是 → 集中式(如 N=2 的精密装配)
└── 否 →
通信可靠吗?
├── 是 → 分布式(ADMM 类分布式 MPC)
└── 否 → 去中心化(靠接触力等隐式信号协调)
这里特别要建立一个**类比**来帮助记忆,但也要标清它的边界。三种架构很像一家公司的三种决策结构:集中式像"事事由 CEO 拍板"——决策最优但 CEO 一忙就成瓶颈、一病全公司停摆;分布式像"各部门负责人开会协商"——开几轮会(通信迭代)达成一致,慢一点但抗单人缺席;去中心化像"没有会议,每个人只看市场价格信号自行决定"——零协调成本但容易各行其是。这个类比像在"信息汇聚程度"和"鲁棒性"上,但不要把它延伸到"效率":公司里集中决策常被认为低效,而在机器人里集中式恰恰给出最优解——差别在于机器人的联合优化能精确求解,而人类组织做不到。
本质洞察:三大架构的根本差异,不在于"有没有一个中央节点",而在于**全局信息在何处汇聚、机器人间的协调以何种媒介传递**。集中式靠中央节点统一计算来传递协调;分布式靠机器人间的通信迭代来传递协调;去中心化则把协调外包给物理世界——靠共享的接触力、共享的负载这些环境媒介来隐式传递。理解了"协调的媒介是什么",你就能预判一个架构的瓶颈在哪里:集中式瓶颈在中央的算力,分布式瓶颈在通信带宽与延迟,去中心化瓶颈在隐式信号是否足够丰富。
在继续之前,先点破一个**极易混淆的概念**(它会反复困扰初学者):"分布式"和"去中心化"不是一回事。两者都没有中央规划器,但**分布式仍然需要通信**——机器人之间要多轮交换各自的局部解才能达成一致;而**去中心化是零通信**的,机器人之间根本不交换信息。后面 §1.2 讲的共识与 ADMM 属于分布式(要通信),第 8 章讲的 decPLM 那种"靠接触力涌现协调"才是去中心化(不通信)。把这两个词当同义词用,会让你在读论文时彻底误判一个方法的通信假设。
还有一对概念也常被初学者搅在一起,值得在这里就分清,因为它正好是本节(§1.1)与下一节(§1.2)的分界:**架构**和**通信拓扑**是两个**正交**的维度。架构回答"谁来做决策"(中央统算 / 各自局部解再协调 / 完全独立),拓扑回答"谁能和谁通信"(谁是谁的邻居)。同一个分布式架构,可以跑在全连接拓扑上,也可以跑在环形或链式拓扑上——架构没变,但拓扑变了,协调的快慢会差很多(这正是 §1.2 要量化的)。反过来,同一张通信拓扑(比如环形),既可以承载分布式架构(邻居间迭代求解),也可以只用于去中心化架构里的状态广播。所以"选了分布式"并不等于"定了拓扑";你还要单独设计拓扑。这一节我们锁定了架构这一维,下一节专门处理拓扑这一维——把它从一张示意图变成能算出协调速度的数学对象。
三种架构在一个控制周期里"线上跑什么"。 复杂度讲的是"算多少",这里补上"传多少、怎么传"——把每个控制周期内的消息流画出来,你对三种架构的差异会有更具体的体感(下图箭头表示一个周期内的信息流向,C 为中央节点,\(R_i\) 为机器人):
集中式(每周期 1 轮汇聚 + 1 轮下发):
R0 ─状态→ ┐ ┌ →动作 R0
R1 ─状态→ ├─→ [ C ] ─→ ┤ →动作 R1 上行 N 条 + 下行 N 条 = O(N) 消息
R2 ─状态→ ┘ └ →动作 R2 但全压在 C 一点,C 是带宽与算力瓶颈
分布式(每周期 k 轮邻居交换,k 由 λ2 定):
R0 ⇄ R1 ⇄ R2 ⇄ R3 每轮沿每条边双向交换,共 O(|E|) 消息/轮
└────(重复 k 轮)────┘ 总通信 = O(k·|E|);无中央点,负载摊到各边
去中心化(每周期 0 条消息):
R0 R1 R2 R3 彼此不发消息
└────[ 共享负载 / 接触力 ]────┘ 协调经物理世界隐式传递,通信量恒为 0
三张图并排,一眼看清三者的通信代价结构完全不同:集中式是"星型汇聚"——消息数虽只 \(O(N)\),但全部经过中央一点,中央的入/出带宽是硬瓶颈,且这一点一挂全断;分布式是"边上往返"——消息总量 \(O(k\cdot|E|)\),\(k\) 这个轮数正是 §1.2 的 \(\lambda_2\) 要决定的(连得越好、\(k\) 越小),负载分摊在各条边上、无单点;去中心化是"零线上消息"——协调完全外包给物理世界(共享负载传递的接触力),所以它在弱网/无网下照常运转。这张图也解释了为什么"通信"这一维必须和"架构"一起看:同样要协调 \(N\) 台机器人,集中式赌的是"中央那一点的带宽够不够",分布式赌的是"\(k\) 轮邻居交换塞不塞得进一个控制周期"(回到 §1.5 的实时预算),去中心化干脆不赌通信、赌"隐式信号够不够丰富"。
代码验证:把"\(O(N^3)\) 爆炸"算给你看。上面的复杂度分析比较抽象。我们用一段最简单的代码,把集中式联合 QP 的规模与求解成本随 \(N\) 的增长直观地打印出来——这能让你对"为什么 \(N\) 一大集中式就不行"有量的感受。
import numpy as np
# 单台四足的单刚体(SRB)模型维度:
# 状态 12 维 = 位置3 + 欧拉角3 + 线速度3 + 角速度3
# 控制 12 维 = 4 个足端 x 3 维接触力
n_x, n_u = 12, 12
T = 20 # MPC 预测步数(horizon)
def centralized_vars(N):
"""集中式:N 台拼成一个联合 QP,决策变量数 = N*(状态+控制)*步数。"""
return N * (n_x + n_u) * T
def distributed_vars_per_agent():
"""分布式:每台只解自己的子问题,单机变量数与 N 无关。"""
return (n_x + n_u) * T
for N in [2, 4, 8, 16]:
vc = centralized_vars(N)
vd = distributed_vars_per_agent()
# 内点法每次迭代解一个 KKT 线性系统,稠密时成本约 O(变量数^3)
cost_central = vc ** 3
cost_dist = vd ** 3 # 各 agent 并行,单机成本与 N 无关
print(f"N={N:2d} | 集中式变量={vc:5d}, 单次迭代成本~{cost_central:.2e}"
f" | 分布式单机变量={vd:4d}, 成本~{cost_dist:.2e}"
f" | 集中/分布 ≈ {cost_central / cost_dist:6.0f}×")
运行结果(数量级)是这样的:
| \(N\) | 集中式变量数 | 集中式单次迭代成本 | 分布式单机成本 | 比值 |
|---|---|---|---|---|
| 2 | 960 | \(\sim 8.8\times10^{8}\) | \(\sim 1.1\times10^{8}\) | 8× |
| 4 | 1920 | \(\sim 7.1\times10^{9}\) | \(\sim 1.1\times10^{8}\) | 64× |
| 8 | 3840 | \(\sim 5.7\times10^{10}\) | \(\sim 1.1\times10^{8}\) | 512× |
| 16 | 7680 | \(\sim 4.5\times10^{11}\) | \(\sim 1.1\times10^{8}\) | 4096× |
比值恰好是 \(N^3\)(\(2^3=8\)、\(4^3=64\)、\(8^3=512\)、\(16^3=4096\))——这就是"集中式计算量随 \(N\) 三次方膨胀"的直观证据。要强调一句:这里用的是**稠密**求解的最坏估计。真实的 MPC 联合 QP 有强烈的**块稀疏结构**(沿时间轴块三对角、机器人之间只在耦合处有非零块),利用稀疏性能把成本压下来。而分布式 MPC 的本质,正是显式地利用并切分这种稀疏结构,把一个大问题拆成 \(N\) 个可并行的小问题——这一点我们会在第 4 章用 ADMM 把它讲透。
一个端到端的架构选择案例。把决策树用在一个真实场景上走一遍,这次的重点是:大系统往往不该选"单一架构"。任务是震后搜救,要同时完成大范围测绘、钻缝隙搜寻、合力清障三件事。
别急着给整个系统选一个架构,先把它拆成**子任务**,各自贴三维标签、各自定架构:
| 子任务 | 规模 / 通信 | 该用的架构 | 理由 |
|---|---|---|---|
| 空中测绘 | 2 架无人机,对地面站通信尚可 | 分布式 | \(N\) 小但要协调覆盖:各自规划覆盖区、交换已扫范围 |
| 地面搜寻 | 6 台四足钻缝隙,无线时断时续 | 去中心化 | 物理隔开、通信不可靠:各自自主,任务级松耦合 |
| 合力清障 | 3 台四足合抬构件,就近通信尚可 | 分布式 MPC | 刚性耦合、要协调精度;通信若中断则退化为去中心化(接触力隐式协调,见第 8 章) |
关键洞察是:真实大系统往往不是单一架构,而是按子任务分层混用。整体之上可能还有一个弱集中的任务分配层(谁去测绘、谁去搜寻、谁去清障,见第 3 章 CBBA),但执行层各子队按自己的标签各选架构。这正是三维分类的用法——先拆子任务、各贴标签、再分别定架构。所以**问"这个子任务该用什么架构",比问"这个系统该用什么架构"更有用**。(本节末练习 2 让你为三个相互独立的场景各选一种架构;这里的案例不同——它把一个任务拆成多个子任务、各用不同架构,练的是"分解 + 混用"的思路。)
一个相邻但不同的概念:群体机器人(swarm robotics)¶
读到这里你可能会冒出一个疑问:既然有"去中心化、\(N\) 很大、靠局部信息协调"这一类系统,它和常听到的**群体机器人(swarm robotics)**是不是一回事?这个问题值得在开篇就讲清,因为这两个词在文献里经常被混用,但它们指向的设计哲学并不相同,误判会让你套错整套方法论。
先给一个**对比性**的定义。多机器人系统(multi-robot system, MRS)**是一个宽泛的总称——任意数量、任意能力、任意协调方式的多台机器人协同,都算 MRS;它**允许**个体很复杂(一台 Go2 就有几十个自由度和一整套 MPC)、**允许**全局信息、**允许**全通信、**允许**中央协调。**群体机器人(swarm robotics)是 MRS 的一个特例,它额外加上一组苛刻的限定:个体**简单且大量**、严格**去中心化**、只用局部信息(每个个体只感知邻近同伴的相对状态,如相对位置、速度)、所有个体遵循**同一套反应式规则**,且系统的稳定性**不依赖任何单个个体**(任意个体离队或失联,群体行为不退化)。它的灵感直接来自生物群体——蚁群、蜂群、鸟群:单只蚂蚁近乎"愚笨",但群体涌现出觅食、筑巢、搬运的复杂行为。把两者并排对照:
| 维度 | 一般多机器人系统(MRS) | 群体机器人(swarm robotics) |
|---|---|---|
| 个体复杂度 | 可以很复杂(如带 MPC 的 Go2) | 刻意简单、同质 |
| 规模 \(N\) | 小到中(典型 2–20) | 大(几十到上千) |
| 信息范围 | 可全局、可邻居、可局部 | 严格只用局部(邻近同伴的相对信息) |
| 通信 | 可全通信 / 中央汇聚 | 仅局部,常经环境间接(stigmergy) |
| 协调方式 | 可集中、可分布、可去中心 | 严格去中心、涌现式 |
| 容错 | 取决于架构 | 极强(去掉任意个体群体不变) |
| 灵感来源 | 控制论、优化、博弈、学习 | 生物群体(蚁群/蜂群/鸟群) |
本质洞察:群体机器人不是"机器人很多的多机系统",而是一种**主动放弃个体能力与全局信息、换取极致可扩展性与鲁棒性**的设计哲学。它的核心赌注是:用"个体简单 + 规则统一 + 只看局部"这三条约束,把系统的复杂度从"随 \(N\) 爆炸"压成"与 \(N\) 几乎无关"——因为每个个体的计算和通信负担都是常数,加再多个体,单体负担也不变。代价是放弃了全局最优性与可预测性(涌现行为难以事先精确设计)。所以判断一个系统"是不是 swarm",不要数机器人个数,而要问三件事:个体是不是被刻意做简单了?协调是不是严格只靠局部信息?去掉任意一个个体系统是不是照常运转? 三个"是"才是真 swarm。
这个区分对本课程有直接的取舍含义。本课程聚焦的腿足/机械臂协同,绝大多数属于"一般 MRS"而非 swarm——我们的个体(Go2、机械臂)恰恰是高度复杂的,\(N\) 也不大(典型 2–10),而且我们会用到通信(分布式)甚至中央优化(集中式)。§1.1 里"去中心化"那一类(如第 8 章 decPLM 的零通信协同)在"去中心 + 局部信息"上最接近 swarm 的精神,但它的个体仍是复杂四足、\(N\) 仍不大,所以严格说它是"借用了 swarm 的去中心化思想的 MRS",而非教科书意义的 swarm。真正的 swarm 控制(大规模、简单个体、涌现行为)有自己的一套方法论(行为规则设计、stigmergy、宏观-微观建模),不在本课程主线内——但你现在知道了它在地图上的位置,以及它和我们要做的事的边界在哪。
⚠️ 常见陷阱¶
💡 概念误区:把"分布式"等同于"去中心化" - 错误描述:看到一篇论文说"我们的方法是 decentralized/distributed 的",就认为它不需要任何机器人间通信。 - 现象/后果:你据此设计了一个无通信的系统,却发现复现的"分布式 MPC"根本跑不通——因为原方法每个控制周期要做 3 轮 ADMM 通信,你把通信砍了,一致性永远达不成,机器人各走各的。 - 根本原因:两者都没有中央规划器,但**分布式靠通信迭代达成一致(有通信),**去中心化靠环境隐式信号(零通信)。论文里这两个词经常被混用,必须看它的"通信假设"而非术语。 - 正确做法:读任何多机方法,先问一句"它每个控制周期需要几轮通信?通信什么内容?"。需要通信的是分布式,完全不通信的才是去中心化。
🧠 思维陷阱:盲目追求"分布式/去中心化"的可扩展性 - 错误描述:认为越"去中心化"越先进,\(N=2\) 的小系统也要上分布式甚至去中心化。 - 现象/后果:对两台机器人精密协同,你上了分布式 MPC,结果每周期要等多轮 ADMM 通信收敛,实时性反而比集中式差,而且非凸时还可能不收敛;最终精度也不如直接解一个联合 QP。 - 根本原因:架构选择是**通信可靠性 × 最优性需求 × 规模**的权衡,不是单一维度的"越分散越好"。\(N\) 小且通信可靠时,集中式的全局最优与确定性收敛反而是优点。 - 正确做法:按决策树走——先看 \(N\),再看是否需要全局最优,最后看通信是否可靠。不要为了"可扩展"这个标签牺牲实际场景里更重要的最优性与实时性。
⚠️ 编程陷阱:集中式联合 QP 直接用稠密求解器 - 错误描述:把 \(N\) 台机器人的联合 MPC 组装成一个大 QP,直接丢给一个通用稠密 QP 求解器。 - 现象/后果:\(N=2\) 时还能跑,\(N\) 稍大(如 6–8)求解时间从毫秒级跳到秒级,控制周期被打爆,机器人卡顿甚至失稳。 - 根本原因:稠密求解忽略了 MPC 问题的块三对角稀疏结构,白白付出了 \(O(N^3)\) 乃至更高的代价。 - 正确做法:用能利用稀疏结构的求解器(如基于 Riccati 递推或稀疏内点的 OSQP/HPIPM),或干脆改用分布式 MPC 切分问题。自检方法:打印 KKT 矩阵的稀疏度,若非零元占比远小于 1 却用了稠密求解器,就是踩了这个坑。
💡 概念误区:把"\(N\) 大的去中心化系统"一律叫成 swarm(群体机器人) - 错误描述:看到一篇做大规模无人机编队的论文用了"去中心化"控制,就称它是 swarm robotics,并去套用群体机器人的行为规则设计方法。 - 现象/后果:你按 swarm 的假设(个体简单、严格只用局部信息、涌现式)去理解一个其实带有全局规划层、个体复杂、用了通信的系统,会误判它的能力边界——比如以为它"去掉任意个体都不受影响",而真实系统其实有关键节点。 - 根本原因:swarm 是 MRS 的**特例**,要同时满足"个体简单 + 严格局部信息 + 不依赖任何单个个体"三条;只满足"\(N\) 大"或"去中心化"不构成 swarm。 - 正确做法:用本节三问判定——个体是否被刻意做简单?是否严格只靠局部信息?去掉任意个体系统是否照常运转?三个"是"才是 swarm,否则只是一个恰好规模较大或去中心化的一般 MRS。
练习¶
- [实现/分析] 扩展本节的复杂度演示代码:让 \(N\) 从 2 取到 20,分别画出集中式单次迭代成本与分布式单机成本随 \(N\) 的曲线(用对数纵轴)。在图上标注:假设你的求解器单次迭代预算是 \(10^{10}\) 次浮点操作,集中式在 \(N\) 等于多少时开始超预算?这个 \(N\) 就是你这套硬件下集中式的"天花板"。
- [设计] 给定三个场景:(a) 工厂里两台机械臂协同把一块面板装到车身上(亚毫米精度、车间有线网络);(b) 野外八台四足排成纵队穿越崎岖地形(WiFi 偶尔丢包);(c) 废墟里十台机器人合力搬开一根倒塌的梁(通信几乎不可用)。为每个场景选择架构,并各用三到五句话论证你的选择(必须引用决策树里的判断维度)。
- [思考/预告] 去中心化架构号称"零通信也能协调",这听起来近乎魔法:机器人之间不交换任何信息,凭什么动作能配合上?先记住一个最简理解——它们靠共享的物理对象(同一个负载)传递信息:一台机器人推动负载,另一台通过负载传来的接触力"感受"到了,从而做出互补反应。这种"通过物理交互实现的隐式通信"的完整机制,会在第 8 章(多足协同 loco-manipulation)展开。这里只需建立这个直觉,不必深究。
- [分类/判断] 用 Farinelli 八维分类表给下面三个系统各贴一套标签(协作? 是否 aware? 协调强弱? 组织? 通信直接/间接? 同构/异构? 团队规模?),并判断哪个最接近 swarm:(a) 一千架灯光秀无人机按预编程轨迹列阵;(b) 三台 Go2 用 ADMM 分布式 MPC 合抬构件;(c) 一群清扫机器人各自随机游走清扫、靠在地上留下的"已清扫"标记避免重复(stigmergy)。提示:重点看"个体是否简单 + 是否严格只用局部信息 + 去掉一个是否照常"。
上一节我们从"谁来决策"的角度把多机系统分了三类,并发现:除了集中式,分布式和去中心化都依赖机器人之间的某种连接——分布式靠通信链路,去中心化靠共享的物理媒介。这就引出了下一个绕不开的问题:"谁能和谁通信(或交互)"这件事,本身该如何精确地描述和分析? 答案是图论。§1.2 将用图、邻接矩阵和 Laplacian 把通信拓扑变成可计算的数学对象,并揭示一个贯穿全 Part 的关键量——代数连通性 \(\lambda_2\)。
§1.2 通信拓扑的图论建模 ⭐⭐⭐¶
这一节解决什么问题:上一节说分布式架构"靠通信迭代达成一致"。但通信既有代价又有结构——每台机器人只能和有限的几个"邻居"交换信息。本节用图论把"谁能和谁通信"变成可计算的数学对象,并提炼出一个贯穿全 Part 的标量——代数连通性 \(\lambda_2\),它定量地回答"这套拓扑下,协调最快能有多快"。
动机:通信结构如何影响协调的快慢¶
回到分布式架构。一队机器人靠彼此交换信息、反复迭代来对齐决策。现在问一个更细的问题:通信的"结构"会怎样影响这个对齐过程?
设想六台机器人。第一种连法,它们排成一个环,每台只和左右两个邻居通信;第二种连法,六台两两全连。直觉告诉你全连应该"更快对齐",但快多少?是快 2 倍还是 20 倍?如果环上断掉一条边变成一条链,对齐还成立吗?会慢多少?这些问题用语言描述不清,你需要一种数学语言,既能精确表示"谁连着谁",又能从这个结构里**算出**协调的速度。
如果没有图论这套语言会怎样¶
如果只凭直觉"多连几条线应该更快",你会在两个地方栽跟头。其一,两个都"连通"的拓扑,收敛速度可能差一个数量级——光看图画完全看不出来,你也无法预算"每个控制周期要留几轮通信时间"。其二,连通性是个悬崖而非斜坡:某些拓扑断一条边就分裂成两半、协调彻底失效,另一些断三条边仍连通;哪条边是"命门",肉眼也看不出。缺了量化工具,你只能靠反复仿真试错,而且永远不知道当前拓扑离"失联"还有多远。我们需要一个标量,把"连通强度"量出来。
历史:从 Fiedler 到 Olfati-Saber¶
代数图论里,Miroslav Fiedler 在 1973 年发现:一张图的连通性可以用它的 Laplacian 矩阵的**第二小特征值**来刻画——这个值后来被称为**代数连通性(algebraic connectivity),也叫 **Fiedler 值。三十年后,Olfati-Saber 与 Murray 在 2004 年把这个纯数学的量引入多智能体共识,证明了它直接决定共识算法的收敛速率。从此 \(\lambda_2\) 成了多机系统设计的一个核心指标。下面我们从头把这套语言建起来。
理论¶
一、用图表示通信拓扑¶
把通信拓扑写成一张图 \(G=(V,E)\):
- \(V=\{1,2,\dots,N\}\) 是 \(N\) 个 agent(顶点);
- \(E\subseteq V\times V\) 是通信边的集合,\((i,j)\in E\) 表示 "agent \(i\) 能接收 agent \(j\) 的信息"。
如果通信是**对称**的(\(i\) 能收到 \(j\) 等价于 \(j\) 能收到 \(i\),比如双向无线链路),我们用**无向图**;如果通信是**单向**的(比如 leader 广播、follower 只收不发),就要用**有向图(directed graph)**。先以无向图为主建立直觉,有向情形放到第六部分。
二、三个关键矩阵:邻接 \(A\)、度 \(D\)、Laplacian \(L\)¶
一张图可以完全由三个矩阵刻画:
\(A\) 记录"谁连谁",\(D\) 是对角阵,对角元 \(d_{ii}\) 是 agent \(i\) 的**度**(邻居个数),\(L\) 则把两者合并。举个具体例子:四台机器人连成一条链 \(1-2-3-4\),则
注意 \(L\) 的每一行加起来都等于零——这不是巧合,是 \(L\) 最重要的性质,马上证明。
三、Laplacian 的核心性质(含证明)¶
性质 1:\(L\mathbf{1}=\mathbf{0}\),即全 1 向量是 \(L\) 的特征向量,对应特征值 0。
证明很直接:\(L\) 的第 \(i\) 行之和 \(=\sum_j L_{ij}=d_{ii}-\sum_j a_{ij}=d_{ii}-d_{ii}=0\)。所以 \(L\mathbf{1}=\mathbf{0}=0\cdot\mathbf{1}\)。这说明 \(L\) 至少有一个零特征值,记 \(\lambda_1=0\)。
性质 2:(无向图时)\(L\) 对称且半正定,特征值可排序为 \(0=\lambda_1\le\lambda_2\le\dots\le\lambda_N\)。
关键是看 \(L\) 的二次型。对任意向量 \(x=(x_1,\dots,x_N)^\top\):
为什么这个等式成立?把 \(x^\top L x = x^\top D x - x^\top A x = \sum_i d_{ii}x_i^2 - \sum_{(i,j)}2x_ix_j\) 展开,再用 \(d_{ii}=\sum_{j\in\mathcal N_i}1\) 把每条边的贡献配方,就得到每条边一项 \((x_i-x_j)^2\)。式 (1.1) 右边是平方和,恒非负,所以 \(L\) 半正定,特征值全部 \(\ge 0\)。
这个二次型还有一个更深刻、更好用的来源,值得专门点出——它把"\(L\) 为什么半正定"从"配方凑出来"变成"一眼看穿"。给每条边任意指定一个方向,定义**关联矩阵(incidence matrix)** \(B\in\mathbb R^{N\times|E|}\):第 \(e\) 列对应边 \(e=(i,j)\),在行 \(i\) 取 \(+1\)、行 \(j\) 取 \(-1\),其余为 0。则可以证明
一旦有了式 (1.2),半正定立刻显然:对任意 \(x\),\(x^\top L x = x^\top B B^\top x = \|B^\top x\|^2 \ge 0\),因为它是某个向量的平方范数。而 \(B^\top x\) 的第 \(e\) 个分量正是 \(x_i - x_j\)(边 \(e\) 两端的差),所以 \(\|B^\top x\|^2 = \sum_{(i,j)\in E}(x_i-x_j)^2\),与式 (1.1) 完全吻合(差一个对每条边计一次还是两次的因子 \(\tfrac12\),取决于求和约定)。
本质洞察:\(L=BB^\top\) 揭示了 Laplacian 的真正身份——它是图上的"差分算子的平方"。\(B^\top\) 把"顶点上的值"映成"每条边上的差",\(L\) 则度量这些差的总能量。这解释了为什么 \(L\) 出现在每一个"惩罚相邻者不一致"的场景里:共识(惩罚邻居状态差)、编队(惩罚相对位置偏离期望)、分布式优化的一致性约束(惩罚副本变量不相等)——它们的二次惩罚项写出来都是 \(x^\top L x\) 的形式。记住这一点,你在第 2 章看到 ADMM 的一致性约束、第 4 章看到编队误差的二次型时,会立刻认出"这又是那个 Laplacian"。关联矩阵 \(B\) 本身也是第 4 章编队刚性(rigidity)理论的核心工具。
性质 3(最关键):\(\lambda_2>0 \iff\) 图连通;更一般地,特征值 0 的重数 = 图的连通分量个数。
阶段小结:到这里我们已经知道 \(L\) 必有零特征值(性质 1)、所有特征值非负(性质 2)。接下来要证的是:第二小特征值 \(\lambda_2\) 到底是大于零还是等于零,恰好对应"图连通还是不连通"。
证明思路是看式 (1.1) 何时取零。\(x^\top L x=0\) 当且仅当对每条边 \((i,j)\in E\) 都有 \(x_i=x_j\),也就是 \(x\) 在每个连通分量内部取常数。于是 \(L\) 的零空间(特征值 0 对应的特征子空间)的维数,正好等于"能独立取值的常数块个数",即连通分量个数 \(c\)。
- 若图**连通**(\(c=1\)):零空间只有一维(全 1 向量方向),所以特征值 0 是单根,第二小特征值 \(\lambda_2>0\)。
- 若图**不连通**(\(c\ge 2\)):零空间至少二维,特征值 0 的重数 \(\ge 2\),于是 \(\lambda_2=0\)。
这就证明了 \(\lambda_2>0\iff\) 连通。\(\lambda_2\) 因此被称为**代数连通性**——它不仅判断"连不连通",其**大小**还度量"连得有多牢",这一点下一部分展开。
四、代数连通性 \(\lambda_2\) 的双重含义¶
\(\lambda_2\) 可以从两个互补的角度理解,这是一处典型的**双重解读**:
- 几何视角(图的"最难切口"):\(\lambda_2\) 对应的特征向量称为 Fiedler 向量。它给出把图分成两块时"切断边数最少"的那种二分方式——\(\lambda_2\) 越小,说明存在一个"窄脖子",图很容易被切成两半。这正是**谱聚类(spectral clustering)**的数学基础。
- 动力学视角(共识收敛速率):考虑最简单的连续时间共识协议 \(\dot x=-Lx\)(每个 agent 朝邻居平均值靠拢,完整推导在第 2 章)。它的解是 \(x(t)=e^{-Lt}x(0)\),而误差按 \(\|x(t)-\bar x\mathbf 1\|\le \|x(0)-\bar x\mathbf 1\|\,e^{-\lambda_2 t}\) 衰减,其中 \(\bar x=\frac1N\sum_i x_i(0)\) 是平均值。换句话说,收敛速率就是 \(\lambda_2\)。\(\lambda_2\) 大一倍,达到同样一致精度所需时间减半。
本质洞察:把式 (1.1) 限制在"垂直于 \(\mathbf 1\)"的子空间上,\(\lambda_2\) 恰好是 \(x^\top L x\) 在该子空间上的最小值(Courant–Fischer 定理)。也就是说,\(\lambda_2\) 度量的是**"把整张图从一致状态拽向不一致状态,所需的最小代价"**。代价越高(\(\lambda_2\) 越大),系统越"愿意"回到一致——这就是为什么同一个 \(\lambda_2\) 既决定"图多难被切开",又决定"共识多快收敛":两者本质上是同一个"抵抗不一致的劲儿"。
四点五、手算一遍 \(\lambda_2\):从特征多项式到 Fiedler 向量¶
上面的双重含义偏抽象。我们就拿本节最早那个四节点链 \(1\!-\!2\!-\!3\!-\!4\),把 \(\lambda_2\) 和它的 Fiedler 向量**完全用手算出来**——亲手算一次,后面看到任何 \(\lambda_2\) 你都知道它从哪来、为什么是那个值。回忆它的 Laplacian:
我们要解特征方程 \(\det(L-\mu I)=0\)。直接展开 \(4\times 4\) 行列式很费劲,但这个矩阵是**对称三对角**的,可以用三对角行列式的递推。记 \(D_k\) 为左上 \(k\times k\) 主子式的行列式(把 \(L-\mu I\) 的对角元写成 \(1-\mu,\,2-\mu,\,2-\mu,\,1-\mu\),次对角元为 \(-1\)),三对角行列式满足递推 \(D_k=a_k D_{k-1}-b^2 D_{k-2}\),其中 \(a_k\) 是第 \(k\) 个对角元、\(b=-1\) 故 \(b^2=1\):
把 \(D_3\) 展开:\((2-\mu)(\mu^2-3\mu+1)=2\mu^2-6\mu+2-\mu^3+3\mu^2-\mu=-\mu^3+5\mu^2-7\mu+2\),再减 \((1-\mu)\) 得 \(D_3=-\mu^3+5\mu^2-6\mu+1\)。最后
逐项乘开 \((1-\mu)(-\mu^3+5\mu^2-6\mu+1)=-\mu^3+5\mu^2-6\mu+1+\mu^4-5\mu^3+6\mu^2-\mu=\mu^4-6\mu^3+11\mu^2-7\mu+1\),再减 \((\mu^2-3\mu+1)\):
先验证两件已知的事:常数项为 0 → \(\mu=0\) 是根(对应性质 1 的 \(L\mathbf 1=0\)),✓;四个根之和应等于 \(\operatorname{tr}(L)=1+2+2+1=6\),而多项式 \(\mu^4-6\mu^3+\dots\) 的根之和正是 \(6\),✓。现在解三次因子 \(\mu^3-6\mu^2+10\mu-4=0\)。试 \(\mu=2\):\(8-24+20-4=0\),✓——所以 \(\mu=2\) 是一个根,可因式分解出 \((\mu-2)(\mu^2-4\mu+2)=0\)。剩下二次方程 \(\mu^2-4\mu+2=0\) 给出 \(\mu=2\pm\sqrt 2\)。于是四个特征值是
所以链 \(P_4\) 的代数连通性 \(\lambda_2=2-\sqrt2\approx0.586\)。对照 §1.2 第五部分给的闭式 \(P_N\) 的 \(\lambda_2=2(1-\cos\frac{\pi}{N})\),代入 \(N=4\):\(2(1-\cos45^\circ)=2(1-\frac{\sqrt2}{2})=2-\sqrt2\),✓——手算和公式完全吻合。
再求 Fiedler 向量(对应 \(\lambda_2=2-\sqrt2\) 的特征向量),它会直观地告诉我们"链该从哪儿切"。解 \((L-\lambda_2 I)v=0\),利用链的对称性可设 \(v=(v_1,v_2,-v_2,-v_1)\)(关于中点反对称)。第一行给 \((1-\lambda_2)v_1-v_2=0\Rightarrow v_2=(1-\lambda_2)v_1=(\sqrt2-1)v_1\)。取 \(v_1=1\),得
这个向量从节点 1 到节点 4 单调递减、正好在链的中点(节点 2 与 3 之间)变号——它告诉你:把这条链一分为二、"切断边数最少"的最优切口,就在中间那条边 \((2,3)\) 上。这正是 Fiedler 向量的几何意义在最简单图上的体现:变号的位置就是图的瓶颈所在。
本质洞察:\(\lambda_2\) 的值不是凭空给的,它是 Laplacian 特征多项式的一个具体根;而 Fiedler 向量的"变号位置"精确地指出图的最薄弱切口在哪。把这两件事连起来看:\(\lambda_2\) 小(链很容易被拽离一致),恰恰因为存在一个"变号平缓、跨度很长"的模态——信息要从链的一端传到另一端,必须途经中间每一跳,这个"长程传播"本身就慢。这解释了为什么链/路径拓扑的协调最慢:不是边少,而是**直径大**——任意两端之间的最短路径长,信息接力的级数多。记住这个直觉,你看到任何"细长"拓扑(长链、大环),不用算就能预判它 \(\lambda_2\) 小、协调慢。
最后给两个**经典的 \(\lambda_2\) 上下界**,它们让你不必每次都解特征多项式就能快速估量级(均出自 Fiedler 1973 及后续代数图论):
- 上界(被最小度卡住):\(\lambda_2\le\frac{N}{N-1}\,\delta_{\min}\),其中 \(\delta_{\min}\) 是图里**最小的顶点度**。直觉是:连通性不可能超过"最孤立那个节点的连接数"太多——哪怕全图很稠密,只要有一个节点只连了一条边,它就是拖后腿的瓶颈。链 \(P_4\) 的端点度为 1,故 \(\lambda_2\le\frac43\cdot1\approx1.33\),与真实值 \(0.586\) 相容。
- 下界(被直径卡住):\(\lambda_2\ge\frac{1}{N\cdot\operatorname{diam}(G)}\) 量级(存在多个版本的精确化),其中 \(\operatorname{diam}(G)\) 是图的直径(任意两点最短路径的最大值)。直径越大,\(\lambda_2\) 下界越小——再次印证"细长图协调慢"。
这两个界合起来给你一个**不用计算就能用的工程判断**:想要 \(\lambda_2\) 大(协调快),既要避免出现度很低的"孤点"(否则被上界压住),也要压缩图的直径(否则被下界拖低)。这正是 §1.2 后面"加边要加在瓶颈、加长程捷径"那条经验的理论根。
"加边加在瓶颈"用代码验一次(这也是练习 3 哑铃拓扑的思路模板)。拿一个"路径哑铃"图:两条 3 节点链 \(0\!-\!1\!-\!2\) 与 \(3\!-\!4\!-\!5\),中间用桥 \((2,3)\) 连起来。先用 Fiedler 向量找瓶颈,再对比"加一条跨瓶颈的长程边" vs "加一条本侧内部的边"两种加法:
import numpy as np
def L_of(N, edges):
A = np.zeros((N, N))
for i, j in edges: A[i, j] = A[j, i] = 1.0
return np.diag(A.sum(1)) - A
def lam2_fiedler(L):
w, V = np.linalg.eigh(L)
return w[1], V[:, 1] # 第二小特征值及其 Fiedler 向量
N = 6
dumbbell = [(0, 1), (1, 2), (3, 4), (4, 5), (2, 3)] # 两条链 + 桥(2,3)
l2, f = lam2_fiedler(L_of(N, dumbbell))
print(f"哑铃原图: λ2 = {l2:.3f}")
print(f" Fiedler 向量 = {np.round(f, 2)} (符号在两侧间翻转 -> 瓶颈在桥处)")
# 方案甲:加一条跨瓶颈的长程边(两条链的远端 0-5,横跨 Fiedler 符号翻转处)
print(f"加跨瓶颈(0-5): λ2 = {lam2_fiedler(L_of(N, dumbbell+[(0,5)]))[0]:.3f}")
# 方案乙:加一条本侧内部的边(0-2 都在左链、Fiedler 同号,不跨瓶颈)
print(f"加本侧内部(0-2): λ2 = {lam2_fiedler(L_of(N, dumbbell+[(0,2)]))[0]:.3f}")
输出(数量级):
哑铃原图: λ2 = 0.268
Fiedler 向量 = [-0.56 -0.41 -0.15 0.15 0.41 0.56] (符号在两侧间翻转 -> 瓶颈在桥处)
加跨瓶颈(0-5): λ2 = 1.000
加本侧内部(0-2): λ2 = 0.325
读法:Fiedler 向量的分量在左链(负)和右链(正)之间**变号**,精确指出瓶颈横在桥上。加一条**跨瓶颈**的长程边(0-5,横跨变号处),\(\lambda_2\) 从 0.268 猛跳到 1.000(近四倍);而加一条**本侧内部**的边(0-2,两端 Fiedler 同号、不跨瓶颈),\(\lambda_2\) 几乎没动(0.268→0.325)。这就是故障排查表"加了通信边但共识没变快"那条 bug 的根因与解药:同样花一条边的通信代价,加在 Fiedler 变号两侧(跨瓶颈)收益是加在本侧的十几倍。所以加边前先算 Fiedler 向量定位瓶颈、把边加在变号处,再加边前后各算一次 \(\lambda_2\) 确认涨幅——这也印证了 §四点五交错定理给的上界:加对地方才吃得到 \(\lambda_3-\lambda_2\) 那段间隙。
五、常见拓扑的 \(\lambda_2\)¶
不同拓扑的 \(\lambda_2\) 差异巨大,直接决定了协调效率:
| 拓扑 | \(\lambda_2\) | 量级 | 典型用途 |
|---|---|---|---|
| 全连接 \(K_N\) | \(N\) | 最大(最快) | \(N\le 5\) 小团队 |
| 环 \(C_N\) | \(2\big(1-\cos\frac{2\pi}{N}\big)\) | 中(随 \(N\) 增大变慢) | 编队巡逻 |
| 星型 \(S_N\) | \(1\) | 固定为 1 | leader–follower |
| 路径 \(P_N\) | \(2\big(1-\cos\frac{\pi}{N}\big)\approx \frac{\pi^2}{N^2}\) | 最小(最慢) | 长链中继 |
两个值得手推一遍:
- 全连接 \(K_N\):此时 \(D=(N-1)I\)、\(A=J-I\)(\(J\) 是全 1 矩阵),故 \(L=NI-J\)。\(J\) 的特征值是 \(N\)(对应 \(\mathbf 1\))和 \(0\)(重数 \(N-1\)),于是 \(L\) 的特征值是 \(0\)(一次)和 \(N\)(\(N-1\) 次)。所以 \(\lambda_2=N\)——全连接的连通性随团队规模线性增长,最快。
- 环 \(C_N\):环图的 \(L\) 是**循环矩阵(circulant matrix)**,其特征值有闭式 \(\mu_k=2-2\cos\frac{2\pi k}{N}=4\sin^2\frac{\pi k}{N}\)(\(k=0,1,\dots,N-1\))。最小的非零值在 \(k=1\) 取得,\(\lambda_2=2\big(1-\cos\frac{2\pi}{N}\big)\)。\(N\) 越大 \(\lambda_2\) 越小,环越大协调越慢。
这里可以建立一个**跨领域类比**(并标清边界)。把通信图想成一张**弹簧网络**:每条边是一根弹簧,顶点是质点,\(x_i\) 是质点位移,则式 (1.1) 的 \(x^\top L x\) 正是这张弹簧网络的弹性势能,共识动力学 \(\dot x=-Lx\) 就是网络在阻尼下松弛到平衡(所有质点等位移)的过程。\(\lambda_2\) 对应**最低的非刚体振动模态**的频率——网络越"软"(\(\lambda_2\) 越小),这个最慢模态衰减得越慢,系统越久才稳定。这个类比像在"\(L\) 是势能矩阵、\(\lambda_2\) 是最慢模态",但不要延伸到弹簧的具体力学(图的边没有自然长度,共识也不存在惯性振荡,纯阻尼松弛)。
六、有向图与动态拓扑¶
前面假设通信对称。现实中常有**单向通信**(leader 广播、异构系统里 UAV 只把目标发给地面机器人)。此时:
- 邻接矩阵 \(A\) 不再对称,\(L=D-A\)(这里 \(D\) 用**入度**)也不对称,特征值可能是**复数**。
- 连通性的判据从"无向连通"换成"存在有向生成树(directed spanning tree)"——即存在一个根节点,沿有向边能到达所有其它节点。可以证明:\(L\) 有单重零特征值 \(\iff\) 图含一棵有向生成树。
- 收敛速率不再由实数 \(\lambda_2\) 决定,而由**最小非零特征值的实部**决定(完整的有向共识收敛分析在第 2 章)。
另一个现实复杂性是**动态拓扑(dynamic / switching topology)**:机器人在运动,通信链路随距离实时通断,\(E\) 在变,\(\lambda_2\) 也在变,甚至某些瞬间 \(\lambda_2=0\)(图暂时分裂)。这时单看某一时刻是否连通已不够,需要**联合连通性(jointly connected)**条件——只要在一段时间窗口内"图的并集"连通,共识仍能收敛。这是 Ren–Beard 2005 的核心结果,第 2 章会用到。
这里要破除一个**常见误解**,它是典型的"不是 X 而是 Y":\(\lambda_2>0\) 不只是"连通与否"的布尔开关,而是连通强度的连续度量;还有一个推论——"加一条通信边不一定能显著加速共识"。加边能否提升 \(\lambda_2\),取决于加在哪里:加在已经稠密、\(\lambda_2\) 不受限的区域几乎没用,只有加在 Fiedler 向量指示的"窄脖子"(割边/桥)上,才能显著抬高 \(\lambda_2\)。换言之,该问的不是"边够不够多",而是"瓶颈在哪条边上"。
七、加权图与加权 Laplacian¶
前面把每条通信边都当作"有或无"——邻接元非 0 即 1。但真实多机系统里链路质量并不均等:有的邻居信号强、带宽高、延迟低,有的勉强连上。把这种差异编码进来就得到**加权图(weighted graph)**:每条边带权重 \(w_{ij}>0\),邻接元 \(a_{ij}=w_{ij}\),度 \(d_{ii}=\sum_j w_{ij}\),加权 Laplacian 仍是 \(L=D-A\)。
加权后,前面的结论几乎原样成立,只是把"边数"换成"边权和"。二次型变为
\(\lambda_2\) 仍是代数连通性、收敛率仍是 \(e^{-\lambda_2 t}\),但现在 \(\lambda_2\) 由**权重**决定。由此得到一个实用结论:强链路(大 \(w_{ij}\))对协调速度的贡献更大。工程上常按链路质量(信噪比、带宽、可靠性)给边加权,让共识与编队更"信任"可靠链路。这一点第 2 章会直接用到——加权共识 \(\dot x=-Lx\) 的收敛分析与无权情形同形,只是 \(\lambda_2\) 取自加权 \(L\);ADMM 里也可用边权调节不同邻居约束的耦合强度。
动态切换拓扑的一个验证。§1.2 第六部分说过:运动中拓扑通断、\(\lambda_2\) 可能瞬时归零,但只要"时间窗内并集连通"(联合连通性),共识仍收敛。这件事跑一遍就清楚:让系统轮流使用三个**各自都不连通**的拓扑(每段只有一两条边),而三段的边并起来恰好构成连通图。结果是——任何单段都不连通,共识照样收敛到一致,只是比始终连通的拓扑慢,因为有效的 \(\lambda_2\) 被"摊薄"了。这正解释了为什么移动编队即使链路频繁通断也能维持协调,只要不长时间彻底分裂。
把它跑出来。下面让 4 个节点轮流用三个各自不连通的拓扑(每个只有一条边),三条边并起来恰是一条连通链 \(0\!-\!1\!-\!2\!-\!3\):
import numpy as np
def L_of(N, edges):
A = np.zeros((N, N))
for i, j in edges:
A[i, j] = A[j, i] = 1.0
return np.diag(A.sum(1)) - A
N = 4
seq = [[(0, 1)], [(1, 2)], [(2, 3)]] # 三个分片:各自都不连通
union = [(0, 1), (1, 2), (2, 3)] # 并集 = 连通链 P4
# 每个分片单独看连不连通:算各自 λ2
for s in seq:
print(f"分片 {s}: λ2 = {np.sort(np.linalg.eigvalsh(L_of(N, s)))[1]:.3f} (=0 即不连通)")
print(f"并集 {union}: λ2 = {np.sort(np.linalg.eigvalsh(L_of(N, union)))[1]:.3f}")
# 轮流切换分片跑离散共识,看能否仍收敛到平均
x = np.array([8.0, 2.0, -4.0, 6.0]); target = x.mean()
eps = 0.3
for k in range(400):
L = L_of(N, seq[k % 3]) # 每步换一个分片(模拟链路通断)
x = x - eps * (L @ x)
if np.max(np.abs(x - target)) < 1e-3:
print(f"切换拓扑下第 {k+1} 步收敛到平均 {target:.2f}"); break
输出(数量级):
分片 [(0, 1)]: λ2 = 0.000 (=0 即不连通)
分片 [(1, 2)]: λ2 = 0.000 (=0 即不连通)
分片 [(2, 3)]: λ2 = 0.000 (=0 即不连通)
并集 [(0, 1), (1, 2), (2, 3)]: λ2 = 0.586
切换拓扑下第 82 步收敛到平均 3.00
每个分片单看 \(\lambda_2=0\)(都不连通),按"某一时刻是否连通"的判据,任何一帧都该判"协调失败";但它们轮流出现、并集连通(并集 \(\lambda_2=0.586\)),共识照样收敛到平均值 3.00——只是慢(每步只有一条边在起作用,有效连通性被"摊薄",比始终用并集链要多花步数)。这就是联合连通性(Ren–Beard 2005)的可运行证据:移动编队链路频繁通断时,不必任一时刻都连通,只要时间窗内并起来连通,协调就不丢——这是动态拓扑下共识仍可用的理论保障。
七点五、\(\lambda_2\) 随图操作怎么变:单调性与交错定理 ⭐⭐⭐¶
前面反复用一句直觉——"加边能提升 \(\lambda_2\)、但只有加在瓶颈上才显著"。这一部分把它从"经验"升级成"定理",因为它是后续所有"动态增删通信链路"分析(移动编队链路通断、主动加边恢复连通)的理论依据。两条性质就够用:
性质一(加边单调不减):给图 \(G\) 加一条边得到 \(G'\),则**所有特征值都不减**:\(\lambda_i(G')\ge\lambda_i(G)\),对每个 \(i\)。特别地 \(\lambda_2(G')\ge\lambda_2(G)\)——加边永远不会让连通性变差。证明一行:加一条边 \((p,q)\) 相当于给 Laplacian 加一个秩 1 半正定项,\(L'=L+(e_p-e_q)(e_p-e_q)^\top\),而"半正定扰动使每个特征值不减"是 Weyl 不等式的直接推论(把二次型 \(x^\top L'x=x^\top Lx+(x_p-x_q)^2\) 代入 Courant–Fischer 极小极大刻画即得)。
性质二(加边涨幅有界——交错):加一条边后 \(\lambda_2\) 涨多少?由 Weyl 不等式还能卡住上界:\(\lambda_i(G)\le\lambda_i(G')\le\lambda_{i+1}(G)\),即新特征值被原特征值**交错(interlace)夹住。对 \(\lambda_2\) 就是 \(\lambda_2(G)\le\lambda_2(G')\le\lambda_3(G)\)。**这条上界正是"加边不一定显著提速"的根源:\(\lambda_2\) 最多只能涨到原来的 \(\lambda_3\),如果原图 \(\lambda_2\) 和 \(\lambda_3\) 本来就挨得很近(稠密图常如此),那加边几乎抬不动 \(\lambda_2\);只有当 \(\lambda_2\ll\lambda_3\)(存在一个孤立的"慢模态",即一个明显的瓶颈)时,加对地方的边才能把 \(\lambda_2\) 一举抬到接近 \(\lambda_3\)。
把两条性质合起来,"加边加在瓶颈"那条经验就有了精确版本:加边一定不亏(性质一),但收益空间被 \(\lambda_3-\lambda_2\) 这个"间隙"限死(性质二);而 Fiedler 向量告诉你该加在哪两组之间,才能真正吃满这个间隙。反过来,删边(链路断裂)则 \(\lambda_2\) 单调不增——这就是为什么移动编队一旦跑远断链,\(\lambda_2\) 会掉、协调变慢,断到图分裂时 \(\lambda_2\) 归零、协调彻底失败(回到性质 3 的连通判据)。
本质洞察:\(\lambda_2\) 对图操作的响应不是杂乱无章的,而是被两条夹逼律精确管住——加边只增不减(下界保证),但增量被 \(\lambda_3\) 顶住(上界封顶)。这把两个看似矛盾的工程经验统一了:"加通信链路总是有益的"(对,因为单调不减)和"加了链路常常没明显变快"(也对,因为被交错上界封顶)。真正的杠杆不在"加几条边",而在"原图的 \(\lambda_2\) 和 \(\lambda_3\) 之间有没有间隙可吃"——有间隙(存在瓶颈)就值得精准加边,没间隙(已经均匀稠密)就别浪费通信预算。这个判断,你用 Fiedler 向量(找瓶颈)加一次特征值计算(看 \(\lambda_3-\lambda_2\))就能做出,不必反复试加边。
八、离散时间共识与步长上界:\(\lambda_2\) 怎样换算成"轮数" ⭐⭐⭐¶
前面的 \(\dot x=-Lx\) 是**连续时间**协议,收敛率写成 \(e^{-\lambda_2 t}\)。但真实系统是离散的——机器人每隔一个通信周期交换一次信息、更新一次状态。所以工程上真正要回答的是:"达到给定一致精度,要**多少轮**通信?"这一部分把连续协议离散化,推出那个会贯穿全 Part(尤其第 2 章 ADMM)的轮数公式;它也是本章故障排查表里"每控制周期该留几轮通信"那条经验的数学根。
把 \(\dot x=-Lx\) 用步长 \(\epsilon\) 做前向欧拉离散,得到迭代
矩阵 \(P=I-\epsilon L\) 叫 Perron 矩阵——它的每一行非负且行和为 1(只要 \(\epsilon\) 足够小),所以 \(P\) 是一个**随机矩阵(row-stochastic)**,\(x[k+1]=Px[k]\) 的含义就是"每台机器人把自己和邻居的值做一次加权平均"。迭代 \(k\) 步即 \(x[k]=P^k x[0]\),收敛快慢由 \(P\) 的特征值决定。\(L\) 的特征值 \(0=\lambda_1\le\lambda_2\le\dots\le\lambda_N\) 一一映成 \(P\) 的特征值
- \(\lambda_1=0\) 映成 \(\mu_1=1\)——对应全 1 方向(共识值)永不衰减,这正是我们要保留的"平均"。
- 其余模态以 \(|\mu_i|=|1-\epsilon\lambda_i|\) 的速率衰减。误差(去掉平均后的分量)按**第二大模的模** \(\rho=\max_{i\ge 2}|1-\epsilon\lambda_i|\) 几何衰减:\(\|x[k]-\bar x\mathbf 1\|\le\rho^k\,\|x[0]-\bar x\mathbf 1\|\)。这个 \(\rho\) 叫**本质谱半径(essential spectral radius)**,是离散共识收敛快慢的唯一决定量。
收敛的前提:步长不能太大。 要让所有 \(i\ge 2\) 的 \(|1-\epsilon\lambda_i|<1\),必须
其中 \(\lambda_N\) 是 \(L\) 的**最大**特征值。直觉:\(\epsilon\) 太大时,\(1-\epsilon\lambda_N\) 会越过 \(-1\),最高频模态发散——表现为机器人状态在邻居均值两侧来回**过冲振荡**、越荡越大。这与单机数值积分里"步长超过稳定区间就发散"是同一回事,只是这里的"系统刚度"由 \(\lambda_N\) 度量。
最优步长与最快收敛。 \(\rho(\epsilon)=\max(|1-\epsilon\lambda_2|,\,|1-\epsilon\lambda_N|)\) 是两条折线的上包络:\(|1-\epsilon\lambda_2|\) 随 \(\epsilon\) 增大而下降(它管最慢模态),\(|1-\epsilon\lambda_N|\) 随 \(\epsilon\) 增大而上升(它管最快模态)。两者相等处取得最小 \(\rho\),解 \(1-\epsilon\lambda_2=-(1-\epsilon\lambda_N)\) 得
这里 \(\kappa=\lambda_N/\lambda_2\) 是 Laplacian 的**条件数**(限制在非平凡子空间上)。这个公式形态你应该觉得眼熟——它和单机数值优化里"梯度下降在条件数 \(\kappa\) 的二次型上的最优收敛率 \(\frac{\kappa-1}{\kappa+1}\)"一模一样。这不是巧合:线性共识就是在 \(\frac12 x^\top L x\) 这个二次型上做梯度下降(回忆 §三的 \(L=BB^\top\),这个二次型正是"邻居不一致的总能量"),\(\lambda_2\) 是它的最小非零曲率、\(\lambda_N\) 是最大曲率。
轮数估计。 要把误差压到初始的 \(\delta\) 倍(\(\delta\ll 1\),如 \(10^{-2}\)),需要 \(\rho^k\le\delta\),即
最后一步用了 \(\kappa\) 大时 \(-\ln\rho^\star=-\ln\frac{\kappa-1}{\kappa+1}\approx\frac2\kappa\)。式 (1.4) 就是把"\(\lambda_2\)"翻译成"轮数"的那把尺子:所需通信轮数与 \(\lambda_N/\lambda_2\) 成正比。注意分子是 \(\lambda_N\) 不是常数——这修正了一个常见的过简化("轮数只看 \(\lambda_2\)"):稠密图虽然 \(\lambda_2\) 大,但 \(\lambda_N\) 也大(度数高),两者比值才是真正的代价。
举个数:四节点链 \(P_4\),我们手算过 \(\lambda_2=2-\sqrt2\approx0.586\)、\(\lambda_N=2+\sqrt2\approx3.414\),故 \(\kappa\approx5.83\),\(\rho^\star\approx\frac{4.83}{6.83}\approx0.707\)。要降到 \(\delta=10^{-2}\),\(k\ge\ln100/\ln(1/0.707)\approx4.6/0.347\approx14\) 轮。换成全连接 \(K_4\)(\(\lambda_2=\lambda_N=4\),\(\kappa=1\),\(\rho^\star=0\)),理论上**一步**到位——这定量印证了"图越连通,协调越快"。
本质洞察:线性共识在数学上**就是**在二次型 \(\tfrac12 x^\top L x\) 上跑梯度下降,Perron 矩阵 \(P=I-\epsilon L\) 就是那一步梯度更新。这一眼看穿,把三件原本散落的事缝成了一件:(1) 共识为什么收敛——因为梯度下降在凸二次型上收敛;(2) 步长为什么有上界 \(2/\lambda_N\)——因为梯度下降步长受最大曲率限制;(3) 轮数为什么正比于 \(\lambda_N/\lambda_2\)——因为它就是这个二次型的条件数,而梯度下降的收敛率由条件数决定。所以你在单机优化里建立的全部"条件数 → 收敛"直觉,**原封不动**地适用于多机共识。第 2 章的 ADMM 之所以比朴素共识快,本质也是它用对偶变量做了"预条件",把有效条件数压下来——届时回头看这一段,你会发现它早把伏笔埋好了。
九、平均共识与权重设计:让稳态停在"平均值"上 ⭐⭐⭐¶
上一部分的 \(P=I-\epsilon L\) 收敛到 \(\bar x\mathbf 1\)(初值的算术平均),靠的是 \(L\) 行和为零这一性质。但工程里常希望用**非对称权重**或**归一化权重**(比如按链路质量加权、或让每台机器人对邻居取真正的平均而非加权和),这时要小心:乱配权重会让稳态偏离平均值,甚至不收敛。这一部分给出"既收敛、又落在平均值上"的权重设计判据——它是第 2 章分布式优化(那里每台机器人维护一个变量副本,必须收敛到全局一致)的直接基础。
把更新写成一般的加权形式 \(x_i[k+1]=\sum_j P_{ij}x_j[k]\),其中 \(P_{ij}>0\) 仅当 \(j=i\) 或 \(j\in\mathcal N_i\)。要让迭代收敛到**平均值** \(\frac1N\sum_i x_i[0]\),\(P\) 需同时满足两个条件:
- 行和为 1(row-stochastic):\(P\mathbf 1=\mathbf 1\),保证 \(\mathbf 1\) 是右特征向量、共识值不被缩放——这让"所有人相等"是一个不动点。
- 列和为 1(column-stochastic):\(\mathbf 1^\top P=\mathbf 1^\top\),保证 \(\mathbf 1^\top x[k]\) 守恒,即**所有机器人状态之和每轮不变**——这让稳态恰好停在初值平均上,而不是某个加权平均。
两者同时成立(\(P\) 双随机, doubly-stochastic)时,\(P^k\to\frac1N\mathbf 1\mathbf 1^\top\),任意初值都收敛到算术平均。\(P=I-\epsilon L\) 在无向图上天然双随机(因 \(L\) 对称、行列和都为零),这就是为什么前面能直接得到平均共识。
一个广泛使用的双随机权重构造是 Metropolis–Hastings 权重,它只用**局部信息**(各自的度数)就能让每台机器人自行算出与邻居的权重,无需全局协调:
直觉:两台机器人 \(i,j\) 之间的权重由**度数大的那一方**决定——度高的节点(邻居多)对每个邻居"分配"的权重小,避免它被过多邻居一起拉扯而过冲。对角元 \(P_{ii}\) 取"剩下的",自动保证行和为 1;又因为 \(P_{ij}=P_{ji}\)(对称),列和也为 1,于是双随机、收敛到平均值。Metropolis 权重的另一个好处是**对动态拓扑友好**:链路通断后,只有受影响的几个节点需要根据新度数重算本地权重,不必全局重新设计。
本质洞察:"收敛到一致"和"收敛到平均值"是两件不同强度的事,被两个不同的随机性条件分别管着——行随机保证收敛到某个一致值,列随机才进一步钉死这个一致值就是算术平均。很多人把共识不收敛或"收敛到一个怪值"归咎于算法 bug,其实根因常是权重矩阵丢了某一个随机性:漏了行随机就不收敛(状态被反复缩放),漏了列随机就收敛到被度数带偏的加权值(度高的机器人"话语权"过大)。记住这条判据,你设计任何分布式平均(传感器融合、分布式优化的一致性步、联邦式参数平均)时,第一件事就是检查权重矩阵是不是双随机——这比事后 debug "为什么稳态不对"省事得多。
代码验证:算 \(\lambda_2\) 并验证它决定收敛速度¶
理论说"\(\lambda_2\) 决定共识速度"。我们用代码把这件事算出来、看出来。先为三种 \(N=5\) 的拓扑构造 \(L\)、求 \(\lambda_2\),再数值积分共识动力学,验证收敛快慢与 \(\lambda_2\) 排序一致。
import numpy as np
def laplacian_from_edges(N, edges):
"""由无向边集构造 Laplacian L = D - A。"""
A = np.zeros((N, N))
for i, j in edges:
A[i, j] = 1.0
A[j, i] = 1.0 # 无向图:邻接矩阵对称
D = np.diag(A.sum(axis=1)) # 度矩阵:对角元 = 邻居数
return D - A
N = 5
K5 = [(i, j) for i in range(N) for j in range(i + 1, N)] # 全连接
C5 = [(i, (i + 1) % N) for i in range(N)] # 环
S5 = [(0, j) for j in range(1, N)] # 星(0 为中心)
for name, edges in [("全连接 K5", K5), ("环 C5", C5), ("星 S5", S5)]:
L = laplacian_from_edges(N, edges)
# 无向->L 对称->用 eigvalsh(对称专用,数值更稳、保证实特征值)
eig = np.sort(np.linalg.eigvalsh(L))
print(f"{name}: λ = {np.round(eig, 3)}, 代数连通性 λ2 = {eig[1]:.3f}")
输出:
全连接 K5: λ = [0. 5. 5. 5. 5. ], 代数连通性 λ2 = 5.000
环 C5: λ = [0. 1.382 1.382 3.618 3.618], 代数连通性 λ2 = 1.382
星 S5: λ = [0. 1. 1. 1. 5. ], 代数连通性 λ2 = 1.000
\(\lambda_2\) 排序为 \(K_5(5)>C_5(1.382)>S_5(1)\)。现在数值积分共识动力学,看收敛速度是否同序:
def run_consensus(L, x0, dt=0.01, steps=1500):
"""前向欧拉离散化 dx/dt = -L x,返回到达一致所需的有效时间常数。"""
x = x0.copy()
x_bar = x0.mean() # 平均共识值(理论稳态)
for k in range(steps):
x = x - dt * (L @ x) # 每步朝邻居平均靠拢一点
if np.linalg.norm(x - x_bar) < 1e-3 * np.linalg.norm(x0 - x_bar):
return k * dt # 误差降到 0.1% 的时刻
return steps * dt
x0 = np.array([1.0, -2.0, 3.0, 0.5, -1.5]) # 同一组初值,公平对比
for name, edges in [("全连接 K5", K5), ("环 C5", C5), ("星 S5", S5)]:
L = laplacian_from_edges(N, edges)
t = run_consensus(L, x0)
lam2 = np.sort(np.linalg.eigvalsh(L))[1]
print(f"{name}: λ2={lam2:.3f} -> 收敛到 0.1% 用时 ≈ {t:.2f}s "
f"(理论 1/λ2 ≈ {1/lam2:.3f})")
结果(数量级):全连接最快、星型最慢,收敛时间与 \(1/\lambda_2\) 大致成正比——理论得到数值证实。
有向拓扑要换一套算法(对应 §1.2 第六部分)。上面全是无向图。一旦通信单向(leader 广播、UAV 只下发不上收),邻接矩阵不再对称,\(L\) 的特征值可能是复数,eigvalsh 会算错。下面演示一个有向环 + 一个无向环的对比,并用正确的 eigvals 看复数特征值的实部:
import numpy as np
def laplacian_directed(N, dir_edges):
"""有向边 (i->j) 表示 i 能收到 j 的信息;L 用入度,可能非对称。"""
A = np.zeros((N, N))
for i, j in dir_edges:
A[i, j] = 1.0 # 只置单向,不对称回填
D = np.diag(A.sum(axis=1)) # 入度(每个节点收到几路)
return D - A
N = 4
dir_ring = [(i, (i + 1) % N) for i in range(N)] # 有向环:每个只听下一个
und_ring = dir_ring + [((i + 1) % N, i) for i in range(N)] # 对称化 -> 无向环
L_dir = laplacian_directed(N, dir_ring)
L_und = laplacian_directed(N, und_ring)
# 有向:必须用 eigvals(通用),返回复数;按实部排序看最小非零实部
ev_dir = np.linalg.eigvals(L_dir)
ev_dir = ev_dir[np.argsort(ev_dir.real)]
print("有向环 L 特征值(复数):", np.round(ev_dir, 3))
print(" 最小非零实部(定收敛速率):", round(sorted(ev_dir.real)[1], 3))
# 无向:对称,可用 eigvalsh
print("无向环 λ2(eigvalsh):", round(np.sort(np.linalg.eigvalsh(L_und))[1], 3))
# 反面教材:对有向 L 误用 eigvalsh —— 它只读下三角,结果是错的
print("⚠️ 对有向 L 误用 eigvalsh(错误):", np.round(np.sort(np.linalg.eigvalsh(L_dir)), 3))
输出(数量级):
有向环 L 特征值(复数): [ 0.+0.j 1.-1.j 1.+1.j 2.+0.j]
最小非零实部(定收敛速率): 1.0
无向环 λ2(eigvalsh): 2.0
⚠️ 对有向 L 误用 eigvalsh(错误): [-1. 0. 1. 2. ]
三件事一次看清:其一,有向环的特征值确实出现**复数**(\(1\pm j\)),收敛速率由**最小非零实部**(这里 1.0)决定,而非某个实数 \(\lambda_2\);其二,有向环含一棵有向生成树(沿环能从任一点到达所有点),所以 0 是单根、系统仍收敛;其三,对有向 \(L\) 误用 eigvalsh 会算出一个带负数的错误谱(-1 根本不该出现,因为它只读了下三角、把矩阵当成了另一个对称阵)——这正是下方编程陷阱要警告的坑,这里用代码把"错在哪"直接摆出来。
⚠️ 上一段(无向图那段)代码里也藏着同一个对有向图就会出错的写法,见下方编程陷阱。
⚠️ 常见陷阱¶
⚠️ 编程陷阱:对有向(非对称)拓扑用
np.linalg.eigvalsh- 错误描述:把上面算 \(\lambda_2\) 的代码直接套到 leader–follower 这类有向拓扑上,仍用eigvalsh。 - 现象/后果:eigvalsh只取矩阵的下三角并假设对称,对非对称 \(L\) 会**默默给出错误特征值**(它根本没看上三角),你据此算出的"\(\lambda_2\)"是错的,后续收敛分析全盘皆错。 - 根本原因:eigvalsh是 Hermitian/对称专用接口;有向图的 \(L\) 不对称,特征值可能是复数。 - 正确做法:有向图用np.linalg.eigvals(通用,返回复数),按**实部**排序,取最小非零实部;判连通用"是否含有向生成树"。在 C++ 里对应Eigen::EigenSolver(通用)而非Eigen::SelfAdjointEigenSolver(对称专用)。💡 概念误区:把 \(\lambda_2>0\) 当成只判"连通与否" - 错误描述:设计通信拓扑时,只要保证图连通(\(\lambda_2>0\))就认为达标,不关心 \(\lambda_2\) 多大。 - 现象/后果:你给一队机器人配了路径(链式)拓扑,图确实连通,但 \(\lambda_2\approx\pi^2/N^2\) 极小,共识慢得让每个控制周期的通信预算严重超时,编队响应迟钝甚至失稳。 - 根本原因:\(\lambda_2\) 是连续量,"连通"只是它 \(>0\) 这一最低要求;协调速度由其**大小**决定。 - 正确做法:把 \(\lambda_2\) 当设计指标,不只要求 \(>0\) 还要尽量大;监控运动中 \(\lambda_2\) 的最小值,作为"协调还有多少裕量"的实时指标。
🧠 思维陷阱:以为"加任意一条边都能加速共识" - 错误描述:嫌共识慢,就随便给某两台机器人之间再加一条通信链路,期望普遍提速。 - 现象/后果:你把边加在了本就稠密的区域,\(\lambda_2\) 几乎没变,通信开销白增;真正的瓶颈"桥"那条边却没动。 - 根本原因:\(\lambda_2\) 由图的"最窄切口"决定,加边只有落在瓶颈上才显著抬高它。 - 正确做法:先算 Fiedler 向量找出瓶颈(向量值差异最大的两组之间的连接),把新边加在瓶颈两侧;自检方法——加边前后各算一次 \(\lambda_2\),涨幅小说明没加在刀刃上。
💡 概念误区:Laplacian 的符号/定义混用 - 错误描述:有的资料把 Laplacian 写成 \(A-D\),或用 normalized Laplacian \(L_{\text{sym}}=I-D^{-1/2}AD^{-1/2}\),你混着用。 - 现象/后果:\(A-D\) 是 \(-L\),特征值符号全反,你会得出"系统发散"的错误结论;normalized 版的特征值范围在 \([0,2]\),数值含义也不同。 - 根本原因:不同社区(图信号处理 vs 控制)约定不同。 - 正确做法:本 Part 统一用组合 Laplacian \(L=D-A\);引用他人公式时先确认其 Laplacian 定义,必要时换算。
练习¶
- [实现] 对 \(N=6\) 构造全连接、环、星、路径四种拓扑的 \(L\),各算 \(\lambda_2\);用前向欧拉积分共识 \(\dot x=-Lx\),在同一张图上画四条收敛曲线(纵轴用对数刻度画 \(\|x(t)-\bar x\mathbf 1\|\)),验证曲线斜率(收敛速率)与各自的 \(\lambda_2\) 成正比。
- [推导] 用循环矩阵的特征值公式,手推环图 \(C_N\) 的 Laplacian 特征值为 \(2-2\cos\frac{2\pi k}{N}\)(\(k=0,\dots,N-1\)),并由此给出 \(\lambda_2\) 及其在 \(N\to\infty\) 时的渐近。提示:用小角度近似 \(1-\cos\theta\approx\theta^2/2\),代入 \(\theta=2\pi/N\),可得 \(\lambda_2=2(1-\cos\frac{2\pi}{N})\approx(2\pi/N)^2=4\pi^2/N^2\)。这解释了为什么大环编队的共识会随规模急剧变慢。
- [设计/思考] 构造一个"哑铃形"拓扑:两个各 4 节点的全连接团,中间用**一条**桥边相连。计算它的 \(\lambda_2\);然后再加一条桥边,重新计算,观察 \(\lambda_2\) 的变化幅度,并解释为什么"桥"是这张图的瓶颈。这个 \(\lambda_2\) 将在第 2 章直接决定 ADMM 与共识算法所需的迭代轮数——现在先记住:瓶颈拓扑下,分布式协调要付出更多通信轮次。
- [推导/动手] 对环 \(C_6\):(a) 算出全部特征值 \(\lambda_i=4\sin^2\frac{\pi i}{6}\)(\(i=0,\dots,5\)),给出 \(\lambda_2\) 与 \(\lambda_N\);(b) 用 \(\epsilon^\star=2/(\lambda_2+\lambda_N)\) 算最优步长,再算最优收敛率 \(\rho^\star=\frac{\kappa-1}{\kappa+1}\);(c) 用前向欧拉跑离散共识 \(x[k{+}1]=(I-\epsilon L)x[k]\),分别取 \(\epsilon=\epsilon^\star\)、\(\epsilon=0.9\cdot\frac{2}{\lambda_N}\)、\(\epsilon=1.1\cdot\frac{2}{\lambda_N}\),画三条误差曲线(对数纵轴),验证:最优步长最快、临界以下收敛、超过 \(2/\lambda_N\) 发散。思路:这道题把 §1.2 第八部分的步长上界、最优步长、发散三件事一次性看全。
- [推导] 证明 Metropolis–Hastings 权重矩阵 \(P\) 是双随机的:(a) 由对角元定义直接验证行和为 1;(b) 由 \(P_{ij}=P_{ji}\)(对称)推出列和也为 1。再说明为什么"双随机"保证共识收敛到**算术平均**而非某个加权平均——提示:用 \(\mathbf1^\top P=\mathbf1^\top\) 证明 \(\mathbf1^\top x[k]\) 每轮守恒。思路:回到 §1.2 第九部分,行随机管"收敛到一致"、列随机管"一致值=平均"。
- [设计/估算] 给定一个分布式编队,实测 \(\lambda_2=0.5\)、\(\lambda_N=8\),控制频率 50 Hz、网络往返 \(\tau_{\text{rt}}=2\) ms、通信占比 \(\gamma=0.4\)。用 §1.2 式 (1.4) 估算共识降到 \(\delta=10^{-2}\) 所需轮数 \(k_{\text{需}}\),再用 §1.5 第五部分的供给式算每周期可用轮数 \(k_{\text{供}}\),判断这套系统的实时协调是否可行;若不可行,分别给出"治拓扑"和"治时序"两条改法各一例。思路:这道题是 §1.2 与 §1.5 的合题,练的是"供给 vs 需求两边各算一个数"的判断习惯。
上一节我们学会了"谁能和谁通信"的数学语言,并用 \(\lambda_2\) 量化了协调效率。但通信结构只是多机系统的一半;另一半是**机器人本身**——它们的动力学是相同还是不同?这个区别(同构 vs 异构)将决定我们能用哪类建模与控制方法。§1.3 把单台机器人的状态/控制"拼接"成多机的联合系统,并揭示同构与异构在建模上的根本分野,以及它为什么会一路影响到第 6 章的异构协同与第 10 章的强化学习范式选择。
§1.3 同构与异构系统建模 ⭐⭐¶
这一节解决什么问题:通信结构(§1.2)只是多机系统的一半,另一半是**机器人本身**——它们的动力学是相同还是不同?这个看似简单的区别(同构 vs 异构)是一道建模分水岭:它决定了你能不能用"一套模型 / 一个共享策略"管所有机器人,会一路影响到第 6 章的异构协同和第 10 章的强化学习范式选择。
动机:为什么"机器人是否相同"是道分水岭¶
对比两个系统:两台 Unitree Go2 协同搬运,和**一台 Go2 加一架四旋翼**协同巡检。
第一个系统里,两台机器人动力学完全一样——同样的腿、同样的运动方程。第二个系统里,四足在地上走、无人机在天上飞,运动方程从根上不同。这个差别不只是"系统里多了一种机器人那么简单"——它改变了你**能用的方法**。同构系统因为所有 agent 一模一样,可以用一套动力学模型描述所有机器人,可以训练**一个**策略网络让所有机器人共享;异构系统做不到这两点。所以在动手建模前,先判断"同构还是异构",决定了后面整条技术路线。
如果把异构当同构会怎样¶
最容易踩的坑,是不假思索地把异构系统当同构处理。假设你想用一个共享的策略网络同时控制 Go2 和无人机。问题立刻出现:四足的观测是 IMU 加 12 个关节角,无人机的观测是姿态加 4 个旋翼转速——观测维度都对不上;四足的动作是 12 维关节指令,无人机的动作是 4 维(总推力 + 三轴力矩)——动作维度和语义也对不上。结果要么网络的输入输出形状直接报错,要么你强行用零填充(padding)把维度对齐,但这样四足和无人机的特征在同一个网络里互相干扰,两类机器人都学不好。把异构当同构,不是"效果差一点",而是方法根本不成立。
理论¶
一、同构系统:一套动力学,可参数共享¶
**同构(homogeneous)**系统里,所有 agent 共享同一个动力学函数:
把 \(N\) 台机器人的状态、控制各自堆叠成**联合状态**与**联合控制**:
联合动力学是一个**块对角**结构,再加上把机器人耦合在一起的项(典型是共享负载的接触力):
同构带来一个极有价值的性质:对称性。因为每一块都是同一个 \(f\),你可以用**一个**共享参数的策略 \(\pi_\theta(u_i\mid o_i)\) 控制所有 agent——这正是第 10 章 MAPPO 高效的根源(一个网络,所有 agent 的经验都拿来训练它);系统对 agent 的编号也是**排列不变**的(交换两台机器人不改变系统行为),这是第 11 章可扩展策略的基础。
二、异构系统:各异的动力学,不可参数共享¶
**异构(heterogeneous)**系统里,各 agent 的动力学不同:
它们通过共享负载或共同任务约束耦合。异构系统**不能**用参数共享策略,因为参数共享要求所有 agent 同观测空间、同动作空间、同语义,而异构恰好违反这三条。出路有两条,这里先点名,细节分别在后面展开:(a) HAPPO——每个 agent 配独立的 actor/critic,顺序更新,且有单调改进的理论保证(第 10 章详讲);(b) 用 attention/Transformer 把不同维度的异构输入统一编码(第 6 章讨论)。
异构还带来一个同构系统里不存在的设计问题:角色分配。同构系统里 agent 对称,谁干什么可以临时商量;异构系统里各 agent 能力不同,往往天然形成分工。两种典型模式:其一是**领导-跟随(leader-follower)——一个 agent(常是感知/算力更强的那个)做主导决策,其余跟随,对应 §1.2 里的有向星型拓扑;典型如地空协同中无人机在高处俯瞰、为地面机器人提供全局视野和目标点,地面机器人负责力量输出。其二是**能力互补的对等协作——各 agent 各司其职、没有单一领导,比如四足提供承载与稳定、机械臂提供精细操作。角色怎么分,直接影响通信拓扑(领导-跟随是有向的)和控制结构(领导的决策成为跟随者的约束)。这条线会在第 6 章异构协同里展开,那里地面四足与空中无人机的分工正是一个完整的异构角色分配案例。
三、联合动力学完整算例:Go2 + 四旋翼¶
把上面抽象的"块对角 + 耦合"用一个具体异构系统算清楚。设一台 Go2 四足在地面承载负载,一架四旋翼在上方通过一根轻绳对负载施加一个辅助拉力 \(f_{\text{cable}}\)。
四足(简化为单刚体 SRB):状态 \(x_q=[\,p_q,\ \Theta_q,\ v_q,\ \omega_q\,]\in\mathbb{R}^{12}\)(位置、欧拉角、线速度、角速度各 3 维),控制 \(u_q=[f_1,f_2,f_3,f_4]\in\mathbb{R}^{12}\)(四个足端力):
四旋翼(SE(3) 模型):状态 \(x_d=[\,p_d,\ \Theta_d,\ v_d,\ \omega_d\,]\in\mathbb{R}^{12}\)(分量顺序与上面四足一致:位置、姿态、线速度、角速度),控制 \(u_d=[T,\ \tau_x,\ \tau_y,\ \tau_z]\in\mathbb{R}^{4}\)(总推力 + 三轴体力矩):
注意绳力 \(f_{\text{cable}}\) 以**作用-反作用**的方式同时进入两者:四足受 \(+f_{\text{cable}}\),无人机受 \(-f_{\text{cable}}\)。把两者拼成联合系统:
这个算例把异构的两个特征都暴露了:两块 \(f\) 形式不同(SRB vs SE(3)),控制维度不齐(12 维 vs 4 维)。块对角结构在,但每一块不一样——这正是异构的数学标志,也正是"不能用一个 \(f\)、一个共享网络套所有 agent"的根本原因。对照之下,同构的"双 Go2 + 刚性杆"系统里,两块 \(f\) 是同一个 SRB、控制都是 12 维,块对角结构对称,才有了参数共享的可能。
四、为什么异构不能参数共享(伏笔第 10 章)¶
这里把"不能参数共享"的逻辑钉死,因为它是一处典型的**"不是 X 而是 Y":**异构不能参数共享,不是因为技术不成熟、等算法进步就能解决,而是观测/动作空间语义不同这个本质障碍。参数共享的前提是"所有 agent 是同一函数的不同实例";异构 agent 压根不是同一函数。第 10 章会看到 HAPPO 如何用"每 agent 独立策略 + 顺序更新 + 优势分解引理"绕过这个障碍并保留单调改进保证——但那是**放弃**参数共享换来的,不是**实现**了参数共享。
本质洞察:同构与异构的分野,本质是"联合系统的块对角结构里,各块是否相同"。各块相同 → 系统有对称性可利用(参数共享、排列不变、一套模型管全部);各块不同 → 每一块都得单独建模、单独配策略。你在判断一个多机系统该用哪类方法时,真正该问的不是"机器人长得像不像",而是"它们的运动方程是不是同一个函数"。
四点五、同构-异构其实是一条连续谱(群组同构与排列等变)¶
把世界硬切成"同构"和"异构"两类,便于建立直觉,但真实系统常常落在两者**之间**。这里把这条**连续谱**铺开,免得你拿着非黑即白的标尺去套一个灰色的系统。从最对称到最不对称,至少有四档:
| 档位 | 定义 | 例子 | 策略方案 |
|---|---|---|---|
| 严格同构 | 所有 agent 同一个 \(f\)、同维度、同参数 | 仿真里完全一致的 \(N\) 台 Go2 | 参数共享(MAPPO) |
| 参数化同构 | 同 \(f\)、同维度,仅物理参数不同 | Go2 + Go1(都是 SRB 四足) | 参数共享 + 域随机化 |
| 群组同构(grouped / typed) | 分成若干**组**,组内严格同构、组间异构 | 5 台 Go2 + 3 架同型无人机 | 每组一个共享策略(组内共享、组间独立) |
| 完全异构 | 各 agent 的 \(f\) 或维度都不同 | Go2 + 无人机 + 双臂移动平台,各一台 | HAPPO / Transformer |
中间那档——群组同构(grouped homogeneity)——在工程上最常见,也最容易被忽视。它的关键洞察是:对称性不必是"全体对称",也可以是"组内对称"。一个由 5 台 Go2 加 3 架同型无人机组成的系统,整体上是异构的(四足 ≠ 无人机),但"5 台 Go2 之间"和"3 架无人机之间"各自严格同构。正确的做法不是因为"整体异构"就给 8 台各配一个独立网络(白白丢掉组内对称),也不是强行用一个网络套两类(语义冲突),而是**按类型分组,每组共享一个策略**——四足共用一个 \(\pi_{\text{quad}}\)、无人机共用一个 \(\pi_{\text{uav}}\)。这样既利用了组内的数据效率,又尊重了组间的语义差异,是 §1.3 的同构/异构二分法在真实异构队伍里的自然推广(第 6、10 章的异构 MARL 正是这样按类型分组的)。
这里顺带把 §1.3 第一部分提过、但容易混淆的一对概念讲透——排列不变(permutation-invariant) 与 排列等变(permutation-equivariant),它们是"同构对称性"在网络设计上的两种不同用法:
- 排列不变:输入是一**组** agent 的状态(无序集合),输出是一个**整体**的量(如团队是否达成编队、全局价值 \(V\)),这个输出**不随 agent 编号的重排而改变**。形式上,对任意置换 \(P\),\(g(P\cdot x)=g(x)\)。典型实现是对各 agent 特征做一个**对称聚合**(求和/平均/max),如 \(g(x)=\rho\big(\sum_i \phi(x_i)\big)\)——因为求和本身与顺序无关,整个函数自然排列不变。第 10 章 MAPPO 的**中央 critic** 就需要这个性质:它给整个团队打一个分,重排 agent 不该改变这个分。
- 排列等变:输入是一组 agent 的状态,输出是**每个 agent 各一个**的量(如每个 agent 的动作),且当输入重排时,输出**按同样的方式重排**。形式上,\(h(P\cdot x)=P\cdot h(x)\)。共享策略 \(\pi_\theta(u_i\mid o_i)\) 配上"对邻居做对称聚合"的网络(如图神经网络 GNN、或 mean-embedding)就是排列等变的:换两台机器人的编号,它们的动作也跟着换,但每台机器人"看到同样的局部情形就做同样的决策"这一点不变。第 11 章用排列等变的网络,使策略能**零成本扩展到任意 \(N\)**——因为网络结构不绑定具体的机器人个数。
本质洞察:同构系统的"对称性"红利,在数学上就是**系统对 agent 编号的置换群作用是不变(或等变)的**。这不是一句空话——它直接决定了你该用哪种网络结构:要团队级输出就用排列不变(对称聚合成标量),要每 agent 输出就用排列等变(对称聚合 + 逐 agent 解码)。一旦系统变成群组同构,这个对称性就从"全置换群 \(S_N\)"缩小为"组内置换群的乘积 \(S_{N_1}\times S_{N_2}\times\dots\)"——网络也要相应地"按组共享"。判断该用哪种网络,本质上是在问"这个系统在哪个置换群下不变"。把这一点想清楚,第 10、11 章的网络设计你就不会觉得是凭空冒出来的技巧,而是对称性的自然结果。
五、刚性约束与耦合力 \(\boldsymbol\lambda\)(同构搬运的前置)¶
第三部分的算例用无人机绳力 \(f_{\text{cable}}\) 做耦合。这里把最常见的耦合——刚性约束——单独推一遍,因为它是第 4 章 SRB 网络的直接前置。设两台 Go2 合抬一根刚性杆,各自附着点 \(p_1^{\text{att}}\)、\(p_2^{\text{att}}\) 之间距离固定:
对时间求导,得到对速度的约束(附着点相对速度为零):
其中 \(J_i\) 是从机器人状态到其附着点速度的 Jacobian。这是一个**等式约束**,在动力学中通过**拉格朗日乘子 \(\boldsymbol\lambda\)**(约束力)来满足——\(\boldsymbol\lambda\) 以**作用-反作用**形式分别进入两台机器人的运动方程:
\(\boldsymbol\lambda\) 就是两台机器人通过刚性杆"对拉/对推"的内部力。它正是 §1.1 里"协调失败时互相较劲"的那个力,也正是第 4 章分布式 MPC 要用 ADMM 协调的耦合量:届时每台机器人本地解自己的 MPC,\(\boldsymbol\lambda\) 通过 ADMM 的一致性步在邻居间达成一致,从而既满足刚性约束、又无需中央求解整个联合系统。把这个 \(\boldsymbol\lambda\) 记住——它是贯穿"建模(本章)→ 分布式优化(第 2 章)→ 分布式 MPC(第 4 章)→ 力分配(第 5 章)"的一条主线。
\(\boldsymbol\lambda\) 到底怎么解出来? 上面只写了 \(\boldsymbol\lambda\) 进入方程的形式,没说它的值从哪来——而"忘了解 \(\boldsymbol\lambda\)、把它当成可以随便填的外力"正是本章故障排查表里"两机抬杆穿模"那条 bug 的根源。这里把它一次性推清楚,你以后写任何带约束的联合仿真都用得上。把两台机器人的运动方程和约束放在一起,就是一个标准的 KKT 系统(约束动力学的鞍点形式)。为简洁记联合无约束加速度 \(\dot v=(\dot v_1,\dot v_2)\)、联合质量阵 \(M=\mathrm{diag}(m_1 I,m_2 I)\)、约束 Jacobian \(J=[\,J_1\ \ -J_2\,]\)(把速度约束写成 \(J\dot v=0\) 的形式),外力(足底力 + 重力)记 \(b=(\sum_i f_{1,i}-m_1 g e_3,\ \sum_i f_{2,i}-m_2 g e_3)\)。则
第一行是牛顿方程 \(M\dot v=b+J^\top\boldsymbol\lambda\)(约束力以 \(J^\top\boldsymbol\lambda\) 进入,符号已含在 \(J\) 的拼法里),第二行是把速度约束 \(J\dot v=0\) 再求一次导得到的**加速度级约束** \(J\dot v+\dot J v=0\)(必须在加速度级施加,否则约束会数值漂移——这点见下)。从 (1.5) 消去 \(\dot v\)(第一行解出 \(\dot v=M^{-1}(b+J^\top\boldsymbol\lambda)\) 代入第二行),得到 \(\boldsymbol\lambda\) 的闭式:
中间那个 \(JM^{-1}J^\top\) 是 (1.5) 的 Schur 补,物理上是"约束方向上的等效惯量"(也叫 operational-space inertia 的约束版)——它一定可逆,只要约束彼此独立(\(J\) 行满秩)。式 (1.6) 的含义很实在:约束力 \(\boldsymbol\lambda\) 不是自由参数,而是被"维持约束"这件事唯一确定的——给定两台机器人当前想施加的足底力 \(b\),\(\boldsymbol\lambda\) 自动取那个"恰好抵消掉会破坏刚性约束的相对加速度"的值。
一个能心算的退化算例(把上面具体到数,验证直觉)。设两台机器人简化成沿一维滑动的质点 \(m_1=m_2=m\),刚性杆约束它们间距固定,即 \(\dot v_1-\dot v_2=0\),故 \(J=[1,-1]\)、\(\dot J=0\)。设机器人 1 想加速(\(b_1=F\))、机器人 2 不出力(\(b_2=0\))。算 Schur 补:\(JM^{-1}J^\top=\frac1m+\frac1m=\frac2m\);再算 \(JM^{-1}b=\frac{F}{m}-0=\frac{F}{m}\)。代入 (1.6):
于是 \(\dot v_1=\frac1m(F+\lambda\cdot 1)=\frac1m(F-\frac F2)=\frac{F}{2m}\),\(\dot v_2=\frac1m(0-\lambda\cdot 1)=\frac1m(\frac F2)=\frac{F}{2m}\)——两者加速度相等,约束满足 ✓。物理解读:机器人 1 施加的力 \(F\) 有一半通过杆"分"给了机器人 2(内力 \(|\lambda|=F/2\) 把机器人 2 一起拽动),最终两者像质量 \(2m\) 的整体一样以 \(F/(2m)\) 加速。这正是 §1.1 那句"一台机器人的动作会通过刚性负载传给另一台"的定量版——内力 \(\boldsymbol\lambda\) 就是传递的媒介。
本质洞察:耦合力 \(\boldsymbol\lambda\) 不是建模时"额外加的一项",而是约束本身的**对偶变量**——它被 KKT 系统 (1.5) 和约束一起唯一确定。这件事有两个直接推论,都会在后面反复用到:其一,任何刚性/绳/接触约束的联合仿真,都必须解一个鞍点系统(1.5)而非各积分各的——漏掉 \(\boldsymbol\lambda\) 就等于撤掉了约束,杆必然穿模或被拉长(故障排查表那条 bug 的根因)。其二,\(\boldsymbol\lambda\) 作为对偶变量这一身份,正是第 2 章 ADMM 能"分布式地"协调它的前提——ADMM 本就是一套求解带耦合约束问题的对偶方法,它让每台机器人本地估计 \(\boldsymbol\lambda\)、再通过一致性步对齐,等价于分布式地解 (1.5)。所以你现在写的这个鞍点系统,到第 4 章会原样变成分布式 MPC 要切分的那个联合 KKT。
补一句工程上极易踩的坑:约束要在**加速度级**施加(式 1.5 第二行用的是 \(J\dot v+\dot J v=0\)),但纯加速度级约束在数值积分中会让位置/速度约束**缓慢漂移**(积分误差累积,杆会肉眼可见地慢慢变长)。实际仿真用 **Baumgarte 镇定**或后投影把位置/速度残差拉回零——把第二行改成 \(J\dot v+\dot J v=-2\alpha(J v)-\beta^2(\text{位置残差})\),用阻尼项主动消除漂移。这就是为什么故障排查表里建议"每步监控附着点约束残差":残差不为零且单调增长,就是漏了镇定项的信号。
把"漏 \(\boldsymbol\lambda\) 就穿模、解 \(\boldsymbol\lambda\) 就守约束"这件事跑出来——两质点用刚性杆约束(间距应恒为 \(d_0\)),各施加不同外力,对比三种积分:
import numpy as np
m1, m2, d0, dt = 1.0, 3.0, 2.0, 0.01 # 不等质量,初始间距 d0
p1, p2, v1, v2 = 0.0, d0, 0.0, 0.0
F1, F2 = 2.0, 0.0 # 机器人 1 出力,机器人 2 不出力
J = np.array([1.0, -1.0]) # 约束 v1 - v2 = 0(间距导数为零)
Minv = np.diag([1/m1, 1/m2])
def step_independent(p1, p2, v1, v2): # 反面:当成两个独立 ODE,不解约束
v1 += dt*F1/m1; v2 += dt*F2/m2
return p1+dt*v1, p2+dt*v2, v1, v2
def step_constrained(p1, p2, v1, v2): # 正确:解 KKT 求 λ,Baumgarte 镇定
b = np.array([F1, F2])
schur = J @ Minv @ J # JM^{-1}J^T(标量)
drift = -2*5.0*(J @ [v1, v2]) - 25.0*((p1 - p2) - (-d0)) # 速度+位置残差镇定
lam = -(J @ Minv @ b - drift) / schur
a = Minv @ (b + J*lam)
v1 += dt*a[0]; v2 += dt*a[1]
return p1+dt*v1, p2+dt*v2, v1, v2
for name, fn in [("独立积分(漏λ)", step_independent), ("KKT+镇定(解λ)", step_constrained)]:
a1=a2=b1=b2=None; q1,q2,w1,w2 = p1,p2,v1,v2
for _ in range(300): q1,q2,w1,w2 = fn(q1,q2,w1,w2)
print(f"{name}: 300 步后间距 = {abs(q2-q1):.4f} (应保持 {d0})")
输出(数量级):
"独立积分"那行,间距从 2.0 一路涨到 7.0(机器人 1 在恒力下匀加速远离、机器人 2 原地不动,杆被活活拉长三倍多)——这就是故障排查表"杆被拉长/穿模"那条 bug 的可运行复现:漏掉约束力 \(\boldsymbol\lambda\),两质点各走各的;"KKT+镇定"那行解出 \(\boldsymbol\lambda\) 并施加,间距纹丝不动地守在 2.0。把这两行并排看,你就再也不会把多机刚性耦合"当成 \(N\) 个独立 ODE 各积分各的"了。
代码验证:联合维度与块结构记账¶
抽象的维度账容易算错。下面这段代码把同构与异构两种系统的联合状态/控制维度、以及块结构直接打印出来——它能让你对"异构维度不齐"有量的感受,也是后面写联合 MPC 时排查维度 bug 的基础。
class Agent:
def __init__(self, name, n_state, n_ctrl, dyn):
self.name, self.n_state, self.n_ctrl, self.dyn = name, n_state, n_ctrl, dyn
def joint_dims(agents):
"""拼接联合状态/控制维度,并判断是否同构。"""
nx = sum(a.n_state for a in agents)
nu = sum(a.n_ctrl for a in agents)
# 同构判定:所有 agent 的动力学函数与维度都相同
homo = len({(a.dyn, a.n_state, a.n_ctrl) for a in agents}) == 1
return nx, nu, homo
# 同构:双 Go2 + 刚性杆(两块都是 SRB,控制都是 12 维)
homo_sys = [Agent("Go2_0", 12, 12, "SRB"), Agent("Go2_1", 12, 12, "SRB")]
# 异构:Go2 + 四旋翼(SRB vs SE3,控制 12 vs 4)
hetero_sys = [Agent("Go2", 12, 12, "SRB"), Agent("Quadrotor", 12, 4, "SE3")]
for label, sys in [("同构(双Go2)", homo_sys), ("异构(Go2+四旋翼)", hetero_sys)]:
nx, nu, homo = joint_dims(sys)
ctrl_dims = [a.n_ctrl for a in sys]
print(f"{label}: 联合状态={nx}, 联合控制={nu}, 各控制维度={ctrl_dims}, "
f"同构={homo} -> {'可参数共享' if homo else '需HAPPO/独立策略'}")
输出:
同构(双Go2): 联合状态=24, 联合控制=24, 各控制维度=[12, 12], 同构=True -> 可参数共享
异构(Go2+四旋翼): 联合状态=24, 联合控制=16, 各控制维度=[12, 4], 同构=False -> 需HAPPO/独立策略
两个系统联合状态都是 24 维,但异构系统的控制维度 \([12,4]\) 参差不齐——这一行 homo=False 就是你后面选 HAPPO 而非 MAPPO 的判据。
⚠️ 常见陷阱¶
💡 概念误区:把"同型号不同参数"当成异构 - 错误描述:看到一队机器人尺寸/质量不一(比如 5 台 Go2 + 2 台体型更小的 Go1),就判定为异构,上 HAPPO。 - 现象/后果:你为本可参数共享的系统配了独立策略,白白放弃了同构带来的数据效率(所有 agent 经验共用),训练慢一截。 - 根本原因:异构与否看**动力学结构/维度**是否相同,而非参数。Go2 和 Go1 都用 SRB、状态/控制维度相同,只是质量惯量不同——这叫**参数化同构**,正确做法是用**域随机化**覆盖参数差异,仍然参数共享。 - 正确做法:先看动力学函数 \(f\) 和维度是否一致;一致就是(参数化)同构,用 DR;只有 \(f\) 结构或维度不同(四足 vs 无人机)才是真异构。
⚠️ 编程陷阱:拼联合状态时漏掉耦合项 - 错误描述:把多机系统当成 \(N\) 个独立 ODE,各积分各的,忘了它们通过负载/接触耦合。 - 现象/后果:仿真里负载约束被违反——两台机器人抬的刚性杆被"拉长"或"穿模",或者各自轨迹正确但合起来物理上不可能。 - 根本原因:联合动力学不是 \(N\) 个块的简单并列,还有 \(g_{\text{couple}}\)(共享负载力、刚性约束)。 - 正确做法:显式写出耦合项(如绳力 \(f_{\text{cable}}\) 的作用-反作用、刚性附着的约束力 \(\boldsymbol\lambda\)),并在每步积分中施加;自检方法——监控负载上的约束残差(如附着点相对位移),应始终接近零。
🧠 思维陷阱:给异构 agent 强行套共享网络 - 错误描述:为了"统一架构",把不同类型机器人的观测/动作用零填充到同一维度,喂给一个共享策略。 - 现象/后果:不同机器人的特征在同一网络里互相污染,训练不收敛或各类机器人都次优。 - 根本原因:参数共享假设 agent 同语义;padding 只对齐了维度,没对齐语义。 - 正确做法:真异构用 HAPPO(每 agent 独立网络)或 Transformer(用 attention 处理变长异构输入,而非粗暴 padding);把方案选择留到第 6、10 章。
练习¶
- [推导/实现] 为"双 Go2 + 一根刚性杆"写出联合状态、联合控制的维度,以及块对角加耦合的动力学结构(刚性杆用约束力 \(\boldsymbol\lambda\) 耦合);再为"Go2 + 四旋翼 + 绳"写出同样的结构。把两者的块结构并排画出,标出"哪里相同(同构对称)、哪里不同(异构)"。
- [分析] 一队机器人由 5 台 Go2 和 2 台 Go1 组成(都是宇树四足,但 Go1 更小更轻)。它是同构还是异构?给出判断依据,并说明应该用"参数共享 + 域随机化"还是 HAPPO,理由是什么。
- [思考/预告] 异构系统的强化学习为什么比同构更难?先记住核心障碍——动作空间的维度和语义不一致,无法用一个网络共享。两条出路(HAPPO 的顺序更新 / Transformer 统一异构输入)分别在第 10 章和第 6 章展开,这里只需建立"异构 → 不能参数共享 → 要么 HAPPO 要么 attention"这条判断链。
- [推导/动手] 沿用 §1.3 第五部分的一维双质点退化算例(\(m_1=m_2=m\),刚性杆约束 \(\dot v_1=\dot v_2\),\(J=[1,-1]\))。(a) 改成机器人 1 出力 \(F\)、机器人 2 反向出力 \(-F/2\),重解 KKT 系统 (1.5),给出 \(\boldsymbol\lambda\) 与两者加速度,并验证约束仍满足;(b) 把质量改成不等 \(m_1=1,\ m_2=3\),重算 Schur 补 \(JM^{-1}J^\top\) 与 \(\boldsymbol\lambda\),观察内力如何按质量重新分配。思路:重质量端"分"到的加速度小、承受的内力按 \(M^{-1}\) 加权,体会 \(\boldsymbol\lambda\) 是被约束唯一钉死的、不是自由量。
- [实现/排错] 写一个最小二维仿真:两质点用刚性杆约束,各自施加随机外力。先**故意**不解约束力(当成两个独立 ODE 积分),画出杆长随时间的变化——你会看到它漂移(违反约束);再按式 (1.6) 解出 \(\boldsymbol\lambda\) 并施加,画出杆长应几乎恒定;最后只在加速度级施加约束(无 Baumgarte),长时间积分观察缓慢漂移,加上镇定项后消失。思路:这道题把故障排查表"抬杆穿模/杆变长"那条 bug 亲手复现一遍再修复,印象远比读一遍深。
到这里,描述多机系统所需的两套语言已经齐备:§1.2 的图论刻画"谁能和谁通信",§1.3 的同构/异构刻画"机器人本身是否相同"。但这些都还停留在数学与建模层面。一个真实的多机系统最终要在软件里跑起来——多台机器人各自有一套节点和坐标系,它们如何在同一个 ROS 2 系统里互不打架地共存、又能彼此通信?§1.4 把前三节的概念落到工程地面:多命名空间、tf2 多坐标系树、DDS 发现的扩展性,以及一个常被忽视却频繁致命的问题——多机之间的时间同步。
§1.4 ROS 2 多机工程化 ⭐⭐¶
这一节解决什么问题:前三节都在数学与建模层面。一个真实多机系统最终要在软件里跑起来——多台机器人各有一套节点和坐标系,如何在同一个 ROS 2 系统里互不打架地共存,又能彼此通信?本节把概念落到工程地面:命名空间、
tf2多坐标系树、DDS 发现的扩展性,以及一个常被忽视却频繁致命的问题——多机时间同步。
动机:从单机 ROS 2 到多机的四个新问题¶
单机时代,你的系统是一套话题(/odom、/cmd_vel)、一棵 tf 树(odom → base_link),清清爽爽。一旦把两台机器人放进同一个 ROS 2 系统,四个新问题立刻冒出来:
- 命名冲突:两台机器人都发布
/odom,订阅者收到的是混在一起的两台数据,分不清谁是谁。 - 坐标系冲突:两个
base_link同时存在于一棵tf树里,变换查询会随机解析到错误的那个。 - 发现风暴:ROS 2 用 DDS 去中心化发现,每个节点要发现所有其它节点;机器人(及其节点)一多,发现流量按节点数平方膨胀,启动变慢、带宽被吃满。
- 时间不同步:不同机器上的时钟各自漂移,传感器时间戳对不齐——这是最隐蔽的杀手,系统不报错却行为错乱。
如果不做隔离会怎样¶
最朴素的做法是把两台机器人用默认话题名直接跑起来。结果:/cmd_vel 串台,你给一台发的速度指令同时驱动了两台;tf 树里两个 base_link 冲突,某台机器人查询"末端在 world 下的位姿"时随机拿到另一台的数据;控制器收到的是两台混合的里程计——系统看起来"跑起来了",但行为诡异且每次启动结果都不一样。多机工程的第一课,就是主动把这些冲突隔离开。
历史/工程背景:ROS 2 的去中心化发现¶
这里先点一句架构背景。ROS 1 有一个中央 master 节点登记所有话题;ROS 2 取消了 master,改用 **DDS(Data Distribution Service)**的去中心化发现——节点上线时通过组播互相"广播自己、发现别人"。这天然支持多机(没有单点),但也带来了上面第 3 个问题:发现本身的扩展性。理解这一点,才能理解后面为什么大规模多机要换发现机制。
实践¶
一、命名空间隔离¶
给每台机器人一个**命名空间(namespace)**,它名下所有话题、服务、参数、tf 帧都自动加上前缀(/go2_0/odom、/go2_1/odom)。下面是一个**可跑的**双 Go2 多命名空间 launch 文件结构:
from launch import LaunchDescription
from launch.actions import GroupAction
from launch_ros.actions import Node, PushRosNamespace
def generate_launch_description():
robots = []
for i in range(2): # 两台 Go2
ns = f'go2_{i}'
group = GroupAction([
PushRosNamespace(ns), # 本组所有节点/话题加前缀 /go2_i/
Node(
package='go2_bringup', executable='controller', name='controller',
parameters=[{'agent_id': i, 'neighbor_ids': [1 - i]}],
remappings=[
# 跨机共享话题:显式 remap 到全局命名空间,而非留在本地前缀下
('neighbor_odom', f'/go2_{1 - i}/odom'),
],
),
Node(
package='robot_state_publisher', executable='robot_state_publisher',
name='rsp',
parameters=[{'frame_prefix': f'{ns}/'}], # tf 帧自动加 go2_i/ 前缀
),
])
robots.append(group)
return LaunchDescription(robots)
两个要点:PushRosNamespace 让本组所有话题自动加前缀,解决命名冲突;但**跨机要共享的话题(如读邻居的里程计)必须显式 remap 到全局名**,否则它会被困在本地前缀下,邻居读不到。
二、tf2 多坐标系树¶
单机的 tf 树是 odom → base_link。多机要在它们之上加一个全局根 world,每台机器人挂在自己的子树下:
分工是:每台机器人在自己的命名空间下发布 odom → base_link(上面 launch 里 robot_state_publisher 的 frame_prefix 参数会自动把帧名加上 go2_i/ 前缀);一个全局定位节点(或协调器)发布 world → go2_i/odom,把各台机器人锚定到同一个世界坐标系。关键纪律:所有帧名必须带命名空间前缀(go2_0/base_link 而非裸 base_link),否则两台机器人的 base_link 在同一棵 tf 树里冲突。
这里有个**类比**帮助记忆(并标边界):命名空间就像**文件系统的目录**——/go2_0/odom 像一条路径,靠目录前缀避免重名;tf 树就像三维场景里的**骨骼/层级变换**,父帧变换会传递给子帧。类比像在"层级 + 前缀避免冲突",但不要延伸过头:tf 帧之间是连续的几何变换(带时间戳、可插值),而文件路径只是静态字符串。
三、DDS 发现的扩展性与 Zenoh¶
ROS 2 默认的 Simple Discovery 是组播式的:每个 DDS 参与者向所有参与者广播自己的端点信息,端点匹配的流量随参与者数大致按平方增长。几台机器人没问题,但当机器人(及其节点)数量上到几十,发现流量会形成"风暴"——启动缓慢、偶发丢消息、带宽被发现流量吃掉。缓解手段有三类:
| 方案 | 机制 | 发现复杂度 | 适用 |
|---|---|---|---|
| Simple Discovery(默认) | 组播,人人发现人人 | \(O(N^2)\) | 小规模(\(N\) 少),同一局域网 |
ROS_DOMAIN_ID 分域 |
不同域互不发现 | 隔离 | 把无关机器人/子系统分到不同域 |
| Discovery Server(Fast DDS) | 集中式发现服务器 | \(O(N)\) | 中大规模,降低发现流量 |
Zenoh(rmw_zenoh) |
替代 RMW,路由式 | 可扩展 | 大规模、跨网络、有损/弱网链路 |
ROS_DOMAIN_ID 是最简单的隔离手段(把不相干的机器人放进不同域,根本不互相发现);Discovery Server 把"人人发现人人"换成"都去问一台服务器",把 \(O(N^2)\) 压到 \(O(N)\);Zenoh 则是替代整个 DDS 中间件的新选项——它在 ROS 2 Jazzy(2024-05)首次以实验性 RMW(rmw_zenoh)登场、需从源码编译,到 ROS 2 Kilted(2025-05)正式成为 Tier 1 RMW。其路由式架构(节点经 Zenoh router 用 gossip 交换发现信息,默认关闭组播)更适合大规模、跨网络、弱网的多机场景,是本节开头提到的 DDS 发现扩展性问题的主流答案之一。
三点五、QoS:跨机通信"配不上"的隐形原因¶
发现解决的是"节点能不能找到彼此",但即使两个节点互相发现了,话题也可能**连不上**——这第二个坑是 QoS(Quality of Service,服务质量)不匹配。它在单机里几乎不咬人(同一套代码默认 QoS 一致),却在多机集成里频繁出现:你订阅一个邻居的话题,ros2 topic echo 能看到数据,自己的回调却一条都收不到。十有八九是 QoS 没对上。
ROS 2 的发布者和订阅者各自带一套 QoS 策略,只有当两端的 QoS 满足"兼容性"规则时,DDS 才会把它们连起来;否则它们彼此发现得到、却拒绝建立数据通道,而且**默认不报错**(新版本会打一条 incompatible-QoS 警告,但容易被刷屏淹没)。三个最常咬人的策略:
| QoS 策略 | 取值 | 多机场景的含义 | 兼容规则(发布者→订阅者) |
|---|---|---|---|
| Reliability(可靠性) | RELIABLE / BEST_EFFORT |
丢包重传还是尽力而为 | 订阅者要 RELIABLE,发布者必须也 RELIABLE;发布者 BEST_EFFORT 则订阅者只能 BEST_EFFORT |
| Durability(持久性) | VOLATILE / TRANSIENT_LOCAL |
晚订阅者能否收到"上线前"发的最后一帧(latched) | 订阅者要 TRANSIENT_LOCAL,发布者也必须是;否则收不到历史 |
| History(历史深度) | KEEP_LAST(depth) / KEEP_ALL |
缓存多少条未送达的消息 | 影响突发流量下的丢包,不影响是否连上 |
一条贯穿多机的经验:传感器数据流(高频、可丢)用 BEST_EFFORT,指令/状态(不可丢)用 RELIABLE,而像地图、静态变换、机器人描述(只发一次、晚来的订阅者也要拿到)这类"latched"话题用 TRANSIENT_LOCAL。多机尤其要警惕弱网下的 RELIABLE:它会为没收到 ACK 的消息不断重传,在丢包链路上反而堆积延迟、拖垮带宽——这时高频流量改用 BEST_EFFORT 往往更顺。下面是给邻居里程计(高频、可丢)和团队指令(不可丢)各配 QoS 的写法:
from rclpy.qos import QoSProfile, ReliabilityPolicy, DurabilityPolicy, HistoryPolicy
# 邻居里程计:高频、容忍偶尔丢一帧 -> BEST_EFFORT,浅缓存
sensor_qos = QoSProfile(
reliability=ReliabilityPolicy.BEST_EFFORT,
durability=DurabilityPolicy.VOLATILE,
history=HistoryPolicy.KEEP_LAST, depth=1, # 只保最新一帧,旧的丢掉
)
# 团队级指令/协调消息:绝不能丢 -> RELIABLE,且晚上线者要拿到最后一条
command_qos = QoSProfile(
reliability=ReliabilityPolicy.RELIABLE,
durability=DurabilityPolicy.TRANSIENT_LOCAL, # latched:后启动的节点也收到最后一条
history=HistoryPolicy.KEEP_LAST, depth=10,
)
# 关键纪律:发布端与订阅端这两个 profile 必须"兼容",否则话题连不上且默认不报错
self.create_subscription(Odometry, 'neighbor_odom', self.cb, sensor_qos)
注意一个多机特有的细节:ROS 2 内置的几个预设(qos_profile_sensor_data 是 BEST_EFFORT、qos_profile_system_default 是 RELIABLE)在不同包里被默认采用,跨机集成时两个团队各用各的预设,正是 QoS 不匹配的高发源头。所以多机系统里,凡是跨机共享的话题,都该把 QoS 显式写出来、两端对齐,而不是各自吃默认值。
四、多机时间同步(隐形杀手)¶
最后这个问题单机里根本不存在,却在多机里频繁致命。多机协调严重依赖**时间戳对齐**:传感器融合要把同一时刻的多机观测配在一起,共识与轨迹交换要知道邻居数据是"何时"的。但不同机器上的时钟各自漂移,如果不同步,你会**把不同时刻的状态当成同一时刻来用**——协调悄悄错乱,而且因为不报错,极难定位。
三类同步手段,精度递增:
- NTP / Chrony(纯软件):毫秒级,适合对时间精度要求不高的场合;
- PTP / IEEE 1588(需网卡硬件时间戳支持):微秒级,实机多机的推荐方案;
- 仿真统一时钟:在仿真里设
use_sim_time=true,所有节点用仿真器发布的/clock,天然完美同步。
工程纪律:仿真里用 /clock 统一时钟就够,实机务必上 PTP;任何跨机比较时间戳的代码,运行前先确认各机时钟源一致。 这一点直接呼应 §1.2 提过的"通信延迟",也为第 2 章的延迟补偿埋下伏笔——时间不同步本质上是一种"未知且漂移的延迟"。
本质洞察:多机工程的核心,是在**共享的通信介质上,同时维持每台机器人命名与时间的"独立性"和"可对齐性"**。命名空间与
tf前缀解决**空间维度**的隔离(谁是谁、坐标系不串),时间同步解决**时间维度**的对齐(数据是何时的、能不能配在一起)。这两个维度缺一不可:命名隔离了但时钟没对齐,数据虽不串台却时序错乱;时钟对齐了但命名串台,时序对了数据却张冠李戴。
五、多机系统上电自检 runbook¶
前四点讲的是"怎么搭",这里给一份"搭完怎么验"的清单——多机系统的坑大多不报错(§故障排查手册的总心法),所以**上电后按固定顺序逐项确认**比出问题再回头查省时得多。这份 runbook 正好沿着故障排查的"空间隔离 → 连接 → 时间/协调"三层顺序展开,每一步给出命令和"通过的标志":
- 查命名隔离(空间层):
通过标志:话题前缀每机独立、
ros2 topic list | sort # 期望:每台机器人的话题都带 /go2_i/ 前缀,无裸名冲突 ros2 run tf2_tools view_frames # 导出 tf 树 PDF,确认无重名帧(每个 base_link 都带前缀)tf树里每台机器人挂在world下各自的子树、无两个裸base_link。 - 查节点都在(连接层): 通过标志:节点数 = 每机节点数 × \(N\);缺了说明某机的 launch 没起来或被发现机制漏掉。
- 查话题真的连上(连接层——最易漏): 通过标志:发布端和订阅端计数都 ≥1、两端 reliability/durability 兼容(§三点五);只有发布端没订阅端 = 没人在听,QoS 不兼容会在此显形。
- 查时钟对齐(时间层):
通过标志:仿真各节点
# 仿真:确认各节点都用 /clock ros2 param get /go2_0/controller use_sim_time # 期望 True,且 /clock 有数据 # 实机:确认 PTP/NTP 已同步(各机命令行) chronyc tracking # 期望偏移在容许范围(实机 μs 级靠 PTP)use_sim_time=True且/clock在发布;实机各机时钟偏移小且同源。 - 查协调裕量(协调层):上电后算一次当前通信拓扑的 \(\lambda_2\)(用 §1.2 的代码),确认 \(\lambda_2>0\)(连通)且大小够本场景的实时预算(§1.5 第五部分)。通过标志:\(\lambda_2\) 不仅 \(>0\),还满足 \(k_{\text{供}}\ge k_{\text{需}}\)。
把这五步走完且全部通过,你的多机底座才算"验过"——可以往上叠协调算法了。反过来,任何"协调诡异"的 bug,也按这五步从上到下复查一遍:十有八九在前三步(命名/节点/连接)就能定位,真到第 4、5 步才暴露的(时钟、\(\lambda_2\))虽少但最隐蔽,而这份清单保证你不会跳过它们直接冤枉算法。这正是把"故障排查三层心法"前移成"上电主动自检"——多机调试里最划算的一笔时间投资。
⚠️ 常见陷阱¶
⚠️ 编程陷阱:tf 帧名未加命名空间前缀 - 错误描述:多机各自发布
tf,但帧名直接用裸base_link、odom,没加go2_i/前缀。 - 现象/后果:两台机器人的base_link在同一棵tf树里冲突,lookupTransform随机返回其中一台的变换,机器人定位"鬼畜",且不报错。 - 根本原因:tf2用帧名做全局唯一标识,裸名在多机下必然撞名。 - 正确做法:用robot_state_publisher的frame_prefix参数(或在发布时手动加前缀)给每台机器人的帧加go2_i/;自检方法——ros2 run tf2_tools view_frames导出 tf 树,确认没有重名帧、每台机器人是独立子树。⚙️ 配置陷阱:大规模多机用默认 Simple Discovery - 错误描述:几十台机器人(或几百个节点)全在同一
ROS_DOMAIN_ID下用默认组播发现。 - 现象/后果:启动时发现流量暴涨,节点上线缓慢、偶发匹配不上、网络拥塞;规模越大越明显。 - 根本原因:Simple Discovery 的端点匹配流量随参与者数平方增长。 - 正确做法:用 Discovery Server 把发现集中化(\(O(N)\)),或换 Zenoh,或用ROS_DOMAIN_ID把无关子系统分域隔离;自检方法——对比换用前后的启动时间和发现流量。🧠 思维陷阱:跨机直接比较时间戳而不同步时钟 - 错误描述:把不同机器人上带 ROS 时间戳的数据直接按时间戳配对/插值,默认它们的时钟一致。 - 现象/后果:实际时钟差几十毫秒,你把 A 机 \(t\) 时刻的状态当成 B 机 \(t\) 时刻来融合/协调,误差被引入却查不出原因。 - 根本原因:多机时钟独立漂移,ROS 时间戳只在"同一时钟源"下才可比。 - 正确做法:实机先上 NTP/PTP,仿真用
/clock统一;跨机时间戳比较前断言时钟源一致;对一个 100 Hz 的协调回路,50 ms 时钟差就等于错了 5 个控制周期——务必量化你的同步精度是否够用。⚙️ 配置陷阱:跨机话题 QoS 不匹配导致"发现得到却收不到" - 错误描述:订阅邻居话题时用默认 QoS(常是
RELIABLE),而对方发布端用的是qos_profile_sensor_data(BEST_EFFORT),或反之。 - 现象/后果:ros2 topic list/echo都能看到话题和数据,自己的回调却**一条都不触发**;程序不崩、不报错(顶多一条容易被淹没的 incompatible-QoS 警告),排查极耗时。 - 根本原因:DDS 只在发布/订阅两端 QoS 兼容**时才建立数据通道;RELIABLE订阅者无法连BEST_EFFORT发布者,TRANSIENT_LOCAL与VOLATILE也不兼容。单机不咬人是因为默认值恰好一致,多机集成两边各吃默认值就撞上。 - **正确做法:跨机共享话题一律**显式写出 QoS 并两端对齐**;用ros2 topic info <topic> --verbose查看两端的实际 QoS 是否兼容;高频可丢的传感器流用BEST_EFFORT、不可丢的指令用RELIABLE、latched 话题(地图/描述)用TRANSIENT_LOCAL。
练习¶
- [实现] 写一个三台 TurtleBot3(或三台 Go2)的多命名空间 launch(命名空间
/tb_0、/tb_1、/tb_2),配置tf2多坐标系树(world → tb_i/odom → tb_i/base_link)。验证两件事:(a) 给/tb_0/cmd_vel发指令不会影响另外两台(话题隔离);(b)/tb_0的节点能通过共享话题读到/tb_1的里程计(跨命名空间通信)。 - [配置/分析] 用
ros2 node list、ros2 topic info等工具,观察默认 Simple Discovery 下多台机器人同时启动时的节点发现过程与耗时;再配置 Fast DDS Discovery Server 重跑,对比启动时间与发现流量的差异,记录在什么规模下差异开始显著。 - [思考] 多机时间同步:为什么仿真里设
use_sim_time用/clock就完美同步,而实机却必须上 PTP?如果两台机器人的时钟相差 50 ms,对一个 100 Hz 运行的共识算法意味着什么(用"错了几个控制周期"来回答)?这个"未知且漂移的延迟"将在第 2 章的延迟补偿里正式处理。 - [调试] 故意制造一次 QoS 不匹配:发布端用
qos_profile_sensor_data(BEST_EFFORT)发邻居里程计,订阅端用默认RELIABLE订阅。确认回调收不到数据,然后用ros2 topic info <topic> --verbose读出两端 QoS,定位不兼容项并修复。写下你是怎么从"echo 有数据但回调不触发"这个现象一步步定位到 QoS 的——这套排查路径在多机集成里会反复用到。
至此,本章的四块内容齐备了:§1.1 教你从架构角度看一个多机系统(谁来决策),§1.2 用图论刻画通信结构并提炼出 \(\lambda_2\)(谁能和谁通信、协调多快),§1.3 用同构/异构区分机器人本身(能不能用一套模型/一个策略),§1.4 把这些落到 ROS 2 工程地面(怎么让多机在软件里共存)。但把系统建出来、跑起来,只是起点——真正考验工程能力的,是拿到一个陌生系统时能不能判断优劣、预见失效、并定出技术路线。§1.5 就把前四节的支柱串成这样一套可操作的判断框架。
§1.5 综合视角:评价、失效、混合架构与设计工作流 ⭐⭐¶
前四节给了四个支柱:架构、拓扑、同构/异构、ROS 2 工程。但真正把一个多机系统做出来,还需要支柱之外的三件事——会评价(知道一个多机系统好不好、好在哪)、懂失效(知道它会怎样坏、坏了怎么办)、有章法(面对新问题知道按什么步骤下手)。这一节补齐这三件事,并把前四节串成一个可操作的整体。
一、评价一个多机系统:六个性能维度¶
判断一个多机系统好不好,远不止"能不能跑通"。下面六个维度,基本覆盖了评价时该问的所有问题:
- 可扩展性(scalability):性能与成本随机器人数 \(N\) 如何变化?这是 §1.1 复杂度分析的核心——集中式 \(O(N^3)\) 差,去中心化近 \(O(1)\) 好。
- 最优性(optimality):得到的解距全局最优有多远?集中式最优,分布式凸时近最优,去中心化无保证。
- 鲁棒性(robustness):对机器人失效、通信丢包、扰动的容忍程度。下一部分专门讲。
- 通信开销(communication cost):每个控制周期的通信量。集中式 \(O(N)\)(全汇聚 + 全下发),分布式 \(O(\lvert E\rvert)\times\) 迭代轮数,去中心化为 0。
- 收敛速度(convergence rate):协调达成一致的快慢,正比于 \(\lambda_2\)(§1.2)。
- 同步精度(synchronization):多机时间与状态对齐的误差,由 §1.4 的时钟同步直接决定。
三种架构在这六维上的定性对比:
| 维度 | 集中式 | 分布式 | 去中心化 |
|---|---|---|---|
| 可扩展性 | 差(\(O(N^3)\)) | 好 | 最好 |
| 最优性 | 最优 | 近最优 | 无保证 |
| 鲁棒性 | 差(单点故障) | 好 | 最好 |
| 通信开销 | \(O(N)\) | \(O(\lvert E\rvert)\times\)轮数 | 0 |
| 收敛速度 | 一步到位 | \(\propto\lambda_2\),需多轮 | 依隐式信号 |
| 同步要求 | 高 | 中 | 低 |
关键结论:没有任何一个架构在所有维度上最优。评价一个多机系统,本质是按场景给这六维**加权**——精密装配看重最优性,大规模集群看重可扩展性与鲁棒性,弱网作业看重低通信与低同步要求。第 13 章会用雷达图把这六维量化成一张可对比的图。
把"加权"落成一个具体打分,你立刻会看到不同场景如何选出不同架构。给每个维度按场景设权重(总和为 1),每种架构在每维上打 1–5 分(由上表的定性档位映射),加权求和。以"\(N=2\) 精密装配(有线网络)"为例,最优性权重压倒一切:
| 维度 | 场景权重 | 集中式得分 | 分布式得分 | 去中心化得分 |
|---|---|---|---|---|
| 最优性 | 0.40 | 5 | 4 | 1 |
| 同步/实时性 | 0.25 | 5 | 3 | 4 |
| 可扩展性 | 0.10 | 2 | 4 | 5 |
| 鲁棒性 | 0.10 | 2 | 4 | 5 |
| 通信开销 | 0.10 | 3 | 2 | 5 |
| 收敛速度 | 0.05 | 5 | 3 | 2 |
| 加权总分 | 1.00 | 4.20 | 3.50 | 3.00 |
集中式以 4.20 胜出——和决策树"\(N\) 小 + 要最优 + 通信可靠 → 集中式"的结论一致。但**只要把场景换成"\(N=20\)、弱网",可扩展性与鲁棒性的权重大幅上调、最优性权重压低,同一张打分表会立刻把去中心化推到第一。这就是"加权"的威力:**架构没有绝对优劣,只有在某组权重下的优劣。
把这个"换场景就反转"用几行代码跑出来,你会对"加权决定选型"有更硬的体感:
import numpy as np
dims = ["最优性", "实时同步", "可扩展", "鲁棒", "通信开销", "收敛速度"]
# 三架构在六维上的固定打分(由定性档位映射,1-5 分),行=架构
scores = {
"集中式": np.array([5, 5, 2, 2, 3, 5]),
"分布式": np.array([4, 3, 4, 4, 2, 3]),
"去中心化": np.array([1, 4, 5, 5, 5, 2]),
}
scenarios = {
"N=2 精密装配(有线)": np.array([0.40, 0.25, 0.10, 0.10, 0.10, 0.05]),
"N=20 弱网作业": np.array([0.05, 0.10, 0.30, 0.30, 0.20, 0.05]),
}
for scn, w in scenarios.items():
ranked = sorted(((arch, float(s @ w)) for arch, s in scores.items()),
key=lambda kv: -kv[1])
print(f"{scn}: " + ", ".join(f"{a}={v:.2f}" for a, v in ranked)
+ f" -> 胜出:{ranked[0][0]}")
输出:
N=2 精密装配(有线): 集中式=4.20, 分布式=3.50, 去中心化=3.00 -> 胜出:集中式
N=20 弱网作业: 去中心化=4.55, 分布式=3.45, 集中式=2.80 -> 胜出:去中心化
架构的打分一个字没改,只换了场景的权重,冠军就从集中式翻成了去中心化。这把"评价 = 按场景加权"从一句话变成了可复算的事实,也正是第 13 章雷达图背后的算法——届时把这六维画成雷达图,一眼就能看出某方案的"短板维"是否落在你能容忍的范围。
本质洞察:这六个维度不是相互独立的"指标清单",而是同一组底层权衡在不同侧面的投影。可扩展性与最优性此消彼长(要全局最优就得汇聚全局信息,而汇聚全局信息正是不可扩展的根源);鲁棒性与同步要求此消彼长(越依赖精确同步的紧耦合协调,越经不起一个节点掉队)。所以你不可能"六维全拉满"——拉高一维往往压低它的对偶维。评价的真正含义,不是找一个"全能"系统,而是**认清你的场景把权重压在哪几维,然后主动接受被牺牲的那几维**。一个工程师的成熟,体现在他能说清"我这套系统牺牲了什么、为什么这个牺牲在我的场景里可接受",而不是声称自己的方案"哪儿都好"。
一个具体的编队 \(\lambda_2\) 算例,把性能度量落到数。设 6 台机器人排成 \(2\times 3\) 网格,每台只与上下左右相邻者通信。网格图是两条路径的笛卡尔积 \(P_2\times P_3\),其 Laplacian 特征值是两者特征值的两两之和(\(P_2\) 为 \(\{0,2\}\),\(P_3\) 为 \(\{0,1,3\}\)),合起来排序为 \(\{0,1,2,3,3,5\}\),于是 \(\lambda_2=1\)。这张图连通,但 \(\lambda_2\) 明显小于同规模全连接(\(\lambda_2=6\))、而大于一字长链(\(P_6\) 的 \(\lambda_2\approx 0.27\))。工程含义很直接:若实测这队机器人共识太慢,与其无脑加密近邻边,不如在网格对角或两端补一条"长程"边(类似小世界网络的捷径),往往能更显著地抬高 \(\lambda_2\)——这正是 §1.2"加边要加在瓶颈上"的量化体现。
二、失效模式与鲁棒性¶
多机系统相对单机的一大价值就是**容错**——一台坏了,任务未必终止。但这个价值能不能兑现,取决于架构选对没有。常见失效模式:
- 单点故障(single point of failure):集中式的中央节点一挂,全队瘫痪;分布式与去中心化没有这个致命点。这正是 §1.1 历史里 ALLIANCE 要解决的核心问题——多机的初心之一就是容错。
- 部分失效(partial failure):某台机器人故障。同构系统里其它机器人可以接管它的任务(ALLIANCE 的自适应分工);异构系统里若失效的恰是唯一能干某活的,任务只能降级。
- 通信失效(communication loss):链路断裂导致拓扑分裂、\(\lambda_2\to 0\),分布式协调随即失败;去中心化因为本就不靠通信,完全不受影响。
- 错误/拜占庭 agent(Byzantine/faulty):某 agent 发出错误信息污染共识。应对需要**鲁棒共识**(用邻居信息的中位数、或按可信度加权),而非朴素平均。
- 优雅降级(graceful degradation):好的多机系统在失效时性能**平滑下降**而非整体崩溃。这是设计目标,而非自动得到的性质。
各失效模式下三架构的脆弱性:
| 失效模式 | 集中式 | 分布式 | 去中心化 |
|---|---|---|---|
| 中央节点失效 | 致命 | 不适用 | 不适用 |
| 单机失效 | 看中央是否冗余 | 可接管(同构) | 可接管(同构) |
| 通信中断 | 致命 | 协调失败 | 不受影响 |
| 错误信息 | 中央可校验 | 需鲁棒共识 | 受隐式信号限制 |
把这张表和 §1.1 的决策树对照,你会发现:架构选择本质上也是鲁棒性选择——决策树里"通信是否可靠"那一问,问的其实就是"你能承受通信失效到什么程度"。
光知道"有哪些失效模式"还不够,工程上真正要做的是**对每种失效预先设计一条应对路径**。下面把五种失效模式各配一套"检测 + 缓解 + 降级"的处置思路,这套思路也正是本章末故障排查手册的设计依据:
- 单点故障的处置:不要让任何一个节点变成"全队的命门"。集中式系统若必须用,就给中央节点配**热备(hot standby)——一个影子节点实时同步状态,主节点失联时秒级接管;或者干脆把"全局规划"降频成"可间歇"的弱集中层(见下一部分的混合架构),让它短暂不可用也不致命。检测手段是**心跳(heartbeat):中央节点周期性广播存活信号,超时未收到即触发接管。
- 部分失效的处置:核心是**任务可重分配**。同构系统天然好办——失效者的任务谁都能接,触发一次任务重分配(第 3 章 CBBA 这类拍卖算法就支持动态重拍)即可。异构系统要警惕**关键能力单点**:若某项任务只有一台机器人能干(如全队唯一的无人机负责定位),它失效就是系统级降级,设计时应对关键能力**配冗余**或预设降级方案(无人机失联则地面机退化为纯里程计定位、降低精度继续)。
- 通信失效的处置:先**检测**——实时监控 \(\lambda_2\),一旦逼近 0 说明拓扑要分裂(§1.2);再**缓解**——若是移动导致的链路断裂,可让机器人主动收拢以恢复连通(把连通性当成一个软约束写进规划),或切换到不依赖该链路的备份拓扑;最后**降级**——分布式协调在通信断裂时若能退回去中心化(如搬运任务退回靠接触力隐式协调,第 8 章),就实现了优雅降级而非崩溃。
- 拜占庭/错误信息的处置:朴素平均的共识对一个发疯的 agent 毫无抵抗力——一个发出极端值的节点能把整个平均拽偏。鲁棒共识**用"对邻居信息取中位数而非均值""丢弃偏离最远的若干个邻居值(trimmed mean)""按历史可信度加权"等手段,使少数错误节点无法主导结果(这类方法在分布式系统里叫 BFT 类策略)。检测层面可加**离群检测:某邻居的报告若持续偏离群体分布,降低其权重甚至隔离。
- 优雅降级的设计:这是贯穿以上四条的总目标,且**必须主动设计、不会自动得到**。做法是为系统预设若干**降级档位**(满编最优 → 损失若干节点的次优 → 最小可用集的保命模式),并明确每个档位的触发条件与性能下限。一个反例是"全有或全无"的脆性系统:差一个节点就整体罢工——这恰恰是没有设计降级的后果。
本质洞察:鲁棒性不是某个架构"自带"的属性,而是**冗余 × 检测 × 降级**三者共同设计出来的。冗余提供"还有备份可用"(热备节点、关键能力冗余),检测提供"知道出事了"(心跳、\(\lambda_2\) 监控、离群检测),降级提供"出事后还能继续"(预设档位)。三者缺一:有冗余无检测 → 故障发生了却不切换;有检测无冗余 → 知道坏了却无可替代;有冗余有检测无降级 → 切换不及时就整体崩。所以当有人说"分布式系统更鲁棒",准确的说法是"分布式架构**消除了单点故障这一种**失效模式",但其余几种(通信分裂、拜占庭、关键能力单点)仍需你逐一设计应对——架构选对只是鲁棒性的起点,不是终点。
三、混合与分层架构:现实中的第四种模式¶
前面的三分法是**理想类型**。真实系统很少是纯粹的某一种,而常常**分层混用**(§1.1 的搜救案例已经见过)。最常见的两种混合模式:
- 弱集中分配 + 分布/去中心执行:一个(可以间歇、可容忍延迟的)中央层负责高层任务分配(谁干什么),执行层各子队自主协调实时动作。仓库机器人是典型——中央调度分配取货任务,每台机器人本地实时避障与路径跟踪。
- 分层时间尺度:慢层集中(低频全局规划,如每秒一次的队形重分配),快层分布(高频实时控制,如每毫秒一次的力协调)。慢层负责全局最优、能容忍延迟,快层负责实时性与鲁棒。
混合架构的逻辑是**取各家之长**:集中层提供全局协调(但低频、可容忍中央偶发不可用),分布/去中心层提供实时性与对失效的鲁棒。这正是第 12 章(MARL 与传统规控混合)的核心思想,也是工业界多机系统的主流形态。所以记住:三分法是用来分析的坐标轴,不是非此即彼的选项——实际设计里你往往在不同层、不同时间尺度上同时用到它们。
一个带数字的分层算例,把"为什么要分层"讲透。 设 4 台 Go2 协同搬运一根长构件穿过一片有动态障碍的场地。如果硬要用**单层集中式**:中央每个控制周期(比如 200 Hz,周期 5 ms)都要解一个 4 机联合 QP——按 §1.1 的 \(O(N^3)\),这个联合问题在 5 ms 内根本解不完,实时性直接崩。如果硬要用**单层分布式**:让 4 台机器人每个 5 ms 周期都跑共识对齐全局队形,按 §1.5 第五部分的预算,\(\lambda_2\) 不够大时一个周期跑不完所需轮数。两条单层路线都卡死——这正是分层的动机。分层后:
| 层 | 频率 / 周期 | 架构 | 负责什么 | 为什么放这层 |
|---|---|---|---|---|
| 慢层(全局) | 5 Hz / 200 ms | 弱集中 | 规划构件整体轨迹、分配各机抓持点、重算队形 | 全局最优需要全局信息,但可低频、能容忍中央偶尔卡顿 |
| 快层(本地) | 200 Hz / 5 ms | 分布/去中心 | 各机跟踪本地落足、实时避障、力协调 | 实时性硬要求,且必须对单机失效鲁棒 |
关键在于**两层的频率差出一两个数量级**——慢层 200 ms 出一次新目标,快层在这 200 ms 里自己跑 40 个周期去跟踪。这样慢层的"算得慢、可容忍延迟"和快层的"必须实时、必须鲁棒"各得其所:慢层就算偶尔晚 50 ms 给出新队形,快层仍按上一个目标稳稳跟着,系统不崩;快层某台机器人掉线,慢层下个周期重分配抓持点即可。这就是分层的本质——用时间尺度把"全局最优"和"实时鲁棒"这对矛盾解耦,让它们不必在同一个周期里同时满足。第 12 章的 MARL+MPC 混合、第 4 章分布式 MPC 的"慢层规划 + 快层 WBC",走的都是这个套路。
本质洞察:混合架构不是"三种纯架构之外的第四种新东西",而是**在不同时间尺度上分别选用三种纯架构**的结果——慢层选集中(要全局最优、容忍延迟),快层选分布/去中心(要实时、要鲁棒)。这把一个看似无解的矛盾(既要全局最优又要实时鲁棒,而这两者在单层里互斥)拆成了两个各自可解的子问题。所以当你面对"既要 A 又要 B 而 A、B 在单层里冲突"的设计困境时,第一反应应该是问:A 和 B 能不能放到不同的时间尺度上去? 能,就分层;频率差得越开(慢层比快层慢一两个数量级),解耦越干净。这个"用时间尺度解耦矛盾"的思路,远比记住"有混合架构这回事"有用——它是一种可迁移的系统设计直觉。
四、多机仿真与训练工具链¶
把多机系统做出来,绝大多数工作在仿真里完成,尤其是后面用强化学习的章节。这里给一张工具链地图(版本以 2026 年中为准):
- 大规模并行 RL 仿真:IsaacLab 2.3(构建于 Isaac Sim 5.1)是当前多机 MARL 训练的主力——在 GPU 上并行成百上千个环境,支撑 \(N\) 台机器人 × 大量并行实例的吞吐。它取代了已停更的 legacy IsaacGym(一些老多机环境如 MQE 仍基于后者)。物理后端方面,Newton 正从 Beta 走向成熟,Isaac Sim 也在向 PhysX + Newton 多后端演进。
- MPC / 最优控制:OCS2(多体 MPC,含 loco-manipulation 扩展)、acados、Pinocchio(刚体动力学)、OSQP(QP 求解)——第 4、5 章的分布式 MPC 主要走这条线。
- 通用机器人仿真:MuJoCo(接触丰富、轻量,适合搬运实验)、Gazebo(与 ROS 2 集成好,适合系统集成测试)。
- MARL 训练框架:MAPPO(
marlbenchmark/on-policy)、PyMARL(QMIX 等值分解方法)、HARL(异构 HAPPO)——第 10–12 章用。
选型经验:MARL 训练优先 IsaacLab(吞吐量决定迭代速度)、MPC 与系统集成走 OCS2 + ROS 2 + Gazebo、接触密集的搬运实验可用 MuJoCo。Sim2Real 的关键是大量域随机化——回忆 §1.3:参数化同构正是靠它覆盖个体差异——细节在第 8、13 章展开。
五、把 \(\lambda_2\) 换算成实时通信预算¶
前面反复说"\(\lambda_2\) 大协调快、小则慢",也在 §1.2 第八部分推出了轮数公式 (1.4)。但工程拍板时需要的是一个更直接的问题的答案:给定我的控制频率和网络,这套拓扑到底能不能在一个控制周期内协调好? 这一部分把 \(\lambda_2\)、控制周期、网络延迟三者拼成一道可以填数的预算题——它是 §1.5 评价维度里"收敛速度 + 通信开销 + 同步要求"三维落到具体数字的地方,也是判断"该用分布式还是必须退回集中/去中心"的硬指标。
预算的核心是一个不等式:一个控制周期内能跑的通信轮数,必须 \(\ge\) 共识收敛到所需精度需要的轮数。左边是网络和时序给的**供给**,右边是 \(\lambda_2\) 给的**需求**。
供给侧:一个周期能跑几轮。 设控制频率 \(f_c\)(周期 \(T_c=1/f_c\)),一轮"邻居交换 + 本地更新"的耗时主要是网络**往返延迟** \(\tau_{\text{rt}}\)(发出去、邻居收到、回信息),则一个周期内可用轮数
其中 \(\gamma<1\) 是留给计算和裕量后能用于通信的比例(经验上 \(\gamma\approx 0.3\sim0.5\),其余时间要算本地 MPC、跑估计)。
需求侧:收敛到精度 \(\delta\) 要几轮。 直接用 §1.2 的式 (1.4):\(k_{\text{需}}\approx\frac{\lambda_N}{2\lambda_2}\ln\frac1\delta\)。
预算判据: 若 \(k_{\text{供}}\ge k_{\text{需}}\),分布式协调在实时约束内可行;否则不可行,你有三条出路——(a) 抬高 \(\lambda_2\)(往瓶颈加边、加长程捷径,§1.2);(b) 降低 \(f_c\) 或放宽精度 \(\delta\)(给协调更多时间);(c) 换架构(退回弱集中:中央一次算完,无需多轮;或退去中心:靠物理隐式协调,零通信轮)。
把它代一组实数走一遍。设 50 Hz 控制(\(T_c=20\) ms)、局域有线网往返 \(\tau_{\text{rt}}=1\) ms、通信占比 \(\gamma=0.4\),则 \(k_{\text{供}}=\lfloor 0.4\times20/1\rfloor=8\) 轮。需求侧:若拓扑是 \(2\times3\) 网格(\(\lambda_2=1\),\(\lambda_N=5\),见 §1.5 第一部分算例),要 \(\delta=10^{-2}\),则 \(k_{\text{需}}\approx\frac{5}{2\times1}\times\ln100\approx2.5\times4.6\approx12\) 轮。\(k_{\text{供}}(8)<k_{\text{需}}(12)\)——不可行,这套网格拓扑在 50 Hz 有线网下协调不达标。出路 (a):在网格对角加一条长程边把 \(\lambda_2\) 抬到 ~2,则 \(k_{\text{需}}\) 减半到 ~6 轮 \(<8\),可行。这就是"加边加在瓶颈"那条经验**带着数字**的样子。
再看一个反例凸显延迟的杀伤力:同样 50 Hz、同样网格,但走 WiFi(\(\tau_{\text{rt}}=10\) ms),则 \(k_{\text{供}}=\lfloor 0.4\times20/10\rfloor=0\)——一个周期连一轮完整往返都跑不完。此时无论 \(\lambda_2\) 多大都没用,问题不在拓扑而在延迟,只能降频或换网络(这正对应故障排查表里"弱网下协调时好时坏"的场景)。
本质洞察:实时多机协调能不能成,从来不是"\(\lambda_2\) 够不够大"单方面决定的,而是**供给(\(T_c/\tau_{\text{rt}}\) 给的轮数)和需求(\(\lambda_N/\lambda_2\) 要的轮数)两边的赛跑**。这把一个常见的误判纠正过来:很多人协调出问题就一头扎进"优化拓扑、抬高 \(\lambda_2\)",却没注意到瓶颈可能在供给侧(延迟大、频率高、计算挤占了通信窗口)——这种情况下再怎么加边都救不回来。所以拿到一个"实时协调不达标"的系统,正确的第一步是**把供给和需求各算一个数摆在一起比**:需求大就治拓扑(加边/换架构),供给小就治时序(降频/换网/减计算占用)。这个"两边各算一个数"的习惯,会让你在第 4 章给分布式 MPC 定 ADMM 迭代轮数、在第 13 章做实时性预算时,不必试错就能直接估出可行区间。
六、面向新问题的设计工作流¶
最后,把全章合成一套**面对一个新多机问题时的下手步骤**:
- 贴三维标签:先把任务拆成子任务,各自标注(架构候选 × 交互 × 信息),不要一上来就给整个系统找单一答案。
- 走架构决策树:对每个子任务按"\(N\) → 最优性需求 → 通信可靠性"判断,必要时分层混用。
- 建通信图、算 \(\lambda_2\):确认拓扑连通、估计协调速度、用 Fiedler 向量找瓶颈;动态场景下监控 \(\lambda_2\) 最小值作为协调裕量。
- 判同构/异构:看动力学结构与维度是否相同——决定参数共享(+域随机化)还是 HAPPO;写出联合动力学与耦合项(\(\boldsymbol\lambda\) 或负载力)。
- 搭 ROS 2 骨架:命名空间隔离 +
tf2帧前缀 + 时间同步(仿真/clock、实机 PTP)。 - 选仿真平台并设评价基线:据任务选 IsaacLab / OCS2+Gazebo / MuJoCo,按场景给六个性能维度加权,明确可扩展性、鲁棒性、最优性的目标与可接受下限。
这套流程会在第 13 章的综合实战里被完整地走一遍——届时你会从一个空场景和一个任务描述出发,一步步把它落成一个能跑、能评、能容错的多机系统。在那之前,本章建立的就是支撑这套流程的全部基础概念。
七、把工作流走一遍:一个全程算例¶
光列六步还不够"解渴"。这里拿一个完全指定的场景,把六步从头到尾走一遍、每步都落到具体结论甚至数字——它把本章 §1.1–§1.5 的所有工具串成一条线,你照着走一遍,就知道遇到真问题该怎么动手。
场景:4 台 Unitree Go2 合抬一根刚性长梁,在室内(WiFi,往返延迟约 8 ms)从 A 点搬到 B 点,途中要绕过几个固定立柱。要求协调稳、不内耗,对"绝对最优"不苛求,控制频率 100 Hz。
第 1 步——贴三维标签。先拆子任务:(i) 抬梁的力/位姿协调(刚性耦合、强协作);(ii) 整体路径绕柱(协作、可低频)。给主子任务 (i) 贴标签:架构候选 = 分布式或去中心(4 台、要协调精度但 WiFi 不够可靠);交互 = 协作;信息 = 邻居(各机看自己 + 相邻抬梁伙伴)。
第 2 步——走架构决策树(§1.1)。\(N=4\le10\),排除"必须去中心";"需要全局最优?"——不苛求,跳过纯集中;"通信可靠吗?"——WiFi 一般。于是路径分层:慢层(绕柱路径,5 Hz)用弱集中规划整梁轨迹;快层(抬梁力协调,100 Hz)用分布式 MPC,且预案——WiFi 断时退化为去中心(靠梁的接触力隐式协调,第 8 章)。这正是 §1.5 第三部分的分层套路。
第 3 步——建通信图、算 \(\lambda_2\)(§1.2)。4 台抬梁,自然拓扑是按物理相邻连边。若排成一排(梁的两端各一对),近似路径图 \(P_4\):\(\lambda_2=2-\sqrt2\approx0.586\)、\(\lambda_N=2+\sqrt2\approx3.414\)(本章手算过)。若让对角也连上(更接近环 \(C_4\) 或加弦),\(\lambda_2\) 可抬到 2。先记下两个候选拓扑的 \((\lambda_2,\lambda_N)\)。
第 4 步——判同构/异构(§1.3)。4 台都是 Go2,动力学函数与维度完全相同——同构(哪怕个体质量略有差异,也是参数化同构,用域随机化覆盖)。联合状态 \(4\times12=48\) 维,联合控制 \(4\times12=48\) 维,块对角 + 刚性梁的约束力 \(\boldsymbol\lambda\) 耦合(§1.3 第五部分的 KKT 系统,只是约束从 1 个变成连接 4 台的多个)。同构 → 控制方法可走参数共享(若上 MARL 用 MAPPO);分布式 MPC 则各机解同构子问题、\(\boldsymbol\lambda\) 经 ADMM 一致。
第 5 步——做实时预算(§1.5 第五部分,这是决定成败的一步)。100 Hz → \(T_c=10\) ms;WiFi \(\tau_{\text{rt}}=8\) ms;取 \(\gamma=0.4\)。供给 \(k_{\text{供}}=\lfloor0.4\times10/8\rfloor=0\) 轮——一个周期连一轮往返都跑不完。对照下表看清楚问题出在哪、怎么治:
| 方案 | \(\lambda_2\) | \(k_{\text{需}}\)(到 \(\delta{=}10^{-2}\)) | \(k_{\text{供}}\) | 可行? | 症结 |
|---|---|---|---|---|---|
| \(P_4\) + 100 Hz + WiFi | 0.586 | \(\approx\frac{3.414}{2\times0.586}\ln100\approx13\) | 0 | 否 | 供给侧:WiFi 太慢 |
| \(P_4\) + 50 Hz + WiFi | 0.586 | 13 | \(\lfloor0.4\times20/8\rfloor=1\) | 否 | 仍供给不足 |
| 加弦(\(\lambda_2{=}2\))+ 50 Hz + 有线 1 ms | 2 | \(\approx\frac{4}{2\times2}\ln100\approx5\) | \(\lfloor0.4\times20/1\rfloor=8\) | 是 | — |
| \(P_4\) + 100 Hz + WiFi,退**去中心** | — | 0(零通信轮) | 0 | 是 | 不靠通信轮 |
读这张表得到两条可落地的结论:其一,100 Hz 紧耦合分布式协调在 WiFi 上不可行,要么换有线 + 加弦拓扑 + 降到 50 Hz(第 3 行可行),要么干脆走去中心(第 4 行——零通信,正是本场景"WiFi 不可靠"时最稳的选择,呼应第 8 章 decPLM)。其二,治法分两路:治时序(换有线、降频)解决供给侧,治拓扑(加弦抬 \(\lambda_2\))减少需求侧——两路都用上才把第 3 行从不可行扳成可行。
第 6 步——搭骨架、设基线。ROS 2 上 4 个命名空间 /go2_0..3,各发布 odom→base_link(带 frame_prefix),一个全局定位发 world→go2_i/odom(§1.4);实机务必上 PTP(WiFi 下时钟漂移会让"协调结果错乱"——§1.4 时间同步)。评价基线(§1.5 第一部分):本场景把权重压在**鲁棒性 + 同步/实时性**(WiFi 易断、要稳),最优性次之——这正是上面倾向"去中心或加弦 + 有线"而非"纯集中追最优"的依据。
走完这六步,你对"这个系统该怎么搭"已经有了一份带数字、带取舍依据的方案,而不是一句模糊的"用分布式吧"。这就是本章全部工具合起来的威力:决策树给架构、\(\lambda_2\) 给协调速度、预算式给可行性、同构判断给控制方法、ROS 2 给落地、六维评价给验收标准——每一步都把上一步的输出收得更窄,直到方案唯一确定。第 13 章会在更大的场景上再走一遍这套流程。
本质洞察:这六步不是孤立的清单,而是一条**信息逐步收窄**的漏斗——每一步的输出都是下一步的输入约束。第 1 步贴标签把"无限可能"收窄成"几类候选架构";第 2 步走决策树把候选定死成"具体架构";第 3 步算 \(\lambda_2\) 把架构需要的通信落成"具体拓扑与通信预算";第 4 步判同构把控制方法收窄成"参数共享还是 HAPPO";第 5 步搭骨架把方法落成"具体的 ROS 2 工程";第 6 步设基线把工程落成"可量化验收的目标"。任何一步跳过,后面就缺一层约束、只能靠猜——比如不算 \(\lambda_2\) 就搭骨架,你根本不知道每个控制周期该留几轮通信时间,实时性出了问题也无从归因。所以这套工作流的价值不在"六个步骤"本身,而在它强制你**按依赖顺序把决策一层层做实**,不让任何一个关键选择悬空。
⚠️ 常见陷阱¶
🧠 思维陷阱:用单一指标评价多机系统 - 错误描述:只用一个指标(常是"能不能跑通"或"收敛多快")判断一个多机方案的好坏,忽略其余维度。 - 现象/后果:你选了一个收敛最快的方案上线,结果它在一台机器人失效时整队崩溃——因为你从没评估鲁棒性这一维。或者反过来,为了"可扩展"牺牲了精度,在精密任务上不达标。 - 根本原因:多机系统的六个性能维度此消彼长,没有"全能"方案;单看一维必然在被忽略的维度上踩坑。 - 正确做法:用六维加权评价,先明确你的场景把权重压在哪几维,再据此选型;并主动写下"我牺牲了哪几维、为什么可接受"。自检方法——给候选方案画一张六维雷达图(第 13 章),看它的"短板维"是否落在你能容忍的范围。
💡 概念误区:以为选了分布式/去中心化就自动鲁棒 - 错误描述:认为"非集中式 = 鲁棒",选了分布式架构就不再设计任何容错机制。 - 现象/后果:分布式系统确实没有中央单点故障,但你没处理通信分裂(拓扑断成两半协调失败)、也没处理拜占庭节点(一个发疯的 agent 污染共识),结果照样整体失效。 - 根本原因:换架构只**消除了单点故障这一种**失效模式,通信失效、关键能力单点、错误信息等其余模式依然存在,需逐一应对。 - 正确做法:鲁棒性 = 冗余 × 检测 × 降级,三者都要主动设计;对每种失效模式预设"检测→缓解→降级"路径,而不是指望架构自动包办。
⚠️ 工程陷阱:仿真到实机直接迁移,缺域随机化 - 错误描述:在仿真里(尤其用 IsaacLab/MuJoCo)训练或调好的多机策略/控制器,不做域随机化就直接搬到实机。 - 现象/后果:仿真里完美协同的系统,上实机后因质量、摩擦、通信延迟、传感噪声与仿真不符而协调崩溃——典型的 sim2real gap,在多机里被放大(每台机器人的差异叠加)。 - 根本原因:仿真无法精确复现每台真实机器人的参数,而多机协调对参数不一致比单机更敏感(回忆 §1.1:微小偏差在刚性负载上被放大成内力)。 - 正确做法:训练时施加大量域随机化覆盖参数分布(回忆 §1.3 参数化同构正是靠它),实机加在线辨识/自适应;先在高保真仿真(含通信延迟模型)验证再上机。细节在第 8、13 章。
练习¶
- [评价/设计] 为"\(N=12\) 的无人机协同测绘、户外 WiFi 偶尔丢包、要求覆盖效率高但不要求最优"这个场景,给六个性能维度分配权重(总和为 1),再给集中式、分布式、去中心化三种架构在每维打 1–5 分,算加权总分选出最优架构。然后把"WiFi 偶尔丢包"改成"通信几乎不可用",重算一遍,观察排序是否反转,并解释反转的原因。
- [失效分析] 给定一个"弱集中分配 + 分布式执行"的仓库机器人系统(中央调度分配取货任务,每台机器人本地实时避障)。分别分析:(a) 中央调度节点宕机会怎样?系统能否继续运行一段时间?(b) 两台机器人之间通信中断会怎样?(c) 一台机器人电量耗尽中途停下会怎样?对每种失效,说明它落在五种失效模式的哪一类,并设计一条"检测→缓解→降级"的处置路径。
- [综合/动手] 用 §1.2 的代码为一个 \(N=8\)、含两个"团块 + 一条桥"的哑铃拓扑算 \(\lambda_2\);假设这是一个分布式编队,通信预算是每控制周期 5 轮迭代。结合 \(\lambda_2\) 估计这套拓扑下共识能否在 5 轮内大致收敛(用收敛率 \(e^{-\lambda_2 t}\) 的直觉判断);若不能,提出一个"在哪加边"的改进方案并重算 \(\lambda_2\) 验证。这道题把 §1.2 的 \(\lambda_2\)、§1.5 的通信开销评价、以及"加边加在瓶颈"三者串起来。
§1.6 放眼全局:应用、相邻支柱与课程地图 ⭐¶
§1.5 给了面对新问题的章法。最后把镜头拉到最远——看看这些方法都用在哪、还有哪些本课程不深入但你该知道位置的支柱,以及本章在整个课程里的坐标。
一、多机器人系统的应用全景¶
多机系统已经落到很多真实场景,它们也正好对应着本课程后面会展开的技术:
| 应用领域 | 典型形态 | 主导架构 | 对应本课程 |
|---|---|---|---|
| 物流仓储 | 数百台移动机器人取货分拣 | 弱集中调度 + 本地避障(混合) | 第 3 章任务分配 |
| 搜救与勘探 | 地空异构、弱网协同 | 去中心 / 分层 | 第 6 章异构协同 |
| 协同搬运/建筑 | 多足/多臂合抬重物 | 集中 / 分布式 MPC | 第 4、5、8 章 |
| 巡检与测绘 | 多机覆盖、协作建图 | 分布式 | 第 3 章覆盖、CSLAM(相邻) |
| 编队与集群 | 无人机编队、卫星编队 | 共识 / 编队控制 | 第 2、4 章 |
| 双臂/多臂装配 | 工厂精密协同操作 | 集中式 | 第 7 章 |
| 农业作业 | 多机播种/采摘/巡田 | 弱集中分配 + 本地作业 | 第 3 章任务分配 |
| 卫星/空间 | 编队飞行、在轨协同 | 共识 / 去中心(深空时延大) | 第 2、4 章 |
这张表里两个看似"离腿足很远"的领域,恰好印证了本章反复讲的两条主线,值得各点一句:农业作业**是"弱集中分配 + 本地执行"的典型——中央按地块把任务分给各机(第 3 章),但每台机器人在田里的实时作业(避障、采摘)必须本地自主,因为田间网络弱、且作业要实时,这正是 §1.5 第三部分分层架构的又一实例。**卫星/空间**则把"通信延迟主导架构选择"推到极端——近地编队尚可共识,深空协同因光速时延动辄数秒甚至数分钟,\(\tau_{\text{rt}}\) 巨大使 §1.5 第五部分的供给侧 \(k_{\text{供}}\to 0\),任何"需多轮通信"的紧耦合协调都不可行,只能走去中心或极低频弱集中。换句话说,**同一套"架构 × \(\lambda_2\) × 实时预算"的分析框架,从室内多足一路适用到深空卫星——这正是本章建立通用分析工具(而非针对某一场景的专用招式)的价值所在。
本课程聚焦腿足与机械臂的协同,主要对应其中的**搬运、建筑、勘探**场景——这也是为什么我们的累积项目 Mini-MultiBot 以多足协同搬运为主线。
把这张应用表和 §1.1 的三架构对照,你会注意到规模跨度极大:从工厂里 2 台机械臂,到仓库里数百台 AGV,再到学术界演示的上千架无人机。规模不只是个数字——它在某个量级上会引发**质变**,这正是"多机器人系统"与"群体机器人(swarm)"的分野(§1.1 末尾已点出哲学差异,这里从规模维度把它讲全)。下面把这条分界用一张表落实,帮你拿到任意一个系统就能判断它落在哪一侧:
| 比较维 | 多机器人系统(multi-robot) | 群体机器人(swarm) |
|---|---|---|
| 典型规模 | 单体 / 双体 / 小队(\(N\lesssim 10\text{–}20\)) | 大群(\(N\) 数十到上千) |
| 个体能力 | 个体强、功能完整(单台就能干不少活) | 个体弱、功能极简(单台几乎没用) |
| 信息范围 | 常有全局/邻居信息,可显式建模队友 | 严格只看局部(几个最近邻或环境信号) |
| 协调方式 | 集中/分布/去中心皆可,常带显式协议 | 几乎只去中心,靠简单规则 + 涌现 |
| 设计目标 | 性能/最优性(把这件具体的事做好) | 可扩展性/鲁棒性(加减个体系统照转) |
| 个体可替换性 | 失一台可能损失某项能力(尤其异构) | 任一个体可替换,去掉一个毫无影响 |
| 本课程位置 | 主线(第 1–13 章几乎都在这一侧) | 相邻领域,仅在此点位 |
这条分界**不是按机器人个数一刀切**的(没有"超过 50 台就算 swarm"这种硬阈值),而是看设计哲学:你是想"把这队机器人精确协调好"(多机思路,看重最优性与协调精度),还是想"用海量简单个体换极致可扩展与鲁棒"(swarm 思路,主动放弃个体能力与全局信息)。本课程的腿足/机械臂协同——双 Go2 搬运、Go2 + 无人机巡检——个体能力强、规模小、要协调精度,整条主线都站在多机器人系统这一侧;swarm 那套(如海量微型无人机的分布式编队)用的是另一套以涌现为核心的方法论,本课程只在这里标个位置,不展开。
本质洞察:判断一个系统"是多机还是 swarm",数个数会骗你——真正的分界在**单个个体被设计成"重"还是"轻"。多机器人系统把每个个体做重(能力完整、信息丰富、不可轻易替换),于是必须精打细算地协调它们,追求的是把一件具体的事做到最优;swarm 把每个个体做轻(能力极简、只看局部、随意替换),于是个体之间几乎不需要精确协调,系统靠"规则统一 + 数量"涌现出宏观行为,追求的是规模与鲁棒。这两种哲学决定了完全不同的方法工具箱:多机这侧用本章的架构分类、\(\lambda_2\)、联合动力学、分布式 MPC/MARL;swarm 那侧用的是元胞自动机式的局部规则、势场、随机性建模。**所以拿到一个新系统,先问"个体是重是轻",就知道该翻哪本工具书——这比纠结"它算不算 swarm"有用得多。
二、本课程不深入、但你该知道位置的相邻支柱¶
本课程聚焦**规划与控制**。一个完整的多机系统还有两根支柱,这里给它们在地图上的位置,免得你以为多机就只有规控:
- 多机感知与状态估计:协作 SLAM(CSLAM)、分布式状态估计(分布式卡尔曼/信息滤波)——多台机器人共享观测、联合建图与定位。本课程**假设每台机器人已有自身的状态估计**(§1.4 的
world_anchor就是这些估计接入全局坐标系的入口),感知与估计的细节属于 SLAM 主线,不在这里展开。 - 多机安全与避碰:机器人之间不相撞是协同的硬约束。控制屏障函数(CBF)及其去中心化/图版本(第 12 章的 GCBF+)是把"安全"作为一层叠加到任意控制器之上的主流方法。值得注意的是:本章 §1.2 的图论语言,和第 12 章图 CBF 用的是**同一套图论**——你在这里建立的图直觉,会在安全控制里再次派上用场。
这两根支柱看似"不在规控范围内",但它们和本章建立的概念其实**咬合得很紧**,值得各多说一句,免得你把它们当成完全无关的另一个世界:
- CSLAM 复用的正是本章的 \(L\) 和 \(\lambda_2\)。分布式状态估计(分布式信息滤波、基于共识的卡尔曼滤波)在数学上就是"让各机器人对一个共同估计量跑共识"——它和 §1.2 的共识动力学同构,收敛速度同样由通信图的 \(\lambda_2\) 决定。所以本章学的"\(\lambda_2\) 大则协调快"在感知侧原样成立:通信图越连通,多机的联合估计收敛越快、越一致。CSLAM 里还会出现一张**位姿图(pose graph)**,它和通信图是两张不同的图(一张连"谁观测到谁",一张连"谁能和谁通信"),但用的是同一套图论工具——这正是单体 SLAM 的位姿图直觉能平移过来的原因。
- 图 CBF 把"安全"也写成了图上的约束。第 12 章的 GCBF+ 把"任意两台机器人不相撞"写成图上每条边的一个屏障函数约束,再叠加到任意底层控制器(MPC 或 MARL 策略)之上。这里的图通常是按距离动态构建的"邻近图"(谁离得近就连边),和 §1.2 的通信图是不同的图、但同样用邻接/度/Laplacian 这套语言描述。一个值得记住的连接点:通信图决定"谁能协调",邻近图决定"谁需要避让"——两张图都在变(机器人在动),安全层关心的是后者,协调层关心的是前者,但它们共享同一套图论底座。
把这两点和本章合起来看,你会发现整个多机系统——决策(架构)、协调(共识/\(\lambda_2\))、估计(CSLAM)、安全(图 CBF)——底层都在反复使用"图 + Laplacian"这一套数学。这不是巧合:多机系统的本质就是"一堆个体 + 个体间的关系",而"关系"最自然的数学载体就是图。所以本章 §1.2 花大力气建立的图直觉,是贯穿多机系统所有支柱(不止规控)的通用语言——这也是为什么我们在第一章就把它讲透。
三、本章在课程地图中的位置¶
把本章放回整张地图:它是多机协作 Part 的**入口**,向后连出三条技术线——分布式优化与 MPC(第 2–6 章)、协同操作(第 7–9 章)、多智能体强化学习(第 10–12 章),三线最终在第 13 章的综合实战汇合。本章建立的每一个概念(架构、\(\lambda_2\)、同构/异构、联合动力学、ROS 2 多机)都是这三条线的**共同前提**——这也是为什么我们把它放在最前面,用一整章打地基。
四、本 Part 符号与记法约定¶
全 Part(第 1–13 章)统一使用下列符号,后续章节不再重复定义:
| 符号 | 含义 |
|---|---|
| \(N\) | agent(机器人)数 |
| \(x_i,\ u_i\) | agent \(i\) 的状态、控制 |
| \(x,\ u\) | 联合状态、联合控制(各 agent 堆叠) |
| \(n_i,\ m_i\) | agent \(i\) 的状态维度、控制维度 |
| \(G=(V,E)\) | 通信图(顶点集、边集) |
| \(A,\ D,\ L\) | 邻接矩阵、度矩阵、Laplacian \(L=D-A\) |
| \(B\) | 关联矩阵(顶点-边),\(L=BB^\top\) |
| \(a_{ij},\ w_{ij}\) | 邻接元(0/1)、边权(加权图) |
| \(\lambda_2\) | 代数连通性(Fiedler 值) |
| \(\mathcal N_i\) | agent \(i\) 的邻居集合 |
| \(T\) | MPC 预测步数(horizon) |
| \(\boldsymbol\lambda\) | 约束力(拉格朗日乘子) |
| \(J_i\) | agent \(i\) 的(附着点)Jacobian |
| \(\pi_\theta\) | 策略,参数为 \(\theta\) |
| \(f(\cdot)\) | 动力学函数 \(\dot x=f(x,u)\) |
约定:向量默认列向量;\(\mathbf 1\) 为全 1 向量,\(I\) 为单位阵;欧拉角/旋转在需要时用 \(\Theta\) / \(R\in SO(3)\);\(e_3=[0,0,1]^\top\)。
本章综合练习¶
下面的练习跨越多个小节,用来检验你能否把本章的概念**合起来用**——这正是真实多机问题需要的能力,也是单看某一节练习练不到的。
-
完整工作流(综合)。场景:3 台 Go2 加 1 架四旋翼,在崎岖地形协同把一根长构件搬到指定位置,同时无人机做空中侦察与定位辅助。请完整走一遍 §1.5 的六步设计工作流:(a) 拆子任务并各贴三维标签;(b) 为每个子任务选架构,指出是否需要分层混用;(c) 画出通信拓扑、写出 Laplacian、估计 \(\lambda_2\) 量级;(d) 判断系统同构性,写出联合状态/控制维度与主要耦合项;(e) 列出 ROS 2 骨架的关键点(命名空间、
tf帧、时间同步方案);(f) 选定评价维度并说明优先级。 思路:先意识到这是异构 + 多子任务,不可能单一架构;搬运子任务的耦合用 \(\boldsymbol\lambda\),侦察子任务与搬运子任务信息维度不同。 -
架构权衡量化。给定 \(N=8\)、通信丢包率约 10%、要求 50 Hz 实时协调。用 §1.5 的六维度表给集中式、分布式、去中心化三种方案打分(定性即可),给出最终选择与理由,并指出在什么条件下你的选择会反转。 思路:50 Hz + 丢包,会强烈惩罚"需多轮可靠通信"的方案;把实时性与鲁棒性的权重调高再看排序。
-
拓扑设计与权衡。为 6 机编队设计两种通信拓扑:拓扑甲要求"断任意一条边仍连通"(鲁棒),拓扑乙要求"\(\lambda_2\) 尽量大"(协调快)。分别画出、计算 \(\lambda_2\),并讨论这两个目标能否兼得;通信成本受限时你会怎样折中。 思路:鲁棒要求至少 2-边连通(无割边),协调快倾向稠密或带长程捷径;两者都要加边,但加在哪里不同。
-
异构联合建模。为"一台四足 + 一台双臂移动操作机器人"的协同写出联合状态、联合控制的维度构成,标出哪部分是耦合项。判断这是同构还是异构,并据此说明该用 MAPPO(参数共享)还是 HAPPO,理由是什么。 思路:四足与双臂移动平台的动力学结构、动作维度都不同——这是真异构,回到 §1.3 第四部分的判据。
-
\(\lambda_2\) 与收敛(动手)。取练习 3 的两种拓扑,用 §1.2 的代码计算各自 \(\lambda_2\),再数值积分共识动力学,验证拓扑乙确实收敛更快。把两条收敛曲线画在一起(纵轴对数),并标注各自的 \(\lambda_2\)。 思路:复用 §1.2 的
laplacian_from_edges与共识积分函数,只换边集即可。 -
失效分析。沿用练习 1 的搜救搬运系统。列出至少三种失效模式(参考 §1.5 第二部分),对每种说明:在你所选架构下它有多致命、系统能否优雅降级、你会怎样缓解。 思路:特别注意通信中断对"搬运子任务(分布式)"和"侦察子任务"的不同影响;搬运可退化到去中心化(接触力),侦察未必。
-
开放判断。选一个你熟悉的真实多机系统(如扫地机器人集群、无人机灯光秀、仓库 AGV 调度)。给它贴三维标签,推测其主导架构(及是否分层混用),指出它最可能的失效模式与现实中的缓解手段,并说明你的判断依据。 思路:从"它们之间需不需要实时协调、靠什么协调"入手,往往一眼就能定位架构。
做完这七题,你应该能体会到:本章的概念不是孤立的知识点,而是一套**配合使用的分析工具**——这正是后续每一章都会反复调用的底层能力。
本章常见误解汇总¶
学完本章,下面这张表把全章最容易形成的错误心智模型与正确理解并排列出,作为快速纠偏:
| 常见误解 | 正确理解 |
|---|---|
| 分布式 = 去中心化 | 分布式仍需通信(多轮迭代达成一致),去中心化才是零通信(靠物理媒介隐式协调) |
| 机器人数 \(N\) 越大,越该用去中心化 | 架构取决于通信可靠性 × 最优性需求 × 规模三者权衡,不是单看 \(N\) |
| 集中式效率低 | 在机器人里集中式给出全局最优,只是计算量随 \(N\) 三次方膨胀;它的问题是扩展性而非最优性 |
| \(\lambda_2>0\) 就达标了 | \(\lambda_2>0\) 只是"连通"这一最低要求,其**大小**决定协调速度,要尽量大 |
| 加任意一条通信边都能加速共识 | 只有加在 Fiedler 瓶颈(桥)上才显著抬高 \(\lambda_2\),加在稠密区几乎无用 |
| Laplacian 就是 \(A-D\) | 本 Part 用 \(L=D-A\);符号反了会得出"系统发散"的错误结论 |
| 同型号不同参数(Go2 与 Go1)是异构 | 动力学结构/维度相同、只是参数不同叫**参数化同构**,用域随机化覆盖即可 |
| 异构不能参数共享是因为算法不成熟 | 这是观测/动作空间语义不同的**本质障碍**,出路是 HAPPO 或 Transformer,而非"等技术进步实现参数共享" |
| 多机 ROS 2 = 单机的简单复制 | 要主动处理命名、坐标系、发现、时间四类冲突 |
| tf 帧名不用加命名空间前缀 | 不加前缀两台机器人的 base_link 必然撞名,tf 查询随机错乱 |
| 跨机时间戳可以直接比较 | 必须先用 NTP/PTP/sim-clock 同步时钟,否则是在比不同时刻的数据 |
本章小结¶
本章建立了"看待任意多机系统"的认知地图。我们从最顶层的问题出发——多机协同,决策与信息如何组织——给出集中式/分布式/去中心化三种架构,并用复杂度分析(\(O(N^3)\) 爆炸)和决策树把"何时用哪种"讲清楚(§1.1)。随后用图论把"谁能和谁通信"变成可计算对象,提炼出贯穿全 Part 的代数连通性 \(\lambda_2\),并证明了它既判连通又定收敛速率(§1.2)。接着用同构/异构区分机器人本身,通过 Go2 + 四旋翼的联合动力学算例,说清了"能不能用一套模型、一个共享策略"的分水岭(§1.3)。最后把这些落到 ROS 2 工程地面:命名空间、tf2 多坐标系、DDS 扩展性、多机时间同步(§1.4)。
回到章首的两个"如果跳过会怎样":现在你有了**复杂度分析**来预见"集中式在第几台机器人时超时",也有了 \(\lambda_2\) 来量化"拓扑离失联还有多远、协调慢在哪里"——这正是这两个场景的解药。章首自测里那五个问题(特征值、连通性、tf 链、MPC 规模、\(O(m^3)\)),现在你应该能看出它们各自对应本章的哪一节。
三维分类全景:给一个多机场景贴上完整标签¶
§1.1 深入了 Farinelli 三轴里最工程化的"组织"维度——也就是架构。本章目标的第 1 条要求你能给场景贴**三维**标签,这里补全另外两轴,并示范怎么用:
- 交互维度(协作 / 竞争 / 混合):刻画 agent 之间目标的关系。协作(cooperative)——共享团队目标(双 Go2 搬运、编队),这是本 Part 的绝大多数;竞争(competitive)——目标对立(追逃、对抗);混合(mixed)——部分协作部分竞争(高速路多车,同道协作避撞、又抢道竞争)。这一维决定你用合作型 MARL(MAPPO/HAPPO,第 10 章)还是博弈方法。
- 信息维度(全局 / 局部 / 邻居):刻画每个 agent 能看到什么。全局——见全系统状态(集中式假设);局部——只见自身;邻居——见自身加通信邻居,而"谁是邻居"恰由 §1.2 的拓扑决定。这一维直接对应第 10 章 CTDE(训练时全局、执行时局部/邻居)的设计。
把三轴合起来,任何多机场景都能贴一张三维标签,据此定位技术线:
| 场景 | 架构 | 交互 | 信息 | 主要技术线 |
|---|---|---|---|---|
| 双 Go2 合抬桌子 | 分布式 / 去中心 | 协作 | 邻居 / 局部 | 分布式 MPC(Ch4)/ MARL(Ch8) |
| 工厂双臂精密装配 | 集中式 | 协作 | 全局 | 集中式联合优化(Ch7) |
| 高速路多车汇入 | 去中心 | 混合 | 邻居(感知范围内) | 博弈 / 交互预测(总大纲 G 线) |
| 十台机器人搬倒塌的梁 | 去中心 | 协作 | 局部(零通信) | 涌现式协调(Ch8 decPLM) |
用法:拿到任何多机问题,先贴这三维标签,你就大致知道该翻本课程的哪一章了。
术语速查表¶
| 术语(中) | English | 一句话定义 |
|---|---|---|
| 集中式 / 分布式 / 去中心化 | centralized / distributed / decentralized | 决策与信息的三种组织方式:中央统算 / 通信迭代 / 物理隐式 |
| 邻接矩阵 | adjacency matrix \(A\) | \(a_{ij}=1\) 表示 \(i\) 能收 \(j\) 的信息 |
| 度矩阵 | degree matrix \(D\) | 对角阵,\(d_{ii}\) 为邻居数 |
| Laplacian 矩阵 | Laplacian \(L=D-A\) | 通信图的核心矩阵,行和为零;等价于 \(BB^\top\) |
| 关联矩阵 | incidence matrix \(B\) | 顶点-边关联,每列对一条边记 \(+1/-1\);\(L=BB^\top\) |
| 代数连通性 / Fiedler 值 | algebraic connectivity \(\lambda_2\) | \(L\) 第二小特征值;\(>0\) 判连通,大小定收敛速率 |
| Fiedler 向量 | Fiedler vector | \(\lambda_2\) 对应特征向量,指示图的最优二分(瓶颈) |
| 共识 | consensus | 各 agent 朝邻居平均靠拢直至一致;\(\dot x=-Lx\) |
| 联合状态 / 联合控制 | joint state / control | \(N\) 台机器人状态/控制的堆叠 |
| 同构 / 异构 | homogeneous / heterogeneous | 各 agent 动力学函数与维度是否相同 |
| 参数化同构 | parametric-homogeneous | 结构相同仅参数不同;用域随机化覆盖 |
| 参数共享 | parameter sharing | 一个策略网络管所有 agent;需同构 |
| 排列不变 | permutation-invariant | 交换 agent 编号不改变系统行为 |
| 领导-跟随 | leader-follower | 异构系统常见角色分配:一个主导、其余跟随;对应有向拓扑 |
| 命名空间 | namespace | ROS 2 给每台机器人加话题/帧前缀,隔离命名 |
| 坐标变换库 | tf2 | 维护多坐标系树并支持变换查询 |
| 数据分发服务 | DDS | ROS 2 底层去中心化发现与通信中间件 |
| 发现服务器 | Discovery Server | Fast DDS 集中式发现,把 \(O(N^2)\) 降到 \(O(N)\) |
| Zenoh | Zenoh / rmw_zenoh |
路由式 RMW,适合大规模/跨网络/弱网 |
| 时间同步 | time synchronization | 多机时钟对齐;软件 NTP(ms)/ 硬件 PTP(μs)/ 仿真 /clock |
| 联合连通性 | jointly connected | 动态拓扑下时间窗内并集连通即可收敛 |
| Perron 矩阵 | Perron matrix \(P=I-\epsilon L\) | 离散共识的一步迭代;双随机时收敛到平均 |
| 双随机矩阵 | doubly-stochastic | 行和与列和都为 1;保证共识收敛到算术平均 |
| Metropolis 权重 | Metropolis–Hastings weights | 只用局部度数构造的双随机权重,对动态拓扑友好 |
| 本质谱半径 | essential spectral radius \(\rho\) | \(\max_{i\ge2}\lvert1-\epsilon\lambda_i\rvert\);离散共识每轮衰减率 |
| KKT 鞍点系统 | KKT saddle-point system | 约束动力学的方程组;解出约束力 \(\boldsymbol\lambda\) |
| Schur 补 | Schur complement \(JM^{-1}J^\top\) | 约束方向的等效惯量;求 \(\boldsymbol\lambda\) 的核心矩阵 |
| Baumgarte 镇定 | Baumgarte stabilization | 加阻尼项消除加速度级约束的数值漂移 |
| 交错定理 | eigenvalue interlacing | 加边后 \(\lambda_2(G)\le\lambda_2(G')\le\lambda_3(G)\),涨幅被封顶 |
知识点总表¶
| 编号 | 知识点 | 核心要点 | 对应节 | 难度 |
|---|---|---|---|---|
| 1.1 | 三大架构 | 集中(全局最优、\(O(N^3)\))/分布(可扩展、需通信)/去中心(零通信、无保证) | §1.1 | ⭐⭐ |
| 1.2 | 架构决策树 | 按 \(N\) → 最优性需求 → 通信可靠性 三层判断 | §1.1 | ⭐⭐ |
| 1.3 | 图论建模 | 图 \(G\)、\(A\)、\(D\)、\(L=D-A\) 的构造 | §1.2 | ⭐⭐ |
| 1.4 | Laplacian 性质 | \(L\mathbf1=0\)、半正定(\(L=BB^\top\))、\(\lambda_2>0\iff\)连通(带证明) | §1.2 | ⭐⭐⭐ |
| 1.5 | 代数连通性 \(\lambda_2\) | 双重含义:图最优二分 + 共识收敛速率 \(e^{-\lambda_2 t}\) | §1.2 | ⭐⭐⭐ |
| 1.6 | 常见拓扑 \(\lambda_2\) | \(K_N{=}N\)、\(C_N{=}2(1{-}\cos\frac{2\pi}{N})\)、\(S_N{=}1\)、\(P_N\) 最慢 | §1.2 | ⭐⭐⭐ |
| 1.7 | 有向/动态拓扑 | 非对称 \(L\)、有向生成树判据、联合连通性 | §1.2 | ⭐⭐⭐ |
| 1.8 | 同构 vs 异构 | 看动力学结构/维度是否相同;决定能否参数共享 | §1.3 | ⭐⭐ |
| 1.9 | 联合动力学 | 块对角 + 耦合项;异构块不同、维度不齐 | §1.3 | ⭐⭐ |
| 1.10 | 三维分类 | 架构 × 交互 × 信息,给场景贴标签定位技术线 | 小结 | ⭐⭐ |
| 1.11 | ROS 2 多机 | 命名空间 + tf2 前缀 + DDS 扩展性 + 时间同步 |
§1.4 | ⭐⭐ |
| 1.12 | 离散共识与步长 | \(P=I-\epsilon L\),步长上界 \(\epsilon<2/\lambda_N\),轮数 \(\propto\lambda_N/\lambda_2\) | §1.2 | ⭐⭐⭐ |
| 1.13 | 双随机权重 | 行随机→收敛、列随机→落在平均值;Metropolis 权重 | §1.2 | ⭐⭐⭐ |
| 1.14 | 约束力 \(\boldsymbol\lambda\) | KKT 鞍点系统解出,\(\boldsymbol\lambda=-(JM^{-1}J^\top)^{-1}(JM^{-1}b+\dot Jv)\) | §1.3 | ⭐⭐⭐ |
| 1.15 | 实时通信预算 | 供给 \(T_c/\tau_{\text{rt}}\) vs 需求 \(\lambda_N/\lambda_2\),两边赛跑定可行 | §1.5 | ⭐⭐⭐ |
| 1.16 | 多机 vs 群体 | 分界看个体"重/轻",非数个数 | §1.6 | ⭐ |
累积项目:本章新增模块¶
本章启动贯穿全 Part 的累积项目 Mini-MultiBot。第 1 章的任务是搭好仓库骨架与多机通信底座——后续每章往里加一个模块,到第 13 章收敛为一套 MPC + MARL 双方案的完整多机系统。
本章新增:仓库骨架 + ROS 2 多命名空间启动 + tf2 多坐标系。仓库结构(本章只填 communication/ 的底座,其余目录留待后续章节):
mini-multibot/
├── communication/
│ ├── ros2_multi_ns.launch.py # 本章:多命名空间启动(见 §1.4)
│ └── world_anchor.py # 本章:发布 world->go2_i/odom 的全局锚定
├── controllers/ # Ch2 起填充(ADMM/MARL/hybrid)
├── planning/ # Ch3 起填充(CBBA/CBS)
├── envs/ # Ch4/Ch8 起填充
└── evaluation/ # Ch13 填充
world_anchor.py 是本章的新代码——把每台机器人的 odom 锚定到全局 world(全局定位/动捕在此接入),它发布 tf 树里 world → go2_i/odom 那一层:
import rclpy
from rclpy.node import Node
from tf2_ros import TransformBroadcaster
from geometry_msgs.msg import TransformStamped
class WorldAnchor(Node):
"""发布 world -> go2_i/odom 的静态(或由全局定位更新的)变换。"""
def __init__(self, n_robots=2):
super().__init__('world_anchor')
self.br = TransformBroadcaster(self)
self.n = n_robots
self.create_timer(0.02, self.broadcast) # 50 Hz
def broadcast(self):
now = self.get_clock().now().to_msg()
for i in range(self.n):
t = TransformStamped()
t.header.stamp = now
t.header.frame_id = 'world' # 父帧:全局世界
t.child_frame_id = f'go2_{i}/odom' # 子帧:带命名空间前缀
# 这里用占位初值;实际由全局定位/动捕填充各机器人在 world 下的位姿
t.transform.rotation.w = 1.0
self.br.sendTransform(t)
def main():
rclpy.init(); rclpy.spin(WorldAnchor()); rclpy.shutdown()
配合 §1.4 的 ros2_multi_ns.launch.py,本章交付的就是一个"多台机器人各占命名空间、统一锚定到 world、时钟同步"的可运行骨架——这是后面所有协同模块的运行容器。
端到端 mini-lab:在通信底座上跑一次分布式平均共识¶
骨架搭好了,但它只是个"空容器"——还没有任何协同逻辑在上面跑。本节给一个**最小但完整**的实验,把本章的两条主线(§1.2 的共识 + §1.4 的多机通信)第一次接到一起:让若干台机器人各持一个初值(比如各自测到的某个量),只和邻居交换、最终对**全队平均值**达成一致。这正是 §三研究实践建议里那句"把 \(\lambda_2\) 与共识仿真亲手跑通"的落地版,也是第 2 章 ADMM、第 4 章分布式 MPC 一致性步的最小原型。
先不依赖 ROS——用纯 NumPy 把算法逻辑跑通、确认收敛,再谈搬到 ROS 节点上。算法就是 §1.2 第九部分的双随机(Metropolis 权重)平均共识:
import numpy as np
def metropolis_weights(N, edges):
"""由无向边集构造 Metropolis-Hastings 双随机权重矩阵 P。"""
adj = [[] for _ in range(N)]
for i, j in edges:
adj[i].append(j); adj[j].append(i)
deg = [len(a) for a in adj]
P = np.zeros((N, N))
for i in range(N):
for j in adj[i]:
P[i, j] = 1.0 / (1 + max(deg[i], deg[j])) # 由度大者决定,防过冲
P[i, i] = 1.0 - P[i].sum() # 对角补足 -> 行和=1
return P # 对称 => 同时列和=1 => 双随机
N = 5
edges = [(0, 1), (1, 2), (2, 3), (3, 4)] # 路径 P5(最慢拓扑,便于看清收敛)
P = metropolis_weights(N, edges)
x = np.array([10.0, 4.0, -2.0, 8.0, 0.0]) # 各机器人初值(如各自的局部测量)
target = x.mean() # 双随机 => 必收敛到算术平均
for k in range(60):
x = P @ x # 一轮 = 每台机器人和邻居做一次加权平均
if np.max(np.abs(x - target)) < 1e-3:
print(f"第 {k+1} 轮收敛到平均值 {target:.3f},此刻各机器人值={np.round(x,3)}")
break
跑出来你会看到:5 台机器人的值从离散的 [10, 4, -2, 8, 0] 逐轮靠拢,最终全部收敛到平均值 4.0。把 edges 换成全连接,收敛轮数会骤减(对照 §1.2 第八部分:\(\lambda_2\) 越大、轮数越少);换成"两段不相连的边",则永远收敛不到同一个值(图不连通,\(\lambda_2=0\))——亲手改一次边集、看一次收敛轮数的变化,比读十遍"\(\lambda_2\) 决定快慢"都管用。
搬到 ROS 2 上,算法一行不改,只是把"P @ x 这步全局矩阵乘"拆成"每台机器人**只问自己的邻居要值、本地加权**"。每台机器人是一个节点(在自己的命名空间里,§1.4),把自己当前的共识值发布到一个话题、同时订阅各邻居的同名话题:
# 概念骨架:第 i 台机器人的共识节点(运行在命名空间 /go2_i 下)
class ConsensusNode(Node):
def __init__(self, my_id, neighbor_ids, weight):
super().__init__('consensus')
self.x = float(my_id) # 本机初值(示例)
self.w = weight # 与每个邻居的 Metropolis 权重
self.nbr_val = {} # 缓存邻居最近一次的值
# 发布自己的值;话题名不带前缀,靠命名空间自动加 /go2_i/ 前缀(§1.4)
self.pub = self.create_publisher(Float64, 'consensus_val', 10)
for j in neighbor_ids: # 订阅每个邻居的绝对话题
self.create_subscription(Float64, f'/go2_{j}/consensus_val',
lambda m, j=j: self.nbr_val.__setitem__(j, m.data), 10)
self.create_timer(0.1, self.step) # 10 Hz:一个 timer 周期 = 一"轮"
def step(self):
if len(self.nbr_val) == len(self.w): # 收齐所有邻居才更新(同步一轮)
# 本地加权平均:x_i <- P_ii x_i + sum_j P_ij x_j,等价于全局 P@x 的第 i 行
self.x = (1 - sum(self.w.values())) * self.x + sum(self.w[j]*v
for j, v in self.nbr_val.items())
self.pub.publish(Float64(data=self.x))
这个骨架把本章四块知识缝在了一起,缝合点恰好是前面每一节踩过的坑——也是为什么本章要按那个顺序讲:
- §1.4 命名空间:话题
consensus_val不带前缀,靠节点所在命名空间自动变成/go2_i/consensus_val,这样 5 台机器人的同名话题互不串台(漏了命名空间就全挤到一个话题上,5 台机器人会读到彼此乱掉的值)。 - §1.2 双随机权重:
step()里的本地加权平均,正是全局P @ x的第 \(i\) 行——分布式执行与集中计算在这里数学上完全等价,差别只在"谁持有全局信息"。 - §1.4 时间同步:"收齐邻居才更新"这步隐含假设各机器人对"这是第几轮"有一致认识;实机上若时钟不同步,各机器人的轮次会错位,共识值时序错乱(这正是故障排查表"协调结果错乱但不报错"那条 bug 在共识场景的具体表现)。
- §1.5 实时预算:
timer设 10 Hz(周期 100 ms),够不够一轮?用 §1.5 第五部分的预算式估一下:有线网 \(\tau_{\text{rt}}\approx1\) ms 时绰绰有余;WiFi 下 \(\tau_{\text{rt}}\approx10\) ms 也还行;但若把频率提到 100 Hz(周期 10 ms)、又走 WiFi,一轮往返就吃满整个周期,共识会跟不上——这时要么降频、要么抬 \(\lambda_2\) 减少所需轮数。
本质洞察:这个 mini-lab 最该带走的不是代码,而是一个**贯穿全 Part 的等价关系——"分布式执行 = 把集中式的全局矩阵运算,按行拆给各个节点本地算"。
P @ x是集中视角(一个上帝视角的矩阵乘),step()里每台机器人各算P的一行是分布式视角(没有上帝,只有邻居),两者结果**逐位相同。理解了这一点,你看第 2 章的分布式优化、第 4 章的分布式 MPC 就不会觉得它们是"另起炉灶的新算法"——它们全都是同一个套路:先写出集中式的联合运算,再论证它可以按节点拆成"本地算 + 和邻居换一下"的形式。能不能这样拆、拆完要换几轮信息(回到 \(\lambda_2\)),就是分布式方法的全部技术核心。本章到此,你手里已经攥着把这套思想用起来的全部基础工具了。
延伸练习(把这个 mini-lab 长出来):(1) 把同步更新改成**异步**——不等收齐邻居,来一个邻居值就更新一次,观察收敛是否还成立、变快还是变慢(提示:异步共识在联合连通下仍收敛,但有效轮次被打乱);(2) 给某台机器人注入一个**持续发错值**的故障(模拟拜占庭节点),看朴素平均如何被带偏,再把更新换成"对邻居值取中位数",验证 §1.5 第二部分说的鲁棒共识确实抗污染;(3) 把它接到真实的 ros2_multi_ns.launch.py 骨架上,用 ros2 topic echo /go2_0/consensus_val 实时观察收敛曲线——这就把累积项目从"空骨架"推进到了"骨架上跑着第一个协同算法"。
延伸阅读¶
教材(打地基): - Bullo, Lectures on Network Systems(2019,免费在线,⭐⭐):图论 + 共识 + 分布式控制最佳入门,本章 §1.2 的内容在这里有完整展开。 - Mesbahi & Egerstedt, Graph Theoretic Methods in Multiagent Networks(Princeton University Press, 2010,⭐⭐⭐):谱图论与编队控制的系统化教材,书中对有向、切换、随机拓扑下的 agreement protocol 有完整论述。
综述(看全貌): - Olfati-Saber, Fax, Murray, Consensus and Cooperation in Networked Multi-Agent Systems, Proc. IEEE 2007(⭐⭐⭐):把图论、共识、编队统一起来的经典综述。 - Ren & Beard, Consensus Seeking in Multiagent Systems, IEEE TAC 2005(⭐⭐⭐):切换拓扑与联合连通性的源头。
论文(看标杆): - Kim, Fawcett, Kamidi, Ames, Akbari Hamed, Layered Control for Cooperative Locomotion of Two Quadrupedal Robots, IEEE T-RO 2023(⭐⭐⭐⭐):集中式 vs 分布式 MPC 的多足标杆,第 4 章精读。 - De Vincenti & Coros, Centralized MPC for Collaborative Loco-Manipulation, RSS 2023(⭐⭐⭐⭐):集中式 robot/payload/gait-agnostic SQP。 - Pandit, Shrestha, Fern, Multi-Quadruped Cooperative Object Transport: Learning Decentralized Pinch-Lift-Move, arXiv 2509.14342, 2025(Oregon State CoRIS,⭐⭐⭐⭐):去中心化(零通信)协同搬运的代表,第 8 章精读。
工程(动手查):
- ROS 2 官方文档:命名空间、tf2、DDS 实现选择与 ROS_DOMAIN_ID。
- Zenoh / rmw_zenoh 文档:大规模与弱网多机的发现替代方案。
本章与后续章节的关系¶
| 后续章节 | 关系 | 本章铺垫的知识点 |
|---|---|---|
| 第 2 章 共识与 ADMM | 把 §1.2 的共识从线性协议推进到分布式优化,补全收敛与时延理论 | \(L\)、\(\lambda_2\)、共识协议 |
| 第 3 章 任务分配与 MAPF | 任务分配与路径规划运行在通信图上 | 图论建模、拓扑 |
| 第 4 章 分布式 MPC 编队 | SRB 网络模型 = 同构联合动力学 + 耦合;ADMM 切分联合 QP | 联合动力学、\(O(N^3)\)、分布式架构 |
| 第 5 章 协同搬运与力控 | 负载力是联合动力学的耦合项 | 耦合项、内力 |
| 第 6 章 异构多机协同 | 异构建模直接落地为地空协同 | 同构/异构、HAPPO 伏笔 |
| 第 8 章 多足 loco-manipulation | 去中心化架构落地为零通信涌现协调 | 去中心化、隐式协调 |
| 第 10 章 MARL 基础 | 同构→参数共享 MAPPO;信息维度→CTDE | 参数共享、排列不变、信息维度 |
| 第 11 章 MARL 运动协调 | 排列不变网络让策略可扩展到任意 \(N\) | 排列不变 |
| 第 12 章 MARL+规控混合 | 混合架构在通信图上叠加 CBF 安全层 | 架构、拓扑 |
| 第 13 章 综合实战 | ROS 2 多机配置直接复用本章骨架 | 命名空间、tf2、时间同步 |
🔧 故障排查手册¶
多机系统的 bug 有一个共性特征:很多失效不报错、只是"行为变怪"——编队慢慢散架、协调结果错乱、某台机器人定位"鬼畜"。这让定位比单机难得多。先给一条贯穿全章的**总排查心法**,再给逐症状的速查表。
多机故障定位的三层切分法:拿到一个"行为怪但不报错"的多机系统,按下面三层从外到内切,能快速缩小范围——
- 先切"空间隔离"层(命名/坐标):用
ros2 topic list看话题前缀是否每机独立、ros2 run tf2_tools view_frames导出 tf 树查有无重名帧。这一层的 bug(命名串台、base_link撞名)表现为"数据张冠李戴",是最常见也最该先排除的。 - 再切"连接"层(发现/QoS):话题前缀对了但收不到数据,就查这一层——
ros2 topic info <topic> --verbose看发布/订阅两端是否都在、QoS 是否兼容;ros2 node list看节点是否都被发现。这一层的 bug(发现风暴、QoS 不匹配)表现为"该连的没连上"。 - 最后切"时间/协调"层(时钟/\(\lambda_2\)):前两层都对、数据也收到了,但协调结果错乱,就查最隐蔽的这一层——确认各机时钟源一致(实机 PTP、仿真
/clock),算当前拓扑的 \(\lambda_2\) 看是否连通且足够大。这一层的 bug(时钟漂移、拓扑悄悄分裂)表现为"数据对、但时序或一致性错"。
为什么按这个顺序切:因为它符合"从确定到隐蔽"的排查经济学——空间隔离层用现成命令一眼可查、连接层稍费力、时间/协调层最隐蔽且最易误判成"算法 bug"。多少人把时钟不同步当成"共识算法写错了",在算法里钻了几天牛角尖,其实跑一句对时就解决——遇到"协调诡异"先怀疑底座(命名、连接、时钟),再怀疑算法,是多机调试最省时间的一条经验。
逐症状速查表:
| 症状 | 可能原因 | 排查步骤 | 相关节 |
|---|---|---|---|
| 共识不收敛 / 编队慢慢散架 | 拓扑不连通(\(\lambda_2=0\))或 \(\lambda_2\) 过小 | 1. 算当前拓扑 \(\lambda_2\) 2. 检查通信链路通断 3. 运动中实时监控 \(\lambda_2\) 最小值 | §1.2 |
tf 变换查询随机返回错误位姿 |
多机帧名未加命名空间前缀,base_link 撞名 |
1. view_frames 导出 tf 树查重名 2. 给 robot_state_publisher 配 frame_prefix |
§1.4 |
| 给一台发指令同时驱动了多台 | 漏 PushRosNamespace 或共享话题未 remap |
1. ros2 topic info 看话题前缀 2. 确认每机一个命名空间 3. 共享话题显式 remap |
§1.4 |
| 多机启动极慢 / 偶发丢消息 | 发现风暴(\(N\) 大却用默认 Simple Discovery) | 1. 统计参与者数 2. 换 Discovery Server 或 Zenoh 3. 或用 ROS_DOMAIN_ID 分域 |
§1.4 |
| 多机融合/协调结果错乱但程序不报错 | 各机时钟未同步,时间戳不可比 | 1. 检查时钟源 2. 实机上 NTP/PTP 3. 仿真确认 use_sim_time + /clock |
§1.4 |
| 集中式 MPC 随机器人增多而超时 | 联合 QP 用稠密求解,成本 \(O(N^3)\) | 1. 打印 KKT 矩阵稀疏度 2. 换稀疏求解器 3. 或改分布式 MPC 切分 | §1.1 |
| 话题 echo 有数据,自己回调收不到 | 跨机 QoS 不匹配(发布/订阅可靠性或持久性不兼容) | 1. ros2 topic info <t> --verbose 比对两端 QoS 2. 对齐 reliability/durability 3. 高频流改 BEST_EFFORT |
§1.4 |
| 加了通信边但共识没变快 | 边加在了稠密区而非 Fiedler 瓶颈(桥) | 1. 算 Fiedler 向量定位瓶颈 2. 把边加在向量值差异最大的两组间 3. 加边前后各算一次 \(\lambda_2\) | §1.2 |
| 有向拓扑算出的 \(\lambda_2\) 像是错的 | 对非对称 \(L\) 误用 eigvalsh(只读下三角) |
1. 确认拓扑是否有向 2. 改用 eigvals 按实部排序 3. 判连通改用"有向生成树" |
§1.2 |
| 异构队伍训练不收敛 / 各类机器人都次优 | 把异构强行套共享网络(padding 对齐维度未对齐语义) | 1. 确认动力学结构/维度是否真不同 2. 真异构改 HAPPO 或按类型分组共享 3. 仅参数不同则用 DR | §1.3 |
| 两机抬刚性杆仿真"穿模"/杆被拉长 | 联合积分漏了耦合项(当成 \(N\) 个独立 ODE) | 1. 检查是否显式施加约束力 \(\boldsymbol\lambda\)/绳力 2. 每步监控附着点约束残差 3. 残差非零即漏耦合 | §1.3 |
| 移动编队链路频繁通断后协调时好时坏 | 动态拓扑瞬时分裂,\(\lambda_2\) 间歇归零 | 1. 监控 \(\lambda_2\) 时间序列 2. 验证"时间窗内并集连通"(联合连通性) 3. 必要时主动收拢恢复连通 | §1.2 |
| 离散共识发散 / 状态在均值两侧越荡越大 | 步长 \(\epsilon\) 超过稳定上界 \(2/\lambda_N\),最高频模态发散 | 1. 算当前拓扑 \(\lambda_N\)(\(L\) 最大特征值) 2. 取 \(\epsilon<2/\lambda_N\),求最快用 \(\epsilon^\star=2/(\lambda_2+\lambda_N)\) 3. 加边降度数也会降 \(\lambda_N\) | §1.2 |
| 共识收敛到一个"怪值"而非算术平均 | 权重矩阵只行随机、丢了列随机,稳态被度数带偏 | 1. 检查 \(P\) 是否双随机(\(P\mathbf1=\mathbf1\) 且 \(\mathbf1^\top P=\mathbf1^\top\)) 2. 改用 Metropolis 权重 3. 验证 \(\mathbf1^\top x\) 每轮守恒 | §1.2 |
| 约束力 \(\boldsymbol\lambda\) 算不出/约束缓慢漂移(杆渐渐变长) | 只在加速度级施加约束、无镇定,积分误差累积 | 1. 确认解的是 KKT 鞍点系统而非独立 ODE 2. 加 Baumgarte 镇定项消位置/速度残差 3. 监控残差是否单调增长 | §1.3 |
| 实时协调达不到目标频率(降频才稳) | 通信供给(\(T_c/\tau_{\text{rt}}\))< 收敛需求(\(\lambda_N/\lambda_2\) 轮) | 1. 供给侧:算每周期可用轮数 \(\gamma T_c/\tau_{\text{rt}}\) 2. 需求侧:算 \(k_{\text{需}}\approx\frac{\lambda_N}{2\lambda_2}\ln\frac1\delta\) 3. 供给小则降频/换网,需求大则加边/换架构 | §1.5 |
API 速查表¶
| API / 工具 | 用途 |
|---|---|
Eigen::SelfAdjointEigenSolver |
对称 Laplacian(无向图)特征值,取 \(\lambda_2\) |
Eigen::EigenSolver |
非对称 Laplacian(有向图)特征值,看实部 |
numpy.linalg.eigvalsh / eigvals |
同上的 Python 对应(对称用前者) |
launch_ros.actions.PushRosNamespace |
给本组节点/话题加命名空间前缀 |
launch.actions.GroupAction |
把同一机器人的节点编成一组 |
robot_state_publisher 的 frame_prefix |
给该机器人的 tf 帧自动加前缀 |
tf2_ros.TransformBroadcaster / Buffer |
发布 / 查询坐标变换 |
tf2_tools view_frames |
导出 tf 树,排查重名帧与断链 |
ROS_DOMAIN_ID(环境变量) |
DDS 分域隔离,无关子系统互不发现 |
| Fast DDS Discovery Server | 集中式发现,降发现流量到 \(O(N)\) |
rmw_zenoh(RMW 实现) |
Zenoh 中间件,适合大规模/跨网络/弱网 |
核心公式速查表¶
把本章会被后续章节反复调用的公式集中在此,便于回查;每条标注它"是什么、从哪来、在哪用"。
| 公式 | 含义 | 关键结论 / 用处 | 节 |
|---|---|---|---|
| \(L=D-A\) | 组合 Laplacian | 通信图的核心矩阵;行和为零 | §1.2 |
| \(L=BB^\top\) | Laplacian = 关联矩阵的 Gram 阵 | 一眼看穿半正定;它是"图上差分算子的平方" | §1.2 |
| \(x^\top L x=\tfrac12\sum_{(i,j)}(x_i-x_j)^2\) | \(L\) 的二次型 | "邻居不一致的总能量";共识/编队惩罚项都长这样 | §1.2 |
| \(\lambda_2>0\iff\) 图连通 | 代数连通性判据 | 0 特征值重数 = 连通分量数 | §1.2 |
| \(\|x(t)-\bar x\mathbf1\|\le e^{-\lambda_2 t}\|x(0)-\bar x\mathbf1\|\) | 连续共识收敛率 | 收敛速率就是 \(\lambda_2\) | §1.2 |
| \(K_N{:}\,\lambda_2{=}N\);\(C_N{:}\,2(1{-}\cos\tfrac{2\pi}{N})\);\(S_N{:}\,1\);\(P_N{:}\,2(1{-}\cos\tfrac{\pi}{N})\) | 常见拓扑 \(\lambda_2\) | 全连接最快、链最慢(\(\sim\pi^2/N^2\)) | §1.2 |
| \(P=I-\epsilon L\) | 离散共识 Perron 矩阵 | 一步 = 邻居加权平均;双随机时收敛到平均 | §1.2 |
| \(0<\epsilon<2/\lambda_N\) | 步长稳定上界 | 越界则最高频模态发散(过冲振荡) | §1.2 |
| \(\epsilon^\star=\tfrac{2}{\lambda_2+\lambda_N},\ \rho^\star=\tfrac{\kappa-1}{\kappa+1}\) | 最优步长与收敛率,\(\kappa=\lambda_N/\lambda_2\) | 与梯度下降在条件数 \(\kappa\) 上同形 | §1.2 |
| \(k\gtrsim\tfrac{\lambda_N}{2\lambda_2}\ln\tfrac1\delta\) | 收敛到精度 \(\delta\) 的轮数 | 轮数正比于 \(\lambda_N/\lambda_2\)(不只是 \(1/\lambda_2\)) | §1.2 |
| \(\dim(\text{决策变量})\approx\sum_i(n_i+m_i)(T+1)\) | 集中式联合 QP 规模 | 单次迭代成本稠密时 \(O(N^3)\) | §1.1 |
| \(\dot x_i=f(x_i,u_i)\)(同) vs \(f_i(x_i,u_i)\)(异) | 同构 / 异构动力学 | 各块是否同函数 → 能否参数共享 | §1.3 |
| \(\boldsymbol\lambda=-(JM^{-1}J^\top)^{-1}(JM^{-1}b+\dot Jv)\) | 约束力(KKT 鞍点的解) | 内力被约束唯一确定,非自由参数 | §1.3 |
| \(k_{\text{供}}\ge k_{\text{需}}\) | 实时通信预算判据 | \(\gamma T_c/\tau_{\text{rt}}\ge\frac{\lambda_N}{2\lambda_2}\ln\frac1\delta\) 才可行 | §1.5 |
| \(\lambda_2(G)\le\lambda_2(G')\le\lambda_3(G)\) | 加边的交错(interlacing)上下界 | 加边只增不减,但增量被 \(\lambda_3-\lambda_2\) 封顶 | §1.2 |
| \(P_{ij}=\frac{1}{1+\max(d_i,d_j)}\)(\(j\in\mathcal N_i\)) | Metropolis 权重(非对角) | 只用局部度数构造双随机矩阵 | §1.2 |
约定提醒:全 Part 用 \(L=D-A\)(非 \(A-D\));无向图 \(L\) 对称用 eigvalsh,有向图非对称用 eigvals 看实部。术语全称与一句话定义见上文术语速查表。
复现说明:本章所有 Python 代码块均可独立运行(仅依赖 NumPy ≥ 1.24),贴出的数值输出即实际运行结果——读者可直接复制验证 \(\lambda_2\)、离散共识收敛、约束力 \(\boldsymbol\lambda\)、Fiedler 瓶颈、六维加权选型等结论;数值因平台浮点实现可能在末位有微小差异,不影响结论。
研究实践建议¶
给初学者:把 §1.2 的 \(\lambda_2\) 与共识仿真**亲手跑通**——这是后面所有分布式方法(共识、ADMM-MPC、CTDE)的直觉来源,只读不写很难真正建立"\(\lambda_2\) 决定快慢"的体感。ROS 2 多机骨架务必动手搭一次,把命名、tf、时间三类坑各踩一遍,远比看文档记得牢。这个阶段先**不要碰异构**,把同构系统(双 Go2)的建模与工程吃透,异构是后面第 6 章的事。
给有经验者:把运动中 \(\lambda_2\) 的实时值当作"协调还剩多少裕量"的在线指标,在动态拓扑下监控它的最小值——这比事后发现编队散架要主动得多。关注 Zenoh(自 ROS 2 Kilted 起为 Tier 1 RMW)在大规模与弱网下的实测表现,它正在成为 DDS 默认发现之外的主流选项。判断一个多机系统该用哪条技术线时,养成先贴"架构 × 交互 × 信息"三维标签的习惯;尤其注意把"参数化同构"(用域随机化解决)和"真异构"(需 HAPPO/Transformer)区分开,别一看机器人型号不同就过早上 HAPPO,白白丢掉同构的数据效率。
进入第 2 章前的自检¶
第 2 章把本章 §1.2 的线性共识推进到分布式优化(ADMM),会**直接复用**本章建立的几样东西。在翻到下一章前,用下面五题自检——答得出来,说明本章的地基已经打牢,第 2 章会接得很顺;答不出的那几题,指向的正是你该回看的小节:
- 写出连续共识 \(\dot x=-Lx\) 的解,并说明误差为什么按 \(e^{-\lambda_2 t}\) 衰减。答不出 → 回 §1.2 第四部分(代数连通性的动力学含义)。
- 给定一张图,如何判断它连通?\(\lambda_2\) 和连通分量个数是什么关系?答不出 → 回 §1.2 第三部分(Laplacian 性质 3 含证明)。
- 离散共识 \(x[k{+}1]=(I-\epsilon L)x[k]\) 的步长 \(\epsilon\) 有上界吗?达到精度 \(\delta\) 大约要几轮?答不出 → 回 §1.2 第八部分(步长上界与轮数公式)——第 2 章 ADMM 的轮数分析直接建在这上面。
- 两台机器人合抬刚性杆,它们之间的内力 \(\boldsymbol\lambda\) 怎么解出来?它和"约束"是什么关系?答不出 → 回 §1.3 第五部分(KKT 鞍点求 \(\boldsymbol\lambda\))——第 2 章 ADMM 协调的正是这个 \(\boldsymbol\lambda\)。
- 同样要协调 \(N\) 台机器人,集中式、分布式各赌什么资源够用?答不出 → 回 §1.1 的消息流对比 + §1.5 第五部分(实时通信预算)。
第 2 章的核心是:把"每台机器人本地解子问题、再和邻居对齐耦合变量"这件事写成一个有收敛保证的迭代算法(ADMM),而它需要多少轮通信才收敛,答案就藏在本章的 \(\lambda_2\) 里。换句话说,第 2 章是本章"分布式执行 = 集中式运算按节点拆分"这一思想(见端到端 mini-lab 的本质洞察)的严格化与算法化。带着本章的 \(L\)、\(\lambda_2\)、\(\boldsymbol\lambda\) 进入第 2 章,你会发现新内容几乎都是老工具的自然延伸,而非另起炉灶。
最后留一个**衔接处最易踩的认知坑**给你提前避开:第 2 章会反复出现"通信轮数"和"控制周期"两个时间尺度,初学者极易把它们混为一谈——以为"一个控制周期 = 共识收敛"。其实一个控制周期里往往只够跑有限几轮通信(本章 §1.5 第五部分的预算就是算这个),共识未必在一个周期内完全收敛,而是"边收敛边控制"。所以读第 2 章时,每看到一个分布式算法,都习惯性地问一句:它假设每个控制周期能跑几轮通信?这个假设在我的网络(\(\lambda_2\)、延迟)下成立吗? 带着这个问题读,你就能一眼看穿一个分布式方法"在纸面上漂亮、在实时系统里能不能落地"的关键——这正是本章反复强调"\(\lambda_2\) 换算成轮数、轮数对上实时预算"的全部用意。
回到本章开头那两个会让人栽跟头的场景——架构选错导致系统在第几台机器人时跑不动、拓扑悄悄不连通导致编队无声散架。读完本章,你已经握有两件趁手的工具:用复杂度分析(\(O(N^3)\))预见前者,用代数连通性 \(\lambda_2\) 量化并监控后者。更重要的是,你现在有了一张贴标签的方法——任何多机场景,先看架构、交互、信息三维,就知道该走哪条技术线。
第 1 章完结。 下一章我们把 §1.2 的共识从"线性协议"推进到"分布式优化",补全线性共识的完整收敛性证明、时延对收敛的影响,并引入 ADMM——它是后续所有分布式 MPC 的数学引擎。\(\lambda_2\) 在那里会再次出现:它将直接决定 ADMM 与共识算法需要多少轮通信才能收敛。
版本信息速查¶
本章涉及的工具、中间件与平台版本(截至 2026 年中),用于复现与查阅;后续各章若引入新版本会在其章末更新。
| 项目 | 版本 / 状态 | 说明 |
|---|---|---|
| ROS 2 发行版 | Jazzy(2024-05,LTS)/ Kilted(2025-05) | 本章 launch、tf2、命名空间在两者上写法一致 |
rmw_zenoh |
Jazzy 起实验性 → Kilted 起 Tier 1 | Zenoh 路由式 RMW;大规模/弱网替代 DDS 默认发现 |
| DDS 实现 | Fast DDS(默认)/ Cyclone DDS | Discovery Server 为 Fast DDS 特性 |
| IsaacLab | 2.3.0(构建于 Isaac Sim 5.1.0) | 后续 MARL 章节(第 8、10–13 章)主力训练平台 |
| Isaac Sim | 5.1.0 | Newton 物理后端逐步成熟,走向 PhysX + Newton 多后端 |
| IsaacGym | legacy(已停更) | 被 IsaacLab 取代;部分旧多机环境(如 MQE)仍基于它 |
| OCS2 | 多体 MPC(含 loco-manipulation 扩展) | 第 4、5 章分布式 MPC 主线 |
| MAPPO | repo marlbenchmark/on-policy |
同构系统参数共享训练,第 10 章起 |
| HARL | repo PKU-MARL/HARL |
异构 HAPPO 实现,第 10 章起 |
| NumPy | ≥ 1.24 | 本章 \(\lambda_2\) 与共识仿真代码 |
说明:上表版本为撰写时锁定值,工具链更新较快;实际复现时以各项目官方发行说明为准。关键论文的作者、年份、会议/期刊以正文引用与延伸阅读为准。