Python Fortran Rosetta Stone#

在表达能力和特性方面,Python 与 NumPy 和 Fortran 非常相似。此 Rosetta Stone 展示了如何在两种语言中并排实现许多常见的习惯用法。

如何执行代码片段#

例如,请考虑以下代码片段

Python#
from numpy import array, size, shape, min, max, sum
a = array([1, 2, 3])
print(shape(a))
print(size(a))
print(max(a))
print(min(a))
print(sum(a))
Fortran#
integer :: a(3)
a = [1, 2, 3]
print *, shape(a)
print *, size(a)
print *, maxval(a)
print *, minval(a)
print *, sum(a)
end program

在 Python 中,只需将代码保存到文件 example.py 中,并使用 python example.py 执行。在 Fortran 中,将其保存到文件 example.f90 中,并在文件末尾追加行 end(有关此工作原理的更多信息,请参阅 模块 部分)。使用 gfortran example.f90 编译,并使用 ./a.out 执行(当然,您可以向 gfortran 添加编译选项,例如生成具有不同名称的可执行文件)。

数组#

数组是 Fortran 中的内置类型,在 Python 的 NumPy 模块中可用。用法相同,但存在以下差异

  • Fortran(默认情况下)从 1 开始计数,NumPy 始终从 0 开始

  • Fortran 数组切片(切片)包含两端,在 NumPy 中,包含起始点,但不包含结束点

  • 在 C 中,数组按行存储在内存中(默认情况下 NumPy 使用 C 存储),而在 Fortran 中,则按列存储(这仅在接下来的两点中重要)

  • 默认情况下,reshape 在 Fortran 中使用 Fortran 顺序,在 NumPy 中使用 C 顺序(在这两种情况下,可选参数 order 允许使用其他顺序)。这在 reshape 在其他操作(如展平)中隐式使用时也很重要。

  • 第一个索引在 Fortran 中最快,而在 NumPy 中,最后一个索引最快

  • 默认情况下,NumPy 会很好地打印二维数组,而在 Fortran 中,必须指定格式才能打印它(Fortran 也按列打印,因此必须转置数组才能按行打印)

其他所有内容都相同,特别是

  • NumPy 和 Fortran 数组操作之间存在一对一的对应关系,并且可以在两种语言中轻松/自然地表达相同的事物

  • 对于二维数组,第一个索引是行索引,第二个是列索引(就像在数学中一样)

  • 如果 NumPy 和 Fortran 数组具有相同的形状并且相同的元素对应于相同的索引,则它们是等效的(内部内存存储是什么并不重要)

  • 允许任何涉及数学函数的数组表达式,例如 a**2 + 2*a + exp(a)sin(a)a * ba + b(它按元素进行运算)

  • 您需要使用函数来使用矩阵乘法将两个矩阵相乘

  • 高级索引/切片

  • 数组可以是任何整数、实数或复数类型

Python#
from numpy import array, size, shape, min, max, sum
a = array([1, 2, 3])
print(shape(a))
print(size(a))
print(max(a))
print(min(a))
print(sum(a))
Fortran#
integer :: a(3)
a = [1, 2, 3]
print *, shape(a)
print *, size(a)
print *, maxval(a)
print *, minval(a)
print *, sum(a)
end program
Python#
from numpy import reshape
a = reshape([1, 2, 3, 4, 5, 6], (2, 3))
b = reshape([1, 2, 3, 4, 5, 6], (2, 3), order="F")
print(a[0, :])
print(a[1, :])
print()
print(b[0, :])
print(b[1, :])
Fortran#
integer :: a(2, 3), b(2, 3)
a = reshape([1, 2, 3, 4, 5, 6], [2, 3], order=[2, 1])
b = reshape([1, 2, 3, 4, 5, 6], [2, 3])
print *, a(1, :)
print *, a(2, :)
print *
print *, b(1, :)
print *, b(2, :)
end program
Python#
[1 2 3]
[4 5 6]

