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 2016
21 * @copyright http://www.gnu.org/licenses/gpl.txt
22 * GNU General Public License
23 */
24
25#include "Task.h"
26
27#include <random>
28
29#include <VirtualRobot/RobotNodeSet.h>
30
32
34#include <MotionPlanning/CSpace/CSpaceSampled.h>
35
37{
38 Task::Task(MotionPlanningTaskBasePtr previousStep,
39 const std::string& taskName,
40 Ice::Long maxTimeForPostprocessingInSeconds,
41 Ice::Float dcdStep,
42 Ice::Long maxTries,
43 Ice::Float minShortcutImprovementRatio,
44 Ice::Float minPathImprovementRatio) :
45 MotionPlanningTaskBase(taskName),
46 PostprocessingMotionPlanningTaskBase(taskName, previousStep),
47 PostprocessingMotionPlanningTask(previousStep, taskName),
48 TaskBase(taskName,
49 previousStep,
50 dcdStep,
51 maxTries,
52 maxTimeForPostprocessingInSeconds,
53 minShortcutImprovementRatio,
54 minPathImprovementRatio)
55 {
56 if (!this->previousStep)
57 {
58 throw std::invalid_argument{"previousStep is NULL"};
59 }
60 if (this->dcdStep <= 0)
61 {
62 throw std::invalid_argument{"dcdStep <= 0"};
63 }
64 if (this->maxTimeForPostprocessingInSeconds <= 0)
65 {
66 throw std::invalid_argument{"maxTimeForPostprocessingInSeconds <= 0"};
67 }
68 if (this->minShortcutImprovementRatio >= 1)
69 {
70 throw std::invalid_argument{"minShortcutImprovementRatio >=1"};
71 }
72 if (this->minShortcutImprovementRatio < 0.01)
73 {
74 ARMARX_VERBOSE_S << "Changed minShortcutImprovementRatio from "
75 << this->minShortcutImprovementRatio << " to " << 0.01;
76 this->minShortcutImprovementRatio = 0.01;
77 }
78 if (this->minPathImprovementRatio >= 1)
79 {
80 throw std::invalid_argument{"minPathImprovementRatio >=1"};
81 }
82 if (this->minPathImprovementRatio < 0.001)
83 {
84 ARMARX_VERBOSE_S << "Changed minShortcutImprovementRatio from "
85 << this->minShortcutImprovementRatio << " to " << 0.001;
86 this->minPathImprovementRatio = 0.001;
87 }
88 }
89
90 void
91 Task::abortTask(const Ice::Current&)
92 {
93 if (!previousStep->finishedRunning())
94 {
95 previousStep->abortTask();
96 }
97 taskIsAborted = true;
98 }
99
100 void
101 Task::run(const RemoteObjectNodePrxList& nodes, const Ice::Current&)
102 {
103 auto previousStep = MotionPlanningTaskPtr::dynamicCast(this->previousStep);
104 ARMARX_CHECK_EXPRESSION(previousStep);
105 previousStep->addTaskStatusCallback(
106 [this](TaskStatus::Status s)
107 {
108 if (s != TaskStatus::eDone)
109 {
110 setTaskStatus(s);
111 }
112 });
113 auto endTime = std::chrono::high_resolution_clock::now() +
114 std::chrono::seconds{getMaximalPlanningTimeInSeconds()};
115 previousStep->run(nodes);
116 endTime = std::min(endTime,
117 std::chrono::high_resolution_clock::now() +
118 std::chrono::seconds{maxTimeForPostprocessingInSeconds});
119 {
120 std::lock_guard<std::mutex> lock{mtx};
121 paths.reserve(2);
122 auto p = previousStep->getPath();
123 paths.emplace_back(p.nodes, 0, "smooth_" + p.pathName);
124 paths.emplace_back(std::move(p.nodes), 0, std::move(p.pathName));
125 //the path length is wrong!
126 //we calculate it anyway
127 }
128
129 //setup the length vars.
130 std::vector<float> lengths;
131 auto updateLengths = [&, this]()
132 {
133 lengths.clear();
134 lengths.reserve(paths.front().nodes.size() + 1);
135 lengths.emplace_back(0);
136 for (std::size_t i = 0; i + 1 < paths.front().nodes.size(); ++i)
137 {
138 auto& from = paths.front().nodes.at(i);
139 auto& to = paths.front().nodes.at(i + 1);
140 lengths.emplace_back(lengths.back() +
141 euclideanDistance(from.begin(), from.end(), to.begin()));
142 }
143 };
144 updateLengths();
145
146 paths.at(0).cost = lengths.back();
147 paths.at(1).cost = lengths.back();
148 if (finishedRunning())
149 {
150 return;
151 }
152 ARMARX_CHECK_EXPRESSION(!paths.front().nodes.empty());
153 if (getTaskStatus() != TaskStatus::eRefining)
154 {
155 setTaskStatus(TaskStatus::eRefining);
156 }
157 if (paths.front().nodes.size() == 2)
158 {
159 //this was a trivial path
160 setTaskStatus(TaskStatus::eDone);
161 return;
162 }
163
164 getCSpace()->initCollisionTest();
165
166 std::mt19937 gen{std::random_device{}()};
167 Ice::Long iteration = 0;
168 for (; iteration < maxTries;)
169 //don't increment the iteration count here.
170 // we only want to count iterations that do collision checking.
171 {
172 ARMARX_CHECK_EXPRESSION(paths.front().nodes.size() == lengths.size());
173 //stop?
174 if (taskIsAborted)
175 {
176 setTaskStatus(TaskStatus::eRefinementAborted);
177 return;
178 }
179 if (endTime <= std::chrono::high_resolution_clock::now())
180 {
181 break;
182 }
183
184 auto segmentOffsets = calcOffsets(lengths.back(), gen);
185 float segmentLength = segmentOffsets.second - segmentOffsets.first;
186
187 VectorXf from(getCSpace()->getDimensionality());
188 VectorXf to(getCSpace()->getDimensionality());
189
190 //nodes with idx < are kept
191 std::size_t takeLastOPTE = 0;
192 //nodes with idx >= are kept
193 std::size_t takeAgainFirstIdx = 0;
194 // select from and to bounding the segment to replace
195 // calc the last node to keep before the segment starts and the first node to keep after tehe segment ends
196 {
197 std::size_t i = 0;
198
199 //search node i with: segmentPoints.first <= pathLenght(start,node_i)
200 for (; i < lengths.size() && segmentOffsets.first > lengths.at(i); ++i)
201 ;
202 takeLastOPTE = i;
203 if (takeLastOPTE + 1 == lengths.size())
204 {
205 //already at the end
206 continue;
207 }
208 ARMARX_CHECK_EXPRESSION(takeLastOPTE + 1 < lengths.size());
209 if (takeLastOPTE == 0)
210 {
211 ARMARX_CHECK_EXPRESSION(segmentOffsets.first == 0);
212 //start the segment to replace at the start
213 from = paths.front().nodes.at(0);
214 }
215 else
216 {
217 ARMARX_CHECK_EXPRESSION(takeLastOPTE);
218 const auto fromSubSegLen =
219 lengths.at(takeLastOPTE) - lengths.at(takeLastOPTE - 1);
220 const auto fromSubSegLenPart =
221 segmentOffsets.first - lengths.at(takeLastOPTE - 1);
222 if (fromSubSegLenPart < 1e-5)
223 {
224 //distance to prev node is low
225 //snap to node in path
226 from = paths.front().nodes.at(takeLastOPTE - 1);
227 --takeLastOPTE; //dont add the node a second time
228 }
229 else
230 {
231 const auto fromSubSegLenRatio = fromSubSegLenPart / fromSubSegLen;
232
233 ARMARX_CHECK_EXPRESSION(from.size() ==
234 paths.front().nodes.at(takeLastOPTE - 1).size());
235 ARMARX_CHECK_EXPRESSION(from.size() ==
236 paths.front().nodes.at(takeLastOPTE).size());
237
238 std::transform(paths.front().nodes.at(takeLastOPTE - 1).begin(),
239 paths.front().nodes.at(takeLastOPTE - 1).end(),
240 paths.front().nodes.at(takeLastOPTE).begin(),
241 from.begin(),
242 [fromSubSegLenRatio](float from, float to) {
243 return (1.f - fromSubSegLenRatio) * from +
244 fromSubSegLenRatio * to;
245 });
246 }
247 }
248
249 for (; i < lengths.size() && segmentOffsets.second > lengths.at(i); ++i)
250 ;
251 takeAgainFirstIdx = i;
252 ARMARX_CHECK_EXPRESSION(takeAgainFirstIdx);
253 if (takeAgainFirstIdx >= lengths.size())
254 {
255 //to the end
256 takeAgainFirstIdx = paths.front().nodes.size();
257 to = paths.front().nodes.back();
258 }
259 else
260 {
261 ARMARX_CHECK_EXPRESSION(takeAgainFirstIdx < lengths.size());
262 //to either some node in the middle
263 const auto toSubSegLen =
264 lengths.at(takeAgainFirstIdx) - lengths.at(takeAgainFirstIdx - 1);
265 const auto toSubSegLenPart =
266 segmentOffsets.second - lengths.at(takeAgainFirstIdx - 1);
267 if (lengths.at(takeAgainFirstIdx) - toSubSegLenPart < 1e-5)
268 {
269 //snap to node in path
270 to = paths.front().nodes.at(takeAgainFirstIdx);
271 ++takeAgainFirstIdx; //dont add the node a second time
272 }
273 else
274 {
275 const auto toSubSegLenRatio = toSubSegLenPart / toSubSegLen;
276 ARMARX_CHECK_EXPRESSION(takeAgainFirstIdx > 0);
278 to.size() == paths.front().nodes.at(takeAgainFirstIdx - 1).size());
279 ARMARX_CHECK_EXPRESSION(to.size() ==
280 paths.front().nodes.at(takeAgainFirstIdx).size());
281 std::transform(
282 paths.front().nodes.at(takeAgainFirstIdx - 1).begin(),
283 paths.front().nodes.at(takeAgainFirstIdx - 1).end(),
284 paths.front().nodes.at(takeAgainFirstIdx).begin(),
285 to.begin(),
286 [toSubSegLenRatio](float from, float to)
287 { return (1.f - toSubSegLenRatio) * from + toSubSegLenRatio * to; });
288 }
289 }
290 }
291 if (takeLastOPTE == takeAgainFirstIdx)
292 {
293 continue;
294 }
295 float segmentShortcutLength = euclideanDistance(from.begin(), from.end(), to.begin());
296
297 if (segmentShortcutLength > segmentLength * (1.f - minShortcutImprovementRatio))
298 {
299 // the possible improvement is not big enough
300 continue;
301 }
302 //we only count iterations with collision checks
303 ++iteration;
304 if (!isPathCollisionFree(from, to))
305 {
306 continue;
307 }
308
309 ARMARX_CHECK_EXPRESSION(takeLastOPTE < takeAgainFirstIdx);
310 VectorXfSeq newPostprocessedSolution;
311 newPostprocessedSolution.reserve(paths.front().nodes.size() +
312 1); //max one node longer (upper bound for new length
313 std::copy(paths.front().nodes.begin(),
314 paths.front().nodes.begin() + takeLastOPTE,
315 std::back_inserter(newPostprocessedSolution));
316 newPostprocessedSolution.emplace_back(std::move(from));
317 newPostprocessedSolution.emplace_back(std::move(to));
318 std::copy(paths.front().nodes.begin() + takeAgainFirstIdx,
319 paths.front().nodes.end(),
320 std::back_inserter(newPostprocessedSolution));
321
322 {
323 std::lock_guard<std::mutex> lock{mtx};
324 paths.front().nodes = std::move(newPostprocessedSolution);
325 }
326 //the path changed
327 updateLengths();
328 }
329 ARMARX_VERBOSE << "Smoothed with " << iteration << " tries";
330 setTaskStatus(TaskStatus::eDone);
331 }
332
333 Ice::Long
334 Task::getPathCount(const Ice::Current&) const
335 {
336 std::lock_guard<std::mutex> lock{mtx};
337 return paths.size();
338 }
339
340 PathWithCost
341 Task::getBestPath(const Ice::Current&) const
342 {
343 return getNthPathWithCost(0);
344 }
345
346 PathWithCost
347 Task::getNthPathWithCost(Ice::Long n, const Ice::Current&) const
348 {
349 std::lock_guard<std::mutex> lock{mtx};
350 return paths.at(n);
351 }
352
353 PathWithCostSeq
354 Task::getAllPathsWithCost(const Ice::Current&) const
355 {
356 std::lock_guard<std::mutex> lock{mtx};
357 return paths;
358 }
359
360 bool
361 Task::isPathCollisionFree(const VectorXf& from, const VectorXf& to)
362 {
363 // it is usually either a simox cspace or an adapter, if not use standard interpolation
364
365
366 SimoxCSpacePtr simoxcspace = SimoxCSpacePtr::dynamicCast(getCSpace());
367 if (!simoxcspace)
368 {
369 CSpaceAdaptorBasePtr simoxcspaceadapter =
370 CSpaceAdaptorBasePtr::dynamicCast(getCSpace());
371 if (simoxcspaceadapter)
372 {
373 simoxcspace = SimoxCSpacePtr::dynamicCast(simoxcspaceadapter->getOriginalCSpace());
374 }
375 }
376 if (simoxcspace)
377 {
378 // do robot specific interpolation
379 VirtualRobot::RobotNodeSetPtr rns = VirtualRobot::RobotNodeSet::createRobotNodeSet(
380 simoxcspace->getAgentSceneObj(), "tmp", simoxcspace->getAgentJointNames());
381 Saba::CSpaceSampled tmpCSpace(
382 simoxcspace->getAgentSceneObj(),
383 VirtualRobot::CDManagerPtr(new VirtualRobot::CDManager(simoxcspace->getCD())),
384 rns);
385 auto distance = [&, this](Eigen::VectorXf const& from, Eigen::VectorXf const& to)
386 { return tmpCSpace.calcDist(from, to); };
387
388 auto interpolation =
389 [&, this](Eigen::VectorXf const& from, Eigen::VectorXf const& to, float step)
390 { return tmpCSpace.interpolate(from, to, step); };
392 from,
393 to,
394 dcdStep,
395 [this](const VectorXf& cfg) {
396 return getCSpace()->isCollisionFree({cfg.data(), cfg.data() + cfg.size()});
397 },
398 false,
399 std::optional<decltype(distance)>(distance),
400 std::optional<decltype(interpolation)>(interpolation));
401 }
402 else
403 {
405 from,
406 to,
407 dcdStep,
408 [this](const VectorXf& cfg) {
409 return getCSpace()->isCollisionFree({cfg.data(), cfg.data() + cfg.size()});
410 },
411 false);
412 }
413 }
414
415 std::pair<float, float>
416 Task::calcOffsets(float length, std::mt19937& gen)
417 {
418 std::uniform_real_distribution<float> distA{
419 0, std::nextafter(length, std::numeric_limits<float>::max())};
420 const auto a = distA(gen);
421 const auto minDelta = minPathImprovementRatio * length;
422 std::uniform_real_distribution<float> distB{
423 0, std::nextafter(length - 2 * minDelta, std::numeric_limits<float>::max())};
424 const auto bSample = distB(gen);
425 const auto b = (bSample <= (a - minDelta)) ? bSample : bSample + 2 * minDelta;
426
427 return std::minmax(a, b);
428 }
429} // namespace armarx::rngshortcut
bool finishedRunning(const Ice::Current &=Ice::emptyCurrent) const override
Returns whether the task has finished planning.
bool setTaskStatus(TaskStatus::Status newTaskStatus, const Ice::Current &=Ice::emptyCurrent) override
TaskStatus::Status getTaskStatus(const Ice::Current &=Ice::emptyCurrent) const override
CSpaceBasePtr getCSpace(const Ice::Current &=Ice::emptyCurrent) const override
PostprocessingMotionPlanningTask(const MotionPlanningTaskBasePtr &previousStep, const std::string &taskName)
bool isPathCollisionFree(const VectorXf &from, const VectorXf &to)
Definition Task.cpp:361
PathWithCostSeq getAllPathsWithCost(const Ice::Current &=Ice::emptyCurrent) const override
Definition Task.cpp:354
Ice::Long getPathCount(const Ice::Current &=Ice::emptyCurrent) const override
Definition Task.cpp:334
Task()=default
Ctor used by object factories.
PathWithCostSeq paths
Definition Task.h:117
std::atomic_bool taskIsAborted
Definition Task.h:118
PathWithCost getNthPathWithCost(Ice::Long n, const Ice::Current &=Ice::emptyCurrent) const override
Definition Task.cpp:347
void abortTask(const Ice::Current &=Ice::emptyCurrent) override
Aborts planning.
Definition Task.cpp:91
Ice::Long getMaximalPlanningTimeInSeconds(const Ice::Current &=Ice::emptyCurrent) const override
Definition Task.h:94
PathWithCost getBestPath(const Ice::Current &=Ice::emptyCurrent) const override
Definition Task.cpp:341
void run(const RemoteObjectNodePrxList &nodes, const Ice::Current &=Ice::emptyCurrent) override
Runs the task.
Definition Task.cpp:101
std::pair< float, float > calcOffsets(float length, std::mt19937 &gen)
Definition Task.cpp:416
#define ARMARX_CHECK_EXPRESSION(expression)
This macro evaluates the expression and if it turns out to be false it will throw an ExpressionExcept...
#define ARMARX_VERBOSE_S
Definition Logging.h:207
#define ARMARX_VERBOSE
The logging level for verbose information.
Definition Logging.h:187
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
IceInternal::Handle< SimoxCSpace > SimoxCSpacePtr
An ice handle for a SimoxCSpace.
Definition SimoxCSpace.h:56
double distance(const Point &a, const Point &b)
Definition point.hpp:95