source: sasmodels/sasmodels/dll.py @ df4dc86

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

support MSVC build of ctypes/openmp models

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