A minimal, grid-based Multi-Agent Path Finding (MAPF) environment built on MovingAI benchmark maps. This package provides tools for loading maps, sampling instances, validating paths, and visualizing multi-agent trajectories.
Here are example visualizations of multi-agent path finding on different maps with 50 agents using random movement:
50 agents moving randomly on warehouse-20-40-10-2-2 map
50 agents moving randomly on room-64-64-8 map
50 agents moving randomly on random-64-64-10 map
50 agents moving randomly on maze-128-128-2 map
50 agents moving randomly on ht_mansion_n map
- Grid-based MAPF Environment: Discrete-time, grid-based environment for multi-agent path finding
- MovingAI Map Support: Load and work with standard MovingAI MAPF benchmark maps
- Instance Sampling: Sample MAPF instances (start/goal pairs) from scenario files
- Path Validation: Validate paths for collisions, illegal moves, and goal completion
- Visualization: Animate and playback paths as GIFs with collision highlighting
- 4-connected and 8-connected Motion: Support for both 4-directional and 8-directional (including diagonals) movement
- Comprehensive Testing: Full test suite for all core functionality
conda env create -f environment.yml
conda activate mapfpip install -r requirements.txt- Python 3.10+
- NumPy >= 1.21.0
- Matplotlib >= 3.5.0
- ImageIO >= 2.9.0
Visualize a map file to see its structure:
python -m scripts.preview_map data/mapf-map/empty-32-32.mapSample a MAPF instance with k agents from a scenario file:
python -m scripts.sample_instance --map empty-32-32 --k 10This will:
- Load the map
empty-32-32.map - Sample 10 agents from the scenario file
- Display start and goal positions for each agent
Validate a solution path file:
python -m scripts.validate_paths paths.npy --map empty-32-32 --k 10The validator checks for:
- Out-of-bounds positions
- Positions on obstacles
- Illegal moves (non-adjacent transitions)
- Vertex collisions (multiple agents on same cell)
- Edge collisions (agents swapping positions)
- Goal completion (if goals are provided)
Create an animated GIF from a path solution:
python -m scripts.playback_paths --map empty-32-32 --paths paths.npy --k 10 --out demo.gif --fps 6Generate a random rollout visualization:
python -m scripts.random_rollout --map empty-32-32 --k 10 --steps 20 --motion 4Download MovingAI MAPF benchmark maps and scenarios from: https://www.movingai.com/benchmarks/mapf/index.html
Place files in the following directories:
- Map files (
.map):data/mapf-map/ - Scenario files (
.scen):data/scens/
The scenario files should follow the naming convention: <map-name>-random-*.scen
from core.env import MAPFEnv
from core.instance import MAPFInstance
import numpy as np
# Create a MAPF instance (see instance sampling below)
instance = MAPFInstance(
grid=grid, # 2D numpy array (0=free, 1=obstacle)
starts=starts, # (N, 2) array of (row, col) start positions
goals=goals, # (N, 2) array of (row, col) goal positions
num_agents=N
)
# Create environment (4-connected or 8-connected motion)
env = MAPFEnv(instance, motion="4") # or motion="8"
# Reset to initial state
state = env.reset()
# Step with joint actions (one action per agent)
# Actions: 0=WAIT, 1=RIGHT, 2=DOWN, 3=UP, 4=LEFT
# For 8-connected: 5=DOWN-RIGHT, 6=DOWN-LEFT, 7=UP-RIGHT, 8=UP-LEFT
joint_action = np.array([1, 2, 0, 3, 4]) # Actions for 5 agents
state, info = env.step(joint_action)
# Access state information
print(f"Time step: {state.t}")
print(f"Agent positions: {state.pos}") # (N, 2) array
print(f"Goals: {state.goals}") # (N, 2) array
# Check for collisions
print(f"Vertex collisions: {info['vertex_collisions']}")
print(f"Edge collisions: {info['edge_collisions']}")
print(f"Invalid moves: {info['invalid_moves']}")from mapf_env.io.movingai_map import load_map
from mapf_env.io.movingai_scene import load_scen
from core.instance import instance_from_scen
# Load map and scenario
grid = load_map("data/mapf-map/empty-32-32.map")
scen_starts, scen_goals = load_scen("data/scens/empty-32-32-random-1.scen")
# Create instance with k agents, starting from offset
instance = instance_from_scen(
grid=grid,
scen_starts=scen_starts,
scen_goals=scen_goals,
k=10, # Number of agents
offset=0 # Starting row in scenario file
)from core.validate import validate_paths
import numpy as np
# Load paths (shape: T x N x 2)
paths = np.load("paths.npy")
# Validate paths
result = validate_paths(
grid=grid,
paths=paths,
starts=starts, # Optional: verify start positions
goals=goals, # Optional: verify goal completion
connectivity="4" # "4" or "8" for motion type
)
if result["ok"]:
print("Paths are valid!")
else:
print(f"Validation failed: {result['first_error']}")
print(f"Vertex collisions: {result['num_vertex_collisions']}")
print(f"Edge collisions: {result['num_edge_collisions']}")
print(f"Illegal moves: {result['num_illegal_moves']}")All paths must follow this canonical format:
-
Shape:
(T, N, 2)where:T= number of time stepsN= number of agents2= (row, col) coordinates
-
Conventions:
paths[0, i]= start position of agentipaths[T-1, i]= final position of agenti(should be goal)- Coordinates are 0-based integers:
(row, col) - Origin
(0, 0)is top-left - If an agent reaches its goal early, it should wait (repeat goal position)
Example:
paths = np.array([
[[0, 0], [5, 5]], # t=0: agent 0 at (0,0), agent 1 at (5,5)
[[0, 1], [5, 4]], # t=1: agent 0 at (0,1), agent 1 at (5,4)
[[0, 2], [5, 3]], # t=2: agent 0 at (0,2), agent 1 at (5,3)
# ... more timesteps
])0: WAIT →(0, 0)1: RIGHT →(0, +1)2: DOWN →(+1, 0)3: UP →(-1, 0)4: LEFT →(0, -1)
Includes all 4-connected actions plus:
5: DOWN-RIGHT →(+1, +1)6: DOWN-LEFT →(+1, -1)7: UP-RIGHT →(-1, +1)8: UP-LEFT →(-1, -1)
- Grid cells are indexed as (row, col)
- Both
rowandcolare 0-based integers - Origin
(0, 0)= top-left cell - Increasing
row→ moves down - Increasing
col→ moves right - Access pattern:
grid[row, col]orgrid[row][col]
mapf/
├── core/ # Core MAPF functionality
│ ├── env.py # MAPF environment implementation
│ ├── instance.py # MAPF instance representation
│ └── validate.py # Path validation logic
├── mapf_env/ # MAPF environment package
│ ├── io/ # Input/output utilities
│ │ ├── movingai_map.py # Map file loading
│ │ └── movingai_scene.py # Scenario file loading
│ └── viz/ # Visualization
│ ├── render.py # State rendering
│ └── animate.py # Path animation
├── scripts/ # Command-line tools
│ ├── sample_instance.py # Sample MAPF instances
│ ├── validate_paths.py # Validate path solutions
│ ├── playback_paths.py # Create animated GIFs
│ ├── preview_map.py # Preview map files
│ └── random_rollout.py # Random rollout visualization
├── tests/ # Test files
│ ├── test_sample_instance.py
│ └── test_validate_paths.py
├── data/ # Data directory
│ ├── mapf-map/ # Map files (.map)
│ └── scens/ # Scenario files (.scen)
├── environment.yml # Conda environment
├── requirements.txt # pip requirements
├── SPEC.md # Detailed specification
└── TESTING.md # Testing guide
./test_all.sh./test_quick.sh# Test instance sampling
./test_sample_instance.sh
python tests/test_sample_instance.py
# Test path validation
./test_validate_paths.sh
python tests/test_validate_paths.py
# Test error cases
./test_error_cases.sh
# Test with different maps
./test_different_maps.shFor more details, see TESTING.md.
Sample a MAPF instance from a scenario file.
python -m scripts.sample_instance --map <map_name> --k <num_agents> [--offset <offset>]Options:
--map: Map basename (without.mapextension)--k: Number of agents to sample--offset: Starting row index in scenario file (default: 0)--maps_dir: Directory containing map files (default:data/mapf-map)--scen_dir: Directory containing scenario files (default:data/scens)
Validate a path solution file.
python -m scripts.validate_paths <paths.npy> --map <map_name> --k <num_agents> [options]Options:
--map: Map basename--k: Number of agents--connectivity: Motion type, "4" or "8" (default: "4")--check_goals: Verify all agents reach their goals
Create an animated GIF from paths.
python -m scripts.playback_paths --map <map_name> --paths <paths.npy> [options]Options:
--map: Map basename--paths: Path to.npyfile with shape(T, N, 2)--out: Output GIF filename (default:paths.gif)--k: Number of agents (for loading starts/goals from scenario)--fps: Frames per second (default: 5)--stride: Temporal downsampling factor (default: 1)--no_collision_highlight: Disable collision highlighting
Preview a map file.
python -m scripts.preview_map <map_file>Generate a random rollout visualization.
python -m scripts.random_rollout --map <map_name> --k <num_agents> --steps <num_steps> [--motion 4|8]For detailed specifications on coordinate systems, path formats, action spaces, and conventions, see SPEC.md.
# 1. Sample an instance
python -m scripts.sample_instance --map empty-32-32 --k 10
# 2. Run your planner (produces paths.npy)
# ... your planning code ...
# 3. Validate the solution
python -m scripts.validate_paths paths.npy --map empty-32-32 --k 10 --check_goals
# 4. Visualize as GIF
python -m scripts.playback_paths --map empty-32-32 --paths paths.npy --k 10 --out results/demo.gif --fps 6Generate visualization GIFs for multiple maps with many agents using random movement:
# Activate conda environment
conda activate mapf
# Run demos on multiple maps
bash run_mapf_demos.shOr run directly:
PYTHONPATH=. python scripts/run_mapf_demos.py \
--maps warehouse-20-40-10-2-2 room-64-64-8 random-64-64-10 maze-128-128-2 ht_mansion_n \
--k 50 \
--fps 5 \
--results_dir resultsSee LICENSE file for details.
If you use this MAPF environment in your research, please cite the MovingAI benchmark:
MovingAI Lab. "MAPF Benchmarks." https://www.movingai.com/benchmarks/mapf/index.html
When contributing to this project:
- Follow the conventions specified in
SPEC.md - Ensure all tests pass:
./test_all.sh - Maintain the path format:
(T, N, 2)with(row, col)coordinates - Use the standard action space (0-4 for 4-connected, 0-8 for 8-connected)