Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/t-kalinowski/rfi

R-Fortran Interface for Modern Fortran
https://github.com/t-kalinowski/rfi

fortran fortran2018 r

Last synced: 9 days ago
JSON representation

R-Fortran Interface for Modern Fortran

Awesome Lists containing this project

README

        

---
output: github_document
---

```{r, include = FALSE}
knitr::opts_knit$set(root.dir = tempdir())
knitr::opts_chunk$set(
collapse = TRUE,
comment = "#>",
fig.path = "man/figures/README-",
out.width = "100%"
)

```

# RFI: R to Modern Fortran Interface

This R package provides `.ModernFortran()`, an interface similar to `.Fortran()`
but for Fortran 2018.

The 2018 Fortran language standard expanded support for interfacing C and Fortran.
One of the additions is the introduction of *C descriptors*, a data structure
for passing arrays between C and Fortran. This package takes advantage of that.

In contrast with `.Fortran`, R arrays are not passed as naked pointers, but as
*C descriptors* that contain information about rank, shape, element size, type,
and memory stride of the array that the Fortran routine can access directly.
This means that additional arguments for passing the size or rank of arrays are
no longer needed, which should lead to cleaner, simpler Fortran code.
Additionally, logical and raw types are now supported directly.

Currently supported type conversions are:

|R type |Fortran type |
|:---------|:---------------------------|
|`logical` |`logical(c_bool)` |
|`integer` |`integer(c_int)` |
|`double` |`real(c_double)` |
|`complex` |`complex(c_double_complex)` |
|`raw` |`integer(c_int8_t)` |

```{r, echo=FALSE, eval = FALSE, results='asis'}
knitr::kable(matrix(ncol = 2, byrow = TRUE,
dimnames = list(NULL, c("R type", "Fortran type")),
sprintf("`%s`",
c(
"logical", "logical(c_bool)",
"integer", "integer(c_int)",
"double", "real(c_double)",
"complex", "complex(c_double_complex)"
)
)))
```

## Installation

You can install RFI from github:
```r
if(!requireNamespace("remotes")) install.packages("remote")
remotes::install_github("t-kalinowski/RFI")
```

## Example

Say you have a Fortran 2018 module with a subroutine like so:

```{r include=FALSE}
fmodule <-
"module mod_cshift

use iso_c_binding, only: c_int, c_double, c_double_complex, c_bool
implicit none

contains

subroutine my_subroutine(array, shift) bind(c)
integer(c_int), intent(in out) :: array(:)
integer(c_int), intent(in) :: shift

array = cshift(array, shift)
end subroutine my_subroutine

end module mod_cshift
"
```

```{r, results='asis', echo = FALSE}
writeLines(paste("```f90\n", fmodule, "```"))
```
(`cshift` by the way is Fortran intrinsic for **circular shift**)

```{r include=FALSE}
f_module_file <- "mod_my_fortran_module.f90"
tmpdir <- writeLines(fmodule, f_module_file)
```

From R you can compile it like so:
```{r}
f_module_file <- "mod_my_fortran_module.f90"
so_file <- sub("f90$", "so", f_module_file)

compile_cmd <- sprintf(
"gfortran -std=f2018 -shared -lgfortran -lm -lquadmath %s -o %s",
f_module_file, so_file)

system(compile_cmd)
```

(you could also use the official R pathway to creating shared objects), with something like:

```{r, eval = FALSE}
compile_cmd <- sprintf(
"PKG_FFLAGS=-std=f2018 R CMD SHLIB %s -o %s",
f_module_file, so_file)
system(compile_cmd)
```

Once the shared object is made, use it from R like so:
```{r}
# load the fortran module
dll <- dyn.load(so_file)

# get C pointer to the Fortran subroutine
func_ptr <- getNativeSymbolInfo('my_subroutine', dll)$address

# call the subroutine with R arrays
RFI::.ModernFortran(func_ptr, array=1:5, shift=2L)
```

Just like the other interfaces to compiled code (`.Fortran`,`.Call`, etc),
you'll probably want to wrap this in an R function for convince and input type
coercion.
```{r}

cshift <- function(array, shift) {
RFI::.ModernFortran(func_ptr,
array = as.integer(array),
shift = as.integer(shift))$array
}

cshift(1:10, -3)
```

For the most part, the interface to `.ModernFortran` tries to match that of
`.Fortran`. One area where `.ModernFortran` deviates a little is in regards to
duplicating objects. A mechanism is provided for selectively duplicating only
some of the SEXP objects by passing an (0-based) integer vector of argument index
positions to the `DUP` argument.
For example, we know the subroutine above only modifies the first argument,
`array`, and does not modify the second argument, `shift`. Accordingly, we can
tell the R interface that only the first argument needs to be duplicated by passing `0L`.
(We include the `typeof` check to avoid duplicating if `as.integer` already duplicated)
```{r}
cshift <- function(array, shift) {
RFI::.ModernFortran(
func_ptr,
array = as.integer(array),
shift = as.integer(shift),
DUP = if(typeof(array) == "integer") 0L else FALSE
)$array
}
cshift(1:10, 3)
```

Tested with gfortran 9.2 and 9.3, R 3.6.3 and R 4.0.