We propose the third variation of the example code. In particular, this is a variation of the code in which arrays where used.
Indeed, we now read the number of the trapezoids from the command line, and we dynamically allocate the memory to store x_values and f_values (if you didn't read the previous examples, don't worry: you will easliy understand what these varaibles are):
program trapezoidal_integration
implicit none
real(8) :: a = 0.0d0 ! Lower limit of integration
real(8) :: b = 1.0d0 ! Upper limit of integration
real(8), allocatable :: x_values(:), f_values(:)
integer :: n, i
real(8) :: result
! Prompt the user for the number of trapezoids
print *, "Enter the number of trapezoids (n):"
read(*,*) n
! Check if n is valid
if (n <= 0) then
print *, "Error: The number of trapezoids must be a positive integer."
stop
end if
! Allocate memory for x_values and f_values arrays
allocate(x_values(n), f_values(n))
! Call the trapezoidal rule to perform the integration
result = trapezoidal_rule(a, b, n, x_values, f_values)
! Print the result of the integration
print *, "The integral of f(x) = x^3 from ", a, " to ", b, " is approximately: ", result
! Optionally, print out the x values and their corresponding f(x) values
print *, "x values and f(x) evaluations:"
do i = 1, n - 1
print '(I3, F10.5, F15.5)', i, x_values(i), f_values(i)
end do
! Free dynamically allocated memory
deallocate(x_values, f_values)
contains
! Function to define f(x) = x^3
real(8) function f(x)
real(8), intent(in) :: x
f = x**3
end function f
! Function to perform the trapezoidal rule
real(8) function trapezoidal_rule(a, b, n, x_values, f_values)
real(8), intent(in) :: a, b
integer, intent(in) :: n
real(8), intent(out) :: x_values(n), f_values(n)
real(8) :: p, sum
integer :: i
p = (b - a) / real(n) ! Width of each trapezoid
sum = 0.5d0 * (f(a) + f(b)) ! End points contribution
! Calculate x values and function evaluations
do i = 1, n - 1
x_values(i) = a + real(i) * p
f_values(i) = f(x_values(i)) ! Store function evaluation
sum = sum + f_values(i)
end do
trapezoidal_rule = sum * p ! Return the integral result
end function trapezoidal_rule
end program trapezoidal_integration
Let's look at the differences w.r.t. the code that used the arrays.
Read command line arguments: read()
In Fortran, the read() statement is used to read input from a source, typically the keyboard (standard input) or a file. It can be used in several forms to capture different types of input.
In our example:
read(*,*) n
the read(*,*) statement takes input for the integer n from the user. The * format specifier means free-form input, so the user can enter the values without needing to worry about specific formatting.
The general syntax is:
read(unit, format) variables
where
unit: Specifies the input source. The most common value is*, which represents standard input (usually the keyboard).format: Defines how the input is interpreted.*can be used for free-form input, which allows the compiler to automatically interpret the format.variables: The variables where the input values are stored.
Allocatable arrays
Allocatable arrays in Fortran are a powerful feature that allows for dynamic memory allocation, enabling the creation of arrays whose size can be determined at runtime. This flexibility is particularly useful when the dimensions of the array are not known at compile time or when working with large datasets that may vary in size.
To declare an allocatable array, the allocatable attribute is used in the array’s declaration. In our example:
real(8), allocatable :: x_values(:), f_values(:)
Memory for the array can then be allocated using the allocate statement, specifying the desired dimensions. In our example:
allocate(x_values(n), f_values(n))
Once the array is no longer needed, it can be deallocated using the deallocate statement, freeing up the memory and helping to prevent memory leaks.
deallocate(x_values, f_values)
[!Warning] Deallocating memory in Fortran is a crucial practice in managing dynamic memory usage effectively. When an allocatable array is created using the
allocatestatement, memory is reserved on the heap to store its elements. If this memory is not released using thedeallocatestatement when the array is no longer needed, it can lead to memory leaks. Memory leaks occur when allocated memory is not properly freed, resulting in wasted resources and potentially causing a program to consume more memory than necessary. Over time, this can degrade system performance, lead to unexpected behavior, and, in extreme cases, exhaust available memory, leading to program crashes. Therefore, always ensure that any dynamically allocated memory is properly deallocated to maintain efficient memory management and optimal program performance.
[!TIP] Automatic Deallocation on Exit: If the allocatable array goes out of scope when the subroutine exits, the memory will be automatically deallocated, but this behavior only applies if the array is declared as allocatable within that subroutine. If you use an allocatable array declared in a module or main program, it will remain allocated until explicitly deallocated.
[!TIP] If you attempt to allocate an already allocated array in Fortran, it will result in a runtime error. Specifically, the program will throw an error indicating that the array is already allocated. To avoid this issue, you should check whether the array is allocated before allocating it again:
if(.not.allocated(array))allocate(array)
Also multidimensional arrays can be allocated dynamically using the allocate statement. You can specify multiple dimensions when allocating, allowing you to create complex data structures such as matrices or tensors. For example, a two-dimensional array can be allocated with a syntax like: allocate(array(m, n)), where m and n are the sizes of the respective dimensions.