Fortran

Osnove Fortrana

Programski jezik FORmulaTRANslation je bil prvotno razvit 1950 in nato standardiziran 1977. Kasnejše posodobitve vsebujejo Fortran 90.

Glavne prednosti so:

  • Pisanje formul
  • Zelo soroden pisanju matematičnih operaci
  • vgrajena kompleksna števila
  • Upravljanje s polji je naravno. Fortran 90 ima zmogljivo sintakso
  • Zelo optimiziran jezik. Uporaba kazalcev je omejena.
  • Naravni jezik za HPC aplikacije

Slabosti:

  • Slabša povezljivost z drugimi jeziji
  • Izdelava grafičnih vmesnikov (GUI) je težka
  • Uporaba znakovnih nizov
  • Zaradi velike količine znanstvenih kod se ohranja kompatibilnost za nazaj, kar povzroča nekoliko obsežnejži jeik
  • Če pa se uporablja v pravem kontekstu je FORTRAN zmogljiv jezik

Programiranje s Fortran 90

Fortran 90 je vpelljal naslednje novosti:

  • struktuirano programiranje
  • varno programiranje s kontrolo tipov
  • moduli
    • prinašajo zmožnosti vsebovanja kode
    • prinašajo "interface" v podprograme (kontrola tipov argumentov)
    • strukture
  • prenosljivost
    • koncept  podatkovnih objekotv "type"
    • prinaša zmožnosti prenosljivega obnašanja, še posebej za artimetiko plavajoče vejice.

Prvi program

program hello
! Display a message to standard output
implicit none
write (unit = *, fmt= *) ”Hello World!”
end program hello
  • Osnovna sintaksa je v vrsticah.
  • Neobčutljivost na velikosti črk
  • Komentarji se pričnejo s klicajem !

Glavni progam in sintaksa

Formalno se sestoji glavni program iz

 [program program-name]
[specification-statements]
[executable-statements]
end [program [program-name]]

Kjer pomeni besedilo v oglatih oklepajih [] opcijsko uporabo. Več stavkov v eni vrstici ločimi s podpičjem;. Če pa želimo daljše stavke zlimiti v vstice dodamo na kondu znak &, ki pomeni nadaljevanje vrstice v naslednji

 write (unit = *, fmt= *) &
”Nekoliko daljši pozdrav v FORTRANU.!”

Spremenljivke in vgrajeni tipi

implicit none  ! Prisili strogo preverjanje tipov
integer :: i! 10
real :: a! 3.14159
character :: letter! a
character (len= 12) :: month ! Januar
logical :: switch! .false.
complex :: z0, z1! (1.0, 1.0)

Spremeljivke

  • Morajo biti deklarirano pred izvršnimi stavki
  • Morajo imeti sprejemljiva imena iz znakov od katerih mora biti prvi črka.
  • Posebni znaki razen podčrtaja niso dovoljeni

S stavkom implicit none prisilimo strogo preverjanje tipov in ne veljajo privzeti tipi, ki so rezervirani za cela števila. Pravilo je

  • Če je prva črka i,j,k,l,m,n je spremenljivka tipa integer
  • V ostalih primerih je tip real

Implicitna raba imen spremenljivk e nevarna in se jo moramo izogibati.

Polja

Polja vsebujejo skupine vrednosti. Elementi polj so dostopna z indeksi. V Fortranu so polja shranjena v spominu kot kolone in ne kot vrstice, kot v jeziku C - column major.

Polja podajamo z atributom dimension

 integer, dimension(4) :: n4

Kar rezervira 4 elemente: n4(1), n4(2), n4(3), n4(4), Prvi element je privzeto z indeksom 1. Lahko pa določimo tudi spodnje in zgornje meje:

 real, dimension(-5:4) :: r

Večdimenzijska polja določimo z dodatkom dimenzij. Npr.

 complex, dimension(1:10, 1:20) :: z

