source: sasmodels/sasmodels/kerneldll.py @ 29aa28f

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

refactor so kernels are loaded via core.load_model

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