IRPF90 Basics

Let us rewrite the same code as in the previous section, but in the IRPF90 framework.

First, we create a file named uvwt.irp.f:

BEGIN_PROVIDER [ integer, t ]
  t = u1+v+4
END_PROVIDER

BEGIN_PROVIDER [integer,w]
  w = d5+3
END_PROVIDER

BEGIN_PROVIDER [ integer, v ]
  v = u2+w+2
END_PROVIDER

BEGIN_PROVIDER [ integer, u1 ]
  integer :: fu
  u1 = fu(d1,d2)
END_PROVIDER

BEGIN_PROVIDER [ integer, u2 ]
  integer :: fu
  u2 = fu(d3,d4)
END_PROVIDER

integer function fu(x,y)
  integer :: x,y
  fu = x+y+1
end function

This file contains usual Fortran statements, as well as new keywords. In Fortran there are subroutines and functions, and IRPF90 introduces Providers. If an entity is declared with a BEGIN_PROVIDER ... END_PROVIDER block, then it is an IRP entity and it will behave as a global variable in the whole program. All the provided entities are not supposed to be modified outside of their providers. The main point is that the provider will always be called automatically before the variable is used. The programmer doesn't know when and where the provider will be called.

Let us now introduce a provider for coupled data. Here, the input data will be read from the standard input in a given order, so it is convenient to provide them all at once in file input.irp.f:

 BEGIN_PROVIDER [ integer, d1 ]
&BEGIN_PROVIDER [ integer, d2 ]
&BEGIN_PROVIDER [ integer, d3 ]
&BEGIN_PROVIDER [ integer, d4 ]
&BEGIN_PROVIDER [ integer, d5 ]

  print *,  'd1, d2, d3, d4, d5?'
  read(*,*) d1, d2, d3, d4, d5

END_PROVIDER

Now, we can write the main function in the irp_example1.irp.f file:

program irp_example1
  implicit none
  print *, 't = ', t
end

To compile the program, we will have to set up the IRPF90 environment:

$ ls
input.irp.f  irp_example1.irp.f  uvwt.irp.f

$ irpf90 --init
$ ls
input.irp.f  irp_example1.irp.f  IRPF90_man  IRPF90_temp  Makefile  uvwt.irp.f

The created IRPF90_temp directory contains temporary files for the compiling step: the generated Fortran files, as well as the corresponding .mod and .o files. IRPF90_man contains the generated man pages that document the code, and a Makefile was created :

IRPF90 = irpf90  #-a -d
FC     = gfortran -ffree-line-length-none
FCFLAGS= -O2

SRC=
OBJ=
LIB=

include irpf90.make

