source: sasmodels/sasmodels/kerneldll.py @ afb2c78

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

don't default to openmp on windows ctypes builds

  • Property mode set to 100644
File size: 6.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 generate
13from .kernelpy import PyInput, PyKernel
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    ##COMPILER = r'"C:\Program Files (x86)\Common Files\Microsoft\Visual C++ for Python\9.0\VC\Bin\cl.exe"'
23    # Can't find VCOMP90.DLL (don't know why), so remove openmp support from windows compiler build
24    #COMPILE = "cl /nologo /Ox /MD /W3 /GS- /DNDEBUG /Tp%(source)s /link /DLL /INCREMENTAL:NO /MANIFEST /OUT:%(output)s"
25    #COMPILE = "cl /nologo /Ox /MD /W3 /GS- /DNDEBUG /Tp%(source)s /openmp /link /DLL /INCREMENTAL:NO /MANIFEST /OUT:%(output)s"
26    #COMPILE = "gcc -shared -fPIC -std=c99 -fopenmp -O2 -Wall %(source)s -o %(output)s -lm"
27    COMPILE = "gcc -shared -fPIC -std=c99 -O2 -Wall %(source)s -o %(output)s -lm"
28else:
29    COMPILE = "cc -shared -fPIC -std=c99 -fopenmp -O2 -Wall %(source)s -o %(output)s -lm"
30
31DLL_PATH = tempfile.gettempdir()
32
33
34def dll_path(info):
35    """
36    Path to the compiled model defined by *info*.
37    """
38    from os.path import join as joinpath, split as splitpath, splitext
39    basename = splitext(splitpath(info['filename'])[1])[0]
40    return joinpath(DLL_PATH, basename+'.so')
41
42
43def load_model(kernel_module, dtype=None):
44    """
45    Load the compiled model defined by *kernel_module*.
46
47    Recompile if any files are newer than the model file.
48
49    *dtype* is ignored.  Compiled files are always double.
50
51    The DLL is not loaded until the kernel is called so models an
52    be defined without using too many resources.
53    """
54    import tempfile
55
56    source, info = generate.make(kernel_module)
57    source_files = generate.sources(info) + [info['filename']]
58    newest = max(os.path.getmtime(f) for f in source_files)
59    dllpath = dll_path(info)
60    if not os.path.exists(dllpath) or os.path.getmtime(dllpath)<newest:
61        # Replace with a proper temp file
62        fid, filename = tempfile.mkstemp(suffix=".c",prefix="sas_"+info['name'])
63        os.fdopen(fid,"w").write(source)
64        command = COMPILE%{"source":filename, "output":dllpath}
65        print "Compile command:",command
66        status = os.system(command)
67        if status != 0:
68            print "compile failed.  File is in %r"%filename
69        else:
70            ## uncomment the following to keep the generated c file
71            #os.unlink(filename); print "saving compiled file in %r"%filename
72            pass
73    return DllModel(dllpath, info)
74
75
76IQ_ARGS = [c_void_p, c_void_p, c_int, c_void_p, c_double]
77IQXY_ARGS = [c_void_p, c_void_p, c_void_p, c_int, c_void_p, c_double]
78
79class DllModel(object):
80    """
81    ctypes wrapper for a single model.
82
83    *source* and *info* are the model source and interface as returned
84    from :func:`gen.make`.
85
86    *dtype* is the desired model precision.  Any numpy dtype for single
87    or double precision floats will do, such as 'f', 'float32' or 'single'
88    for single and 'd', 'float64' or 'double' for double.  Double precision
89    is an optional extension which may not be available on all devices.
90
91    Call :meth:`release` when done with the kernel.
92    """
93    def __init__(self, dllpath, info):
94        self.info = info
95        self.dllpath = dllpath
96        self.dll = None
97
98    def _load_dll(self):
99        Nfixed1d = len(self.info['partype']['fixed-1d'])
100        Nfixed2d = len(self.info['partype']['fixed-2d'])
101        Npd1d = len(self.info['partype']['pd-1d'])
102        Npd2d = len(self.info['partype']['pd-2d'])
103
104        #print "dll",self.dllpath
105        self.dll = ct.CDLL(self.dllpath)
106
107        self.Iq = self.dll[generate.kernel_name(self.info, False)]
108        self.Iq.argtypes = IQ_ARGS + [c_double]*Nfixed1d + [c_int]*Npd1d
109
110        self.Iqxy = self.dll[generate.kernel_name(self.info, True)]
111        self.Iqxy.argtypes = IQXY_ARGS + [c_double]*Nfixed2d + [c_int]*Npd2d
112
113    def __getstate__(self):
114        return {'info': self.info, 'dllpath': self.dllpath, 'dll': None}
115
116    def __setstate__(self, state):
117        self.__dict__ = state
118
119    def __call__(self, input):
120        # Support pure python kernel call
121        if input.is_2D and callable(self.info['Iqxy']):
122            return PyKernel(self.info['Iqxy'], self.info, input)
123        elif not input.is_2D and callable(self.info['Iq']):
124            return PyKernel(self.info['Iq'], self.info, input)
125
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, pars, pd_pars, cutoff):
176        real = np.float32 if self.input.dtype == F32 else np.float64
177        fixed = [real(p) for p in pars]
178        cutoff = real(cutoff)
179        loops = np.hstack(pd_pars)
180        loops = np.ascontiguousarray(loops.T, self.input.dtype).flatten()
181        loops_N = [np.uint32(len(p[0])) for p in pd_pars]
182
183        nq = c_int(self.input.nq)
184        p_loops = loops.ctypes.data
185        args = self.input.q_pointers + [self.p_res, nq, p_loops, cutoff] + fixed + loops_N
186        #print pars
187        self.kernel(*args)
188
189        return self.res
190
191    def release(self):
192        pass
Note: See TracBrowser for help on using the repository browser.