Syclop.cpp
1 /*********************************************************************
2 * Software License Agreement (BSD License)
3 *
4 * Copyright (c) 2011, Rice University
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 Rice University 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: Matt Maly */
36 
37 #include "ompl/control/planners/syclop/Syclop.h"
38 #include "ompl/base/goals/GoalSampleableRegion.h"
39 #include "ompl/base/ProblemDefinition.h"
40 #include <limits>
41 #include <stack>
42 #include <algorithm>
43 
44 const double ompl::control::Syclop::Defaults::PROB_ABANDON_LEAD_EARLY = 0.25;
45 const double ompl::control::Syclop::Defaults::PROB_KEEP_ADDING_TO_AVAIL = 0.50;
46 const double ompl::control::Syclop::Defaults::PROB_SHORTEST_PATH = 0.95;
47 
49 {
51  if (!leadComputeFn)
52  setLeadComputeFn(std::bind(&ompl::control::Syclop::defaultComputeLead, this,
53  std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
54  buildGraph();
55  addEdgeCostFactor(std::bind(&ompl::control::Syclop::defaultEdgeCost, this,
56  std::placeholders::_1, std::placeholders::_2));
57 }
58 
60 {
62  lead_.clear();
63  availDist_.clear();
64  clearEdgeCostFactors();
65  clearGraphDetails();
66  startRegions_.clear();
67  goalRegions_.clear();
68 }
69 
71 {
72  checkValidity();
73  if (!graphReady_)
74  {
75  numMotions_ = 0;
76  setupRegionEstimates();
77  setupEdgeEstimates();
78  graphReady_ = true;
79  }
80  while (const base::State *s = pis_.nextStart())
81  {
82  const int region = decomp_->locateRegion(s);
83  startRegions_.insert(region);
84  Motion *startMotion = addRoot(s);
85  graph_[boost::vertex(region, graph_)].motions.push_back(startMotion);
86  ++numMotions_;
87  updateCoverageEstimate(graph_[boost::vertex(region, graph_)], s);
88  }
89  if (startRegions_.empty())
90  {
91  OMPL_ERROR("%s: There are no valid start states", getName().c_str());
93  }
94 
95  //We need at least one valid goal sample so that we can find the goal region
96  if (goalRegions_.empty())
97  {
98  if (const base::State *g = pis_.nextGoal(ptc))
99  goalRegions_.insert(decomp_->locateRegion(g));
100  else
101  {
102  OMPL_ERROR("%s: Unable to sample a valid goal state", getName().c_str());
104  }
105  }
106 
107  OMPL_INFORM("%s: Starting planning with %u states already in datastructure", getName().c_str(), numMotions_);
108 
109  std::vector<Motion*> newMotions;
110  const Motion *solution = nullptr;
111  base::Goal *goal = pdef_->getGoal().get();
112  double goalDist = std::numeric_limits<double>::infinity();
113  bool solved = false;
114  while (!ptc && !solved)
115  {
116  const int chosenStartRegion = startRegions_.sampleUniform();
117  int chosenGoalRegion = -1;
118 
119  // if we have not sampled too many goal regions already
120  if (pis_.haveMoreGoalStates() && goalRegions_.size() < numMotions_/2)
121  {
122  if (const base::State *g = pis_.nextGoal())
123  {
124  chosenGoalRegion = decomp_->locateRegion(g);
125  goalRegions_.insert(chosenGoalRegion);
126  }
127  }
128  if (chosenGoalRegion == -1)
129  chosenGoalRegion = goalRegions_.sampleUniform();
130 
131  leadComputeFn(chosenStartRegion, chosenGoalRegion, lead_);
132  computeAvailableRegions();
133  for (int i = 0; i < numRegionExpansions_ && !solved && !ptc; ++i)
134  {
135  const int region = selectRegion();
136  bool improved = false;
137  for (int j = 0; j < numTreeSelections_ && !solved && !ptc; ++j)
138  {
139  newMotions.clear();
140  selectAndExtend(graph_[boost::vertex(region, graph_)], newMotions);
141  for (std::vector<Motion*>::const_iterator m = newMotions.begin(); m != newMotions.end() && !ptc; ++m)
142  {
143  Motion *motion = *m;
144  double distance;
145  solved = goal->isSatisfied(motion->state, &distance);
146  if (solved)
147  {
148  goalDist = distance;
149  solution = motion;
150  break;
151  }
152 
153  // Check for approximate (best-so-far) solution
154  if (distance < goalDist)
155  {
156  goalDist = distance;
157  solution = motion;
158  }
159  const int newRegion = decomp_->locateRegion(motion->state);
160  graph_[boost::vertex(newRegion, graph_)].motions.push_back(motion);
161  ++numMotions_;
162  Region &newRegionObj = graph_[boost::vertex(newRegion, graph_)];
163  improved |= updateCoverageEstimate(newRegionObj, motion->state);
164  /* If tree has just crossed from one region to its neighbor,
165  update the connection estimates. If the tree has crossed an entire region,
166  then region and newRegion are not adjacent, and so we do not update estimates. */
167  if (newRegion != region
168  && regionsToEdge_.count(std::pair<int,int>(region,newRegion)) > 0)
169  {
170  Adjacency *adj = regionsToEdge_[std::pair<int,int>(region,newRegion)];
171  adj->empty = false;
172  ++adj->numSelections;
173  improved |= updateConnectionEstimate(graph_[boost::vertex(region, graph_)], newRegionObj, motion->state);
174  }
175 
176  /* If this region already exists in availDist, update its weight. */
177  if (newRegionObj.pdfElem != nullptr)
178  availDist_.update(newRegionObj.pdfElem, newRegionObj.weight);
179  /* Otherwise, only add this region to availDist
180  if it already exists in the lead. */
181  else if (std::find(lead_.begin(), lead_.end(), newRegion) != lead_.end())
182  {
183  PDF<int>::Element *elem = availDist_.add(newRegion, newRegionObj.weight);
184  newRegionObj.pdfElem = elem;
185  }
186  }
187  }
188  if (!improved && rng_.uniform01() < probAbandonLeadEarly_)
189  break;
190  }
191  }
192  bool addedSolution = false;
193  if (solution != nullptr)
194  {
195  std::vector<const Motion*> mpath;
196  while (solution != nullptr)
197  {
198  mpath.push_back(solution);
199  solution = solution->parent;
200  }
201  PathControl *path = new PathControl(si_);
202  for (int i = mpath.size()-1; i >= 0; --i)
203  if (mpath[i]->parent)
204  path->append(mpath[i]->state, mpath[i]->control, mpath[i]->steps * siC_->getPropagationStepSize());
205  else
206  path->append(mpath[i]->state);
207  pdef_->addSolutionPath(base::PathPtr(path), !solved, goalDist, getName());
208  addedSolution = true;
209  }
211 }
212 
214 {
215  leadComputeFn = compute;
216 }
217 
219 {
220  edgeCostFactors_.push_back(factor);
221 }
222 
224 {
225  edgeCostFactors_.clear();
226 }
227 
228 void ompl::control::Syclop::initRegion(Region &r)
229 {
230  r.numSelections = 0;
231  r.volume = 1.0;
232  r.percentValidCells = 1.0;
233  r.freeVolume = 1.0;
234  r.pdfElem = nullptr;
235 }
236 
237 void ompl::control::Syclop::setupRegionEstimates()
238 {
239  std::vector<int> numTotal(decomp_->getNumRegions(), 0);
240  std::vector<int> numValid(decomp_->getNumRegions(), 0);
241  base::StateValidityCheckerPtr checker = si_->getStateValidityChecker();
242  base::StateSamplerPtr sampler = si_->allocStateSampler();
243  base::State *s = si_->allocState();
244 
245  for (int i = 0; i < numFreeVolSamples_; ++i)
246  {
247  sampler->sampleUniform(s);
248  int rid = decomp_->locateRegion(s);
249  if (rid >= 0)
250  {
251  if (checker->isValid(s))
252  ++numValid[rid];
253  ++numTotal[rid];
254  }
255  }
256  si_->freeState(s);
257 
258  for (int i = 0; i < decomp_->getNumRegions(); ++i)
259  {
260  Region &r = graph_[boost::vertex(i, graph_)];
261  r.volume = decomp_->getRegionVolume(i);
262  if (numTotal[i] == 0)
263  r.percentValidCells = 1.0;
264  else
265  r.percentValidCells = ((double) numValid[i]) / (double)numTotal[i];
266  r.freeVolume = r.percentValidCells * r.volume;
267  if (r.freeVolume < std::numeric_limits<double>::epsilon())
268  r.freeVolume = std::numeric_limits<double>::epsilon();
269  updateRegion(r);
270  }
271 }
272 
273 void ompl::control::Syclop::updateRegion(Region &r)
274 {
275  const double f = r.freeVolume*r.freeVolume*r.freeVolume*r.freeVolume;
276  r.alpha = 1.0 / ((1 + r.covGridCells.size()) * f);
277  r.weight = f / ((1 + r.covGridCells.size())*(1 + r.numSelections*r.numSelections));
278 }
279 
280 void ompl::control::Syclop::initEdge(Adjacency &adj, const Region *source, const Region *target)
281 {
282  adj.source = source;
283  adj.target = target;
284  updateEdge(adj);
285  regionsToEdge_[std::pair<int,int>(source->index, target->index)] = &adj;
286 }
287 
288 void ompl::control::Syclop::setupEdgeEstimates()
289 {
290  EdgeIter ei, eend;
291  for (boost::tie(ei,eend) = boost::edges(graph_) ; ei != eend; ++ei)
292  {
293  Adjacency &adj = graph_[*ei];
294  adj.empty = true;
295  adj.numLeadInclusions = 0;
296  adj.numSelections = 0;
297  updateEdge(adj);
298  }
299 }
300 
301 void ompl::control::Syclop::updateEdge(Adjacency &a)
302 {
303  a.cost = 1.0;
304  for (std::vector<EdgeCostFactorFn>::const_iterator i = edgeCostFactors_.begin(); i != edgeCostFactors_.end(); ++i)
305  {
306  const EdgeCostFactorFn& factor = *i;
307  a.cost *= factor(a.source->index, a.target->index);
308  }
309 }
310 
311 bool ompl::control::Syclop::updateCoverageEstimate(Region &r, const base::State *s)
312 {
313  const int covCell = covGrid_.locateRegion(s);
314  if (r.covGridCells.count(covCell) == 1)
315  return false;
316  r.covGridCells.insert(covCell);
317  updateRegion(r);
318  return true;
319 }
320 
321 bool ompl::control::Syclop::updateConnectionEstimate(const Region &c, const Region &d, const base::State *s)
322 {
323  Adjacency &adj = *regionsToEdge_[std::pair<int,int>(c.index,d.index)];
324  const int covCell = covGrid_.locateRegion(s);
325  if (adj.covGridCells.count(covCell) == 1)
326  return false;
327  adj.covGridCells.insert(covCell);
328  updateEdge(adj);
329  return true;
330 }
331 
332 void ompl::control::Syclop::buildGraph()
333 {
334  VertexIndexMap index = get(boost::vertex_index, graph_);
335  std::vector<int> neighbors;
336  for (int i = 0; i < decomp_->getNumRegions(); ++i)
337  {
338  const RegionGraph::vertex_descriptor v = boost::add_vertex(graph_);
339  Region &r = graph_[boost::vertex(v, graph_)];
340  initRegion(r);
341  r.index = index[v];
342  }
343  VertexIter vi, vend;
344  for (boost::tie(vi,vend) = boost::vertices(graph_); vi != vend; ++vi)
345  {
346  /* Create an edge between this vertex and each of its neighboring regions in the decomposition,
347  and initialize the edge's Adjacency object. */
348  decomp_->getNeighbors(index[*vi], neighbors);
349  for (std::vector<int>::const_iterator j = neighbors.begin(); j != neighbors.end(); ++j)
350  {
351  RegionGraph::edge_descriptor edge;
352  bool ignore;
353  boost::tie(edge, ignore) = boost::add_edge(*vi, boost::vertex(*j, graph_), graph_);
354  initEdge(graph_[edge], &graph_[*vi], &graph_[boost::vertex(*j, graph_)]);
355  }
356  neighbors.clear();
357  }
358 }
359 
360 void ompl::control::Syclop::clearGraphDetails()
361 {
362  VertexIter vi, vend;
363  for (boost::tie(vi,vend) = boost::vertices(graph_); vi != vend; ++vi)
364  graph_[*vi].clear();
365  EdgeIter ei, eend;
366  for (boost::tie(ei,eend) = boost::edges(graph_); ei != eend; ++ei)
367  graph_[*ei].clear();
368  graphReady_ = false;
369 }
370 
371 int ompl::control::Syclop::selectRegion()
372 {
373  const int index = availDist_.sample(rng_.uniform01());
374  Region &region = graph_[boost::vertex(index, graph_)];
375  ++region.numSelections;
376  updateRegion(region);
377  return index;
378 }
379 
380 void ompl::control::Syclop::computeAvailableRegions()
381 {
382  for (unsigned int i = 0; i < availDist_.size(); ++i)
383  graph_[boost::vertex(availDist_[i],graph_)].pdfElem = nullptr;
384  availDist_.clear();
385  for (int i = lead_.size()-1; i >= 0; --i)
386  {
387  Region &r = graph_[boost::vertex(lead_[i], graph_)];
388  if (!r.motions.empty())
389  {
390  r.pdfElem = availDist_.add(lead_[i], r.weight);
391  if (rng_.uniform01() >= probKeepAddingToAvail_)
392  break;
393  }
394  }
395 }
396 
397 void ompl::control::Syclop::defaultComputeLead(int startRegion, int goalRegion, std::vector<int>& lead)
398 {
399  lead.clear();
400  if (startRegion == goalRegion)
401  {
402  lead.push_back(startRegion);
403  return;
404  }
405 
406  else if (rng_.uniform01() < probShortestPath_)
407  {
408  std::vector<RegionGraph::vertex_descriptor> parents(decomp_->getNumRegions());
409  std::vector<double> distances(decomp_->getNumRegions());
410 
411  try
412  {
413  boost::astar_search(graph_, boost::vertex(startRegion, graph_), DecompositionHeuristic(this, getRegionFromIndex(goalRegion)),
414  boost::weight_map(get(&Adjacency::cost, graph_)).distance_map(
415  boost::make_iterator_property_map(distances.begin(), get(boost::vertex_index, graph_)
416  )).predecessor_map(
417  boost::make_iterator_property_map(parents.begin(), get(boost::vertex_index, graph_))
418  ).visitor(GoalVisitor(goalRegion))
419  );
420  }
421  catch (found_goal fg)
422  {
423  int region = goalRegion;
424  int leadLength = 1;
425 
426  while (region != startRegion)
427  {
428  region = parents[region];
429  ++leadLength;
430  }
431  lead.resize(leadLength);
432  region = goalRegion;
433  for (int i = leadLength-1; i >= 0; --i)
434  {
435  lead[i] = region;
436  region = parents[region];
437  }
438  }
439  }
440  else
441  {
442  /* Run a random-DFS over the decomposition graph from the start region to the goal region.
443  There may be a way to do this using boost::depth_first_search. */
444  VertexIndexMap index = get(boost::vertex_index, graph_);
445  std::stack<int> nodesToProcess;
446  std::vector<int> parents(decomp_->getNumRegions(), -1);
447  parents[startRegion] = startRegion;
448  nodesToProcess.push(startRegion);
449  bool goalFound = false;
450  while (!goalFound && !nodesToProcess.empty())
451  {
452  const int v = nodesToProcess.top();
453  nodesToProcess.pop();
454  std::vector<int> neighbors;
455  boost::graph_traits<RegionGraph>::adjacency_iterator ai, aend;
456  for (boost::tie(ai,aend) = adjacent_vertices(boost::vertex(v, graph_), graph_); ai != aend; ++ai)
457  {
458  if (parents[index[*ai]] < 0)
459  {
460  neighbors.push_back(index[*ai]);
461  parents[index[*ai]] = v;
462  }
463  }
464  for (std::size_t i = 0; i < neighbors.size(); ++i)
465  {
466  const int choice = rng_.uniformInt(i, neighbors.size()-1);
467  if (neighbors[choice] == goalRegion)
468  {
469  int region = goalRegion;
470  int leadLength = 1;
471  while (region != startRegion)
472  {
473  region = parents[region];
474  ++leadLength;
475  }
476  lead.resize(leadLength);
477  region = goalRegion;
478  for (int j = leadLength-1; j >= 0; --j)
479  {
480  lead[j] = region;
481  region = parents[region];
482  }
483  goalFound = true;
484  break;
485  }
486  nodesToProcess.push(neighbors[choice]);
487  std::swap(neighbors[i], neighbors[choice]);
488  }
489  }
490  }
491 
492  //Now that we have a lead, update the edge weights.
493  for (std::size_t i = 0; i < lead.size()-1; ++i)
494  {
495  Adjacency &adj = *regionsToEdge_[std::pair<int,int>(lead[i], lead[i+1])];
496  if (adj.empty)
497  {
498  ++adj.numLeadInclusions;
499  updateEdge(adj);
500  }
501  }
502 }
503 
504 double ompl::control::Syclop::defaultEdgeCost(int r, int s)
505 {
506  const Adjacency &a = *regionsToEdge_[std::pair<int,int>(r,s)];
507  double factor = 1.0;
508  const int nsel = (a.empty ? a.numLeadInclusions : a.numSelections);
509  factor = (double)(1 + nsel*nsel) / (double)(1 + a.covGridCells.size()*a.covGridCells.size());
510  factor *= (a.source->alpha * a.target->alpha);
511  return factor;
512 }
virtual void setup()
Perform extra configuration steps, if needed. This call will also issue a call to ompl::base::SpaceIn...
Definition: Syclop.cpp:48
base::State * state
The state contained by the motion.
Definition: Syclop.h:266
Representation of an adjacency (a directed edge) between two regions in the Decomposition assigned to...
Definition: Syclop.h:329
The planner failed to find a solution.
Definition: PlannerStatus.h:62
int numSelections
The number of times the low-level tree planner has selected motions from the source region when attem...
Definition: Syclop.h:356
void append(const base::State *state)
Append state to the end of the path; it is assumed state is the first state, so no control is applied...
PDF< int >::Element * pdfElem
The Element corresponding to this region in the PDF of available regions.
Definition: Syclop.h:322
A shared pointer wrapper for ompl::base::StateSampler.
Representation of a region in the Decomposition assigned to Syclop.
Definition: Syclop.h:278
Abstract definition of goals.
Definition: Goal.h:62
std::function< void(int, int, std::vector< int > &)> LeadComputeFn
Leads should consist of a path of adjacent regions in the decomposition that start with the start reg...
Definition: Syclop.h:92
Encapsulate a termination condition for a motion planner. Planners will call operator() to decide whe...
virtual void clear()
Clear all internal datastructures. Planner settings are not affected. Subsequent calls to solve() wil...
Definition: Planner.cpp:112
Representation of a motion.
Definition: Syclop.h:252
Definition of a control path.
Definition: PathControl.h:60
A shared pointer wrapper for ompl::base::StateValidityChecker.
virtual void clear()
Clear all internal datastructures. Planner settings are not affected. Subsequent calls to solve() wil...
Definition: Syclop.cpp:59
A container that supports probabilistic sampling over weighted data.
Definition: PDF.h:48
void clearEdgeCostFactors()
Clears all edge cost factors, making all edge weights equivalent to 1.
Definition: Syclop.cpp:223
Invalid start state or no start state specified.
Definition: PlannerStatus.h:56
virtual base::PlannerStatus solve(const base::PlannerTerminationCondition &ptc)
Continues solving until a solution is found or a given planner termination condition is met...
Definition: Syclop.cpp:70
#define OMPL_ERROR(fmt,...)
Log a formatted error string.
Definition: Console.h:64
void addEdgeCostFactor(const EdgeCostFactorFn &factor)
Adds an edge cost factor to be used for edge weights between adjacent regions.
Definition: Syclop.cpp:218
bool empty
This value is true if and only if this adjacency's source and target regions both contain zero tree m...
Definition: Syclop.h:358
The planner found an exact solution.
Definition: PlannerStatus.h:66
virtual void setup()
Perform extra configuration steps, if needed. This call will also issue a call to ompl::base::SpaceIn...
Definition: Planner.cpp:86
std::function< double(int, int)> EdgeCostFactorFn
Each edge weight between two adjacent regions in the Decomposition is defined as a product of edge co...
Definition: Syclop.h:89
A class to store the exit status of Planner::solve()
Definition: PlannerStatus.h:48
Definition of an abstract state.
Definition: State.h:50
virtual bool isSatisfied(const State *st) const =0
Return true if the state satisfies the goal constraints.
void setLeadComputeFn(const LeadComputeFn &compute)
Allows the user to override the lead computation function.
Definition: Syclop.cpp:213
Element * add(const _T &d, const double w)
Adds a piece of data with a given weight to the PDF. Returns a corresponding Element, which can be used to subsequently update or remove the data from the PDF.
Definition: PDF.h:97
const Motion * parent
The parent motion in the tree.
Definition: Syclop.h:270
double weight
The probabilistic weight of this region, used when sampling from PDF.
Definition: Syclop.h:314
A shared pointer wrapper for ompl::base::Path.
#define OMPL_INFORM(fmt,...)
Log a formatted information string.
Definition: Console.h:68