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

Adds MaximalCommonSubdigraph and MinimalCommonSupergraph #334

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions gap/grahom.gd
Original file line number Diff line number Diff line change
Expand Up @@ -92,3 +92,6 @@ DeclareOperation("DigraphsRespectsColouring",
[IsDigraph, IsDigraph, IsTransformation, IsList, IsList]);
DeclareOperation("DigraphsRespectsColouring",
[IsDigraph, IsDigraph, IsPerm, IsList, IsList]);

DeclareOperation("MaximalCommonSubdigraph", [IsDigraph, IsDigraph]);
DeclareOperation("MinimalCommonSuperdigraph", [IsDigraph, IsDigraph]);
132 changes: 132 additions & 0 deletions gap/grahom.gi
Original file line number Diff line number Diff line change
Expand Up @@ -678,3 +678,135 @@ function(D, t)
n := DigraphNrVertices(D);
return IsDigraphColouring(D, ImageListOfTransformation(t, n));
end);

InstallMethod(MaximalCommonSubdigraph, "for a pair of digraphs",
[IsDigraph, IsDigraph],
function(A, B)
local D1, D2, MPG, Clqus, M, i, j, l, vertices, edges, n,
m, adj, embedding1, embedding2, iso, A1, A2, intto4tuple, t;

D1 := DigraphImmutableCopy(A);
D2 := DigraphImmutableCopy(B);

# If the digraphs are isomorphic then we return the first one as the answer
Copy link
Member

Choose a reason for hiding this comment

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

Shouldn't the isomorphism check be done before A and B are copied? After all what's the point of copying them if it's not necessary.

iso := IsomorphismDigraphs(D1, D2);
if iso <> fail then
return [D1, IdentityTransformation, AsTransformation(iso)];
fi;

n := DigraphNrVertices(D1);
m := DigraphNrVertices(D2);
A1 := AdjacencyMatrix(D1);
A2 := AdjacencyMatrix(D2);

# The algorithm works as follows: We construct the modular product digraph
# MPG (see https://en.wikipedia.org/wiki/Modular_product_of_graphs for the
# undirected version) a maximal partial isomorphism between D1 and D2 is
# equal to a maximal clique this digraph. We then search for cliques using the
# DigraphMaximalCliquesReps function.

# we represent an element of V(D1)xV(D2)xV(D1)xV(D2) as an element of
# [0 .. (n x m x n x m) - 1]
intto4tuple := function(i)
local t, tempi;
Copy link
Member

Choose a reason for hiding this comment

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

This could be more compact as:

  intto4tuple := i -> [RemInt(i, n),
                              QuoInt(RemInt(i, n * m), n),
                              QuoInt(RemInt(i, n * m * n), n * m),
                              QuoInt(i, n * m * n)];

Copy link
Member

Choose a reason for hiding this comment

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

But with things properly aligned...

t := [0, 0, 0, QuoInt(i, n * m * n)];
tempi := RemInt(i, n * m * n);
t[3] := QuoInt(tempi, n * m);
tempi := RemInt(i, n * m);
t[2] := QuoInt(tempi, n);
t[1] := RemInt(i, n);
return t;
end;

# As we are only concerned with cliques we don't need to consider the isolated
# vertices of MPG so we constrict it without them
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
# vertices of MPG so we constrict it without them
# vertices of MPG so we construct it without them


edges := List([1 .. n * m], x -> []);
for i in [0 .. (n * m * n * m - 1)] do
t := intto4tuple(i);
# not that we are only concerned with cliques so we can ignore edges
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
# not that we are only concerned with cliques so we can ignore edges
# note that we are only concerned with cliques so we can ignore edges

# if we don't have their corresponding reverse edges
if t[1] <> t[3] and t[2] <> t[4] and
A1[t[1] + 1][t[1] + 1] = A2[t[2] + 1][t[2] + 1] and
A1[t[3] + 1][t[3] + 1] = A2[t[4] + 1][t[4] + 1] and
A1[t[1] + 1][t[3] + 1] = A2[t[2] + 1][t[4] + 1] and
A1[t[3] + 1][t[1] + 1] = A2[t[4] + 1][t[2] + 1] then
Add(edges[t[1] + t[2] * n + 1], t[3] + t[4] * n);
fi;
od;

vertices := Filtered([0 .. n * m - 1], x -> edges[1 + x] <> []);

# In the case that the modular product digraph has no edges, we attempt
# to find a vertex in the second digraph with the same number of loops
# as one in the first
if vertices = [] then
for i in DigraphVertices(D1) do
for j in DigraphVertices(D2) do
if AdjacencyMatrix(D1)[i][i] = AdjacencyMatrix(D2)[j][j] then
Copy link
Member

Choose a reason for hiding this comment

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

You assign the variables A1 and A2 to AdjacencyMatrix(D1) and AdjacencyMatrix(D2) above, so why not use these variables again here?

return [Digraph([ListWithIdenticalEntries(AdjacencyMatrix(D1)[i][i],
1)]),
Transformation([1], [i]), Transformation([1], [j])];
fi;
od;
od;
return [Digraph([]), IdentityTransformation, IdentityTransformation];
fi;

# We now build the modular product graph
adj := function(v, w);
return w in edges[v + 1];
end;
MPG := Digraph(vertices, adj);

# We find a big clique
Clqus := DigraphMaximalCliquesReps(MPG);
M := 1;
for l in [1 .. Size(Clqus)] do
if Size(Clqus[l]) > Size(Clqus[M]) then
M := l;
fi;
od;

embedding1 := List(Clqus[M], x -> intto4tuple(vertices[x])[1] + 1);
embedding2 := List(Clqus[M], x -> intto4tuple(vertices[x])[2] + 1);
return [InducedSubdigraph(D1, embedding1),
Transformation([1 .. Size(embedding1)], embedding1),
Transformation([1 .. Size(embedding2)], embedding2)];

end);