Dovoljeno je do 7 dimenzij:

real, dimension(2, 3, 4, 5, 6, 1) :: veliko

Znakovna polja

 

So deklarirana podobno kot numerični tipi. Znakovne  spremenljivke lahko

  • Vsebijejo en sam znak
  • Kažejo na niz znakov

Naslednja so pravilne deklaracije

 character :: sex
character (len= 20) :: name
character (len= 10), dimension(10,10) :: carray

Za prirejanje konstantnih nizov lahko uporabimo enojne ali dvojne narekovaje.

Parametri

Se imenjejo konstante in so lahko vseh tipov. Njihova vrednost se ne more spremeniti.

 integer, parameter :: n = 100
real, dimension(2*n) :: r
real, parameter :: pi = 3.14

Prirejanje spremenljivk

Spremenljivke se kahko inicializira na poljubnem mestu v programu,

 program initial_declare
implicit none
integer:: i = 10
real :: pi = 3.14159
character (len= 12) :: month = ”Januar”
end program initial_declare

ali v glavnem programu

 complex :: ci
logical:: iostatus
ci= (0.0, 1.0)
iostatus= .true.

Koncept tipa

S tipom lahko izpeljemo lastne tipe iz obstoječih. Primer take rabe je primeren npr za spremenljivke tipa real, ki so običajno 4 byte, vendar to ni nikjer določeno. Z mehanizmom tipa pa lahko določimo izbrani tip. Npr. s specificiranjem danga decimalne vejice, kar naredimo s parametrom kind.

 integer, parameter :: sp= kind(1.0)
real (kind = sp), dimension(10) :: variable

Razširimo ločljivost na double

 integer, parameter :: dp= kind(1.0d0)
real (kind = dp) :: variable

Fortran 90 omogoča tudi izpeljane tipe s katerimi združujemo osnovne tipe. P

 type person
character (len=10):: name
real:: age
integer:: id
end type person


type(person) :: you, me
you = person(”Janez Kranjec”, 21, 1234)

Elemente izpeljanih tipov lahko naslavljamo z znakom %. Npr za popreje deklarirani tip person

 you%name! vsebuje ime
you%age! starost
you%id! oznaka

Numerični izrazi

  • ** - eksponent
  • * - množenje
  • / - deljenje
  • + - seštevanje
  • - - odštevanje

Pri izračunavanju izrazov z različnimi tipo spremenljivk velja pravilo, da je izračunan izraz vedno tipa, ki je višjega reda. Možno pa je tudi ročno prirejanje tipov z vgrajenimi funkcijami int(), real(), cmplx().

Vgrajene funkcije

Fortran ima zgodovinsko gledano, množico funkcij, ki si vgrajene v sam jezik. Teh je preko 100 in operirajo s polji, biti, znaki,..., kot tudi funkcije za

  • Pretvorbo: int() real() cmplx()abs() nint() aint() aimag()ceiling() floor()
  • Matematiko: sqrt(x)exp(x) log(x) log10(x)sin(x) cos(x) tan(x)asin(x) acos(x) atan(x) sinh(x)cosh(x) tanh(x)
  • Druge: min(x1, x2, ...) max(x1, x2, ...)mod(a, p) conjg() tiny(x) huge(x)

Relacijski operatorji

V 90/95 so poleg že zastarelih opisnih relacijski operatorjev uvedeni še "normalni" izrazi za operatorje, kot jih poznamo v ostalih "modernih" jezikij. Ti so:

 < ! less than
<= ! less than or equal
> ! greater than
>= ! greater than or equal
== ! equal
/= ! not equal

in logični operatorji

 .true.
.false.
.not. ! unary not
.and. ! logical and
.or. ! logical or
.eqv. ! equivalent
.neqv. ! not equivalent

Nadzor poteka programa

Pogojni stavek

 if (logical-expression) then
   block
