/* * Copyright 2026, Matthew-Gretton-Dann * SPDX-License-Identifier: Apache-2.0 */ #define PARTRIDGE_TESTING #include "../main.cc" #include #include #include #include #include #include namespace { using Clock = std::chrono::steady_clock; auto seconds(Clock::time_point begin, Clock::time_point end) -> double { return std::chrono::duration(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, ComponentPruning component_pruning, ComponentSchedule component_schedule, 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, component_pruning, component_schedule) : search_solution(predecessor_order, policy, symmetry, pruning, component_pruning, component_schedule); 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 multiplicities(order + 1); std::vector 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"; } 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) { if (argc < 3 || argc > 9) { std::cerr << "usage: partridge_benchmark ORDER counters|plain " "[ascending|descending|best-fit] [public|direct] " "[d4|none] [all|valley-capacity|large-square|none] " "[subset-sum|gcd|none] [boundary|periodic-8]\n"; return 2; } auto const order = static_cast(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; } 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; auto timed = solve(order, instrument, policy, direct_search, symmetry, pruning, component_pruning, component_schedule, 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) << "\"" << ",\"component_pruning\":\"" << component_pruning_name(component_pruning) << "\"" << ",\"component_schedule\":\"" << component_schedule_name(component_schedule) << "\"" << ",\"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 << ",\"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 << ",\"completed_tasks\":" << counters.completed_tasks << "}}\n"; return validation_ok ? 0 : 1; }