Tool to translate from loops to array operations

The Pure-Fortran project now has a program xarray.py that transforms code with loops to use array operations when possible. For example,

program xloop
implicit none
integer :: i, nseed
integer, allocatable :: seed(:)
integer, parameter :: n = 5
real :: x(n), sum_positive, xmin, xmax, xmean
xmean = 0.5
! set seed
call random_seed(size=nseed)
allocate(seed(nseed))
do i=1,nseed
   seed(i) = i * 10**6
end do
call random_seed(put=seed)
deallocate(seed)
! set x to de-meaned random uniform 
do i=1,n
   call random_number(x(i))
   x(i) = x(i) - xmean
end do
! compute minval, maxval
do i=1,n
   if (i >= 2) then
      xmin = min(x(i), xmin)
      xmax = max(x(i), xmax)
   else
      xmin = x(i)
      xmax = x(i)
   end if
end do
! print x
do i=1,n
   print "(i2,f7.3)", i, x(i)
end do
! compute sum of positive values
sum_positive = 0.0
do i=1,n
   if (x(i) > 0) sum_positive = sum_positive + x(i)
end do
print*,sum_positive
print*,xmin,xmax
end program xloop

is transformed (including the positioning of comments) to

program xloop
implicit none
integer :: i, nseed
integer, parameter :: n = 5
real :: x(n)
! set seed
call random_seed(size=nseed)
call random_seed(put=[(i * 10**6, i=1,nseed)])
! set x to de-meaned random uniform 
call random_number(x)
x = x - 0.5
! print x
print "(i2,f7.3)", (i, x(i), i=1,n)
! compute sum of positive values
print*,sum(x, mask = x > 0)
! compute minval, maxval
print*,minval(x),maxval(x)
end program xloop

by the command python xarray.py xloop.f90 --out temp.f90 --run-diff --inline which gave output

5 array-operation replacement candidate(s).
xloop.f90:11-13 constructor_fill_loop program xloop
xloop.f90:17-20 random_number_fill_elementwise program xloop
xloop.f90:22-30 reduction_minmax_value_pair program xloop
xloop.f90:32-34 io_loop_implied_do program xloop
xloop.f90:36-39 reduction_masked_sum program xloop

Fixed xloop.f90: replaced 5 block(s), wrote temp.f90
  removed redundant allocate-before-assignment: 1

--fix summary: files changed 1, replaced 5, allocates removed 1
Build (original): gfortran xloop.f90 -o xloop_orig.exe
Build (original): PASS
Run (original): xloop_orig.exe
Run (original): PASS
 1  0.382
 2  0.034
 3  0.011
 4  0.080
 5 -0.343
  0.507028103    
 -0.343274832      0.381796479
Build (transformed): gfortran temp.f90 -o temp.exe
Build (transformed): PASS
Run (transformed): temp.exe
Run (transformed): PASS
 1  0.382
 2  0.034
 3  0.011
 4  0.080
 5 -0.343
  0.507028103    
 -0.343274832      0.381796479
Run diff: MATCH

The --inline option tells it to write code that when possible uses expressions directly instead of saving them in variables. I created it by giving Codex examples of loop-based code I would like it to transform. After it could do that I tested xarray.py with code from John Burkardt, Lapack, and other sources to find bugs. There is a --concurrent option to use do concurrent when possible. It will improve as it is tested against more code, so please try it and report issues.

6 Likes

The project now has a program xto_loop.py that converts code with array operations to loops, for a few reasons:

  • create test cases for xarray.py to see if it can recover the original code that uses array operations
  • demonstrate what array functions do
  • enable timing comparisons of array-based and loop-based code

python xto_loop.py on the 2nd code above gives

! created by xto_loop.py from xxarray.f90 on 2026-02-19 10:34
program xloop
implicit none
integer :: i, nseed
integer, parameter :: n = 5
real :: x(n)
! set seed
call random_seed(size=nseed)
! call random_seed(put=[(i * 10**6, i=1,nseed)])  !! replaced by xto_loop.py
block
   integer, allocatable :: put_tmp(:)
   allocate(put_tmp(nseed))
   do i = 1, nseed
      put_tmp(i) = i * 10**6
   end do
   call random_seed(put=put_tmp)
   if (allocated(put_tmp)) deallocate(put_tmp)
end block
! set x to de-meaned random uniform 
! call random_number(x)  !! replaced by xto_loop.py
block
   integer :: i_
   do i_ = 1, n
      call random_number(x(i_))
   end do
end block
! x = x - 0.5  !! replaced by xto_loop.py
block
   integer :: i_
   do i_ = 1, n
      x(i_) = x(i_) - 0.5
   end do
end block
! print x
! print "(i2,f7.3)", (i, x(i), i=1,n)  !! replaced by xto_loop.py
do i = 1, n
   print "(i2,f7.3)", i, x(i)
end do
! compute sum of positive values
! print*,sum(x, mask = x > 0)  !! replaced by xto_loop.py
block
   integer :: i_
   real :: sum_x_mask_x_gt_0
   sum_x_mask_x_gt_0 = 0.0
   do i_ = 1, n
      if (x(i_) > 0) sum_x_mask_x_gt_0 = sum_x_mask_x_gt_0 + x(i_)
   end do
   print *, sum_x_mask_x_gt_0
end block
! compute minval, maxval
! print*,minval(x),maxval(x)  !! replaced by xto_loop.py
block
   integer :: i_
   real :: minval_x
   real :: maxval_x
   minval_x = x(1)
   do i_ = 2, n
      if (x(i_) < minval_x) minval_x = x(i_)
   end do
   maxval_x = x(1)
   do i_ = 2, n
      if (x(i_) > maxval_x) maxval_x = x(i_)
   end do
   print *, minval_x, maxval_x
end block
end program xloop

and running python xarray.py foo.f90 –inline on this code recovers the original.

2 Likes