source: sasmodels/sasmodels/kerneldll.py @ e69b36f

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

tag sasmodels generated .so files with sas_ prefix

  • Property mode set to 100644
File size: 10.6 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_int32, c_longdouble, c_double, c_float
53
54import numpy as np
55
56from . import generate
57from .kernelpy import PyInput, PyModel
58from .exception import annotate_exception
59
60# Compiler platform details
61if sys.platform == 'darwin':
62    #COMPILE = "gcc-mp-4.7 -shared -fPIC -std=c99 -fopenmp -O2 -Wall %s -o %s -lm -lgomp"
63    COMPILE = "gcc -shared -fPIC -std=c99 -O2 -Wall %(source)s -o %(output)s -lm"
64elif os.name == 'nt':
65    # call vcvarsall.bat before compiling to set path, headers, libs, etc.
66    if "VCINSTALLDIR" in os.environ:
67        # MSVC compiler is available, so use it.  OpenMP requires a copy of
68        # vcomp90.dll on the path.  One may be found here:
69        #       C:/Windows/winsxs/x86_microsoft.vc90.openmp*/vcomp90.dll
70        # Copy this to the python directory and uncomment the OpenMP COMPILE
71        # TODO: remove intermediate OBJ file created in the directory
72        # TODO: maybe don't use randomized name for the c file
73        CC = "cl /nologo /Ox /MD /W3 /GS- /DNDEBUG /Tp%(source)s "
74        LN = "/link /DLL /INCREMENTAL:NO /MANIFEST /OUT:%(output)s"
75        if "SAS_OPENMP" in os.environ:
76            COMPILE = " ".join((CC, "/openmp", LN))
77        else:
78            COMPILE = " ".join((CC, LN))
79    else:
80        COMPILE = "gcc -shared -fPIC -std=c99 -O2 -Wall %(source)s -o %(output)s -lm"
81        if "SAS_OPENMP" in os.environ:
82            COMPILE += " -fopenmp"
83else:
84    COMPILE = "cc -shared -fPIC -fopenmp -std=c99 -O2 -Wall %(source)s -o %(output)s -lm"
85
86DLL_PATH = tempfile.gettempdir()
87
88ALLOW_SINGLE_PRECISION_DLLS = True
89
90
91def dll_path(model_info, dtype="double"):
92    """
93    Path to the compiled model defined by *model_info*.
94    """
95    from os.path import join as joinpath, split as splitpath, splitext
96    basename = splitext(splitpath(model_info['filename'])[1])[0]
97    bits = 8*np.dtype(dtype).itemsize
98    return joinpath(DLL_PATH, "sas_%s%d.so"%(basename, bits))
99
100
101def make_dll(source, model_info, dtype="double"):
102    """
103    Load the compiled model defined by *kernel_module*.
104
105    Recompile if any files are newer than the model file.
106
107    *dtype* is a numpy floating point precision specifier indicating whether
108    the model should be single or double precision.  The default is double
109    precision.
110
111    The DLL is not loaded until the kernel is called so models can
112    be defined without using too many resources.
113
114    Set *sasmodels.kerneldll.DLL_PATH* to the compiled dll output path.
115    The default is the system temporary directory.
116
117    Set *sasmodels.ALLOW_SINGLE_PRECISION_DLLS* to True if single precision
118    models are allowed as DLLs.
119    """
120    if callable(model_info.get('Iq', None)):
121        return PyModel(model_info)
122   
123    dtype = np.dtype(dtype)
124    if dtype == generate.F16:
125        raise ValueError("16 bit floats not supported")
126    if dtype == generate.F32 and not ALLOW_SINGLE_PRECISION_DLLS:
127        dtype = generate.F64  # Force 64-bit dll
128
129    if dtype == generate.F32: # 32-bit dll
130        tempfile_prefix = 'sas_' + model_info['name'] + '32_'
131    elif dtype == generate.F64:
132        tempfile_prefix = 'sas_' + model_info['name'] + '64_'
133    else:
134        tempfile_prefix = 'sas_' + model_info['name'] + '128_'
135 
136    source = generate.convert_type(source, dtype)
137    newest = generate.timestamp(model_info)
138    dll = dll_path(model_info, dtype)
139    if not os.path.exists(dll) or os.path.getmtime(dll) < newest:
140        # Replace with a proper temp file
141        fid, filename = tempfile.mkstemp(suffix=".c", prefix=tempfile_prefix)
142        os.fdopen(fid, "w").write(source)
143        command = COMPILE%{"source":filename, "output":dll}
144        print("Compile command: "+command)
145        status = os.system(command)
146        if status != 0 or not os.path.exists(dll):
147            raise RuntimeError("compile failed.  File is in %r"%filename)
148        else:
149            ## comment the following to keep the generated c file
150            os.unlink(filename)
151            #print("saving compiled file in %r"%filename)
152    return dll
153
154
155def load_dll(source, model_info, dtype="double"):
156    """
157    Create and load a dll corresponding to the source, info pair returned
158    from :func:`sasmodels.generate.make` compiled for the target precision.
159
160    See :func:`make_dll` for details on controlling the dll path and the
161    allowed floating point precision.
162    """
163    filename = make_dll(source, model_info, dtype=dtype)
164    return DllModel(filename, model_info, dtype=dtype)
165
166class DllModel(object):
167    """
168    ctypes wrapper for a single model.
169
170    *source* and *model_info* are the model source and interface as returned
171    from :func:`gen.make`.
172
173    *dtype* is the desired model precision.  Any numpy dtype for single
174    or double precision floats will do, such as 'f', 'float32' or 'single'
175    for single and 'd', 'float64' or 'double' for double.  Double precision
176    is an optional extension which may not be available on all devices.
177
178    Call :meth:`release` when done with the kernel.
179    """
180   
181    def __init__(self, dllpath, model_info, dtype=generate.F32):
182        self.info = model_info
183        self.dllpath = dllpath
184        self.dll = None
185        self.dtype = np.dtype(dtype)
186
187    def _load_dll(self):
188        #print("dll", self.dllpath)
189        try:
190            self.dll = ct.CDLL(self.dllpath)
191        except Exception as exc:
192            annotate_exception(exc, "while loading "+self.dllpath)
193            raise
194
195        fp = (c_float if self.dtype == generate.F32
196              else c_double if self.dtype == generate.F64
197              else c_longdouble)
198
199        # int, int, int, int*, double*, double*, double*, double*, double*, double
200        argtypes = [c_int32]*3 + [c_void_p]*5 + [fp]
201        self.Iq = self.dll[generate.kernel_name(self.info, False)]
202        self.Iqxy = self.dll[generate.kernel_name(self.info, True)]
203        self.Iq.argtypes = argtypes
204        self.Iqxy.argtypes = argtypes
205
206    def __getstate__(self):
207        return self.info, self.dllpath
208
209    def __setstate__(self, state):
210        self.info, self.dllpath = state
211        self.dll = None
212
213    def make_kernel(self, q_vectors):
214        q_input = PyInput(q_vectors, self.dtype)
215        if self.dll is None: self._load_dll()
216        kernel = self.Iqxy if q_input.is_2d else self.Iq
217        return DllKernel(kernel, self.info, q_input)
218   
219    def release(self):
220        """
221        Release any resources associated with the model.
222        """
223        if os.name == 'nt':
224            #dll = ct.cdll.LoadLibrary(self.dllpath)
225            dll = ct.CDLL(self.dllpath)
226            libHandle = dll._handle
227            #libHandle = ct.c_void_p(dll._handle)
228            del dll, self.dll
229            self.dll = None
230            #_ctypes.FreeLibrary(libHandle)
231            ct.windll.kernel32.FreeLibrary(libHandle)
232        else:   
233            pass 
234
235
236class DllKernel(object):
237    """
238    Callable SAS kernel.
239
240    *kernel* is the c function to call.
241
242    *model_info* is the module information
243
244    *q_input* is the DllInput q vectors at which the kernel should be
245    evaluated.
246
247    The resulting call method takes the *pars*, a list of values for
248    the fixed parameters to the kernel, and *pd_pars*, a list of (value, weight)
249    vectors for the polydisperse parameters.  *cutoff* determines the
250    integration limits: any points with combined weight less than *cutoff*
251    will not be calculated.
252
253    Call :meth:`release` when done with the kernel instance.
254    """
255    def __init__(self, kernel, model_info, q_input):
256        self.kernel = kernel
257        self.info = model_info
258        self.q_input = q_input
259        self.dtype = q_input.dtype
260        self.dim = '2d' if q_input.is_2d else '1d'
261        self.result = np.empty(q_input.nq+3, q_input.dtype)
262
263    def __call__(self, details, weights, values, cutoff):
264        real = (np.float32 if self.q_input.dtype == generate.F32
265                else np.float64 if self.q_input.dtype == generate.F64
266                else np.float128)
267        assert details.dtype == np.int32
268        assert weights.dtype == real and values.dtype == real
269
270        max_pd = self.info['max_pd']
271        start, stop = 0, details[4*max_pd-1]
272        print(details)
273        args = [
274            self.q_input.nq, # nq
275            start, # pd_start
276            stop, # pd_stop pd_stride[MAX_PD]
277            details.ctypes.data, # problem
278            weights.ctypes.data,  # weights
279            values.ctypes.data,  #pars
280            self.q_input.q.ctypes.data, #q
281            self.result.ctypes.data,   # results
282            real(cutoff), # cutoff
283            ]
284        self.kernel(*args)
285        return self.result[:-3]
286
287    def release(self):
288        """
289        Release any resources associated with the kernel.
290        """
291        pass
Note: See TracBrowser for help on using the repository browser.