Weicheng Zhu
2013.12.06
MinGW
InstallationA contraction of "Minimalist GNU for Windows", is a minimalist development environment for native Microsoft Windows applications.
MinGW
: http://www.mingw.org/
Download: http://sourceforge.net/projects/mingw/files/latest/download?source=files
Install: check to install Fortran GNU Compiler
where
is the equivalent of which
in Linux.
As shown in the following figure, there are two gfortran
executable file in my system path, the first one will be used by default.gfortran
Usagegfortran/tri-area.f90
, from: http://www.cs.mtu.edu/~shene/COURSES/cs201/NOTES/chap06/area-2.html## The simplest way, default output is `a.exe` gfortran tri-area.f90 ## -o: specify the output name gfortran tri-area.f90 -o TriArea
gfortran/fact-mod.f90
, gfortran/fact-prog.f90
, from http://www.cs.mtu.edu/~shene/COURSES/cs201/NOTES/chap06/fact-2.html## 1. Straight forward
gfortran fact-prog.f90 fact-mod.f90 -o fact
## 2. Step by step
gfortran -c fact-mod.f90
gfortran -c fact-prog.f90
gfortran fact-prog.o fact-prog.o -o fact
Note1: In the step by step method, a factorialmodule.mod
file is generated together with the fact-mod.o
file. The former file is associated with the latter one, so if you delete the mod file, the last step will fail.
Note2: If you want to be an advanced developer, you should learn how to use command line and avoid using IDE if possible.
Note3: There are also some other popular Fortran compilers in Windows, such as Absoft Pro Fortran
and Intel Fortran Compiler
, which are all proprietary software. Since R
uses gcc
to build package and R itself in Windows, we only introduce gfortran
compiler here.
The Basic Linear Algebra Subprograms (BLAS) define a set of fundamental operations on vectors and matrices which can be used to create optimized higher-level linear algebra functionality.
There are three levels of BLAS operations,
Quick Reference Guide to the BLAS: http://www.netlib.org/lapack/lug/node145.html
Reference Card: http://www.netlib.org/blas/blasqr.pdf
Homepage: http://www.netlib.org/blas/
Installation for Windows: http://icl.cs.utk.edu/lapack-for-windows/lapack/
Note: In this manual, we use the 'Prebuilt libraries'(check the 'Prebuilt dynamic libraries using Mingw' section described in Installation for Windows' above). Download the 32-bit dll files for both BLAS and LAPACK: libblas.dll
and liblapack.dll
. Of course, you can also build BLAS and LAPACK manually by yourself.
gfortran/BlasStandalone/vector_dot.f
and gfortran/BlasStandalone/matrix_sm.f
libblas.dll
to the BlasStandalone
folder. Then, in the CMD command line, input make
and enter, two executable files matrix_sm.exe
and vector_dot.exe
will be generated. Or you can input directly in the command line gfortran -o xxx.exe xxx.f -L. -lblas
, where the -L. -lblas
option means linking the libblas.dll
file in the current directory to the program.sdot
: http://www.netlib.org/blas/sdot.f; dtrsm
: http://www.netlib.org/blas/dtrsm.fNote: The gfortran
compiler installed by Rtools lacks of some component for compiling standalone fortran program with BLAS and LAPACK, so you need to install MinGW
first.
c vector_dot.f
c Example. Using BLAS Level 1 Routine
c vector-vector dot product
c Compute (1, 3, 5, 7, 9) * (10, 9, 8, 7, 6)
program dot_main
real x(10), y(10), sdot, res
integer n, incx, incy, i
n=5
incx=2
incy=1
do i = 1, 10
x(i) = i
y(i) = 11-i
end do
res = sdot(n, x, incx, y, incy)
print*,'SDOT =', res
end
c matrix_sm.f
c Example. Using BLAS Level 3 Routine
c matrix-matrix solve, solve(A)%*%B, where A is mxm, B is mxn
c [1, 2, 3] [1, 4]
c A = [2, 4, 5], B = [2, 5]
c [3, 5, 6] [3, 6]
c Function:
c DTRSM (SIDE, UPLO, TRANSA, DIAG, M, N, ALPHA, A, LDA, B, LDB)
program dot_main
double precision a(3,3), b(3,2), alpha
integer i, j, m, n, lda, ldb
character side, uplo, transa, diag
side='l'
uplo='u'
transa='n'
diag='n'
data A/1.0, 2.0, 3.0, 2.0, 4.0, 5.0, 3.0, 5.0, 6.0/
data B/1.0, 2.0, 3.0, 4.0, 5.0, 6.0/
alpha=1.0
m=3
n=2
lda=3
ldb=3
write(*,*) "A is:"
do i=1, m
print*, (A(i,j), j=1,m)
enddo
write(*,*) "B is:"
do i=1, m
print*, (B(i,j), j=1,n)
enddo
call dtrsm(side, uplo, transa, diag, m, n, alpha, a, lda, b, ldb)
write(*,*) "After calling 'dtrsm', B is:"
do i=1, m
print*, (B(i,j), j=1,n)
enddo
end
LAPACK is written in Fortran 90 and provides routines for solving systems of simultaneous linear equations, least-squares solutions of linear systems of equations, eigenvalue problems, and singular value problems. The associated matrix factorizations (LU, Cholesky, QR, SVD, Schur, generalized Schur) are also provided, as are related computations such as reordering of the Schur factorizations and estimating condition numbers. Dense and banded matrices are handled, but not general sparse matrices. In all areas, similar functionality is provided for real and complex matrices, in both single and double precision.
Note: Check the Note information in BLAS section.
gfortran/LapackStandalone/linear_equ.f
libblas.dll
and liblapack.dll
to the R_Fortran/05_Lapack/LapackStandalone
folder. In the CMD command line input make
or directly input gfortran linear_equ.f -L. -llapack -o linear_equ.exe
in the command line. The -L. -llapack
option means link the liblapack.dll
file in the current directory to the program. liblapack.dll
depends on libblas.dll
, so libblas.dll
is also necessary.SGESV
: http://www.netlib.org/lapack/single/sgesv.fc linear_equ.f
c Example of solving linear equations
c solving the matrix equation A*x=b using LAPACK
c From: http://www.physics.orst.edu/~rubin/nacphy/lapack/codes/linear-f.html
Program LinearEquations
Implicit none
c declarations, notice single precision
Real*4 A(3,3), b(3)
integer i, j, pivot(3), ok
c define matrix A
data A/3.1, 1.0, 3.4, 1.3, -6.9, 7.2, -5.7, 5.8, -8.8/
c define vector b, make b a matrix and you can solve multiple
c equations with the same A but different b
data b/-1.3, -0.1, 1.8/
c find the solution using the LAPACK routine SGESV
call SGESV(3, 1, A, 3, pivot, b, 3, ok)
c parameters in the order as they appear in the function call
c order of matrix A, number of right hand sides (b), matrix A,
c leading dimension of A, array that records pivoting,
c result vector b on entry, x on exit, leading dimension of b
c return value
c
c print the vector x
do i=1, 3
write(*,*) b(i)
end do
end
ATLAS stands for Automatically Tuned Linear Algebra Software. ATLAS is both a research project and a software package. ATLAS's purpose is to provide portably optimal linear algebra software. The current version provides a complete BLAS API (for both C and Fortran77), and a very small subset of the LAPACK API. For all supported operations, ATLAS achieves performance on par with machine-specific tuned libraries.
Rblas.dll
file frome http://cran.r-project.org/bin/windows/contrib/ATLAS/Rblas.dll
file in the R-x.x.x\bin\x32
directory.When to use? When a task requires explicit loops that can't be vectorized away. If a task can be vectorized or has no more than one loop, using R only would be a better choice, since in such situation, converting R code to C or Fortran doesn't improve much implementation efficient.
Tools?
In linux system, if R has been installed, the developing environment is already prepared. In MS Windows system, after installing R, you also need to install the Rtools
.
Rtools
The standard toolchain for building R packages under Microsoft Windows, or for building R itself. It contains the command line tools, MinGW compilers, etc.
path\to\Rtools\bin;path\to\Rtools\gcc-x.x.x\bin;
to the system PATH
variableRtools.txt
and README.txt
file in the main Rtools directory.xxx(arg1, arg2)
, and saving it in the foo.f
file..dll
/.so
) with R CMD SHLIB foo.f
command.foo.dll
/foo.so
to R with dyn.load('foo.dll')
foo.f
with .Fortran
function: .Fortran('xxx', arg1, arg2)
.Fortran
function, argument types should be matched by explicit coersions with as.integer
or as.double
or by creating vectors of an explicit storage mode and length using the constructors integer(n)
and double(n)
..Fortran
is a list with the same number of components (and names, if given) as the argument list.Comparison of R and Fortran data types
R storage mode | FORTRAN type |
---|---|
logical | INTEGER |
integer | INTEGER |
double | DOUBLE PRECISION |
complex | DOUBLE COMPLEX |
character | CHARACTER*255 |
raw unsigned | none |
This is the canonical example introduced in Writting R Extensions.
The convolution of two sequences \(x_1, ..., x_n\) and \(y_1, ...y_m\) is defined as: \(z_k = \sum_{i+j=k}x_iy_j\)
The Fortran part
c A subroutine to convole two finite sequences
subroutine convolvef(a, na, b, nb, ab)
integer na, nb, nab, i, j
double precision a(na), b(nb), ab(na+nb-1)
nab = na + nb - 1
do 10 i = 1, nab
ab(i) = 0.0
10 continue
do 20 i = 1, na
do 30 j = 1, nb
ab(i+j-1) = ab(i+j) + a(i)*b(j)
30 continue
20 continue
end
The R part
# Load Shared Objects (*.so) / Dynamic-Link Library (*.DLL) # dyn.load("01_convolve/convolve.so") # Linux # dyn.load("01_convolve/convolve.dll") # Windows dyn.load(file.path("R_Fortran", "01_Convolve", paste0("convolve", .Platform$dynlib.ext))) # Platform independence # Check whether Fortran subroutine `convolvef` is available is.loaded("convolvef")
## [1] TRUE
x=1:5 y=6:10 # .Fortran("convolvef", as.double(x), as.integer(length(x)), as.double(y), as.integer(length(y)), double(length(x) + length(y) - 1))
## [[1]] ## [1] 1 2 3 4 5 ## ## [[2]] ## [1] 5 ## ## [[3]] ## [1] 6 7 8 9 10 ## ## [[4]] ## [1] 5 ## ## [[5]] ## [1] 6 19 40 70 110 114 106 85 50
.Fortran("convolvef", as.double(x), as.integer(length(x)), as.double(y), as.integer(length(y)), xy = double(length(x) + length(y) - 1))$xy
## [1] 6 19 40 70 110 114 106 85 50
# R wrapper conv_f <- function(x, y){ if(!(is.numeric(x) && is.numeric(y))) stop("x and y should be both numeric vector") .Fortran("convolvef", as.double(x), as.integer(length(x)), as.double(y), as.integer(length(y)), double(length(x) + length(y) - 1)) } conv_f(1:5, 6:10)
## [[1]] ## [1] 1 2 3 4 5 ## ## [[2]] ## [1] 5 ## ## [[3]] ## [1] 6 7 8 9 10 ## ## [[4]] ## [1] 5 ## ## [[5]] ## [1] 6 19 40 70 110 114 106 85 50
conv_f(1:5, 6:7)
## [[1]] ## [1] 1 2 3 4 5 ## ## [[2]] ## [1] 5 ## ## [[3]] ## [1] 6 7 ## ## [[4]] ## [1] 2 ## ## [[5]] ## [1] 6 19 32 45 58 35
dyn.unload(file.path("R_Fortran", "01_Convolve", paste0("convolve", .Platform$dynlib.ext))) # Platform independence
We'll study more details about the .Fortran
interface though this example.
The Fortran part
c swap two integer number
subroutine ISWAPF (a, b)
integer a, b, tmp
tmp = a
a = b
b = tmp
end
c swap two double number
subroutine DSWAPF (a, b)
double precision a, b, tmp
tmp = a
a = b
b = tmp
end
c Subroutine Reverse:
c reverses the order of the input array
subroutine Reversef(a, n)
integer n, i
double precision a(n)
do 10 i = 1, INT(n/2)
call DSWAPF(a(i), a(n - i + 1))
10 continue
end
The R part
# ?.Fortran # All Fortran compilers known to be usable to compile R map # symbol names to lower case. Symbol names containing underscores are # not valid Fortran 77 (although they are valid in Fortran # 9x). Portable code should not use Fortran names containing underscores. # `gfortran` transforms names of entities specified in the Fortran source file by appending underscores to them. dyn.load(file.path("R_Fortran", "02_Swap", paste0("swap", .Platform$dynlib.ext))) is.loaded("iswapf")
## [1] TRUE
x=10 y=20 # a and b are a copy of x and y, x and y doesn't change # a and b are called by reference .Fortran("iswapf", a=as.integer(x), b=as.integer(y))
## $a ## [1] 20 ## ## $b ## [1] 10
x; y
## [1] 20
# The arguments are not meaningful .Fortran("iswapf", p=as.integer(x), q=as.integer(y))
## $p ## [1] 20 ## ## $q ## [1] 10
# With 'DUP = FALSE', your compiled code can alter the local variable # DEP=FALSE can speed up the program by avoiding pssing hudge matrix # Oops..., didn't swap! Why? .Fortran("iswapf", as.integer(x), as.integer(y), DUP=FALSE)
## [[1]] ## [1] 20 ## ## [[2]] ## [1] 10
x; y
## [1] 20
# Use `dswapf`. Wow! .Fortran("dswapf", as.double(x), as.double(y), DUP=FALSE)
## [[1]] ## [1] 20 ## ## [[2]] ## [1] 10
x; y
## [1] 10
# Mhm... is.double(x)
## [1] TRUE
x=as.integer(10) y=as.integer(20) .Fortran("iswapf", x, y, DUP=FALSE)
## [[1]] ## [1] 20 ## ## [[2]] ## [1] 10
x; y
## [1] 10
# Reverse a vector # x, y are vectors of lenth n, exchange x[i] and y[i] if x[i] is bigger than y[i], i=1,...,n # ivswapf subroutine invokes the iswapf subroutine # Again, what's the problem here? x=1:10 if(is.loaded("reversef")) .Fortran("reversef", as.double(x), as.integer(length(x)), DUP=FALSE)
## [[1]] ## [1] 10 9 8 7 6 5 4 3 2 1 ## ## [[2]] ## [1] 10
# So, always be careful about the dat type is.integer(x)
## [1] TRUE
x=as.double(1:10) if(is.loaded("reversef")) .Fortran("reversef", x, as.integer(length(x)), DUP=FALSE)
## [[1]] ## [1] 10 9 8 7 6 5 4 3 2 1 ## ## [[2]] ## [1] 10
x
## [1] 10 9 8 7 6 5 4 3 2 1
dyn.unload(file.path("R_Fortran", "02_Swap", paste0("swap", .Platform$dynlib.ext)))
Concepts & Conclusions
symbol name
vs transformed symbol name
DUP=FALSE
An example of using Fortran90 to calculate the summation and multiplication of matrix.
Intrinsic functions in Fortran 90: http://www.nsc.liu.se/~boein/f77to90/a5.html
The Fortran part
! subroutine of matrix summation with fortran90
subroutine arraysumf90(a, b, c, nrow, ncol)
implicit none
integer nrow, ncol, i, j
double precision a(nrow, ncol), b(nrow, ncol), c(nrow, ncol)
c = a + b
end subroutine arraysumf90
! subroutine of matrix inner product with fortran90
subroutine arraymulf90(a, b, c, nrow, ncol)
implicit none
integer nrow, ncol, i, j
double precision a(nrow, ncol), b(nrow, ncol), c(nrow, ncol)
c = matmul(a, b)
end subroutine arraymulf90
The R part
# Fortran90 x=y=z=matrix(1:9, 3) dyn.load(file.path("R_Fortran", "03_Array", paste0("array90", .Platform$dynlib.ext))) # matrix summation is.loaded("arraysumf90")
## [1] TRUE
.Fortran("arraysumf90", as.double(x), as.double(y), as.double(z), as.integer(nrow(x)), as.integer(ncol(x)))[[3]]
## [1] 2 4 6 8 10 12 14 16 18
# matrix multiplication is.loaded("arraymulf90")
## [1] TRUE
.Fortran("arraymulf90", as.double(x), as.double(y), as.double(z), as.integer(nrow(x)), as.integer(ncol(x)))[[3]]
## [1] 30 36 42 66 81 96 102 126 150
x%*%y
## [,1] [,2] [,3] ## [1,] 30 66 102 ## [2,] 36 81 126 ## [3,] 42 96 150
dyn.unload(file.path("R_Fortran", "03_Array", paste0("array90", .Platform$dynlib.ext)))
Working principle
R uses the BLAS and LAPACK libraries to do basic linear algebra calculations, and the BLAS and LAPACK is used via Dynamic Link Library. On Windows, these DLL files, named Rblas.dll
and Rlapack.dll
, maybe exist in the bin
folder under R home directory, such as R-x.xx/bin/x64
.
Fortran can use R's BLAS and Lapack library directly by linking to the DLLs, so you don't need to install BLAS and Lapack seperately on your system. To link your Fortran code to the DLLs, you need to use the -lblas
and -llapack
option whlie compiling your code to DLLs. Usually it would be convenient to write a Makevars
file with the content:
PKG_LIBS = $(LAPACK_LIBS) $(BLAS_LIBS)
Then when you run the R CMD SHLIB
command, R can link your code to BLAS and Lapack properly.
In this example, we'll make a fortran subroutine called idamaxf
by using the IDAMAX
function in BLAS library to calculate the first occurrence of the maximum absolute value of a double precision vector. Then compile it to DLL and invoke it in R.
The Fortran part
c IDAMAX searches a double precision vector for the first occurrence
c of the the maximum absolute value.
c Arguments:
c n: Number of elements to process in the vector to be searched.
c x: Array of dimension (n-1) * |incx| + 1.
c incx: Increment between elements of x.
subroutine idamaxf(n, x, incx, y)
integer n, incx, y, idamax
double precision x(*)
y = idamax(n, x, incx)
return
end
The R part
dyn.load(file.path("R_Fortran", "04_Blas", paste0("blas_idamax", .Platform$dynlib.ext))) # Test is.loaded("idamaxf")
## [1] TRUE
n = 10 # PROBLEM: when incx < 0, return value is always 0! incx = 1 x = sample((n-1)*abs(incx) + 1) x
## [1] 4 7 10 1 8 5 3 6 2 9
.Fortran("idamaxf", as.integer(n), as.double(x), as.integer(incx), as.integer(1))[[4]]
## [1] 3
# R wrapper idamax <- function(x, n, by=1){ if(length(x) < ((n-1)*abs(by) +1)) stop("x is too short!") .Fortran("idamaxf", as.integer(n), as.double(x), as.integer(by), as.integer(1))[[4]] } (x=sample(10))
## [1] 1 4 2 5 9 3 10 8 6 7
# which(x[1:5]==max(x[1:5])) idamax(x, 5)
## [1] 5
# which(x[seq(1,10,2)]==max(x[seq(1,10,2)])) idamax(x, 5, by=2)
## [1] 4
dyn.unload(file.path("R_Fortran", "04_Blas", paste0("blas_idamax", .Platform$dynlib.ext)))
Working principle is the same with BLAS introduced in previous section.
In this example, we'll make a subroutine called Lineareq
to solve the equation \(Ax=b\), where \(A\) is a \(nxn\) matrix and \(b\) is a vector of length \(n\), by using the DGESV
subroutine in LAPACK library.
The Fortran part
c Example of solving linear equations
c From: http://www.physics.orst.edu/~rubin/nacphy/lapack/codes/linear-f.html
c SUBROUTINE: DGESV( N, NRHS, A, LDA, IPIV, B, LDB, INFO )
c http://www.netlib.org/lapack/single/sgesv.f
c Solving the matrix equation A*x=b using LAPACK
Subroutine Lineareq(A, b, n, info)
Implicit none
integer n, info
integer ipiv(n)
double precision A(n, n), b(n)
call DGESV(n, 1, A, n, ipiv, b, n, info)
end
The R part
# Solving the matrix equation A*x=b using LAPACK dyn.load(file.path("R_Fortran", "05_Lapack", paste0("linear_equ", .Platform$dynlib.ext))) is.loaded("lineareq")
## [1] TRUE
A = matrix(c(3.1, 1.3, -5.7, 1.0, -6.9, 5.8, 3.4, 7.2, -8.8), 3, byrow=TRUE) b = c(-1.3, -0.1, 1.8) solve(A)%*%b
## [,1] ## [1,] 1 ## [2,] 1 ## [3,] 1
.Fortran("lineareq", A=as.double(A), b=as.double(b), n=as.integer(nrow(A)), info=as.integer(1))
## $A ## [1] 3.4000 0.2941 0.9118 7.2000 -9.0176 0.5838 -8.8000 8.3882 -2.5737 ## ## $b ## [1] 1 1 1 ## ## $n ## [1] 3 ## ## $info ## [1] 0
# R wrapper lineareq <- function(A, b){ if(!is.matrix(A)) stop("A must be a matrix") if(nrow(A) != ncol(A)) stop("A must be a symmetic matrix") if(nrow(A) != length(b)) stop("The row number of matrix 'A' is different with the length of vector 'b'") res = .Fortran("lineareq", A=as.double(A), b=as.double(b), n=as.integer(nrow(A)), info=as.integer(1)) if(res$info < 0) stop("Arguments error!") else if(res$info > 0) stop("Singular!") else res$b } lineareq(A, b)
## [1] 1 1 1
dyn.unload(file.path("R_Fortran", "05_Lapack", paste0("linear_equ", .Platform$dynlib.ext)))
There are a large number of entry points in the R executable/DLL that can be called from C code (and some that can be called from FORTRAN code)
Check Writting R Extension Chapter 6 for more information
The Fortran part
normal distribution pdf (version 1)
c dnorm defined in R: dnorm(x, mean = 0, sd = 1, log = FALSE)
subroutine dnormf(x, mean, sd, log, re)
double precision x, mean, sd, re
integer log
call dnormwrap(x, mean, sd, log, re)
return
end
The corresponding C wrapper
#include <R.h>
#include <Rmath.h>
// DO NOT USE `dnorm` as the wrap function name.
void F77_SUB(dnormwrap)(double *x, double *mean, double *sd, int *log, double *re)
{
*re = Rf_dnorm4(*x, *mean, *sd, *log);
}
normal distribution pdf (version 2)
c dnorm defined in R: dnorm(x, mean = 0, sd = 1, log = FALSE)
subroutine dnormf1(x, mean, sd, log, re)
double precision x, mean, sd, re, dnormwrap
integer log
re = dnormwrap(x, mean, sd, log)
return
end
The corresponding C wrapper
#include <R.h>
#include <Rmath.h>
// DO NOT USE `dnorm` as the wrap function name, since it is a kind of key words
double F77_SUB(dnormwrap)(double *x, double *mean, double *sd, int *log)
{
return Rf_dnorm4(*x, *mean, *sd, *log);
}
The R part
# fortran using R's dnorm function # C wrapper version 1: void type, no return value dyn.load(file.path("R_Fortran", "06_Rapi", "dnorm", paste0("dnorm", .Platform$dynlib.ext))) is.loaded("dnormf")
## [1] TRUE
.Fortran("dnormf", as.double(0), as.double(0), as.double(1), as.integer(0), as.double(0))[[5]]
## [1] 0.3989
dyn.unload(file.path("R_Fortran", "06_Rapi", "dnorm", paste0("dnorm", .Platform$dynlib.ext))) # C wrapper version 2: double type, has return value dyn.load(file.path("R_Fortran", "06_Rapi", "dnorm", paste0("dnorm1", .Platform$dynlib.ext))) is.loaded("dnormf1")
## [1] TRUE
.Fortran("dnormf1", as.double(0), as.double(0), as.double(1), as.integer(0), as.double(0))[[5]]
## [1] 0.3989
dyn.unload(file.path("R_Fortran", "06_Rapi", "dnorm", paste0("dnorm1", .Platform$dynlib.ext)))
The Fortran part
random number generation
c A subroutine generating normal & uniform random numbers
subroutine nurnd(x, y)
real*8 normrnd, unifrnd, x, y
call rndstart()
x = normrnd()
y = unifrnd()
call rndend()
return
end
The corresponding C wrapper
#include <R.h>
#include <Rmath.h>
void F77_SUB(rndstart)(void) { GetRNGstate(); }
void F77_SUB(rndend)(void) { PutRNGstate(); }
double F77_SUB(normrnd)(void) { return rnorm(0, 1); }
double F77_SUB(unifrnd)(void) { return runif(0, 1); }
The R part
# fortran using R's random number generation dyn.load(file.path("R_Fortran", "06_Rapi", "random_number", paste0("random", .Platform$dynlib.ext))) is.loaded("nurnd")
## [1] TRUE
.Fortran("nurnd", as.double(1), as.double(1))
## [[1]] ## [1] 2.059 ## ## [[2]] ## [1] 0.286
dyn.unload(file.path("R_Fortran", "06_Rapi", "random_number", paste0("random", .Platform$dynlib.ext)))
Why?
There are two interfaces for C, .C
and .Call
void
type C functions, say xxx(arg1, arg2)
, and saving it in the foo.c
file. .dll
/.so
) with R CMD SHLIB foo.c
command.foo.dll
/foo.so
to R with dyn.load('foo.dll')
foo.c
with .C
function: .C('xxx', arg1, arg2)
void
type and all values should be returned via arguments..C
function, argument types should be matched by explicit coersions with as.integer
or as.double
or by creating vectors of an explicit storage mode and length using the constructors integer(n)
and double(n)
..C
is a list with the same number of components (and names, if given) as the argument list.Comparison of R and C data types
R storage mode | C type |
---|---|
logical | int * |
integer | int * |
double | double * |
complex | Rcomplex * |
character | char ** |
raw | unsigned char * |
The C part
void convolveC(double *a, int *na, double *b, int *nb, double *ab)
{
int i, j, nab = *na + *nb - 1;
for(i = 0; i < nab; i++)
ab[i] = 0.0;
for( i = 0; i < *na; i++)
for(j = 0; j < *nb; j++)
ab[i + j] += a[i]*b[j];
}
dyn.load(file.path("R_C", "01_convolve", paste0("convolveC", .Platform$dynlib.ext))) is.loaded("convolveC")
## [1] TRUE
x=1:5 y=6:10 .C("convolveC", as.double(x), as.integer(length(x)), as.double(y), as.integer(length(y)), double(length(x) + length(y) -1))
## [[1]] ## [1] 1 2 3 4 5 ## ## [[2]] ## [1] 5 ## ## [[3]] ## [1] 6 7 8 9 10 ## ## [[4]] ## [1] 5 ## ## [[5]] ## [1] 6 19 40 70 110 114 106 85 50
# R wrapper conv_C <- function(x, y){ if(!(is.numeric(x) && is.numeric(y))) stop("x and y should be both numeric vector") .C("convolveC", as.double(x), as.integer(length(x)), as.double(y), as.integer(length(y)), double(length(x) + length(y) - 1))[[5]] } conv_C(1:5, 6:10)
## [1] 6 19 40 70 110 114 106 85 50
conv_C(1:5, 6:7)
## [1] 6 19 32 45 58 35
dyn.unload(file.path("R_C", "01_convolve", paste0("convolveC", .Platform$dynlib.ext)))
Features
SEXP FunctionName(SEXP arg1, SEXP arg2, ...)
PROTECT
to keep them from being cleaned by R as garbage.The C part
#include <Rinternals.h>
SEXP convolveCall(SEXP a, SEXP b)
{
int na, nb, nab;
double *xa, *xb, *xab;
SEXP ab;
a = PROTECT(coerceVector(a, REALSXP));
b = PROTECT(coerceVector(b, REALSXP));
na = length(a); nb = length(b); nab = na + nb - 1;
ab = PROTECT(allocVector(REALSXP, nab));
xa = REAL(a); xb = REAL(b); xab = REAL(ab);
for(int i = 0; i < nab; i++) xab[i] = 0.0;
for(int i = 0; i < na; i++)
for(int j = 0; j < nb; j++) xab[i + j] += xa[i] * xb[j];
UNPROTECT(3);
return ab;
}
dyn.load(file.path("R_C", "01_convolve", paste0("convolveCall", .Platform$dynlib.ext))) is.loaded("convolveCall")
## [1] TRUE
x=1:5 y=6:10 .Call("convolveCall", x, y)
## [1] 6 19 40 70 110 114 106 85 50
dyn.unload(file.path("R_C", "01_convolve", paste0("convolveCall", .Platform$dynlib.ext)))
We use the convolve
example to compare the speed of all the R interfaces.
## Compare the speed of C and Fortran interfaces with R dyn.load(file.path("R_Fortran", "01_convolve", paste0("convolve", .Platform$dynlib.ext))) # Platform independence dyn.load(file.path("R_C", "01_convolve", paste0("convolveC", .Platform$dynlib.ext))) dyn.load(file.path("R_C", "01_convolve", paste0("convolveCall", .Platform$dynlib.ext))) convolveR <- function(x, y){ nx <- length(x) ny <- length(y) z <- double(nx + ny -1) for(i in 1:nx) for (j in 1:ny) z[i + j - 1] = z[i + j-1] + x[i]*y[j] z } convolveF <- function(x, nx, y, ny){ .Fortran("convolvef", as.double(x), as.integer(nx), as.double(y), as.integer(ny), double(nx + ny - 1)) } convolveC <- function(x, nx, y, ny){ .C("convolveC", as.double(x), as.integer(nx), as.double(y), as.integer(ny), double(nx + ny - 1)) } convolveCall <- function(x, y){ .Call("convolveCall", x, y) } library(rbenchmark) set.seed(1204) x = rnorm(1000); y = rnorm(1000) nx = length(x); ny = length(y) benchmark(convolveF = convolveF(x, nx, y, ny), convolveC = convolveC(x, nx, y ,ny), convolveCall = convolveCall(x, y), replications=1000, columns=c('test', 'replications', 'elapsed', 'relative'))
## test replications elapsed relative ## 2 convolveC 1000 1.02 1.500 ## 3 convolveCall 1000 0.98 1.441 ## 1 convolveF 1000 0.68 1.000
system.time(convolveR(x, y))
## user system elapsed ## 4.2 0.0 4.2
dyn.unload(file.path("R_Fortran", "01_convolve", paste0("convolve", .Platform$dynlib.ext))) # Platform independence dyn.unload(file.path("R_C", "01_convolve", paste0("convolveC", .Platform$dynlib.ext))) dyn.unload(file.path("R_C", "01_convolve", paste0("convolveCall", .Platform$dynlib.ext)))
Selected Sources:
library/Rcpp/doc/
Fibonacci example
## basic R function fibR <- function(n) { if (n == 0) return(0) if (n == 1) return(1) return (fibR(n - 1) + fibR(n - 2)) } library(Rcpp) library(inline) ## we need a pure C/C++ function here incltxt <- ' int fibonacci(const int x) { if (x == 0) return(0); if (x == 1) return(1); return (fibonacci(x - 1)) + fibonacci(x - 2); }' ## Rcpp version of Fibonacci fibRcpp <- cxxfunction(signature(xs="int"), plugin="Rcpp", incl=incltxt, body=' int x = Rcpp::as(xs); return Rcpp::wrap( fibonacci(x) ); ')
## cygwin warning: ## MS-DOS style path detected: D:/PROGRA~1/R-30~1.2/etc/x64/Makeconf ## Preferred POSIX equivalent is: /cygdrive/d/PROGRA~1/R-30~1.2/etc/x64/Makeconf ## CYGWIN environment variable option "nodosfilewarning" turns off this warning. ## Consult the user's guide for more details about POSIX paths: ## http://cygwin.com/cygwin-ug-net/using.html#using-pathnames
library(rbenchmark) N = 20 res <- benchmark(fibR(N), fibRcpp(N), columns=c("test", "replications", "elapsed", "relative", "user.self", "sys.self"), order="relative", replications=100) res
## test replications elapsed relative user.self sys.self ## 1 fibR(N) 100 5.96 NA 5.96 0 ## 2 fibRcpp(N) 100 0.00 NA 0.00 0
Armadillo is a C++ linear algebra library (matrix maths) aiming towards a good balance between speed and ease of use.
API: http://arma.sourceforge.net/docs.html
Rtools
See the R foreign language interfaces/Rtools
section for details.
MiKTex
(optional)A LaTeX and pdftex package used to build .pdf forms of documentation.
pdflatex --version
in the command line to test whether MiKTeX is installed successfully.The simplest package structure is:
pkg (the package name)
|
|--DESCRIPTION (contains basic information about the package)
|--NAMESPACE (controls which variables to export, import, etc.)
|--R (R source files)
|--function1.R
|--function2.R
|--...
|--man (help documents)
|--function1.Rd
|--function2.Rd
|--...
|--...
Optional subdirectories are data
, demo
, exec
, inst
, po
, src
, tests
, tools
and vignettes
.
For more information, read 1.1 Package structure of the Writting R extensions
In the traditional way of making package, you need to write R functions (say myfun
) first and then add the correponding help files (myfun.Rd
) to the man
directory. However, it is an error-prone process. Here I'll introduce a more advanced process using Roxygen2
package.
As an example, we'll make a package called MyPkg
here.
Mypkg
Make a file called DESCRIPTION
, with the following content:
Package: MyPkg
Type: Package
Title: A package demo
Version: 1.0
Date: 2013-12-06
Author: Weicheng Zhu
Maintainer: Weicheng <weicheng@dreamhunter.me>
Description: More about what it does (maybe more than one line)
License: GPL
Make a folder named R
under Mypkg
Make a file named myFun.R
under Mypkg
Add the convolvR
function to myFun.R
:
##' A description of this function ##' ##' Details about this function ##' @title Convolution of two vector ##' @param x numeric vector ##' @param y numeric vector ##' @return numeric vector of length (nx+ny-1) ##' @author weicheng ##' @export convolveR <- function(x, y){ nx <- length(x) ny <- length(y) z <- double(nx + ny -1) for(i in 1:nx) for (j in 1:ny) z[i + j - 1] = z[i + j-1] + x[i]*y[j] z }
Now, your directory structure would be like this:
MyPkg
|--DESCRIPTION
|--R
|--myFun.R
Run R
and set the working direcotry to the parent direcotry of MyPkg
, then run the following command:
library(roxygen2) roxygenize("MyPkg")
After running this commands, the directory structure of the MyPkg
would be like this:
MyPkg
|--DESCRIPTION
|--man
|--convolveR.Rd
|--NAMESPACE
|--R
|--myFun.R
In the system command line, go the parent directory of MyPkg
and run R CMD build MyPkg
, then a file named MyPkg_1.0.tar.gz
will be made. Then run R CMD check MyPkg_1.0.tar.gz
, if no error occurs this R package is made successfully and can be installed with the command R CMD INSTALL MyPkg_1.0.tar.gz
.
Test MyPkg
in R:
library(MyPkg) help(package="MyPkg") convolveR(1:5,1:5)
Rcpp and RcppArmadillo both provide a package skeleton function, which makes building R packages more easier.
library(Rcpp) Rcpp.package.skeleton()
library(RcppArmadillo) RcppArmadillo.package.skeleton