source: sasmodels/sasmodels/kerneldll.py @ fa5fd8d

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

still more linting; ignore numpy types

  • Property mode set to 100644
File size: 11.5 KB
RevLine 
[92da231]1r"""
[eafc9fa]2DLL driver for C kernels
[750ffa5]3
4The global attribute *ALLOW_SINGLE_PRECISION_DLLS* should be set to *True* if
5you wish to allow single precision floating point evaluation for the compiled
6models, otherwise it defaults to *False*.
[92da231]7
8The compiler command line is stored in the attribute *COMPILE*, with string
9substitutions for %(source)s and %(output)s indicating what to compile and
10where to store it.  The actual command is system dependent.
11
12On windows systems, you have a choice of compilers.  *MinGW* is the GNU
13compiler toolchain, available in packages such as anaconda and PythonXY,
14or available stand alone. This toolchain has had difficulties on some
15systems, and may or may not work for you.  In order to build DLLs, *gcc*
16must be on your path.  If the environment variable *SAS_OPENMP* is given
17then -fopenmp is added to the compiler flags.  This requires a version
18of MinGW compiled with OpenMP support.
19
20An alternative toolchain uses the Microsoft Visual C++ compiler, available
21free from microsoft:
22
[d138d43]23    `<http://www.microsoft.com/en-us/download/details.aspx?id=44266>`_
[92da231]24
25Again, this requires that the compiler is available on your path.  This is
26done by running vcvarsall.bat in a windows terminal.  Install locations are
27system dependent, such as:
28
29    C:\Program Files (x86)\Common Files\Microsoft\Visual C++ for Python\9.0\vcvarsall.bat
30
31or maybe
32
33    C:\Users\yourname\AppData\Local\Programs\Common\Microsoft\Visual C++ for Python\9.0\vcvarsall.bat
34
35And again, the environment variable *SAS_OPENMP* controls whether OpenMP is
36used to compile the C code.  This requires the Microsoft vcomp90.dll library,
37which doesn't seem to be included with the compiler, nor does there appear
38to be a public download location.  There may be one on your machine already
39in a location such as:
40
41    C:\Windows\winsxs\x86_microsoft.vc90.openmp*\vcomp90.dll
42
43If you copy this onto your path, such as the python directory or the install
44directory for this application, then OpenMP should be supported.
[14de349]45"""
[eafc9fa]46from __future__ import print_function
[750ffa5]47
[5d4777d]48import sys
49import os
[df4dc86]50import tempfile
[7ae2b7f]51import ctypes as ct  # type: ignore
52from ctypes import c_void_p, c_int32, c_longdouble, c_double, c_float  # type: ignore
[14de349]53
[7ae2b7f]54import numpy as np  # type: ignore
[14de349]55
[cb6ecf4]56from . import generate
[6d6508e]57from . import details
[f619de7]58from .kernel import KernelModel, Kernel
59from .kernelpy import PyInput
[2c801fe]60from .exception import annotate_exception
[f619de7]61from .generate import F16, F32, F64
62
63try:
64    from typing import Tuple, Callable, Any
65    from .modelinfo import ModelInfo
66    from .details import CallDetails
67except ImportError:
68    pass
[14de349]69
[5d4777d]70# Compiler platform details
71if sys.platform == 'darwin':
[216a9e1]72    #COMPILE = "gcc-mp-4.7 -shared -fPIC -std=c99 -fopenmp -O2 -Wall %s -o %s -lm -lgomp"
[4a72d1a]73    COMPILE = "gcc -shared -fPIC -std=c99 -O2 -Wall %(source)s -o %(output)s -lm"
[5d4777d]74elif os.name == 'nt':
[3c56da87]75    # call vcvarsall.bat before compiling to set path, headers, libs, etc.
[68d3c1b]76    if "VCINSTALLDIR" in os.environ:
[a30cdd5]77        # MSVC compiler is available, so use it.  OpenMP requires a copy of
78        # vcomp90.dll on the path.  One may be found here:
79        #       C:/Windows/winsxs/x86_microsoft.vc90.openmp*/vcomp90.dll
80        # Copy this to the python directory and uncomment the OpenMP COMPILE
[f734e7d]81        # TODO: remove intermediate OBJ file created in the directory
82        # TODO: maybe don't use randomized name for the c file
[a30cdd5]83        CC = "cl /nologo /Ox /MD /W3 /GS- /DNDEBUG /Tp%(source)s "
84        LN = "/link /DLL /INCREMENTAL:NO /MANIFEST /OUT:%(output)s"
85        if "SAS_OPENMP" in os.environ:
86            COMPILE = " ".join((CC, "/openmp", LN))
87        else:
88            COMPILE = " ".join((CC, LN))
[68d3c1b]89    else:
90        COMPILE = "gcc -shared -fPIC -std=c99 -O2 -Wall %(source)s -o %(output)s -lm"
[a30cdd5]91        if "SAS_OPENMP" in os.environ:
[03cac08]92            COMPILE += " -fopenmp"
[5d4777d]93else:
[a30cdd5]94    COMPILE = "cc -shared -fPIC -fopenmp -std=c99 -O2 -Wall %(source)s -o %(output)s -lm"
[df4dc86]95
96DLL_PATH = tempfile.gettempdir()
[5d4777d]97
[5d316e9]98ALLOW_SINGLE_PRECISION_DLLS = True
[5d4777d]99
[750ffa5]100
[151f3bc]101def dll_name(model_info, dtype):
[f619de7]102    # type: (ModelInfo, np.dtype) ->  str
[5d4777d]103    """
[151f3bc]104    Name of the dll containing the model.  This is the base file name without
105    any path or extension, with a form such as 'sas_sphere32'.
[5d4777d]106    """
[151f3bc]107    bits = 8*dtype.itemsize
[6d6508e]108    return "sas_%s%d"%(model_info.id, bits)
[5d4777d]109
[f619de7]110
[151f3bc]111def dll_path(model_info, dtype):
[f619de7]112    # type: (ModelInfo, np.dtype) -> str
[151f3bc]113    """
114    Complete path to the dll for the model.  Note that the dll may not
115    exist yet if it hasn't been compiled.
116    """
117    return os.path.join(DLL_PATH, dll_name(model_info, dtype)+".so")
[5d4777d]118
[f619de7]119
120def make_dll(source, model_info, dtype=F64):
121    # type: (str, ModelInfo, np.dtype) -> str
[5d4777d]122    """
[f619de7]123    Returns the path to the compiled model defined by *kernel_module*.
[5d4777d]124
[f619de7]125    If the model has not been compiled, or if the source file(s) are newer
126    than the dll, then *make_dll* will compile the model before returning.
127    This routine does not load the resulting dll.
[5d4777d]128
[aa4946b]129    *dtype* is a numpy floating point precision specifier indicating whether
[f619de7]130    the model should be single, double or long double precision.  The default
131    is double precision, *np.dtype('d')*.
[5d4777d]132
[f619de7]133    Set *sasmodels.ALLOW_SINGLE_PRECISION_DLLS* to False if single precision
134    models are not allowed as DLLs.
[aa4946b]135
136    Set *sasmodels.kerneldll.DLL_PATH* to the compiled dll output path.
137    The default is the system temporary directory.
[5d4777d]138    """
[f619de7]139    if dtype == F16:
[5d316e9]140        raise ValueError("16 bit floats not supported")
[f619de7]141    if dtype == F32 and not ALLOW_SINGLE_PRECISION_DLLS:
142        dtype = F64  # Force 64-bit dll
143    # Note: dtype may be F128 for long double precision
[750ffa5]144
[69aa451]145    newest = generate.timestamp(model_info)
[17bbadd]146    dll = dll_path(model_info, dtype)
[823e620]147    if not os.path.exists(dll) or os.path.getmtime(dll) < newest:
[151f3bc]148        basename = dll_name(model_info, dtype) + "_"
149        fid, filename = tempfile.mkstemp(suffix=".c", prefix=basename)
[f619de7]150        source = generate.convert_type(source, dtype)
[823e620]151        os.fdopen(fid, "w").write(source)
[aa4946b]152        command = COMPILE%{"source":filename, "output":dll}
[9404dd3]153        print("Compile command: "+command)
[df4dc86]154        status = os.system(command)
[aa4946b]155        if status != 0 or not os.path.exists(dll):
[f734e7d]156            raise RuntimeError("compile failed.  File is in %r"%filename)
[5d4777d]157        else:
[74667d3]158            ## comment the following to keep the generated c file
159            os.unlink(filename)
160            #print("saving compiled file in %r"%filename)
[aa4946b]161    return dll
162
163
[f619de7]164def load_dll(source, model_info, dtype=F64):
165    # type: (str, ModelInfo, np.dtype) -> "DllModel"
[aa4946b]166    """
[823e620]167    Create and load a dll corresponding to the source, info pair returned
[aa4946b]168    from :func:`sasmodels.generate.make` compiled for the target precision.
169
170    See :func:`make_dll` for details on controlling the dll path and the
171    allowed floating point precision.
172    """
[17bbadd]173    filename = make_dll(source, model_info, dtype=dtype)
174    return DllModel(filename, model_info, dtype=dtype)
[5d4777d]175
[f619de7]176
177class DllModel(KernelModel):
[14de349]178    """
179    ctypes wrapper for a single model.
180
[17bbadd]181    *source* and *model_info* are the model source and interface as returned
[14de349]182    from :func:`gen.make`.
183
184    *dtype* is the desired model precision.  Any numpy dtype for single
185    or double precision floats will do, such as 'f', 'float32' or 'single'
186    for single and 'd', 'float64' or 'double' for double.  Double precision
187    is an optional extension which may not be available on all devices.
[ff7119b]188
189    Call :meth:`release` when done with the kernel.
[14de349]190    """
[6ad0e87]191   
[17bbadd]192    def __init__(self, dllpath, model_info, dtype=generate.F32):
[f619de7]193        # type: (str, ModelInfo, np.dtype) -> None
[17bbadd]194        self.info = model_info
[ce27e21]195        self.dllpath = dllpath
[f619de7]196        self._dll = None  # type: ct.CDLL
[750ffa5]197        self.dtype = np.dtype(dtype)
[14de349]198
[ce27e21]199    def _load_dll(self):
[f619de7]200        # type: () -> None
[823e620]201        #print("dll", self.dllpath)
[2c801fe]202        try:
[f619de7]203            self._dll = ct.CDLL(self.dllpath)
[4d76711]204        except:
205            annotate_exception("while loading "+self.dllpath)
[2c801fe]206            raise
[14de349]207
[5edfe12]208        fp = (c_float if self.dtype == generate.F32
209              else c_double if self.dtype == generate.F64
210              else c_longdouble)
[ce27e21]211
[303d8d6]212        # int, int, int, int*, double*, double*, double*, double*, double*, double
[7ff3cf3]213        argtypes = [c_int32]*3 + [c_void_p]*5 + [fp]
[f619de7]214        self._Iq = self._dll[generate.kernel_name(self.info, is_2d=False)]
215        self._Iqxy = self._dll[generate.kernel_name(self.info, is_2d=True)]
216        self._Iq.argtypes = argtypes
217        self._Iqxy.argtypes = argtypes
[ce27e21]218
219    def __getstate__(self):
[f619de7]220        # type: () -> Tuple[ModelInfo, str]
[eafc9fa]221        return self.info, self.dllpath
[ce27e21]222
223    def __setstate__(self, state):
[f619de7]224        # type: (Tuple[ModelInfo, str]) -> None
[eafc9fa]225        self.info, self.dllpath = state
[f619de7]226        self._dll = None
[ce27e21]227
[48fbd50]228    def make_kernel(self, q_vectors):
[f619de7]229        # type: (List[np.ndarray]) -> DllKernel
[eafc9fa]230        q_input = PyInput(q_vectors, self.dtype)
[f619de7]231        # Note: pickle not supported for DllKernel
232        if self._dll is None:
233            self._load_dll()
234        kernel = self._Iqxy if q_input.is_2d else self._Iq
[3c56da87]235        return DllKernel(kernel, self.info, q_input)
[4d76711]236
[eafc9fa]237    def release(self):
[f619de7]238        # type: () -> None
[14de349]239        """
[eafc9fa]240        Release any resources associated with the model.
[14de349]241        """
[6ad0e87]242        if os.name == 'nt':
243            #dll = ct.cdll.LoadLibrary(self.dllpath)
244            dll = ct.CDLL(self.dllpath)
245            libHandle = dll._handle
246            #libHandle = ct.c_void_p(dll._handle)
[f619de7]247            del dll, self._dll
248            self._dll = None
[6ad0e87]249            #_ctypes.FreeLibrary(libHandle)
250            ct.windll.kernel32.FreeLibrary(libHandle)
251        else:   
252            pass 
[ff7119b]253
[14de349]254
[f619de7]255class DllKernel(Kernel):
[ff7119b]256    """
257    Callable SAS kernel.
258
[b3f6bc3]259    *kernel* is the c function to call.
[ff7119b]260
[17bbadd]261    *model_info* is the module information
[ff7119b]262
[3c56da87]263    *q_input* is the DllInput q vectors at which the kernel should be
[ff7119b]264    evaluated.
265
266    The resulting call method takes the *pars*, a list of values for
[823e620]267    the fixed parameters to the kernel, and *pd_pars*, a list of (value, weight)
[ff7119b]268    vectors for the polydisperse parameters.  *cutoff* determines the
269    integration limits: any points with combined weight less than *cutoff*
270    will not be calculated.
271
272    Call :meth:`release` when done with the kernel instance.
273    """
[17bbadd]274    def __init__(self, kernel, model_info, q_input):
[f619de7]275        # type: (Callable[[], np.ndarray], ModelInfo, PyInput) -> None
[48fbd50]276        self.kernel = kernel
[17bbadd]277        self.info = model_info
[3c56da87]278        self.q_input = q_input
[39cc3be]279        self.dtype = q_input.dtype
[445d1c0]280        self.dim = '2d' if q_input.is_2d else '1d'
[48fbd50]281        self.result = np.empty(q_input.nq+3, q_input.dtype)
[14de349]282
[6d6508e]283    def __call__(self, call_details, weights, values, cutoff):
[f619de7]284        # type: (CallDetails, np.ndarray, np.ndarray, float) -> np.ndarray
[5edfe12]285        real = (np.float32 if self.q_input.dtype == generate.F32
286                else np.float64 if self.q_input.dtype == generate.F64
287                else np.float128)
[6d6508e]288        assert isinstance(call_details, details.CallDetails)
[48fbd50]289        assert weights.dtype == real and values.dtype == real
290
[6d6508e]291        start, stop = 0, call_details.total_pd
[5ff1b03]292        #print("in kerneldll")
293        #print("weights", weights)
294        #print("values", values)
[303d8d6]295        args = [
296            self.q_input.nq, # nq
[48fbd50]297            start, # pd_start
298            stop, # pd_stop pd_stride[MAX_PD]
[6d6508e]299            call_details.ctypes.data, # problem
[303d8d6]300            weights.ctypes.data,  # weights
301            values.ctypes.data,  #pars
[48fbd50]302            self.q_input.q.ctypes.data, #q
303            self.result.ctypes.data,   # results
[303d8d6]304            real(cutoff), # cutoff
305            ]
[f619de7]306        self.kernel(*args) # type: ignore
[48fbd50]307        return self.result[:-3]
[14de349]308
309    def release(self):
[f619de7]310        # type: () -> None
[eafc9fa]311        """
312        Release any resources associated with the kernel.
313        """
[f619de7]314        self.q_input.release()
Note: See TracBrowser for help on using the repository browser.