为不同的(曲线制作)classes 编写一个便携式(求根器)求解器 class
Write a portable (root-finder) solver class for different (curve-making) classes
我非常热衷于实现 FORTRAN 2003 (F2003) 的面向对象编程 (OOP) 功能。我的问题更多是关于程序的设计。假设我有一个 求解器,就像函数 f(x)=0 的求根器一样;在 FORTRAN 中最简单的格式中,它将得到
的形状
function solver(f,a,b) result(root)
[some definition of variables]
[some iterative procedures]
root = ...
end function
在 f95 等 FORTRAN 的早期版本中,为了获得可移植性,代码是单独编译的,并将外部函数传递给 求解器。
现在从 F2003 OOP 的角度来看,在一般情况下,我们有一个 求解器
的 class
type solver_t
real :: a,b,root
contains
procedure :: solve => solve_solver_t ! root=this%solve()
[some initialization and post-calculation procedures]
end type
还有另一个 class 用于我们的曲线
type curve_t
[some variable definition]
contains
[some initialization procedures]
procedure :: func => function_curve_t ! y=this%func(x)
procedure :: plot => plot_curve
end type
并且会有更多不同的曲线classes(类型)。现在,我如何以编译求解器 class(不知道曲线 class/type)的方式连接这两个概念,并且每当我编写一个新的不同的曲线 class(如 2nd_order_polynomial_curve、3rd_order_polynomial_curve、log_curve、exp_curve、...)。我的意思是,最后,我以某种方式得到了曲线的根。
下面是一个如何使用 F2003 OOP 实现这一想法的示例。我将从要构建到共享库中的模块开始:
module solver
implicit none
type, abstract :: curve_t
contains
procedure(func_f), pass(this), deferred :: f
end type curve_t
type :: solver_t
class(curve_t), pointer :: curve
contains
procedure, pass :: solve => solve_root_bisect_method
end type solver_t
abstract interface
function func_f(this, x)
import curve_t
class(curve_t) :: this
real, intent(in) :: x
real :: func_f
end function func_f
end interface
contains
function solve_root_bisect_method(this, a_start, b_start) result(root)
implicit none
class(solver_t) :: this
real, intent(in) :: a_start, b_start
real :: root, c, eps, a, b
integer :: i, imax
imax = 100
eps = 1e-5
a = a_start
b = b_start
do i=1, imax
c = (a+b)/2.
if ( (this%curve%f(c) == 0) .or. ((b-a)/2. < eps)) then
root = c
return
end if
if (sign(1.,this%curve%f(c)) == sign(1.,this%curve%f(a))) then
a = c
else
b = c
end if
end do
! solution did not converge, produce error
root = -999
end function solve_root_bisect_method
end module solver
这定义了一个抽象 class 来表示曲线和一个 class 来表示求解器。求解器也可以抽象化,但为了演示的目的,我选择不这样做并提供一个求解器。您仍然可以扩展此类型并为求解接口提供不同的过程。你可以把它编译成一个共享库,例如
gfortran -shared -fPIC -o solver.so solver.f90
这将产生 solver.so
和 solver.mod
。我做了这个额外的步骤来演示可移植性和在不了解任何曲线的情况下进行编译。
现在我们可以伪装成第三方,想使用这个方便的库来寻找任意曲线的根。首先我们可以定义自己的模块来扩展curve并提供一些功能。
module curves
use solver
implicit none
type, extends(curve_t) :: linear_curve
real :: m, b
contains
procedure, pass(this) :: f => f_linear
end type linear_curve
type, extends(curve_t) :: polynomial_curve
real :: a, b, c
contains
procedure, pass(this) :: f => f_polynomial
end type polynomial_curve
contains
real function f_linear(this, x)
use solver
implicit none
class(linear_curve) :: this
real, intent(in) :: x
f_linear = this%m * x + this%b
end function f_linear
real function f_polynomial(this, x)
use solver
implicit none
class(polynomial_curve) :: this
real, intent(in) :: x
f_polynomial = this%a*x*x + this%b*x + this%c
end function f_polynomial
end module curves
这定义了线性曲线和多项式曲线的类型,其中包含它们的参数和用于计算 y
作为给定这些参数的 x
的函数的函数。因为我们派生自 curve_t
并符合 f
的接口,所以我们可以轻松地将这些 class 与 solver_t
class.
一起使用
这里有一个小程序来演示这个
program test
use solver
use curves
implicit none
type(linear_curve), target :: linear
type(polynomial_curve), target :: parabola
type(solver_t) :: root_solver
real :: root
linear%m = 1.
linear%b = 0. ! y=x
parabola%a = 1.
parabola%b = 0.
parabola%c = -1. ! y=x^2-1
root_solver%curve => linear
root = root_solver%solve(-1., 1.)
print *, "root = ", root
root_solver%curve => parabola
root = root_solver%solve(-4., 0.5)
print *, "root1 = ", root
root = root_solver%solve(-0.5, 4.)
print *, "root2 = ", root
end program test
在这里,我声明了一些曲线,设置了它们的参数,然后调用求解器来寻找根。如果将我们的曲线模块、测试程序和 link 编译到我们之前创建的共享库中,我们可以 运行 输出:
% ./roots
root = 0.00000000
root1 = -1.00000286
root2 = 1.00000286
(根的质量受限于我转储到第一个模块中的示例求解器的质量,您可以做得更好)。这不是纯 OO 的最佳演示,因为 solver_t class 可以做得更好,但我专注于演示如何处理多个用户定义的曲线而无需了解它们的任何信息solve_t已编译。
我非常热衷于实现 FORTRAN 2003 (F2003) 的面向对象编程 (OOP) 功能。我的问题更多是关于程序的设计。假设我有一个 求解器,就像函数 f(x)=0 的求根器一样;在 FORTRAN 中最简单的格式中,它将得到
的形状function solver(f,a,b) result(root)
[some definition of variables]
[some iterative procedures]
root = ...
end function
在 f95 等 FORTRAN 的早期版本中,为了获得可移植性,代码是单独编译的,并将外部函数传递给 求解器。 现在从 F2003 OOP 的角度来看,在一般情况下,我们有一个 求解器
的 classtype solver_t
real :: a,b,root
contains
procedure :: solve => solve_solver_t ! root=this%solve()
[some initialization and post-calculation procedures]
end type
还有另一个 class 用于我们的曲线
type curve_t
[some variable definition]
contains
[some initialization procedures]
procedure :: func => function_curve_t ! y=this%func(x)
procedure :: plot => plot_curve
end type
并且会有更多不同的曲线classes(类型)。现在,我如何以编译求解器 class(不知道曲线 class/type)的方式连接这两个概念,并且每当我编写一个新的不同的曲线 class(如 2nd_order_polynomial_curve、3rd_order_polynomial_curve、log_curve、exp_curve、...)。我的意思是,最后,我以某种方式得到了曲线的根。
下面是一个如何使用 F2003 OOP 实现这一想法的示例。我将从要构建到共享库中的模块开始:
module solver
implicit none
type, abstract :: curve_t
contains
procedure(func_f), pass(this), deferred :: f
end type curve_t
type :: solver_t
class(curve_t), pointer :: curve
contains
procedure, pass :: solve => solve_root_bisect_method
end type solver_t
abstract interface
function func_f(this, x)
import curve_t
class(curve_t) :: this
real, intent(in) :: x
real :: func_f
end function func_f
end interface
contains
function solve_root_bisect_method(this, a_start, b_start) result(root)
implicit none
class(solver_t) :: this
real, intent(in) :: a_start, b_start
real :: root, c, eps, a, b
integer :: i, imax
imax = 100
eps = 1e-5
a = a_start
b = b_start
do i=1, imax
c = (a+b)/2.
if ( (this%curve%f(c) == 0) .or. ((b-a)/2. < eps)) then
root = c
return
end if
if (sign(1.,this%curve%f(c)) == sign(1.,this%curve%f(a))) then
a = c
else
b = c
end if
end do
! solution did not converge, produce error
root = -999
end function solve_root_bisect_method
end module solver
这定义了一个抽象 class 来表示曲线和一个 class 来表示求解器。求解器也可以抽象化,但为了演示的目的,我选择不这样做并提供一个求解器。您仍然可以扩展此类型并为求解接口提供不同的过程。你可以把它编译成一个共享库,例如
gfortran -shared -fPIC -o solver.so solver.f90
这将产生 solver.so
和 solver.mod
。我做了这个额外的步骤来演示可移植性和在不了解任何曲线的情况下进行编译。
现在我们可以伪装成第三方,想使用这个方便的库来寻找任意曲线的根。首先我们可以定义自己的模块来扩展curve并提供一些功能。
module curves
use solver
implicit none
type, extends(curve_t) :: linear_curve
real :: m, b
contains
procedure, pass(this) :: f => f_linear
end type linear_curve
type, extends(curve_t) :: polynomial_curve
real :: a, b, c
contains
procedure, pass(this) :: f => f_polynomial
end type polynomial_curve
contains
real function f_linear(this, x)
use solver
implicit none
class(linear_curve) :: this
real, intent(in) :: x
f_linear = this%m * x + this%b
end function f_linear
real function f_polynomial(this, x)
use solver
implicit none
class(polynomial_curve) :: this
real, intent(in) :: x
f_polynomial = this%a*x*x + this%b*x + this%c
end function f_polynomial
end module curves
这定义了线性曲线和多项式曲线的类型,其中包含它们的参数和用于计算 y
作为给定这些参数的 x
的函数的函数。因为我们派生自 curve_t
并符合 f
的接口,所以我们可以轻松地将这些 class 与 solver_t
class.
这里有一个小程序来演示这个
program test
use solver
use curves
implicit none
type(linear_curve), target :: linear
type(polynomial_curve), target :: parabola
type(solver_t) :: root_solver
real :: root
linear%m = 1.
linear%b = 0. ! y=x
parabola%a = 1.
parabola%b = 0.
parabola%c = -1. ! y=x^2-1
root_solver%curve => linear
root = root_solver%solve(-1., 1.)
print *, "root = ", root
root_solver%curve => parabola
root = root_solver%solve(-4., 0.5)
print *, "root1 = ", root
root = root_solver%solve(-0.5, 4.)
print *, "root2 = ", root
end program test
在这里,我声明了一些曲线,设置了它们的参数,然后调用求解器来寻找根。如果将我们的曲线模块、测试程序和 link 编译到我们之前创建的共享库中,我们可以 运行 输出:
% ./roots
root = 0.00000000
root1 = -1.00000286
root2 = 1.00000286
(根的质量受限于我转储到第一个模块中的示例求解器的质量,您可以做得更好)。这不是纯 OO 的最佳演示,因为 solver_t class 可以做得更好,但我专注于演示如何处理多个用户定义的曲线而无需了解它们的任何信息solve_t已编译。