4

I've implemented the D*-Lite algorithm (here's a description, it's an algorithm for doing pathfinding when edge costs change over time), but I'm having problems with doing the edge cost updates. It works mostly, but sometimes it gets stuck in a loop, going back and forth between two vertices. I'm trying to create a test case which exhibits this behaviour, at the moment it happens for some cases when used in a large application, which makes it difficult to debug.

I'll get a test case up as soon as I can, but maybe someone can spot the error I've done going from pseudo to C++ right away.(There is a test case included below) The article presents an optimized version, Figure 4, which is the one I've implemented. The pseudocode is pasted below.

The source for my implementation is availible here.

If it helps, I'm using these types in my code:

struct VertexProperties { double x, y; };
typedef boost::adjacency_list<boost::vecS, 
                              boost::vecS, 
                              boost::undirectedS,
                              VertexProperties,
                              boost::property<boost::edge_weight_t, double> > Graph;
typedef boost::graph_traits<Graph>::vertex_descriptor Vertex;
typedef DStarEuclidianHeuristic<Graph, Vertex> Heuristic; 
typedef DStarPathfinder<Graph, Heuristic> DStarPathfinder;

If any more information about usage is needed just ask, there's just too much to paste.

Pseudo code for D*-Lite:

procedure CalculateKey(s)
{01”} return [min(g(s), rhs(s)) + h(s_start, s) + km;min(g(s), rhs(s))];

procedure Initialize()
{02”} U = ∅;
{03”} km = 0;
{04”} for all s ∈ S rhs(s) = g(s) = ∞;
{05”} rhs(s_goal) = 0;
{06”} U.Insert(s_goal, [h(s_start, s_goal); 0]);

procedure UpdateVertex(u)
{07”} if (g(u) != rhs(u) AND u ∈ U) U.Update(u,CalculateKey(u));
{08”} else if (g(u) != rhs(u) AND u /∈ U) U.Insert(u,CalculateKey(u));
{09”} else if (g(u) = rhs(u) AND u ∈ U) U.Remove(u);