InstallMethod(MinimalCommonSuperdigraph, "for a pair of digraphs",
[IsDigraph, IsDigraph],
function(D1, D2)
local out, L, v, e, embfunc, embedding1, embedding2, newvertices;
L := MaximalCommonSubdigraph(D1, D2);
L[2] := List([1 .. DigraphNrVertices(L[1])], x -> x ^ L[2]);
L[3] := List([1 .. DigraphNrVertices(L[1])], x -> x ^ L[3]);
out := List(OutNeighbours(D1), x -> ShallowCopy(x));
newvertices := Filtered(DigraphVertices(D2), x -> not x in L[3]);
embedding1 := [1 .. DigraphNrVertices(D1)];

embfunc := function(v)
if v in L[3] then
return L[2][Position(L[3], v)];
fi;
return Position(newvertices, v) + DigraphNrVertices(D1);
end;
embedding2 := List(DigraphVertices(D2), embfunc);

for v in newvertices do
Add(out, []);
od;

for e in DigraphEdges(D2) do
if (not e[1] in L[3]) or (not e[2] in L[3]) then
Add(out[embedding2[e[1]]], embedding2[e[2]]);
fi;
od;

return [Digraph(out), Transformation([1 .. Size(embedding1)], embedding1),
Transformation([1 .. Size(embedding2)], embedding2)];

end);

42 changes: 42 additions & 0 deletions tst/standard/grahom.tst
Original file line number Diff line number Diff line change
Expand Up @@ -2453,6 +2453,48 @@ false
gap> IsDigraphEmbedding(ran, src, (), [2, 1], [1, 1, 2]);
false

# MaximalCommSubdigraph and MinimalCommonSuperDigraph
gap> MaximalCommonSubdigraph(NullDigraph(0), CompleteDigraph(10));
[ <immutable empty digraph with 0 vertices>, IdentityTransformation,
IdentityTransformation ]
gap> MinimalCommonSuperdigraph(NullDigraph(0), CompleteDigraph(10));
[ <immutable digraph with 10 vertices, 90 edges>, IdentityTransformation,
IdentityTransformation ]
gap> MaximalCommonSubdigraph(PetersenGraph(), CompleteDigraph(10));
[ <immutable digraph with 2 vertices, 2 edges>, IdentityTransformation,
IdentityTransformation ]
gap> MinimalCommonSuperdigraph(PetersenGraph(), CompleteDigraph(10));
[ <immutable digraph with 18 vertices, 118 edges>, IdentityTransformation,
Transformation( [ 1, 2, 11, 12, 13, 14, 15, 16, 17, 18, 11, 12, 13, 14, 15,
16, 17, 18 ] ) ]
gap> MaximalCommonSubdigraph(NullDigraph(10), CompleteDigraph(10));
[ <immutable empty digraph with 1 vertex>, IdentityTransformation,
IdentityTransformation ]
gap> MinimalCommonSuperdigraph(NullDigraph(10), CompleteDigraph(10));
[ <immutable digraph with 19 vertices, 90 edges>, IdentityTransformation,
Transformation( [ 1, 11, 12, 13, 14, 15, 16, 17, 18, 19, 11, 12, 13, 14, 15,
16, 17, 18, 19 ] ) ]
gap> MaximalCommonSubdigraph(CompleteDigraph(100), CompleteDigraph(100));
[ <immutable digraph with 100 vertices, 9900 edges>, IdentityTransformation,
IdentityTransformation ]
gap> MinimalCommonSuperdigraph(CompleteDigraph(100), CompleteDigraph(100));
Copy link
Contributor

Choose a reason for hiding this comment

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

To be even more certain that these functions continue working in the future, perhaps there should be some tests that check not only equality of display, but also equality of objects, here? For example:

gap> comm := MinimalCommonSuperdigraph(CompleteDigraph(100), CompleteDigraph(100));;
gap> comm[1] = CompleteDigraph(100);
true

I'm not sure to what extent this is implemented in other test files, it may be overkill :)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

good idea

[ <immutable digraph with 100 vertices, 9900 edges>, IdentityTransformation,
IdentityTransformation ]
gap> MaximalCommonSubdigraph(PetersenGraph(),
> DigraphSymmetricClosure(CycleDigraph(5)));
[ <immutable digraph with 5 vertices, 10 edges>, IdentityTransformation,
IdentityTransformation ]
gap> MinimalCommonSuperdigraph(PetersenGraph(),
> DigraphSymmetricClosure(CycleDigraph(5)));
[ <immutable digraph with 10 vertices, 30 edges>, IdentityTransformation,
IdentityTransformation ]
gap> MaximalCommonSubdigraph(Digraph([[1, 1]]), Digraph([[1]]));
[ <immutable empty digraph with 0 vertices>, IdentityTransformation,
IdentityTransformation ]
gap> MinimalCommonSuperdigraph(Digraph([[1, 1]]), Digraph([[1]]));
[ <immutable multidigraph with 2 vertices, 3 edges>, IdentityTransformation,
Transformation( [ 2, 2 ] ) ]

# DIGRAPHS_UnbindVariables
gap> Unbind(edges);
gap> Unbind(epis);
Expand Down