How to get values from Fortran DLL using Python? - python

I have Fortran dll(FortDll.dll) similar to;
integer function FortAddr(x) bind(c, name="FORTADDR") RESULT(res)
!DEC$ ATTRIBUTES DLLEXPOR::FortAddr
use, intrinsic :: iso_c_binding
integer(kind=c_int), intent(in) :: x
integer :: y
y = 2*x
res = loc(y)
end function
I want to import Fortran data to visualize using python.
And from python, I call it as
import ctypes
flib = CDLL('FortDll.dll')
x = c_int(15)
addrx = flib.FORTADDR(byref(x))
val = ctypes.cast(addrx, ctypes.py_object).value
But Python script stops at ctypes.cast. How can I get the value in Fortran dll?

Related

Issues Transferring Numpy array into Fortran using Ctypes

I am trying to wrap some fortran code into python using the ctypes library but have am having major issues with the data transfer. When I print the data from my python script, it looks substantially different from when I print it within the fortran code. Can someone please help me figure out what is going on here? I tried playing around with the datatypes which did not fix the solution and so far I have not found any other SO questions that have addressed my issue. Also please note that using f2py will not work for the end product that this example refers too.
Below is an example of my code:
temp.f90
subroutine test(num_mod_sel,goodval,x,y,coeff,coeff_flag)
implicit none
! Input/Output
integer num_mod_sel, goodval
real, dimension(goodval,num_mod_sel) :: x
real, dimension(goodval) :: y
real, dimension(num_mod_sel) :: coeff
integer, dimension(num_mod_sel) :: coeff_flag
print*, num_mod_sel,goodval,x
return
end subroutine test
!===================================================================================================!
The above f90 code is compiled with:
gfortran -shared -fPIC -o temp.so temp.f90
test.py
from ctypes import CDLL,cdll, POINTER, c_int, c_float
import numpy as np
def test_fcode(num_mod_sel,goodval,x,y,coeff,coeff_flag):
fortran = CDLL('./temp.so')
fortran.test_.argtypes = [ POINTER(c_int),
POINTER(c_int),
POINTER(c_float),
POINTER(c_float),
POINTER(c_float),
POINTER(c_int) ]
fortran.test_.restype = None
num_mod_sel_ = c_int(num_mod_sel)
goodval_ = c_int(goodval)
x_ = x.ctypes.data_as(POINTER(c_float))
y_ = y.ctypes.data_as(POINTER(c_float))
coeff_ = coeff.ctypes.data_as(POINTER(c_float))
coeff_flag_ = coeff_flag.ctypes.data_as(POINTER(c_int))
fortran.test_(num_mod_sel_,goodval_,x_,y_,coeff_,coeff_flag_)
#Create some test data
num_mod_sel = 4
goodval = 10
x = np.full((num_mod_sel,goodval),999.,dtype=float)
x[:] = np.random.rand(num_mod_sel,goodval)
y = np.full(goodval,999.,dtype=float)
y[:] = np.random.rand(goodval)
coeff = np.empty(num_mod_sel,dtype=float)
coeff_flag = np.empty(num_mod_sel,dtype=int)
#Run the fortran code
test_fcode(num_mod_sel,goodval,x,y,coeff,coeff_flag)
print(x) from the python code:
[[0.36677304 0.8734628 0.72076823 0.20234787 0.91754331 0.26591916
0.46325577 0.00334941 0.98890871 0.3284262 ]
[0.15428096 0.24979671 0.97374747 0.83996786 0.59849493 0.55188578
0.9668523 0.98441142 0.50954678 0.22003844]
[0.54362548 0.42636074 0.65118397 0.69455346 0.30531619 0.88668116
0.97278714 0.29046492 0.64851937 0.64885967]
[0.31798739 0.37279389 0.88855305 0.38754276 0.94985151 0.56566525
0.99488508 0.13812829 0.0940132 0.07921261]]
print*, x from f90:
-2.91465824E-17 1.68338645 13.0443134 1.84336567 -7.44153724E-34 1.80519199 -2.87629426E+27 1.57734776 -1297264.38 1.85438573 -236487.531 1.63295949 -1.66118658E-33 1.73162782 -6.73423983E-09 0.919681191 -1.09687280E+21 1.87222707 5.50313165E+09 1.66421306 8.38275158E+34 1.52928090 -2.15154066E-13 1.62479663 3.88800366E+30 1.86843681 127759.977 1.83499193 -3.55062879E+15 1.77462363 2.43241945E+19 1.76297140 3.16150975E-03 1.86671305 1.35183692E+21 1.87110281 1.74403865E-31 1.75238669 9.85857248E-02 1.59503841 -2.33541620E+30 1.79045486 -1.86185171E+11 1.78229403 4.23132255E-20 1.81525886 2.96771497E-04 1.82888138 -4.55096013E-26 1.86097753 0.00000000 3.68934881E+19 -7.37626273E+15 1.58494916E+29 0 -1064355840 -646470284 -536868869
The problem is a mismatch of datatypes.
The Fortran real is usually a 32 bit float (C float) while numpy interprets the Python datatype float as numpy.float_ which is an alias of numpy.float64, the C double with 64 bits.
Solution: In Python use numpy.float32 as dtype for numpy array creation.

