Changeset bde38b5 in sasmodels for sasmodels/details.py


Ignore:
Timestamp:
Aug 14, 2016 11:12:40 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:
ac98886
Parents:
b1c40bee
git-author:
Paul Kienzle <pkienzle@…> (08/14/16 23:08:59)
git-committer:
Paul Kienzle <pkienzle@…> (08/14/16 23:12:40)
Message:

simplify kernel calling

File:
1 edited

Legend:

Unmodified
Added
Removed
  • sasmodels/details.py

    r40a87fa rbde38b5  
    7171        #   pd_offset[max_pd]  offset of pd values in parameter array 
    7272        #   pd_stride[max_pd]  index of pd value in loop = n//stride[k] 
    73         #   pd_prod            total length of pd loop 
    74         #   pd_sum             total length of the weight vector 
     73        #   num_eval           total length of pd loop 
     74        #   num_weights        total length of the weight vector 
    7575        #   num_active         number of pd params 
    7676        #   theta_par          parameter number for theta parameter 
    77         self.buffer = np.zeros(4*max_pd + 4, 'i4') 
     77        self.buffer = np.empty(4*max_pd + 4, 'i4') 
    7878 
    7979        # generate views on different parts of the array 
     
    8686        self.theta_par = parameters.theta_offset 
    8787 
     88        # offset and length are for all parameters, not just pd parameters 
     89        # They are not sent to the kernel function, though they could be. 
     90        # They are used by the composite models (sum and product) to 
     91        # figure out offsets into the combined value list. 
     92        self.offset = None  # type: np.ndarray 
     93        self.length = None  # type: np.ndarray 
     94 
     95        # keep hold of ifno show() so we can break a values vector 
     96        # into the individual components 
     97        self.info = model_info 
     98 
    8899    @property 
    89100    def pd_par(self): 
     
    107118 
    108119    @property 
    109     def pd_prod(self): 
     120    def num_eval(self): 
    110121        """Total size of the pd mesh""" 
    111122        return self.buffer[-4] 
    112123 
    113     @pd_prod.setter 
    114     def pd_prod(self, v): 
     124    @num_eval.setter 
     125    def num_eval(self, v): 
    115126        """Total size of the pd mesh""" 
    116127        self.buffer[-4] = v 
    117128 
    118129    @property 
    119     def pd_sum(self): 
     130    def num_weights(self): 
    120131        """Total length of all the weight vectors""" 
    121132        return self.buffer[-3] 
    122133 
    123     @pd_sum.setter 
    124     def pd_sum(self, v): 
     134    @num_weights.setter 
     135    def num_weights(self, v): 
    125136        """Total length of all the weight vectors""" 
    126137        self.buffer[-3] = v 
     
    146157        self.buffer[-1] = v 
    147158 
    148     def show(self): 
     159    def show(self, values=None): 
    149160        """Print the polydispersity call details to the console""" 
    150         print("num_active", self.num_active) 
    151         print("pd_prod", self.pd_prod) 
    152         print("pd_sum", self.pd_sum) 
    153         print("theta par", self.theta_par) 
    154         print("pd_par", self.pd_par) 
    155         print("pd_length", self.pd_length) 
    156         print("pd_offset", self.pd_offset) 
    157         print("pd_stride", self.pd_stride) 
    158  
    159  
    160 def mono_details(model_info): 
    161     # type: (ModelInfo) -> CallDetails 
    162     """ 
    163     Return a :class:`CallDetails` object for a monodisperse calculation 
    164     of the model defined by *model_info*. 
    165     """ 
    166     call_details = CallDetails(model_info) 
    167     call_details.pd_prod = 1 
    168     call_details.pd_sum = model_info.parameters.nvalues 
    169     call_details.pd_par[:] = np.arange(0, model_info.parameters.max_pd) 
    170     call_details.pd_length[:] = 1 
    171     call_details.pd_offset[:] = np.arange(0, model_info.parameters.max_pd) 
    172     call_details.pd_stride[:] = 1 
    173     return call_details 
    174  
    175  
    176 def poly_details(model_info, weights): 
     161        print("===== %s details ===="%self.info.name) 
     162        print("num_active:%d  num_eval:%d  num_weights:%d  theta=%d" 
     163              % (self.num_active, self.num_eval, self.num_weights, self.theta_par)) 
     164        if self.pd_par.size: 
     165            print("pd_par", self.pd_par) 
     166            print("pd_length", self.pd_length) 
     167            print("pd_offset", self.pd_offset) 
     168            print("pd_stride", self.pd_stride) 
     169        if values is not None: 
     170            nvalues = self.info.parameters.nvalues 
     171            print("scale, background", values[:2]) 
     172            print("val", values[2:nvalues]) 
     173            print("pd", values[nvalues:nvalues+self.num_weights]) 
     174            print("wt", values[nvalues+self.num_weights:nvalues+2*self.num_weights]) 
     175            print("offsets", self.offset) 
     176 
     177 
     178def make_details(model_info, length, offset, num_weights): 
    177179    """ 
    178180    Return a :class:`CallDetails` object for a polydisperse calculation 
    179     of the model defined by *model_info* for the given set of *weights*. 
    180     *weights* is a list of vectors, one for each parameter.  Monodisperse 
    181     parameters should provide a weight vector containing [1.0]. 
    182     """ 
    183     # type: (ModelInfo) -> CallDetails 
    184     #print("weights",weights) 
    185     #weights = weights[2:] # Skip scale and background 
    186  
    187     # Decreasing list of polydispersity lengths 
    188     #print([p.id for p in model_info.parameters.call_parameters]) 
    189     pd_length = np.array([len(w) 
    190                           for w in weights[2:2+model_info.parameters.npars]]) 
    191     num_active = np.sum(pd_length > 1) 
     181    of the model defined by *model_info*.  Polydispersity is defined by 
     182    the *length* of the polydispersity distribution for each parameter 
     183    and the *offset* of the distribution in the polydispersity array. 
     184    Monodisperse parameters should use a polydispersity length of one 
     185    with weight 1.0. *num_weights* is the total length of the polydispersity 
     186    array. 
     187    """ 
     188    # type: (ModelInfo, np.ndarray, np.ndarray, int) -> CallDetails 
     189    #pars = model_info.parameters.call_parameters[2:model_info.parameters.npars+2] 
     190    #print(", ".join(str(i)+"-"+p.id for i,p in enumerate(pars))) 
     191    #print("len:",length) 
     192    #print("off:",offset) 
     193 
     194    # Check that we arn't using too many polydispersity loops 
     195    num_active = np.sum(length > 1) 
    192196    max_pd = model_info.parameters.max_pd 
    193197    if num_active > max_pd: 
    194198        raise ValueError("Too many polydisperse parameters") 
    195199 
    196     pd_offset = np.cumsum(np.hstack((0, pd_length))) 
    197     #print(", ".join(str(i)+"-"+p.id for i,p in enumerate(model_info.parameters.call_parameters))) 
    198     #print("len:",pd_length) 
    199     #print("off:",pd_offset) 
     200    # Decreasing list of polydpersity lengths 
    200201    # Note: the reversing view, x[::-1], does not require a copy 
    201     idx = np.argsort(pd_length)[::-1][:max_pd] 
    202     pd_stride = np.cumprod(np.hstack((1, pd_length[idx]))) 
     202    idx = np.argsort(length)[::-1][:max_pd] 
     203    pd_stride = np.cumprod(np.hstack((1, length[idx]))) 
    203204 
    204205    call_details = CallDetails(model_info) 
    205206    call_details.pd_par[:max_pd] = idx 
    206     call_details.pd_length[:max_pd] = pd_length[idx] 
    207     call_details.pd_offset[:max_pd] = pd_offset[idx] 
     207    call_details.pd_length[:max_pd] = length[idx] 
     208    call_details.pd_offset[:max_pd] = offset[idx] 
    208209    call_details.pd_stride[:max_pd] = pd_stride[:-1] 
    209     call_details.pd_prod = pd_stride[-1] 
    210     call_details.pd_sum = sum(len(w) for w in weights) 
     210    call_details.num_eval = pd_stride[-1] 
     211    call_details.num_weights = num_weights 
    211212    call_details.num_active = num_active 
     213    call_details.length = length 
     214    call_details.offset = offset 
    212215    #call_details.show() 
    213216    return call_details 
     217 
     218 
     219ZEROS = tuple([0.]*31) 
     220def make_kernel_args(kernel, pairs): 
     221    # type: (Kernel, Tuple[List[np.ndarray], List[np.ndarray]]) -> Tuple[CallDetails, np.ndarray, bool] 
     222    """ 
     223    Converts (value, weight) pairs into parameters for the kernel call. 
     224 
     225    Returns a CallDetails object indicating the polydispersity, a data object 
     226    containing the different values, and the magnetic flag indicating whether 
     227    any magnetic magnitudes are non-zero. Magnetic vectors (M0, phi, theta) are 
     228    converted to rectangular coordinates (mx, my, mz). 
     229    """ 
     230    npars = kernel.info.parameters.npars 
     231    nvalues = kernel.info.parameters.nvalues 
     232    scalars = [v[0][0] for v in pairs] 
     233    values, weights = zip(*pairs[2:npars+2]) if npars else ((),()) 
     234    length = np.array([len(w) for w in weights]) 
     235    offset = np.cumsum(np.hstack((0, length))) 
     236    call_details = make_details(kernel.info, length, offset[:-1], offset[-1]) 
     237    # Pad value array to a 32 value boundaryd 
     238    data_len = nvalues + 2*sum(len(v) for v in values) 
     239    extra = (32 - data_len%32)%32 
     240    data = np.hstack((scalars,) + values + weights + ZEROS[:extra]) 
     241    data = data.astype(kernel.dtype) 
     242    is_magnetic = convert_magnetism(kernel.info.parameters, data) 
     243    #call_details.show() 
     244    return call_details, data, is_magnetic 
     245 
     246 
     247def convert_magnetism(parameters, values): 
     248    """ 
     249    Convert magnetism values from polar to rectangular coordinates. 
     250 
     251    Returns True if any magnetism is present. 
     252    """ 
     253    mag = values[parameters.nvalues-3*parameters.nmagnetic:parameters.nvalues] 
     254    mag = mag.reshape(-1, 3) 
     255    scale = mag[:,0] 
     256    if np.any(scale): 
     257        theta, phi = mag[:, 1]*pi/180., mag[:, 2]*pi/180. 
     258        cos_theta = cos(theta) 
     259        mag[:, 0] = scale*cos_theta*cos(phi)  # mx 
     260        mag[:, 1] = scale*sin(theta)  # my 
     261        mag[:, 2] = -scale*cos_theta*sin(phi)  # mz 
     262        return True 
     263    else: 
     264        return False 
    214265 
    215266 
     
    239290        value = pars 
    240291    return value, weight 
    241  
    242  
    243  
    244 def build_details(kernel, pairs): 
    245     # type: (Kernel, Tuple[List[np.ndarray], List[np.ndarray]]) -> Tuple[CallDetails, np.ndarray, bool] 
    246     """ 
    247     Converts (value, weight) pairs into parameters for the kernel call. 
    248  
    249     Returns a CallDetails object indicating the polydispersity, a data object 
    250     containing the different values, and the magnetic flag indicating whether 
    251     any magnetic magnitudes are non-zero. Magnetic vectors (M0, phi, theta) are 
    252     converted to rectangular coordinates (mx, my, mz). 
    253     """ 
    254     values, weights = zip(*pairs) 
    255     scalars = [v[0] for v in values] 
    256     if all(len(w) == 1 for w in weights): 
    257         call_details = mono_details(kernel.info) 
    258         # Pad value array to a 32 value boundary 
    259         data_len = 3*len(scalars) 
    260         extra = ((data_len+31)//32)*32 - data_len 
    261         data = np.array(scalars+scalars+[1.]*len(scalars)+[0.]*extra, 
    262                         dtype=kernel.dtype) 
    263     else: 
    264         call_details = poly_details(kernel.info, weights) 
    265         # Pad value array to a 32 value boundary 
    266         data_len = len(scalars) + 2*sum(len(v) for v in values) 
    267         extra = ((data_len+31)//32)*32 - data_len 
    268         data = np.hstack(scalars+list(values)+list(weights)+[0.]*extra) 
    269         data = data.astype(kernel.dtype) 
    270     is_magnetic = convert_magnetism(kernel.info.parameters, data) 
    271     #call_details.show() 
    272     return call_details, data, is_magnetic 
    273  
    274 def convert_magnetism(parameters, values): 
    275     """ 
    276     Convert magnetism values from polar to rectangular coordinates. 
    277  
    278     Returns True if any magnetism is present. 
    279     """ 
    280     mag = values[parameters.nvalues-3*parameters.nmagnetic:parameters.nvalues] 
    281     mag = mag.reshape(-1, 3) 
    282     scale = mag[:,0] 
    283     if np.any(scale): 
    284         theta, phi = mag[:, 1]*pi/180., mag[:, 2]*pi/180. 
    285         cos_theta = cos(theta) 
    286         mag[:, 0] = scale*cos_theta*cos(phi)  # mx 
    287         mag[:, 1] = scale*sin(theta)  # my 
    288         mag[:, 2] = -scale*cos_theta*sin(phi)  # mz 
    289         return True 
    290     else: 
    291         return False 
Note: See TracChangeset for help on using the changeset viewer.