我收到了
Python 3.12.0 | packaged by conda-forge | (main, Oct 3 2023, 08:43:22) [GCC 12.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import foo
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ImportError: /home/.../myproject/build/foo.cpython-312-x86_64-linux-gnu.so: undefined symbol: f2pywrapfoo_
尝试使用
numpy.f2py
从 Fortran 代码加载使用 CMake 创建的 Python 模块时。
我正在遵循 官方指南 在 CMake 中使用
numpy.f2py
。我正在使用最新版本的 NumPy (1.26)、CMake 3.22 以及 Xubuntu 22.04 存储库中的 gfortran
和 gcc
编译器(分别为 11.4 和 12.3)。
我的 Fortran 函数比文档中的示例更简单(因为我对该语言非常陌生),即
function foo(a) result(b)
implicit none
real(kind=8), intent(in) :: a(:,:)
complex(kind=8) :: b(size(a,1),size(a,2))
b = exp((0,1)*a)
end function foo
我的 CMake 中处理模块生成的部分是
if(PYTHON_F2PY)
# https://numpy.org/doc/stable/f2py/buildtools/cmake.html
# https://numpy.org/doc/stable/f2py/usage.html
message("Creating Python module from Fortran code enabled")
# Example for interfacing with Python using f2py
# Check if Python with the required version and components is available
find_package(Python 3.12 REQUIRED
COMPONENTS Interpreter Development.Module NumPy)
# Grab the variables from a local Python installation
# F2PY headers
execute_process(
COMMAND "${Python_EXECUTABLE}"
-c "import numpy.f2py; print(numpy.f2py.get_include())"
OUTPUT_VARIABLE F2PY_INCLUDE_DIR
OUTPUT_STRIP_TRAILING_WHITESPACE
)
#message("${F2PY_INCLUDE_DIR}")
# Print out the discovered paths
include(CMakePrintHelpers)
cmake_print_variables(Python_INCLUDE_DIRS)
cmake_print_variables(F2PY_INCLUDE_DIR)
cmake_print_variables(Python_NumPy_INCLUDE_DIRS)
# Common variables
set(f2py_module_name "foo")
set(fortran_src_file "${CMAKE_SOURCE_DIR}/src/foo.f90")
set(f2py_module_c "${f2py_module_name}module.c")
# Generate sources
add_custom_target(
genpyf
DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/${f2py_module_c}"
)
add_custom_command(
OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${f2py_module_c}"
COMMAND ${Python_EXECUTABLE} -m "numpy.f2py"
"${fortran_src_file}"
-m "${f2py_module_name}"
--lower # Important
DEPENDS "src/foo.f90" # Fortran source
)
# Set up target
Python_add_library(foo MODULE WITH_SOABI
"${CMAKE_CURRENT_BINARY_DIR}/${f2py_module_c}" # Generated
"${F2PY_INCLUDE_DIR}/fortranobject.c" # From NumPy
"${fortran_src_file}" # Fortran source(s) #"foo-f2pywrappers2.f90"
)
# Depend on sources
target_link_libraries(foo PRIVATE Python::NumPy)
add_dependencies(foo genpyf)
target_include_directories(foo PRIVATE "${F2PY_INCLUDE_DIR}")
endif(PYTHON_F2PY)
在我运行的构建目录中
cmake -Wno-dev -DPYTHON_F2PY=1 ..
生成项目,然后
make -j10
构建它。
除其他外,我还得到以下文件:
foo.cpython-312-x86_64-linux-gnu.so
- 我可以在Python中加载的共享库
foo-f2pywrappers.f
- 空的 Fortran 文件
foo-f2pywrappers2.f90
- 包含一些包装器代码的 Fortran 文件
! -*- f90 -*-
! This file is autogenerated with f2py (version:1.26.4)
! It contains Fortran 90 wrappers to fortran functions.
subroutine f2pywrapfoo (foof2pywrap, a, f2py_a_d0, f2py_a_d1)
integer f2py_a_d0
integer f2py_a_d1
real(kind=8) a(f2py_a_d0,f2py_a_d1)
complex(kind=8) foof2pywrap(size(a, 1),size(a, 2))
interface
function foo(a) result (b)
real(kind=8), intent(in),dimension(:,:) :: a
complex(kind=8), dimension(size(a,1),size(a,2)) :: b
end function foo
end interface
foof2pywrap = foo(a)
end
foomodule.c
- 为我的模块生成的 C 代码将用于构建共享库
错误信息
Python 3.12.0 | packaged by conda-forge | (main, Oct 3 2023, 08:43:22) [GCC 12.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import foo
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ImportError: /home/.../myproject/build/foo.cpython-312-x86_64-linux-gnu.so: undefined symbol: f2pywrapfoo_
点为
f2pywrapfoo_
。正如您在上面看到的,foo-f2pywrappers2.f90
文件包含该函数(尽管没有 _
后缀)。
我所做的就是将该包装器添加到 Fortran 源文件列表中
# Set up target
Python_add_library(foo MODULE WITH_SOABI
"${CMAKE_CURRENT_BINARY_DIR}/${f2py_module_c}" # Generated
"${F2PY_INCLUDE_DIR}/fortranobject.c" # From NumPy
"${fortran_src_file}" "foo-f2pywrappers2.f90" # Fortran source(s)
)
我再次运行 CMake 和
make
。当我重复导入模块的步骤时,现在它可以工作了:
Python 3.12.0 | packaged by conda-forge | (main, Oct 3 2023, 08:43:22) [GCC 12.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from foo import foo
>>> import numpy as np
>>> a = np.array([[1,2,3,4], [5,6,7,8]], order='F')
>>> foo(a)
array([[ 0.54030231+0.84147098j, -0.41614684+0.90929743j,
-0.9899925 +0.14112001j, -0.65364362-0.7568025j ],
[ 0.28366219-0.95892427j, 0.96017029-0.2794155j ,
0.75390225+0.6569866j , -0.14550003+0.98935825j]])
问题在于,在首次运行 CMake 和
foo-f2pywrappers2.f90
期间,make
不可用。它仅在执行 make
后创建。所以我真的无法将其添加为CMakeLists.txt
中库构建阶段的依赖项。
有什么想法需要改变才能使这项工作成功吗?
您可以通过简单的
Makefile
来做到这一点:
f2py_module_name = foo
all: foo.pyf foo.cpython-311-x86_64-linux-gnu.so
clean:
rm -f *.pyf *.so *wrappers* *module.c
foo.pyf: src/foo.f90
f2py -m $(f2py_module_name) $< -h $@ --overwrite-signature
foo.cpython-311-x86_64-linux-gnu.so: src/foo.f90
f2py -m $(f2py_module_name) -c $<
foo-f2pywrappers.f foomodule.c foo-f2pywrappers2.f90: src/foo.f90
f2py -m $(f2py_module_name) $< --lower
test:
python3 test.py
或者,
CMakeLists.txt
可能看起来像:
cmake_minimum_required(VERSION 3.12)
project(foo_f2py)
find_package(Python 3.12 REQUIRED COMPONENTS Interpreter Development.Module NumPy)
set(f2py_module_name foo)
add_custom_target(generate_foo_pyf
COMMAND f2py -m ${f2py_module_name} src/foo.f90 -h foo.pyf --overwrite-signature
COMMENT "Generate foo.pyf."
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
VERBATIM
)
add_custom_target(generate_foo_so
COMMAND f2py -m ${f2py_module_name} -c src/foo.f90
COMMENT "Generate foo.cpython-311-x86_64-linux-gnu.so."
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
VERBATIM
)
add_custom_target(clean_files
COMMAND rm -f *.pyf *.so *wrappers* *module.c
COMMENT "Clean files."
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
VERBATIM
)
add_custom_target(run_tests
COMMAND python3 test.py
COMMENT "Run test."
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
VERBATIM
)
add_dependencies(generate_foo_so generate_foo_pyf)
# Set default target.
add_custom_target(default_target ALL DEPENDS generate_foo_so)
构建:
mkdir build
cd build
cmake ..
make
# Return to directory containing built package.
cd ..
为了使用您的 Python 版本(3.12.0)评估这种方法,我制作了一个小 Docker 镜像。
🗎
Dockerfile
FROM python:3.12.0
RUN apt-get update -qq && \
apt-get install -y -qq cmake gfortran
WORKDIR /app
COPY requirements.txt .
RUN pip3 install -r requirements.txt
COPY . .
RUN rm -rf build && \
mkdir build && \
cd build && \
cmake .. && \
# make
true
🗎
requirements.txt
numpy==1.26.4
meson==1.3.2
ninja==1.11.1.1