source: sasmodels/compare.py @ 3d5c6f8

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

add resolution accuracy setting to compare

  • Property mode set to 100755
File size: 13.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 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.accuracy = data.accuracy
111                smearer.set_index(index)
112                value = smearer.get_value()
113            else:
114                value = model.evalDistribution([data.qx_data[index], data.qy_data[index]])
115        else:
116            value = model.evalDistribution(data.x)
117            if smearer is not None:
118                value = smearer(value)
119    average_time = toc()*1000./Nevals
120    return value, average_time
121
122def eval_opencl(model_definition, pars, data, dtype='single', Nevals=1, cutoff=0):
123    try:
124        model = core.load_model(model_definition, dtype=dtype, platform="ocl")
125    except Exception,exc:
126        print exc
127        print "... trying again with single precision"
128        model = core.load_model(model_definition, dtype='single', platform="ocl")
129    problem = Experiment(data, Model(model, **pars), cutoff=cutoff)
130    toc = tic()
131    for _ in range(Nevals):
132        #pars['scale'] = np.random.rand()
133        problem.update()
134        value = problem.theory()
135    average_time = toc()*1000./Nevals
136    return value, average_time
137
138def eval_ctypes(model_definition, pars, data, dtype='double', Nevals=1, cutoff=0):
139    model = core.load_model(model_definition, dtype=dtype, platform="dll")
140    problem = Experiment(data, Model(model, **pars), cutoff=cutoff)
141    toc = tic()
142    for _ in range(Nevals):
143        problem.update()
144        value = problem.theory()
145    average_time = toc()*1000./Nevals
146    return value, average_time
147
148def make_data(qmax, is2D, Nq=128, resolution=0.0, accuracy='Low', view='log'):
149    if is2D:
150        from sasmodels.bumps_model import empty_data2D, set_beam_stop
151        data = empty_data2D(np.linspace(-qmax, qmax, Nq), resolution=resolution)
152        data.accuracy = accuracy
153        set_beam_stop(data, 0.004)
154        index = ~data.mask
155    else:
156        from sasmodels.bumps_model import empty_data1D
157        if view == 'log':
158            qmax = math.log10(qmax)
159            q = np.logspace(qmax-3, qmax, Nq)
160        else:
161            q = np.linspace(0.001*qmax, qmax, Nq)
162        data = empty_data1D(q, resolution=resolution)
163        index = slice(None, None)
164    return data, index
165
166def compare(name, pars, Ncpu, Nocl, opts, set_pars):
167    view = 'linear' if '-linear' in opts else 'log' if '-log' in opts else 'q4' if '-q4' in opts else 'log'
168
169    opt_values = dict(split
170                      for s in opts for split in ((s.split('='),))
171                      if len(split) == 2)
172    # Sort out data
173    qmax = 10.0 if '-exq' in opts else 1.0 if '-highq' in opts else 0.2 if '-midq' in opts else 0.05
174    Nq = int(opt_values.get('-Nq', '128'))
175    res = float(opt_values.get('-res', '0'))
176    accuracy = opt_values.get('-accuracy', 'Low')
177    is2D = not "-1d" in opts
178    data, index = make_data(qmax, is2D, Nq, res, accuracy, view=view)
179
180
181    # modelling accuracy is determined by dtype and cutoff
182    dtype = 'double' if '-double' in opts else 'single'
183    cutoff = float(opt_values.get('-cutoff','1e-5'))
184
185    # randomize parameters
186    pars.update(set_pars)
187    if '-random' in opts or '-random' in opt_values:
188        seed = int(opt_values['-random']) if '-random' in opt_values else None
189        pars, seed = randomize_model(name, pars, seed=seed)
190        print "Randomize using -random=%i"%seed
191
192    # parameter selection
193    if '-mono' in opts:
194        suppress_pd(pars)
195    if '-pars' in opts:
196        print "pars",parlist(pars)
197
198    model_definition = core.load_model_definition(name)
199    # OpenCl calculation
200    if Nocl > 0:
201        ocl, ocl_time = eval_opencl(model_definition, pars, data,
202                                    dtype=dtype, cutoff=cutoff, Nevals=Nocl)
203        print "opencl t=%.1f ms, intensity=%.0f"%(ocl_time, sum(ocl))
204        #print max(ocl), min(ocl)
205
206    # ctypes/sasview calculation
207    if Ncpu > 0 and "-ctypes" in opts:
208        cpu, cpu_time = eval_ctypes(model_definition, pars, data,
209                                    dtype=dtype, cutoff=cutoff, Nevals=Ncpu)
210        comp = "ctypes"
211        print "ctypes t=%.1f ms, intensity=%.0f"%(cpu_time, sum(cpu))
212    elif Ncpu > 0:
213        cpu, cpu_time = eval_sasview(model_definition, pars, data, Ncpu)
214        comp = "sasview"
215        print "sasview t=%.1f ms, intensity=%.0f"%(cpu_time, sum(cpu))
216
217    # Compare, but only if computing both forms
218    if Nocl > 0 and Ncpu > 0:
219        #print "speedup %.2g"%(cpu_time/ocl_time)
220        #print "max |ocl/cpu|", max(abs(ocl/cpu)), "%.15g"%max(abs(ocl)), "%.15g"%max(abs(cpu))
221        #cpu *= max(ocl/cpu)
222        resid = (ocl - cpu)
223        relerr = resid/cpu
224        #bad = (relerr>1e-4)
225        #print relerr[bad],cpu[bad],ocl[bad],data.qx_data[bad],data.qy_data[bad]
226        def stats(label,err):
227            sorted_err = np.sort(abs(err))
228            p50 = int((len(err)-1)*0.50)
229            p98 = int((len(err)-1)*0.98)
230            data = [
231                "max:%.3e"%sorted_err[-1],
232                "median:%.3e"%sorted_err[p50],
233                "98%%:%.3e"%sorted_err[p98],
234                "rms:%.3e"%np.sqrt(np.mean(err**2)),
235                "zero-offset:%+.3e"%np.mean(err),
236                ]
237            print label,"  ".join(data)
238        stats("|ocl-%s|"%comp+(" "*(3+len(comp))), resid)
239        stats("|(ocl-%s)/%s|"%(comp,comp), relerr)
240
241    # Plot if requested
242    if '-noplot' in opts: return
243    import matplotlib.pyplot as plt
244    if Ncpu > 0:
245        if Nocl > 0: plt.subplot(131)
246        plot_theory(data, cpu, view=view)
247        plt.title("%s t=%.1f ms"%(comp,cpu_time))
248        cbar_title = "log I"
249    if Nocl > 0:
250        if Ncpu > 0: plt.subplot(132)
251        plot_theory(data, ocl, view=view)
252        plt.title("opencl t=%.1f ms"%ocl_time)
253        cbar_title = "log I"
254    if Ncpu > 0 and Nocl > 0:
255        plt.subplot(133)
256        if '-abs' in opts:
257            err,errstr,errview = resid, "abs err", "linear"
258        else:
259            err,errstr,errview = abs(relerr), "rel err", "log"
260        #err,errstr = ocl/cpu,"ratio"
261        plot_theory(data, err, view=errview)
262        plt.title("max %s = %.3g"%(errstr, max(abs(err))))
263        cbar_title = errstr if errview=="linear" else "log "+errstr
264    if is2D:
265        h = plt.colorbar()
266        h.ax.set_title(cbar_title)
267
268    if Ncpu > 0 and Nocl > 0 and '-hist' in opts:
269        plt.figure()
270        v = relerr
271        v[v==0] = 0.5*np.min(np.abs(v[v!=0]))
272        plt.hist(np.log10(np.abs(v)), normed=1, bins=50);
273        plt.xlabel('log10(err), err = | F(q) single - F(q) double| / | F(q) double |');
274        plt.ylabel('P(err)')
275        plt.title('Comparison of single and double precision models for %s'%name)
276
277    plt.show()
278
279# ===========================================================================
280#
281USAGE="""
282usage: compare.py model [Nopencl] [Nsasview] [options...] [key=val]
283
284Compare the speed and value for a model between the SasView original and the
285OpenCL rewrite.
286
287model is the name of the model to compare (see below).
288Nopencl is the number of times to run the OpenCL model (default=5)
289Nsasview is the number of times to run the Sasview model (default=1)
290
291Options (* for default):
292
293    -plot*/-noplot plots or suppress the plot of the model
294    -single*/-double uses double precision for comparison
295    -lowq*/-midq/-highq/-exq use q values up to 0.05, 0.2, 1.0, 10.0
296    -Nq=128 sets the number of Q points in the data set
297    -1d/-2d* computes 1d or 2d data
298    -preset*/-random[=seed] preset or random parameters
299    -mono/-poly* force monodisperse/polydisperse
300    -ctypes/-sasview* whether cpu is tested using sasview or ctypes
301    -cutoff=1e-5* cutoff value for including a point in polydispersity
302    -pars/-nopars* prints the parameter set or not
303    -abs/-rel* plot relative or absolute error
304    -linear/-log/-q4 intensity scaling
305    -hist/-nohist* plot histogram of relative error
306    -res=0 sets the resolution width dQ/Q if calculating with resolution
307    -accuracy=Low resolution accuracy Low, Mid, High, Xhigh
308
309Key=value pairs allow you to set specific values to any of the model
310parameters.
311
312Available models:
313
314    %s
315"""
316
317NAME_OPTIONS = set([
318    'plot','noplot',
319    'single','double',
320    'lowq','midq','highq','exq',
321    '2d','1d',
322    'preset','random',
323    'poly','mono',
324    'sasview','ctypes',
325    'nopars','pars',
326    'rel','abs',
327    'linear', 'log', 'q4',
328    'hist','nohist',
329    ])
330VALUE_OPTIONS = [
331    # Note: random is both a name option and a value option
332    'cutoff', 'random', 'Nq', 'res', 'accuracy',
333    ]
334
335def get_demo_pars(name):
336    import sasmodels.models
337    __import__('sasmodels.models.'+name)
338    model = getattr(sasmodels.models, name)
339    pars = getattr(model, 'demo', None)
340    if pars is None: pars = dict((p[0],p[2]) for p in model.parameters)
341    return pars
342
343def main():
344    opts = [arg for arg in sys.argv[1:] if arg.startswith('-')]
345    args = [arg for arg in sys.argv[1:] if not arg.startswith('-')]
346    models = "\n    ".join("%-15s"%v for v in MODELS)
347    if len(args) == 0:
348        print(USAGE%models)
349        sys.exit(1)
350    if args[0] not in MODELS:
351        print "Model %r not available. Use one of:\n    %s"%(args[0],models)
352        sys.exit(1)
353
354    invalid = [o[1:] for o in opts
355               if o[1:] not in NAME_OPTIONS
356                  and not any(o.startswith('-%s='%t) for t in VALUE_OPTIONS)]
357    if invalid:
358        print "Invalid options: %s"%(", ".join(invalid))
359        sys.exit(1)
360
361    # Get demo parameters from model definition, or use default parameters
362    # if model does not define demo parameters
363    name = args[0]
364    pars = get_demo_pars(name)
365
366    Nopencl = int(args[1]) if len(args) > 1 else 5
367    Nsasview = int(args[2]) if len(args) > 2 else 1
368
369    # Fill in default polydispersity parameters
370    pds = set(p.split('_pd')[0] for p in pars if p.endswith('_pd'))
371    for p in pds:
372        if p+"_pd_nsigma" not in pars: pars[p+"_pd_nsigma"] = 3
373        if p+"_pd_type" not in pars: pars[p+"_pd_type"] = "gaussian"
374
375    # Fill in parameters given on the command line
376    set_pars = {}
377    for arg in args[3:]:
378        k,v = arg.split('=')
379        if k not in pars:
380            # extract base name without distribution
381            s = set(p.split('_pd')[0] for p in pars)
382            print "%r invalid; parameters are: %s"%(k,", ".join(sorted(s)))
383            sys.exit(1)
384        set_pars[k] = float(v) if not v.endswith('type') else v
385
386    compare(name, pars, Nsasview, Nopencl, opts, set_pars)
387
388if __name__ == "__main__":
389    main()
Note: See TracBrowser for help on using the repository browser.