Skip to content
Draft
Show file tree
Hide file tree
Changes from 3 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
36 changes: 36 additions & 0 deletions doc/planar.xml
Original file line number Diff line number Diff line change
Expand Up @@ -464,3 +464,39 @@ true
</ManSection>

<#/GAPDoc>

<#GAPDoc Label="IsMapGraph">
<ManSection>
<Attr Name="IsMapGraph" Arg="D"/>
<Returns><K>true</K> or <K>false</K>.</Returns>
<Description>
A map graph is a graph whose vertices can be represented as disjoint
regions in the plane, where two vertices are adjacent if the
corresponding regions share at least one point on their boundaries.
This is a generalized notion of planar graphs by allowing any number
of regions to meet at a single point rather than just along
borders of non-zero length.
<P/>
A graph <A>D</A> is a map graph if and only if it is the
<E>half-square</E> of some planar bipartite graph <M>H</M>. That is,
<A>D</A> is the graph formed by one part of the bipartition of <M>H</M>,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
<A>D</A> is the graph formed by one part of the bipartition of <M>H</M>,
<A>D</A> is the graph formed by one part of the bipartitions of <M>H</M>,

where two vertices in <A>D</A> are connected if they are at distance 2 in
<M>H</M>.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
<M>H</M>.
<M>H</M>.<P/>


Note that every planar graph is a map graph, but map graphs may contain
arbitrarily large cliques making them a strictly larger class than planar graphs.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Be good to indicate something about the complexity of the implementation, it looks fairly high (unavoidably) so be good to indicate this.


<Example><![CDATA[
gap> D := CompleteDigraph(5);
<immutable complete digraph with 5 vertices>
gap> IsPlanarDigraph(D);
false
gap> IsMapGraph(D);
true
gap> D := CompleteBipartiteDigraph(3, 7);;
gap> IsMapGraph(D);
false
]]></Example>
</Description>
</ManSection>
<#/GAPDoc>
1 change: 1 addition & 0 deletions doc/z-chap4.xml
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@
<#Include Label="OuterPlanarEmbedding">
<#Include Label="SubdigraphHomeomorphicToK">
<#Include Label="DualPlanarGraph">
<#Include Label="IsMapGraph">
</Section>

<Section><Heading>Hashing</Heading>
Expand Down
2 changes: 2 additions & 0 deletions gap/planar.gd
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ DeclareAttribute("DualPlanarGraph", IsDigraph);

DeclareProperty("IsPlanarDigraph", IsDigraph);
DeclareProperty("IsOuterPlanarDigraph", IsDigraph);
DeclareProperty("IsMapGraph", IsDigraph);

# True methods . . .

Expand All @@ -41,3 +42,4 @@ InstallTrueMethod(IsHamiltonianDigraph,
InstallTrueMethod(IsPlanarDigraph, IsChainDigraph);
InstallTrueMethod(IsPlanarDigraph, IsCycleDigraph);
InstallTrueMethod(IsPlanarDigraph, IsOuterPlanarDigraph);
InstallTrueMethod(IsMapGraph, IsPlanarDigraph);
253 changes: 253 additions & 0 deletions gap/planar.gi
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,231 @@ function(D)
return DigraphByEdges(dualEdges);
end);

# A graph is a map graph if we can find a "witness" that is planar
# and bipartite that the original graph is a half-square of

