sasdata.model_requirements module

class sasdata.model_requirements.ComposeRequirements(fst, snd)

Bases: ModellingRequirements

Composition of two models

__abstractmethods__ = frozenset({})
__annotations__ = {'first': <class 'sasdata.model_requirements.ModellingRequirements'>, 'second': <class 'sasdata.model_requirements.ModellingRequirements'>}
__doc__ = 'Composition of two models'
__firstlineno__ = 39
__init__(fst, snd)
__module__ = 'sasdata.model_requirements'
__static_attributes__ = ('first', 'second')
_abc_impl = <_abc._abc_data object>
first: ModellingRequirements
postprocess_iq(data: ndarray, full_data: SasData) ndarray

Perform both transformations in order

preprocess_q(data: ndarray, full_data: SasData) ndarray

Perform both transformations in order

second: ModellingRequirements
class sasdata.model_requirements.ModellingRequirements

Bases: ABC

Requirements that need to be passed to any modelling step

__abstractmethods__ = frozenset({'postprocess_iq', 'preprocess_q'})
__add__(other: Self) Self
__annotations__ = {'dimensionality': <class 'int'>, 'operation': <class 'sasdata.quantities.quantity.Operation'>}
__dict__ = mappingproxy({'__module__': 'sasdata.model_requirements', '__firstlineno__': 14, '__annotations__': {'dimensionality': <class 'int'>, 'operation': <class 'sasdata.quantities.quantity.Operation'>}, '__doc__': 'Requirements that need to be passed to any modelling step', '__add__': <function ModellingRequirements.__add__>, 'compose': <function ModellingRequirements.compose>, 'preprocess_q': <function ModellingRequirements.preprocess_q>, 'postprocess_iq': <function ModellingRequirements.postprocess_iq>, '__static_attributes__': (), '__dict__': <attribute '__dict__' of 'ModellingRequirements' objects>, '__weakref__': <attribute '__weakref__' of 'ModellingRequirements' objects>, '__abstractmethods__': frozenset({'postprocess_iq', 'preprocess_q'}), '_abc_impl': <_abc._abc_data object>})
__doc__ = 'Requirements that need to be passed to any modelling step'
__firstlineno__ = 14
__module__ = 'sasdata.model_requirements'
__static_attributes__ = ()
__weakref__

list of weak references to the object

_abc_impl = <_abc._abc_data object>
compose(other: Self) Self
dimensionality: int
operation: Operation
abstractmethod postprocess_iq(data: ndarray, full_data: SasData) ndarray

Transform the I(Q) values after running the model

abstractmethod preprocess_q(data: ndarray, full_data: SasData) ndarray

Transform the Q values before processing in the model

class sasdata.model_requirements.NullModel

Bases: ModellingRequirements

A model that does nothing

__abstractmethods__ = frozenset({})
__annotations__ = {}
__doc__ = 'A model that does nothing'
__firstlineno__ = 211
__module__ = 'sasdata.model_requirements'
__static_attributes__ = ()
_abc_impl = <_abc._abc_data object>
compose(other: ModellingRequirements) ModellingRequirements
postprocess_iq(data: ndarray, _full_data: SasData) ndarray

Do nothing

preprocess_q(data: Quantity[ndarray], _full_data: SasData) ndarray

Do nothing

class sasdata.model_requirements.PinholeModel(q_width: ndarray, nsigma: (<class 'float'>, <class 'float'>)=(2.5, 3.0))

Bases: ModellingRequirements

Perform a pin hole smearing

__abstractmethods__ = frozenset({})
__annotations__ = {}
__doc__ = 'Perform a pin hole smearing'
__firstlineno__ = 138
__init__(q_width: ndarray, nsigma: (<class 'float'>, <class 'float'>)=(2.5, 3.0))
__module__ = 'sasdata.model_requirements'
__static_attributes__ = ('nsigma_high', 'nsigma_low', 'q', 'q_calc', 'q_width', 'weight_matrix')
_abc_impl = <_abc._abc_data object>
postprocess_iq(data: ndarray, full_data: SasData) ndarray

Perform smearing transform

preprocess_q(q: ndarray, full_data: SasData) ndarray

Perform smearing transform

class sasdata.model_requirements.SesansModel

Bases: ModellingRequirements

Perform Hankel transform for SESANS

__abstractmethods__ = frozenset({})
__annotations__ = {}
__doc__ = 'Perform Hankel transform for SESANS'
__firstlineno__ = 62
__module__ = 'sasdata.model_requirements'
__static_attributes__ = ('H', 'H0', 'q')
_abc_impl = <_abc._abc_data object>
postprocess_iq(data: ndarray, full_data: SasData) ndarray

