Writing Programs that Use QDYN¶
Now that we have installed the QDYN library, we can use it to write our own Fortran programs.
The QDYN project contains a cookiecutter template for generating a Fortran project based on QDYN. In this tutorial, we will walk through using this template to generate a simple “Hello World” program that can easily be expanded into something more useful.
To use the project template, you must install the cookiecutter
program into your primary Python environment:
[1]:
! pip install --quiet cookiecutter
Generating a “Hello World” project with cookiecutter
¶
The cookiecutter template is located in the cookiecutter
subfolder of the QDYN repository. Thus, assuming that we have a checkout of the QDYN repository in ~/qdyn
, we can generate a “Hello World” project with the following command:
[2]:
! cookiecutter $HOME/qdyn/cookiecutter --no-input --output-dir $HOME --overwrite-if-exists
Project settings:
project_name: hello_world_proj
prog_name: hello_world
use_module: y
module_name: global
use_precompiler: n
Project settings for 'hello_world' OK
Your project has been created in /home/user/hello_world_proj
The --no-input
option uses the default “Project settings” shown above. Without that option, we would have been prompted for settings.
See the template README.md for a summary of the options.
Structure of the “Hello World” project¶
Let’s switch to the ~/hello_world_proj
folder
[3]:
%cd ~/hello_world_proj
/home/user/hello_world_proj
and see which files cookiecutter
generated for us:
[4]:
! ls
Makefile.in README.md configure global.f90 hello_world.f90
The actual source code is split into a program file, hello_world.f90
(cf. prog_name
setting) and a global.f90
module (cf. module_name
setting). The two files look as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | program hello_world use qdyn use global_mod implicit none character (len=error_l) :: module_name = 'hello_world' character(len=error_l), parameter :: routine_name = 'main' write(*,'("***** START OF PROGRAM ",A," ******")') trim(module_name) write(*,'(A)') trim(date_string()) write(*,'("")') call print_qdyn_version(1) !call get_command_argument(1, runfolder) !call read_para(para, runfolder=runfolder, configfile='config') !quiet = .false. !call init(para, grid, gen, state=state, pulses=pulses, quiet=quiet) !call init_prop(gen, grid, prop_work, para, pulses) !call prop(state, grid, gen, prop_work, para, pulses) write(*,'("")') write(*,'(A)') trim(date_string()) write(*,'("***** END OF PROGRAM ",A," ******")') trim(module_name) contains ! program subroutines end program hello_world |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | module global_mod use qdyn implicit none character(len=file_l) :: runfolder type(para_t) :: para type(grid_t) :: grid type(pulse_t), allocatable :: pulses(:) type(dyn_generator_t) :: gen type(state_t) :: state type(prop_work_t) :: prop_work logical :: quiet contains ! module subroutines end module global_mod |
The global_mod
module in global.f90
contains all the global variables for the hello_world
program. Using a separate module has the advantage of keeping the program source clean, but it also has two significant additional benefits:
Suppose we were to later add additional programs to the project (e.g., one program for time propagation and a second program for optimal control). In that case, we can use
global_mod
in all of the programs and thus share the variable definitions.Any subroutines we may add to
global.f90
have access to the module variables as global variables. Having global module variables can be extremely useful for, e.g., aprop_info_hook
: it allows to access a variable inside theprop_info_hook
that is not a direct argument of theprop_info_hook
.
If we were sure that the project is only going to consist of a single program, we could have chosen n
for the use_module
project settings. In this case, the variables would have been declared directly in hello_world.f90
.
Looking at the source code for hello_world.f90
in more detail, we see that it simply prints its start and end time and the version information of the QDYN library against which the program will be linked. You should always do this in your programs: it dramatically increases reproducibility to have this information included in any captured output.
The three commented blocks are exemplary for how one would extend the program into something more useful:
Read config file data from a file
config
inside a runfolder given as the argument to the program (lines 15-16)Initialize data structures (Hamiltonian, pulses, state) from the data in the config file and data files in the runfolder that
config
may be referring to (lines 18-19)Do something with the data, usually either a propagation or optimal control (lines 21-22)
Most programs written on top of QDYN will follow this scheme.
Compiling the hello_world
program¶
The README.md
file contains the instructions for compiling the program:
# hello_world_proj
## Prerequisites
This program depends on the QDYN Fortran library. You must have `libqdyn.a` installed in a location that the `./configure` script can find.
## Compilation and Installation
To compile and install this program, run
./configure
make
make install
See
./configure --help
make help
for available compilation options.
The first step is to call
[5]:
! ./configure
Using compiler FC=gfortran with FFLAGS=-O2 -fopenmp -ffree-line-length-0
Hello World: True
compiler supports allocatable components: True
compiler supports allocatable dummy arrays: True
compiler has EXECUTE_COMMAND_LINE routine: True
compiler has SYSTEM function: True
compiler has quad precision: True
compiler supports atanh: False
compiler supports ieee_arithmetic: True
./Makefile.in -> ./Makefile
PREFIX : /home/user
BIN_INSTALL_DIR: $(PREFIX)/bin
QDYN_LIB : $(PREFIX)/lib/qdyn
QDYN_INCLUDE : $(PREFIX)/bin
PREP :
FC : gfortran
FFLAGS : -O2 -fopenmp -ffree-line-length-0
LIBS : -lqdyn -llapack -ldfftpack -llbfgsb -larpack -lblas -lwigxjpf -lfastwigxj
FEATURES : backtraces, no-debug, no-no-ipo
This Python script will generate a Makefile
based on the template in Makefile.in
, taking into account compiler settings and the location of the installed QDYN library. This is, in fact, the same configure
script we used when compiling the QDYN library itself! Calling it without arguments here uses the default options, which should be compatible with the default options used in the installation of QDYN. Most importantly, the QDYN_LIB
and QDYN_INCLUDE
variables must point to
the folder where the libqdyn.a
and the QDYN *.mod
files were installed, respectively. The configure
script has options for settings these paths. See configure --help
for details.
Once the Makefile
has been generated, we can then run make
to compile the hello_world
program:
[6]:
! make
make[1]: Entering directory '/home/user/hello_world_proj'
gfortran -O2 -fopenmp -ffree-line-length-0 -I/home/user/lib/qdyn/mod -L/home/user/lib/qdyn -c -o global.o global.f90
gfortran -O2 -fopenmp -ffree-line-length-0 -I/home/user/lib/qdyn/mod -L/home/user/lib/qdyn -c -o hello_world.o hello_world.f90
gfortran -O2 -fopenmp -ffree-line-length-0 -I/home/user/lib/qdyn/mod -L/home/user/lib/qdyn -o hello_world global.o hello_world.o -lqdyn -llapack -ldfftpack -llbfgsb -larpack -lblas -lwigxjpf -lfastwigxj
make[1]: Leaving directory '/home/user/hello_world_proj'
Running the program produces the following output:
[7]:
! ./hello_world
***** START OF PROGRAM hello_world ******
Sun Mar 14 04:38:24 +0000 2021
QDYN 2.0dev revision 8f9e5a7559eabc2d6987aa2848c799b0cfaf8a09 (installation_docker)
features: no-check-cheby, no-check-newton, no-parallel-ham, parallel-oct, backtraces, no-debug, no-no-ipo
Sun Mar 14 04:38:24 +0000 2021
***** END OF PROGRAM hello_world ******
Lastly, running
[8]:
! make install
make[1]: Entering directory '/home/user/hello_world_proj'
cp hello_world /home/user/bin/
Finished installation
configure options were
PREFIX : /home/user
BIN_INSTALL_DIR: $(PREFIX)/bin
QDYN_LIB : $(PREFIX)/lib/qdyn
QDYN_INCLUDE : $(PREFIX)/bin
PREP :
FC : gfortran
FFLAGS : -O2 -fopenmp -ffree-line-length-0
LIBS : -lqdyn -llapack -ldfftpack -llbfgsb -larpack -lblas -lwigxjpf -lfastwigxj
FEATURES : backtraces, no-debug, no-no-ipo
make[1]: Leaving directory '/home/user/hello_world_proj'
will install the hello_world
binary in the BIN_INSTALL_DIR
. Assuming that directory is in your $PATH
, you will then be able to run hello_world
from anywhere.
Run
[9]:
! make help
make[1]: Entering directory '/home/user/hello_world_proj'
help Show this help
all Compile all binaries
install Install compiled binaries into $(BIN_INSTALL_DIR)
uninstall Remove compiled binaries from $(BIN_INSTALL_DIR)
clean Remove compilation artifacts
distclean Remove all generated files
make[1]: Leaving directory '/home/user/hello_world_proj'
to see what else you can do with make
, e.g.,
[10]:
! make uninstall
make[1]: Entering directory '/home/user/hello_world_proj'
Running removal script
+ cd /home/user/bin
+ rm -f hello_world
Done
make[1]: Leaving directory '/home/user/hello_world_proj'
to remove the previously installed binary.
Extending the “Hello World” project¶
Going forward from this example, we would fill out the commented section of hello_world.f90
(of course, in a real project, you should probably have chosen a different prog_name
in the cookiecutter project settings).
For many projects, we will want to add additional programs, e.g., one program for doing time propagation, one program for optimal control, and maybe more programs for generating input data (diagonalization!) or analyzing outputs. We may also need additional module files.
These are easy to add to Makefile.in
. The core of Makefile.in
is the following:
OBJ = global.o
ALL = hello_world
all: $(ALL) ## Compile all binaries
hello_world: $(OBJ) hello_world.o
$(PREP) $(MPIFORT) $(FFLAGS) $(LDFLAGS) -o $@ $^ $(LIBS)
For any new module file, we simply have to add the corresponding .o
file to the OBJ
list. For any new program in the project, we have to add the program name (without an extension) to the ALL
list, and add two lines like the two lines for hello_world
(only the first of those lines needs to be adapted). Everything else is automatically handled by make
.
Warning
Make sure to edit Makefile.in
, not the generated Makefile
. Any changes to Makefile
will be overwritten the next time you run the ./configure
script.
You should also keep README.md
up to date with additional information about the project.