Lightning.cpp
1 /*********************************************************************
2  * Software License Agreement (BSD License)
3  *
4  * Copyright (c) 2014, JSK, The University of Tokyo.
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 JSK, The University of Tokyo 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 
38 #include "ompl/tools/lightning/Lightning.h"
39 #include "ompl/tools/lightning/LightningDB.h"
40 
41 namespace og = ompl::geometric;
42 namespace ob = ompl::base;
43 namespace ot = ompl::tools;
44 
46  ompl::tools::ExperienceSetup(si)
47 {
48  initialize();
49 }
50 
52  ompl::tools::ExperienceSetup(space)
53 {
54  initialize();
55 }
56 
57 void ompl::tools::Lightning::initialize()
58 {
59  recallEnabled_ = true;
60  scratchEnabled_ = true;
61 
62  // Load dynamic time warp
63  dtw_.reset(new ot::DynamicTimeWarp(si_));
64 
65  // Load the experience database
66  experienceDB_.reset(new ompl::tools::LightningDB(si_->getStateSpace()));
67 
68  // Load the Retrieve repair database. We do it here so that setRepairPlanner() works
70 
71  OMPL_INFORM("Lightning Framework initialized.");
72 }
73 
75 {
76  if (!configured_ || !si_->isSetup() || !planner_->isSetup() || !rrPlanner_->isSetup() )
77  {
78  OMPL_INFORM("Setting up the Lightning Framework");
79 
80  if (!configured_)
81  OMPL_INFORM(" Setting up because not configured");
82  else if(!si_->isSetup())
83  OMPL_INFORM(" Setting up because not si->isSetup");
84  else if(!planner_->isSetup())
85  OMPL_INFORM(" Setting up because not planner->isSetup");
86  else if(!rrPlanner_->isSetup())
87  OMPL_INFORM(" Setting up because not rrPlanner->isSetup");
88 
89  // Setup Space Information if we haven't already done so
90  if (!si_->isSetup())
91  si_->setup();
92 
93  // Setup planning from scratch planner
94  if (!planner_)
95  {
96  if (pa_)
97  planner_ = pa_(si_);
98  if (!planner_)
99  {
100  planner_ = tools::SelfConfig::getDefaultPlanner(pdef_->getGoal()); // we could use the repairProblemDef_ here but that isn't setup yet
101 
102  OMPL_INFORM("No planner specified. Using default: %s", planner_->getName().c_str() );
103  }
104  }
105  planner_->setProblemDefinition(pdef_);
106  if (!planner_->isSetup())
107  planner_->setup();
108 
109  // Setup planning from experience planner
110  rrPlanner_->setProblemDefinition(pdef_);
111 
112  if (!rrPlanner_->isSetup())
113  rrPlanner_->setup();
114 
115  // Create the parallel component for splitting into two threads
116  pp_ = ot::ParallelPlanPtr(new ot::ParallelPlan(pdef_) );
118  {
119  throw Exception("Both planning from scratch and experience have been disabled, unable to plan");
120  }
121  if (scratchEnabled_)
122  pp_->addPlanner(planner_); // Add the planning from scratch planner if desired
123  if (recallEnabled_)
124  pp_->addPlanner(rrPlanner_); // Add the planning from experience planner if desired
125 
126 
127  // Check if experience database is already loaded
128  if (experienceDB_->isEmpty())
129  {
130  if (filePath_.empty())
131  {
132  OMPL_ERROR("No file path has been specified, unable to load experience DB");
133  }
134  else
135  {
136  experienceDB_->load(filePath_); // load from file
137  }
138  }
139  else
140  OMPL_ERROR("Attempting to load experience database when it is not empty");
141 
142  // Set the configured flag
143  configured_ = true;
144  }
145 }
146 
148 {
149  if (planner_)
150  planner_->clear();
151  if (rrPlanner_)
152  rrPlanner_->clear();
153  if (pdef_)
154  pdef_->clearSolutionPaths();
155  if (pp_)
156  {
157  pp_->clearHybridizationPaths();
158  }
159 }
160 
161 // we provide a duplicate implementation here to allow the planner to choose how the time is turned into a planner termination condition
163 {
164  OMPL_INFORM("Lightning Framework: Starting solve()");
165 
166  // Setup again in case it has not been done yet
167  setup();
168 
170  time::point start = time::now();
171 
172  // Insertion time
173  double insertionTime = 0.;
174 
175  // Start both threads
176  bool hybridize = false;
177  lastStatus_ = pp_->solve(ptc, hybridize);
178 
179  // Planning time
180  planTime_ = time::seconds(time::now() - start);
181  stats_.totalPlanningTime_ += planTime_; // used for averaging
182  stats_.numProblems_++; // used for averaging
183 
184  // Create log
186  log.planning_time = planTime_;
187 
189  {
190  // Skip further processing if absolutely no path is available
191  OMPL_ERROR("Lightning Solve: No solution found after %f seconds", planTime_);
192  stats_.numSolutionsTimedout_++;
193 
194  // Logging
195  log.planner = "neither_planner";
196  log.result = "timedout";
197  log.is_saved = "not_saved";
198  }
199  else if (!lastStatus_)
200  {
201  // Skip further processing if absolutely no path is available
202  OMPL_ERROR("Lightning Solve: Unknown failure, planner status: %s", lastStatus_.asString().c_str());
203  stats_.numSolutionsFailed_++;
204 
205  // Logging
206  log.planner = "neither_planner";
207  log.result = "failed";
208  log.is_saved = "not_saved";
209  }
210  else
211  {
212  OMPL_INFORM("Lightning Solve: Possible solution found in %f seconds", planTime_);
213 
214  // Smooth the result
215  simplifySolution(ptc);
216 
217  og::PathGeometric solutionPath = getSolutionPath(); // copied so that it is non-const
218  OMPL_INFORM("Solution path has %d states and was generated from planner %s", solutionPath.getStateCount(), getSolutionPlannerName().c_str());
219 
220  // Logging
221  log.planner = getSolutionPlannerName();
222 
223  // Do not save if approximate
224  if (!haveExactSolutionPath())
225  {
226  // Logging
227  log.result = "not_exact_solution";
228  log.is_saved = "not_saved";
229  log.approximate = true;
230 
231  // Stats
232  stats_.numSolutionsApproximate_++;
233 
234  // not sure what to do here, use case not tested
235  OMPL_INFORM("NOT saving to database because the solution is APPROXIMATE");
236  }
237  // Use dynamic time warping to see if the repaired path is too similar to the original
238  else if (getSolutionPlannerName() == rrPlanner_->getName())
239  {
240  // Stats
241  stats_.numSolutionsFromRecall_++;
242 
243  // Logging
244  log.result = "from_recall";
245 
246  // Make sure solution has at least 2 states
247  if (solutionPath.getStateCount() < 2)
248  {
249  OMPL_INFORM("NOT saving to database because solution is less than 2 states long");
250  stats_.numSolutionsTooShort_++;
251 
252  // Logging
253  log.is_saved = "less_2_states";
254  log.too_short = true;
255  }
256  else
257  {
258  // Benchmark runtime
259  time::point startTime = time::now();
260 
261  // Convert the original recalled path to PathGeometric
263  og::PathGeometric chosenRecallPath(si_);
264  convertPlannerData(chosenRecallPathData, chosenRecallPath);
265 
266  // Reverse path2 if necessary so that it matches path1 better
267  reversePathIfNecessary(solutionPath, chosenRecallPath);
268 
269  double score = dtw_->getPathsScore( solutionPath, chosenRecallPath );
270  log.score = score;
271 
272  if (score < 4)
273  {
274  OMPL_INFORM("NOT saving to database because best solution was from database and is too similar (score %f)", score);
275 
276  // Logging
277  log.insertion_failed = true;
278  log.is_saved = "score_too_similar";
279  }
280  else
281  {
282  OMPL_INFORM("Adding path to database because repaired path is different enough from original recalled path (score %f)", score);
283 
284  // Logging
285  log.insertion_failed = false;
286  log.is_saved = "score_different_enough";
287 
288  // Stats
289  stats_.numSolutionsFromRecallSaved_++;
290 
291  // Save to database
292  double dummyInsertionTime; // unused because does not include scoring function
293  experienceDB_->addPath(solutionPath, dummyInsertionTime);
294  }
295  insertionTime += time::seconds(time::now() - startTime);
296  }
297  }
298  else
299  {
300  // Logging
301  log.result = "from_scratch";
302 
303  // Stats
304  stats_.numSolutionsFromScratch_++;
305 
306  // Make sure solution has at least 2 states
307  if (solutionPath.getStateCount() < 2)
308  {
309  OMPL_INFORM("NOT saving to database because solution is less than 2 states long");
310 
311  // Logging
312  log.is_saved = "less_2_states";
313  log.too_short = true;
314 
315  // Stats
316  stats_.numSolutionsTooShort_++;
317  }
318  else
319  {
320  OMPL_INFORM("Adding path to database because best solution was not from database");
321 
322  // Logging
323  log.result = "from_scratch";
324  log.is_saved = "saving";
325 
326  // Save to database
327  experienceDB_->addPath(solutionPath, insertionTime);
328  }
329  }
330  }
331 
332  stats_.totalInsertionTime_ += insertionTime; // used for averaging
333 
334  // Final log data
335  log.insertion_time = insertionTime;
336  log.num_vertices = experienceDB_->getStatesCount();
337  log.num_edges = 0;
338  log.num_connected_components = 0;
339 
340  // Flush the log to buffer
341  convertLogToString(log);
342 
343  return lastStatus_;
344 }
345 
347 {
348  ob::PlannerTerminationCondition ptc = ob::timedPlannerTerminationCondition(time);
349  return solve(ptc);
350 }
351 
353 {
354  if (filePath_.empty())
355  {
356  OMPL_ERROR("No file path has been specified, unable to save experience DB");
357  return false;
358  }
359  return experienceDB_->save(filePath_);
360 }
361 
363 {
364  if (filePath_.empty())
365  {
366  OMPL_ERROR("No file path has been specified, unable to save experience DB");
367  return false;
368  }
369  return experienceDB_->saveIfChanged(filePath_);
370 }
371 
372 void ompl::tools::Lightning::printResultsInfo(std::ostream &out) const
373 {
374  for (std::size_t i = 0; i < pdef_->getSolutionCount(); ++i)
375  {
376  out << "Solution " << i
377  << " | Length: " << pdef_->getSolutions()[i].length_
378  << " | Approximate: " << (pdef_->getSolutions()[i].approximate_ ? "true" : "false")
379  << " | Planner: " << pdef_->getSolutions()[i].plannerName_
380  << std::endl;
381  }
382 }
383 
384 void ompl::tools::Lightning::print(std::ostream &out) const
385 {
386  if (si_)
387  {
388  si_->printProperties(out);
389  si_->printSettings(out);
390  }
391  if (planner_)
392  {
393  planner_->printProperties(out);
394  planner_->printSettings(out);
395  }
396  if (rrPlanner_)
397  {
398  rrPlanner_->printProperties(out);
399  rrPlanner_->printSettings(out);
400  }
401  if (pdef_)
402  pdef_->print(out);
403 }
404 
405 void ompl::tools::Lightning::printLogs(std::ostream &out) const
406 {
407  out << "Lightning Framework Logging Results" << std::endl;
408  out << " Solutions Attempted: " << stats_.numProblems_ << std::endl;
409  out << " Solved from scratch: " << stats_.numSolutionsFromScratch_ << " (" << stats_.numSolutionsFromScratch_/stats_.numProblems_*100.0 << "%)" << std::endl;
410  out << " Solved from recall: " << stats_.numSolutionsFromRecall_ << " (" << stats_.numSolutionsFromRecall_/stats_.numProblems_*100.0 << "%)" << std::endl;
411  out << " That were saved: " << stats_.numSolutionsFromRecallSaved_ << std::endl;
412  out << " That were discarded: " << stats_.numSolutionsFromRecall_ - stats_.numSolutionsFromRecallSaved_ << std::endl;
413  out << " Less than 2 states: " << stats_.numSolutionsTooShort_ << std::endl;
414  out << " Failed: " << stats_.numSolutionsFailed_ << std::endl;
415  out << " Timedout: " << stats_.numSolutionsTimedout_ << std::endl;
416  out << " Approximate: " << stats_.numSolutionsApproximate_ << std::endl;
417  out << " LightningDb " << std::endl;
418  out << " Total paths: " << experienceDB_->getExperiencesCount() << std::endl;
419  out << " Vertices (states): " << experienceDB_->getStatesCount() << std::endl;
420  out << " Unsaved solutions: " << experienceDB_->getNumUnsavedPaths() << std::endl;
421  out << " Average planning time: " << stats_.getAveragePlanningTime() << std::endl;
422  out << " Average insertion time: " << stats_.getAverageInsertionTime() << std::endl;
423 }
424 
426 {
427  return experienceDB_->getExperiencesCount();
428 }
429 
430 void ompl::tools::Lightning::getAllPlannerDatas(std::vector<ob::PlannerDataPtr> &plannerDatas) const
431 {
432  experienceDB_->getAllPlannerDatas(plannerDatas);
433 }
434 
436 {
437  // Convert the planner data verticies into a vector of states
438  for (std::size_t i = 0; i < plannerData->numVertices(); ++i)
439  path.append(plannerData->getVertex(i).getState());
440 }
441 
443 {
444  // Reverse path2 if it matches better
445  const ob::State* s1 = path1.getState(0);
446  const ob::State* s2 = path2.getState(0);
447  const ob::State* g1 = path1.getState(path1.getStateCount()-1);
448  const ob::State* g2 = path2.getState(path2.getStateCount()-1);
449 
450  double regularDistance = si_->distance(s1, s2) + si_->distance(g1, g2);
451  double reversedDistance = si_->distance(s1, g2) + si_->distance(s2, g1);
452 
453  // Check if path is reversed from normal [start->goal] direction
454  if ( regularDistance > reversedDistance )
455  {
456  // needs to be reversed
457  path2.reverse();
458  return true;
459  }
460 
461  return false;
462 }
Save and load entire paths from file.
Definition: LightningDB.h:73
This is a utility that allows executing multiple planners in parallel, until one or more find a solut...
Definition: ParallelPlan.h:66
const std::string getSolutionPlannerName(void) const
Get the best solution&#39;s planer name. Throw an exception if no solution is available.
Single entry for the csv data logging file.
void log(const char *file, int line, LogLevel level, const char *m,...)
Root level logging function. This should not be invoked directly, but rather used via a logging macro...
Definition: Console.cpp:120
PathGeometric & getSolutionPath() const
Get the solution path. Throw an exception if no solution is available.
base::PlannerAllocator pa_
The optional planner allocator.
Definition: SimpleSetup.h:286
The planner failed to find a solution.
Definition: PlannerStatus.h:62
A shared pointer wrapper for ompl::base::StateSpace.
base::PlannerDataPtr getChosenRecallPath() const
Get the chosen path used from database for repair.
bool saveIfChanged()
Save the experience database to file if there has been a change.
Definition: Lightning.cpp:362
virtual void print(std::ostream &out=std::cout) const
Print information about the current setup.
Definition: Lightning.cpp:384
virtual void setup()
This method will create the necessary classes for planning. The solve() method will call this functio...
Definition: Lightning.cpp:74
Encapsulate a termination condition for a motion planner. Planners will call operator() to decide whe...
void getAllPlannerDatas(std::vector< ompl::base::PlannerDataPtr > &plannerDatas) const
Get a vector of all the planning data in the database.
Definition: Lightning.cpp:430
void printResultsInfo(std::ostream &out=std::cout) const
Display debug data about potential available solutions.
Definition: Lightning.cpp:372
std::size_t getExperiencesCount() const
Get the total number of paths stored in the database.
Definition: Lightning.cpp:425
void append(const base::State *state)
Append state to the end of this path. The memory for state is copied.
base::SpaceInformationPtr si_
The created space information.
Definition: SimpleSetup.h:277
ompl::tools::LightningDBPtr experienceDB_
A shared object between all the planners for saving and loading previous experience.
Definition: Lightning.h:215
base::State * getState(unsigned int index)
Get the state located at index along the path.
Includes various tools such as self config, benchmarking, etc.
std::size_t getStateCount() const
Get the number of states (way-points) that make up this path.
bool reversePathIfNecessary(ompl::geometric::PathGeometric &path1, ompl::geometric::PathGeometric &path2)
If path1 and path2 have a better start/goal match when reverse, then reverse path2.
Definition: Lightning.cpp:442
ExperienceStats stats_
States data for display to console.
duration seconds(double sec)
Return the time duration representing a given number of seconds.
Definition: Time.h:78
ompl::tools::DynamicTimeWarpPtr dtw_
Tool for comparing two paths and scoring them.
Definition: Lightning.h:218
Main namespace. Contains everything in this library.
Definition: Cost.h:42
A shared pointer wrapper for ompl::base::Planner.
std::string asString() const
Return a string representation.
#define OMPL_ERROR(fmt,...)
Log a formatted error string.
Definition: Console.h:64
std::string filePath_
File location of database.
base::PlannerPtr planner_
The maintained planner instance.
Definition: SimpleSetup.h:283
void convertPlannerData(const ompl::base::PlannerDataPtr plannerData, ompl::geometric::PathGeometric &path)
Convert PlannerData to PathGeometric. Assume ordering of verticies is order of path.
Definition: Lightning.cpp:435
void reverse()
Reverse the path.
base::ProblemDefinitionPtr pdef_
The created problem definition.
Definition: SimpleSetup.h:280
A class to store the exit status of Planner::solve()
Definition: PlannerStatus.h:48
A shared pointer wrapper for ompl::base::SpaceInformation.
ompl::geometric::LightningRetrieveRepair & getLightningRetrieveRepairPlanner() const
Get a pointer to the retrieve repair planner.
Definition: Lightning.h:130
The Lightning Framework&#39;s Retrieve-Repair component.
Definition of an abstract state.
Definition: State.h:50
This namespace contains sampling based planning routines shared by both planning under geometric cons...
Definition: Cost.h:44
bool haveExactSolutionPath() const
Return true if a solution path is available (previous call to solve() was successful) and the solutio...
bool scratchEnabled_
Flag indicating whether planning from scratch should be used to find solutions. Enabled by default...
static base::PlannerPtr getDefaultPlanner(const base::GoalPtr &goal)
Given a goal specification, decide on a planner for that goal.
Definition: SelfConfig.cpp:248
The exception type for ompl.
Definition: Exception.h:47
base::PlannerStatus lastStatus_
The status of the last planning request.
Definition: SimpleSetup.h:301
point now()
Get the current time point.
Definition: Time.h:72
virtual void clear()
Clear all planning data. This only includes data generated by motion plan computation. Planner settings, start & goal states are not affected.
Definition: Lightning.cpp:147
bool configured_
Flag indicating whether the classes needed for planning are set up.
Definition: SimpleSetup.h:292
double planTime_
The amount of time the last planning step took.
Definition: SimpleSetup.h:295
base::PlannerPtr rrPlanner_
The maintained experience planner instance.
Definition: Lightning.h:209
void simplifySolution(double duration=0.0)
Attempt to simplify the current solution path. Spent at most duration seconds in the simplification p...
Definition of a geometric path.
Definition: PathGeometric.h:60
void printLogs(std::ostream &out=std::cout) const
Display debug data about overall results from Lightning since being loaded.
Definition: Lightning.cpp:405
std::chrono::system_clock::time_point point
Representation of a point in time.
Definition: Time.h:66
bool recallEnabled_
Flag indicating whether recalled plans should be used to find solutions. Enabled by default...
void convertLogToString(const ExperienceLog &log)
Move data to string format and put in buffer.
virtual base::PlannerStatus solve(double time=1.0)
Run the planner for up to a specified amount of time (default is 1 second)
Definition: Lightning.cpp:346
bool save()
Save the experience database to file.
Definition: Lightning.cpp:352
ompl::tools::ParallelPlanPtr pp_
Instance of parallel planning to use for computing solutions in parallel.
Definition: Lightning.h:212
A shared pointer wrapper for ompl::base::PlannerData.
Lightning(const base::SpaceInformationPtr &si)
Constructor needs the state space used for planning.
Definition: Lightning.cpp:45
Create the set of classes typically needed to solve a geometric problem.
This namespace contains code that is specific to planning under geometric constraints.
Definition: GeneticSearch.h:48
#define OMPL_INFORM(fmt,...)
Log a formatted information string.
Definition: Console.h:68