source: sasmodels/compare.py @ ab87a12

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

allow q4 plots in compare; choose random values within a factor of 2 of the model value for compare -random

  • Property mode set to 100755
File size: 12.8 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 BumpsModel, plot_data, tic
12try: from sasmodels import kernelcl
13except: from sasmodels import kerneldll as kernelcl
14from sasmodels import kerneldll
15from sasmodels.convert import revert_model
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(modelname, **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(modelname, 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 load_opencl(modelname, dtype='single'):
51    sasmodels = __import__('sasmodels.models.'+modelname)
52    module = getattr(sasmodels.models, modelname, None)
53    kernel = kernelcl.load_model(module, dtype=dtype)
54    return kernel
55
56def load_ctypes(modelname, dtype='single'):
57    sasmodels = __import__('sasmodels.models.'+modelname)
58    module = getattr(sasmodels.models, modelname, None)
59    kernel = kerneldll.load_model(module, dtype=dtype)
60    return kernel
61
62def randomize(p, v):
63    """
64    Randomizing parameter.
65
66    Guess the parameter type from name.
67    """
68    if any(p.endswith(s) for s in ('_pd_n','_pd_nsigma','_pd_type')):
69        return v
70    elif any(s in p for s in ('theta','phi','psi')):
71        # orientation in [-180,180], orientation pd in [0,45]
72        if p.endswith('_pd'):
73            return 45*np.random.rand()
74        else:
75            return 360*np.random.rand() - 180
76    elif 'sld' in p:
77        # sld in in [-0.5,10]
78        return 10.5*np.random.rand() - 0.5
79    elif p.endswith('_pd'):
80        # length pd in [0,1]
81        return np.random.rand()
82    else:
83        # values from 0 to 2*x for all other parameters
84        return 2*np.random.rand()*(v if v != 0 else 1)
85
86def randomize_model(name, pars, seed=None):
87    if seed is None:
88        seed = np.random.randint(1e9)
89    np.random.seed(seed)
90    # Note: the sort guarantees order of calls to random number generator
91    pars = dict((p,randomize(p,v)) for p,v in sorted(pars.items()))
92    # The capped cylinder model has a constraint on its parameters
93    if name == 'capped_cylinder' and pars['cap_radius'] < pars['radius']:
94        pars['radius'],pars['cap_radius'] = pars['cap_radius'],pars['radius']
95    return pars, seed
96
97def parlist(pars):
98    return "\n".join("%s: %s"%(p,v) for p,v in sorted(pars.items()))
99
100def suppress_pd(pars):
101    """
102    Suppress theta_pd for now until the normalization is resolved.
103
104    May also suppress complete polydispersity of the model to test
105    models more quickly.
106    """
107    for p in pars:
108        if p.endswith("_pd"): pars[p] = 0
109
110def eval_sasview(name, pars, data, Nevals=1):
111    model = sasview_model(name, **pars)
112    toc = tic()
113    for _ in range(Nevals):
114        if hasattr(data, 'qx_data'):
115            value = model.evalDistribution([data.qx_data, data.qy_data])
116        else:
117            value = model.evalDistribution(data.x)
118    average_time = toc()*1000./Nevals
119    return value, average_time
120
121def eval_opencl(name, pars, data, dtype='single', Nevals=1, cutoff=0):
122    try:
123        model = load_opencl(name, dtype=dtype)
124    except Exception,exc:
125        print exc
126        print "... trying again with single precision"
127        model = load_opencl(name, dtype='single')
128    problem = BumpsModel(data, model, cutoff=cutoff, **pars)
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(name, pars, data, dtype='double', Nevals=1, cutoff=0):
138    model = load_ctypes(name, dtype=dtype)
139    problem = BumpsModel(data, model, cutoff=cutoff, **pars)
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, 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))
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)
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    is2D = not "-1d" in opts
174    data, index = make_data(qmax, is2D, Nq, view=view)
175
176
177    # modelling accuracy is determined by dtype and cutoff
178    dtype = 'double' if '-double' in opts else 'single'
179    cutoff = float(opt_values.get('-cutoff','1e-5'))
180
181    # randomize parameters
182    pars.update(set_pars)
183    if '-random' in opts or '-random' in opt_values:
184        seed = int(opt_values['-random']) if '-random' in opt_values else None
185        pars, seed = randomize_model(name, pars, seed=seed)
186        print "Randomize using -random=%i"%seed
187
188    # parameter selection
189    if '-mono' in opts:
190        suppress_pd(pars)
191    if '-pars' in opts:
192        print "pars",parlist(pars)
193
194    # OpenCl calculation
195    if Nocl > 0:
196        ocl, ocl_time = eval_opencl(name, pars, data, dtype, Nocl)
197        print "opencl t=%.1f ms, intensity=%.0f"%(ocl_time, sum(ocl[index]))
198        #print max(ocl), min(ocl)
199
200    # ctypes/sasview calculation
201    if Ncpu > 0 and "-ctypes" in opts:
202        cpu, cpu_time = eval_ctypes(name, pars, data, dtype=dtype, cutoff=cutoff, Nevals=Ncpu)
203        comp = "ctypes"
204        print "ctypes t=%.1f ms, intensity=%.0f"%(cpu_time, sum(cpu[index]))
205    elif Ncpu > 0:
206        cpu, cpu_time = eval_sasview(name, pars, data, Ncpu)
207        comp = "sasview"
208        print "sasview t=%.1f ms, intensity=%.0f"%(cpu_time, sum(cpu[index]))
209
210    # Compare, but only if computing both forms
211    if Nocl > 0 and Ncpu > 0:
212        #print "speedup %.2g"%(cpu_time/ocl_time)
213        #print "max |ocl/cpu|", max(abs(ocl/cpu)), "%.15g"%max(abs(ocl)), "%.15g"%max(abs(cpu))
214        #cpu *= max(ocl/cpu)
215        resid, relerr = np.zeros_like(ocl), np.zeros_like(ocl)
216        resid[index] = (ocl - cpu)[index]
217        relerr[index] = resid[index]/cpu[index]
218        #bad = (relerr>1e-4)
219        #print relerr[bad],cpu[bad],ocl[bad],data.qx_data[bad],data.qy_data[bad]
220        print "max(|ocl-%s|)"%comp, max(abs(resid[index]))
221        print "max(|(ocl-%s)/%s|)"%(comp,comp), max(abs(relerr[index]))
222        p98 = int(len(relerr[index])*0.98)
223        print "98%% (|(ocl-%s)/%s|) <"%(comp,comp), np.sort(abs(relerr[index]))[p98]
224
225
226    # Plot if requested
227    if '-noplot' in opts: return
228    import matplotlib.pyplot as plt
229    if Ncpu > 0:
230        if Nocl > 0: plt.subplot(131)
231        plot_data(data, cpu, view=view)
232        plt.title("%s t=%.1f ms"%(comp,cpu_time))
233        cbar_title = "log I"
234    if Nocl > 0:
235        if Ncpu > 0: plt.subplot(132)
236        plot_data(data, ocl, view=view)
237        plt.title("opencl t=%.1f ms"%ocl_time)
238        cbar_title = "log I"
239    if Ncpu > 0 and Nocl > 0:
240        plt.subplot(133)
241        if '-abs' in opts:
242            err,errstr,errview = resid, "abs err", "linear"
243        else:
244            err,errstr,errview = abs(relerr), "rel err", "log"
245        #err,errstr = ocl/cpu,"ratio"
246        plot_data(data, err, view=errview)
247        plt.title("max %s = %.3g"%(errstr, max(abs(err[index]))))
248        cbar_title = errstr if errview=="linear" else "log "+errstr
249    if is2D:
250        h = plt.colorbar()
251        h.ax.set_title(cbar_title)
252
253    if Ncpu > 0 and Nocl > 0 and '-hist' in opts:
254        plt.figure()
255        v = relerr[index]
256        v[v==0] = 0.5*np.min(np.abs(v[v!=0]))
257        plt.hist(np.log10(np.abs(v)), normed=1, bins=50);
258        plt.xlabel('log10(err), err = | F(q) single - F(q) double| / | F(q) double |');
259        plt.ylabel('P(err)')
260        plt.title('Comparison of single and double precision models for %s'%name)
261
262    plt.show()
263
264# ===========================================================================
265#
266USAGE="""
267usage: compare.py model [Nopencl] [Nsasview] [options...] [key=val]
268
269Compare the speed and value for a model between the SasView original and the
270OpenCL rewrite.
271
272model is the name of the model to compare (see below).
273Nopencl is the number of times to run the OpenCL model (default=5)
274Nsasview is the number of times to run the Sasview model (default=1)
275
276Options (* for default):
277
278    -plot*/-noplot plots or suppress the plot of the model
279    -single*/-double uses double precision for comparison
280    -lowq*/-midq/-highq/-exq use q values up to 0.05, 0.2, 1.0, 10.0
281    -Nq=128 sets the number of Q points in the data set
282    -1d/-2d* computes 1d or 2d data
283    -preset*/-random[=seed] preset or random parameters
284    -mono/-poly* force monodisperse/polydisperse
285    -ctypes/-sasview* whether cpu is tested using sasview or ctypes
286    -cutoff=1e-5*/value cutoff for including a point in polydispersity
287    -pars/-nopars* prints the parameter set or not
288    -abs/-rel* plot relative or absolute error
289    -linear/-log/-q4 intensity scaling
290    -hist/-nohist* plot histogram of relative error
291
292Key=value pairs allow you to set specific values to any of the model
293parameters.
294
295Available models:
296
297    %s
298"""
299
300NAME_OPTIONS = set([
301    'plot','noplot',
302    'single','double',
303    'lowq','midq','highq','exq',
304    '2d','1d',
305    'preset','random',
306    'poly','mono',
307    'sasview','ctypes',
308    'nopars','pars',
309    'rel','abs',
310    'linear', 'log', 'q4',
311    'hist','nohist',
312    ])
313VALUE_OPTIONS = [
314    # Note: random is both a name option and a value option
315    'cutoff', 'random', 'Nq',
316    ]
317
318def get_demo_pars(name):
319    import sasmodels.models
320    __import__('sasmodels.models.'+name)
321    model = getattr(sasmodels.models, name)
322    pars = getattr(model, 'demo', None)
323    if pars is None: pars = dict((p[0],p[2]) for p in model.parameters)
324    return pars
325
326def main():
327    opts = [arg for arg in sys.argv[1:] if arg.startswith('-')]
328    args = [arg for arg in sys.argv[1:] if not arg.startswith('-')]
329    models = "\n    ".join("%-15s"%v for v in MODELS)
330    if len(args) == 0:
331        print(USAGE%models)
332        sys.exit(1)
333    if args[0] not in MODELS:
334        print "Model %r not available. Use one of:\n    %s"%(args[0],models)
335        sys.exit(1)
336
337    invalid = [o[1:] for o in opts
338               if o[1:] not in NAME_OPTIONS
339                  and not any(o.startswith('-%s='%t) for t in VALUE_OPTIONS)]
340    if invalid:
341        print "Invalid options: %s"%(", ".join(invalid))
342        sys.exit(1)
343
344    # Get demo parameters from model definition, or use default parameters
345    # if model does not define demo parameters
346    name = args[0]
347    pars = get_demo_pars(name)
348
349    Nopencl = int(args[1]) if len(args) > 1 else 5
350    Nsasview = int(args[2]) if len(args) > 2 else 1
351
352    # Fill in default polydispersity parameters
353    pds = set(p.split('_pd')[0] for p in pars if p.endswith('_pd'))
354    for p in pds:
355        if p+"_pd_nsigma" not in pars: pars[p+"_pd_nsigma"] = 3
356        if p+"_pd_type" not in pars: pars[p+"_pd_type"] = "gaussian"
357
358    # Fill in parameters given on the command line
359    set_pars = {}
360    for arg in args[3:]:
361        k,v = arg.split('=')
362        if k not in pars:
363            # extract base name without distribution
364            s = set(p.split('_pd')[0] for p in pars)
365            print "%r invalid; parameters are: %s"%(k,", ".join(sorted(s)))
366            sys.exit(1)
367        set_pars[k] = float(v) if not v.endswith('type') else v
368
369    compare(name, pars, Nsasview, Nopencl, opts, set_pars)
370
371if __name__ == "__main__":
372    main()
Note: See TracBrowser for help on using the repository browser.