source: sasmodels/sasmodels/core.py @ 27fc92f

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

Changing unset to None for SAS_OPENCL variable

  • Property mode set to 100644
File size: 9.1 KB
RevLine 
[aa4946b]1"""
2Core model handling routines.
3"""
[6d6508e]4from __future__ import print_function
5
[98f60fc]6__all__ = [
[6d6508e]7    "list_models", "load_model", "load_model_info",
[2547694]8    "build_model", "precompile_dlls",
[98f60fc]9    ]
[f734e7d]10
[7bf4757]11import os
[2547694]12from os.path import basename, dirname, join as joinpath
[f734e7d]13from glob import glob
14
[7ae2b7f]15import numpy as np # type: ignore
[f734e7d]16
[aa4946b]17from . import generate
[6d6508e]18from . import modelinfo
19from . import product
[72a081d]20from . import mixture
[aa4946b]21from . import kernelpy
22from . import kerneldll
[880a2ed]23
[27fc92f]24if os.environ["SAS_OPENCL"]=="None":
[aa4946b]25    HAVE_OPENCL = False
[880a2ed]26else:
27    try:
28        from . import kernelcl
29        HAVE_OPENCL = True
30    except Exception:
31        HAVE_OPENCL = False
[aa4946b]32
[f619de7]33try:
34    from typing import List, Union, Optional, Any
35    from .kernel import KernelModel
[dd7fc12]36    from .modelinfo import ModelInfo
[f619de7]37except ImportError:
38    pass
39
[4d76711]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
[f734e7d]51
[8407d8c]52KINDS = ("all", "py", "c", "double", "single", "opencl", "1d", "2d",
[2547694]53         "nonmagnetic", "magnetic")
[0b03001]54def list_models(kind=None):
[f619de7]55    # type: () -> List[str]
[aa4946b]56    """
57    Return the list of available models on the model path.
[40a87fa]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
[8407d8c]66        * opencl: controls if OpenCL is supperessed
[40a87fa]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
[aa4946b]71    """
[0b03001]72    if kind and kind not in KINDS:
[2547694]73        raise ValueError("kind not in " + ", ".join(KINDS))
[3d9001f]74    files = sorted(glob(joinpath(generate.MODEL_PATH, "[a-zA-Z]*.py")))
[f734e7d]75    available_models = [basename(f)[:-3] for f in files]
[0b03001]76    selected = [name for name in available_models if _matches(name, kind)]
77
78    return selected
79
80def _matches(name, kind):
[2547694]81    if kind is None or kind == "all":
[0b03001]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
[d2d6100]91    elif kind == "single" and info.single:
92        return True
[8407d8c]93    elif kind == "opencl" and info.opencl:
[407bf48]94        return True
[2547694]95    elif kind == "2d" and any(p.type == 'orientation' for p in pars):
[d2d6100]96        return True
[40a87fa]97    elif kind == "1d" and all(p.type != 'orientation' for p in pars):
[0b03001]98        return True
[2547694]99    elif kind == "magnetic" and any(p.type == 'sld' for p in pars):
[0b03001]100        return True
[2547694]101    elif kind == "nonmagnetic" and any(p.type != 'sld' for p in pars):
[d2d6100]102        return True
[0b03001]103    return False
[f734e7d]104
[f619de7]105def load_model(model_name, dtype=None, platform='ocl'):
[dd7fc12]106    # type: (str, str, str) -> KernelModel
[b8e5e21]107    """
108    Load model info and build model.
[f619de7]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`.
[b8e5e21]112    """
[f619de7]113    return build_model(load_model_info(model_name),
114                       dtype=dtype, platform=platform)
[aa4946b]115
116
[17bbadd]117def load_model_info(model_name):
[f619de7]118    # type: (str) -> modelinfo.ModelInfo
[aa4946b]119    """
120    Load a model definition given the model name.
[1d4017a]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.
[aa4946b]124    """
[72a081d]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
[68e7f9d]137    kernel_module = generate.load_kernel_module(model_name)
[6d6508e]138    return modelinfo.make_model_info(kernel_module)
[d19962c]139
140
[17bbadd]141def build_model(model_info, dtype=None, platform="ocl"):
[dd7fc12]142    # type: (modelinfo.ModelInfo, str, str) -> KernelModel
[aa4946b]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
[17bbadd]149    *model_info* is the model definition structure returned from
150    :func:`load_model_info`.
[bcd3aa3]151
[aa4946b]152    *dtype* indicates whether the model should use single or double precision
[dd7fc12]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.
[aa4946b]156
157    *platform* should be "dll" to force the dll to be used for C models,
158    otherwise it uses the default "ocl".
159    """
[6d6508e]160    composition = model_info.composition
[72a081d]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':
[e79f0a5]167            P, S = models
[72a081d]168            return product.ProductModel(model_info, P, S)
169        else:
170            raise ValueError('unknown mixture type %s'%composition_type)
[aa4946b]171
[fa5fd8d]172    # If it is a python model, return it immediately
173    if callable(model_info.Iq):
174        return kernelpy.PyModel(model_info)
175
[def2c1b]176    numpy_dtype, fast, platform = parse_dtype(model_info, dtype, platform)
[7891a2a]177
[72a081d]178    source = generate.make_source(model_info)
[7891a2a]179    if platform == "dll":
[f2f67a6]180        #print("building dll", numpy_dtype)
[a4280bd]181        return kerneldll.load_dll(source['dll'], model_info, numpy_dtype)
[aa4946b]182    else:
[f2f67a6]183        #print("building ocl", numpy_dtype)
[dd7fc12]184        return kernelcl.GpuModel(source, model_info, numpy_dtype, fast=fast)
[aa4946b]185
[7bf4757]186def precompile_dlls(path, dtype="double"):
[7891a2a]187    # type: (str, str) -> List[str]
[b8e5e21]188    """
[7bf4757]189    Precompile the dlls for all builtin models, returning a list of dll paths.
[b8e5e21]190
[7bf4757]191    *path* is the directory in which to save the dlls.  It will be created if
192    it does not already exist.
[b8e5e21]193
194    This can be used when build the windows distribution of sasmodels
[7bf4757]195    which may be missing the OpenCL driver and the dll compiler.
[b8e5e21]196    """
[7891a2a]197    numpy_dtype = np.dtype(dtype)
[7bf4757]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)
[a4280bd]203        if not callable(model_info.Iq):
204            source = generate.make_source(model_info)['dll']
[7bf4757]205            old_path = kerneldll.DLL_PATH
206            try:
207                kerneldll.DLL_PATH = path
[def2c1b]208                dll = kerneldll.make_dll(source, model_info, dtype=numpy_dtype)
[7bf4757]209            finally:
210                kerneldll.DLL_PATH = old_path
211            compiled_dlls.append(dll)
212    return compiled_dlls
[b8e5e21]213
[7891a2a]214def parse_dtype(model_info, dtype=None, platform=None):
215    # type: (ModelInfo, str, str) -> (np.dtype, bool, str)
[dd7fc12]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    """
[7891a2a]223    # Assign default platform, overriding ocl with dll if OpenCL is unavailable
[8407d8c]224    # If opencl=False OpenCL is switched off
[fe77343]225
[f49675c]226    if platform is None:
[7891a2a]227        platform = "ocl"
[8407d8c]228    if platform == "ocl" and not HAVE_OPENCL or not model_info.opencl:
[7891a2a]229        platform = "dll"
230
231    # Check if type indicates dll regardless of which platform is given
232    if dtype is not None and dtype.endswith('!'):
233        platform = "dll"
[dd7fc12]234        dtype = dtype[:-1]
235
[7891a2a]236    # Convert special type names "half", "fast", and "quad"
[2547694]237    fast = (dtype == "fast")
[7891a2a]238    if fast:
239        dtype = "single"
[2547694]240    elif dtype == "quad":
[7891a2a]241        dtype = "longdouble"
[2547694]242    elif dtype == "half":
[7891a2a]243        dtype = "f16"
244
245    # Convert dtype string to numpy dtype.
246    if dtype is None:
[2547694]247        numpy_dtype = (generate.F32 if platform == "ocl" and model_info.single
248                       else generate.F64)
[dd7fc12]249    else:
[7891a2a]250        numpy_dtype = np.dtype(dtype)
[9890053]251
[7891a2a]252    # Make sure that the type is supported by opencl, otherwise use dll
[2547694]253    if platform == "ocl":
[7891a2a]254        env = kernelcl.environment()
255        if not env.has_type(numpy_dtype):
256            platform = "dll"
257            if dtype is None:
258                numpy_dtype = generate.F64
[dd7fc12]259
[7891a2a]260    return numpy_dtype, fast, platform
[0c24a82]261
[2547694]262def list_models_main():
[40a87fa]263    # type: () -> None
264    """
265    Run list_models as a main program.  See :func:`list_models` for the
266    kinds of models that can be requested on the command line.
267    """
[0b03001]268    import sys
269    kind = sys.argv[1] if len(sys.argv) > 1 else "all"
270    print("\n".join(list_models(kind)))
[2547694]271
272if __name__ == "__main__":
273    list_models_main()
Note: See TracBrowser for help on using the repository browser.