File size: 10,717 Bytes
6953bd1
 
4827ad3
 
6953bd1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
# Multi-agent RL navigation — trustworthy autonomous systems (simulation only)

**Repo: `dummy033485/multi-agent-rl-navigation`**
**Status: model repo live. No Hugging Face Space has been created.**

A shared decentralized PPO policy for 2D multi-agent navigation, compared
honestly against a textbook classical controller on the *same* seeded
layouts. Built as a portfolio project for an application to Prof. Jiachen
Li's Trustworthy Autonomous Systems Lab (Georgia Tech): robotics,
trustworthy AI/ML, reinforcement learning, control, optimization and
multi-agent systems.

## What this is — and is not

- **Is:** a self-contained 2D physics simulator (written for this project,
  no external environment, no hardware, no proprietary data) + a learned
  shared policy + a classical baseline + a fixed, seeded evaluation.
- **Is not:** a robot result. No hardware was used. The RL policy has **no
  formal safety guarantee** — every safety number below is empirical,
  measured on the simulator's fixed test layouts. The wall-trap layout is
  deliberately constructed to defeat a pure potential-field controller.

## Simulator (`sim.py`)

- Arena 12x12 with walls. Agents: double-integrator kinematics,
  `dt=0.1`, max speed 1.2, max acceleration 3.0, agent radius 0.25,
  episode cap 200 steps.
- Each layout: 3 circle obstacles (radius 0.5–0.8) **and** 1 wall/barrier
  segment. Two layout families, generated with fixed seeds:
  - *general*: a short (3 m) barrier segment at a random mid-arena angle.
  - *trap*: a vertical segment at x=6, y in [2,10], with gaps only at the
    ends; starts on the left, goals near-directly opposite on the right,
    so a direct potential field is attracted right and repelled left and
    stalls in the middle.
- Decentralized 20-dim observation per agent: own velocity, relative goal
  vector + distance, nearest circle obstacle (direction, surface distance,
  radius), nearest wall segment (direction, distance, both endpoint
  vectors), nearest other agent (direction, distance), nearest arena-wall
  distance. (Endpoint vectors were added after a failed training run:
  with only the nearest segment point, the policy could not observe where
  the wall ends — see Limitations and `BUILD_REPORT.md`.)
- Collision = distance to an obstacle/wall segment/arena wall below the
  agent radius, or agent–agent distance below 0.5. A colliding arrival is
  never counted as a success. Safety margin for violation counting: 0.15.

## Policies

- **Classical** (`policies.py`, exact gains documented in code): PD
  controller toward the goal (KD=2.0) + radial repulsive potential field
  (ETA=6.0, influence distance D0=2.0) from the nearest obstacle, segment
  and neighbour. No learning, no tangential escape trick — the honest
  textbook baseline. It stalls (timeout, 0 collisions) on every trap layout.
- **RL**: shared decentralized PPO, MLP 20→128→128 (the observation grew
  from 15 to 20 dims when segment-endpoint features were added),
  Gaussian actions, tanh-bounded to the acceleration limit. One set of
  weights drives every agent from its own observation.
  Training recipe (all in `train.py`, PPO from scratch with torch only):
  the policy was warm-started by behaviour cloning the classical controller
  on general layouts only (400 episodes, 4 agents, final action MSE 0.079),
  then PPO fine-tuned in four phases — 1-agent mixed (trap ramp), 1-agent
  trap-only with an exploration reset, 1-agent mixed consolidation, and
  4-agent mixed — 300 iterations × 128-step rollouts × 16 envs per phase
  (614,400 environment steps per phase; 2,457,600 in the kept chain plus
  the BC dataset). Earlier from-scratch PPO runs failed outright (success
  5–15%) and are documented, with their fixes, in `BUILD_REPORT.md` —
  they are not hidden. The final phase's curve is `training_curve.csv`
  (earlier phases have their own `training_curve_*.csv` files).

## Evaluation protocol (exact)

