/* * 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, 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) : search_solution(predecessor_order, policy); 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"; } } int main(int argc, char **argv) { if (argc < 3 || argc > 5) { std::cerr << "usage: partridge_benchmark ORDER counters|plain " "[ascending|descending|best-fit] [public|direct]\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; } SearchCounters counters; auto timed = solve(order, instrument, policy, direct_search, 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") << "\"" << ",\"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 << ",\"generated_tasks\":" << counters.generated_tasks << ",\"completed_tasks\":" << counters.completed_tasks << "}}\n"; return validation_ok ? 0 : 1; }