Tasks/Transport & Logistics/Rail Planning

Plan a commuter rail network that maximises the operator's balance

Grow a rail network that pays for its own next track

commuter_rail_network_designTransport & LogisticsRail Planning
instruction.mdthis is what the agent is given

You are the network-planning engine for a startup rail operator dropped into a brand-new city. The city is a 50 x 50 grid of blocks. Thousands of residents each commute from a home block to a workplace block, and every commute you manage to serve pays a fare proportional to how far that person travels. You start with a fixed cash reserve and a fixed horizon of T = 800 planning steps. Each step you may lay one length of track, build one station, or wait and let the fares roll in — but you can never let the balance go negative. Your job is to decide what to build, where, and when so that the cash on hand at the end of the horizon is as large as possible.

The tension is pure capital budgeting under a network effect. A station costs a lot (5000) and reaches every commuter endpoint within a Manhattan radius of 2, so a single well-placed station can feed many commuters; track is cheap (100) but only chains two neighbours together. Fares only start flowing once a home and a workplace are both within reach of stations that sit in the same connected rail component, so early spend is an investment that pays back over the remaining turns. Spend too little and you leave fares on the table; spend too much too late and the horizon ends before the investment is recouped.

Hard Constraints

The city. An N x N grid, N = 50. Block (0,0) is the top-left; (i,j) is i blocks down and j blocks right. Every block is one of three states:

  • Empty (the initial state of every block): connects to nothing.
  • Rail: connects to at most two of its four orthogonal non-empty neighbours. There are six rail types, numbered by which two sides they join:
type joins
1 left - right (horizontal)
2 up - down (vertical)
3 left - down
4 left - up
5 right - up
6 right - down
  • Station: connects to all four orthogonal non-empty neighbours.

Two adjacent blocks are linked iff each one's piece "faces" the other: a station faces all four sides; a rail faces only the two sides of its type. Linked blocks form connected components.

Commuters. M residents live in the city (50 <= M <= 1600). Resident c has a home at (i_{c,s}, j_{c,s}) and a workplace at (i_{c,t}, j_{c,t}), with home-to-work Manhattan distance strictly greater than 4.

The game. You begin with K funds (11000 <= K <= 20000) and an all-empty grid, and play T = 800 turns. Each turn runs a build phase then a collect phase.

Build phase — choose exactly one action:

  • Lay track: pick an empty block and set it to one of the six rail types. Costs 100.
  • Build station: pick an empty or rail block and set it to a station (a station may upgrade an existing rail block). Costs 5000.
  • Wait: do nothing. Costs 0.

An action is illegal — and makes the whole submission WA, forfeiting that case — if it would drop the balance below 0, lays track on a non-empty block, builds a station on a block that is already a station, or names an out-of-range block or rail type. The balance check happens before the collect phase.

Collect phase — every resident commutes simultaneously. Resident c pays a fare iff there exist two station blocks (i_p,j_p) and (i_q,j_q) such that:

  • both blocks are stations, and they are in the same connected component (reachable through linked station/rail blocks),
  • |i_{c,s} - i_p| + |j_{c,s} - j_p| <= 2 (a station within radius 2 of home),
  • |i_{c,t} - i_q| + |j_{c,t} - j_q| <= 2 (a station within radius 2 of work).

When resident c pays, your balance increases by |i_{c,s} - i_{c,t}| + |j_{c,s} - j_{c,t}| (their home-to-work Manhattan distance). Fares are collected every turn for as long as the connection holds, so building earlier compounds.

Beyond the game rules:

  • Submit an algorithm, not precomputed answers — the grader re-runs your code on instances you have never seen. Do not key on seeds or instance names.
  • /app/methods/main/ is what gets graded. Keep the run.sh contract below.
  • Each sealed case runs your run.sh under a wall-clock cap of 15 s. A crash, timeout, or a wrong number of action lines forfeits that case.
  • There is no network at run time, on the workbench or in the grader. Python 3, g++ and a Rust toolchain are available in both; anything else you have to write yourself.

