Skip to content

Fortran Hoja de referencia

Lenguaje pionero para computación científica y numérica.

01

Fundamentos y Estructura del Programa

Estructura del Programa y Hello World

Cada programa Fortran comienza con 'program NAME' y termina con 'end program NAME'. 'implicit none' es OBLIGATORIO en Fortran moderno — fuerza declaración explícita de todas las variables (sin él, Fortran usa tipado implícito donde las variables que comienzan con i-n son integer, otras real, lo cual es una fuente importante de bugs). El bloque 'contains' separa código ejecutable de procedimientos internos (subroutines/functions definidas dentro del programa). Los comentarios comienzan con '!'. Free source form (Fortran 90+) usa extensión .f90; las columnas no importan. Compile con 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 y Tipos Intrínsecos

Fortran tiene 5 tipos intrínsecos: integer, real, complex, character, logical. 'kind' selecciona precisión/tamaño — use kind=8 para 64-bit (o mejor, use selected_real_kind/iso_fortran_env para portabilidad). Los literales real necesitan un sufijo kind: 3.14_8 (no solo 3.14). Double precision es sintaxis legacy para real(kind=8). Los literales complex usan forma (real, imag). Los valores logical son .true. / .false. (con puntos). Las cadenas character tienen un 'len' fijo a menos que se declaren con len=: y allocatable (deferred-length, Fortran 2003+). Siempre inicialice con el sufijo kind coincidente para evitar pérdida silenciosa de precisión.

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 y Parámetros

Las constantes usan el atributo 'parameter' y deben inicializarse en la declaración. No pueden modificarse — el compilador puede optimizarlas e inlinearlas. Use SCREAMING_SNAKE_CASE por convención. character(*) significa 'tomar la longitud del inicializador' (conveniente para constantes de cadena). Los parameters se usan comúnmente para tamaños de arrays, constantes físicas y códigos integer tipo-enum. Fortran 2003+ también tiene tipos ENUM propios pero los integers parameter siguen siendo la elección idiomática. Los parameters pueden usarse en declaraciones de dimensiones de arrays y otros contextos de expresión 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

Operadores y Expresiones

Operadores de Fortran: aritméticos (+ - * / **), con ** para exponenciación (único de Fortran). La división integer trunca hacia cero — use real(a)/b para división verdadera. Dos sintaxis relacionales: moderna (< > == /= <= >=) y legacy (.lt. .gt. .eq. .ne. .le. .ge.). Lógicos: .and. .or. .not. .eqv. (equivalencia) .neqv. (exclusive-or). La concatenación de cadenas usa //; trim() elimina espacios trailing (Fortran rellena cadenas de longitud fija con espacios). mod vs modulo: mod sigue el signo de división truncada, modulo sigue división floored — difieren para operandos negativos.

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

Funciones Intrínsecas y Math

Fortran tiene un conjunto rico de funciones intrínsecas (built-in). Math: abs, sqrt, exp, log (natural), log10, sin/cos/tan/asin/acos/atan/atan2, sinh/cosh/tanh. Redondeo: int (truncate), nint (nearest), floor, ceiling. Conversión: real(), int(), cmplx(). Inquiry: size, shape, huge (max value), tiny (min positive), kind. Todas las funciones trig toman radianes. atan2(y, x) devuelve el ángulo en el cuadrante correcto (a diferencia de atan). Use huge/tiny para verificar límites de rango. Las intrinsics son elementales — funcionan en arrays element-wise automáticamente.

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

Flujo de Control

If...Then...Else

Block IF: 'if (cond) then ... else if (cond) then ... else ... end if'. Cada rama necesita 'then' (excepto el else final). Logical IF es one-liner: 'if (cond) statement' (sin 'then'/'end if'). Las condiciones usan operadores relacionales (< > == /= <= >= o .lt. .gt. .eq. .ne. .le. .ge.) combinados con .and. .or. .not. 'stop' termina el programa (opcionalmente con un mensaje/código). El arithmetic IF (if (x) label1, label2, label3) está eliminado en Fortran 2018 — nunca lo use. Siempre use 'implicit none' para que las variables no declaradas sean capturadas.

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 es la sentencia switch de Fortran. Los cases pueden ser valores únicos (case (3)), listas (case (1, 3, 5)), o rangos (case (4:5) significa 4 a 5 inclusive). case default es el fallback. A diferencia de C, NO hay fall-through — cada rama es independiente y solo una se ejecuta. Funciona con tipos integer, character y logical (NO real). Los rangos de character usan ordenamiento ASCII ('A':'Z'). Para comparaciones floating-point, use if/else. select case es más eficiente que largas cadenas if/else if para dispatch integer/char (el compilador puede usar jump tables).

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

Bucles Do (Contados)

Bucle DO contado: 'do var = start, end, step' (step por defecto 1). El bucle se ejecuta mientras var <= end (para step positivo) o var >= end (para step negativo). var se incrementa DESPUÉS de cada iteración. Los constructs implied-do [(expr, var=start,end)] son poderosos para inicialización de arrays y listas de I/O. Los bucles nombrados (outer: do ... end do outer) permiten dirigir cycle/exit a un nivel de anidamiento específico. La variable del bucle se define automáticamente; en Fortran retiene su valor final después del bucle. Evite modificar la variable del bucle dentro del cuerpo del bucle.

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 y Bucles Infinitos

do while (cond) ... end do es un bucle pre-test (verifica condición antes de cada iteración; puede ejecutarse cero veces). Para comportamiento post-test, use do ... if (cond) exit ... end do. El 'do ... end do' bare es un bucle infinito — DEBE tener una sentencia exit (de lo contrario infinito). 'exit' sale del bucle más interno (o un bucle nombrado). Los bucles nombrados (factorial_loop:) permiten que exit apunte a un bucle externo. Use do while cuando el conteo de iteraciones es desconocido y depende de una condición; use counted do cuando el conteo se conoce de antemano.

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 y Control de Bucle

cycle salta el resto de la iteración actual y pasa a la siguiente (como 'continue' en C/Python). exit sale del bucle completamente (como 'break'). Ambos apuntan al bucle más interno por defecto, pero con bucles nombrados (search: do ... end do search) puede apuntar a un bucle externo: 'exit search' o 'cycle search'. Esto es esencial para salir de bucles anidados limpiamente. Use cycle para filtrado (saltar iteraciones no deseadas) y exit para terminación temprana (búsqueda encontrada, error detectado). Los bucles nombrados hacen el flujo de control anidado explícito y legible.

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

Arrays y Operaciones Vectoriales

Declaración e Inicialización de Arrays

Los arrays de Fortran son 1-indexados por defecto (lower bound = 1), pero puede especificar un lower bound personalizado: a(0:4) tiene índices 0..4. Los arrays multi-dimensionales usan ordenamiento (rows, cols) — almacenamiento column-major (el primer índice varía más rápido en memoria). Inicialice con array constructors [1,2,3] o implied-do [(expr, i=start,end)]. reshape llena un array multi-dim desde una lista 1D. size() devuelve elementos totales; lbound/ubound devuelven límites lower/upper. shape() devuelve la shape como un array 1D. Los arrays son 'whole-array' — puede asignar y operar en ellos sin bucles explícitos.

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

Secciones de Array y Vector Subscripts

