Skip to content
The Lumenary
Advanced undergraduateUndergraduate 120 min

Enumerative combinatorics

How many? Generating functions turn counting problems into algebra — from integer partitions and derangements to necklaces counted up to symmetry.

Formally verified · 11 LeanComputationally tested · 3 PythonAI-drafted · awaiting human reviewMathematically reviewed: Awaiting human review
MotivationH

Three counting puzzles

Secret gifts. Twelve colleagues in an office in Accra put their names in a bowl, and each draws one to choose a recipient for a secret gift. What is the chance that nobody draws their own name? The answer is 0.36787944…0.36787944\ldots — and it barely changes whether there are twelve people or twelve thousand. That number is 1/e1/e.

Ways to make change. In how many ways can nn be written as a sum of positive integers, ignoring order? For n=4n = 4 there are five: 44, 3+13+1, 2+22+2, 2+1+12+1+1, 1+1+1+11+1+1+1. For n=100n = 100 there are 190 569 292190\,569\,292. No simple closed formula exists, yet these numbers obey astonishing identities: Euler noticed in 1748 that writing nn as a sum of odd parts and as a sum of distinct parts can always be done in the same number of ways.

Beadwork. An artisan strings six beads, each red or white, into a necklace. Two necklaces are the same if one can be rotated into the other. How many different necklaces are there? Not 26/62^6 / 6, which is not even an integer: the honest answer, 1414, requires thinking about symmetry.

All three are problems of enumerative combinatorics, and the main tools for all three are generating functions — power series whose coefficients are the numbers we want — and group actions. This lesson builds on recurrences and generating functions, combinatorics (binomial coefficients, inclusion–exclusion) and groups (group actions and the orbit–stabiliser theorem).

IntuitionH

A clothesline for numbers

Herbert Wilf described a generating function as "a clothesline on which we hang up a sequence of numbers for display". The point is that operations on sequences that are awkward to describe become simple operations on power series. If ana_n counts ways to build something of size nn from one kind of part and bnb_n from another, then the number of ways to build something of size nn from one of each is ∑kakbn−k\sum_k a_k b_{n-k} — exactly the coefficient of xnx^n in the product A(x)B(x)A(x)B(x).

Two flavours matter. When the pieces are unlabelled — coins, parts of a partition — we use the ordinary generating function ∑anxn\sum a_n x^n. When the pieces carry labels — people, positions, elements of a set — combining two structures also involves choosing which labels go to which, a factor (nk)\binom{n}{k}. The exponential generating function ∑anxn/n!\sum a_n x^n / n! absorbs those binomial coefficients, so that labelled products again become products of series.

Symmetry is the third idea. Counting necklaces means counting orbits of the rotation group acting on colourings. Dividing the number of colourings by the size of the group would be right if every orbit had the same size — but symmetric colourings have small orbits. Burnside's lemma counts orbits correctly by averaging the number of colourings each symmetry fixes.

DefinitionsH

Definitions

Definition— ordinary and exponential generating functions

The ordinary generating function (OGF) of a sequence (an)n≥0(a_n)_{n \ge 0} is the formal power series A(x)=∑n≥0anxnA(x) = \sum_{n \ge 0} a_n x^n; its exponential generating function (EGF) is A^(x)=∑n≥0anxnn!\hat A(x) = \sum_{n \ge 0} a_n \frac{x^n}{n!}. We write [xn]A(x)[x^n] A(x) for the coefficient of xnx^n.

"Formal" means that xx is a symbol, not a number: we add and multiply series coefficientwise as polynomials, and never ask whether they converge. In the ring Q[[x]]\Q[[x]] of formal power series, a series with nonzero constant term has an inverse — for instance 11−x=1+x+x2+⋯\frac{1}{1 - x} = 1 + x + x^2 + \cdots — and infinite sums and products make sense as long as each coefficient is affected by only finitely many terms. For example ∏k≥1(1+xk)\prod_{k \ge 1} (1 + x^k) is a well-defined series, because the factor 1+xk1 + x^k cannot affect coefficients below xkx^k.

Definition— partition

A partition of nn is a way of writing nn as an unordered sum of positive integers, its parts; we list the parts in non-increasing order, λ=(λ1≥λ2≥⋯≥λℓ)\lambda = (\lambda_1 \ge \lambda_2 \ge \cdots \ge \lambda_\ell) with ∑λi=n\sum \lambda_i = n. The number of partitions of nn is p(n)p(n), with p(0)=1p(0) = 1 (the empty partition).

Definition— derangement, set partition, Stirling and Bell numbers

A derangement of a set is a permutation with no fixed point; D(n)D(n) is the number of derangements of an nn-element set. A set partition of a set SS is a collection of nonempty, pairwise disjoint blocks whose union is SS. The Stirling number of the second kind S(n,k)S(n, k) counts set partitions of {1,…,n}\{1, \dots, n\} into kk blocks, and the Bell number B(n)=∑kS(n,k)B(n) = \sum_k S(n, k) counts all of them. The Stirling number of the first kind c(n,k)c(n, k) counts permutations of {1,…,n}\{1, \dots, n\} with exactly kk cycles.

Definition— group action, orbit, stabiliser, fixed set

A group GG acts on a set XX if each g∈Gg \in G gives a map x↦g⋅xx \mapsto g \cdot x with e⋅x=xe \cdot x = x and g⋅(h⋅x)=(gh)⋅xg \cdot (h \cdot x) = (gh) \cdot x. The orbit of xx is G⋅x={g⋅x∣g∈G}G \cdot x = \{g \cdot x \mid g \in G\}, its stabiliser is Gx={g∣g⋅x=x}G_x = \{g \mid g \cdot x = x\}, and the fixed set of gg is Fix(g)={x∣g⋅x=x}\mathrm{Fix}(g) = \{x \mid g \cdot x = x\}. The orbits partition XX; X/GX/G denotes the set of orbits.

ProofH

Partitions and ordinary generating functions

Theorem— Theorem 1 (Euler)

∑n≥0p(n) xn=∏k≥111−xk.\sum_{n \ge 0} p(n)\, x^n = \prod_{k \ge 1} \frac{1}{1 - x^k}.

More generally, if PP is any set of positive integers, the partitions of nn with all parts in PP are counted by ∏k∈P11−xk\prod_{k \in P} \frac{1}{1 - x^k}, and those with distinct parts in PP by ∏k∈P(1+xk)\prod_{k \in P} (1 + x^k).

Proof

Expand each factor as a geometric series: 11−xk=∑mk≥0xkmk\frac{1}{1 - x^k} = \sum_{m_k \ge 0} x^{k m_k}, where mkm_k will be the number of parts equal to kk. Multiplying out, a term xnx^n arises once for each sequence (m1,m2,… )(m_1, m_2, \dots) of non-negative integers, all but finitely many zero, with ∑kk mk=n\sum_k k\, m_k = n. Such sequences are exactly the partitions of nn (mkm_k copies of kk). For distinct parts each mkm_k is 00 or 11, giving the factor 1+xk1 + x^k. Only the factors with k≤nk \le n affect [xn][x^n], so all the products are well defined.

∎

Theorem— Theorem 2 (Euler 1748)

For every nn, the number of partitions of nn into odd parts equals the number of partitions of nn into distinct parts.

Proof

By generating functions. By Theorem 1 we must show ∏k≥1(1+xk)=∏k odd11−xk\prod_{k \ge 1} (1 + x^k) = \prod_{k \text{ odd}} \frac{1}{1 - x^k}. Since (1+xk)(1−xk)=1−x2k(1 + x^k)(1 - x^k) = 1 - x^{2k},

∏k=1N(1+xk)=∏k=1N(1−x2k)∏k=1N(1−xk)=∏N<j≤2N, j even(1−xj)∏k≤N, k odd(1−xk),\prod_{k=1}^{N} (1 + x^k) = \frac{\prod_{k=1}^{N} (1 - x^{2k})}{\prod_{k=1}^{N} (1 - x^k)} = \frac{\prod_{N < j \le 2N,\ j \text{ even}} (1 - x^j)}{\prod_{k \le N,\ k \text{ odd}} (1 - x^k)},

because the even factors 1−xj1 - x^j with j≤Nj \le N cancel between numerator and denominator. The remaining numerator is 11 plus terms of degree greater than NN. So the two sides of the claimed identity have the same coefficients up to xNx^N, for every NN.