What You Have

  • tools/in/ — 100 visible instances (seeds 0-99).
  • tools/gen — the generator. Make more instances with ./tools/gen seeds.txt --dir=OUTDIR, one unsigned-64-bit seed per line. For local testing use seeds in 0..10000 only — the sealed grading seeds live far outside that range, so staying inside it keeps your practice set from colliding with the hidden set.
  • tools/vis — the visualiser/judge, the same one the grader uses: ./tools/vis in.txt out.txt prints Score = <final balance> and writes a vis.html you can open.
  • python3 selfcheck.py [N] — free and unlimited: runs your run.sh on the first N visible cases (default 100) and prints each case's raw score.
  • methods/main/solution.py — a crude greedy starter, yours to rewrite or delete.

The instance arrives on stdin:

N M K T
i_{0,s} j_{0,s} i_{0,t} j_{0,t}
...
i_{M-1,s} j_{M-1,s} i_{M-1,t} j_{M-1,t}

N = 50, T = 800; the M lines give each resident's home then workplace.

What You Submit

Leave your best solver in methods/main/:

  • run.sh (required): run once per test case as bash run.sh < instance.txt > out.txt. It must read one instance on stdin and write the T-line action list on stdout. Any language.
  • build.sh (optional): if present, the grader runs it once before grading (e.g. to compile a C++ or Rust solver). Do your compilation here and have run.sh exec the built binary.

The action list on stdout is exactly T = 800 lines, one action per turn, in order:

  • lay track: p i j (rail type 1..6 at block (i,j)),
  • build station: 0 i j,
  • wait: -1.

Outputting a number of actions other than T is WA. Lines beginning with # are treated as comments and ignored, so you may annotate freely.

There is no submit step and no per-attempt feedback. Work and self-check for as long as your run window allows, then leave your best run.sh in place.

How It Is Judged

The grader reruns your run.sh on 200 sealed instances you never see, drawn from the same generator, and scores each with the same visualiser. The absolute score of a case is your balance after turn Thigher is better; the visualiser reports Score = 0 for any invalid, incomplete, wrong-length or timed-out output. Scoring is per case and then aggregated, so a case you forfeit cannot be carried by a case you optimise.

How the raw balances map to the final reward is deliberately not disclosed — optimise the raw balance itself. The starter as shipped is the zero of that scale: submitted unchanged it scores 0.

Metric

mean relative final balance over 200 sealed cities · higher is better

rel(c) = your final funds / strong-reference funds on case c; invalid or timed-out output scores 0

anchorvisible setheld-outreward
Bshipped greedy starter0.030.030.00
Mintermediate reference0.91510.30
Rstrong reference solver1.00.60
Ustrong reference x 1.051.051.00
normalisation
m <= B0
B < m <= M0.30 * L(m/B) / L(M/B)
M < m <= R0.30 + 0.30 * L(m/M) / L(R/M)
R < m <= U0.60 + 0.40 * L(m/R) / L(U/R)
m > U1

m = this run's held-out metric  ·  B = shipped greedy starter  ·  M = intermediate reference  ·  R = strong reference solver  ·  U = strong reference x 1.05

L = natural log; B/M/R/U = 0.03 / 0.9151 / 1.0 / 1.05. The 200 rel values are averaged first, then the mean is mapped once.

Rollouts

534 minwall clock
$30.32spend
41.1Mtokens
8versions, 8 kept
4.3M 4.4M 4.5M 4.6M 4.7M $0 $7.5 $15 $22 cumulative spend on the run mean final balance, 100 visible cases v1 v2 v6 v7 v11 v12 v13 v14
keptrevertedno scoreturning point
  1. v1C++ rewrite: greedy connected-network growth, exact simulation, multi-startGrow one connected component, ranking each station-plus-branch by income times remaining turns minus cost.4,252,9667 min · $1.42
  2. v2Plan becomes an ordered station list with derived rails; annealing over itMake the plan an object the search can rewrite instead of a one-pass construction. Wins 99 of 100 visible cases.4,657,989.627 min · $3.17
  3. v6Turn-bucketed beam search seeded from the best greedy openings; NCH default 24,674,408116 min · $10.32
  4. v7SA move mix rebalanced from acceptance stats; insert targets an unpaid commuterdev-150 only194 min · $13.57
  5. v11Solver budget 10s to 11.5s; equal-length rail routes prefer high-demand cells4,722,978298 min · $18.59
  6. v12Iterated SA restart from its own best after stagnation, tried and left offnot re-measured360 min · $21.37
  7. v13Marginal income updated incrementally, stamped BFS; 2.6x more rolloutsgprof put the time in construction, not the annealing; cheaper construction buys 156 greedy rollouts per 1.5s, up from 60.4,737,641452 min · $26.21
  8. v14Housekeeping: run.sh compiles a fallback binary and cuts budget if unbuilt4,737,641494 min · $28.20