[else if (logical-expression) then
   block]...
[else
   block]
end if

Primer:

 if (t < 0) then
  ! Mraz je
  led = .true.
else if (t > 100) then
  ! Toplo je
   para = .true.
else
   voda = .true.
   mokro = .true.
end if

Poleg tega so možne še druge oblike pogojnih stavkov, kot je npr elseif ali izbirni if z case. Na primer:

 seasons: select case (mesec)! mesec je tipa integer
   case (1:2,12)! Zima, Dec, Jan, Feb
     write(*,*)”Zima”
   case(3:5)! Pomlad, Mar, Apr, May
     write(*,*)”Pomlad je
   case(6:8)! Poletje, Jun, Jul, Aug
     write(*,*)”Poletje”
   case(9:11) ! Jesen, Sep, Oct, Nov
     write(*,*)”Jesen”
   case default! če je mesec izven 1-12
     write(*,*)"Vnesti je potrebno 1-12"
   end select seasons

Ponavljanje v mejah

 do n = 1, 100
  ! računaj
end do

Formalno je oblika stavka zanke:

 do [variable= expr1, expr2[, expr3]]
   blok
end do

kjer je expr3 izraz za korak. Možen je tudi negativni korak:

 do n = 10, 1, -1
  ! računaj
end do

Enostaven vhod/ihod

Uporabljamo ukaz print v osnovni obliki.

 print*,”Hello World”

Lahko pa uporabljamo tudi write(), ki je bolj uporaben.

Uporabniške funkcije

 result = dist(a, b, c)
...
real function dist(x, y, z)
  real, intent(in) :: x, y, z ! zaščiteni argumenti
  dist = sqrt(x*x + y*y + z*z)! Priredba rezultata
end function dist

  • Funkcije nimajo stavka return
  • So lahko rekurzivni
  • Vrednosti a, b, c se posredujejo z referenco
  • Običajno ne vsebujejo I/O
  • Vrednosti vhodnih argumentov se ne sme spreminjati sicer lahko imamo stranske učinke
  • Uporabimo intent(in), da zagotovimo zaščito argumentov

Podprogrami (subroutine)

call sort(nmax, a, ipass)
...
subroutine sort(nmax, a, ipass)
  integer, intent(in):: nmax
  real, dimension(nmax), intent(inout) :: a
  integer, intent(out) :: ipass
  ! ... tukaj računamo ...
  return
end subroutine sort
 

Podprogrami nikoli ne vračajo vrednosti. Če želimo izhod iz podprograma prej uporabimo ukaz return. Argumenti so lahko:

  • intent(in)
  • intent(inout)
  • intent(out)

Neupoštevanje predpisanega obnašanja se javi pri prevajanju, kar je dobra programerska praksa.

Moduli

Modul je skupek ali zbirka spremenljivk in procedur z naslednjo zgradbo:

 module sort
implicit none
  ! specifikacija spremenljivk
  ...
contains
  ! specifikacija procedur
  subroutine sort_sub1()
  ...
  end subroutine sort_sub1
  ...
end modulesort

Z moduli dosežemo modularno programiranje. Module v drugih delih kode uporabimo s stavkom use:

 program main
  use sort
  implicit none
  ...
  call sort_sub1()
end program main

Primer uporabe modula:

 program scope_main
  use scope_mod
  implicit none

  real :: a = 10.0, b=20.0
  write(*,*)”Pred klicanjem  sub1 a = ”,a,”b = ”,b
  call sub1(a)
  write(*,*)”Po klicanju sub1 a = ”,a,”b = ”,b

end program scope_main

 

 module scope_mod
  implicit none ! Velja za vse kar sledi
  contains
  subroutine sub1(x)
    real, intent(in) :: x
    real :: b = 5.0
    b = b + 1
    write(*,*)”Znotraj sub1 x = ”,x,”b = ”,b
    return
  end subroutine sub1
end module scope_mod

