source: sasmodels/sasmodels/sasview_model.py @ 3271e20

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

adding parameters to models for SasView? compatibility

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