Integrands in C or Fortran — vegas 3.3.5 documentation

# 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()