Task.cpp
Go to the documentation of this file.
1 /*
2  * This file is part of ArmarX.
3  *
4  * Copyright (C) 2011-2016, High Performance Humanoid Technologies (H2T), Karlsruhe Institute of Technology (KIT), all rights reserved.
5  *
6  * ArmarX is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License version 2 as
8  * published by the Free Software Foundation.
9  *
10  * ArmarX is distributed in the hope that it will be useful, but
11  * WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program. If not, see <http://www.gnu.org/licenses/>.
17  *
18  * @package RobotComponents
19  * @author Raphael Grimm ( raphael dot grimm at kit dot edu )
20  * @date 2015
21  * @copyright http://www.gnu.org/licenses/gpl.txt
22  * GNU General Public License
23  */
24 
25 #include "Task.h"
26 #include "../../util/CollisionCheckUtil.h"
27 
28 #include <algorithm>
29 
30 namespace armarx::astar
31 {
32  Task::Task(const CSpaceBasePtr& cspace, const VectorXf& startCfg, const VectorXf& goalCfg, const std::string& taskName, float dcdStep,
33  float gridStepSize, Ice::Long maximalPlanningTimeInSeconds)
34  {
35  this->cspace = cspace->clone();
36  this->startCfg = startCfg;
37  this->goalCfg = goalCfg;
38  this->dcdStep = dcdStep;
39  this->gridStepSize = gridStepSize;
40  this->maximalPlanningTimeInSeconds = maximalPlanningTimeInSeconds;
41  this->taskName = taskName;
42 
43  if (startCfg.size() != goalCfg.size() || startCfg.size() != static_cast<std::size_t>(this->cspace->getDimensionality()))
44  {
45  throw std::invalid_argument {"start and goal have to be the size of the cspace's dimensionality"};
46  }
47 
48  if (gridStepSize < dcdStep)
49  {
50  throw std::invalid_argument {"the step size of the implicit grid has to be larger than the DCD step size"};
51  }
52  }
53 
54 
55  Path Task::getPath(const Ice::Current&) const
56  {
57  std::lock_guard<std::mutex> lock {mtx};
58  return solution;
59  }
60 
61  void Task::run(const RemoteObjectNodePrxList&, const Ice::Current&)
62  {
63  const std::size_t n = cspace->getDimensionality();
64  setTaskStatus(TaskStatus::ePlanning);
65 
66  //check trivial cases
67  cspace->initCollisionTest();
68 
69  const auto startIsCollisionFree = cspace->isCollisionFree(std::pair<const Ice::Float*, const Ice::Float*> {startCfg.data(), startCfg.data() + startCfg.size()});
70  const auto goalIscollisionFree = cspace->isCollisionFree(std::pair<const Ice::Float*, const Ice::Float*> { goalCfg.data(), goalCfg.data() + goalCfg.size()});
71  if (!startIsCollisionFree || !goalIscollisionFree)
72  {
73  setTaskStatus(TaskStatus::ePlanningFailed);
74  return;
75  }
76 
77  const auto distStartEnd = euclideanDistance(startCfg.begin(), startCfg.end(), goalCfg.begin());
78 
79  if (distStartEnd <= dcdStep)
80  {
81  setTaskStatus(TaskStatus::eDone);
82  solution.nodes.emplace_back(startCfg);
83  solution.nodes.emplace_back(goalCfg);
84  return;
85  }
86 
87  // //define grid
88  using GridPointType = std::vector<long>;
89 
90  //grid bounds (the 0 gridpoint is the start cfg)
91  LongRangeSeq gridBounds;
92  gridBounds.reserve(n);
93  const auto&& cspaceBounds = cspace->getDimensionsBounds();
95  cspaceBounds.begin(), cspaceBounds.end(), startCfg.begin(), std::back_inserter(gridBounds),
96  [this](const FloatRange & dimBounds, float start)
97  {
98 
99  const auto distLower = dimBounds.min - start;
100  const auto gridPointsLower = std::trunc(distLower / gridStepSize);
101 
102  const auto distHigher = dimBounds.max - start;
103  const auto gridPointsHigher = std::trunc(distHigher / gridStepSize);
104 
105  return LongRange {static_cast<Ice::Long>(gridPointsLower), static_cast<Ice::Long>(gridPointsHigher)};
106 
107  }
108  );
109 
110 
111  //calculates all neighbours in the grid (only along the cspace's coordinate axes)
112  auto getNeighbours = [gridBounds, n](const GridPointType & p)
113  {
114  std::vector<GridPointType> neighbours;
115  //allow diag neighbours
116  neighbours.push_back(p);
117  neighbours.reserve(std::pow(3, gridBounds.size()));
118  for (std::size_t i = 0; i < n; ++i)
119  {
120  const auto n = neighbours.size();
121  const auto generateLower = (gridBounds.at(i).min < p.at(i));
122  const auto generateHigher = (gridBounds.at(i).max > p.at(i));
123  neighbours.reserve(n * (1 + generateHigher + generateLower));//shold be already big enough
124  if (generateLower)
125  {
127  neighbours.begin(), neighbours.begin() + n, std::back_inserter(neighbours),
128  [i](typename decltype(neighbours)::value_type v)
129  {
130  --v.at(i);
131  return v;
132  }
133  );
134  }
135  if (generateHigher)
136  {
138  neighbours.begin(), neighbours.begin() + n, std::back_inserter(neighbours),
139  [i](typename decltype(neighbours)::value_type v)
140  {
141  ++v.at(i);
142  return v;
143  }
144  );
145  }
146  }
147  ARMARX_CHECK_EXPRESSION(p == neighbours.front());
148  return neighbours;
149  };
150 
151  //define grid conversion / distance meassure
152  auto toConfig = [this](const GridPointType & gridPoint)
153  {
154  VectorXf cfg;
155  cfg.reserve(gridPoint.size());
157  gridPoint.begin(), gridPoint.end(), startCfg.begin(), std::back_inserter(cfg),
158  [this](float g, float s)
159  {
160  return s + g * gridStepSize;
161  }
162  );
163  return cfg;
164  };
165 
166  auto distance = [](const GridPointType & a, const GridPointType & b)
167  {
168  return euclideanDistance(a.begin(), a.end(), b.begin());
169  };
170 
171  //project goal to grid
172  GridPointType goal;
173  goal.reserve(n);
174  for (std::size_t i = 0; i < n; ++i)
175  {
176  auto dist = goalCfg.at(i) - startCfg.at(i);
177  long gridPoint = std::round(dist / gridStepSize);
178  goal.emplace_back(std::clamp(gridPoint, gridBounds.at(i).min, gridBounds.at(i).max));
179  }
180  if (!isPathCollisionFree(goalCfg, toConfig(goal)))
181  {
182  setTaskStatus(TaskStatus::ePlanningFailed);
183  return;
184  }
185  setTaskStatus(TaskStatus::eDone);
186 
187  // //set up a*
188  struct NodeData
189  {
190  GridPointType node;
191  VectorXf cfg;
192  float fScore = 0; //cost to goal
193  float gScore = 0; //path cost
194  std::size_t predecessor = 0;
195  };
196  //holds all created nodes
197  std::deque<NodeData> nodes;
198 
199  std::set<std::size_t> openSet; //holds ids to nodes (these nodes are not processed yet)
200  //add start to the open set
201  nodes.emplace_back();
202  nodes.back().cfg = startCfg;
203  nodes.back().node.assign(n, 0);
204  openSet.emplace(0);
205 
206  std::set<std::size_t> closedSet; //holds ids to nodes (these nodes are processed)
207 
208  auto assemblePath = [&](std::size_t toId)//returns the found path to a given node
209  {
210  std::deque<VectorXf> reversedPath;
211  reversedPath.emplace_back(goalCfg);//maybe it was not on a grid point
212  while (true)
213  {
214  auto& currentNode = nodes.at(toId);
215  reversedPath.emplace_back(std::move(currentNode.cfg));
216  if (toId == currentNode.predecessor)
217  {
218  break;
219  }
220  toId = currentNode.predecessor;
221  }
222  VectorXfSeq path;
223  path.reserve(reversedPath.size());
224  std::move(reversedPath.rbegin(), reversedPath.rend(), std::back_inserter(path));
225  return path;
226  };
227 
228  auto expandNode = [&](std::size_t idToExpand)
229  {
230  const auto& toExpand = nodes.at(idToExpand);
231  const auto successors = getNeighbours(toExpand.node);//the first neighbour is the point itself
232  for (std::size_t sucIdx = 1; sucIdx < successors.size(); ++sucIdx)
233  {
234  auto& successor = successors.at(sucIdx);
235  //is the successor already in the open or closed set?
236  auto successorIt = std::find_if(nodes.begin(), nodes.end(), [&](const NodeData & elem)
237  {
238  return successor == elem.node;
239  });
240  auto successorId = std::distance(nodes.begin(), successorIt);
241 
242  if (closedSet.find(successorId) != closedSet.end())
243  {
244  continue; //already in closed set
245  }
246 
247  float tentativeGScore = toExpand.gScore + distance(successor, toExpand.node);
248 
249  bool successorIsInOpenSet = openSet.find(successorId) != openSet.end();
250  if (successorIsInOpenSet && tentativeGScore >= successorIt->gScore)
251  {
252  continue; // already a better path
253  }
254 
255  //if successorIsInOpenSet the cfg will be moved back (2 moves should be faster than alloc + calc)
256  auto successorCfg = successorIsInOpenSet ? std::move(successorIt->cfg) : toConfig(successor);
257  //check path for collision
258  if (!isPathCollisionFree(toExpand.cfg, successorCfg))
259  {
260  continue;
261  }
262 
263  const auto newFScore = tentativeGScore + distance(successor, goal);
264  if (successorIsInOpenSet)
265  {
266  //update
267  successorIt->cfg = std::move(successorCfg);
268  successorIt->fScore = newFScore;
269  successorIt->gScore = tentativeGScore;
270  successorIt->predecessor = idToExpand;
271  }
272  else
273  {
274  //create entry
275  openSet.emplace(nodes.size());
276  nodes.emplace_back();
277  nodes.back().node = std::move(successor);
278  nodes.back().cfg = std::move(successorCfg);
279  nodes.back().fScore = newFScore;
280  nodes.back().gScore = tentativeGScore;
281  nodes.back().predecessor = idToExpand;
282  }
283  }
284  };
285 
286  //wait for done or timeout
287  const auto endTime = std::chrono::system_clock::now() + std::chrono::seconds {maximalPlanningTimeInSeconds};
288 
289  while (true)
290  {
291  //check for exit condition
292  if (taskIsAborted)
293  {
294  setTaskStatus(TaskStatus::ePlanningAborted);
295  return;
296  }
297  if (endTime < std::chrono::system_clock::now())
298  {
299  setTaskStatus(TaskStatus::ePlanningFailed);
300  return;
301  }
302  if (openSet.empty())
303  {
304  //search done (but failed)!
305  setTaskStatus(TaskStatus::ePlanningFailed);
306  return;
307  }
308  auto currentIdIt = std::min_element(
309  openSet.begin(), openSet.end(),
310  [&](std::size_t osId1, std::size_t osId2)
311  {
312  return nodes.at(osId1).fScore < nodes.at(osId2).fScore;
313  }
314  );
315  auto currentId = *currentIdIt;
316  openSet.erase(currentIdIt);
317 
318  if (nodes.at(currentId).node == goal)
319  {
320  solution.nodes = assemblePath(currentId);
321  setTaskStatus(TaskStatus::eDone);
322  return;
323  }
324 
325  closedSet.emplace(currentId);
326  expandNode(currentId);
327  }
328  ARMARX_CHECK_EXPRESSION(!"unreachable");
329  }
330 
331 
332  bool Task::isPathCollisionFree(const VectorXf& from, const VectorXf& to)
333  {
334  return dcdIsPathCollisionFree(
335  from, to, dcdStep,
336  [this](const VectorXf & cfg)
337  {
338  return cspace->isCollisionFree({cfg.data(), cfg.data() + cfg.size()});
339  },
340  false
341  );
342  }
343 }
344 
armarx::astar::Task::run
void run(const RemoteObjectNodePrxList &, const Ice::Current &=Ice::emptyCurrent) override
Runs the task.
Definition: Task.cpp:61
armarx::astar::Task::solution
Path solution
Definition: Task.h:98
armarx::astar::Task::Task
Task()=default
Ctor used by object factories.
armarx::dcdIsPathCollisionFree
bool dcdIsPathCollisionFree(const ConfigType &from, const ConfigType &to, RealType dcdStepSize, CollisionChecker isCollisionFree, bool toIsCollisionFree=true, const std::optional< Distance > &distanceLambda=std::optional< Distance >(), const std::optional< Interpolate > &interpolationLambda=std::optional< Interpolate >(), std::vector< ConfigType > *resultPath=NULL)
Returns whether the line startCfg to to is collision free.
Definition: CollisionCheckUtil.h:115
clamp
double clamp(double x, double a, double b)
Definition: point.hpp:125
armarx::ctrlutil::a
double a(double t, double a0, double j)
Definition: CtrlUtil.h:45
armarx::VariantType::Long
const VariantTypeId Long
Definition: Variant.h:917
Task.h
armarx::astar::Task::getPath
Path getPath(const Ice::Current &=Ice::emptyCurrent) const override
Definition: Task.cpp:55
armarx::euclideanDistance
float euclideanDistance(IteratorType1 first1, IteratorType1 last1, IteratorType2 first2)
Returns the euclidean distance.
Definition: Metrics.h:94
armarx::ctrlutil::v
double v(double t, double v0, double a0, double j)
Definition: CtrlUtil.h:39
ARMARX_CHECK_EXPRESSION
#define ARMARX_CHECK_EXPRESSION(expression)
This macro evaluates the expression and if it turns out to be false it will throw an ExpressionExcept...
Definition: ExpressionException.h:73
armarx::transform
auto transform(const Container< InputT, Alloc > &in, OutputT(*func)(InputT const &)) -> Container< OutputT, typename std::allocator_traits< Alloc >::template rebind_alloc< OutputT > >
Convenience function (with less typing) to transform a container of type InputT into the same contain...
Definition: algorithm.h:315
armarx::MotionPlanningTask::setTaskStatus
bool setTaskStatus(TaskStatus::Status newTaskStatus, const Ice::Current &=Ice::emptyCurrent) override
Definition: MotionPlanningTask.cpp:30
armarx::armem::server::ltm::detail::mixin::Path
std::filesystem::path Path
Definition: DiskStorageMixin.h:17
distance
double distance(const Point &a, const Point &b)
Definition: point.hpp:88
armarx::astar
Definition: Task.cpp:30
armarx::ctrlutil::s
double s(double t, double s0, double v0, double a0, double j)
Definition: CtrlUtil.h:33
armarx::astar::Task::mtx
std::mutex mtx
Definition: Task.h:97