commit 4e82c42518028d082a166f735e32991aaccb71ca
Author: Carles Martí <carles.marti2@gmail.com>
Date:   Thu Feb 11 19:45:42 2021 +0100

    Extended the documentation

diff --git a/dockonsurf.py b/dockonsurf.py
index f2543ca..2ffc46a 100755
--- a/dockonsurf.py
+++ b/dockonsurf.py
@@ -21,6 +21,8 @@ print("Running DockOnSurf.\n"
 
 with daemon.DaemonContext(working_directory=os.getcwd(), umask=0o002,
                           files_preserve=[logger.handlers[0].stream.fileno()]):
+    # From here on, the execution is carried out by a separate process in
+    # background
     logger.info(f'DockOnSurf started on {os.getcwd()}.')
     logger.info(f'To kill DockOnSurf execution type: `$ kill {os.getpid()}`.')
     logger.info(f"Using '{args.input}' as input.")
diff --git a/modules/calculation.py b/modules/calculation.py
index 9fced99..f9d63e9 100644
--- a/modules/calculation.py
+++ b/modules/calculation.py
@@ -1,3 +1,14 @@
+"""Module for the submission or execution of the calculations to be done
+
+functions:
+check_finished_calcs: Checks if the calculations finished normally or not.
+prep_cp2k: Prepares the directories to run calculations with CP2K.
+prep_vasp: Prepares the directories to run calculations with VASP.
+get_jobs_status: Returns a list of job status for a list of job ids.
+submit_jobs: Submits jobs to a custom queuing system with the provided script
+run_calc: Directs calculation run/submission.
+"""
+
 import os
 import logging
 
@@ -5,16 +16,16 @@ logger = logging.getLogger('DockOnSurf')
 
 
 def check_finished_calcs(run_type, code):
-    from modules.utilities import _human_key
-    """Returns two lists of calculations finished normally and abnormally.
+    """Checks if the calculations finished normally or not.
 
     @param run_type: The type of calculation to check.
     @param code: The code used for the specified job.
-    @return finished_calcs: List of calculation directories that have finished 
+    @return finished_calcs: List of calculation directories that have finished
     normally.
-    @return unfinished_calcs: List of calculation directories that have finished 
+    @return unfinished_calcs: List of calculation directories that have finished
     abnormally.
     """
+    from modules.utilities import _human_key
     from glob import glob
     import ase.io
     from modules.utilities import tail, is_binary
@@ -182,6 +193,7 @@ def prep_vasp(inp_files, run_type, atms_list, proj_name, cell, potcar_dir):
             incar = Incar.from_file(inp_file)
             incar["SYSTEM"] = proj_name+"_"+run_type
 
+    # Builds the directory hierarchy and copies/creates the relevant files
     for c, conf in enumerate(atms_list):
         subdir = f'{run_type}/conf_{c}/'
         os.mkdir(subdir)
@@ -297,7 +309,7 @@ def submit_jobs(run_type, sub_cmd, sub_script, stat_cmd, stat_dict, max_jobs,
 
 
 def run_calc(run_type, inp_vars, atms_list):
-    """Directs the calculation run according to the provided arguments.
+    """Directs the calculation run/submission.
 
     @param run_type: Type of calculation. 'isolated', 'screening' or
     'refinement'
@@ -353,7 +365,6 @@ def run_calc(run_type, inp_vars, atms_list):
         submit_jobs(run_type, 'ccc_msub -r %s %s', inp_vars['subm_script'],
                     stat_cmd, stat_dict, inp_vars['max_jobs'],
                     inp_vars['project_name'])
-
     elif inp_vars['batch_q_sys'] == 'local':
         pass  # TODO implement local
     elif not inp_vars['batch_q_sys']:
diff --git a/modules/clustering.py b/modules/clustering.py
index e427d96..37675ed 100644
--- a/modules/clustering.py
+++ b/modules/clustering.py
@@ -1,14 +1,16 @@
 """Functions to cluster structures.
 
 functions:
+get_rmsd: Computes the rmsd matrix of the conformers in a list of rdkit mol
+    objects.
 get_labels_affty: Clusters data in affinity matrix form by assigning labels to
-data points.
+    data points.
 get_labels_vector: Clusters data in vectorial form by assigning labels to
-data points.
+    data points.
 get_clusters: Groups data-points belonging to the same cluster into arrays of
-indices.
+    indices.
 get_exemplars_affty: Computes the exemplars for every cluster and returns a list
-of indices.
+    of indices.
 plot_clusters: Plots the clustered data casting a color to every cluster.
 clustering: Directs the clustering process by calling the relevant functions.
 """
@@ -21,7 +23,7 @@ logger = logging.getLogger('DockOnSurf')
 
 
 def get_rmsd(mol_list: list, remove_Hs="c"):
-    """Computes the rmsd matrix of the conformers in a rdkit mol object.
+    """Computes the rmsd matrix of the conformers in a list of rdkit mol objects
 
     @param mol_list: list of rdkit mol objects containing the conformers.
     @param remove_Hs: bool or str,
@@ -92,6 +94,7 @@ def get_labels_vector():
     @return: list of cluster labels. Every data point is assigned a number
     corresponding to the cluster it belongs to.
     """
+    # TODO Implement it.
     return []
 
 
@@ -115,13 +118,23 @@ def get_exemplars_affty(affty_mtx, clusters):
     distances between points, RMSD Matrix, etc.) shape: [n_points, n_points].
     @param clusters: tuple of arrays. Every array contains the indices (relative
     to the affinity matrix) of the data points belonging to the same cluster.
