Changeset c0c3393 in sasmodels


Ignore:
Timestamp:
Mar 21, 2016 4:57:19 AM (9 years ago)
Author:
wojciech
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:
9f51b0e
Parents:
d148fbc (diff), 7ff3cf3 (diff)
Note: this is a merge changeset, the changes displayed below correspond to the merge itself.
Use the (diff) links above to see all the changes relative to each parent.
Message:

Merge branch 'polydisp' of https://github.com/SasView/sasmodels into polydisp

Location:
sasmodels
Files:
2 added
5 edited

Legend:

Unmodified
Added
Removed
  • sasmodels/core.py

    r303d8d6 r7ff3cf3  
    226226        weights = np.array([1.0]) 
    227227        details = kernel.info['mono_details'] 
    228         return kernel(pars, weights, details, cutoff) 
     228        return kernel(details, pars, weights, cutoff) 
    229229    else: 
    230230        pairs = [get_weights(p, values) for p in kernel.info['parameters']] 
     
    232232        details = generate.poly_details(kernel.info, weights, pars) 
    233233        weights, pars = [np.hstack(v) for v in (weights, pars)] 
    234         return kernel(pars, weights, details, cutoff) 
     234        return kernel(details, pars, weights, cutoff) 
    235235 
    236236def call_ER_VR(model_info, vol_pars): 
  • sasmodels/generate.py

    r4a72d1a r7ff3cf3  
    650650    model_info['has_2d'] = par_type['orientation'] or par_type['magnetic'] 
    651651 
    652 def mono_details(max_pd, npars): 
     652def mono_details(model_info): 
     653    max_pd = model_info['max_pd'] 
     654    npars = len(model_info['parameters']) - 2 
    653655    p = 5*max_pd 
    654656    c = p + 3*npars 
    655657 
    656     mono = np.zeros(c + 3, 'i') 
    657     mono[0*max_pd:1*max_pd] = range(max_pd)       # pd_par: arbitrary order; use first 
    658     mono[1*max_pd:2*max_pd] = [1]*max_pd          # pd_length: only one element 
    659     mono[2*max_pd:3*max_pd] = range(2, max_pd+2)  # pd_offset: skip scale and background 
    660     mono[3*max_pd:4*max_pd] = [1]*max_pd          # pd_stride: vectors of length 1 
    661     mono[4*max_pd:5*max_pd] = [0]*max_pd          # pd_isvol: doens't matter if no norm 
    662     mono[p+0*npars:p+1*npars] = range(2, npars+2) # par_offset 
    663     mono[p+1*npars:p+2*npars] = [0]*npars         # no coordination 
    664     #mono[p+npars] = 1 # par_coord[0] is coordinated with the first par? 
    665     mono[p+2*npars:p+3*npars] = 0 # fast coord with 0 
    666     mono[c]   = 1     # fast_coord_count: one fast index 
    667     mono[c+1] = -1  # theta_var: None 
    668     mono[c+2] = 0   # fast_theta: False 
    669     return mono 
    670  
    671 def poly_details(model_info, weights, pars, constraints=None): 
     658    details = np.zeros(c + 3, 'int32') 
     659    details[0*max_pd:1*max_pd] = range(max_pd)       # pd_par: arbitrary order; use first 
     660    details[1*max_pd:2*max_pd] = [1]*max_pd          # pd_length: only one element 
     661    details[2*max_pd:3*max_pd] = range(2, max_pd+2)  # pd_offset: skip scale and background 
     662    details[3*max_pd:4*max_pd] = [1]*max_pd          # pd_stride: vectors of length 1 
     663    details[4*max_pd:5*max_pd] = [0]*max_pd          # pd_isvol: doens't matter if no norm 
     664    details[p+0*npars:p+1*npars] = range(2, npars+2) # par_offset 
     665    details[p+1*npars:p+2*npars] = [0]*npars         # no coordination 
     666    #details[p+npars] = 1 # par_coord[0] is coordinated with the first par? 
     667    details[p+2*npars:p+3*npars] = 0 # fast coord with 0 
     668    details[c]   = 1     # fast_coord_count: one fast index 
     669    details[c+1] = -1  # theta_var: None 
     670    details[c+2] = 0   # fast_theta: False 
     671    return details 
     672 
     673def poly_details(model_info, weights, constraints=None): 
    672674    if constraints is not None: 
    673675        # Need to find the independently varying pars and sort them 
     
    676678        # and weights, returning par blocks 
    677679        raise ValueError("Can't handle constraints yet") 
     680 
     681    max_pd = model_info['max_pd'] 
     682    npars = len(model_info['parameters']) - 2 
     683    p = 5*max_pd 
     684    c = p + 3*npars 
     685 
     686    # Decreasing list of polydispersity lengths 
     687    # Note: the reversing view, x[::-1], does not require a copy 
     688    idx = np.argsort([len(w) for w in weights])[::-1] 
     689    details = np.zeros(c + 3, 'int32') 
     690    details[0*max_pd:1*max_pd] = idx[:max_pd]        # pd_par: arbitrary order; use first 
     691    details[1*max_pd:2*max_pd] = [1]*max_pd          # pd_length: only one element 
     692    details[2*max_pd:3*max_pd] = range(2, max_pd+2)  # pd_offset: skip scale and background 
     693    details[3*max_pd:4*max_pd] = [1]*max_pd          # pd_stride: vectors of length 1 
     694    details[4*max_pd:5*max_pd] = [0]*max_pd          # pd_isvol: doens't matter if no norm 
     695    details[p+0*npars:p+1*npars] = range(2, npars+2) # par_offset 
     696    details[p+1*npars:p+2*npars] = [0]*npars         # no coordination 
     697    #details[p+npars] = 1 # par_coord[0] is coordinated with the first par? 
     698    details[p+2*npars:p+3*npars] = 0 # fast coord with 0 
     699    details[c]   = 1     # fast_coord_count: one fast index 
     700    details[c+1] = -1  # theta_var: None 
     701    details[c+2] = 0   # fast_theta: False 
     702    return details 
     703 
     704 
    678705 
    679706    raise ValueError("polydispersity not supported") 
     
    768795        oldpars=getattr(kernel_module, 'oldpars', {}), 
    769796        tests=getattr(kernel_module, 'tests', []), 
    770         mono_details = mono_details(MAX_PD, len(kernel_module.parameters)) 
    771797        ) 
    772798    process_parameters(model_info) 
     
    775801    model_info.update((k, getattr(kernel_module, k, None)) for k in functions) 
    776802    create_default_functions(model_info) 
     803    # Precalculate the monodisperse parameters 
     804    # TODO: make this a lazy evaluator 
     805    # make_model_info is called for every model on sasview startup 
     806    model_info['mono_details'] = mono_details(model_info) 
    777807    return model_info 
    778808 
  • sasmodels/kernel_iq.c

    re1bdc7e r7ff3cf3  
    5050    ) 
    5151{ 
    52 printf("aliasing\n"); 
    5352  // Storage for the current parameter values.  These will be updated as we 
    5453  // walk the polydispersity cube. 
     
    5655  double *pvec = (double *)(&local_pars);  // Alias named parameters with a vector 
    5756 
    58 printf("allocating\n"); 
    5957  local int offset[NPARS-2]; 
    6058 
    6159#if 1 // defined(USE_SHORTCUT_OPTIMIZATION) 
    62 printf("dereferencing %p as %d\n", problem, problem->pd_length[0]); 
    6360  if (problem->pd_length[0] == 1) { 
    6461    // Shouldn't need to copy!! 
  • sasmodels/kerneldll.py

    r4a72d1a r7ff3cf3  
    5050import tempfile 
    5151import ctypes as ct 
    52 from ctypes import c_void_p, c_int, c_longdouble, c_double, c_float 
     52from ctypes import c_void_p, c_int32, c_longdouble, c_double, c_float 
    5353 
    5454import numpy as np 
     
    205205 
    206206        # int, int, int, int*, double*, double*, double*, double*, double*, double 
    207         argtypes = [c_int]*3 + [c_void_p]*5 + [fp] 
     207        argtypes = [c_int32]*3 + [c_void_p]*5 + [fp] 
    208208        self.Iq = self.dll[generate.kernel_name(self.info, False)] 
    209209        self.Iqxy = self.dll[generate.kernel_name(self.info, True)] 
  • sasmodels/kernelpy.py

    rd5ac45f r31d22de  
    128128 
    129129    def __call__(self, fixed, pd, cutoff=1e-5): 
    130         print("fixed",fixed) 
    131         print("pd", pd) 
     130        #print("fixed",fixed) 
     131        #print("pd", pd) 
    132132        args = self.args[:]  # grab a copy of the args 
    133133        form, form_volume = self.kernel, self.info['form_volume'] 
     
    187187    ################################################################ 
    188188 
    189     #TODO: Wojtek's notes 
    190     #TODO: The goal is to restructure polydispersity loop 
    191     #so it allows fitting arbitrary polydispersity parameters 
    192     #and it accepts cases like coupled parameters 
    193189    weight = np.empty(len(pd), 'd') 
    194190    if weight.size > 0: 
     
    264260    result = scale * ret / norm + background 
    265261    return result 
    266  
    267 """ 
    268 Python driver for python kernels 
    269  
    270 Calls the kernel with a vector of $q$ values for a single parameter set. 
    271 Polydispersity is supported by looping over different parameter sets and 
    272 summing the results.  The interface to :class:`PyModel` matches those for 
    273 :class:`kernelcl.GpuModel` and :class:`kerneldll.DllModel`. 
    274 """ 
    275 import numpy as np 
    276 from numpy import pi, cos 
    277  
    278 from .generate import F64 
    279  
    280 class PyModel(object): 
    281     """ 
    282     Wrapper for pure python models. 
    283     """ 
    284     def __init__(self, model_info): 
    285         self.info = model_info 
    286  
    287     def __call__(self, q_vectors): 
    288         q_input = PyInput(q_vectors, dtype=F64) 
    289         kernel = self.info['Iqxy'] if q_input.is_2d else self.info['Iq'] 
    290         return PyKernel(kernel, self.info, q_input) 
    291  
    292     def release(self): 
    293         """ 
    294         Free resources associated with the model. 
    295         """ 
    296         pass 
    297  
    298 class PyInput(object): 
    299     """ 
    300     Make q data available to the gpu. 
    301  
    302     *q_vectors* is a list of q vectors, which will be *[q]* for 1-D data, 
    303     and *[qx, qy]* for 2-D data.  Internally, the vectors will be reallocated 
    304     to get the best performance on OpenCL, which may involve shifting and 
    305     stretching the array to better match the memory architecture.  Additional 
    306     points will be evaluated with *q=1e-3*. 
    307  
    308     *dtype* is the data type for the q vectors. The data type should be 
    309     set to match that of the kernel, which is an attribute of 
    310     :class:`GpuProgram`.  Note that not all kernels support double 
    311     precision, so even if the program was created for double precision, 
    312     the *GpuProgram.dtype* may be single precision. 
    313  
    314     Call :meth:`release` when complete.  Even if not called directly, the 
    315     buffer will be released when the data object is freed. 
    316     """ 
    317     def __init__(self, q_vectors, dtype): 
    318         self.nq = q_vectors[0].size 
    319         self.dtype = dtype 
    320         self.is_2d = (len(q_vectors) == 2) 
    321         self.q_vectors = [np.ascontiguousarray(q, self.dtype) for q in q_vectors] 
    322         self.q_pointers = [q.ctypes.data for q in self.q_vectors] 
    323  
    324     def release(self): 
    325         """ 
    326         Free resources associated with the model inputs. 
    327         """ 
    328         self.q_vectors = [] 
    329  
    330 class PyKernel(object): 
    331     """ 
    332     Callable SAS kernel. 
    333  
    334     *kernel* is the DllKernel object to call. 
    335  
    336     *model_info* is the module information 
    337  
    338     *q_input* is the DllInput q vectors at which the kernel should be 
    339     evaluated. 
    340  
    341     The resulting call method takes the *pars*, a list of values for 
    342     the fixed parameters to the kernel, and *pd_pars*, a list of (value,weight) 
    343     vectors for the polydisperse parameters.  *cutoff* determines the 
    344     integration limits: any points with combined weight less than *cutoff* 
    345     will not be calculated. 
    346  
    347     Call :meth:`release` when done with the kernel instance. 
    348     """ 
    349     def __init__(self, kernel, model_info, q_input): 
    350         self.info = model_info 
    351         self.q_input = q_input 
    352         self.res = np.empty(q_input.nq, q_input.dtype) 
    353         dim = '2d' if q_input.is_2d else '1d' 
    354         # Loop over q unless user promises that the kernel is vectorized by 
    355         # taggining it with vectorized=True 
    356         if not getattr(kernel, 'vectorized', False): 
    357             if dim == '2d': 
    358                 def vector_kernel(qx, qy, *args): 
    359                     """ 
    360                     Vectorized 2D kernel. 
    361                     """ 
    362                     return np.array([kernel(qxi, qyi, *args) 
    363                                      for qxi, qyi in zip(qx, qy)]) 
    364             else: 
    365                 def vector_kernel(q, *args): 
    366                     """ 
    367                     Vectorized 1D kernel. 
    368                     """ 
    369                     return np.array([kernel(qi, *args) 
    370                                      for qi in q]) 
    371             self.kernel = vector_kernel 
    372         else: 
    373             self.kernel = kernel 
    374         fixed_pars = model_info['partype']['fixed-' + dim] 
    375         pd_pars = model_info['partype']['pd-' + dim] 
    376         vol_pars = model_info['partype']['volume'] 
    377  
    378         # First two fixed pars are scale and background 
    379         pars = [p.name for p in model_info['parameters'][2:]] 
    380         offset = len(self.q_input.q_vectors) 
    381         self.args = self.q_input.q_vectors + [None] * len(pars) 
    382         self.fixed_index = np.array([pars.index(p) + offset 
    383                                      for p in fixed_pars[2:]]) 
    384         self.pd_index = np.array([pars.index(p) + offset 
    385                                   for p in pd_pars]) 
    386         self.vol_index = np.array([pars.index(p) + offset 
    387                                    for p in vol_pars]) 
    388         try: self.theta_index = pars.index('theta') + offset 
    389         except ValueError: self.theta_index = -1 
    390  
    391         # Caller needs fixed_pars and pd_pars 
    392         self.fixed_pars = fixed_pars 
    393         self.pd_pars = pd_pars 
    394  
    395     def __call__(self, fixed, pd, cutoff=1e-5): 
    396         #print("fixed",fixed) 
    397         #print("pd", pd) 
    398         args = self.args[:]  # grab a copy of the args 
    399         form, form_volume = self.kernel, self.info['form_volume'] 
    400         # First two fixed 
    401         scale, background = fixed[:2] 
    402         for index, value in zip(self.fixed_index, fixed[2:]): 
    403             args[index] = float(value) 
    404         res = _loops(form, form_volume, cutoff, scale, background, args, 
    405                      pd, self.pd_index, self.vol_index, self.theta_index) 
    406  
    407         return res 
    408  
    409     def release(self): 
    410         """ 
    411         Free resources associated with the kernel. 
    412         """ 
    413         self.q_input = None 
    414  
    415 def _loops(form, form_volume, cutoff, scale, background, 
    416            args, pd, pd_index, vol_index, theta_index): 
    417     """ 
    418     *form* is the name of the form function, which should be vectorized over 
    419     q, but otherwise have an interface like the opencl kernels, with the 
    420     q parameters first followed by the individual parameters in the order 
    421     given in model.parameters (see :mod:`sasmodels.generate`). 
    422  
    423     *form_volume* calculates the volume of the shape.  *vol_index* gives 
    424     the list of volume parameters 
    425  
    426     *cutoff* ignores the corners of the dispersion hypercube 
    427  
    428     *scale*, *background* multiplies the resulting form and adds an offset 
    429  
    430     *args* is the prepopulated set of arguments to the form function, starting 
    431     with the q vectors, and including slots for all the parameters.  The 
    432     values for the parameters will be substituted with values from the 
    433     dispersion functions. 
    434  
    435     *pd* is the list of dispersion parameters 
    436  
    437     *pd_index* are the indices of the dispersion parameters in *args* 
    438  
    439     *vol_index* are the indices of the volume parameters in *args* 
    440  
    441     *theta_index* is the index of the theta parameter for the sasview 
    442     spherical correction, or -1 if there is no angular dispersion 
    443     """ 
    444  
    445     ################################################################ 
    446     #                                                              # 
    447     #   !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!   # 
    448     #   !!                                                    !!   # 
    449     #   !!  KEEP THIS CODE CONSISTENT WITH KERNEL_TEMPLATE.C  !!   # 
    450     #   !!                                                    !!   # 
    451     #   !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!   # 
    452     #                                                              # 
    453     ################################################################ 
    454  
    455     weight = np.empty(len(pd), 'd') 
    456     if weight.size > 0: 
    457         # weight vector, to be populated by polydispersity loops 
    458  
    459         # identify which pd parameters are volume parameters 
    460         vol_weight_index = np.array([(index in vol_index) for index in pd_index]) 
    461  
    462         # Sort parameters in decreasing order of pd length 
    463         Npd = np.array([len(pdi[0]) for pdi in pd], 'i') 
    464         order = np.argsort(Npd)[::-1] 
    465         stride = np.cumprod(Npd[order]) 
    466         pd = [pd[index] for index in order] 
    467         pd_index = pd_index[order] 
    468         vol_weight_index = vol_weight_index[order] 
    469  
    470         fast_value = pd[0][0] 
    471         fast_weight = pd[0][1] 
    472     else: 
    473         stride = np.array([1]) 
    474         vol_weight_index = slice(None, None) 
    475         # keep lint happy 
    476         fast_value = [None] 
    477         fast_weight = [None] 
    478  
    479     ret = np.zeros_like(args[0]) 
    480     norm = np.zeros_like(ret) 
    481     vol = np.zeros_like(ret) 
    482     vol_norm = np.zeros_like(ret) 
    483     for k in range(stride[-1]): 
    484         # update polydispersity parameter values 
    485         fast_index = k % stride[0] 
    486         if fast_index == 0:  # bottom loop complete ... check all other loops 
    487             if weight.size > 0: 
    488                 for i, index, in enumerate(k % stride): 
    489                     args[pd_index[i]] = pd[i][0][index] 
    490                     weight[i] = pd[i][1][index] 
    491         else: 
    492             args[pd_index[0]] = fast_value[fast_index] 
    493             weight[0] = fast_weight[fast_index] 
    494  
    495         # Computes the weight, and if it is not sufficient then ignore this 
    496         # parameter set. 
    497         # Note: could precompute w1*...*wn so we only need to multiply by w0 
    498         w = np.prod(weight) 
    499         if w > cutoff: 
    500             # Note: can precompute spherical correction if theta_index is not 
    501             # the fast index. Correction factor for spherical integration 
    502             #spherical_correction = abs(cos(pi*args[phi_index])) if phi_index>=0 else 1.0 
    503             spherical_correction = (abs(cos(pi * args[theta_index])) * pi / 2 
    504                                     if theta_index >= 0 else 1.0) 
    505             #spherical_correction = 1.0 
    506  
    507             # Call the scattering function and adds it to the total. 
    508             I = form(*args) 
    509             if np.isnan(I).any(): continue 
    510             ret += w * I * spherical_correction 
    511             norm += w 
    512  
    513             # Volume normalization. 
    514             # If there are "volume" polydispersity parameters, then these 
    515             # will be used to call the form_volume function from the user 
    516             # supplied kernel, and accumulate a normalized weight. 
    517             # Note: can precompute volume norm if fast index is not a volume 
    518             if form_volume: 
    519                 vol_args = [args[index] for index in vol_index] 
    520                 vol_weight = np.prod(weight[vol_weight_index]) 
    521                 vol += vol_weight * form_volume(*vol_args) 
    522                 vol_norm += vol_weight 
    523  
    524     positive = (vol * vol_norm != 0.0) 
    525     ret[positive] *= vol_norm[positive] / vol[positive] 
    526     result = scale * ret / norm + background 
    527     return result 
Note: See TracChangeset for help on using the changeset viewer.