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:
+22
-7
@@ -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
|
||||
|
||||
+19
-2
@@ -79,11 +79,20 @@ def environment(binary):
|
||||
}
|
||||
|
||||
|
||||
def run_once(binary, order, mode, search_policy, search_route, timeout):
|
||||
def run_once(
|
||||
binary, order, mode, search_policy, search_route, symmetry, timeout
|
||||
):
|
||||
started = time.monotonic()
|
||||
try:
|
||||
process = subprocess.run(
|
||||
[str(binary), str(order), mode, search_policy, search_route],
|
||||
[
|
||||
str(binary),
|
||||
str(order),
|
||||
mode,
|
||||
search_policy,
|
||||
search_route,
|
||||
symmetry,
|
||||
],
|
||||
check=False,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
@@ -223,6 +232,11 @@ def main():
|
||||
default="ascending",
|
||||
)
|
||||
parser.add_argument("--direct-search", action="store_true")
|
||||
parser.add_argument(
|
||||
"--no-symmetry",
|
||||
action="store_true",
|
||||
help="disable unique-unit-square D4 symmetry breaking",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--measure-overhead",
|
||||
action="store_true",
|
||||
@@ -254,6 +268,7 @@ def main():
|
||||
mode,
|
||||
args.search_policy,
|
||||
"direct" if args.direct_search else "public",
|
||||
"none" if args.no_symmetry else "d4",
|
||||
args.timeout,
|
||||
)
|
||||
for trial in range(args.repetitions):
|
||||
@@ -265,6 +280,7 @@ def main():
|
||||
mode,
|
||||
args.search_policy,
|
||||
"direct" if args.direct_search else "public",
|
||||
"none" if args.no_symmetry else "d4",
|
||||
args.timeout,
|
||||
)
|
||||
)
|
||||
@@ -293,6 +309,7 @@ def main():
|
||||
"per_run_timeout_seconds": args.timeout,
|
||||
"candidate_order": args.search_policy,
|
||||
"search_route": "direct" if args.direct_search else "public",
|
||||
"symmetry_breaking": "none" if args.no_symmetry else "d4",
|
||||
"stdout": "captured; rendered grid suppressed by probe",
|
||||
},
|
||||
"cases": cases,
|
||||
|
||||
Reference in New Issue
Block a user