source: sasmodels/sasmodels/sasview_model.py @ fcd7bbd

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

use named tuple for parameter information

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