solver: break board dihedral symmetry

Constrain the unique unit square to a closed D4 fundamental region once its placement is known. This preserves one representative of every board-orientation orbit without assigning identities to repeated squares.

Keep a symmetry-disabled benchmark path, document the proof and measurements, and cover generic, diagonal, midline, corner, and centre orbits.

Tests: Release, Debug, ASan, and UBSan CTest (11 passed each)

Refs: #3
This commit was merged in pull request #25.
This commit is contained in:
Codex instance
2026-07-30 18:18:45 +01:00
parent 74bde266d0
commit f37e08768d
7 changed files with 284 additions and 28 deletions
+22 -7
View File
@@ -28,6 +28,7 @@ namespace {
auto solve(std::uint64_t order, bool instrument,
SearchPolicy policy, bool direct_search,
SymmetryBreaking symmetry,
SearchCounters &counters)
-> TimedSolution {
auto const predecessor_order =
@@ -36,8 +37,8 @@ namespace {
auto predecessor =
instrument
? search_solution_instrumented(predecessor_order, counters,
policy)
: search_solution(predecessor_order, policy);
policy, symmetry)
: search_solution(predecessor_order, policy, symmetry);
auto const search_end = Clock::now();
auto const construction_begin = Clock::now();
@@ -93,9 +94,10 @@ namespace {
}
int main(int argc, char **argv) {
if (argc < 3 || argc > 5) {
if (argc < 3 || argc > 6) {
std::cerr << "usage: partridge_benchmark ORDER counters|plain "
"[ascending|descending|best-fit] [public|direct]\n";
"[ascending|descending|best-fit] [public|direct] "
"[d4|none]\n";
return 2;
}
auto const order = static_cast<std::uint64_t>(std::strtoull(argv[1], nullptr, 10));
@@ -117,15 +119,25 @@ int main(int argc, char **argv) {
return 2;
}
auto const direct_search =
argc == 5 && std::string_view(argv[4]) == "direct";
if (argc == 5 && std::string_view(argv[4]) != "public" &&
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;
}
auto const symmetry =
argc < 6 || std::string_view(argv[5]) == "d4"
? SymmetryBreaking::d4_unit_square
: SymmetryBreaking::disabled;
if (argc == 6 && std::string_view(argv[5]) != "d4" &&
std::string_view(argv[5]) != "none") {
std::cerr << "symmetry mode must be d4 or none\n";
return 2;
}
SearchCounters counters;
auto timed = solve(order, instrument, policy, direct_search, counters);
auto timed =
solve(order, instrument, policy, direct_search, symmetry, counters);
auto const validation_begin = Clock::now();
auto const validation_ok = valid(order, timed.result);
@@ -150,6 +162,9 @@ int main(int argc, char **argv) {
<< "\""
<< ",\"search_route\":\""
<< (direct_search ? "direct" : "public") << "\""
<< ",\"symmetry_breaking\":\""
<< (symmetry == SymmetryBreaking::d4_unit_square ? "d4" : "none")
<< "\""
<< ",\"solved\":" << boolean(!timed.result.squares().empty())
<< ",\"valid\":" << boolean(validation_ok)
<< ",\"timing_seconds\":{\"solve\":" << timed.search_seconds