source: sasmodels/sasmodels/bumps_model.py @ 21b116f

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

fix the remaining references to the parameter table as list

  • 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    pd_types = {} # distribution names
85    for p in model_info['parameters'].call_parameters:
86        value = kwargs.pop(p.name, p.default)
87        pars[p.name] = Parameter.default(value, name=p.name, limits=p.limits)
88        if p.polydisperse:
89            for part, default, limits in [
90                    ('_pd', 0., pars[p.name].limits),
91                    ('_pd_n', 35., (0, 1000)),
92                    ('_pd_nsigma', 3., (0, 10)),
93                ]:
94                name = p.name + part
95                value = kwargs.pop(name, default)
96                pars[name] = Parameter.default(value, name=name, limits=limits)
97            name = p.name + '_pd_type'
98            pd_types[name] = kwargs.pop(name, 'gaussian')
99
100    if kwargs:  # args not corresponding to parameters
101        raise TypeError("unexpected parameters: %s"
102                        % (", ".join(sorted(kwargs.keys()))))
103
104    return pars, pd_types
105
106class Model(object):
107    """
108    Bumps wrapper for a SAS model.
109
110    *model* is a runnable module as returned from :func:`core.load_model`.
111
112    *cutoff* is the polydispersity weight cutoff.
113
114    Any additional *key=value* pairs are model dependent parameters.
115    """
116    def __init__(self, model, **kwargs):
117        self._sasmodel = model
118        pars, pd_types = create_parameters(model.info, **kwargs)
119        for k, v in pars.items():
120            setattr(self, k, v)
121        for k, v in pd_types.items():
122            setattr(self, k, v)
123        self._parameter_names = list(pars.keys())
124        self._pd_type_names = list(pd_types.keys())
125
126    def parameters(self):
127        """
128        Return a dictionary of parameters objects for the parameters,
129        excluding polydispersity distribution type.
130        """
131        return dict((k, getattr(self, k)) for k in self._parameter_names)
132
133    def state(self):
134        """
135        Return a dictionary of current values for all the parameters,
136        including polydispersity distribution type.
137        """
138        pars = dict((k, getattr(self, k).value) for k in self._parameter_names)
139        pars.update((k, getattr(self, k)) for k in self._pd_type_names)
140        return pars
141
142class Experiment(DataMixin):
143    r"""
144    Bumps wrapper for a SAS experiment.
145
146    *data* is a :class:`data.Data1D`, :class:`data.Data2D` or
147    :class:`data.Sesans` object.  Use :func:`data.empty_data1D` or
148    :func:`data.empty_data2D` to define $q, \Delta q$ calculation
149    points for displaying the SANS curve when there is no measured data.
150
151    *model* is a :class:`Model` object.
152
153    *cutoff* is the integration cutoff, which avoids computing the
154    the SAS model where the polydispersity weight is low.
155
156    The resulting model can be used directly in a Bumps FitProblem call.
157    """
158    def __init__(self, data, model, cutoff=1e-5):
159
160        # remember inputs so we can inspect from outside
161        self.model = model
162        self.cutoff = cutoff
163        self._interpret_data(data, model._sasmodel)
164        self.update()
165
166    def update(self):
167        """
168        Call when model parameters have changed and theory needs to be
169        recalculated.
170        """
171        self._cache = {}
172
173    def numpoints(self):
174        """
175        Return the number of data points
176        """
177        return len(self.Iq)
178
179    def parameters(self):
180        """
181        Return a dictionary of parameters
182        """
183        return self.model.parameters()
184
185    def theory(self):
186        """
187        Return the theory corresponding to the model parameters.
188
189        This method uses lazy evaluation, and requires model.update() to be
190        called when the parameters have changed.
191        """
192        if 'theory' not in self._cache:
193            pars = self.model.state()
194            self._cache['theory'] = self._calc_theory(pars, cutoff=self.cutoff)
195        return self._cache['theory']
196
197    def residuals(self):
198        """
199        Return theory minus data normalized by uncertainty.
200        """
201        #if np.any(self.err ==0): print("zeros in err")
202        return (self.theory() - self.Iq) / self.dIq
203
204    def nllf(self):
205        """
206        Return the negative log likelihood of seeing data given the model
207        parameters, up to a normalizing constant which depends on the data
208        uncertainty.
209        """
210        delta = self.residuals()
211        #if np.any(np.isnan(R)): print("NaN in residuals")
212        return 0.5 * np.sum(delta ** 2)
213
214    #def __call__(self):
215    #    return 2 * self.nllf() / self.dof
216
217    def plot(self, view='log'):
218        """
219        Plot the data and residuals.
220        """
221        data, theory, resid = self._data, self.theory(), self.residuals()
222        plot_theory(data, theory, resid, view, Iq_calc = self.Iq_calc)
223
224    def simulate_data(self, noise=None):
225        """
226        Generate simulated data.
227        """
228        Iq = self.theory()
229        self._set_data(Iq, noise)
230
231    def save(self, basename):
232        """
233        Save the model parameters and data into a file.
234
235        Not Implemented.
236        """
237        if self.data_type == "sesans":
238            np.savetxt(basename+".dat", np.array([self._data.x, self.theory()]).T)
239        pass
240
241    def __getstate__(self):
242        # Can't pickle gpu functions, so instead make them lazy
243        state = self.__dict__.copy()
244        state['_kernel'] = None
245        return state
246
247    def __setstate__(self, state):
248        # pylint: disable=attribute-defined-outside-init
249        self.__dict__ = state
Note: See TracBrowser for help on using the repository browser.