solver: prune insufficient valley capacity
A selected valley cannot admit a square wider than itself until it reaches the lower neighbouring rim. Reject states whose remaining narrow-square area cannot fill that strip, including width-one and width-two gaps. Keep an unpruned benchmark mode and dedicated counters so the rule remains independently measurable. Record the soundness argument and the measured default-on improvement. Tests: Debug CTest (12 passed) Tests: ASan+UBSan CTest (12 passed) Refs: #10
This commit was merged in pull request #26.
This commit is contained in:
@@ -150,6 +150,8 @@ namespace {
|
||||
size_t backtracks = 0;
|
||||
size_t prune_checks = 0;
|
||||
size_t prune_hits = 0;
|
||||
size_t valley_capacity_checks = 0;
|
||||
size_t valley_capacity_prunes = 0;
|
||||
size_t generated_tasks = 0;
|
||||
size_t completed_tasks = 0;
|
||||
};
|
||||
@@ -168,6 +170,12 @@ namespace {
|
||||
d4_unit_square,
|
||||
};
|
||||
|
||||
/** Cheap necessary conditions applied before branching at a search node. */
|
||||
enum class Pruning {
|
||||
disabled,
|
||||
valley_capacity,
|
||||
};
|
||||
|
||||
/** 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
|
||||
@@ -223,7 +231,35 @@ namespace {
|
||||
return best;
|
||||
}
|
||||
|
||||
template<bool Instrument, bool BreakD4Symmetry>
|
||||
/** Return whether the remaining narrow pieces can fill a valley to its rim.
|
||||
*
|
||||
* Until the valley reaches the lower of its two neighbouring heights, no
|
||||
* square wider than the valley can enter it. The area of all remaining
|
||||
* squares no wider than the valley is therefore an upper bound on the area
|
||||
* available to fill this isolated strip. This includes the familiar
|
||||
* width-one and width-two gap checks without special cases.
|
||||
*/
|
||||
[[nodiscard]] auto valley_has_sufficient_capacity(
|
||||
Valley const valley, std::vector<size_t> const &skyline,
|
||||
Avail const &available) noexcept -> bool {
|
||||
auto const end = valley.x + valley.width;
|
||||
auto const left_height =
|
||||
valley.x == 0 ? skyline.size() : skyline[valley.x - 1];
|
||||
auto const right_height =
|
||||
end == skyline.size() ? skyline.size() : skyline[end];
|
||||
auto const rim_height = std::min(left_height, right_height);
|
||||
auto const required_area = valley.width * (rim_height - valley.height);
|
||||
|
||||
size_t available_area = 0;
|
||||
auto const largest = std::min(
|
||||
valley.width, static_cast<size_t>(available.size() - 1));
|
||||
for (size_t side = 1; side <= largest; ++side) {
|
||||
available_area += available[side] * side * side;
|
||||
}
|
||||
return available_area >= required_area;
|
||||
}
|
||||
|
||||
template<bool Instrument, bool BreakD4Symmetry, bool PruneValleyCapacity>
|
||||
auto search_skyline(size_t const n, size_t const length,
|
||||
SearchPolicy const policy,
|
||||
std::vector<size_t> &skyline, Avail &available,
|
||||
@@ -239,6 +275,19 @@ namespace {
|
||||
}
|
||||
|
||||
auto const valley = smallest_valley(skyline);
|
||||
if constexpr (PruneValleyCapacity) {
|
||||
if constexpr (Instrument) {
|
||||
++counters->prune_checks;
|
||||
++counters->valley_capacity_checks;
|
||||
}
|
||||
if (!valley_has_sufficient_capacity(valley, skyline, available)) {
|
||||
if constexpr (Instrument) {
|
||||
++counters->prune_hits;
|
||||
++counters->valley_capacity_prunes;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
auto const largest =
|
||||
std::min({n, valley.width, length - valley.height});
|
||||
auto try_side = [&](size_t const side) {
|
||||
@@ -271,7 +320,7 @@ namespace {
|
||||
side, valley.height + side);
|
||||
squares.emplace_back(valley.x + valley.height * length, side);
|
||||
|
||||
if (search_skyline<Instrument, BreakD4Symmetry>(
|
||||
if (search_skyline<Instrument, BreakD4Symmetry, PruneValleyCapacity>(
|
||||
n, length, policy, skyline, available, squares, counters)) {
|
||||
return true;
|
||||
}
|
||||
@@ -318,7 +367,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, bool BreakD4Symmetry>
|
||||
template<bool Instrument, bool BreakD4Symmetry, bool PruneValleyCapacity>
|
||||
auto search_solution_impl(size_t const n, SearchPolicy const policy,
|
||||
SearchCounters *const counters) noexcept
|
||||
-> Results {
|
||||
@@ -330,7 +379,8 @@ namespace {
|
||||
}
|
||||
std::vector<Square> squares;
|
||||
squares.reserve(length);
|
||||
static_cast<void>(search_skyline<Instrument, BreakD4Symmetry>(
|
||||
static_cast<void>(
|
||||
search_skyline<Instrument, BreakD4Symmetry, PruneValleyCapacity>(
|
||||
n, length, policy, skyline, available, squares, counters));
|
||||
|
||||
return {length, std::move(squares)};
|
||||
@@ -340,12 +390,19 @@ namespace {
|
||||
size_t const n,
|
||||
SearchPolicy const policy = SearchPolicy::ascending,
|
||||
SymmetryBreaking const symmetry =
|
||||
SymmetryBreaking::d4_unit_square) noexcept
|
||||
SymmetryBreaking::d4_unit_square,
|
||||
Pruning const pruning = Pruning::valley_capacity) noexcept
|
||||
-> Results {
|
||||
if (symmetry == SymmetryBreaking::d4_unit_square) {
|
||||
return search_solution_impl<false, true>(n, policy, nullptr);
|
||||
if (pruning == Pruning::valley_capacity) {
|
||||
return search_solution_impl<false, true, true>(n, policy, nullptr);
|
||||
}
|
||||
return search_solution_impl<false, true, false>(n, policy, nullptr);
|
||||
}
|
||||
return search_solution_impl<false, false>(n, policy, nullptr);
|
||||
if (pruning == Pruning::valley_capacity) {
|
||||
return search_solution_impl<false, false, true>(n, policy, nullptr);
|
||||
}
|
||||
return search_solution_impl<false, false, false>(n, policy, nullptr);
|
||||
}
|
||||
|
||||
auto search_solution_instrumented(size_t const n,
|
||||
@@ -353,13 +410,23 @@ namespace {
|
||||
SearchPolicy const policy =
|
||||
SearchPolicy::ascending,
|
||||
SymmetryBreaking const symmetry =
|
||||
SymmetryBreaking::d4_unit_square) noexcept
|
||||
SymmetryBreaking::d4_unit_square,
|
||||
Pruning const pruning =
|
||||
Pruning::valley_capacity) noexcept
|
||||
-> Results {
|
||||
counters = {};
|
||||
if (symmetry == SymmetryBreaking::d4_unit_square) {
|
||||
return search_solution_impl<true, true>(n, policy, &counters);
|
||||
if (pruning == Pruning::valley_capacity) {
|
||||
return search_solution_impl<true, true, true>(
|
||||
n, policy, &counters);
|
||||
}
|
||||
return search_solution_impl<true, true, false>(n, policy, &counters);
|
||||
}
|
||||
return search_solution_impl<true, false>(n, policy, &counters);
|
||||
if (pruning == Pruning::valley_capacity) {
|
||||
return search_solution_impl<true, false, true>(
|
||||
n, policy, &counters);
|
||||
}
|
||||
return search_solution_impl<true, false, false>(n, policy, &counters);
|
||||
}
|
||||
|
||||
/** Construct an odd-order solution from its even-order predecessor. */
|
||||
@@ -396,12 +463,13 @@ namespace {
|
||||
size_t const n,
|
||||
SearchPolicy const policy = SearchPolicy::ascending,
|
||||
SymmetryBreaking const symmetry =
|
||||
SymmetryBreaking::d4_unit_square) noexcept -> Results {
|
||||
SymmetryBreaking::d4_unit_square,
|
||||
Pruning const pruning = Pruning::valley_capacity) noexcept -> Results {
|
||||
if (uses_odd_construction(n)) {
|
||||
return construct_odd_solution(
|
||||
n, search_solution(n - 1, policy, symmetry));
|
||||
n, search_solution(n - 1, policy, symmetry, pruning));
|
||||
}
|
||||
return search_solution(n, policy, symmetry);
|
||||
return search_solution(n, policy, symmetry, pruning);
|
||||
}
|
||||
|
||||
auto find_solution_instrumented(size_t const n,
|
||||
@@ -409,14 +477,17 @@ namespace {
|
||||
SearchPolicy const policy =
|
||||
SearchPolicy::ascending,
|
||||
SymmetryBreaking const symmetry =
|
||||
SymmetryBreaking::d4_unit_square) noexcept
|
||||
SymmetryBreaking::d4_unit_square,
|
||||
Pruning const pruning =
|
||||
Pruning::valley_capacity) noexcept
|
||||
-> Results {
|
||||
if (uses_odd_construction(n)) {
|
||||
return construct_odd_solution(
|
||||
n, search_solution_instrumented(
|
||||
n - 1, counters, policy, symmetry));
|
||||
n - 1, counters, policy, symmetry, pruning));
|
||||
}
|
||||
return search_solution_instrumented(n, counters, policy, symmetry);
|
||||
return search_solution_instrumented(
|
||||
n, counters, policy, symmetry, pruning);
|
||||
}
|
||||
} // anon namespace
|
||||
|
||||
|
||||
Reference in New Issue
Block a user