source: sasmodels/sasmodels/compare.py @ e21cc31

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

note that quad precision is probably only 80-bit, not 128-bit

  • Property mode set to 100755
File size: 22.0 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
8import datetime
9import traceback
10
11import numpy as np
12
13ROOT = dirname(__file__)
14sys.path.insert(0, ROOT)  # Make sure sasmodels is first on the path
15
16
17from . import core
18from . import kerneldll
19from . import generate
20from .data import plot_theory, empty_data1D, empty_data2D
21from .direct_model import DirectModel
22from .convert import revert_model, constrain_new_to_old
23kerneldll.ALLOW_SINGLE_PRECISION_DLLS = True
24
25# List of available models
26MODELS = [basename(f)[:-3]
27          for f in sorted(glob.glob(joinpath(ROOT,"models","[a-zA-Z]*.py")))]
28
29# CRUFT python 2.6
30if not hasattr(datetime.timedelta, 'total_seconds'):
31    def delay(dt):
32        """Return number date-time delta as number seconds"""
33        return dt.days * 86400 + dt.seconds + 1e-6 * dt.microseconds
34else:
35    def delay(dt):
36        """Return number date-time delta as number seconds"""
37        return dt.total_seconds()
38
39
40def tic():
41    """
42    Timer function.
43
44    Use "toc=tic()" to start the clock and "toc()" to measure
45    a time interval.
46    """
47    then = datetime.datetime.now()
48    return lambda: delay(datetime.datetime.now() - then)
49
50
51def set_beam_stop(data, radius, outer=None):
52    """
53    Add a beam stop of the given *radius*.  If *outer*, make an annulus.
54
55    Note: this function does not use the sasview package
56    """
57    if hasattr(data, 'qx_data'):
58        q = np.sqrt(data.qx_data**2 + data.qy_data**2)
59        data.mask = (q < radius)
60        if outer is not None:
61            data.mask |= (q >= outer)
62    else:
63        data.mask = (data.x < radius)
64        if outer is not None:
65            data.mask |= (data.x >= outer)
66
67
68def parameter_range(p, v):
69    """
70    Choose a parameter range based on parameter name and initial value.
71    """
72    if p.endswith('_pd_n'):
73        return [0, 100]
74    elif p.endswith('_pd_nsigma'):
75        return [0, 5]
76    elif p.endswith('_pd_type'):
77        return v
78    elif any(s in p for s in ('theta','phi','psi')):
79        # orientation in [-180,180], orientation pd in [0,45]
80        if p.endswith('_pd'):
81            return [0,45]
82        else:
83            return [-180, 180]
84    elif 'sld' in p:
85        return [-0.5, 10]
86    elif p.endswith('_pd'):
87        return [0, 1]
88    elif p in ['background', 'scale']:
89        return [0, 1e3]
90    else:
91        return [0, (2*v if v>0 else 1)]
92
93def _randomize_one(p, v):
94    """
95    Randomizing parameter.
96    """
97    if any(p.endswith(s) for s in ('_pd_n','_pd_nsigma','_pd_type')):
98        return v
99    else:
100        return np.random.uniform(*parameter_range(p, v))
101
102def randomize_pars(pars, seed=None):
103    np.random.seed(seed)
104    # Note: the sort guarantees order `of calls to random number generator
105    pars = dict((p,_randomize_one(p,v))
106                for p,v in sorted(pars.items()))
107    return pars
108
109def constrain_pars(model_definition, pars):
110    """
111    Restrict parameters to valid values.
112    """
113    name = model_definition.name
114    if name == 'capped_cylinder' and pars['cap_radius'] < pars['radius']:
115        pars['radius'],pars['cap_radius'] = pars['cap_radius'],pars['radius']
116    if name == 'barbell' and pars['bell_radius'] < pars['radius']:
117        pars['radius'],pars['bell_radius'] = pars['bell_radius'],pars['radius']
118
119    # Limit guinier to an Rg such that Iq > 1e-30 (single precision cutoff)
120    if name == 'guinier':
121        #q_max = 0.2  # mid q maximum
122        q_max = 1.0  # high q maximum
123        rg_max = np.sqrt(90*np.log(10) + 3*np.log(pars['scale']))/q_max
124        pars['rg'] = min(pars['rg'],rg_max)
125
126def parlist(pars):
127    return "\n".join("%s: %s"%(p,v) for p,v in sorted(pars.items()))
128
129def suppress_pd(pars):
130    """
131    Suppress theta_pd for now until the normalization is resolved.
132
133    May also suppress complete polydispersity of the model to test
134    models more quickly.
135    """
136    pars = pars.copy()
137    for p in pars:
138        if p.endswith("_pd"): pars[p] = 0
139    return pars
140
141def eval_sasview(model_definition, data):
142    # importing sas here so that the error message will be that sas failed to
143    # import rather than the more obscure smear_selection not imported error
144    import sas
145    from sas.models.qsmearing import smear_selection
146
147    # convert model parameters from sasmodel form to sasview form
148    #print("old",sorted(pars.items()))
149    modelname, pars = revert_model(model_definition, {})
150    #print("new",sorted(pars.items()))
151    sas = __import__('sas.models.'+modelname)
152    ModelClass = getattr(getattr(sas.models,modelname,None),modelname,None)
153    if ModelClass is None:
154        raise ValueError("could not find model %r in sas.models"%modelname)
155    model = ModelClass()
156    smearer = smear_selection(data, model=model)
157
158    if hasattr(data, 'qx_data'):
159        q = np.sqrt(data.qx_data**2 + data.qy_data**2)
160        index = ((~data.mask) & (~np.isnan(data.data))
161                 & (q >= data.qmin) & (q <= data.qmax))
162        if smearer is not None:
163            smearer.model = model  # because smear_selection has a bug
164            smearer.accuracy = data.accuracy
165            smearer.set_index(index)
166            theory = lambda: smearer.get_value()
167        else:
168            theory = lambda: model.evalDistribution([data.qx_data[index], data.qy_data[index]])
169    elif smearer is not None:
170        theory = lambda: smearer(model.evalDistribution(data.x))
171    else:
172        theory = lambda: model.evalDistribution(data.x)
173
174    def calculator(**pars):
175        # paying for parameter conversion each time to keep life simple, if not fast
176        _, pars = revert_model(model_definition, pars)
177        for k,v in pars.items():
178            parts = k.split('.')  # polydispersity components
179            if len(parts) == 2:
180                model.dispersion[parts[0]][parts[1]] = v
181            else:
182                model.setParam(k, v)
183        return theory()
184
185    calculator.engine = "sasview"
186    return calculator
187
188DTYPE_MAP = {
189    'half': '16',
190    'fast': 'fast',
191    'single': '32',
192    'double': '64',
193    'quad': '128',
194    'f16': '16',
195    'f32': '32',
196    'f64': '64',
197    'longdouble': '128',
198}
199def eval_opencl(model_definition, data, dtype='single', cutoff=0.):
200    try:
201        model = core.load_model(model_definition, dtype=dtype, platform="ocl")
202    except Exception as exc:
203        print(exc)
204        print("... trying again with single precision")
205        dtype = 'single'
206        model = core.load_model(model_definition, dtype=dtype, platform="ocl")
207    calculator = DirectModel(data, model, cutoff=cutoff)
208    calculator.engine = "OCL%s"%DTYPE_MAP[dtype]
209    return calculator
210
211def eval_ctypes(model_definition, data, dtype='double', cutoff=0.):
212    if dtype=='quad':
213        dtype = 'longdouble'
214    model = core.load_model(model_definition, dtype=dtype, platform="dll")
215    calculator = DirectModel(data, model, cutoff=cutoff)
216    calculator.engine = "OMP%s"%DTYPE_MAP[dtype]
217    return calculator
218
219def time_calculation(calculator, pars, Nevals=1):
220    # initialize the code so time is more accurate
221    value = calculator(**suppress_pd(pars))
222    toc = tic()
223    for _ in range(max(Nevals, 1)):  # make sure there is at least one eval
224        value = calculator(**pars)
225    average_time = toc()*1000./Nevals
226    return value, average_time
227
228def make_data(opts):
229    qmax, nq, res = opts['qmax'], opts['nq'], opts['res']
230    if opts['is2d']:
231        data = empty_data2D(np.linspace(-qmax, qmax, nq), resolution=res)
232        data.accuracy = opts['accuracy']
233        set_beam_stop(data, 0.004)
234        index = ~data.mask
235    else:
236        if opts['view'] == 'log':
237            qmax = math.log10(qmax)
238            q = np.logspace(qmax-3, qmax, nq)
239        else:
240            q = np.linspace(0.001*qmax, qmax, nq)
241        data = empty_data1D(q, resolution=res)
242        index = slice(None, None)
243    return data, index
244
245def make_engine(model_definition, data, dtype, cutoff):
246    if dtype == 'sasview':
247        return eval_sasview(model_definition, data)
248    elif dtype.endswith('!'):
249        return eval_ctypes(model_definition, data, dtype=dtype[:-1],
250                           cutoff=cutoff)
251    else:
252        return eval_opencl(model_definition, data, dtype=dtype,
253                           cutoff=cutoff)
254
255def compare(opts):
256    Nbase, Ncomp = opts['N1'], opts['N2']
257    pars = opts['pars']
258    data = opts['data']
259
260    # Base calculation
261    if Nbase > 0:
262        base = opts['engines'][0]
263        try:
264            base_value, base_time = time_calculation(base, pars, Nbase)
265            print("%s t=%.1f ms, intensity=%.0f"%(base.engine, base_time, sum(base_value)))
266        except ImportError:
267            traceback.print_exc()
268            Nbase = 0
269
270    # Comparison calculation
271    if Ncomp > 0:
272        comp = opts['engines'][1]
273        try:
274            comp_value, comp_time = time_calculation(comp, pars, Ncomp)
275            print("%s t=%.1f ms, intensity=%.0f"%(comp.engine, comp_time, sum(comp_value)))
276        except ImportError:
277            traceback.print_exc()
278            Ncomp = 0
279
280    # Compare, but only if computing both forms
281    if Nbase > 0 and Ncomp > 0:
282        #print("speedup %.2g"%(comp_time/base_time))
283        #print("max |base/comp|", max(abs(base_value/comp_value)), "%.15g"%max(abs(base_value)), "%.15g"%max(abs(comp_value)))
284        #comp *= max(base_value/comp_value)
285        resid = (base_value - comp_value)
286        relerr = resid/comp_value
287        _print_stats("|%s - %s|"%(base.engine,comp.engine)+(" "*(3+len(comp.engine))), resid)
288        _print_stats("|(%s - %s) / %s|"%(base.engine,comp.engine,comp.engine), relerr)
289
290    # Plot if requested
291    if not opts['plot'] and not opts['explore']: return
292    view = opts['view']
293    import matplotlib.pyplot as plt
294    if Nbase > 0:
295        if Ncomp > 0: plt.subplot(131)
296        plot_theory(data, base_value, view=view, plot_data=False)
297        plt.title("%s t=%.1f ms"%(base.engine, base_time))
298        #cbar_title = "log I"
299    if Ncomp > 0:
300        if Nbase > 0: plt.subplot(132)
301        plot_theory(data, comp_value, view=view, plot_data=False)
302        plt.title("%s t=%.1f ms"%(comp.engine,comp_time))
303        #cbar_title = "log I"
304    if Ncomp > 0 and Nbase > 0:
305        plt.subplot(133)
306        if '-abs' in opts:
307            err,errstr,errview = resid, "abs err", "linear"
308        else:
309            err,errstr,errview = abs(relerr), "rel err", "log"
310        #err,errstr = base/comp,"ratio"
311        plot_theory(data, None, resid=err, view=errview, plot_data=False)
312        plt.title("max %s = %.3g"%(errstr, max(abs(err))))
313        #cbar_title = errstr if errview=="linear" else "log "+errstr
314    #if is2D:
315    #    h = plt.colorbar()
316    #    h.ax.set_title(cbar_title)
317
318    if Ncomp > 0 and Nbase > 0 and '-hist' in opts:
319        plt.figure()
320        v = relerr
321        v[v==0] = 0.5*np.min(np.abs(v[v!=0]))
322        plt.hist(np.log10(np.abs(v)), normed=1, bins=50);
323        plt.xlabel('log10(err), err = |(%s - %s) / %s|'%(base.engine, comp.engine, comp.engine));
324        plt.ylabel('P(err)')
325        plt.title('Distribution of relative error between calculation engines')
326
327    if not opts['explore']:
328        plt.show()
329
330def _print_stats(label, err):
331    sorted_err = np.sort(abs(err))
332    p50 = int((len(err)-1)*0.50)
333    p98 = int((len(err)-1)*0.98)
334    data = [
335        "max:%.3e"%sorted_err[-1],
336        "median:%.3e"%sorted_err[p50],
337        "98%%:%.3e"%sorted_err[p98],
338        "rms:%.3e"%np.sqrt(np.mean(err**2)),
339        "zero-offset:%+.3e"%np.mean(err),
340        ]
341    print(label+"  ".join(data))
342
343
344
345# ===========================================================================
346#
347USAGE="""
348usage: compare.py model N1 N2 [options...] [key=val]
349
350Compare the speed and value for a model between the SasView original and the
351sasmodels rewrite.
352
353model is the name of the model to compare (see below).
354N1 is the number of times to run sasmodels (default=1).
355N2 is the number times to run sasview (default=1).
356
357Options (* for default):
358
359    -plot*/-noplot plots or suppress the plot of the model
360    -lowq*/-midq/-highq/-exq use q values up to 0.05, 0.2, 1.0, 10.0
361    -nq=128 sets the number of Q points in the data set
362    -1d*/-2d computes 1d or 2d data
363    -preset*/-random[=seed] preset or random parameters
364    -mono/-poly* force monodisperse/polydisperse
365    -cutoff=1e-5* cutoff value for including a point in polydispersity
366    -pars/-nopars* prints the parameter set or not
367    -abs/-rel* plot relative or absolute error
368    -linear/-log*/-q4 intensity scaling
369    -hist/-nohist* plot histogram of relative error
370    -res=0 sets the resolution width dQ/Q if calculating with resolution
371    -accuracy=Low accuracy of the resolution calculation Low, Mid, High, Xhigh
372    -edit starts the parameter explorer
373
374Any two calculation engines can be selected for comparison:
375
376    -single/-double/-half/-fast sets an OpenCL calculation engine
377    -single!/-double!/-quad! sets an OpenMP calculation engine
378    -sasview sets the sasview calculation engine
379
380The default is -single -sasview.  Note that the interpretation of quad
381precision depends on architecture, and may vary from 64-bit to 128-bit,
382with 80-bit floats being common (1e-19 precision).
383
384Key=value pairs allow you to set specific values for the model parameters.
385
386Available models:
387"""
388
389
390NAME_OPTIONS = set([
391    'plot', 'noplot',
392    'half', 'fast', 'single', 'double',
393    'single!', 'double!', 'quad!', 'sasview',
394    'lowq', 'midq', 'highq', 'exq',
395    '2d', '1d',
396    'preset', 'random',
397    'poly', 'mono',
398    'nopars', 'pars',
399    'rel', 'abs',
400    'linear', 'log', 'q4',
401    'hist', 'nohist',
402    'edit',
403    ])
404VALUE_OPTIONS = [
405    # Note: random is both a name option and a value option
406    'cutoff', 'random', 'nq', 'res', 'accuracy',
407    ]
408
409def columnize(L, indent="", width=79):
410    column_width = max(len(w) for w in L) + 1
411    num_columns = (width - len(indent)) // column_width
412    num_rows = len(L) // num_columns
413    L = L + [""] * (num_rows*num_columns - len(L))
414    columns = [L[k*num_rows:(k+1)*num_rows] for k in range(num_columns)]
415    lines = [" ".join("%-*s"%(column_width, entry) for entry in row)
416             for row in zip(*columns)]
417    output = indent + ("\n"+indent).join(lines)
418    return output
419
420
421def get_demo_pars(model_definition):
422    info = generate.make_info(model_definition)
423    # Get the default values for the parameters
424    pars = dict((p[0],p[2]) for p in info['parameters'])
425
426    # Fill in default values for the polydispersity parameters
427    for p in info['parameters']:
428        if p[4] in ('volume', 'orientation'):
429            pars[p[0]+'_pd'] = 0.0
430            pars[p[0]+'_pd_n'] = 0
431            pars[p[0]+'_pd_nsigma'] = 3.0
432            pars[p[0]+'_pd_type'] = "gaussian"
433
434    # Plug in values given in demo
435    pars.update(info['demo'])
436    return pars
437
438def parse_opts():
439    flags = [arg for arg in sys.argv[1:] if arg.startswith('-')]
440    values = [arg for arg in sys.argv[1:] if not arg.startswith('-') and '=' in arg]
441    args = [arg for arg in sys.argv[1:] if not arg.startswith('-') and '=' not in arg]
442    models = "\n    ".join("%-15s"%v for v in MODELS)
443    if len(args) == 0:
444        print(USAGE)
445        print(columnize(MODELS, indent="  "))
446        sys.exit(1)
447    if args[0] not in MODELS:
448        print("Model %r not available. Use one of:\n    %s"%(args[0],models))
449        sys.exit(1)
450    if len(args) > 3:
451        print("expected parameters: model Nopencl Nsasview")
452
453    invalid = [o[1:] for o in flags
454               if o[1:] not in NAME_OPTIONS
455                  and not any(o.startswith('-%s='%t) for t in VALUE_OPTIONS)]
456    if invalid:
457        print("Invalid options: %s"%(", ".join(invalid)))
458        sys.exit(1)
459
460
461    # Interpret the flags
462    opts = {
463        'plot'      : True,
464        'view'      : 'log',
465        'is2d'      : False,
466        'qmax'      : 0.05,
467        'nq'        : 128,
468        'res'       : 0.0,
469        'accuracy'  : 'Low',
470        'cutoff'    : 1e-5,
471        'seed'      : -1,  # default to preset
472        'mono'      : False,
473        'show_pars' : False,
474        'show_hist' : False,
475        'rel_err'   : True,
476        'explore'   : False,
477    }
478    engines = []
479    for arg in flags:
480        if arg == '-noplot':    opts['plot'] = False
481        elif arg == '-plot':    opts['plot'] = True
482        elif arg == '-linear':  opts['view'] = 'linear'
483        elif arg == '-log':     opts['view'] = 'log'
484        elif arg == '-q4':      opts['view'] = 'q4'
485        elif arg == '-1d':      opts['is2d'] = False
486        elif arg == '-2d':      opts['is2d'] = True
487        elif arg == '-exq':     opts['qmax'] = 10.0
488        elif arg == '-highq':   opts['qmax'] = 1.0
489        elif arg == '-midq':    opts['qmax'] = 0.2
490        elif arg == '-loq':     opts['qmax'] = 0.05
491        elif arg.startswith('-nq='):       opts['nq'] = int(arg[4:])
492        elif arg.startswith('-res='):      opts['res'] = float(arg[5:])
493        elif arg.startswith('-accuracy='): opts['accuracy'] = arg[10:]
494        elif arg.startswith('-cutoff='):   opts['cutoff'] = float(arg[8:])
495        elif arg.startswith('-random='):   opts['seed'] = int(arg[8:])
496        elif arg == '-random':  opts['seed'] = np.random.randint(1e6)
497        elif arg == '-preset':  opts['seed'] = -1
498        elif arg == '-mono':    opts['mono'] = True
499        elif arg == '-poly':    opts['mono'] = False
500        elif arg == '-pars':    opts['show_pars'] = True
501        elif arg == '-nopars':  opts['show_pars'] = False
502        elif arg == '-hist':    opts['show_hist'] = True
503        elif arg == '-nohist':  opts['show_hist'] = False
504        elif arg == '-rel':     opts['rel_err'] = True
505        elif arg == '-abs':     opts['rel_err'] = False
506        elif arg == '-half':    engines.append(arg[1:])
507        elif arg == '-fast':    engines.append(arg[1:])
508        elif arg == '-single':  engines.append(arg[1:])
509        elif arg == '-double':  engines.append(arg[1:])
510        elif arg == '-single!': engines.append(arg[1:])
511        elif arg == '-double!': engines.append(arg[1:])
512        elif arg == '-quad!':   engines.append(arg[1:])
513        elif arg == '-sasview': engines.append(arg[1:])
514        elif arg == '-edit':    opts['explore'] = True
515
516    if len(engines) == 0:
517        engines.extend(['single','sasview'])
518    elif len(engines) == 1:
519        if engines[0][0] != 'sasview':
520            engines.append('sasview')
521        else:
522            engines.append('single')
523    elif len(engines) > 2:
524        del engines[2:]
525
526    name = args[0]
527    model_definition = core.load_model_definition(name)
528
529    N1 = int(args[1]) if len(args) > 1 else 1
530    N2 = int(args[2]) if len(args) > 2 else 1
531
532    # Get demo parameters from model definition, or use default parameters
533    # if model does not define demo parameters
534    pars = get_demo_pars(model_definition)
535
536    # Fill in parameters given on the command line
537    presets = {}
538    for arg in values:
539        k,v = arg.split('=',1)
540        if k not in pars:
541            # extract base name without polydispersity info
542            s = set(p.split('_pd')[0] for p in pars)
543            print("%r invalid; parameters are: %s"%(k,", ".join(sorted(s))))
544            sys.exit(1)
545        presets[k] = float(v) if not k.endswith('type') else v
546
547    # randomize parameters
548    #pars.update(set_pars)  # set value before random to control range
549    if opts['seed'] > -1:
550        pars = randomize_pars(pars, seed=opts['seed'])
551        print("Randomize using -random=%i"%opts['seed'])
552    pars.update(presets)  # set value after random to control value
553    constrain_pars(model_definition, pars)
554    constrain_new_to_old(model_definition, pars)
555    if opts['mono']:
556        pars = suppress_pd(pars)
557    if opts['show_pars']:
558        print("pars " + str(parlist(pars)))
559
560    # Create the computational engines
561    data, _index = make_data(opts)
562    if N1:
563        base = make_engine(model_definition, data, engines[0], opts['cutoff'])
564    else:
565        base = None
566    if N2:
567        comp = make_engine(model_definition, data, engines[1], opts['cutoff'])
568    else:
569        comp = None
570
571    # Remember it all
572    opts.update({
573        'name'      : name,
574        'def'       : model_definition,
575        'N1'        : N1,
576        'N2'        : N2,
577        'presets'   : presets,
578        'pars'      : pars,
579        'data'      : data,
580        'engines'   : [base, comp],
581    })
582
583    return opts
584
585def main():
586    opts = parse_opts()
587    if opts['explore']:
588        explore(opts)
589    else:
590        compare(opts)
591
592def explore(opts):
593    import wx
594    from bumps.names import FitProblem
595    from bumps.gui.app_frame import AppFrame
596
597    problem = FitProblem(Explore(opts))
598    isMac = "cocoa" in wx.version()
599    app = wx.App()
600    frame = AppFrame(parent=None, title="explore")
601    if not isMac: frame.Show()
602    frame.panel.set_model(model=problem)
603    frame.panel.Layout()
604    frame.panel.aui.Split(0, wx.TOP)
605    if isMac: frame.Show()
606    app.MainLoop()
607
608class Explore(object):
609    """
610    Return a bumps wrapper for a SAS model comparison.
611    """
612    def __init__(self, opts):
613        from bumps.cli import config_matplotlib
614        import bumps_model
615        config_matplotlib()
616        self.opts = opts
617        info = generate.make_info(opts['def'])
618        pars, pd_types = bumps_model.create_parameters(info, **opts['pars'])
619        if not opts['is2d']:
620            active = [base + ext
621                      for base in info['partype']['pd-1d']
622                      for ext in ['','_pd','_pd_n','_pd_nsigma']]
623            active.extend(info['partype']['fixed-1d'])
624            for k in active:
625                v = pars[k]
626                v.range(*parameter_range(k, v.value))
627        else:
628            for k, v in self.pars.items():
629                v.range(*parameter_range(k, v.value))
630
631        self.pars = pars
632        self.pd_types = pd_types
633
634    def numpoints(self):
635        """
636        Return the number of points
637        """
638        return len(self.pars) + 1  # so dof is 1
639
640    def parameters(self):
641        """
642        Return a dictionary of parameters
643        """
644        return self.pars
645
646    def nllf(self):
647        return 0.  # No nllf
648
649    def plot(self, view='log'):
650        """
651        Plot the data and residuals.
652        """
653        pars = dict((k, v.value) for k,v in self.pars.items())
654        pars.update(self.pd_types)
655        self.opts['pars'] = pars
656        compare(self.opts)
657
658
659if __name__ == "__main__":
660    main()
Note: See TracBrowser for help on using the repository browser.