Prevajanje kode z moduli

V primeru imamo celoten porogram iz dveh delov. Glavni (main.f90), ki uporablja sortiranje (sort.f90) sta ločeni datoteki.

 f95 sort.f90 main.f90 –o progsort

Ker main uporablja sort, je pri večini prevajalnikov potrebno napisati sort.f90 pred main.f90

Vidnost spremenljivk v modulih

 module another_module
  implicit none
  private ! nastavi privzeto obravnavo
  public :: nlimit! javno dostopna spramenljivka modula
  integer, parameter :: nlimit= 20! public
  integer :: nlocal= 0! privatna
  real, public :: tmp! javna spremenljivka
contains
...
end module another_module

Privzeta vidnost spremenljivk je public.

 

Zunanji podprogrami

V naslednjem primeru

 program main
...
call sub1()
end program main
subroutine sub1()
...
end subroutine sub1

sta ta dva programa povsem ločeni prevajalni enoti, ki si ne delita vidnosti. Vsa skupna informacija se prenaša z argumenti, katere se na da kontrolirati na pravilnost tipa. Tak način modularnega programiranja je bil nekoč edino možen in zazo se imanuje tudi zunanji podprogram. Primer takega programa je:

 program scope_external
  implicit none
  real :: a = 10.0, b=20.0
  write(*,*)”Pred klicanjem sub1 a = ”,a,”b = ”,b
  call sub1(a)
  write(*,*)”Po klicanju sub1 a = ”,a,”b = ”,b
end program scope_external

subroutine sub1(x)
  implicit none
  real, intent(in) :: x
  real :: b= 5.0
  b = b + 1
  write(*, *)”Znotraj sub1 x = ”,x,”b = ”,b
return
end subroutine sub1

V primeru je razvidno, da prevajalnik ne testira tipov. Če želimo to doseči (kar je dobra praksa), to dosežemo z ekplicitno navedbo funkcije ali podprograma.

Interni podprogrami

V glavnem programu lahko pišemo tudi interne podprograme, ki jih označimo z "internal" Primer:

 program main
  ...
  call sub1()
  contains
   subroutine sub1()
   ...
   end subroutine sub1
end program main

Rekurzivni podprogrami

Zarani privzetega prenašanja argumentov z referenco morajo rekurzivni podprogrami

  • vsebovati označbo recursive.
  • Rezultati morajo biti označeni z result

Primer rekurzivne funkcije:

 res = factorial(m)
...
recursive function factorial(n) result(nfact)
  implicit none
  integer, intent(in) :: n
  integer :: nfact
  if (n > 0) then
    nfact= n * factorial(n-1)
  else
    nfact= 1
  end if 
end function factorial

Primer rekurzivnega podprograma:

 call factorial(m,res)
...
recursive subroutine factorial(n,result)
integer, intent(in) :: n
integer, intent(inout) :: result
if (n > 0) then
call factorial(n-1,result)
result = result * n! Need to update result
else
result = 1
end if
return
end subroutine factorial

Povzetek

Fortran v vsebuje različne programske enote:

  • Glavni program
  • Procedure: funkcije in podprogrami
  • Moduli

Ob pravilni rabi lahko dobimo dobro struktuirano kodo, ki je zanesljiva, razumljiva in se jo da vzdrževati.

Polja v Fortranu

Polja

Polja deklratiramo i atributom dimension. Pomnite, da so polja shranjena v spominu kot kolone, kar pri izpisu izgleda kot transponiran izpis. Tako bo pri definiciji

 real, dimension(3,2) :: a
write(*,*) a

izpisalo polja v naslednjem vrstnem redu: 1,3, 5, 2, 4, 6

Če želimo pravilen matematični izpis po vrsticah je potrebno izpisati v zanki

 do i = 1,3
  write(*,*) (a(i,j), j = 1,2) ) ! vgrajena zanka
end do

