source: sasmodels/compare.py @ 346bc88

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

Add 2D resolution smearing. Split BumpsModel? into Experiment and Model and fix up examples.

  • Property mode set to 100755
File size: 13.6 KB
Line 
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3
4import sys
5import math
6from os.path import basename, dirname, join as joinpath
7import glob
8
9import numpy as np
10
11from sasmodels.bumps_model import Model, Experiment, plot_theory, tic
12from sasmodels import core
13from sasmodels import kerneldll
14from sasmodels.convert import revert_model
15kerneldll.ALLOW_SINGLE_PRECISION_DLLS = True
16
17# List of available models
18ROOT = dirname(__file__)
19MODELS = [basename(f)[:-3]
20          for f in sorted(glob.glob(joinpath(ROOT,"sasmodels","models","[a-zA-Z]*.py")))]
21
22
23def sasview_model(model_definition, **pars):
24    """
25    Load a sasview model given the model name.
26    """
27    # convert model parameters from sasmodel form to sasview form
28    #print "old",sorted(pars.items())
29    modelname, pars = revert_model(model_definition, pars)
30    #print "new",sorted(pars.items())
31    sas = __import__('sas.models.'+modelname)
32    ModelClass = getattr(getattr(sas.models,modelname,None),modelname,None)
33    if ModelClass is None:
34        raise ValueError("could not find model %r in sas.models"%modelname)
35    model = ModelClass()
36
37    for k,v in pars.items():
38        if k.endswith("_pd"):
39            model.dispersion[k[:-3]]['width'] = v
40        elif k.endswith("_pd_n"):
41            model.dispersion[k[:-5]]['npts'] = v
42        elif k.endswith("_pd_nsigma"):
43            model.dispersion[k[:-10]]['nsigmas'] = v
44        elif k.endswith("_pd_type"):
45            model.dispersion[k[:-8]]['type'] = v
46        else:
47            model.setParam(k, v)
48    return model
49
50def randomize(p, v):
51    """
52    Randomizing parameter.
53
54    Guess the parameter type from name.
55    """
56    if any(p.endswith(s) for s in ('_pd_n','_pd_nsigma','_pd_type')):
57        return v
58    elif any(s in p for s in ('theta','phi','psi')):
59        # orientation in [-180,180], orientation pd in [0,45]
60        if p.endswith('_pd'):
61            return 45*np.random.rand()
62        else:
63            return 360*np.random.rand() - 180
64    elif 'sld' in p:
65        # sld in in [-0.5,10]
66        return 10.5*np.random.rand() - 0.5
67    elif p.endswith('_pd'):
68        # length pd in [0,1]
69        return np.random.rand()
70    else:
71        # values from 0 to 2*x for all other parameters
72        return 2*np.random.rand()*(v if v != 0 else 1)
73
74def randomize_model(name, pars, seed=None):
75    if seed is None:
76        seed = np.random.randint(1e9)
77    np.random.seed(seed)
78    # Note: the sort guarantees order of calls to random number generator
79    pars = dict((p,randomize(p,v)) for p,v in sorted(pars.items()))
80    # The capped cylinder model has a constraint on its parameters
81    if name == 'capped_cylinder' and pars['cap_radius'] < pars['radius']:
82        pars['radius'],pars['cap_radius'] = pars['cap_radius'],pars['radius']
83    return pars, seed
84
85def parlist(pars):
86    return "\n".join("%s: %s"%(p,v) for p,v in sorted(pars.items()))
87
88def suppress_pd(pars):
89    """
90    Suppress theta_pd for now until the normalization is resolved.
91
92    May also suppress complete polydispersity of the model to test
93    models more quickly.
94    """
95    for p in pars:
96        if p.endswith("_pd"): pars[p] = 0
97
98def eval_sasview(name, pars, data, Nevals=1):
99    from sas.models.qsmearing import smear_selection
100    model = sasview_model(name, **pars)
101    smearer = smear_selection(data, model=model)
102    toc = tic()
103    for _ in range(Nevals):
104        if hasattr(data, 'qx_data'):
105            q = np.sqrt(data.qx_data**2 + data.qy_data**2)
106            index = ((~data.mask) & (~np.isnan(data.data))
107                     & (q >= data.qmin) & (q <= data.qmax))
108            if smearer is not None:
109                smearer.model = model  # because smear_selection has a bug
110                smearer.set_index(index)
111                value = smearer.get_value()
112            else:
113                value = model.evalDistribution([data.qx_data[index], data.qy_data[index]])
114        else:
115            value = model.evalDistribution(data.x)
116            if smearer is not None:
117                value = smearer(value)
118    average_time = toc()*1000./Nevals
119    return value, average_time
120
121def eval_opencl(model_definition, pars, data, dtype='single', Nevals=1, cutoff=0):
122    try:
123        model = core.load_model(model_definition, dtype=dtype, platform="ocl")
124    except Exception,exc:
125        print exc
126        print "... trying again with single precision"
127        model = core.load_model(model_definition, dtype='single', platform="ocl")
128    problem = Experiment(data, Model(model, **pars), cutoff=cutoff)
129    toc = tic()
130    for _ in range(Nevals):
131        #pars['scale'] = np.random.rand()
132        problem.update()
133        value = problem.theory()
134    average_time = toc()*1000./Nevals
135    return value, average_time
136
137def eval_ctypes(model_definition, pars, data, dtype='double', Nevals=1, cutoff=0):
138    model = core.load_model(model_definition, dtype=dtype, platform="dll")
139    problem = Experiment(data, Model(model, **pars), cutoff=cutoff)
140    toc = tic()
141    for _ in range(Nevals):
142        problem.update()
143        value = problem.theory()
144    average_time = toc()*1000./Nevals
145    return value, average_time
146
147def make_data(qmax, is2D, Nq=128, resolution=0.0, view='log'):
148    if is2D:
149        from sasmodels.bumps_model import empty_data2D, set_beam_stop
150        data = empty_data2D(np.linspace(-qmax, qmax, Nq), resolution=resolution)
151        set_beam_stop(data, 0.004)
152        index = ~data.mask
153    else:
154        from sasmodels.bumps_model import empty_data1D
155        if view == 'log':
156            qmax = math.log10(qmax)
157            q = np.logspace(qmax-3, qmax, Nq)
158        else:
159            q = np.linspace(0.001*qmax, qmax, Nq)
160        data = empty_data1D(q, resolution=resolution)
161        index = slice(None, None)
162    return data, index
163
164def compare(name, pars, Ncpu, Nocl, opts, set_pars):
165    view = 'linear' if '-linear' in opts else 'log' if '-log' in opts else 'q4' if '-q4' in opts else 'log'
166
167    opt_values = dict(split
168                      for s in opts for split in ((s.split('='),))
169                      if len(split) == 2)
170    # Sort out data
171    qmax = 10.0 if '-exq' in opts else 1.0 if '-highq' in opts else 0.2 if '-midq' in opts else 0.05
172    Nq = int(opt_values.get('-Nq', '128'))
173    res = float(opt_values.get('-res', '0'))
174    is2D = not "-1d" in opts
175    data, index = make_data(qmax, is2D, Nq, res, view=view)
176
177
178    # modelling accuracy is determined by dtype and cutoff
179    dtype = 'double' if '-double' in opts else 'single'
180    cutoff = float(opt_values.get('-cutoff','1e-5'))
181
182    # randomize parameters
183    pars.update(set_pars)
184    if '-random' in opts or '-random' in opt_values:
185        seed = int(opt_values['-random']) if '-random' in opt_values else None
186        pars, seed = randomize_model(name, pars, seed=seed)
187        print "Randomize using -random=%i"%seed
188
189    # parameter selection
190    if '-mono' in opts:
191        suppress_pd(pars)
192    if '-pars' in opts:
193        print "pars",parlist(pars)
194
195    model_definition = core.load_model_definition(name)
196    # OpenCl calculation
197    if Nocl > 0:
198        ocl, ocl_time = eval_opencl(model_definition, pars, data,
199                                    dtype=dtype, cutoff=cutoff, Nevals=Nocl)
200        print "opencl t=%.1f ms, intensity=%.0f"%(ocl_time, sum(ocl))
201        #print max(ocl), min(ocl)
202
203    # ctypes/sasview calculation
204    if Ncpu > 0 and "-ctypes" in opts:
205        cpu, cpu_time = eval_ctypes(model_definition, pars, data,
206                                    dtype=dtype, cutoff=cutoff, Nevals=Ncpu)
207        comp = "ctypes"
208        print "ctypes t=%.1f ms, intensity=%.0f"%(cpu_time, sum(cpu))
209    elif Ncpu > 0:
210        cpu, cpu_time = eval_sasview(model_definition, pars, data, Ncpu)
211        comp = "sasview"
212        print "sasview t=%.1f ms, intensity=%.0f"%(cpu_time, sum(cpu))
213
214    # Compare, but only if computing both forms
215    if Nocl > 0 and Ncpu > 0:
216        #print "speedup %.2g"%(cpu_time/ocl_time)
217        #print "max |ocl/cpu|", max(abs(ocl/cpu)), "%.15g"%max(abs(ocl)), "%.15g"%max(abs(cpu))
218        #cpu *= max(ocl/cpu)
219        resid = (ocl - cpu)
220        relerr = resid/cpu
221        #bad = (relerr>1e-4)
222        #print relerr[bad],cpu[bad],ocl[bad],data.qx_data[bad],data.qy_data[bad]
223        def stats(label,err):
224            sorted_err = np.sort(abs(err))
225            p50 = int((len(err)-1)*0.50)
226            p98 = int((len(err)-1)*0.98)
227            data = [
228                "max:%.3e"%sorted_err[-1],
229                "median:%.3e"%sorted_err[p50],
230                "98%%:%.3e"%sorted_err[p98],
231                "rms:%.3e"%np.sqrt(np.mean(err**2)),
232                "zero-offset:%+.3e"%np.mean(err),
233                ]
234            print label,"  ".join(data)
235        stats("|ocl-%s|"%comp+(" "*(3+len(comp))), resid)
236        stats("|(ocl-%s)/%s|"%(comp,comp), relerr)
237
238    # Plot if requested
239    if '-noplot' in opts: return
240    import matplotlib.pyplot as plt
241    if Ncpu > 0:
242        if Nocl > 0: plt.subplot(131)
243        plot_theory(data, cpu, view=view)
244        plt.title("%s t=%.1f ms"%(comp,cpu_time))
245        cbar_title = "log I"
246    if Nocl > 0:
247        if Ncpu > 0: plt.subplot(132)
248        plot_theory(data, ocl, view=view)
249        plt.title("opencl t=%.1f ms"%ocl_time)
250        cbar_title = "log I"
251    if Ncpu > 0 and Nocl > 0:
252        plt.subplot(133)
253        if '-abs' in opts:
254            err,errstr,errview = resid, "abs err", "linear"
255        else:
256            err,errstr,errview = abs(relerr), "rel err", "log"
257        #err,errstr = ocl/cpu,"ratio"
258        plot_theory(data, err, view=errview)
259        plt.title("max %s = %.3g"%(errstr, max(abs(err))))
260        cbar_title = errstr if errview=="linear" else "log "+errstr
261    if is2D:
262        h = plt.colorbar()
263        h.ax.set_title(cbar_title)
264
265    if Ncpu > 0 and Nocl > 0 and '-hist' in opts:
266        plt.figure()
267        v = relerr
268        v[v==0] = 0.5*np.min(np.abs(v[v!=0]))
269        plt.hist(np.log10(np.abs(v)), normed=1, bins=50);
270        plt.xlabel('log10(err), err = | F(q) single - F(q) double| / | F(q) double |');
271        plt.ylabel('P(err)')
272        plt.title('Comparison of single and double precision models for %s'%name)
273
274    plt.show()
275
276# ===========================================================================
277#
278USAGE="""
279usage: compare.py model [Nopencl] [Nsasview] [options...] [key=val]
280
281Compare the speed and value for a model between the SasView original and the
282OpenCL rewrite.
283
284model is the name of the model to compare (see below).
285Nopencl is the number of times to run the OpenCL model (default=5)
286Nsasview is the number of times to run the Sasview model (default=1)
287
288Options (* for default):
289
290    -plot*/-noplot plots or suppress the plot of the model
291    -single*/-double uses double precision for comparison
292    -lowq*/-midq/-highq/-exq use q values up to 0.05, 0.2, 1.0, 10.0
293    -Nq=128 sets the number of Q points in the data set
294    -1d/-2d* computes 1d or 2d data
295    -preset*/-random[=seed] preset or random parameters
296    -mono/-poly* force monodisperse/polydisperse
297    -ctypes/-sasview* whether cpu is tested using sasview or ctypes
298    -cutoff=1e-5*/value cutoff for including a point in polydispersity
299    -pars/-nopars* prints the parameter set or not
300    -abs/-rel* plot relative or absolute error
301    -linear/-log/-q4 intensity scaling
302    -hist/-nohist* plot histogram of relative error
303    -res=0 sets the resolution width dQ/Q if calculating with resolution
304
305Key=value pairs allow you to set specific values to any of the model
306parameters.
307
308Available models:
309
310    %s
311"""
312
313NAME_OPTIONS = set([
314    'plot','noplot',
315    'single','double',
316    'lowq','midq','highq','exq',
317    '2d','1d',
318    'preset','random',
319    'poly','mono',
320    'sasview','ctypes',
321    'nopars','pars',
322    'rel','abs',
323    'linear', 'log', 'q4',
324    'hist','nohist',
325    ])
326VALUE_OPTIONS = [
327    # Note: random is both a name option and a value option
328    'cutoff', 'random', 'Nq', 'res',
329    ]
330
331def get_demo_pars(name):
332    import sasmodels.models
333    __import__('sasmodels.models.'+name)
334    model = getattr(sasmodels.models, name)
335    pars = getattr(model, 'demo', None)
336    if pars is None: pars = dict((p[0],p[2]) for p in model.parameters)
337    return pars
338
339def main():
340    opts = [arg for arg in sys.argv[1:] if arg.startswith('-')]
341    args = [arg for arg in sys.argv[1:] if not arg.startswith('-')]
342    models = "\n    ".join("%-15s"%v for v in MODELS)
343    if len(args) == 0:
344        print(USAGE%models)
345        sys.exit(1)
346    if args[0] not in MODELS:
347        print "Model %r not available. Use one of:\n    %s"%(args[0],models)
348        sys.exit(1)
349
350    invalid = [o[1:] for o in opts
351               if o[1:] not in NAME_OPTIONS
352                  and not any(o.startswith('-%s='%t) for t in VALUE_OPTIONS)]
353    if invalid:
354        print "Invalid options: %s"%(", ".join(invalid))
355        sys.exit(1)
356
357    # Get demo parameters from model definition, or use default parameters
358    # if model does not define demo parameters
359    name = args[0]
360    pars = get_demo_pars(name)
361
362    Nopencl = int(args[1]) if len(args) > 1 else 5
363    Nsasview = int(args[2]) if len(args) > 2 else 1
364
365    # Fill in default polydispersity parameters
366    pds = set(p.split('_pd')[0] for p in pars if p.endswith('_pd'))
367    for p in pds:
368        if p+"_pd_nsigma" not in pars: pars[p+"_pd_nsigma"] = 3
369        if p+"_pd_type" not in pars: pars[p+"_pd_type"] = "gaussian"
370
371    # Fill in parameters given on the command line
372    set_pars = {}
373    for arg in args[3:]:
374        k,v = arg.split('=')
375        if k not in pars:
376            # extract base name without distribution
377            s = set(p.split('_pd')[0] for p in pars)
378            print "%r invalid; parameters are: %s"%(k,", ".join(sorted(s)))
379            sys.exit(1)
380        set_pars[k] = float(v) if not v.endswith('type') else v
381
382    compare(name, pars, Nsasview, Nopencl, opts, set_pars)
383
384if __name__ == "__main__":
385    main()
Note: See TracBrowser for help on using the repository browser.