Fortran 

Introduction To Fortran 

a

  1. Complete Fortran Programming Learning Roadmap
    1. Chapter 1: Why Fortran Still Matters in 2026
      1. What This Guide Covers
      2. What Is Fortran, Really?
      3. History of Fortran
      4. Features of Fortran
      5. Applications of Fortran
      6. Fortran vs Other Languages
      7. Software Development Process in Fortran
      8. Career Relevance of Fortran
      9. Dependencies and Prerequisites
      10. Common Beginner Mistakes
      11. Common Intermediate-Level Mistakes
      12. Role of AI in Fortran Development
    2. Chapter 2: Complete Setup Guide for Windows, Mac, and Linux
      1. Windows Setup
      2. macOS Setup
      3. Linux (Ubuntu) Setup
      4. Compilation Process and Software Execution Lifecycle
    3. Chapter 3: Program Units and Basic Syntax
      1. 1. Structure of a Fortran Program
      2. 2. Identifiers, Variables, and Constants
      3. 3. Primitive Data Types
      4. 4. Operators and Expressions
      5. 5. Comments and Formatting Standards
    4. Chapter 4: Input and Output
      1. 1. The READ and WRITE Statements
      2. 2. Formatted versus Unformatted I/O
      3. 3. Working with Text and Binary Files
      4. 4. Error Handling and Data Validation
    5. Chapter 5: Control Structures
      1. 1. IF, IF-ELSE, and Nested IF Statements
      2. 2. The SELECT CASE Statement
      3. 3. DO Loops and DO WHILE Loops
      4. 4. CYCLE and EXIT
      5. 5. Loop Optimization
    6. Chapter 6: Functions and Subroutines
      1. 1. The Difference Between Functions and Subroutines
      2. 2. Arguments and INTENT Declarations
      3. 3. Recursion
      4. 4. Scope Management
    7. Chapter 7: Arrays and Strings
      1. 1. Array Declaration and Initialization
      2. 2. Array Operations and Slicing
      3. 3. Multi-dimensional Arrays
      4. 4. Allocatable and Dynamic Arrays
      5. 5. Strings and String Manipulation
    8. Chapter 8: Derived Data Types and Pointers
      1. 1. Derived Types and Structures
      2. 2. Components
      3. 3. Pointers
      4. 4. Dynamic Memory Allocation and Management
    9. Chapter 9: Modules and Advanced Program Structure
      1. 1. Creating and Using Modules
      2. 2. Public and Private Components
      3. 3. Interface Blocks
      4. 4. Procedure Overloading
      5. 5. Modular Architecture
    10. Chapter 10: Modern Fortran Features
      1. 1. Fortran 90 through Fortran 2018 Features
      2. 2. Object-Oriented Programming in Fortran
      3. 3. Intrinsic Functions and Array Programming
      4. 4. C Interoperability with ISO_C_BINDING
      5. 5. Stream and Non-Advancing I/O
    11. Chapter 11: Scientific Computing with Fortran
      1. 1. Linear Algebra and Matrix Operations
      2. 2. Numerical Integration and Differentiation
      3. 3. Root Finding Algorithms
      4. 4. Differential Equations
      5. 5. Random Number Generation and Statistical Computing
      6. 6. Scientific Simulations
    12. Chapter 12: Parallel and High-Performance Fortran
      1. 1. HPC Fundamentals
      2. 2. OpenMP and Shared Memory Parallelism
      3. 3. MPI and Distributed Computing
      4. 4. Vectorization and Performance Optimization
      5. 5. Profiling and Scalability
    13. Chapter 13: Software Engineering with Fortran
      1. 1. Version Control and Git Workflows
      2. 2. Testing and Debugging
      3. 3. Refactoring and Documentation
      4. 4. Maintainability
    14. Chapter 14: Practical Projects
      1. 1. Scientific Calculator
      2. 2. Projectile Motion Simulation
      3. 3. Linear Equation Solver
      4. 4. Temperature Data Analysis
      5. 5. Matrix Computation Engine
    15. Chapter 15: Competitive Programming with Fortran
      1. 1. Fast I/O Techniques
      2. 2. Mathematical Algorithms and Dynamic Programming
      3. 3. Graph Algorithms
      4. 4. Optimization Techniques and Contest Strategies
    16. Chapter 16: Production-Level Scientific Computing
      1. 1. HPC Architecture
      2. 2. Large Scientific Codebases and Legacy Modernization
      3. 3. Performance Engineering
      4. 4. Research Software Development and Reproducible Computing
    17. Chapter 17: Career Readiness
      1. 1. Scientific Programming and HPC Engineering Careers
      2. 2. Research Computing and Computational Science Paths
      3. 3. Portfolio Development
      4. 4. AI-Augmented Scientific Development
    18. Final Advice

Complete Fortran Programming Learning Roadmap

Chapter 1: Why Fortran Still Matters in 2026

When you open your laptop right now, you’re not directly interacting with code written in Fortran. But the weather forecast you checked this morning, the financial models that move money between banks, the simulations that design safer aircraft, and the climate models predicting our planet’s future—all of these systems rely heavily on Fortran. Despite being over six decades old, Fortran remains one of the most influential and widely-used programming languages in the world of scientific computing.

Fortran has a reputation for being “old.” It’s not—it’s just purpose-built, and this is a very different thing. Python and JavaScript let you get away with sloppy habits because they quietly manage memory and types behind the scenes. Fortran refuses to do that. It forces you to actually understand what your computer is doing at every step—where a variable lives in memory, how big it is, and how to structure code for maximum performance. This honesty is precisely why Fortran still powers supercomputers, climate models, and scientific simulations decades after “easier” languages appeared. It’s also exactly why learning it properly makes you a noticeably stronger programmer in every other language you touch afterward.

The goal of this guide is comprehensive: to take you from complete beginner to confident, job-ready Fortran programmer, on Windows, Mac, or Linux. We provide plain-English explanations first, followed by a full professional-grade roadmap covering every stage from your very first “Hello, World!” to HPC and career readiness.

What This Guide Covers

This comprehensive introduction covers everything you need to know before diving into actual coding. We explore what Fortran actually is and why it was created, the complete setup process for every major operating system, a detailed stage-by-stage roadmap of everything you’ll learn, how to use AI effectively without becoming dependent on it, what happens when you compile code through the compilation and execution lifecycle, and common pitfalls and how to avoid them. By the end of this guide, you’ll have a crystal-clear understanding of Fortran and a complete learning path that will take you from absolute beginner to professional scientific programmer.

What Is Fortran, Really?

Before you write a single line of Fortran code, you need to understand what this language actually is and why it was created. Let’s break down the definition slowly and carefully.

Fortran is a compiled, statically-typed programming language purpose-built for numerical and scientific computation.

Let’s unpack every word in that sentence, because each one matters.

Compiled

Here’s the fundamental distinction that shapes everything about Fortran: Fortran is compiled, not interpreted. When you write a program in Python or JavaScript, an interpreter reads your code line-by-line and executes it, every single time the program runs. This is convenient for development because you can make changes and see results immediately, but it comes with a performance cost. The interpreter is essentially translating your code on the fly while it’s running, which adds overhead.

When you write Fortran, a separate tool called a compiler reads your human-readable code and translates it into machine code—raw binary instructions your CPU can execute directly. This happens as an extra step before you ever run your program. The payoff is speed: a compiled Fortran program typically runs many times faster than an equivalent interpreted program because the CPU is executing instructions meant exactly for it, with no translation happening on the fly.

Here’s what the process looks like at a high level:

Source Code (.f90) → Compiler → Machine Code (.exe or .out) → CPU Executes

This compilation step is why Fortran can feel slower to develop in—you have to compile before you can test—but the resulting performance is unmatched by most other languages.

Statically-Typed

Every variable in Fortran must be declared with a specific data type before it can be used. This catches many errors at compile time rather than at runtime, making programs more reliable and easier to debug. The compiler can also optimize code better because it knows exactly what type of data each variable holds.

Purpose-Built for Numerical and Scientific Computation

This is the feature that makes Fortran truly special. Fortran was designed from the ground up for math, science, and engineering. It has native complex number support with real and imaginary parts built in, first-class array operations where arrays are fundamental to the language not an afterthought, high-performance semantics designed to run on supercomputers, parallel programming capabilities with OpenMP and MPI support built into the language, and explicit performance control allowing you to optimize code for specific hardware.

History of Fortran

Fortran was created by a team at IBM led by John Backus in the late 1950s, with the first manual published in 1956 and the first compiler released in 1957. The name “FORTRAN” stands for “FORmula TRANslation” or “Formula Translator.”

Backus’s motivation was simple: programming in assembly language was tedious, error-prone, and machine-dependent. Scientists and engineers needed a way to express mathematical formulas directly in code without worrying about low-level machine details. Fortran introduced the concept of a compiler that could translate human-readable mathematical formulas into efficient machine code. This was revolutionary at the time and made Fortran the first high-level programming language to achieve widespread adoption.

The Evolution of Fortran

Fortran has evolved significantly over the decades, with major revisions that have kept it relevant. FORTRAN I (1957) was the first compiler. FORTRAN II (1958) expanded the language by introducing support for subroutines and functions.. FORTRAN IV (1962) provided better I/O and logical constructs. FORTRAN 66 (1966) became the first ANSI standard. FORTRAN 77 (1978) introduced structured programming and character handling. Fortran 90 (1990) brought free-form syntax, modules, array operations, and recursion. Fortran 95 (1995) added minor updates and high-performance features. Fortran 2003 (2003) introduced object-oriented programming and C interoperability. Fortran 2008 (2008) added coarrays for parallel programming and submodule support. Fortran 2018 (2018) brought new features and enhancements. Fortran 2023 is the latest standard.

Each version adds safer, more expressive ways to write code—modules to avoid global variables, array operations for concise math, and object-oriented features for better code organization—without breaking the huge amount of existing Fortran code already running in production systems worldwide. This backward-compatibility promise is a big reason companies trust Fortran for decades-long projects. Code written in the 1970s can still compile with a modern Fortran compiler. This stability is unusual in the programming world and has kept Fortran relevant across generations of developers.

Features of Fortran

Fortran offers several distinctive features that make it uniquely suited for scientific and high-performance computing. First-class array operations means arrays are fundamental to the language, not just a library feature. You can perform operations on entire arrays at once: C = A + B adds all elements of arrays A and B and stores in C. Built-in complex numbers means Fortran has native support for complex numbers, essential for many scientific applications: COMPLEX :: z; z = (1.0, 2.0). Intrinsic functions means Fortran provides a rich set of built-in mathematical and array functions that execute efficiently on modern hardware. Performance means Fortran compilers produce highly optimized code, especially for numerical operations. Parallel programming means modern Fortran includes coarrays for parallel programming directly in the language, plus excellent support for OpenMP and MPI. C interoperability means Fortran can call C functions and be called from C, making it possible to combine Fortran’s numerical strength with C’s system programming capabilities.

Applications of Fortran

Fortran remains a critical tool in many scientific and engineering domains. In Climate Science, it’s used for weather forecasting, climate modeling, and ocean dynamics. In Astrophysics, it’s used for stellar evolution, cosmology, and planetary science. In Computational Fluid Dynamics, it’s used for aerospace design, automotive engineering, and weather prediction. In Quantum Chemistry, it’s used for molecular dynamics and quantum mechanics calculations. In Computational Biology, it’s used for protein folding, genomics, and biophysics. In Financial Modeling, it’s used for quantitative finance, risk analysis, and option pricing. In Engineering, it’s used for structural analysis, finite element analysis, and optimization. In Geophysics, it’s used for seismology, reservoir modeling, and plate tectonics.

Fortran vs Other Languages

Fortran vs C

Fortran focuses on scientific computation while C focuses on system programming. Fortran uses mathematical notation while C uses terse, symbol-heavy syntax. Fortran has first-class arrays with slicing while C uses pointers to memory blocks. Fortran has native complex number support while C must use structs. Fortran is optimized for math while C is flexible but manual. Fortran wins for numerical computations, array operations, and mathematical code.

Fortran vs C++

Fortran focuses on scientific computation while C++ is general-purpose with full OOP. Fortran has basic OOP (Fortran 2003+) while C++ has full OOP. Fortran is superior for numerical code performance while C++ is fast but more complex. Fortran is simpler and more focused while C++ is complex with more features. Fortran wins for simpler numerical code, faster compilation, and easier array syntax.

Fortran vs Python

Fortran is extremely fast while Python is slow (requires libraries). Fortran is less forgiving while Python is very forgiving. Fortran has native arrays and math while Python has NumPy, SciPy, and Pandas. Fortran is compiled and not interactive while Python has excellent interactivity. Fortran wins for production high-performance computing and large-scale simulations.

Software Development Process in Fortran

The Fortran development workflow follows these general steps: Edit source code (in .f90 files), Compile with a Fortran compiler, Link with libraries, Execute the resulting program, Analyze output and results.

Career Relevance of Fortran

Fortran skills remain valuable in several specialized roles. Scientific Programmers develop simulation and analysis software for research. HPC Engineers optimize code for supercomputers. Computational Scientists use simulations to solve scientific problems. Weather Forecasters work with weather and climate models. CFD Engineers simulate fluid flow for engineering applications. Quantitative Analysts use numerical methods in finance.

Dependencies and Prerequisites

You Do NOT Need a math or computer science degree, prior programming experience, or an expensive or powerful computer.

You DO Need patience with error messages. Fortran compiler errors can look intimidating, especially for beginners. This gets dramatically easier with practice. You need a willingness to understand why an error happened. You need comfort navigating files and folders and using a terminal.

Common Beginner Mistakes

Forgetting implicit none uses implicit typing rules leading to unexpected bugs. Mismatched data types means confusing integer, real, and double precision. Array index errors means off-by-one errors in array indexing. Missing or wrong intent means forgetting to specify INTENT for subroutine parameters. File I/O errors means opening files that don’t exist or forgetting to close them.

Common Intermediate-Level Mistakes

Assuming aliasing means incorrectly assuming arrays don’t overlap. Ignoring compiler warnings means failing to fix warnings that indicate performance issues. Not using modules means using common blocks instead of modern modules. Misusing allocatables means not deallocating memory or losing pointers. Overcomplicating I/O means not using formatted and unformatted files correctly.

Role of AI in Fortran Development

AI assistants like ChatGPT, Claude, and GitHub Copilot are especially valuable for Fortran because the language has a steep learning curve and a lot of legacy code.

Good Uses of AI include asking “Explain this compiler error in plain English,” “How do I optimize this Fortran loop?” “Convert this Fortran 77 code to modern Fortran,” “Show me how to use allocatable arrays,” and “Quiz me on array slicing in Fortran.”

Habits to Avoid include pasting AI-generated code without understanding it, trusting AI blindly about Fortran’s performance and semantics, and skipping the “why” behind a fix.

Chapter 2: Complete Setup Guide for Windows, Mac, and Linux

Every Fortran setup, regardless of operating system, needs exactly two things: a compiler which is the tool that translates your code into a runnable program, and an editor or IDE where you actually write your code.

Recommended Approach: GFortran from the GNU Compiler Collection (GCC). It’s free, open-source, cross-platform, and well-maintained.

Windows Setup

Installing GFortran

Download MSYS2 from https://www.msys2.org/ and run the installer following the default installation steps. The default installation location is C:\msys64. Open the MSYS2 terminal that comes with the installation. Update the package database by running:

pacman -Syu

Install GFortran by running:

pacman -S mingw-w64-ucrt-x86_64-gcc-fortran

Adding the Compiler to Your PATH

Search for “Environment Variables” in the Start Menu. Click “Edit the system environment variables” and then “Environment Variables…” Under “System variables,” find the Path variable and click “Edit…” Click “New” and add C:\msys64\ucrt64\bin. Click “OK” on all windows.

Verifying Installation

Open Command Prompt (not the MSYS2 terminal) and run:

gfortran --version

You should see version information.

Installing VS Code

Download VS Code from https://code.visualstudio.com/ and run the installer following the default steps. Open VS Code, go to the Extensions panel (Ctrl+Shift+X). Search for and install the “Fortran” extension.

Creating Your First Project

Create a project folder:

mkdir fortran-course
cd fortran-course

Open this folder in VS Code:

code .

Create a new file named exactly main.f90 and paste this code:

program hello
    implicit none
    print *, "Hello, World!"
end program hello

Compiling and Running

Open the VS Code integrated terminal with Ctrl+`. Compile the program:

gfortran main.f90 -o main

Run the program:

main.exe

You should see:

Hello, World!

Installing Intel Fortran (Optional Alternative)

If you have access to Intel Fortran, download and install the Intel oneAPI HPC Toolkit from Intel’s website. It provides world-class performance, especially for Intel architectures.

Common Windows Problems

If you get 'gfortran' is not recognized, the PATH variable wasn’t set correctly. Double-check the folder path and restart your terminal. If the program window opens and closes instantly, run from a terminal instead of double-clicking. If compilation produces no output but also no error, check that the file is saved. VS Code doesn’t auto-save.

macOS Setup

Installing GFortran

Open Terminal. Install Homebrew (if you don’t have it) with:

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

Install GFortran with:

brew install gcc

Note: The gcc formula includes gfortran.

Verifying Installation

In Terminal, run:

gfortran --version

You should see GCC version information.

Installing VS Code

Download VS Code from https://code.visualstudio.com/ and open the downloaded file, then drag the VS Code application into your Applications folder. Open VS Code and install the “Fortran” extension.

Creating Your First Project

Create a project folder:

mkdir fortran-course
cd fortran-course

Create a new file:

touch main.f90

Open VS Code in this folder:

code .

Open main.f90 and paste the Hello World code:

program hello
    implicit none
    print *, "Hello, World!"
end program hello

Compiling and Running

Open the integrated terminal in VS Code (Ctrl+` on Mac). Compile with:

gfortran main.f90 -o main

Run with:

./main

Expected output:

Hello, World!

Linux (Ubuntu) Setup

Installing GFortran

Open a terminal (you can press Ctrl+Alt+T on Ubuntu). Update the package list:

sudo apt update

Install GFortran:

sudo apt install gfortran

Verifying Installation

Run this command:

gfortran --version

You should see GCC version information.

Installing VS Code

Download the .deb package from https://code.visualstudio.com/ and install it with:

sudo dpkg -i code_*.deb

If you get dependency errors, run:

sudo apt install -f

Open VS Code and install the Fortran extension.

Creating Your First Project

Create a project folder:

mkdir fortran-course
cd fortran-course

Create a new file:

touch main.f90

Open VS Code:

code .

Open main.f90 and paste the Hello World code.

Compiling and Running

Open the integrated terminal in VS Code. Compile with:

gfortran main.f90 -o main

Run with:

./main

Expected output:

Hello, World!

Common Linux and Mac Problems

If you get permission denied when running ./main, run chmod +x main to mark the file as executable. If you get command not found: gfortran, re-run the install command for your specific OS. If you get code: command not found, on Mac, open VS Code, open the Command Palette (Cmd+Shift+P), and run “Shell Command: Install ‘code’ command in PATH.” On Linux, restart your terminal.

Compilation Process and Software Execution Lifecycle

When you run gfortran main.f90 -o main, several stages happen behind the scenes. Understanding this pipeline removes a huge amount of confusion later, especially around error messages.

Stage 1: Preprocessing
Any lines starting with # or using #include or #define are handled first. The preprocessor expands macros and includes header files.

Stage 2: Compilation
Your Fortran source code is translated into assembly code. This is where syntax errors are caught—if your code has a mistake in the grammar of the language, the compiler will tell you here.

Stage 3: Assembly
That assembly code is converted into object code: pure binary, machine-readable, but not yet a complete, runnable program. The output is typically a .o file on Linux/Mac or .obj on Windows.

Stage 4: Linking
Your object code gets combined with pre-compiled library code—for example, the code that actually implements print * and other intrinsics—to produce one final, complete executable file. This stage resolves references between different files and libraries. Linking is where “undefined reference” errors happen. If you declared a subroutine but never defined it, you’ll see the error during linking, not during compilation.

Stage 5: Execution
Your operating system loads that executable into memory, and the CPU begins running its instructions.

Why This Pipeline Matters
Knowing this pipeline is what makes error categories make sense later. When you see a “linker error” or “undefined reference,” you know that the error is happening during the linking stage, not while your code is being compiled. This helps you narrow down where to look for the problem.

Chapter 3: Program Units and Basic Syntax

1. Structure of a Fortran Program

The structure of a Fortran program follows a specific format that every program must adhere to. This includes the program statement, declarations, and the executable code.

The program statement names the program. implicit none enforces explicit variable declarations, preventing bugs from implicit typing. The print * statement outputs data, and the end program statement marks the end. Understanding each element is crucial before writing more complex code.

Code Example

program hello
    ! This is a comment
    implicit none        ! Enforces explicit declarations
    integer :: x         ! Variable declaration
    x = 10               ! Assignment
    print *, "Hello, World!"  ! Output
    print *, "x =", x
end program hello

The program starts at the program statement. implicit none turns off implicit typing, requiring all variables to be declared. The executable statements run in order. The program ends at end program.

2. Identifiers, Variables, and Constants

Variables store data in a program. Each variable has a name (identifier) and a type. Constants are variables whose values cannot change.

Identifiers must follow specific rules: can contain letters, digits, and underscores, cannot start with a digit. Fortran is case-insensitive. Variables are declared with a type. Constants use the parameter attribute. The parameter attribute makes a variable constant.

Code Example

program variables
    implicit none
    
    ! Variable declarations
    integer :: count              ! Integer variable
    real :: weight                ! Real variable
    character(len=20) :: name     ! String variable
    logical :: is_migratory       ! Boolean variable
    
    ! Constants (parameter attribute)
    real, parameter :: PI = 3.14159
    integer, parameter :: MAX_BIRDS = 100
    
    ! Valid identifiers
    integer :: bird_count         ! Contains underscore
    integer :: birdCount          ! Case doesn't matter in Fortran
    integer :: bird_1             ! Contains digit (not starting)
    
    ! Invalid identifiers (commented out)
    ! integer :: 1bird            ! ERROR: Cannot start with digit
    ! integer :: bird-count       ! ERROR: Hyphen not allowed
    
    count = 10
    weight = 25.5
    name = "Sparrow"
    is_migratory = .true.
    
    print *, "Count:", count
    print *, "Weight:", weight
    print *, "Name:", name
    print *, "PI:", PI
    print *, "MAX_BIRDS:", MAX_BIRDS
    
    ! Cannot modify constants
    ! PI = 3.14                   ! ERROR: Cannot modify constant
    
end program variables

Variables are declared with a type before use. parameter makes values constant. Case-insensitivity means birdCount and birdcount are the same. Constants cannot be modified after declaration.

3. Primitive Data Types

Fortran has several built-in data types for storing different kinds of data.

integer stores whole numbers. Range depends on system but typically -2,147,483,648 to 2,147,483,647. Memory size is 4 bytes on most systems. Use for counting and indexing.

real stores decimal numbers (single precision). Range is approximately ±1.18e-38 to ±3.4e38. Memory size is 4 bytes with about 7 decimal digits of precision. Use for general scientific calculations.

double precision stores decimal numbers with higher precision. Range is approximately ±2.23e-308 to ±1.79e308. Memory size is 8 bytes with about 15 decimal digits of precision. Use for high-precision calculations.

complex stores real and imaginary parts. Memory size depends on the underlying real type. Use for complex number calculations.

character stores text strings. Can be fixed-length or allocatable. Use for text data and formatting.

logical stores .true. or .false.. Memory size is typically 1 byte. Use for conditions and boolean logic.

Code Example

