source: sasmodels/sasmodels/dll.py @ 216a9e1

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

batch generate random old/new comparisons across all models

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