web-dev-qa-db-fra.com

Habillage simple du code C avec cython

J'ai un certain nombre de fonctions C, et je voudrais les appeler à partir de python. cython semble être la voie à suivre, mais je ne peux pas vraiment trouver un exemple de la façon exacte dont cela est fait. Ma fonction C ressemble à ceci:

void calculate_daily ( char *db_name, int grid_id, int year,
                       double *dtmp, double *dtmn, double *dtmx, 
                       double *dprec, double *ddtr, double *dayl, 
                       double *dpet, double *dpar ) ;

Tout ce que je veux faire, c'est spécifier les trois premiers paramètres (une chaîne et deux entiers), et récupérer 8 tableaux numpy (ou python listes. Tous les tableaux doubles ont N éléments). Mon code suppose que les pointeurs pointent vers un morceau de mémoire déjà alloué. De plus, le code C produit doit être lié à certaines bibliothèques externes.

44
Jose

Voici un exemple minuscule mais complet de passage de tableaux numpy à une fonction C externe, logiquement

fc( int N, double* a, double* b, double* z )  # z = a + b

en utilisant Cython. (Ceci est sûrement bien connu de ceux qui le connaissent bien. Les commentaires sont les bienvenus. Dernier changement: 23 février 2011, pour Cython 0.14.)

Lisez d'abord ou écrémez build Cython et Cython avec NumPy .

2 étapes:

  • python f-setup.py build_ext --inplace
    transforme f.pyx et fc.cpp -> f.so, une bibliothèque dynamique
  • python test-f.py
    import f Charge f.so; f.fpy( ... ) appelle le C fc( ... ).

python f-setup.py Utilise distutils pour exécuter cython, compiler et lier:
cython f.pyx -> f.cpp
compile f.cpp et fc.cpp
lien f.o fc.o -> f.so, une bibliothèque dynamique que python import f va charger.

Pour les étudiants, je suggère: faites un diagramme de ces étapes, parcourez les fichiers ci-dessous, puis téléchargez-les et exécutez-les.

(distutils est un énorme package compliqué utilisé pour créer Python packages pour la distribution et les installer. Ici, nous en utilisons juste une petite partie pour compiler et créer un lien vers f.so. Cette étape n'a rien à voir avec Cython, mais elle peut être déroutante; de ​​simples erreurs dans un .pyx peuvent provoquer des pages de messages d'erreur obscurs de la compilation et du lien g ++. Voir aussi doc distutils et/ou questions SO sur les distutils .)

Comme make, setup.py Réexécutera cython f.pyx Et g++ -c ... f.cpp Si f.pyx Est plus récent que f.cpp.
Pour nettoyer, rm -r build/.

Une alternative à setup.py Serait d'exécuter les étapes séparément, dans un script ou Makefile:
cython --cplus f.pyx -> f.cpp # see cython -h
g++ -c ... f.cpp -> f.o
g++ -c ... fc.cpp -> fc.o
cc-lib f.o fc.o -> dynamic library f.so.
Modifiez le wrapper cc-lib-mac Ci-dessous pour votre plate-forme et votre installation: ce n'est pas joli, mais petit.

Pour des exemples réels d'encapsulation Cython C, regardez les fichiers .pyx dans à peu près n'importe quel SciKit .

Voir aussi: Cython pour les utilisateurs de NumPy et SO questions/tagged/cython .


Pour décompresser les fichiers suivants, coupez-collez le lot dans un gros fichier, dites cython-numpy-c-demo, Puis sous Unix (dans un nouveau répertoire propre) exécutez sh cython-numpy-c-demo.

#--------------------------------------------------------------------------------
cat >f.pyx <<\!
# f.pyx: numpy arrays -> extern from "fc.h"
# 3 steps:
# cython f.pyx  -> f.c
# link: python f-setup.py build_ext --inplace  -> f.so, a dynamic library
# py test-f.py: import f gets f.so, f.fpy below calls fc()

import numpy as np
cimport numpy as np

cdef extern from "fc.h": 
    int fc( int N, double* a, double* b, double* z )  # z = a + b

def fpy( N,
    np.ndarray[np.double_t,ndim=1] A,
    np.ndarray[np.double_t,ndim=1] B,
    np.ndarray[np.double_t,ndim=1] Z ):
    """ wrap np arrays to fc( a.data ... ) """
    assert N <= len(A) == len(B) == len(Z)
    fcret = fc( N, <double*> A.data, <double*> B.data, <double*> Z.data )
        # fcret = fc( N, A.data, B.data, Z.data )  grr char*
    return fcret

!

#--------------------------------------------------------------------------------
cat >fc.h <<\!
// fc.h: numpy arrays from cython , double*

int fc( int N, const double a[], const double b[], double z[] );
!