By a bijection (Glaisher 1883). Given a partition into odd parts in which the odd number kk occurs mm times, write mm in binary, m=2a1+2a2+⋯m = 2^{a_1} + 2^{a_2} + \cdots with distinct aia_i, and replace the mm copies of kk by the parts 2a1k,2a2k,…2^{a_1} k, 2^{a_2} k, \dots. These parts are distinct: two of them, 2ak2^a k and 2bk′2^b k' with k,k′k, k' odd, are equal only if k=k′k = k' and a=ba = b, since every positive integer is uniquely a power of 22 times an odd number. Conversely, from a partition into distinct parts, split each part d=2akd = 2^a k (kk odd) into 2a2^a copies of kk. The two maps are inverse to each other, because binary expansions are unique.

∎

Example— Glaisher's bijection for n = 8

3+3+1+1↦6+23 + 3 + 1 + 1 \mapsto 6 + 2 (two 33s merge into a 66, two 11s into a 22); 3+1+1+1+1+1↦4+3+13 + 1 + 1 + 1 + 1 + 1 \mapsto 4 + 3 + 1 (five 11s: 5=4+15 = 4 + 1); 18↦81^8 \mapsto 8. All six partitions of 88 into odd parts, and their images, are listed by the experiment below.

Example— Conjugation and Ferrers diagrams

Draw a partition as left-justified rows of dots, row ii having λi\lambda_i dots (its Ferrers diagram). Reflecting in the diagonal gives the conjugate partition λ′\lambda', whose parts are the column lengths. Conjugation is a bijection, and it proves at a glance that the number of partitions of nn into at most kk parts equals the number into parts of size at most kk.

↔
The partition 5 + 3 + 3 + 1 of 12 and its conjugate 4 + 3 + 3 + 1 + 1: rows become columns.

Partitions have no simple closed formula, but a great deal is known about them — all stated here without proof:

  • The pentagonal number theorem (Euler): ∏k≥1(1−xk)=∑j∈Z(−1)jxj(3j−1)/2=1−x−x2+x5+x7−x12−x15+⋯\prod_{k \ge 1} (1 - x^k) = \sum_{j \in \Z} (-1)^j x^{j(3j - 1)/2} = 1 - x - x^2 + x^5 + x^7 - x^{12} - x^{15} + \cdots. Multiplying by ∑p(n)xn\sum p(n) x^n gives the fast recurrence p(n)=p(n−1)+p(n−2)−p(n−5)−p(n−7)+⋯p(n) = p(n-1) + p(n-2) - p(n-5) - p(n-7) + \cdots.
  • Hardy and Ramanujan (1918): p(n)∼14n3exp⁡ ⁣(π2n/3)p(n) \sim \frac{1}{4n\sqrt 3} \exp\!\big(\pi \sqrt{2n/3}\big) as n→∞n \to \infty, proved with the circle method; Rademacher (1937) turned their series into an exact convergent formula.
  • Ramanujan's congruences: p(5m+4)≡0(mod5)p(5m + 4) \equiv 0 \pmod 5, p(7m+5)≡0(mod7)p(7m + 5) \equiv 0 \pmod 7 and p(11m+6)≡0(mod11)p(11m + 6) \equiv 0 \pmod{11}, found by Ramanujan, who proved the first two in 1919.
ProofH

Labelled structures and exponential generating functions

Theorem— Theorem 3 (the product rule for EGFs)

Let ana_n and bnb_n count two kinds of structures on an nn-element set of labels. The number cnc_n of ways to split an nn-element set into two parts S1⊔S2S_1 \sqcup S_2 and put a structure of the first kind on S1S_1 and of the second on S2S_2 is cn=∑k(nk)akbn−kc_n = \sum_k \binom{n}{k} a_k b_{n-k}, and C^(x)=A^(x) B^(x)\hat C(x) = \hat A(x)\, \hat B(x).

Proof

There are (nk)\binom{n}{k} choices of S1S_1 with ∣S1∣=k|S_1| = k, then aka_k and bn−kb_{n-k} choices of structures. And [xn] A^(x)B^(x)=∑kakk!bn−k(n−k)!=1n!∑k(nk)akbn−k[x^n]\, \hat A(x) \hat B(x) = \sum_k \frac{a_k}{k!} \frac{b_{n-k}}{(n-k)!} = \frac{1}{n!} \sum_k \binom{n}{k} a_k b_{n-k}.

∎

Theorem— Theorem 4 (the exponential formula)

Let cnc_n count "connected" structures on an nn-element set, with c0=0c_0 = 0, and let ana_n count the ways to partition an nn-element set into blocks and put a connected structure on each block (a0=1a_0 = 1). Then

A^(x)=exp⁡(C^(x))=∑k≥0C^(x)kk!.\hat A(x) = \exp\big(\hat C(x)\big) = \sum_{k \ge 0} \frac{\hat C(x)^k}{k!}.

Proof

By Theorem 3 applied k−1k - 1 times, n! [xn] C^(x)kn!\, [x^n]\, \hat C(x)^k counts ordered sequences of kk connected structures on blocks S1,…,SkS_1, \dots, S_k that partition the label set. The blocks are nonempty (since c0=0c_0 = 0) and disjoint, hence distinct, so each unordered family of kk blocks arises from exactly k!k! orderings. Thus C^(x)k/k!\hat C(x)^k / k! is the EGF of structures with exactly kk blocks, and summing over kk gives the formula. (The sum makes sense in Q[[x]]\Q[[x]] because C^(x)k\hat C(x)^k has no terms below xkx^k.)

∎

Example— Four applications of the exponential formula

  • Set partitions. A block carries exactly one (trivial) structure on any nonempty set: C^(x)=ex−1\hat C(x) = e^x - 1. So ∑nB(n)xnn!=exp⁡(ex−1)\sum_n B(n) \frac{x^n}{n!} = \exp(e^x - 1), and ∑nS(n,k)xnn!=(ex−1)kk!\sum_n S(n, k) \frac{x^n}{n!} = \frac{(e^x - 1)^k}{k!}.
  • Permutations. A permutation is a set of disjoint cycles, and there are (k−1)!(k - 1)! cyclic orders on a kk-set, so C^(x)=∑k≥1(k−1)!xkk!=∑k≥1xkk=−log⁡(1−x)\hat C(x) = \sum_{k \ge 1} (k-1)! \frac{x^k}{k!} = \sum_{k \ge 1} \frac{x^k}{k} = -\log(1 - x). Then exp⁡(C^)=11−x=∑n!xnn!\exp(\hat C) = \frac{1}{1 - x} = \sum n! \frac{x^n}{n!}: there are n!n! permutations, as there should be.
  • Derangements are permutations with no 1-cycles: C^(x)=−log⁡(1−x)−x\hat C(x) = -\log(1 - x) - x, so D^(x)=e−x1−x\hat D(x) = \frac{e^{-x}}{1 - x}.
  • Involutions (σ2=id\sigma^2 = \mathrm{id}) have cycles of length 11 and 22 only: exp⁡(x+x22)\exp\big(x + \tfrac{x^2}{2}\big).

Theorem— Theorem 5 (derangements)

D(n)=n!∑k=0n(−1)kk!.D(n) = n! \sum_{k=0}^{n} \frac{(-1)^k}{k!}.

Consequently D(n)D(n) is the integer nearest to n!/en!/e for n≥1n \ge 1, and the probability that a uniformly random permutation is a derangement tends to 1/e1/e.

Proof

By the EGF. D^(x)=e−x⋅11−x\hat D(x) = e^{-x} \cdot \frac{1}{1 - x}, and multiplying by 11−x\frac{1}{1-x} forms partial sums of coefficients: [xn] D^(x)=∑k=0n(−1)kk![x^n]\, \hat D(x) = \sum_{k=0}^{n} \frac{(-1)^k}{k!}. Multiply by n!n!.

By inclusion–exclusion. Let AiA_i be the set of permutations of {1,…,n}\{1, \dots, n\} fixing ii. A permutation fixing kk prescribed points is a permutation of the other n−kn - k, so ∣Ai1∩⋯∩Aik∣=(n−k)!|A_{i_1} \cap \cdots \cap A_{i_k}| = (n - k)!, and

