ThunderRetrieveRepair.cpp
1 /*********************************************************************
2  * Software License Agreement (BSD License)
3  *
4  * Copyright (c) 2013, University of Colorado, Boulder
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 the Univ of CO, Boulder 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: Dave Coleman */
36 
37 #include <ompl/geometric/planners/experience/ThunderRetrieveRepair.h>
38 #include <ompl/geometric/planners/rrt/RRTConnect.h>
39 #include <ompl/base/goals/GoalState.h>
40 #include <ompl/base/goals/GoalSampleableRegion.h>
41 #include <ompl/tools/config/SelfConfig.h>
42 #include <ompl/util/Console.h>
43 #include <ompl/tools/thunder/ThunderDB.h>
44 #include "ompl/tools/config/MagicConstants.h"
45 
46 #include <thread>
47 
48 #include <limits>
49 
50 namespace ompl
51 {
52 
53 namespace geometric
54 {
55 
56 ThunderRetrieveRepair::ThunderRetrieveRepair(const base::SpaceInformationPtr &si, const tools::ThunderDBPtr &experienceDB)
57  : base::Planner(si, "Thunder_Retrieve_Repair")
58  , experienceDB_(experienceDB)
59  , nearestK_(ompl::magic::NEAREST_K_RECALL_SOLUTIONS) // default value
60  , smoothingEnabled_(false) // makes understanding recalled paths more difficult if enabled
61 {
63  specs_.directed = true;
64 
65  // Repair Planner Specific:
67 
69 }
70 
71 ThunderRetrieveRepair::~ThunderRetrieveRepair(void)
72 {
73  freeMemory();
74 }
75 
77 {
78  Planner::clear();
79  freeMemory();
80 
81  // Clear the inner planner
82  if (repairPlanner_)
83  repairPlanner_->clear();
84 }
85 
86 void ThunderRetrieveRepair::setExperienceDB(const tools::ThunderDBPtr &experienceDB)
87 {
88  experienceDB_ = experienceDB;
89 }
90 
92 {
93  if (planner && planner->getSpaceInformation().get() != si_.get())
94  throw Exception("Repair planner instance does not match space information");
95  repairPlanner_ = planner;
96  setup_ = false;
97 }
98 
100 {
101  Planner::setup();
102 
103  // Setup repair planner (for use by the rrPlanner)
104  // Note: does not use the same pdef as the main planner in this class
105  if (!repairPlanner_)
106  {
107  // Set the repair planner
108  std::shared_ptr<RRTConnect> repair_planner( new RRTConnect( si_ ) );
109 
110  OMPL_DEBUG("No repairing planner specified. Using default: %s", repair_planner->getName().c_str() );
111  repairPlanner_ = repair_planner; //Planner( repair_planer );
112  }
113  // Setup the problem definition for the repair planner
114  repairProblemDef_->setOptimizationObjective(pdef_->getOptimizationObjective()); // copy primary problem def
115 
116  // Setup repair planner
117  repairPlanner_->setProblemDefinition(repairProblemDef_);
118  if (!repairPlanner_->isSetup())
119  repairPlanner_->setup();
120 }
121 
123 {
124 }
125 
127 {
128  bool solved = false;
129  double approxdif = std::numeric_limits<double>::infinity();
130  nearestPaths_.clear();
131 
132  // Check if the database is empty
133  if (experienceDB_->isEmpty())
134  {
135  OMPL_INFORM("Experience database is empty so unable to run ThunderRetrieveRepair algorithm.");
136 
138  }
139 
140  // Restart the Planner Input States so that the first start and goal state can be fetched
141  pis_.restart();
142 
143  // Get a single start and goal state TODO: more than one
144  const base::State *startState = pis_.nextStart();
145  const base::State *goalState = pis_.nextGoal(ptc);
146 
147  // Create solution path struct
148  SPARSdb::CandidateSolution candidateSolution;
149 
150  // Search for previous solution in database
151  // TODO make this more than 1 path
152  if (!experienceDB_->findNearestStartGoal(nearestK_, startState, goalState, candidateSolution, ptc))
153  {
154  OMPL_INFORM("RetrieveRepair::solve() No nearest start or goal found");
155  return base::PlannerStatus::TIMEOUT; // The planner failed to find a solution
156  }
157 
158  // Save this for future debugging
159  nearestPaths_.push_back(candidateSolution.getGeometricPath());
160  nearestPathsChosenID_ = 0; // TODO not hardcode
161 
162  // All save trajectories should be at least 2 states long, then we append the start and goal states, for min of 4
163  assert(candidateSolution.getStateCount() >= 4);
164 
165  // Smooth the result
166  if (smoothingEnabled_)
167  {
168  OMPL_INFORM("ThunderRetrieveRepair solve: Simplifying solution (smoothing)...");
169  time::point simplifyStart = time::now();
170  std::size_t numStates = candidateSolution.getGeometricPath().getStateCount();
171  //ompl::geometric::PathGeometric pg = candidateSolution.getGeometricPath(); // TODO do not copy to new type
172  path_simplifier_->simplify(candidateSolution.getGeometricPath(), ptc);
173  double simplifyTime = time::seconds(time::now() - simplifyStart);
174  OMPL_INFORM("ThunderRetrieveRepair: Path simplification took %f seconds and removed %d states",
175  simplifyTime, numStates - candidateSolution.getGeometricPath().getStateCount());
176  }
177 
178  // Finished
179  approxdif = 0;
180  bool approximate = candidateSolution.isApproximate_;
181 
182  pdef_->addSolutionPath(candidateSolution.path_, approximate, approxdif, getName());
183  solved = true;
184  return base::PlannerStatus(solved, approximate);
185 }
186 
188 {
189  // \todo: we could reuse our collision checking from the previous step to make this faster
190  // but that complicates everything and I'm not suppose to be spending too much time
191  // on this prototype - DTC
192 
193  OMPL_INFORM("Repairing path ----------------------------------");
194 
195  // Error check
196  if (primaryPath.getStateCount() < 2)
197  {
198  OMPL_ERROR("Cannot repair a path with less than 2 states");
199  return false;
200  }
201 
202  // Loop through every pair of states and make sure path is valid.
203  // If not, replan between those states
204  for (std::size_t toID = 1; toID < primaryPath.getStateCount(); ++toID)
205  {
206  std::size_t fromID = toID - 1; // this is our last known valid state
207  base::State* fromState = primaryPath.getState(fromID);
208  base::State* toState = primaryPath.getState(toID);
209 
210  // Check if our planner is out of time
211  if (ptc == true)
212  {
213  OMPL_DEBUG("Repair path function interrupted because termination condition is true.");
214  return false;
215  }
216 
217  // Check path between states
218  if (!si_->checkMotion(fromState, toState))
219  {
220  // Path between (from, to) states not valid, but perhaps to STATE is
221  // Search until next valid STATE is found in existing path
222  std::size_t subsearch_id = toID;
223  base::State* new_to;
224  OMPL_DEBUG("Searching for next valid state, because state %d to %d was not valid out %d total states",
225  fromID,toID,primaryPath.getStateCount());
226  while (subsearch_id < primaryPath.getStateCount())
227  {
228  new_to = primaryPath.getState(subsearch_id);
229  if (si_->isValid(new_to))
230  {
231  OMPL_DEBUG("State %d was found to valid, we can now repair between states", subsearch_id);
232  // This future state is valid, we can stop searching
233  toID = subsearch_id;
234  toState = new_to;
235  break;
236  }
237  ++subsearch_id; // keep searching for a new state to plan to
238  }
239  // Check if we ever found a next state that is valid
240  if (subsearch_id >= primaryPath.getStateCount())
241  {
242  // We never found a valid state to plan to, instead we reached the goal state and it too wasn't valid. This is bad.
243  // I think this is a bug.
244  OMPL_ERROR("No state was found valid in the remainder of the path. Invalid goal state. This should not happen.");
245  return false;
246  }
247 
248  // Plan between our two valid states
249  PathGeometric newPathSegment(si_);
250 
251  // Not valid motion, replan
252  OMPL_DEBUG("Planning from %d to %d", fromID, toID);
253 
254  if (!replan(fromState, toState, newPathSegment, ptc))
255  {
256  OMPL_WARN("Unable to repair path between state %d and %d", fromID, toID);
257  return false;
258  }
259 
260  // TODO make sure not approximate solution
261 
262  // Reference to the path
263  std::vector<base::State*>& primaryPathStates = primaryPath.getStates();
264 
265  // Remove all invalid states between (fromID, toID) - not including those states themselves
266  while (fromID != toID - 1)
267  {
268  OMPL_INFORM("Deleting state %d", fromID + 1);
269  primaryPathStates.erase(primaryPathStates.begin() + fromID + 1);
270  --toID; // because vector has shrunk
271  }
272 
273  // Insert new path segment into current path
274  OMPL_DEBUG("Inserting new %d states into old path. Previous length: %d", newPathSegment.getStateCount()-2, primaryPathStates.size());
275 
276  // Note: skip first and last states because they should be same as our start and goal state, same as `fromID` and `toID`
277  for (std::size_t i = 1; i < newPathSegment.getStateCount() - 1; ++i)
278  {
279  std::size_t insertLocation = toID + i - 1;
280  OMPL_DEBUG("Inserting newPathSegment state %d into old path at position %d", i, insertLocation);
281  primaryPathStates.insert( primaryPathStates.begin() + insertLocation, si_->cloneState(newPathSegment.getStates()[i]) );
282  }
283  //primaryPathStates.insert( primaryPathStates.begin() + toID, newPathSegment.getStates().begin(), newPathSegment.getStates().end() );
284  OMPL_DEBUG("Inserted new states into old path. New length: %d", primaryPathStates.size());
285 
286  // Set the toID to jump over the newly inserted states to the next unchecked state. Subtract 2 because we ignore start and goal
287  toID = toID + newPathSegment.getStateCount() - 2;
288  OMPL_DEBUG("Continuing searching at state %d", toID);
289  }
290  }
291 
292  OMPL_INFORM("Done repairing ---------------------------------");
293 
294  return true;
295 }
296 
297 bool ThunderRetrieveRepair::replan(const base::State* start, const base::State* goal, PathGeometric &newPathSegment,
299 {
300  // Reset problem definition
301  repairProblemDef_->clearSolutionPaths();
302  repairProblemDef_->clearStartStates();
303  repairProblemDef_->clearGoal();
304 
305  // Reset planner
306  repairPlanner_->clear();
307 
308  // Configure problem definition
309  repairProblemDef_->setStartAndGoalStates(start, goal);
310 
311  // Configure planner
312  repairPlanner_->setProblemDefinition(repairProblemDef_);
313 
314  // Solve
315  OMPL_INFORM("Preparing to repair path-----------------------------------------");
317  time::point startTime = time::now();
318 
319  // TODO: if we use replanner like RRT* the ptc will allow it to run too long and no time will be left for the rest of algorithm
320  lastStatus = repairPlanner_->solve(ptc);
321 
322  // Results
323  double planTime = time::seconds(time::now() - startTime);
324  if (!lastStatus)
325  {
326  OMPL_WARN("Replan Solve: No replan solution between disconnected states found after %f seconds", planTime);
327  return false;
328  }
329 
330  // Check if approximate
331  if (repairProblemDef_->hasApproximateSolution() || repairProblemDef_->getSolutionDifference() > std::numeric_limits<double>::epsilon())
332  {
333  OMPL_INFORM("Replan Solve: Solution is approximate, not using");
334  return false;
335  }
336 
337  // Convert solution into a PathGeometric path
338  base::PathPtr p = repairProblemDef_->getSolutionPath();
339  if (!p)
340  {
341  OMPL_ERROR("Unable to get solution path from problem definition");
342  return false;
343  }
344 
345  newPathSegment = static_cast<PathGeometric&>(*p);
346 
347  // Smooth the result
348  OMPL_INFORM("Repair: Simplifying solution (smoothing)...");
349  time::point simplifyStart = time::now();
350  std::size_t numStates = newPathSegment.getStateCount();
351  path_simplifier_->simplify(newPathSegment, ptc);
352  double simplifyTime = time::seconds(time::now() - simplifyStart);
353  OMPL_INFORM("ThunderRetrieveRepair: Path simplification took %f seconds and removed %d states", simplifyTime, numStates - newPathSegment.getStateCount());
354 
355  // Save the planner data for debugging purposes
357  repairPlanner_->getPlannerData( *repairPlannerDatas_.back() );
358  repairPlannerDatas_.back()->decoupleFromPlanner(); // copy states so that when planner unloads/clears we don't lose them
359 
360  // Return success
361  OMPL_INFORM("Replan Solve: solution found in %f seconds with %d states", planTime, newPathSegment.getStateCount() );
362 
363  return true;
364 }
365 
367 {
368  OMPL_INFORM("ThunderRetrieveRepair getPlannerData: including %d similar paths", nearestPaths_.size());
369 
370  // Visualize the n candidate paths that we recalled from the database
371  for (std::size_t i = 0 ; i < nearestPaths_.size() ; ++i)
372  {
373  PathGeometric path = nearestPaths_[i];
374  for (std::size_t j = 1; j < path.getStateCount(); ++j)
375  {
376  data.addEdge(
377  base::PlannerDataVertex(path.getState(j-1) ),
379  }
380  }
381 }
382 
383 const std::vector<PathGeometric>& ThunderRetrieveRepair::getLastRecalledNearestPaths() const
384 {
385  return nearestPaths_; // list of candidate paths
386 }
387 
389 {
390  return nearestPathsChosenID_; // of the candidate paths list, the one we chose
391 }
392 
394 {
396 }
397 
398 void ThunderRetrieveRepair::getRepairPlannerDatas(std::vector<base::PlannerDataPtr> &data) const
399 {
400  data = repairPlannerDatas_;
401 }
402 
403 std::size_t ThunderRetrieveRepair::checkMotionScore(const base::State *s1, const base::State *s2) const
404 {
405  int segmentCount = si_->getStateSpace()->validSegmentCount(s1, s2);
406 
407  std::size_t invalidStatesScore = 0; // count number of interpolated states in collision
408 
409  // temporary storage for the checked state
410  base::State *test = si_->allocState();
411 
412  // Linerarly step through motion between state 0 to state 1
413  double iteration_step = 1.0/double(segmentCount);
414  for (double location = 0.0; location <= 1.0; location += iteration_step )
415  {
416  si_->getStateSpace()->interpolate(s1, s2, location, test);
417 
418  if (!si_->isValid(test))
419  {
420  //OMPL_DEBUG("Found INVALID location between states at gradient %f", location);
421  invalidStatesScore ++;
422  }
423  else
424  {
425  //OMPL_DEBUG("Found valid location between states at gradient %f", location);
426  }
427  }
428  si_->freeState(test);
429 
430  return invalidStatesScore;
431 }
432 
433 } // namespace geometric
434 } // namespace ompl
std::vector< base::PlannerDataPtr > repairPlannerDatas_
Debug the repair planner by saving its planner data each time it is used.
bool approximateSolutions
Flag indicating whether the planner is able to compute approximate solutions.
Definition: Planner.h:212
virtual void setup(void)
Perform extra configuration steps, if needed. This call will also issue a call to ompl::base::SpaceIn...
Object containing planner generated vertex and edge data. It is assumed that all vertices are unique...
Definition: PlannerData.h:163
void getRepairPlannerDatas(std::vector< base::PlannerDataPtr > &data) const
Get information about the exploration data structure the repair motion planner used each call...
tools::ThunderDBPtr experienceDB_
The database of motions to search through.
The planner failed to find a solution.
Definition: PlannerStatus.h:62
base::ProblemDefinitionPtr repairProblemDef_
A secondary problem definition for the repair planner to use.
const State * nextGoal(const PlannerTerminationCondition &ptc)
Return the next valid goal state or nullptr if no more valid goal states are available. Because sampling of goal states may also produce invalid goals, this function takes an argument that specifies whether a termination condition has been reached. If the termination condition evaluates to true the function terminates even if no valid goal has been found.
Definition: Planner.cpp:271
virtual void getPlannerData(base::PlannerData &data) const
Get information about the exploration data structure the planning from scratch motion planner used...
Encapsulate a termination condition for a motion planner. Planners will call operator() to decide whe...
const PathGeometric & getChosenRecallPath() const
Get the chosen path used from database for repair.
Struct for passing around partially solved solutions.
Definition: SPARSdb.h:240
ProblemDefinitionPtr pdef_
The user set problem definition.
Definition: Planner.h:401
bool directed
Flag indicating whether the planner is able to account for the fact that the validity of a motion fro...
Definition: Planner.h:220
base::State * getState(unsigned int index)
Get the state located at index along the path.
std::size_t getStateCount() const
Get the number of states (way-points) that make up this path.
Base class for a vertex in the PlannerData structure. All derived classes must implement the clone an...
Definition: PlannerData.h:59
bool setup_
Flag indicating whether setup() has been called.
Definition: Planner.h:419
duration seconds(double sec)
Return the time duration representing a given number of seconds.
Definition: Time.h:78
bool repairPath(const base::PlannerTerminationCondition &ptc, PathGeometric &path)
Repairs a path to be valid in the current planning environment.
void setExperienceDB(const tools::ThunderDBPtr &experienceDB)
Pass a pointer of the database from the thunder framework.
std::size_t nearestPathsChosenID_
the ID within nearestPaths_ of the path that was chosen for repair
Main namespace. Contains everything in this library.
Definition: Cost.h:42
void freeMemory(void)
Free the memory allocated by this planner.
A shared pointer wrapper for ompl::base::Planner.
The planner did not find a solution for some other reason.
Definition: PlannerStatus.h:70
base::PlannerPtr repairPlanner_
A secondary planner for replanning.
std::vector< base::State * > & getStates()
Get the states that make up the path (as a reference, so it can be modified, hence the function is no...
#define OMPL_ERROR(fmt,...)
Log a formatted error string.
Definition: Console.h:64
bool smoothingEnabled_
Optionally smooth retrieved and repaired paths from database.
int nearestK_
Number of &#39;k&#39; close solutions to choose from database for further filtering.
RRT-Connect (RRTConnect)
Definition: RRTConnect.h:61
A class to store the exit status of Planner::solve()
Definition: PlannerStatus.h:48
virtual bool addEdge(unsigned int v1, unsigned int v2, const PlannerDataEdge &edge=PlannerDataEdge(), Cost weight=Cost(1.0))
Adds a directed edge between the given vertex indexes. An optional edge structure and weight can be s...
This class contains routines that attempt to simplify geometric paths.
A shared pointer wrapper for ompl::base::SpaceInformation.
Definition of an abstract state.
Definition: State.h:50
#define OMPL_WARN(fmt,...)
Log a formatted warning string.
Definition: Console.h:66
PlannerInputStates pis_
Utility class to extract valid input states.
Definition: Planner.h:404
PlannerSpecs specs_
The specifications of the planner (its capabilities)
Definition: Planner.h:410
const State * nextStart()
Return the next valid start state or nullptr if no more valid start states are available.
Definition: Planner.cpp:230
The exception type for ompl.
Definition: Exception.h:47
#define OMPL_DEBUG(fmt,...)
Log a formatted debugging string.
Definition: Console.h:70
std::size_t checkMotionScore(const base::State *s1, const base::State *s2) const
Count the number of states along the discretized path that are in collision Note: This is kind of an ...
Definition of a problem to be solved. This includes the start state(s) for the system and a goal spec...
std::vector< PathGeometric > nearestPaths_
Recall the nearest paths and store this in planner data for introspection later.
void setRepairPlanner(const base::PlannerPtr &planner)
Set the planner that will be used for repairing invalid paths recalled from experience.
point now()
Get the current time point.
Definition: Time.h:72
std::size_t getLastRecalledNearestPathChosen() const
Get debug information about the top recalled paths that were chosen for further filtering.
PathSimplifierPtr path_simplifier_
The instance of the path simplifier.
const std::vector< PathGeometric > & getLastRecalledNearestPaths() const
Get debug information about the top recalled paths that were chosen for further filtering.
void restart()
Forget how many states were returned by nextStart() and nextGoal() and return all states again...
Definition: Planner.cpp:170
ThunderRetrieveRepair(const base::SpaceInformationPtr &si, const tools::ThunderDBPtr &experienceDB)
Constructor.
Definition of a geometric path.
Definition: PathGeometric.h:60
virtual base::PlannerStatus solve(const base::PlannerTerminationCondition &ptc)
Function that can solve the motion planning problem. This function can be called multiple times on th...
std::chrono::system_clock::time_point point
Representation of a point in time.
Definition: Time.h:66
SpaceInformationPtr si_
The space information for which planning is done.
Definition: Planner.h:398
bool replan(const base::State *start, const base::State *goal, PathGeometric &newPathSegment, const base::PlannerTerminationCondition &ptc)
Use our secondary planner to find a valid path between start and goal, and return that path...
A shared pointer wrapper for ompl::base::PlannerData.
const std::string & getName() const
Get the name of the planner.
Definition: Planner.cpp:55
A shared pointer wrapper for ompl::base::Path.
virtual void clear(void)
Clear all internal datastructures. Planner settings are not affected. Subsequent calls to solve() wil...
#define OMPL_INFORM(fmt,...)
Log a formatted information string.
Definition: Console.h:68