How to add a hump to the background#

Append a HumpComponent to Instrument.extra_components, and free instrument.extra_components.* in a stage after the peak widths have refined.

A hump is one broad Gaussian added on top of whichever background model you use. Use it for scattering with no Bragg peaks behind it: an amorphous phase, a glass or polymer capillary, a cryostat window or a sample holder. Other programs call it a background peak.

Declare the hump#

Give it a starting position, height and width. Seed the position where the feature is, from a blank scan or from the pattern itself:

import rietx as rx
from rietx.schemas.instrument import HUMP_FWHM_MIN

capillary = rx.Instrument.debye_scherrer(wavelength=0.412359)
capillary.extra_components = [rx.HumpComponent(
    label="Kapton halo",
    position=rx.Parameter(value=5.0, unit="deg"),
    height=rx.Parameter(value=50.0, min=0.0, unit="counts"),
    fwhm=rx.Parameter(value=2.0, min=HUMP_FWHM_MIN, unit="deg"))]
assert capillary.extra_components[0].height.vary is False

position and fwhm are in degrees 2θ, and height is the peak value in counts. Keep the min bounds: a height of zero switches the hump off, and the width is floored at HUMP_FWHM_MIN because the Gaussian divides by it. label is free text and never part of a parameter’s name.

The parameters are named instrument.extra_components.0.position, .height and .fwhm, numbered by the hump’s place in the list. All three start fixed, and a declared hump at zero height changes nothing.

Free it after the peak widths#

mccusker_structural frees declared humps in its extra_components stage, after the zero shift, cell and peak widths. No other preset frees them, so with another plan add a stage for instrument.extra_components.*, as How to add a stage to a refinement plan shows. Freeing the hump earlier lets its free position chase misfit from a wrong zero shift or cell. For the same reason instrument.background.*, which most presets free in their first stage, does not reach the humps.

No plan ever adds a hump. A plan frees only the humps you declared.

Compare it with more polynomial terms#

A free position, height and width will lower Rwp on any pattern, so compare the hump with the same number of extra polynomial terms. This runs APS 11-BM’s scan of NIST SRM 640c silicon in a Kapton capillary, which shows the capillary’s halo near 5° 2θ. rietx.examples.fetch downloads the file, about 1.5 MB, the first time it is asked for.


import rietx as rx
from rietx.examples import fetch
from rietx.schemas.instrument import HUMP_FWHM_MIN

data = rx.read_pattern(fetch("11BM_Si640c.xy"))


def silicon():
    """SRM 640c at its certified lattice parameter, 5.4311946 Å."""
    a = rx.Parameter(value=5.4311946)
    right = rx.Parameter(value=90.0)
    return rx.Structure(phases=[rx.Phase(
        name="Si", space_group="F d -3 m :2",
        cell=rx.Cell(a=a, b=a, c=a, alpha=right, beta=right, gamma=right),
        scale=rx.Parameter(value=3e-4, min=0.0),
        atoms=[rx.Atom(label="Si", species="Si", x=rx.Parameter(value=0.125),
                       y=rx.Parameter(value=0.125), z=rx.Parameter(value=0.125),
                       biso=rx.Parameter(value=0.44, min=0.0))])])


def halo():
    """Seeded where the empty capillary's scan peaks, near 5° 2θ."""
    return rx.HumpComponent(
        label="Kapton halo",
        position=rx.Parameter(value=5.0, unit="deg"),
        height=rx.Parameter(value=50.0, min=0.0, unit="counts"),
        fwhm=rx.Parameter(value=2.0, min=HUMP_FWHM_MIN, unit="deg"))


def fit_with(n_terms, humps):
    instrument = rx.Instrument.debye_scherrer(wavelength=0.412359)
    instrument.background = rx.BackgroundChebyshev.with_terms(n_terms)
    instrument.extra_components = humps
    ref = rx.Refinement(silicon(), instrument)
    ref.hold("phases.0.cell.*")
    return ref.fit(data, plan="mccusker_structural", two_theta_limits=(1.997, 49.996))


bisos = {}
for label, n_terms, humps in [("3 terms", 3, []), ("3 terms + hump", 3, [halo()]),
                              ("6 terms", 6, [])]:
    biso = bisos[label] = fit_with(n_terms, humps).parameter("phases.0.atoms.0.biso")
    print(f"{label:<15} Biso {biso.value:.3f} ± {biso.stderr:.3f} Ų")
3 terms         Biso 0.443 ± 0.100 Ų
3 terms + hump  Biso 0.446 ± 0.018 Ų
6 terms         Biso 0.448 ± 0.039 Ų

Silicon’s Biso is the only structural parameter in this fit. Its value barely moves, and its esd falls by a factor of 5.6 with the hump, against 2.5 with three more polynomial terms. A background that cannot describe the halo trades against the Bragg intensities, and the hump ends that trade.

Check what the hump is#

Rwp cannot tell a container halo from a missed reflection, so look for evidence outside the fit. For this pattern, 11-BM’s scan of the same capillary empty peaks near 5.0° 2θ, d ≈ 4.7 Å, the amorphous halo of the polyimide the capillary is made of. A scan of the beam path with no capillary shows no feature at all. Without such a check, a hump is three parameters that lower Rwp.

A hump narrower than 4.0 times the instrumental peak width at its position is reported as HUMP_TOO_NARROW. That width belongs to a reflection, so model it as a second phase instead.

Read the fitted position with care#

The refined position and width describe the background model, not the halo on its own:

humps = {}
for n_terms in (3, 6):
    result = fit_with(n_terms, [halo()])
    position, fwhm = humps[n_terms] = tuple(
        result.parameter(f"instrument.extra_components.0.{name}") for name in ("position", "fwhm"))
    print(f"{n_terms} terms: hump at {position.value:.2f} ± {position.stderr:.2f}°, "
          f"FWHM {fwhm.value:.2f} ± {fwhm.stderr:.2f}°")
3 terms: hump at 4.16 ± 0.10°, FWHM 5.59 ± 0.25°
6 terms: hump at 5.24 ± 0.04°, FWHM 1.91 ± 0.11°

Against three polynomial terms, the hump also absorbs the rise of the direct beam below 3°, which pulls its centre low and widens it. Against six, the polynomial takes the rise, and the hump moves onto the halo and narrows to a third of the width. Its esds cover neither shift. Quote the blank’s envelope for where the halo is, and keep the background low-order if you mean to quote the hump’s parameters.

The full script is examples/guides/hump.py.

See also#

  • How to use a measured blank as the background holds the empty capillary’s whole scan under the pattern, which describes the halo without three free parameters.

  • save_instrument_profile leaves humps out, because a hump belongs to the specimen and its holder, not to the diffractometer.