Understanding Fortran Array Indexing in OpenACC

What You’ll Learn Today

Fortran counts differently than some other programming languages, and this matters A LOT when working with OpenACC! It’s like the difference between floor numbers in American buildings (1st floor, 2nd floor) versus British buildings (ground floor, 1st floor).

The Big Difference: Starting from 1

Fortran Way (starts from 1):

Array positions: [1] [2] [3] [4] [5]
Array values:    [10][20][30][40][50]

C/Python Way (starts from 0):

Array positions: [0] [1] [2] [3] [4]
Array values:    [10][20][30][40][50]

Think of it like house numbers on a street – some streets start numbering from 1, others from 0!

Why This Matters for OpenACC

When you write parallel loops, you need to get the indexing right, or you’ll access the wrong memory locations:

! CORRECT Fortran way
do i = 1, n
  array(i) = i * 2
end do

! WRONG (this would miss the first element!)
do i = 0, n-1
  array(i+1) = i * 2  ! Confusing and error-prone
end do

Visual: Array Bounds in Parallel Regions

Fortran Array Declaration: real :: numbers(100)

Memory Layout:
┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┐
│ (1) │ (2) │ (3) │ (4) │ ... │ (98)│ (99)│(100)│
└─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘

Valid indices: 1, 2, 3, ..., 98, 99, 100
Invalid:       0, 101, 102, ... (will cause errors!)

Custom Array Bounds

Fortran lets you define custom starting and ending points:

! Array from 0 to 10
real :: temperatures(0:10)

! Array from -5 to 5  
real :: displacement(-5:5)

! Array from 1990 to 2024 (great for years!)
real :: yearly_data(1990:2024)

This is like numbering floors in a building: basement (-1), ground (0), first (1), etc.

Boundary Conditions in Parallel Loops

Always be careful with loop boundaries:

! Safe parallel loop
!$acc parallel loop
do i = 1, n
  if (i <= n) then  ! Extra safety check
    array(i) = array(i) * 2
  end if
end do
!$acc end parallel loop

Index Mapping Between Different Systems

When working with data from other sources:

Data from C program (0-based):     [0][1][2][3][4]
Transfer to Fortran (1-based):     [1][2][3][4][5]

Mapping: fortran_index = c_index + 1

Common Indexing Mistakes to Avoid

❌ Off-by-one errors:

do i = 0, n  ! Wrong! Should be 1, n

❌ Accessing beyond bounds:

do i = 1, n+1  ! Wrong! Goes beyond array size

❌ Forgetting custom bounds:

real :: data(-10:10)
do i = 1, 10  ! Wrong! Should be -10, 10

Visual: Multi-dimensional Array Indexing

2D Array: matrix(3,4)

     Column: 1    2    3    4
Row 1:     (1,1)(1,2)(1,3)(1,4)
Row 2:     (2,1)(2,2)(2,3)(2,4)  
Row 3:     (3,1)(3,2)(3,3)(3,4)

Valid ranges: i = 1 to 3, j = 1 to 4

Key Points to Remember

  • Fortran arrays start from index 1 (not 0)
  • Always use 1 to n in your parallel loops (unless custom bounds)
  • Check array bounds carefully – going outside causes crashes
  • Custom bounds are allowed – use them when they make sense
  • Be extra careful when converting from other languages

Example Code

Let us consider the following OpenACC code –

program proper_indexing
  ! This program demonstrates correct Fortran array indexing with OpenACC
  
  implicit none
  
  ! Standard 1-based arrays
  integer, parameter :: n = 20
  real :: data(n)              ! Indices: 1, 2, 3, ..., 20
  real :: results(n)           ! Indices: 1, 2, 3, ..., 20
  integer :: i
  
  ! Initialize with proper 1-based indexing
  write(*,*) 'Initializing arrays with 1-based indexing...'
  do i = 1, n
    data(i) = real(i) * 3.14159  ! Each element = i * π
  end do
  
  ! Parallel computation with correct bounds
  write(*,*) 'Running parallel loop with proper indexing...'
  !$acc parallel loop
  do i = 1, n  ! CORRECT: starts from 1, ends at n
    results(i) = data(i) * data(i)  ! Square each element
  end do
  !$acc end parallel loop
  
  ! Display results showing proper indexing
  write(*,*) 'Results (showing 1-based indexing):'
  do i = 1, 10  ! Show first 10 elements
    write(*,'(A,I0,A,F8.3,A,F10.3)') 'Element [', i, ']: ', &
           data(i), ' → ', results(i)
  end do
  
  ! Verify bounds are correct
  write(*,*) ''
  write(*,'(A,I0)') 'Array size: ', n
  write(*,'(A,I0,A,I0)') 'Valid indices: ', 1, ' to ', n
  write(*,'(A,F8.3)') 'First element data(1) = ', data(1)
  write(*,'(A,F8.3)') 'Last element data(' // trim(str(n)) // ') = ', data(n)
  
