Envoltura simple de código C con cython


Tengo varias funciones de C, y me gustaría llamarlas desde python. cython parece ser el camino a seguir, pero realmente no puedo encontrar un ejemplo de cómo se hace exactamente esto. Mi función C se ve así:

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

Todo lo que quiero hacer es especificar los tres primeros parámetros (una cadena y dos enteros), y recuperar 8 matrices numpy (o listas de python. Todos los arrays dobles tienen N elementos). Mi código asume que los punteros están apuntando a un trozo de memoria ya asignado. También, el código C producido debería enlazar a algunas bibliotecas externas.

Author: Jose, 2010-06-15

4 answers

Aquí hay un pequeño pero completo ejemplo de pasar arrays numpy a una función C externa, lógicamente

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

Usando Cython. Esto es bien sabido por quienes lo conocen bien. Los comentarios son bienvenidos. Última modificación: 23 Feb 2011, para Cython 0.14.)

Primera lectura o descremada Cython build y Cython con NumPy .

2 pasos:

  • python f-setup.py build_ext --inplace
    vueltas f.pyx y fc.cpp -> f.so, una biblioteca dinámica
  • python test-f.py
    import f cargas f.so; f.fpy( ... ) llama a la C fc( ... ).

python f-setup.py usa distutils para ejecutar cython, compilar y enlazar:
cython f.pyx -> f.cpp
compile f.cpp and fc.cpp
enlace f.o fc.o -> f.so, una lib dinámica que python import f cargará.

Para los estudiantes, yo sugeriría: haga un diagrama de estos pasos, mire a través de los archivos a continuación, luego descárguelos y ejecútelos.

(distutils es un paquete enorme, enrevesado usado para crea paquetes de Python para su distribución e instálalos. Aquí estamos usando solo una pequeña parte de ella para compilar y enlazar a f.so. Este paso no tiene nada que ver con Cython, pero puede ser confuso; errores simples en a .pyx puede causar páginas de mensajes de error oscuros de g++ compile y link. Véase también distutils doc y/o ASÍ que preguntas sobre distutils.)

Me gusta make, setup.py se volverá a ejecutar cython f.pyx y g++ -c ... f.cpp si f.pyx es más reciente que f.cpp.
Para limpiar, rm -r build/ .

Una alternativa a setup.py sería ejecutar los pasos por separado, en un 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.
Modificar el envoltorio cc-lib-mac a continuación para su plataforma e instalación: no es bonito, pero pequeño.

Para ejemplos reales de Cython envolver C, mira .archivos pyx en casi cualquier SciKit .

Véase también: Cython para usuarios NumPy y SO questions/tagged/cython.


Para descomprimir los siguientes archivos, cortar-pegar el lote en un archivo grande, digamos cython-numpy-c-demo, luego en Unix (en un limpio nuevo directorio) ejecutar 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
 64
Author: denis,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 11:47:18

El siguiente código Cython de http://article.gmane.org/gmane.comp.python.cython.user/5625 no requiere casts explícitos y también maneja arrays no continuos:

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
Author: Nikratio,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-02-02 17:14:48

Básicamente puede escribir su función Cython de tal manera que asigne los arrays (asegúrese de que cimport numpy as np):

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

Luego pase el puntero .data de cada uno a su función C. Eso debería funcionar. Si no necesita comenzar con ceros, podría usar np.empty para un pequeño aumento de velocidad.

Vea el tutorial Cython for NumPy Users en los documentos (arreglado para el enlace correcto).

 3
Author: dwf,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-06-17 05:19:27

Usted debe comprobar hacia fuera Ctypes es probablemente la cosa más fácil de usar si todo lo que desea es una función.

 2
Author: Yon,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-06-15 14:53:28