program data_types
    implicit none
    
    ! Integer
    integer :: bird_count = 42
    integer, parameter :: MAX_SIZE = 100
    
    ! Real (single precision)
    real :: weight = 25.5
    real :: temperature = -10.5
    
    ! Double precision
    double precision :: pi = 3.141592653589793d0
    double precision :: large_value = 1.234d-5
    
    ! Complex
    complex :: z = (1.0, 2.0)
    complex, parameter :: i_unit = (0.0, 1.0)
    
    ! Character
    character(len=20) :: name = "Sparrow"
    character(len=10) :: status = "Active"
    
    ! Logical
    logical :: is_migratory = .true.
    logical :: is_extinct = .false.
    
    ! Display values
    print *, "Integer:", bird_count
    print *, "Real:", weight
    print *, "Double precision:", pi
    print *, "Complex:", z
    print *, "Complex real part:", real(z)
    print *, "Complex imag part:", aimag(z)
    print *, "Character:", name
    print *, "Logical:", is_migratory
    
    ! Memory sizes
    print *, "Size of integer:", storage_size(bird_count), "bits"
    print *, "Size of real:", storage_size(weight), "bits"
    print *, "Size of double:", storage_size(pi), "bits"
    print *, "Size of complex:", storage_size(z), "bits"
    print *, "Size of logical:", storage_size(is_migratory), "bits"
    
end program data_types

Each data type stores values in a specific format. integer and real are the most common. double precision provides more accuracy. complex stores two real values. character stores text with specified length. logical stores boolean values.

4. Operators and Expressions

Operators perform operations on data. Expressions bring together operands and operators to perform operations and generate a resulting value.

Arithmetic operators include + for addition, - for subtraction, * for multiplication, / for division, and ** for exponentiation. Relational operators are used to compare values. In Fortran, these include == for equality, /= for inequality, < for less than, > for greater than, <= for less than or equal to, and >= for greater than or equal to.

Logical operators combine or modify logical conditions. Fortran provides .and. for AND, .or. for OR, .not. for NOT, .eqv. for logical equivalence, and .neqv. for logical non-equivalence.

Code Example

program operators
    implicit none
    
    integer :: a = 10, b = 3
    real :: x = 5.5, y = 2.0
    logical :: flag1, flag2, result
    
    ! ---- ARITHMETIC OPERATORS ----
    print *, "--- Arithmetic Operators ---"
    print *, "a + b =", a + b           ! 13
    print *, "a - b =", a - b           ! 7
    print *, "a * b =", a * b           ! 30
    print *, "a / b =", a / b           ! 3 (integer division)
    print *, "x / y =", x / y           ! 2.75 (real division)
    print *, "a ** b =", a ** b         ! 1000 (10^3)
    print *, "x ** y =", x ** y         ! 30.25 (5.5^2)
    print *
    
    ! ---- RELATIONAL OPERATORS ----
    print *, "--- Relational Operators ---"
    print *, "a == b:", a == b          ! .false.
    print *, "a /= b:", a /= b          ! .true.
    print *, "a > b:", a > b            ! .true.
    print *, "a < b:", a < b            ! .false.
    print *, "a >= b:", a >= b          ! .true.
    print *, "a <= b:", a <= b          ! .false.
    print *
    
    ! ---- LOGICAL OPERATORS ----
    print *, "--- Logical Operators ---"
    flag1 = .true.
    flag2 = .false.
    
    result = flag1 .and. flag2
    print *, "true .and. false:", result   ! .false.
    
    result = flag1 .or. flag2
    print *, "true .or. false:", result    ! .true.
    
    result = .not. flag1
    print *, ".not. true:", result         ! .false.
    
    result = flag1 .eqv. flag2
    print *, "true .eqv. false:", result   ! .false.
    
    result = flag1 .neqv. flag2
    print *, "true .neqv. false:", result  ! .true.
    print *
    
    ! ---- OPERATOR PRECEDENCE ----
    print *, "--- Operator Precedence ---"
    print *, "a + b * 2 =", a + b * 2     ! 16 (b*2 first)
    print *, "(a + b) * 2 =", (a + b) * 2 ! 26 (parentheses first)
    print *, "a / b * 2 =", a / b * 2     ! 6 (left to right)
    
    ! ---- CHARACTER CONCATENATION ----
    print *, "--- Character Operators ---"
    character(len=20) :: first = "Sparrow"
    character(len=20) :: last = "Bird"
    print *, "first // ' ' // last:", trim(first) // " " // trim(last)
    
end program operators

Arithmetic operators perform mathematical calculations. The / operator performs real division if either operand is real; otherwise integer division. ** is exponentiation. Relational operators compare values and return logical results. Logical operators combine or negate logical values. Operator precedence determines evaluation order.

5. Comments and Formatting Standards

Comments document your code and are ignored by the compiler. Formatting standards make code more readable.

Comments in Fortran begin with an exclamation mark (!) and extend to the end of the current line. For a comment spanning multiple lines, each line must begin with !. Formatting standards include lowercase for code and uppercase for constants. Indentation improves readability. Line length should be limited to 132 characters. Case-insensitivity means Fortran doesn’t distinguish between uppercase and lowercase.

Code Example

program comments_demo
    ! This is a single-line comment
    ! It continues to the end of the line
    
    ! This program demonstrates comments and formatting
    ! Author: John Doe
    ! Date: 2024-01-01
    
    implicit none
    
    ! Constants often use uppercase
    real, parameter :: PI = 3.14159
    integer, parameter :: MAX_BIRDS = 100
    
    ! Variables use lowercase
    integer :: bird_count
    real :: bird_weight
    character(len=20) :: bird_name
    
    ! Multi-line comment example:
    ! This is a longer comment that explains
    ! what the following code section does.
    ! It can span multiple lines.
    
    bird_count = 10
    bird_weight = 25.5
    bird_name = "Sparrow"
    
    ! Print results with proper formatting
    print *, "Bird Count:", bird_count
    print *, "Bird Weight:", bird_weight
    print *, "Bird Name:", trim(bird_name)
    
    ! FORMATTED OUTPUT EXAMPLE
    !   print *, "Result:", result  ! Good: Clear and aligned
    
    ! CASE INSENSITIVITY
    ! birdCount and BIRDCOUNT are the same in Fortran
    ! But using consistent case improves readability
    
end program comments_demo

Comments are ignored by the compiler and exist only for human readers. ! marks comments. Consistent formatting improves code readability. Case-insensitivity means bird_count, BIRD_COUNT, and Bird_Count are the same variable.

Chapter 4: Input and Output

1. The READ and WRITE Statements

READ and WRITE are Fortran’s standard input and output statements.

WRITE(*,*) outputs data to the console. The first * means standard output, the second * means default formatting. READ(*,*) reads data from the console. Unit numbers can be used for files. Format specifiers control how data is read or written.

Code Example

program read_write
    implicit none
    
    integer :: bird_count
    real :: bird_weight
    character(len=20) :: bird_name
    logical :: is_migratory
    
    ! ---- WRITE STATEMENT ----
    print *, "--- WRITE Statement ---"
    write(*,*) "Welcome to Bird Sanctuary!"
    write(*,*) "This is a simple output"
    
    ! ---- WRITE WITH VARIABLES ----
    bird_count = 10
    bird_weight = 25.5
    bird_name = "Sparrow"
    
    write(*,*) "Bird:", bird_name
    write(*,*) "Count:", bird_count
    write(*,*) "Weight:", bird_weight
    write(*,*) "Weight (formatted):", bird_weight
    
    ! ---- READ STATEMENT ----
    print *, "--- READ Statement ---"
    write(*,*) "Enter bird name:"
    read(*,*) bird_name
    
    write(*,*) "Enter bird count:"
    read(*,*) bird_count
    
    write(*,*) "Enter bird weight:"
    read(*,*) bird_weight
    
    ! ---- DISPLAY INPUT ----
    write(*,*) "--- Bird Information ---"
    write(*,*) "Name:", trim(bird_name)
    write(*,*) "Count:", bird_count
    write(*,*) "Weight:", bird_weight
    
    ! ---- READING MULTIPLE VALUES ----
    write(*,*) "Enter name, count, weight (space separated):"
    read(*,*) bird_name, bird_count, bird_weight
    write(*,*) "Name:", trim(bird_name), "Count:", bird_count, "Weight:", bird_weight
    
end program read_write

WRITE(*,*) sends data to the console. READ(*,*) reads data from the console. Multiple values can be read or written in one statement. The * indicates default formatting.

2. Formatted versus Unformatted I/O

Formatted I/O uses format specifications to control data representation. Unformatted I/O reads and writes binary data directly.

Formatted I/O uses WRITE(unit, format) or READ(unit, format) with format specifiers. It produces human-readable output. Unformatted I/O uses WRITE(unit) or READ(unit) without format. It is faster and more compact but not human-readable. Format specifiers control spacing, decimal places, and alignment.

Code Example

program formatted_io
    implicit none
    
    integer :: count = 10
    real :: weight = 25.5
    real :: pi = 3.14159
    character(len=20) :: name = "Sparrow"
    
    ! ---- FORMATTED OUTPUT ----
    print *, "--- Formatted Output ---"
    
    ! Basic formatting
    write(*, "(A)") "Bird Information"
    write(*, "(A, I5)") "Count:", count
    write(*, "(A, F8.2)") "Weight:", weight
    write(*, "(A, F8.4)") "PI:", pi
    
    ! Multiple items in one format
    write(*, "(A, A, I5)") "Bird:", trim(name), count
    
    ! Table formatting
    write(*, *)
    write(*, "(A10, A10, A10)") "Name", "Count", "Weight"
    write(*, "(A10, I10, F10.2)") "Sparrow", 10, 25.5
    write(*, "(A10, I10, F10.2)") "Eagle", 3, 4500.0
    write(*, "(A10, I10, F10.2)") "Hawk", 5, 1200.5
    
    ! ---- FORMAT SPECIFIERS ----
    print *, "--- Format Specifiers ---"
    ! A - Character
    ! I - Integer
    ! F - Real (fixed point)
    ! E - Real (exponential)
    ! X - Space
    ! / - Newline
    
    write(*, "(A10, I10, F10.2, E15.5)") "Bird", 10, 25.5, 3.14159
    
    ! ---- FORMAT STATEMENT ----
    print *, "--- Format Statement ---"
    write(*, 100) "Bird", "Count", "Weight"
    100 format(A10, A10, A10)
    
    write(*, 200) "Sparrow", 10, 25.5
    200 format(A10, I10, F10.2)
    
    ! ---- UNFORMATTED I/O (BINARY) ----
    print *, "--- Unformatted I/O ---"
    ! Unformatted I/O is faster but not human-readable
    open(unit=10, file="bird_data.bin", form="unformatted", status="replace")
    write(10) name, count, weight
    close(10)
    
    ! Reading unformatted data
    open(unit=10, file="bird_data.bin", form="unformatted", status="old")
    read(10) name, count, weight
    close(10)
    
    write(*,*) "Unformatted data read:"
    write(*,*) "Name:", trim(name), "Count:", count, "Weight:", weight
    
end program formatted_io

Formatted I/O uses format specifiers to control output appearance. Format specifiers include A for character, I for integer, F for real (fixed point), E for exponential. Format statements provide reusable formats. Unformatted I/O writes binary data directly for speed.

3. Working with Text and Binary Files

File operations allow programs to read from and write to files for data persistence.

OPEN opens a file and associates it with a unit number. CLOSE closes a file. File access modes include STATUS (old/new/replace), ACTION (read/write/readwrite), and POSITION (rewind/append/asis). Text files are human-readable. Binary files generally offer faster processing and require less storage space than text-based files.

Code Example

program file_handling
    implicit none
    
    integer :: unit_num = 10
    character(len=50) :: filename = "birds.txt"
    character(len=50) :: bird_name
    integer :: bird_count
    real :: bird_weight
    integer :: i, ios
    
    ! ---- WRITING TO TEXT FILE ----
    print *, "--- Writing Text File ---"
    open(unit=unit_num, file=filename, status="replace", action="write")
    
    write(unit_num, "(A)") "Bird Sanctuary Data"
    write(unit_num, "(A)") "===================="
    write(unit_num, "(A10, A10, A10)") "Name", "Count", "Weight"
    
    ! Write multiple records
    do i = 1, 3
        select case(i)
        case(1)
            bird_name = "Sparrow"
            bird_count = 10
            bird_weight = 25.5
        case(2)
            bird_name = "Eagle"
            bird_count = 3
            bird_weight = 4500.0
        case(3)
            bird_name = "Hawk"
            bird_count = 5
            bird_weight = 1200.5
        end select
        
        write(unit_num, "(A10, I10, F10.2)") bird_name, bird_count, bird_weight
    end do
    
    close(unit_num)
    print *, "Data written to", trim(filename)
    
    ! ---- READING TEXT FILE ----
    print *, "--- Reading Text File ---"
    open(unit=unit_num, file=filename, status="old", action="read")
    
    ! Skip header lines
    read(unit_num, *) bird_name
    read(unit_num, *) bird_name
    read(unit_num, *) bird_name
    
    ! Read data
    print *, "Reading data from file:"
    do i = 1, 3
        read(unit_num, "(A10, I10, F10.2)") bird_name, bird_count, bird_weight
        print *, "Bird:", trim(bird_name), "Count:", bird_count, "Weight:", bird_weight
    end do
    
    close(unit_num)
    
    ! ---- BINARY FILE ----
    print *, "--- Binary File ---"
    filename = "birds.bin"
    open(unit=unit_num, file=filename, form="unformatted", status="replace")
    
    ! Write binary data
    bird_name = "Cardinal"
    bird_count = 8
    bird_weight = 45.2
    write(unit_num) bird_name, bird_count, bird_weight
    
    close(unit_num)
    
    ! Read binary data
    open(unit=unit_num, file=filename, form="unformatted", status="old")
    read(unit_num) bird_name, bird_count, bird_weight
    close(unit_num)
    
    print *, "Binary data read:"
    print *, "Bird:", trim(bird_name), "Count:", bird_count, "Weight:", bird_weight
    
    ! ---- ERROR HANDLING ----
    print *, "--- Error Handling ---"
    open(unit=unit_num, file="nonexistent.txt", status="old", action="read", iostat=ios)
    
    if (ios == 0) then
        print *, "File opened successfully"
        close(unit_num)
    else
        print *, "Error opening file:", ios
        print *, "File does not exist!"
    end if
    
end program file_handling

OPEN connects a file to a unit number. CLOSE disconnects it. Text files use formatted I/O. Binary files use unformatted I/O. IOSTAT detects I/O errors.

4. Error Handling and Data Validation

Error handling and data validation ensure programs handle unexpected input gracefully.

IOSTAT detects I/O errors. Data validation checks input is of the correct type and range. Error handling deals with invalid input gracefully. Best practices include checking for errors after I/O operations and providing meaningful error messages.

Code Example

program error_handling
    implicit none
    
    integer :: bird_count
    real :: bird_weight
    character(len=50) :: bird_name
    integer :: ios
    logical :: valid_input
    
    ! ---- INPUT VALIDATION ----
    print *, "--- Input Validation ---"
    
    ! Loop until valid input
    valid_input = .false.
    do while (.not. valid_input)
        write(*,*) "Enter bird count (integer, >= 0):"
        read(*,*,iostat=ios) bird_count
        
        if (ios == 0) then
            if (bird_count >= 0) then
                valid_input = .true.
                print *, "Valid input:", bird_count
            else
                print *, "Error: Count must be >= 0"
            end if
        else
            print *, "Error: Please enter a valid integer"
            ! Clear input buffer
            read(*,*) 
        end if
    end do
    
    ! ---- WEIGHT VALIDATION ----
    valid_input = .false.
    do while (.not. valid_input)
        write(*,*) "Enter bird weight (positive real):"
        read(*,*,iostat=ios) bird_weight
        
        if (ios == 0) then
            if (bird_weight > 0.0) then
                valid_input = .true.
                print *, "Valid input:", bird_weight
            else
                print *, "Error: Weight must be > 0"
            end if
        else
            print *, "Error: Please enter a valid number"
            read(*,*)
        end if
    end do
    
    ! ---- STRING VALIDATION ----
    print *, "Enter bird name (max 50 characters):"
    read(*, "(A)") bird_name
    bird_name = trim(bird_name)
    
    if (len_trim(bird_name) > 0) then
        print *, "Valid name:", trim(bird_name)
    else
        print *, "Error: Name cannot be empty"
    end if
    
    ! ---- FILE ERROR HANDLING ----
    print *, "--- File Error Handling ---"
    open(unit=10, file="test.txt", status="old", action="read", iostat=ios)
    
    if (ios == 0) then
        print *, "File opened successfully"
        close(10)
    else
        print *, "Error opening file. Creating new file."
        open(unit=10, file="test.txt", status="new", action="write", iostat=ios)
        if (ios == 0) then
            write(10,*) "New file created"
            close(10)
            print *, "New file created successfully"
        else
            print *, "Fatal error: Cannot create file"
        end if
    end if
    
    print *, "--- Complete ---"
    
end program error_handling

IOSTAT returns 0 for successful operations and non-zero for errors. Input validation checks data type and range. Loops continue until valid input is received. Error handling provides fallback behavior.

Chapter 5: Control Structures

1. IF, IF-ELSE, and Nested IF Statements

Conditional statements enable a program to evaluate conditions and choose different actions based on the results.

IF (condition) THEN executes a block when true. IF-ELSE executes one block when true, another when false. IF-ELSE IF checks multiple conditions in sequence. Logical IF is a single-line form without THEN. Nested IF allows conditions inside conditions.

Code Example

program if_demo
    implicit none
    
    integer :: bird_count
    real :: bird_weight
    
    bird_count = 15
    bird_weight = 4500.0
    
    ! ---- BASIC IF ----
    print *, "--- Basic IF ---"
    if (bird_count > 10) then
        print *, "We have many birds:", bird_count
    end if
    
    ! ---- IF-ELSE ----
    print *, "--- IF-ELSE ---"
    if (bird_weight > 1000.0) then
        print *, "Heavy bird:", bird_weight
    else
        print *, "Light bird:", bird_weight
    end if
    
    ! ---- IF-ELSE IF ----
    print *, "--- IF-ELSE IF Ladder ---"
    if (bird_count > 20) then
        print *, "Large flock"
    else if (bird_count > 10) then
        print *, "Medium flock"
    else if (bird_count > 5) then
        print *, "Small flock"
    else
        print *, "Just a few birds"
    end if
    
    ! ---- NESTED IF ----
    print *, "--- Nested IF ---"
    if (bird_count > 0) then
        if (bird_count > 10) then
            print *, "More than 10 birds"
        else
            print *, "Between 1 and 10 birds"
        end if
    else
        print *, "No birds"
    end if
    
    ! ---- LOGICAL IF (Single line) ----
    print *, "--- Logical IF ---"
    if (bird_count == 0) print *, "No birds"
    
    ! ---- COMPLEX CONDITIONS ----
    print *, "--- Complex Conditions ---"
    if ((bird_count > 10) .and. (bird_weight > 1000.0)) then
        print *, "Many heavy birds"
    end if
    
    if ((bird_count > 10) .or. (bird_weight > 1000.0)) then
        print *, "Either many or heavy birds"
    end if
    
    ! ---- NESTED WITH ELSE ----
    print *, "--- Nested with ELSE ---"
    if (bird_count > 0) then
        if (bird_count > 10) then
            print *, "More than 10"
        else
            print *, "Between 1 and 10"
        end if
    else
        print *, "Zero birds"
    end if
    
end program if_demo

The condition is evaluated. If true, the THEN block executes. In IF-ELSE IF, conditions are checked in order. Nested IF allows complex logic. Logical IF is a single statement.

2. The SELECT CASE Statement

SELECT CASE provides clean multi-way branching based on a single value.

SELECT CASE (expression) selects one of many branches. CASE labels specify values to match. CASE DEFAULT handles unmatched values. Allowed types include integers, characters, and logical values. SELECT CASE is cleaner than long IF-ELSE IF chains when checking one variable.

Code Example

program select_case_demo
    implicit none
    
    integer :: bird_type
    character(len=1) :: bird_class
    integer :: score
    
    bird_type = 2
    bird_class = 'A'
    score = 85
    
    ! ---- BASIC SELECT CASE ----
    print *, "--- Basic SELECT CASE ---"
    select case(bird_type)
    case(1)
        print *, "You selected: Sparrow"
    case(2)
        print *, "You selected: Eagle"
    case(3)
        print *, "You selected: Hawk"
    case(4)
        print *, "You selected: Cardinal"
    case default
        print *, "Invalid selection"
    end select
    
    ! ---- SELECT CASE WITH CHARACTER ----
    print *, "--- SELECT CASE with Character ---"
    select case(bird_class)
    case('A')
        print *, "Class A: Birds of prey"
    case('B')
        print *, "Class B: Songbirds"
    case('C')
        print *, "Class C: Water birds"
    case default
        print *, "Unknown class"
    end select
    
    ! ---- SELECT CASE WITH RANGES ----
    print *, "--- SELECT CASE with Ranges ---"
    select case(score)
    case(90:100)
        print *, "Excellent!"
    case(80:89)
        print *, "Very good!"
    case(70:79)
        print *, "Good"
    case(60:69)
        print *, "Satisfactory"
    case(0:59)
        print *, "Needs improvement"
    case default
        print *, "Invalid score"
    end select
    
    ! ---- SELECT CASE WITH MULTIPLE VALUES ----
    print *, "--- SELECT CASE with Multiple Values ---"
    bird_type = 2
    select case(bird_type)
    case(1, 2)
        print *, "Small bird"
    case(3, 4)
        print *, "Medium bird"
    case(5, 6, 7)
        print *, "Large bird"
    case default
        print *, "Unknown size"
    end select
    
    ! ---- SELECT CASE WITH LOGICAL ----
    print *, "--- SELECT CASE with Logical ---"
    logical :: is_migratory
    is_migratory = .true.
    
    select case(is_migratory)
    case(.true.)
        print *, "This bird migrates"
    case(.false.)
        print *, "This bird does not migrate"
    end select
    
    ! ---- SELECT CASE VS IF-ELSE IF ----
    print *, "--- SELECT CASE vs IF-ELSE IF ---"
    print *, "SELECT CASE is cleaner for single variable checks"
    print *, "IF-ELSE IF is better for complex conditions"
    
end program select_case_demo

SELECT CASE evaluates the expression once. It jumps to the matching CASE branch. CASE DEFAULT handles unmatched values. Ranges and multiple values are supported.

3. DO Loops and DO WHILE Loops

Loops allow a program to execute the same block of code repeatedly, usually until a specified condition is met.

DO loop counts from a start to an end value. A DO WHILE loop repeatedly executes a block of code as long as its specified condition remains true.Loop variable is an integer counter. EXIT exits the loop early. CYCLE skips to the next iteration.

Code Example

program loops_demo
    implicit none
    
    integer :: i
    integer :: total
    integer :: bird_count
    
    ! ---- BASIC DO LOOP ----
    print *, "--- Basic DO Loop ---"
    do i = 1, 5
        print *, "Bird", i, "chirping"
    end do
    print *, "All birds chirped!"
    
    ! ---- DO LOOP WITH STEP ----
    print *, "--- DO Loop with Step ---"
    do i = 1, 10, 2
        print *, "Odd bird:", i
    end do
    
    ! ---- DO LOOP COUNTING DOWN ----
    print *, "--- DO Loop Counting Down ---"
    do i = 5, 1, -1
        print *, "Countdown:", i
    end do
    print *, "Take off!"
    
    ! ---- NESTED DO LOOPS ----
    print *, "--- Nested DO Loops ---"
    do i = 1, 3
        do j = 1, 4
            write(*, "(A, I1, A, I1)", advance="no") "Tree", i, " Bird", j, " "
        end do
        print *
    end do
    
    ! ---- DO WHILE LOOP ----
    print *, "--- DO WHILE Loop ---"
    bird_count = 1
    do while (bird_count <= 5)
        print *, "Counting bird:", bird_count
        bird_count = bird_count + 1
    end do
    
    ! ---- DO LOOP WITH EXIT ----
    print *, "--- DO Loop with EXIT ---"
    do i = 1, 10
        if (i == 5) then
            print *, "Found bird at position", i
            exit
        end if
        print *, "Checking bird", i
    end do
    
    ! ---- DO LOOP WITH CYCLE ----
    print *, "--- DO Loop with CYCLE ---"
    total = 0
    do i = 1, 10
        if (mod(i, 2) == 0) then
            cycle    ! Skip even numbers
        end if
        total = total + i
        print *, "Adding odd number:", i
    end do
    print *, "Sum of odd numbers:", total
    
    ! ---- INFINITE LOOP PREVENTION ----
    print *, "--- Safe Loop ---"
    i = 1
    do while (i <= 3)
        print *, "Safe iteration:", i
        i = i + 1
    end do
    