Using parallel NetCDF to save a distributed 3D complex array

I have an MPI-based program written in Fortran which produces a 3D array of complex data at each node (sections of a 2D time-series). I would like to use parallel I/O to write these arrays to a single file which can be relatively easily opened in python for further analysis/visualization. Ideally I would like the solution to be memory efficient (i.e. avoid the creation of intermediate temporary arrays).
Using NetCDF, I have managed to adapt a subroutine which achieves this for a 3D array of real numbers. However, I've hit a stumbling block when it comes to complex arrays.
In the following code I have attempted to extend the subroutine from reals to complex numbers by creating a compound datatype consisting of two reals and assuming that the real and imaginary components of the Fortran complex datatype are stored contiguously in the 1st dimension of the 3D array.
module IO
use NetCDF
use, intrinsic :: iso_fortran_env, only: dp => real64
implicit none
contains
subroutine output_3D(dataname, starts, ends, global_data_dims, &
local_data, MPI_communicator)
character(len=*), intent(in) :: dataname
integer, dimension(3), intent(in) :: starts
integer, dimension(3), intent(in) :: ends
integer, dimension(3), intent(in) :: global_data_dims
complex(dp), intent(in) :: local_data( 1:(ends(1) - starts(1)+ 1), &
1:(ends(2) - starts(2) + 1), &
1:(ends(3) - starts(3) + 1))
integer, dimension(3) :: expanded_starts
integer, intent(in) :: MPI_communicator
integer :: ncid, varid, dimid(3)
integer :: counts(3)
integer :: typeid
expanded_starts(1) = (starts(1))* 2 + 1
expanded_starts = starts(2)
expanded_starts(3) = starts(3)
call check(nf90_create( trim(dataname)//'.cdf', &
IOR(NF90_NETCDF4, NF90_MPIIO), &
ncid, &
comm = MPI_communicator, &
info = MPI_INFO_NULL))
call check(nf90_def_dim(ncid, "x", global_data_dims(1), dimid(1)))
call check(nf90_def_dim(ncid, "y", global_data_dims(2) * 2, dimid(2)))
call check(nf90_def_dim(ncid, "z", global_data_dims(3), dimid(3)))
! define a complex data type consisting of two real(8)
call check(nf90_def_compound(ncid, 16, "COMPLEX", typeid))
call check(nf90_insert_compound(ncid, typeid, "REAL", 0, NF90_DOUBLE))
call check(nf90_insert_compound(ncid, typeid, "IMAG", 8, NF90_DOUBLE))
! define a 3D variable of type "complex"
call check(nf90_def_var(ncid, dataname, typeid, dimid, varid))
! exit define mode
call check(nf90_enddef(ncid))
! Now in NETCDF data mode
! set to use MPI/PnetCDF collective I/O
call check(nf90_var_par_access(ncid, varid, NF90_COLLECTIVE))
counts(1) = (ends(1) - starts(1) + 1) * 2
counts(2) = (ends(2) - starts(2) + 1)
counts(3) = (ends(3) - starts(3) + 1)
call check(nf90_put_var(ncid, &
varid, &
local_data, &
start = expanded_starts,&
count = counts))
! close the file
call check(nf90_close(ncid))
return
end subroutine output_3D
subroutine check(status)
integer, intent ( in) :: status
if(status /= nf90_noerr) then
print *, trim(nf90_strerror(status))
stop 2
end if
end subroutine check
end module IO
program test_write
use IO
use MPI
complex(dp) :: data(2,2,3)
integer :: flock
integer :: rank
integer :: ierr
integer :: i, j, k
call MPI_init(ierr)
call MPI_comm_size(MPI_comm_world, flock, ierr)
call MPI_comm_rank(MPI_comm_world, rank, ierr)
do k = 1, 3
do j = 1, 2
do i = 1, 2
data(i,j,k) = cmplx(i, j, 8)
enddo
enddo
enddo
if (rank == 0) then
call output_3D_hdf5('out', [1,1,1], [2,2,3], [2,2,6], &
data, MPI_comm_world)
else
call output_3D_hdf5('out', [1,1,4], [2,2,6], [2,2,6], &
data, MPI_comm_world)
endif
call MPI_finalize(ierr)
end program test_write
The above code results in an "There is no specific function for nf90_put_var" error on compilation. This indicates that the function is not happy with the data type of the input array, so clearly there is something I'm missing regarding the usage of compound data types.
EDIT: One simple workaround is to assign the complex array to a real pointer as described in this post. The array can then be reshaped/recast using numpy to arrive at the complex array in python. It's a bit clunky, and somewhat dissatisfying - but is probably good enough for my purposes for now.
This is only a partial answer for reasons you will see below - but it is too long for a comment. Hopefully I will be able to find the missing info and "upgrade" it, but this is what I have so far.
If you look in the NetCDF4 documentation under the "Compound type introduction" at https://www.unidata.ucar.edu/software/netcdf/fortran/docs/f90-user-defined-data-types.html#f90-compound-types-introduction you will see:
To write data in a compound type, first use nf90_def_compound to create the type, multiple calls to nf90_insert_compound to add to the compound type, and then write data with the appropriate nf90_put_var1, nf90_put_vara, nf90_put_vars, or nf90_put_varm call.
Note it doesn't mention nf90_put_var at all, but 4 different functions. This makes some kind of sense, nf90_put_var is presumably nicely overloaded to deal with all intrinsic types NetCDF support (and it is utterly crap it doesn't support complex), so for non-intrinsic type presumably there is some C like interface with something like a void *, and I'm guessing it is this that the four functions mentioned above implement.
So far so good, you should call one of nf90_put_var1, nf90_put_vara, nf90_put_vars, or nf90_put_varm rather than nf90_put_var. Now the bad news - I can't find any documentation for these 4 functions. The equivalent C functions are at https://www.unidata.ucar.edu/software/netcdf/docs/group__variables.html so you might be able to work out what is required from there, but it's not very good - I'd at the very least put in a bug report to Unidata, but that said for me lack of intrinsic support for complex is enough to make me look elsewhere for my I/O solution ...
While I am here you really shouldn't use explicit numbers for kinds for variables, I can show you compilers where complex(8) will fail to compile. Instead use Selected_real_kind or similar, or use the constant in the intrinsic module iso_fortran_env, or possibly those in iso_c_binding, and the fact that the kind of a complex number is the same as the kind of the reals that compose it.

