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
+23 -5
View File
@@ -29,6 +29,7 @@ namespace {
auto solve(std::uint64_t order, bool instrument,
SearchPolicy policy, bool direct_search,
SymmetryBreaking symmetry,
Pruning pruning,
SearchCounters &counters)
-> TimedSolution {
auto const predecessor_order =
@@ -37,8 +38,8 @@ namespace {
auto predecessor =
instrument
? search_solution_instrumented(predecessor_order, counters,
policy, symmetry)
: search_solution(predecessor_order, policy, symmetry);
policy, symmetry, pruning)
: search_solution(predecessor_order, policy, symmetry, pruning);
auto const search_end = Clock::now();
auto const construction_begin = Clock::now();
@@ -94,10 +95,10 @@ namespace {
}
int main(int argc, char **argv) {
if (argc < 3 || argc > 6) {
if (argc < 3 || argc > 7) {
std::cerr << "usage: partridge_benchmark ORDER counters|plain "
"[ascending|descending|best-fit] [public|direct] "
"[d4|none]\n";
"[d4|none] [valley-capacity|none]\n";
return 2;
}
auto const order = static_cast<std::uint64_t>(std::strtoull(argv[1], nullptr, 10));
@@ -134,10 +135,20 @@ int main(int argc, char **argv) {
std::cerr << "symmetry mode must be d4 or none\n";
return 2;
}
auto const pruning =
argc < 7 || std::string_view(argv[6]) == "valley-capacity"
? Pruning::valley_capacity
: Pruning::disabled;
if (argc == 7 && std::string_view(argv[6]) != "valley-capacity" &&
std::string_view(argv[6]) != "none") {
std::cerr << "pruning mode must be valley-capacity or none\n";
return 2;
}
SearchCounters counters;
auto timed =
solve(order, instrument, policy, direct_search, symmetry, counters);
solve(order, instrument, policy, direct_search, symmetry, pruning,
counters);
auto const validation_begin = Clock::now();
auto const validation_ok = valid(order, timed.result);
@@ -165,6 +176,9 @@ int main(int argc, char **argv) {
<< ",\"symmetry_breaking\":\""
<< (symmetry == SymmetryBreaking::d4_unit_square ? "d4" : "none")
<< "\""
<< ",\"pruning\":\""
<< (pruning == Pruning::valley_capacity ? "valley-capacity" : "none")
<< "\""
<< ",\"solved\":" << boolean(!timed.result.squares().empty())
<< ",\"valid\":" << boolean(validation_ok)
<< ",\"timing_seconds\":{\"solve\":" << timed.search_seconds
@@ -177,6 +191,10 @@ int main(int argc, char **argv) {
<< ",\"backtracks\":" << counters.backtracks
<< ",\"prune_checks\":" << counters.prune_checks
<< ",\"prune_hits\":" << counters.prune_hits
<< ",\"valley_capacity_checks\":"
<< counters.valley_capacity_checks
<< ",\"valley_capacity_prunes\":"
<< counters.valley_capacity_prunes
<< ",\"generated_tasks\":" << counters.generated_tasks
<< ",\"completed_tasks\":" << counters.completed_tasks << "}}\n";
return validation_ok ? 0 : 1;
+10 -1
View File
@@ -80,7 +80,7 @@ def environment(binary):
def run_once(
binary, order, mode, search_policy, search_route, symmetry, timeout
binary, order, mode, search_policy, search_route, symmetry, pruning, timeout
):
started = time.monotonic()
try:
@@ -92,6 +92,7 @@ def run_once(
search_policy,
search_route,
symmetry,
pruning,
],
check=False,
capture_output=True,
@@ -237,6 +238,11 @@ def main():
action="store_true",
help="disable unique-unit-square D4 symmetry breaking",
)
parser.add_argument(
"--no-pruning",
action="store_true",
help="disable valley-capacity pruning",
)
parser.add_argument(
"--measure-overhead",
action="store_true",
@@ -269,6 +275,7 @@ def main():
args.search_policy,
"direct" if args.direct_search else "public",
"none" if args.no_symmetry else "d4",
"none" if args.no_pruning else "valley-capacity",
args.timeout,
)
for trial in range(args.repetitions):
@@ -281,6 +288,7 @@ def main():
args.search_policy,
"direct" if args.direct_search else "public",
"none" if args.no_symmetry else "d4",
"none" if args.no_pruning else "valley-capacity",
args.timeout,
)
)
@@ -310,6 +318,7 @@ def main():
"candidate_order": args.search_policy,
"search_route": "direct" if args.direct_search else "public",
"symmetry_breaking": "none" if args.no_symmetry else "d4",
"pruning": "none" if args.no_pruning else "valley-capacity",
"stdout": "captured; rendered grid suppressed by probe",
},
"cases": cases,