Search code examples
pythonfortranf2py

f2py - order of function arguments messed up


I have written a small Fortran function and pass arguments to it in Python using f2py. Somehow the order of arguments is messed up during the transfer and I can't figure out why.

Relevant parts of the Fortran function (which is in a file called calc_density.f95):

subroutine calc_density(position, nparticles, ncells, L, density)

implicit none

integer, intent(in) :: nparticles
integer, intent(in) :: ncells
double precision, intent(in) :: L
double precision, dimension(nparticles), intent(in) :: position
double precision, dimension(ncells), intent(out) :: density

double precision :: sumBuf, offSum
integer :: pLower, pUpper, pBuf, numBuf, last, idx
double precision, dimension(nparticles) :: sorted

 print *, 'Fortran ', 'position length ', size(position), &
  'density length ', size(density), 'nparticles ', nparticles, &
  'ncells ', ncells, 'L ', L

end subroutine calc_density

f2py compile command:

f2py -c --fcompiler=gnu95 -m fortran_calc_density calc_density.f95

Relevant parts of the Python code:

from fortran_calc_density import calc_density as densityCalc
from numpy import array, float64

def calc_density(position, ncells, L):
  arg = array(position, dtype = float64, order = 'F')
  nparticles = len(position)
  density = densityCalc(position, nparticles,  ncells, L)

  print 'Python ', 'position length ', len(position), 'density length',  len(density), 'nparticles ', nparticles, 'ncells ', ncells, 'L ', L   
  return density

Sample of screen output showing the mismatch in all transferred variables:

Fortran position length           12 density length          100 nparticles           12 ncells          100 L    20.000000000000000    
Python  position length  100 density length  100 nparticles  100 ncells  20 L  12.5663706144

The print out from Python shows the values, except for the length of the density array which should be equal to ncells and therefore 20 by the design of the Fortran function , exactly as they should be. The Fortran values however are totally off so something must have happened during the transfer which scrambled the arguments around.

What am I doing wrong here?


Solution

  • Looking at the doc created by f2py (compiled with gfortran-5.3.0):

    >>> print calc_density.__doc__
    
    Wrapper for ``calc_density``.
    
    Parameters
    ----------
    position : input rank-1 array('d') with bounds (nparticles)
    ncells : input int
    l : input float
    
    
    Other Parameters
    ----------------
    nparticles : input int, optional
        Default: len(position)
    
    Returns
    -------
    density : rank-1 array('d') with bounds (cells)
    

    you can see that nparticles is optional (this is automatically done by f2py) and that the default value is len(position). By default, optional arguments are moved to the end of the argument list. Consequently, within your call, the last argument is interpreted as nparticles.

    You can either leave nparticles out of the function call or move it to the last argument. Both:

    density = densityCalc(position, ncells, L)
    density = densityCalc(position, ncells, L, nparticles)
    

    should result in the right results. If you want to keep the order of the fortran subroutine argument list, you also can use keywords:

    density = densityCalc(position=position, nparticles=nparticles, ncells=ncells, l=L)
    

    Note that fortran is not case sensitive so the keyword has to be lowercase l = L.