From 50c20ad3eea2df0dc5727f5ae7f5a4abb69fde9d Mon Sep 17 00:00:00 2001 From: Tinko Sebastian Bartels Date: Thu, 9 Jan 2025 20:28:59 +0800 Subject: [PATCH] Add statistics, verbosity, fixed bit pattern options to random_integer_grids test. --- .../overlay/areal_areal/CMakeLists.txt | 2 +- test/robustness/overlay/areal_areal/Jamfile | 2 +- .../areal_areal/random_bitset_grids.cpp | 381 ++++++++++++++++++ .../areal_areal/random_integer_grids.cpp | 274 ------------- 4 files changed, 383 insertions(+), 276 deletions(-) create mode 100644 test/robustness/overlay/areal_areal/random_bitset_grids.cpp delete mode 100644 test/robustness/overlay/areal_areal/random_integer_grids.cpp diff --git a/test/robustness/overlay/areal_areal/CMakeLists.txt b/test/robustness/overlay/areal_areal/CMakeLists.txt index 9f0a1b99f..8e4428766 100644 --- a/test/robustness/overlay/areal_areal/CMakeLists.txt +++ b/test/robustness/overlay/areal_areal/CMakeLists.txt @@ -15,7 +15,7 @@ foreach(item IN ITEMS recursive_polygons star_comb #ticket_9081 - #random_integer_grid + random_bitset_grids ) boost_geometry_add_unit_test("robustness" ${item}) target_include_directories(${BOOST_GEOMETRY_UNIT_TEST_NAME} diff --git a/test/robustness/overlay/areal_areal/Jamfile b/test/robustness/overlay/areal_areal/Jamfile index 8e950e97d..71ed82798 100644 --- a/test/robustness/overlay/areal_areal/Jamfile +++ b/test/robustness/overlay/areal_areal/Jamfile @@ -25,4 +25,4 @@ exe recursive_polygons : recursive_polygons.cpp ; exe star_comb : star_comb.cpp ; exe ticket_9081 : ticket_9081.cpp ; -exe random_integer_grids : random_integer_grids.cpp ; +exe random_bitset_grids : random_bitset_grids.cpp ; diff --git a/test/robustness/overlay/areal_areal/random_bitset_grids.cpp b/test/robustness/overlay/areal_areal/random_bitset_grids.cpp new file mode 100644 index 000000000..674a26e23 --- /dev/null +++ b/test/robustness/overlay/areal_areal/random_bitset_grids.cpp @@ -0,0 +1,381 @@ +// Boost.Geometry (aka GGL, Generic Geometry Library) +// Robustness Test + +// Copyright (c) 2025 Tinko Bartels, Shenzhen, China. + +// Use, modification and distribution is subject to the Boost Software License, +// Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#define BOOST_GEOMETRY_NO_BOOST_TEST + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +#define BOOST_GEOMETRY_TEST_ONLY_ONE_TYPE +#ifndef BOOST_GEOMETRY_DEFAULT_TEST_TYPE +#define BOOST_GEOMETRY_DEFAULT_TEST_TYPE int +#endif +#include + +constexpr int chunk_size = 64; +using bits = std::vector>; +using generator_t = std::mt19937_64; +static_assert(sizeof(generator_t::result_type) >= chunk_size / 8, "Generator output too narrow."); + +namespace bg = boost::geometry; + +using point = bg::model::d2::point_xy; +using box = bg::model::box; +using mp_t = bg::model::multi_polygon>; + +struct grid_settings +{ + int width = 5; + int height = 5; + int count = 1; + generator_t::result_type seed = generator_t::default_seed; + bool verbose = false; + std::vector bits1{}; + std::vector bits2{}; +}; + +constexpr int cell_dimension = 2; + +std::vector grid_cells(grid_settings const& settings) +{ + std::vector out; + out.reserve(settings.height * settings.width); + for (int y = 0; y < settings.height; ++y) + { + for (int x = 0; x < settings.width; ++x) + { + out.push_back(box{point(x * cell_dimension, y * cell_dimension), + point((x + 1) * cell_dimension, (y + 1) * cell_dimension)}); + } + } + return out; +} + +std::vector test_points(grid_settings const& settings) +{ + std::vector out; + out.reserve(settings.height * settings.width); + for (int y = 0; y < settings.height; ++y) + { + for (int x = 0; x < settings.width; ++x) + { + out.push_back(point(x * cell_dimension + cell_dimension / 2, + y * cell_dimension + cell_dimension / 2)); + } + } + return out; +} + +std::ostream& operator<<(std::ostream& os, std::pair const& b_gs) +{ + if (b_gs.second.verbose) + { + os << '\n'; + for (int y = b_gs.second.height - 1; y >= 0; --y) + { + for (int x = 0; x < b_gs.second.width; ++x) + { + int index = y * b_gs.second.width + x; + os << b_gs.first[index / chunk_size][index % chunk_size]; + } + os << '\n'; + } + } + else + { + os << '{' << b_gs.first[0].to_ullong(); + for (size_t i = 1; i < b_gs.first.size(); ++i) os << ' ' << b_gs.first[i].to_ullong(); + os << '}'; + } + return os; +} + +bits geometry_to_bits(mp_t const& geometry, std::vector const& test_points) +{ + bits out((test_points.size() + chunk_size - 1) / chunk_size); + for (size_t i = 0; i < test_points.size(); ++i) + { + out[i / chunk_size][i % chunk_size] = bg::within(test_points[i], geometry); + } + return out; +} + +mp_t bits_to_geometry(bits const& b, std::vector const& grid, std::vector const& points, + grid_settings const& settings, std::map& failures) +{ + mp_t out; + for (size_t i = 0; i < grid.size(); ++i) + { + if (b[i / chunk_size][i % chunk_size]) + { + mp_t temp; + bg::union_(out, grid[i], temp); + out = std::move(temp); + } + } + // Convenience lambda to pair bits with settings to use width/height in operator<<(os, ...) + const auto b_gs = [&settings](bits const& b) { return std::make_pair(b, settings); }; + std::string reason{}; + if (! bg::is_valid(out, reason)) + { + if (settings.verbose) + { + std::cout << bg::wkt(out) << "\ngenerated from" << b_gs(b) + << "is invalid: " << reason << ".\n\n"; + } + else std::cout << b_gs(b) << " invalid (" << reason << ")\n"; + ++failures["bits_to_geometry validity"]; + } + if (geometry_to_bits(out, points) != b) + { + if (settings.verbose) + { + std::cout << "Generating grid from pattern" << b_gs(b) + << "results in mismatching geometry: " << bg::wkt(out) << ".\n\n"; + } + else std::cout << b_gs(b) << " mismatch.\n"; + ++failures["bits_to_geometry mismatch"]; + } + return out; +} + +bits gen_bits(generator_t& generator, int bits_size) +{ + bits b((bits_size + chunk_size - 1) / chunk_size); + std::generate(b.begin(), b.end(), std::ref(generator)); + if (bits_size % chunk_size != 0) + { + std::bitset bm; + bm.set(); + bm >>= chunk_size - bits_size % chunk_size; + b.back() &= bm; + } + return b; +} + +bits to_bits(std::vector const& in) +{ + bits out; + out.reserve(in.size()); + for (auto const& ullong : in) out.push_back(std::bitset(ullong)); + return out; +} + +template +bits apply_for_each(bits a, bits const& b, BitOp const& bit_op) +{ + for (size_t i = 0; i < a.size(); ++i) a[i] = bit_op(a[i], b[i]); + return a; +} + +template +void test_op(bits const& bits1, bits const& bits2, mp_t const& geo1, mp_t const& geo2, + std::string const& op_label, BitOp const& bit_op, GeoOp const& geo_op, + std::vector const& test_points, std::vector const& grid, + grid_settings const& settings, std::map& failures) +{ + auto test_geo = geo_op(geo1, geo2); + // Convenience lambda to pair bits with settings to use width/height in operator<<(os, ...) + const auto b_gs = [&settings](bits const& b) { return std::make_pair(b, settings); }; + std::string reason{}; + if (! bg::is_valid(test_geo, reason)) + { + if (settings.verbose) + { + std::cout << op_label << "(\n\t" << bg::wkt(geo1) << ",\n\t " << bg::wkt(geo2) << "\n)," + << "\ngenerated from" << b_gs(bits1) << "and" << b_gs(bits2) << "is invalid: " + << reason << ".\n\n"; + } + else + { + std::cout << op_label << '(' << b_gs(bits1) << ", " << b_gs(bits2) << " invalid (" + << reason << ").\n"; + } + ++failures[op_label + " validity"]; + } + const bits expected = apply_for_each(bits1, bits2, bit_op); + const bits obtained = geometry_to_bits(test_geo, test_points); + if (obtained != expected) + { + if (settings.verbose) + { + std::cout << op_label << "(\n\t" << bg::wkt(geo1) << ",\n\t" << bg::wkt(geo2) << "\n)," + << "\ngenerated from" << b_gs(bits1) << "and" << b_gs(bits2) + << "is incorrect.\nExpected: " + << bg::wkt(bits_to_geometry(expected, grid, test_points, settings, failures)) + << "\ncorresponding to" << b_gs(expected) << "Obtained: " + << bg::wkt(test_geo) << "\ncorresponding to" << b_gs(obtained) << "\n"; + } + else std::cout << op_label << '(' << b_gs(bits1) << ", " << b_gs(bits2) << ") mismatch.\n"; + ++failures[op_label + " mismatch"]; + } +} + +void test_bits(bits const& bits1, bits const& bits2, + std::vector const& grid, std::vector const& test_points, + grid_settings const& settings, std::map& failures) +{ + const auto geo1 = bits_to_geometry(bits1, grid, test_points, settings, failures); + const auto geo2 = bits_to_geometry(bits2, grid, test_points, settings, failures); + test_op(bits1, bits2, geo1, geo2, "union", std::bit_or<>{}, + [](mp_t const& g1, mp_t const& g2) { mp_t g; bg::union_(g1, g2, g); return g; }, + test_points, grid, settings, failures); + test_op(bits1, bits2, geo1, geo2, "intersection", std::bit_and<>{}, + [](mp_t const& g1, mp_t const& g2) { mp_t g; bg::intersection(g1, g2, g); return g; }, + test_points, grid, settings, failures); + test_op(bits1, bits2, geo1, geo2, "sym_difference", std::bit_xor<>{}, + [](mp_t const& g1, mp_t const& g2) { mp_t g; bg::sym_difference(g1, g2, g); return g; }, + test_points, grid, settings, failures); + test_op(bits1, bits2, geo1, geo2, "difference g1 \\ g2", + [](std::bitset b1, std::bitset b2) { return b1 & (~b2); }, + [](mp_t const& g1, mp_t const& g2) { mp_t g; bg::difference(g1, g2, g); return g; }, + test_points, grid, settings, failures); + test_op(bits1, bits2, geo1, geo2, "difference g2 \\ g1", + [](std::bitset b1, std::bitset b2) { return b2 & (~b1); }, + [](mp_t const& g1, mp_t const& g2) { mp_t g; bg::difference(g2, g1, g); return g; }, + test_points, grid, settings, failures); +} + +bool test_all(grid_settings const& settings) +{ + generator_t genenerator(settings.seed); + const auto grid = grid_cells(settings); + const auto points = test_points(settings); + std::map failures; + auto const t0 = std::chrono::high_resolution_clock::now(); + for (int i = 0; i < settings.count || settings.count == -1; i++) + { + const bits bits1 = settings.bits1.size() == 0 ? + gen_bits(genenerator, settings.width * settings.height) + : to_bits(settings.bits1); + const bits bits2 = settings.bits2.size() == 0 ? + gen_bits(genenerator, settings.width * settings.height) + : to_bits(settings.bits2); + test_bits(bits1, bits2, grid, points, settings, failures); + } + auto const t = std::chrono::high_resolution_clock::now(); + auto const elapsed_ms = std::chrono::duration_cast(t - t0).count(); + int failure_count = std::accumulate(failures.begin(), failures.end(), 0, + [](int acc, auto const& kv) { return acc + kv.second; }); + std::cout << "\niterations: " << settings.count + << " errors: " << failure_count + << " time: " << elapsed_ms / 1000.0 << '\n'; + if (failure_count != 0) + { + std::cout << "Failure counts by failure mode:\n"; + for (auto const& fm : failures) std::cout << '\t' << fm.first << ": " << fm.second << '\n'; + } + return failure_count == 0; +} + +bool validate_bits_input(std::vector const& bits_in, size_t bits_size) +{ + if (bits_in.size() == 0) return true; + if (bits_in.size() != (bits_size + chunk_size - 1) / chunk_size) return false; + if (bits_size % chunk_size != 0) + { + std::bitset bm; + bm.set(); + bm >>= chunk_size - bits_size % chunk_size; + if (bits_in.back() & ~bm.to_ullong()) return false; + } + return true; +} + +int main(int argc, char** argv) +{ + BoostGeometryWriteTestConfiguration(); + try + { + namespace po = boost::program_options; + po::options_description description("=== random_integer_grids ===\nAllowed options"); + + grid_settings settings; + + description.add_options() + ("help", "Help message") + ("seed", + po::value(&settings.seed)->default_value(settings.seed), + "Initialization seed for random generator") + ("count", + po::value(&settings.count)->default_value(settings.count), + "Number of tests (-1 for infinite loop)") + ("width", + po::value(&settings.width)->default_value(settings.width), + "Width of grid (>= 1)") + ("height", + po::value(&settings.height)->default_value(settings.height), + "Height of grid (>= 1)") + ("verbose", + po::bool_switch(&settings.verbose), + "Print WKT and bit patterns for each failure.") + ("bits1", + po::value(&settings.bits1)->multitoken(), + "Fixed bit pattern for first operand as list of ullong.") + ("bits2", + po::value(&settings.bits2)->multitoken(), + "Fixed bit pattern for second operand as list of ullong.") + ; + + po::variables_map varmap; + po::store(po::parse_command_line(argc, argv, description), varmap); + po::notify(varmap); + + if (! validate_bits_input(settings.bits1, settings.height * settings.width)) + { + std::cout << "bits1 was provided but does not match dimensions.\n"; + return 1; + } + if (! validate_bits_input(settings.bits2, settings.height * settings.width)) + { + std::cout << "bits2 was provided but does not match dimensions.\n"; + return 1; + } + if (settings.bits1.size() != 0 && settings.bits2.size() != 0 && settings.count != 1) + { + std::cout << "Both bit patterns fixed, count is changed to 1.\n"; + settings.count = 1; + } + if (settings.height < 1 || settings.width < 1) + { + std::cout << "Invalid dimensions, height and width need to be positive.\n"; + return 1; + } + if (varmap.count("help")) + { + std::cout << description << std::endl; + return 1; + } + if (! test_all(settings)) return 1; + } + catch (std::exception const& e) + { + std::cout << "Exception " << e.what() << '\n'; + } + catch (...) + { + std::cout << "Other exception" << '\n'; + } + + return 0; +} diff --git a/test/robustness/overlay/areal_areal/random_integer_grids.cpp b/test/robustness/overlay/areal_areal/random_integer_grids.cpp deleted file mode 100644 index 791f1bdb2..000000000 --- a/test/robustness/overlay/areal_areal/random_integer_grids.cpp +++ /dev/null @@ -1,274 +0,0 @@ -// Boost.Geometry (aka GGL, Generic Geometry Library) -// Robustness Test - -// Copyright (c) 2025 Tinko Bartels, Shenzhen, China. - -// Use, modification and distribution is subject to the Boost Software License, -// Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at -// http://www.boost.org/LICENSE_1_0.txt) - -#define BOOST_GEOMETRY_NO_BOOST_TEST - -#include -#include -#include -#include -#include - -#include - -#include -#include -#include -#include -#include -#include -#include - -#define BOOST_GEOMETRY_TEST_ONLY_ONE_TYPE -#define BOOST_GEOMETRY_DEFAULT_TEST_TYPE int -#include - -constexpr int chunk_size = 64; -using bits = std::vector>; -using generator_t = std::mt19937_64; -static_assert(sizeof(generator_t::result_type) >= chunk_size / 8, "Generator output too narrow."); - -namespace bg = boost::geometry; - -using point = bg::model::d2::point_xy; -using box = bg::model::box; -using mp_t = bg::model::multi_polygon>; - -struct grid_settings -{ - int width = 5; - int height = 5; - int count = 1; - generator_t::result_type seed = generator_t::default_seed; -}; - -constexpr int cell_dimension = 2; - -std::vector grid_cells(grid_settings const& settings) -{ - std::vector out; - for (int y = 0; y < settings.height; ++y) - { - for (int x = 0; x < settings.width; ++x) - { - out.push_back(box{point{x * cell_dimension, y * cell_dimension}, - point{(x + 1) * cell_dimension, (y + 1) * cell_dimension}}); - } - } - return out; -} - -std::vector test_points(grid_settings const& settings) -{ - std::vector out; - for (int y = 0; y < settings.height; ++y) - { - for (int x = 0; x < settings.width; ++x) - { - out.push_back(point{x * cell_dimension + cell_dimension / 2, - y * cell_dimension + cell_dimension / 2}); - } - } - return out; -} - -std::ostream& operator<<(std::ostream& os, std::pair const& b_gs) -{ - os << '\n'; - for (int y = b_gs.second.height - 1; y >= 0; --y) - { - for (int x = 0; x < b_gs.second.width; ++x) - { - int index = y * b_gs.second.width + x; - os << b_gs.first[index / chunk_size][index % chunk_size]; - } - os << '\n'; - } - return os; -} - -bits geometry_to_bits(mp_t const& geometry, std::vector const& test_points) -{ - bits out((test_points.size() + chunk_size - 1) / chunk_size); - for (size_t i = 0; i < test_points.size(); ++i) - { - out[i / chunk_size][i % chunk_size] = bg::within(test_points[i], geometry); - } - return out; -} - -mp_t bits_to_geometry(bits const& b, std::vector const& grid, std::vector const& points, - grid_settings const& settings, bool& all_success) -{ - mp_t out; - for (size_t i = 0; i < grid.size(); ++i) - { - if( b[i / chunk_size][i % chunk_size] ) - { - mp_t temp; - bg::union_(out, grid[i], temp); - out = std::move(temp); - } - } - // Convenience lambda to pair bits with settings to use width/height in operator<<(os, ...) - const auto b_gs = [&settings](bits const& b) { return std::make_pair(b, settings); }; - std::string reason{}; - if (! bg::is_valid(out, reason)) - { - std::cout << bg::wkt(out) << "\ngenerated from" << b_gs(b) - << "is invalid: " << reason << ".\n\n"; - all_success = false; - } - if (geometry_to_bits(out, points) != b) - { - std::cout << "Generating grid from pattern" << b_gs(b) - << "results in mismatching geometry: " << bg::wkt(out) << ".\n\n"; - all_success = false; - } - return out; -} - -bits gen_bits(generator_t& generator, int bits_size) -{ - bits b((bits_size + chunk_size - 1) / chunk_size); - std::generate(b.begin(), b.end(), std::ref(generator)); - if (bits_size % chunk_size != 0) - { - std::bitset bm; - bm.set(); - bm >>= chunk_size - bits_size % chunk_size; - b.back() &= bm; - } - return b; -} - -template -bits apply_for_each(bits a, bits const& b, BitOp const& bit_op) -{ - for (size_t i = 0; i < a.size(); ++i) a[i] = bit_op(a[i], b[i]); - return a; -} - -template -void test_op(bits const& bits1, bits const& bits2, mp_t const& geo1, mp_t const& geo2, - std::string const& op_label, BitOp const& bit_op, GeoOp const& geo_op, - std::vector const& test_points, std::vector const& grid, - grid_settings const& settings, bool& success) -{ - auto test_geo = geo_op(geo1, geo2); - // Convenience lambda to pair bits with settings to use width/height in operator<<(os, ...) - const auto b_gs = [&settings](bits const& b) { return std::make_pair(b, settings); }; - std::string reason{}; - if (! bg::is_valid(test_geo, reason)) - { - std::cout << op_label << "(\n\t " << bg::wkt(geo1) << ",\n\t " << bg::wkt(geo2) << "\n),\n" - << "generated from" << b_gs(bits1) << "and" << b_gs(bits2) << "is invalid: " - << reason << ".\n\n"; - success = false; - } - const bits expected = apply_for_each(bits1, bits2, bit_op); - const bits obtained = geometry_to_bits(test_geo, test_points); - if (obtained != expected) - { - std::cout << op_label << "(\n\t" << bg::wkt(geo1) << ",\n\t" << bg::wkt(geo2) << "\n),\n" - << "generated from" << b_gs(bits1) << "and" << b_gs(bits2) - << "is incorrect.\nExpected: " - << bg::wkt(bits_to_geometry(expected, grid, test_points, settings, success)) - << "\ncorresponding to" << b_gs(expected) << "Obtained: " - << bg::wkt(test_geo) << "\ncorresponding to" << b_gs(obtained) << "\n"; - success = false; - } -} - -bool test_all(grid_settings const& settings) -{ - generator_t genenerator(settings.seed); - const auto grid = grid_cells(settings); - const auto points = test_points(settings); - bool all_success = true; - for (int i = 0; i < settings.count || settings.count == -1; i++) - { - const bits bits1 = gen_bits(genenerator, settings.width * settings.height); - const bits bits2 = gen_bits(genenerator, settings.width * settings.height); - const auto geo1 = bits_to_geometry(bits1, grid, points, settings, all_success); - const auto geo2 = bits_to_geometry(bits2, grid, points, settings, all_success); - test_op(bits1, bits2, geo1, geo2, "union", std::bit_or<>{}, - [](mp_t const& g1, mp_t const& g2) { mp_t g; bg::union_(g1, g2, g); return g; }, - points, grid, settings, all_success); - test_op(bits1, bits2, geo1, geo2, "intersection", std::bit_and<>{}, - [](mp_t const& g1, mp_t const& g2) { mp_t g; bg::intersection(g1, g2, g); return g; }, - points, grid, settings, all_success); - test_op(bits1, bits2, geo1, geo2, "sym_difference", std::bit_xor<>{}, - [](mp_t const& g1, mp_t const& g2) { mp_t g; bg::sym_difference(g1, g2, g); return g; }, - points, grid, settings, all_success); - test_op(bits1, bits2, geo1, geo2, "difference g1 \\ g2", - [](std::bitset b1, std::bitset b2) { return b1 & (~b2); }, - [](mp_t const& g1, mp_t const& g2) { mp_t g; bg::difference(g1, g2, g); return g; }, - points, grid, settings, all_success); - test_op(bits1, bits2, geo1, geo2, "difference g2 \\ g1", - [](std::bitset b1, std::bitset b2) { return b2 & (~b1); }, - [](mp_t const& g1, mp_t const& g2) { mp_t g; bg::difference(g2, g1, g); return g; }, - points, grid, settings, all_success); - } - return all_success; -} - -int main(int argc, char** argv) -{ - BoostGeometryWriteTestConfiguration(); - try - { - namespace po = boost::program_options; - po::options_description description("=== random_integer_grids ===\nAllowed options"); - - grid_settings settings; - - description.add_options() - ("help", "Help message") - ("seed", - po::value(&settings.seed)->default_value(settings.seed), - "Initialization seed for random generator") - ("count", - po::value(&settings.count)->default_value(settings.count), - "Number of tests (-1 for infinite loop)") - ("width", - po::value(&settings.width)->default_value(settings.width), - "Width of grid (>= 1)") - ("height", - po::value(&settings.height)->default_value(settings.height), - "Height of grid (>= 1)") - ; - - po::variables_map varmap; - po::store(po::parse_command_line(argc, argv, description), varmap); - po::notify(varmap); - - if(settings.height < 1 || settings.width < 1) - { - std::cout << "Invalid dimensions, height and width need to be positive.\n"; - return 1; - } - if (varmap.count("help")) - { - std::cout << description << std::endl; - return 1; - } - if (! test_all(settings)) return 1; - } - catch(std::exception const& e) - { - std::cout << "Exception " << e.what() << '\n'; - } - catch(...) - { - std::cout << "Other exception" << '\n'; - } - - return 0; -}