RailSimEnv / README.md
bpHigh's picture
Licences: MIT for the code; credit Rijden de Treinen and OpenStreetMap
3f8636d verified
|
Raw History Blame Contribute Delete
5.55 kB
---
title: RailSimEnv v1
emoji: 🚆
colorFrom: blue
colorTo: gray
sdk: gradio
sdk_version: 6.29.1
python_version: "3.12"
app_file: app.py
license: mit
pinned: false
short_description: Dispatch trains on a real single-track Dutch line
tags: [openenv, simulation, rl-environment, railway, scheduling, operations-research]
---
# RailSimEnv v1 · OpenEnv
Dispatch Arriva's trains on the single-track Groningen – Leeuwarden line (Netherlands) after a real 2025 disruption:
a broken-down train, a collision at a level crossing, a signal or points failure, on its real day and time, with every
train where it really was. Hold, short-turn and cancel trains so opposing trains cross safely. One graded submit per
episode against the proven CP-SAT optimum. Serves the rail-v1 packs: 45 eval tasks chosen for hardness and 159 train
tasks on other days.
| page | what |
|---|---|
| `/web` | OpenEnv's web UI. **Explore**: the eval explorer, the 3D replay and the train graph. **Playground**: reset, step and the MCP tools by hand |
| `/explorer/` | the leaderboard and every model's plan on every eval task; pick one to watch it |
| `/twin/` | the 3D replay: each model's plan on every eval task, on the line's real track from OpenStreetMap (plus a few drawn sidings outside each terminal, where a set waits when both platform tracks are taken), with signals, level crossings, closures, the train graph and the cost as it accrues |
| `/viewer/` | the train graph (time across, distance down) of any plan, with the plan's holds, short-turns and rule breaks |
| `/reset`, `/step`, `/state`, `/ws`, `/mcp` | OpenEnv |
| `/rail_ops/...` | OpenEnv Task API (`splits`, `num_tasks`, `task`, `task_range`) |
| `/api/...` | read-only JSON for the pages |
## Tools
| tool | returns |
|---|---|
| `get_situation()` | the line, the closures, every train's timetable and position, the train sets and their chains of runs |
| `check_plan(plan)` | simulates a draft: every train's times, delay and lost stops, every rule broken, the cost; 10 per episode |
| `submit_plan(plan)` | ends the episode with one grade |
`plan` lists only the trains you change: `[{"train": "37413", "to": "HDG"}, {"train": "37324", "from": "HDG"},
{"train": "37419", "hold": {"GK": "08:36"}}, {"train": "37330", "cancel": true}]`. Trains left alone run as early as
the physics allow, never ahead of the timetable. `reset(task_id=...)`, `reset(split=..., index=...)` or
`reset(seed=...)`.
**Rules:** no two opposing trains in a single-track section at once; 3-minute headways for trains going the same way;
never more trains at a station than tracks (the terminals have room for every set); no train in a closed section;
runs start where their train set is, and every set ends the window where its next run leaves; no stop more than 2
hours late.
**Cost**, in stop-minutes: every minute of delay at every stop served, 30 per stop not served, and lateness handed on
to the next window.
## Reward
Graded once, on `submit_plan`, by `RailPlanRubric` (every other action scores 0.0). A plan that breaks any rule scores
0.2 × the share of runs it leaves clean. A legal plan scores 0.2 + 0.8 × exp(−gap / 0.5), with
gap = (cost − optimum) / (optimum − floor + 100), so the proven CP-SAT optimum scores 1.0. No submission scores 0. The
children `runs_clean` and `cost_quality` are reported in the final observation's `metadata.rubric`.
## Results on the 45 eval tasks
One rollout per model, the same rules for every model (12 turns, 64k output tokens per turn):
| model | mean reward (95% CI) | legal plans | at the optimum |
|---|---|---:|---:|
| Qwen3.8-2.4T-A95B | **0.556** (0.452-0.665) | 39 | 12 |
| DeepSeek-V4.1-Flash | **0.534** (0.427-0.643) | 33 | 12 |
| Claude Sonnet 5.5 | **0.467** (0.384-0.553) | 45 | 4 |
| GLM-5.3-Flash | **0.360** (0.251-0.477) | 24 | 6 |
| the real dispatchers (real tier, scored as if legal) | 0.502 | | |
| greedy dispatcher | 0.180 | | |
| change nothing | 0.105 | 0 | 0 |
Every plan is in the eval explorer, and replays in 3D exactly as it was graded.
## Connect an agent
```python
from openenv.core.env_server.mcp_types import CallToolAction
from openenv.core.mcp_client import MCPToolClient
env = MCPToolClient("https://bphigh-railsimenv.hf.space").sync()
obs = env.reset(split="eval", index=0)
rules = obs.observation.metadata["instructions"] # the system prompt
situation = env.step(CallToolAction(tool_name="get_situation", arguments={}))
plan = [{"train": "37413", "to": "HDG"}, {"train": "37324", "from": "HDG"}]
print(env.step(CallToolAction(tool_name="check_plan", arguments={"plan": plan})).observation)
```
## Licences
The code is MIT. The data keep their sources' licences:
| data | source | licence |
|---|---|---|
| trains, timetables, delays, cancellations, platforms; disruptions | [Rijden de Treinen](https://www.rijdendetreinen.nl/en/open-data) open data | CC BY 4.0 ("Rijden de Treinen") |
| track layout; the 3D replay's map (track, platforms, buildings, land cover, water, roads) | © OpenStreetMap contributors | ODbL 1.0 |
The OpenStreetMap-derived data in this Space (the 3D map, `openenv/rail_openenv/web/twin/twin.json.gz`, and the line
layout inside every task in `tasks/`) is licensed under the [ODbL 1.0](https://opendatacommons.org/licenses/odbl/1-0/),
not MIT, and stays so when reused or adapted. The 3D replay and the train graph credit OpenStreetMap and Rijden de
Treinen on screen.
The rolling-stock circulation, the disruption effects by cause and the cost weights are modelling choices.