
一句话介绍: 这套 MATLAB 程序实现了 Consensus-Based Bundle Algorithm(CBBA):每个代理根据任务奖励、到达时间、燃料距离代价和任务时间窗独立构造任务 Bundle,再通过邻居通信不断交换赢家与投标信息、消除冲突,最终形成多代理多任务分配结果;当前示例包含 QUAD、CAR 两类代理和 TRACK、RESCUE 两类任务,并显式设置异构代理—任务兼容关系。
多智能体任务分配真正困难的地方,不只是“把 10 个任务分给 5 个代理”。
因为每个任务还有自己的:
空间位置;可执行时间;持续时间;任务奖励;奖励随延迟产生的折扣。
每个代理也有自己的:
类型;初始位置;可用时间;巡航速度;燃料距离代价。
更重要的是,并不是所有代理都能执行所有任务。
当前示例中,QUAD 只允许执行 TRACK,CAR 只允许执行 RESCUE,因此分配过程还必须满足异构兼容约束。
CBBA 的思路不是设置一个中央调度器一次性求解全部组合,而是让各代理反复完成:
本地计算任务价值 → 构造 Bundle → 与邻居交换赢家信息 → 解决冲突 → 释放失去的任务 → 重新竞价 → 达成一致
最终得到每个代理的:任务执行路径 + 预计开始时间 + 本地得分 + 全网任务赢家,这正是当前程序的核心。
1. 当前工程真正由哪些程序组成?
压缩包中的工程比较紧凑,全部为 MATLAB .m 文件,没有额外数据集、训练模型或配置文件。
其中最关键的文件是:
文件 | 主要作用 |
|---|---|
MainTestScript.m | 示例主入口,生成代理、任务和通信网络并启动 CBBA |
CBBA_Main.m | CBBA 主循环,负责通信、Bundle 更新和收敛判断 |
CBBA_ComputeBids.m | 尝试不同插入位置,计算各任务可获得的最佳投标值 |
CBBA_Communicate.m | 在相邻代理之间执行赢家与投标值的一致性更新 |
Scoring_CalcScore.m | 计算任务开始时间、可行时间区间和任务得分 |
PlotAssignments.m | 绘制空间—时间任务路径和各代理任务时间表 |
此外还有:
CBBA_Bundle.m、CBBA_BundleAdd.m、CBBA_BundleRemove.m
分别负责 Bundle 的总体更新、增加任务和释放失去的任务。
所以整个真实执行链可以概括为:
MainTestScript→CBBA_Main→ 通信一致性 → Bundle 删除/增加 → 投标计算 → 得分计算 → 收敛 → 绘制分配结果
2. 当前示例到底设置了多少代理和任务?
MainTestScript.m 中直接给出了:
代理数量 N = 5、任务数量 M = 10
代理分为两类:
前 2 个代理为 QUAD;后 3 个代理为 CAR。
任务也分为两类:
任务 1~5 为 TRACK;任务 6~10 为 RESCUE。
当前世界坐标范围为:
方向 | 范围 |
|---|---|
X | -2.0 ~ 2.5 m |
Y | -1.5 ~ 5.5 m |
Z | 0 ~ 2.0 m |
代理的 X、Y 初始位置随机生成,Z 使用默认值 0;任务的 X、Y、Z 三维位置都在上述区域中随机产生。
3. “异构代理”在当前程序里具体体现在哪里?
CBBA 初始化函数 CBBA_Init.m 定义了两个代理类型:
QUAD = 1、CAR = 2
以及两个任务类型:
TRACK = 1、RESCUE = 2
真正决定“谁能做什么”的,是兼容矩阵 CM。
当前配置相当于:
代理类型 | TRACK | RESCUE |
|---|---|---|
QUAD | 1 | 0 |
CAR | 0 | 1 |
也就是说:
QUAD → TRACK、CAR → RESCUE
交叉组合全部禁止。
这一步非常重要,因为 CBBA_ComputeBids.m 在真正计算投标之前,会先检查:
CM(代理类型, 任务类型) > 0
只有满足兼容条件的任务才会进入后续竞价。
需要注意的是,当前示例中 QUAD 和 CAR 的默认巡航速度都为:2 m/s
燃料距离系数也都为:1
因此当前演示里的“异构性”主要体现在:
任务类型兼容关系不同,而不是两类代理拥有不同的速度或燃料模型。
如果需要更明显的异构特征,可以进一步分别修改两类代理的速度、燃料系数以及评分函数。
4. 每个代理内部到底保存了什么?
CBBA_Main.m 会为每个代理建立一套独立的 CBBA 状态。
其中最关键的是:
bundle:代理按照竞价过程获得任务的顺序;
path:真正准备执行任务的时间顺序;
times:路径中各任务预计开始时间;
scores:当前路径中任务对应的得分;
bids:当前代理对全部任务计算出的投标值;
winners:代理当前认为每个任务属于谁;
winnerBids:代理当前掌握的各任务最高投标值。
这里特别容易混淆的是:
Bundle 顺序不等于任务执行顺序。
Bundle 记录的是“任务被加入本地任务集 合的次序”,而 path 记录的是考虑时间窗和旅行时间之后真正的执行次序。
CBBA 正是利用这种设计,让代理可以不断增加任务,同时把新任务插入当前路径中的不同位置。
5. 一个代理准备竞拍任务时,首先要判断什么?
首先不是比较奖励,而是判断:
这个任务插 进当前路径以后还能不能按时间完成?
假设当前准备插入任务 j。
如果它是路径中的第一个任务,程序先计算代理从当前位置飞行或行驶到任务点所需的时间:
Δt = D(agent, taskⱼ) / v
最早开始时间为:
tₘᵢₙ = max(taskⱼ.start, agent.avail + Δt)
如果前面已经有一个任务,则改成:
tₘᵢₙ = max(taskⱼ.start, tₚᵣₑᵥ + durationₚᵣₑᵥ + Δt)
也就是必须满足:
完成前一个任务 → 移动到新任务 → 到达任务允许开始的时间
如果新任务后面还有已经承诺的任务,程序还会反向计算它允许的最晚开始时间:
tₘₐₓ = min(taskⱼ.end, tₙₑₓₜ - durationⱼ - Δtₙₑₓₜ)
最终只有:
tₘᵢₙ ≤ tₘₐₓ
这个插入位置才被认为可行。
因此,当前程序并不是简单执行“最近任务优先”,而是在每一次任务插入时同时检查:
空间距离 + 旅行时间 + 前序任务 + 后续任务 + 时间窗
6. 一个任务的投标值是怎样算出来的?
任务在当前示例中首先有一个基础奖励:
value = 100
同时设置指数折扣参数:λ = 0.1
如果任务实际开始时间比任务允许的起始时间晚,奖励会逐渐降低。
源码使用的关系为:
reward = value × exp[-λ × (tₘᵢₙ - task.start)]
因此:
越接近任务开始时间执行,奖励越高;延迟越大,奖励越低。
程序随后减去燃料距离代价:
penalty = fuel × D(agent, task)
最终:
score = reward - penalty
当前所有代理:fuel = 1
所以投标值实际上是在权衡:
任务越有价值、越早执行越好,同时距离过远又会产生惩罚。
这正是辅助元数据中所说的:
平衡任务奖励和燃料成本的得分函数。
7. 为什么同一个任务要尝试很多个插入位置?
假设代理当前路径已经是:
任务 A → 任务 B → 任务 C
新任务 D 并不一定只能放在最后。
程序会依次尝试:
D → A → B → C
A → D → B → C
A → B → D → C
A → B → C → D
每一个位置都重新检查时间可行性,并计算当前任务对应的得分。
CBBA_ComputeBids.m 最终保留:
最佳投标值 bestBid;
最佳插入位置 bestIndex;
对应开始时间 bestTime。
因此它解决的是:
“如果我想增加这个任务,插到现有计划中的哪个位置最合适?”
而不是简单把任务依次追加到队尾。
程序还维护一个 feasibility 矩阵。
如果某个任务在某个插入位置已经被判定不可行,就将该位置标记掉,后续 Bundle 构造时不再重复尝试这项明显无效的组合。
8. 每个代理怎样逐步建立自己的 Bundle?
CBBA_BundleAdd.m 会不断重新计算当前还能竞争哪些任务。
对于任务 j,程序主要判断:
当前自己的 bid 是否高于已知 winnerBid?
如果两者相差在容差以内,则进一步通过代理编号解决平局:
编号更小的代理优先。
当前使用的数值容差为:ε = 10⁻⁵
在所有当前可以赢得的任务中,代理选择投标值最高的一个,将它:
写入自己的 bundle;插入 path 的最佳位置;写入预计执行时间;保存对应得分;
把自己记录成这个任务的赢家。
然后继续重新计算下一项任务。
所以单个代理的 Bundle 构造过程可以理解成:
检查可做任务 → 测试路径插入位置 → 计算投标 → 选择收益最高任务 → 加入 Bundle → 更新路径 → 再次竞价
一直到:Bundle 达到最大深度;
或者再也没有正收益且能够击败当前赢家的任务。
当前程序把:MAX_DEPTH = M
因此 10 个任务的示例中,Bundle 最大深度也是 10。
9. 每个代理都独立竞价,为什么最后不会抢到同一个任务?
这就是 CBBA 名字里 Consensus-Based 最关键的一部分。
CBBA_Communicate.m 会让通信图中相邻代理交换两类信息:
这个任务当前由谁获胜?
以及:
当前获胜投标值是多少?
程序按照源码中完整的 17 类通信规则,对不同信息冲突执行:
Update;Reset;Leave。
例如两个代理都认为自己获得了同一个任务时,会比较投标值。
如果投标值存在明显差异:更高投标值胜出。
如果两者在 ε 范围内相同:代理编号更小的一方胜出。
同时程序还维护代理间信息更新时间矩阵 t,用于判断:谁掌握的赢家信息更新。
因此代理之间不是简单地“广播一次最高分”,而是在不断交换:
赢家身份 + 赢家投标值 + 信息时间戳
以后逐步消除不同代理内部认知之间的冲突。
10. 一个代理被别人出价超过以后会发生什么?
这是 Bundle 机制的另一个关键点。
假设某个代理建立 Bundle 的顺序为:
任务 A → 任务 B → 任务 C → 任务 D
通信以后发现:任务 B 已经被其他代理抢走。
CBBA_BundleRemove.m 不只是删除 B。
程序会释放:B 以及它后面加入 Bundle 的 C、D
也就是:A 保留,B/C/D 重新参与后续竞价。
同时从:path;times;scores中删除对应任务。
这样做的原因是:后续任务的投标价值是在之前 Bundle 已经成立的前提下计算出来的。
一旦前面的任务归属发生变化,后续结果也需要重新计算,而不能继续沿用旧计划。
于是每轮过程实际是:
邻居通信 → 找到被别人抢走的任务 → 回退受影响的 Bundle → 重新计算剩余任务价值 → 再次投标
这也是 CBBA 能够逐步收敛到一致任务分配的重要机制。
11. 当前示例使用什么样的通信网络?
MainTestScript.m 中直接设置:
Graph = ~eye(N)
对于 N=5,这代表:
除自己以外,每个代理都可以直接和其他所有代理通信。
也就是一个全连接通信图。
因此当前示例重点展示的是:
CBBA 的任务竞争和一致性规则
而不是复杂通信拓扑带来的影响。
CBBA_Main 和 CBBA_Communicate 本身仍然通过 Graph(k,i) 判断两个代理之间是否能够通信,因此如果后续希望研究:
稀疏通信网络;邻接拓扑变化;不同网络连通结构;
可以从 Graph 入手修改。
不过当前压缩包没有进一步实现通信延迟、丢包率或无线链路质量模型。
12. 程序在什么条件下认为分配已经收敛?
CBBA 主循环每一轮执行:
通信 → Bundle 删除/增加 → 检查是否出现新投标
只要任意代理产生新的有效投标,就更新 lastTime。
当前代码的主要停止条件为:
T - lastTime > N
也就是:连续超过一个代理数量尺度的迭代没有产生新投标时,认为任务分配已经稳定。
对于当前:N = 5
最终停止不是由预先指定固定迭代次数决定,而是由任务竞价是否继续发生变化决定。
收敛以后,程序把内部路径索引重新映射成任务真实 id,并得到:CBBA_Assignments
以及:Total_Score
其中每个代理的分配结构中仍然保存路径、执行时间、任务得分和赢家信息。
13. 最终结果怎样显示出来?
PlotAssignments.m 会把分配结果从两个角度展示出来。
第一类结果把:
X 位置 + Y 位置 + 时间
放在一个三维坐标系中。
图中同时包含:
代理初始位置;任务空间位置;任务有效时间区间;每个代理前往各任务的连接路径;
实际任务执行时段。
因此它能够直接观察:哪个代理在什么时候,从哪里前往哪个任务。
第二类结果则为每个代理单独建立时间轴,显示:
任务允许时间窗 + 当前分配得到的任务执行时间
这样可以更直接地检查任务调度在时间维度上的安排。
因此程序最终提供的并不只是一个“任务编号列表”,而是:
任务归属 + 执行顺序 + 开始时间 + 时空路径 + 调度时间表
14. 当前程序里的 Total_Score 应该怎样理解?
这里有一个非常重要的源码说明。
Scoring_CalcScore.m 在燃料代价部分明确写明:
当前 fuel score 是一个简化的示范评分,会出现燃料重复计算,不应直接用于和真正的全局最优得分进行比较。
原因之一是当前燃料惩罚使用:
代理初始位置 → 当前任务
之间的距离:
penalty = fuel × D(agent, task)
即使这个任务实际上被 插入路径中间,也没有按照完整的:
前一任务 → 当前任务 → 后一任务
重新计算整个路径真实燃料增量。
所以当前 Total_Score 更适合理解为:
这套 CBBA 示例评分规则下,各代理当前任务得分的汇总。
而不应该把它宣传成:
全局真实燃料成本;全局最优目标函数;与集中式最优求解器严格可比较的最优性指标。
如果后续用于论文对比或工程优化,评分函数是最值得重新设计的模块之一。
15. 这套程序适合用在哪里?
当前程序已经具备:
代理类型 + 任务类型 + 兼容矩阵
因此很适合把 QUAD/CAR、TRACK/RESCUE 替换为实际任务中的:
无人机与无人车;巡检平台与运输平台;搜索任务与救援任务;不同传感器能力对应的不同任务类别。
程序显式考虑:
任务起始时间;时间上界;任务持续时间;代理旅行时间;前后任务衔接。
因此比单纯的“最近任务分配”更适合研究有时序约束的任务规划。
工程把 CBBA 核心过程拆得比较清楚:
投标 → Bundle → 路径插入 → 邻居通信 → 冲突消解 → Bundle 回退
因此适合用于:
多智能体系统课程;分布式任务分配算法学习;CBBA 二次开发;自定义评分函数实验;通信拓扑实验。
如果后续准备加入:
更多代理类型;更多任务类型;能量约束;不同速度;优先级;动态任务;新的效用函数;
当前程序可以作为一个较清晰的基础版本继续修改。
16. 使用当前代码时有哪些边界需要特别注意?
首先,当前 MATLAB 工程模拟的是:
分布式 CBBA 算法逻辑。
虽然每个代理维护自己的 Bundle、赢家和投标信息,但所有代理仍在同一个 MATLAB 程序中同步迭代,并不等于已经实现真实机器人之间的网络通信程序。
其次,当前时间窗定义值得特别注意。
源码把 task.end 注释为任务 expiry time,但在评分函数中,如果当前任务位于路径末尾,直接使用:
maxStart = task.end
也就是说现有实现实际上把 end 当成了:
任务允许开始时间的上界。
而不是严格的:
任务必须在 end 之前完成。
当前示例又设置:
task.end = task.start + task.duration
如果实际项目要求严格的“完成截止时间”,应重新核对这一时间窗定义。
另外,主函数中的收敛判断先检查:
T - lastTime > N
随后才通过 elseif 检查:
T - lastTime > 2N
由于满足后者时一定已经满足前者,因此当前“communication trouble”这一分支实际上不会被执行。
默认全连接网络下这通常不会影响示例主流程,但在研究稀疏或异常通信网络时,需要留意这一判断逻辑。
最后,当前工程没有实现:
障碍物规避;车辆动力学路径规划;代理碰撞避免;通信时延和丢包模型;在线新增任务;实际电池剩余量约束。
因此不要把它理解成完整无人集群任务规划系统。
它更准确的定位是:
一套实现 CBBA 核心竞价、Bundle 构造、时间窗检查、异构兼容和一致性冲突解决机制的 MATLAB 多智能体任务分配程序。
17. 哪些参数最值得修改?
参数 | 当前值 | 为什么值得改 | 调整后的主要影响 |
|---|---|---|---|
代理/任务数量 | N=5,M=10 | 决定问题规模 | 改变竞价规模、Bundle 深度和通信量 |
CM 兼容矩阵 | QUAD→TRACK,CAR→RESCUE | 定义异构能力边界 | 决定不同代理可以参与哪些任务竞争 |
nom_vel / fuel | 两类代理均为 2 m/s / 1 | 当前两类代理运动参数相同 | 直接改变到达时间和距离惩罚 |
value / lambda | 100 / 0.1 | 控制任务价值和延迟敏感度 | 改变代理优先争夺哪些任务 |
时间参数 | TRACK 5 s,RESCUE 15 s;start 随机产生 | 决定任务的时序冲突程度 | 改变路径插入可行性和任务执行顺序 |
Graph | 5 个代理全连接 | 控制信息传播方式 | 可用于研究不同通信拓扑下的一致性过程 |
其中,如果只准备做一次有明显效果的二次实验,最值得优先修改的是:
兼容矩阵 + 代理速度/燃料系数 + 任务价值/时间窗 + 通信图
这些参数分别控制:
谁能做 → 做得多快/多贵 → 哪些任务更值得抢 → 信息怎样传播
18. 怎么运行?
当前工程的主入口是:
MainTestScript.m
运行时把 MATLAB 当前工作目录切换到解压后的 CBBA 根目录,然后执行:
MainTestScript
脚本开头已经调用:
addpath(genpath(cd))
因此会把 Base、Scoring 等子目录加入 MATLAB 搜索路径。
整个运行过程为:
初始化随机场景 → 创建 5 个代理 → 创建 10 个任务 → 构造全连接通信图 → 调用CBBA_Main→ 迭代通信和竞价 → 输出CBBA_Assignments与Total_Score→ 绘制任务路径和时间表
当前压缩包没有附带固定运行截图或预先保存的结果文件,因此不在这里虚构某一组具体代理—任务路径和最终总分。
源码本身没有读取外部数据文件,也没有调用工程中额外的模型文件;从当前函数调用看,核心实现主要使用 MATLAB 基础矩阵运算、指数/距离计算和绘图功能。
19. 一句话看懂这个项目
这是一套CBBA 多智能体多任务分配程序:当前示例构造 5 个代理和 10 个随机空间任务,其中 2 个 QUAD 只竞争 5 个 TRACK 任务、3 个 CAR 只竞争 5 个 RESCUE 任务;代理先根据旅行时间和前后任务关系检查不同路径插入位置是否满足时间约束,再用“指数折扣任务奖励-燃料距离惩罚”形成投标值并逐步建立 Bundle,同时通过全连接通信网络交换任务赢家、赢家投标值和时间戳,用 CBBA 的冲突处理规则让不同代理逐渐形成一致认知;代理一旦在 Bundle 前部任务上被别人出价超过,就释放该任务及之后的任务重新竞价,最终输出每个代理的任务路径、执行时间、任务得分和时空调度图。当前工程非常适合学习和二次开发异构多智能体任务分配、时间窗调度、分布式拍卖与一致性机制,但源码也明确采用了简化燃料评分,因此Total_Score应作为当前示例评分结果理解,而不能直接当作严格的全局最优代价。