source: sasmodels/sasmodels/kernel.py @ e44432d

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

support hollow models in structure factor calculations

  • Property mode set to 100644
File size: 6.9 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        if total_weight == 0.:
136            total_weight = 1.
137        # Note: shell_volume == form_volume for solid objects
138        form_volume = self.result[nout*self.q_input.nq + 1]/total_weight
139        shell_volume = self.result[nout*self.q_input.nq + 2]/total_weight
140        effective_radius = self.result[nout*self.q_input.nq + 3]/total_weight
141        if shell_volume == 0.:
142            shell_volume = 1.
143        F1 = self.result[1:nout*self.q_input.nq:nout]/total_weight if nout == 2 else None
144        F2 = self.result[0:nout*self.q_input.nq:nout]/total_weight
145        return F1, F2, effective_radius, shell_volume, form_volume/shell_volume
146
147    def release(self):
148        # type: () -> None
149        pass
150
151    def _call_kernel(self, call_details, values, cutoff, magnetic, effective_radius_type):
152        # type: (CallDetails, np.ndarray, np.ndarray, float, bool, int) -> np.ndarray
153        """
154        Call the kernel.  Subclasses defining kernels for particular execution
155        engines need to provide an implementation for this.
156        """
157        raise NotImplementedError()
Note: See TracBrowser for help on using the repository browser.