source: sasview/src/sas/sascalc/fit/MultiplicationModel.py @ 2e7be0d

ESS_GUIESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_opencl
Last change on this file since 2e7be0d was b8080e1, checked in by Piotr Rozyczko <rozyczko@…>, 6 years ago

cherry picking sascalc changes from master SASVIEW-996
minor unit test fixes

  • Property mode set to 100644
File size: 11.7 KB
RevLine 
[cb4ef58]1import copy
2
[9a5097c]3import numpy as np
[08959b8]4
5from sas.sascalc.calculator.BaseComponent import BaseComponent
[cb4ef58]6
[08959b8]7class MultiplicationModel(BaseComponent):
8    r"""
9        Use for P(Q)\*S(Q); function call must be in the order of P(Q) and then S(Q):
[fd62331]10        The model parameters are combined from both models, P(Q) and S(Q), except 1) 'radius_effective' of S(Q)
11        which will be calculated from P(Q) via calculate_ER(),
12        and 2) 'scale' in P model which is synchronized w/ volfraction in S
[08959b8]13        then P*S is multiplied by a new parameter, 'scale_factor'.
14        The polydispersion is applicable only to P(Q), not to S(Q).
15
16        .. note:: P(Q) refers to 'form factor' model while S(Q) does to 'structure factor'.
17    """
18    def __init__(self, p_model, s_model ):
19        BaseComponent.__init__(self)
20        """
21        :param p_model: form factor, P(Q)
22        :param s_model: structure factor, S(Q)
23        """
24
25        ## Setting  model name model description
26        self.description = ""
27        self.name = p_model.name +" * "+ s_model.name
28        self.description= self.name + "\n"
29        self.fill_description(p_model, s_model)
30
31        ## Define parameters
32        self.params = {}
33
34        ## Parameter details [units, min, max]
35        self.details = {}
[fd62331]36
37        ## Define parameters to exclude from multiplication model
38        self.excluded_params={'radius_effective','scale','background'}
39
40        ##models
[08959b8]41        self.p_model = p_model
[fd62331]42        self.s_model = s_model
[08959b8]43        self.magnetic_params = []
44        ## dispersion
45        self._set_dispersion()
46        ## Define parameters
47        self._set_params()
48        ## New parameter:Scaling factor
49        self.params['scale_factor'] = 1
[fd62331]50        self.params['background']  = 0
51
[08959b8]52        ## Parameter details [units, min, max]
53        self._set_details()
[9a5097c]54        self.details['scale_factor'] = ['', 0.0, np.inf]
55        self.details['background'] = ['',-np.inf,np.inf]
[fd62331]56
[08959b8]57        #list of parameter that can be fitted
[fd62331]58        self._set_fixed_params()
[08959b8]59        ## parameters with orientation
60        for item in self.p_model.orientation_params:
61            self.orientation_params.append(item)
[fd62331]62        for item in self.p_model.magnetic_params:
63            self.magnetic_params.append(item)
[08959b8]64        for item in self.s_model.orientation_params:
65            if not item in self.orientation_params:
66                self.orientation_params.append(item)
67        # get multiplicity if model provide it, else 1.
68        try:
69            multiplicity = p_model.multiplicity
[b8080e1]70        except AttributeError:
[08959b8]71            multiplicity = 1
72        ## functional multiplicity of the model
[fd62331]73        self.multiplicity = multiplicity
74
[08959b8]75        # non-fittable parameters
[fd62331]76        self.non_fittable = p_model.non_fittable
77        self.multiplicity_info = []
[b8080e1]78        self.fun_list = []
[08959b8]79        if self.non_fittable > 1:
80            try:
[fd62331]81                self.multiplicity_info = p_model.multiplicity_info
[08959b8]82                self.fun_list = p_model.fun_list
[cb4ef58]83                self.is_multiplicity_model = True
[b8080e1]84            except AttributeError:
[08959b8]85                pass
86        else:
[cb4ef58]87            self.is_multiplicity_model = False
88            self.multiplicity_info = [0]
[fd62331]89
[08959b8]90    def _clone(self, obj):
91        """
92        Internal utility function to copy the internal data members to a
93        fresh copy.
94        """
95        obj.params     = copy.deepcopy(self.params)
96        obj.description     = copy.deepcopy(self.description)
97        obj.details    = copy.deepcopy(self.details)
98        obj.dispersion = copy.deepcopy(self.dispersion)
99        obj.p_model  = self.p_model.clone()
100        obj.s_model  = self.s_model.clone()
101        #obj = copy.deepcopy(self)
102        return obj
[fd62331]103
104
[08959b8]105    def _set_dispersion(self):
106        """
107        combine the two models' dispersions. Polydispersity should not be
108        applied to s_model
109        """
[fd62331]110        ##set dispersion only from p_model
[574adc7]111        for name , value in self.p_model.dispersion.items():
[fd62331]112            self.dispersion[name] = value
113
[08959b8]114    def getProfile(self):
115        """
116        Get SLD profile of p_model if exists
[fd62331]117
[08959b8]118        :return: (r, beta) where r is a list of radius of the transition points\
119                beta is a list of the corresponding SLD values
120
121        .. note:: This works only for func_shell num = 2 (exp function).
122        """
123        try:
124            x, y = self.p_model.getProfile()
125        except:
126            x = None
127            y = None
[fd62331]128
[08959b8]129        return x, y
[fd62331]130
[08959b8]131    def _set_params(self):
132        """
133        Concatenate the parameters of the two models to create
[fd62331]134        these model parameters
[08959b8]135        """
136
[574adc7]137        for name , value in self.p_model.params.items():
[fd62331]138            if not name in self.params.keys() and name not in self.excluded_params:
[08959b8]139                self.params[name] = value
[fd62331]140
[574adc7]141        for name , value in self.s_model.params.items():
[fd62331]142            #Remove the radius_effective from the (P*S) model parameters.
143            if not name in self.params.keys() and name not in self.excluded_params:
[08959b8]144                self.params[name] = value
[fd62331]145
[08959b8]146        # Set "scale and effec_radius to P and S model as initializing
147        # since run P*S comes from P and S separately.
[fd62331]148        self._set_backgrounds()
[08959b8]149        self._set_scale_factor()
[fd62331]150        self._set_radius_effective()
151
[08959b8]152    def _set_details(self):
153        """
154        Concatenate details of the two models to create
[fd62331]155        this model's details
[08959b8]156        """
[574adc7]157        for name, detail in self.p_model.details.items():
[fd62331]158            if name not in self.excluded_params:
[08959b8]159                self.details[name] = detail
[fd62331]160
[574adc7]161        for name , detail in self.s_model.details.items():
[fd62331]162            if not name in self.details.keys() or name not in self.exluded_params:
[08959b8]163                self.details[name] = detail
[fd62331]164
165    def _set_backgrounds(self):
166        """
167        Set component backgrounds to zero
168        """
[68669da]169        if 'background' in self.p_model.params:
170            self.p_model.setParam('background',0)
171        if 'background' in self.s_model.params:
172            self.s_model.setParam('background',0)
[fd62331]173
174
[08959b8]175    def _set_scale_factor(self):
176        """
177        Set scale=volfraction for P model
178        """
179        value = self.params['volfraction']
[7432acb]180        if value is not None:
[08959b8]181            factor = self.p_model.calculate_VR()
[235f514]182            if factor is None or factor == NotImplemented or factor == 0.0:
[08959b8]183                val = value
184            else:
185                val = value / factor
186            self.p_model.setParam('scale', value)
187            self.s_model.setParam('volfraction', val)
[fd62331]188
189    def _set_radius_effective(self):
[08959b8]190        """
191        Set effective radius to S(Q) model
192        """
[fd62331]193        if not 'radius_effective' in self.s_model.params.keys():
[08959b8]194            return
195        effective_radius = self.p_model.calculate_ER()
196        #Reset the effective_radius of s_model just before the run
[7432acb]197        if effective_radius is not None and effective_radius != NotImplemented:
[fd62331]198            self.s_model.setParam('radius_effective', effective_radius)
199
[08959b8]200    def setParam(self, name, value):
[fd62331]201        """
[08959b8]202        Set the value of a model parameter
[fd62331]203
[08959b8]204        :param name: name of the parameter
205        :param value: value of the parameter
206        """
207        # set param to P*S model
208        self._setParamHelper( name, value)
[fd62331]209
210        ## setParam to p model
211        # set 'scale' in P(Q) equal to volfraction
[08959b8]212        if name == 'volfraction':
213            self._set_scale_factor()
[fd62331]214        elif name in self.p_model.getParamList() and name not in self.excluded_params:
[08959b8]215            self.p_model.setParam( name, value)
[fd62331]216
217        ## setParam to s model
218        # This is a little bit abundant: Todo: find better way
219        self._set_radius_effective()
220        if name in self.s_model.getParamList() and name not in self.excluded_params:
[08959b8]221            if name != 'volfraction':
222                self.s_model.setParam( name, value)
[fd62331]223
[08959b8]224
225        #self._setParamHelper( name, value)
[fd62331]226
[08959b8]227    def _setParamHelper(self, name, value):
228        """
229        Helper function to setparam
230        """
231        # Look for dispersion parameters
232        toks = name.split('.')
233        if len(toks)==2:
234            for item in self.dispersion.keys():
235                if item.lower()==toks[0].lower():
236                    for par in self.dispersion[item]:
237                        if par.lower() == toks[1].lower():
238                            self.dispersion[item][par] = value
239                            return
240        else:
241            # Look for standard parameter
242            for item in self.params.keys():
243                if item.lower() == name.lower():
244                    self.params[item] = value
245                    return
[fd62331]246
[574adc7]247        raise ValueError("Model does not contain parameter %s" % name)
[fd62331]248
249
[08959b8]250    def _set_fixed_params(self):
251        """
252        Fill the self.fixed list with the p_model fixed list
253        """
254        for item in self.p_model.fixed:
255            self.fixed.append(item)
256
257        self.fixed.sort()
[fd62331]258
259
[08959b8]260    def run(self, x = 0.0):
[fd62331]261        """
[08959b8]262        Evaluate the model
[fd62331]263
[08959b8]264        :param x: input q-value (float or [float, float] as [r, theta])
265        :return: (scattering function value)
266        """
267        # set effective radius and scaling factor before run
[fd62331]268        self._set_radius_effective()
[08959b8]269        self._set_scale_factor()
270        return self.params['scale_factor'] * self.p_model.run(x) * \
[fd62331]271                            self.s_model.run(x) + self.params['background']
[08959b8]272
273    def runXY(self, x = 0.0):
[fd62331]274        """
[08959b8]275        Evaluate the model
[fd62331]276
[08959b8]277        :param x: input q-value (float or [float, float] as [qx, qy])
278        :return: scattering function value
[fd62331]279        """
[08959b8]280        # set effective radius and scaling factor before run
[fd62331]281        self._set_radius_effective()
[08959b8]282        self._set_scale_factor()
283        out = self.params['scale_factor'] * self.p_model.runXY(x) * \
[fd62331]284                        self.s_model.runXY(x) + self.params['background']
[08959b8]285        return out
[fd62331]286
287    ## Now (May27,10) directly uses the model eval function
[08959b8]288    ## instead of the for-loop in Base Component.
289    def evalDistribution(self, x = []):
[fd62331]290        """
[08959b8]291        Evaluate the model in cartesian coordinates
[fd62331]292
[08959b8]293        :param x: input q[], or [qx[], qy[]]
294        :return: scattering function P(q[])
295        """
296        # set effective radius and scaling factor before run
[fd62331]297        self._set_radius_effective()
[08959b8]298        self._set_scale_factor()
299        out = self.params['scale_factor'] * self.p_model.evalDistribution(x) * \
[fd62331]300                        self.s_model.evalDistribution(x) + self.params['background']
[08959b8]301        return out
302
303    def set_dispersion(self, parameter, dispersion):
304        """
305        Set the dispersion object for a model parameter
[fd62331]306
[08959b8]307        :param parameter: name of the parameter [string]
308        :dispersion: dispersion object of type DispersionModel
309        """
310        value = None
311        try:
312            if parameter in self.p_model.dispersion.keys():
313                value = self.p_model.set_dispersion(parameter, dispersion)
314            self._set_dispersion()
315            return value
316        except:
[fd62331]317            raise
[08959b8]318
319    def fill_description(self, p_model, s_model):
320        """
321        Fill the description for P(Q)*S(Q)
322        """
323        description = ""
[fd62331]324        description += "Note:1) The radius_effective (effective radius) of %s \n"%\
[08959b8]325                                                                (s_model.name)
326        description += "             is automatically calculated "
327        description += "from size parameters (radius...).\n"
328        description += "         2) For non-spherical shape, "
329        description += "this approximation is valid \n"
330        description += "            only for limited systems. "
331        description += "Thus, use it at your own risk.\n"
332        description += "See %s description and %s description \n"% \
333                                                ( p_model.name, s_model.name )
334        description += "        for details of individual models."
335        self.description += description
Note: See TracBrowser for help on using the repository browser.