Curs de Fortran

Curs de Fortran

Índex


📌
Busca en aquesta taula de continguts la teoria que necessites saber.

Previ


A tenir en compte sobre aquest curs

Què conté el curs?
  • El curs és exclusivament sobre el llenguatge de programació Fortran. No s’ensenyen mètodes numèrics ni d’altres conceptes de física computacional.
    • Per aprendre els diferents mètodes numèrics i la seva implementació veure: .
Quin és l’objectiu del curs?
  • L’objectiu és que juntament amb el es pugui adquirir una base de programació suficientment sòlida per enfocar les pràctiques de l’assignatura.
Quina versió Fortran utilitzarem?
  • Per programar utilitzarem Fortran 90. Els fitxers tindran per extensió .f90.
    • Si utilitzeu Fortran 77 aquest curs no és per vosaltres. Hi ha molts detalls diferents de sintaxis i la majoria dels programes us donarien error de compilació.
    • Si utilitzeu un Fortran superior (95, 2003, 2008, 2018 o 2023) aquest curs sí que és per vosaltres, cada nova versió de Fortran suporta les anteriors.
Amb què programarem?
  • Per programar utilitzarem l’editor Visual Studio Code.
    • Podeu aprendre a instal·lar Fortran, Gnuplot, descarregar el VSCode així com algunes extensions útils en la següent guia:
Com executar els programes d’exemple?
  • El curs és principalment pràctic, conté una mica de teoria però sobretot programes d’exemple. Aquests programes d’exemple es poden copiar i enganxar al VSCode i executar-los des del vostre ordinador, o alternativament també es poden executar en línia mitjançant el compilador online LFortran. A la descripció de cada un hi trobareu l’enllaç.
    • També es pot fer servir l’opció .
      Quan tingui temps faré un compilador en línia amb el build de WebAssembly de LFortran que permeti lectura i escriptura de fitxers (similar a Gnuplot Online).

Resum de tota la sintaxis (codi amb comentaris)

🚧
Pendent de fer…
En el següent programa d’exemple hi podeu trobar tota la sintaxis bàsica que es necessitaria per aprendre Fortran ràpidament.
Fitxer
Codi
PROGRAM Sintaxis  implicit none ! Sempre !__________Declaració de variables_____________    integer :: a = 3, b = 5, c !___________________Codi_______________________     print *, 'Hello World!' c = a * b   print *, a, ' per ', b, ' dona ', c    END PROGRAM

Idees bàsiques del Fortran


Primer programa — Estructura d’un fitxer Fortran i com imprimir per pantalla

PROGRAM primerPrograma  implicit none ! Sempre !__________Declaració de variables_____________    integer :: a = 3, b = 5, c !___________________Codi_______________________     print *, 'Hello World!' c = a * b   print *, a, ' per ', b, ' dona ', c    END PROGRAM
▶️ Executa aquest codi
Què hem après?
  • El programa s’escriu entre PROGRAM [nom programa] i END PROGRAM (o simplement END).
    • Nota: Fortran 90 accepta tan majúscules com minúscules (program i end també són correctes)
  • Sempre hem de posar un implicit none al principi del programa.
    • (Bé, estrictament parlant no és obligatori però ens estalviarà molts futurs errors de compilació. Aleshores és una pràctica habitual començar qualsevol programa, funció, subrutina o mòdul amb un implicit none)
  • Les variables s’han de declarar TOTES abans de fer cap manipulació amb elles.
    • Que se’ns quedi gravat al cap: primer declarem variables i després escrivim el codi.
  • Utilitzem la paraula clau integer per declarar nombres enters.
  • Podem declarar diverses variables (d’un mateix tipus) en una mateixa línia si les separem per comes.
  • Quan declarem una variable si volem la podem inicialitzar (integer :: b = 5) o si volem simplement la declarem (integer :: c) i ja més endavant li donarem un valor (c = 15).
  • Podem utilitzar print *, [variable1], [variable2]... per imprimir per pantalla.
    • Alternativament també podem utilitzar write (*,*).
Extra
Podem evitar els espais en blanc innecessaris al imprimir per pantalla. Això ho veurem aquí.

Tipus de variables i constants

Tenim diferents tipus de variables:
  • integer
  • real
    • double precision (s’inicialitza amb .d0, és equivalent a real*8)
  • character
  • logical
