Definition¶
The rooted three-body distribution captures pairwise distance and angular correlations between atomic triplets. For a centre atom at position with neighbours at and :
G3Distribution.measure_g3 replicates the supercell up to
and, for every site, accumulates counts into a 4D
histogram
over all neighbour pairs inside the radial cutoff (with when the two are the same species). Radial bins use width ; angular bins use .
Channel indexing¶
Channels are labelled by species triplets
(centre | neigh_1 neigh_2) with
species_idx(neigh_1) ≤ species_idx(neigh_2). For a system with
species the channel count is
Examples: SiO₂ () → 6 channels (Si | Si Si, Si | Si O,
Si | O O, O | Si Si, O | Si O, O | O O); SrTiO₃ () →
18 channels.
g3_index stores the (centre, neigh_1, neigh_2) species indices for
each channel, and g3_lookup[c, a, b] maps any ordered species triple
to its channel; both orderings of the two neighbours land on the same
entry so the caller doesn’t have to sort. The atom-pair and
are added separately when the two neighbour species differ
(symmetrising the plane); when they are the same
species the diagonal is masked so no atom pairs with itself.
Acceleration backend¶
measure_g3(backend="auto") (the default) uses the numba-parallel
kernel. numba is a hard dependency of atomode (installed by
pip install atomode), so the fast path is always available; the
pure-numpy reference loop remains in the source as a self-check and
runs when backend="python" is passed explicitly. Both backends
produce bit-identical g3count and g2count (verified by
tests/test_g3_numba.py).
backend | speed (40 Å SrTiO₃, 5125 atoms) | when to use |
|---|---|---|
"numba" | ~1.2 s (≈ 25 × faster) | default |
"python" | ~30 s | reference / debugging |
"auto" | numba | the default; same as "numba" |
The first call pays a one-time JIT compile cost (~1–2 s); subsequent
calls hit the cached kernel. The kernel uses prange over origin
atoms with per-thread accumulators reduced at the end — no atomic
adds, no GIL.
Subsampled measurement (large cells)¶
Measuring g3 directly on a very large cell (100³ Å and beyond) takes
minutes even with the numba kernel. measure_g3 accepts a
sample_fraction knob that uniformly subsamples origin atoms
while keeping the full neighbour set intact — periodicity and
histogram shape are preserved while the cost drops linearly:
# ~1% of origins — same statistics as a full measurement at 40³ Å
cell.measure_g3(sample_fraction=0.01, sample_rng_seed=2026)The subsampled histogram correlates > 0.998 with the full measurement
for any sample_fraction ≥ 1/64 (validated on 60³ Å SiO₂), and the
bond-peak position is unchanged to three decimals. plot_g2_compare
accepts the same kwargs for g(r) overlays on large cells.
Reduced coordinates¶
The random-limit (ideal gas) g3 scales as . The reduced g3 divides this out:
where is a per-channel amplitude estimated from the far-field mean. In reduced coordinates in the random limit and crystalline peaks sit as sharp islands above the background. The pairwise byproduct is normalised analogously with the factor divided out.
Coordination shell target¶
CoordinationShellTarget.from_atoms(atoms) extracts first-shell
structural targets from a reference crystal using ASE’s periodic
neighbor_list at an adaptive cutoff (clamped between 2 Å and
the median nearest-neighbour distance). The resulting
frozen dataclass exposes (per species pair ):
| Field | Meaning |
|---|---|
pair_peak[a, b] | Mean first-shell radius |
pair_inner[a, b] / pair_outer[a, b] | Radial boundaries of the first shell (derived from the histogram minimum) |
pair_hard_min[a, b] | Hard-core overlap radius (max(0, r_inner − 1.5 σ_r)) |
pair_sigma[a, b] | First-shell width |
pair_mask[a, b] | True iff any first-shell neighbours of this pair exist |
coordination_target[a, b] | Mean number of -neighbours around an -atom |
angle_mode_deg[t] | Most probable bond angle for triplet channel |
angle_target[t, :] | Full angular histogram (width phi_num_bins) |
Triplet channels on the shell target use the same
(centre, neigh_1 ≤ neigh_2) indexing (angle_index, angle_lookup)
as the g3 object, so downstream consumers can re-use the same channel
layout.
Bond restrictions¶
Pairs with zero coordination_target install no bond springs and
cannot be bonded during shell_relax. from_atoms zeroes
lattice-artefact pairs automatically
(auto_filter_lattice_artifacts=True): a species pair whose
first-shell peak is not the smallest in either its row or column of
pair_peak is a lattice separation through a bridging atom, not a
chemical bond. This is what keeps SiO₂ / SrTiO₃ from developing a
spurious Si-Si or Ti-Ti bond at the second-shell distance.
Two helpers set the bond graph explicitly instead:
shell_target.with_cross_species_bonds_only() # zero diagonal: only A–B bonds
shell_target.with_bonded_species_pairs([('Ti', 'O')]) # keep listed pairs, zero the restBoth return a new CoordinationShellTarget; the original is unchanged.
Angle-spring masking (multi-modal shells)¶
For shells with multiple physically valid bond angles (the 12-coord
cuboctahedron at 60°/90°/120°/180°, Sr in SrTiO₃’s SrO₁₂ motif, …)
the single angle_mode_deg that gets extracted picks one of the
peaks and forcing it strains the others. Two helpers flip entries of
angle_enabled_mask to silence specific triplet types without
touching their bond-distance springs:
shell_target.with_angle_triplets([('Ti','O','O'), ('O','Ti','Ti')]) # whitelist
shell_target.without_angle_triplets([('Sr','O','O')]) # blacklistFor SrTiO₃ the whitelist form keeps Ti-centered 90° (octahedral) and
O | Ti Ti 180° (linear Ti-O-Ti backbone) while silencing every
Sr-centered triplet, consistent with Cu FCC’s angle_weight=0
treatment of the cuboctahedron.
Composite shell target¶
CoordinationShellTarget.from_targets({key: target, ...}) stacks
several per-chemistry targets into one with a widened species axis.
Each input contributes its own virtual-species rows of
coordination_target, pair_peak, angle_mode_deg, etc.
Cross-target coordination defaults to zero (no bonds form across the
virtual boundary), but cross-target repulsion is preserved so
different phases don’t overlap. Used for the sp²/sp³ carbon ladder
where the two virtual species (sp2_C, sp3_C) share atomic number
6 but carry distinct coordination (3 vs 4) and angle targets (120°
vs 109.5°). Per-atom virtual species is assigned at grain-build
time via Supercell.generate(..., grain_sources=[...]); see
Supercell generation for the grain
mechanism.