-    @return: list of indices (relative to the affinity matrix) exemplars for
-    every cluster.
+    @return: list of indices (relative to the affinity matrix) of the exemplars
+    for every cluster.
+
+    This function finds the exemplars of already clusterized data. It does
+    that by (i) building a rmsd matrix for each existing cluster with the values
+    of the total RMSD matrix (ii) carrying out an actual clustering for each
+    cluster-specific matrix using a set of parameters (large negative value of
+    preference) such that it always finds only one cluster and (iii) it then
+    calculates the exemplar for the matrix.
     """
     from sklearn.cluster import AffinityPropagation
+    # Splits Total RMSD matrix into cluster-specific RMSD matrices.
     clust_affty_mtcs = tuple(affty_mtx[np.ix_(clust, clust)]
                              for clust in clusters)
     exemplars = []
+    # Carries out the forced-to-converge-to-1 clustering for each already
+    # existing cluster rmsd matrix and calculates the exemplar.
     for i, mtx in enumerate(clust_affty_mtcs):
         pref = -1e6 * np.max(np.abs(mtx))
         af = AffinityPropagation(affinity='precomputed', preference=pref,
diff --git a/modules/config_log.py b/modules/config_log.py
index 58326b2..6e41a5b 100644
--- a/modules/config_log.py
+++ b/modules/config_log.py
@@ -1,10 +1,17 @@
-"""Configures the logger to record all calculation events on a log file."""
+"""Module for the configuration of how and what is recorded in the log file."""
 import sys
 import logging
 import warnings
 
 
 def log_exception(exc_type, exc_value, exc_tb):
+    """Sets up the recording of exceptions on the log file
+
+    @param exc_type: Type of exception
+    @param exc_value: Value of the exception
+    @param exc_tb:
+    @return: None
+    """
     if issubclass(exc_type, KeyboardInterrupt):
         sys.__excepthook__(exc_type, exc_value, exc_tb)
         return
@@ -13,11 +20,23 @@ def log_exception(exc_type, exc_value, exc_tb):
 
 
 def log_warning(message, *args, **kwargs):
+    """Sets up the recording of warnings on the log file
+
+    @param message: Warning message.
+    @param args: Additional arguments.
+    @param kwargs: Additional keyword arguments.
+    @return: None
+    """
     logger = logging.getLogger('DockOnSurf')
     logger.warning(" ".join(f"{message}".split()))
 
 
-def config_log(label):  # TODO Format log to break line after column 80.
+def config_log(label):  # TODO Format log to break long lines (after column 80).
+    """Configures the logger to record all calculation events on a log file.
+
+    @param label: Label of the logger to be used.
+    @return: The logger object.
+    """
     logger = logging.getLogger(label)
     logger.setLevel(logging.INFO)
 
diff --git a/modules/dos_input.py b/modules/dos_input.py
index 5b45a9b..13c5c0d 100644
--- a/modules/dos_input.py
+++ b/modules/dos_input.py
@@ -1,47 +1,90 @@
 """Functions to deal with DockOnSurf input files.
 