end program loops_demo

DO loops use a counter variable. The loop executes from the start value to the end value. DO WHILE checks the condition before each iteration. EXIT exits the loop. CYCLE skips to the next iteration.

4. CYCLE and EXIT

EXIT exits a loop immediately. CYCLE skips to the next iteration.

EXIT terminates the current loop entirely. CYCLE skips the rest of the current iteration. In nested loops, EXIT only exits the innermost loop. Use EXIT when you’ve found what you’re looking for. Use CYCLE when you want to skip processing for certain conditions.

Code Example

program cycle_exit_demo
    implicit none
    
    integer :: i, j
    
    ! ---- EXIT EXAMPLE ----
    print *, "--- EXIT Example ---"
    print *, "Finding first eagle..."
    do i = 1, 10
        if (i == 4) then
            print *, "Eagle found at position", i
            exit    ! Exit loop immediately
        end if
        print *, "Checking bird", i
    end do
    print *, "Search complete!"
    
    ! ---- CYCLE EXAMPLE ----
    print *, "--- CYCLE Example ---"
    print *, "Counting only odd birds..."
    do i = 1, 10
        if (mod(i, 2) == 0) then
            cycle    ! Skip even numbers
        end if
        print *, "Bird", i, "counted"
    end do
    print *, "All odd birds counted!"
    
    ! ---- EXIT IN NESTED LOOPS ----
    print *, "--- EXIT in Nested Loops ---"
    outer: do i = 1, 3
        do j = 1, 4
            if (j == 3) then
                print *, "Found at row", i, "col", j
                exit   ! Exits only inner loop
            end if
            print *, "Checking row", i, "col", j
        end do
    end do outer
    
    ! ---- NAMED EXIT ----
    print *, "--- Named EXIT ---"
    outer: do i = 1, 3
        do j = 1, 4
            if (i == 2 .and. j == 3) then
                print *, "Found at row", i, "col", j
                exit outer  ! Exits outer loop
            end if
        end do
    end do outer
    
    ! ---- PRACTICAL EXAMPLE ----
    print *, "--- Practical Example ---"
    integer :: bird_count
    logical :: found
    
    bird_count = 0
    found = .false.
    
    do i = 1, 10
        bird_count = bird_count + 1
        if (bird_count == 5) then
            found = .true.
            print *, "Found bird at", bird_count
            exit
        end if
        if (mod(i, 2) == 0) cycle
        print *, "Checking odd position", i
    end do
    
    if (found) then
        print *, "Bird found!"
    else
        print *, "Bird not found"
    end if
    
end program cycle_exit_demo

EXIT immediately exits the current loop. In nested loops, it only exits the innermost loop. CYCLE skips the rest of the iteration and goes to the next. Named loops allow EXIT to exit outer loops.

5. Loop Optimization

Loop optimization improves performance by writing efficient loops.

Loop unrolling reduces loop overhead by executing multiple operations per iteration. Loop fusion is an optimization technique that merges two or more loops into a single loop when their operations can be safely performed together. Minimizing operations reduces work inside loops. Compiler flags help with optimization.

Code Example

program loop_optimization
    implicit none
    
    integer :: i
    integer :: size = 1000
    real, allocatable :: a(:), b(:), c(:), d(:)
    real :: start_time, end_time
    integer :: count_rate
    
    allocate(a(size), b(size), c(size), d(size))
    
    ! Initialize arrays
    do i = 1, size
        a(i) = real(i)
        b(i) = real(i) * 2.0
        c(i) = 0.0
        d(i) = 0.0
    end do
    
    ! ---- UNOPTIMIZED LOOP ----
    print *, "--- Unoptimized Loop ---"
    call cpu_time(start_time)
    do i = 1, size
        c(i) = a(i) + b(i)
    end do
    call cpu_time(end_time)
    print *, "Time (unoptimized):", end_time - start_time
    
    ! ---- OPTIMIZED LOOP (Loop Unrolling) ----
    print *, "--- Optimized Loop (Unrolling) ---"
    call cpu_time(start_time)
    do i = 1, size, 4
        c(i) = a(i) + b(i)
        c(i+1) = a(i+1) + b(i+1)
        c(i+2) = a(i+2) + b(i+2)
        c(i+3) = a(i+3) + b(i+3)
    end do
    call cpu_time(end_time)
    print *, "Time (unrolled):", end_time - start_time
    
    ! ---- ARRAY OPERATION ----
    print *, "--- Array Operation ---"
    call cpu_time(start_time)
    c = a + b
    call cpu_time(end_time)
    print *, "Time (array op):", end_time - start_time
    
    ! ---- LOOP FUSION ----
    print *, "--- Loop Fusion ---"
    ! Separate loops
    call cpu_time(start_time)
    do i = 1, size
        c(i) = a(i) + b(i)
    end do
    do i = 1, size
        d(i) = c(i) * 2.0
    end do
    call cpu_time(end_time)
    print *, "Time (separate loops):", end_time - start_time
    
    ! Fused loop
    call cpu_time(start_time)
    do i = 1, size
        c(i) = a(i) + b(i)
        d(i) = c(i) * 2.0
    end do
    call cpu_time(end_time)
    print *, "Time (fused loop):", end_time - start_time
    
    ! ---- MINIMIZING OPERATIONS ----
    print *, "--- Minimizing Operations ---"
    real :: factor = 2.0
    
    ! Unoptimized
    call cpu_time(start_time)
    do i = 1, size
        c(i) = a(i) * factor + b(i) * factor
    end do
    call cpu_time(end_time)
    print *, "Time (unoptimized):", end_time - start_time
    
    ! Optimized (factor pulled out)
    call cpu_time(start_time)
    do i = 1, size
        c(i) = (a(i) + b(i)) * factor
    end do
    call cpu_time(end_time)
    print *, "Time (optimized):", end_time - start_time
    
    ! ---- COMPILER OPTIMIZATION FLAGS ----
    print *, "--- Compiler Optimization Flags ---"
    print *, "Use -O2 or -O3 for optimization"
    print *, "Use -O0 for debugging"
    print *, "Use -ffast-math for faster floating point"
    print *, "Use -march=native for CPU-specific optimization"
    
    deallocate(a, b, c, d)
    
end program loop_optimization

Loop unrolling reduces loop overhead by combining iterations. Loop fusion combines multiple loops. Array operations are often fastest. Compiler optimization flags improve performance. Minimizing operations inside loops reduces work.

Chapter 6: Functions and Subroutines

1. The Difference Between Functions and Subroutines

Functions return a single value and are used in expressions. Subroutines perform operations and return multiple results.

Functions have a return type and return a single value. They are called in expressions. Subroutines perform a specific task without directly returning a value to the calling code. They modify arguments. Functions use function keyword; subroutines use subroutine. Functions return via the function name or RESULT. Subroutines return via arguments with INTENT(OUT) or INTENT(INOUT).

Code Example

program functions_subroutines_demo
    implicit none
    
    integer :: x, y, result, sum
    
    ! ---- FUNCTION EXAMPLE ----
    print *, "--- Function Example ---"
    x = 10
    y = 5
    result = add_numbers(x, y)
    print *, "Function result:", result
    
    ! Function in expression
    print *, "Function in expression:", add_numbers(20, 30) * 2
    
    ! ---- SUBROUTINE EXAMPLE ----
    print *, "--- Subroutine Example ---"
    x = 10
    y = 5
    call add_numbers_sub(x, y, sum)
    print *, "Subroutine result:", sum
    
    ! ---- FUNCTION CAN BE CALLED MULTIPLE TIMES ----
    print *, "--- Multiple Function Calls ---"
    do i = 1, 5
        print *, "Factorial of", i, "=", factorial(i)
    end do
    
contains

    ! ---- FUNCTION DEFINITION ----
    function add_numbers(a, b) result(result_val)
        integer, intent(in) :: a, b
        integer :: result_val
        result_val = a + b
    end function add_numbers
    
    ! ---- SUBROUTINE DEFINITION ----
    subroutine add_numbers_sub(a, b, result_val)
        integer, intent(in) :: a, b
        integer, intent(out) :: result_val
        result_val = a + b
    end subroutine add_numbers_sub
    
    ! ---- RECURSIVE FUNCTION ----
    recursive function factorial(n) result(result_val)
        integer, intent(in) :: n
        integer :: result_val
        
        if (n <= 1) then
            result_val = 1
        else
            result_val = n * factorial(n - 1)
        end if
    end function factorial

end program functions_subroutines_demo

Functions produce a return value and can be used as part of expressions wherever that value is needed. Subroutines are called with CALL. Functions use RESULT for return value. Subroutines use INTENT for arguments. Recursive functions call themselves.

2. Arguments and INTENT Declarations

INTENT specifies how arguments are used in procedures.

INTENT(IN) means the argument is read-only. INTENT(OUT) means the argument is written to. INTENT(INOUT) means the argument is both read and written. Using INTENT helps catch errors and improves code clarity.

Code Example

program intent_demo
    implicit none
    
    integer :: x, y, z
    
    x = 10
    y = 20
    z = 30
    
    print *, "Before: x =", x, "y =", y, "z =", z
    
    ! ---- INTENT(IN) ----
    print *, "--- INTENT(IN) ---"
    call process_in(x)
    print *, "After INTENT(IN): x =", x
    
    ! ---- INTENT(OUT) ----
    print *, "--- INTENT(OUT) ---"
    call process_out(y)
    print *, "After INTENT(OUT): y =", y
    
    ! ---- INTENT(INOUT) ----
    print *, "--- INTENT(INOUT) ---"
    call process_inout(z)
    print *, "After INTENT(INOUT): z =", z
    
    ! ---- MULTIPLE ARGUMENTS ----
    print *, "--- Multiple Arguments ---"
    integer :: a = 5, b = 10
    print *, "Before: a =", a, "b =", b
    call swap_values(a, b)
    print *, "After swap: a =", a, "b =", b
    
contains

    ! INTENT(IN) - Read only
    subroutine process_in(a)
        integer, intent(in) :: a
        print *, "INTENT(IN): Received", a
        ! a = a + 1  ! ERROR: Cannot modify INTENT(IN)
    end subroutine process_in
    
    ! INTENT(OUT) - Write only
    subroutine process_out(a)
        integer, intent(out) :: a
        a = 100  ! Set new value
        print *, "INTENT(OUT): Set to", a
    end subroutine process_out
    
    ! INTENT(INOUT) - Read and write
    subroutine process_inout(a)
        integer, intent(inout) :: a
        print *, "INTENT(INOUT): Original", a
        a = a * 2
        print *, "INTENT(INOUT): Doubled to", a
    end subroutine process_inout
    
    ! Multiple arguments with different INTENT
    subroutine swap_values(a, b)
        integer, intent(inout) :: a, b
        integer :: temp
        temp = a
        a = b
        b = temp
    end subroutine swap_values

end program intent_demo

INTENT(IN) prevents modification. INTENT(OUT) requires assignment. INTENT(INOUT) allows both. INTENT helps catch errors at compile time.

3. Recursion

Recursion occurs when a function or subroutine calls itself.

Recursive functions call themselves with modified arguments. Base case stops the recursion. Recursive case calls itself. Without a base case, recursion continues indefinitely. Stack overflow occurs when recursion goes too deep.

Code Example

program recursion_demo
    implicit none
    
    ! ---- RECURSIVE FACTORIAL ----
    print *, "--- Recursive Factorial ---"
    do i = 0, 5
        print *, "Factorial(", i, ") =", factorial(i)
    end do
    
    ! ---- RECURSIVE FIBONACCI ----
    print *, "--- Recursive Fibonacci ---"
    do i = 0, 8
        print *, "Fibonacci(", i, ") =", fibonacci(i)
    end do
    
    ! ---- RECURSIVE SUM ----
    print *, "--- Recursive Sum ---"
    print *, "Sum(1..5) =", sum_range(5)
    
    ! ---- RECURSIVE COUNTDOWN ----
    print *, "--- Recursive Countdown ---"
    call countdown(5)
    
contains

    ! ---- RECURSIVE FACTORIAL ----
    recursive function factorial(n) result(result_val)
        integer, intent(in) :: n
        integer :: result_val
        
        if (n <= 1) then
            result_val = 1  ! Base case
        else
            result_val = n * factorial(n - 1)  ! Recursive case
        end if
    end function factorial
    
    ! ---- RECURSIVE FIBONACCI ----
    recursive function fibonacci(n) result(result_val)
        integer, intent(in) :: n
        integer :: result_val
        
        if (n <= 1) then
            result_val = n  ! Base cases
        else
            result_val = fibonacci(n - 1) + fibonacci(n - 2)
        end if
    end function fibonacci
    
    ! ---- RECURSIVE SUM ----
    recursive function sum_range(n) result(result_val)
        integer, intent(in) :: n
        integer :: result_val
        
        if (n <= 0) then
            result_val = 0
        else
            result_val = n + sum_range(n - 1)
        end if
    end function sum_range
    
    ! ---- RECURSIVE PROCEDURE ----
    recursive subroutine countdown(n)
        integer, intent(in) :: n
        
        if (n > 0) then
            print *, "Count:", n
            call countdown(n - 1)
        end if
    end subroutine countdown

end program recursion_demo

The function calls itself with a smaller value. The base case stops the recursion. Without a base case, the recursion continues indefinitely.

4. Scope Management

Scope determines where a variable is accessible.

Local variables are declared inside a procedure and only exist there. Module variables are accessible within the module. Common blocks are global but discouraged. Scope rules prevent naming conflicts.

Code Example

module scope_module
    implicit none
    
    ! ---- MODULE VARIABLES (Global within module) ----
    integer :: global_count = 0
    character(len=20) :: module_name = "BirdModule"
    
contains
    
    subroutine increment_count()
        ! Local variable
        integer :: local_count = 0
        
        global_count = global_count + 1
        local_count = local_count + 1
        
        print *, "Global:", global_count, "Local:", local_count
    end subroutine increment_count
    
    subroutine display_module_info()
        print *, "Module:", module_name
        print *, "Global count:", global_count
    end subroutine display_module_info
    
end module scope_module

program scope_demo
    use scope_module
    implicit none
    
    ! ---- LOCAL VARIABLES IN MAIN ----
    integer :: main_count = 100
    character(len=20) :: local_name = "MainBird"
    
    print *, "--- Module Variables ---"
    print *, "Global count:", global_count
    call increment_count()
    call increment_count()
    
    print *, "--- Module Variables After Updates ---"
    call display_module_info()
    
    print *, "--- Local Variables ---"
    print *, "Main count:", main_count
    print *, "Local name:", trim(local_name)
    
    ! ---- LOCAL VARIABLE IN BLOCK ----
    print *, "--- Block Scope ---"
    block
        integer :: block_var = 50
        print *, "Block variable:", block_var
        
        block
            integer :: nested_block = 20
            print *, "Nested block:", nested_block
        end block
        ! nested_block not accessible here
    end block
    ! block_var not accessible here
    
    ! ---- COMMON BLOCKS (Discouraged) ----
    ! Use modules instead of common blocks
    print *, "--- Common Blocks (Discouraged) ---"
    print *, "Use modules instead of common blocks for better encapsulation"
    
end program scope_demo

Local variables are only accessible in their declaring procedure. Module variables are accessible in the module and its uses. Common blocks are global but discouraged. Blocks create nested scope.

Chapter 7: Arrays and Strings

1. Array Declaration and Initialization

Arrays store multiple elements of the same data type in contiguous memory.

Arrays are declared with dimension or DIMENSION attribute. Indexing starts at 1 by default in Fortran. Initialization can be explicit or using array constructors. Multi-dimensional arrays are supported.

Code Example

program arrays_demo
    implicit none
    
    ! ---- 1D ARRAY DECLARATION ----
    integer, dimension(5) :: bird_count
    integer :: bird_weight(5)    ! Alternative syntax
    real :: bird_values(10)
    character(len=20) :: bird_names(3)
    
    ! ---- ARRAY INITIALIZATION ----
    bird_count = [10, 15, 12, 8, 20]
    bird_weight = [25, 4500, 1200, 45, 15]
    
    ! ---- ARRAY CONSTRUCTORS ----
    real :: a(5) = [1.0, 2.0, 3.0, 4.0, 5.0]
    real :: b(5) = [(real(i), i = 1, 5)]
    
    ! ---- CUSTOM INDEX RANGE ----
    integer :: custom_range(0:4)
    integer :: custom_range2(-2:2)
    
    print *, "--- Arrays ---"
    
    ! ---- ACCESSING ELEMENTS ----
    print *, "Bird counts:"
    do i = 1, 5
        print *, "Bird", i, ":", bird_count(i)
    end do
    
    print *, "Bird weights:"
    do i = 1, 5
        print *, "Weight", i, ":", bird_weight(i)
    end do
    
    ! ---- CUSTOM INDEX RANGE ----
    custom_range(0) = 10
    custom_range(1) = 20
    custom_range(2) = 30
    custom_range(3) = 40
    custom_range(4) = 50
    
    print *, "Custom range (0-4):"
    do i = 0, 4
        print *, "Index", i, ":", custom_range(i)
    end do
    
    ! ---- ARRAY SECTION ----
    print *, "Array sections:"
    print *, "First 3 counts:", bird_count(1:3)
    print *, "Last 2 counts:", bird_count(4:5)
    print *, "Every other:", bird_count(1:5:2)
    
    ! ---- STRING ARRAYS ----
    bird_names = ["Sparrow", "Eagle", "Hawk"]
    print *, "Bird names:", bird_names
    
    print *, "--- Array Properties ---"
    print *, "Size:", size(bird_count)
    print *, "Shape:", shape(bird_count)
    print *, "LBound:", lbound(bird_count)
    print *, "UBound:", ubound(bird_count)
    
end program arrays_demo

Arrays store elements in contiguous memory. Indexing starts at 1 by default. Array constructors create arrays. Custom index ranges allow flexible indexing.

2. Array Operations and Slicing

Fortran provides powerful array operations and slicing for efficient code.

Array arithmetic performs operations on entire arrays. Array slicing extracts subarrays. Array sections use start:stop:step. Element-wise operations work on arrays of the same shape.

Code Example

program array_ops
    implicit none
    
    integer :: i
    real, dimension(5) :: a, b, c
    
    ! Initialize arrays
    a = [1.0, 2.0, 3.0, 4.0, 5.0]
    b = [10.0, 20.0, 30.0, 40.0, 50.0]
    
    ! ---- ARITHMETIC ON ENTIRE ARRAYS ----
    print *, "--- Array Arithmetic ---"
    c = a + b
    print *, "a + b =", c
    
    c = a * b
    print *, "a * b =", c
    
    c = a / 2.0
    print *, "a / 2 =", c
    
    c = a ** 2
    print *, "a^2 =", c
    
    ! ---- COMPLEX ARRAY EXPRESSIONS ----
    print *, "--- Complex Array Expressions ---"
    c = (a + b) * 2.0
    print *, "(a + b) * 2 =", c
    
    c = sin(a) + cos(b)
    print *, "sin(a) + cos(b) =", c
    
    ! ---- ARRAY SLICING ----
    print *, "--- Array Slicing ---"
    print *, "a(1:3) =", a(1:3)
    print *, "a(2:4) =", a(2:4)
    print *, "a(1:5:2) =", a(1:5:2)    ! Odd indices
    print *, "a(5:1:-1) =", a(5:1:-1)  ! Reverse
    
    ! ---- ASSIGNMENT WITH SLICING ----
    print *, "--- Slicing Assignment ---"
    c = 0.0
    c(2:4) = a(2:4)
    print *, "c with slice assigned:", c
    
    c(1:5:2) = 99.0
    print *, "c with odd positions set:", c
    
    ! ---- ARRAY REDUCTION ----
    print *, "--- Array Reduction ---"
    print *, "Sum of a:", sum(a)
    print *, "Product of a:", product(a)
    print *, "Max of a:", maxval(a)
    print *, "Min of a:", minval(a)
    print *, "Average of a:", sum(a) / size(a)
    
    ! ---- ARRAY WHERE ----
    print *, "--- WHERE Statement ---"
    c = a
    where (c > 3.0)
        c = 0.0
    end where
    print *, "c with values > 3 set to 0:", c
    
    ! ---- MULTIPLE ARRAY OPERATIONS ----
    print *, "--- Multiple Operations ---"
    real :: d(5) = [1.0, 2.0, 3.0, 4.0, 5.0]
    real :: e(5) = [5.0, 4.0, 3.0, 2.0, 1.0]
    
    c = d + e
    print *, "d + e =", c
    
    c = (d - e) * (d + e)
    print *, "(d - e) * (d + e) =", c
    
end program array_ops

Array operations work element-wise. Slicing extracts subarrays. start:stop:step defines sections. Reduction functions summarize arrays. WHERE applies operations conditionally.

3. Multi-dimensional Arrays

Multi-dimensional arrays store data in grids, cubes, or higher dimensions.

2D arrays use dimension(rows, cols). 3D arrays add another dimension. Column-major order means the first index changes fastest. Matrix operations are efficient in Fortran.

Code Example

program multidimensional
    implicit none
    
    integer :: i, j, k
    
    ! ---- 2D ARRAY ----
    integer, dimension(3, 4) :: sightings
    integer :: matrix(3, 3)
    
    ! ---- 2D ARRAY INITIALIZATION ----
    sightings = reshape([10, 15, 12, 8, 5, 3, 7, 12, 20, 18, 15, 10], [3, 4])
    
    print *, "--- 2D Array ---"
    print *, "Shape:", shape(sightings)
    
    do i = 1, 3
        print *, "Row", i, ":", sightings(i, 1:4)
    end do
    
    ! ---- ACCESSING ELEMENTS ----
    print *, "Element (2,3):", sightings(2, 3)
    print *, "Element (3,2):", sightings(3, 2)
    
    ! ---- MATRIX OPERATIONS ----
    print *, "--- Matrix Operations ---"
    matrix = reshape([1, 2, 3, 4, 5, 6, 7, 8, 9], [3, 3])
    
    print *, "Matrix:"
    do i = 1, 3
        print *, matrix(i, 1:3)
    end do
    
    print *, "Matrix * 2:"
    do i = 1, 3
        print *, matrix(i, 1:3) * 2
    end do
    
    ! ---- SLICING MULTI-DIMENSIONAL ARRAYS ----
    print *, "--- Multi-dimensional Slicing ---"
    print *, "Row 1:", sightings(1, :)
    print *, "Column 2:", sightings(:, 2)
    print *, "Submatrix (1-2, 1-3):"
    do i = 1, 2
        print *, sightings(i, 1:3)
    end do
    
    ! ---- 3D ARRAY ----
    integer, dimension(2, 3, 4) :: cube
    
    cube = reshape([(i, i = 1, 24)], [2, 3, 4])
    
    print *, "--- 3D Array ---"
    print *, "Shape:", shape(cube)
    print *, "Cube(1,2,3):", cube(1, 2, 3)
    
    ! ---- COLUMN-MAJOR ORDER ----
    print *, "--- Column-Major Order ---"
    print *, "Elements are stored column-first"
    print *, "This affects performance for certain operations"
    
    ! ---- PERFORMANCE CONSIDERATIONS ----
    print *, "--- Performance Considerations ---"
    print *, "Accessing contiguous memory is faster"
    print *, "Column-major means varying first index fastest"
    
end program multidimensional

Multi-dimensional arrays use dimension(rows, cols, depth). Column-major order stores the first index fastest. Slicing works on any dimension.

4. Allocatable and Dynamic Arrays

Allocatable arrays can be allocated and deallocated at runtime.

Allocatable arrays are declared with allocatable attribute. ALLOCATE allocates memory. DEALLOCATE frees memory. Dynamic sizing handles unknown array sizes at runtime.

Code Example

