Powder indexing
Peak positions to reciprocal space
Every observed line becomes Q = 1/d^2 = (2 sin(theta) / lambda)^2. A metric family predicts Q = h^2 A + k^2 B + l^2 C + h l D with one coefficient per free parameter:
| Family | Parameters | Form |
|---|---|---|
| cubic | 1 | Q = (h^2+k^2+l^2) / a^2 |
| tetragonal | 2 | (h^2+k^2)/a^2 + l^2/c^2 |
| hexagonal | 2 | 4/3 (h^2+hk+k^2)/a^2 + l^2/c^2 |
| orthorhombic | 3 | h^2/a^2 + k^2/b^2 + l^2/c^2 |
| monoclinic (unique b) | 4 | h^2/(a^2 sin^2 b) + k^2/b^2 + l^2/(c^2 sin^2 b) - 2 hl cos b/(a c sin^2 b) |
Solving a family
A family with k parameters is solved from k observed lines whose hkl are enumerated up to max_index; the resulting linear system is solved by Gaussian elimination. The first lines of a family are often degenerate for this solve (a tetragonal pattern starts with h00 and hk0, both with l = 0; the first four monoclinic lines do not reach the cross term), so k+2 lines are offered and the singular subsets are skipped. Monoclinic tries four of the first six.
A candidate cell is kept when:
- the metric parameters are positive and the cell is inside the length, angle and volume bounds;
- it predicts the next unused observed line within the tolerance;
- it indexes at least
min_completenessof the pattern.
The metric is then refined by linear least squares on the matched reflections, in windows that grow from the unambiguous low-angle lines to the whole pattern, with re-matching between passes. The window matters for large cells: one wrong high-angle assignment would otherwise pull the cell far enough to lose the rest of the pattern.
Figures of merit
M20 (de Wolff) and F_N (Smith and Snyder) are computed from the same prediction:
Q20is the reciprocal squared of the twentieth observed line (or the last one when fewer),N20the number of calculated reflections in one hemisphere withQ <= Q20, and<|dQ|>the mean absolute difference over the matched lines among those twenty;M20 = Q20 / (2 <|dQ|> N20);<|d2theta|>is the mean two-theta difference over the same lines andN_possiblethe number of calculated reflections up to the last observed line;F_N = N_observed / (<|d2theta|> N_possible).
Both count reflections in one hemisphere (the factor two fills the sphere) and both grow with the number of predicted lines, so a cell that predicts far more lines than the pattern shows is not rewarded. coverage is the fraction of those predicted lines below Q20 that the observed peaks explain, which is what separates a real cell from a large overfit cell.
Ranking
The candidates are ordered by:
- coverage class: at least 95% of the observed lines indexed counts as full coverage (the rest is an impurity tolerance);
- the metric family, highest symmetry first;
M20, then the smaller cell volume, thenF_N.
A supercell indexes the same lines with a worse figure of merit; it is removed when its M20 is below one tenth of the best cell in its coverage class. The symmetry preference is what makes a cubic pattern come back cubic instead of one of the many monoclinic descriptions of the same lattice.
Single-parameter cubic search
index_cubic (the default of powder index without --general) assigns the first line every h^2+k^2+l^2 up to max_index, derives a and counts the lines it explains. A cell a and its multiples a sqrt(2), a sqrt(3), ... index the same lines, so among the candidates with the best completeness and an error within a tenth of the tolerance of the smallest, the smallest cell wins. A face-centred pattern (NaCl) therefore returns a = 5.62, not 7.95.
Validation
crates/xtalforge-analysis/tests/indexing_golden.rs reads tests/golden/phase-7/indexing/cells.json, generated by scripts/golden/indexing.py from five known cells (cubic Si, tetragonal TiO2, hexagonal ZnO, orthorhombic MFI and a synthetic monoclinic cell). The peaks are the Bragg positions up to 100 degrees with a deterministic 0.006 degree jitter, and the golden file stores M20/F_N computed by an independent Python implementation of the definitions above. The Rust test checks that:
figures_of_meriton the true cell reproduces the Python M20,F_N,N20andN_possibleto1e-4relative;index_patternfinds a candidate with the same lattice (Q spectra agree to5e-4relative) in the expected family, with at least 95% coverage.
The unit tests add the analytic dispersion of each family and the option and input validation.
Limits
- The solving lines must have indices within
max_index(default 3); a cell whose first observed line has a larger index needs a larger budget. - The per-family work is capped by
max_evaluations(default 20,000 solved assignments), so the search is deterministic and bounded on a dense pattern. - A cell and a supercell of the same family with systematic absences can both index a short peak list; the ranking prefers the smaller cell, but a pattern without the distinguishing reflections cannot separate them.
- The M20/F_N definitions follow the conventions stated above; other programs may count
N20slightly differently, which changes the absolute value but not the ranking.