Lasciate ogni speranza voi ch'entrate.
Dante's Inferno, Canto III 9
Atomsk is a quite simple and dumb program, written by a humble physicist not so skilled at programming. The program was designed so that, in its properly compiled binary form, it can be understood by a computer, but that does not mean that the source code can easily be understood by a human being. I make this statement as a hint of what you will get into if you want to look at or modify the source code: lousy programming, non-optimized allocations, some GOTO statements, and a zest of global variables. And did I mention the many modules?
This page contains some informations for courageous developers who would like to make their way through the source code, and some informations for those who would like to add their contribution, be it for their personal use or for public release. Take a deep breath before diving.
Atomsk is made of many modules, each of which handling a very specific operation. For instance one module is responsible for writing XYZ file (out_xyz.f90
), and it does just that -if you need to change the format of XYZ files you have to modify only this module. This structure facilitates the maintainance of the code, makes it easier for new developers to dive into the code (if you want to check something, most of the time you will only need to edit one module that is a few hundreds of lines of code), and also makes it easier to implement new modules.
Besides the main module atomsk.f90
, the source files are "organized" in subdirectories:
comv.f90
), general subroutines (subroutines.f90
), functions (functions.f90
), messages (messages.f90
) etc. that are used by several other modules.readin.f90
, which first calls the module guess_format.f90
to guess the format of the input file, and then calls the appropriate module to read the input file (these modules have a name that start with in_
).options.f90
, which then calls the necessary options modules (which names usually start with opt_
).writeout.f90
, which calls the necessary output modules, each of which writes the data into one specific file format (these modules have a name that start with out_
).In order to keep things simple, some rules should be respected within each type of module. Input modules, option modules, and output modules, should not depend on one another, so be careful when introducing a USE statement in any module. For instance do not introduce a dependence to an input module (like "USE in_xyz
") in an option module. In output modules, the arrays P and H must not be modified and should be INTENT(IN)
. "Modes" modules can use any of the input, options and/or output. In the end the dependence tree should look like the following:
A good way to start digging into the source code is to look at the structure of the file mode_normal.f90
. One can see that this module reads in a file (by calling the reading module readin.f90
), applies options if any (by calling the module options.f90
), and then outputs one or several files (by calling writeout.f90
). This is exactly what Atomsk does when converting a file.
In other modes, these modules are called in more or less different ways.
Most variables and arrays are allocated dynamically and passed between modules. The following describes some arrays commonly used in Atomsk and passed between modules:
.TRUE.
) or not (.FALSE.
). This array is created when the option -select
is invoked. It is passed to some options, in which case the option is applied only to atoms that have a value .TRUE.
in the array SELECT, or to all atoms if the array SELECT is unallocated.One in all one can have in mind that atom properties are stored according to the following scheme:
Atom positions + atomic number | Shells positions (optional) | Auxiliary properties (optional) | Selected atoms (optional) |
---|---|---|---|
P(1,1) P(1,2) P(1,3) P(1,4) | S(1,1) S(1,2) S(1,3) S(1,4) | AUX(1,1) AUX(1,2)...AUX(1,M) | SELECT(1) |
P(2,1) P(2,2) P(2,3) P(2,4) | S(2,1) S(2,2) S(2,3) S(2,4) | AUX(2,1) AUX(2,2)...AUX(2,M) | SELECT(2) |
... ... ... | ... ... ... | ... ... ... | ... ... ... |
P(NP,1) P(NP,2) P(NP,3) P(NP,4) | S(NP,1) S(NP,2) S(NP,3) S(NP,4) | AUX(NP,1) AUX(NP,2)...AUX(NP,M) | SELECT(NP) |
From version beta 0.6 Atomsk reads and writes auxiliary properties, i.e. any per-atom property like velocities, forces, electric charges, etc. The array AUXNAMES contains strings that are the names of the auxiliary properties; the array AUX contains real numbers which are the values of the properties for each atom. As such, the array AUX must always have its first dimension equal to the first dimension of the array P.
When reading file formats Atomsk reads some or all auxiliary properties it may contain. The arrays AUXNAMES and AUX must then be allocated properly and consistently. If no auxiliary property exist then these arrays must not be allocated by input modules.
Some common properties are recognized by Atomsk by their names. Below is a list of names that one should use to store properties in the array AUXNAMES:
Using these names ensure that these properties are properly recognized in all modules, especially output modules. As an example, if forces are stored with the names "force_x, force_y, force_z" then they will be seen as ordinary properties, not as forces, and as a result they will not be written to XSF format for instance. On the contrary if they are saved with the names above (fx, fy, fz) then they will be recognized as forces and written to XSF format.
The array AUX is passed as a whole to output modules. It is up to each module to use the properties relevant for its file format. For instance out_xsf.f90
searches for forces (fx, fy and fz) in the array AUXNAMES, and if they exist, writes forces to the XSF file. Each module shall look for properties supported by the file format it is writing.
The following UNITs are used by specific files and should be followed by all modules:
A "mode" is a routine that decides what is done with the atom positions. For instance the default mode (so-called "normal mode") reads a file and converts it to one or many other formats. The "mode difference" computes the difference between two files. Many different things can be imagined, and the appropriate way to do them within Atomsk is through a mode. If you add a new mode, it is advised that you take advantage of routines that are already implemented in Atomsk to read/write files, and apply transformations.
A mode should start by mentionning which modules of Atomsk you wish to use. Using common variables defined in the file /include/comv.f90
is mandatory (USE comv
). If your module uses mathematical or physical constants, load the module /include/constants.f90
(USE constants
). The same holds for functions (USE functions
, see /include/functions.f90
) and/or subroutines (USE subroutines
, see /include/subroutines.f90
) already implemented in Atomsk.
If you wish to read a file containing atomic positions, insert the statement USE readin
. Then you can use the subroutine READ_AFF as follows to read a file:
CALL READ_AFF(inputfile,H,P,S,comment,AUXNAMES,AUX)
This will read the inputfile
and store the data in the appropriate arrays: box vectors in H, atom positions in P, etc. as described above. If you need to read many files, then create the necessary arrays (e.g. P1, P2, P3 etc. for atom positions) and call the routine READ_AFF for each file that must be read.
Then you can write your mode, working with the arrays H, P, etc. at your convenience. If your mode computes per-atom quantities, it is advised to save them as auxiliary properties (see definition of arrays AUX and AUXNAMES above) so that output modules can write these auxiliary properties to the final files. For instance if your mode computes the local von Mises strain of each atom, it can be saved in AUX and written as auxiliary properties in a CFG file. This is of course a dumb example since this quantity can already be computed and displayed within Atomeye, but hopefully it gives the idea.
If you wish to write one or several output files of atom positions, insert the statement USE writeout
at the beginning of your module. Then you can call the routine WRITE_AFF as follows to write the file(s):
CALL WRITE_AFF(outputfile,outfileformats,H,P,S,comment,AUXNAMES,AUX)
where outputfile
is the name of an output file (with or without an extension), outfileformats
is a rank-1 character array containing the names of the output formats (e.g. cfg, xsf, lmp...). The file names with the corresponding extensions will be automatically generated. The other arrays have the same meaning as before: H for box vectors, P for atom positions etc.
Finally you will need to attach your module to the rest of the code. For that the source code of the following files need to be edited:
Also, please create the pages for your module in the documentation, explaining what it does.
Each module managing input (which names should start with in_) should read in one specific file format, and load it to memory. Ssupercell parameters must be saved in the array H, atom positions in P, and if relevant, shells in S, auxiliary properties in AUX, and names of auxiliary properties in AUXNAMES.
It is of course recommended to take example on the existing modules.
If you wrote a reading module and want to integrate it to Atomsk, the source code has to be edited in the following way:
Also, please add the new format in the documentation (i.e. DISPLAY_HELP in the file messages_EN.f90
, and in doc/formats.html), and indicate the restrictions if there are any.
Each module managing output (whose names should start with out_) must use the arrays that are stored into the memory: H (supercell parameters), P (atom positions), and if the format supports it, S (positions of shells), AUX and AUXNAMES (auxiliary properties), and write data to one specific file format. These input arrays (H, P, S...) must not be modified inside these modules, and should be declared as "INTENT(IN)
".
It is of course recommended to take example on the existing modules.
If you wrote a writing module and want to integrate it to Atomsk, the source code has to be edited in the following way:
SELECT CASE(extension)
), in label 300 (line likely=MAX(...)
and in the following IF
statement);Also, please add the new format in the documentation (i.e. DISPLAY_HELP in the file messages_EN.f90
, and in doc/formats.html), and indicate the restrictions if there are any.
Options can apply transformations to the supercell (array H), to the atomic positions (array P), to the shells (S), and/or to the auxiliary properties (AUX, AUXNAMES). After the option is applied, the module must also return the values of the modified arrays. Keep in mind that these modules use the same arrays as input and output, and writing to an array while reading it can lead to errors. If necessary the arrays can be duplicated to apply the option -just don't forget to save the updated values to the correct arrays.
It is of course recommended to take example on the existing modules.
If you develop a module that applies such a transformation, you can include it in the program by editing the following files:
-cut
") followed by a given number of parameters. All of this should be stored as a string in the global variable options_array
.USE ...
), and to call your module in the DO loop. Please respect the alphabetical order so that other developers can find an option easily.Also, please add the new option in the documentation (i.e. DISPLAY_HELP in the file messages_EN.f90
, and in doc/options.html), and indicate the restrictions if there are any.
All messages displayed by Atomsk are in a separate module to make it easy to add translations to other languages. For the program to speak a language of your choice, you will need to edit the following files:
CASE DEFAULT
should redirect to the English messages.CASE("XX") ...
) for using the new language in the four subroutines. You may also define the one-letter abbreviations for "yes" and "no" in the subroutine PIKASHU_MSG (take example on existing languages).