2012-10-02 26 views
9

INFORME FINAL DE ACTUALIZACIÓNCython y Fortran - cómo compilar juntos sin f2py

Esta pregunta es acerca de cómo escribir un setup.py que compilar un módulo Cython que accede directamente el código FORTRAN, al igual que lo haría C. Fue un viaje bastante largo y arduo a la solución, pero el desastre completo se incluye a continuación para el contexto.

pregunta original

Tengo una extensión que es un archivo Cython, que fija un poco de memoria del montón y lo pasa al código Fortran, y un archivo de Fortran, que es un módulo venerable viejo que yo Me gustaría evitar volver a implementar si puedo.

El archivo .pyx compila bien a C, pero el compilador Cython ahoga en el archivo .f90 con el siguiente error:

$ python setup.py build_ext --inplace 
running build_ext 
cythoning delaunay/__init__.pyx to delaunay/__init__.c 
building 'delaunay' extension 
error: unknown file type '.f90' (from 'delaunay/stripack.f90') 

He aquí (la mitad superior de) mi archivo de configuración:

from distutils.core import setup, Extension 
from Cython.Distutils import build_ext 

ext_modules = [ 
    Extension("delaunay", 
    sources=["delaunay/__init__.pyx", 
      "delaunay/stripack.f90"]) 
] 

setup(
    cmdclass = {'build_ext': build_ext}, 
    ext_modules = ext_modules, 
    ... 
) 

NOTA: Originalmente tuve la ubicación del archivo fortran incorrectamente especificada (sin el prefijo de directorio) pero esto se rompe exactamente de la misma manera después de que lo arreglé.

cosas que he intentado:

me encontré this, y trató de pasar en el nombre del compilador FORTRAN (es decir gfortran) así:

$ python setup.py config --fcompiler=gfortran build_ext --inplace 
usage: setup.py [global_opts] cmd1 [cmd1_opts] [cmd2 [cmd2_opts] ...] 
    or: setup.py --help [cmd1 cmd2 ...] 
    or: setup.py --help-commands 
    or: setup.py cmd --help 

error: option --fcompiler not recognized 

Y también he intentado eliminando --inplace, en caso de que ese fuera el problema (no era lo mismo que el mensaje de error superior).

Entonces, ¿cómo compilo este fortran? ¿Puedo hackearlo en un .o y salí con la vinculación? O is this a bug in Cython, lo que me obligará a volver a implementar distutils o hackear con el preprocesador?

ACTUALIZACIÓN

Así que, habiendo leído los numpy.distutils paquetes, entiendo el problema un poco más. Parece que usted tiene que

  1. Uso Cython para convertir los archivos a .pyx CPython .c archivos,
  2. A continuación, utilice una combinación Extension/setup() que soporta Fortran, como numpy 's.

Habiendo probado esto, mi setup.py ahora se ve así:

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

cy_modules = cythonize('delaunay/sphere.pyx') 
e = cy_modules[0] 

ext_modules = [ 
    Extension("delaunay.sphere", 
     sources=e.sources + ['delaunay/stripack.f90']) 
] 

setup(
    ext_modules = ext_modules, 
    name="delaunay", 
    ... 
) 

(tenga en cuenta que también he reestructurado el módulo un poco, ya que al parecer un __init__.pyx no está permitido ...)

Ahora es cuando las cosas se vuelven defectuosas y dependen de la plataforma. Tengo dos sistemas de prueba disponibles: un Mac OS X 10.6 (Snow Leopard), usando Macports Python 2.7, y un Mac OS X 10.7 (Lion) usando el sistema python 2.7.

En Snow Leopard, se aplica lo siguiente:

Esto significa que las compilaciones de módulos (hurra!) (Aunque no hay --inplace para numpy, parece, así que tuve que todo el sistema de instalar el módulo de prueba: /), pero sigo teniendo un accidente en import de la siguiente manera:

