source: sasmodels/sasmodels/core.py @ fb5914f

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

bind sasview to new parameter table definition

  • Property mode set to 100644
File size: 10.9 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
244    details, weights, values = build_details(kernel, vw_pairs)
245    return kernel(details, weights, values, cutoff)
246
247def build_details(kernel, pairs):
248    values, weights = zip(*pairs)
249    if max([len(w) for w in weights]) > 1:
250        details = generate.poly_details(kernel.info, weights)
251    else:
252        details = kernel.info['mono_details']
253    weights, values = [np.hstack(v) for v in (weights, values)]
254    weights = weights.astype(dtype=kernel.dtype)
255    values = values.astype(dtype=kernel.dtype)
256    return details, weights, values
257
258
259def call_ER_VR(model_info, vol_pars):
260    """
261    Return effect radius and volume ratio for the model.
262
263    *info* is either *kernel.info* for *kernel=make_kernel(model,q)*
264    or *model.info*.
265
266    *pars* are the parameters as expected by :func:`call_kernel`.
267    """
268    ER = model_info.get('ER', None)
269    VR = model_info.get('VR', None)
270    value, weight = dispersion_mesh(vol_pars)
271
272    individual_radii = ER(*value) if ER else 1.0
273    whole, part = VR(*value) if VR else (1.0, 1.0)
274
275    effect_radius = np.sum(weight*individual_radii) / np.sum(weight)
276    volume_ratio = np.sum(weight*part)/np.sum(weight*whole)
277    return effect_radius, volume_ratio
278
279
280def call_ER(model_info, values):
281    """
282    Call the model ER function using *values*. *model_info* is either
283    *model.info* if you have a loaded model, or *kernel.info* if you
284    have a model kernel prepared for evaluation.
285    """
286    ER = model_info.get('ER', None)
287    if ER is None:
288        return 1.0
289    else:
290        vol_pars = [get_weights(parameter, values)
291                    for parameter in model_info['parameters']
292                    if parameter.type == 'volume']
293        value, weight = dispersion_mesh(vol_pars)
294        individual_radii = ER(*value)
295        #print(values[0].shape, weights.shape, fv.shape)
296        return np.sum(weight*individual_radii) / np.sum(weight)
297
298def call_VR(model_info, values):
299    """
300    Call the model VR function using *pars*.
301    *info* is either *model.info* if you have a loaded model, or *kernel.info*
302    if you have a model kernel prepared for evaluation.
303    """
304    VR = model_info.get('VR', None)
305    if VR is None:
306        return 1.0
307    else:
308        vol_pars = [get_weights(parameter, values)
309                    for parameter in model_info['parameters']
310                    if parameter.type == 'volume']
311        value, weight = dispersion_mesh(vol_pars)
312        whole, part = VR(*value)
313        return np.sum(weight*part)/np.sum(weight*whole)
314
315# TODO: remove call_ER, call_VR
316
Note: See TracBrowser for help on using the repository browser.