简体   繁体   English

使用 Cython 将 np.ndarray 传递给 Fortran

[英]Passing np.ndarray to Fortran with Cython

I am working on wrapping a Fortran module in Python.我正在努力将 Fortran 模块包装在 Python 中。 I chose to do it with use of Cython.我选择使用 Cython 来做到这一点。 My problem is passing a np.ndarray to Fortran.我的问题是将np.ndarray传递给 Fortran。 I am able to receive an np.ndarray from Fortran, but all my attempts to passing to Fortran didn't work.我能够从 Fortran 接收np.ndarray ,但我所有传递给 Fortran 的尝试都没有奏效。

I figured out, that the problem lays directly on the Cython - Fortran interface, as my Fotran subroutine is working properly (as much as it can work without data).我发现问题直接出在 Cython - Fortran 接口上,因为我的 Fotran 子程序工作正常(只要它可以在没有数据的情况下工作)。 The Cython side seems to be working properly too, I can manipulate variables over there. Cython 方面似乎也工作正常,我可以在那里操纵变量。

My minimum working example:我的最小工作示例:

PATTERN_wrap.f90

module PATTERN_wrap
    use iso_c_binding, only: c_float, c_double, c_short, c_int
    implicit none

CONTAINS
    subroutine c_pattern(scalar_variable, array_variable, return_array) bind(c)
        implicit NONE

        INTEGER(c_int), intent(in) :: scalar_variable
        INTEGER(c_int), intent(in), DIMENSION(10, 15) :: array_variable

        REAL(c_float), INTENT(OUT), DIMENSION(10) :: return_array

        write(*,*) "start fortran"
        write(*,*) "scalar_variable"
        write(*,*) scalar_variable
        write(*,*) "array_variable"
        write(*,*) array_variable

        return_array = 3
        write(*,*) "end fortran"


!        call DO_PATTERN(&
!                scalar_variable=scalar_variable, &
!                array_variable=array_variable, &
!                return_array=return_array)
!
    end subroutine

end module PATTERN_wrap

Note: The call to subroutine DO_PATTERN that actually does something is commented out because it's not relevant at this moment.注意:对实际执行某些操作的子例程DO_PATTERN的调用被注释掉,因为此时它不相关。 I just wanted to point out that code above is a wrapper.我只是想指出上面的代码是一个包装器。

pattern.pyx

#cython: language_level=3
import cython
import numpy as np
cimport numpy as np

cdef extern:
    void c_pattern(
            int *scalar_variable,
            int *array_variable,
            float *return_array
    )

def run_pattern(
        int scalar_variable,
):
    cdef:
        np.ndarray[int, ndim=2, mode="fortran"] array_variable = np.ones((10,15), dtype=np.int32, order='F')
        np.ndarray[float, ndim=1, mode="fortran"] return_array = np.zeros(10, dtype=np.float32, order='F')

    c_pattern(
        &scalar_variable,
        &array_variable[0,0],
        &return_array[0],
    )

    print('Cython side')
    print(return_array)

    return return_array

setup.py

from distutils.core import setup
from distutils.extension import Extension
from Cython.Distutils import build_ext
import numpy
npy_include_dir = numpy.get_include()

ext_modules = [Extension("pattern", ["pattern.pyx"],
                         include_dirs = [npy_include_dir],
                         libraries = ['gfortran', 'fftw3'], # need to include gfortran as a library
                         extra_link_args=[
                             "PATTERN_wrap.o"
                         ])]

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

I am compiling my fortran code with我正在编译我的 fortran 代码

gfortran -Wall -fbounds-check -lm -g -fbacktrace  -fcheck=all -Wall -ffpe-trap=zero,invalid,overflow -fPIC -L/usr/lib/ -lfftw3 -L/usr/lib/ -lfftw3 -c PATTERN_wrap.f90

and compiling the Cython code with either python -m pip install.并使用python -m pip install. or python setup.py build_ext --inplace .python setup.py build_ext --inplace That does not seem to have any difference.这似乎没有任何区别。

I test the package:我测试了 package:

$ python -c "import pattern; pattern.run_pattern(2);"
 start fortran
 scalar_variable
           2
 array_variable

 end fortran
Cython side
[3. 3. 3. 3. 3. 3. 3. 3. 3. 3.]

As you can see, scalar is being passed to fortran properly, returning array is also passed back to Cython properly.如您所见,标量被正确地传递给 fortran,返回的数组也被正确地传递回 Cython。 The only thing not working is passing arrays from Cython to Fortran.唯一不起作用的是将 arrays 从 Cython 传递到 Fortran。 In short, there should be an 2D array of ones printed after array_variable .简而言之,应该在array_variable之后打印一个二维数组。

