Skip to content

Commit

Permalink
Implementation of facial walks and dual graphs (#713)
Browse files Browse the repository at this point in the history
* added FacialWalks

* added dual graph

* Suggestions for improvement built in

* Update attr.gi

---------

Co-authored-by: James Mitchell <[email protected]>
  • Loading branch information
MeikeWeiss and james-d-mitchell authored Nov 12, 2024
1 parent 332a076 commit c0a8e1b
Show file tree
Hide file tree
Showing 9 changed files with 251 additions and 0 deletions.
49 changes: 49 additions & 0 deletions doc/attr.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1495,6 +1495,55 @@ gap> DigraphAllChordlessCycles(D);
</ManSection>
<#/GAPDoc>

<#GAPDoc Label="FacialWalks">
<ManSection>
<Oper Name="FacialWalks" Arg="digraph, list"/>
<Returns>A list of lists of vertices.</Returns>
<Description>
If <A>digraph</A> is an Eulerian digraph and <A>list</A> is a rotation system of <A>digraph</A>,
then <C>FacialWalks</C> returns a list of the <E>facial walks</E> in <A>digraph</A>. <P/>

A rotation system defines for each vertex the ordering of the out-neighbours.
For example, the method <Ref Subsect="PlanarEmbedding" Style="Number" /> computes for a
planar digraph <A>D</A> the rotation system of a planar embedding of <A>D</A>.
The facial walks of <A>digraph</A> are closed walks and they are defined by the rotation system <A>list</A>.
They describe the boundaries of the faces of the embedding of <A>digraph</A> given
by the rotation system <A>list</A>.

The operation <C>FacialWalks</C> ignores
multiple edges and loops.<P/>
Here are some examples for planar embeddings:
<Example><![CDATA[
gap> D1 := CycleDigraph(4);;
gap> planar := PlanarEmbedding(D1);
[ [ 2 ], [ 3 ], [ 4 ], [ 1 ] ]
gap> FacialWalks(D1, planar);
[ [ 1, 2, 3, 4 ] ]
gap> nonPlanar := [[2, 4], [1, 3], [2, 4], [1, 3]];;
gap> FacialWalks(D1, nonPlanar);
[ [ 1, 2, 3, 4 ] ]
gap> D2 := CompleteMultipartiteDigraph([2, 2, 2]);;
gap> rotationSystem := PlanarEmbedding(D2);
[ [ 3, 5, 4, 6 ], [ 6, 4, 5, 3 ], [ 6, 2, 5, 1 ], [ 1, 5, 2, 6 ],
[ 1, 3, 2, 4 ], [ 1, 4, 2, 3 ] ]
gap> FacialWalks(D2, rotationSystem);
[ [ 1, 3, 6 ], [ 1, 4, 5 ], [ 1, 5, 3 ], [ 1, 6, 4 ], [ 2, 3, 5 ],
[ 2, 4, 6 ], [ 2, 5, 4 ], [ 2, 6, 3 ] ]
]]></Example>
Here is an example of a non-planar digraph with a corresponding rotation system:
<Example><![CDATA[
gap> D3 := CompleteMultipartiteDigraph([3, 3]);;
gap> rot := [[6, 5, 4], [6, 5, 4], [6, 5, 4], [1, 2, 3],
> [1, 2, 3], [1, 2, 3]];
[ [ 6, 5, 4 ], [ 6, 5, 4 ], [ 6, 5, 4 ], [ 1, 2, 3 ], [ 1, 2, 3 ],
[ 1, 2, 3 ] ]
gap> FacialWalks(D3, rot);
[ [ 1, 4, 2, 6, 3, 5 ], [ 1, 5, 2, 4, 3, 6 ], [ 1, 6, 2, 5, 3, 4 ] ]
]]></Example>
</Description>
</ManSection>
<#/GAPDoc>

<#GAPDoc Label="HamiltonianPath">
<ManSection>
<Attr Name="HamiltonianPath" Arg="digraph"/>
Expand Down
31 changes: 31 additions & 0 deletions doc/planar.xml
Original file line number Diff line number Diff line change
Expand Up @@ -427,3 +427,34 @@ fail
</Description>
</ManSection>
<#/GAPDoc>

<#GAPDoc Label="DualPlanarGraph">
<ManSection>
<Attr Name="DualPlanarGraph" Arg="digraph"/>
<Returns>A digraph or <K>fail</K>.</Returns>
<Description>
If <A>digraph</A> is a planar digraph, then <E>DualPlanarGraph</E> returns the the dual graph of <A>digraph</A>.
If <A>digraph</A> is not planar, then fail is returned.<P/>

The dual graph of a planar digraph <A>digraph</A> has a vertex for each face of <A>digraph</A> and an edge for
each pair of faces that are separated by an edge from each other.
Vertex <A>i</A> of the dual graph corresponds to the facial walk at the <A>i</A>-th position calling
<E>FacialWalks</E> of <A>digraph</A> with the rotation system returned by <E>PlanarEmbedding</E>.

<Example><![CDATA[
gap> D := CompleteDigraph(4);;
gap> dualD := DualPlanarGraph(D);
<immutable digraph with 4 vertices, 12 edges>
gap> IsIsomorphicDigraph(D, dualD);
true
gap> cube := Digraph([[2, 4, 5], [1, 3, 6], [2, 4, 7], [1, 3, 8],
> [1, 6, 8], [2, 5, 7], [3, 6, 8], [4, 5, 7]]);
<immutable digraph with 8 vertices, 24 edges>
gap> oct := DualPlanarGraph(cube);;
gap> IsIsomorphicDigraph(oct, CompleteMultipartiteDigraph([2, 2, 2]));
true
]]></Example>
</Description>
</ManSection>

<#/GAPDoc>
2 changes: 2 additions & 0 deletions doc/z-chap4.xml
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@
<#Include Label="DigraphLongestSimpleCircuit">
<#Include Label="DigraphAllUndirectedSimpleCircuits">
<#Include Label="DigraphAllChordlessCycles">
<#Include Label="FacialWalks">
<#Include Label="DigraphLayers">
<#Include Label="DigraphDegeneracy">
<#Include Label="DigraphDegeneracyOrdering">
Expand All @@ -105,6 +106,7 @@
<#Include Label="PlanarEmbedding">
<#Include Label="OuterPlanarEmbedding">
<#Include Label="SubdigraphHomeomorphicToK">
<#Include Label="DualPlanarGraph">
</Section>

<Section><Heading>Hashing</Heading>
Expand Down
1 change: 1 addition & 0 deletions gap/attr.gd
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ DeclareAttribute("DigraphAllSimpleCircuits", IsDigraph);
DeclareAttribute("DigraphLongestSimpleCircuit", IsDigraph);
DeclareAttribute("DigraphAllUndirectedSimpleCircuits", IsDigraph);
DeclareAttribute("DigraphAllChordlessCycles", IsDigraph);
DeclareOperation("FacialWalks", [IsDigraph, IsList]);
DeclareAttribute("HamiltonianPath", IsDigraph);
DeclareAttribute("DigraphPeriod", IsDigraph);
DeclareAttribute("DigraphLoops", IsDigraph);
Expand Down
74 changes: 74 additions & 0 deletions gap/attr.gi
Original file line number Diff line number Diff line change
Expand Up @@ -1653,6 +1653,80 @@ function(D)
return C;
end);