Las secciones de array (slicing) usan sintaxis a(start:end:stride) — todas las partes opcionales. Stride puede ser negativo (reverso). Los vector subscripts permiten gather/scatter con un array de índices: a(idx) devuelve [a(idx(1)), a(idx(2)), ...]. Las secciones pueden asignarse: a(2:4) = [99,98,97]. El construct 'where' es asignación condicional a nivel de array (como numpy where). Las operaciones de array de Fortran se vectorizan — sin bucles explícitos necesarios para ops element-wise. Esta es la característica killer de Fortran para código numérico: sintaxis limpia y matemática que los compiladores auto-vectorizan.

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

Arrays Multidimensionales y Matrices

Fortran almacena arrays column-major (el primer índice varía más rápido en memoria) — opuesto a C. reshape llena en orden column-major, así que reshape([1,2,3,4],[2,2]) da [[1,3],[2,4]]. matmul(A,B) es multiplicación de matrices verdadera (álgebra lineal); A*B es element-wise (Hadamard) — ¡NO son lo mismo! transpose(A) devuelve la transpuesta. Reducciones: sum, product, maxval, minval, maxloc, minloc, count — todas soportan dim= para reducir a lo largo de un eje. Para alto rendimiento, escriba bucles en orden column-major (bucle más interno sobre el primer índice) para ser 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

Arrays Allocatable (Dinámicos)

Los arrays allocatable son la forma moderna de hacer memoria dinámica en Fortran — más seguros que punteros (sin fugas de memoria, desasignación automática al salir del scope). Declare con atributo allocatable y shape diferida (:, (:,:), etc.). allocate() con stat= captura errores (¡siempre verifique!). deallocate() libera explícitamente. Fortran 2003+ soporta reasignación automática en asignación: flex = [flex, 4] crece el array. allocated() verifica si está actualmente asignado. Los allocatables se prefieren sobre punteros para arrays dinámicos porque el compilador los rastrea y libera automáticamente — sin fugas, sin punteros colgantes.

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

Funciones Intrínsecas de Array

Las intrinsics de array de Fortran son su superpoder. Reducciones: sum, product, maxval, minval, maxloc (índice del max), minloc, count (conteo de true), any (existe), all (todos). Todas soportan mask= para reducción condicional. pack() reúne elementos donde mask es true (como numpy compress); unpack() dispersa. cshift/eoshift rotan arrays (circular vs end-off). merge(a, b, mask) hace selección element-wise. Nota: Fortran NO tiene sort integrado — debe escribir uno (o usar una librería). Estas intrinsics son elementales y vectorizables, haciendo el código Fortran tanto limpio como rápido.

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

Cadenas y Manejo de Caracteres

Declaración de Character y Length

Las cadenas de Fortran son FIXED-LENGTH por defecto — las cadenas más cortas se rellenan con espacios para llenar la longitud declarada. character(N) o character(len=N) declara longitud N. character(*) toma la longitud del contexto (inicializador de parameter o argumento dummy). character(:), allocatable habilita cadenas dinámicas deferred-length (Fortran 2003+) — la cadena se reasigna en asignación. len() devuelve la longitud declarada; len_trim() devuelve la longitud sin espacios trailing. trim() devuelve la cadena sin espacios trailing (pero el resultado aún es fixed-length en contexto). Para procesamiento de texto de longitud variable, use cadenas allocatable deferred-length.

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

Concatenación de Cadenas y Operaciones

La concatenación de cadenas usa el operador //. repeat(s, n) repite una cadena n veces. Las subcadenas usan s(start:end) — 1-indexado, INCLUSIVE en ambos extremos (a diferencia de Python). s(8:) significa desde posición 8 hasta el final; s(:5) significa desde el inicio hasta posición 5. index(s, sub) devuelve la posición de la primera ocurrencia de sub (0 si no se encuentra, sensible a mayúsculas). scan(s, set) devuelve la posición del primer carácter EN set; verify(s, set) devuelve el primer carácter NO en set. adjustl/adjustr desplazan espacios leading/trailing. Las cadenas de Fortran NO están null-terminated como C — la longitud se rastrea separadamente.

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

Funciones Intrínsecas de Cadena

iachar(c) devuelve el código ASCII de un carácter; achar(i) es el inverso. (ichar/char son processor-dependent — prefiera iachar/achar para portabilidad.) Fortran NO tiene conversión de mayúsculas integrada — use iachar/achar manualmente (A-Z es 65-90, a-z es 97-122, la diferencia es 32). Comparación lexicográfica: lge/lgt/lle/llt (lexicamente mayor/menor que) manejan cadenas de diferentes longitudes de forma graceful. La E/S interna (read/write a una cadena en lugar de un archivo) es la forma idiomática de convertir entre cadenas y números: read(str, *) num y write(str, fmt) num. El formato I0 da integers de ancho mínimo (sin padding).

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

Formateo de Cadenas

Las especificaciones de formato viven en una cadena: '(I5, F8.2, A)'. I=integer, F=fixed-point real, E=exponential, ES=scientific (mantisa 1-10), A=character, X=space, /=newline. El ancho viene primero (I5 = ancho 5), luego .m opcional para dígitos mínimos (I5.3). I0 significa ancho mínimo (sin padding). ES da notación científica proper (1.23E+6) vs E (0.12E+7). Repeticiones: 3I4 = tres integers cada uno ancho 4. Literales de cadena en formato: '("text")'. Format puede ser una variable de cadena, un * (list-directed, el compilador elige), o una etiqueta (número de sentencia). Para salida limpia, prefiera I0 para integers y F o ES para reals.

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

Parseo y Tokenización

Fortran NO tiene split/tokenize integrado — debe escribirlo manualmente usando index() y subcadenas. El patrón: encuentre el delimitador con index, extraiga el token con subcadena, avance pasado el delimitador, repita. Para pares clave=valor, encuentre '=' con index y divida en clave (antes) y valor (después). trim() elimina espacios trailing; adjustl() elimina espacios leading. Para parseo robusto, también maneje tokens vacíos y espacios en blanco. Alternativamente, use reads internos con especificadores de formato para datos estructurados, o lea desde una cadena como si fuera un archivo. Librerías como split() existen en algunos frameworks de Fortran pero no son estándar.

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

Procedimientos: Funciones y Subroutines

Funciones

Las funciones devuelven un valor y se usan en expresiones (como funciones math). Sintaxis moderna: 'function name(args) result(var)' — la variable result es lo que se devuelve. intent(in) marca argumentos de solo lectura (el compilador lo impone). Las funciones pueden devolver escalares O arrays (use size() de la entrada para dimensionar la salida). Las funciones deben ser PURE (sin efectos secundarios) — no modifique estado global ni haga E/S en una función. Los procedimientos internos (en bloque 'contains') tienen acceso a las variables del host (host association). Para procedimientos externos, use un interface block para especificar la firma.

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

Subroutines e Intent

Las subroutines se llaman con 'call' y no devuelven un valor — modifican argumentos in place. Use subroutines cuando: (1) necesita modificar múltiples argumentos, (2) la operación es un 'comando' no una 'computación', (3) devolver un resultado array-valued es incómodo. Los atributos intent documentan y imponen la dirección del argumento: intent(in) = solo lectura (error del compilador si asigna a él), intent(out) = solo escritura (indefinido en entrada, debe establecerse antes de retornar), intent(inout) = lectura-escritura. Siempre especifique intent — captura bugs y habilita optimización. Las subroutines pueden modificar arrays pasados a ellas (sin copia si son contiguos).

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

Funciones Pure y Elemental