Eight snapshots over 8.9 h and $28; v3-v5 and v8-v10 were in-place edits, never snapshotted. After v2 the score moved 1.7%.

On the hidden set

held-out metricreward
shipped greedy starter0.030.00
intermediate reference0.91510.30
strong reference solver1.00.60
strong reference x 1.051.051.00
this run0.94640.4139
372 minwall clock
$104.34spend
164.3Mtokens
47versions, 39 kept
0 1M 2M 3M 4M 5M $0 $25 $50 $75 $100 cumulative spend on the run mean final balance, visible seeds 0-59 v0 v1 v2 v3 v4 v5 v6 v7 v8 v9 v10 v11 v12 v13 v14 v15 v16 v17 v18 v19 v20 v21 v22 v23 v24 v25 v26 v27 v28 v29 v30 v31 v32 v33 v34 v35 v36 v37 v38 v39 v40 v41 v42 v43 v44 v45 v46
keptrevertedno scoreturning point
  1. v0Shipped greedy starter, unchangedvisible 0-99 only2 min · $0.40
  2. v1C++ rewrite: best affordable initial pair by weighted coverage, one L-line95,362.657 min · $0.87
  3. v2Greedy positive-NPV expansion by marginal fare per completion turnGrow one network by repeatedly adding the station with best fare per turn, routing to exploit zero-cost rail upgrades.4,409,380.159 min · $1.15
  4. v3Initial pair chosen by income/build-duration instead of stand-alone value4,261,039.9812 min · $1.43
  5. v4Expansion ordered by remaining-horizon NPV instead of fare per turn2,969,365.3312 min · $1.56
  6. v5Initial L-line flipped from horizontal-first to vertical-first4,465,958.9313 min · $1.71
  7. v6Both initial L orientations simulated, higher predicted balance emitted4,496,196.0715 min · $1.99
  8. v7Four-plan exact portfolio: two expansion orderings x two initial orientationsStop picking between rules: simulate every candidate plan exactly and emit whichever ends with the most cash.4,502,305.116 min · $2.15
  9. v8Portfolio widened to 7 project priorities x 2 initial routes4,567,594.3218 min · $2.53
  10. v9Second seed: fare per build-turn optimal pair, full 14-plan portfolio each4,629,70221 min · $3.00
  11. v10Six generic initial seeds, each through the exact expansion portfolio4,640,748.126 min · $3.87
  12. v11Synergy seeding: up to 3 zero-gain stations a counterpart can repay4,663,518.8531 min · $4.70
  13. v12Exact-counterpart synergy branch scoring joint commuters, routes and timing4,664,338.0742 min · $6.95
  14. v13Three shortest-path BFS tie geometries per start for rail-upgrade value4,677,138.7349 min · $8.39
  15. v14Marginal-fare and endpoint-density weighted shortest paths of equal length4,697,342.2858 min · $10.35
  16. v15One endpoint-density-maximizing monotone shortest initial path per start4,698,693.866 min · $12.23
  17. v16Three expansion priorities blending marginal fare with 25/50/100% frontier4,826,258.53371 min · $103.69
  18. v17Exact one-sided coverage for all pairs; three frontier-aware starts added4,827,511.7386 min · $18.17
  19. v18Frontier weights 0.125x/2x/4x added; seed 59 took 15.06s, over the capseed 59 timed out91 min · $19.60
  20. v19Runtime rewrite: marginal fare and frontier aggregated once per commuterSame plans, 2.5x less work: the 15 s cap, not the search space, was blocking every wider variant.score-identical to v1797 min · $21.70
  21. v20Frontier weights 0.125x/2x/4x reinstated once runtime headroom existed4,858,023.88102 min · $23.30
  22. v21Proactive 0.5x-frontier policy may fund a station before greedy stalls4,858,420.7107 min · $24.78
  23. v220.5x-frontier priorities per physical build action and per capital cost4,859,718.17115 min · $27.12
  24. v23Eight deterministic +/-3/10/25/40% perturbations of the 0.5x-frontier order4,859,799.88121 min · $30.29
  25. v24Initial beam: top 12 value pairs, up to 4 new starts x 3 frontier weights4,883,396.3127 min · $32.40
  26. v25Beam widened to top 24 pairs and up to 8 unrepresented starts4,892,738.58135 min · $34.91
  27. v26Accessibility-discounted frontier priority, denominator 20+actions4,893,583.25143 min · $38.35
  28. v27Sharper accessibility decay, denominator 10+actions4,907,392.15151 min · $40.84
  29. v28Very local accessibility decay, denominator 5+actions4,925,116.18158 min · $43.68
  30. v29Strongest local-accessibility policy, both orientations, 8 beam starts4,943,215.82167 min · $47.01
  31. v30Eight +/-3/10/25/40% perturbations of the local-accessibility ordering4,954,281.93178 min · $50.86
  32. v31Post-scheduler moves rails into forced waits and compacts later actions4,954,393.58188 min · $55.66
  33. v32Two plans routed by positive-cost endpoint-density Dijkstra, modest detours4,955,204198 min · $59.56
  34. v33Local-accessibility policy also run from three frontier-aware startsdelta 0 on 0-59208 min · $63.24
  35. v34Rail-junction plans that upgrade an existing rail into a station mid-route4,955,204220 min · $67.12
  36. v35Top 32 raw portfolio candidates rescheduled before winner selection4,955,204226 min · $68.17
  37. v36Four portfolios of up to eight independent two-station corridorssparse cases only231 min · $68.96
  38. v37Endpoint-density route reward caps 150/450/650 added to the existing 3004,955,713.3244 min · $70.82
  39. v38Route reward grid filled with caps 75/225/375/800, unused 550 pruned4,957,508.82257 min · $73.07
  40. v39Low-detour region resolved with route caps 25/50/100, duplicate 125 pruned4,959,602.47271 min · $75.70
  41. v40Route-cap midpoints 175/200/250/350/400/725 added4,959,602.47276 min · $76.94
  42. v41Cap-75/25 Dijkstra routes on the first two unrepresented beam pairs4,963,465.63290 min · $81.06
  43. v42Cap-75/25 routes extended from two to four unrepresented beam pairs4,963,670.85304 min · $84.68
  44. v43Cap-75/25 routes extended to all eight unrepresented beam pairs4,965,695.08321 min · $89.48
  45. v44Cap-50 routes added, pruned to the top two beam starts after tracing4,966,038.17340 min · $95.31
  46. v45Caps 75/25 on fast and cheap metric starts, pruned to cap75-fast/cap25-cheap4,967,610.25360 min · $100.56
  47. v46Packaging cleanup: visualizer vis.html removed from the graded directoryscores unchanged370 min · $103.41