Apply the SESANS transform to the computed I(q)

preprocess_q(spin_echo_length: ndarray, full_data: SasData) ndarray

Calculate the q values needed to perform the Hankel transform

Note: this is undefined for the case when spin_echo_lengths contains exactly one element and that values is zero.

class sasdata.model_requirements.SlitModel(q_length: ndarray, q_width: ndarray, nsigma: (<class 'float'>, <class 'float'>)=(2.5, 3.0))

Bases: ModellingRequirements

Perform a slit smearing

__abstractmethods__ = frozenset({})
__annotations__ = {}
__doc__ = 'Perform a slit smearing'
__firstlineno__ = 173
__init__(q_length: ndarray, q_width: ndarray, nsigma: (<class 'float'>, <class 'float'>)=(2.5, 3.0))
__module__ = 'sasdata.model_requirements'
__static_attributes__ = ('nsigma_high', 'nsigma_low', 'q', 'q_calc', 'q_length', 'q_width', 'weight_matrix')
_abc_impl = <_abc._abc_data object>
postprocess_iq(data: ndarray, full_data: SasData) ndarray

Perform smearing transform

preprocess_q(q: ndarray, full_data: SasData) ndarray

Perform smearing transform

sasdata.model_requirements._(second: SesansModel, first: ModellingRequirements) ModellingRequirements
sasdata.model_requirements._q_perp_weights(q_edges, qi, w)
sasdata.model_requirements.bin_edges(x)

Determine bin edges from bin centers, assuming that edges are centered between the bins.

Note: this uses the arithmetic mean, which may not be appropriate for log-scaled data.

sasdata.model_requirements.compose(second: ModellingRequirements, first: ModellingRequirements) ModellingRequirements
sasdata.model_requirements.compose(second: NullModel, first: ModellingRequirements) ModellingRequirements
sasdata.model_requirements.compose(second: SesansModel, first: ModellingRequirements) ModellingRequirements

Compose to models together

This function uses a reverse order so that it can perform dispatch on the second term, since the classes already had a chance to dispatch on the first parameter

sasdata.model_requirements.geometric_extrapolation(q, q_min, q_max, points_per_decade=None)

Extrapolate q to [q_min, q_max] using geometric steps, with the average geometric step size in q as the step size.

if q_min is zero or less then q[0]/10 is used instead.

points_per_decade sets the ratio between consecutive steps such that there will be \(n\) points used for every factor of 10 increase in q.

If points_per_decade is not given, it will be estimated as follows. Starting at \(q_1\) and stepping geometrically by \(\Delta q\) to \(q_n\) in \(n\) points gives a geometric average of:

\[\log \Delta q = (\log q_n - \log q_1) / (n - 1)\]

From this we can compute the number of steps required to extend \(q\) from \(q_n\) to \(q_\text{max}\) by \(\Delta q\) as:

\[n_\text{extend} = (\log q_\text{max} - \log q_n) / \log \Delta q\]

Substituting:

\[n_\text{extend} = (n-1) (\log q_\text{max} - \log q_n) / (\log q_n - \log q_1)\]
sasdata.model_requirements.guess_requirements(data: SasData) ModellingRequirements

Use names of axes and units to guess what kind of processing needs to be done

sasdata.model_requirements.linear_extrapolation(q, q_min, q_max)

Extrapolate q out to [q_min, q_max] using the step size in q as a guide. Extrapolation below uses about the same size as the first interval. Extrapolation above uses about the same size as the final interval.

Note that extrapolated values may be negative.

sasdata.model_requirements.pinhole_resolution(q_calc: ndarray, q: ndarray, q_width: ndarray, nsigma=(2.5, 3.0)) ndarray

Compute the convolution matrix W for pinhole resolution 1-D data.

Each row W[i] determines the normalized weight that the corresponding points q_calc contribute to the resolution smeared point q[i]. Given W, the resolution smearing can be computed using dot(W,q).

Note that resolution is limited to \(\pm 2.5 \sigma\).[1] The true resolution function is a broadened triangle, and does not extend over the entire range \((-\infty, +\infty)\). It is important to impose this limitation since some models fall so steeply that the weighted value in gaussian tails would otherwise dominate the integral.

q_calc must be increasing. q_width must be greater than zero.

[1] Barker, J. G., and J. S. Pedersen. 1995. Instrumental Smearing Effects in Radially Symmetric Small-Angle Neutron Scattering by Numerical and Analytical Methods. Journal of Applied Crystallography 28 (2): 105–14. https://doi.org/10.1107/S0021889894010095.

