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.
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: ./helloVariables 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.
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 variablesConstantes 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.
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 constantsOperadores 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.
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 operatorsFunciones 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.
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 intrinsicsFlujo 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.
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_demoSelect 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).
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_demoBucles 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.
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_loopsDo 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.
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_demoCycle, 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.
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_controlArrays 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.
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_declSecciones 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.
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_sectionsArrays 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.
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 matricesArrays 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.
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_demoFunciones 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.
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_funcsCadenas 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.
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_declConcatenació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.
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_opsFunciones 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).
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_funcsFormateo 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.
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_demoParseo 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.
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: NYCProcedimientos: 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.
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_demoSubroutines 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).
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_demoFunciones 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.
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_demoArgumentos 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.
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_demoProcedimientos 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.
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_demoMó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.
! 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 mainControl 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.
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_accountDerived 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.
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_modProcedimientos 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.
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_genericsSobrecarga 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.
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_opsDerived 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.
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_typesComponentes 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).
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_componentsType-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.
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_stackHerencia 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++.
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_polymorphismTipos 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.
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_typesE/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.
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_openE/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.
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_ioE/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).
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_ioNamelist (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.
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_demoArchivos 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.
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_ioComputació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).
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.
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 linalgNú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.
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_demoIntegració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.
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 numericalAritmé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).
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_demoCoarrays 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.
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 programAcceso 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.
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 programOperaciones 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.
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 programDerived 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(:)[:].
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 programCoarrays 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+).
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 programInteroperabilidad 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.
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.
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 programTipos 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).
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:
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.
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);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.
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 modulePolimorfismo 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.
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 moduleFinalizers 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.
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 automaticallyConstructores 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).
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 programTipos 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.
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)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.
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 programPDT 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.
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 programPDT 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.
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 programPDT 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.
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 moduleLimitaciones 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.
! 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 insteadSubmodules
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.
! 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 submoduleMú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.
! 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 submoduleProcedimientos 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.
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 submoduleHeredando 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).
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.
! 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.oIEEE 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.
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 programManejo 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.
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 programModos 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.
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 programConsulta 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.
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 programControlando 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.
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 programRendimiento 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.
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 programProcedimientos 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.
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 programFlags 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.
! 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.
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 programProfiling 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.
! 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 programProgramació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.
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 moduleInterop 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.
! 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 appInterop 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 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.
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 programIntegració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.
# 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 filesE/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.
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 programStream 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).
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 programE/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.
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 programE/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.
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.
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 programDepuració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).
! 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 1Manejo 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.
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 programGDB 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'.
! 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 executionProfiling 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.
! 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 perfBenchmarking 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.
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 programFortran 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.
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.
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 testDerived 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.
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 typeAtributos 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.
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 subroutinePure 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.
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]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.
! 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.
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) ! 3Secciones 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.
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 rowConstruct 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.
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 ifAsignació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.
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+)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.
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, /: newlineE/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.
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, readwriteNamelist
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.
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.
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 unitsE/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.
! 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 architecturesProgramació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.
!$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=4Coarrays
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.
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.f90Fundamentos 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.
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.f90do 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.
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 iterationPatró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.
!$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 endMé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.
! 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.
! 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 subroutineNú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.
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.
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 functionIntegració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.
! 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 functionPitfalls 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 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 ! OKTipado 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.
! 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 programOrden 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 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 doPaso 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.
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.
! 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 literalsFragmentos de Fortran relacionados
Copy-paste ready code for common tasks.
Arrays y Operaciones Vectoriales
Crear y operar con arrays en Fortran moderno.
Subrutinas y Funciones
Definir procedimientos reutilizables en Fortran.
Módulos y Tipos Derivados
Organizar código con módulos y tipos estilo OOP.
E/S de Archivos y Formateo
Leer y escribir archivos con salida formateada.
Paralelismo con OpenMP
Paralelizar bucles con directivas OpenMP.
Cálculo Numérico: Álgebra Lineal (BLAS/LAPACK)
Llamar a BLAS/LAPACK para operaciones matriciales.
Tipos Derivados y Punteros
Tipos personalizados con componentes allocatable y punteros.
Punteros y Allocatable
Asignación dinámica de memoria en Fortran.
Was this helpful?