Changeset 17bbadd in sasmodels


Ignore:
Timestamp:
Mar 15, 2016 12:47:12 PM (7 years ago)
Author:
Paul Kienzle <pkienzle@…>
Branches:
master, core_shell_microgels, costrafo411, magnetic_model, release_v0.94, release_v0.95, ticket-1257-vesicle-product, ticket_1156, ticket_1265_superball, ticket_822_more_unit_tests
Children:
754e27b
Parents:
5ceb7d0
Message:

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

Location:
sasmodels
Files:
1 added
13 edited

Legend:

Unmodified
Added
Removed
  • sasmodels/compare.py

    r6869ceb r17bbadd  
    3838from . import core 
    3939from . import kerneldll 
    40 from . import generate 
     40from . import product 
    4141from .data import plot_theory, empty_data1D, empty_data2D 
    4242from .direct_model import DirectModel 
    43 from .convert import revert_model, constrain_new_to_old 
     43from .convert import revert_pars, constrain_new_to_old 
    4444 
    4545USAGE = """ 
     
    264264    return pars 
    265265 
    266 def constrain_pars(model_definition, pars): 
     266def constrain_pars(model_info, pars): 
    267267    """ 
    268268    Restrict parameters to valid values. 
     
    272272    cylinder radius in this case). 
    273273    """ 
    274     name = model_definition.name 
     274    name = model_info['id'] 
     275    # if it is a product model, then just look at the form factor since 
     276    # none of the structure factors need any constraints. 
     277    if '*' in name: 
     278        name = name.split('*')[0] 
     279 
    275280    if name == 'capped_cylinder' and pars['cap_radius'] < pars['radius']: 
    276281        pars['radius'], pars['cap_radius'] = pars['cap_radius'], pars['radius'] 
     
    340345    return pars 
    341346 
    342 def eval_sasview(model_definition, data): 
     347def eval_sasview(model_info, data): 
    343348    """ 
    344349    Return a model calculator using the SasView fitting engine. 
     
    349354    from sas.models.qsmearing import smear_selection 
    350355 
    351     # convert model parameters from sasmodel form to sasview form 
    352     #print("old",sorted(pars.items())) 
    353     modelname, _ = revert_model(model_definition, {}) 
    354     #print("new",sorted(_pars.items())) 
    355     sas = __import__('sas.models.'+modelname) 
    356     ModelClass = getattr(getattr(sas.models, modelname, None), modelname, None) 
    357     if ModelClass is None: 
    358         raise ValueError("could not find model %r in sas.models"%modelname) 
    359     model = ModelClass() 
     356    def get_model(name): 
     357        #print("new",sorted(_pars.items())) 
     358        sas = __import__('sas.models.' + name) 
     359        ModelClass = getattr(getattr(sas.models, name, None), name, None) 
     360        if ModelClass is None: 
     361            raise ValueError("could not find model %r in sas.models"%name) 
     362        return ModelClass() 
     363 
     364    # grab the sasview model, or create it if it is a product model 
     365    if model_info['composition']: 
     366        composition_type, parts = model_info['composition'] 
     367        if composition_type == 'product': 
     368            from sas.models import MultiplicationModel 
     369            P, S = [get_model(p) for p in model_info['oldname']] 
     370            model = MultiplicationModel(P, S) 
     371        else: 
     372            raise ValueError("mixture models not handled yet") 
     373    else: 
     374        model = get_model(model_info['oldname']) 
     375 
     376    # build a smearer with which to call the model, if necessary 
    360377    smearer = smear_selection(data, model=model) 
    361  
    362378    if hasattr(data, 'qx_data'): 
    363379        q = np.sqrt(data.qx_data**2 + data.qy_data**2) 
     
    382398        """ 
    383399        # paying for parameter conversion each time to keep life simple, if not fast 
    384         _, pars = revert_model(model_definition, pars) 
     400        pars = revert_pars(model_info, pars) 
    385401        for k, v in pars.items(): 
    386402            parts = k.split('.')  # polydispersity components 
     
    405421    'longdouble': '128', 
    406422} 
    407 def eval_opencl(model_definition, data, dtype='single', cutoff=0.): 
     423def eval_opencl(model_info, data, dtype='single', cutoff=0.): 
    408424    """ 
    409425    Return a model calculator using the OpenCL calculation engine. 
    410426    """ 
    411     try: 
    412         model = core.load_model(model_definition, dtype=dtype, platform="ocl") 
    413     except Exception as exc: 
    414         print(exc) 
    415         print("... trying again with single precision") 
    416         dtype = 'single' 
    417         model = core.load_model(model_definition, dtype=dtype, platform="ocl") 
     427    def builder(model_info): 
     428        try: 
     429            return core.build_model(model_info, dtype=dtype, platform="ocl") 
     430        except Exception as exc: 
     431            print(exc) 
     432            print("... trying again with single precision") 
     433            dtype = 'single' 
     434            return core.build_model(model_info, dtype=dtype, platform="ocl") 
     435    if model_info['composition']: 
     436        composition_type, parts = model_info['composition'] 
     437        if composition_type == 'product': 
     438            P, S = [builder(p) for p in parts] 
     439            model = product.ProductModel(P, S) 
     440        else: 
     441            raise ValueError("mixture models not handled yet") 
     442    else: 
     443        model = builder(model_info) 
    418444    calculator = DirectModel(data, model, cutoff=cutoff) 
    419445    calculator.engine = "OCL%s"%DTYPE_MAP[dtype] 
    420446    return calculator 
    421447 
    422 def eval_ctypes(model_definition, data, dtype='double', cutoff=0.): 
     448def eval_ctypes(model_info, data, dtype='double', cutoff=0.): 
    423449    """ 
    424450    Return a model calculator using the DLL calculation engine. 
     
    426452    if dtype == 'quad': 
    427453        dtype = 'longdouble' 
    428     model = core.load_model(model_definition, dtype=dtype, platform="dll") 
     454    def builder(model_info): 
     455        return core.build_model(model_info, dtype=dtype, platform="dll") 
     456 
     457    if model_info['composition']: 
     458        composition_type, parts = model_info['composition'] 
     459        if composition_type == 'product': 
     460            P, S = [builder(p) for p in parts] 
     461            model = product.ProductModel(P, S) 
     462        else: 
     463            raise ValueError("mixture models not handled yet") 
     464    else: 
     465        model = builder(model_info) 
    429466    calculator = DirectModel(data, model, cutoff=cutoff) 
    430467    calculator.engine = "OMP%s"%DTYPE_MAP[dtype] 
     
    470507    return data, index 
    471508 
    472 def make_engine(model_definition, data, dtype, cutoff): 
     509def make_engine(model_info, data, dtype, cutoff): 
    473510    """ 
    474511    Generate the appropriate calculation engine for the given datatype. 
     
    478515    """ 
    479516    if dtype == 'sasview': 
    480         return eval_sasview(model_definition, data) 
     517        return eval_sasview(model_info, data) 
    481518    elif dtype.endswith('!'): 
    482         return eval_ctypes(model_definition, data, dtype=dtype[:-1], 
    483                            cutoff=cutoff) 
    484     else: 
    485         return eval_opencl(model_definition, data, dtype=dtype, 
    486                            cutoff=cutoff) 
     519        return eval_ctypes(model_info, data, dtype=dtype[:-1], cutoff=cutoff) 
     520    else: 
     521        return eval_opencl(model_info, data, dtype=dtype, cutoff=cutoff) 
    487522 
    488523def compare(opts, limits=None): 
     
    642677 
    643678 
    644 def get_demo_pars(model_definition): 
     679def get_demo_pars(model_info): 
    645680    """ 
    646681    Extract demo parameters from the model definition. 
    647682    """ 
    648     info = generate.make_info(model_definition) 
    649683    # Get the default values for the parameters 
    650     pars = dict((p[0], p[2]) for p in info['parameters']) 
     684    pars = dict((p[0], p[2]) for p in model_info['parameters']) 
    651685 
    652686    # Fill in default values for the polydispersity parameters 
    653     for p in info['parameters']: 
     687    for p in model_info['parameters']: 
    654688        if p[4] in ('volume', 'orientation'): 
    655689            pars[p[0]+'_pd'] = 0.0 
     
    659693 
    660694    # Plug in values given in demo 
    661     pars.update(info['demo']) 
     695    pars.update(model_info['demo']) 
    662696    return pars 
     697 
    663698 
    664699def parse_opts(): 
     
    679714        print(columnize(MODELS, indent="  ")) 
    680715        sys.exit(1) 
    681  
    682     name = args[0] 
    683     try: 
    684         model_definition = core.load_model_definition(name) 
    685     except ImportError, exc: 
    686         print(str(exc)) 
    687         print("Use one of:\n    " + models) 
    688         sys.exit(1) 
    689716    if len(args) > 3: 
    690717        print("expected parameters: model N1 N2") 
     718 
     719    def load_model(name): 
     720        try: 
     721            model_info = core.load_model_info(name) 
     722        except ImportError, exc: 
     723            print(str(exc)) 
     724            print("Use one of:\n    " + models) 
     725            sys.exit(1) 
     726        return model_info 
     727 
     728    name = args[0] 
     729    if '*' in name: 
     730        parts = [load_model(k) for k in name.split('*')] 
     731        model_info = product.make_product_info(*parts) 
     732    else: 
     733        model_info = load_model(name) 
    691734 
    692735    invalid = [o[1:] for o in flags 
     
    770813    # Get demo parameters from model definition, or use default parameters 
    771814    # if model does not define demo parameters 
    772     pars = get_demo_pars(model_definition) 
     815    pars = get_demo_pars(model_info) 
    773816 
    774817    # Fill in parameters given on the command line 
     
    791834        pars = suppress_pd(pars) 
    792835    pars.update(presets)  # set value after random to control value 
    793     constrain_pars(model_definition, pars) 
    794     constrain_new_to_old(model_definition, pars) 
     836    constrain_pars(model_info, pars) 
     837    constrain_new_to_old(model_info, pars) 
    795838    if opts['show_pars']: 
    796839        print(str(parlist(pars))) 
     
    799842    data, _ = make_data(opts) 
    800843    if n1: 
    801         base = make_engine(model_definition, data, engines[0], opts['cutoff']) 
     844        base = make_engine(model_info, data, engines[0], opts['cutoff']) 
    802845    else: 
    803846        base = None 
    804847    if n2: 
    805         comp = make_engine(model_definition, data, engines[1], opts['cutoff']) 
     848        comp = make_engine(model_info, data, engines[1], opts['cutoff']) 
    806849    else: 
    807850        comp = None 
     
    811854    opts.update({ 
    812855        'name'      : name, 
    813         'def'       : model_definition, 
     856        'def'       : model_info, 
    814857        'n1'        : n1, 
    815858        'n2'        : n2, 
     
    854897        config_matplotlib() 
    855898        self.opts = opts 
    856         info = generate.make_info(opts['def']) 
    857         pars, pd_types = bumps_model.create_parameters(info, **opts['pars']) 
     899        model_info = opts['def'] 
     900        pars, pd_types = bumps_model.create_parameters(model_info, **opts['pars']) 
    858901        if not opts['is2d']: 
    859902            active = [base + ext 
    860                       for base in info['partype']['pd-1d'] 
     903                      for base in model_info['partype']['pd-1d'] 
    861904                      for ext in ['', '_pd', '_pd_n', '_pd_nsigma']] 
    862             active.extend(info['partype']['fixed-1d']) 
     905            active.extend(model_info['partype']['fixed-1d']) 
    863906            for k in active: 
    864907                v = pars[k] 
  • sasmodels/compare_many.py

    r4f2478e r17bbadd  
    101101 
    102102    is_2d = hasattr(data, 'qx_data') 
    103     model_definition = core.load_model_definition(name) 
    104     pars = get_demo_pars(model_definition) 
     103    model_info = core.load_model_info(name) 
     104    pars = get_demo_pars(model_info) 
    105105    header = ('\n"Model","%s","Count","%d","Dimension","%s"' 
    106106              % (name, N, "2D" if is_2d else "1D")) 
     
    109109 
    110110    if is_2d: 
    111         info = generate.make_info(model_definition) 
    112         partype = info['partype'] 
     111        partype = model_info['partype'] 
    113112        if not partype['orientation'] and not partype['magnetic']: 
    114113            print(',"1-D only"') 
     
    150149 
    151150 
    152     calc_base = make_engine(model_definition, data, base, cutoff) 
    153     calc_comp = make_engine(model_definition, data, comp, cutoff) 
     151    calc_base = make_engine(model_info, data, base, cutoff) 
     152    calc_comp = make_engine(model_info, data, comp, cutoff) 
    154153    expected = max(PRECISION[base], PRECISION[comp]) 
    155154 
     
    161160        seed = np.random.randint(1e6) 
    162161        pars_i = randomize_pars(pars, seed) 
    163         constrain_pars(model_definition, pars_i) 
    164         constrain_new_to_old(model_definition, pars_i) 
     162        constrain_pars(model_info['id'], pars_i) 
     163        constrain_new_to_old(model_info['id'], pars_i) 
    165164        if mono: 
    166165            pars_i = suppress_pd(pars_i) 
  • sasmodels/convert.py

    r0d0aee1 r17bbadd  
    44import warnings 
    55 
     6STRUCTURE_FACTORS = [ 
     7    'hardsphere', 
     8    'stickyhardsphere', 
     9    'squarewell', 
     10    'HayterMSAsq' 
     11] 
    612# List of models which SasView versions don't contain the explicit 'scale' argument. 
    713# When converting such a model, please update this list. 
    8 MODELS_WITHOUT_SCALE = [ 
     14MODELS_WITHOUT_SCALE = STRUCTURE_FACTORS + [ 
    915    'teubner_strey', 
    1016    'broad_peak', 
     
    1521    'be_polyelectrolyte', 
    1622    'correlation_length', 
     23    'fractal_core_shell' 
    1724    'binary_hard_sphere', 
    18     'fractal_core_shell' 
    1925] 
    2026 
    2127# List of models which SasView versions don't contain the explicit 'background' argument. 
    2228# When converting such a model, please update this list. 
    23 MODELS_WITHOUT_BACKGROUND = [ 
     29MODELS_WITHOUT_BACKGROUND = STRUCTURE_FACTORS + [ 
    2430    'guinier', 
    2531] 
     
    5258    new model definition end with sld. 
    5359    """ 
    54     return dict((p, (v*1e6 if p.endswith('sld') else v*1e-15 if 'ndensity' in p else v)) 
     60    return dict((p, (v*1e6 if p.endswith('sld') 
     61                     else v*1e-15 if 'ndensity' in p 
     62                     else v)) 
    5563                for p, v in pars.items()) 
    5664 
     
    7078    new model definition end with sld. 
    7179    """ 
    72     return dict((p, (v*1e-6 if p.endswith('sld') else v*1e15 if 'ndensity' in p else v)) 
     80    return dict((p, (v*1e-6 if p.endswith('sld') 
     81                     else v*1e15 if 'ndensity' in p 
     82                     else v)) 
    7383                for p, v in pars.items()) 
    7484 
     
    109119    return newpars 
    110120 
    111 def revert_model(model_definition, pars): 
     121def revert_pars(model_info, pars): 
    112122    """ 
    113123    Convert model from new style parameter names to old style. 
    114124    """ 
    115     mapping = model_definition.oldpars 
    116     oldname = model_definition.oldname 
     125    mapping = model_info['oldpars'] 
    117126    oldpars = _revert_pars(_unscale_sld(pars), mapping) 
    118127 
    119128    # Note: update compare.constrain_pars to match 
    120     name = model_definition.name 
     129    name = model_info['id'] 
    121130    if name in MODELS_WITHOUT_SCALE: 
    122131        if oldpars.pop('scale', 1.0) != 1.0: 
    123132            warnings.warn("parameter scale not used in sasview %s"%name) 
    124     elif name in MODELS_WITHOUT_BACKGROUND: 
     133    if name in MODELS_WITHOUT_BACKGROUND: 
    125134        if oldpars.pop('background', 0.0) != 0.0: 
    126135            warnings.warn("parameter background not used in sasview %s"%name) 
    127     elif getattr(model_definition, 'category', None) == 'structure-factor': 
    128         if oldpars.pop('scale', 1.0) != 1.0: 
    129             warnings.warn("parameter scale not used in sasview %s"%name) 
    130         if oldpars.pop('background', 0.0) != 0.0: 
    131             warnings.warn("parameter background not used in sasview %s"%name) 
    132     elif name == 'pearl_necklace': 
    133         _remove_pd(oldpars, 'num_pearls', name) 
    134         _remove_pd(oldpars, 'thick_string', name) 
    135     elif name == 'core_shell_parallelepiped': 
    136         _remove_pd(oldpars, 'rimA', name) 
    137         _remove_pd(oldpars, 'rimB', name) 
    138         _remove_pd(oldpars, 'rimC', name) 
    139     elif name == 'rpa': 
    140         # convert scattering lengths from femtometers to centimeters 
    141         for p in "La", "Lb", "Lc", "Ld": 
    142             if p in oldpars: oldpars[p] *= 1e-13 
    143136 
    144     return oldname, oldpars 
     137    # If it is a product model P*S, then check the individual forms for special 
     138    # cases.  Note: despite the structure factor alone not having scale or 
     139    # background, the product model does, so this is below the test for 
     140    # models without scale or background. 
     141    namelist = name.split('*') if '*' in name else [name] 
     142    for name in namelist: 
     143        if name == 'pearl_necklace': 
     144            _remove_pd(oldpars, 'num_pearls', name) 
     145            _remove_pd(oldpars, 'thick_string', name) 
     146        elif name == 'core_shell_parallelepiped': 
     147            _remove_pd(oldpars, 'rimA', name) 
     148            _remove_pd(oldpars, 'rimB', name) 
     149            _remove_pd(oldpars, 'rimC', name) 
     150        elif name == 'rpa': 
     151            # convert scattering lengths from femtometers to centimeters 
     152            for p in "La", "Lb", "Lc", "Ld": 
     153                if p in oldpars: oldpars[p] *= 1e-13 
    145154 
    146 def constrain_new_to_old(model_definition, pars): 
     155    return oldpars 
     156 
     157def constrain_new_to_old(model_info, pars): 
    147158    """ 
    148159    Restrict parameter values to those that will match sasview. 
    149160    """ 
     161    name = model_info['id'] 
    150162    # Note: update convert.revert_model to match 
    151     name = model_definition.name 
    152163    if name in MODELS_WITHOUT_SCALE: 
    153164        pars['scale'] = 1 
    154     elif name in MODELS_WITHOUT_BACKGROUND: 
     165    if name in MODELS_WITHOUT_BACKGROUND: 
    155166        pars['background'] = 0 
    156     elif name == 'pearl_necklace': 
    157         pars['string_thickness_pd_n'] = 0 
    158         pars['number_of_pearls_pd_n'] = 0 
    159     elif name == 'line': 
    160         pars['scale'] = 1 
    161         pars['background'] = 0 
    162     elif name == 'rpa': 
    163         pars['case_num'] = int(pars['case_num']) 
    164     elif getattr(model_definition, 'category', None) == 'structure-factor': 
    165         pars['scale'], pars['background'] = 1, 0 
    166167 
     168    # If it is a product model P*S, then check the individual forms for special 
     169    # cases.  Note: despite the structure factor alone not having scale or 
     170    # background, the product model does, so this is below the test for 
     171    # models without scale or background. 
     172    namelist = name.split('*') if '*' in name else [name] 
     173    for name in namelist: 
     174        if name == 'pearl_necklace': 
     175            pars['string_thickness_pd_n'] = 0 
     176            pars['number_of_pearls_pd_n'] = 0 
     177        elif name == 'line': 
     178            pars['scale'] = 1 
     179            pars['background'] = 0 
     180        elif name == 'rpa': 
     181            pars['case_num'] = int(pars['case_num']) 
  • sasmodels/core.py

    rd18582e r17bbadd  
    2121 
    2222__all__ = [ 
    23     "list_models", "load_model_definition", "precompile_dll", 
    24     "load_model", "make_kernel", "call_kernel", "call_ER", "call_VR", 
     23    "list_models", "load_model_info", "precompile_dll", 
     24    "build_model", "make_kernel", "call_kernel", "call_ER_VR", 
    2525] 
    2626 
     
    3535 
    3636 
    37 def load_model_definition(model_name): 
     37def load_model_info(model_name): 
    3838    """ 
    3939    Load a model definition given the model name. 
     
    4343    """ 
    4444    __import__('sasmodels.models.'+model_name) 
    45     model_definition = getattr(models, model_name, None) 
    46     return model_definition 
     45    kernel_module = getattr(models, model_name, None) 
     46    return generate.make_model_info(kernel_module) 
    4747 
    4848 
     
    5151    Precompile the dll for a model. 
    5252 
    53     Returns the path to the compiled model. 
     53    Returns the path to the compiled model, or None if the model is a pure 
     54    python model. 
    5455 
    5556    This can be used when build the windows distribution of sasmodels 
     
    6061    dll path and the allowed floating point precision. 
    6162    """ 
    62     model_definition = load_model_definition(model_name) 
    63     source, info = generate.make(model_definition) 
    64     return kerneldll.make_dll(source, info, dtype=dtype) 
     63    model_info = load_model_info(model_name) 
     64    source = generate.make_source(model_info) 
     65    return kerneldll.make_dll(source, model_info, dtype=dtype) if source else None 
    6566 
    6667 
     
    7374    return True 
    7475 
    75 def load_model(model_definition, dtype=None, platform="ocl"): 
     76def build_model(model_info, dtype=None, platform="ocl"): 
    7677    """ 
    7778    Prepare the model for the default execution platform. 
     
    8081    on the model and the computing platform. 
    8182 
    82     *model_definition* is the python module which defines the model.  If the 
    83     model name is given instead, then :func:`load_model_definition` will be 
    84     called with the model name. 
     83    *model_info* is the model definition structure returned from 
     84    :func:`load_model_info`. 
    8585 
    8686    *dtype* indicates whether the model should use single or double precision 
     
    9393    otherwise it uses the default "ocl". 
    9494    """ 
    95     if isstr(model_definition): 
    96         model_definition = load_model_definition(model_definition) 
     95    source = generate.make_source(model_info) 
    9796    if dtype is None: 
    98         dtype = 'single' if getattr(model_definition, 'single', True) else 'double' 
    99     source, info = generate.make(model_definition) 
    100     if callable(info.get('Iq', None)): 
    101         return kernelpy.PyModel(info) 
     97        dtype = 'single' if model_info['single'] else 'double' 
     98    if callable(model_info.get('Iq', None)): 
     99        return kernelpy.PyModel(model_info) 
    102100 
    103101    ## for debugging: 
     
    107105    ##  4. rerun "python -m sasmodels.direct_model $MODELNAME" 
    108106    ##  5. uncomment open().read() so that source will be regenerated from model 
    109     # open(info['name']+'.c','w').write(source) 
    110     # source = open(info['name']+'.cl','r').read() 
     107    # open(model_info['name']+'.c','w').write(source) 
     108    # source = open(model_info['name']+'.cl','r').read() 
    111109 
    112110    if (platform == "dll" 
    113111            or not HAVE_OPENCL 
    114112            or not kernelcl.environment().has_type(dtype)): 
    115         return kerneldll.load_dll(source, info, dtype) 
     113        return kerneldll.load_dll(source, model_info, dtype) 
    116114    else: 
    117         return kernelcl.GpuModel(source, info, dtype) 
     115        return kernelcl.GpuModel(source, model_info, dtype) 
    118116 
    119117def make_kernel(model, q_vectors): 
     
    123121    return model(q_vectors) 
    124122 
    125 def get_weights(info, pars, name): 
     123def get_weights(model_info, pars, name): 
    126124    """ 
    127125    Generate the distribution for parameter *name* given the parameter values 
     
    131129    from the *pars* dictionary for parameter value and parameter dispersion. 
    132130    """ 
    133     relative = name in info['partype']['pd-rel'] 
    134     limits = info['limits'][name] 
     131    relative = name in model_info['partype']['pd-rel'] 
     132    limits = model_info['limits'][name] 
    135133    disperser = pars.get(name+'_pd_type', 'gaussian') 
    136     value = pars.get(name, info['defaults'][name]) 
     134    value = pars.get(name, model_info['defaults'][name]) 
    137135    npts = pars.get(name+'_pd_n', 0) 
    138136    width = pars.get(name+'_pd', 0.0) 
     
    173171    return kernel(fixed_pars, pd_pars, cutoff=cutoff) 
    174172 
     173def call_ER_VR(model_info, vol_pars): 
     174    """ 
     175    Return effect radius and volume ratio for the model. 
     176 
     177    *info* is either *kernel.info* for *kernel=make_kernel(model,q)* 
     178    or *model.info*. 
     179 
     180    *pars* are the parameters as expected by :func:`call_kernel`. 
     181    """ 
     182    ER = model_info.get('ER', None) 
     183    VR = model_info.get('VR', None) 
     184    value, weight = dispersion_mesh(vol_pars) 
     185 
     186    individual_radii = ER(*value) if ER else 1.0 
     187    whole, part = VR(*value) if VR else (1.0, 1.0) 
     188 
     189    effect_radius = np.sum(weight*individual_radii) / np.sum(weight) 
     190    volume_ratio = np.sum(weight*part)/np.sum(weight*whole) 
     191    return effect_radius, volume_ratio 
     192 
     193 
    175194def call_ER(info, pars): 
    176195    """ 
    177196    Call the model ER function using *pars*. 
    178  
    179197    *info* is either *model.info* if you have a loaded model, or *kernel.info* 
    180198    if you have a model kernel prepared for evaluation. 
     
    194212    """ 
    195213    Call the model VR function using *pars*. 
    196  
    197214    *info* is either *model.info* if you have a loaded model, or *kernel.info* 
    198215    if you have a model kernel prepared for evaluation. 
     
    208225        return np.sum(weight*part)/np.sum(weight*whole) 
    209226 
     227# TODO: remove call_ER, call_VR 
     228 
  • sasmodels/direct_model.py

    rd18582e r17bbadd  
    2525import numpy as np 
    2626 
    27 from .core import load_model_definition, load_model, make_kernel 
    28 from .core import call_kernel, call_ER, call_VR 
     27from .core import make_kernel 
     28from .core import call_kernel, call_ER_VR 
    2929from . import sesans 
    3030from . import resolution 
     
    180180        return self._calc_theory(pars, cutoff=self.cutoff) 
    181181 
    182     def ER(self, **pars): 
    183         """ 
    184         Compute the equivalent radius for the model. 
    185  
    186         Return 0. if not defined. 
    187         """ 
    188         return call_ER(self.model.info, pars) 
    189  
    190     def VR(self, **pars): 
    191         """ 
    192         Compute the equivalent volume for the model, including polydispersity 
    193         effects. 
    194  
    195         Return 1. if not defined. 
    196         """ 
    197         return call_VR(self.model.info, pars) 
     182    def ER_VR(self, **pars): 
     183        """ 
     184        Compute the equivalent radius and volume ratio for the model. 
     185        """ 
     186        return call_ER_VR(self.model.info, pars) 
    198187 
    199188    def simulate_data(self, noise=None, **pars): 
     
    210199    import sys 
    211200    from .data import empty_data1D, empty_data2D 
     201    from .core import load_model_info, build_model 
    212202 
    213203    if len(sys.argv) < 3: 
     
    216206    model_name = sys.argv[1] 
    217207    call = sys.argv[2].upper() 
    218     if call not in ("ER", "VR"): 
     208    if call != "ER_VR": 
    219209        try: 
    220210            values = [float(v) for v in call.split(',')] 
     
    233223        data = empty_data1D([0.001])  # Data not used in ER/VR 
    234224 
    235     model_definition = load_model_definition(model_name) 
    236     model = load_model(model_definition) 
     225    model_info = load_model_info(model_name) 
     226    model = build_model(model_info) 
    237227    calculator = DirectModel(data, model) 
    238228    pars = dict((k, float(v)) 
    239229                for pair in sys.argv[3:] 
    240230                for k, v in [pair.split('=')]) 
    241     if call == "ER": 
    242         print(calculator.ER(**pars)) 
    243     elif call == "VR": 
    244         print(calculator.VR(**pars)) 
     231    if call == "ER_VR": 
     232        print(calculator.ER_VR(**pars)) 
    245233    else: 
    246234        Iq = calculator(**pars) 
  • sasmodels/generate.py

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

    re6a5556 r17bbadd  
    289289    GPU wrapper for a single model. 
    290290 
    291     *source* and *info* are the model source and interface as returned 
    292     from :func:`gen.make`. 
     291    *source* and *model_info* are the model source and interface as returned 
     292    from :func:`generate.make_source` and :func:`generate.make_model_info`. 
    293293 
    294294    *dtype* is the desired model precision.  Any numpy dtype for single 
     
    300300    that the compiler is allowed to take shortcuts. 
    301301    """ 
    302     def __init__(self, source, info, dtype=generate.F32): 
    303         self.info = info 
     302    def __init__(self, source, model_info, dtype=generate.F32): 
     303        self.info = model_info 
    304304        self.source = source 
    305305        self.dtype = generate.F32 if dtype == 'fast' else np.dtype(dtype) 
     
    356356    """ 
    357357    def __init__(self, q_vectors, dtype=generate.F32): 
     358        # TODO: do we ever need double precision q? 
    358359        env = environment() 
    359360        self.nq = q_vectors[0].size 
     
    389390    *kernel* is the GpuKernel object to call 
    390391 
    391     *info* is the module information 
     392    *model_info* is the module information 
    392393 
    393394    *q_vectors* is the q vectors at which the kernel should be evaluated 
     
    403404    Call :meth:`release` when done with the kernel instance. 
    404405    """ 
    405     def __init__(self, kernel, info, q_vectors, dtype): 
     406    def __init__(self, kernel, model_info, q_vectors, dtype): 
    406407        q_input = GpuInput(q_vectors, dtype) 
    407408        self.kernel = kernel 
    408         self.info = info 
     409        self.info = model_info 
    409410        self.res = np.empty(q_input.nq, q_input.dtype) 
    410411        dim = '2d' if q_input.is_2d else '1d' 
    411         self.fixed_pars = info['partype']['fixed-' + dim] 
    412         self.pd_pars = info['partype']['pd-' + dim] 
     412        self.fixed_pars = model_info['partype']['fixed-' + dim] 
     413        self.pd_pars = model_info['partype']['pd-' + dim] 
    413414 
    414415        # Inputs and outputs for each kernel call 
     
    430431                else np.float32)  # will never get here, so use np.float32 
    431432 
     433        #print "pars", fixed_pars, pd_pars 
    432434        res_bi = self.res_b 
    433435        nq = np.uint32(self.q_input.nq) 
  • sasmodels/kerneldll.py

    reafc9fa r17bbadd  
    8989 
    9090 
    91 def dll_path(info, dtype="double"): 
    92     """ 
    93     Path to the compiled model defined by *info*. 
     91def dll_path(model_info, dtype="double"): 
     92    """ 
     93    Path to the compiled model defined by *model_info*. 
    9494    """ 
    9595    from os.path import join as joinpath, split as splitpath, splitext 
    96     basename = splitext(splitpath(info['filename'])[1])[0] 
     96    basename = splitext(splitpath(model_info['filename'])[1])[0] 
    9797    if np.dtype(dtype) == generate.F32: 
    9898        basename += "32" 
     
    104104 
    105105 
    106 def make_dll(source, info, dtype="double"): 
     106def make_dll(source, model_info, dtype="double"): 
    107107    """ 
    108108    Load the compiled model defined by *kernel_module*. 
     
    123123    models are allowed as DLLs. 
    124124    """ 
    125     if callable(info.get('Iq', None)): 
    126         return PyModel(info) 
     125    if callable(model_info.get('Iq', None)): 
     126        return PyModel(model_info) 
    127127 
    128128    dtype = np.dtype(dtype) 
     
    133133 
    134134    if dtype == generate.F32: # 32-bit dll 
    135         tempfile_prefix = 'sas_'+info['name']+'32_' 
     135        tempfile_prefix = 'sas_' + model_info['name'] + '32_' 
    136136    elif dtype == generate.F64: 
    137         tempfile_prefix = 'sas_'+info['name']+'64_' 
     137        tempfile_prefix = 'sas_' + model_info['name'] + '64_' 
    138138    else: 
    139         tempfile_prefix = 'sas_'+info['name']+'128_' 
     139        tempfile_prefix = 'sas_' + model_info['name'] + '128_' 
    140140 
    141141    source = generate.convert_type(source, dtype) 
    142     source_files = generate.model_sources(info) + [info['filename']] 
    143     dll = dll_path(info, dtype) 
     142    source_files = generate.model_sources(model_info) + [model_info['filename']] 
     143    dll = dll_path(model_info, dtype) 
    144144    newest = max(os.path.getmtime(f) for f in source_files) 
    145145    if not os.path.exists(dll) or os.path.getmtime(dll) < newest: 
     
    159159 
    160160 
    161 def load_dll(source, info, dtype="double"): 
     161def load_dll(source, model_info, dtype="double"): 
    162162    """ 
    163163    Create and load a dll corresponding to the source, info pair returned 
     
    167167    allowed floating point precision. 
    168168    """ 
    169     filename = make_dll(source, info, dtype=dtype) 
    170     return DllModel(filename, info, dtype=dtype) 
     169    filename = make_dll(source, model_info, dtype=dtype) 
     170    return DllModel(filename, model_info, dtype=dtype) 
    171171 
    172172 
     
    178178    ctypes wrapper for a single model. 
    179179 
    180     *source* and *info* are the model source and interface as returned 
     180    *source* and *model_info* are the model source and interface as returned 
    181181    from :func:`gen.make`. 
    182182 
     
    188188    Call :meth:`release` when done with the kernel. 
    189189    """ 
    190     def __init__(self, dllpath, info, dtype=generate.F32): 
    191         self.info = info 
     190    def __init__(self, dllpath, model_info, dtype=generate.F32): 
     191        self.info = model_info 
    192192        self.dllpath = dllpath 
    193193        self.dll = None 
     
    244244    *kernel* is the c function to call. 
    245245 
    246     *info* is the module information 
     246    *model_info* is the module information 
    247247 
    248248    *q_input* is the DllInput q vectors at which the kernel should be 
     
    257257    Call :meth:`release` when done with the kernel instance. 
    258258    """ 
    259     def __init__(self, kernel, info, q_input): 
    260         self.info = info 
     259    def __init__(self, kernel, model_info, q_input): 
     260        self.info = model_info 
    261261        self.q_input = q_input 
    262262        self.kernel = kernel 
    263263        self.res = np.empty(q_input.nq, q_input.dtype) 
    264264        dim = '2d' if q_input.is_2d else '1d' 
    265         self.fixed_pars = info['partype']['fixed-'+dim] 
    266         self.pd_pars = info['partype']['pd-'+dim] 
     265        self.fixed_pars = model_info['partype']['fixed-' + dim] 
     266        self.pd_pars = model_info['partype']['pd-' + dim] 
    267267 
    268268        # In dll kernel, but not in opencl kernel 
  • sasmodels/kernelpy.py

    reafc9fa r17bbadd  
    1616    Wrapper for pure python models. 
    1717    """ 
    18     def __init__(self, info): 
    19         self.info = info 
     18    def __init__(self, model_info): 
     19        self.info = model_info 
    2020 
    2121    def __call__(self, q_vectors): 
     
    6868    *kernel* is the DllKernel object to call. 
    6969 
    70     *info* is the module information 
     70    *model_info* is the module information 
    7171 
    7272    *q_input* is the DllInput q vectors at which the kernel should be 
     
    8181    Call :meth:`release` when done with the kernel instance. 
    8282    """ 
    83     def __init__(self, kernel, info, q_input): 
    84         self.info = info 
     83    def __init__(self, kernel, model_info, q_input): 
     84        self.info = model_info 
    8585        self.q_input = q_input 
    8686        self.res = np.empty(q_input.nq, q_input.dtype) 
     
    106106        else: 
    107107            self.kernel = kernel 
    108         fixed_pars = info['partype']['fixed-' + dim] 
    109         pd_pars = info['partype']['pd-' + dim] 
    110         vol_pars = info['partype']['volume'] 
     108        fixed_pars = model_info['partype']['fixed-' + dim] 
     109        pd_pars = model_info['partype']['pd-' + dim] 
     110        vol_pars = model_info['partype']['volume'] 
    111111 
    112112        # First two fixed pars are scale and background 
    113         pars = [p[0] for p in info['parameters'][2:]] 
     113        pars = [p[0] for p in model_info['parameters'][2:]] 
    114114        offset = len(self.q_input.q_vectors) 
    115115        self.args = self.q_input.q_vectors + [None] * len(pars) 
  • sasmodels/list_pars.py

    r5c962df r17bbadd  
    1313import sys 
    1414 
    15 from .core import load_model_definition 
    16 from .generate import make_info 
     15from .core import load_model_info 
    1716from .compare import MODELS, columnize 
    1817 
     
    2524    partable = {} 
    2625    for name in sorted(MODELS): 
    27         definition = load_model_definition(name) 
    28         info = make_info(definition) 
    29         for p in info['parameters']: 
     26        model_info = load_model_info(name) 
     27        for p in model_info['parameters']: 
    3028            pname = p[0] 
    3129            partable.setdefault(pname, []) 
  • sasmodels/model_test.py

    r13ed84c r17bbadd  
    5050import numpy as np 
    5151 
    52 from .core import list_models, load_model_definition, load_model, HAVE_OPENCL 
     52from .core import list_models, load_model_info, build_model, HAVE_OPENCL 
    5353from .core import make_kernel, call_kernel, call_ER, call_VR 
    5454from .exception import annotate_exception 
    5555 
     56#TODO: rename to tests so that tab completion works better for models directory 
    5657 
    5758def make_suite(loaders, models): 
     
    7677    for model_name in models: 
    7778        if model_name in skip: continue 
    78         model_definition = load_model_definition(model_name) 
     79        model_info = load_model_info(model_name) 
    7980 
    8081        #print('------') 
     
    8586        # don't try to call cl kernel since it will not be 
    8687        # available in some environmentes. 
    87         is_py = callable(getattr(model_definition, 'Iq', None)) 
     88        is_py = callable(model_info['Iq']) 
    8889 
    8990        if is_py:  # kernel implemented in python 
    9091            test_name = "Model: %s, Kernel: python"%model_name 
    9192            test_method_name = "test_%s_python" % model_name 
    92             test = ModelTestCase(test_name, model_definition, 
     93            test = ModelTestCase(test_name, model_info, 
    9394                                 test_method_name, 
    9495                                 platform="dll",  # so that 
     
    104105                # single precision.  The choice is determined by the 
    105106                # presence of *single=False* in the model file. 
    106                 test = ModelTestCase(test_name, model_definition, 
     107                test = ModelTestCase(test_name, model_info, 
    107108                                     test_method_name, 
    108109                                     platform="ocl", dtype=None) 
     
    114115                test_name = "Model: %s, Kernel: dll"%model_name 
    115116                test_method_name = "test_%s_dll" % model_name 
    116                 test = ModelTestCase(test_name, model_definition, 
     117                test = ModelTestCase(test_name, model_info, 
    117118                                     test_method_name, 
    118119                                     platform="dll", 
     
    132133        description file. 
    133134        """ 
    134         def __init__(self, test_name, definition, test_method_name, 
     135        def __init__(self, test_name, model_info, test_method_name, 
    135136                     platform, dtype): 
    136137            self.test_name = test_name 
    137             self.definition = definition 
     138            self.info = model_info 
    138139            self.platform = platform 
    139140            self.dtype = dtype 
     
    150151                ] 
    151152 
    152             tests = getattr(self.definition, 'tests', []) 
     153            tests = self.info['tests'] 
    153154            try: 
    154                 model = load_model(self.definition, dtype=self.dtype, 
    155                                    platform=self.platform) 
     155                model = build_model(self.info, dtype=self.dtype, 
     156                                    platform=self.platform) 
    156157                for test in smoke_tests + tests: 
    157158                    self._run_one_test(model, test) 
  • sasmodels/resolution.py

    r5925e90 r17bbadd  
    10611061    else: 
    10621062        pars = {} 
    1063     defn = core.load_model_definition(name) 
    1064     model = core.load_model(defn) 
     1063    model_info = core.load_model_info(name) 
     1064    model = core.build_model(model_info) 
    10651065 
    10661066    kernel = core.make_kernel(model, [resolution.q_calc]) 
  • sasmodels/sasview_model.py

    reafc9fa r17bbadd  
    2222from . import core 
    2323 
    24 def make_class(model_definition, dtype='single', namestyle='name'): 
     24def make_class(model_info, dtype='single', namestyle='name'): 
    2525    """ 
    2626    Load the sasview model defined in *kernel_module*. 
     
    3232    compatible with SasView. 
    3333    """ 
    34     model = core.load_model(model_definition, dtype=dtype) 
     34    model = core.build_model(model_info, dtype=dtype) 
    3535    def __init__(self, multfactor=1): 
    3636        SasviewModel.__init__(self, model) 
Note: See TracChangeset for help on using the changeset viewer.