Loading...
Searching...
No Matches
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
40namespace og = ompl::geometric;
41namespace ob = ompl::base;
42namespace ot = ompl::tools;
43
44ompl::tools::Lightning::Lightning(const base::SpaceInformationPtr &si) : ompl::tools::ExperienceSetup(si)
45{
46 initialize();
47}
48
49ompl::tools::Lightning::Lightning(const base::StateSpacePtr &space) : ompl::tools::ExperienceSetup(space)
50{
51 initialize();
52}
53
54void 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_);
83 {
84 throw Exception("Both planning from scratch and experience have been disabled, unable to plan");
85 }
87 pp_->addPlanner(planner_); // Add the planning from scratch planner if desired
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
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
148 ExperienceLog log;
149 log.planning_time = planTime_;
150
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 }
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
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
323
324 return lastStatus_;
325}
326
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
353void 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
363void 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
384void 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
412void ompl::tools::Lightning::getAllPlannerDatas(std::vector<ob::PlannerDataPtr> &plannerDatas) const
413{
414 experienceDB_->getAllPlannerDatas(plannerDatas);
415}
416
417void 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}
The exception type for ompl.
Definition Exception.h:47
Encapsulate a termination condition for a motion planner. Planners will call operator() to decide whe...
Definition of an abstract state.
Definition State.h:50
Definition of a geometric path.
std::size_t getStateCount() const
Get the number of states (way-points) that make up this path.
void reverse()
Reverse the path.
base::State * getState(unsigned int index)
Get the state located at index along the path.
PathGeometric & getSolutionPath() const
Get the solution path. Throw an exception if no solution is available.
double planTime_
The amount of time the last planning step took.
base::PlannerStatus lastStatus_
The status of the last planning request.
bool haveExactSolutionPath() const
Return true if a solution path is available (previous call to solve() was successful) and the solutio...
const std::string getSolutionPlannerName() const
Get the best solution's planer name. Throw an exception if no solution is available.
bool configured_
Flag indicating whether the classes needed for planning are set up.
base::PlannerPtr planner_
The maintained planner instance.
void simplifySolution(double duration=0.0)
Attempt to simplify the current solution path. Spent at most duration seconds in the simplification p...
base::SpaceInformationPtr si_
The created space information.
base::ProblemDefinitionPtr pdef_
The created problem definition.
std::string filePath_
File location of database.
void convertLogToString(const ExperienceLog &log)
Move data to string format and put in buffer.
ExperienceStats stats_
States data for display to console.
bool recallEnabled_
Flag indicating whether recalled plans should be used to find solutions. Enabled by default.
bool scratchEnabled_
Flag indicating whether planning from scratch should be used to find solutions. Enabled by default.
ExperienceSetup(const base::SpaceInformationPtr &si)
Constructor needs the state space used for planning.
ompl::tools::LightningDBPtr experienceDB_
A shared object between all the planners for saving and loading previous experience.
Definition Lightning.h:210
std::size_t getExperiencesCount() const override
Get the total number of paths stored in the database.
ompl::geometric::LightningRetrieveRepair & getLightningRetrieveRepairPlanner() const
Get a pointer to the retrieve repair planner.
Definition Lightning.h:125
void clear() override
Clear all planning data. This only includes data generated by motion plan computation....
void printLogs(std::ostream &out=std::cout) const override
Display debug data about overall results from Lightning since being loaded.
bool saveIfChanged() override
Save the experience database to file if there has been a change.
bool save() override
Save the experience database to file.
void getAllPlannerDatas(std::vector< ompl::base::PlannerDataPtr > &plannerDatas) const override
Get a vector of all the planning data in the database.
ompl::tools::ParallelPlanPtr pp_
Instance of parallel planning to use for computing solutions in parallel.
Definition Lightning.h:207
void printResultsInfo(std::ostream &out=std::cout) const override
Display debug data about potential available solutions.
base::PlannerPtr rrPlanner_
The maintained experience planner instance.
Definition Lightning.h:204
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.
void setup() override
This method will create the necessary classes for planning. The solve() method will call this functio...
Definition Lightning.cpp:68
void convertPlannerData(const ompl::base::PlannerDataPtr &plannerData, ompl::geometric::PathGeometric &path)
Convert PlannerData to PathGeometric. Assume ordering of vertices is order of path.
base::PlannerStatus solve(double time=1.0) override
Run the planner for up to a specified amount of time (default is 1 second).
void print(std::ostream &out=std::cout) const override
Print information about the current setup.
Lightning(const base::SpaceInformationPtr &si)
Constructor needs the state space used for planning.
Definition Lightning.cpp:44
ompl::tools::DynamicTimeWarpPtr dtw_
Tool for comparing two paths and scoring them.
Definition Lightning.h:213
#define OMPL_INFORM(fmt,...)
Log a formatted information string.
Definition Console.h:68
#define OMPL_ERROR(fmt,...)
Log a formatted error string.
Definition Console.h:64
This namespace contains sampling based planning routines shared by both planning under geometric cons...
PlannerTerminationCondition timedPlannerTerminationCondition(double duration)
Return a termination condition that will become true duration seconds in the future (wall-time).
This namespace contains code that is specific to planning under geometric constraints.
Namespace containing time datatypes and time operations.
Definition Time.h:50
std::chrono::system_clock::time_point point
Representation of a point in time.
Definition Time.h:52
point now()
Get the current time point.
Definition Time.h:58
duration seconds(double sec)
Return the time duration representing a given number of seconds.
Definition Time.h:64
Includes various tools such as self config, benchmarking, etc.
Main namespace. Contains everything in this library.
A class to store the exit status of Planner::solve().
@ INVALID_START
Invalid start state or no start state specified.
@ INVALID_GOAL
Invalid goal state.
@ UNRECOGNIZED_GOAL_TYPE
The goal is of a type that a planner does not recognize.
@ TIMEOUT
The planner failed to find a solution.
@ UNKNOWN
Uninitialized status.
Single entry for the csv data logging file.