source: sasmodels/sasmodels/kerneldll.py @ 303d8d6

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

new calculator says hello before crashing

  • Property mode set to 100644
File size: 10.5 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
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 -fno-unknown-pragmas"
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"
83        else:
84            COMPILE += " -fWno-unknown-pragmas"
85else:
86    COMPILE = "cc -shared -fPIC -fopenmp -std=c99 -O2 -Wall %(source)s -o %(output)s -lm"
87
88DLL_PATH = tempfile.gettempdir()
89
90ALLOW_SINGLE_PRECISION_DLLS = True
91
92
93def dll_path(model_info, dtype="double"):
94    """
95    Path to the compiled model defined by *model_info*.
96    """
97    from os.path import join as joinpath, split as splitpath, splitext
98    basename = splitext(splitpath(model_info['filename'])[1])[0]
99    if np.dtype(dtype) == generate.F32:
100        basename += "32"
101    elif np.dtype(dtype) == generate.F64:
102        basename += "64"
103    else:
104        basename += "128"
105    return joinpath(DLL_PATH, basename+'.so')
106
107
108def make_dll(source, model_info, dtype="double"):
109    """
110    Load the compiled model defined by *kernel_module*.
111
112    Recompile if any files are newer than the model file.
113
114    *dtype* is a numpy floating point precision specifier indicating whether
115    the model should be single or double precision.  The default is double
116    precision.
117
118    The DLL is not loaded until the kernel is called so models can
119    be defined without using too many resources.
120
121    Set *sasmodels.kerneldll.DLL_PATH* to the compiled dll output path.
122    The default is the system temporary directory.
123
124    Set *sasmodels.ALLOW_SINGLE_PRECISION_DLLS* to True if single precision
125    models are allowed as DLLs.
126    """
127    if callable(model_info.get('Iq', None)):
128        return PyModel(model_info)
129
130    dtype = np.dtype(dtype)
131    if dtype == generate.F16:
132        raise ValueError("16 bit floats not supported")
133    if dtype == generate.F32 and not ALLOW_SINGLE_PRECISION_DLLS:
134        dtype = generate.F64  # Force 64-bit dll
135
136    if dtype == generate.F32: # 32-bit dll
137        tempfile_prefix = 'sas_' + model_info['name'] + '32_'
138    elif dtype == generate.F64:
139        tempfile_prefix = 'sas_' + model_info['name'] + '64_'
140    else:
141        tempfile_prefix = 'sas_' + model_info['name'] + '128_'
142
143    source = generate.convert_type(source, dtype)
144    source_files = (generate.model_sources(model_info)
145                    + [model_info['filename']]
146                    + generate.model_templates())
147    dll = dll_path(model_info, dtype)
148    newest = max(os.path.getmtime(f) for f in source_files)
149    if not os.path.exists(dll) or os.path.getmtime(dll) < newest:
150        # Replace with a proper temp file
151        fid, filename = tempfile.mkstemp(suffix=".c", prefix=tempfile_prefix)
152        os.fdopen(fid, "w").write(source)
153        command = COMPILE%{"source":filename, "output":dll}
154        print("Compile command: "+command)
155        status = os.system(command)
156        if status != 0 or not os.path.exists(dll):
157            raise RuntimeError("compile failed.  File is in %r"%filename)
158        else:
159            ## comment the following to keep the generated c file
160            os.unlink(filename)
161            #print("saving compiled file in %r"%filename)
162    return dll
163
164
165def load_dll(source, model_info, dtype="double"):
166    """
167    Create and load a dll corresponding to the source, info pair returned
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    """
173    filename = make_dll(source, model_info, dtype=dtype)
174    return DllModel(filename, model_info, dtype=dtype)
175
176class DllModel(object):
177    """
178    ctypes wrapper for a single model.
179
180    *source* and *model_info* are the model source and interface as returned
181    from :func:`gen.make`.
182
183    *dtype* is the desired model precision.  Any numpy dtype for single
184    or double precision floats will do, such as 'f', 'float32' or 'single'
185    for single and 'd', 'float64' or 'double' for double.  Double precision
186    is an optional extension which may not be available on all devices.
187
188    Call :meth:`release` when done with the kernel.
189    """
190    def __init__(self, dllpath, model_info, dtype=generate.F32):
191        self.info = model_info
192        self.dllpath = dllpath
193        self.dll = None
194        self.dtype = np.dtype(dtype)
195
196    def _load_dll(self):
197        #print("dll", self.dllpath)
198        try:
199            self.dll = ct.CDLL(self.dllpath)
200        except Exception as exc:
201            annotate_exception(exc, "while loading "+self.dllpath)
202            raise
203
204        fp = (c_float if self.dtype == generate.F32
205              else c_double if self.dtype == generate.F64
206              else c_longdouble)
207
208        # int, int, int, int*, double*, double*, double*, double*, double*, double
209        argtypes = [c_int]*3 + [c_void_p]*5 + [fp]
210        self.Iq = self.dll[generate.kernel_name(self.info, False)]
211        self.Iqxy = self.dll[generate.kernel_name(self.info, True)]
212        self.Iq.argtypes = argtypes
213        self.Iqxy.argtypes = argtypes
214
215    def __getstate__(self):
216        return self.info, self.dllpath
217
218    def __setstate__(self, state):
219        self.info, self.dllpath = state
220        self.dll = None
221
222    def __call__(self, q_vectors):
223        q_input = PyInput(q_vectors, self.dtype)
224        if self.dll is None: self._load_dll()
225        kernel = self.Iqxy if q_input.is_2d else self.Iq
226        return DllKernel(kernel, self.info, q_input)
227
228    def release(self):
229        """
230        Release any resources associated with the model.
231        """
232        pass # TODO: should release the dll
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.info = model_info
256        self.q_input = q_input
257        self.kernel = kernel
258        self.res = np.empty(q_input.nq+3, q_input.dtype)
259        dim = '2d' if q_input.is_2d else '1d'
260        self.parameters = model_info['par_type'][dim]
261
262        # In dll kernel, but not in opencl kernel
263        self.p_res = self.res.ctypes.data
264
265    def __call__(self, details, values, weights, cutoff):
266        real = (np.float32 if self.q_input.dtype == generate.F32
267                else np.float64 if self.q_input.dtype == generate.F64
268                else np.float128)
269        args = [
270            self.q_input.nq, # nq
271            0, # pd_start
272            1, # pd_stop
273            details.ctypes.data, # problem
274            weights.ctypes.data,  # weights
275            values.ctypes.data,  #pars
276            self.q_input.q_pointers[0], #q
277            self.p_res,   # results
278            real(cutoff), # cutoff
279            ]
280        self.kernel(*args)
281
282        return self.res[:-3]
283
284    def release(self):
285        """
286        Release any resources associated with the kernel.
287        """
288        pass
Note: See TracBrowser for help on using the repository browser.