Commit f3f12271 authored by celinedurniak's avatar celinedurniak
Browse files

Added examples using forpy

parent 30d6b3d1
# Fortran-Python binding benchmark
This project aims to benchmark different schemes of Python-Fortran bindings
\ No newline at end of file
This project aims to benchmark different schemes of Python-Fortran bindings
## TestForpy
This folder tests [forpy](https://ylikx.github.io/forpy/index.html).
The scripts have been tested on Ubuntu18.04 with gfortran.
\ No newline at end of file
FC=gfortran
forpy_mod.o:
$(FC) -c -fPIC forpy_mod.F90
extexample01.so: forpy_mod.o
$(FC) -shared -fPIC -o extexample01.so extexample01.F90 forpy_mod.o
extexample02.so: forpy_mod.o
$(FC) -shared -fPIC -o extexample02.so extexample02.F90 forpy_mod.o
extexample03.so: forpy_mod.o
$(FC) -shared -fPIC -o extexample03.so extexample03.F90 forpy_mod.o
mymath.so: forpy_mod.o
$(FC) -shared -fPIC -o mymath.so mymath.f90 forpy_mod.o
The examples illustrate how to use Fortran features in Python with `forpy`.
`mymath.f90` has been provided by Eliot Rabel, the developer of `forpy`.
In order to run the programs, you can use the commands in the `Makefile` to deal with the Fortran scripts.
Then run the Python` script importing the library.
For example, in a terminal, type
```bash
make mymath.so
python test_mymath.py
``
\ No newline at end of file
module extexample01
use forpy_mod
use iso_c_binding
implicit none
! You need to declare exactly one PythonModule and PythonMethodTable
! at Fortran module level
type(PythonModule), save :: mod_def
type(PythonMethodTable), save :: method_table
CONTAINS
! Initialisation function for Python 3
! called when importing module
! must use bind(c, name="PyInit_<module name>")
! return value must be type(c_ptr), use the return value of PythonModule%init
function PyInit_extexample01() bind(c, name="PyInit_extexample01") result(m)
type(c_ptr) :: m
m = init()
end function
function init() result(m)
type(c_ptr) :: m
integer :: ierror
type(object) :: pi
ierror = forpy_initialize()
call method_table%init(1) ! module shall have 1 method
! must add function print_args to method table to be able to use it in Python
call method_table%add_method("print_args", & ! method name
"Prints arguments and keyword arguments", & !doc-string
METH_VARARGS + METH_KEYWORDS, & ! this method takes arguments AND keyword arguments
c_funloc(print_args)) ! address of Fortran function to add
m = mod_def%init("extexample01", "A Python extension with a method and a member.", method_table)
! Example: Numerical constant as member of module
ierror = cast(pi, 3.141592653589793d0)
ierror = mod_def%add_object("pi", pi)
call pi%destroy
end function
! Implementation of our Python method
!
! Corresponding Python method shall allow arguments and keyword arguments
! -> We need 3 "type(c_ptr), value" arguments
! First arg is c_ptr to module, second is c_ptr to argument tuple
! third is c_ptr to keyword argument dict
! Return value must be type(c_ptr)
! bind(c) attribute to make sure that C calling conventions are used
function print_args(self_ptr, args_ptr, kwargs_ptr) result(r) bind(c)
type(c_ptr), value :: self_ptr
type(c_ptr), value :: args_ptr
type(c_ptr), value :: kwargs_ptr
type(c_ptr) :: r
type(tuple) :: args
type(dict) :: kwargs
type(NoneType) :: retval
integer :: ierror
! use unsafe_cast_from_c_ptr to cast from c_ptr to tuple/dict
call unsafe_cast_from_c_ptr(args, args_ptr)
call unsafe_cast_from_c_ptr(kwargs, kwargs_ptr)
if (is_null(kwargs)) then
! This is a check if keyword argument were passed to this function.
! If is_null(kwargs), kwargs is not a valid Python object, therefore
! we initialise it as an empty dict
ierror = dict_create(kwargs)
endif
ierror = print_py(args)
ierror = print_py(kwargs)
! You always need to return something, at least None
ierror = NoneType_create(retval)
r = retval%get_c_ptr() ! need return value as c_ptr
call args%destroy
call kwargs%destroy
end function
end module
\ No newline at end of file
module extexample02
use forpy_mod
use iso_c_binding
implicit none
! You need to declare exactly one PythonModule and PythonMethodTable
! at Fortran module level
type(PythonModule), save :: mod_def
type(PythonMethodTable), save :: method_table
CONTAINS
! Initialisation function for Python 3
! called when importing module
! must use bind(c, name="PyInit_<module name>")
! return value must be type(c_ptr), use the return value of PythonModule%init
function PyInit_extexample02() bind(c, name="PyInit_extexample02") result(m)
type(c_ptr) :: m
m = init()
end function
function init() result(m)
type(c_ptr) :: m
integer :: ierror
type(object) :: pi
ierror = forpy_initialize()
call method_table%init(2) ! module shall have 1 method
! must add function print_args to method table to be able to use it in Python
call method_table%add_method("print_args", & ! method name
"Prints arguments and keyword arguments", & !doc-string
METH_VARARGS + METH_KEYWORDS, & ! this method takes arguments AND keyword arguments
c_funloc(print_args)) ! address of Fortran function to add
call method_table%add_method("getrij", & ! method name
"Gets interparticle distance", & !doc-string
METH_VARARGS + METH_KEYWORDS, & ! this method takes arguments AND keyword arguments
c_funloc(getrij)) ! address of Fortran function to add
m = mod_def%init("extexample02", "A Python extension with a method and a member.", method_table)
! Example: Numerical constant as member of module
ierror = cast(pi, 3.141592653589793d0)
ierror = mod_def%add_object("pi", pi)
call pi%destroy
end function
! Implementation of our Python method
!
! Corresponding Python method shall allow arguments and keyword arguments
! -> We need 3 "type(c_ptr), value" arguments
! First arg is c_ptr to module, second is c_ptr to argument tuple
! third is c_ptr to keyword argument dict
! Return value must be type(c_ptr)
! bind(c) attribute to make sure that C calling conventions are used
function print_args(self_ptr, args_ptr, kwargs_ptr) result(r) bind(c)
type(c_ptr), value :: self_ptr
type(c_ptr), value :: args_ptr
type(c_ptr), value :: kwargs_ptr
type(c_ptr) :: r
type(tuple) :: args
type(dict) :: kwargs
type(NoneType) :: retval
integer :: ierror
! use unsafe_cast_from_c_ptr to cast from c_ptr to tuple/dict
call unsafe_cast_from_c_ptr(args, args_ptr)
call unsafe_cast_from_c_ptr(kwargs, kwargs_ptr)
if (is_null(kwargs)) then
! This is a check if keyword argument were passed to this function.
! If is_null(kwargs), kwargs is not a valid Python object, therefore
! we initialise it as an empty dict
ierror = dict_create(kwargs)
endif
ierror = print_py(args)
ierror = print_py(kwargs)
! You always need to return something, at least None
ierror = NoneType_create(retval)
r = retval%get_c_ptr() ! need return value as c_ptr
call args%destroy
call kwargs%destroy
end function
function getrij(self_ptr, args_ptr, kwargs_ptr) result(r) bind(c)
type(c_ptr), value :: self_ptr
type(c_ptr), value :: args_ptr
type(c_ptr), value :: kwargs_ptr
type(c_ptr) :: r
type(tuple) :: args
type(dict) :: kwargs
type(NoneType) :: retval
integer :: ierror
! use unsafe_cast_from_c_ptr to cast from c_ptr to tuple/dict
call unsafe_cast_from_c_ptr(args, args_ptr)
call unsafe_cast_from_c_ptr(kwargs, kwargs_ptr)
if (is_null(kwargs)) then
! This is a check if keyword argument were passed to this function.
! If is_null(kwargs), kwargs is not a valid Python object, therefore
! we initialise it as an empty dict
ierror = dict_create(kwargs)
endif
ierror = print_py(args)
ierror = print_py(kwargs)
! You always need to return something, at least None
ierror = NoneType_create(retval)
r = retval%get_c_ptr() ! need return value as c_ptr
call args%destroy
call kwargs%destroy
end function
end module
module extexample03
use forpy_mod
use iso_c_binding
implicit none
! You need to declare exactly one PythonModule and PythonMethodTable
! at Fortran module level
type(PythonModule), save :: mod_def
type(PythonMethodTable), save :: method_table
CONTAINS
! Initialisation function for Python 3
! called when importing module
! must use bind(c, name="PyInit_<module name>")
! return value must be type(c_ptr), use the return value of PythonModule%init
function PyInit_extexample03() bind(c, name="PyInit_extexample03") result(m)
type(c_ptr) :: m
m = init()
end function
function init() result(m)
type(c_ptr) :: m
integer :: ierror
type(object) :: pi
ierror = forpy_initialize()
call method_table%init(3) ! module shall have 1 method
! must add function print_args to method table to be able to use it in Python
call method_table%add_method("print_args", & ! method name
"Prints arguments and keyword arguments", & !doc-string
METH_VARARGS + METH_KEYWORDS, & ! this method takes arguments AND keyword arguments
c_funloc(print_args)) ! address of Fortran function to add
call method_table%add_method("getrij", & ! method name
"Gets interparticle distance", & !doc-string
METH_VARARGS + METH_KEYWORDS, & ! this method takes arguments AND keyword arguments
c_funloc(getrij)) ! address of Fortran function to add
call method_table%add_method("function_add", & ! method name
"Adds numbers", & !doc-string
METH_VARARGS + METH_KEYWORDS, & ! this method takes arguments AND keyword arguments
c_funloc(function_add)) ! address of Fortran function to add
m = mod_def%init("extexample03", "A Python extension with a method and a member.", method_table)
! Example: Numerical constant as member of module
ierror = cast(pi, 3.141592653589793d0)
ierror = mod_def%add_object("pi", pi)
call pi%destroy
end function
! Implementation of our Python method
!
! Corresponding Python method shall allow arguments and keyword arguments
! -> We need 3 "type(c_ptr), value" arguments
! First arg is c_ptr to module, second is c_ptr to argument tuple
! third is c_ptr to keyword argument dict
! Return value must be type(c_ptr)
! bind(c) attribute to make sure that C calling conventions are used
function print_args(self_ptr, args_ptr, kwargs_ptr) result(r) bind(c)
type(c_ptr), value :: self_ptr
type(c_ptr), value :: args_ptr
type(c_ptr), value :: kwargs_ptr
type(c_ptr) :: r
type(tuple) :: args
type(dict) :: kwargs
type(NoneType) :: retval
integer :: ierror
! use unsafe_cast_from_c_ptr to cast from c_ptr to tuple/dict
call unsafe_cast_from_c_ptr(args, args_ptr)
call unsafe_cast_from_c_ptr(kwargs, kwargs_ptr)
if (is_null(kwargs)) then
! This is a check if keyword argument were passed to this function.
! If is_null(kwargs), kwargs is not a valid Python object, therefore
! we initialise it as an empty dict
ierror = dict_create(kwargs)
endif
ierror = print_py(args)
ierror = print_py(kwargs)
! You always need to return something, at least None
ierror = NoneType_create(retval)
r = retval%get_c_ptr() ! need return value as c_ptr
call args%destroy
call kwargs%destroy
end function
function getrij(self_ptr, args_ptr, kwargs_ptr) result(r) bind(c)
type(c_ptr), value :: self_ptr
type(c_ptr), value :: args_ptr
type(c_ptr), value :: kwargs_ptr
type(c_ptr) :: r
type(tuple) :: args
type(dict) :: kwargs
type(NoneType) :: retval
integer :: ierror
! use unsafe_cast_from_c_ptr to cast from c_ptr to tuple/dict
call unsafe_cast_from_c_ptr(args, args_ptr)
call unsafe_cast_from_c_ptr(kwargs, kwargs_ptr)
if (is_null(kwargs)) then
! This is a check if keyword argument were passed to this function.
! If is_null(kwargs), kwargs is not a valid Python object, therefore
! we initialise it as an empty dict
ierror = dict_create(kwargs)
endif
ierror = print_py(args)
ierror = print_py(kwargs)
! You always need to return something, at least None
ierror = NoneType_create(retval)
r = retval%get_c_ptr() ! need return value as c_ptr
call args%destroy
call kwargs%destroy
end function
function function_add(self_ptr, args_ptr) result(r) bind(c)
type(c_ptr), value :: self_ptr
type(c_ptr), value :: args_ptr
!type(c_ptr), value :: kwargs_ptr
type(c_ptr) :: r
type(tuple) :: args
!type(dict) :: kwargs
type(NoneType) :: retval
integer :: ierror
! use unsafe_cast_from_c_ptr to cast from c_ptr to tuple/dict
call unsafe_cast_from_c_ptr(args, args_ptr)
!call unsafe_cast_from_c_ptr(kwargs, kwargs_ptr)
!if (is_null(kwargs)) then
! This is a check if keyword argument were passed to this function.
! If is_null(kwargs), kwargs is not a valid Python object, therefore
! we initialise it as an empty dict
! ierror = dict_create(kwargs)
!endif
ierror = print_py(args)
!ierror = print_py(kwargs)
! You always need to return something, at least None
ierror = NoneType_create(retval)
r = retval%get_c_ptr() ! need return value as c_ptr
call args%destroy
!call kwargs%destroy
end function
end module
This diff is collapsed.
! Fortran-Python hybrid module
! Elias Rabel
module mymath
use forpy_mod
use, intrinsic :: iso_c_binding
use, intrinsic :: iso_fortran_env
implicit none
public :: square
PRIVATE
! You need to declare exactly one PythonModule and PythonMethodTable
! at Fortran module level
type(PythonModule), save :: mod_def
type(PythonMethodTable), save :: method_table
CONTAINS
! Initialisation function for Python 3
! called when importing module
! must use bind(c, name="PyInit_<module name>")
! return value must be type(c_ptr), use the return value of PythonModule%init
function PyInit_mymath() bind(c, name="PyInit_mymath") result(m)
type(c_ptr) :: m
m = init()
end function
! Initialisation function for Python 2
! called when importing module
! must use bind(c, name="init<module name>")
! Initialisation function for Python 2
! called when importing module
! must be called init<module name>
subroutine initmymath() bind(c, name="initmymath")
type(c_ptr) :: m
m = init()
end subroutine
function init() result(m)
type(c_ptr) :: m
integer :: ierror
type(object) :: pi
ierror = forpy_initialize()
call method_table%init(1)
! must add function square to method table to be able to use it in Python
call method_table%add_method("square", & ! method name
"Computes square of a float", & !doc-string
METH_VARARGS, & ! this method takes arguments but no keyword arguments
c_funloc(mymath_square_python)) ! address of Fortran function to add
m = mod_def%init("mymath", "A Python extension with a math function", method_table)
end function
! Implementation of our Python method
!
! Corresponding Python method shall allow arguments but no keyword arguments
! -> We need 2 "type(c_ptr), value" arguments
! First arg is c_ptr to module, second is c_ptr to argument tuple
! Return value must be type(c_ptr)
function mymath_square_python(self_ptr, args_ptr) result(r) bind(c)
! used bind(c) attribute to ensure that C calling conventions are used
! used a long method name to avoid name clashes with other C libs
!
type(c_ptr), value :: self_ptr
type(c_ptr), value :: args_ptr
type(c_ptr) :: r
type(tuple) :: args
type(object) :: retval
integer :: ierror
integer :: num_args
real(kind=real64) :: a_number
r = C_NULL_PTR ! in case of an exception return C_NULL_PTR
! use unsafe_cast_from_c_ptr to cast from c_ptr to tuple
call unsafe_cast_from_c_ptr(args, args_ptr)
! Check if the arguments are OK
ierror = args%len(num_args) ! we should also check ierror, but this example does not do complete error checking for simplicity
if (num_args /= 1) then
call raise_exception(TypeError, "square expects exactly 1 argument")
call args%destroy
return
endif
ierror = args%getitem(a_number, 0)
if (ierror == 0) then
ierror = cast(retval, square(a_number))
r = retval%get_c_ptr()
endif
call args%destroy
end function
! Fortran implementation
elemental function square(a)
real(kind=real64), intent(in) :: a
real(kind=real64) :: square
square = a**2
end function
end module
# File: mymodule.py
def print_args(*args, **kwargs):
print("Arguments: ", args)
print("Keyword arguments: ", kwargs)
return "Returned from mymodule.print_args"
program mymodule_example
use forpy_mod
implicit none
integer :: ierror
type(tuple) :: args
type(dict) :: kwargs
type(module_py) :: mymodule
type(object) :: return_value
type(list) :: paths
character(len=:), allocatable :: return_string
ierror = forpy_initialize()
! Instead of setting the environment variable PYTHONPATH,
! we can add the current directory "." to sys.path
ierror = get_sys_path(paths)
ierror = paths%append(".")
ierror = import_py(mymodule, "mymodule")
! Python:
! return_value = mymodule.print_args(12, "Hi", True, message="Hello world!")
ierror = tuple_create(args, 3)
ierror = args%setitem(0, 12)
ierror = args%setitem(1, "Hi")
ierror = args%setitem(2, .true.)
ierror = dict_create(kwargs)
ierror = kwargs%setitem("message", "Hello world!")
ierror = call_py(return_value, mymodule, "print_args", args, kwargs)
ierror = cast(return_string, return_value)
write(*,*) return_string
! For call_py, args and kwargs are optional
! use call_py_noret to ignore the return value
! E. g.:
! ierror = call_py_noret(mymodule, "print_args")