source: sasmodels/sasmodels/bumps_model.py @ aaf75b6

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

Merge branch 'master' into polydisp

Conflicts:

sasmodels/direct_model.py

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