Fixed layouts, identical for every policy and condition:
- Single-agent: seeds 1000–1019 (10 general + 10 trap, alternating).
- 4-agent: seeds 2000–2011 (6 general + 6 trap, alternating).
- RL evaluated with the deterministic (mean) action; classical is
  deterministic. Noise conditions reuse the same noise RNG seed per
  layout/condition for both policies.
- Conditions: clean; observation noise σ=0.05; observation noise σ=0.10;
  dynamics disturbance (added acceleration noise σ=1.0); obstacle shift
  +0.5 (circles and segments shifted +0.5 in x).
- Metrics are per agent-episode (20 single-agent and 48 4-agent episodes
  per policy per condition; 728 rows in `eval.csv`). Steps-to-goal and
  clearance are reported on successes only; safety-violation rate is the
  fraction of active steps with clearance < 0.15.

## Results (copied from `results.json` — no other numbers are claimed)

### Clean

| Policy | Agents | Success | Collision | Obstacle coll. | Agent–agent coll. | Timeout | Steps (succ.) | Path len. | Min clearance (succ.) | Safety-viol. rate | General | Trap |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Classical | 1 | 0.450 | 0.000 | 0.000 | 0.000 | 0.550 | 73.22 | 5.35 | 0.588 | 0.000 | 0.90 | 0.00 |
| RL | 1 | 0.700 | 0.200 | 0.200 | 0.000 | 0.100 | 86.00 | 9.59 | 0.419 | 0.033 | 0.60 | 0.80 |
| Classical | 4 | 0.396 | 0.000 | 0.000 | 0.000 | 0.604 | 160.42 | 5.87 | 0.690 | 0.000 | 0.79 | 0.00 |
| RL | 4 | 0.792 | 0.146 | 0.146 | 0.000 | 0.062 | 141.18 | 10.63 | 0.505 | 0.019 | 0.75 | 0.83 |

Reading, honestly: the RL policy wins on success everywhere, and is the
only policy that solves trap layouts or coordinates 4 agents (zero
agent–agent collisions clean). It loses on safety in the single-agent
case: 20% collisions and lower clearance than the classical controller's
0% collisions. The classical controller never collides clean — it fails
by timing out at the wall instead. RL paths are longer (detours).

### Robustness (success / collision)

| Condition | RL 1-agent | Classical 1-agent | RL 4-agent | Classical 4-agent |
|---|---|---|---|---|
| Clean | 0.700 / 0.200 | 0.450 / 0.000 | 0.792 / 0.146 | 0.396 / 0.000 |
| Obs. noise σ=0.05 | 0.750 / 0.150 | 0.450 / 0.100 | 0.646 / 0.292 | 0.333 / 0.125 |
| Obs. noise σ=0.10 | 0.600 / 0.300 | 0.200 / 0.250 | 0.542 / 0.396 | 0.104 / 0.396 |
| Dynamics disturbance σ=1.0 | 0.650 / 0.200 | 0.350 / 0.000 | 0.771 / 0.167 | 0.375 / 0.000 |
| Obstacle shift +0.5 | 0.550 / 0.300 | 0.350 / 0.000 | 0.708 / 0.250 | 0.375 / 0.000 |

The RL policy degrades gracefully on success under every perturbation and
stays well above classical, but its collision rate rises under noise
(up to 0.396 at σ=0.10, 4-agent) — the trustworthy claim is *measured
robustness of success*, not a safety proof.

### Ablation — neighbour observation, 4-agent clean

| Policy | Success | Collision | Agent–agent collision |
|---|---|---|---|
| RL with neighbour observation | 0.792 | 0.146 | 0.000 |
| RL with neighbour observation zeroed | 0.146 | 0.708 | 0.333 |

Removing the neighbour observation collapses coordination: success falls
from 0.792 to 0.146 and agent–agent collisions jump from 0 to 0.333.
The shared policy genuinely uses the neighbour channel; it is not
four independent single-agent policies.

## Files in this repo

