diff --git a/include/hpp/manipulation/path-planner/states-path-finder.hh b/include/hpp/manipulation/path-planner/states-path-finder.hh index 30e4dcd2883b1555b0da6dd7c932b1a7bd4f881b..28d3344b75e5fdda3c535412dce29485284d9281 100644 --- a/include/hpp/manipulation/path-planner/states-path-finder.hh +++ b/include/hpp/manipulation/path-planner/states-path-finder.hh @@ -193,14 +193,53 @@ class HPP_MANIPULATION_DLLAPI StatesPathFinder : public core::PathPlanner { /// Step 4 of the algorithm void preInitializeRHS(std::size_t j, Configuration_t& q); - bool analyseOptimizationProblem(const graph::Edges_t& transitions); - bool analyseOptimizationProblem2(const graph::Edges_t& transitions, + bool analyseOptimizationProblem(const graph::Edges_t& transitions, core::ProblemConstPtr_t _problem); /// Step 5 of the algorithm void initializeRHS(std::size_t j); bool solveOptimizationProblem(); + // Data structure used to store a constraint right hand side as value and its name as key, both in form + // of hash numbers (so that names and rhs of two constraints can be easily merge). + // Exemple : ConstraintMap_t map = {{nameStringHash, rhsVectorHash}}; + // With rhsVectorHash the hash of a vector_t of rights hand side constraints with hashRHS, and nameStringHash + // the hash of a std::string - obtained for instance with std::hash. + typedef std::unordered_map<size_t, size_t> ConstraintMap_t; //map (name, rhs) + + /// @brief Get a configuration in accordance with the statuts matrix at a step j for the constraint i + /// @param i number of the constraint in the status matrix + /// @param j step of the potential solution (index of a waypoint) + /// @return a configuration Configuration_t which follows the status matrix indication at the given indices + Configuration_t getConfigStatus(size_type i, size_type j) const; + + /// @brief Get the right hand side of a constraint w.r.t a set configuration for this constraint + /// @param constraint the constraint to compute the right hand side of + /// @param q the configuration in which the constraint is set + /// @return a right hand side vector_t + vector_t getConstraintRHS(ImplicitPtr_t constraint, Configuration_t q) const; + + /// @brief Hash a vector of right hand side into a long unsigned integer + /// @param rhs the right hand side vector vector_t + /// @return a size_t integer hash + size_t hashRHS(vector_t rhs) const; + + /// @brief Check if a solution (a list of transition) contains impossible to solve steps due to inevitable collisions + /// @param pairMap The ConstraintMap_tf table of pairs of incompatibles constraints + /// @param constraintMap The hasmap table of constraints which are in pairMap + /// @return a bool which is true is there is no impossible to solve steps, true otherwise + bool checkSolvers(ConstraintMap_t const & pairMap, ConstraintMap_t const & constraintMap) const; + + /// @brief For a certain step wp during solving check for collision impossible to solve. + /// @param pairMap The ConstraintMap_t table of pairs of incompatibles constraints + /// @param constraintMap The hasmap table of constraints which are in pairMap + /// @param wp The index of the current step + /// @return a bool which is true if there is no collision or impossible to solve ones, false otherwise. + bool saveIncompatibleRHS(ConstraintMap_t & pairMap, ConstraintMap_t & constraintMap, size_type const wp); + + // For a joint get his most, constrained with it, far parent + core::JointConstPtr_t maximalJoint(size_t const wp, core::JointConstPtr_t a); + /// Step 6 of the algorithm core::Configurations_t getConfigList() const; diff --git a/src/path-planner/states-path-finder.cc b/src/path-planner/states-path-finder.cc index 562aaf582c366d55809d7182153be3d3e351cda2..0c6db52c50b0ff3a4568814908f44ba974974b4c 100644 --- a/src/path-planner/states-path-finder.cc +++ b/src/path-planner/states-path-finder.cc @@ -17,6 +17,7 @@ // received a copy of the GNU Lesser General Public License along with // hpp-manipulation. If not, see <http://www.gnu.org/licenses/>. +#define HPP_DEBUG #include <hpp/constraints/affine-function.hh> #include <hpp/constraints/explicit.hh> #include <hpp/constraints/locked-joint.hh> @@ -44,10 +45,16 @@ #include <hpp/util/exception-factory.hh> #include <hpp/util/timer.hh> #include <map> +#include <unordered_map> #include <pinocchio/fwd.hpp> #include <pinocchio/multibody/model.hpp> #include <queue> #include <vector> +#include <typeinfo> +#include <ranges> +#include <iomanip> +#include <algorithm> +#include <stack> namespace hpp { namespace manipulation { @@ -72,6 +79,7 @@ using core::problemTarget::TaskTarget; using core::problemTarget::TaskTargetPtr_t; #ifdef HPP_DEBUG +/* static void displayRoadmap(const core::RoadmapPtr_t& roadmap) { unsigned i = 0; for (auto cc : roadmap->connectedComponents()) { @@ -80,7 +88,7 @@ static void displayRoadmap(const core::RoadmapPtr_t& roadmap) { hppDout(info, pinocchio::displayConfig(n->configuration())); } } -} +}*/ #endif StatesPathFinder::StatesPathFinder(const core::ProblemConstPtr_t& problem, @@ -879,71 +887,6 @@ void StatesPathFinder::preInitializeRHS(std::size_t j, Configuration_t& q) { } bool StatesPathFinder::analyseOptimizationProblem( - const graph::Edges_t& transitions) { - OptimizationData& d = *optData_; - size_type nTriesMax = - problem_->getParameter("StatesPathFinder/maxTriesCollisionAnalysis") - .intValue(); - if (nTriesMax == 0) return true; - for (std::size_t wp = 1; wp <= d.solvers.size(); wp++) { - std::size_t j = wp - 1; - const Solver_t& solver = d.solvers[j]; - using namespace core; - Solver_t::Status status; - size_type tries = 0; - Configuration_t q; - do { - q = problem()->configurationShooter()->shoot(); - preInitializeRHS(j, q); - status = solver.solve(q, constraints::solver::lineSearch::Backtracking()); - } while ((status != Solver_t::SUCCESS) && (++tries <= nTriesMax)); - if (tries > nTriesMax) { - hppDout(info, "Collision analysis stopped at WP " - << wp << " because of too many bad solve statuses"); - return false; - } - CollisionValidationPtr_t collisionValidations = - CollisionValidation::create(problem_->robot()); - collisionValidations->checkParameterized(true); - collisionValidations->computeAllContacts(true); - ValidationReportPtr_t validationReport; - bool ok = true; - if (!collisionValidations->validate(q, validationReport)) { - AllCollisionsValidationReportPtr_t allReports = - HPP_DYNAMIC_PTR_CAST(AllCollisionsValidationReport, validationReport); - assert(allReports); - std::size_t nbReports = allReports->collisionReports.size(); - hppDout(info, wp << " nbReports: " << nbReports); - for (std::size_t i = 0; i < nbReports; i++) { - CollisionValidationReportPtr_t& report = - allReports->collisionReports[i]; - JointConstPtr_t j1 = report->object1->joint(); - JointConstPtr_t j2 = report->object2->joint(); - if (!j1 || !j2) continue; - const EdgePtr_t& edge = transitions[wp]; - RelativeMotion::matrix_type m = edge->relativeMotion(); - RelativeMotion::RelativeMotionType rmt = - m(RelativeMotion::idx(j1), RelativeMotion::idx(j2)); - hppDout(info, "report " << i << " joints names \n" - << j1->name() << "\n" - << j2->name() << "\n" - << rmt); - if (rmt == RelativeMotion::RelativeMotionType::Unconstrained) continue; - ok = false; - break; - } - } - if (!ok) { - hppDout(info, "Analysis found a collision at WP " << wp); - return false; - } - hppDout(info, "Analysis at WP " << wp << " passed after " << tries - << " solve tries"); - } - return true; -} - -bool StatesPathFinder::analyseOptimizationProblem2( const graph::Edges_t& transitions, core::ProblemConstPtr_t _problem) { typedef constraints::JointConstPtr_t JointConstPtr_t; typedef core::RelativeMotion RelativeMotion; @@ -1150,7 +1093,266 @@ std::string StatesPathFinder::displayConfigsSolved() const { return ans; } +// Get a configuration in accordance with the statuts matrix at a step j for the constraint i +Configuration_t StatesPathFinder::getConfigStatus(size_type i, size_type j) const { + switch (optData_ -> M_status(i, j)) { + case OptimizationData::EQUAL_TO_PREVIOUS: + return optData_ -> waypoint.col(j - 1); + case OptimizationData::EQUAL_TO_INIT: + return optData_ -> q1; + case OptimizationData::EQUAL_TO_GOAL: + return optData_ -> q2; + default: + return optData_ -> waypoint.col(j); + } +} + +// Get the right hand side of a constraint w.r.t a set configuration for this constraint +vector_t StatesPathFinder::getConstraintRHS(ImplicitPtr_t constraint, Configuration_t q) const { + LiegroupElement rhs(constraint -> copy() -> function().outputSpace()); + constraint -> rightHandSideFromConfig(q, rhs); + return rhs.vector(); +} + +// Hash a vector of right hand side into a long unsigned integer +size_t StatesPathFinder::hashRHS(vector_t rhs) const { + std::stringstream ss; + ss << std::setprecision(3) << (0.01 < rhs.array().abs()).select(rhs, 0.0f); + return std::hash<std::string>{}(ss.str()); +} + +// Check if a solution (a list of transition) contains impossible to solve steps due to inevitable collisions +// A step is impossible to solve if it has two constraints set from init or goal which have produce a collision between +// objects constrained by them. +// The list of such known constraint pairs are memorized in pairMap table and individually in constraintMap. +// +// Return : true if no impossible to solve steps, false otherwise +bool StatesPathFinder::checkSolvers(ConstraintMap_t const & pairMap, ConstraintMap_t const & constraintMap) const { + // do nothing if there is no known incompatible constraint pairs. + if (constraintMap.empty()) + return true; + + // for each steps of the solution + for (long unsigned i{0} ; i < optData_ -> solvers.size() ; i++) { + std::vector<std::pair<ImplicitPtr_t, Configuration_t>> constraints {}; + + // gather all constraints of this step which are set from init or goal configuration + for (long unsigned j{0} ; j < constraints_.size() ; j++) { + auto c = constraints_[j]; + auto status = optData_ -> M_status(j, i); + if( + status != OptimizationData::EQUAL_TO_INIT && + status != OptimizationData::EQUAL_TO_GOAL + ) // if not init or goal + continue; + auto q = getConfigStatus(j, i); + auto name = std::hash<std::string>{}(c -> function().name()); + if (constraintMap.count(name)) + constraints.push_back(std::make_pair(c, q)); + } + + // if there are less than two constraints gathered, go to next step + if (constraints.size() < 2) + continue; + + // else, check if there is a pair of constraints in the table of known incompatible pairs + for (auto & c1 : constraints) { + auto rhs_1 = hashRHS(getConstraintRHS(std::get<0>(c1), std::get<1>(c1))); + auto name_1 = std::hash<std::string>{}(std::get<0>(c1) -> function().name()); + if (constraintMap.count(name_1)) + for (auto & c2 : constraints) { + auto rhs_2 = hashRHS(getConstraintRHS(std::get<0>(c2), std::get<1>(c2))); + auto name_2 = std::hash<std::string>{}(std::get<0>(c2) -> function().name()); + auto namesPair = name_1 * name_2; + auto rhsPair = rhs_1 * rhs_2; + if (name_1 != name_2 && pairMap.count(namesPair) && pairMap.at(namesPair) == rhsPair) + return false; + } + } + } + + return true; +} + +core::JointConstPtr_t StatesPathFinder::maximalJoint(size_t const wp, core::JointConstPtr_t a) { + const auto & current_edge = lastBuiltTransitions_[wp + 1]; + core::RelativeMotion::matrix_type m = current_edge -> relativeMotion(); + + size_t ida = core::RelativeMotion::idx(a); + + core::JointConstPtr_t last = nullptr; + core::JointConstPtr_t current = a; + + while(current != nullptr) { + auto parent = current -> parentJoint(); + size_t idp = core::RelativeMotion::idx(parent); + last = current; + if (parent && m(ida, idp)) + current = current -> parentJoint(); + else break; + } + + return last; +} + +// For a certain step wp during solving check for collision impossible to solve. +// If there is any store the constraints involved and stop the resolution +// +// Return : true if there is no collision or impossible to solve ones, false otherwise. +bool StatesPathFinder::saveIncompatibleRHS(ConstraintMap_t & pairMap, ConstraintMap_t & constraintMap, size_type const wp) { + core::ValidationReportPtr_t validationReport; + auto q = optData_->waypoint.col(wp -1); + bool nocollision {true}; + + core::CollisionValidationPtr_t collisionValidations = + core::CollisionValidation::create(problem_->robot()); + collisionValidations->checkParameterized(true); + collisionValidations->computeAllContacts(true); + + // If there was a collision in the last configuration + if (!collisionValidations->validate(q, validationReport)) { + auto allReports = HPP_DYNAMIC_PTR_CAST ( + core::AllCollisionsValidationReport, validationReport + ); + + // check all collision reports between joints + for (auto & report : allReports -> collisionReports) { + + // Store the two joints involved + core::JointConstPtr_t j1 = report -> object1 -> joint(); + core::JointConstPtr_t j2 = report -> object2 -> joint(); + + // check that there is indeed two joints + if (!j1 || !j2) + return nocollision; + + // get the maximal parent joint which are constrained with their child + j1 = maximalJoint(wp, j1); + j2 = maximalJoint(wp, j2); + + // Function to check if two joints are equals, bye their address is nullptr, by their value otherwise + auto equalJoints = [] (core::JointConstPtr_t a, core::JointConstPtr_t b) { + return (a && b) ? *a==*b : a==b; + }; + + //std::cout << "test are constrained for j1 & j2: " << areConstrained(wp, j1, j2) << std::endl; + + typedef std::pair<core::JointConstPtr_t,size_t> jointOfConstraint; + + // Get all constraints which involve a joint + auto associatedConstraints = [&] (core::JointConstPtr_t j) { + std::vector<jointOfConstraint> constraints {}; + for (size_t i{0} ; i < constraints_.size() ; i++) { + auto constraint = constraints_[i]; + auto joints = constraint -> doesConstrainRelPoseBetween(problem_->robot()); + if (equalJoints(j, std::get<0>(joints)) || equalJoints(j, std::get<1>(joints))) { + auto joint = (j == std::get<0>(joints)) ? std::get<1>(joints) : std::get<0>(joints); + constraints.push_back(std::make_pair(joint,i)); + } + } + return constraints; + }; + + // We get all the constraints which contain j1 + auto constraints_j1 = associatedConstraints(j1); + + const auto & current_edge = lastBuiltTransitions_[wp + 1]; + core::RelativeMotion::matrix_type m = current_edge -> relativeMotion(); + + std::vector<short> visited (constraints_.size(), 0); + std::function<std::vector<int>(core::JointConstPtr_t , jointOfConstraint, int)> exploreJOC; + + // Check if a joint is indirectly co-constrained with an other, return the indices of their respective constraints + exploreJOC = [&] (core::JointConstPtr_t j, jointOfConstraint current, int initial) { + int constraint_index = std::get<1>(current); + visited[constraint_index]++; + core::JointConstPtr_t current_joint = std::get<0>(current); + auto iconstraint = constraints_[constraint_index]; + auto JOCs = associatedConstraints(current_joint); + auto id_cj = core::RelativeMotion::idx(current_joint); + + for (auto & joc : JOCs) { + int ci = std::get<1>(joc); + core::JointConstPtr_t ji = std::get<0>(joc); + auto id_ji = core::RelativeMotion::idx(ji); + if (m(id_cj, id_ji) == core::RelativeMotion::RelativeMotionType::Unconstrained) + continue; + if (equalJoints(ji, j)) + return std::vector<int>{initial, ci}; + else if (visited[ci] < 2) { + return exploreJOC(j, joc, initial); + } + } + return std::vector<int>{-1, -1}; + }; + + // get the indices of the constraints associated to the two joints + auto getIndices = [&] -> std::vector<int> { + for (auto & joc : constraints_j1) { + visited[std::get<1>(joc)] = 1; + auto indices = exploreJOC(j2, joc, std::get<1>(joc)); + if (indices[0] >= 0 || indices[1] >= 0) + return indices; + } + return std::vector<int>{-1, -1}; + }; + + auto indices = getIndices(); + + // Make sure indices are all defined + if (indices[0] < 0 || indices[1] < 0) + return nocollision; + + // for each of the two constraint identified + std::vector<std::pair<size_t, size_t>> constraints {}; + for (auto ic : indices) { + + // check that there are set from goal or init configurations + auto status = optData_ -> M_status((size_t)ic, wp - 1); + if( + status != OptimizationData::EQUAL_TO_INIT && + status != OptimizationData::EQUAL_TO_GOAL + ) + return nocollision; + + // if so prepare to store them in the tables of known incompatible constraints + auto c = constraints_[(size_t)ic]; + auto rhs = hashRHS(getConstraintRHS(c, q)); + auto name = std::hash<std::string>{}(c -> function().name()); + + constraints.push_back(std::make_pair(name, rhs)); + } + + // then add them both in the tables of incompatible constraints + nocollision = false; + + // first, individually, in constraintMap + for (auto & c : constraints) + if (!constraintMap.count(std::get<0>(c))) + constraintMap[std::get<0>(c)] = std::get<1>(c); + + // then, both merged together in pairMap + auto names = std::get<0>(constraints[0]) * std::get<0>(constraints[1]); //key + auto rhs = std::get<1>(constraints[0]) * std::get<1>(constraints[1]); //value + + if (!pairMap.count(names)) + pairMap[names] = rhs; + } + } + + return nocollision; +} + bool StatesPathFinder::solveOptimizationProblem() { + static ConstraintMap_t pairMap {}; + static ConstraintMap_t constraintMap {}; + + // check if the solution is feasible (no inevitable collision), if not abort the solving + if (!checkSolvers(pairMap, constraintMap)) { + hppDout(info, "Path is invalid for collision is inevitable"); + return false; + } + OptimizationData& d = *optData_; // Try to solve with sets of random configs for each waypoint std::size_t nTriesMax = @@ -1260,6 +1462,12 @@ bool StatesPathFinder::solveOptimizationProblem() { // much, go back to first // waypoint case SolveStepStatus::COLLISION_AFTER: + // if collision check that it is not due to non-solvable constraints, + // if so, store the constraints involved and abort the solving + if (!saveIncompatibleRHS(pairMap, constraintMap, wp)) { + hppDout(info, "Path is invalid, found inevitable collision"); + return false; + } nFails++; break; default: @@ -1268,7 +1476,7 @@ bool StatesPathFinder::solveOptimizationProblem() { } displayConfigsSolved(); - // displayRhsMatrix (); + displayRhsMatrix (); return true; } @@ -1296,7 +1504,6 @@ core::Configurations_t StatesPathFinder::computeConfigList( ConfigurationIn_t q1, ConfigurationIn_t q2) { const graph::GraphPtr_t& graph(problem_->constraintGraph()); GraphSearchData& d = *graphData_; - size_t& idxSol = d.idxSol; bool maxDepthReached; @@ -1324,7 +1531,7 @@ core::Configurations_t StatesPathFinder::computeConfigList( if (buildOptimizationProblem(transitions)) { lastBuiltTransitions_ = transitions; if (nTryConfigList_ > 0 || - analyseOptimizationProblem2(transitions, problem())) { + analyseOptimizationProblem(transitions, problem())) { if (solveOptimizationProblem()) { core::Configurations_t path = getConfigList(); hppDout(warning, @@ -1344,7 +1551,7 @@ core::Configurations_t StatesPathFinder::computeConfigList( } transitions = getTransitionList(d, ++idxSol); // reset of the number of tries for a sequence - nTryConfigList_ = 0; + // nTryConfigList_ = 0; } } core::Configurations_t empty_path;