program allocatable_demo
    implicit none
    
    integer :: size
    real, allocatable :: bird_data(:)
    real, allocatable :: matrix(:, :)
    character(len=20), allocatable :: bird_names(:)
    integer :: i, j
    
    ! ---- ALLOCATING 1D ARRAY ----
    print *, "--- Allocating 1D Array ---"
    print *, "Enter array size:"
    read(*,*) size
    
    allocate(bird_data(size))
    
    ! Initialize and display
    do i = 1, size
        bird_data(i) = real(i) * 10.0
    end do
    
    print *, "Bird data:", bird_data
    
    ! ---- ALLOCATING 2D ARRAY ----
    print *, "--- Allocating 2D Array ---"
    integer :: rows, cols
    rows = 3
    cols = 4
    
    allocate(matrix(rows, cols))
    
    do i = 1, rows
        do j = 1, cols
            matrix(i, j) = real(i * j)
        end do
    end do
    
    print *, "Matrix:"
    do i = 1, rows
        print *, matrix(i, :)
    end do
    
    ! ---- ALLOCATING CHARACTER ARRAY ----
    print *, "--- Allocating Character Array ---"
    integer :: num_birds = 3
    allocate(bird_names(num_birds))
    
    bird_names = ["Sparrow", "Eagle", "Hawk"]
    print *, "Bird names:", bird_names
    
    ! ---- DEALLOCATING ----
    print *, "--- Deallocating ---"
    deallocate(bird_data)
    deallocate(matrix)
    deallocate(bird_names)
    
    ! ---- REALLOCATING ----
    print *, "--- Reallocating ---"
    allocate(bird_data(10))
    bird_data = [(real(i), i = 1, 10)]
    print *, "Reallocated data:", bird_data
    
    deallocate(bird_data)
    
    ! ---- ALLOCATABLE DUMMY ARGUMENTS ----
    print *, "--- Allocatable Dummy Arguments ---"
    call process_array(5)
    call process_array(8)
    
    ! ---- MULTIPLE ALLOCATES ----
    print *, "--- Multiple Allocates ---"
    real, allocatable :: a(:), b(:)
    
    allocate(a(5), b(5))
    a = [1.0, 2.0, 3.0, 4.0, 5.0]
    b = a * 2.0
    
    print *, "a:", a
    print *, "b:", b
    
    deallocate(a, b)
    
contains

    ! ---- SUBROUTINE WITH ALLOCATABLE ARGUMENT ----
    subroutine process_array(n)
        integer, intent(in) :: n
        real, allocatable :: data(:)
        
        allocate(data(n))
        data = [(real(i), i = 1, n)]
        print *, "Processed array size", n, ":", data
        deallocate(data)
    end subroutine process_array

end program allocatable_demo

ALLOCATE allocates memory at runtime. DEALLOCATE frees it. Arrays can be reallocated. Allocatable arrays are essential for dynamic data.

5. Strings and String Manipulation

Strings handle text data in Fortran.

character strings can be fixed-length or allocatable. String functions include len, trim, adjustl, index, and scan. Concatenation uses //. Comparison works with relational operators.

Code Example

program strings_demo
    implicit none
    
    ! ---- FIXED-LENGTH STRINGS ----
    character(len=20) :: bird_name
    character(len=10) :: bird_type
    character(len=50) :: description
    
    ! ---- ALLOCATABLE STRINGS ----
    character(len=:), allocatable :: dynamic_name
    character(len=:), allocatable :: full_name
    
    ! ---- STRING ASSIGNMENT ----
    bird_name = "Sparrow"
    bird_type = "Songbird"
    
    ! ---- STRING CONCATENATION ----
    description = trim(bird_name) // " is a " // trim(bird_type)
    print *, "Description:", trim(description)
    
    ! ---- ALLOCATABLE STRINGS ----
    dynamic_name = "Eagle"
    full_name = dynamic_name // " (Bird of Prey)"
    print *, "Dynamic name:", trim(full_name)
    
    ! ---- STRING LENGTH ----
    print *, "--- String Length ---"
    print *, "bird_name length:", len(trim(bird_name))
    print *, "len(bird_name):", len(bird_name)
    print *, "len_trim(bird_name):", len_trim(bird_name)
    print *, "trimmed length:", len(trim(bird_name))
    
    ! ---- STRING TRIMMING ----
    character(len=20) :: padded = "  Sparrow  "
    print *, "--- String Trimming ---"
    print *, "Padded:", padded
    print *, "Trimmed:", trim(padded)
    print *, "Left adjusted:", adjustl(padded)
    print *, "Right adjusted:", adjustr(padded)
    
    ! ---- STRING SEARCH ----
    print *, "--- String Search ---"
    character(len=50) :: text = "The eagle soared high"
    integer :: pos
    
    pos = index(text, "eagle")
    print *, "Position of 'eagle':", pos
    
    pos = scan(text, "aeiou")
    print *, "Position of first vowel:", pos
    
    ! ---- STRING COMPARISON ----
    print *, "--- String Comparison ---"
    character(len=10) :: str1 = "Sparrow"
    character(len=10) :: str2 = "Eagle"
    
    print *, "str1 == str2:", str1 == str2
    print *, "str1 /= str2:", str1 /= str2
    print *, "str1 > str2:", str1 > str2  ! Lexicographic
    print *, "str1 < str2:", str1 < str2
    
    ! ---- STRING REPEAT ----
    print *, "--- String Repeat ---"
    character(len=20) :: repeated
    repeated = repeat("Bird ", 3)
    print *, "Repeated:", trim(repeated)
    
    ! ---- STRING VERIFICATION ----
    print *, "--- String Verification ---"
    character(len=20) :: verify_str = "Bird123"
    print *, "Verify:", verify(verify_str, "abcdefghijklmnopqrstuvwxyz")
    
    ! ---- STRING CASE CONVERSION ----
    print *, "--- String Case Conversion ---"
    character(len=20) :: mixed = "Sparrow"
    ! Fortran doesn't have built-in uppercase/lowercase
    ! Use external functions or modules for case conversion
    
    ! ---- PRACTICAL STRING OPERATIONS ----
    print *, "--- Practical String Operations ---"
    character(len=100) :: input_line = "Sparrow,10,25.5"
    integer :: comma1, comma2
    
    comma1 = index(input_line, ",")
    comma2 = index(input_line(comma1+1:), ",") + comma1
    
    if (comma1 > 0 .and. comma2 > comma1) then
        character(len=50) :: name
        character(len=10) :: count_str
        character(len=10) :: weight_str
        
        name = input_line(1:comma1-1)
        count_str = input_line(comma1+1:comma2-1)
        weight_str = input_line(comma2+1:)
        
        print *, "Parsed CSV:"
        print *, "Name:", trim(name)
        print *, "Count:", trim(count_str)
        print *, "Weight:", trim(weight_str)
    end if
    
end program strings_demo

Strings are declared with character(len=length). trim removes trailing spaces. // concatenates strings. index finds substrings. scan finds characters. Strings can be compared lexicographically. Allocatable strings have dynamic length.

Chapter 8: Derived Data Types and Pointers

1. Derived Types and Structures

Derived types allow grouping related data into custom structures.

Derived types are defined with type ... end type. Components are the member variables. Access uses the % operator. Derived types can contain any intrinsic type or other derived types.

Code Example

program derived_types
    implicit none
    
    ! ---- DERIVED TYPE DEFINITION ----
    type :: Bird
        character(len=20) :: species
        integer :: count
        real :: weight
        logical :: is_migratory
    end type Bird
    
    ! ---- TYPE WITH ARRAY ----
    type :: BirdSighting
        type(Bird) :: bird_info
        character(len=30) :: location
        character(len=10) :: date
        integer :: sightings(12)
    end type BirdSighting
    
    ! ---- NESTED DERIVED TYPE ----
    type :: BirdFlock
        character(len=30) :: name
        type(Bird) :: birds(10)
        integer :: size
    end type BirdFlock
    
    ! ---- DECLARE VARIABLES ----
    type(Bird) :: sparrow, eagle
    type(BirdSighting) :: sighting
    type(BirdFlock) :: forest_flock
    
    ! ---- ASSIGNING COMPONENTS ----
    sparrow%species = "Sparrow"
    sparrow%count = 10
    sparrow%weight = 25.5
    sparrow%is_migratory = .true.
    
    eagle%species = "Eagle"
    eagle%count = 3
    eagle%weight = 4500.0
    eagle%is_migratory = .false.
    
    ! ---- ACCESSING COMPONENTS ----
    print *, "--- Bird Types ---"
    print *, "Sparrow:"
    print *, "  Species:", sparrow%species
    print *, "  Count:", sparrow%count
    print *, "  Weight:", sparrow%weight
    print *, "  Migratory:", sparrow%is_migratory
    
    print *, "Eagle:"
    print *, "  Species:", eagle%species
    print *, "  Count:", eagle%count
    print *, "  Weight:", eagle%weight
    
    ! ---- NESTED TYPE ----
    sighting%bird_info = sparrow
    sighting%location = "Central Park"
    sighting%date = "2024-01-15"
    
    print *, "--- Sighting ---"
    print *, "Bird:", sighting%bird_info%species
    print *, "Location:", sighting%location
    print *, "Date:", sighting%date
    
    ! ---- ARRAY OF DERIVED TYPE ----
    type(Bird) :: bird_list(3)
    integer :: i
    
    bird_list(1) = sparrow
    bird_list(2) = eagle
    
    bird_list(3)%species = "Hawk"
    bird_list(3)%count = 5
    bird_list(3)%weight = 1200.5
    bird_list(3)%is_migratory = .true.
    
    print *, "--- Bird List ---"
    do i = 1, 3
        print *, i, ":", bird_list(i)%species, bird_list(i)%count
    end do
    
    ! ---- FLOCK ----
    forest_flock%name = "Forest Birds"
    forest_flock%size = 2
    forest_flock%birds(1) = sparrow
    forest_flock%birds(2) = eagle
    
    print *, "--- Flock ---"
    print *, "Flock:", forest_flock%name
    print *, "Size:", forest_flock%size
    
    do i = 1, forest_flock%size
        print *, "  Bird", i, ":", forest_flock%birds(i)%species
    end do
    
    ! ---- DEFAULT INITIALIZATION ----
    type :: BirdType
        character(len=20) :: species = "Unknown"
        integer :: count = 0
        real :: weight = 0.0
    end type BirdType
    
    type(BirdType) :: default_bird
    print *, "--- Default Initialization ---"
    print *, "Species:", default_bird%species
    print *, "Count:", default_bird%count
    
end program derived_types

Derived types group related data. Components are accessed with %. Types can be nested. Arrays of derived types are supported. Default initialization is possible.

2. Components

Components are the individual elements (members) of a derived type.

Components can be any intrinsic type or derived type. Access uses % notation. Components can be initialized with default values. Arrays can be components. Nested components are accessed with multiple %.

Code Example

program components_demo
    implicit none
    
    ! ---- TYPE WITH VARIOUS COMPONENTS ----
    type :: Bird
        character(len=20) :: species
        integer :: count = 0
        real :: weight = 0.0
        logical :: is_migratory = .false.
    end type Bird
    
    ! ---- TYPE WITH DERIVED COMPONENTS ----
    type :: Sighting
        type(Bird) :: bird
        character(len=30) :: location
        character(len=10) :: date
        real :: temperature
    end type Sighting
    
    ! ---- TYPE WITH ARRAY COMPONENTS ----
    type :: BirdData
        character(len=20) :: name
        integer :: data(10)
        real :: values(5, 5)
        type(Bird) :: bird_array(3)
    end type BirdData
    
    ! ---- TYPE WITH COMPLEX COMPONENTS ----
    type :: BirdStats
        character(len=20) :: species
        integer :: total_count
        real :: avg_weight
        real, allocatable :: weights(:)
        logical, allocatable :: is_migratory(:)
    end type BirdStats
    
    type(Bird) :: sparrow
    type(Sighting) :: sighting
    type(BirdData) :: data
    type(BirdStats) :: stats
    
    ! ---- COMPONENT ACCESS ----
    sparrow%species = "Sparrow"
    sparrow%count = 10
    sparrow%weight = 25.5
    sparrow%is_migratory = .true.
    
    print *, "--- Bird Components ---"
    print *, "Species:", sparrow%species
    print *, "Count:", sparrow%count
    print *, "Weight:", sparrow%weight
    
    ! ---- NESTED COMPONENT ACCESS ----
    sighting%bird = sparrow
    sighting%location = "Central Park"
    sighting%date = "2024-01-15"
    sighting%temperature = 22.5
    
    print *, "--- Nested Components ---"
    print *, "Bird:", sighting%bird%species
    print *, "Location:", sighting%location
    print *, "Date:", sighting%date
    print *, "Temperature:", sighting%temperature
    
    ! ---- ARRAY COMPONENTS ----
    data%name = "Sightings"
    data%data = [(i * 10, i = 1, 10)]
    
    print *, "--- Array Components ---"
    print *, "Name:", data%name
    print *, "Data:", data%data(1:5)
    
    ! ---- DERIVED TYPE ARRAY COMPONENTS ----
    data%bird_array(1) = sparrow
    data%bird_array(2)%species = "Eagle"
    data%bird_array(2)%count = 3
    
    print *, "Bird array:"
    print *, "  Bird 1:", data%bird_array(1)%species
    print *, "  Bird 2:", data%bird_array(2)%species
    
    ! ---- ALLOCATABLE COMPONENTS ----
    allocate(stats%weights(5))
    allocate(stats%is_migratory(5))
    
    stats%species = "Sparrow"
    stats%total_count = 10
    stats%avg_weight = 25.5
    stats%weights = [25.5, 26.0, 24.5, 25.8, 26.2]
    stats%is_migratory = [.true., .true., .false., .true., .true.]
    
    print *, "--- Allocatable Components ---"
    print *, "Species:", stats%species
    print *, "Weights:", stats%weights
    print *, "Migratory:", stats%is_migratory
    
    ! ---- COMPONENT INITIALIZATION ----
    type :: DefaultType
        integer :: a = 10
        real :: b = 20.5
        character(len=10) :: c = "default"
    end type DefaultType
    
    type(DefaultType) :: default_obj
    
    print *, "--- Default Component Values ---"
    print *, "a:", default_obj%a
    print *, "b:", default_obj%b
    print *, "c:", default_obj%c
    
    ! ---- CLEANUP ----
    deallocate(stats%weights)
    deallocate(stats%is_migratory)
    
end program components_demo

Components are the members of derived types. They can be intrinsic types, derived types, or arrays. Access uses % notation. Components can have default values. Allocatable components provide flexibility.

3. Pointers

Pointers in Fortran are safer than in C but still provide dynamic data handling.

Pointers are declared with pointer attribute. => associates a pointer with a target. null() sets a pointer to null. Allocatable is often preferred. Safety features include no arbitrary pointer arithmetic.

Code Example

program pointers_demo
    implicit none
    
    integer, pointer :: int_ptr(:)
    real, pointer :: real_ptr
    type :: Bird
        character(len=20) :: species
        integer :: count
    end type Bird
    type(Bird), pointer :: bird_ptr
    
    ! ---- DECLARE TARGETS ----
    integer, target :: array(10)
    integer, target :: value = 42
    type(Bird), target :: my_bird
    
    ! ---- ASSOCIATE POINTER WITH TARGET ----
    int_ptr => array
    real_ptr => null()  ! Null pointer
    
    ! ---- USING POINTERS ----
    array = [(i, i = 1, 10)]
    int_ptr(1:5) = 0
    
    print *, "--- Pointers ---"
    print *, "int_ptr:", int_ptr
    print *, "array:", array
    
    ! ---- POINTER TO DERIVED TYPE ----
    my_bird%species = "Sparrow"
    my_bird%count = 10
    
    bird_ptr => my_bird
    print *, "Bird pointer:", bird_ptr%species, bird_ptr%count
    
    bird_ptr%count = 15
    print *, "Modified bird:", my_bird%species, my_bird%count
    
    ! ---- ALLOCATABLE POINTER ----
    ! Allocatable arrays are preferred over pointers
    integer, allocatable :: alloc_array(:)
    allocate(alloc_array(5))
    alloc_array = [1, 2, 3, 4, 5]
    print *, "Allocatable array:", alloc_array
    deallocate(alloc_array)
    
    ! ---- POINTER STATUS ----
    print *, "--- Pointer Status ---"
    print *, "int_ptr associated:", associated(int_ptr)
    print *, "real_ptr associated:", associated(real_ptr)
    
    ! ---- POINTER ASSIGNMENT ----
    integer, target :: x = 10
    integer, target :: y = 20
    integer, pointer :: p
    
    p => x
    print *, "p => x:", p
    
    p => y
    print *, "p => y:", p
    
    ! ---- POINTER ARRAYS ----
    integer, target :: matrix(3, 3)
    integer, pointer :: row(:)
    
    matrix = reshape([(i, i = 1, 9)], [3, 3])
    row => matrix(2, :)
    print *, "Row 2:", row
    
    ! ---- POINTER WITH ALLOCATABLE TARGET ----
    integer, allocatable, target :: dynamic(:)
    integer, pointer :: dyn_ptr(:)
    
    allocate(dynamic(5))
    dynamic = [10, 20, 30, 40, 50]
    dyn_ptr => dynamic
    
    print *, "Dynamic pointer:", dyn_ptr
    
    deallocate(dynamic)
    ! dyn_ptr now points to deallocated memory
    print *, "After deallocation (pointer is dangling)"
    
end program pointers_demo

Pointers are declared with pointer attribute. => associates pointer with target. associated() checks pointer status. Allocatable is preferred for safety. No pointer arithmetic in Fortran.

4. Dynamic Memory Allocation and Management

Dynamic memory allocation handles data whose size isn’t known at compile time.

ALLOCATE allocates memory. DEALLOCATE frees memory.Memory leaks happen when allocated memory is no longer needed but is not properly released.. Allocatable arrays are preferred over pointers. ALLOCATABLE attribute handles automatic deallocation.

Code Example

program dynamic_memory
    implicit none
    
    integer :: size, rows, cols
    real, allocatable :: data1(:)
    real, allocatable :: matrix(:, :)
    character(len=20), allocatable :: names(:)
    integer :: i, j
    
    ! ---- ALLOCATING 1D ARRAY ----
    print *, "--- Allocating 1D Array ---"
    size = 5
    allocate(data1(size))
    
    data1 = [(real(i), i = 1, size)]
    print *, "Data:", data1
    
    ! ---- ALLOCATING 2D ARRAY ----
    print *, "--- Allocating 2D Array ---"
    rows = 3
    cols = 4
    allocate(matrix(rows, cols))
    
    do i = 1, rows
        do j = 1, cols
            matrix(i, j) = real(i + j)
        end do
    end do
    
    print *, "Matrix:"
    do i = 1, rows
        print *, matrix(i, :)
    end do
    
    ! ---- ALLOCATING CHARACTER ARRAY ----
    print *, "--- Allocating Character Array ---"
    allocate(names(3))
    names = ["Sparrow", "Eagle", "Hawk"]
    print *, "Names:", names
    
    ! ---- DEALLOCATING ----
    print *, "--- Deallocating ---"
    deallocate(data1)
    deallocate(matrix)
    deallocate(names)
    print *, "Memory freed"
    
    ! ---- REALLOCATING ----
    print *, "--- Reallocating ---"
    size = 8
    allocate(data1(size))
    data1 = [(real(i) * 2.0, i = 1, size)]
    print *, "Reallocated data:", data1
    
    deallocate(data1)
    
    ! ---- AUTO-DEALLOCATION ----
    print *, "--- Auto-deallocation ---"
    call auto_deallocate_example()
    
    ! ---- MULTIPLE ALLOCATES ----
    print *, "--- Multiple Allocates ---"
    integer, allocatable :: a(:), b(:)
    
    allocate(a(5), b(5))
    a = [1, 2, 3, 4, 5]
    b = a * 2
    
    print *, "a:", a
    print *, "b:", b
    
    deallocate(a, b)
    
    ! ---- MOVE ALLOC ----
    print *, "--- MOVE_ALLOC ---"
    integer, allocatable :: src(:), dst(:)
    
    allocate(src(3))
    src = [10, 20, 30]
    
    call move_alloc(src, dst)
    print *, "Source (deallocated):", allocated(src)
    print *, "Destination:", dst
    
    deallocate(dst)
    
    ! ---- ALLOCATABLE STATUS ----
    print *, "--- Allocatable Status ---"
    real, allocatable :: test(:)
    print *, "allocated(test):", allocated(test)
    
    allocate(test(5))
    print *, "allocated(test):", allocated(test)
    
    deallocate(test)
    print *, "allocated(test):", allocated(test)
    
contains

    subroutine auto_deallocate_example()
        real, allocatable :: local_data(:)
        
        allocate(local_data(5))
        local_data = [1.0, 2.0, 3.0, 4.0, 5.0]
        print *, "Local data:", local_data
        ! Automatically deallocated when subroutine returns
    end subroutine auto_deallocate_example

end program dynamic_memory

ALLOCATE allocates memory. DEALLOCATE frees it. Auto-deallocation happens when variables go out of scope. MOVE_ALLOC transfers allocation. ALLOCATED checks allocation status.

Chapter 9: Modules and Advanced Program Structure

1. Creating and Using Modules

Modules are Fortran’s primary mechanism for encapsulation and code organization.

Modules group related procedures and variables. USE imports a module. Interface section declares public components. Implementation section contains code. Modules are the modern alternative to common blocks.

Code Example

module bird_module
    implicit none
    
    ! ---- MODULE VARIABLES ----
    integer, public :: bird_count = 0
    character(len=20), private :: module_name = "BirdModule"
    
    ! ---- DERIVED TYPE ----
    type, public :: Bird
        character(len=20) :: species
        integer :: count
        real :: weight
    end type Bird
    
    ! ---- INTERFACE SECTION ----
    public :: add_bird, display_bird, bird_count
    private :: module_name, validate_count
    
contains
    
    ! ---- PUBLIC SUBROUTINE ----
    subroutine add_bird(bird, count)
        type(Bird), intent(inout) :: bird
        integer, intent(in) :: count
        
        if (validate_count(count)) then
            bird%count = bird%count + count
            bird_count = bird_count + count
        end if
    end subroutine add_bird
    
    ! ---- PUBLIC SUBROUTINE ----
    subroutine display_bird(bird)
        type(Bird), intent(in) :: bird
        print *, "Species:", bird%species
        print *, "Count:", bird%count
        print *, "Weight:", bird%weight
        print *, "---"
    end subroutine display_bird
    
    ! ---- PRIVATE FUNCTION ----
    function validate_count(count) result(valid)
        integer, intent(in) :: count
        logical :: valid
        
        valid = count > 0
        if (.not. valid) then
            print *, "Error: Count must be positive"
        end if
    end function validate_count
    
end module bird_module

program module_demo
    use bird_module
    implicit none
    
    type(Bird) :: sparrow, eagle
    
    ! ---- INITIALIZE BIRDS ----
    sparrow%species = "Sparrow"
    sparrow%count = 10
    sparrow%weight = 25.5
    
    eagle%species = "Eagle"
    eagle%count = 3
    eagle%weight = 4500.0
    
    ! ---- USE MODULE PROCEDURES ----
    print *, "--- Initial Bird Data ---"
    call display_bird(sparrow)
    call display_bird(eagle)
    
    print *, "Total birds:", bird_count
    
    ! ---- ADD BIRDS ----
    call add_bird(sparrow, 5)
    call add_bird(eagle, 2)
    
    print *, "--- After Adding Birds ---"
    call display_bird(sparrow)
    call display_bird(eagle)
    
    print *, "Total birds:", bird_count
    
    ! ---- PRIVATE COMPONENTS NOT ACCESSIBLE ----
    ! print *, module_name  ! ERROR: Private
    ! call validate_count(5) ! ERROR: Private
    
end program module_demo

Modules group related code. PUBLIC and PRIVATE control visibility. USE imports modules. Modules are compiled separately.

2. Public and Private Components

PUBLIC and PRIVATE control visibility of module components.

PUBLIC makes components accessible outside. PRIVATE restricts access to within the module. Encapsulation improves maintainability. Default is PUBLIC unless specified.

Code Example

module access_module
    implicit none
    
    ! ---- PUBLIC BY DEFAULT ----
    integer :: public_var = 10
    
    ! ---- PRIVATE SPECIFICATION ----
    private :: private_var
    private :: private_subroutine
    
    ! ---- PRIVATE VARIABLE ----
    integer, private :: private_var = 20
    character(len=20), private :: secret = "Secret"
    
    ! ---- PUBLIC PROCEDURES ----
    public :: set_private, get_private
    public :: display_info
    
    ! ---- MODULE DATA ----
    integer :: module_data = 0
    
