Changeset 6e7ff6d in sasmodels for sasmodels/kernelpy.py


Ignore:
Timestamp:
Apr 7, 2016 12:01:54 PM (8 years ago)
Author:
Paul Kienzle <pkienzle@…>
Branches:
master, core_shell_microgels, costrafo411, magnetic_model, release_v0.94, release_v0.95, ticket-1257-vesicle-product, ticket_1156, ticket_1265_superball, ticket_822_more_unit_tests
Children:
3543141
Parents:
8bd7b77
Message:

reenable python models

File:
1 edited

Legend:

Unmodified
Added
Removed
  • sasmodels/kernelpy.py

    r1e2a1ba r6e7ff6d  
    8787    """ 
    8888    def __init__(self, kernel, model_info, q_input): 
     89        self.dtype = np.dtype('d') 
    8990        self.info = model_info 
    9091        self.q_input = q_input 
    9192        self.res = np.empty(q_input.nq, q_input.dtype) 
    92         dim = '2d' if q_input.is_2d else '1d' 
    93         # Loop over q unless user promises that the kernel is vectorized by 
    94         # taggining it with vectorized=True 
    95         if not getattr(kernel, 'vectorized', False): 
    96             if dim == '2d': 
    97                 def vector_kernel(qx, qy, *args): 
    98                     """ 
    99                     Vectorized 2D kernel. 
    100                     """ 
    101                     return np.array([kernel(qxi, qyi, *args) 
    102                                      for qxi, qyi in zip(qx, qy)]) 
     93        self.kernel = kernel 
     94        self.dim = '2d' if q_input.is_2d else '1d' 
     95 
     96        partable = model_info['parameters'] 
     97        kernel_parameters = (partable.iqxy_parameters if q_input.is_2d 
     98                             else partable.iq_parameters) 
     99        volume_parameters = partable.form_volume_parameters 
     100 
     101        # Create an array to hold the parameter values.  There will be a 
     102        # single array whose values are updated as the calculator goes 
     103        # through the loop.  Arguments to the kernel and volume functions 
     104        # will use views into this vector, relying on the fact that a 
     105        # an array of no dimensions acts like a scalar. 
     106        parameter_vector = np.empty(len(partable.call_parameters), 'd') 
     107 
     108        # Create views into the array to hold the arguments 
     109        offset = 2 
     110        kernel_args, volume_args = [], [] 
     111        for p in partable.kernel_parameters: 
     112            if p.length == 1: 
     113                # Scalar values are length 1 vectors with no dimensions. 
     114                v = parameter_vector[offset:offset+1].reshape(()) 
    103115            else: 
    104                 def vector_kernel(q, *args): 
    105                     """ 
    106                     Vectorized 1D kernel. 
    107                     """ 
    108                     return np.array([kernel(qi, *args) 
    109                                      for qi in q]) 
    110             self.kernel = vector_kernel 
     116                # Vector values are simple views. 
     117                v = parameter_vector[offset:offset+p.length] 
     118            offset += p.length 
     119            if p in kernel_parameters: 
     120                kernel_args.append(v) 
     121            if p in volume_parameters: 
     122                volume_args.append(p) 
     123 
     124        # Hold on to the parameter vector so we can use it to call kernel later. 
     125        # This may also be required to preserve the views into the vector. 
     126        self._parameter_vector = parameter_vector 
     127 
     128        # Generate a closure which calls the kernel with the views into the 
     129        # parameter array. 
     130        if q_input.is_2d: 
     131            form = model_info['Iqxy'] 
     132            qx, qy = q_input.q[:,0], q_input.q[:,1] 
     133            self._form = lambda: form(qx, qy, *kernel_args) 
    111134        else: 
    112             self.kernel = kernel 
    113         fixed_pars = model_info['partype']['fixed-' + dim] 
    114         pd_pars = model_info['partype']['pd-' + dim] 
    115         vol_pars = model_info['partype']['volume'] 
    116  
    117         # First two fixed pars are scale and background 
    118         pars = [p.name for p in model_info['parameters'][2:]] 
    119         offset = len(self.q_input.q_vectors) 
    120         self.args = self.q_input.q_vectors + [None] * len(pars) 
    121         self.fixed_index = np.array([pars.index(p) + offset 
    122                                      for p in fixed_pars[2:]]) 
    123         self.pd_index = np.array([pars.index(p) + offset 
    124                                   for p in pd_pars]) 
    125         self.vol_index = np.array([pars.index(p) + offset 
    126                                    for p in vol_pars]) 
    127         try: self.theta_index = pars.index('theta') + offset 
    128         except ValueError: self.theta_index = -1 
    129  
    130         # Caller needs fixed_pars and pd_pars 
    131         self.fixed_pars = fixed_pars 
    132         self.pd_pars = pd_pars 
    133  
    134     def __call__(self, fixed, pd, cutoff=1e-5): 
    135         #print("fixed",fixed) 
    136         #print("pd", pd) 
    137         args = self.args[:]  # grab a copy of the args 
    138         form, form_volume = self.kernel, self.info['form_volume'] 
    139         # First two fixed 
    140         scale, background = fixed[:2] 
    141         for index, value in zip(self.fixed_index, fixed[2:]): 
    142             args[index] = float(value) 
    143         res = _loops(form, form_volume, cutoff, scale, background, args, 
    144                      pd, self.pd_index, self.vol_index, self.theta_index) 
    145  
     135            form = model_info['Iq'] 
     136            q = q_input.q 
     137            self._form = lambda: form(q, *kernel_args) 
     138 
     139        # Generate a closure which calls the form_volume if it exists. 
     140        form_volume = model_info['form_volume'] 
     141        self._volume = ((lambda: form_volume(*volume_args)) if form_volume 
     142                        else (lambda: 1.0)) 
     143 
     144    def __call__(self, details, weights, values, cutoff): 
     145        # type: (.generate.CoordinationDetails, np.ndarray, np.ndarray, float) -> np.ndarray 
     146        res = _loops(self._parameter_vector, self._form, self._volume, 
     147                     self.q_input.nq, details, weights, values, cutoff) 
    146148        return res 
    147149 
     
    152154        self.q_input = None 
    153155 
    154 def _loops(form, form_volume, cutoff, scale, background, 
    155            args, pd, pd_index, vol_index, theta_index): 
    156     """ 
    157     *form* is the name of the form function, which should be vectorized over 
    158     q, but otherwise have an interface like the opencl kernels, with the 
    159     q parameters first followed by the individual parameters in the order 
    160     given in model.parameters (see :mod:`sasmodels.generate`). 
    161  
    162     *form_volume* calculates the volume of the shape.  *vol_index* gives 
    163     the list of volume parameters 
    164  
    165     *cutoff* ignores the corners of the dispersion hypercube 
    166  
    167     *scale*, *background* multiplies the resulting form and adds an offset 
    168  
    169     *args* is the prepopulated set of arguments to the form function, starting 
    170     with the q vectors, and including slots for all the parameters.  The 
    171     values for the parameters will be substituted with values from the 
    172     dispersion functions. 
    173  
    174     *pd* is the list of dispersion parameters 
    175  
    176     *pd_index* are the indices of the dispersion parameters in *args* 
    177  
    178     *vol_index* are the indices of the volume parameters in *args* 
    179  
    180     *theta_index* is the index of the theta parameter for the sasview 
    181     spherical correction, or -1 if there is no angular dispersion 
    182     """ 
    183  
     156def _loops(parameters,  # type: np.ndarray 
     157           form,        # type: Callable[[], np.ndarray] 
     158           form_volume, # type: Callable[[], float] 
     159           nq,          # type: int 
     160           details,     # type: .generate.CoordinationDetails 
     161           weights,     # type: np.ndarray 
     162           values,      # type: np.ndarray 
     163           cutoff,      # type: float 
     164           ):           # type: (...) -> None 
    184165    ################################################################ 
    185166    #                                                              # 
     
    191172    #                                                              # 
    192173    ################################################################ 
    193  
    194     weight = np.empty(len(pd), 'd') 
    195     if weight.size > 0: 
    196         # weight vector, to be populated by polydispersity loops 
    197  
    198         # identify which pd parameters are volume parameters 
    199         vol_weight_index = np.array([(index in vol_index) for index in pd_index]) 
    200  
    201         # Sort parameters in decreasing order of pd length 
    202         Npd = np.array([len(pdi[0]) for pdi in pd], 'i') 
    203         order = np.argsort(Npd)[::-1] 
    204         stride = np.cumprod(Npd[order]) 
    205         pd = [pd[index] for index in order] 
    206         pd_index = pd_index[order] 
    207         vol_weight_index = vol_weight_index[order] 
    208  
    209         fast_value = pd[0][0] 
    210         fast_weight = pd[0][1] 
     174    parameters[2:] = values[details.par_offset] 
     175    scale, background = values[0], values[1] 
     176    if details.num_active == 0: 
     177        norm = float(form_volume()) 
     178        if norm > 0.0: 
     179            return (scale/norm)*form() + background 
     180        else: 
     181            return np.ones(nq, 'd')*background 
     182 
     183    partial_weight = np.NaN 
     184    spherical_correction = 1.0 
     185    pd_stride = details.pd_stride[:details.num_active] 
     186    pd_length = details.pd_length[:details.num_active] 
     187    pd_offset = details.pd_offset[:details.num_active] 
     188    pd_index = np.empty_like(pd_offset) 
     189    offset = np.empty_like(details.par_offset) 
     190    theta = details.theta_par 
     191    fast_length = pd_length[0] 
     192    pd_index[0] = fast_length 
     193    total = np.zeros(nq, 'd') 
     194    norm = 0.0 
     195    for loop_index in range(details.total_pd): 
     196        # update polydispersity parameter values 
     197        if pd_index[0] == fast_length: 
     198            pd_index[:] = (loop_index/pd_stride)%pd_length 
     199            partial_weight = np.prod(weights[pd_offset+pd_index][1:]) 
     200            for k in range(details.num_coord): 
     201                par = details.par_coord[k] 
     202                coord = details.pd_coord[k] 
     203                this_offset = details.par_offset[par] 
     204                block_size = 1 
     205                for bit in xrange(32): 
     206                    if coord&1: 
     207                        this_offset += block_size * pd_index[bit] 
     208                        block_size *= pd_length[bit] 
     209                    coord >>= 1 
     210                    if coord == 0: break 
     211                offset[par] = this_offset 
     212                parameters[par] = values[this_offset] 
     213                if par == theta and not (details.par_coord[k]&1): 
     214                    spherical_correction = max(abs(cos(pi/180 * parameters[theta])), 1e-6) 
     215        for k in range(details.num_coord): 
     216            if details.pd_coord[k]&1: 
     217                par = details.par_coord[k] 
     218                parameters[par] = values[offset[par]] 
     219                offset[par] += 1 
     220                if par == theta: 
     221                    spherical_correction = max(abs(cos(pi/180 * parameters[theta])), 1e-6) 
     222 
     223        weight = partial_weight * weights[pd_offset[0] + pd_index[0]] 
     224        pd_index[0] += 1 
     225        if weight > cutoff: 
     226            # Call the scattering function 
     227            # Assume that NaNs are only generated if the parameters are bad; 
     228            # exclude all q for that NaN.  Even better would be to have an 
     229            # INVALID expression like the C models, but that is too expensive. 
     230            I = form() 
     231            if np.isnan(I).any(): continue 
     232 
     233            # update value and norm 
     234            weight *= spherical_correction 
     235            total += weight * I 
     236            norm += weight * form_volume() 
     237 
     238    if norm > 0.0: 
     239        return (scale/norm)*total + background 
    211240    else: 
    212         stride = np.array([1]) 
    213         vol_weight_index = slice(None, None) 
    214         # keep lint happy 
    215         fast_value = [None] 
    216         fast_weight = [None] 
    217  
    218     ret = np.zeros_like(args[0]) 
    219     norm = np.zeros_like(ret) 
    220     vol = np.zeros_like(ret) 
    221     vol_norm = np.zeros_like(ret) 
    222     for k in range(stride[-1]): 
    223         # update polydispersity parameter values 
    224         fast_index = k % stride[0] 
    225         if fast_index == 0:  # bottom loop complete ... check all other loops 
    226             if weight.size > 0: 
    227                 for i, index, in enumerate(k % stride): 
    228                     args[pd_index[i]] = pd[i][0][index] 
    229                     weight[i] = pd[i][1][index] 
    230         else: 
    231             args[pd_index[0]] = fast_value[fast_index] 
    232             weight[0] = fast_weight[fast_index] 
    233  
    234         # Computes the weight, and if it is not sufficient then ignore this 
    235         # parameter set. 
    236         # Note: could precompute w1*...*wn so we only need to multiply by w0 
    237         w = np.prod(weight) 
    238         if w > cutoff: 
    239             # Note: can precompute spherical correction if theta_index is not 
    240             # the fast index. Correction factor for spherical integration 
    241             #spherical_correction = abs(cos(pi*args[phi_index])) if phi_index>=0 else 1.0 
    242             spherical_correction = (abs(cos(pi * args[theta_index])) * pi / 2 
    243                                     if theta_index >= 0 else 1.0) 
    244             #spherical_correction = 1.0 
    245  
    246             # Call the scattering function and adds it to the total. 
    247             I = form(*args) 
    248             if np.isnan(I).any(): continue 
    249             ret += w * I * spherical_correction 
    250             norm += w 
    251  
    252             # Volume normalization. 
    253             # If there are "volume" polydispersity parameters, then these 
    254             # will be used to call the form_volume function from the user 
    255             # supplied kernel, and accumulate a normalized weight. 
    256             # Note: can precompute volume norm if fast index is not a volume 
    257             if form_volume: 
    258                 vol_args = [args[index] for index in vol_index] 
    259                 vol_weight = np.prod(weight[vol_weight_index]) 
    260                 vol += vol_weight * form_volume(*vol_args) 
    261                 vol_norm += vol_weight 
    262  
    263     positive = (vol * vol_norm != 0.0) 
    264     ret[positive] *= vol_norm[positive] / vol[positive] 
    265     result = scale * ret / norm + background 
    266     return result 
     241        return np.ones(nq, 'd')*background 
Note: See TracChangeset for help on using the changeset viewer.