source: sasmodels/sasmodels/pykernel.py @ b3f6bc3

core_shell_microgelscostrafo411magnetic_modelrelease_v0.94release_v0.95ticket-1257-vesicle-productticket_1156ticket_1265_superballticket_822_more_unit_tests
Last change on this file since b3f6bc3 was b3f6bc3, checked in by pkienzle, 9 years ago

support pure python model Iq/Iqxy?

  • Property mode set to 100644
File size: 8.6 KB
Line 
1import numpy as np
2from numpy import pi, sin, cos, sqrt
3
4from .generate import F32, F64
5
6class PyInput(object):
7    """
8    Make q data available to the gpu.
9
10    *q_vectors* is a list of q vectors, which will be *[q]* for 1-D data,
11    and *[qx, qy]* for 2-D data.  Internally, the vectors will be reallocated
12    to get the best performance on OpenCL, which may involve shifting and
13    stretching the array to better match the memory architecture.  Additional
14    points will be evaluated with *q=1e-3*.
15
16    *dtype* is the data type for the q vectors. The data type should be
17    set to match that of the kernel, which is an attribute of
18    :class:`GpuProgram`.  Note that not all kernels support double
19    precision, so even if the program was created for double precision,
20    the *GpuProgram.dtype* may be single precision.
21
22    Call :meth:`release` when complete.  Even if not called directly, the
23    buffer will be released when the data object is freed.
24    """
25    def __init__(self, q_vectors, dtype):
26        self.nq = q_vectors[0].size
27        self.dtype = dtype
28        self.is_2D = (len(q_vectors) == 2)
29        self.q_vectors = [np.ascontiguousarray(q,self.dtype) for q in q_vectors]
30        self.q_pointers = [q.ctypes.data for q in q_vectors]
31
32    def release(self):
33        self.q_vectors = []
34
35class PyKernel(object):
36    """
37    Callable SAS kernel.
38
39    *kernel* is the DllKernel object to call.
40
41    *info* is the module information
42
43    *input* is the DllInput q vectors at which the kernel should be
44    evaluated.
45
46    The resulting call method takes the *pars*, a list of values for
47    the fixed parameters to the kernel, and *pd_pars*, a list of (value,weight)
48    vectors for the polydisperse parameters.  *cutoff* determines the
49    integration limits: any points with combined weight less than *cutoff*
50    will not be calculated.
51
52    Call :meth:`release` when done with the kernel instance.
53    """
54    def __init__(self, kernel, info, input):
55        self.info = info
56        self.input = input
57        self.res = np.empty(input.nq, input.dtype)
58        dim = '2d' if input.is_2D else '1d'
59        # Loop over q unless user promises that the kernel is vectorized by
60        # taggining it with vectorized=True
61        if not getattr(kernel, 'vectorized', False):
62            if dim == '2d':
63                def vector_kernel(qx, qy, *args):
64                    return np.array([kernel(qxi,qyi,*args) for qxi,qyi in zip(qx,qy)])
65            else:
66                def vector_kernel(q, *args):
67                    return np.array([kernel(qi,*args) for qi in q])
68            self.kernel = vector_kernel
69        else:
70            self.kernel = kernel
71        fixed_pars = info['partype']['fixed-'+dim]
72        pd_pars = info['partype']['pd-'+dim]
73        vol_pars = info['partype']['volume']
74
75        # First two fixed pars are scale and background
76        pars = [p[0] for p in info['parameters'][2:]]
77        offset = len(self.input.q_vectors)
78        self.args = self.input.q_vectors + [None]*len(pars)
79        self.fixed_index = np.array([pars.index(p)+offset for p in fixed_pars[2:] ])
80        self.pd_index = np.array([pars.index(p)+offset for p in pd_pars])
81        self.vol_index = np.array([pars.index(p)+offset for p in vol_pars])
82        try: self.theta_index = pars.index('theta')+offset
83        except ValueError: self.theta_index = -1
84
85        # Caller needs fixed_pars and pd_pars
86        self.fixed_pars = fixed_pars
87        self.pd_pars = pd_pars
88
89    def __call__(self, fixed, pd, cutoff):
90        #print "fixed",fixed
91        #print "pd", pd
92        args = self.args[:]  # grab a copy of the args
93        form, form_volume = self.kernel, self.info['form_volume']
94        # First two fixed
95        scale, background = fixed[:2]
96        for index,value in zip(self.fixed_index, fixed[2:]):
97            args[index] = float(value)
98        res = _loops(form, form_volume, cutoff, scale, background,  args,
99                     pd, self.pd_index, self.vol_index, self.theta_index)
100
101        return res
102
103    def release(self):
104        self.input = None
105
106def _loops(form, form_volume, cutoff, scale, background,
107           args, pd, pd_index, vol_index, theta_index):
108    """
109    *form* is the name of the form function, which should be vectorized over
110    q, but otherwise have an interface like the opencl kernels, with the
111    q parameters first followed by the individual parameters in the order
112    given in model.parameters (see :mod:`sasmodels.generate`).
113
114    *form_volume* calculates the volume of the shape.  *vol_index* gives
115    the list of volume parameters
116
117    *cutoff* ignores the corners of the dispersion hypercube
118
119    *scale*, *background* multiplies the resulting form and adds an offset
120
121    *args* is the prepopulated set of arguments to the form function, starting
122    with the q vectors, and including slots for all the parameters.  The
123    values for the parameters will be substituted with values from the
124    dispersion functions.
125
126    *pd* is the list of dispersion parameters
127
128    *pd_index* are the indices of the dispersion parameters in *args*
129
130    *vol_index* are the indices of the volume parameters in *args*
131
132    *theta_index* is the index of the theta parameter for the sasview
133    spherical correction, or -1 if there is no angular dispersion
134    """
135
136    ##########################################################
137    #                                                        #
138    #   !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!   #
139    #   !!                                              !!   #
140    #   !!  KEEP THIS CODE CONSISTENT WITH GENERATE.PY  !!   #
141    #   !!                                              !!   #
142    #   !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!   #
143    #                                                        #
144    ##########################################################
145
146    # weight vector, to be populated by polydispersity loops
147    weight = np.empty(len(pd), 'd')
148
149    # identify which pd parameters are volume parameters
150    vol_weight_index = np.array([(index in vol_index) for index in pd_index])
151
152    # Sort parameters in decreasing order of pd length
153    Npd = np.array([len(pdi[0]) for pdi in pd], 'i')
154    order = np.argsort(Npd)[::-1]
155    stride = np.cumprod(Npd[order])
156    pd = [pd[index] for index in order]
157    pd_index = pd_index[order]
158    vol_weight_index = vol_weight_index[order]
159
160    fast_value = pd[0][0]
161    fast_weight = pd[0][1]
162
163    ret = np.zeros_like(args[0])
164    norm = np.zeros_like(ret)
165    vol = np.zeros_like(ret)
166    vol_norm = np.zeros_like(ret)
167    for k in range(stride[-1]):
168        # update polydispersity parameter values
169        fast_index = k%stride[0]
170        if fast_index == 0:  # bottom loop complete ... check all other loops
171            for i,index, in enumerate(k%stride):
172                args[pd_index[i]] = pd[i][0][index]
173                weight[i] = pd[i][1][index]
174        else:
175            args[pd_index[0]] = fast_value[fast_index]
176            weight[0] = fast_weight[fast_index]
177        # This computes the weight, and if it is sufficient, calls the scattering
178        # function and adds it to the total.  If there is a volume normalization,
179        # it will also be added here.
180        # Note: make sure this is consistent with the code in PY_LOOP_BODY!!
181        # Note: can precompute w1*w2*...*wn
182        w = np.prod(weight)
183        if w > cutoff:
184            I = form(*args)
185            positive = (I>=0.0)
186
187            # Note: can precompute spherical correction if theta_index is not the fast index
188            # Correction factor for spherical integration p(theta) I(q) sin(theta) dtheta
189            #spherical_correction = abs(sin(pi*args[theta_index])) if theta_index>=0 else 1.0
190            #spherical_correction = abs(cos(pi*args[theta_index]))*pi/2 if theta_index>=0 else 1.0
191            spherical_correction = 1.0
192            ret += w*I*spherical_correction*positive
193            norm += w*positive
194
195            # Volume normalization.
196            # If there are "volume" polydispersity parameters, then these will be used
197            # to call the form_volume function from the user supplied kernel, and accumulate
198            # a normalized weight.
199            # Note: can precompute volume norm if the fast index is not a volume index
200            if form_volume:
201                vol_args = [args[index] for index in vol_index]
202                vol_weight = np.prod(weight[vol_weight_index])
203                vol += vol_weight*form_volume(*vol_args)*positive
204                vol_norm += vol_weight*positive
205
206    positive = (vol*vol_norm != 0.0)
207    ret[positive] *= vol_norm[positive] / vol[positive]
208    result = scale*ret/norm+background
209    return result
Note: See TracBrowser for help on using the repository browser.