source: sasmodels/sasmodels/core.py @ d19962c

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

working vector parameter example using dll engine

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