- `ppo_policy.pt` — final shared PPO weights (ActorCritic, obs dim 20).
- `sim.py`, `policies.py`, `train.py`, `bc.py`, `evaluate.py`, `plots.py`
- `results.json`, `eval.csv` (728 rows), `training_curve.csv` (final
  4-agent phase) + `training_curve_single.csv`,
  `training_curve_single2.csv`, `training_curve_ft1.csv`,
  `training_curve_ft2.csv`, `training_curve_ft3.csv` (earlier phases)
- `plots/trap_trajectories.png`, `plots/four_agent.png`,
  `plots/clean_comparison.png`, `plots/training_curve.png`
- `space/` — self-contained Gradio demo (copies of `sim.py`,
  `policies.py`, `ppo_policy.pt` + `app.py`, `requirements.txt`).
  See `SMOKE_TEST.md`.

## Limitations

1. 2D simulation only. No hardware, no real robot, no vision (the
   observation is ground-truth state features + optional noise, not pixels).
2. The wall trap is constructed. It demonstrates a known potential-field
   failure mode; it does not prove the policy generalises to arbitrary
   unseen maps (the +0.5 obstacle-shift condition is the only unseen-layout
   test, and success there is 0.55–0.71, not 1.0).
3. No formal safety guarantee. Collisions still occur (5–40% depending on
   condition). A safety shield / control-barrier filter would be the
   natural next step and is explicitly **not** claimed here.
4. Small evaluation: 20 single-agent and 12 four-agent layouts. Differences
   of a few percent are within layout noise; the large effects (trap
   0.00 → 0.80–0.83, 4-agent 0.396 → 0.792, ablation 0.792 → 0.146) are
   the ones to rely on.
5. Training required a behaviour-cloning warm-start and a phased
   curriculum (including a trap-only phase that temporarily caused
   catastrophic forgetting of general layouts, fixed by a mixed
   consolidation phase). From-scratch PPO did not learn this task in
   ~1.9M environment steps of attempts. See `BUILD_REPORT.md`.

## Reproduce

```bash
# shared venv (torch, numpy, matplotlib, gradio); proxy vars must be unset for gradio imports
export VENV=~/workspace/rul-project/venv/bin/python
env -u http_proxy -u https_proxy -u HTTP_PROXY -u HTTPS_PROXY -u all_proxy -u ALL_PROXY -u no_proxy -u NO_PROXY $VENV bc.py --episodes 400 --n-agents 4 --epochs 30 --out bc_policy.pt
env -u ... $VENV train.py --iterations 300 --rollout 128 --n-agents 1 --init bc_policy.pt --out ppo_ft1.pt --curve training_curve_ft1.csv
# (trap-only exploration-reset weights were made from ppo_ft1.pt by setting log_std to -0.5; see BUILD_REPORT.md)
env -u ... $VENV train.py --iterations 300 --rollout 128 --n-agents 1 --init ppo_ft1_explore.pt --trap-prob 1.0 --trap-ramp-iters 1 --ent-coef 0.02 --out ppo_ft2.pt --curve training_curve_ft2.csv
env -u ... $VENV train.py --iterations 300 --rollout 128 --n-agents 1 --init ppo_ft2.pt --trap-prob 0.5 --trap-ramp-iters 1 --out ppo_ft3.pt --curve training_curve_ft3.csv
env -u ... $VENV train.py --iterations 300 --rollout 128 --n-agents 4 --init ppo_ft3.pt --trap-prob 0.5 --trap-ramp-iters 1 --out ppo_policy.pt --curve training_curve.csv
env -u ... $VENV evaluate.py --weights ppo_policy.pt --out results.json --csv eval.csv
env -u ... $VENV plots.py --weights ppo_policy.pt --results results.json --curve training_curve.csv --outdir plots
```

All commands are run from `~/workspace/li-project/`. Fixed seeds throughout
(training seed 0, BC seed 123, evaluation layout seeds 1000–1019 /
2000–2011, noise seeds 90000+layout index).