BindTo Users Guide

In many todays applications it is common to use two or even more programming languages. The use of mixture of the languages lets programmers exploited the strong sides of each language. No doubt, that strong side of Fortran is the speed of calculation. However, when it comes to building of a graphical user interface, a preparation of data for calculations (pre-processing) or an analysis of the calculation results (post-processing), other programming languages may add significantly to the end product of your work.

BindTo tool tries to make the communication with Fortran code from the outside more easily by automatically generating the required wrapping code. An intrinsic “iso_c_binding” module is used. In addition to the exposing Fortran code to C, BindTo can generate Cython (cython.org) files, which enable to call Fortran from Python language. In general, this tool doesn’t do anything what you could not do by yourself. It just saves your time. Actually, BindTo is what Fwrap intended to be, but its development was abandoned (maybe it is time for revival?). Alternatively, Fortran code can be connected to Python by using “f2py” or its extension “f90wrap”.

Fortran to C: Simple example

Let start from a simple example. Say, we have a nice Fortran code and now what to make it accessible from C or other language, which can call C. For this purpose Fortran standard includes iso_c_binding module. By use of this module, we can make our code callable from C. We just need to write a thin wrapper code using this iso_c_binding. BindTo does exactly the same: writes this wrapper code.

In this example we have a simple Fortran subroutine:

! Add two integers in Fortran
subroutine add_it(a,b,c)
    implicit none
    integer, intent(in) :: a
    integer, intent(in) :: b
    integer, intent(out) :: c

    print *, "Hello from Fortran!"
    c = a + b
end subroutine

Steps:

  • Create a new Code::Blocks project: File->New->Project, select "Fortran library" and follow wizard. I created project called “bind_me” (Fig.1). For the compiler, I selected “GNU Fortran Compiler”.
  • The new project contains “main.f90”, but you can rename it to “add.f90” (you can rename the files directly in Projects tree, if files are closed). Open that file and change the content to the code from above. Compile the project. The result is the “libbind_me.a”.
/images/cb_bindto_bindme_project.png

Fig.1. Project "bind_me" properties

  • Now open BindTo dialog (Fortran->Bind To…) (Fig.2).

    For this simple example, the default settings should work. For more sophisticated cases you may need to add or change the items in “Types” table. An information from this table is used in generated files. The column “Fortran” on the table is the type in your Fortran code. The column “Fortran Bind(c)” is the type used in the generated Fortran wrapping code. And the column “C” is the type in the generated C header file.

    Run BindTo by pressing “OK” button. The files “add_bc.f90” and “add_bc.h” should be generated in “<your-project-path>/bind” folder.

/images/cb_bindto_dlg.png

Fig.2. BindTo dialog

  • Add generated “add_bc.f90” file to “bind_me” project (Project->Add files…). Compile or recompile the project.
  • Now is the time to write C++ code (I will use C++ not C) from which Fortran subroutine is called. However first a new build target should be created, for which GCC compiler should be selected: Project->Properties…, Build targets, Add. Give name “cmain_debug” for the new target. Select type “Console application”. Check “Pause when execution ends”. Select “Build options…” for this target. Change target’s compiler to “GNU GCC Compiler”. Close dialogs.
  • Create a new file (File->New->Empty file). Add this file to your project. Assign this file to “cmain_debug” build target. Also add automatically generated “add_bc.h” file from “<your-project-path>/bind” folder to the same “cmain_debug” build target. Add “libbind_me” and “libgfortran” libraries to the Linker Settings of this target (Fig.3)
/images/cb_bindto_cmain_link_settings.png

Fig.3. Linker Settings

  • Write C++ main function, in which call Fortran subroutine. It could be like this:
#include <iostream>
#include "bind/add_bc.h"

int main()
{
    int mysum;
    int a=1, b=2;
    add_it(&a, &b, &mysum);
    std::cout << "Result = " << mysum << std::endl;
    return 0;
}
  • Make the “cmain_debug” build target active. Compile it and execute the program.