Las pure functions NO tienen efectos secundarios: sin E/S, sin modificación de variables globales, sin stop, solo pueden llamar otros procedimientos puros. Habilitan optimización del compilador (paralelización, eliminación de subexpresiones comunes) y son obligatorias en algunos contextos (ej., DO CONCURRENT). Las elemental functions se escriben para ESCALARES pero automáticamente funcionan en arrays element-wise — escriba una vez, use para ambos. pure elemental combina ambos. Use pure para cualquier función que sea verdaderamente una función math (sin efectos secundarios). Use elemental cuando la operación se aplica naturalmente element-wise a arrays (funciones math, conversiones). El compilador puede auto-vectorizar llamadas elementales en arrays.

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

Argumentos Optional y Keyword

Los argumentos optional permiten a los llamadores omitirlos. Use present(arg) dentro del procedimiento para verificar si se proporcionó un argumento — acceder a un optional ausente es comportamiento indefinido. Los argumentos keyword (name="value") permiten pasar argumentos en cualquier orden y hacen las llamadas self-documenting. Una vez que use un keyword, todos los argumentos subsiguientes también deben usar keywords. Los argumentos optional deben venir después de todos los obligatorios en la firma. Los valores default se implementan vía verificaciones present() (Fortran no tiene sintaxis default integrada). Keyword + optional juntos habilitan APIs flexibles: los llamadores especifican solo lo que necesitan.

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

Procedimientos Internos y Recursivos

Los procedimientos internos (dentro de 'contains') tienen host association — pueden leer Y modificar las variables del programa host (como closures). Úselos para helpers que necesitan estado del host. Los procedimientos recursive deben declararse con el prefijo 'recursive' (Fortran 90/2003); Fortran 2018 hace recursion default. Para recursión mutua, use un interface block para declarar la forward reference. La recursión es elegante pero puede ser lenta (overhead de llamada a función) y riesgosa (stack overflow para recursión profunda). Para factorial/fibonacci, las versiones iterativas son más rápidas y seguras. Use recursión para problemas naturalmente recursivos (tree traversal, divide-and-conquer) con profundidad acotada.

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

Módulos y Encapsulación

Fundamentos de Module y Use

Los modules son el mecanismo primario de encapsulación de Fortran (reemplazando common blocks y procedimientos externos). Un archivo module contiene: (1) declaraciones (constantes, variables, derived types), (2) un bloque 'contains' con procedimientos. Use 'use module_name' para importar; 'use module_name, only: x, y' importa solo entidades específicas (recomendado — evita polución de namespace). Las variables de module son persistentes (static) y compartidas a través de todos los procedimientos que usan el module. Los modules proporcionan interfaces explícitas (el compilador verifica tipos de argumentos), a diferencia de procedimientos externos. Siempre compile archivos module antes de los archivos que los usan. 'implicit none' en un module se propaga a todos sus procedimientos.

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

Control de Acceso (Public/Private)

Control de acceso: 'private' hace entidades module-internal; 'public' las exporta. El default puede establecerse a nivel de module ('private' luego selectivamente 'public :: ...') — esta es la mejor práctica (interfaz explícita). Los componentes de derived type pueden ser private incluso si el tipo mismo es public — los llamadores pueden usar el tipo pero no acceder a internals directamente; deben ir a través de procedimientos. 'save' hace las variables de module persistentes (retienen valores entre llamadas) — las variables de module se guardan por defecto. 'final' define un destructor (llamado cuando el objeto sale del scope). Esta encapsulación habilita verdadera POO con invariantes impuestas a través de procedimientos.

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

Derived Types en Modules

Los derived types definidos en modules pueden tener: type-bound procedures (procedure :: name => impl), constructores (vía interface sobrecargado con el nombre del tipo) y componentes allocatable. El 'class(keyword)' en type-bound procedures habilita polimorfismo (el tipo actual puede ser una subclase). Los type-bound procedures se llaman como obj%method(args) — sintaxis POO. Sobrecargar el nombre del tipo como interface le permite tener múltiples constructores (vector_from_array, vector_from_size). Los componentes allocatable se asignan/desasignan automáticamente. Esta es POO moderna de Fortran: encapsulación, métodos, constructores y polimorfismo, todo dentro del sistema 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

Procedimientos Genéricos y Sobrecarga

Las interfaces genéricas proporcionan polimorfismo ad-hoc (sobrecarga): un nombre dispatcha a diferentes procedimientos específicos basado en tipos de argumento. El bloque 'interface name / module procedure proc1, proc2 / end interface' lista todos los procedimientos específicos. El compilador elige el coincidente por tipo/rango de argumento en tiempo de compilación. Todos los procedimientos específicos en un generic deben tener firmas DISTINTAS (distinguibles por tipos de argumento) — de lo contrario ambigüedad. Así es como Fortran hace sobrecarga de operadores/funciones sin templates. El dispatch genérico se resuelve en tiempo de compilación (sin overhead de runtime). Use generics para proporcionar una API uniforme a través de tipos.

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

Sobrecarga de Operadores

La sobrecarga de operadores le permite definir cómo +, -, *, /, ==, etc. funcionan en sus derived types. interface operator(+) / module procedure vec_add / end interface enlaza el operador + a una función. Para operadores binarios, puede sobrecargar ambos ordenamientos (vec*scalar y scalar*vec) con procedimientos separados. assignment(=) sobrecarga el operador de asignación (el procedimiento es una subroutine con intent(out) LHS y intent(in) RHS). Esto habilita sintaxis matemática: c = a + b en lugar de c = vec_add(a, b). Use sobrecarga de operadores para tipos matemáticos (vectores, matrices, números complejos) donde mejora la legibilidad. Evite sobrecargar para semánticas no obvias. El constructor de estructura vec3(x,y,z) está integrado para derived types.

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

Derived Types (Structs) y POO

Definiendo Derived Types

Los derived types son los structs de Fortran (tipos compuestos definidos por el usuario). Defina con 'type :: Name ... end type Name'. Componentes accedidos con % (NO . — eso es para números complejos). Constructor de estructura: Name(val1, val2) crea una instancia. Los componentes pueden tener valores default (= value en declaración). La asignación whole-type copia todos los componentes (copia profunda para componentes allocatable). Los arrays de derived types se soportan. Los derived types son la base para POO en Fortran (con type-bound procedures, herencia, polimorfismo). Use % para acceso a componentes: obj%field, obj%method(). La sintaxis de constructor Name(args) es automática a menos que la sobrescriba con un 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

Componentes de Tipo y Constructores

Los derived types pueden tener: componentes allocatable (memoria auto-gestionada), componentes default-initialized, type-bound procedures (métodos) y finalizers (destructores). El 'interface TypeName / module procedure custom_init / end interface' sobrecarga el constructor de estructura con una función factory personalizada. 'class(ClassName)' (vs 'type(ClassName)') en type-bound procedures habilita polimorfismo (el tipo actual puede ser una subclase). Los procedimientos 'final' se ejecutan cuando un objeto sale del scope (destructor) — úselos para liberar recursos. Los componentes allocatable se desasignan automáticamente en finalización, pero los finalizers explícitos son más claros para limpieza compleja. Los constructs block permiten declarar variables mid-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

Type-Bound Procedures (Métodos)

Los type-bound procedures son los métodos de Fortran: 'procedure :: method_name => implementation'. Llámelos como obj%method(args) — sintaxis POO. El primer argumento es 'self' (el objeto), declarado como 'class(TypeName)' (polimórfico) o 'type(TypeName)' (concreto). 'class' permite herencia/polimorfismo; 'type' es para tipos no extensibles. El '=> implementation' mapea el nombre del método a un procedimiento específico (permite renombrado). final es el destructor. Este ejemplo implementa un stack dinámico con array auto-creciente. Los type-bound procedures dan verdadera POO: encapsulación (datos + métodos juntos), sintaxis message-passing (obj%method) y polimorfismo (vía class). Siempre use class() para type-bound procedures para habilitar herencia futura.

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

