There’s a standard genre of group theory exercise that goes like, “Given a group G acting on a set X, count the number of G-orbits in X.” The intended approach is to use Burnside’s lemma ∣G∣⋅∣X/G∣=g∈G∑∣Fix(G)∣.
For some recent work, I wanted to generate all binary relations on a finite set up to equivalence under permuting the elements of the finite set. In order to check my code, I wanted to count the number of binary relations up to equivalence. This smells like a Burnside’s-lemma problem! Today we’ll solve that problem, then spend some time talking about how to actually generate all binary relations.
Fixing notation
Let S be a finite set, and consider the set X of all binary relations on S. Let the permutation group Aut(S) act on X by relabeling the elements of S; if we view a relation R as an ∣S∣×∣S∣ 0/1-matrix MR (with ones corresponding to pairs a,b with aRb), this action simultaneously permutes the rows and columns of MR. Then ∣X/Aut(S)∣ counts the number of binary relations on a finite set up to relabeling the elements of the set.
In our problem, it’s clear how big ∣Aut(S)∣ is, so all of the work is going to come in figuring out the sizes of the fixed-point sets. A general fact that’s useful in Burnside-lemma problems is that the size of the fixed-point set is constant on conjugacy classes of G, so we can collect the summands into conjugacy classes g∈G∑∣Fix(g)∣=π∈Cl(G)∑∣π∣∣Fix(π)∣.
Since Aut(S) is a finite symmetric group, its conjugacy classes are indexed by cycle type α=(α1,α2,⋯,α∣S∣) as the cycle type ranges over all solutions in nonnegative integers to ∑i=1∣S∣iαi=∣S∣; i.e., over all integer partitions of ∣S∣. Calculating the number of permutations of a given cycle type is classical, so the only new ingredient will be calculating the size of the fixed-point set.
The grid
Here it’s helpful to draw a picture. Let’s consider the permutation on six elements (1)(23)(456) with cycle type (1,1,1,0,0,0). This permutation moves the elements of the matrix MR of a relation as shown below:
The orbits of the action of the permutation (1)(23)(456) on the matrix of a relation.
How many 0/1-matrices does this permutation fix? Well, the matrix elements along any orbit of this action should be constant, so the permutation fixes 2#orbits matrices. Okay, so how many orbits are there? Each orbit is contained in a grid cell in the picture above, so we can work cell-by-cell. The number of orbits in a cell of height h and width w is simply gcd(h,w), so that the total number of orbits of the action of a permutation with cycle type α is simply ∑i,j=1ngcd(i,j)αiαj.
Checking our work
Putting everything together and doing a tiny bit of cancellation, we get the formula ∣X/Aut(S)∣=α⊥∣S∣∑∏i=1∣S∣iαiαi!2∑i,j=1∣S∣gcd(i,j)αiαj. For ∣S∣=1,2,…, we get the sequence of counts 2,10,104,3044,291968,96928992,112282908928,458297100061728,…. This is OEIS sequence A000595, so we’ve done everything correctly.
One fun aspect of the formula above is that it’s a bunch of different powers of two divided by various integers that are definitely not powers of two, yet somehow the total is guaranteed to still be an integer. This is typical of Burnside problems. In fact, in our specific instance we could abandon the binary-relation interpretation, allow our matrices to be 0/1/2-valued, and replace 2 by 3 in the formula above, and we’d know we’d still get an integer out. The same goes for 4 or 5, so replacing 2 by x we get for each ∣S∣ a polynomial with rational coefficients that is nonetheless always integer-valued. Here are the first few polynomials for ∣S∣=1,2,…:
x,2x4+2x2,6x9+2x5+3x3,24x16+4x10+8x8+3x6+4x4,….
We can see that the maximal-degree term comes from the cycle type (∣S∣,0,…,0) and the minimal-degree term comes from the cycle type (0,…,0,1). This suggests that the number of inequivalent binary relations should grow as the intuitive guess 2n2/n!. A little bit more work is needed to make this precise since it could a priori be the case that there are a large number of slightly-lower-degree terms with smaller denominators that eventually overwhelm the leading-term asymptotics.
Indeed, in some sense this happens for x=1 where all the polynomials are equal to 1 rather than decaying as O(1/∣S∣!).
Divertimento: a Möbius trick
For large n, there’s a cute little way to speed up the summation ∑i,j=1ngcd(i,j)αiαj, which naively takes O(n2logn) time (assuming we have O(1) access to the αi). The trick is to find coefficients cd such that i,j=1∑ngcd(i,j)αiαj=d=1∑ncdi,j=1∑⌊n/d⌋αdiαdj=d=1∑ncd(i=1∑nαdi)2. We can compute all the stride-d sums ∑i=1⌊n/d⌋αdi in O(nlogn) time, so the only question left is what the coefficients cd should be. The easiest way to determine this is to match the coefficients on an arbitrary αiαj term. Letting g=gcd(i,j), we find that we need ∑d∣gcd=g. Then we can just apply Möbius inversion to get cd=k∣d∑μ(k)kd=ϕ(d), where ϕ(d) is the Euler phi function. Of course, now we need all the ϕ(d) values for 1≤d≤n, but these can be computed via a sieve-based approach in time
The time analysis here uses Mertens’ theorem that ∑p≤xp1=O(loglogx). Note that when computing the full Burnside sum we should obviously cache these values.
O(nloglogn). Hence the dominant term is the O(nlogn) computation of the stride-d sums, so we’ve shaved off a full factor of n from the computation!
Of course, in our broader context n is so small (since the number of partitions grows quickly enough to overwhelm my computer around n=40) as to make this improvement barely noticeable.
Combinatorial enumeration
What if we want to actually generate representatives of each equivalence class of binary relations on a set of size n? It’s one thing to count them; it’s another to actually generate them. The obvious and very stupid strategy is:
Enumerate all 2n2 binary relations.
For each relation, check whether it is equivalent to any relation we’ve already seen.
If not, add it to our list.
Thankfully, there is a far more practical approach, due to Read
R. C. Read, “Every one a winner, or how to avoid isomorphism search when cataloguing combinatorial configurations,” Annals of Discrete Mathematics 2 (1978), 107–120.
, that avoids equivalence testing almost entirely. Somehow the frustratingly-vague term “combinatorial enumeration” seems to have attached itself to this approach (Read uses the far-better phrase “orderly algorithm”), which is applicable to a number of problems similar to our enumerating-binary-relations focus for today.
The setup
Let us suppose that we have some set S with an isomorphism relation ≅, and we want to generate a single representative from each isomorphism class. The setup for an orderly algorithm requires several choices of auxiliary data:
a partition S=S0⨿S1⨿⋯ of S depending on some integer parameter q≥0;
an ordering ≺ on S;
a subset C⊂S of canonical elements of S, such that each isomorphism class of (S,≅) has a unique representative in C; and
an augmenting operationA:Sq⟶Sq+1∗ mapping elements of Sq to ≺-ordered lists of elements of Sq+1.
We should also have some algorithm to determine whether an element of S is canonical, as well as some way of constructing the ordered list L0=(C∩S0,≺). The data will need to satisfy two conditions–canonical covering and weak monotonicity–which we discuss below. First, though, let us describe how an orderly algorithm proceeds.
The central idea is that we construct the ordered list Lq+1=(C∩Sq+1,≺) from the ordered list Lq and the augmentation operation A; we can then recover C=L0⨿L1⋯. To build Lq+1, we begin with the empty list. For each element e∈Lq, we form the list A(e). Then, for each f∈A(e), we append f to Lq iff f is greater than the ≺-maximal element of Lq
Taking this to be vacuously true when Lq+1 is empty.
and f∈C. The key point is that while we should store Lq+1 somewhere as we are building it up
Even if we intend to consume Lq+1 towards some other end goal, we do need to store it in order to generate Lq+2.
, we never need to search over Lq+1; we simply have to store the current maximal element instead.
The conditions
Let’s now turn to what conditions we should require of our data for this orderly algorithm to work. First, if all elements of C∩Sq+1 are going to appear in Lq+1, then each of them must be generated in some augmentation step. This is what we call canonical covering: namely, that we have C∩Sq+1⊆e∈C∩Sq⋃A(e).
This is not enough to ensure that all canonical elements of Sq+1 actually appear in Lq+1, however. We must also choose to append them, which means they must be maximal in the ≺-order at the first time they are generated in an augmentation step. Tracing this backwards gives us weak monotonicity: namely, if e,f∈C∩Sq+1 with e<f, then we must have
min{x∈C∩Sq:e∈A(x)}⪯min{x∈C∩Sq:f∈A(x)}.
It turns out that these two conditions are enough to ensure that the orderly algorithm generates all elements of C. The only argument that needs to be made is that if an element of Sq+1 is not appended to Lq+1 the first time it is generated, then it cannot be appended any subsequent time, which is an easy monovariance argument.
Binary relations
Let us now turn to the specific example of binary relations, following the construction of Read.
Note that Read uses the language of digraphs rather than binary relations.
For a binary relation R on {1,2,⋯,n}, define code(R)=1≤i,j≤n∑1[iRj]2n(n−i)+(n−j). This code is simply the 0/1-matrix MR read out row-by-row in binary. As discussed above, we need to fix notions for the parameter q, the ordering ≺, the canonical set C, and the augmentation operation A. We take these in a somewhat-haphazard order:
We define the ordering ≺ to be the reverse of the usual integer ordering on codes; in other words R1≺R2 iff code(R1)>code(R2).
We define a relation R to be canonical if code(R) is ≺-minimal (i.e., maximal under the usual ordering on integers) among all relations on {1,2,⋯,n} in the Sn-orbit containing R.
Note that canonicity-testing will still be somewhat expensive, but we won’t have to run it very often. In addition, in this setup we can stop early if we observe some permutation σ such that code(R)>code(σ(R)). We could also do this early stopping in a slightly-less-naive generation algorithm than that above: namely, enumerate all 2n2 binary relations, discarding any whose codes are not ≺-minimal in their Sn orbit; the advantage of our orderly algorithm is that we have to test far fewer candidates for canonicity.
An initial guess might be that we will filter binary relations by the size of the underlying set. While this decomposition is appropriate in some contexts
See for instance §5 of Read on rooted trees.
, we will instead fix the size n of the underlying set and use an alternate definition for q:
We define q to be the number of ones in the 0/1-matrix MR of the relation R.
Hence q will vary over the range 0≤q≤n2, though by taking complements we may further restrict q≤n2/2. This makes L0 very easy to construct indeed.
Augmentation must then increase the number of ones in the matrix of a relation somehow. Read gives two augmentation operations that will work: a relatively naive version, and a more efficient version.
We define the augmentation operation A0(R) as follows: as a set, A0(R) consists of all relations whose codes are formed by taking code(R) and changing one of the digits that is a zero to a one.
We define the augmentation operation A1(R) as follows: as a set, A1(R) consists of all relations whose codes are formed by taking code(R) and changing one of the trailing zeroes to a one.
Both augmentation operations produce ordered lists by using the ≺ ordering on relations. We now need to check that both augmentation operations are canonical-covering and weakly-monotone. We’ll do that below; for now, let’s work a couple explicit examples.
Two elements
Let’s work through generating all 10 equivalence classes of binary relations on {1,2}, using both augmentation operations. Given a relation R on {1,2} with code r11r12r21r22, the non-identity element σ of S2 sends R to the relation with code rσ(1)σ(1)rσ(1)σ(2)rσ(2)σ(1)rσ(2)σ(2)=r22r21r12r11. In other words, it reverses the code. So the canonicity check is quite simple: is the code greater than its reversal?
We begin with the A0 case, and have initially L0=[0000].
We have A0(0000)=[1000≺0100≺0010≺0001]. Initially we have L1=[]. 1000 is canonical, so gets appended to L1. We have 1000≺0100 and 0100 is canonical, so it also gets appended to L1. We have 0100≺0010 and 0100≺0001, but neither are canonical, so we end up with L1=[1000≺0100].
We have A0(1000)=[1100≺1010≺1001] and A0(0100)=[1100≺0110≺0101]. We initialize L2=[] and begin with A0(1000). All three elements are canonical, so all get appended to L2 in that order, with 1001 now maximal. We then proceed to A0(0100). We have 1100≺1001, so we skip 1100–note that we didn’t need to search L2 to see whether we had previously seen 1100. Moving on, we have 1001≺0110 and 0110 is canonical, so we append it. We have 0110≺0101, but 0101 is not canonical, so we end up with L2=[1100≺1010≺1001≺0110].
We have A0(1100)=[1110≺1101], A0(1010)=[1110≺1011], A0(1001)=[1101≺1011], and A0(0110)=[1110≺0111]. We initialize L3=[] and process A0(1100): 1110 is canonical (its reversal is 0111), so it gets appended; then 1101 is canonical (reversal 1011) and 1110≺1101, so it also gets appended, leaving 1101 as maximal. For A0(1010), we observe 1110≺1101 (so we would skip 1110 even if it weren’t a duplicate), while 1011 is ≻1101 but is not canonical. For A0(1001), 1101 equals the current maximum and so gets skipped, while 1011 is non-canonical. For A0(0110), 1110 is not ≻1101, and 0111 is not canonical. Hence L3=[1110≺1101].
We have A0(1110)=[1111] and A0(1101)=[1111]. The relation 1111 is canonical, so when processing A0(1110) we append it, making it the current maximum. When processing A0(1101), it obviously gets skipped, so that L4=[1111].
Note that we could have stopped after generating L2, since in the binary-relation setting we can obtain (non-canonical) representatives of the remaining equivalence classes by taking the elements of L1 and L0 and swapping all zeroes and ones.
For the A1 case, we will start at the same point with L0=[0000]. Since all of the zeroes of 0000 are trailing, we have A1(0000)=A0(0000)=[1000≺0100≺0010≺0001]. Hence L1=[1000≺0100] as above, but the differences start at L2.
All three zeroes of 1000 are trailing, so A1(1000)=[1100≺1010≺1001], but only positions 3 and 4 of 0100 are trailing, so A1(0100)=[0110≺0101]. Processing A1(1000): 1100, 1010, and 1001 are all canonical (their reversals 0011, 0101, and 1001 are each ≤ the originals), and the list is already in ≺-order, so all three get appended and the maximum becomes 1001. Processing A1(0100): 0110 is canonical (self-reverse) and ≻1001, so it gets appended; 0101 is then ≻0110 but is not canonical. Hence L2=[1100≺1010≺1001≺0110]. This matches the A0 output, but notice that A1 never generated 1100 a second time.
We have A1(1100)=[1110≺1101] (both remaining zeroes are trailing), A1(1010)=[1011] (only position 4 is trailing), A1(1001)=[] (no trailing zeroes), and A1(0110)=[0111] (only position 4 is trailing). Processing A1(1100): both 1110 and 1101 are canonical, so both get appended, leaving 1101 maximal. Processing A1(1010): 1011 is ≻-greater than the maximum but is not canonical. A1(1001) is empty. Processing A1(0110): 0111 is not canonical. Hence L3=[1110≺1101].
We have A1(1110)=[1111] (only position 4 is trailing) and A1(1101)=[] (no trailing zeroes). Processing A1(1110): 1111 is canonical, append. Hence L4=[1111].
Notice that the A1 traces contain no “wasted” candidates that get skipped due to duplication with an earlier augmentation; this is the sense in which A1 is more efficient than A0.
Property checking
Let us now verify that the A0 augmentation is canonical-covering and weakly-monotone, following the arguments in §2 of Read. The corresponding arguments for A1 are similar in spirit but noticeably more intricate, and we direct the interested reader to Read’s paper.
For any σ∈Sn, write πσ∈Sn2 for the permutation on code positions induced by the simultaneous row-and-column action of σ on matrices, so that code(σR)p=code(R)πσ(p) for any relation R on {1,…,n} and any code position p. The key observation is that if two relations Y and X differ only at one code position k, then σY and σX differ only at the position p∗:=πσ−1(k).
Canonical covering
Let Y∈C∩Sq+1, and let k be the position of the rightmost 1 in code(Y). Let X be the relation obtained by flipping this 1 to a 0, so that X∈Sq and Y∈A0(X). We claim that X is canonical; this suffices for canonical covering, since we have exhibited Y as the image of a canonical element of Sq under A0.
Suppose for contradiction that some σ∈Sn satisfies code(σX)>code(X), and let j be the first position where they disagree, so that (σX)j=1 and Xj=0. Since X’s q ones are all in positions 1,…,k−1, and σX has the same weight q, we must have j<k: for if j≥k, then σX agrees with X on all q of X’s 1s, accounting for its entire weight, so (σX)j could not be 1.
Now compare σY to Y, recalling that they differ only at code position p∗:=πσ−1(k) where (σY)p∗=1 and (σX)p∗=0. Note p∗=j, since (σX)j=1=0=(σX)p∗. Consider two sub-cases.
If p∗>j, then at positions 1,…,j−1 we have σY=σX=X=Y (with the first equality because p∗>j−1 and the third because j−1<k), and at position j we have (σY)j=(σX)j=1 while Yj=Xj=0. Hence code(σY)>code(Y).
If p∗<j, then at positions 1,…,p∗−1 we have σY=σX=X=Y again, and at position p∗ we have (σY)p∗=1 while Yp∗=Xp∗=(σX)p∗=0 (the last equality because p∗<j). Hence again code(σY)>code(Y).
Either way, σ violates the canonicity of Y. This is a contradiction, so X must have been canonical after all.
Weak monotonicity. Let e,f∈C∩Sq+1 with e≺f, i.e., code(e)>code(f). We must show that the ≺-minimum canonical predecessor of e under A0 is ⪯ the ≺-minimum canonical predecessor of f under A0.
For any canonical R∈Sq+1, the canonical covering argument shows that flipping the rightmost 1 of R produces a canonical element of Sq. This flip also produces the element with the largest possible code (since we are subtracting the smallest power of 2), so it is the ≺-minimum canonical predecessor of R under A0. Write Xe and Xf for the rightmost-1 flips of e and f, and ke and kf for the positions of their rightmost 1s; we must show code(Xe)≥code(Xf).
If ke≥kf, then 2n2−ke≤2n2−kf, so
code(Xe)=code(e)−2n2−ke≥code(e)−2n2−kf>code(f)−2n2−kf=code(Xf),
and we are done. Assume instead that ke<kf, and compare the prefixes e[1…ke−1] and f[1…ke−1].
First, we rule out e[1…ke−1]<f[1…ke−1]: if this held, then at the first differing position j<ke we would have ej=0 and fj=1, and since the largest possible contribution of positions >j to code(e) is 2n2−j−1 (strictly less than the 2n2−j contributed by fj), we would have code(e)<code(f), a contradiction.
If e[1…ke−1]>f[1…ke−1], then Xe and Xf first disagree at the first position j<ke where e and f disagree, where (Xe)j=ej=1 and (Xf)j=fj=0; hence code(Xe)>code(Xf).
Otherwise e and f have identical prefixes, containing all q of e’s 1s other than the rightmost. Since f has total weight q+1 and a 1 at kf>ke, its prefix already accounts for q of its 1s, so f has no 1s in positions ke,…,kf−1. Then Xe and Xf both have 1s at exactly the positions of the common prefix, and nowhere else, so Xe=Xf and code(Xe)=code(Xf).
In all cases code(Xe)≥code(Xf), as required.