diff --git a/.gitignore b/.gitignore index 8b152582..56317407 100644 --- a/.gitignore +++ b/.gitignore @@ -54,3 +54,8 @@ renv/sandbox # OPTIONAL # Editors .vscode + +# Executables +a.out +exercises/*/solutions/* +!exercises/*/solutions/*.* diff --git a/config.yaml b/config.yaml index 0a64b1ab..f44e8c05 100644 --- a/config.yaml +++ b/config.yaml @@ -60,7 +60,10 @@ contact: 'd.theodorakis@metoffice.gov.uk' # Order of episodes in your lesson episodes: -- introduction.Rmd +- 02-variables.md +- 03-maths.md +- 04-logic.md +- 05-strings.md # Information for Learners learners: @@ -78,4 +81,3 @@ profiles: sandpaper: astroDimitrios/sandpaper pegboard: astroDimitrios/pegboard varnish: astroDimitrios/varnish - diff --git a/episodes/02-basics.Rmd b/episodes/02-basics.Rmd new file mode 100644 index 00000000..46bbdab4 --- /dev/null +++ b/episodes/02-basics.Rmd @@ -0,0 +1,343 @@ +--- +title: 'Basics' +teaching: 35 +exercises: 15 +--- + +::::::::::::::::::::::::::::::::::::: questions + +- How do we represent and manipulate data in Fortran? + +:::::::::::::::::::::::::::::::::::::::::::::::: + +::::::::::::::::::::::::::::::::::::: objectives + +- Understand the different intrinsic data types +- Understand the different intrinsic operators and functions available + +:::::::::::::::::::::::::::::::::::::::::::::::: + +## Fortran data types + +Fortran provides the following intrinsic data types: + +* numeric types + * `integer` + * `real` + * `complex` +* non-numeric types + * `logical` + * `character` + +### Defining numeric variables + +The following program declares a variable with each of the three intrinsic +numeric types, and provides an initial value in each case. +```fortran +program example1 + + integer :: i = 1 ! An integer + real :: a = 2.0 ! A floating point number + complex :: z = (0.0, 1.0) ! A complex number as (real-part, imag-part) + +end program example1 +``` +Initial values are optional, and these values may be changed later within the code. +If a declaration does not specify an initial value, the variable is said to be _undefined_. + +### Variable names + +The valid Fortran character set for names is `a-z`, `A-Z`, `0-9` and the +underscore `_`. Valid names must begin with a character. The maximum +length of a name is 63 characters (introduced in the F2003 standard) with +no spaces allowed. This includes names for programs, modules, subroutines, +and functions, as well as names for variables. + +### The `implicit` statement + +By default, variables in Fortran do not need to be declared a specific type, they +can just be used within the code. Variables with names beginning with letters `i-n` are implicitly +of type `integer`, while anything else is of type `real` (unless explicitly declared otherwise). + +**This is very bad practice and modern Fortran should not be used in this way.** + +The solution to prevent errors involving undeclared variables (usually arising from +typos) is to declare that no names have implicit type via the use of the +```fortran +implicit none +``` +statment at the very top of the code. + +With this statement inserted, all variable names must be declared explicitly before they +are referenced. It is still common to see variables beginning with `i-n` as integers. + +::::::::::::::::::::::::::::::::::::: challenge + +## Use `implicit none` + +Edit the above example to include an `implicit none` statment, and print out the three variables + +:::::::::::::::: solution + +```fortran +program solution1 + implicit none + + integer :: i = 1 ! An integer + real :: a = 2.0 ! A floating point number + complex :: z = (0.0, 1.0) ! A complex number as (real-part, imag-part) + + print *, i, a, z + +end program solution1 +``` +Compiling and running this code will give the following output +``` +$ ./a.out + 1 2.00000000 (0.00000000,1.00000000) +``` + +::::::::::::::::::::::::: +::::::::::::::::::::::::::::::::::::::::::::::: + +### Parameters + +In the above example, it is possible to change the values of initialised variables, e.g. +```fortran +program example2 + implicit none + + real :: a = 2.0 + + print *, a + + a = a + 5.0 + + print *, a + +end program example2 +``` +This will then give the output: +``` +$ ./a.out + 2.00000000 + 7.00000000 +``` +However, you can also define constant values that cannot change by defining variables using `parameter`. +We are then unable to modify `parameter` variables, e.g. +```fortran +program example3 + implicit none + + real, parameter :: pi = 3.14159265 + + pi = pi + 2.0 + + print *, pi + +end program example3 +``` +When compiling this program, this will give an error similar to the following from the `gfortran` compiler: +``` +example3.f90:6:3: + + 6 | pi = pi + 2.0 + | 1 +Error: Named constant 'pi' in variable definition context (assignment) at (1) +``` + +::::::::::::::::::::::::::::::::::::: challenge + +## Write a Fortran program to calculate the circumference of a circle + +Using the `parameter` statement, write a Fortran program to calculate the circumference of a circle of +radius 3.0. + +::::::::::::::::: hint + +The cicumference $c$ of a circle can be calculated from the radius $r$ and constant $\pi$ by + +$c = 2 \pi r$ + +:::::::::::::::::::::: + +:::::::::::::::: solution + +```fortran +program solution2 + implicit none + + real, parameter :: pi = 3.14159265 + + real :: r=3.0 ! radius of the circle + real :: c ! circumference of the circle + + c = 2.0 * pi * r + + print *, c + +end program solution2 +``` +Compiling and running this code will give the following output +``` +$ ./a.out + 18.8495560 +``` + +::::::::::::::::::::::::: +::::::::::::::::::::::::::::::::::::::::::::::: + +## Logical variables + +Fortran has a `logical` type that has two literal values, True and False, defined by +```fortran + logical :: switch0 = .false. + logical :: switch1 = .true. +``` + +### Logical operators and expressions + +Values can be tested logical operators `.or.`, `.and.` and `.not.` are available, and +these can be used to set the value of logical variables. + +The precedence is illustrated by, e.g., +```fortran + q = i .or. j .and. .not. k ! evaluated as i .or. (j .and. (.not. k)) +``` +where q, i, j, and k are all logical variables. + +The use of parentheses is highly recommended to avoid ambiguity and/or to add clarity. + +### Relational operators + +To form logical expressions from numeric or other expressions, we require +relational operators. The are two forms in Fortran, illustrated in the table +below. It is recommended that you avoid the older form. + +| Relation | Operator | Older form | For | +|--------------------------|----------|------------|------------------| +| Less than | `< ` | `.lt.` | `integer` `real` | +| Less than or equal to | `<=` | `.le.` | `integer` `real` | +| Greater than | `> ` | `.gt.` | `integer` `real` | +| Greater than or equal to | `>=` | `.ge.` | `integer` `real` | +| Equal to | `==` | `.eq.` | `integer` `real` `complex`| +| Not equal to | `/=` | `.neq.` | `integer` `real` `complex`| + +### Logical equivalence + +Equivalence between two logical expressions or variables is established +via the logical operators `.eqv.` and `.neqv.`. + +While some some compilers may allow the use of `==`, this should be avoided. + +### Using logical operators + +These operators can be used to check and set the values of logical variables, dependent on other variables, e.g. +```fortran +program example4 + implicit none + + real, parameter :: pi = 3.14159265 + logical, parameter :: switch1 = .true. + + real :: a=3.0 + logical :: test1, test2, test3 + + test1 = a >= pi ! True if a is greater than or equal to pi + + test2 = (.not. test1) ! True if test1 is False, False if test1 is True + + test3 = (test2 .eqv. switch1) ! True if test2 is True, False if test2 is False + + print *, test1 + + print *, test2 + + print *, test3 + +end program example4 +``` +Compiling and running this code will give the following output +``` +$ ./a.out + F + T + T +``` + +## Character variables + +Character variables are strings that hold zero or more characters. Some examples are: +```fortran +program example5 + implicit none + + character (len = *), parameter :: string1 = "don't" ! assumed length of 5 characters + character (len = 5) :: string2 = "Don""t" ! 5 characters + character (len = 6) :: string3 = ' don''t' ! blank + 5 characters + +end program example5 +``` +There should be a `len` specifier. Specifying `len = *` means that the length will be assumed +from the length of the string. A space is a valid character in a string. + +Strings must be defined within quotation marks, either single, `'`, or double, `"`, beginning +and ending with the same type of mark. To define a quotation mark within a string, either +use one of the alternative type, e.g. `"'"`, or use two of the same type, e.g. `''''`, which +will be intepreted as a single mark within the string. + +Strings may be concatenated with the string concatenation operator `//`. For example `string2` +and `string3` could be concatenated into a new variable `string4` by +```fortran + string4 = string2//string3 +``` +A single character, or a subset of characters, can be extracted via +use of an array-index like notation by defining the start and end characters +to extract, e.g. `string1(1:1)` would extract only the first character from the string +variable `string1`. + +::::::::::::::::::::::::::::::::::::: challenge + +## Working with strings + +Using the above `example5`, concatenate `string2` and `string3` into a new variable `string4`, +and print out the values of all strings. Also, print the third character of `string4`. + +:::::::::::::::: solution + +```fortran +program solution3 + implicit none + + character (len = *), parameter :: string1 = "don't" ! 5 characters + character (len = 5) :: string2 = "Don""t" ! 5 characters + character (len = 6) :: string3 = ' don''t' ! blank + 5 characters + character (len = 11) :: string4 ! length of string2+string3 + + string4 = string2//string3 + + print *, string1 + + print *, string2 + + print *, string3 + + print *, string4 + + print *, string4(3:3) + +end program solution3 +``` +Compiling and running this code will give the following output +``` +$ ./a.out + don't + Don"t + don't + Don"t don't + n +``` + +::::::::::::::::::::::::: +::::::::::::::::::::::::::::::::::::::::::::::: + diff --git a/episodes/02-variables.md b/episodes/02-variables.md new file mode 100644 index 00000000..b46c3594 --- /dev/null +++ b/episodes/02-variables.md @@ -0,0 +1,482 @@ +--- +title: 'Variables' +teaching: 15 +exercises: 15 +--- + +::::::::::::::::::::::::::::::::::::: questions + +- How do we declare and assign values to variables? + +:::::::::::::::::::::::::::::::::::::::::::::::: + +::::::::::::::::::::::::::::::::::::: objectives + +- Understand the different intrinsic data types. +- Declare and assign variables and parameters. + +:::::::::::::::::::::::::::::::::::::::::::::::: + +Variables store information we can use in our programs. +Fortran is a ***strongly typed*** language. +Each variable must be declared with a type.[^static] +Fortran provides the following intrinsic (built-in) data types for variables: + +- `integer`: A whole number which is positive, negative or zero. +- `real`: A real number includes the fractional part, + even if the fractional part is 0. +- `complex`: A number made up of a real and an imaginary part. +- `logical`: A boolean value which can be **.true.** or **.false.**. +- `character`: A single ASCII character. + Strings are made from sequences of characters. + +Variable names must be 63 characters or less (F2003 standard). +No spaces are allowed. +Names can contain the characters `a-z`, `A-Z`, `0-9` and the underscore `_`. +No spaces are allowed. +Names must begin with a letter. +This also applies to names for programs, modules, subroutines, +and functions, which you will learn about later in the course. + +Do **not** use Fortran keywords as variable names. +Fortran will let you overwrite these keywords if you're not careful. +The [Fortran wiki keywords page](https://fortranwiki.org/fortran/show/Keywords) +contains a list of all Fortran keywords. + +[^static]: Fortran is also ***statically typed***. + You can not change a variables type after the variable declaration. + +## Declaring Variables + +Fortran variables are declared with this syntax: + +```fortran + :: +``` + +So to declare an integer: + +```fortran +integer :: number_of_pelicans +``` + +For this episode complete challenges in: + +```bash +cd ~/Desktop/intro-to-modern-fortran/02-variables +``` + +:::: challenge + +## Create a `variables` program + +1. Create a new Fortran program named `variables.f90`. +2. Declare 5 variables. One of each of the 5 intrinsic data types. +3. Print your variables. +4. Compile and run your program. + +What do you notice about the output? + +::: solution + +```fortran +program variables + + implicit none + + integer :: number_of_pelicans + real :: pelican_weight + complex :: pelican_population_dynamics + logical :: is_young_pelican + character :: pelican_tag + + print *, number_of_pelicans + print *, pelican_weight + print *, pelican_population_dynamics + print *, is_young_pelican + print *, pelican_tag + +end program variables + +``` + +::: group-tab + +### GFortran + +```bash +gfortran -o variables variables.f90 +``` + +### Intel + +```bash +ifx -o variables variables.f90 +``` + +### Flang + +```bash +flang -o variables variables.f90 +``` + +### Cray + +```bash +ftn -o variables variables.f90 +``` + +::: + +```bash +./variables +``` + +The output below is from the GNU gfortran compiled executable: + +```output + -922534656 + 0.00000000 + (2.063298560E+11,0.00000000) + T +``` + +Where did those values come from? +If you forget to assign a value to a variable +the output will depend on your compiler. +Here the program accessed the memory allocated for each variable +and printed what was leftover in the memory from other processes. + +Most compilers have a flag which warns you +if there are uninitialised variables in your code. +Have a look and see if there is a flag like this for your compiler. + +::: +:::: + +::: spoiler + +### Style + +- Fortran is case-insensitive. + This course prefers the use of lowercase. +- Fortran file names must match the name of the program or module + contained in the file. + ie. `variables.f90` contains the program `variables`. + `matrix_mod.f90` contains the module `matrix_mod`. +- `::` markers should be aligned to improve readability. + +::: + +## Variable Assignment + +Variables are assigned using the assignment operator `=`: + +```fortran + = +``` + +For example: + +```fortran +number_of_pelicans = 5 +pelican_weight = 2.5 ! kg +pelican_population_dynamics = (-1.2e3, 0.9e2) ! Scientific notation, -1,200 etc +is_young_pelican = .false. +pelican_tag = 'Jeff' +``` + +Logicals can be `.true.` or `.false.`. +Characters (strings) such as `pelican_tag` can be surrounded +by single or double quotes. + +:::: challenge + +## Modify your `variables` program + +1. Assign values to the variables in your program. +2. Compile and run your program. + +::: solution + +```fortran +program variables + + implicit none + + integer :: number_of_pelicans + real :: pelican_weight + complex :: pelican_population_dynamics + logical :: is_young_pelican + character :: pelican_tag + + number_of_pelicans = 5 + pelican_weight = 2.5 ! kg + pelican_population_dynamics = (-1.2e3, 0.9e2) + is_young_pelican = .false. + pelican_tag = 'J' + + print *, number_of_pelicans + print *, pelican_weight + print *, pelican_population_dynamics + print *, is_young_pelican + print *, pelican_tag + +end program variables + +``` + +::: group-tab + +### GFortran + +```bash +gfortran -o variables variables.f90 +``` + +### Intel + +```bash +ifx -o variables variables.f90 +``` + +### Flang + +```bash +flang -o variables variables.f90 +``` + +### Cray + +```bash +ftn -o variables variables.f90 +``` + +::: + +```bash +./variables +``` + +Example output: + +```output + 5 + 2.50000000 + (-1.20000005,0.899999976) + F + J +``` + +::: +:::: + +::: spoiler + +### Style + +- The assignment operator `=` should be aligned to improve readability. +- Variable names are written in snake case and are verbose. +- Variables with units must have a comment (Ford or vanilla Fortran style) with the unit. + +::: + +::: caution + +### Assignment on declaration + +Fortran lets you assign a value to a variable when you declare it. + +```fortran +integer :: number_of_pelicans = 5 +``` + +This gives the variable the `save` attribute. +With `save` the variable will keep its value between procedure (function) calls. +This is not good practice. +Never assign a value to a variable on declaration unless it's a parameter. + +::: + +## Parameters + +In your program, it is possible to change the values of initialised variables, e.g. + +```fortran +program variables + + implicit none + + integer :: number_of_pelicans + real :: pelican_weight + complex :: pelican_population_dynamics + logical :: is_young_pelican + character :: pelican_tag + + number_of_pelicans = 5 + pelican_weight = 2.5 ! kg + pelican_population_dynamics = (-1.2e3, 0.9e2) + is_young_pelican = .false. + pelican_tag = 'J' + + ! print *, number_of_pelicans + ! print *, pelican_weight + ! print *, pelican_population_dynamics + ! print *, is_young_pelican + print *, pelican_tag + + ! Changing the value of the tag + pelican_tag = 'F' + print *, pelican_tag + +end program variables + +``` + +This will now give the output: + +```output + J + F +``` + +However, you can also define constant values that cannot change. +You do this by defining variables using `parameter`. +We are then unable to modify `parameter` variables, e.g. + +```fortran +character, parameter :: pelican_tag = 'J' +``` + +:::: challenge + +## Add a parameter to your `variables` program + +1. Modify a variable in your code to be a parameter. +2. Try modifying the parameter in your code. + What output do you get when compiling? + +::: solution + +```fortran +program variables + + implicit none + + integer :: number_of_pelicans + real :: pelican_weight + complex :: pelican_population_dynamics + logical :: is_young_pelican + + character, parameter :: pelican_tag = 'J' + + number_of_pelicans = 5 + pelican_weight = 2.5 ! kg + pelican_population_dynamics = (-1.2e3, 0.9e2) + is_young_pelican = .false. + ! pelican_tag = 'J' + + print *, number_of_pelicans + print *, pelican_weight + print *, pelican_population_dynamics + print *, is_young_pelican + print *, pelican_tag + + pelican_tag = 'F' + +end program variables + +``` + +Here we have modified `pelican_tag` to be the parameter. +Then at the end of the program we attempt to change its value. + +Example GFortran output: + +```output +variables.f90:23:4: + + 23 | pelican_tag = 'F' + | 1 +Error: Named constant ‘pelican_tag’ in variable definition context (assignment) at (1) +``` + +The compiler has given us an error. +This is because we are trying to edit the value, `variable definition context (assignment)`, of a parameter, `Named constant`. +The error is in the `variables.f90` file, +on line `23`, starting at character `4`. +This location has been marked as `1` in the compiler output. + +Different compilers will show different error messages. +Some have clearer messages for certain errors than others. +We recommend testing code with **at least** two compilers. +This will aid your debugging and help make your code more portable. + +::: +:::: + +:::: challenge + +## Tidy up your program + +1. Make sure your code conforms to the style +followed by this course. +2. Add Ford comments to document the program +and each variable. + +::: solution + +```fortran +program variables + !! A test program to lean how to declare and assign variables. + + implicit none + + integer :: number_of_pelicans + !! The number of pelicans in the pod + real :: pelican_weight + !! The average weight of a pelican in the pod / kg + complex :: pelican_population_dynamics + !! The birth and death rate as a complex number + !! Units are the number of pelicans per year + logical :: is_young_pelican + !! Test to see if the current pelican is young + + character, parameter :: pelican_tag = 'J' + !! Pelican pod tracking tag + + number_of_pelicans = 5 + pelican_weight = 2.5 ! kg + pelican_population_dynamics = (-1.2e3, 0.9e2) ! births, deaths per year + is_young_pelican = .false. + + print *, number_of_pelicans + print *, pelican_weight + print *, pelican_population_dynamics + print *, is_young_pelican + print *, pelican_tag + +end program variables + +``` + +Notice we have left an extra blank line in-between the parameter +declaration and the other variable declarations. +This is so we didn't have to align all the `::` markers far to the right. +If you are declaring lots of variables, +break up the declarations into sections for readability. + +::: +:::: + +:::::::::::::::::::::::::::::::::::::::: keypoints + +- There are 5 intrinsic data types for Fortran variables: `integer`, `real`, `complex`, `logical`, and `character`. +- Fortran variables are declared with the syntax: ` :: ` +- Assign a value to a variable with the syntax: ` = ` +- Never assign a value on the same line as a variable is declared. + This gives the variable the `save` attribute. +- Parameters are variables whose value can't be changed: + `, parameter :: = `. + +:::::::::::::::::::::::::::::::::::::::::::::::::: diff --git a/episodes/03-maths.md b/episodes/03-maths.md new file mode 100644 index 00000000..8c95314c --- /dev/null +++ b/episodes/03-maths.md @@ -0,0 +1,608 @@ +--- +title: 'Maths' +teaching: 15 +exercises: 15 +--- + +::::::::::::::::::::::::::::::::::::: questions + +- How do we perform calculations in Fortran? + +:::::::::::::::::::::::::::::::::::::::::::::::: + +::::::::::::::::::::::::::::::::::::: objectives + +- Use the built in operators and intrinsic functions. +- Specify the precision of our numeric variables. + +:::::::::::::::::::::::::::::::::::::::::::::::: + +In this episode we will look at arithmetic with Fortran. +Arithmetic operations are where Fortran shines. +Fortran is much faster than Python, Matlab and +other interpreted languages. +Good Fortran code will be comparable in speed, +perhaps faster, than equivalent C/C++ code +depending on your compiler and optimisations. +To provide a good starting point for this episode +complete the challenge below. + +For this episode complete challenges in: + +```bash +cd ~/Desktop/intro-to-modern-fortran/03-maths +``` + +:::: challenge + +## Create a `maths` Fortran program + +1. Create a new Fortran program `maths.f90`. +2. Define a real parameter `pi`. +3. Print the value of `pi`. +4. Check your program compiles and runs. + +::: solution + +```fortran +program maths + !! Test program to demonstrate Fortran arithmetic + + implicit none + + real, parameter :: pi = 3.141592654 + !! Value of pi + + print *, 'Pi = ', pi + +end program maths + +``` + +Notice the print statement outputs the string +`'Pi = '` before printing the value of `pi` on the same line. + +::: group-tab + +### GFortran + +```bash +gfortran -o maths maths.f90 +``` + +### Intel + +```bash +ifx -o maths maths.f90 +``` + +### Flang + +```bash +flang -o maths maths.f90 +``` + +### Cray + +```bash +ftn -o maths maths.f90 +``` + +::: + +```bash +./maths +``` + +Example output: + +```output + 3.14159274 +``` + +This is slightly different to the value we coded. +We will discuss why in the +[`kinds` section](./02-basics-maths.md#kinds) of this episode. + +::: +:::: + +## Operators & Intrinsics + +The usual operators are available in Fortran: + +Operator | Description +---------|------------ +`**` | Exponent +`*` | Multiplication +`/` | Division +`+` | Addition +`-` | Subtraction + +They are listed in order of precedence. +Fortran also has a number of +[intrinsic maths functions](https://fortran-lang.org/learn/intrinsics/math/). + +:::: challenge + +## Calculate the area of a circle with radius 5 cm + +1. Add two new real variables for the `radius` and `area` to your program. +2. Print the value of `radius` and `area`. +3. Calculate the area of the circle using $\pi r^2$. +4. Check your program compiles and runs. + +::: solution + +```fortran +program maths + !! Test program to demonstrate Fortran arithmetic + + implicit none + + real, parameter :: pi = 3.141592654 + !! Value of pi + + real :: radius + !! Radius of the circle in cm + real :: area + !! Area of the circle in cm + + radius = 5.0 ! cm + area = pi * radius**2 + + print *, 'Pi = ', pi + print *, 'Radius = ', radius, ' cm' + print *, 'Area = ', area, ' cm^2' + +end program maths + +``` + +::: group-tab + +### GFortran + +```bash +gfortran -o maths maths.f90 +``` + +### Intel + +```bash +ifx -o maths maths.f90 +``` + +### Flang + +```bash +flang -o maths maths.f90 +``` + +### Cray + +```bash +ftn -o maths maths.f90 +``` + +::: + +```bash +./maths +``` + +Example output: + +```output + Pi = 3.14159274 + Radius = 5.00000000 cm + Area = 78.5398178 cm^2 +``` + +::: +:::: + +## Kinds + +Numeric types such as `integer`, `real`, +and `complex` can have different floating-point precisions. +This is commonly 32 or 64-bit precision. +We can specify the precision using **kind** parameters. +There are two common ways to specify **kind** parameters: + +::: tab + +### Using `iso_fortran_env` + +We will be using `iso_fortran_env` throughout this lesson. +`iso_fortran_env` is an intrinsic Fortran module. +It was introduced in the F2008 standard. + +```fortran +! This goes after the program statement +! We will cover modules in a later episode +use, intrinsic :: iso_fortran_env, only: r_32 => real32, r_64 => real64 +``` + +These parameters are then used when declaring variables: + +```fortran +real(kind=r_32), parameter :: earth_radius = 6371_r_32 ! km - single precision +real(kind=r_64) :: current_distance_from_sun ! AU - double precision +``` + +and when assigning values to variables: + +```fortran +current_distance_from_sun = 1.3_r_64 ! AU +``` + +### Using intrinsic functions + +```fortran +! hardware specific 32 bit real +integer, parameter :: r_32 = selected_real_kind(6, 37) +! hardware specific 64 bit real +integer, parameter :: r_64 = selected_real_kind(15, 307) +``` + +These parameters are then used when declaring variables: + +```fortran +real(kind=r_32), parameter :: earth_radius = 6371_r_32 ! km +real(kind=r_64) :: current_distance_from_sun ! AU +``` + +and when assigning values to variables: + +```fortran +current_distance_from_sun = 1.3_r_64 +``` + +::: + +::: caution + +### Fortran defaults to single precision + +Fortran differs from other languages. +The default precision for reals is single. + +```fortran +use, intrinsic :: iso_fortran_env, only: r_64 => real64 + +real(kind=r_64) :: current_distance_from_sun ! AU + +current_distance_from_sun = 1.3 ! no kind suffix - this is single precision +current_distance_from_sun = 1.3_r_64 ! double precision +``` + +**Always** use a kind suffix for real and integer types. + +::: + +::: spoiler + +### Style + +You can omit the `kind=`: + +```fortran +real(kind=r_64) +! is the same as +real(r_64) +``` + +In this lesson we prefer explicitly stating `kind=`. + +::: + +::: spoiler + +### Legacy + +You might see variables declared as: + +```fortran +15d00 ! 15.00 +9.375d-11 ! 9.375E-11 +``` + +The `d` here specifies the reals as double precision. + +You might also see variables with kinds which are plain integers: + +```fortran +real(8) +``` + +Some information on this older style is available in the +[gfortran docs](https://gcc.gnu.org/onlinedocs/gfortran/KIND-Type-Parameters.html). + +::: + +:::: challenge + +## Specify the precision in your program + +Update your `maths.f90` program to specify the real kind as `real64`. +Note the output **before** and **after** you modify the precision. +How has the output changed? + +::: solution + +```fortran +program maths + !! Test program to demonstrate Fortran arithmetic + + use, intrinsic :: iso_fortran_env, only: r_64 => real64 + + implicit none + + real(kind=r_64), parameter :: pi = 3.141592654_r_64 + !! Value of pi + + real(kind=r_64) :: radius + !! Radius of the circle in cm + real(kind=r_64) :: area + !! Area of the circle in cm + + ! this float must be written as 5.0 (sometimes seen as 5.) + ! not 5 on its own without the decimal point + radius = 5.0_r_64 ! cm + area = pi * radius**2 + + print *, 'Pi = ', pi + print *, 'Radius = ', radius, ' cm' + print *, 'Area = ', area, ' cm^2' + +end program maths + +``` + +Example output before (32 bit single precision): + +```output +Pi = 3.14159274 +Radius = 5.00000000 cm +Area = 78.5398178 cm^2 +``` + +Example output after (64 bit double precision): + +```output +Pi = 3.1415926540000001 +Radius = 5.0000000000000000 cm +Area = 78.539816349999995 cm^2 +``` + +The value of pi now accurately reflects the value you coded in the program. +The value of the area is also now more accurate. + +::: +:::: + +:::: challenge + +## Lennard-Jones Potential + +Create a new program to calculate the +[Lennard-Jones Potential](https://chem.libretexts.org/Bookshelves/Physical_and_Theoretical_Chemistry_Textbook_Maps/Supplemental_Modules_(Physical_and_Theoretical_Chemistry)/Physical_Properties_of_Matter/Atomic_and_Molecular_Properties/Intermolecular_Forces/Specific_Interactions/Lennard-Jones_Potential) +for two Xenon atoms. +They are separated by 4.0 Angstroms. +Approximate the potential using: + +$$V(r)=4\epsilon\left[\left(\frac{\sigma}{r}\right)^{12}-\left(\frac{\sigma}{r}\right)^{6}\right]$$ + +where: + +- $\epsilon=0.997\ kJ/mol$ +- $\sigma=3.40\ Angstroms$ +- $r=4.0\ Angstroms$ + +Print the value at the end of your program. + +::: solution + +In the file `lennard_jones_potential.f90`: + +```fortran +program lennard_jones_potential + !! Calculates the Lennard-Jones Potential for 2 Xenon atoms + + use, intrinsic :: iso_fortran_env, only: i_64 => int64, r_64 => real64 + + implicit none + + real(kind=r_64), parameter :: epsilon = 0.997_r_64 ! kJ/mol + !! well depth kJ/mol + real(kind=r_64), parameter :: sigma = 3.40_r_64 ! Angstroms + !! van der Waals radius Angstroms + integer(kind=i_64), parameter :: lj_potential_const = 4_i_64 + !! unit-less Lennard-Jones Potential constant + + real(kind=r_64) :: separation_distance + !! separation distance r in Angstroms + real(kind=r_64) :: lj_potential + !! Lennard-Jones Potential kJ/mol + + separation_distance = 4.0_r_64 ! Angstroms + + ! Calculate the Lennard-Jones Potential using: + ! V(r) = 4*epsilon*[(sigma/r)**12 - (sigma/r)**6] + lj_potential = lj_potential_const * epsilon * & + ((sigma/separation_distance)**12 & + - (sigma/separation_distance)**6) + + print *, 'V(4.0 Angstrom) = ', lj_potential, ' kJ/mol' + +end program lennard_jones_potential + +``` + +Note: + +- We couldn't name the `lj_potential` variable `lennard_jones_potential` + since that's the program name. + Try changing it to be the same as the program name. + What compiler error do you get? +- We've used verbose names for all variables. + This is good practice since using single character maths related + variable names makes code harder to read. + This does mean that the formula on one line + would break the 80 character line limit in our style guide. + To get around this the formula has been split over multiple lines + using the continuation marker `&`. +- The constant `lj_potential_const` has been defined as a variable. + This avoids [magic numbers](https://en.wikipedia.org/wiki/Magic_number_(programming)) in our code. + +::: +:::: + +### Type Casting & Mixed Precision + +Take a look at the calculation of `lj_potential` (from the challenge above). +What kinds are each of the variables in the equation? + +```fortran +lj_potential = lj_potential_const * epsilon * & + ((sigma/separation_distance)**12 & + - (sigma/separation_distance)**6) +``` + +The value we are calculating is a 64 bit real (`lj_potential`). +The first variable in the equation is a 64 bit integer (`lj_potential_const`). +The compiler will ***cast*** the integer +to a 64 bit real before using it in the multiplication. + +In this program the casting only occurs once. +Doing this casting many times can slow a program down. +To remove this implicit casting we can: + +- cast `lj_potential_const` to a real explicitly before + the calculation. + + ```fortran + lj_potential_const_real = real(lj_potential_const, r_64) + ``` + + We would also have to define the new real variable `lj_potential_const_real`. +- declare the constant as a real to start with. + This is the simplest solution which avoids casting. + + ```fortran + real(kind=r_64), parameter :: lj_potential_const = 4.0_r_64 + ``` + +This program uses mixed type in its arithmetic (reals and integers). +Some programs have mixed precision (kinds) as well. +i.e. 32 bit integers in an equation with 64 bit integers. +In this case the 32 bit integers are promoted to 64 bit +by the compiler before use. +It is best to avoid implicit conversions like this +by being consistent with your precision. + +:::: challenge + +## Remove Casting + +1. Find your compiler documentation. + Is there a flag that warns you about implicit conversions / kind casting? + If there is, compile your program with the flag. + What warning do you get? +2. Modify your solution to the last challenge to remove + any type/kind casting. + +::: solution + +1. For gfortran there are two flags we can use. + These are taken from the + [gfortran options documentation](https://gcc.gnu.org/onlinedocs/gfortran/Error-and-Warning-Options.html): + + ```shell + $ gfortran -o ljp lennard_jones_potential.f90 -Wconversion -Wconversion-extra + ``` + + Note that in the solution code to the last challenge + gfortran will still **not** provide a warning. + The parameter was declared as an integer + and used in arithmetic with reals (mixed type). + So why is there no warning about conversion? + + Because the variable is a parameter the compiler is performing + optimisations under the hood that remove the type casting. + Remove the parameter keyword and the following warning appears: + + ```output + lennard_jones_potential.f90:24:35: + + 24 | lj_potential = lj_potential_const * epsilon * & + | 1 + Warning: Conversion from INTEGER(8) to REAL(8) at (1) [-Wconversion-extra] + ``` + + So turning on compiler warnings can be useful + but are no substitute for thorough code and science review. + A later episode goes deeper into compiler usage and debugging. + +2. Example modified code with no type/kind casting: + +```fortran +program lennard_jones_potential + !! Calculates the Lennard-Jones Potential for 2 Xenon atoms + + use, intrinsic :: iso_fortran_env, only: r_64 => real64 + + implicit none + + real(kind=r_64), parameter :: epsilon = 0.997_r_64 ! kJ/mol + !! well depth kJ/mol + real(kind=r_64), parameter :: sigma = 3.40_r_64 ! Angstroms + !! van der Waals radius Angstroms + real(kind=r_64), parameter :: lj_potential_const = 4_r_64 + !! unit-less Lennard-Jones Potential constant + + real(kind=r_64) :: separation_distance + !! separation distance r in Angstroms + real(kind=r_64) :: lj_potential + !! Lennard-Jones Potential kJ/mol + + separation_distance = 4.0_r_64 ! Angstroms + + ! Calculate the Lennard-Jones Potential using: + ! V(r) = 4*epsilon*[(sigma/r)**12 - (sigma/r)**6] + lj_potential = lj_potential_const * epsilon * & + ((sigma/separation_distance)**12 & + - (sigma/separation_distance)**6) + + print *, 'V(4.0 Angstrom) = ', lj_potential, ' kJ/mol' + +end program lennard_jones_potential + +``` + +The exponents in the equation `12` and `6` +have been left as integers. +They have no kind suffix. +This means they are the compilers default integer kind. +Using an integer exponent where possible +is faster than using a real exponent. + +::: +:::: + +:::::::::::::::::::::::::::::::::::::::: keypoints + +- Operators in order of precedance: `**`, `*`, `/`, `+`, and `-`. +- List of [intrinsic maths functions](https://fortran-lang.org/learn/intrinsics/math/). +- A numeric variables **kind** specifies its floating-point precision. + 32-bit, 64-bit etc. +- Always specify a kind when defining and assigning values to variables. + Otherwise Fortran will default to the compilers single precision. +- Avoid mixing precision and kinds + (e.g. integers with reals, or 32-bit with 64-bit). + The compiler will implicitly convert the lower precision value + to a higher precision value. + This can slow down your programs. + +:::::::::::::::::::::::::::::::::::::::::::::::::: diff --git a/episodes/04-logic.md b/episodes/04-logic.md new file mode 100644 index 00000000..308f2a1b --- /dev/null +++ b/episodes/04-logic.md @@ -0,0 +1,101 @@ +--- +title: 'Logic' +teaching: 15 +exercises: 15 +--- + +::::::::::::::::::::::::::::::::::::: questions + +- How do we declare and assign values to variables? + +:::::::::::::::::::::::::::::::::::::::::::::::: + +::::::::::::::::::::::::::::::::::::: objectives + +- Understand the different intrinsic data types. +- Declare and assign variables and parameters. + +:::::::::::::::::::::::::::::::::::::::::::::::: + +Fortran's `logical` type has two values: + +```fortran + logical :: is_below_20_metres = .false. + logical :: use_stochastic_physics = .true. +``` + +### Logical operators + +Values can be tested logical operators `.or.`, `.and.` and `.not.` are available, and +these can be used to set the value of logical variables. + +The precedence is illustrated by, e.g., +```fortran + q = i .or. j .and. .not. k ! evaluated as i .or. (j .and. (.not. k)) +``` +where q, i, j, and k are all logical variables. + +Use brackets to avoid confusion over operator precedence. + +### Relational operators + +To form logical expressions from numeric or other expressions, we require +relational operators. The are two forms in Fortran, illustrated in the table +below. It is recommended that you avoid the older form. + +| Relation | Operator | Older form | For | +|--------------------------|----------|------------|------------------| +| Less than | `< ` | `.lt.` | `integer` `real` | +| Less than or equal to | `<=` | `.le.` | `integer` `real` | +| Greater than | `> ` | `.gt.` | `integer` `real` | +| Greater than or equal to | `>=` | `.ge.` | `integer` `real` | +| Equal to | `==` | `.eq.` | `integer` `real` `complex`| +| Not equal to | `/=` | `.neq.` | `integer` `real` `complex`| + +### Logical equivalence + +Equivalence between two logical expressions or variables is established +via the logical operators `.eqv.` and `.neqv.`. + +While some some compilers may allow the use of `==`, this should be avoided. + +### Using logical operators + +These operators can be used to check and set the values of logical variables, dependent on other variables, e.g. +```fortran +program example4 + implicit none + + real, parameter :: pi = 3.14159265 + logical, parameter :: switch1 = .true. + + real :: a=3.0 + logical :: test1, test2, test3 + + test1 = a >= pi ! True if a is greater than or equal to pi + + test2 = (.not. test1) ! True if test1 is False, False if test1 is True + + test3 = (test2 .eqv. switch1) ! True if test2 is True, False if test2 is False + + print *, test1 + + print *, test2 + + print *, test3 + +end program example4 +``` +Compiling and running this code will give the following output +``` +$ ./a.out + F + T + T +``` + +:::::::::::::::::::::::::::::::::::::::: keypoints + +- Th + +:::::::::::::::::::::::::::::::::::::::::::::::::: diff --git a/episodes/05-strings.md b/episodes/05-strings.md new file mode 100644 index 00000000..4f73e9d4 --- /dev/null +++ b/episodes/05-strings.md @@ -0,0 +1,26 @@ +--- +title: 'Strings' +teaching: 15 +exercises: 15 +--- + +::::::::::::::::::::::::::::::::::::: questions + +- How do we declare and assign values to variables? + +:::::::::::::::::::::::::::::::::::::::::::::::: + +::::::::::::::::::::::::::::::::::::: objectives + +- Understand the different intrinsic data types. +- Declare and assign variables and parameters. + +:::::::::::::::::::::::::::::::::::::::::::::::: + +Variables store information we can use in our + +:::::::::::::::::::::::::::::::::::::::: keypoints + +- Th + +:::::::::::::::::::::::::::::::::::::::::::::::::: diff --git a/episodes/data/exercises.tar.gz b/episodes/data/exercises.tar.gz index 1c2b985d..d6ab22c2 100644 Binary files a/episodes/data/exercises.tar.gz and b/episodes/data/exercises.tar.gz differ diff --git a/exercises/02-variables/README.md b/exercises/02-variables/README.md new file mode 100644 index 00000000..539d10c6 --- /dev/null +++ b/exercises/02-variables/README.md @@ -0,0 +1,28 @@ +# Challenges + +## Create a `variables` program + +1. Create a new Fortran program named `variables.f90`. +2. Declare 5 variables. One of each of the 5 intrinsic data types. +3. Print your variables. +4. Compile and run your program. + +What do you notice about the output? + +## Modify your `variables` program + +1. Assign values to the variables in your program. +2. Compile and run your program. + +## Add a parameter to your `variables` program + +1. Modify a variable in your code to be a parameter. +2. Try modifying the parameter in your code. + What output do you get when compiling? + +## Tidy up your program + +1. Make sure your code conforms to the style +followed by this course. +2. Add Ford comments to document the program +and each variable. diff --git a/exercises/02-variables/solutions/variables.f90 b/exercises/02-variables/solutions/variables.f90 new file mode 100644 index 00000000..9fe51f94 --- /dev/null +++ b/exercises/02-variables/solutions/variables.f90 @@ -0,0 +1,35 @@ +!-------------------------------------------------------- +! Carpentries - Introduction to Modern Fortran +! CC BY 4.0, https://creativecommons.org/licenses/by/4.0/ +!-------------------------------------------------------- +! +program variables + !! A test program to lean how to declare and assign variables. + + implicit none + + integer :: number_of_pelicans + !! The number of pelicans in the pod + real :: pelican_weight + !! The average weight of a pelican in the pod / kg + complex :: pelican_population_dynamics + !! The birth and death rate as a complex number + !! Units are the number of pelicans per year + logical :: is_young_pelican + !! Test to see if the current pelican is young + + character, parameter :: pelican_tag = 'J' + !! Pelican pod tracking tag + + number_of_pelicans = 5 + pelican_weight = 2.5 ! kg + pelican_population_dynamics = (-1.2e3, 0.9e2) ! births, deaths per year + is_young_pelican = .false. + + print *, number_of_pelicans + print *, pelican_weight + print *, pelican_population_dynamics + print *, is_young_pelican + print *, pelican_tag + +end program variables diff --git a/exercises/03-maths/README.md b/exercises/03-maths/README.md new file mode 100644 index 00000000..84a91f40 --- /dev/null +++ b/exercises/03-maths/README.md @@ -0,0 +1,48 @@ +# Challenges + +## Create a `maths` Fortran program + +1. Create a new Fortran program `maths.f90`. +2. Define a real parameter `pi`. +3. Print the value of `pi`. +4. Check your program compiles and runs. + +## Calculate the area of a circle with radius 5 cm + +1. Add two new real variables for the `radius` and `area` to your program. +2. Print the value of `radius` and `area`. +3. Calculate the area of the circle using $\pi r^2$. +4. Check your program compiles and runs. + +## Specify the precision in your program + +Update your `maths.f90` program to specify the real kind as `real64`. +Note the output **before** and **after** you modify the precision. +How has the output changed? + +## Lennard-Jones Potential + +Create a new program to calculate the +[Lennard-Jones Potential](https://chem.libretexts.org/Bookshelves/Physical_and_Theoretical_Chemistry_Textbook_Maps/Supplemental_Modules_(Physical_and_Theoretical_Chemistry)/Physical_Properties_of_Matter/Atomic_and_Molecular_Properties/Intermolecular_Forces/Specific_Interactions/Lennard-Jones_Potential) +for two Xenon atoms. +They are separated by 4.0 Angstroms. +Approximate the potential using: + +$$V(r)=4\epsilon\left[\left(\frac{\sigma}{r}\right)^{12}-\left(\frac{\sigma}{r}\right)^{6}\right]$$ + +where: + +- $\epsilon=0.997\ kJ/mol$ +- $\sigma=3.40\ Angstroms$ +- $r=4.0\ Angstroms$ + +Print the value at the end of your program. + +## Remove Casting + +1. Find your compiler documentation. + Is there a flag that warns you about implicit conversions / kind casting? + If there is, compile your program with the flag. + What warning do you get? +2. Modify your solution to the last challenge to remove + any kind casting. diff --git a/exercises/03-maths/solutions/lennard_jones_potential.f90 b/exercises/03-maths/solutions/lennard_jones_potential.f90 new file mode 100644 index 00000000..de958743 --- /dev/null +++ b/exercises/03-maths/solutions/lennard_jones_potential.f90 @@ -0,0 +1,35 @@ +!-------------------------------------------------------- +! Carpentries - Introduction to Modern Fortran +! CC BY 4.0, https://creativecommons.org/licenses/by/4.0/ +!-------------------------------------------------------- +! +program lennard_jones_potential + !! Calculates the Lennard-Jones Potential for 2 Xenon atoms + + use, intrinsic :: iso_fortran_env, only: r_64 => real64 + + implicit none + + real(kind=r_64), parameter :: epsilon = 0.997_r_64 ! kJ/mol + !! well depth kJ/mol + real(kind=r_64), parameter :: sigma = 3.40_r_64 ! Angstroms + !! van der Waals radius Angstroms + real(kind=r_64), parameter :: lj_potential_const = 4.0_r_64 + !! unit-less Lennard-Jones Potential constant + + real(kind=r_64) :: separation_distance + !! separation distance r in Angstroms + real(kind=r_64) :: lj_potential + !! Lennard-Jones Potential kJ/mol + + separation_distance = 4.0_r_64 ! Angstroms + + ! Calculate the Lennard-Jones Potential using: + ! V(r) = 4*epsilon*[(sigma/r)**12 - (sigma/r)**6] + lj_potential = lj_potential_const * epsilon * & + ((sigma/separation_distance)**12 & + - (sigma/separation_distance)**6) + + print *, 'V(4.0 Angstrom) = ', lj_potential, ' kJ/mol' + +end program lennard_jones_potential diff --git a/exercises/03-maths/solutions/maths.f90 b/exercises/03-maths/solutions/maths.f90 new file mode 100644 index 00000000..14169c6b --- /dev/null +++ b/exercises/03-maths/solutions/maths.f90 @@ -0,0 +1,28 @@ +!-------------------------------------------------------- +! Carpentries - Introduction to Modern Fortran +! CC BY 4.0, https://creativecommons.org/licenses/by/4.0/ +!-------------------------------------------------------- +! +program maths + !! Test program to demonstrate Fortran arithmetic + + use, intrinsic :: iso_fortran_env, only: r_64 => real64 + + implicit none + + real(kind=r_64), parameter :: pi = 3.141592654_r_64 + !! Value of pi + + real(kind=r_64) :: radius + !! Radius of the circle in cm + real(kind=r_64) :: area + !! Area of the circle in cm + + radius = 5.0_r_64 ! cm + area = pi * radius**2 + + print *, 'Pi = ', pi + print *, 'Radius = ', radius, ' cm' + print *, 'Area = ', area, ' cm^2' + +end program maths diff --git a/learners/discuss.md b/learners/discuss.md new file mode 100644 index 00000000..3f0a8aba --- /dev/null +++ b/learners/discuss.md @@ -0,0 +1,20 @@ +--- +title: Discussion +--- + +## Frequently Asked Questions + +People often have questions about Fortran beyond the scope of the core material. +Students who have completed episodes early +might find value in looking through the following topics. + +Note that since this material isn't essential for most Fortran usage, +it won't be covered by the instructor. + +## Floating point precision + +There is a great article +[Examples of floating point problems](https://jvns.ca/blog/2023/01/13/examples-of-floating-point-problems/) +by Julia Evans. +The article outlines what floating points numbers are +and common problems with precision.