Podnaslavljanje

Za primer polja

 real, dimension(1:10) :: a

lahko dele polja naslovimo z

  • a(7:) ! a(7), a(8), a(9), a(10)
  • a(2:10:2) ! a(2), a(4), a(6), ...
  • a(:) ! whole array
  • a(10:2:-2) ! a(10), a(8), a(6),...

Tako lahko tudi cele range uporabljamo v artimetiki

  • a(1:10) = 0.0 ! priredi 0 vsem elementom
  • a(:) = a(:) + 1.0 ! dodaj 1 vsakemu elementu
  • a = 0.0 ! priredi polju ali skalarju?

Lahko tudi inicializiramo ob začetku programa s konstruktorjem (/.../)

  • real, dimension(2) :: s = (/ -1.0, 1.0 /)
  • a(1:5) = (/ 1.0, -1.0, 1.0, -1.0, 1.0 /)

ali z vsebovano zanko

  • real, dimension(10000) :: a = (/ (i, i = 1, 10000) /)

Skladna polja

Za medsebojno delo morajo biti polja skladna (konformna)

  • imeti morajo enako obliko
  • oz. isti rank (število dimenzij)
  • enak obseg v vsaki smeri

Pogojni operatorji

Za polja obstajajo tudi pogojni operatorji, ji se izvajajo na polju kot celota

  • Pogojni if
where (logical array-expression)
    array assignments
[else where] (logical array-expression)
   array assignments
end where

Forall

Namesto

 do i = 1, n
  a(i, i) = x(i)
end do

lahko uporabimo tudi

 forall(i = 1:n) a(i, i) = x(i)

ali celo bolj zahtevne izvedbe

 forall(i = 1:n)
  where (a(i, :) == 0) a(i, :) = i
  b(i, :) = i / a(i, :)
end forall

Vgrajene funkcije

lahko apliciramo tudi na polja kot celoto s tem da npr. a = sqrt(a) izračuna korene za vse elemente polja. Za polja imamo tudi vgrajene funkcije za poizvedovanje (size, lbound, ubound,shape) in pretvorbo:

  • Ustvarjanje polj: spread, pack, reshape, …
  • Množenje vektorjev in matrik: dot_product, matmul
  • Redukcijske funkcije: sum, product, any, maxval, minval…
  • Geometrijske funkcije: minloc, maxloc
  • Manipulacijske: transpose, cshift, …

Predpostavljena in avtomatska polja

Fortran omogoča prilagodljive velikosti polj

  • velikost je določena ob vstopu v podprogram
  • Se lahko uporablja le v podprogramu
  • Omogoča prilagodljivo ponovno izrabo v podprogramu
  • dimenzije teh polj so znane v času prevajanja
  • V fortranu 90 je znana kot privzeta-obllika ali avomatsko polje
  • Deklariramo z
 real, dimension(:) :: a! 1D polje
real, dimension(:,:) :: b! 2D polje
real, dimension(:,:,:) :: c ! 3D polje

Lokalne spremenljivke, ki so spremenljive velikosti so avtomatske. Velikost je lahko predpostavljana

 subroutine swap(a, b)
  real, dimension(:), intent(inout) :: a, b

Dinamična polja

  • Velikost se alocira pri izvajanju
  • zelo prilagodljiva, lahko pa upočasnijo računanje
  • Manjkajo meje za kontrolo pri prevajanj
  • V Fortranu so to alocirana (zasedena)  polja

Deklarira se jih z:

 real, dimension(:), allocatable:: names ! 1D polje
real, dimension(:,:), allocatable:: grid ! 2D polje

Spomin se alocira (priredi) in dealocira (sprosti) z ukazom

 allocate(work(n,2*n,3*n))
deallocate(work)

Primer dimaničih polj v modulu:

 module work_array
  implicit none
  integer :: n
  real, dimension(:,:,:), allocatable:: work
