Integrands in C or Fortran¶
Older implementations of the vegas
algorithm
have been used extensively in C and Fortran codes. The Python
implementation described here uses a more powerful algorithm.
It is relatively straightforward to combine this version with integrands
coded in C or Fortran. Such integrands are usually substantially
faster than integrands coded directly in Python; they are similar in
speed to optimized Cython code.
There are
many ways to access C and Fortran integrands from Python. Here we
review a few of the options.
ctypes
for C¶
The simplest way to access an integrand coded in C is to use the
Python ctypes
module. To illustrate, consider the following
integrand, written in C and stored in file cfcn.c
:
// file cfcn.c
#include <math.h>
double fcn(double x[], int dim)
{
int i;
double xsq = 0.0;
for(i=0; i<dim; i++)
xsq += x[i] * x[i] ;
return exp(-100. * sqrt(xsq)) * pow(100.,dim);
}
This file needs to be compiled into a shared library using something like:
cc -fPIC -shared -o cfcn.so cfcn.c
The exact compilation command depends on the operating system and compiler
being used. The function in this library is then wrapped in
Python function f
, and integrated using vegas
:
import vegas
import numpy as np
import ctypes
# import cfcn.so
cfcn = ctypes.CDLL('cfcn.so')
# specify argument types and result type for cfcn.fcn
cfcn.fcn.argtypes = (ctypes.POINTER(ctypes.c_double), ctypes.c_int)
cfcn.fcn.restype = ctypes.c_double
# Python wrapper for function cfcn.fcn
def f(x):
global cfcn
n = len(x)
array_type = ctypes.c_double * n
return cfcn.fcn(array_type(*x), ctypes.c_int(n))
def main():
integ = vegas.Integrator(4 * [[0., 1.]])
print(integ(f, neval=1e4, nitn=10).summary())
print(integ(f, neval=1e4, nitn=10).summary())
if __name__ == '__main__':
main()
The output shows 10 iterations that are used to adapt vegas
to the
integrand, and then an additional 10 iterations to generate the
final result:
itn integral wgt average chi2/dof Q
-------------------------------------------------------
1 8.6(7.1) 8.6(7.1) 0.00 1.00
2 8.2(1.7) 8.2(1.7) 0.00 0.96
3 7.14(76) 7.32(69) 0.18 0.84
4 7.88(38) 7.75(33) 0.29 0.84
5 7.39(13) 7.44(12) 0.47 0.76
6 7.359(81) 7.383(68) 0.43 0.82
7 7.400(55) 7.393(43) 0.37 0.90
8 7.392(51) 7.393(33) 0.32 0.95
9 7.427(48) 7.404(27) 0.32 0.96
10 7.388(41) 7.399(23) 0.30 0.98
itn integral wgt average chi2/dof Q
-------------------------------------------------------
1 7.429(34) 7.429(34) 0.00 1.00
2 7.412(32) 7.420(24) 0.13 0.72
3 7.413(28) 7.417(18) 0.08 0.92
4 7.366(25) 7.400(15) 0.96 0.41
5 7.366(23) 7.390(12) 1.12 0.34
6 7.410(22) 7.395(11) 1.02 0.40
7 7.395(20) 7.3951(95) 0.85 0.53
8 7.425(19) 7.4011(85) 1.02 0.42
9 7.394(19) 7.3998(77) 0.91 0.51
10 7.386(17) 7.3976(71) 0.86 0.56
The final estimate for the integral is 7.3976(71)
(1000 times more accurate than the very first iteration).
The ctypes
implementation is probably the slowest of the
implementations shown here.
Cython for C¶
A more flexible (and often faster) interface to a C integrand can be
created using Cython. To increase efficiency (slightly, in this case),
we use Cython code in file cfcn.pyx
to convert the orginal
function (in cfcn.c
) into a batch integral:
# file cfcn.pyx
import numpy as np
import vegas
cdef extern double fcn (double[] x, int n)
@vegas.batchintegrand
def f(double[:, ::1] x):
cdef double[:] ans
cdef int i, dim=x.shape[1]
ans = np.empty(x.shape[0], type(x[0,0]))
for i in range(x.shape[0]):
ans[i] = fcn(&x[i, 0], dim)
return ans
We also have to tell Cython how to construct the cfcn
Python
module since that module needs to include compiled code
from cfcn.c
. This is done with a .pyxbld file:
# file cfcn.pyxbld
import numpy as np
def make_ext(modname, pyxfilename):
from distutils.extension import Extension
return Extension(name = modname,
sources=[pyxfilename, 'cfcn.c'],
libraries=[],
include_dirs=[np.get_include()],
)
def make_setup_args():
return dict()
Finally the integral is evaluated using the Python code
import vegas
# compile cfcn, if needed, at import
import pyximport
pyximport.install(inplace=True)
import cfcn
def main():
integ = vegas.Integrator(4 *[[0,1]])
print(integ(cfcn.f, neval=1e4, nitn=10).summary())
print(integ(cfcn.f, neval=1e4, nitn=10).summary())
if __name__ == '__main__':
main()
where, again, pyximport
guarantees that the cfcn
module
is compiled the first time the code is run.
This implementation is probably the fastest of those presented here. Cython also works with C++.
f2py
for Fortran¶
The f2py
package, which is distributed with numpy
,
makes it relatively easy to compile Fortran
code directly into Python modules. Consider a Fortran implementation of
integrand discussed above, stored in file ffcn.f
:
c file ffcn.f
c
function fcn(x, dim)
integer i, dim
real*8 x(dim), x2, fcn
x2 = 0.0
do i=1,dim
x2 = x2 + x(i) ** 2
end do
fcn = exp(-100. * sqrt(x2)) * 100. ** dim
return
end
This code is compiled into a Python module using
f2py -m ffcn -c ffcn.f
and the resulting module provides access to the integrand from Python:
import vegas
import ffcn
def main():
integ = vegas.Integrator(4 *[[0,1]])
print(integ(ffcn.fcn, neval=1e4, nitn=10).summary())
print(integ(ffcn.fcn, neval=1e4, nitn=10).summary())
if __name__ == '__main__':
main()
Again you can make the code somewhat faster by converting the integrand
into a batch integrand inside the Fortran module. Adding the following
function to the end of file ffcn.f
above :
c part 2 of file ffcn.f --- batch form of integrand
subroutine batch_fcn(ans, x, dim, nbatch)
integer dim, nbatch, i, j
real*8 x(nbatch, dim), xi(dim), ans(nbatch), fcn
cf2py intent(out) ans
do i=1,nbatch
do j=1,dim
xi(j) = x(i, j)
end do
ans(i) = fcn(xi, dim)
end do
end
results in a second Python function ffcn.batch_fcn(x)
that takes the
integration points x[i,d]
as input and returns an array of
integrand values ans[i]
. (The second Fortran comment tells f2py
that array ans
should be returned by the correponding Python
function; f2py
also has the function automatically deduce dim
and
nbatch
from the shape of x
.)
The correponding Python script for doing the integral
is then:
import vegas
import ffcn_f2py
import numpy as np
def main():
integ = vegas.Integrator(4 *[[0,1]])
batch_fcn = vegas.batchintegrand(ffcn_f2py.batch_fcn)
print(integ(batch_fcn, neval=1e4, nitn=10).summary())
print(integ(batch_fcn, neval=1e4, nitn=10).summary())
if __name__ == '__main__':
main()
This runs roughly twice as fast as the original
when neval
is large (eg, 1e6).
f2py
for C¶
f2py
can also be used to compile C code directly into Python
modules, but usually needs an interface file to specify how the
C is turned into Python. The interface file cfcn.pyf
for
the C file cfcn.c
(above) is:
python module cfcn
interface
real*8 function fcn(x, n)
intent(c) fcn ! fcn is a C function
intent(c) ! all fcn arguments are
! considered as C based
integer intent(hide), depend(x) :: n=len(x) ! n is the length
! of input array x
real*8 intent(in) :: x(n) ! x is input array
end function fcn
end interface
end python module cfcn
More information is available in the documentation for f2py
.
The module is created using
f2py -m cfcn -c cfcn.pyf cfcn.c
and the integral evaluated using Python code:
import vegas
import cfcn
def main():
integ = vegas.Integrator(4 *[[0,1]])
print(integ(cfcn.fcn, neval=1e4, nitn=10).summary())
print(integ(cfcn.fcn, neval=1e4, nitn=10).summary())
if __name__ == '__main__':
main()