SWIG and Ctypes#
Interfacing Fortran code through C interface
I found this reference with good examples.
Problem: Collatz conjecture#
Choose \(u_0\)
If \(u_k\) even, \(u_{k+1} \rightarrow \frac{u_k}{2}\) ;
If \(u_k\) odd, \(u_{k+1} \rightarrow 3 u_k+1\)
The Collatz conjecture is: For all \(u_0>0\) , the process will eventually reach \(u_k=1\).
The programs below compute number of steps (named flight) to reach \(f(u_0)\) for \(1\leq u_0 \leq N\), \(N\) given.
C program#
%%file syracuse.c
#include <stdlib.h>
#include <stdio.h>
long syracuse(long n) {
long count = 0L ;
while (n > 1) {
if ((n&1)==0)
n /= 2;
else
n = 3*n+1;
count++;
}
return count ;
}
int main() {
const long N = 1000000;
double t1, t2;
long i , *flights ;
flights = (long*)malloc(N*sizeof(long));
for (i = 0; i <N; i++) flights[i] = syracuse(i+1);
return EXIT_SUCCESS;
}
Writing syracuse.c
%%bash
gcc -O3 syracuse.c
time ./a.out
real 0m0.001s
user 0m0.001s
sys 0m0.000s
Python program#
%%time
from itertools import count
def syracuse(n):
x = n
for steps in count() :
if x & 1 :
x = 3*x+1
else:
x = x // 2
if x == 1:
return steps
N = 1000000
flights = [syracuse(i) for i in range(1,N+1)]
CPU times: user 9.55 s, sys: 743 µs, total: 9.55 s
Wall time: 9.55 s
Performances#
The python syntax is simpler.
100 times slower
Solution : call the C function from python.
Ctypes#
This is the C function we will call from python
%%file syrac.c
long syracuse(long n)
{
long count = 0L ;
while (n > 1)
{
if ((n&1)==0)
n /= 2;
else
n = 3*n+1;
count++;
}
return count ;
}
Writing syrac.c
Build the shared library
%%bash
gcc -fPIC -shared -O3 \
-o syrac.so syrac.c
%%time
import time
from ctypes import *
syracDLL = CDLL("./syrac.so")
syracuse = syracDLL.syracuse
flights = [syracuse(i) for i in range(1,N+1)]
CPU times: user 346 ms, sys: 12 ms, total: 358 ms
Wall time: 358 ms
Ctypes with Fortran module#
If you change the fortran file you have to restart the kernel
%%file syrac.F90
module syrac_f90
use iso_c_binding
implicit none
contains
function f_syrac(n) bind(c, name='c_syrac') result(f)
integer(c_long) :: f
integer(c_long), intent(in), value :: n
integer(c_long) :: x
x = n
f = 0_8
do while(x>1)
if (iand(x,1_8) == 0) then
x = x / 2
else
x = 3*x+1
end if
f = f + 1_8
end do
end function f_syrac
end module syrac_f90
Writing syrac.F90
%%bash
rm -f *.o *.so *.dylib
gfortran -fPIC -shared -O3 -o syrac.dylib syrac.F90
from ctypes import *
syrac_f90 = CDLL('./syrac.dylib')
syrac_f90.c_syrac.restype = c_long
syrac_f90.c_syrac(1000)
111
%%time
N = 1000000
flights = [syrac_f90.c_syrac(i) for i in range(1,N+1)]
CPU times: user 408 ms, sys: 4 ms, total: 412 ms
Wall time: 411 ms
Faster than pure Python
We can call function from DLL windows libraries.
Unfortunately you need to adapt the syntax to the operating system.
SWIG#
Interface file syrac.i for C function in syrac.c
%%file syrac.i
%module syracuseC
%{
extern long syracuse(long n);
%}
extern long syracuse(long n);
Writing syrac.i
%%bash
swig -python syrac.i
Build the python module#
Using command line
swig -python syrac.i
gcc `python3-config --cflags` -fPIC \
-shared -O3 -o _syracuseC.so syrac_wrap.c syrac.c `python3-config --ldflags`
With distutils
%%file setup.py
from numpy.distutils.core import Extension, setup
module_swig = Extension('_syracuseC', sources=['syrac_wrap.c', 'syrac.c'])
setup( name='Syracuse',
version = '0.1.0',
author = "Pierre Navaro",
description = """Simple C Fortran interface example """,
ext_modules = [module_swig],
)
Writing setup.py
import sys, os
if sys.platform == "darwin":
os.environ["CC"] = "gcc-10"
!{sys.executable} setup.py build_ext --inplace --quiet
/home/runner/work/python-fortran/python-fortran/notebooks/setup.py:1: DeprecationWarning:
`numpy.distutils` is deprecated since NumPy 1.23.0, as a result
of the deprecation of `distutils` itself. It will be removed for
Python >= 3.12. For older Python versions it will remain present.
It is recommended to use `setuptools < 60.0` for those Python versions.
For more details, see:
https://numpy.org/devdocs/reference/distutils_status_migration.html
from numpy.distutils.core import Extension, setup
Traceback (most recent call last):
File "/home/runner/work/python-fortran/python-fortran/notebooks/setup.py", line 1, in <module>
from numpy.distutils.core import Extension, setup
File "/usr/share/miniconda/envs/python-fortran/lib/python3.9/site-packages/numpy/distutils/core.py", line 24, in <module>
from numpy.distutils.command import config, config_compiler, \
File "/usr/share/miniconda/envs/python-fortran/lib/python3.9/site-packages/numpy/distutils/command/config.py", line 19, in <module>
from numpy.distutils.mingw32ccompiler import generate_manifest
File "/usr/share/miniconda/envs/python-fortran/lib/python3.9/site-packages/numpy/distutils/mingw32ccompiler.py", line 27, in <module>
from distutils.msvccompiler import get_build_version as get_build_msvc_version
ModuleNotFoundError: No module named 'distutils.msvccompiler'
import _syracuseC
syracuse = _syracuseC.syracuse
syracuse(1000)
---------------------------------------------------------------------------
ModuleNotFoundError Traceback (most recent call last)
Cell In[15], line 1
----> 1 import _syracuseC
3 syracuse = _syracuseC.syracuse
4 syracuse(1000)
ModuleNotFoundError: No module named '_syracuseC'
%%time
N=1000000
flights = [syracuse(i) for i in range(1,N+1)]
CPU times: user 260 ms, sys: 4 ms, total: 264 ms
Wall time: 263 ms
References#
Python Scripting for Computational Science de Hans Petter Langtangen chez Springer