"We consider here wrapping two static languages: C and fortran.\n",
"\n",
"We classically wrapp already existing code to access them via python. \n",
"\n"
"\n",
"Depending on the language to wrap the tool to use are a bit different. \n"
]
},
{
...
...
@@ -479,6 +480,21 @@
"source": [
"sq = dllib.square(2.0, 3.0)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Alternatives techniques: \n",
"------------------------------------\n",
"\n",
"The historical tool is swig (http://swig.org/). It allows to access C/C++ code from a variety of languages. \n",
"It requires the writing of an intermediate file that describes the C API. \n",
"\n",
"From now wrapping C code can be done quite easily using CFFI as presented before. \n",
"\n",
"For wrapping C++ code, one will consider pybind11 (https://github.com/pybind/pybind11) that relies on features available from the 11 versions of C++. "
]
}
],
"metadata": {
...
...
%% Cell type:markdown id: tags:
# Wrapping codes in static languages
%% Cell type:markdown id: tags:
We consider here wrapping two static languages: C and fortran.
We classically wrapp already existing code to access them via python.
Depending on the language to wrap the tool to use are a bit different.
%% Cell type:markdown id: tags:
## Fortran with [f2py](https://docs.scipy.org/doc/numpy/f2py/)
`f2py` is a tool that allows to call Fortran code into Python. It is a part of `numpy` meaning that to use it, we only need to install and import numpy (which should already be done if you do scientific Python !) :
```bash
pip3 install numpy
```
%% Cell type:markdown id: tags:
### How does it work ?
The documentation gives several ways to wrap Fortran codes but it all boils down to the same thing:
**f2py allows to wrap the Fortran code in a Python module that can be then imported**
Given this simple Fortran (F90) snippet, that computes the sum of squares of the element of an array:
%% Cell type:markdown id: tags:
*pyfiles/f2py/file_to_wrap.f90*
```fortran
subroutinesum_squares(A,res)
implicitnone
real,dimension(:)::A
real::res
integer::i,N
N=size(A)
res=0.
doi=1,N
res=res+A(i)*A(i)
enddo
endsubroutine
```
%% Cell type:markdown id: tags:
The Fortran code can then be wrapped in one command:
This command calls the module `f2py` of `numpy` to compile (`-c`) *file_to_wrap.f90* into a Python module (`-m`) named *wrap_f90*. The module can then be imported in Python:
%% Cell type:code id: tags:
``` python
importnumpyasnp
importwrap_f90
A=np.ones(10)
result=0.
wrap_f90.sum_squares(A,result)
print(result)
```
%% Cell type:markdown id: tags:
### With intents
%% Cell type:markdown id: tags:
In Fortran, it is considered best practice to put intents to subroutine arguments. This also helps `f2py` to wrap efficiently the code but also changes the subroutine a bit.
This time, f2py recognized that `result` was a outgoing arg. As a consequence, the subroutine was wrapped smartly and made to return the arg.
Note that using a `function` (in the Fortran sense of the term) leads to the same result (see the other example in *pyfiles/f2py/file_to_wrap2.f90*).
%% Cell type:markdown id: tags:
### With modules
%% Cell type:markdown id: tags:
In Fortran, it is also considered best practice to organize the subroutines in modules. These are highly similar to Python modules and are in fact, intepreted as such by f2py !
Consider the following code that implements the dtw and cort computations in Fortran:
*pyfiles/dtw_cort_dist/V9_fortran/dtw_cort.f90*
```fortran
moduledtw_cort
implicitnone
contains
subroutinedtwdistance(s1,s2,dtw_result)
! Computes the dtw between s1 and s2 with distance the absolute distance
doubleprecision,intent(in)::s1(:),s2(:)
doubleprecision,intent(out)::dtw_result
integer::i,j
integer::len_s1,len_s2
doubleprecision::dist
doubleprecision,allocatable::dtw_mat(:,:)
len_s1=size(s1)
len_s2=size(s1)
allocate(dtw_mat(len_s1,len_s2))
dtw_mat(1,1)=dabs(s1(1)-s2(1))
doj=2,len_s2
dist=dabs(s1(1)-s2(j))
dtw_mat(1,j)=dist+dtw_mat(1,j-1)
enddo
doi=2,len_s1
dist=dabs(s1(i)-s2(1))
dtw_mat(i,1)=dist+dtw_mat(i-1,1)
enddo
! Fill the dtw_matrix
doi=2,len_s1
doj=2,len_s2
dist=dabs(s1(i)-s2(j))
dtw_mat(i,j)=dist+dmin1(dtw_mat(i-1,j),&
dtw_mat(i,j-1),&
dtw_mat(i-1,j-1))
enddo
enddo
dtw_result=dtw_mat(len_s1,len_s2)
endsubroutinedtwdistance
doubleprecisionfunctioncort(s1,s2)
! Computes the cort between s1 and s2 (assuming they have the same length)
doubleprecision,intent(in)::s1(:),s2(:)
integer::len_s1,t
doubleprecision::slope_1,slope_2
doubleprecision::num,sum_square_x,sum_square_y
len_s1=size(s1)
num=0
sum_square_x=0
sum_square_y=0
dot=1,len_s1-1
slope_1=s1(t+1)-s1(t)
slope_2=s2(t+1)-s2(t)
num=num+slope_1*slope_2
sum_square_x=sum_square_x+slope_1*slope_1
sum_square_y=sum_square_y+slope_2*slope_2
enddo
cort=num/(dsqrt(sum_square_x*sum_square_y))
endfunctioncort
endmoduledtw_cort
```
%% Cell type:markdown id: tags:
The subroutines `dtwdistance` and `cort` are part of the `dtw_cort` module. The file can be wrapped as before
But the import slighlty changes as `dtw_cort` is now a module of `distances_fort`:
%% Cell type:code id: tags:
``` python
importnumpyasnp
fromdistances_fortimportdtw_cort
cort_result=dtw_cort.cort(s1,s2)
print(cort_result)
```
%% Cell type:markdown id: tags:
Note that the wrapping integrates the documentation of the function (if written...) !
%% Cell type:code id: tags:
``` python
fromdistances_fortimportdtw_cort
print(dtw_cort.cort.__doc__)
```
%% Cell type:markdown id: tags:
### To go further...
Running the command `python3 -m numpy.f2py` (without arguments) gives a lot of information on the supported arguments for further uses of `f2py`. Know that you can this way:
* Specify the compiler to use
* Give the compiler flags (warnings, optimisations...)
* Specify the functions to wrap
* ...
The documentation of f2py (https://docs.scipy.org/doc/numpy/f2py/) can also help, covering notably:
*`Cf2py` directives to overcome F77 limitations (e.g. intents)
* How to integrate Fortran sources to your Python packages and compile them on install
* How to use `f2py` inside Python scripts
* ...
%% Cell type:markdown id: tags:
Wrapping C code
--------------------------
%% Cell type:markdown id: tags:
They are different ways of wrapping C code. We present CFFI.
The workflow is the following:
1. Get your C code working (with a .c and a .h)
2. Set up your packaging to compile your code as a module
3. Compile your code
4. In the python code, declare the function you will be using
5. In the python code, open/load the compiled module
6. Use your functions
%% Cell type:markdown id: tags:
**1. Get your C code working (with a .c and a .h)**
Ok, supposed to be done
%% Cell type:markdown id: tags:
**2. Set up your packaging to compile your code as a module**
We give the compilation instructions in the file setup.py:
%% Cell type:raw id: tags:
from setuptools import setup, Extension
version = "0.1"
module_distance = Extension(
name="cdtw",
sources=["cdtw.c"],
)
setup(
name="dtw_cort_dist_mat",
version=version,
description="data scientist tool for time series",
long_description="data scientist tool for time series",
classifiers=[],
author="Robert Bidochon",
author_email="robert@bidochon.fr",
license="GPL",
include_package_data=True,
install_requires=["cffi", "numpy", "setuptools"],
entry_points="",
ext_modules=[module_distance],
)
%% Cell type:markdown id: tags:
**3. Compile your code**
%% Cell type:markdown id: tags:
in a terminal, type:
python3 setup.py build_ext
%% Cell type:markdown id: tags:
**4. In the python code, declare the function you will be using**
%% Cell type:code id: tags:
``` python
fromcffiimportFFI
ffi=FFI()
ffi.cdef("double square(double x, double y);")
```
%% Cell type:markdown id: tags:
**5. In the python code, open/load the compiled module**