irpf90.make: $(filter-out IRPF90_temp/%, $(wildcard */*.irp.f)) \
             $(wildcard *.irp.f) $(wildcard *.inc.f) Makefile
        $(IRPF90)

To build the test program, simply run make. The Makefile includes the irpf90.make file which does not exist, but there is a rule to create it by calling IRPF90. IRPF90 analyzes the code present in all the *.irp.f files of the current directory. The list of IRP entities is created in a first pass, then a second pass analyzes the dependencies between the entities. From all this information, it creates the Fortran code that will call the providers of each entity before it is used. As the dependencies between the entities are known the irpf90.make file, containing all the dependencies between the files, can be written.

Once IRPF90 has created the irpf90.make file, it can be included and the Fortran files can be compiled. As the irpf90.make file depends on all the *.irp.f files of the current directory, each modification or creation of an *.irp.f file will force IRPF90 to run before compiling. To summarize, you almost never need to write anything in the Makefiles. You just need to write *.irp.f files and run make.

$ make
Makefile:9: irpf90.make: No such file or directory
irpf90  
IRPF90_temp/irp_example1.irp.module.F90
IRPF90_temp/irp_example1.irp.F90
IRPF90_temp/uvwt.irp.module.F90
IRPF90_temp/uvwt.irp.F90
IRPF90_temp/input.irp.module.F90
IRPF90_temp/input.irp.F90
Warning: Variable u1 is not documented
Warning: Variable u2 is not documented
Warning: Variable t is not documented
Warning: Variable w is not documented
Warning: Variable v is not documented
Warning: Variable d1 is not documented
Warning: Subroutine irp_example1 is not documented
Warning: Subroutine fu is not documented
gfortran -ffree-line-length-none -I IRPF90_temp/  -O2 -c IRPF90_temp/irp_example1.irp.module.F90 -o IRPF90_temp/irp_example1.irp.module.o
gfortran -ffree-line-length-none -I IRPF90_temp/  -O2 -c IRPF90_temp/uvwt.irp.module.F90 -o IRPF90_temp/uvwt.irp.module.o
gfortran -ffree-line-length-none -I IRPF90_temp/  -O2 -c IRPF90_temp/irp_example1.irp.F90 -o IRPF90_temp/irp_example1.irp.o
gfortran -ffree-line-length-none -I IRPF90_temp/  -O2 -c IRPF90_temp/irp_stack.irp.F90 -o IRPF90_temp/irp_stack.irp.o
gfortran -ffree-line-length-none -I IRPF90_temp/  -O2 -c IRPF90_temp/input.irp.module.F90 -o IRPF90_temp/input.irp.module.o
gfortran -ffree-line-length-none -I IRPF90_temp/  -O2 -c IRPF90_temp/uvwt.irp.F90 -o IRPF90_temp/uvwt.irp.o
gfortran -ffree-line-length-none -I IRPF90_temp/  -O2 -c IRPF90_temp/input.irp.F90 -o IRPF90_temp/input.irp.o
gfortran -ffree-line-length-none -I IRPF90_temp/  -O2 -c IRPF90_temp/irp_touches.irp.F90 -o IRPF90_temp/irp_touches.irp.o
gfortran -ffree-line-length-none -I IRPF90_temp/  -o irp_example1 IRPF90_temp/irp_example1.irp.o IRPF90_temp/irp_example1.irp.module.o IRPF90_temp/irp_stack.irp.o  IRPF90_temp/uvwt.irp.o IRPF90_temp/uvwt.irp.module.o IRPF90_temp/input.irp.o IRPF90_temp/input.irp.module.o  IRPF90_temp/irp_touches.irp.o

Array entities

An array is considered valid when all of its values are valid. The dimensions of an array entity can be IRP entities, constants or intervals.

BEGIN_PROVIDER [ integer, fact_max ]
  fact_max = 10
END_PROVIDER

BEGIN_PROVIDER [ double precision, fact, (0:fact_max) ]
  implicit none
  integer :: i
  fact(0) = 1.d0
  do i=1,fact_max
    fact(i) = fact(i-1)*dble(i)
  end do
END_PROVIDER

In this example, as the array fact depends on its dimensioning variable fact_max, fact_max is provided first. Then, the fact array is allocated with the required dimensions, and then the code inside the provider is executed. Note that if the fact array is not used in the program, it will never be allocated.

Freeing entities

It is possible to free memory by using the FREE keyword.

BEGIN_PROVIDER [ double precision, table2, (size(table1,1)) ]
  implicit none
  table2(:) = 2.d0 * table1(:)
  FREE table1
END_PROVIDER

When table1 is freed, the entity table1 is marked as non-valid, such that if it is needed again, it will be reallocated and rebuilt.

When applying the FREE keyword to scalar entities, those are just marked as non-built.

Forcing to provide entities

The PROVIDE keyword forces to provide an entity, even if it is not needed.

In this example,

subroutine s()
  implicit none
  PROVIDE u v
end

u and v will be provided before entering in the scope of subroutine s.

This second example forces to re-provide the random_x entity at every loop cycle (version >= 1.5.0):

do i=1,N
  PROVIDE random_x
  print *, random_x
  FREE random_x
end do