Source code for ess.powder.transform
# SPDX-License-Identifier: BSD-3-Clause
# Copyright (c) 2023 Scipp contributors (https://github.com/scipp)
"""Signal transformation algorithms for powder diffraction."""
import scipp as sc
from ess.reduce.uncertainty import UncertaintyBroadcastMode, broadcast_uncertainties
def _covariance_of_matrix_vector_product(A, v):
if A.variances is not None:
raise ValueError('The expression is not valid if the matrix has variances.')
v = sc.variances(v)
if A.dims[1] != v.dim:
A = A.transpose()
cov = (sc.sqrt(v) * A).values
cov = cov @ cov.T
return sc.array(
dims=[A.dims[0], A.dims[0] + '_2'], values=cov, unit=v.unit * A.unit**2
)
[docs]
def compute_pdf_from_structure_factor(
s: sc.DataArray,
r: sc.Variable,
*,
uncertainty_broadcast_mode=UncertaintyBroadcastMode.drop,
return_covariances=False,
) -> sc.DataArray:
'''
Compute a pair distribution function from a structure factor.
Computes the pair distribution function :math:`G(r)` as defined in
`Review: Pair distribution functions from neutron total scattering for the study of local structure in disordered materials <https://www.sciencedirect.com/science/article/pii/S2773183922000374>`_ (note, in the reference, the pair distribution function is denoted :math:`D(r)`, but since :math:`G(r)` is the more common name, that is what is used here).
from the overall scattering function :math:`S(Q)`.
The inputs to the algorithm are:
* A histogram representing :math:`S(Q)` with :math:`N` bins on a bin-edge grid with :math:`N+1` edges :math:`Q_j` for :math:`j=0\\ldots N`.
* The bin-edge grid over :math:`r` the output histogram representing :math:`G(r)` will be computed on.
In each output bin, the output is computed as:
.. math::
G_{i+\\frac{1}{2}} &= \\frac{2}{\\pi(r_{i+1}-r_i)} \\int_{r_i}^{r_{i+1}} \\int_{0}^\\infty (S(Q) - 1) Q \\sin(Q r) dQ \\ dr \\\\
&\\approx \\frac{2}{\\pi(r_{i+1}-r_i)} \\sum_{j=0}^{N-1} (S(Q)_{j+\\frac{1}{2}} - 1) (\\cos(\\bar{Q}_{j+\\frac{1}{2}} r_{i})-\\cos(\\bar{Q}_{j+\\frac{1}{2}} r_{i+1})) \\Delta Q_{j+\\frac{1}{2}}
Note that in the above expression the subscript :math:`_{j+\\frac{1}{2}}` is used to denote
quantities belonging to the :math:`j^\\text{th}` bin of a histogram, :math:`\\bar{Q}_{j+\\frac{1}{2}} = \\frac{Q_j + Q_{j+1}}{2}` and :math:`\\Delta Q_{j+\\frac{1}{2}} = Q_{j+1} - Q_{j}`.
Parameters
----------
s:
1D DataArray representing :math:`S(Q)` with a bin-edge coordinate called :math:`Q`
r:
1D array, bin-edges of output grid
uncertainty_broadcast_mode: UncertaintyBroadcastMode,
Choose how uncertainties in S(Q) are broadcast to G(r).
Defaults to ``UncertaintyBroadcastMode.drop``.
return_covariances:
bool, if True the second output of the function will be a 2D array representing the covariance
matrix of the entries in the first output.
Returns
-------
:
1D DataArray representing :math:`G(r)` with a bin-edge coordinate called :math:`r` that is the provided output grid, and optionally a 2D DataArray representing the covariances of :math:`G(r)`.
''' # noqa: E501
q = s.coords['Q']
qm = sc.midpoints(q)
dq = q[1:] - q[:-1]
dr = r[1:] - r[:-1]
v = sc.cos(qm * r * sc.scalar(1, unit='rad'))
v = v[r.dim, :-1] - v[r.dim, 1:]
ioq = (s - sc.scalar(1.0, unit=s.unit)) * dq
mat_ioq = broadcast_uncertainties(ioq, prototype=v, mode=uncertainty_broadcast_mode)
c = 2 / sc.constants.pi / dr
g = c * (v * mat_ioq).sum(q.dim)
g = sc.DataArray(g.data, coords={'r': r})
if return_covariances:
cov_g = _covariance_of_matrix_vector_product(c * v, ioq)
cov_g = sc.DataArray(
cov_g, coords={d: r.rename_dims({r.dim: d}) for d in cov_g.dims}
)
return g, cov_g
return g