contains
    
    ! ---- PUBLIC SUBROUTINE ----
    subroutine set_private(value)
        integer, intent(in) :: value
        private_var = value
        module_data = module_data + 1
    end subroutine set_private
    
    ! ---- PUBLIC FUNCTION ----
    function get_private() result(value)
        integer :: value
        value = private_var
    end function get_private
    
    ! ---- PUBLIC SUBROUTINE ----
    subroutine display_info()
        print *, "--- Module Info ---"
        print *, "Public var:", public_var
        print *, "Private var:", private_var
        print *, "Secret:", secret
        print *, "Module data:", module_data
    end subroutine display_info
    
    ! ---- PRIVATE SUBROUTINE ----
    subroutine private_subroutine()
        print *, "This is private"
    end subroutine private_subroutine

end module access_module

program access_demo
    use access_module
    implicit none
    
    integer :: value
    
    print *, "--- Access Module ---"
    
    ! ---- PUBLIC VARIABLE ACCESSIBLE ----
    public_var = 100
    print *, "public_var:", public_var
    
    ! ---- PRIVATE VARIABLE NOT ACCESSIBLE ----
    ! private_var = 30   ! ERROR: Private
    
    ! ---- ACCESS VIA PUBLIC PROCEDURES ----
    call set_private(50)
    value = get_private()
    print *, "Private var via get:", value
    
    ! ---- DISPLAY INFO (Public) ----
    call display_info()
    
    ! ---- PRIVATE SUBROUTINE NOT ACCESSIBLE ----
    ! call private_subroutine()  ! ERROR: Private
    
    print *, "--- Done ---"
    
end program access_demo

PUBLIC makes components visible. PRIVATE hides them. Encapsulation improves maintainability. Default is PUBLIC.

3. Interface Blocks

Interface blocks describe procedure interfaces for better error checking.

Interface blocks declare procedure signatures. Explicit interfaces enable better error checking. Generic interfaces allow procedure overloading. Interface blocks are essential for external procedures.

Code Example

module interface_module
    implicit none
    
    ! ---- GENERIC INTERFACE ----
    interface add
        module procedure add_integer
        module procedure add_real
        module procedure add_complex
    end interface add
    
    ! ---- MODULE SUBROUTINES ----
    contains
    
    function add_integer(a, b) result(result_val)
        integer, intent(in) :: a, b
        integer :: result_val
        result_val = a + b
    end function add_integer
    
    function add_real(a, b) result(result_val)
        real, intent(in) :: a, b
        real :: result_val
        result_val = a + b
    end function add_real
    
    function add_complex(a, b) result(result_val)
        complex, intent(in) :: a, b
        complex :: result_val
        result_val = a + b
    end function add_complex
    
end module interface_module

program interface_demo
    use interface_module
    implicit none
    
    integer :: i = 10, j = 20
    real :: x = 5.5, y = 3.2
    complex :: z1 = (1.0, 2.0), z2 = (3.0, 4.0)
    
    ! ---- USING GENERIC INTERFACE ----
    print *, "--- Generic Interface ---"
    print *, "Integer add:", add(i, j)
    print *, "Real add:", add(x, y)
    print *, "Complex add:", add(z1, z2)
    
    ! ---- INTERFACE BLOCK FOR EXTERNAL ----
    interface
        subroutine external_sub(a, b, result)
            integer, intent(in) :: a, b
            integer, intent(out) :: result
        end subroutine external_sub
    end interface
    
    integer :: result
    call external_sub(5, 3, result)
    print *, "External result:", result
    
contains
    
    ! ---- PROCEDURE DEFINITION ----
    subroutine external_sub(a, b, result)
        integer, intent(in) :: a, b
        integer, intent(out) :: result
        result = a * b
    end subroutine external_sub
    
end program interface_demo

Interface blocks declare procedure signatures. Generic interfaces allow overloading. Explicit interfaces enable better error checking.

4. Procedure Overloading

Procedure overloading allows multiple procedures with the same name.

Generic interfaces enable overloading. Procedures are selected by argument types. Overloading makes code more readable. Rules require distinct signatures.

Code Example

module overloading_module
    implicit none
    
    ! ---- GENERIC INTERFACE FOR OVERLOADING ----
    interface process
        module procedure process_integer
        module procedure process_real
        module procedure process_string
        module procedure process_bird
    end interface process
    
    ! ---- DERIVED TYPE ----
    type :: Bird
        character(len=20) :: species
        integer :: count
    end type Bird
    
    contains
    
    ! ---- INTEGER VERSION ----
    subroutine process_integer(value)
        integer, intent(in) :: value
        print *, "Processing integer:", value
    end subroutine process_integer
    
    ! ---- REAL VERSION ----
    subroutine process_real(value)
        real, intent(in) :: value
        print *, "Processing real:", value
    end subroutine process_real
    
    ! ---- STRING VERSION ----
    subroutine process_string(value)
        character(len=*), intent(in) :: value
        print *, "Processing string:", trim(value)
    end subroutine process_string
    
    ! ---- BIRD VERSION ----
    subroutine process_bird(bird)
        type(Bird), intent(in) :: bird
        print *, "Processing bird:", bird%species, "Count:", bird%count
    end subroutine process_bird

end module overloading_module

program overloading_demo
    use overloading_module
    implicit none
    
    type(Bird) :: sparrow
    
    sparrow%species = "Sparrow"
    sparrow%count = 10
    
    ! ---- USING OVERLOADED PROCEDURE ----
    print *, "--- Procedure Overloading ---"
    call process(42)
    call process(3.14)
    call process("Hello")
    call process(sparrow)
    
    ! ---- AUTOMATIC SELECTION ----
    print *, "--- Automatic Selection ---"
    call process(100)
    call process(99.9)
    call process("Bird")
    
end program overloading_demo

interface with module procedure enables overloading. The compiler selects the matching procedure based on argument types.

5. Modular Architecture

Modular architecture organizes code into cohesive, loosely coupled modules.

Modular architecture separates concerns. Cohesion means related code stays together. Coupling means modules are independent. Benefits include maintainability and reusability.

Code Example

module bird_data_module
    implicit none
    
    type, public :: Bird
        character(len=20) :: species
        integer :: count
        real :: weight
    end type Bird
    
    public :: create_bird, get_species, get_count
    
contains
    
    function create_bird(species, count, weight) result(bird)
        character(len=*), intent(in) :: species
        integer, intent(in) :: count
        real, intent(in) :: weight
        type(Bird) :: bird
        
        bird%species = trim(species)
        bird%count = count
        bird%weight = weight
    end function create_bird
    
    function get_species(bird) result(species)
        type(Bird), intent(in) :: bird
        character(len=20) :: species
        species = bird%species
    end function get_species
    
    function get_count(bird) result(count)
        type(Bird), intent(in) :: bird
        integer :: count
        count = bird%count
    end function get_count
    
end module bird_data_module

module bird_operations_module
    use bird_data_module
    implicit none
    
    public :: add_to_count, display_bird
    private :: validate_count
    
contains
    
    subroutine add_to_count(bird, amount)
        type(Bird), intent(inout) :: bird
        integer, intent(in) :: amount
        
        if (validate_count(amount)) then
            bird%count = bird%count + amount
        end if
    end subroutine add_to_count
    
    subroutine display_bird(bird)
        type(Bird), intent(in) :: bird
        print *, "Species:", bird%species
        print *, "Count:", bird%count
        print *, "Weight:", bird%weight
    end subroutine display_bird
    
    function validate_count(amount) result(valid)
        integer, intent(in) :: amount
        logical :: valid
        valid = amount > 0
    end function validate_count
    
end module bird_operations_module

program modular_architecture
    use bird_data_module
    use bird_operations_module
    implicit none
    
    type(Bird) :: sparrow, eagle
    
    ! ---- CREATE BIRDS ----
    sparrow = create_bird("Sparrow", 10, 25.5)
    eagle = create_bird("Eagle", 3, 4500.0)
    
    ! ---- DISPLAY BIRDS ----
    print *, "--- Modular Architecture ---"
    print *, "Initial state:"
    call display_bird(sparrow)
    call display_bird(eagle)
    
    ! ---- MODIFY BIRDS ----
    call add_to_count(sparrow, 5)
    call add_to_count(eagle, 2)
    
    print *, "After additions:"
    call display_bird(sparrow)
    call display_bird(eagle)
    
    ! ---- ACCESS FUNCTIONS ----
    print *, "--- Access Functions ---"
    print *, "Sparrow species:", get_species(sparrow)
    print *, "Eagle count:", get_count(eagle)
    
    print *, "--- Architecture Benefits ---"
    print *, "1. Cohesion: Related code together"
    print *, "2. Coupling: Modules are independent"
    print *, "3. Maintainability: Easy to update"
    print *, "4. Reusability: Can use in other programs"
    
end program modular_architecture

Modular architecture separates data and operations. Cohesion keeps related code together. Coupling minimizes dependencies.

Chapter 10: Modern Fortran Features

1. Fortran 90 through Fortran 2018 Features

Modern Fortran includes many powerful features for safer, more expressive code.

Fortran 90 brought free-form syntax, modules, array operations, and allocatable arrays. Fortran 95 added high-performance features. Fortran 2003 introduced OOP and C interoperability. Fortran 2008 added coarrays and submodules. Fortran 2018 added new features. Modern features improve safety and expressiveness.

Code Example

program modern_fortran
    implicit none
    
    ! ---- FREE-FORM SYNTAX ----
    ! No fixed column formatting required
    
    ! ---- ALLOCATABLE ARRAYS ----
    integer, allocatable :: data(:)
    allocate(data(5))
    data = [1, 2, 3, 4, 5]
    
    ! ---- ARRAY OPERATIONS ----
    integer :: a(5) = [1, 2, 3, 4, 5]
    integer :: b(5) = [5, 4, 3, 2, 1]
    integer :: c(5)
    
    c = a + b
    print *, "Array addition:", c
    print *, "Sum:", sum(a)
    
    ! ---- DERIVED TYPES ----
    type :: Bird
        character(len=20) :: species
        integer :: count
    end type Bird
    
    type(Bird) :: sparrow = Bird("Sparrow", 10)
    print *, "Derived type:", sparrow%species, sparrow%count
    
    ! ---- MODULES ----
    ! Modules provide encapsulation
    
    ! ---- KIND PARAMETERS ----
    integer, parameter :: dp = kind(0.0d0)
    real(kind=dp) :: high_precision = 3.141592653589793_dp
    print *, "High precision:", high_precision
    
    ! ---- CHARACTER FUNCTIONS ----
    character(len=20) :: name = "Sparrow"
    print *, "Trimmed:", trim(name)
    
    ! ---- ELEMENTAL PROCEDURES ----
    print *, "--- Elemental Example ---"
    integer :: arr(5) = [1, 2, 3, 4, 5]
    print *, "Double:", double_array(arr)
    
    ! ---- BLOCK CONSTRUCTS ----
    print *, "--- Block Construct ---"
    block
        integer :: block_var = 50
        print *, "Block variable:", block_var
    end block
    
    ! ---- SUBMODULE (Fortran 2008) ----
    ! Allows splitting modules
    
    ! ---- COARRAYS (Fortran 2008) ----
    ! Parallel programming feature
    
    print *, "--- Modern Fortran Benefits ---"
    print *, "1. Free-form syntax"
    print *, "2. Array operations"
    print *, "3. Modules"
    print *, "4. Kind parameters"
    print *, "5. Better safety"
    
contains
    
    elemental function double_array(x) result(y)
        integer, intent(in) :: x
        integer :: y
        y = x * 2
    end function double_array
    
end program modern_fortran

Modern Fortran features improve safety and expressiveness. Free-form syntax removes fixed column requirements. Array operations are concise and efficient. Modules provide encapsulation. Kind parameters control precision.

2. Object-Oriented Programming in Fortran

Fortran 2003 introduced object-oriented programming features.

Type extension creates derived classes. Type-bound procedures are methods. Polymorphism allows multiple types. extends keyword for inheritance.

Code Example

module oop_module
    implicit none
    
    ! ---- BASE TYPE ----
    type, abstract :: Bird
        character(len=20) :: species
    contains
        procedure :: display => display_bird
        procedure :: speak => speak_bird
    end type Bird
    
    ! ---- DERIVED TYPE ----
    type, extends(Bird) :: Sparrow
        integer :: count
    contains
        procedure :: speak => speak_sparrow
    end type Sparrow
    
    ! ---- ANOTHER DERIVED TYPE ----
    type, extends(Bird) :: Eagle
        real :: wingspan
    contains
        procedure :: speak => speak_eagle
    end type Eagle
    
contains
    
    subroutine display_bird(this)
        class(Bird), intent(in) :: this
        print *, "Species:", this%species
    end subroutine display_bird
    
    subroutine speak_bird(this)
        class(Bird), intent(in) :: this
        print *, "Generic bird sound"
    end subroutine speak_bird
    
    subroutine speak_sparrow(this)
        class(Sparrow), intent(in) :: this
        print *, "Chirp! Chirp!"
    end subroutine speak_sparrow
    
    subroutine speak_eagle(this)
        class(Eagle), intent(in) :: this
        print *, "Screech!"
    end subroutine speak_eagle

end module oop_module

program oop_demo
    use oop_module
    implicit none
    
    type(Sparrow) :: sparrow
    type(Eagle) :: eagle
    class(Bird), pointer :: bird_ptr
    
    ! ---- INITIALIZE OBJECTS ----
    sparrow%species = "Sparrow"
    sparrow%count = 10
    
    eagle%species = "Eagle"
    eagle%wingspan = 2.3
    
    ! ---- CALL METHODS ----
    print *, "--- OOP in Fortran ---"
    call sparrow%display()
    call sparrow%speak()
    
    call eagle%display()
    call eagle%speak()
    
    ! ---- POLYMORPHISM ----
    print *, "--- Polymorphism ---"
    bird_ptr => sparrow
    call bird_ptr%speak()
    
    bird_ptr => eagle
    call bird_ptr%speak()
    
    ! ---- TYPE EXTENSION ----
    print *, "--- Type Extension ---"
    print *, "Sparrow extends Bird"
    print *, "Eagle extends Bird"
    
end program oop_demo

OOP in Fortran uses type extension. extends creates derived types. Type-bound procedures are methods. Polymorphism works with class pointer.

3. Intrinsic Functions and Array Programming

Intrinsic functions are built-in functions for math, arrays, and strings.

Mathematical functions include sin, cos, exp, log, sqrt. Array functions include sum, product, matmul, dot_product. Character functions include len, trim, index. Array programming uses whole-array operations.

Code Example

program intrinsics_demo
    implicit none
    
    real :: x = 3.14159
    integer :: i
    
    ! ---- MATHEMATICAL INTRINSICS ----
    print *, "--- Mathematical Intrinsics ---"
    print *, "sin(x):", sin(x)
    print *, "cos(x):", cos(x)
    print *, "tan(x):", tan(x)
    print *, "exp(1):", exp(1.0)
    print *, "log(10):", log(10.0)
    print *, "sqrt(16):", sqrt(16.0)
    print *, "abs(-5):", abs(-5)
    print *, "mod(10,3):", mod(10, 3)
    
    ! ---- ARRAY INTRINSICS ----
    print *, "--- Array Intrinsics ---"
    real :: a(5) = [1.0, 2.0, 3.0, 4.0, 5.0]
    real :: b(5) = [5.0, 4.0, 3.0, 2.0, 1.0]
    real :: c(5)
    real :: matrix(3, 3)
    
    print *, "a:", a
    print *, "sum(a):", sum(a)
    print *, "product(a):", product(a)
    print *, "maxval(a):", maxval(a)
    print *, "minval(a):", minval(a)
    print *, "size(a):", size(a)
    
    c = a + b
    print *, "a + b:", c
    
    c = a * 2.0
    print *, "a * 2:", c
    
    ! ---- MATRIX OPERATIONS ----
    print *, "--- Matrix Operations ---"
    matrix = reshape([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0], [3, 3])
    
    print *, "Matrix:"
    do i = 1, 3
        print *, matrix(i, :)
    end do
    
    print *, "Trace:", sum([matrix(1,1), matrix(2,2), matrix(3,3)])
    print *, "Dot product:", dot_product(a(1:3), b(1:3))
    
    ! ---- CHARACTER INTRINSICS ----
    print *, "--- Character Intrinsics ---"
    character(len=20) :: str = "  Sparrow  "
    
    print *, "String:", str
    print *, "len:", len(str)
    print *, "len_trim:", len_trim(str)
    print *, "trim:", trim(str)
    print *, "adjustl:", adjustl(str)
    print *, "adjustr:", adjustr(str)
    print *, "index:", index(str, "par")
    
    ! ---- KIND FUNCTIONS ----
    print *, "--- Kind Functions ---"
    print *, "kind(1):", kind(1)
    print *, "kind(1.0):", kind(1.0)
    print *, "kind(1.0d0):", kind(1.0d0)
    
    ! ---- SELECTED REAL KIND ----
    integer, parameter :: dp = selected_real_kind(15, 307)
    print *, "Selected real kind:", dp
    
end program intrinsics_demo

Intrinsic functions are built-in and highly optimized. Mathematical functions work on scalars and arrays. Array operations are element-wise.

4. C Interoperability with ISO_C_BINDING

ISO_C_BINDING allows Fortran to call C functions.

ISO_C_BINDING module provides C interoperability. BIND(C) specifies C binding. C_INT, C_FLOAT, C_DOUBLE are C types. VALUE passes by value. INTENT controls access.

Code Example

module c_interop_module
    use iso_c_binding
    implicit none
    
    ! ---- INTERFACE TO C FUNCTION ----
    interface
        function c_abs_value(x) bind(C, name="abs_value")
            import :: c_double
            real(c_double), value :: x
            real(c_double) :: c_abs_value
        end function c_abs_value
        
        subroutine c_print_message(str) bind(C, name="print_message")
            import :: c_char
            character(kind=c_char), dimension(*), intent(in) :: str
        end subroutine c_print_message
        
        function c_multiply(a, b) bind(C, name="multiply")
            import :: c_int
            integer(c_int), value :: a, b
            integer(c_int) :: c_multiply
        end function c_multiply
    end interface
    
