0

I use a dll that contains differential equation solvers among other useful mathematical tools. Unfortunately, this dll is written in Fortran. My program is written in python 3.7 and I use spyder as an IDE.

I successfully called easy functions from the dll. However, I can't seem to get functions to work that require multidimensional arrays.

This is the online documentation to the function I am trying to call: https://www.nag.co.uk/numeric/fl/nagdoc_fl26/html/f01/f01adf.html

The kernel dies without an error message if I execute the following code:

import numpy as np
import cffi as cf

ffi=cf.FFI()
lib=ffi.dlopen("C:\Windows\SysWOW64\DLL20DDS")
ffi.cdef("""void F01ADF (const int *n, double** a, const int *lda, int *ifail);""")

#Integer
nx = 4
n = ffi.new('const int*', nx)
lda = nx + 1
lda = ffi.new('const int*', lda)
ifail = 0
ifail = ffi.new('int*', ifail)

#matrix to be inversed

ax1 = np.array([5,7,6,5],dtype = float, order = 'F')
ax2 = np.array([7,10,8,7],dtype = float, order = 'F')
ax3 = np.array([6,8,10,9],dtype = float, order = 'F')
ax4 = np.array([5,7,9,10], dtype = float, order = 'F')
ax5 = np.array([0,0,0,0], dtype = float, order = 'F')

ax = (ax1,ax2,ax3,ax4,ax5)

#Array
zx = np.zeros(nx, dtype = float, order = 'F')

a = ffi.cast("double** ", zx.__array_interface__['data'][0])
for i in range(lda[0]):
    a[i] = ffi.cast("double* ", ax[i].__array_interface__['data'][0])

lib.F01ADF(n, a, lda, ifail)

Since function with 1D arrays work I assume that the multidimensional array is the issues.

Any kind of help is greatly appreciated, Thilo

ThlHckmnn
  • 5
  • 4

1 Answers1

1

Not having access to the dll you refer to complicates giving a definitive answer, however, the documentation of the dll and the provided Python script may be enough to diagnose the problem. There are at least two issues in your example:

  • The C header interface:

    Your documentation link clearly states what the function's C header interface should look like. I'm not very well versed in C, Python's cffi or cdef, but the parameter declaration for a in your function interface seems wrong. The double** a (pointer to pointer to double) in your function interface should most likely be double a[] or double* a (pointer to double) as stated in the documentation.

  • Defining a 2d Numpy array with Fortran ordering:

    1. Note that your Numpy arrays ax1..5 are one dimensional arrays, since the arrays only have one dimension order='F' and order='C' are equivalent in terms of memory layout and access. Thus, specifying order='F' here, probably does not have the intended effect (Fortran using column-major ordering for multi-dimensional arrays).
    2. The variable ax is a tuple of Numpy arrays, not a 2d Numpy array, and will therefore have a very different representation in memory (which is of utmost importance when passing data to the Fortran dll) than a 2d array.

Towards a solution

My first step would be to correct the C header interface. Next, I would declare ax as a proper Numpy array with two dimensions, using Fortran ordering, and then cast it to the appropriate data type, as in this example:

#file: test.py
import numpy as np
import cffi as cf

ffi=cf.FFI()
lib=ffi.dlopen("./f01adf.dll")
ffi.cdef("""void f01adf_ (const int *n, double a[], const int *lda, int *ifail);""")

# integers
nx = 4
n = ffi.new('const int*', nx)
lda = nx + 1
lda = ffi.new('const int*', lda)
ifail = 0
ifail = ffi.new('int*', ifail)

# matrix to be inversed
ax = np.array([[5,  7,  6,  5],
               [7, 10,  8,  7],
               [6,  8, 10,  9],
               [5,  7,  9, 10],
               [0,  0,  0,  0]], dtype=float, order='F')

#  operation on matrix using dll
print("BEFORE:")
print(ax.astype(int))

a = ffi.cast("double* ", ax.__array_interface__['data'][0])
lib.f01adf_(n, a, lda, ifail)

print("\nAFTER:")
print(ax.astype(int))

For testing purposes, consider the following Fortran subroutine that has the same interface as your actual dll as a substitute for your dll. It will simply add 10**(i-1) to the i'th column of input array a. This will allow checking that the interface between Python and Fortran works as intended, and that the intended elements of array a are operated on:

!file: f01adf.f90
Subroutine f01adf(n, a, lda, ifail)
  Integer, Intent (In) :: n, lda
  Integer, Intent (Inout) :: ifail
  Real(Kind(1.d0)), Intent (Inout) :: a(lda,*)

  Integer :: i

  print *, "Fortran DLL says: Hello world!"

  If ((n < 1) .or. (lda < n+1)) Then
    ! Input variables not conforming to requirements
    ifail = 2

  Else
    ! Input variables acceptable
    ifail = 0

    ! add 10**(i-1) to the i'th column of 2d array 'a'
    Do i = 1, n
      a(:, i) = a(:, i) + 10**(i-1)
    End Do

  End If

End Subroutine

Compiling the Fortran code, and then running the suggested Python script, gives me the following output:

> gfortran -O3 -shared -fPIC -fcheck=all -Wall -Wextra -std=f2008 -o f01adf.dll f01adf.f90
> python test.py
  BEFORE:
  [[ 5  7  6  5]
  [ 7 10  8  7]
  [ 6  8 10  9]
  [ 5  7  9 10]
  [ 0  0  0  0]]

  Fortran DLL says: Hello world!

  AFTER:
  [[   6   17  106 1005]
  [   8   20  108 1007]
  [   7   18  110 1009]
  [   6   17  109 1010]
  [   1   10  100 1000]]
jbdv
  • 1,263
  • 1
  • 11
  • 18