IRPF90

IRPF90 is a Fortran code generator. Schematically, the programmer only writes computation kernels, and IRPF90 generates the "glue code" that will link all these kernels together to produce the expected result, handling all the relationships between the variables. In this way, even large codes can still be under control.

Introduction

Today, large scientific codes in Fortran are difficult to maintain. The complexity of the programs comes from the dependencies between the different entities of the code. As the entities become more and more coupled, the program becomes more and more difficult to maintain and to debug.

If the programmer wants to keep the code under control, he has to be aware of all the consequences of a modification of the source code on all possible execution paths. When the code was written by multiple developers and when the code is large (hundred thousands of lines), this becomes extremely difficult for the programmer. However, the machine can handle easily such a complexity by handling all the dependencies between the variables, as in a Makefile.

IRPF90 is a Fortran code generator. Schematically, the programmer only writes computation kernels, and IRPF90 generates the "glue code" that will link all these kernels together to produce the expected result, handling all the relationships between the variables. In this way, even large codes can still be under control. irpf90 Introduction

The Implicit Reference to Parameters programming strategy

The Implicit Reference to Parameters (IRP) method was first introduced by François Colonna in the paper IRP programming : an efficient way to reduce intermodule coupling. Here, we only give a practical overview of the main ideas, but the reader is encouraged to read the original paper. This way of programming is imperative, which is the natural way to write Fortran :

the programmer tells the machine how its internal state will change by giving stepby-step instructions. If the instructions are not given in the proper order, the program is wrong. Therefore, at each line the programmer has to be aware of the full state of the program, which results from the needs/needed_by relationships of the variables. Imperative programming explores the tree from the leaves to the root.

The same program can be written using the functional programming paradigm.

Instead of telling the machine what to do, we can express what we want.

Considering the program this way explores the tree from the root to the leaves.

irpf90 The Implicit Reference to Parameters method program compute_t implicit none integer :: d1, d2, d3, d4 d5 ! Input data integer, external :: u, u, v, w, t ! Functions call read_data (d1,d2,d3,d4,d5) write(*,*), "t=", t( u(d1,d2), v( u(d3,d4), w(d5) ) )

end program

Now, the needs/needed_by relationships between the entities are expressed by calling function t . The programmer doesn't handle any more the order in which the instructions will be executed : we don't known which one u(d3,d4) and w(d5) will be executed first. However, the global knowledge of the tree is still required to write this program.

In order to get rid of the global knowledge of the tree, we will transform it into local knowledge, which is much easier to handle. For each entity, we will only express the other needed entities: t --needs --> u1 and v u1 --needs --> d1 and d2 v --needs --> u2 and w u2 --needs --> d3 and d4 w --needs --> d5

It appears now that the arguments of the functions are not variables but parameters. In that case, we can put the parameters inside the functions, as they will always be the same. (d1,d2,d3,d4,d5) implicit none integer, intent(out) :: d1,d2,d3,d4,d5 print *, 'd1,d2,d3,d4,d5 ?' read (*,*) d1,d2,d3,d4,d5 end Now, the program automatically builds the tree and explores it. The programmer doesn't have to handle the execution of the code any more. However, there are a few aspects that can be improved. First, we have to write many empty parentheses () which make the code less readable. Secondly, we have to declare the return type of these functions every time we use them. Finally there is a major drawback: here, the data ( d1 ... d5 ) is read three times because there is no way to know that it has already been read. These last points can all be easily addressed. Indeed, if a function is a pure function (with no side effects), calling the function with the same values as arguments will always return the same value. In our program, the functions have no arguments, so we only need to build once the return value and cache it for subsequent calls. This mechanism is known as memoization.

For each node we write a builder, which is a subroutine that builds a valid value of an entity (according to the equations given at the beginning of this section). Then, we write a provider for each entity. A provider is a subroutine with no input arguments whose role is to prepare a valid value of an entity. It calls the providers of the needed entities, calls the builder of the desired entity, saves the computed irpf90 The Implicit Reference to Parameters method value in a cache and then marks the quantity as built. The next calls to the provider will return the cached value.

Before writing the providers, we need to create a global variable for each node of the tree, as well as a flag to mark it as built. For convenience, we shall put all of them in a Fortran module nodes : The rules are simple:

1. Each entity has only one builder and only one provider 2. The arguments of the builder are the values of the needed entities.

3. Calling a provider always guarantees that the entity of interest is valid after the provider has been called

Applying rigorously these rules makes the development of large codes as easy as for smaller codes. irpf90 The Implicit Reference to Parameters method

Introduction to IRPF90

As we have seen in the previous section, the IRP method is very powerful, but it requires a lot of discipline. IRPF90 is a tool that will write all the boilerplate IRP code for you, keeping your source code clear. It will also write Makefiles, documentation man pages, introduce compiler directives for code optimization, etc... irpf90 Introduction to IRPF90

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 : 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 irpf90 IRPF90 Basics 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.

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
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 irpf90 IRPF90 Basics
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 : 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.

IRPF90 = irpf90 #-a -d FC =
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

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. 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. This file is very useful for scripting. For instance,