També podem declarar constants utilitzant la paraula clau:
  • parameter
Veiem el següent programa d’exemple:
PROGRAM primerPrograma  implicit none ! Sempre !__________Declaració de variables_____________ real :: radi = 5.2, area parameter :: PI = 3.14159 !___________________Codi_______________________   area = PI * radi**2     print *, ' Un cercle de radi ', radi, ' tindria per area ', area END PROGRAM
▶️ Executa aquest codi
Convertir entre diferents tipus
  • int(x) per convertir a enter
  • dble(x) per convertir a double precision
  • real(x) per convertir a real

Arrays

En programació un array és un llistat ordenat de variables del mateix tipus.
Nota: a vegades també s’anomena vector o matriu a un array, però és important no confondre-ho amb vectors i matrius de física, ara estem fent programació.
Podem declarar arrays de qualsevol tipus, per exemple:
  • integer :: enters(5) seria un llistat de 5 nombres enters
  • double precision :: posicio(3) seria un llistat de tres nombres reals
Quan inicialitzem un array ho fem amb brackets [] i amb els valors separats per comes:
  • enters(5) = [1, 5, -2, 3, 4]
Cas especial — String
Una cosa important a notar és que en Fortran no existeix el tipus de variable ‘String’ ja s’utilitza un array de caràcters.
Tot i així aquest és un cas especial, i no funciona amb la mateixa sintaxis que la resta d’arrays.
Per declarar-lo ens cal especificar la seva dimensió màxima amb len, i enlloc d’inicialitzar-lo amb brackets, utilitzem cometes dobles “” o cometes simples ‘’:
character(len=6) :: nom = “Miquel”
Normalment declarem deixant marge per cadenes llargues: character(len=30) :: nom.
A tenir en compte
Existeix una notació alternativa pels arrays utilitzant la paraula dimension
  • double precision, dimension(3) :: x
Es poden declarar arrays dobles o triples o de la dimensió que vulguem. La notació és simplement:
  • double precision:: taula(5,3)
  • integer :: cubRubik(3,3,3)
Nota: També es poden declarar arrays sense una dimensió definida, però això ho veurem més endavant.
Petit spoiler
integer :: dim integer, allocatable :: array(:) read *, dim allocate array(dim)
Referir-nos a valors d’un array
En Fortran (a diferència de Python) el primer element té per índex 1 (enlloc de 0). La resta funciona de manera molt similar. (Per exemple ens referim a grups d’elements amb la sintaxis típica de inici:final:pas).
array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] array(7) ! 7 array(1:5) ! [1, 2, 3, 4, 5] array(:4) ! [1, 2, 3, 4] array(7:) ! [7, 8, 9, 10] array(1:10:2) ! [1, 3, 5, 7, 9] array(1:10:-1) ! [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]

Condicionals

program exempleCondicional implicit none ! Declaració de variables integer :: A = 6, B = 4 ! Codi if (A > B) then print *, 'A és més gran que B' else if (A < B) then print *, 'A és més petit que B' else print *, 'A és igual a B' end if end if end program

Bucles

Bucle do genèric, pot anar seguit d’una condició i = inci, fi, pas o pot no tenir cap condició i executar-se fins fer servir la paraula exit. També podem fer un do while que és una versió equivalent d’un do genèric amb una condició que fa sortir del bucle.
program exempleBucle implicit none ! Declaració de variables integer i ! Codi do i = 0, 10 if (i <= 5) then print *, 'mes petit de 5' else print *, 'mes gran de 5' endif end do end program

Obtenir input de l’usuari

Per assignar a una variable un valor decidit per l’usuari, utilitzem la funció read, juntament amb el nom de la variable que volem llegir:
  • read (*,*) [variable]
