Introduction

  • An overview of the basics of calling C from Julia
  • Based on my limited 'learning by going' experience from the Sparse Grid Integration package I use as an example today
  • Just scratching the surface...

Do I need C programming experience?

  • Not essential. Lots of online resources for both C and Julia for beginners.
  • Maybe only need to be able to small wrappers in C can make calling from Julia much easier.

C resources:

Julia docs:

Lots more information in the documentation that will likely prove useful to call C from Julia.

Why might you like to call C/Fortran from Julia?

  • Many mature, high-quality existing packages written in C (or Fortran)

  • The underlying package might be difficult/time consuming to (re)-code in Julia

  • C package may be very fast, well tested, few bugs etc.

Calling C libraries from Julia

  • Can call C functions directly from shared C libraries using ccall

  • Shared C libraries called by (:function, "library")

      :function is the exported C function name
      "library" is the name of the shared library

    If C library is available as a shared library only needs to be on system LOAD_PATH, Otherwise need to compile it and put the library on the LOAD_PATH. Will see in example later.

Using ccall

Easy to use

  • Using ccall is like an ordinary function call, can use in REPL
  • No 'glue' code needed
ccall( (:function, “library”), 
        Return Type (i.e. Int32),
        Tuple of Input Types. (i.e. (Int32, Int64, ...) ),
        Arguments to pass to C from Julia )

Note: ccall automatically converts Julia type to C type. See Julia documentation for a list of type correspondences.

Wrapping `ccall

In practice, can be useful to wrap ccall in a Julia function to:

  • Help re-usability
  • Setup arguments to pass to C
  • Check for errors from C and Fortran

Example: Sparse Grid Integration

A Julia package that wraps for John Burharkdt's C code to generate nodes and weights for sparse grid integration.

Overview of Sparse Grid Integration

Approximate a $D$-variate integral of the form

$$\int_{\Omega} g(x)f(x)dx$$

where $\Omega\in D$ and $f(x)$ is a weighting function. A quadrature rule prescribes a $D\times R$ matrix of nodes $X$ and a $R$-vector of weights $w$ depending on $\Omega$ and $f$. The approximation is calculated as

$$g(X)w$$

The built-in families of integration rules are identified by a 3-letter key:

  • Rules for the unweighted integration: $\Omega=\left[0,1\right]^D$, $f(x)=1$
    • GQU: Univariate Gaussian quadrature rules as basis
    • KPU: Univariate nested quadrature rules as basis - delayed Kronrod-Patterson rules, see Knut Petras (2003): "Smolyak cubature of given polynomial degree with few nodes for increasing dimension." Numerische Mathematik 93, 729-753.
  • Rules for the integration over $\Omega=\mathbb{R}^D$ with Gaussian weight $f(x)=\frac{\exp\left\{ -x^{2}\right\} }{\sqrt{2\pi}}$
    • GQN: Univariate Gaussian quadrature rules as basis
    • KPN: Univariate nested quadrature rules as basis, see A. Genz and B. D. Keister (1996): "Fully symmetric interpolatory rules for multiple integrals over infinite regions with Gaussian weight." Journal of Computational and Applied Mathematics 71, 299-309.

As a general rule, the versions with the nested univariate rules are more efficient in the sense that they require fewer nodes to achieve the same level of polynomial exactness.

Outline of rest of talk

  1. Adapting C library source code
  2. Compiling the C library and linking it to Julia
  3. Julia code to call/wrap modified C library
  4. Using the C-library in Julia
  5. Some tests/examples

1. Adapting C library source code and Julia

The C function I want to use, nwspgr, calls nested functions. See lines 3377-3428 in 'sparse_grids_hw.c'.

void nwspgr ( void rule ( int n, double x[], double w[] ), 
              int rule_order ( int l ), 
              int dim, int k, int r_size, int *s_size, double nodes[], double weights[] )

Problems: Nested C functions are not compatible with ccall (as far as i know!)

$\rightarrow$ Added 3 functions in C so can use Julia's ccall to run nwspgr See lines 6061-6492 in 'sparse_grids_hw.c' and the header file: 'sparse_grids_hw.h'.

   - int nwspgr_rule_size ( void rule ( int n, double x[], double w[] ), 
                                int rule_order ( int l ), int dim, int k, int r_size);
   - int nwspgr_rule_size_wrapper (int rule , int d, int k);
   - void nwspgr_wrapper (int rule , int d, int k, double x[], double w[]);
  • More on these below when discussing Julia part of the module
  • Minimal knowledge of C. Essentially, minor adaptions to existing C code.

Two steps:

  1. Compile the C-code to create C library
  2. Link library to Julia path

2.1. Compiling C Code

To use the code in Julia compile the C-code to create a dynamic library ready to be called by Julia. If using the 'sparse_grids_hw_jl.sh', it creates a dynamic library called 'sparselib.dylib' in a new folder, /lib.

2. 2. Put library on Julia Load Path

To enable Julia to find the newly created library, add the path where the library is stored to Julia's DL_LOAD_PATH. One way to do this is to add the line

        push!(Sys.DL_LOAD_PATH,"[Path to new library's folder]")

to ~/.juliarc.jl.

3. Calling in Julia

To output nodes and weights specify which integration rule to use, the number of dimensions, and the level accuracy of the integration rule. The constructor function is:

        nw = nwspgr( rule, dims, level )

where rule is one of {"GQU","KPU","GQN","KPN"} and nw is an instance of

type nwspgr
    rule    :: ASCIIString
    dims    :: Int64
    level   :: Int64
    nodes   :: Array{Float64,2}
    weights :: Vector{Float64}
end

Note: The maximum dimension supported by the code is 20.

3.1. Constructor function

Julia code to where the Julia nwspgr type is defined is contained in the file sparse_grids_hw.jl.

Recall that nwspgr in C-code was nested $\rightarrow$ added separate C functions to make compatible with Julia ccall syntax.

So, in the constructor function in Julia there are ccall's to these `ccall' compatible C functions.

  1. ccall for nwspgr_rule_size_wrapper to tell Julia the correct size of the Array needed to store the nodes and weights.

  2. ccall for nwspgr_wrapper is passed a pointer to correctly sized arrays created in Julia and fills them in with nodes and weights.

