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
27#include <algorithm>
28
30
32{
33 Task::Task(const CSpaceBasePtr& cspace,
34 const VectorXf& startCfg,
35 const VectorXf& goalCfg,
36 const std::string& taskName,
37 float dcdStep,
38 float gridStepSize,
39 Ice::Long maximalPlanningTimeInSeconds)
40 {
41 this->cspace = cspace->clone();
42 this->startCfg = startCfg;
43 this->goalCfg = goalCfg;
44 this->dcdStep = dcdStep;
45 this->gridStepSize = gridStepSize;
46 this->maximalPlanningTimeInSeconds = maximalPlanningTimeInSeconds;
47 this->taskName = taskName;
48
49 if (startCfg.size() != goalCfg.size() ||
50 startCfg.size() != static_cast<std::size_t>(this->cspace->getDimensionality()))
51 {
52 throw std::invalid_argument{
53 "start and goal have to be the size of the cspace's dimensionality"};
54 }
55
56 if (gridStepSize < dcdStep)
57 {
58 throw std::invalid_argument{
59 "the step size of the implicit grid has to be larger than the DCD step size"};
60 }
61 }
62
63 Path
64 Task::getPath(const Ice::Current&) const
65 {
66 std::lock_guard<std::mutex> lock{mtx};
67 return solution;
68 }
69
70 void
71 Task::run(const RemoteObjectNodePrxList&, const Ice::Current&)
72 {
73 const std::size_t n = cspace->getDimensionality();
74 setTaskStatus(TaskStatus::ePlanning);
75
76 //check trivial cases
77 cspace->initCollisionTest();
78
79 const auto startIsCollisionFree =
80 cspace->isCollisionFree(std::pair<const Ice::Float*, const Ice::Float*>{
81 startCfg.data(), startCfg.data() + startCfg.size()});
82 const auto goalIscollisionFree =
83 cspace->isCollisionFree(std::pair<const Ice::Float*, const Ice::Float*>{
84 goalCfg.data(), goalCfg.data() + goalCfg.size()});
85 if (!startIsCollisionFree || !goalIscollisionFree)
86 {
87 setTaskStatus(TaskStatus::ePlanningFailed);
88 return;
89 }
90
91 const auto distStartEnd =
92 euclideanDistance(startCfg.begin(), startCfg.end(), goalCfg.begin());
93
94 if (distStartEnd <= dcdStep)
95 {
96 setTaskStatus(TaskStatus::eDone);
97 solution.nodes.emplace_back(startCfg);
98 solution.nodes.emplace_back(goalCfg);
99 return;
100 }
101
102 // //define grid
103 using GridPointType = std::vector<long>;
104
105 //grid bounds (the 0 gridpoint is the start cfg)
106 LongRangeSeq gridBounds;
107 gridBounds.reserve(n);
108 const auto&& cspaceBounds = cspace->getDimensionsBounds();
109 std::transform(cspaceBounds.begin(),
110 cspaceBounds.end(),
111 startCfg.begin(),
112 std::back_inserter(gridBounds),
113 [this](const FloatRange& dimBounds, float start)
114 {
115 const auto distLower = dimBounds.min - start;
116 const auto gridPointsLower = std::trunc(distLower / gridStepSize);
117
118 const auto distHigher = dimBounds.max - start;
119 const auto gridPointsHigher = std::trunc(distHigher / gridStepSize);
120
121 return LongRange{static_cast<Ice::Long>(gridPointsLower),
122 static_cast<Ice::Long>(gridPointsHigher)};
123 });
124
125
126 //calculates all neighbours in the grid (only along the cspace's coordinate axes)
127 auto getNeighbours = [gridBounds, n](const GridPointType& p)
128 {
129 std::vector<GridPointType> neighbours;
130 //allow diag neighbours
131 neighbours.push_back(p);
132 neighbours.reserve(std::pow(3, gridBounds.size()));
133 for (std::size_t i = 0; i < n; ++i)
134 {
135 const auto n = neighbours.size();
136 const auto generateLower = (gridBounds.at(i).min < p.at(i));
137 const auto generateHigher = (gridBounds.at(i).max > p.at(i));
138 neighbours.reserve(
139 n * (1 + generateHigher + generateLower)); //shold be already big enough
140 if (generateLower)
141 {
142 std::transform(neighbours.begin(),
143 neighbours.begin() + n,
144 std::back_inserter(neighbours),
145 [i](typename decltype(neighbours)::value_type v)
146 {
147 --v.at(i);
148 return v;
149 });
150 }
151 if (generateHigher)
152 {
153 std::transform(neighbours.begin(),
154 neighbours.begin() + n,
155 std::back_inserter(neighbours),
156 [i](typename decltype(neighbours)::value_type v)
157 {
158 ++v.at(i);
159 return v;
160 });
161 }
162 }
163 ARMARX_CHECK_EXPRESSION(p == neighbours.front());
164 return neighbours;
165 };
166
167 //define grid conversion / distance meassure
168 auto toConfig = [this](const GridPointType& gridPoint)
169 {
170 VectorXf cfg;
171 cfg.reserve(gridPoint.size());
172 std::transform(gridPoint.begin(),
173 gridPoint.end(),
174 startCfg.begin(),
175 std::back_inserter(cfg),
176 [this](float g, float s) { return s + g * gridStepSize; });
177 return cfg;
178 };
179
180 auto distance = [](const GridPointType& a, const GridPointType& b)
181 { return euclideanDistance(a.begin(), a.end(), b.begin()); };
182
183 //project goal to grid
184 GridPointType goal;
185 goal.reserve(n);
186 for (std::size_t i = 0; i < n; ++i)
187 {
188 auto dist = goalCfg.at(i) - startCfg.at(i);
189 long gridPoint = std::round(dist / gridStepSize);
190 goal.emplace_back(std::clamp(gridPoint, gridBounds.at(i).min, gridBounds.at(i).max));
191 }
192 if (!isPathCollisionFree(goalCfg, toConfig(goal)))
193 {
194 setTaskStatus(TaskStatus::ePlanningFailed);
195 return;
196 }
197 setTaskStatus(TaskStatus::eDone);
198
199 // //set up a*
200 struct NodeData
201 {
202 GridPointType node;
203 VectorXf cfg;
204 float fScore = 0; //cost to goal
205 float gScore = 0; //path cost
206 std::size_t predecessor = 0;
207 };
208
209 //holds all created nodes
210 std::deque<NodeData> nodes;
211
212 std::set<std::size_t> openSet; //holds ids to nodes (these nodes are not processed yet)
213 //add start to the open set
214 nodes.emplace_back();
215 nodes.back().cfg = startCfg;
216 nodes.back().node.assign(n, 0);
217 openSet.emplace(0);
218
219 std::set<std::size_t> closedSet; //holds ids to nodes (these nodes are processed)
220
221 auto assemblePath = [&](std::size_t toId) //returns the found path to a given node
222 {
223 std::deque<VectorXf> reversedPath;
224 reversedPath.emplace_back(goalCfg); //maybe it was not on a grid point
225 while (true)
226 {
227 auto& currentNode = nodes.at(toId);
228 reversedPath.emplace_back(std::move(currentNode.cfg));
229 if (toId == currentNode.predecessor)
230 {
231 break;
232 }
233 toId = currentNode.predecessor;
234 }
235 VectorXfSeq path;
236 path.reserve(reversedPath.size());
237 std::move(reversedPath.rbegin(), reversedPath.rend(), std::back_inserter(path));
238 return path;
239 };
240
241 auto expandNode = [&](std::size_t idToExpand)
242 {
243 const auto& toExpand = nodes.at(idToExpand);
244 const auto successors =
245 getNeighbours(toExpand.node); //the first neighbour is the point itself
246 for (std::size_t sucIdx = 1; sucIdx < successors.size(); ++sucIdx)
247 {
248 auto& successor = successors.at(sucIdx);
249 //is the successor already in the open or closed set?
250 auto successorIt =
251 std::find_if(nodes.begin(),
252 nodes.end(),
253 [&](const NodeData& elem) { return successor == elem.node; });
254 auto successorId = std::distance(nodes.begin(), successorIt);
255
256 if (closedSet.find(successorId) != closedSet.end())
257 {
258 continue; //already in closed set
259 }
260
261 float tentativeGScore = toExpand.gScore + distance(successor, toExpand.node);
262
263 bool successorIsInOpenSet = openSet.find(successorId) != openSet.end();
264 if (successorIsInOpenSet && tentativeGScore >= successorIt->gScore)
265 {
266 continue; // already a better path
267 }
268
269 //if successorIsInOpenSet the cfg will be moved back (2 moves should be faster than alloc + calc)
270 auto successorCfg =
271 successorIsInOpenSet ? std::move(successorIt->cfg) : toConfig(successor);
272 //check path for collision
273 if (!isPathCollisionFree(toExpand.cfg, successorCfg))
274 {
275 continue;
276 }
277
278 const auto newFScore = tentativeGScore + distance(successor, goal);
279 if (successorIsInOpenSet)
280 {
281 //update
282 successorIt->cfg = std::move(successorCfg);
283 successorIt->fScore = newFScore;
284 successorIt->gScore = tentativeGScore;
285 successorIt->predecessor = idToExpand;
286 }
287 else
288 {
289 //create entry
290 openSet.emplace(nodes.size());
291 nodes.emplace_back();
292 nodes.back().node = std::move(successor);
293 nodes.back().cfg = std::move(successorCfg);
294 nodes.back().fScore = newFScore;
295 nodes.back().gScore = tentativeGScore;
296 nodes.back().predecessor = idToExpand;
297 }
298 }
299 };
300
301 //wait for done or timeout
302 const auto endTime =
303 std::chrono::system_clock::now() + std::chrono::seconds{maximalPlanningTimeInSeconds};
304
305 while (true)
306 {
307 //check for exit condition
308 if (taskIsAborted)
309 {
310 setTaskStatus(TaskStatus::ePlanningAborted);
311 return;
312 }
313 if (endTime < std::chrono::system_clock::now())
314 {
315 setTaskStatus(TaskStatus::ePlanningFailed);
316 return;
317 }
318 if (openSet.empty())
319 {
320 //search done (but failed)!
321 setTaskStatus(TaskStatus::ePlanningFailed);
322 return;
323 }
324 auto currentIdIt =
325 std::min_element(openSet.begin(),
326 openSet.end(),
327 [&](std::size_t osId1, std::size_t osId2)
328 { return nodes.at(osId1).fScore < nodes.at(osId2).fScore; });
329 auto currentId = *currentIdIt;
330 openSet.erase(currentIdIt);
331
332 if (nodes.at(currentId).node == goal)
333 {
334 solution.nodes = assemblePath(currentId);
335 setTaskStatus(TaskStatus::eDone);
336 return;
337 }
338
339 closedSet.emplace(currentId);
340 expandNode(currentId);
341 }
342 ARMARX_CHECK_EXPRESSION(!"unreachable");
343 }
344
345 bool
346 Task::isPathCollisionFree(const VectorXf& from, const VectorXf& to)
347 {
349 from,
350 to,
351 dcdStep,
352 [this](const VectorXf& cfg) {
353 return cspace->isCollisionFree({cfg.data(), cfg.data() + cfg.size()});
354 },
355 false);
356 }
357} // namespace armarx::astar
bool setTaskStatus(TaskStatus::Status newTaskStatus, const Ice::Current &=Ice::emptyCurrent) override
bool isPathCollisionFree(const VectorXf &from, const VectorXf &to)
Definition Task.cpp:346
Task()=default
Ctor used by object factories.
void run(const RemoteObjectNodePrxList &, const Ice::Current &=Ice::emptyCurrent) override
Runs the task.
Definition Task.cpp:71
Path getPath(const Ice::Current &=Ice::emptyCurrent) const override
Definition Task.cpp:64
std::mutex mtx
Definition Task.h:96
#define ARMARX_CHECK_EXPRESSION(expression)
This macro evaluates the expression and if it turns out to be false it will throw an ExpressionExcept...
double a(double t, double a0, double j)
Definition CtrlUtil.h:45
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.
float euclideanDistance(IteratorType1 first1, IteratorType1 last1, IteratorType2 first2)
Returns the euclidean distance.
Definition Metrics.h:104
std::vector< T > max(const std::vector< T > &v1, const std::vector< T > &v2)
std::vector< T > min(const std::vector< T > &v1, const std::vector< T > &v2)
constexpr auto n() noexcept
double distance(const Point &a, const Point &b)
Definition point.hpp:95