$ # Get the file in which fact_max is defined $ awk '/:: fact_max/ { print $1 }' irpf90_entities fact.irp.f $ # Get the names of all double precision IRP entities $ INTS=$(awk '/integer / { print $5 }' irpf90_entities) $ echo $INTS d1 d2 d3 d4 d5 fact_max t u1 u2 v w
Another very useful tool is the irpman command:

$ irpman <irp_entity>
This opens a man page for the desired IRP entity containing its description (given in the BEGIN_DOC ... END_DOC blocks), the file in which it is defined, which other entities are needed to build it, and which other entities need the current entity. It also gives an Instability factor, which is an estimate measure of how dangerous it can be to modify the IRP entity.

Here is the man page displayed for the v entity:

Helping features Assertions

Assertions are boolean expressions that must be true, to check the runtime behavior of the program. Assertions can be introduced with ASSERT keyword:

BEGIN_PROVIDER [ integer, u2 ] call compute_u(d3,d4,u2) ASSERT (u2 < d3)

END_PROVIDER

In this particular example, if u2 < u3 nothing happens. If u2 >= u3 , then the program fails:

Stack trace: 0 ------------------------- provide_t provide_v provide_u2 u2 ------------------------- u2: Assert failed: file: uvwt.irp.f, line: 23 (u2 < d3) u2 = 8 d3 = 3 STOP 1
Assertions are activated by using irpf90 -a . If the -a option is not present, all the assertions are discarded.

Augmented assignment operators

These patterns are very frequent in scientific applications:

a = a + b a = a * b
If a has a very explicit name, this pattern can give:

my_very_explicit_name(dim1,dim2,dim3) = my_very_explicit_name(dim1,dim2,dim + b*c -d

Such constructs are not optimal:

The name of the variable is long, so the line has to be split and the code is less readable

The programmer is likely to make a typo by typing twice a very long variable name. This is likely to be caught by the compiler.

When the programmer modifies a dimension in the left member, he has to modify it accordingly in the right member. Such errors will not be caught by the compiler.

Augmented assignment operators cure these problems by allowing the programmer to write:

my_very_explicit_name(dim1,dim2,dim3) += b*c -d
IRPF90 introduces three operators: += , -= , and *= . Divisions could not be added since /= already means "not equal". To divide using an augmented assignment operator, *= 1. / can be used to multiply by the inverse.

Embedded shell scripts irpf90 Helping features

When a programmer writes code, the input comes from the keyboard. With IRPF90 it is possible to define sections where the input is not the keyboard but it comes from the output of script that will be executed at compile time. This is achieved with BEGIN_SHELL ... END_SHELL blocks. Any scripting language can be used. This example will use Bash to generate code that will print the date when the program was compiled:

Conditional compilation

In IRPF90, the C preprocessor can't really be used, as the produced Fortran files may not have everything in the same order as the *.irp.f files. Instead, IRPF90 provides the IRP_IF ... IRP_ELSE ... IRP_ENDIF keywords to enable conditional compilation.

Integration in Vim

When running irpf90 , two files are created for the interaction with vim : the $HOME/.vim/syntax/irpf90.vim file a tags file in the current directory

The first file is a syntax file for syntax highlighting. It extends the standard Fortran file to color the additional keywords of IRPF90. It also adds two features : hitting K when the cursor is on the name of an IRP entity displays its man page, and hitting = on a group lines selected with <Shift>-V auto-indents the code. However, auto-indentation is to be used outside of BEGIN_SHELL ... END_SHELL blocks, especially for embedded Python scripts. The tags file is similar to the file created with the ctags utility when programming in C. The presence of this file allows vim to jump automatically on the definitions of providers, functions and subroutines. For instance, inside vim , :tag u1 jumps to the provider of u1 . Another option is to place your cursor on an IRP entity somewhere where it is used and hit <CTRL>-] to jump on its definition. To come back where you were, hit <CTRL>-T . irpf90

Modifying entities outside of providers Touch IRPF90 guarantees that an entity will not be re-provided if its value is already computed and valid. If the value of an entity is modified by a side effect outside of its provider, all the entities which are parent of the modified entity should be invalidated. This is done using the TOUCH keyword.

In this example, all the entities have been provided (figure (a)). If the user requests the value of z , it will be fetched from the memory. Then, the value of x is modified by a side effect, so the tree is not valid any more. Using

Iterative processes

An iterative process at iteration n is characterized by : A

(n+1) needs C(n) needs B(n) needs A(n) needs C(n-1) etc...
As in IRPF90 the dependence tree is static, we will want to write the iterative process as: A needs B needs C needs A ... But this implies a cyclic dependency. In that case, we will violate the rule that an entity is created only by one provider, and we will allow C to be modified from outside of its provider, such that it only depends on C0 , its initial guess value. The provider of C will then only describe its initialization by copying its initial guess value C0 . After a TOUCH statement, all the entities in the current variable scope are provided again to ensure that the program will be correct. This can sometimes lead to providing entities that will not be needed later, especially in the cases where the TOUCH statement is the last statement of a provider. irpf90 Modifying entities outside of providers

The SOFT_TOUCH statement has the same effect as TOUCH , except that the entities in the current scope are not re-provided. It can be used as an optimization of the TOUCH when all the other entities are no more used in the current subroutine, function or provider. However, it has to be used with caution. irpf90

