source: sasmodels/sasmodels/core.py @ 3d9001f

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

fix missing models problem for py2exe version: now looking in sasmodels-data for the model definitions

  • Property mode set to 100644
File size: 8.5 KB
Line 
1"""
2Core model handling routines.
3"""
4from __future__ import print_function
5
6__all__ = [
7    "list_models", "load_model", "load_model_info",
8    "build_model", "precompile_dlls",
9    ]
10
11import os
12from os.path import basename, dirname, join as joinpath
13from glob import glob
14
15import numpy as np # type: ignore
16
17from . import generate
18from . import modelinfo
19from . import product
20from . import mixture
21from . import kernelpy
22from . import kerneldll
23try:
24    from . import kernelcl
25    HAVE_OPENCL = True
26except Exception:
27    HAVE_OPENCL = False
28
29try:
30    from typing import List, Union, Optional, Any
31    from .kernel import KernelModel
32    from .modelinfo import ModelInfo
33except ImportError:
34    pass
35
36try:
37    np.meshgrid([])
38    meshgrid = np.meshgrid
39except Exception:
40    # CRUFT: np.meshgrid requires multiple vectors
41    def meshgrid(*args):
42        if len(args) > 1:
43            return np.meshgrid(*args)
44        else:
45            return [np.asarray(v) for v in args]
46
47# TODO: refactor composite model support
48# The current load_model_info/build_model does not reuse existing model
49# definitions when loading a composite model, instead reloading and
50# rebuilding the kernel for each component model in the expression.  This
51# is fine in a scripting environment where the model is built when the script
52# starts and is thrown away when the script ends, but may not be the best
53# solution in a long-lived application.  This affects the following functions:
54#
55#    load_model
56#    load_model_info
57#    build_model
58
59KINDS = ("all", "py", "c", "double", "single", "1d", "2d",
60         "nonmagnetic", "magnetic")
61def list_models(kind=None):
62    # type: () -> List[str]
63    """
64    Return the list of available models on the model path.
65    """
66    if kind and kind not in KINDS:
67        raise ValueError("kind not in " + ", ".join(KINDS))
68    files = sorted(glob(joinpath(generate.MODEL_PATH, "[a-zA-Z]*.py")))
69    available_models = [basename(f)[:-3] for f in files]
70    selected = [name for name in available_models if _matches(name, kind)]
71
72    return selected
73
74def _matches(name, kind):
75    if kind is None or kind == "all":
76        return True
77    info = load_model_info(name)
78    pars = info.parameters.kernel_parameters
79    if kind == "py" and callable(info.Iq):
80        return True
81    elif kind == "c" and not callable(info.Iq):
82        return True
83    elif kind == "double" and not info.single:
84        return True
85    elif kind == "single" and info.single:
86        return True
87    elif kind == "2d" and any(p.type == 'orientation' for p in pars):
88        return True
89    elif kind == "1d" and any(p.type != 'orientation' for p in pars):
90        return True
91    elif kind == "magnetic" and any(p.type == 'sld' for p in pars):
92        return True
93    elif kind == "nonmagnetic" and any(p.type != 'sld' for p in pars):
94        return True
95    return False
96
97def load_model(model_name, dtype=None, platform='ocl'):
98    # type: (str, str, str) -> KernelModel
99    """
100    Load model info and build model.
101
102    *model_name* is the name of the model as used by :func:`load_model_info`.
103    Additional keyword arguments are passed directly to :func:`build_model`.
104    """
105    return build_model(load_model_info(model_name),
106                       dtype=dtype, platform=platform)
107
108
109def load_model_info(model_name):
110    # type: (str) -> modelinfo.ModelInfo
111    """
112    Load a model definition given the model name.
113
114    This returns a handle to the module defining the model.  This can be
115    used with functions in generate to build the docs or extract model info.
116    """
117    parts = model_name.split('+')
118    if len(parts) > 1:
119        model_info_list = [load_model_info(p) for p in parts]
120        return mixture.make_mixture_info(model_info_list)
121
122    parts = model_name.split('*')
123    if len(parts) > 1:
124        if len(parts) > 2:
125            raise ValueError("use P*S to apply structure factor S to model P")
126        P_info, Q_info = [load_model_info(p) for p in parts]
127        return product.make_product_info(P_info, Q_info)
128
129    kernel_module = generate.load_kernel_module(model_name)
130    return modelinfo.make_model_info(kernel_module)
131
132
133def build_model(model_info, dtype=None, platform="ocl"):
134    # type: (modelinfo.ModelInfo, str, str) -> KernelModel
135    """
136    Prepare the model for the default execution platform.
137
138    This will return an OpenCL model, a DLL model or a python model depending
139    on the model and the computing platform.
140
141    *model_info* is the model definition structure returned from
142    :func:`load_model_info`.
143
144    *dtype* indicates whether the model should use single or double precision
145    for the calculation.  Choices are 'single', 'double', 'quad', 'half',
146    or 'fast'.  If *dtype* ends with '!', then force the use of the DLL rather
147    than OpenCL for the calculation.
148
149    *platform* should be "dll" to force the dll to be used for C models,
150    otherwise it uses the default "ocl".
151    """
152    composition = model_info.composition
153    if composition is not None:
154        composition_type, parts = composition
155        models = [build_model(p, dtype=dtype, platform=platform) for p in parts]
156        if composition_type == 'mixture':
157            return mixture.MixtureModel(model_info, models)
158        elif composition_type == 'product':
159            P, S = models
160            return product.ProductModel(model_info, P, S)
161        else:
162            raise ValueError('unknown mixture type %s'%composition_type)
163
164    # If it is a python model, return it immediately
165    if callable(model_info.Iq):
166        return kernelpy.PyModel(model_info)
167
168    numpy_dtype, fast, platform = parse_dtype(model_info, dtype, platform)
169
170    source = generate.make_source(model_info)
171    if platform == "dll":
172        #print("building dll", numpy_dtype)
173        return kerneldll.load_dll(source['dll'], model_info, numpy_dtype)
174    else:
175        #print("building ocl", numpy_dtype)
176        return kernelcl.GpuModel(source, model_info, numpy_dtype, fast=fast)
177
178def precompile_dlls(path, dtype="double"):
179    # type: (str, str) -> List[str]
180    """
181    Precompile the dlls for all builtin models, returning a list of dll paths.
182
183    *path* is the directory in which to save the dlls.  It will be created if
184    it does not already exist.
185
186    This can be used when build the windows distribution of sasmodels
187    which may be missing the OpenCL driver and the dll compiler.
188    """
189    numpy_dtype = np.dtype(dtype)
190    if not os.path.exists(path):
191        os.makedirs(path)
192    compiled_dlls = []
193    for model_name in list_models():
194        model_info = load_model_info(model_name)
195        if not callable(model_info.Iq):
196            source = generate.make_source(model_info)['dll']
197            old_path = kerneldll.DLL_PATH
198            try:
199                kerneldll.DLL_PATH = path
200                dll = kerneldll.make_dll(source, model_info, dtype=numpy_dtype)
201            finally:
202                kerneldll.DLL_PATH = old_path
203            compiled_dlls.append(dll)
204    return compiled_dlls
205
206def parse_dtype(model_info, dtype=None, platform=None):
207    # type: (ModelInfo, str, str) -> (np.dtype, bool, str)
208    """
209    Interpret dtype string, returning np.dtype and fast flag.
210
211    Possible types include 'half', 'single', 'double' and 'quad'.  If the
212    type is 'fast', then this is equivalent to dtype 'single' with the
213    fast flag set to True.
214    """
215    # Assign default platform, overriding ocl with dll if OpenCL is unavailable
216    if platform is None:
217        platform = "ocl"
218    if platform == "ocl" and not HAVE_OPENCL:
219        platform = "dll"
220
221    # Check if type indicates dll regardless of which platform is given
222    if dtype is not None and dtype.endswith('!'):
223        platform = "dll"
224        dtype = dtype[:-1]
225
226    # Convert special type names "half", "fast", and "quad"
227    fast = (dtype == "fast")
228    if fast:
229        dtype = "single"
230    elif dtype == "quad":
231        dtype = "longdouble"
232    elif dtype == "half":
233        dtype = "f16"
234
235    # Convert dtype string to numpy dtype.
236    if dtype is None:
237        numpy_dtype = (generate.F32 if platform == "ocl" and model_info.single
238                       else generate.F64)
239    else:
240        numpy_dtype = np.dtype(dtype)
241
242    # Make sure that the type is supported by opencl, otherwise use dll
243    if platform == "ocl":
244        env = kernelcl.environment()
245        if not env.has_type(numpy_dtype):
246            platform = "dll"
247            if dtype is None:
248                numpy_dtype = generate.F64
249
250    return numpy_dtype, fast, platform
251
252def list_models_main():
253    import sys
254    kind = sys.argv[1] if len(sys.argv) > 1 else "all"
255    print("\n".join(list_models(kind)))
256
257if __name__ == "__main__":
258    list_models_main()
Note: See TracBrowser for help on using the repository browser.