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

A newer version of the Gradio SDK is available: 6.31.0

Upgrade
metadata
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

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 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, 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.