Changeset 6d6508e in sasmodels for sasmodels/generate.py


Ignore:
Timestamp:
Apr 7, 2016 6:57:33 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:
d2fc9a4
Parents:
3707eee
Message:

refactor model_info from dictionary to class

File:
1 edited

Legend:

Unmodified
Added
Removed
  • sasmodels/generate.py

    r6e7ff6d r6d6508e  
    155155#__all__ = ["model_info", "make_doc", "make_source", "convert_type"] 
    156156 
    157 from os.path import abspath, dirname, join as joinpath, exists, basename, \ 
    158     splitext, getmtime 
     157from os.path import abspath, dirname, join as joinpath, exists, getmtime 
    159158import re 
    160159import string 
     
    163162import numpy as np 
    164163 
    165 from .modelinfo import ModelInfo, Parameter, make_parameter_table, set_demo 
     164from .modelinfo import Parameter 
    166165from .custom import load_custom_kernel_module 
    167166 
     
    273272    Return a list of the sources file paths for the module. 
    274273    """ 
    275     search_path = [dirname(model_info['filename']), 
     274    search_path = [dirname(model_info.filename), 
    276275                   abspath(joinpath(dirname(__file__), 'models'))] 
    277     return [_search(search_path, f) for f in model_info['source']] 
     276    return [_search(search_path, f) for f in model_info.source] 
    278277 
    279278def timestamp(model_info): 
     
    284283    source_files = (model_sources(model_info) 
    285284                    + model_templates() 
    286                     + [model_info['filename']]) 
     285                    + [model_info.filename]) 
    287286    newest = max(getmtime(f) for f in source_files) 
    288287    return newest 
     
    334333    Name of the exported kernel symbol. 
    335334    """ 
    336     return model_info['name'] + "_" + ("Iqxy" if is_2d else "Iq") 
     335    return model_info.name + "_" + ("Iqxy" if is_2d else "Iq") 
    337336 
    338337 
     
    420419    Uses source files found in the given search path. 
    421420    """ 
    422     if callable(model_info['Iq']): 
     421    if callable(model_info.Iq): 
    423422        return None 
    424423 
     
    432431    # for computing volume even if we allow non-disperse volume parameters. 
    433432 
    434     partable = model_info['parameters'] 
     433    partable = model_info.parameters 
    435434 
    436435    # Identify parameters for Iq, Iqxy, Iq_magnetic and form_volume. 
     
    448447    q, qx, qy = [Parameter(name=v) for v in ('q', 'qx', 'qy')] 
    449448    # Generate form_volume function, etc. from body only 
    450     if model_info['form_volume'] is not None: 
     449    if model_info.form_volume is not None: 
    451450        pars = partable.form_volume_parameters 
    452         source.append(_gen_fn('form_volume', pars, model_info['form_volume'])) 
    453     if model_info['Iq'] is not None: 
     451        source.append(_gen_fn('form_volume', pars, model_info.form_volume)) 
     452    if model_info.Iq is not None: 
    454453        pars = [q] + partable.iq_parameters 
    455         source.append(_gen_fn('Iq', pars, model_info['Iq'])) 
    456     if model_info['Iqxy'] is not None: 
     454        source.append(_gen_fn('Iq', pars, model_info.Iq)) 
     455    if model_info.Iqxy is not None: 
    457456        pars = [qx, qy] + partable.iqxy_parameters 
    458         source.append(_gen_fn('Iqxy', pars, model_info['Iqxy'])) 
     457        source.append(_gen_fn('Iqxy', pars, model_info.Iqxy)) 
    459458 
    460459    # Define the parameter table 
     
    494493 
    495494    # define the Iq kernel 
    496     source.append("#define KERNEL_NAME %s_Iq"%model_info['name']) 
     495    source.append("#define KERNEL_NAME %s_Iq"%model_info.name) 
    497496    source.append(call_iq) 
    498497    source.append(kernel_code) 
     
    501500 
    502501    # define the Iqxy kernel from the same source with different #defines 
    503     source.append("#define KERNEL_NAME %s_Iqxy"%model_info['name']) 
     502    source.append("#define KERNEL_NAME %s_Iqxy"%model_info.name) 
    504503    source.append(call_iqxy) 
    505504    source.append(kernel_code) 
     
    508507 
    509508    return '\n'.join(source) 
    510  
    511 class CoordinationDetails(object): 
    512     def __init__(self, model_info): 
    513         parameters = model_info['parameters'] 
    514         max_pd = parameters.max_pd 
    515         npars = parameters.npars 
    516         par_offset = 4*max_pd 
    517         self.details = np.zeros(par_offset + 3*npars + 4, 'i4') 
    518  
    519         # generate views on different parts of the array 
    520         self._pd_par     = self.details[0*max_pd:1*max_pd] 
    521         self._pd_length  = self.details[1*max_pd:2*max_pd] 
    522         self._pd_offset  = self.details[2*max_pd:3*max_pd] 
    523         self._pd_stride  = self.details[3*max_pd:4*max_pd] 
    524         self._par_offset = self.details[par_offset+0*npars:par_offset+1*npars] 
    525         self._par_coord  = self.details[par_offset+1*npars:par_offset+2*npars] 
    526         self._pd_coord   = self.details[par_offset+2*npars:par_offset+3*npars] 
    527  
    528         # theta_par is fixed 
    529         self.details[-1] = parameters.theta_offset 
    530  
    531     @property 
    532     def ctypes(self): return self.details.ctypes 
    533  
    534     @property 
    535     def pd_par(self): return self._pd_par 
    536  
    537     @property 
    538     def pd_length(self): return self._pd_length 
    539  
    540     @property 
    541     def pd_offset(self): return self._pd_offset 
    542  
    543     @property 
    544     def pd_stride(self): return self._pd_stride 
    545  
    546     @property 
    547     def pd_coord(self): return self._pd_coord 
    548  
    549     @property 
    550     def par_coord(self): return self._par_coord 
    551  
    552     @property 
    553     def par_offset(self): return self._par_offset 
    554  
    555     @property 
    556     def num_active(self): return self.details[-4] 
    557     @num_active.setter 
    558     def num_active(self, v): self.details[-4] = v 
    559  
    560     @property 
    561     def total_pd(self): return self.details[-3] 
    562     @total_pd.setter 
    563     def total_pd(self, v): self.details[-3] = v 
    564  
    565     @property 
    566     def num_coord(self): return self.details[-2] 
    567     @num_coord.setter 
    568     def num_coord(self, v): self.details[-2] = v 
    569  
    570     @property 
    571     def theta_par(self): return self.details[-1] 
    572  
    573     def show(self): 
    574         print("total_pd", self.total_pd) 
    575         print("num_active", self.num_active) 
    576         print("pd_par", self.pd_par) 
    577         print("pd_length", self.pd_length) 
    578         print("pd_offset", self.pd_offset) 
    579         print("pd_stride", self.pd_stride) 
    580         print("par_offsets", self.par_offset) 
    581         print("num_coord", self.num_coord) 
    582         print("par_coord", self.par_coord) 
    583         print("pd_coord", self.pd_coord) 
    584         print("theta par", self.details[-1]) 
    585  
    586 def mono_details(model_info): 
    587     details = CoordinationDetails(model_info) 
    588     # The zero defaults for monodisperse systems are mostly fine 
    589     details.par_offset[:] = np.arange(2, len(details.par_offset)+2) 
    590     return details 
    591  
    592 def poly_details(model_info, weights): 
    593     #print("weights",weights) 
    594     weights = weights[2:] # Skip scale and background 
    595  
    596     # Decreasing list of polydispersity lengths 
    597     # Note: the reversing view, x[::-1], does not require a copy 
    598     pd_length = np.array([len(w) for w in weights]) 
    599     num_active = np.sum(pd_length>1) 
    600     if num_active > model_info['parameters'].max_pd: 
    601         raise ValueError("Too many polydisperse parameters") 
    602  
    603     pd_offset = np.cumsum(np.hstack((0, pd_length))) 
    604     idx = np.argsort(pd_length)[::-1][:num_active] 
    605     par_length = np.array([max(len(w),1) for w in weights]) 
    606     pd_stride = np.cumprod(np.hstack((1, par_length[idx]))) 
    607     par_offsets = np.cumsum(np.hstack((2, par_length))) 
    608  
    609     details = CoordinationDetails(model_info) 
    610     details.pd_par[:num_active] = idx 
    611     details.pd_length[:num_active] = pd_length[idx] 
    612     details.pd_offset[:num_active] = pd_offset[idx] 
    613     details.pd_stride[:num_active] = pd_stride[:-1] 
    614     details.par_offset[:] = par_offsets[:-1] 
    615     details.total_pd = pd_stride[-1] 
    616     details.num_active = num_active 
    617     # Without constraints coordinated parameters are just the pd parameters 
    618     details.par_coord[:num_active] = idx 
    619     details.pd_coord[:num_active] = 2**np.arange(num_active) 
    620     details.num_coord = num_active 
    621     #details.show() 
    622     return details 
    623  
    624 def constrained_poly_details(model_info, weights, constraints): 
    625     # Need to find the independently varying pars and sort them 
    626     # Need to build a coordination list for the dependent variables 
    627     # Need to generate a constraints function which takes values 
    628     # and weights, returning par blocks 
    629     raise NotImplementedError("Can't handle constraints yet") 
    630  
    631  
    632 def create_vector_Iq(model_info): 
    633     """ 
    634     Define Iq as a vector function if it exists. 
    635     """ 
    636     Iq = model_info['Iq'] 
    637     if callable(Iq) and not getattr(Iq, 'vectorized', False): 
    638         def vector_Iq(q, *args): 
    639             """ 
    640             Vectorized 1D kernel. 
    641             """ 
    642             return np.array([Iq(qi, *args) for qi in q]) 
    643         model_info['Iq'] = vector_Iq 
    644  
    645 def create_vector_Iqxy(model_info): 
    646     """ 
    647     Define Iqxy as a vector function if it exists, or default it from Iq(). 
    648     """ 
    649     Iq, Iqxy = model_info['Iq'], model_info['Iqxy'] 
    650     if callable(Iqxy) and not getattr(Iqxy, 'vectorized', False): 
    651         def vector_Iqxy(qx, qy, *args): 
    652             """ 
    653             Vectorized 2D kernel. 
    654             """ 
    655             return np.array([Iqxy(qxi, qyi, *args) for qxi, qyi in zip(qx, qy)]) 
    656         model_info['Iqxy'] = vector_Iqxy 
    657     elif callable(Iq): 
    658         # Iq is vectorized because create_vector_Iq was already called. 
    659         def default_Iqxy(qx, qy, *args): 
    660             """ 
    661             Default 2D kernel. 
    662             """ 
    663             return Iq(np.sqrt(qx**2 + qy**2), *args) 
    664         model_info['Iqxy'] = default_Iqxy 
    665  
    666 def create_default_functions(model_info): 
    667     """ 
    668     Autogenerate missing functions, such as Iqxy from Iq. 
    669  
    670     This only works for Iqxy when Iq is written in python. :func:`make_source` 
    671     performs a similar role for Iq written in C.  This also vectorizes 
    672     any functions that are not already marked as vectorized. 
    673     """ 
    674     create_vector_Iq(model_info) 
    675     create_vector_Iqxy(model_info)  # call create_vector_Iq() first 
    676509 
    677510def load_kernel_module(model_name): 
     
    685518 
    686519 
    687 def make_model_info(kernel_module): 
    688     """ 
    689     Interpret the model definition file, categorizing the parameters. 
    690  
    691     The module can be loaded with a normal python import statement if you 
    692     know which module you need, or with __import__('sasmodels.model.'+name) 
    693     if the name is in a string. 
    694  
    695     The *model_info* structure contains the following fields: 
    696  
    697     * *id* is the id of the kernel 
    698     * *name* is the display name of the kernel 
    699     * *filename* is the full path to the module defining the file (if any) 
    700     * *title* is a short description of the kernel 
    701     * *description* is a long description of the kernel (this doesn't seem 
    702       very useful since the Help button on the model page brings you directly 
    703       to the documentation page) 
    704     * *docs* is the docstring from the module.  Use :func:`make_doc` to 
    705     * *category* specifies the model location in the docs 
    706     * *parameters* is the model parameter table 
    707     * *single* is True if the model allows single precision 
    708     * *structure_factor* is True if the model is useable in a product 
    709     * *variant_info* contains the information required to select between 
    710       model variants (e.g., the list of cases) or is None if there are no 
    711       model variants 
    712     * *par_type* categorizes the model parameters. See 
    713       :func:`categorize_parameters` for details. 
    714     * *demo* contains the *{parameter: value}* map used in compare (and maybe 
    715       for the demo plot, if plots aren't set up to use the default values). 
    716       If *demo* is not given in the file, then the default values will be used. 
    717     * *tests* is a set of tests that must pass 
    718     * *source* is the list of library files to include in the C model build 
    719     * *Iq*, *Iqxy*, *form_volume*, *ER*, *VR* and *sesans* are python functions 
    720       implementing the kernel for the module, or None if they are not 
    721       defined in python 
    722     * *composition* is None if the model is independent, otherwise it is a 
    723       tuple with composition type ('product' or 'mixture') and a list of 
    724       *model_info* blocks for the composition objects.  This allows us to 
    725       build complete product and mixture models from just the info. 
    726     """ 
    727     # TODO: maybe turn model_info into a class ModelDefinition 
    728     #print("make parameter table", kernel_module.parameters) 
    729     parameters = make_parameter_table(kernel_module.parameters) 
    730     filename = abspath(kernel_module.__file__) 
    731     kernel_id = splitext(basename(filename))[0] 
    732     name = getattr(kernel_module, 'name', None) 
    733     if name is None: 
    734         name = " ".join(w.capitalize() for w in kernel_id.split('_')) 
    735     model_info = dict( 
    736         id=kernel_id,  # string used to load the kernel 
    737         filename=abspath(kernel_module.__file__), 
    738         name=name, 
    739         title=getattr(kernel_module, 'title', name+" model"), 
    740         description=getattr(kernel_module, 'description', 'no description'), 
    741         parameters=parameters, 
    742         composition=None, 
    743         docs=kernel_module.__doc__, 
    744         category=getattr(kernel_module, 'category', None), 
    745         single=getattr(kernel_module, 'single', True), 
    746         structure_factor=getattr(kernel_module, 'structure_factor', False), 
    747         profile_axes=getattr(kernel_module, 'profile_axes', ['x','y']), 
    748         variant_info=getattr(kernel_module, 'invariant_info', None), 
    749         demo=getattr(kernel_module, 'demo', None), 
    750         source=getattr(kernel_module, 'source', []), 
    751         tests=getattr(kernel_module, 'tests', []), 
    752         ) 
    753     set_demo(model_info, getattr(kernel_module, 'demo', None)) 
    754     # Check for optional functions 
    755     functions = "ER VR form_volume Iq Iqxy profile sesans".split() 
    756     model_info.update((k, getattr(kernel_module, k, None)) for k in functions) 
    757     create_default_functions(model_info) 
    758     # Precalculate the monodisperse parameters 
    759     # TODO: make this a lazy evaluator 
    760     # make_model_info is called for every model on sasview startup 
    761     model_info['mono_details'] = mono_details(model_info) 
    762     return model_info 
    763520 
    764521section_marker = re.compile(r'\A(?P<first>[%s])(?P=first)*\Z' 
     
    801558    Iq_units = "The returned value is scaled to units of |cm^-1| |sr^-1|, absolute scale." 
    802559    Sq_units = "The returned value is a dimensionless structure factor, $S(q)$." 
    803     docs = convert_section_titles_to_boldface(model_info['docs']) 
    804     subst = dict(id=model_info['id'].replace('_', '-'), 
    805                  name=model_info['name'], 
    806                  title=model_info['title'], 
    807                  parameters=make_partable(model_info['parameters']), 
    808                  returns=Sq_units if model_info['structure_factor'] else Iq_units, 
     560    docs = convert_section_titles_to_boldface(model_info.docs) 
     561    subst = dict(id=model_info.id.replace('_', '-'), 
     562                 name=model_info.name, 
     563                 title=model_info.title, 
     564                 parameters=make_partable(model_info.parameters), 
     565                 returns=Sq_units if model_info.structure_factor else Iq_units, 
    809566                 docs=docs) 
    810567    return DOC_HEADER % subst 
     
    815572    Show how long it takes to process a model. 
    816573    """ 
     574    import datetime 
     575    from .modelinfo import make_model_info 
    817576    from .models import cylinder 
    818     import datetime 
     577 
    819578    tic = datetime.datetime.now() 
    820579    make_source(make_model_info(cylinder)) 
     
    827586    """ 
    828587    import sys 
     588    from .modelinfo import make_model_info 
     589 
    829590    if len(sys.argv) <= 1: 
    830591        print("usage: python -m sasmodels.generate modelname") 
Note: See TracChangeset for help on using the changeset viewer.