How to check a pattern before fitting#
Call rietx.diagnose on the pattern, read its signal_cutoffs first, then draw the pattern under your model’s reflection ticks.
Neither step needs a fit, and neither changes the pattern.
Read the pattern diagnostics#
diagnose measures the pattern with no model.
Pass the wavelength, so it can screen for a Kβ or tungsten Lα line.
This runs on the GSAS-II tutorial fluorapatite pattern that ships with rietx:
import rietx as rx
from rietx.examples import examples_dir
data = rx.read_pattern(examples_dir() / "FAP.XRA")
d = rx.diagnose(data, wavelength=1.5406)
print(d)
PatternDiagnostics of 5753 points, 15–130.04° 2θ
checked on every pattern
signal_cutoffs: none
steps_per_fwhm: 4.65
n_peaks_measured: 76
contamination: none
signal_to_background: 146
matters on some instruments
dead_channels: not checked (no measured σ)
coverage_plateau: not checked (no measured σ)
coverage_regions: not checked (no measured σ)
air_scatter_gain: 0.745
amorphous_hump_score: 0.162
rietx's own measurements
baseline_lambda: 10000
noise_sigma_median: 12.0
peak_fraction: 0.524
n_peaks: 97
peak_density_per_deg: 0.843
The rows fall under three headings.
Read the first group on every pattern.
The second group matters on some instruments and specimens: a detector with dead cells, a bank of detectors, air scatter at low angle, or an amorphous hump.
The third holds numbers rietx measures for its own use, such as the baseline stiffness it chose.
A row reads none when its check ran and found nothing, and not checked when the check could not run.
Read these five rows in this order:
signal_cutoffsnames an end of the range where the counts collapsed and stayed down, behind a beamstop or past the detector’s edge. Itstwo_thetais where to set the fit range. Every other field is measured over the whole range, so after you trim, calldiagnoseagain on the trimmed range.steps_per_fwhmis how many 2θ steps span the top of a typical peak. The IUCr guidelines ask for five to ten [McCusker et al., 1999]. This pattern has 4.65, so the integrated intensities are slightly under-sampled, and the fit reportsPATTERN_UNDERSAMPLED. Only a new measurement at a finer step fixes it.contaminationlists Kβ or tungsten Lα lines, each at a fixed ratio to its parent reflection. Without a wavelength the row readsnot checked. A synchrotron wavelength reads the same, because it has no anode lines.dead_channelsnames short runs that count almost nothing with a tiny σ. Such a channel can carry thousands of times the weight of a live one, and it drags the background down to meet it. Exclude it as How to limit the fit range or leave out a region shows. The check needs the file to give a σ for each point. This pattern gives none, so the row readsnot checked (no measured σ).air_scatter_gainmeasures the low-angle rise from air scatter. Above 0.3,auto_backgroundadds a 1/2θ term to the background.
Check the starting cell against the peaks#
PatternData.plot with model= draws one row of reflection ticks per phase under the data, at the positions the model gives now:
structure = rx.Structure.from_cif(examples_dir() / "fluorapatite.cif")
ref = rx.Refinement(structure, rx.Instrument.bragg_brentano(radiation="CuKa"))
fig = data.plot(model=ref, two_theta_range=(25, 40)) # path="ticks.png" writes it
Each tick should sit under a peak. A cell 1 % too large puts every fluorapatite tick about 0.3° to the left of its peak between 25° and 40°. An offset that is the same at every angle points to a zero shift. An offset that grows with angle comes from the cell or the wavelength, which peak positions alone cannot tell apart. A peak with no tick is an impurity, or a phase you have not declared.
The plot also draws the model’s background at its starting values, as a dashed line.
Between the peaks it should run through the observed counts.
This model keeps the default background, which is zero everywhere, so none is drawn.
model= needs the linear intensity axis, and refuses any other y_scale.
Size the background from the diagnostics#
auto_background reads two of the diagnostics and sizes a background model to the pattern.
It returns the model with the scores it chose on:
choice = rx.auto_background(data, diagnostics=d)
background = choice.background
print(type(background).__name__, "with", len(background.coefficients), "coefficients")
print(f"knot step {choice.knot_step_deg:g}°: hump score "
f"{choice.amorphous_hump_score:.3f}, trigger {choice.hump_trigger:g}")
print(f"air-scatter term {choice.air_scatter}: gain "
f"{choice.air_scatter_gain:.3g}, trigger {choice.air_scatter_trigger:g}")
BackgroundPSpline with 41 coefficients
knot step 3°: hump score 0.162, trigger 0.05
air-scatter term True: gain 0.745, trigger 0.3
It sets the knots 3° apart because amorphous_hump_score is above its trigger, and 8° apart otherwise.
It adds the air-scatter term because air_scatter_gain is above its trigger.
When you will fit a narrower range, pass two_theta_limits, so the knots stay inside it.
Assign choice.background to Instrument.background.
The full script is examples/guides/check.py.
See also#
How to limit the fit range or leave out a region acts on what this check finds.
Pattern diagnostics gives every field and the measurements behind them.