# for all vertices v find all ways to cover its edges using a set of cliques
BindGlobal("DIGRAPHS_NbrCliqueCovers",
function(D, v)
local nbrs, sub, subEdges, maxCliques, mapping, cliqueCoverage,
covers, i, j, e, temp, EdgesCoveredBy, Backtrack;
nbrs := ShallowCopy(OutNeighboursOfVertex(D, v));
nbrs := Filtered(nbrs, x -> x <> v);
Sort(nbrs);

if IsEmpty(nbrs) then
return [[]];
fi;
sub := InducedSubdigraph(D, nbrs);

# gets the edges in the new graph
subEdges := [];
for i in DigraphVertices(sub) do
for e in OutNeighboursOfVertex(sub, i) do
if e > i then

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'm struggling to understand why e > i is being checked here? Be good to have some more comments to understand what's being done here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Intent is so that we don't double count edges([1,2] and [2,1]) ordering doesn't matter but edges are undirected(as a cant neighbor b without b also neighboring a)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Gotcha, I'd still like to understand what algorithm your implementing but we can discuss it tomorrow, looks like there's great stuff in here!

Add(subEdges, [i, e]);
fi;
od;
od;

if IsEmpty(subEdges) then
return [List(nbrs, u -> [u])];
fi;

maxCliques := List(DigraphMaximalCliques(sub),
cl -> List(cl, idx -> nbrs[idx]));

mapping := [];
for i in [1 .. Length(nbrs)] do
mapping[nbrs[i]] := i;
od;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Local functions that depend on the state of "global" variables, like subEdges here, smell bad, and should probably be avoided. Can you please refactor this so that EdgesCoveredBy is a global function and that it's arguments are explicitly given?

EdgesCoveredBy := function(clique)
local covered, a, b, ia, ib, edgeIdx;
covered := BlistList([1 .. Length(subEdges)], []);

for a in [1 .. Length(clique)] do
for b in [a + 1 .. Length(clique)] do
ia := mapping[clique[a]];
ib := mapping[clique[b]];

if ia > ib then
temp := ia;
ia := ib;
ib := temp;
fi;

edgeIdx := PositionSorted(subEdges, [ia, ib]);
if edgeIdx <= Length(subEdges) and subEdges[edgeIdx] = [ia, ib] then
covered[edgeIdx] := true;
fi;
od;
od;
return covered;
end;

cliqueCoverage := List(maxCliques, EdgesCoveredBy);

covers := [];

# find all edge covering clique sets
Backtrack := function(cover, uncovered, start)
local newUncovered, cov, touchedVerts, u, jj;

if not ForAny(uncovered, x -> x) then
touchedVerts := Union(cover);
cov := ShallowCopy(cover);

for u in nbrs do
if not u in touchedVerts then
Add(cov, [u]);
fi;
od;

Add(covers, cov);
return;
fi;

for i in [start .. Length(maxCliques)] do

if ForAny([1 .. Length(subEdges)],
jj -> uncovered[jj] and cliqueCoverage[i][jj]) then

newUncovered := ShallowCopy(uncovered);
for j in [1 .. Length(subEdges)] do
if cliqueCoverage[i][j] then
newUncovered[j] := false;
fi;
od;

Add(cover, maxCliques[i]);
Backtrack(cover, newUncovered, i + 1);
Remove(cover);
fi;
od;
end;

Backtrack([], BlistList([1 .. Length(subEdges)],
[1 .. Length(subEdges)]), 1);

if IsEmpty(covers) then
return [List(nbrs, u -> [u])];
fi;

return covers;
end);

BindGlobal("DIGRAPHS_BuildWitness",
function(n, covers)
local witnessMap, nextWitness, outNeighb, v, clique, canon, key, w, u;

witnessMap := rec();
nextWitness := n + 1;
outNeighb := List([1 .. n], i -> []);

for v in [1 .. n] do
for clique in covers[v] do
canon := ShallowCopy(clique);
if not v in canon then
Add(canon, v);
fi;
Sort(canon);
key := String(canon);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I don't think this will work in general, how is the key "123" interpreted? It can be generated by [1, 23], and [123], and there's no way to tell the difference. I think you maybe want to use a HashMap here instead of a record.


if not IsBound(witnessMap.(key)) then
witnessMap.(key) := nextWitness;
Add(outNeighb, []);
nextWitness := nextWitness + 1;
fi;
w := witnessMap.(key);

for u in canon do
if not w in outNeighb[u] then

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Unless there's a reason to have the elements in outNeighb[u] in the order they are added here, then you'd be better to do AddSet(outNeighb[u], w); instead of this if-condition. The reason being that w in outNeighb[u] is a linear search if outNeighb[u] is not sorted (or it doesn't know its sorted), whereas AddSet(outNeighb[u], w) does a binary search, with log complexity.

Add(outNeighb[u], w);
fi;
if not u in outNeighb[w] then

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

same coment here

Add(outNeighb[w], u);
fi;
od;
od;
od;

for v in [1 .. Length(outNeighb)] do
Sort(outNeighb[v]);
od;

return DigraphImmutableCopy(Digraph(outNeighb));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is also an unnecessary copy

end);

# reconstruct graph from the planar bipartite graph
# for the inputted H we have U = [1...n] and V is the rest(all junctions)
BindGlobal("DIGRAPHS_HalfSquare",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think this should be an attribute (or maybe an operation), for a bipartite graph, that has independent documentation and tests. It should probably take which set of vertices to use (instead of [1 .. n]), and check if that set of vertices is one of the two sets used in the definition of the bipartite graph.

function(H, n)
local adj, u, w, v, outgoing;

adj := List([1 .. n], i -> BlistList([1 .. n], []));

for u in [1 .. n] do

Copy link
Copy Markdown
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 whole loop with DigraphDistanceSet

for w in OutNeighboursOfVertex(H, u) do
if w > n then
for v in OutNeighboursOfVertex(H, w) do
if v <= n and v <> u then
adj[u][v] := true;
fi;
od;
fi;
od;
od;

outgoing := List([1 .. n], u -> ListBlist([1 .. n], adj[u]));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I also don't really see any advantage of using blists here, rather than just constructing outgoing directly. Also we'd usually refer to this as the out-neighbours of H.

return DigraphImmutableCopy(Digraph(outgoing));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
return DigraphImmutableCopy(Digraph(outgoing));
return Digraph(outgoing);

This is immutable by default and there's no sense in taking an additional copy here.

end);

BindGlobal("DIGRAPHS_IsMapGraphSearch",
function(G, n)
local allCovers, coverCounts, indices, assignment, H,
halfsq, i, v, targetEdges;

targetEdges := Set(DigraphEdges(G));

allCovers := List([1 .. n], v -> DIGRAPHS_NbrCliqueCovers(G, v));
coverCounts := List(allCovers, Length);

if ForAny(allCovers, IsEmpty) then
return false;
fi;

indices := List([1 .. n], i -> 1);

while true do
assignment := List([1 .. n], v -> allCovers[v][indices[v]]);
H := DIGRAPHS_BuildWitness(n, assignment);

if IsPlanarDigraph(H) then
halfsq := DIGRAPHS_HalfSquare(H, n);

if Set(DigraphEdges(halfsq)) = targetEdges then
return true;
fi;
fi;
i := n;
while i >= 1 do
if indices[i] < coverCounts[i] then
indices[i] := indices[i] + 1;
break;
else
indices[i] := 1;
i := i - 1;
fi;
od;
if i = 0 then
break;
fi;
od;

return false;
end);

########################################################################
# 2. Properties
########################################################################
Expand Down Expand Up @@ -151,3 +376,31 @@ function(D)
fi;
return IS_OUTER_PLANAR(D);
end);

