Utrymme för flyttalsvektorn a(i), i = 1, 2, ... , 20, reserveras med satsen
REAL, DIMENSION(20) :: Aoch för flyttalsmatrisen b(i,j), i = 1, 2, ... , 10, j = 1, 2, ... , 10, med satsen
REAL, DIMENSION(10,10) :: Bdvs 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) :: Cfö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*d2Maximal 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 44Dessa ä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 ENDDenna 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 ENDDenna 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 ENDInnan 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 ENDNotera 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 ENDI 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 ENDDimensioneringen 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) ... ENDDeallokering 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 SUBEn 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(*,*) MATRISvilket ä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 DOeller, 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 DOOvanstå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 WHERENotera 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) :: Aså 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 = Autnyttja 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