source: sasmodels/sasmodels/kerneldll.py @ 6ad0e87

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

Trying to free the DLL model library in Windows (still not working)

  • Property mode set to 100644
File size: 11.3 KB
Line 
1r"""
2DLL driver for C kernels
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*.
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
23    `<http://www.microsoft.com/en-us/download/details.aspx?id=44266>`_
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.
45"""
46from __future__ import print_function
47
48import sys
49import os
50import tempfile
51import ctypes as ct
52from ctypes import c_void_p, c_int, c_longdouble, c_double, c_float
53import _ctypes
54
55import numpy as np
56
57from . import generate
58from .kernelpy import PyInput, PyModel
59from .exception import annotate_exception
60
61# Compiler platform details
62if sys.platform == 'darwin':
63    #COMPILE = "gcc-mp-4.7 -shared -fPIC -std=c99 -fopenmp -O2 -Wall %s -o %s -lm -lgomp"
64    COMPILE = "gcc -shared -fPIC -std=c99 -O2 -Wall %(source)s -o %(output)s -lm"
65elif os.name == 'nt':
66    # call vcvarsall.bat before compiling to set path, headers, libs, etc.
67    if "VCINSTALLDIR" in os.environ:
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
72        # TODO: remove intermediate OBJ file created in the directory
73        # TODO: maybe don't use randomized name for the c file
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))
80    else:
81        COMPILE = "gcc -shared -fPIC -std=c99 -O2 -Wall %(source)s -o %(output)s -lm"
82        if "SAS_OPENMP" in os.environ:
83            COMPILE = COMPILE + " -fopenmp"
84else:
85    COMPILE = "cc -shared -fPIC -fopenmp -std=c99 -O2 -Wall %(source)s -o %(output)s -lm"
86
87DLL_PATH = tempfile.gettempdir()
88
89ALLOW_SINGLE_PRECISION_DLLS = True
90
91
92def dll_path(model_info, dtype="double"):
93    """
94    Path to the compiled model defined by *model_info*.
95    """
96    from os.path import join as joinpath, split as splitpath, splitext
97    basename = splitext(splitpath(model_info['filename'])[1])[0]
98    if np.dtype(dtype) == generate.F32:
99        basename += "32"
100    elif np.dtype(dtype) == generate.F64:
101        basename += "64"
102    else:
103        basename += "128"
104    return joinpath(DLL_PATH, basename+'.so')
105
106
107def make_dll(source, model_info, dtype="double"):
108    """
109    Load the compiled model defined by *kernel_module*.
110
111    Recompile if any files are newer than the model file.
112
113    *dtype* is a numpy floating point precision specifier indicating whether
114    the model should be single or double precision.  The default is double
115    precision.
116
117    The DLL is not loaded until the kernel is called so models can
118    be defined without using too many resources.
119
120    Set *sasmodels.kerneldll.DLL_PATH* to the compiled dll output path.
121    The default is the system temporary directory.
122
123    Set *sasmodels.ALLOW_SINGLE_PRECISION_DLLS* to True if single precision
124    models are allowed as DLLs.
125    """
126    if callable(model_info.get('Iq', None)):
127        return PyModel(model_info)
128   
129    dtype = np.dtype(dtype)
130    if dtype == generate.F16:
131        raise ValueError("16 bit floats not supported")
132    if dtype == generate.F32 and not ALLOW_SINGLE_PRECISION_DLLS:
133        dtype = generate.F64  # Force 64-bit dll
134
135    if dtype == generate.F32: # 32-bit dll
136        tempfile_prefix = 'sas_' + model_info['name'] + '32_'
137    elif dtype == generate.F64:
138        tempfile_prefix = 'sas_' + model_info['name'] + '64_'
139    else:
140        tempfile_prefix = 'sas_' + model_info['name'] + '128_'
141 
142    source = generate.convert_type(source, dtype)
143    source_files = generate.model_sources(model_info) + [model_info['filename']]
144    dll = dll_path(model_info, dtype)
145    newest = max(os.path.getmtime(f) for f in source_files)
146    if not os.path.exists(dll) or os.path.getmtime(dll) < newest:
147        # Replace with a proper temp file
148        fid, filename = tempfile.mkstemp(suffix=".c", prefix=tempfile_prefix)
149        os.fdopen(fid, "w").write(source)
150        command = COMPILE%{"source":filename, "output":dll}
151        print("Compile command: "+command)
152        status = os.system(command)
153        if status != 0 or not os.path.exists(dll):
154            raise RuntimeError("compile failed.  File is in %r"%filename)
155        else:
156            ## comment the following to keep the generated c file
157            os.unlink(filename)
158            #print("saving compiled file in %r"%filename)
159    return dll
160
161
162def load_dll(source, model_info, dtype="double"):
163    """
164    Create and load a dll corresponding to the source, info pair returned
165    from :func:`sasmodels.generate.make` compiled for the target precision.
166
167    See :func:`make_dll` for details on controlling the dll path and the
168    allowed floating point precision.
169    """
170    filename = make_dll(source, model_info, dtype=dtype)
171    return DllModel(filename, model_info, dtype=dtype)
172
173
174IQ_ARGS = [c_void_p, c_void_p, c_int]
175IQXY_ARGS = [c_void_p, c_void_p, c_void_p, c_int]
176
177class DllModel(object):
178    """
179    ctypes wrapper for a single model.
180
181    *source* and *model_info* are the model source and interface as returned
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.
188
189    Call :meth:`release` when done with the kernel.
190    """
191   
192    def __init__(self, dllpath, model_info, dtype=generate.F32):
193        self.info = model_info
194        self.dllpath = dllpath
195        self.dll = None
196        self.dtype = np.dtype(dtype)
197
198    def _load_dll(self):
199        Nfixed1d = len(self.info['partype']['fixed-1d'])
200        Nfixed2d = len(self.info['partype']['fixed-2d'])
201        Npd1d = len(self.info['partype']['pd-1d'])
202        Npd2d = len(self.info['partype']['pd-2d'])
203
204        #print("dll", self.dllpath)
205        try:
206            self.dll = ct.CDLL(self.dllpath)
207        except Exception as exc:
208            annotate_exception(exc, "while loading "+self.dllpath)
209            raise
210
211        fp = (c_float if self.dtype == generate.F32
212              else c_double if self.dtype == generate.F64
213              else c_longdouble)
214        pd_args_1d = [c_void_p, fp] + [c_int]*Npd1d if Npd1d else []
215        pd_args_2d = [c_void_p, fp] + [c_int]*Npd2d if Npd2d else []
216        self.Iq = self.dll[generate.kernel_name(self.info, False)]
217        self.Iq.argtypes = IQ_ARGS + pd_args_1d + [fp]*Nfixed1d
218
219        self.Iqxy = self.dll[generate.kernel_name(self.info, True)]
220        self.Iqxy.argtypes = IQXY_ARGS + pd_args_2d + [fp]*Nfixed2d
221       
222        self.release()
223
224    def __getstate__(self):
225        return self.info, self.dllpath
226
227    def __setstate__(self, state):
228        self.info, self.dllpath = state
229        self.dll = None
230
231    def __call__(self, q_vectors):
232        q_input = PyInput(q_vectors, self.dtype)
233        if self.dll is None: self._load_dll()
234        kernel = self.Iqxy if q_input.is_2d else self.Iq
235        return DllKernel(kernel, self.info, q_input)
236   
237    def release(self):
238        """
239        Release any resources associated with the model.
240        """
241        if os.name == 'nt':
242            #dll = ct.cdll.LoadLibrary(self.dllpath)
243            dll = ct.CDLL(self.dllpath)
244            libHandle = dll._handle
245            #libHandle = ct.c_void_p(dll._handle)
246            del dll, self.dll
247            self.dll = None
248            #_ctypes.FreeLibrary(libHandle)
249            ct.windll.kernel32.FreeLibrary(libHandle)
250        else:   
251            pass 
252
253
254class DllKernel(object):
255    """
256    Callable SAS kernel.
257
258    *kernel* is the c function to call.
259
260    *model_info* is the module information
261
262    *q_input* is the DllInput q vectors at which the kernel should be
263    evaluated.
264
265    The resulting call method takes the *pars*, a list of values for
266    the fixed parameters to the kernel, and *pd_pars*, a list of (value, weight)
267    vectors for the polydisperse parameters.  *cutoff* determines the
268    integration limits: any points with combined weight less than *cutoff*
269    will not be calculated.
270
271    Call :meth:`release` when done with the kernel instance.
272    """
273    def __init__(self, kernel, model_info, q_input):
274        self.info = model_info
275        self.q_input = q_input
276        self.kernel = kernel
277        self.res = np.empty(q_input.nq, q_input.dtype)
278        dim = '2d' if q_input.is_2d else '1d'
279        self.fixed_pars = model_info['partype']['fixed-' + dim]
280        self.pd_pars = model_info['partype']['pd-' + dim]
281
282        # In dll kernel, but not in opencl kernel
283        self.p_res = self.res.ctypes.data
284
285    def __call__(self, fixed_pars, pd_pars, cutoff):
286        real = (np.float32 if self.q_input.dtype == generate.F32
287                else np.float64 if self.q_input.dtype == generate.F64
288                else np.float128)
289
290        nq = c_int(self.q_input.nq)
291        if pd_pars:
292            cutoff = real(cutoff)
293            loops_N = [np.uint32(len(p[0])) for p in pd_pars]
294            loops = np.hstack(pd_pars)
295            loops = np.ascontiguousarray(loops.T, self.q_input.dtype).flatten()
296            p_loops = loops.ctypes.data
297            dispersed = [p_loops, cutoff] + loops_N
298        else:
299            dispersed = []
300        fixed = [real(p) for p in fixed_pars]
301        args = self.q_input.q_pointers + [self.p_res, nq] + dispersed + fixed
302        #print(pars)
303        self.kernel(*args)
304
305        return self.res
306
307    def release(self):
308        """
309        Release any resources associated with the kernel.
310        """
311        pass
Note: See TracBrowser for help on using the repository browser.