CBS(Conflict-Based Search)算法是一种用于多智能体路径规划(MAPF)的算法。关于MAPF问题的定义、冲突类型、目标函数等基础知识可以参考这篇文章:https://zhuanlan.zhihu.com/p/613639712
因为MAPF的状态空间随agent数量呈指数增长,所以也不能保证多项式时间复杂度能验证一个结果,是一个NP-hard问题,无法得到最优解。agent数量多时通常使用次优求解器
CBS算法
全称Conflict-Based-Search,CBS分为两层,下层对单个agent进行调度,上层解决多agent之间的冲突问题
原论文:Sharon G, Stern R, Felner A, et al. Conflict-based search for optimal multi-agent pathfinding[J]. Artificial intelligence, 2015, 219: 40-66.
论文地址:https://www.sciencedirect.com/science/article/pii/S0004370214001386
关键定义
-
path : 一条机器人的路径,也就是我们平时用得最多的单机规划结果
-
solution : 多机系统中所有机器人的 path 的集合( n 条 path ),也就是 mapf 算法的全局规划结果
-
conflict : 冲突.上述的 solution 中, n 条 path 之间可能会有冲突(没冲突当然皆大欢喜了).具体的描述形式为 () ,表示在时刻 t , 和 同时占据了顶点 v . 拿栅格地图来说,就是在时刻 t , 和 同时占据了矩阵的一个格子 matrix(i,j) .
-
constraints : 约束.一个约束 (,v,t) ,表示在时刻 t , 不能占据顶点 v .
-
约束树 CT
CT的每个节点,包含:
-
constraint,也就是某agent在某个时刻不能经过某个节点
-
solution,也就是能够满足这个约束的一种策略
-
cost,当前策略的成本,比如所有agent要走的路径之和
步骤
1.先计算每个agent的最短路径
2.检测冲突,如果没有冲突则算法结束,返回结果
3.如果有冲突,选择一个冲突创建两个子问题:如下图,发现1号和2号在t=2时存在冲突,那么两个子问题就是让2号在t=2时不经过冲突点和让1号在t=2时不经过冲突点
4.最终形成一个搜索树,每个节点表示一个路径规划状态,算法在树中搜索得到无冲突的路径
局限性
计算复杂性可能会在处理大规模问题时变得很高。
尽管CBS被证明是完备和最优的,但在实践中,它可能会花费较长时间来找到最优解。
对于一些问题,CBS生成的路径可能会相对较长,因为它主要关注冲突解决而不是路径优化。这可能导致生成的解决方案在路径长度上不够优秀。
CBS的性能可能受到特定问题实例的影响。在某些问题上表现良好,而在另一些问题上可能效果不佳。
CBS代码实现(python)
源代码:GitHub - msaudulhassan/mapf: Python implementation of Space-time A*, Prioritized Planning, & Conflict-based Search for Multi-agent Path Finding (MAPF) 这个链接里面有Space-time A*, Prioritized Planning, & Conflict-based Search for Multi-agent Path Finding的python实现
下面是关于源代码的解读:
CBS上层
对应的py文件:mapf-main/cbs.py
CBS上层有一个open_list,这个列表用来记录冲突节点树
open_list的节点:
root = {'cost': 0, # 当前的花销,即所有agent都走完花费的最少时间步 'constraints': [], # 限制条件 'paths': [], # 给每个agent单独规划出的路径 'collisions': []} # 冲突,每条冲突记录了冲突发生的agent、位置、时间戳
冲突检测:
使用A*规划好若干agent的路径之后,对这些路径进行冲突检测
冲突有两类,顶点冲突vertex_collision和边冲突edge_collision。顶点冲突就是某一时刻t,两个agent同时到达同一位置边冲突就是两个agnet不会发生位置互换,例如agent_1在时刻位于,agent_2在时刻位于,而agent_1在下一时刻位于,agent_2在下一时刻位于
当冲突发生时,记录发生冲突的两个agent、冲突发生的位置、时间戳(不是指现实世界的时间戳,而是记录agent行动步数的时间戳)
算法:
-
创建第一个节点,即所有agent在没有约束下使用下层算法得到各自的路径,将这个节点加入open_list
-
从open_list中弹出一个节点,检测它的冲突,如果无冲突则返回当前每个agent的路径;有冲突则根据冲突得到对不同agent在某一时刻的约束,这些约束每一个都产生一个子节点
-
重复2步骤,直到open_list为空
CBS下层的A*实现
在CBS下层对单个agent使用A*来规划路径,与单独A*相比,需要添加上对路径的限制constraints
对应py文件:mapf-main/single_agent_planner.py
计算heuristics
def compute_heuristics(my_map, goal)
使用Dijkstra构建从goal出发的最短路径树:
使用open_list存储待扩展节点,也就是已发现但未被探索的节点。这是一个list,其中元素为cost、goal以及{cost,goal}组成的一个tuple。例如:(0, (1, 5), {'cost': 0, 'loc': (1, 5)})
使用closed_list存储已探索节点,确保这些节点不会被重复处理。这是一个dict,元素的key为某个非障碍物的位置,value为由这个点的cost和value组成的dict
算法:
-
根据goal以及它的cost构建open_list节点,作为open_list的初始元素
-
选出open_list堆最上面的节点,在它的4个方向上进行探索,如果探索节点是障碍物或超出地图边界,则进入下一循环;如果没问题则在之前cost基础上+1,并检查这个节点是否被探索过(是否在closed_list),如果被探索过则更新closed_list,如果没有被探索过则在closed_list中直接创建一个新的node
-
重复2步骤,直到open_list为空
-
将closed_list节点中的节点存入h_values作为返回内容
这个函数被调用的位置:初始化CBS等算法时,获得每个agent在不同位置时距离终点的启发式。本以为这个地方使用的是曼哈顿距离,但是仔细一看还真不是
寻路
def a_star(my_map, start_loc, goal_loc, h_values, agent, constraints)
参数agent是该智能体的序号
关键结构:
-
node节点:包含loc,g_val,h_val,parent和timestamp,记录agent在当前位置下的实际成本、启发式、父节点和时间戳
-
frontier:相当于open_list,栈中存储的节点元素为总成本、启发式、位置和对应节点构成的tuple
-
explored_set:相当于closed_list,元素key为节点位置+节点时间戳,value为节点本身
算法:
-
构建约束表,初始化frontier和explored_set,相当于open_list和closed_list
-
创建agent初始位置的节点,并加入到frontier和explored_set
-
从frontier弹出节点,检查这个节点的5个动作(除了上下左右还有STAY)之后的节点是否是终点,如果是则退出循环返回path;否则判断是否出界/是否是障碍物/是否和约束相违背,如果没有,则更新frontier和explored_set,更新策略和上面的计算heuristics相同
-
循环3步骤,直到frontier为空或到达终点