From 6336d4c438ba0f77ac521be217921c5c2f9b3b2e Mon Sep 17 00:00:00 2001 From: "James D. Mitchell" Date: Wed, 13 Mar 2024 14:44:32 +0000 Subject: [PATCH] Use graphviz v1 --- PackageInfo.g | 1 + gap/tools/display.gi | 157 +++++++++++++++++++++++++++++-------------- 2 files changed, 107 insertions(+), 51 deletions(-) diff --git a/PackageInfo.g b/PackageInfo.g index 26effbfe5..448766d7d 100644 --- a/PackageInfo.g +++ b/PackageInfo.g @@ -379,6 +379,7 @@ Dependencies := rec( NeededOtherPackages := [["datastructures", ">=0.2.5"], ["digraphs", ">=1.6.2"], ["genss", ">=1.6.5"], + ["graphviz", ">=0.0.0"], ["images", ">=1.3.1"], ["IO", ">=4.5.1"], ["orb", ">=4.8.2"]], diff --git a/gap/tools/display.gi b/gap/tools/display.gi index d0b2c6e4e..b3cd0a7c3 100644 --- a/gap/tools/display.gi +++ b/gap/tools/display.gi @@ -731,6 +731,8 @@ function(D) local S, edge_colors, N, msg, legend, node_labels, offset, en, label, next, edge_func, node_func, result, i; + # TODO replace with an appropriate call to DotWordGraph + S := SemigroupOfCayleyDigraph(D); edge_colors := ["\"#00ff00\"", "\"#ff00ff\"", "\"#007fff\"", "\"#ff7f00\"", "\"#7fbf7f\"", "\"#4604ac\"", "\"#de0328\"", "\"#19801d\"", @@ -755,6 +757,8 @@ function(D) if IsOne(en[i]) then label := "ε"; else + # TODO maybe reuse some of this it seems to give better factorisations, + # than the DotRightCayleyDigraph method below label := SEMIGROUPS.WordToExtRepObj(MinimalFactorization(S, i) + offset); label := SEMIGROUPS.ExtRepObjToString(label); fi; @@ -805,88 +809,139 @@ function(D) end); InstallMethod(DotLeftCayleyDigraph, "for a semigroup", [IsSemigroup], -S -> DotString(LeftCayleyDigraph(S))); +function(S) + local label; + label := x -> SEMIGROUPS.WordToString(MinimalFactorization(S, x)); + return DotWordGraph(LeftCayleyDigraph(S), + List(S, label), + List(GeneratorsOfSemigroup(S), label)); +end); InstallMethod(DotRightCayleyDigraph, "for a semigroup", [IsSemigroup], -S -> DotString(RightCayleyDigraph(S))); +function(S) + local label; + + # TODO handle monoids + # ToExtRepObj := SEMIGROUPS.WordToExtRepObj; + # ToString := SEMIGROUPS.ExtRepObjToString; + # label := x -> ToString(ToExtRepObj(MinimalFactorization(S, x))); + # Current the above approach doesn't work becase labels aren't put into + # quotes " by default and so an error is given for this at present. + # TODO delete the next line when graphviz is fixed. + label := x -> SEMIGROUPS.WordToString(MinimalFactorization(S, x)); + return DotWordGraph(RightCayleyDigraph(S), + List(S, label), + List(GeneratorsOfSemigroup(S), label)); +end); -InstallMethod(DotWordGraph, "for a word graph and list of edge labels", -[IsDigraph, IsList, IsList], +InstallMethod(DotWordGraph, +"for a word graph, list of node labels, and list of edge labels", +[IsDigraph, IsHomogeneousList, IsHomogeneousList], function(D, node_labels, edge_labels) - local M, N, edge_colors, msg, legend, label, next, node_func, edge_func, result, v, i; + local M, N, msg, edge_colors, dot, mm, pos, targets, e, legend, subgraph, + key, key2, label, next, str, m, n, i; + + # TODO have some options, like include the legend, maybe fancy labels - if IsNullDigraph(D) then + if not DigraphHasAVertex(D) then # Word graphs must have at least one node . . . - ErrorNoReturn("TODO3"); + ErrorNoReturn("The 1st argument (a digraph) must have at least one vertex"); + elif not IsOutRegularDigraph(D) then + ErrorNoReturn("The 1st argument (a digraph) must be out-regular"); fi; M := DigraphNrVertices(D); N := Length(OutNeighboursOfVertex(D, 1)); - - if not IsOutRegularDigraph(D) then - ErrorNoReturn("TODO1"); + if Length(node_labels) <> M then + msg := "Expected the 2nd argument (a list) to have length "; + Append(msg, "{}, but found {}"); + ErrorNoReturn(StringFormatted(msg), M, Length(node_labels)); + elif not IsString(node_labels[1]) then + ErrorNoReturn("TODO"); elif Length(edge_labels) <> N then - ErrorNoReturn("TODO2"); + msg := "Expected the 3rd argument (a list) to have length "; + Append(msg, "{}, but found {}"); + ErrorNoReturn(msg, M, Length(edge_labels)); + elif not IsString(edge_labels[1]) then + ErrorNoReturn("TODO"); fi; - edge_colors := ["\"#00ff00\"", "\"#ff00ff\"", "\"#007fff\"", "\"#ff7f00\"", - "\"#7fbf7f\"", "\"#4604ac\"", "\"#de0328\"", "\"#19801d\"", - "\"#d881f5\"", "\"#00ffff\"", "\"#ffff00\"", "\"#00ff7f\"", - "\"#ad5867\"", "\"#85f610\"", "\"#84e9f5\"", "\"#f5c778\"", - "\"#207090\"", "\"#764ef3\"", "\"#7b4c00\"", "\"#0000ff\"", - "\"#b80c9a\"", "\"#601045\"", "\"#29b7c0\"", "\"#839f12"]; + # TODO longer list here + edge_colors := ["#00ff00", "#ff00ff", "#007fff", "#ff7f00", + "#7fbf7f", "#4604ac", "#de0328", "#19801d", + "#d881f5", "#00ffff", "#ffff00", "#00ff7f", + "#ad5867", "#85f610", "#84e9f5", "#f5c778", + "#207090", "#764ef3", "#7b4c00", "#0000ff", + "#b80c9a", "#601045", "#29b7c0", "#839f12"]; if N > Length(edge_colors) then - msg := Concatenation("the out-degree of every vertex in the 1st argument (a digraph) ", - "must have at most {}, found {}"); + msg := Concatenation("the out-degree of every vertex in the 1st argument ", + "(a digraph) must be at most {}, found {}"); ErrorNoReturn(StringFormatted(msg, Length(edge_colors), N)); fi; - # node_labels := ["ε"]; + dot := GV_Digraph("WordGraph"); + GV_SetAttr(dot, "node [shape=\"box\"]"); + for m in [1 .. M] do + mm := GV_AddNode(dot, m); + GV_SetAttr(mm, "label", node_labels[m]); + pos := Position(edge_labels, node_labels[m]); + if pos <> fail then + GV_SetAttr(mm, "color", edge_colors[pos]); + GV_SetAttr(mm, "style", "filled"); + fi; + od; - # for v in [2 .. M] do - # Add(node_labels, edge_labels{DigraphPath(D, 1, v)[2]}); - # od; + for m in [1 .. M] do + targets := OutNeighboursOfVertex(D, m); + for n in [1 .. N] do + e := GV_AddEdge(dot, m, targets[n]); + GV_SetAttr(e, "color", edge_colors[n]); + od; + od; - legend := "node [shape=plaintext]\nsubgraph cluster_01 {\nlabel=\"Legend\"\n"; - Append(legend, "key2 [label=<\n"); + legend := GV_AddContext(dot, "legend"); + GV_SetAttr(legend, "node [shape=plaintext]"); + subgraph := GV_AddSubgraph(legend, "legend"); + key := GV_AddNode(subgraph, "key"); + key2 := GV_AddNode(subgraph, "key2"); + label := Concatenation("<
\n"); for i in [1 .. N] do - Append(legend, + Append(label, StringFormatted(" \n", i)); od; - Append(legend, "
 
>]\n"); - Append(legend, "key [label=<\n"); + Append(label, "
>\n"); + GV_SetAttr(key2, "label", label); + # TODO remove code dupl + label := Concatenation("<\n"); for i in [1 .. N] do - label := [edge_labels[i]]; - next := "\n", i, label)); - Append(legend, next); + next := " \n", + i, + edge_labels[i])); + Append(label, next); od; + Append(label, "
{} 
{} 
>\n"); - Append(legend, ">]\n\n"); + GV_SetAttr(key, "label", label); for i in [1 .. N] do - next := StringFormatted("key:i{1}:e -> key2:i{1}:w [color={2},", - i, - edge_colors[i]); - Append(next, "constraint=false]\n"); - Append(legend, next); + e := GV_AddEdge(subgraph, + StringFormatted("key:i{}:e", i), + StringFormatted("key2:i{}:w", i)); + GV_SetAttr(e, "color", edge_colors[i]); + GV_SetAttr(e, "constraint", false); od; - Append(legend, "}\n"); - - node_func := i -> StringFormatted(" [label=\"{}\"]", node_labels[i]); - edge_func := {i, j} -> StringFormatted("[color={}]", edge_colors[j]); - result := DIGRAPHS_DotDigraph(D, [node_func], [edge_func]); - result := SplitString(result, "\n"); - result[3] := "subgraph 00 {"; - Add(result, "node [shape=box]", 3); - Add(result, legend); - Add(result, "}"); - return JoinStringsWithSeparator(result, "\n"); + str := GV_String(dot); + # TODO remove the next two lines, these work around some issues in graphviz + # str := ReplacedString(str, "--", "->"); + return Concatenation("//dot\n", str); end);