Semplice avvolgimento di codice C con Cython
Domanda
Ho un certo numero di funzioni C, e vorrei chiamarli in pitone. Cython sembra essere la strada da percorrere, ma non posso davvero trovare un esempio di come esattamente questo è fatto. Le mie C sguardi funzione come questa:
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 ) ;
Tutto quello che voglio fare è quello di indicare i primi tre parametri (una stringa e due numeri interi), e recuperare 8 array NumPy (o le liste di pitone. Tutte le doppie hanno array N elementi). Il mio codice si presuppone che i puntatori siano rivolte a un pezzo già allocata della memoria. Inoltre, il codice C prodotta dovrebbe link per alcune librerie esterne.
Soluzione
Ecco un piccolo ma completo esempio di passaggio di array numpy a una funzione C esterna, logicamente
fc( int N, double* a, double* b, double* z ) # z = a + b
utilizzando Cython. (Questo è sicuramente ben noto a chi lo conosce bene. I commenti sono benvenuti. Ultima modifica:. 23 Feb 2011, per Cython 0,14)
Prima leggi o scremato Cython accumulo e Cython con NumPy .
2 passi:
-
python f-setup.py build_ext --inplace
girif.pyx
efc.cpp
->f.so
, una libreria dinamica -
python test-f.py
carichiimport f
f.so
;f.fpy( ... )
chiama ilfc( ... )
C.
usi python f-setup.py
distutils
per eseguire Cython, compilazione e link:
cython f.pyx -> f.cpp
compilazione f.cpp
e fc.cpp
Link f.o fc.o
-> f.so
,
una dinamica lib che import f
pitone verrà caricato.
Per gli studenti, io suggerirei: fare un diagramma di questi passaggi, Guardare attraverso il file qui sotto, quindi scaricare ed eseguire.
(distutils
è un enorme, pacchetto contorto utilizzato per
creare pacchetti di Python per la distribuzione, e installarli.
Qui stiamo usando solo una piccola parte di esso per compilare e linkare f.so
.
Questo passaggio non ha nulla a che fare con Cython, ma può essere fonte di confusione;
semplici errori in un .pyx possono causare pagine di messaggi di errore oscuri da g ++ di compilazione e di collegamento.
Guarda anche
distutils doc
e / o
SO domande sul distutils .)
Come make
, setup.py
sarà rieseguire
cython f.pyx
e g++ -c ... f.cpp
se f.pyx
è più recente f.cpp
.
Per la pulizia, rm -r build/
.
Un'alternativa al setup.py
sarebbe quella di eseguire i passi a parte, in uno script o 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
.
Modificare l'involucro cc-lib-mac
qui di seguito per la piattaforma e l'installazione: non è bello, ma piccolo
Per esempi reali di Cython involucro C, guardare i file .pyx in quasi tutte le SciKit .
Vedere anche: Cython per gli utenti numpy e SO domande / tag / Cython .
Per decomprimere i seguenti file,
cut-incollare il molto da un unico grande file, dicono cython-numpy-c-demo
,
poi in Unix (in un ambiente pulito directory nuovo) sh cython-numpy-c-demo
run.
#--------------------------------------------------------------------------------
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
Altri suggerimenti
Il seguente codice Cython da http://article.gmane.org/gmane.comp.python.cython.user/5625 non richiede cast espliciti e gestisce anche le matrici non continui:
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])
In sostanza è possibile scrivere la funzione Cython tale che assegna gli array (assicuratevi di cimport numpy as np
):
cdef np.ndarray[np.double_t, ndim=1] rr = np.zeros((N,), dtype=np.double)
poi passare nel puntatore .data
di ciascuna alla funzione C. Che dovrebbe funzionare. Se non è necessario iniziare con zeri si potrebbe usare np.empty
per un piccolo aumento di velocità.
Vedere la Cython per NumPy utenti esercitazione nella documentazione (riparato per il link corretto).
Si dovrebbe verificare ctypes è probabilmente la cosa più facile da usare se tutto si vuole è una funzione.