Changeset 6d6508e in sasmodels


Ignore:
Timestamp:
Apr 7, 2016 4: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

Location:
sasmodels
Files:
16 edited

Legend:

Unmodified
Added
Removed
  • sasmodels/bumps_model.py

    r21b116f r6d6508e  
    8383    pars = {}     # floating point parameters 
    8484    pd_types = {} # distribution names 
    85     for p in model_info['parameters'].call_parameters: 
     85    for p in model_info.parameters.call_parameters: 
    8686        value = kwargs.pop(p.name, p.default) 
    8787        pars[p.name] = Parameter.default(value, name=p.name, limits=p.limits) 
  • sasmodels/compare.py

    r8bd7b77 r6d6508e  
    244244 
    245245    # Find the parameter definition 
    246     for par in model_info['parameters'].call_parameters: 
     246    for par in model_info.parameters.call_parameters: 
    247247        if par.name == p: 
    248248            break 
     
    284284    cylinder radius in this case). 
    285285    """ 
    286     name = model_info['id'] 
     286    name = model_info.id 
    287287    # if it is a product model, then just look at the form factor since 
    288288    # none of the structure factors need any constraints. 
     
    318318    """ 
    319319    lines = [] 
    320     parameters = model_info['parameters'] 
     320    parameters = model_info.parameters 
    321321    for p in parameters.user_parameters(pars, is2d): 
    322322        fields = dict( 
     
    368368 
    369369    # grab the sasview model, or create it if it is a product model 
    370     if model_info['composition']: 
    371         composition_type, parts = model_info['composition'] 
     370    if model_info.composition: 
     371        composition_type, parts = model_info.composition 
    372372        if composition_type == 'product': 
    373373            from sas.models.MultiplicationModel import MultiplicationModel 
     
    668668    # Get the default values for the parameters 
    669669    pars = {} 
    670     for p in model_info['parameters'].call_parameters: 
     670    for p in model_info.parameters.call_parameters: 
    671671        parts = [('', p.default)] 
    672672        if p.polydisperse: 
     
    683683    # Plug in values given in demo 
    684684    if use_demo: 
    685         pars.update(model_info['demo']) 
     685        pars.update(model_info.demo) 
    686686    return pars 
    687687 
     
    891891        # Initialize parameter ranges, fixing the 2D parameters for 1D data. 
    892892        if not opts['is2d']: 
    893             for p in model_info['parameters'].user_parameters(is2d=False): 
     893            for p in model_info.parameters.user_parameters(is2d=False): 
    894894                for ext in ['', '_pd', '_pd_n', '_pd_nsigma']: 
    895895                    k = p.name+ext 
  • sasmodels/convert.py

    r8bd7b77 r6d6508e  
    153153def revert_name(model_info): 
    154154    _read_conversion_table() 
    155     oldname, oldpars = CONVERSION_TABLE.get(model_info['id'], [None, {}]) 
     155    oldname, oldpars = CONVERSION_TABLE.get(model_info.id, [None, {}]) 
    156156    return oldname 
    157157 
    158158def _get_old_pars(model_info): 
    159159    _read_conversion_table() 
    160     oldname, oldpars = CONVERSION_TABLE.get(model_info['id'], [None, {}]) 
     160    oldname, oldpars = CONVERSION_TABLE.get(model_info.id, [None, {}]) 
    161161    return oldpars 
    162162 
     
    165165    Convert model from new style parameter names to old style. 
    166166    """ 
    167     if model_info['composition'] is not None: 
    168         composition_type, parts = model_info['composition'] 
     167    if model_info.composition is not None: 
     168        composition_type, parts = model_info.composition 
    169169        if composition_type == 'product': 
    170170            P, S = parts 
     
    180180 
    181181    # Note: update compare.constrain_pars to match 
    182     name = model_info['id'] 
    183     if name in MODELS_WITHOUT_SCALE or model_info['structure_factor']: 
     182    name = model_info.id 
     183    if name in MODELS_WITHOUT_SCALE or model_info.structure_factor: 
    184184        if oldpars.pop('scale', 1.0) != 1.0: 
    185185            warnings.warn("parameter scale not used in sasview %s"%name) 
    186     if name in MODELS_WITHOUT_BACKGROUND or model_info['structure_factor']: 
     186    if name in MODELS_WITHOUT_BACKGROUND or model_info.structure_factor: 
    187187        if oldpars.pop('background', 0.0) != 0.0: 
    188188            warnings.warn("parameter background not used in sasview %s"%name) 
     
    225225    Restrict parameter values to those that will match sasview. 
    226226    """ 
    227     name = model_info['id'] 
     227    name = model_info.id 
    228228    # Note: update convert.revert_model to match 
    229     if name in MODELS_WITHOUT_SCALE or model_info['structure_factor']: 
     229    if name in MODELS_WITHOUT_SCALE or model_info.structure_factor: 
    230230        pars['scale'] = 1 
    231     if name in MODELS_WITHOUT_BACKGROUND or model_info['structure_factor']: 
     231    if name in MODELS_WITHOUT_BACKGROUND or model_info.structure_factor: 
    232232        pars['background'] = 0 
    233233    # sasview multiplies background by structure factor 
  • sasmodels/core.py

    ree8f734 r6d6508e  
    22Core model handling routines. 
    33""" 
     4from __future__ import print_function 
     5 
    46__all__ = [ 
    5     "list_models", "load_model_info", "precompile_dll", 
    6     "build_model", "make_kernel", "call_kernel", "call_ER_VR", 
     7    "list_models", "load_model", "load_model_info", 
     8    "build_model", "precompile_dll", 
    79    ] 
    810 
    9 from os.path import basename, dirname, join as joinpath, splitext 
     11from os.path import basename, dirname, join as joinpath 
    1012from glob import glob 
    1113 
    1214import numpy as np 
    1315 
    14 from . import models 
    15 from . import weights 
    1616from . import generate 
    17 # TODO: remove circular references between product and core 
    18 # product uses call_ER/call_VR, core uses make_product_info/ProductModel 
    19 #from . import product 
     17from . import modelinfo 
     18from . import product 
    2019from . import mixture 
    2120from . import kernelpy 
     
    2625except Exception: 
    2726    HAVE_OPENCL = False 
    28  
    29 try: 
    30     np.meshgrid([]) 
    31     meshgrid = np.meshgrid 
    32 except ValueError: 
    33     # CRUFT: np.meshgrid requires multiple vectors 
    34     def meshgrid(*args): 
    35         if len(args) > 1: 
    36             return np.meshgrid(*args) 
    37         else: 
    38             return [np.asarray(v) for v in args] 
    3927 
    4028# TODO: refactor composite model support 
     
    8876    parts = model_name.split('*') 
    8977    if len(parts) > 1: 
    90         from . import product 
    91         # Note: currently have circular reference 
    9278        if len(parts) > 2: 
    9379            raise ValueError("use P*S to apply structure factor S to model P") 
     
    9682 
    9783    kernel_module = generate.load_kernel_module(model_name) 
    98     return generate.make_model_info(kernel_module) 
     84    return modelinfo.make_model_info(kernel_module) 
    9985 
    10086 
     
    118104    otherwise it uses the default "ocl". 
    119105    """ 
    120     composition = model_info.get('composition', None) 
     106    composition = model_info.composition 
    121107    if composition is not None: 
    122108        composition_type, parts = composition 
     
    137123    ##  4. rerun "python -m sasmodels.direct_model $MODELNAME" 
    138124    ##  5. uncomment open().read() so that source will be regenerated from model 
    139     # open(model_info['name']+'.c','w').write(source) 
    140     # source = open(model_info['name']+'.cl','r').read() 
     125    # open(model_info.name+'.c','w').write(source) 
     126    # source = open(model_info.name+'.cl','r').read() 
    141127    source = generate.make_source(model_info) 
    142128    if dtype is None: 
    143         dtype = 'single' if model_info['single'] else 'double' 
    144     if callable(model_info.get('Iq', None)): 
     129        dtype = 'single' if model_info.single else 'double' 
     130    if callable(model_info.Iq): 
    145131        return kernelpy.PyModel(model_info) 
    146132    if (platform == "dll" 
     
    168154    source = generate.make_source(model_info) 
    169155    return kerneldll.make_dll(source, model_info, dtype=dtype) if source else None 
    170  
    171  
    172 def get_weights(parameter, values): 
    173     """ 
    174     Generate the distribution for parameter *name* given the parameter values 
    175     in *pars*. 
    176  
    177     Uses "name", "name_pd", "name_pd_type", "name_pd_n", "name_pd_sigma" 
    178     from the *pars* dictionary for parameter value and parameter dispersion. 
    179     """ 
    180     value = float(values.get(parameter.name, parameter.default)) 
    181     relative = parameter.relative_pd 
    182     limits = parameter.limits 
    183     disperser = values.get(parameter.name+'_pd_type', 'gaussian') 
    184     npts = values.get(parameter.name+'_pd_n', 0) 
    185     width = values.get(parameter.name+'_pd', 0.0) 
    186     nsigma = values.get(parameter.name+'_pd_nsigma', 3.0) 
    187     if npts == 0 or width == 0: 
    188         return [value], [] 
    189     value, weight = weights.get_weights( 
    190         disperser, npts, width, nsigma, value, limits, relative) 
    191     return value, weight / np.sum(weight) 
    192  
    193 def dispersion_mesh(pars): 
    194     """ 
    195     Create a mesh grid of dispersion parameters and weights. 
    196  
    197     Returns [p1,p2,...],w where pj is a vector of values for parameter j 
    198     and w is a vector containing the products for weights for each 
    199     parameter set in the vector. 
    200     """ 
    201     value, weight = zip(*pars) 
    202     weight = [w if w else [1.] for w in weight] 
    203     value = [v.flatten() for v in meshgrid(*value)] 
    204     weight = np.vstack([v.flatten() for v in meshgrid(*weight)]) 
    205     weight = np.prod(weight, axis=0) 
    206     return value, weight 
    207  
    208 def call_kernel(kernel, pars, cutoff=0, mono=False): 
    209     """ 
    210     Call *kernel* returned from *model.make_kernel* with parameters *pars*. 
    211  
    212     *cutoff* is the limiting value for the product of dispersion weights used 
    213     to perform the multidimensional dispersion calculation more quickly at a 
    214     slight cost to accuracy. The default value of *cutoff=0* integrates over 
    215     the entire dispersion cube.  Using *cutoff=1e-5* can be 50% faster, but 
    216     with an error of about 1%, which is usually less than the measurement 
    217     uncertainty. 
    218  
    219     *mono* is True if polydispersity should be set to none on all parameters. 
    220     """ 
    221     parameters = kernel.info['parameters'] 
    222     if mono: 
    223         active = lambda name: False 
    224     elif kernel.dim == '1d': 
    225         active = lambda name: name in parameters.pd_1d 
    226     elif kernel.dim == '2d': 
    227         active = lambda name: name in parameters.pd_2d 
    228     else: 
    229         active = lambda name: True 
    230  
    231     vw_pairs = [(get_weights(p, pars) if active(p.name) 
    232                  else ([pars.get(p.name, p.default)], [])) 
    233                 for p in parameters.call_parameters] 
    234  
    235     details, weights, values = build_details(kernel, vw_pairs) 
    236     return kernel(details, weights, values, cutoff) 
    237  
    238 def build_details(kernel, pairs): 
    239     values, weights = zip(*pairs) 
    240     if max([len(w) for w in weights]) > 1: 
    241         details = generate.poly_details(kernel.info, weights) 
    242     else: 
    243         details = kernel.info['mono_details'] 
    244     weights, values = [np.hstack(v) for v in (weights, values)] 
    245     weights = weights.astype(dtype=kernel.dtype) 
    246     values = values.astype(dtype=kernel.dtype) 
    247     return details, weights, values 
    248  
    249  
    250 def call_ER_VR(model_info, vol_pars): 
    251     """ 
    252     Return effect radius and volume ratio for the model. 
    253  
    254     *info* is either *kernel.info* for *kernel=make_kernel(model,q)* 
    255     or *model.info*. 
    256  
    257     *pars* are the parameters as expected by :func:`call_kernel`. 
    258     """ 
    259     ER = model_info.get('ER', None) 
    260     VR = model_info.get('VR', None) 
    261     value, weight = dispersion_mesh(vol_pars) 
    262  
    263     individual_radii = ER(*value) if ER else 1.0 
    264     whole, part = VR(*value) if VR else (1.0, 1.0) 
    265  
    266     effect_radius = np.sum(weight*individual_radii) / np.sum(weight) 
    267     volume_ratio = np.sum(weight*part)/np.sum(weight*whole) 
    268     return effect_radius, volume_ratio 
    269  
    270  
    271 def call_ER(model_info, values): 
    272     """ 
    273     Call the model ER function using *values*. *model_info* is either 
    274     *model.info* if you have a loaded model, or *kernel.info* if you 
    275     have a model kernel prepared for evaluation. 
    276     """ 
    277     ER = model_info.get('ER', None) 
    278     if ER is None: 
    279         return 1.0 
    280     else: 
    281         vol_pars = [get_weights(parameter, values) 
    282                     for parameter in model_info['parameters'].call_parameters 
    283                     if parameter.type == 'volume'] 
    284         value, weight = dispersion_mesh(vol_pars) 
    285         individual_radii = ER(*value) 
    286         return np.sum(weight*individual_radii) / np.sum(weight) 
    287  
    288 def call_VR(model_info, values): 
    289     """ 
    290     Call the model VR function using *pars*. 
    291     *info* is either *model.info* if you have a loaded model, or *kernel.info* 
    292     if you have a model kernel prepared for evaluation. 
    293     """ 
    294     VR = model_info.get('VR', None) 
    295     if VR is None: 
    296         return 1.0 
    297     else: 
    298         vol_pars = [get_weights(parameter, values) 
    299                     for parameter in model_info['parameters'].call_parameters 
    300                     if parameter.type == 'volume'] 
    301         value, weight = dispersion_mesh(vol_pars) 
    302         whole, part = VR(*value) 
    303         return np.sum(weight*part)/np.sum(weight*whole) 
    304  
    305 # TODO: remove call_ER, call_VR 
    306  
  • sasmodels/direct_model.py

    ree8f734 r6d6508e  
    2525import numpy as np 
    2626 
    27 from .core import call_kernel, call_ER_VR 
    2827from . import sesans 
     28from . import weights 
    2929from . import resolution 
    3030from . import resolution2d 
     31from . import details 
     32 
     33 
     34def call_kernel(kernel, pars, cutoff=0, mono=False): 
     35    """ 
     36    Call *kernel* returned from *model.make_kernel* with parameters *pars*. 
     37 
     38    *cutoff* is the limiting value for the product of dispersion weights used 
     39    to perform the multidimensional dispersion calculation more quickly at a 
     40    slight cost to accuracy. The default value of *cutoff=0* integrates over 
     41    the entire dispersion cube.  Using *cutoff=1e-5* can be 50% faster, but 
     42    with an error of about 1%, which is usually less than the measurement 
     43    uncertainty. 
     44 
     45    *mono* is True if polydispersity should be set to none on all parameters. 
     46    """ 
     47    parameters = kernel.info.parameters 
     48    if mono: 
     49        active = lambda name: False 
     50    elif kernel.dim == '1d': 
     51        active = lambda name: name in parameters.pd_1d 
     52    elif kernel.dim == '2d': 
     53        active = lambda name: name in parameters.pd_2d 
     54    else: 
     55        active = lambda name: True 
     56 
     57    vw_pairs = [(get_weights(p, pars) if active(p.name) 
     58                 else ([pars.get(p.name, p.default)], [])) 
     59                for p in parameters.call_parameters] 
     60 
     61    call_details, weights, values = details.build_details(kernel, vw_pairs) 
     62    return kernel(call_details, weights, values, cutoff) 
     63 
     64def get_weights(parameter, values): 
     65    """ 
     66    Generate the distribution for parameter *name* given the parameter values 
     67    in *pars*. 
     68 
     69    Uses "name", "name_pd", "name_pd_type", "name_pd_n", "name_pd_sigma" 
     70    from the *pars* dictionary for parameter value and parameter dispersion. 
     71    """ 
     72    value = float(values.get(parameter.name, parameter.default)) 
     73    relative = parameter.relative_pd 
     74    limits = parameter.limits 
     75    disperser = values.get(parameter.name+'_pd_type', 'gaussian') 
     76    npts = values.get(parameter.name+'_pd_n', 0) 
     77    width = values.get(parameter.name+'_pd', 0.0) 
     78    nsigma = values.get(parameter.name+'_pd_nsigma', 3.0) 
     79    if npts == 0 or width == 0: 
     80        return [value], [] 
     81    value, weight = weights.get_weights( 
     82        disperser, npts, width, nsigma, value, limits, relative) 
     83    return value, weight / np.sum(weight) 
    3184 
    3285class DataMixin(object): 
     
    80133            q_mono = sesans.make_all_q(data) 
    81134        elif self.data_type == 'Iqxy': 
    82             #if not model.info['parameters'].has_2d: 
     135            #if not model.info.parameters.has_2d: 
    83136            #    raise ValueError("not 2D without orientation or magnetic parameters") 
    84137            q = np.sqrt(data.qx_data**2 + data.qy_data**2) 
     
    211264    def __call__(self, **pars): 
    212265        return self._calc_theory(pars, cutoff=self.cutoff) 
    213  
    214     def ER_VR(self, **pars): 
    215         """ 
    216         Compute the equivalent radius and volume ratio for the model. 
    217         """ 
    218         return call_ER_VR(self.model.info, pars) 
    219266 
    220267    def simulate_data(self, noise=None, **pars): 
  • 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") 
  • sasmodels/kernelcl.py

    rd2bb604 r6d6508e  
    342342        if self.program is None: 
    343343            compiler = environment().compile_program 
    344             self.program = compiler(self.info['name'], self.source, 
     344            self.program = compiler(self.info.name, self.source, 
    345345                                    self.dtype, self.fast) 
    346346        is_2d = len(q_vectors) == 2 
     
    354354        """ 
    355355        if self.program is not None: 
    356             environment().release_program(self.info['name']) 
     356            environment().release_program(self.info.name) 
    357357            self.program = None 
    358358 
     
    441441    """ 
    442442    def __init__(self, kernel, model_info, q_vectors, dtype): 
    443         max_pd = model_info['max_pd'] 
    444         npars = len(model_info['parameters'])-2 
     443        max_pd = model_info.max_pd 
     444        npars = len(model_info.parameters)-2 
    445445        q_input = GpuInput(q_vectors, dtype) 
    446446        self.dtype = dtype 
     
    465465        self._need_release = [ self.result_b, self.q_input ] 
    466466 
    467     def __call__(self, details, weights, values, cutoff): 
     467    def __call__(self, call_details, weights, values, cutoff): 
    468468        real = (np.float32 if self.q_input.dtype == generate.F32 
    469469                else np.float64 if self.q_input.dtype == generate.F64 
    470470                else np.float16 if self.q_input.dtype == generate.F16 
    471471                else np.float32)  # will never get here, so use np.float32 
    472         assert details.dtype == np.int32 
     472        assert call_details.dtype == np.int32 
    473473        assert weights.dtype == real and values.dtype == real 
    474474 
    475475        context = self.queue.context 
    476476        details_b = cl.Buffer(context, mf.READ_ONLY | mf.COPY_HOST_PTR, 
    477                               hostbuf=details) 
     477                              hostbuf=call_details) 
    478478        weights_b = cl.Buffer(context, mf.READ_ONLY | mf.COPY_HOST_PTR, 
    479479                              hostbuf=weights) 
  • sasmodels/kerneldll.py

    r1e2a1ba r6d6508e  
    5555 
    5656from . import generate 
     57from . import details 
    5758from .kernelpy import PyInput, PyModel 
    5859from .exception import annotate_exception 
     
    9596    """ 
    9697    bits = 8*dtype.itemsize 
    97     return "sas_%s%d"%(model_info['id'], bits) 
     98    return "sas_%s%d"%(model_info.id, bits) 
    9899 
    99100def dll_path(model_info, dtype): 
     
    123124    models are allowed as DLLs. 
    124125    """ 
    125     if callable(model_info.get('Iq', None)): 
     126    if callable(model_info.Iq): 
    126127        return PyModel(model_info) 
    127128     
     
    259260        self.result = np.empty(q_input.nq+3, q_input.dtype) 
    260261 
    261     def __call__(self, details, weights, values, cutoff): 
     262    def __call__(self, call_details, weights, values, cutoff): 
    262263        real = (np.float32 if self.q_input.dtype == generate.F32 
    263264                else np.float64 if self.q_input.dtype == generate.F64 
    264265                else np.float128) 
    265         assert isinstance(details, generate.CoordinationDetails) 
     266        assert isinstance(call_details, details.CallDetails) 
    266267        assert weights.dtype == real and values.dtype == real 
    267268 
    268         start, stop = 0, details.total_pd 
     269        start, stop = 0, call_details.total_pd 
    269270        #print("in kerneldll") 
    270271        #print("weights", weights) 
     
    274275            start, # pd_start 
    275276            stop, # pd_stop pd_stride[MAX_PD] 
    276             details.ctypes.data, # problem 
     277            call_details.ctypes.data, # problem 
    277278            weights.ctypes.data,  # weights 
    278279            values.ctypes.data,  #pars 
  • sasmodels/kernelpy.py

    ra8a7f08 r6d6508e  
    1717    """ 
    1818    def __init__(self, model_info): 
     19        # Make sure Iq and Iqxy are available and vectorized 
     20        _create_default_functions(model_info) 
    1921        self.info = model_info 
    2022 
    2123    def make_kernel(self, q_vectors): 
    2224        q_input = PyInput(q_vectors, dtype=F64) 
    23         kernel = self.info['Iqxy'] if q_input.is_2d else self.info['Iq'] 
     25        kernel = self.info.Iqxy if q_input.is_2d else self.info.Iq 
    2426        return PyKernel(kernel, self.info, q_input) 
    2527 
     
    9496        self.dim = '2d' if q_input.is_2d else '1d' 
    9597 
    96         partable = model_info['parameters'] 
     98        partable = model_info.parameters 
    9799        kernel_parameters = (partable.iqxy_parameters if q_input.is_2d 
    98100                             else partable.iq_parameters) 
     
    129131        # parameter array. 
    130132        if q_input.is_2d: 
    131             form = model_info['Iqxy'] 
     133            form = model_info.Iqxy 
    132134            qx, qy = q_input.q[:,0], q_input.q[:,1] 
    133135            self._form = lambda: form(qx, qy, *kernel_args) 
    134136        else: 
    135             form = model_info['Iq'] 
     137            form = model_info.Iq 
    136138            q = q_input.q 
    137139            self._form = lambda: form(q, *kernel_args) 
    138140 
    139141        # Generate a closure which calls the form_volume if it exists. 
    140         form_volume = model_info['form_volume'] 
     142        form_volume = model_info.form_volume 
    141143        self._volume = ((lambda: form_volume(*volume_args)) if form_volume 
    142144                        else (lambda: 1.0)) 
    143145 
    144     def __call__(self, details, weights, values, cutoff): 
    145         # type: (.generate.CoordinationDetails, np.ndarray, np.ndarray, float) -> np.ndarray 
     146    def __call__(self, call_details, weights, values, cutoff): 
     147        # type: (.generate.CallDetails, np.ndarray, np.ndarray, float) -> np.ndarray 
    146148        res = _loops(self._parameter_vector, self._form, self._volume, 
    147                      self.q_input.nq, details, weights, values, cutoff) 
     149                     self.q_input.nq, call_details, weights, values, cutoff) 
    148150        return res 
    149151 
     
    158160           form_volume, # type: Callable[[], float] 
    159161           nq,          # type: int 
    160            details,     # type: .generate.CoordinationDetails 
     162           call_details,# type: .generate.CallDetails 
    161163           weights,     # type: np.ndarray 
    162164           values,      # type: np.ndarray 
     
    172174    #                                                              # 
    173175    ################################################################ 
    174     parameters[:] = values[details.par_offset] 
     176    parameters[:] = values[call_details.par_offset] 
    175177    scale, background = values[0], values[1] 
    176     if details.num_active == 0: 
     178    if call_details.num_active == 0: 
    177179        norm = float(form_volume()) 
    178180        if norm > 0.0: 
     
    183185    partial_weight = np.NaN 
    184186    spherical_correction = 1.0 
    185     pd_stride = details.pd_stride[:details.num_active] 
    186     pd_length = details.pd_length[:details.num_active] 
    187     pd_offset = details.pd_offset[:details.num_active] 
     187    pd_stride = call_details.pd_stride[:call_details.num_active] 
     188    pd_length = call_details.pd_length[:call_details.num_active] 
     189    pd_offset = call_details.pd_offset[:call_details.num_active] 
    188190    pd_index = np.empty_like(pd_offset) 
    189     offset = np.empty_like(details.par_offset) 
    190     theta = details.theta_par 
     191    offset = np.empty_like(call_details.par_offset) 
     192    theta = call_details.theta_par 
    191193    fast_length = pd_length[0] 
    192194    pd_index[0] = fast_length 
    193195    total = np.zeros(nq, 'd') 
    194196    norm = 0.0 
    195     for loop_index in range(details.total_pd): 
     197    for loop_index in range(call_details.total_pd): 
    196198        # update polydispersity parameter values 
    197199        if pd_index[0] == fast_length: 
    198200            pd_index[:] = (loop_index/pd_stride)%pd_length 
    199201            partial_weight = np.prod(weights[pd_offset+pd_index][1:]) 
    200             for k in range(details.num_coord): 
    201                 par = details.par_coord[k] 
    202                 coord = details.pd_coord[k] 
    203                 this_offset = details.par_offset[par] 
     202            for k in range(call_details.num_coord): 
     203                par = call_details.par_coord[k] 
     204                coord = call_details.pd_coord[k] 
     205                this_offset = call_details.par_offset[par] 
    204206                block_size = 1 
    205207                for bit in xrange(32): 
     
    211213                offset[par] = this_offset 
    212214                parameters[par] = values[this_offset] 
    213                 if par == theta and not (details.par_coord[k]&1): 
     215                if par == theta and not (call_details.par_coord[k]&1): 
    214216                    spherical_correction = max(abs(cos(pi/180 * parameters[theta])), 1e-6) 
    215         for k in range(details.num_coord): 
    216             if details.pd_coord[k]&1: 
    217                 #par = details.par_coord[k] 
     217        for k in range(call_details.num_coord): 
     218            if call_details.pd_coord[k]&1: 
     219                #par = call_details.par_coord[k] 
    218220                parameters[par] = values[offset[par]] 
    219221                #print "par",par,offset[par],parameters[par+2] 
     
    241243    else: 
    242244        return np.ones(nq, 'd')*background 
     245 
     246 
     247 
     248def _create_vector_Iq(model_info): 
     249    """ 
     250    Define Iq as a vector function if it exists. 
     251    """ 
     252    Iq = model_info.Iq 
     253    if callable(Iq) and not getattr(Iq, 'vectorized', False): 
     254        def vector_Iq(q, *args): 
     255            """ 
     256            Vectorized 1D kernel. 
     257            """ 
     258            return np.array([Iq(qi, *args) for qi in q]) 
     259        vector_Iq.vectorized = True 
     260        model_info.Iq = vector_Iq 
     261 
     262def _create_vector_Iqxy(model_info): 
     263    """ 
     264    Define Iqxy as a vector function if it exists, or default it from Iq(). 
     265    """ 
     266    Iq, Iqxy = model_info.Iq, model_info.Iqxy 
     267    if callable(Iqxy) and not getattr(Iqxy, 'vectorized', False): 
     268        def vector_Iqxy(qx, qy, *args): 
     269            """ 
     270            Vectorized 2D kernel. 
     271            """ 
     272            return np.array([Iqxy(qxi, qyi, *args) for qxi, qyi in zip(qx, qy)]) 
     273        vector_Iqxy.vectorized = True 
     274        model_info.Iqxy = vector_Iqxy 
     275    elif callable(Iq): 
     276        # Iq is vectorized because create_vector_Iq was already called. 
     277        def default_Iqxy(qx, qy, *args): 
     278            """ 
     279            Default 2D kernel. 
     280            """ 
     281            return Iq(np.sqrt(qx**2 + qy**2), *args) 
     282        default_Iqxy.vectorized = True 
     283        model_info.Iqxy = default_Iqxy 
     284 
     285def _create_default_functions(model_info): 
     286    """ 
     287    Autogenerate missing functions, such as Iqxy from Iq. 
     288 
     289    This only works for Iqxy when Iq is written in python. :func:`make_source` 
     290    performs a similar role for Iq written in C.  This also vectorizes 
     291    any functions that are not already marked as vectorized. 
     292    """ 
     293    _create_vector_Iq(model_info) 
     294    _create_vector_Iqxy(model_info)  # call create_vector_Iq() first 
     295 
  • sasmodels/list_pars.py

    r21b116f r6d6508e  
    2525    for name in sorted(MODELS): 
    2626        model_info = load_model_info(name) 
    27         for p in model_info['parameters'].kernel_parameters: 
     27        for p in model_info.parameters.kernel_parameters: 
    2828            partable.setdefault(p.name, []) 
    2929            partable[p.name].append(name) 
  • sasmodels/mixture.py

    r69aa451 r6d6508e  
    1414import numpy as np 
    1515 
    16 from .modelinfo import Parameter, ParameterTable 
    17  
    18 SCALE=0 
    19 BACKGROUND=1 
    20 EFFECT_RADIUS=2 
    21 VOLFRACTION=3 
     16from .modelinfo import Parameter, ParameterTable, ModelInfo 
    2217 
    2318def make_mixture_info(parts): 
     
    5045    partable = ParameterTable(pars) 
    5146 
    52     model_info = {} 
    53     model_info['id'] = '+'.join(part['id']) 
    54     model_info['name'] = ' + '.join(part['name']) 
    55     model_info['filename'] = None 
    56     model_info['title'] = 'Mixture model with ' + model_info['name'] 
    57     model_info['description'] = model_info['title'] 
    58     model_info['docs'] = model_info['title'] 
    59     model_info['category'] = "custom" 
    60     model_info['parameters'] = partable 
    61     #model_info['single'] = any(part['single'] for part in parts) 
    62     model_info['structure_factor'] = False 
    63     model_info['variant_info'] = None 
    64     #model_info['tests'] = [] 
    65     #model_info['source'] = [] 
     47    model_info = ModelInfo() 
     48    model_info.id = '+'.join(part['id']) 
     49    model_info.name = ' + '.join(part['name']) 
     50    model_info.filename = None 
     51    model_info.title = 'Mixture model with ' + model_info.name 
     52    model_info.description = model_info.title 
     53    model_info.docs = model_info.title 
     54    model_info.category = "custom" 
     55    model_info.parameters = partable 
     56    #model_info.single = any(part['single'] for part in parts) 
     57    model_info.structure_factor = False 
     58    model_info.variant_info = None 
     59    #model_info.tests = [] 
     60    #model_info.source = [] 
    6661    # Iq, Iqxy, form_volume, ER, VR and sesans 
    6762    # Remember the component info blocks so we can build the model 
    68     model_info['composition'] = ('mixture', parts) 
     63    model_info.composition = ('mixture', parts) 
    6964 
    7065 
     
    108103        if dim == '2d': 
    109104            for p in kernels: 
    110                 partype = p.info['partype'] 
     105                partype = p.info.partype 
    111106                accumulate(partype['fixed-2d'], partype['pd-2d'], partype['volume']) 
    112107        else: 
    113108            for p in kernels: 
    114                 partype = p.info['partype'] 
     109                partype = p.info.partype 
    115110                accumulate(partype['fixed-1d'], partype['pd-1d'], partype['volume']) 
    116111 
  • sasmodels/model_test.py

    r6e7ff6d r6d6508e  
    4343Precision defaults to 5 digits (relative). 
    4444""" 
     45#TODO: rename to tests so that tab completion works better for models directory 
     46 
    4547from __future__ import print_function 
    4648 
     
    5153 
    5254from .core import list_models, load_model_info, build_model, HAVE_OPENCL 
    53 from .core import call_kernel, call_ER, call_VR 
     55from .details import dispersion_mesh 
     56from .direct_model import call_kernel, get_weights 
    5457from .exception import annotate_exception 
    5558 
    56 #TODO: rename to tests so that tab completion works better for models directory 
     59 
     60def call_ER(model_info, values): 
     61    """ 
     62    Call the model ER function using *values*. *model_info* is either 
     63    *model.info* if you have a loaded model, or *kernel.info* if you 
     64    have a model kernel prepared for evaluation. 
     65    """ 
     66    if model_info.ER is None: 
     67        return 1.0 
     68    else: 
     69        vol_pars = [get_weights(parameter, values) 
     70                    for parameter in model_info.parameters.call_parameters 
     71                    if parameter.type == 'volume'] 
     72        value, weight = dispersion_mesh(vol_pars) 
     73        individual_radii = model_info.ER(*value) 
     74        return np.sum(weight*individual_radii) / np.sum(weight) 
     75 
     76def call_VR(model_info, values): 
     77    """ 
     78    Call the model VR function using *pars*. 
     79    *info* is either *model.info* if you have a loaded model, or *kernel.info* 
     80    if you have a model kernel prepared for evaluation. 
     81    """ 
     82    if model_info.VR is None: 
     83        return 1.0 
     84    else: 
     85        vol_pars = [get_weights(parameter, values) 
     86                    for parameter in model_info.parameters.call_parameters 
     87                    if parameter.type == 'volume'] 
     88        value, weight = dispersion_mesh(vol_pars) 
     89        whole, part = model_info.VR(*value) 
     90        return np.sum(weight*part)/np.sum(weight*whole) 
     91 
    5792 
    5893def make_suite(loaders, models): 
     
    86121        # don't try to call cl kernel since it will not be 
    87122        # available in some environmentes. 
    88         is_py = callable(model_info['Iq']) 
     123        is_py = callable(model_info.Iq) 
    89124 
    90125        if is_py:  # kernel implemented in python 
     
    151186                ] 
    152187 
    153             tests = self.info['tests'] 
     188            tests = self.info.tests 
    154189            try: 
    155190                model = build_model(self.info, dtype=self.dtype, 
  • sasmodels/modelinfo.py

    r6e7ff6d r6d6508e  
     1from copy import copy 
     2from os.path import abspath, basename, splitext 
    13 
    24import numpy as np 
    35 
    4 # TODO: turn ModelInfo into a proper class 
    5 ModelInfo = dict 
     6from .details import mono_details 
    67 
    78MAX_PD = 4 
     
    116117 
    117118 
    118 def set_demo(model_info, demo): 
    119     """ 
    120     Assign demo parameters to model_info['demo'] 
    121  
    122     If demo is not defined, then use defaults. 
    123  
    124     If demo is defined, override defaults with value from demo. 
    125  
    126     If demo references vector fields, such as thickness[n], then support 
     119def make_demo_pars(partable, demo): 
     120    """ 
     121    Create demo parameter set from key-value pairs. 
     122 
     123    *demo* are the key-value pairs to use for the demo parameters.  Any 
     124    parameters not specified in *demo* are set from the *partable* defaults. 
     125 
     126    If *demo* references vector fields, such as thickness[n], then support 
    127127    different ways of assigning the demo values, including assigning a 
    128128    specific value (e.g., thickness3=50.0), assigning a new value to all 
    129129    (e.g., thickness=50.0) or assigning values using list notation. 
    130130    """ 
    131     partable = model_info['parameters'] 
    132131    if demo is None: 
    133132        result = partable.defaults 
     
    157156            result.update(demo) 
    158157 
    159     model_info['demo'] = result 
     158    return result 
     159 
     160def prefix_parameter(par, prefix): 
     161    """ 
     162    Return a copy of the parameter with its name prefixed. 
     163    """ 
     164    new_par = copy(par) 
     165    new_par.name = prefix + par.name 
     166    new_par.id = prefix + par.id 
     167 
     168def suffix_parameter(par, suffix): 
     169    """ 
     170    Return a copy of the parameter with its name prefixed. 
     171    """ 
     172    new_par = copy(par) 
     173    # If name has the form x[n], replace with x_suffix[n] 
     174    new_par.name = par.id + suffix + par.name[len(par.id):] 
     175    new_par.id = par.id + suffix 
    160176 
    161177class Parameter(object): 
     
    517533    return isinstance(x, str) 
    518534 
     535def make_model_info(kernel_module): 
     536    info = ModelInfo() 
     537    #print("make parameter table", kernel_module.parameters) 
     538    parameters = make_parameter_table(kernel_module.parameters) 
     539    demo = make_demo_pars(parameters, getattr(kernel_module, 'demo', None)) 
     540    filename = abspath(kernel_module.__file__) 
     541    kernel_id = splitext(basename(filename))[0] 
     542    name = getattr(kernel_module, 'name', None) 
     543    if name is None: 
     544        name = " ".join(w.capitalize() for w in kernel_id.split('_')) 
     545 
     546    info.id = kernel_id  # string used to load the kernel 
     547    info.filename = abspath(kernel_module.__file__) 
     548    info.name = name 
     549    info.title = getattr(kernel_module, 'title', name+" model") 
     550    info.description = getattr(kernel_module, 'description', 'no description') 
     551    info.parameters = parameters 
     552    info.demo = demo 
     553    info.composition = None 
     554    info.docs = kernel_module.__doc__ 
     555    info.category = getattr(kernel_module, 'category', None) 
     556    info.single = getattr(kernel_module, 'single', True) 
     557    info.structure_factor = getattr(kernel_module, 'structure_factor', False) 
     558    info.profile_axes = getattr(kernel_module, 'profile_axes', ['x','y']) 
     559    info.variant_info = getattr(kernel_module, 'invariant_info', None) 
     560    info.demo = getattr(kernel_module, 'demo', None) 
     561    info.source = getattr(kernel_module, 'source', []) 
     562    info.tests = getattr(kernel_module, 'tests', []) 
     563    info.ER = getattr(kernel_module, 'ER', None) 
     564    info.VR = getattr(kernel_module, 'VR', None) 
     565    info.form_volume = getattr(kernel_module, 'form_volume', None) 
     566    info.Iq = getattr(kernel_module, 'Iq', None) 
     567    info.Iqxy = getattr(kernel_module, 'Iqxy', None) 
     568    info.profile = getattr(kernel_module, 'profile', None) 
     569    info.sesans = getattr(kernel_module, 'sesans', None) 
     570 
     571    # Precalculate the monodisperse parameter details 
     572    info.mono_details = mono_details(info) 
     573    return info 
     574 
     575class ModelInfo(object): 
     576    """ 
     577    Interpret the model definition file, categorizing the parameters. 
     578 
     579    The module can be loaded with a normal python import statement if you 
     580    know which module you need, or with __import__('sasmodels.model.'+name) 
     581    if the name is in a string. 
     582 
     583    The *model_info* structure contains the following fields: 
     584 
     585    * *id* is the id of the kernel 
     586    * *name* is the display name of the kernel 
     587    * *filename* is the full path to the module defining the file (if any) 
     588    * *title* is a short description of the kernel 
     589    * *description* is a long description of the kernel (this doesn't seem 
     590      very useful since the Help button on the model page brings you directly 
     591      to the documentation page) 
     592    * *docs* is the docstring from the module.  Use :func:`make_doc` to 
     593    * *category* specifies the model location in the docs 
     594    * *parameters* is the model parameter table 
     595    * *single* is True if the model allows single precision 
     596    * *structure_factor* is True if the model is useable in a product 
     597    * *variant_info* contains the information required to select between 
     598      model variants (e.g., the list of cases) or is None if there are no 
     599      model variants 
     600    * *par_type* categorizes the model parameters. See 
     601      :func:`categorize_parameters` for details. 
     602    * *demo* contains the *{parameter: value}* map used in compare (and maybe 
     603      for the demo plot, if plots aren't set up to use the default values). 
     604      If *demo* is not given in the file, then the default values will be used. 
     605    * *tests* is a set of tests that must pass 
     606    * *source* is the list of library files to include in the C model build 
     607    * *Iq*, *Iqxy*, *form_volume*, *ER*, *VR* and *sesans* are python functions 
     608      implementing the kernel for the module, or None if they are not 
     609      defined in python 
     610    * *composition* is None if the model is independent, otherwise it is a 
     611      tuple with composition type ('product' or 'mixture') and a list of 
     612      *model_info* blocks for the composition objects.  This allows us to 
     613      build complete product and mixture models from just the info. 
     614    """ 
     615    id = None 
     616    filename = None 
     617    name = None 
     618    title = None 
     619    description = None 
     620    parameters = None 
     621    demo = None 
     622    composition = None 
     623    docs = None 
     624    category = None 
     625    single = None 
     626    structure_factor = None 
     627    profile_axes = None 
     628    variant_info = None 
     629    demo = None 
     630    source = None 
     631    tests = None 
     632    ER = None 
     633    VR = None 
     634    form_volume = None 
     635    Iq = None 
     636    Iqxy = None 
     637    profile = None 
     638    sesans = None 
     639    mono_details = None 
     640 
     641    def __init__(self): 
     642        pass 
     643 
     644 
  • sasmodels/product.py

    rea05c87 r6d6508e  
    1313import numpy as np 
    1414 
    15 from .core import call_ER_VR 
     15from .details import dispersion_mesh 
     16from .modelinfo import suffix_parameter, ParameterTable, Parameter, ModelInfo 
    1617 
    17 SCALE=0 
    18 BACKGROUND=1 
    19 RADIUS_EFFECTIVE=2 
    20 VOLFRACTION=3 
     18# TODO: make estimates available to constraints 
     19#ESTIMATED_PARAMETERS = [ 
     20#    ["est_effect_radius", "A", 0.0, [0, np.inf], "", "Estimated effective radius"], 
     21#    ["est_volume_ratio", "", 1.0, [0, np.inf], "", "Estimated volume ratio"], 
     22#] 
    2123 
    2224# TODO: core_shell_sphere model has suppressed the volume ratio calculation 
     
    2628    Create info block for product model. 
    2729    """ 
    28     p_id, p_name, p_pars = p_info['id'], p_info['name'], p_info['parameters'] 
    29     s_id, s_name, s_pars = s_info['id'], s_info['name'], s_info['parameters'] 
    30     # We require models to start with scale and background 
    31     assert s_pars[SCALE].name == 'scale' 
    32     assert s_pars[BACKGROUND].name == 'background' 
    33     # We require structure factors to start with effect radius and volfraction 
    34     assert s_pars[RADIUS_EFFECTIVE].name == 'radius_effective' 
    35     assert s_pars[VOLFRACTION].name == 'volfraction' 
    36     # Combine the parameter sets.  We are skipping the first three 
    37     # parameters of S since scale, background are defined in P and 
    38     # effect_radius is set from P.ER(). 
    39     pars = p_pars + s_pars[3:] 
    40     # check for duplicates; can't use assertion since they may never be checked 
    41     if len(set(p.name for p in pars)) != len(pars): 
    42         raise ValueError("Duplicate parameters in %s and %s"%(p_id)) 
    43     model_info = {} 
    44     model_info['id'] = '*'.join((p_id, s_id)) 
    45     model_info['name'] = ' X '.join((p_name, s_name)) 
    46     model_info['filename'] = None 
    47     model_info['title'] = 'Product of %s and structure factor %s'%(p_name, s_name) 
    48     model_info['description'] = model_info['title'] 
    49     model_info['docs'] = model_info['title'] 
    50     model_info['category'] = "custom" 
    51     model_info['parameters'] = pars 
    52     #model_info['single'] = p_info['single'] and s_info['single'] 
    53     model_info['structure_factor'] = False 
    54     model_info['variant_info'] = None 
    55     #model_info['tests'] = [] 
    56     #model_info['source'] = [] 
     30    p_id, p_name, p_partable = p_info.id, p_info.name, p_info.parameters 
     31    s_id, s_name, s_partable = s_info.id, s_info.name, s_info.parameters 
     32    p_set = set(p.id for p in p_partable) 
     33    s_set = set(p.id for p in s_partable) 
     34 
     35    if p_set & s_set: 
     36        # there is some overlap between the parameter names; tag the 
     37        # overlapping S parameters with name_S 
     38        s_pars = [(suffix_parameter(par, "_S") if par.id in p_set else par) 
     39                  for par in s_partable.kernel_parameters] 
     40        pars = p_partable.kernel_parameters + s_pars 
     41    else: 
     42        pars= p_partable.kernel_parameters + s_partable.kernel_parameters 
     43 
     44    model_info = ModelInfo() 
     45    model_info.id = '*'.join((p_id, s_id)) 
     46    model_info.name = ' X '.join((p_name, s_name)) 
     47    model_info.filename = None 
     48    model_info.title = 'Product of %s and %s'%(p_name, s_name) 
     49    model_info.description = model_info.title 
     50    model_info.docs = model_info.title 
     51    model_info.category = "custom" 
     52    model_info.parameters = ParameterTable(pars) 
     53    #model_info.single = p_info.single and s_info.single 
     54    model_info.structure_factor = False 
     55    model_info.variant_info = None 
     56    #model_info.tests = [] 
     57    #model_info.source = [] 
    5758    # Iq, Iqxy, form_volume, ER, VR and sesans 
    58     model_info['composition'] = ('product', [p_info, s_info]) 
     59    model_info.composition = ('product', [p_info, s_info]) 
    5960    return model_info 
    6061 
     
    8687class ProductKernel(object): 
    8788    def __init__(self, model_info, p_kernel, s_kernel): 
    88         dim = '2d' if p_kernel.q_input.is_2d else '1d' 
    89  
    90         # Need to know if we want 2D and magnetic parameters when constructing 
    91         # a parameter map. 
    92         par_map = {} 
    93         p_info = p_kernel.info['par_type'] 
    94         s_info = s_kernel.info['par_type'] 
    95         vol_pars = set(p_info['volume']) 
    96         if dim == '2d': 
    97             num_p_fixed = len(p_info['fixed-2d']) 
    98             num_p_pd = len(p_info['pd-2d']) 
    99             num_s_fixed = len(s_info['fixed-2d']) 
    100             num_s_pd = len(s_info['pd-2d']) - 1 # exclude effect_radius 
    101             # volume parameters are amongst the pd pars for P, not S 
    102             vol_par_idx = [k for k,v in enumerate(p_info['pd-2d']) 
    103                            if v in vol_pars] 
    104         else: 
    105             num_p_fixed = len(p_info['fixed-1d']) 
    106             num_p_pd = len(p_info['pd-1d']) 
    107             num_s_fixed = len(s_info['fixed-1d']) 
    108             num_s_pd = len(s_info['pd-1d']) - 1  # exclude effect_radius 
    109             # volume parameters are amongst the pd pars for P, not S 
    110             vol_par_idx = [k for k,v in enumerate(p_info['pd-1d']) 
    111                            if v in vol_pars] 
    112  
    113         start = 0 
    114         par_map['p_fixed'] = np.arange(start, start+num_p_fixed) 
    115         # User doesn't set scale, background or effect_radius for S in P*S, 
    116         # so borrow values from end of p_fixed.  This makes volfraction the 
    117         # first S parameter. 
    118         start += num_p_fixed 
    119         par_map['s_fixed'] = np.hstack(([start,start], 
    120                                         np.arange(start, start+num_s_fixed-2))) 
    121         par_map['volfraction'] = num_p_fixed 
    122         start += num_s_fixed-2 
    123         # vol pars offset from the start of pd pars 
    124         par_map['vol_pars'] = [start+k for k in vol_par_idx] 
    125         par_map['p_pd'] = np.arange(start, start+num_p_pd) 
    126         start += num_p_pd-1 
    127         par_map['s_pd'] = np.hstack((start, 
    128                                      np.arange(start, start+num_s_pd-1))) 
    129  
    130         self.fixed_pars = model_info['partype']['fixed-' + dim] 
    131         self.pd_pars = model_info['partype']['pd-' + dim] 
    13289        self.info = model_info 
    13390        self.p_kernel = p_kernel 
    13491        self.s_kernel = s_kernel 
    135         self.par_map = par_map 
    13692 
    137     def __call__(self, fixed_pars, pd_pars, cutoff=1e-5): 
    138         pars = fixed_pars + pd_pars 
    139         scale = pars[SCALE] 
    140         background = pars[BACKGROUND] 
    141         s_volfraction = pars[self.par_map['volfraction']] 
    142         p_fixed = [pars[k] for k in self.par_map['p_fixed']] 
    143         s_fixed = [pars[k] for k in self.par_map['s_fixed']] 
    144         p_pd = [pars[k] for k in self.par_map['p_pd']] 
    145         s_pd = [pars[k] for k in self.par_map['s_pd']] 
    146         vol_pars = [pars[k] for k in self.par_map['vol_pars']] 
    147  
     93    def __call__(self, details, weights, values, cutoff): 
    14894        effect_radius, vol_ratio = call_ER_VR(self.p_kernel.info, vol_pars) 
    14995 
     
    155101        s_pd[0] = [effect_radius], [1.0] 
    156102 
    157         p_res = self.p_kernel(p_fixed, p_pd, cutoff) 
    158         s_res = self.s_kernel(s_fixed, s_pd, cutoff) 
     103        p_res = self.p_kernel(p_details, p_weights, p_values, cutoff) 
     104        s_res = self.s_kernel(s_details, s_weights, s_values, cutoff) 
    159105        #print s_fixed, s_pd, p_fixed, p_pd 
    160106 
     
    165111        self.q_kernel.release() 
    166112 
     113def call_ER_VR(model_info, vol_pars): 
     114    """ 
     115    Return effect radius and volume ratio for the model. 
     116    """ 
     117    value, weight = dispersion_mesh(vol_pars) 
     118 
     119    individual_radii = model_info.ER(*value) if model_info.ER else 1.0 
     120    whole, part = model_info.VR(*value) if model_info.VR else (1.0, 1.0) 
     121 
     122    effect_radius = np.sum(weight*individual_radii) / np.sum(weight) 
     123    volume_ratio = np.sum(weight*part)/np.sum(weight*whole) 
     124    return effect_radius, volume_ratio 
  • sasmodels/resolution.py

    rd2bb604 r6d6508e  
    476476    *pars* are the parameter values to use when evaluating. 
    477477    """ 
    478     from sasmodels import core 
     478    from sasmodels import direct_model 
    479479    kernel = form.make_kernel([q]) 
    480     theory = core.call_kernel(kernel, pars) 
     480    theory = direct_model.call_kernel(kernel, pars) 
    481481    kernel.release() 
    482482    return theory 
     
    502502    from scipy.integrate import romberg 
    503503 
    504     par_set = set([p.name for p in form.info['parameters'].call_parameters]) 
     504    par_set = set([p.name for p in form.info.parameters.call_parameters]) 
    505505    if any(k not in par_set for k in pars.keys()): 
    506506        extra = set(pars.keys()) - par_set 
     
    557557    from scipy.integrate import romberg 
    558558 
    559     par_set = set([p.name for p in form.info['parameters'].call_parameters]) 
     559    par_set = set([p.name for p in form.info.parameters.call_parameters]) 
    560560    if any(k not in par_set for k in pars.keys()): 
    561561        extra = set(pars.keys()) - par_set 
     
    694694 
    695695    def _eval_sphere(self, pars, resolution): 
    696         from sasmodels import core 
     696        from sasmodels import direct_model 
    697697        kernel = self.model.make_kernel([resolution.q_calc]) 
    698         theory = core.call_kernel(kernel, pars) 
     698        theory = direct_model.call_kernel(kernel, pars) 
    699699        result = resolution.apply(theory) 
    700700        kernel.release() 
     
    10451045    import sys 
    10461046    from sasmodels import core 
     1047    from sasmodels import direct_model 
    10471048    name = sys.argv[1] if len(sys.argv) > 1 else 'cylinder' 
    10481049 
     
    10651066 
    10661067    kernel = model.make_kernel([resolution.q_calc]) 
    1067     theory = core.call_kernel(kernel, pars) 
     1068    theory = direct_model.call_kernel(kernel, pars) 
    10681069    Iq = resolution.apply(theory) 
    10691070 
  • sasmodels/sasview_model.py

    ree8f734 r6d6508e  
    2727from . import generate 
    2828from . import weights 
     29from . import details 
     30from . import modelinfo 
    2931 
    3032def load_standard_models(): 
     
    4951    """ 
    5052    kernel_module = custom.load_custom_kernel_module(path) 
    51     model_info = generate.make_model_info(kernel_module) 
     53    model_info = modelinfo.make_model_info(kernel_module) 
    5254    return _make_model_from_info(model_info) 
    5355 
     
    6264    """ 
    6365    kernel_module = generate.load_kernel_module(name) 
    64     model_info = generate.make_model_info(kernel_module) 
     66    model_info = modelinfo.make_model_info(kernel_module) 
    6567    return _make_model_from_info(model_info) 
    6668 
     
    7375        SasviewModel.__init__(self) 
    7476    attrs = dict(__init__=__init__, _model_info=model_info) 
    75     ConstructedModel = type(model_info['name'], (SasviewModel,), attrs) 
     77    ConstructedModel = type(model_info.name, (SasviewModel,), attrs) 
    7678    return ConstructedModel 
    7779 
     
    8587        self._model = None 
    8688        model_info = self._model_info 
    87         parameters = model_info['parameters'] 
    88  
    89         self.name = model_info['name'] 
    90         self.description = model_info['description'] 
     89        parameters = model_info.parameters 
     90 
     91        self.name = model_info.name 
     92        self.description = model_info.description 
    9193        self.category = None 
    9294        #self.is_multifunc = False 
    9395        for p in parameters.kernel_parameters: 
    9496            if p.is_control: 
    95                 profile_axes = model_info['profile_axes'] 
     97                profile_axes = model_info.profile_axes 
    9698                self.multiplicity_info = [ 
    9799                    p.limits[1], p.name, p.choices, profile_axes[0] 
     
    132134 
    133135        ## independent parameter name and unit [string] 
    134         self.input_name = model_info.get("input_name", "Q") 
    135         self.input_unit = model_info.get("input_unit", "A^{-1}") 
    136         self.output_name = model_info.get("output_name", "Intensity") 
    137         self.output_unit = model_info.get("output_unit", "cm^{-1}") 
     136        self.input_name = "Q", #model_info.get("input_name", "Q") 
     137        self.input_unit = "A^{-1}" #model_info.get("input_unit", "A^{-1}") 
     138        self.output_name = "Intensity" #model_info.get("output_name", "Intensity") 
     139        self.output_unit = "cm^{-1}" #model_info.get("output_unit", "cm^{-1}") 
    138140 
    139141        ## _persistency_dict is used by sas.perspectives.fitting.basepage 
     
    248250        # TODO: fix test so that parameter order doesn't matter 
    249251        ret = ['%s.%s' % (p.name.lower(), ext) 
    250                for p in self._model_info['parameters'].user_parameters() 
     252               for p in self._model_info.parameters.user_parameters() 
    251253               for ext in ('npts', 'nsigmas', 'width') 
    252254               if p.polydisperse] 
     
    323325            # Check whether we have a list of ndarrays [qx,qy] 
    324326            qx, qy = qdist 
    325             if not self._model_info['parameters'].has_2d: 
     327            if not self._model_info.parameters.has_2d: 
    326328                return self.calculate_Iq(np.sqrt(qx ** 2 + qy ** 2)) 
    327329            else: 
     
    350352        kernel = self._model.make_kernel(q_vectors) 
    351353        pairs = [self._get_weights(p) 
    352                  for p in self._model_info['parameters'].call_parameters] 
    353         details, weights, values = core.build_details(kernel, pairs) 
    354         result = kernel(details, weights, values, cutoff=self.cutoff) 
     354                 for p in self._model_info.parameters.call_parameters] 
     355        call_details, weights, values = details.build_details(kernel, pairs) 
     356        result = kernel(call_details, weights, values, cutoff=self.cutoff) 
    355357        kernel.q_input.release() 
    356358        kernel.release() 
     
    363365        :return: the value of the effective radius 
    364366        """ 
    365         ER = self._model_info.get('ER', None) 
    366         if ER is None: 
     367        if model_info.ER is None: 
    367368            return 1.0 
    368369        else: 
    369370            values, weights = self._dispersion_mesh() 
    370             fv = ER(*values) 
     371            fv = model_info.ER(*values) 
    371372            #print(values[0].shape, weights.shape, fv.shape) 
    372373            return np.sum(weights * fv) / np.sum(weights) 
     
    378379        :return: the value of the volf ratio 
    379380        """ 
    380         VR = self._model_info.get('VR', None) 
    381         if VR is None: 
     381        if model_info.VR is None: 
    382382            return 1.0 
    383383        else: 
    384384            values, weights = self._dispersion_mesh() 
    385             whole, part = VR(*values) 
     385            whole, part = model_info.VR(*values) 
    386386            return np.sum(weights * part) / np.sum(weights * whole) 
    387387 
     
    422422        parameter set in the vector. 
    423423        """ 
    424         pars = self._model_info['partype']['volume'] 
    425         return core.dispersion_mesh([self._get_weights(p) for p in pars]) 
     424        pars = self._model_info.partype['volume'] 
     425        return details.dispersion_mesh([self._get_weights(p) for p in pars]) 
    426426 
    427427    def _get_weights(self, par): 
Note: See TracChangeset for help on using the changeset viewer.