Changeset ce27e21 in sasmodels for sasmodels/gen.py


Ignore:
Timestamp:
Aug 24, 2014 7:18:14 PM (10 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:
1780d59
Parents:
14de349
Message:

first pass for sasview wrapper around opencl models

File:
1 edited

Legend:

Unmodified
Added
Removed
  • sasmodels/gen.py

    r14de349 rce27e21  
    99F64 = np.dtype('float64') 
    1010F32 = np.dtype('float32') 
     11 
     12# Scale and background, which are parameters common to every form factor 
     13COMMON_PARAMETERS = [ 
     14    [ "scale", "", 1, [0, np.inf], "", "Source intensity" ], 
     15    [ "background", "1/cm", 0, [0, np.inf], "", "Source background" ], 
     16    ] 
     17 
    1118 
    1219# Conversion from units defined in the parameter table for each model 
     
    2936 
    3037PARTABLE_VALUE_WIDTH = 10 
    31  
    32 # Scale and background, which are parameters common to every form factor 
    33 COMMON_PARAMETERS = [ 
    34     [ "scale", "", 0, [0, np.inf], "", "Source intensity" ], 
    35     [ "background", "1/cm", 0, [0, np.inf], "", "Source background" ], 
    36     ] 
    37  
    3838 
    3939# Header included before every kernel. 
     
    6060#  define kernel 
    6161#  define SINCOS(angle,svar,cvar) do {svar=sin(angle);cvar=cos(angle);} while (0) 
     62#  define powr(a,b) pow(a,b) 
    6263#else 
    6364#  ifdef USE_SINCOS 
     
    9394# respectively, so the template builder will need to do extra work to 
    9495# declare, initialize and pass the q parameters. 
    95 IQ_KERNEL = { 
     96KERNEL_1D = { 
    9697    'fn': "Iq", 
    9798    'q_par_decl': "global const real *q,", 
     
    100101    } 
    101102 
    102 IQXY_KERNEL = { 
     103KERNEL_2D = { 
    103104    'fn': "Iqxy", 
    104105    'q_par_decl': "global const real *qx,\n    global const real *qy,", 
     
    176177# Volume normalization. 
    177178# If there are "volume" polydispersity parameters, then these will be used 
    178 # to call the volume function from the user supplied kernel, and accumulate 
     179# to call the form_volume function from the user supplied kernel, and accumulate 
    179180# a normalized weight. 
    180181VOLUME_NORM="""const real vol_weight = %(weight)s; 
    181   vol += vol_weight*volume(%(pars)s); 
     182  vol += vol_weight*form_volume(%(pars)s); 
    182183  norm_vol += vol_weight;""" 
     184 
    183185 
    184186def indent(s, depth): 
     
    191193 
    192194 
    193 def make_kernel(meta, form): 
     195def kernel_name(info, is_2D): 
     196    return info['name'] + "_" + ("Iqxy" if is_2D else "Iq") 
     197 
     198 
     199def make_kernel(info, is_2D): 
    194200    """ 
    195201    Build a kernel call from metadata supplied by the user. 
    196202 
    197     *meta* is the json object defined in the kernel file. 
     203    *info* is the json object defined in the kernel file. 
    198204 
    199205    *form* is either "Iq" or "Iqxy". 
     
    206212    # If we are building the Iqxy kernel, we need to propagate qx,qy 
    207213    # parameters, otherwise we can 
    208     if form == "Iqxy": 
    209         qpars = IQXY_KERNEL 
    210     else: 
    211         qpars = IQ_KERNEL 
    212  
     214    dim = "2d" if is_2D else "1d" 
     215    fixed_pars = info['partype']['fixed-'+dim] 
     216    pd_pars = info['partype']['pd-'+dim] 
     217    vol_pars = info['partype']['volume'] 
     218    q_pars = KERNEL_2D if is_2D else KERNEL_1D 
     219 
     220    # Build polydispersity loops 
    213221    depth = 4 
    214222    offset = "" 
    215223    loop_head = [] 
    216224    loop_end = [] 
    217     vol_pars = [] 
    218     fixed_pars = [] 
    219     pd_pars = [] 
    220     fn_pars = [] 
    221     for i,p in enumerate(meta['parameters']): 
    222         name = p[0] 
    223         ptype = p[4] 
    224         if ptype == "volume": 
    225             vol_pars.append(name) 
    226         elif ptype == "orientation": 
    227             if form != "Iqxy": continue  # no orientation for 1D kernels 
    228         elif ptype == "magnetic": 
    229             raise NotImplementedError("no magnetic parameters yet") 
    230         if name not in ['scale','background']: fn_pars.append(name) 
    231         if ptype == "": 
    232             fixed_pars.append(name) 
    233             continue 
    234         else: 
    235             pd_pars.append(name) 
     225    for name in pd_pars: 
    236226        subst = { 'name': name, 'offset': offset } 
    237227        loop_head.append(indent(LOOP_OPEN%subst, depth)) 
     
    254244 
    255245    # Define the inner loop function call 
     246    # The parameters to the f(q,p1,p2...) call should occur in the same 
     247    # order as given in the parameter info structure.  This may be different 
     248    # from the parameter order in the call to the kernel since the kernel 
     249    # call places all fixed parameters before all polydisperse parameters. 
     250    fq_pars = [p[0] for p in info['parameters'][len(COMMON_PARAMETERS):] 
     251               if p[0] in set(fixed_pars+pd_pars)] 
    256252    subst = { 
    257253        'weight_product': "*".join(p+"_w" for p in pd_pars), 
    258254        'volume_norm': volume_norm, 
    259         'fn': qpars['fn'], 
    260         'qcall': qpars['qcall'], 
    261         'pcall': ", ".join(fn_pars), 
     255        'fn': q_pars['fn'], 
     256        'qcall': q_pars['qcall'], 
     257        'pcall': ", ".join(fq_pars), # skip scale and background 
    262258        } 
    263259    loop_body = [indent(LOOP_BODY%subst, depth)] 
     
    280276    subst = { 
    281277        # kernel name is, e.g., cylinder_Iq 
    282         'name': "_".join((meta['name'], qpars['fn'])), 
     278        'name': kernel_name(info, is_2D), 
    283279        # to declare, e.g., global real q[], 
    284         'q_par_decl': qpars['q_par_decl'], 
     280        'q_par_decl': q_pars['q_par_decl'], 
    285281        # to declare, e.g., real sld, int Nradius, int Nlength 
    286282        'par_decl': par_decl, 
     
    288284        'pd_length': "+".join('N'+p for p in pd_pars), 
    289285        # the q initializers, e.g., real qi = q[i]; 
    290         'qinit': qpars['qinit'], 
     286        'qinit': q_pars['qinit'], 
    291287        # the actual polydispersity loop 
    292288        'loops': loops, 
     
    295291    return kernel 
    296292 
    297 def make_partable(meta): 
    298     pars = meta['parameters'] 
     293def make_partable(info): 
     294    pars = info['parameters'] 
    299295    column_widths = [ 
    300296        max(len(p[0]) for p in pars), 
     
    320316    return "\n".join(lines) 
    321317 
    322 def make_doc(kernelfile, meta, doc): 
    323     doc = doc%{'parameters': make_partable(meta)} 
     318def make_doc(kernelfile, info, doc): 
     319    doc = doc%{'parameters': make_partable(info)} 
    324320    return doc 
    325321 
    326 def make_model(kernelfile, meta, source): 
    327     kernel_Iq = make_kernel(meta, "Iq") 
    328     kernel_Iqxy = make_kernel(meta, "Iqxy") 
     322def make_model(kernelfile, info, source): 
     323    kernel_Iq = make_kernel(info, is_2D=False) 
     324    kernel_Iqxy = make_kernel(info, is_2D=True) 
    329325    path = os.path.dirname(kernelfile) 
    330     extra = [open("%s/%s"%(path,f)).read() for f in meta['include']] 
     326    extra = [open("%s/%s"%(path,f)).read() for f in info['include']] 
    331327    kernel = "\n\n".join([KERNEL_HEADER]+extra+[source, kernel_Iq, kernel_Iqxy]) 
    332328    return kernel 
     
    339335    if len(parts) != 3: 
    340336        raise ValueError("PARAMETERS block missing from %r"%kernelfile) 
    341     meta_source = parts[1].strip() 
     337    info_source = parts[1].strip() 
    342338    try: 
    343         meta = relaxed_loads(meta_source) 
     339        info = relaxed_loads(info_source) 
    344340    except: 
    345341        print "in json text:" 
    346342        print "\n".join("%2d: %s"%(i+1,s) 
    347                         for i,s in enumerate(meta_source.split('\n'))) 
     343                        for i,s in enumerate(info_source.split('\n'))) 
    348344        raise 
    349345        #raise ValueError("PARAMETERS block could not be parsed from %r"%kernelfile) 
    350     meta['parameters'] = COMMON_PARAMETERS + meta['parameters'] 
    351     meta['filename'] = kernelfile 
    352346 
    353347    # select documentation out of the source file 
    354348    parts = source.split("DOCUMENTATION") 
    355349    if len(parts) == 3: 
    356         doc = make_doc(kernelfile, meta, parts[1].strip()) 
     350        doc = make_doc(kernelfile, info, parts[1].strip()) 
    357351    elif len(parts) == 1: 
    358352        raise ValueError("DOCUMENTATION block is missing from %r"%kernelfile) 
     
    360354        raise ValueError("DOCUMENTATION block incorrect from %r"%kernelfile) 
    361355 
    362     return source, meta, doc 
     356    return source, info, doc 
     357 
     358def categorize_parameters(pars): 
     359    """ 
     360    Build parameter categories out of the the parameter definitions. 
     361 
     362    Returns a dictionary of categories. 
     363 
     364    The function call sequence consists of q inputs and the return vector, 
     365    followed by the loop value/weight vector, followed by the values for 
     366    the non-polydisperse parameters, followed by the lengths of the 
     367    polydispersity loops.  To construct the call for 1D models, the 
     368    categories *fixed-1d* and *pd-1d* list the names of the parameters 
     369    of the non-polydisperse and the polydisperse parameters respectively. 
     370    Similarly, *fixed-2d* and *pd-2d* provide parameter names for 2D models. 
     371    The *pd-rel* category is a set of those parameters which give 
     372    polydispersitiy as a portion of the value (so a 10% length dispersity 
     373    would use a polydispersity value of 0.1) rather than absolute 
     374    dispersity such as an angle plus or minus 15 degrees. 
     375 
     376    The *volume* category lists the volume parameters in order for calls 
     377    to volume within the kernel (used for volume normalization) and for 
     378    calls to ER and VR for effective radius and volume ratio respectively. 
     379 
     380    The *orientation* and *magnetic* categories list the orientation and 
     381    magnetic parameters.  These are used by the sasview interface.  The 
     382    blank category is for parameters such as scale which don't have any 
     383    other marking. 
     384    """ 
     385    partype = { 
     386        'volume': [], 'orientation': [], 'magnetic': [], '': [], 
     387        'fixed-1d': [], 'fixed-2d': [], 'pd-1d': [], 'pd-2d': [], 
     388        'pd-rel': set(), 
     389    } 
     390 
     391    for p in pars: 
     392        name,ptype = p[0],p[4] 
     393        if ptype == 'volume': 
     394            partype['pd-1d'].append(name) 
     395            partype['pd-2d'].append(name) 
     396            partype['pd-rel'].add(name) 
     397        elif ptype == 'magnetic': 
     398            partype['fixed-2d'].append(name) 
     399        elif ptype == 'orientation': 
     400            partype['pd-2d'].append(name) 
     401        elif ptype == '': 
     402            partype['fixed-1d'].append(name) 
     403            partype['fixed-2d'].append(name) 
     404        else: 
     405            raise ValueError("unknown parameter type %r"%ptype) 
     406        partype[ptype].append(name) 
     407 
     408    return partype 
    363409 
    364410def make(kernelfile): 
     
    370416    """ 
    371417    #print kernelfile 
    372     source, meta, doc = parse_file(kernelfile) 
    373     doc = make_doc(kernelfile, meta, doc) 
    374     model = make_model(kernelfile, meta, source) 
    375     return model, meta, doc 
    376  
    377  
    378  
    379 # Convert from python float to C float or double, depending on dtype 
    380 FLOAT_CONVERTER = { 
    381     F32: np.float32, 
    382     F64: np.float64, 
    383     } 
    384  
    385 def kernel_name(meta, is_2D): 
    386     return meta['name'] + "_" + ("Iqxy" if is_2D else "Iq") 
    387  
    388  
    389 def kernel_pars(pars, par_info, is_2D, dtype=F32): 
    390     """ 
    391     Convert parameter dictionary into arguments for the kernel. 
    392  
    393     *pars* is a dictionary of *{ name: value }*, with *name_pd* for the 
    394     polydispersity width, *name_pd_n* for the number of pd steps, and 
    395     *name_pd_nsigma* for the polydispersity limits. 
    396  
    397     *par_info* is the parameter info structure from the kernel metadata. 
    398  
    399     *is_2D* is True if the dataset represents 2D data, with the corresponding 
    400     orientation parameters. 
    401  
    402     *dtype* is F32 or F64, the numpy single and double precision floating 
    403     point dtypes.  These should not be the strings. 
    404     """ 
    405     from .weights import GaussianDispersion 
    406     real = np.float32 if dtype == F32 else np.float64 
    407     fixed = [] 
    408     parts = [] 
    409     for p in par_info['parameters']: 
    410         name, ptype = p[0],p[4] 
    411         value = pars[name] 
    412         if ptype == "": 
    413             fixed.append(real(value)) 
    414         elif is_2D or ptype != "orientation": 
    415             limits, width = p[3], pars[name+'_pd'] 
    416             n, nsigma = pars[name+'_pd_n'], pars[name+'_pd_nsigma'] 
    417             relative = (ptype != "orientation") 
    418             dist = GaussianDispersion(int(n), width, nsigma) 
    419             # Make sure that weights are normalized to peaks at 1 so that 
    420             # the tolerance term can be used properly on truncated distributions 
    421             v,w = dist.get_weights(value, limits[0], limits[1], relative) 
    422             parts.append((v, w/w.max())) 
    423     loops = np.hstack(parts) 
    424     loops = np.ascontiguousarray(loops.T, dtype).flatten() 
    425     loopsN = [np.uint32(len(p[0])) for p in parts] 
    426     return fixed, loops, loopsN 
     418    source, info, doc = parse_file(kernelfile) 
     419    info['filename'] = kernelfile 
     420    info['parameters'] = COMMON_PARAMETERS + info['parameters'] 
     421    info['partype'] = categorize_parameters(info['parameters']) 
     422    info['limits'] = dict((p[0],p[3]) for p in info['parameters']) 
     423    doc = make_doc(kernelfile, info, doc) 
     424    model = make_model(kernelfile, info, source) 
     425    return model, info, doc 
    427426 
    428427 
     
    437436def demo(): 
    438437    from os.path import join as joinpath, dirname 
    439     c, meta, doc = make_model(joinpath(dirname(__file__), "models", "cylinder.c")) 
     438    c, info, doc = make_model(joinpath(dirname(__file__), "models", "cylinder.c")) 
    440439    #print doc 
    441440    #print c 
Note: See TracChangeset for help on using the changeset viewer.