source: sasmodels/sasmodels/core.py @ 7891a2a

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

Merge branch 'master' into polydisp

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