Apart of the MWE above, I tried different approaches:除了上面的 MWE,我尝试了不同的方法:

All my attempts failed in the same way as MWE.我所有的尝试都以与 MWE 相同的方式失败。

I tried also using a header file, doesn't make a difference.我也尝试使用 header 文件,没有任何区别。 Header file was used for example here: Fortran - Cython Workflow This question itself does not contain answer for my question - only scalars are passed to Fortran there. Header 文件在此处使用例如: Fortran - Cython 工作流程这个问题本身不包含我的问题的答案 - 只有标量传递给 Fortran 那里。

I would also like to note that the same wrapper plus all underlying files are working properly when I compile a package with f2py.我还想指出,当我使用 f2py 编译 package 时,相同的包装器和所有底层文件都可以正常工作。 The subroutine also works inside of the original Fortran program.该子例程也适用于原始 Fortran 程序。

EDIT:编辑:

My development environment is running in docker.我的开发环境运行在 docker 中。 The baseimage is continuumio/miniconda3:4.8.2 which on the other hand is based on Debian Buster.基础映像是continuumio/miniconda3:4.8.2 ,另一方面,它基于 Debian Buster。 I tested there gfortran-8 and gfortran-9 as well as a hdf5 compiler with enabled fortran.我在那里测试了 gfortran-8 和 gfortran-9 以及启用了 fortran 的 hdf5 编译器。 The result was all the time the same.结果一直都是一样的。

I decided to run my tests on my host system, Ubuntu 18.04 with gcc/gfortran 7.50.我决定在我的主机系统 Ubuntu 18.04 和 gcc/gfortran 7.50 上运行我的测试。 It did work properly.它确实工作正常。 So I went to try different gcc versions.所以我去尝试不同的 gcc 版本。

I tested images:我测试了图像:

  • gcc:7 gcc:7
  • gcc:8 gcc:8
  • gcc:9 gcc:9
  • gcc:10 gcc:10

running them with:运行它们:

docker run --rm -v ~/minimum_working_example:/mwe -it gcc:7  /bin/bash

and then接着

apt update && apt install python3-pip -yy && cd /mwe && python3 -m pip install cython numpy && make && python3 setup.py build_ext --inplace && python3 -c "import pattern; pattern.run_pattern(2);" && rm -rf build/ *.so *.c *.mod *.o

On all of those images my code is working properly.在所有这些图像上,我的代码都可以正常工作。

EDIT2:编辑2:

I just ran test on bare continuumio/miniconda3:4.8.2 , with the same test command (with added apt install gfortran since there is no fortran by default) and the code works.我刚刚在裸continuumio/miniconda3:4.8.2上运行了测试,使用相同的测试命令(添加了 apt install gfortran,因为默认情况下没有 fortran )并且代码有效。

I rebuilt my image and tested in the same way.我重建了我的图像并以相同的方式进行了测试。 It doesn't work...它不起作用...

I managed to find the solution.我设法找到了解决方案。 The code is okay.代码没问题。 The problem was my configuration.问题是我的配置。

As I described above, I tested different configurations of gcc/gfortran to see if that was influencing the Cythonizing.如上所述,我测试了 gcc/gfortran 的不同配置,看看这是否会影响 Cythonizing。 It was not.不是。 Thus I proceeded to disassemble my Dockerfile in order to find a step that was causing the code to break.因此,我着手拆卸我的 Dockerfile 以找到导致代码中断的步骤。 It turned out, that it was installation of numpy by conda.原来,这是 conda 安装的 numpy。

All my test above with ggc images I did with use of pip:我在上面使用 ggc 图像进行的所有测试都是使用 pip 进行的:

$ python -m pip install numpy
Collecting numpy
  Downloading numpy-1.18.4-cp38-cp38-manylinux1_x86_64.whl (20.7 MB)
     |████████████████████████████████| 20.7 MB 18.9 MB/s
Installing collected packages: numpy
Successfully installed numpy-1.18.4

One package, one wheel, quick and easy.一个package,一个轮子,快捷方便。 However, I was using conda in my 'production' image.但是,我在“生产”图像中使用了 conda。

If you install numpy by conda:如果您通过 conda 安装 numpy:

$ conda install numpy
Collecting package metadata (current_repodata.json): done
Solving environment: done

