source: sasmodels/sasmodels/kernel.py @ e5bbe64

core_shell_microgelsmagnetic_modelticket-1257-vesicle-productticket_1156ticket_1265_superballticket_822_more_unit_tests
Last change on this file since e5bbe64 was cd28947, checked in by Paul Kienzle <pkienzle@…>, 5 years ago

code cleanup and clarification comments

  • Property mode set to 100644
File size: 7.0 KB
Line 
1"""
2Execution kernel interface
3==========================
4
5:class:`KernelModel` defines the interface to all kernel models.
6In particular, each model should provide a :meth:`KernelModel.make_kernel`
7call which returns an executable kernel, :class:`Kernel`, that operates
8on the given set of *q_vector* inputs.  On completion of the computation,
9the kernel should be released, which also releases the inputs.
10"""
11
12from __future__ import division, print_function
13
14# pylint: disable=unused-import
15try:
16    from typing import List
17except ImportError:
18    pass
19else:
20    import numpy as np
21    from .details import CallDetails
22    from .modelinfo import ModelInfo
23# pylint: enable=unused-import
24
25class KernelModel(object):
26    info = None  # type: ModelInfo
27    dtype = None # type: np.dtype
28    def make_kernel(self, q_vectors):
29        # type: (List[np.ndarray]) -> "Kernel"
30        raise NotImplementedError("need to implement make_kernel")
31
32    def release(self):
33        # type: () -> None
34        pass
35
36class Kernel(object):
37    #: kernel dimension, either "1d" or "2d"
38    dim = None  # type: str
39    info = None  # type: ModelInfo
40    results = None # type: List[np.ndarray]
41    dtype = None  # type: np.dtype
42
43    def Iq(self, call_details, values, cutoff, magnetic):
44        # type: (CallDetails, np.ndarray, np.ndarray, float, bool) -> np.ndarray
45        r"""
46        Returns I(q) from the polydisperse average scattering.
47
48        .. math::
49
50            I(q) = \text{scale} \cdot P(q) + \text{background}
51
52        With the correct choice of model and contrast, setting *scale* to
53        the volume fraction $V_f$ of particles should match the measured
54        absolute scattering.  Some models (e.g., vesicle) have volume fraction
55        built into the model, and do not need an additional scale.
56        """
57        _, F2, _, shell_volume, _ = self.Fq(call_details, values, cutoff, magnetic,
58                              effective_radius_type=0)
59        combined_scale = values[0]/shell_volume
60        background = values[1]
61        return combined_scale*F2 + background
62    __call__ = Iq
63
64    def Fq(self, call_details, values, cutoff, magnetic, effective_radius_type=0):
65        # type: (CallDetails, np.ndarray, np.ndarray, float, bool, int) -> np.ndarray
66        r"""
67        Returns <F(q)>, <F(q)^2>, effective radius, shell volume and
68        form:shell volume ratio.  The <F(q)> term may be None if the
69        form factor does not support direct computation of $F(q)$
70
71        $P(q) = <F^2(q)>/<V>$ is used for structure factor calculations,
72
73        .. math::
74
75            I(q) = \text{scale} \cdot P(q) \cdot S(q) + \text{background}
76
77        For the beta approximation, this becomes
78
79        .. math::
80
81            I(q) = \text{scale} * P (1 + <F>^2/<F^2> (S - 1)) + \text{background}
82                 = \text{scale}/<V> (<F^2> + <F>^2 (S - 1)) + \text{background}
83
84        $<F(q)>$ and $<F^2(q)>$ are averaged by polydispersity in shape
85        and orientation, with each configuration $x_k$ having form factor
86        $F(q, x_k)$, weight $w_k$ and volume $V_k$.  The result is:
87
88        .. math::
89
90            P(q) = \frac{\sum w_k F^2(q, x_k) / \sum w_k}{\sum w_k V_k / \sum w_k}
91
92        The form factor itself is scaled by volume and contrast to compute the
93        total scattering.  This is then squared, and the volume weighted
94        F^2 is then normalized by volume F.  For a given density, the number
95        of scattering centers is assumed to scale linearly with volume.  Later
96        scaling the resulting $P(q)$ by the volume fraction of particles
97        gives the total scattering on an absolute scale. Most models
98        incorporate the volume fraction into the overall scale parameter.  An
99        exception is vesicle, which includes the volume fraction parameter in
100        the model itself, scaling $F$ by $\surd V_f$ so that the math for
101        the beta approximation works out.
102
103        By scaling $P(q)$ by total weight $\sum w_k$, there is no need to make
104        sure that the polydisperisity distributions normalize to one.  In
105        particular, any distibution values $x_k$ outside the valid domain
106        of $F$ will not be included, and the distribution will be implicitly
107        truncated.  This is controlled by the parameter limits defined in the
108        model (which truncate the distribution before calling the kernel) as
109        well as any region excluded using the *INVALID* macro defined within
110        the model itself.
111
112        The volume used in the polydispersity calculation is the form volume
113        for solid objects or the shell volume for hollow objects.  Shell
114        volume should be used within $F$ so that the normalizing scale
115        represents the volume fraction of the shell rather than the entire
116        form.  This corresponds to the volume fraction of shell-forming
117        material added to the solvent.
118
119        The calculation of $S$ requires the effective radius and the
120        volume fraction of the particles.  The model can have several
121        different ways to compute effective radius, with the
122        *effective_radius_type* parameter used to select amongst them.  The
123        volume fraction of particles should be determined from the total
124        volume fraction of the form, not just the shell volume fraction.
125        This makes a difference for hollow shapes, which need to scale
126        the volume fraction by the returned volume ratio when computing $S$.
127        For solid objects, the shell volume is set to the form volume so
128        this scale factor evaluates to one and so can be used for both
129        hollow and solid shapes.
130        """
131        self._call_kernel(call_details, values, cutoff, magnetic, effective_radius_type)
132        #print("returned",self.q_input.q, self.result)
133        nout = 2 if self.info.have_Fq and self.dim == '1d' else 1
134        total_weight = self.result[nout*self.q_input.nq + 0]
135        # Note: total_weight = sum(weight > cutoff), with cutoff >= 0, so it
136        # is okay to test directly against zero.  If weight is zero then I(q),
137        # etc. must also be zero.
138        if total_weight == 0.:
139            total_weight = 1.
140        # Note: shell_volume == form_volume for solid objects
141        form_volume = self.result[nout*self.q_input.nq + 1]/total_weight
142        shell_volume = self.result[nout*self.q_input.nq + 2]/total_weight
143        effective_radius = self.result[nout*self.q_input.nq + 3]/total_weight
144        if shell_volume == 0.:
145            shell_volume = 1.
146        F1 = self.result[1:nout*self.q_input.nq:nout]/total_weight if nout == 2 else None
147        F2 = self.result[0:nout*self.q_input.nq:nout]/total_weight
148        return F1, F2, effective_radius, shell_volume, form_volume/shell_volume
149
150    def release(self):
151        # type: () -> None
152        pass
153
154    def _call_kernel(self, call_details, values, cutoff, magnetic, effective_radius_type):
155        # type: (CallDetails, np.ndarray, np.ndarray, float, bool, int) -> np.ndarray
156        """
157        Call the kernel.  Subclasses defining kernels for particular execution
158        engines need to provide an implementation for this.
159        """
160        raise NotImplementedError()
Note: See TracBrowser for help on using the repository browser.