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 <numeric>
26 
27 #include <Eigen/Core>
28 
31 
33 
34 #include "range/v3/algorithm/reverse.hpp"
35 
37 {
38 
39  inline int
40  ravel(int row, int col, int num_cols)
41  {
42  return row * num_cols + col;
43  }
44 
45  Eigen::Vector2i
46  unravel(int pos, int num_cols)
47  {
49  ARMARX_CHECK_GREATER(num_cols, 0);
50 
51  Eigen::Vector2i p;
52  p.x() = pos / num_cols;
53  p.y() = pos % num_cols;
54 
55  return p;
56  }
57 
59  const Parameters& params) :
60  costmap(grid), params(params)
61  {
62  ARMARX_VERBOSE << "Grid with size (" << grid.getGrid().rows() << ", "
63  << grid.getGrid().cols() << ").";
64  }
65 
67  ShortestPathFasterAlgorithm::plan(const Eigen::Vector2f& start,
68  const Eigen::Vector2f& goal,
69  const bool checkStartForCollision) const
70  {
71  // ARMARX_CHECK(not costmap.isInCollision(start)) << "Start is in collision";
72  // ARMARX_CHECK(not costmap.isInCollision(goal)) << "Goal is in collision";
73 
74  const Costmap::Vertex startVertex = costmap.toVertex(start);
75  const auto goalVertex = Eigen::Vector2i{costmap.toVertex(goal).index};
76 
77  ARMARX_VERBOSE << "Planning from " << startVertex.index << " to " << goalVertex;
78 
79  if ((startVertex.index.array() == goalVertex.array()).all())
80  {
81  ARMARX_INFO << "Already at goal.";
82  return {.path = {}, .success = true};
83  }
84 
85  auto costmapWithValidStart = costmap.getGrid();
86  if (costmap.isInCollision(start))
87  {
88  costmapWithValidStart(startVertex.index.x(), startVertex.index.y()) = 0.1;
89  }
90 
91  const Result result =
92  spfa(costmapWithValidStart, Eigen::Vector2i{startVertex.index}, checkStartForCollision);
93 
94  const auto isStart = [&startVertex](const Eigen::Vector2i& v) -> bool
95  { return (v.array() == startVertex.index.array()).all(); };
96 
97  const auto isInvalid = [](const Eigen::Vector2i& v) -> bool
98  { return (v.x() == -1) and (v.y() == -1); };
99 
100  // traverse path from goal to start
101  std::vector<Eigen::Vector2f> path;
102  path.push_back(goal);
103 
104  const Eigen::Vector2i* pt = &goalVertex;
105 
106  ARMARX_VERBOSE << "Creating shortest path from grid";
107  while (true)
108  {
109  if (isInvalid(*pt))
110  {
111  ARMARX_WARNING << "No path found from (" << start << ") to (" << goal << ").";
112  return {};
113  }
114 
115  const Eigen::Vector2i& parent = result.parents.at(pt->x()).at(pt->y());
116 
117  ARMARX_DEBUG << VAROUT(parent);
118 
119  if (isStart(parent))
120  {
121  break;
122  }
123 
124  path.push_back(costmap.toPositionGlobal(parent.array()));
125 
126  pt = &parent;
127  }
128 
129  path.push_back(start);
130  ARMARX_VERBOSE << "Path found with " << path.size() << " nodes.";
131 
132  // reverse the path such that: start -> ... -> goal
133  ranges::reverse(path);
134 
135  return {.path = path, .success = true};
136  }
137 
139  ShortestPathFasterAlgorithm::spfa(const Eigen::Vector2f& start,
140  const bool checkStartForCollision) const
141  {
142  return spfa(costmap.getGrid(), costmap.toVertex(start).index, checkStartForCollision);
143  }
144 
146  ShortestPathFasterAlgorithm::spfa(const Eigen::MatrixXf& inputMap,
147  const Eigen::Vector2i& source,
148  const bool checkStartForCollision) const
149  {
150  if (checkStartForCollision)
151  {
152  ARMARX_CHECK_GREATER(inputMap(source.x(), source.y()), 0.F)
153  << "Start must not be in collision";
154  }
155 
156  const float eps = 1e-6;
157  const int num_dirs = 8;
158 
159  const int dirs[num_dirs][2] = {
160  {-1, -1}, {-1, 0}, {-1, 1}, {0, 1}, {1, 1}, {1, 0}, {1, -1}, {0, -1}};
161 
162  const float dir_lengths[num_dirs] = {
163  std::sqrt(2.0f), 1, std::sqrt(2.0f), 1, std::sqrt(2.0f), 1, std::sqrt(2.0f), 1};
164 
165  // Process input map
166  // py::buffer_info map_buf = input_map.request();
167  int num_rows = inputMap.rows();
168  int num_cols = inputMap.cols();
169 
170  // Get source coordinates
171  int source_i = source.x();
172  int source_j = source.y();
173 
174  int max_num_verts = num_rows * num_cols;
175  int max_edges_per_vert = num_dirs;
176  const float inf = 2 * max_num_verts;
177  int queue_size = num_dirs * num_rows * num_cols;
178 
179  // Initialize arrays
180  int* edges = new int[max_num_verts * max_edges_per_vert]();
181  int* edge_counts = new int[max_num_verts]();
182  int* queue = new int[queue_size]();
183  bool* in_queue = new bool[max_num_verts]();
184  float* weights = new float[max_num_verts * max_edges_per_vert]();
185  float* dists = new float[max_num_verts];
186  for (int i = 0; i < max_num_verts; ++i)
187  dists[i] = inf;
188 
189  std::vector<int> parents(max_num_verts, -1);
190 
191  // Build graph
192  ARMARX_VERBOSE << "Build graph";
193  for (int row = 0; row < num_rows; ++row)
194  {
195  for (int col = 0; col < num_cols; ++col)
196  {
197  int v = ravel(row, col, num_cols);
198  if (inputMap(row, col) == 0.F) // collision
199  continue;
200 
201  for (int k = 0; k < num_dirs; ++k)
202  {
203  int ip = row + dirs[k][0], jp = col + dirs[k][1];
204  if (ip < 0 || jp < 0 || ip >= num_rows || jp >= num_cols)
205  continue;
206 
207  int vp = ravel(ip, jp, num_cols);
208  if (inputMap(ip, jp) == 0.F) // collision
209  continue;
210 
211 
212  const float clippedObstacleDistance =
213  std::min(inputMap(ip, jp), params.obstacleMaxDistance);
214 
215  const float travelCost = dir_lengths[k];
216 
217  const float targetDistanceCost =
218  params.obstacleDistanceWeight *
219  std::pow(1.F - clippedObstacleDistance / params.obstacleMaxDistance,
220  params.obstacleCostExponent);
221 
222  const float edgeCost = params.obstacleDistanceCosts
223  ? travelCost * (1 + targetDistanceCost)
224  : travelCost;
225 
226  int e = ravel(v, edge_counts[v], max_edges_per_vert);
227  edges[e] = vp;
228  weights[e] = edgeCost;
229  edge_counts[v]++;
230  }
231  }
232  }
233 
234  // SPFA
235  ARMARX_DEBUG << "SPFA";
236  int s = ravel(source_i, source_j, num_cols);
237  int head = 0, tail = 0;
238  dists[s] = 0;
239  queue[++tail] = s;
240  in_queue[s] = true;
241  while (head < tail)
242  {
243  int u = queue[++head];
244  in_queue[u] = false;
245  for (int j = 0; j < edge_counts[u]; ++j)
246  {
247  int e = ravel(u, j, max_edges_per_vert);
248  int v = edges[e];
249  float new_dist = dists[u] + weights[e];
250  if (new_dist < dists[v])
251  {
252  parents[v] = u;
253  dists[v] = new_dist;
254  if (!in_queue[v])
255  {
256  assert(tail < queue_size);
257  queue[++tail] = v;
258  in_queue[v] = true;
259  if (dists[queue[tail]] < dists[queue[head + 1]])
260  std::swap(queue[tail], queue[head + 1]);
261  }
262  }
263  }
264  }
265 
266  // Copy output into numpy array
267  ARMARX_DEBUG << "Copy to output variables";
268 
269  Eigen::MatrixXf output_dists(num_rows, num_cols);
270  Result::Mask reachable(num_rows, num_cols);
271  reachable.setOnes();
272  // auto output_dists = py::array_t<float>({num_rows, num_cols});
273 
274 
275  std::vector<std::vector<Eigen::Vector2i>> output_parents;
276 
277  for (int row = 0; row < num_rows; row++)
278  {
279  output_parents.push_back(std::vector<Eigen::Vector2i>(static_cast<size_t>(num_cols),
280  Eigen::Vector2i{-1, -1}));
281  }
282 
283  int invalids = 0;
284 
285  for (int row = 0; row < num_rows; ++row)
286  {
287  for (int col = 0; col < num_cols; ++col)
288  {
289  int u = ravel(row, col, num_cols);
290  output_dists(row, col) = (dists[u] < inf - eps) * dists[u];
291 
292  if (parents[u] == -1) // no parent
293  {
294  invalids++;
295  reachable(row, col) = false;
296  continue;
297  }
298 
299  output_parents.at(row).at(col) = unravel(parents[u], num_cols);
300  }
301  }
302 
303  // "fix": the initial position does not have any parents. But it's still reachable ...
304  reachable(source.x(), source.y()) = true;
305 
306  ARMARX_VERBOSE << "Fraction of invalid cells: (" << invalids << "/" << parents.size()
307  << ")";
308 
309  // Free memory
310  delete[] edges;
311  delete[] edge_counts;
312  delete[] queue;
313  delete[] in_queue;
314  delete[] weights;
315  delete[] dists;
316 
317  ARMARX_DEBUG << "Done.";
318 
319  // The distances are in a unit-like system, so the distance between two neighbor cells is 1
320  // We need to convert it back to a metric system (mutiplying it by the cell size) to be independent of the cell size.
321  return Result{.distances = costmap.params().cellSize * output_dists,
322  .parents = output_parents,
323  .reachable = reachable};
324  }
325 
326 
327 } // namespace armarx::navigation::algorithms::spfa
GfxTL::sqrt
VectorXD< D, T > sqrt(const VectorXD< D, T > &a)
Definition: VectorXD.h:662
armarx::navigation::algorithms::spfa::ShortestPathFasterAlgorithm::Parameters
Definition: ShortestPathFasterAlgorithm.h:36
ARMARX_VERBOSE
#define ARMARX_VERBOSE
Definition: Logging.h:180
armarx::navigation::algorithms::spfa::ShortestPathFasterAlgorithm::spfa
Result spfa(const Eigen::Vector2f &start, bool checkStartForCollision=true) const
Definition: ShortestPathFasterAlgorithm.cpp:139
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:41
armarx::navigation::algorithms::Costmap::Vertex::index
Index index
Definition: Costmap.h:61
armarx::navigation::algorithms::spfa::ShortestPathFasterAlgorithm::PlanningResult
Definition: ShortestPathFasterAlgorithm.h:62
armarx::navigation::algorithms::Costmap::Vertex
Definition: Costmap.h:59
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
Definition: ShortestPathFasterAlgorithm.h:39
armarx::navigation::algorithms::Costmap::toPositionGlobal
Position toPositionGlobal(const Index &index) const
Definition: Costmap.cpp:71
armarx::navigation::algorithms::spfa::ravel
int ravel(int row, int col, int num_cols)
Definition: ShortestPathFasterAlgorithm.cpp:40
armarx::navigation::algorithms::spfa::ShortestPathFasterAlgorithm::Result
Definition: ShortestPathFasterAlgorithm.h:51
ShortestPathFasterAlgorithm.h
Costmap.h
armarx::reverse
T reverse(const T &o)
Definition: container.h:32
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:10
armarx::navigation::algorithms::spfa::ShortestPathFasterAlgorithm::plan
PlanningResult plan(const Eigen::Vector2f &start, const Eigen::Vector2f &goal, bool checkStartForCollision=true) const
Definition: ShortestPathFasterAlgorithm.cpp:67
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:230
ARMARX_DEBUG
#define ARMARX_DEBUG
Definition: Logging.h:177
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:99
armarx::navigation::algorithms::Costmap::getGrid
const Grid & getGrid() const
Definition: Costmap.cpp:236
armarx::navigation::algorithms::spfa::unravel
Eigen::Vector2i unravel(int pos, int num_cols)
Definition: ShortestPathFasterAlgorithm.cpp:46
armarx::navigation::algorithms::spfa::ShortestPathFasterAlgorithm::ShortestPathFasterAlgorithm
ShortestPathFasterAlgorithm(const Costmap &costmap, const Parameters &params)
Definition: ShortestPathFasterAlgorithm.cpp:58
boost::source
Vertex source(const detail::edge_base< Directed, Vertex > &e, const PCG &)
Definition: point_cloud_graph.h:681
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
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:174
VAROUT
#define VAROUT(x)
Definition: StringHelpers.h:182
armarx::navigation::algorithms::spfa::ShortestPathFasterAlgorithm::Result::distances
Eigen::MatrixXf distances
Definition: ShortestPathFasterAlgorithm.h:53
F
Definition: ExportDialogControllerTest.cpp:16
armarx::navigation::algorithms::spfa
This file is part of ArmarX.
Definition: ShortestPathFasterAlgorithm.cpp:36
Eigen::Matrix< bool, Eigen::Dynamic, Eigen::Dynamic >
Logging.h
min
T min(T t1, T t2)
Definition: gdiam.h:42
ARMARX_WARNING
#define ARMARX_WARNING
Definition: Logging.h:186
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:242
armarx::navigation::algorithms::Costmap
Definition: Costmap.h:13