>>> import delaunay 
    Traceback (most recent call last): 
    File "<input>", line 1, in <module> 
    File "<snip>site-packages/delaunay/__init__.py", line 1, in <module> 
     from sphere import delaunay_mesh 
    ImportError: dlopen(<snip>site-packages/delaunay/sphere.so, 2): no suitable image found. Did find: 
    <snip>site-packages/delaunay/sphere.so: mach-o, but wrong architecture 

y en León, me sale un error de compilación, siguiendo una línea de compilación en lugar confuso buscando:

gfortran:f77: build/src.macosx-10.7-intel-2.7/delaunay/sphere-f2pywrappers.f 
/usr/local/bin/gfortran -Wall -arch i686 -arch x86_64 -Wall -undefined dynamic_lookup -bundle build/temp.macosx-10.7-intel-2.7/delaunay/sphere.o build/temp.macosx-10.7-intel-2.7/build/src.macosx-10.7-intel-2.7/delaunay/spheremodule.o build/temp.macosx-10.7-intel-2.7/build/src.macosx-10.7-intel-2.7/fortranobject.o build/temp.macosx-10.7-intel-2.7/delaunay/stripack.o build/temp.macosx-10.7-intel-2.7/build/src.macosx-10.7-intel-2.7/delaunay/sphere-f2pywrappers.o -lgfortran -o build/lib.macosx-10.7-intel-2.7/delaunay/sphere.so 
ld: duplicate symbol _initsphere in build/temp.macosx-10.7-intel-2.7/build/src.macosx-10.7-intel-2.7/delaunay/spheremodule.o ldand :build /temp.macosx-10.7-intelduplicate- 2.7symbol/ delaunay/sphere.o _initsphere in forbuild architecture /i386 
temp.macosx-10.7-intel-2.7/build/src.macosx-10.7-intel-2.7/delaunay/spheremodule.o and build/temp.macosx-10.7-intel-2.7/delaunay/sphere.o for architecture x86_64 

Ahora retrocedamos un momento antes de estudiar detenidamente los detalles aquí. En primer lugar, sé que hay muchos dolores de cabeza por los choques de arquitectura en Mac OS X de 64 bits; Tuve que trabajar mucho para conseguir que Macports Python trabaje en la máquina Snow Leopard (solo para actualizar desde el sistema Python 2.6). También sé que cuando veas gfortran -arch i686 -arch x86_64 estarás enviando mensajes mezclados a tu compilador. Hay todo tipo de problemas específicos de la plataforma enterrados allí, de los que no tenemos que preocuparnos en el contexto de esta pregunta.

Pero vamos a ver en esta línea: gfortran:f77: build/src.macosx-10.7-intel-2.7/delaunay/sphere-f2pywrappers.f

¿Qué está haciendo numpy ?! ¡No necesito ninguna característica f2py en esta versión! De hecho, escribí un módulo cython para evitar que trata con la locura de f2py (necesito tener 4 o 5 variables de salida, así como argumentos none-in-or-out, ninguno de los cuales es bien soportado en f2py). solo quiere que compile .c ->.o, y .f90 ->.o y vincule. Podría escribir esta línea de compilación si supiera cómo incluir todos los encabezados relevantes.

Por favor, dime que no necesito escribir mi propio archivo MAKE para esto ... o que hay una forma de traducir Fortran a C (compatible con la salida), así que puedo evitar que python vea la extensión .f90 (que corrige todo el problema.) Tenga en cuenta que f2c no es adecuado para esto, ya que solo funciona en F77 y este es un dialecto más moderno (de ahí la extensión de archivo .f90).

ACTUALIZACIÓN 2 La siguiente secuencia de comandos bash felizmente compilar y enlazar el código en su sitio:

PYTHON_H_LOCATION="/opt/local/Library/Frameworks/Python.framework/Versions/2.7/include/python2.7/" 

cython sphere.pyx 

gcc -arch x86_64 -c sphere.c -I$PYTHON_H_LOCATION 
gfortran -arch x86_64 -c stripack.f90 
gfortran -arch x86_64 -bundle -undefined dynamic_lookup -L/opt/local/lib *.o -o sphere.so 

Algún consejo sobre cómo hacer este tipo de corte compatible con una setup.py? Ni cualquier instalación de este módulo a tener que ir a buscar manualmente Python.h ...

