A C-based Prolog interface to R.
This library enables the communication with an R process started as a shared library. It is the result of the efforts of two research groups that have worked in parallel. The syntactic emphasis on a minimalistic interface. A single predicate (<-/2,<-/1) channels the bulk of the interactions. In addition to using R as a shared library, r..eal uses the c-interfaces of Yap and R to pass objects in both directions. The usual mode of operation is to load Prolog values on to R variables and then call an R function on these values. The return value of the called function can be either placed on R variable or passed back to Prolog. It has been tested extensively on YAP 6.3.0 on Linux machines but it should also compile and work on MS operating systems and Macs. We hope it will soon be ported to the SWI engine.
The main modes for utilising the interface are
<- +Rexpr <- +Rvar
Print Rvar or evaluate expression, Rexpr, in R
+PLvar <- +Rexpr +Rexpr1 <- +Rexpr2 +Rvar <- +PLval +PLvar <- +Rvar
Evaluate Rexpr and pass result to PLvar.
Pass Rexpr1 <- Rexpr2 to R.
Pass PLval to Rvar (using the c-interface when Rvar is numeric, list or list of 2 levels). See data transfer below.
Assign contents of Rvar to PLvar using the c-interface.
There are syntactic conventions in R that make unparsable prolog code. Notably function and variable names are allowed to contain dots, square brackets are used to access parts of vectors and arrays and functions are allowed empty argument tuples. We have introduced relevant syntax which allows for easy transition between prolog and R. Prolog constructs are converted by the library as follows:
..
within atoms -> .
(ex. as..integer(c(1,2,3)) -> as.integer(c(1,2,3))
)^[]
after atoms -> []
(ex. a^[2] -> a[2]
)(.)
at the end of atoms that are known R functions -> ()
(ex. dev..off(.) -> dev.off()
)[]
-> c() (which equal to R's NULL value)Following R convension we will talk about the type of and the class of data. i, defined by i <- as.integer(c(1,2,3)), is of class integer vector and type integer. R vectors are mapped to prolog lists and matrices are mapped to nested lists. The convention works the other way around too.
There are two ways to pass prolog data to R. The more efficient one is by using
Rvar <- PLval
We will call this the low-level interface. It is implemented as C code and transfers via C data between R and Prolog. In what follows atomic PLval data are simply considered as singleton lists. Flat Plval lists are translated to R vectors and lists of one level of nesting to R matrices (which are 2 dimensional arrays in R parlance). The type of values of the vector or matrice is taken to be the type of the first data element of the Plval according to the following :
Booleans are represented in prolog as true/false atoms. Currently arrays of aribtrary dimensions are not supported in the low-level interface. Note that in R a scalar is just a one element vector. When passing Prolog objects to R, the user can specify the type of the R object by using the (:) operator (see real_type/1). For example:
r <- float:[1,2,3]. <- r. % don't be deceived : R <- r. i <- int:[1,2,3]. <- i. I <- i.
In addition Prolog data can be passed through the expression mechanism. That is, data appearing in an arbitrary R expression will be parsed and be part of the long string that will be passed from Prolog to R for evaluation. This is only advisable for short data structures. For instance,
tut_4a :- state <- c("tas", "sa", "qld", "nsw", "nsw"), <- state.
Through this interface it is more convenient to represent R chars by Prolog list of codes, as in the above example.
?- e <- numeric. yes ?- e^[3] <- 17. yes ?- Z <- e. Z = ['$NaN','$NaN',17.0] ?- e^[10] <- 12. yes ?- Z <- e. Z = ['$NaN','$NaN',17.0,'$NaN','$NaN','$NaN','$NaN','$NaN','$NaN',12.0] int :- i <- [1,2,3,4], I <- i, write( i(I) ), nl. bool_back :- t <- [1,2,3,4,5,1], s <- t==1, S <- s, write( s(S) ), nl. list :- a <- [x=1,y=0,z=3], A <- a, write( 'A'(A) ), nl. matrix :- a <- [[1,2,3],[4,5,6]], A <- a. expr1 :- a <- [[1,2,3],[4,5,6]], B <- a*3. expr2 :- a <- [[1,2,3],[4,5,6]], b <- 3, C <- a*b. use_module( library(apply_macros), [maplist/3] ). between_1_and( N, Bs ) :- findall( B, between(1,N,B), Bs ). :- use_module( library(apply_macros), [maplist/3] ). plot_cpu :- points <- [10,100,500,1000], points <- 'as.integer'(points * 10), <- points, Points <- points, write( points(Points) ), nl, findall( T, (member(P,Points),between_1_and(P,Long), write( '.' ), flush_output, statistics( cputime, _) , long <- Long, statistics( cputime, [_,SCpu] ), _Back <- long, statistics( cputime, [_,TCpu] ), T = SCpu-TCpu ), Ts ), write( ts(Ts) ), nl, maplist( arg(1), Ts, Push ), write( push(Push) ), nl, maplist( arg(2), Ts, Pull ), write( pull(Pull) ), nl, push <- Push, pull <- Pull, <- plot( points, push, ylab='"push + pull (red)"' ), r_char( red, Red ), <- points( points, pull, col=Red ). rtest :- y <- rnorm(50), <- y, x <- rnorm(y), <- x11(width=5,height=3.5), <- plot(x,y), write( 'Press Return to continue...' ), nl, read_line_to_codes( user_input, _ ), <- dev..off(.), Y <- y, write( y(Y) ), nl, findall( Zx, between(1,9,Zx), Z ), z <- Z, <- z, cars <- [1, 3, 6, 4, 9], % cars <- c(1, 3, 6, 4, 9), <- pie(cars), write( 'Press Return to continue...' ), nl, read_line_to_codes( user_input, _ ), devoff. tut6 :- d <- outer(0:9, 0:9), fr <- table(outer(d, d, "-")), r(plot(as.numeric(names(fr)), fr, type="h", xlab="Determinant", ylab="Frequency")). tut4b :- astate <- [tas,sa,qld,nsw,nsw,nt,wa], statef <- factor(state), incmeans <- tapply( c(60, 49, 40, 61, 64, 60, 59, 54), statef, mean ), <- incmeans. logical :- t <- [1,2,3,4,5,1], s <- t==1, <- s, S <- s, write( s(S) ), nl.
Note, r..eal is sensitive to the amount of stack made available to Yap. With a generous stack, huge objects can be passed between the two systems.
κρότος;yapr/sureal% yap -s80000 -f -g "ensure_loaded(library(real))" YAP 6.3.0 (x86_64-linux): Fri Nov 11 16:06:00 CET 2011 MYDDAS version MYDDAS-0.9.1 ?- findall( I, between(1,1000000,I), Is), i <- Is, K <- i, length( K, Len ), write( len(Len) ), nl, fail. len(1000000)
We are grateful to Jan Wielemaker for re-setting and improving the whole of the C code. Many thanks to the developers of YapR for inspiration and some functions.
If Rvar is an atom and a known R object, then print Rvar on R. Else treat the input as an R expression and pass it on R for interpretation. (Throws result away, if expression is not a <- expression itself).
Pass Rexpr1 <- Rexpr2 to R.
Pass Prolog value PLval to Rvar. If PLval is a single value, flat list or list of depth 2 use the low-level interface to pass the value to an R variable (see section on data transfer).
Place value of Rvar to PLvar.
Note that all Rexpr* are first processed as described in the section about syntax before passed to R.
i <- int:[1,2.0,3].