Back to main menu

## Informations for developers

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.

#### Structure of the program

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:

- include contain base modules, on which most other modules depend. These base modules define global variables (`comv.f90`), general subroutines (`subroutines.f90`), functions (`functions.f90`), messages (`messages.f90`) etc. that are used by several other modules.
- input contains the modules that have the role of reading data from one input file and load it into the memory. The main module performing this task is `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 contain modules that apply some transformations to the system. The main module performing this task is `options.f90`, which then calls the necessary options modules (which names usually start with `opt_`).
- output contains modules that have the role of writing the data contained in the memory into one or several files. The main module performing this task is `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_`).
- modes contain modules that decide how files are treated. Each mode can use any of the aforementionned modules.

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.

#### Arrays

Most variables and arrays are allocated dynamically and passed between modules. The following describes some arrays commonly used in Atomsk and passed between modules:

- H(3,3): the three vectors of the supercell H(1,:), H(2,:) and H(3,:) (cartesian coordinates in Å).
- P(:,:): atom positions (cartesian coordinates in Å); first dimension corresponds to the number of atoms; second dimension is 4 for storing X, Y, Z and atomic number.
- S(:,:): shell positions, must be unallocated (if no shell exist in the system) or have exactly the same dimensions as P. Similarly, S is used to store the cartesian coordinates (x,y,z) of the shells, and their atomic number. If the ion i possesses a shell, then S(i,4)=P(i,4), otherwise S(i,4)=0.0.
- AUX(:,:): values of auxiliary properties (real); first dimension must equal the number of particles in P; second dimension must be equal to the size of AUXNAMES, i.e. AUX(i,j) is the j-th property of atom i; if no auxiliary property exist then AUX and AUXNAMES should be unallocated.
- AUXNAMES(:): names of auxiliary properties (characters); AUXNAMES(j) is the name associated with the j-th property (i.e. the property which values are stored in AUX(:,j)).
- SELECT(:): boolean array of same size as P(1,:), deciding if each atom is selected (`.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)`

#### Auxiliary properties

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:

- type for atom types (e.g. used in LAMMPS data and custom formats);
- q for ion charges, and qs for the charge of shells in ionic core-shell models;
- vx, vy, vz for atom velocities;
- fx, fy, fz for forces on atoms;
- fixx, fixy, fixz for fixed coordinates of atoms (=1 if atom coordinate is fixed, 0 otherwise).

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.

#### Units for files

The following UNITs are used by specific files and should be followed by all modules:

- 20: logfile (by default "atomsk.log");
- 30-34: input files;
- 35-39: intermediate files (used internally by Atomsk);
- 40-45: output files.

#### Conceiving your own mode to treat files or compute a property

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:

- modes.f90: load your module at the beginning of the file (USE ...); add the corresponding section to call your module;
- read_cla.f90: this module interprets command-line arguments; add the section corresponding to your module, so that it is recognized and all its parameters are read when the user types it in the command-line.

Also, please create the pages for your module in the documentation, explaining what it does.

#### Adding a file format to read in

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:

- readin.f90: load your module at the beginning of the file (USE ...); add the corresponding line in the 'read' section (label 200);
- guess_format.f90: add some rules by which Atomsk can recognize the file format you add: file extension, keywords...

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.

#### Adding a file format to write to

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:

- comv.f90: add the extension of your file format (e.g. 'xyz', 'cfg'...) to the global array "listofformats";
- read_cla.f90: add the command-line keyword corresponding to the file format you are adding (e.g. 'xyz', 'cfg'...);
- writeout.f90: load your module at the beginning of the file (USE ...); and then add the corresponding lines to the "write" section (label 200);
- guess_format.f90: for Atomsk to recognize the file format, add the file extension at label 100 (in `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.

#### Adding an option

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:

- read_cla.f90: you have to add your option parameters to the list of strings that are read from the command-line. Your option should start with a keyword (for instance "`-cut`") followed by a given number of parameters. All of this should be stored as a string in the global variable `options_array`.
- options.f90: you need to call your module at the beginning of the file (`USE ...`), and to call your module in the DO loop. Please respect the alphabetical order so that other developers can find an option easily.
- Makefile: add your module in the SOP variable of the Makefile so it is compiled together with the program. Make sure to add it before the module "options.f90".

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.

#### Adding a language

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:

- messages_XX.f90: copy the file "messages_EN.f90" to a new file, replacing the "EN" in the file name by the two-letters abbreviation of your language (e.g. "messages_FR.f90" for French, "messages_DE.f90" for German, etc.). Then you can start editing the new file and translating the English messages. The condition `CASE DEFAULT` should redirect to the English messages.
- messages.f90: add the conditions (`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).
- Makefile: add your module in the MSG variable of the Makefile so it is compiled together with the program. Make sure to add it after the module "messages.f90".

Back to main menu