solver: add optional component-area pruning
Full-height skyline columns partition the remaining board. Add sound gcd and bounded subset-sum checks for the resulting component areas, with boundary-event and periodic benchmark schedules. Keep the rules disabled by default because their small tree reductions do not recover their measured cost. Record the rejected default and scheduling evidence so it can be revisited only with new data. Tests: Debug CTest (14 passed) Tests: ASan+UBSan CTest (14 passed) Refs: #13
This commit was merged in pull request #28.
This commit is contained in:
@@ -24,6 +24,10 @@ otherwise identical unconstrained baseline.
|
|||||||
The production default also applies the pruning rules described below.
|
The production default also applies the pruning rules described below.
|
||||||
Use `--pruning valley-capacity`, `--pruning large-square`, or
|
Use `--pruning valley-capacity`, `--pruning large-square`, or
|
||||||
`--no-pruning` to measure each rule alone or obtain an unpruned search.
|
`--no-pruning` to measure each rule alone or obtain an unpruned search.
|
||||||
|
Use `--component-pruning gcd`, `--component-pruning subset-sum`, or
|
||||||
|
`--component-pruning none` to compare the component-area rule separately.
|
||||||
|
Component pruning is disabled by default because the measurements below do not
|
||||||
|
recover its cost.
|
||||||
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:
|
||||||
|
|
||||||
@@ -157,6 +161,62 @@ is already recovered; it was not added without evidence that the complexity
|
|||||||
would improve elapsed time. The simple every-node scan remains enabled by
|
would improve elapsed time. The simple every-node scan remains enabled by
|
||||||
default for the measured public benchmark benefit.
|
default for the measured public benchmark benefit.
|
||||||
|
|
||||||
|
## Component-area pruning
|
||||||
|
|
||||||
|
In a skyline, every non-full column is empty from its filled height to the top
|
||||||
|
of the board. Adjacent non-full columns therefore belong to the same empty
|
||||||
|
component, while a full-height column is an impassable separator. Each
|
||||||
|
remaining square must lie wholly within one such component, so every component
|
||||||
|
area must be the sum of a bounded subset of the remaining square areas.
|
||||||
|
|
||||||
|
The `gcd` mode first rejects a component area which is not divisible by the
|
||||||
|
greatest common divisor of all remaining square areas. The `subset-sum` mode
|
||||||
|
then computes exact reachable areas using each remaining multiplicity as a
|
||||||
|
bound. Each component is checked against the same reachable set; this is a
|
||||||
|
necessary condition, not a claim that independently selected subsets are
|
||||||
|
mutually disjoint.
|
||||||
|
|
||||||
|
The check is triggered only after a placement reaches full board height and
|
||||||
|
can create or extend a component boundary. This keeps the potentially more
|
||||||
|
expensive bounded subset sum off ordinary nodes. Instrumented output records
|
||||||
|
`component_area_checks`, `component_area_prunes`, `component_gcd_prunes`, and
|
||||||
|
`component_subset_prunes`. The three `--component-pruning` modes allow the
|
||||||
|
trigger cost, cheap gcd rule, and bounded subset sum to be compared directly.
|
||||||
|
Use `--component-schedule periodic-8` to compare the event-driven boundary
|
||||||
|
trigger with checking every eighth placement depth.
|
||||||
|
|
||||||
|
Measurements used the issue #13 working tree based on commit `220cec0`, Apple
|
||||||
|
Clang 21.0.0, `-O3 -DNDEBUG`, macOS arm64, one worker, one warm-up, and seven
|
||||||
|
measured repetitions for the boundary-trigger modes. Counter-free and counted
|
||||||
|
runs were interleaved; the table reports counter-free medians. The existing
|
||||||
|
valley-capacity and large-square rules remained enabled. All results passed
|
||||||
|
independent validation and counters were stable:
|
||||||
|
|
||||||
|
| Order | Component rule | Median solve | Nodes | Checks | Prunes |
|
||||||
|
| --- | --- | ---: | ---: | ---: | ---: |
|
||||||
|
| 7 exhaustive | disabled | 0.985 s | 13,221,239 | 0 | 0 |
|
||||||
|
| 7 exhaustive | gcd | 1.027 s | 13,220,729 | 171,088 | 2,568 |
|
||||||
|
| 7 exhaustive | subset sum | 1.052 s | 13,189,961 | 161,706 | 69,483 |
|
||||||
|
| 8 first solution | disabled | 0.203 s | 2,597,678 | 0 | 0 |
|
||||||
|
| 8 first solution | gcd | 0.212 s | 2,597,548 | 23,943 | 637 |
|
||||||
|
| 8 first solution | subset sum | 0.219 s | 2,592,212 | 23,097 | 11,858 |
|
||||||
|
|
||||||
|
Gcd-only checking changed fewer than 0.005% of nodes while slowing the
|
||||||
|
counter-free solver by 4.3% for both orders. Bounded subset sum reduced nodes
|
||||||
|
by only 0.24% for order 7 and 0.21% for order 8, while slowing them by 6.8% and
|
||||||
|
7.5%. Neither rule is enabled by default.
|
||||||
|
|
||||||
|
The boundary trigger is an incremental event check: it runs only when the
|
||||||
|
latest placement reaches full height and can change the component partition.
|
||||||
|
For comparison, subset sum was also sampled every eighth placement depth with
|
||||||
|
one warm-up and three measured repetitions. Periodic checking made 1,501,035
|
||||||
|
checks for order 7 and 290,774 for order 8, versus 161,706 and 23,097 at
|
||||||
|
boundary events. Its counter-free medians were 1.088 s and 0.227 s, 10.5% and
|
||||||
|
11.7% slower than disabled pruning and materially worse than boundary
|
||||||
|
triggering. The periodic schedule is retained only as an opt-in measurement
|
||||||
|
mode; event-triggered checking is the cheaper schedule if component pruning is
|
||||||
|
reconsidered with new evidence.
|
||||||
|
|
||||||
## 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
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ if(BUILD_TESTING)
|
|||||||
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)
|
add_test(NAME valley-capacity COMMAND partridge_tests valley-capacity)
|
||||||
add_test(NAME large-square COMMAND partridge_tests large-square)
|
add_test(NAME large-square COMMAND partridge_tests large-square)
|
||||||
|
add_test(NAME component-area COMMAND partridge_tests component-area)
|
||||||
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
|
||||||
|
|||||||
+62
-7
@@ -30,6 +30,8 @@ namespace {
|
|||||||
SearchPolicy policy, bool direct_search,
|
SearchPolicy policy, bool direct_search,
|
||||||
SymmetryBreaking symmetry,
|
SymmetryBreaking symmetry,
|
||||||
Pruning pruning,
|
Pruning pruning,
|
||||||
|
ComponentPruning component_pruning,
|
||||||
|
ComponentSchedule component_schedule,
|
||||||
SearchCounters &counters)
|
SearchCounters &counters)
|
||||||
-> TimedSolution {
|
-> TimedSolution {
|
||||||
auto const predecessor_order =
|
auto const predecessor_order =
|
||||||
@@ -38,8 +40,11 @@ namespace {
|
|||||||
auto predecessor =
|
auto predecessor =
|
||||||
instrument
|
instrument
|
||||||
? search_solution_instrumented(predecessor_order, counters,
|
? search_solution_instrumented(predecessor_order, counters,
|
||||||
policy, symmetry, pruning)
|
policy, symmetry, pruning,
|
||||||
: search_solution(predecessor_order, policy, symmetry, pruning);
|
component_pruning,
|
||||||
|
component_schedule)
|
||||||
|
: search_solution(predecessor_order, policy, symmetry, pruning,
|
||||||
|
component_pruning, component_schedule);
|
||||||
auto const search_end = Clock::now();
|
auto const search_end = Clock::now();
|
||||||
|
|
||||||
auto const construction_begin = Clock::now();
|
auto const construction_begin = Clock::now();
|
||||||
@@ -103,13 +108,30 @@ namespace {
|
|||||||
assert(false);
|
assert(false);
|
||||||
return "none";
|
return "none";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto component_pruning_name(ComponentPruning const pruning) -> char const * {
|
||||||
|
switch (pruning) {
|
||||||
|
case ComponentPruning::disabled: return "none";
|
||||||
|
case ComponentPruning::gcd: return "gcd";
|
||||||
|
case ComponentPruning::subset_sum: return "subset-sum";
|
||||||
|
}
|
||||||
|
assert(false);
|
||||||
|
return "none";
|
||||||
|
}
|
||||||
|
|
||||||
|
auto component_schedule_name(ComponentSchedule const schedule)
|
||||||
|
-> char const * {
|
||||||
|
return schedule == ComponentSchedule::boundary ? "boundary"
|
||||||
|
: "periodic-8";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, char **argv) {
|
int main(int argc, char **argv) {
|
||||||
if (argc < 3 || argc > 7) {
|
if (argc < 3 || argc > 9) {
|
||||||
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] [all|valley-capacity|large-square|none]\n";
|
"[d4|none] [all|valley-capacity|large-square|none] "
|
||||||
|
"[subset-sum|gcd|none] [boundary|periodic-8]\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));
|
||||||
@@ -141,7 +163,7 @@ int main(int argc, char **argv) {
|
|||||||
argc < 6 || std::string_view(argv[5]) == "d4"
|
argc < 6 || std::string_view(argv[5]) == "d4"
|
||||||
? SymmetryBreaking::d4_unit_square
|
? SymmetryBreaking::d4_unit_square
|
||||||
: SymmetryBreaking::disabled;
|
: SymmetryBreaking::disabled;
|
||||||
if (argc == 6 && std::string_view(argv[5]) != "d4" &&
|
if (argc >= 6 && std::string_view(argv[5]) != "d4" &&
|
||||||
std::string_view(argv[5]) != "none") {
|
std::string_view(argv[5]) != "none") {
|
||||||
std::cerr << "symmetry mode must be d4 or none\n";
|
std::cerr << "symmetry mode must be d4 or none\n";
|
||||||
return 2;
|
return 2;
|
||||||
@@ -154,7 +176,7 @@ int main(int argc, char **argv) {
|
|||||||
: std::string_view(argv[6]) == "large-square"
|
: std::string_view(argv[6]) == "large-square"
|
||||||
? Pruning::large_square
|
? Pruning::large_square
|
||||||
: Pruning::disabled;
|
: Pruning::disabled;
|
||||||
if (argc == 7 && std::string_view(argv[6]) != "all" &&
|
if (argc >= 7 && std::string_view(argv[6]) != "all" &&
|
||||||
std::string_view(argv[6]) != "valley-capacity" &&
|
std::string_view(argv[6]) != "valley-capacity" &&
|
||||||
std::string_view(argv[6]) != "large-square" &&
|
std::string_view(argv[6]) != "large-square" &&
|
||||||
std::string_view(argv[6]) != "none") {
|
std::string_view(argv[6]) != "none") {
|
||||||
@@ -162,11 +184,32 @@ int main(int argc, char **argv) {
|
|||||||
"large-square, or none\n";
|
"large-square, or none\n";
|
||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
|
auto const component_pruning =
|
||||||
|
argc < 8 || std::string_view(argv[7]) == "none"
|
||||||
|
? ComponentPruning::disabled
|
||||||
|
: std::string_view(argv[7]) == "gcd"
|
||||||
|
? ComponentPruning::gcd
|
||||||
|
: ComponentPruning::subset_sum;
|
||||||
|
if (argc >= 8 && std::string_view(argv[7]) != "subset-sum" &&
|
||||||
|
std::string_view(argv[7]) != "gcd" &&
|
||||||
|
std::string_view(argv[7]) != "none") {
|
||||||
|
std::cerr << "component pruning mode must be subset-sum, gcd, or none\n";
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
auto const component_schedule =
|
||||||
|
argc < 9 || std::string_view(argv[8]) == "boundary"
|
||||||
|
? ComponentSchedule::boundary
|
||||||
|
: ComponentSchedule::periodic_eight;
|
||||||
|
if (argc == 9 && std::string_view(argv[8]) != "boundary" &&
|
||||||
|
std::string_view(argv[8]) != "periodic-8") {
|
||||||
|
std::cerr << "component schedule must be boundary or periodic-8\n";
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
SearchCounters counters;
|
SearchCounters counters;
|
||||||
auto timed =
|
auto timed =
|
||||||
solve(order, instrument, policy, direct_search, symmetry, pruning,
|
solve(order, instrument, policy, direct_search, symmetry, pruning,
|
||||||
counters);
|
component_pruning, component_schedule, 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);
|
||||||
@@ -196,6 +239,10 @@ int main(int argc, char **argv) {
|
|||||||
<< "\""
|
<< "\""
|
||||||
<< ",\"pruning\":\""
|
<< ",\"pruning\":\""
|
||||||
<< pruning_name(pruning) << "\""
|
<< pruning_name(pruning) << "\""
|
||||||
|
<< ",\"component_pruning\":\""
|
||||||
|
<< component_pruning_name(component_pruning) << "\""
|
||||||
|
<< ",\"component_schedule\":\""
|
||||||
|
<< component_schedule_name(component_schedule) << "\""
|
||||||
<< ",\"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
|
||||||
@@ -216,6 +263,14 @@ int main(int argc, char **argv) {
|
|||||||
<< counters.large_square_checks
|
<< counters.large_square_checks
|
||||||
<< ",\"large_square_prunes\":"
|
<< ",\"large_square_prunes\":"
|
||||||
<< counters.large_square_prunes
|
<< counters.large_square_prunes
|
||||||
|
<< ",\"component_area_checks\":"
|
||||||
|
<< counters.component_area_checks
|
||||||
|
<< ",\"component_area_prunes\":"
|
||||||
|
<< counters.component_area_prunes
|
||||||
|
<< ",\"component_gcd_prunes\":"
|
||||||
|
<< counters.component_gcd_prunes
|
||||||
|
<< ",\"component_subset_prunes\":"
|
||||||
|
<< counters.component_subset_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;
|
||||||
|
|||||||
+32
-1
@@ -80,7 +80,16 @@ def environment(binary):
|
|||||||
|
|
||||||
|
|
||||||
def run_once(
|
def run_once(
|
||||||
binary, order, mode, search_policy, search_route, symmetry, pruning, timeout
|
binary,
|
||||||
|
order,
|
||||||
|
mode,
|
||||||
|
search_policy,
|
||||||
|
search_route,
|
||||||
|
symmetry,
|
||||||
|
pruning,
|
||||||
|
component_pruning,
|
||||||
|
component_schedule,
|
||||||
|
timeout,
|
||||||
):
|
):
|
||||||
started = time.monotonic()
|
started = time.monotonic()
|
||||||
try:
|
try:
|
||||||
@@ -93,6 +102,8 @@ def run_once(
|
|||||||
search_route,
|
search_route,
|
||||||
symmetry,
|
symmetry,
|
||||||
pruning,
|
pruning,
|
||||||
|
component_pruning,
|
||||||
|
component_schedule,
|
||||||
],
|
],
|
||||||
check=False,
|
check=False,
|
||||||
capture_output=True,
|
capture_output=True,
|
||||||
@@ -249,6 +260,18 @@ def main():
|
|||||||
action="store_true",
|
action="store_true",
|
||||||
help="disable all pruning (compatibility alias for --pruning none)",
|
help="disable all pruning (compatibility alias for --pruning none)",
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--component-pruning",
|
||||||
|
choices=("subset-sum", "gcd", "none"),
|
||||||
|
default="none",
|
||||||
|
help="select component-area pruning strength",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--component-schedule",
|
||||||
|
choices=("boundary", "periodic-8"),
|
||||||
|
default="boundary",
|
||||||
|
help="trigger component checks on boundaries or every eighth depth",
|
||||||
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--measure-overhead",
|
"--measure-overhead",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
@@ -282,6 +305,8 @@ def main():
|
|||||||
"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 args.pruning,
|
"none" if args.no_pruning else args.pruning,
|
||||||
|
"none" if args.no_pruning else args.component_pruning,
|
||||||
|
args.component_schedule,
|
||||||
args.timeout,
|
args.timeout,
|
||||||
)
|
)
|
||||||
for trial in range(args.repetitions):
|
for trial in range(args.repetitions):
|
||||||
@@ -295,6 +320,8 @@ def main():
|
|||||||
"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 args.pruning,
|
"none" if args.no_pruning else args.pruning,
|
||||||
|
"none" if args.no_pruning else args.component_pruning,
|
||||||
|
args.component_schedule,
|
||||||
args.timeout,
|
args.timeout,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -325,6 +352,10 @@ def main():
|
|||||||
"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 args.pruning,
|
"pruning": "none" if args.no_pruning else args.pruning,
|
||||||
|
"component_pruning": (
|
||||||
|
"none" if args.no_pruning else args.component_pruning
|
||||||
|
),
|
||||||
|
"component_schedule": args.component_schedule,
|
||||||
"stdout": "captured; rendered grid suppressed by probe",
|
"stdout": "captured; rendered grid suppressed by probe",
|
||||||
},
|
},
|
||||||
"cases": cases,
|
"cases": cases,
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <numeric>
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
using size_t = std::uint64_t;
|
using size_t = std::uint64_t;
|
||||||
@@ -154,6 +155,10 @@ namespace {
|
|||||||
size_t valley_capacity_prunes = 0;
|
size_t valley_capacity_prunes = 0;
|
||||||
size_t large_square_checks = 0;
|
size_t large_square_checks = 0;
|
||||||
size_t large_square_prunes = 0;
|
size_t large_square_prunes = 0;
|
||||||
|
size_t component_area_checks = 0;
|
||||||
|
size_t component_area_prunes = 0;
|
||||||
|
size_t component_gcd_prunes = 0;
|
||||||
|
size_t component_subset_prunes = 0;
|
||||||
size_t generated_tasks = 0;
|
size_t generated_tasks = 0;
|
||||||
size_t completed_tasks = 0;
|
size_t completed_tasks = 0;
|
||||||
};
|
};
|
||||||
@@ -180,6 +185,24 @@ namespace {
|
|||||||
all = 3,
|
all = 3,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Component-area rule used after a full-height boundary is created. */
|
||||||
|
enum class ComponentPruning {
|
||||||
|
disabled,
|
||||||
|
gcd,
|
||||||
|
subset_sum,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class ComponentSchedule {
|
||||||
|
boundary,
|
||||||
|
periodic_eight,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class ComponentFeasibility {
|
||||||
|
feasible,
|
||||||
|
gcd_failure,
|
||||||
|
subset_sum_failure,
|
||||||
|
};
|
||||||
|
|
||||||
/** 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
|
||||||
@@ -301,13 +324,98 @@ namespace {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Return areas of empty regions separated by full-height columns.
|
||||||
|
*
|
||||||
|
* Every non-full skyline column is empty from its height to the top of the
|
||||||
|
* board. Adjacent non-full columns therefore connect at the top row, while
|
||||||
|
* a full-height column is an impassable separator.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] auto empty_component_areas(
|
||||||
|
std::vector<size_t> const &skyline) -> std::vector<size_t> {
|
||||||
|
std::vector<size_t> areas;
|
||||||
|
size_t area = 0;
|
||||||
|
for (auto const height: skyline) {
|
||||||
|
if (height == skyline.size()) {
|
||||||
|
if (area != 0) {
|
||||||
|
areas.push_back(area);
|
||||||
|
area = 0;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
area += skyline.size() - height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (area != 0) {
|
||||||
|
areas.push_back(area);
|
||||||
|
}
|
||||||
|
return areas;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Check necessary component-area conditions for the remaining squares.
|
||||||
|
*
|
||||||
|
* Every square lies wholly within one empty component, so each component
|
||||||
|
* area must be a sum of a bounded subset of the remaining square areas.
|
||||||
|
* Divisibility by their gcd is a cheaper necessary condition tested first.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] auto component_area_feasibility(
|
||||||
|
std::vector<size_t> const &skyline, Avail const &available,
|
||||||
|
ComponentPruning const pruning) -> ComponentFeasibility {
|
||||||
|
assert(pruning != ComponentPruning::disabled);
|
||||||
|
auto const components = empty_component_areas(skyline);
|
||||||
|
if (components.size() < 2) {
|
||||||
|
return ComponentFeasibility::feasible;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t divisor = 0;
|
||||||
|
for (size_t side = 1; side < available.size(); ++side) {
|
||||||
|
if (available[side] != 0) {
|
||||||
|
divisor = std::gcd(divisor, side * side);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (divisor != 0 &&
|
||||||
|
std::ranges::any_of(components, [divisor](size_t const area) {
|
||||||
|
return area % divisor != 0;
|
||||||
|
})) {
|
||||||
|
return ComponentFeasibility::gcd_failure;
|
||||||
|
}
|
||||||
|
if (pruning == ComponentPruning::gcd) {
|
||||||
|
return ComponentFeasibility::feasible;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto const maximum_area =
|
||||||
|
*std::ranges::max_element(components);
|
||||||
|
std::vector<bool> reachable(maximum_area + 1);
|
||||||
|
reachable[0] = true;
|
||||||
|
for (size_t side = 1; side < available.size(); ++side) {
|
||||||
|
auto const square_area = side * side;
|
||||||
|
for (size_t copy = 0; copy < available[side]; ++copy) {
|
||||||
|
for (auto area = maximum_area; area >= square_area; --area) {
|
||||||
|
if (reachable[area - square_area]) {
|
||||||
|
reachable[area] = true;
|
||||||
|
}
|
||||||
|
if (area == square_area) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (std::ranges::any_of(components, [&reachable](size_t const area) {
|
||||||
|
return !reachable[area];
|
||||||
|
})) {
|
||||||
|
return ComponentFeasibility::subset_sum_failure;
|
||||||
|
}
|
||||||
|
return ComponentFeasibility::feasible;
|
||||||
|
}
|
||||||
|
|
||||||
template<bool Instrument, bool BreakD4Symmetry, bool PruneValleyCapacity,
|
template<bool Instrument, bool BreakD4Symmetry, bool PruneValleyCapacity,
|
||||||
bool PruneLargeSquare>
|
bool PruneLargeSquare, bool PruneComponents>
|
||||||
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,
|
||||||
std::vector<Square> &squares,
|
std::vector<Square> &squares,
|
||||||
SearchCounters *const counters) noexcept -> bool {
|
SearchCounters *const counters,
|
||||||
|
ComponentPruning const component_pruning,
|
||||||
|
ComponentSchedule const component_schedule,
|
||||||
|
bool const check_components) -> bool {
|
||||||
if constexpr (Instrument) {
|
if constexpr (Instrument) {
|
||||||
assert(counters != nullptr);
|
assert(counters != nullptr);
|
||||||
++counters->search_nodes;
|
++counters->search_nodes;
|
||||||
@@ -317,6 +425,33 @@ namespace {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if constexpr (PruneComponents) {
|
||||||
|
auto const should_check =
|
||||||
|
component_schedule == ComponentSchedule::boundary
|
||||||
|
? check_components
|
||||||
|
: squares.size() % 8 == 0;
|
||||||
|
if (should_check) {
|
||||||
|
if constexpr (Instrument) {
|
||||||
|
++counters->prune_checks;
|
||||||
|
++counters->component_area_checks;
|
||||||
|
}
|
||||||
|
auto const feasibility = component_area_feasibility(
|
||||||
|
skyline, available, component_pruning);
|
||||||
|
if (feasibility != ComponentFeasibility::feasible) {
|
||||||
|
if constexpr (Instrument) {
|
||||||
|
++counters->prune_hits;
|
||||||
|
++counters->component_area_prunes;
|
||||||
|
if (feasibility == ComponentFeasibility::gcd_failure) {
|
||||||
|
++counters->component_gcd_prunes;
|
||||||
|
} else {
|
||||||
|
++counters->component_subset_prunes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
auto const valley = smallest_valley(skyline);
|
auto const valley = smallest_valley(skyline);
|
||||||
if constexpr (PruneValleyCapacity) {
|
if constexpr (PruneValleyCapacity) {
|
||||||
if constexpr (Instrument) {
|
if constexpr (Instrument) {
|
||||||
@@ -377,8 +512,10 @@ namespace {
|
|||||||
squares.emplace_back(valley.x + valley.height * length, side);
|
squares.emplace_back(valley.x + valley.height * length, side);
|
||||||
|
|
||||||
if (search_skyline<Instrument, BreakD4Symmetry, PruneValleyCapacity,
|
if (search_skyline<Instrument, BreakD4Symmetry, PruneValleyCapacity,
|
||||||
PruneLargeSquare>(
|
PruneLargeSquare, PruneComponents>(
|
||||||
n, length, policy, skyline, available, squares, counters)) {
|
n, length, policy, skyline, available, squares, counters,
|
||||||
|
component_pruning, component_schedule,
|
||||||
|
valley.height + side == length)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -425,9 +562,11 @@ namespace {
|
|||||||
* 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, bool PruneValleyCapacity,
|
template<bool Instrument, bool BreakD4Symmetry, bool PruneValleyCapacity,
|
||||||
bool PruneLargeSquare>
|
bool PruneLargeSquare, bool PruneComponents>
|
||||||
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,
|
||||||
|
ComponentPruning const component_pruning,
|
||||||
|
ComponentSchedule const component_schedule)
|
||||||
-> Results {
|
-> Results {
|
||||||
auto const length = triangle_num(n);
|
auto const length = triangle_num(n);
|
||||||
std::vector<size_t> skyline(length);
|
std::vector<size_t> skyline(length);
|
||||||
@@ -439,34 +578,59 @@ namespace {
|
|||||||
squares.reserve(length);
|
squares.reserve(length);
|
||||||
static_cast<void>(
|
static_cast<void>(
|
||||||
search_skyline<Instrument, BreakD4Symmetry, PruneValleyCapacity,
|
search_skyline<Instrument, BreakD4Symmetry, PruneValleyCapacity,
|
||||||
PruneLargeSquare>(
|
PruneLargeSquare, PruneComponents>(
|
||||||
n, length, policy, skyline, available, squares, counters));
|
n, length, policy, skyline, available, squares, counters,
|
||||||
|
component_pruning, component_schedule, false));
|
||||||
|
|
||||||
return {length, std::move(squares)};
|
return {length, std::move(squares)};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<bool Instrument, bool BreakD4Symmetry, bool PruneComponents>
|
||||||
|
auto search_solution_rules(size_t const n, SearchPolicy const policy,
|
||||||
|
Pruning const pruning,
|
||||||
|
SearchCounters *const counters,
|
||||||
|
ComponentPruning const component_pruning,
|
||||||
|
ComponentSchedule const component_schedule)
|
||||||
|
-> Results {
|
||||||
|
switch (pruning) {
|
||||||
|
case Pruning::all:
|
||||||
|
return search_solution_impl<Instrument, BreakD4Symmetry, true, true,
|
||||||
|
PruneComponents>(
|
||||||
|
n, policy, counters, component_pruning, component_schedule);
|
||||||
|
case Pruning::valley_capacity:
|
||||||
|
return search_solution_impl<Instrument, BreakD4Symmetry, true, false,
|
||||||
|
PruneComponents>(
|
||||||
|
n, policy, counters, component_pruning, component_schedule);
|
||||||
|
case Pruning::large_square:
|
||||||
|
return search_solution_impl<Instrument, BreakD4Symmetry, false, true,
|
||||||
|
PruneComponents>(
|
||||||
|
n, policy, counters, component_pruning, component_schedule);
|
||||||
|
case Pruning::disabled:
|
||||||
|
return search_solution_impl<Instrument, BreakD4Symmetry, false, false,
|
||||||
|
PruneComponents>(
|
||||||
|
n, policy, counters, component_pruning, component_schedule);
|
||||||
|
}
|
||||||
|
assert(false);
|
||||||
|
return search_solution_impl<Instrument, BreakD4Symmetry, false, false,
|
||||||
|
PruneComponents>(
|
||||||
|
n, policy, counters, component_pruning, component_schedule);
|
||||||
|
}
|
||||||
|
|
||||||
template<bool Instrument, bool BreakD4Symmetry>
|
template<bool Instrument, bool BreakD4Symmetry>
|
||||||
auto search_solution_dispatch(size_t const n, SearchPolicy const policy,
|
auto search_solution_dispatch(size_t const n, SearchPolicy const policy,
|
||||||
Pruning const pruning,
|
Pruning const pruning,
|
||||||
SearchCounters *const counters) noexcept
|
SearchCounters *const counters,
|
||||||
|
ComponentPruning const component_pruning,
|
||||||
|
ComponentSchedule const component_schedule)
|
||||||
-> Results {
|
-> Results {
|
||||||
switch (pruning) {
|
if (component_pruning == ComponentPruning::disabled) {
|
||||||
case Pruning::all:
|
return search_solution_rules<Instrument, BreakD4Symmetry, false>(
|
||||||
return search_solution_impl<Instrument, BreakD4Symmetry, true, true>(
|
n, policy, pruning, counters, component_pruning,
|
||||||
n, policy, counters);
|
component_schedule);
|
||||||
case Pruning::valley_capacity:
|
|
||||||
return search_solution_impl<Instrument, BreakD4Symmetry, true, false>(
|
|
||||||
n, policy, counters);
|
|
||||||
case Pruning::large_square:
|
|
||||||
return search_solution_impl<Instrument, BreakD4Symmetry, false, true>(
|
|
||||||
n, policy, counters);
|
|
||||||
case Pruning::disabled:
|
|
||||||
return search_solution_impl<Instrument, BreakD4Symmetry, false, false>(
|
|
||||||
n, policy, counters);
|
|
||||||
}
|
}
|
||||||
assert(false);
|
return search_solution_rules<Instrument, BreakD4Symmetry, true>(
|
||||||
return search_solution_impl<Instrument, BreakD4Symmetry, false, false>(
|
n, policy, pruning, counters, component_pruning,
|
||||||
n, policy, counters);
|
component_schedule);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto search_solution(
|
auto search_solution(
|
||||||
@@ -474,14 +638,20 @@ namespace {
|
|||||||
SearchPolicy const policy = SearchPolicy::ascending,
|
SearchPolicy const policy = SearchPolicy::ascending,
|
||||||
SymmetryBreaking const symmetry =
|
SymmetryBreaking const symmetry =
|
||||||
SymmetryBreaking::d4_unit_square,
|
SymmetryBreaking::d4_unit_square,
|
||||||
Pruning const pruning = Pruning::all) noexcept
|
Pruning const pruning = Pruning::all,
|
||||||
|
ComponentPruning const component_pruning =
|
||||||
|
ComponentPruning::disabled,
|
||||||
|
ComponentSchedule const component_schedule =
|
||||||
|
ComponentSchedule::boundary)
|
||||||
-> Results {
|
-> Results {
|
||||||
if (symmetry == SymmetryBreaking::d4_unit_square) {
|
if (symmetry == SymmetryBreaking::d4_unit_square) {
|
||||||
return search_solution_dispatch<false, true>(
|
return search_solution_dispatch<false, true>(
|
||||||
n, policy, pruning, nullptr);
|
n, policy, pruning, nullptr, component_pruning,
|
||||||
|
component_schedule);
|
||||||
}
|
}
|
||||||
return search_solution_dispatch<false, false>(
|
return search_solution_dispatch<false, false>(
|
||||||
n, policy, pruning, nullptr);
|
n, policy, pruning, nullptr, component_pruning,
|
||||||
|
component_schedule);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto search_solution_instrumented(size_t const n,
|
auto search_solution_instrumented(size_t const n,
|
||||||
@@ -491,15 +661,21 @@ namespace {
|
|||||||
SymmetryBreaking const symmetry =
|
SymmetryBreaking const symmetry =
|
||||||
SymmetryBreaking::d4_unit_square,
|
SymmetryBreaking::d4_unit_square,
|
||||||
Pruning const pruning =
|
Pruning const pruning =
|
||||||
Pruning::all) noexcept
|
Pruning::all,
|
||||||
|
ComponentPruning const component_pruning =
|
||||||
|
ComponentPruning::disabled,
|
||||||
|
ComponentSchedule const component_schedule =
|
||||||
|
ComponentSchedule::boundary)
|
||||||
-> Results {
|
-> Results {
|
||||||
counters = {};
|
counters = {};
|
||||||
if (symmetry == SymmetryBreaking::d4_unit_square) {
|
if (symmetry == SymmetryBreaking::d4_unit_square) {
|
||||||
return search_solution_dispatch<true, true>(
|
return search_solution_dispatch<true, true>(
|
||||||
n, policy, pruning, &counters);
|
n, policy, pruning, &counters, component_pruning,
|
||||||
|
component_schedule);
|
||||||
}
|
}
|
||||||
return search_solution_dispatch<true, false>(
|
return search_solution_dispatch<true, false>(
|
||||||
n, policy, pruning, &counters);
|
n, policy, pruning, &counters, component_pruning,
|
||||||
|
component_schedule);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Construct an odd-order solution from its even-order predecessor. */
|
/** Construct an odd-order solution from its even-order predecessor. */
|
||||||
@@ -537,12 +713,19 @@ namespace {
|
|||||||
SearchPolicy const policy = SearchPolicy::ascending,
|
SearchPolicy const policy = SearchPolicy::ascending,
|
||||||
SymmetryBreaking const symmetry =
|
SymmetryBreaking const symmetry =
|
||||||
SymmetryBreaking::d4_unit_square,
|
SymmetryBreaking::d4_unit_square,
|
||||||
Pruning const pruning = Pruning::all) noexcept -> Results {
|
Pruning const pruning = Pruning::all,
|
||||||
|
ComponentPruning const component_pruning =
|
||||||
|
ComponentPruning::disabled,
|
||||||
|
ComponentSchedule const component_schedule =
|
||||||
|
ComponentSchedule::boundary) -> 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, pruning));
|
n, search_solution(
|
||||||
|
n - 1, policy, symmetry, pruning, component_pruning,
|
||||||
|
component_schedule));
|
||||||
}
|
}
|
||||||
return search_solution(n, policy, symmetry, pruning);
|
return search_solution(n, policy, symmetry, pruning, component_pruning,
|
||||||
|
component_schedule);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto find_solution_instrumented(size_t const n,
|
auto find_solution_instrumented(size_t const n,
|
||||||
@@ -552,15 +735,21 @@ namespace {
|
|||||||
SymmetryBreaking const symmetry =
|
SymmetryBreaking const symmetry =
|
||||||
SymmetryBreaking::d4_unit_square,
|
SymmetryBreaking::d4_unit_square,
|
||||||
Pruning const pruning =
|
Pruning const pruning =
|
||||||
Pruning::all) noexcept
|
Pruning::all,
|
||||||
|
ComponentPruning const component_pruning =
|
||||||
|
ComponentPruning::disabled,
|
||||||
|
ComponentSchedule const component_schedule =
|
||||||
|
ComponentSchedule::boundary)
|
||||||
-> 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, pruning));
|
n - 1, counters, policy, symmetry, pruning,
|
||||||
|
component_pruning, component_schedule));
|
||||||
}
|
}
|
||||||
return search_solution_instrumented(
|
return search_solution_instrumented(
|
||||||
n, counters, policy, symmetry, pruning);
|
n, counters, policy, symmetry, pruning, component_pruning,
|
||||||
|
component_schedule);
|
||||||
}
|
}
|
||||||
} // anon namespace
|
} // anon namespace
|
||||||
|
|
||||||
|
|||||||
+110
-8
@@ -371,6 +371,11 @@ namespace {
|
|||||||
counters.valley_capacity_prunes &&
|
counters.valley_capacity_prunes &&
|
||||||
counters.large_square_checks >=
|
counters.large_square_checks >=
|
||||||
counters.large_square_prunes &&
|
counters.large_square_prunes &&
|
||||||
|
counters.component_area_checks >=
|
||||||
|
counters.component_area_prunes &&
|
||||||
|
counters.component_area_prunes ==
|
||||||
|
counters.component_gcd_prunes +
|
||||||
|
counters.component_subset_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");
|
||||||
@@ -431,10 +436,12 @@ 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, Pruning::disabled);
|
SymmetryBreaking::d4_unit_square, Pruning::disabled,
|
||||||
|
ComponentPruning::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, Pruning::disabled);
|
SymmetryBreaking::disabled, Pruning::disabled,
|
||||||
|
ComponentPruning::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(
|
||||||
@@ -557,10 +564,12 @@ namespace {
|
|||||||
SearchCounters unpruned_counters;
|
SearchCounters unpruned_counters;
|
||||||
auto const pruned = search_solution_instrumented(
|
auto const pruned = search_solution_instrumented(
|
||||||
order, pruned_counters, SearchPolicy::ascending,
|
order, pruned_counters, SearchPolicy::ascending,
|
||||||
SymmetryBreaking::disabled, Pruning::valley_capacity);
|
SymmetryBreaking::disabled, Pruning::valley_capacity,
|
||||||
|
ComponentPruning::disabled);
|
||||||
auto const unpruned = search_solution_instrumented(
|
auto const unpruned = search_solution_instrumented(
|
||||||
order, unpruned_counters, SearchPolicy::ascending,
|
order, unpruned_counters, SearchPolicy::ascending,
|
||||||
SymmetryBreaking::disabled, Pruning::disabled);
|
SymmetryBreaking::disabled, Pruning::disabled,
|
||||||
|
ComponentPruning::disabled);
|
||||||
failures += expect(
|
failures += expect(
|
||||||
pruned.squares().empty() == unpruned.squares().empty(),
|
pruned.squares().empty() == unpruned.squares().empty(),
|
||||||
"valley pruning changed exhaustive order-" +
|
"valley pruning changed exhaustive order-" +
|
||||||
@@ -574,7 +583,7 @@ namespace {
|
|||||||
SearchCounters counters;
|
SearchCounters counters;
|
||||||
static_cast<void>(search_solution_instrumented(
|
static_cast<void>(search_solution_instrumented(
|
||||||
5, counters, SearchPolicy::ascending, SymmetryBreaking::disabled,
|
5, counters, SearchPolicy::ascending, SymmetryBreaking::disabled,
|
||||||
Pruning::valley_capacity));
|
Pruning::valley_capacity, ComponentPruning::disabled));
|
||||||
failures += expect(
|
failures += expect(
|
||||||
counters.valley_capacity_checks > 0 &&
|
counters.valley_capacity_checks > 0 &&
|
||||||
counters.valley_capacity_prunes > 0 &&
|
counters.valley_capacity_prunes > 0 &&
|
||||||
@@ -622,10 +631,12 @@ namespace {
|
|||||||
SearchCounters baseline_counters;
|
SearchCounters baseline_counters;
|
||||||
auto const pruned = search_solution_instrumented(
|
auto const pruned = search_solution_instrumented(
|
||||||
order, pruned_counters, SearchPolicy::ascending,
|
order, pruned_counters, SearchPolicy::ascending,
|
||||||
SymmetryBreaking::disabled, Pruning::all);
|
SymmetryBreaking::disabled, Pruning::all,
|
||||||
|
ComponentPruning::disabled);
|
||||||
auto const baseline = search_solution_instrumented(
|
auto const baseline = search_solution_instrumented(
|
||||||
order, baseline_counters, SearchPolicy::ascending,
|
order, baseline_counters, SearchPolicy::ascending,
|
||||||
SymmetryBreaking::disabled, Pruning::valley_capacity);
|
SymmetryBreaking::disabled, Pruning::valley_capacity,
|
||||||
|
ComponentPruning::disabled);
|
||||||
failures += expect(
|
failures += expect(
|
||||||
pruned.squares().empty() == baseline.squares().empty(),
|
pruned.squares().empty() == baseline.squares().empty(),
|
||||||
"large-square pruning changed exhaustive order-" +
|
"large-square pruning changed exhaustive order-" +
|
||||||
@@ -639,7 +650,7 @@ namespace {
|
|||||||
SearchCounters counters;
|
SearchCounters counters;
|
||||||
static_cast<void>(search_solution_instrumented(
|
static_cast<void>(search_solution_instrumented(
|
||||||
5, counters, SearchPolicy::ascending, SymmetryBreaking::disabled,
|
5, counters, SearchPolicy::ascending, SymmetryBreaking::disabled,
|
||||||
Pruning::large_square));
|
Pruning::large_square, ComponentPruning::disabled));
|
||||||
failures += expect(
|
failures += expect(
|
||||||
counters.large_square_checks > 0 &&
|
counters.large_square_checks > 0 &&
|
||||||
counters.large_square_prunes > 0 &&
|
counters.large_square_prunes > 0 &&
|
||||||
@@ -649,6 +660,94 @@ namespace {
|
|||||||
return failures;
|
return failures;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto test_component_area_pruning() -> int {
|
||||||
|
int failures = 0;
|
||||||
|
auto const components =
|
||||||
|
empty_component_areas(std::vector<std::uint64_t>{2, 4, 1, 1});
|
||||||
|
failures += expect(
|
||||||
|
components == std::vector<std::uint64_t>{2, 6},
|
||||||
|
"full-height boundary did not split the expected component areas");
|
||||||
|
failures += expect(
|
||||||
|
empty_component_areas(
|
||||||
|
std::vector<std::uint64_t>{2, 1, 4, 4}) ==
|
||||||
|
std::vector<std::uint64_t>{5},
|
||||||
|
"adjacent non-full columns did not remain one empty component");
|
||||||
|
|
||||||
|
Avail gcd_available(3);
|
||||||
|
gcd_available[2] = 2;
|
||||||
|
failures += expect(
|
||||||
|
component_area_feasibility(
|
||||||
|
std::vector<std::uint64_t>{2, 4, 1, 1}, gcd_available,
|
||||||
|
ComponentPruning::gcd) ==
|
||||||
|
ComponentFeasibility::gcd_failure,
|
||||||
|
"component gcd check accepted indivisible component areas");
|
||||||
|
|
||||||
|
Avail subset_available(3);
|
||||||
|
subset_available[1] = 1;
|
||||||
|
subset_available[2] = 1;
|
||||||
|
failures += expect(
|
||||||
|
component_area_feasibility(
|
||||||
|
std::vector<std::uint64_t>{2, 4, 1, 4}, subset_available,
|
||||||
|
ComponentPruning::gcd) ==
|
||||||
|
ComponentFeasibility::feasible,
|
||||||
|
"component gcd check rejected a gcd-one state");
|
||||||
|
failures += expect(
|
||||||
|
component_area_feasibility(
|
||||||
|
std::vector<std::uint64_t>{2, 4, 1, 4}, subset_available,
|
||||||
|
ComponentPruning::subset_sum) ==
|
||||||
|
ComponentFeasibility::subset_sum_failure,
|
||||||
|
"bounded subset sum accepted unreachable component areas");
|
||||||
|
failures += expect(
|
||||||
|
component_area_feasibility(
|
||||||
|
std::vector<std::uint64_t>{3, 4, 0, 4}, subset_available,
|
||||||
|
ComponentPruning::subset_sum) ==
|
||||||
|
ComponentFeasibility::feasible,
|
||||||
|
"bounded subset sum rejected reachable component areas");
|
||||||
|
|
||||||
|
for (auto const order: std::array<std::uint64_t, 5>{1, 2, 3, 4, 5}) {
|
||||||
|
SearchCounters pruned_counters;
|
||||||
|
SearchCounters periodic_counters;
|
||||||
|
SearchCounters baseline_counters;
|
||||||
|
auto const pruned = search_solution_instrumented(
|
||||||
|
order, pruned_counters, SearchPolicy::ascending,
|
||||||
|
SymmetryBreaking::disabled, Pruning::all,
|
||||||
|
ComponentPruning::subset_sum);
|
||||||
|
auto const periodic = search_solution_instrumented(
|
||||||
|
order, periodic_counters, SearchPolicy::ascending,
|
||||||
|
SymmetryBreaking::disabled, Pruning::all,
|
||||||
|
ComponentPruning::subset_sum,
|
||||||
|
ComponentSchedule::periodic_eight);
|
||||||
|
auto const baseline = search_solution_instrumented(
|
||||||
|
order, baseline_counters, SearchPolicy::ascending,
|
||||||
|
SymmetryBreaking::disabled, Pruning::all,
|
||||||
|
ComponentPruning::disabled);
|
||||||
|
failures += expect(
|
||||||
|
pruned.squares().empty() == baseline.squares().empty() &&
|
||||||
|
periodic.squares().empty() == baseline.squares().empty(),
|
||||||
|
"component pruning changed exhaustive order-" +
|
||||||
|
std::to_string(order) + " feasibility");
|
||||||
|
failures += expect(
|
||||||
|
pruned_counters.search_nodes <= baseline_counters.search_nodes &&
|
||||||
|
periodic_counters.search_nodes <=
|
||||||
|
baseline_counters.search_nodes,
|
||||||
|
"component 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::disabled, ComponentPruning::subset_sum));
|
||||||
|
failures += expect(
|
||||||
|
counters.component_area_checks > 0 &&
|
||||||
|
counters.component_area_prunes > 0 &&
|
||||||
|
counters.component_area_prunes ==
|
||||||
|
counters.component_gcd_prunes +
|
||||||
|
counters.component_subset_prunes,
|
||||||
|
"component-area instrumentation did not count checks and prune kinds");
|
||||||
|
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}) {
|
||||||
@@ -703,6 +802,9 @@ int main(int argc, char **argv) {
|
|||||||
if (test == "large-square") {
|
if (test == "large-square") {
|
||||||
return test_large_square_pruning();
|
return test_large_square_pruning();
|
||||||
}
|
}
|
||||||
|
if (test == "component-area") {
|
||||||
|
return test_component_area_pruning();
|
||||||
|
}
|
||||||
std::cerr << "unknown test: " << test << '\n';
|
std::cerr << "unknown test: " << test << '\n';
|
||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user