Writing Fortran subroutine outputs to a file Running from Python interface

Below is the Fortran code that I am running and I want to save the Qr values to a file. This subroutine is called and executed in python.
subroutine thrustTorque(n, Np, Tp, r, precurve, presweep, precone, &
Rhub, Rtip, precurveTip, presweepTip, T, Q)
implicit none
integer, parameter :: dp = kind(0.d0)
! in
integer, intent(in) :: n
real(dp), dimension(n), intent(in) :: Np, Tp, r, precurve, presweep
real(dp), intent(in) :: precone, Rhub, Rtip, precurveTip, presweepTip
! out
real(dp), intent(out) :: T, Q
! local
real(dp) :: ds
real(dp), dimension(n+2) :: rfull, curvefull, sweepfull, Npfull, Tpfull
real(dp), dimension(n+2) :: thrust, torque, x_az, y_az, z_az, cone, s
integer :: i
There is long list of more variables and their definations here, which I am skipping.
cone =0.0_dp
z_az = 0.0_dp
! integrate Thrust and Torque (trapezoidal)
thrust = Npfull*cos(cone)
torque = Tpfull*z_az
Now here Qr(i) values I want to be saved in a file.
T = 0.0_dp
do i = 1, n+1
ds = s(i+1) - s(i)
T = T + 0.5_dp*(thrust(i) + thrust(i+1))*ds
Q = Q + 0.5_dp*(torque(i) + torque(i+1))*ds
Qr(i) = Q
end do
end subroutine thrustTorque
I tried this:
T = 0.0_dp
open (1, file = 'data1.dat', status ='new')
do i = 1, n+1
ds = s(i+1) - s(i)
T = T + 0.5_dp*(thrust(i) + thrust(i+1))*ds
Q = Q + 0.5_dp*(torque(i) + torque(i+1))*ds
Qr(i) = Q
write(1, *) Qr(i)
end do
close(1)
end subroutine thrustTorque
This subroutine is called in python using:
T, Q = _oxi.thrustTorque(Np, Tp, *args)
I cannot return the values of Qr as this is also linked to other areas of the code and will require many changes. Instead, I would prefer if I can print the output in Terminal or save them in a file.
Although the program is executed I don't see the results being saved in a file or even a file being created.
Several issues stand out:
You use file unit 1 -- that's not a good idea. Fortran uses these low numbers often for specific units, i.e. standard out, error out, standard in. Better use this syntax:
integer :: u ! unit for file i/o
open(newunit=u, file='data1.dat', status='new', action='write')
do
...
end do
That way, you can be sure that the unit number is free.
The write(*, *) <data> always writes to standard out -- you should have seen the values being displayed on screen when you ran it. In order to write to a file, you need to replace the first * of the write statement with the file unit.
write(u, *) Qr(i)
Oki #francescalus was correct in the comments it was still using the old version, I had to update my code after the changes so that interface knows an update has been made. Do this using: f2py -c -m codename codename.f90
Edit: Oki after running some tests on this, I am able to print and create a file but this has to be in a separate subroutine, I don't understand this. looks like it has something to do with import function. import _codename is diffrent from import codename. If anyone can explain this, please let me know.