Per exemple:
PROGRAM holaNOM  implicit none integer :: edat     print *, "Quina es la teva edat?" read (*,*) edat print *, "Molt bé, en altres paraules: Tens ", edat," anys!" END PROGRAM
▶️ Executa aquest codi (Nota: si es vol compilar en línia el següent programa, cal introduir l’input manualment en la part d’input del compilador LFortran abans de clicar el botó d’executar).
Si es vol fer bé, i demanar un input a l’usuari fins que l’input sigui correcte es pot utilitzar un bucle do while o similar i detectar l’input amb iostat.
integer :: k, num_error ! Declarem 'k' com un nombre enter do print *, "Introdueix un valor per k: " read (*,*, iostat = num_error) k ! La funció 'read' espera un nombre enter ! Explicació: iostat (input-output status) retorna un 0 si efectua correctament l'operació 'read' o 'write', i retorna un nombre diferent (nombre d'error) si alguna cosa va malament. if (k >= 5 .and. k <= 301 .and. num_error == 0) then exit end if print *, "Introdueix un nombre ENTER entre 5 i 301" end do ! Resta del codi (ara ja tenim un valor de 'k' correcte)

Funcions

Hi ha dues maneres d’utilitzar una funció declarada fora del programa
  • Amb una sola línia:
    • double precision, external :: fun
  • Amb dues línies:
    • double precision :: fun external :: fun
Exemple d’ús en un programa
program utilitzant_funcions implicit none double precision :: x = 5.d0 double precision, external :: y print *, "y = ", y(x) end program double precision function y(x) implicit none double precision, intent(in) :: x y = 2.d0*x + 3.d0 end function
També podem declarar funcions dins d’un mateix programa (o mòdul). Això s’utilitza en casos més avançats (programació orientada a objectes) on tenim diferents programes (o mòduls) que tenen les seves variables i funcions públiques (externes) i privades (internes) de manera que un programa principal sols necessita interactuar amb les públiques sense preocupar-se amb tot l’entramat del que fa el programa a nivell intern. Explicat aquí.

Subrutines

Una subrutina és com una funció però no només tenim paràmetres d’entrada sinó que els mateixos paràmetres es poden modificar.
subroutine intercanvi(a, b, c) implicit none double precision, intent(in) :: a double precision, intent(inout) :: b double precision, intent(out) :: c if (a == 0) then return end if b = b/a c = a + b end subroutine
Posar intent(inout) o no posar-ho vindria a ser el mateix, és opcional.

Variables globals (COMMON)

📌
Tot i que està bé saber utilitzar COMMON la pràctica recomanada és utilitzar mòduls. Pots aconseguir el mateix i de manera més efectiva. Pots veure-ho aquí.

Funcions externes i internes (CONTAINS)

Arrays dinàmics

double precision, allocatable :: temps(:), posicions(:) integer :: N ! Codi on es calculen coses allocate(temps(N), posicions(N))
Obtenir les dimensions d’un array
Utilitzant size podem trobar les dimensions d’un array.
Nz = size(posicions, 3) ! posicions = [x_i, y_i, z_i]

Escriure en fitxers

  1. Obrim fitxer
  1. Escrivim una línia comentant les dades
  1. Desenvolupem el codi que calcula les dades i escrivim les dades en el fitxer
  1. Deixem dues línies en blanc
  1. Tanquem el fitxer
open(10, file = 'data/P6-24-25-res.dat') write(10, *) "# N | Int_u | Error_u | Int_d | Error_d" do i = 1, k write(10, *) i*N_sep, ints_u(i), errors_u(i), ints_d(i), errors_d(i) end do write(10,*) write(10,*) close(10)
Separem les dades amb dues línies buides per poder utilitzar index a gnuplot i per posar comentaris utilitzem #.

Llegir fitxers

Funcionalitats avançades de Fortran


Especificar el format (xifres decimals desitjades)

PROGRAM exempleFormat implicit none double precision :: x = 123.4567890123456894d0 print '(F0.12)', x ! write (*, '(F0.12)') x END PROGRAM
Nota: Si volem podem guardar-nos el tipus de format com a variable
Format com a variable
PROGRAM exempleFormat implicit none character(len=20) :: dec_12 = '(F0.12)' ! 12 xifres significatives double precision :: x = 123.4567890123456d0 print dec_12, x ! write (*, dec_12) x END PROGRAM
Teoria
Per especificar el format de xifres decimals podem utilitzar f, e o g.
  • f per notació decimal fixa (la normal)
  • e per notació decimal exponencial (notació científica)
  • g per tal que Fortran triï quina ha d’utilitzar
