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:
Codex instance
2026-07-31 08:11:30 +01:00
parent f37e08768d
commit e27427d231
6 changed files with 234 additions and 26 deletions
+76 -2
View File
@@ -367,6 +367,8 @@ namespace {
counters.backtracks > 0,
"instrumented solver did not count placements/backtracks");
failures += expect(counters.prune_checks >= counters.prune_hits &&
counters.valley_capacity_checks >=
counters.valley_capacity_prunes &&
counters.generated_tasks == 0 &&
counters.completed_tasks == 0,
"search counters are inconsistent");
@@ -427,10 +429,10 @@ namespace {
SearchCounters disabled_counters;
auto const enabled = search_solution_instrumented(
8, enabled_counters, SearchPolicy::ascending,
SymmetryBreaking::d4_unit_square);
SymmetryBreaking::d4_unit_square, Pruning::disabled);
auto const disabled = search_solution_instrumented(
8, disabled_counters, SearchPolicy::ascending,
SymmetryBreaking::disabled);
SymmetryBreaking::disabled, Pruning::disabled);
auto enabled_validation = validate(8, enabled);
auto disabled_validation = validate(8, disabled);
failures += expect(
@@ -511,6 +513,75 @@ namespace {
return failures;
}
auto test_valley_capacity_pruning() -> int {
int failures = 0;
Avail available(5);
available[1] = 1;
failures += expect(
!valley_has_sufficient_capacity(
{1, 0, 1}, std::vector<std::uint64_t>{4, 0, 4}, available),
"width-one gap accepted insufficient unit-square capacity");
available = {};
available.resize(5);
available[2] = 1;
failures += expect(
!valley_has_sufficient_capacity(
{1, 0, 2}, std::vector<std::uint64_t>{4, 0, 0, 4}, available),
"width-two gap accepted insufficient narrow-piece area");
available[2] = 2;
failures += expect(
valley_has_sufficient_capacity(
{1, 0, 2}, std::vector<std::uint64_t>{4, 0, 0, 4}, available),
"width-two gap rejected exactly sufficient narrow-piece area");
available = {};
available.resize(5);
available[1] = 1;
available[2] = 1;
failures += expect(
!valley_has_sufficient_capacity(
{1, 2, 2}, std::vector<std::uint64_t>{5, 2, 2, 5}, available),
"general valley accepted less area than required to reach its rim");
available[2] = 2;
failures += expect(
valley_has_sufficient_capacity(
{1, 2, 2}, std::vector<std::uint64_t>{5, 2, 2, 5}, available),
"general valley rejected sufficient fitting-square area");
for (auto const order: std::array<std::uint64_t, 5>{1, 2, 3, 4, 5}) {
SearchCounters pruned_counters;
SearchCounters unpruned_counters;
auto const pruned = search_solution_instrumented(
order, pruned_counters, SearchPolicy::ascending,
SymmetryBreaking::disabled, Pruning::valley_capacity);
auto const unpruned = search_solution_instrumented(
order, unpruned_counters, SearchPolicy::ascending,
SymmetryBreaking::disabled, Pruning::disabled);
failures += expect(
pruned.squares().empty() == unpruned.squares().empty(),
"valley pruning changed exhaustive order-" +
std::to_string(order) + " feasibility");
failures += expect(
pruned_counters.search_nodes <= unpruned_counters.search_nodes,
"valley pruning enlarged the exhaustive order-" +
std::to_string(order) + " search");
}
SearchCounters counters;
static_cast<void>(search_solution_instrumented(
5, counters, SearchPolicy::ascending, SymmetryBreaking::disabled,
Pruning::valley_capacity));
failures += expect(
counters.valley_capacity_checks > 0 &&
counters.valley_capacity_prunes > 0 &&
counters.prune_checks == counters.valley_capacity_checks &&
counters.prune_hits == counters.valley_capacity_prunes,
"valley pruning instrumentation did not count checks and hits");
return failures;
}
auto test_solver_completion() -> int {
int failures = 0;
for (auto const order: std::array<std::uint64_t, 2>{1, 8}) {
@@ -559,6 +630,9 @@ int main(int argc, char **argv) {
if (test == "d4-symmetry") {
return test_d4_symmetry();
}
if (test == "valley-capacity") {
return test_valley_capacity_pruning();
}
std::cerr << "unknown test: " << test << '\n';
return 2;
}