Debugging

Displaying the exploration of the tree New users of IRPF90 who are experienced Fortran programmers like to display the exploration of the tree when they execute their first programs. The -d option of irpf90 prints a message when the program enters or exits a function/provider/subroutine.

In the uvwt example, the output is:

$ ./irp_example1

IRP_here

You can remark the presence of the irp_here variable in the generated bld_u2 generated subroutine. Every subroutine, function or provider has a string local variable named irp_here , which contains the name of the current context. This variable is very helpful for users to print debug/error messages: print *, irp_here//' : a = ', a

Tracing memory allocations

When the memory used by a program becomes too large, one would like to find the IRP entities that may be responsible. The -m option will display a message in the standard output when an array for an IRP entity is allocated or deallocated (using the FREE keyword).

Here is a real-world example (taken from the Quantum package IRPF90 code): The integer at the beginning of the line is the number of elements in the array.

Debugging an embedded script

Embedded shell scripts may be difficult to debug. The --preprocess option helps the programmer to check the files IRPF90 will produce after executing the scripts. For example, consider the file named test.irp.f : irpf90 Debugging The IRPF90 variable contains the IRPF90 invocation. All the options of IRPF90 should be given in this line. To include some additional directories to be read by IRPF90, the -I option should be used.

The FC variable describes the Fortran compiler to use. As IRPF90 adds comments at the end of the lines to express the correspondence between the Fortran generated files and the IRPF90 source files, the lines are too long for the default options of gfortran. This explains why -ffree-line-length-none is inserted by default. If the Intel Fortran compiler is used, this option should be removed. The FC variable should contain the invocation of the Fortran compiler which is common to compiling Fortran files and to link the project. For example, the -openmp option of the Intel compiler should be placed on this line as it should be mentioned to compile the Fortran files, but it is also required at the link stage.

irpf90

FCFLAGS contains the flags that should be present at compile time but not at link time. The code optimization options should appear here.

It is possible to add some files to the project that will not be seen by IRPF90, but need to be compiled and linked to the project. For example, a Fortran file containing a "black box" subroutine could be taken from another project, and your IRPF90 program could call this subroutine. To do that, the names of these Fortran source files should be added to the SRC variable, and the names of the corresponding object files to the OBJ variable. Note that it is also possible to add some C source files and objects, but then the corresponding compiling rules would have to be added to the Makefile .

External libraries may be added to the LIB variable.

Auto-generated configuration

The targets are generated by IRPF90 and are written in the irpf90.make file.

The name of each main program is a target. The all target builds all the programs.

The clean target removes all the object files. The veryclean target removes also the IRPF90_temp/ and IRPF90_man/ directories as well as the irpf90.make , irpf90_entities and tags files.

If the USE_IRPF90_A environment variable is set, then an archive irpf90.a containing everything except the main functions is created. This archive is linked with every target program. If this environment variable is not set, all object files are linked to create the executables. IRPF90 creates Fortran modules that will store the cached data. These modules only contain data but no subroutines nor functions. This makes them very fast to compile. All the subroutines and functions are written in other files, and they use the modules. In this way, all the files containing subroutines and functions depend only on module files. The greatest advantage is that all of the files containing subroutines and functions, which are the longest to compile, are independent of each other. This makes the parallel builds very efficient using make -j <n> where <n> is the number of threads. Another advantage is that the irpf90.a library can be linked and used in any other program without requiring any .mod irpf90 file. Module .mod files are to be avoided in libraries as they are compilerspecific (Intel Fortran .mod files are not compatible with gfortran and vice versa).

Profiling

IRPF90 includes a profiler that will measure the number of CPU cycles spent in all the providers. At the end of the run, it will display for each entity the total number of cycles, the average number of cycles (with an error bar), the total time and the average time for each entity.

Here is an example taken from a real application:

N.Calls Tot Cycles Avg Cycles To --------------------------------------------------------------------------

Array alignment

Array alignment is necessary to get performance on x86 architectures. Indeed, vector instructions (SSE,AVX,AVX-512) require the data to be aligned on a 16-, 32-or 64-byte boundary. With the Intel compiler, it is possible to give the compiler a directive to align an array on a given boundary:

!DIR$ ATTRIBUTES ALIGN : 32 :: X Doing this will force the first element of array X to have an address which is a multiple of 256 bits. Using aligned arrays for one-dimensional array will remove the peeling loops produced by the compiler when producing and auto-vectorized binary.

For two-dimensional arrays, it is possible to have all columns aligned if the array is aligned and the length of a column is a multiple of the alignment. IRPF90 can set the alignment directive for all the IRP entities that are arrays using a command-line argument: irpf90 --align=32 will use a 32 byte alignment for every array entity, but it will also replace in the code all the $IRP_ALIGN patterns with 32 . In this way, it is possible to make a code which is valid for all kind of array alignments.

Let's create a function that will calculate the length of the leading dimension such that it is a multiple of the alignment: The if (.True.) statement can be interperted by the Fortran compiler. It will then remove the else branch that will never be taken, and remove the if test. For the loop which will run, the compiler knows exactly how many loop cycles will be performed, and it can take the right decisions for loop unrolling and vectorization strategies. irpf90