[1 3 5]
[2 4 6]
Fortran#
1           2           3
4           5           6

1           3           5
2           4           6
Python#
from numpy import array, size, shape, max, min
a = array([[1, 2, 3], [4, 5, 6]])
print(shape(a))
print(size(a, 0))
print(size(a, 1))
print(max(a))
print(min(a))
print(a[0, 0], a[0, 1], a[0, 2])
print(a[1, 0], a[1, 1], a[1, 2])
print(a)
Fortran#
integer :: a(2, 3)
a = reshape([1, 2, 3, 4, 5, 6], [2, 3], order=[2, 1])
print *, shape(a)
print *, size(a, 1)
print *, size(a, 2)
print *, maxval(a)
print *, minval(a)
print *, a(1, 1), a(1, 2), a(1, 3)
print *, a(2, 1), a(2, 2), a(2, 3)
print "(3i5)", transpose(a)
end program
Python#
(2, 3)
2
3
6
1
1 2 3
4 5 6
[[1 2 3]
[4 5 6]]
Fortran#
2 3
2
3
6
1
1 2 3
4 5 6
1 2 3
4 5 6
Python#
from numpy import array, all, any
i = array([1, 2, 3])
all(i == [1, 2, 3])
any(i == [2, 2, 3])
Fortran#
integer :: i(3)
i = [1, 2, 3]
all(i == [1, 2, 3])
any(i == [2, 2, 3])
Python#
from numpy import array, empty
a = array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
b = empty(10)
b[:] = 0
b[a > 2] = 1
b[a > 5] = a[a > 5] - 3
Fortran#
integer :: a(10), b(10)
a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
where (a > 5)
    b = a - 3
elsewhere (a > 2)
    b = 1
elsewhere
    b = 0
end where
end program
Python#
from numpy import array, empty                     
a = array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])     
b = empty(10)                                  
for i in range(len(a)):                 
    if a[i] > 5:                            
        b[i] = a[i] - 3                        
    elif a[i] > 2:                     
        b[i] = 1                 
    else:                                      
        b[i] = 0  
Fortran#
integer :: a(10), b(10)
a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
where (a > 5)
    b = a - 3
elsewhere (a > 2)
    b = 1
elsewhere
    b = 0
end where
end program
Python#
from numpy import array, sum, ones, size
a = array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
print(sum(a))
print(sum(a[(a > 2) & (a < 6)]))
o = ones(size(a), dtype="int")
print(sum(o[(a > 2) & (a < 6)]))
Fortran#
integer :: a(10)
a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
print *, sum(a)
print *, sum(a, mask=a > 2 .and. a < 6)
print *, count(a > 2 .and. a < 6)
end program
Python#
from numpy import array, dot
a = array([[1, 2], [3, 4]])
b = array([[2, 3], [4, 5]])
print(a * b)
print(dot(a, b))
Fortran#
integer :: a(2, 2), b(2, 2)
a = reshape([1, 2, 3, 4], [2, 2], order=[2, 1])
b = reshape([2, 3, 4, 5], [2, 2], order=[2, 1])
print *, a * b
print *, matmul(a, b)
end program
Python#
[[ 2  6]
[12 20]]
[[10 13]
[22 29]]
Fortran#
2          12           6          20
10          22          13          29
Python#
from numpy import array, pi
a = array([i for i in range(1, 7)])
b = array([(2*i*pi+1)/2 for i in range(1, 7)])
c = array([i for i in range(1, 7) for j in range(1, 4)])
Fortran#
use types, only: dp
use constants, only: pi
integer :: a(6), c(18)
real(dp) :: b(6)
integer :: i, j
a = [ (i, i = 1, 6) ]
b = [ ((2*i*pi+1)/2, i = 1, 6) ]
c = [ ((i, j = 1, 3), i = 1, 6) ]

一些索引示例#

