source: sasmodels/sasmodels/bumps_model.py @ 0ac3db5

core_shell_microgelscostrafo411magnetic_modelrelease_v0.94release_v0.95ticket-1257-vesicle-productticket_1156ticket_1265_superballticket_822_more_unit_tests
Last change on this file since 0ac3db5 was 0ac3db5, checked in by jhbakker, 9 years ago

SESANSfit now uses sesans_reader dataloader

  • Property mode set to 100644
File size: 13.6 KB
Line 
1"""
2Sasmodels core.
3"""
4import sys, os
5import datetime
6
7from sasmodels import sesans
8
9# CRUFT python 2.6
10if not hasattr(datetime.timedelta, 'total_seconds'):
11    def delay(dt): return dt.days*86400 + dt.seconds + 1e-6*dt.microseconds
12else:
13    def delay(dt): return dt.total_seconds()
14
15import numpy as np
16
17try:
18    from .kernelcl import load_model as _loader
19except ImportError,exc:
20    import warnings
21    warnings.warn(str(exc))
22    warnings.warn("OpenCL not available --- using ctypes instead")
23    from .kerneldll import load_model as _loader
24
25def load_model(modelname, dtype='single'):
26    """
27    Load model by name.
28    """
29    sasmodels = __import__('sasmodels.models.'+modelname)
30    module = getattr(sasmodels.models, modelname, None)
31    model = _loader(module, dtype=dtype)
32    return model
33
34
35def tic():
36    """
37    Timer function.
38
39    Use "toc=tic()" to start the clock and "toc()" to measure
40    a time interval.
41    """
42    then = datetime.datetime.now()
43    return lambda: delay(datetime.datetime.now()-then)
44
45
46def load_data(filename):
47    """
48    Load data using a sasview loader.
49    """
50    from sas.dataloader.loader import Loader
51    loader = Loader()
52    data = loader.load(filename)
53    if data is None:
54        raise IOError("Data %r could not be loaded"%filename)
55    return data
56
57
58def empty_data1D(q):
59    """
60    Create empty 1D data using the given *q* as the x value.
61
62    Resolutions dq/q is 5%.
63    """
64
65    from sas.dataloader.data_info import Data1D
66
67    Iq = 100*np.ones_like(q)
68    dIq = np.sqrt(Iq)
69    data = Data1D(q, Iq, dx=0.05*q, dy=dIq)
70    data.filename = "fake data"
71    data.qmin, data.qmax = q.min(), q.max()
72    return data
73
74
75def empty_data2D(qx, qy=None):
76    """
77    Create empty 2D data using the given mesh.
78
79    If *qy* is missing, create a square mesh with *qy=qx*.
80
81    Resolution dq/q is 5%.
82    """
83    from sas.dataloader.data_info import Data2D, Detector
84
85    if qy is None:
86        qy = qx
87    Qx,Qy = np.meshgrid(qx,qy)
88    Qx,Qy = Qx.flatten(), Qy.flatten()
89    Iq = 100*np.ones_like(Qx)
90    dIq = np.sqrt(Iq)
91    mask = np.ones(len(Iq), dtype='bool')
92
93    data = Data2D()
94    data.filename = "fake data"
95    data.qx_data = Qx
96    data.qy_data = Qy
97    data.data = Iq
98    data.err_data = dIq
99    data.mask = mask
100
101    # 5% dQ/Q resolution
102    data.dqx_data = 0.05*Qx
103    data.dqy_data = 0.05*Qy
104
105    detector = Detector()
106    detector.pixel_size.x = 5 # mm
107    detector.pixel_size.y = 5 # mm
108    detector.distance = 4 # m
109    data.detector.append(detector)
110    data.xbins = qx
111    data.ybins = qy
112    data.source.wavelength = 5 # angstroms
113    data.source.wavelength_unit = "A"
114    data.Q_unit = "1/A"
115    data.I_unit = "1/cm"
116    data.q_data = np.sqrt(Qx**2 + Qy**2)
117    data.xaxis("Q_x", "A^{-1}")
118    data.yaxis("Q_y", "A^{-1}")
119    data.zaxis("Intensity", r"\text{cm}^{-1}")
120    return data
121
122
123def set_beam_stop(data, radius, outer=None):
124    """
125    Add a beam stop of the given *radius*.  If *outer*, make an annulus.
126    """
127    from sas.dataloader.manipulations import Ringcut
128    if hasattr(data, 'qx_data'):
129        data.mask = Ringcut(0, radius)(data)
130        if outer is not None:
131            data.mask += Ringcut(outer,np.inf)(data)
132    else:
133        data.mask = (data.x>=radius)
134        if outer is not None:
135            data.mask &= (data.x<outer)
136
137
138def set_half(data, half):
139    """
140    Select half of the data, either "right" or "left".
141    """
142    from sas.dataloader.manipulations import Boxcut
143    if half == 'right':
144        data.mask += Boxcut(x_min=-np.inf, x_max=0.0, y_min=-np.inf, y_max=np.inf)(data)
145    if half == 'left':
146        data.mask += Boxcut(x_min=0.0, x_max=np.inf, y_min=-np.inf, y_max=np.inf)(data)
147
148
149def set_top(data, max):
150    """
151    Chop the top off the data, above *max*.
152    """
153    from sas.dataloader.manipulations import Boxcut
154    data.mask += Boxcut(x_min=-np.inf, x_max=np.inf, y_min=-np.inf, y_max=max)(data)
155
156
157def plot_data(data, iq, vmin=None, vmax=None, scale='log'):
158    """
159    Plot the target value for the data.  This could be the data itself,
160    the theory calculation, or the residuals.
161
162    *scale* can be 'log' for log scale data, or 'linear'.
163    """
164    from numpy.ma import masked_array, masked
165    import matplotlib.pyplot as plt
166    if hasattr(data, 'qx_data'):
167        iq = iq[:]
168        valid = np.isfinite(iq)
169        if scale == 'log':
170            valid[valid] = (iq[valid] > 0)
171            iq[valid] = np.log10(iq[valid])
172        iq[~valid|data.mask] = 0
173        #plottable = iq
174        plottable = masked_array(iq, ~valid|data.mask)
175        xmin, xmax = min(data.qx_data), max(data.qx_data)
176        ymin, ymax = min(data.qy_data), max(data.qy_data)
177        if vmin is None: vmin = iq[valid&~data.mask].min()
178        if vmax is None: vmax = iq[valid&~data.mask].max()
179        plt.imshow(plottable.reshape(128,128),
180                   interpolation='nearest', aspect=1, origin='upper',
181                   extent=[xmin, xmax, ymin, ymax], vmin=vmin, vmax=vmax)
182    else: # 1D data
183        if scale == 'linear':
184            idx = np.isfinite(iq)
185            plt.plot(data.x[idx], iq[idx])
186        else:
187            idx = np.isfinite(iq)
188            idx[idx] = (iq[idx]>0)
189            plt.loglog(data.x[idx], iq[idx])
190
191
192def _plot_result1D(data, theory, view):
193    """
194    Plot the data and residuals for 1D data.
195    """
196    import matplotlib.pyplot as plt
197    from numpy.ma import masked_array, masked
198    #print "not a number",sum(np.isnan(data.y))
199    #data.y[data.y<0.05] = 0.5
200    mdata = masked_array(data.y, data.mask)
201    mdata[np.isnan(mdata)] = masked
202    if view is 'log':
203        mdata[mdata <= 0] = masked
204    mtheory = masked_array(theory, mdata.mask)
205    mresid = masked_array((theory-data.y)/data.dy, mdata.mask)
206
207    plt.subplot(121)
208    plt.errorbar(data.x, mdata, yerr=data.dy)
209    plt.plot(data.x, mtheory, '-', hold=True)
210    plt.yscale(view)
211    plt.subplot(122)
212    plt.plot(data.x, mresid, 'x')
213    #plt.axhline(1, color='black', ls='--',lw=1, hold=True)
214    #plt.axhline(0, color='black', lw=1, hold=True)
215    #plt.axhline(-1, color='black', ls='--',lw=1, hold=True)
216
217def _plot_sesans(data, theory, view):
218    import matplotlib.pyplot as plt
219    resid = (theory - data.y)/data.dy
220    plt.subplot(121)
221    plt.errorbar(data.x, data.y, yerr=data.dy)
222    plt.plot(data.x, theory, '-', hold=True)
223    plt.xlabel('spin echo length (A)')
224    plt.ylabel('polarization')
225    plt.subplot(122)
226    plt.plot(data.x, resid, 'x')
227    plt.xlabel('spin echo length (A)')
228    plt.ylabel('residuals')
229
230def _plot_result2D(data, theory, view):
231    """
232    Plot the data and residuals for 2D data.
233    """
234    import matplotlib.pyplot as plt
235    resid = (theory-data.data)/data.err_data
236    plt.subplot(131)
237    plot_data(data, data.data, scale=view)
238    plt.colorbar()
239    plt.subplot(132)
240    plot_data(data, theory, scale=view)
241    plt.colorbar()
242    plt.subplot(133)
243    plot_data(data, resid, scale='linear')
244    plt.colorbar()
245
246class BumpsModel(object):
247    """
248    Return a bumps wrapper for a SAS model.
249
250    *data* is the data to be fitted.
251
252    *model* is the SAS model, e.g., from :func:`gen.opencl_model`.
253
254    *cutoff* is the integration cutoff, which avoids computing the
255    the SAS model where the polydispersity weight is low.
256
257    Model parameters can be initialized with additional keyword
258    arguments, or by assigning to model.parameter_name.value.
259
260    The resulting bumps model can be used directly in a FitProblem call.
261    """
262    def __init__(self, data, model, cutoff=1e-5, **kw):
263        from bumps.names import Parameter
264
265        # remember inputs so we can inspect from outside
266        self.data = data
267        self.model = model
268        self.cutoff = cutoff
269# TODO       if  isinstance(data,SESANSData1D)       
270        if hasattr(data, 'lam'):
271            self.data_type = 'sesans'
272        elif hasattr(data, 'qx_data'):
273            self.data_type = 'Iqxy'
274        else:
275            self.data_type = 'Iq'
276
277        partype = model.info['partype']
278
279        # interpret data
280        if self.data_type == 'sesans':
281            q = sesans.make_q(data.sample.zacceptance, data.Rmax)
282            self.index = slice(None,None)
283            self.iq = data.y
284            self.diq = data.dy
285            self._theory = np.zeros_like(q)
286            q_vectors = [q]
287        elif self.data_type == 'Iqxy':
288            self.index = (data.mask==0) & (~np.isnan(data.data))
289            self.iq = data.data[self.index]
290            self.diq = data.err_data[self.index]
291            self._theory = np.zeros_like(data.data)
292            if not partype['orientation'] and not partype['magnetic']:
293                q_vectors = [np.sqrt(data.qx_data**2+data.qy_data**2)]
294            else:
295                q_vectors = [data.qx_data, data.qy_data]
296        elif self.data_type == 'Iq':
297            self.index = (data.x>=data.qmin) & (data.x<=data.qmax) & ~np.isnan(data.y)
298            self.iq = data.y[self.index]
299            self.diq = data.dy[self.index]
300            self._theory = np.zeros_like(data.y)
301            q_vectors = [data.x]
302        else:
303            raise ValueError("Unknown data type") # never gets here
304
305        # Remember function inputs so we can delay loading the function and
306        # so we can save/restore state
307        self._fn_inputs = [v[self.index] for v in q_vectors]
308        self._fn = None
309
310        # define bumps parameters
311        pars = []
312        for p in model.info['parameters']:
313            name, default, limits, ptype = p[0], p[2], p[3], p[4]
314            value = kw.pop(name, default)
315            setattr(self, name, Parameter.default(value, name=name, limits=limits))
316            pars.append(name)
317        for name in partype['pd-2d']:
318            for xpart,xdefault,xlimits in [
319                    ('_pd', 0, limits),
320                    ('_pd_n', 35, (0,1000)),
321                    ('_pd_nsigma', 3, (0, 10)),
322                    ('_pd_type', 'gaussian', None),
323                ]:
324                xname = name+xpart
325                xvalue = kw.pop(xname, xdefault)
326                if xlimits is not None:
327                    xvalue = Parameter.default(xvalue, name=xname, limits=xlimits)
328                    pars.append(xname)
329                setattr(self, xname, xvalue)
330        self._parameter_names = pars
331        if kw:
332            raise TypeError("unexpected parameters: %s"%(", ".join(sorted(kw.keys()))))
333        self.update()
334
335    def update(self):
336        self._cache = {}
337
338    def numpoints(self):
339        return len(self.iq)
340
341    def parameters(self):
342        return dict((k,getattr(self,k)) for k in self._parameter_names)
343
344    def theory(self):
345        if 'theory' not in self._cache:
346            if self._fn is None:
347                input = self.model.make_input(self._fn_inputs)
348                self._fn = self.model(input)
349
350            pars = [getattr(self,p).value for p in self._fn.fixed_pars]
351            pd_pars = [self._get_weights(p) for p in self._fn.pd_pars]
352            #print pars
353            self._theory[self.index] = self._fn(pars, pd_pars, self.cutoff)
354            #self._theory[:] = self._fn.eval(pars, pd_pars)
355            if self.data_type == 'sesans':
356                P = sesans.hankel(self.data.x, self.data.lam,
357                                  self.data.sample.thickness/10, self._fn_inputs[0],
358                                  self._theory)
359                self._cache['theory'] = P
360            else:
361                self._cache['theory'] = self._theory
362        return self._cache['theory']
363
364    def residuals(self):
365        #if np.any(self.err ==0): print "zeros in err"
366        return (self.theory()[self.index]-self.iq)/self.diq
367
368    def nllf(self):
369        R = self.residuals()
370        #if np.any(np.isnan(R)): print "NaN in residuals"
371        return 0.5*np.sum(R**2)
372
373    def __call__(self):
374        return 2*self.nllf()/self.dof
375
376    def plot(self, view='log'):
377        """
378        Plot the data and residuals.
379        """
380        data, theory = self.data, self.theory()
381        if self.data_type == 'Iq':
382            _plot_result1D(data, theory, view)
383        elif self.data_type == 'Iqxy':
384            _plot_result2D(data, theory, view)
385        elif self.data_type == 'sesans':
386            _plot_sesans(data, theory, view)
387        else:
388            raise ValueError("Unknown data type")
389
390    def simulate_data(self, noise=None):
391        print "noise", noise
392        if noise is None:
393            noise = self.diq[self.index]
394        else:
395            noise = 0.01*noise
396            self.diq[self.index] = noise
397        y = self.theory()
398        y += y*np.random.randn(*y.shape)*noise
399        if self.data_type == 'Iq':
400            self.data.y[self.index] = y
401        elif self.data_type == 'Iqxy':
402            self.data.data[self.index] = y
403        elif self.data_type == 'sesans':
404            self.data.y[self.index] = y
405        else:
406            raise ValueError("Unknown model")
407
408    def save(self, basename):
409        pass
410
411    def _get_weights(self, par):
412        from . import weights
413
414        relative = self.model.info['partype']['pd-rel']
415        limits = self.model.info['limits']
416        disperser,value,npts,width,nsigma = [getattr(self, par+ext)
417                for ext in ('_pd_type','','_pd_n','_pd','_pd_nsigma')]
418        v,w = weights.get_weights(
419            disperser, int(npts.value), width.value, nsigma.value,
420            value.value, limits[par], par in relative)
421        return v,w/w.max()
422
423    def __getstate__(self):
424        # Can't pickle gpu functions, so instead make them lazy
425        state = self.__dict__.copy()
426        state['_fn'] = None
427        return state
428
429    def __setstate__(self, state):
430        self.__dict__ = state
431
432
433def demo():
434    data = load_data('DEC07086.DAT')
435    set_beam_stop(data, 0.004)
436    plot_data(data)
437    import matplotlib.pyplot as plt; plt.show()
438
439
440if __name__ == "__main__":
441    demo()
Note: See TracBrowser for help on using the repository browser.