Fortran to C: Types

In this example I will try to explain a bit more about the type conversion between Fortran and C. Actually I am unsure if "conversion" is the right term, because most Fortran types just have corresponding C types. The types information used by BindTo tool is in the table “Binding types” on BindTo dialog. For example Fortran integer(4) type corresponds to integer(c_int32_t) in iso_c_binding module and it corresponds to int32_t in C. If some line in this table is not true for your compiler, you can change it. In some cases you will need to add new types. E.g. if you use real(rp), where rp is a parameter defined somewhere in your program, you will need to add a new line with real(rp) for Fortran, real(8) for Bind(C) (here I assumed, that rp=8) and double for C. There are three cases, about which I should speak separately: a) character type; b) logical type; c) derived types.

Character type

A string in C language is a one-dimensional array of characters terminated by a null character \0. Exactly this is assumed by BindTo, i.e. strings which come from C have to be terminated with \0. On Fortran side in the generated wrapping code the string from C is seeing as one-dimensional assumed size array of character(c_char) type. The size of this array is determined from the position of c_null_char (\0 in Fortran). The conversion (copy) of character array to character(len=determined_size) is made and passed to Fortran code. On the return, an opposite operation is performed. By using intent(in) or intent(out) in your Fortran code, one copy operation may be avoided. The wrapping of Fortran character arrays (e.g. character(len=20), dimension(*) :: names ) is not supported.

Logical type

While iso_c_binding module includes c_bool, it seems that this solution doesn’t work for default logical type. Therefore, in the generated Fortran wrapper code, the conversion from Fortran logical type to C int (and oppositely) is performed. There is no entry for logical types in Types table on BindTo dialog, because this rule is used for logical<->int conversion implicitly. However user may add an entry for some logical type in the table and then this new rule will be used.

Derived types

In the Fortran defined derived type may, in some cases, correspond to C struct. For this user need to add Bind(C) attribute in declaration of such derived type. If BindTo finds such attribute, it writes corresponding struct in the generated *.h file. However, in many cases there is not possible to have corresponding struct in C for Fortran derived type (allocatable or pointer components). If BindTo doesn’t find Bind(C) attribute in the type declaration, it takes C pointer to derived type variable. This could look something like:

type(mytype), pointer :: fvar
type(c_ptr) :: cp_fvar
allocate(fvar)
cp_fvar = c_loc(fvar)
! do the stuff with fvar variable
....

In this case, when type(c_ptr) is used, it is required to allocate memory for the derived type pointer variable. Therefore, every derived type variable should have a constructor procedure, where memory is allocated and a destructor procedure, where memory is freed. If your code has a procedure (subroutine or function) in which the components of the derived type variable are initialized, you can tell for BindTo how it is called, and BindTo will use it as a constructor. However, there is one important convention: BindTo requires, that first dummy argument in the constructor subroutine be of the derived type for which this constructor is created. Every Fortran function which returns the derived type variable, can be called “constructor”, because the memory have to be allocated for the return variable. It is assumed that constructor and destructor is in the same module where the derived type is defined.

For the destructor, a subroutine with derived type dummy argument can be used. However, this subroutine should have no other arguments except of this one. The destructor should be called from C code by the user for every derived type variable to free memory, when this variable is not needed anymore.

If BindTo can’t find the constructor or/and the destructor procedure, then the constructor or/and the destructor is created automatically.

Fortran to C: Example of derived type with Bind(C)

In this example the use of a derived type variable with Bind(C) attribute is showed. Fortran code:

module person_m
    use iso_c_binding

    type, bind(c) :: person_t
        integer(c_int) :: age
        character(len=20, kind=c_char) :: name
    end type

contains
    subroutine print_person(p)
        type(person_t), intent(in) :: p

        print *, "Person received:"
        print *, " age=", p%age
        print *, " name=", p%name
    end subroutine
