2.10. H¨ arledda typer i Fortran 90
En sammanst¨allning av v¨arden, som inte n¨odv¨andigtvis ¨ar av samma typ, brukar kallas f¨or en struktur.
Objekten som ing˚ar i en s˚adan struktur brukar kallas komponenter. Strukturkomponenter ges s¨arskilda namn i Fortran, i motsats till vektorkomponenter, som anges med nummer.
Antag att vi vill spara p˚a datorn adressuppgifter f¨or personer som ing˚ar i en adressbok. Vi kan d˚a behandla uppgifterna f¨or en person som en struktur med t.ex. fyra komponenter: namn, adress, telefon, kommentar.
Strukturen kan vi kalla person. Om vi vill, kan vi g˚a ett steg vidare och uppfatta t.ex. adress som en struktur med komponenterna gatnummer, gatnamn, stad, postnummer.
Adressen f¨or en person Kalle kan man d˚a h¨anvisa till med uttrycket Kalle%adress, som i sin tur ¨ar en struktur. Staden d¨ar Kalle bor skulle man t.ex. kunna h¨anvisa till med Kalle%adress%stad. F¨or att deklarera Kalle som en struktur m˚aste man f¨orst definera en ny typ, t.ex. person, och sedan deklarera Kalle som en variabel av denna typ.
Detta ¨ar m¨ojligt i Fortran 90, d¨ar man kan deklarera egna typer f¨orutom de n¨amnda inbyggda typerna.
S˚adana datatyper h¨arleds fr˚an existerande inbyggda eller tidigare definierade datatyper och kallas d¨arf¨or ocks˚a f¨or h¨arledda typer. En h¨arledd datatyp deklareras genom en f¨oljd av satser av formen (ett dubbelkolon kan ins¨attas efter TYPE):
TYPE nytyp
(deklarationer) ...
END TYPE nytyp
Som ett exempel skall vi t¨anka oss att vi vill s¨atta upp en databas f¨or en f¨orening som inneh˚aller medlems- adresser. Det ¨ar d˚a praktiskt att definiera en ny datatyp, som t.ex. kallas medlem:
TYPE medlem
CHARACTER(LEN=20) :: titel,f_namn,t_namn CHARACTER(LEN=20) :: adress, ort*10
INTEGER :: postnr END TYPE medlem
Variabler av en h¨arledd datatyp kan deklareras p˚a samma s¨att som tidigare behandlade variabler, med undantag av att typnamnet skrivs inom parentes, och f¨oreg˚as av ordet TYPE:
TYPE(medlem) :: kalle, lisa
Ett v¨arde av en h¨arledd typ skrivs som en lista av konstanter, som svarar mot de variabeltyper, som ing˚ar i den h¨arledda typens definition. Listan omges av en parentes, och f¨oreg˚as av typnamnet:
kalle = medlem("stud","Karl","Fridolfsson","Stugv. 17 A 21","K:stad",1234) lisa = medlem("stud","Elisabet", "Fridolin","˚Asv. 21 A 17","K:stad",1234)
F¨or att h¨anvisa till en komponent av en h¨arledd datatyp, skriver man datatypens namn, efterf¨oljt av ett procenttecken och komponentens namn, t.ex. lisa%t_namn = "Fridolin". I definitionen f¨or en ny datatyp kan givetvis ocks˚a ing˚a en tidigare deklarerad h¨arledd typ.
Som ett exempel p˚a hur man i praktiken kan anv¨anda h¨arledda typer i Fortran skall vi skriva ett program som l¨aser in koordinaterna f¨or tv˚a punkter i planet och d¨arp˚a ber¨aknar ekvationen f¨or en r¨at linje som passerar dem.
Vi kan g¨ora detta genom att definiera tv˚a datatyper, av vilka den ena anger en punkt med hj¨alp av tv˚a koordinater (x, y) och den andra beskriver linjens ekvation ax + by + c = 0 med hj¨alp av koefficienterna a, b, c.
Om de tv˚a punkterna inte sammanfaller och har koordinaterna (x1, y1) och (x2, y2) s˚a ¨ar linjens vinkelko- efficient k = (y2− y1)/(x2− x1), och ekvationen f¨or en r¨at linje som g˚ar genom punkterna kan skrivas y = k(x − x1) + y1 (enpunktsformeln). Genom j¨amf¨orelse med normalformen finner man d˚a koefficien- terna a = y2 − y1, b = x1 − x2 och c = x2y1 − x1y2.
Ett program som ber¨aknar linjens ekvation skulle d˚a kunna skrivas p˚a f¨oljande s¨att:
PROGRAM ekvation IMPLICIT NONE
! Typdefinitioner:
TYPE punkt ! kartesiska koordinater f¨or en punkt REAL :: x,y
END TYPE punkt
TYPE linje ! koefficienter f¨or linjens ekvation REAL :: a,b,c
END TYPE linje
TYPE(punkt) :: p1, p2 TYPE(linje) :: p1p2
! L¨as datapunkter
PRINT *, "Ange koordinaterna f¨or f¨orsta punkten"
READ *,p1
PRINT *, "Ange koordinaterna f¨or andra punkten"
READ *,p2
! Ber¨akna koefficienterna i ekvationen p1p2%a = p2%y - p1%y
p1p2%b = p1%x - p2%x
p1p2%c = p2%x*p1%y - p1%x*p2%y
! Skriv ut resultat
PRINT *, "Ekvationen f¨or linjen genom de tv˚a punkterna ¨ar"
PRINT *, "ax + by + c = 0"
PRINT *, "d¨ar a = ", p1p2%a PRINT *, " b = ", p1p2%b PRINT *, " c = ", p1p2%c END PROGRAM ekvation
Programmet kan g¨oras betydligt elegantare om man anv¨ander en modul f¨or att definera de nya typerna.
Vi skall se p˚a ett enkelt exempel, d¨ar koordinater definieras i ett tredimensionellt rum med en h¨arledd typ, som initialiseras med en funktion av samma typ:
MODULE tredim IMPLICIT NONE TYPE koord
REAL :: x,y,z END TYPE koord CONTAINS
TYPE(koord) FUNCTION InitKoord(x,y,z) REAL, INTENT(IN), OPTIONAL :: x,y,z
! Nollst¨all koordinaterna
InitKoord = koord(0.0, 0.0, 0.0) IF (PRESENT(x)) InitKoord%x = x IF (PRESENT(y)) InitKoord%y = y IF (PRESENT(z)) InitKoord%z = z END FUNCTION InitKoord
END MODULE tredim
H¨ar har vi anv¨ant den inbyggda funktionen PRESENT f¨or att ta reda p˚a om ett verkligt argument ¨ar associerat med den invariabel, som har attributet OPTIONAL. Om inget argument anges, blir koordinaterna nollst¨allda. Modulen kan testas med f¨oljande program:
PROGRAM test3dim USE tredim
IMPLICIT NONE
TYPE (koord) :: p1, p2, p3 p1 = InitKoord()
p2 = InitKoord(1.0,-1.0) p3 = InitKoord(z=3.0) PRINT *, "P1: ",p1 PRINT *, "P2: ",p2 PRINT *, "P3: ",p3 END PROGRAM test3dim
Vi skall nu till¨ampa en liknande metod p˚a programmet som ber¨aknar den r¨ata linjens ekvation.
F¨orst s¨atter vi in de nya typerna i en modul, och skriver en subrutin f¨or att ber¨akna koefficienterna, som inkapslas i en annan modul:
MODULE linjedata IMPLICIT NONE SAVE
! Typdefinitioner:
TYPE punkt ! kartesiska koordinater f¨or en punkt REAL :: x,y
END TYPE punkt
TYPE linje ! koefficienter f¨or linjens ekvation REAL :: a,b,c
END TYPE linje
END MODULE linjedata MODULE geometri
USE linjedata IMPLICIT NONE CONTAINS
SUBROUTINE ekvation(linje1,punkt1,punkt2) IMPLICIT NONE
TYPE(linje), INTENT(OUT) :: linje1 ! utdata TYPE(punkt), INTENT(IN) :: punkt1, punkt2 ! indata
! Ber¨akna linjens koefficienter linje1%a = punkt2%y - punkt1%y linje1%b = punkt1%x - punkt2%x
linje1%c = punkt2%x*punkt1%y - punkt1%x*punkt2%y END SUBROUTINE ekvation
END MODULE geometri PROGRAM testgeom
USE geometri IMPLICIT NONE
! Variabeldeklarationer:
TYPE(punkt) :: p1, p2 TYPE(linje) :: p1p2
! L¨as datapunkter
PRINT *, "Ange koordinaterna f¨or f¨orsta punkten"
READ *,p1
PRINT *, "Ange koordinaterna f¨or andra punkten"
READ *,p2
! Anropa subrutinen ekvation CALL ekvation(p1p2,p1,p2)
! Skriv ut resultat
PRINT *, "Ekvationen f¨or linjen genom de tv˚a punkterna ¨ar"
PRINT *, "ax + by + c = 0"
PRINT *, "d¨ar a = ", p1p2%a PRINT *, " b = ", p1p2%b PRINT *, " c = ", p1p2%c END PROGRAM testgeom
Observera h¨ar, att man inte beh¨over s¨atta in USE linjedata i subrutinen ekvation eftersom denna sats redan ing˚ar i modulen. I testprogrammet r¨acker det likas˚a att s¨atta USE geometri.
Som ett annat exempel p˚a anv¨andningen av datatyper, skall vi g¨ora ett program som ber¨aknar summan och produkten av tv˚a komplexa tal. Som bekant kan ett komplext tal z uttryckas som z = x + iy, d¨ar i betecknar den imagin¨ara enheten.
Summan av tv˚a komplexa tal z1 = x1 + iy1 och z2 = x2 + iy2 kan skrivas z1 + z2 = (x1 + x2) + i(y1 + y2)
och deras produkt ¨ar
z1z2 = (x1x2 − y1y2) + i(x1y2 + x2y1).
Vi skall b¨orja med att definiera en s¨arskild datatyp f¨or de komplexa talen, l¨aser sedan tv˚a komplexa tal, ber¨aknar d¨arp˚a deras summa och produkt, och skriver till sist ut resultatet:
PROGRAM komplex IMPLICIT NONE
! Deklarationer:
TYPE :: komplext_tal
REAL :: reell_del, imaginar_del END TYPE komplext_tal
TYPE(komplext_tal) :: z1, z2, summa, produkt
! L¨as in talen:
PRINT *, "Skriv tv˚a komplexa tal , uttryckta som tv˚a tal "
PRINT *, "som representerar den reella och imagin¨ara delen:"
READ *, z1, z2
! Ber¨akna summan och produkten:
summa%reell_del = z1%reell_del + z2%reell_del
summa%imaginar_del = z1%imaginar_del + z2%imaginar_del produkt%reell_del = z1%reell_del*z2%reell_del - &
z1%imaginar_del*z2%imaginar_del produkt%imaginar_del = z1%reell_del*z2%imaginar_del + &
z1%imaginar_del*z2%reell_del
! Skriv ut resultat:
PRINT *, "Summan av talen ¨ar ", summa
PRINT *, "och deras produkt ¨ar ", produkt STOP
END PROGRAM komplex
Ocks˚a h¨ar kan vi anv¨anda oss av en modul f¨or att definiera de komplexa talen, samt en annan modul, som definierar tv˚a funktionsrutiner, som ber¨aknar summan, resp. produkten av tv˚a komplexa tal:
MODULE kompl_tal IMPLICIT NONE
TYPE :: komplext_tal
REAL :: reell_del, imaginar_del END TYPE komplext_tal
END MODULE kompl_tal MODULE functions CONTAINS
FUNCTION kompl_add(z1,z2) USE kompl_tal
IMPLICIT NONE
! Deklarationer
TYPE(komplext_tal) :: kompl_add
TYPE(komplext_tal) , INTENT(IN) :: z1, z2
! R¨akna ut resultatet
kompl_add%reell_del = z1%reell_del + z2%reell_del kompl_add%imaginar_del = z1%imaginar_del + &
z2%imaginar_del RETURN
END FUNCTION kompl_add FUNCTION kompl_mult(z1,z2)
USE kompl_tal USE functions
IMPLICIT NONE
! Deklarationer
TYPE(komplext_tal) :: kompl_mult
TYPE(komplext_tal) , INTENT(IN) :: z1, z2
! R¨akna ut resultatet
kompl_mult%reell_del = z1%reell_del*z2%reell_del-&
z1%imaginar_del*z2%imaginar_del kompl_mult%imaginar_del= &
z1%reell_del*z2%imaginar_del+ &
z1%imaginar_del*z2%reell_del RETURN
END FUNCTION kompl_mult END MODULE functions PROGRAM komplex_test
USE kompl_tal USE functions IMPLICIT NONE
! Deklarera funktionerna, och de komplexa talen
TYPE(komplext_tal) , EXTERNAL :: kompl_add, kompl_mult TYPE(komplext_tal) :: a,b
! L¨as in talen:
PRINT *, "Skriv tv˚a komplexa tal , uttryckta som tv˚a tal "
PRINT *, "som representerar den reella och imagin¨ara delen:"
READ *, a,b
! Skriv ut resultat:
PRINT *, "Summan av talen ¨ar ", kompl_add(a,b) PRINT *, "och deras produkt ¨ar ", kompl_mult(a,b) STOP
END PROGRAM komplex_test
Matriser och vektorer kan ofta anv¨andas f¨or att underl¨atta programmeringen. S˚alunda kan vektorer anv¨an- das i kombination med h¨arledda typer, om antalet komponenter blir stort. Ett enkelt exempel ¨ar en h¨arledd typ som definierar en cirkel. Cirkeln kan anges med hj¨alp av medelpunktens koordinater och radien:
TYPE cirkel
REAL, DIMENSION(3) :: medelpunkt REAL :: radie
END TYPE cirkel
Till medelpunktens x–koordinat kan man d˚a h¨anvisa med cirkel%medelpunkt(1).
Ett annat exempel utg¨ors av en f¨orenings adresslista:
PROGRAM adresser IMPLICIT NONE TYPE medlem
CHARACTER(LEN=20) :: fnamn, tnamn CHARACTER(LEN=30) :: adress
CHARACTER(LEN=15) :: stad INTEGER :: postnr
END TYPE medlem
INTEGER, PARAMETER :: max_medl INTEGER :: i
TYPE(medlem), DIMENSION(max_medl) :: fmedl ...
DO i=1,max_medl READ *, fmedl(i)
IF (fmedl(i)%fnamn = "END") EXIT ! END betyder slut p˚a data END DO
...
END PROGRAM adresser
2.11. Avancerade moduler och procedurer i Fortran 90
Vi har redan tidigare anv¨ant oss av procedurer, som definierats i en modul (¨aven kallade modulprocedurer).
Moduler, som inneh˚aller procedurdefinitioner k¨anns igen p˚a satsen CONTAINS, som f¨oreg˚ar procedurerna:
MODULE (namn)
(deklarationer ...) ...
CONTAINS
(modulprocedur 1) (modulprocedur 2) ...
END MODULE (namn)
Anv¨ander man modulprocedurer, s˚a beh¨ovs inte explicita gr¨anssnitt, eftersom procedurer som definieras i en modul har tillg˚ang till alla data och typdeklarationer i modulen, och deras gr¨anssnitt ¨ar sinsemellan explicita.
I fysik och matematik kombinerar man ofta flera likartade variabler med varandra till stora enheter, s˚asom t.ex. komplexa tal, vektorer och matriser. Vi har redan tidigare visat hur man kan definiera en komplex typ i Fortran 90, fast¨an det inte ¨ar n¨odv¨andigt, eftersom som det finns en inbyggd typ COMPLEX. Emellertid kan man framst¨alla ett komplext tal p˚a tv˚a olika s¨att, antingen i kartesisk form z(x, y) = x + iy, eller pol¨ar form z(r, φ) = r(cos φ + i sin φ). B˚ada dessa framst¨allningar kan, som vi ser, h¨arledas ur tv˚a reella tal.
Detta kan leda till problem, ifall man har skrivit modulen, som deklarerar en typ komplex f¨or ett komplext tal p˚a basen av den pol¨ara formen. En okritisk anv¨andare skulle d˚a l¨att kunna g¨ora tillordningen z = komplex(1, pi), i tron att den betyder detsamma som z = 1 + iπ, d˚a den i sj¨alva verket betyder z = −1!. Det ¨ar d¨arf¨or b¨attre om man kan kontrollera anv¨andningen av komponenterna av en h¨arledd typ i programmet, s˚a att de antingen ¨ar fritt tillg¨angliga i hela programmet (PUBLIC), eller endast tillg¨angliga i den modul, d¨ar den h¨arledda typen deklareras (PRIVATE).
Komponenterna av en h¨arledd typ g¨ors privata genom att man ins¨atter ordet PRIVATE framf¨or den f¨orsta deklarationen:
TYPE komplex PRIVATE
REAL :: r, phi END TYPE komplex
I detta exempel kommer komponenterna av komplex att vara fritt tillg¨angliga inom hela modulen, men otillg¨angliga utanf¨or den.
Som ett exempel skall vi studera en modul, som inneh˚aller en deklaration f¨or en h¨arledd typ kallad vektor och en procedur som bildar skal¨arprodukten av tv˚a vektorer.
Till en b¨orjan skall vi deklarera en h¨arledd typ som beskriver en vektor. En vektor uppfattas som en r¨acka reella tal, vilkas antal best¨ammer vektorns dimension. En vektor har allts˚a tv˚a komponenter, den reella talr¨ackan, som vi kallar vektorns element, och dess dimension. En vektortyp kan d¨arf¨or definieras p˚a f¨oljande s¨att:
TYPE vektor PRIVATE
INTEGER :: dim
REAL, DIMENSION(max_dim) :: element END TYPE vektor
Observera anv¨andningen av PRIVATE, som har till f¨oljd att elementen av en vektortyp inte ¨ar tillg¨angliga utanf¨or den modul, d¨ar den ¨ar definierad. Via USE kan man dock komma ˚at en vektortyp i sin helhet.
Konstanten max_dim anger h¨ar vektorns st¨orsta till˚atna dimension. Denna konstant deklareras i modulen.
Om man m˚aste ¨andra dimensionen, beh¨over man endast g¨ora denna f¨or¨andring i modulen.
F¨or att kunna anv¨anda vektorer som deklarerats p˚a detta s¨att, m˚aste man f¨orst skriva en funktionsrutin, som alstrar en vektor fr˚an en talr¨acka. Denna rutin ¨ar i sj¨alva verket r¨att s˚a enkel, eftersom den f¨orst bara kontrollerar att antalet element inte ¨overskrider max_dim, varp˚a den anger vektorns dimension, och dess element. D¨arp˚a skriver man en enkel funktionsrutin som ber¨aknar dimensionen av en s˚adan vektor, och en annan rutin som skriver ut elementen av en vektor som en talr¨acka. Till sist skriver man en rutin som ber¨aknar skal¨arprodukten av tv˚a vektorer, som definierats p˚a ovan angivna s¨att. Hela modulen ser ut p˚a f¨oljande s¨att:
MODULE vektorer IMPLICIT NONE
! Ange vektorernas maximidimension:
INTEGER, PARAMETER :: max_dim = 50
! H¨arled en vektortyp:
TYPE :: vektor PRIVATE
INTEGER :: dim
REAL, DIMENSION(max_dim) :: element END TYPE vektor
CONTAINS
FUNCTION alstra_vektor(a,n) IMPLICIT NONE
! Denna funktionsrutin alstrar en vektor ur n element
! av r¨ackan a
! Deklarera argumenten
TYPE (vektor) :: alstra_vektor INTEGER, INTENT(IN) :: n
REAL, DIMENSION(:), INTENT(IN) :: a
! Kontrollera dimensionen IF (n > max_dim) THEN
! Dimensionen ¨ar f¨or stor
PRINT ’(1X,A,I4,A,I3)’, "Vektorns l¨angd ¨ar ",n, &
" - st¨orsta till˚atna l¨angd ¨ar ",max_dim alstra_vektor%dim = 0
ELSE
! kopiera n element i r¨ackan till en vektor alstra_vektor%dim = n
alstra_vektor%element(1:n) = a(1:n) END IF
RETURN
END FUNCTION alstra_vektor FUNCTION vektor_dim(x) IMPLICIT NONE
! Funktionen ber¨aknar en vektors dimension INTEGER :: vektor_dim
TYPE(vektor), INTENT(IN) :: x vektor_dim = x%dim
RETURN
END FUNCTION vektor_dim FUNCTION vektor_racka(x) IMPLICIT NONE
! Funktionen ger ut vektorelementen som en talr¨acka TYPE(vektor), INTENT(IN) :: x
REAL, DIMENSION(x%dim) :: vektor_racka vektor_racka(1:x%dim) = x%element(1:x%dim) RETURN
END FUNCTION vektor_racka REAL FUNCTION skal_prod(x,y) IMPLICIT NONE
! Funktionen ger ut skal¨arprodukten av tv˚a vektorer TYPE(vektor), INTENT(IN) :: x,y
! Lokala variabler REAL :: prod = 0.0 INTEGER :: i
! Kontrollera, att vektorerna har samma dimension IF (x%dim /= y%dim) THEN
! vektorerna ¨ar olika l˚anga
PRINT ’(A,I4,A,I4/,A)’, " Vektorerna har inte samma l¨angd: ", &
x%dim," och ",y%dim," Resultatet ¨ar noll!"
skal_prod = 0.0 ELSE
! ber¨akna skal¨arprodukten DO i=1,x%dim
prod = prod + x%element(i)*y%element(i) END DO
skal_prod = prod END IF
RETURN
END FUNCTION skal_prod END MODULE vektorer
Vi skall ocks˚a skriva ett program f¨or att testa modulen:
PROGRAM vektor_test USE vektorer IMPLICIT NONE INTEGER :: i
REAL, DIMENSION(5) :: a REAL, DIMENSION(60) :: b REAL :: z
TYPE(vektor) :: x,y
! Konstruera talr¨ackor och konvertera till vektorer a = (/(REAL(i), i=1,5)/)
b = (/(REAL(2*i), i=1,10),(0.0, i=11,60)/) x = alstra_vektor(a,5)
y = alstra_vektor(b,5)
! Skriv ut vektorerna:
PRINT 100, "L¨angden av vektorn x ¨ar ",vektor_dim(x), &
" och dess element ¨ar (", vektor_racka(x),")"
PRINT 100, "L¨angden av vektorn y ¨ar ",vektor_dim(y), &
" och dess element ¨ar (", vektor_racka(y),")"
! R¨akna ut deras skal¨arprodukt:
PRINT ’(A,F6.1)’, " Deras skal¨arprodukt ¨ar ",skal_prod(x,y)
! Testa felutskrifter:
y = alstra_vektor(b,60) y = alstra_vektor(b,8) z = skal_prod(x,y)
100 FORMAT (T2,A,I3/,A,4(F4.1,","),F4.1,A) STOP
END PROGRAM vektor_test
Programmet ger ocks˚a exempel p˚a formaterad utskrift. Utskriften fr˚an programmet ser ut p˚a f¨oljande s¨att:
L¨angden av vektorn x ¨ar 5
och dess element ¨ar ( 1.0, 2.0, 3.0, 4.0, 5.0) L¨angden av vektorn y ¨ar 5
och dess element ¨ar ( 2.0, 4.0, 6.0, 8.0,10.0) Deras skal¨arprodukt ¨ar 110.0
Vektorns l¨angd ¨ar 60 - st¨orsta till˚atna l¨angd ¨ar 50 Vektorerna har inte samma l¨angd: 5 och 8
Resultatet ¨ar noll!