InstallMethod(IsMapGraph, "for a digraph", [IsDigraph],
function(D)
local G, n, m;

if IsMultiDigraph(D) then
ErrorNoReturn("expected a digraph with no multiple edges");
fi;

G := MaximalSymmetricSubdigraphWithoutLoops(D);

n := DigraphNrVertices(G);
m := DigraphNrAdjacenciesWithoutLoops(G);

if n < 5 or m < 9 then
return true;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

be good to combine these ifs into a single statement using elif, and possibly providing a reference for why these conditions imply the answers they do.

fi;

if m > 2 * (6 * n - 12) then
return false;
fi;

if IsPlanarDigraph(G) then
return true;
fi;

return DIGRAPHS_IsMapGraphSearch(G, n);
end);
42 changes: 42 additions & 0 deletions tst/standard/planar.tst
Original file line number Diff line number Diff line change
Expand Up @@ -278,3 +278,45 @@ gap> DIGRAPHS_StopTest();
gap> STOP_TEST("Digraphs package: standard/planar.tst", 0);

# DigraphSource and DigraphRange

# IsMapGraph
gap> D := NullDigraph(0);
<immutable empty digraph with 0 vertices>
gap> IsMapGraph(D);
true
gap> D := CompleteDigraph(4);
<immutable complete digraph with 4 vertices>
gap> IsMapGraph(D);
true
gap> D := CompleteDigraph(5);
<immutable complete digraph with 5 vertices>
gap> IsPlanarDigraph(D);
false
gap> IsMapGraph(D);
true
gap> D := Digraph([
> [2, 3, 4, 5, 6, 7, 8, 9, 10, 11], [1, 3, 4, 5, 6, 7, 8, 9, 10, 11],
> [1, 2, 4, 5, 6, 7, 8, 9, 10, 11], [1, 2, 3, 5, 6, 7, 8, 9, 10, 11],
> [1, 2, 3, 4, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
> [1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 12, 13, 14, 15],
> [1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14, 15],
> [1, 2, 3, 4, 5, 6, 7, 9, 10, 11, 12, 13, 14, 15],
> [1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15],
> [1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20],
> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 13, 14, 15, 16, 17, 18, 19, 20],
> [5, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 17, 18, 19, 20],
> [5, 6, 7, 8, 9, 10, 11, 12, 14, 15, 16, 17, 18, 19, 20],
> [5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 17, 18, 19, 20],
> [5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 18, 19, 20],
> [10, 11, 12, 13, 14, 15, 17, 18, 19, 20], [10, 11, 12, 13, 14, 15, 16, 18, 19, 20],
> [10, 11, 12, 13, 14, 15, 16, 17, 19, 20], [10, 11, 12, 13, 14, 15, 16, 17, 18, 20],
> [10, 11, 12, 13, 14, 15, 16, 17, 18, 19]]);
<immutable digraph with 20 vertices, 258 edges>
gap> IsPlanarDigraph(D);
false
gap> IsMapGraph(D);
true
gap> D := CompleteMultipartiteDigraph([3, 3]);
<immutable complete bipartite digraph with bicomponents of size 3>
gap> IsMapGraph(D);
false
Loading