Source code for ess.snspowder.powgen.peaks

# SPDX-License-Identifier: BSD-3-Clause
# Copyright (c) 2024 Scipp contributors (https://github.com/scipp)
"""Peak fitting and removal.

This module is specialized to POWGEN.
"""

import math
from collections.abc import Iterable
from itertools import combinations_with_replacement

import scipp as sc
from scippneutron.peaks import FitParameters, FitRequirements, FitResult, fit_peaks
from scippneutron.peaks.model import Model


[docs] def theoretical_vanadium_dspacing( *, hkl_range: int = 10, min_d: sc.Variable | None = None ) -> sc.Variable: r"""Return the d-spacing values for vanadium in an ideal case. Based on the bcc structure of vanadium, the values are .. math:: d = \frac{a}{\sqrt{h^2 + k^2 + l^2}} where :math:`a = 3.0272` Å is the lattice constant of vanadium :cite:`Arblaster:2018` and :math:`h+k+l` is even. Parameters ---------- hkl_range: h, k, l are each limited to the integer interval ``[0, hkl_range]``. min_d: If given, only return values greater than this. Returns ------- : Array of vanadium d-spacing values. Has dimension ``'dspacing'``. """ a = 3.0272 d_values = { a / math.sqrt(h**2 + k**2 + l**2) for h, k, l in combinations_with_replacement(range(hkl_range), 3) # noqa: E741 if (h + k + l) % 2 == 0 and (h + k + l) > 0 } d = sc.array(dims=['dspacing'], values=sorted(d_values), unit='angstrom') if min_d is not None: return d[d > min_d] return d
[docs] def fit_vanadium_peaks( data: sc.DataArray, *, peak_estimates: sc.Variable | None = None, windows: sc.Variable | None = None, background: Model | str | Iterable[Model] | Iterable[str] | None = None, peak: Model | str | Iterable[Model] | Iterable[str] | None = None, fit_parameters: FitParameters | None = None, fit_requirements: FitRequirements | None = None, ) -> list[FitResult]: """Fit coherent scattering peaks of vanadium. This function wraps :func:`scippneutron.peaks.fit_peaks` and provides default parameters for vanadium at POWGEN. Parameters ---------- data: A 1d data array where ``data.data`` is the dependent variable and ``data.coords[data.dim]`` is the independent variable for the fit. Must be 1-dimensional and not binned. peak_estimates: Initial estimates of peak locations. A peak will be fitted for each estimate. Must be a 1d variable with dimension ``data.dim``. If ``None``, estimates are derived using :func:`theoretical_vanadium_dspacing`. windows: If a scalar, the size of fit windows. A window is constructed for each peak estimate centered on the estimate with a width equal to ``windows`` (adjusted to the data range and to maintain a separation between peaks, see :attr:`scippneutron.peaks.FitParameters.neighbor_separation_factor`). If a 2d array, the windows for each peak. Must have sizes ``{data.dim: len(data), 'range': 2}`` where ``windows['range', 0]`` and ``windows['range', 1]`` are the lower and upper bounds of the fit windows, respectively. The windows are not adjusted automatically in this case. Defaults to ``sc.scalar(0.02, unit='angstrom')``. background: The background model or models. Defaults to ``('linear', 'quadratic')``. That is, a fit with a linear background is attempted, and if the fit fails, a quadratic background is tried. peak: The peak model or models. Defaults to ``'gaussian'``. fit_parameters: Parameters for the fit not otherwise listed as function arguments. fit_requirements: Constraints on the fit result. Returns ------- : A :class:`FitResult` for each peak. """ if peak_estimates is None: peak_estimates = theoretical_vanadium_dspacing( hkl_range=10, min_d=sc.scalar(0.41, unit='angstrom') ) if windows is None: windows = sc.scalar(0.02, unit='angstrom') if background is None: background = ('linear', 'quadratic') if peak is None: peak = 'gaussian' fits = fit_peaks( data, peak_estimates=peak_estimates, windows=windows, background=background, peak=peak, fit_parameters=fit_parameters, fit_requirements=fit_requirements, ) return fits