Skip to content

Fortran Aide-mémoire

Langage pionnier pour le calcul scientifique et numérique.

01

Bases & Structure de programme

Structure de programme & Hello World

Chaque programme Fortran commence par 'program NAME' et se termine par 'end program NAME'. 'implicit none' est OBLIGATOIRE en Fortran moderne — il force la déclaration explicite de toutes les variables (sans lui, Fortran utilise le typage implicite où les variables commençant par i-n sont entières, les autres réelles, ce qui est une source majeure de bugs). Le bloc 'contains' sépare le code exécutable des procédures internes (sous-routines/fonctions définies à l'intérieur du programme). Les commentaires commencent par '!'. La forme source libre (Fortran 90+) utilise l'extension .f90 ; les colonnes n'ont pas d'importance. Compilez avec gfortran/ifort.

fortran
program hello
  ! A complete Fortran program structure
  implicit none
  ! declarations go here
  integer :: status = 0

  print *, "Hello, World!"   ! list-directed output to stdout

  ! executable statements
  call do_work(status)

  print *, "Exit status: ", status
contains
  subroutine do_work(st)
    integer, intent(out) :: st
    st = 0
    print *, "Working..."
  end subroutine do_work
end program hello

! Compile: gfortran hello.f90 -o hello
! Run:     ./hello

Variables & Types intrinsèques

Fortran a 5 types intrinsèques : integer, real, complex, character, logical. 'kind' sélectionne la précision/taille — utilisez kind=8 pour 64 bits (ou mieux, utilisez selected_real_kind/iso_fortran_env pour la portabilité). Les littéraux réels ont besoin d'un suffixe de kind : 3.14_8 (pas seulement 3.14). Double precision est la syntaxe héritée pour real(kind=8). Les littéraux complexes utilisent la forme (réel, imag). Les valeurs logiques sont .true. / .false. (avec des points). Les chaînes de caractères ont une 'len' fixe sauf si déclarées avec len=: et allocatable (longueur différée, Fortran 2003+). Initialisez toujours avec le suffixe de kind correspondant pour éviter une perte de précision silencieuse.

fortran
program variables
  implicit none
  ! Integer types
  integer :: count = 0
  integer(kind=8) :: big = 9223372036854775807_8   ! 64-bit
  ! Real types
  real :: x = 3.14              ! default (often 32-bit)
  real(kind=8) :: y = 2.718281828459045_8   ! double precision
  double precision :: z = 1.0d0
  ! Complex
  complex :: c = (1.0, 2.0)    ! 1 + 2i
  complex(kind=8) :: cw = (1.0_8, 2.0_8)
  ! Character
  character(len=20) :: name = "Alice"
  character(len=:), allocatable :: flexible   ! deferred length
  ! Logical
  logical :: flag = .true.
  ! Print all
  print *, count, big
  print *, x, y, z
  print *, c, cw
  print *, name, flag
end program variables

Constantes & Paramètres

Les constantes utilisent l'attribut 'parameter' et doivent être initialisées à la déclaration. Elles ne peuvent pas être modifiées — le compilateur peut les optimiser et les intégrer. Utilisez SCREAMING_SNAKE_CASE par convention. character(*) signifie 'prendre la longueur de l'initialiseur' (pratique pour les constantes de chaîne). Les paramètres sont couramment utilisés pour les tailles de tableau, les constantes physiques et les codes entiers de type énumération. Fortran 2003+ a aussi de vrais types ENUM mais les entiers parameter restent le choix idiomatique. Les paramètres peuvent être utilisés dans les déclarations de dimension de tableau et d'autres contextes d'expression constante.

fortran
program constants
  implicit none
  ! Named constants via 'parameter' attribute
  integer, parameter :: MAX_SIZE = 100
  real, parameter :: PI = 3.14159265
  real, parameter :: E = 2.718281828
  character(*), parameter :: APP_NAME = "MyApp"
  ! Using parameters
  real :: arr(MAX_SIZE)
  arr = 0.0
  print *, APP_NAME, " size=", MAX_SIZE
  print *, "Circumference: ", 2.0 * PI * 5.0

  ! Enum-like via parameter
  integer, parameter :: SUNDAY = 1, MONDAY = 2, TUESDAY = 3
  integer :: day = MONDAY
  print *, "Day code: ", day
end program constants

Opérateurs & Expressions

Opérateurs Fortran : arithmétiques (+ - * / **), avec ** pour l'exponentiation (unique à Fortran). La division entière tronque vers zéro — utilisez real(a)/b pour la vraie division. Deux syntaxes relationnelles : moderne (< > == /= <= >=) et héritée (.lt. .gt. .eq. .ne. .le. .ge.). Logique : .and. .or. .not. .eqv. (équivalence) .neqv. (ou exclusif). La concaténation de chaînes utilise // ; trim() supprime les espaces de fin (Fortran remplit les chaînes de longueur fixe avec des espaces). mod vs modulo : mod suit le signe de la division tronquée, modulo suit la division par défaut — ils diffèrent pour les opérandes négatifs.

fortran
program operators
  implicit none
  integer :: a = 17, b = 5
  real :: x = 2.0
  ! Arithmetic
  print *, a + b, a - b, a * b    ! 22 12 85
  print *, a / b                   ! 3 (integer division!)
  print *, real(a) / b             ! 3.4 (cast to real)
  print *, a ** 2                  ! 289 (exponentiation)
  print *, mod(a, b)               ! 2 (modulo)
  print *, modulo(a, b)            ! 2 (differs for negatives)
  ! Relational (both forms work)
  print *, a > b, a < b            ! T F
  print *, a .gt. b, a .lt. b      ! T F (old form)
  print *, a == b, a /= b          ! F T
  ! Logical
  print *, (a > 0) .and. (b > 0)   ! T
  print *, (a > 0) .or. (b < 0)    ! T
  print *, .not. (a > 0)           ! F
  print *, (a > 0) .eqv. (b > 0)   ! T (equivalence)
  ! String concatenation
  character(10) :: s1 = "Hello", s2 = "World"
  print *, trim(s1) // " " // trim(s2)   ! Hello World
end program operators

Fonctions intrinsèques & Math

Fortran a un riche ensemble de fonctions intrinsèques (intégrées). Math : abs, sqrt, exp, log (naturel), log10, sin/cos/tan/asin/acos/atan/atan2, sinh/cosh/tanh. Arrondi : int (tronquer), nint (le plus proche), floor, ceiling. Conversion : real(), int(), cmplx(). Interrogation : size, shape, huge (valeur max), tiny (min positif), kind. Toutes les fonctions trig prennent des radians. atan2(y, x) retourne l'angle dans le bon quadrant (contrairement à atan). Utilisez huge/tiny pour vérifier les limites de portée. Les intrinsèques sont élémentaires — elles fonctionnent sur les tableaux élément par élément automatiquement.

fortran
program intrinsics
  implicit none
  real :: x = -3.7, y = 2.5
  ! Math functions
  print *, abs(x)         ! 3.7
  print *, sqrt(2.0)      ! 1.414...
  print *, exp(1.0)       ! 2.718... (e^x)
  print *, log(2.0)       ! 0.693... (natural log)
  print *, log10(1000.0)  ! 3.0
  print *, sin(3.14159/2) ! 1.0
  print *, cos(0.0)       ! 1.0
  print *, atan2(1.0,1.0) ! 0.785... (pi/4)
  ! Rounding
  print *, int(x)         ! -3 (truncate toward zero)
  print *, nint(x)        ! -4 (nearest integer)
  print *, floor(x)       ! -4 (toward -inf)
  print *, ceiling(x)     ! -3 (toward +inf)
  print *, abs(x), max(x, y), min(x, y)   ! 3.7 2.5 -3.7
  ! Type conversion
  print *, real(5)        ! 5.0
  print *, int(3.9)       ! 3
  ! Inquiry
  real :: arr(10)
  print *, size(arr)      ! 10
  print *, huge(1)        ! 2147483647
  print *, tiny(1.0)      ! smallest positive real
end program intrinsics
02

Flux de contrôle

If...Then...Else

IF bloc : 'if (cond) then ... else if (cond) then ... else ... end if'. Chaque branche a besoin de 'then' (sauf le else final). Le IF logique est sur une ligne : 'if (cond) statement' (pas de 'then'/'end if'). Les conditions utilisent des opérateurs relationnels (< > == /= <= >= ou .lt. .gt. .eq. .ne. .le. .ge.) combinés avec .and. .or. .not. 'stop' termine le programme (optionnellement avec un message/code). Le IF arithmétique (if (x) label1, label2, label3) est supprimé dans Fortran 2018 — ne l'utilisez jamais. Utilisez toujours 'implicit none' pour que les variables non déclarées soient détectées.

fortran
program if_demo
  implicit none
  integer :: score = 85
  character(1) :: grade

  ! Multi-branch if/else if/else
  if (score >= 90) then
    grade = 'A'
  else if (score >= 80) then
    grade = 'B'
  else if (score >= 70) then
    grade = 'C'
  else if (score >= 60) then
    grade = 'D'
  else
    grade = 'F'
  end if

  print *, "Score ", score, " -> Grade ", grade

  ! Logical if (single statement, no 'then')
  if (score < 0 .or. score > 100) stop "Invalid score"

  ! Arithmetic if (OBSOLETE - avoid)
  ! if (x) 10, 20, 30   ! jump to label based on sign
end program if_demo

Select Case (Switch)

select case est l'instruction switch de Fortran. Les cas peuvent être des valeurs uniques (case (3)), des listes (case (1, 3, 5)) ou des intervalles (case (4:5) signifie 4 à 5 inclus). case default est le repli. Contrairement à C, il n'y a PAS de chute — chaque branche est indépendante et une seule s'exécute. Fonctionne avec les types integer, character et logical (PAS real). Les intervalles de caractères utilisent l'ordre ASCII ('A':'Z'). Pour les comparaisons en virgule flottante, utilisez if/else. select case est plus efficace que les longues chaînes if/else if pour le dispatch entier/caractère (le compilateur peut utiliser des tables de saut).

fortran
program case_demo
  implicit none
  integer :: day = 3
  character(1) :: ch = 'A'
  character(10) :: day_name

  ! Integer select case
  select case (day)
  case (1)
    day_name = "Monday"
  case (2)
    day_name = "Tuesday"
  case (3)
    day_name = "Wednesday"
  case (4:5)
    day_name = "Thu/Fri"
  case (6:7)
    day_name = "Weekend"
  case default
    day_name = "Invalid"
  end select
  print *, day_name

  ! Character select case (case-insensitive via pre-upper)
  select case (ch)
  case ('A':'Z')
    print *, "Uppercase letter"
  case ('a':'z')
    print *, "Lowercase letter"
  case ('0':'9')
    print *, "Digit"
  case default
    print *, "Other"
  end select

  ! Logical select case
  select case (day > 5)
  case (.true.)
    print *, "Weekend!"
  case (.false.)
    print *, "Weekday"
  end select
end program case_demo

Boucles Do (comptées)

Boucle DO comptée : 'do var = start, end, step' (step par défaut à 1). La boucle s'exécute tant que var <= end (pour step positif) ou var >= end (pour step négatif). var est incrémenté APRÈS chaque itération. Les constructions implied-do [(expr, var=start,end)] sont puissantes pour l'initialisation de tableau et les listes d'E/S. Les boucles nommées (outer: do ... end do outer) permettent de cibler cycle/exit vers un niveau d'imbrication spécifique. La variable de boucle est automatiquement définie ; en Fortran elle conserve sa valeur finale après la boucle. Évitez de modifier la variable de boucle à l'intérieur du corps de la boucle.

fortran
program do_loops
  implicit none
  integer :: i, j, total

  ! Basic counted loop: do var = start, end [, step]
  do i = 1, 5
    print *, i          ! 1 2 3 4 5
  end do

  ! With step
  do i = 10, 1, -1      ! countdown
    print *, i
  end do

  do i = 0, 100, 25     ! 0 25 50 75 100
    print *, i
  end do

  ! Implied-do (inline, for array init / I/O)
  integer :: arr(5) = [(i**2, i=1,5)]   ! 1 4 9 16 25
  print *, arr
  print *, (i, i=1,3)                   ! 1 2 3

  ! Nested loops with labels (for cycle/exit targeting)
  total = 0
  outer: do i = 1, 3
    inner: do j = 1, 3
      total = total + i*j
    end do inner
  end do outer
  print *, "Total: ", total
end program do_loops

Do While & Boucles infinies

do while (cond) ... end do est une boucle à pré-test (vérifie la condition avant chaque itération ; peut s'exécuter zéro fois). Pour un comportement post-test, utilisez do ... if (cond) exit ... end do. Le 'do ... end do' nu est une boucle infinie — vous DEVEZ avoir une instruction exit (sinon infini). 'exit' quitte la boucle la plus interne (ou une boucle nommée). Les boucles nommées (factorial_loop:) permettent à exit de cibler une boucle externe. Utilisez do while lorsque le nombre d'itérations est inconnu et dépend d'une condition ; utilisez do compté lorsque le nombre est connu à l'avance.

fortran
program while_demo
  implicit none
  integer :: n, count
  real :: x, sum

  ! do while: pre-test loop
  n = 1024
  count = 0
  do while (n > 1)
    n = n / 2
    count = count + 1
  end do
  print *, "log2(1024) = ", count   ! 10

  ! Infinite loop with exit
  sum = 0.0
  do
    read(*, *) x
    if (x < 0) exit          ! leave loop
    sum = sum + x
  end do
  print *, "Sum: ", sum

  ! do ... end do with conditional exit
  n = 1
  factorial_loop: do
    if (n > 10) exit factorial_loop
    print *, n, factorial(n)
    n = n + 1
  end do factorial_loop

contains
  recursive function factorial(n) result(f)
    integer, intent(in) :: n
    integer :: f
    if (n <= 1) then
      f = 1
    else
      f = n * factorial(n-1)
    end if
  end function factorial
end program while_demo

Cycle, Exit & Contrôle de boucle

cycle saute le reste de l'itération courante et passe à la suivante (comme 'continue' en C/Python). exit sort de la boucle entièrement (comme 'break'). Les deux ciblent la boucle la plus interne par défaut, mais avec les boucles nommées (search: do ... end do search) vous pouvez cibler une boucle externe : 'exit search' ou 'cycle search'. C'est essentiel pour sortir proprement des boucles imbriquées. Utilisez cycle pour le filtrage (sauter les itérations indésirables) et exit pour l'arrêt anticipé (recherche trouvée, erreur détectée). Les boucles nommées rendent le flux de contrôle imbriqué explicite et lisible.

fortran
program loop_control
  implicit none
  integer :: i, j

  ! cycle: skip to next iteration (like 'continue' in C)
  do i = 1, 10
    if (mod(i, 2) == 0) cycle   ! skip even numbers
    print *, i                  ! 1 3 5 7 9
  end do

  ! exit: break out of loop (like 'break' in C)
  do i = 1, 100
    if (i * i > 50) then
      print *, "Stopped at i=", i
      exit
    end if
  end do

  ! Named loops: cycle/exit can target outer loops
  search: do i = 1, 5
    do j = 1, 5
      if (i + j == 7) then
        print *, "Found: ", i, "+", j, "= 7"
        exit search             ! break out of OUTER loop
      end if
    end do
  end do search

  ! Early exit from a search
  integer :: arr(10) = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3]
  do i = 1, size(arr)
    if (arr(i) == 9) then
      print *, "Found 9 at index ", i
      exit
    end if
  end do
end program loop_control
03

Tableaux & Opérations vectorielles

Déclaration & Initialisation de tableau

Les tableaux Fortran sont indexés à 1 par défaut (borne inférieure = 1), mais vous pouvez spécifier une borne inférieure personnalisée : a(0:4) a des indices 0..4. Les tableaux multidimensionnels utilisent l'ordre (lignes, colonnes) — stockage colonne-major (le premier index varie le plus vite en mémoire). Initialisez avec des constructeurs de tableau [1,2,3] ou implied-do [(expr, i=start,end)]. reshape remplit un tableau multidimensionnel à partir d'une liste 1D. size() retourne le nombre total d'éléments ; lbound/ubound retournent les bornes inférieures/supérieures. shape() retourne la forme comme un tableau 1D. Les tableaux sont 'whole-array' — vous pouvez les affecter et opérer dessus sans boucles explicites.

fortran
program array_decl
  implicit none
  ! Declaration with dimension
  integer :: a(5)                  ! 1D, indices 1..5
  integer :: b(0:4)                ! 1D, indices 0..4 (custom lower bound)
  real :: c(3, 4)                  ! 2D, 3 rows x 4 cols
  real, dimension(10) :: d         ! using dimension attribute

  ! Initialization at declaration
  integer :: x(5) = [1, 2, 3, 4, 5]
  integer :: y(5) = [(i*2, i=1,5)] ! implied-do: 2 4 6 8 10
  integer :: z(5) = 0              ! all zeros
  real :: m(2,2) = reshape([1,2,3,4], [2,2])

  ! Allocation later
  print *, size(x), lbound(x), ubound(x)   ! 5 1 5
  print *, size(c, dim=1)                  ! 3 (rows)
  print *, size(c, dim=2)                  ! 4 (cols)

  ! Array of characters
  character(10) :: names(3) = ["Alice", "Bob", "Carol"]
  print *, names(2)                        ! Bob
end program array_decl

Sections de tableau & Indices vectoriels

Les sections de tableau (slicing) utilisent la syntaxe a(start:end:stride) — toutes les parties optionnelles. Le pas peut être négatif (inverser). Les indices vectoriels permettent de rassembler/disperser avec un tableau d'indices : a(idx) retourne [a(idx(1)), a(idx(2)), ...]. Les sections peuvent être affectées : a(2:4) = [99,98,97]. La construction 'where' est une affectation conditionnelle au niveau du tableau (comme numpy where). Les opérations de tableau de Fortran sont vectorisées — pas de boucles explicites nécessaires pour les opérations élément par élément. C'est la fonctionnalité tueuse de Fortran pour le code numérique : syntaxe propre, de type mathématique que les compilateurs auto-vectorisent.

fortran
program array_sections
  implicit none
  integer :: a(10) = [(i, i=1,10)]
  integer :: b(5)
  integer :: idx(3) = [2, 5, 7]

  ! Array sections (slicing): a(start:end[:stride])
  print *, a(3:7)         ! 3 4 5 6 7
  print *, a(1:10:2)      ! 1 3 5 7 9 (stride 2)
  print *, a(:5)          ! 1 2 3 4 5 (start defaults to 1)
  print *, a(6:)          ! 6 7 8 9 10 (end defaults to ubound)
  print *, a(::3)         ! 1 4 7 10
  print *, a(10:1:-1)     ! 10 9 8 ... 1 (reverse)

  ! Vector subscript (gather)
  b = a(idx)              ! b = [a(2), a(5), a(7)] = [2, 5, 7]
  print *, b

  ! Assigning to a section
  a(2:4) = [99, 98, 97]
  print *, a(1:5)         ! 1 99 98 97 5

  ! 2D sections
  real :: m(4,4) = 0.0
  m(2:3, 2:3) = 1.0       ! set 2x2 sub-block
  print *, m(2,2), m(3,3) ! 1.0 1.0

  ! Where construct (masked array assignment)
  where (a > 50) a = 0    ! set elements > 50 to 0
end program array_sections

Tableaux multidimensionnels & Matrices

Fortran stocke les tableaux en colonne-major (le premier index varie le plus vite en mémoire) — contrairement à C. reshape remplit dans l'ordre colonne-major, donc reshape([1,2,3,4],[2,2]) donne [[1,3],[2,4]]. matmul(A,B) est la vraie multiplication matricielle (algèbre linéaire) ; A*B est élément par élément (Hadamard) — ils ne sont PAS les mêmes ! transpose(A) retourne la transposée. Réductions : sum, product, maxval, minval, maxloc, minloc, count — toutes prennent en charge dim= pour réduire le long d'un axe. Pour des performances élevées, écrivez les boucles dans l'ordre colonne-major (boucle la plus interne sur le premier index) pour être cache-friendly.

fortran
program matrices
  implicit none
  real :: A(3,3), B(3,3), C(3,3)
  integer :: i, j

  ! Initialize with implied-do
  A = reshape([(real(i), i=1,9)], [3,3])
  ! A = 1 4 7
  !     2 5 8
  !     3 6 9   (column-major fill!)

  ! Identity matrix
  B = 0.0
  do i = 1, 3
    B(i,i) = 1.0
  end do

  ! Matrix multiplication (intrinsic)
  C = matmul(A, B)        ! A * I = A
  print *, C(1,1), C(2,2) ! 1.0 5.0

  ! Transpose
  print *, transpose(A)(1,:)   ! 1 2 3 (first row of A^T)

  ! Element-wise operations
  C = A + B               ! element-wise add
  C = A * 2.0             ! scalar multiply
  C = A * B               ! element-wise (NOT matmul!)

  ! Array reduction along a dimension
  print *, sum(A, dim=1)   ! column sums: 6 15 24
  print *, sum(A, dim=2)   ! row sums: 12 15 18
  print *, maxval(A)       ! 9.0
  print *, maxloc(A)       ! 3 3 (location of max)

  ! Reshape
  integer :: flat(6) = [1,2,3,4,5,6]
  integer :: mat(2,3)
  mat = reshape(flat, [2,3])
end program matrices

Tableaux allouables (Dynamiques)

Les tableaux allocatable sont la méthode moderne pour la mémoire dynamique en Fortran — plus sûrs que les pointeurs (pas de fuites de mémoire, désallocation automatique à la sortie de la portée). Déclarez avec l'attribut allocatable et une forme différée (:, (:,:), etc.). allocate() avec stat= attrape les erreurs (vérifiez toujours !). deallocate() libère explicitement. Fortran 2003+ prend en charge la réallocation automatique à l'affectation : flex = [flex, 4] agrandit le tableau. allocated() vérifie si actuellement alloué. Les allocatables sont préférés aux pointeurs pour les tableaux dynamiques car le compilateur les suit et les libère automatiquement — pas de fuites, pas de pointeurs pendants.

fortran
program alloc_demo
  implicit none
  integer, allocatable :: arr(:), matrix(:,:)
  integer :: n, m, i, stat

  ! Get size from user
  print *, "Enter size:"
  read(*, *) n
  m = n * 2

  ! Allocate
  allocate(arr(n), matrix(n, m), stat=stat)
  if (stat /= 0) then
    print *, "Allocation failed!"
    stop 1
  end if

  ! Use the arrays
  arr = [(i, i=1, n)]
  matrix = 0.0
  do i = 1, n
    matrix(i, :) = i
  end do

  print *, size(arr), size(matrix, dim=2)
  print *, allocated(arr)   ! T

  ! Deallocate (or let it auto-deallocate at scope exit)
  deallocate(arr, matrix)
  print *, allocated(arr)   ! F

  ! Automatic reallocation on assignment (Fortran 2003)
  integer, allocatable :: flex(:)
  flex = [1, 2, 3]          ! auto-allocates to size 3
  flex = [flex, 4, 5]       ! reallocates to size 5: 1 2 3 4 5
  print *, flex
  deallocate(flex)
end program alloc_demo

Fonctions intrinsèques de tableau

Les intrinsèques de tableau de Fortran sont son superpouvoir. Réductions : sum, product, maxval, minval, maxloc (index du max), minloc, count (nombre de true), any (existe), all (tous). Toutes prennent en charge mask= pour la réduction conditionnelle. pack() rassemble les éléments où le masque est true (comme numpy compress) ; unpack() disperse. cshift/eoshift font tourner les tableaux (circulaire vs end-off). merge(a, b, mask) fait la sélection élément par élément. Note : Fortran n'a PAS de tri intégré — vous devez en écrire un (ou utiliser une bibliothèque). Ces intrinsèques sont élémentaires et vectorisables, rendant le code Fortran à la fois propre et rapide.

fortran
program array_funcs
  implicit none
  integer :: a(5) = [3, 1, 4, 1, 5, 9, 2, 6]
  ! Wait, let's fix size
  integer :: b(8) = [3, 1, 4, 1, 5, 9, 2, 6]
  integer :: c(5) = [10, 20, 30, 40, 50]
  logical :: mask(5) = [.true., .false., .true., .false., .true.]

  ! Inquiry
  print *, size(b)        ! 8
  print *, shape(b)       ! 8
  print *, lbound(b), ubound(b)   ! 1 8

  ! Reductions
  print *, sum(b)         ! 31
  print *, product(c)     ! 120000000
  print *, maxval(b), minval(b)   ! 9 1
  print *, maxloc(b)      ! 6 (index of max)
  print *, minloc(b)      ! 2 (index of min, first occurrence)
  print *, count(b > 3)   ! 5 (number of true elements)
  print *, any(b > 8)     ! T (at least one)
  print *, all(b > 0)     ! T (all of them)

  ! With mask
  print *, sum(b, mask=b > 3)     ! sum of elements > 3
  print *, pack(b, b > 3)         ! compact array of elements > 3
  print *, unpack([1,2], mask, 0) ! spread values per mask

  ! Manipulation
  print *, cshift(b, 2)   ! circular shift left by 2
  print *, eoshift(b, 2)  ! end-off shift left by 2 (fills 0)
  print *, merge(b, c, mask)  ! element-wise: b where mask true, else c

  ! Sorting (Fortran 2003+)
  integer :: sorted(8)
  sorted = b
  call sort_array(sorted)   ! custom sort (no built-in sort)
end program array_funcs
04

Chaînes & Gestion de caractères

Déclaration de caractère & Longueur

Les chaînes Fortran sont de LONGUEUR FIXE par défaut — les chaînes plus courtes sont remplies d'espaces pour atteindre la longueur déclarée. character(N) ou character(len=N) déclare une longueur N. character(*) prend la longueur du contexte (initialiseur de paramètre ou argument factice). character(:), allocatable permet les chaînes dynamiques à longueur différée (Fortran 2003+) — la chaîne se réalloue à l'affectation. len() retourne la longueur déclarée ; len_trim() retourne la longueur sans espaces de fin. trim() retourne la chaîne sans espaces de fin (mais le résultat est toujours de longueur fixe dans le contexte). Pour le traitement de texte à longueur variable, utilisez des chaînes allocatable à longueur différée.

fortran
program char_decl
  implicit none
  ! Fixed-length strings
  character(10) :: s1 = "Hello"
  character(len=20) :: s2 = "World"
  character(20) :: s3          ! len= keyword optional

  ! Deferred-length (allocatable) - Fortran 2003+
  character(:), allocatable :: flex
  flex = "Dynamic"             ! len=7
  flex = "Now longer string"   ! reallocates to len=18
  print *, len(flex)           ! 18

  ! Array of strings
  character(15) :: names(3) = ["Alice", "Bob", "Carol"]

  ! Single character
  character(1) :: ch = 'A'
  character :: ch2 = 'B'       ! len=1 default

  print *, s1                  ! "Hello     " (padded to 10)
  print *, trim(s1)            ! "Hello" (no padding)
  print *, len(s1), len_trim(s1)   ! 10 5
  print *, names(2)            ! "Bob" (padded)
end program char_decl

Concaténation de chaînes & Opérations

La concaténation de chaînes utilise l'opérateur //. repeat(s, n) répète une chaîne n fois. Les sous-chaînes utilisent s(start:end) — indexé à 1, INCLUSIF aux deux extrémités (contrairement à Python). s(8:) signifie de la position 8 à la fin ; s(:5) signifie du début à la position 5. index(s, sub) retourne la position de la première occurrence de sub (0 si non trouvée, sensible à la casse). scan(s, set) retourne la position du premier caractère DANS l'ensemble ; verify(s, set) retourne le premier caractère PAS dans l'ensemble. adjustl/adjustr décalent les espaces de début/fin. Les chaînes Fortran ne sont PAS terminées par null comme C — la longueur est suivie séparément.

fortran
program string_ops
  implicit none
  character(20) :: first = "John", last = "Doe"
  character(50) :: full

  ! Concatenation with //
  full = first // " " // last   ! "John Doe"
  print *, trim(full)

  ! Repeat
  print *, repeat("-", 30)      ! 30 dashes

  ! Substring (1-indexed, inclusive)
  character(20) :: s = "Hello, World!"
  print *, s(1:5)               ! "Hello"
  print *, s(8:12)              ! "World"
  print *, s(8:)                ! "World!"  (to end)
  print *, s(:5)                ! "Hello"   (from start)

  ! Index (find substring)
  print *, index(s, "World")    ! 8 (position, 0 if not found)
  print *, index(s, "world")    ! 0 (case-sensitive)
  print *, scan(s, "aeiou")     ! 2 (first vowel position)
  print *, verify(s, "abcdefg") ! 1 (first char NOT in set)

  ! Length
  print *, len_trim(s)          ! 13
  print *, adjustl(s)           ! left-justify
  print *, adjustr(s)           ! right-justify
end program string_ops

Fonctions intrinsèques de chaîne

iachar(c) retourne le code ASCII d'un caractère ; achar(i) est l'inverse. (ichar/char dépendent du processeur — préférez iachar/achar pour la portabilité.) Fortran n'a PAS de conversion de casse intégrée — utilisez iachar/achar manuellement (A-Z est 65-90, a-z est 97-122, la différence est 32). Comparaison lexicographique : lge/lgt/lle/llt (lexicalement supérieur/inférieur à) gèrent les chaînes de longueurs différentes gracieusement. Les E/S internes (read/write vers une chaîne au lieu d'un fichier) sont la méthode idiomatique pour convertir entre chaînes et nombres : read(str, *) num et write(str, fmt) num. Le format I0 donne des entiers à largeur minimale (pas de remplissage).