f2py: printing a function

I try to build a very simple example of f2py program to print the result of a function. The program is build as follow:
1) a main program in which I call the function:
subroutine function_main
use parameters
implicit none
!-------------------------------------!
integer, PARAMETER :: n = 2 ! dimension of the function ( = num. of variables)
real*8, parameter :: a = 0.001d0
!-------------------------------------!
real*8, DIMENSION(n) :: x ! this is the vector
integer :: m
m = n
! DECLARE THE variables !
x(1) = 0.d0
x(2) = 0.001d0
print*, 'chi2(x,m)'
print*, chi2(x,m)
end subroutine function_main
2) a module in which I define the function:
MODULE parameters
contains
real*8 function chi2(x,m)
implicit none
!-------------------------------------!
integer, PARAMETER :: n = 2 ! dimension of the function ( = num. of variables)
real*8, parameter :: a = 0.001d0
!-------------------------------------!
real*8, dimension(n) :: var
real*8, DIMENSION(n) :: x ! defined in the main program
integer :: m
var(1)=x(1)
var(2)=x(2)-asin(x(2))
chi2 = 1.d0 + (var(2)-a)**2.d0
end function
end module parameters
I post it the modify version of the program as should be used with f2py ( http://websrv.cs.umt.edu/isis/index.php/F2py_example I follow this guide). I compile and launch the program with the following commands:
f2py -m function_main -h function_main.pyf function_module.f90 function_main.f90
and
f2py -c --fcompiler=gnu95 test_basic.pyf function_module.f90 function_main.f90
It create the following function_main.pyf file (I link the file here), and I use the Ipython script to launch it:
import function_main
def run():
n = 2
m = 2
a = 0.001
funtion_main.function_main()
if __name__=='__main__':
print function_main.chi2
I think that the only problem is in this script because I am able to create the
function_main.so needed to launch the program via python. In fact the errors I receive is the follow:
AttributeError: 'module' object has no attribute 'chi2'
Could someone help me? Thanks a lot !

Complex number in ctypes

This might be a bit foolish but I'm trying to use ctypes to call a function that receives a complex vector as a paramter. But in ctypes there isn't a class c_complex.
Does anybody know how to solve this?
edit: I'm refering to python's ctypes, in case there are others....
I don't really like the #Biggsy answer above, because it requires additional wrapper written in c/fortran. For a simple case it does not matter, but if you want to use external libraries (like lapack) that would require you to make some proxy dll for every function/library you want to call and it probably won't be fun and many things can go wrong (like linkage, reusability e.t.c). My approach relies only on python with the aid of ctypes and numpy. Hope this will be helpful.
In the example below I show how to pass complex numbers/arrays between python, numpy (both are essentially C) and pure C or fortran (i use fortran, but for C it is exactly the same on python side).
First lets make a dummy fortran (f90) library, say f.f90, with C interface:
! print complex number
subroutine print_c(c) bind(c)
use iso_c_binding
implicit none
complex(c_double_complex), intent(in) :: c
print "(A, 2F20.16)", "#print_c, c=", c
end subroutine print_c
! multiply real numbers
real(c_double) function mtp_rr(r1,r2) bind(c)
use iso_c_binding
implicit none
real(c_double), intent(in) :: r1,r2
print "(A)", "#mpt_rr" ! make sure its fortran
mtp_rr = r1 * r2
end function mtp_rr
! multiply complex numbers
complex(c_double_complex) function mtp_cc(c1,c2) bind(c)
use iso_c_binding
implicit none
complex(c_double_complex), intent(in) :: c1,c2
print "(A)", "#mpt_cc" ! make sure its fortran
mtp_cc = c1 * c2
return
end function mtp_cc
! print array of complex numbers
subroutine print_carr(n, carr) bind(c)
use iso_c_binding
implicit none
integer(c_long), intent(in) :: n
integer(c_long) :: i
complex(c_double_complex), intent(in) :: carr(n)
print "(A)", "#print_carr"
do i=1,n
print "(I5,2F20.16)", i, carr(i)
end do
end subroutine print_carr
The library can be simply compiled as usual (in this example into libf.so). Notice that each subroutine contains its own "use" statement, this can be avoided if declare "use iso_c_binding" on module level. (I don't know why gfortran understands type of functions without global use of iso_c_binding, but it works and its fine for me.)
gfortran -shared -fPIC f.f90 -o libf.so
Then i created a python script, say p.py, with the following content:
#!/usr/bin/env python3
import ctypes as ct
import numpy as np
## ctypes 1.1.0
## numpy 1.19.5
# print("ctypes", ct.__version__)
# print("numpy", np.__version__)
from numpy.ctypeslib import ndpointer
## first we prepare some datatypes
c_double = ct.c_double
class c_double_complex(ct.Structure):
"""complex is a c structure
https://docs.python.org/3/library/ctypes.html#module-ctypes suggests
to use ctypes.Structure to pass structures (and, therefore, complex)
"""
_fields_ = [("real", ct.c_double),("imag", ct.c_double)]
#property
def value(self):
return self.real+1j*self.imag # fields declared above
c_double_complex_p = ct.POINTER(c_double_complex) # pointer to our complex
## pointers
c_long_p = ct.POINTER(ct.c_long) # pointer to long (python `int`)
c_double_p = ct.POINTER(ct.c_double) # similar to ctypes.c_char_p, i guess?
# ndpointers work well with unknown dimension and shape
c_double_complex_ndp = ndpointer(np.complex128, ndim=None)
### ct.c_double_complex
### > AttributeError: module 'ctypes' has no attribute 'c_double_complex'
## then we prepare some dummy functions to simplify argument passing
b_long = lambda i: ct.byref(ct.c_long(i))
b_double = lambda d: ct.byref(ct.c_double(d))
b_double_complex = lambda c: ct.byref(c_double_complex(c.real, c.imag))
## now we load the library
libf = ct.CDLL("libf.so")
## and define IO types of its functions/routines
print_c = libf.print_c
print_c.argtypes = [c_double_complex_p] # fortran accepes only references
print_c.restype = None # fortran subroutine (c void)
mtp_rr = libf.mtp_rr
mtp_rr.argtypes = [c_double_p, c_double_p]
mtp_rr.restype = c_double # ctypes.c_double
mtp_cc = libf.mtp_cc
mtp_cc.argtypes = [c_double_complex_p, c_double_complex_p]
mtp_cc.restype = c_double_complex # custom c_double_complex
print_carr = libf.print_carr
print_carr.argtypes = [c_long_p, c_double_complex_ndp]
print_carr.restype = None
## finally we can prepare some data and test what we got
print("test print_c")
c = 5.99j+3.1234567890123456789
print_c(b_double_complex(c)) # directly call c/fortran function
print(c)
print("\ntest mtp_rr")
d1 = 2.2
d2 = 3.3
res_d = mtp_rr(b_double(d1),b_double(d2))
print("d1*d2 =", res_d)
print("\ntest mtp_cc")
c1 = 2j+3
c2 = 3j
res = mtp_cc(b_double_complex(c1), b_double_complex(c2))
print(res.value)
print("\ntest print_carr")
n = 10
arr = np.arange(n) + 10j * np.arange(10)
print("arr.shape =",arr.shape)
print_carr(b_long(n), arr)
arr = arr.reshape((5,2)) # still contiguous chunk of memory
print("arr.shape =",arr.shape)
print_carr(b_long(n), arr) # same result
print("done")
and everything works. I have no idea why something like this is not implemented yet in ctypes.
Also related to other suggestions here: you can pass complex numbers by making new ctypes type
__c_double_complex_p = ct.POINTER(2*ct.c_double)
dll.some_function.argtypes = [__c_double_complex_p, ...]
but you can not retrieve results in that way! To set proper restype only method via class worked for me.
As noted by the OP in their comment on #Armin Rigo's answer, the correct way to do this is with wrapper functions. Also, as noted in the comments (by me) on the original question, this is also possible for C++ and Fortran. However, the method for getting this to work in all three languages is not necessarily obvious. Therefore, this answer presents a working example for each language.
Let's say you have a C/C++/Fortran procedure that takes a scalar complex argument. Then you would need to write a wrapper procedure that takes two floats / doubles (the real and imaginary parts), combines them into a complex number and then calls the original procedure. Obviously, this can be extended to arrays of complex numbers but let's keep it simple with a single complex number for now.
C
For example, let's say you have a C function to print a formatted complex number. So we have my_complex.c:
#include <stdio.h>
#include <complex.h>
void print_complex(double complex z)
{
printf("%.1f + %.1fi\n", creal(z), cimag(z));
}
Then we would have to add a wrapper function (at the end of the same file) like this:
void print_complex_wrapper(double z_real, double z_imag)
{
double complex z = z_real + z_imag * I;
print_complex(z);
}
Compile that into a library in the usual way:
gcc -shared -fPIC -o my_complex_c.so my_complex.c
Then call the wrapper function from Python:
from ctypes import CDLL, c_double
c = CDLL('./my_complex_c.so')
c.print_complex_wrapper.argtypes = [c_double, c_double]
z = complex(1.0 + 1j * 2.0)
c.print_complex_wrapper(c_double(z.real), c_double(z.imag))
C++
The same thing in C++ is a bit more fiddly because an extern "C" interface needs to be defined to avoid name-mangling and we need to deal with the class in Python (both as per this SO Q&A).
So, now we have my_complex.cpp (to which I have already added the wrapper function):
#include <stdio.h>
#include <complex>
class ComplexPrinter
{
public:
void printComplex(std::complex<double> z)
{
printf("%.1f + %.1fi\n", real(z), imag(z));
}
void printComplexWrapper(double z_real, double z_imag)
{
std::complex<double> z(z_real, z_imag);
printComplex(z);
}
};
We also need to add an extern "C" interface (at the end of the same file) like this:
extern "C"
{
ComplexPrinter* ComplexPrinter_new()
{
return new ComplexPrinter();
}
void ComplexPrinter_printComplexWrapper(ComplexPrinter* printer, double z_real, double z_imag)
{
printer->printComplexWrapper(z_real, z_imag);
}
}
Compile into a library in the usual way:
g++ -shared -fPIC -o my_complex_cpp.so my_complex.cpp
And call the wrapper from Python:
from ctypes import CDLL, c_double
cpp = CDLL('./my_complex_cpp.so')
cpp.ComplexPrinter_printComplexWrapper.argtypes = [c_double, c_double]
class ComplexPrinter:
def __init__(self):
self.obj = cpp.ComplexPrinter_new()
def printComplex(self, z):
cpp.ComplexPrinter_printComplexWrapper(c_double(z.real), c_double(z.imag))
printer = ComplexPrinter()
z = complex(1.0 + 1j * 2.0)
printer.printComplex(z)
Fortran
And finally in Fortran we have the original subroutine in my_complex.f90:
subroutine print_complex(z)
use iso_c_binding, only: c_double_complex
implicit none
complex(c_double_complex), intent(in) :: z
character(len=16) :: my_format = "(f4.1,a3,f4.1,a)"
print my_format, real(z), " + ", aimag(z), "i"
end subroutine print_complex
To which we add the wrapper function (at the end of the same file):
subroutine print_complex_wrapper(z_real, z_imag) bind(c, name="print_complex_wrapper")
use iso_c_binding, only: c_double, c_double_complex
implicit none
real(c_double), intent(in) :: z_real, z_imag
complex(c_double_complex) :: z
z = cmplx(z_real, z_imag)
call print_complex(z)
end subroutine print_complex_wrapper
Then compile into a library in the usual way:
gfortran -shared -fPIC -o my_complex_f90.so my_complex.f90
And call from Python (note the use of POINTER):
from ctypes import CDLL, c_double, POINTER
f90 = CDLL('./my_complex_f90.so')
f90.print_complex_wrapper.argtypes = [POINTER(c_double), POINTER(c_double)]
z = complex(1.0 + 1j * 2.0)
f90.print_complex_wrapper(c_double(z.real), c_double(z.imag))
I think that you mean the C99 complex types, e.g. "_Complex double". These types are not natively supported by ctypes. See for example the discussion there: https://bugs.python.org/issue16899
My problem was to pass a numpy array of complex values from Python to C++.
The steps followed where:
Creating a numpy array in Python as
data=numpy.ascontiguousarray(data,dtype=complex)
Creating a double pointer to the data.
c_double_p = ctypes.POINTER(ctypes.c_double)
data_p = data.ctypes.data_as(c_double_p)
Obtaining the value in C++ by defining the parameter as
extern "C" void sample(std::complex < double > *data_p)
If c_complex is a C struct and you have the definition of in documentation or a header file you could utilize ctypes to compose a compatible type. It is also possible, although less likely, that c_complex is a typdef for a type that ctypes already supports.
More information would be needed to provide a better answer...
Use c_double or c_float twice, once for real and once for imaginary.
For example:
from ctypes import c_double, c_int
dimType = c_int * 1
m = dimType(2)
n = dimType(10)
complexArrayType = (2 * c_double) * ( n * m )
complexArray = complexArrayType()
status = yourLib.libcall(m,n,complexArray)
on the library side (fortran example):
subroutine libcall(m,n,complexArrayF) bind(c, name='libcall')
use iso_c_binding, only: c_double_complex,c_int
implicit none
integer(c_int) :: m, n
complex(c_double_complex), dimension(m,n) :: complexArrayF
integer :: ii, jj
do ii = 1,m
do jj = 1,n
complexArrayF(ii,jj) = (1.0, 0.0)
enddo
enddo
end subroutine

Categories