Programové jednotky

Řešení komplikovaného problému lze často zjednodužšit jeho rozdělením na několik podproblémů, které lze vyřešit snáze. V programovacích jazycích tomu odpovídá dělení celého programu na podprogramy které jsou víceméně nezávislé na sobě navzájem. Složením několika podprogramů z určité oblasti pak můžeme konstruovat celé programové knihovny.

Podprogramy a funkce

Ve fortranu existují tři typy programových jednotek:

Hlavní program

Je nejvyšší v hierarchii, začíná slovem program, v programu může být jen jedinný, všechny proměnné které program obsahuje jsou lokální, tj. nejsou známy žádné jiné programové jednotce

Podprogram

Podprogram je volán hlavním programem nebo jinou funkcí či podprogramem, případně sebou samým. Proměné jsou lokální. Podprogram nevrací žádnou návratovou hodnotu a se svým okolím může komunikovat výhradne prostřednictvím parametrů.

Funkce

Funkce je volána hlavním programem, jinou funkci nebo prodprogramem nebo sebou sama. Proměnné jsou lokální. Funkce musí vracet návratovou hodnotu ale může měnit i svoje parametry.

Obecně je vhodné psát samostatné programové jednotky do samostatných souborů na disku a případě i samostatně překládat. Proto je nevhodné používat něco jako globální proměnné, které navíc komplikují práci ve složitých programech.

Struktura podprogramu nebo funkce je podobná jako u hlavního programu, začíná deklaracemi lokalních proměnných po kterých následuje vlastní kód.

Podprogramy

Podprogram začíná slovem subroutine za nímž následuje jeho název a seznam parametrů. Končí slovem end subroutine. Parametry slouží na učení typu proměnných a na jejich případné přejmenování. Extrémě důležité je přesně dodržovat pořadí a typy parametrů jinak dostaneme zcela nesmyslné výsledky. Podprogram se z jiné programové jednotky volá příkazem call Typický příklad zápisu podprogramu je zde:


subroutine podprogram(a,b,x,i)

  real :: a,b,x
  integer :: i
 
  if( i == 666 ) return

  write(*,*) "Predane parametry:",a,b,x,i
  x = x + 1

end subroutine

Funkce

Funkce je uvozena slovem function před kterým ovšem může stát definice typu. Pokud ne, mělo by být někde v definicích funkce řečeno jakého typu je návratová hodnota funkce. Dále následují parametry podobně jako v prípadě podprogramu. Někde ve těle funkce by měla být do funce přiřazena návratová hodnota. Příklad:


function funkce(i,x) ! result(y)

  real :: x,funkce
  integer :: i
! real :: y
 
  if( i == 666 ) then
    funkce = 0.0
!   y = x + 1.0
    return
  endif

  write(*,*) "Predane parametry:",x,i
  
  funkce = x + 1.0
! y = x + 1.0

end function

V příkladu je v komentářích uvedena alternativní metoda návratové hodnoty. Je zapoznámkována nová lokální proměnná jejích přiřazená hodnota se předa volajícímu programu uvedením slova result. V takovém případě ovšem nesmí být název funkce natypován.

Použití

Jako příklad užití spolupráce funkcí a programu zde máme


Program Main

  real :: x,funkce
  integer :: i

! volani podprogramu
  call podprogram(2.0,3.0,x,0)
  write(*,*) "x po navratu z podprogramu = ",x

! volani funkce
  write(*,*) "vysledek funkce = ",funkce(1,x)

end 

Rekurse

Rekurse, tj, volání podprogramu samo sebou se musí překladači explicitně sdělit uvedením slova recursive před názvem (recursive subroutine, recursive function). K použití rekurse se (možná) dostaneme později.

Použití

Jak již bylo řečeno programové jednotky jsou samostatné, takže se dají například samostaně překládat f90 -c podprogram.f90 s parametrem -c značícím pouze překlad. Tento příkaz vytvoří soubor podprogram.o. S ten pak můžeme dak se spustitelnému souboru a vytvořit program např. f90 podprogram.o program.f90nebo knihovnu (viz dále).

Moduly

Protože je předávání parametrů mezi programovými jednotkami dost obtížná tak i poměrně omezená věc, byly zavedny tzv. moduly. To jsou programové jednotky vyššího řádu sloužící k větší intergraci samostatných jednotek. Mají následující strukturu:


Module modul

! definice typu, promennych

contains

! funkce, podprogramy, bud uvedene jejich kody nebo inkludnute

end module

V první části, která je veřejná, můžeme definovat vlastní typy, nastavit konstatny, proměnné pro celý program a pod. Vše co nemá atribut private je veřejné.

