Skip to content

Commit c968511

Browse files
committed
Unify OrbitalGraphs with _BTKit.getOrbitalList
This way, _BTKit.getOrbitalList can be removed from the BacktrackKit package, and BacktrackKit can rely on the OrbitalGraphs package instead. This reduces code duplication and gives further motivation to improve the OrbitalGraphs package.
1 parent e19b02f commit c968511

File tree

4 files changed

+117
-30
lines changed

4 files changed

+117
-30
lines changed

gap/OrbitalGraphs.gd

+2-2
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,9 @@ DeclareOperation("OrbitalGraphs", [IsPermGroup, IsHomogeneousList]);
6767
#! gap> D8 := Group([ (1,2,3,4), (2,4) ]);; StructureDescription(D8);
6868
#! "D8"
6969
#! gap> OrbitalGraphs(D8);
70-
#! [ <self-paired orbital graph of D8 on 4 vertices with base-pair (1,3), 4 arcs>
70+
#! [ <self-paired orbital graph of D8 on 4 vertices with base-pair (1,2), 8 arcs>
7171
#! , <self-paired orbital graph of D8 on 4 vertices
72-
#! with base-pair (1,2), 8 arcs> ]
72+
#! with base-pair (1,3), 4 arcs> ]
7373
#! @EndExampleSession
7474
DeclareOperation("OrbitalGraphs", [IsPermGroup, IsInt]);
7575

gap/OrbitalGraphs.gi

+87-23
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010

1111
# Permutation groups
1212

13+
# FIXME: Currently, if ValueOptions are set, then this can set the wrong value
14+
# of this attribute!
1315
InstallMethod(OrbitalGraphs, "for a permutation group", [IsPermGroup],
1416
{G} -> OrbitalGraphs(G, MovedPoints(G)));
1517