Python#
from numpy import array
a = array([1, 2, 3])
b = a
print(a[:])
print(b[:])
print(a[:2])
print(b[:2])
Fortran#
integer :: a(3), b(-1:1)
a = [1, 2, 3]
b = a
print *, a(:)
print *, b(:)
print *, a(:2)
print *, b(:0)
end program
Python#
[1 2 3]
[1 2 3]
[1 2]
[1 2]
Fortran#
1           2           3
1           2           3
1           2
1           2

前 n 个元素

Python#
a[:n]
Fortran#
a(:n)     ! assuming starting index 1 (default)
a(:n+m-1) ! assuming starting index m

后 n 个元素

Python#
a[-n:] # equivalent to a[size(a)-n:]
Fortran#
a(size(a)-n+1:)

选择索引 i 和 j 之间的元素(包括 i 和 j)

Python#
a[i:j+1]
Fortran#
a(i:j)

选择从索引 i 开始的 n 个元素

Python#
a[i:i+n]
Fortran#
a(i:i+n-1)

选择 -n 到 n 之间的元素
(包括 -n 和 n)

Python#
# Not possible (arrays start at 0 index)
Fortran#
a(-n:n)

遍历整个数组

Python#
r = 1                                     
for i in range(len(a)):            
    r *= a[i]  
Fortran#
r = 1
do i = 1, size(a)
    r = r*a(i)
end do

循环遍历第 3 个到第 7 个元素(包括第 3 个和第 7 个)

Python#
r = 1
for i in range(3, 8):
    r *= a[i]
Fortran#
r = 1
do i = 3, 7
    r = r*a(i)
end do

将字符串在索引 i 和 j 处拆分为三个部分,这些部分为

Python#
a[ :i]
a[i:j]
a[j: ]
Fortran#
a( :i-1)
a(i:j-1)
a(j:   )

拉普拉斯更新

Python#
u[1:-1,1:-1] = ((u[2:,1:-1]+u[:-2,1:-1])*dy2 +
    (u[1:-1,2:] + u[1:-1,:-2])*dx2) / (2*(dx2+dy2))
Fortran#
nx = size(u, 1)
ny = size(u, 2)
u(2:nx-1,2:ny-1) = ((u(3:,2:ny-1)+u(:ny-2,2:ny-1))*dy2 + &
    (u(2:nx-1,3:) + u(2:nx-1,:ny-2))*dx2) / (2*(dx2+dy2))

模块#

Fortran 和 Python 导入语句的比较

Python#
from A import foo
from A import foo as Afoo
from A import *
Fortran#
use A, only: foo
use A, only: Afoo => foo
use A

以下 Python 语句在 Fortran 中没有等效项

Python#
import A
import ALongName as A
Fortran#

Fortran 模块的工作方式与 Python 模块相同。差异

  • Fortran 模块不能嵌套(即它们都是顶级模块,而在 Python 中,可以使用 __init__.py 文件任意嵌套模块)

  • Python 中的 import A 在 Fortran 中没有等价的功能。

  • 可以在 Fortran 中指定私有模块符号。

相同的功能

  • 一个模块包含变量、类型和函数/子程序。

  • 默认情况下,所有变量/类型/函数都可以从其他模块访问,但可以通过显式指定哪些符号是私有的或公有的来更改此行为(在 Python 中,这只适用于隐式导入)。

  • 公共符号不会污染全局命名空间,但需要从模块中显式导入才能使用它们。

  • 将符号导入模块成为该模块的一部分,然后可以从其他模块导入。

  • 可以使用显式或隐式导入(推荐使用显式导入)。

创建模块

Python a.py#
i = 5

def f(x):                            
    return x + 5          

def g(x):                                 
    return x - 5  
Fortran a.f90#
module a
implicit none

integer :: i = 5

contains

integer function f(x) result(r)
integer, intent(in) :: x
r = x + 5
end function

integer function g(x) result(r)
integer, intent(in) :: x
r = x - 5
end function