end module work_array

program main
  use work_array
  implicit none
  read(*,*) n
  allocate(work(n,2*n,3*n))
  . . .
  deallocate(work)
end program main

Vhod/Izhod v Fortranu

Formatiran Vhod/Izhod (Input/Output)

Velokokrat želimo poravnan izpis, kar lahko naredimo s formatnim stavkom

 write(*, fmt=”(i2,2x,f5.1,1x,a4)”) i,temp,name

Podobni lahko pišemo z ločenim formatnim stavkom, ki ga oštevilčimo

 write(*,10) i, temp, name
10 format (i2,2x,f5.1,1x,a4)

Neformatiran I/O

Podaja interno presdtavitev. Formatiran izpis ima več slabosti:

  • Dodatno delo s formatiranjem
  • zaokroževanje
  • Na splošno pa formatiran izpis vzame več prostora

Neformatiran I/O omogoča ohranitev interne strukture.

Primer pisanja v datoteko:

 open(unit=10, file="unformatted.dat",& action="write", form="unformatted")
. . .
do i = 1, imax
  write(10, err=100) a(i)
  end do
100 continue
close(10)

Številke enot

Datoteke se naslavljajo s številkami odprtih enot. Številke enot so pozitivne in se lahko uporabljajo kot cela števila v programu.Običajo enota 5 predstavlja vhod, enota 6 pa izhod.

Primeri v Fortranu 90

Vaja 1 : Pozdrav

Preverimo delovanje programa z najkrajšim programom.

 program hello
  !Izpiše klasični pozdrav na terminal
  implicit none
  write (unit = *, fmt= *) "Hello World!"
end program hello

Program prevedemo in poženemo z ukazom

$ f95 hello.f90 -o hello
$ ./hello
 Hello World

Vaja 2 : Stopinje v Radiane

Poleg vgrajenih trigonometrijskimi funkcijami (sin(), cos(), tan, ...) si želimo še razne pomožne funkcije, kot je pretvorba stopinj.

Napišimo modul imenovan trig, ki vsebuje te funkcije za modularno rabo v drugih programih. Osnutek programa trig.f90 je naslednji

 module trig
  implicit none
contains
  real function degtorad
    ...
  end function degtorad

  real function radtodeg
   ...
  end function radtodeg
end module trig

Napišimo še osnutek programa anglecov.f90, ki prebere 3 kote (v stopinjah) iz zaslona

 program anglecov
  use trig
  ! preberi tri kote
  ! Pretvori kote iz stoping v radiane
  ! izpiši kote
  ! pretvori kote iz radianov v stopinje
  ! izpiši kote
end program anglecov

Vaja 3: Kvadratni koreni

Napišite program, ki izračuna oba korena kvadratne funkcije

ax2+bx+c=0

za a=1, b=0, c=-4 s tem, da je potrebno vključiti pogojni stavek (if), pri katerem se kontrolira pozitivnost diskrimimante b2-4ac in javiti napako v primeru napake vhodnih podatkov. Lahko pa ta primer razširite še na kompleksna števila, ki mora za a=4, b=1, c=1 izpisati x=-0.125+0.48i in x=-0.125-0.48i.

Fortran - Vaja 2

module trig implicit none real, parameter :: pi = 3.1415992 contains real function degtorad(deg) real, intent(in) :: deg degtorad = deg*pi/180 end function degtorad real function radtodeg(rad) real, intent(in) :: rad radtodeg = rad*180/pi end function radtodeg end module trig program anglecov use trig implicit none real a,b,c, d,e,f ! preberi tri kote read *, a, b, c ! Pretvori kote iz stopinj v radiane d=degtorad(a); e=degtorad(b); f=degtorad(c) ! izpiši kote print *, d, e, f ! pretvori kote iz radianov v stopinje print *, radtodeg(d), radtodeg(e), radtodeg(f) ! izpiši kote end program anglecov