fortran
program string_funcs
  implicit none
  character(20) :: s = "Hello World"
  integer :: i
  character(1) :: ch

  ! Character code conversion
  print *, iachar('A')          ! 65 (ASCII code)
  print *, achar(66)            ! 'B' (from ASCII code)
  print *, ichar('A')           ! processor-dependent (use iachar for ASCII)

  ! Case conversion (manual - no built-in)
  do i = 1, len_trim(s)
    ch = s(i:i)
    if (ch >= 'A' .and. ch <= 'Z') then
      s(i:i) = achar(iachar(ch) + 32)   ! to lowercase
    end if
  end do
  print *, s                    ! "hello world"

  ! Comparison
  print *, lge("apple", "banana")   ! F (lexicographic >=)
  print *, lgt("zebra", "apple")    ! T
  print *, lle("abc", "abcd")       ! T (<=)
  print *, llt("abc", "abd")        ! T (<)

  ! String to number conversion (internal read)
  character(20) :: num_str = "3.14159"
  real :: pi_val
  read(num_str, *) pi_val
  print *, pi_val * 2           ! 6.28318

  ! Number to string (internal write)
  character(20) :: out_str
  integer :: n = 42
  write(out_str, '(I0)') n      ! I0 = minimal-width integer
  print *, "Number: " // trim(out_str)
end program string_funcs

Formatage de chaînes

