From bea14d006ddf8fa3ab8856713d00a0bb91ec3e67 Mon Sep 17 00:00:00 2001 From: Joseph Edwards Date: Wed, 4 Sep 2024 16:58:03 +0100 Subject: [PATCH] Fix edge cases --- doc/attr.xml | 28 ++++++++++++++++--- doc/z-chap4.xml | 1 + gap/attr.gd | 1 + gap/attr.gi | 3 ++ gap/planar.gi | 31 ++++++++++----------- src/digraphs.c | 29 ++++++++++++++++++++ src/digraphs.h | 1 + src/planar.c | 61 ++++++++++++++++++++++++++++++----------- tst/standard/planar.tst | 4 +-- 9 files changed, 120 insertions(+), 39 deletions(-) diff --git a/doc/attr.xml b/doc/attr.xml index e24d817c4..d07ec2ab7 100644 --- a/doc/attr.xml +++ b/doc/attr.xml @@ -142,11 +142,10 @@ gap> DigraphNrEdges(D); An integer. - Returns the number of pairs of adjacent vertices of the digraph digraph. This - function is agnostic to the direction of an edge, so if digraph contains both the edges (a, - b) and (b, a), this only counts as one adjacency. The following equality holds for + Returns the number of sets \{u, v\} of vertices of the digraph digraph, such that + either (u, v) or (v, u) is an edge. The following equality holds for any digraph D with no multiple edges: DigraphNrAdjacencies(D) * 2 - DigraphNrLoops(D) - = DigraphNrEdges(DigraphSymmetricClosure(D)) + = DigraphNrEdges(DigraphSymmetricClosure(D)). gr := Digraph([ > [1, 3, 4, 5], [1, 2, 3, 5], [2, 4, 5], [2, 4, 5], [1]]);; @@ -160,6 +159,27 @@ true <#/GAPDoc> +<#GAPDoc Label="DigraphNrAdjacenciesWithoutLoops"> + + + An integer. + Returns the number of sets \{u, v\} of vertices of the digraph digraph, such that + u \neq v and either (u, v) or (v, u) is an edge. The following equality holds for + any digraph D with no multiple edges: DigraphNrAdjacencies(D) * 2 + DigraphNrLoops(D) + = DigraphNrEdges(DigraphSymmetricClosure(D)). + gr := Digraph([ +> [1, 3, 4, 5], [1, 2, 3, 5], [2, 4, 5], [2, 4, 5], [1]]);; +gap> DigraphNrAdjacencies(gr); +10 +gap> DigraphNrAdjacencies(gr) * 2 + DigraphNrLoops(gr) = +> DigraphNrEdges(DigraphSymmetricClosure(gr)); +true +]]> + + +<#/GAPDoc> + <#GAPDoc Label="DigraphNrLoops"> diff --git a/doc/z-chap4.xml b/doc/z-chap4.xml index 79113ce92..2573226f4 100644 --- a/doc/z-chap4.xml +++ b/doc/z-chap4.xml @@ -5,6 +5,7 @@ <#Include Label="DigraphEdges"> <#Include Label="DigraphNrEdges"> <#Include Label="DigraphNrAdjacencies"> + <#Include Label="DigraphNrAdjacenciesWithoutLoops"> <#Include Label="DigraphNrLoops"> <#Include Label="DigraphSinks"> <#Include Label="DigraphSources"> diff --git a/gap/attr.gd b/gap/attr.gd index cc011d274..dfdc4ec80 100644 --- a/gap/attr.gd +++ b/gap/attr.gd @@ -15,6 +15,7 @@ DeclareAttribute("DigraphNrVertices", IsDigraph); DeclareAttribute("DigraphEdges", IsDigraph); DeclareAttribute("DigraphNrEdges", IsDigraph); DeclareAttribute("DigraphNrAdjacencies", IsDigraph); +DeclareAttribute("DigraphNrAdjacenciesWithoutLoops", IsDigraph); DeclareAttribute("DigraphNrLoops", IsDigraph); DeclareAttribute("DigraphHash", IsDigraph); diff --git a/gap/attr.gi b/gap/attr.gi index 3078cdd72..0de44b0a9 100644 --- a/gap/attr.gi +++ b/gap/attr.gi @@ -704,6 +704,9 @@ end); InstallMethod(DigraphNrAdjacencies, "for a digraph", [IsDigraphByOutNeighboursRep], DIGRAPH_NRADJACENCIES); +InstallMethod(DigraphNrAdjacenciesWithoutLoops, "for a digraph", +[IsDigraphByOutNeighboursRep], DIGRAPH_NRADJACENCIESWITHOUTLOOPS); + InstallMethod(DigraphNrLoops, "for a digraph by out-neighbours", [IsDigraphByOutNeighboursRep], diff --git a/gap/planar.gi b/gap/planar.gi index 0d7f85cd0..9a8fbbfb2 100644 --- a/gap/planar.gi +++ b/gap/planar.gi @@ -30,23 +30,21 @@ # 1. Attributes ######################################################################## -HasTrivialRotaionSystem := +BindGlobal("DIGRAPHS_HasTrivialRotationSystem", function(D) if IsMultiDigraph(D) then ErrorNoReturn("expected a digraph with no multiple edges"); - fi; - if HasIsPlanarDigraph(D) and not IsPlanarDigraph(D) then + elif HasIsPlanarDigraph(D) and not IsPlanarDigraph(D) then return false; - fi; - if DigraphNrVertices(D) < 3 then + elif DigraphNrVertices(D) < 3 then return true; fi; - return DigraphNrAdjacencies(D) = DigraphNrLoops(D); -end; + return DigraphNrAdjacenciesWithoutLoops(D) = 0; +end); InstallMethod(PlanarEmbedding, "for a digraph", [IsDigraph], function(D) - if HasTrivialRotaionSystem(D) then; + if DIGRAPHS_HasTrivialRotationSystem(D) then; return OutNeighbors(D); fi; return PLANAR_EMBEDDING(D); @@ -54,7 +52,7 @@ end); InstallMethod(OuterPlanarEmbedding, "for a digraph", [IsDigraph], function(D) - if HasTrivialRotaionSystem(D) then; + if DIGRAPHS_HasTrivialRotationSystem(D) then; return OutNeighbors(D); fi; return OUTER_PLANAR_EMBEDDING(D); @@ -81,12 +79,12 @@ SUBGRAPH_HOMEOMORPHIC_TO_K33); InstallMethod(IsPlanarDigraph, "for a digraph", [IsDigraph], function(D) - local v, n_antisymmetric_edges; + local v, e; v := DigraphNrVertices(D); - n_antisymmetric_edges := DigraphNrAdjacencies(D) - DigraphNrLoops(D); - if v < 5 or n_antisymmetric_edges < 9 then + e := DigraphNrAdjacenciesWithoutLoops(D); + if v < 5 or e < 9 then return true; - elif (IsConnectedDigraph(D) and n_antisymmetric_edges > 3 * v - 6) + elif (IsConnectedDigraph(D) and e > 3 * v - 6) or (HasChromaticNumber(D) and ChromaticNumber(D) > 4) then return false; fi; @@ -95,14 +93,13 @@ end); InstallMethod(IsOuterPlanarDigraph, "for a digraph", [IsDigraph], function(D) - local v, n_antisymmetric_edges; + local v, e; if HasIsPlanarDigraph(D) and not IsPlanarDigraph(D) then return false; fi; v := DigraphNrVertices(D); - n_antisymmetric_edges := DigraphNrAdjacencies(D) - DigraphNrLoops(D); - - if v < 4 or n_antisymmetric_edges < 6 then + e := DigraphNrAdjacenciesWithoutLoops(D); + if v < 4 or e < 6 then return true; elif HasChromaticNumber(D) and ChromaticNumber(D) > 3 then # Outer planar graphs are 3-colourable diff --git a/src/digraphs.c b/src/digraphs.c index 90183ccef..6697ad613 100644 --- a/src/digraphs.c +++ b/src/digraphs.c @@ -166,6 +166,34 @@ static Obj FuncDIGRAPH_NRADJACENCIES(Obj self, Obj D) { return INTOBJ_INT(DigraphNrAdjacencies(D)); } +Int DigraphNrAdjacenciesWithoutLoops(Obj D) { + Int nr = 0; + if (IsbPRec(D, RNamName("DigraphNrAdjacenciesWithoutLoops"))) { + return INT_INTOBJ(ElmPRec(D, RNamName("DigraphNrAdjacenciesWithoutLoops"))); + } else { + Obj const out = FuncOutNeighbours(0L, D); + for (Int v = 1; v <= LEN_LIST(out); ++v) { + Obj const out_v = ELM_LIST(out, v); + for (Int w = 1; w <= LEN_LIST(out_v); ++w) { + Int u = INT_INTOBJ(ELM_LIST(out_v, w)); + if (v < u + || CALL_3ARGS(IsDigraphEdge, D, INTOBJ_INT(u), INTOBJ_INT(v)) + == False) { + ++nr; + } + } + } + } + if (IsAttributeStoringRep(D)) { + AssPRec(D, RNamName("DigraphNrAdjacenciesWithoutLoops"), INTOBJ_INT(nr)); + } + return nr; +} + +static Obj FuncDIGRAPH_NRADJACENCIESWITHOUTLOOPS(Obj self, Obj D) { + return INTOBJ_INT(DigraphNrAdjacenciesWithoutLoops(D)); +} + /**************************************************************************** ** *F FuncGABOW_SCC @@ -2142,6 +2170,7 @@ FuncMULTIDIGRAPH_CANONICAL_LABELLING(Obj self, Obj digraph, Obj colours) { static StructGVarFunc GVarFuncs[] = { GVAR_FUNC(DIGRAPH_NREDGES, 1, "digraph"), GVAR_FUNC(DIGRAPH_NRADJACENCIES, 1, "digraph"), + GVAR_FUNC(DIGRAPH_NRADJACENCIESWITHOUTLOOPS, 1, "digraph"), GVAR_FUNC(GABOW_SCC, 1, "adj"), GVAR_FUNC(DIGRAPH_CONNECTED_COMPONENTS, 1, "digraph"), GVAR_FUNC(IS_ACYCLIC_DIGRAPH, 1, "adj"), diff --git a/src/digraphs.h b/src/digraphs.h index 4017a16a3..a5a2708e3 100644 --- a/src/digraphs.h +++ b/src/digraphs.h @@ -25,6 +25,7 @@ Obj FuncADJACENCY_MATRIX(Obj self, Obj D); Int DigraphNrEdges(Obj digraph); Int DigraphNrAdjacencies(Obj digraph); +Int DigraphNrAdjacenciesWithoutLoops(Obj digraph); Obj DigraphSource(Obj digraph); Obj DigraphRange(Obj digraph); diff --git a/src/planar.c b/src/planar.c index 13559f753..7d3233c5f 100644 --- a/src/planar.c +++ b/src/planar.c @@ -98,6 +98,28 @@ Obj FuncSUBGRAPH_HOMEOMORPHIC_TO_K4(Obj self, Obj digraph) { // The implementation of the main functions in this file. +Obj trivial_planarity_output(Int V, bool krtwsk) { + Obj res; + if (krtwsk) { + Obj subgraph = NEW_PLIST_IMM(T_PLIST, V); + SET_LEN_PLIST(subgraph, V); + for (int i = 1; i <= V; ++i) { + Obj list = NEW_PLIST_IMM(T_PLIST, 0); + SET_LEN_PLIST(list, 0); + SET_ELM_PLIST(subgraph, i, list); + CHANGED_BAG(subgraph); + } + res = NEW_PLIST_IMM(T_PLIST, 2); + SET_LEN_PLIST(res, 2); + SET_ELM_PLIST(res, 1, True); + SET_ELM_PLIST(res, 2, subgraph); + CHANGED_BAG(res); + } else { + res = True; + } + return res; +} + // This function only accepts digraphs without multiple edges Obj boyers_planarity_check(Obj digraph, int flags, bool krtwsk) { @@ -115,19 +137,15 @@ Obj boyers_planarity_check(Obj digraph, int flags, bool krtwsk) { if (CALL_1ARGS(IsMultiDigraph, digraph) == True) { ErrorQuit("expected a digraph without multiple edges!", 0L, 0L); } - Obj const out = FuncOutNeighbours(0L, digraph); - Int V = DigraphNrVertices(digraph); - Int E = 0; - for (Int v = 1; v <= LEN_LIST(out); ++v) { - Obj const out_v = ELM_LIST(out, v); - for (Int w = 1; w <= LEN_LIST(out_v); ++w) { - Int u = INT_INTOBJ(ELM_LIST(out_v, w)); - if (v < u - || CALL_3ARGS(IsDigraphEdge, digraph, INTOBJ_INT(u), INTOBJ_INT(v)) - == False) { - ++E; - } - } + + Int V = DigraphNrVertices(digraph); + if (V == 0) { + return trivial_planarity_output(0, krtwsk); + } + + Int E = DigraphNrAdjacenciesWithoutLoops(digraph); + if (E == 0) { + return trivial_planarity_output(V, krtwsk); } if (V > INT_MAX) { // Cannot currently test this, it might always be true, depending on the @@ -150,10 +168,16 @@ Obj boyers_planarity_check(Obj digraph, int flags, bool krtwsk) { if (gp_InitGraph(theGraph, V) != OK) { gp_Free(&theGraph); - return Fail; + ErrorQuit("Digraphs: boyers_planarity_check (C): invalid number of nodes!", + 0L, + 0L); + return 0L; } else if (gp_EnsureArcCapacity(theGraph, 2 * E) != OK) { gp_Free(&theGraph); - return Fail; + ErrorQuit("Digraphs: boyers_planarity_check (C): invalid number of edges!", + 0L, + 0L); + return 0L; } switch (flags) { @@ -170,8 +194,10 @@ Obj boyers_planarity_check(Obj digraph, int flags, bool krtwsk) { break; } - int status; + int status; + Obj const out = FuncOutNeighbours(0L, digraph); + // Construct the antisymmetric digraph with no loops for (Int v = 1; v <= LEN_LIST(out); ++v) { DIGRAPHS_ASSERT(gp_VertexInRange(theGraph, v)); gp_SetVertexIndex(theGraph, v, v); @@ -196,6 +222,7 @@ Obj boyers_planarity_check(Obj digraph, int flags, bool krtwsk) { } } } + status = gp_Embed(theGraph, flags); if (status == NOTOK) { // Cannot currently test this, i.e. it shouldn't happen (and @@ -203,6 +230,8 @@ Obj boyers_planarity_check(Obj digraph, int flags, bool krtwsk) { gp_Free(&theGraph); ErrorQuit("Digraphs: boyers_planarity_check (C): status is not ok", 0L, 0L); } + + // Construct the return value Obj res; if (krtwsk) { // Kuratowski subgraph isolator diff --git a/tst/standard/planar.tst b/tst/standard/planar.tst index c8f4c8ecf..b0d08f622 100644 --- a/tst/standard/planar.tst +++ b/tst/standard/planar.tst @@ -235,9 +235,9 @@ gap> IS_PLANAR(2); Error, Digraphs: boyers_planarity_check (C): the 1st argument must be a digrap\ h, not integer gap> IS_PLANAR(NullDigraph(0)); -fail +true gap> IS_PLANAR(NullDigraph(70000)); -fail +true gap> IsPlanarDigraph(NullDigraph(70000)); true gap> IS_PLANAR(CompleteDigraph(2));