# Compute for a given rotation system the facial walks
InstallMethod(FacialWalks, "for a digraph and a list",
[IsDigraph, IsDenseList],
function(D, rotationSystem)

local FacialWalk, facialWalks, remEdges, cycle;

if not IsEulerianDigraph(D) then
Error("the 1st argument (digraph <D>) must be Eulerian, but it is not");
fi;

if Length(rotationSystem) <> DigraphNrVertices(D) then
Error("the 2nd argument (list <rotationSystem>) is not a rotation ",
"system for the 1st argument (digraph <D>), expected a ",
"dense list of length ", DigraphNrVertices(D),
"but found dense list of length ", Length(rotationSystem));
fi;

if Difference(Union(rotationSystem), DigraphVertices(D))
<> [] then
Error("the 2nd argument (dense list <rotationSystem>) is not a rotation",
" system for the 1st argument (digraph <D>), expected the union",
" to be ", DigraphVertices(D), " but found ",
Union(rotationSystem));
fi;

# computes a facial cycles starting with the edge 'startEdge'
FacialWalk := function(rotationSystem, startEdge)
local startVertex, preVertex, actVertex, cycle, nextVertex, pos;

startVertex := startEdge[1];
actVertex := startEdge[2];
preVertex := startVertex;

cycle := [startVertex, actVertex];

nextVertex := 0; # just an initialization
while true do
pos := Position(rotationSystem[actVertex], preVertex);

if pos < Length(rotationSystem[actVertex]) then
nextVertex := rotationSystem[actVertex][pos + 1];
else
nextVertex := rotationSystem[actVertex][1];
fi;
if nextVertex <> startEdge[2] or actVertex <> startVertex then
Add(cycle, nextVertex);
Remove(remEdges, Position(remEdges, [preVertex, actVertex]));
preVertex := actVertex;
actVertex := nextVertex;
else
break;
fi;
od;
Remove(remEdges, Position(remEdges, [preVertex, startVertex]));
# Remove the last vertex, otherwise otherwise
# the start vertex is contained twice
Remove(cycle);
return cycle;
end;

D := DigraphRemoveLoops(DigraphRemoveAllMultipleEdges(
DigraphMutableCopyIfMutable(D)));

facialWalks := [];
remEdges := ShallowCopy(DigraphEdges(D));

while remEdges <> [] do
cycle := FacialWalk(rotationSystem, remEdges[1]);
Add(facialWalks, cycle);
od;
return facialWalks;
end);