end module

并从主程序中使用它,如下所示

Python main.py#
from a import f, i

print(f(3))
print(i)
Fortran main.f90#
program main
use a, only: f, i
implicit none
print *, f(3)
print *, i
end program
Python#
8
5
Fortran#
8
5

在 Fortran 中,可以省略 program main 行,也可以只用 end 而不是 end program 结束文件。这样,只需在代码片段末尾添加 end 即可测试任何代码片段。

为了指定哪些符号是公共的和私有的,可以使用

Python#
__all__ = ["i", "f"]

i = 5

def f(x):                            
    return x + 5          

def g(x):                                 
    return x - 5  
Fortran#
module a
implicit none
private
public i, f

integer :: i = 5

contains

integer function f(x) result(r)
integer, intent(in) :: x
r = x + 5
end function

integer function g(x) result(r)
integer, intent(in) :: x
r = x - 5
end function

end module

不过存在差异。在 Fortran 中,符号 g 将是私有的(无论使用显式或隐式导入,都无法从其他模块导入),fi 是公共的。在 Python 中,当使用隐式导入时,不会导入符号 g,但当使用显式导入时,仍然可以导入符号 g

浮点数#

NumPy 和 Fortran 都可以处理任何指定的精度,如果未指定精度,则使用默认的平台精度。

在 Python 中,默认精度通常是双精度,而在 Fortran 中是单精度。另请参见相关的 PythonNumPy 文档。

Python#
from numpy import float32
f = float32(1.1)
Fortran#
real :: f
f = 1.1
Python#
f = 1.1            # 1.1
f = 1e8            # 100000000.0
f = float(1) / 2   # 0.5
f = float(1 / 2)   # 0.0
f = float(5)       # 5.0
Fortran#
integer, parameter :: dp=kind(0.d0)
real(dp) :: f
f = 1.1_dp             ! 1.1
f = 1e8_dp             ! 100000000.0
f = real(1, dp) / 2    ! 0.5
f = 1 / 2              ! 0.0
f = 5                  ! 5.0

在 Fortran 中,习惯上始终使用 _dp 后缀指定精度,其中 dp 在下面的 types.f90 模块中定义为 integer, parameter :: dp=kind(0.d0)(以便可以在需要时在一个地方更改精度)。如果未指定精度,则使用单精度(因此,这会导致单/双精度损坏),因此*始终*需要指定精度。

在下面所有 Fortran 代码片段中,假设您已执行 use types, only: dptypes.f90 模块为

Fortran#
module types
implicit none
private
public dp, hp
integer, parameter :: dp=kind(0.d0), &          ! double precision
                        hp=selected_real_kind(15) ! high precision
end module

数学和复数#

Fortran 具有内置的数学函数,在 Python 中,必须从 math 模块或(对于更高级的函数)从 SciPy 包中导入它们。Fortran 不包含常量,因此必须使用 constants.f90 模块(包含在下面)。

否则用法相同。

Python#
from math import cos, pi, e
I = 1j
print(e**(I*pi) + 1)
print(cos(pi))
print(4 + 5j)
print(4 + 5*I)
Fortran#
use constants, only: pi, e
complex(dp) :: I = (0, 1)
print *, e**(I*pi) + 1
print *, cos(pi)
print *, (4, 5)
print *, 4 + 5*I
Python#
1.22460635382e-16j
-1.0
(4+5j)
(4+5j)
Fortran#
(  0.0000000000000000     , 1.22460635382237726E-016)
-1.0000000000000000
(  4.0000000    ,  5.0000000    )
(  4.0000000000000000     ,  5.0000000000000000     )

Fortran 模块 constants.f90

Fortran#
module constants
use types, only: dp
implicit none
private
public pi, e, I
! Constants contain more digits than double precision, so that
! they are rounded correctly:
real(dp), parameter :: pi   = 3.1415926535897932384626433832795_dp
real(dp), parameter :: e    = 2.7182818284590452353602874713527_dp
complex(dp), parameter :: I = (0, 1)
end module

