solver: prune insufficient valley capacity #26
+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
|
The production default also removes equivalent D4 board orientations by
|
||||||
constraining the unique unit square. Pass `--no-symmetry` to obtain an
|
constraining the unique unit square. Pass `--no-symmetry` to obtain an
|
||||||
otherwise identical unconstrained baseline.
|
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
|
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:
|
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,
|
construction separately. The document also records compiler,
|
||||||
flags, build type, commit, OS/CPU metadata, worker count, search policy, seed,
|
flags, build type, commit, OS/CPU metadata, worker count, search policy, seed,
|
||||||
timeouts, errors, invalid outputs, and the stdout policy. Search counts must
|
timeouts, errors, invalid outputs, and the stdout policy. Search counts must
|
||||||
be stable across repeated runs. Prune and task counters are zero for the
|
be stable across repeated runs. Prune counters report enabled search rules;
|
||||||
current unpruned, single-threaded solver and reserve stable schema fields for
|
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.
|
later work.
|
||||||
|
|
||||||
The runner writes its JSON report before returning a failure status if any mode
|
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
|
JSON outside version control unless it is being deliberately added as a named
|
||||||
comparison baseline.
|
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
|
## D4 board symmetry
|
||||||
|
|
||||||
Every solution contains exactly one 1-by-1 square. Rotations and reflections
|
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 search-counters COMMAND partridge_tests search-counters)
|
||||||
add_test(NAME skyline-search COMMAND partridge_tests skyline-search)
|
add_test(NAME skyline-search COMMAND partridge_tests skyline-search)
|
||||||
add_test(NAME d4-symmetry COMMAND partridge_tests d4-symmetry)
|
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)
|
find_package(Python3 COMPONENTS Interpreter)
|
||||||
if(Python3_Interpreter_FOUND)
|
if(Python3_Interpreter_FOUND)
|
||||||
add_test(NAME benchmark-format
|
add_test(NAME benchmark-format
|
||||||
|
|||||||
+23
-5
@@ -29,6 +29,7 @@ namespace {
|
|||||||
auto solve(std::uint64_t order, bool instrument,
|
auto solve(std::uint64_t order, bool instrument,
|
||||||
SearchPolicy policy, bool direct_search,
|
SearchPolicy policy, bool direct_search,
|
||||||
SymmetryBreaking symmetry,
|
SymmetryBreaking symmetry,
|
||||||
|
Pruning pruning,
|
||||||
SearchCounters &counters)
|
SearchCounters &counters)
|
||||||
-> TimedSolution {
|
-> TimedSolution {
|
||||||
auto const predecessor_order =
|
auto const predecessor_order =
|
||||||
@@ -37,8 +38,8 @@ namespace {
|
|||||||
auto predecessor =
|
auto predecessor =
|
||||||
instrument
|
instrument
|
||||||
? search_solution_instrumented(predecessor_order, counters,
|
? search_solution_instrumented(predecessor_order, counters,
|
||||||
policy, symmetry)
|
policy, symmetry, pruning)
|
||||||
: search_solution(predecessor_order, policy, symmetry);
|
: search_solution(predecessor_order, policy, symmetry, pruning);
|
||||||
auto const search_end = Clock::now();
|
auto const search_end = Clock::now();
|
||||||
|
|
||||||
auto const construction_begin = Clock::now();
|
auto const construction_begin = Clock::now();
|
||||||
@@ -94,10 +95,10 @@ namespace {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, char **argv) {
|
int main(int argc, char **argv) {
|
||||||
if (argc < 3 || argc > 6) {
|
if (argc < 3 || argc > 7) {
|
||||||
std::cerr << "usage: partridge_benchmark ORDER counters|plain "
|
std::cerr << "usage: partridge_benchmark ORDER counters|plain "
|
||||||
"[ascending|descending|best-fit] [public|direct] "
|
"[ascending|descending|best-fit] [public|direct] "
|
||||||
"[d4|none]\n";
|
"[d4|none] [valley-capacity|none]\n";
|
||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
auto const order = static_cast<std::uint64_t>(std::strtoull(argv[1], nullptr, 10));
|
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";
|
std::cerr << "symmetry mode must be d4 or none\n";
|
||||||
return 2;
|
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;
|
SearchCounters counters;
|
||||||
auto timed =
|
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_begin = Clock::now();
|
||||||
auto const validation_ok = valid(order, timed.result);
|
auto const validation_ok = valid(order, timed.result);
|
||||||
@@ -165,6 +176,9 @@ int main(int argc, char **argv) {
|
|||||||
<< ",\"symmetry_breaking\":\""
|
<< ",\"symmetry_breaking\":\""
|
||||||
<< (symmetry == SymmetryBreaking::d4_unit_square ? "d4" : "none")
|
<< (symmetry == SymmetryBreaking::d4_unit_square ? "d4" : "none")
|
||||||
<< "\""
|
<< "\""
|
||||||
|
<< ",\"pruning\":\""
|
||||||
|
<< (pruning == Pruning::valley_capacity ? "valley-capacity" : "none")
|
||||||
|
<< "\""
|
||||||
<< ",\"solved\":" << boolean(!timed.result.squares().empty())
|
<< ",\"solved\":" << boolean(!timed.result.squares().empty())
|
||||||
<< ",\"valid\":" << boolean(validation_ok)
|
<< ",\"valid\":" << boolean(validation_ok)
|
||||||
<< ",\"timing_seconds\":{\"solve\":" << timed.search_seconds
|
<< ",\"timing_seconds\":{\"solve\":" << timed.search_seconds
|
||||||
@@ -177,6 +191,10 @@ int main(int argc, char **argv) {
|
|||||||
<< ",\"backtracks\":" << counters.backtracks
|
<< ",\"backtracks\":" << counters.backtracks
|
||||||
<< ",\"prune_checks\":" << counters.prune_checks
|
<< ",\"prune_checks\":" << counters.prune_checks
|
||||||
<< ",\"prune_hits\":" << counters.prune_hits
|
<< ",\"prune_hits\":" << counters.prune_hits
|
||||||
|
<< ",\"valley_capacity_checks\":"
|
||||||
|
<< counters.valley_capacity_checks
|
||||||
|
<< ",\"valley_capacity_prunes\":"
|
||||||
|
<< counters.valley_capacity_prunes
|
||||||
<< ",\"generated_tasks\":" << counters.generated_tasks
|
<< ",\"generated_tasks\":" << counters.generated_tasks
|
||||||
<< ",\"completed_tasks\":" << counters.completed_tasks << "}}\n";
|
<< ",\"completed_tasks\":" << counters.completed_tasks << "}}\n";
|
||||||
return validation_ok ? 0 : 1;
|
return validation_ok ? 0 : 1;
|
||||||
|
|||||||
+10
-1
@@ -80,7 +80,7 @@ def environment(binary):
|
|||||||
|
|
||||||
|
|
||||||
def run_once(
|
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()
|
started = time.monotonic()
|
||||||
try:
|
try:
|
||||||
@@ -92,6 +92,7 @@ def run_once(
|
|||||||
search_policy,
|
search_policy,
|
||||||
search_route,
|
search_route,
|
||||||
symmetry,
|
symmetry,
|
||||||
|
pruning,
|
||||||
],
|
],
|
||||||
check=False,
|
check=False,
|
||||||
capture_output=True,
|
capture_output=True,
|
||||||
@@ -237,6 +238,11 @@ def main():
|
|||||||
action="store_true",
|
action="store_true",
|
||||||
help="disable unique-unit-square D4 symmetry breaking",
|
help="disable unique-unit-square D4 symmetry breaking",
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--no-pruning",
|
||||||
|
action="store_true",
|
||||||
|
help="disable valley-capacity pruning",
|
||||||
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--measure-overhead",
|
"--measure-overhead",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
@@ -269,6 +275,7 @@ def main():
|
|||||||
args.search_policy,
|
args.search_policy,
|
||||||
"direct" if args.direct_search else "public",
|
"direct" if args.direct_search else "public",
|
||||||
"none" if args.no_symmetry else "d4",
|
"none" if args.no_symmetry else "d4",
|
||||||
|
"none" if args.no_pruning else "valley-capacity",
|
||||||
args.timeout,
|
args.timeout,
|
||||||
)
|
)
|
||||||
for trial in range(args.repetitions):
|
for trial in range(args.repetitions):
|
||||||
@@ -281,6 +288,7 @@ def main():
|
|||||||
args.search_policy,
|
args.search_policy,
|
||||||
"direct" if args.direct_search else "public",
|
"direct" if args.direct_search else "public",
|
||||||
"none" if args.no_symmetry else "d4",
|
"none" if args.no_symmetry else "d4",
|
||||||
|
"none" if args.no_pruning else "valley-capacity",
|
||||||
args.timeout,
|
args.timeout,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -310,6 +318,7 @@ def main():
|
|||||||
"candidate_order": args.search_policy,
|
"candidate_order": args.search_policy,
|
||||||
"search_route": "direct" if args.direct_search else "public",
|
"search_route": "direct" if args.direct_search else "public",
|
||||||
"symmetry_breaking": "none" if args.no_symmetry else "d4",
|
"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",
|
"stdout": "captured; rendered grid suppressed by probe",
|
||||||
},
|
},
|
||||||
"cases": cases,
|
"cases": cases,
|
||||||
|
|||||||
@@ -150,6 +150,8 @@ namespace {
|
|||||||
size_t backtracks = 0;
|
size_t backtracks = 0;
|
||||||
size_t prune_checks = 0;
|
size_t prune_checks = 0;
|
||||||
size_t prune_hits = 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 generated_tasks = 0;
|
||||||
size_t completed_tasks = 0;
|
size_t completed_tasks = 0;
|
||||||
};
|
};
|
||||||
@@ -168,6 +170,12 @@ namespace {
|
|||||||
d4_unit_square,
|
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.
|
/** 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
|
* Reflect a cell into the left half of the board, rotate so its distance
|
||||||
@@ -223,7 +231,35 @@ namespace {
|
|||||||
return best;
|
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,
|
auto search_skyline(size_t const n, size_t const length,
|
||||||
SearchPolicy const policy,
|
SearchPolicy const policy,
|
||||||
std::vector<size_t> &skyline, Avail &available,
|
std::vector<size_t> &skyline, Avail &available,
|
||||||
@@ -239,6 +275,19 @@ namespace {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto const valley = smallest_valley(skyline);
|
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 =
|
auto const largest =
|
||||||
std::min({n, valley.width, length - valley.height});
|
std::min({n, valley.width, length - valley.height});
|
||||||
auto try_side = [&](size_t const side) {
|
auto try_side = [&](size_t const side) {
|
||||||
@@ -271,7 +320,7 @@ namespace {
|
|||||||
side, valley.height + side);
|
side, valley.height + side);
|
||||||
squares.emplace_back(valley.x + valley.height * length, 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)) {
|
n, length, policy, skyline, available, squares, counters)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -318,7 +367,7 @@ namespace {
|
|||||||
* candidates and updating up to n columns for each costs O(n^2), for
|
* 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.
|
* 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,
|
auto search_solution_impl(size_t const n, SearchPolicy const policy,
|
||||||
SearchCounters *const counters) noexcept
|
SearchCounters *const counters) noexcept
|
||||||
-> Results {
|
-> Results {
|
||||||
@@ -330,7 +379,8 @@ namespace {
|
|||||||
}
|
}
|
||||||
std::vector<Square> squares;
|
std::vector<Square> squares;
|
||||||
squares.reserve(length);
|
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));
|
n, length, policy, skyline, available, squares, counters));
|
||||||
|
|
||||||
return {length, std::move(squares)};
|
return {length, std::move(squares)};
|
||||||
@@ -340,12 +390,19 @@ namespace {
|
|||||||
size_t const n,
|
size_t const n,
|
||||||
SearchPolicy const policy = SearchPolicy::ascending,
|
SearchPolicy const policy = SearchPolicy::ascending,
|
||||||
SymmetryBreaking const symmetry =
|
SymmetryBreaking const symmetry =
|
||||||
SymmetryBreaking::d4_unit_square) noexcept
|
SymmetryBreaking::d4_unit_square,
|
||||||
|
Pruning const pruning = Pruning::valley_capacity) noexcept
|
||||||
-> Results {
|
-> Results {
|
||||||
if (symmetry == SymmetryBreaking::d4_unit_square) {
|
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, false>(n, policy, nullptr);
|
return search_solution_impl<false, true, 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,
|
auto search_solution_instrumented(size_t const n,
|
||||||
@@ -353,13 +410,23 @@ namespace {
|
|||||||
SearchPolicy const policy =
|
SearchPolicy const policy =
|
||||||
SearchPolicy::ascending,
|
SearchPolicy::ascending,
|
||||||
SymmetryBreaking const symmetry =
|
SymmetryBreaking const symmetry =
|
||||||
SymmetryBreaking::d4_unit_square) noexcept
|
SymmetryBreaking::d4_unit_square,
|
||||||
|
Pruning const pruning =
|
||||||
|
Pruning::valley_capacity) noexcept
|
||||||
-> Results {
|
-> Results {
|
||||||
counters = {};
|
counters = {};
|
||||||
if (symmetry == SymmetryBreaking::d4_unit_square) {
|
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, false>(n, policy, &counters);
|
return search_solution_impl<true, 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. */
|
/** Construct an odd-order solution from its even-order predecessor. */
|
||||||
@@ -396,12 +463,13 @@ namespace {
|
|||||||
size_t const n,
|
size_t const n,
|
||||||
SearchPolicy const policy = SearchPolicy::ascending,
|
SearchPolicy const policy = SearchPolicy::ascending,
|
||||||
SymmetryBreaking const symmetry =
|
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)) {
|
if (uses_odd_construction(n)) {
|
||||||
return construct_odd_solution(
|
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,
|
auto find_solution_instrumented(size_t const n,
|
||||||
@@ -409,14 +477,17 @@ namespace {
|
|||||||
SearchPolicy const policy =
|
SearchPolicy const policy =
|
||||||
SearchPolicy::ascending,
|
SearchPolicy::ascending,
|
||||||
SymmetryBreaking const symmetry =
|
SymmetryBreaking const symmetry =
|
||||||
SymmetryBreaking::d4_unit_square) noexcept
|
SymmetryBreaking::d4_unit_square,
|
||||||
|
Pruning const pruning =
|
||||||
|
Pruning::valley_capacity) noexcept
|
||||||
-> Results {
|
-> Results {
|
||||||
if (uses_odd_construction(n)) {
|
if (uses_odd_construction(n)) {
|
||||||
return construct_odd_solution(
|
return construct_odd_solution(
|
||||||
n, search_solution_instrumented(
|
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
|
} // anon namespace
|
||||||
|
|
||||||
|
|||||||
+76
-2
@@ -367,6 +367,8 @@ namespace {
|
|||||||
counters.backtracks > 0,
|
counters.backtracks > 0,
|
||||||
"instrumented solver did not count placements/backtracks");
|
"instrumented solver did not count placements/backtracks");
|
||||||
failures += expect(counters.prune_checks >= counters.prune_hits &&
|
failures += expect(counters.prune_checks >= counters.prune_hits &&
|
||||||
|
counters.valley_capacity_checks >=
|
||||||
|
counters.valley_capacity_prunes &&
|
||||||
counters.generated_tasks == 0 &&
|
counters.generated_tasks == 0 &&
|
||||||
counters.completed_tasks == 0,
|
counters.completed_tasks == 0,
|
||||||
"search counters are inconsistent");
|
"search counters are inconsistent");
|
||||||
@@ -427,10 +429,10 @@ namespace {
|
|||||||
SearchCounters disabled_counters;
|
SearchCounters disabled_counters;
|
||||||
auto const enabled = search_solution_instrumented(
|
auto const enabled = search_solution_instrumented(
|
||||||
8, enabled_counters, SearchPolicy::ascending,
|
8, enabled_counters, SearchPolicy::ascending,
|
||||||
SymmetryBreaking::d4_unit_square);
|
SymmetryBreaking::d4_unit_square, Pruning::disabled);
|
||||||
auto const disabled = search_solution_instrumented(
|
auto const disabled = search_solution_instrumented(
|
||||||
8, disabled_counters, SearchPolicy::ascending,
|
8, disabled_counters, SearchPolicy::ascending,
|
||||||
SymmetryBreaking::disabled);
|
SymmetryBreaking::disabled, Pruning::disabled);
|
||||||
auto enabled_validation = validate(8, enabled);
|
auto enabled_validation = validate(8, enabled);
|
||||||
auto disabled_validation = validate(8, disabled);
|
auto disabled_validation = validate(8, disabled);
|
||||||
failures += expect(
|
failures += expect(
|
||||||
@@ -511,6 +513,75 @@ namespace {
|
|||||||
return failures;
|
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 {
|
auto test_solver_completion() -> int {
|
||||||
int failures = 0;
|
int failures = 0;
|
||||||
for (auto const order: std::array<std::uint64_t, 2>{1, 8}) {
|
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") {
|
if (test == "d4-symmetry") {
|
||||||
return test_d4_symmetry();
|
return test_d4_symmetry();
|
||||||
}
|
}
|
||||||
|
if (test == "valley-capacity") {
|
||||||
|
return test_valley_capacity_pruning();
|
||||||
|
}
|
||||||
std::cerr << "unknown test: " << test << '\n';
|
std::cerr << "unknown test: " << test << '\n';
|
||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user