Herencia y Polimorfismo

POO de Fortran: 'type, extends(Parent) :: Child' crea una subclase (herencia). 'type, abstract :: Name' con 'procedure(...), deferred :: method' define una base abstracta (como class abstract de Java / pure virtual de C++). 'class(Base)' es polimórfico — puede mantener cualquier subclase. Dispatch polimórfico: llamar obj%method() invoca el override de la subclase. 'select type (var => expr) / type is (ConcreteType) / end select' hace verificación de tipo en runtime (downcasting). Asigne con 'allocate(TypeName::var)' para crear un objeto polimórfico de un tipo concreto específico. Esta es POO completa: herencia, polimorfismo, tipos abstractos y dispatch en runtime — comparable a 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

Tipos Anidados y Arrays de Tipos

Los derived types pueden anidarse (composición): un tipo puede tener componentes de otros derived types. Acceda a componentes anidados con % encadenado: emp%home%city. Los constructores de estructura se anidan naturalmente: Employee(id, name, Address(...), salary). Los arrays de derived types se soportan: type(Employee) :: emps(N). Puede extraer un array de un solo componente: emps(:)%id da un array de integers. Los componentes allocatable permiten colecciones de tamaño dinámico (ej., un departamento con un número variable de empleados). Este modelo de composición es la base para construir estructuras de datos complejas (árboles, grafos, listas) en Fortran. Use composición (has-a) sobre herencia (is-a) cuando no hay una relación de subtipo clara.

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 de Archivos y Formateo

Apertura y Cierre de Archivos

open() conecta un archivo a un número de unit. newunit=u deja que el compilador elija un unit único (evita conflictos) — siempre prefiera esto sobre números de unit hard-coded. status: 'old' (el archivo debe existir), 'new' (no debe existir), 'replace' (eliminar + crear), 'scratch' (temporal, auto-eliminado al cerrar). action: 'read', 'write', 'readwrite'. position: 'rewind' (inicio), 'append' (final), 'asis' (donde sea). Siempre verifique iostat después de open y read — non-zero significa error (negativo = EOF, positivo = error). iomsg da un mensaje de error descriptivo. close() desconecta. Para manejo robusto de archivos, siempre verifique iostat y maneje errores de forma graceful.

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 Formateada

Los especificadores de formato controlan la E/S: '(A, I0, F8.2)'. A=character, I0=integer ancho mínimo, F8.2=real ancho 8 con 2 decimales. write(unit, fmt) escribe; read(unit, fmt) lee. unit=* significa stdout/stdin. Para archivos, use el unit de open(). List-directed I/O (*) es flexible: read(u, *) a, b, c lee valores separados por coma/espacio automáticamente. Para CSV, list-directed read funciona si los valores están separados por comas. Las cadenas de formato pueden reusarse: '(3I4)' aplica I4 tres veces. Siempre haga coincidir el formato con el tipo de dato — los formatos no coincidentes causan errores de runtime. Para texto mezclado con números, lea como cadena luego parse, o use formatos explícitos.

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 No Formateada (Binaria)

La E/S no formateada (binaria) es más rápida y más compacta que la E/S formateada (texto) — sin conversión de cadena. form='unformatted' la habilita. access='stream' (Fortran 2003) da acceso byte-stream (como E/S de archivos de C, sin record markers). access='sequential' (default) usa record markers (cada write/read es un record con prefijos de longitud) — portable dentro de Fortran pero no a otros lenguajes. Para interoperabilidad con C/Python, use stream access. Los archivos binarios no son legibles por humanos pero ideales para datasets numéricos grandes. Siempre escriba metadatos (tamaños de array, info de tipo) antes de los datos para poder leerlos de vuelta correctamente. La E/S no formateada preserva precisión completa (sin redondeo por conversión de texto).

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 Agrupada)

namelist agrupa variables para E/S de texto estructurada — como JSON/YAML pero nativo de Fortran. Defina con 'namelist /name/ var1, var2, ...'. write(u, nml=name) genera en el formato &NAME var=val, var=val, /. read(u, nml=name) lo parsea de vuelta. Namelist es perfecto para archivos de configuración: los usuarios editan un archivo de texto, el programa lo lee. Las variables mantienen sus valores declarados como defaults; solo las del archivo se sobrescriben. El formato es forgiving (insensible a espacios en blanco, comas opcionales). Namelist soporta todos los tipos intrínsecos y arrays. iostat captura errores de parseo. Esta es la forma más fácil de hacer un programa Fortran configurable sin escribir un parser personalizado.

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

Archivos Internos y Manejo de Errores

Los archivos internos le permiten leer desde / escribir a una cadena character en lugar de un archivo — el sprintf/sscanf de Fortran. write(str, fmt) formatea a una cadena; read(str, fmt) parsea desde una cadena. Esta es la forma estándar de convertir entre cadenas y números. Siempre use iostat para manejo de errores: 0 = éxito, negativo = EOF, positivo = error. Las etiquetas legacy end= y err= funcionan pero iostat es más limpio (sin goto). Para parseo robusto, verifique iostat después de cada read. La E/S interna es genial para: construir cadenas de salida, parsear entrada del usuario, convertir valores de archivos de configuración. La cadena actúa como un 'archivo interno' — mismas sentencias de E/S, solo un destino de cadena.

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

Computación Numérica

Parámetros Kind y Precisión

Los parámetros kind controlan precisión/tamaño. Forma moderna portable: use iso_fortran_env (int32/int64, real32/real64/real128). Legacy: selected_real_kind(digits, exponent_range). real32 ≈ 7 dígitos significativos, real64 ≈ 15 dígitos (double), real128 ≈ 33 dígitos (quad). CRÍTICO: siempre añada el sufijo kind a los literales (3.14_dp, no 3.14) — de lo contrario el literal se parsea como precisión simple LUEGO se convierte, perdiendo dígitos. precision() devuelve dígitos significativos; range() devuelve el rango de exponente decimal. epsilon() da el machine epsilon (incremento más pequeño distinguible). tiny/huge dan min/max. Para computación científica, por defecto use real64 (double precision).

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

Álgebra Lineal (matmul, solve, BLAS)

Fortran tiene álgebra lineal integrada: matmul (multiplicación matriz-matriz/matriz-vector), dot_product, transpose. Para resolver sistemas lineales (Ax=b), eigenvalues, SVD, etc., use LAPACK (la librería estándar de la industria de Fortran): dgesv resuelve Ax=b, dgesvd hace SVD, dsyev hace eigendecomposition. Enlace con -llapack -lblas. El ejemplo muestra eliminación Gaussiana manual para un sistema 3x3 — para trabajo real, use LAPACK (más rápido, más preciso con pivoting, maneja cualquier tamaño). El almacenamiento column-major de Fortran coincide con las expectativas de LAPACK nativamente (sin transposición necesaria). matmul está optimizado pero para matrices grandes, BLAS dgemm es más rápido. Siempre verifique el número de condición para estabilidad numérica.

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

Números Aleatorios