procedure ComputeShortestPath()
{10”} while (U.TopKey() < CalculateKey(s_start) OR rhs(s_start) > g(s_start))
{11”} u = U.Top();
{12”} k_old = U.TopKey();
{13”} k_new = CalculateKey(u));
{14”} if(k_old < k_new)
{15”}   U.Update(u, k_new);
{16”} else if (g(u) > rhs(u))
{17”}   g(u) = rhs(u);
{18”}   U.Remove(u);
{19”}   for all s ∈ Pred(u)
{20”}   if (s != s_goal) rhs(s) = min(rhs(s), c(s, u) + g(u));
{21”}   UpdateVertex(s);
{22”} else
{23”}   g_old = g(u);
{24”}   g(u) = ∞;
{25”}   for all s ∈ Pred(u) ∪ {u}
{26”}   if (rhs(s) = c(s, u) + g_old)
{27”}     if (s != s_goal) rhs(s) = min s'∈Succ(s)(c(s, s') + g(s'));
{28”}   UpdateVertex(s);

procedure Main()
{29”} s_last = s_start;
{30”} Initialize();
{31”} ComputeShortestPath();
{32”} while (s_start != s_goal)
{33”} /* if (g(s_start) = ∞) then there is no known path */
{34”}   s_start = argmin s'∈Succ(s_start)(c(s_start, s') + g(s'));
{35”}   Move to s_start;
{36”}   Scan graph for changed edge costs;
{37”}   if any edge costs changed
{38”}     km = km + h(s_last, s_start);
{39”}     s_last = s_start;
{40”}     for all directed edges (u, v) with changed edge costs
{41”}       c_old = c(u, v);
{42”}       Update the edge cost c(u, v);
{43”}       if (c_old > c(u, v))
{44”}         if (u != s_goal) rhs(u) = min(rhs(u), c(u, v) + g(v));
{45”}       else if (rhs(u) = c_old + g(v))
{46”}         if (u != s_goal) rhs(u) = min s'∈Succ(u)(c(u, s') + g(s'));
{47”}       UpdateVertex(u);
{48”}     ComputeShortestPath()

EDIT:

I've succeeded in creating a test-case which shows the erronous behaviour. Running this along with the code in the pastebin, it will hang up in the last get_path call, going back and forth between nodes 1 and 2. It seems to me it is because the node 3 is never touched, and so going that way has an infinite cost.

#include <cmath>
#include <boost/graph/adjacency_list.hpp>
#include "dstar_search.h"

template <typename Graph, typename Vertex>
struct DStarEuclidianHeuristic {
    DStarEuclidianHeuristic(const Graph& G_) : G(G_) {}

    double operator()(const Vertex& u, const Vertex& v) {
        double dx = G[u].x - G[v].x;
        double dy = G[u].y - G[v].y;
        double len = sqrt(dx*dx+dy*dy);
        return len;
    }

    const Graph& G;
};

struct VertexProp {
    double x, y;
};

int main() {
    typedef boost::adjacency_list<boost::vecS, boost::vecS, boost::undirectedS,
        VertexProp, boost::property<boost::edge_weight_t, double> > Graph;
    typedef boost::graph_traits<Graph>::vertex_descriptor Vertex;
    typedef boost::graph_traits<Graph>::edge_descriptor Edge;
    typedef DStarEuclidianHeuristic<Graph, Vertex> Heur;

    typedef boost::property_map<Graph, boost::edge_weight_t>::type WMap;

    Graph g(7);
    WMap weights = boost::get(boost::edge_weight, g);
    Edge e;
    // Create a graph
    e = boost::add_edge(0, 1, g).first;
    weights[e] = sqrt(2.);
    e = boost::add_edge(1, 2, g).first;
    weights[e] = 1;
    e = boost::add_edge(2, 3, g).first;
    weights[e] = 1;
    e = boost::add_edge(1, 4, g).first;
    weights[e] = 1;
    e = boost::add_edge(3, 4, g).first;
    weights[e] = 1;
    e = boost::add_edge(3, 5, g).first;
    weights[e] = sqrt(2.);
    e = boost::add_edge(2, 6, g).first;
    weights[e] = sqrt(2.);
    e = boost::add_edge(5, 6, g).first;
    weights[e] = 1;
    e = boost::add_edge(6, 7, g).first;
    weights[e] = 1;
    g[0].x = 1; g[0].y = 0;
    g[1].x = 0; g[1].y = 1;
    g[2].x = 0; g[2].y = 2;
    g[3].x = 1; g[3].y = 2;
    g[4].x = 1; g[4].y = 1;
    g[5].x = 2; g[5].y = 3;
    g[6].x = 1; g[6].y = 3;
    g[7].x = 1; g[7].y = 4;

    DStarPathfinder<Graph, Heur> dstar(g, Heur(g), 0, 7);
    std::list<std::pair<Edge, double>> changes;

    auto a =  dstar.get_path(); // Find the initial path, works well
    std::copy(a.begin(), a.end(), std::ostream_iterator<Vertex>(std::cout, ","));
    // Now change the cost of going from 2->6, and try to find a new path
    changes.push_back(std::make_pair(boost::edge(2, 6, g).first, 4.));
    dstar.update(changes);
    a = dstar.get_path(); // Stuck in loop
    std::copy(a.begin(), a.end(), std::ostream_iterator<Vertex>(std::cout, ","));

    return 0;
}

EDIT 2: More progress. If I replace the break condition in the while loop in ComputeShortestPath with just U != Ø (U is not empty), the path is found! It's quite slow though, since it always examines every node in the graph, which is not supposed to be neccessary. Also, since I use undirected graphs, I added some code to {40"} to update both u and v.

Andrew Walker
  • 40,984
  • 8
  • 62
  • 84
carlpett
  • 12,203
  • 5
  • 48
  • 82
  • Use underscores: knew->k_new, gold->g_old, etc. It makes parsing the code by humans easier - my brain sees 'gold' and thinks of the metal! – Skizz Aug 30 '11 at 15:05
  • In your comments responding to fred you write that the Manhattan distance is abs(dx+dy). You mean abs(dx)+abs(dy), right? – Clark Sep 09 '11 at 16:51
  • Err, yes. Can't even get my corrections correct can I? However, as I also pointed out there, the manhattan distance is larger than (or equal to) the euclidian distance. The only reason it worked was because first time I forgot the `abs` entierly, so there was cancellation. Thanks anyway! – carlpett Sep 09 '11 at 16:56

5 Answers5

7

There are at least two problems with your code (not including the typenames I had to prepend to constructs like std::vector<TemplateParameter>::iterator in order to compile it).

  1. You're using a non-admissible heuristic, since the diagonals cost 1 but have length √2. This prevents the second call to ComputeShortestPath from doing anything at all.

  2. The update method of the heap you're using (which is private by convention to Boost and thus apparently undocumented) supports key decreases only. D* Lite needs key increases as well.

fred
  • 331
  • 1
  • 2
  • Thanks for your feedback! I've changed the cost for diagonal traversal, that was the result of mind-lapse. I also read up on the d-ary heap, and indeed it only allows decreases. I changed to `boost::relaxed_heap`, which (I think, it's also undocumented...) allows both increase and decrease. However, it still does not work. If I change to a manhattan heuristic (`dx+dy`), it works for this case, but not my real problem. Why should that help? According to the paper, the heuristic must be non-negative, lower than the cost, and satisfy the triangle ineq. I think my euclidian distance does? – carlpett Sep 06 '11 at 15:36
  • Correction, it does not work with a correctly implemented manhattan distance, ie with `abs(dx+dy)` (which should be longer that the euclidian distance anyways, don't know what I was thinking) – carlpett Sep 08 '11 at 10:50
  • It turns out one problem was I was having non-admissible heuristics in the "real" problem too (it used dynamical weightings, and under some conditions, the multiplicative factor could drop below 1, which caused nonadmissibility). If that was fixed, a "non optimized" version implementation worked, but still not the one I've linked above though. However, if noone figures out what is wrong with the optimized version, this answer will get the bounty, since it did point me in the right direction – carlpett Sep 10 '11 at 00:44
3

Unfortunately, posting pseudocode isn't really useful here since the pseudocode may be correct but the actual implementation may be at fault.

Generally, in path finding algorithms, if you're cycling between nodes then there is a really good chance that the algorithm is not removing visited nodes from the set of potential route nodes. This is usually done by setting a flag on the node as you traverse through it and reset the flag as you back step up the search tree.

Skizz
  • 69,698
  • 10
  • 71
  • 108
  • The flag method is not really scalable for MT use, in which case you'll prefer keeping a set of pointers to the nodes rather than "marking" the nodes. – Matthieu M. Aug 30 '11 at 15:32
  • @Skizz: I'm pretty sure that it's the implementation that is wrong `;)`. I linked to it as a pastebin, it's a bit too long to include here. I included the pseudocode so people wouldn't have to dig it out of the article. – carlpett Aug 30 '11 at 18:04
  • Regarding the cycling, the algorithm computes the distances to some nodes until it finds the goal (directed by the heuristic). The problem arises when it tries to extract the path by following the computed shortest path, because for some reason some node has the is the cheapest alternative from one node, which in turn is the cheapest from that one... (This explanation isn't very good, the article does it much better if you've got the time) – carlpett Aug 30 '11 at 18:06
0

I'm having your same problem too. I think I got the cause, maybe in this time you find the solution to your problem and can give me some tips.

I think the problem come from the U list.

Since probably some key of every vertex have a value higher than the key of the s_start. So ComputeKey(s)<ComputeKeu(s_start) is not satisfied (first condition of the while in ComputePath), the second condition rhs(s_start)>g(s_start) is not satisfied since when you move along the path you move through cells that are being made consistent.

Then when this two conditions don't hold the while stop, so the program stops expanding new cells.

When you go calculating the path, using as successive along the path the one that minimize g(s)+c(u,s) you end up on a cell the still has and infinite g cost (cause it has not been expanded in the while cycle).

This is the reason even to the fact that if you change condition, using while U!=0 the algorithm works, this force the program to expand all the vertices in the U list. (But you definitely lost the advantages of dynamical algorithm).

Now I hope I've helped you, if you don't need this help no more maybe you can help me.

A5C1D2H2I1M1N2O1R2T1
  • 190,393
  • 28
  • 405
  • 485
Ned112
  • 25
  • 5
0

I had this issue myself when implementing both the D* Lite (regular version) and the optimized version. I am a bit unsure why it happens in the first place, but I seems to be triggered for some arrangement of obstacles (cross shaped or tall vertical obstacles) where the algorithm suddenly is unable to explore more options and end up jumping back and forth between two options in an infinite loop. I already created a post on this earlier here and how I bypassed the infinite loop issue, however at the cost of the algorithm probably becoming a little slower.

0

The problem is in the UpdateVertex function.

The psuedo-code was written assuming that the comparisons are on integers (which they are in the papers). In your implementation you're doing comparisons on floating point values. You need to add a tolerance if you are dealing with non-integer costs.

You can test this on GCC by compiling with -Wfloat-equal (or even better -Werror=float-equal)

Andrew Walker
  • 40,984
  • 8
  • 62
  • 84