Changeset 03cac08 in sasmodels for sasmodels/generate.py
- Timestamp:
- Mar 20, 2016 9:44:11 PM (8 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:
- 303d8d6
- Parents:
- d5ac45f
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
sasmodels/generate.py
rd5ac45f r03cac08 236 236 237 237 TEMPLATE_ROOT = dirname(__file__) 238 239 MAX_PD = 4 238 240 239 241 F16 = np.dtype('float16') … … 420 422 return _template_cache[filename][1] 421 423 424 _FN_TEMPLATE = """\ 425 double %(name)s(%(pars)s); 426 double %(name)s(%(pars)s) { 427 %(body)s 428 } 429 430 431 """ 432 422 433 def _gen_fn(name, pars, body): 423 434 """ … … 431 442 } 432 443 """ 433 template = """\434 double %(name)s(%(pars)s);435 double %(name)s(%(pars)s) {436 %(body)s437 }438 439 440 """441 444 par_decl = ', '.join('double ' + p for p in pars) if pars else 'void' 442 return template % {'name': name, 'body': body, 'pars': par_decl} 443 444 def _gen_call_pars(name, pars): 445 name += "." 446 return ",".join(name+p for p in pars) 445 return _FN_TEMPLATE % {'name': name, 'body': body, 'pars': par_decl} 446 447 def _call_pars(prefix, pars): 448 """ 449 Return a list of *prefix.parameter* from parameter items. 450 """ 451 prefix += "." 452 return [prefix+p.name for p in pars] 453 454 _IQXY_PATTERN = re.compile("^((inline|static) )? *(double )? *Iqxy *([(]|$)", 455 flags=re.MULTILINE) 456 def _have_Iqxy(sources): 457 """ 458 Return true if any file defines Iqxy. 459 460 Note this is not a C parser, and so can be easily confused by 461 non-standard syntax. Also, it will incorrectly identify the following 462 as having Iqxy:: 463 464 /* 465 double Iqxy(qx, qy, ...) { ... fill this in later ... } 466 */ 467 468 If you want to comment out an Iqxy function, use // on the front of the 469 line instead. 470 """ 471 for code in sources: 472 if _IQXY_PATTERN.search(code): 473 return True 474 else: 475 return False 447 476 448 477 def make_source(model_info): … … 464 493 # for computing volume even if we allow non-disperse volume parameters. 465 494 466 # Load template 467 source = [load_template('kernel_header.c')] 468 469 # Load additional sources 470 source += [open(f).read() for f in model_sources(model_info)] 471 472 # Prepare defines 473 defines = [] 474 475 iq_parameters = [p.name 476 for p in model_info['parameters'][2:] # skip scale, background 477 if p.name in model_info['par_set']['1d']] 478 iqxy_parameters = [p.name 479 for p in model_info['parameters'][2:] # skip scale, background 480 if p.name in model_info['par_set']['2d']] 481 volume_parameters = model_info['par_type']['volume'] 495 # kernel_iq assumes scale and background are the first parameters; 496 # they should be first for 1d and 2d parameter lists as well. 497 assert model_info['parameters'][0].name == 'scale' 498 assert model_info['parameters'][1].name == 'background' 499 500 # Identify parameter types 501 iq_parameters = model_info['par_type']['1d'][2:] 502 iqxy_parameters = model_info['par_type']['2d'][2:] 503 vol_parameters = model_info['par_type']['volume'] 504 505 # Load templates and user code 506 kernel_header = load_template('kernel_header.c') 507 kernel_code = load_template('kernel_iq.c') 508 user_code = [open(f).read() for f in model_sources(model_info)] 509 510 # Build initial sources 511 source = [kernel_header] + user_code 482 512 483 513 # Generate form_volume function, etc. from body only 484 514 if model_info['form_volume'] is not None: 485 pnames = [p.name for p in vol ume_parameters]515 pnames = [p.name for p in vol_parameters] 486 516 source.append(_gen_fn('form_volume', pnames, model_info['form_volume'])) 487 517 if model_info['Iq'] is not None: … … 492 522 source.append(_gen_fn('Iqxy', pnames, model_info['Iqxy'])) 493 523 494 # Fill in definitions for volume parameters 495 if volume_parameters: 496 deref_vol = ",".join("v."+p.name for p in volume_parameters) 497 defines.append(('CALL_VOLUME(v)', 'form_volume(%s)\n'%deref_vol)) 524 # Define the parameter table 525 source.append("#define PARAMETER_TABLE \\") 526 source.append("\\\n ".join("double %s;"%p.name 527 for p in model_info['parameters'][2:])) 528 529 # Define the function calls 530 if vol_parameters: 531 refs = ",".join(_call_pars("v", vol_parameters)) 532 call_volume = "#define CALL_VOLUME(v) form_volume(%s)"%refs 498 533 else: 499 534 # Model doesn't have volume. We could make the kernel run a little 500 535 # faster by not using/transferring the volume normalizations, but 501 536 # the ifdef's reduce readability more than is worthwhile. 502 defines.append(('CALL_VOLUME(v)', '0.0')) 503 504 # Fill in definitions for Iq parameters 505 defines.append(('KERNEL_NAME', model_info['name'])) 506 defines.append(('IQ_PARAMETERS', ', '.join(iq_parameters))) 507 if fixed_1d: 508 defines.append(('IQ_FIXED_PARAMETER_DECLARATIONS', 509 ', \\\n '.join('const double %s' % p for p in fixed_1d))) 510 # Fill in definitions for Iqxy parameters 511 defines.append(('IQXY_KERNEL_NAME', model_info['name'] + '_Iqxy')) 512 defines.append(('IQXY_PARAMETERS', ', '.join(iqxy_parameters))) 513 if fixed_2d: 514 defines.append(('IQXY_FIXED_PARAMETER_DECLARATIONS', 515 ', \\\n '.join('const double %s' % p for p in fixed_2d))) 516 if pd_2d: 517 defines.append(('IQXY_WEIGHT_PRODUCT', 518 '*'.join(p + '_w' for p in pd_2d))) 519 defines.append(('IQXY_DISPERSION_LENGTH_DECLARATIONS', 520 ', \\\n '.join('const int N%s' % p for p in pd_2d))) 521 defines.append(('IQXY_DISPERSION_LENGTH_SUM', 522 '+'.join('N' + p for p in pd_2d))) 523 open_loops, close_loops = build_polydispersity_loops(pd_2d) 524 defines.append(('IQXY_OPEN_LOOPS', 525 open_loops.replace('\n', ' \\\n'))) 526 defines.append(('IQXY_CLOSE_LOOPS', 527 close_loops.replace('\n', ' \\\n'))) 528 # Need to know if we have a theta parameter for Iqxy; it is not there 529 # for the magnetic sphere model, for example, which has a magnetic 530 # orientation but no shape orientation. 531 if 'theta' in pd_2d: 532 defines.append(('IQXY_HAS_THETA', '1')) 533 534 #for d in defines: print(d) 535 defines = '\n'.join('#define %s %s' % (k, v) for k, v in defines) 536 sources = '\n\n'.join(source) 537 return C_KERNEL_TEMPLATE % { 538 'DEFINES': defines, 539 'SOURCES': sources, 540 } 537 call_volume = "#define CALL_VOLUME(v) 0.0" 538 source.append(call_volume) 539 540 refs = ["q[i]"] + _call_pars("v", iq_parameters) 541 call_iq = "#define CALL_IQ(q,i,v) Iq(%s)" % (",".join(refs)) 542 if _have_Iqxy(user_code): 543 # Call 2D model 544 refs = ["q[2*i]", "q[2*i+1]"] + _call_pars("v", iqxy_parameters) 545 call_iqxy = "#define CALL_IQ(q,i,v) Iqxy(%s)" % (",".join(refs)) 546 else: 547 # Call 1D model with sqrt(qx^2 + qy^2) 548 warnings.warn("Creating Iqxy = Iq(sqrt(qx^2 + qy^2))") 549 # still defined:: refs = ["q[i]"] + _call_pars("v", iq_parameters) 550 pars_sqrt = ["sqrt(q[2*i]*q[2*i]+q[2*i+1]*q[2*i+1])"] + refs[1:] 551 call_iqxy = "#define CALL_IQ(q,i,v) Iq(%s)" % (",".join(pars_sqrt)) 552 553 # Fill in definitions for numbers of parameters 554 source.append("#define MAX_PD %s"%MAX_PD) 555 source.append("#define NPARS %d"%(len(model_info['parameters'])-2)) 556 557 # TODO: allow mixed python/opencl kernels? 558 559 # define the Iq kernel 560 source.append("#define KERNEL_NAME %s_Iq"%model_info['name']) 561 source.append(call_iq) 562 source.append(kernel_code) 563 source.append("#undef CALL_IQ") 564 source.append("#undef KERNEL_NAME") 565 566 # define the Iqxy kernel from the same source with different #defines 567 source.append("#define KERNEL_NAME %s_Iqxy"%model_info['name']) 568 source.append(call_iqxy) 569 source.append(kernel_code) 570 source.append("#undef CALL_IQ") 571 source.append("#undef KERNEL_NAME") 572 573 return '\n'.join(source) 541 574 542 575 def categorize_parameters(pars): … … 588 621 } 589 622 for p in pars: 590 par_type[p.type if p.type else 'other'].append(p .name)623 par_type[p.type if p.type else 'other'].append(p) 591 624 return par_type 592 625 … … 605 638 pars = [Parameter(*p) for p in model_info['parameters']] 606 639 # Fill in the derived attributes 640 par_type = collect_types(pars) 641 par_type.update(categorize_parameters(pars)) 607 642 model_info['parameters'] = pars 608 partype = categorize_parameters(pars)609 643 model_info['limits'] = dict((p.name, p.limits) for p in pars) 610 model_info['par_type'] = collect_types(pars) 611 model_info['par_set'] = categorize_parameters(pars) 644 model_info['par_type'] = par_type 612 645 model_info['defaults'] = dict((p.name, p.default) for p in pars) 613 646 if model_info.get('demo', None) is None: 614 647 model_info['demo'] = model_info['defaults'] 615 model_info['has_2d'] = partype['orientation'] or partype['magnetic'] 648 model_info['has_2d'] = par_type['orientation'] or par_type['magnetic'] 649 650 def create_default_functions(model_info): 651 """ 652 Autogenerate missing functions, such as Iqxy from Iq. 653 654 This only works for Iqxy when Iq is written in python. :func:`make_source` 655 performs a similar role for Iq written in C. 656 """ 657 if model_info['Iq'] is not None and model_info['Iqxy'] is None: 658 if model_info['par_type']['1d'] != model_info['par_type']['2d']: 659 raise ValueError("Iqxy model is missing") 660 Iq = model_info['Iq'] 661 def Iqxy(qx, qy, **kw): 662 return Iq(np.sqrt(qx**2 + qy**2), **kw) 663 model_info['Iqxy'] = Iqxy 616 664 617 665 def make_model_info(kernel_module): … … 693 741 functions = "ER VR form_volume Iq Iqxy shape sesans".split() 694 742 model_info.update((k, getattr(kernel_module, k, None)) for k in functions) 743 create_default_functions(model_info) 695 744 return model_info 696 745
Note: See TracChangeset
for help on using the changeset viewer.