The indexing search and its result#
This page gives index_pattern: what it takes and refuses, the three engines it runs, the presets and budgets that bound it, the SearchSpecSpec controls and a volume window from a formula, and the IndexingResult it returns, with each candidate’s figures of merit, lattice symmetry, ambiguity partners, supercell checks, whole-profile test and confidence grade.
The peak list the search runs on, and the verdict on whether that list can be searched at all, are in Peak lists for indexing. What a consumer reads when it weighs the candidates itself, and how a candidate becomes a phase to refine, are in Indexing evidence and the route to a phase. The quadratic form the engines fit, the figures of merit and the lattice ambiguities are in Indexing.
Provisional
Indexing is still being measured against real data, so every name on this page may change in a 1.x release: index_pattern, the types in rietx.schemas.indexing and the helpers under rietx.indexing.
A change is announced in the release notes, and the data contracts a consumer parses do not move with it.
Provisional by declaration gives the promise in full.
Running the search#
import rietx as rx
data = rx.read_pattern("corundum.prn")
ins = rx.Instrument.bragg_brentano(radiation="CuKa")
peaks = rx.pick_peaks(data, ins)
result = rx.index_pattern(peaks, data=data, instrument=ins)
print(result)
Argument |
Default |
Does |
|---|---|---|
|
|
the |
|
|
the pattern; passing it is what turns the whole-profile test on |
|
|
needed with |
|
|
the search bounds, as the frozen |
|
|
the preset that sets the whole-run ceiling; |
|
|
which engines to run; |
|
|
a |
|
|
measure the shift allowance from harmonic reflection pairs, when |
|
|
run the whole-profile Le Bail test when |
|
|
how many of the top candidates get the expensive checks; |
|
|
the range the validation fits run over |
|
|
a chemical formula each candidate’s implied Z is checked against (A volume window from the formula) |
|
|
the measurement temperature in K, for the formula’s volume |
|
|
an event stream, as |
|
|
a |
index_pattern raises ValueError when it gets neither a peak list nor a data and instrument pair, for a preset it does not know, for a formula Hofmann’s table cannot read (before any peak is picked), for an engine name the registry does not hold, and for a time-of-flight pattern.
A list that cannot be searched in any crystal system is not an error.
The answer comes back with no candidates, an empty IndexingResult.engines_run, and INDEX_DATA_INSUFFICIENT from the quality report, before any budget is spent.
A list that can be searched but is short of the twenty lines the classical figures of merit need is searched over the systems it supports, ranked on the reduced panel, and capped (Enough lines, and precise enough).
The run goes in this order: the quality report, which may end it; a shift template adopted from the screen when it is safe to name (The systematic shift); the engines, one crystal system at a time; a second pass under the inlier rule when no lattice has two engines behind it; the declared prior cells; the merge, the ranking, the supercell checks and the lattice-symmetry screen; the Le Bail validation; the geometrical-ambiguity walk on what the clock leaves; and the confidence gate.
Three engines, and why the default is all of them#
Confidence here is engines agreeing, and the three registered engines fail in different ways, which is the point.
Engine |
How it searches |
What defeats it |
|---|---|---|
|
exhaustive branch-and-bound over boxes of the reciprocal metric |
a domain too wide for its budget; it is the only engine whose silence, when it finishes a system, is evidence that no cell exists there |
|
assumes indices for a few base lines, solves the metric exactly, then checks every line |
a wrong base line |
|
proposes metrics at random over a volume ladder and iterates the assignment and the solve until the indices stop changing; seeded by |
a bad starting basin |
The set is a registry, so ask which engines this build has:
import rietx as rx
caps = rx.capabilities()
engines = {engine.name: engine.description for engine in caps.indexing_engines}
assert set(engines) == {"dichotomy", "svd", "trial_error"}
Each row is an EngineCapability, quoted from the live registry (Calling rietx from a program).
Naming a subset narrows what the answer can say.
Adding an engine raises the bar rather than lowering it, because a candidate is high only when every engine that ran found it.
A run of one engine carries INDEX_SINGLE_ENGINE and grades every candidate low, since one finder is never agreement.
Only dichotomy makes an exhaustiveness claim, and it holds only where IndexingResult.search_complete is true for the system.
Read that field before concluding anything from a silence.
Presets, budgets and the three states of a system#
Preset |
Whole-run ceiling |
Use it |
|---|---|---|
|
120 s, covering search, probe and validation |
by default: a first look, and every interactive call |
|
none; only the per-slice budget bounds the search, and validation runs with no budget |
for a rerun, when a |
Capabilities.search_presets quotes the same rows with their measured typical times (Calling rietx from a program).
quick narrows nothing: no engine, system or search box is dropped.
What a binding ceiling cuts is the trailing low-symmetry systems, and the run says so with INDEX_BUDGET_EXHAUSTED.
A declared SearchSpecSpec.total_budget_seconds is never overridden, and the result then records IndexingResult.preset as "custom".
There are two budgets, and they bound different things.
SearchSpecSpec.budget_seconds bounds one slice, one engine on one crystal system, and SearchSpecSpec.total_budget_seconds bounds the whole run.
The slices run system by system: every engine finishes one system before any engine starts the next, cubic first and triclinic last.
So a binding ceiling cuts trailing systems for every engine alike, and a completed system holds every engine’s answer, which the agreement gate needs.
Under a ceiling, the search stops 8 % of it early when validation is going to run, so the validation fits are not starved.
Each fit then draws an equal slice of what remains, and a fast fit’s surplus goes to the later ones.
rietx.indexing.engines.estimate_ceiling is the arithmetic for choosing a value before starting, and rietx index --ceiling prints it.
After a run, every crystal system is in one of three states:
in
IndexingResult.systems_searchedwithIndexingResult.search_completetrue: searched to exhaustion;in
systems_searchedwithsearch_completefalse: truncated, so a negative result there means nothing;absent from
systems_searched: never reached.
A system counts as complete only when every engine that ran both entered it and exhausted its domain.
INDEX_SEARCH_INCOMPLETE says which engine stopped where and why, and INDEX_SYSTEMS_NOT_COVERED names the systems never searched.
Measured on the SRM 676a corundum scan with every default (2026-10-22, Linux, [dev]), the run ran to its 120 s ceiling and entered four of the seven systems: cubic, hexagonal and trigonal completed, tetragonal was truncated, and the three lowest were never reached.
The search specification#
SearchSpecSpec is the whole control surface.
It is flat and complete, because the engines’ agreement only means something when they were given identical bounds.
The same model is the GUI’s search form and the project document’s ProjectDoc.indexing.search, and Indexing search settings gives each field’s help entry.
Field |
Default |
Does |
|---|---|---|
|
|
the crystal systems to search; they always run from cubic to triclinic |
|
|
Bravais centrings per system, as a dict from system to letters |
|
2 Å, 25 Å |
the range of principal d-spacings, a bound on d(100) rather than on a; raising the top costs search volume |
|
15 ų |
the volume floor |
|
|
the volume ceiling, taken verbatim; |
|
|
driven lines a cell may leave unexplained: |
|
20 |
how many lines drive the search, the strongest N |
|
3 |
the matching window, in multiples of each line’s σ with the shift allowance added in quadrature |
|
0 |
a shift amplitude you measured, ° 2θ; 0 lets the run size the window itself |
|
|
|
|
30 s |
the budget of one engine on one system |
|
|
the whole-run ceiling; |
|
|
the preset, passed to |
|
12 |
how many candidates the answer reports |
|
0 |
the seed of the |
|
|
cells from a structural analogue, (a, b, c, α, β, γ) in Å and degrees |
|
|
space-group symbols from that analogue |
System |
Centrings it admits |
|---|---|
cubic |
P, I, F |
hexagonal |
P |
trigonal |
P, R |
tetragonal |
P, I |
orthorhombic |
P, C, I, F |
monoclinic |
P, C |
triclinic |
P |
SearchSpecSpec.to_spec builds the SearchSpec that index_pattern takes.
The two carry the same search fields, held equal by a test.
SearchSpecSpec.preset is the one field to_spec leaves out, because a preset is an argument of the run rather than a bound of the search.
from rietx.schemas.indexing import SearchSpecSpec
controls = SearchSpecSpec(systems=["trigonal", "hexagonal"],
centrings={"trigonal": ["R"]},
max_volume=600.0, budget_seconds=60.0, preset="full")
spec = controls.to_spec()
assert spec.systems == ("trigonal", "hexagonal")
assert spec.centrings == {"trigonal": ("R",)}
assert spec.max_volume == 600.0
The model refuses rather than narrowing in silence.
It raises a ValidationError naming the live vocabulary for an unknown crystal system, centring, shift template, preset or space-group symbol, and for a centring the system does not admit.
An empty centring list is refused rather than skipping the system, because leaving the key out is how a system keeps its full set and dropping a system is systems’ job.
A prior cell with an axis at or below zero, or an angle outside (0, 180)°, is refused too, as are a zero or negative budget, d-spacing or volume floor, and n_search_lines below 2.
SearchSpecSpec.shift_allowance_deg takes the shift’s amplitude, ShiftScreen.allowance_deg, never the scatter ShiftScreen.sigma_sys_deg.
Left at 0, the run sizes the window itself, and an assumed window caps every candidate (The systematic shift, and the window it opens).
A window does not correct a cell; only a shift template does.
Driven lines and the inlier rule#
A search is driven by the n_search_lines strongest usable lines, chosen from a low-angle pool twice that size, and every candidate is then scored against every usable line.
Leave SearchSpecSpec.n_search_lines alone: raising it is neither free nor safe.
Under a count, a cell must index all but that many of the driven lines, an absolute budget, so every foreign line admitted can refute the true cell rather than merely rank it lower.
The default first pass lets a cell leave two driven lines unindexed.
A run that ends with no lattice two engines agree on is searched again under the inlier rule, the acceptance RANSAC uses [Fischler and Bolles, 1981]: a cell is accepted when it indexes at least 60 % of the driven lines (and at least four more than the metric’s degrees of freedom), the five strongest among them.
The retry adds candidates and never removes the first pass’s, and INDEX_INLIER_RETRY says the answer came from it.
n_unindexed="inliers" searches that way from the start, and an integer forbids the retry.
Only such a run retries, because the inlier rule changes the pool the panel ranks, and the ranking depends on that pool.
A list whose lines all carry one intensity, such as PeakList.from_positions without intensities, has no strongest five to anchor on and keeps the count of two.
Under every rule, what a cell leaves is reported rather than hidden: CellCandidate.n_search_unindexed counts the driven lines it left, and CellCandidate.unindexed_two_theta lists every usable line it left, the residue a second phase would explain.
Priors from a structural analogue#
A prior steers the search and never gates it.
Each crystal system a prior names moves to the front of the queue, and a prior cell’s metric seeds the svd engine’s starting basin.
The cell is then checked against the peak list the engines’ own way, and if it indexes the driven lines it joins the answer as finder "prior": added to CellCandidate.found_by when an engine also found the lattice, and otherwise appended after the ranked list, never ranked among the engines’ candidates.
"prior" never appears in IndexingResult.engines_run, so the agreement caveat grades a prior-only candidate down.
No system is dropped and no range changes, so a wrong prior costs time rather than truth.
A prior cell outside the declared axis range is reported as unusable rather than widening the box, and the check refutes a cell it would have to move more than 10 % in volume to fit.
INDEX_PRIOR_USED records what was supplied and what it changed.
Two settings of one lattice, such as an acute and an obtuse monoclinic angle, come back as one candidate, because the merge compares reduced cells.
The controls a project stores#
IndexingControls is the same surface one level up: everything a run is asked with that is not the data.
It is what ProjectDoc.indexing persists, so a run can be repeated from a stored setting rather than from a call site.
Field |
Default |
Holds |
|---|---|---|
|
every default |
the |
|
|
which engines to run; an unknown name raises |
|
|
whether to run the whole-profile test |
|
|
how many top candidates get the expensive checks, at least 1 |
two_theta_limits is deliberately not a field: the project document already owns the range.
A volume window from the formula#
A formula says roughly how much room one formula unit takes in a crystal.
rietx.indexing.SearchSpec.from_formula turns that into a volume window.
One formula unit’s volume is the sum of Hofmann’s average atomic volumes [Hofmann, 2002], from rietx.crystallography.atomic_volume.formula_unit_volume.
The window holds z formula units, widened to the bounds 0.8 to 1.25 on the ratio of observed to estimated volume, outside which about 1 % of the structures Hofmann fitted lie.
from rietx.crystallography.atomic_volume import formula_unit_volume
from rietx.indexing import SearchSpec
volume, esd = formula_unit_volume("C7H6O2") # benzoic acid, at 298 K
assert (round(volume, 2), round(esd, 2)) == (150.35, 0.93)
spec = SearchSpec.from_formula("C7H6O2", z=(2, 8), max_d_axis=32.0)
assert round(spec.min_volume, 2) == 240.56 # 2 x 150.35 x 0.8
assert round(spec.max_volume, 2) == 1503.5 # 8 x 150.35 x 1.25
z counts formula units in the conventional cell: an integer, a (min, max) pair or None.
None sets only the floor of one formula unit and leaves the ceiling to the quality report’s envelope, because a formula says nothing about how many units the cell holds.
The floor never drops below the 15 ų default.
Every other SearchSpec field passes through as a keyword.
from_formula raises ValueError when min_volume or max_volume is passed beside the formula, for a z that is not a whole number of at least 1 with its minimum no larger than its maximum, when the formula’s ceiling falls below the floor, and for a formula or temperature formula_unit_volume refuses.
One crystal scatters about Hofmann’s estimate by 4.00 %, and the window is wider still, because a window that excludes the true cell makes the search return a wrong one. The volumes were fitted to organic and metal-organic crystals, and an ionic compound comes out too large, so the floor can exclude its cell.
Passing formula= to index_pattern checks each candidate instead of narrowing the search.
A cell whose volume holds a fractional number of formula units carries INDEX_Z_NOT_INTEGER, a warning whose value is the implied Z.
It fires when the nearest whole number is more than 8 % away, twice one crystal’s scatter, so it is silent above Z = 6.25.
The candidate keeps its rank and its grade.
Volume from a formula gives the estimate.
The result object#
IndexingResult is a ranked list of hypotheses with the evidence behind each, and what the search covered.
It has no cell and no best: the only route to one cell is IndexingResult.best_or_none.
Field |
Holds |
|---|---|
|
the ranked |
|
the engines that ran, the denominator of the agreement gate |
|
the systems any engine entered |
|
per system, whether every engine that ran entered it and exhausted its domain |
|
per-engine counters, each key prefixed with the engine’s name and a dot |
|
whether the panel’s members put different candidates first |
|
the |
|
whether a pattern was supplied and validation ran at all; it says nothing about any one candidate |
|
the usable lines the answer is about |
|
the primary wavelength, Å |
|
the preset that set the ceiling, |
|
the package version and time, with every search setting in |
|
which set of gates and vocabularies produced the grades, |
|
the run-level findings |
IndexingResult.best_or_none returns a candidate only when exactly one is high and it has no ambiguity partners.
Everything else returns None: nothing found, two cells that both explain the pattern, a search with no validation, an assumed tolerance.
INDEX_ABSTAINED then names the top candidate’s caveats, and INDEX_MULTIPLE_SOLUTIONS fires when more than one candidate is high.
IndexingResult.diagnostics says things about the run, such as a truncated budget or systems not covered, while a statement about one cell lives on that candidate, in CellCandidate.diagnostics and CellCandidate.confidence_caveats.
Printing a result gives the run line, one block per candidate with its grade and the caveats holding it down, the verdict, and every diagnostic.
rietx index prints exactly that (rietx index).
One candidate#
Field |
Holds |
|---|---|
|
a, b, c in Å and α, β, γ in degrees, with esds, in the conventional setting |
|
the members of |
|
the cell in the setting the engine found, and the integer matrix M taking it to |
|
the crystal system, and the Bravais centring letter |
|
the absence-free group of the lattice: its holohedry and centring |
|
the cell volume and its esd, ų |
|
the six quadratic-form parameters A to F actually fitted (12.2), in the reported setting |
|
the usable lines the engine’s assignment indexes, of the usable lines it was offered |
|
driven lines this cell leaves unindexed; |
|
2θ of every usable line this cell leaves unindexed, ascending; |
|
reduced χ² of the metric fit |
|
the shift column refitted with the cell, its amplitude in ° 2θ and its esd; |
|
the figure-of-merit panel, one |
|
the engines that reached this lattice, sorted, with |
|
the lattices this data cannot tell from this one, as |
|
one |
|
the two opinions on the lattice symmetry, a |
|
the whole-profile test, a |
|
|
|
every reason it is not |
|
the findings about this candidate |
CellCandidate.to_cell returns cell as a Cell with every parameter fixed, and the esds stay on cell_esd, since a Parameter carries none.
CellCandidate.fom_value reads one panel member by name and returns None rather than raising, because which members exist depends on what the list could support.
A candidate is reported in its conventional setting, whatever setting the engine reached.
Only a monoclinic lattice has one to choose: b is the unique axis, a and c are the shortest translations normal to it, and β is obtuse.
An engine may arrive at β = 81.78° for a lattice whose conventional β is 98.22°, or at any of a C cell’s settings, and af, cell and cell_esd are re-set through one exact map.
The engine’s own cell stays in engine_cell, and change_of_basis is M with (reported axes) = M · (engine axes), the identity where nothing moved.
Two cells are one lattice when rietx.indexing.same_lattice says so on their af, never when the six numbers match.
CellCandidate.lattice_group is the absence-free group, never a plausible space group, and the distinction is load-bearing.
A group carrying reflection conditions would hide exactly the reflections whose absence is not yet established, and hiding them is how an oversized cell passes.
The figure-of-merit panel#
The panel ranks candidates and does not score them. A margin is comparable within one member and not across them, so the members vote by a Borda count instead of being summed, and each carries what it is blind to.
Field |
Holds |
|---|---|
|
which member this is |
|
its value |
|
the observed lines it read, and the lines the lattice allows over them |
|
the matching window it was computed at, in multiples of each line’s σ |
|
the mean |Δ| of the matched lines in the member’s own units, Å⁻² for M₂₀ and ° for \(F_N\); −1 when nothing matched, which is not zero |
|
what this member cannot see, as a sentence |
On a list of 20 or more usable lines the panel has seven members.
Member |
What it asks |
Lines it reads |
|---|---|---|
|
de Wolff’s M₂₀ (12.9) |
the 20 lowest-angle lines |
|
Smith and Snyder’s \(F_N\) (12.10), scored at N = 20 |
the 20 lowest-angle lines |
|
the share of observed lines the lattice indexes |
every usable line |
|
the same share, weighted by intensity |
every usable line |
|
the share of the lattice’s predicted lines that are observed |
every usable line |
|
Oishi-Tomiyasu’s reversed figure [Oishi-Tomiyasu, 2013] |
the 20 lowest-angle lines |
|
the product of the forward and reversed figures over one window |
the 20 lowest-angle lines |
Below 20 usable lines m20 and f_n are left out of every candidate’s panel alike, and DataQualityReport.fom_undefined names each with its reason.
Coverage is scored in both directions, and that is what the reversed members are for.
Share-of-observed alone puts a supercell above the truth, since a supercell indexes every observed line, while the reversed direction asks how much of what the cell predicts was actually seen.
A candidate refitted with a shift column is scored on its own corrected positions, and every coverage member matches in the window the search matched with.
FigureOfMerit.blind_spot is a field because every published figure of merit has a failure mode, and a value read without it is one step from a confident wrong answer.
Where the search let lines go unindexed, it also says how many lines the mean discrepancy was trimmed by.
The ranking puts corroboration first: every candidate at least two engines found ranks above every candidate fewer found, and the Borda count orders each group.
IndexingResult.fom_panel_disagrees is true when the members’ first choices differ, which means at least one blind spot is active, and the gate reads it (The confidence gate).
Figures of merit, in both directions gives the formulas.
The lattice symmetry, and its ambiguities#
CellCandidate.bravais is a BravaisOpinion: two independent readings of the same Niggli-reduced cell, each over a tolerance sweep.
gemmi’s tolerance is an obliquity in degrees (0.5°, 1°, 2° and 3°) and spglib’s a distance in Å (1, 3 and 10 times the cell’s own esd), so a disagreement between them is information about the cell rather than a bug in either.
Field |
Holds |
|---|---|
|
the symmetry that survives the whole sweep: the lower of the two methods’ answers at their tightest tolerance |
|
the highest symmetry either method reported at any tolerance |
|
each method’s answer at its tightest tolerance |
|
whether |
|
whether the two methods’ tightest answers differ |
|
the Niggli-reduced, primitive cell the symbols refer to |
Either flag raises INDEX_BRAVAIS_AMBIGUOUS on the candidate.
A powder pattern carries only the length of a reciprocal vector, so distinct lattices can produce the same line positions (12.11).
Those are reported, never resolved, as AmbiguityPartner entries.
The search walks the derivative lattices of index 2 to 4.
A partner counts when it indexes at least as many observed lines with a mean discrepancy no more than 1.5 times worse, and no extra line it predicts inside the measured range is missing from the pattern.
Field |
Holds |
|---|---|
|
the partner lattice, a superlattice of this candidate’s cell |
|
always |
|
its volume, ų |
|
the integer matrix from this candidate’s basis to the partner’s |
|
|det| of that matrix, 2 to 4 |
|
up to six hkl, lowest angle first, whose positions would break the tie |
|
where each would have to appear or be absent, ° 2θ |
A partner that survived has no missing extra line inside the measured range, so its discriminating reflections lie outside it, up to 1.5 times the top of the range or below its bottom.
They say where to measure again, which makes the ambiguity something to act on.
A partner raises INDEX_GEOMETRIC_AMBIGUITY on the candidate.
The walk finds only larger cells, so a smaller rival with the same line positions is not reported.
The walk is expensive and runs only on the checked candidates, after validation, on whatever the clock leaves.
A candidate the walk never reached has an empty CellCandidate.ambiguity, and the gate reads that as a question not asked (The confidence gate).
A supercell and the cell inside it#
A cell twice as long along one axis explains every line the smaller cell explains, and predicts more, and the panel can rank it first: on the round-robin brucite pattern it ranked a supercell above the certified cell. So every pair of reported candidates in which one is a superlattice of the other, holding 2 to 4 of its primitive cells, is asked whether the pattern shows the extra lines the larger cell needs.
Missing lines alone cannot answer that, since a correct cell’s symmetry can extinguish lines. But an extinction only ever acts on a line that sits on a mirror plane or a rotation axis of the lattice, so only the extra lines lying on neither are counted. A line is seen when it falls inside an observed line’s matching window, and the count is compared with chance: the share of the measured range those windows cover, which is how often a position with no line at all would still read as seen (12.12).
The verdict is one of three, at a significance level of 0.01:
refuted: the extras are seen no more often than chance, and the test had the power to say otherwise. The larger cell moves to directly below the lowest-ranked smaller cell that refutes it, stays in the list, and carries the
supercell_refutedcaveat andINDEX_SUPERCELL_REFUTED.supported: the extras seen on observed lines the smaller cell cannot index are more than chance gives. The larger cell moves to directly above the highest-ranked smaller cell that supports it, unless another check refutes it.
undecided: even seeing every extra could not have reached the level, or the extras are seen beyond chance only on lines the smaller cell already explains, as on a pseudo-symmetric description of the smaller cell’s own lattice. Nothing moves.
Nothing else in the order changes, and a refuted cell is never moved up. The check needs only the peak list, so it runs on a bare list too.
Field |
Holds |
|---|---|
|
the smaller candidate this cell contains |
|
how many of the parent’s primitive cells one of this cell’s holds |
|
the extra lines in range no extinction could remove, and how many sit on an observed line |
|
of those seen, the ones on an observed line the parent cannot index; |
|
the chance that a position with no line reads as seen |
|
the binomial chance of seeing at least |
|
|
|
where the extras nothing was seen at would be, on the pattern’s own 2θ axis, lowest first and at most six |
On the SRM 676a corundum scan all three engines find the certified cell’s c/2 subcell, which leaves 10 of the usable lines unexplained, and corroboration ranks it above the certified cell, which fewer engines find.
The certified cell’s 18 extras over the subcell are seen 8 times, all on lines the subcell cannot index, where chance gives 2.7, so the check is supported and the certified cell moves to first.
A real superstructure whose extra lines are too weak to pick is what this check cannot see.
Before discarding a refuted cell whose chemistry makes it likely, look at the pattern at SupercellCheck.absent_two_theta.
The whole-profile test#
Four of the panel’s members read only the first twenty lines, and none of the seven sees intensity.
A Le Bail fit against the whole pattern sees three things the panel cannot: lines beyond the twentieth, reflections predicted where there is no intensity, and impurity content.
The middle one is the classic doubled-cell false positive, which is why validation is part of the default run.
CellCandidate.lebail is that test, as a LeBailValidation.
Field |
Holds |
|---|---|
|
the fit’s agreement; both infinite when the fit raised |
|
the absence-free lattice group the fit used |
|
the primary line’s reflections in the fitted range |
|
of those, the ones with no net intensity above the fitted background: under 3σ over half a FWHM either side |
|
observed peaks with no calculated reflection nearby, as the fit report’s Layer 0 counts them |
|
where each of those is, ° 2θ |
|
the fit’s status; |
|
the stages the fit ran, of the validation plan’s four |
|
findings from the fit; |
The fit holds the cell.
The candidate is the hypothesis under test, and letting it walk would validate a different cell from the one reported.
It frees the background, then exactly one peak-position parameter chosen from the candidate’s own shift template (the zero shift by default, and always on a geometry other than Bragg-Brentano), then W, then U, V, X and Y together.
When the declared instrument’s widths disagree with the picked lines’, W starts from the lines’ median FWHM.
The fit is single-phase, a measured constraint rather than a simplification: Le Bail partitioning has nothing to arbitrate two phases claiming the same channel.
The test runs on the checked candidates only: the top check_top, plus every candidate every engine found, since those are the only ones the gate could promote.
A candidate outside that set, or one whose fit the clock stopped, keeps lebail = None.
LeBailValidation.rwp is not a panel member.
It costs a refinement, so it is computed for a shortlist rather than for every candidate, and reading it as a rank would bring back the blind spot it exists to close, since a bigger cell with more free intensities fits better.
Read predicted_but_absent as “this cell predicts lines the pattern lacks”, never as “this cell is too big”.
It counts against the lattice group, so a space-group extinction (a glide plane, a screw axis) refutes a correct cell, and only the extinction screen afterwards separates the two (The extinction symbol).
A nonzero count raises INDEX_PREDICTED_BUT_ABSENT, and unmatched observed lines raise INDEX_IMPURITY_LINES.
The confidence gate#
CellCandidate.confidence is high, medium or low, written by one rule:
lowwhen fewer than two finders stand behind the candidate, or when any refuting caveat applies;highwhen every engine that ran found it and no caveat applies at all;mediumotherwise: at least two finders, and only capping caveats.
Every reason a candidate falls short is a member of a closed vocabulary in CellCandidate.confidence_caveats.
Seven of the fourteen refute the candidate, each being evidence against the cell or evidence that the data cannot choose.
The other seven cap it.
The count of caveats decides nothing.
Caveat |
Written when |
Effect |
|---|---|---|
|
the candidate has an ambiguity partner, or the ambiguity walk never reached it |
refutes |
|
the panel’s members put different candidates first; written on every candidate of the run |
refutes |
|
the Le Bail fit ran and |
refutes |
|
the intensity share the cell explains ( |
refutes |
|
the volume is below 15 ų, or above 1.5 times Smith’s envelope for its system |
refutes |
|
the Le Bail fit’s status is |
refutes |
|
a |
refutes |
|
the intensity share passes 0.9 but the |
caps |
|
some engine that ran did not find this lattice |
caps |
|
this candidate has no Le Bail fit behind it |
caps |
|
the candidate’s own crystal system was not searched to completion |
caps |
|
the matching window was widened by an assumed allowance |
caps |
|
the list is too short for |
caps |
|
|
caps |
A candidate the ambiguity walk never reached reads geometric_ambiguity with an empty CellCandidate.ambiguity.
Not enumerated is not the same as none found, and the gate will not read an unasked question as a clean answer.
On a run whose clock ran out before the walk, every candidate carries this caveat and grades low.
volume_unphysical uses the same 1.5 times the envelope the search prunes at, so it fires only on a cell a declared max_volume let past it.
It raises INDEX_VOLUME_UNPHYSICAL.
not_validated and validation_failed are separate on purpose: the absence of a test and a failed test are different statements, and only the second is evidence about the cell.
not_validated has two causes, and they are not the same news.
Either no pattern was supplied, so no candidate could be validated, and IndexingResult.validated is false with INDEX_NOT_VALIDATED on the run.
Or a pattern was supplied and this candidate’s fit did not run, because it was outside the checked set or the ceiling expired partway down it, which INDEX_BUDGET_EXHAUSTED counts.
So IndexingResult.validated true does not mean any one candidate was validated: on the bundled fluorapatite pattern a default run (the same day and build) reports it true while none of its twelve candidates carries a fit.
shift_allowance_assumed is why high is out of reach on real data with no measured shift: a cell found inside a widened window has absorbed the shift.
Reference positions handed to assess_peak_list, a shift the pair screen measured, or a declared SearchSpecSpec.shift_allowance_deg clears it.
Never take candidates[0] because it is ranked first.
The ranking orders the hypotheses and the gate judges them, which are different questions.
The order leads with corroboration and moves a supercell by its checks, which is closer to the gate’s reading than a panel ranking alone, and still not it.
On the default corundum run above, the certified trigonal R lattice comes back first, found by two of the three engines, with a converged Le Bail fit at Rwp 0.28 that reads 11 of 85 reflections absent.
It grades low on four caveats, so best_or_none returns None.
predicted_but_absent has the shape a space-group extinction takes when read against a lattice group that does not know about it, and the extinction screen (The extinction symbol) is what separates that from an oversized cell.
geometric_ambiguity is there on every candidate because the ceiling stopped the run before the ambiguity walk, and fom_panel_disagrees because the panel’s members split.
None of the four says the cell is wrong; they say what has not been established, which is the difference between this answer and a confident one.