字符串和格式化#

Python 和 Fortran 的功能几乎等效,只有语法略有不同。

在 Python 和 Fortran 中,字符串都可以用 "' 分隔。

有三种通用的方法可以打印格式化字符串

Python#
print("Integer", 5, "and float", 5.5, "works fine.")
print("Integer " + str(5) + " and float " + str(5.5) + ".")
print("Integer %d and float %f." % (5, 5.5))
Fortran#
use utils, only: str
print *, "Integer", 5, "and float", 5.5, "works fine."
print *, "Integer " // str(5) // " and float " // str(5.5_dp) // "."
print '("Integer ", i0, " and float ", f0.6, ".")', 5, 5.5
Python#
Integer 5 and float 5.5 works fine.
Integer 5 and float 5.5.
Integer 5 and float 5.500000.
Fortran#
Integer           5 and float   5.5000000     works fine.
Integer 5 and float 5.500000.
Integer 5 and float 5.500000.

以下是一些常用的格式

Python#
print("%3d" % 5)
print("%03d" % 5)
print("%s" % "text")
print("%15.7f" % 5.5)
print("%23.16e" % -5.5)
Fortran#
print '(i3)', 5
print '(i3.3)', 5
print '(a)', "text"
print '(f15.7)', 5.5_dp
print '(es23.16)', -5.5_dp
Python#
5
005
text
   5.5000000
-5.5000000000000000e+00
Fortran#
5
005
text
   5.5000000
-5.5000000000000000E+00

嵌套函数#

Python 和 Fortran 都允许嵌套函数访问外部函数的命名空间。

Python#
def foo(a, b, c):                   
    def f(x):                                    
        return a*x**2 + b*x + c                    
    print(f(1), f(2), f(3))
Fortran#
subroutine foo(a, b, c)
real(dp) :: a, b, c
print *, f(1._dp), f(2._dp), f(3._dp)

contains

real(dp) function f(x) result(y)
real(dp), intent(in) :: x
y = a*x**2 + b*x + c
end function f

end subroutine foo

使用方法如下

Python#
foo(1, 2, 1)
foo(2, 2, 1)
Fortran#
call foo(1._dp, 2._dp, 1._dp)
call foo(2._dp, 2._dp, 1._dp)
Python#
4 9 16
5 13 25
Fortran#
4.0000000000000000        9.0000000000000000        16.000000000000000
5.0000000000000000        13.000000000000000        25.000000000000000

可以在回调中使用嵌套函数传递上下文

Python#
def simpson(f, a, b):                       
    return (b-a) / 6 * (f(a) + 4*f((a+b)/2) + f(b))    
                                                
def foo(a, k):                           
    def f(x):                                    
        return a*sin(k*x)                       
    print(simpson(f, 0., pi))                       
    print(simpson(f, 0., 2*pi))  
Fortran#
real(dp) function simpson(f, a, b) result(s)
real(dp), intent(in) :: a, b
interface
    real(dp) function f(x)
    use types, only: dp
    implicit none
    real(dp), intent(in) :: x
    end function
end interface
s = (b-a) / 6 * (f(a) + 4*f((a+b)/2) + f(b))
end function


subroutine foo(a, k)
real(dp) :: a, k
print *, simpson(f, 0._dp, pi)
print *, simpson(f, 0._dp, 2*pi)

contains

real(dp) function f(x) result(y)
real(dp), intent(in) :: x
y = a*sin(k*x)
end function f

end subroutine foo

使用方法如下

Python#
foo(0.5, 1.)
foo(0.5, 2.)
Fortran#
call foo(0.5_dp, 1._dp)
call foo(0.5_dp, 2._dp)
Python#
1.0471975512
1.28244712915e-16
6.41223564574e-17
-7.69468277489e-16
Fortran#
1.0471975511965976
1.28244712914785977E-016
6.41223564573929883E-017
-7.69468277488715811E-016

