Pólya counting
Public API for the Polya submodule, including the Möbius-inversion helpers.
Most users never call these directly — count_inequivalent selects the right method from the Sites and kwargs it is given. Reach for this submodule when you want an orbit count for a group action Enumlib doesn't build for you.
Which method you want comes down to three questions. Can every position take every label, or does each one have its own allowed_labels? Is the composition fixed, or free? And do you want the aperiodic count or the raw Burnside count that includes super-periodic orbits? The first question picks the column, the other two pick the row.
labels unrestricted (scalar k) | per-position allowed_labels | |
|---|---|---|
| any concentration | polya_count(G, k) | polya_count(G, allowed_labels) |
| fixed concentration | polya_count(G, multiplicities) | polya_count(G, allowed_labels, multiplicities) |
| aperiodic, any conc. | aperiodic_orbit_count(G, snf, k) | aperiodic_orbit_count(G, snf, allowed_labels) |
| aperiodic, fixed conc. | aperiodic_orbit_count(G, snf, multiplicities) | aperiodic_orbit_count(G, snf, allowed_labels, multiplicities) |
allowed_labels::AbstractVector{BitSet} carries one entry per supercell position, in the dset-blocks layout the permutation group already uses (dset position i owns positions (i-1)·n + 1 … i·n). The aperiodic_* rows apply the super-periodicity policy include_superperiodic = false; the polya_count rows are the raw Burnside counts.
Enumlib.Polya — Module
module PolyaPólya / Burnside math primitives used by Enumlib's Enumlib.count_inequivalent. Independently usable for general group-action orbit counting.
Exports:
polya_count(perm_group, k_or_mults)— Burnside-averaged orbit count.cycle_structure(permutation)— cycle lengths of a single permutation.aperiodic_orbit_count(perm_group, T_factors, k_or_mults)— Möbius-corrected primitive (aperiodic) orbit count.
See research.md §4.6 (Rosenbrock 2016) for the algorithm digest and research.md §5.2.1 for the super-periodicity policy that motivates the aperiodic variant.
Enumlib.Polya.polya_count — Function
polya_count(perm_group, k::Integer) -> BigIntBurnside-averaged count of orbits of k-colorings under perm_group — the raw orbit count, including super-periodic orbits. For each ρ ∈ perm_group with c(ρ) cycles, fix(ρ) = k^c(ρ); orbit count = (1/|G|) Σ_ρ k^c(ρ).
Returns BigInt. Cost: O(|G| · n).
The aperiodic version is aperiodic_orbit_count.
Examples
Cyclic C_4 on 4 positions (the rotations of a 1×1×4 supercell), binary colorings (k = 2): Burnside sum is (2^4 + 2^1 + 2^2 + 2^1) / 4 = 24 / 4 = 6.
julia> c4 = [[1,2,3,4], [2,3,4,1], [3,4,1,2], [4,1,2,3]];
julia> polya_count(c4, 2)
6The 6 orbits are: {AAAA}, {BBBB}, {AAAB-rotation}, {ABBB-rotation}, {AABB-rotation} (period 4), and {ABAB, BABA} (period 2).
polya_count(perm_group, multiplicities::AbstractVector{<:Integer}) -> BigIntBurnside-averaged orbit count at fixed multiplicities. For each ρ with cycle lengths [c_1, ..., c_t], the number of fixed colorings is the coefficient of x_1^{a_1}⋯x_k^{a_k} in ∏_j (x_1^{c_j} + ⋯ + x_k^{c_j}) (HF 2012 §A.2). Computed via dynamic programming over partial-multiplicity states.
Cost per permutation: O(t · ∏(ai + 1) · k). Across the whole group: O(|G| · t · ∏(ai + 1) · k).
Examples
Cyclic C_4 with multiplicities [2, 2] (2 of each species in 4 positions). Hand-verifiable: 6 raw colorings (C(4, 2)) group into 2 orbits — {AABB, ABBA, BBAA, BAAB} (period 4) and {ABAB, BABA} (period 2).
julia> c4 = [[1,2,3,4], [2,3,4,1], [3,4,1,2], [4,1,2,3]];
julia> polya_count(c4, [2, 2])
2polya_count(perm_group, allowed_labels::AbstractVector{BitSet}) -> BigInt
polya_count(perm_group, allowed_labels::AbstractVector{BitSet}, multiplicities) -> BigIntBurnside-averaged orbit count for label-restricted positions. allowed_labels[p] is the set of labels (0-based, as in Enumlib.Site) permitted at supercell position p.
A coloring fixed by ρ is constant on each cycle of ρ, so a cycle can only carry a label allowed at every position it visits: fix(ρ) = ∏_cycles |⋂ allowed_labels|, and 0 when some cycle's intersection is empty. With a uniform allowed_labels this reproduces the scalar-k method exactly.
This is the count that matches length(enumerate(...)) whenever Sites are heterogeneous — zinc-blende, half/full-Heusler, perovskite, or any site pinned to one species. The scalar-k method assumes every position may take any of the k labels, and overcounts by orders of magnitude when that is false.
Enumlib.Polya.cycle_structure — Function
cycle_structure(perm) -> Vector{Int}Cycle-length multiset of a permutation, sorted descending.
Examples
julia> cycle_structure([2, 1, 4, 3, 5]) # cycles (1 2)(3 4)(5)
3-element Vector{Int64}:
2
2
1
julia> cycle_structure([2, 3, 4, 1]) # cycles (1 2 3 4) — one 4-cycle
1-element Vector{Int64}:
4Enumlib.Polya.aperiodic_orbit_count — Function
aperiodic_orbit_count(perm_group, snf_diagonal, k_or_mults) -> BigIntAperiodic orbit count — orbits whose stabilizer in the supercell's translation subgroup T is trivial. Matches length(enumerate(...; include_superperiodic = false)) per supercell.
snf_diagonal is a 3-tuple (d_1, d_2, d_3) from the supercell HNF's Smith Normal Form, with d_1 | d_2 | d_3 (so T = Z/d_1 × Z/d_2 × Z/d_3 and |T| = d_1·d_2·d_3 = n). k_or_mults is either an Integer (unrestricted, k colors) or AbstractVector{<:Integer} (fixed multiplicities).
Burnside on the aperiodic-labeling subspace, with the per-permutation count expanded by Möbius inversion over the subgroup lattice of T:
N_aperiodic = (1/|G|) · Σ_{g ∈ G} Σ_{H ⊆ T} μ_T(H) · |fix(g) ∩ fix(H)|where |fix(g) ∩ fix(H)| is the number of labelings fixed by both g and every element of H — equivalently, labelings constant on each joint orbit of {g} ∪ H_perms acting on supercell positions. For unrestricted k-colorings this is k^(num joint orbits); for fixed multiplicities it's the polynomial-coefficient dynamic program on the joint-orbit partition.
This formulation is correct for non-normal H (subgroups of T need not be normal in G — e.g., for SNF (1, 2, 2) where T = Z/2 × Z/2 is non-cyclic). Earlier sketches assumed H-normality and gave wrong counts on non-cyclic T.
At typical supercell sizes (|T| ≤ ~32), subgroup enumeration is cheap; the inner per-(g, H) joint-orbit computation is O(n + |H|) via union-find.
Examples
Same cyclic C_4 case as polya_count, showing the aperiodic-vs-raw contrast. The SNF diagonal (1, 1, 4) says T = Z/4 (the 1×1×4 supercell's translation subgroup).
Unrestricted binary (k=2): polya_count gives 6 orbits total; aperiodic count drops the 1-cycle orbits {AAAA}, {BBBB} and the period-2 orbit {ABAB, BABA}, leaving 3 period-4 orbits.
julia> c4 = [[1,2,3,4], [2,3,4,1], [3,4,1,2], [4,1,2,3]];
julia> aperiodic_orbit_count(c4, (1, 1, 4), 2)
3Fixed-multiplicity [2, 2]: polya_count gives 2 orbits; aperiodic drops the period-2 {ABAB, BABA}, leaving 1 period-4 orbit {AABB, ABBA, BBAA, BAAB}.
julia> c4 = [[1,2,3,4], [2,3,4,1], [3,4,1,2], [4,1,2,3]];
julia> aperiodic_orbit_count(c4, (1, 1, 4), [2, 2])
1aperiodic_orbit_count(perm_group, snf_diagonal, allowed_labels::AbstractVector{BitSet}) -> BigInt
aperiodic_orbit_count(perm_group, snf_diagonal, allowed_labels::AbstractVector{BitSet}, multiplicities) -> BigIntLabel-restricted counterpart of aperiodic_orbit_count. Identical Möbius inversion over the subgroup lattice of T; the per-(g, H) fixed-count intersects allowed_labels across each joint orbit instead of assuming all k labels everywhere.
Use these whenever the Sites are heterogeneous — see the restricted polya_count.
See also
- Explanation: Pólya counting — the Burnside, Möbius, and label-restriction math these methods implement.
- How-to: Count without enumerating.