source: sasmodels/sasmodels/kerneldll.py @ f3f46cd

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

default to no openmp for windows compiler

  • Property mode set to 100644
File size: 8.8 KB
Line 
1"""
2C types wrapper for sasview models.
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"""
8
9import sys
10import os
11import tempfile
12import ctypes as ct
13from ctypes import c_void_p, c_int, c_double, c_float
14
15import numpy as np
16
17from . import generate
18from .kernelpy import PyInput, PyModel
19from .exception import annotate_exception
20
21# Compiler platform details
22if sys.platform == 'darwin':
23    #COMPILE = "gcc-mp-4.7 -shared -fPIC -std=c99 -fopenmp -O2 -Wall %s -o %s -lm -lgomp"
24    COMPILE = "gcc -shared -fPIC -std=c99 -O2 -Wall %(source)s -o %(output)s -lm"
25elif os.name == 'nt':
26    # call vcvarsall.bat before compiling to set path, headers, libs, etc.
27    if "VCINSTALLDIR" in os.environ:
28        # MSVC compiler is available, so use it.
29        # TODO: remove intermediate OBJ file created in the directory
30        # TODO: maybe don't use randomized name for the c file
31        #COMPILE = "cl /nologo /Ox /MD /W3 /GS- /DNDEBUG /Tp%(source)s /openmp /link /DLL /INCREMENTAL:NO /MANIFEST /OUT:%(output)s"
32        # Can't find VCOMP90.DLL (don't know why), so remove openmp support
33        # from windows compiler build
34        COMPILE = "cl /nologo /Ox /MD /W3 /GS- /DNDEBUG /Tp%(source)s /link /DLL /INCREMENTAL:NO /MANIFEST /OUT:%(output)s"
35    else:
36        #COMPILE = "gcc -shared -fPIC -std=c99 -fopenmp -O2 -Wall %(source)s -o %(output)s -lm"
37        COMPILE = "gcc -shared -fPIC -std=c99 -O2 -Wall %(source)s -o %(output)s -lm"
38else:
39    COMPILE = "cc -shared -fPIC -std=c99 -fopenmp -O2 -Wall %(source)s -o %(output)s -lm"
40
41DLL_PATH = tempfile.gettempdir()
42
43ALLOW_SINGLE_PRECISION_DLLS = False
44
45
46def dll_path(info, dtype="double"):
47    """
48    Path to the compiled model defined by *info*.
49    """
50    from os.path import join as joinpath, split as splitpath, splitext
51    basename = splitext(splitpath(info['filename'])[1])[0]
52    if np.dtype(dtype) == generate.F32:
53        basename += "32"
54    return joinpath(DLL_PATH, basename+'.so')
55
56
57def make_dll(source, info, dtype="double"):
58    """
59    Load the compiled model defined by *kernel_module*.
60
61    Recompile if any files are newer than the model file.
62
63    *dtype* is a numpy floating point precision specifier indicating whether
64    the model should be single or double precision.  The default is double
65    precision.
66
67    The DLL is not loaded until the kernel is called so models can
68    be defined without using too many resources.
69
70    Set *sasmodels.kerneldll.DLL_PATH* to the compiled dll output path.
71    The default is the system temporary directory.
72
73    Set *sasmodels.ALLOW_SINGLE_PRECISION_DLLS* to True if single precision
74    models are allowed as DLLs.
75    """
76    if not ALLOW_SINGLE_PRECISION_DLLS: dtype = "double"   # Force 64-bit dll
77    dtype = np.dtype(dtype)
78
79    if callable(info.get('Iq',None)):
80        return PyModel(info)
81
82    if dtype == generate.F32: # 32-bit dll
83        source = generate.use_single(source)
84        tempfile_prefix = 'sas_'+info['name']+'32_'
85    else:
86        tempfile_prefix = 'sas_'+info['name']+'_'
87
88    source_files = generate.sources(info) + [info['filename']]
89    dll= dll_path(info, dtype)
90    newest = max(os.path.getmtime(f) for f in source_files)
91    if not os.path.exists(dll) or os.path.getmtime(dll)<newest:
92        # Replace with a proper temp file
93        fid, filename = tempfile.mkstemp(suffix=".c",prefix=tempfile_prefix)
94        os.fdopen(fid,"w").write(source)
95        command = COMPILE%{"source":filename, "output":dll}
96        print "Compile command:",command
97        status = os.system(command)
98        if status != 0 or not os.path.exists(dll):
99            raise RuntimeError("compile failed.  File is in %r"%filename)
100        else:
101            ## uncomment the following to keep the generated c file
102            os.unlink(filename); print "saving compiled file in %r"%filename
103    return dll
104
105
106def load_dll(source, info, dtype="double"):
107    """
108    Create and load a dll corresponding to the source,info pair returned
109    from :func:`sasmodels.generate.make` compiled for the target precision.
110
111    See :func:`make_dll` for details on controlling the dll path and the
112    allowed floating point precision.
113    """
114    filename = make_dll(source, info, dtype=dtype)
115    return DllModel(filename, info, dtype=dtype)
116
117
118IQ_ARGS = [c_void_p, c_void_p, c_int]
119IQXY_ARGS = [c_void_p, c_void_p, c_void_p, c_int]
120
121class DllModel(object):
122    """
123    ctypes wrapper for a single model.
124
125    *source* and *info* are the model source and interface as returned
126    from :func:`gen.make`.
127
128    *dtype* is the desired model precision.  Any numpy dtype for single
129    or double precision floats will do, such as 'f', 'float32' or 'single'
130    for single and 'd', 'float64' or 'double' for double.  Double precision
131    is an optional extension which may not be available on all devices.
132
133    Call :meth:`release` when done with the kernel.
134    """
135    def __init__(self, dllpath, info, dtype=generate.F32):
136        self.info = info
137        self.dllpath = dllpath
138        self.dll = None
139        self.dtype = np.dtype(dtype)
140
141    def _load_dll(self):
142        Nfixed1d = len(self.info['partype']['fixed-1d'])
143        Nfixed2d = len(self.info['partype']['fixed-2d'])
144        Npd1d = len(self.info['partype']['pd-1d'])
145        Npd2d = len(self.info['partype']['pd-2d'])
146
147        #print "dll",self.dllpath
148        try:
149            self.dll = ct.CDLL(self.dllpath)
150        except WindowsError, exc:
151            # TODO: update annotate_exception so it can handle WindowsError
152            raise RuntimeError(str(exc)+" while loading "+self.dllpath)
153        except Exception, exc:
154            annotate_exception(exc, "while loading "+self.dllpath)
155            raise
156
157        fp = c_float if self.dtype == generate.F32 else c_double
158        pd_args_1d = [c_void_p, fp] + [c_int]*Npd1d if Npd1d else []
159        pd_args_2d= [c_void_p, fp] + [c_int]*Npd2d if Npd2d else []
160        self.Iq = self.dll[generate.kernel_name(self.info, False)]
161        self.Iq.argtypes = IQ_ARGS + pd_args_1d + [fp]*Nfixed1d
162
163        self.Iqxy = self.dll[generate.kernel_name(self.info, True)]
164        self.Iqxy.argtypes = IQXY_ARGS + pd_args_2d + [fp]*Nfixed2d
165
166    def __getstate__(self):
167        return {'info': self.info, 'dllpath': self.dllpath, 'dll': None}
168
169    def __setstate__(self, state):
170        self.__dict__ = state
171
172    def __call__(self, q_input):
173        if self.dtype != q_input.dtype:
174            raise TypeError("data is %s kernel is %s" % (q_input.dtype, self.dtype))
175        if self.dll is None: self._load_dll()
176        kernel = self.Iqxy if q_input.is_2D else self.Iq
177        return DllKernel(kernel, self.info, q_input)
178
179    # pylint: disable=no-self-use
180    def make_input(self, q_vectors):
181        """
182        Make q input vectors available to the model.
183
184        Note that each model needs its own q vector even if the case of
185        mixture models because some models may be OpenCL, some may be
186        ctypes and some may be pure python.
187        """
188        return PyInput(q_vectors, dtype=self.dtype)
189
190    def release(self):
191        pass # TODO: should release the dll
192
193
194class DllKernel(object):
195    """
196    Callable SAS kernel.
197
198    *kernel* is the c function to call.
199
200    *info* is the module information
201
202    *q_input* is the DllInput q vectors at which the kernel should be
203    evaluated.
204
205    The resulting call method takes the *pars*, a list of values for
206    the fixed parameters to the kernel, and *pd_pars*, a list of (value,weight)
207    vectors for the polydisperse parameters.  *cutoff* determines the
208    integration limits: any points with combined weight less than *cutoff*
209    will not be calculated.
210
211    Call :meth:`release` when done with the kernel instance.
212    """
213    def __init__(self, kernel, info, q_input):
214        self.info = info
215        self.q_input = q_input
216        self.kernel = kernel
217        self.res = np.empty(q_input.nq, q_input.dtype)
218        dim = '2d' if q_input.is_2D else '1d'
219        self.fixed_pars = info['partype']['fixed-'+dim]
220        self.pd_pars = info['partype']['pd-'+dim]
221
222        # In dll kernel, but not in opencl kernel
223        self.p_res = self.res.ctypes.data
224
225    def __call__(self, fixed_pars, pd_pars, cutoff):
226        real = np.float32 if self.q_input.dtype == generate.F32 else np.float64
227
228        nq = c_int(self.q_input.nq)
229        if pd_pars:
230            cutoff = real(cutoff)
231            loops_N = [np.uint32(len(p[0])) for p in pd_pars]
232            loops = np.hstack(pd_pars)
233            loops = np.ascontiguousarray(loops.T, self.q_input.dtype).flatten()
234            p_loops = loops.ctypes.data
235            dispersed = [p_loops, cutoff] + loops_N
236        else:
237            dispersed = []
238        fixed = [real(p) for p in fixed_pars]
239        args = self.q_input.q_pointers + [self.p_res, nq] + dispersed + fixed
240        #print pars
241        self.kernel(*args)
242
243        return self.res
244
245    def release(self):
246        pass
Note: See TracBrowser for help on using the repository browser.