Source code for bonafide.utils.helper_functions
"""General helper functions for small common tasks."""
import glob
import inspect
import os
from typing import Any, Dict, List, Optional, Tuple
[docs]
def flatten_dict(dictionary: Dict[str, Any], all_keys: List[str]) -> List[str]:
"""Flatten a nested dictionary and return a list of all keys.
The input dictionary is recursively traversed, and all keys are collected. The keys are
converted to lowercase to ensure uniformity.
Parameters
----------
dictionary : Dict[str, Any]
The dictionary to be flattened.
all_keys : List[str]
A list to store all keys found in the dictionary.
Returns
-------
List[str]
A list of all keys in the dictionary.
"""
all_keys.extend([str(k).lower() for k in list(dictionary.keys())])
for value in dictionary.values():
if isinstance(value, dict):
all_keys = flatten_dict(value, all_keys)
return all_keys
[docs]
def clean_up(to_be_removed: List[str]) -> None:
"""Remove temporary files that should not be kept within the current working directory.
All files that match the patterns specified are deleted.
Parameters
----------
to_be_removed : List[str]
A list of glob patterns that match the files to be removed.
Returns
-------
None
"""
for pattern in to_be_removed:
for file in glob.glob(pattern):
if os.path.isfile(file):
os.remove(file)
[docs]
def standardize_string(inp_data: Any, case: str = "lower") -> str:
"""Standardize a string by removing leading and trailing whitespace and converting it to
lowercase or uppercase.
Parameters
----------
inp_data : Any
The input data to be standardized.
case : str, optional
The case to convert the string to, either "lower" or "upper", by default "lower".
Returns
-------
str
The standardized string.
"""
if case == "lower":
return str(inp_data).strip().lower()
if case == "upper":
return str(inp_data).strip().upper()
return str(inp_data).strip()
[docs]
def matrix_parser(
files_lines: List[str], n_atoms: int
) -> Tuple[Optional[List[List[float]]], Optional[str]]:
"""Parse a 2D matrix from the lines of a file.
The matrix must be in this format:
.. code-block:: text
1 2 3 4
1 0.1 0.2 0.3 0.4
2 0.5 0.6 0.7 0.8
3 0.9 1.0 1.1 1.2
4 1.3 1.4 1.5 1.6
5 1.7 1.8 1.9 2.0
6 2.1 2.2 2.3 2.4
5 6
1 2.5 2.6
2 2.7 2.8
3 2.9 3.0
4 3.1 3.2
5 3.3 3.4
6 3.5 3.6
An error message is returned if the parsing fails or the number of elements per row is
inconsistent.
Parameters
----------
files_lines : List[str]
The respective lines of the file with the matrix data.
n_atoms : int
The number of atoms in the molecule.
Returns
-------
Tuple[Optional[List[List[float]]], Optional[str]]
A tuple containing:
* the parsed matrix as a list of lists of floats, or ``None`` if an error
occurred, and
* an error message if applicable (``None`` if no error occurred).
"""
matrix_block: List[List[float]] = [[] for _ in range(n_atoms)]
_errmsg = None
counter = 0
# Read the matrix elements
try:
for line in files_lines[1:]:
if line.strip() == "":
break
if "." not in line:
counter = 0
continue
matrix_block[counter].extend([float(x) for x in line.split()][1:])
counter += 1
except Exception as e:
_errmsg = f"error while parsing the 2D matrix: {e}"
return None, _errmsg
# Check the parsed data
for row in matrix_block:
if len(row) != n_atoms:
_errmsg = "error while parsing the 2D matrix: inconsistent number of elements per row."
return None, _errmsg
return matrix_block, _errmsg
[docs]
def get_function_or_method_name() -> str:
"""Get the name of the calling function or method.
Returns
-------
str
The name of the calling function or method, or "unknown_function_or_method" if unavailable.
"""
frame = inspect.currentframe()
if frame is None:
return "unknown_function_or_method"
caller_frame = frame.f_back
if caller_frame is None:
return "unknown_function_or_method"
return caller_frame.f_code.co_name