Changeset 03cac08 in sasmodels for sasmodels/generate.py


Ignore:
Timestamp:
Mar 20, 2016 9:44:11 PM (8 years ago)
Author:
Paul Kienzle <pkienzle@…>
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
Message:

new generator produces code that compiles

File:
1 edited

Legend:

Unmodified
Added
Removed
  • sasmodels/generate.py

    rd5ac45f r03cac08  
    236236 
    237237TEMPLATE_ROOT = dirname(__file__) 
     238 
     239MAX_PD = 4 
    238240 
    239241F16 = np.dtype('float16') 
     
    420422    return _template_cache[filename][1] 
    421423 
     424_FN_TEMPLATE = """\ 
     425double %(name)s(%(pars)s); 
     426double %(name)s(%(pars)s) { 
     427    %(body)s 
     428} 
     429 
     430 
     431""" 
     432 
    422433def _gen_fn(name, pars, body): 
    423434    """ 
     
    431442         } 
    432443    """ 
    433     template = """\ 
    434 double %(name)s(%(pars)s); 
    435 double %(name)s(%(pars)s) { 
    436     %(body)s 
    437 } 
    438  
    439  
    440 """ 
    441444    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 
     447def _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) 
     456def _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 
    447476 
    448477def make_source(model_info): 
     
    464493    # for computing volume even if we allow non-disperse volume parameters. 
    465494 
    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 
    482512 
    483513    # Generate form_volume function, etc. from body only 
    484514    if model_info['form_volume'] is not None: 
    485         pnames = [p.name for p in volume_parameters] 
     515        pnames = [p.name for p in vol_parameters] 
    486516        source.append(_gen_fn('form_volume', pnames, model_info['form_volume'])) 
    487517    if model_info['Iq'] is not None: 
     
    492522        source.append(_gen_fn('Iqxy', pnames, model_info['Iqxy'])) 
    493523 
    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 
    498533    else: 
    499534        # Model doesn't have volume.  We could make the kernel run a little 
    500535        # faster by not using/transferring the volume normalizations, but 
    501536        # 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) 
    541574 
    542575def categorize_parameters(pars): 
     
    588621    } 
    589622    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) 
    591624    return  par_type 
    592625 
     
    605638    pars = [Parameter(*p) for p in model_info['parameters']] 
    606639    # Fill in the derived attributes 
     640    par_type = collect_types(pars) 
     641    par_type.update(categorize_parameters(pars)) 
    607642    model_info['parameters'] = pars 
    608     partype = categorize_parameters(pars) 
    609643    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 
    612645    model_info['defaults'] = dict((p.name, p.default) for p in pars) 
    613646    if model_info.get('demo', None) is None: 
    614647        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 
     650def 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 
    616664 
    617665def make_model_info(kernel_module): 
     
    693741    functions = "ER VR form_volume Iq Iqxy shape sesans".split() 
    694742    model_info.update((k, getattr(kernel_module, k, None)) for k in functions) 
     743    create_default_functions(model_info) 
    695744    return model_info 
    696745 
Note: See TracChangeset for help on using the changeset viewer.