diff --git a/doc/attr.xml b/doc/attr.xml
index 7eecc5721..011f74628 100644
--- a/doc/attr.xml
+++ b/doc/attr.xml
@@ -1495,6 +1495,55 @@ gap> DigraphAllChordlessCycles(D);
<#/GAPDoc>
+<#GAPDoc Label="FacialWalks">
+
+
+ A list of lists of vertices.
+
+ If digraph is an Eulerian digraph and list is a rotation system of digraph,
+ then FacialWalks returns a list of the facial walks in digraph.
+
+ A rotation system defines for each vertex the ordering of the out-neighbours.
+ For example, the method computes for a
+ planar digraph D the rotation system of a planar embedding of D.
+ The facial walks of digraph are closed walks and they are defined by the rotation system list.
+ They describe the boundaries of the faces of the embedding of digraph given
+ by the rotation system list.
+
+ The operation FacialWalks ignores
+ multiple edges and loops.
+ Here are some examples for planar embeddings:
+ 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 ] ]
+]]>
+ Here is an example of a non-planar digraph with a corresponding rotation system:
+ 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 ] ]
+]]>
+
+
+<#/GAPDoc>
+
<#GAPDoc Label="HamiltonianPath">
diff --git a/doc/planar.xml b/doc/planar.xml
index 6c9e44d5c..fae0f411e 100644
--- a/doc/planar.xml
+++ b/doc/planar.xml
@@ -427,3 +427,34 @@ fail
<#/GAPDoc>
+
+<#GAPDoc Label="DualPlanarGraph">
+
+
+ A digraph or fail.
+
+ If digraph is a planar digraph, then DualPlanarGraph returns the the dual graph of digraph.
+ If digraph is not planar, then fail is returned.
+
+ The dual graph of a planar digraph digraph has a vertex for each face of digraph and an edge for
+ each pair of faces that are separated by an edge from each other.
+ Vertex i of the dual graph corresponds to the facial walk at the i-th position calling
+ FacialWalks of digraph with the rotation system returned by PlanarEmbedding.
+
+ D := CompleteDigraph(4);;
+gap> dualD := DualPlanarGraph(D);
+
+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]]);
+
+gap> oct := DualPlanarGraph(cube);;
+gap> IsIsomorphicDigraph(oct, CompleteMultipartiteDigraph([2, 2, 2]));
+true
+]]>
+
+
+
+<#/GAPDoc>
\ No newline at end of file
diff --git a/doc/z-chap4.xml b/doc/z-chap4.xml
index 4e87e055b..d0c764ab1 100644
--- a/doc/z-chap4.xml
+++ b/doc/z-chap4.xml
@@ -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">
@@ -105,6 +106,7 @@
<#Include Label="PlanarEmbedding">
<#Include Label="OuterPlanarEmbedding">
<#Include Label="SubdigraphHomeomorphicToK">
+ <#Include Label="DualPlanarGraph">
Hashing
diff --git a/gap/attr.gd b/gap/attr.gd
index 7814a5f37..47faa5e31 100644
--- a/gap/attr.gd
+++ b/gap/attr.gd
@@ -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);
diff --git a/gap/attr.gi b/gap/attr.gi
index de37f4618..aed31b012 100644
--- a/gap/attr.gi
+++ b/gap/attr.gi
@@ -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 ) must be Eulerian, but it is not");
+ fi;
+
+ if Length(rotationSystem) <> DigraphNrVertices(D) then
+ Error("the 2nd argument (list ) is not a rotation ",
+ "system for the 1st argument (digraph ), 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 ) is not a rotation",
+ " system for the 1st argument (digraph ), 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
diff --git a/gap/planar.gd b/gap/planar.gd
index 6afb5f1e8..36e0726a4 100644
--- a/gap/planar.gd
+++ b/gap/planar.gd
@@ -25,6 +25,7 @@ DeclareAttribute("KuratowskiOuterPlanarSubdigraph", IsDigraph);
DeclareAttribute("SubdigraphHomeomorphicToK23", IsDigraph);
DeclareAttribute("SubdigraphHomeomorphicToK4", IsDigraph);
DeclareAttribute("SubdigraphHomeomorphicToK33", IsDigraph);
+DeclareAttribute("DualPlanarGraph", IsDigraph);
# Properties . . .
diff --git a/gap/planar.gi b/gap/planar.gi
index 8a36385ca..dbcee889e 100644
--- a/gap/planar.gi
+++ b/gap/planar.gi
@@ -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
########################################################################
diff --git a/tst/standard/attr.tst b/tst/standard/attr.tst
index 468abcd59..39a88872e 100644
--- a/tst/standard/attr.tst
+++ b/tst/standard/attr.tst
@@ -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"]);
diff --git a/tst/standard/planar.tst b/tst/standard/planar.tst
index b0d08f622..870f2d6f7 100644
--- a/tst/standard/planar.tst
+++ b/tst/standard/planar.tst
@@ -230,6 +230,26 @@ gap> D := CompleteDigraph(3);
gap> KuratowskiOuterPlanarSubdigraph(D);
fail
+# DualPlanarGraph
+gap> DualPlanarGraph(CompleteDigraph(5));
+fail
+gap> D := CycleDigraph(4);
+
+gap> DualPlanarGraph(D);
+
+gap> D := ChainDigraph(4);
+
+gap> DualPlanarGraph(D);
+
+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);
+
+gap> DigraphHasLoops(dualD);
+true
+gap> D := CompleteDigraph(3);;
+gap> DualPlanarGraph(D);
+
+
# Kernel function boyers_planarity_check, errors
gap> IS_PLANAR(2);
Error, Digraphs: boyers_planarity_check (C): the 1st argument must be a digrap\