source: sasmodels/sasmodels/kerneldll.py @ 92da231

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

add notes about compiler and opencl drivers to docs

  • Property mode set to 100644
File size: 10.5 KB
RevLine 
[92da231]1r"""
[14de349]2C types wrapper for sasview models.
[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
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.
[14de349]45"""
[750ffa5]46
[5d4777d]47import sys
48import os
[df4dc86]49import tempfile
[14de349]50import ctypes as ct
[750ffa5]51from ctypes import c_void_p, c_int, c_double, c_float
[14de349]52
53import numpy as np
54
[cb6ecf4]55from . import generate
[f734e7d]56from .kernelpy import PyInput, PyModel
[2c801fe]57from .exception import annotate_exception
[14de349]58
[5d4777d]59# Compiler platform details
60if sys.platform == 'darwin':
[216a9e1]61    #COMPILE = "gcc-mp-4.7 -shared -fPIC -std=c99 -fopenmp -O2 -Wall %s -o %s -lm -lgomp"
[df4dc86]62    COMPILE = "gcc -shared -fPIC -std=c99 -O2 -Wall %(source)s -o %(output)s -lm"
[5d4777d]63elif os.name == 'nt':
[3c56da87]64    # call vcvarsall.bat before compiling to set path, headers, libs, etc.
[68d3c1b]65    if "VCINSTALLDIR" in os.environ:
[a30cdd5]66        # MSVC compiler is available, so use it.  OpenMP requires a copy of
67        # vcomp90.dll on the path.  One may be found here:
68        #       C:/Windows/winsxs/x86_microsoft.vc90.openmp*/vcomp90.dll
69        # Copy this to the python directory and uncomment the OpenMP COMPILE
[f734e7d]70        # TODO: remove intermediate OBJ file created in the directory
71        # TODO: maybe don't use randomized name for the c file
[a30cdd5]72        CC = "cl /nologo /Ox /MD /W3 /GS- /DNDEBUG /Tp%(source)s "
73        LN = "/link /DLL /INCREMENTAL:NO /MANIFEST /OUT:%(output)s"
74        if "SAS_OPENMP" in os.environ:
75            COMPILE = " ".join((CC, "/openmp", LN))
76        else:
77            COMPILE = " ".join((CC, LN))
[68d3c1b]78    else:
79        COMPILE = "gcc -shared -fPIC -std=c99 -O2 -Wall %(source)s -o %(output)s -lm"
[a30cdd5]80        if "SAS_OPENMP" in os.environ:
81            COMPILE = COMPILE + " -fopenmp"
[5d4777d]82else:
[a30cdd5]83    COMPILE = "cc -shared -fPIC -fopenmp -std=c99 -O2 -Wall %(source)s -o %(output)s -lm"
[df4dc86]84
85DLL_PATH = tempfile.gettempdir()
[5d4777d]86
[750ffa5]87ALLOW_SINGLE_PRECISION_DLLS = False
[5d4777d]88
[750ffa5]89
90def dll_path(info, dtype="double"):
[5d4777d]91    """
92    Path to the compiled model defined by *info*.
93    """
94    from os.path import join as joinpath, split as splitpath, splitext
95    basename = splitext(splitpath(info['filename'])[1])[0]
[750ffa5]96    if np.dtype(dtype) == generate.F32:
97        basename += "32"
[5d4777d]98    return joinpath(DLL_PATH, basename+'.so')
99
100
[aa4946b]101def make_dll(source, info, dtype="double"):
[5d4777d]102    """
103    Load the compiled model defined by *kernel_module*.
104
105    Recompile if any files are newer than the model file.
106
[aa4946b]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.
[5d4777d]110
[aa4946b]111    The DLL is not loaded until the kernel is called so models can
[5d4777d]112    be defined without using too many resources.
[aa4946b]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.
[5d4777d]119    """
[750ffa5]120    if not ALLOW_SINGLE_PRECISION_DLLS: dtype = "double"   # Force 64-bit dll
121    dtype = np.dtype(dtype)
122
[f734e7d]123    if callable(info.get('Iq',None)):
124        return PyModel(info)
[750ffa5]125
126    if dtype == generate.F32: # 32-bit dll
127        source = generate.use_single(source)
128        tempfile_prefix = 'sas_'+info['name']+'32_'
129    else:
130        tempfile_prefix = 'sas_'+info['name']+'_'
131
[cb6ecf4]132    source_files = generate.sources(info) + [info['filename']]
[aa4946b]133    dll= dll_path(info, dtype)
[5d4777d]134    newest = max(os.path.getmtime(f) for f in source_files)
[aa4946b]135    if not os.path.exists(dll) or os.path.getmtime(dll)<newest:
[5d4777d]136        # Replace with a proper temp file
[750ffa5]137        fid, filename = tempfile.mkstemp(suffix=".c",prefix=tempfile_prefix)
[5d4777d]138        os.fdopen(fid,"w").write(source)
[aa4946b]139        command = COMPILE%{"source":filename, "output":dll}
[df4dc86]140        print "Compile command:",command
141        status = os.system(command)
[aa4946b]142        if status != 0 or not os.path.exists(dll):
[f734e7d]143            raise RuntimeError("compile failed.  File is in %r"%filename)
[5d4777d]144        else:
145            ## uncomment the following to keep the generated c file
[aa4946b]146            os.unlink(filename); print "saving compiled file in %r"%filename
147    return dll
148
149
150def load_dll(source, info, dtype="double"):
151    """
152    Create and load a dll corresponding to the source,info pair returned
153    from :func:`sasmodels.generate.make` compiled for the target precision.
154
155    See :func:`make_dll` for details on controlling the dll path and the
156    allowed floating point precision.
157    """
158    filename = make_dll(source, info, dtype=dtype)
159    return DllModel(filename, info, dtype=dtype)
[5d4777d]160
[14de349]161
[f734e7d]162IQ_ARGS = [c_void_p, c_void_p, c_int]
163IQXY_ARGS = [c_void_p, c_void_p, c_void_p, c_int]
[14de349]164
165class DllModel(object):
166    """
167    ctypes wrapper for a single model.
168
[ce27e21]169    *source* and *info* are the model source and interface as returned
[14de349]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.
[ff7119b]176
177    Call :meth:`release` when done with the kernel.
[14de349]178    """
[750ffa5]179    def __init__(self, dllpath, info, dtype=generate.F32):
[ce27e21]180        self.info = info
181        self.dllpath = dllpath
182        self.dll = None
[750ffa5]183        self.dtype = np.dtype(dtype)
[14de349]184
[ce27e21]185    def _load_dll(self):
186        Nfixed1d = len(self.info['partype']['fixed-1d'])
187        Nfixed2d = len(self.info['partype']['fixed-2d'])
188        Npd1d = len(self.info['partype']['pd-1d'])
189        Npd2d = len(self.info['partype']['pd-2d'])
[14de349]190
[df4dc86]191        #print "dll",self.dllpath
[2c801fe]192        try:
193            self.dll = ct.CDLL(self.dllpath)
194        except Exception, exc:
[f3f46cd]195            annotate_exception(exc, "while loading "+self.dllpath)
[2c801fe]196            raise
[14de349]197
[750ffa5]198        fp = c_float if self.dtype == generate.F32 else c_double
199        pd_args_1d = [c_void_p, fp] + [c_int]*Npd1d if Npd1d else []
200        pd_args_2d= [c_void_p, fp] + [c_int]*Npd2d if Npd2d else []
[cb6ecf4]201        self.Iq = self.dll[generate.kernel_name(self.info, False)]
[750ffa5]202        self.Iq.argtypes = IQ_ARGS + pd_args_1d + [fp]*Nfixed1d
[ce27e21]203
[cb6ecf4]204        self.Iqxy = self.dll[generate.kernel_name(self.info, True)]
[750ffa5]205        self.Iqxy.argtypes = IQXY_ARGS + pd_args_2d + [fp]*Nfixed2d
[ce27e21]206
207    def __getstate__(self):
208        return {'info': self.info, 'dllpath': self.dllpath, 'dll': None}
209
210    def __setstate__(self, state):
211        self.__dict__ = state
212
[3c56da87]213    def __call__(self, q_input):
[750ffa5]214        if self.dtype != q_input.dtype:
215            raise TypeError("data is %s kernel is %s" % (q_input.dtype, self.dtype))
[b3f6bc3]216        if self.dll is None: self._load_dll()
[3c56da87]217        kernel = self.Iqxy if q_input.is_2D else self.Iq
218        return DllKernel(kernel, self.info, q_input)
[14de349]219
[3c56da87]220    # pylint: disable=no-self-use
[14de349]221    def make_input(self, q_vectors):
222        """
223        Make q input vectors available to the model.
224
[b3f6bc3]225        Note that each model needs its own q vector even if the case of
226        mixture models because some models may be OpenCL, some may be
227        ctypes and some may be pure python.
[14de349]228        """
[750ffa5]229        return PyInput(q_vectors, dtype=self.dtype)
[14de349]230
[ff7119b]231    def release(self):
232        pass # TODO: should release the dll
233
[14de349]234
235class DllKernel(object):
[ff7119b]236    """
237    Callable SAS kernel.
238
[b3f6bc3]239    *kernel* is the c function to call.
[ff7119b]240
241    *info* is the module information
242
[3c56da87]243    *q_input* is the DllInput q vectors at which the kernel should be
[ff7119b]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    """
[3c56da87]254    def __init__(self, kernel, info, q_input):
[5d4777d]255        self.info = info
[3c56da87]256        self.q_input = q_input
[14de349]257        self.kernel = kernel
[3c56da87]258        self.res = np.empty(q_input.nq, q_input.dtype)
259        dim = '2d' if q_input.is_2D else '1d'
[ce27e21]260        self.fixed_pars = info['partype']['fixed-'+dim]
261        self.pd_pars = info['partype']['pd-'+dim]
[14de349]262
[ce27e21]263        # In dll kernel, but not in opencl kernel
264        self.p_res = self.res.ctypes.data
[14de349]265
[f734e7d]266    def __call__(self, fixed_pars, pd_pars, cutoff):
[63b32bb]267        real = np.float32 if self.q_input.dtype == generate.F32 else np.float64
[14de349]268
[3c56da87]269        nq = c_int(self.q_input.nq)
[f734e7d]270        if pd_pars:
271            cutoff = real(cutoff)
272            loops_N = [np.uint32(len(p[0])) for p in pd_pars]
273            loops = np.hstack(pd_pars)
[3c56da87]274            loops = np.ascontiguousarray(loops.T, self.q_input.dtype).flatten()
[f734e7d]275            p_loops = loops.ctypes.data
276            dispersed = [p_loops, cutoff] + loops_N
277        else:
278            dispersed = []
279        fixed = [real(p) for p in fixed_pars]
[3c56da87]280        args = self.q_input.q_pointers + [self.p_res, nq] + dispersed + fixed
[14de349]281        #print pars
[ce27e21]282        self.kernel(*args)
[14de349]283
284        return self.res
285
286    def release(self):
287        pass
Note: See TracBrowser for help on using the repository browser.