source: sasmodels/sasmodels/sasview_model.py @ ff7119b

core_shell_microgelscostrafo411magnetic_modelrelease_v0.94release_v0.95ticket-1257-vesicle-productticket_1156ticket_1265_superballticket_822_more_unit_tests
Last change on this file since ff7119b was ff7119b, checked in by Paul Kienzle <pkienzle@…>, 10 years ago

docu update

  • Property mode set to 100644
File size: 11.6 KB
Line 
1import math
2from copy import deepcopy
3import warnings
4
5import numpy as np
6
7try:
8    import pyopencl
9    from .gen import opencl_model as load_model
10except ImportError:
11    warnings.warn("OpenCL not available --- using ctypes instead")
12    from .gen import dll_model as load_model
13
14
15def make_class(kernel_module, dtype='single'):
16    """
17    Load the sasview model defined in *kernel_module*.
18    :param kernel_module:
19    :param dtype:
20    :return:
21    """
22    model =  load_model(kernel_module, dtype=dtype)
23    def __init__(self, multfactor=1):
24        SasviewModel.__init__(self, model)
25    attrs = dict(__init__=__init__)
26    ConstructedModel = type(model.info['name'],  (SasviewModel,), attrs)
27    return ConstructedModel
28
29class SasviewModel(object):
30    """
31    Sasview wrapper for opencl/ctypes model.
32    """
33    def __init__(self, model):
34        """Initialization"""
35        self._model = model
36
37        self.name = model.info['name']
38        self.description = model.info['description']
39        self.category = None
40        self.multiplicity_info = None
41        self.is_multifunc = False
42
43        ## interpret the parameters
44        ## TODO: reorganize parameter handling
45        self.details = dict()
46        self.params = dict()
47        self.dispersion = dict()
48        partype = model.info['partype']
49        for name,units,default,limits,ptype,description in model.info['parameters']:
50            self.params[name] = default
51            self.details[name] = [units]+limits
52
53        for name in partype['pd-2d']:
54            self.dispersion[name] = {
55                'width': 0,
56                'npts': 35,
57                'nsigmas': 3,
58                'type': 'gaussian',
59            }
60
61        self.orientation_params = (
62            partype['orientation']
63            + [n+'.width' for n in partype['orientation']]
64            + partype['magnetic'])
65        self.magnetic_params = partype['magnetic']
66        self.fixed = [n+'.width' for n in partype['pd-2d']]
67        self.non_fittable = []
68
69        ## independent parameter name and unit [string]
70        self.input_name = model.info.get("input_name","Q")
71        self.input_unit = model.info.get("input_unit","A^{-1}")
72        self.output_name = model.info.get("output_name","Intensity")
73        self.output_unit = model.info.get("output_unit","cm^{-1}")
74
75        ## _persistency_dict is used by sans.perspectives.fitting.basepage
76        ## to store dispersity reference.
77        ## TODO: _persistency_dict to persistency_dict throughout sasview
78        self._persistency_dict = {}
79
80        ## New fields introduced for opencl rewrite
81        self.cutoff = 1e-5
82
83    def __str__(self):
84        """
85        :return: string representation
86        """
87        return self.name
88
89    def is_fittable(self, par_name):
90        """
91        Check if a given parameter is fittable or not
92
93        :param par_name: the parameter name to check
94        """
95        return par_name.lower() in self.fixed
96        #For the future
97        #return self.params[str(par_name)].is_fittable()
98
99
100    def getProfile(self):
101        """
102        Get SLD profile
103
104        : return: (z, beta) where z is a list of depth of the transition points
105                beta is a list of the corresponding SLD values
106        """
107        return None, None
108
109    def setParam(self, name, value):
110        """
111        Set the value of a model parameter
112
113        :param name: name of the parameter
114        :param value: value of the parameter
115
116        """
117        # Look for dispersion parameters
118        toks = name.split('.')
119        if len(toks)==2:
120            for item in self.dispersion.keys():
121                if item.lower()==toks[0].lower():
122                    for par in self.dispersion[item]:
123                        if par.lower() == toks[1].lower():
124                            self.dispersion[item][par] = value
125                            return
126        else:
127            # Look for standard parameter
128            for item in self.params.keys():
129                if item.lower()==name.lower():
130                    self.params[item] = value
131                    return
132
133        raise ValueError, "Model does not contain parameter %s" % name
134
135    def getParam(self, name):
136        """
137        Set the value of a model parameter
138
139        :param name: name of the parameter
140
141        """
142        # Look for dispersion parameters
143        toks = name.split('.')
144        if len(toks)==2:
145            for item in self.dispersion.keys():
146                if item.lower()==toks[0].lower():
147                    for par in self.dispersion[item]:
148                        if par.lower() == toks[1].lower():
149                            return self.dispersion[item][par]
150        else:
151            # Look for standard parameter
152            for item in self.params.keys():
153                if item.lower()==name.lower():
154                    return self.params[item]
155
156        raise ValueError, "Model does not contain parameter %s" % name
157
158    def getParamList(self):
159        """
160        Return a list of all available parameters for the model
161        """
162        list = self.params.keys()
163        # WARNING: Extending the list with the dispersion parameters
164        list.extend(self.getDispParamList())
165        return list
166
167    def getDispParamList(self):
168        """
169        Return a list of all available parameters for the model
170        """
171        # TODO: fix test so that parameter order doesn't matter
172        ret = ['%s.%s'%(d.lower(), p)
173               for d in self._model.info['partype']['pd-2d']
174               for p in ('npts', 'nsigmas', 'width')]
175        #print ret
176        return ret
177
178    def clone(self):
179        """ Return a identical copy of self """
180        return deepcopy(self)
181
182    def run(self, x=0.0):
183        """
184        Evaluate the model
185
186        :param x: input q, or [q,phi]
187
188        :return: scattering function P(q)
189
190        **DEPRECATED**: use calculate_Iq instead
191        """
192        if isinstance(x, (list,tuple)):
193            q, phi = x
194            return self.calculate_Iq([q * math.cos(phi)],
195                                     [q * math.sin(phi)])[0]
196        else:
197            return self.calculate_Iq([float(x)])[0]
198
199
200    def runXY(self, x=0.0):
201        """
202        Evaluate the model in cartesian coordinates
203
204        :param x: input q, or [qx, qy]
205
206        :return: scattering function P(q)
207
208        **DEPRECATED**: use calculate_Iq instead
209        """
210        if isinstance(x, (list,tuple)):
211            return self.calculate_Iq([float(x[0])],[float(x[1])])[0]
212        else:
213            return self.calculate_Iq([float(x)])[0]
214
215    def evalDistribution(self, qdist):
216        """
217        Evaluate a distribution of q-values.
218
219        * For 1D, a numpy array is expected as input: ::
220
221            evalDistribution(q)
222
223          where q is a numpy array.
224
225        * For 2D, a list of numpy arrays are expected: [qx,qy],
226          with 1D arrays::
227
228              qx = [ qx[0], qx[1], qx[2], ....]
229
230          and::
231
232              qy = [ qy[0], qy[1], qy[2], ....]
233
234        Then get ::
235
236            q = numpy.sqrt(qx^2+qy^2)
237
238        that is a qr in 1D array::
239
240            q = [q[0], q[1], q[2], ....]
241
242
243        :param qdist: ndarray of scalar q-values or list [qx,qy] where qx,qy are 1D ndarrays
244        """
245        if isinstance(qdist, (list,tuple)):
246            # Check whether we have a list of ndarrays [qx,qy]
247            qx, qy = qdist
248            return self.calculate_Iq(qx, qy)
249
250        elif isinstance(qdist, np.ndarray):
251            # We have a simple 1D distribution of q-values
252            return self.calculate_Iq(qdist)
253
254        else:
255            raise TypeError("evalDistribution expects q or [qx, qy], not %r"%type(qdist))
256
257    def calculate_Iq(self, *args):
258        """
259        Calculate Iq for one set of q with the current parameters.
260
261        If the model is 1D, use *q*.  If 2D, use *qx*, *qy*.
262
263        This should NOT be used for fitting since it copies the *q* vectors
264        to the card for each evaluation.
265        """
266        q_vectors = [np.asarray(q) for q in args]
267        fn = self._model(self._model.make_input(q_vectors))
268        pars = [self.params[v] for v in fn.fixed_pars]
269        pd_pars = [self._get_weights(p) for p in fn.pd_pars]
270        result = fn(pars, pd_pars, self.cutoff)
271        fn.input.release()
272        fn.release()
273        return result
274
275    def calculate_ER(self):
276        """
277        Calculate the effective radius for P(q)*S(q)
278
279        :return: the value of the effective radius
280        """
281        ER = self._model.info.get('ER', None)
282        if ER is None:
283            return 1.0
284        else:
285            vol_pars = self._model.info['partype']['volume']
286            values, weights = self._dispersion_mesh(vol_pars)
287            fv = ER(*values)
288            #print values[0].shape, weights.shape, fv.shape
289            return np.sum(weights*fv) / np.sum(weights)
290
291    def calculate_VR(self):
292        """
293        Calculate the volf ratio for P(q)*S(q)
294
295        :return: the value of the volf ratio
296        """
297        VR = self._model.info.get('VR', None)
298        if VR is None:
299            return 1.0
300        else:
301            vol_pars = self._model.info['partype']['volume']
302            values, weights = self._dispersion_mesh(vol_pars)
303            whole,part = VR(*values)
304            return np.sum(weights*part)/np.sum(weights*whole)
305
306    def set_dispersion(self, parameter, dispersion):
307        """
308        Set the dispersion object for a model parameter
309
310        :param parameter: name of the parameter [string]
311        :param dispersion: dispersion object of type Dispersion
312        """
313        if parameter.lower() in (s.lower() for s in self.params.keys()):
314            # TODO: Store the disperser object directly in the model.
315            # The current method of creating one on the fly whenever it is
316            # needed is kind of funky.
317            # Note: can't seem to get disperser parameters from sasview
318            # (1) Could create a sasview model that has not yet # been
319            # converted, assign the disperser to one of its polydisperse
320            # parameters, then retrieve the disperser parameters from the
321            # sasview model.  (2) Could write a disperser parameter retriever
322            # in sasview.  (3) Could modify sasview to use sasmodels.weights
323            # dispersers.
324            # For now, rely on the fact that the sasview only ever uses
325            # new dispersers in the set_dispersion call and create a new
326            # one instead of trying to assign parameters.
327            from . import weights
328            disperser = weights.dispersers[dispersion.__class__.__name__]
329            dispersion = weights.models[disperser]()
330            self.dispersion[parameter] = dispersion.get_pars()
331        else:
332            raise ValueError("%r is not a dispersity or orientation parameter")
333
334    def _dispersion_mesh(self, pars):
335        """
336        Create a mesh grid of dispersion parameters and weights.
337
338        Returns [p1,p2,...],w where pj is a vector of values for parameter j
339        and w is a vector containing the products for weights for each
340        parameter set in the vector.
341        """
342        values, weights = zip(*[self._get_weights(p) for p in pars])
343        values = [v.flatten() for v in np.meshgrid(*values)]
344        weights = np.vstack([v.flatten() for v in np.meshgrid(*weights)])
345        weights = np.prod(weights, axis=0)
346        return values, weights
347
348    def _get_weights(self, par):
349        from . import weights
350
351        relative = self._model.info['partype']['pd-rel']
352        limits = self._model.info['limits']
353        dis = self.dispersion[par]
354        v,w = weights.get_weights(
355            dis['type'], dis['npts'], dis['width'], dis['nsigmas'],
356            self.params[par], limits[par], par in relative)
357        return v,w/w.max()
358
Note: See TracBrowser for help on using the repository browser.