科学计算, 生活

Python和Fortran的混合编程

这里使用 f2py 来实现 Python 和 Fortran 的混合编程。 f2py 是 NumPy 提供的工具,可将 Fortran 代码编译为 Python 可调用的模块,是 Python 中调用 Fortran 代码的最常用方法。

在使用 f2py 前可能需要安装:

pip install meson
pip install ninja

sudo apt-get update
sudo apt-get install gfortran

一、入门的例子

将 example.f90 文件和 a.py 文件放在同一个文件夹中。

example.f90 文件内容:

subroutine hello()
    print *, "Hello from Fortran!"
end subroutine hello

a.py 文件内容:

import example
example.hello()

在当前文件夹中使用命令:

f2py -c -m example example.f90

说明:

  • -c ​​编译​​(compile),表示要生成动态链接库。动态链接库的文件格式为: .so(Linux 系统)或 .pyd(Windows 系统)。在 Windows 系统下编译时可能需要额外的 DLL 文件,报错可能性比较大。这里推荐直接在 Linux 系统下测试。 在 Linux 中,.so 文件(Shared Object 共享对象文件)是标准的动态库格式。
  • -m example 指定生成的 Python 模块名为 example(导入时用 import example)。
  • example.f90 为输入的 Fortran 源代码文件。

编译后在文件夹中得到文件(大概的文件名):example.cpython-39-x86_64-linux-gnu.so

然后运行 Python 程序,调用这个模块:

python a.py

运行结果:

Hello from Fortran!

二、循环求和的例子

example.f90 文件内容:

subroutine sum_matrix(matrix, total)
    implicit none
    ! 输入参数
    real, intent(in)  :: matrix(:,:)  ! 假定形状数组
    real, intent(out) :: total  ! 输出总和
    
    ! 局部变量
    integer :: i, j
    
    ! 双重循环计算总和
    total = 0.0
    do j = 1, size(matrix, 2)  ! 获取列数
        do i = 1, size(matrix, 1)  ! 获取行数
            total = total + matrix(i, j)
        end do
    end do
end subroutine sum_matrix

a.py 文件内容:

import example

matrix = [[1, 2], [3, 4]]
print(example.sum_matrix(matrix))

print('---')

# 时间对比

import numpy as np
import time

matrix = np.random.rand(5000, 5000)

start = time.time()
print(example.sum_matrix(matrix))
end = time.time()
print('Python + Fortran 循环求和时间:', end - start)

start = time.time()
sum_result = 0
for i0 in range(matrix.shape[0]):
    for j0 in range(matrix.shape[1]):
        sum_result += matrix[i0, j0]
print(sum_result)
end = time.time()
print('Python 循环求和时间:', end - start)

from numba import jit
@jit
def numba_for_sum_matrix(matrix):
    sum_result = 0
    for i0 in range(matrix.shape[0]):
        for j0 in range(matrix.shape[1]):
            sum_result += matrix[i0, j0]
    return sum_result

start = time.time()
print(numba_for_sum_matrix(matrix))
end = time.time()
print('Python numba 循环求和时间:', end - start)

start = time.time()
print(np.sum(matrix))
end = time.time()
print('Python numpy.sum() 求和时间:', end - start)

编译和运行:

f2py -c -m example example.f90
python a.py

运行结果:

10.0
---
12497711.0
Python + Fortran 循环求和时间: 0.7034428119659424
12498084.4906657
Python 循环求和时间: 8.130049228668213
12498084.4906657
Python numba 循环求和时间: 1.0548856258392334
12498084.490664132
Python numpy.sum() 求和时间: 0.0256350040435791

大致结论:

  • Python + Fortran 循环求和的时间明显比 Python 循环求和时间短,同时也比 Numba 的表现好些。
  • 如果只是求和,那么 numpy.sum() 的表现最好,这是因为 numpy 核心部分主要基于 C 实现,以及做了极致的优化。

三、矩阵求逆的例子

可能需要安装 LAPACK/BLAS:

sudo apt update
sudo apt install liblapack-dev libblas-dev

通过以下命令可以查看版本:

apt show liblapack-dev libblas-dev

这样安装 LAPACK/BLAS 默认的是 Netlib 版本的。如果需要性能更好一些的,也可以考虑安装和配置  ​​OpenBLAS​ 版本和 MKL 版本的,可能比较麻烦,这里暂不讨论。

example.f90 文件内容:

! 矩阵求逆子程序
subroutine inverse_matrix(A, Ainv)
    implicit none
    real(8), intent(in) :: A(:,:)       ! 输入矩阵
    real(8), intent(inout) :: Ainv(:,:)    ! 输出逆矩阵
    real(8), allocatable :: work(:)
    integer, allocatable :: ipiv(:)
    integer :: n, info

    n = size(A,1)
    allocate(ipiv(n), work(n))
    
    Ainv = A  ! 复制输入矩阵
    
    call dgetrf(n, n, Ainv, n, ipiv, info)  ! LU 分解
    call dgetri(n, Ainv, n, ipiv, work, n, info) ! 计算逆矩阵
        
    deallocate(ipiv, work)
end subroutine inverse_matrix

特别说明:上面 Fortran 代码中把求逆后的矩阵 Ainv 也设置为既是输出变量,又是输入变量,所以在下面 Python 的代码中,不再以函数的形式调用,而是以子程序的形式。之所以这么处理,是因为如果 Ainv(:,:) 只是作为输出变量,在这里会出现数组维度没有正确定义的报错。

a.py 文件内容:

import numpy as np
import example

A = [[1.0, 2.0, 3.0],
    [0.0, 1.0, 4.0],
    [5.0, 6.0, 0.0]]

Ainv_1 = np.asfortranarray(np.empty_like(A))
example.inverse_matrix(A, Ainv_1) # 调用 Fortran 子程序
print(Ainv_1)

Ainv_2 = np.linalg.inv(A)
print(Ainv_2)

print('---')

# 时间对比

A = np.random.rand(3000, 3000)

import time
start = time.time()
Ainv_1 = np.asfortranarray(np.empty_like(A))
example.inverse_matrix(A, Ainv_1) # 调用 Fortran 子程序
end = time.time()
print('Python + Fortran 求逆时间:', end - start)

start = time.time()
Ainv_2 = np.linalg.inv(A)
end = time.time()
print('Python np.linalg.inv() 求逆时间:', end - start)

说明:这里的 Ainv_1 变量作为 Fortran 中的输入输出变量,在调用之前可能需要用 np.asfortranarray() 做个转换,不然可能会报错。

编译和运行:

f2py -c -m example example.f90
python a.py

运行结果:

[[-24.  18.   5.]
 [ 20. -15.  -4.]
 [ -5.   4.   1.]]
[[-24.  18.   5.]
 [ 20. -15.  -4.]
 [ -5.   4.   1.]]
---
Python + Fortran 求逆时间: 10.027450561523438
Python np.linalg.inv() 求逆时间: 2.1545636653900146

大致结论:

  • 使用 f2py 来混合编程时,gfortran 中 LAPACK/BLAS 可以成功调用。
  • np.linalg.inv() 会比 Python + Fortran 求逆的表现更好些。
10 次浏览

【说明:本站主要是个人的一些笔记和代码分享,内容可能会不定期修改。为了使全网显示的始终是最新版本,这里的文章未经同意请勿转载。引用请注明出处:https://www.guanjihuan.com

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

Captcha Code