-Functions
-try_command:Tries to run a command and logs its exceptions (expected and not).
+List of functions:
+
+Auxiliary functions
+-------------------
 str2lst: Converts a string of integers, and groups of them, to a list of lists.
 check_expect_val: Checks whether the value of an option has an adequate value.
-read_input: Sets up the calculation by reading the parameters from input file.
+check_inp_files: Checks if the CP2K/VASP input files are consistent.
+
+Functions to read parameters in the Global section
+--------------------------------------------------
 get_run_type: Gets 'run_type' value and checks that its value is acceptable.
 get_code: Gets 'code' value and checks that its value is acceptable.
 get_batch_q_sys: Gets 'batch_q_sys' value and checks that its value is
-acceptable.
+    acceptable.
+get_pbc_cell: Gets 'pbc_cell' value and checks that its value is acceptable.
+get_subm_script: Gets 'subm_script' value and checks that its value is
+    acceptable.
+get_project_name: Gets 'project_name' value and checks that its value is
+    acceptable.
 get_relaunch_err: Gets 'relaunch_err' value and checks that its value is
-acceptable.
+    acceptable. # WARNING: OPTION NOT IMPLEMENTED
 get_max_jobs: Gets 'max_jobs' value and checks that its value is acceptable.
 get_special_atoms: Gets 'special_atoms' value and checks that its value is
-acceptable.
+    acceptable.
+get_potcar_dir: Gets 'potcar_dir' value and checks that its value is
+    acceptable.
+
+Functions to read parameters in the Isolated section
+----------------------------------------------------
 get_isol_inp_file: Gets 'isol_inp_file' value and checks that its value is
-acceptable.
-get_cluster_magns: Gets 'cluster_magns' value and checks that its value is
-acceptable.
+    acceptable.
+get_molec_file: Gets 'molec_file' value and checks that its value is acceptable.
 get_num_conformers: Gets 'num_conformers' value and checks that its value is
-acceptable.
-get_num_prom_cand: Gets 'num_prom_cand' value and checks that its value is
-acceptable.
-get_iso_rmsd: Gets 'iso_rmsd' value and checks that its value is acceptable.
+    acceptable.
 get_pre_opt: Gets 'pre_opt' value and checks that its value is acceptable.
+
+Functions to read parameters in the Screening section
+-----------------------------------------------------
 get_screen_inp_file: Gets 'screen_inp_file' value and checks that its value is
-acceptable.
+    acceptable.
+get_surf_file: Gets 'surf_file' value and checks that its value is acceptable.
 get_sites: Gets 'sites' value and checks that its value is acceptable.
-get_molec_ctrs: Gets 'molec_ctrs' value and checks that its value is
-acceptable.
-get_try_disso: Gets 'try_disso' value and checks that its value is acceptable.
+get_surf_ctrs2: Gets 'surf_ctrs2' value and checks that its value is acceptable.
+get_molec_ctrs: Gets 'molec_ctrs' value and checks that its value is acceptable.
+get_molec_ctrs2: Gets 'molec_ctrs2' value and checks that its value is
+    acceptable.
+get_molec_ctrs3: Gets 'molec_ctrs3' value and checks that its value is
+    acceptable.
+get_max_helic_angle: Gets 'max_helic_angle' value and checks that its value is
+    acceptable.
+get_select_magns: Gets 'select_magns' value and checks that its value is
+    acceptable.
+get_confs_per_magn: Gets 'confs_per_magn' value and checks that its value is
+    acceptable.
+get_surf_norm_vect: Gets 'surf_norm_vect' value and checks that its value is
+    acceptable.
+get_adsorption_height: Gets 'adsorption_height' value and checks that its value
+    is acceptable.
+get_set_angles: Gets 'set_angles' value and checks that its value is
+    acceptable.
 get_pts_per_angle: Gets 'pts_per_angle' value and checks that its value is
-acceptable.
+    acceptable.
+get_max_structures: Gets 'max_structures' value and checks that its value is
+    acceptable.
 get_coll_thrsld: Gets 'coll_thrsld' value and checks that its value is
