Changeset 17bbadd in sasmodels for sasmodels/generate.py


Ignore:
Timestamp:
Mar 15, 2016 12:47:12 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:
754e27b
Parents:
5ceb7d0
Message:

refactor so all model defintion queries use model_info; better documentation of model_info structure; initial implementation of product model (broken)

File:
1 edited

Legend:

Unmodified
Added
Removed
  • sasmodels/generate.py

    r5ceb7d0 r17bbadd  
    117117    are added to the beginning of the parameter list.  They will show up 
    118118    in the documentation as model parameters, but they are never sent to 
    119     the kernel functions. 
    120  
    121     *category* is the default category for the model.  Models in the 
    122     *structure-factor* category do not have *scale* and *background* 
    123     added. 
     119    the kernel functions.  Note that *effect_radius* and *volfraction* 
     120    must occur first in structure factor calculations. 
     121 
     122    *category* is the default category for the model.  The category is 
     123    two level structure, with the form "group:section", indicating where 
     124    in the manual the model will be located.  Models are alphabetical 
     125    within their section. 
    124126 
    125127    *source* is the list of C-99 source files that must be joined to 
     
    157159 
    158160 
    159 An *info* dictionary is constructed from the kernel meta data and 
     161An *model_info* dictionary is constructed from the kernel meta data and 
    160162returned to the caller. 
    161163 
     
    190192 
    191193The function :func:`make` loads the metadata from the module and returns 
    192 the kernel source.  The function :func:`doc` extracts the doc string 
     194the kernel source.  The function :func:`make_doc` extracts the doc string 
    193195and adds the parameter table to the top.  The function :func:`model_sources` 
    194196returns a list of files required by the model. 
     
    217219import numpy as np 
    218220 
    219 #__all__ = ["make", "doc", "model_sources", "convert_type"] 
     221#TODO: determine which functions are useful outside of generate 
     222#__all__ = ["model_info", "make_doc", "make_source", "convert_type"] 
    220223 
    221224C_KERNEL_TEMPLATE_PATH = joinpath(dirname(__file__), 'kernel_template.c') 
     
    327330    raise ValueError("%r not found in %s" % (filename, search_path)) 
    328331 
    329 def model_sources(info): 
     332def model_sources(model_info): 
    330333    """ 
    331334    Return a list of the sources file paths for the module. 
    332335    """ 
    333     search_path = [dirname(info['filename']), 
     336    search_path = [dirname(model_info['filename']), 
    334337                   abspath(joinpath(dirname(__file__), 'models'))] 
    335     return [_search(search_path, f) for f in info['source']] 
     338    return [_search(search_path, f) for f in model_info['source']] 
    336339 
    337340# Pragmas for enable OpenCL features.  Be sure to protect them so that they 
     
    391394 
    392395 
    393 def kernel_name(info, is_2d): 
     396def kernel_name(model_info, is_2d): 
    394397    """ 
    395398    Name of the exported kernel symbol. 
    396399    """ 
    397     return info['name'] + "_" + ("Iqxy" if is_2d else "Iq") 
    398  
     400    return model_info['name'] + "_" + ("Iqxy" if is_2d else "Iq") 
     401 
     402 
     403def indent(s, depth): 
     404    """ 
     405    Indent a string of text with *depth* additional spaces on each line. 
     406    """ 
     407    spaces = " "*depth 
     408    sep = "\n" + spaces 
     409    return spaces + sep.join(s.split("\n")) 
     410 
     411 
     412LOOP_OPEN = """\ 
     413for (int %(name)s_i=0; %(name)s_i < N%(name)s; %(name)s_i++) { 
     414  const double %(name)s = loops[2*(%(name)s_i%(offset)s)]; 
     415  const double %(name)s_w = loops[2*(%(name)s_i%(offset)s)+1];\ 
     416""" 
     417def build_polydispersity_loops(pd_pars): 
     418    """ 
     419    Build polydispersity loops 
     420 
     421    Returns loop opening and loop closing 
     422    """ 
     423    depth = 4 
     424    offset = "" 
     425    loop_head = [] 
     426    loop_end = [] 
     427    for name in pd_pars: 
     428        subst = {'name': name, 'offset': offset} 
     429        loop_head.append(indent(LOOP_OPEN % subst, depth)) 
     430        loop_end.insert(0, (" "*depth) + "}") 
     431        offset += '+N' + name 
     432        depth += 2 
     433    return "\n".join(loop_head), "\n".join(loop_end) 
     434 
     435C_KERNEL_TEMPLATE = None 
     436def make_source(model_info): 
     437    """ 
     438    Generate the OpenCL/ctypes kernel from the module info. 
     439 
     440    Uses source files found in the given search path. 
     441    """ 
     442    if callable(model_info['Iq']): 
     443        return None 
     444 
     445    # TODO: need something other than volume to indicate dispersion parameters 
     446    # No volume normalization despite having a volume parameter. 
     447    # Thickness is labelled a volume in order to trigger polydispersity. 
     448    # May want a separate dispersion flag, or perhaps a separate category for 
     449    # disperse, but not volume.  Volume parameters also use relative values 
     450    # for the distribution rather than the absolute values used by angular 
     451    # dispersion.  Need to be careful that necessary parameters are available 
     452    # for computing volume even if we allow non-disperse volume parameters. 
     453 
     454    # Load template 
     455    global C_KERNEL_TEMPLATE 
     456    if C_KERNEL_TEMPLATE is None: 
     457        with open(C_KERNEL_TEMPLATE_PATH) as fid: 
     458            C_KERNEL_TEMPLATE = fid.read() 
     459 
     460    # Load additional sources 
     461    source = [open(f).read() for f in model_sources(model_info)] 
     462 
     463    # Prepare defines 
     464    defines = [] 
     465    partype = model_info['partype'] 
     466    pd_1d = partype['pd-1d'] 
     467    pd_2d = partype['pd-2d'] 
     468    fixed_1d = partype['fixed-1d'] 
     469    fixed_2d = partype['fixed-1d'] 
     470 
     471    iq_parameters = [p[0] 
     472                     for p in model_info['parameters'][2:]  # skip scale, background 
     473                     if p[0] in set(fixed_1d + pd_1d)] 
     474    iqxy_parameters = [p[0] 
     475                       for p in model_info['parameters'][2:]  # skip scale, background 
     476                       if p[0] in set(fixed_2d + pd_2d)] 
     477    volume_parameters = [p[0] 
     478                         for p in model_info['parameters'] 
     479                         if p[4] == 'volume'] 
     480 
     481    # Fill in defintions for volume parameters 
     482    if volume_parameters: 
     483        defines.append(('VOLUME_PARAMETERS', 
     484                        ','.join(volume_parameters))) 
     485        defines.append(('VOLUME_WEIGHT_PRODUCT', 
     486                        '*'.join(p + '_w' for p in volume_parameters))) 
     487 
     488    # Generate form_volume function from body only 
     489    if model_info['form_volume'] is not None: 
     490        if volume_parameters: 
     491            vol_par_decl = ', '.join('double ' + p for p in volume_parameters) 
     492        else: 
     493            vol_par_decl = 'void' 
     494        defines.append(('VOLUME_PARAMETER_DECLARATIONS', 
     495                        vol_par_decl)) 
     496        fn = """\ 
     497double form_volume(VOLUME_PARAMETER_DECLARATIONS); 
     498double form_volume(VOLUME_PARAMETER_DECLARATIONS) { 
     499    %(body)s 
     500} 
     501""" % {'body':model_info['form_volume']} 
     502        source.append(fn) 
     503 
     504    # Fill in definitions for Iq parameters 
     505    defines.append(('IQ_KERNEL_NAME', model_info['name'] + '_Iq')) 
     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    if pd_1d: 
     511        defines.append(('IQ_WEIGHT_PRODUCT', 
     512                        '*'.join(p + '_w' for p in pd_1d))) 
     513        defines.append(('IQ_DISPERSION_LENGTH_DECLARATIONS', 
     514                        ', \\\n    '.join('const int N%s' % p for p in pd_1d))) 
     515        defines.append(('IQ_DISPERSION_LENGTH_SUM', 
     516                        '+'.join('N' + p for p in pd_1d))) 
     517        open_loops, close_loops = build_polydispersity_loops(pd_1d) 
     518        defines.append(('IQ_OPEN_LOOPS', 
     519                        open_loops.replace('\n', ' \\\n'))) 
     520        defines.append(('IQ_CLOSE_LOOPS', 
     521                        close_loops.replace('\n', ' \\\n'))) 
     522    if model_info['Iq'] is not None: 
     523        defines.append(('IQ_PARAMETER_DECLARATIONS', 
     524                        ', '.join('double ' + p for p in iq_parameters))) 
     525        fn = """\ 
     526double Iq(double q, IQ_PARAMETER_DECLARATIONS); 
     527double Iq(double q, IQ_PARAMETER_DECLARATIONS) { 
     528    %(body)s 
     529} 
     530""" % {'body':model_info['Iq']} 
     531        source.append(fn) 
     532 
     533    # Fill in definitions for Iqxy parameters 
     534    defines.append(('IQXY_KERNEL_NAME', model_info['name'] + '_Iqxy')) 
     535    defines.append(('IQXY_PARAMETERS', ', '.join(iqxy_parameters))) 
     536    if fixed_2d: 
     537        defines.append(('IQXY_FIXED_PARAMETER_DECLARATIONS', 
     538                        ', \\\n    '.join('const double %s' % p for p in fixed_2d))) 
     539    if pd_2d: 
     540        defines.append(('IQXY_WEIGHT_PRODUCT', 
     541                        '*'.join(p + '_w' for p in pd_2d))) 
     542        defines.append(('IQXY_DISPERSION_LENGTH_DECLARATIONS', 
     543                        ', \\\n    '.join('const int N%s' % p for p in pd_2d))) 
     544        defines.append(('IQXY_DISPERSION_LENGTH_SUM', 
     545                        '+'.join('N' + p for p in pd_2d))) 
     546        open_loops, close_loops = build_polydispersity_loops(pd_2d) 
     547        defines.append(('IQXY_OPEN_LOOPS', 
     548                        open_loops.replace('\n', ' \\\n'))) 
     549        defines.append(('IQXY_CLOSE_LOOPS', 
     550                        close_loops.replace('\n', ' \\\n'))) 
     551    if model_info['Iqxy'] is not None: 
     552        defines.append(('IQXY_PARAMETER_DECLARATIONS', 
     553                        ', '.join('double ' + p for p in iqxy_parameters))) 
     554        fn = """\ 
     555double Iqxy(double qx, double qy, IQXY_PARAMETER_DECLARATIONS); 
     556double Iqxy(double qx, double qy, IQXY_PARAMETER_DECLARATIONS) { 
     557    %(body)s 
     558} 
     559""" % {'body':model_info['Iqxy']} 
     560        source.append(fn) 
     561 
     562    # Need to know if we have a theta parameter for Iqxy; it is not there 
     563    # for the magnetic sphere model, for example, which has a magnetic 
     564    # orientation but no shape orientation. 
     565    if 'theta' in pd_2d: 
     566        defines.append(('IQXY_HAS_THETA', '1')) 
     567 
     568    #for d in defines: print(d) 
     569    defines = '\n'.join('#define %s %s' % (k, v) for k, v in defines) 
     570    sources = '\n\n'.join(source) 
     571    return C_KERNEL_TEMPLATE % { 
     572        'DEFINES': defines, 
     573        'SOURCES': sources, 
     574        } 
    399575 
    400576def categorize_parameters(pars): 
     
    403579 
    404580    Returns a dictionary of categories. 
     581 
     582    Note: these categories are subject to change, depending on the needs of 
     583    the UI and the needs of the kernel calling function. 
     584 
     585    The categories are as follows: 
     586 
     587    * *volume* list of volume parameter names 
     588    * *orientation* list of orientation parameters 
     589    * *magnetic* list of magnetic parameters 
     590    * *<empty string>* list of parameters that have no type info 
     591 
     592    Each parameter is in one and only one category. 
     593 
     594    The following derived categories are created: 
     595 
     596    * *fixed-1d* list of non-polydisperse parameters for 1D models 
     597    * *pd-1d* list of polydisperse parameters for 1D models 
     598    * *fixed-2d* list of non-polydisperse parameters for 2D models 
     599    * *pd-d2* list of polydisperse parameters for 2D models 
    405600    """ 
    406601    partype = { 
     
    429624    return partype 
    430625 
    431 def indent(s, depth): 
    432     """ 
    433     Indent a string of text with *depth* additional spaces on each line. 
    434     """ 
    435     spaces = " "*depth 
    436     sep = "\n" + spaces 
    437     return spaces + sep.join(s.split("\n")) 
    438  
    439  
    440 LOOP_OPEN = """\ 
    441 for (int %(name)s_i=0; %(name)s_i < N%(name)s; %(name)s_i++) { 
    442   const double %(name)s = loops[2*(%(name)s_i%(offset)s)]; 
    443   const double %(name)s_w = loops[2*(%(name)s_i%(offset)s)+1];\ 
    444 """ 
    445 def build_polydispersity_loops(pd_pars): 
    446     """ 
    447     Build polydispersity loops 
    448  
    449     Returns loop opening and loop closing 
    450     """ 
    451     depth = 4 
    452     offset = "" 
    453     loop_head = [] 
    454     loop_end = [] 
    455     for name in pd_pars: 
    456         subst = {'name': name, 'offset': offset} 
    457         loop_head.append(indent(LOOP_OPEN % subst, depth)) 
    458         loop_end.insert(0, (" "*depth) + "}") 
    459         offset += '+N' + name 
    460         depth += 2 
    461     return "\n".join(loop_head), "\n".join(loop_end) 
    462  
    463 C_KERNEL_TEMPLATE = None 
    464 def make_model(info): 
    465     """ 
    466     Generate the code for the kernel defined by info, using source files 
    467     found in the given search path. 
    468     """ 
    469     # TODO: need something other than volume to indicate dispersion parameters 
    470     # No volume normalization despite having a volume parameter. 
    471     # Thickness is labelled a volume in order to trigger polydispersity. 
    472     # May want a separate dispersion flag, or perhaps a separate category for 
    473     # disperse, but not volume.  Volume parameters also use relative values 
    474     # for the distribution rather than the absolute values used by angular 
    475     # dispersion.  Need to be careful that necessary parameters are available 
    476     # for computing volume even if we allow non-disperse volume parameters. 
    477  
    478     # Load template 
    479     global C_KERNEL_TEMPLATE 
    480     if C_KERNEL_TEMPLATE is None: 
    481         with open(C_KERNEL_TEMPLATE_PATH) as fid: 
    482             C_KERNEL_TEMPLATE = fid.read() 
    483  
    484     # Load additional sources 
    485     source = [open(f).read() for f in model_sources(info)] 
    486  
    487     # Prepare defines 
    488     defines = [] 
    489     partype = info['partype'] 
    490     pd_1d = partype['pd-1d'] 
    491     pd_2d = partype['pd-2d'] 
    492     fixed_1d = partype['fixed-1d'] 
    493     fixed_2d = partype['fixed-1d'] 
    494  
    495     iq_parameters = [p[0] 
    496                      for p in info['parameters'][2:] # skip scale, background 
    497                      if p[0] in set(fixed_1d + pd_1d)] 
    498     iqxy_parameters = [p[0] 
    499                        for p in info['parameters'][2:] # skip scale, background 
    500                        if p[0] in set(fixed_2d + pd_2d)] 
    501     volume_parameters = [p[0] 
    502                          for p in info['parameters'] 
    503                          if p[4] == 'volume'] 
    504  
    505     # Fill in defintions for volume parameters 
    506     if volume_parameters: 
    507         defines.append(('VOLUME_PARAMETERS', 
    508                         ','.join(volume_parameters))) 
    509         defines.append(('VOLUME_WEIGHT_PRODUCT', 
    510                         '*'.join(p + '_w' for p in volume_parameters))) 
    511  
    512     # Generate form_volume function from body only 
    513     if info['form_volume'] is not None: 
    514         if volume_parameters: 
    515             vol_par_decl = ', '.join('double ' + p for p in volume_parameters) 
    516         else: 
    517             vol_par_decl = 'void' 
    518         defines.append(('VOLUME_PARAMETER_DECLARATIONS', 
    519                         vol_par_decl)) 
    520         fn = """\ 
    521 double form_volume(VOLUME_PARAMETER_DECLARATIONS); 
    522 double form_volume(VOLUME_PARAMETER_DECLARATIONS) { 
    523     %(body)s 
    524 } 
    525 """ % {'body':info['form_volume']} 
    526         source.append(fn) 
    527  
    528     # Fill in definitions for Iq parameters 
    529     defines.append(('IQ_KERNEL_NAME', info['name'] + '_Iq')) 
    530     defines.append(('IQ_PARAMETERS', ', '.join(iq_parameters))) 
    531     if fixed_1d: 
    532         defines.append(('IQ_FIXED_PARAMETER_DECLARATIONS', 
    533                         ', \\\n    '.join('const double %s' % p for p in fixed_1d))) 
    534     if pd_1d: 
    535         defines.append(('IQ_WEIGHT_PRODUCT', 
    536                         '*'.join(p + '_w' for p in pd_1d))) 
    537         defines.append(('IQ_DISPERSION_LENGTH_DECLARATIONS', 
    538                         ', \\\n    '.join('const int N%s' % p for p in pd_1d))) 
    539         defines.append(('IQ_DISPERSION_LENGTH_SUM', 
    540                         '+'.join('N' + p for p in pd_1d))) 
    541         open_loops, close_loops = build_polydispersity_loops(pd_1d) 
    542         defines.append(('IQ_OPEN_LOOPS', 
    543                         open_loops.replace('\n', ' \\\n'))) 
    544         defines.append(('IQ_CLOSE_LOOPS', 
    545                         close_loops.replace('\n', ' \\\n'))) 
    546     if info['Iq'] is not None: 
    547         defines.append(('IQ_PARAMETER_DECLARATIONS', 
    548                         ', '.join('double ' + p for p in iq_parameters))) 
    549         fn = """\ 
    550 double Iq(double q, IQ_PARAMETER_DECLARATIONS); 
    551 double Iq(double q, IQ_PARAMETER_DECLARATIONS) { 
    552     %(body)s 
    553 } 
    554 """ % {'body':info['Iq']} 
    555         source.append(fn) 
    556  
    557     # Fill in definitions for Iqxy parameters 
    558     defines.append(('IQXY_KERNEL_NAME', info['name'] + '_Iqxy')) 
    559     defines.append(('IQXY_PARAMETERS', ', '.join(iqxy_parameters))) 
    560     if fixed_2d: 
    561         defines.append(('IQXY_FIXED_PARAMETER_DECLARATIONS', 
    562                         ', \\\n    '.join('const double %s' % p for p in fixed_2d))) 
    563     if pd_2d: 
    564         defines.append(('IQXY_WEIGHT_PRODUCT', 
    565                         '*'.join(p + '_w' for p in pd_2d))) 
    566         defines.append(('IQXY_DISPERSION_LENGTH_DECLARATIONS', 
    567                         ', \\\n    '.join('const int N%s' % p for p in pd_2d))) 
    568         defines.append(('IQXY_DISPERSION_LENGTH_SUM', 
    569                         '+'.join('N' + p for p in pd_2d))) 
    570         open_loops, close_loops = build_polydispersity_loops(pd_2d) 
    571         defines.append(('IQXY_OPEN_LOOPS', 
    572                         open_loops.replace('\n', ' \\\n'))) 
    573         defines.append(('IQXY_CLOSE_LOOPS', 
    574                         close_loops.replace('\n', ' \\\n'))) 
    575     if info['Iqxy'] is not None: 
    576         defines.append(('IQXY_PARAMETER_DECLARATIONS', 
    577                         ', '.join('double ' + p for p in iqxy_parameters))) 
    578         fn = """\ 
    579 double Iqxy(double qx, double qy, IQXY_PARAMETER_DECLARATIONS); 
    580 double Iqxy(double qx, double qy, IQXY_PARAMETER_DECLARATIONS) { 
    581     %(body)s 
    582 } 
    583 """ % {'body':info['Iqxy']} 
    584         source.append(fn) 
    585  
    586     # Need to know if we have a theta parameter for Iqxy; it is not there 
    587     # for the magnetic sphere model, for example, which has a magnetic 
    588     # orientation but no shape orientation. 
    589     if 'theta' in pd_2d: 
    590         defines.append(('IQXY_HAS_THETA', '1')) 
    591  
    592     #for d in defines: print(d) 
    593     defines = '\n'.join('#define %s %s' % (k, v) for k, v in defines) 
    594     sources = '\n\n'.join(source) 
    595     return C_KERNEL_TEMPLATE % { 
    596         'DEFINES': defines, 
    597         'SOURCES': sources, 
    598         } 
    599  
    600 def make_info(kernel_module): 
     626def process_parameters(model_info): 
     627    """ 
     628    Process parameter block, precalculating parameter details. 
     629    """ 
     630    # Fill in the derived attributes 
     631    model_info['limits'] = dict((p[0], p[3]) for p in model_info['parameters']) 
     632    model_info['partype'] = categorize_parameters(model_info['parameters']) 
     633    model_info['defaults'] = dict((p[0], p[2]) for p in model_info['parameters']) 
     634    if model_info.get('demo', None) is None: 
     635        model_info['demo'] = model_info['defaults'] 
     636 
     637def make_model_info(kernel_module): 
    601638    """ 
    602639    Interpret the model definition file, categorizing the parameters. 
    603     """ 
     640 
     641    The module can be loaded with a normal python import statement if you 
     642    know which module you need, or with __import__('sasmodels.model.'+name) 
     643    if the name is in a string. 
     644 
     645    The *model_info* structure contains the following fields: 
     646 
     647    * *id* is the id of the kernel 
     648    * *name* is the display name of the kernel 
     649    * *title* is a short description of the kernel 
     650    * *description* is a long description of the kernel (this doesn't seem 
     651      very useful since the Help button on the model page brings you directly 
     652      to the documentation page) 
     653    * *docs* is the docstring from the module.  Use :func:`make_doc` to 
     654    * *category* specifies the model location in the docs 
     655    * *parameters* is the model parameter table 
     656    * *single* is True if the model allows single precision 
     657    * *defaults* is the *{parameter: value}* table built from the parameter 
     658      description table. 
     659    * *limits* is the *{parameter: [min, max]}* table built from the 
     660      parameter description table. 
     661    * *partypes* categorizes the model parameters. See 
     662      :func:`categorize_parameters` for details. 
     663    * *demo* contains the *{parameter: value}* map used in compare (and maybe 
     664      for the demo plot, if plots aren't set up to use the default values). 
     665      If *demo* is not given in the file, then the default values will be used. 
     666    * *tests* is a set of tests that must pass 
     667    * *source* is the list of library files to include in the C model build 
     668    * *Iq*, *Iqxy*, *form_volume*, *ER*, and *VR* are python functions 
     669      implementing the kernel for the module, or None if they are not 
     670      defined in python 
     671    * *oldname* is the model name in pre-4.0 Sasview 
     672    * *oldpars* is the *{new: old}* parameter translation table 
     673      from pre-4.0 Sasview 
     674    * *composition* is None if the model is independent, otherwise it is a 
     675      tuple with composition type ('product' or 'mixture') and a list of 
     676      *model_info* blocks for the composition objects.  This allows us to 
     677      build complete product and mixture models from just the info. 
     678    """ 
     679    # TODO: maybe turn model_info into a class ModelDefinition 
    604680    #print(kernelfile) 
    605681    category = getattr(kernel_module, 'category', None) 
     
    608684    # parameters if an explicit demo parameter set has not been specified. 
    609685    demo_parameters = getattr(kernel_module, 'demo', None) 
    610     if demo_parameters is None: 
    611         demo_parameters = dict((p[0], p[2]) for p in parameters) 
    612686    filename = abspath(kernel_module.__file__) 
    613687    kernel_id = splitext(basename(filename))[0] 
    614688    name = getattr(kernel_module, 'name', None) 
     689    single = getattr(kernel_module, 'single', True) 
    615690    if name is None: 
    616691        name = " ".join(w.capitalize() for w in kernel_id.split('_')) 
    617     info = dict( 
     692    model_info = dict( 
    618693        id=kernel_id,  # string used to load the kernel 
    619694        filename=abspath(kernel_module.__file__), 
     
    621696        title=kernel_module.title, 
    622697        description=kernel_module.description, 
     698        docs=kernel_module.__doc__, 
    623699        category=category, 
    624700        parameters=parameters, 
     701        composition=None, 
     702        single=single, 
    625703        demo=demo_parameters, 
    626704        source=getattr(kernel_module, 'source', []), 
    627         oldname=kernel_module.oldname, 
    628         oldpars=kernel_module.oldpars, 
     705        oldname=getattr(kernel_module, 'oldname', None), 
     706        oldpars=getattr(kernel_module, 'oldpars', {}), 
     707        tests=getattr(kernel_module, 'tests', []), 
    629708        ) 
     709    process_parameters(model_info) 
    630710    # Fill in attributes which default to None 
    631     info.update((k, getattr(kernel_module, k, None)) 
    632                 for k in ('ER', 'VR', 'form_volume', 'Iq', 'Iqxy')) 
    633     # Fill in the derived attributes 
    634     info['limits'] = dict((p[0], p[3]) for p in info['parameters']) 
    635     info['partype'] = categorize_parameters(info['parameters']) 
    636     info['defaults'] = dict((p[0], p[2]) for p in info['parameters']) 
    637     return info 
    638  
    639 def make(kernel_module): 
    640     """ 
    641     Build an OpenCL/ctypes function from the definition in *kernel_module*. 
    642  
    643     The module can be loaded with a normal python import statement if you 
    644     know which module you need, or with __import__('sasmodels.model.'+name) 
    645     if the name is in a string. 
    646     """ 
    647     info = make_info(kernel_module) 
    648     # Assume if one part of the kernel is python then all parts are. 
    649     source = make_model(info) if not callable(info['Iq']) else None 
    650     return source, info 
     711    model_info.update((k, getattr(kernel_module, k, None)) 
     712                      for k in ('ER', 'VR', 'form_volume', 'Iq', 'Iqxy')) 
     713    return model_info 
    651714 
    652715section_marker = re.compile(r'\A(?P<first>[%s])(?P=first)*\Z' 
     
    683746    return "\n".join(_convert_section_titles_to_boldface(s.split('\n'))) 
    684747 
    685 def doc(kernel_module): 
     748def make_doc(model_info): 
    686749    """ 
    687750    Return the documentation for the model. 
     
    689752    Iq_units = "The returned value is scaled to units of |cm^-1| |sr^-1|, absolute scale." 
    690753    Sq_units = "The returned value is a dimensionless structure factor, $S(q)$." 
    691     info = make_info(kernel_module) 
    692     is_Sq = ("structure-factor" in info['category']) 
     754    is_Sq = ("structure-factor" in model_info['category']) 
    693755    #docs = kernel_module.__doc__ 
    694     docs = convert_section_titles_to_boldface(kernel_module.__doc__) 
    695     subst = dict(id=info['id'].replace('_', '-'), 
    696                  name=info['name'], 
    697                  title=info['title'], 
    698                  parameters=make_partable(info['parameters']), 
     756    docs = convert_section_titles_to_boldface(model_info['docs']) 
     757    subst = dict(id=model_info['id'].replace('_', '-'), 
     758                 name=model_info['name'], 
     759                 title=model_info['title'], 
     760                 parameters=make_partable(model_info['parameters']), 
    699761                 returns=Sq_units if is_Sq else Iq_units, 
    700762                 docs=docs) 
     
    710772    import datetime 
    711773    tic = datetime.datetime.now() 
    712     make(cylinder) 
     774    make_source(make_model_info(cylinder)) 
    713775    toc = (datetime.datetime.now() - tic).total_seconds() 
    714776    print("time: %g"%toc) 
     
    725787        __import__('sasmodels.models.' + name) 
    726788        model = getattr(sasmodels.models, name) 
    727         source, _ = make(model) 
     789        model_info = make_model_info(model) 
     790        source = make_source(model_info) 
    728791        print(source) 
    729792 
Note: See TracChangeset for help on using the changeset viewer.