The instrument#
This page gives the fields of Instrument and of everything inside it: the radiation source, the geometry, the instrumental peak width, the background and declared extra peaks.
Patterns and structures gives the other two objects a refinement needs. The parameter map gives the name, unit and typical range of each refinable instrument parameter. Instrument preset fields describes each constructor argument.
Every object on this page refuses a field it does not know, and checks its bounds when it is built.
Instrument#
Instrument is everything about the measurement except the sample.
Field |
Type |
Default |
Meaning |
|---|---|---|---|
|
|
required |
the radiation, chosen by its |
|
|
a capillary with no aberrations |
how the specimen sits in the beam |
|
|
0.0 deg, unbounded |
a constant 2θ offset, the one position error every geometry has |
|
|
see Peak width |
the instrument’s contribution to every peak’s width |
|
|
four Chebyshev terms, fixed at 0 |
the curve under the peaks |
|
list of |
|
declared peaks added on top of the background |
A source and a correction derived for the other radiation are refused together.
Geometry.surface_roughness on a neutron source raises, because a thermal neutron beam penetrates centimetres of the specimen where an X-ray beam penetrates microns.
Constructors#
Five constructors build a plausible instrument. They differ in which aberrations exist at all, and none of them frees anything.
Constructor |
Geometry |
Radiation |
Other arguments |
|---|---|---|---|
|
capillary |
one wavelength you pass |
|
|
flat plate, reflection |
an anode name (default |
goniometer radius (default 217.5 mm), |
|
flat plate, transmission |
an anode name (default |
µt, thickness, packing fraction, |
|
capillary |
one neutron wavelength |
goniometer and capillary radii, µR, |
|
capillary |
a time-of-flight bank’s DIFC and angle |
DIFA, TZERO, DIFB, flight paths, capillary radius, µR, a |
from rietx import Instrument
synchrotron = Instrument.debye_scherrer(wavelength=0.4139090)
lab = Instrument.bragg_brentano(radiation="CuKa", monochromator_two_theta=26.6)
assert [line.wavelength.value for line in lab.source.lines] == [1.5405929,
1.5444274]
assert round(lab.source.polarization.value, 4) == 0.5557
assert lab.source.kbeta == "monochromator"
monochromator_two_theta sets the polarisation fraction K to 1/(1 + cos²2θm), and declares that Kβ is removed.
26.6° is graphite (002) at Cu Kα.
The same crystal sits near 12.1° at Mo Kα, where K is 0.511, so compute the angle for the anode in use.
Without a monochromator, K is 0.5.
The anode names are AgKa, CoKa, CrKa, CuKa, FeKa and MoKa, and each has a Kα1-only form such as CuKa1, for a beam monochromated before the sample.
Capabilities.anodes lists them for the version you run.
Instrument.debye_scherrer applies no capillary absorption until you give a capillary radius or µR.
Its capillary offsets can refine only once you give the goniometer radius.
X-ray source#
Source is a constant-wavelength X-ray source, kind="xray_cw".
Field |
Type |
Default |
Meaning |
|---|---|---|---|
|
|
required |
one entry per emission line, at least one |
|
|
0.5, in [0, 1] |
the fraction K of (5.1); 0.5 is an unpolarised beam |
|
|
|
what stands in the beam as far as Kβ goes |
|
|
on |
anomalous scattering, (4.8) |
|
|
|
declared λ/n orders; refused on this radiation (Monochromator harmonics) |
|
|
|
the discriminator |
Source.kbeta decides whether the contamination screen looks for Kβ and tungsten Lα lines.
None means undeclared, and the screen runs.
"monochromator" skips both searches.
"filter" and "mirror" are recorded and change nothing, because the screen still finds an injected leak at 2 % of Kα behind either.
The screen reads the field where it is handed the source: pick_peaks, diagnose(source=) and auto_background(source=).
The GUI’s import wizard fills kbeta from the file’s header.
A monochromator after the sample gives "monochromator", otherwise a filter gives "filter" and a mirror "mirror".
A file that lists a monochromator before the sample stays None.
Source.primary_wavelength is the first line’s wavelength as a float, and every d-spacing is quoted against it.
Source.wavelength_parameters is the list of live wavelength Parameter objects, one per line, for code that writes a wavelength.
Emission lines#
Field |
Type |
Default |
Meaning |
|---|---|---|---|
|
|
required |
λ in Å, positive |
|
|
1.0, at or above 0 |
intensity relative to line 0 |
A bare number is accepted for the wavelength, so EmissionLine(wavelength=1.5406) builds a fixed one, bounded below at 1e-3 Å.
Line 0’s weight is locked at 1, because it is degenerate with the phase scales.
The presets give a Kα2 line a weight of ka2_ratio, in [0, 1].
Each line diffracts at its own Bragg angle, so a doublet’s splitting grows with tan θ (2.5) and is never a fixed 2θ offset.
Wavelength refinement#
EmissionLine.wavelength and NeutronSource.wavelength are fixed by default.
Bragg’s law (2.4) fixes only the product of λ with the reciprocal cell, so on one pattern a free λ beside a free cell is an exactly flat direction.
With the cell held, λ refines on one pattern.
That is how a beamline’s wavelength is calibrated: a standard of certified cell, such as NIST SRM 640c silicon, holds the scale the cell would otherwise trade against λ.
A glob that would free λ beside a free cell skips λ, as a plan’s stage does.
A solve that finds both free refuses, naming the degeneracy:
from rietx import Instrument, Structure
from rietx.examples import examples_dir
from rietx.params.vector import ParameterTable
wavelength = "instrument.source.lines.0.wavelength"
structure = Structure.from_cif(examples_dir() / "fluorapatite.cif")
held = ParameterTable(structure, Instrument.debye_scherrer(wavelength=1.5406))
assert held.set_vary([wavelength], True) == [wavelength] # the cell is held
held.check_wavelength_against_cell() # a solve accepts it
free = ParameterTable(structure, Instrument.debye_scherrer(wavelength=1.5406))
free.set_vary(["phases.0.cell.a"], True)
assert free.set_vary([wavelength], True) == [] # skipped beside a free cell
held.set_vary(["phases.0.cell.a"], True)
try:
held.check_wavelength_against_cell()
except ValueError as exc:
assert "cannot both be free" in str(exc)
else:
raise AssertionError("a free λ beside a free cell was accepted")
Patterns of one specimen fitted together share one cell, and that breaks the degeneracy too. Hold one wavelength and free at most N − 1 of N, holding the wavelength of the pattern that determines the cell best. A free wavelength also needs its pattern’s cell shared with a pattern whose wavelength is held. How to fit several patterns of one specimen together shows the procedure.
The rule has the same shape as the line weights. One member of a set is pinned to fix a scale the data cannot set, and the rest are free. A line weight’s scale lives inside one source, so the table locks line 0. A wavelength’s scale lives in the cell. Several instruments share the cell, so the check runs where the joint fit is assembled.
Only line 0’s wavelength can refine. The ratio between lines of one source is atomic physics: the Kα1/Kα2 pairs come from one column of the NIST tabulation, known to about 20 ppm. A secondary line’s wavelength is therefore locked, as line 0’s weight is.
On an X-ray source, a λ/2 harmonic can only be a second EmissionLine at its own fixed wavelength.
That wavelength does not follow line 0 as it refines.
A Harmonic declared on a neutron source is derived from the fundamental and follows it.
Anomalous dispersion#
Dispersion is on by default.
Field |
Type |
Default |
Meaning |
|---|---|---|---|
|
|
|
the tabulation f′ and f″ come from |
|
|
|
measured (f′, f″) per element, such as |
f′ and f″ are constants of the element and the wavelength, looked up once when a stage compiles, and never refined.
An override key is a bare element symbol, because f′ and f″ are core-level effects.
Near an absorption edge the table is wrong in principle, since f″ there depends on the compound.
A wavelength inside an absorption-edge interval of the table raises, and an override lifts that refusal for its element.
dispersion=None declines the correction, reproduces the numbers of versions before 1.0 exactly, and reports DISPERSION_NEGLECTED.
Neutron source#
NeutronSource is a constant-wavelength neutron source, kind="neutron_cw".
It is a class of its own because almost every field of an X-ray source has no meaning for neutrons.
Field |
Type |
Default |
Meaning |
|---|---|---|---|
|
|
required |
λ in Å, fixed (Wavelength refinement) |
|
|
|
declared λ/n monochromator orders (Monochromator harmonics) |
|
|
|
the discriminator |
Read-only properties let code written for an X-ray source run unchanged.
Property |
Value |
|---|---|
|
the fundamental, then one line per declared harmonic; the fundamental’s weight is 1 |
|
the wavelength as a float |
|
the live wavelength |
|
1.0, fixed |
|
|
NeutronSource.lines builds new objects at each access.
A write through lines[0].wavelength is lost, so write a refined wavelength through wavelength_parameters.
K = 1 because a monochromator does not polarise neutrons the way the Thomson cross-section polarises X-rays.
The Lorentz-polarisation factor (5.1) then reduces to the Lorentz factor 1/(sin²θ cos θ), and no separate correction code exists.
K is fixed by force, so Refinement.set_vary cannot free it.
There is no anomalous dispersion.
The neutron analogue is a complex scattering length near a nuclear resonance.
It belongs to a few nuclides rather than to the source, so there is no field to set, and DISPERSION_NEGLECTED stays quiet.
The scattering amplitude is the only part of the calculation that changes. A nucleus is a point scatterer on this scale, so its scattering length b is one number per species, independent of Q, and it may be negative. The neutron scattering length gives the amplitude and its source. A species with no tabulated scattering length raises when the model compiles, naming the species.
Instrument.constant_wavelength_neutron builds a capillary geometry, so the cylindrical absorption correction and the capillary offsets apply unchanged.
from rietx import Instrument
bt1 = Instrument.constant_wavelength_neutron(wavelength=2.0780, fwhm_deg=0.3)
assert bt1.source.kind == "neutron_cw"
assert bt1.source.polarization.value == 1.0
assert bt1.source.dispersion is None
Pass fwhm_deg.
It sets ProfileTCHZ.w to the square of the width you observed and leaves x at its default, so the starting profile is a Gaussian of that width at every angle.
A neutron instrument’s lines are typically 0.2-0.5° wide, against about 0.03° for the default profile.
The evaluation windows of each stage are sized from the starting width, so a 0.3° line started from the default is not found.
The width bounds are an X-ray instrument’s.
No width has a chosen ceiling. Each stops at the width of the 2θ axis, 180° for x and y and 180² deg² for u, v and w, which no peak reaches.
u, v and y have no chosen floor either, so a long-wavelength neutron line’s model builds: on the ILL D1B Co₃O₄ set at λ = 2.52 Å the published model has V = −0.501.
The neutron constructor builds its profile with ProfileTCHZ.coarse, which builds all five widths from values.
from rietx.schemas.instrument import ProfileTCHZ
try:
ProfileTCHZ(u=1.576)
except ValueError as refusal:
assert "ProfileTCHZ.coarse" in str(refusal)
else:
raise AssertionError("a bare width is refused")
d1b = ProfileTCHZ.coarse(u=1.576, v=-0.501, w=0.475)
assert d1b.v.min == -(180.0 ** 2) and d1b.w.max == 180.0 ** 2
Geometry.surface_roughness is refused on a neutron source, because neither roughness model has a regime where the beam penetrates centimetres.
Time-of-flight bank#
TOFSource is one detector bank of a neutron time-of-flight diffractometer, kind="neutron_tof".
The whole moderator spectrum reaches the specimen, and a bank at a fixed angle separates the reflections by arrival time.
The source has no wavelength.
A reflection’s position comes from its d-spacing through the bank’s calibration, (2.6): TOF = DIFC·d + DIFA·d² + TZERO + DIFB/d.
This version reads a bank and refines none.
Refinement.fit and every other entry point that computes a pattern refuse a neutron_tof source by name.
Field |
Type |
Default |
Meaning |
|---|---|---|---|
|
|
required |
the linear term in µs/Å, fixed, at least 1 |
|
|
0, fixed |
the quadratic term in µs/Ų, usually small and often negative |
|
|
0, fixed |
the constant offset in µs |
|
|
0, fixed |
GSAS-II’s 1/d term, in µs·Å |
|
|
required |
the bank’s fixed scattering angle, in (0, 180)° |
|
|
|
primary flight path in m, a record of the file |
|
|
|
secondary flight path in m, a record of the file |
|
|
all zero |
the GSAS type-3 peak-shape coefficients |
|
|
|
the moderator spectrum the bank’s data still carries |
|
|
|
the discriminator |
A bare number is accepted for each of the four constants and becomes a fixed Parameter, because a constant read from an instrument-parameter file is a calibration.
The bank angle is a float and never refines, because it is already folded into the refined DIFC.
The GSAS manual writes the offset ZERO, GSAS-II writes Zero, and Mantid writes TZERO.
The field takes Mantid’s spelling, because it is the only one that cannot be confused with Instrument.zero_shift.
That field is a 2θ offset in degrees.
The readers map all three.
DIFB is carried because dropping it would be silent.
Neither the GSAS manual nor Mantid documents a fourth term, and GSAS-II evaluates one.
A reader of the three-term relation would put the peaks of a GSAS-II project that sets difB in the wrong place, with nothing said.
Member |
Value |
|---|---|
|
1.0, fixed, as for |
|
|
|
|
|
|
|
the flight time in µs for a d-spacing in Å |
|
the d-spacing for a flight time |
With DIFA or DIFB non-zero the relation has no useful closed-form inverse.
d_from_tof iterates by successive substitution, and raises rather than return an unconverged value.
On a real calibration, with a small DIFA against a DIFC of thousands, it converges in a few iterations.
Time-of-flight peak shape#
ProfileTOF holds the GSAS type-3 coefficients: ProfileTOF.alpha0, ProfileTOF.alpha1, ProfileTOF.beta0, ProfileTOF.beta1, ProfileTOF.sig0, ProfileTOF.sig1, ProfileTOF.sig2, ProfileTOF.gam0, ProfileTOF.gam1 and ProfileTOF.gam2.
Each is a term of a polynomial in the reflection’s d-spacing, where ProfileTCHZ uses the angle:
All ten default to 0 and fixed, because an instrument-parameter file’s profile is a calibration refined against a standard.
The three sig terms are variances and are bounded below at 0.
A bare number becomes a fixed Parameter with the field’s unit and bounds.
In this version the coefficients are carried and listed in a bank’s parameter table, and nothing evaluates them.
Incident spectrum#
IncidentSpectrum is the moderator spectrum a bank’s data still carries, and whether it carries one is a fact about the file.
Field |
Type |
Default |
Meaning |
|---|---|---|---|
|
|
0 |
the GSAS |
|
|
|
the file’s |
|
|
|
start of the flight-time window the spectrum was fitted over, in µs |
|
|
|
end of that window, in µs |
The file gives the window in milliseconds, and the reader converts it.
A type the GSAS manual does not define is refused by number.
Types 1 and 2 take 11 coefficients and types 3 to 5 take 12, and a list of another length is refused rather than padded.
The default, type 0 with no coefficients, is what a reduction that already divided by a vanadium measurement leaves.
ISIS GEM, SNS NOMAD and POWGEN write it.
A LANSCE-style file writes ITYP 1 with a full ICOFF block.
read_gsas_tof_iparm holds to the documented layout, and refuses a block whose fifth pair (P10, P11) is non-zero.
rietx.io.legacy.read_lansce_iparm carries such a block as written.
Nothing in this version evaluates a spectrum.
Building a bank#
Instrument.tof_neutron_bank is the constructor, with the same shape as Instrument.constant_wavelength_neutron.
A multi-bank experiment is several instruments, one per bank, because DIFC, the angle and the whole profile differ per bank.
A bank’s parameter table carries its own rows: instrument.source.difc, instrument.source.difa, instrument.source.tzero, instrument.source.difb, instrument.source.profile_tof.* and instrument.source.incident_spectrum.p*.
It does not carry the constant-wavelength rows whose roles these play: instrument.profile.* (the widths), instrument.zero_shift (the zero) and the two specimen position aberrations.
A pattern naming one of those matches nothing on a bank, and vary=True written on one of them is refused by name.
from rietx import Instrument
# a synthetic bank at 90°: DIFC ≈ 505.555·L·sin θ for a total path L in m
bank = Instrument.tof_neutron_bank(
difc=12000.0, difa=-1.5, tzero=-5.0,
two_theta_bank_deg=90.0, l1_m=30.0, l2_m=3.5)
assert bank.source.kind == "neutron_tof"
assert round(float(bank.source.d_from_tof(bank.source.tof_from_d(3.1355))), 4) == 3.1355
read_gsas_tof_iparm fills a bank in from a GSAS instrument-parameter file.
Monochromator harmonics#
A crystal monochromator set to pass λ also reflects the higher orders of the same planes. The beam therefore carries a small λ/n component for every order that is not extinct, (2.7). At a given 2θ that component diffracts from planes of spacing λ/(2n sin θ), so its peak from a given hkl sits at lower 2θ than the fundamental’s, (2.8). Left out of the model, it is intensity where the model puts none.
Declare it with Harmonic, on the source or through the constructor, naming the orders the monochromator passes:
from rietx import Instrument
from rietx.schemas.instrument import Harmonic
# NIST BT-1's Cu(311) passes λ/2
bt1 = Instrument.constant_wavelength_neutron(
wavelength=1.54040, fwhm_deg=0.3, harmonics=[2])
assert [line.wavelength.value for line in bt1.source.lines] == [1.54040, 0.77020]
# a harmonic's λ is derived, so it is never free even when the fundamental is
assert [line.wavelength.vary for line in bt1.source.lines] == [False, False]
# general in n
thirds = Instrument.constant_wavelength_neutron(1.54040, harmonics=[2, 3])
assert len(thirds.source.lines) == 3
assert thirds.source.harmonics == [Harmonic(order=2), Harmonic(order=3)]
The order has no default, because which orders pass depends on the monochromator’s crystal.
Cu(311) and graphite pass λ/2.
In the diamond structure of Si and Ge, a reflection on all-odd indices has its second order extinct, so Si(111), Ge(311) and Ge(733) pass λ/3 and no λ/2.
harmonics=True is refused with that message.
Field |
Type |
Default |
Meaning |
|---|---|---|---|
|
|
none, required |
n in λ/n |
|
|
0.01, fixed, at or above 0 |
intensity relative to line 0, as for any emission line |
|
|
derived |
1/n, the factor on the fundamental wavelength |
The weight starts at 1 %, below every value reported for a real beamline, so the fit has to move it to reach one.
A weight that refines far above a few per cent says the peaks it fills are not harmonic peaks, and HARMONIC_FRACTION reports it at warning above 15 %.
Free the weights with instrument.source.lines.*.weight once the profile and the scale are settled, never in the first stage.
HARMONIC_FRACTION reports the refined fraction.
Source.harmonics_supported and NeutronSource.harmonics_supported say whether a radiation accepts a declaration at all: False and True.
The refusal reads them, and so does RadiationCapability.harmonic_contamination, the answer capabilities().radiations gives.
Both classes have a harmonics field, so read support from the flag.
A harmonic is an emission line and not a phase. The usual workaround is an extra phase with a doubled lattice parameter, which gets the positions right and the intensities wrong. A cell 2a has d′ = 2d, so λ diffracts from it where λ/2 diffracts from the real d, but its structure factors belong to a cell that does not exist. A line at λ/n diffracts the same hkl list with the same |F|², because |F|² depends on the reflection through sin θ/λ = 1/2d and not on the wavelength that reaches it. So positions and intensities come out together, for one refinable number.
An empty list is off, and gives the spectrum the source always had, bit for bit.
Three declarations are refused.
An
orderof 1. n = 1 is the fundamental, and a second copy of line 0 is degenerate with the phase scales.A repeated order. Two lines at one wavelength with two weights for one physical component are a flat direction.
Any harmonic on an X-ray source. One f′ + if″ is shared across every emission line of a phase. That is exact only while f is real, and λ against λ/2 is a 100 % change in wavelength, usually with absorption edges between. On an X-ray source, declare the λ/n component as a plain
EmissionLinewithdispersion=None, where f = f₀ serves both lines. The refusal’s message says so.
Whether a monochromator passes a harmonic at all is arithmetic on its reflection. Cu(311) doubles to the allowed (622), so the second order passes. A diamond-structure crystal cut on all-odd indices, such as Ge(733), cancels its own second order. Monochromator harmonics has both.
Geometry#
Geometry.kind is one of "debye_scherrer", "bragg_brentano" and "flat_plate_transmission", and it decides which other fields apply.
A field that belongs to another geometry is refused.
Every geometry:
Field |
Type |
Default |
Meaning |
|---|---|---|---|
|
one of three strings |
|
which of the three geometries |
|
|
|
R in mm; required for |
|
|
0.0, at or above 0 |
Finger-Cox-Jephcoat axial divergence: the specimen and receiving-slit half-lengths over R |
|
|
0.6, in (0, 1] |
the solid fraction of the specimen, used to estimate µR and µt |
A capillary, kind="debye_scherrer":
Field |
Type |
Default |
Meaning |
|---|---|---|---|
|
|
0.0 mm, unbounded |
the sin 2θ half of (2.15), positive downstream |
|
|
0.0 mm, unbounded |
the cos 2θ half, positive toward increasing 2θ |
|
|
|
µR of the packed specimen, (5.5); 0 is off |
|
|
|
the bore radius in mm, used to estimate µR |
A flat plate, kind="bragg_brentano" or kind="flat_plate_transmission":
Field |
Type |
Default |
Meaning |
|---|---|---|---|
|
|
0.0 mm, unbounded |
the specimen surface off the goniometer axis, as cos θ, (2.13) |
|
|
0.0, at or above 0 |
penetration into the specimen, as sin 2θ, (2.14) |
|
|
|
|
|
|
|
the specimen thickness in mm, used to estimate µt |
|
|
|
low-angle intensity loss, (5.10) or (5.11); |
A transmission plate has no displacement or transparency model: only Instrument.zero_shift moves its peaks.
No position correction carries a default bound, and the transparency and the axial ratios keep their floor of 0.
Before 1.10, Instrument.zero_shift was held to ±0.5°, a displacement and each capillary offset to ±1 mm, and the transparency to 0.05.
Where a scan cannot separate a position error from the cell, the fit reports FLAT_DIRECTION and the cell’s esd grows to show it, with or without a bound.
On 11-BM NAC over 2-24° 2θ with the zero shift and both capillary offsets free, 1.9.0’s bounded fit put the cell 1097 ppm from its reference with an esd of 2610 ppm.
The unbounded fit lets the offsets slide to +4.86 and +2.12 mm, puts the cell 5111 ppm away, and reports an esd of 2544 ppm.
Both fits reported the same two FLAT_DIRECTION findings.
The bound acted as a prior that the mount is aligned, which held on 11-BM, and it left the esd as wide as before.
That prior is a fact about your instrument, so state a bound yourself where you know the mount.
Absorption#
The absorption coefficients are plain floats rather than parameters, and their off states differ.
A capillary is off at µR = 0.
A flat plate in reflection is off at µt = ∞, and leaving mu_t unset states that.
A specimen thicker than the penetration depth needs no correction, because the correction is then exactly degenerate with the scale.
So mu_t unset differs from mu_t = 0, and mu_t = 0 under bragg_brentano is a specimen of no thickness and raises.
Under transmission, mu_t = 0 is legal and means a non-absorbing plate.
A transmission plate applies the sec θ growth of its illuminated volume whether or not mu_t is set.
µR cannot be refined because it is exactly a combination of the phase scale and the displacement parameters. µt is ill-conditioned against the same parameters, and the specimen’s thickness and composition give it. Capillary (cylindrical) absorption and flat-plate absorption give the factors.
estimate_mu_r computes a starting µR from a structure’s composition and the geometry.
An X-ray source reads the McMaster cross-sections.
A NeutronSource reads the Sears neutron cross-sections of (5.3), which weigh hydrogen and the light elements very differently.
It returns None rather than raising when it cannot answer:
the geometry is not a capillary, or has no capillary radius;
an element is outside the tabulation, a wavelength straddles an absorption edge, or the energy is outside 2-120 keV;
a neutron species is missing from the Sears table, or is a resonant absorber (Cd, Sm, Eu, Gd or Yb, refused at every wavelength);
the source kind has no table, as for a time-of-flight bank.
A refinement does the same calculation when mu_r is left None, and estimates µt from thickness_mm the same way.
A declared mu_r or mu_t is used on every source.
Capillary offsets#
Both capillary offsets default to zero and fixed.
At a synchrotron with a crystal analyser the displacement error is eliminated, so freeing them is a deliberate act.
Setting or freeing either without goniometer_radius_mm is refused, because (2.15) divides by R.
Surface roughness#
RoughnessSuortti and RoughnessPitschke model the low-angle intensity a rough flat specimen loses.
Each is exactly no correction at its default values.
Field |
Default |
Bounds |
Meaning |
|---|---|---|---|
|
0.5 |
[0, 1] |
the intensity fraction that survives at grazing incidence; 1 − a bounds the loss |
|
0.0 |
[0, 5] |
the depleted layer’s optical depth; sets where in angle the loss falls |
|
0.0 |
[0, 4] |
the strength; 0 is no correction |
|
0.05 |
[0, 0.3] |
the dimensionless roughness τ |
RoughnessSuortti.b gives the same loss at two values, one on each side of a peak, and past about 3 the correction does nothing.
ROUGHNESS_UNCONSTRAINED reports either dead branch.
ROUGHNESS_OUTSIDE_REGIME reports a τ above sin θ of the lowest fitted angle.
RoughnessSuortti.kind and RoughnessPitschke.kind name the model, so a saved instrument comes back as the model it was.
Peak width#
ProfileTCHZ is the instrument’s contribution to every peak’s width.
Its five widths are in degrees 2θ.
Field |
Type |
Default |
Meaning |
|---|---|---|---|
|
|
0.0 deg², in [−180², 180²] |
Gaussian tan²θ term, (3.1) |
|
|
0.0 deg², in [−180², 180²] |
Gaussian tan θ term |
|
|
1e-3 deg², in [1e-8, 180²] |
Gaussian constant term |
|
|
1e-3 deg, in [0, 180] |
Lorentzian 1/cos θ term, (3.4) |
|
|
0.0 deg, in [−180, 180] |
Lorentzian tan θ term |
|
|
|
the pseudo-Voigt, or the exact convolution |
Match the physics and not the letters. GSAS and FullProf swap the X and Y assignments, so match a value copied from another program by its angular dependence. Size broadening goes as 1/cos θ and strain as tan θ. Swapping them gives a different width function.
x is bounded below at 0, because as θ → 0 the Lorentzian width is x itself.
w stops at 1e-8 deg², the variance under which the Gaussian width is clamped and every width column is zero, (3.3).
A stored w bound below that is raised to it when the object is built.
u, v and y have no floor of their own, and each stops at the mirror image of its ceiling.
What has to stay positive is the total ΓG² and the total ΓL over the measured range, (3.5).
The default solver holds both at every reflection, and under solver="trf" RESOLUTION_NOT_POSITIVE reports where either fell below.
Read a negative u, v or y as a statement about the fitted resolution curve, and check the width it gives at both ends of the range.
A width is a Parameter carrying its own bounds, and a bare number for any width is refused by name.
The message names two ways out: ProfileTCHZ.coarse(...), which builds all five widths from values, or an explicit Parameter(value, min, max).
A Parameter that leaves its bounds out takes the default box, and a value outside it is refused.
shape is chosen when the model compiles and does not refine.
Both shapes use the same five widths, so switching shape never changes the parameter table.
Background#
Three models describe the curve under the peaks. The choice is about what the background is allowed to imitate. Background explains why a background flexible enough to follow the peaks biases the displacement parameters and the scales.
Instrument.background is a union discriminated on kind.
BackgroundChebyshev.kind, BackgroundPSpline.kind and BackgroundFixedPlusChebyshev.kind make a saved instrument come back as the model it was.
Chebyshev polynomial#
BackgroundChebyshev is a shifted Chebyshev polynomial over the fitted range.
BackgroundChebyshev.coefficients is a list of parameters, four fixed zeros by default.
BackgroundChebyshev.with_terms builds n of them, free.
rietx.background.select_chebyshev_order chooses the number of terms for a pattern by BIC.
P-spline#
BackgroundPSpline is a cubic spline co-refined with the structure under the second-difference penalty of (7.1).
Field |
Type |
Default |
Meaning |
|---|---|---|---|
|
|
required |
the knots in 2θ, at least two, strictly increasing |
|
|
required |
exactly |
|
|
1.0, ≥ 0 |
the penalty weight, a pure number against the data’s own weight per coefficient |
|
|
|
|
|
|
|
the scale of an additive 1/2θ term for the low-angle air-scatter rise |
One lambda_smooth means the same stiffness in counts, in a normalised unit and at any step size.
It does not make the penalty’s cost independent of the counts: on a well-counted pattern with a broad hump, the default of 1 can be stiffer than the data supports (What the pure number leaves to the counts).
The air term is absent unless you declare it, for example with Parameter(value=1e-3, min=0.0), or auto_background does.
auto_background declares it only when the pattern diagnostics report the rise.
Absent, it has no parameter name, so no plan can free it.
A P-spline fit without it reports AIR_SCATTER_UNDECLARED when its own residual says the term would have lowered χ².
BackgroundPSpline.for_range builds uniform knots over a 2θ range, about knot_step_deg apart (default 5°), with free coefficients and no air term.
from rietx.schemas.instrument import BackgroundPSpline
bkg = BackgroundPSpline.for_range(2.0, 40.0, knot_step_deg=5.0)
assert len(bkg.coefficients) == len(bkg.breakpoints) + 2
auto_background sizes a model to a pattern.
It takes the knot spacing from the amorphous-hump score, and adds the air term only where the diagnostics ask for it.
With kind="chebyshev" it gives a polynomial whose order BIC chose.
It returns a BackgroundChoice, which carries the model and the scores each choice was made on.
Assign BackgroundChoice.background to Instrument.background, as in ins.background = rx.auto_background(data).background.
Before 1.10 the call returned the model alone.
Field |
Meaning |
|---|---|
|
the model, a P-spline or a Chebyshev |
|
|
|
the score the knot spacing was chosen on, and the trigger above which the knots are 3° apart rather than 8° |
|
the knot spacing chosen, in degrees 2θ |
|
the score the 1/(2θ) term was chosen on, and the trigger above which it is declared |
|
whether the model carries the 1/(2θ) term |
|
the Chebyshev order scan: every order tried, with its BIC and Durbin-Watson d, and whether d reached the stop |
|
the count every coefficient starts at under |
A field the kind made no choice on is None: the P-spline fields on a Chebyshev, selection on a P-spline, and seed_level without seed=True.
The order scan is a BackgroundSelection, the same object rietx.background.select_chebyshev_order returns.
Field |
Meaning |
|---|---|
|
|
|
the order chosen, the minimum-BIC order of the scan |
|
the background channels the scan scored, after the peaks were masked |
|
one |
|
whether Durbin-Watson d reached the stop, 1.8, and ended the scan |
|
|
Fixed curve plus Chebyshev#
BackgroundFixedPlusChebyshev holds a fixed curve in the model and refines a polynomial on top of it.
The curve is a measured blank or an estimated baseline.
Holding it in the model keeps the counting statistics intact, where subtracting it would not.
Field |
Type |
Default |
Meaning |
|---|---|---|---|
|
|
required |
the curve’s 2θ values |
|
|
required |
the curve, one value per 2θ |
|
|
|
the curve’s own esds, non-negative; |
|
|
|
where the curve came from, free text |
|
|
1.0, fixed, ≥ 0 |
a multiplier on the curve as stored |
|
|
four fixed terms |
the refinable polynomial on top |
BackgroundFixedPlusChebyshev.from_pattern builds the model from a blank read with read_pattern.
It takes n_terms (default 4, free), vary_scale (default False), scale (default 1.0) and source.
It carries the blank’s own σ where the file had one, and leaves out channels in the blank’s own excluded regions.
How to use a measured blank as the background is the procedure.
With fixed_sigma set, each channel’s weight becomes σ² + s²·σf², with s the scale, (7.3).
The weight then depends on the scale, so Rwp is not comparable between fits that declare different scales.
Compare a scan of scales under one σ.
fixed_source separates a measurement from an estimate for anything that reads the model back.
The CIF export calls the background measured only when it is set.
A scale at its floor of 0 comes back as FLOOR_HIT.
A fitted channel outside the curve’s 2θ range, by more than about one of its steps, is refused, naming both ranges.
Crop the fit range, or supply a curve that covers it.
A curve of fewer than two points, or one whose 2θ values decrease, is refused when the model compiles.
Extra components#
Instrument.extra_components is a list of declared peaks, each added on top of whichever background model is in use.
It composes with all three models and leaves their design matrix untouched.
The empty default is off.
The list’s type is a union with two members, discriminated on HumpComponent.kind and PeakComponent.kind.
Member |
|
For |
Guide |
|---|---|---|---|
|
|
a broad Gaussian, for scattering with no Bragg peaks behind it |
|
|
|
a sharp pseudo-Voigt that no phase explains, such as a sample holder’s line |
Ask a version which members it has:
import rietx as rx
caps = rx.capabilities()
print(caps.features["extra_components"], caps.extra_component_kinds)
True ['hump', 'peak']
Capabilities.extra_component_kinds is read from the union itself, so it cannot miss a member.
A component is a declaration and never code. It is stored state, so it survives a save, a history checkout and a replay like any other field. A member holding an expression over this package’s own parameter names would fit the seam, because text serialises and both traced backends differentiate an expression tree. A member holding a Python function would not fit, because it does not serialise, it does not trace, and it has no JSON form. No expression member exists yet.
Field |
Default |
Bounds |
Meaning |
|---|---|---|---|
|
0.0 deg |
none |
the hump’s apparent 2θ |
|
0.0 counts |
≥ 0 |
its peak value; 0 is off |
|
5.0 deg |
≥ |
its width in 2θ |
|
|
free text, never part of a parameter’s name |
|
|
0.0 deg |
finite |
the line’s apparent 2θ for the primary emission line |
|
0.0 counts·deg |
≥ 0 |
the integrated intensity; 0 is off |
|
0.1 deg |
[0.005, 0.5] by default; finite bounds required |
the width in 2θ |
|
0.5 |
[0, 1] |
the Lorentzian fraction of the pseudo-Voigt |
|
|
place an image of the line at every emission line |
|
|
|
free text, never part of a parameter’s name |
A hump’s position has no bound, because it is not a Bragg position and the fitted range belongs to the pattern rather than the instrument.
A peak’s center and fwhm need finite bounds, because they size the window the peak is evaluated over, frozen when a stage compiles.
The window spans the centre’s bounds and reaches out by a multiple of fwhm.max that grows with eta.max, about 16 at η = 1.
At the default fwhm.max of 0.5° that is about ±8°, so state the bounds you believe.
A window holding no fitted channel is refused, naming the peak.
With all_lines set, each emission line’s image is scaled by that line’s weight times the ratio of its Lorentz-polarisation factor to the primary line’s, so area is the primary line’s area.
A declared peak adds no reflections, so it leaves the observation count effective_observations unchanged.
A width bound at 0 is raised to the member’s floor when the object is built, because each profile divides by its width.
Every refinable field defaults to fixed, so a declared component is inert until a stage frees it.
A height or area of 0 gives the same pattern as no component at all.
The parameters are named by place in the list, as instrument.extra_components.0.position is the first component’s position, so a new label never moves a refined value.
Most presets free instrument.background.* in their first stage, and that pattern does not reach them.
mccusker_structural frees declared components in its extra_components stage, and nothing in the package adds one.
save_instrument_profile leaves them out, because a component belongs to the specimen and its holder.
A hump that refines narrower than 4.0 times the instrumental width at its position raises HUMP_TOO_NARROW.
At that width it is a reflection with no cell behind it, and its parameters cannot be quoted.
Note
Before 1.4.0 the field was background_peaks and the class BackgroundPeak.
A project, history tree or .rxt written then still opens, and its saved plan still frees the hump it froze.
The stored parameter names are migrated on read as well as the values, because a value under a vanished name fails loudly while a pattern under one quietly stops matching.
Code that assigns the old attribute raises.