-acceptable.
-get_screen_rmsd: Gets 'screen_rmsd' value and checks that its value is
-acceptable.
-get_coll_bottom_z: Gets 'coll_bottom_z' value and checks that its value is
-acceptable.
+    acceptable.
+get_min_coll_height: Gets 'coll_bottom_z' value and checks that its value is
+    acceptable.
+get_exclude_ads_ctr: Gets 'exclude_ads_ctr' value and checks that its value is
+    acceptable.
+get_H_donor: Gets 'H_donor' value and checks that its value is
+    acceptable.
+get_H_acceptor: Gets 'H_acceptor' value and checks that its value is
+    acceptable.
+get_use_molec_file: Gets 'use_molec_file' value and checks that its value is
+    acceptable.
+
+Functions to read parameters in the Refinement section
+------------------------------------------------------
 get_refine_inp_file: Gets 'refine_inp_file' value and checks that its value is
-acceptable.
+    acceptable.
 get_energy_cutoff: Gets 'energy_cutoff' value and checks that its value is
-acceptable.
+    acceptable.
+
+read_input: Directs the reading of the parameters in the input file
 """
 import os.path
 import logging
@@ -54,7 +97,7 @@ logger = logging.getLogger('DockOnSurf')
 
 dos_inp = ConfigParser(inline_comment_prefixes='#',
                        empty_lines_in_values=False)
-
+# Define new answers to be interpreted as True or False.
 new_answers = {'n': False, 'none': False, 'nay': False,
                'y': True, 'sí': True, 'aye': True, 'sure': True}
 for answer, val in new_answers.items():
@@ -64,6 +107,7 @@ turn_false_answers = [answer for answer in dos_inp.BOOLEAN_STATES
 turn_true_answers = [answer for answer in dos_inp.BOOLEAN_STATES
                      if dos_inp.BOOLEAN_STATES[answer]]
 
+# Template error messages to be customized in place.
 no_sect_err = "Section '%s' not found on input file"
 no_opt_err = "Option '%s' not found on section '%s'"
 num_error = "'%s' value must be a %s"
@@ -73,7 +117,7 @@ num_error = "'%s' value must be a %s"
 
 def str2lst(cmplx_str, func=int):  # TODO: enable deeper level of nested lists
     # TODO Treat all-enclosing parenthesis as a list instead of list of lists.
-    """Converts a string of integers, and groups of them, to a list.
+    """Converts a string of integers/floats, and groups of them, to a list.
 
     Keyword arguments:
     @param cmplx_str: str, string of integers (or floats) and groups of them
@@ -159,7 +203,14 @@ def check_expect_val(value, expect_vals, err_msg=None):
     return True
 
 
-def check_inp_files(inp_files, code, potcar_dir=None):
+def check_inp_files(inp_files, code: str, potcar_dir=None):
+    """Checks if the CP2K/VASP input files are consistent.
+
+    @param inp_files: List of input files
+    @param code: The code for which the input files are for (VASP or CP2K).
+    @param potcar_dir: The path where POTCARs are found
+    @return: None
+    """
     if code == 'cp2k':
         from pycp2k import CP2K
         if not isinstance(inp_files, str):
@@ -184,7 +235,7 @@ def check_inp_files(inp_files, code, potcar_dir=None):
                                "also enabled via the 'potcar_dir' keyword. The "
                                "POTCAR specified as input_file will be used "
                                "instead of the auto-generated one.")
-        # Check that it inp_files is a list of file paths
+        # Check that if inp_files is a list of file paths
         if not isinstance(inp_files, list) and all(isinstance(inp_file, str)
                                                    for inp_file in inp_files):
             err_msg = "'inp_files' should be a list of file names/paths"
@@ -306,6 +357,7 @@ def get_project_name():
 
 
 def get_relaunch_err():
+    # WARNING: OPTION NOT IMPLEMENTED
     relaunch_err_vals = ['geo_not_conv']
     relaunch_err = dos_inp.get('Global', 'relaunch_err',
                                fallback="False")
@@ -815,8 +867,15 @@ def get_energy_cutoff():
 # Read input parameters
 
 def read_input(in_file):
+    """Directs the reading of the parameters in the input file.
+
+    @param in_file: The path to the DockOnSurf input file.
+    @return inp_vars: Dictionary with the values for every option in the input
+    file.
+    """
     from modules.formats import adapt_format
 
+    # Checks for errors in the Input file.
     err_msg = False
     try:
         dos_inp.read(in_file)
@@ -851,7 +910,7 @@ def read_input(in_file):
             logger.error(no_opt_err % (opt, 'Global'))
             raise NoOptionError(opt, 'Global')
 
-    # Gets which sections are to be carried out
+    # Mandatory options
     isolated, screening, refinement = get_run_type()
     inp_vars['isolated'] = isolated
     inp_vars['screening'] = screening
@@ -882,7 +941,6 @@ def read_input(in_file):
             logger.error(no_sect_err % 'Isolated')
             raise NoSectionError('Isolated')
         # Mandatory options
-        # Checks whether the mandatory options are present.
         iso_mand_opts = ['isol_inp_file', 'molec_file']
         for opt in iso_mand_opts:
             if not dos_inp.has_option('Isolated', opt):
@@ -929,6 +987,7 @@ def read_input(in_file):
             raise NoSectionError('Screening')
         # Mandatory options:
         # Checks whether the mandatory options are present.
+        # Mandatory options
         screen_mand_opts = ['screen_inp_file', 'surf_file', 'sites',
                             'molec_ctrs']
         for opt in screen_mand_opts:
@@ -946,7 +1005,6 @@ def read_input(in_file):
         inp_vars['molec_ctrs'] = get_molec_ctrs()
 
         # Checks for PBC
-        # Checks for PBC
         atms = adapt_format('ase', inp_vars['surf_file'],
                             inp_vars['special_atoms'])
         if inp_vars['code'] == 'vasp' and np.linalg.det(atms.cell) == 0.0 \
diff --git a/modules/formats.py b/modules/formats.py
index c652a45..436c26d 100644
--- a/modules/formats.py
+++ b/modules/formats.py
@@ -1,4 +1,4 @@
-"""Module for the interconversion between different kinds of atomic data.
+"""Module for the conversion between different kinds of atomic data.
 
 functions:
 confs_to_mol_list: Converts the conformers inside a rdkit.Mol object to a list
@@ -9,9 +9,11 @@ add_special_atoms: Allows ase to use custom elements with symbols not in the
 adapt_format: Converts the coordinate files into a required library object type.
 read_coords_cp2k: Reads the coordinates from a CP2K restart file and returns an
     ase.Atoms object.
-collect_coords: Directs the reading of coordinates on a set of subdirectories.
+read_coords_vasp: Reads the coordinates from VASP OUTCAR file and returns an
+    ase.Atoms object.
 read_energy_cp2k: Reads the CP2K out file and returns its final energy.
-collect_energies: Directs the reading of energies on a set of subdirectories.
+collect_confs: Reads the coordinates and energies of a list of finished
+    calculations.
 """
 
 import logging
