sasdata.model_requirements module¶
- class sasdata.model_requirements.ComposeRequirements(fst, snd)¶
Bases:
ModellingRequirementsComposition 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¶
- second: ModellingRequirements¶
- class sasdata.model_requirements.ModellingRequirements¶
Bases:
ABCRequirements 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¶
- class sasdata.model_requirements.NullModel¶
Bases:
ModellingRequirementsA 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¶
- class sasdata.model_requirements.PinholeModel(q_width: ndarray, nsigma: (<class 'float'>, <class 'float'>)=(2.5, 3.0))¶
Bases:
ModellingRequirementsPerform 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>¶
- class sasdata.model_requirements.SesansModel¶
Bases:
ModellingRequirementsPerform 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>¶
- class sasdata.model_requirements.SlitModel(q_length: ndarray, q_width: ndarray, nsigma: (<class 'float'>, <class 'float'>)=(2.5, 3.0))¶
Bases:
ModellingRequirementsPerform 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>¶
- 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)\]