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:
+37
-2
@@ -21,6 +21,8 @@ and `--candidate-order`. The choices are `ascending`, `descending`, and
|
||||
The production default also removes equivalent D4 board orientations by
|
||||
constraining the unique unit square. Pass `--no-symmetry` to obtain an
|
||||
otherwise identical unconstrained baseline.
|
||||
The production default also applies the valley-capacity rule described below.
|
||||
Pass `--no-pruning` to obtain an otherwise identical unpruned search.
|
||||
Order 9 uses the constructive odd-order path, searching order 8 and then tiling
|
||||
the enlarged border, so it is suitable for normal local benchmarking:
|
||||
|
||||
@@ -45,8 +47,9 @@ solve time. Constructed odd-order cases report predecessor search and
|
||||
construction separately. The document also records compiler,
|
||||
flags, build type, commit, OS/CPU metadata, worker count, search policy, seed,
|
||||
timeouts, errors, invalid outputs, and the stdout policy. Search counts must
|
||||
be stable across repeated runs. Prune and task counters are zero for the
|
||||
current unpruned, single-threaded solver and reserve stable schema fields for
|
||||
be stable across repeated runs. Prune counters report enabled search rules;
|
||||
they are zero in the corresponding disabled modes. Task counters remain zero
|
||||
for the current single-threaded solver and reserve stable schema fields for
|
||||
later work.
|
||||
|
||||
The runner writes its JSON report before returning a failure status if any mode
|
||||
@@ -72,6 +75,38 @@ Do not use wall-clock thresholds as correctness checks. Keep the generated
|
||||
JSON outside version control unless it is being deliberately added as a named
|
||||
comparison baseline.
|
||||
|
||||
## Valley-capacity pruning
|
||||
|
||||
For a selected valley, let its rim be the lower height of its two neighbours,
|
||||
treating a board edge as full height. Until the valley reaches that rim, no
|
||||
square wider than the valley can enter it. Therefore the combined area of all
|
||||
remaining squares no wider than the valley must be at least the valley width
|
||||
times its depth below the rim. Rejecting a state when that necessary
|
||||
inequality fails is sound. At widths one and two it provides the usual
|
||||
narrow-gap capacity checks without separate special cases.
|
||||
|
||||
The rule is compiled out of the recursive search in `--no-pruning` mode.
|
||||
Instrumented output records `valley_capacity_checks` and
|
||||
`valley_capacity_prunes` as well as the aggregate pruning counters, allowing
|
||||
enabled and disabled runs to report nodes, checks, hits, and elapsed time.
|
||||
|
||||
Measurements used the issue #10 working tree based on commit `f37e087`, Apple
|
||||
Clang 21.0.0, `-O3 -DNDEBUG`, macOS arm64, one worker, one warm-up, and five
|
||||
measured repetitions. The production ascending policy and D4 constraint were
|
||||
enabled. Every result passed the benchmark's independent validator and all
|
||||
counters were stable:
|
||||
|
||||
| Order | Valley capacity | Median solve (range) | Nodes | Checks | Prunes |
|
||||
| --- | --- | ---: | ---: | ---: | ---: |
|
||||
| 7 exhaustive | enabled | 0.996 s (0.990-0.998 s) | 13,833,048 | 13,833,048 | 7,411,551 |
|
||||
| 7 exhaustive | disabled | 1.110 s (1.109-1.111 s) | 14,997,603 | 0 | 0 |
|
||||
| 8 first solution | enabled | 0.205 s (0.204-0.208 s) | 2,724,096 | 2,724,095 | 1,606,836 |
|
||||
| 8 first solution | disabled | 0.228 s (0.228-0.233 s) | 2,931,203 | 0 | 0 |
|
||||
|
||||
The rule reduced nodes by 7.8% for order 7 and 7.1% for order 8. Its low
|
||||
per-node cost also reduced median counted solve time by 10.3% and 9.9%,
|
||||
respectively, so it remains enabled by default.
|
||||
|
||||
## D4 board symmetry
|
||||
|
||||
Every solution contains exactly one 1-by-1 square. Rotations and reflections
|
||||
|
||||
@@ -26,6 +26,7 @@ if(BUILD_TESTING)
|
||||
add_test(NAME search-counters COMMAND partridge_tests search-counters)
|
||||
add_test(NAME skyline-search COMMAND partridge_tests skyline-search)
|
||||
add_test(NAME d4-symmetry COMMAND partridge_tests d4-symmetry)
|
||||
add_test(NAME valley-capacity COMMAND partridge_tests valley-capacity)
|
||||
find_package(Python3 COMPONENTS Interpreter)
|
||||
if(Python3_Interpreter_FOUND)
|
||||
add_test(NAME benchmark-format
|
||||
|
||||
+23
-5
@@ -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
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
+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