# The following method 'DIGRAPHS_Bipartite' was originally written by Isabella
# Scott and then modified by FLS.
# It is the backend to IsBipartiteDigraph, Bicomponents, and DigraphColouring
Expand Down
1 change: 1 addition & 0 deletions gap/planar.gd
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ DeclareAttribute("KuratowskiOuterPlanarSubdigraph", IsDigraph);
DeclareAttribute("SubdigraphHomeomorphicToK23", IsDigraph);
DeclareAttribute("SubdigraphHomeomorphicToK4", IsDigraph);
DeclareAttribute("SubdigraphHomeomorphicToK33", IsDigraph);
DeclareAttribute("DualPlanarGraph", IsDigraph);

# Properties . . .

Expand Down
41 changes: 41 additions & 0 deletions gap/planar.gi
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,47 @@ SUBGRAPH_HOMEOMORPHIC_TO_K4);
InstallMethod(SubdigraphHomeomorphicToK33, "for a digraph", [IsDigraph],
SUBGRAPH_HOMEOMORPHIC_TO_K33);

InstallMethod(DualPlanarGraph, "for a digraph", [IsDigraph],
function(D)
local digraph, rotationSystem, facialWalks, dualEdges,
cycle1, cycle2, commonNodes, i;

if not IsPlanarDigraph(D) then
return fail;
fi;

digraph := DigraphSymmetricClosure(DigraphRemoveLoops(
DigraphRemoveAllMultipleEdges(DigraphMutableCopyIfMutable(D))));
rotationSystem := PlanarEmbedding(digraph);
facialWalks := FacialWalks(digraph, rotationSystem);

dualEdges := [];
for cycle1 in [1 .. Length(facialWalks) - 1] do
for cycle2 in [cycle1 .. Length(facialWalks)] do
if cycle1 = cycle2 then
if not IsDuplicateFree(facialWalks[cycle1]) then
Add(dualEdges, [cycle1, cycle1]);
fi;
else
commonNodes := Intersection(facialWalks[cycle1],
facialWalks[cycle2]);
if Length(commonNodes) = Length(facialWalks[cycle1]) then
for i in [1 .. Length(commonNodes)] do
Add(dualEdges, [cycle1, cycle2]);
Add(dualEdges, [cycle2, cycle1]);
od;
else
for i in [1 .. Length(commonNodes) - 1] do
Add(dualEdges, [cycle1, cycle2]);
Add(dualEdges, [cycle2, cycle1]);
od;
fi;
fi;
od;
od;
return DigraphByEdges(dualEdges);
end);

########################################################################
# 2. Properties
########################################################################
Expand Down
32 changes: 32 additions & 0 deletions tst/standard/attr.tst
Original file line number Diff line number Diff line change
Expand Up @@ -1071,6 +1071,38 @@ gap> DigraphAllUndirectedSimpleCircuits(g);
[ 4, 3, 9, 5, 7, 8, 6, 10 ], [ 4, 3, 9, 10 ], [ 5, 6, 8, 7 ],
[ 9, 5, 6, 10 ], [ 9, 5, 7, 8, 6, 10 ] ]

