source: sasmodels/sasmodels/kerneldll.py @ f22e50e

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

hack to look in sasmodels-data/../compiled_models for the precompiled dlls (fixed?)

  • Property mode set to 100644
File size: 12.3 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
[5a91c6b]50from os.path import join as joinpath, split as splitpath, splitext
[df4dc86]51import tempfile
[14de349]52import ctypes as ct
[5edfe12]53from ctypes import c_void_p, c_int, c_longdouble, c_double, c_float
[14de349]54
55import numpy as np
56
[cb6ecf4]57from . import generate
[f734e7d]58from .kernelpy import PyInput, PyModel
[2c801fe]59from .exception import annotate_exception
[14de349]60
[5d4777d]61# Compiler platform details
62if sys.platform == 'darwin':
[216a9e1]63    #COMPILE = "gcc-mp-4.7 -shared -fPIC -std=c99 -fopenmp -O2 -Wall %s -o %s -lm -lgomp"
[df4dc86]64    COMPILE = "gcc -shared -fPIC -std=c99 -O2 -Wall %(source)s -o %(output)s -lm"
[5d4777d]65elif os.name == 'nt':
[3c56da87]66    # call vcvarsall.bat before compiling to set path, headers, libs, etc.
[68d3c1b]67    if "VCINSTALLDIR" in os.environ:
[a30cdd5]68        # MSVC compiler is available, so use it.  OpenMP requires a copy of
69        # vcomp90.dll on the path.  One may be found here:
70        #       C:/Windows/winsxs/x86_microsoft.vc90.openmp*/vcomp90.dll
71        # Copy this to the python directory and uncomment the OpenMP COMPILE
[f734e7d]72        # TODO: remove intermediate OBJ file created in the directory
73        # TODO: maybe don't use randomized name for the c file
[a30cdd5]74        CC = "cl /nologo /Ox /MD /W3 /GS- /DNDEBUG /Tp%(source)s "
75        LN = "/link /DLL /INCREMENTAL:NO /MANIFEST /OUT:%(output)s"
76        if "SAS_OPENMP" in os.environ:
77            COMPILE = " ".join((CC, "/openmp", LN))
78        else:
79            COMPILE = " ".join((CC, LN))
[5a91c6b]80    elif True:
81        # If MSVC compiler is not available, try using mingw
[fb69211]82        # fPIC is not needed on windows
[e1454ab]83        COMPILE = "gcc -shared -std=c99 -O2 -Wall %(source)s -o %(output)s -lm"
[a30cdd5]84        if "SAS_OPENMP" in os.environ:
85            COMPILE = COMPILE + " -fopenmp"
[fb69211]86    else:
[5a91c6b]87        # If MSVC compiler is not available, try using tinycc
[fb69211]88        from tinycc import TCC
89        COMPILE = TCC + " -shared -rdynamic -Wall %(source)s -o %(output)s"
[5d4777d]90else:
[a30cdd5]91    COMPILE = "cc -shared -fPIC -fopenmp -std=c99 -O2 -Wall %(source)s -o %(output)s -lm"
[df4dc86]92
[ff97458]93# Windows-specific solution
94if os.name == 'nt':
[3ecf034]95    # Assume the default location of module DLLs is in .sasmodels/compiled_models.
96    DLL_PATH = os.path.join(os.path.expanduser("~"), ".sasmodels", "compiled_models")
97    if not os.path.exists(DLL_PATH):
98        os.makedirs(DLL_PATH)
[ff97458]99else:
100    # Set up the default path for compiled modules.
101    DLL_PATH = tempfile.gettempdir()
[5d4777d]102
[5d316e9]103ALLOW_SINGLE_PRECISION_DLLS = True
[5d4777d]104
[750ffa5]105
[17bbadd]106def dll_path(model_info, dtype="double"):
[5d4777d]107    """
[17bbadd]108    Path to the compiled model defined by *model_info*.
[5d4777d]109    """
[17bbadd]110    basename = splitext(splitpath(model_info['filename'])[1])[0]
[750ffa5]111    if np.dtype(dtype) == generate.F32:
112        basename += "32"
[5edfe12]113    elif np.dtype(dtype) == generate.F64:
114        basename += "64"
115    else:
116        basename += "128"
[01b8659]117
118    # Hack to find precompiled dlls
119    path = joinpath(generate.DATA_PATH, '..', 'compiled_models', basename+'.so')
120    if os.path.exists(path):
121        return path
122
[5d4777d]123    return joinpath(DLL_PATH, basename+'.so')
124
[17bbadd]125def make_dll(source, model_info, dtype="double"):
[5d4777d]126    """
127    Load the compiled model defined by *kernel_module*.
128
129    Recompile if any files are newer than the model file.
130
[aa4946b]131    *dtype* is a numpy floating point precision specifier indicating whether
132    the model should be single or double precision.  The default is double
133    precision.
[5d4777d]134
[aa4946b]135    The DLL is not loaded until the kernel is called so models can
[5d4777d]136    be defined without using too many resources.
[aa4946b]137
138    Set *sasmodels.kerneldll.DLL_PATH* to the compiled dll output path.
139    The default is the system temporary directory.
140
141    Set *sasmodels.ALLOW_SINGLE_PRECISION_DLLS* to True if single precision
142    models are allowed as DLLs.
[5d4777d]143    """
[17bbadd]144    if callable(model_info.get('Iq', None)):
145        return PyModel(model_info)
[6ad0e87]146   
[750ffa5]147    dtype = np.dtype(dtype)
[5d316e9]148    if dtype == generate.F16:
149        raise ValueError("16 bit floats not supported")
[5edfe12]150    if dtype == generate.F32 and not ALLOW_SINGLE_PRECISION_DLLS:
151        dtype = generate.F64  # Force 64-bit dll
[750ffa5]152
153    if dtype == generate.F32: # 32-bit dll
[17bbadd]154        tempfile_prefix = 'sas_' + model_info['name'] + '32_'
[5edfe12]155    elif dtype == generate.F64:
[17bbadd]156        tempfile_prefix = 'sas_' + model_info['name'] + '64_'
[750ffa5]157    else:
[17bbadd]158        tempfile_prefix = 'sas_' + model_info['name'] + '128_'
[01b8659]159
[17bbadd]160    dll = dll_path(model_info, dtype)
[e1454ab]161
162    if not os.path.exists(dll):
[5a91c6b]163        need_recompile = True
[31bc9bf]164    elif getattr(sys, 'frozen', None) is not None:
[5a91c6b]165        # TODO: don't suppress time stamp
166        # Currently suppressing recompile when running in a frozen environment
167        need_recompile = False
168    else:
169        dll_time = os.path.getmtime(dll)
170        source_files = generate.model_sources(model_info) + [model_info['filename']]
171        newest_source = max(os.path.getmtime(f) for f in source_files)
172        need_recompile = dll_time < newest_source
173    if need_recompile:
[823e620]174        fid, filename = tempfile.mkstemp(suffix=".c", prefix=tempfile_prefix)
[5a91c6b]175        source = generate.convert_type(source, dtype)
[823e620]176        os.fdopen(fid, "w").write(source)
[aa4946b]177        command = COMPILE%{"source":filename, "output":dll}
[df4dc86]178        status = os.system(command)
[aa4946b]179        if status != 0 or not os.path.exists(dll):
[f734e7d]180            raise RuntimeError("compile failed.  File is in %r"%filename)
[5d4777d]181        else:
[74667d3]182            ## comment the following to keep the generated c file
183            os.unlink(filename)
184            #print("saving compiled file in %r"%filename)
[aa4946b]185    return dll
186
187
[17bbadd]188def load_dll(source, model_info, dtype="double"):
[aa4946b]189    """
[823e620]190    Create and load a dll corresponding to the source, info pair returned
[aa4946b]191    from :func:`sasmodels.generate.make` compiled for the target precision.
192
193    See :func:`make_dll` for details on controlling the dll path and the
194    allowed floating point precision.
195    """
[17bbadd]196    filename = make_dll(source, model_info, dtype=dtype)
197    return DllModel(filename, model_info, dtype=dtype)
[5d4777d]198
[14de349]199
[f734e7d]200IQ_ARGS = [c_void_p, c_void_p, c_int]
201IQXY_ARGS = [c_void_p, c_void_p, c_void_p, c_int]
[14de349]202
203class DllModel(object):
204    """
205    ctypes wrapper for a single model.
206
[17bbadd]207    *source* and *model_info* are the model source and interface as returned
[14de349]208    from :func:`gen.make`.
209
210    *dtype* is the desired model precision.  Any numpy dtype for single
211    or double precision floats will do, such as 'f', 'float32' or 'single'
212    for single and 'd', 'float64' or 'double' for double.  Double precision
213    is an optional extension which may not be available on all devices.
[ff7119b]214
215    Call :meth:`release` when done with the kernel.
[14de349]216    """
[6ad0e87]217   
[17bbadd]218    def __init__(self, dllpath, model_info, dtype=generate.F32):
219        self.info = model_info
[ce27e21]220        self.dllpath = dllpath
221        self.dll = None
[750ffa5]222        self.dtype = np.dtype(dtype)
[14de349]223
[ce27e21]224    def _load_dll(self):
225        Nfixed1d = len(self.info['partype']['fixed-1d'])
226        Nfixed2d = len(self.info['partype']['fixed-2d'])
227        Npd1d = len(self.info['partype']['pd-1d'])
228        Npd2d = len(self.info['partype']['pd-2d'])
[14de349]229
[823e620]230        #print("dll", self.dllpath)
[2c801fe]231        try:
232            self.dll = ct.CDLL(self.dllpath)
[4d76711]233        except:
234            annotate_exception("while loading "+self.dllpath)
[2c801fe]235            raise
[14de349]236
[5edfe12]237        fp = (c_float if self.dtype == generate.F32
238              else c_double if self.dtype == generate.F64
239              else c_longdouble)
[750ffa5]240        pd_args_1d = [c_void_p, fp] + [c_int]*Npd1d if Npd1d else []
[823e620]241        pd_args_2d = [c_void_p, fp] + [c_int]*Npd2d if Npd2d else []
[cb6ecf4]242        self.Iq = self.dll[generate.kernel_name(self.info, False)]
[750ffa5]243        self.Iq.argtypes = IQ_ARGS + pd_args_1d + [fp]*Nfixed1d
[ce27e21]244
[cb6ecf4]245        self.Iqxy = self.dll[generate.kernel_name(self.info, True)]
[750ffa5]246        self.Iqxy.argtypes = IQXY_ARGS + pd_args_2d + [fp]*Nfixed2d
[6ad0e87]247       
248        self.release()
[ce27e21]249
250    def __getstate__(self):
[eafc9fa]251        return self.info, self.dllpath
[ce27e21]252
253    def __setstate__(self, state):
[eafc9fa]254        self.info, self.dllpath = state
255        self.dll = None
[ce27e21]256
[4d76711]257    def make_kernel(self, q_vectors):
[eafc9fa]258        q_input = PyInput(q_vectors, self.dtype)
[b3f6bc3]259        if self.dll is None: self._load_dll()
[eafc9fa]260        kernel = self.Iqxy if q_input.is_2d else self.Iq
[3c56da87]261        return DllKernel(kernel, self.info, q_input)
[4d76711]262
[eafc9fa]263    def release(self):
[14de349]264        """
[eafc9fa]265        Release any resources associated with the model.
[14de349]266        """
[6ad0e87]267        if os.name == 'nt':
268            #dll = ct.cdll.LoadLibrary(self.dllpath)
269            dll = ct.CDLL(self.dllpath)
270            libHandle = dll._handle
271            #libHandle = ct.c_void_p(dll._handle)
272            del dll, self.dll
273            self.dll = None
274            ct.windll.kernel32.FreeLibrary(libHandle)
275        else:   
276            pass 
[ff7119b]277
[14de349]278
279class DllKernel(object):
[ff7119b]280    """
281    Callable SAS kernel.
282
[b3f6bc3]283    *kernel* is the c function to call.
[ff7119b]284
[17bbadd]285    *model_info* is the module information
[ff7119b]286
[3c56da87]287    *q_input* is the DllInput q vectors at which the kernel should be
[ff7119b]288    evaluated.
289
290    The resulting call method takes the *pars*, a list of values for
[823e620]291    the fixed parameters to the kernel, and *pd_pars*, a list of (value, weight)
[ff7119b]292    vectors for the polydisperse parameters.  *cutoff* determines the
293    integration limits: any points with combined weight less than *cutoff*
294    will not be calculated.
295
296    Call :meth:`release` when done with the kernel instance.
297    """
[17bbadd]298    def __init__(self, kernel, model_info, q_input):
299        self.info = model_info
[3c56da87]300        self.q_input = q_input
[14de349]301        self.kernel = kernel
[3c56da87]302        self.res = np.empty(q_input.nq, q_input.dtype)
[eafc9fa]303        dim = '2d' if q_input.is_2d else '1d'
[17bbadd]304        self.fixed_pars = model_info['partype']['fixed-' + dim]
305        self.pd_pars = model_info['partype']['pd-' + dim]
[14de349]306
[ce27e21]307        # In dll kernel, but not in opencl kernel
308        self.p_res = self.res.ctypes.data
[14de349]309
[f734e7d]310    def __call__(self, fixed_pars, pd_pars, cutoff):
[5edfe12]311        real = (np.float32 if self.q_input.dtype == generate.F32
312                else np.float64 if self.q_input.dtype == generate.F64
313                else np.float128)
[14de349]314
[3c56da87]315        nq = c_int(self.q_input.nq)
[f734e7d]316        if pd_pars:
317            cutoff = real(cutoff)
318            loops_N = [np.uint32(len(p[0])) for p in pd_pars]
319            loops = np.hstack(pd_pars)
[3c56da87]320            loops = np.ascontiguousarray(loops.T, self.q_input.dtype).flatten()
[f734e7d]321            p_loops = loops.ctypes.data
322            dispersed = [p_loops, cutoff] + loops_N
323        else:
324            dispersed = []
325        fixed = [real(p) for p in fixed_pars]
[3c56da87]326        args = self.q_input.q_pointers + [self.p_res, nq] + dispersed + fixed
[9404dd3]327        #print(pars)
[ce27e21]328        self.kernel(*args)
[14de349]329
330        return self.res
331
332    def release(self):
[eafc9fa]333        """
334        Release any resources associated with the kernel.
335        """
[14de349]336        pass
Note: See TracBrowser for help on using the repository browser.