Changeset ce27e21 in sasmodels
- Timestamp:
- Aug 24, 2014 5:18:14 PM (10 years ago)
- 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
- Files:
-
- 4 added
- 8 edited
Legend:
- Unmodified
- Added
- Removed
-
compare-new.py
r14de349 rce27e21 48 48 gpu = model.theory() 49 49 gpu_time = toc()*1000./Ngpu 50 print "ocl t=%.1f ms "%gpu_time50 print "ocl t=%.1f ms, intensity=%.0f"%(gpu_time, sum(gpu[~np.isnan(gpu)])) 51 51 #print max(gpu), min(gpu) 52 52 … … 61 61 print "dll t=%.1f ms"%cpu_time 62 62 63 elif 1: # Hack to check new vs old for GpuCylinder63 elif 0: # Hack to check new vs old for GpuCylinder 64 64 from Models.code_cylinder_f import GpuCylinder as oldgpu 65 65 from sasmodel import SasModel … … 78 78 cpu = cpumodel.evalDistribution([data.qx_data, data.qy_data]) 79 79 cpu_time = toc()*1000./Ncpu 80 print "sasview t=%.1f ms "%cpu_time80 print "sasview t=%.1f ms, intensity=%.0f"%(cpu_time, sum(cpu[model.index])) 81 81 82 82 if Ngpu > 0 and Ncpu > 0: -
sasmodel.py
ra953943 rce27e21 4 4 import datetime 5 5 import numpy as np 6 import pyopencl as cl7 from bumps.names import Parameter8 from sans.dataloader.loader import Loader9 from sans.dataloader.manipulations import Ringcut, Boxcut10 6 11 7 … … 20 16 21 17 def load_data(filename): 18 from sans.dataloader.loader import Loader 22 19 loader = Loader() 23 20 data = loader.load(filename) … … 57 54 58 55 def set_beam_stop(data, radius, outer=None): 56 from sans.dataloader.manipulations import Ringcut 59 57 if hasattr(data, 'qx_data'): 60 58 data.mask = Ringcut(0, radius)(data) … … 67 65 68 66 def set_half(data, half): 67 from sans.dataloader.manipulations import Boxcut 69 68 if half == 'right': 70 69 data.mask += Boxcut(x_min=-np.inf, x_max=0.0, y_min=-np.inf, y_max=np.inf)(data) … … 73 72 74 73 def set_top(data, max): 74 from sans.dataloader.manipulations import Boxcut 75 75 data.mask += Boxcut(x_min=-np.inf, x_max=np.inf, y_min=-np.inf, y_max=max)(data) 76 76 … … 144 144 global GPU_CONTEXT, GPU_QUEUE 145 145 if GPU_CONTEXT is None: 146 import pyopencl as cl 146 147 GPU_CONTEXT = cl.create_some_context() 147 148 GPU_QUEUE = cl.CommandQueue(GPU_CONTEXT) … … 151 152 class SasModel(object): 152 153 def __init__(self, data, model, dtype='float32', **kw): 154 from bumps.names import Parameter 155 153 156 self.__dict__['_parameters'] = {} 154 157 #self.name = data.filename -
sasmodels/core.py
r14de349 rce27e21 4 4 import sys, os 5 5 import datetime 6 import warnings 6 7 7 8 import numpy as np … … 14 15 return gen.make(modelpath) 15 16 17 18 16 19 def opencl_model(modelname, dtype="single"): 17 20 from sasmodels import gpu 18 21 19 source, meta, _ = load_model(modelname)22 source, info, _ = load_model(modelname) 20 23 # for debugging, save source to a .cl file, edit it, and reload as model 21 24 #open(modelname+'.cl','w').write(source) 22 25 #source = open(modelname+'.cl','r').read() 23 return gpu.GpuModel(source, meta, dtype)26 return gpu.GpuModel(source, info, dtype) 24 27 25 28 … … 31 34 COMPILE = "cc -shared -fPIC -std=c99 -fopenmp -O2 -Wall %s -o %s -lm" 32 35 DLL_PATH = "/tmp" 33 def dll_path(meta): 36 37 38 def dll_path(info): 34 39 from os.path import join as joinpath, split as splitpath, splitext 35 basename = splitext(splitpath( meta['filename'])[1])[0]40 basename = splitext(splitpath(info['filename'])[1])[0] 36 41 return joinpath(DLL_PATH, basename+'.so') 42 37 43 38 44 def dll_model(modelname): … … 40 46 from sasmodels import dll 41 47 42 source, meta, _ = load_model(modelname)43 dllpath = dll_path( meta)48 source, info, _ = load_model(modelname) 49 dllpath = dll_path(info) 44 50 if not os.path.exists(dllpath) \ 45 or (os.path.getmtime(dllpath) < os.path.getmtime( meta['filename'])):51 or (os.path.getmtime(dllpath) < os.path.getmtime(info['filename'])): 46 52 # Replace with a proper temp file 47 53 srcfile = '/tmp/%s.c'%modelname 48 54 open(srcfile, 'w').write(source) 49 55 os.system(COMPILE%(srcfile, dllpath)) 50 return dll.DllModel(dllpath, meta) 56 return dll.DllModel(dllpath, info) 57 51 58 52 59 TIC = None … … 57 64 return TIC 58 65 66 59 67 def toc(): 60 68 return TIC() 69 61 70 62 71 def load_data(filename): … … 68 77 return data 69 78 79 70 80 def fake_data2D(qx, qy=None): 71 81 from sans.dataloader.data_info import Data2D, Detector 72 73 82 74 83 if qy is None: … … 121 130 data.mask &= (data.x<outer) 122 131 132 123 133 def set_half(data, half): 124 134 from sans.dataloader.manipulations import Boxcut … … 128 138 data.mask += Boxcut(x_min=0.0, x_max=np.inf, y_min=-np.inf, y_max=np.inf)(data) 129 139 140 130 141 def set_top(data, max): 131 142 from sans.dataloader.manipulations import Boxcut 132 143 data.mask += Boxcut(x_min=-np.inf, x_max=np.inf, y_min=-np.inf, y_max=max)(data) 144 133 145 134 146 def plot_data(data, iq, vmin=None, vmax=None): … … 141 153 interpolation='nearest', aspect=1, origin='upper', 142 154 extent=[xmin, xmax, ymin, ymax], vmin=vmin, vmax=vmax) 155 143 156 144 157 def plot_result2D(data, theory, view='linear'): … … 184 197 mresid = masked_array((theory-data.y)/data.dy, mdata.mask) 185 198 186 plt.subplot(1 ,2,1)199 plt.subplot(121) 187 200 plt.errorbar(data.x, mdata, yerr=data.dy) 188 201 plt.plot(data.x, mtheory, '-', hold=True) 189 202 plt.yscale(view) 190 plt.subplot(1 , 2,2)203 plt.subplot(122) 191 204 plt.plot(data.x, mresid, 'x') 192 205 #plt.axhline(1, color='black', ls='--',lw=1, hold=True) … … 219 232 220 233 # create model 221 self.fn = model(input, cutoff=cutoff) 234 self.fn = model(input) 235 self.cutoff = cutoff 222 236 223 237 # define bumps parameters 224 238 pars = [] 225 for p in model.meta['parameters']: 239 extras = [] 240 for p in model.info['parameters']: 226 241 name, default, limits, ptype = p[0], p[2], p[3], p[4] 227 242 value = kw.pop(name, default) 228 243 setattr(self, name, Parameter.default(value, name=name, limits=limits)) 229 244 pars.append(name) 230 if ptype != "": 231 for xpart,xdefault,xlimits in [ 232 ('_pd', 0, limits), 233 ('_pd_n', 35, (0,1000)), 234 ('_pd_nsigma', 3, (0,10)), 235 ]: 236 xname = name+xpart 237 xvalue = kw.pop(xname, xdefault) 238 setattr(self, xname, Parameter.default(xvalue, name=xname)) 245 for name in model.info['partype']['pd-2d']: 246 for xpart,xdefault,xlimits in [ 247 ('_pd', 0, limits), 248 ('_pd_n', 35, (0,1000)), 249 ('_pd_nsigma', 3, (0, 10)), 250 ('_pd_type', 'gaussian', None), 251 ]: 252 xname = name+xpart 253 xvalue = kw.pop(xname, xdefault) 254 if xlimits is not None: 255 xvalue = Parameter.default(xvalue, name=xname, limits=xlimits) 239 256 pars.append(xname) 257 setattr(self, xname, xvalue) 258 self._parameter_names = pars 240 259 if kw: 241 260 raise TypeError("unexpected parameters: %s"%(", ".join(sorted(kw.keys())))) 242 self._parameter_names = pars243 261 self.update() 244 262 … … 254 272 def theory(self): 255 273 if 'theory' not in self._cache: 256 pars = dict((k,getattr(self,k).value) for k in self._parameter_names) 274 pars = [getattr(self,p).value for p in self.fn.fixed_pars] 275 pd_pars = [self._get_weights(p) for p in self.fn.pd_pars] 257 276 #print pars 258 self._theory[self.index] = self.fn .eval(pars)259 #self._theory[:] = self.fn.eval(pars )277 self._theory[self.index] = self.fn(pars, pd_pars, self.cutoff) 278 #self._theory[:] = self.fn.eval(pars, pd_pars) 260 279 self._cache['theory'] = self._theory 261 280 return self._cache['theory'] … … 282 301 pass 283 302 303 def _get_weights(self, par): 304 from . import weights 305 306 relative = self.fn.info['partype']['pd-rel'] 307 limits = self.fn.info['limits'] 308 disperser,value,npts,width,nsigma = [getattr(self, par+ext) 309 for ext in ('_pd_type','','_pd_n','_pd','_pd_nsigma')] 310 v,w = weights.get_weights( 311 disperser, int(npts.value), width.value, nsigma.value, 312 value.value, limits[par], par in relative) 313 return v,w/w.max() 314 315 284 316 def demo(): 285 317 data = load_data('DEC07086.DAT') … … 288 320 import matplotlib.pyplot as plt; plt.show() 289 321 322 290 323 if __name__ == "__main__": 291 324 demo() -
sasmodels/dll.py
r14de349 rce27e21 19 19 ctypes wrapper for a single model. 20 20 21 *source* and * meta* are the model source and interface as returned21 *source* and *info* are the model source and interface as returned 22 22 from :func:`gen.make`. 23 23 … … 27 27 is an optional extension which may not be available on all devices. 28 28 """ 29 def __init__(self, dllpath, meta): 30 self.meta = meta 31 self.dll = ct.CDLL(dllpath) 32 self.Iq = self.dll[gen.kernel_name(self.meta, False)] 33 self.Iqxy = self.dll[gen.kernel_name(self.meta, True)] 29 def __init__(self, dllpath, info): 30 self.info = info 31 self.dllpath = dllpath 32 self.dll = None 34 33 34 def _load_dll(self): 35 Nfixed1d = len(self.info['partype']['fixed-1d']) 36 Nfixed2d = len(self.info['partype']['fixed-2d']) 37 Npd1d = len(self.info['partype']['pd-1d']) 38 Npd2d = len(self.info['partype']['pd-2d']) 35 39 36 self.PARS = dict((p[0],p[2]) for p in meta['parameters']) 37 self.PD_PARS = [p[0] for p in meta['parameters'] if p[4] != ""] 40 self.dll = ct.CDLL(self.dllpath) 38 41 39 # Determine the set of fixed and polydisperse parameters 40 Nfixed = len([p[0] for p in meta['parameters'] if p[4] == ""]) 41 N1D = len([p for p in meta['parameters'] if p[4]=="volume"]) 42 N2D = len([p for p in meta['parameters'] if p[4]!=""]) 43 self.Iq.argtypes = IQ_ARGS + [c_double]*Nfixed + [c_int]*N1D 44 self.Iqxy.argtypes = IQXY_ARGS + [c_double]*Nfixed + [c_int]*N2D 42 self.Iq = self.dll[gen.kernel_name(self.info, False)] 43 self.Iq.argtypes = IQ_ARGS + [c_double]*Nfixed1d + [c_int]*Npd1d 45 44 46 def __call__(self, input, cutoff=1e-5): 45 self.Iqxy = self.dll[gen.kernel_name(self.info, True)] 46 self.Iqxy.argtypes = IQXY_ARGS + [c_double]*Nfixed2d + [c_int]*Npd2d 47 48 def __getstate__(self): 49 return {'info': self.info, 'dllpath': self.dllpath, 'dll': None} 50 51 def __setstate__(self, state): 52 self.__dict__ = state 53 54 def __call__(self, input): 55 if self.dll is None: self._load_dll() 56 47 57 kernel = self.Iqxy if input.is_2D else self.Iq 48 return DllKernel(kernel, self. meta, input, cutoff)58 return DllKernel(kernel, self.info, input) 49 59 50 60 def make_input(self, q_vectors): … … 90 100 91 101 class DllKernel(object): 92 def __init__(self, kernel, meta, input, cutoff): 93 self.cutoff = cutoff 102 def __init__(self, kernel, info, input): 94 103 self.input = input 95 104 self.kernel = kernel 96 self.meta = meta 105 self.info = info 106 self.res = np.empty(input.nq, input.dtype) 107 dim = '2d' if input.is_2D else '1d' 108 self.fixed_pars = info['partype']['fixed-'+dim] 109 self.pd_pars = info['partype']['pd-'+dim] 97 110 98 self.res = np.empty(input.nq, input.dtype)111 # In dll kernel, but not in opencl kernel 99 112 self.p_res = self.res.ctypes.data 100 113 101 # Determine the set of fixed and polydisperse parameters 102 self.fixed_pars = [p[0] for p in meta['parameters'] if p[4] == ""] 103 self.pd_pars = [p for p in meta['parameters'] 104 if p[4]=="volume" or (p[4]=="orientation" and input.is_2D)] 114 def __call__(self, pars, pd_pars, cutoff): 115 real = np.float32 if self.input.dtype == F32 else np.float64 116 fixed = [real(p) for p in pars] 117 cutoff = real(cutoff) 118 loops = np.hstack(pd_pars) 119 loops = np.ascontiguousarray(loops.T, self.input.dtype).flatten() 120 loops_N = [np.uint32(len(p[0])) for p in pd_pars] 105 121 106 def eval(self, pars):107 fixed, loops, loop_n = \108 gen.kernel_pars(pars, self.meta, self.input.is_2D, dtype=self.input.dtype)109 real = np.float32 if self.input.dtype == F32 else np.float64110 122 nq = c_int(self.input.nq) 111 cutoff = real(self.cutoff)112 113 123 p_loops = loops.ctypes.data 114 pars = self.input.q_pointers + [self.p_res, nq, p_loops, cutoff] + fixed + loop_n124 args = self.input.q_pointers + [self.p_res, nq, p_loops, cutoff] + fixed + loops_N 115 125 #print pars 116 self.kernel(* pars)126 self.kernel(*args) 117 127 118 128 return self.res … … 120 130 def release(self): 121 131 pass 122 123 def __del__(self):124 self.release() -
sasmodels/gen.py
r14de349 rce27e21 9 9 F64 = np.dtype('float64') 10 10 F32 = np.dtype('float32') 11 12 # Scale and background, which are parameters common to every form factor 13 COMMON_PARAMETERS = [ 14 [ "scale", "", 1, [0, np.inf], "", "Source intensity" ], 15 [ "background", "1/cm", 0, [0, np.inf], "", "Source background" ], 16 ] 17 11 18 12 19 # Conversion from units defined in the parameter table for each model … … 29 36 30 37 PARTABLE_VALUE_WIDTH = 10 31 32 # Scale and background, which are parameters common to every form factor33 COMMON_PARAMETERS = [34 [ "scale", "", 0, [0, np.inf], "", "Source intensity" ],35 [ "background", "1/cm", 0, [0, np.inf], "", "Source background" ],36 ]37 38 38 39 39 # Header included before every kernel. … … 60 60 # define kernel 61 61 # define SINCOS(angle,svar,cvar) do {svar=sin(angle);cvar=cos(angle);} while (0) 62 # define powr(a,b) pow(a,b) 62 63 #else 63 64 # ifdef USE_SINCOS … … 93 94 # respectively, so the template builder will need to do extra work to 94 95 # declare, initialize and pass the q parameters. 95 IQ_KERNEL= {96 KERNEL_1D = { 96 97 'fn': "Iq", 97 98 'q_par_decl': "global const real *q,", … … 100 101 } 101 102 102 IQXY_KERNEL= {103 KERNEL_2D = { 103 104 'fn': "Iqxy", 104 105 'q_par_decl': "global const real *qx,\n global const real *qy,", … … 176 177 # Volume normalization. 177 178 # If there are "volume" polydispersity parameters, then these will be used 178 # to call the volume function from the user supplied kernel, and accumulate179 # to call the form_volume function from the user supplied kernel, and accumulate 179 180 # a normalized weight. 180 181 VOLUME_NORM="""const real vol_weight = %(weight)s; 181 vol += vol_weight* volume(%(pars)s);182 vol += vol_weight*form_volume(%(pars)s); 182 183 norm_vol += vol_weight;""" 184 183 185 184 186 def indent(s, depth): … … 191 193 192 194 193 def make_kernel(meta, form): 195 def kernel_name(info, is_2D): 196 return info['name'] + "_" + ("Iqxy" if is_2D else "Iq") 197 198 199 def make_kernel(info, is_2D): 194 200 """ 195 201 Build a kernel call from metadata supplied by the user. 196 202 197 * meta* is the json object defined in the kernel file.203 *info* is the json object defined in the kernel file. 198 204 199 205 *form* is either "Iq" or "Iqxy". … … 206 212 # If we are building the Iqxy kernel, we need to propagate qx,qy 207 213 # 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 213 221 depth = 4 214 222 offset = "" 215 223 loop_head = [] 216 224 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: 236 226 subst = { 'name': name, 'offset': offset } 237 227 loop_head.append(indent(LOOP_OPEN%subst, depth)) … … 254 244 255 245 # 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)] 256 252 subst = { 257 253 'weight_product': "*".join(p+"_w" for p in pd_pars), 258 254 'volume_norm': volume_norm, 259 'fn': q pars['fn'],260 'qcall': q pars['qcall'],261 'pcall': ", ".join(f n_pars),255 'fn': q_pars['fn'], 256 'qcall': q_pars['qcall'], 257 'pcall': ", ".join(fq_pars), # skip scale and background 262 258 } 263 259 loop_body = [indent(LOOP_BODY%subst, depth)] … … 280 276 subst = { 281 277 # kernel name is, e.g., cylinder_Iq 282 'name': "_".join((meta['name'], qpars['fn'])),278 'name': kernel_name(info, is_2D), 283 279 # to declare, e.g., global real q[], 284 'q_par_decl': q pars['q_par_decl'],280 'q_par_decl': q_pars['q_par_decl'], 285 281 # to declare, e.g., real sld, int Nradius, int Nlength 286 282 'par_decl': par_decl, … … 288 284 'pd_length': "+".join('N'+p for p in pd_pars), 289 285 # the q initializers, e.g., real qi = q[i]; 290 'qinit': q pars['qinit'],286 'qinit': q_pars['qinit'], 291 287 # the actual polydispersity loop 292 288 'loops': loops, … … 295 291 return kernel 296 292 297 def make_partable( meta):298 pars = meta['parameters']293 def make_partable(info): 294 pars = info['parameters'] 299 295 column_widths = [ 300 296 max(len(p[0]) for p in pars), … … 320 316 return "\n".join(lines) 321 317 322 def make_doc(kernelfile, meta, doc):323 doc = doc%{'parameters': make_partable( meta)}318 def make_doc(kernelfile, info, doc): 319 doc = doc%{'parameters': make_partable(info)} 324 320 return doc 325 321 326 def make_model(kernelfile, meta, source):327 kernel_Iq = make_kernel( meta, "Iq")328 kernel_Iqxy = make_kernel( meta, "Iqxy")322 def make_model(kernelfile, info, source): 323 kernel_Iq = make_kernel(info, is_2D=False) 324 kernel_Iqxy = make_kernel(info, is_2D=True) 329 325 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']] 331 327 kernel = "\n\n".join([KERNEL_HEADER]+extra+[source, kernel_Iq, kernel_Iqxy]) 332 328 return kernel … … 339 335 if len(parts) != 3: 340 336 raise ValueError("PARAMETERS block missing from %r"%kernelfile) 341 meta_source = parts[1].strip()337 info_source = parts[1].strip() 342 338 try: 343 meta = relaxed_loads(meta_source)339 info = relaxed_loads(info_source) 344 340 except: 345 341 print "in json text:" 346 342 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'))) 348 344 raise 349 345 #raise ValueError("PARAMETERS block could not be parsed from %r"%kernelfile) 350 meta['parameters'] = COMMON_PARAMETERS + meta['parameters']351 meta['filename'] = kernelfile352 346 353 347 # select documentation out of the source file 354 348 parts = source.split("DOCUMENTATION") 355 349 if len(parts) == 3: 356 doc = make_doc(kernelfile, meta, parts[1].strip())350 doc = make_doc(kernelfile, info, parts[1].strip()) 357 351 elif len(parts) == 1: 358 352 raise ValueError("DOCUMENTATION block is missing from %r"%kernelfile) … … 360 354 raise ValueError("DOCUMENTATION block incorrect from %r"%kernelfile) 361 355 362 return source, meta, doc 356 return source, info, doc 357 358 def 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 363 409 364 410 def make(kernelfile): … … 370 416 """ 371 417 #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 427 426 428 427 … … 437 436 def demo(): 438 437 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")) 440 439 #print doc 441 440 #print c -
sasmodels/gpu.py
r14de349 rce27e21 45 45 #define real double 46 46 """ 47 48 # The max loops number is limited by the amount of local memory available 49 # on the device. You don't want to make this value too big because it will 50 # waste resources, nor too small because it may interfere with users trying 51 # to do their polydispersity calculations. A value of 1024 should be much 52 # larger than necessary given that cost grows as npts^k where k is the number 53 # of polydisperse parameters. 54 MAX_LOOPS = 1024 47 55 48 56 ENV = None … … 96 104 dtype = np.dtype(dtype) 97 105 if dtype==F64 and not all(has_double(d) for d in context.devices): 98 warnings.warn(RuntimeWarning("Double precision not support; using single precision instead")) 99 dtype = F32 106 raise RuntimeError("Double precision not supported for devices") 100 107 101 108 header = F64_DEFS if dtype == F64 else F32_DEFS … … 104 111 header += "#define USE_SINCOS\n" 105 112 program = cl.Program(context, header+source).build() 106 return program , dtype113 return program 107 114 108 115 … … 125 132 self.boundary = max(d.min_data_type_align_size 126 133 for d in self.context.devices) 134 self.has_double = all(has_double(d) for d in self.context.devices) 135 self.compiled = {} 136 137 def compile_program(self, name, source, dtype): 138 if name not in self.compiled: 139 #print "compiling",name 140 self.compiled[name] = compile_model(self.context, source, dtype) 141 return self.compiled[name] 142 143 def release_program(self, name): 144 if name in self.compiled: 145 self.compiled[name].release() 146 del self.compiled[name] 127 147 128 148 class GpuModel(object): … … 130 150 GPU wrapper for a single model. 131 151 132 *source* and * meta* are the model source and interface as returned152 *source* and *info* are the model source and interface as returned 133 153 from :func:`gen.make`. 134 154 … … 138 158 is an optional extension which may not be available on all devices. 139 159 """ 140 def __init__(self, source, meta, dtype=F32): 141 context = environment().context 142 self.meta = meta 143 self.program, self.dtype = compile_model(context, source, dtype) 144 145 #TODO: need better interface 146 self.PARS = dict((p[0],p[2]) for p in meta['parameters']) 147 self.PD_PARS = [p[0] for p in meta['parameters'] if p[4] != ""] 148 149 def __call__(self, input, cutoff=1e-5): 160 def __init__(self, source, info, dtype=F32): 161 self.info = info 162 self.source = source 163 self.dtype = dtype 164 self.program = None # delay program creation 165 166 def __getstate__(self): 167 state = self.__dict__.copy() 168 state['program'] = None 169 return state 170 171 def __setstate__(self, state): 172 self.__dict__ = state.copy() 173 174 def __call__(self, input): 150 175 if self.dtype != input.dtype: 151 176 raise TypeError("data and kernel have different types") 152 kernel_name = gen.kernel_name(self.meta, input.is_2D) 177 if self.program is None: 178 self.program = environment().compile_program(self.info['name'],self.source, self.dtype) 179 kernel_name = gen.kernel_name(self.info, input.is_2D) 153 180 kernel = getattr(self.program, kernel_name) 154 return GpuKernel(kernel, self.meta, input, cutoff) 181 return GpuKernel(kernel, self.info, input) 182 183 def release(self): 184 if self.program is not None: 185 environment().release_program(self.info['name']) 186 self.program = None 155 187 156 188 def make_input(self, q_vectors): … … 210 242 211 243 class GpuKernel(object): 212 def __init__(self, kernel, meta, input, cutoff): 213 env = environment() 214 215 self.cutoff = cutoff 244 def __init__(self, kernel, info, input): 216 245 self.input = input 217 246 self.kernel = kernel 218 self.meta = meta 247 self.info = info 248 self.res = np.empty(input.nq, input.dtype) 249 dim = '2d' if input.is_2D else '1d' 250 self.fixed_pars = info['partype']['fixed-'+dim] 251 self.pd_pars = info['partype']['pd-'+dim] 219 252 220 253 # Inputs and outputs for each kernel call 254 # Note: res may be shorter than res_b if global_size != nq 255 env = environment() 221 256 self.loops_b = [cl.Buffer(env.context, mf.READ_WRITE, 222 1024*input.dtype.itemsize)257 MAX_LOOPS*input.dtype.itemsize) 223 258 for _ in env.queues] 224 259 self.res_b = [cl.Buffer(env.context, mf.READ_WRITE, … … 226 261 for _ in env.queues] 227 262 228 # Note: may be shorter than res_b if global_size != nq 229 self.res = np.empty(input.nq, input.dtype) 230 231 # Determine the set of fixed and polydisperse parameters 232 self.fixed_pars = [p[0] for p in meta['parameters'] if p[4] == ""] 233 self.pd_pars = [p for p in meta['parameters'] 234 if p[4]=="volume" or (p[4]=="orientation" and input.is_2D)] 235 236 def eval(self, pars): 263 264 def __call__(self, pars, pd_pars, cutoff=1e-5): 265 real = np.float32 if self.input.dtype == F32 else np.float64 266 fixed = [real(p) for p in pars] 267 cutoff = real(cutoff) 268 loops = np.hstack(pd_pars) 269 loops = np.ascontiguousarray(loops.T, self.input.dtype).flatten() 270 loops_N = [np.uint32(len(p[0])) for p in pd_pars] 271 272 #print "opencl eval",pars 273 if len(loops) > MAX_LOOPS: 274 raise ValueError("too many polydispersity points") 237 275 device_num = 0 238 276 res_bi = self.res_b[device_num] 239 277 queuei = environment().queues[device_num] 240 fixed, loops, loop_n = \ 241 gen.kernel_pars(pars, self.meta, self.input.is_2D, dtype=self.input.dtype) 278 loops_bi = self.loops_b[device_num] 242 279 loops_l = cl.LocalMemory(len(loops.data)) 243 real = np.float32 if self.input.dtype == F32 else np.float64244 cutoff = real(self.cutoff)245 246 loops_bi = self.loops_b[device_num]247 280 cl.enqueue_copy(queuei, loops_bi, loops) 248 281 #ctx = environment().context 249 282 #loops_bi = cl.Buffer(ctx, mf.READ_ONLY | mf.COPY_HOST_PTR, hostbuf=loops) 250 pars = self.input.q_buffers + [res_bi,loops_bi,loops_l,cutoff] + fixed + loop_n251 self.kernel(queuei, self.input.global_size, None, * pars)283 args = self.input.q_buffers + [res_bi,loops_bi,loops_l,cutoff] + fixed + loops_N 284 self.kernel(queuei, self.input.global_size, None, *args) 252 285 cl.enqueue_copy(queuei, self.res, res_bi) 253 286 -
sasmodels/models/cylinder.c
r14de349 rce27e21 140 140 DOCUMENTATION END 141 141 */ 142 real volume(real radius, real length);142 real form_volume(real radius, real length); 143 143 real Iq(real q, real sld, real solvent_sld, real radius, real length); 144 144 real Iqxy(real qx, real qy, real sld, real solvent_sld, real radius, real length, real theta, real phi); 145 145 146 146 147 real volume(real radius, real length)147 real form_volume(real radius, real length) 148 148 { 149 149 return M_PI*radius*radius*length; 150 150 } 151 152 151 153 152 real Iq(real q, … … 170 169 // NOTE that for this (Fournet) definition of the integral, one must MULTIPLY by Vcyl 171 170 // The additional volume factor is for polydisperse volume normalization. 172 const real s = (sld - solvent_sld) * volume(radius, length);171 const real s = (sld - solvent_sld) * form_volume(radius, length); 173 172 return REAL(1.0e-4) * form * s * s; 174 173 } … … 203 202 const real bj = (besarg == REAL(0.0) ? REAL(0.5) : J1(besarg)/besarg); 204 203 const real si = (siarg == REAL(0.0) ? REAL(1.0) : sin(siarg)/siarg); 205 const real form = REAL(4.0)*bj*bj*si*si;204 const real form = bj*bj*si*si; 206 205 207 206 // Multiply by contrast^2, normalize by cylinder volume and convert to cm-1 208 207 // NOTE that for this (Fournet) definition of the integral, one must MULTIPLY by Vcyl 209 208 // The additional volume factor is for polydisperse volume normalization. 210 const real s = (sld - solvent_sld) * volume(radius, length);209 const real s = (sld - solvent_sld) * form_volume(radius, length); 211 210 return REAL(1.0e-4) * form * s * s; // * correction; 212 211 } -
sasmodels/weights.py
r14de349 rce27e21 1 # TODO: include dispersion docs with the disperser models 2 from math import sqrt 1 3 import numpy as np 4 from scipy.special import gammaln 2 5 3 class GaussianDispersion(object): 4 def __init__(self, npts=35, width=0, nsigmas=3): #number want, percent deviation, #standard deviations from mean 5 self.type = 'gaussian' 6 self.npts = npts 7 self.width = width 8 self.nsigmas = nsigmas 6 class Dispersion(object): 7 """ 8 Base dispersion object. 9 10 Subclasses should define *_weights(center, sigma, lb, ub)* 11 which returns the x points and their corresponding weights. 12 """ 13 type = "base disperser" 14 default = dict(npts=35, width=0, nsigmas=3) 15 def __init__(self, npts=None, width=None, nsigmas=None): 16 self.npts = self.default['npts'] if npts is None else npts 17 self.width = self.default['width'] if width is None else width 18 self.nsigmas = self.default['nsigmas'] if nsigmas is None else nsigmas 9 19 10 20 def get_pars(self): 11 return self.__dict__ 21 pars = {'type': self.type} 22 pars.update(self.__dict__) 23 return pars 12 24 13 def get_weights(self, center, min, max, relative): 14 """ *center* is the center of the distribution 15 *min*,*max* are the min, max allowed values 16 *relative* is True if the width is relative to the center instead of absolute 17 For polydispersity use relative. For orientation parameters use absolute.""" 25 def set_weights(self, values, weights): 26 raise RuntimeError("set_weights is only available for ArrayDispersion") 27 28 def get_weights(self, center, lb, ub, relative): 29 """ 30 Return the weights for the distribution. 31 32 *center* is the center of the distribution 33 34 *lb*,*ub* are the min and max allowed values 35 36 *relative* is True if the distribution width is proportional to the 37 center value instead of absolute. For polydispersity use relative. 38 For orientation parameters use absolute. 39 """ 18 40 npts, width, nsigmas = self.npts, self.width, self.nsigmas 19 sigma = width * center if relative elsewidth41 sigma = self.width * center if relative else self.width 20 42 if sigma == 0: 21 return np.array([center],'d'), np.array([1.], 'd') 22 x = center + np.linspace(-nsigmas * sigma, +nsigmas * sigma, npts) 23 x = x[(x >= min) & (x <= max)] 43 return np.array([center], 'd'), np.array([1.], 'd') 44 return self._weights(center, sigma, lb, ub) 45 46 def _weights(self, center, sigma, lb, ub): 47 """actual work of computing the weights""" 48 raise NotImplementedError 49 50 def _linspace(self, center, sigma, lb, ub): 51 """helper function to provide linear spaced weight points within range""" 52 npts, nsigmas = self.npts, self.nsigmas 53 x = center + np.linspace(-nsigmas*sigma, +nsigmas*sigma, npts) 54 x = x[(x >= lb) & (x <= ub)] 55 return x 56 57 58 class GaussianDispersion(Dispersion): 59 type = "gaussian" 60 default = dict(npts=35, width=0, nsigmas=3) 61 def _weights(self, center, sigma, lb, ub): 62 x = self._linspace(center, sigma, lb, ub) 24 63 px = np.exp((x-center)**2 / (-2.0 * sigma * sigma)) 25 64 return x, px 26 65 66 67 class RectangleDispersion(Dispersion): 68 type = "rectangle" 69 default = dict(npts=35, width=0, nsigmas=1.70325) 70 def _weights(self, center, sigma, lb, ub): 71 x = self._linspace(center, sigma*sqrt(3.0), lb, ub) 72 px = np.ones_like(x) 73 return x, px 74 75 76 class LogNormalDispersion(Dispersion): 77 type = "lognormal" 78 default = dict(npts=80, width=0, nsigmas=8) 79 def _weights(self, center, sigma, lb, ub): 80 x = self._linspace(center, sigma, max(lb,1e-8), max(ub,1e-8)) 81 px = np.exp(-0.5*(np.log(x)-center)**2)/sigma**2/(x*sigma) 82 return x, px 83 84 85 class SchulzDispersion(Dispersion): 86 type = "schulz" 87 default = dict(npts=80, width=0, nsigmas=8) 88 def _weights(self, center, sigma, lb, ub): 89 x = self._linspace(center, sigma, max(lb,1e-8), max(ub,1e-8)) 90 R= x/center 91 z = (center/sigma)**2 92 arg = z*np.log(z) + (z-1)*np.log(R) - R*z - np.log(center) - gammaln(z) 93 px = np.exp(arg) 94 return x, px 95 96 97 class ArrayDispersion(Dispersion): 98 type = "array" 99 default = dict(npts=35, width=0, nsigmas=1) 100 def __init__(self, npts=None, width=None, nsigmas=None): 101 Dispersion.__init__(self, npts, width, nsigmas) 102 self.values = np.array([0.], 'd') 103 self.weights = np.array([1.], 'd') 104 105 def set_weights(self, values, weights): 106 self.values = np.ascontiguousarray(values, 'd') 107 self.weights = np.ascontiguousarray(weights, 'd') 108 self.npts = len(values) 109 110 def _weights(self, center, sigma, lb, ub): 111 # TODO: interpolate into the array dispersion using npts 112 x = center + self.values*sigma 113 idx = (x>=lb)&(x<=ub) 114 x = x[idx] 115 px = self.weights[idx] 116 return x, px 117 118 119 models = dict((d.type,d) for d in ( 120 GaussianDispersion, RectangleDispersion, 121 ArrayDispersion, SchulzDispersion, LogNormalDispersion 122 )) 123 124 def get_weights(disperser, n, width, nsigma, value, limits, relative): 125 cls = models[disperser] 126 obj = cls(n, width, nsigma) 127 v,w = obj.get_weights(value, limits[0], limits[1], relative) 128 return v,w
Note: See TracChangeset
for help on using the changeset viewer.