call random_number(x) llena x con reales uniformes [0,1) — funciona en escalares o arrays. call random_seed(size=n) obtiene el tamaño del seed; random_seed(put=seed) establece el seed para reproducibilidad (esencial para testing/debugging). Para integers en [a,b]: a + int(r * (b-a+1)). Para números aleatorios Gaussianos (normales), use la transformación Box-Muller (mostrada) o el método polar. Fortran no tiene generador de distribución normal integrado — impleméntelo o use una librería. Para simulaciones Monte Carlo, establezca el seed para reproducibilidad, luego ejecute muchos trials. random_number NO es criptográficamente seguro — use una librería crypto para seguridad. Para código paralelo, cada image necesita un seed distinto.

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

Integración Numérica y Búsqueda de Raíces

Integración numérica: la regla de Simpson es más precisa que la regla trapezoidal (error O(h^4) vs O(h^2)). Pase funciones como argumentos usando un interface block. Búsqueda de raíces: bisection es robusto (siempre converge si cambia el signo) pero lento (convergencia lineal); el método de Newton es rápido (convergencia cuadrática) pero necesita la derivada y puede diverger. Para trabajo de producción, use QUADPACK (integración) o MINPACK (búsqueda de raíces) — librerías de Fortran probadas en batalla. El interface block es esencial al pasar funciones como argumentos — le dice al compilador la firma de la función. Siempre establezca un conteo máximo de iteraciones para evitar bucles infinitos. Verifique convergencia con tolerancias tanto de valor de función como de tamaño de paso.

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

Aritmética IEEE y Excepciones

El module ieee_arithmetic (Fortran 2003) proporciona soporte IEEE 754: Infinity, NaN (Not a Number), signaling/quiet NaN, exception flags y rounding modes. NaN NUNCA es igual a nada (incluyéndose a sí mismo) — use ieee_is_nan() para probar. Infinity resulta de overflow o división por cero. Los exception flags (ieee_divide_by_zero, ieee_overflow, ieee_underflow, ieee_inexact, ieee_invalid) rastrean si ocurrieron excepciones — verifique con ieee_get_flag, limpie con ieee_set_flag. ieee_set_halting_mode controla si una excepción detiene el programa. Los rounding modes afectan operaciones floating-point. Use esto para código numérico robusto: detecte NaN/Inf, maneje excepciones de forma graceful y controle precisión. Nota: -ffast-math en gfortran rompe compliance IEEE (no lo use para código que confíe en estas características).

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 y Paralelo

Declaración básica de coarray

Los coarrays son el modelo paralelo integrado de Fortran (F2008). Cada 'image' es un proceso paralelo. Declare con sufijo [*]. this_image() devuelve el rank; num_images() el conteo. Acceda remoto con x[k]. sync all es una barrera. Compiladores: gfortran (con -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

Acceso remoto y sync

Acceda coarrays remotos con sufijo [k] — comunicación one-sided. Las lecturas y escrituras son non-blocking hasta sync. sync all es barrera global; sync images([1,2]) espera images específicas. Secciones críticas: lock/unlock con critical...end critical. Evite deadlock ordenando syncs consistentemente.

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

Operaciones colectivas

Colectivas: co_sum, co_min, co_max, co_broadcast. Operan en coarrays y reducen/broadcast a través de images. result_image especifica quién obtiene la respuesta (default: todos). source_image para broadcast. Siempre sincronice antes de leer resultados colectivos en otras images. Más rápido que bucles manuales con 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

Derived types coarray

Los derived types pueden ser coarrays — cada componente se replica por image. Acceda componentes remotos con p[k]%field. Los componentes allocatable en coarrays requieren Fortran 2018+ (algunos compiladores pueden no soportar). Para arrays de partículas, use 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 allocatable y teams

Los coarrays allocatable se asignan en todas las images simultáneamente con sufijo [*]. Los teams (F2018) dividen images en grupos independientes — cada uno tiene su propio this_image/num_images. form team crea teams; change team entra al scope. Útil para paralelismo jerárquico. El soporte del compilador varía (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

Interoperabilidad con C

Fundamentos de ISO_C_BINDING

iso_c_binding proporciona kinds compatibles con C (c_int, c_double, c_char, etc.). bind(C, name='...') expone Fortran a C con un nombre de símbolo específico. Las cadenas de C necesitan terminador c_null_char. El interface block declara la firma de la función C. Compile C y Fortran separadamente, enlace juntos.

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); }

Pasando arrays a C

Pase arrays vía c_loc (obtiene puntero C) y tipo c_ptr. Use atributo 'value' para argumentos escalares de C (pasados por valor, no referencia). Los arrays de Fortran son column-major; C es row-major — transponga arrays 2D o documente la convención. c_f_pointer convierte punteros C de vuelta a punteros 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

Tipos C-interoperables

Los tipos con bind(C) tienen layout de memoria compatible con C — requerido para pasar structs a C. Solo kinds compatibles con C permitidos (sin real/integer default). Sin componentes allocatable/pointer. Los arrays char de longitud fija emulan cadenas C. El orden importa — Fortran puede reordenar componentes sin 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]; };

Llamando Fortran desde C

Las subroutines con bind(C, name='...') son llamables desde C por ese nombre. Use 'value' para escalares que C pasa por valor; los arrays se pasan por referencia (sin value). Los resultados de function deben ser escalares compatibles con C. Use nombres C-binding para evitar name mangling del compilador (underscores, cambios de mayúsculas).

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);

Punteros a función C y callbacks

abstract interface declara una firma de función C. procedure(iface) acepta una función coincidente como argumento. Pase punteros a función C directamente. c_funloc obtiene la dirección C de un procedimiento Fortran; c_f_procpointer convierte un c_funptr de vuelta a un procedimiento Fortran. Útil para callbacks estilo 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

Orientado a Objetos (extends/final)

Extensión de tipo (herencia)

Extensión de tipo = herencia. 'extends(parent)' declara una subclase. 'class(T)' es polimórfico (acepta T o cualquier extensión); 'type(T)' es exacto. 'abstract' + 'deferred' = método abstracto (debe sobrescribirse). 'contains' introduce type-bound procedures. Sobrescriba re-declarando el procedimiento con el mismo nombre.

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

Polimorfismo y SELECT TYPE

select type hace discriminación de tipo en runtime en variables polimórficas. 'type is (T)' coincide con tipo exacto; 'class is (T)' coincide con T y extensiones. Dentro del bloque, la variable se trata como el tipo coincidente (accede a componentes específicos). Los arrays polimórficos mantienen tipos mixtos vía class(shape) — pero la asignación debe ser por elemento.

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

Finalizers y destructores

Los procedimientos final se ejecutan automáticamente cuando una variable sale del scope — como destructores de C++. Defina con 'final :: name'. Debe ser una subroutine tomando TYPE (no class) — sin polimorfismo. Un tipo puede tener múltiples finalizers (sobrecargados por rank). Use para cerrar archivos, liberar memoria, liberar recursos. No pueden fallar/lanzar.

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

Constructores y asignación

Un interface genérico con el nombre del tipo actúa como constructor personalizado — sobrecarga el constructor de estructura default. Múltiples procedimientos permiten diferentes conjuntos de argumentos. El constructor default (point(x=..., y=...)) aún está disponible a menos que se sobrescriba. Asignación polimórfica: allocate(circle :: shape_var) crea un circle en una 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

Tipos abstractos y templates

Los tipos abstractos no pueden instanciarse — solo extenderse. Los procedimientos deferred deben sobrescribirse en subclases concretas. class(*) es polimórfico ilimitado — mantiene cualquier tipo (use select type para recuperar). Este patrón implementa classes base abstractas e interfaces. Los contenedores concretos (list, stack, queue) extienden e implementan los procedimientos 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