循环中的控制流#

Python 和 Fortran 中常见的循环类型分别是 fordo 循环。在这两种语言中,都可以跳过单个循环或停止循环的执行,但执行此操作的语句有所不同。

break 和 exit 语句#

在 Python 中,break 用于停止最内层循环的执行。在 Fortran 中,这是通过 exit 语句完成的。对于命名循环,可以通过将循环名称附加到 exit 语句来指定受影响的循环。否则,将中断最内层循环。

Python 的 exit() 中断程序或交互式会话的执行。

Python#
for i in range(1, 9):                  
    if i>2:                                       
        break                       
    print(i)
Fortran#
loop_name: do i = 1, 8
    if (i>2) exit loop_name
    print *, i
end do loop_name

continue 和 cycle 语句#

Python 的 continue 语句用于跳过循环体中的其余部分。然后循环在下一个迭代周期继续。Fortran 的 continue 语句不执行任何操作,应使用 cycle 代替。对于命名循环,可以通过将循环名称附加到 cycle 语句来指定受影响的循环。

Python#
for i in range(1, 9):                 
    if i%2 == 0:                   
        continue                   
    print(i)
Fortran#
loop_name: do i = 1, 8
    if (modulo(i, 2) == 0) cycle loop_name
    print *, i
end do loop_name

示例#

Mandelbrot 集#

这是一个用 NumPy 编写的真实世界程序,并翻译成 Fortran。

Python#
import numpy as np

ITERATIONS = 100
DENSITY = 1000
x_min, x_max = -2.68, 1.32
y_min, y_max = -1.5, 1.5

x, y = np.meshgrid(np.linspace(x_min, x_max, DENSITY),
                   np.linspace(y_min, y_max, DENSITY))
c = x + 1j*y
z = c.copy()
fractal = np.zeros(z.shape, dtype=np.uint8) + 255

for n in range(ITERATIONS):
    print("Iteration %d" % n)
    mask = abs(z) <= 10
    z[mask] *= z[mask]
    z[mask] += c[mask]
    fractal[(fractal == 255) & (~mask)] = 254. * n / ITERATIONS

print("Saving...")
np.savetxt("fractal.dat", np.log(fractal))
np.savetxt("coord.dat", [x_min, x_max, y_min, y_max])
Fortran#
program Mandelbrot
use types, only: dp
use constants, only: I
use utils, only: savetxt, linspace, meshgrid
implicit none

integer, parameter :: ITERATIONS = 100
integer, parameter :: DENSITY = 1000
real(dp) :: x_min, x_max, y_min, y_max
real(dp), dimension(DENSITY, DENSITY) :: x, y
complex(dp), dimension(DENSITY, DENSITY) :: c, z
integer, dimension(DENSITY, DENSITY) :: fractal
integer :: n
x_min = -2.68_dp
x_max = 1.32_dp
y_min = -1.5_dp
y_max = 1.5_dp

call meshgrid(linspace(x_min, x_max, DENSITY), &
    linspace(y_min, y_max, DENSITY), x, y)
c = x + I*y
z = c
fractal = 255

do n = 1, ITERATIONS
    print "('Iteration ', i0)", n
    where (abs(z) <= 10) z = z**2 + c
    where (fractal == 255 .and. abs(z) > 10) fractal = 254 * (n-1) / ITERATIONS
end do

print *, "Saving..."
call savetxt("fractal.dat", log(real(fractal, dp)))
call savetxt("coord.dat", reshape([x_min, x_max, y_min, y_max], [4, 1]))
end program

要运行 Python 版本,您需要 Python 和 NumPy。要运行 Fortran 版本,您需要来自 Fortran-utils 包的 types.f90constants.f90utils.f90。这两个版本都会生成等效的 fractal.datcoord.dat 文件。