end module

The BindTo creates two files. The first file with Fortran code:

module person_m_bc
    use :: person_m
    use, intrinsic :: iso_c_binding
    implicit none
contains
    subroutine print_person_bc(p) bind(c,name='print_person')
        type(person_t), intent(in) :: p

        call print_person(p)
    end subroutine
end module

As you can see, the derived type person_t variable comes as a dummy argument to subroutine print_person_bc.

The second file is C header file:

#ifdef __cplusplus
extern "C" {
#endif
#ifndef PERSON_BC_H
#define PERSON_BC_H

typedef struct {
    int age;
    char name[21];
} person_t;

// Module 'person_m' procedures
void print_person(person_t* p);

#endif
#ifdef __cplusplus
}
#endif

In the header file, the structure person_t is created. To call Fortran, the following C++ code can be used:

#include <string.h>
#include "bind/person_bc.h"

int main()
{
    // Create structure and call fortran
    person_t person;
    person.age = 33;
    strcpy(person.name,"Spyder-Man          ");
    print_person(&person);
    return 0;
}

Fortran to C: Example of derived type without Bind(C)

Second example demonstrates the use of a derived type, which doesn’t have Bind(C) attribute.

Fortran code:

module balls
    implicit none

    type balls_t
        integer :: n
        real, dimension(:,:), allocatable :: coord
    end type

contains
    subroutine balls_ctor(self, n)
        type(balls_t), intent(inout) :: self
        integer, intent(in) :: n

        allocate(self%coord(3,n))
        self%n = n
        print *, "Fortran: A variable of type 'balls_t' was created."
    end subroutine

    subroutine set_coord(self, bc)
        type(balls_t), intent(inout) :: self
        real, dimension(:,:), intent(in) :: bc

        if (any(shape(self%coord) /= shape(bc))) then
            stop "Error. sub. set_coord. Array sizes should be the same!"
        end if
        self%coord = bc
    end subroutine

    subroutine get_coord(self, i, xb)
        type(balls_t), intent(in) :: self
        integer, intent(in) :: i
        real, dimension(*), intent(out) :: xb

        xb(1:3) = self%coord(:,i)
    end subroutine
end module

BindTo generates Fortran wrapper code:

module balls_bc
    use :: balls
    use, intrinsic :: iso_c_binding
    implicit none
contains

    subroutine balls_ctor_bc(self, n) bind(c,name='balls_ctor')
        type(c_ptr), intent(out) :: self
        integer(c_int), intent(in) :: n
        type(balls_t), pointer :: self_fp

        allocate(self_fp)
        self = c_loc(self_fp)
        call balls_ctor(self_fp, n)
    end subroutine

    subroutine set_coord_bc(self, bc, m1, m2) bind(c, name= 'set_coord')
        type(c_ptr), intent(inout) :: self
        real(c_float), dimension(m1,m2), intent(in) :: bc
        type(balls_t), pointer :: self_fp
        integer(c_int), intent(in) :: m1
        integer(c_int), intent(in) :: m2

        call c_f_pointer(self, self_fp)
        call set_coord(self_fp, bc)
    end subroutine

    subroutine get_coord_bc(self, i, xb) bind(c,name='get_coord')
        type(c_ptr), intent(in) :: self
        integer(c_int), intent(in) :: i
        real(c_float), dimension(*), intent(out) :: xb
        type(balls_t), pointer :: self_fp

        call c_f_pointer(self, self_fp)
        call get_coord(self_fp, i, xb)
    end subroutine

    function balls_t_ctor_bc() bind(c,name='balls_t_ctor')
        type(c_ptr) :: balls_t_ctor_bc
        type(balls_t), pointer :: this_fp

        allocate(this_fp)
        balls_t_ctor_bc = c_loc(this_fp)
    end function

    subroutine balls_t_dtor_bc(this_cp) bind(c,name='balls_t_dtor')
        type(c_ptr), intent(in) :: this_cp
        type(balls_t), pointer :: this_fp

        call c_f_pointer(this_cp, this_fp)
        deallocate(this_fp)
    end subroutine