D(n)=n!−∣⋃iAi∣=∑k=0n(−1)k(nk)(n−k)!=n!∑k=0n(−1)kk!.D(n) = n! - \Big|\bigcup_i A_i\Big| = \sum_{k=0}^{n} (-1)^k \binom{n}{k} (n - k)! = n! \sum_{k=0}^{n} \frac{(-1)^k}{k!}.

For the consequences: n!e−D(n)=n!∑k>n(−1)kk!\frac{n!}{e} - D(n) = n! \sum_{k > n} \frac{(-1)^k}{k!}, an alternating series with decreasing terms, so its absolute value is at most the first term, n!(n+1)!=1n+1<12\frac{n!}{(n+1)!} = \frac{1}{n+1} < \frac12 for n≥1n \ge 1. Dividing by n!n!, ∣D(n)n!−1e∣≤1(n+1)!→0\big|\frac{D(n)}{n!} - \frac1e\big| \le \frac{1}{(n+1)!} \to 0.

∎

Example— A recurrence, proved by a bijection

D(n)=(n−1)(D(n−1)+D(n−2))D(n) = (n - 1)\big(D(n-1) + D(n-2)\big) for n≥2n \ge 2. In a derangement σ\sigma of {1,…,n}\{1, \dots, n\}, let i=σ(n)i = \sigma(n): there are n−1n - 1 choices. If σ(i)=n\sigma(i) = n, then σ\sigma swaps ii and nn and deranges the other n−2n - 2 points: D(n−2)D(n - 2) possibilities. Otherwise let j=σ−1(n)≠ij = \sigma^{-1}(n) \ne i, and define τ\tau on {1,…,n−1}\{1, \dots, n-1\} by τ(j)=i\tau(j) = i and τ=σ\tau = \sigma elsewhere. Then τ\tau is a derangement (since j≠ij \ne i), and σ↦τ\sigma \mapsto \tau is a bijection onto all derangements of n−1n - 1 points: D(n−1)D(n - 1) possibilities. So D(2),D(3),⋯=1,2,9,44,265,1854,…D(2), D(3), \dots = 1, 2, 9, 44, 265, 1854, \dots — and for twelve colleagues, D(12)/12!≈0.3678794D(12)/12! \approx 0.3678794.

Theorem— Theorem 6 (cycles of permutations)

∑kc(n,k) yk=y(y+1)(y+2)⋯(y+n−1).\sum_{k} c(n, k)\, y^k = y (y + 1) (y + 2) \cdots (y + n - 1).

In particular the expected number of cycles of a uniformly random permutation of nn points is Hn=1+12+⋯+1n∼ln⁡nH_n = 1 + \frac12 + \cdots + \frac1n \sim \ln n.

Proof