La sintaxis en principi és: f[nombre de xifres totals].[nombre de xifres decimals]
Ara bé, si posem un 0 a xifres totals, calcularà quantes xifres significatives ha de donar per tal que tingui les xifres decimals exactes demanades.
Això funciona molt bé per f però no tant bé per e i menys per g.
A la pràctica
  • Utilitzar f20.12 quan haguem d’escriure en fitxers
  • Quan vulguem imprimir per pantalla utilitzar:
    • f0.12 per nombres decimals
    • e18.12 per nombres declarats en notació exponencial
Exemple
double precision :: PI = 4.d0*datan(1.d0) double precision :: h = 6.62607015d-34 ! 12 xifres decimals print '(A, f0.12)', "PI = ", PI ! Notació decimal normal print '(A, e18.12)', "h = ", h ! Notació científica write (10, '(g20.12)') h, PI
No preocupar-se pel ‘(A, f0.12)’, ho expliquem a continuació.

Imprimir diferents formats correctament

Si intentem combinar variables en un print, al separar-les per comes obtindrem un espaiat innecessari entre elles.
Malauradament l’única manera de solucionar-ho és canviant l’argument * del print o del write, per un que indica el format de cada variable que li passem, i en l’ordre correcte.
Exemple
program printSenseEspais implicit none ! Declaració de variables character(len=20) :: nom = "Marti" integer :: edat = 22 double precision :: PI = 4.d0*datan(1.d0) ! CAL ESPECIFICAR CADA INPUT ! A = cadena sense espais ! I0 = enter ! f0.12 = double precision amb 12 xifres decimals ! Exemple 1 print '(A, A, A, I0, A)', "Em dic ", trim(nom), " i tinc ", edat, " anys." ! Exemple 2 print '(A, f0.12)', "PI = ", PI ! 12 xifres decimals end program
Nota: tal com hem explicat al principi, les cadenes en Fortran se’ls ha de donar una longitud màxima inicial (en el nostre cas len=20). Per evitar espais innecessaris al imprimir per pantalla (sols utilitzem 6 caràcters de 20 i per tant queden 14 espais en blanc al final) fem servir trim().

Imprimir amb accents i caràcters UTF-8

En MacOS i Linux això no passa, però en Windows, si provem de fer print *, "Hola què tal? pot ser que obtinguem com a output: Hola qu├¿ tal?.
Això ho podem solucionar si volem afegint call system("chcp 65001 > nul") dins el codi Fortran.
El que fa és activar el encoding com a UTF-8.
Exemple
program configurantUTF8 implicit none call system("chcp 65001 > nul") print *, "Hola què tal?" print *, "à è é í ò ó ú" end program

Funcions i subrutines internes (CONTAINS)

Pendent de redactar…

Crear i importar mòduls (llibreries)

Podem crear un mòdul en un fitxer a part tal com si fos un programa però li diem module.
module graus_i_radians implicit none ! parameter = valor constant (en fortran 90) real, parameter :: PI = 3.14159265359 ! Funcions (mètodes) del mòdul contains real function graus_to_radians (graus) implicit none real, intent(in) :: graus graus_to_radians = (graus*2*PI)/360 end function real function radians_to_graus (radians) implicit none real, intent(in) :: radians radians_to_graus = (radians*360)/(2*PI) end function end module
I podem desprès importar-los en el nostre programa principal mitjançant use i el nom del mòdul.
program importacio_de_moduls use graus_i_radians implicit none ! Declaració de variables ! Codi print *, "45 graus son ", graus_to_radians(45.0), " radians." end program
Nota: A Fortran tot són mòduls descarregats, i si no es troben a la mateixa carpeta (directori) que el programa principal, hem d’indicar-ne la ruta correctament.
Clarament també podem declarar els mòduls en el mateix fitxer que el programa.
Tot en un sol fitxer
Podem declarar els mòduls abans o després del programa principal (és indiferent).
program importacio_de_moduls use graus_i_radians implicit none ! Declaració de variables ! Codi print *, "45 graus son ", graus_to_radians(45.0), " radians." end program module graus_i_radians implicit none ! parameter = valor constant (en fortran 90) real, parameter :: PI = 3.14159265359 ! Funcions (mètodes) del mòdul contains real function graus_to_radians (graus) implicit none real, intent(in) :: graus graus_to_radians = (graus*2*PI)/360 end function real function radians_to_graus (radians) implicit none real, intent(in) :: radians radians_to_graus = (radians*360)/(2*PI) end function end module

