source: sasmodels/sasmodels/dll.py @ 31819c5

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

reorganize, check and update models

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