source: sasmodels/sasmodels/core.py @ b7172bb

core_shell_microgelscostrafo411magnetic_modelrelease_v0.94release_v0.95ticket-1257-vesicle-productticket_1156ticket_1265_superballticket_822_more_unit_tests
Last change on this file since b7172bb was b7172bb, checked in by Paul Kienzle <pkienzle@…>, 8 years ago

… and remember to use the new meshgrid wrapper the I created

  • Property mode set to 100644
File size: 10.2 KB
Line 
1"""
2Core model handling routines.
3"""
4__all__ = [
5    "list_models", "load_model_info", "precompile_dll",
6    "build_model", "make_kernel", "call_kernel", "call_ER_VR",
7    ]
8
9
10from os.path import basename, dirname, join as joinpath, splitext
11from glob import glob
12import imp
13
14import numpy as np
15
16from . import models
17from . import weights
18from . import generate
19# TODO: remove circular references between product and core
20# product uses call_ER/call_VR, core uses make_product_info/ProductModel
21#from . import product
22from . import mixture
23from . import kernelpy
24from . import kerneldll
25try:
26    from . import kernelcl
27    HAVE_OPENCL = True
28except:
29    HAVE_OPENCL = False
30
31try:
32    np.meshgrid([])
33    meshgrid = np.meshgrid
34except ValueError:
35    # CRUFT: np.meshgrid requires multiple vectors
36    def meshgrid(*args):
37        if len(args) > 1:
38            return np.meshgrid(*args)
39        else:
40            return [np.asarray(v) for v in args]
41
42
43def list_models():
44    """
45    Return the list of available models on the model path.
46    """
47    root = dirname(__file__)
48    files = sorted(glob(joinpath(root, 'models', "[a-zA-Z]*.py")))
49    available_models = [basename(f)[:-3] for f in files]
50    return available_models
51
52def isstr(s):
53    """
54    Return True if *s* is a string-like object.
55    """
56    try: s + ''
57    except: return False
58    return True
59
60def load_model(model_name, **kw):
61    """
62    Load model info and build model.
63    """
64    return build_model(load_model_info(model_name), **kw)
65
66def load_model_info_from_path(path):
67    # Pull off the last .ext if it exists; there may be others
68    name = basename(splitext(path)[0])
69
70    # Not cleaning name since don't need to be able to reload this
71    # model later
72    # Should probably turf the model from sys.modules after we are done...
73
74    # Placing the model in the 'sasmodels.custom' name space, even
75    # though it doesn't actually exist.  imp.load_source doesn't seem
76    # to care.
77    kernel_module = imp.load_source('sasmodels.custom.'+name, path)
78
79    # Now that we have the module, we can load it as usual
80    model_info = generate.make_model_info(kernel_module)
81    return model_info
82
83def load_model_info(model_name):
84    """
85    Load a model definition given the model name.
86
87    This returns a handle to the module defining the model.  This can be
88    used with functions in generate to build the docs or extract model info.
89    """
90    parts = model_name.split('+')
91    if len(parts) > 1:
92        model_info_list = [load_model_info(p) for p in parts]
93        return mixture.make_mixture_info(model_info_list)
94
95    parts = model_name.split('*')
96    if len(parts) > 1:
97        from . import product
98        # Note: currently have circular reference
99        if len(parts) > 2:
100            raise ValueError("use P*S to apply structure factor S to model P")
101        P_info, Q_info = [load_model_info(p) for p in parts]
102        return product.make_product_info(P_info, Q_info)
103
104    #import sys; print "\n".join(sys.path)
105    __import__('sasmodels.models.'+model_name)
106    kernel_module = getattr(models, model_name, None)
107    return generate.make_model_info(kernel_module)
108
109
110def build_model(model_info, dtype=None, platform="ocl"):
111    """
112    Prepare the model for the default execution platform.
113
114    This will return an OpenCL model, a DLL model or a python model depending
115    on the model and the computing platform.
116
117    *model_info* is the model definition structure returned from
118    :func:`load_model_info`.
119
120    *dtype* indicates whether the model should use single or double precision
121    for the calculation. Any valid numpy single or double precision identifier
122    is valid, such as 'single', 'f', 'f32', or np.float32 for single, or
123    'double', 'd', 'f64'  and np.float64 for double.  If *None*, then use
124    'single' unless the model defines single=False.
125
126    *platform* should be "dll" to force the dll to be used for C models,
127    otherwise it uses the default "ocl".
128    """
129    composition = model_info.get('composition', None)
130    if composition is not None:
131        composition_type, parts = composition
132        models = [build_model(p, dtype=dtype, platform=platform) for p in parts]
133        if composition_type == 'mixture':
134            return mixture.MixtureModel(model_info, models)
135        elif composition_type == 'product':
136            from . import product
137            P, S = models
138            return product.ProductModel(model_info, P, S)
139        else:
140            raise ValueError('unknown mixture type %s'%composition_type)
141
142    ## for debugging:
143    ##  1. uncomment open().write so that the source will be saved next time
144    ##  2. run "python -m sasmodels.direct_model $MODELNAME" to save the source
145    ##  3. recomment the open.write() and uncomment open().read()
146    ##  4. rerun "python -m sasmodels.direct_model $MODELNAME"
147    ##  5. uncomment open().read() so that source will be regenerated from model
148    # open(model_info['name']+'.c','w').write(source)
149    # source = open(model_info['name']+'.cl','r').read()
150    source = generate.make_source(model_info)
151    if dtype is None:
152        dtype = 'single' if model_info['single'] else 'double'
153    if callable(model_info.get('Iq', None)):
154        return kernelpy.PyModel(model_info)
155    if (platform == "dll"
156            or not HAVE_OPENCL
157            or not kernelcl.environment().has_type(dtype)):
158        return kerneldll.load_dll(source, model_info, dtype)
159    else:
160        return kernelcl.GpuModel(source, model_info, dtype)
161
162def precompile_dll(model_name, dtype="double"):
163    """
164    Precompile the dll for a model.
165
166    Returns the path to the compiled model, or None if the model is a pure
167    python model.
168
169    This can be used when build the windows distribution of sasmodels
170    (which may be missing the OpenCL driver and the dll compiler), or
171    otherwise sharing models with windows users who do not have a compiler.
172
173    See :func:`sasmodels.kerneldll.make_dll` for details on controlling the
174    dll path and the allowed floating point precision.
175    """
176    model_info = load_model_info(model_name)
177    source = generate.make_source(model_info)
178    return kerneldll.make_dll(source, model_info, dtype=dtype) if source else None
179
180
181def make_kernel(model, q_vectors):
182    """
183    Return a computation kernel from the model definition and the q input.
184    """
185    return model(q_vectors)
186
187def get_weights(model_info, pars, name):
188    """
189    Generate the distribution for parameter *name* given the parameter values
190    in *pars*.
191
192    Uses "name", "name_pd", "name_pd_type", "name_pd_n", "name_pd_sigma"
193    from the *pars* dictionary for parameter value and parameter dispersion.
194    """
195    relative = name in model_info['partype']['pd-rel']
196    limits = model_info['limits'][name]
197    disperser = pars.get(name+'_pd_type', 'gaussian')
198    value = pars.get(name, model_info['defaults'][name])
199    npts = pars.get(name+'_pd_n', 0)
200    width = pars.get(name+'_pd', 0.0)
201    nsigma = pars.get(name+'_pd_nsigma', 3.0)
202    value, weight = weights.get_weights(
203        disperser, npts, width, nsigma, value, limits, relative)
204    return value, weight / np.sum(weight)
205
206def dispersion_mesh(pars):
207    """
208    Create a mesh grid of dispersion parameters and weights.
209
210    Returns [p1,p2,...],w where pj is a vector of values for parameter j
211    and w is a vector containing the products for weights for each
212    parameter set in the vector.
213    """
214    value, weight = zip(*pars)
215    value = [v.flatten() for v in meshgrid(*value)]
216    weight = np.vstack([v.flatten() for v in meshgrid(*weight)])
217    weight = np.prod(weight, axis=0)
218    return value, weight
219
220def call_kernel(kernel, pars, cutoff=0, mono=False):
221    """
222    Call *kernel* returned from :func:`make_kernel` with parameters *pars*.
223
224    *cutoff* is the limiting value for the product of dispersion weights used
225    to perform the multidimensional dispersion calculation more quickly at a
226    slight cost to accuracy. The default value of *cutoff=0* integrates over
227    the entire dispersion cube.  Using *cutoff=1e-5* can be 50% faster, but
228    with an error of about 1%, which is usually less than the measurement
229    uncertainty.
230    """
231    fixed_pars = [pars.get(name, kernel.info['defaults'][name])
232                  for name in kernel.fixed_pars]
233    if mono:
234        pd_pars = [( np.array([pars[name]]), np.array([1.0]) )
235                   for name in kernel.pd_pars]
236    else:
237        pd_pars = [get_weights(kernel.info, pars, name) for name in kernel.pd_pars]
238    return kernel(fixed_pars, pd_pars, cutoff=cutoff)
239
240def call_ER_VR(model_info, vol_pars):
241    """
242    Return effect radius and volume ratio for the model.
243
244    *info* is either *kernel.info* for *kernel=make_kernel(model,q)*
245    or *model.info*.
246
247    *pars* are the parameters as expected by :func:`call_kernel`.
248    """
249    ER = model_info.get('ER', None)
250    VR = model_info.get('VR', None)
251    value, weight = dispersion_mesh(vol_pars)
252
253    individual_radii = ER(*value) if ER else 1.0
254    whole, part = VR(*value) if VR else (1.0, 1.0)
255
256    effect_radius = np.sum(weight*individual_radii) / np.sum(weight)
257    volume_ratio = np.sum(weight*part)/np.sum(weight*whole)
258    return effect_radius, volume_ratio
259
260
261def call_ER(info, pars):
262    """
263    Call the model ER function using *pars*.
264    *info* is either *model.info* if you have a loaded model, or *kernel.info*
265    if you have a model kernel prepared for evaluation.
266    """
267    ER = info.get('ER', None)
268    if ER is None:
269        return 1.0
270    else:
271        vol_pars = [get_weights(info, pars, name)
272                    for name in info['partype']['volume']]
273        value, weight = dispersion_mesh(vol_pars)
274        individual_radii = ER(*value)
275        #print(values[0].shape, weights.shape, fv.shape)
276        return np.sum(weight*individual_radii) / np.sum(weight)
277
278def call_VR(info, pars):
279    """
280    Call the model VR function using *pars*.
281    *info* is either *model.info* if you have a loaded model, or *kernel.info*
282    if you have a model kernel prepared for evaluation.
283    """
284    VR = info.get('VR', None)
285    if VR is None:
286        return 1.0
287    else:
288        vol_pars = [get_weights(info, pars, name)
289                    for name in info['partype']['volume']]
290        value, weight = dispersion_mesh(vol_pars)
291        whole, part = VR(*value)
292        return np.sum(weight*part)/np.sum(weight*whole)
293
294# TODO: remove call_ER, call_VR
295
Note: See TracBrowser for help on using the repository browser.