Changeset caeb06d in sasmodels
- Timestamp:
- Jan 21, 2016 5:50:44 PM (9 years ago)
- Branches:
- master, core_shell_microgels, costrafo411, magnetic_model, release_v0.94, release_v0.95, ticket-1257-vesicle-product, ticket_1156, ticket_1265_superball, ticket_822_more_unit_tests
- Children:
- 9cfcac8
- Parents:
- eb46451
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
sasmodels/compare.py
reb46451 rcaeb06d 1 1 #!/usr/bin/env python 2 2 # -*- coding: utf-8 -*- 3 """ 4 Program to compare models using different compute engines. 5 6 This program lets you compare results between OpenCL and DLL versions 7 of the code and between precision (half, fast, single, double, quad), 8 where fast precision is single precision using native functions for 9 trig, etc., and may not be completely IEEE 754 compliant. This lets 10 make sure that the model calculations are stable, or if you need to 11 tag the model as double precision only. 12 13 Run using ./compare.sh (Linux, Mac) or compare.bat (Windows) in the 14 sasmodels root to see the command line options. 15 16 Note that there is no way within sasmodels to select between an 17 OpenCL CPU device and a GPU device, but you can do so by setting the 18 PYOPENCL_CTX environment variable ahead of time. Start a python 19 interpreter and enter:: 20 21 import pyopencl as cl 22 cl.create_some_context() 23 24 This will prompt you to select from the available OpenCL devices 25 and tell you which string to use for the PYOPENCL_CTX variable. 26 On Windows you will need to remove the quotes. 27 """ 28 29 from __future__ import print_function 30 31 USAGE = """ 32 usage: compare.py model N1 N2 [options...] [key=val] 33 34 Compare the speed and value for a model between the SasView original and the 35 sasmodels rewrite. 36 37 model is the name of the model to compare (see below). 38 N1 is the number of times to run sasmodels (default=1). 39 N2 is the number times to run sasview (default=1). 40 41 Options (* for default): 42 43 -plot*/-noplot plots or suppress the plot of the model 44 -lowq*/-midq/-highq/-exq use q values up to 0.05, 0.2, 1.0, 10.0 45 -nq=128 sets the number of Q points in the data set 46 -1d*/-2d computes 1d or 2d data 47 -preset*/-random[=seed] preset or random parameters 48 -mono/-poly* force monodisperse/polydisperse 49 -cutoff=1e-5* cutoff value for including a point in polydispersity 50 -pars/-nopars* prints the parameter set or not 51 -abs/-rel* plot relative or absolute error 52 -linear/-log*/-q4 intensity scaling 53 -hist/-nohist* plot histogram of relative error 54 -res=0 sets the resolution width dQ/Q if calculating with resolution 55 -accuracy=Low accuracy of the resolution calculation Low, Mid, High, Xhigh 56 -edit starts the parameter explorer 57 58 Any two calculation engines can be selected for comparison: 59 60 -single/-double/-half/-fast sets an OpenCL calculation engine 61 -single!/-double!/-quad! sets an OpenMP calculation engine 62 -sasview sets the sasview calculation engine 63 64 The default is -single -sasview. Note that the interpretation of quad 65 precision depends on architecture, and may vary from 64-bit to 128-bit, 66 with 80-bit floats being common (1e-19 precision). 67 68 Key=value pairs allow you to set specific values for the model parameters. 69 """ 70 71 # Update docs with command line usage string. This is separate from the usual 72 # doc string so that we can display it at run time if there is an error. 73 # lin 74 __doc__ = __doc__ + """ 75 Program description 76 ------------------- 77 78 """ + USAGE 79 80 3 81 4 82 import sys … … 25 103 # List of available models 26 104 MODELS = [basename(f)[:-3] 27 for f in sorted(glob.glob(joinpath(ROOT, "models","[a-zA-Z]*.py")))]105 for f in sorted(glob.glob(joinpath(ROOT, "models", "[a-zA-Z]*.py")))] 28 106 29 107 # CRUFT python 2.6 … … 76 154 elif p.endswith('_pd_type'): 77 155 return v 78 elif any(s in p for s in ('theta', 'phi','psi')):156 elif any(s in p for s in ('theta', 'phi', 'psi')): 79 157 # orientation in [-180,180], orientation pd in [0,45] 80 158 if p.endswith('_pd'): 81 return [0, 45]159 return [0, 45] 82 160 else: 83 161 return [-180, 180] … … 97 175 return [2*v, -2*v] 98 176 else: 99 return [0, (2*v if v >0 else 1)]177 return [0, (2*v if v > 0 else 1)] 100 178 101 179 def _randomize_one(p, v): 102 180 """ 103 Randomiz ingparameter.104 """ 105 if any(p.endswith(s) for s in ('_pd_n', '_pd_nsigma','_pd_type')):181 Randomize a single parameter. 182 """ 183 if any(p.endswith(s) for s in ('_pd_n', '_pd_nsigma', '_pd_type')): 106 184 return v 107 185 else: … … 109 187 110 188 def randomize_pars(pars, seed=None): 189 """ 190 Generate random values for all of the parameters. 191 192 Valid ranges for the random number generator are guessed from the name of 193 the parameter; this will not account for constraints such as cap radius 194 greater than cylinder radius in the capped_cylinder model, so 195 :func:`constrain_pars` needs to be called afterward.. 196 """ 111 197 np.random.seed(seed) 112 198 # Note: the sort guarantees order `of calls to random number generator 113 pars = dict((p, _randomize_one(p,v))114 for p, v in sorted(pars.items()))199 pars = dict((p, _randomize_one(p, v)) 200 for p, v in sorted(pars.items())) 115 201 return pars 116 202 … … 118 204 """ 119 205 Restrict parameters to valid values. 206 207 This includes model specific code for models such as capped_cylinder 208 which need to support within model constraints (cap radius more than 209 cylinder radius in this case). 120 210 """ 121 211 name = model_definition.name 122 212 if name == 'capped_cylinder' and pars['cap_radius'] < pars['radius']: 123 pars['radius'], pars['cap_radius'] = pars['cap_radius'],pars['radius']213 pars['radius'], pars['cap_radius'] = pars['cap_radius'], pars['radius'] 124 214 if name == 'barbell' and pars['bell_radius'] < pars['radius']: 125 pars['radius'], pars['bell_radius'] = pars['bell_radius'],pars['radius']215 pars['radius'], pars['bell_radius'] = pars['bell_radius'], pars['radius'] 126 216 127 217 # Limit guinier to an Rg such that Iq > 1e-30 (single precision cutoff) … … 130 220 q_max = 1.0 # high q maximum 131 221 rg_max = np.sqrt(90*np.log(10) + 3*np.log(pars['scale']))/q_max 132 pars['rg'] = min(pars['rg'], rg_max)222 pars['rg'] = min(pars['rg'], rg_max) 133 223 134 224 if name == 'rpa': … … 144 234 145 235 def parlist(pars): 146 return "\n".join("%s: %s"%(p,v) for p,v in sorted(pars.items())) 236 """ 237 Format the parameter list for printing. 238 """ 239 return "\n".join("%s: %s"%(p, v) for p, v in sorted(pars.items())) 147 240 148 241 def suppress_pd(pars): … … 159 252 160 253 def eval_sasview(model_definition, data): 254 """ 255 Return a model calculator using the SasView fitting engine. 256 """ 161 257 # importing sas here so that the error message will be that sas failed to 162 258 # import rather than the more obscure smear_selection not imported error … … 166 262 # convert model parameters from sasmodel form to sasview form 167 263 #print("old",sorted(pars.items())) 168 modelname, pars = revert_model(model_definition, {})169 #print("new",sorted( pars.items()))264 modelname, _pars = revert_model(model_definition, {}) 265 #print("new",sorted(_pars.items())) 170 266 sas = __import__('sas.models.'+modelname) 171 ModelClass = getattr(getattr(sas.models, modelname,None),modelname,None)267 ModelClass = getattr(getattr(sas.models, modelname, None), modelname, None) 172 268 if ModelClass is None: 173 269 raise ValueError("could not find model %r in sas.models"%modelname) … … 192 288 193 289 def calculator(**pars): 290 """ 291 Sasview calculator for model. 292 """ 194 293 # paying for parameter conversion each time to keep life simple, if not fast 195 294 _, pars = revert_model(model_definition, pars) 196 for k, v in pars.items():295 for k, v in pars.items(): 197 296 parts = k.split('.') # polydispersity components 198 297 if len(parts) == 2: … … 217 316 } 218 317 def eval_opencl(model_definition, data, dtype='single', cutoff=0.): 318 """ 319 Return a model calculator using the OpenCL calculation engine. 320 """ 219 321 try: 220 322 model = core.load_model(model_definition, dtype=dtype, platform="ocl") … … 229 331 230 332 def eval_ctypes(model_definition, data, dtype='double', cutoff=0.): 231 if dtype =='quad':333 if dtype == 'quad': 232 334 dtype = 'longdouble' 233 335 model = core.load_model(model_definition, dtype=dtype, platform="dll") … … 237 339 238 340 def time_calculation(calculator, pars, Nevals=1): 341 """ 342 Compute the average calculation time over N evaluations. 343 344 An additional call is generated without polydispersity in order to 345 initialize the calculation engine, and make the average more stable. 346 """ 239 347 # initialize the code so time is more accurate 240 348 value = calculator(**suppress_pd(pars)) … … 246 354 247 355 def make_data(opts): 356 """ 357 Generate an empty dataset, used with the model to set Q points 358 and resolution. 359 360 *opts* contains the options, with 'qmax', 'nq', 'res', 361 'accuracy', 'is2d' and 'view' parsed from the command line. 362 """ 248 363 qmax, nq, res = opts['qmax'], opts['nq'], opts['res'] 249 364 if opts['is2d']: … … 263 378 264 379 def make_engine(model_definition, data, dtype, cutoff): 380 """ 381 Generate the appropriate calculation engine for the given datatype. 382 383 Datatypes with '!' appended are evaluated using external C DLLs rather 384 than OpenCL. 385 """ 265 386 if dtype == 'sasview': 266 387 return eval_sasview(model_definition, data) … … 273 394 274 395 def compare(opts, limits=None): 396 """ 397 Preform a comparison using options from the command line. 398 399 *limits* are the limits on the values to use, either to set the y-axis 400 for 1D or to set the colormap scale for 2D. If None, then they are 401 inferred from the data and returned. When exploring using Bumps, 402 the limits are set when the model is initially called, and maintained 403 as the values are adjusted, making it easier to see the effects of the 404 parameters. 405 """ 275 406 Nbase, Ncomp = opts['N1'], opts['N2'] 276 407 pars = opts['pars'] … … 304 435 resid = (base_value - comp_value) 305 436 relerr = resid/comp_value 306 _print_stats("|%s - %s|"%(base.engine,comp.engine)+(" "*(3+len(comp.engine))), resid) 307 _print_stats("|(%s - %s) / %s|"%(base.engine,comp.engine,comp.engine), relerr) 437 _print_stats("|%s-%s|"%(base.engine, comp.engine) + (" "*(3+len(comp.engine))), 438 resid) 439 _print_stats("|(%s-%s)/%s|"%(base.engine, comp.engine, comp.engine), 440 relerr) 308 441 309 442 # Plot if requested … … 329 462 if Nbase > 0: plt.subplot(132) 330 463 plot_theory(data, comp_value, view=view, plot_data=False, limits=limits) 331 plt.title("%s t=%.1f ms"%(comp.engine, comp_time))464 plt.title("%s t=%.1f ms"%(comp.engine, comp_time)) 332 465 #cbar_title = "log I" 333 466 if Ncomp > 0 and Nbase > 0: 334 467 plt.subplot(133) 335 468 if '-abs' in opts: 336 err, errstr,errview = resid, "abs err", "linear"469 err, errstr, errview = resid, "abs err", "linear" 337 470 else: 338 err, errstr,errview = abs(relerr), "rel err", "log"471 err, errstr, errview = abs(relerr), "rel err", "log" 339 472 #err,errstr = base/comp,"ratio" 340 473 plot_theory(data, None, resid=err, view=errview, plot_data=False) … … 348 481 plt.figure() 349 482 v = relerr 350 v[v==0] = 0.5*np.min(np.abs(v[v!=0])) 351 plt.hist(np.log10(np.abs(v)), normed=1, bins=50); 352 plt.xlabel('log10(err), err = |(%s - %s) / %s|'%(base.engine, comp.engine, comp.engine)); 483 v[v == 0] = 0.5*np.min(np.abs(v[v != 0])) 484 plt.hist(np.log10(np.abs(v)), normed=1, bins=50) 485 plt.xlabel('log10(err), err = |(%s - %s) / %s|' 486 % (base.engine, comp.engine, comp.engine)) 353 487 plt.ylabel('P(err)') 354 488 plt.title('Distribution of relative error between calculation engines') … … 370 504 "zero-offset:%+.3e"%np.mean(err), 371 505 ] 372 print(label+" " .join(data))506 print(label+" "+" ".join(data)) 373 507 374 508 … … 376 510 # =========================================================================== 377 511 # 378 USAGE="""379 usage: compare.py model N1 N2 [options...] [key=val]380 381 Compare the speed and value for a model between the SasView original and the382 sasmodels rewrite.383 384 model is the name of the model to compare (see below).385 N1 is the number of times to run sasmodels (default=1).386 N2 is the number times to run sasview (default=1).387 388 Options (* for default):389 390 -plot*/-noplot plots or suppress the plot of the model391 -lowq*/-midq/-highq/-exq use q values up to 0.05, 0.2, 1.0, 10.0392 -nq=128 sets the number of Q points in the data set393 -1d*/-2d computes 1d or 2d data394 -preset*/-random[=seed] preset or random parameters395 -mono/-poly* force monodisperse/polydisperse396 -cutoff=1e-5* cutoff value for including a point in polydispersity397 -pars/-nopars* prints the parameter set or not398 -abs/-rel* plot relative or absolute error399 -linear/-log*/-q4 intensity scaling400 -hist/-nohist* plot histogram of relative error401 -res=0 sets the resolution width dQ/Q if calculating with resolution402 -accuracy=Low accuracy of the resolution calculation Low, Mid, High, Xhigh403 -edit starts the parameter explorer404 405 Any two calculation engines can be selected for comparison:406 407 -single/-double/-half/-fast sets an OpenCL calculation engine408 -single!/-double!/-quad! sets an OpenMP calculation engine409 -sasview sets the sasview calculation engine410 411 The default is -single -sasview. Note that the interpretation of quad412 precision depends on architecture, and may vary from 64-bit to 128-bit,413 with 80-bit floats being common (1e-19 precision).414 415 Key=value pairs allow you to set specific values for the model parameters.416 417 Available models:418 """419 420 421 512 NAME_OPTIONS = set([ 422 513 'plot', 'noplot', … … 439 530 440 531 def columnize(L, indent="", width=79): 532 """ 533 Format a list of strings into columns for printing. 534 """ 441 535 column_width = max(len(w) for w in L) + 1 442 536 num_columns = (width - len(indent)) // column_width … … 451 545 452 546 def get_demo_pars(model_definition): 547 """ 548 Extract demo parameters from the model definition. 549 """ 453 550 info = generate.make_info(model_definition) 454 551 # Get the default values for the parameters … … 468 565 469 566 def parse_opts(): 470 flags = [arg for arg in sys.argv[1:] if arg.startswith('-')] 471 values = [arg for arg in sys.argv[1:] if not arg.startswith('-') and '=' in arg] 472 args = [arg for arg in sys.argv[1:] if not arg.startswith('-') and '=' not in arg] 567 """ 568 Parse command line options. 569 """ 570 flags = [arg for arg in sys.argv[1:] 571 if arg.startswith('-')] 572 values = [arg for arg in sys.argv[1:] 573 if not arg.startswith('-') and '=' in arg] 574 args = [arg for arg in sys.argv[1:] 575 if not arg.startswith('-') and '=' not in arg] 473 576 models = "\n ".join("%-15s"%v for v in MODELS) 474 577 if len(args) == 0: 475 578 print(USAGE) 579 print("\nAvailable models:") 476 580 print(columnize(MODELS, indent=" ")) 477 581 sys.exit(1) 478 582 if args[0] not in MODELS: 479 print("Model %r not available. Use one of:\n %s"%(args[0], models))583 print("Model %r not available. Use one of:\n %s"%(args[0], models)) 480 584 sys.exit(1) 481 585 if len(args) > 3: … … 484 588 invalid = [o[1:] for o in flags 485 589 if o[1:] not in NAME_OPTIONS 486 and not any(o.startswith('-%s='%t) for t in VALUE_OPTIONS)]590 and not any(o.startswith('-%s='%t) for t in VALUE_OPTIONS)] 487 591 if invalid: 488 592 print("Invalid options: %s"%(", ".join(invalid)))
Note: See TracChangeset
for help on using the changeset viewer.