使用 Cython 将 np.ndarray 传递给 Fortran
Passing np.ndarray to Fortran with Cython
我正在努力在 Python 中包装 Fort运行 模块。我选择使用 Cython 来做到这一点。我的问题是将 np.ndarray
传递给 Fort运行。我能够从 Fort运行 收到 np.ndarray
,但我所有尝试传递给 Fort运行 的尝试都没有成功。
我想通了,问题直接出在 Cython - Fort运行 接口上,因为我的 Fot运行 子例程工作正常(尽可能在没有数据的情况下工作)。 Cython 端似乎也能正常工作,我可以在那里操作变量。
我的最低工作示例:
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
注意:调用子例程 DO_PATTERN
实际做某事已被注释掉,因为此时它不相关。我只是想指出上面的代码是一个包装器。
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)
我正在用
编译我的堡垒 运行 代码
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
并使用 python -m pip install .
或 python setup.py build_ext --inplace
编译 Cython 代码。这似乎没有任何区别。
我测试包:
$ 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.]
如您所见,标量被正确传递给 fort运行,返回的数组也被正确传递回 Cython。唯一不起作用的是将数组从 Cython 传递到 Fort运行。简而言之,应该在 array_variable
.
之后打印一个二维数组
除了上面的MWE,我尝试了不同的方法:
用<int*> array_variable.data
传递数组
Cython (https://github.com/cython/cython/wiki/tutorials-NumpyPointerToC)
不鼓励这样做
正在创建变量作为 Fort运行 连续 MemoryView int[::1,:] array_variable = np.ones((10,15), dtype=np.int32, order='F')
.
我所有的尝试都以与 MWE 相同的方式失败。
我也试过使用头文件,没有什么区别。例如,此处使用了头文件:Fortran - Cython Workflow 这个问题本身不包含我的问题的答案 - 只有标量被传递给那里的 Fort运行。
我还想指出,当我使用 f2py 编译包时,相同的包装器和所有底层文件都可以正常工作。该子例程也适用于原始 Fort运行 程序。
编辑:
我的开发环境是 运行ning in docker。 baseimage 是 continuumio/miniconda3:4.8.2
,另一方面是基于 Debian Buster。
我在那里测试了 gfort运行-8 和 gfort运行-9 以及启用了 fort运行 的 hdf5 编译器。结果一直都是一样的。
我决定 运行 在我的主机系统上进行测试,Ubuntu 18.04 和 gcc/gfortran 7.50。它确实工作正常。所以我去尝试了不同的gcc版本。
我测试了图像:
- gcc:7
- gcc:8
- gcc:9
- gcc:10
运行将它们与:
docker run --rm -v ~/minimum_working_example:/mwe -it gcc:7 /bin/bash
然后
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
在所有这些图像上,我的代码都正常工作。
编辑2:
我只是 运行 在裸 continuumio/miniconda3:4.8.2
上测试,使用相同的测试命令(添加了 apt install gfort运行 因为默认情况下没有 fort运行)并且代码有效。
我重建了我的图像并以同样的方式进行了测试。没用...
我设法找到了解决方案。 代码没问题。问题出在我的配置上。
正如我上面所描述的,我测试了 gcc/gfortran 的不同配置,看看它是否影响了 Cythonizing。它不是。
因此,我继续反汇编我的 Dockerfile,以找到导致代码中断的步骤。原来是conda安装了numpy
我上面的所有测试都是使用 pip 进行的 ggc 图像:
$ 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
一包一轮,快捷方便。但是,我在 'production' 图像中使用了 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
...
这里要注意的重要一点是,除了 numpy 之外,conda 也在安装 libgfortran-ng-7.3.0
。在我正在处理的图像中,安装了 gcc/gfortran 8.5.0.
为什么这很重要?当你 运行 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
正如您在列表行中看到的,传递给 gcc 的包含是 /opt/conda/lib
。
$ ls /opt/conda/lib | grep "fortran"
libgfortran.so
libgfortran.so.4
libgfortran.so.4.0.0
在这里,libgfortran
,在我最初编译我的代码时使用的不同版本。
解决方案是:
$ conda install -c conda-forge libgfortran-ng==8.2.0
注意:使用 conda-forge 通道是必要的,在我的例子中,conda 无法解决仅来自基本通道的包的依赖关系。此外,此版本的 libgfortran-ng 还需要将 libblas 从 openblas 版本更改为 mkl,如果您担心的话。
这样,我在conda中安装了一个与我在系统中使用的主版本相同的libgfortran。在重新运行编译 Cythonized 包后,一切正常。
总之,小心conda
PS:感谢@DawidW 的反馈和测试我的代码。
我正在努力在 Python 中包装 Fort运行 模块。我选择使用 Cython 来做到这一点。我的问题是将 np.ndarray
传递给 Fort运行。我能够从 Fort运行 收到 np.ndarray
,但我所有尝试传递给 Fort运行 的尝试都没有成功。
我想通了,问题直接出在 Cython - Fort运行 接口上,因为我的 Fot运行 子例程工作正常(尽可能在没有数据的情况下工作)。 Cython 端似乎也能正常工作,我可以在那里操作变量。
我的最低工作示例:
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
注意:调用子例程 DO_PATTERN
实际做某事已被注释掉,因为此时它不相关。我只是想指出上面的代码是一个包装器。
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)
我正在用
编译我的堡垒 运行 代码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
并使用 python -m pip install .
或 python setup.py build_ext --inplace
编译 Cython 代码。这似乎没有任何区别。
我测试包:
$ 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.]
如您所见,标量被正确传递给 fort运行,返回的数组也被正确传递回 Cython。唯一不起作用的是将数组从 Cython 传递到 Fort运行。简而言之,应该在 array_variable
.
除了上面的MWE,我尝试了不同的方法:
用
<int*> array_variable.data
传递数组 Cython (https://github.com/cython/cython/wiki/tutorials-NumpyPointerToC) 不鼓励这样做
正在创建变量作为 Fort运行 连续 MemoryView
int[::1,:] array_variable = np.ones((10,15), dtype=np.int32, order='F')
.
我所有的尝试都以与 MWE 相同的方式失败。
我也试过使用头文件,没有什么区别。例如,此处使用了头文件:Fortran - Cython Workflow 这个问题本身不包含我的问题的答案 - 只有标量被传递给那里的 Fort运行。
我还想指出,当我使用 f2py 编译包时,相同的包装器和所有底层文件都可以正常工作。该子例程也适用于原始 Fort运行 程序。
编辑:
我的开发环境是 运行ning in docker。 baseimage 是 continuumio/miniconda3:4.8.2
,另一方面是基于 Debian Buster。
我在那里测试了 gfort运行-8 和 gfort运行-9 以及启用了 fort运行 的 hdf5 编译器。结果一直都是一样的。
我决定 运行 在我的主机系统上进行测试,Ubuntu 18.04 和 gcc/gfortran 7.50。它确实工作正常。所以我去尝试了不同的gcc版本。
我测试了图像:
- gcc:7
- gcc:8
- gcc:9
- gcc:10
运行将它们与:
docker run --rm -v ~/minimum_working_example:/mwe -it gcc:7 /bin/bash
然后
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
在所有这些图像上,我的代码都正常工作。
编辑2:
我只是 运行 在裸 continuumio/miniconda3:4.8.2
上测试,使用相同的测试命令(添加了 apt install gfort运行 因为默认情况下没有 fort运行)并且代码有效。
我重建了我的图像并以同样的方式进行了测试。没用...
我设法找到了解决方案。 代码没问题。问题出在我的配置上。
正如我上面所描述的,我测试了 gcc/gfortran 的不同配置,看看它是否影响了 Cythonizing。它不是。 因此,我继续反汇编我的 Dockerfile,以找到导致代码中断的步骤。原来是conda安装了numpy
我上面的所有测试都是使用 pip 进行的 ggc 图像:
$ 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
一包一轮,快捷方便。但是,我在 'production' 图像中使用了 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
...
这里要注意的重要一点是,除了 numpy 之外,conda 也在安装 libgfortran-ng-7.3.0
。在我正在处理的图像中,安装了 gcc/gfortran 8.5.0.
为什么这很重要?当你 运行 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
正如您在列表行中看到的,传递给 gcc 的包含是 /opt/conda/lib
。
$ ls /opt/conda/lib | grep "fortran"
libgfortran.so
libgfortran.so.4
libgfortran.so.4.0.0
在这里,libgfortran
,在我最初编译我的代码时使用的不同版本。
解决方案是:
$ conda install -c conda-forge libgfortran-ng==8.2.0
注意:使用 conda-forge 通道是必要的,在我的例子中,conda 无法解决仅来自基本通道的包的依赖关系。此外,此版本的 libgfortran-ng 还需要将 libblas 从 openblas 版本更改为 mkl,如果您担心的话。
这样,我在conda中安装了一个与我在系统中使用的主版本相同的libgfortran。在重新运行编译 Cythonized 包后,一切正常。
总之,小心conda
PS:感谢@DawidW 的反馈和测试我的代码。