Peaks, indexing and a Le Bail check#
When the phase is unknown there is no CIF to start from, and the cell has to come from the peak positions alone. We pick the peaks of a synthetic pattern whose answer we know, index them, and read how rietx decides whether to name a single cell. Then we check the best candidate with a Le Bail fit. By the end you can pick and fit peaks, run an indexing search, and test a candidate cell against the whole pattern.
Written by Claude Code, Anthropic’s coding agent, for the rietx project.
You need rietx 1.10 or later, which the next cell installs. Notebook 01 introduces Le Bail fitting.
The data is synthetic aragonite, CaCO₃, made from its published structure: Dal Negro and Ungaretti (1971), American Mineralogist 56, 768, as entry 9000229 of the Crystallography Open Database (public domain). The displacement parameters are the isotropic equivalents of that entry’s anisotropic ones.
Runtime is under a minute on a laptop, most of it the indexing search.
%pip install rietx
A synthetic pattern#
Write aragonite’s structure to a CIF and read it back.
import tempfile
from pathlib import Path
import numpy as np
import rietx as rx
from rietx.indexing import SearchSpec, structure_from_candidate
work = Path(tempfile.mkdtemp())
cif = work / "aragonite.cif"
cif.write_text(
"data_aragonite\n_space_group_name_H-M_alt 'P m c n'\n"
"_cell_length_a 4.9616\n_cell_length_b 7.9705\n_cell_length_c 5.7394\n"
"_cell_angle_alpha 90\n_cell_angle_beta 90\n_cell_angle_gamma 90\n"
"loop_\n_atom_site_label\n_atom_site_type_symbol\n_atom_site_fract_x\n"
"_atom_site_fract_y\n_atom_site_fract_z\n_atom_site_B_iso_or_equiv\n"
"Ca1 Ca 0.25 0.4151 0.2403 0.67\n"
"C1 C 0.25 0.7627 0.0850 0.80\n"
"O1 O 0.25 0.9231 0.0952 1.25\n"
"O2 O 0.4729 0.6801 0.0870 1.19\n",
encoding="utf-8",
)
truth = rx.Structure.from_cif(cif)
predict computes a pattern from a structure and an instrument without fitting anything.
This instrument sets its own peak widths and a flat background of 200 counts.
We scale the phase so the strongest peak reaches about 20 000 counts above the background, then draw Poisson counts.
true_instrument = rx.Instrument.bragg_brentano(radiation="CuKa")
true_instrument.profile.w.value = 0.004
true_instrument.profile.x.value = 0.05
true_instrument.background = rx.BackgroundChebyshev.with_terms(1)
true_instrument.background.coefficients[0].value = 200.0
two_theta = np.arange(10, 80, 0.02)
y = rx.Refinement(truth, true_instrument).predict(two_theta) - 200.0
truth.phases[0].scale.value *= 20000 / y.max()
y = rx.Refinement(truth, true_instrument).predict(two_theta)
data = rx.PatternData(
two_theta=two_theta,
intensity=np.random.default_rng(0).poisson(y).astype(float),
)
data.plot()
From here on we pretend not to know the structure. The indexer should find the CIF’s cell, perhaps with its axes in another order.
Find the peaks#
pick_peaks finds every resolvable peak and fits each one for its position, with an esd.
It needs an instrument for the wavelength and a first guess at the peak width.
Give it a default copper instrument, as you would for an unknown sample.
Its widths do not match these peaks, so expect the picker to say so.
lab = rx.Instrument.bragg_brentano(radiation="CuKa")
peaks = rx.pick_peaks(data, lab)
peaks
PeakList: 100 peaks, 54 usable, 10–79.98° 2θ, λ = 1.54059 Å, source fitted
# two_theta two_theta_esd intensity fwhm origin flags
0 21.0733 0.0025 31.5 0.106 fitted
1 22.2914 0.0056 9.7 0.078 fitted
2 26.2221 0.0003 2174.3 0.095 fitted asymmetry_unmodelled
3 26.6070 0.0104 19.6 0.097 fitted asymmetry_unmodelled
4 27.2200 0.0003 1236.9 0.097 fitted asymmetry_unmodelled
5 31.1394 0.0015 95.0 0.108 fitted
6 32.7500 0.0012 169.8 0.097 fitted asymmetry_unmodelled
7 32.7501 1045.0429 32.3 0.097 fitted unresolved_shoulder, not_separable, no_intensity, position_unmeasured, asymmetry_unmodelled
8 32.7501 246.3679 136.9 0.097 fitted unresolved_shoulder, no_intensity, position_unmeasured, asymmetry_unmodelled
9 33.1521 0.0004 1063.5 0.097 fitted asymmetry_unmodelled
10 33.1522 0.0004 1065.8 0.097 fitted asymmetry_unmodelled, duplicate_line
11 36.1488 0.0006 946.7 0.125 fitted
12 37.2579 0.0024 323.6 0.098 fitted duplicate_line
13 37.2650 0.0011 298.2 0.100 fitted asymmetry_unmodelled
14 37.8984 0.0334 958.3 0.117 fitted duplicate_line
15 37.9064 0.0041 848.5 0.100 fitted asymmetry_unmodelled, duplicate_line
16 37.9071 0.0004 869.0 0.097 fitted asymmetry_unmodelled
17 37.9074 0.0005 866.2 0.098 fitted asymmetry_unmodelled, duplicate_line
18 38.3949 0.0069 551.9 0.117 fitted duplicate_line
19 38.4023 0.0005 618.3 0.097 fitted asymmetry_unmodelled
20 38.4033 0.0006 642.5 0.098 fitted asymmetry_unmodelled, duplicate_line
21 38.5784 0.0099 377.6 0.117 fitted position_at_bound, duplicate_line
22 38.6307 0.0007 402.6 0.097 fitted
23 41.1956 0.0008 271.1 0.093 fitted asymmetry_unmodelled
24 42.9067 0.0008 426.2 0.099 fitted
25 45.4857 0.0022 71.0 0.097 fitted
26 45.4858 0.0031 70.5 0.097 fitted asymmetry_unmodelled, duplicate_line
27 45.8585 0.0003 1528.8 0.097 fitted
28 45.8585 0.0004 1529.6 0.097 fitted asymmetry_unmodelled, duplicate_line
29 48.3096 0.0007 643.1 0.099 fitted fit_failed, asymmetry_unmodelled
30 48.3098 0.0008 633.4 0.099 fitted
31 48.4600 0.0011 420.1 0.099 fitted position_at_bound
32 48.4659 9.7164 264.3 0.099 fitted fit_failed, no_intensity, asymmetry_unmodelled
33 48.4659 17.5602 145.6 0.099 fitted fit_failed, unresolved_split, no_intensity, asymmetry_unmodelled, kalpha2_residual
34 49.8874 0.0051 33.4 0.099 fitted asymmetry_unmodelled
35 49.9000 0.0061 32.3 0.099 fitted position_at_bound, axial_tail, asymmetry_unmodelled, duplicate_line
36 50.2395 0.0006 624.5 0.099 fitted
37 50.2396 0.0007 626.3 0.099 fitted asymmetry_unmodelled, duplicate_line
38 51.9124 0.0035 56.9 0.097 fitted asymmetry_unmodelled
39 52.4903 0.0012 656.6 0.097 fitted asymmetry_unmodelled, duplicate_line
40 52.4910 0.0019 675.9 0.095 fitted asymmetry_unmodelled, duplicate_line
41 52.4911 0.0004 665.8 0.097 fitted
42 52.9157 0.0013 146.2 0.097 fitted
43 52.9163 0.0062 143.9 0.095 fitted asymmetry_unmodelled, duplicate_line
44 52.9872 0.0295 531.5 0.242 fitted fit_failed, asymmetry_unmodelled
45 53.0541 0.0043 296.8 0.095 fitted asymmetry_unmodelled, duplicate_line
46 53.0548 0.0008 292.0 0.097 fitted
47 53.9604 0.0025 48.1 0.097 fitted asymmetry_unmodelled
48 56.1487 0.0032 58.0 0.100 fitted duplicate_line
49 56.1503 0.0018 56.5 0.102 fitted
50 56.4382 0.0052 17.3 0.102 fitted asymmetry_unmodelled, duplicate_line
51 56.4382 0.0045 17.8 0.100 fitted
52 56.8078 0.0023 34.6 0.100 fitted
53 56.8427 0.0290 79.3 0.102 fitted duplicate_line
54 59.2422 0.0015 146.6 0.101 fitted
55 59.5416 0.0169 8.3 0.101 fitted position_at_bound
56 59.5943 0.0180 9.7 0.117 fitted duplicate_line
57 60.1989 0.0035 36.6 0.117 fitted
58 61.8181 0.0013 120.3 0.110 fitted
59 62.9099 0.0019 58.1 0.106 fitted asymmetry_unmodelled
60 62.9100 0.0020 58.0 0.107 fitted asymmetry_unmodelled, duplicate_line
61 63.3339 0.0010 149.2 0.106 fitted asymmetry_unmodelled, duplicate_line
62 63.3340 0.0010 148.3 0.107 fitted
63 65.0380 0.0092 8.6 0.094 fitted
64 66.1075 0.0030 73.2 0.091 fitted asymmetry_unmodelled, duplicate_line
65 66.1127 0.0023 77.8 0.094 fitted
66 66.1476 0.0041 125.0 0.157 fitted duplicate_line
67 66.1985 0.0034 66.1 0.091 fitted position_at_bound, asymmetry_unmodelled, duplicate_line
68 66.2091 0.0029 63.9 0.094 fitted
69 66.5200 0.0074 53.5 0.091 fitted position_at_bound, asymmetry_unmodelled, duplicate_line
70 66.5346 0.0062 72.3 0.157 fitted asymmetry_unmodelled, duplicate_line
71 66.5420 0.0019 71.8 0.094 fitted
72 68.6421 0.0015 74.9 0.102 fitted duplicate_line
73 68.6424 0.0015 73.9 0.104 fitted
74 69.0998 0.0012 103.4 0.102 fitted duplicate_line
75 69.1002 0.0012 102.0 0.102 fitted
76 69.1006 0.0013 101.7 0.104 fitted asymmetry_unmodelled, duplicate_line
77 69.5884 0.0026 40.0 0.102 fitted
78 69.5885 0.0028 39.6 0.112 fitted asymmetry_unmodelled, duplicate_line
79 69.5898 0.0027 40.8 0.102 fitted duplicate_line
80 70.0819 0.0023 50.1 0.112 fitted asymmetry_unmodelled, duplicate_line
81 70.0832 0.0023 53.8 0.102 fitted
82 70.2868 0.0161 12.7 0.115 fitted
83 70.8746 0.0049 35.6 0.115 fitted
84 72.2883 0.0038 16.3 0.058 fitted asymmetry_unmodelled
85 72.2901 0.0052 12.0 0.090 fitted duplicate_line
86 72.4957 0.0118 5.3 0.090 fitted
87 75.2904 0.0010 150.1 0.107 fitted
88 76.6592 0.0033 63.4 0.108 fitted asymmetry_unmodelled
89 76.6595 0.0034 63.0 0.107 fitted asymmetry_unmodelled, duplicate_line
90 76.7800 0.0021 117.0 0.108 fitted
91 76.7801 0.0021 117.5 0.107 fitted asymmetry_unmodelled, duplicate_line
92 77.0994 0.0011 191.9 0.108 fitted
93 77.0996 0.0012 193.3 0.107 fitted asymmetry_unmodelled, duplicate_line
94 78.0359 0.0021 124.3 0.116 fitted duplicate_line
95 78.0378 0.0014 122.5 0.116 fitted
96 78.6934 0.0053 20.8 0.116 fitted
97 78.7218 0.0458 33.2 0.116 fitted no_intensity
98 79.3618 0.0220 65.7 0.116 fitted duplicate_line
99 79.4031 0.0012 203.1 0.125 fitted asymmetry_unmodelled
info[PEAK_UNRESOLVED_SHOULDER]: 2 line(s) never separated from a neighbour by half a
FWHM.
at: 32.7501°, 32.7501°
help: Keep them, since their σ already carries the penalty. Quote neither line of a
pair as an independent line.
means: Lines were kept in groups where they stayed within half a FWHM of a
neighbour. Their positions are correlated with their neighbours' and are less
precise than their σ alone suggests.
warning[PEAK_NOT_SEPARABLE]: 1 component(s) were added by a re-seed pass inside a much
stronger neighbour's own profile, in a group whose fit is
still refuted.
at: 32.7501°
help: Leave them in the model, because removing one displaces the real line it sits
on. If many fire, check the declared instrument profile, since undeclared
axial divergence reproduces this.
means: Components improved their group's fit but did not survive the nested
comparison against a fit without them. They stay in the model and are
withheld from indexing.
info[PEAK_UNRESOLVED_SPLIT]: 1 component(s) lie within Sparrow's limit of a comparable
group-mate, so each pair is reported as one line whose
σ(2θ) reaches both.
at: 48.4659°
help: Collect with a finer step, or declare the instrument profile, to resolve real
close pairs. If many appear, suspect flat-topped or other non-pseudo-Voigt
lines.
means: Pairs of comparable components closer than two lines of the fitted shape can
be and still show a dip. Each pair is reported as one line whose position
uncertainty reaches both.
info[PEAK_AXIAL_TAIL]: 1 weak component(s) sit on the axial-divergence tail side of a
much stronger group-mate.
at: 49.9000°
help: Check each group's fit, then exclude the flagged components with
use_for_indexing=False on a well-aligned Bragg-Brentano pattern. On SRM 660c,
five such components carried 125 ppm of cell bias.
means: Weak components lie within 3.5 FWHM of a stronger group-mate on the side
axial divergence points. They may be real lines and are reported rather than
removed.
info[PEAK_KALPHA2_RESIDUAL]: 1 weak component(s) sit at a strong group-mate's
predicted Kα2 maximum, at a few per cent of its area.
at: 48.4659°
help: Exclude them before indexing, unless the pattern gives you a reason to believe
a genuine reflection hides under the doublet.
means: Components sit where the Kα2 partner of a stronger line is already modelled,
so they are that doublet's residual rather than new lines. Reported rather
than removed.
warning[PEAK_ASYMMETRY_UNMODELLED]: 47 line(s) leave a significant odd residual after
fitting.
at: 26.2221°, 26.6070°, 27.2200°, 32.7500°, 32.7501°, 32.7501°, 33.1521°, 33.1522°, 37.2650°, 37.9064°, 37.9071°, 37.9074°, 38.4023°, 38.4033°, 41.1956°, 45.4858°, 45.8585°, 48.3096°, 48.4659°, 48.4659°, 49.8874°, 49.9000°, 50.2396°, 51.9124°, 52.4903°, 52.4910°, 52.9163°, 52.9872°, 53.0541°, 53.9604°, 56.4382°, 62.9099°, 62.9100°, 63.3339°, 66.1075°, 66.1985°, 66.5200°, 66.5346°, 69.1006°, 69.5885°, 70.0819°, 72.2883°, 76.6592°, 76.6595°, 76.7801°, 77.0996°, 79.4031°
help: Set instrument.geometry.axial_sl and axial_hl to the real aperture ratios,
then re-pick.
means: Lines show asymmetric residuals beyond the fitted shape, so their positions
are pulled towards the tail. Declaring the axial divergence terms is the
usual fix.
info[PEAK_EXPECTED_FALSE_LINES]: 54 of 54 fitted component(s) clear the pattern's
false-discovery bar (I/σ_I ≥ 3.03, Benjamini–Hochberg
at q = 0.01 over 446 resolution elements). The
expected number of noise lines among those kept is at
least 0.54.
help: Keep weak_line components unless the list is known to carry flank or tail
junk. Count longer to tell weak lines from noise.
means: The pattern's own false-discovery bar on each component's intensity over its
esd, and the expected number of noise lines among those kept, a lower bound,
since detection selected every component first. Components under the bar are
flagged weak and stay usable. A component that absorbs a shape or background
misfit carries real intensity and is not reached.
warning[PEAK_WIDTH_LAW_MISMATCH]: The measured FWHM of 0.1566° differs by a factor of
4.9 from the 0.0322° that the instrument's U, V, W,
X and Y predict. Seeds and windows were scaled by
4.87 to match the data.
at: median of the 12 most prominent lines
help: Run lab_calibrate on a standard, or set U, V, W, X and Y from one. A factor
near 13 means the ProfileTCHZ default (W = 1e-3 deg²), a synchrotron line, is
still in place.
means: The fitted widths do not follow the declared instrumental width law. Either
the specimen is broadened, which is a finding, or the declared instrument is
wrong, which is a setup error. The message carries the ratio so the two can
be told apart. On a refinement the census is compared with the fitted widths,
size and strain included, so it fires only when no phase's widths came within
the factor. A phase the data cannot see is left out of that comparison.
info[PEAK_KALPHA2_ALIAS]: 30 candidate(s) were dropped as the Kα2 maximum of a
stronger line, so each reported position is a Kα1 position.
at: 33.2400°, 38.0200°, 38.5000°, 38.7400°, 41.3000°, 43.0200°, 45.9800°, 48.6000°, 50.3800°, 52.0600°, 52.6400°, 53.2000°, 54.1200°, 56.3000°, 56.9600°, 59.4000°, 60.3400°, 61.9800°, 63.0800°, 63.5200°, 66.7200°, 68.8200°, 69.3000°, 69.7800°, 70.2800°, 71.0800°, 75.5000°, 77.3400°, 78.2600°, 79.6400°
help: Confirm with an incident-side monochromator (Instrument.bragg_brentano with
Kα1-only radiation) if one of these lines matters.
means: Detection found candidates at the Kα2 positions of stronger lines and dropped
them before any fit. The drop is reported because a genuine weak line can
alias onto one.Each row is one peak: its position, the esd of that position, its area and its width.
flags records anything the fit noticed.
Some flags, such as duplicate_line and fit_failed, take a row out of indexing.
The header line counts the rows left usable.
The diagnostics under the table explain the list.
PEAK_WIDTH_LAW_MISMATCH says the default instrument predicts narrower peaks than these, so the picker scaled its windows to the data.
PEAK_KALPHA2_ALIAS lists candidates dropped as the Kα2 partner of a stronger line, so each reported position is a Kα1 position.
Open a diagnostic to read what its code means.
Diagnostics: peaks and indexing lists every code.
Measure lines you name#
fit_peaks fits exactly the positions you give it, and nothing else.
Use it to measure lines you have chosen: a few reflections for a size analysis, or one line as a quick check.
chosen = rx.fit_peaks(data, lab, [26.22, 27.22, 33.13])
chosen
PeakList: 3 peaks, 3 usable, 10–79.98° 2θ, λ = 1.54059 Å, source fitted
# two_theta two_theta_esd intensity fwhm origin flags
0 26.2221 0.0003 2174.3 0.095 manual asymmetry_unmodelled
1 27.2200 0.0006 1241.3 0.096 manual asymmetry_unmodelled, unnamed_neighbour
2 33.1512 0.0031 1110.2 0.092 manual asymmetry_unmodelled, unnamed_neighbour
warning[PEAK_ASYMMETRY_UNMODELLED]: 3 line(s) leave a significant odd residual after
fitting.
at: 26.2221°, 27.2200°, 33.1512°
help: Set instrument.geometry.axial_sl and axial_hl to the real aperture ratios,
then re-pick.
means: Lines show asymmetric residuals beyond the fitted shape, so their positions
are pulled towards the tail. Declaring the axial divergence terms is the
usual fix.
warning[PEAK_WIDTH_LAW_MISMATCH]: The measured FWHM of 0.1566° differs by a factor of
4.9 from the 0.0322° that the instrument's U, V, W,
X and Y predict. Seeds and windows were scaled by
4.87 to match the data.
at: median of the 12 most prominent lines
help: Run lab_calibrate on a standard, or set U, V, W, X and Y from one. A factor
near 13 means the ProfileTCHZ default (W = 1e-3 deg²), a synchrotron line, is
still in place.
means: The fitted widths do not follow the declared instrumental width law. Either
the specimen is broadened, which is a finding, or the declared instrument is
wrong, which is a setup error. The message carries the ratio so the two can
be told apart. On a refinement the census is compared with the fitted widths,
size and strain included, so it fires only when no phase's widths came within
the factor. A phase the data cannot see is left out of that comparison.
info[PEAK_KALPHA2_ALIAS]: 30 candidate(s) were dropped as the Kα2 maximum of a
stronger line, so each reported position is a Kα1 position.
at: 33.2400°, 38.0200°, 38.5000°, 38.7400°, 41.3000°, 43.0200°, 45.9800°, 48.6000°, 50.3800°, 52.0600°, 52.6400°, 53.2000°, 54.1200°, 56.3000°, 56.9600°, 59.4000°, 60.3400°, 61.9800°, 63.0800°, 63.5200°, 66.7200°, 68.8200°, 69.3000°, 69.7800°, 70.2800°, 71.0800°, 75.5000°, 77.3400°, 78.2600°, 79.6400°
help: Confirm with an incident-side monochromator (Instrument.bragg_brentano with
Kα1-only radiation) if one of these lines matters.
means: Detection found candidates at the Kα2 positions of stronger lines and dropped
them before any fit. The drop is reported because a genuine weak line can
alias onto one.Each line is marked manual, because its position came from you.
A line flagged unnamed_neighbour shares its fitting window with a line you did not name, and that line’s intensity had nowhere to go but into it.
A whole peak list rebuilt from edited positions in code meets this at every line it leaves out.
The same work in the GUI#
Editing a peak list is easier by hand, and rietx has a graphical mode for it.
Run rietx gui in a terminal, open or create a project with your pattern, and choose the Peaks tab.
The plot then becomes an editing surface:
Gesture |
Does |
|---|---|
click empty space |
add a line, measured by |
drag a marker |
move it |
shift-click |
exclude a line from indexing |
right-click |
remove it |
The same tab runs the indexing search, draws a candidate’s predicted lines over the pattern, and adopts a candidate as the starting point of a Le Bail fit. Every gesture also has a typed equivalent in the panel. The GUI panel by panel covers the tab in full.
Index#
index_pattern searches for cells that index the usable peaks.
A search over every crystal system can take minutes, so we narrow it.
SearchSpec limits it to orthorhombic cells, with a volume between 200 and 250 ų and no axis longer than 8.5 Å.
Those limits come from knowing the answer.
With a real unknown, start from the formula and a wide window, and let the search run longer.
budget_seconds caps each engine’s search of each crystal system, and its default is 30 s.
This search finishes inside that on a quiet laptop.
On a laptop running other work, one engine ran past 30 s and the whole call took 58 s.
A search stopped by its cap cannot rule anything out, so we allow 120 s.
spec = SearchSpec(
systems=("orthorhombic",),
min_volume=200,
max_volume=250,
max_d_axis=8.5,
budget_seconds=120,
)
indexing = rx.index_pattern(peaks, data=data, instrument=lab, spec=spec)
indexing
engines: dichotomy, svd, trial_error systems: orthorhombic lines: 54 validated: yes
1. [ high ] orthorhombic P 5.73930 4.96157 7.97039 Å 90.000 90.000 90.000° V = 226.96 ų
found by dichotomy, svd, trial_error; indexed 51/54 lines; chi2_red 0.46; Le Bail Rwp 0.2049, 0 of 105 reflections absent
CELL: orthorhombic P 5.73930 4.96157 7.97039 Å 90.000 90.000 90.000°
info[PEAK_POSITION_PRECISION]: The median σ(2θ) is 0.0015° and the worst is 0.0169°.
The median σ(Q)/Q is 5.31e-05 and σ(Q) is 0.002 of the
mean line spacing.
at: 2θ 10.00-79.98°
means: States the measured position precision against what an indexing search needs.
A cell search matches within an assumed systematic allowance rather than
within σ, so this is context for reading a search result, not a refusal.
info[INDEX_SHIFT_FROM_PAIRS]: A systematic shift of 0.0043° 2θ was measured from 8
harmonic reflection pairs agreeing to 0.0030° (of 14
admitted, z = 8.3 against 200 structureless replicates,
p = 0.005). The cause is not named: no template was
refuted.
at: allowance = 0.0043° 2θ (measured), best template = cos_theta
help: Extend the 2θ range if the cause matters.
means: The size of a systematic 2θ shift was measured from pairs of harmonic
reflections in the peak list, with no reference positions. That size set the
search window. The pair method fixes the amplitude but cannot name the cause,
because a zero-point shift and a specimen displacement look alike over a
short range. A wider 2θ range separates them.
info[INDEX_SYSTEMS_NOT_COVERED]: 6 crystal system(s) were not searched.
at: cubic, hexagonal, trigonal, tetragonal, monoclinic, triclinic
help: Search the missing systems before you conclude anything about the specimen.
means: The search covered only some crystal systems. A failure to find a cell means
only that none was found in those systems. It says nothing about the
specimen, and it is no evidence of a mixture. Search the missing systems
before you draw a conclusion.
info[INDEX_CELL_SYSTEMATIC_UNQUANTIFIED]: A Bragg-Brentano cell carries about ±85 ppm
that no esd reports. Sweeping the goniometer
radius over 180-320 mm moves Rwp by only
0.029 points, and specimen displacement
absorbs the change 4.6×.
at: R = 217.5 mm (declared, not measured by the fit)
help: Quote the cell to no better than this level, or calibrate against a certified
standard (lab_calibrate with the certified cell held fixed) and carry the
correction.
means: The esd on an indexed cell comes from the line positions alone. It measures
precision, not accuracy. Instrument geometry adds a systematic error, such as
the goniometer radius of a Bragg-Brentano diffractometer, that the data
cannot identify and no esd reports. Quote the cell to a coarser level than
its esd, or calibrate against a certified standard.
warning[INDEX_IMPURITY_LINES]: 53 observed peak(s) have no calculated reflection
nearby after a whole-pattern Le Bail fit of this cell.
on: candidate orthorhombic P V=227.0
at: orthorhombic P, V = 227.0 ų, cell 5.7393 4.9616 7.9704 Å, 90.000 90.000 90.000°, strongest region near 10.100°
help: If a handful of lines remain, subtract the solved phase and index the
remainder. If most of the pattern is unmatched, treat the cell as wrong.
means: After a whole-pattern Le Bail fit of this cell, some observed peaks sit where
it predicts no reflection. A handful of such lines points to a second phase.
Many of them point to a wrong cell. This package does not index multi-phase
patterns, so subtract the solved phase before you index the remainder.The top candidate is the true cell, with its axes in a different order. All three search engines found it.
The verdict line names it.
rietx names a single cell only when exactly one candidate reaches high confidence, and this one does.
Its row indexes 51 of the 54 usable lines, and none of the reflections it predicts is missing from the indexer’s quick Le Bail fit.
The three lines it leaves are the candidate’s unindexed_two_theta, printed below.
The one near 36° is two reflections 0.05° apart, fitted as one line between them.
The verdict depends on every search finishing.
An engine stopped by budget_seconds makes the row say search_incomplete, and the verdict line then says NO CELL.
The candidate’s INDEX_IMPURITY_LINES warning comes from the indexer’s own quick Le Bail fit, whose Rwp in the candidate’s row is poor.
A poor fit leaves many peaks unmatched, so that count is not evidence of a second phase.
Our own Le Bail fit below settles it.
best_or_none() returns a cell only when the gate passes.
Here it returns the candidate.
print(indexing.best_or_none())
CellCandidate
cell: [5.7392979, 4.9615748, 7.9703933, 90, 90, 90]
cell_esd: [0.00020205575, 0.0001722875, 0.00031808872, 0, 0, 0]
system: 'orthorhombic'
centring: 'P'
lattice_group: 'P m m m'
volume: 226.96457
volume_esd: 0.019150129
af: [0.030358651, 0.040621964, 0.015741296, 0, 0, 0]
engine_cell: [5.7392979, 4.9615748, 7.9703933, 90, 90, 90]
change_of_basis: [[1, 0, 0], [0, 1, 0], [0, 0, 1]]
n_indexed: 51
n_lines: 54
n_search_unindexed: 1
unindexed_two_theta: [26.606972, 36.148784, 70.286849]
chi2_red: 0.46489036
shift_template: 'cos_theta'
shift_coefficient: -0.00055261417
shift_esd: 0.002002964
fom: 7 FigureOfMerit
[0] FigureOfMerit
name: 'm20'
value: 292.49411
n_lines: 20
n_possible: 35
k_sigma: 3
mean_discrepancy: 1.4639996e-05
blind_spot: 'counts lattice-possible lines, so it is blind to space-group extinctions; its ⟨ΔQ⟩ is a plain mean, so one impurity line among the first twenty wrecks it; its mean discrepancy is trimmed by 2 line(s) to match what the search was allowed to leave unindexed, so that many badly-fitting lines are invisible to it'
[1] FigureOfMerit
name: 'f_n'
value: 377.71612
n_lines: 20
n_possible: 35
k_sigma: 3
mean_discrepancy: 0.001512852
blind_spot: 'lives in 2θ, so a refined zero shift can manufacture a large value by absorbing a mismatch a Q-space figure would show; its mean discrepancy is trimmed by 2 line(s) to match what the search was allowed to leave unindexed, so that many badly-fitting lines are invisible to it'
[2] FigureOfMerit
name: 'indexed_fraction'
value: 0.94444444
n_lines: 54
n_possible: 103
k_sigma: 3
mean_discrepancy: -1
blind_spot: "a big enough cell indexes everything — measured, a wrong phase scored 83.7 % against the truth's 79.2 % with 390 predicted lines to the truth's 23"
[3] FigureOfMerit
name: 'indexed_intensity_fraction'
value: 0.93563866
n_lines: 54
n_possible: 103
k_sigma: 3
mean_discrepancy: -1
blind_spot: "a big enough cell indexes everything — measured, a wrong phase scored 83.7 % against the truth's 79.2 % with 390 predicted lines to the truth's 23"
[4] FigureOfMerit
name: 'predicted_seen_fraction'
value: 0.49514563
n_lines: 54
n_possible: 103
k_sigma: 3
mean_discrepancy: -1
blind_spot: 'legitimately absent reflections count against a correct cell: space-group extinctions (unknown until the extinction symbol is determined) and reflections too weak to detect'
[5] FigureOfMerit
name: 'm_rev'
value: 2.8805593
n_lines: 20
n_possible: 31
k_sigma: 3
mean_discrepancy: 0.0021123377
blind_spot: "penalises a correct cell for space-group extinctions, the same blind spot as predicted_seen_fraction and confirmed by the paper that defines it; its δ is floored at the median σ(Q), which is this package's addition and not Oishi-Tomiyasu's, because δ_rev vanishes on fp-exact data exactly as M₂₀'s ⟨ΔQ⟩ does; and its scale is not M₂₀'s — correct cells score ~1.7-15.8 where M̃₂₀ reaches 556, so a de Wolff threshold would reject almost all of them; its mean discrepancy is trimmed by 2 line(s) to match what the search was allowed to leave unindexed, so that many badly-fitting lines are invisible to it"
[6] FigureOfMerit
name: 'm_sym'
value: 772.12419
n_lines: 20
n_possible: 31
k_sigma: 3
mean_discrepancy: 0.0021123377
blind_spot: 'the product of the forward and reversed figures over one window, so it inherits both their blind spots and is not independent evidence from m_rev; a candidate can reach it by being mediocre in both directions rather than good in either; its mean discrepancy is trimmed by 2 line(s) to match what the search was allowed to leave unindexed, so that many badly-fitting lines are invisible to it'
found_by: ['dichotomy', 'svd', 'trial_error']
bravais: BravaisOpinion
system: 'orthorhombic'
system_loosest: 'orthorhombic'
system_gemmi: 'orthorhombic'
system_spglib: 'orthorhombic'
ambiguous: False
methods_disagree: False
reduced_cell: [4.9615748, 5.7392979, 7.9703933, 90, 90, 90]
lebail: LeBailValidation
rwp: 0.20490532
gof: 4.7776264
space_group: 'P m m m'
n_reflections: 105
predicted_but_absent: 0
unmatched_observed: 53
unmatched_observed_two_theta: <53 floats 10.1…78.48>
status: 'converged'
n_stages: 4
confidence: 'high'
diagnostics: 1 Diagnostic
[0] warning[INDEX_IMPURITY_LINES]: 53 observed peak(s) have no calculated reflection nearby after a whole-pattern Le Bail fit of this cell.
at: orthorhombic P, V = 227.0 ų, cell 5.7393 4.9616 7.9704 Å, 90.000 90.000 90.000°, strongest region near 10.100°
help: If a handful of lines remain, subtract the solved phase and index the remainder. If most of the pattern is unmatched, treat the cell as wrong.
The candidate prints every field, its figures of merit included.
When no candidate reaches high confidence, best_or_none() returns None and the result carries INDEX_ABSTAINED.
Indexing explains the figures of merit behind the ranking.
Check the candidate with Le Bail#
structure_from_candidate turns a candidate into a structure for Le Bail fitting.
It uses the lattice’s highest-symmetry space group, so the fit tests the lattice and nothing else.
We test the first candidate because it is the true cell.
With a real unknown, test each candidate whose caveats you cannot rule out.
candidate = indexing.candidates[0]
structure = structure_from_candidate(candidate)
structure.phases[0].space_group
'P m m m'
As in notebook 01, start the background’s constant term at the 5th percentile of the counts, and run the plan twice.
instrument = rx.Instrument.bragg_brentano(radiation="CuKa")
instrument.background = rx.BackgroundChebyshev.with_terms(4)
c0 = float(np.percentile(data.intensity, 5))
instrument.background.coefficients[0].value = c0
ref = rx.Refinement(structure, instrument)
first = ref.fit(data, mode="lebail", plan="profile_only")
second = ref.fit(data, mode="lebail", plan="profile_only")
ref.history
t4604ebdd 13 nodes data=4604ebdd
n0000 root —
n0001 stage:bkg Rwp 0.7281
n0002 stage:zero Rwp 0.7149
n0003 stage:cell Rwp 0.7108
n0004 stage:profile_w Rwp 0.1538
n0005 stage:profile Rwp 0.0702
n0006 stage:closing Rwp 0.0568
n0007 stage:bkg Rwp 0.0793
n0008 stage:zero Rwp 0.0716
n0009 stage:cell Rwp 0.0691
n0010 stage:profile_w Rwp 0.0680
n0011 stage:profile Rwp 0.0666
*n0012 stage:closing Rwp 0.0533
The first pass starts from the default instrument’s peak widths, so its Rwp falls most at the two width stages. The second pass starts where the first ended.
Keep the pass with the lower Rwp, and return the working state to its node.
best = min([first, second], key=lambda r: r.statistics.rwp)
ref.checkout(best.node_id)
best
RefinementResult: converged (lebail)
stage bkg: converged (2 evals, ftol=1e-06)
stage zero: converged (4 evals, ftol=1e-06)
stage cell: converged (4 evals, ftol=1e-06)
stage profile_w: converged (5 evals, ftol=1e-06)
stage profile: converged (5 evals, ftol=1e-06)
stage closing: converged (5 evals, ftol=solver default), max|Δθ|/esd=0.000
diagnostics: none
provenance: rietx 1.10.0, backend=numpy, solver=lm
Rwp 0.0533 / Rexp 0.0429 (GoF 1.24), Rp 0.0341, χ² 1.5, DW 1.40
parameters: 13
path value at_bound
phases.0.cell.a 5.739529(82) false
phases.0.cell.b 4.961587(71) false
phases.0.cell.c 7.97060(12) false
instrument.zero_shift 0.00002(52) false
instrument.profile.u 0.0086(26) false
instrument.profile.v -0.0077(26) false
instrument.profile.w 0.00560(65) false
instrument.profile.x 0.0515(20) false
instrument.profile.y -0.0061(58) false
instrument.background.c0 195.01(80) false
instrument.background.c1 -0.0(14) false
instrument.background.c2 -5.6(12) false
instrument.background.c3 -0.2(12) falseThe fit raises no diagnostic. The profile’s Y term, the Lorentzian width that grows as tan θ, refined to zero within its esd. The pattern was made with no such term, so zero is the right answer.
The table under the summary starts with the refined lattice parameters. They match the CIF’s cell, in the candidate’s order of axes. Now look at the fit.
best.plot()
The fit puts a reflection under every peak and leaves only small residuals, so the lattice is right.
Some ticks stand over no peak, such as those near 11° and 15.5°.
They are reflections P m m m allows and aragonite’s glide planes remove.
What this does not settle is the space group. That is a question about which reflections are absent, and it comes after the cell is refined.