solver: select skyline candidate policy

Make candidate ordering an explicit deterministic policy and benchmark ascending, descending, and exact-width-first choices. Keep ascending as the default because it produces the best measured time to first solution despite best-fit's slightly smaller tree.

Retain the benchmark v1 interface and document why randomized and duplicate-work portfolio policies are deferred.

Tests: Release and Debug CTest (10 passed each)

Refs: #12
This commit was merged in pull request #24.
This commit is contained in:
Codex instance
2026-07-30 18:07:29 +01:00
parent d751d1b13e
commit 74bde266d0
6 changed files with 141 additions and 49 deletions
+54 -1
View File
@@ -16,7 +16,8 @@ The default policy is one unrecorded warm-up followed by five repetitions per
case, with a 600-second timeout for each process. Solver stdout is captured;
the probe renders into an in-memory stream so grids do not perturb terminal I/O.
Override the policy with `--orders`, `--warmup`, `--repetitions`, `--timeout`,
and `--candidate-order`. Ascending candidate sizes are the production default.
and `--candidate-order`. The choices are `ascending`, `descending`, and
`best-fit`; ascending candidate sizes are the production default.
Order 9 uses the constructive odd-order path, searching order 8 and then tiling
the enlarged border, so it is suitable for normal local benchmarking:
@@ -114,6 +115,58 @@ search bottleneck, while the existing route-boundary test still verifies that
11 selects construction. Revisit both sizes when order 10 completes within a
practical test budget.
## Candidate policy selection
Candidate ordering is a deterministic search policy and does not alter the
smallest-valley selection or set of placements tried. `ascending` tries
smaller available squares first and `descending` tries larger ones first.
`best-fit` first tries a square exactly as wide as the selected valley, because
that placement closes the valley without leaving a shelf remainder, then tries
the other sizes in ascending order. If no exact-width square fits, best-fit
and ascending are identical at that node.
The policy comparison used the issue #12 working tree based on commit
`d751d1b`, Apple Clang 21.0.0, `-O3 -DNDEBUG`, macOS arm64, and one worker.
Order 8 used two warm-ups and seven sequential measured repetitions; direct
order 9 used one warm-up and three measured repetitions. All completed
results passed independent validation and node counts were stable:
| Order | Policy | Counted median (range) | Counter-free median | Nodes |
| --- | --- | --- | --- | ---: |
| 8 | ascending | 0.808 s (0.7900.852 s) | 0.794 s | 7,735,369 |
| 8 | descending | 1.310 s (1.2791.449 s) | 1.268 s | 12,186,125 |
| 8 | best-fit | 0.817 s (0.8130.857 s) | 0.823 s | 7,679,349 |
| 9 direct | ascending | 5.522 s (5.4995.830 s) | not measured | 45,840,266 |
| 9 direct | best-fit | 5.651 s (5.5335.820 s) | not measured | 45,746,016 |
The earlier direct-order-9 descending probe exceeded its 15-second limit.
Ascending is retained as the stable single-threaded default because it had the
lowest measured median time to the first solution at both measured solvable
sizes. Best-fit's slightly smaller trees did not compensate for its policy
checks, while descending was substantially worse. Exhaustive infeasible
order-5 tests visit the same number of nodes under all three policies, which
checks that ordering does not affect completeness.
One policy therefore applies to the currently measured sizes 8 and 9. This
does not establish that ascending is optimal for order 10: a bounded best-fit
order-9 comparison changed the search tree by only 0.2%, so there was no
evidence that repeating the known long order-10/11 search would be useful.
Keep 10 and 11 as opt-in benchmark cases. Public order 11 is particularly
important to interpret correctly: it constructs from an order-10 search, so it
does not independently measure an odd-order candidate policy.
A worker portfolio was considered but not added. Running identical policies
duplicates the same deterministic traversal. Pairing ascending with best-fit
adds little diversity on the measured trees, and pairing ascending with
descending dedicates a worker to the consistently slower policy. Splitting a
shared frontier could avoid duplicated prefixes, but that is the parallel
frontier work tracked separately in issue #11. Seeded randomized ordering was
also rejected for now: the deterministic alternatives already select a clear
default, and there is no measurement showing that seed distributions would
improve time to first solution. The benchmark schema retains its nullable
seed field so a future evidence-backed randomized policy can report
reproducible runs without changing the format.
## Post-correctness baseline
This framework starts from commit `ce39d0a` after the rendering assertion fix