solver: prune unplaceable large squares

A skyline may retain enough total empty area while no longer containing a box for its largest remaining square. Scan for the required consecutive low columns and reject such monotonic dead states.

Keep each pruning combination independently measurable and record the small public-path gain, the direct-order-9 regression, and the rejected periodic schedule.

Tests: Debug CTest (13 passed)

Tests: ASan+UBSan CTest (13 passed)

Refs: #15
This commit was merged in pull request #27.
This commit is contained in:
Codex instance
2026-07-31 08:25:36 +01:00
parent e27427d231
commit 220cec06a9
6 changed files with 264 additions and 43 deletions
+70
View File
@@ -369,6 +369,8 @@ namespace {
failures += expect(counters.prune_checks >= counters.prune_hits &&
counters.valley_capacity_checks >=
counters.valley_capacity_prunes &&
counters.large_square_checks >=
counters.large_square_prunes &&
counters.generated_tasks == 0 &&
counters.completed_tasks == 0,
"search counters are inconsistent");
@@ -582,6 +584,71 @@ namespace {
return failures;
}
auto test_large_square_pruning() -> int {
int failures = 0;
auto const fragmented =
std::vector<std::uint64_t>{0, 5, 0, 5, 0, 5};
failures += expect(
!skyline_has_empty_square(fragmented, 2),
"large-square check accepted a fragmented state without a 2x2 box");
failures += expect(
skyline_has_empty_square(
std::vector<std::uint64_t>{0, 0, 5, 5, 5, 5}, 2),
"large-square check rejected an available 2x2 box");
Avail available(4);
available[1] = 1;
available[2] = 1;
failures += expect(
!remaining_large_square_fits(fragmented, available),
"remaining-square check ignored the impossible largest square");
available[2] = 0;
failures += expect(
remaining_large_square_fits(fragmented, available),
"remaining-square check did not use geometric size monotonicity");
// The fragmented profile has 21 empty cells, more than the area of the
// remaining 2x2 square, but no two adjacent columns have two free rows.
std::uint64_t filled_area = 0;
for (auto const height: fragmented) {
filled_area += height;
}
failures += expect(
filled_area <= 36 - 4,
"fragmented test state does not have sufficient total empty area");
for (auto const order: std::array<std::uint64_t, 5>{1, 2, 3, 4, 5}) {
SearchCounters pruned_counters;
SearchCounters baseline_counters;
auto const pruned = search_solution_instrumented(
order, pruned_counters, SearchPolicy::ascending,
SymmetryBreaking::disabled, Pruning::all);
auto const baseline = search_solution_instrumented(
order, baseline_counters, SearchPolicy::ascending,
SymmetryBreaking::disabled, Pruning::valley_capacity);
failures += expect(
pruned.squares().empty() == baseline.squares().empty(),
"large-square pruning changed exhaustive order-" +
std::to_string(order) + " feasibility");
failures += expect(
pruned_counters.search_nodes <= baseline_counters.search_nodes,
"large-square 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::large_square));
failures += expect(
counters.large_square_checks > 0 &&
counters.large_square_prunes > 0 &&
counters.prune_checks == counters.large_square_checks &&
counters.prune_hits == counters.large_square_prunes,
"large-square instrumentation did not count checks and prunes");
return failures;
}
auto test_solver_completion() -> int {
int failures = 0;
for (auto const order: std::array<std::uint64_t, 2>{1, 8}) {
@@ -633,6 +700,9 @@ int main(int argc, char **argv) {
if (test == "valley-capacity") {
return test_valley_capacity_pruning();
}
if (test == "large-square") {
return test_large_square_pruning();
}
std::cerr << "unknown test: " << test << '\n';
return 2;
}