OpenMP

OpenMP is straightforward to use with IRPF90 for simple loops. Trouble may arrive when entities are provided in OpenMP blocks such that two threads may be providing the same entities simultaneously.

To avoid such situations, an error message is displayed if an entity is not provided by thread zero. A common solution to this problem is to explicitly provide the needed entities before entering in the OpenMP section.

Another possibility is to use irpf90 --openmp . In that case, all the providers become automatically thread-safe using one OpenMP lock per IRP entity. irpf90

Coarray Fortran support

When the --coarray option is given, all the entities are co-arrays in the CoArray Fortran (CAF) language extension, defined as [*] . Therefore, it is possible for any process image to access the IRP entities of all the other images.

Let us first create convenient providers to cache the values of the num_images and image_id functions that will be called very often. Only the first image will print, so this will imply an if statement as irpf90 57 CoArray Fortran

if (this_image() == 1) then print *, X endif
The problem is that X will need to be provided only if the image_id is equal to one. Here, we will have to force to provide X , whatever the value of

Prepare the working environment

Create a new directory for the project. Inside this directory, initialize the IRPF90 environment using: The user will be asked for the values of the Lennard-Jones parameters sigma_lj and epsilon_lj , as well as the interatomic_distance .

Create the main program in a file named test.irp.f , and the providers in a file named potential.irp.f . You don't need to modify the Makefile .

To compile the program, run The warning Makefile:9: irpf90.make: No such file or directory can be ignored: the missing irpf90.make will be created by applying the rule in the Makefile that calls IRPF90.

A binary file named test will be created. 0.3345 0 : <-0="" epsilon_lj="" 3.63000000000000041E-004="" :="" <-=" 0 :

-> interatomic_distance

Inter-atomic distance? 0.3 0 : <-0="" interatomic_distance="" 1.70499999999999992E-003="" 0 : <-0="" v_lj="" 0.0000000000000000="" :="" <-="" provide_v_l 0.46819241808782769 0 :

Solution

Describing the atoms Exercise

In the same directory, create a program which reads in the standard input:

The number of atoms For each atom: the mass and the x, y, z coordinates The program will the print the matrix of the distances between atom pairs.

You will have to create :

A provider for Natoms , the number of atoms A provider for coord and mass , the atom coordinates and mass. These are arrays with dimensions (3,Natoms) and (Natoms) respectively.

A provider for distance , the distance matrix. Its dimension is (Natoms,Natoms) .

You can check that your code is well documented using the irpman command: -> natoms Number of atoms? 3 0 : <-0="" natoms="" 1.59999999999999986E-004="" :="" <-="" pr 0 :

-> coord

For each atom: x, y, z, mass? 0. 0. 0. 40.

1. 2. 3. 10.

-1. 0. 2. 20. 0 : <-0="" coord="" 2.03999999999999754E-004="" :="" <-="" pro 0 : <-0="" distance="" 1.99999999999983177E-006="" :="" <-="" p 0.0000000000000000 <-0="" coord="" 0.0000000000000000="" :="" <-="" provide_ 0 : <-0="" distance="" 0.0000000000000000="" :="" <-="" provid 0 : <-0="" v_lj="" 0.0000000000000000="" :="" <-="" provide_v_l 0.39685690695535791 0 : -I, --include : Include directory -a, --assert : Activates ASSERT statements. If absent, remove ASSERT statements.

Solution

-c, --codelet (<entity>:<NMAX>|<entity>:<precondition>:<NMAX>) : Generate a codelet to profile a provider running NMAX times -d, --debug : Activates debug. The name of the current subroutine/function/provider will be printed on the standard output when entering or exiting a routine, as well as the CPU time passed inside the routine.

-g, --profile : Activates the profiling of the code.

-h, --help : Print this help -i, --init : Initialize current directory. Creates a default Makefile and the temporary working directories.

-l, --align <N> : Align arrays using compiler directives and sets the $IRP_ALIGN variable. For example, --align=32 aligns all arrays on a 32 byte boundary.

-m, --memory : Prints memory allocations/deallocations. irpf90 107 Index of command-line options -n, --inline (all|providers|builders) : Force inlining of providers or builders -o, --checkopt : Shows where optimization may be required -p, --preprocess : Prints a preprocessed file to standard output. Useful for debugging files containing shell scripts.

-r, --no_directives : Ignore all !DEC$ and !DIR$ compiler directives -s, --substitute : Substitute values in do loops for generating specific optimized code.

-t, --touch : Display which entities are touched when touching the variable given as an argument.

