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