sasdata.model_requirements.slit_extend_q(q, width, length)

Given q, width and length, find a set of sampling points q_calc so that each point I(q) has sufficient support from the underlying function.

sasdata.model_requirements.slit_resolution(q_calc, q, width, length, n_length=30)

Build a weight matrix to compute I_s(q) from I(q_calc), given \(q_\perp\) = width (in the high-resolution axis) and \(q_\parallel\) = length (in the low resolution axis). n_length is the number of steps to use in the integration over \(q_\parallel\) when both \(q_\perp\) and \(q_\parallel\) are non-zero.

Each \(q\) can have an independent width and length value even though current instruments use the same slit setting for all measured points.

If slit length is large relative to width, use:

\[I_s(q_i) = \frac{1}{\Delta q_\perp} \int_0^{\Delta q_\perp} I\left(\sqrt{q_i^2 + q_\perp^2}\right) \,dq_\perp\]

If slit width is large relative to length, use:

\[I_s(q_i) = \frac{1}{2 \Delta q_\parallel} \int_{-\Delta q_\parallel}^{\Delta q_\parallel} I\left(|q_i + q_\parallel|\right) \,dq_\parallel\]

For a mixture of slit width and length use:

\[I_s(q_i) = \frac{1}{2 \Delta q_\parallel \Delta q_\perp} \int_{-\Delta q_\parallel}^{\Delta q_\parallel} \int_0^{\Delta q_\perp} I\left(\sqrt{(q_i + q_\parallel)^2 + q_\perp^2}\right) \,dq_\perp dq_\parallel\]

Definition

We are using the mid-point integration rule to assign weights to each element of a weight matrix \(W\) so that

\[I_s(q) = W\,I(q_\text{calc})\]

If q_calc is at the mid-point, we can infer the bin edges from the pairwise averages of q_calc, adding the missing edges before q_calc[0] and after q_calc[-1].

For \(q_\parallel = 0\), the smeared value can be computed numerically using the \(u\) substitution

\[u_j = \sqrt{q_j^2 - q^2}\]

This gives

\[I_s(q) \approx \sum_j I(u_j) \Delta u_j\]

where \(I(u_j)\) is the value at the mid-point, and \(\Delta u_j\) is the difference between consecutive edges which have been first converted to \(u\). Only \(u_j \in [0, \Delta q_\perp]\) are used, which corresponds to \(q_j \in \left[q, \sqrt{q^2 + \Delta q_\perp}\right]\), so

\[W_{ij} = \frac{1}{\Delta q_\perp} \Delta u_j = \frac{1}{\Delta q_\perp} \left( \sqrt{q_{j+1}^2 - q_i^2} - \sqrt{q_j^2 - q_i^2} \right) \ \text{if}\ q_j \in \left[q_i, \sqrt{q_i^2 + q_\perp^2}\right]\]

where \(I_s(q_i)\) is the theory function being computed and \(q_j\) are the mid-points between the calculated values in q_calc. We tweak the edges of the initial and final intervals so that they lie on integration limits.

(To be precise, the transformed midpoint \(u(q_j)\) is not necessarily the midpoint of the edges \(u((q_{j-1}+q_j)/2)\) and \(u((q_j + q_{j+1})/2)\), but it is at least in the interval, so the approximation is going to be a little better than the left or right Riemann sum, and should be good enough for our purposes.)

For \(q_\perp = 0\), the \(u\) substitution is simpler:

\[u_j = \left|q_j - q\right|\]

so

\[W_{ij} = \frac{1}{2 \Delta q_\parallel} \Delta u_j = \frac{1}{2 \Delta q_\parallel} (q_{j+1} - q_j) \ \text{if}\ q_j \in \left[q-\Delta q_\parallel, q+\Delta q_\parallel\right]\]

However, we need to support cases were \(u_j < 0\), which means using \(2 (q_{j+1} - q_j)\) when \(q_j \in \left[0, q_\parallel-q_i\right]\). This is not an issue for \(q_i > q_\parallel\).

For both \(q_\perp > 0\) and \(q_\parallel > 0\) we perform a 2 dimensional integration with

\[u_{jk} = \sqrt{q_j^2 - (q + (k\Delta q_\parallel/L))^2} \ \text{for}\ k = -L \ldots L\]

for \(L\) = n_length. This gives

\[W_{ij} = \frac{1}{2 \Delta q_\perp q_\parallel} \sum_{k=-L}^L \Delta u_{jk} \left(\frac{\Delta q_\parallel}{2 L + 1}\right)\]