A skyline may retain enough total empty area while no longer containing a box for its largest remaining square. Scan for the required consecutive low columns and reject such monotonic dead states. Keep each pruning combination independently measurable and record the small public-path gain, the direct-order-9 regression, and the rejected periodic schedule. Tests: Debug CTest (13 passed) Tests: ASan+UBSan CTest (13 passed) Refs: #15
223 lines
7.9 KiB
C++
223 lines
7.9 KiB
C++
/*
|
|
* Copyright 2026, Matthew-Gretton-Dann
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#define PARTRIDGE_TESTING
|
|
#include "../main.cc"
|
|
|
|
#include <algorithm>
|
|
#include <chrono>
|
|
#include <cstdint>
|
|
#include <cstdlib>
|
|
#include <sstream>
|
|
#include <string>
|
|
|
|
namespace {
|
|
using Clock = std::chrono::steady_clock;
|
|
|
|
auto seconds(Clock::time_point begin, Clock::time_point end) -> double {
|
|
return std::chrono::duration<double>(end - begin).count();
|
|
}
|
|
|
|
struct TimedSolution {
|
|
Results result;
|
|
double search_seconds;
|
|
double construction_seconds;
|
|
};
|
|
|
|
auto solve(std::uint64_t order, bool instrument,
|
|
SearchPolicy policy, bool direct_search,
|
|
SymmetryBreaking symmetry,
|
|
Pruning pruning,
|
|
SearchCounters &counters)
|
|
-> TimedSolution {
|
|
auto const predecessor_order =
|
|
!direct_search && uses_odd_construction(order) ? order - 1 : order;
|
|
auto const search_begin = Clock::now();
|
|
auto predecessor =
|
|
instrument
|
|
? search_solution_instrumented(predecessor_order, counters,
|
|
policy, symmetry, pruning)
|
|
: search_solution(predecessor_order, policy, symmetry, pruning);
|
|
auto const search_end = Clock::now();
|
|
|
|
auto const construction_begin = Clock::now();
|
|
auto result = !direct_search && uses_odd_construction(order)
|
|
? construct_odd_solution(order, std::move(predecessor))
|
|
: std::move(predecessor);
|
|
auto const construction_end = Clock::now();
|
|
return {
|
|
std::move(result),
|
|
seconds(search_begin, search_end),
|
|
!direct_search && uses_odd_construction(order)
|
|
? seconds(construction_begin, construction_end)
|
|
: 0.0,
|
|
};
|
|
}
|
|
|
|
auto valid(std::uint64_t order, Results const &result) -> bool {
|
|
auto const expected = triangle_num(order);
|
|
if (result.length() != expected) {
|
|
return false;
|
|
}
|
|
|
|
std::vector<std::uint64_t> multiplicities(order + 1);
|
|
std::vector<bool> occupied(expected * expected);
|
|
for (auto const &square: result.squares()) {
|
|
auto const side = square.length();
|
|
auto const x0 = square.pos() % expected;
|
|
auto const y0 = square.pos() / expected;
|
|
if (side == 0 || side > order || x0 + side > expected ||
|
|
y0 + side > expected) {
|
|
return false;
|
|
}
|
|
++multiplicities[side];
|
|
for (auto y = y0; y < y0 + side; ++y) {
|
|
for (auto x = x0; x < x0 + side; ++x) {
|
|
auto &&cell = occupied[x + y * expected];
|
|
if (cell) {
|
|
return false;
|
|
}
|
|
cell = true;
|
|
}
|
|
}
|
|
}
|
|
for (std::uint64_t side = 1; side <= order; ++side) {
|
|
if (multiplicities[side] != side) {
|
|
return result.squares().empty() && order < 8;
|
|
}
|
|
}
|
|
return std::ranges::find(occupied, false) == occupied.end();
|
|
}
|
|
|
|
auto boolean(bool value) -> char const * { return value ? "true" : "false"; }
|
|
|
|
auto pruning_name(Pruning const pruning) -> char const * {
|
|
switch (pruning) {
|
|
case Pruning::all: return "all";
|
|
case Pruning::valley_capacity: return "valley-capacity";
|
|
case Pruning::large_square: return "large-square";
|
|
case Pruning::disabled: return "none";
|
|
}
|
|
assert(false);
|
|
return "none";
|
|
}
|
|
}
|
|
|
|
int main(int argc, char **argv) {
|
|
if (argc < 3 || argc > 7) {
|
|
std::cerr << "usage: partridge_benchmark ORDER counters|plain "
|
|
"[ascending|descending|best-fit] [public|direct] "
|
|
"[d4|none] [all|valley-capacity|large-square|none]\n";
|
|
return 2;
|
|
}
|
|
auto const order = static_cast<std::uint64_t>(std::strtoull(argv[1], nullptr, 10));
|
|
auto const instrument = std::string_view(argv[2]) == "counters";
|
|
if (!instrument && std::string_view(argv[2]) != "plain") {
|
|
std::cerr << "counter mode must be counters or plain\n";
|
|
return 2;
|
|
}
|
|
auto const policy =
|
|
argc < 4 || std::string_view(argv[3]) == "ascending"
|
|
? SearchPolicy::ascending
|
|
: std::string_view(argv[3]) == "descending"
|
|
? SearchPolicy::descending
|
|
: SearchPolicy::best_fit;
|
|
if (argc >= 4 && std::string_view(argv[3]) != "ascending" &&
|
|
std::string_view(argv[3]) != "descending" &&
|
|
std::string_view(argv[3]) != "best-fit") {
|
|
std::cerr << "search policy must be ascending, descending, or best-fit\n";
|
|
return 2;
|
|
}
|
|
auto const direct_search =
|
|
argc >= 5 && std::string_view(argv[4]) == "direct";
|
|
if (argc >= 5 && std::string_view(argv[4]) != "public" &&
|
|
std::string_view(argv[4]) != "direct") {
|
|
std::cerr << "search route must be public or direct\n";
|
|
return 2;
|
|
}
|
|
auto const symmetry =
|
|
argc < 6 || std::string_view(argv[5]) == "d4"
|
|
? SymmetryBreaking::d4_unit_square
|
|
: SymmetryBreaking::disabled;
|
|
if (argc == 6 && std::string_view(argv[5]) != "d4" &&
|
|
std::string_view(argv[5]) != "none") {
|
|
std::cerr << "symmetry mode must be d4 or none\n";
|
|
return 2;
|
|
}
|
|
auto const pruning =
|
|
argc < 7 || std::string_view(argv[6]) == "all"
|
|
? Pruning::all
|
|
: std::string_view(argv[6]) == "valley-capacity"
|
|
? Pruning::valley_capacity
|
|
: std::string_view(argv[6]) == "large-square"
|
|
? Pruning::large_square
|
|
: Pruning::disabled;
|
|
if (argc == 7 && std::string_view(argv[6]) != "all" &&
|
|
std::string_view(argv[6]) != "valley-capacity" &&
|
|
std::string_view(argv[6]) != "large-square" &&
|
|
std::string_view(argv[6]) != "none") {
|
|
std::cerr << "pruning mode must be all, valley-capacity, "
|
|
"large-square, or none\n";
|
|
return 2;
|
|
}
|
|
|
|
SearchCounters counters;
|
|
auto timed =
|
|
solve(order, instrument, policy, direct_search, symmetry, pruning,
|
|
counters);
|
|
|
|
auto const validation_begin = Clock::now();
|
|
auto const validation_ok = valid(order, timed.result);
|
|
auto const validation_end = Clock::now();
|
|
|
|
auto const render_begin = Clock::now();
|
|
std::ostringstream rendered;
|
|
auto *const old_buffer = std::cout.rdbuf(rendered.rdbuf());
|
|
timed.result.output();
|
|
std::cout.rdbuf(old_buffer);
|
|
auto const render_end = Clock::now();
|
|
|
|
std::cout.precision(17);
|
|
std::cout
|
|
<< "{\"schema_version\":1"
|
|
<< ",\"order\":" << order
|
|
<< ",\"instrumented\":" << boolean(instrument)
|
|
<< ",\"candidate_order\":\""
|
|
<< (policy == SearchPolicy::ascending
|
|
? "ascending"
|
|
: policy == SearchPolicy::descending ? "descending" : "best-fit")
|
|
<< "\""
|
|
<< ",\"search_route\":\""
|
|
<< (direct_search ? "direct" : "public") << "\""
|
|
<< ",\"symmetry_breaking\":\""
|
|
<< (symmetry == SymmetryBreaking::d4_unit_square ? "d4" : "none")
|
|
<< "\""
|
|
<< ",\"pruning\":\""
|
|
<< pruning_name(pruning) << "\""
|
|
<< ",\"solved\":" << boolean(!timed.result.squares().empty())
|
|
<< ",\"valid\":" << boolean(validation_ok)
|
|
<< ",\"timing_seconds\":{\"solve\":" << timed.search_seconds
|
|
<< ",\"construction\":" << timed.construction_seconds
|
|
<< ",\"validation\":" << seconds(validation_begin, validation_end)
|
|
<< ",\"render\":" << seconds(render_begin, render_end) << "}"
|
|
<< ",\"counters\":{\"search_nodes\":" << counters.search_nodes
|
|
<< ",\"loop_iterations\":" << counters.loop_iterations
|
|
<< ",\"attempted_placements\":" << counters.attempted_placements
|
|
<< ",\"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
|
|
<< ",\"large_square_checks\":"
|
|
<< counters.large_square_checks
|
|
<< ",\"large_square_prunes\":"
|
|
<< counters.large_square_prunes
|
|
<< ",\"generated_tasks\":" << counters.generated_tasks
|
|
<< ",\"completed_tasks\":" << counters.completed_tasks << "}}\n";
|
|
return validation_ok ? 0 : 1;
|
|
}
|