end module

Here the subroutine balls_ctor was recognized as a constructor and therefore the allocation of self_fp takes place in the subroutine balls_ctor_bc. In addition, a constructor function balls_t_ctor_bc, which takes no arguments, was created also. Freeing of memory is performed in the destructor subroutine balls_t_dtor_bc. Do not forget to call it for every derived type variable from your C/C++ code.

BindTo generated the following header file:

#ifdef __cplusplus
extern "C" {
#endif
#ifndef BALLS_BC_H
#define BALLS_BC_H

// Module 'balls' procedures
void balls_ctor(void** self, int* n);
void set_coord(void** self, float* bc, int* m1, int* m2);
void get_coord(void** self, int* i, float* xb);
void* balls_t_ctor();
void balls_t_dtor(void** this_cp);

#endif
#ifdef __cplusplus
}
#endif

Below is an example of C++ code in which Fortran is called:

#include <iostream>
#include "bind/balls_bc.h"

int main()
{
    void* balls; // Fortran derived type
    int n = 2;
    int dim = 3;
    balls_ctor(&balls, &n);

    float bcoord[] = {1.,2.,3.,4.,5.,6.};
    set_coord(&balls, bcoord, &dim, &n);

    int i = 2;
    float xp[3];
    get_coord(&balls, &i, xp);
    for (int j=0; j<3; j++)
    {
        std::cout<<"xp[j]="<<xp[j]<<"\n";
    }
    balls_t_dtor(&balls);
    return 0;
}

Fortran to C: Arrays

BindTo recognizes explicit-shape arrays (var(n)), assumed-size arrays (var(n,*)) and assumes-shape arrays (var(:,:)). To call assumed-shape arrays from C, such array is wrapped with explicit-shape array. For example, if in Fortran an array is declared with integer :: ivar(:), then in generated wrapper code we will have integer :: iver(m1) and m1 will be added to the list of dummy arguments of the procedure. An example of passing an assumed-shape array can be found in the previous section in the subroutine set_coord.

What if Fortran procedure has several assumed-shape arrays as the dummy arguments? In such case a separate argument will be created for every array dimension. But probably several arrays have the same dimension? For such case a special directive can be used. If a line in the Fortran code starts with !BindTo sentinel (case doesn’t matter), this line interpreted by BindTo tool. In case of assumed-shape arrays, it may be useful to explicitly define dimensions of arrays. For example:

subroutine calculate(a, b, c)
    real, dimension(:,:) :: a, b, c
    !bindto dimension(m,m) :: a, b, c
    ....
end subroutine

The generated Fortran wrapper code:

subroutine calculate_bc(a, b, c, m) bind(c,name='calculate')
    real(c_float), dimension(m,m) :: a
    real(c_float), dimension(m,m) :: b
    real(c_float), dimension(m,m) :: c
    integer(c_int), intent(in) :: m

    call calculate(a, b, c)
end subroutine

Generated C header:

void calculate(float* a, float* b, float* c, int* m);

Here you see, that !BindTo sentinel was recognized and instead of 6 different variables for array sizes just one m was added to the list of the arguments.

More about BindTo directives read in the section "Fortran to Python: BindTo directives".

Fortran to Python: Introduction

Python interpretable language is attractive for its flexibility, simplicity etc. One of the attractive features of Python for a number crunching folk is its convenient support of arrays using NumPy library. It is possible to operate on NumPy arrays in a similar fashion as in Fortran. However, pure Python program may quickly come to a point, where speed of calculations forces to use a compilable language. At this point the delegation of the number crunching part for Fortran can really be useful.

BindTo tool can generate not only code required Fortran to be called from C, however this tool can generate Cython files which enable Fortran to be called from Python.