Les spécifications de format vivent dans une chaîne : '(I5, F8.2, A)'. I=entier, F=réel à virgule fixe, E=exponentiel, ES=scientifique (mantisse 1-10), A=caractère, X=espace, /=nouvelle ligne. La largeur vient en premier (I5 = largeur 5), puis .m optionnel pour les chiffres min (I5.3). I0 signifie largeur minimale (pas de remplissage). ES donne une vraie notation scientifique (1.23E+6) vs E (0.12E+7). Répétitions : 3I4 = trois entiers chacun de largeur 4. Littéraux de chaîne dans le format : '("text")'. Le format peut être une variable chaîne, un * (list-directed, le compilateur choisit) ou une étiquette (numéro d'instruction). Pour une sortie propre, préférez I0 pour les entiers et F ou ES pour les réels.

fortran
program format_demo
  implicit none
  integer :: n = 42
  real :: pi = 3.14159265
  real :: big = 1234567.89
  character(20) :: name = "Alice"

  ! Format specifiers
  ! Iw     - integer, width w
  ! Iw.m   - integer, width w, at least m digits
  ! Fw.d   - fixed-point real, width w, d decimals
  ! Ew.d   - exponential, width w, d decimals
  ! Aw     - character, width w
  ! A      - character, default width
  ! nX     - n spaces
  ! /      - newline

  write(*, '(I5)') n            ! "   42"
  write(*, '(I5.3)') n          ! "  042"
  write(*, '(I0)') n            ! "42" (minimal)
  write(*, '(F10.4)') pi        ! "    3.1416"
  write(*, '(E12.4)') big       ! " 0.1235E+07"
  write(*, '(ES12.4)') big      ! " 1.2346E+06" (scientific)
  write(*, '(A10)') name        ! "     Alice"
  write(*, '(A, I3)') "n=", n   ! "n= 42"

  ! Multiple items
  write(*, '(A, I3, A, F8.4)') "n=", n, " pi=", pi

  ! Repeated format: 3I4 = three integers width 4
  write(*, '(3I4)') 1, 2, 3     ! "   1   2   3"

  ! Newline and spacing
  write(*, '("Name: ", A, /, "Age: ", I3)') name, n

  ! List-directed (default format)
  print *, name, n, pi
end program format_demo

Analyse & Tokenisation

Fortran n'a PAS de split/tokenize intégré — vous devez l'écrire manuellement en utilisant index() et les sous-chaînes. Le modèle : trouvez le délimiteur avec index, extrayez le jeton avec une sous-chaîne, avancez au-delà du délimiteur, répétez. Pour les paires clé=valeur, trouvez '=' avec index et divisez en clé (avant) et valeur (après). trim() supprime les espaces de fin ; adjustl() supprime les espaces de début. Pour une analyse robuste, gérez aussi les jetons vides et les espaces. Alternativement, utilisez des lectures internes avec des spécificateurs de format pour les données structurées, ou lisez depuis une chaîne comme si c'était un fichier. Des bibliothèques comme split() existent dans certains frameworks Fortran mais ne sont pas standard.

fortran
program parse_demo
  implicit none
  character(100) :: line = "name=Bob,age=30,city=NYC"
  character(50) :: token
  integer :: pos, start, end

  ! Split by comma
  start = 1
  do
    end = index(line(start:), ",")
    if (end == 0) then
      ! last token
      token = line(start:)
      call process(trim(token))
      exit
    end if
    token = line(start:start+end-2)
    call process(trim(token))
    start = start + end
  end do

contains
  subroutine process(t)
    character(*), intent(in) :: t
    integer :: eq_pos
    eq_pos = index(t, "=")
    if (eq_pos > 0) then
      print *, "Key: ", trim(t(:eq_pos-1)), &
               " Value: ", trim(t(eq_pos+1:))
    end if
  end subroutine process
end program parse_demo

! Output:
! Key: name Value: Bob
! Key: age Value: 30
! Key: city Value: NYC
05

Procédures : Fonctions & Sous-routines

Fonctions

Les fonctions retournent une valeur et sont utilisées dans les expressions (comme les fonctions math). Syntaxe moderne : 'function name(args) result(var)' — la variable result est ce qui est retourné. intent(in) marque les arguments en lecture seule (le compilateur l'applique). Les fonctions peuvent retourner des scalaires OU des tableaux (utilisez size() de l'entrée pour dimensionner la sortie). Les fonctions devraient être PURE (pas d'effets de bord) — ne modifiez pas l'état global et ne faites pas d'E/S dans une fonction. Les procédures internes (dans le bloc 'contains') ont accès aux variables de l'hôte (association hôte). Pour les procédures externes, utilisez un bloc interface pour spécifier la signature.

fortran
program func_demo
  implicit none
  ! Function declared in interface or as external
  print *, add(3, 4)         ! 7
  print *, square(5)         ! 25
  print *, distance(0.0, 0.0, 3.0, 4.0)   ! 5.0

contains
  ! Basic function with result clause
  function add(a, b) result(c)
    integer, intent(in) :: a, b
    integer :: c
    c = a + b
  end function add

  ! Function with result named same as function (legacy)
  function square(x) result(y)
    integer, intent(in) :: x
    integer :: y
    y = x * x
  end function square

  ! Real function
  function distance(x1, y1, x2, y2) result(d)
    real, intent(in) :: x1, y1, x2, y2
    real :: d
    d = sqrt((x2-x1)**2 + (y2-y1)**2)
  end function distance

  ! Array-valued function
  function reverse(arr) result(rev)
    integer, intent(in) :: arr(:)
    integer :: rev(size(arr))
    integer :: i, n
    n = size(arr)
    do i = 1, n
      rev(i) = arr(n-i+1)
    end do
  end function reverse
end program func_demo

Sous-routines & Intent

Les sous-routines sont appelées avec 'call' et ne retournent pas de valeur — elles modifient les arguments sur place. Utilisez des sous-routines lorsque : (1) vous devez modifier plusieurs arguments, (2) l'opération est une 'commande' pas un 'calcul', (3) retourner un résultat de type tableau est maladroit. Les attributs intent documentent et appliquent la direction de l'argument : intent(in) = lecture seule (erreur de compilation si vous affectez), intent(out) = écriture seule (non défini à l'entrée, doit être défini avant le retour), intent(inout) = lecture-écriture. Spécifiez toujours intent — cela détecte les bugs et permet l'optimisation. Les sous-routines peuvent modifier les tableaux qui leur sont passés (pas de copie faite s'ils sont contigus).

fortran
program sub_demo
  implicit none
  integer :: x = 10, y = 20
  real :: arr(5) = [1.0, 2.0, 3.0, 4.0, 5.0]

  ! Subroutines are called with 'call'
  call swap(x, y)
  print *, x, y              ! 20 10

  call scale_array(arr, 2.0)
  print *, arr               ! 2 4 6 8 10

  call fill_zero(arr)
  print *, arr               ! 0 0 0 0 0

contains
  subroutine swap(a, b)
    integer, intent(inout) :: a, b   ! read AND write
    integer :: tmp
    tmp = a
    a = b
    b = tmp
  end subroutine swap

  subroutine scale_array(a, factor)
    real, intent(inout) :: a(:)
    real, intent(in) :: factor
    a = a * factor           ! whole-array operation
  end subroutine scale_array

  subroutine fill_zero(a)
    real, intent(out) :: a(:)        ! write-only (output)
    a = 0.0
  end subroutine fill_zero
end program sub_demo

Fonctions pures & élémentaires

Les fonctions pure n'ont PAS d'effets de bord : pas d'E/S, pas de modification de variable globale, pas de stop, ne peuvent appeler que d'autres procédures pure. Elles permettent l'optimisation du compilateur (parallélisation, élimination des sous-expressions communes) et sont requises dans certains contextes (par ex., DO CONCURRENT). Les fonctions élémentaires sont écrites pour des SCALAIRES mais fonctionnent automatiquement sur les tableaux élément par élément — écrivez une fois, utilisez pour les deux. pure elemental combine les deux. Utilisez pure pour toute fonction qui est vraiment une fonction math (pas d'effets de bord). Utilisez elemental lorsque l'opération s'applique naturellement élément par élément aux tableaux (fonctions math, conversions). Le compilateur peut auto-vectoriser les appels élémentaires sur les tableaux.

fortran
program pure_demo
  implicit none
  real :: a(5) = [1.0, 2.0, 3.0, 4.0, 5.0]
  real :: b(5)
  integer :: i

  ! Pure function: no side effects, no I/O
  b = square_arr(a)
  print *, b                 ! 1 4 9 16 25

  ! Elemental function: works on scalars AND arrays automatically
  b = cube(a)                ! applies cube() element-wise
  print *, b                 ! 1 8 27 64 125

  print *, cube(2.0)         ! also works on scalar: 8.0

contains
  ! Pure: no side effects, no I/O, no stop, only pure calls
  pure function square_arr(x) result(y)
    real, intent(in) :: x(:)
    real :: y(size(x))
    y = x * x
  end function square_arr

  ! Elemental: scalar signature, but works on arrays too
  elemental function cube(x) result(y)
    real, intent(in) :: x
    real :: y
    y = x * x * x
  end function cube

  ! Pure elemental: both
  pure elemental double precision function sq(x) result(y)
    double precision, intent(in) :: x
    double precision :: y
    y = x * x
  end function sq
end program pure_demo

Arguments optionnels & par mot-clé

Les arguments optionnels permettent aux appelants de les omettre. Utilisez present(arg) à l'intérieur de la procédure pour vérifier si un argument a été fourni — accéder à un optionnel absent est un comportement indéfini. Les arguments par mot-clé (name="value") permettent de passer les arguments dans n'importe quel ordre et rendent les appels auto-documentés. Une fois que vous utilisez un mot-clé, tous les arguments suivants doivent aussi utiliser des mots-clés. Les arguments optionnels doivent venir après tous les arguments requis dans la signature. Les valeurs par défaut sont implémentées via des vérifications present() (Fortran n'a pas de syntaxe de défaut intégrée). Mot-clé + optionnel ensemble permettent des API flexibles : les appelants spécifient uniquement ce dont ils ont besoin.

fortran
program optional_demo
  implicit none
  ! All arguments after the first optional must also be optional
  print *, greet("Alice")                    ! Hello, Alice!
  print *, greet("Bob", "Hi")                ! Hi, Bob!
  print *, greet("Carol", greeting="Hey")    ! keyword argument
  print *, greet(greeting="Welcome", name="Dave")  ! all keywords

  ! With present() check
  call log_msg("Starting up")
  call log_msg("Error!", level=2)
  call log_msg("Debug info", level=0, file="debug.log")

contains
  function greet(name, greeting) result(msg)
    character(*), intent(in) :: name
    character(*), intent(in), optional :: greeting
    character(50) :: msg
    character(20) :: g

    if (present(greeting)) then
      g = greeting
    else
      g = "Hello"
    end if
    msg = trim(g) // ", " // name // "!"
  end function greet

  subroutine log_msg(message, level, file)
    character(*), intent(in) :: message
    integer, intent(in), optional :: level
    character(*), intent(in), optional :: file
    integer :: lvl
    lvl = 1
    if (present(level)) lvl = level
    print *, "[L", lvl, "] ", trim(message)
  end subroutine log_msg
end program optional_demo

Procédures internes & récursives

Les procédures internes (à l'intérieur de 'contains') ont une association hôte — elles peuvent lire ET modifier les variables du programme hôte (comme des fermetures). Utilisez-les pour les assistants qui ont besoin de l'état hôte. Les procédures récursives doivent être déclarées avec le préfixe 'recursive' (Fortran 90/2003) ; Fortran 2018 fait de la récursivité le défaut. Pour la récursivité mutuelle, utilisez un bloc interface pour déclarer la référence forward. La récursivité est élégante mais peut être lente (surcoût d'appel de fonction) et risquée (débordement de pile pour la récursivité profonde). Pour factorielle/fibonacci, les versions itératives sont plus rapides et plus sûres. Utilisez la récursivité pour les problèmes naturellement récursifs (traversal d'arbre, diviser-pour-régner) avec profondeur bornée.

fortran
program nested_demo
  implicit none
  integer :: counter = 0   ! host variable

  ! Internal procedures (in contains) access host variables
  call increment
  call increment
  print *, counter          ! 2

  ! Recursive function
  print *, factorial(5)     ! 120
  print *, fib(10)          ! 55

  ! Mutual recursion (needs forward declaration)
  print *, is_even(4)       ! T

contains
  subroutine increment
    counter = counter + 1   ! modifies host's counter
  end subroutine increment

  recursive function factorial(n) result(f)
    integer, intent(in) :: n
    integer :: f
    if (n <= 1) then
      f = 1
    else
      f = n * factorial(n-1)
    end if
  end function factorial

  recursive function fib(n) result(f)
    integer, intent(in) :: n
    integer :: f
    if (n < 2) then
      f = n
    else
      f = fib(n-1) + fib(n-2)
    end if
  end function fib

  ! Mutual recursion with interface
  recursive function is_even(n) result(r)
    integer, intent(in) :: n
    logical :: r
    interface
      recursive function is_odd(m) result(ro)
        integer, intent(in) :: m
        logical :: ro
      end function is_odd
    end interface
    if (n == 0) then
      r = .true.
    else
      r = is_odd(n-1)
    end if
  end function is_even
end program nested_demo
06

Modules & Encapsulation

Bases des modules & Use

Les modules sont le mécanisme d'encapsulation principal de Fortran (remplaçant les common blocks et procédures externes). Un fichier module contient : (1) des déclarations (constantes, variables, types dérivés), (2) un bloc 'contains' avec des procédures. Utilisez 'use module_name' pour importer ; 'use module_name, only: x, y' importe uniquement des entités spécifiques (recommandé — évite la pollution d'espace de noms). Les variables de module sont persistantes (statiques) et partagées entre toutes les procédures qui utilisent le module. Les modules fournissent des interfaces explicites (le compilateur vérifie les types d'arguments), contrairement aux procédures externes. Compilez toujours les fichiers module avant les fichiers qui les utilisent. 'implicit none' dans un module se propage à toutes ses procédures.

fortran
! geometry.f90 - module file
module geometry
  implicit none
  private                      ! default: everything private
  public :: circle_area, circle_perimeter, PI

  ! Module-level constants (persistent)
  real, parameter :: PI = 3.14159265

contains
  function circle_area(r) result(a)
    real, intent(in) :: r
    real :: a
    a = PI * r * r
  end function circle_area

  function circle_perimeter(r) result(p)
    real, intent(in) :: r
    real :: p
    p = 2.0 * PI * r
  end function circle_perimeter
end module geometry

! main.f90 - using the module
program use_module
  use geometry, only: circle_area, circle_perimeter, PI
  implicit none
  real :: r = 5.0
  print *, "Area: ", circle_area(r)         ! 78.5398
  print *, "Perimeter: ", circle_perimeter(r) ! 31.4159
  print *, "PI: ", PI
end program use_module

! Compile: gfortran geometry.f90 main.f90 -o main

Contrôle d'accès (Public/Private)

Contrôle d'accès : 'private' rend les entités internes au module ; 'public' les exporte. Le défaut peut être défini au niveau du module ('private' puis sélectivement 'public :: ...') — c'est la meilleure pratique (interface explicite). Les composants de type dérivé peuvent être private même si le type lui-même est public — les appelants peuvent utiliser le type mais ne pas accéder aux internes directement ; ils doivent passer par des procédures. 'save' rend les variables de module persistantes (elles conservent leurs valeurs entre les appels) — les variables de module sont sauvegardées par défaut. 'final' définit un destructeur (appelé lorsque l'objet sort de la portée). Cette encapsulation permet la vraie POO avec des invariants appliqués via des procédures.

fortran
module bank_account
  implicit none
  private                       ! default: everything private
  ! Explicitly export:
  public :: account_t, deposit, withdraw, get_balance

  ! Derived type - can expose type but hide internals
  type :: account_t
    private                    ! components are private
    real :: balance = 0.0
    integer :: id = 0
  contains
    procedure :: balance => get_bal   ! type-bound procedure
    final :: cleanup                  ! destructor
  end type account_t

  ! Module-level counter (private, not exported)
  integer, save :: next_id = 1000

contains
  ! Constructor (factory function)
  function create_account(initial) result(acc)
    type(account_t) :: acc
    real, intent(in) :: initial
    acc%balance = initial
    acc%id = next_id
    next_id = next_id + 1
  end function create_account

  subroutine deposit(acc, amount)
    type(account_t), intent(inout) :: acc
    real, intent(in) :: amount
    acc%balance = acc%balance + amount
  end subroutine deposit

  function get_bal(acc) result(b)
    class(account_t), intent(in) :: acc
    real :: b
    b = acc%balance
  end function get_bal

  subroutine cleanup(acc)
    type(account_t) :: acc
    ! cleanup code (e.g., log closure)
  end subroutine cleanup
end module bank_account

Types dérivés dans les modules

Les types dérivés définis dans les modules peuvent avoir : des procédures liées au type (procedure :: name => impl), des constructeurs (via une interface surchargée avec le nom du type) et des composants allocatable. Le 'class(keyword)' dans les procédures liées au type permet le polymorphisme (le type réel peut être une sous-classe). Les procédures liées au type sont appelées comme obj%method(args) — syntaxe POO. La surcharge du nom du type comme interface vous permet d'avoir plusieurs constructeurs (vector_from_array, vector_from_size). Les composants allocatable sont automatiquement alloués/désalloués. C'est la POO Fortran moderne : encapsulation, méthodes, constructeurs et polymorphisme, tous dans le système de modules.

fortran
module vector_mod
  implicit none
  private
  public :: vector_t, vector_add, vector_scale

  ! Derived type with type parameters (Fortran 2003)
  type :: vector_t
    real, allocatable :: data(:)
    integer :: length = 0
  contains
    procedure :: norm => vector_norm
    procedure :: print => vector_print
  end type vector_t

  ! Constructor interface (overloaded)
  interface vector_t
    procedure vector_from_array
    procedure vector_from_size
  end interface vector_t

contains
  function vector_from_array(arr) result(v)
    real, intent(in) :: arr(:)
    type(vector_t) :: v
    v%data = arr
    v%length = size(arr)
  end function

  function vector_from_size(n, fill) result(v)
    integer, intent(in) :: n
    real, intent(in) :: fill
    type(vector_t) :: v
    allocate(v%data(n))
    v%data = fill
    v%length = n
  end function

  function vector_norm(self) result(n)
    class(vector_t), intent(in) :: self
    real :: n
    n = sqrt(sum(self%data**2))
  end function

  subroutine vector_print(self)
    class(vector_t), intent(in) :: self
    print *, "Vector(len=", self%length, "): ", self%data
  end subroutine

  function vector_add(a, b) result(c)
    type(vector_t), intent(in) :: a, b
    type(vector_t) :: c
    c%data = a%data + b%data
    c%length = a%length
  end function

  function vector_scale(a, s) result(c)
    type(vector_t), intent(in) :: a
    real, intent(in) :: s
    type(vector_t) :: c
    c%data = a%data * s
    c%length = a%length
  end function
end module vector_mod

Procédures génériques & Surcharge

Les interfaces génériques fournissent un polymorphisme ad-hoc (surcharge) : un nom dispatche vers différentes procédures spécifiques selon les types d'arguments. Le bloc 'interface name / module procedure proc1, proc2 / end interface' liste toutes les procédures spécifiques. Le compilateur choisit celle qui correspond par type/rang d'argument à la compilation. Toutes les procédures spécifiques dans un générique doivent avoir des signatures DISTINCTES (distinguables par les types d'arguments) — sinon ambiguïté. C'est ainsi que Fortran fait la surcharge d'opérateur/fonction sans templates. Le dispatch générique est résolu à la compilation (pas de surcoût à l'exécution). Utilisez les génériques pour fournir une API uniforme entre les types.

fortran
module generics_mod
  implicit none
  private
  public :: print_value, add

  ! Generic interface: one name, multiple specific procedures
  interface print_value
    module procedure print_int
    module procedure print_real
    module procedure print_str
    module procedure print_int_array
  end interface print_value

  interface add
    module procedure add_int
    module procedure add_real
    module procedure add_arrays
  end interface add

contains
  subroutine print_int(x)
    integer, intent(in) :: x
    print *, "Integer: ", x
  end subroutine

  subroutine print_real(x)
    real, intent(in) :: x
    print *, "Real: ", x
  end subroutine

  subroutine print_str(s)
    character(*), intent(in) :: s
    print *, "String: ", trim(s)
  end subroutine

  subroutine print_int_array(arr)
    integer, intent(in) :: arr(:)
    print *, "Array: ", arr
  end subroutine

  function add_int(a, b) result(c)
    integer, intent(in) :: a, b
    integer :: c
    c = a + b
  end function

  function add_real(a, b) result(c)
    real, intent(in) :: a, b
    real :: c
    c = a + b
  end function

  function add_arrays(a, b) result(c)
    real, intent(in) :: a(:), b(:)
    real :: c(size(a))
    c = a + b
  end function
end module generics_mod

program use_generics
  use generics_mod
  implicit none
  call print_value(42)              ! Integer: 42
  call print_value(3.14)            ! Real: 3.14
  call print_value("Hello")         ! String: Hello
  call print_value([1,2,3])         ! Array: 1 2 3
  print *, add(2, 3)                ! 5
  print *, add(2.5, 3.5)            ! 6.0
end program use_generics

Surcharge d'opérateurs

La surcharge d'opérateurs vous permet de définir comment +, -, *, /, ==, etc. fonctionnent sur vos types dérivés. interface operator(+) / module procedure vec_add / end interface lie l'opérateur + à une fonction. Pour les opérateurs binaires, vous pouvez surcharger les deux ordres (vec*scalaire et scalaire*vec) avec des procédures séparées. assignment(=) surcharge l'opérateur d'affectation (la procédure est une sous-routine avec intent(out) LHS et intent(in) RHS). Cela permet une syntaxe de type math : c = a + b au lieu de c = vec_add(a, b). Utilisez la surcharge d'opérateurs pour les types mathématiques (vecteurs, matrices, nombres complexes) où elle améliore la lisibilité. Évitez la surcharge pour des sémantiques non évidentes. Le constructeur de structure vec3(x,y,z) est intégré pour les types dérivés.

fortran
module vec_ops
  implicit none
  private
  public :: vec3, operator(+), operator(*), assignment(=)

  type :: vec3
    real :: x, y, z
  end type vec3

  ! Overload the + operator for vec3 + vec3
  interface operator(+)
    module procedure vec_add
  end interface

  ! Overload * for vec3 * scalar and scalar * vec3
  interface operator(*)
    module procedure vec_scale_r
    module procedure vec_scale_l
    module procedure vec_dot
  end interface

  ! Overload = for array-to-vec assignment
  interface assignment(=)
    module procedure arr_to_vec
  end interface

contains
  function vec_add(a, b) result(c)
    type(vec3), intent(in) :: a, b
    type(vec3) :: c
    c = vec3(a%x+b%x, a%y+b%y, a%z+b%z)
  end function

  function vec_scale_r(v, s) result(r)
    type(vec3), intent(in) :: v
    real, intent(in) :: s
    type(vec3) :: r
    r = vec3(v%x*s, v%y*s, v%z*s)
  end function

  function vec_scale_l(s, v) result(r)
    real, intent(in) :: s
    type(vec3), intent(in) :: v
    type(vec3) :: r
    r = vec3(v%x*s, v%y*s, v%z*s)
  end function

  ! vec3 * vec3 = dot product (scalar)
  function vec_dot(a, b) result(d)
    type(vec3), intent(in) :: a, b
    real :: d
    d = a%x*b%x + a%y*b%y + a%z*b%z
  end function

  subroutine arr_to_vec(v, arr)
    type(vec3), intent(out) :: v
    real, intent(in) :: arr(3)
    v = vec3(arr(1), arr(2), arr(3))
  end subroutine
end module vec_ops

program use_ops
  use vec_ops
  implicit none
  type(vec3) :: a, b, c
  a = vec3(1.0, 2.0, 3.0)
  b = vec3(4.0, 5.0, 6.0)
  c = a + b              ! vec3 addition
  print *, c%x, c%y, c%z ! 5 7 9
  print *, a * 2.0       ! scale: 2 4 6
  print *, 3.0 * b       ! scale: 12 15 18
  print *, a * b         ! dot product: 32
end program use_ops
07

Types dérivés (Structs) & POO

Définition de types dérivés

Les types dérivés sont les structs de Fortran (types composites définis par l'utilisateur). Définissez avec 'type :: Name ... end type Name'. Composants accédés avec % (PAS . — c'est pour les nombres complexes). Constructeur de structure : Name(val1, val2) crée une instance. Les composants peuvent avoir des valeurs par défaut (= value dans la déclaration). L'affectation de type entier copie tous les composants (copie profonde pour les composants allocatable). Les tableaux de types dérivés sont pris en charge. Les types dérivés sont le fondement de la POO en Fortran (avec procédures liées au type, héritage, polymorphisme). Utilisez % pour l'accès aux composants : obj%field, obj%method(). La syntaxe de constructeur Name(args) est automatique sauf si vous la surchargez avec une interface.

fortran
program derived_types
  implicit none
  ! Basic derived type (struct)
  type :: Point
    real :: x, y
  end type Point

  ! Type with default initialization
  type :: Person
    character(20) :: name = "Unknown"
    integer :: age = 0
    logical :: active = .true.
  end type Person

  ! Declare and construct
  type(Point) :: p1, p2
  type(Person) :: alice, bob

  ! Structure constructor
  p1 = Point(3.0, 4.0)
  p2 = Point(0.0, 0.0)
  alice = Person("Alice", 30, .true.)
  bob = Person("Bob", 25)        ! uses default for 'active'

  ! Access components with %
  print *, p1%x, p1%y            ! 3.0 4.0
  print *, alice%name, alice%age ! Alice 30
  print *, bob%active            ! T (default)

  ! Modify components
  p1%x = p1%x + 1.0
  alice%age = 31

  ! Whole-type assignment (component-wise copy)
  p2 = p1
  print *, p2%x                  ! 4.0

  ! Array of derived types
  type(Point) :: points(3)
  points(1) = Point(1.0, 1.0)
  points(2) = Point(2.0, 2.0)
  points(3) = Point(3.0, 3.0)
  print *, points(2)%y           ! 2.0
end program derived_types

Composants de type & Constructeurs

Les types dérivés peuvent avoir : des composants allocatable (mémoire auto-gérée), des composants initialisés par défaut, des procédures liées au type (méthodes) et des finaliseurs (destructeurs). L''interface TypeName / module procedure custom_init / end interface' surcharge le constructeur de structure avec une fonction de fabrique personnalisée. 'class(ClassName)' (vs 'type(ClassName)') dans les procédures liées au type permet le polymorphisme (le type réel peut être une sous-classe). Les procédures 'final' s'exécutent lorsqu'un objet sort de la portée (destructeur) — utilisez-les pour libérer les ressources. Les composants allocatable sont automatiquement désalloués à la finalisation, mais les finaliseurs explicites sont plus clairs pour le nettoyage complexe. Les constructions block permettent de déclarer des variables en milieu de code (Fortran 2008).

fortran
program type_components
  implicit none
  ! Type with various component kinds
  type :: Student
    integer :: id
    character(20) :: name
    real, allocatable :: grades(:)    ! allocatable component
    integer :: num_grades = 0
  contains
    procedure :: add_grade
    procedure :: average => student_avg
    final :: student_finalize
  end type Student

  ! Overloaded constructor
  interface Student
    module procedure student_init
  end interface Student

  type(Student) :: s

  ! Use custom constructor
  s = Student(101, "Alice")
  call s%add_grade(85.0)
  call s%add_grade(92.0)
  call s%add_grade(78.0)
  print *, s%average()    ! 85.0

contains
  function student_init(id, name) result(s)
    integer, intent(in) :: id
    character(*), intent(in) :: name
    type(Student) :: s
    s%id = id
    s%name = name
    s%num_grades = 0
  end function

  subroutine add_grade(self, g)
    class(Student), intent(inout) :: self
    real, intent(in) :: g
    integer :: n
    n = self%num_grades
    if (n == 0) then
      allocate(self%grades(1))
    else
      ! grow array
      block
        real, allocatable :: tmp(:)
        tmp = self%grades
        deallocate(self%grades)
        allocate(self%grades(n+1))
        self%grades(1:n) = tmp
      end block
    end if
    self%num_grades = n + 1
    self%grades(n+1) = g
  end subroutine

  function student_avg(self) result(avg)
    class(Student), intent(in) :: self
    real :: avg
    if (self%num_grades > 0) then
      avg = sum(self%grades) / self%num_grades
    else
      avg = 0.0
    end if
  end function

  subroutine student_finalize(self)
    type(Student) :: self
    if (allocated(self%grades)) deallocate(self%grades)
  end subroutine
end program type_components

Procédures liées au type (Méthodes)

Les procédures liées au type sont les méthodes de Fortran : 'procedure :: method_name => implementation'. Appelez-les comme obj%method(args) — syntaxe POO. Le premier argument est 'self' (l'objet), déclaré comme 'class(TypeName)' (polymorphe) ou 'type(TypeName)' (concret). 'class' permet l'héritage/polymorphisme ; 'type' est pour les types non extensibles. Le '=> implementation' mappe le nom de méthode à une procédure spécifique (permet le renommage). final est le destructeur. Cet exemple implémente une pile dynamique avec un tableau à croissance automatique. Les procédures liées au type donnent la vraie POO : encapsulation (données + méthodes ensemble), syntaxe de passage de message (obj%method) et polymorphisme (via class). Utilisez toujours class() pour les procédures liées au type pour permettre un futur héritage.

fortran
module stack_mod
  implicit none
  private
  public :: stack_t

  type :: stack_t
    integer, allocatable :: data(:)
    integer :: top = 0
    integer :: capacity = 0
  contains
    procedure :: push => stack_push
    procedure :: pop => stack_pop
    procedure :: peek => stack_peek
    procedure :: is_empty => stack_is_empty
    procedure :: size => stack_size
    procedure :: clear => stack_clear
    final :: stack_finalize
  end type stack_t

  interface stack_t
    module procedure stack_init
  end interface

contains
  function stack_init(initial_cap) result(s)
    integer, intent(in), optional :: initial_cap
    type(stack_t) :: s
    integer :: cap
    cap = 16
    if (present(initial_cap)) cap = initial_cap
    allocate(s%data(cap))
    s%capacity = cap
    s%top = 0
  end function

  subroutine stack_push(self, val)
    class(stack_t), intent(inout) :: self
    integer, intent(in) :: val
    if (self%top >= self%capacity) then
      ! grow
      block
        integer, allocatable :: tmp(:)
        tmp = self%data
        deallocate(self%data)
        allocate(self%data(self%capacity * 2))
        self%data(1:self%capacity) = tmp
        self%capacity = self%capacity * 2
      end block
    end if
    self%top = self%top + 1
    self%data(self%top) = val
  end subroutine

  function stack_pop(self) result(val)
    class(stack_t), intent(inout) :: self
    integer :: val
    if (self%top == 0) stop "Stack underflow"
    val = self%data(self%top)
    self%top = self%top - 1
  end function

  function stack_peek(self) result(val)
    class(stack_t), intent(in) :: self
    integer :: val
    val = self%data(self%top)
  end function

  logical function stack_is_empty(self) result(r)
    class(stack_t), intent(in) :: self
    r = (self%top == 0)
  end function

  integer function stack_size(self) result(n)
    class(stack_t), intent(in) :: self
    n = self%top
  end function

  subroutine stack_clear(self)
    class(stack_t), intent(inout) :: self
    self%top = 0
  end subroutine

  subroutine stack_finalize(self)
    type(stack_t) :: self
    if (allocated(self%data)) deallocate(self%data)
  end subroutine
end module stack_mod

program use_stack
  use stack_mod
  implicit none
  type(stack_t) :: s
  s = stack_t(8)         ! initial capacity 8
  call s%push(10)
  call s%push(20)
  call s%push(30)
  print *, s%size()      ! 3
  print *, s%pop()       ! 30
  print *, s%pop()       ! 20
  print *, s%is_empty()  ! F
end program use_stack

Héritage & Polymorphisme

POO Fortran : 'type, extends(Parent) :: Child' crée une sous-classe (héritage). 'type, abstract :: Name' avec 'procedure(...), deferred :: method' définit une base abstraite (comme une classe abstraite Java / virtuelle pure C++). 'class(Base)' est polymorphe — peut contenir n'importe quelle sous-classe. Dispatch polymorphe : appeler obj%method() invoque la surcharge de la sous-classe. 'select type (var => expr) / type is (ConcreteType) / end select' fait la vérification de type à l'exécution (downcasting). Allouez avec 'allocate(TypeName::var)' pour créer un objet polymorphe d'un type concret spécifique. C'est la pleine POO : héritage, polymorphisme, types abstraits et dispatch à l'exécution — comparable à Java/C++.

fortran
module shapes
  implicit none
  private
  public :: shape, circle, rectangle, shape_ptr

  ! Base type (abstract)
  type, abstract :: shape
    character(20) :: name
  contains
    procedure(area_if), deferred :: area
    procedure(describe_if), deferred :: describe
    procedure :: get_name => shape_get_name
  end type shape

  ! Abstract interface (must be implemented by subclasses)
  abstract interface
    function area_if(self) result(a)
      import :: shape
      class(shape), intent(in) :: self
      real :: a
    end function area_if
    subroutine describe_if(self)
      import :: shape
      class(shape), intent(in) :: self
    end subroutine describe_if
  end interface

  ! Derived type: circle extends shape
  type, extends(shape) :: circle
    real :: radius
  contains
    procedure :: area => circle_area
    procedure :: describe => circle_describe
  end type circle

  ! Derived type: rectangle extends shape
  type, extends(shape) :: rectangle
    real :: width, height
  contains
    procedure :: area => rect_area
    procedure :: describe => rect_describe
  end type rectangle

  ! Class pointer for polymorphism
  type :: shape_ptr
    class(shape), allocatable :: ptr
  end type shape_ptr

contains
  function shape_get_name(self) result(n)
    class(shape), intent(in) :: self
    character(20) :: n
    n = self%name
  end function

  function circle_area(self) result(a)
    class(circle), intent(in) :: self
    real :: a
    a = 3.14159265 * self%radius**2
  end function

  subroutine circle_describe(self)
    class(circle), intent(in) :: self
    print *, "Circle '", trim(self%name), "' r=", self%radius
  end subroutine

  function rect_area(self) result(a)
    class(rectangle), intent(in) :: self
    real :: a
    a = self%width * self%height
  end function

  function rect_describe(self) result(s)
    class(rectangle), intent(in) :: self
    character(50) :: s
    write(s, '("Rectangle ", F6.2, "x", F6.2)') self%width, self%height
  end function
end module shapes

program use_polymorphism
  use shapes
  implicit none
  type(shape_ptr) :: shapes_arr(2)
  ! Allocate concrete types into polymorphic container
  allocate(circle::shapes_arr(1)%ptr)
  select type(s => shapes_arr(1)%ptr)
  type is (circle)
    s%radius = 5.0
    s%name = "C1"
  end select

  allocate(rectangle::shapes_arr(2)%ptr)
  select type(s => shapes_arr(2)%ptr)
  type is (rectangle)
    s%width = 3.0
    s%height = 4.0
    s%name = "R1"
  end select

  ! Polymorphic dispatch
  block
    integer :: i
    do i = 1, 2
      call shapes_arr(i)%ptr%describe
      print *, "Area: ", shapes_arr(i)%ptr%area()
    end do
  end block
end program use_polymorphism

Types imbriqués & Tableaux de types

Les types dérivés peuvent être imbriqués (composition) : un type peut avoir des composants d'autres types dérivés. Accédez aux composants imbriqués avec des % chaînés : emp%home%city. Les constructeurs de structure s'imbriquent naturellement : Employee(id, name, Address(...), salary). Les tableaux de types dérivés sont pris en charge : type(Employee) :: emps(N). Vous pouvez extraire un tableau d'un seul composant : emps(:)%id donne un tableau d'entiers. Les composants allocatable permettent des collections à taille dynamique (par ex., un département avec un nombre variable d'employés). Ce modèle de composition est le fondement pour construire des structures de données complexes (arbres, graphes, listes) en Fortran. Utilisez la composition (has-a) plutôt que l'héritage (is-a) lorsqu'il n'y a pas de relation de sous-type claire.

fortran
program nested_types
  implicit none
  ! Nested derived types
  type :: Address
    character(50) :: street
    character(30) :: city
    character(10) :: zip
  end type Address

  type :: Employee
    integer :: id
    character(30) :: name
    type(Address) :: home      ! nested type
    real :: salary
  end type Employee

  type :: Department
    character(30) :: name
    type(Employee), allocatable :: employees(:)   ! array of types
    integer :: count = 0
  end type Department

  ! Construct with nested structure constructor
  type(Employee) :: emp
  emp = Employee(101, "Alice", &
        Address("123 Main St", "Springfield", "12345"), 75000.0)

  ! Access nested components
  print *, emp%name                      ! Alice
  print *, emp%home%city                 ! Springfield
  print *, emp%home%zip                  ! 12345

  ! Department with array of employees
  type(Department) :: dept
  dept%name = "Engineering"
  allocate(dept%employees(3))
  dept%employees(1) = emp
  dept%employees(2) = Employee(102, "Bob", &
                        Address("456 Oak Ave", "Shelbyville", "54321"), 68000.0)
  dept%count = 2

  ! Iterate over array of types
  block
    integer :: i
    do i = 1, dept%count
      print *, dept%employees(i)%id, trim(dept%employees(i)%name), &
               dept%employees(i)%home%city
    end do
  end block

  ! Array of derived type components
  print *, dept%employees(1:2)%id        ! array of id field
end program nested_types
08

E/S Fichiers & Formatage

Ouverture & Fermeture de fichiers

open() connecte un fichier à un numéro d'unité. newunit=u laisse le compilateur choisir une unité unique (évite les conflits) — préférez toujours cela aux numéros d'unité codés en dur. status : 'old' (le fichier doit exister), 'new' (ne doit pas exister), 'replace' (supprimer + créer), 'scratch' (temporaire, auto-supprimé à la fermeture). action : 'read', 'write', 'readwrite'. position : 'rewind' (début), 'append' (fin), 'asis' (où que ce soit). Vérifiez toujours iostat après open et read — non nul signifie erreur (négatif = EOF, positif = erreur). iomsg donne un message d'erreur descriptif. close() déconnecte. Pour une gestion robuste des fichiers, vérifiez toujours iostat et gérez les erreurs gracieusement.

fortran
program file_open
  implicit none
  integer :: u, ios
  character(100) :: msg

  ! newunit: compiler picks a unique unit number (Fortran 2008)
  ! status: 'old' (must exist), 'new' (must not exist),
  !         'replace' (overwrite), 'scratch' (temporary)
  ! action: 'read', 'write', 'readwrite'
  ! position: 'rewind', 'append', 'asis'
  open(newunit=u, file="data.txt", status="replace", &
       action="write", iostat=ios, iomsg=msg)
  if (ios /= 0) then
    print *, "Open failed: ", trim(msg)
    stop 1
  end if

  write(u, *) "First line"
  write(u, *) "Second line"
  close(u)

  ! Append to existing file
  open(newunit=u, file="data.txt", status="old", &
       action="write", position="append", iostat=ios)
  if (ios == 0) then
    write(u, *) "Appended line"
    close(u)
  end if

  ! Scratch file (auto-deleted on close)
  open(newunit=u, status="scratch", action="readwrite")
  write(u, *) "Temporary data"
  rewind(u)
  ! read back...
  close(u)   ! file disappears

  ! Reading with end-of-file detection
  open(newunit=u, file="data.txt", status="old", action="read")
  do
    read(u, '(A)', iostat=ios) msg
    if (ios /= 0) exit   ! EOF or error
    print *, trim(msg)
  end do
  close(u)
end program file_open

E/S formatées

Les spécificateurs de format contrôlent les E/S : '(A, I0, F8.2)'. A=caractère, I0=entier largeur minimale, F8.2=réel largeur 8 avec 2 décimales. write(unit, fmt) écrit ; read(unit, fmt) lit. unit=* signifie stdout/stdin. Pour les fichiers, utilisez l'unité de open(). Les E/S list-directed (*) sont flexibles : read(u, *) a, b, c lit des valeurs séparées par des virgules/espaces automatiquement. Pour le CSV, la lecture list-directed fonctionne si les valeurs sont séparées par des virgules. Les chaînes de format peuvent être réutilisées : '(3I4)' applique I4 trois fois. Faites toujours correspondre le format au type de données — les formats non correspondants causent des erreurs d'exécution. Pour le texte mixte + nombres, lisez comme une chaîne puis analysez, ou utilisez des formats explicites.

fortran
program formatted_io
  implicit none
  integer :: u, n = 42
  real :: pi = 3.14159265
  character(20) :: name = "Alice"

  ! Write formatted data to file
  open(newunit=u, file="output.txt", status="replace")
  write(u, '(A, I0)') "Count: ", n
  write(u, '(A, F8.4)') "Pi: ", pi
  write(u, '(A, A)') "Name: ", trim(name)
  write(u, '(3(I0, 1X))') 1, 2, 3   ! "1 2 3 "
  close(u)

  ! Read formatted data back
  open(newunit=u, file="output.txt", status="old", action="read")
  block
    character(100) :: line
    integer :: i
    do i = 1, 5
      read(u, '(A)') line
      print *, trim(line)
    end do
  end block
  close(u)

  ! Reading structured data
  open(newunit=u, file="data.csv", status="replace")
  write(u, '(I0, ",", I0, ",", I0)') 1, 10, 100
  write(u, '(I0, ",", I0, ",", I0)') 2, 20, 200
  close(u)

  ! Read back as numbers
  open(newunit=u, file="data.csv", status="old", action="read")
  block
    integer :: a, b, c
    ! List-directed read handles commas as separators
    read(u, *) a, b, c
    print *, a, b, c   ! 1 10 100
    read(u, *) a, b, c
    print *, a, b, c   ! 2 20 200
  end block
  close(u)
end program formatted_io

E/S non formatées (binaires)

Les E/S non formatées (binaires) sont plus rapides et plus compactes que les E/S formatées (texte) — pas de conversion de chaîne. form='unformatted' les active. access='stream' (Fortran 2003) donne un accès en flux d'octets (comme les E/S fichier C, pas de marqueurs d'enregistrement). access='sequential' (défaut) utilise des marqueurs d'enregistrement (chaque write/read est un enregistrement avec des préfixes de longueur) — portable au sein de Fortran mais pas vers d'autres langages. Pour l'interopérabilité avec C/Python, utilisez l'accès stream. Les fichiers binaires ne sont pas lisibles par l'homme mais idéaux pour les grands jeux de données numériques. Écrivez toujours les métadonnées (tailles de tableau, infos de type) avant les données pour pouvoir les relire correctement. Les E/S non formatées préservent la pleine précision (pas d'arrondi par conversion texte).

fortran
program binary_io
  implicit none
  integer :: u, i
  real :: arr(5) = [1.0, 2.0, 3.0, 4.0, 5.0]
  real :: arr_in(5)
  integer :: n

  ! Write binary (unformatted) - faster, no format conversion
  open(newunit=u, file="data.bin", status="replace", &
       form="unformatted", access="stream")
  write(u) size(arr)        ! write the count first
  write(u) arr              ! write the whole array
  close(u)

  ! Read binary back
  open(newunit=u, file="data.bin", status="old", &
       form="unformatted", access="stream", action="read")
  read(u) n                 ! read the count
  print *, "Count: ", n
  read(u) arr_in            ! read the array
  print *, arr_in           ! 1 2 3 4 5
  close(u)

  ! Sequential unformatted (default) - with record markers
  open(newunit=u, file="data_seq.bin", status="replace", &
       form="unformatted")   ! access defaults to "sequential"
  do i = 1, 5
    write(u) i, arr(i)      ! each write is a "record"
  end do
  close(u)

  ! Read sequentially
  open(newunit=u, file="data_seq.bin", status="old", &
       form="unformatted", action="read")
  do
    read(u, iostat=i) n, arr_in(1)
    if (i /= 0) exit
    print *, n, arr_in(1)
  end do
  close(u)
end program binary_io

Namelist (E/S groupées)

namelist groupe des variables pour des E/S texte structurées — comme JSON/YAML mais natif Fortran. Définissez avec 'namelist /name/ var1, var2, ...'. write(u, nml=name) produit au format &NAME var=val, var=val, /. read(u, nml=name) l'analyse en retour. Namelist est parfait pour les fichiers de configuration : les utilisateurs éditent un fichier texte, le programme le lit. Les variables conservent leurs valeurs déclarées comme défauts ; seules celles dans le fichier sont surchargées. Le format est indulgent (insensible aux espaces, virgules optionnelles). Namelist prend en charge tous les types intrinsèques et les tableaux. iostat attrape les erreurs d'analyse. C'est la façon la plus simple de rendre un programme Fortran configurable sans écrire un analyseur personnalisé.

fortran
program namelist_demo
  implicit none
  ! Namelist groups variables for easy I/O
  integer :: max_iter = 100
  real :: tolerance = 1.0e-6
  logical :: verbose = .true.
  character(20) :: method = "newton"
  real :: params(3) = [0.1, 0.2, 0.3]

  ! Define a namelist group
  namelist /config/ max_iter, tolerance, verbose, method, params

  ! Write namelist to file
  block
    integer :: u
    open(newunit=u, file="config.nml", status="replace")
    write(u, nml=config)
    close(u)
  end block

  ! The file looks like:
  ! &CONFIG
  !  MAX_ITER=100,
  !  TOLERANCE=1.0000000E-06,
  !  VERBOSE=T,
  !  METHOD="newton",
  !  PARAMS=0.100000, 0.200000, 0.300000,
  ! /

  ! Read namelist from file (overrides defaults)
  block
    integer :: u, ios
    open(newunit=u, file="config.nml", status="old", action="read")
    read(u, nml=config, iostat=ios)
    close(u)
    if (ios /= 0) then
      print *, "Error reading config"
    else
      print *, "max_iter=", max_iter
      print *, "tolerance=", tolerance
      print *, "method=", trim(method)
      print *, "params=", params
    end if
  end block

  ! User can edit config.nml in a text editor
  ! then re-run to pick up new values
end program namelist_demo

Fichiers internes & Gestion d'erreurs

Les fichiers internes vous permettent de lire depuis / écrire vers une chaîne de caractères au lieu d'un fichier — le sprintf/sscanf de Fortran. write(str, fmt) formate vers une chaîne ; read(str, fmt) analyse depuis une chaîne. C'est la méthode standard pour convertir entre chaînes et nombres. Utilisez toujours iostat pour la gestion d'erreurs : 0 = succès, négatif = EOF, positif = erreur. Les étiquettes héritées end= et err= fonctionnent mais iostat est plus propre (pas de goto). Pour une analyse robuste, vérifiez iostat après chaque lecture. Les E/S internes sont excellentes pour : construire des chaînes de sortie, analyser l'entrée utilisateur, convertir des valeurs de fichier de configuration. La chaîne agit comme un 'fichier interne' — mêmes instructions d'E/S, juste une destination chaîne.

fortran
program internal_io
  implicit none
  character(100) :: buffer
  integer :: n = 42
  real :: x = 3.14159
  integer :: ios

  ! Internal write: format to a string (like sprintf)
  write(buffer, '(A, I0, A, F8.4)') "n=", n, " x=", x
  print *, trim(buffer)   ! n=42 x=  3.1416

  ! Internal read: parse from a string (like sscanf)
  character(50) :: input = "100 3.14 hello"
  integer :: a
  real :: b
  character(20) :: c
  read(input, *, iostat=ios) a, b, c
  if (ios == 0) print *, a, b, trim(c)   ! 100 3.14 hello

  ! Robust number parsing with error handling
  character(20) :: num_str = "3.14abc"
  real :: val
  read(num_str, *, iostat=ios) val
  if (ios /= 0) then
    print *, "Parse error: '", trim(num_str), "' is not a number"
  else
    print *, "Value: ", val
  end if

  ! End-of-file and error handling on file reads
  block
    integer :: u
    character(100) :: line
    open(newunit=u, file="data.txt", status="old", action="read")
    do
      read(u, '(A)', iostat=ios) line
      if (ios < 0) then
        print *, "End of file"
        exit
      else if (ios > 0) then
        print *, "Read error at line"
        exit
      end if
      print *, trim(line)
    end do
    close(u)
  end block

  ! err= and end= labels (legacy style)
  block
    integer :: u, val
    open(newunit=u, file="nums.txt", status="old", action="read")
    do
      read(u, *, end=100, err=200) val
      print *, val
    end do
100 print *, "Reached EOF"
    close(u)
    goto 300
200 print *, "Read error!"
    close(u)
300 continue
  end block
end program internal_io
09

Calcul numérique

Paramètres de kind & Précision

Les paramètres de kind contrôlent la précision/taille. Méthode portable moderne : utilisez iso_fortran_env (int32/int64, real32/real64/real128). Héritée : selected_real_kind(digits, exponent_range). real32 ≈ 7 chiffres significatifs, real64 ≈ 15 chiffres (double), real128 ≈ 33 chiffres (quad). CRITIQUE : ajoutez toujours le suffixe de kind aux littéraux (3.14_dp, pas 3.14) — sinon le littéral est analysé en simple précision PUIS converti, perdant des chiffres. precision() retourne les chiffres significatifs ; range() retourne la portée de l'exposant décimal. epsilon() donne l'epsilon machine (plus petit incrément distinguable). tiny/huge donnent min/max. Pour le calcul scientifique, utilisez par défaut real64 (double précision).

fortran
program precision_demo
  use iso_fortran_env, only: int32, int64, real32, real64, real128
  implicit none
  ! Portable kind selection via iso_fortran_env
  integer(kind=int32) :: i32 = 100
  integer(kind=int64) :: i64 = 9223372036854775807_int64
  real(kind=real32) :: r32 = 3.14159265_real32   ! single (~7 digits)
  real(kind=real64) :: r64 = 3.14159265358979_real64  ! double (~15 digits)
  real(kind=real128) :: r128 = 3.14159265358979_real128 ! quad (~33 digits)

  ! Legacy: selected_real_kind (still works)
  integer, parameter :: dp = selected_real_kind(15, 307)  ! double
  integer, parameter :: sp = selected_real_kind(6, 37)    ! single
  real(kind=dp) :: pi = 3.14159265358979_dp

  print *, "real32 precision: ", precision(r32), " range: ", range(r32)
  print *, "real64 precision: ", precision(r64), " range: ", range(r64)
  print *, "real128 precision: ", precision(r128)

  ! Epsilon and tiny
  print *, "epsilon(r32): ", epsilon(r32)   ! ~1.19e-7
  print *, "epsilon(r64): ", epsilon(r64)   ! ~2.22e-16
  print *, "tiny(r64): ", tiny(r64)         ! smallest positive
  print *, "huge(i32): ", huge(i32)         ! 2147483647
  print *, "huge(i64): ", huge(i64)

  ! Always use _kind suffix on literals
  ! WRONG: real(kind=dp) :: x = 3.14  (loses precision!)
  ! RIGHT: real(kind=dp) :: x = 3.14_dp
end program precision_demo

Algèbre linéaire (matmul, solve, BLAS)

Fortran a de l'algèbre linéaire intégrée : matmul (multiplication matrice-matrice/matrice-vecteur), dot_product, transpose. Pour résoudre des systèmes linéaires (Ax=b), valeurs propres, SVD, etc., utilisez LAPACK (la bibliothèque Fortran standard de l'industrie) : dgesv résout Ax=b, dgesvd fait la SVD, dsyev fait la décomposition en valeurs propres. Liez avec -llapack -lblas. L'exemple montre une élimination gaussienne manuelle pour un système 3x3 — pour le vrai travail, utilisez LAPACK (plus rapide, plus précis avec pivotage, gère n'importe quelle taille). Le stockage colonne-major de Fortran correspond nativement aux attentes de LAPACK (pas de transposition nécessaire). matmul est optimisé mais pour les grandes matrices, BLAS dgemm est plus rapide. Vérifiez toujours le nombre de condition pour la stabilité numérique.

fortran
program linalg
  implicit none
  real(8) :: A(3,3), B(3,3), C(3,3)
  real(8) :: v(3), w(3), x(3)
  real(8) :: det
  integer :: i

  ! Initialize matrices
  A = reshape([1,2,3, 4,5,6, 7,8,10], [3,3])  ! column-major fill
  B = reshape([1,0,0, 0,1,0, 0,0,1], [3,3])   ! identity

  ! Matrix-matrix multiplication
  C = matmul(A, B)        ! A * I = A
  print *, "A * I = A? ", all(abs(C - A) < 1e-10)

  ! Matrix-vector multiplication
  v = [1.0, 2.0, 3.0]
  w = matmul(A, v)
  print *, "A * v = ", w    ! 14 32 53

  ! Dot product
  print *, "v . v = ", dot_product(v, v)   ! 14

  ! Outer product
  C = 0.0
  do i = 1, 3
    C(:,i) = v * v(i)
  end do

  ! Transpose
  C = transpose(A)

  ! Solve linear system Ax = b (need LAPACK or custom)
  ! Using LAPACK dgesv:
  !   call dgesv(n, nrhs, A, lda, ipiv, b, ldb, info)
  ! For demo, manual Gaussian elimination:
  x = solve_3x3(A, v)
  print *, "Solution: ", x

contains
  function solve_3x3(A, b) result(x)
    real(8), intent(inout) :: A(3,3)
    real(8), intent(in) :: b(3)
    real(8) :: x(3), M(3,4), factor
    integer :: k, j
    M(:,1:3) = A
    M(:,4) = b
    ! Forward elimination
    do k = 1, 2
      do i = k+1, 3
        factor = M(i,k) / M(k,k)
        M(i,:) = M(i,:) - factor * M(k,:)
      end do
    end do
    ! Back substitution
    x(3) = M(3,4) / M(3,3)
    x(2) = (M(2,4) - M(2,3)*x(3)) / M(2,2)
    x(1) = (M(1,4) - M(1,2)*x(2) - M(1,3)*x(3)) / M(1,1)
  end function
end program linalg

Nombres aléatoires

call random_number(x) remplit x avec des réels uniformes [0,1) — fonctionne sur des scalaires ou des tableaux. call random_seed(size=n) obtient la taille de la graine ; random_seed(put=seed) définit la graine pour la reproductibilité (essentiel pour les tests/débogage). Pour les entiers dans [a,b] : a + int(r * (b-a+1)). Pour les nombres aléatoires gaussiens (normaux), utilisez la transformée de Box-Muller (montrée) ou la méthode polaire. Fortran n'a pas de générateur de distribution normale intégré — implémentez-le ou utilisez une bibliothèque. Pour les simulations de Monte Carlo, définissez la graine pour la reproductibilité, puis exécutez de nombreux essais. random_number n'est PAS cryptographiquement sûr — utilisez une bibliothèque crypto pour la sécurité. Pour le code parallèle, chaque image a besoin d'une graine distincte.

fortran
program random_demo
  implicit none
  real :: r
  integer :: i, n = 10
  real :: arr(10)
  integer :: seed_size
  integer, allocatable :: seed(:)

  ! Initialize random seed (Fortran 2003: random_seed with no args
  ! uses a processor-dependent seed; for reproducibility, set it)
  call random_seed(size=seed_size)
  allocate(seed(seed_size))
  ! Set a fixed seed for reproducibility
  seed = [(12345 + i*6789, i=1, seed_size)]
  call random_seed(put=seed)

  ! Generate uniform [0,1) random reals
  call random_number(r)
  print *, "Single: ", r

  call random_number(arr)   ! fills entire array
  print *, "Array: ", arr

  ! Generate integers in [a, b]
  block
    integer :: a = 1, b = 6
    integer :: dice
    do i = 1, 5
      call random_number(r)
      dice = a + int(r * (b - a + 1))   ! [1, 6]
      print *, "Dice roll: ", dice
    end do
  end block

  ! Normal (Gaussian) via Box-Muller transform
  block
    real :: u1, u2, z1, z2
    integer :: j
    do j = 1, 5
      call random_number(u1)
      call random_number(u2)
      u1 = max(u1, 1e-10)   ! avoid log(0)
      z1 = sqrt(-2.0 * log(u1)) * cos(2.0 * 3.14159265 * u2)
      z2 = sqrt(-2.0 * log(u1)) * sin(2.0 * 3.14159265 * u2)
      print *, "Gaussian: ", z1, z2
    end do
  end block

  ! Monte Carlo: estimate pi
  block
    integer :: inside = 0, total = 100000
    real :: x, y
    do i = 1, total
      call random_number(x)
      call random_number(y)
      if (x*x + y*y <= 1.0) inside = inside + 1
    end do
    print *, "Pi estimate: ", 4.0 * real(inside) / total
  end block
end program random_demo

Intégration numérique & Recherche de racines

Intégration numérique : la règle de Simpson est plus précise que la règle des trapèzes (erreur O(h^4) vs O(h^2)). Passez des fonctions en arguments en utilisant un bloc interface. Recherche de racines : la bissection est robuste (converge toujours si le signe change) mais lente (convergence linéaire) ; la méthode de Newton est rapide (convergence quadratique) mais nécessite la dérivée et peut diverger. Pour le travail de production, utilisez QUADPACK (intégration) ou MINPACK (recherche de racines) — bibliothèques Fortran éprouvées. Le bloc interface est essentiel lors du passage de fonctions en arguments — il indique au compilateur la signature de la fonction. Définissez toujours un nombre maximal d'itérations pour éviter les boucles infinies. Vérifiez la convergence avec les tolérances de valeur de fonction ET de taille de pas.

fortran
program numerical
  implicit none
  real(8) :: a, b, result
  integer :: n

  ! Numerical integration: Simpson's rule
  ! Integrate f(x) = x^2 from 0 to 2 (exact: 8/3 ≈ 2.6667)
  a = 0.0; b = 2.0; n = 1000
  result = simpson(f_sq, a, b, n)
  print *, "Integral of x^2 from 0 to 2: ", result   ! ~2.6667

  ! Integrate sin(x) from 0 to pi (exact: 2.0)
  result = simpson(f_sin, 0.0_8, 3.14159265358979_8, 1000)
  print *, "Integral of sin(x) from 0 to pi: ", result  ! ~2.0

  ! Root finding: bisection method
  ! Find root of f(x) = x^2 - 2 (i.e., sqrt(2) ≈ 1.4142)
  result = bisection(f_x2_minus_2, 0.0_8, 2.0_8, 1e-12_8)
  print *, "sqrt(2) = ", result   ! ~1.41421356

  ! Newton's method (needs derivative)
  result = newton(f_x2_minus_2, fp_x2_minus_2, 1.0_8, 1e-12_8)
  print *, "sqrt(2) via Newton: ", result

contains
  ! Function to integrate: x^2
  function f_sq(x) result(y)
    real(8), intent(in) :: x
    real(8) :: y
    y = x * x
  end function

  function f_sin(x) result(y)
    real(8), intent(in) :: x
    real(8) :: y
    y = sin(x)
  end function

  ! Simpson's rule: integral of f from a to b with n intervals
  function simpson(f, a, b, n) result(integral)
    interface
      function f(x) result(y)
        import
        real(8), intent(in) :: x
        real(8) :: y
      end function
    end interface
    real(8), intent(in) :: a, b
    integer, intent(in) :: n
    real(8) :: integral, h, x
    integer :: i
    h = (b - a) / n
    integral = f(a) + f(b)
    do i = 1, n-1
      x = a + i * h
      if (mod(i, 2) == 0) then
        integral = integral + 2.0 * f(x)
      else
        integral = integral + 4.0 * f(x)
      end if
    end do
    integral = integral * h / 3.0
  end function

  function f_x2_minus_2(x) result(y)
    real(8), intent(in) :: x
    real(8) :: y
    y = x*x - 2.0
  end function

  function fp_x2_minus_2(x) result(y)
    real(8), intent(in) :: x
    real(8) :: y
    y = 2.0 * x
  end function

  ! Bisection: find root in [a, b] (f(a) and f(b) must have opposite signs)
  function bisection(f, a, b, tol) result(root)
    interface
      function f(x) result(y)
        import
        real(8), intent(in) :: x
        real(8) :: y
      end function
    end interface
    real(8), intent(in) :: a, b, tol
    real(8) :: root, fa, fb, mid, fmid
    integer :: iter, maxiter = 100
    fa = f(a); fb = f(b)
    if (fa * fb > 0) stop "No sign change in interval"
    do iter = 1, maxiter
      mid = (a + b) / 2.0
      fmid = f(mid)
      if (abs(fmid) < tol .or. (b - a)/2.0 < tol) then
        root = mid
        return
      end if
      if (fa * fmid < 0) then
        b = mid; fb = fmid
      else
        a = mid; fa = fmid
      end if
    end do
    root = (a + b) / 2.0
  end function

  ! Newton's method: x_{n+1} = x_n - f(x)/f'(x)
  function newton(f, fp, x0, tol) result(root)
    interface
      function f(x) result(y)
        import
        real(8), intent(in) :: x
        real(8) :: y
      end function
      function fp(x) result(y)
        import
        real(8), intent(in) :: x
        real(8) :: y
      end function
    end interface
    real(8), intent(in) :: x0, tol
    real(8) :: root, x, fx, fpx
    integer :: iter, maxiter = 100
    x = x0
    do iter = 1, maxiter
      fx = f(x); fpx = fp(x)
      if (abs(fx) < tol) then
        root = x
        return
      end if
      x = x - fx / fpx
    end do
    root = x
  end function
end program numerical

Arithmétique IEEE & Exceptions

Le module ieee_arithmetic (Fortran 2003) fournit le support IEEE 754 : Infini, NaN (Not a Number), NaN signalant/silencieux, drapeaux d'exception et modes d'arrondi. NaN n'est JAMAIS égal à quoi que ce soit (y compris lui-même) — utilisez ieee_is_nan() pour tester. L'infini résulte d'un débordement ou d'une division par zéro. Les drapeaux d'exception (ieee_divide_by_zero, ieee_overflow, ieee_underflow, ieee_inexact, ieee_invalid) suivent si des exceptions se sont produites — vérifiez avec ieee_get_flag, effacez avec ieee_set_flag. ieee_set_halting_mode contrôle si une exception arrête le programme. Les modes d'arrondi affectent les opérations en virgule flottante. Utilisez cela pour le code numérique robuste : détectez NaN/Inf, gérez les exceptions gracieusement et contrôlez la précision. Note : -ffast-math dans gfortran casse la conformité IEEE (ne l'utilisez pas pour le code reposant sur ces fonctionnalités).

fortran
program ieee_demo
  use ieee_arithmetic
  implicit none
  real :: a, b, c
  logical :: flag

  ! IEEE special values
  a = ieee_value(a, ieee_positive_inf)   ! +Infinity
  b = ieee_value(b, ieee_negative_inf)   ! -Infinity
  c = ieee_value(c, ieee_quiet_nan)      ! NaN

  print *, "Infinity: ", a                ! Infinity
  print *, "NaN: ", c                     ! NaN
  print *, "Inf > 1e30: ", a > 1e30       ! T
  print *, "NaN == NaN: ", c == c         ! F (NaN is never equal!)

  ! Check for special values
  print *, "is_nan(c): ", ieee_is_nan(c)         ! T
  print *, "is_finite(1.0): ", ieee_is_finite(1.0)  ! T
  print *, "is_inf(a): ", ieee_is_finite(a)      ! F

  ! Operations producing special values
  print *, "1.0/0.0: ", 1.0/0.0           ! Infinity (if -ffast-math off)
  print *, "0.0/0.0: ", 0.0/0.0           ! NaN
  print *, "sqrt(-1.0): ", sqrt(-1.0)     ! NaN

  ! IEEE exception flags
  call ieee_set_halting_mode(ieee_divide_by_zero, .false.)  ! don't halt
  b = 1.0 / 0.0   ! sets divide_by_zero flag, returns Inf
  call ieee_get_flag(ieee_divide_by_zero, flag)
  print *, "Divide by zero occurred: ", flag   ! T

  ! Check and clear flags
  call ieee_set_flag(ieee_all, .false.)   ! clear all flags
  b = 1.0 / 0.0
  call ieee_get_flag(ieee_divide_by_zero, flag)
  print *, "Flag after division: ", flag   ! T
  call ieee_set_flag(ieee_all, .false.)   ! clear

  ! Rounding modes
  call ieee_set_rounding_mode(ieee_nearest)   ! default
  call ieee_set_rounding_mode(ieee_down)      ! round toward -inf
  call ieee_set_rounding_mode(ieee_up)        ! round toward +inf
  call ieee_set_rounding_mode(ieee_to_zero)   ! truncate

  ! Comparing NaN-safe
  if (ieee_unordered(c, 1.0)) print *, "c is unordered (NaN)"
end program ieee_demo
10

Coarrays & Parallèle

Déclaration de coarray de base

Les coarrays sont le modèle parallèle intégré de Fortran (F2008). Chaque 'image' est un processus parallèle. Déclarez avec le suffixe [*]. this_image() retourne le rang ; num_images() le nombre. Accédez à distance avec x[k]. sync all est une barrière. Compilateurs : gfortran (avec -fcoarray=lib), ifort, Cray.

fortran
program coarray_hello
  use iso_fortran_env, only: real64
  implicit none
  real(real64) :: x[*]  ! coarray — one copy per image
  integer :: me

  me = this_image()
  x = real(me, real64)  ! local assignment

  call co_sum(x, result_image=1)  ! reduce sum to image 1
  if (me == 1) print *, 'Sum =', x

  sync all  ! barrier
end program

Accès distant et sync

Accédez au coarray distant avec le suffixe [k] — communication unilatérale. Les lectures et écritures sont non bloquantes jusqu'à la sync. sync all est une barrière globale ; sync images([1,2]) attend des images spécifiques. Sections critiques : lock/unlock avec critical...end critical. Évitez le deadlock en ordonnançant les syncs de manière cohérente.

fortran
program remote_access
  implicit none
  integer :: val[*], neighbor
  integer :: me, n

  me = this_image()
  n = num_images()
  val = me * 10

  sync all  ! ensure all writes complete

  ! read from neighbor (circular)
  neighbor = merge(1, me + 1, me == n)
  print *, 'Image', me, 'sees neighbor', neighbor, 'value', val[neighbor]

  ! write to image 1 from all
  if (me /= 1) val[1] = val[me]
  sync all
  if (me == 1) print *, 'Image 1 received:', val
end program

Opérations collectives

Collectives : co_sum, co_min, co_max, co_broadcast. Elles opèrent sur les coarrays et réduisent/diffusent à travers les images. result_image spécifie qui obtient la réponse (défaut : tous). source_image pour la diffusion. Synchronisez toujours avant de lire les résultats collectifs sur d'autres images. Plus rapide que les boucles manuelles avec syncs.

fortran
program collectives
  use iso_fortran_env, only: real64
  implicit none
  real(real64) :: local_sum, global_sum[*]
  integer :: i

  local_sum = 0.0_real64
  do i = 1, 100
    local_sum = local_sum + real(i * this_image(), real64)
  end do

  global_sum = local_sum
  call co_sum(global_sum, result_image=1)
  if (this_image() == 1) print *, 'Total:', global_sum

  ! other collectives: co_min, co_max, co_broadcast
  call co_broadcast(global_sum, source_image=1)
end program

Types dérivés coarray

Les types dérivés peuvent être des coarrays — chaque composant est répliqué par image. Accédez aux composants distants avec p[k]%field. Les composants allocatable dans les coarrays nécessitent Fortran 2018+ (certains compilateurs peuvent ne pas prendre en charge). Pour les tableaux de particules, utilisez type(particle), allocatable :: particles(:)[:].

fortran
program coarray_types
  implicit none
  type :: particle
    real :: x, y, z
    real :: mass
  end type
  type(particle) :: p[*]
  integer :: me

  me = this_image()
  p%mass = real(me)
  p%x = real(me) * 0.5

  sync all
  ! access component of remote coarray
  if (me == 1) print *, 'Image 2 mass:', p[2]%mass
end program

Coarrays allouables et équipes

Les coarrays allocatable sont alloués sur toutes les images simultanément avec le suffixe [*]. Les équipes (F2018) divisent les images en groupes indépendants — chacune a son propre this_image/num_images. form team crée des équipes ; change team entre dans la portée. Utile pour le parallélisme hiérarchique. Le support du compilateur varie (ifort, gfortran 9+).

fortran
program alloc_coarrays
  use iso_fortran_env, only: team_type
  implicit none
  integer, allocatable :: data(:)[:]
  type(team_type) :: odd_team, even_team
  integer :: me

  me = this_image()
  allocate(data(100)[*])  ! allocate on all images

  ! F2018 teams — split images into groups
  form team(merge(1, 2, mod(me, 2) == 1), odd_team, even_team)
  if (mod(me, 2) == 1) then
    change team(odd_team)
      ! this_image() and num_images() now refer to team
      data(this_image()) = me
    end team
  end if

  deallocate(data)
end program
11

Interopérabilité avec C

Bases d'ISO_C_BINDING

iso_c_binding fournit des kinds compatibles C (c_int, c_double, c_char, etc.). bind(C, name='...') expose Fortran à C avec un nom de symbole spécifique. Les chaînes C nécessitent un terminateur c_null_char. Le bloc interface déclare la signature de fonction C. Compilez C et Fortran séparément, liez ensemble.

fortran
program c_interop
  use iso_c_binding, only: c_int, c_double, c_char, c_null_char
  implicit none
  interface
    subroutine c_print(msg) bind(C, name='c_print')
      import :: c_char
      character(kind=c_char), dimension(*) :: msg
    end subroutine
  end interface

  call c_print('Hello from Fortran' // c_null_char)
end program

! Corresponding C:
!   void c_print(const char* msg) { printf("%s\n", msg); }

Passage de tableaux à C

Passez les tableaux via c_loc (obtient le pointeur C) et le type c_ptr. Utilisez l'attribut 'value' pour les arguments scalaires C (passés par valeur, pas par référence). Les tableaux Fortran sont colonne-major ; C est ligne-major — transposez les tableaux 2D ou documentez la convention. c_f_pointer convertit les pointeurs C en pointeurs Fortran.

fortran
program array_interop
  use iso_c_binding, only: c_double, c_int, c_loc, c_f_pointer
  implicit none
  real(c_double), target :: arr(10)
  integer(c_int) :: n
  type(c_ptr) :: ptr

  arr = [(real(i), i=1,10)]
  ptr = c_loc(arr(1))  ! get C pointer

  ! call C function: void process(double* arr, int n);
  call process_c(ptr, size(arr, kind=c_int))

  interface
    subroutine process_c(arr, n) bind(C, name='process')
      import :: c_double, c_int, c_ptr
      type(c_ptr), value :: arr
      integer(c_int), value :: n
    end subroutine
  end interface
end program

Types interopérables avec C

Les types avec bind(C) ont une disposition mémoire compatible C — requis pour passer des structs à C. Seuls les kinds compatibles C autorisés (pas de real/integer par défaut). Pas de composants allocatable/pointer. Les tableaux char de longueur fixe émulent les chaînes C. L'ordre compte — Fortran peut réordonner les composants sans bind(C).

fortran
module data_types
  use iso_c_binding, only: c_double, c_int, c_char
  implicit none

  type, bind(C) :: point
    real(c_double) :: x, y
    integer(c_int) :: id
  end type

  type, bind(C) :: string_holder
    character(kind=c_char, len=1) :: name(64)
  end type
end module

! C equivalent:
!   struct point { double x, y; int id; };
!   struct string_holder { char name[64]; };

Appel de Fortran depuis C

Les sous-routines avec bind(C, name='...') sont appelables depuis C par ce nom. Utilisez 'value' pour les scalaires que C passe par valeur ; les tableaux sont passés par référence (pas de value). Les résultats de fonction doivent être des scalaires compatibles C. Utilisez des noms de liaison C pour éviter la décoration de nom du compilateur (underscores, changements de casse).

fortran
! Fortran:
module fmod
  use iso_c_binding, only: c_double
  implicit none
contains
  subroutine square_array(arr, n) bind(C, name='square_array')
    integer, value :: n
    real(c_double), intent(inout) :: arr(n)
    arr = arr ** 2
  end subroutine
end module

! C caller:
!   extern void square_array(double* arr, int n);
!   double data[5] = {1, 2, 3, 4, 5};
!   square_array(data, 5);

Pointeurs de fonction C et rappels

abstract interface déclare une signature de fonction C. procedure(iface) accepte une fonction correspondante en argument. Passez les pointeurs de fonction C directement. c_funloc obtient l'adresse C d'une procédure Fortran ; c_f_procpointer convertit un c_funptr en une procédure Fortran. Utile pour les rappels de type qsort.

fortran
module callbacks
  use iso_c_binding, only: c_funloc, c_funptr, c_int
  implicit none

  abstract interface
    function comparator(a, b) bind(C)
      import :: c_int
      integer(c_int), value :: a, b
      integer(c_int) :: comparator
    end function
  end interface

contains
  subroutine sort_with_c(arr, n, cmp) bind(C)
    integer(c_int), value :: n
    integer(c_int), intent(inout) :: arr(n)
    procedure(comparator) :: cmp
    ! ... use cmp(a, b) to compare ...
  end subroutine
end module

! C side:
!   int descending(int a, int b) { return b - a; }
!   sort_with_c(arr, n, descending);
12

Orienté objet (extends/final)

Extension de type (héritage)

Extension de type = héritage. 'extends(parent)' déclare une sous-classe. 'class(T)' est polymorphe (accepte T ou toute extension) ; 'type(T)' est exact. 'abstract' + 'deferred' = méthode abstraite (doit être surchargée). 'contains' introduit les procédures liées au type. Surchargez en redéclarant la procédure avec le même nom.

fortran
module shapes
  implicit none
  type, abstract :: shape
    real :: x = 0, y = 0
  contains
    procedure(area_iface), deferred :: area
    procedure :: move => shape_move
  end type

  abstract interface
    function area_iface(this) result(a)
      import :: shape
      class(shape), intent(in) :: this
      real :: a
    end function
  end interface

  type, extends(shape) :: circle
    real :: radius
  contains
    procedure :: area => circle_area
  end type

contains
  subroutine shape_move(this, dx, dy)
    class(shape), intent(inout) :: this
    real, intent(in) :: dx, dy
    this%x = this%x + dx
    this%y = this%y + dy
  end subroutine

  function circle_area(this) result(a)
    class(circle), intent(in) :: this
    real :: a
    a = 3.14159 * this%radius ** 2
  end function
end module

Polymorphisme et SELECT TYPE

select type fait la discrimination de type à l'exécution sur les variables polymorphes. 'type is (T)' correspond au type exact ; 'class is (T)' correspond à T et aux extensions. À l'intérieur du bloc, la variable est traitée comme le type correspondant (accède aux composants spécifiques). Les tableaux polymorphes contiennent des types mixtes via class(shape) — mais l'allocation doit être par élément.

fortran
module polymorph
  use shapes, only: shape, circle, rectangle
  implicit none
contains
  subroutine describe(s)
    class(shape), intent(in) :: s
    select type(s)
    type is (circle)
      print *, 'Circle radius:', s%radius
    type is (rectangle)
      print *, 'Rectangle:', s%width, 'x', s%height
    class is (shape)
      print *, 'Some shape at:', s%x, s%y
    class default
      print *, 'Unknown type'
    end select
  end subroutine

  subroutine process_all(shapes)
    class(shape), intent(in) :: shapes(:)
    integer :: i
    do i = 1, size(shapes)
      call describe(shapes(i))
      print *, 'Area:', shapes(i)%area()
    end do
  end subroutine
end module

Finaliseurs et destructeurs

Les procédures final s'exécutent automatiquement lorsqu'une variable sort de la portée — comme les destructeurs C++. Définissez avec 'final :: name'. Doit être une sous-routine prenant TYPE (pas class) — pas de polymorphisme. Un type peut avoir plusieurs finaliseurs (surchargés par rang). Utilisez pour fermer les fichiers, libérer la mémoire, relâcher les ressources. Ne peut pas échouer/lever.

fortran
module resources
  implicit none
  type :: file_handle
    integer :: unit = -1
  contains
    final :: close_file
    procedure :: open
  end type
contains
  subroutine open(this, filename)
    class(file_handle), intent(inout) :: this
    character(*), intent(in) :: filename
    open(newunit=this%unit, file=filename, status='old')
  end subroutine

  subroutine close_file(this)
    type(file_handle), intent(inout) :: this
    if (this%unit /= -1) then
      close(this%unit)
      this%unit = -1
    end if
  end subroutine
end module

program demo
  use resources
  type(file_handle) :: f
  call f%open('data.txt')
  ! ... use file ...
end program  ! f goes out of scope -> close_file called automatically

Constructeurs et allocation

L'interface générique avec le nom du type agit comme un constructeur personnalisé — surcharge le constructeur de structure par défaut. Plusieurs procédures permettent différents ensembles d'arguments. Le constructeur par défaut (point(x=..., y=...)) est toujours disponible sauf si surchargé. Allocation polymorphe : allocate(circle :: shape_var) crée un circle dans une variable class(shape).

fortran
module points
  implicit none
  type :: point
    real :: x, y
  contains
    procedure :: norm
  end type
  interface point
    procedure new_point
    procedure new_point_polar
  end interface
contains
  function new_point(x, y) result(p)
    real, intent(in) :: x, y
    type(point) :: p
    p%x = x
    p%y = y
  end function

  function new_point_polar(r, theta) result(p)
    real, intent(in) :: r, theta
    type(point) :: p
    p%x = r * cos(theta)
    p%y = r * sin(theta)
  end function

  function norm(this) result(n)
    class(point), intent(in) :: this
    real :: n
    n = sqrt(this%x**2 + this%y**2)
  end function
end module

program use_points
  use points
  type(point) :: a, b
  a = point(1.0, 2.0)         ! cartesian
  b = point(3.0, 0.5)         ! polar (same name, different args)
  print *, a%norm(), b%norm()
end program

Types abstraits et modèles

Les types abstraits ne peuvent pas être instanciés — uniquement étendus. Les procédures deferred doivent être surchargées dans les sous-classes concrètes. class(*) est polymorphe illimité — contient n'importe quel type (utilisez select type pour récupérer). Ce modèle implémente les classes de base abstraites et les interfaces. Les conteneurs concrets (list, stack, queue) étendent et implémentent les procédures deferred.

fortran
module container
  implicit none
  type, abstract :: container
  contains
    procedure(add_iface), deferred :: add
    procedure(size_iface), deferred :: size
    procedure :: is_empty => container_is_empty
  end type

  abstract interface
    subroutine add_iface(this, item)
      import :: container
      class(container), intent(inout) :: this
      class(*), intent(in) :: item
    end subroutine
    function size_iface(this) result(n)
      import :: container
      class(container), intent(in) :: this
      integer :: n
    end function
  end interface
contains
  function container_is_empty(this) result(e)
    class(container), intent(in) :: this
    logical :: e
    e = (this%size() == 0)
  end function
end module

! Concrete subclass implements add/size
! class(*) allows storing any type (unlimited polymorphic)
13

Types dérivés paramétrés

Déclaration PDT de base

Les Parameterized Derived Types (PDT, F2003) sont comme les templates C++. Les paramètres 'kind' sont à la compilation (fixes par instance) ; les paramètres 'len' sont à l'exécution (peuvent être différés avec ':'). Allouez les types à longueur différée avec allocate(type(params) :: var). Les PDTs permettent des conteneurs génériques de type sûr sans astuces de préprocesseur.

fortran
module pdt_types
  implicit none
  type :: matrix(k, n, m)
    integer, kind :: k = kind(1.0)
    integer, len :: n, m
    real(k) :: data(n, m)
  end type

  ! kind parameters: compile-time (like template params)
  ! len parameters: runtime (like allocatable dimensions)
end module

program use_pdt
  use pdt_types
  implicit none
  type(matrix(kind(1.0), 3, 3)) :: small  ! fixed 3x3
  type(matrix(kind(1.d0), :, :)), allocatable :: big  ! deferred

  small%data = 0.0
  allocate(matrix(kind(1.d0), 100, 100) :: big)
  big%data = 0.d0
  deallocate(big)
end program

PDT avec paramètre kind

Les types paramétrés par kind vous permettent d'écrire un type qui fonctionne pour plusieurs précisions. Le type de retour de fonction utilise this%k pour correspondre. Notez la syntaxe class(array_t(k=*)) pour la procédure — requise pour les types à paramètre kind. Support du compilateur : gfortran 9+, ifort. Utile pour les bibliothèques prenant en charge simple/double/quad précision.

fortran
module typed_array
  implicit none
  type :: array_t(k)
    integer, kind :: k = kind(1.0)
    real(k), allocatable :: data(:)
  contains
    procedure :: sum_values
  end type
contains
  function sum_values(this) result(s)
    class(array_t(k=*)), intent(in) :: this
    real(this%k) :: s
    s = sum(this%data)
  end function
end module

program test
  use typed_array
  type(array_t(kind(1.0)))  :: float_arr
  type(array_t(kind(1.d0))) :: double_arr

  allocate(float_arr%data(10))
  allocate(double_arr%data(10))
  float_arr%data = 1.0
  double_arr%data = 1.d0

  print *, float_arr%sum_values()
  print *, double_arr%sum_values()
end program

PDT avec paramètre length

Les types paramétrés par length portent la taille comme paramètre de type. fstr(5) et fstr(6) sont des types différents. Les procédures utilisent fstr(*) pour accepter n'importe quelle longueur. Le type de résultat peut dépendre des longueurs d'entrée (c a la longueur a%n + b%n). Utile pour les tampons de taille fixe, les tableaux de taille statique. Support de compilateur limité — testez minutieusement.

fortran
module fixed_string
  implicit none
  type :: fstr(n)
    integer, len :: n
    character(len=n) :: str
  end type
contains
  function concat(a, b) result(c)
    type(fstr(*)), intent(in) :: a, b
    type(fstr(a%n + b%n)) :: c
    c%str = a%str // b%str
  end function
end module

program test
  use fixed_string
  type(fstr(5))  :: a = fstr(5)('Hello')
  type(fstr(6))  :: b = fstr(6)(' World')
  type(fstr(11)) :: c

  c = concat(a, b)
  print *, c%str
end program

Tableaux allouables PDT

Les PDTs peuvent contenir des composants allocatable. Le paramètre kind se propage aux types de composants (real(this%k)). move_alloc transfère efficacement l'allocation (pas de copie). Utilisez class(stack(k=*)) dans les procédures pour accepter n'importe quel kind. Les PDTs avec composants allocatable combinent le typage générique avec le dimensionnement dynamique.

fortran
module stack_t
  implicit none
  type :: stack(k)
    integer, kind :: k = kind(1.0)
    real(k), allocatable :: data(:)
    integer :: top = 0
  contains
    procedure :: push
    procedure :: pop
    procedure :: is_empty
  end type
contains
  subroutine push(this, val)
    class(stack(k=*)), intent(inout) :: this
    real(this%k), intent(in) :: val
    if (this%top == size(this%data)) then
      block
        real(this%k), allocatable :: tmp(:)
        tmp = [this%data, val]
        call move_alloc(tmp, this%data)
      end block
    else
      this%top = this%top + 1
      this%data(this%top) = val
    end if
  end subroutine
end module

Limitations des PDT et solutions de contournement

Le support des PDT varie — les paramètres kind sont largement pris en charge ; les paramètres length moins. Pour le dimensionnement à l'exécution, préférez les composants allocatable aux paramètres len. Les tableaux PDT (type(matrix(4,4)) :: arr(10)) peuvent ne pas fonctionner sur tous les compilateurs. Testez sur votre compilateur cible. Pour une portabilité maximale, utilisez le préprocesseur (#define) ou les interfaces génériques.

fortran
! Limitations of PDTs:
! - Not all compilers fully support len parameters
! - I/O of PDTs may not work as expected
! - Some F2003 features (e.g., PDT arrays) have spotty support

! Workaround: use allocatable components instead of len params
module alt_matrix
  implicit none
  type :: matrix_alt(k)
    integer, kind :: k = kind(1.0)
    real(k), allocatable :: data(:,:)
  end type
contains
  function make_matrix(k_, n, m) result(mat)
    integer, intent(in) :: n, m
    ! kind must be compile-time constant — can't pass dynamically
    ! workaround: separate constructors per kind
  end function
end module

! Best practice: use kind params (well-supported), avoid len params
! Use allocatable components for runtime sizing instead
14

Sous-modules

Structure de sous-module de base

Les sous-modules (F2008) séparent l'interface de l'implémentation. Le module déclare l'interface ; le sous-module fournit le corps. Les modifications du corps du sous-module ne déclenchent pas la recompilation des dépendants — seules les modifications d'interface le font. Utilisez 'module procedure' pour implémenter. Excellent pour les grandes bibliothèques avec une compilation coûteuse.

fortran
! parent_module.f90
module math_lib
  implicit none
  interface
    module function integrate(f, a, b, n) result(s)
      abstract interface
        real function func_t(x)
          real, intent(in) :: x
        end function
      end interface
      procedure(func_t) :: f
      real, intent(in) :: a, b
      integer, intent(in) :: n
      real :: s
    end function
  end interface
end module

! sub_math.f90
submodule(math_lib) math_impl
  implicit none
contains
  module procedure integrate
    integer :: i
    real :: dx, x
    dx = (b - a) / n
    s = 0.5 * (f(a) + f(b))
    do i = 1, n-1
      x = a + i * dx
      s = s + f(x)
    end do
    s = s * dx
  end procedure
end submodule

Plusieurs sous-modules par module

Un module peut avoir plusieurs sous-modules — divisez les implémentations entre les fichiers. Chaque sous-module commence par 'submodule(parent) name'. Les procédures dans le même module parent peuvent s'appeler mutuellement. Cela permet la compilation incrémentale : éditez un sous-module, recompilez uniquement lui et liez. Utile pour les très grands modules.

fortran
! big_lib.f90
module big_lib
  implicit none
  interface
    module subroutine sort(arr)
      real, intent(inout) :: arr(:)
    end subroutine
    module function mean(arr) result(m)
      real, intent(in) :: arr(:)
      real :: m
    end function
    module function stddev(arr) result(s)
      real, intent(in) :: arr(:)
      real :: s
    end function
  end interface
end module

! sub_sort.f90 — one submodule
submodule(big_lib) sort_impl
contains
  module procedure sort
    ! quicksort implementation
  end procedure
end submodule

! sub_stats.f90 — another submodule
submodule(big_lib) stats_impl
contains
  module procedure mean
    m = sum(arr) / size(arr)
  end procedure
  module procedure stddev
    block
      real :: mu
      mu = mean(arr)  ! can call sibling module procedures
      s = sqrt(sum((arr - mu)**2) / (size(arr) - 1))
    end block
  end procedure
end submodule

Procédures internes uniquement au sous-module

Les sous-modules peuvent contenir des procédures d'assistance privées non visibles via l'interface du module parent. Cela cache les détails d'implémentation tout en les gardant co-localisés. Seules les implémentations 'module procedure' sont accessibles via le parent. Les assistants restent internes — meilleure encapsulation que de tout mettre dans le bloc contains du module.

fortran
module sparse_ops
  implicit none
  interface
    module function sparse_multiply(a, b) result(c)
      ! ... sparse matrix types ...
      real, allocatable :: c(:,:)
    end function
  end interface
end module

submodule(sparse_ops) sparse_impl
  implicit none
contains
  module procedure sparse_multiply
    ! call helper — invisible to module users
    call validate_dimensions(a, b)
    c = multiply_kernel(a, b)
  end procedure

  ! private helper — not exposed via parent module
  subroutine validate_dimensions(a, b)
    ! ...
  end subroutine

  function multiply_kernel(a, b) result(c)
    ! ...
  end function
end submodule

Héritage des variables de module

Les sous-modules ont accès aux variables et types du module parent via l'association hôte. Ils peuvent lire et modifier l'état au niveau du module. C'est utile pour la configuration qui affecte le comportement de l'implémentation. Les modifications des variables du module nécessitent toujours la recompilation du sous-module (et des dépendants).

fortran
module config
  implicit none
  integer :: verbosity = 0
  interface
    module subroutine log_message(msg)
      character(*), intent(in) :: msg
    end subroutine
  end interface
end module

submodule(config) config_impl
  implicit none
  ! submodule can access parent module's variables
contains
  module procedure log_message
    if (verbosity > 0) then
      print *, '[LOG]', msg
    end if
  end procedure
end submodule

! Usage:
!   use config
!   verbosity = 1
!   call log_message('Starting up')

Bénéfices de compilation des sous-modules

Le bénéfice clé : les modifications d'implémentation dans un sous-module ne déclenchent pas la recompilation du code qui 'use' uniquement le module parent. Seul le sous-module lui-même recompile. Cela accélère considérablement les builds incrémentaux pour les grands projets Fortran. Restructurez les implémentations à changement fréquent dans des sous-modules ; gardez les interfaces stables dans le parent.

fortran
! Without submodules:
!   module big_mod
!     ... 5000 lines of implementation ...
!   end module
!   ! Any change -> recompile big_mod + all users

! With submodules:
!   module big_mod
!     ! just interfaces (~200 lines)
!   end module
!   submodule(big_mod) impl
!     ! 5000 lines of implementation
!   end submodule
!   ! Change impl body -> recompile only submodule
!   ! Change interface -> recompile everything (unavoidable)

! Build system example (Makefile):
!   big_mod.o: big_mod.f90
!   impl.o: impl.f90 big_mod.o
!   user.o: user.f90 big_mod.o  ! NOT impl.o
!   app: user.o impl.o big_mod.o
!   	$(FC) -o app user.o impl.o big_mod.o
15

Virgule flottante IEEE

Exceptions et drapeaux IEEE

ieee_exceptions fournit l'accès aux drapeaux d'exception IEEE (overflow, underflow, divide_by_zero, invalid, inexact). ieee_get_flag lit ; ieee_set_flag efface. ieee_set_halting_mode contrôle si les exceptions arrêtent le programme. ieee_arithmetic fournit ieee_is_nan, ieee_is_finite, ieee_is_negative, etc. Vérifiez toujours les drapeaux après les calculs critiques.

fortran
program ieee_exceptions
  use ieee_exceptions
  use ieee_arithmetic, only: ieee_is_nan, ieee_is_finite
  implicit none
  real :: x, y
  logical :: overflow_flag

  call ieee_set_halting_mode(ieee_overflow, .false.)
  x = huge(x) * 10  ! overflow -> Inf, no halt

  call ieee_get_flag(ieee_overflow, overflow_flag)
  print *, 'Overflow occurred:', overflow_flag

  y = 0.0 / 0.0  ! NaN
  print *, 'x is finite:', ieee_is_finite(x)
  print *, 'y is NaN:', ieee_is_nan(y)

  call ieee_set_flag(ieee_all, .false.)  ! clear all flags
end program

Gestion de NaN et Inf

Inf et NaN sont des valeurs spéciales IEEE. NaN != NaN (utilisez ieee_is_nan pour tester). Inf se propage à travers l'arithmétique. Opérations produisant NaN : 0/0, Inf-Inf, 0*Inf, sqrt(-1). Compilez avec -ffpe-trap=invalid,zero,overflow pour s'arrêter sur celles-ci (débogage). Le code de production devrait vérifier les NaN après les opérations risquées.

fortran
program nan_inf
  use ieee_arithmetic, only: ieee_value, ieee_nan, &
                             ieee_positive_inf, ieee_negative_inf
  implicit none
  real :: pos_inf, neg_inf, nan_val

  pos_inf = ieee_value(0.0, ieee_positive_inf)
  neg_inf = ieee_value(0.0, ieee_negative_inf)
  nan_val = ieee_value(0.0, ieee_nan)

  print *, 'Inf - Inf =', pos_inf - pos_inf  ! NaN
  print *, '0 * Inf =', 0.0 * pos_inf         ! NaN
  print *, 'Inf > 1e30:', pos_inf > 1e30       ! T
  print *, 'NaN == NaN:', nan_val == nan_val   ! F (always!)

  ! generate via arithmetic
  pos_inf = 1.0 / 0.0  ! may halt without -fno-trapping-math
end program

Modes d'arrondi

IEEE prend en charge 4 modes d'arrondi : nearest (défaut), down, up, to zero. ieee_set_rounding_mode le change à l'exécution. Utile pour l'arithmétique d'intervalle (calculez les bornes supérieure/inférieure en arrondissant vers le haut/bas). Affecte toutes les opérations FP suivantes jusqu'au changement. Restaurez à nearest lorsque terminé. Certains compilateurs optimisent en supposant nearest — utilisez avec soin.

fortran
program rounding_modes
  use ieee_arithmetic, only: ieee_set_rounding_mode, &
                             ieee_nearest, ieee_down, ieee_up, ieee_to_zero
  implicit none
  real :: x, y, result

  x = 1.0 / 3.0
  print *, 'Nearest:', x

  call ieee_set_rounding_mode(ieee_down)
  result = 1.0 / 3.0
  print *, 'Down:', result

  call ieee_set_rounding_mode(ieee_up)
  result = 1.0 / 3.0
  print *, 'Up:', result

  call ieee_set_rounding_mode(ieee_to_zero)
  result = 1.0 / 3.0
  print *, 'To zero:', result

  call ieee_set_rounding_mode(ieee_nearest)  ! restore default
end program

Interrogation des fonctionnalités IEEE

ieee_features et les fonctions d'interrogation ieee_arithmetic vous permettent de vérifier quelles fonctionnalités IEEE le compilateur/plateforme prend en charge. ieee_support_nan, ieee_support_inf, ieee_support_rounding, ieee_support_datatype, etc. Utile pour le code portable — dégradez gracieusement sur les systèmes non-IEEE. La plupart des systèmes modernes prennent en charge toutes les fonctionnalités.

fortran
program ieee_inquiry
  use ieee_features
  use ieee_arithmetic, only: ieee_support_nan, ieee_support_inf, &
                             ieee_support_rounding, ieee_nearest
  implicit none

  if (ieee_support_nan(1.0)) print *, 'NaN supported'
  if (ieee_support_inf(1.0)) print *, 'Inf supported'
  if (ieee_support_rounding(ieee_nearest, 1.0)) &
    print *, 'Nearest rounding supported'

  ! Check halting mode
  block
    use ieee_exceptions
    logical :: halting
    call ieee_get_halting_mode(ieee_overflow, halting)
    print *, 'Overflow halts:', halting
  end block
end program

Contrôle de l'environnement FP

Contrôlez le comportement FP par section : désactivez l'arrêt pour le code risqué, vérifiez les drapeaux après, restaurez. ieee_all correspond à toutes les exceptions. Modèle : effacez les drapeaux, exécutez le calcul, vérifiez les drapeaux, gérez les erreurs. Pour la production, préférez les vérifications explicites (ieee_is_nan) à la détection basée sur les drapeaux — les drapeaux peuvent être définis par du code non lié.

fortran
program fp_env
  use ieee_arithmetic
  use ieee_exceptions
  implicit none
  real :: a, b, c

  ! Save/restore FP state
  block
    use iso_fortran_env, only: real32
    real(real32) :: saved_state
    ! (no portable save/restore in standard — use compiler intrinsics)
  end block

  ! Disable halting for a risky section
  call ieee_set_halting_mode(ieee_all, .false.)
  call ieee_set_flag(ieee_all, .false.)

  a = sqrt(-1.0)  ! NaN, no halt
  b = 1.0 / 0.0   ! Inf, no halt

  if (ieee_is_nan(a)) then
    print *, 'Got NaN, using fallback'
    a = 0.0
  end if

  ! Re-enable halting
  call ieee_set_halting_mode(ieee_divide_by_zero, .true.)
  call ieee_set_halting_mode(ieee_invalid, .true.)
end program
16

Performance & Optimisation

Ordonnancement de tableau et contiguïté

Les tableaux Fortran sont colonne-major : a(i,j) et a(i+1,j) sont adjacents en mémoire. L'ordre des boucles compte : la boucle la plus interne devrait itérer le premier index. Le mauvais ordre cause des cache misses — ralentissement de 10x+. La syntaxe de tableau (sum, matmul) laisse le compilateur optimiser. Utilisez des tableaux contigus (pas de pointeurs) pour les meilleures performances.

fortran
program array_perf
  implicit none
  real, allocatable :: a(:,:), b(:,:)
  integer :: i, j, n
  real :: s

  n = 1000
  allocate(a(n,n), b(n,n))

  ! GOOD: column-major access (Fortran is column-major)
  do j = 1, n
    do i = 1, n
      s = s + a(i,j)
    end do
  end do

  ! BAD: row-major access (cache misses)
  do i = 1, n
    do j = 1, n
      s = s + a(i,j)
    end do
  end do

  ! BEST: array syntax (compiler optimizes)
  s = sum(a)
end program

Procédures pures et élémentaires

Les procédures pure n'ont pas d'effets de bord — le compilateur peut optimiser, paralléliser et réordonnancer les appels. Les procédures élémentaires fonctionnent à la fois sur les scalaires et les tableaux (auto-vectorisées). pure elemental combine les deux. Utilisez-les pour les fonctions math. Restrictions : pas d'E/S, pas de modification globale, intent(in) pour toutes les entrées. Permet une meilleure optimisation que les procédures régulières.

fortran
module math_ops
  implicit none
contains
  ! pure: no side effects, enables optimization
  pure function square(x) result(y)
    real, intent(in) :: x
    real :: y
    y = x * x
  end function

  ! elemental: works on scalars and arrays element-wise
  pure elemental function clamp(x, lo, hi) result(y)
    real, intent(in) :: x, lo, hi
    real :: y
    y = max(lo, min(x, hi))
  end function
end module

program use_math
  use math_ops
  implicit none
  real :: arr(100), scalar

  arr = clamp(arr, 0.0, 1.0)  ! applies to each element
  scalar = clamp(1.5, 0.0, 1.0)  ! also works on scalar
  arr = square(arr)  ! elemental too (if declared)
end program

Indicateurs d'optimisation du compilateur

Commencez avec -O2 -march=native pour la production. -O3 peut aider ou nuire — benchmarkz. -Ofast casse la conformité IEEE (ne l'utilisez pas si NaN/Inf comptent). -flto permet l'inlining inter-fichiers (compilation plus lente, exécution plus rapide). Utilisez -fcheck=all et -ffpe-trap dans les builds de débogage pour attraper les erreurs. L'optimisation guidée par profil (-fprofile-use) donne 5-15% d'accélération.

fortran
! gfortran optimization flags:
!   -O0   no optimization (debug)
!   -O1   basic optimization
!   -O2   standard optimization (recommended)
!   -O3   aggressive (may increase code size)
!   -Ofast  -O3 + -ffast-math (breaks IEEE compliance)
!   -march=native  use CPU's full instruction set
!   -flto  link-time optimization (cross-file inlining)
!   -fopenmp  enable OpenMP pragmas
!   -fprofile-generate/use  profile-guided optimization

! Example Makefile:
!   FC = gfortran
!   FFLAGS = -O2 -march=native -Wall -fcheck=all
!   FFLAGS_RELEASE = -O3 -march=native -flto -fno-trapping-math
!   FFLAGS_DEBUG = -O0 -g -fcheck=all -fbacktrace -ffpe-trap=invalid,zero

! ifort equivalents:
!   -O2, -O3, -xHost (= -march=native), -ipo (= -flto)

Parallélisme OpenMP

OpenMP ajoute du parallélisme via des directives (!$omp). 'parallel do' parallélise la boucle suivante. 'reduction(+:s)' gère les sommes en toute sécurité. Compilez avec -fopenmp (gfortran) ou -qopenmp (ifort). Définissez le nombre de threads via la variable d'environnement OMP_NUM_THREADS. Meilleur pour les boucles liées au CPU avec des itérations indépendantes. Méfiez-vous du faux partage et du déséquilibre de charge.

fortran
program openmp_demo
  use omp_lib
  implicit none
  real, allocatable :: a(:), b(:), c(:)
  integer :: i, n, nthreads

  n = 10000000
  allocate(a(n), b(n), c(n))
  a = 1.0; b = 2.0

  !$omp parallel
  if (omp_get_thread_num() == 0) then
    nthreads = omp_get_num_threads()
    print *, 'Threads:', nthreads
  end if
  !$omp end parallel

  !$omp parallel do
  do i = 1, n
    c(i) = a(i) + b(i)
  end do
  !$omp end parallel do

  ! reduction
  block
    real :: s
    s = 0.0
    !$omp parallel do reduction(+:s)
    do i = 1, n
      s = s + c(i)
    end do
    !$omp end parallel do
    print *, 'Sum:', s
  end block
end program

Profilage et détection des points chauds

system_clock donne un chronométrage portable. Pour le profilage sérieux, utilisez gprof (compilez avec -pg), perf (Linux) ou Intel VTune. Profilez avant d'optimiser — les surprises sont courantes. Concentrez-vous sur les points chauds (les 20% de code qui prennent 80% du temps). Optimisez les boucles les plus internes d'abord. Benchmarkz toujours avant/après les modifications — l'intuition est souvent fausse.

fortran
! Compile with profiling:
!   gfortran -pg -O2 prog.f90 -o prog
!   ./prog  (generates gmon.out)
!   gprof prog gmon.out > profile.txt

! Or use perf (Linux):
!   perf record ./prog
!   perf report

! Or gprofng (modern):
!   gprofng collect app ./prog
!   gprofng analyze

! Manual timing:
program timing
  use iso_fortran_env, only: real64, int64
  implicit none
  integer(int64) :: start, finish, rate
  real(real64) :: elapsed
  real, allocatable :: a(:)
  integer :: i

  allocate(a(10000000))
  call system_clock(start, rate)
  do i = 1, 100
    a = a + 1.0
  end do
  call system_clock(finish)
  elapsed = real(finish - start, real64) / real(rate, real64)
  print *, 'Elapsed:', elapsed, 'sec'
end program
17

Programmation en langage mixte

Fortran appelant une bibliothèque C

Enveloppez les fonctions de bibliothèque C dans un module d'interface Fortran. Utilisez bind(C, name='...') pour lier au symbole C exact. Passez les pointeurs comme c_ptr avec 'value'. Pour les arguments de pointeur de fonction (comme le comparateur de qsort), utilisez une abstract interface. Cela vous permet d'appeler n'importe quelle fonction C — y compris libc, BLAS, appels système.

fortran
module c_glue
  use iso_c_binding
  implicit none
  interface
    function c_malloc(size) bind(C, name='malloc')
      import :: c_ptr, c_size_t
      integer(c_size_t), value :: size
      type(c_ptr) :: c_malloc
    end function

    subroutine c_free(ptr) bind(C, name='free')
      import :: c_ptr
      type(c_ptr), value :: ptr
    end subroutine

    subroutine c_qsort(base, nmemb, size, compar) bind(C, name='qsort')
      import :: c_ptr, c_size_t, c_int
      type(c_ptr), value :: base
      integer(c_size_t), value :: nmemb, size
      abstract interface
        function compar_fn(a, b) bind(C)
          import :: c_ptr
          type(c_ptr), value :: a, b
          integer(c_int) :: compar_fn
        end function
      end interface
      procedure(compar_fn) :: compar
    end subroutine
  end interface
end module

Interop C++ via extern C

C++ décore les noms de symboles, donc enveloppez les fonctions C++ dans des blocs extern "C". L'interface Fortran se lie ensuite au nom non décoré. Liez avec -lstdc++ (gfortran) ou utilisez un éditeur de liens C++. Cela vous permet d'utiliser des bibliothèques C++ (STL, Boost, Qt) depuis Fortran. Pour les classes C++, écrivez un wrapper d'API C plat, puis appelez depuis Fortran.

fortran
! C++ side (wrapper.cpp):
!   extern "C" {
!     void cpp_process(double* data, int n) {
!       std::vector<double> v(data, data+n);
!       std::sort(v.begin(), v.end());
!       std::copy(v.begin(), v.end(), data);
!     }
!   }

! Fortran side:
module cpp_glue
  use iso_c_binding
  implicit none
  interface
    subroutine cpp_process(data, n) bind(C, name='cpp_process')
      import :: c_double, c_int
      real(c_double), intent(inout) :: data(*)
      integer(c_int), value :: n
    end subroutine
  end interface
end module

program use_cpp
  use cpp_glue
  implicit none
  real(c_double) :: arr(10) = [5.d0, 3.d0, 8.d0, 1.d0, 9.d0, &
                                2.d0, 7.d0, 4.d0, 6.d0, 0.d0]
  call cpp_process(arr, size(arr, kind=c_int))
  print *, arr
end program

! Build: gfortran use_cpp.f90 wrapper.cpp -lstdc++ -o app

Interop Python via f2py

f2py (partie de numpy) génère automatiquement des liaisons Python pour Fortran. Il lit les attributs intent et crée des interfaces Python/numpy appropriées. intent(out) devient une valeur de retour. Les tableaux mappent vers des tableaux numpy (zéro-copie lorsqu'ils sont contigus). Excellent pour le code numérique critique en performance appelé depuis Python. Support limité des fonctionnalités F2003+.

fortran
! Fortran module (myfuncs.f90):
module myfuncs
  implicit none
contains
  subroutine compute(arr, n, result)
    integer, intent(in) :: n
    real(8), intent(in) :: arr(n)
    real(8), intent(out) :: result
    result = sum(arr**2)
  end subroutine

  function fast_sin(x) result(y)
    real(8), intent(in) :: x
    real(8) :: y
    y = sin(x)
  end function
end module

! Build Python extension:
!   f2py -c myfuncs.f90 -m myfuncs
!   (creates myfuncs.cpython-*.so)

! Python usage:
!   import numpy as np
!   import myfuncs
!   arr = np.array([1.0, 2.0, 3.0])
!   result = myfuncs.myfuncs.compute(arr)
!   y = myfuncs.myfuncs.fast_sin(1.5)

Bibliothèque partagée et chargement dynamique

Utilisez dlopen/dlsym (POSIX) ou LoadLibrary/GetProcAddress (Windows) pour les plugins chargés à l'exécution. Enveloppez dans des interfaces iso_c_binding. Convertissez c_ptr en pointeur de procédure avec c_f_procpointer. Cela permet les architectures de plugins — chargez différentes implémentations à l'exécution. Multiplateforme : utilisez #ifdef pour les chargeurs spécifiques à l'OS.

fortran
module dynamic_loader
  use iso_c_binding
  implicit none
  interface
    function dlopen(filename, flag) bind(C, name='dlopen')
      import :: c_ptr, c_char, c_int
      character(kind=c_char), dimension(*) :: filename
      integer(c_int), value :: flag
      type(c_ptr) :: dlopen
    end function

    function dlsym(handle, name) bind(C, name='dlsym')
      import :: c_ptr, c_char
      type(c_ptr), value :: handle
      character(kind=c_char), dimension(*) :: name
      type(c_ptr) :: dlsym
    end function

    function dlclose(handle) bind(C, name='dlclose')
      import :: c_ptr, c_int
      type(c_ptr), value :: handle
      integer(c_int) :: dlclose
    end function
  end interface
end module

program plugin
  use dynamic_loader
  implicit none
  type(c_ptr) :: lib, sym
  integer, parameter :: RTLD_NOW = 2

  lib = dlopen('./plugin.so' // c_null_char, RTLD_NOW)
  if (.not. c_associated(lib)) then
    print *, 'Failed to load library'
    stop 1
  end if

  sym = dlsym(lib, 'init' // c_null_char)
  ! call init via c_f_procpointer...
  print *, dlclose(lib)
end program

Intégration du système de build

CMake gère bien les builds en langage mixte. Déclarez tous les langages dans project(). Définissez les indicateurs par langage. Liez les bibliothèques dans l'ordre des dépendances. CMake suit automatiquement les dépendances de module Fortran (pas d'ordonnancement .mod manuel). Pour la liaison de bibliothèque standard C++, utilisez le bon indicateur par plateforme. Utilisez Fortran comme éditeur de liens principal si le runtime Fortran est nécessaire.

fortran
# CMakeLists.txt for mixed Fortran/C/C++ project:
cmake_minimum_required(VERSION 3.18)
project(mixed LANGUAGES Fortran C CXX)

set(CMAKE_Fortran_FLAGS "-O2 -fopenmp")
set(CMAKE_C_FLAGS "-O2")
set(CMAKE_CXX_FLAGS "-O2 -std=c++17")

add_library(fortran_lib STATIC mymod.f90)
add_library(c_lib STATIC helper.c)
add_library(cpp_lib STATIC wrapper.cpp)

# Mixed-language executable
add_executable(app main.f90)
target_link_libraries(app fortran_lib c_lib cpp_lib)

# Fortran needs C runtime
if(APPLE)
  target_link_libraries(app "-lc++")
else()
  target_link_libraries(app stdc++)
endif()

# Module dependency tracking (CMake handles automatically)
#   fortran_lib depends on its own .mod files
#   app depends on fortran_lib's .mod files
18

E/S modernes

Newunit et gestion sûre des fichiers

newunit= évite les collisions de numéro d'unité — le compilateur choisit un numéro libre. Vérifiez toujours iostat après open/read/write — non nul signifie erreur. iomsg donne un message d'erreur lisible par l'homme. action= ('read', 'write', 'readwrite') empêche l'utilisation accidentelle incorrecte. status= ('old', 'new', 'replace', 'scratch', 'unknown') contrôle la création de fichier.

fortran
program safe_io
  implicit none
  integer :: u, ios
  character(256) :: msg

  ! newunit: compiler picks an unused unit number
  open(newunit=u, file='data.txt', status='old', action='read', &
       iostat=ios, iomsg=msg)
  if (ios /= 0) then
    print *, 'Open failed:', trim(msg)
    stop 1
  end if

  ! read with error handling
  read(u, *, iostat=ios, iomsg=msg) some_value
  if (ios /= 0) then
    print *, 'Read failed:', trim(msg)
  end if

  close(u)
contains
  integer function some_value()
    some_value = 0
  end function
end program

E/S en flux (binaire)

L'accès en flux (F2003) donne des E/S orientées octets comme C — pas de marqueurs d'enregistrement. access='stream' l'active. pos= lit/écrit à un offset d'octet spécifique. form='unformatted' pour binaire. Excellent pour les fichiers binaires interopérables (lisez les fichiers écrits en Fortran depuis C/Python). L'accès séquentiel par défaut utilise des marqueurs d'enregistrement (non portable).

fortran
program stream_io
  implicit none
  integer :: u, i
  real, allocatable :: data(:)

  ! stream access = byte-oriented (like C fread/fwrite)
  open(newunit=u, file='data.bin', access='stream', &
       form='unformatted', status='replace')

  data = [(real(i), i=1,100)]
  write(u) data  ! binary, no record markers
  close(u)

  ! read back
  allocate(data(100))
  open(newunit=u, file='data.bin', access='stream', &
       form='unformatted', status='old', action='read')
  read(u) data
  close(u)

  ! position-based access
  open(newunit=u, file='data.bin', access='stream', &
       form='unformatted', status='old')
  read(u, pos=21) data(1)  ! read 5th real (4 bytes each)
  close(u)
end program

E/S de type dérivé

Les E/S de Type Dérivé Définies par l'Utilisateur (F2003) vous permettent de contrôler comment les types sont lus/écrits. Définissez une sous-routine avec la signature spécifique et liez-la via 'generic :: write(formatted)'. Le code de format DT la déclenche. Permet la sérialisation personnalisée (CSV, de type JSON, binaire). iotype est 'LISTDIRECTED', 'NAMELIST' ou 'DT' pour les E/S formatées.

fortran
module person_type
  implicit none
  type :: person
    character(20) :: name
    integer :: age
    real :: height
  end type
contains
  ! custom formatted I/O via DT format
  subroutine write_person(dtv, unit, iotype, v_list, iostat, iomsg)
    class(person), intent(in) :: dtv
    integer, intent(in) :: unit
    character(*), intent(in) :: iotype
    integer, intent(in) :: v_list(:)
    integer, intent(out) :: iostat
    character(*), intent(inout) :: iomsg
    write(unit, '(a,",",i0,",",f0.2)', iostat=iostat) &
      trim(dtv%name), dtv%age, dtv%height
  end subroutine
end module

program use_dtv
  use person_type
  implicit none
  type(person) :: p = person('Alice', 30, 5.7)

  ! DT format triggers custom writer
  print "(DT)", p  ! calls write_person
end program

E/S Namelist

Namelist fournit des E/S lisibles par l'homme, basées sur le nom, pour des groupes de variables. Format : &group_name var=value, ... /. La lecture met à jour uniquement les variables présentes dans le fichier — les autres conservent leurs valeurs. Excellent pour les fichiers de configuration — les utilisateurs éditent du texte, pas d'analyseur nécessaire. Limitations : pas de commentaires dans certains compilateurs, support de type limité.

fortran
program namelist_demo
  implicit none
  integer :: n_iterations = 100
  real :: tolerance = 1.0e-6
  character(50) :: output_file = 'results.txt'
  logical :: verbose = .true.

  namelist /config/ n_iterations, tolerance, output_file, verbose

  ! write namelist
  open(newunit=u, file='config.nml', status='replace')
  write(u, nml=config)
  close(u)

  ! read namelist (only specified vars are updated)
  open(newunit=u, file='config.nml', status='old', action='read')
  read(u, nml=config)
  close(u)

  print *, 'Iterations:', n_iterations
end program

! config.nml format:
! &config
!   n_iterations = 500
!   tolerance = 1.0e-8
!   verbose = .false.
! /

E/S asynchrones

Les E/S asynchrones (F2003) chevauchent les E/S avec le calcul. write(..., asynchronous='yes') démarre une opération non bloquante. wait(unit) bloque jusqu'à ce qu'elle se termine. Utile pour les grands jeux de données — commencez à écrire pendant le calcul du prochain morceau. Le support du compilateur varie. Utilisez inquire(unit=u, pending=...) pour vérifier le statut. Associez à un double-buffering pour les pipelines.

fortran
program async_io
  implicit none
  integer :: u1, u2, ios
  real, allocatable :: a(:), b(:)

  allocate(a(1000000), b(1000000))
  a = 1.0; b = 2.0

  open(newunit=u1, file='a.bin', access='stream', form='unformatted', &
       asynchronous='yes')
  open(newunit=u2, file='b.bin', access='stream', form='unformatted', &
       asynchronous='yes')

  ! non-blocking writes
  write(u1, asynchronous='yes') a
  write(u2, asynchronous='yes') b

  ! do other work while I/O proceeds...

  ! wait for completion
  wait(u1)
  wait(u2)

  close(u1); close(u2)
end program
19

Débogage & Profilage

Indicateurs de débogage à la compilation

Utilisez -fcheck=all pour la vérification des bornes (détecte les erreurs off-by-one). -ffpe-trap s'arrête sur NaN/Inf/overflow — inestimable pour le code numérique. -finit-real=nan rend les variables non initialisées visibles (elles se propagent comme NaN). -fbacktrace imprime une trace de pile sur crash. Déboguez toujours avec ces indicateurs ; supprimez-les pour les builds de production (ils ralentissent le code).

fortran
! gfortran debug build:
!   gfortran -O0 -g -fcheck=all -fbacktrace -ffpe-trap=invalid,zero,overflow \
!            -Wall -Wextra -Wpedantic -finit-real=nan prog.f90

! Flag explanations:
!   -O0          no optimization (easier debugging)
!   -g           debug symbols (for gdb)
!   -fcheck=all  bounds, pointer, recursion checks
!   -fbacktrace  print stack trace on error
!   -ffpe-trap   halt on FP exceptions (invalid, zero, overflow)
!   -finit-real=nan  initialize reals to NaN (catch uninitialized)
!   -finit-integer=-9999  initialize integers to sentinel
!   -Wall -Wextra  more warnings

! Runtime error message:
!   At line 42 of file prog.f90
!   Fortran runtime error: Array bound out of bounds for dimension 1

Gestion d'erreurs avec iostat

iostat : négatif = EOF, zéro = succès, positif = erreur. iomsg donne les détails. Lisez d'abord dans une chaîne, puis analysez — sépare les erreurs d'E/S des erreurs d'analyse. Suivez les numéros de ligne pour des messages d'erreur utiles. Gérez toujours les erreurs explicitement — les échecs silencieux sont difficiles à déboguer. Utilisez stop avec un code non nul pour signaler l'échec aux scripts.

fortran
program robust_io
  implicit none
  integer :: u, ios, line_num
  character(256) :: msg, line
  real :: value

  open(newunit=u, file='data.txt', status='old', action='read', &
       iostat=ios, iomsg=msg)
  if (ios /= 0) call error_exit('Open: ' // trim(msg))

  line_num = 0
  do
    line_num = line_num + 1
    read(u, '(a)', iostat=ios) line
    if (ios < 0) exit  ! EOF
    if (ios > 0) then
      print *, 'Read error at line', line_num
      cycle
    end if

    read(line, *, iostat=ios, iomsg=msg) value
    if (ios /= 0) then
      print *, 'Parse error at line', line_num, ':', trim(msg)
      cycle
    end if
    ! process value
  end do
  close(u)
contains
  subroutine error_exit(m)
    character(*), intent(in) :: m
    print *, 'ERROR:', m
    stop 1
  end subroutine
end program

GDB pour Fortran

GDB prend en charge Fortran : tranches de tableau, composants de type dérivé, procédures de module (format de nom : modname__procname). Utilisez -g -O0 pour la meilleure expérience de débogage. 'display' imprime automatiquement les variables à chaque arrêt — utile pour surveiller les boucles. 'info locals' montre tous les locaux. Pour les variables de module, utilisez 'print modname::varname'.

fortran
! Compile: gfortran -g -O0 prog.f90 -o prog
! Start: gdb ./prog

! Common GDB commands for Fortran:
!   (gdb) break main           break at main
!   (gdb) break prog.f90:42     break at line 42
!   (gdb) break mymod__my_sub   break at subroutine (note double underscore)
!   (gdb) run                   start program
!   (gdb) next                  step over
!   (gdb) step                  step into
!   (gdb) print arr(5)          print array element
!   (gdb) print arr(1:10)       print array slice
!   (gdb) print mat(2,3)        print 2D array element
!   (gdb) print p%name          print derived type component
!   (gdb) display x             watch variable (auto-print on stop)
!   (gdb) backtrace             show call stack
!   (gdb) info locals           show all local variables
!   (gdb) continue              resume execution

Profilage gprof

gprof échantillonne le compteur de programme pendant l'exécution. Compilez TOUS les fichiers source avec -pg pour des profils complets. Le profil plat montre où le temps est passé ; le graphe d'appels montre la hiérarchie d'appels. Concentrez-vous sur les fonctions avec un temps 'self' élevé — c'est là que l'optimisation aide. Note : -pg change le timing — le code de type profil peut se comporter différemment en production.

fortran
! Compile with profiling:
!   gfortran -pg -O2 prog.f90 -o prog
! Run:
!   ./prog
!   (creates gmon.out)
! Analyze:
!   gprof prog gmon.out > profile.txt
!   gprof prog gmon.out | less

! profile.txt sections:
!   - Flat profile: time per function (self + cumulative)
!   - Call graph: who called whom, how many times
!   - Index: function cross-references

! Key columns:
!   %time  percentage of total time
!   cumulative  running total
!   self    seconds in function (excluding children)
!   calls   number of calls
!   self/call  average time per call (self)

! For multi-threaded: use gprofng (modern) or perf

Benchmarking de performance

Benchmarkz correctement : échauffez d'abord (effets de cache), exécutez plusieurs itérations, prenez le minimum (moins de bruit). system_clock avec count_rate donne le temps écoulé. Rapportez le débit (éléments/sec) pour les opérations de tableau. Comparez les implémentations sur la même machine avec les mêmes indicateurs. Attention : le compilateur peut optimiser les résultats 'inutilisés' — utilisez le résultat (par ex., print sum) pour empêcher cela.

fortran
program benchmark
  use iso_fortran_env, only: real64, int64
  implicit none
  integer, parameter :: n = 1000000
  integer, parameter :: niter = 100
  real(real64), allocatable :: a(:), b(:), c(:)
  integer(int64) :: start, finish, rate
  real(real64) :: t_start, t_end, min_time
  integer :: i, iter

  allocate(a(n), b(n), c(n))
  a = 1.0_real64; b = 2.0_real64

  ! warmup (cache, JIT-like effects)
  do i = 1, n
    c(i) = a(i) + b(i)
  end do

  min_time = huge(min_time)
  call system_clock(count_rate=rate)
  do iter = 1, niter
    call system_clock(start)
    do i = 1, n
      c(i) = a(i) + b(i)
    end do
    call system_clock(finish)
    t_start = real(start, real64) / rate
    t_end = real(finish, real64) / rate
    min_time = min(min_time, t_end - t_start)
  end do

  print *, 'Best time:', min_time * 1e6, 'us'
  print *, 'Throughput:', real(n) / min_time / 1e9, 'G elem/s'
end program
20

Fortran moderne

Forme libre

Le Fortran moderne (90+) utilise la forme libre : pas de restrictions de colonnes. Les commentaires commencent par !. Les instructions peuvent s'étendre sur plusieurs lignes avec &. Implicit none est obligatoire pour la sécurité de type. Beaucoup plus lisible que le Fortran 77 en forme fixe.

fortran
program modern
    implicit none
    integer :: i
    do i = 1, 10
        print *, "Value:", i
    end do
end program modern
! Free form: no column restrictions
! Comments start with !

Modules

Les modules regroupent les procédures et données associées. use importe un module. contains sépare les déclarations au niveau du module des procédures. Les modules fournissent des interfaces explicites, permettant la vérification de type. Préférez les modules aux procédures externes.

fortran
module math_utils
    implicit none
    contains
    function square(x) result(y)
        real, intent(in) :: x
        real :: y
        y = x * x
    end function square
end module math_utils

program test
    use math_utils
    print *, square(3.0)  ! 9.0
end program test

Types dérivés

Les types dérivés sont des structures de données définies par l'utilisateur (structs). % accède aux composants. Les procédures liées au type permettent la POO. Les constructeurs créent des instances. Les types dérivés peuvent avoir des valeurs par défaut et étendre d'autres types (héritage). Utilisez pour la modélisation de données complexes.

fortran
type :: Point
    real :: x, y
end type Point
type(Point) :: p
p = Point(1.0, 2.0)  ! Constructor
p%x = 3.0  ! Component access
print *, p%x, p%y
! Type-bound procedures (OOP)
type :: Circle
    real :: radius
contains
    procedure :: area => circle_area
end type

Attributs Intent

Les paramètres intent(in) sont en lecture seule (ne peuvent pas être modifiés). intent(out) est en écriture seule (défini par la procédure). intent(inout) est en lecture-écriture. Le compilateur vérifie les violations d'intent. Améliore la clarté du code et permet les optimisations. Spécifiez toujours intent.

fortran
subroutine process(input, output, inout)
    integer, intent(in) :: input    ! Read-only
    integer, intent(out) :: output  ! Write-only
    integer, intent(inout) :: inout ! Read-write
    output = input * 2
    inout = inout + 1
end subroutine

Pure & Élémentaire

Les fonctions pure n'ont pas d'effets de bord (pas d'E/S, pas d'état mutable). Le compilateur peut les optimiser. Les fonctions élémentaires fonctionnent à la fois sur les scalaires et les tableaux automatiquement. Elles sont pure par défaut. Idéales pour les opérations mathématiques. Permet l'exécution parallèle.

fortran
pure function square(x) result(y)
    real, intent(in) :: x
    real :: y
    y = x * x
end function
! Elemental: works on scalars and arrays
elemental function double_it(x) result(y)
    real, intent(in) :: x
    real :: y
    y = 2.0 * x
end function
! double_it([1,2,3]) returns [2,4,6]
21

Tableaux

Déclaration de tableau

Les tableaux Fortran sont indexés à 1 par défaut. Bornes inférieures personnalisées avec (0:). Ordre colonne-major (le premier index varie le plus vite). Les tableaux allocatable sont alloués sur le tas et doivent être désalloués. Les constantes de tableau utilisent [ ]. Les tableaux Fortran sont plus efficaces que les tableaux C grâce aux descripteurs.

fortran
! Static arrays
real :: a(10)           ! 1D, indices 1-10
real :: b(0:9)          ! 1D, indices 0-9
real :: c(3, 4)         ! 2D, 3 rows x 4 cols
! Allocatable (dynamic)
real, allocatable :: d(:)
allocate(d(100))        ! Allocate
deallocate(d)           ! Free
! Array constants
integer :: nums(5) = [1, 2, 3, 4, 5]

Opérations de tableau

Fortran prend en charge les opérations de tableau entier : +, -, *, /, **. Pas de boucles nécessaires pour les maths élément par élément. Fonctions intrinsèques : sum, product, maxval, minval, any, all, count. Beaucoup plus rapide que les boucles grâce à la vectorisation. C'est la force de Fortran pour le calcul numérique.

fortran
real :: a(5) = [1, 2, 3, 4, 5]
real :: b(5)
b = a * 2          ! Element-wise: [2,4,6,8,10]
b = a + 1          ! [2,3,4,5,6]
print *, sum(a)    ! 15
print *, maxval(a) ! 5
print *, any(a > 3)  ! .true.
print *, count(a > 2) ! 3

Sections de tableau

Les sections de tableau utilisent la syntaxe (start:end:stride). :: signifie le défaut (1 à la fin, pas 1). Les pas négatifs inversent. Les sections multidimensionnelles fonctionnent sur n'importe quelle dimension. Les sections peuvent être passées aux procédures. Très puissant pour le découpage sans copie.

fortran
real :: a(10) = [(i, i=1,10)]
print *, a(3:7)     ! Elements 3 to 7
print *, a(::2)     ! Every other: [1,3,5,7,9]
print *, a(2:8:2)   ! Stride 2: [2,4,6,8]
real :: m(3,3)
m(:, 2)             ! Second column
m(1, :)             ! First row

Construction where

where est une affectation conditionnelle au niveau du tableau. Comme un if vectorisé. Elsewhere gère le cas faux. Plus efficace que les boucles car il peut être vectorisé. Utilisez pour les opérations conditionnelles élément par élément sur les tableaux.

fortran
real :: a(5) = [1, -2, 3, -4, 5]
where (a > 0)
    a = a * 2       ! Double positives
elsewhere
    a = 0           ! Zero negatives
end where
! Result: [2, 0, 6, 0, 10]
! Equivalent to a loop with if

Allocation dynamique

Les tableaux allocatable sont dimensionnés dynamiquement. allocate crée, deallocate libère. Fortran 2003+ auto-désalloue à la fin de la portée. Vérifiez le statut d'allocation avec allocated(). Les tableaux allocatable sont automatiquement réalloués à l'affectation (Fortran 2003+). Beaucoup plus sûr que C malloc/free.

fortran
real, allocatable :: matrix(:,:)
integer :: n
n = 100
allocate(matrix(n, n))
matrix = 0.0  ! Initialize all to 0
! ... use matrix ...
deallocate(matrix)
! Automatic deallocation at end of scope
! (Fortran 2003+)
22

Opérations d'E/S

Sortie formatée

Les chaînes de format contrôlent la sortie. I5 = entier largeur 5. F8.3 = flottant largeur 8, 3 décimales. I0 = largeur minimale. A = chaîne. X = espace. / = nouvelle ligne. write(*,...) est identique à print mais plus flexible. Utilisez des chaînes de format pour une sortie alignée.

fortran
integer :: i = 42
real :: x = 3.14159
print "(I5, F8.3)", i, x    ! "   42    3.142"
print "(A, I0)", "Count=", i  ! "Count=42"
write(*, "(3F6.2)") 1.0, 2.0, 3.0
! Format specifiers:
! I: integer, F: float, E: exponential
! A: string, X: space, /: newline

E/S fichier

open connecte un fichier à une unité. newunit attribue un numéro d'unité libre. status : old (doit exister), new (ne doit pas exister), replace. iostat retourne non nul sur erreur ou EOF. Vérifiez toujours iostat pour éviter les crashes. close déconnecte le fichier.

fortran
integer :: unit, ios
open(newunit=unit, file="data.txt", status="old", action="read")
do
    read(unit, *, iostat=ios) value
    if (ios /= 0) exit
    print *, value
end do
close(unit)
! status: old, new, replace, scratch
! action: read, write, readwrite

Namelist

namelist groupe des variables pour les E/S. Le format de fichier est &config n=10, x=3.14 /. Utile pour les fichiers de configuration. Les variables peuvent être dans n'importe quel ordre. Seules les variables listées sont lues/écrites. Beaucoup plus facile que d'analyser des formats personnalisés.

fortran
integer :: n = 10
real :: x = 3.14
namelist /config/ n, x
! Write namelist
open(1, file="config.nml")
write(1, nml=config)
close(1)
! Read namelist
open(1, file="config.nml")
read(1, nml=config)
close(1)

Fichiers internes

Les fichiers internes utilisent des chaînes de caractères comme unités d'E/S. write vers une chaîne convertit les valeurs en texte. read depuis une chaîne analyse le texte. Utile pour la conversion de type et le formatage. trim supprime les espaces de fin. Beaucoup plus simple que C sprintf/sscanf.

fortran
character(20) :: str
integer :: num = 42
! Integer to string
write(str, "(I0)") num
print *, trim(str)  ! "42"
! String to integer
read(str, *) num
! Internal files use character variables as units

E/S binaires

Les E/S non formatées écrivent des données binaires brutes. Plus rapide et plus compact que le texte. access="stream" pour l'accès au niveau des octets (Fortran 2008). Non portable entre architectures (endianness). Utilisez pour les grands jeux de données scientifiques. Les E/S formatées sont pour les données lisibles par l'homme.

fortran
! Unformatted (binary) I/O
open(1, file="data.bin", form="unformatted", &
     access="stream")
write(1) array  ! No format, raw bytes
read(1) array2
close(1)
! Faster than formatted I/O
! Smaller file size
! Not portable across architectures
23

Programmation parallèle

OpenMP

OpenMP parallélise les boucles avec des directives. !$omp parallel do distribue les itérations entre les threads. private : chaque thread a sa propre copie. reduction : combine les résultats. Compilez avec -fopenmp. Façon facile de paralléliser le code numérique.

fortran
!$omp parallel do private(i) reduction(+:sum)
do i = 1, n
    sum = sum + a(i) * b(i)
end do
!$omp end parallel do
! Compile: gfortran -fopenmp program.f90
! Environment: OMP_NUM_THREADS=4

Coarrays

Les coarrays (Fortran 2008) sont des tableaux parallèles intégrés. Chaque image (processus) a sa propre copie. [N] accède les données d'une autre image. sync all est une barrière. this_image() retourne le numéro d'image. Intégré au langage, pas de bibliothèque nécessaire.

fortran
program coarray_example
    implicit none
    integer :: me[*]  ! Coarray: one per image
    me = this_image()
    sync all  ! Barrier
    if (this_image() == 1) then
        print *, "Image 2 has:", me[2]  ! Remote access
    end if
end program
! Compile: gfortran -fcoarray=single program.f90

Bases MPI

MPI (Message Passing Interface) est le standard pour le parallélisme distribué. mpi_init/finalize démarrent et arrêtent. comm_rank donne l'ID de processus. comm_size donne le nombre total de processus. Send/recv pour la communication. Passe à des milliers de cœurs. Utilisez pour les clusters.

fortran
program mpi_example
    use mpi
    integer :: rank, size, ierr
    call mpi_init(ierr)
    call mpi_comm_rank(MPI_COMM_WORLD, rank, ierr)
    call mpi_comm_size(MPI_COMM_WORLD, size, ierr)
    print *, "I am rank", rank, "of", size
    call mpi_finalize(ierr)
end program
! Compile: mpifort program.f90

do concurrent

do concurrent (Fortran 2008) indique que les itérations de boucle sont indépendantes. Le compilateur peut paralléliser automatiquement. local déclare les variables privées. Plus sûr qu'OpenMP : le compilateur vérifie l'indépendance. Utilisez pour les boucles embarrassingly parallel.

fortran
do concurrent (i = 1:n) local(tmp)
    tmp = expensive_compute(a(i))
    b(i) = tmp * 2
end do
! Tells compiler iterations are independent
! Can be parallelized automatically
! local: private variable per iteration

Modèle de réduction

Les réductions combinent les résultats partiels de chaque thread. Courantes : sum, product, max, min. Chaque thread calcule un résultat partiel local. Le runtime les combine à la fin. Évite les courses de données. Essentiel pour les algorithmes numériques parallèles.

fortran
!$omp parallel do reduction(+:total)
do i = 1, n
    total = total + a(i)
end do
!$omp end parallel do
! Common reductions: +, *, max, min, .and., .or.
! Each thread has a private copy
! Combined at the end
24

Méthodes numériques

Algèbre linéaire

Fortran a des opérations matricielles intégrées. matmul multiplie les matrices. dot_product calcule le produit scalaire. transpose transpose. Celles-ci sont hautement optimisées (niveau BLAS). Pour la production, utilisez LAPACK. Fortran est le langage de choix pour le calcul numérique haute performance.

fortran
! Matrix multiplication
do i = 1, n
    do j = 1, n
        c(i,j) = sum(a(i,:) * b(:,j))
    end do
end do
! Or use matmul intrinsic
c = matmul(a, b)
! Dot product
dot = dot_product(a, b)
! Transpose
at = transpose(a)

Résolution d'ODE

La méthode d'Euler est le solveur d'ODE le plus simple : y(n+1) = y(n) + dt*f(t,y). Pour la précision, utilisez Runge-Kutta (RK4). Le bloc interface passe les fonctions en arguments. Fortran est idéal pour le calcul scientifique grâce aux opérations de tableau et à la performance.

fortran
! Euler method: dy/dt = f(t, y)
subroutine euler(f, t0, y0, dt, n, t, y)
    interface
        real function f(t, y)
            real, intent(in) :: t, y
        end function
    end interface
    real, intent(in) :: t0, y0, dt
    integer, intent(in) :: n
    real, intent(out) :: t(n+1), y(n+1)
    integer :: i
    t(1) = t0; y(1) = y0
    do i = 1, n
        t(i+1) = t(i) + dt
        y(i+1) = y(i) + dt * f(t(i), y(i))
    end do
end subroutine

Nombres aléatoires

random_number génère des réels uniformes [0,1). random_seed initialise le générateur. Pour les entiers, mettez à l'échelle et convertissez. Box-Muller transforme l'uniforme en distribution normale. Pour le travail sérieux, utilisez une bibliothèque (par ex., Mersenne Twister). Semez toujours pour la reproductibilité.

fortran
call random_seed()  ! Seed from system
call random_number(x)  ! x in [0, 1)
! Array of randoms
real :: arr(100)
call random_number(arr)
! Integer in range [1, 6]
integer :: dice
call random_number(r)
dice = int(r * 6) + 1
! Normal distribution (Box-Muller)
call random_number(u1)
call random_number(u2)
z = sqrt(-2*log(u1)) * cos(2*PI*u2)

Interpolation

L'interpolation linéaire estime les valeurs entre les points connus. Trouvez l'intervalle, puis interpolez. Pour des résultats plus lisses, utilisez l'interpolation par spline cubique. Les opérations de tableau de Fortran rendent cela concis. Vérifiez toujours les bornes pour éviter les erreurs d'extrapolation.

fortran
function interp(x, xs, ys) result(y)
    real, intent(in) :: x, xs(:), ys(:)
    real :: y
    integer :: i
    ! Find interval
    i = 1
    do while (i < size(xs) .and. x > xs(i+1))
        i = i + 1
    end do
    ! Linear interpolation
    y = ys(i) + (ys(i+1) - ys(i)) * &
        (x - xs(i)) / (xs(i+1) - xs(i))
end function

Intégration numérique

La règle des trapèzes approxime les intégrales : somme des trapèzes. Plus précis : la règle de Simpson. Pour les dimensions supérieures, utilisez la quadrature gaussienne. Fortran excelle à l'intégration numérique grâce à la performance. Validez toujours avec des solutions analytiques connues.

fortran
! Trapezoidal rule
function trapezoid(f, a, b, n) result(integral)
    interface
        real function f(x)
            real, intent(in) :: x
        end function
    end interface
    real, intent(in) :: a, b
    integer, intent(in) :: n
    real :: integral, h
    integer :: i
    h = (b - a) / n
    integral = (f(a) + f(b)) / 2
    do i = 1, n-1
        integral = integral + f(a + i*h)
    end do
    integral = integral * h
end function
25

Pièges courants

Indexation à partir de 1

Les tableaux Fortran sont indexés à 1 par défaut, contrairement à C/Python (indexé à 0). Cela cause des erreurs off-by-one lors du portage de code. Les bornes inférieures personnalisées (0:9) sont autorisées. Soyez cohérent au sein d'un projet. Vérifiez les bornes de tableau avec l'indicateur -fcheck=bounds du compilateur.

fortran
! Fortran arrays are 1-based by default
real :: a(10)  ! Indices 1 to 10
! a(0) = 1.0  ! Error: out of bounds
a(1) = 1.0    ! OK
! Custom bounds
real :: b(0:9)  ! Indices 0 to 9
b(0) = 1.0    ! OK

Typage implicite

Sans implicit none, les variables commençant par i-n sont entières, les autres réelles. Cela cause des bugs subtils (une faute de frappe crée une nouvelle variable). Utilisez toujours implicit none. Le Fortran moderne (2018+) peut le définir globalement avec -fimplicit-none. C'est la meilleure pratique Fortran la plus importante.

fortran
! BAD: implicit typing (Fortran 77 style)
program bad
    ! i-n start with integer by default
    i = 1      ! integer (implicit)
    x = 3.14   ! real (implicit)
end program
! GOOD: explicit typing
program good
    implicit none  ! Force explicit declaration
    integer :: i
    real :: x
end program

Ordre colonne-major

Fortran stocke les tableaux en colonne-major : m(1,1), m(2,1), m(3,1), m(1,2), ... Accéder colonne par colonne est cache-friendly. Le mauvais ordre de boucle cause des cache misses et ralentit de 10x+. Faites toujours correspondre l'ordre de boucle à la disposition mémoire. Contraire de C (ligne-major).

fortran
! Fortran is column-major (first index varies fastest)
real :: m(3, 3)
! Efficient: iterate over first index in inner loop
do j = 1, 3
    do i = 1, 3
        m(i, j) = 0.0  ! Cache-friendly
    end do
end do
! Inefficient: row-major access
do i = 1, 3
    do j = 1, 3
        m(i, j) = 0.0  ! Cache misses
    end do
end do

Passage par référence

Fortran passe les arguments par référence (comme les pointeurs C). Les sous-routines peuvent modifier les variables de l'appelant sauf si intent(in) est spécifié. Sans intent, les modifications accidentelles causent des bugs. Spécifiez toujours intent. intent(out) signale que la procédure définira la valeur.

fortran
subroutine modify(x)
    integer, intent(inout) :: x
    x = 99  ! Modifies the caller variable
end subroutine
! Fortran passes by reference by default
! intent(in) prevents modification
! Without intent, modification is allowed (dangerous)

Précision en virgule flottante

Le real par défaut est en simple précision (~7 chiffres), souvent insuffisant. Utilisez la double précision pour le calcul scientifique. kind(1.0d0) ou selected_real_kind(15) définit le double. Suffixez toujours les littéraux : 3.14_dp. Mélanger les précisions cause une troncature silencieuse. Utilisez iso_fortran_env pour des kinds portables.

fortran
! Single precision (default)
real :: x = 3.14159  ! ~7 digits
! Double precision
real(kind=8) :: y = 3.14159d0  ! ~15 digits
! Or use kind parameter
integer, parameter :: dp = kind(1.0d0)
real(dp) :: z = 3.14159_dp
! Always use _dp or d0 for double literals

Was this helpful?