V druhé části jsou pak uvedeny zdrojové kódy procedur a programů. Příkazem include 'funkce.f90' můžeme vložit zdrojový text z jiného souboru.

Tato konstrukce slouží jednak ke sdílení společných veřejných proměnných různým jednotkam a jednak ke kontrole předávaných parametrů procedur již překladačem. Sdílení proměnných se budeme věnovat příšte.

Použití modulů je jednoduché, hned na začátku jednotky která jej bude používat se dá příkaz use 'nejakymodul' a program se přeloží následujícím způsobem: f90 modul.f90 program.f90

Hledání kořenů rovnic

Teď se zaměříme na zcela funkční a elegantní přepsání procedur na hledání kořenů rovnic, kterým jsme se už věnovali. Předním jsme totiž museli hledat kořen pouze určité funkce a ne takové, kterou by jsme si definovali sami. Použití funcí ovšem tento problém zcela elegantně řeší. Uvedeme dvě řešení, obě mají své výhody a nevýhody (jaké?).

Newton v samostatné funkci

Na ukázku si vezmeme newtonovu metodu (je kratší). Napíšeme funkci do samostaného souboru. V tomto případě musíme vytvořit podprogram který ma jako parametry počáteční odhad, funkci a konvergenční parametry. Zde je.


subroutine newton(f,df,x,fx,maxn,tol)

! poprogram resi rovnici f(x) = 0 newtonovou metodou,
!
! na vstupu ma funkci f(x), funci df(x) pocitajici jeji derivace,
! pocatecni odhad, povoleny pocet vycisleni funce a prenost
!
! na vystupu je v promenne x ulozen hledany koren


integer :: i,maxn
real :: x,f1,tol,x0

! vnejsi funkce musi byt oznaceny specifikatorem external aby prekladac
! poznal, ze jde o funkci
real, external :: f,df

x0 = x + 2.0*tol

do i = 1, maxn

   ! vypocet funkce
   fx  = f(x)

   ! vypocet derivace
   f1 = df(x)

   ! vypocet noveho odhadu korene
   if( f1 /= 0.0 ) then
      x = x - f/f1
   else
      stop 'Nulova derivace'
   endif

   ! vypis aktualniho korene
   write(*,*) "iterace = ",i," funkce = ",fx," derivace = ", f1, &
              " odhad korene = ",x, 

   ! testy na ukonceni   
   if( abs(x-x0) < tol ) exit
   
   ! uchovani korene
   x0 = x

enddo     

end subroutine

Newton v modulu

Zde je příklad v modulu. Předávané funkce jsou zde navíc přesněji specifikovány, jak procedura předpokládá, že budou volané.


Module Rovnice

contains

  subroutine newton(f,df,x,fx,maxn,tol)

  ! poprogram resi rovnici f(x) = 0 newtonovou metodou,
  !
  ! na vstupu ma funkci f(x), funci df(x) pocitajici jeji derivace,
  ! pocatecni odhad, povoleny pocet vycisleni funce a prenost
  !
  ! na vystupu je v promenne x ulozen hledany koren

    interface
       function f(x)
         real :: f,x
       end function f

       function df(x)
         real :: df,x
       end function df
    end interface

    integer :: i,maxn
    real :: x,f1,tol,x0
  
    x0 = x + 2.0*tol

    do i = 1, maxn

      ! vypocet funkce
      fx  = f(x)

      ! vypocet derivace
      f1 = df(x)

      ! vypocet noveho odhadu korene
      if( f1 /= 0.0 ) then
        x = x - f/f1
      else
        stop 'Nulova derivace'
      endif

      ! vypis aktualniho korene
      write(*,*) "iterace = ",i," funkce = ",fx," derivace = ", f1, &
              " odhad korene = ",x, 

      ! testy na ukonceni   
      if( abs(x-x0) < tol ) exit
   
      ! uchovani korene
      x0 = x

    enddo     

  end subroutine
end module

Tento modul pak z hlavního programu použijeme následovně.


program Newton
! program resi rovnici cos(x) = 0 newtonovou metodou

use Rovnice

integer :: i,maxn
real :: x,fx,f1,tol

! zadani pocatecniho odhadu minima, napr. z pulek
x = 1.5

! maximalni pocet opakovani
maxn = 100

! hledana presnost vypoctu
tol = 1e-5

call newton(f,df,x,fx,maxn,tol)

! vypis vysledku
write(*,*) "pocet iteraci = ",i
write(*,*) "hledany koren = ",x
write(*,*) "hodnota funkce = ",fx

end

function f(x)
  real :: f,x
  
  f = cos(x)
end function

function df(x)
  real :: df,x
  
  df = -sin(x)
end function