From c0a8e1b2491ba6586d236ec4920eb4449e900aeb Mon Sep 17 00:00:00 2001 From: MeikeWeiss <82507837+MeikeWeiss@users.noreply.github.com> Date: Tue, 12 Nov 2024 13:27:12 +0100 Subject: [PATCH] Implementation of facial walks and dual graphs (#713) * added FacialWalks * added dual graph * Suggestions for improvement built in * Update attr.gi --------- Co-authored-by: James Mitchell --- doc/attr.xml | 49 +++++++++++++++++++++++++++ doc/planar.xml | 31 +++++++++++++++++ doc/z-chap4.xml | 2 ++ gap/attr.gd | 1 + gap/attr.gi | 74 +++++++++++++++++++++++++++++++++++++++++ gap/planar.gd | 1 + gap/planar.gi | 41 +++++++++++++++++++++++ tst/standard/attr.tst | 32 ++++++++++++++++++ tst/standard/planar.tst | 20 +++++++++++ 9 files changed, 251 insertions(+) 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\