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:
@@ -162,6 +162,29 @@ namespace {
|
||||
best_fit,
|
||||
};
|
||||
|
||||
/** Whether to remove equivalent board orientations from the search. */
|
||||
enum class SymmetryBreaking {
|
||||
disabled,
|
||||
d4_unit_square,
|
||||
};
|
||||
|
||||
/** Return whether a cell is the canonical representative of its D4 orbit.
|
||||
*
|
||||
* Reflect a cell into the left half of the board, rotate so its distance
|
||||
* from the left edge is no greater than its distance from the top edge, and
|
||||
* finally reflect it into the top half. The resulting fundamental region is
|
||||
* the closed triangle x <= y <= floor((length - 1) / 2). Closed boundaries
|
||||
* retain representatives whose orbit is smaller than eight.
|
||||
*/
|
||||
[[nodiscard]] auto canonical_unit_position(size_t const x,
|
||||
size_t const y,
|
||||
size_t const length) noexcept
|
||||
-> bool {
|
||||
assert(x < length);
|
||||
assert(y < length);
|
||||
return x <= y && y <= (length - 1) / 2;
|
||||
}
|
||||
|
||||
/** A maximal level skyline segment which is lower than its neighbours. */
|
||||
struct Valley {
|
||||
size_t x;
|
||||
@@ -200,7 +223,7 @@ namespace {
|
||||
return best;
|
||||
}
|
||||
|
||||
template<bool Instrument>
|
||||
template<bool Instrument, bool BreakD4Symmetry>
|
||||
auto search_skyline(size_t const n, size_t const length,
|
||||
SearchPolicy const policy,
|
||||
std::vector<size_t> &skyline, Avail &available,
|
||||
@@ -226,6 +249,20 @@ namespace {
|
||||
return false;
|
||||
}
|
||||
|
||||
if constexpr (BreakD4Symmetry) {
|
||||
if (side == 1) {
|
||||
if constexpr (Instrument) {
|
||||
++counters->prune_checks;
|
||||
}
|
||||
if (!canonical_unit_position(valley.x, valley.height, length)) {
|
||||
if constexpr (Instrument) {
|
||||
++counters->prune_hits;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if constexpr (Instrument) {
|
||||
++counters->attempted_placements;
|
||||
}
|
||||
@@ -234,8 +271,8 @@ namespace {
|
||||
side, valley.height + side);
|
||||
squares.emplace_back(valley.x + valley.height * length, side);
|
||||
|
||||
if (search_skyline<Instrument>(n, length, policy, skyline,
|
||||
available, squares, counters)) {
|
||||
if (search_skyline<Instrument, BreakD4Symmetry>(
|
||||
n, length, policy, skyline, available, squares, counters)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -281,7 +318,7 @@ namespace {
|
||||
* candidates and updating up to n columns for each costs O(n^2), for
|
||||
* O(board width + n^2) local work per node and the same total state.
|
||||
*/
|
||||
template<bool Instrument>
|
||||
template<bool Instrument, bool BreakD4Symmetry>
|
||||
auto search_solution_impl(size_t const n, SearchPolicy const policy,
|
||||
SearchCounters *const counters) noexcept
|
||||
-> Results {
|
||||
@@ -293,7 +330,7 @@ namespace {
|
||||
}
|
||||
std::vector<Square> squares;
|
||||
squares.reserve(length);
|
||||
static_cast<void>(search_skyline<Instrument>(
|
||||
static_cast<void>(search_skyline<Instrument, BreakD4Symmetry>(
|
||||
n, length, policy, skyline, available, squares, counters));
|
||||
|
||||
return {length, std::move(squares)};
|
||||
@@ -301,18 +338,28 @@ namespace {
|
||||
|
||||
auto search_solution(
|
||||
size_t const n,
|
||||
SearchPolicy const policy = SearchPolicy::ascending) noexcept
|
||||
SearchPolicy const policy = SearchPolicy::ascending,
|
||||
SymmetryBreaking const symmetry =
|
||||
SymmetryBreaking::d4_unit_square) noexcept
|
||||
-> Results {
|
||||
return search_solution_impl<false>(n, policy, nullptr);
|
||||
if (symmetry == SymmetryBreaking::d4_unit_square) {
|
||||
return search_solution_impl<false, true>(n, policy, nullptr);
|
||||
}
|
||||
return search_solution_impl<false, false>(n, policy, nullptr);
|
||||
}
|
||||
|
||||
auto search_solution_instrumented(size_t const n,
|
||||
SearchCounters &counters,
|
||||
SearchPolicy const policy =
|
||||
SearchPolicy::ascending) noexcept
|
||||
SearchPolicy::ascending,
|
||||
SymmetryBreaking const symmetry =
|
||||
SymmetryBreaking::d4_unit_square) noexcept
|
||||
-> Results {
|
||||
counters = {};
|
||||
return search_solution_impl<true>(n, policy, &counters);
|
||||
if (symmetry == SymmetryBreaking::d4_unit_square) {
|
||||
return search_solution_impl<true, true>(n, policy, &counters);
|
||||
}
|
||||
return search_solution_impl<true, false>(n, policy, &counters);
|
||||
}
|
||||
|
||||
/** Construct an odd-order solution from its even-order predecessor. */
|
||||
@@ -347,23 +394,29 @@ namespace {
|
||||
|
||||
auto find_solution(
|
||||
size_t const n,
|
||||
SearchPolicy const policy = SearchPolicy::ascending) noexcept -> Results {
|
||||
SearchPolicy const policy = SearchPolicy::ascending,
|
||||
SymmetryBreaking const symmetry =
|
||||
SymmetryBreaking::d4_unit_square) noexcept -> Results {
|
||||
if (uses_odd_construction(n)) {
|
||||
return construct_odd_solution(n, search_solution(n - 1, policy));
|
||||
return construct_odd_solution(
|
||||
n, search_solution(n - 1, policy, symmetry));
|
||||
}
|
||||
return search_solution(n, policy);
|
||||
return search_solution(n, policy, symmetry);
|
||||
}
|
||||
|
||||
auto find_solution_instrumented(size_t const n,
|
||||
SearchCounters &counters,
|
||||
SearchPolicy const policy =
|
||||
SearchPolicy::ascending) noexcept
|
||||
SearchPolicy::ascending,
|
||||
SymmetryBreaking const symmetry =
|
||||
SymmetryBreaking::d4_unit_square) noexcept
|
||||
-> Results {
|
||||
if (uses_odd_construction(n)) {
|
||||
return construct_odd_solution(
|
||||
n, search_solution_instrumented(n - 1, counters, policy));
|
||||
n, search_solution_instrumented(
|
||||
n - 1, counters, policy, symmetry));
|
||||
}
|
||||
return search_solution_instrumented(n, counters, policy);
|
||||
return search_solution_instrumented(n, counters, policy, symmetry);
|
||||
}
|
||||
} // anon namespace
|
||||
|
||||
|
||||
Reference in New Issue
Block a user