Utrymme för flyttalsvektorn a(i), i = 1, 2, ... , 20, reserveras med satsen
REAL, DIMENSION(20) :: A
och för flyttalsmatrisen b(i,j), i = 1, 2, ... , 10, j = 1, 2, ... , 10, med satsen
REAL, DIMENSION(10,10) :: B
dvs fältet har nu rangen 2.
I stället för ordet REAL kan man använda något av CHARACTER, COMPLEX, DOUBLE PRECISION, INTEGER eller LOGICAL, varvid fältet får angiven typ.
Från och med Fortran 77 behöver fältet ej längre börja i position 1, man anger då exempelvis
INTEGER, DIMENSION(-7:5) :: C
för heltalsvektorn c(-7), c(-6), ..., c(-1), c(0), c(1), ..., c(5) och
motsvarande vid högre
ordning. Om man vill att fältet skall börja i position ett skriver man till exempel
(1:10) eller enklare (10), däremot kan kompileringsfel fås för (:10).
I deklarationen av omfång (dimensionen) kan normalt endast konstanter användas (siffror
eller PARAMETER-storheter). Vi skall senare i detta kapitel diskutera olika former av dynamisk
minnesallokering och i vilka fall som dimensionsgränserna får vara variabler.
Elementen i ett fält lagras i en entydigt bestämd ordning, jämför nedanstående tabell
Rang Deklarerad dimension Index vid Plats
anrop
1 (j1:k1) (s1) 1+(s1-j1)
2 (j1:k1, j2:k2) (s1, s2) 1+(s1-j1) +(s2-j2)*d1
3 (j1:k1, j2:k2, j3:k3) (s1, s2, s3) 1+(s1-j1) +(s2-j2)*d1
+(s3-j3)*d1*d2
Maximal rang är 7, storheten di = ki-ji+1 anger omfånget
(längden) av respektive
dimension. Notera att den "sista" di ej ingår i någon
formel för beräkning
av plats.
Vi tittar nu på ett vektor-fält och ett matris-fält.
REAL, DIMENSION(-1:8) :: A
REAL, DIMENSION(10,10) :: B
A(2) = B(1,2)
Här identifierar A(2) det fjärde elementet i fältet
A, och sättes till
värdet av B(1,2), det elfte elementet i fältet B.
Ovanstående formler innebär att ett fält A(I,J) lagras så att först kommer alla element svarande mot att det andra indexet har lägsta värde, sedan alla element med andra index med näst lägsta värde osv. Lagringen av en matris sker därför kolumn för kolumn, medan matematiker oftast tänker sig matriser lagrade rad för rad.
MINNESREGEL I FORTRAN: Första index varierar snabbast!
Titta på en 2*2 matris och en 4*4 matris
11 12 13 14
11 12 21 22 23 24
A = B =
21 22 31 32 33 34
41 42 43 44
Dessa är lagrade i fältet A med följande ordning,
nämligen 11, 21, 12,
22 och i fältet B med 11, 21, 31, 41, 12, 22, 32, 42, 13, 23,
33, 43, 14, 24, 34, 44. Detta
innebär att den 2*2 matris som "sitter i början av B",
dvs i övre vänstra
hörnet, och som överenstämmer med 2*2 matrisen A,
ej är lagrad som de första
fyra elementen i fältet B. Man måste därför
skilja på den matematiska
ordningen på en matris, och hur motsvarande fält är dimensionerat.
I Fortran 90 bör man notera att inte alla fält är lagrade på detta enkla sätt. En möjlighet finns till exempel att som argument vid funktions- eller subrutinanrop använda en fältsektion bestående av vartannat element i ett visst fält. Då kommer "avståndet" mellan elementen att vara dubbelt så stort som normalt. Detta problem kan åtgärdas genom mellanlagring. En ytterligare komplikation uppstår vid High Performance Fortran (HPF, se Appendix 9), nämligen ett behov av att låta elementen i ett fält vara fördelade över de olika processorerna i ett parallellt system.
Alla variabler i Fortran är normalt lokala för varje underprogram. Man kan kommunicera mellan olika underprogram på tre olika sätt, nämligen antingen utnyttjande COMMON eller moduler eller argumentlistan.
Om man överför fält utnyttjande COMMON måste fälten ha samma utseende i alla underprogrammen. Man måste då deklarera dimensionerna med konstanter, och samma konstanter (samma värden) i alla underprogrammen. Begreppet COMMON anses numera föråldrat. Jag återkommer till användning av COMMON i kapitel 13. Begreppet modul kan ersätta en del av funktionaliteten i COMMON, och behandlas i kapitel 14.
Jag skall nu utförligt diskutera det viktiga begreppet överföring av fält mellan huvudprogram, subrutiner och funktioner utnyttjande argument vid anrop. Under Fortran 77 rådde vissa allvarliga begränsningar i dessa möjligheter, men eftersom många gamla program och programbibliotek utnyttjar dessa metoder är kännedom om dessa väsentlig. I de följande avsnitten diskuterar jag därför först möjligheterna i Fortran 77 och sedan de tillkommande faciliteterna i Fortran 90.
Den grundläggande begränsningen i Fortran 90 är att i huvudprogrammet (egentligen den överordnade anropande programenheten) måste dimensioneringen ha skett med konstanter och ej med variabler.
PROGRAM MAIN
REAL, DIMENSION(20,10) :: A
! BERÄKNING
CALL SUB(A)
! BERÄKNING
END
SUBROUTINE SUB(B)
REAL, DIMENSION(20,10) :: B
! BERÄKNING I SUBRUTINEN
RETURN
END
Denna metod tillåter ingen som helst flexibilitet. Subrutinen måste passa exakt mot
dimensioneringen i den anropande programenheten.
Alternativt kan man utnyttja COMMON med fix dimensionering av fälten. Detta innebär dock ingen fördel, och bör undvikas. Begreppet COMMON behandlas i kapitel 13.
PROGRAM MAIN
REAL, DIMENSION(20,10) :: A
COMMON /ARG/ A
! BERÄKNING
CALL SUB
! BERÄKNING
END
SUBROUTINE SUB
REAL, DIMENSION(20,10) :: B
COMMON /ARG/ B
! BERÄKNING I SUBRUTINEN
RETURN
END
PROGRAM MAIN
REAL, DIMENSION(20,10) :: A
! BERÄKNING
CALL SUB(A,20,10)
! BERÄKNING
END
SUBROUTINE SUB(B,N,M)
REAL, DIMENSION(N,M) :: B
! BERÄKNING I SUBRUTINEN
RETURN
END
Denna metod med justerbart fält är mycket användbar, eftersom den innebär att samma subrutin kan
utnyttjas vid olika dimensionering hos det verkliga argumentet (vid olika anrop av subrutinen).
PROGRAM MAIN
REAL,DIMENSION(20,10) :: A
! BERÄKNING
CALL SUB(A,20)
! BERÄKNING
END
SUBROUTINE SUB(B,N)
REAL, DIMENSION(N,*) :: B
! BERÄKNING I SUBRUTINEN
RETURN
END
Innan asterisken infördes i samband med Fortran 77 "fuskade" man i stället med att
ge en etta (1) för den sista dimensioneringen. Båda dessa varianter innebär att
den sista dimensioneringen är ospecificerad. Notera dessutom att om man utnyttjar möjligheten
i vissa Fortan system till indexkontroll kan detta ej fungera bra vid antagen dimension. Problemet
är att varken * eller 1 känner till den verkliga dimensioneringen i anropande programenhet
(om inte systemet är mycket avancerat så att det automatiskt överför korrekt
dimensioneringsinformation). Metoden med en etta för den sista dimensioneringen fungerar
inte alls under de flesta Fortran 90 system!
Följande subrutin multiplicerar en matris C med en vektor V, den enda dimensioneringsinformation som måste överföras är den ledande dimension av det fält som innehåller matrisen C. Notera att vi här måste skilja på matematisk dimensionering av vektorer och matriser gentemot programmeringsteknisk dimensionering av fält som skall rymma dessa.
SUBROUTINE MATVEK(C, V, W, N, M, LED_DIM_C)
INTEGER :: N, M, LED_DIM_C
REAL, DIMENSION(*) :: V, W
REAL, DIMENSION(LED_DIM_C,*) :: C
! Beräknar W som produkten av C med V,
! där C är en N gånger M matris i fältet C
! med första dimensionen LED_DIM_C.
! Vektorn V antas ha längden M.
! Vektorn W antas ha längden N.
! Lokala variabler
INTEGER :: I, J
DO I = 1, N ! Nollställning av produkten
W(I) = 0.0
END DO
DO J = 1, M ! Beräkning av produkten
DO I = 1, N
W(I) = W(I) + C(I,J)*V(J)
END DO
END DO
RETURN
END
Notera att MATVEK kräver dels rad- och kolumn-dimensioneringen av
matrisen C (matematiska
begrepp, storheterna N och M) och dels rad-dimensioneringen av
fältet C (datatekniskt begrepp,
storheten LED_DIM_C). Notera även att ingen automatisk kontroll av dimensioneringen sker
i subrutinen, utan måste läggas in explicit i huvudprogrammet.
Ett tillhörande anropande program kan se ut på följande sätt. Här måste således matriser och vektorer (som fält) ges explicita dimensioneringar.
INTEGER IDIM, JDIM
PARAMETER (IDIM=50, JDIM=40)
INTEGER N, M
REAL, DIMENSION(IDIM,JDIM) :: A
REAL, DIMENSION(JDIM) :: X
REAL, DIMENSION(IDIM) :: Y
WRITE(*,*) ' Ge dimensionen för vektorn X '
READ(*,*) M
WRITE(*,*) ' Ge dimensionen för vektorn Y '
READ(*,*) N
IF ( N < 1 .OR. N > IDIM .OR. &
M < 1 .OR. M > JDIM ) THEN
WRITE(*,*) ' Felaktig dimensionering '
ELSE
! Bestämning av vektorn X och matrisen A
! bör ske här.
CALL MATVEK(A, X, Y, N, M, IDIM)
WRITE(*,*) ' Vektorn Y = AX '
WRITE(*,*) (Y(I), I = 1, N)
END IF
STOP
END
En variant av föregående metod innebär att lagringsutrymme för aktuell matris skapas i huvudprogrammet, men att matrisen aldrig användes i huvudprogrammet utan bara i subrutiner och funktioner. Metoden innebär att matrisen aldrig är tillgänglig på ett normalt sätt i huvudprogrammet, och förutsätter därför att den ej heller användes där, ej ens för utmatning. Däremot måste matrisens matematiska dimension bestämmas i huvudprogrammet, eller i en speciell subrutin, innan anrop av någon av de subrutiner som innehåller aktuell matris kan ske.
PROGRAM MAIN
INTEGER :: N
REAL, DIMENSION(20,20) :: A
CALL SUBN(N)
CALL SUB1(A,N)
CALL SUB2(A,N)
CALL SUB3(A,N)
END
SUBROUTINE SUBN(N)
INTEGER :: N
DO
WRITE(*,*) ' Ge aktuell dimension '
READ (*,*) N
IF ( N < 1 .OR. N > 20 ) THEN
WRITE(*,*) ' Felaktig dimension '
ELSE
EXIT
END IF
END DO
END
SUBROUTINE SUB1(B,M)
REAL, DIMENSION(M,M) :: B
! BERÄKNING I SUBRUTINEN
RETURN
END
SUBROUTINE SUB2(C,L)
REAL, DIMENSION(L,L) :: C
! BERÄKNING I SUBRUTINEN
RETURN
END
SUBROUTINE SUB3(D,K)
REAL, DIMENSION(K,K) :: D
! BERÄKNING I SUBRUTINEN
RETURN
END
I subrutinen SUBN har satsen EXIT den funktionen att den vid ett acceptabelt värde på
dimensionen N avbryter den "eviga" slingan. Subrutinen ger då återhopp till huvudprogrammet.
Jag upprepar att matrisen A är lagrad på ett onormalt sätt i huvudprogrammet (jämfört med lagringen i subrutinerna), om inte N råkar vara just 20. Utnyttjande bara första index vid beräkningen, dvs A(i+(j-1)*N, 1), kan man dock få fram rätt värde på elementet i den i-te raden och den j-te kolumnen. Däremot ger A(i,j) fel värde, nämligen uträknat som A(i+(j-1)*20, 1).
PROGRAM HUVUD
INTEGER :: N, K
N = 3
K = 17
CALL SUB(N, K, 2*K+N*N)
END
SUBROUTINE SUB (I, J, K)
INTEGER :: I, J, K
REAL, DIMENSION (I, J, K) :: X
Inne i subrutinen kan fältet X användas
RETURN
END
Dimensioneringen för X hämtas från heltalen i det anropande programmet. Automatiskt
fält är mycket praktiskt för arbetsareor i subrutiner och funktioner. De skapas
automatiskt (dynamisk minnesallokering) och försvinner likaså automatiskt vid uthopp
ur programenheten. Arbetsareor vid utnyttjande av programbibliotek var tidigare en stor administrativ
börda vid programmeringen.
Gränssnittet har till uppgift att tala om för systemet att dimensioneringsinformationen i anropande programenhet skall överföras automatiskt till den anropade programenheten. Detta sker medelst INTERFACE.
PROGRAM HUVUD
INTERFACE
SUBROUTINE SUB(A)
REAL, DIMENSION (:,:,:) :: A
END SUBROUTINE SUB
END INTERFACE
REAL, DIMENSION(1:20,1:12,-3:7) :: B
...
CALL SUB(B)
...
END PROGRAM HUVUD
SUBROUTINE SUB (A)
REAL, DIMENSION(:, :, :) :: A
...
END SUBROUTINE SUB
I Fortran 77 kan dynamisk minnesallokering egentligen ej ske, men den simuleras ibland genom att utrymme allokeras redan i den anropande programenheten, och både fältnamn och erforderlig dimensionering finns med i anropet, justerbart fält, avsnitt 4.1.2. En förenklad variant är där den sista dimensioneringen ges med en * i deklarationen, antagen dimension, avsnitt 4.1.3.
Nu tillkommer, förutom de redan diskuterade automatiskt fält, avsnitt 4.2.1, och antaget mönster, avsnitt 4.2.2, de båda ytterligare möjligheterna allokerbart fält och användning av fält utnyttjande pekare.
En ytterligare förutsättning är att aktuellt fält deklarerats med attributet ALLOCATABLE, vilket talar om för systemet att dynamisk minnesallokering kommer att ske.
REAL, DIMENSION(:), ALLOCATABLE :: X
...
ALLOCATE(X(N:M)) ! N och M är heltalsuttryck
! Ge båda gränserna eller bara
! den övre och då utan kolon!
...
X(J) = Q
CALL SUB(X)
...
DEALLOCATE (X)
...
END
Deallokering av lokala fält förekommer automatiskt i en subrutin
eller funktion (om
ej attributet SAVE getts) när man når RETURN
eller END.
För att kunna deklarera med pekare i huvudprogrammet och sedan allokera ett utrymme i subrutinen krävs användning av ett gränssnitt eller INTERFACE i huvudprogrammet.
På detta sätt erhålles i nedanstående exempel en dynamisk minnesallokering av vektorn B i subrutinen, och den kan även användas som vektorn A i huvudprogrammet. Storleken på vektorn bestäms i subrutinen. Gränssnittet är i huvudsak en upprepning av deklarationen i subrutinen. Det har till uppgift att informera huvudprogrammet om att verklig dimensioneringsinformation kan erhållas ur subrutinen.
Detta är en bra metod att föra en dimension uppåt i anropskedjan. Man kan naturligtvis i stället "fuska" genom att ha en subrutin som bestämmer dimensionen, hoppa tillbaks till huvudprogrammet och där göra en allokering av ett ALLOCATABLE fält, för att därefter anropa en annan subrutin som kan utnyttja det nu skapade fältet.
PROGRAM HUVUD
INTERFACE
SUBROUTINE SUB(B)
REAL, DIMENSION (:), POINTER :: B
END SUBROUTINE SUB
END INTERFACE
INTEGER :: N
REAL, DIMENSION (:), POINTER :: A
! Här har vektorn A ännu ej fått någon dimensionering!
CALL SUB(A)
! Använd nu vektorn A, den har nu fått dimensionering
! och även tilldelats värden i subrutinen SUB
N = SIZE(A) ! N är nu dimensionen av A
STOP
END PROGRAM HUVUD
SUBROUTINE SUB(B)
REAL, DIMENSION (:), POINTER :: B
INTEGER :: I, M
WRITE(*,*) " Tilldela nu en dimension till vektorn"
READ(*,*) M
IF (M < 1 ) M = 1
ALLOCATE (B(M))
DO I = 1, M
B(I) = 17.0*I + 3.0*I**2 - 1.0/I
END DO
RETURN
END SUBROUTINE SUB
En alternativ metod att föra dimensionering
uppåt i anropskedjan är användning av en
modul med ett allokerat och
sparat SAVEd
fält. Ett enkelt exempel på detta förfarande för
en vektor finns här.
MODULE DYNA
IMPLICIT NONE
REAL, DIMENSION(:), ALLOCATABLE, SAVE :: ARBETE
END MODULE DYNA
PROGRAM TEST_AV_DYNAMIK
USE DYNA
IMPLICIT NONE
PRINT *, 'MAIN'
CALL SUB1
CALL SUB2
PRINT *, 'MAIN'
END PROGRAM TEST_AV_DYNAMIK
SUBROUTINE SUB1
USE DYNA
IMPLICIT NONE
! Storleken av vektorn ARBETE bestäms här
INTEGER :: I
REAL, DIMENSION(5) :: A, B
PRINT *, 'SUB1'
A = 1.0
B = (/ (3.0, I = 1, 5) /)
ALLOCATE ( ARBETE(SIZE(B)) )
ARBETE = A
WRITE(*,*) A
WRITE(*,*) B
WRITE(*,*) ARBETE
END SUBROUTINE SUB1
SUBROUTINE SUB2
USE DYNA
IMPLICIT NONE
! Vektorn ARBETE används här
REAL :: B
REAL, DIMENSION(:), ALLOCATABLE :: A
PRINT *, 'SUB2'
ALLOCATE ( A(SIZE(ARBETE)) )
A = 2.*ARBETE
B = SUM(ARBETE)
ARBETE = A
WRITE(*,*) A
WRITE(*,*) B
WRITE(*,*) ARBETE
END SUBROUTINE SUB2
REAL, DIMENSION(10,10) :: MATRIS
WRITE(*,*) MATRIS
vilket är mycket effektivare, både vad gäller tidsåtgång och erforderligt
lagringsutrymme (om skrivningen i stället sker till fil), jämfört med det explicita
anropet av varje element.
REAL, DIMENSION(10,10) :: MATRIS
DO I = 1, 10
DO J = 1, 10
WRITE(*,*) MATRIS(I,J)
END DO
END DO
eller, med en implicit slinga för kolumnerna, varvid varje rad av matrsen skrives ut på
en rad på "papperet". I ovanstående exempel kommer i stället varje element på
egen rad.
REAL, DIMENSION(10,10) :: MATRIS
DO I = 1, 10
WRITE(*,*) (MATRIS(I,J), J = 1, 10)
END DO
Ovanstående kan enklare skrivas
REAL, DIMENSION(10,10) :: MATRIS
DO I = 1, 10
WRITE(*,*) MATRIS(I,:)
END DO
REAL, DIMENSION(5,20) :: X, Y
REAL, DIMENSION(-2:2,20) :: Z
:
Z = 4.0*SIN(Y)*SQRT(X)
Vi kanske här vill skydda oss för negativa element i X. Detta sker genom
WHERE ( X >= 0.0 )
Z = 4.0*SIN(Y)*SQRT(X)
ELSEWHERE
Z = 0.0
END WHERE
Notera att ELSEWHERE måste vara i ett ord!
Med fältsektion menas en del av ett fält. Om fältet A är deklarerat med
REAL, DIMENSION(-4:0,7) :: A
så väljer A(-3,:) ut hela den andra raden, medan
A(0:-4:-2, 1:7:2) väljer i omvänd
ordning ut vartannat element i varannan kolumn.
Liksom variabler kan bilda fält, så kan även konstanter det.
REAL, DIMENSION(6) :: B
REAL, DIMENSION(2,3) :: C
B = (/ 1, 1, 2, 3, 5, 8 /)
C = RESHAPE( B, (/ 2,3 /) )
där det första argumentet till den inbyggda funktionen RESHAPE ger värdena och
det andra argumentet ger det nya mönstret. Ytterligare två argument finns som möjliga
till denna funktion.
I följande mycket enkla exempel visar jag hur man kan tilldela matriser med satser av typ
B = A
utnyttja den inbyggda matrismultiplikationen MATMUL
(denna multiplicerar inte element för
element utan efter reglerna för matrismultiplikation) och
fältadderaren SUM (som adderar
samtliga element i fältet), samt utnyttja fältsektioner (i exemplet av typ vektorer).
PROGRAM FALT
IMPLICIT NONE
INTEGER :: I, J
REAL, DIMENSION (4,4) :: A, B, C, D, E
DO I = 1, 4 ! Beräkna en test matris.
DO J = 1, 4
A(I,J) = (I-1.2)**J
END DO
END DO
B = A*A ! Element för element multiplikation.
CALL SKRIV(A,4)
CALL SKRIV(B,4)
C = MATMUL(A,B) ! Inbyggd matris-multiplikation.
DO I = 1, 4 ! Explicit matris-multiplikation
! utnyttjande fältsektion för
! den inre slingan.
DO J = 1, 4
D(I,J) = SUM( A(I,:)*B(:,J) )
END DO
END DO
CALL SKRIV(C,4)
CALL SKRIV(D,4)
E = C - D ! Jämförelse av de två metoderna.
CALL SKRIV(E,4)
END PROGRAM FALT
SUBROUTINE SKRIV(FALT,N)
Generell rutin för utskrift av en flyttalsmatris.
IMPLICIT NONE
INTEGER :: N, I
REAL, DIMENSION (N,N) :: FALT
DO I = 1, N
WRITE(*,'(5E15.6)') FALT(I,:)
! Samtliga värden i matrisens i:te rad skrives ut,
! med (högst) fem värden per utskriftsrad).
END DO
WRITE(*,*) ! Skriv en blank rad.
END SUBROUTINE SKRIV
END SUBROUTINE SKRIV