+1

El soporte de Fortran parece provenir de 'numpy'. Tal vez pueda importar ['numpy.distutils.extension.Extension'] (http://www.scipy.org/doc/numpy_api_docs/numpy.distutils.extension.html) en lugar de' distutils.core.Extension'. – MvG

+0

Lea también la [Guía del usuario de NumPy Distutils] (https://github.com/numpy/numpy/blob/master/doc/DISTUTILS.rst.txt) y la [referencia del paquete] (http: //docs.scipy. org/doc/numpy-1.6.0/reference/distutils.html). – MvG

+0

¿Tiene instalado gfortran en absoluto? Realmente necesitas un compilador Fortran, es realmente necesario. –

Respuesta

3

ACTUALIZACIÓN: He creado un proyecto en GitHub que envuelve a esta generación de líneas de compilación con la mano. se llama complicated_build.

ACTUALIZACIÓN 2:, de hecho, "de generación a mano" es realmente una mala idea ya que es específico de la plataforma — el proyecto ahora lee los valores del módulo de distutils.sysconfig, que es la configuración utilizada para compilar Python (es decir, exactamente lo queremos), la única configuración que se adivina es el compilador y las extensiones de archivo (que son configurables por el usuario). ¡Sospecho que está reimplementando un poco de distutils ahora!


La manera de hacer esto es para escribir sus propias líneas del compilador, y cortar ellos en su setup.py. Puedo mostrar un ejemplo a continuación, que trabaja para mi caso (muy simple), que tiene la siguiente strucutre:

  • importaciones
  • cythonize() cualquier .pyx archivos, por lo que sólo tienen archivos FORTRAN y C.
  • definir una función build() que compila el código:
    • tal vez algunas constantes fáciles de cambiar, como nombres del compilador y la arquitectura
    • lista de los archivos FORTRAN y C
    • generar los comandos de shell que construirán los módulos
    • agregue la línea enlazadora
    • ejecute los comandos de la shell.
  • si el comando era install y el destino aún no existe, créelo.
  • configuración de ejecución (que creará las secciones puras de python)
  • si el comando era build, ejecute la compilación ahora.

mi implementación de esto se muestra a continuación. Está diseñado para un solo módulo de extensión, y recompila todos los archivos cada vez, por lo que puede requerir una extensión adicional para ser de uso más general. También tenga en cuenta que he codificado varias Unix / s, por lo que si está transfiriendo esto a Windows, asegúrese de adaptar o reemplazar con os.path.sep.

from distutils.core import setup 
from distutils.sysconfig import get_python_inc 
from Cython.Build import cythonize 
import sys, os, shutil 

cythonize('delaunay/sphere.pyx') 

target = 'build/lib/delaunay/sphere.so' 

def build(): 
    fortran_compiler = 'gfortran' 
    c_compiler = 'gcc' 
    architecture = 'x86_64' 
    python_h_location = get_python_inc() 
    build_temp = 'build/custom_temp' 
    global target 

    try: 
    shutil.rmtree(build_temp) 
    except OSError: 
    pass 

    os.makedirs(build_temp) # if you get an error here, please ensure the build/ ... 
    # folder is writable by this user. 

    c_files = ['delaunay/sphere.c'] 
    fortran_files = ['delaunay/stripack.f90'] 

    c_compile_commands = [] 

    for cf in c_files: 
    # use the path (sans /s), without the extension, as the object file name: 
    components = os.path.split(cf) 
    name = components[0].replace('/', '') + '.'.join(components[1].split('.')[:-1]) 
    c_compile_commands.append(
     c_compiler + ' -arch ' + architecture + ' -I' + python_h_location + ' -o ' + 
     build_temp + '/' + name + '.o -c ' + cf 
    ) 

    fortran_compile_commands = [] 

    for ff in fortran_files: 
    # prefix with f in case of name collisions with c files: 
    components = os.path.split(ff) 
    name = components[0].replace('/', '') + 'f' + '.'.join(components[1].split('.')[:-1]) 
    fortran_compile_commands.append(
     fortran_compiler + ' -arch ' + architecture + ' -o ' + build_temp + 
     '/' + name + '.o -c ' + ff 
    ) 

    commands = c_compile_commands + fortran_compile_commands + [ 
    fortran_compiler + ' -arch ' + architecture + 
    ' -bundle -undefined dynamic_lookup ' + build_temp + '/*.o -o ' + target 
    ] 

    for c in commands: 
    os.system(c) 


if 'install' in sys.argv and not os.path.exists(target): 
    try: 
    os.makedirs('build/lib/delaunay') 
    except OSError: 
    # we don't care if the containing folder already exists. 
    pass 
    build() 

setup(
    name="delaunay", 
    version="0.1", 
    ... 
    packages=["delaunay"] 
) 

if 'build' in sys.argv: 
    build() 

Esto podría ser envuelto en una nueva clase Extension supongo, con su propio comando build_ext - un ejercicio para el estudiante avanzado;)

2

Simplemente construir e instalar su biblioteca Fortran vendimia fuera de Python, luego enlaza a él en distutils. Su pregunta indica que no tiene la intención de moderarse con esta biblioteca, por lo que una instalación de una vez por todas probablemente sí (utilizando las instrucciones de compilación e instalación de la biblioteca). A continuación, vincule la extensión de Python a la biblioteca externa instalada:

ext_modules = [ 
    Extension("delaunay", 
       sources = ["delaunay/__init__.pyx"], 
       libraries = ["delaunay"]) 
] 

Este enfoque también es seguro para el caso de que se da cuenta de que necesita contenedores para otros idiomas, así como Matlab, Octave, IDL, ...

actualización

en algún momento, si al final con más de unos pocos de tales bibliotecas externas que desee para envolver, es ventajoso añadir un sistema de construcción de alto nivel que se instala todas estas bibliotecas, y maneja la construcción de todas las envolturas también. Tengo cmake para este propósito, que es excelente para manejar construcciones e instalaciones en todo el sistema. Sin embargo, no puede compilar cosas de Python, pero se puede enseñar fácilmente para llamar a "instalación de python setup.py" en cada subdirectorio python, invocando así distutils.Así que el proceso de construcción en general es el siguiente:

mkdir build 
cd build 
cmake .. 
make 
make install 
make python 
(make octave) 
(make matlab) 

Es muy importante núcleo de código de la biblioteca siempre separada de los envoltorios de idiomas front-end específicos (también para sus propios proyectos!), Ya que tienden a cambiar bastante rápido . Lo que sucede de otra manera se puede ver en el ejemplo de numpy: en lugar de escribir una biblioteca de C de uso general libndarray.so y crear envoltorios delgados para Python, hay llamadas API de Python C en todas partes en las fuentes. Esto es lo que ahora está reteniendo Pypy como una alternativa seria a CPython, ya que para obtener numpy tienen que soportar hasta el último bit de la API CPython, lo cual no pueden hacer, ya que tienen un compilador just-in-time y un recolector de basura diferente. Esto significa que estamos perdiendo muchas mejoras potenciales.

En pocas palabras:

  1. construir bibliotecas de propósito general Fortran/C por separado e instalarlos en todo el sistema.

  2. Tengan un paso de construcción separado para los envoltorios, que debe mantenerse lo más ligero posible, de modo que sea fácil de adaptar para el próximo gran lenguaje X que se produzca. Si hay una suposición segura, es que X admitirá el enlace con las bibliotecas de C.

+0

que es una solución muy elegante, ¡bien hecho! Veré si puedo hacerlo funcionar, y cambiar la respuesta "correcta" si puedo. – tehwalrus

0

Usted puede construir el archivo objeto fuera de distutils luego incluirla en el paso de la vinculación con el argumento extra_objects al constructor de extensión. En setup.py:

... 
e = Extension(..., extra_objects = ['holycode.o']) 
... 

En el símbolo del sistema:

# gfortran -c -fPIC holycode.f 
# ./setup.py build_ext ... 

Con sólo un objeto externo, esta será la forma más fácil para muchos.

Cuestiones relacionadas