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:
+76
-2
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user