Unordered Close Following Agents

Unordered Close Following Agents#

Agent-Close Following is an edge cose in mutual exclusive resource allocation, i.e. the same resource can be hold by at most one agent at the same time. In Flatland, grid cell is a resource, and only one agent can be in a grid cell. The only exception are level-free crossings, where there is horizontal and a vertical resource at the same grid cell.

The naive solution to this problem is to iterate among all agents, verify that their targeted resource is not occupied, if so allow the movement, else stop the agent. However, when agents follow each other in chain of cells, different agent orderings lead to different decisions under this algorithm.

MotionCheck ensures:

  • no swaps (i.e. collisions)

  • no two agents must be allowed to move to the same target cell (resource)

  • if all agents in the chain run at the same speeed, all can run (behaviour not depending on agent indices)

%load_ext autoreload
%autoreload 2
from IPython import display 
display.display(display.HTML("<style>.container { width:95% !important; }</style>"))
import networkx as nx
import PIL
from IPython import display
import time
from matplotlib import pyplot as plt
import numpy as np
import sys
sys.executable
'/opt/hostedtoolcache/Python/3.10.17/x64/bin/python'
from flatland.envs import malfunction_generators as malgen
from flatland.envs.agent_utils import EnvAgent
from flatland.envs import rail_generators as rail_gen
from flatland.envs import agent_chains as ac
from flatland.envs.rail_env import RailEnv, RailEnvActions
from flatland.envs.persistence import RailEnvPersister
from flatland.utils.rendertools import RenderTool
from flatland.utils import env_edit_utils as eeu
from flatland.utils import jupyter_utils as ju
from tests.test_agent_chains import create_test_agents2
agents = {
    # stopped chain
    0: ((1, 0), (1, 1)),
    1: ((1, 1), (1, 2)),
    2: ((1, 2), (1, 3)),
    3: ((1, 3), (1, 3)),

    # running chain
    4: ((2, 0), (2, 1)),
    5: ((2, 1), (2, 2)),
    6: ((2, 2), (2, 3)),
    7: ((2, 3), (2, 4)),

    # stopped short chain
    8: ((3, 0), (3, 1)),
    9: ((3, 1), (3, 1)),

    # swap
    10: ((4, 1), (4, 2)),
    11: ((4, 2), (4, 1)),

    # mid-chain stop
    12: ((5, 1), (5, 2)),
    13: ((5, 2), (5, 3)),
    14: ((5, 3), (5, 2)),
    15: ((6, 1), (6, 2)),
    16: ((6, 2), (6, 3)),
    17: ((6, 3), (6, 4)),
    18: ((6, 4), (6, 4)),
    19: ((6, 5), (6, 6)),
    20: ((6, 6), (6, 7)),

    # mid-chain swap
    21: ((7, 1), (7, 2)),
    22: ((7, 2), (7, 3)),
    23: ((7, 3), (7, 4)),
    24: ((7, 4), (7, 3)),
    25: ((7, 5), (7, 4)),
    26: ((7, 6), (7, 5)),

    # land on same
    27: ((8, 1), (8, 2)),
    28: ((8, 3), (8, 2)),

    # chains onto same
    29: ((9, 1), (9, 2)),
    30: ((9, 2), (9, 3)),
    31: ((9, 3), (9, 4)),
    32: ((9, 5), (9, 4)),
    33: ((9, 6), (9, 5)),
    34: ((9, 7), (9, 6)),

    # 3-way same
    35: ((10, 1), (10, 2)),
    36: ((10, 3), (10, 2)),
    37: ((11, 2), (10, 2)),

    # tee
    38: ((12, 1), (12, 2)),
    39: ((12, 2), (12, 3)),
    40: ((12, 3), (12, 4)),
    41: ((13, 3), (12, 3)),

    # tree
    42: ((14, 1), (14, 2)),
    43: ((14, 2), (14, 3)),
    44: ((14, 3), (14, 4)),
    45: ((15, 3), (14, 3)),
    46: ((15, 2), (15, 3)),
    47: ((16, 2), (15, 3)),
    48: ((18, 3), (17, 3)),
    49: ((18, 2), (18, 3)),
    50: ((19, 2), (18, 3)),
    51: ((17, 1), (17, 2)),
    52: ((17, 2), (17, 3)),
    53: ((17, 3), (17, 4)),
}
expected = {0: False, 1: False, 2: False, 3: False, 4: True, 5: True, 6: True, 7: True, 8: False, 9: False, 10: False, 11: False, 12: False, 13: False,
            14: False, 15: False, 16: False, 17: False, 18: False, 19: True, 20: True, 21: False, 22: False, 23: False, 24: False, 25: False, 26: False,
            27: True, 28: False, 29: True, 30: True, 31: True, 32: False, 33: False, 34: False, 35: True, 36: False, 37: False, 38: True, 39: True,
            40: True, 41: False, 42: True, 43: True, 44: True, 45: False, 46: False, 47: False, 48: True, 49: True, 50: False, 51: False, 52: False,
            53: True}
G = nx.DiGraph()
edge_labels = {}
edge_colors_map = {}
node_color_map = {}
for i, (u,v) in agents.items():
    G.add_edge(u,v)
    edge_labels[(u,v)]=i
    edge_colors_map[(u,v)]= "red" if not expected[i] else "blue"
    node_color_map[u] = "red" if not expected[i] else "blue"
pos = {n:n for n in G.nodes}
node_color = [node_color_map[n] if n in node_color_map else "blue" for n in G.nodes]
edge_colors = [edge_colors_map[e] for e in G.edges]
fig, ax = plt.subplots(figsize=(30, 10))
nx.draw(G, pos=pos, with_labels=True,ax=ax, edge_color=edge_colors,connectionstyle="arc3,rad=0.1",arrowsize=20, node_color=node_color)
nx.draw_networkx_edge_labels(G, pos=pos, edge_labels=edge_labels, font_size=15)
plt.show()
../../_images/f373bbf63dfd1b3a92756d74e60c15647e08aadebd4684dbac081f2881a2d662.png

Arrows represent the desired movement of agents.

Edges got from the agent’s current position (cell as row-column pair) to the desired target position, and the edge label is the agent ID.

Self-loops represent agents not wanting to move - additionally the corresponding node is also marked red.

Red edges represent agents which are stopped by motion check or which do not want to move (if self-loop) - the corresponding start cell is also marked red.