Complex number in ctypes

complex-numbers, ctypes, python

Solution

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))

Problem

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....

Original source

Related problems