Fortran
Fortran is a high-level language that was created primarily to support scientific calculations, numerical analysis, and computational tasks. It is recognized for its execution speed, ability to perform complex mathematical operations efficiently, and extensive use in applications involving large-scale computation. Despite being one of the older programming languages, Fortran has continued to develop and remains widely used in scientific research, engineering, simulation, and high-performance computing.
This section introduces the major concepts of Fortran, covering its syntax, variables, data types, arrays, decision-making and looping structures, functions, and subroutines. It also discusses the language’s historical evolution, key advantages, limitations, and practical uses, helping explain its continued role in developing software for scientific and computational environments.

Introduction To Fortran
a
- Complete Fortran Programming Learning Roadmap
- Chapter 1: Why Fortran Still Matters in 2026
- What This Guide Covers
- What Is Fortran, Really?
- History of Fortran
- Features of Fortran
- Applications of Fortran
- Fortran vs Other Languages
- Software Development Process in Fortran
- Career Relevance of Fortran
- Dependencies and Prerequisites
- Common Beginner Mistakes
- Common Intermediate-Level Mistakes
- Role of AI in Fortran Development
- Chapter 2: Complete Setup Guide for Windows, Mac, and Linux
- Chapter 3: Program Units and Basic Syntax
- Chapter 4: Input and Output
- Chapter 5: Control Structures
- Chapter 6: Functions and Subroutines
- Chapter 7: Arrays and Strings
- Chapter 8: Derived Data Types and Pointers
- Chapter 9: Modules and Advanced Program Structure
- Chapter 10: Modern Fortran Features
- Chapter 11: Scientific Computing with Fortran
- Chapter 12: Parallel and High-Performance Fortran
- Chapter 13: Software Engineering with Fortran
- Chapter 14: Practical Projects
- Chapter 15: Competitive Programming with Fortran
- Chapter 16: Production-Level Scientific Computing
- Chapter 17: Career Readiness
- Final Advice
- Chapter 1: Why Fortran Still Matters in 2026
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.