source: sasmodels/sasmodels/core.py @ a557a99

core_shell_microgelscostrafo411magnetic_modelrelease_v0.94release_v0.95ticket-1257-vesicle-productticket_1156ticket_1265_superballticket_822_more_unit_tests
Last change on this file since a557a99 was a557a99, checked in by wojciech, 8 years ago

A few fixes to make code cleaner and add PYOPENCL_CTX control

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