Concentration and multiplicity

A concentration is a vector of fractions; a multiplicity is a vector of counts. The two live one n_total = n_D · n factor apart. This page covers how Enumlib's Concentration and ConcentrationRange types convert between them, the divisibility constraint that drops some supercell volumes silently, and how orbit sizes (orbit_size) relate to concentration counts.

Fractions vs counts

A Concentration value carries Rational{Int} fractions that sum to 1. To enumerate at that concentration on a supercell with n_total sites, Enumlib resolves the fractions to integer counts via multiplicities:

mults = multiplicities(concentration, n_total)   # ::Vector{Int}, sum(mults) == n_total

Four constructors land on the same Concentration:

ConstructorInputUse case
Concentration([f₁, f₂, ...])Fractions summing to 1You already have the composition as fractions.
concentration_ratio([a₁, a₂, ...])Raw ratiosYou have integers like [1, 3] meaning 1 of A per 3 of B; divides by the sum.
concentration_count([n₁, n₂, ...]; n_total = N)Anchored countsYou have a specific supercell in mind; validates sum == n_total.
Concentration(sites, [[r₁₁, r₁₂, ...], [r₂₁, ...], ...])Per-sublattice ratiosRegime C (heterogeneous sublattices). Stating "1:1 on A, 1:1 on B, fixed on O" directly is much less error-prone than computing the global flat-vector by hand.

They're interchangeable downstream: enumerate(...; concentration = c) takes any of them.

The divisibility constraint

A Concentration is resolvable at a supercell of size n_total iff each fraction times n_total is an integer. Equivalently: every fraction's denominator divides n_total.

Concentration([1//4, 3//4])         # denominators are 4 — needs n_total divisible by 4
Concentration([1//3, 2//3])         # denominators are 3 — needs n_total divisible by 3
Concentration([15//32, 17//32])     # needs n_total divisible by 32

When enumerate or count_inequivalent encounter a (concentration, n) pair where the concentration doesn't resolve cleanly, they skip that volume silently. So enumerate(parent, sites; supercells = VolumeRange(3:6), concentration = c_third) with c_third = Concentration([1//3, 2//3]) only enumerates on volumes 3 and 6 (multiples of 3); volumes 4 and 5 emit nothing.

This isn't an error — it's the natural semantics ("at this volume, this composition is impossible") and follows from multiplicities throwing EmptyEnumerationError on non-divisible inputs.

Multilattice — multiplicity over n_D · n

For a multilattice parent with n_D dset positions, every supercell carries n_total = n_D · n substitutable atoms. Multiplicities are resolved against n_total, not n:

# HCP binary at supercell volume 2: n_total = 2 · 2 = 4 atoms.
multiplicities(Concentration([1//2, 1//2]), 4)   # → [2, 2]
multiplicities(Concentration([1//4, 3//4]), 4)   # → [1, 3]

Internally count_inequivalent, estimate_cost, and the per-concentration enumeration path all resolve against n_total = n_D · n, not n. The single-lattice case is the degenerate n_D = 1 case.

Concentration ranges

ConcentrationRange holds per-species (lower, upper) bounds. concentrations_in_range materializes every Concentration inside the range whose fractions resolve cleanly at a given n_total:

julia> using Enumlib

julia> concentrations_in_range(ConcentrationRange([(0//1, 1//1), (0//1, 1//1)]), 4)
5-element Vector{Concentration}:
 Concentration(0//1, 1//1)
 Concentration(1//4, 3//4)
 Concentration(1//2, 1//2)
 Concentration(3//4, 1//4)
 Concentration(1//1, 0//1)

For binary 0:1, 1:3, 2:2, 3:1, 1:0 at n = 4. Note Enumlib does not auto-collapse label-exchange image pairs (the [1:3] and [3:1] pair, for example) — that's a deliberate design choice (different chemical species so the same composition swapped is a different material). Bound the range tighter — e.g., (0//1, 1//2) and (1//2, 1//1) — if you want only one half.

The range can decompose into a lot of concentrations at high n_total. The partition-explosion gate caps it via partition_threshold (default 50).

Orbit size ≠ multiplicity vector

Two unrelated meanings of "multiplicity" live in Enumlib; don't confuse them:

QuantityWhat it measuresWhere
MultiplicitiesCounts of each species in one labeling, summing to n_totalmultiplicities(c, n_total)::Vector{Int}
Orbit size (a.k.a. degeneracy, figure degeneracy)Number of labelings equivalent to a given one under the supercell's permutation groupEnumeratedStructure.orbit_size::Int

Multiplicity vectors are about composition (the macroscopic concentration). Orbit sizes are about symmetry (how many physical configurations correspond to one symmetry-inequivalent representative). Both have units of "count," which is why the name collision can confuse — but they answer different questions.

See also