source: sasmodels/sasmodels/kerneldll.py @ aa4946b

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

refactor so kernels are loaded via core.load_model

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