#--------------------------------------------------------------------------------
cat >fc.cpp <<\!
// fc.cpp: z = a + b, numpy arrays from cython

#include "fc.h"
#include <stdio.h>

int fc( int N, const double a[], const double b[], double z[] )
{
    printf( "fc: N=%d a[0]=%f b[0]=%f \n", N, a[0], b[0] );
    for( int j = 0;  j < N;  j ++ ){
        z[j] = a[j] + b[j];
    }
    return N;
}
!

#--------------------------------------------------------------------------------
cat >f-setup.py <<\!
# python f-setup.py build_ext --inplace
#   cython f.pyx -> f.cpp
#   g++ -c f.cpp -> f.o
#   g++ -c fc.cpp -> fc.o
#   link f.o fc.o -> f.so

# distutils uses the Makefile distutils.sysconfig.get_makefile_filename()
# for compiling and linking: a sea of options.

# http://docs.python.org/distutils/introduction.html
# http://docs.python.org/distutils/apiref.html  20 pages ...
# https://stackoverflow.com/questions/tagged/distutils+python

import numpy
from distutils.core import setup
from distutils.extension import Extension
from Cython.Distutils import build_ext
# from Cython.Build import cythonize

ext_modules = [Extension(
    name="f",
    sources=["f.pyx", "fc.cpp"],
        # extra_objects=["fc.o"],  # if you compile fc.cpp separately
    include_dirs = [numpy.get_include()],  # .../site-packages/numpy/core/include
    language="c++",
        # libraries=
        # extra_compile_args = "...".split(),
        # extra_link_args = "...".split()
    )]

setup(
    name = 'f',
    cmdclass = {'build_ext': build_ext},
    ext_modules = ext_modules,
        # ext_modules = cythonize(ext_modules)  ? not in 0.14.1
    # version=
    # description=
    # author=
    # author_email=
    )

# test: import f
!

#--------------------------------------------------------------------------------
cat >test-f.py <<\!
#!/usr/bin/env python
# test-f.py

import numpy as np
import f  # loads f.so from cc-lib: f.pyx -> f.c + fc.o -> f.so

N = 3
a = np.arange( N, dtype=np.float64 )
b = np.arange( N, dtype=np.float64 )
z = np.ones( N, dtype=np.float64 ) * np.NaN

fret = f.fpy( N, a, b, z )
print "fpy -> fc z:", z

!

#--------------------------------------------------------------------------------
cat >cc-lib-mac <<\!
#!/bin/sh
me=${0##*/}
case $1 in
"" )
    set --  f.cpp fc.cpp ;;  # default: g++ these
-h* | --h* )
    echo "
$me [g++ flags] xx.c yy.cpp zz.o ...
    compiles .c .cpp .o files to a dynamic lib xx.so
"
    exit 1
esac

# Logically this is simple, compile and link,
# but platform-dependent, layers upon layers, gloom, Doom

base=${1%.c*}
base=${base%.o}
set -x

g++ -dynamic -Arch ppc \
    -bundle -undefined dynamic_lookup \
    -fno-strict-aliasing -fPIC -fno-common -DNDEBUG `# -g` -fwrapv \
    -isysroot /Developer/SDKs/MacOSX10.4u.sdk \
    -I/Library/Frameworks/Python.framework/Versions/2.6/include/python2.6 \
    -I${Pysite?}/numpy/core/include \
    -O2 -Wall \
    "$@" \
    -o $base.so

# undefs: nm -gpv $base.so | egrep '^ *U _+[^P]'
!

# 23 Feb 2011 13:38
65
denis

Le code Cython suivant de http://article.gmane.org/gmane.comp.python.cython.user/5625 ne nécessite pas de transtypages explicites et gère également les tableaux non continus:

def fpy(A):
    cdef np.ndarray[np.double_t, ndim=2, mode="c"] A_c
    A_c = np.ascontiguousarray(A, dtype=np.double)
    fc(&A_c[0,0])
12
Nikratio

Fondamentalement, vous pouvez écrire votre fonction Cython de telle sorte qu'elle alloue les tableaux (assurez-vous que cimport numpy as np):

cdef np.ndarray[np.double_t, ndim=1] rr = np.zeros((N,), dtype=np.double)

passez ensuite dans le .data pointeur de chacun vers votre fonction C. Cela devrait fonctionner. Si vous n'avez pas besoin de commencer par des zéros, vous pouvez utiliser np.empty pour une petite augmentation de vitesse.

Voir le tutoriel Cython for NumPy Users dans la documentation (l'a corrigé sur le lien correct).

3
dwf

Vous devriez vérifier Ctypes c'est probablement la chose la plus simple à utiliser si tout ce que vous voulez c'est une fonction.

2
Yon