Changeset ce27e21 in sasmodels for sasmodels/gen.py
- Timestamp:
- Aug 24, 2014 7:18:14 PM (10 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:
- 1780d59
- Parents:
- 14de349
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
sasmodels/gen.py
r14de349 rce27e21 9 9 F64 = np.dtype('float64') 10 10 F32 = np.dtype('float32') 11 12 # Scale and background, which are parameters common to every form factor 13 COMMON_PARAMETERS = [ 14 [ "scale", "", 1, [0, np.inf], "", "Source intensity" ], 15 [ "background", "1/cm", 0, [0, np.inf], "", "Source background" ], 16 ] 17 11 18 12 19 # Conversion from units defined in the parameter table for each model … … 29 36 30 37 PARTABLE_VALUE_WIDTH = 10 31 32 # Scale and background, which are parameters common to every form factor33 COMMON_PARAMETERS = [34 [ "scale", "", 0, [0, np.inf], "", "Source intensity" ],35 [ "background", "1/cm", 0, [0, np.inf], "", "Source background" ],36 ]37 38 38 39 39 # Header included before every kernel. … … 60 60 # define kernel 61 61 # define SINCOS(angle,svar,cvar) do {svar=sin(angle);cvar=cos(angle);} while (0) 62 # define powr(a,b) pow(a,b) 62 63 #else 63 64 # ifdef USE_SINCOS … … 93 94 # respectively, so the template builder will need to do extra work to 94 95 # declare, initialize and pass the q parameters. 95 IQ_KERNEL= {96 KERNEL_1D = { 96 97 'fn': "Iq", 97 98 'q_par_decl': "global const real *q,", … … 100 101 } 101 102 102 IQXY_KERNEL= {103 KERNEL_2D = { 103 104 'fn': "Iqxy", 104 105 'q_par_decl': "global const real *qx,\n global const real *qy,", … … 176 177 # Volume normalization. 177 178 # If there are "volume" polydispersity parameters, then these will be used 178 # to call the volume function from the user supplied kernel, and accumulate179 # to call the form_volume function from the user supplied kernel, and accumulate 179 180 # a normalized weight. 180 181 VOLUME_NORM="""const real vol_weight = %(weight)s; 181 vol += vol_weight* volume(%(pars)s);182 vol += vol_weight*form_volume(%(pars)s); 182 183 norm_vol += vol_weight;""" 184 183 185 184 186 def indent(s, depth): … … 191 193 192 194 193 def make_kernel(meta, form): 195 def kernel_name(info, is_2D): 196 return info['name'] + "_" + ("Iqxy" if is_2D else "Iq") 197 198 199 def make_kernel(info, is_2D): 194 200 """ 195 201 Build a kernel call from metadata supplied by the user. 196 202 197 * meta* is the json object defined in the kernel file.203 *info* is the json object defined in the kernel file. 198 204 199 205 *form* is either "Iq" or "Iqxy". … … 206 212 # If we are building the Iqxy kernel, we need to propagate qx,qy 207 213 # parameters, otherwise we can 208 if form == "Iqxy": 209 qpars = IQXY_KERNEL 210 else: 211 qpars = IQ_KERNEL 212 214 dim = "2d" if is_2D else "1d" 215 fixed_pars = info['partype']['fixed-'+dim] 216 pd_pars = info['partype']['pd-'+dim] 217 vol_pars = info['partype']['volume'] 218 q_pars = KERNEL_2D if is_2D else KERNEL_1D 219 220 # Build polydispersity loops 213 221 depth = 4 214 222 offset = "" 215 223 loop_head = [] 216 224 loop_end = [] 217 vol_pars = [] 218 fixed_pars = [] 219 pd_pars = [] 220 fn_pars = [] 221 for i,p in enumerate(meta['parameters']): 222 name = p[0] 223 ptype = p[4] 224 if ptype == "volume": 225 vol_pars.append(name) 226 elif ptype == "orientation": 227 if form != "Iqxy": continue # no orientation for 1D kernels 228 elif ptype == "magnetic": 229 raise NotImplementedError("no magnetic parameters yet") 230 if name not in ['scale','background']: fn_pars.append(name) 231 if ptype == "": 232 fixed_pars.append(name) 233 continue 234 else: 235 pd_pars.append(name) 225 for name in pd_pars: 236 226 subst = { 'name': name, 'offset': offset } 237 227 loop_head.append(indent(LOOP_OPEN%subst, depth)) … … 254 244 255 245 # Define the inner loop function call 246 # The parameters to the f(q,p1,p2...) call should occur in the same 247 # order as given in the parameter info structure. This may be different 248 # from the parameter order in the call to the kernel since the kernel 249 # call places all fixed parameters before all polydisperse parameters. 250 fq_pars = [p[0] for p in info['parameters'][len(COMMON_PARAMETERS):] 251 if p[0] in set(fixed_pars+pd_pars)] 256 252 subst = { 257 253 'weight_product': "*".join(p+"_w" for p in pd_pars), 258 254 'volume_norm': volume_norm, 259 'fn': q pars['fn'],260 'qcall': q pars['qcall'],261 'pcall': ", ".join(f n_pars),255 'fn': q_pars['fn'], 256 'qcall': q_pars['qcall'], 257 'pcall': ", ".join(fq_pars), # skip scale and background 262 258 } 263 259 loop_body = [indent(LOOP_BODY%subst, depth)] … … 280 276 subst = { 281 277 # kernel name is, e.g., cylinder_Iq 282 'name': "_".join((meta['name'], qpars['fn'])),278 'name': kernel_name(info, is_2D), 283 279 # to declare, e.g., global real q[], 284 'q_par_decl': q pars['q_par_decl'],280 'q_par_decl': q_pars['q_par_decl'], 285 281 # to declare, e.g., real sld, int Nradius, int Nlength 286 282 'par_decl': par_decl, … … 288 284 'pd_length': "+".join('N'+p for p in pd_pars), 289 285 # the q initializers, e.g., real qi = q[i]; 290 'qinit': q pars['qinit'],286 'qinit': q_pars['qinit'], 291 287 # the actual polydispersity loop 292 288 'loops': loops, … … 295 291 return kernel 296 292 297 def make_partable( meta):298 pars = meta['parameters']293 def make_partable(info): 294 pars = info['parameters'] 299 295 column_widths = [ 300 296 max(len(p[0]) for p in pars), … … 320 316 return "\n".join(lines) 321 317 322 def make_doc(kernelfile, meta, doc):323 doc = doc%{'parameters': make_partable( meta)}318 def make_doc(kernelfile, info, doc): 319 doc = doc%{'parameters': make_partable(info)} 324 320 return doc 325 321 326 def make_model(kernelfile, meta, source):327 kernel_Iq = make_kernel( meta, "Iq")328 kernel_Iqxy = make_kernel( meta, "Iqxy")322 def make_model(kernelfile, info, source): 323 kernel_Iq = make_kernel(info, is_2D=False) 324 kernel_Iqxy = make_kernel(info, is_2D=True) 329 325 path = os.path.dirname(kernelfile) 330 extra = [open("%s/%s"%(path,f)).read() for f in meta['include']]326 extra = [open("%s/%s"%(path,f)).read() for f in info['include']] 331 327 kernel = "\n\n".join([KERNEL_HEADER]+extra+[source, kernel_Iq, kernel_Iqxy]) 332 328 return kernel … … 339 335 if len(parts) != 3: 340 336 raise ValueError("PARAMETERS block missing from %r"%kernelfile) 341 meta_source = parts[1].strip()337 info_source = parts[1].strip() 342 338 try: 343 meta = relaxed_loads(meta_source)339 info = relaxed_loads(info_source) 344 340 except: 345 341 print "in json text:" 346 342 print "\n".join("%2d: %s"%(i+1,s) 347 for i,s in enumerate( meta_source.split('\n')))343 for i,s in enumerate(info_source.split('\n'))) 348 344 raise 349 345 #raise ValueError("PARAMETERS block could not be parsed from %r"%kernelfile) 350 meta['parameters'] = COMMON_PARAMETERS + meta['parameters']351 meta['filename'] = kernelfile352 346 353 347 # select documentation out of the source file 354 348 parts = source.split("DOCUMENTATION") 355 349 if len(parts) == 3: 356 doc = make_doc(kernelfile, meta, parts[1].strip())350 doc = make_doc(kernelfile, info, parts[1].strip()) 357 351 elif len(parts) == 1: 358 352 raise ValueError("DOCUMENTATION block is missing from %r"%kernelfile) … … 360 354 raise ValueError("DOCUMENTATION block incorrect from %r"%kernelfile) 361 355 362 return source, meta, doc 356 return source, info, doc 357 358 def categorize_parameters(pars): 359 """ 360 Build parameter categories out of the the parameter definitions. 361 362 Returns a dictionary of categories. 363 364 The function call sequence consists of q inputs and the return vector, 365 followed by the loop value/weight vector, followed by the values for 366 the non-polydisperse parameters, followed by the lengths of the 367 polydispersity loops. To construct the call for 1D models, the 368 categories *fixed-1d* and *pd-1d* list the names of the parameters 369 of the non-polydisperse and the polydisperse parameters respectively. 370 Similarly, *fixed-2d* and *pd-2d* provide parameter names for 2D models. 371 The *pd-rel* category is a set of those parameters which give 372 polydispersitiy as a portion of the value (so a 10% length dispersity 373 would use a polydispersity value of 0.1) rather than absolute 374 dispersity such as an angle plus or minus 15 degrees. 375 376 The *volume* category lists the volume parameters in order for calls 377 to volume within the kernel (used for volume normalization) and for 378 calls to ER and VR for effective radius and volume ratio respectively. 379 380 The *orientation* and *magnetic* categories list the orientation and 381 magnetic parameters. These are used by the sasview interface. The 382 blank category is for parameters such as scale which don't have any 383 other marking. 384 """ 385 partype = { 386 'volume': [], 'orientation': [], 'magnetic': [], '': [], 387 'fixed-1d': [], 'fixed-2d': [], 'pd-1d': [], 'pd-2d': [], 388 'pd-rel': set(), 389 } 390 391 for p in pars: 392 name,ptype = p[0],p[4] 393 if ptype == 'volume': 394 partype['pd-1d'].append(name) 395 partype['pd-2d'].append(name) 396 partype['pd-rel'].add(name) 397 elif ptype == 'magnetic': 398 partype['fixed-2d'].append(name) 399 elif ptype == 'orientation': 400 partype['pd-2d'].append(name) 401 elif ptype == '': 402 partype['fixed-1d'].append(name) 403 partype['fixed-2d'].append(name) 404 else: 405 raise ValueError("unknown parameter type %r"%ptype) 406 partype[ptype].append(name) 407 408 return partype 363 409 364 410 def make(kernelfile): … … 370 416 """ 371 417 #print kernelfile 372 source, meta, doc = parse_file(kernelfile) 373 doc = make_doc(kernelfile, meta, doc) 374 model = make_model(kernelfile, meta, source) 375 return model, meta, doc 376 377 378 379 # Convert from python float to C float or double, depending on dtype 380 FLOAT_CONVERTER = { 381 F32: np.float32, 382 F64: np.float64, 383 } 384 385 def kernel_name(meta, is_2D): 386 return meta['name'] + "_" + ("Iqxy" if is_2D else "Iq") 387 388 389 def kernel_pars(pars, par_info, is_2D, dtype=F32): 390 """ 391 Convert parameter dictionary into arguments for the kernel. 392 393 *pars* is a dictionary of *{ name: value }*, with *name_pd* for the 394 polydispersity width, *name_pd_n* for the number of pd steps, and 395 *name_pd_nsigma* for the polydispersity limits. 396 397 *par_info* is the parameter info structure from the kernel metadata. 398 399 *is_2D* is True if the dataset represents 2D data, with the corresponding 400 orientation parameters. 401 402 *dtype* is F32 or F64, the numpy single and double precision floating 403 point dtypes. These should not be the strings. 404 """ 405 from .weights import GaussianDispersion 406 real = np.float32 if dtype == F32 else np.float64 407 fixed = [] 408 parts = [] 409 for p in par_info['parameters']: 410 name, ptype = p[0],p[4] 411 value = pars[name] 412 if ptype == "": 413 fixed.append(real(value)) 414 elif is_2D or ptype != "orientation": 415 limits, width = p[3], pars[name+'_pd'] 416 n, nsigma = pars[name+'_pd_n'], pars[name+'_pd_nsigma'] 417 relative = (ptype != "orientation") 418 dist = GaussianDispersion(int(n), width, nsigma) 419 # Make sure that weights are normalized to peaks at 1 so that 420 # the tolerance term can be used properly on truncated distributions 421 v,w = dist.get_weights(value, limits[0], limits[1], relative) 422 parts.append((v, w/w.max())) 423 loops = np.hstack(parts) 424 loops = np.ascontiguousarray(loops.T, dtype).flatten() 425 loopsN = [np.uint32(len(p[0])) for p in parts] 426 return fixed, loops, loopsN 418 source, info, doc = parse_file(kernelfile) 419 info['filename'] = kernelfile 420 info['parameters'] = COMMON_PARAMETERS + info['parameters'] 421 info['partype'] = categorize_parameters(info['parameters']) 422 info['limits'] = dict((p[0],p[3]) for p in info['parameters']) 423 doc = make_doc(kernelfile, info, doc) 424 model = make_model(kernelfile, info, source) 425 return model, info, doc 427 426 428 427 … … 437 436 def demo(): 438 437 from os.path import join as joinpath, dirname 439 c, meta, doc = make_model(joinpath(dirname(__file__), "models", "cylinder.c"))438 c, info, doc = make_model(joinpath(dirname(__file__), "models", "cylinder.c")) 440 439 #print doc 441 440 #print c
Note: See TracChangeset
for help on using the changeset viewer.