はじめに
Python 3.12以降では、NumPyのf2pyがmesonバックエンドを使用するようになりました。これにより、従来の--fcompilerオプションが使えなくなり、Intel Fortranコンパイラ(ifx)を使う場合は追加の設定が必要になります。
本記事では、Windows環境でPython 3.12 + Intel oneAPI 2024のifxを使ってf2pyでFortran拡張モジュールをビルドする方法を解説します。
環境
- Windows 10/11 または Windows Server
- Python 3.12以降
- NumPy 2.x
- Intel oneAPI 2024.2 (ifx)
- Visual Studio 2022 (MSVCリンカ用)
- meson, ninja
問題点
Python 3.12以降でf2pyを使おうとすると、以下のエラーが発生します:
Cannot use distutils backend with Python>=3.12, using meson backend instead.
さらに、Intel Fortranを使おうとしても:
- mesonがデフォルトでgfortranを探す
- ifxを見つけても、シンボル名の規約が合わない(大文字 vs 小文字、下線の有無)
解決方法
1. 必要なパッケージのインストール
pip install numpy meson ninja
2. meson native fileの作成
Intel Fortranのパスとコンパイルオプションを指定するnative fileを作成します。
meson_intel.ini:
[binaries]
fc = 'C:/Program Files (x86)/Intel/oneAPI/compiler/2024.2/bin/ifx.exe'
[built-in options]
fortran_args = ['/assume:underscore', '/names:lowercase']
重要なオプション:
-
/assume:underscore: サブルーチン名に下線サフィックスを追加 -
/names:lowercase: シンボル名を小文字に統一
これらがないと、f2pyが生成するCラッパーが期待するシンボル名(add_arrays_)とFortranが生成するシンボル名(ADD_ARRAYS)が一致しません。
3. ビルドスクリプト
以下のPythonスクリプトでビルドを自動化できます。
build_f2py.py:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
f2pyでFortranモジュールをビルド(Intel Fortran使用)
"""
import subprocess
import sys
import os
import shutil
import glob
def find_intel_fortran():
"""Intel Fortranコンパイラのパスを探す"""
possible_paths = [
r"C:\Program Files (x86)\Intel\oneAPI\compiler\2024.2\bin\ifx.exe",
r"C:\Program Files (x86)\Intel\oneAPI\compiler\2024.1\bin\ifx.exe",
r"C:\Program Files (x86)\Intel\oneAPI\compiler\latest\bin\ifx.exe",
]
for path in possible_paths:
if os.path.exists(path):
return path
return None
def main():
script_dir = os.path.dirname(os.path.abspath(__file__))
os.chdir(script_dir)
# Intel Fortranを探す
ifx_path = find_intel_fortran()
if ifx_path is None:
print("ERROR: Intel Fortran (ifx) not found!")
return 1
print(f"Found Intel Fortran: {ifx_path}")
# meson native fileを作成
ifx_path_escaped = ifx_path.replace("\\", "/")
native_file_content = f"""[binaries]
fc = '{ifx_path_escaped}'
[built-in options]
fortran_args = ['/assume:underscore', '/names:lowercase']
"""
native_file = os.path.join(script_dir, "meson_intel.ini")
with open(native_file, "w") as f:
f.write(native_file_content)
# 環境変数を設定
env = os.environ.copy()
intel_bin = os.path.dirname(ifx_path)
env["PATH"] = intel_bin + ";" + env.get("PATH", "")
env["FC"] = ifx_path
# Step 1: f2pyでCラッパーを生成
print("Step 1: Generating C wrapper with f2py...")
subprocess.run([
sys.executable, "-m", "numpy.f2py",
"your_module.f90", # ここを変更
"-m", "your_module", # ここを変更
"-h", "your_module.pyf",
"--overwrite-signature"
], env=env, check=True)
subprocess.run([
sys.executable, "-m", "numpy.f2py",
"your_module.pyf"
], env=env, check=True)
# Step 2: mesonでビルド
print("Step 2: Building with meson...")
build_dir = os.path.join(script_dir, "build")
if os.path.exists(build_dir):
shutil.rmtree(build_dir)
subprocess.run([
"meson", "setup", "build",
f"--native-file={native_file}",
"--buildtype=release"
], env=env, cwd=script_dir, check=True)
subprocess.run([
"meson", "compile", "-C", "build"
], env=env, cwd=script_dir, check=True)
# .pydファイルをコピー
for pyd in glob.glob(os.path.join(build_dir, "*.pyd")):
shutil.copy(pyd, script_dir)
print(f"Created: {os.path.basename(pyd)}")
return 0
if __name__ == "__main__":
sys.exit(main())
4. meson.buildの作成
mesonでビルドするために、meson.buildファイルが必要です。
meson.build:
project('your_module', 'c', 'fortran',
version: '0.1',
)
py = import('python').find_installation(pure: false)
py_dep = py.dependency()
incdir_numpy = run_command(py,
['-c', 'import numpy; print(numpy.get_include())'],
check: true
).stdout().strip()
incdir_f2py = run_command(py,
['-c', 'import numpy.f2py; print(numpy.f2py.get_include())'],
check: true
).stdout().strip()
inc_dirs = include_directories(incdir_numpy, incdir_f2py)
py.extension_module('your_module',
'your_module.f90',
'your_modulemodule.c', # f2pyが生成
incdir_f2py / 'fortranobject.c',
include_directories: inc_dirs,
dependencies: py_dep,
install: true
)
5. サンプルFortranコード
test_simple.f90:
subroutine add_arrays(a, b, c, n)
implicit none
integer, intent(in) :: n
real(8), intent(in) :: a(n), b(n)
real(8), intent(out) :: c(n)
!f2py intent(in) :: a, b
!f2py intent(out) :: c
!f2py intent(hide) :: n
integer :: i
do i = 1, n
c(i) = a(i) + b(i)
end do
end subroutine
subroutine dot_product_f(a, b, result, n)
implicit none
integer, intent(in) :: n
real(8), intent(in) :: a(n), b(n)
real(8), intent(out) :: result
!f2py intent(in) :: a, b
!f2py intent(out) :: result
!f2py intent(hide) :: n
result = dot_product(a, b)
end subroutine
使用例
ビルド後、Pythonから以下のように呼び出せます:
import numpy as np
import test_fortran
# 配列加算
a = np.array([1.0, 2.0, 3.0])
b = np.array([4.0, 5.0, 6.0])
c = test_fortran.add_arrays(a, b)
print(f"add_arrays result: {c}") # [5. 7. 9.]
# 内積
result = test_fortran.dot_product_f(a, b)
print(f"dot_product result: {result}") # 32.0
トラブルシューティング
LNK2001: 外部シンボル xxx_ は未解決です
Fortranのシンボル名規約が合っていません。meson native fileに以下を追加してください:
fortran_args = ['/assume:underscore', '/names:lowercase']
python312_d.lib が見つからない
デバッグビルドになっています。--buildtype=releaseを指定してください。
ifxが見つからない
環境変数FCにifxのフルパスを設定してください:
env["FC"] = r"C:\Program Files (x86)\Intel\oneAPI\compiler\2024.2\bin\ifx.exe"
ifortが使われる
NumPy 2.xのmesonバックエンドはifortよりifxを優先しますが、環境によってはifortが選ばれることがあります。FC環境変数で明示的にifxを指定してください。
まとめ
Python 3.12以降でf2pyとIntel Fortran (ifx) を使うには:
- meson native fileでifxのパスとコンパイルオプションを指定
- **
/assume:underscoreと/names:lowercase**オプションでシンボル名の規約を合わせる - **環境変数
FC**でifxを明示的に指定 - **
--buildtype=release**でリリースビルドを指定
これにより、Intel Fortranの高性能な最適化を活かしたPython拡張モジュールを作成できます。