Pick an algorithm
In practice, enumerate will automatically pick the best algorithm for your request. enumerate ships with three concentration-aware algorithms plus the "original" exhaustive (HF 2008) algorithm that sweeps over all concentrations. In most cases, leave algorithm = :auto — it makes the right call and you save reading the rest of this page.
Cross-algorithm benchmarks (bench/runbench.jl Sections 2, 4, 5) show that the recursive-stabilizer tree (Morgan-Hart 2017) is faster than the bitmap algorithms in nearly every case measured — ~2-3× over :exhaustive for unrestricted enumeration (and ~½ the memory), and ~9-60× over :multinomial_restricted for Regime C. :auto defaults to the tree for unrestricted enumeration too (synthesizing a full-range ConcentrationRange internally). The bitmap algorithms remain available for explicit cross-checks and edge cases.
Setup
julia> using Enumlib
julia> p = ParentLattice([0.0 0.5 0.5; 0.5 0.0 0.5; 0.5 0.5 0.0]); # FCC primitive
julia> sites = Sites([Site([0.0, 0.0, 0.0], [0, 1])]); # binaryWhat :auto decides
| Inputs | :auto picks | Why |
|---|---|---|
No concentration kwarg | :recursive_stabilizer (Morgan-Hart 2017) | The tree streams over all concentrations via a synthesized full-range ConcentrationRange; bench Section 5 measures ~2-3× speedup and ~½ memory vs :exhaustive. |
concentration supplied and the multinomial bitmap fits in memory_budget × 0.8 | :multinomial (HF 2012) | Memory-bounded but fast. Mixed-radix hash into a [0, C-1] bitmap; cross out orbits. |
concentration supplied but the multinomial bitmap won't fit | :recursive_stabilizer (Morgan-Hart 2017) | Memory-cheap (no bitmap). Good for very long enumerations. Tree search with shrinking stabilizers; generates configurations "lazily". |
Heterogeneous Sites — different allowed labels per dset position (e.g. perovskite A-site mixing, the As-fixed-on-anion AlGaAs example) — plus concentration | :recursive_stabilizer | The tree scales by the valid-colorings subspace; the bitmap variant :multinomial_restricted iterates the full multinomial coefficient and ends up slower for sparse Regime-C masks. |
The "Regime" labels you may see in error messages and design docs come from a three-way split of multilattice problems: A = single-site parent (just a Bravais lattice), B = multilattice with every dset position carrying the same allowed labels ("uniform sublattices" — HCP binary, diamond binary), C = multilattice with allowed labels that differ per dset position ("heterogeneous sublattices" — perovskite-style). Regime A and B run on :multinomial when concentration is given and the bitmap fits the memory budget; everything else (including Regime C and unrestricted enumeration) runs on the tree.
You can see what :auto chose by calling estimate_cost — its chosen_algorithm field is the dispatch result:
julia> c = concentration_count([4, 4]; n_total = 8);
julia> est = estimate_cost(p, sites; supercells = VolumeRange(8:8), concentration = c);
julia> est.chosen_algorithm # small case: bitmap easily fits
:multinomialWhat each algorithm does (one paragraph each)
You shouldn't need this to call enumerate. It's here so you can read the dispatcher's choice and know what it means.
A note on "bitmap": throughout the multinomial path, this is a literal packed bit array (Julia's BitVector) with one bit per labeling at the target concentration. The multinomial hash maps each labeling deterministically to an index in the array, and the algorithm flips bits as orbit-equivalents are eliminated. It's not a hash table (no separate chains, no open addressing) — just multinomial coefficient bits, indexed by hash. That's why the memory cost grows linearly with the multinomial coefficient and the dispatcher tracks it for the resource check.
:exhaustive(HF 2008) — iterate everyk^ncoloring of the supercell, check each against the symmetry group, keep one representative per orbit. The original algorithm; correct and simple but no longer the default — bench Section 5 shows the tree beats it by ~2-3× on FCC binary/ternary and HCP with ~½ the memory. Still useful for explicit cross-checks of the bitmap memory profile.:multinomial(HF 2012) — only iterate colorings at the targetconcentration. Uses the bitmap of remaining candidates described above; memory grows with the multinomial coefficient. Fast per-config, but the bitmap can get big. Roughly ties the tree at FCC binary fixed-concentration (bench Section 2); the tree wins as the bitmap grows.:multinomial_restricted(HF 2012 §A.1) — bitmap variant with a per-siteallowed_labelsmask for Regime C. Iterates the full multinomial coefficient and skips invalid slots lazily; slow when most slots are invalid (bench Section 4 shows it's ~9-60× slower than the tree on the Regime-C corpus). Reserved for dense-mask cases where the linear sweep is cheap; tree-walk pruning queued for a later release.:recursive_stabilizer(Morgan-Hart 2017) — tree search with shrinking stabilizers. Streams configurations instead of materializing a bitmap. The current:autodefault for almost everything (including unrestricted enumeration, via a synthesized full-rangeConcentrationRange); wins on speed and memory across the bench suite.
For the math behind each, see exhaustive-2008, multinomial-2012, recursive-stabilizer-2017, and the algorithm overview.
When :auto refuses (the resource check)
If Enumlib predicts the request will exceed your memory_budget (default max(2 GiB, 25% of system RAM)), it throws an EnumerationTooLargeError before any work starts. The error names the algorithm :auto was about to use, the predicted memory, and your budget. Three responses:
- Raise the budget.
enumerate(...; memory_budget = 16_000_000_000)for 16 GB. - Force
:recursive_stabilizer. It streams, so it has no bitmap memory dependence. Slower per config but finishes:enumerate(parent, sites; supercells, concentration, algorithm = :recursive_stabilizer) - Subset the request. Shrink the supercell volume range or pin a stricter
concentration; the resource check refused for a reason. You might need to make your problem smaller for it to fit in memory.
The estimate-cost how-to covers the budget kwargs and what each prediction means.
When to override
The override (algorithm = :exhaustive | :multinomial | :recursive_stabilizer) is for cases where :auto's rule misses a special concern of yours. Three motivations:
- You want the bitmap explicitly for reproducibility against earlier runs:
algorithm = :multinomial - You want the tree for systems with high configurational freedom even when the bitmap would fit — the tree's per-structure cost is sometimes lower at large n × k:
algorithm = :recursive_stabilizer - You want the unrestricted enumeration even though you supplied a
concentration—:exhaustiveignores theconcentrationkwarg, which is occasionally useful for cross-checks.
For both :multinomial and :recursive_stabilizer, the concentration kwarg is required (the algorithms are concentration-restricted by design):
julia> est = estimate_cost(p, sites; supercells = VolumeRange(8:8),
concentration = c, algorithm = :recursive_stabilizer);
julia> est.chosen_algorithm # explicit override sticks
:recursive_stabilizer