Parameterized Derived Types

Declaración básica de PDT

Los Parameterized Derived Types (PDTs, F2003) son como templates de C++. Los parámetros 'kind' son compile-time (fijos por instancia); los parámetros 'len' son runtime (pueden ser deferred con ':'). Asigne tipos deferred-length con allocate(type(params) :: var). Los PDTs habilitan contenedores genéricos con seguridad de tipos sin tricks de preprocesador.

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 con parámetro kind

Los tipos parameterized por kind le permiten escribir un tipo que funciona para múltiples precisiones. El tipo de retorno de la función usa this%k para coincidir. Note la sintaxis class(array_t(k=*)) para el procedimiento — requerido para tipos kind-param. Soporte del compilador: gfortran 9+, ifort. Útil para librerías que soportan precisión single/double/quad.

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 con parámetro length

Los tipos parameterized por length llevan tamaño como parámetro de tipo. fstr(5) y fstr(6) son tipos diferentes. Los procedimientos usan fstr(*) para aceptar cualquier longitud. El tipo de resultado puede depender de longitudes de entrada (c tiene longitud a%n + b%n). Útil para buffers de tamaño fijo, arrays de tamaño estático. Soporte limitado del compilador — pruebe exhaustivamente.

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

PDT arrays allocatable

Los PDTs pueden contener componentes allocatable. El parámetro kind se propaga a tipos de componente (real(this%k)). move_alloc transfiere asignación eficientemente (sin copia). Use class(stack(k=*)) en procedimientos para aceptar cualquier kind. Los PDTs con componentes allocatable combinan tipado genérico con sizing dinámico.

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

Limitaciones de PDT y workarounds

El soporte de PDT varía — los parámetros kind están ampliamente soportados; los parámetros length menos. Para sizing en runtime, prefiera componentes allocatable sobre parámetros len. Los arrays PDT (type(matrix(4,4)) :: arr(10)) pueden no funcionar en todos los compiladores. Pruebe en su compilador objetivo. Para máxima portabilidad, use preprocesador (#define) o interfaces genéricos.

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

Submodules

Estructura básica de submodule

Los submodules (F2008) separan interface de implementación. El module declara el interface; el submodule proporciona el cuerpo. Los cambios al cuerpo del submodule no disparan recompilación de dependientes — solo los cambios de interface lo hacen. Use 'module procedure' para implementar. Genial para librerías grandes con compilación costosa.

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

Múltiples submodules por module

Un module puede tener múltiples submodules — divida implementaciones a través de archivos. Cada submodule comienza con 'submodule(parent) name'. Los procedimientos en el mismo module parent pueden llamarse entre sí. Esto habilita compilación incremental: edite un submodule, recompile solo eso y enlace. Útil para modules muy grandes.

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

Procedimientos internos solo de submodule

Los submodules pueden contener procedimientos helper privados no visibles a través del interface del module parent. Esto oculta detalles de implementación mientras los mantiene co-localizados. Solo las implementaciones 'module procedure' son accesibles vía el parent. Los helpers permanecen internos — mejor encapsulación que poner todo en el bloque contains del 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

Heredando variables de module

Los submodules tienen acceso a las variables y tipos del module parent vía host association. Pueden leer y modificar estado a nivel de module. Esto es útil para configuración que afecta el comportamiento de implementación. Los cambios a variables de module aún requieren recompilar el submodule (y dependientes).

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')

Beneficios de compilación de submodule

El beneficio clave: los cambios de implementación en un submodule no disparan recompilación de código que solo 'use'a el module parent. Solo el submodule mismo se recompila. Esto acelera dramáticamente builds incrementales para proyectos grandes de Fortran. Reestructure implementaciones que cambian frecuentemente en submodules; mantenga interfaces estables en el 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

IEEE Floating Point

Excepciones y flags IEEE

ieee_exceptions proporciona acceso a flags de excepción IEEE (overflow, underflow, divide_by_zero, invalid, inexact). ieee_get_flag lee; ieee_set_flag limpia. ieee_set_halting_mode controla si las excepciones detienen el programa. ieee_arithmetic proporciona ieee_is_nan, ieee_is_finite, ieee_is_negative, etc. Siempre verifique flags después de computaciones críticas.

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

Manejo de NaN e Inf

Inf y NaN son valores especiales IEEE. NaN != NaN (use ieee_is_nan para probar). Inf se propaga a través de aritmética. Operaciones que producen NaN: 0/0, Inf-Inf, 0*Inf, sqrt(-1). Compile con -ffpe-trap=invalid,zero,overflow para detener en estos (depuración). El código de producción debería verificar NaN después de operaciones riesgosas.

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

Modos de redondeo

IEEE soporta 4 modos de redondeo: nearest (default), down, up, to zero. ieee_set_rounding_mode lo cambia en runtime. Útil para aritmética de intervalos (compute límites upper/lower redondeando up/down). Afecta todas las operaciones FP subsiguientes hasta que se cambie. Restaure a nearest cuando termine. Algunos compiladores optimizan asumiendo nearest — use con cuidado.

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

Consulta de características IEEE

ieee_features y funciones de inquiry de ieee_arithmetic le permiten verificar qué características IEEE soportan el compilador/plataforma. ieee_support_nan, ieee_support_inf, ieee_support_rounding, ieee_support_datatype, etc. Útil para código portable — degrade de forma graceful en sistemas no-IEEE. La mayoría de sistemas modernos soportan todas las características.

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

Controlando el entorno FP

Controle el comportamiento FP por sección: deshabilite halting para código riesgoso, verifique flags después, restaure. ieee_all coincide con todas las excepciones. Patrón: limpie flags, ejecute computación, verifique flags, maneje errores. Para producción, prefiera verificaciones explícitas (ieee_is_nan) sobre detección basada en flags — los flags pueden ser establecidos por código no relacionado.

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

Rendimiento y Optimización

Ordenamiento de array y contigüidad

Los arrays de Fortran son column-major: a(i,j) y a(i+1,j) son adyacentes en memoria. El orden del bucle importa: el bucle más interno debería iterar el primer índice. El orden incorrecto causa cache misses — slowdown de 10x+. La sintaxis de array (sum, matmul) deja que el compilador optimice. Use arrays contiguos (no punteros) para mejor rendimiento.

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

Procedimientos pure y elemental

Los procedimientos pure no tienen efectos secundarios — el compilador puede optimizar, paralelizar y reordenar llamadas. Los procedimientos elemental funcionan tanto en escalares como arrays (auto-vectorizados). pure elemental combina ambos. Use estos para funciones math. Restricciones: sin E/S, sin modificación global, intent(in) para todas las entradas. Habilita mejor optimización que procedimientos regulares.

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

Flags de optimización del compilador

Comience con -O2 -march=native para producción. -O3 puede ayudar o perjudicar — haga benchmark. -Ofast rompe compliance IEEE (no lo use si NaN/Inf importan). -flto habilita inlining cross-file (compilación más lenta, ejecución más rápida). Use -fcheck=all y -ffpe-trap en builds debug para capturar errores. La optimización profile-guided (-fprofile-use) da 5-15% speedup.

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)

Paralelismo OpenMP

OpenMP añade paralelismo vía directivas (!$omp). 'parallel do' paraleliza el siguiente bucle. 'reduction(+:s)' maneja sumas de forma segura. Compile con -fopenmp (gfortran) o -qopenmp (ifort). Establezca conteo de hilos vía variable de entorno OMP_NUM_THREADS. Mejor para bucles CPU-bound con iteraciones independientes. Cuidado con false sharing y load imbalance.

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