From Cython documentation: “Cython is a programming language that makes writing C extensions for the Python language as easy as Python itself.”

Don’t be afraid, you don’t need to learn yet another programming language. To enable Python to call your Fortran code, you just need to install Cython (how to do it for your system, you can find on cython.org) and compile BindTo generated files into Python extension module. It’s all.

Few rules: Fortran arrays are wrapped with NumPy arrays. BindTo assumes, that NumPy multidimensional array is C-order (row-major order). Therefore, an array arr(m,n) in Fortran should be created in Python with e.g. arr=numpy.zeros([n,m]). It is assumed that arrays are contiguous and no check for it is performed.

Fortran to Python: Example

Here, the same example from section “Fortran to C: Simple example” is used to show how Fortran code can be called from Python.

Open “add.f90” file, then go to BindTo dialog (Fortran->BindTo…), select tab “Python” and enable “Generate Cython files”. After pressing “OK”, the files “add.pyx”, “add_f.pxd” “setup_add.py” are generated in “bind” directory additionaly.

File “add.pyx”:

#!python
#cython: boundscheck=False, wraparound=False
import numpy as np
cimport numpy as np
cimport add_f

def add_it(int a, int b):
    cdef int c
    add_f.add_it(&a, &b, &c)
    return c

Here you see a declaration of Python function def add_it(int a, int b) which returns c variable. In this function the Fortran subroutine is called. Because in the Fortran code the variables a and b have intent(in) attributes, these variables do not go to the return list. c variable has intent(out), therefore it is returned with return statement. If a variable has no intent attribute, intent(in) is assumed.

File “setup_add.py”:

# Run this file using:
# python setup_add.py build_ext --inplace

from distutils.core import setup
from distutils.extension import Extension
from Cython.Build import cythonize
import numpy

extensions = [
    Extension('add', ['add.pyx'],
        runtime_library_dirs=['./'],
        library_dirs=['../bin/Debug'],
        include_dirs=[numpy.get_include()],
        libraries=['bind_me', 'gfortran'],
        ),
    ]
setup(
    ext_modules = cythonize(extensions),
)

"setup_*.py” file is used for the compilation of Python extension. You may need to adjust this file to include additional libraries and library search paths. The file can be involved with python setup_add.py build_ext --inplace in the shell. If you just do it now, you may get an error during the compilation. To avoid it, it is required to make one adjustment. In the example, we compiled “add.f90” and “add_bc.f90” into static library “libbind_me.a”. It is OK. But now this library should be included into another Python extension shared library. Therefore GCC requires to recompile our library with added “-fPIC” option. Go to “Project->Build options…” dialog and add “-fPIC” to the compiler options (see screenshot on Fig.***). Alternatively, C::B project with Fortran files can be compiled into a shared library (*.so or *.dll), if you prefer.

/images/cb_bindto_fPIC.png

Fig.4. Compiler options

Now the Python extension library can be compiled with python setup_add.py build_ext --inplace. As a result of compilation, “add.so” or “add.pyd” is created. Test it with Python (Fig.5).

/images/cb_bindto_python_addit.png

Fig.5. Python calling Fortran

Fortran to Python: BindTo directives

Fortran comment lines, which starts with “!BindTo” sentinel (case doesn’t matter) are interpreted by BindTo tool. Here you can add additional information about procedure dummy arguments. “!BindTo” directives should come after the declaration of the procedure somewhere near to declaration of dummy arguments.

Syntax:

!bindto <attributes> :: <variables>

Supported attributes are:

  • intent(<intent_list>)
  • dimension(<array_spec>)

