source: sasmodels/sasmodels/kerneldll.py @ f734e7d

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

restructure c code generation for maintainability; extend test harness to allow opencl and ctypes tests

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