Profiling y detección de hotspots

system_clock da timing portable. Para profiling serio, use gprof (compile con -pg), perf (Linux), o Intel VTune. Haga profile antes de optimizar — las sorpresas son comunes. Enfóquese en hotspots (el 20% del código que toma el 80% del tiempo). Optimice los bucles más internos primero. Siempre haga benchmark antes/después de cambios — la intuición a menudo está equivocada.

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

Programación de Lenguaje Mixto

Fortran llamando a librería C

Envuelva funciones de librería C en un module de interface Fortran. Use bind(C, name='...') para enlazar al símbolo C exacto. Pase punteros como c_ptr con 'value'. Para argumentos function-pointer (como el comparator de qsort), use un abstract interface. Esto le permite llamar cualquier función C — incluyendo libc, BLAS, system calls.

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 con C++ vía extern C

C++ name-manglea símbolos, así que envuelva funciones C++ en bloques extern "C". El interface Fortran luego se enlaza al nombre no-mangled. Enlace con -lstdc++ (gfortran) o use un linker C++. Esto le permite usar librerías C++ (STL, Boost, Qt) desde Fortran. Para classes C++, escriba un wrapper de API C flat, luego llame desde 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 con Python vía f2py

f2py (parte de numpy) auto-genera bindings Python para Fortran. Lee atributos intent y crea interfaces proper Python/numpy. intent(out) se vuelve un valor de retorno. Los arrays mapean a arrays numpy (zero-copy cuando son contiguos). Genial para código numérico performance-critical llamado desde Python. Soporte limitado para características 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)

Librería compartida y carga dinámica

Use dlopen/dlsym (POSIX) o LoadLibrary/GetProcAddress (Windows) para plugins cargados en runtime. Envuelva en interfaces iso_c_binding. Convierta c_ptr a procedure pointer con c_f_procpointer. Esto habilita arquitecturas de plugins — cargue diferentes implementaciones en runtime. Cross-platform: use #ifdef para loaders específicos del 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

Integración de sistema de build

CMake maneja builds de lenguaje mixto bien. Declare todos los lenguajes en project(). Establezca flags por lenguaje. Enlace librerías en orden de dependencia. CMake rastrea dependencias de module Fortran automáticamente (sin ordenamiento manual de .mod). Para enlazar la librería estándar C++, use el flag correcto por plataforma. Use Fortran como linker principal si el runtime de Fortran es necesario.

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 Moderna

Newunit y manejo seguro de archivos

newunit= evita colisiones de número de unit — el compilador elige un número libre. Siempre verifique iostat después de open/read/write — non-zero significa error. iomsg da un mensaje de error legible por humanos. action= ('read', 'write', 'readwrite') previene uso accidental indebido. status= ('old', 'new', 'replace', 'scratch', 'unknown') controla la creación de archivos.

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

Stream I/O (binario)

Stream access (F2003) da E/S orientada a bytes como C — sin record markers. access='stream' la habilita. pos= lee/escribe en un offset de byte específico. form='unformatted' para binario. Genial para archivos binarios interoperables (lea archivos escritos por Fortran desde C/Python). El access sequential default usa record markers (no 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 derived type

User-Defined Derived Type I/O (F2003) le permite controlar cómo se leen/escriben los tipos. Defina una subroutine con la firma específica y enlácela vía 'generic :: write(formatted)'. El código de formato DT la dispara. Habilita serialización personalizada (CSV, tipo-JSON, binaria). iotype es 'LISTDIRECTED', 'NAMELIST', o 'DT' para E/S formateada.

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 proporciona E/S legible por humanos y basada en nombres para grupos de variables. Formato: &group_name var=value, ... /. La lectura solo actualiza variables presentes en el archivo — otras mantienen sus valores. Genial para archivos de config — los usuarios editan texto, sin parser necesario. Limitaciones: sin comentarios en algunos compiladores, soporte limitado de tipos.

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 asíncrona

E/S asíncrona (F2003) superpone E/S con computación. write(..., asynchronous='yes') inicia una operación non-blocking. wait(unit) bloquea hasta que complete. Útil para datasets grandes — comience a escribir mientras computa el siguiente chunk. El soporte del compilador varía. Use inquire(unit=u, pending=...) para verificar estado. Empárelo con double-buffering para 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

Depuración y Profiling

Flags de depuración en tiempo de compilación

Use -fcheck=all para verificación de bounds (captura errores off-by-one). -ffpe-trap se detiene en NaN/Inf/overflow — invaluable para código numérico. -finit-real=nan hace visibles las variables no inicializadas (se propagan como NaN). -fbacktrace imprime un stack trace en crash. Siempre depure con estos flags; remuévalos para builds de producción (ralentizan el código).

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

Manejo de errores con iostat

iostat: negativo = EOF, cero = éxito, positivo = error. iomsg da detalles. Lea en una cadena primero, luego parse — separa errores de E/S de errores de parse. Rastree números de línea para mensajes de error útiles. Siempre maneje errores explícitamente — los fallos silenciosos son difíciles de depurar. Use stop con un código non-zero para señalar fallo a 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 para Fortran

GDB soporta Fortran: array slices, componentes de derived type, module procedures (formato de nombre: modname__procname). Use -g -O0 para la mejor experiencia de depuración. 'display' auto-imprime variables en cada parada — útil para monitorear bucles. 'info locals' muestra todos los locales. Para variables de module, use '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

Profiling con gprof

gprof muestrea el program counter durante la ejecución. Compile TODOS los archivos fuente con -pg para profiles completos. El flat profile muestra dónde se gasta el tiempo; el call graph muestra la jerarquía de llamadas. Enfóquese en funciones con alto tiempo 'self' — ahí es donde la optimización ayuda. Nota: -pg cambia el timing — código profile-like puede comportarse diferente en producción.

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 rendimiento

Haga benchmark proper: warmup primero (efectos de cache), ejecute múltiples iteraciones, tome el mínimo (menos ruido). system_clock con count_rate da wall time. Reporte throughput (elements/sec) para ops de array. Compare implementaciones en la misma máquina con los mismos flags. Cuidado: el compilador puede optimizar resultados 'no usados' — use el resultado (ej., print sum) para prevenir esto.

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 Moderno

Free Form

Fortran moderno (90+) usa free form: sin restricciones de columnas. Los comentarios comienzan con !. Las sentencias pueden abarcar múltiples líneas con &. Implicit none es obligatorio para seguridad de tipos. Mucho más legible que el fixed-form Fortran 77.

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

Los modules agrupan procedimientos y datos relacionados. use importa un module. contains separa declaraciones a nivel de module de procedimientos. Los modules proporcionan interfaces explícitas, habilitando verificación de tipos. Prefiera modules sobre procedimientos externos.

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

Derived Types

Los derived types son estructuras de datos definidas por el usuario (structs). % accede componentes. Los type-bound procedures habilitan POO. Los constructores crean instancias. Los derived types pueden tener valores default y extender otros tipos (herencia). Use para modelado de datos complejos.

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

Atributos Intent

Los parámetros intent(in) son de solo lectura (no pueden modificarse). intent(out) es solo escritura (establecido por el procedimiento). intent(inout) es lectura-escritura. El compilador verifica violaciones de intent. Mejora la claridad del código y habilita optimizaciones. Siempre especifique 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 y Elemental

Las pure functions no tienen efectos secundarios (sin E/S, sin estado mutable). El compilador puede optimizarlas. Las elemental functions funcionan tanto en escalares como arrays automáticamente. Son pure por defecto. Ideales para operaciones matemáticas. Habilita ejecución paralela.

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

Arrays

Declaración de Array

Los arrays de Fortran son 1-indexados por defecto. Lower bounds personalizados con (0:). Orden column-major (el primer índice varía más rápido). Los arrays allocatable se asignan en heap y deben desasignarse. Las constantes de array usan [ ]. Los arrays de Fortran son más eficientes que los arrays de C debido a descriptores.

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]