Build a permutation of {1,…,n}\{1, \dots, n\} from one of {1,…,n−1}\{1, \dots, n-1\} by inserting nn: either as a new fixed point (one new cycle, a factor yy), or immediately after one of the n−1n - 1 existing elements in its cycle (n−1n - 1 ways, no new cycle). Every permutation of {1,…,n}\{1, \dots, n\} arises exactly once, so Pn(y)=(y+n−1)Pn−1(y)P_n(y) = (y + n - 1) P_{n-1}(y) with P1(y)=yP_1(y) = y. For the expectation, differentiate: Pn′(1)Pn(1)=∑j=0n−111+j=Hn\frac{P_n'(1)}{P_n(1)} = \sum_{j=0}^{n-1} \frac{1}{1 + j} = H_n.

∎

The second-kind numbers also have a clean interpretation: a function from a kk-set to an mm-set is determined by the partition of its domain into nonempty fibres and an injective labelling of the fibres, so mk=∑jS(k,j) m(m−1)⋯(m−j+1)m^k = \sum_j S(k, j)\, m(m-1)\cdots(m - j + 1).

ProofH

Counting up to symmetry

Theorem— Theorem 7 (Burnside's lemma, also called the Cauchy–Frobenius lemma)

If a finite group GG acts on a finite set XX, the number of orbits is the average number of fixed points:

∣X/G∣=1∣G∣∑g∈G∣Fix(g)∣.|X / G| = \frac{1}{|G|} \sum_{g \in G} |\mathrm{Fix}(g)|.

Proof

Count the set {(g,x)∈G×X∣g⋅x=x}\{(g, x) \in G \times X \mid g \cdot x = x\} in two ways. Grouping by gg gives ∑g∣Fix(g)∣\sum_g |\mathrm{Fix}(g)|; grouping by xx gives ∑x∣Gx∣\sum_x |G_x|. By the orbit–stabiliser theorem ∣Gx∣=∣G∣/∣G⋅x∣|G_x| = |G| / |G \cdot x|, so

∑x∈X∣Gx∣=∣G∣∑x∈X1∣G⋅x∣=∣G∣∑orbits O∑x∈O1∣O∣=∣G∣⋅∣X/G∣.\sum_{x \in X} |G_x| = |G| \sum_{x \in X} \frac{1}{|G \cdot x|} = |G| \sum_{\text{orbits } O} \sum_{x \in O} \frac{1}{|O|} = |G| \cdot |X/G|.

∎

Theorem— Corollary 8 (necklaces)

The number of necklaces of nn beads in kk colours, up to rotation, is

N(n,k)=1n∑d∣nφ(d) kn/d,N(n, k) = \frac{1}{n} \sum_{d \mid n} \varphi(d)\, k^{n/d},

where φ\varphi is Euler's totient function.

Proof

The cyclic group of rotations {ρ0,…,ρn−1}\{\rho^0, \dots, \rho^{n-1}\} acts on the knk^n colourings of the positions Z/nZ\Z/n\Z. The rotation ρr\rho^r moves position ii to i+ri + r; its cycles on positions are the cosets of the subgroup generated by rr, which has n/gcd⁡(r,n)n / \gcd(r, n) elements, so ρr\rho^r has gcd⁡(r,n)\gcd(r, n) cycles. A colouring is fixed by ρr\rho^r exactly when it is constant on each cycle, so ∣Fix(ρr)∣=kgcd⁡(r,n)|\mathrm{Fix}(\rho^r)| = k^{\gcd(r, n)}. Finally, gcd⁡(r,n)=n/d\gcd(r, n) = n/d for exactly φ(d)\varphi(d) values of r∈{0,…,n−1}r \in \{0, \dots, n-1\} (write r=(n/d) sr = (n/d)\, s with 0≤s<d0 \le s < d and gcd⁡(s,d)=1\gcd(s, d) = 1). Burnside's lemma completes the proof.

∎

For n=6n = 6 and k=2k = 2: the six rotations fix 26,21,22,23,22,212^6, 2^1, 2^2, 2^3, 2^2, 2^1 colourings, so there are 64+2+4+8+4+26=846=14\frac{64 + 2 + 4 + 8 + 4 + 2}{6} = \frac{84}{6} = 14 necklaces. The naive count 64/664/6 fails because, for instance, the all-red colouring is alone in its orbit.

Pólya's refinement. The proof of Corollary 8 used only the number of cycles of each symmetry. For a group GG of permutations of mm positions, let ci(g)c_i(g) be the number of cycles of length ii of gg, and define the cycle index

ZG(x1,…,xm)=1∣G∣∑g∈Gx1c1(g)x2c2(g)⋯xmcm(g).Z_G(x_1, \dots, x_m) = \frac{1}{|G|} \sum_{g \in G} x_1^{c_1(g)} x_2^{c_2(g)} \cdots x_m^{c_m(g)}.

Then the number of colourings with kk colours up to symmetry is ZG(k,k,…,k)Z_G(k, k, \dots, k), and — Pólya's enumeration theorem (Redfield 1927, Pólya 1937) — substituting xi=ri+bi+⋯x_i = r^i + b^i + \cdots (one variable per colour) gives a polynomial whose coefficient of rαbβ⋯r^{\alpha} b^{\beta} \cdots is the number of inequivalent colourings using each colour the prescribed number of times. (Proof: apply Burnside's lemma to the GG-invariant set of colourings with given colour counts; a colouring fixed by gg is constant on the cycles of gg, and a cycle of length ii contributes rir^i or bib^i or ….)

Example— Colouring the faces of a cube

The 24 rotations of a cube permute its 6 faces: the identity (x16x_1^6); 6 quarter turns about face axes (x12x4x_1^2 x_4); 3 half turns about face axes (x12x22x_1^2 x_2^2); 8 turns by 120∘120^\circ about vertex axes (x32x_3^2); 6 half turns about edge axes (x23x_2^3). So

Z=124(x16+6x12x4+3x12x22+8x32+6x23),Z = \tfrac{1}{24}\big(x_1^6 + 6 x_1^2 x_4 + 3 x_1^2 x_2^2 + 8 x_3^2 + 6 x_2^3\big),

and with kk colours there are 124(k6+3k4+12k3+8k2)\frac{1}{24}(k^6 + 3k^4 + 12k^3 + 8k^2) inequivalent cubes: 1010 for two colours, 5757 for three.

How many cubes have three red and three white faces? Substitute xi=ri+bix_i = r^i + b^i and extract the coefficient of r3b3r^3 b^3 term by term: x16=(r+b)6x_1^6 = (r + b)^6 gives (63)=20\binom{6}{3} = 20; 3x12x22=3(r+b)2(r2+b2)23 x_1^2 x_2^2 = 3 (r + b)^2 (r^2 + b^2)^2 gives 3⋅4=123 \cdot 4 = 12 (from 2rb⋅2r2b22rb \cdot 2r^2 b^2); 8x32=8(r3+b3)28 x_3^2 = 8 (r^3 + b^3)^2 gives 8⋅2=168 \cdot 2 = 16; and 6x12x46 x_1^2 x_4 and 6x236 x_2^3 contain no r3b3r^3 b^3. So there are 20+12+1624=2\frac{20 + 12 + 16}{24} = 2 such cubes: the three red faces either meet at a corner, or two of them are opposite and the third joins them.

A useful check on such computations: Burnside's lemma always produces an integer, so a fractional answer signals a missed term.

Formal mathematicsF

Counting in Lean and Mathlib

Mathlib has a good deal of enumerative combinatorics. Derangements are a finite type with a recursively computed count; the library proves that the recursion counts them and that D(n)/n!→1/eD(n)/n! \to 1/e. The closed formula of Theorem 5 is proved here by induction from Mathlib's recurrence D(n+1)=(n+1)D(n)−(−1)nD(n+1) = (n+1)D(n) - (-1)^n:

card_derangementsLean 4Applies a library theoremFormally verified
/-- The number of fixed-point-free permutations of a finite set depends only on its size, and
is computed by Mathlib's recursively defined `numDerangements`, which satisfies
`D(n + 2) = (n + 1) (D(n) + D(n + 1))`. This applies `card_derangements_eq_numDerangements`. -/
theorem card_derangements (α : Type*) [Fintype α] [DecidableEq α] :
    Fintype.card (derangements α) = numDerangements (Fintype.card α) :=
  card_derangements_eq_numDerangements α
Axioms used: propext, Classical.choice, Quot.soundformal/Lumenary/EnumerativeCombinatorics.leanTry it in the Lean web editor
derangements_formulaLean 4Proved here from basic factsFormally verified
/-- **The derangement formula** `D(n) = n! Σ_{k=0}^{n} (-1)^k / k!` (the inclusion–exclusion
formula of the lesson), proved by induction from the recurrence `D(n + 1) = (n + 1) D(n) + (-1)^(n+1)`
(Mathlib's `numDerangements_succ`): multiplying the formula for `n` by `n + 1` gives
`(n + 1)! Σ_{k ≤ n}`, and the new term is `(n + 1)! (-1)^(n+1) / (n + 1)! = (-1)^(n+1)`. -/
theorem derangements_formula (n : ℕ) :
    (numDerangements n : ℚ) =
      n.factorial * ∑ k ∈ range (n + 1), (-1 : ℚ) ^ k / k.factorial := by
  induction n with
  | zero => simp [numDerangements_zero]
  | succ n ih =>
    have h : (numDerangements (n + 1) : ℚ) = (n + 1) * (numDerangements n : ℚ) - (-1) ^ n := by
      exact_mod_cast numDerangements_succ n
    rw [h, ih, sum_range_succ (fun k => (-1 : ℚ) ^ k / k.factorial) (n + 1),
      Nat.factorial_succ]
    push_cast
    have hfact : (n.factorial : ℚ) ≠ 0 := by exact_mod_cast Nat.factorial_ne_zero n
    field_simp
    ring
Axioms used: propext, Classical.choice, Quot.soundformal/Lumenary/EnumerativeCombinatorics.leanTry it in the Lean web editor
derangements_tendstoLean 4Applies a library theoremFormally verified
/-- **A random permutation is a derangement with probability tending to `1/e`.** This applies
Mathlib's `numDerangements_tendsto_inv_e`. -/
theorem derangements_tendsto :
    Filter.Tendsto (fun n => (numDerangements n : ℝ) / n.factorial) Filter.atTop
      (nhds (Real.exp (-1))) :=
  numDerangements_tendsto_inv_e
Axioms used: propext, Classical.choice, Quot.soundformal/Lumenary/EnumerativeCombinatorics.leanTry it in the Lean web editor

Euler's partition theorem is in Mathlib, proved — like our first proof — by identifying generating functions, as the case m=2m = 2 of Glaisher's theorem; and the Stirling identity at the end of the previous section:

euler_partitionLean 4Applies a library theoremFormally verified
/-- **Euler's partition theorem** (1748): the partitions of `n` into odd parts are as numerous as
the partitions of `n` into distinct parts. Mathlib proves it, as in the lesson, by showing that
the two generating functions `Π 1/(1 - x^(2k-1))` and `Π (1 + x^k)` are equal (as the case
`m = 2` of Glaisher's theorem). This applies `Nat.Partition.card_odds_eq_card_distincts`. -/
theorem euler_partition (n : ℕ) :
    (Nat.Partition.odds n).card = (Nat.Partition.distincts n).card :=
  Nat.Partition.card_odds_eq_card_distincts n
Axioms used: propext, Classical.choice, Quot.soundformal/Lumenary/EnumerativeCombinatorics.leanTry it in the Lean web editor
pow_eq_sum_stirlingLean 4Applies a library theoremFormally verified
/-- **Counting functions by their image**: `m^k = Σ_j S(k, j) · m(m-1)⋯(m-j+1)`, where the
Stirling number of the second kind `S(k, j)` counts partitions of a `k`-set into `j` blocks: a
function from a `k`-set to an `m`-set is a partition of its domain into `j` nonempty fibres
together with an injective labelling of the fibres. This applies Mathlib's
`Nat.pow_eq_sum_stirlingSecond_mul_descFactorial`. -/
theorem pow_eq_sum_stirling (m k : ℕ) :
    m ^ k = ∑ j ∈ range (k + 1), Nat.stirlingSecond k j * m.descFactorial j :=
  Nat.pow_eq_sum_stirlingSecond_mul_descFactorial m k
Axioms used: propext, Classical.choice, Quot.soundformal/Lumenary/EnumerativeCombinatorics.leanTry it in the Lean web editor

Burnside's lemma. The double-counting step of the proof is proved here; orbit–stabiliser and Burnside's lemma itself are quoted from Mathlib, which states the lemma without division, as ∑g∣Fix(g)∣=∣X/G∣⋅∣G∣\sum_g |\mathrm{Fix}(g)| = |X/G| \cdot |G|:

burnside_double_countLean 4Proved here from basic factsFormally verified
/-- **The double-counting step of Burnside's lemma.** Count the pairs `(g, x)` with `g • x = x`
in two ways: by `g` (the fixed points of each group element) or by `x` (the stabiliser of each
point). Both sums are the size of the same set. -/
theorem burnside_double_count {G X : Type*} [Group G] [MulAction G X] [Fintype G] [Fintype X]
    [DecidableEq X] :
    ∑ g : G, #(univ.filter fun x : X => g • x = x) =
      ∑ x : X, #(univ.filter fun g : G => g • x = x) := by
  simp only [card_filter]
  exact sum_comm
Axioms used: propext, Classical.choice, Quot.soundformal/Lumenary/EnumerativeCombinatorics.leanTry it in the Lean web editor
orbit_stabilizerLean 4Applies a library theoremFormally verified
/-- **The orbit–stabiliser theorem**: `|orbit x| · |Stab x| = |G|`. Dividing the stabiliser sum
above by `|G|` therefore gives `Σ_x 1/|orbit x|`, which is the number of orbits, since each orbit
contributes `1`. This applies Mathlib's `MulAction.card_orbit_mul_card_stabilizer_eq_card_group`. -/
theorem orbit_stabilizer {G X : Type*} [Group G] [MulAction G X] [Fintype G] (x : X)
    [Fintype (MulAction.orbit G x)] [Fintype (MulAction.stabilizer G x)] :
    Fintype.card (MulAction.orbit G x) * Fintype.card (MulAction.stabilizer G x) =
      Fintype.card G :=
  MulAction.card_orbit_mul_card_stabilizer_eq_card_group G x
Axioms used: propext, Classical.choice, Quot.soundformal/Lumenary/EnumerativeCombinatorics.leanTry it in the Lean web editor
burnsideLean 4Applies a library theoremFormally verified
/-- **Burnside's lemma** (Cauchy–Frobenius): the number of orbits times `|G|` is the total number
of fixed points, `Σ_g |Fix g|`. This applies Mathlib's
`MulAction.sum_card_fixedBy_eq_card_orbits_mul_card_group`. -/
theorem burnside {G X : Type*} [Group G] [MulAction G X] [Fintype G]
    [∀ g : G, Fintype (MulAction.fixedBy X g)] [Fintype (MulAction.orbitRel.Quotient G X)] :
    ∑ g : G, Fintype.card (MulAction.fixedBy X g) =
      Fintype.card (MulAction.orbitRel.Quotient G X) * Fintype.card G :=
  MulAction.sum_card_fixedBy_eq_card_orbits_mul_card_group G X
Axioms used: propext, Classical.choice, Quot.soundformal/Lumenary/EnumerativeCombinatorics.leanTry it in the Lean web editor

Necklaces, checked by computation. Encode a 6-bead necklace as a number below 6464; a rotation class has a unique smallest member. Counting these canonical members gives 1414, and counting fixed colourings of each rotation gives the numbers 2gcd⁡(k,6)2^{\gcd(k,6)} used in Corollary 8:

isCanonicalLean 4Formally verified
/-- A colouring is the *canonical representative* of its rotation class if it is the smallest
number among its rotations. Each necklace has exactly one canonical representative. -/
def isCanonical (n x : ℕ) : Bool :=
  (List.range n).all fun k => x ≤ rotate n k x
Axioms used: propextformal/Lumenary/EnumerativeCombinatorics.leanTry it in the Lean web editor
necklaces_sixLean 4Proved here from basic factsFormally verified
/-- **There are 14 binary necklaces with 6 beads** (up to rotation): exactly 14 of the 64
colourings are canonical. Checked by `decide`. -/
theorem necklaces_six : ((List.range 64).filter (isCanonical 6)).length = 14 := by
  decide
Axioms used: propextformal/Lumenary/EnumerativeCombinatorics.leanTry it in the Lean web editor
necklace_fixed_countsLean 4Proved here from basic factsFormally verified
/-- **The fixed-point counts in Burnside's lemma for necklaces.** Rotation by `k` places fixes
exactly `2^gcd(k, 6)` of the 64 colourings (a fixed colouring is periodic with period
`gcd(k, 6)`). So Burnside's lemma gives `(64 + 2 + 4 + 8 + 4 + 2) / 6 = 84 / 6 = 14` orbits,
agreeing with `necklaces_six`. Checked by `decide`. -/
theorem necklace_fixed_counts :
    (∀ k < 6, ((List.range 64).filter fun x => rotate 6 k x = x).length = 2 ^ Nat.gcd k 6) ∧
      (∑ k ∈ range 6, 2 ^ Nat.gcd k 6) / 6 = 14 := by
  decide
Axioms used: propext, Classical.choice, Quot.soundformal/Lumenary/EnumerativeCombinatorics.leanTry it in the Lean web editor

What is not formalised here: Glaisher's bijection, the exponential formula, the cycle-index form of Pólya's theorem, and the general necklace formula (only the case n=6n = 6 is checked).

ComputationC

Generating functions, computed and checked

The first program expands the generating functions of Theorem 1 exactly — multiplying by 11−xk\frac{1}{1 - x^k} is the update bi=ai+bi−kb_i = a_i + b_{i-k} — and checks Euler's theorem up to x60x^{60}, runs Glaisher's bijection on all partitions of 88, and compares p(n)p(n) with the Hardy–Ramanujan formula.

Partitions: generating functions, Euler's theorem, Glaisher's mapPythonExact computationExhaustive finite checkNumerical approximationComputationally tested
"""Integer partitions: generating functions, Euler's theorem and Glaisher's map.

We expand the generating functions

    prod 1/(1 - x^k)          (all partitions, p(n)),
    prod 1/(1 - x^(2k-1))     (partitions into odd parts),
    prod (1 + x^k)            (partitions into distinct parts)

as exact integer power series up to x^60, and compare the last two, as
Euler's theorem predicts. For n = 8 we also list Glaisher's bijection, which
merges equal odd parts into distinct parts, and check that it is a bijection.
Finally we compare p(n) with the Hardy-Ramanujan asymptotic formula and test
Ramanujan's congruence p(5m + 4) = 0 (mod 5) on a range.

All counts are exact. Agreement up to x^60 is evidence; Euler's theorem is
proved for all n in the lesson (and in Mathlib).
"""

import math

def times_geometric(series, step):
    """Multiply a truncated power series by 1/(1 - x^step), in place.

    If b = a / (1 - x^step) then b = a + x^step * b, so b[i] = a[i] + b[i - step].
    """
    for i in range(step, len(series)):
        series[i] += series[i - step]
    return series

def times_one_plus(series, step):
    """Multiply a truncated power series by (1 + x^step), in place."""
    for i in range(len(series) - 1, step - 1, -1):
        series[i] += series[i - step]
    return series

def partition_counts(n_max):
    """p(0), ..., p(n_max): coefficients of the product of 1/(1 - x^k)."""
    series = [1] + [0] * n_max
    for k in range(1, n_max + 1):
        times_geometric(series, k)
    return series

def odd_part_counts(n_max):
    """Coefficients of the product of 1/(1 - x^k) over odd k."""
    series = [1] + [0] * n_max
    for k in range(1, n_max + 1, 2):
        times_geometric(series, k)
    return series

def distinct_part_counts(n_max):
    """Coefficients of the product of (1 + x^k)."""
    series = [1] + [0] * n_max
    for k in range(1, n_max + 1):
        times_one_plus(series, k)
    return series

def partitions(n, largest=None):
    """All partitions of n as non-increasing tuples."""
    if largest is None:
        largest = n
    if n == 0:
        return [()]
    out = []
    for first in range(min(n, largest), 0, -1):
        out += [(first,) + rest for rest in partitions(n - first, first)]
    return out

def glaisher(odd_partition):
    """Merge equal odd parts: m copies of k become k * 2^i for the bits of m."""
    parts = []
    for k in set(odd_partition):
        m = odd_partition.count(k)
        i = 0
        while m:
            if m & 1:
                parts.append(k * 2 ** i)
            m >>= 1
            i += 1
    return tuple(sorted(parts, reverse=True))

def hardy_ramanujan(n):
    """The leading asymptotic exp(pi sqrt(2n/3)) / (4 n sqrt 3)."""
    return math.exp(math.pi * math.sqrt(2 * n / 3)) / (4 * n * math.sqrt(3))

def main():
    n_max = 60
    odd = odd_part_counts(n_max)
    distinct = distinct_part_counts(n_max)
    print("Generating functions expanded exactly up to x^60:")
    print(f"  odd-part counts:      {odd[:13]} ...")
    print(f"  distinct-part counts: {distinct[:13]} ...")
    print(f"  equal for every n <= {n_max}: {odd == distinct}")
    print()
    eights = partitions(8)
    odds8 = [q for q in eights if all(part % 2 for part in q)]
    images = [glaisher(q) for q in odds8]
    print(f"Glaisher's map on the {len(odds8)} odd-part partitions of 8:")
    for q, image in zip(odds8, images):
        print(f"  {'+'.join(map(str, q)):<17} -> {'+'.join(map(str, image))}")
    distinct8 = {q for q in eights if len(set(q)) == len(q)}
    print(f"  images distinct and exactly the distinct-part partitions: "
          f"{len(set(images)) == len(images) and set(images) == distinct8}")
    print()
    p = partition_counts(300)
    print("p(n) versus Hardy-Ramanujan exp(pi sqrt(2n/3))/(4n sqrt 3):")
    for n in (10, 50, 100, 300):
        print(f"  n = {n:>3}: p(n) = {p[n]:<19} ratio = "
              f"{p[n] / hardy_ramanujan(n):.4f}")
    bad = [n for n in range(4, 300, 5) if p[n] % 5]
    print(f"Ramanujan: p(5m + 4) divisible by 5 for all 5m + 4 < 300: {not bad}")
    print()
    print("Epistemic status: exact integer computation, exhaustive for n <= 60 "
          "(Euler)\nand n < 300 (Ramanujan), and for n = 8 (Glaisher). These "
          "are finite checks\nof theorems proved in general; the asymptotic "
          "ratios are numerical and only\nillustrate that the relative error "
          "tends to 0, slowly.")

if __name__ == "__main__":
    main()
Runs entirely in your browser, in an isolated worker. Nothing is sent to a server.

The second computes exponential generating functions with exact fractions, using exp⁡(f)\exp(f) for a series with no constant term via g′=f′gg' = f' g, and compares their coefficients with brute-force counts.

Exponential generating functions versus brute-force countingPythonExact computationExhaustive finite checkNumerical approximationComputationally tested
"""Exponential generating functions, computed exactly and checked by brute force.

A labelled structure on n points is counted by a_n, and its exponential
generating function (EGF) is A(x) = sum a_n x^n / n!. The exponential
formula says: if a structure is a set of "connected" pieces counted by
C(x), then A(x) = exp(C(x)). We expand three EGFs with exact fractions:

* set partitions:   exp(e^x - 1)        (blocks are nonempty sets)
* involutions:      exp(x + x^2/2)      (cycles of length 1 or 2)
* derangements:     e^(-x) / (1 - x)    (permutations with no fixed point)

and compare their coefficients with direct enumeration for n <= 7. We also
watch D(n)/n! approach 1/e, with the alternating-series error bound.
"""

import itertools
import math
from fractions import Fraction

N = 12

def series_mul(a, b):
    """Product of two truncated power series (lists of Fractions)."""
    return [sum(a[i] * b[n - i] for i in range(n + 1)) for n in range(len(a))]

def series_exp(f):
    """exp(f) for a truncated series with f[0] = 0, via g' = f' g:
    n g_n = sum_{k=1}^{n} k f_k g_{n-k}."""
    if f[0] != 0:
        raise ValueError("constant term must be 0")
    g = [Fraction(1)] + [Fraction(0)] * (len(f) - 1)
    for n in range(1, len(f)):
        g[n] = sum(k * f[k] * g[n - k] for k in range(1, n + 1)) / n
    return g

def exp_series(c=1):
    """e^(c x) as a truncated series."""
    return [Fraction(c) ** n / math.factorial(n) for n in range(N + 1)]

def egf_counts(series):
    """Turn EGF coefficients into counts a_n = n! [x^n]."""
    counts = [s * math.factorial(n) for n, s in enumerate(series)]
    assert all(c.denominator == 1 for c in counts)
    return [int(c) for c in counts]

def set_partitions_egf():
    e = exp_series()
    return egf_counts(series_exp([Fraction(0)] + e[1:]))

def involutions_egf():
    f = [Fraction(0)] * (N + 1)
    f[1], f[2] = Fraction(1), Fraction(1, 2)
    return egf_counts(series_exp(f))

def derangements_egf():
    geometric = [Fraction(1)] * (N + 1)
    return egf_counts(series_mul(exp_series(-1), geometric))

def count_set_partitions(n):
    """Brute force: build partitions of {0..n-1} element by element."""
    blocks_list = [[]]
    for x in range(n):
        new = []
        for blocks in blocks_list:
            for i in range(len(blocks)):
                new.append(blocks[:i] + [blocks[i] + [x]] + blocks[i + 1:])
            new.append(blocks + [[x]])
        blocks_list = new
    return len(blocks_list)

def count_involutions(n):
    return sum(1 for p in itertools.permutations(range(n))
               if all(p[p[i]] == i for i in range(n)))

def count_derangements(n):
    return sum(1 for p in itertools.permutations(range(n))
               if all(p[i] != i for i in range(n)))

def main():
    rows = [
        ("set partitions (Bell)", "exp(e^x - 1)", set_partitions_egf(),
         count_set_partitions),
        ("involutions", "exp(x + x^2/2)", involutions_egf(), count_involutions),
        ("derangements", "e^(-x)/(1 - x)", derangements_egf(),
         count_derangements),
    ]
    for name, egf, coeffs, brute in rows:
        checked = all(coeffs[n] == brute(n) for n in range(8))
        print(f"{name} from {egf}:")
        print(f"  n = 0..10: {coeffs[:11]}")
        print(f"  equal to brute-force counts for n <= 7: {checked}")
    print()
    d = derangements_egf()
    print("Derangements: D(n)/n! approaches 1/e = 0.3678794412...")
    for n in (3, 6, 9, 12):
        ratio = Fraction(d[n], math.factorial(n))
        error = abs(float(ratio) - math.exp(-1))
        bound = 1 / math.factorial(n + 1)
        print(f"  n = {n:>2}: D(n)/n! = {float(ratio):.10f}, error {error:.2e}"
              f" <= {bound:.2e}: {error <= bound}")
    print(f"  D(n) = round(n!/e) for 1 <= n <= 12: "
          f"{all(d[n] == round(math.factorial(n) / math.e) for n in range(1, 13))}")
    print()
    print("Epistemic status: exact rational arithmetic for the series and "
          "exhaustive\nenumeration for n <= 7, so the agreement is a finite "
          "check of the\nexponential formula, not a proof; the error column "
          "uses floating point,\nwhich is ample at this precision.")

if __name__ == "__main__":
    main()
Runs entirely in your browser, in an isolated worker. Nothing is sent to a server.

The third counts necklaces, bracelets and coloured cubes twice — by listing colourings and keeping the smallest in each orbit, and by Burnside's lemma — and shows how far off the naive count kn/nk^n / n is.

Necklaces, bracelets and cubes: brute force versus BurnsidePythonExact computationExhaustive finite checkComputationally tested
"""Counting up to symmetry: necklaces, bracelets and coloured cubes.

Two colourings are "the same" if a symmetry carries one to the other, so we
are counting orbits of a group acting on colourings. We count orbits in two
independent ways and compare:

* brute force: list all colourings and pick one canonical representative
  (the smallest) from each orbit;
* Burnside's lemma: average the number of colourings fixed by each group
  element, which for necklaces gives (1/n) sum_{d | n} phi(d) k^(n/d), and
  for the cube is Polya's cycle-index polynomial evaluated at k colours.

We also show why the naive count k^n / n is wrong: symmetric colourings
have smaller orbits. The checks are exhaustive for the sizes shown.
"""

import itertools
from fractions import Fraction
from math import gcd

def phi(n):
    """Euler's totient function."""
    return sum(1 for a in range(1, n + 1) if gcd(a, n) == 1)

def rotations(n):
    """The n rotations of an n-bead necklace, as position permutations."""
    return [tuple((i + r) % n for i in range(n)) for r in range(n)]

def dihedral(n):
    """Rotations and reflections: the dihedral group of order 2n."""
    return rotations(n) + [tuple((r - i) % n for i in range(n)) for r in range(n)]

def act(perm, colouring):
    """The colouring moved by a permutation of positions."""
    return tuple(colouring[perm[i]] for i in range(len(perm)))

def orbits_brute_force(group, k, size):
    """Number of orbits: count colourings that are smallest in their orbit."""
    return sum(1 for c in itertools.product(range(k), repeat=size)
               if all(c <= act(g, c) for g in group))

def cycles(perm):
    """Number of cycles of a permutation."""
    seen, count = set(), 0
    for start in range(len(perm)):
        if start not in seen:
            count += 1
            i = start
            while i not in seen:
                seen.add(i)
                i = perm[i]
    return count

def orbits_burnside(group, k):
    """Burnside: average of |Fix g|; a permutation with c cycles fixes k^c."""
    total = sum(k ** cycles(g) for g in group)
    return Fraction(total, len(group))

def necklace_formula(n, k):
    """(1/n) sum over divisors d of n of phi(d) k^(n/d)."""
    return Fraction(sum(phi(d) * k ** (n // d) for d in range(1, n + 1)
                        if n % d == 0), n)

def cube_rotations():
    """The 24 rotations of a cube acting on its faces U, D, F, B, L, R."""
    spin = (0, 1, 5, 4, 2, 3)   # quarter turn about the U-D axis
    tilt = (2, 3, 1, 0, 4, 5)   # quarter turn about the L-R axis
    group, frontier = {tuple(range(6))}, [tuple(range(6))]
    while frontier:
        g = frontier.pop()
        for h in (spin, tilt):
            gh = tuple(g[h[i]] for i in range(6))
            if gh not in group:
                group.add(gh)
                frontier.append(gh)
    return sorted(group)

def main():
    print("Necklaces of n beads in k colours (rotations only):")
    print("   n  k   brute force   Burnside   formula   naive k^n/n")
    for n, k in [(4, 2), (6, 2), (6, 3), (8, 2), (9, 2), (9, 3)]:
        group = rotations(n)
        brute = orbits_brute_force(group, k, n)
        burn = orbits_burnside(group, k)
        naive = Fraction(k ** n, n)
        print(f"{n:>4} {k:>2} {brute:>13} {str(burn):>10} "
              f"{str(necklace_formula(n, k)):>9} {float(naive):>13.2f}")
    print("Bracelets (rotations and reflections), k = 2:")
    for n in (4, 6, 8, 9):
        group = dihedral(n)
        print(f"   n = {n}: brute force {orbits_brute_force(group, 2, n)}, "
              f"Burnside {orbits_burnside(group, 2)}")
    print()
    cube = cube_rotations()
    types = sorted(cycles(g) for g in cube)
    print(f"Cube: {len(cube)} rotations generated from two quarter turns.")
    print(f"  number with c cycles on the faces, c = 1..6: "
          f"{[types.count(c) for c in range(1, 7)]}")
    for k in (2, 3):
        print(f"  face colourings with {k} colours up to rotation: brute force "
              f"{orbits_brute_force(cube, k, 6)}, Burnside "
              f"{orbits_burnside(cube, k)}")
    print()
    print("Epistemic status: exact and exhaustive for every size shown - each "
          "count\nwas obtained by listing all colourings and agrees with "
          "Burnside's lemma.\nThat is evidence for the formulas at these sizes; "
          "the lemma itself is\nproved in the lesson (and in Mathlib).")

if __name__ == "__main__":
    main()
Runs entirely in your browser, in an isolated worker. Nothing is sent to a server.

All three are exact and exhaustive at the sizes shown: strong evidence for the formulas, and a check on our implementations, but not proofs for all nn.

Explore

Explore: the derangement probability

D(n)/n!D(n)/n! is the nn-th partial sum of the series ∑k≥0(−1)kk!=e−1\sum_{k \ge 0} \frac{(-1)^k}{k!} = e^{-1}. Watch the partial sums home in on 1/e1/e:

Partial sums of Σ (−1)^k / k! → 1/e

The n-th partial sum is D(n−1)/(n−1)!, the chance that a random permutation of n − 1 objects has no fixed point.

Examples
an =
  • partial sums Sₙ
  • terms aₙ
  • Known sum

S12 = a1 + … + a12 = 0.367879

Known sum: L = exp(−1) ≈ 0.367879 · still missing: L − S12 ≈ 1.94×10⁻⁹

Partial sums on a screen are evidence, never proof: a series can look settled and still diverge, as the harmonic series does.

Things to notice: the partial sums alternate above and below 1/e1/e, and the error after nn terms is smaller than the next term 1/n!1/n! — which is why the probability is practically constant from about seven people onwards. Change the term to 1/(n-1)! to see ee itself appear: the EGF of the sequence 1,1,1,…1, 1, 1, \dots is exe^x.

Exercises

Exercises

1. Enumerative combinatorics

Warm-upHuman

How many derangements (permutations with no fixed point) does a set of 55 elements have?

2. Enumerative combinatorics

Warm-upHuman

How many partitions of 77 into distinct parts are there?

3. Enumerative combinatorics

CoreHuman

How many necklaces of 55 beads, each bead one of 33 colours, are there up to rotation?

4. Enumerative combinatorics

CoreHuman

A student counts two-colour necklaces with 44 beads up to rotation as 24/4=42^4 / 4 = 4. The true number is 66. What exactly goes wrong?

Choose one

5. Enumerative combinatorics

CoreHuman

Glaisher's bijection merges equal odd parts: mm copies of an odd number kk become the parts 2ak2^{a} k for the powers 2a2^a in the binary expansion of mm. What is the image of the partition 3+3+3+1+13 + 3 + 3 + 1 + 1 of 1111?

Choose one

6. Enumerative combinatorics

StretchHuman

Which exponential generating function counts the permutations of {1,…,n}\{1, \dots, n\} all of whose cycles have odd length?

Choose one

7. Enumerative combinatorics

CoreHuman

In the proof of Burnside's lemma we used ∑x∈X∣Gx∣=∣G∣⋅∣X/G∣\sum_{x \in X} |G_x| = |G| \cdot |X/G|. Which fact justifies this step?

Choose one

8. Enumerative combinatorics

CoreFormal

Mathlib writes fixedBy X g for the fixed set of g, stabilizer G x for the stabiliser and orbitRel.Quotient G X for the set of orbits. Which statement is Burnside's lemma (true for every finite group acting on a finite set)?

Choose one

9. Enumerative combinatorics

StretchComputational

Write necklaces(n, k) returning the number of necklaces of nn beads in kk colours up to rotation, using Corollary 8: 1n∑d∣nφ(d) kn/d\frac1n \sum_{d \mid n} \varphi(d)\, k^{n/d}. Write your own totient function phi(d), and return an int.

Runs entirely in your browser, in an isolated worker. Nothing is sent to a server.
Common misconceptionsH

Common misconceptions

Misconception

"To count up to symmetry, divide by the number of symmetries." That works only when every orbit has full size ∣G∣|G|, i.e. when no colouring is fixed by a non-identity symmetry. For two-colour necklaces of four beads, 24/4=42^4/4 = 4, but the true count is 66: the colourings RRRR, WWWW (fixed by every rotation) and RWRW (fixed by the half-turn) have small orbits. Burnside's lemma corrects for exactly this.

Misconception

"A generating function must converge to be useful." Formal power series are algebraic objects; identities between them are identities of coefficients, valid whether or not the series converge. ∑n! xn\sum n!\, x^n converges only at 00, yet it is a perfectly good element of Q[[x]]\Q[[x]]. Convergence matters only when one wants analytic information, such as asymptotics.

Misconception

"Ordinary and exponential generating functions are interchangeable." They encode different combinatorics. OGFs multiply for unlabelled combinations (the coefficient of a product is ∑akbn−k\sum a_k b_{n-k}); EGFs multiply for labelled ones (with (nk)\binom{n}{k} built in). Using an OGF for permutations or set partitions gives the wrong convolution.

Misconception

"The probability of no fixed point goes to zero for large groups, since there are more people to collide with." More people means more chances for someone to draw their own name, but each chance is smaller; the two effects balance, and the probability tends to 1/e≈0.3681/e \approx 0.368, not to 00.

ViewpointsH

Many ways of seeing

Algebra— Partitions and the symmetric group

The partitions of nn index the conjugacy classes of the symmetric group SnS_n (by cycle type) — and therefore, since the number of irreducible complex representations of a finite group equals its number of conjugacy classes, also its irreducible representations. Unusually, there is a natural bijection: the partition λ\lambda gives the Specht module SλS^\lambda, whose dimension fλf^\lambda is the number of standard Young tableaux of shape λ\lambda. The identity ∑λ⊢n(fλ)2=n!\sum_{\lambda \vdash n} (f^\lambda)^2 = n!, true for any finite group's representations, has a bijective proof — the Robinson–Schensted correspondence between permutations and pairs of tableaux of the same shape. The cycle index of SnS_n is the generating function of conjugacy classes, and Pólya theory grows into the theory of symmetric functions. See representation theory and groups.

Analysis— Singularities decide asymptotics

Treated as analytic functions, generating functions reveal how fast their coefficients grow: the coefficients are governed by the singularities nearest the origin. D^(x)=e−x1−x\hat D(x) = \frac{e^{-x}}{1 - x} has a simple pole at x=1x = 1 with residue −e−1-e^{-1}, so D^(x)−e−11−x\hat D(x) - \frac{e^{-1}}{1 - x} is entire and its coefficients decay faster than any exponential: D(n)/n!=e−1+o(r−n)D(n)/n! = e^{-1} + o(r^{-n}) for every rr, a much stronger statement than Theorem 5. For partitions, ∏(1−xk)−1\prod (1 - x^k)^{-1} has the whole unit circle as a natural boundary; Hardy and Ramanujan's circle method analyses its behaviour near every root of unity, with the dominant contribution from x=1x = 1, giving p(n)∼14n3eπ2n/3p(n) \sim \frac{1}{4n\sqrt3} e^{\pi\sqrt{2n/3}}. Flajolet and Sedgewick's Analytic Combinatorics systematises this "singularity analysis". See complex analysis.

Number theory— Modular forms behind partitions

The partition generating function is essentially a modular form. With q=e2πiτq = e^{2\pi i \tau}, the Dedekind eta function η(τ)=q1/24∏k≥1(1−qk)\eta(\tau) = q^{1/24} \prod_{k \ge 1} (1 - q^k) satisfies η(−1/τ)=−iτ η(τ)\eta(-1/\tau) = \sqrt{-i\tau}\, \eta(\tau), so ∑p(n)qn−1/24=1/η(τ)\sum p(n) q^{n - 1/24} = 1/\eta(\tau) transforms predictably under SL2(Z)SL_2(\Z) — the source of the Hardy–Ramanujan–Rademacher formula. Euler's pentagonal number theorem is the qq-expansion of η\eta, a special case of Jacobi's triple product identity. Ramanujan's congruences modulo 5,7,115, 7, 11 come from modular identities such as ∑mp(5m+4)qm=5∏k(1−q5k)5(1−qk)6\sum_{m} p(5m + 4) q^m = 5 \prod_k \frac{(1 - q^{5k})^5}{(1 - q^k)^6}; Dyson's rank (1944) and Andrews and Garvan's crank (1988) explain them combinatorially; and Ken Ono (2000) showed that congruences of this kind exist modulo every prime ℓ≥5\ell \ge 5, though on more complicated arithmetic progressions. See analytic number theory.

Probability— Random permutations

The exponential formula is a probabilistic statement in disguise. For a uniformly random permutation of nn points, the number of fixed points converges in distribution to a Poisson variable with mean 11: Pr⁡(exactly j fixed points)=(nj)D(n−j)n!→e−1j!\Pr(\text{exactly } j \text{ fixed points}) = \binom{n}{j} \frac{D(n - j)}{n!} \to \frac{e^{-1}}{j!}. More generally the numbers of cycles of lengths 1,2,3,…1, 2, 3, \dots converge jointly to independent Poisson variables with means 1,12,13,…1, \frac12, \frac13, \dots, reflecting the factorisation exp⁡(∑kxk/k)=∏kexk/k\exp\big(\sum_k x^k/k\big) = \prod_k e^{x^k/k}. The total number of cycles has mean Hn∼ln⁡nH_n \sim \ln n (Theorem 6) and is asymptotically normal (Goncharov, 1944). The longest cycle typically covers a positive fraction of all points (about 0.62 n0.62\,n on average — the Golomb–Dickman constant), a fact with a surprising twin in the distribution of the largest prime factor of a random integer. See probability theory.

Geometry— Symmetry groups of solids and molecules

Burnside's lemma is geometry made countable: the group in the cube example is the rotation group of the cube, isomorphic to S4S_4 acting on the four long diagonals, and the cycle types were read off from its axes of symmetry. Pólya's original motivation (1937) was chemistry: counting isomers, molecules with the same formula but different arrangements, such as the substituted benzenes, where the group is the dihedral symmetry group of the hexagonal ring. Replacing the cube by the icosahedron, or the necklace by a crystal lattice, changes only the group and its cycle index. The naive count has a geometric meaning too. Weighting each orbit by 1/∣Gx∣1/|G_x| instead of 11 gives ∑orbits1/∣Gx∣=∣X∣/∣G∣\sum_{\text{orbits}} 1/|G_x| = |X|/|G|, the cardinality of the quotient regarded as an orbifold (or groupoid), in which a point with symmetry counts only fractionally; for a finite group acting on a finite complex, the orbifold Euler characteristic is likewise χ(X)/∣G∣\chi(X)/|G|. Burnside's lemma is what you need when every orbit must count as exactly one. See symmetry and transformations.

Category theory— Species: generating functions as functors

André Joyal's theory of combinatorial species (1981) explains why EGFs work. A species is a functor FF from the category of finite sets and bijections to itself: it sends a label set UU to the set F[U]F[U] of structures on UU (all set partitions of UU, all permutations of UU, …) and relabels structures along bijections. Its EGF is ∑∣F[n]∣xnn!\sum |F[n]| \frac{x^n}{n!}. Operations on species — sum, product (split the label set), composition (partition the labels, put a structure on each block) — correspond to sum, product and composition of EGFs, so Theorem 3 and the exponential formula become functorial statements: the species of sets EE has EGF exe^x, and "sets of connected structures" is the composite E∘CE \circ C, with EGF exp⁡(C^(x))\exp(\hat C(x)). Counting unlabelled structures uses the cycle index of a species, which unifies Pólya theory with EGFs. See functors.

History & global context

History and global context

  • Early combinatorics in India. Piṅgala's Chandaḥśāstra (last centuries BCE) on Sanskrit prosody counted the patterns of short and long syllables, which leads to binary sequences and to the numbers now called binomial coefficients (the meru-prastāra); Virahāṅka and later Hemacandra (c. 1150) counted metres of a given total length by what are now called Fibonacci numbers. Bhāskara II's Līlāvatī (1150) and Nārāyaṇa Paṇḍita's Gaṇita Kaumudī (1356) treat permutations and combinations systematically.
  • The Islamic world, China and Europe. Al-Karajī (c. 1000) worked with the binomial coefficients; in Marrakesh in the 13th century Ibn Munʿim counted the words that can be formed from an alphabet and used the arithmetic triangle to do so. In China the triangle appears in Yang Hui's 1261 book, citing Jia Xian (11th century). Levi ben Gershon (1321) proved the formulas for permutations and combinations by induction.
  • Probability and derangements. Pierre Rémond de Montmort posed the problème des rencontres — how often does no card fall in its own place? — in 1708, and solved it with Nicolaus Bernoulli; the limit 1/e1/e emerged from their work and Euler's.
  • Generating functions. Abraham de Moivre used generating functions to solve recurrences (1730), and Leonhard Euler's Introductio in analysin infinitorum (1748) founded the theory of partitions with them, including the odd/distinct theorem and the pentagonal number theorem (proved 1750). J. W. L. Glaisher gave his bijection in 1883; Percy MacMahon's Combinatory Analysis (1915–16) summed up the classical theory. G. H. Hardy and Srinivasa Ramanujan's asymptotic formula (1918), and Ramanujan's congruences (1919), opened the analytic and arithmetic theory of partitions.
  • Symmetry. The orbit-counting lemma appears in work of Augustin-Louis Cauchy (1845) and Georg Frobenius (1887); William Burnside's 1897 textbook made it famous, so his name stuck. J. Howard Redfield (1927) and George Pólya (1937) developed the cycle-index method, Pólya with applications to chemical isomers. Gian-Carlo Rota's 1960s programme and André Joyal's species (1981) gave the field its modern foundations.
Further reading

Further reading

  • Richard P. Stanley, Enumerative Combinatorics, Volume 1 (1986; 2nd edition 2012) and Volume 2 (1999) — the standard reference; generating functions, partitions and symmetric functions.
  • Herbert S. Wilf, generatingfunctionology (1990; 3rd edition 2006) — a lively introduction to generating functions.
  • Philippe Flajolet and Robert Sedgewick, Analytic Combinatorics (2009) — symbolic method, EGFs and singularity analysis.
  • George E. Andrews, The Theory of Partitions (1976) — the classic text on partitions.
  • George E. Andrews and Kimmo Eriksson, Integer Partitions (2004) — an accessible introduction.
  • Peter J. Cameron, Combinatorics: Topics, Techniques, Algorithms (1994) — includes Burnside's lemma and Pólya theory.

Next steps in the Lumenary: representation theory of finite groups, where partitions classify the representations of SnS_n; the probabilistic method; and analytic number theory.