moveit2
The MoveIt Motion Planning Framework for ROS 2.
Loading...
Searching...
No Matches
model_based_planning_context.cpp
Go to the documentation of this file.
1/*********************************************************************
2 * Software License Agreement (BSD License)
3 *
4 * Copyright (c) 2012, Willow Garage, Inc.
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 *
11 * * Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 * * Redistributions in binary form must reproduce the above
14 * copyright notice, this list of conditions and the following
15 * disclaimer in the documentation and/or other materials provided
16 * with the distribution.
17 * * Neither the name of Willow Garage nor the names of its
18 * contributors may be used to endorse or promote products derived
19 * from this software without specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
25 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
27 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
29 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
31 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32 * POSSIBILITY OF SUCH DAMAGE.
33 *********************************************************************/
34
35/* Author: Ioan Sucan */
36
37#include <boost/algorithm/string/trim.hpp>
38#include <boost/algorithm/string/split.hpp>
39#include <boost/algorithm/string/replace.hpp>
40#include <boost/lexical_cast.hpp>
41
42#include <cstdint>
50
52
55
56#include <ompl/config.h>
57#include <ompl/base/samplers/UniformValidStateSampler.h>
58#include <ompl/base/goals/GoalLazySamples.h>
59#include <ompl/tools/config/SelfConfig.h>
60#include <ompl/base/spaces/SE3StateSpace.h>
61#include <ompl/datastructures/PDF.h>
62#include <ompl/base/terminationconditions/IterationTerminationCondition.h>
63#include <ompl/base/terminationconditions/CostConvergenceTerminationCondition.h>
64
65#include <ompl/base/objectives/PathLengthOptimizationObjective.h>
66#include <ompl/base/objectives/MechanicalWorkOptimizationObjective.h>
67#include <ompl/base/objectives/MinimaxObjective.h>
68#include <ompl/base/objectives/StateCostIntegralObjective.h>
69#include <ompl/base/objectives/MaximizeMinClearanceObjective.h>
70#include <ompl/geometric/planners/prm/LazyPRM.h>
71
72namespace ompl_interface
73{
74namespace
75{
76rclcpp::Logger getLogger()
77{
78 return moveit::getLogger("moveit.planners.ompl.model_based_planning_context");
79}
80} // namespace
81
84 : planning_interface::PlanningContext(name, spec.state_space_->getJointModelGroup()->getName())
85 , spec_(spec)
86 , complete_initial_robot_state_(spec.state_space_->getRobotModel())
89 , ompl_parallel_plan_(ompl_simple_setup_->getProblemDefinition())
90 , ptc_(nullptr)
91 , last_plan_time_(0.0)
99 , multi_query_planning_enabled_(false) // maintain "old" behavior by default
100 , simplify_solutions_(true)
101 , interpolate_(true)
102 , hybridize_(true)
103{
104 complete_initial_robot_state_.setToDefaultValues(); // avoid uninitialized memory
106
107 constraints_library_ = std::make_shared<ConstraintsLibrary>(this);
108}
109
110void ModelBasedPlanningContext::configure(const rclcpp::Node::SharedPtr& node, bool use_constraints_approximations)
111{
113 if (!use_constraints_approximations)
114 {
115 setConstraintsApproximations(ConstraintsLibraryPtr());
116 }
118 ompl_simple_setup_->getStateSpace()->computeSignature(space_signature_);
119 ompl_simple_setup_->getStateSpace()->setStateSamplerAllocator(
120 [this](const ompl::base::StateSpace* ss) { return allocPathConstrainedSampler(ss); });
121
122 if (spec_.constrained_state_space_)
123 {
124 // convert the input state to the corresponding OMPL state
125 ompl::base::ScopedState<> ompl_start_state(spec_.constrained_state_space_);
126 spec_.state_space_->copyToOMPLState(ompl_start_state.get(), getCompleteInitialRobotState());
127 ompl_simple_setup_->setStartState(ompl_start_state);
128 ompl_simple_setup_->setStateValidityChecker(std::make_shared<ConstrainedPlanningStateValidityChecker>(this));
129 }
130 else
131 {
132 // convert the input state to the corresponding OMPL state
133 ompl::base::ScopedState<> ompl_start_state(spec_.state_space_);
134 spec_.state_space_->copyToOMPLState(ompl_start_state.get(), getCompleteInitialRobotState());
135 ompl_simple_setup_->setStartState(ompl_start_state);
136 ompl_simple_setup_->setStateValidityChecker(std::make_shared<StateValidityChecker>(this));
137 }
138
140 {
141 const ConstraintApproximationPtr& constraint_approx =
142 constraints_library_->getConstraintApproximation(path_constraints_msg_);
143 if (constraint_approx)
144 {
145 getOMPLStateSpace()->setInterpolationFunction(constraint_approx->getInterpolationFunction());
146 RCLCPP_INFO(getLogger(), "Using precomputed interpolation states");
147 }
148 }
149
150 useConfig();
151 if (ompl_simple_setup_->getGoal())
152 ompl_simple_setup_->setup();
153}
154
156{
157 if (!spec_.state_space_)
158 {
159 RCLCPP_ERROR(getLogger(), "No state space is configured yet");
160 return;
161 }
162 ob::ProjectionEvaluatorPtr projection_eval = getProjectionEvaluator(peval);
163 if (projection_eval)
164 spec_.state_space_->registerDefaultProjection(projection_eval);
165}
166
167ompl::base::ProjectionEvaluatorPtr ModelBasedPlanningContext::getProjectionEvaluator(const std::string& peval) const
168{
169 if (peval.find_first_of("link(") == 0 && peval[peval.length() - 1] == ')')
170 {
171 std::string link_name = peval.substr(5, peval.length() - 6);
172 if (getRobotModel()->hasLinkModel(link_name))
173 {
174 return std::make_shared<ProjectionEvaluatorLinkPose>(this, link_name);
175 }
176 else
177 {
178 RCLCPP_ERROR(getLogger(),
179 "Attempted to set projection evaluator with respect to position of link '%s', "
180 "but that link is not known to the kinematic model.",
181 link_name.c_str());
182 }
183 }
184 else if (peval.find_first_of("joints(") == 0 && peval[peval.length() - 1] == ')')
185 {
186 std::string joints = peval.substr(7, peval.length() - 8);
187 boost::replace_all(joints, ",", " ");
188 std::vector<unsigned int> j;
189 std::stringstream ss(joints);
190 while (ss.good() && !ss.eof())
191 {
192 std::string joint;
193 ss >> joint >> std::ws;
194 if (getJointModelGroup()->hasJointModel(joint))
195 {
196 unsigned int variable_count = getJointModelGroup()->getJointModel(joint)->getVariableCount();
197 if (variable_count > 0)
198 {
199 int idx = getJointModelGroup()->getVariableGroupIndex(joint);
200 for (unsigned int q = 0; q < variable_count; ++q)
201 {
202 j.push_back(idx + q);
203 }
204 }
205 else
206 {
207 RCLCPP_WARN(getLogger(), "%s: Ignoring joint '%s' in projection since it has 0 DOF", name_.c_str(),
208 joint.c_str());
209 }
210 }
211 else
212 {
213 RCLCPP_ERROR(getLogger(),
214 "%s: Attempted to set projection evaluator with respect to value of joint "
215 "'%s', but that joint is not known to the group '%s'.",
216 name_.c_str(), joint.c_str(), getGroupName().c_str());
217 }
218 }
219 if (j.empty())
220 {
221 RCLCPP_ERROR(getLogger(), "%s: No valid joints specified for joint projection", name_.c_str());
222 }
223 else
224 {
225 return std::make_shared<ProjectionEvaluatorJointValue>(this, j);
226 }
227 }
228 else
229 {
230 RCLCPP_ERROR(getLogger(), "Unable to allocate projection evaluator based on description: '%s'", peval.c_str());
231 }
232 return ob::ProjectionEvaluatorPtr();
233}
234
235ompl::base::StateSamplerPtr
236ModelBasedPlanningContext::allocPathConstrainedSampler(const ompl::base::StateSpace* state_space) const
237{
238 if (spec_.state_space_.get() != state_space)
239 {
240 RCLCPP_ERROR(getLogger(), "%s: Attempted to allocate a state sampler for an unknown state space", name_.c_str());
241 return ompl::base::StateSamplerPtr();
242 }
243
244 RCLCPP_DEBUG(getLogger(), "%s: Allocating a new state sampler (attempts to use path constraints)", name_.c_str());
245
247 {
249 {
250 const ConstraintApproximationPtr& constraint_approx =
251 constraints_library_->getConstraintApproximation(path_constraints_msg_);
252 if (constraint_approx)
253 {
254 ompl::base::StateSamplerAllocator state_sampler_allocator =
255 constraint_approx->getStateSamplerAllocator(path_constraints_msg_);
256 if (state_sampler_allocator)
257 {
258 ompl::base::StateSamplerPtr state_sampler = state_sampler_allocator(state_space);
259 if (state_sampler)
260 {
261 RCLCPP_INFO(getLogger(),
262 "%s: Using precomputed state sampler (approximated constraint space) for constraint '%s'",
263 name_.c_str(), path_constraints_msg_.name.c_str());
264 return state_sampler;
265 }
266 }
267 }
268 }
269
270 constraint_samplers::ConstraintSamplerPtr constraint_sampler;
271 if (spec_.constraint_sampler_manager_)
272 {
273 constraint_sampler = spec_.constraint_sampler_manager_->selectSampler(getPlanningScene(), getGroupName(),
274 path_constraints_->getAllConstraints());
275 }
276
277 if (constraint_sampler)
278 {
279 RCLCPP_INFO(getLogger(), "%s: Allocating specialized state sampler for state space", name_.c_str());
280 return std::make_shared<ConstrainedSampler>(this, constraint_sampler);
281 }
282 }
283 RCLCPP_DEBUG(getLogger(), "%s: Allocating default state sampler for state space", name_.c_str());
284 return state_space->allocDefaultStateSampler();
285}
286
288{
289 const std::map<std::string, std::string>& config = spec_.config_;
290 if (config.empty())
291 return;
292 std::map<std::string, std::string> cfg = config;
293
294 // set the distance between waypoints when interpolating and collision checking.
295 auto it = cfg.find("longest_valid_segment_fraction");
296 // If one of the two variables is set.
297 if (it != cfg.end() || max_solution_segment_length_ != 0.0)
298 {
299 // clang-format off
300 double longest_valid_segment_fraction_config = (it != cfg.end())
301 ? moveit::core::toDouble(it->second) // value from config file if there
302 : 0.01; // default value in OMPL.
303 double longest_valid_segment_fraction_final = longest_valid_segment_fraction_config;
305 {
306 // If this parameter is specified too, take the most conservative of the two variables,
307 // i.e. the one that uses the shorter segment length.
308 longest_valid_segment_fraction_final = std::min(
309 longest_valid_segment_fraction_config,
310 max_solution_segment_length_ / spec_.state_space_->getMaximumExtent()
311 );
312 }
313 // clang-format on
314
315 // convert to string using no locale
316 cfg["longest_valid_segment_fraction"] = moveit::core::toString(longest_valid_segment_fraction_final);
317 }
318
319 // set the projection evaluator
320 it = cfg.find("projection_evaluator");
321 if (it != cfg.end())
322 {
323 setProjectionEvaluator(boost::trim_copy(it->second));
324 cfg.erase(it);
325 }
326
327 if (cfg.empty())
328 {
329 return;
330 }
331
332 std::string optimizer;
333 ompl::base::OptimizationObjectivePtr objective;
334 it = cfg.find("optimization_objective");
335 if (it != cfg.end())
336 {
337 optimizer = it->second;
338 cfg.erase(it);
339
340 if (optimizer == "PathLengthOptimizationObjective")
341 {
342 objective =
343 std::make_shared<ompl::base::PathLengthOptimizationObjective>(ompl_simple_setup_->getSpaceInformation());
344 }
345 else if (optimizer == "MinimaxObjective")
346 {
347 objective = std::make_shared<ompl::base::MinimaxObjective>(ompl_simple_setup_->getSpaceInformation());
348 }
349 else if (optimizer == "StateCostIntegralObjective")
350 {
351 objective = std::make_shared<ompl::base::StateCostIntegralObjective>(ompl_simple_setup_->getSpaceInformation());
352 }
353 else if (optimizer == "MechanicalWorkOptimizationObjective")
354 {
355 objective =
356 std::make_shared<ompl::base::MechanicalWorkOptimizationObjective>(ompl_simple_setup_->getSpaceInformation());
357 }
358 else if (optimizer == "MaximizeMinClearanceObjective")
359 {
360 objective =
361 std::make_shared<ompl::base::MaximizeMinClearanceObjective>(ompl_simple_setup_->getSpaceInformation());
362 }
363 else
364 {
365 RCLCPP_WARN(getLogger(),
366 "Optimization objective %s is invalid or not defined, using PathLengthOptimizationObjective instead",
367 optimizer.c_str());
368 objective =
369 std::make_shared<ompl::base::PathLengthOptimizationObjective>(ompl_simple_setup_->getSpaceInformation());
370 }
371
372 ompl_simple_setup_->setOptimizationObjective(objective);
373 }
374
375 // Don't clear planner data if multi-query planning is enabled
376 it = cfg.find("multi_query_planning_enabled");
377 if (it != cfg.end())
378 {
379 multi_query_planning_enabled_ = boost::lexical_cast<bool>(it->second);
380 }
381
382 // check whether the path returned by the planner should be interpolated
383 it = cfg.find("interpolate");
384 if (it != cfg.end())
385 {
386 interpolate_ = boost::lexical_cast<bool>(it->second);
387 cfg.erase(it);
388 }
389
390 // check whether the path returned by the planner should be simplified
391 it = cfg.find("simplify_solutions");
392 if (it != cfg.end())
393 {
394 simplify_solutions_ = boost::lexical_cast<bool>(it->second);
395 cfg.erase(it);
396 }
397
398 // check whether solution paths from parallel planning should be hybridized
399 it = cfg.find("hybridize");
400 if (it != cfg.end())
401 {
402 hybridize_ = boost::lexical_cast<bool>(it->second);
403 cfg.erase(it);
404 }
405
406 // remove the 'type' parameter; the rest are parameters for the planner itself
407 it = cfg.find("type");
408 if (it == cfg.end())
409 {
410 if (name_ != getGroupName())
411 RCLCPP_WARN(getLogger(), "%s: Attribute 'type' not specified in planner configuration", name_.c_str());
412 }
413 else
414 {
415 std::string type = it->second;
416 cfg.erase(it);
417 const std::string planner_name = getGroupName() + "/" + name_;
418 ompl_simple_setup_->setPlannerAllocator(
419 [planner_name, &spec = spec_, allocator = spec_.planner_selector_(type)](
420 const ompl::base::SpaceInformationPtr& si) { return allocator(si, planner_name, spec); });
421 RCLCPP_INFO(getLogger(),
422 "Planner configuration '%s' will use planner '%s'. "
423 "Additional configuration parameters will be set when the planner is constructed.",
424 name_.c_str(), type.c_str());
425 }
426
427 // call the setParams() after setup(), so we know what the params are
428 ompl_simple_setup_->getSpaceInformation()->setup();
429 ompl_simple_setup_->getSpaceInformation()->params().setParams(cfg, true);
430 // call setup() again for possibly new param values
431 ompl_simple_setup_->getSpaceInformation()->setup();
432}
433
434void ModelBasedPlanningContext::setPlanningVolume(const moveit_msgs::msg::WorkspaceParameters& wparams)
435{
436 if (wparams.min_corner.x == wparams.max_corner.x && wparams.min_corner.x == 0.0 &&
437 wparams.min_corner.y == wparams.max_corner.y && wparams.min_corner.y == 0.0 &&
438 wparams.min_corner.z == wparams.max_corner.z && wparams.min_corner.z == 0.0)
439 {
440 RCLCPP_WARN(getLogger(), "It looks like the planning volume was not specified.");
441 }
442
443 RCLCPP_DEBUG(getLogger(),
444 "%s: Setting planning volume (affects SE2 & SE3 joints only) to x = [%f, %f], y = "
445 "[%f, %f], z = [%f, %f]",
446 name_.c_str(), wparams.min_corner.x, wparams.max_corner.x, wparams.min_corner.y, wparams.max_corner.y,
447 wparams.min_corner.z, wparams.max_corner.z);
448
449 spec_.state_space_->setPlanningVolume(wparams.min_corner.x, wparams.max_corner.x, wparams.min_corner.y,
450 wparams.max_corner.y, wparams.min_corner.z, wparams.max_corner.z);
451}
452
454{
455 ompl::time::point start = ompl::time::now();
456 ob::PlannerTerminationCondition ptc = constructPlannerTerminationCondition(timeout, start);
458 ompl_simple_setup_->simplifySolution(ptc);
459 last_simplify_time_ = ompl_simple_setup_->getLastSimplificationTime();
461}
462
464{
465 if (ompl_simple_setup_->haveSolutionPath())
466 {
467 og::PathGeometric& pg = ompl_simple_setup_->getSolutionPath();
468
469 // Find the number of states that will be in the interpolated solution.
470 // This is what interpolate() does internally.
471 unsigned int eventual_states = 1;
472 std::vector<ompl::base::State*> states = pg.getStates();
473 for (size_t i = 0; i < states.size() - 1; ++i)
474 {
475 eventual_states += ompl_simple_setup_->getStateSpace()->validSegmentCount(states[i], states[i + 1]);
476 }
477
478 if (eventual_states < minimum_waypoint_count_)
479 {
480 // If that's not enough states, use the minimum amount instead.
481 pg.interpolate(minimum_waypoint_count_);
482 }
483 else
484 {
485 // Interpolate the path to have as the exact states that are checked when validating motions.
486 pg.interpolate();
487 }
488 }
489}
490
491void ModelBasedPlanningContext::convertPath(const ompl::geometric::PathGeometric& pg,
493{
495 for (std::size_t i = 0; i < pg.getStateCount(); ++i)
496 {
497 spec_.state_space_->copyToRobotState(ks, pg.getState(i));
498 traj.addSuffixWayPoint(ks, 0.0);
499 }
500}
501
503{
504 traj.clear();
505 if (ompl_simple_setup_->haveSolutionPath())
506 {
507 convertPath(ompl_simple_setup_->getSolutionPath(), traj);
508 }
509 return ompl_simple_setup_->haveSolutionPath();
510}
511
513{
514 if (ompl_simple_setup_->getStateValidityChecker())
515 {
516 static_cast<StateValidityChecker*>(ompl_simple_setup_->getStateValidityChecker().get())->setVerbose(flag);
517 }
518}
519
521{
522 // ******************* set up the goal representation, based on goal constraints
523
524 std::vector<ob::GoalPtr> goals;
525 for (kinematic_constraints::KinematicConstraintSetPtr& goal_constraint : goal_constraints_)
526 {
527 constraint_samplers::ConstraintSamplerPtr constraint_sampler;
528 if (spec_.constraint_sampler_manager_)
529 {
530 constraint_sampler = spec_.constraint_sampler_manager_->selectSampler(getPlanningScene(), getGroupName(),
531 goal_constraint->getAllConstraints());
532 }
533
534 if (constraint_sampler)
535 {
536 ob::GoalPtr goal = std::make_shared<ConstrainedGoalSampler>(this, goal_constraint, constraint_sampler);
537 goals.push_back(goal);
538 }
539 }
540
541 if (!goals.empty())
542 {
543 return goals.size() == 1 ? goals[0] : std::make_shared<GoalSampleableRegionMux>(goals);
544 }
545 else
546 {
547 RCLCPP_ERROR(getLogger(), "Unable to construct goal representation");
548 }
549
550 return ob::GoalPtr();
551}
552
553ompl::base::PlannerTerminationCondition
554ModelBasedPlanningContext::constructPlannerTerminationCondition(double timeout, const ompl::time::point& start)
555{
556 auto it = spec_.config_.find("termination_condition");
557 if (it == spec_.config_.end())
558 {
559 return ob::timedPlannerTerminationCondition(timeout - ompl::time::seconds(ompl::time::now() - start));
560 }
561
562 std::string termination_string = it->second;
563 std::vector<std::string> termination_and_params;
564 boost::split(termination_and_params, termination_string, boost::is_any_of("[ ,]"));
565
566 if (termination_and_params.empty())
567 {
568 RCLCPP_ERROR(getLogger(), "Termination condition not specified");
569 }
570
571 // Terminate if a maximum number of iterations is exceeded or a timeout occurs.
572 // The semantics of "iterations" are planner-specific, but typically it corresponds to the number of times
573 // an attempt was made to grow a roadmap/tree.
574 else if (termination_and_params[0] == "Iteration")
575 {
576 if (termination_and_params.size() > 1)
577 {
578 return ob::plannerOrTerminationCondition(
579 ob::timedPlannerTerminationCondition(timeout - ompl::time::seconds(ompl::time::now() - start)),
580 ob::IterationTerminationCondition(std::stoul(termination_and_params[1])));
581 }
582 else
583 {
584 RCLCPP_ERROR(getLogger(), "Missing argument to Iteration termination condition");
585 }
586 }
587 // Terminate if the cost has converged or a timeout occurs.
588 // Only useful for anytime/optimizing planners.
589 else if (termination_and_params[0] == "CostConvergence")
590 {
591 std::size_t solutions_window = 10u;
592 double epsilon = 0.1;
593 if (termination_and_params.size() > 1)
594 {
595 solutions_window = std::stoul(termination_and_params[1]);
596 if (termination_and_params.size() > 2)
597 {
598 epsilon = moveit::core::toDouble(termination_and_params[2]);
599 }
600 }
601 return ob::plannerOrTerminationCondition(
602 ob::timedPlannerTerminationCondition(timeout - ompl::time::seconds(ompl::time::now() - start)),
603 ob::CostConvergenceTerminationCondition(ompl_simple_setup_->getProblemDefinition(), solutions_window, epsilon));
604 }
605 // Terminate as soon as an exact solution is found or a timeout occurs.
606 // This modifies the behavior of anytime/optimizing planners to terminate upon discovering
607 // the first feasible solution.
608 else if (termination_and_params[0] == "ExactSolution")
609 {
610 return ob::plannerOrTerminationCondition(
611 ob::timedPlannerTerminationCondition(timeout - ompl::time::seconds(ompl::time::now() - start)),
612 ob::exactSolnPlannerTerminationCondition(ompl_simple_setup_->getProblemDefinition()));
613 }
614 else
615 {
616 RCLCPP_ERROR(getLogger(), "Unknown planner termination condition");
617 }
618 // return a planner termination condition to suppress compiler warning
619 return ob::plannerAlwaysTerminatingCondition();
620}
621
623{
624 complete_initial_robot_state_ = complete_initial_robot_state;
626}
627
629{
631 {
632 ompl_simple_setup_->clear();
633 }
634 else
635 {
636 // For LazyPRM and LazyPRMstar we assume that the environment *could* have changed
637 // This means that we need to reset the validity flags for every node and edge in
638 // the roadmap. For PRM and PRMstar we assume that the environment is static. If
639 // this is not the case, then multi-query planning should not be enabled.
640 auto planner = dynamic_cast<ompl::geometric::LazyPRM*>(ompl_simple_setup_->getPlanner().get());
641 if (planner != nullptr)
642 {
643 planner->clearValidity();
644 }
645 }
646 ompl_simple_setup_->clearStartStates();
647 ompl_simple_setup_->setGoal(ob::GoalPtr());
648 ompl_simple_setup_->setStateValidityChecker(ob::StateValidityCheckerPtr());
649 path_constraints_.reset();
650 goal_constraints_.clear();
651 getOMPLStateSpace()->setInterpolationFunction(InterpolationFunction());
652}
653
654bool ModelBasedPlanningContext::setPathConstraints(const moveit_msgs::msg::Constraints& path_constraints,
655 moveit_msgs::msg::MoveItErrorCodes* /*error*/)
656{
657 // ******************* set the path constraints to use
658 path_constraints_ = std::make_shared<kinematic_constraints::KinematicConstraintSet>(getRobotModel());
659 path_constraints_->add(path_constraints, getPlanningScene()->getTransforms());
660 path_constraints_msg_ = path_constraints;
661
662 return true;
663}
664
665bool ModelBasedPlanningContext::setGoalConstraints(const std::vector<moveit_msgs::msg::Constraints>& goal_constraints,
666 const moveit_msgs::msg::Constraints& path_constraints,
667 moveit_msgs::msg::MoveItErrorCodes* error)
668{
669 // ******************* check if the input is correct
670 goal_constraints_.clear();
671 for (const moveit_msgs::msg::Constraints& goal_constraint : goal_constraints)
672 {
673 moveit_msgs::msg::Constraints constr = kinematic_constraints::mergeConstraints(goal_constraint, path_constraints);
674 kinematic_constraints::KinematicConstraintSetPtr kset(
676 kset->add(constr, getPlanningScene()->getTransforms());
677 if (!kset->empty())
678 {
679 goal_constraints_.push_back(kset);
680 }
681 }
682
683 if (goal_constraints_.empty())
684 {
685 RCLCPP_WARN(getLogger(), "%s: No goal constraints specified. There is no problem to solve.", name_.c_str());
686 if (error)
687 {
688 error->val = moveit_msgs::msg::MoveItErrorCodes::INVALID_GOAL_CONSTRAINTS;
689 }
690 return false;
691 }
692
693 ob::GoalPtr goal = constructGoal();
694 ompl_simple_setup_->setGoal(goal);
695 return static_cast<bool>(goal);
696}
697
698bool ModelBasedPlanningContext::benchmark(double timeout, unsigned int count, const std::string& filename)
699{
700 ompl_benchmark_.clearPlanners();
701 ompl_simple_setup_->setup();
702 ompl_benchmark_.addPlanner(ompl_simple_setup_->getPlanner());
703 ompl_benchmark_.setExperimentName(getRobotModel()->getName() + "_" + getGroupName() + "_" +
704 getPlanningScene()->getName() + "_" + name_);
705
706 ot::Benchmark::Request req;
707 req.maxTime = timeout;
708 req.runCount = count;
709 req.displayProgress = true;
710 req.saveConsoleOutput = false;
711 ompl_benchmark_.benchmark(req);
712 return filename.empty() ? ompl_benchmark_.saveResultsToFile() : ompl_benchmark_.saveResultsToFile(filename.c_str());
713}
714
716{
717 bool gls = ompl_simple_setup_->getGoal()->hasType(ob::GOAL_LAZY_SAMPLES);
718 if (gls)
719 {
720 static_cast<ob::GoalLazySamples*>(ompl_simple_setup_->getGoal().get())->startSampling();
721 }
722 else
723 {
724 // we know this is a GoalSampleableMux by elimination
725 static_cast<GoalSampleableRegionMux*>(ompl_simple_setup_->getGoal().get())->startSampling();
726 }
727}
728
730{
731 bool gls = ompl_simple_setup_->getGoal()->hasType(ob::GOAL_LAZY_SAMPLES);
732 if (gls)
733 {
734 static_cast<ob::GoalLazySamples*>(ompl_simple_setup_->getGoal().get())->stopSampling();
735 }
736 else
737 {
738 // we know this is a GoalSampleableMux by elimination
739 static_cast<GoalSampleableRegionMux*>(ompl_simple_setup_->getGoal().get())->stopSampling();
740 }
741}
742
744{
745 // clear previously computed solutions
746 ompl_simple_setup_->getProblemDefinition()->clearSolutionPaths();
747 const ob::PlannerPtr planner = ompl_simple_setup_->getPlanner();
748 if (planner && !multi_query_planning_enabled_)
749 {
750 planner->clear();
751 }
753 ompl_simple_setup_->getSpaceInformation()->getMotionValidator()->resetMotionCounter();
754}
755
757{
758 stopSampling();
759 int v = ompl_simple_setup_->getSpaceInformation()->getMotionValidator()->getValidMotionCount();
760 int iv = ompl_simple_setup_->getSpaceInformation()->getMotionValidator()->getInvalidMotionCount();
761 RCLCPP_DEBUG(getLogger(), "There were %d valid motions and %d invalid motions.", v, iv);
762
763 // Debug OMPL setup and solution
764 RCLCPP_DEBUG(getLogger(), "%s",
765 [&] {
766 std::stringstream debug_out;
767 ompl_simple_setup_->print(debug_out);
768 return debug_out.str();
769 }()
770 .c_str());
771}
772
774{
775 res.planner_id = request_.planner_id;
776 res.error_code = solve(request_.allowed_planning_time, request_.num_planning_attempts);
777 if (res.error_code.val != moveit_msgs::msg::MoveItErrorCodes::SUCCESS)
778 {
779 RCLCPP_ERROR(getLogger(), "Unable to solve the planning problem");
780 return;
781 }
782 double ptime = getLastPlanTime();
784 {
785 simplifySolution(request_.allowed_planning_time - ptime);
786 ptime += getLastSimplifyTime();
787 }
788
789 if (interpolate_)
790 {
792 }
793
794 // fill the response
795 RCLCPP_DEBUG(getLogger(), "%s: Returning successful solution with %lu states", getName().c_str(),
796 getOMPLSimpleSetup()->getSolutionPath().getStateCount());
797
798 res.trajectory = std::make_shared<robot_trajectory::RobotTrajectory>(getRobotModel(), getGroupName());
800 res.planning_time = ptime;
801}
802
804{
805 res.planner_id = request_.planner_id;
806 res.error_code = solve(request_.allowed_planning_time, request_.num_planning_attempts);
807 if (res.error_code.val != moveit_msgs::msg::MoveItErrorCodes::SUCCESS)
808 {
809 RCLCPP_INFO(getLogger(), "Unable to solve the planning problem");
810 return;
811 }
812
813 res.trajectory.reserve(3);
814
815 // add info about planned solution
816 double ptime = getLastPlanTime();
817 res.processing_time.push_back(ptime);
818 res.description.emplace_back("plan");
819 res.trajectory.resize(res.trajectory.size() + 1);
820 res.trajectory.back() = std::make_shared<robot_trajectory::RobotTrajectory>(getRobotModel(), getGroupName());
821 getSolutionPath(*res.trajectory.back());
822
823 // simplify solution if time remains
825 {
826 simplifySolution(request_.allowed_planning_time - ptime);
827 res.processing_time.push_back(getLastSimplifyTime());
828 res.description.emplace_back("simplify");
829 res.trajectory.resize(res.trajectory.size() + 1);
830 res.trajectory.back() = std::make_shared<robot_trajectory::RobotTrajectory>(getRobotModel(), getGroupName());
831 getSolutionPath(*res.trajectory.back());
832 }
833
834 if (interpolate_)
835 {
836 ompl::time::point start_interpolate = ompl::time::now();
838 res.processing_time.push_back(ompl::time::seconds(ompl::time::now() - start_interpolate));
839 res.description.emplace_back("interpolate");
840 res.trajectory.resize(res.trajectory.size() + 1);
841 res.trajectory.back() = std::make_shared<robot_trajectory::RobotTrajectory>(getRobotModel(), getGroupName());
842 getSolutionPath(*res.trajectory.back());
843 }
844
845 RCLCPP_DEBUG(getLogger(), "%s: Returning successful solution with %lu states", getName().c_str(),
846 getOMPLSimpleSetup()->getSolutionPath().getStateCount());
847}
848
849const moveit_msgs::msg::MoveItErrorCodes ModelBasedPlanningContext::solve(double timeout, unsigned int count)
850{
851 ompl::time::point start = ompl::time::now();
852 preSolve();
853
854 moveit_msgs::msg::MoveItErrorCodes result;
855 result.val = moveit_msgs::msg::MoveItErrorCodes::FAILURE;
856 if (count <= 1 || multi_query_planning_enabled_) // multi-query planners should always run in single instances
857 {
858 RCLCPP_DEBUG(getLogger(), "%s: Solving the planning problem once...", name_.c_str());
859 ob::PlannerTerminationCondition ptc = constructPlannerTerminationCondition(timeout, start);
861 std::ignore = ompl_simple_setup_->solve(ptc);
862 last_plan_time_ = ompl_simple_setup_->getLastPlanComputationTime();
864 // fill the result status code
866 }
867 else
868 {
869 RCLCPP_DEBUG(getLogger(), "%s: Solving the planning problem %u times...", name_.c_str(), count);
870 ompl_parallel_plan_.clearHybridizationPaths();
871 if (count <= max_planning_threads_)
872 {
873 ompl_parallel_plan_.clearPlanners();
874 if (ompl_simple_setup_->getPlannerAllocator())
875 {
876 for (unsigned int i = 0; i < count; ++i)
877 {
878 ompl_parallel_plan_.addPlannerAllocator(ompl_simple_setup_->getPlannerAllocator());
879 }
880 }
881 else
882 {
883 for (unsigned int i = 0; i < count; ++i)
884 {
885 ompl_parallel_plan_.addPlanner(ompl::tools::SelfConfig::getDefaultPlanner(ompl_simple_setup_->getGoal()));
886 }
887 }
888
889 ob::PlannerTerminationCondition ptc = constructPlannerTerminationCondition(timeout, start);
891 if (ompl_parallel_plan_.solve(ptc, 1, count, hybridize_) == ompl::base::PlannerStatus::EXACT_SOLUTION)
892 {
893 result.val = moveit_msgs::msg::MoveItErrorCodes::SUCCESS;
894 }
895 last_plan_time_ = ompl::time::seconds(ompl::time::now() - start);
897 }
898 else
899 {
900 ob::PlannerTerminationCondition ptc = constructPlannerTerminationCondition(timeout, start);
902 int n = count / max_planning_threads_;
903 result.val = moveit_msgs::msg::MoveItErrorCodes::SUCCESS;
904 for (int i = 0; i < n && !ptc(); ++i)
905 {
906 ompl_parallel_plan_.clearPlanners();
907 if (ompl_simple_setup_->getPlannerAllocator())
908 {
909 for (unsigned int i = 0; i < max_planning_threads_; ++i)
910 {
911 ompl_parallel_plan_.addPlannerAllocator(ompl_simple_setup_->getPlannerAllocator());
912 }
913 }
914 else
915 {
916 for (unsigned int i = 0; i < max_planning_threads_; ++i)
917 {
918 ompl_parallel_plan_.addPlanner(ompl::tools::SelfConfig::getDefaultPlanner(ompl_simple_setup_->getGoal()));
919 }
920 }
921
922 bool r = ompl_parallel_plan_.solve(ptc, 1, count, hybridize_) == ompl::base::PlannerStatus::EXACT_SOLUTION;
923 // Was this latest call successful too?
924 result.val = (result.val == moveit_msgs::msg::MoveItErrorCodes::SUCCESS && r) ?
925 moveit_msgs::msg::MoveItErrorCodes::SUCCESS :
926 moveit_msgs::msg::MoveItErrorCodes::FAILURE;
927 }
928 n = count % max_planning_threads_;
929 if (n && !ptc())
930 {
931 ompl_parallel_plan_.clearPlanners();
932 if (ompl_simple_setup_->getPlannerAllocator())
933 {
934 for (int i = 0; i < n; ++i)
935 {
936 ompl_parallel_plan_.addPlannerAllocator(ompl_simple_setup_->getPlannerAllocator());
937 }
938 }
939 else
940 {
941 for (int i = 0; i < n; ++i)
942 {
943 ompl_parallel_plan_.addPlanner(ompl::tools::SelfConfig::getDefaultPlanner(ompl_simple_setup_->getGoal()));
944 }
945 }
946
947 bool r = ompl_parallel_plan_.solve(ptc, 1, count, hybridize_) == ompl::base::PlannerStatus::EXACT_SOLUTION;
948 // Was this latest call successful too?
949 result.val = (result.val == moveit_msgs::msg::MoveItErrorCodes::SUCCESS && r) ?
950 moveit_msgs::msg::MoveItErrorCodes::SUCCESS :
951 moveit_msgs::msg::MoveItErrorCodes::FAILURE;
952 }
953 last_plan_time_ = ompl::time::seconds(ompl::time::now() - start);
955 }
956 }
957
958 postSolve();
959 return result;
960}
961
962void ModelBasedPlanningContext::registerTerminationCondition(const ob::PlannerTerminationCondition& ptc)
963{
964 std::unique_lock<std::mutex> slock(ptc_lock_);
965 ptc_ = &ptc;
966}
967
969{
970 std::unique_lock<std::mutex> slock(ptc_lock_);
971 ptc_ = nullptr;
972}
973
974int32_t ModelBasedPlanningContext::logPlannerStatus(const og::SimpleSetupPtr& ompl_simple_setup)
975{
976 auto result = moveit_msgs::msg::MoveItErrorCodes::PLANNING_FAILED;
977 const ompl::base::PlannerStatus ompl_status = ompl_simple_setup->getLastPlannerStatus();
978 switch (ompl::base::PlannerStatus::StatusType(ompl_status))
979 {
980 case ompl::base::PlannerStatus::UNKNOWN:
981 RCLCPP_WARN(getLogger(), "Motion planning failed for an unknown reason");
982 result = moveit_msgs::msg::MoveItErrorCodes::PLANNING_FAILED;
983 break;
984 case ompl::base::PlannerStatus::INVALID_START:
985 RCLCPP_WARN(getLogger(), "Invalid start state");
986 result = moveit_msgs::msg::MoveItErrorCodes::START_STATE_INVALID;
987 break;
988 case ompl::base::PlannerStatus::INVALID_GOAL:
989 RCLCPP_WARN(getLogger(), "Invalid goal state");
990 result = moveit_msgs::msg::MoveItErrorCodes::GOAL_STATE_INVALID;
991 break;
992 case ompl::base::PlannerStatus::UNRECOGNIZED_GOAL_TYPE:
993 RCLCPP_WARN(getLogger(), "Unrecognized goal type");
994 result = moveit_msgs::msg::MoveItErrorCodes::UNRECOGNIZED_GOAL_TYPE;
995 break;
996 case ompl::base::PlannerStatus::TIMEOUT:
997 RCLCPP_WARN(getLogger(), "Timed out: %.1fs ≥ %.1fs", ompl_simple_setup->getLastPlanComputationTime(),
998 request_.allowed_planning_time);
999 result = moveit_msgs::msg::MoveItErrorCodes::TIMED_OUT;
1000 break;
1001 case ompl::base::PlannerStatus::APPROXIMATE_SOLUTION:
1002 // timeout is a common reason for APPROXIMATE_SOLUTION
1003 if (ompl_simple_setup->getLastPlanComputationTime() > request_.allowed_planning_time)
1004 {
1005 RCLCPP_WARN(getLogger(), "Planning timed out: %.1fs ≥ %.1fs", ompl_simple_setup->getLastPlanComputationTime(),
1006 request_.allowed_planning_time);
1007 result = moveit_msgs::msg::MoveItErrorCodes::TIMED_OUT;
1008 }
1009 else
1010 {
1011 RCLCPP_WARN(getLogger(), "Solution is approximate");
1012 result = moveit_msgs::msg::MoveItErrorCodes::PLANNING_FAILED;
1013 }
1014 break;
1015 case ompl::base::PlannerStatus::EXACT_SOLUTION:
1016 result = moveit_msgs::msg::MoveItErrorCodes::SUCCESS;
1017 break;
1018 case ompl::base::PlannerStatus::CRASH:
1019 RCLCPP_WARN(getLogger(), "OMPL crashed!");
1020 result = moveit_msgs::msg::MoveItErrorCodes::CRASH;
1021 break;
1022 case ompl::base::PlannerStatus::ABORT:
1023 RCLCPP_WARN(getLogger(), "OMPL was aborted");
1024 result = moveit_msgs::msg::MoveItErrorCodes::ABORT;
1025 break;
1026 default:
1027 // This should never happen
1028 RCLCPP_WARN(getLogger(), "Unexpected PlannerStatus code from OMPL.");
1029 result = moveit_msgs::msg::MoveItErrorCodes::PLANNING_FAILED;
1030 }
1031 return result;
1032}
1033
1035{
1036 std::unique_lock<std::mutex> slock(ptc_lock_);
1037 if (ptc_)
1038 {
1039 ptc_->terminate();
1040 }
1041 return true;
1042}
1043
1044bool ModelBasedPlanningContext::saveConstraintApproximations(const rclcpp::Node::SharedPtr& node)
1045{
1046 std::string constraint_path;
1047 if (node->get_parameter("constraint_approximations_path", constraint_path))
1048 {
1049 constraints_library_->saveConstraintApproximations(constraint_path);
1050 return true;
1051 }
1052 RCLCPP_WARN(getLogger(), "ROS param 'constraint_approximations' not found. Unable to save constraint approximations");
1053 return false;
1054}
1055
1056bool ModelBasedPlanningContext::loadConstraintApproximations(const rclcpp::Node::SharedPtr& node)
1057{
1058 std::string constraint_path;
1059 if (node->get_parameter("constraint_approximations_path", constraint_path))
1060 {
1061 constraints_library_->loadConstraintApproximations(constraint_path);
1062 std::stringstream ss;
1063 constraints_library_->printConstraintApproximations(ss);
1064 RCLCPP_INFO_STREAM(getLogger(), ss.str());
1065 return true;
1066 }
1067 return false;
1068}
1069
1070} // namespace ompl_interface
A class that contains many different constraints, and can check RobotState *versus the full set....
const JointModel * getJointModel(const std::string &joint) const
Get a joint by its name. Throw an exception if the joint is not part of this group.
int getVariableGroupIndex(const std::string &variable) const
Get the index of a variable within the group. Return -1 on error.
std::size_t getVariableCount() const
Get the number of variables that describe this joint.
Representation of a robot's state. This includes position, velocity, acceleration and effort.
void startSampling()
If there are any member lazy samplers, start them.
void stopSampling()
If there are any member lazy samplers, stop them.
int32_t logPlannerStatus(const og::SimpleSetupPtr &ompl_simple_setup)
Convert OMPL PlannerStatus to moveit_msgs::msg::MoveItErrorCode.
virtual ob::ProjectionEvaluatorPtr getProjectionEvaluator(const std::string &peval) const
void clear() override
Clear the data structures used by the planner.
bool getSolutionPath(robot_trajectory::RobotTrajectory &traj) const
double last_plan_time_
the time spent computing the last plan
void registerTerminationCondition(const ob::PlannerTerminationCondition &ptc)
unsigned int max_planning_threads_
when planning in parallel, this is the maximum number of threads to use at one time
bool loadConstraintApproximations(const rclcpp::Node::SharedPtr &node)
Look up param server 'constraint_approximations' and use its value as the path to load constraint app...
bool setPathConstraints(const moveit_msgs::msg::Constraints &path_constraints, moveit_msgs::msg::MoveItErrorCodes *error)
ModelBasedPlanningContext(const std::string &name, const ModelBasedPlanningContextSpecification &spec)
virtual ob::PlannerTerminationCondition constructPlannerTerminationCondition(double timeout, const ompl::time::point &start)
bool saveConstraintApproximations(const rclcpp::Node::SharedPtr &node)
Look up param server 'constraint_approximations' and use its value as the path to save constraint app...
const moveit::core::RobotState & getCompleteInitialRobotState() const
bool multi_query_planning_enabled_
when false, clears planners before running solve()
virtual void configure(const rclcpp::Node::SharedPtr &node, bool use_constraints_approximations)
Configure ompl_simple_setup_ and optionally the constraints_library_.
bool setGoalConstraints(const std::vector< moveit_msgs::msg::Constraints > &goal_constraints, const moveit_msgs::msg::Constraints &path_constraints, moveit_msgs::msg::MoveItErrorCodes *error)
const moveit::core::RobotModelConstPtr & getRobotModel() const
void convertPath(const og::PathGeometric &pg, robot_trajectory::RobotTrajectory &traj) const
std::vector< kinematic_constraints::KinematicConstraintSetPtr > goal_constraints_
const ModelBasedStateSpacePtr & getOMPLStateSpace() const
kinematic_constraints::KinematicConstraintSetPtr path_constraints_
unsigned int max_goal_sampling_attempts_
maximum number of attempts to be made at sampling a goal states
void setCompleteInitialState(const moveit::core::RobotState &complete_initial_robot_state)
og::SimpleSetupPtr ompl_simple_setup_
the OMPL planning context; this contains the problem definition and the planner used
void setPlanningVolume(const moveit_msgs::msg::WorkspaceParameters &wparams)
const moveit::core::JointModelGroup * getJointModelGroup() const
ot::Benchmark ompl_benchmark_
the OMPL tool for benchmarking planners
bool benchmark(double timeout, unsigned int count, const std::string &filename="")
bool terminate() override
If solve() is running, terminate the computation. Return false if termination not possible....
virtual ob::StateSamplerPtr allocPathConstrainedSampler(const ompl::base::StateSpace *ss) const
void solve(planning_interface::MotionPlanResponse &res) override
Solve the motion planning problem and store the result in res. This function should not clear data st...
ot::ParallelPlan ompl_parallel_plan_
tool used to compute multiple plans in parallel; this uses the problem definition maintained by ompl_...
double last_simplify_time_
the time spent simplifying the last plan
void setConstraintsApproximations(const ConstraintsLibraryPtr &constraints_library)
An interface for a OMPL state validity checker.
std::string name_
The name of this planning context.
MotionPlanRequest request_
The planning request for this context.
const planning_scene::PlanningSceneConstPtr & getPlanningScene() const
Get the planning scene associated to this planning context.
const std::string & getGroupName() const
Get the name of the group this planning context is for.
PlanningContext(const std::string &name, const std::string &group)
Construct a planning context named name for the group group.
const std::string & getName() const
Get the name of this planning context.
Maintain a sequence of waypoints and the time durations between these waypoints.
RobotTrajectory & addSuffixWayPoint(const moveit::core::RobotState &state, double dt)
Add a point to the trajectory.
moveit_msgs::msg::Constraints mergeConstraints(const moveit_msgs::msg::Constraints &first, const moveit_msgs::msg::Constraints &second)
Merge two sets of constraints into one.
Definition utils.cpp:64
double toDouble(const std::string &s)
Converts a std::string to double using the classic C locale.
std::string toString(double d)
Convert a double to std::string using the classic C locale.
rclcpp::Logger getLogger(const std::string &name)
Creates a namespaced logger.
Definition logger.cpp:79
The MoveIt interface to OMPL.
std::function< bool(const ompl::base::State *from, const ompl::base::State *to, const double t, ompl::base::State *state)> InterpolationFunction
This namespace includes the base class for MoveIt planners.
moveit_msgs::msg::MoveItErrorCodes error_code
std::vector< robot_trajectory::RobotTrajectoryPtr > trajectory
moveit::core::MoveItErrorCode error_code
robot_trajectory::RobotTrajectoryPtr trajectory