Note: ccall on passes a reference location in memory of array, not the array itself.

3. 2. Managing memory

Managing memory in non-standard ccall of C functions (i.e. nested functions)

  • General rule stated in Julia docs: The program that created the memory needs to clean it up once it is finished with it.

  • How to avoid problems? If passing Julia created array for C-code to fill in, make sure Julia array has correct size when array is initiated. Helps to avoid segmentation faults...

Note: Segmentation faults occur due to bad memory management. For example, can occur when a program tries to grab memory it thinks is free only to find there is something already there (or the reverse).

4. Tests/Examples

4.1. Unweighted integration

Integral to approximate:

$$\mathcal{I}=\int_{0}^{1}\cdots\int_{0}^{1}\underbrace{{\displaystyle \prod_{d=1}^{D}\frac{1}{\sqrt{2\pi\sigma^{2}}}e^{-\frac{1}{2}\left(\frac{x_{d}-\mu}{\sigma}\right)^{2}}}}_{g(x)}dx_{D}\cdots dx_{1}$$

where $x_{d}\sim N\left(\mu,\sigma\right)$.

Let $\mu=0$ and $\sigma=2$, then the "truth":

$$\mathcal{I} = \frac{1}{2} \operatorname{erf} \left(\frac{1}{ \sigma\sqrt{2}}\right)$$

because $\Phi(x) = \frac{1}{2}\left(1+\operatorname{erf}\left(\frac{x-\mu}{ \sigma\sqrt{2}}\right)\right)$

Link to code

Overview of test
  • Rule: GQU or KPU since $\Omega=\left[0,1\right]^{10}$, $f(x)=1$
  • Dimensions = 10
  • Vary accuracy: $k=\left\{2,3,4,5\right\}$
  • Compare to k-node MC integration

In [ ]:
include("./Git/SparseGridsHW/test/nwspgr_demo.jl")

4.2 MVRN integration

Integral to approximate:

$$\mathcal{I}=\int_{-\infty}^{\infty}\cdots\int_{-\infty}^{\infty}\mathcal{P}(x)f\left(x\right)dx_{D}\cdots dx_{1}$$

where $\mathcal{P}(x)=4x_{1}^{2}-3x_{2}^{3}+x_{3}^{9}-2x_{4}^{4}$ and $x\sim N\left(\mu,\Sigma\right)$.

Link to code

Overview of test
  • Rule: Normal -> GQN or KPN
  • Dimensions = 4
  • Vary accuracy: $k=\left\{2,3,4,5\right\}$
  • Compare to MC

In [ ]:
include("./Git/SparseGridsHW/test/nwspgr_mvrn.jl")