Mòduls vs COMMON (Declaració de constants)

module constants double precision, parameter :: PI = 4.d0 * datan(1.d0) ! Es poden declarar les constants que es vulguin a dins d'un modul double precision, parameter :: m = 510.d0 ! g double precision, parameter :: l = 45.d0 ! cm double precision, parameter :: g = 1.62d0 ! m/s^(-2) double precision, parameter :: w_N = dsqrt(g/l) end module program utilitzant_moduls use constants implicit none double precision :: radi = 2.d0 double precision, external :: perimetre print *, "PI = ", PI print *, "Per r = ", radi, "perimetre = ", perimetre(radi) end program double precision function perimetre(r) use constants implicit none double precision, intent(in) :: r perimetre = 2.d0*PI*r end function

Gestionar els .mod

Al treballar amb mòduls es generen fitxers .mod, per tal de gestionar-ho millor seguirem la filosofia de i els mantindrem dins la seva carpeta separada.
Solució senzilla
a tasks.json afegir una tasca que generi la carpeta ‘mods’
{ "label": "Create 'mods' Folder", "type": "shell", "command": "if(!(Test-Path ${fileDirname}\\mods)) {md 'mods'}", "options": { "cwd": "${fileDirname}" }, "problemMatcher": [], "group": "build" }
i modificar la tasca ‘Build Fortran’ afegint -Jmods i -Imods com a arguments.
{ "label": "Build Fortran", "type": "shell", "command": "gfortran", "options": { "cwd": "${fileDirname}", "shell": { "executable": "powershell", "args": [ "-NoLogo", "-Command" ] } }, "args": [ "-Jmods", "-Imods", "${fileDirname}\\${fileBasename}", "-o", "${fileDirname}\\exes\\${fileBasenameNoExtension}.exe" ] }
Ara amb això ja podem compilar correctament els .mod en la seva carpeta. Tot i així per defecte el linter de Modern Fortran ens generarà un arxiu al directori del programa al obrir el fitxer .f90, per evitar-ho afegim el següent a dins de .vscode\settings.json:
"fortran.linter.includePaths": [ "/mods" ], "fortran.linter.extraArgs": [ "-Wall", "-J/mods" ]
I com a última cosa, anem a personalitzar l’icona de la carpeta ‘mods’. Anem al buscador d’extensions, busquem Material Icon Theme, cliquem la rodeta de configuració i després Settings. Busquem on posa “Material-icon-theme › Folders: Custom Clones” i cliquem el text blau de “edit i settings.json”, se’ns obrirà el fitxer de configuració d’usuari (settings.json però a nivell d’usuari, no del directori).
En aquest fitxer a dins de "material-icon-theme.folders.customClones" [ ... ] hi afegim el següent:
{ "name": "mods-carpeta", "base": "folder-plugin", // opció alternativa: folder-lib "color": "#000736", // Blau fosc per 'mods' "folderNames": ["mods"] }
I a dins de "material-icon-theme.files.customClones” [ ... ] hi afegim el següent:
{ "name": "arxiu-mod", "base": "wxt", "color": "#7584d9", // Blau pels mods "fileExtensions": ["mod"] }
I ja estaria! Ara al guardar el fitxer (Ctrl+S) hauríem de veure el canvi d’icona.
Hauria de lluir una mica així:
notion image

Utilitzar subrutines dins d’una INTERFACE

És més xulo fer servir mòduls, però en altres compiladors (diferents de gfortran) cal posar sí o sí la interface.
Pendent de redactar…

Passar paràmetres opcionals a funcions i subrutines

Mitjançant optional podem definir paràmetres opcionals. Els quals sempre hauran de ser dels últims paràmetres.
program demoOptional implicit none double precision :: a = 3.d0, b = 4.d0, c = 5.d0 double precision, external :: suma double precision :: sum ! Funció amb paràmetre opcional print *, suma(a,b) print *, suma(a,b,c) ! Subrutina amb paràmetre opcional call sumar(sum,a,b) print *, sum call sumar(sum,a,b,c) print *, sum end program subroutine sumar(sum,a,b,c_) implicit none double precision, intent(in) :: a, b double precision, intent(out) :: sum double precision, optional :: c_ ! Input opcional double precision :: c ! Utilitzem una variable auxiliar ja que malauradament no podem assignar valors a arguments opcionals if (present(c_)) then c = c_ else c = 0.d0 ! Valor per defecte end if sum = a + b + c end subroutine double precision function suma(a,b,c_) implicit none double precision :: a, b double precision, optional :: c_ ! Input opcional double precision :: c ! Utilitzem una variable auxiliar ja que malauradament no podem assignar valors a arguments opcionals if (present(c_)) then c = c_ else c = 0.d0 ! Valor per defecte end if suma = a + b + c end function

Definir un nom diferent de la funció per la variable output

En quant a claredat del codi, pot ser còmode tenir noms diferents per la funció i per la variable que retorna la funció. Per exemple és més natural cridar una funció anomenada com un verb, com calcularMitjana i en canvi dins la funció operar amb una variable anomenada mitjana.
program demoResult implicit none double precision, dimension(5) :: dades = [2.0d0, 4.0d0, 6.0d0, 8.0d0, 10.0d0] double precision, external :: calcularMitjana ! Nom de la funció externa double precision :: valorMig valorMig = calcularMitjana(dades, 5) print *, "La mitjana es ", valorMig end program demoResult double precision function calcularMitjana(vec, n) result(mitjana) implicit none integer, intent(in) :: n double precision, intent(in) :: vec(n) double precision :: mitjana ! Aquesta és la variable on es guarda l'output integer :: i mitjana = 0.d0 if (n <= 0) then return end if do i = 1, n mitjana = mitjana + vec(i) end do mitjana = mitjana / n end function

Select per triar entre diferents casos

Equivalent al switch de C++ o al match de Python, tenim el select de Fortran. (Documentació).

Model d’estructura d’una pràctica amb mòduls


Estructura base

module constants implicit none double precision :: PI = 4.d0*datan(1.d0) end module module funcions use constants implicit none contains double precision function suma(a,b) implicit none double precision :: a, b suma = a + b end function end module module subrutines use constants use funcions implicit none contains subroutine sumar(sum,a,b) implicit none double precision, intent(in) :: a, b double precision, intent(out) :: sum sum = a + b end subroutine end module program practicaRandom use constants use funcions use subrutines implicit none ! ________ Declaració de variables ________ double precision :: a = 3.d0, b = 4.d0, c = 5.d0 double precision :: sum ! ________________ Codi ___________________ ! Funció print *, suma(a,b) ! Subrutina call sumar(sum,a,b) end program

Llibreries de Fortran


Lapack (Linear Algebra Pack)

PETSc (per resoldre EDOS)

NetCDF (per llegir i escriure el format de dades utilitzat en meteorologia)

FFTW (Transformades de Fourier)

Quantum Espresso

Altres

Hi ha altres llibreries útils com Arpack o Cernlib però que estan escrites en Fortran 77 i el seu ús s’ha discontinuat.

Altres


Compilar Fortran en línia amb LFortran Playground

Hi ha la següent playground molt xula que utilitza l’innovador compilador lfortran:
És la playground oficial recomanada per fortran-lang. Quina és la part xula? que a part de poder compilar allà el codi que nosaltres hi escrivim al moment, podem posar codi a GitHub Gists i després utilitzar correctament la URL.
Per exemple vegeu el següent Gist:
Si ens fixem en la seva URL, ara podem crear una URL per compilar aquest codi el línia amb la playground de lfortran simplement utilitzant ?gist= seguit de l’identificador del nostre Gist.
Així doncs podem crear i compartir exemples en línia.

Altres compiladors en línia

Un altre compilador que està molt bé és OneCompiler. El principal avantatge és que permet input de l’usuari a través de stdin, i que es poden compartir programes si et crees un usuari.
Després també hi ha el playground antic de fortran-lang (però actualment ja no es manté i la playground oficial és la de dev.lfortran.org)

Conversió de Fortran a Python i viceversa

A continuació una “Rosseta Stone” per canviar de Fortran a Pyhton i viceversa.

Conversió de Fortran a C++

La manera més senzilla és posar el codi a la playground del compilador lfortran i canviar l’output de STDOUT a CPP