Operaciones de Array

Fortran soporta operaciones whole-array: +, -, *, /, **. Sin bucles necesarios para math element-wise. Funciones intrínsecas: sum, product, maxval, minval, any, all, count. Mucho más rápido que bucles debido a vectorización. Esta es la fortaleza de Fortran para computación numérica.

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

Secciones de Array

Las secciones de array usan sintaxis (start:end:stride). :: significa default (1 hasta end, stride 1). Strides negativos revierten. Las secciones multi-dimensionales funcionan en cualquier dimensión. Las secciones pueden pasarse a procedimientos. Muy poderoso para slicing sin copia.

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

Construct where

where es asignación condicional a nivel de array. Como un if vectorizado. Elsewhere maneja el caso falso. Más eficiente que bucles porque puede vectorizarse. Use para operaciones condicionales element-wise en arrays.

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

Asignación dinámica

Los arrays allocatable son de tamaño dinámico. allocate crea, deallocate libera. Fortran 2003+ auto-desasigna al final del scope. Verifique el estado de asignación con allocated(). Los arrays allocatable se reasignan automáticamente en asignación (Fortran 2003+). Mucho más seguro 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

Operaciones de E/S

Salida formateada

Las cadenas de formato controlan la salida. I5 = integer ancho 5. F8.3 = float ancho 8, 3 decimales. I0 = ancho mínimo. A = cadena. X = espacio. / = newline. write(*,...) es lo mismo que print pero más flexible. Use cadenas de formato para salida alineada.

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 de archivos

open conecta un archivo a un unit. newunit asigna un número de unit libre. status: old (debe existir), new (no debe existir), replace. iostat devuelve non-zero en error o EOF. Siempre verifique iostat para evitar crashes. close desconecta el archivo.

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 agrupa variables para E/S. El formato de archivo es &config n=10, x=3.14 /. Útil para archivos de configuración. Las variables pueden estar en cualquier orden. Solo las variables listadas se leen/escriben. Mucho más fácil que parsear formatos personalizados.

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)

Archivos internos

Los archivos internos usan cadenas character como units de E/S. write a una cadena convierte valores a texto. read desde una cadena parsea texto. Útil para conversión de tipos y formateo. trim elimina espacios trailing. Mucho más 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 binaria

La E/S no formateada escribe datos binarios crudos. Más rápida y más compacta que texto. access="stream" para acceso a nivel de byte (Fortran 2008). No portable entre arquitecturas (endianness). Use para datasets científicos grandes. La E/S formateada es para datos legibles por humanos.

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

Programación Paralela

OpenMP

OpenMP paraleliza bucles con directivas. !$omp parallel do distribuye iteraciones a través de hilos. private: cada hilo tiene su propia copia. reduction: combina resultados. Compile con -fopenmp. Forma fácil de paralelizar código numérico.

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

Los coarrays (Fortran 2008) son arrays paralelos integrados. Cada image (proceso) tiene su propia copia. [N] accede datos de otra image. sync all es una barrera. this_image() devuelve el número de image. Integrados en el lenguaje, sin librería necesaria.

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

Fundamentos de MPI

MPI (Message Passing Interface) es el estándar para paralelismo distribuido. mpi_init/finalize inician y terminan. comm_rank da el ID del proceso. comm_size da el total de procesos. Send/recv para comunicación. Escala a miles de cores. Use para 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) indica que las iteraciones del bucle son independientes. El compilador puede paralelizar automáticamente. local declara variables privadas. Más seguro que OpenMP: el compilador verifica independencia. Use para bucles 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

Patrón de reducción

Las reducciones combinan resultados parciales de cada hilo. Comunes: sum, product, max, min. Cada hilo computa un resultado parcial local. El runtime los combina al final. Evita data races. Esencial para algoritmos numéricos paralelos.

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étodos Numéricos

Álgebra Lineal

Fortran tiene operaciones de matriz integradas. matmul multiplica matrices. dot_product computa producto punto. transpose transpone. Estas están altamente optimizadas (nivel BLAS). Para producción, use LAPACK. Fortran es el lenguaje de elección para computación numérica de alto rendimiento.

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)

Resolviendo ODEs

El método de Euler es el solver ODE más simple: y(n+1) = y(n) + dt*f(t,y). Para precisión, use Runge-Kutta (RK4). El interface block pasa funciones como argumentos. Fortran es ideal para computación científica debido a operaciones de array y rendimiento.

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

Números aleatorios

random_number genera reales uniformes [0,1). random_seed inicializa el generador. Para integers, escale y convierta. Box-Muller transforma uniform a distribución normal. Para trabajo serio, use una librería (ej., Mersenne Twister). Siempre haga seed para reproducibilidad.

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)

Interpolación

La interpolación lineal estima valores entre puntos conocidos. Encuentre el intervalo, luego interpole. Para resultados más suaves, use interpolación cubic spline. Las operaciones de array de Fortran hacen esto conciso. Siempre verifique bounds para evitar errores de extrapolación.

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

Integración numérica

La regla trapezoidal aproxima integrales: suma de trapezoides. Más precisa: regla de Simpson. Para dimensiones superiores, use cuadratura Gaussiana. Fortran destaca en integración numérica debido al rendimiento. Siempre valide con soluciones analíticas conocidas.

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

Pitfalls Comunes

Indexación basada en 1

Los arrays de Fortran son 1-indexados por defecto, a diferencia de C/Python (0-indexados). Esto causa errores off-by-one al portar código. Los lower bounds personalizados (0:9) están permitidos. Sea consistente dentro de un proyecto. Verifique bounds de array con el flag del compilador -fcheck=bounds.

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

Tipado implícito

Sin implicit none, las variables que comienzan con i-n son integer, otras real. Esto causa bugs sutiles (un typo crea una nueva variable). Siempre use implicit none. Fortran moderno (2018+) puede establecerlo globalmente con -fimplicit-none. Esta es la práctica recomendada más importante de Fortran.

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

Orden column-major

Fortran almacena arrays column-major: m(1,1), m(2,1), m(3,1), m(1,2), ... Acceder columna por columna es cache-friendly. El orden de bucle incorrecto causa cache misses y ralentiza 10x+. Siempre haga coincidir el orden del bucle con el layout de memoria. Opuesto a C (row-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

Paso por referencia

Fortran pasa argumentos por referencia (como punteros C). Las subroutines pueden modificar variables del llamador a menos que se especifique intent(in). Sin intent, las modificaciones accidentales causan bugs. Siempre especifique intent. intent(out) señala que el procedimiento establecerá el valor.

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)

Precisión de punto flotante

El real por defecto es precisión simple (~7 dígitos), a menudo insuficiente. Use double precision para computación científica. kind(1.0d0) o selected_real_kind(15) define double. Siempre añada sufijo a literales: 3.14_dp. Mezclar precisiones causa truncamiento silencioso. Use iso_fortran_env para 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?