@@ -30,7 +32,8 @@ end);
3032
InstallMethod(OrbitalGraphs, "for a permutation group and a homogeneous list",
3133
[IsPermGroup, IsHomogeneousList],
3234
function(G, points)
33-
local orb, orbitsG, iorb, graph, graphlist, val, p, i, orbsizes, D,
35+
local orb, orbitsG, iorb, graph, graphlist, val, p, i, orbsizes, D, cutoff,
36+
biggestOrbit, shouldSkipOneLargeOrbit, allowLoopyGraphs, search,
3437
orbpos, innerorblist, orbitsizes, orbreps, fillRepElts, maxval, moved;
3538

3639
if IsEmpty(points) then
@@ -48,6 +51,15 @@ function(G, points)
4851
fi;
4952
moved := Intersection(points, MovedPoints(G));
5053

54+
# Optimisation from BacktrackKit
55+
# Warning: in the future, if we wish to allow supporting orbital graphs
56+
# with loops, and choosing base points that are allowed to be fixed, then
57+
# this would mean that a trivial group could have some orbital graphs to
58+
# return, and so this optimisation would be invalid.
59+
if IsTrivial(G) then
60+
return [];
61+
fi;
62+
5163
fillRepElts := function(G, orb)
5264
local val, g, reps, buildorb, gens;
5365
reps := [];
@@ -65,49 +77,101 @@ function(G, points)
6577
return reps;
6678
end;
6779

68-
orbitsG := Orbits(G, moved);
80+
# Option `cutoff`:
81+
# Have a limit for the number of edges that an orbital graph that this
82+
# function will create. The default is infinity.
83+
if IsPosInt(ValueOption("cutoff")) then
84+
cutoff := ValueOption("cutoff");
85+
else
86+
cutoff := infinity;
87+
fi;
6988

70-
# FIXME: Currently unused
71-
orbsizes := [];
72-
# FIXME: Currently unused
73-
orbpos := [];
89+
# TODO: Implement this
90+
# Option `loops`:
91+
# Create OrbitalGraphs with base pair is a loop (i.e. a repeated vertex).
92+
# People do not normally want these kinds of orbital graphs, since they
93+
# only tell you about the orbit of that point.
94+
#allowLoopyGraphs := ValueOption("loops") = true;
95+
96+
# Option `search`:
97+
# If `true`, then OrbitalGraphs will not create some orbital graphs
98+
# that are useless from the point of view of a backtrack search algorithm.
99+
search := ValueOption("search") = true;
100+
101+
# Option `skipone`
102+
# If `true`, then OrbitalGraphs will not create one of the orbital graphs
103+
# that has the largest possible number of edges. In a backtrack search,
104+
# one such orbital graph can be ignored without losing anything.
105+
shouldSkipOneLargeOrbit := search or ValueOption("skipone") = true;
106+
107+
# Make sure that the orbits of G are stably sorted, so that the resulting
108+
# list of orbital graphs for a group always comes in the same order,
109+
# with the same base pairs.
110+
orbitsG := Set(Orbits(G, moved), Set);
74111

75112
# Efficently store size of orbits of values
113+
orbsizes := [];
114+
orbpos := [];
76115
for orb in [1 .. Length(orbitsG)] do
77116
for i in orbitsG[orb] do
78117
orbsizes[i] := Length(orbitsG[orb]);
79118
orbpos[i] := orb;
80119
od;
81120
od;
82121

83-
innerorblist := List(orbitsG, o -> Orbits(Stabilizer(G, o[1]), moved));
84-
# FIXME: Currently unused
122+
# FIXME: In the following line, BacktrackKit uses [1..LargestMovedPoint(G)]
123+
# instead of moved (which omits non-moved points). Is this a problem?
124+
innerorblist := List(orbitsG, o -> Set(Orbits(Stabilizer(G, o[1]), moved), Set));
85125
orbitsizes := List([1 .. Length(orbitsG)],
86126
x -> List(innerorblist[x], y -> Length(orbitsG[x]) * Length(y)));
127+
biggestOrbit := Maximum(List(orbitsizes, Maximum));
87128

88129
graphlist := [];
89130
for i in [1 .. Length(orbitsG)] do
90131
orb := orbitsG[i];
91132
orbreps := [];
92-
93133
for iorb in innerorblist[i] do
94-
if not (Size(iorb) = 1 and orb[1] = iorb[1]) # No loopy orbitals
95-
then
96-
graph := List([1..maxval], x -> []);
97-
if IsEmpty(orbreps) then
98-
orbreps := fillRepElts(G, orb);
99-
fi;
100-
for val in orb do
101-
p := orbreps[val];
102-
graph[val] := List(iorb, x -> x^p);
103-
od;
104-
D := Digraph(graph);
105-
SetUnderlyingGroup(D, G);
106-
AddSet(graphlist, D);
134+
# Find reasons to not construct this orbital graph...
135+
# (These conditions are split to allow for future Info statements
136+
# explaining what is happening, and also to make sure we have
137+
# good code coverage, and therefore that we have good tests.)
138+
if Size(orb) * Size(iorb) > cutoff then
139+
# orbital graph is too big
140+
continue;
141+
elif search and orbpos[orb[1]] = orbpos[iorb[1]] and Size(iorb) + 1 = orbsizes[iorb[1]] then
142+
# orbit size only removed one point
143+
# TODO: Is this only relevant to 2-or-more-transitive groups, in which case there is just a unique orbital graph?
144+
continue;
145+
elif Length(iorb) = 1 and orb[1] = iorb[1] then
146+
# don't want to take the fixed point orbit
147+
continue;
148+
elif search and Size(iorb) = orbsizes[iorb[1]] then
149+
# orbit size unchanged
150+
# TODO: Give an explanation of what this means
151+
continue;
152+
elif shouldSkipOneLargeOrbit and Size(orb) * Size(iorb) = biggestOrbit then
153+
# FIXME: Is it safe putting this here, not as the first check?
154+
# largest possible; skip
155+
shouldSkipOneLargeOrbit := false;
156+
continue;
107157
fi;
158+
159+
# Construct the orbital graph as a Digraphs package object
160+
if IsEmpty(orbreps) then
161+
orbreps := fillRepElts(G, orb);
162+
fi;
163+
graph := List([1 .. maxval], x -> []);
164+
for val in orb do
165+
p := orbreps[val];
166+
graph[val] := List(iorb, x -> x ^ p);
167+
od;
168+
D := Digraph(graph);
169+
SetFilterObj(D, IsOrbitalGraphOfGroup);
170+
SetUnderlyingGroup(D, G);
171+
Add(graphlist, D);
108172
od;
109173
od;
110-
Perform(graphlist, function(x) SetFilterObj(x, IsOrbitalGraphOfGroup); end);
174+
# Note: `graphlist` should already be stably ordered because of the uses of `Set`
111175
return graphlist;
112176
end);
113177

