solver: replace cell DFS with skyline search
Represent partial placements as column heights and branch on the narrowest local valley. This removes the board-area cell state and makes first-solution search substantially smaller for feasible orders. Expose direct-search and candidate-order benchmark controls so the skyline core can be measured independently of odd-order construction. Document the completeness argument and the 10/11 test-tier decision. Tests: Release, Debug, ASan, and UBSan CTest (10 passed each) Refs: #4
This commit was merged in pull request #23.
This commit is contained in:
+59
-4
@@ -15,16 +15,25 @@ python3 benchmarks/run.py --binary build-benchmark/partridge_benchmark \
|
||||
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`, and
|
||||
`--timeout`. 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:
|
||||
Override the policy with `--orders`, `--warmup`, `--repetitions`, `--timeout`,
|
||||
and `--candidate-order`. 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:
|
||||
|
||||
```sh
|
||||
python3 benchmarks/run.py --binary build-benchmark/partridge_benchmark \
|
||||
--orders 8 9 --warmup 1 --repetitions 5 --timeout 60 > benchmark.json
|
||||
```
|
||||
|
||||
Use `--direct-search` when benchmarking the skyline core rather than the public
|
||||
even-predecessor construction used for odd orders from 9 onwards:
|
||||
|
||||
```sh
|
||||
python3 benchmarks/run.py --binary build-benchmark/partridge_benchmark \
|
||||
--orders 6 7 8 9 --warmup 1 --repetitions 5 --timeout 60 \
|
||||
--candidate-order ascending --direct-search > direct.json
|
||||
```
|
||||
|
||||
The JSON contains every run and median, range, and median absolute deviation
|
||||
for solve, construction, independent validation, and rendering. Direct-search
|
||||
cases report zero construction time: their setup and allocation remain part of
|
||||
@@ -59,6 +68,52 @@ Do not use wall-clock thresholds as correctness checks. Keep the generated
|
||||
JSON outside version control unless it is being deliberately added as a named
|
||||
comparison baseline.
|
||||
|
||||
## Smallest-valley skyline
|
||||
|
||||
The solver stores one filled height per board column instead of one value per
|
||||
cell. Equal adjacent heights form conceptual vertical bars. A valley is a
|
||||
maximal bar lower than both neighbours, with board edges treated as bars of
|
||||
full board height. Each node scans for the smallest-width valley, breaking
|
||||
ties by lower height and then leftmost position, and tries every available
|
||||
square which fits at that valley's far-left edge.
|
||||
|
||||
This branching remains complete: the bottom-left cell of the selected valley
|
||||
must be covered, a square covering it cannot begin to the left across the
|
||||
taller neighbour, and it cannot extend beyond the equal-height run without
|
||||
overlap or leaving an unreachable hole. Trying every fitting available size
|
||||
therefore includes the placement used by every possible completion.
|
||||
|
||||
For board width `W` and order `n`, the skyline scan is `O(W)`. A node tries at
|
||||
most `n` candidates and each placement or exact undo changes at most `n`
|
||||
heights, giving `O(W + n^2)` local work. The skyline, multiplicities,
|
||||
placements, and recursion stack use `O(W + n^2)` state, compared with the
|
||||
former `O(W^2)` cell grid.
|
||||
|
||||
One-run exploratory measurements used the issue #4 dirty worktree at base
|
||||
commit `0a7ce1e`, Apple Clang 21.0.0, `-O3 -DNDEBUG`, macOS arm64, one worker,
|
||||
no warm-up, and a 15-second timeout. Every completed result passed the
|
||||
independent benchmark validator:
|
||||
|
||||
| Order | Result | Ascending time | Ascending nodes | Descending time | Descending nodes |
|
||||
| --- | --- | ---: | ---: | ---: | ---: |
|
||||
| 6 | infeasible | 0.040 s | 659,598 | 0.039 s | 659,598 |
|
||||
| 7 | infeasible | 3.103 s | 43,604,507 | 3.071 s | 43,604,507 |
|
||||
| 8 | solution | 0.585 s | 7,735,369 | 0.941 s | 12,186,125 |
|
||||
| 9 direct | solution | 3.831 s | 45,840,266 | timeout | unavailable |
|
||||
|
||||
The infeasible orders exhaust the same tree in either direction. Ascending
|
||||
was selected as the default because it reaches the first order-8 solution with
|
||||
36% fewer nodes and also completed direct order 9 within the timeout;
|
||||
descending direct order 9 did not.
|
||||
|
||||
A direct ascending order-10 probe exceeded 20 seconds. Public order 10 is
|
||||
also a direct search, and public order 11 first searches order 10 before using
|
||||
odd-predecessor construction. Consequently neither 10 nor 11 is in the
|
||||
routine correctness suite: doing so would test the same unresolved order-10
|
||||
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.
|
||||
|
||||
## Post-correctness baseline
|
||||
|
||||
This framework starts from commit `ce39d0a` after the rendering assertion fix
|
||||
|
||||
Reference in New Issue
Block a user