source: sasmodels/sasmodels/bumps_model.py @ e9b1663d

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

pull from master

  • Property mode set to 100644
File size: 8.3 KB
Line 
1"""
2Wrap sasmodels for direct use by bumps.
3
4:class:`Model` is a wrapper for the sasmodels kernel which defines a
5bumps *Parameter* box for each kernel parameter.  *Model* accepts keyword
6arguments to set the initial value for each parameter.
7
8:class:`Experiment` combines the *Model* function with a data file loaded by
9the sasview data loader.  *Experiment* takes a *cutoff* parameter controlling
10how far the polydispersity integral extends.
11
12"""
13
14import warnings
15
16import numpy as np
17
18from .data import plot_theory
19from .direct_model import DataMixin
20
21__all__ = [
22    "Model", "Experiment",
23    ]
24
25# CRUFT: old style bumps wrapper which doesn't separate data and model
26# pylint: disable=invalid-name
27def BumpsModel(data, model, cutoff=1e-5, **kw):
28    r"""
29    Bind a model to data, along with a polydispersity cutoff.
30
31    *data* is a :class:`data.Data1D`, :class:`data.Data2D` or
32    :class:`data.Sesans` object.  Use :func:`data.empty_data1D` or
33    :func:`data.empty_data2D` to define $q, \Delta q$ calculation
34    points for displaying the SANS curve when there is no measured data.
35
36    *model* is a runnable module as returned from :func:`core.load_model`.
37
38    *cutoff* is the polydispersity weight cutoff.
39
40    Any additional *key=value* pairs are model dependent parameters.
41
42    Returns an :class:`Experiment` object.
43
44    Note that the usual Bumps semantics is not fully supported, since
45    assigning *M.name = parameter* on the returned experiment object
46    does not set that parameter in the model.  Range setting will still
47    work as expected though.
48
49    .. deprecated:: 0.1
50        Use :class:`Experiment` instead.
51    """
52    warnings.warn("Use of BumpsModel is deprecated.  Use bumps_model.Experiment instead.")
53
54    # Create the model and experiment
55    model = Model(model, **kw)
56    experiment = Experiment(data=data, model=model, cutoff=cutoff)
57
58    # Copy the model parameters up to the experiment object.
59    for k, v in model.parameters().items():
60        setattr(experiment, k, v)
61    return experiment
62
63
64def create_parameters(model_info, **kwargs):
65    """
66    Generate Bumps parameters from the model info.
67
68    *model_info* is returned from :func:`generate.model_info` on the
69    model definition module.
70
71    Any additional *key=value* pairs are initial values for the parameters
72    to the models.  Uninitialized parameters will use the model default
73    value.
74
75    Returns a dictionary of *{name: Parameter}* containing the bumps
76    parameters for each model parameter, and a dictionary of
77    *{name: str}* containing the polydispersity distribution types.
78    """
79    # lazy import; this allows the doc builder and nosetests to run even
80    # when bumps is not on the path.
81    from bumps.names import Parameter
82
83    pars = {} # => floating point parameters
84    for p in model_info['parameters']:
85        value = kwargs.pop(p.name, p.default)
86        pars[p.name] = Parameter.default(value, name=p.name, limits=p.limits)
87    for name in model_info['par_type']['pd']:
88        for xpart, xdefault, xlimits in [
89                ('_pd', 0., pars[name].limits),
90                ('_pd_n', 35., (0, 1000)),
91                ('_pd_nsigma', 3., (0, 10)),
92            ]:
93            xname = name + xpart
94            xvalue = kwargs.pop(xname, xdefault)
95            pars[xname] = Parameter.default(xvalue, name=xname, limits=xlimits)
96
97    pd_types = {}  # => distribution names
98    for name in model_info['par_type']['pd']:
99        xname = name + '_pd_type'
100        xvalue = kwargs.pop(xname, 'gaussian')
101        pd_types[xname] = xvalue
102
103    if kwargs:  # args not corresponding to parameters
104        raise TypeError("unexpected parameters: %s"
105                        % (", ".join(sorted(kwargs.keys()))))
106
107    return pars, pd_types
108
109class Model(object):
110    """
111    Bumps wrapper for a SAS model.
112
113    *model* is a runnable module as returned from :func:`core.load_model`.
114
115    *cutoff* is the polydispersity weight cutoff.
116
117    Any additional *key=value* pairs are model dependent parameters.
118    """
119    def __init__(self, model, **kwargs):
120        self._sasmodel = model
121        pars, pd_types = create_parameters(model.info, **kwargs)
122        for k, v in pars.items():
123            setattr(self, k, v)
124        for k, v in pd_types.items():
125            setattr(self, k, v)
126        self._parameter_names = list(pars.keys())
127        self._pd_type_names = list(pd_types.keys())
128
129    def parameters(self):
130        """
131        Return a dictionary of parameters objects for the parameters,
132        excluding polydispersity distribution type.
133        """
134        return dict((k, getattr(self, k)) for k in self._parameter_names)
135
136    def state(self):
137        """
138        Return a dictionary of current values for all the parameters,
139        including polydispersity distribution type.
140        """
141        pars = dict((k, getattr(self, k).value) for k in self._parameter_names)
142        pars.update((k, getattr(self, k)) for k in self._pd_type_names)
143        return pars
144
145class Experiment(DataMixin):
146    r"""
147    Bumps wrapper for a SAS experiment.
148
149    *data* is a :class:`data.Data1D`, :class:`data.Data2D` or
150    :class:`data.Sesans` object.  Use :func:`data.empty_data1D` or
151    :func:`data.empty_data2D` to define $q, \Delta q$ calculation
152    points for displaying the SANS curve when there is no measured data.
153
154    *model* is a :class:`Model` object.
155
156    *cutoff* is the integration cutoff, which avoids computing the
157    the SAS model where the polydispersity weight is low.
158
159    The resulting model can be used directly in a Bumps FitProblem call.
160    """
161    def __init__(self, data, model, cutoff=1e-5):
162
163        # remember inputs so we can inspect from outside
164        self.model = model
165        self.cutoff = cutoff
166        self._interpret_data(data, model._sasmodel)
167        self.update()
168
169    def update(self):
170        """
171        Call when model parameters have changed and theory needs to be
172        recalculated.
173        """
174        self._cache = {}
175
176    def numpoints(self):
177        """
178        Return the number of data points
179        """
180        return len(self.Iq)
181
182    def parameters(self):
183        """
184        Return a dictionary of parameters
185        """
186        return self.model.parameters()
187
188    def theory(self):
189        """
190        Return the theory corresponding to the model parameters.
191
192        This method uses lazy evaluation, and requires model.update() to be
193        called when the parameters have changed.
194        """
195        if 'theory' not in self._cache:
196            pars = self.model.state()
197            self._cache['theory'] = self._calc_theory(pars, cutoff=self.cutoff)
198        return self._cache['theory']
199
200    def residuals(self):
201        """
202        Return theory minus data normalized by uncertainty.
203        """
204        #if np.any(self.err ==0): print("zeros in err")
205        return (self.theory() - self.Iq) / self.dIq
206
207    def nllf(self):
208        """
209        Return the negative log likelihood of seeing data given the model
210        parameters, up to a normalizing constant which depends on the data
211        uncertainty.
212        """
213        delta = self.residuals()
214        #if np.any(np.isnan(R)): print("NaN in residuals")
215        return 0.5 * np.sum(delta ** 2)
216
217    #def __call__(self):
218    #    return 2 * self.nllf() / self.dof
219
220    def plot(self, view='log'):
221        """
222        Plot the data and residuals.
223        """
224        data, theory, resid = self._data, self.theory(), self.residuals()
225        plot_theory(data, theory, resid, view)
226
227    def simulate_data(self, noise=None):
228        """
229        Generate simulated data.
230        """
231        Iq = self.theory()
232        self._set_data(Iq, noise)
233
234    def save(self, basename):
235        """
236        Save the model parameters and data into a file.
237
238        Not Implemented.
239        """
240        if self.data_type == "sesans":
241            np.savetxt(basename+".dat", np.array([self._data.x, self.theory()]).T)
242        pass
243
244    def __getstate__(self):
245        # Can't pickle gpu functions, so instead make them lazy
246        state = self.__dict__.copy()
247        state['_kernel'] = None
248        return state
249
250    def __setstate__(self, state):
251        # pylint: disable=attribute-defined-outside-init
252        self.__dict__ = state
Note: See TracBrowser for help on using the repository browser.