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:
Codex instance
2026-07-30 17:55:21 +01:00
parent 0a7ce1e49e
commit d751d1b13e
7 changed files with 296 additions and 198 deletions
+36 -9
View File
@@ -26,26 +26,29 @@ namespace {
double construction_seconds;
};
auto solve(std::uint64_t order, bool instrument, SearchCounters &counters)
auto solve(std::uint64_t order, bool instrument,
CandidateOrder candidate_order, bool direct_search,
SearchCounters &counters)
-> TimedSolution {
auto const predecessor_order =
uses_odd_construction(order) ? order - 1 : order;
!direct_search && uses_odd_construction(order) ? order - 1 : order;
auto const search_begin = Clock::now();
auto predecessor =
instrument
? search_solution_instrumented(predecessor_order, counters)
: search_solution(predecessor_order);
? search_solution_instrumented(predecessor_order, counters,
candidate_order)
: search_solution(predecessor_order, candidate_order);
auto const search_end = Clock::now();
auto const construction_begin = Clock::now();
auto result = uses_odd_construction(order)
auto result = !direct_search && uses_odd_construction(order)
? construct_odd_solution(order, std::move(predecessor))
: std::move(predecessor);
auto const construction_end = Clock::now();
return {
std::move(result),
seconds(search_begin, search_end),
uses_odd_construction(order)
!direct_search && uses_odd_construction(order)
? seconds(construction_begin, construction_end)
: 0.0,
};
@@ -90,8 +93,9 @@ namespace {
}
int main(int argc, char **argv) {
if (argc != 3) {
std::cerr << "usage: partridge_benchmark ORDER counters|plain\n";
if (argc < 3 || argc > 5) {
std::cerr << "usage: partridge_benchmark ORDER counters|plain "
"[ascending|descending] [public|direct]\n";
return 2;
}
auto const order = static_cast<std::uint64_t>(std::strtoull(argv[1], nullptr, 10));
@@ -100,9 +104,26 @@ int main(int argc, char **argv) {
std::cerr << "counter mode must be counters or plain\n";
return 2;
}
auto const candidate_order =
argc < 4 || std::string_view(argv[3]) == "ascending"
? CandidateOrder::ascending
: CandidateOrder::descending;
if (argc >= 4 && std::string_view(argv[3]) != "ascending" &&
std::string_view(argv[3]) != "descending") {
std::cerr << "candidate order must be ascending or descending\n";
return 2;
}
auto const direct_search =
argc == 5 && std::string_view(argv[4]) == "direct";
if (argc == 5 && std::string_view(argv[4]) != "public" &&
std::string_view(argv[4]) != "direct") {
std::cerr << "search route must be public or direct\n";
return 2;
}
SearchCounters counters;
auto timed = solve(order, instrument, counters);
auto timed =
solve(order, instrument, candidate_order, direct_search, counters);
auto const validation_begin = Clock::now();
auto const validation_ok = valid(order, timed.result);
@@ -120,6 +141,12 @@ int main(int argc, char **argv) {
<< "{\"schema_version\":1"
<< ",\"order\":" << order
<< ",\"instrumented\":" << boolean(instrument)
<< ",\"candidate_order\":\""
<< (candidate_order == CandidateOrder::ascending ? "ascending"
: "descending")
<< "\""
<< ",\"search_route\":\""
<< (direct_search ? "direct" : "public") << "\""
<< ",\"solved\":" << boolean(!timed.result.squares().empty())
<< ",\"valid\":" << boolean(validation_ok)
<< ",\"timing_seconds\":{\"solve\":" << timed.search_seconds