@@ -89,9 +91,9 @@ def adapt_format(requirement, coord_file, spec_atms=tuple()):
     Depending on the library required to use and the file type, it converts the
     coordinate file into a library-workable object.
     @param requirement: str, the library for which the conversion should be
-    made. Accepted values: 'ase', 'rdkit'.
+        made. Accepted values: 'ase', 'rdkit'.
     @param coord_file: str, path to the coordinates file aiming to convert.
-    Accepted file tyoes: 'xyz', 'mol'.
+        Accepted file formats: all file formats readable by ase.
     @param spec_atms: List of tuples containing pairs of new/traditional
         chemical symbols.
     @return: an object the required library can work with.
@@ -234,9 +236,9 @@ def collect_confs(dir_list, code, run_type, spec_atms=tuple()):
                                         spec_atms)
             # Assign energy
             for fil in os.listdir(conf_path):
-                if is_binary(conf_path+fil):
+                if is_binary(conf_path + fil):
                     continue
-                conf_energy = read_energy_cp2k(conf_path+fil)
+                conf_energy = read_energy_cp2k(conf_path + fil)
                 if conf_energy is not None:
                     ase_atms.info["energy"] = conf_energy
                     break
@@ -252,4 +254,3 @@ def collect_confs(dir_list, code, run_type, spec_atms=tuple()):
             logger.error(err_msg)
             raise NotImplementedError(err_msg)
     return atoms_list
