Напишите переносимый (поиск корня) класс решателя для разных (составляющих кривые) классов.

Я очень хочу реализовать возможности объектно-ориентированного программирования (ООП) FORTRAN 2003 (F2003). Мой вопрос больше касается дизайна программы. Допустим, у меня есть решатель, например средство поиска корня функции f(x)=0; в самом простом формате на FORTRAN он будет иметь форму

function solver(f,a,b) result(root)
    [some definition of variables]
    [some iterative procedures]
    root = ...
end function

В предыдущих версиях FORTRAN, таких как f95, для обеспечения переносимости код компилировался отдельно, а внешняя функция передавалась решателю. Теперь с точки зрения ООП F2003, скажем, в общем случае у нас есть класс для решателя

type solver_t
   real :: a,b,root
contains
   procedure :: solve => solve_solver_t          ! root=this%solve()
   [some initialization and post-calculation procedures]
end type

и еще один класс для нашей кривой

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

и было бы больше разных классов кривых (типов). Теперь, как я могу связать эти две концепции таким образом, чтобы я скомпилировал свой класс решателя (не зная класса/типа кривой), и я смогу реализовать его без изменений всякий раз, когда я пишу новый другой класс кривой (например, 2nd_order_polynomial_curve , 3rd_order_polynomial_curve, log_curve, exp_curve,...). Я имею в виду, что в конце каким-то образом я получаю корни кривой.


person Sorush    schedule 06.08.2015    source источник
comment
В книге Modern Fortran in Practice (Markus) есть примеры этого.   -  person casey    schedule 06.08.2015
comment
Я собираюсь прочитать книгу, спасибо @Casey.   -  person Sorush    schedule 07.08.2015


Ответы (1)


Вот один из примеров использования ООП F2003 для реализации этой идеи. Я начну с модуля, который будет встроен в разделяемую библиотеку:

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

Это определяет абстрактный класс для представления кривых и класс для решателя. Решатель также можно сделать абстрактным, но для целей демонстрации я решил не делать этого и предоставить один решатель. Вы все еще можете расширить этот тип и предоставить другую процедуру для интерфейса решения. Вы можете скомпилировать это в общую библиотеку, например.

gfortran -shared -fPIC -o solver.so solver.f90

что даст solver.so и solver.mod. Я сделал этот дополнительный шаг, чтобы продемонстрировать переносимость и компиляцию без знания каких-либо кривых.

Теперь мы можем представить себя третьим лицом, которое хочет использовать эту удобную библиотеку для поиска корней произвольных кривых. Сначала мы можем определить наш собственный модуль для расширения кривой и предоставления некоторых функций.

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, мы можем легко использовать эти классы с классом solver_t.

Вот небольшая программа для демонстрации этого

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

Здесь я объявляю несколько кривых, задаю их параметры и затем вызываю решатель, чтобы найти корень. Если вы скомпилируете наш модуль кривой, тестовую программу и ссылку на общую библиотеку, которую мы создали ранее, мы сможем запустить с выводом:

% ./roots                               
 root  =    0.00000000    
 root1 =   -1.00000286    
 root2 =    1.00000286

(качество корней ограничено качеством решателя примера, который я закинул в первый модуль, можете сделать лучше). Это не лучшая демонстрация чистого объектно-ориентированного программирования, так как классsolver_t можно было бы сделать лучше, но я сосредоточился на демонстрации того, как вы будете подходить к нескольким определяемым пользователем кривым, не зная ничего о них при компиляцииsolve_t.

person casey    schedule 07.08.2015