From 0c788114241ed8e91bcf00548f7a3c3cbc4e3a77 Mon Sep 17 00:00:00 2001 From: Ryan Hechenberger Date: Fri, 26 Jun 2026 14:21:25 +1000 Subject: [PATCH 01/10] update warthog-core version --- extern/warthog-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extern/warthog-core b/extern/warthog-core index ba9b5f6..3d300e9 160000 --- a/extern/warthog-core +++ b/extern/warthog-core @@ -1 +1 @@ -Subproject commit ba9b5f66281ca6a767360787037ae80a7226e38e +Subproject commit 3d300e9c52f1794f0c0ac47237495a7c13b248ae From 17b71e94414139e025e27d19772c08373cf6f8c8 Mon Sep 17 00:00:00 2001 From: Ryan Hechenberger Date: Fri, 26 Jun 2026 15:48:09 +1000 Subject: [PATCH 02/10] update warthog-core version --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index dea0e84..68ca5c4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,11 +6,11 @@ project(WarthogJPS set_property(GLOBAL PROPERTY WARTHOG_warthog-jps ON) -set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD 23) set(CMAKE_CXX_STANDARD_REQUIRED TRUE) include(cmake/warthog.cmake) -warthog_module_declare(warthog-core v0.5.0) +warthog_module_declare(warthog-core 3d300e9c52f1794f0c0ac47237495a7c13b248ae) warthog_module(warthog-core) add_library(warthog_libjps) From b7ee584adc78f96a609858633e3468fa13964474 Mon Sep 17 00:00:00 2001 From: Ryan Hechenberger Date: Fri, 26 Jun 2026 15:50:53 +1000 Subject: [PATCH 03/10] update warthog-core --- extern/warthog-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extern/warthog-core b/extern/warthog-core index 3d300e9..2ca48fe 160000 --- a/extern/warthog-core +++ b/extern/warthog-core @@ -1 +1 @@ -Subproject commit 3d300e9c52f1794f0c0ac47237495a7c13b248ae +Subproject commit 2ca48feef71b20a82164eb2462ae6b9bb57c2114 From fb1db55b52daebb7e8e3779b0530c40896a53c4e Mon Sep 17 00:00:00 2001 From: Ryan Hechenberger Date: Fri, 26 Jun 2026 15:51:13 +1000 Subject: [PATCH 04/10] jps to work with dynamic environments --- apps/jps.cpp | 264 +++++++++++++++++++--------- include/jps/domain/rotate_gridmap.h | 52 ++++++ 2 files changed, 233 insertions(+), 83 deletions(-) diff --git a/apps/jps.cpp b/apps/jps.cpp index 5448d47..e0d97ce 100644 --- a/apps/jps.cpp +++ b/apps/jps.cpp @@ -1,4 +1,4 @@ -// warthog.cpp +// jps.cpp // // Pulls together a variety of different algorithms // for pathfinding on grid graphs. @@ -7,13 +7,19 @@ // @created: 2016-11-23 // +#include + #include #include #include #include + +#include +#include +#include #include #include -#include +#include #include #ifdef WARTHOG_POSTHOC #include @@ -26,7 +32,6 @@ #include "cfg.h" #include -#include #include #include @@ -48,12 +53,15 @@ int checkopt = 0; int verbose = 0; // display program help on startup int print_help = 0; -// run only this query, or -1 for all +// run only this inst, or -1 for all int filter_id = -1; +// dump map at id if set +int dump_map_id = -1; +std::string dump_map_file; #ifdef WARTHOG_POSTHOC // write trace to file, empty string to disable std::string trace_file; -using listener_grid = ::warthog::io::octile_grid_trace; +using listener_grid = ::warthog::io::grid_trace; using listener_type = std::tuple; #else using listener_type = std::tuple<>; @@ -74,53 +82,52 @@ help(std::ostream& out) << "\t--scen [scen file] (required) \n" << "\t--map [map file] (optional; specify this to override map " "values in scen file) \n" - << "\t--costs [costs file] (required if using a weighted " - "terrain algorithm)\n" + << "\t--cost [type] (optional; force use of selected solution cost of " + "instance, error if not exists)" << "\t--checkopt (optional; compare solution costs against " "values in the scen file)\n" << "\t--verbose (optional; prints debugging info when compiled " "with debug symbols)\n" - << "\t--filter [id] (optional; run only query [id])\n" + << "\t--filter [id] (optional; run only inst [id])\n" + << "\t--dump-map [id] (optional; dump current gridmap at start of " + "inst id to stderr default)\n" + << "\t--dump-map-file [filename] (optional; override dump map to " + "file)\n" #ifdef WARTHOG_POSTHOC << "\t--trace [.trace.yaml file] (optional; write posthoc trace for " - "first query to [file])\n" + "first instance to [file])\n" #endif << "Invoking the program this way solves all instances in [scen " "file] with algorithm [alg]\n" << "Currently recognised values for [alg]:\n" - << "\tjps, jpsP or jps2, jps+, jpsP+ or jps2+\n"; - // << "" - // << "The following are valid parameters for GENERATING instances:\n" - // << "\t --gen [map file (required)]\n" - // << "Invoking the program this way generates at random 1000 valid - // problems for \n" - // << "gridmap [map file]\n"; + << "\tjps, jpsP or jps2, jps+, jpsP+ or jps2+\n" + << "Currently recognised values for [v2-cost]:\n" + << "8c-ncc (default), 8c-cc, 4c, aa-ncc, aa-cc\n" + << "8c = 8-connected, 4c = 4-connected, aa = anyangle, ncc = " + "no-corner-cut, cc = corner-cut\n"; } bool check_optimality( - warthog::search::solution& sol, warthog::util::experiment* exp) + const warthog::search::solution& sol, + const warthog::scenario::experiment* exp) { - uint32_t precision = 2; - double epsilon = (1.0 / (int)pow(10, precision)) / 2; - double delta = fabs(sol.sum_of_edge_costs_ - exp->distance()); - - if(fabs(delta - epsilon) > epsilon) + if(!exp->distance()) { - std::stringstream strpathlen; - strpathlen << std::fixed << std::setprecision(exp->precision()); - strpathlen << sol.sum_of_edge_costs_; - - std::stringstream stroptlen; - stroptlen << std::fixed << std::setprecision(exp->precision()); - stroptlen << exp->distance(); + // unknown solution + return true; + } + constexpr int32_t precision = 2; + double epsilon = std::pow(10.0, -precision) * 0.5; + double delta = std::fabs(sol.sum_of_edge_costs_ - *exp->distance()); - std::cerr << std::setprecision(exp->precision()); + if(delta > epsilon) + { std::cerr << "optimality check failed!" << std::endl; std::cerr << std::endl; - std::cerr << "optimal path length: " << exp->distance() + std::cerr << "optimal path length: " << sol.sum_of_edge_costs_ << " computed length: "; - std::cerr << sol.sum_of_edge_costs_ << std::endl; + std::cerr << *exp->distance() << std::endl; std::cerr << "precision: " << precision << " epsilon: " << epsilon << std::endl; std::cerr << "delta: " << delta << std::endl; @@ -135,30 +142,89 @@ check_optimality( #define WARTHOG_POSTHOC_DO(f) #endif +// convenience wrapper around initialisation code +struct gridmap_scenario +{ + const warthog::scenario::scenario_manager* mgr; + warthog::scenario::scenario_runner run; + warthog::domain::gridmap grid; + jps::domain::rotate_gridmap rgrid; + warthog::scenario::grid_patch_set patches; + + gridmap_scenario(const warthog::scenario::scenario_manager& scen) + : mgr(&scen), run(&scen) + { } + + bool + load_map(const std::filesystem::path map) + { + if(!patches.load(map)) { return false; } + if(!run.gridmap_init(grid, patches)) { return false; } + rgrid.create_rmap(grid); + return true; + } + + bool + apply_patches() + { + for (auto& P : run.get_patches()) + { + uint32_t x, y; + grid.to_padded_xy_from_unpadded(P.topleft_x, P.topleft_y, x, y); + if (!rgrid.apply_patch_map(patches.get_patch(P.patch_id), warthog::grid::point(x, y))) + { return false; } + if (!rgrid.apply_patch_rmap(patches.get_patch(P.patch_id), warthog::grid::point(x, y))) + { return false; } + } + + return true; + } +}; + template int run_experiments( - Search& algo, std::string alg_name, - warthog::util::scenario_manager& scenmgr, bool verbose, bool checkopt, - std::ostream& out) + Search& algo, std::string alg_name, gridmap_scenario& scen, bool verbose, + bool checkopt, std::ostream& out) { WARTHOG_GINFO_FMT("start search with algorithm {}", alg_name); warthog::search::search_parameters par; warthog::search::solution sol; auto* expander = algo.get_expander(); - if(expander == nullptr) return 1; + if(expander == nullptr) return (int)std::errc::invalid_argument; - out << "id\talg\texpanded\tgenerated\treopen\tsurplus\theapops" + out << "id\tsnapshot\talg\texpanded\tgenerated\treopen\tsurplus\theapops" << "\tnanos\tplen\tpcost\tscost\tmap\n"; - for(uint32_t i = filter_id >= 0 ? static_cast(filter_id) : 0, - ie = filter_id >= 0 - ? i + 1 - : static_cast(scenmgr.num_experiments()); - i < ie; i++) + + for(uint32_t i = 0;; ++i) { #ifdef WARTHOG_POSTHOC std::optional trace_stream; // open and pass to trace if used + +#endif + auto [exp, patch_count] = scen.run.experiment_next(); + if(exp == nullptr) { break; } + if(patch_count != 0) + { + if(!scen.apply_patches()) + { + // failed to apply patches, exit + WARTHOG_GCRIT("dynamic patch error: failed to apply patches"); + return (int)std::errc::io_error; + } + } + + if(i == dump_map_id) + { + // print map + scen.grid.save(dump_map_file, false); + } + + if(filter_id >= 0 && i == filter_id) + { + // trace +#ifdef WARTHOG_POSTHOC if constexpr(std::same_as< listener_type, std::remove_cvref_t>) @@ -172,7 +238,8 @@ run_experiments( } } #endif - warthog::util::experiment* exp = scenmgr.get_experiment(i); + } + else if(filter_id >= 0) { continue; } warthog::pack_id startid = expander->get_pack(exp->startx(), exp->starty()); @@ -182,6 +249,11 @@ run_experiments( sol.reset(); algo.get_path(&pi, &par, &sol); + // check for no solution + if(sol.sum_of_edge_costs_ >= warthog::COST_MAX) + { + sol.sum_of_edge_costs_ = -1; + } #ifdef WARTHOG_POSTHOC if constexpr(std::same_as< @@ -196,46 +268,57 @@ run_experiments( } #endif - out << i << "\t" << alg_name << "\t" << sol.met_.nodes_expanded_ - << "\t" << sol.met_.nodes_generated_ << "\t" - << sol.met_.nodes_reopen_ << "\t" << sol.met_.nodes_surplus_ - << "\t" << sol.met_.heap_ops_ << "\t" - << sol.met_.time_elapsed_nano_.count() << "\t" + out << i << "\t" << scen.run.get_snapshot_at() << "\t" << alg_name + << "\t" << sol.met_.nodes_expanded_ << "\t" + << sol.met_.nodes_generated_ << "\t" << sol.met_.nodes_reopen_ + << "\t" << sol.met_.nodes_surplus_ << "\t" << sol.met_.heap_ops_ + << "\t" << sol.met_.time_elapsed_nano_.count() << "\t" << (!sol.path_.empty() ? sol.path_.size() - 1 : 0) << "\t" - << sol.sum_of_edge_costs_ << "\t" << exp->distance() << "\t" - << scenmgr.last_file_loaded() << std::endl; + << sol.sum_of_edge_costs_ << "\t"; + if(exp->distance()) + out << *exp->distance(); + else + out << '-'; + out << "\t" << scen.mgr->last_file_loaded() << std::endl; if(checkopt) { if(!check_optimality(sol, exp)) { WARTHOG_GCRIT("search error: failed suboptimal 4"); - return 4; + return (int)std::errc::result_out_of_range; } } } WARTHOG_GINFO_FMT( - "search complete; total memory: {}", algo.mem() + scenmgr.mem()); + "search complete; total memory: {}", algo.mem() + scen.mgr->mem()); return 0; } template int run_jps( - warthog::util::scenario_manager& scenmgr, std::string mapname, + warthog::scenario::scenario_manager& scenmgr, std::string mapname, std::string alg_name) { - warthog::domain::gridmap map(mapname.c_str()); - ExpansionPolicy expander(&map); - warthog::heuristic::octile_heuristic heuristic(map.width(), map.height()); + gridmap_scenario scen(scenmgr); + if(!scen.load_map(std::filesystem::path(mapname))) + { + WARTHOG_GCRIT("failed to load map"); + return (int)std::errc::io_error; + } + ExpansionPolicy expander(nullptr); + expander.set_map(scen.rgrid); // gridmap_scenario manages both grids + warthog::heuristic::octile_heuristic heuristic( + scen.grid.width(), scen.grid.height()); warthog::util::pqueue_min open; warthog::search::unidirectional_search jps( &heuristic, &expander, &open, listener_type(WARTHOG_POSTHOC_DO(&map))); - int ret = run_experiments( - jps, alg_name, scenmgr, verbose, checkopt, std::cout); + int ret + = run_experiments(jps, alg_name, scen, verbose, checkopt, std::cout); return ret; } @@ -252,12 +335,14 @@ main(int argc, char** argv) // {"gen", required_argument, 0, 3}, {"help", no_argument, &print_help, 1}, {"checkopt", no_argument, &checkopt, 1}, + {"cost", required_argument, 0, 0}, {"verbose", no_argument, &verbose, 1}, {"filter", required_argument, &filter_id, 1}, + {"dump-map", required_argument, &dump_map_id, 1}, + {"dump-map-file", required_argument, 0, 0}, #ifdef WARTHOG_POSTHOC {"trace", required_argument, 0, 0}, #endif - {"costs", required_argument, 0, 1}, {0, 0, 0, 0}}; warthog::util::cfg cfg; @@ -272,26 +357,23 @@ main(int argc, char** argv) std::string sfile = cfg.get_param_value("scen"); std::string alg = cfg.get_param_value("alg"); // std::string gen = cfg.get_param_value("gen"); - std::string mapfile = cfg.get_param_value("map"); - std::string costfile = cfg.get_param_value("costs"); + std::string mapfile = cfg.get_param_value("map"); + std::string costtype = cfg.get_param_value("cost"); + dump_map_file = cfg.get_param_value("dump-map-file"); if(filter_id == 1) { - filter_id = std::stoi(cfg.get_param_value("filter")); + if(warthog::util::parse_token(cfg.get_param_value("filter"), filter_id) + != std::errc{}) + { + WARTHOG_GERROR_FMT("invalid --filter argument {}", filter_id); + return (int)std::errc::invalid_argument; + } } #ifdef WARTHOG_POSTHOC trace_file = cfg.get_param_value("trace"); #endif - // if(gen != "") - // { - // warthog::util::scenario_manager sm; - // warthog::domain::gridmap gm(gen.c_str()); - // sm.generate_experiments(&gm, 1000) ; - // sm.write_scenario(std::cout); - // exit(0); - // } - // running experiments if(alg == "" || sfile == "") { @@ -299,29 +381,49 @@ main(int argc, char** argv) return 0; } + if(dump_map_id == 1) + { + if(warthog::util::parse_token( + cfg.get_param_value("dump-map"), dump_map_id) + != std::errc{}) + { + WARTHOG_GCRIT_FMT("invalid --dump-map argument {}", dump_map_id); + return (int)std::errc::invalid_argument; + } + } + if(dump_map_file.empty()) { dump_map_file = "/dev/stderr"; } + // load up the instances - warthog::util::scenario_manager scenmgr; - scenmgr.load_scenario(sfile.c_str()); + warthog::scenario::scenario_manager scenmgr; + scenmgr.set_cost_type(costtype); + try + { + scenmgr.load_scenario(sfile.c_str()); + } + catch(const std::runtime_error& e) + { + return (int)std::errc::io_error; + } if(scenmgr.num_experiments() == 0) { - std::cerr << "err; scenario file does not contain any instances\n"; - return 1; + WARTHOG_GCRIT("scenario file does not contain any instances"); + return (int)std::errc::invalid_argument; } // the map filename can be given or (default) taken from the scenario file if(mapfile == "") { // first, try to load the map from the scenario file - mapfile = warthog::util::find_map_filename(scenmgr, sfile); + mapfile = warthog::scenario::find_map_filename(scenmgr, sfile); if(mapfile.empty()) { std::cerr << "could not locate a corresponding map file\n"; help(std::cout); return 0; } + WARTHOG_GINFO_FMT("deduced mapfile: ", mapfile); } - std::cerr << "mapfile=" << mapfile << std::endl; using namespace jps::search; if(alg == "jps") @@ -348,11 +450,7 @@ main(int argc, char** argv) return run_jps>( scenmgr, mapfile, alg); } - else - { - std::cerr << "err; invalid search algorithm: " << alg << "\n"; - return 1; - } - return 0; + WARTHOG_GCRIT_FMT("invalid search algorithm: ", alg); + return (int)std::errc::invalid_argument; } diff --git a/include/jps/domain/rotate_gridmap.h b/include/jps/domain/rotate_gridmap.h index ae4688b..32f2f52 100644 --- a/include/jps/domain/rotate_gridmap.h +++ b/include/jps/domain/rotate_gridmap.h @@ -521,6 +521,7 @@ class rotate_gridmap : public rgridmap_point_conversions } else { clear(); } } + void clear() { @@ -528,6 +529,7 @@ class rotate_gridmap : public rgridmap_point_conversions maps = {}; static_cast(*this) = {}; } + void create_rmap(domain::gridmap& map) { @@ -571,6 +573,56 @@ class rotate_gridmap : public rgridmap_point_conversions #endif // NDEBUG } + bool + apply_patch_map( + domain::gridmap::bittable patch, point padded_loc) + { + if((uint64_t)padded_loc.x + patch.width() >= (uint64_t)map().width() + || (uint64_t)padded_loc.y + patch.height() >= (uint64_t)map().height()) + return false; + + // apply patch + patch.copy( + map(), point_to_id(padded_loc), + pad_id::zero(), patch.width(), patch.height()); + + return true; + } + + bool + apply_patch_rmap( + domain::gridmap::bittable patch, point padded_loc) + { + if((uint64_t)padded_loc.x + patch.width() >= (uint64_t)map().width() + || (uint64_t)padded_loc.y + patch.height() >= (uint64_t)map().height()) + return false; + + // apply patch + // copy row-by-row on rmap, thus start at bottom-right corner of patch + rgrid_id dest_id = rpoint_to_rid(point_to_rpoint(point(padded_loc.x, padded_loc.y + patch.height() - 1))); + grid_id src_id = static_cast(patch.xy_to_id(0, patch.height()-1)); + const uint32_t dest_y_adj = rmap().width(); + auto& rgridp = rmap(); + + // go left-to-right, bottom-to-top on patch + for(uint32_t x = 0; x < patch.width(); x++) + { + auto row_dest_id = dest_id; + dest_id.id += dest_y_adj; + auto row_src_id = src_id; + src_id.id += 1; + for(uint32_t y = 0; y < patch.height(); y++) + { + bool label = patch.get(static_cast(row_src_id)); + row_src_id.id -= patch.width(); + rgridp.set_label(static_cast(row_dest_id), label); + row_dest_id.id += 1; + } + } + + return true; + } + domain::gridmap& map() noexcept { From 0987b5b5d2b00c2cba657712be32700932d8a5da Mon Sep 17 00:00:00 2001 From: Ryan Hechenberger Date: Mon, 29 Jun 2026 14:14:32 +1000 Subject: [PATCH 05/10] bugfix dynamic scenario runner --- apps/jps.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/jps.cpp b/apps/jps.cpp index e0d97ce..4b98e26 100644 --- a/apps/jps.cpp +++ b/apps/jps.cpp @@ -145,6 +145,7 @@ check_optimality( // convenience wrapper around initialisation code struct gridmap_scenario { + bool dynamic = false; const warthog::scenario::scenario_manager* mgr; warthog::scenario::scenario_runner run; warthog::domain::gridmap grid; @@ -158,6 +159,7 @@ struct gridmap_scenario bool load_map(const std::filesystem::path map) { + dynamic = true; if(!patches.load(map)) { return false; } if(!run.gridmap_init(grid, patches)) { return false; } rgrid.create_rmap(grid); @@ -167,6 +169,8 @@ struct gridmap_scenario bool apply_patches() { + if(!dynamic) return true; + for (auto& P : run.get_patches()) { uint32_t x, y; @@ -315,7 +319,7 @@ run_jps( warthog::util::pqueue_min open; warthog::search::unidirectional_search jps( - &heuristic, &expander, &open, listener_type(WARTHOG_POSTHOC_DO(&map))); + &heuristic, &expander, &open, listener_type(WARTHOG_POSTHOC_DO(&scen.grid))); int ret = run_experiments(jps, alg_name, scen, verbose, checkopt, std::cout); From 0ace1481b688473f78af103ae16683ed6b3f8137 Mon Sep 17 00:00:00 2001 From: Ryan Hechenberger Date: Wed, 22 Jul 2026 14:49:14 +1000 Subject: [PATCH 06/10] update to new warthog-core release v0.6.0 --- CMakeLists.txt | 2 +- extern/warthog-core | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 68ca5c4..d56f483 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,7 +10,7 @@ set(CMAKE_CXX_STANDARD 23) set(CMAKE_CXX_STANDARD_REQUIRED TRUE) include(cmake/warthog.cmake) -warthog_module_declare(warthog-core 3d300e9c52f1794f0c0ac47237495a7c13b248ae) +warthog_module_declare(warthog-core v0.6.0) warthog_module(warthog-core) add_library(warthog_libjps) diff --git a/extern/warthog-core b/extern/warthog-core index 2ca48fe..1866931 160000 --- a/extern/warthog-core +++ b/extern/warthog-core @@ -1 +1 @@ -Subproject commit 2ca48feef71b20a82164eb2462ae6b9bb57c2114 +Subproject commit 18669316c6621376d6a469795968b97e635c65e3 From ab7f0d2e2bb13020b88ad5e545ce3fabeca1bd40 Mon Sep 17 00:00:00 2001 From: Ryan Hechenberger Date: Wed, 22 Jul 2026 15:46:28 +1000 Subject: [PATCH 07/10] jps app updated to support dynamic scenarios --- apps/jps.cpp | 244 ++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 182 insertions(+), 62 deletions(-) diff --git a/apps/jps.cpp b/apps/jps.cpp index 4b98e26..0dcd553 100644 --- a/apps/jps.cpp +++ b/apps/jps.cpp @@ -43,8 +43,6 @@ #include #include -// #include "time_constraints.h" - namespace { // check computed solutions are optimal @@ -53,11 +51,11 @@ int checkopt = 0; int verbose = 0; // display program help on startup int print_help = 0; +// run only this snapshot, or -1 for all +int snapshot_id = -1; // run only this inst, or -1 for all int filter_id = -1; -// dump map at id if set -int dump_map_id = -1; -std::string dump_map_file; +std::string dump_map; #ifdef WARTHOG_POSTHOC // write trace to file, empty string to disable std::string trace_file; @@ -70,7 +68,7 @@ using listener_type = std::tuple<>; void help(std::ostream& out) { - out << "warthog version " << WARTHOG_VERSION << "\n"; + out << "warthog version " WARTHOG_VERSION "\n"; out << "==> manual <==\n" << "This program solves/generates grid-based pathfinding " "problems using the\n" @@ -82,17 +80,21 @@ help(std::ostream& out) << "\t--scen [scen file] (required) \n" << "\t--map [map file] (optional; specify this to override map " "values in scen file) \n" - << "\t--cost [type] (optional; force use of selected solution cost of " - "instance, error if not exists)" + << "\t--cost [type] (default first cost from scenario if exists;\n" + "\t\tuse cost type for solution from scenario or error if not " + "exists\n" + << "\t\tpass '-' to discard all provided solution costs)\n" << "\t--checkopt (optional; compare solution costs against " "values in the scen file)\n" << "\t--verbose (optional; prints debugging info when compiled " "with debug symbols)\n" - << "\t--filter [id] (optional; run only inst [id])\n" - << "\t--dump-map [id] (optional; dump current gridmap at start of " - "inst id to stderr default)\n" - << "\t--dump-map-file [filename] (optional; override dump map to " - "file)\n" + << "\t--snapshot [id] (default -1; select all instances (-1) or only " + "instances on snapshot id)\n" + << "\t--filter [num] (default -1; run all instances (-1) or run " + "instance num;\n" + << "\t\tif used with --snapshot, is instance num in snapshot id)\n" + << "\t--dump-map [file] (optional; dump gridmap at first instance to " + "[file], use with --snapshot or --filter)\n" #ifdef WARTHOG_POSTHOC << "\t--trace [.trace.yaml file] (optional; write posthoc trace for " "first instance to [file])\n" @@ -100,11 +102,7 @@ help(std::ostream& out) << "Invoking the program this way solves all instances in [scen " "file] with algorithm [alg]\n" << "Currently recognised values for [alg]:\n" - << "\tjps, jpsP or jps2, jps+, jpsP+ or jps2+\n" - << "Currently recognised values for [v2-cost]:\n" - << "8c-ncc (default), 8c-cc, 4c, aa-ncc, aa-cc\n" - << "8c = 8-connected, 4c = 4-connected, aa = anyangle, ncc = " - "no-corner-cut, cc = corner-cut\n"; + << "\tjps, jpsP or jps2, jps+, jpsP+ or jps2+\n"; } bool @@ -142,10 +140,18 @@ check_optimality( #define WARTHOG_POSTHOC_DO(f) #endif -// convenience wrapper around initialisation code +/// @brief general wrapper around scenario runner and gridmap management +/// +/// Gets given a scenario manager and handles program management of running +/// with user-provided parameters and algorithm support. +/// +/// Owns and update the gridmap over a dynamic scenario for algorithms that +/// support dynamic scenarios. struct gridmap_scenario { - bool dynamic = false; + bool scenario_v1 = true; ///< scenario is v1 + bool grid_managed = false; ///< grid is managed by this class + bool static_scenario = false; ///< scenario is static const warthog::scenario::scenario_manager* mgr; warthog::scenario::scenario_runner run; warthog::domain::gridmap grid; @@ -154,23 +160,116 @@ struct gridmap_scenario gridmap_scenario(const warthog::scenario::scenario_manager& scen) : mgr(&scen), run(&scen) - { } + { + scenario_v1 + = mgr->get_version() == warthog::io::scenario_version::VERSION_1; + static_scenario = mgr->is_static_scenario(); + } + /// @brief loads the map to current state + /// @param file map filename (single or patches) + /// @return true on success, false otherwise bool - load_map(const std::filesystem::path map) + load_map(const std::filesystem::path file) { - dynamic = true; - if(!patches.load(map)) { return false; } + grid_managed = true; + if(!patches.load(file)) { return false; } if(!run.gridmap_init(grid, patches)) { return false; } + return true; + } + + /// @brief will update to runner based on user-provided parameters + /// @param snapshot_id set map to match snapshot + /// @param filter_id if snapshot_id==-1, set map to match at instance id + /// @return true on success, false otherwise + bool + setup_runner(int snapshot_id, int filter_id) + { + if(snapshot_id != -1) + { + // goto snapshot_id + static_scenario = true; + while(run.get_snapshot_at() != snapshot_id) + { + // ran out of snapshots + if(run.complete()) + { + WARTHOG_GWARN_FMT( + "scenario complete before reaching snapshot {}", + snapshot_id); + return false; + } + // apply snapshot + run.snapshot_next(true); + run.snapshot_patches(false); + if(!apply_patches()) { return false; } + } + } + + if(filter_id != -1) + { + // skip next filter_id instances + static_scenario = true; + auto [exp, patches] = run.experiment_next(filter_id + 1, false); + if(run.complete() || exp == nullptr) + { + WARTHOG_GWARN_FMT( + "scenario complete before reaching filter {}", filter_id); + return false; + } + if(snapshot_id != -1 && run.get_snapshot_at() != snapshot_id) + { + WARTHOG_GWARN_FMT( + "scenario filter {} exceeded snapshot {} instances", + filter_id, snapshot_id); + return false; + } + } + + // update gridmap to match patch, as rgrid is not created, do through run + if(int c = run.gridmap_apply_patches(grid, patches); c < 0) + { + c = -c - 1; + auto p = run.get_patches()[c]; + WARTHOG_GWARN_FMT( + "failed to apply patch {} at ({},{})", p.patch_id, p.topleft_x, + p.topleft_y); + return false; + } + rgrid.create_rmap(grid); + return true; } + /// @brief apply patches from runner to owned grid (if managed) + /// @return true on success, false otherwise bool apply_patches() { - if(!dynamic) return true; + if(!grid_managed) return true; + + if(int c = run.gridmap_apply_patches(grid, patches); c < 0) + { + c = -c - 1; + auto p = run.get_patches()[c]; + WARTHOG_GWARN_FMT( + "failed to apply patch {} at ({},{})", p.patch_id, p.topleft_x, + p.topleft_y); + return false; + } + + return true; + } + + /// @brief will update the grid for dynamic scenarios + /// @return true on success, false otherwise + bool + dynamic_grid_update() + { + if(!grid_managed) return true; + // update grid through rgrid interface for (auto& P : run.get_patches()) { uint32_t x, y; @@ -180,7 +279,7 @@ struct gridmap_scenario if (!rgrid.apply_patch_rmap(patches.get_patch(P.patch_id), warthog::grid::point(x, y))) { return false; } } - + return true; } }; @@ -209,9 +308,17 @@ run_experiments( #endif auto [exp, patch_count] = scen.run.experiment_next(); if(exp == nullptr) { break; } + // check if only run one instance + if(filter_id >= 0 && i != 0) { break; } + // check if instance is on snapshot + if(snapshot_id >= 0 && scen.run.get_snapshot_at() != snapshot_id) + { + break; + } + if(patch_count != 0) { - if(!scen.apply_patches()) + if(!scen.dynamic_grid_update()) { // failed to apply patches, exit WARTHOG_GCRIT("dynamic patch error: failed to apply patches"); @@ -219,21 +326,19 @@ run_experiments( } } - if(i == dump_map_id) + // special actions on first scenario + if(i == 0) { // print map - scen.grid.save(dump_map_file, false); - } - - if(filter_id >= 0 && i == filter_id) - { + if(!dump_map.empty()) { scen.grid.save(dump_map, false); } // trace #ifdef WARTHOG_POSTHOC if constexpr(std::same_as< listener_type, - std::remove_cvref_t>) + std::remove_cvref_t< + decltype(algo.get_listeners())>>) { - if(i == filter_id && !trace_file.empty()) + if(!trace_file.empty()) { listener_grid& l = std::get(algo.get_listeners()); @@ -243,7 +348,6 @@ run_experiments( } #endif } - else if(filter_id >= 0) { continue; } warthog::pack_id startid = expander->get_pack(exp->startx(), exp->starty()); @@ -272,11 +376,12 @@ run_experiments( } #endif - out << i << "\t" << scen.run.get_snapshot_at() << "\t" << alg_name - << "\t" << sol.met_.nodes_expanded_ << "\t" - << sol.met_.nodes_generated_ << "\t" << sol.met_.nodes_reopen_ - << "\t" << sol.met_.nodes_surplus_ << "\t" << sol.met_.heap_ops_ - << "\t" << sol.met_.time_elapsed_nano_.count() << "\t" + out << scen.run.get_experiment_at() << "\t" + << scen.run.get_snapshot_at() << "\t" << alg_name << "\t" + << sol.met_.nodes_expanded_ << "\t" << sol.met_.nodes_generated_ + << "\t" << sol.met_.nodes_reopen_ << "\t" + << sol.met_.nodes_surplus_ << "\t" << sol.met_.heap_ops_ << "\t" + << sol.met_.time_elapsed_nano_.count() << "\t" << (!sol.path_.empty() ? sol.path_.size() - 1 : 0) << "\t" << sol.sum_of_edge_costs_ << "\t"; if(exp->distance()) @@ -300,7 +405,7 @@ run_experiments( return 0; } -template +template int run_jps( warthog::scenario::scenario_manager& scenmgr, std::string mapname, @@ -312,6 +417,21 @@ run_jps( WARTHOG_GCRIT("failed to load map"); return (int)std::errc::io_error; } + // init runner to start at correct instance and update the map + if(!scen.setup_runner(snapshot_id, filter_id)) + { + WARTHOG_GCRIT("failed to setup scenario"); + return (int)std::errc::io_error; + } + if constexpr (!Online) { + // check that scenario is offline + if(!scen.static_scenario) + { + WARTHOG_GCRIT_FMT( + "algorithm {} requires scenario file/filter/snapshot to be static (restrict to single snapshot)", alg_name); + return (int)std::errc::invalid_argument; + } + } ExpansionPolicy expander(nullptr); expander.set_map(scen.rgrid); // gridmap_scenario manages both grids warthog::heuristic::octile_heuristic heuristic( @@ -319,7 +439,8 @@ run_jps( warthog::util::pqueue_min open; warthog::search::unidirectional_search jps( - &heuristic, &expander, &open, listener_type(WARTHOG_POSTHOC_DO(&scen.grid))); + &heuristic, &expander, &open, + listener_type(WARTHOG_POSTHOC_DO(&scen.grid))); int ret = run_experiments(jps, alg_name, scen, verbose, checkopt, std::cout); @@ -341,9 +462,9 @@ main(int argc, char** argv) {"checkopt", no_argument, &checkopt, 1}, {"cost", required_argument, 0, 0}, {"verbose", no_argument, &verbose, 1}, + {"snapshot", required_argument, &snapshot_id, 1}, {"filter", required_argument, &filter_id, 1}, - {"dump-map", required_argument, &dump_map_id, 1}, - {"dump-map-file", required_argument, 0, 0}, + {"dump-map", required_argument, 0, 0}, #ifdef WARTHOG_POSTHOC {"trace", required_argument, 0, 0}, #endif @@ -363,8 +484,19 @@ main(int argc, char** argv) // std::string gen = cfg.get_param_value("gen"); std::string mapfile = cfg.get_param_value("map"); std::string costtype = cfg.get_param_value("cost"); - dump_map_file = cfg.get_param_value("dump-map-file"); + std::string weightsfile = cfg.get_param_value("grid-weight"); + dump_map = cfg.get_param_value("dump-map"); + if(snapshot_id == 1) + { + if(warthog::util::parse_token( + cfg.get_param_value("snapshot"), snapshot_id) + != std::errc{}) + { + WARTHOG_GERROR_FMT("invalid --snapshot argument {}", snapshot_id); + return (int)std::errc::invalid_argument; + } + } if(filter_id == 1) { if(warthog::util::parse_token(cfg.get_param_value("filter"), filter_id) @@ -385,18 +517,6 @@ main(int argc, char** argv) return 0; } - if(dump_map_id == 1) - { - if(warthog::util::parse_token( - cfg.get_param_value("dump-map"), dump_map_id) - != std::errc{}) - { - WARTHOG_GCRIT_FMT("invalid --dump-map argument {}", dump_map_id); - return (int)std::errc::invalid_argument; - } - } - if(dump_map_file.empty()) { dump_map_file = "/dev/stderr"; } - // load up the instances warthog::scenario::scenario_manager scenmgr; scenmgr.set_cost_type(costtype); @@ -433,25 +553,25 @@ main(int argc, char** argv) if(alg == "jps") { using jump_point = jps::jump::jump_point_online; - return run_jps>( + return run_jps, true>( scenmgr, mapfile, alg); } else if(alg == "jpsP" || alg == "jps2") { using jump_point = jps::jump::jump_point_online; - return run_jps>( + return run_jps, true>( scenmgr, mapfile, alg); } else if(alg == "jps+") { using jump_point = jps::jump::jump_point_offline<>; - return run_jps>( + return run_jps, false>( scenmgr, mapfile, alg); } else if(alg == "jpsP+" || alg == "jps2+") { using jump_point = jps::jump::jump_point_offline<>; - return run_jps>( + return run_jps, false>( scenmgr, mapfile, alg); } From 6440bc9288bddc5c410782a301d509acc04544f0 Mon Sep 17 00:00:00 2001 From: Ryan Hechenberger Date: Wed, 22 Jul 2026 15:47:34 +1000 Subject: [PATCH 08/10] update clang-format to support C++23, in line with warthog-core --- .clang-format | 2 +- .github/workflows/clang-format-apply.yml | 5 +++-- .github/workflows/clang-format-test.yml | 5 +++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/.clang-format b/.clang-format index b05423f..1d1d724 100644 --- a/.clang-format +++ b/.clang-format @@ -1,6 +1,6 @@ --- Language: Cpp -Standard: c++20 +Standard: Latest BasedOnStyle: GNU AccessModifierOffset: -4 PointerAlignment: Left diff --git a/.github/workflows/clang-format-apply.yml b/.github/workflows/clang-format-apply.yml index 4c9a158..77c173f 100644 --- a/.github/workflows/clang-format-apply.yml +++ b/.github/workflows/clang-format-apply.yml @@ -10,11 +10,12 @@ jobs: steps: - uses: actions/checkout@v6 - - uses: DoozyX/clang-format-lint-action@v0.18.1 + - uses: DoozyX/clang-format-lint-action@v0.20 with: source: '.' exclude: './extern ./libs' - clangFormatVersion: 18.1.3 + extensions: 'h,cpp,c' + clangFormatVersion: 20 inplace: True - run: | git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com" diff --git a/.github/workflows/clang-format-test.yml b/.github/workflows/clang-format-test.yml index 9213b80..2a9d327 100644 --- a/.github/workflows/clang-format-test.yml +++ b/.github/workflows/clang-format-test.yml @@ -10,8 +10,9 @@ jobs: steps: - uses: actions/checkout@v6 - - uses: DoozyX/clang-format-lint-action@v0.18.1 + - uses: DoozyX/clang-format-lint-action@v0.20 with: source: '.' exclude: './extern ./libs' - clangFormatVersion: 18.1.3 + extensions: 'h,cpp,c' + clangFormatVersion: 20 From 3946bca6dd12f344bc72262d05a55a61049218a5 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 22 Jul 2026 05:49:06 +0000 Subject: [PATCH 09/10] auto clang-format action --- apps/jps.cpp | 50 +++++++++++-------- include/jps/domain/rotate_gridmap.h | 34 +++++++------ include/jps/search/jps_expansion_policy.h | 9 ++-- .../jps/search/jps_prune_expansion_policy.h | 10 ++-- 4 files changed, 59 insertions(+), 44 deletions(-) diff --git a/apps/jps.cpp b/apps/jps.cpp index 0dcd553..ce9e2fb 100644 --- a/apps/jps.cpp +++ b/apps/jps.cpp @@ -177,7 +177,7 @@ struct gridmap_scenario if(!run.gridmap_init(grid, patches)) { return false; } return true; } - + /// @brief will update to runner based on user-provided parameters /// @param snapshot_id set map to match snapshot /// @param filter_id if snapshot_id==-1, set map to match at instance id @@ -226,7 +226,8 @@ struct gridmap_scenario } } - // update gridmap to match patch, as rgrid is not created, do through run + // update gridmap to match patch, as rgrid is not created, do through + // run if(int c = run.gridmap_apply_patches(grid, patches); c < 0) { c = -c - 1; @@ -236,7 +237,7 @@ struct gridmap_scenario p.topleft_y); return false; } - + rgrid.create_rmap(grid); return true; @@ -258,7 +259,7 @@ struct gridmap_scenario p.topleft_y); return false; } - + return true; } @@ -270,14 +271,20 @@ struct gridmap_scenario if(!grid_managed) return true; // update grid through rgrid interface - for (auto& P : run.get_patches()) + for(auto& P : run.get_patches()) { uint32_t x, y; grid.to_padded_xy_from_unpadded(P.topleft_x, P.topleft_y, x, y); - if (!rgrid.apply_patch_map(patches.get_patch(P.patch_id), warthog::grid::point(x, y))) - { return false; } - if (!rgrid.apply_patch_rmap(patches.get_patch(P.patch_id), warthog::grid::point(x, y))) - { return false; } + if(!rgrid.apply_patch_map( + patches.get_patch(P.patch_id), warthog::grid::point(x, y))) + { + return false; + } + if(!rgrid.apply_patch_rmap( + patches.get_patch(P.patch_id), warthog::grid::point(x, y))) + { + return false; + } } return true; @@ -333,19 +340,19 @@ run_experiments( if(!dump_map.empty()) { scen.grid.save(dump_map, false); } // trace #ifdef WARTHOG_POSTHOC - if constexpr(std::same_as< - listener_type, + if constexpr(std::same_as< + listener_type, std::remove_cvref_t< decltype(algo.get_listeners())>>) - { - if(!trace_file.empty()) { - listener_grid& l - = std::get(algo.get_listeners()); - trace_stream.emplace(trace_file); - l.open(*trace_stream); + if(!trace_file.empty()) + { + listener_grid& l + = std::get(algo.get_listeners()); + trace_stream.emplace(trace_file); + l.open(*trace_stream); + } } - } #endif } @@ -423,12 +430,15 @@ run_jps( WARTHOG_GCRIT("failed to setup scenario"); return (int)std::errc::io_error; } - if constexpr (!Online) { + if constexpr(!Online) + { // check that scenario is offline if(!scen.static_scenario) { WARTHOG_GCRIT_FMT( - "algorithm {} requires scenario file/filter/snapshot to be static (restrict to single snapshot)", alg_name); + "algorithm {} requires scenario file/filter/snapshot to be " + "static (restrict to single snapshot)", + alg_name); return (int)std::errc::invalid_argument; } } diff --git a/include/jps/domain/rotate_gridmap.h b/include/jps/domain/rotate_gridmap.h index 32f2f52..0c41d3e 100644 --- a/include/jps/domain/rotate_gridmap.h +++ b/include/jps/domain/rotate_gridmap.h @@ -574,46 +574,48 @@ class rotate_gridmap : public rgridmap_point_conversions } bool - apply_patch_map( - domain::gridmap::bittable patch, point padded_loc) + apply_patch_map(domain::gridmap::bittable patch, point padded_loc) { if((uint64_t)padded_loc.x + patch.width() >= (uint64_t)map().width() - || (uint64_t)padded_loc.y + patch.height() >= (uint64_t)map().height()) + || (uint64_t)padded_loc.y + patch.height() + >= (uint64_t)map().height()) return false; // apply patch patch.copy( - map(), point_to_id(padded_loc), - pad_id::zero(), patch.width(), patch.height()); - + map(), point_to_id(padded_loc), pad_id::zero(), patch.width(), + patch.height()); + return true; } bool - apply_patch_rmap( - domain::gridmap::bittable patch, point padded_loc) + apply_patch_rmap(domain::gridmap::bittable patch, point padded_loc) { if((uint64_t)padded_loc.x + patch.width() >= (uint64_t)map().width() - || (uint64_t)padded_loc.y + patch.height() >= (uint64_t)map().height()) + || (uint64_t)padded_loc.y + patch.height() + >= (uint64_t)map().height()) return false; // apply patch // copy row-by-row on rmap, thus start at bottom-right corner of patch - rgrid_id dest_id = rpoint_to_rid(point_to_rpoint(point(padded_loc.x, padded_loc.y + patch.height() - 1))); - grid_id src_id = static_cast(patch.xy_to_id(0, patch.height()-1)); + rgrid_id dest_id = rpoint_to_rid(point_to_rpoint( + point(padded_loc.x, padded_loc.y + patch.height() - 1))); + grid_id src_id + = static_cast(patch.xy_to_id(0, patch.height() - 1)); const uint32_t dest_y_adj = rmap().width(); - auto& rgridp = rmap(); + auto& rgridp = rmap(); // go left-to-right, bottom-to-top on patch for(uint32_t x = 0; x < patch.width(); x++) { auto row_dest_id = dest_id; - dest_id.id += dest_y_adj; - auto row_src_id = src_id; - src_id.id += 1; + dest_id.id += dest_y_adj; + auto row_src_id = src_id; + src_id.id += 1; for(uint32_t y = 0; y < patch.height(); y++) { - bool label = patch.get(static_cast(row_src_id)); + bool label = patch.get(static_cast(row_src_id)); row_src_id.id -= patch.width(); rgridp.set_label(static_cast(row_dest_id), label); row_dest_id.id += 1; diff --git a/include/jps/search/jps_expansion_policy.h b/include/jps/search/jps_expansion_policy.h index 39714bf..4d7acbf 100644 --- a/include/jps/search/jps_expansion_policy.h +++ b/include/jps/search/jps_expansion_policy.h @@ -184,10 +184,11 @@ jps_expansion_policy::expand( if(jump_result.first > 0) // jump point { // successful jump - pad_id node{pad_id(static_cast( - current_id.id - + warthog::grid::dir_id_adj(di, map_width_) - * res.inter))}; + pad_id node{pad_id( + static_cast( + current_id.id + + warthog::grid::dir_id_adj(di, map_width_) + * res.inter))}; assert(rmap_.map().get( node)); // successor must be traversable warthog::search::search_node* jp_succ diff --git a/include/jps/search/jps_prune_expansion_policy.h b/include/jps/search/jps_prune_expansion_policy.h index 2af154f..66dc2f8 100644 --- a/include/jps/search/jps_prune_expansion_policy.h +++ b/include/jps/search/jps_prune_expansion_policy.h @@ -257,8 +257,9 @@ jps_prune_expansion_policy::expand( * res_i.hori); const auto cost_j = cost + warthog::DBL_ONE * res_i.hori; - assert(rmap_.map().get(pad_id{ - node_j})); // successor must be traversable + assert(rmap_.map().get( + pad_id{ + node_j})); // successor must be traversable warthog::search::search_node* jp_succ = this->generate(pad_id{node_j}); add_neighbour(jp_succ, cost_j); @@ -271,8 +272,9 @@ jps_prune_expansion_policy::expand( * res_i.vert); const auto cost_j = cost + warthog::DBL_ONE * res_i.vert; - assert(rmap_.map().get(pad_id{ - node_j})); // successor must be traversable + assert(rmap_.map().get( + pad_id{ + node_j})); // successor must be traversable warthog::search::search_node* jp_succ = this->generate(pad_id{node_j}); add_neighbour(jp_succ, cost_j); From fe844cfdf8e8d18c4eaf8c1f517d11819e59a567 Mon Sep 17 00:00:00 2001 From: Ryan Hechenberger Date: Fri, 7 Aug 2026 11:17:47 +1000 Subject: [PATCH 10/10] update apply patches to single location --- apps/jps.cpp | 38 +++++++++++++++----------------------- 1 file changed, 15 insertions(+), 23 deletions(-) diff --git a/apps/jps.cpp b/apps/jps.cpp index ce9e2fb..3e034e4 100644 --- a/apps/jps.cpp +++ b/apps/jps.cpp @@ -202,7 +202,19 @@ struct gridmap_scenario // apply snapshot run.snapshot_next(true); run.snapshot_patches(false); - if(!apply_patches()) { return false; } + if(grid_managed) + { + // apply patches to inital grid + if(int c = run.gridmap_apply_patches(grid, patches); c < 0) + { + c = -c - 1; + auto p = run.get_patches()[c]; + WARTHOG_GWARN_FMT( + "failed to apply patch {} at ({},{})", p.patch_id, + p.topleft_x, p.topleft_y); + return false; + } + } } } @@ -243,33 +255,13 @@ struct gridmap_scenario return true; } - /// @brief apply patches from runner to owned grid (if managed) + /// @brief apply patches from runner to owned grids (if managed) /// @return true on success, false otherwise bool apply_patches() { if(!grid_managed) return true; - if(int c = run.gridmap_apply_patches(grid, patches); c < 0) - { - c = -c - 1; - auto p = run.get_patches()[c]; - WARTHOG_GWARN_FMT( - "failed to apply patch {} at ({},{})", p.patch_id, p.topleft_x, - p.topleft_y); - return false; - } - - return true; - } - - /// @brief will update the grid for dynamic scenarios - /// @return true on success, false otherwise - bool - dynamic_grid_update() - { - if(!grid_managed) return true; - // update grid through rgrid interface for(auto& P : run.get_patches()) { @@ -325,7 +317,7 @@ run_experiments( if(patch_count != 0) { - if(!scen.dynamic_grid_update()) + if(!scen.apply_patches()) { // failed to apply patches, exit WARTHOG_GCRIT("dynamic patch error: failed to apply patches");