contains
    
    ! ---- FORTRAN WRAPPER ----
    function fortran_abs(x) result(result_val)
        real, intent(in) :: x
        real :: result_val
        result_val = real(c_abs_value(real(x, c_double)))
    end function fortran_abs
    
    subroutine fortran_print(str)
        character(len=*), intent(in) :: str
        call c_print_message(trim(str) // c_null_char)
    end subroutine fortran_print

end module c_interop_module

program c_interop_demo
    use c_interop_module
    implicit none
    
    ! ---- USING C FUNCTIONS ----
    print *, "--- C Interoperability ---"
    print *, "abs(-5.5):", fortran_abs(-5.5)
    print *, "c_multiply(5, 3):", c_multiply(5, 3)
    
    call fortran_print("Message from Fortran!")
    
    ! ---- ISO_C_BINDING TYPES ----
    print *, "--- ISO_C_BINDING Types ---"
    print *, "C_INT:", c_int
    print *, "C_FLOAT:", c_float
    print *, "C_DOUBLE:", c_double
    print *, "C_CHAR:", c_char
    
end program c_interop_demo

! ---- C FUNCTIONS (Would be in separate C file) ----
! double abs_value(double x) {
!     return x < 0 ? -x : x;
! }
! 
! void print_message(const char* str) {
!     printf("%s\n", str);
! }
! 
! int multiply(int a, int b) {
!     return a * b;
! }

ISO_C_BINDING enables C interoperability. BIND(C) specifies C binding. C types are mapped to Fortran types. C_NULL_CHAR terminates strings.

5. Stream and Non-Advancing I/O

Stream I/O treats files as streams of bytes. Non-advancing I/O allows multiple operations on the same line.

Stream I/O uses access="stream". Non-advancing I/O uses advance="no". Stream I/O is more flexible for binary files. Non-advancing I/O allows formatted output without newlines.

Code Example

program stream_io_demo
    implicit none
    
    integer :: ios, i
    real :: data(5) = [1.0, 2.0, 3.0, 4.0, 5.0]
    real :: read_data(5)
    character(len=20) :: buffer
    
    ! ---- STREAM I/O ----
    print *, "--- Stream I/O ---"
    
    open(unit=10, file="stream.dat", access="stream", form="unformatted", status="replace")
    write(10) data
    close(10)
    
    open(unit=10, file="stream.dat", access="stream", form="unformatted", status="old")
    read(10) read_data
    close(10)
    
    print *, "Read stream data:", read_data
    
    ! ---- NON-ADVANCING I/O ----
    print *, "--- Non-Advancing I/O ---"
    
    do i = 1, 5
        write(*, "(A, I1, A)", advance="no") "Bird", i, " "
    end do
    write(*, *) "Flock"
    
    ! ---- POSITIONING ----
    print *, "--- Positioning ---"
    
    open(unit=10, file="position.dat", access="stream", form="unformatted", status="replace")
    write(10) 1.0, 2.0, 3.0, 4.0, 5.0
    close(10)
    
    open(unit=10, file="position.dat", access="stream", form="unformatted", status="old")
    
    ! Read from position
    read(10, pos=9) read_data(1)  ! Read second element (4 bytes)
    print *, "Read at position 9:", read_data(1)
    
    close(10)
    
    ! ---- COMBINED EXAMPLE ----
    print *, "--- Combined Example ---"
    
    ! Write formatted non-advancing
    open(unit=10, file="table.txt", status="replace", action="write")
    write(10, "(A10, A10)", advance="no") "Bird", "Count"
    write(10, *)
    write(10, "(A10, I10)") "Sparrow", 10
    write(10, "(A10, I10)") "Eagle", 3
    close(10)
    
    ! Read back
    open(unit=10, file="table.txt", status="old", action="read")
    read(10, *) buffer  ! Skip header
    read(10, *) buffer  ! Skip separator
    read(10, *) buffer, i
    print *, "Read:", trim(buffer), i
    close(10)
    
    ! ---- CLEANUP ----
    print *, "--- Done ---"
    
end program stream_io_demo

Stream I/O treats files as bytes. Non-advancing I/O allows output without newlines. Positioning reads at specific locations.

Chapter 11: Scientific Computing with Fortran

1. Linear Algebra and Matrix Operations

Linear algebra operations are fundamental to scientific computing.

MATMUL performs matrix multiplication. DOT_PRODUCT computes dot product. Matrix transpose uses transpose(). Linear equations can be solved using numerical methods.

Code Example

program linear_algebra
    implicit none
    
    integer :: i, j
    real :: A(3, 3), B(3, 3), C(3, 3)
    real :: X(3), Y(3)
    real :: matrix(3, 3)
    
    ! ---- MATRIX INITIALIZATION ----
    A = reshape([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0], [3, 3])
    B = reshape([9.0, 8.0, 7.0, 6.0, 5.0, 4.0, 3.0, 2.0, 1.0], [3, 3])
    X = [1.0, 2.0, 3.0]
    Y = [4.0, 5.0, 6.0]
    
    ! ---- DISPLAY MATRICES ----
    print *, "--- Matrix A ---"
    do i = 1, 3
        print *, A(i, :)
    end do
    
    print *, "--- Matrix B ---"
    do i = 1, 3
        print *, B(i, :)
    end do
    
    ! ---- MATRIX MULTIPLICATION ----
    print *, "--- Matrix Multiplication (MATMUL) ---"
    C = matmul(A, B)
    print *, "C = A * B:"
    do i = 1, 3
        print *, C(i, :)
    end do
    
    ! ---- DOT PRODUCT ----
    print *, "--- Dot Product ---"
    print *, "X . Y =", dot_product(X, Y)
    
    ! ---- MATRIX TRANSPOSE ----
    print *, "--- Matrix Transpose ---"
    matrix = transpose(A)
    print *, "Transpose A:"
    do i = 1, 3
        print *, matrix(i, :)
    end do
    
    ! ---- MATRIX ADDITION ----
    print *, "--- Matrix Addition ---"
    C = A + B
    print *, "A + B:"
    do i = 1, 3
        print *, C(i, :)
    end do
    
    ! ---- SCALAR MULTIPLICATION ----
    print *, "--- Scalar Multiplication ---"
    C = A * 2.0
    print *, "A * 2:"
    do i = 1, 3
        print *, C(i, :)
    end do
    
    ! ---- MATRIX DETERMINANT ----
    print *, "--- Matrix Determinant ---"
    ! For 2x2 matrix
    real :: M(2, 2) = reshape([1.0, 2.0, 3.0, 4.0], [2, 2])
    real :: det
    
    det = M(1,1) * M(2,2) - M(1,2) * M(2,1)
    print *, "Determinant of M:", det
    
    ! ---- SOLVING LINEAR EQUATIONS ----
    print *, "--- Solving Linear Equations ---"
    ! A * x = b
    real :: A2(3, 3), b2(3), x2(3)
    integer :: ipiv(3), info
    
    A2 = reshape([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 10.0], [3, 3])
    b2 = [1.0, 2.0, 3.0]
    
    print *, "A2:"
    do i = 1, 3
        print *, A2(i, :)
    end do
    print *, "b2:", b2
    
    ! Note: Full linear algebra libraries like LAPACK are
    ! recommended for serious work
    
    print *, "--- Linear Algebra Summary ---"
    print *, "MATMUL: Matrix multiplication"
    print *, "DOT_PRODUCT: Dot product"
    print *, "Transpose: transpose()"
    print *, "LAPACK for advanced operations"
    
end program linear_algebra

MATMUL multiplies matrices. DOT_PRODUCT computes dot product. transpose transposes matrices. LAPACK is used for advanced operations.

2. Numerical Integration and Differentiation

Numerical methods for integration and differentiation.

Trapezoidal rule approximates integrals. Simpson’s rule is more accurate. Numerical differentiation uses finite differences.

Code Example

program numerical_methods
    implicit none
    
    real :: a = 0.0, b = 3.14159  ! Integration limits
    integer :: n = 100
    real :: h, integral, error
    
    ! ---- TRAPEZOIDAL RULE ----
    print *, "--- Trapezoidal Rule ---"
    h = (b - a) / real(n)
    integral = 0.5 * (f(a) + f(b))
    
    do i = 1, n-1
        integral = integral + f(a + real(i) * h)
    end do
    
    integral = integral * h
    print *, "Trapezoidal rule:", integral
    
    ! ---- SIMPSON'S RULE ----
    print *, "--- Simpson's Rule ---"
    if (mod(n, 2) == 0) then
        h = (b - a) / real(n)
        integral = f(a) + f(b)
        
        do i = 1, n-1
            if (mod(i, 2) == 0) then
                integral = integral + 2.0 * f(a + real(i) * h)
            else
                integral = integral + 4.0 * f(a + real(i) * h)
            end if
        end do
        
        integral = integral * h / 3.0
        print *, "Simpson's rule:", integral
    else
        print *, "Need even number of intervals for Simpson's rule"
    end if
    
    ! ---- NUMERICAL DIFFERENTIATION ----
    print *, "--- Numerical Differentiation ---"
    real :: x0 = 1.0
    real :: fx0, fx1, fx2
    real :: derivative
    
    h = 0.001
    fx0 = f(x0)
    fx1 = f(x0 + h)
    fx2 = f(x0 - h)
    
    ! Central difference
    derivative = (fx1 - fx2) / (2.0 * h)
    print *, "Derivative at x=1:", derivative
    
    ! ---- ERROR ANALYSIS ----
    print *, "--- Error Analysis ---"
    real :: exact = 1.0 - cos(1.0)  ! Integral of sin(x) from 0 to 1
    
    ! Converge with more intervals
    do n = 10, 100, 10
        call trapezoidal_error(a, b, n, exact)
    end do
    
contains
    
    function f(x) result(result_val)
        real, intent(in) :: x
        real :: result_val
        result_val = sin(x)
    end function f
    
    subroutine trapezoidal_error(a, b, n, exact)
        real, intent(in) :: a, b, exact
        integer, intent(in) :: n
        real :: h, integral
        integer :: i
        
        h = (b - a) / real(n)
        integral = 0.5 * (f(a) + f(b))
        
        do i = 1, n-1
            integral = integral + f(a + real(i) * h)
        end do
        
        integral = integral * h
        print *, "n=", n, "Error:", abs(integral - exact)
    end subroutine trapezoidal_error

end program numerical_methods

Trapezoidal rule sums trapezoids. Simpson’s rule uses parabolas. Numerical differentiation uses finite differences. Error decreases with more intervals.

3. Root Finding Algorithms

Root finding locates zeros of functions.

Bisection method halves the interval each iteration. Newton-Raphson uses derivatives. Brent’s method combines methods.

Code Example

program root_finding
    implicit none
    
    real :: a = 0.0, b = 2.0
    real :: tol = 1e-6
    real :: root
    integer :: max_iter = 100
    
    ! ---- BISECTION METHOD ----
    print *, "--- Bisection Method ---"
    call bisection(f, a, b, tol, max_iter, root)
    print *, "Root found:", root
    print *, "f(root):", f(root)
    
    ! ---- NEWTON-RAPHSON METHOD ----
    print *, "--- Newton-Raphson Method ---"
    real :: x0 = 1.0
    call newton_raphson(f, df, x0, tol, max_iter, root)
    print *, "Root found:", root
    print *, "f(root):", f(root)
    
contains
    
    function f(x) result(result_val)
        real, intent(in) :: x
        real :: result_val
        result_val = x**2 - 2.0  ! Root at sqrt(2)
    end function f
    
    function df(x) result(result_val)
        real, intent(in) :: x
        real :: result_val
        result_val = 2.0 * x  ! Derivative of f
    end function df
    
    subroutine bisection(f, a, b, tol, max_iter, root)
        interface
            function f(x) result(result_val)
                real, intent(in) :: x
                real :: result_val
            end function f
        end interface
        
        real, intent(in) :: a, b, tol
        integer, intent(in) :: max_iter
        real, intent(out) :: root
        
        real :: fa, fb, fc, c
        integer :: iter
        
        fa = f(a)
        fb = f(b)
        
        if (fa * fb > 0) then
            print *, "Error: f(a) and f(b) have same sign"
            root = 0.0
            return
        end if
        
        do iter = 1, max_iter
            c = (a + b) / 2.0
            fc = f(c)
            
            if (abs(fc) < tol .or. (b - a) < tol) then
                root = c
                print *, "Converged after", iter, "iterations"
                return
            end if
            
            if (fa * fc < 0) then
                b = c
                fb = fc
            else
                a = c
                fa = fc
            end if
        end do
        
        root = (a + b) / 2.0
        print *, "Max iterations reached"
    end subroutine bisection
    
    subroutine newton_raphson(f, df, x0, tol, max_iter, root)
        interface
            function f(x) result(result_val)
                real, intent(in) :: x
                real :: result_val
            end function f
            
            function df(x) result(result_val)
                real, intent(in) :: x
                real :: result_val
            end function df
        end interface
        
        real, intent(in) :: x0, tol
        integer, intent(in) :: max_iter
        real, intent(out) :: root
        
        real :: x, fx, dfx
        integer :: iter
        
        x = x0
        
        do iter = 1, max_iter
            fx = f(x)
            dfx = df(x)
            
            if (abs(dfx) < 1e-12) then
                print *, "Derivative too small"
                root = x
                return
            end if
            
            x = x - fx / dfx
            
            if (abs(fx) < tol) then
                root = x
                print *, "Converged after", iter, "iterations"
                return
            end if
        end do
        
        root = x
        print *, "Max iterations reached"
    end subroutine newton_raphson

end program root_finding

Bisection halves the interval. Newton-Raphson uses derivative for faster convergence. Both find roots of functions.

4. Differential Equations

Differential equations model dynamic systems.

Euler’s method is simple but inaccurate. Runge-Kutta methods are more accurate. ODEs model continuous systems.

Code Example

program differential_equations
    implicit none
    
    real :: t0 = 0.0, tf = 10.0
    real :: y0 = 1.0
    real :: h
    integer :: n_steps = 100
    
    ! ---- EULER METHOD ----
    print *, "--- Euler Method ---"
    h = (tf - t0) / real(n_steps)
    call euler(dydt, t0, tf, y0, n_steps)
    
    ! ---- RUNGE-KUTTA METHOD (4th order) ----
    print *, "--- Runge-Kutta Method ---"
    call runge_kutta_4(dydt, t0, tf, y0, n_steps)
    
    ! ---- STIFF EQUATION ----
    print *, "--- Stiff Equation ---"
    real :: y0_stiff = 1.0
    call stiff_demo(y0_stiff)
    
contains
    
    function dydt(t, y) result(result_val)
        real, intent(in) :: t, y
        real :: result_val
        result_val = -0.1 * y  ! Simple decay
    end function dydt
    
    subroutine euler(f, t0, tf, y0, n)
        interface
            function f(t, y) result(result_val)
                real, intent(in) :: t, y
                real :: result_val
            end function f
        end interface
        
        real, intent(in) :: t0, tf, y0
        integer, intent(in) :: n
        
        real :: t, y
        real :: h
        integer :: i
        
        h = (tf - t0) / real(n)
        t = t0
        y = y0
        
        print *, "Euler method:"
        do i = 1, n
            y = y + h * f(t, y)
            t = t + h
            
            if (mod(i, 10) == 0) then
                print *, "t =", t, "y =", y
            end if
        end do
    end subroutine euler
    
    subroutine runge_kutta_4(f, t0, tf, y0, n)
        interface
            function f(t, y) result(result_val)
                real, intent(in) :: t, y
                real :: result_val
            end function f
        end interface
        
        real, intent(in) :: t0, tf, y0
        integer, intent(in) :: n
        
        real :: t, y, h
        real :: k1, k2, k3, k4
        integer :: i
        
        h = (tf - t0) / real(n)
        t = t0
        y = y0
        
        print *, "RK4 method:"
        do i = 1, n
            k1 = f(t, y)
            k2 = f(t + h/2.0, y + h*k1/2.0)
            k3 = f(t + h/2.0, y + h*k2/2.0)
            k4 = f(t + h, y + h*k3)
            
            y = y + h * (k1 + 2.0*k2 + 2.0*k3 + k4) / 6.0
            t = t + h
            
            if (mod(i, 10) == 0) then
                print *, "t =", t, "y =", y
            end if
        end do
    end subroutine runge_kutta_4
    
    subroutine stiff_demo(y0)
        real, intent(in) :: y0
        
        real :: t, y, h
        integer :: i, n = 100
        
        ! Stiff equation: dy/dt = -1000 * y
        ! Analytical solution: y = y0 * exp(-1000 * t)
        
        print *, "Stiff equation with Euler (unstable):"
        h = 0.001
        t = 0.0
        y = y0
        
        do i = 1, 10
            y = y + h * (-1000.0 * y)
            t = t + h
            print *, "t =", t, "y =", y
        end do
        
        print *, "Use implicit methods for stiff equations"
    end subroutine stiff_demo

end program differential_equations

Euler method approximates with slopes. RK4 is more accurate. Stiff equations require special methods.

5. Random Number Generation and Statistical Computing

Random numbers are used for simulations and statistical analysis.

RANDOM_NUMBER generates uniform random numbers. Seeding controls the sequence. Different distributions can be generated.

Code Example

program random_demo
    implicit none
    
    real :: x
    real :: r(10)
    integer :: i
    
    ! ---- UNIFORM RANDOM NUMBERS ----
    print *, "--- Uniform Random Numbers ---"
    call random_number(r)
    print *, "Random numbers:", r
    
    ! ---- SEEDING ----
    print *, "--- Seeding ---"
    integer :: seed_size
    integer, allocatable :: seed(:)
    
    call random_seed(size=seed_size)
    allocate(seed(seed_size))
    
    seed = 12345  ! Set seed
    call random_seed(put=seed)
    
    call random_number(r)
    print *, "Seeded numbers:", r
    
    ! ---- GENERATING DIFFERENT DISTRIBUTIONS ----
    print *, "--- Generating Distributions ---"
    
    ! Normal distribution (Box-Muller)
    real :: u1, u2, z1, z2
    real :: mean = 0.0, std = 1.0
    
    call random_number(u1)
    call random_number(u2)
    
    z1 = sqrt(-2.0 * log(u1)) * cos(2.0 * 3.14159 * u2)
    z2 = sqrt(-2.0 * log(u1)) * sin(2.0 * 3.14159 * u2)
    
    print *, "Normal samples:", z1, z2
    
    ! ---- STATISTICAL ANALYSIS ----
    print *, "--- Statistical Analysis ---"
    real :: data(100)
    real :: mean_val, variance, std_dev
    
    call random_number(data)
    data = data * 100.0  ! Scale to 0-100
    
    mean_val = sum(data) / size(data)
    variance = sum((data - mean_val)**2) / (size(data) - 1)
    std_dev = sqrt(variance)
    
    print *, "Mean:", mean_val
    print *, "Variance:", variance
    print *, "Std Dev:", std_dev
    print *, "Min:", minval(data)
    print *, "Max:", maxval(data)
    
    ! ---- MONTE CARLO INTEGRATION ----
    print *, "--- Monte Carlo Integration ---"
    integer :: n_points = 10000
    real :: pi_estimate
    integer :: points_inside = 0
    
    do i = 1, n_points
        call random_number(u1)
        call random_number(u2)
        
        if (u1**2 + u2**2 <= 1.0) then
            points_inside = points_inside + 1
        end if
    end do
    
    pi_estimate = 4.0 * real(points_inside) / real(n_points)
    print *, "Pi estimate:", pi_estimate
    print *, "Error:", abs(pi_estimate - 3.14159)
    
    ! ---- STATISTICAL FUNCTIONS ----
    print *, "--- Statistical Functions ---"
    real :: percentile
    
    ! Sort data
    call sort_data(data)
    
    ! Percentiles
    percentile = data(int(0.5 * size(data)))
    print *, "Median:", percentile
    
    percentile = data(int(0.25 * size(data)))
    print *, "Q1:", percentile
    
    percentile = data(int(0.75 * size(data)))
    print *, "Q3:", percentile
    
contains
    
    subroutine sort_data(x)
        real, intent(inout) :: x(:)
        integer :: i, j
        real :: temp
        
        do i = 1, size(x) - 1
            do j = i + 1, size(x)
                if (x(i) > x(j)) then
                    temp = x(i)
                    x(i) = x(j)
                    x(j) = temp
                end if
            end do
        end do
    end subroutine sort_data

end program random_demo

RANDOM_NUMBER generates uniform numbers. Seeding controls the sequence. Transformations generate other distributions. Monte Carlo integrates using randomness.

6. Scientific Simulations

Scientific simulations model complex systems.

Simulation components include initial conditions, dynamics, and output. Structured code separates concerns. Performance is critical for large simulations.

Code Example

module simulation_module
    implicit none
    
    type, public :: Simulation
        integer :: n_steps
        real :: dt
        real :: time
        
        ! Physical parameters
        real :: gravity = 9.8
        real :: drag = 0.1
        
        ! State variables
        real :: y, v  ! Position, velocity
    end type Simulation
    
contains
    
    subroutine init_sim(sim, y0, v0, dt, n_steps)
        type(Simulation), intent(out) :: sim
        real, intent(in) :: y0, v0, dt
        integer, intent(in) :: n_steps
        
        sim%y = y0
        sim%v = v0
        sim%dt = dt
        sim%n_steps = n_steps
        sim%time = 0.0
    end subroutine init_sim
    
    subroutine step_sim(sim)
        type(Simulation), intent(inout) :: sim
        
        ! Simple physics: gravity and drag
        real :: acceleration
        
        acceleration = -sim%gravity - sim%drag * sim%v
        
        ! Euler integration
        sim%v = sim%v + acceleration * sim%dt
        sim%y = sim%y + sim%v * sim%dt
        sim%time = sim%time + sim%dt
    end subroutine step_sim
    
    subroutine run_sim(sim)
        type(Simulation), intent(inout) :: sim
        integer :: i
        
        print *, "--- Running Simulation ---"
        print *, "Step, Time, Position, Velocity"
        
        do i = 1, sim%n_steps
            call step_sim(sim)
            
            if (mod(i, 10) == 0) then
                print *, i, sim%time, sim%y, sim%v
            end if
            
            ! Stop if hits ground
            if (sim%y <= 0.0) then
                sim%y = 0.0
                sim%v = 0.0
                print *, "Hit ground at step", i
                exit
            end if
        end do
    end subroutine run_sim
    
    subroutine output_sim(sim, filename)
        type(Simulation), intent(in) :: sim
        character(len=*), intent(in) :: filename
        
        integer :: unit = 20
        
        open(unit, file=filename, status="replace")
        write(unit, "(A)") "Time, Position, Velocity"
        write(unit, *) sim%time, sim%y, sim%v
        close(unit)
        
        print *, "Output written to", trim(filename)
    end subroutine output_sim

end module simulation_module

program simulation_demo
    use simulation_module
    implicit none
    
    type(Simulation) :: sim
    
    ! ---- INITIALIZE SIMULATION ----
    call init_sim(sim, y0=100.0, v0=10.0, dt=0.01, n_steps=1000)
    
    ! ---- RUN SIMULATION ----
    call run_sim(sim)
    
    ! ---- OUTPUT RESULTS ----
    call output_sim(sim, "sim_results.txt")
    
    ! ---- PARAMETER STUDY ----
    print *, "--- Parameter Study ---"
    real :: drag_values(3) = [0.0, 0.1, 0.5]
    integer :: i
    
    do i = 1, 3
        call init_sim(sim, y0=100.0, v0=10.0, dt=0.01, n_steps=500)
        sim%drag = drag_values(i)
        call run_sim(sim)
        print *, "Drag =", sim%drag, "Final position:", sim%y
    end do
    
    print *, "--- Simulation Complete ---"
    
end program simulation_demo

Simulations model physical systems. Components include initialization, stepping, and output. Parameter studies explore behavior.

Chapter 12: Parallel and High-Performance Fortran

1. HPC Fundamentals

High-Performance Computing (HPC) uses large-scale parallel systems.

Parallel computing uses multiple processors. Scalability measures performance growth. Performance measurement uses timing.

Code Example

program hpc_fundamentals
    implicit none
    
    integer :: i, j, n = 1000
    real, allocatable :: a(:,:), b(:,:), c(:,:)
    real :: start_time, end_time
    real :: flops
    
    allocate(a(n, n), b(n, n), c(n, n))
    
    ! ---- PERFORMANCE MEASUREMENT ----
    print *, "--- Performance Measurement ---"
    
    ! Initialize arrays
    a = 1.0
    b = 2.0
    c = 0.0
    
    ! Time matrix multiplication
    call cpu_time(start_time)
    
    do i = 1, n
        do j = 1, n
            c(i, j) = a(i, j) + b(i, j)
        end do
    end do
    
    call cpu_time(end_time)
    print *, "Time:", end_time - start_time
    
    ! ---- SCALABILITY ----
    print *, "--- Scalability ---"
    integer :: sizes(4) = [100, 200, 500, 1000]
    
    do i = 1, 4
        n = sizes(i)
        allocate(a(n, n), b(n, n), c(n, n))
        
        a = 1.0
        b = 2.0
        
        call cpu_time(start_time)
        
        ! Simulate work
        do j = 1, n
            c(j, :) = a(j, :) + b(j, :)
        end do
        
        call cpu_time(end_time)
        
        print *, "Size", n, "Time:", end_time - start_time
        
        deallocate(a, b, c)
    end do
    
    ! ---- CACHE EFFECTS ----
    print *, "--- Cache Effects ---"
    n = 1000
    allocate(a(n, n), b(n, n), c(n, n))
    
    a = 1.0
    b = 2.0
    
    ! Row-major access
    call cpu_time(start_time)
    do i = 1, n
        do j = 1, n
            c(i, j) = a(i, j) + b(i, j)
        end do
    end do
    call cpu_time(end_time)
    print *, "Row-major:", end_time - start_time
    
    ! Column-major access
    call cpu_time(start_time)
    do j = 1, n
        do i = 1, n
            c(i, j) = a(i, j) + b(i, j)
        end do
    end do
    call cpu_time(end_time)
    print *, "Column-major:", end_time - start_time
    
    ! ---- PERFORMANCE TIPS ----
    print *, "--- Performance Tips ---"
    print *, "1. Use contiguous arrays"
    print *, "2. Vectorize loops"
    print *, "3. Minimize I/O"
    print *, "4. Use compiler optimizations"
    print *, "5. Profile and optimize"
    
    deallocate(a, b, c)
    
end program hpc_fundamentals

Performance measurement uses timing. Scalability tests different sizes. Cache effects affect performance.

2. OpenMP and Shared Memory Parallelism

OpenMP provides directive-based parallel programming for shared memory.

OpenMP uses compiler directives. Parallel loops distribute work across threads. Reduction combines results. Data dependencies must be managed.

Code Example

program openmp_demo
    implicit none
    
    integer :: i, n = 1000000
    real, allocatable :: a(:), b(:), c(:)
    real :: start_time, end_time
    
    allocate(a(n), b(n), c(n))
    
    a = 1.0
    b = 2.0
    c = 0.0
    
    ! ---- SERIAL VERSION ----
    print *, "--- Serial Version ---"
    call cpu_time(start_time)
    
    do i = 1, n
        c(i) = a(i) + b(i)
    end do
    
    call cpu_time(end_time)
    print *, "Serial time:", end_time - start_time
    
    ! ---- OPENMP PARALLEL VERSION ----
    print *, "--- OpenMP Parallel Version ---"
    call cpu_time(start_time)
    
    !$OMP PARALLEL DO
    do i = 1, n
        c(i) = a(i) + b(i)
    end do
    !$OMP END PARALLEL DO
    
    call cpu_time(end_time)
    print *, "Parallel time:", end_time - start_time
    
    ! ---- REDUCTION ----
    print *, "--- Reduction ---"
    real :: sum_val = 0.0
    
    !$OMP PARALLEL DO REDUCTION(+:sum_val)
    do i = 1, n
        sum_val = sum_val + a(i)
    end do
    !$OMP END PARALLEL DO
    
    print *, "Sum:", sum_val
    
    ! ---- MULTIPLE PARALLEL REGIONS ----
    print *, "--- Multiple Parallel Regions ---"
    real :: sum1 = 0.0, sum2 = 0.0
    
    !$OMP PARALLEL
    !$OMP SECTIONS
    !$OMP SECTION
    sum1 = sum(a)
    !$OMP SECTION
    sum2 = sum(b)
    !$OMP END SECTIONS
    !$OMP END PARALLEL
    
    print *, "Sum1:", sum1, "Sum2:", sum2
    
    ! ---- PRIVATE VARIABLES ----
    print *, "--- Private Variables ---"
    integer :: thread_id
    
    !$OMP PARALLEL PRIVATE(thread_id)
    thread_id = omp_get_thread_num()
    !$OMP CRITICAL
    print *, "Thread", thread_id
    !$OMP END CRITICAL
    !$OMP END PARALLEL
    
    ! ---- COMPILER FLAGS ----
    print *, "--- Compiler Flags ---"
    print *, "Use -fopenmp for GCC"
    print *, "Use -openmp for Intel"
    print *, "Use -O2 or -O3 with OpenMP"
    
    deallocate(a, b, c)
    
end program openmp_demo

!$OMP PARALLEL DO parallelizes loops. Reduction combines results. Private variables avoid conflicts.


3. MPI and Distributed Computing

MPI enables distributed computing across clusters.

MPI is Message Passing Interface. MPI_INIT initializes MPI. MPI_COMM_SIZE gets total processes. MPI_COMM_RANK gets process ID. MPI_SEND and MPI_RECV exchange messages.

Code Example

program mpi_demo
    use mpi
    implicit none
    
    integer :: ierr, rank, size
    integer :: i, n = 1000000
    real, allocatable :: data(:), result(:)
    real :: sum_val = 0.0
    
    ! ---- MPI INITIALIZATION ----
    call mpi_init(ierr)
    call mpi_comm_rank(mpi_comm_world, rank, ierr)
    call mpi_comm_size(mpi_comm_world, size, ierr)
    
    print *, "Rank", rank, "of", size
    
    ! ---- DATA DISTRIBUTION ----
    allocate(data(n))
    data = real(rank * n + 1, kind=kind(data))  ! Each rank gets different data
    
    ! ---- PARALLEL COMPUTATION ----
    do i = 1, n
        sum_val = sum_val + data(i)
    end do
    
    print *, "Rank", rank, "sum:", sum_val
    
    ! ---- REDUCTION ----
    real :: global_sum = 0.0
    
    call mpi_reduce(sum_val, global_sum, 1, mpi_real, mpi_sum, 0, mpi_comm_world, ierr)
    
    if (rank == 0) then
        print *, "Global sum:", global_sum
    end if
    
    ! ---- MESSAGE PASSING ----
    if (rank == 0) then
        integer :: msg = 42
        print *, "Rank 0 sending message..."
        call mpi_send(msg, 1, mpi_integer, 1, 0, mpi_comm_world, ierr)
        print *, "Message sent"
    else if (rank == 1) then
        integer :: msg
        call mpi_recv(msg, 1, mpi_integer, 0, 0, mpi_comm_world, mpi_status_ignore, ierr)
        print *, "Rank 1 received:", msg
    end if
    
    ! ---- GATHERING DATA ----
    real, allocatable :: all_data(:)
    integer :: total_size = 0
    
    if (rank == 0) then
        allocate(all_data(n * size))
    end if
    
    call mpi_gather(data, n, mpi_real, all_data, n, mpi_real, 0, mpi_comm_world, ierr)
    
    if (rank == 0) then
        print *, "First 5 values:", all_data(1:5)
        deallocate(all_data)
    end if
    
    deallocate(data)
    
    ! ---- MPI FINALIZATION ----
    call mpi_finalize(ierr)
    
end program mpi_demo

MPI_INIT starts MPI. MPI_COMM_RANK gets process ID. MPI_REDUCE combines results. MPI_SEND and MPI_RECV exchange messages.

4. Vectorization and Performance Optimization

Vectorization uses SIMD instructions for performance.

Vectorization performs operations on multiple data elements. SIMD is Single Instruction, Multiple Data. Compiler flags control vectorization. Loop structure affects vectorization.

Code Example

program vectorization_demo
    implicit none
    
    integer :: i, n = 1000000
    real, allocatable :: a(:), b(:), c(:)
    real :: start_time, end_time
    
    allocate(a(n), b(n), c(n))
    
    a = 1.0
    b = 2.0
    
    ! ---- NON-VECTORIZABLE LOOP ----
    print *, "--- Non-vectorizable Loop ---"
    call cpu_time(start_time)
    
    do i = 1, n
        if (a(i) > 0.0) then
            c(i) = a(i) + b(i)
        else
            c(i) = a(i) - b(i)
        end if
    end do
    
    call cpu_time(end_time)
    print *, "Non-vectorizable:", end_time - start_time
    
    ! ---- VECTORIZABLE LOOP ----
    print *, "--- Vectorizable Loop ---"
    call cpu_time(start_time)
    
    do i = 1, n
        c(i) = a(i) + b(i)
    end do
    
    call cpu_time(end_time)
    print *, "Vectorizable:", end_time - start_time
    
    ! ---- SIMD DIRECTIVE ----
    print *, "--- SIMD Directive ---"
    call cpu_time(start_time)
    
    !$OMP SIMD
    do i = 1, n
        c(i) = a(i) * b(i)
    end do
    
    call cpu_time(end_time)
    print *, "SIMD directive:", end_time - start_time
    
    ! ---- COMPILER OPTIMIZATION ----
    print *, "--- Compiler Optimization ---"
    print *, "Use -O2 or -O3 for optimization"
    print *, "Use -march=native for CPU-specific"
    print *, "Use -ftree-vectorize for vectorization"
    
    ! ---- ALIGNED DATA ----
    print *, "--- Aligned Data ---"
    real, allocatable :: aligned(:)
    integer :: align = 64
    
    ! Allocate aligned memory (implementation dependent)
    allocate(aligned(n))
    
    call cpu_time(start_time)
    do i = 1, n
        aligned(i) = a(i) * 2.0
    end do
    call cpu_time(end_time)
    print *, "Aligned access:", end_time - start_time
    
    ! ---- VECTORIZATION TIPS ----
    print *, "--- Vectorization Tips ---"
    print *, "1. Use contiguous arrays"
    print *, "2. Avoid complex conditionals in loops"
    print *, "3. Use stride-1 access"
    print *, "4. Minimize dependencies"
    print *, "5. Use compiler directives"
    
    deallocate(a, b, c, aligned)
    
end program vectorization_demo

Vectorization processes multiple elements at once. SIMD directives guide the compiler. Vectorizable loops have simple operations.

5. Profiling and Scalability

Profiling identifies performance bottlenecks. Scalability measures performance growth.

Profiling measures where time is spent. Scalability tests performance with more processors. Tools include gprof, Intel VTune.

Code Example

program profiling_demo
    implicit none
    
    integer :: i, j, n = 1000
    real, allocatable :: a(:,:), b(:,:), c(:,:)
    real :: start_time, end_time
    
    allocate(a(n, n), b(n, n), c(n, n))
    
    a = 1.0
    b = 2.0
    
    ! ---- MATRIX OPERATION ----
    print *, "--- Matrix Operation ---"
    call cpu_time(start_time)
    
    do i = 1, n
        do j = 1, n
            c(i, j) = a(i, j) + b(i, j)
        end do
    end do
    
    call cpu_time(end_time)
    print *, "Matrix add:", end_time - start_time
    
    ! ---- ARRAY OPERATION ----
    print *, "--- Array Operation ---"
    call cpu_time(start_time)
    
    c = a + b
    
    call cpu_time(end_time)
    print *, "Array operation:", end_time - start_time
    
    ! ---- SCALABILITY ANALYSIS ----
    print *, "--- Scalability Analysis ---"
    integer :: sizes(4) = [100, 200, 500, 1000]
    integer :: total_flops
    
    do i = 1, 4
        n = sizes(i)
        allocate(a(n, n), b(n, n), c(n, n))
        
        a = 1.0
        b = 2.0
        
        call cpu_time(start_time)
        
        do j = 1, n
            c(j, :) = a(j, :) + b(j, :)
        end do
        
        call cpu_time(end_time)
        
        total_flops = n * n * 2  ! Approximate FLOPs
        print *, "Size", n, "Time:", end_time - start_time
        print *, "Approx FLOPs:", total_flops
        
        deallocate(a, b, c)
    end do
    
    ! ---- PERFORMANCE METRICS ----
    print *, "--- Performance Metrics ---"
    print *, "FLOPS: Floating point operations per second"
    print *, "Bandwidth: Memory transfer rate"
    print *, "Latency: Time per operation"
    print *, "Scalability: Performance increase with cores"
    
    ! ---- PROFILING TOOLS ----
    print *, "--- Profiling Tools ---"
    print *, "gprof: GNU profiler"
    print *, "Intel VTune: Performance analyzer"
    print *, "Valgrind: Memory and performance"
    print *, "Perf: Linux performance tool"
    
    deallocate(a, b, c)
    
end program profiling_demo

Profiling measures performance. Scalability tests different sizes. Array operations are often faster than explicit loops.

Chapter 13: Software Engineering with Fortran

1. Version Control and Git Workflows

Version control tracks code changes and enables collaboration.

Git is the standard version control. Branches isolate development. Commits save changes. Pull requests merge code.

Code Example

# ---- GIT WORKFLOW EXAMPLE ----
# Initialize repository
git init

# Add files
git add main.f90
git add README.md

# Commit changes
git commit -m "Initial commit: Hello World"

# Create branch
git branch feature-array

# Switch to branch
git checkout feature-array

# Make changes
# Edit main.f90

# Commit changes
git add main.f90
git commit -m "Added array operations"

# Merge back to main
git checkout main
git merge feature-array

# Push to remote
git push origin main
! Example of well-structured Git history

program git_demo
    implicit none
    
    ! This program demonstrates version control
    
    integer :: i
    real :: arr(5)
    
    ! Git tracks changes over time
    
    arr = [1.0, 2.0, 3.0, 4.0, 5.0]
    
    do i = 1, 5
        print *, arr(i)
    end do
    
end program git_demo

Git tracks file changes. Branches allow parallel development. Commits save snapshots. Merging combines changes.

2. Testing and Debugging

Testing verifies correctness. Debugging fixes errors.

Unit tests test individual components. Debuggers step through code. Assertions check conditions.

Code Example

module test_module
    implicit none
    
contains
    
    function add_numbers(a, b) result(result_val)
        integer, intent(in) :: a, b
        integer :: result_val
        result_val = a + b
    end function add_numbers
    
    subroutine assert(condition, message)
        logical, intent(in) :: condition
        character(len=*), intent(in) :: message
        
        if (.not. condition) then
            print *, "FAIL:", message
            stop 1
        end if
    end subroutine assert

end module test_module

program testing_demo
    use test_module
    implicit none
    
    ! ---- UNIT TESTS ----
    print *, "--- Running Tests ---"
    
    ! Test addition
    call assert(add_numbers(2, 3) == 5, "2 + 3 = 5")
    call assert(add_numbers(-1, 1) == 0, "-1 + 1 = 0")
    call assert(add_numbers(0, 0) == 0, "0 + 0 = 0")
    
    ! ---- DEBUGGING ----
    print *, "--- Debugging ---"
    print *, "Compile with -g for debug symbols"
    print *, "Use gdb for debugging"
    print *, "Add print statements for debug output"
    
    print *, "--- Test Results ---"
    print *, "All tests passed!"
    
end program testing_demo

Unit tests verify functions. Assertions check conditions. Debuggers allow stepping through code.

3. Refactoring and Documentation

Refactoring improves code structure. Documentation explains code.

Refactoring improves code without changing behavior. Documentation explains code to others. Comments describe non-obvious logic.

Code Example

! ---- BEFORE REFACTORING ----
program unrefactored
    implicit none
    
    integer :: a(100), b(100), c(100)
    integer :: i, n = 100
    
    do i = 1, n
        a(i) = i
        b(i) = i * 2
    end do
    
    do i = 1, n
        c(i) = a(i) + b(i)
    end do
    
    do i = 1, n
        print *, c(i)
    end do
    
end program unrefactored

! ---- AFTER REFACTORING ----
module array_operations
    implicit none
    
    public :: create_arrays, add_arrays, display_array
    
contains
    
    ! Creates and initializes arrays
    subroutine create_arrays(a, b, n)
        integer, allocatable, intent(out) :: a(:), b(:)
        integer, intent(in) :: n
        integer :: i
        
        allocate(a(n), b(n))
        
        do i = 1, n
            a(i) = i
            b(i) = i * 2
        end do
    end subroutine create_arrays
    
    ! Adds two arrays element-wise
    subroutine add_arrays(a, b, c)
        integer, intent(in) :: a(:), b(:)
        integer, intent(out) :: c(:)
        integer :: i
        
        do i = 1, size(a)
            c(i) = a(i) + b(i)
        end do
    end subroutine add_arrays
    
    ! Displays array contents
    subroutine display_array(arr)
        integer, intent(in) :: arr(:)
        integer :: i
        
        do i = 1, size(arr)
            print *, arr(i)
        end do
    end subroutine display_array

end module array_operations

program refactored_demo
    use array_operations
    implicit none
    
    integer :: n = 100
    integer, allocatable :: a(:), b(:), c(:)
    
    call create_arrays(a, b, n)
    allocate(c(n))
    call add_arrays(a, b, c)
    call display_array(c)
    
    deallocate(a, b, c)
    
end program refactored_demo

Refactoring improves structure. Documentation explains code. Separation of concerns improves maintainability.

4. Maintainability

Maintainability ensures code can be modified over time.

Modularity separates code into independent parts. Readability makes code understandable. Consistency follows coding standards. Testing catches bugs early.

Code Example

module maintainable_module
    implicit none
    
    ! ---- CONSTANTS ----
    integer, parameter :: MAX_BIRDS = 100
    integer, parameter :: MAX_NAME = 50
    
    ! ---- DERIVED TYPES ----
    type :: Bird
        character(len=MAX_NAME) :: species
        integer :: count
        real :: weight
    end type Bird
    
    ! ---- PUBLIC INTERFACE ----
    public :: create_bird, update_count, bird_to_string
    public :: Bird, MAX_BIRDS
    
contains
    
    ! ---- CREATE NEW BIRD ----
    function create_bird(species, count, weight) result(bird)
        character(len=*), intent(in) :: species
        integer, intent(in) :: count
        real, intent(in) :: weight
        type(Bird) :: bird
        
        bird%species = trim(species)
        bird%count = count
        bird%weight = weight
    end function create_bird
    
    ! ---- UPDATE BIRD COUNT ----
    subroutine update_count(bird, delta)
        type(Bird), intent(inout) :: bird
        integer, intent(in) :: delta
        
        if (bird%count + delta >= 0) then
            bird%count = bird%count + delta
        else
            print *, "Error: Count would be negative"
        end if
    end subroutine update_count
    
    ! ---- CONVERT BIRD TO STRING ----
    function bird_to_string(bird) result(str)
        type(Bird), intent(in) :: bird
        character(len=100) :: str
        
        write(str, "(A, ', ', I0, ', ', F6.1)") &
            trim(bird%species), bird%count, bird%weight
    end function bird_to_string

end module maintainable_module

program maintainable_demo
    use maintainable_module
    implicit none
    
    type(Bird) :: sparrow, eagle
    
    ! ---- CREATE BIRDS ----
    sparrow = create_bird("Sparrow", 10, 25.5)
    eagle = create_bird("Eagle", 3, 4500.0)
    
    ! ---- UPDATE COUNTS ----
    call update_count(sparrow, 5)
    call update_count(eagle, -1)
    
    ! ---- DISPLAY ----
    print *, "--- Maintainable Code ---"
    print *, "Sparrow:", bird_to_string(sparrow)
    print *, "Eagle:", bird_to_string(eagle)
    
    print *, "--- Maintainability Principles ---"
    print *, "1. Modularity: Separate concerns"
    print *, "2. Readability: Clear names and structure"
    print *, "3. Consistency: Follow coding standards"
    print *, "4. Testing: Verify correctness"
    print *, "5. Documentation: Explain intent"
    
end program maintainable_demo

Maintainability uses modularity, readability, and consistency. Clear interfaces hide implementation. Constants reduce magic numbers.

Chapter 14: Practical Projects

1. Scientific Calculator

A scientific calculator implements mathematical functions.

Operations include sin, cos, exp, log, sqrt, pow. Input handles numbers and operators. Output formats results.

Code Example

program scientific_calculator
    implicit none
    
    real :: x, result
    character(len=20) :: operation
    logical :: running = .true.
    
    print *, "--- Scientific Calculator ---"
    print *, "Operations: sin, cos, tan, exp, log, sqrt, pow"
    print *, "Type 'quit' to exit"
    
    do while (running)
        print *, "Enter operation:"
        read(*, *) operation
        
        select case(trim(operation))
        case("quit", "exit")
            running = .false.
            print *, "Goodbye!"
            
        case("sin", "cos", "tan", "exp", "log", "sqrt")
            print *, "Enter value:"
            read(*, *) x
            
            select case(trim(operation))
            case("sin")
                result = sin(x)
            case("cos")
                result = cos(x)
            case("tan")
                result = tan(x)
            case("exp")
                result = exp(x)
            case("log")
                if (x > 0.0) then
                    result = log(x)
                else
                    print *, "Error: log only for positive values"
                    cycle
                end if
            case("sqrt")
                if (x >= 0.0) then
                    result = sqrt(x)
                else
                    print *, "Error: sqrt only for non-negative values"
                    cycle
                end if
            end select
            
            print *, "Result:", result
            
        case("pow")
            real :: y
            print *, "Enter base and exponent:"
            read(*, *) x, y
            result = x ** y
            print *, "Result:", result
            
        case default
            print *, "Unknown operation:", trim(operation)
        end select
        
    end do
    
end program scientific_calculator

The calculator accepts commands and values. It performs operations and displays results. Input validation prevents errors.

2. Projectile Motion Simulation

A projectile motion simulation models projectile trajectory.

Physics uses gravity and initial conditions. Integration steps through time. Output tracks position and velocity.

Code Example

program projectile_simulation
    implicit none
    
    real :: v0, theta, g = 9.8
    real :: x, y, vx, vy
    real :: t, dt = 0.01
    integer :: n_steps = 1000
    integer :: i
    
    print *, "--- Projectile Motion ---"
    print *, "Enter initial velocity (m/s):"
    read(*, *) v0
    
    print *, "Enter launch angle (degrees):"
    read(*, *) theta
    
    ! Convert to radians
    theta = theta * 3.14159 / 180.0
    
    ! Initial conditions
    vx = v0 * cos(theta)
    vy = v0 * sin(theta)
    x = 0.0
    y = 0.0
    t = 0.0
    
    print *, "Time, X, Y"
    
    do i = 1, n_steps
        ! Update velocity
        vy = vy - g * dt
        
        ! Update position
        x = x + vx * dt
        y = y + vy * dt
        
        t = t + dt
        
        ! Output
        if (mod(i, 10) == 0) then
            print *, t, x, y
        end if
        
        ! Stop if hits ground
        if (y <= 0.0) then
            y = 0.0
            print *, "Hit ground at:", t, "seconds"
            exit
        end if
    end do
    
    print *, "--- Simulation Complete ---"
    print *, "Range:", x, "meters"
    
end program projectile_simulation

The simulation calculates projectile motion. Euler integration steps through time. Output tracks position and velocity.

3. Linear Equation Solver

A linear equation solver finds solutions to systems of equations.

Gauss elimination solves linear systems. Back substitution finds unknowns. Pivoting improves stability.

Code Example

program linear_solver
    implicit none
    
    integer :: n = 3
    real :: A(3, 4) = reshape([ &
        2.0, 1.0, -1.0, 8.0, &
        -3.0, -1.0, 2.0, -11.0, &
        -2.0, 1.0, 2.0, -3.0], [3, 4], order=[2,1])
    
    real :: x(3)
    integer :: i, j, k
    
    print *, "--- Linear Equation Solver ---"
    print *, "System:"
    do i = 1, n
        print *, A(i, 1:4)
    end do
    
    ! ---- GAUSS ELIMINATION ----
    do k = 1, n-1
        do i = k+1, n
            real :: factor = A(i, k) / A(k, k)
            do j = k, n+1
                A(i, j) = A(i, j) - factor * A(k, j)
            end do
        end do
    end do
    
    ! ---- BACK SUBSTITUTION ----
    do i = n, 1, -1
        x(i) = A(i, n+1)
        do j = i+1, n
            x(i) = x(i) - A(i, j) * x(j)
        end do
        x(i) = x(i) / A(i, i)
    end do
    
    print *, "--- Solution ---"
    do i = 1, n
        print *, "x(", i, ") =", x(i)
    end do
    
    print *, "--- Verification ---"
    real :: check(3)
    check(1) = 2.0*x(1) + x(2) - x(3)
    check(2) = -3.0*x(1) - x(2) + 2.0*x(3)
    check(3) = -2.0*x(1) + x(2) + 2.0*x(3)
    print *, "Ax:", check
    
end program linear_solver

Gauss elimination transforms the matrix. Back substitution computes unknowns. Verification checks the solution.

4. Temperature Data Analysis

Temperature data analysis processes and analyzes temperature data.

Reading loads temperature data. Statistics compute mean, min, max. Analysis detects trends.

Code Example

program temperature_analysis
    implicit none
    
    integer, parameter :: MAX_SIZE = 1000
    real :: temps(MAX_SIZE)
    integer :: n = 0
    integer :: ios, i
    real :: sum, mean, min_val, max_val
    character(len=100) :: filename
    
    print *, "--- Temperature Data Analysis ---"
    print *, "Enter filename:"
    read(*, *) filename
    
    ! ---- READ DATA ----
    open(unit=10, file=filename, status="old", action="read", iostat=ios)
    
    if (ios /= 0) then
        print *, "Error opening file:", trim(filename)
        stop 1
    end if
    
    do
        read(10, *, iostat=ios) temps(n+1)
        if (ios /= 0) then
            exit
        end if
        n = n + 1
        if (n >= MAX_SIZE) then
            print *, "Warning: Maximum size reached"
            exit
        end if
    end do
    
    close(10)
    print *, "Read", n, "data points"
    
    ! ---- STATISTICS ----
    sum = sum(temps(1:n))
    mean = sum / real(n)
    min_val = minval(temps(1:n))
    max_val = maxval(temps(1:n))
    
    print *, "--- Statistics ---"
    print *, "Mean:", mean
    print *, "Min:", min_val
    print *, "Max:", max_val
    print *, "Range:", max_val - min_val
    
    ! ---- TEMPERATURE TREND ----
    print *, "--- Trend Analysis ---"
    real :: first_avg, last_avg
    
    if (n >= 20) then
        first_avg = sum(temps(1:10)) / 10.0
        last_avg = sum(temps(n-9:n)) / 10.0
        
        print *, "First 10 avg:", first_avg
        print *, "Last 10 avg:", last_avg
        
        if (last_avg > first_avg) then
            print *, "Trend: Increasing"
        else if (last_avg < first_avg) then
            print *, "Trend: Decreasing"
        else
            print *, "Trend: Stable"
        end if
    end if
    
    ! ---- FREQUENCY ANALYSIS ----
    print *, "--- Temperature Distribution ---"
    integer :: bins(10) = 0
    real :: min_t = floor(min_val)
    real :: max_t = ceil(max_val)
    real :: bin_size = (max_t - min_t) / 10.0
    
    do i = 1, n
        integer :: bin = int((temps(i) - min_t) / bin_size) + 1
        if (bin >= 1 .and. bin <= 10) then
            bins(bin) = bins(bin) + 1
        end if
    end do
    
    do i = 1, 10
        real :: lower = min_t + (i-1) * bin_size
        real :: upper = min_t + i * bin_size
        print *, "[", lower, "-", upper, "]:", bins(i)
    end do
    
    print *, "--- Analysis Complete ---"
    
end program temperature_analysis

The program reads temperature data. It computes statistics and detects trends. Frequency analysis shows distribution.

5. Matrix Computation Engine

A matrix computation engine performs matrix operations.

Operations include addition, multiplication, transpose. Memory management handles dynamic sizes. Performance uses array operations.

Code Example

module matrix_engine
    implicit none
    
    type :: Matrix
        integer :: rows, cols
        real, allocatable :: data(:, :)
    contains
        procedure :: add => matrix_add
        procedure :: multiply => matrix_multiply
        procedure :: transpose => matrix_transpose
        procedure :: display => matrix_display
    end type Matrix
    
    interface Matrix
        module procedure create_matrix
    end interface
    
contains
    
    function create_matrix(rows, cols) result(mat)
        integer, intent(in) :: rows, cols
        type(Matrix) :: mat
        
        mat%rows = rows
        mat%cols = cols
        allocate(mat%data(rows, cols))
        mat%data = 0.0
    end function create_matrix
    
    subroutine matrix_add(A, B, C)
        class(Matrix), intent(in) :: A, B
        type(Matrix), intent(out) :: C
        
        if (A%rows /= B%rows .or. A%cols /= B%cols) then
            print *, "Error: Matrix dimensions mismatch"
            return
        end if
        
        C = Matrix(A%rows, A%cols)
        C%data = A%data + B%data
    end subroutine matrix_add
    
    subroutine matrix_multiply(A, B, C)
        class(Matrix), intent(in) :: A, B
        type(Matrix), intent(out) :: C
        
        if (A%cols /= B%rows) then
            print *, "Error: Matrix dimensions incompatible"
            return
        end if
        
        C = Matrix(A%rows, B%cols)
        C%data = matmul(A%data, B%data)
    end subroutine matrix_multiply
    
    subroutine matrix_transpose(A, B)
        class(Matrix), intent(in) :: A
        type(Matrix), intent(out) :: B
        
        B = Matrix(A%cols, A%rows)
        B%data = transpose(A%data)
    end subroutine matrix_transpose
    
    subroutine matrix_display(M)
        class(Matrix), intent(in) :: M
        integer :: i
        
        do i = 1, M%rows
            print *, M%data(i, :)
        end do
    end subroutine matrix_display

end module matrix_engine

program matrix_demo
    use matrix_engine
    implicit none
    
    type(Matrix) :: A, B, C, D
    
    ! ---- CREATE MATRICES ----
    A = Matrix(3, 3)
    B = Matrix(3, 3)
    
    A%data = reshape([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0], [3, 3])
    B%data = reshape([9.0, 8.0, 7.0, 6.0, 5.0, 4.0, 3.0, 2.0, 1.0], [3, 3])
    
    ! ---- DISPLAY ----
    print *, "--- Matrix A ---"
    call A%display()
    print *, "--- Matrix B ---"
    call B%display()
    
    ! ---- ADDITION ----
    print *, "--- A + B ---"
    call A%add(B, C)
    call C%display()
    
    ! ---- MULTIPLICATION ----
    print *, "--- A * B ---"
    call A%multiply(B, C)
    call C%display()
    
    ! ---- TRANSPOSE ----
    print *, "--- A^T ---"
    call A%transpose(C)
    call C%display()
    
    print *, "--- Matrix Engine Complete ---"
    
end program matrix_demo

The Matrix type encapsulates operations. Methods handle addition, multiplication, transpose. Type-bound procedures provide an object-oriented interface.

Chapter 15: Competitive Programming with Fortran

1. Fast I/O Techniques

Fast I/O techniques handle large inputs efficiently.

Formatted I/O is slower but readable. Unformatted I/O is faster. Stream I/O is flexible. Buffering improves performance.

Code Example

program fast_io_demo
    implicit none
    
    integer :: n = 1000000
    integer :: i
    real, allocatable :: data(:)
    real :: start_time, end_time
    
    allocate(data(n))
    data = [(real(i), i = 1, n)]
    
    ! ---- SLOW FORMATTED I/O ----
    print *, "--- Formatted I/O (Slow) ---"
    call cpu_time(start_time)
    
    open(unit=10, file="formatted.txt", status="replace")
    do i = 1, n
        write(10, "(F10.3)") data(i)
    end do
    close(10)
    
    call cpu_time(end_time)
    print *, "Formatted write time:", end_time - start_time
    
    ! ---- FAST UNFORMATTED I/O ----
    print *, "--- Unformatted I/O (Fast) ---"
    call cpu_time(start_time)
    
    open(unit=10, file="unformatted.bin", form="unformatted", status="replace")
    write(10) data
    close(10)
    
    call cpu_time(end_time)
    print *, "Unformatted write time:", end_time - start_time
    
    ! ---- STREAM I/O ----
    print *, "--- Stream I/O ---"
    call cpu_time(start_time)
    
    open(unit=10, file="stream.bin", access="stream", form="unformatted", status="replace")
    write(10) data
    close(10)
    
    call cpu_time(end_time)
    print *, "Stream I/O time:", end_time - start_time
    
    ! ---- READING FAST ----
    print *, "--- Reading Fast ---"
    call cpu_time(start_time)
    
    open(unit=10, file="unformatted.bin", form="unformatted", status="old")
    read(10) data
    close(10)
    
    call cpu_time(end_time)
    print *, "Read time:", end_time - start_time
    
    ! ---- COMPARISON ----
    print *, "--- Comparison ---"
    print *, "Formatted I/O: Human readable, slow"
    print *, "Unformatted I/O: Fast, binary"
    print *, "Stream I/O: Flexible, fast"
    print *, "Use unformatted for large data"
    
    deallocate(data)
    
end program fast_io_demo

Formatted I/O is slow but readable. Unformatted I/O is fast but binary. Stream I/O offers flexibility.

2. Mathematical Algorithms and Dynamic Programming

Mathematical algorithms solve well-defined problems. Dynamic programming solves overlapping subproblems.

Mathematical algorithms include prime generation, GCD, LCM. Dynamic programming uses memoization or tabulation. Fibonacci and knapsack are classic DP problems.

Code Example

program algorithms_demo
    implicit none
    
    integer :: i, n
    
    ! ---- PRIME GENERATION ----
    print *, "--- Prime Numbers ---"
    do i = 1, 100
        if (is_prime(i)) then
            print *, i, "is prime"
        end if
    end do
    
    ! ---- GCD AND LCM ----
    print *, "--- GCD and LCM ---"
    print *, "GCD(48, 18):", gcd(48, 18)
    print *, "LCM(48, 18):", lcm(48, 18)
    
    ! ---- FIBONACCI (DP) ----
    print *, "--- Fibonacci (DP) ---"
    n = 10
    print *, "Fibonacci(", n, ") =", fibonacci_dp(n)
    
    ! ---- KNAPSACK (DP) ----
    print *, "--- Knapsack (DP) ---"
    integer :: weights(4) = [2, 3, 4, 5]
    integer :: values(4) = [3, 4, 5, 6]
    integer :: capacity = 5
    
    print *, "Max value:", knapsack(weights, values, capacity)
    
contains
    
    function is_prime(n) result(prime)
        integer, intent(in) :: n
        logical :: prime
        integer :: i
        
        if (n <= 1) then
            prime = .false.
            return
        end if
        
        prime = .true.
        do i = 2, int(sqrt(real(n)))
            if (mod(n, i) == 0) then
                prime = .false.
                return
            end if
        end do
    end function is_prime
    
    recursive function gcd(a, b) result(result_val)
        integer, intent(in) :: a, b
        integer :: result_val
        
        if (b == 0) then
            result_val = a
        else
            result_val = gcd(b, mod(a, b))
        end if
    end function gcd
    
    function lcm(a, b) result(result_val)
        integer, intent(in) :: a, b
        integer :: result_val
        result_val = a / gcd(a, b) * b
    end function lcm
    
    recursive function fibonacci_dp(n) result(result_val)
        integer, intent(in) :: n
        integer :: result_val
        integer :: memo(0:100)
        integer :: i
        
        ! Simple memoization
        memo = -1
        memo(0) = 0
        memo(1) = 1
        
        do i = 2, n
            memo(i) = memo(i-1) + memo(i-2)
        end do
        
        result_val = memo(n)
    end function fibonacci_dp
    
    function knapsack(weights, values, capacity) result(max_val)
        integer, intent(in) :: weights(:), values(:), capacity
        integer :: max_val
        integer :: n = size(weights)
        integer :: dp(0:size(weights), 0:capacity)
        integer :: i, w
        
        dp = 0
        
        do i = 1, n
            do w = 0, capacity
                if (weights(i) > w) then
                    dp(i, w) = dp(i-1, w)
                else
                    dp(i, w) = max(dp(i-1, w), dp(i-1, w-weights(i)) + values(i))
                end if
            end do
        end do
        
        max_val = dp(n, capacity)
    end function knapsack

end program algorithms_demo

Prime generation checks divisibility. GCD uses Euclid’s algorithm. Fibonacci uses DP memoization. Knapsack uses tabulation DP.

3. Graph Algorithms

Graph algorithms solve problems on graphs.

Dijkstra’s algorithm finds shortest paths. DFS traverses graphs. BFS explores level by level.

Code Example

program graph_algorithms
    implicit none
    
    integer, parameter :: INF = 99999
    integer :: n = 5
    integer :: graph(5, 5)
    
    ! Initialize graph
    graph = INF
    do i = 1, 5
        graph(i, i) = 0
    end do
    
    graph(1, 2) = 2
    graph(1, 3) = 4
    graph(2, 3) = 1
    graph(2, 4) = 7
    graph(3, 5) = 3
    graph(4, 5) = 1
    graph(4, 2) = 2
    
    print *, "--- Dijkstra's Algorithm ---"
    call dijkstra(graph, 1, n)
    
contains
    
    subroutine dijkstra(graph, start, n)
        integer, intent(in) :: graph(:,:), start, n
        integer :: dist(5)
        logical :: visited(5)
        integer :: i, j, min_dist, u
        
        dist = INF
        visited = .false.
        dist(start) = 0
        
        do i = 1, n
            ! Find unvisited vertex with minimum distance
            min_dist = INF
            u = -1
            
            do j = 1, n
                if (.not. visited(j) .and. dist(j) < min_dist) then
                    min_dist = dist(j)
                    u = j
                end if
            end do
            
            if (u == -1) then
                exit
            end if
            
            visited(u) = .true.
            
            ! Update distances
            do j = 1, n
                if (.not. visited(j) .and. graph(u, j) < INF) then
                    if (dist(u) + graph(u, j) < dist(j)) then
                        dist(j) = dist(u) + graph(u, j)
                    end if
                end if
            end do
        end do
        
        print *, "Shortest distances from node", start
        do i = 1, n
            if (dist(i) < INF) then
                print *, "Node", i, ":", dist(i)
            else
                print *, "Node", i, ": INF"
            end if
        end do
    end subroutine dijkstra

end program graph_algorithms

Dijkstra’s algorithm finds shortest paths. Visited tracks processed nodes. Distances are updated iteratively.

4. Optimization Techniques and Contest Strategies

Optimization techniques improve performance. Contest strategies help win competitions.

Time management allocates time wisely. Problem solving uses efficient approaches. Common traps include edge cases and performance pitfalls.

Code Example

program contest_strategies
    implicit none
    
    print *, "--- Contest Strategies ---"
    print *, "1. Read all problems first"
    print *, "2. Solve easiest first"
    print *, "3. Use efficient algorithms"
    print *, "4. Watch for edge cases"
    print *, "5. Test with examples"
    
    print *, "--- Optimization Techniques ---"
    print *, "1. Use fast I/O"
    print *, "2. Avoid unnecessary allocations"
    print *, "3. Use array operations"
    print *, "4. Precompute when possible"
    print *, "5. Use compiler optimizations"
    
    ! ---- EXAMPLE: EFFICIENT PRIME SUMMATION ----
    print *, "--- Efficient Algorithm Example ---"
    integer :: n = 100
    integer :: sum_primes = 0
    
    do i = 2, n
        if (is_prime_efficient(i)) then
            sum_primes = sum_primes + i
        end if
    end do
    
    print *, "Sum of primes up to", n, ":", sum_primes
    
contains
    
    function is_prime_efficient(n) result(prime)
        integer, intent(in) :: n
        logical :: prime
        integer :: i
        
        if (n <= 1) then
            prime = .false.
            return
        end if
        
        if (n <= 3) then
            prime = .true.
            return
        end if
        
        if (mod(n, 2) == 0 .or. mod(n, 3) == 0) then
            prime = .false.
            return
        end if
        
        i = 5
        do while (i * i <= n)
            if (mod(n, i) == 0 .or. mod(n, i + 2) == 0) then
                prime = .false.
                return
            end if
            i = i + 6
        end do
        
        prime = .true.
    end function is_prime_efficient

end program contest_strategies

Efficient algorithms reduce time. Prime checking uses 6k±1 optimization. Contest strategies manage time and focus.

Chapter 16: Production-Level Scientific Computing

1. HPC Architecture

HPC architecture covers hardware and software for large-scale computing.

Hardware includes CPUs, GPUs, memory, network. Memory hierarchy affects performance. Cache and NUMA are important. Parallel systems use multiple processors.

Code Example

program hpc_architecture
    implicit none
    
    integer :: i, n = 1000000
    real, allocatable :: a(:), b(:), c(:)
    real :: start_time, end_time
    
    allocate(a(n), b(n), c(n))
    a = 1.0
    b = 2.0
    
    ! ---- CACHE EFFECTS ----
    print *, "--- Cache Effects ---"
    call cpu_time(start_time)
    
    do i = 1, n
        c(i) = a(i) + b(i)
    end do
    
    call cpu_time(end_time)
    print *, "Cache-friendly:", end_time - start_time
    
    ! ---- NUMA AWARENESS ----
    print *, "--- NUMA Awareness ---"
    print *, "Use first-touch policy"
    print *, "Allocate on appropriate node"
    print *, "Use numactl for binding"
    
    ! ---- PERFORMANCE TIPS ----
    print *, "--- Performance Tips ---"
    print *, "1. Keep data in cache"
    print *, "2. Minimize memory access"
    print *, "3. Use contiguous memory"
    print *, "4. Avoid false sharing"
    print *, "5. Profile and optimize"
    
    ! ---- HARDWARE CONSIDERATIONS ----
    print *, "--- Hardware Considerations ---"
    print *, "CPU: Cores, frequency, cache"
    print *, "Memory: Bandwidth, latency"
    print *, "Network: Interconnect speed"
    print *, "Storage: I/O performance"
    
    deallocate(a, b, c)
    
end program hpc_architecture

Cache effects impact performance. NUMA affects memory access. Hardware considerations guide optimization.

2. Large Scientific Codebases and Legacy Modernization

Managing large scientific codebases and modernizing legacy code.

Legacy code is old but working. Modernization updates code for performance and maintainability. Challenges include compatibility and testing.

Code Example

! ---- LEGACY FORTRAN 77 CODE ----
      PROGRAM LEGACY
      IMPLICIT NONE
      INTEGER I, N
      PARAMETER (N=100)
      REAL A(N), B(N), C(N)
      
      DO 10 I = 1, N
          A(I) = REAL(I)
          B(I) = REAL(I) * 2.0
   10 CONTINUE
      
      DO 20 I = 1, N
          C(I) = A(I) + B(I)
   20 CONTINUE
      
      DO 30 I = 1, N
          WRITE(*,*) C(I)
   30 CONTINUE
      
      END

! ---- MODERNIZED CODE ----
module modern_operations
    implicit none
    
    public :: process_arrays
    
contains
    
    subroutine process_arrays(n)
        integer, intent(in) :: n
        real, allocatable :: a(:), b(:), c(:)
        integer :: i
        
        allocate(a(n), b(n), c(n))
        
        ! Array operations
        a = [(real(i), i = 1, n)]
        b = a * 2.0
        c = a + b
        
        ! Structured output
        do i = 1, n
            print *, c(i)
        end do
        
        deallocate(a, b, c)
    end subroutine process_arrays

end module modern_operations

program modernization_demo
    use modern_operations
    implicit none
    
    integer :: n = 100
    
    print *, "--- Modernization Example ---"
    call process_arrays(n)
    
    print *, "--- Modernization Benefits ---"
    print *, "1. Free-form syntax"
    print *, "2. Modules for encapsulation"
    print *, "3. Array operations"
    print *, "4. Allocatable arrays"
    print *, "5. Better maintainability"
    
end program modernization_demo

Legacy code uses fixed-form syntax. Modern code uses free-form syntax. Modules and array operations improve code.

3. Performance Engineering

Performance engineering systematically optimizes code.

Profiling identifies bottlenecks. Optimization improves performance. Scalability tests with more resources.

Code Example

module performance_engine
    implicit none
    
    integer, parameter :: dp = kind(0.0d0)
    
contains
    
    subroutine time_operation(operation, name)
        interface
            subroutine operation()
            end subroutine operation
        end interface
        
        character(len=*), intent(in) :: name
        real :: start_time, end_time
        
        call cpu_time(start_time)
        call operation()
        call cpu_time(end_time)
        
        print *, name, ":", end_time - start_time
    end subroutine time_operation

end module performance_engine

program performance_demo
    use performance_engine
    implicit none
    
    integer :: n = 1000000
    real, allocatable :: a(:), b(:), c(:)
    
    allocate(a(n), b(n), c(n))
    a = 1.0
    b = 2.0
    
    ! ---- TEST DIFFERENT APPROACHES ----
    print *, "--- Performance Testing ---"
    
    call time_operation(loop_version, "Loop version")
    call time_operation(array_version, "Array version")
    
    ! ---- LOOP VERSION ----
    subroutine loop_version
        integer :: i
        do i = 1, n
            c(i) = a(i) + b(i)
        end do
    end subroutine loop_version
    
    ! ---- ARRAY VERSION ----
    subroutine array_version
        c = a + b
    end subroutine array_version
    
    ! ---- PERFORMANCE ANALYSIS ----
    print *, "--- Performance Analysis ---"
    print *, "Loop version: Explicit, flexible"
    print *, "Array version: Concise, optimized"
    print *, "Use array operations for performance"
    
    ! ---- OPTIMIZATION TIPS ----
    print *, "--- Optimization Tips ---"
    print *, "1. Use compiler optimizations (-O2, -O3)"
    print *, "2. Profile to find bottlenecks"
    print *, "3. Optimize the critical path"
    print *, "4. Use appropriate data structures"
    print *, "5. Minimize I/O operations"
    
    deallocate(a, b, c)
    
end program performance_demo

Performance testing compares approaches. Array operations are often faster. Profiling identifies bottlenecks.

4. Research Software Development and Reproducible Computing

Research software development focuses on scientific discovery. Reproducible computing ensures results can be verified.

Research software must be correct and documented. Reproducibility requires sharing code and data. Best practices include version control and testing.

Code Example

program reproducible_demo
    implicit none
    
    ! ---- DOCUMENTED AND REPRODUCIBLE CODE ----
    print *, "--- Research Software Best Practices ---"
    print *, "1. Version control (Git)"
    print *, "2. Documentation"
    print *, "3. Testing"
    print *, "4. Reproducibility"
    print *, "5. Open access"
    
    ! ---- REPRODUCIBLE EXAMPLE ----
    print *, "--- Reproducible Simulation ---"
    integer :: seed = 12345
    integer :: n = 10
    real :: results(10)
    integer :: i
    
    ! Fixed seed for reproducibility
    call random_seed(put=[seed])
    
    do i = 1, n
        call random_number(results(i))
    end do
    
    print *, "Results (reproducible):", results
    
    ! ---- SHARING CODE ----
    print *, "--- Sharing Code ---"
    print *, "Include:"
    print *, "- Source code"
    print *, "- Build instructions"
    print *, "- Sample input/output"
    print *, "- Documentation"
    print *, "- License"
    
    print *, "--- Research Software Complete ---"
    
end program reproducible_demo

Reproducibility uses fixed seeds. Code sharing includes documentation. Best practices ensure quality.

Chapter 17: Career Readiness

1. Scientific Programming and HPC Engineering Careers

Scientific programming and HPC engineering careers involve developing software for scientific applications.

Roles include scientific programmer, HPC engineer, computational scientist. Skills include Fortran, parallel computing, and numerical methods. Career paths include academia, national labs, and industry.

Code Example

program career_readiness
    implicit none
    
    print *, "--- Scientific Programming Careers ---"
    print *, "Roles:"
    print *, "1. Scientific Programmer"
    print *, "2. HPC Engineer"
    print *, "3. Computational Scientist"
    print *, "4. Research Software Engineer"
    
    print *, "--- Skills Required ---"
    print *, "1. Fortran programming"
    print *, "2. Parallel computing (MPI, OpenMP)"
    print *, "3. Numerical methods"
    print *, "4. Performance optimization"
    print *, "5. Version control"
    print *, "6. Testing and debugging"
    
    print *, "--- Career Paths ---"
    print *, "Academia: Research positions"
    print *, "National Labs: Large-scale computing"
    print *, "Industry: Finance, engineering, weather"
    print *, "Startups: Scientific software"
    
    ! ---- EXAMPLE SKILLS ----
    print *, "--- Example Fortran Skills ---"
    print *, "1. Modern Fortran (2003+)"
    print *, "2. Array operations"
    print *, "3. Modules and encapsulation"
    print *, "4. C interoperability"
    print *, "5. Parallel programming"
    
end program career_readiness

Career readiness identifies required skills. Different paths offer various opportunities. Example skills demonstrate expertise.

2. Research Computing and Computational Science Paths

Research computing and computational science use computing for scientific discovery.

Research computing supports scientific research. Computational science uses simulations to solve problems. Paths include academia, national labs, and industry.

Code Example

program research_paths
    implicit none
    
    print *, "--- Research Computing Paths ---"
    print *, "Academic:"
    print *, "- Professor"
    print *, "- Postdoctoral researcher"
    print *, "- Graduate student"
    
    print *, "National Labs:"
    print *, "- Staff scientist"
    print *, "- HPC engineer"
    print *, "- Research associate"
    
    print *, "Industry:"
    print *, "- R&D engineer"
    print *, "- Data scientist"
    print *, "- Scientific software developer"
    
    ! ---- SKILLS FOR RESEARCH ----
    print *, "--- Skills for Research Computing ---"
    print *, "1. Strong numerical methods"
    print *, "2. High-performance computing"
    print *, "3. Scientific domain knowledge"
    print *, "4. Programming (Fortran, Python, C++)"
    print *, "5. Data analysis and visualization"
    print *, "6. Scientific writing"
    
    ! ---- SAMPLE RESEARCH CODE ----
    print *, "--- Sample Research Application ---"
    print *, "Climate modeling"
    print *, "Fluid dynamics"
    print *, "Quantum chemistry"
    print *, "Molecular dynamics"
    print *, "Astrophysics"
    
end program research_paths

Research paths vary by sector. Skills combine computing and science. Domain knowledge is essential.

3. Portfolio Development

A portfolio showcases skills and projects.

Projects demonstrate skills. Organization shows professionalism. README explains projects. Quality matters more than quantity.

Code Example

program portfolio_demo
    implicit none
    
    print *, "--- Fortran Portfolio ---"
    print *, "Sample Projects:"
    print *, "1. Numerical integration tool"
    print *, "2. Matrix computation library"
    print *, "3. Simulation framework"
    print *, "4. Parallel computing examples"
    print *, "5. Scientific data analysis"
    
    print *, "--- Project Structure ---"
    print *, "1. README.md: Project description"
    print *, "2. src/: Source code"
    print *, "3. test/: Test suite"
    print *, "4. doc/: Documentation"
    print *, "5. examples/: Example usage"
    print *, "6. build/: Build system"
    
    ! ---- README EXAMPLE ----
    print *, "--- README.md Example ---"
    print *, "# Numerical Integration Library"
    print *, "## Overview"
    print *, "A modern Fortran library for numerical integration."
    print *, "## Features"
    print *, "- Trapezoidal rule"
    print *, "- Simpson's rule"
    print *, "- Adaptive integration"
    print *, "## Requirements"
    print *, "- GFortran 10+"
    print *, "- OpenMP for parallel"
    
    print *, "--- Portfolio Tips ---"
    print *, "1. Show real projects"
    print *, "2. Write clear documentation"
    print *, "3. Include test coverage"
    print *, "4. Show performance results"
    print *, "5. Link to repositories"
    
end program portfolio_demo

Portfolios showcase skills. Projects should be complete and documented. README files explain projects.

4. AI-Augmented Scientific Development

AI can assist scientific software development.

AI tools help with code generation, optimization, and debugging. Best practices include validating AI output. Integration combines AI with human expertise.

Code Example

program ai_augmented
    implicit none
    
    print *, "--- AI-Augmented Development ---"
    print *, "AI can help with:"
    print *, "1. Code generation"
    print *, "2. Error explanation"
    print *, "3. Optimization suggestions"
    print *, "4. Documentation writing"
    print *, "5. Code translation"
    
    print *, "--- Best Practices ---"
    print *, "1. Validate AI output"
    print *, "2. Understand generated code"
    print *, "3. Test thoroughly"
    print *, "4. Use AI as assistant, not replacement"
    print *, "5. Document AI assistance"
    
    ! ---- EXAMPLE AI USAGE ----
    print *, "--- Example AI Prompts ---"
    print *, "1. 'Write a Fortran subroutine for matrix multiplication'"
    print *, "2. 'Explain this compiler error'"
    print *, "3. 'Optimize this loop for performance'"
    print *, "4. 'Convert this Fortran 77 code to modern Fortran'"
    
    print *, "--- AI Limitations ---"
    print *, "1. May produce incorrect code"
    print *, "2. May miss edge cases"
    print *, "3. May not understand context"
    print *, "4. Requires human verification"
    
end program ai_augmented

AI assists development but requires validation. Best practices ensure quality. Human expertise is essential.

Final Advice

Fortran feels different at first because it was designed for scientists and engineers, not general-purpose programming—but that focus is exactly what makes it such a powerful language for scientific computing. Every other language you learn after this one will feel less natural for numerical work, because you’ll already understand how to write high-performance, mathematically correct code.

Begin with the first stage today by running “Hello, World!” on your own computer through your terminal. Then break it on purpose—remove implicit none, misspell print, forget the end program—and read the resulting error message slowly and carefully instead of panicking.

That single habit, repeated consistently over weeks and months, is genuinely how every strong Fortran programmer built their foundation.

Use the AI prompts provided for each concept. They’re designed to give you code examples, clear explanations, and practical exercises. Copy them into your favorite AI assistant and work through the examples yourself. Write the code, run it, modify it, break it, and fix it.

Good luck, and welcome to Fortran. The journey is mathematically precise, but the destination is worth it.

Scroll to Top