diff --git a/descartes_light/solvers/include/descartes_light/solvers/bgl/bgl_dijkstra_solver.h b/descartes_light/solvers/include/descartes_light/solvers/bgl/bgl_dijkstra_solver.h new file mode 100644 index 00000000..5bcfbccf --- /dev/null +++ b/descartes_light/solvers/include/descartes_light/solvers/bgl/bgl_dijkstra_solver.h @@ -0,0 +1,43 @@ +#ifndef DESCARTES_LIGHT_SOLVERS_BGL_BGL_DIJKSTRA_SOLVER_H +#define DESCARTES_LIGHT_SOLVERS_BGL_BGL_DIJKSTRA_SOLVER_H + +#include + +namespace descartes_light +{ +/** + * @brief BGL solver implementation that constructs vertices and edges in the build function and uses Dijkstra's + * algorithm with a default visitor to search the graph + */ +template +class BGLDijkstraSVSESolver : public BGLSolverBaseSVSE +{ +public: + using BGLSolverBaseSVSE::BGLSolverBaseSVSE; + + SearchResult search() override; +}; + +using BGLDijkstraSVSESolverF = BGLDijkstraSVSESolver; +using BGLDijkstraSVSESolverD = BGLDijkstraSVSESolver; + +/** + * @brief BGL solver implementation that constructs vertices and edges in the build function and uses Dijkstra's + * algorithm with a visitor that terminates the search once a vertex in the last rung of the graph is encountered rather + * than allowing it to continue until the distance to all nodes in the graph has been calculated + */ +template +class BGLEfficientDijkstraSVSESolver : public BGLSolverBaseSVSE +{ +public: + using BGLSolverBaseSVSE::BGLSolverBaseSVSE; + + SearchResult search() override; +}; + +using BGLEfficientDijkstraSVSESolverF = BGLEfficientDijkstraSVSESolver; +using BGLEfficientDijkstraSVSESolverD = BGLEfficientDijkstraSVSESolver; + +} // namespace descartes_light + +#endif // DESCARTES_LIGHT_SOLVERS_BGL_BGL_DIJKSTRA_SOLVER_H diff --git a/descartes_light/solvers/include/descartes_light/solvers/bgl/bgl_solver.h b/descartes_light/solvers/include/descartes_light/solvers/bgl/bgl_solver.h index 014d90b1..72bfc109 100644 --- a/descartes_light/solvers/include/descartes_light/solvers/bgl/bgl_solver.h +++ b/descartes_light/solvers/include/descartes_light/solvers/bgl/bgl_solver.h @@ -69,11 +69,12 @@ class BGLSolverBase : public Solver }; /** - * @brief BGL solver partial implementation that constructs only vertices in the build function with the assumption that - * edges will be added by discovery during the search + * @brief BGL Solver Static Vertex Dynamic Edge (SVDE) partial implementation + * @details Constructs only vertices in the build function (i.e. statically) with the assumption that edges will be + * added during the search (i.e. dynamically) */ template -class BGLSolverBaseV : public BGLSolverBase +class BGLSolverBaseSVDE : public BGLSolverBase { public: using BGLSolverBase::BGLSolverBase; @@ -84,33 +85,18 @@ class BGLSolverBaseV : public BGLSolverBase }; /** - * @brief BGL solver partial implementation that constructs both vertices and edges in the build function + * @brief BGL solver Static Vertex Static Edge (SVSE) partial implementation + * @details Constructs both vertices and edges in the build function (i.e. statically) */ template -class BGLSolverBaseVE : public BGLSolverBaseV +class BGLSolverBaseSVSE : public BGLSolverBaseSVDE { public: - using BGLSolverBaseV::BGLSolverBaseV; + using BGLSolverBaseSVDE::BGLSolverBaseSVDE; BuildStatus buildImpl(const std::vector::ConstPtr>& trajectory, const std::vector::ConstPtr>& edge_eval, const std::vector::ConstPtr>& state_eval) override; }; -/** - * @brief BGL solver implementation that constructs vertices and edges in the build function and uses Dijkstra's - * algorithm with a default visitor to search the graph - */ -template -class BGLDijkstraSolverVE : public BGLSolverBaseVE -{ -public: - using BGLSolverBaseVE::BGLSolverBaseVE; - - SearchResult search() override; -}; - -using BGLDijkstraSolverVEF = BGLDijkstraSolverVE; -using BGLDijkstraSolverVED = BGLDijkstraSolverVE; - } // namespace descartes_light diff --git a/descartes_light/solvers/include/descartes_light/solvers/bgl/impl/bgl_dijkstra_solver.hpp b/descartes_light/solvers/include/descartes_light/solvers/bgl/impl/bgl_dijkstra_solver.hpp new file mode 100644 index 00000000..a2e5920a --- /dev/null +++ b/descartes_light/solvers/include/descartes_light/solvers/bgl/impl/bgl_dijkstra_solver.hpp @@ -0,0 +1,146 @@ +#ifndef DESCARTES_LIGHT_SOLVERS_BGL_IMPL_BGL_DIJKSTRA_SOLVER_HPP +#define DESCARTES_LIGHT_SOLVERS_BGL_IMPL_BGL_DIJKSTRA_SOLVER_HPP + +#include + +#include +DESCARTES_IGNORE_WARNINGS_PUSH +#include +DESCARTES_IGNORE_WARNINGS_POP + +namespace descartes_light +{ +template +SearchResult BGLDijkstraSVSESolver::search() +{ + // Convenience aliases + auto& graph_ = BGLSolverBase::graph_; + const auto& source_ = BGLSolverBase::source_; + auto& predecessor_map_ = BGLSolverBase::predecessor_map_; + const auto& ladder_rungs_ = BGLSolverBase::ladder_rungs_; + + // Internal properties + auto index_prop_map = boost::get(boost::vertex_index, graph_); + auto weight_prop_map = boost::get(boost::edge_weight, graph_); + auto color_prop_map = boost::get(&Vertex::color, graph_); + auto distance_prop_map = boost::get(&Vertex::distance, graph_); + + predecessor_map_.clear(); + boost::associative_property_map, VertexDesc>> predecessor_prop_map( + predecessor_map_); + + // Perform the search + boost::dijkstra_shortest_paths(graph_, + source_, + predecessor_prop_map, + distance_prop_map, + weight_prop_map, + index_prop_map, + std::less<>(), + std::plus<>(), + std::numeric_limits::max(), + static_cast(0.0), + boost::default_dijkstra_visitor(), + color_prop_map); + + // Find lowest cost node in last rung + auto target = std::min_element(ladder_rungs_.back().begin(), + ladder_rungs_.back().end(), + [&](const VertexDesc& a, const VertexDesc& b) { + return graph_[a].distance < graph_[b].distance; + }); + + SearchResult result; + + // Reconstruct the path from the predecesor map; remove the artificial start state + const auto vd_path = BGLSolverBase::reconstructPath(source_, *target); + result.trajectory = BGLSolverBase::toStates(vd_path); + result.trajectory.erase(result.trajectory.begin()); + + result.cost = graph_[*target].distance; + + return result; +} + +/** + * @brief Visitor for Dijkstra search that terminates the search once a vertex in the last rung has been encountered + */ +template +class DijkstraTerminateEarlyVisitor : public boost::default_dijkstra_visitor +{ +public: + DijkstraTerminateEarlyVisitor(long last_rung_idx) : last_rung_idx_(last_rung_idx) {} + + /** + * @brief Hook for introspecting the search when a new vertex is opened by the search. + * @details Throws the vertex descriptor that is the termination of the path once the first vertex in the last rung of + * the graph is encountered + */ + void examine_vertex(VertexDesc u, const BGLGraph& g) + { + if (g[u].rung_idx == last_rung_idx_) + throw u; + } + +private: + const long last_rung_idx_; +}; + +template +SearchResult BGLEfficientDijkstraSVSESolver::search() +{ + // Convenience aliases + auto& graph_ = BGLSolverBase::graph_; + const auto& source_ = BGLSolverBase::source_; + auto& predecessor_map_ = BGLSolverBase::predecessor_map_; + const auto& ladder_rungs_ = BGLSolverBase::ladder_rungs_; + + // Internal properties + auto index_prop_map = boost::get(boost::vertex_index, graph_); + auto weight_prop_map = boost::get(boost::edge_weight, graph_); + auto color_prop_map = boost::get(&Vertex::color, graph_); + auto distance_prop_map = boost::get(&Vertex::distance, graph_); + + predecessor_map_.clear(); + boost::associative_property_map, VertexDesc>> predecessor_prop_map( + predecessor_map_); + + DijkstraTerminateEarlyVisitor visitor(static_cast(ladder_rungs_.size() - 1)); + + // Perform the search + try + { + boost::dijkstra_shortest_paths(graph_, + source_, + predecessor_prop_map, + distance_prop_map, + weight_prop_map, + index_prop_map, + std::less<>(), + std::plus<>(), + std::numeric_limits::max(), + static_cast(0.0), + visitor, + color_prop_map); + } + catch (const VertexDesc& target) + { + SearchResult result; + + // Reconstruct the path from the predecesor map; remove the artificial start state + const auto vd_path = BGLSolverBase::reconstructPath(source_, target); + result.trajectory = BGLSolverBase::toStates(vd_path); + result.trajectory.erase(result.trajectory.begin()); + + result.cost = graph_[target].distance; + + return result; + } + + // If the visitor never threw the vertex descriptor, there was an issue with the search + throw std::runtime_error("Search failed to encounter vertex associated with the last waypoint in the trajectory"); +} + +} // namespace descartes_light + +#endif // DESCARTES_LIGHT_SOLVERS_BGL_IMPL_BGL_DIJKSTRA_SOLVER_HPP diff --git a/descartes_light/solvers/include/descartes_light/solvers/bgl/impl/bgl_solver.hpp b/descartes_light/solvers/include/descartes_light/solvers/bgl/impl/bgl_solver.hpp index 098520c6..11a442c2 100644 --- a/descartes_light/solvers/include/descartes_light/solvers/bgl/impl/bgl_solver.hpp +++ b/descartes_light/solvers/include/descartes_light/solvers/bgl/impl/bgl_solver.hpp @@ -19,7 +19,6 @@ #include DESCARTES_IGNORE_WARNINGS_PUSH -#include #include #include #include @@ -132,10 +131,10 @@ void BGLSolverBase::writeGraphWithPath(const std::string& filename) c } template -BuildStatus -BGLSolverBaseV::buildImpl(const std::vector::ConstPtr>& trajectory, - const std::vector::ConstPtr>&, - const std::vector::ConstPtr>& state_evaluators) +BuildStatus BGLSolverBaseSVDE::buildImpl( + const std::vector::ConstPtr>& trajectory, + const std::vector::ConstPtr>&, + const std::vector::ConstPtr>& state_evaluators) { // Convenience aliases auto& ladder_rungs_ = BGLSolverBase::ladder_rungs_; @@ -220,17 +219,17 @@ BGLSolverBaseV::buildImpl(const std::vector -BuildStatus -BGLSolverBaseVE::buildImpl(const std::vector::ConstPtr>& trajectory, - const std::vector::ConstPtr>& edge_evaluators, - const std::vector::ConstPtr>& state_evaluators) +BuildStatus BGLSolverBaseSVSE::buildImpl( + const std::vector::ConstPtr>& trajectory, + const std::vector::ConstPtr>& edge_evaluators, + const std::vector::ConstPtr>& state_evaluators) { // Convenience aliases auto& ladder_rungs_ = BGLSolverBase::ladder_rungs_; auto& graph_ = BGLSolverBase::graph_; // Use the base class to build the vertices - BuildStatus status = BGLSolverBaseV::buildImpl(trajectory, edge_evaluators, state_evaluators); + BuildStatus status = BGLSolverBaseSVDE::buildImpl(trajectory, edge_evaluators, state_evaluators); // Build Edges long cnt = 0; @@ -299,56 +298,4 @@ BGLSolverBaseVE::buildImpl(const std::vector -SearchResult BGLDijkstraSolverVE::search() -{ - // Convenience aliases - auto& graph_ = BGLSolverBase::graph_; - const auto& source_ = BGLSolverBase::source_; - auto& predecessor_map_ = BGLSolverBase::predecessor_map_; - const auto& ladder_rungs_ = BGLSolverBase::ladder_rungs_; - - // Internal properties - auto index_prop_map = boost::get(boost::vertex_index, graph_); - auto weight_prop_map = boost::get(boost::edge_weight, graph_); - auto color_prop_map = boost::get(&Vertex::color, graph_); - auto distance_prop_map = boost::get(&Vertex::distance, graph_); - - predecessor_map_.clear(); - boost::associative_property_map, VertexDesc>> predecessor_prop_map( - predecessor_map_); - - // Perform the search - boost::dijkstra_shortest_paths(graph_, - source_, - predecessor_prop_map, - distance_prop_map, - weight_prop_map, - index_prop_map, - std::less<>(), - std::plus<>(), - std::numeric_limits::max(), - static_cast(0.0), - boost::default_dijkstra_visitor(), - color_prop_map); - - // Find lowest cost node in last rung - auto target = std::min_element(ladder_rungs_.back().begin(), - ladder_rungs_.back().end(), - [&](const VertexDesc& a, const VertexDesc& b) { - return graph_[a].distance < graph_[b].distance; - }); - - SearchResult result; - - // Reconstruct the path from the predecesor map; remove the artificial start state - const auto vd_path = BGLSolverBase::reconstructPath(source_, *target); - result.trajectory = BGLSolverBase::toStates(vd_path); - result.trajectory.erase(result.trajectory.begin()); - - result.cost = graph_[*target].distance; - - return result; -} - } // namespace descartes_light diff --git a/descartes_light/solvers/src/bgl/bgl_solver.cpp b/descartes_light/solvers/src/bgl/bgl_solver.cpp index bdee26b4..4aee5211 100644 --- a/descartes_light/solvers/src/bgl/bgl_solver.cpp +++ b/descartes_light/solvers/src/bgl/bgl_solver.cpp @@ -16,6 +16,7 @@ * limitations under the License. */ #include +#include #include namespace descartes_light @@ -25,15 +26,18 @@ namespace descartes_light template class BGLSolverBase; template class BGLSolverBase; -template class BGLSolverBaseV; -template class BGLSolverBaseV; +template class BGLSolverBaseSVDE; +template class BGLSolverBaseSVDE; -template class BGLSolverBaseVE; -template class BGLSolverBaseVE; +template class BGLSolverBaseSVSE; +template class BGLSolverBaseSVSE; // Full Implementations -template class BGLDijkstraSolverVE; -template class BGLDijkstraSolverVE; +template class BGLDijkstraSVSESolver; +template class BGLDijkstraSVSESolver; + +template class BGLEfficientDijkstraSVSESolver; +template class BGLEfficientDijkstraSVSESolver; // Free functions template SubGraph createDecoratedSubGraph(const BGLGraph& g); diff --git a/descartes_light/test/benchmarks/benchmarks.cpp b/descartes_light/test/benchmarks/benchmarks.cpp index 3dd780f0..ae7241a0 100644 --- a/descartes_light/test/benchmarks/benchmarks.cpp +++ b/descartes_light/test/benchmarks/benchmarks.cpp @@ -3,7 +3,7 @@ #include // Solvers #include -#include +#include DESCARTES_IGNORE_WARNINGS_PUSH #include @@ -80,9 +80,13 @@ int main(int, char**) benchmark(SolverFactory()); benchmark(SolverFactory()); - // BGL ladder graph - benchmark(SolverFactory()); - benchmark(SolverFactory()); + // BGL full Dijkstra solver + benchmark(SolverFactory()); + benchmark(SolverFactory()); + + // BGL efficient Dijkstra solver + benchmark(SolverFactory()); + benchmark(SolverFactory()); return 0; } diff --git a/descartes_light/test/include/descartes_light/test/impl/solver_factory.hpp b/descartes_light/test/include/descartes_light/test/impl/solver_factory.hpp index e807588c..61e6c5f1 100644 --- a/descartes_light/test/include/descartes_light/test/impl/solver_factory.hpp +++ b/descartes_light/test/include/descartes_light/test/impl/solver_factory.hpp @@ -2,24 +2,32 @@ #include #include -#include +#include namespace descartes_light { -// Ladder graph solver Factory -template -struct SolverFactory> +// Ladder graph solver factory +template +struct SolverFactory> { - using FloatType = FloatT; - typename Solver::Ptr create() const { return std::make_unique>(1); } + typename Solver::Ptr create() const { return std::make_shared>(1); } }; -// Boost Ladder graph solver Factory -template -struct SolverFactory> +// Boost full Dijkstra graph solver factory +template +struct SolverFactory> { - using FloatType = FloatT; - typename Solver::Ptr create() const { return std::make_unique>(1); } + typename Solver::Ptr create() const { return std::make_shared>(1); } +}; + +// Boost efficient Dijkstra graph solver factory +template +struct SolverFactory> +{ + typename Solver::Ptr create() const + { + return std::make_shared>(1); + } }; } // namespace descartes_light diff --git a/descartes_light/test/include/descartes_light/test/impl/utils.hpp b/descartes_light/test/include/descartes_light/test/impl/utils.hpp index a469e81d..e8eb8e19 100644 --- a/descartes_light/test/include/descartes_light/test/impl/utils.hpp +++ b/descartes_light/test/include/descartes_light/test/impl/utils.hpp @@ -13,15 +13,13 @@ DESCARTES_IGNORE_WARNINGS_POP namespace descartes_light { -static std::mt19937 RAND_GEN(0); - template -typename State::Ptr generateRandomState(Eigen::Index dof) +typename State::Ptr generateRandomState(Eigen::Index dof, std::mt19937& rand_gen) { std::normal_distribution dist; Eigen::Matrix sol(dof); for (Eigen::Index i = 0; i < dof; ++i) - sol(i) = dist(RAND_GEN); + sol(i) = dist(rand_gen); return std::make_shared>(sol); } @@ -29,9 +27,14 @@ typename State::Ptr generateRandomState(Eigen::Index dof) template RandomStateSampler::RandomStateSampler(const Eigen::Index dof, const std::size_t n_samples, + const FloatType state_cost, const std::size_t zero_state_idx, - const FloatType state_cost) - : dof_(dof), n_samples_(n_samples), zero_state_idx_(zero_state_idx), state_cost_(state_cost) + std::shared_ptr rand_gen) + : dof_(dof) + , n_samples_(n_samples) + , state_cost_(state_cost) + , zero_state_idx_(zero_state_idx) + , rand_gen_(std::move(rand_gen)) { } @@ -42,7 +45,7 @@ typename std::vector> RandomStateSampler::samp typename std::vector> waypoints; waypoints.reserve(n_samples_); std::generate_n(std::back_inserter(waypoints), n_samples_, [this]() { - return StateSample{ generateRandomState(dof_), state_cost_ }; + return StateSample{ generateRandomState(dof_, *rand_gen_), state_cost_ }; }); // Set one of the joint states to all zeros @@ -81,20 +84,19 @@ std::vector::ConstPtr> createSamplers(std::size_t dof, std::size_t n_waypoints, std::size_t samples_per_waypoint, FloatType state_cost) { std::vector::ConstPtr> samplers; - std::vector zero_state_indices; samplers.reserve(n_waypoints); - zero_state_indices.reserve(n_waypoints); + auto rand_gen = std::make_shared(0); std::uniform_int_distribution dist(0, samples_per_waypoint - 1); // Create waypoint samplers for (std::size_t i = 0; i < n_waypoints; ++i) { - auto zero_state_idx = dist(RAND_GEN); - zero_state_indices.push_back(zero_state_idx); - samplers.push_back( - std::make_shared>(dof, samples_per_waypoint, zero_state_idx, state_cost)); + const std::size_t zero_state_idx = dist(*rand_gen); + samplers.push_back(std::make_shared>( + dof, samples_per_waypoint, state_cost, zero_state_idx, rand_gen)); } + return samplers; } diff --git a/descartes_light/test/include/descartes_light/test/utils.h b/descartes_light/test/include/descartes_light/test/utils.h index 6c7aad6a..4d8efcda 100644 --- a/descartes_light/test/include/descartes_light/test/utils.h +++ b/descartes_light/test/include/descartes_light/test/utils.h @@ -4,6 +4,11 @@ #include #include +#include +DESCARTES_IGNORE_WARNINGS_PUSH +#include +DESCARTES_IGNORE_WARNINGS_POP + namespace descartes_light { /** @@ -15,16 +20,18 @@ class RandomStateSampler : public WaypointSampler public: RandomStateSampler(const Eigen::Index dof, const std::size_t n_samples, + const FloatType state_cost, const std::size_t zero_state_idx, - const FloatType state_cost); + std::shared_ptr rand_gen); typename std::vector> sample() const override; private: const Eigen::Index dof_; const std::size_t n_samples_; - const std::size_t zero_state_idx_; const FloatType state_cost_; + const std::size_t zero_state_idx_; + std::shared_ptr rand_gen_; }; /** diff --git a/descartes_light/test/src/solver_factory.cpp b/descartes_light/test/src/solver_factory.cpp index 82b5e1a4..661bfea7 100644 --- a/descartes_light/test/src/solver_factory.cpp +++ b/descartes_light/test/src/solver_factory.cpp @@ -4,6 +4,8 @@ namespace descartes_light { template struct SolverFactory; template struct SolverFactory; -template struct SolverFactory; -template struct SolverFactory; +template struct SolverFactory; +template struct SolverFactory; +template struct SolverFactory; +template struct SolverFactory; } // namespace descartes_light diff --git a/descartes_light/test/utest.cpp b/descartes_light/test/utest.cpp index 5a27e84c..6d2f723d 100644 --- a/descartes_light/test/utest.cpp +++ b/descartes_light/test/utest.cpp @@ -1,7 +1,7 @@ #include #include #include -#include +#include #include #include @@ -41,8 +41,10 @@ class SolverFixture : public ::testing::Test using Implementations = ::testing::Types, SolverFactory, - SolverFactory, - SolverFactory>; + SolverFactory, + SolverFactory, + SolverFactory, + SolverFactory>; TYPED_TEST_CASE(SolverFixture, Implementations); @@ -87,7 +89,7 @@ TYPED_TEST(SolverFixture, KnownPathTest) // Total path cost should be zero for edge costs and 2 * state_cost (one from sampling, one from state evaluation) for // state costs FloatType total_cost = static_cast(this->n_waypoints) * this->state_cost * 2; - ASSERT_TRUE(std::abs(result.cost - total_cost) < std::numeric_limits::epsilon()); + ASSERT_DOUBLE_EQ(static_cast(result.cost), static_cast(total_cost)); for (const auto& state : result.trajectory) {