47 snapshots over 6.2 h and $104, half the 12 h budget. From v7 on it is one exact-simulation portfolio, widened 39 times for +10%.

On the hidden set

held-out metricreward
shipped greedy starter0.030.00
intermediate reference0.91510.30
strong reference solver1.00.60
strong reference x 1.051.051.00
this run0.85700.2942
76 minwall clock
-spend
-tokens
8versions, 7 kept
3.26M 3.28M 3.30M 3.32M 3.34M 0 30 60 90 120 agent step (this harness reports no tokens or timestamps) mean final balance, holdout cases 80-99 v0 v1 v2 v3 v4 v5 v6 v7
keptrevertedno scoreturning point
  1. v0Shipped greedy starter, unchangedstarter, 4-case probe
  2. v1C++ multi-start greedy on 2-station OD pairs, attach-to-network expansionConnect a commuter's two endpoints as one pair, then hang later pairs off the network already built.4-case probe only
  3. v2Pair diversity, coverage-first decode, local search, time limit read from AHC_TL4-case probe, TL=4
  4. v3Payback bar on pair selection, commuter-seeded first pairs, wider coverage k0-19 probe, TL=3.5
  5. v4Job queue over beam, coverage-first and randomized greedy jobs, polish passRun many differently seeded constructions as queued jobs inside one time limit and emit the best finished plan.3,338,136.5
  6. v5Fewer coverage jobs at TL=113,323,179.5
  7. v6v4 restored without the sc<=0 pair filter; 21 coverage jobs, TL=11.5, time guards3,286,798.6
  8. v7Large-k coverage jobs queued first so one thread still reaches the dense-map winnerThe grader's core count is unknown, so order the queue to put the job that wins dense maps within reach of a single thread.3,258,631.4