tst/orbitalgraphs01.tst

+2-2
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ gap> START_TEST("orbitalgraphs01.tst");
1414
gap> D8 := Group([ (1,2,3,4), (2,4) ]);; StructureDescription(D8);
1515
"D8"
1616
gap> OrbitalGraphs(D8);
17-
[ <self-paired orbital graph of D8 on 4 vertices with base-pair (1,3), 4 arcs>
17+
[ <self-paired orbital graph of D8 on 4 vertices with base-pair (1,2), 8 arcs>
1818
, <self-paired orbital graph of D8 on 4 vertices
19-
with base-pair (1,2), 8 arcs> ]
19+
with base-pair (1,3), 4 arcs> ]
2020

2121
# doc/_Chapter_Orbital_graphs.xml:101-114
2222
gap> D8 := DihedralGroup(IsPermGroup, 8);

tst/orbitals.tst

+26-3
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,9 @@ gap> Length(orbitals);
2727
gap> ForAll(orbitals, D -> IsDigraph(D) and DigraphNrVertices(D) = 114);
2828
true
2929
gap> List(orbitals, DigraphNrEdges);
30-
[ 2, 2, 2, 2, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 8, 8, 8, 8, 8, 8, 8, 8,
31-
8, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100,
32-
100, 100, 200, 200, 200, 200, 2450, 2450, 2500, 2500 ]
30+
[ 2450, 100, 100, 2500, 100, 100, 200, 100, 2, 4, 100, 4, 4, 8, 100, 4, 2,
31+
100, 4, 4, 8, 2500, 100, 100, 2450, 100, 100, 200, 100, 4, 4, 100, 2, 4, 8,
32+
100, 4, 4, 100, 4, 2, 8, 200, 8, 8, 200, 8, 8, 8, 4 ]
3333
gap> Number(orbitals, IsSymmetricDigraph);
3434
8
3535
gap> Set(orbitals, x -> Minimum(DigraphEdges(x)));
@@ -107,5 +107,28 @@ gap> OrbitalGraphs(G) =
107107
> x -> OrbitalGraph(G, BasePair(x), LargestMovedPoint(G)));
108108
true
109109

110+
# OrbitalGraphs: `skipone` option
111+
gap> IsEmpty(OrbitalGraphs(SymmetricGroup(5) : skipone));
112+
true
113+
gap> Length(OrbitalGraphs(Group((1,2,3,4))));
114+
3
115+
gap> Length(OrbitalGraphs(Group((1,2,3,4)) : skipone));
116+
2
117+
118+
# OrbitalGraphs: `cutoff` option
119+
gap> Length(OrbitalGraphs(DihedralGroup(IsPermGroup, 8)));
120+
2
121+
gap> Length(OrbitalGraphs(DihedralGroup(IsPermGroup, 8) : cutoff := 4));
122+
1
123+
124+
# OrbitalGraphs: `search` option
125+
gap> IsEmpty(OrbitalGraphs(PSL(2, 5) : search));
126+
true
127+
gap> G := Group([ (1,4)(2,3), (2,3)(5,6) ]);;
128+
gap> Length(OrbitalGraphs(G, 6));
129+
9
130+
gap> IsEmpty(OrbitalGraphs(G, 6 : search));
131+
true
132+
110133
#
111134
gap> STOP_TEST("OrbitalGraphs package: orbitalgraphs.tst", 0);

0 commit comments

Comments
 (0)