-
diff --git a/modules/isolated.py b/modules/isolated.py
index 8ea63e0..8b21536 100644
--- a/modules/isolated.py
+++ b/modules/isolated.py
@@ -3,9 +3,9 @@
 functions:
 remove_C_linked_Hs: Removes hydrogens bonded to a carbon atom from a molecule.
 gen_confs: Generate a number of conformers in random orientations.
-get_rmsd: Gets the rmsd matrix of the conformers in a rdkit mol object.
 get_moments_of_inertia: Computes moments of inertia of the given conformers.
-mmff_opt_confs: Optimizes the geometry of the given conformers and returns the
+get_moments_of_inertia: Computes the moments of inertia of the given conformers.
+pre_opt_confs: Optimizes the geometry of the given conformers and returns the
     new mol object and the energies of its conformers.
 run_isolated: directs the execution of functions to achieve the goal
 """
@@ -64,7 +64,7 @@ def gen_confs(mol: Chem.rdchem.Mol, num_confs: int):
 
 
 def get_moments_of_inertia(mol: Chem.rdchem.Mol):
-    """Computes the moments of inertia of the given conformers
+    """Computes the moments of inertia of the given conformers.
 
     @param mol: rdkit mol object of the relevant molecule.
     @return numpy array 2D: The inner array contains the moments of inertia for
@@ -144,16 +144,21 @@ def run_isolated(inp_vars):
     from modules.refinement import select_stable_confs
 
     logger.info('Carrying out procedures for the isolated molecule.')
+    # Read the molecule
     rd_mol = adapt_format('rdkit', inp_vars['molec_file'],
                           inp_vars['special_atoms'])
+    # Generate conformers
     confs = gen_confs(rd_mol, inp_vars['num_conformers'])
+    # Pre-optimizes conformers
     if inp_vars['pre_opt']:
         confs, confs_ener = pre_opt_confs(confs, inp_vars['pre_opt'])
     else:
         confs_ener = pre_opt_confs(confs, max_iters=0)
     conf_list = confs_to_mol_list(confs)
+    # Calculates RMSD matrix of the conformers
     rmsd_mtx = get_rmsd(conf_list)
     confs_moi = get_moments_of_inertia(confs)
+    # Clusters the conformers and selects a representative
     exemplars = clustering(rmsd_mtx)
     mol_list = confs_to_mol_list(confs, exemplars)
     ase_atms_list = [rdkit_mol_to_ase_atoms(mol) for mol in mol_list]
@@ -162,6 +167,7 @@ def run_isolated(inp_vars):
                   "dockonsurf.inp"
         logger.error(err_msg)
         raise ValueError(err_msg)
+    # Runs the jobs.
     run_calc('isolated', inp_vars, ase_atms_list)
     logger.info("Finished the procedures for the isolated molecule section. ")
     if inp_vars["batch_q_sys"]:
diff --git a/modules/utilities.py b/modules/utilities.py
index 0a547e4..fdee900 100644
--- a/modules/utilities.py
+++ b/modules/utilities.py
@@ -4,6 +4,12 @@ logger = logging.getLogger('DockOnSurf')
 
 
 def tail(f, lines=20):
+    """Returns the specified last number of lines of a file.
+
+    @param f: The file to retrieve the last lines from.
+    @param lines: The number of lines to be retrieved.
+    @return str: The last number of lines
+    """
     total_lines_wanted = lines
 
     block_size = 1024
@@ -29,6 +35,7 @@ def tail(f, lines=20):
 
 def check_bak(file_name):
     """Checks if a file already exists and backs it up if so.
+
     @param file_name: file to be checked if exists
     """
     import os
@@ -83,6 +90,11 @@ def try_command(command, expct_error_types: list, *args, **kwargs):
 
 
 def _human_key(key):
+    """Function used as sorting strategy where numbers are sorted human-wise.
+
+    @param key:
+    @return:
+    """
     import re
     parts = re.split('(\d*\.\d+|\d+)', key)
     return tuple((e.swapcase() if i % 2 == 0 else float(e))
@@ -90,6 +102,11 @@ def _human_key(key):
 
 
 def is_binary(file):
+    """Checks if a file is a text file or a binary one.
+
+    @param file:
+    @return:
+    """
     try:
         with open(file, "r") as fh:
             fh.read(50)
