Source code for geom.functions.general

import sys
import os
import ast

from geom.classes import parameters
from geom.functions import output, create_geom

# -------------------------------------------------------------------------------------
[docs] def read_command_line(argv, inp): """ Parses command-line arguments and determines the operation mode. Args: argv (list[str]): List of command-line arguments. inp (input_class): An instance containing input parameters. Returns: None: Calls the appropriate parsing function or displays help. Notes: - If no arguments are provided, an error is raised. - Recognizes different command-line options and triggers the corresponding function. - Prints help if `-h` or `-help` is passed. """ if len(argv) < 2: output.error("Missing command line arguments.") command = argv[1] if command in ['-h', '-help']: print_help() elif command == '-t' or command =='-t1' or command == '-tc': parse_translation(argv, inp) elif command == '-r' or command =='-r1': parse_rotation(argv, inp) elif command == '-min': parse_min(argv,inp) elif command == '-c': parse_center(argv,inp) elif command == '-mirror': parse_mirror(argv, inp) elif command == '-create': parse_create(argv, inp) elif command == '-merge': parse_merge(argv,inp) elif command == '-rdkit': parse_rdkit(argv,inp) else: output.error(f'Option "{command}" not recognized. Try python3 geom -h')
# ------------------------------------------------------------------------------------- # -------------------------------------------------------------------------------------
[docs] def parse_translation(argv, inp): """ Parses translation-related command-line arguments. Args: argv (list[str]): List of command-line arguments. inp (input_class): An instance containing input parameters. Returns: None: Sets translation-related attributes in `inp`. Notes: - Handles both controlled distance translation (`-t`), simple shift translation (`-t1`), and translation of geometrical center to the center of coordinates (`-tc`). - Extracts input filenames, translation parameters, and verbosity settings. """ inp.translate = True if argv[1] == '-t': inp.translate_controlled_distance = True inp.distances_input = str(argv[2]) inp.geom1_file = str(argv[3]) inp.origin_CM_1 = str(argv[4]) inp.geom2_file = str(argv[5]) inp.origin_CM_2 = str(argv[6]) inp.dir_axis_input = str(argv[7]) inp.verbose_inp = str(argv[8]) if (inp.origin_CM_1 == 'origin_CM_1_yes'): inp.move_geom_1_to_000 = True if (inp.origin_CM_2 == 'origin_CM_2_yes'): inp.move_geom_2_to_000 = True if (inp.verbose_inp == 'verbose_yes'): inp.verbose = True elif argv[1] == '-t1': inp.translate_1 = True inp.shift_t1 = float(argv[2]) inp.geom_file = str(argv[3]) inp.origin_CM = str(argv[4]) inp.dir_axis_input = str(argv[5]) if (inp.origin_CM == 'origin_CM_yes'): inp.move_geom_to_000 = True elif argv[1] == '-tc': inp.translate_center = True inp.geom_file = str(argv[2])
# -------------------------------------------------------------------------------------
[docs] def parse_rotation(argv, inp): """ Parses rotation-related command-line arguments. Args: argv (list[str]): List of command-line arguments. inp (input_class): An instance containing input parameters. Returns: None: Sets rotation-related attributes in `inp`. Notes: - Handles both list-based rotation (`-r`) and single-angle rotation (`-r1`). - Extracts input filenames and rotation parameters. """ inp.rotate = True if argv[1] == '-r': inp.rotate_angles = True inp.angles_input = str(argv[2]) inp.geom_file = str(argv[3]) inp.origin_CM = str(argv[4]) inp.dir_axis_input = str(argv[5]) if (inp.origin_CM == 'origin_CM_yes'): inp.move_geom_to_000 = True elif argv[1] == '-r1': inp.rotate_1 = True inp.angle = float(argv[2]) inp.geom_file = str(argv[3]) inp.origin_CM = str(argv[4]) inp.dir_axis_input = str(argv[5]) if (inp.origin_CM == 'origin_CM_yes'): inp.move_geom_to_000 = True
# -------------------------------------------------------------------------------------
[docs] def parse_mirror(argv, inp): """ Parses command-line arguments for performing a mirror reflection. Args: argv (list[str]): List of command-line arguments. inp (input_class): An instance containing input parameters. Returns: None: Sets mirroring attributes in `inp`. """ inp.small_tasks = True inp.geom_specular = True inp.geom_file = str(argv[2])
# -------------------------------------------------------------------------------------
[docs] def parse_merge(argv, inp): """ Parses command-line arguments for merging geometries. Args: argv (list[str]): List of command-line arguments. inp (input_class): An instance containing input parameters. Returns: None: Sets merging attributes in `inp`. Notes: - Requires two geometry files and a cutoff distance. """ inp.small_tasks = True inp.merge = True inp.geom1_file = str(argv[2]) inp.geom2_file = str(argv[3]) inp.merge_cutoff = float(argv[4])
# -------------------------------------------------------------------------------------
[docs] def parse_rdkit(argv, inp): """ TO DO """ # Sanitary check if "-i" not in argv: output.error('Missing "-i" option for parsing input.') inp.rdkit = True # Visualization if "-vis2d" in argv: inp.rdkit_visualize_2d = True if "-vis3d" in argv: inp.rdkit_visualize_3d = True if "-stereo" in argv: inp.stereo_annotations = True if "-index" in argv: inp.atom_index = True if "-bw" in argv: inp.rdkit_bw = True if "-rm_H" or "-rm_h" in argv: inp.remove_H = True if "-abbrevs" in argv: inp.rdkit_abbreviations = True if "-aromatic" in argv: inp.check_aromaticity = True if "-match" in argv: inp.rdkit_match = True inp.match_smiles = extract_value(argv, "-match",value_type=str) if (inp.rdkit_visualize_2d or inp.rdkit_visualize_3d): inp.rdkit_visualize = True inp.rdkit_mol_file = argv[3] inp.rdkit_mol_file_extension = inp.rdkit_mol_file[-4:] # File conversion if "-o" in argv: inp.rdkit_file_conversion = True inp.rdkit_output_file = extract_value(argv, "-o",value_type=str) # Force field optimization if "-opt" in argv: inp.rdkit_opt = True inp.rdkit_force_field = extract_value_or_default(argv,"-opt",value_type=str,default="mmff94") if "-o" in argv: inp.rdkit_file_conversion = False # Turn off file conversion else: inp.rdkit_output_file = inp.rdkit_mol_file[:-4] + "_opt.pdb" if any(arg.lower() == "-maxiters" for arg in argv): inp.rdkit_max_iters = extract_value(argv, "-maxIters",value_type=int) # Conformers generation if "-confs" in argv: inp.rdkit_conformers = True inp.rdkit_confs = extract_value_or_default(argv,"-confs",value_type=int,default=50) if "-ext" in argv: inp.rdkit_confs_ext = "." + extract_value(argv,"-ext",value_type=str) if any(arg.lower() == "-prunerms" for arg in argv): inp.rdkit_confs_prune_rms = extract_value(argv, "-pruneRms", value_type=float) inp.rdkit_output_file = inp.rdkit_mol_file[:-4] + inp.rdkit_confs_ext
# -------------------------------------------------------------------------------------
[docs] def parse_min(argv, inp): """ Parses command-line arguments for calculating the minimum distance between two geometries. Args: argv (list[str]): List of command-line arguments. inp (input_class): An instance containing input parameters. Returns: None: Sets minimum distance calculation attributes in `inp`. """ inp.small_tasks = True inp.min_dist = True inp.geom1_file = str(argv[2]) inp.geom2_file = str(argv[3])
# -------------------------------------------------------------------------------------
[docs] def parse_center(argv, inp): """ Parses command-line arguments for computing the geometric center of a structure. Args: argv (list[str]): List of command-line arguments. inp (input_class): An instance containing input parameters. Returns: None: Sets geometric centering attributes in `inp`. """ inp.small_tasks = True inp.geom_center = True inp.geom_file = str(argv[2])
# -------------------------------------------------------------------------------------
[docs] def parse_create(argv, inp): """ Parses command-line arguments for generating different types of geometries. Args: argv (list[str]): List of command-line arguments. inp (input_class): An instance containing input parameters. Returns: None: Sets attributes in `inp` for geometry generation. Notes: - Handles the creation of graphene structures, nanoparticles, and bulk metal structures. - Validates input parameters and extracts atomic and structural properties. """ inp.create_geom = True # Extract parameters param = parameters.parameters() # Determine the script's location script_path = os.path.abspath(__file__) # Get the directory containing the script script_dir = os.path.dirname(script_path) # Get upper directory base_dir = os.path.dirname(script_dir) if (argv[2] == '-graphene'): inp.gen_graphene = True inp.create_ase_bulk = True inp.atomtype = "c" inp.graphene_structure = argv[3] if inp.graphene_structure not in inp.graphene_structures: output.error(f'Requested graphene structure "{inp.graphene_structure}" not recognised') elif inp.graphene_structure == "rib": inp.X_length = float(argv[4]) inp.Y_length = float(argv[5]) elif inp.graphene_structure == 'disk': inp.radius = float(argv[4]) elif inp.graphene_structure == 'ring': inp.radius_out = float(argv[4]) inp.radius_in = float(argv[5]) if (inp.radius_in >= inp.radius_out): output.error(f'Inner radius must be smaller than outer radius.') elif inp.graphene_structure == 'triangle': inp.graphene_edge_type = argv[4] inp.side_length = float(argv[5]) if inp.graphene_edge_type not in inp.graphene_edge_types: output.error(f'Requested edge type "{inp.graphene_edge_type}" not recognised') else: output.error(f'Create graphene option "{inp.graphene_structure}" not recognized. Try python3 geom -h') # Create bulk graphene dynamically create_geom.create_ase_bulk_graphene(inp, base_dir) else: if ('-core' and '-shell') in argv: inp.gen_core_shell = True elif ('-continuum') in argv: inp.gen_3d_mesh = True elif ("-pencil") in argv: inp.gen_pencil = True elif ("-pentbipyramid") in argv: inp.gen_pentbipyramid = True else: inp.atomtype = argv[3].lower() if inp.atomtype not in param.metal_atomtypes: output.error(f'Atom Type "{argv[3]}" not recognised') check_FCC_or_BCC(inp.atomtype) if (argv[2] == '-sphere'): if (inp.gen_core_shell): inp.gen_sphere_core_shell = True inp.create_ase_bulk = True inp.atomtype_in = argv[4].lower() inp.radius_in = float(argv[5]) inp.atomtype_out = argv[7].lower() inp.radius_out = float(argv[8]) if (inp.atomtype_in not in inp.atomtypes_core_shell): output.error(f'Core atom type "{inp.atomtype_in}" not supported.') elif (inp.atomtype_out not in inp.atomtypes_core_shell): output.error(f'Shell atom type "{inp.atomtype_out}" not supported.') elif (inp.atomtype_in == inp.atomtype_out): output.error(f"Core and shell atom types coincide.") if inp.radius_in >= inp.radius_out: output.error(f'Shell radius must be greater than core radius.') # Set to create bulk ase geometry inp.atomtype = inp.atomtype_out inp.radius = inp.radius_out elif (inp.gen_3d_mesh): inp.gen_3d_mesh_sphere = True inp.create_ase_bulk = False inp.radius = float(argv[4]) inp.mesh_size = float(argv[5]) inp.mesh_output = f"results_geom/sphere_r_{inp.radius}_mesh_size_{inp.mesh_size}.msh" else: inp.gen_sphere = True inp.create_ase_bulk = True inp.radius = float(argv[4]) elif (argv[2] == '-rod'): if (inp.gen_core_shell): inp.gen_rod_core_shell = True inp.create_ase_bulk = True inp.main_axis = argv[3].lower() inp.atomtype_in = argv[5].lower() inp.rod_length_in = float(argv[6]) inp.rod_width_in = float(argv[7]) inp.atomtype_out = argv[9].lower() inp.rod_length_out = float(argv[10]) inp.rod_width_out = float(argv[11]) if (inp.atomtype_in not in inp.atomtypes_core_shell): output.error(f'Core atom type "{inp.atomtype_in}" not supported.') elif (inp.atomtype_out not in inp.atomtypes_core_shell): output.error(f'Shell atom type "{inp.atomtype_out}" not supported.') elif (inp.atomtype_in == inp.atomtype_out): output.error(f"Core and shell atom types coincide.") if inp.rod_width_in >= inp.rod_length_in: output.error(f"Core rod width must be greater than core length.") if inp.rod_width_out >= inp.rod_length_out: output.error(f"Shell rod width must be greater than shell length.") if inp.rod_width_in >= inp.rod_width_out: output.error(f"Shell rod width must be greater than core rod width.") if inp.rod_length_in >= inp.rod_length_out: output.error(f"Shell rod length must be greater than core rod length.") # Set to create bulk ase geometry inp.atomtype = inp.atomtype_out inp.rod_length = inp.rod_length_out inp.rod_width = inp.rod_width_out elif (inp.gen_3d_mesh): inp.gen_3d_mesh_rod = True inp.create_ase_bulk = False inp.main_axis = argv[4].lower() inp.rod_length = float(argv[5]) inp.rod_width = float(argv[6]) inp.mesh_size = float(argv[7]) if inp.rod_width >= inp.rod_length: output.error(f"Rod width must be smaller than length.") inp.mesh_output = f"results_geom/rod_{inp.main_axis.upper()}_l_{inp.rod_length}_w_{inp.rod_width}_mesh_size_{inp.mesh_size}.msh" else: inp.gen_rod = True inp.create_ase_bulk = True inp.main_axis = argv[4].lower() inp.rod_length = float(argv[5]) inp.rod_width = float(argv[6]) if inp.rod_width >= inp.rod_length: output.error(f"Rod width must be smaller than length.") if inp.main_axis not in inp.axes: output.error(f"Axis {inp.main_axis} not recognized.") elif (argv[2] == '-tip'): inp.gen_tip = True inp.create_ase_bulk = True inp.z_max = float(argv[4]) inp.elliptic_parabola_a = float(argv[5]) inp.elliptic_parabola_b = float(argv[6]) elif (argv[2] == '-pyramid'): inp.gen_pyramid = True inp.create_ase_bulk = True inp.z_max = float(argv[4]) inp.side_length = float(argv[5]) elif (argv[2] == '-pentpyramid'): inp.gen_pentpyramid = True inp.create_ase_bulk = True inp.z_max = float(argv[5]) inp.base_width = float(argv[4]) inp.main_axis = "z" elif (argv[2] == '-microscope'): inp.gen_microscope = True inp.create_ase_bulk = True inp.z_max_paraboloid = float(argv[4]) inp.elliptic_parabola_a = float(argv[5]) inp.elliptic_parabola_b = float(argv[6]) inp.z_max_pyramid = float(argv[7]) inp.side_length = float(argv[8]) elif (argv[2] == '-cone'): inp.gen_cone = True inp.create_ase_bulk = True inp.z_max = float(argv[4]) inp.radius = float(argv[5]) elif (argv[2] == '-ico'): inp.gen_icosahedra = True inp.radius = float(argv[4]) elif (argv[2] == '-cto'): inp.gen_cto = True inp.radius = float(argv[4]) elif (argv[2] == '-idh'): inp.gen_idh = True inp.radius = float(argv[4]) elif (argv[2] == '-bipyramid'): inp.gen_bipyramid = True inp.create_ase_bulk = True inp.bipyramid_width = float(argv[4]) / 2.0 inp.bipyramid_length = float(argv[5]) / 2.0 if inp.bipyramid_width >= inp.bipyramid_length: output.error(f"Bipyramid width must be smaller than length.") # Set to create bulk ase geometry inp.rod_width = inp.bipyramid_width inp.rod_length = inp.bipyramid_length inp.main_axis = "z" elif (inp.gen_pencil): inp.create_ase_bulk = True inp.atomtype_in = argv[4].lower() inp.atomtype_out = argv[6].lower() inp.bipyramid_width = float(argv[7]) / 2.0 inp.bipyramid_length = float(argv[8]) / 2.0 inp.rod_width = float(argv[9]) / 2.0 inp.rod_length = float(argv[10]) / 2.0 if inp.bipyramid_length <= inp.bipyramid_width: output.error(f"Pencil shell outer radius must be greater than core inner radius.") if inp.bipyramid_length > inp.rod_length: output.error(f"Pencil shell outer length must be greater than or equal to core inner length.") if inp.bipyramid_width > inp.rod_width: output.error(f"Pencil shell outer width must be greater than or equal to core inner width.") if any("-fullshell" in arg.lower() for arg in argv): inp.pencil_type = "fullshell" elif any("-halfshell" in arg.lower() for arg in argv): inp.pencil_type = "halfshell" else: output.error("Pencil shell type not recognised. \n\n" " Options: \n\n" " -fullshell\n\n" \ " -halfshell") if (inp.atomtype_in not in inp.atomtypes_core_shell): output.error(f'Core atom type "{inp.atomtype_in}" not supported.') elif (inp.atomtype_out not in inp.atomtypes_core_shell): output.error(f'Shell atom type "{inp.atomtype_out}" not supported.') elif (inp.atomtype_in == inp.atomtype_out): output.error(f"Core and shell atom types coincide.") # Set to create bulk ase geometry inp.atomtype = inp.atomtype_out inp.rod_width = inp.rod_width*2.0 inp.rod_length = inp.rod_length*2.0 inp.main_axis = "z" elif (inp.gen_pentbipyramid): inp.create_ase_bulk = True inp.atomtype_in = argv[4].lower() inp.atomtype_out = argv[6].lower() inp.base_width = float(argv[7]) inp.z_max = float(argv[8]) inp.rod_width = float(argv[9]) / 2.0 inp.rod_length = float(argv[10]) / 2.0 if inp.rod_width >= inp.rod_length: output.error(f"Outer rod width must be smaller than outer rod length.") if any("-fullshell" in arg.lower() for arg in argv): inp.pentbipyramid_type = "fullshell" elif any("-halfshell" in arg.lower() for arg in argv): inp.pentbipyramid_type = "halfshell" else: output.error("Pentbipyramid shell type not recognised. \n\n" " Options: \n\n" " -fullshell\n\n" " -halfshell") if inp.atomtype_in not in inp.atomtypes_core_shell: output.error(f'Core atom type "{inp.atomtype_in}" not supported.') elif inp.atomtype_out not in inp.atomtypes_core_shell: output.error(f'Shell atom type "{inp.atomtype_out}" not supported.') elif inp.atomtype_in == inp.atomtype_out: output.error(f"Core and shell atom types coincide.") # Set to create bulk ase geometry for outer rod inp.atomtype = inp.atomtype_out inp.rod_width = inp.rod_width * 2.0 inp.rod_length = inp.rod_length * 2.0 inp.main_axis = "z" else: output.error(f'Create nanoparticle option "{argv[2]}" not recognized. Try python3 geom -h') # Create bulk metal dynamically if inp.create_ase_bulk: create_geom.create_ase_bulk_metal(inp, base_dir) # Alloy case parse_alloy_arguments(argv, inp, output) # Create dimer case parse_dimer_argument(argv, inp, output) # Create bowtie case parse_bowtie_argument(argv, inp, output)
# -------------------------------------------------------------------------------------
[docs] def parse_dimer_argument(argv, inp, output): """ Parses dimer-related command-line arguments and updates the `inp` object. Args: argv (list[str]): Command-line arguments list. inp (input_class): The input class instance where dimer attributes are stored. output (module): The output module for error handling. Returns: None: Updates inp.create_dimer, inp.distances, and inp.dir_axis_input. Notes: - Searches for "-dimer" in argv. - Reads the next argument as a float and stores it in inp.distances as a list. - Reads the following argument as an axis specification (+x, -y, +z, etc.). - Ensures the extracted values are valid. """ if "-dimer" in argv: inp.create_dimer = True idx = argv.index("-dimer") # Ensure there are at least two more arguments after "-dimer" (distance & axis) if idx + 2 >= len(argv): output.error("Missing dimer distance value or axis after '-dimer'.") # Parse the next argument as a float and store it in a list try: inp.distances = [float(argv[idx + 1])] except ValueError: output.error(f'Invalid dimer distance "{argv[idx + 1]}". It must be a number.') # Ensure the dimer distance is greater than 0 if inp.distances[0] <= 0: output.error(f'Dimer distance "{inp.distances[0]}" must be greater than zero.') # Extract and validate the axis argument inp.dir_axis_input = argv[idx + 2].lower() # Call axis validation function check_dir_axis(inp)
# -------------------------------------------------------------------------------------
[docs] def parse_bowtie_argument(argv, inp, output): """ Parses bowtie-related command-line arguments and updates the `inp` object. Args: argv (list[str]): List of command-line arguments. inp (input_class): An instance of the input class where bowtie attributes are stored. output (module): The output module used for error handling. Returns: None: This function does not return a value but updates `inp.create_bowtie`, `inp.distances`, and `inp.dir_axis_input`. Raises: ValueError: If bowtie arguments are missing or contain invalid values. Notes: - This function checks if "-bowtie" is present in `argv`. - If found, it ensures that the bowtie structure is only available for tip, pyramid (square/pentagonal base) , cone, and microscope structures. - The function then extracts the bowtie distance (must be a positive float). - If any validation fails, it calls `output.error()` to handle errors. """ if "-bowtie" in argv: # Option only available for tip, pyramid, cone, microscope structures if (not inp.gen_tip and not inp.gen_pyramid and not inp.gen_pentpyramid and not inp.gen_cone and not inp.gen_microscope): output.error(f'bowtie structure only available for tip, pyramid (square/pentagonal base), cone, and microscope structures.') inp.create_bowtie = True idx = argv.index("-bowtie") # Ensure there are at least two more arguments after "-bowtie" (distance & axis) if idx + 1 >= len(argv): output.error("Missing bowtie distance value.") # Parse the next argument as a float and store it in a list try: inp.distances = [float(argv[idx + 1])] except ValueError: output.error(f'Invalid dimer distance "{argv[idx + 1]}". It must be a number.') # Ensure the bowtie distance is greater than 0 if inp.distances[0] <= 0: output.error(f'Bowtie distance "{inp.distances[0]}" must be greater than zero.')
# -------------------------------------------------------------------------------------
[docs] def parse_alloy_arguments(argv, inp, output): """ Parses alloy-related command-line arguments and updates the `inp` object. Args: argv (list[str]): Command-line arguments list. inp (input_class): The input class instance where alloy attributes are stored. output (module): The output module for error handling. Returns: None: Updates `inp` attributes based on the parsed alloy arguments. Notes: - Handles both cases: 1. `-alloy atom_type -percentual float` 2. `-alloy -percentual float` - Ensures `inp.alloy_perc` is a **valid float** and **between 0 and 100**. - If atom type is omitted, it defaults to `inp.atomtype`. """ # Check if "-alloy" exists in argv if "-alloy" in argv: inp.alloy = True idx = argv.index("-alloy") # Get index of "-alloy" # Ensure there is at least one more argument after "-alloy" if idx + 1 >= len(argv): output.error("Missing alloy parameters after '-alloy'.") # Case 1: Next argument is an atom type if argv[idx + 1] != "-percentual": inp.atomtype_alloy = argv[idx + 1].lower() if idx + 2 >= len(argv) or argv[idx + 2] != "-percentual": output.error("Expected '-percentual' after alloy atom type.") percentual_idx = idx + 3 # Alloy percentage should be after "-percentual" # Case 2: No atom type, only "-percentual float" else: percentual_idx = idx + 2 # Alloy percentage should be after "-percentual" # Ensure there's a percentage value if percentual_idx >= len(argv): output.error("Missing alloy percentage value after '-percentual'.") # Extract and validate alloy percentage try: inp.alloy_perc = float(argv[percentual_idx]) except ValueError: output.error(f'Invalid alloy percentage "{argv[percentual_idx]}". It must be a number.') # Validate range if inp.alloy_perc == 0.0 or inp.alloy_perc == 100.0 or inp.alloy_perc < 0.0 or inp.alloy_perc > 100.0: output.error(f'Alloy percentual requested {inp.alloy_perc} %. It must be greater than 0 and lower than 100.') # Validate atom types if not in core-shell mode if not inp.gen_core_shell: if inp.atomtype not in inp.atomtypes_alloys: output.error(f'Alloy atom type "{inp.atomtype}" not supported.') if inp.atomtype_alloy not in inp.atomtypes_alloys: output.error(f'Alloy atom type "{inp.atomtype_alloy}" not supported.') if inp.atomtype_alloy == inp.atomtype: output.error(f'Alloy atom type coincides with original geometry atom type.') # Generate alloy string inp.alloy_string = f'_alloy_{inp.atomtype_alloy}_{inp.alloy_perc:5.2f}_perc'
# -------------------------------------------------------------------------------------
[docs] def check_file_exists(infile): """ Checks if a given file exists. Args: infile (str): Path to the input file. Returns: None: Raises an error if the file is not found. """ if (not os.path.exists(infile)): output.error('file "' + infile + '" not found')
# -------------------------------------------------------------------------------------
[docs] def check_FCC(atomtype,string): """ Checks if the given metallic atom type follows an FCC arrangement. Args: atomtype (str): Type of metal atom. string (str): Structure name being validated. Returns: None: Raises an error if the atom type is not FCC. """ param = parameters.parameters() arrangement = param.atomic_arrangement.get(atomtype) if arrangement != 'FCC': output.error(f'"{atomtype.capitalize()}" presents {arrangement} arrangement. FCC is required to create {string}.')
# -------------------------------------------------------------------------------------
[docs] def check_FCC_or_BCC(atomtype): """ Checks if the given metallic atom type follows either an FCC or BCC arrangement. Args: atomtype (str): Type of metal atom. Returns: None: Raises an error if the atom type is not FCC or BCC. """ param = parameters.parameters() arrangement = param.atomic_arrangement.get(atomtype) if arrangement !='FCC' and arrangement != 'BCC': output.error(f'"{atomtype.capitalize()}" presents {arrangement} arrangement. Only FCC and BCC are supported for metal creation.')
# -------------------------------------------------------------------------------------
[docs] def check_file_extension(infile,extension): """ Checks if the input file has the correct extension. Args: infile (str): Path to the input file. extension (str): Expected file extension. Returns: None: Raises an error if the file does not have the expected extension. """ i = len(extension) if (infile[-i:] != extension): output.error('extension "' + extension + '" not found in file "' + infile + '"' )
# -------------------------------------------------------------------------------------
[docs] def check_file_extension_rdkit(infile,accepted_extensions): """ Checks if the input file has a RDKit-supported extension. Args: infile (str): Path to the input file. accepted_extensions (list): List of accepted file extensions for RDKit. Returns: None: Raises an error if the file extension is not supported. """ file_extension = infile[-4:].lower() if file_extension not in accepted_extensions: msg = ( f"File extension '{file_extension}' for RDKit not supported.\n\n" " Accepted file extensions:" + "".join(f"\n - {ext}" for ext in accepted_extensions) ) output.error(msg)
# -------------------------------------------------------------------------------------
[docs] def check_accepted_parameters(param,accepted_params,label="Parameter"): """ Checks if a parameter is in the list of accepted parameters. Args: param (str): Parameter value to check. accepted_params (list): List of accepted parameter values. label (str, optional): Label for the parameter in error messages. Returns: None: Raises an error if the parameter is not in accepted parameters. """ if param not in accepted_params: msg = ( f"{label.capitalize()} '{param}' not supported.\n\n" " Accepted entries:" + "".join(f"\n - {parameter}" for parameter in accepted_params) ) output.error(msg)
# -------------------------------------------------------------------------------------
[docs] def check_dir_axis(inp): """ Validates the direction axis input for translation or rotation. Args: inp (input_class): An instance containing input parameters. Returns: input_class: Updated `inp` with validated direction axis settings. Notes: - Ensures that the axis is properly formatted (e.g., `+x`, `-y`). - Assigns the corresponding numerical translation factor. """ if ((len(inp.dir_axis_input) < 2) or (len(inp.dir_axis_input) > 2) or (inp.dir_axis_input[1] != 'x' and inp.dir_axis_input[1] != 'y' and inp.dir_axis_input[1] != 'z') or (inp.dir_axis_input[0] != '+' and inp.dir_axis_input[0] != '-')): output.error_dir_axis(inp.dir_axis_input) if (inp.dir_axis_input[0] == '+'): inp.direction = 1.0 if (inp.dir_axis_input[0] == '-'): inp.direction = -1.0 if (inp.dir_axis_input[1] =='x') : inp.dir_factor = [inp.direction,0.0,0.0] if (inp.dir_axis_input[1] =='y') : inp.dir_factor = [0.0,inp.direction,0.0] if (inp.dir_axis_input[1] =='z') : inp.dir_factor = [0.0,0.0,inp.direction] return(inp)
# -------------------------------------------------------------------------------------
[docs] def create_results_geom(): """ Creates the `results_geom` directory if it does not already exist. Returns: None: Ensures that output files can be stored in the correct location. """ #if (os.path.exists('results_geom')): # print(' ') # print(' ------------------------------------------------') # print(f' WARNING: "results_geom" folder already exists"') # print(' ------------------------------------------------') # print(' ') # erase_results = input(' Do you want to delete it and continue? (y/n) ') # if(erase_results == "y" or erase_results == "yes"): # os.system(f'rm -rf results_geom') # print(' ') # elif(erase_results == 'n' or erase_results == 'no'): # print(' ') # continue_ = input(' Type "stop" to kill the job, \n' + # ' otherwise type any key to continue ') # print(' ') # if continue_.lower() == 'stop': # print(' Program stopped.') # print(' ') # sys.exit() # else: # print(' ') # print(' I did not understand what you mean by "' + erase_results + '"') # print(' ') # print(' Program stopped.') # print(' ') # sys.exit() if (not(os.path.exists('results_geom'))): os.system('mkdir results_geom')
# -------------------------------------------------------------------------------------
[docs] def extract_string_or_list(argv, flag): """ Extract the argument(s) that follow a CLI flag. Accepts: - <flag> CO -> ["CO"] - <flag> "CO" -> ["CO"] - <flag> CO,OH -> ["CO","OH"] - <flag> ["CO","OH"] -> ["CO","OH"] (quoted or unquoted list) - <flag> [CO, OH] -> ["CO","OH"] (bare items) Returns a list of strings. """ if flag not in argv: return [] i = argv.index(flag) if i + 1 >= len(argv): raise ValueError(f'Missing argument after "{flag}"') # Grab everything after the flag tokens = argv[i+1:] # Reassemble if it's a list starting with '[' if tokens[0].startswith('['): depth = 0 parts = [] for tok in tokens: depth += tok.count('[') depth -= tok.count(']') parts.append(tok) if depth <= 0: break raw = ' '.join(parts).strip() else: raw = tokens[0].strip() # Try Python literal eval try: if raw.startswith('['): val = ast.literal_eval(raw) if isinstance(val, (list, tuple)): return [str(x).strip().strip('"').strip("'") for x in val] return [str(val)] else: if ',' in raw: return [p.strip().strip('"').strip("'") for p in raw.split(',') if p.strip()] return [raw.strip().strip('"').strip("'")] except Exception: # Fallback for [CO, OH] without quotes if raw.startswith('[') and raw.endswith(']'): inner = raw[1:-1] items = [p.strip().strip('"').strip("'") for p in inner.split(',') if p.strip()] return items return [raw.strip().strip('"').strip("'")]
# -------------------------------------------------------------------------------------
[docs] def extract_value(argv, flag, value_type): """ Extract the argument that follows a CLI flag and cast it to a given type. Args: argv (list): Command-line arguments. flag (str): Flag to search for (case-insensitive). value_type (type): Expected Python type (e.g., str, int, float). Returns: The value converted to the expected type. Raises: ValueError: If the flag is present but has no value, or if the value cannot be converted to the expected type. SystemExit: If the flag is not found (via output.error()). """ # Normalize for case-insensitive search argv_lower = [arg.lower() for arg in argv] flag_lower = flag.lower() if flag_lower not in argv_lower: output.error(f'Requested flag "{flag}" not found') i = argv_lower.index(flag_lower) # Case 1: flag is last in argv → no value provided if i + 1 >= len(argv): raise ValueError(f'Missing argument after "{flag}"') token = argv[i + 1] # Case 2: next token looks like another flag → treat as missing if token.startswith("-"): raise ValueError(f'Missing argument after "{flag}" (found another flag \"{token}\")') # Type validation try: if value_type is str: value = token elif value_type is int: value = int(token) elif value_type is float: value = float(token) else: raise TypeError(f'Unsupported value_type "{value_type.__name__}". ' 'Allowed: str, int, float.') except ValueError: raise ValueError(f'Expected a {value_type.__name__} after "{flag}", got "{token}"') return value
# -------------------------------------------------------------------------------------
[docs] def extract_value_or_default(argv, flag, value_type=str, default=None): """ Extract the argument that follows a CLI flag and cast it to a given type. If the flag is absent or has no following value, return the provided default. If the flag is present but the value is not convertible to the requested type, raise ValueError. Args: argv (list[str]): Command-line arguments. flag (str): Flag to search for (case-insensitive). value_type (type): One of {str, int, float}. default (Any): Default value if the flag is missing or has no argument. If not None, its type must match `value_type`. Returns: Any: The value converted to `value_type`, or `default` when appropriate. Raises: TypeError: If `value_type` is unsupported, or `default`'s type doesn't match `value_type`. ValueError: If the flag is present but the following token can't be converted to `value_type`. """ # Validate requested type if value_type not in (str, int, float): raise TypeError(f'Unsupported value_type "{getattr(value_type, "__name__", value_type)}". Allowed: str, int, float.') # Validate default type (allow None) if default is not None and not isinstance(default, value_type): raise TypeError( f'Default value type mismatch for flag "{flag}": ' f'expected {value_type.__name__}, got {type(default).__name__}.' ) # Case-insensitive search for the flag argv_lower = [a.lower() for a in argv] flag_lower = flag.lower() # If flag not present → return default if flag_lower not in argv_lower: return default i = argv_lower.index(flag_lower) # Flag is last token → no value provided → return default if i + 1 >= len(argv): return default token = argv[i + 1] # Next token looks like another flag → treat as missing → return default if token.startswith("-"): return default # Cast to the requested type try: if value_type is str: return token elif value_type is int: return int(token) elif value_type is float: return float(token) except ValueError as e: raise ValueError( f'Invalid {value_type.__name__} value "{token}" provided for flag "{flag}".' ) from e
# -------------------------------------------------------------------------------------
[docs] def check_equal_extensions(infile1,infile2): """ Checks if two input files have the same extension. If yes, raises an error. Args: infile1 (str): Path to the first input file. infile2 (str): Path to the second input file. """ ext1 = infile1[-4:].lower() ext2 = infile2[-4:].lower() if (ext1 == ext2): output.error('input files must have different extensions ("' + ext1 + '" found in both files)')
# -------------------------------------------------------------------------------------