What a number here means
A benchmark that reports only its successes is an advertisement. This one records every run it was asked to make, and the tables above show the failures next to the wins because that is the comparison a reader needs.
Outcomes
- solved
- The planner returned a plan and an independent validator accepted it. For classical planning the plan is replayed through the grounded task and the goal is re-checked; for MAPF the returned paths are re-scanned for vertex and edge conflicts. A planner's own claim of success is never sufficient — a plan the validator rejects is shown as invalid and does not count towards coverage.
- unsolved
- The planner terminated on its own and reported no solution. For an incomplete planner this is data, not a bug: it means the search strategy failed on this instance, not that the instance is unsolvable.
- timeout
- The wall-clock budget was exhausted. Two numbers are recorded and
they are not the same one.
timeout_sis the budget the run was given.wall_time_sis the elapsed time at which the run was actually stopped — a little past the budget, because a planner notices its own limit and unwinds, and so it is a measurement rather than a constant. Neither is an extrapolation of how long a solution would have taken; solvability is unknown. This is the single most important distinction on the page: a timeout is not a proof of unsolvability. Timing statistics in the tables are computed from solved runs only, so a timeout moves coverage and nothing else. - memory
- The address-space limit fired, or the process was killed by the OOM killer. As with a timeout, solvability is unknown.
- error
- The planner raised, crashed, or produced output the adapter could not read. The message is kept in the row. Parse failures on a domain a planner does not support land here, which is deliberate: "cannot read this domain" is a real limitation and belongs in the table.
- skipped
- Deliberately not run. The CUDA arm on a machine with no working GPU is the common case, and the row says so rather than vanishing.
- not-installed
- The backend was not importable in the environment that produced these results. The runs were still enumerated, so a gap in the table is always explained.
Coverage
An instance counts as solved for a configuration only when every seed returned a valid solution. A configuration that solves an instance on two seeds out of three is reported as partial, not as solved: a coverage number that a re-run would not reproduce is not worth printing. Runs that never happened are excluded from the denominator rather than counted as failures.
Timing
- Wall-clock,
time.perf_counter, measured inside the child process around the whole call — parsing and grounding included, because that is time a user waits. - Every run happens in a fresh subprocess with a hard kill and an
RLIMIT_AScap, so a wedged planner costs one row, not the sweep. - Runs are executed serially. Timing N planners on N cores measures the memory subsystem, not the planners.
- Reported statistics are the median over seeds with the observed min–max as the band. Three seeds do not support a standard deviation, and a mean is moved by a single scheduling hiccup.
- Timing aggregates use solved runs only. Folding a timeout's budget into a mean runtime makes a planner look faster as it starts failing.
Budgets
A coverage number is meaningless without the budget it was measured under: “solved 9 of 18” says nothing until you know whether the planner had twenty seconds or twenty minutes. Every table on this site carries its suite's budget in its caption; this is all of them in one place.
| Suite | Wall clock | Address space | Rows |
|---|---|---|---|
| Weekly regression suite | 15 s | 2,048 MiB | 68 |
| Classical planning — coverage | 20 s | 3,072 MiB | 108 |
| Classical planning — smoke | 30 s | 3,072 MiB | 234 |
| MAPF — obstacle density | 20 s | 3,072 MiB | 150 |
| MAPF — scaling with agent count | 20 s | 3,072 MiB | 400 |
The wall-clock budget is enforced twice — the planner is asked to stop itself
at it, and the harness kills the child 15 s later if it
has not. The address-space figure is a hard RLIMIT_AS set inside
the child before the planner is imported, so exceeding it produces a
memory row rather than a swapping machine and a
meaningless sweep.
Hardware these numbers came from
11th Gen Intel(R) Core(TM) i7-11850H @ 2.50GHz (16 logical CPUs), Linux 6.8.0-136-generic (x86_64), Python 3.10.12, harness 0.1.0@1c81f77e6938. Installed: jupyddl 2.3.0, matplotlib 3.10.9, numpy 2.2.6, openplan-bench 0.1.0, pymapf 0.8.0.11th Gen Intel(R) Core(TM) i7-11850H @ 2.50GHz (16 logical CPUs), Linux 6.8.0-136-generic (x86_64), Python 3.10.12, harness 0.1.0@62459c293440. Installed: jupyddl 2.3.0, matplotlib 3.10.9, numpy 2.2.6, openplan-bench 0.1.0, pymapf 0.8.0.11th Gen Intel(R) Core(TM) i7-11850H @ 2.50GHz (16 logical CPUs), Linux 6.8.0-136-generic (x86_64), Python 3.10.12, harness 0.1.0@7c0b8e81ea92. Installed: jupyddl 2.3.0, matplotlib 3.10.9, numpy 2.2.6, openplan-bench 0.1.0, pymapf 0.8.0.AMD EPYC 7763 64-Core Processor (4 logical CPUs), Linux 6.17.0-1022-azure (x86_64), Python 3.12.14, harness 0.1.0@d2a32a74ad18. Installed: jupyddl 2.3.0, matplotlib 3.11.1, numpy 2.5.2, openplan-bench 0.1.0, pymapf 0.8.0.
Every row carries the CPU model, platform, Python version, package versions, seed and harness git SHA it was produced under, so results files from different machines stay interpretable after they are concatenated.
timestamp_utc is the exception and is worth stating exactly:
in a schema 2 results file it is one stamp per suite run,
taken when the run started and copied onto every row of that file — not a
per-row clock reading, and not the moment any individual measurement was taken.
Rows written from schema 3 onwards are stamped as each one is produced.
The published tables above therefore show a run start, not a per-row time, and
say so.
What this does not measure
Read this section before quoting a number.
- It is not a planner competition. The problem set is what pddl-examples happens to contain plus generated MAPF grids — not a stratified benchmark set. Coverage here does not transfer to IPC coverage.
- It does not compare these planners to the state of the art. Nothing here is measured against Fast Downward, LAMA, EECBS or any other external system. These are pure-Python research implementations and the numbers should be read as such.
- Cross-machine times are not comparable. Suites marked runner-grade ran on shared CI hardware. Their coverage and costs are exact; their seconds are not a hardware comparison and must not be quoted as one.
- Absolute times include Python startup and parsing inside the measured region for classical planning. That is honest for a user waiting on a CLI and unfair as an algorithmic comparison; node expansions are the metric to use for the latter.
- No replication. Each suite was measured on one machine, once. Where a suite also runs one seed and one repetition, its per-cell Median, Min and Max are one sample printed three times and the suite says so above its own table. Coverage, node counts, cost and validity do not depend on this; the seconds do.
- No memory profiling. Peak RSS is recorded per run as a coarse figure, not measured carefully.
- No GPU results unless a suite says so. The CUDA arm is skipped, visibly, wherever no working device was found.