A

  scientific program (or sub-program) is a complicated function of its data. One can represent the program as a tree whose root is the output and whose leaves are the data. The nodes are the intermediate variables, and the edges represent the needs/needed_by relationships. Let us consider a program which computes t( u(d1,d2), v(u(d3,d4), w(d5)) This program can be represented with the following tree: irpf90 The Implicit Reference to Parameters method Writing the program in Fortran would require the programmer to have this tree in mind:

  (in) :: x,y f_u = x+y+1 end irpf90 The Implicit Reference to Parameters method subroutine read_data

  (out) :: d1,d2,d3,d4,d5 read(*,*) d1,d2,d3,d4,d5 end

  build_t(u1,v,t) t_is_built = .True.

  Warning: Subroutine fu is not documented gfortran -ffree-line-length-none -I IRPF90_temp/ -O2 -c IRPF90_temp/irp_exa gfortran -ffree-line-length-none -I IRPF90_temp/ -O2 -c IRPF90_temp/uvwt.ir gfortran -ffree-line-length-none -I IRPF90_temp/ -O2 -c IRPF90_temp/irp_exa gfortran -ffree-line-length-none -I IRPF90_temp/ -O2 -c IRPF90_temp/irp_sta gfortran -ffree-line-length-none -I IRPF90_temp/ -O2 -c IRPF90_temp/input.i gfortran -ffree-line-length-none -I IRPF90_temp/ -O2 -c IRPF90_temp/uvwt.ir gfortran -ffree-line-length-none -I IRPF90_temp/ -O2 -c IRPF90_temp/input.i gfortran -ffree-line-length-none -I IRPF90_temp/ -O2 -c IRPF90_temp/irp_tou gfortran -ffree-line-length-none -I IRPF90_temp/ -o irp_example1 IRPF90_tem

  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): Inside each provider, subroutine and function it is recommended to write a few lines to explain what it does. The documentation is written inside a BEGIN_DOC ... END_DOC block. BEGIN_PROVIDER [ double precision, fact, (0:fact_max) runs, a warning will be printed if the documentation block is absent. A file named irpf90_entities is created, where each line corresponds to one IRP entity and gives: the name of the file in which it is defined the Fortran type the name of the IRP entity the dimensions if the entity is an array irpf90

  To activate tab completion in Bash, you can source the irpman exectuable itself $ source $(which irpman)Now, pressing tab on the command line after irpman gives the list of all

  program test BEGIN_SHELL [ /bin/bash ] cat << EOF | sed 's/\(.*\)/echo "\1\"/g' print *, 'Compiled by `whoami` on `date`' print *, '$PWD' print *, '$(hostname)' EOF END_SHELL end $ ./test Compiled by scemama on Wed Feb 4 22:27:46 CET 2015 /tmp/irpf90_test laptop Another example generates 100 functions with Python: BEGIN_SHELL [ /usr/bin/python ] for i in range(100): print """ double precision function times_%d(x) double precision, intent(in) :: x times_%d = x*%d end """%locals() END_SHELL irpf90

  'Old feature' call old_feature() IRP_ENDIF To generate the program with the old feature, just run irpf90 as usual. If you want to activate the new feature instead, use irpf90 -Dnew_feature . Multiple -D options can be given in the command line

  TOUCH x (figure (b)), all the parents are invalidated, but the value of x is set as valid (figure (c)). Requesting z now will give the correct value of z taking account of the modification of x and re-providing only what is necessary between x and z .

  A_converged of A will be written as follows: BEGIN_PROVIDER [ double precision, A_converged ] logical :: iteration_condition do while (iteration_condition) ! Modify the value of C depending on ! the current value of A C = f(A) the TOUCH keyword, it produces the following Fortran code:

  numbers given in the output are the CPU times, and the integer on the left of each line is the thread ID.Compiler errors irpf90When the Fortran compiler fails, it reports an error in the Fortran code. This error is difficult for us to track because IRPF90 generated the Fortran and we need to be able to do the mapping from the Fortran compiler's error to the error in the *.irp.f file. To achieve this goal, the generated Fortran code has comments at the end of the lines which correspond to the file names and line numbers of the original *.irp.f file.Let us introduce an error in the IRPF90 code (a missing closing parenthesis) code in the IRPF90_temp/uvwt.irp.F90 file is :: irp_here = 'u2' ! uvwt.irp.f: 34 integer :: fu ! uvwt.irp.f: 39 u2 = fu(d3,d4 ! uvwt.irp.f: 40 end subroutine bld_u2 Running make produces this error (with the Intel Fortran compiler) ifort -I IRPF90_temp/ -O2 -c IRPF90_temp/uvwt.irp.F90 -o IRPF90_temp/uvwt. IRPF90_temp/uvwt.irp.F90(71): error #5082: Syntax error, found END-OF-STATEM u2 = fu(d3,d4 ! uvwt.irp.f: 40 ----------------------------------------------------ĉ ompilation aborted for IRPF90_temp/uvwt.irp.F90 (code 1) make: *** [IRPF90_temp/uvwt.irp.o] Error 1 irpf90

  :: irp_here = 'test' print *, 'Compiled by scemama on Mon Mar 9 18:41:50 CET 2015' print *, '/dev/shm/tmp' print *, 'lpqlx139' end Debugging TOUCH statements TOUCH statements are particularly dangerous because they violate the principle that one IRP entity can only be built by its builder, which can only be called by its provider. To see what will be invalidated by a TOUCH statement can be useful to understand the consequences of a dangerous modification. The -t option displays which IRP entities will be invalidated : When irpf90 --init is run, a standard Makefile is created: : $(filter-out IRPF90_temp/%, $(wildcard */*.irp.f)) \ $(wildcard *.irp.f) $(wildcard *.inc.f) Makefile $(IRPF90)

  create a matrix with all columns aligned, using the !DIR$ VECTOR ALIGNED directive safely. to create a binary executable specifically tuned for one input file. The option --substitute replaces the variables present in loop ranges and if conditions by those given in the command line. Doing this gives much more information to the Fortran compiler and typically up 5-10% of performance can be gained with such a strategy.For example, consider this piece of code: the variables lmax , nmax and choice1 by their input value using $ irpf90 -s lmax:100 -s nmax:48 -s choice1:.True.This will generate the following fortran code:

  create an array that will be different on each image: program, you will want to print the value of X of images 1 and 2.

  Build the program and the output will give:

  : $(filter-out IRPF90_temp/%, $(wildcard */*.irp.f)) $(wildcard * $(IRPF90) In the Makefile, activate the asserts and the debug options by uncommentinga and -d in the definition of the IRPF90 variable. irpf90 The Lennard-Jones potential Exercise Write a program which computes the Lennard-Jones potential :

  .irp.module.F90 IRPF90_temp/test.irp.F90 gfortran -I IRPF90_temp/ -O2 -ffree-line-length-none -c IRPF90_temp/test.ir gfortran -I IRPF90_temp/ -O2 -ffree-line-length-none -c IRPF90_temp/potenti gfortran -I IRPF90_temp/ -O2 -ffree-line-length-none -c IRPF90_temp/test.ir gfortran -I IRPF90_temp/ -O2 -ffree-line-length-none -c IRPF90_temp/irp_sta gfortran -I IRPF90_temp/ -O2 -ffree-line-length-none -c IRPF90_temp/potenti gfortran -I IRPF90_temp/ -O2 -ffree-line-length-none -c IRPF90_temp/irp_tou gfortran -I IRPF90_temp/ -o test IRPF90_temp/test.irp.o IRPF90_temp/test.ir

File

  data, input in atomic units. END_DOC print *, 'For each atom: x, y, z, mass?' integer :: i,j ! <--Variables can be declared of V_lj of the first program. Now, instead of computing the Lennard-Jones potential of a single inter-atomic distance r, you will compute the total potential energy which is the sum of the potential energies due to each pair of atoms: The dependencies have changed now, as your new version of V_lj needs the previously defined distance matrix distance . You can now run again the first program.

FileThe

  x_i to its initial position ! It is not necessary to re-touch coord since ! -at the next loop iteration it will be touche ! -out of the loop, it is soft-touched enddo enddo SOFT_TOUCH coord mass ! Does not re-provide the current entities. Here, V ! not be re-computed. This reduced the CPU time, ,i) = -V_grad(k,i)/mass(i) create a benchmark.irp.f file that contains provider to compute the 20 first n -th power of x using the traditional x**n , and another provider that will use our specialized functions BEGIN_PROVIDER [ double precision, x power.py script here for better inlining of the functions BEGIN_SHELL [ /usr/bin/python ] x**p for 0 < p < 21 with the fast power functions END_DOC BEGIN_SHELL [ /bin/bash ] for i in {1..20} do echo " double precision, external :: power_$i" echo " !DIR$ FORCEINLINE" echo " x_p_fast($i) = power_$i(x)" done END_SHELL END_PROVIDER We now Create a codelet for the x_p and the x_p_fast providers using $ irpf90 -c x_p:100000000 $ irpf90 -c x_p_fast:100000000 $ make irpf90 92 Using scripts to generate specialized functionsWe easily see that we get a speedup of 12x with the specialized power routines: N_blocks entity is the total number of independent calculations of $$\pi$$ one will do in a single process. BEGIN_PROVIDER [ integer, N_blocks ] implicit none BEGIN_DOC ! Total number of blocks, each containing N_steps steps. END_DOC N_blocks = 100 END_PROVIDER One will need to initialize the seed of the Fortran random number generator: d0*dble(count_in)/dble(N_steps)END_PROVIDERLet us now write the main program in test_mono.irp.f . It will print the running average and error bar of $$\pi$$ on the standard output. At the end of each loop cycle, the pi_block entity is freed, such that it will be freshly provided at the beginning of the next loop iteration. This example generates 4 routines with the exact same algorithm. insertion_sort for real arrays insertion_dsort for double precision arrays insertion_isort for integer arrays insertion_i8sort for integer*list of IRP entites !---------if (iargc() == 0) then print *, 'List of IRP entities' ./get_doc titi toto momo ! Prints the documentation of IRP entities titi, toto and momo !---------do i=1,iargc() call getarg(i,arg) !----------! Python script executed at compile time that will find the name of all the ! IRP entities of the current program. If the name of an entity is in the ! command line, its documentation will be printed. !----------BEGIN_SHELL [ /usr/bin/python ] import os entities = [] for filename in os.listdir('.'): # Loop over all file names irpf90 104 Introspection if filename.endswith('.irp.f'): # If the name ends with .irp.f file = open(filename,'rstrip().lower().startswith('begin_provider'): name = line.split(',')[1].split(']')[0].strip() doc[name] = "" elif line.strip().lower().startswith('begin_doc'): --coarray : All providers are coarrays -D, --define : Defines a variable identified by the IRP_IF statements.

  -v, --version : Prints version of irpf90-z, --openmp : Has to be set for OpenMP codes `

  In this example, type and name are the template variables, referenced as $type and $name in the first block. Six providers and functions will be generated : replacing $type with integer and name with size_tab1 replacing $type with integer and name with size_tab2 replacing $type with real and name with distance irpf90 27 Helping features replacing $type with real and name with x replacing $type with real and name with y replacing $type with real and name with z

	end function
	SUBST [ type, name ]
	integer	;	size_tab1 ;;
	integer	;	size_tab2 ;;
	real	;	distance ;;
	real	;	x	;;
	real	;	y	;;
	real	;	z	;;
	END_TEMPLATE

Templates irpf90 Helping features

Templates is a very useful feature of many languages. IRPF90 provides a simple way to write templates to generate similar providers and functions. The template is defined in the BEGIN_TEMPLATE ... END_TEMPLATE block. The first section of the block contains the template code, in which template variables are used prefixed with a dollar sign. Then the SUBST keyword defines the template variables to substitute, and multiple substitution definition lines are given. The substitution definitions are separated by two semi-colons ( ;; ), and within a substitution definition the variable substitutions are separated by one semi-colon ( ; ). BEGIN_TEMPLATE BEGIN_PROVIDER [ $type , $name ] call find_in_input('$name', $name) END_PROVIDER logical function $name_is_zero() $name_is_zero = ($name == 0)

Automatic documentation

Helping features

Profiling

Prepare the working environment

Computing the total energy

CoArray Fortran example

Codelet generation

When optimizing for performance, it is common to write a simple codelet that will just benchmark one provider. IRPF90 can write this codelet for you: $ irpf90 --codelet <NAME>[:<PRECONDITION>]:<NMAX> NAME : Name of the IRP entity whose provider is to test PRECONDITION : A space-separated list of other entities to provide before running the benchmark NMAX : Number of repetitions to improve the accuracy.

Here is an example of the uvwt example.

$ irpf90 --codelet v:t:100000

This will generate the codelet_v.irp.f in which t is provided before the benchmark is run, and v will be built 100000 times: irpf90 Codelet generation Now a new main program has been generated, it can be built using make . When the run is finished, the number of CPU cycles and the time in seconds is given for one execution of the provider:

Inlining providers

For each IRP entity, a provider and a builder function are created. The provider always calls the builder. The --inline builders forces to inline the builders in the providers.

When an IRP entity A is used, the following code is generated if (.not.a_is_built) then call provide_a endif If the --inline providers option is present, there will be a directive in the generated Fortran code to force the inlining of the call provide statement.

To inline both providers and builders, use ifpr90 --inline all . irpf90 Tutorial : A molecular dynamics code Molecular dynamics models the movement of atoms according to their initial positions and velocities. In this tutorial, we will write a molecular dynamics program to illustrate how to use IRPF90. This program will read the force field parameters from an input file, as well as the initial positions of the atoms. After each little displacement of the atoms according to their velocities, the new set of coordinates will be printed into an output file such that a video animation can easily be produced with an external tool.

Here is the list of what we will have to code:

The potential energy of a couple of atoms (Lennard-Jones potential). This will will be a very simple introduction to IRPF90.

The potential and kinetic energy of system of N atoms. We will have to create arrays dimensioned by other IRP entities.

The acceleration of the particles using finite differences for the calculation of derivatives. This part will introduce the TOUCH keyword.

The Verlet algorithm to make everything move.

The first thing you will have to do is download IRPF90 from the web site: http://irpf90.ups-tlse.fr

Physical Parameters

For all this tutorial, we will use Argon atoms with the following parameters: mass : 39.948 g/mol epsilon : 0.0661 j/mol sigma : 0.3345 nm The atom coordinates are given in nanometers. irpf90

Computing the total energy Exercise

Write a program which prints the total energy of the system.

V is the potential (Lennard-Jones here) and T is the kinetic energy Write the providers for the kinetic energy and for the total energy. All the velocities will be chosen to be initialized equal to zero in the velocities provider.

Remember you already have the provider for the masses of the atoms. -> provide_velocity2 :

Expected output

-> provide_velocity :

-> provide_natoms :

-> natoms Number of atoms? 3 : <-0="" natoms="" 1.58999999999999853E-004="" :="" <-="" : <-0="" velocity="" 5.99999999999992900E-006="" :="" <-=" : <-0="" velocity2="" 5.00000000000022995E-006="" :="" <-=" :

-> coord For each atom: x, y, z, mass? 0 0 0 10 0 0 .3 20 .1 .2 -.3 : <-0="" coord="" 1.97999999999999825E-004="" :="" <-="" pr : <-0="" t="" 9.99999999999699046E-007="" :="" <-="" provide :

-> provide_v_lj :

-> provide_epsilon_lj :

-> epsilon_lj Epsilon? 0.0661 Sigma?

.3345 : <-0="" epsilon_lj="" 2.07999999999999852E-004="" :="" <-:

-> distance : <-0="" distance="" 2.00000000000026545E-006="" :="" <-=" : <-0="" v_lj="" 1.00000000000013273E-006="" :="" <-="" pro : <-0="" v="" 1.00000000000013273E-006="" :="" <-="" provide : <-0="" e_tot="" 1.00000000000013273E-006="" :="" <-="" prov 0.39685690695535791 :

Solution

File test3.irp.f irpf90

Computing the acceleration Exercise

The acceleration vector is given by where x_i is the x coordinate of atom i (an element of the coord array). Write the provider for V_grad_numeric , the finite-difference approximation of the derivative of the potential with respect to the coordinates:

It will be necessary to use the TOUCH keyword.

The computation of the acceleration should not depend directly on the method used to compute the gradient, so we will use V_grad in the provider for the acceleration . V_grad will be a simple copy of V_grad_numeric . .3345 : <-0="" epsilon_lj="" 1.60999999999999685E-004="" :="" :

Expected output

-> distance : <-0="" distance="" 2.00000000000026545E-006="" :="" <-: <-0="" v_lj="" 2.00000000000026545E-006="" :="" <-="" p : <-0="" v="" 1.00000000000013273E-006="" :="" <-="" provi :

-> dstep : <-0="" dstep="" 9.99999999999699046E-007="" :="" <-="" p :

-> touch_coord : <-0="" touch_coord="" 1.00000000000013273E-006="" :="" -:

-> provide_v_lj :

-> provide_distance :

-> distance : <-0="" distance="" 1.00000000000013273E-006="" :="" < : <-0="" v_lj="" 9.99999999999265365E-007="" :="" <-="" : <-0="" v="" 9.99999999999265365E-007="" :="" <-="" prov : <-0="" touch_coord="" 1.00000000000013273E-006="" :="" -:

-> provide_v_lj :

-> provide_distance :

-> distance : <-0="" distance="" 1.00000000000013273E-006="" :="" < : <-0="" v_lj="" 1.00000000000013273E-006="" :="" <-="" : <-0="" v="" 1.00000000000013273E-006="" :="" <-="" prov

-> provide_distance :

-> distance : <-0="" distance="" 1.00000000000013273E-006="" :="" < : <-0="" v_lj="" 1.00000000000013273E-006="" :="" <-="" : <-0="" v="" 1.00000000000013273E-006="" :="" <-="" prov : <-0="" touch_coord="" 1.00000000000013273E-006="" :="" < : <-0="" v_grad="" 1.00000000000013273E-006="" :="" <-="" pr : <-0="" acceleration="" 1.00000000000013273E-006="" :="" <-= -1.21434697006317371E-003 -2.42873782740904431E-003 -2.8852483886706581 irpf90

Implementing the molecular dynamics Exercise

The Verlet algorithm is the following where n is the index of the current step, r is the position vector, v is the velocity vector, a is the acceleration vector and is the time step.

Write a subroutine which implements the Verlet algorithm. To do this, at each iteration :

Compute the coordinates at step n+1

Compute the component of the velocity which depends on the position at step n TOUCH the coordinates and the velocities Add to the velocities the part which depends on step n+1

TOUCH the velocities For this exercise, remove the debug option in the Makefile .

Expected output irpf90

Using scripts to generate specialized functions

In this example we write a Python script power.py that will generate specialized functions to calculate the n -th power of x .

#!/usr/bin/python POWER_MAX = 20 def compute_x_prod(n,d):

if n == 0:

CoArray Fortran example

Here, we want to calculate $$\pi$$ with a Monte-Carlo algorithm. Each image will compute its own Monte Carlo average, and the global average will be computed at the end.

The area inside a unit circle is $$\pi$$. The red square is the square containing all points with coordinates in the $$([0,1],[0,1])$$ range. The grey area represents the set of points that are in the $$([0,1],[0,1])$$ range and which are at a distance less than one to the center of the circle. For the computation of $$\pi$$ with a Monte Carlo algorithm, each sample will consist in drawing two uniform random numbers in the $$[0,1]$$ range (one for the $$x$$ coordinate and one for the $$y$$ coordinate). Is the distance of the point to the center is less than one, we increment a counter. Our estimate of $$\pi$$ will be $$4 N{\rm inside} / N{\rm total}$$.

Single core program

Let us first write a single core program. We write the providers in the pi.irp.f file. The N_steps entity defines the number of Monte-Carlo steps to compute the value of $$\pi$$ in a single process. irpf90

Parallel program

Now, we will write the prallel version of the program. First we will add to the Makefile the --coarray option to irpf90 and the -coarray option to ifort .

We can now write the parallel main program. We use a temporary array that will fetch all the remote values of pi_block . After a synchronization directive ( SYNC ALL ), the master process can compute the running average and error bar, and print the result. ---------------------------------------------------------------print "BEGIN_PROVIDER [ character*(%d), entities, (%d) ]"%(lenmax,len(doc)) print " BEGIN_DOC" print "! List of IRP entities" print " END_DOC" for i,e in enumerate(doc.keys()):

print "entities(%d) = '%s'"%(i+1, e) print "END_PROVIDER" # We create the providers of each entity # -------------------------------------for e in doc.keys():

print "BEGIN_PROVIDER [ character*(%d), %s_doc ]"%(len(doc[e]),e) print " BEGIN_DOC" print "! Documentation of variable %s"%(e,) print " END_DOC" print " %s_doc = '%s'"%(e,doc[e]) print "END_PROVIDER" END_SHELL irpf90