# FacialCycles
gap> g := Digraph([]);;
gap> rotationSy := [];;
gap> FacialWalks(g, rotationSy);
[ ]
gap> g := Digraph([[2], [1, 3], [2, 4], [3]]);;;
gap> rotationSy := [[2], [1, 3], [2, 4], [3]];;
gap> FacialWalks(g, rotationSy);
[ [ 1, 2, 3, 4, 3, 2 ] ]
gap> g := CycleDigraph(4);;
gap> planar := PlanarEmbedding(g);
[ [ 2 ], [ 3 ], [ 4 ], [ 1 ] ]
gap> FacialWalks(g, planar);
[ [ 1, 2, 3, 4 ] ]
gap> nonPlanar := [[2, 4], [1, 3], [2, 4], [1, 3]];;
gap> FacialWalks(g, nonPlanar);
[ [ 1, 2, 3, 4 ] ]
gap> g := CompleteMultipartiteDigraph([2, 2, 2]);;
gap> rotationSystem := PlanarEmbedding(g);
[ [ 3, 5, 4, 6 ], [ 6, 4, 5, 3 ], [ 6, 2, 5, 1 ], [ 1, 5, 2, 6 ],
[ 1, 3, 2, 4 ], [ 1, 4, 2, 3 ] ]
gap> FacialWalks(g, rotationSystem);
[ [ 1, 3, 6 ], [ 1, 4, 5 ], [ 1, 5, 3 ], [ 1, 6, 4 ], [ 2, 3, 5 ],
[ 2, 4, 6 ], [ 2, 5, 4 ], [ 2, 6, 3 ] ]
gap> g := Digraph([[2, 3, 4], [1, 3, 5], [1, 2, 4], [1, 3, 5], [2, 4, 6], [5, 7, 9], [6, 8, 10], [7, 9, 10], [6, 8, 10], [7, 8, 9]]);;
gap> rotationSy := PlanarEmbedding(g);
[ [ 2, 4, 3 ], [ 3, 5, 1 ], [ 1, 4, 2 ], [ 1, 5, 3 ], [ 2, 4, 6 ],
[ 5, 7, 9 ], [ 8, 10, 6 ], [ 9, 10, 7 ], [ 6, 10, 8 ], [ 7, 8, 9 ] ]
gap> FacialWalks(g, rotationSy);
[ [ 1, 2, 3 ], [ 1, 3, 4 ], [ 1, 4, 5, 6, 7, 8, 9, 6, 5, 2 ], [ 2, 5, 4, 3 ],
[ 6, 9, 10, 7 ], [ 7, 10, 8 ], [ 8, 10, 9 ] ]

# Issue #676
gap> D := Digraph([[], [3], []]);;
gap> SetDigraphVertexLabels(D, ["one", "two", "three"]);
Expand Down
20 changes: 20 additions & 0 deletions tst/standard/planar.tst
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,26 @@ gap> D := CompleteDigraph(3);
gap> KuratowskiOuterPlanarSubdigraph(D);
fail

# DualPlanarGraph
gap> DualPlanarGraph(CompleteDigraph(5));
fail
gap> D := CycleDigraph(4);
<immutable cycle digraph with 4 vertices>
gap> DualPlanarGraph(D);
<immutable multidigraph with 2 vertices, 8 edges>
gap> D := ChainDigraph(4);
<immutable chain digraph with 4 vertices>
gap> DualPlanarGraph(D);
<immutable empty digraph with 0 vertices>
gap> D := Digraph([[2, 3, 4], [1, 3, 5], [1, 2, 4], [1, 3, 5], [2, 4, 6], [5, 7, 9], [6, 8, 10], [7, 9, 10], [6, 8, 10], [7, 8, 9]]);;
gap> dualD := DualPlanarGraph(D);
<immutable multidigraph with 7 vertices, 29 edges>
gap> DigraphHasLoops(dualD);
true
gap> D := CompleteDigraph(3);;
gap> DualPlanarGraph(D);
<immutable multidigraph with 2 vertices, 6 edges>

# Kernel function boyers_planarity_check, errors
gap> IS_PLANAR(2);
Error, Digraphs: boyers_planarity_check (C): the 1st argument must be a digrap\
Expand Down

0 comments on commit c0a8e1b

Please sign in to comment.