contains
  
  function str(num) result(string)
    integer, intent(in) :: num
    character(len=10) :: string
    write(string, '(I0)') num
  end function str
  
end program proper_indexing

To compile this code –

nvfortran -acc -o proper_indexing proper_indexing.f90

To execute this code –

./proper_indexing

Sample output –

 Initializing arrays with 1-based indexing...
 Running parallel loop with proper indexing...
 Results (showing 1-based indexing):
Element [1]:    3.142 →      9.870
Element [2]:    6.283 →     39.478
Element [3]:    9.425 →     88.826
Element [4]:   12.566 →    157.913
Element [5]:   15.708 →    246.740
Element [6]:   18.850 →    355.305
Element [7]:   21.991 →    483.610
Element [8]:   25.133 →    631.654
Element [9]:   28.274 →    799.437
Element [10]:   31.416 →    986.959
 
Array size: 20
Valid indices: 1 to 20
First element data(1) =    3.142
Last element data(20) =   62.832

Let us consider the another OpenACC code –

program custom_bounds
  ! This program shows how to use custom array bounds with OpenACC
  
  implicit none
  
  ! Arrays with custom bounds
  real :: temperatures(-10:10)     ! From -10°C to +10°C
  real :: yearly_sales(2020:2024)  ! Years 2020 to 2024
  real :: height_data(0:100)       ! Heights from 0cm to 100cm
  integer :: i, year
  
  write(*,*) 'Working with custom array bounds in OpenACC!'
  write(*,*) ''
  
  ! Example 1: Temperature data with negative indices
  write(*,*) '1. Temperature array (indices -10 to +10):'
  !$acc parallel loop
  do i = -10, 10
    temperatures(i) = real(i) * 1.5  ! Temperature formula
  end do
  !$acc end parallel loop
  
  ! Show some temperature results
  do i = -5, 5, 2
    write(*,'(A,I0,A,F6.2,A)') 'Temperature at index ', i, ': ', &
           temperatures(i), '°C'
  end do
  
  write(*,*) ''
  
  ! Example 2: Yearly sales data
  write(*,*) '2. Yearly sales (indices 2020 to 2024):'
  !$acc parallel loop
  do year = 2020, 2024
    yearly_sales(year) = real(year - 2019) * 1000.0  ! Growing sales
  end do
  !$acc end parallel loop
  
  ! Show sales results
  do year = 2020, 2024
    write(*,'(A,I0,A,F8.0)') 'Sales in ', year, ': $', yearly_sales(year)
  end do
  
  write(*,*) ''
  
  ! Example 3: Height data starting from 0
  write(*,*) '3. Height data (indices 0 to 100):'
  !$acc parallel loop
  do i = 0, 100
    height_data(i) = sqrt(real(i)) * 10.0  ! Some height formula
  end do
  !$acc end parallel loop
  
  ! Show some height results
  write(*,*) 'Sample heights:'
  do i = 0, 100, 20
    write(*,'(A,I0,A,F6.2,A)') 'Height[', i, ']: ', height_data(i), ' cm'
  end do
  
  write(*,*) ''
  write(*,*) 'Key lesson: Always use the CORRECT bounds in your loops!'
  write(*,*) 'Array bounds:        Loop bounds:'
  write(*,*) 'temperatures(-10:10) → do i = -10, 10'
  write(*,*) 'yearly_sales(2020:2024) → do year = 2020, 2024'  
  write(*,*) 'height_data(0:100)   → do i = 0, 100'
  
end program custom_bounds

To compile this code –

nvfortran -acc -o custom_bounds custom_bounds.f90  

To execute this code –

./custom_bounds

Sample output –

 Working with custom array bounds in OpenACC!
 
 1. Temperature array (indices -10 to +10):
Temperature at index -5:  -7.50°C
Temperature at index -3:  -4.50°C
Temperature at index -1:  -1.50°C
Temperature at index 1:   1.50°C
Temperature at index 3:   4.50°C
Temperature at index 5:   7.50°C
 
 2. Yearly sales (indices 2020 to 2024):
Sales in 2020: $   1000.
Sales in 2021: $   2000.
Sales in 2022: $   3000.
Sales in 2023: $   4000.
Sales in 2024: $   5000.
 
 3. Height data (indices 0 to 100):
 Sample heights:
