ShortestPathFasterAlgorithm.cpp
Go to the documentation of this file.
1 /**
2  * This file is part of ArmarX.
3  *
4  * ArmarX is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License version 2 as
6  * published by the Free Software Foundation.
7  *
8  * ArmarX is distributed in the hope that it will be useful, but
9  * WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11  * GNU General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License
14  * along with this program. If not, see <http://www.gnu.org/licenses/>.
15  *
16  * @author Fabian Reister ( fabian dot reister at kit dot edu )
17  * @date 2022
18  * @copyright http://www.gnu.org/licenses/gpl-2.0.txt
19  * GNU General Public License
20  */
21 
23 
24 #include <algorithm>
25 #include <array>
26 #include <cmath>
27 #include <cstddef>
28 #include <cstdint>
29 #include <string>
30 #include <vector>
31 
32 #include <Eigen/Core>
33 
37 
39 
40 #include <range/v3/algorithm/reverse.hpp>
41 
43 {
44 
45  namespace
46  {
47  constexpr std::size_t
48  ravel(std::size_t row, std::size_t col, std::size_t num_cols)
49  {
50  return (row * num_cols) + col;
51  }
52 
53  Eigen::Vector2i
54  unravel(const std::size_t pos, const std::size_t numCols)
55  {
57  ARMARX_CHECK_GREATER(numCols, 0);
58 
59  Eigen::Vector2i p;
60  p.x() = static_cast<int>(pos) / numCols;
61  p.y() = static_cast<int>(pos) % numCols;
62 
63  return p;
64  }
65 
66  } // namespace
67 
69  const Parameters& params) :
70  costmap(grid), params(params)
71  {
72  ARMARX_VERBOSE << "Grid with size (" << grid.getGrid().rows() << ", "
73  << grid.getGrid().cols() << ").";
74  }
75 
77  ShortestPathFasterAlgorithm::plan(const Eigen::Vector2f& start,
78  const Eigen::Vector2f& goal,
79  const bool checkStartForCollision) const
80  {
81  // ARMARX_CHECK(not costmap.isInCollision(start)) << "Start is in collision";
82  // ARMARX_CHECK(not costmap.isInCollision(goal)) << "Goal is in collision";
83 
84  const Costmap::Vertex startVertex = costmap.toVertex(start);
85  const auto goalVertex = Eigen::Vector2i{costmap.toVertex(goal).index};
86 
87  ARMARX_VERBOSE << "Planning from " << startVertex.index << " to " << goalVertex;
88 
89  if ((startVertex.index.array() == goalVertex.array()).all())
90  {
91  ARMARX_INFO << "Already at goal.";
92  return {.path = {}, .success = true};
93  }
94 
95  auto costmapWithValidStart = costmap.getGrid();
96  if (costmap.isInCollision(start))
97  {
98  costmapWithValidStart(startVertex.index.x(), startVertex.index.y()) = 0.1;
99  }
100 
101  const Result result =
102  spfa(costmapWithValidStart, Eigen::Vector2i{startVertex.index}, checkStartForCollision);
103 
104  const auto isStart = [&startVertex](const Eigen::Vector2i& v) -> bool
105  { return (v.array() == startVertex.index.array()).all(); };
106 
107  const auto isInvalid = [](const Eigen::Vector2i& v) -> bool
108  { return (v.x() == -1) and (v.y() == -1); };
109 
110  // traverse path from goal to start
111  std::vector<Eigen::Vector2f> path;
112  path.push_back(goal);
113 
114  const Eigen::Vector2i* pt = &goalVertex;
115 
116  ARMARX_VERBOSE << "Creating shortest path from grid";
117  while (true)
118  {
119  if (isInvalid(*pt))
120  {
121  ARMARX_WARNING << "No path found from (" << start << ") to (" << goal << ").";
122  return {};
123  }
124 
125  const Eigen::Vector2i& parent = result.parents.at(pt->x()).at(pt->y());
126 
127  ARMARX_DEBUG << VAROUT(parent);
128 
129  if (isStart(parent))
130  {
131  break;
132  }
133 
134  path.push_back(costmap.toPositionGlobal(parent.array()));
135 
136  pt = &parent;
137  }
138 
139  path.push_back(start);
140  ARMARX_VERBOSE << "Path found with " << path.size() << " nodes.";
141 
142  // reverse the path such that: start -> ... -> goal
143  ranges::reverse(path);
144 
145  return {.path = path, .success = true};
146  }
147 
149  ShortestPathFasterAlgorithm::spfa(const Eigen::Vector2f& start,
150  const bool checkStartForCollision) const
151  {
152  return spfa(costmap.getGrid(), costmap.toVertex(start).index, checkStartForCollision);
153  }
154 
156  ShortestPathFasterAlgorithm::spfa(const Eigen::MatrixXf& inputMap,
157  const Eigen::Vector2i& source,
158  const bool checkStartForCollision) const
159  {
160  if (checkStartForCollision)
161  {
162  ARMARX_CHECK_GREATER(inputMap(source.x(), source.y()), 0.F)
163  << "Start must not be in collision";
164  }
165 
166  constexpr float eps = 1e-6;
167  constexpr std::size_t numDirs = 8;
168 
169  const std::array<std::array<std::int64_t, 2>, numDirs> dirs{
170  std::array<std::int64_t, 2>{-1, -1},
171  std::array<std::int64_t, 2>{-1, 0},
172  std::array<std::int64_t, 2>{-1, 1},
173  std::array<std::int64_t, 2>{0, 1},
174  std::array<std::int64_t, 2>{1, 1},
175  std::array<std::int64_t, 2>{1, 0},
176  std::array<std::int64_t, 2>{1, -1},
177  std::array<std::int64_t, 2>{0, -1}};
178 
179  const std::array<float, numDirs> dirLengths{
180  std::sqrt(2.0f), 1, std::sqrt(2.0f), 1, std::sqrt(2.0f), 1, std::sqrt(2.0f), 1};
181 
182  // Process input map
183  // py::buffer_info map_buf = input_map.request();
184  const std::size_t numRows = inputMap.rows();
185  const std::size_t numCols = inputMap.cols();
186 
187  // Get source coordinates
188  const int source_i = source.x();
189  const int source_j = source.y();
190 
191  const std::size_t maxNumVerts = numRows * numCols;
192  constexpr std::size_t maxEdgesPerVert = numDirs;
193  // const float inf = 2 * maxNumVerts;
194  const float inf = 2 * maxNumVerts; // FIXME numeric_limits<float>::max();
195  const std::size_t queueSize = numDirs * numRows * numCols;
196 
197  // Initialize arrays
198  std::vector<std::size_t> edges(maxNumVerts * maxEdgesPerVert);
199  std::vector<std::size_t> edge_counts(maxNumVerts);
200  std::vector<std::size_t> queue(queueSize);
201  std::vector<bool> in_queue(maxNumVerts);
202  std::vector<float> weights(maxNumVerts * maxEdgesPerVert);
203  std::vector<float> dists(maxNumVerts, inf); // initialize with inf
204 
205  const auto& verify = [](std::int64_t index, std::size_t maxIndex, const std::string& str)
206  {
207  if (index >= static_cast<std::int64_t>(maxIndex) || index < 0)
208  {
209  ARMARX_IMPORTANT << "invalid index " << index << " max allowed is " << maxIndex
210  << "; " << str;
211  }
212  };
213 
214 
215  // Build graph
216  ARMARX_VERBOSE << "Build graph";
217  for (std::size_t row = 0; row < numRows; ++row)
218  {
219  for (std::size_t col = 0; col < numCols; ++col)
220  {
221  const std::size_t v = ravel(row, col, numCols);
222  if (inputMap(static_cast<int>(row), static_cast<int>(col)) <= 0.F) // collision
223  {
224  continue;
225  }
226 
227  for (std::size_t k = 0; k < numDirs; ++k)
228  {
229  const std::int64_t ip = static_cast<std::int64_t>(row) +
230  dirs[k][0]; // could eventually become negative
231  const std::int64_t jp = static_cast<std::int64_t>(col) + dirs[k][1];
232 
233  if (ip < 0 || jp < 0 || ip >= static_cast<std::int64_t>(numRows) ||
234  jp >= static_cast<std::int64_t>(numCols))
235  {
236  continue;
237  }
238 
239  const std::size_t vp = ravel(ip, jp, numCols);
240  if (inputMap(ip, jp) <= 0.F) // collision
241  {
242  continue;
243  }
244 
245  const float clippedObstacleDistance =
246  std::min(inputMap(ip, jp), params.obstacleMaxDistance);
247 
248  const float travelCost = dirLengths[k];
249 
250  const float targetDistanceCost =
251  params.obstacleDistanceWeight *
252  std::pow(1.F - clippedObstacleDistance / params.obstacleMaxDistance,
253  params.obstacleCostExponent);
254 
255  const float edgeCost = params.obstacleDistanceCosts
256  ? travelCost * (1 + targetDistanceCost)
257  : travelCost;
258 
259  const std::size_t e = ravel(v, edge_counts.at(v), maxEdgesPerVert);
260  edges.at(e) = vp;
261  verify(e, maxNumVerts * maxEdgesPerVert, "edges");
262  weights.at(e) = edgeCost;
263  verify(e, maxNumVerts * maxEdgesPerVert, "weights");
264  edge_counts[v]++;
265  verify(v, maxNumVerts, "edges_counts");
266  }
267  }
268  }
269 
270 
271  // SPFA
272  ARMARX_DEBUG << "SPFA";
273  std::size_t head = 0;
274  std::size_t tail = 0;
275 
276  const std::size_t s = ravel(source_i, source_j, numCols);
277  dists[s] = 0;
278  verify(s, maxNumVerts, "dists");
279  queue[++tail] = s;
280  verify(tail, queueSize, "queue");
281  in_queue[s] = true;
282  verify(s, maxNumVerts, "in_queue");
283 
284  std::vector<std::int64_t> parents(maxNumVerts, -1);
285  while (head < tail)
286  {
287  const std::size_t u = queue[++head];
288  in_queue[u] = false;
289  verify(u, maxNumVerts, "in_queue");
290  for (std::size_t j = 0; j < edge_counts[u]; ++j)
291  {
292  const std::size_t e = ravel(u, j, maxEdgesPerVert);
293  const std::size_t v = edges[e];
294  const float newDist = dists[u] + weights[e];
295  if (newDist < dists[v])
296  {
297  parents[v] = u;
298  verify(v, maxNumVerts, "parents");
299  dists[v] = newDist;
300  verify(v, maxNumVerts, "dists");
301  if (not in_queue[v])
302  {
303  ARMARX_CHECK_LESS(tail, queueSize);
304  queue[++tail] = v;
305  verify(tail, queueSize, "queue!");
306  in_queue[v] = true;
307  verify(v, maxNumVerts, "in_queue");
308 
309  if (dists[queue[tail]] < dists[queue[head + 1]])
310  {
311  std::swap(queue[tail], queue[head + 1]);
312  }
313  }
314  }
315  }
316  }
317 
318  // Copy output into numpy array
319  ARMARX_DEBUG << "Copy to output variables";
320 
321  Eigen::MatrixXf output_dists(numRows, numCols);
322  Result::Mask reachable(numRows, numCols);
323  reachable.setOnes();
324 
325  std::vector<std::vector<Eigen::Vector2i>> output_parents(
326  numRows, std::vector<Eigen::Vector2i>(numCols, Eigen::Vector2i{-1, -1}));
327 
328  std::size_t invalids = 0;
329 
330  for (std::size_t row = 0; row < numRows; ++row)
331  {
332  for (std::size_t col = 0; col < numCols; ++col)
333  {
334  const std::size_t u = ravel(row, col, numCols);
335  output_dists(row, col) = (dists[u] < inf - eps) * dists[u];
336 
337  if (parents[u] == -1) // no parent
338  {
339  invalids++;
340  reachable(row, col) = false;
341  continue;
342  }
343 
344  output_parents.at(row).at(col) = unravel(parents[u], numCols);
345  }
346  }
347 
348  // "fix": the initial position does not have any parents. But it's still reachable ...
349  reachable(source.x(), source.y()) = true;
350 
351  ARMARX_VERBOSE << "Fraction of invalid cells: (" << invalids << "/" << parents.size()
352  << ")";
353 
354  ARMARX_DEBUG << "Done.";
355 
356  // The distances are in a unit-like system, so the distance between two neighbor cells is 1
357  // We need to convert it back to a metric system (mutiplying it by the cell size) to be independent of the cell size.
358  return Result{.distances = costmap.params().cellSize * output_dists,
359  .parents = output_parents,
360  .reachable = reachable};
361  }
362 
363 
364 } // namespace armarx::navigation::algorithms::spfa
armarx::navigation::algorithms::spfa::ShortestPathFasterAlgorithm::Parameters
Definition: ShortestPathFasterAlgorithm.h:36
ARMARX_VERBOSE
#define ARMARX_VERBOSE
Definition: Logging.h:187
str
std::string str(const T &t)
Definition: UserAssistedSegmenterGuiWidgetController.cpp:43
ARMARX_IMPORTANT
#define ARMARX_IMPORTANT
Definition: Logging.h:190
index
uint8_t index
Definition: EtherCATFrame.h:59
armarx::navigation::algorithms::spfa::ShortestPathFasterAlgorithm::spfa
Result spfa(const Eigen::Vector2f &start, bool checkStartForCollision=true) const
Definition: ShortestPathFasterAlgorithm.cpp:149
ARMARX_CHECK_GREATER
#define ARMARX_CHECK_GREATER(lhs, rhs)
This macro evaluates whether lhs is greater (>) than rhs and if it turns out to be false it will thro...
Definition: ExpressionException.h:116
armarx::navigation::algorithms::spfa::ShortestPathFasterAlgorithm::Parameters::obstacleDistanceWeight
float obstacleDistanceWeight
Definition: ShortestPathFasterAlgorithm.h:46
armarx::navigation::algorithms::Costmap::Vertex::index
Index index
Definition: Costmap.h:69
armarx::navigation::algorithms::spfa::ShortestPathFasterAlgorithm::PlanningResult
Definition: ShortestPathFasterAlgorithm.h:62
ARMARX_CHECK_LESS
#define ARMARX_CHECK_LESS(lhs, rhs)
This macro evaluates whether lhs is less (<) than rhs and if it turns out to be false it will throw a...
Definition: ExpressionException.h:102
armarx::navigation::algorithms::Costmap::Vertex
Definition: Costmap.h:67
armarx::navigation::algorithms::spfa::ShortestPathFasterAlgorithm::Result::parents
std::vector< std::vector< Eigen::Vector2i > > parents
Definition: ShortestPathFasterAlgorithm.h:54
armarx::navigation::algorithms::spfa::ShortestPathFasterAlgorithm::Parameters::obstacleMaxDistance
float obstacleMaxDistance
For ARMAR-7 the following was tested:
Definition: ShortestPathFasterAlgorithm.h:45
armarx::navigation::algorithms::Costmap::toPositionGlobal
Position toPositionGlobal(const Index &index) const
Definition: Costmap.cpp:69
StringHelpers.h
armarx::navigation::algorithms::spfa::ShortestPathFasterAlgorithm::Result
Definition: ShortestPathFasterAlgorithm.h:51
ShortestPathFasterAlgorithm.h
Costmap.h
armarx::reverse
T reverse(const T &o)
Definition: container.h:33
armarx::armem::client::util::swap
void swap(SubscriptionHandle &first, SubscriptionHandle &second)
Definition: SubscriptionHandle.cpp:66
armarx::armem::client::query_fns::all
auto all()
Definition: query_fns.h:9
armarx::navigation::algorithms::spfa::ShortestPathFasterAlgorithm::plan
PlanningResult plan(const Eigen::Vector2f &start, const Eigen::Vector2f &goal, bool checkStartForCollision=true) const
Definition: ShortestPathFasterAlgorithm.cpp:77
armarx::navigation::algorithms::spfa::ShortestPathFasterAlgorithm::Parameters::obstacleCostExponent
float obstacleCostExponent
Definition: ShortestPathFasterAlgorithm.h:42
armarx::navigation::algorithms::Costmap::params
const Parameters & params() const noexcept
Definition: Costmap.cpp:237
ARMARX_DEBUG
#define ARMARX_DEBUG
Definition: Logging.h:184
armarx::navigation::algorithms::spfa::ShortestPathFasterAlgorithm::Parameters::obstacleDistanceCosts
bool obstacleDistanceCosts
Definition: ShortestPathFasterAlgorithm.h:38
armarx::navigation::algorithms::Costmap::toVertex
Vertex toVertex(const Position &globalPosition) const
Definition: Costmap.cpp:103
armarx::navigation::algorithms::Costmap::getGrid
const Grid & getGrid() const
Definition: Costmap.cpp:243
armarx::navigation::algorithms::spfa::ShortestPathFasterAlgorithm::ShortestPathFasterAlgorithm
ShortestPathFasterAlgorithm(const Costmap &costmap, const Parameters &params)
Definition: ShortestPathFasterAlgorithm.cpp:68
boost::source
Vertex source(const detail::edge_base< Directed, Vertex > &e, const PCG &)
Definition: point_cloud_graph.h:661
ARMARX_CHECK_GREATER_EQUAL
#define ARMARX_CHECK_GREATER_EQUAL(lhs, rhs)
This macro evaluates whether lhs is greater or equal (>=) rhs and if it turns out to be false it will...
Definition: ExpressionException.h:123
ExpressionException.h
GfxTL::sqrt
VectorXD< D, T > sqrt(const VectorXD< D, T > &a)
Definition: VectorXD.h:704
armarx::ctrlutil::v
double v(double t, double v0, double a0, double j)
Definition: CtrlUtil.h:39
ARMARX_INFO
#define ARMARX_INFO
Definition: Logging.h:181
VAROUT
#define VAROUT(x)
Definition: StringHelpers.h:198
armarx::navigation::algorithms::spfa::ShortestPathFasterAlgorithm::Result::distances
Eigen::MatrixXf distances
Definition: ShortestPathFasterAlgorithm.h:53
F
Definition: ExportDialogControllerTest.cpp:18
armarx::navigation::algorithms::spfa
This file is part of ArmarX.
Definition: ShortestPathFasterAlgorithm.cpp:42
Eigen::Matrix< bool, Eigen::Dynamic, Eigen::Dynamic >
Logging.h
min
T min(T t1, T t2)
Definition: gdiam.h:44
ARMARX_WARNING
#define ARMARX_WARNING
Definition: Logging.h:193
armarx::ctrlutil::s
double s(double t, double s0, double v0, double a0, double j)
Definition: CtrlUtil.h:33
armarx::navigation::algorithms::Costmap::isInCollision
bool isInCollision(const Position &p) const
Definition: Costmap.cpp:249
armarx::navigation::algorithms::Costmap
Definition: Costmap.h:16