<intent_list> is a comma separated list of keys:

  • in intent(in) means the same as in the standard Fortran, i.e. argument is for input only. Therefore a dummy argument with this attribute is not placed in Python function return list.
  • out An argument with intent(out) attribute is considered as an return variable. It is removed from the Python function argument list and added to the function return list.
  • inout A dummy argument with intent(inout) attribute is considered as input-output.
  • hide A dummy argument with intent(hide) attribute is removed from the list of arguments. Normally, this attribute should be combined with an expression for initialization. Such expression can be any valid Python statement with an assignment. If the argument is array, then Python statement should create a NumPy array too (see the example below).
  • copy This attribute is placed to make a copy of the original argument and in this way to preserve original values. For scalar arguments this attribute is useless, because scalar arguments are copied automatically anyway.

Only one intent is allowed in one !BindTo directive, however copy and other attribute can be combined into one, e.g. intent(inout, copy).

BindTo assumes, that NumPy arrays are contiguous and are C-order (row-major order).

dimension(<array_spec>) is useful for adding an additional information about the dimensions of array in the case of assumed shape or assumed size array.

An example:

 subroutine work_power(ain, aout, ainout, ano)
     implicit none
     integer, dimension(:) :: ain
     integer, dimension(:), intent(out) :: aout
     integer, dimension(:), intent(inout) :: ainout
     integer, dimension(:) :: ano
     !bindto intent(in, copy), dimension(n) :: ain
     !bindto dimension(n) :: aout, ano
     !bindto intent(hide) :: ainout=np.arange(1,n+1,dtype=np.intc)

     ....
end subroutine

Fortran to Python: Benchmark test

In general, Python is slower than Fortran and, when connecting Fortran to Python, some overhead should be expected. However, how big is this overhead? In this section a small benchmark test is performed to make a rough impression what to expect.

Here is a short Fortran code which is used in the test:

subroutine sum_arr(a, b, c, m)
    real, dimension(m), intent(in)  :: a, b
    real, dimension(m), intent(out) :: c
    integer, intent(in) :: m

    c = a + b
end subroutine

Using BindTo Cython files are generated and a Python extension module “test.so” is produced from Cython files and Fortran static library with the test code. GCC 4.8 is used. Fortran code compiled with -O3 compiler option, while Cython code compiled with the default options using python setup_test.py build_ext --inplace command line.

For the comparison purposes, a Fortran program is compiled too. The program code:

program test
    implicit none

    real, dimension(2) :: a, b, c
    integer :: i
    integer :: niterations = 1

    do i = 1, niterations
        a = i
        b = i
        call sum_arr(a, b, c, 2)
    end do
end program

At first, niterations=1 is set:

$ time ./test

real    0m0.001s
user    0m0.000s
sys     0m0.001s

The corresponding Python code:

import test_me
import numpy as np

niterations = 1
a = np.empty([2],np.float32)
b = np.empty([2],np.float32)
for i in range(niterations):
    a[:] = i
    b[:] = i
    c = test_me.sum_arr(a,b,2)

Command line time python test_it.py produces:

real    0m0.047s
user    0m0.039s
sys     0m0.008s

This performed test showed what is overhead of starting Python interpreter.

In the second test case, niterations=100000 is set.

With pure Fortran code output is:

real    0m0.002s
user    0m0.002s
sys     0m0.002s

Python-Fortran version:

real    0m0.304s
user    0m0.292s
sys     0m0.012s

In the performed test, the useful calculations are very short. Almost all of the measured time is spent in the Python side, not in the Fortran. And (0.304s-0.047s)/100000 is the cost for calling Fortran ones. If your Fortran calculations are performed e.g. in 10s in 100k calls, then with Python-Fortran code you may expect 10.3s. It is up to you if you want to pay this 0.3s price.

Not supported features

Generally, is not supported everything except what is supported. Just few Fortran features which come into my mind:
  • Fixed form source code (???)
  • Type bound procedures (yet?)
  • Procedure pointer in an argument list (how to deal with it?)
  • Common blocks (how to deal with it?)
  • Derived types with Bind(C) attribute in binding to Python (C wrapping is supported)
  • Module level variables
  • ...