生成的碎形可以通过以下方式查看(您需要 matplotlib)

Python#
from numpy import loadtxt
import matplotlib.pyplot as plt

fractal = loadtxt("fractal.dat")
x_min, x_max, y_min, y_max = loadtxt("coord.dat")

plt.imshow(fractal, cmap=plt.cm.hot,
            extent=(x_min, x_max, y_min, y_max))
plt.title('Mandelbrot Set')
plt.xlabel('Re(z)')
plt.ylabel('Im(z)')
plt.savefig("mandelbrot.png")

image

在配备 gFortran 4.6.1 的 Acer 1830T 上的计时结果为

Python

Fortran

加速比

计算

12.749

00.784

16.3 倍

保存

01.904

01.456

1.3 倍

总计

14.653

02.240

6.5 倍

最小二乘拟合#

在 Python 中,我们通过 SciPy 使用 Minpack,在 Fortran 中,我们直接使用 Minpack。我们首先创建一个名为 find_fit_module 的模块,其中包含一个名为 find_fit 的函数。

Python#
from numpy import array
from scipy.optimize import leastsq

def find_fit(data_x, data_y, expr, pars):
    data_x = array(data_x)
    data_y = array(data_y)
    def fcn(x):
        return data_y - expr(data_x, x)
    x, ier = leastsq(fcn, pars)
    if (ier != 1):
        raise Exception("Failed to converge.")
    return x
Fortran#
module find_fit_module
use minpack, only: lmdif1
use types, only: dp
implicit none
private
public find_fit

contains

subroutine find_fit(data_x, data_y, expr, pars)
real(dp), intent(in) :: data_x(:), data_y(:)
interface
    function expr(x, pars) result(y)
    use types, only: dp
    implicit none
    real(dp), intent(in) :: x(:), pars(:)
    real(dp) :: y(size(x))
    end function
end interface
real(dp), intent(inout) :: pars(:)

real(dp) :: tol, fvec(size(data_x))
integer :: iwa(size(pars)), info, m, n
real(dp), allocatable :: wa(:)

tol = sqrt(epsilon(1._dp))
m = size(fvec)
n = size(pars)
allocate(wa(m*n + 5*n + m))
call lmdif1(fcn, m, n, pars, fvec, tol, info, iwa, wa, size(wa))
if (info /= 1) stop "failed to converge"

contains

subroutine fcn(m, n, x, fvec, iflag)
integer, intent(in) :: m, n, iflag
real(dp), intent(in) :: x(n)
real(dp), intent(out) :: fvec(m)
! Suppress compiler warning:
fvec(1) = iflag
fvec = data_y - expr(data_x, x)
end subroutine

end subroutine

end module

然后我们使用它来寻找一个形如 a*x*log(b + c*x) 的非线性拟合,用于素数列表。

Python#
from numpy import size, log
from find_fit_module import find_fit

def expression(x, pars):
    a, b, c = pars
    return a*x*log(b + c*x)

y = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31,
    37, 41, 43, 47, 53, 59, 61, 67, 71]
pars = [1., 1., 1.]
pars = find_fit(range(1, size(y)+1), y, expression, pars)
print(pars)
Fortran#
program example_primes
use find_fit_module, only: find_fit
use types, only: dp
implicit none

real(dp) :: pars(3)
real(dp), parameter :: y(*) = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, &
    37, 41, 43, 47, 53, 59, 61, 67, 71]
integer :: i
pars = [1._dp, 1._dp, 1._dp]
call find_fit([(real(i, dp), i=1,size(y))], y, expression, pars)
print *, pars

contains

function expression(x, pars) result(y)
real(dp), intent(in) :: x(:), pars(:)
real(dp) :: y(size(x))
real(dp) :: a, b, c
a = pars(1)
b = pars(2)
c = pars(3)
y = a*x*log(b + c*x)
end function

end program

这将打印:

1.4207732655565537        1.6556111085593115       0.53462502018670921