Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add DigraphShortestPath method #148

Merged
merged 1 commit into from
Jan 31, 2019

Conversation

MTWhyte
Copy link
Collaborator

@MTWhyte MTWhyte commented Nov 21, 2018

This pull request implements a DigraphShortestPath method, which takes a digraphs and two positive integers, and returns a list in the same format as DigraphPath.

The shortest path is found using a breadth-first search.

@james-d-mitchell james-d-mitchell added 0.14 new-feature A label for new features. labels Nov 23, 2018
Copy link
Member

@james-d-mitchell james-d-mitchell left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @MTWhyte this looks really good for a first PR!! I've made a number of suggestions, please revise the PR and we can then move forward with it.

doc/oper.xml Outdated
<Returns>A pair of lists, or <K>fail</K>.</Returns>
<Description>
If there exists a non-trivial directed path (or a non-trivial cycle, in the
case that <A>u</A> <C>=</C> <A>v</A>) from vertex <A>u</A> to vertex
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a minor quibble, but I think it'd be better if there were no parentheses in this sentence. Better possibly as two sentences:

Returns the shortest directed path in the digraph digraph from the vertex u to the vertex v, if such a path exists. If u = v, then the shortest cycle of length greater than 1 is returned, again, if it exists.

Copy link
Collaborator

@wilfwilson wilfwilson Nov 23, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In reply to @james-d-mitchell: this should be either shortest cycle of length greater than 0, or shortest non-trivial cycle, as discussed below.

gap/oper.gi Outdated
distance := [];

if u = v and v in nbs[v] then # Considers the trivial path from v to v
return [[v, v], [Position(nbs[v], v)]];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, does this agree with the documentation? Isn't this condition true if u = v and there is a loop at u? Or, is it that the loop has to be of length > 0, not > 1 as I somehow assumed before??

Copy link
Collaborator

@wilfwilson wilfwilson Nov 23, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In reply to @james-d-mitchell: the definition of trivial in the package documentation:

The length of a directed walk (v1,e1,v2,e2,...,en−1,vn) is equal to n−1, the number of edges it contains. A directed walk of zero length, i.e. a sequence (v) for some vertex v, is called trivial. A trivial directed walk is considered to be both a circuit and a cycle, as is the empty directed walk ().

By the original documentation in this PR:

(or a non-trivial cycle, in the case that <A>u</A> <C>=</C> <A>v</A>)

a loop therefore qualifies as the shortest path, since a loop is a non-trivial cycle.

However, I therefore suggest adjusting the misleading comment in the line above.

gap/oper.gi Outdated
# Setting up objects useful in the function.
for i in verts do
Add(distance, -1);
od;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could replace this entire for-loop with: distance := ListWithIdenticalEntries(Length(verts), -1);

gap/oper.gi Outdated
for i in verts do
Add(next, false);
Add(falselist, false);
od;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar, to the previous comment, this could be next := BlistList([1 .. Length(verts)], []);
and falselist := BlistList([1 .. Length(verts)], []);

gap/oper.gi Outdated
Add(falselist, false);
od;

n := 0; # Counts the loops
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest either removing or improving the comment here. "The loops" could be misconstrued as meaning loops in the digraph, which is not what you mean I think.

gap/oper.gi Outdated
# Finds the path
for i in [1 .. n] do
Add(path[1], b);
Add(path[2], Position(nbs[parent[b]], b));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Position(nbs[parent[b]], b) lookup is potentially time consuming (possible linear search in nbs[parent[b]]). You could avoid this altogether by having another list edge, replacing the for-loop for b in nbs[a] do by for j in [1 .. Length(nbs[a])] do b := nbs[a][i]; and after parent[b] := a writing edge[b] := i, and the replacing Position(nbs[parent[b]], b) by Add(path[2], edge[b]). I hope that this makes sense.