Height[0]:   0.00 cm
Height[20]:  44.72 cm
Height[40]:  63.25 cm
Height[60]:  77.46 cm
Height[80]:  89.44 cm
Height[100]: 100.00 cm
 
 Key lesson: Always use the CORRECT bounds in your loops!
 Array bounds:        Loop bounds:
 temperatures(-10:10) → do i = -10, 10
 yearly_sales(2020:2024) → do year = 2020, 2024
 height_data(0:100)   → do i = 0, 100

Let us consider the another OpenACC code –

program common_mistakes
  ! This program shows common indexing mistakes and how to fix them
  
  implicit none
  
  integer, parameter :: n = 10
  real :: array(n)
  real :: safe_result(n)
  integer :: i
  
  ! Initialize array properly
  do i = 1, n
    array(i) = real(i) * 2.0
  end do
  
  write(*,*) 'Original array:'
  do i = 1, n
    write(*,'(A,I0,A,F6.1)') 'array(', i, ') = ', array(i)
  end do
  write(*,*) ''
  
  ! CORRECT WAY: Proper 1-based indexing
  write(*,*) 'CORRECT: Using proper 1-based indexing'
  !$acc parallel loop
  do i = 1, n  ! ✓ Correct bounds
    safe_result(i) = array(i) + 10.0
  end do
  !$acc end parallel loop
  
  write(*,*) 'Results with correct indexing:'
  do i = 1, 5
    write(*,'(A,I0,A,F6.1)') 'safe_result(', i, ') = ', safe_result(i)
  end do
  
  write(*,*) ''
  write(*,*) '=========================================='
  write(*,*) 'COMMON MISTAKES TO AVOID:'
  write(*,*) '=========================================='
  write(*,*) ''
  
  ! Show what NOT to do (we won't actually run these dangerous loops)
  write(*,*) 'MISTAKE 1: Starting from 0 (like C/Python)'
  write(*,*) 'WRONG CODE: do i = 0, n-1'
  write(*,*) '  - This would try to access array(0) which does not exist!'
  write(*,*) '  - In Fortran, array(0) is INVALID for array(1:n)'
  write(*,*) ''
  
  write(*,*) 'MISTAKE 2: Going beyond array bounds'
  write(*,*) 'WRONG CODE: do i = 1, n+1'
  write(*,*) '  - This would try to access array(11) when array only has 10 elements'
  write(*,*) '  - This causes memory access errors!'
  write(*,*) ''
  
  write(*,*) 'MISTAKE 3: Off-by-one errors'
  write(*,*) 'WRONG CODE: do i = 0, n'
  write(*,*) '  - This tries to access both array(0) and array(11)'
  write(*,*) '  - Both are invalid for our array(1:10)!'
  write(*,*) ''
  
  ! Demonstrate bounds checking
  write(*,*) 'SAFETY TIP: Always check your bounds!'
  write(*,*) 'For array(n), valid indices are: 1, 2, 3, ..., n'
  write(*,'(A,I0,A,I0,A,I0)') 'Our array(', n, ') has valid indices: 1 to ', n
  
  write(*,*) ''
  write(*,*) 'REMEMBER: Fortran arrays start from 1, not 0!'
  
end program common_mistakes

To compile this code –

nvfortran -acc -o common_mistakes common_mistakes.f90

To execute this code –

./common_mistakes

Sample output –

 Original array:
array(1) =    2.0
array(2) =    4.0
array(3) =    6.0
array(4) =    8.0
array(5) =   10.0
array(6) =   12.0
array(7) =   14.0
array(8) =   16.0
array(9) =   18.0
array(10) =   20.0
 
 CORRECT: Using proper 1-based indexing
 Results with correct indexing:
safe_result(1) =   12.0
safe_result(2) =   14.0
safe_result(3) =   16.0
safe_result(4) =   18.0
safe_result(5) =   20.0
 
 ==========================================
 COMMON MISTAKES TO AVOID:
 ==========================================
 
 MISTAKE 1: Starting from 0 (like C/Python)
 WRONG CODE: do i = 0, n-1
   - This would try to access array(0) which does not exist!
   - In Fortran, array(0) is INVALID for array(1:n)
 
 MISTAKE 2: Going beyond array bounds
 WRONG CODE: do i = 1, n+1
   - This would try to access array(11) when array only has 10 elements
   - This causes memory access errors!
 
 MISTAKE 3: Off-by-one errors
 WRONG CODE: do i = 0, n
   - This tries to access both array(0) and array(11)
   - Both are invalid for our array(1:10)!
 
 SAFETY TIP: Always check your bounds!
 For array(n), valid indices are: 1, 2, 3, ..., n
Our array(10) has valid indices: 1 to 10
 
 REMEMBER: Fortran arrays start from 1, not 0!

Click here to go back to OpenACC Fortran tutorials page.

References