source: sasmodels/sasmodels/core.py @ 725ee36

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

clean up meshgrid cruft handling

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