gap/oper.gi Outdated
InstallMethod(DigraphShortestPath, "for a digraph and two pos ints",
[IsDigraph, IsPosInt, IsPosInt],
function(digraph, u, v)
local current, next, parent, distance, falselist, verts, nbs, n, a, b, i, path;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should add some basic checks at the start of this function too. For example, the start of DigraphPath is what's written below. I think you should probably have the same thing at the start of this method.

  verts := DigraphVertices(digraph);
  if not (u in verts and v in verts) then
    ErrorNoReturn("Digraphs: DigraphPath: usage,\n",
                  "the second and third arguments <u> and <v> must be\n",
                  "vertices of the first argument <digraph>,");
  fi;

  if IsDigraphEdge(digraph, [u, v]) then
    return [[u, v], [Position(OutNeighboursOfVertex(digraph, u), v)]];
  elif HasIsTransitiveDigraph(digraph) and IsTransitiveDigraph(digraph) then
    # If it's a known transitive digraph, just check whether the edge exists
    return fail;
    # Glean information from WCC if we have it
  elif HasDigraphConnectedComponents(digraph)
      and DigraphConnectedComponents(digraph).id[u] <>
          DigraphConnectedComponents(digraph).id[v] then
    return fail;
  fi;

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added these tests to the start of the function. I believe the

if IsDigraphEdge(digraph, [u, v]) then
      return [[u, v], [Position(OutNeighboursOfVertex(digraph, u), v)]];

does much the same thing as the existing

if u = v and v in nbs[v] then  # Considers the trivial path from v to v
    return [[v, v], [Position(nbs[v], v)]];

which was the subject of a previous comment. I shall just remove the latter for simplicity.

gap/oper.gi Outdated
Add(path[1], u); # Adds the starting vertex to the list of vertices.
return [Reversed(path[1]), Reversed(path[2])];
fi;

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove this blank line.

gap/oper.gi Outdated

od;
od;

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove the two blank lines here too.

gap/oper.gi Outdated
od;

current := ListBlist(verts, next);
next := IntersectionBlist(next, falselist);
Copy link
Member

@james-d-mitchell james-d-mitchell Nov 23, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think (not 100%) that you'd be better off doing IntersectBlist(next, falselist);. The reason being that IntersectionBlist produces a new list, whereas IntersectBlist changes next in-place.

gap/oper.gi Outdated

n := 0; # Counts the loops
while current <> [] do
n := n + 1;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Weird spacing before the := here.

gap/oper.gi Outdated
next := IntersectionBlist(next, falselist);

od;
return fail;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bad indentation.

Copy link
Collaborator

@wilfwilson wilfwilson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for this! I've added some comments in relation to James's ones.

doc/oper.xml Outdated
<Returns>A pair of lists, or <K>fail</K>.</Returns>
<Description>
If there exists a non-trivial directed path (or a non-trivial cycle, in the
case that <A>u</A> <C>=</C> <A>v</A>) from vertex <A>u</A> to vertex
Copy link
Collaborator

@wilfwilson wilfwilson Nov 23, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In reply to @james-d-mitchell: this should be either shortest cycle of length greater than 0, or shortest non-trivial cycle, as discussed below.

gap/oper.gi Outdated
distance := [];

if u = v and v in nbs[v] then # Considers the trivial path from v to v
return [[v, v], [Position(nbs[v], v)]];
Copy link
Collaborator

@wilfwilson wilfwilson Nov 23, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In reply to @james-d-mitchell: the definition of trivial in the package documentation:

The length of a directed walk (v1,e1,v2,e2,...,en−1,vn) is equal to n−1, the number of edges it contains. A directed walk of zero length, i.e. a sequence (v) for some vertex v, is called trivial. A trivial directed walk is considered to be both a circuit and a cycle, as is the empty directed walk ().

By the original documentation in this PR:

(or a non-trivial cycle, in the case that <A>u</A> <C>=</C> <A>v</A>)

a loop therefore qualifies as the shortest path, since a loop is a non-trivial cycle.

However, I therefore suggest adjusting the misleading comment in the line above.

gap/oper.gi Outdated
nbs := OutNeighbors(digraph);
distance := [];

if u = v and v in nbs[v] then # Considers the trivial path from v to v
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A loop is not a trivial path. Therefore please change this comment, to something like # is u = v, and there is a loop at v?

@wilfwilson wilfwilson added 0.15 and removed 0.14 labels Nov 23, 2018
@james-d-mitchell
Copy link
Member

I'm happy for this to be merged, but it needs to be rebased on the current master first.

@james-d-mitchell james-d-mitchell merged commit 4902698 into digraphs:master Jan 31, 2019
@MTWhyte MTWhyte deleted the shortest-path-2 branch June 21, 2019 19:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
new-feature A label for new features.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants