Changes in / [31d22de:c2e4be1] in sasmodels


Ignore:
Files:
13 edited

Legend:

Unmodified
Added
Removed
  • doc/developer/calculator.rst

    rd5ac45f r03cac08  
    77- KERNEL declares a function to be available externally 
    88- KERNEL_NAME is the name of the function being declared 
     9- MAX_PD is the maximum depth of the polydispersity loop 
    910- NPARS is the number of parameters in the kernel 
    1011- PARAMETER_TABLE is the declaration of the parameters to the kernel:: 
     
    2930        double sld_solvent 
    3031 
    31 - CALL_IQ(q, nq, i, pars) is the declaration of a call to the kernel:: 
     32- CALL_IQ(q, i, pars) is the declaration of a call to the kernel:: 
    3233 
    3334    Cylinder: 
    3435 
    35         #define CALL_IQ(q, nq, i, var) \ 
    36         Iq(q[i], \ 
     36        #define CALL_IQ(q, i, var) Iq(q[i], \ 
    3737        var.length, \ 
    3838        var.radius, \ 
     
    4242    Multi-shell cylinder: 
    4343 
    44         #define CALL_IQ(q, nq, i, var) \ 
    45         Iq(q[i], \ 
     44        #define CALL_IQ(q, i, var) Iq(q[i], \ 
    4645        var.num_shells, \ 
    4746        var.length, \ 
     
    5049        var.sld_solvent) 
    5150 
     51    Cylinder2D: 
     52 
     53        #define CALL_IQ(q, i, var) Iqxy(q[2*i], q[2*i+1], \ 
     54        var.length, \ 
     55        var.radius, \ 
     56        var.sld, \ 
     57        var.sld_solvent, \ 
     58        var.theta, \ 
     59        var.phi) 
     60 
    5261- CALL_VOLUME(var) is similar, but for calling the form volume:: 
    5362 
     
    6978        inline bool constrained(p1, p2, p3) { return expression; } 
    7079        #define INVALID(var) constrained(var.p1, var.p2, var.p3) 
    71  
    72 - IQ_FUNC could be Iq or Iqxy 
    73 - IQ_PARS could be q[i] or q[2*i],q[2*i+1] 
    7480 
    7581Our design supports a limited number of polydispersity loops, wherein 
     
    200206 
    201207TODO: cutoff 
     208 
     209For accuracy we may want to introduce Kahan summation into the integration:: 
     210 
     211 
     212    double accumulated_error = 0.0; 
     213    ... 
     214    #if USE_KAHAN_SUMMATION 
     215        const double y = next - accumulated_error; 
     216        const double t = ret + y; 
     217        accumulated_error = (t - ret) - y; 
     218        ret = t; 
     219    #else 
     220        ret += next; 
     221    #endif 
  • sasmodels/bumps_model.py

    ra84a0ca r303d8d6  
    8181    from bumps.names import Parameter 
    8282 
    83     pars = {} 
     83    pars = {} # => floating point parameters 
    8484    for p in model_info['parameters']: 
    8585        value = kwargs.pop(p.name, p.default) 
    8686        pars[p.name] = Parameter.default(value, name=p.name, limits=p.limits) 
    87     for name in model_info['partype']['pd-2d']: 
     87    for name in model_info['par_type']['pd']: 
    8888        for xpart, xdefault, xlimits in [ 
    8989                ('_pd', 0., pars[name].limits), 
     
    9595            pars[xname] = Parameter.default(xvalue, name=xname, limits=xlimits) 
    9696 
    97     pd_types = {} 
    98     for name in model_info['partype']['pd-2d']: 
     97    pd_types = {}  # => distribution names 
     98    for name in model_info['par_type']['pd']: 
    9999        xname = name + '_pd_type' 
    100100        xvalue = kwargs.pop(xname, 'gaussian') 
  • sasmodels/compare.py

    r72a081d r303d8d6  
    308308        exclude = lambda n: False 
    309309    else: 
    310         partype = model_info['partype'] 
    311         par1d = set(partype['fixed-1d']+partype['pd-1d']) 
     310        par1d = model_info['par_type']['1d'] 
    312311        exclude = lambda n: n not in par1d 
    313312    lines = [] 
     
    870869        pars, pd_types = bumps_model.create_parameters(model_info, **opts['pars']) 
    871870        if not opts['is2d']: 
    872             active = [base + ext 
    873                       for base in model_info['partype']['pd-1d'] 
    874                       for ext in ['', '_pd', '_pd_n', '_pd_nsigma']] 
    875             active.extend(model_info['partype']['fixed-1d']) 
    876             for k in active: 
    877                 v = pars[k] 
    878                 v.range(*parameter_range(k, v.value)) 
     871            for name in model_info['par_type']['1d']: 
     872                for ext in ['', '_pd', '_pd_n', '_pd_nsigma']: 
     873                    k = name+ext 
     874                    v = pars.get(k, None) 
     875                    if v is not None: 
     876                        v.range(*parameter_range(k, v.value)) 
    879877        else: 
    880878            for k, v in pars.items(): 
  • sasmodels/core.py

    rcf52f9c r303d8d6  
    173173    return model(q_vectors) 
    174174 
    175 def get_weights(model_info, pars, name): 
     175def get_weights(parameter, values): 
    176176    """ 
    177177    Generate the distribution for parameter *name* given the parameter values 
     
    181181    from the *pars* dictionary for parameter value and parameter dispersion. 
    182182    """ 
    183     relative = name in model_info['partype']['pd-rel'] 
    184     limits = model_info['limits'][name] 
    185     disperser = pars.get(name+'_pd_type', 'gaussian') 
    186     value = pars.get(name, model_info['defaults'][name]) 
    187     npts = pars.get(name+'_pd_n', 0) 
    188     width = pars.get(name+'_pd', 0.0) 
    189     nsigma = pars.get(name+'_pd_nsigma', 3.0) 
     183    value = values.get(parameter.name, parameter.default) 
     184    if parameter.type not in ('volume', 'orientation'): 
     185        return [value], [] 
     186    relative = parameter.type == 'volume' 
     187    limits = parameter.limits 
     188    disperser = values.get(parameter.name+'_pd_type', 'gaussian') 
     189    npts = values.get(parameter.name+'_pd_n', 0) 
     190    width = values.get(parameter.name+'_pd', 0.0) 
     191    nsigma = values.get(parameter.name+'_pd_nsigma', 3.0) 
    190192    value, weight = weights.get_weights( 
    191193        disperser, npts, width, nsigma, value, limits, relative) 
     
    206208    return value, weight 
    207209 
    208 def call_kernel(kernel, pars, cutoff=0, mono=False): 
     210def call_kernel(kernel, values, cutoff=0, mono=False): 
    209211    """ 
    210212    Call *kernel* returned from :func:`make_kernel` with parameters *pars*. 
     
    219221    *mono* is True if polydispersity should be set to none on all parameters. 
    220222    """ 
    221     fixed_pars = [pars.get(name, kernel.info['defaults'][name]) 
    222                   for name in kernel.fixed_pars] 
    223     if mono: 
    224         pd_pars = [( np.array([pars[name]]), np.array([1.0]) ) 
    225                    for name in kernel.pd_pars] 
    226     else: 
    227         pd_pars = [get_weights(kernel.info, pars, name) for name in kernel.pd_pars] 
    228     return kernel(fixed_pars, pd_pars, cutoff=cutoff) 
     223    if mono or True: 
     224        pars = np.array([values.get(p.name, p.default) 
     225                         for p in kernel.info['parameters']]) 
     226        weights = np.array([1.0]) 
     227        details = kernel.info['mono_details'] 
     228        return kernel(pars, weights, details, cutoff) 
     229    else: 
     230        pairs = [get_weights(p, values) for p in kernel.info['parameters']] 
     231        weights, pars = [v for v in zip(*pairs)] 
     232        details = generate.poly_details(kernel.info, weights, pars) 
     233        weights, pars = [np.hstack(v) for v in (weights, pars)] 
     234        return kernel(pars, weights, details, cutoff) 
    229235 
    230236def call_ER_VR(model_info, vol_pars): 
     
    249255 
    250256 
    251 def call_ER(info, pars): 
    252     """ 
    253     Call the model ER function using *pars*. 
    254     *info* is either *model.info* if you have a loaded model, or *kernel.info* 
    255     if you have a model kernel prepared for evaluation. 
    256     """ 
    257     ER = info.get('ER', None) 
     257def call_ER(model_info, values): 
     258    """ 
     259    Call the model ER function using *values*. *model_info* is either 
     260    *model.info* if you have a loaded model, or *kernel.info* if you 
     261    have a model kernel prepared for evaluation. 
     262    """ 
     263    ER = model_info.get('ER', None) 
    258264    if ER is None: 
    259265        return 1.0 
    260266    else: 
    261         vol_pars = [get_weights(info, pars, name) 
    262                     for name in info['partype']['volume']] 
     267        vol_pars = [get_weights(parameter, values) 
     268                    for parameter in model_info['parameters'] 
     269                    if parameter.type == 'volume'] 
    263270        value, weight = dispersion_mesh(vol_pars) 
    264271        individual_radii = ER(*value) 
     
    266273        return np.sum(weight*individual_radii) / np.sum(weight) 
    267274 
    268 def call_VR(info, pars): 
     275def call_VR(model_info, values): 
    269276    """ 
    270277    Call the model VR function using *pars*. 
     
    272279    if you have a model kernel prepared for evaluation. 
    273280    """ 
    274     VR = info.get('VR', None) 
     281    VR = model_info.get('VR', None) 
    275282    if VR is None: 
    276283        return 1.0 
    277284    else: 
    278         vol_pars = [get_weights(info, pars, name) 
    279                     for name in info['partype']['volume']] 
     285        vol_pars = [get_weights(parameter, values) 
     286                    for parameter in model_info['parameters'] 
     287                    if parameter.type == 'volume'] 
    280288        value, weight = dispersion_mesh(vol_pars) 
    281289        whole, part = VR(*value) 
  • sasmodels/direct_model.py

    r02e70ff r303d8d6  
    6666            self.data_type = 'Iq' 
    6767 
    68         partype = model.info['partype'] 
    69  
    7068        if self.data_type == 'sesans': 
    7169             
     
    8179            q_mono = sesans.make_all_q(data) 
    8280        elif self.data_type == 'Iqxy': 
     81            partype = model.info['par_type'] 
    8382            if not partype['orientation'] and not partype['magnetic']: 
    8483                raise ValueError("not 2D without orientation or magnetic parameters") 
  • sasmodels/generate.py

    rd5ac45f r303d8d6  
    236236 
    237237TEMPLATE_ROOT = dirname(__file__) 
     238 
     239MAX_PD = 4 
    238240 
    239241F16 = np.dtype('float16') 
     
    352354    return [_search(search_path, f) for f in model_info['source']] 
    353355 
    354  
    355356def convert_type(source, dtype): 
    356357    """ 
     
    417418    if filename not in _template_cache or mtime > _template_cache[filename][0]: 
    418419        with open(path) as fid: 
    419             _template_cache[filename] = (mtime, fid.read()) 
     420            _template_cache[filename] = (mtime, fid.read(), path) 
    420421    return _template_cache[filename][1] 
     422 
     423def model_templates(): 
     424    # TODO: fails DRY; templates are listed in two places. 
     425    # should instead have model_info contain a list of paths 
     426    return [joinpath(TEMPLATE_ROOT, filename) 
     427            for filename in ('kernel_header.c', 'kernel_iq.c')] 
     428 
     429 
     430_FN_TEMPLATE = """\ 
     431double %(name)s(%(pars)s); 
     432double %(name)s(%(pars)s) { 
     433    %(body)s 
     434} 
     435 
     436 
     437""" 
    421438 
    422439def _gen_fn(name, pars, body): 
     
    431448         } 
    432449    """ 
    433     template = """\ 
    434 double %(name)s(%(pars)s); 
    435 double %(name)s(%(pars)s) { 
    436     %(body)s 
    437 } 
    438  
    439  
    440 """ 
    441450    par_decl = ', '.join('double ' + p for p in pars) if pars else 'void' 
    442     return template % {'name': name, 'body': body, 'pars': par_decl} 
    443  
    444 def _gen_call_pars(name, pars): 
    445     name += "." 
    446     return ",".join(name+p for p in pars) 
     451    return _FN_TEMPLATE % {'name': name, 'body': body, 'pars': par_decl} 
     452 
     453def _call_pars(prefix, pars): 
     454    """ 
     455    Return a list of *prefix.parameter* from parameter items. 
     456    """ 
     457    prefix += "." 
     458    return [prefix+p for p in pars] 
     459 
     460_IQXY_PATTERN = re.compile("^((inline|static) )? *(double )? *Iqxy *([(]|$)", 
     461                           flags=re.MULTILINE) 
     462def _have_Iqxy(sources): 
     463    """ 
     464    Return true if any file defines Iqxy. 
     465 
     466    Note this is not a C parser, and so can be easily confused by 
     467    non-standard syntax.  Also, it will incorrectly identify the following 
     468    as having Iqxy:: 
     469 
     470        /* 
     471        double Iqxy(qx, qy, ...) { ... fill this in later ... } 
     472        */ 
     473 
     474    If you want to comment out an Iqxy function, use // on the front of the 
     475    line instead. 
     476    """ 
     477    for code in sources: 
     478        if _IQXY_PATTERN.search(code): 
     479            return True 
     480    else: 
     481        return False 
    447482 
    448483def make_source(model_info): 
     
    464499    # for computing volume even if we allow non-disperse volume parameters. 
    465500 
    466     # Load template 
    467     source = [load_template('kernel_header.c')] 
    468  
    469     # Load additional sources 
    470     source += [open(f).read() for f in model_sources(model_info)] 
    471  
    472     # Prepare defines 
    473     defines = [] 
    474  
    475     iq_parameters = [p.name 
    476                      for p in model_info['parameters'][2:]  # skip scale, background 
    477                      if p.name in model_info['par_set']['1d']] 
    478     iqxy_parameters = [p.name 
    479                        for p in model_info['parameters'][2:]  # skip scale, background 
    480                        if p.name in model_info['par_set']['2d']] 
    481     volume_parameters = model_info['par_type']['volume'] 
     501    # kernel_iq assumes scale and background are the first parameters; 
     502    # they should be first for 1d and 2d parameter lists as well. 
     503    assert model_info['parameters'][0].name == 'scale' 
     504    assert model_info['parameters'][1].name == 'background' 
     505 
     506    # Identify parameter types 
     507    iq_parameters = model_info['par_type']['1d'][2:] 
     508    iqxy_parameters = model_info['par_type']['2d'][2:] 
     509    vol_parameters = model_info['par_type']['volume'] 
     510 
     511    # Load templates and user code 
     512    kernel_header = load_template('kernel_header.c') 
     513    kernel_code = load_template('kernel_iq.c') 
     514    user_code = [open(f).read() for f in model_sources(model_info)] 
     515 
     516    # Build initial sources 
     517    source = [kernel_header] + user_code 
    482518 
    483519    # Generate form_volume function, etc. from body only 
    484520    if model_info['form_volume'] is not None: 
    485         pnames = [p.name for p in volume_parameters] 
     521        pnames = [p for p in vol_parameters] 
    486522        source.append(_gen_fn('form_volume', pnames, model_info['form_volume'])) 
    487523    if model_info['Iq'] is not None: 
    488         pnames = ['q'] + [p.name for p in iq_parameters] 
     524        pnames = ['q'] + [p for p in iq_parameters] 
    489525        source.append(_gen_fn('Iq', pnames, model_info['Iq'])) 
    490526    if model_info['Iqxy'] is not None: 
    491         pnames = ['qx', 'qy'] + [p.name for p in iqxy_parameters] 
     527        pnames = ['qx', 'qy'] + [p for p in iqxy_parameters] 
    492528        source.append(_gen_fn('Iqxy', pnames, model_info['Iqxy'])) 
    493529 
    494     # Fill in definitions for volume parameters 
    495     if volume_parameters: 
    496         deref_vol = ",".join("v."+p.name for p in volume_parameters) 
    497         defines.append(('CALL_VOLUME(v)', 'form_volume(%s)\n'%deref_vol)) 
     530    # Define the parameter table 
     531    source.append("#define PARAMETER_TABLE \\") 
     532    source.append("\\\n    ".join("double %s;"%p.name 
     533                                   for p in model_info['parameters'][2:])) 
     534 
     535    # Define the function calls 
     536    if vol_parameters: 
     537        refs = ",".join(_call_pars("v", vol_parameters)) 
     538        call_volume = "#define CALL_VOLUME(v) form_volume(%s)"%refs 
    498539    else: 
    499540        # Model doesn't have volume.  We could make the kernel run a little 
    500541        # faster by not using/transferring the volume normalizations, but 
    501542        # the ifdef's reduce readability more than is worthwhile. 
    502         defines.append(('CALL_VOLUME(v)', '0.0')) 
    503  
    504     # Fill in definitions for Iq parameters 
    505     defines.append(('KERNEL_NAME', model_info['name'])) 
    506     defines.append(('IQ_PARAMETERS', ', '.join(iq_parameters))) 
    507     if fixed_1d: 
    508         defines.append(('IQ_FIXED_PARAMETER_DECLARATIONS', 
    509                         ', \\\n    '.join('const double %s' % p for p in fixed_1d))) 
    510     # Fill in definitions for Iqxy parameters 
    511     defines.append(('IQXY_KERNEL_NAME', model_info['name'] + '_Iqxy')) 
    512     defines.append(('IQXY_PARAMETERS', ', '.join(iqxy_parameters))) 
    513     if fixed_2d: 
    514         defines.append(('IQXY_FIXED_PARAMETER_DECLARATIONS', 
    515                         ', \\\n    '.join('const double %s' % p for p in fixed_2d))) 
    516     if pd_2d: 
    517         defines.append(('IQXY_WEIGHT_PRODUCT', 
    518                         '*'.join(p + '_w' for p in pd_2d))) 
    519         defines.append(('IQXY_DISPERSION_LENGTH_DECLARATIONS', 
    520                         ', \\\n    '.join('const int N%s' % p for p in pd_2d))) 
    521         defines.append(('IQXY_DISPERSION_LENGTH_SUM', 
    522                         '+'.join('N' + p for p in pd_2d))) 
    523         open_loops, close_loops = build_polydispersity_loops(pd_2d) 
    524         defines.append(('IQXY_OPEN_LOOPS', 
    525                         open_loops.replace('\n', ' \\\n'))) 
    526         defines.append(('IQXY_CLOSE_LOOPS', 
    527                         close_loops.replace('\n', ' \\\n'))) 
    528     # Need to know if we have a theta parameter for Iqxy; it is not there 
    529     # for the magnetic sphere model, for example, which has a magnetic 
    530     # orientation but no shape orientation. 
    531     if 'theta' in pd_2d: 
    532         defines.append(('IQXY_HAS_THETA', '1')) 
    533  
    534     #for d in defines: print(d) 
    535     defines = '\n'.join('#define %s %s' % (k, v) for k, v in defines) 
    536     sources = '\n\n'.join(source) 
    537     return C_KERNEL_TEMPLATE % { 
    538         'DEFINES': defines, 
    539         'SOURCES': sources, 
    540         } 
     543        call_volume = "#define CALL_VOLUME(v) 0.0" 
     544    source.append(call_volume) 
     545 
     546    refs = ["q[i]"] + _call_pars("v", iq_parameters) 
     547    call_iq = "#define CALL_IQ(q,i,v) Iq(%s)" % (",".join(refs)) 
     548    if _have_Iqxy(user_code): 
     549        # Call 2D model 
     550        refs = ["q[2*i]", "q[2*i+1]"] + _call_pars("v", iqxy_parameters) 
     551        call_iqxy = "#define CALL_IQ(q,i,v) Iqxy(%s)" % (",".join(refs)) 
     552    else: 
     553        # Call 1D model with sqrt(qx^2 + qy^2) 
     554        warnings.warn("Creating Iqxy = Iq(sqrt(qx^2 + qy^2))") 
     555        # still defined:: refs = ["q[i]"] + _call_pars("v", iq_parameters) 
     556        pars_sqrt = ["sqrt(q[2*i]*q[2*i]+q[2*i+1]*q[2*i+1])"] + refs[1:] 
     557        call_iqxy = "#define CALL_IQ(q,i,v) Iq(%s)" % (",".join(pars_sqrt)) 
     558 
     559    # Fill in definitions for numbers of parameters 
     560    source.append("#define MAX_PD %s"%model_info['max_pd']) 
     561    source.append("#define NPARS %d"%(len(model_info['parameters'])-2)) 
     562 
     563    # TODO: allow mixed python/opencl kernels? 
     564 
     565    # define the Iq kernel 
     566    source.append("#define KERNEL_NAME %s_Iq"%model_info['name']) 
     567    source.append(call_iq) 
     568    source.append(kernel_code) 
     569    source.append("#undef CALL_IQ") 
     570    source.append("#undef KERNEL_NAME") 
     571 
     572    # define the Iqxy kernel from the same source with different #defines 
     573    source.append("#define KERNEL_NAME %s_Iqxy"%model_info['name']) 
     574    source.append(call_iqxy) 
     575    source.append(kernel_code) 
     576    source.append("#undef CALL_IQ") 
     577    source.append("#undef KERNEL_NAME") 
     578 
     579    return '\n'.join(source) 
    541580 
    542581def categorize_parameters(pars): 
     
    551590    * *magnetic* set of parameters that are used to compute magnetic 
    552591      patterns (which includes all 1D and 2D parameters) 
    553     * *sesans* set of parameters that are used to compute sesans patterns 
    554      (which is just 1D without background) 
    555     * *pd-relative* is the set of parameters with relative distribution 
     592    * *pd_relative* is the set of parameters with relative distribution 
    556593      width (e.g., radius +/- 10%) rather than absolute distribution 
    557594      width (e.g., theta +/- 6 degrees). 
    558595    """ 
    559596    par_set = {} 
    560     par_set['1d'] = [p for p in pars if p.type not in ('orientation', 'magnetic')] 
    561     par_set['2d'] = [p for p in pars if p.type != 'magnetic'] 
    562     par_set['magnetic'] = [p for p in pars] 
    563     par_set['pd'] = [p for p in pars if p.type in ('volume', 'orientation')] 
    564     par_set['pd_relative'] = [p for p in pars if p.type == 'volume'] 
     597    par_set['1d'] = [p.name for p in pars if p.type not in ('orientation', 'magnetic')] 
     598    par_set['2d'] = [p.name for p in pars if p.type != 'magnetic'] 
     599    par_set['magnetic'] = [p.name for p in pars] 
     600    par_set['pd'] = [p.name for p in pars if p.type in ('volume', 'orientation')] 
     601    par_set['pd_relative'] = [p.name for p in pars if p.type == 'volume'] 
    565602    return par_set 
    566603 
     
    605642    pars = [Parameter(*p) for p in model_info['parameters']] 
    606643    # Fill in the derived attributes 
     644    par_type = collect_types(pars) 
     645    par_type.update(categorize_parameters(pars)) 
    607646    model_info['parameters'] = pars 
    608     partype = categorize_parameters(pars) 
    609     model_info['limits'] = dict((p.name, p.limits) for p in pars) 
    610     model_info['par_type'] = collect_types(pars) 
    611     model_info['par_set'] = categorize_parameters(pars) 
    612     model_info['defaults'] = dict((p.name, p.default) for p in pars) 
     647    model_info['par_type'] = par_type 
    613648    if model_info.get('demo', None) is None: 
    614         model_info['demo'] = model_info['defaults'] 
    615     model_info['has_2d'] = partype['orientation'] or partype['magnetic'] 
     649        model_info['demo'] = dict((p.name, p.default) for p in pars) 
     650    model_info['has_2d'] = par_type['orientation'] or par_type['magnetic'] 
     651 
     652def mono_details(max_pd, npars): 
     653    par_offset = 5*max_pd 
     654    const_offset = par_offset + 3*npars 
     655 
     656    mono = np.zeros(const_offset + 3, 'i') 
     657    mono[0] = 0                # pd_par: arbitrary order; use first 
     658    mono[1*max_pd] = 1         # pd_length: only one element 
     659    mono[2*max_pd] = 2         # pd_offset: skip scale and background 
     660    mono[3*max_pd] = 1         # pd_stride: vectors of length 1 
     661    mono[4*max_pd-1] = 1       # pd_stride[-1]: only one element in set 
     662    mono[4*max_pd] = 0         # pd_isvol: doens't matter if no norm 
     663    mono[par_offset:par_offset+npars] = np.arange(2, npars+2, dtype='i') 
     664    # par_offset: copied in order 
     665    mono[par_offset+npars:par_offset+2*npars] = 0 
     666    # par_coord: no coordinated parameters 
     667    mono[par_offset+npars] = 1 # par_coord[0]: except par 0 
     668    mono[par_offset+2*npars:par_offset+3*npars] = 0 
     669    # fast coord with 0 
     670    mono[const_offset] = 1     # fast_coord_count: one fast index 
     671    mono[const_offset+1] = -1  # theta_var: None 
     672    mono[const_offset+2] = 0   # fast_theta: False 
     673    return mono 
     674 
     675def poly_details(model_info, weights, pars, constraints=None): 
     676    if constraints is not None: 
     677        # Need to find the independently varying pars and sort them 
     678        # Need to build a coordination list for the dependent variables 
     679        # Need to generate a constraints function which takes values 
     680        # and weights, returning par blocks 
     681        raise ValueError("Can't handle constraints yet") 
     682 
     683    raise ValueError("polydispersity not supported") 
     684 
     685 
     686def create_default_functions(model_info): 
     687    """ 
     688    Autogenerate missing functions, such as Iqxy from Iq. 
     689 
     690    This only works for Iqxy when Iq is written in python. :func:`make_source` 
     691    performs a similar role for Iq written in C. 
     692    """ 
     693    if model_info['Iq'] is not None and model_info['Iqxy'] is None: 
     694        if model_info['par_type']['1d'] != model_info['par_type']['2d']: 
     695            raise ValueError("Iqxy model is missing") 
     696        Iq = model_info['Iq'] 
     697        def Iqxy(qx, qy, **kw): 
     698            return Iq(np.sqrt(qx**2 + qy**2), **kw) 
     699        model_info['Iqxy'] = Iqxy 
    616700 
    617701def make_model_info(kernel_module): 
     
    640724      model variants (e.g., the list of cases) or is None if there are no 
    641725      model variants 
    642     * *defaults* is the *{parameter: value}* table built from the parameter 
    643       description table. 
    644     * *limits* is the *{parameter: [min, max]}* table built from the 
    645       parameter description table. 
    646     * *partypes* categorizes the model parameters. See 
     726    * *par_type* categorizes the model parameters. See 
    647727      :func:`categorize_parameters` for details. 
    648728    * *demo* contains the *{parameter: value}* map used in compare (and maybe 
     
    661741      *model_info* blocks for the composition objects.  This allows us to 
    662742      build complete product and mixture models from just the info. 
     743    * *max_pd* is the max polydispersity dimension.  This is constant and 
     744      should not be reset.  You may be able to change it when the program 
     745      starts by setting *sasmodels.generate.MAX_PD*. 
    663746 
    664747    """ 
     
    671754        name = " ".join(w.capitalize() for w in kernel_id.split('_')) 
    672755    model_info = dict( 
     756        max_pd=MAX_PD, 
    673757        id=kernel_id,  # string used to load the kernel 
    674758        filename=abspath(kernel_module.__file__), 
     
    688772        oldpars=getattr(kernel_module, 'oldpars', {}), 
    689773        tests=getattr(kernel_module, 'tests', []), 
     774        mono_details = mono_details(MAX_PD, len(kernel_module.parameters)) 
    690775        ) 
    691776    process_parameters(model_info) 
     
    693778    functions = "ER VR form_volume Iq Iqxy shape sesans".split() 
    694779    model_info.update((k, getattr(kernel_module, k, None)) for k in functions) 
     780    create_default_functions(model_info) 
    695781    return model_info 
    696782 
  • sasmodels/kernel_header.c

    r2e44ac7 r03cac08  
    11#ifdef __OPENCL_VERSION__ 
    22# define USE_OPENCL 
     3#elif defined(_OPENMP) 
     4# define USE_OPENMP 
    35#endif 
    4  
    5 #define USE_KAHAN_SUMMATION 0 
    66 
    77// If opencl is not available, then we are compiling a C function 
     
    1616         #include <float.h> 
    1717         #define kernel extern "C" __declspec( dllexport ) 
    18          inline double trunc(double x) { return x>=0?floor(x):-floor(-x); } 
    19          inline double fmin(double x, double y) { return x>y ? y : x; } 
    20          inline double fmax(double x, double y) { return x<y ? y : x; } 
    21          inline double isnan(double x) { return _isnan(x); } 
     18         static double trunc(double x) { return x>=0?floor(x):-floor(-x); } 
     19         static double fmin(double x, double y) { return x>y ? y : x; } 
     20         static double fmax(double x, double y) { return x<y ? y : x; } 
     21         static double isnan(double x) { return _isnan(x); } 
    2222         #define NAN (std::numeric_limits<double>::quiet_NaN()) // non-signalling NaN 
    2323         static double cephes_expm1(double x) { 
     
    4848         #define kernel extern "C" 
    4949     #endif 
    50      inline void SINCOS(double angle, double &svar, double &cvar) { svar=sin(angle); cvar=cos(angle); } 
     50     static void SINCOS(double angle, double &svar, double &cvar) { svar=sin(angle); cvar=cos(angle); } 
    5151#  else 
    5252     #include <stdio.h> 
     
    9999#  define M_4PI_3 4.18879020478639 
    100100#endif 
    101 //inline double square(double x) { return pow(x,2.0); } 
    102 //inline double square(double x) { return pown(x,2); } 
    103 inline double square(double x) { return x*x; } 
    104 inline double cube(double x) { return x*x*x; } 
    105 inline double sinc(double x) { return x==0 ? 1.0 : sin(x)/x; } 
     101static double square(double x) { return x*x; } 
     102static double cube(double x) { return x*x*x; } 
     103static double sinc(double x) { return x==0 ? 1.0 : sin(x)/x; } 
    106104 
  • sasmodels/kernel_iq.c

    rd5ac45f r303d8d6  
    1212*/ 
    1313 
     14#ifndef _PAR_BLOCK_ // protected block so we can include this code twice. 
     15#define _PAR_BLOCK_ 
    1416 
    1517#define MAX_PD 4  // MAX_PD is the max number of polydisperse parameters 
    16 #define PD_2N 16  // PD_2N is the size of the coordination step table 
    1718 
    1819typedef struct { 
     
    3132 
    3233typedef struct { 
    33     PARAMETER_DECL; 
     34    PARAMETER_TABLE; 
    3435} ParameterBlock; 
    35  
    36 #define FULL_KERNEL_NAME KERNEL_NAME ## _ ## IQ_FUNC 
    37 KERNEL 
    38 void FULL_KERNEL_NAME( 
     36#endif 
     37 
     38 
     39kernel 
     40void KERNEL_NAME( 
    3941    int nq,                 // number of q values 
     42    const int pd_start,     // where we are in the polydispersity loop 
     43    const int pd_stop,      // where we are stopping in the polydispersity loop 
    4044    global const ProblemDetails *problem, 
    4145    global const double *weights, 
    4246    global const double *pars, 
    4347    global const double *q, // nq q values, with padding to boundary 
    44     global double *result,  // nq return values, again with padding 
    45     const double cutoff,    // cutoff in the polydispersity weight product 
    46     const int pd_start,     // where we are in the polydispersity loop 
    47     const int pd_stop,      // where we are stopping in the polydispersity loop 
     48    global double *result,  // nq+3 return values, again with padding 
     49    const double cutoff     // cutoff in the polydispersity weight product 
    4850    ) 
    4951{ 
    50  
     52printf("hello\n"); 
    5153  // Storage for the current parameter values.  These will be updated as we 
    5254  // walk the polydispersity cube. 
    5355  local ParameterBlock local_pars;  // current parameter values 
    54   const double *parvec = &local_pars;  // Alias named parameters with a vector 
     56  double *pvec = (double *)(&local_pars);  // Alias named parameters with a vector 
    5557 
    5658  local int offset[NPARS-2]; 
     
    6062    // Shouldn't need to copy!! 
    6163    for (int k=0; k < NPARS; k++) { 
    62       parvec[k] = pars[k+2];  // skip scale and background 
     64      pvec[k] = pars[k+2];  // skip scale and background 
    6365    } 
    6466 
     
    6870    for (int i=0; i < nq; i++) { 
    6971    { 
    70       const double scattering = IQ_FUNC(IQ_PARS, IQ_PARAMETERS); 
     72      const double scattering = CALL_IQ(q, i, local_pars); 
    7173      result[i] += pars[0]*scattering + pars[1]; 
    7274    } 
     
    99101    norm = result[nq]; 
    100102    vol = result[nq+1]; 
    101     norm_vol = results[nq+2]; 
     103    norm_vol = result[nq+2]; 
    102104  } 
    103105 
    104106  // Location in the polydispersity hypercube, one index per dimension. 
    105   local int pd_index[PD_MAX]; 
     107  local int pd_index[MAX_PD]; 
    106108 
    107109  // Trigger the reset behaviour that happens at the end the fast loop 
    108110  // by setting the initial index >= weight vector length. 
    109   pd_index[0] = pd_length[0]; 
     111  pd_index[0] = problem->pd_length[0]; 
     112 
    110113 
    111114  // need product of weights at every Iq calc, so keep product of 
     
    120123  for (int loop_index=pd_start; loop_index < pd_stop; loop_index++) { 
    121124    // check if indices need to be updated 
    122     if (pd_index[0] >= pd_length[0]) { 
     125    if (pd_index[0] >= problem->pd_length[0]) { 
    123126 
    124127      // RESET INDICES 
    125       pd_index[0] = loop_index%pd_length[0]; 
     128      pd_index[0] = loop_index%problem->pd_length[0]; 
    126129      partial_weight = 1.0; 
    127130      partial_volweight = 1.0; 
    128131      for (int k=1; k < MAX_PD; k++) { 
    129         pd_index[k] = (loop_index%pd_length[k])/pd_stride[k]; 
    130         const double wi = weights[pd_offset[0]+pd_index[0]]; 
     132        pd_index[k] = (loop_index%problem->pd_length[k])/problem->pd_stride[k]; 
     133        const double wi = weights[problem->pd_offset[0]+pd_index[0]]; 
    131134        partial_weight *= wi; 
    132         if (pd_isvol[k]) partial_volweight *= wi; 
     135        if (problem->pd_isvol[k]) partial_volweight *= wi; 
    133136      } 
    134137      for (int k=0; k < NPARS; k++) { 
    135         int coord = par_coord[k]; 
    136         int this_offset = par_offset[k]; 
     138        int coord = problem->par_coord[k]; 
     139        int this_offset = problem->par_offset[k]; 
    137140        int block_size = 1; 
    138141        for (int bit=0; bit < MAX_PD && coord != 0; bit++) { 
    139142          if (coord&1) { 
    140143              this_offset += block_size * pd_index[bit]; 
    141               block_size *= pd_length[bit]; 
     144              block_size *= problem->pd_length[bit]; 
    142145          } 
    143146          coord /= 2; 
    144147        } 
    145148        offset[k] = this_offset; 
    146         parvec[k] = pars[this_offset]; 
    147       } 
    148       weight = partial_weight * weights[pd_offset[0]+pd_index[0]] 
    149       if (theta_var >= 0) { 
    150         spherical_correction = fabs(cos(M_PI_180*parvec[theta_var])); 
    151       } 
    152       if (!fast_theta) weight *= spherical_correction; 
     149        pvec[k] = pars[this_offset]; 
     150      } 
     151      weight = partial_weight * weights[problem->pd_offset[0]+pd_index[0]]; 
     152      if (problem->theta_var >= 0) { 
     153        spherical_correction = fabs(cos(M_PI_180*pvec[problem->theta_var])); 
     154      } 
     155      if (!problem->fast_theta) { 
     156        weight *= spherical_correction; 
     157      } 
    153158 
    154159    } else { 
     
    156161      // INCREMENT INDICES 
    157162      pd_index[0] += 1; 
    158       const double wi = weights[pd_offset[0]+pd_index[0]]; 
     163      const double wi = weights[problem->pd_offset[0]+pd_index[0]]; 
    159164      weight = partial_weight*wi; 
    160       if (pd_isvol[0]) vol_weight *= wi; 
     165      if (problem->pd_isvol[0]) vol_weight *= wi; 
    161166      for (int k=0; k < problem->fast_coord_count; k++) { 
    162         parvec[ fast_coord_index[k]] 
    163             = pars[offset[fast_coord_index[k]] + pd_index[0]]; 
    164       } 
    165       if (fast_theta) weight *= fabs(cos(M_PI_180*parvec[theta_var])); 
    166  
     167        pvec[problem->fast_coord_index[k]] 
     168            = pars[offset[problem->fast_coord_index[k]]++]; 
     169      } 
     170      if (problem->fast_theta) { 
     171        weight *= fabs(cos(M_PI_180*pvec[problem->theta_var])); 
     172      } 
    167173    } 
    168174 
     
    171177    #endif 
    172178 
     179    // Accumulate I(q) 
     180    // Note: weight==0 must always be excluded 
    173181    if (weight > cutoff) { 
    174182      norm += weight; 
     
    180188      #endif 
    181189      for (int i=0; i < nq; i++) { 
    182       { 
    183         const double scattering = CALL_IQ(q, nq, i, local_pars); 
     190        const double scattering = CALL_IQ(q, i, local_pars); 
    184191        result[i] += weight*scattering; 
    185192      } 
     193    } 
    186194  } 
    187195  //Makes a normalization avialable for the next round 
     
    191199 
    192200  //End of the PD loop we can normalize 
    193   if (pd_stop == pd_stride[MAX_PD-1]) {} 
     201  if (pd_stop >= problem->pd_stride[MAX_PD-1]) { 
    194202    #ifdef USE_OPENMP 
    195203    #pragma omp parallel for 
  • sasmodels/kerneldll.py

    r17bbadd r303d8d6  
    6161if sys.platform == 'darwin': 
    6262    #COMPILE = "gcc-mp-4.7 -shared -fPIC -std=c99 -fopenmp -O2 -Wall %s -o %s -lm -lgomp" 
    63     COMPILE = "gcc -shared -fPIC -std=c99 -O2 -Wall %(source)s -o %(output)s -lm" 
     63    COMPILE = "gcc -shared -fPIC -std=c99 -O2 -Wall %(source)s -o %(output)s -lm -fno-unknown-pragmas" 
    6464elif os.name == 'nt': 
    6565    # call vcvarsall.bat before compiling to set path, headers, libs, etc. 
     
    8080        COMPILE = "gcc -shared -fPIC -std=c99 -O2 -Wall %(source)s -o %(output)s -lm" 
    8181        if "SAS_OPENMP" in os.environ: 
    82             COMPILE = COMPILE + " -fopenmp" 
     82            COMPILE += " -fopenmp" 
     83        else: 
     84            COMPILE += " -fWno-unknown-pragmas" 
    8385else: 
    8486    COMPILE = "cc -shared -fPIC -fopenmp -std=c99 -O2 -Wall %(source)s -o %(output)s -lm" 
     
    140142 
    141143    source = generate.convert_type(source, dtype) 
    142     source_files = generate.model_sources(model_info) + [model_info['filename']] 
     144    source_files = (generate.model_sources(model_info) 
     145                    + [model_info['filename']] 
     146                    + generate.model_templates()) 
    143147    dll = dll_path(model_info, dtype) 
    144148    newest = max(os.path.getmtime(f) for f in source_files) 
     
    170174    return DllModel(filename, model_info, dtype=dtype) 
    171175 
    172  
    173 IQ_ARGS = [c_void_p, c_void_p, c_int] 
    174 IQXY_ARGS = [c_void_p, c_void_p, c_void_p, c_int] 
    175  
    176176class DllModel(object): 
    177177    """ 
     
    195195 
    196196    def _load_dll(self): 
    197         Nfixed1d = len(self.info['partype']['fixed-1d']) 
    198         Nfixed2d = len(self.info['partype']['fixed-2d']) 
    199         Npd1d = len(self.info['partype']['pd-1d']) 
    200         Npd2d = len(self.info['partype']['pd-2d']) 
    201  
    202197        #print("dll", self.dllpath) 
    203198        try: 
     
    210205              else c_double if self.dtype == generate.F64 
    211206              else c_longdouble) 
    212         pd_args_1d = [c_void_p, fp] + [c_int]*Npd1d if Npd1d else [] 
    213         pd_args_2d = [c_void_p, fp] + [c_int]*Npd2d if Npd2d else [] 
     207 
     208        # int, int, int, int*, double*, double*, double*, double*, double*, double 
     209        argtypes = [c_int]*3 + [c_void_p]*5 + [fp] 
    214210        self.Iq = self.dll[generate.kernel_name(self.info, False)] 
    215         self.Iq.argtypes = IQ_ARGS + pd_args_1d + [fp]*Nfixed1d 
    216  
    217211        self.Iqxy = self.dll[generate.kernel_name(self.info, True)] 
    218         self.Iqxy.argtypes = IQXY_ARGS + pd_args_2d + [fp]*Nfixed2d 
     212        self.Iq.argtypes = argtypes 
     213        self.Iqxy.argtypes = argtypes 
    219214 
    220215    def __getstate__(self): 
     
    261256        self.q_input = q_input 
    262257        self.kernel = kernel 
    263         self.res = np.empty(q_input.nq, q_input.dtype) 
     258        self.res = np.empty(q_input.nq+3, q_input.dtype) 
    264259        dim = '2d' if q_input.is_2d else '1d' 
    265         self.fixed_pars = model_info['partype']['fixed-' + dim] 
    266         self.pd_pars = model_info['partype']['pd-' + dim] 
     260        self.parameters = model_info['par_type'][dim] 
    267261 
    268262        # In dll kernel, but not in opencl kernel 
    269263        self.p_res = self.res.ctypes.data 
    270264 
    271     def __call__(self, fixed_pars, pd_pars, cutoff): 
     265    def __call__(self, details, values, weights, cutoff): 
    272266        real = (np.float32 if self.q_input.dtype == generate.F32 
    273267                else np.float64 if self.q_input.dtype == generate.F64 
    274268                else np.float128) 
    275  
    276         nq = c_int(self.q_input.nq) 
    277         if pd_pars: 
    278             cutoff = real(cutoff) 
    279             loops_N = [np.uint32(len(p[0])) for p in pd_pars] 
    280             loops = np.hstack(pd_pars) 
    281             loops = np.ascontiguousarray(loops.T, self.q_input.dtype).flatten() 
    282             p_loops = loops.ctypes.data 
    283             dispersed = [p_loops, cutoff] + loops_N 
    284         else: 
    285             dispersed = [] 
    286         fixed = [real(p) for p in fixed_pars] 
    287         args = self.q_input.q_pointers + [self.p_res, nq] + dispersed + fixed 
    288         #print(pars) 
     269        args = [ 
     270            self.q_input.nq, # nq 
     271            0, # pd_start 
     272            1, # pd_stop 
     273            details.ctypes.data, # problem 
     274            weights.ctypes.data,  # weights 
     275            values.ctypes.data,  #pars 
     276            self.q_input.q_pointers[0], #q 
     277            self.p_res,   # results 
     278            real(cutoff), # cutoff 
     279            ] 
    289280        self.kernel(*args) 
    290281 
    291         return self.res 
     282        return self.res[:-3] 
    292283 
    293284    def release(self): 
  • sasmodels/models/cylinder.c

    r50e1e40 r03cac08  
    33double Iqxy(double qx, double qy, double sld, double solvent_sld, 
    44    double radius, double length, double theta, double phi); 
     5 
     6#define INVALID(v) (v.radius<0 || v.length<0) 
    57 
    68double form_volume(double radius, double length) 
     
    1517    double length) 
    1618{ 
    17     // TODO: return NaN if radius<0 or length<0? 
    1819    // precompute qr and qh to save time in the loop 
    1920    const double qr = q*radius; 
     
    4748    double phi) 
    4849{ 
    49     // TODO: return NaN if radius<0 or length<0? 
    5050    double sn, cn; // slots to hold sincos function output 
    5151 
  • sasmodels/models/gel_fit.c

    r30b4ddf r03cac08  
    1 double form_volume(void); 
    2  
    3 double Iq(double q, 
    4           double guinier_scale, 
    5           double lorentzian_scale, 
    6           double gyration_radius, 
    7           double fractal_exp, 
    8           double cor_length); 
    9  
    10 double Iqxy(double qx, double qy, 
    11           double guinier_scale, 
    12           double lorentzian_scale, 
    13           double gyration_radius, 
    14           double fractal_exp, 
    15           double cor_length); 
    16  
    17 static double _gel_fit_kernel(double q, 
     1static double Iq(double q, 
    182          double guinier_scale, 
    193          double lorentzian_scale, 
     
    248    // Lorentzian Term 
    259    ////////////////////////double a(x[i]*x[i]*zeta*zeta); 
    26     double lorentzian_term = q*q*cor_length*cor_length; 
     10    double lorentzian_term = square(q*cor_length); 
    2711    lorentzian_term = 1.0 + ((fractal_exp + 1.0)/3.0)*lorentzian_term; 
    2812    lorentzian_term = pow(lorentzian_term, (fractal_exp/2.0) ); 
     
    3014    // Exponential Term 
    3115    ////////////////////////double d(x[i]*x[i]*rg*rg); 
    32     double exp_term = q*q*gyration_radius*gyration_radius; 
     16    double exp_term = square(q*gyration_radius); 
    3317    exp_term = exp(-1.0*(exp_term/3.0)); 
    3418 
     
    3721    return result; 
    3822} 
    39 double form_volume(void){ 
    40     // Unused, so free to return garbage. 
    41     return NAN; 
    42 } 
    43  
    44 double Iq(double q, 
    45           double guinier_scale, 
    46           double lorentzian_scale, 
    47           double gyration_radius, 
    48           double fractal_exp, 
    49           double cor_length) 
    50 { 
    51     return _gel_fit_kernel(q, 
    52                           guinier_scale, 
    53                           lorentzian_scale, 
    54                           gyration_radius, 
    55                           fractal_exp, 
    56                           cor_length); 
    57 } 
    58  
    59 // Iqxy is never called since no orientation or magnetic parameters. 
    60 double Iqxy(double qx, double qy, 
    61           double guinier_scale, 
    62           double lorentzian_scale, 
    63           double gyration_radius, 
    64           double fractal_exp, 
    65           double cor_length) 
    66 { 
    67     double q = sqrt(qx*qx + qy*qy); 
    68     return _gel_fit_kernel(q, 
    69                           guinier_scale, 
    70                           lorentzian_scale, 
    71                           gyration_radius, 
    72                           fractal_exp, 
    73                           cor_length); 
    74 } 
    75  
  • sasmodels/product.py

    r72a081d r303d8d6  
    9999        # a parameter map. 
    100100        par_map = {} 
    101         p_info = p_kernel.info['partype'] 
    102         s_info = s_kernel.info['partype'] 
     101        p_info = p_kernel.info['par_type'] 
     102        s_info = s_kernel.info['par_type'] 
    103103        vol_pars = set(p_info['volume']) 
    104104        if dim == '2d': 
  • sasmodels/resolution.py

    ra146eaa r303d8d6  
    504504    from scipy.integrate import romberg 
    505505 
    506     if any(k not in form.info['defaults'] for k in pars.keys()): 
    507         keys = set(form.info['defaults'].keys()) 
    508         extra = set(pars.keys()) - keys 
     506    par_set = set([p.name for p in form.info['parameters']]) 
     507    if any(k not in par_set for k in pars.keys()): 
     508        extra = set(pars.keys()) - par_set 
    509509        raise ValueError("bad parameters: [%s] not in [%s]"% 
    510510                         (", ".join(sorted(extra)), ", ".join(sorted(keys)))) 
     
    558558    from scipy.integrate import romberg 
    559559 
    560     if any(k not in form.info['defaults'] for k in pars.keys()): 
    561         keys = set(form.info['defaults'].keys()) 
    562         extra = set(pars.keys()) - keys 
     560    par_set = set([p.name for p in form.info['parameters']]) 
     561    if any(k not in par_set for k in pars.keys()): 
     562        extra = set(pars.keys()) - par_set 
    563563        raise ValueError("bad parameters: [%s] not in [%s]"% 
    564564                         (", ".join(sorted(extra)), ", ".join(sorted(keys)))) 
Note: See TracChangeset for help on using the changeset viewer.