Eight snapshots in 76 min of the 12 h budget; this harness reports no tokens or cost. The holdout mean fell 2% from v4 to v7.

On the hidden set

held-out metricreward
shipped greedy starter0.030.00
intermediate reference0.91510.30
strong reference solver1.00.60
strong reference x 1.051.051.00
this run0.79070.2872
51 minwall clock
$8.63spend
12.0Mtokens
15versions, 10 kept
0 0.75M 1.50M 2.25M 3.00M 3.75M $0 $2 $4 $6 $8 cumulative spend on the run mean final balance, tune seeds 0-19 v0 v1 v2 v3 v4 v5 v6 v7 v8 v9 v10 v11 v12 v13 v14
keptrevertedno scoreturning point
  1. v0Shipped greedy starter, unchanged42,227.350 min · $0.11
  2. v1Multi-start connected-tree planner replaces per-commuter L-linesReplace independent per-commuter lines with one connected tree grown by exact remaining-horizon project value.seeds 0-59, not 0-198 min · $0.88
  3. v2Expansions sequenced by gain density net/(turns+5), not absolute gainseeds 0-59, not 0-1911 min · $1.32
  4. v3Runs both sequencing rules over the same 10 starts, emits the better planStop tuning a threshold between two sequencing rules: run both, simulate exactly, keep whichever ends with more cash.3,234,233.1514 min · $1.90
  5. v4Diverse initial starts raised from 10 to 153,234,386.716 min · $2.28
  6. v5Density rank strengthened from net/(turns+5) to net/turnsDrop the constant that flattened the ranking, so a branch is judged on gain per construction turn alone.3,504,426.6521 min · $2.85
  7. v6Density rank changed to marginal_income/turns, gated on positive net3,465,762.5523 min · $3.29
  8. v7Equal-length paths chosen by summed future rail-upgrade value3,475,278.6530 min · $4.35
  9. v8Cheap part of v7 kept: initial L orientation picked by upgrade value3,510,722.230 min · $4.35
  10. v9Both initial L orientations simulated for the top 3 starts, best cash kept3,536,305.3534 min · $5.09
  11. v10Opposite-orientation search cut from the top 3 starts to the top 23,532,576.7536 min · $5.45
  12. v11M<=250 two-station fallback for commuters with neither endpoint covered3,595,79043 min · $6.86
  13. v12Pair-fallback cutoff extended from M<=250 to M<=400seed 29 spot check46 min · $7.59
  14. v13Pair-fallback cutoff extended again to M<=500two spot cases47 min · $7.79
  15. v14Pair fallback lets the second station connect through the firsteight spot cases50 min · $8.57

Fifteen snapshots in 51 min for $8.6, a fourteenth of the 12 h budget. v0 is the 42k starter; v1 onward sit between 3.23M and 3.60M.

On the hidden set

held-out metricreward
shipped greedy starter0.030.00
intermediate reference0.91510.30
strong reference solver1.00.60
strong reference x 1.051.051.00
this run0.74180.2816
67 minwall clock
$1.08spend
4.7Mtokens
4versions, 4 kept
1.2M 1.5M 1.8M 2.1M 2.4M $0 $0.2 $0.5 $0.8 $1 cumulative spend on the run mean final balance, 10-case tuning subset v1 v2 v3 v4
keptrevertedno scoreturning point
  1. v1One connected component grown by BFS greedy, exact wait-for-funds simulationKeep every station on one component and simulate the waiting turns exactly, instead of independent per-commuter lines.1,178,337.37 min · $0.31
  2. v2Starting-pair candidates filtered for affordability as they are generated1,658,56010 min · $0.42
  3. v3Expansion ranked by profit/build_turns^1.5; O(1) candidate lookup, 12x fasterDivide profit by construction time raised to a swept exponent, so a slow build is penalised more than linearly.2,384,773.163 min · $0.72
  4. v4Same payback exponent applied to the starting pair, swept to ALPHA_START=1.02,450,27067 min · $1.04

Four snapshots in 67 min for $1.08. Every decision came off the same 10 cases; the widest check it ever ran was 50.

On the hidden set

held-out metricreward
shipped greedy starter0.030.00
intermediate reference0.91510.30
strong reference solver1.00.60
strong reference x 1.051.051.00
this run0.55780.2565