source: sasmodels/sasmodels/kerneldll.py @ 68d3c1b

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

use MSVC to build ctypes if vcvarsall has been set

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