source: sasmodels/sasmodels/core.py @ 5b0335b

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

Merge remote-tracking branch 'origin/master' into polydisp

Conflicts:

sasmodels/core.py
sasmodels/models/spherical_sld.py

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