## Package Plan ##

  environment location: /opt/conda

  added / updated specs:
    - numpy


The following packages will be downloaded:

    package                    |            build
    ---------------------------|-----------------
    blas-1.0                   |              mkl           6 KB
    intel-openmp-2020.1        |              217         780 KB
    libgfortran-ng-7.3.0       |       hdf63c60_0        1006 KB
    mkl-2020.1                 |              217       129.0 MB
    mkl-service-2.3.0          |   py38he904b0f_0          62 KB
    mkl_fft-1.0.15             |   py38ha843d7b_0         159 KB
    mkl_random-1.1.1           |   py38h0573a6f_0         341 KB
    numpy-1.18.1               |   py38h4f9e942_0           5 KB
    numpy-base-1.18.1          |   py38hde5b4d6_1         4.2 MB
    ------------------------------------------------------------
                                           Total:       135.5 MB

...

Important thing to note here is that conda, apart of numpy, is also installing libgfortran-ng-7.3.0 .这里需要注意的重要一点是,除了 numpy 之外,conda 也在安装libgfortran-ng-7.3.0 In the image I am working on, there is installed gcc/gfortran 8.5.0.在我正在处理的图像中,安装了 gcc/gfortran 8.5.0。

Why this is important?为什么这很重要? When you run cython compilation:当您运行 cython 编译时:

$ python setup.py build_ext --inplace
running build_ext
cythoning pattern.pyx to pattern.c
building 'pattern' extension
creating build
creating build/temp.linux-x86_64-3.8
gcc -pthread -B /opt/conda/compiler_compat -Wl,--sysroot=/ -Wsign-compare -DNDEBUG -g -fwrapv -O3 -Wall -Wstrict-prototypes -fPIC -I/opt/conda/lib/python3.8/site-packages/numpy/core/include -I/opt/conda/include/python3.8 -c pattern.c -o build/temp.linux-x86_64-3.8/pattern.o
In file included from /opt/conda/lib/python3.8/site-packages/numpy/core/include/numpy/ndarraytypes.h:1832,
                 from /opt/conda/lib/python3.8/site-packages/numpy/core/include/numpy/ndarrayobject.h:12,
                 from /opt/conda/lib/python3.8/site-packages/numpy/core/include/numpy/arrayobject.h:4,
                 from pattern.c:599:
/opt/conda/lib/python3.8/site-packages/numpy/core/include/numpy/npy_1_7_deprecated_api.h:17:2: warning: #warning "Using deprecated NumPy API, disable it with " "#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION" [-Wcpp]
 #warning "Using deprecated NumPy API, disable it with " \
  ^~~~~~~
gcc -pthread -shared -B /opt/conda/compiler_compat -L/opt/conda/lib -Wl,-rpath=/opt/conda/lib -Wl,--no-as-needed -Wl,--sysroot=/ build/temp.linux-x86_64-3.8/pattern.o -lgfortran -o /mwe/pattern.cpython-38-x86_64-linux-gnu.so PATTERN_wrap.o

As you can see in the list line, among includes that are passed to gcc is /opt/conda/lib .正如您在列表行中看到的,传递给 gcc 的包含是/opt/conda/lib

$ ls /opt/conda/lib | grep "fortran"
libgfortran.so
libgfortran.so.4
libgfortran.so.4.0.0          

Here it is, libgfortran , in different version that I compiled originally my code with.这是libgfortran ,在我最初编译我的代码时使用的不同版本。

The solution was:解决方案是:

$ conda install -c conda-forge libgfortran-ng==8.2.0

Note: using conda-forge channel is necessary, in my case conda was not able to resolve dependencies with packages from base channel only.注意:使用 conda-forge 通道是必要的,在我的情况下,conda 无法仅解决来自基本通道的包的依赖关系。 What is more, this version of libgfortran-ng also required changing libblas from openblas version to mkl, if that concerns you.更重要的是,这个版本的 libgfortran-ng 还需要将 libblas 从 openblas 版本更改为 mkl,如果这与您有关的话。

In this way, I installed in conda a libgfortran that has the same major version as one I was using in my system.通过这种方式,我在 conda 中安装了一个 libgfortran,它与我在系统中使用的主要版本相同。 After reruning compilation of Cythonized package, everything worked properly.重新编译 Cythonized package 后,一切正常。

Anyway, beware of conda.无论如何,当心康达。

PS: Thanks @DawidW for your feedback and testing my code. PS:感谢@DawidW 的反馈和测试我的代码。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM