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

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

refactor model_info from dictionary to class

  • 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 . import details
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 += " -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_name(model_info, dtype):
93    """
94    Name of the dll containing the model.  This is the base file name without
95    any path or extension, with a form such as 'sas_sphere32'.
96    """
97    bits = 8*dtype.itemsize
98    return "sas_%s%d"%(model_info.id, bits)
99
100def dll_path(model_info, dtype):
101    """
102    Complete path to the dll for the model.  Note that the dll may not
103    exist yet if it hasn't been compiled.
104    """
105    return os.path.join(DLL_PATH, dll_name(model_info, dtype)+".so")
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.Iq):
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    source = generate.convert_type(source, dtype)
136    newest = generate.timestamp(model_info)
137    dll = dll_path(model_info, dtype)
138    if not os.path.exists(dll) or os.path.getmtime(dll) < newest:
139        basename = dll_name(model_info, dtype) + "_"
140        fid, filename = tempfile.mkstemp(suffix=".c", prefix=basename)
141        os.fdopen(fid, "w").write(source)
142        command = COMPILE%{"source":filename, "output":dll}
143        print("Compile command: "+command)
144        status = os.system(command)
145        if status != 0 or not os.path.exists(dll):
146            raise RuntimeError("compile failed.  File is in %r"%filename)
147        else:
148            ## comment the following to keep the generated c file
149            os.unlink(filename)
150            #print("saving compiled file in %r"%filename)
151    return dll
152
153
154def load_dll(source, model_info, dtype="double"):
155    """
156    Create and load a dll corresponding to the source, info pair returned
157    from :func:`sasmodels.generate.make` compiled for the target precision.
158
159    See :func:`make_dll` for details on controlling the dll path and the
160    allowed floating point precision.
161    """
162    filename = make_dll(source, model_info, dtype=dtype)
163    return DllModel(filename, model_info, dtype=dtype)
164
165class DllModel(object):
166    """
167    ctypes wrapper for a single model.
168
169    *source* and *model_info* are the model source and interface as returned
170    from :func:`gen.make`.
171
172    *dtype* is the desired model precision.  Any numpy dtype for single
173    or double precision floats will do, such as 'f', 'float32' or 'single'
174    for single and 'd', 'float64' or 'double' for double.  Double precision
175    is an optional extension which may not be available on all devices.
176
177    Call :meth:`release` when done with the kernel.
178    """
179   
180    def __init__(self, dllpath, model_info, dtype=generate.F32):
181        self.info = model_info
182        self.dllpath = dllpath
183        self.dll = None
184        self.dtype = np.dtype(dtype)
185
186    def _load_dll(self):
187        #print("dll", self.dllpath)
188        try:
189            self.dll = ct.CDLL(self.dllpath)
190        except:
191            annotate_exception("while loading "+self.dllpath)
192            raise
193
194        fp = (c_float if self.dtype == generate.F32
195              else c_double if self.dtype == generate.F64
196              else c_longdouble)
197
198        # int, int, int, int*, double*, double*, double*, double*, double*, double
199        argtypes = [c_int32]*3 + [c_void_p]*5 + [fp]
200        self.Iq = self.dll[generate.kernel_name(self.info, False)]
201        self.Iqxy = self.dll[generate.kernel_name(self.info, True)]
202        self.Iq.argtypes = argtypes
203        self.Iqxy.argtypes = argtypes
204
205    def __getstate__(self):
206        return self.info, self.dllpath
207
208    def __setstate__(self, state):
209        self.info, self.dllpath = state
210        self.dll = None
211
212    def make_kernel(self, q_vectors):
213        q_input = PyInput(q_vectors, self.dtype)
214        if self.dll is None: self._load_dll()
215        kernel = self.Iqxy if q_input.is_2d else self.Iq
216        return DllKernel(kernel, self.info, q_input)
217
218    def release(self):
219        """
220        Release any resources associated with the model.
221        """
222        if os.name == 'nt':
223            #dll = ct.cdll.LoadLibrary(self.dllpath)
224            dll = ct.CDLL(self.dllpath)
225            libHandle = dll._handle
226            #libHandle = ct.c_void_p(dll._handle)
227            del dll, self.dll
228            self.dll = None
229            #_ctypes.FreeLibrary(libHandle)
230            ct.windll.kernel32.FreeLibrary(libHandle)
231        else:   
232            pass 
233
234
235class DllKernel(object):
236    """
237    Callable SAS kernel.
238
239    *kernel* is the c function to call.
240
241    *model_info* is the module information
242
243    *q_input* is the DllInput q vectors at which the kernel should be
244    evaluated.
245
246    The resulting call method takes the *pars*, a list of values for
247    the fixed parameters to the kernel, and *pd_pars*, a list of (value, weight)
248    vectors for the polydisperse parameters.  *cutoff* determines the
249    integration limits: any points with combined weight less than *cutoff*
250    will not be calculated.
251
252    Call :meth:`release` when done with the kernel instance.
253    """
254    def __init__(self, kernel, model_info, q_input):
255        self.kernel = kernel
256        self.info = model_info
257        self.q_input = q_input
258        self.dtype = q_input.dtype
259        self.dim = '2d' if q_input.is_2d else '1d'
260        self.result = np.empty(q_input.nq+3, q_input.dtype)
261
262    def __call__(self, call_details, weights, values, cutoff):
263        real = (np.float32 if self.q_input.dtype == generate.F32
264                else np.float64 if self.q_input.dtype == generate.F64
265                else np.float128)
266        assert isinstance(call_details, details.CallDetails)
267        assert weights.dtype == real and values.dtype == real
268
269        start, stop = 0, call_details.total_pd
270        #print("in kerneldll")
271        #print("weights", weights)
272        #print("values", values)
273        args = [
274            self.q_input.nq, # nq
275            start, # pd_start
276            stop, # pd_stop pd_stride[MAX_PD]
277            call_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.