source: sasmodels/sasmodels/dll.py @ cb6ecf4

core_shell_microgelscostrafo411magnetic_modelrelease_v0.94release_v0.95ticket-1257-vesicle-productticket_1156ticket_1265_superballticket_822_more_unit_tests
Last change on this file since cb6ecf4 was cb6ecf4, checked in by pkienzle, 9 years ago

rename gen.py to generate.py for clarity

  • Property mode set to 100644
File size: 7.7 KB
Line 
1"""
2C types wrapper for sasview models.
3"""
4import sys
5import os
6import tempfile
7import ctypes as ct
8from ctypes import c_void_p, c_int, c_double
9
10import numpy as np
11
12from . import generate
13
14from .generate import F32, F64
15# Compiler platform details
16if sys.platform == 'darwin':
17    #COMPILE = "gcc-mp-4.7 -shared -fPIC -std=c99 -fopenmp -O2 -Wall %s -o %s -lm -lgomp"
18    COMPILE = "gcc -shared -fPIC -std=c99 -O2 -Wall %(source)s -o %(output)s -lm"
19elif os.name == 'nt':
20    # make sure vcvarsall.bat is called first in order to set compiler, headers, lib paths, etc.
21    ##COMPILER = r'"C:\Program Files (x86)\Common Files\Microsoft\Visual C++ for Python\9.0\VC\Bin\cl.exe"'
22    # Can't find VCOMP90.DLL (don't know why), so remove openmp support from windows compiler build
23    #COMPILE = "cl /nologo /Ox /MD /W3 /GS- /DNDEBUG /Tp%(source)s /link /DLL /INCREMENTAL:NO /MANIFEST /OUT:%(output)s"
24    COMPILE = "cl /nologo /Ox /MD /W3 /GS- /DNDEBUG /Tp%(source)s /openmp /link /DLL /INCREMENTAL:NO /MANIFEST /OUT:%(output)s"
25    #COMPILE = "gcc -shared -fPIC -std=c99 -fopenmp -O2 -Wall %(source)s -o %(output)s -lm"
26else:
27    COMPILE = "cc -shared -fPIC -std=c99 -fopenmp -O2 -Wall %(source)s -o %(output)s -lm"
28
29DLL_PATH = tempfile.gettempdir()
30
31
32def dll_path(info):
33    """
34    Path to the compiled model defined by *info*.
35    """
36    from os.path import join as joinpath, split as splitpath, splitext
37    basename = splitext(splitpath(info['filename'])[1])[0]
38    return joinpath(DLL_PATH, basename+'.so')
39
40
41def load_model(kernel_module, dtype=None):
42    """
43    Load the compiled model defined by *kernel_module*.
44
45    Recompile if any files are newer than the model file.
46
47    *dtype* is ignored.  Compiled files are always double.
48
49    The DLL is not loaded until the kernel is called so models an
50    be defined without using too many resources.
51    """
52    import tempfile
53
54    source, info = generate.make(kernel_module)
55    source_files = generate.sources(info) + [info['filename']]
56    newest = max(os.path.getmtime(f) for f in source_files)
57    dllpath = dll_path(info)
58    if not os.path.exists(dllpath) or os.path.getmtime(dllpath)<newest:
59        # Replace with a proper temp file
60        fid, filename = tempfile.mkstemp(suffix=".c",prefix="sas_"+info['name'])
61        os.fdopen(fid,"w").write(source)
62        command = COMPILE%{"source":filename, "output":dllpath}
63        print "Compile command:",command
64        status = os.system(command)
65        if status != 0:
66            print "compile failed.  File is in %r"%filename
67        else:
68            ## uncomment the following to keep the generated c file
69            #os.unlink(filename); print "saving compiled file in %r"%filename
70            pass
71    return DllModel(dllpath, info)
72
73
74IQ_ARGS = [c_void_p, c_void_p, c_int, c_void_p, c_double]
75IQXY_ARGS = [c_void_p, c_void_p, c_void_p, c_int, c_void_p, c_double]
76
77class DllModel(object):
78    """
79    ctypes wrapper for a single model.
80
81    *source* and *info* are the model source and interface as returned
82    from :func:`gen.make`.
83
84    *dtype* is the desired model precision.  Any numpy dtype for single
85    or double precision floats will do, such as 'f', 'float32' or 'single'
86    for single and 'd', 'float64' or 'double' for double.  Double precision
87    is an optional extension which may not be available on all devices.
88
89    Call :meth:`release` when done with the kernel.
90    """
91    def __init__(self, dllpath, info):
92        self.info = info
93        self.dllpath = dllpath
94        self.dll = None
95
96    def _load_dll(self):
97        Nfixed1d = len(self.info['partype']['fixed-1d'])
98        Nfixed2d = len(self.info['partype']['fixed-2d'])
99        Npd1d = len(self.info['partype']['pd-1d'])
100        Npd2d = len(self.info['partype']['pd-2d'])
101
102        #print "dll",self.dllpath
103        self.dll = ct.CDLL(self.dllpath)
104
105        self.Iq = self.dll[generate.kernel_name(self.info, False)]
106        self.Iq.argtypes = IQ_ARGS + [c_double]*Nfixed1d + [c_int]*Npd1d
107
108        self.Iqxy = self.dll[generate.kernel_name(self.info, True)]
109        self.Iqxy.argtypes = IQXY_ARGS + [c_double]*Nfixed2d + [c_int]*Npd2d
110
111    def __getstate__(self):
112        return {'info': self.info, 'dllpath': self.dllpath, 'dll': None}
113
114    def __setstate__(self, state):
115        self.__dict__ = state
116
117    def __call__(self, input):
118        if self.dll is None: self._load_dll()
119
120        kernel = self.Iqxy if input.is_2D else self.Iq
121        return DllKernel(kernel, self.info, input)
122
123    def make_input(self, q_vectors):
124        """
125        Make q input vectors available to the model.
126
127        This only needs to be done once for all models that operate on the
128        same input.  So for example, if you are adding two different models
129        together to compare to a data set, then only one model needs to
130        needs to call make_input, so long as the models have the same dtype.
131        """
132        return DllInput(q_vectors)
133
134    def release(self):
135        pass # TODO: should release the dll
136
137
138class DllInput(object):
139    """
140    Make q data available to the gpu.
141
142    *q_vectors* is a list of q vectors, which will be *[q]* for 1-D data,
143    and *[qx, qy]* for 2-D data.  Internally, the vectors will be reallocated
144    to get the best performance on OpenCL, which may involve shifting and
145    stretching the array to better match the memory architecture.  Additional
146    points will be evaluated with *q=1e-3*.
147
148    *dtype* is the data type for the q vectors. The data type should be
149    set to match that of the kernel, which is an attribute of
150    :class:`GpuProgram`.  Note that not all kernels support double
151    precision, so even if the program was created for double precision,
152    the *GpuProgram.dtype* may be single precision.
153
154    Call :meth:`release` when complete.  Even if not called directly, the
155    buffer will be released when the data object is freed.
156    """
157    def __init__(self, q_vectors):
158        self.nq = q_vectors[0].size
159        self.dtype = np.dtype('double')
160        self.is_2D = (len(q_vectors) == 2)
161        self.q_vectors = [np.ascontiguousarray(q,self.dtype) for q in q_vectors]
162        self.q_pointers = [q.ctypes.data for q in q_vectors]
163
164    def release(self):
165        self.q_vectors = []
166
167class DllKernel(object):
168    """
169    Callable SAS kernel.
170
171    *kernel* is the DllKernel object to call.
172
173    *info* is the module information
174
175    *input* is the DllInput q vectors at which the kernel should be
176    evaluated.
177
178    The resulting call method takes the *pars*, a list of values for
179    the fixed parameters to the kernel, and *pd_pars*, a list of (value,weight)
180    vectors for the polydisperse parameters.  *cutoff* determines the
181    integration limits: any points with combined weight less than *cutoff*
182    will not be calculated.
183
184    Call :meth:`release` when done with the kernel instance.
185    """
186    def __init__(self, kernel, info, input):
187        self.info = info
188        self.input = input
189        self.kernel = kernel
190        self.res = np.empty(input.nq, input.dtype)
191        dim = '2d' if input.is_2D else '1d'
192        self.fixed_pars = info['partype']['fixed-'+dim]
193        self.pd_pars = info['partype']['pd-'+dim]
194
195        # In dll kernel, but not in opencl kernel
196        self.p_res = self.res.ctypes.data
197
198    def __call__(self, pars, pd_pars, cutoff):
199        real = np.float32 if self.input.dtype == F32 else np.float64
200        fixed = [real(p) for p in pars]
201        cutoff = real(cutoff)
202        loops = np.hstack(pd_pars)
203        loops = np.ascontiguousarray(loops.T, self.input.dtype).flatten()
204        loops_N = [np.uint32(len(p[0])) for p in pd_pars]
205
206        nq = c_int(self.input.nq)
207        p_loops = loops.ctypes.data
208        args = self.input.q_pointers + [self.p_res, nq, p_loops, cutoff] + fixed + loops_N
209        #print pars
210        self.kernel(*args)
211
212        return self.res
213
214    def release(self):
215        pass
Note: See TracBrowser for help on using the repository browser.