source: sasmodels/sasmodels/kerneldll.py @ a30cdd5

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

use SAS_OPENMP=1 in environment to use openmp for dlls

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