def read_yaml_file( path: str, project_directory: Optional[str] = None, ) -> Union[dict, list]: """ Read a YAML file (usually an input / restart file, but also conformers file) and return the parameters as python variables. Args: path (str): The YAML file path to read. project_directory (str, optional): The current project directory to rebase upon. Returns: Union[dict, list] The content read from the file. """ if project_directory is not None: path = globalize_paths(path, project_directory) if not isinstance(path, str): raise InputError( f'path must be a string, got {path} which is a {type(path)}') if not os.path.isfile(path): raise InputError(f'Could not find the YAML file {path}') with open(path, 'r') as f: content = yaml.load(stream=f, Loader=yaml.FullLoader) return content
def __init__(self, label: str = '', reactants: Optional[List[str]] = None, products: Optional[List[str]] = None, ts_label: Optional[str] = None, rmg_reaction: Optional[Reaction] = None, ts_methods: Optional[List[str]] = None, ts_xyz_guess: Optional[list] = None, multiplicity: Optional[int] = None, charge: int = 0, reaction_dict: Optional[dict] = None, preserve_param_in_scan: Optional[list] = None, ): self.arrow = ' <=> ' self.plus = ' + ' self.r_species = list() self.p_species = list() self.kinetics = None self.rmg_kinetics = None self.long_kinetic_description = '' self.family = None self.family_own_reverse = 0 self.ts_label = ts_label self.dh_rxn298 = None self.rmg_reactions = None self.ts_xyz_guess = ts_xyz_guess or list() self.preserve_param_in_scan = preserve_param_in_scan if reaction_dict is not None: # Reading from a dictionary self.from_dict(reaction_dict=reaction_dict) else: # Not reading from a dictionary self.label = label self.index = None self.ts_species = None self.multiplicity = multiplicity self.charge = charge if self.multiplicity is not None and not isinstance(self.multiplicity, int): raise InputError('Reaction multiplicity must be an integer, got {0} of type {1}.'.format( self.multiplicity, type(self.multiplicity))) self.reactants = reactants self.products = products self.rmg_reaction = rmg_reaction if self.rmg_reaction is None and (self.reactants is None or self.products is None) and not self.label: raise InputError('Cannot determine reactants and/or products labels for reaction {0}'.format( self.label)) self.set_label_reactants_products() self.ts_methods = ts_methods if ts_methods is not None else default_ts_methods self.ts_methods = [tsm.lower() for tsm in self.ts_methods] self.ts_xyz_guess = ts_xyz_guess if ts_xyz_guess is not None else list() self._atom_map = None if len(self.reactants) > 3 or len(self.products) > 3: raise ReactionError(f'An ARC Reaction can have up to three reactants / products. got {len(self.reactants)} ' f'reactants and {len(self.products)} products for reaction {self.label}.') if self.ts_xyz_guess is not None and not isinstance(self.ts_xyz_guess, list): self.ts_xyz_guess = [self.ts_xyz_guess] self.check_atom_balance()
def sort_two_lists_by_the_first( list1: List[Union[float, int, None]], list2: List[Union[float, int, None]], ) -> Tuple[List[Union[float, int]], List[Union[float, int]]]: """ Sort two lists in increasing order by the values of the first list. Ignoring None entries from list1 and their respective entries in list2. The function was written in this format rather the more pytonic ``zip(*sorted(zip(list1, list2)))`` style to accommodate for dictionaries as entries of list2, otherwise a ``TypeError: '<' not supported between instances of 'dict' and 'dict'`` error is raised. Args: list1 (list, tuple): Entries are floats or ints (could also be None). list2 (list, tuple): Entries could be anything. Raises: InputError: If types are wrong, or lists are not the same length. Returns: Tuple[list, list] - Sorted values from list1, ignoring None entries. - Respective entries from list2. """ if not isinstance(list1, (list, tuple)) or not isinstance(list2, (list, tuple)): raise InputError( f'Arguments must be lists, got: {type(list1)} and {type(list2)}') for entry in list1: if not isinstance(entry, (float, int)) and entry is not None: raise InputError( f'Entries of list1 must be either floats or integers, got: {type(entry)}.' ) if len(list1) != len(list2): raise InputError( f'Both lists must be the same length, got {len(list1)} and {len(list2)}' ) # remove None entries from list1 and their respective entries from list2: new_list1, new_list2 = list(), list() for entry1, entry2 in zip(list1, list2): if entry1 is not None: new_list1.append(entry1) new_list2.append(entry2) indices = list(range(len(new_list1))) zipped_lists = zip(new_list1, indices) sorted_lists = sorted(zipped_lists) sorted_list1 = [x for x, _ in sorted_lists] sorted_indices = [x for _, x in sorted_lists] sorted_list2 = [0] * len(new_list2) for counter, index in enumerate(sorted_indices): sorted_list2[counter] = new_list2[index] return sorted_list1, sorted_list2
def __init__(self, output_directory: str, output_dict: dict, bac_type: Optional[str], sp_level: Optional[Level] = None, freq_scale_factor: float = 1.0, species: Type[ARCSpecies] = None, reaction: Type[ARCReaction] = None, species_dict: dict = None, T_min: tuple = None, T_max: tuple = None, T_count: int = 50, three_params: bool = True, ): self.output_directory = output_directory self.output_dict = output_dict self.bac_type = bac_type self.sp_level = sp_level self.freq_scale_factor = freq_scale_factor self.species = species self.reaction = reaction self.species_dict = species_dict self.T_min = T_min self.T_max = T_max self.T_count = T_count self.three_params = three_params if not self.output_directory: raise InputError('A project directory was not provided.')
def parse_1d_scan_energies( path: str) -> Tuple[Optional[List[float]], Optional[List[float]]]: """ Parse the 1D torsion scan energies from an ESS log file. Args: path (str): The ESS log file to parse from. Raises: InputError: If ``path`` is invalid. Returns: Tuple[Optional[List[float]], Optional[List[float]]] The electronic energy in kJ/mol and the dihedral scan angle in degrees. """ if not os.path.isfile(path): raise InputError(f'Could not find file {path}') log = ess_factory(fullpath=path) try: energies, angles = log.load_scan_energies() energies *= 0.001 # convert to kJ/mol angles *= 180 / np.pi # convert to degrees except (LogError, NotImplementedError, ZeroDivisionError): logger.warning(f'Could not read energies from {path}') energies, angles = None, None return energies, angles
def load_families_only(rmgdb, kinetics_families='default'): """ A helper function for loading kinetic families from RMG's database. Args: rmgdb (RMGDatabase): The RMG database instance. kinetics_families (str, optional): Specific kinetics families to load. """ if kinetics_families not in ('default', 'all', 'none'): if not isinstance(kinetics_families, list): raise InputError( "kinetics families should be either 'default', 'all', 'none', or a list of names, e.g.," " ['H_Abstraction','R_Recombination'] or ['!Intra_Disproportionation']." ) logger.debug('\n\nLoading only kinetic families from the RMG database...') rmgdb.load( path=db_path, thermo_libraries=list(), transport_libraries='none', reaction_libraries=list(), seed_mechanisms=list(), kinetics_families=kinetics_families, kinetics_depositories=['training'], depository=False, )
def upload_file( self, remote_file_path: str, local_file_path: str = '', file_string: str = '', ) -> None: """ Upload a local file or contents from a string to the remote server. Args: remote_file_path (str): The path to write into on the remote server. local_file_path (Optional[str]): The local file path to be copied to the remote location. file_string (Optional[str]): The file content to be copied and saved as the remote file. Raises: InputError: If both `local_file_path` or `file_string` are invalid, or `local_file_path` does not exists. ServerError: If the file cannot be uploaded with maximum times to try """ if not local_file_path and not file_string: raise InputError( 'Cannot not upload file to server. Either `file_string` or `local_file_path`' ' must be specified') if local_file_path and not os.path.isfile(local_file_path): raise InputError( f'Cannot upload a non-existing file. ' f'Check why file in path {local_file_path} is missing.') # If the directory does not exist, _upload_file cannot create a file based on the given path remote_dir_path = os.path.dirname(remote_file_path) if not self._check_dir_exists(remote_dir_path): self._create_dir(remote_dir_path) try: if file_string: with self._sftp.open(remote_file_path, 'w') as f_remote: f_remote.write(file_string) else: self._sftp.put(localpath=local_file_path, remotepath=remote_file_path) except IOError: logger.debug( f'Could not upload file {local_file_path} to {self.server}!') raise ServerError( f'Could not write file {remote_file_path} on {self.server}. ')
def compute_thermo(self, kinetics_flag: bool = False, e0_only: bool = False, ) -> None: """ Generate thermodynamic data for a species. Populates the species.thermo attribute. Args: kinetics_flag (bool, optional): Whether this call is used for generating species statmech for a rate coefficient calculation. e0_only (bool, optional): Whether to only run statmech (w/o thermo) to compute E0. """ if not kinetics_flag: # initialize the Arkane species_dict so that species for which thermo is calculated won't interfere # with species used for a rate coefficient calculation. arkane.input.species_dict = dict() if self.sp_level.to_arkane_level_of_theory(variant='AEC', raise_error=False, warn=False) is None: raise ValueError(f'Cannot compute thermo without a valid Arkane Level for AEC.') if self.species is None: raise InputError('Cannot not compute thermo without a species object.') arkane_output_path = self.generate_arkane_species_file(species=self.species, bac_type=self.bac_type) if arkane_output_path is not None: try: arkane_species = arkane_input_species(self.species.label, self.species.arkane_file) except ValueError: arkane_species = arkane.input.species_dict[self.species.label] self.species.rmg_species = Species(molecule=[self.species.mol]) self.species.rmg_species.reactive = True if self.species.mol_list: # add resonance structures for thermo determination arkane_species.molecule = self.species.mol_list self.species.rmg_species.molecule = self.species.mol_list statmech_success = self.run_statmech(arkane_species=arkane_species, arkane_file_path=self.species.arkane_file, arkane_output_path=arkane_output_path, bac_type=self.bac_type, sp_level=self.sp_level, plot=False) if statmech_success: self.species.e0 = arkane_species.conformer.E0.value_si * 0.001 # convert to kJ/mol logger.debug(f'Assigned E0 to {self.species.label}: {self.species.e0:.2f} kJ/mol') if not e0_only: thermo_job = ThermoJob(arkane_species, 'NASA') thermo_job.execute(output_directory=arkane_output_path, plot=True) self.species.thermo = arkane_species.get_thermo_data() if not kinetics_flag: plotter.log_thermo(self.species.label, path=arkane_output_path) else: logger.error(f'Could not run statmech job for species {self.species.label}') clean_output_directory(species_path=os.path.join(self.output_directory, 'Species', self.species.label))
def from_dict(self, reaction_dict: dict): """ A helper function for loading this object from a dictionary in a YAML file for restarting ARC. """ self.index = reaction_dict['index'] if 'index' in reaction_dict else None self.label = reaction_dict['label'] if 'label' in reaction_dict else '' self.multiplicity = reaction_dict['multiplicity'] if 'multiplicity' in reaction_dict else None self.charge = reaction_dict['charge'] if 'charge' in reaction_dict else 0 self.reactants = reaction_dict['reactants'] if 'reactants' in reaction_dict else None self.products = reaction_dict['products'] if 'products' in reaction_dict else None self.family = reaction_dict['family'] if 'family' in reaction_dict else None self.family_own_reverse = reaction_dict['family_own_reverse'] if 'family_own_reverse' in reaction_dict else 0 if 'rmg_reaction' in reaction_dict: self.rmg_reaction_from_str(reaction_string=reaction_dict['rmg_reaction']) else: self.rmg_reaction = None self.set_label_reactants_products() if self.rmg_reaction is None and (self.reactants is None or self.products is None): raise InputError(f'Cannot determine reactants and/or products labels for reaction {self.label}') if self.reactants is None or self.products is None: if not all([spc.label for spc in self.rmg_reaction.reactants + self.rmg_reaction.products]): raise InputError(f'All species in a reaction must be labeled (and the labels must correspond ' f'to respective Species in ARC). If an RMG Reaction object was passes, make ' f'sure that all species in the reactants and products are correctly labeled. ' f'Problematic reaction: {self.label}') self.reactants = [spc.label for spc in self.rmg_reaction.reactants] self.products = [spc.label for spc in self.rmg_reaction.products] self.set_label_reactants_products() if self.ts_label is None: self.ts_label = reaction_dict['ts_label'] if 'ts_label' in reaction_dict else None self.r_species = [r.from_dict() for r in reaction_dict['r_species']] if 'r_species' in reaction_dict else list() self.p_species = [p.from_dict() for p in reaction_dict['p_species']] if 'p_species' in reaction_dict else list() self.ts_species = reaction_dict['ts_species'].from_dict() if 'ts_species' in reaction_dict else None self.long_kinetic_description = reaction_dict['long_kinetic_description'] \ if 'long_kinetic_description' in reaction_dict else '' self.ts_methods = reaction_dict['ts_methods'] if 'ts_methods' in reaction_dict else default_ts_methods self.ts_methods = [tsm.lower() for tsm in self.ts_methods] self.ts_xyz_guess = reaction_dict['ts_xyz_guess'] if 'ts_xyz_guess' in reaction_dict else list() self.preserve_param_in_scan = reaction_dict['preserve_param_in_scan'] \ if 'preserve_param_in_scan' in reaction_dict else None self._atom_map = reaction_dict['atom_map'] if 'atom_map' in reaction_dict else None
def _send_command_to_server( self, command: Union[str, list], remote_path: str = '', ) -> Tuple[list, list]: """ A wrapper for exec_command in paramiko.SSHClient. Send commands to the server. Args: command (Union[str, list]): A string or an array of string commands to send. remote_path (Optional[str]): The directory path at which the command will be executed. Returns: Tuple[list, list]: - A list of lines of standard output stream. - A list of lines of the standard error stream. """ if isinstance(command, list): command = '; '.join(command) if remote_path != '': # execute command in remote_path directory. # Check remote path existence, otherwise the cmd will be invalid # and even yield different behaviors. # Make sure to change directory back after the command is executed if self._check_dir_exists(remote_path): command = f'cd "{remote_path}"; {command}; cd ' else: raise InputError( f'Cannot execute command at given remote_path({remote_path})' ) try: _, stdout, stderr = self._ssh.exec_command(command) except Exception as e: # SSHException: Timeout opening channel. logger.debug(f'ssh timed-out in the first trial. Got: {e}') try: # try again _, stdout, stderr = self._ssh.exec_command(command) except Exception as e: logger.debug(f'ssh timed-out after two trials. Got: {e}') return [ '', ], [ 'ssh timed-out after two trials', ] stdout = stdout.readlines() stderr = stderr.readlines() return stdout, stderr
def main(): """ Delete ARC jobs according to the command line arguments specifications. """ args = parse_command_line_arguments() if not args.all and not args.project and not args.job and not args.server: raise InputError( "Either a project (e,g,, '-p project_name'), a job (e.g., '-j a4563'), " "or a server (e,g,, '-s server_name'), or ALL (i.e., '-a')") server_list = args.server if args.server else [ server for server in servers.keys() ] local_arc_path_ = local_arc_path if os.path.isdir( local_arc_path) else ARC_PATH csv_path = os.path.join(local_arc_path_, 'initiated_jobs.csv') project, jobs = None, list() if args.project: project = args.project elif args.job: with open(csv_path, 'r') as f: reader = csv.reader(f, dialect='excel') for row in reader: if args.job == row[0]: project = row[1] if project is not None: with open(csv_path, 'r') as f: reader = csv.reader(f, dialect='excel') for row in reader: if row[1] == project: jobs.append(row[8]) if args.all: jobs = None for server in server_list: if server != 'local': delete_all_arc_jobs(server_list=server_list, jobs=jobs) else: delete_all_local_arc_jobs(jobs=jobs)
def process_conformers_file( conformers_path: str) -> Tuple[List[Dict[str, tuple]], List[float]]: """ Parse coordinates and energies from an ARC conformers file of either species or TSs. Args: conformers_path (str): The path to an ARC conformers file (either a "conformers_before_optimization" or a "conformers_after_optimization" file). Raises: InputError: If the file could not be found. Returns: Tuple[List[Dict[str, tuple]], List[float]] Conformer coordinates in a dict format, the respective energies in kJ/mol. """ if not os.path.isfile(conformers_path): raise InputError( 'Conformers file {0} could not be found'.format(conformers_path)) with open(conformers_path, 'r') as f: lines = f.readlines() xyzs, energies = list(), list() line_index = 0 while line_index < len(lines): if 'conformer' in lines[line_index] and ':' in lines[ line_index] and lines[line_index].strip()[-2].isdigit(): xyz, energy = '', None line_index += 1 while len(lines) and line_index < len(lines) and lines[line_index].strip() \ and 'SMILES' not in lines[line_index] \ and 'energy' not in lines[line_index].lower() \ and 'guess method' not in lines[line_index].lower(): xyz += lines[line_index] line_index += 1 while len(lines) and line_index < len( lines) and 'conformer' not in lines[line_index]: if 'relative energy:' in lines[line_index].lower(): energy = float(lines[line_index].split()[2]) line_index += 1 xyzs.append(str_to_xyz(xyz)) energies.append(energy) else: line_index += 1 return xyzs, energies
def _get_lines_from_file(path: str) -> List[str]: """ A helper function for getting a list of lines from a file. Args: path (str): The file path. Raises: InputError: If the file could not be read. Returns: List[str] Entries are lines from the file. """ if os.path.isfile(path): with open(path, 'r') as f: lines = f.readlines() else: raise InputError(f'Could not find file {path}') return lines
def parse_zpe(path: str) -> Optional[float]: """ Determine the calculated ZPE from a frequency output file Args: path (str): The path to a frequency calculation output file. Returns: Optional[float] The calculated zero point energy in kJ/mol. """ if not os.path.isfile(path): raise InputError('Could not find file {0}'.format(path)) log = ess_factory(fullpath=path) try: zpe = log.load_zero_point_energy() * 0.001 # convert to kJ/mol except (LogError, NotImplementedError): logger.warning('Could not read zpe from {0}'.format(path)) zpe = None return zpe
def parse_t1(path: str) -> Optional[float]: """ Parse the T1 parameter from a Molpro or Orca coupled cluster calculation. Args: path (str): The ess log file path. Returns: Optional[float] The T1 parameter. """ if not os.path.isfile(path): raise InputError('Could not find file {0}'.format(path)) log = ess_factory(fullpath=path) try: t1 = log.get_T1_diagnostic() except (LogError, NotImplementedError): logger.warning('Could not read t1 from {0}'.format(path)) t1 = None return t1
def save_yaml_file( path: str, content: list or dict, ) -> None: """ Save a YAML file (usually an input / restart file, but also conformers file) Args: path (str): The YAML file path to save. content (list, dict): The content to save. """ if not isinstance(path, str): raise InputError( f'path must be a string, got {path} which is a {type(path)}') yaml.add_representer(str, string_representer) logger.debug('Creating a restart file...') content = yaml.dump(data=content) if '/' in path and os.path.dirname(path) and not os.path.exists( os.path.dirname(path)): os.makedirs(os.path.dirname(path)) with open(path, 'w') as f: f.write(content)
def parse_e_elect( path: str, zpe_scale_factor: float = 1., ) -> Optional[float]: """ Parse the electronic energy from an sp job output file. Args: path (str): The ESS log file to parse from. zpe_scale_factor (float): The ZPE scaling factor, used only for composite methods in Gaussian via Arkane. Returns: Optional[float] The electronic energy in kJ/mol. """ if not os.path.isfile(path): raise InputError(f'Could not find file {path}') log = ess_factory(fullpath=path) try: e_elect = log.load_energy( zpe_scale_factor) * 0.001 # convert to kJ/mol except (LogError, NotImplementedError): logger.warning(f'Could not read e_elect from {path}') e_elect = None return e_elect
def determine_ess(log_file: str) -> str: """ Determine the ESS to which the log file belongs. Args: log_file (str): The ESS log file path. Returns: str The ESS log class from Arkane. """ log = ess_factory(log_file) if isinstance(log, GaussianLog): return 'gaussian' if isinstance(log, MolproLog): return 'molpro' if isinstance(log, OrcaLog): return 'orca' if isinstance(log, QChemLog): return 'qchem' if isinstance(log, TeraChemLog): return 'terachem' raise InputError( f'Could not identify the log file in {log_file} as belonging to ' f'Gaussian, Molpro, Orca, QChem, or TeraChem.')
def parse_str_blocks( file_path: str, head_pat: Union[Match, str], tail_pat: Union[Match, str], regex: bool = True, tail_count: int = 1, block_count: int = 1, ) -> List[str]: """ Return a list of blocks defined by the head pattern and the tail pattern. Args: file_path (str): The path to the readable file. head_pat (str/regex): Str pattern or regular expression of the head of the block. tail_pat (str/regex): Str pattern or regular expresion of the tail of the block. regex (bool, optional): Use regex (True) or str pattern (False) to search. tail_count (int, optional): The number of times that the tail repeats. block_count (int, optional): The max number of blocks to search. -1 for any number. Raises: InputError: If the file could not be found. Returns: List[str] List of str blocks. """ if not os.path.isfile(file_path): raise InputError('Could not find file {0}'.format(file_path)) with open(file_path, 'r') as f: blks = [] # Different search mode if regex: def search(x, y): return re.search(x, y) else: def search(x, y): return x in y # 'search' for the head or 'read' until the tail mode = 'search' line = f.readline() while line != '': if mode == 'search': # Stop searching if found enough blocks if (len(blks)) == block_count: break # Check if matching the head pattern else: match = search(head_pat, line) # Switch to 'read' mode if match: tail_repeat = 0 mode = 'read' blks.append([]) blks[-1].append(line) elif mode == 'read': blks[-1].append(line) match = search(tail_pat, line) if match: tail_repeat += 1 # If see enough tail patterns, switch to 'search' mode if tail_repeat == tail_count: mode = 'search' line = f.readline() # Remove the last incomplete search if len(blks) > 0 and (tail_repeat != tail_count): blks.pop() return blks
def load_rmg_database(rmgdb, thermo_libraries=None, reaction_libraries=None, kinetics_families='default', load_thermo_libs=True, load_kinetic_libs=True, include_nist=False): """ A helper function for loading the RMG database. Args: rmgdb (RMGDatabase): The RMG database instance. thermo_libraries (list, optional): Specific thermodynamic libraries to load (``None`` will load all). reaction_libraries (list, optional): Specific kinetics libraries to load (``None`` will load all). kinetics_families (list, str, optional): Specific kinetics families to load (either a list or 'default', 'all', 'none') load_thermo_libs (bool, optional): Whether to load thermodynamic libraries, ``True`` to load. load_kinetic_libs (bool, optional): Whether to load kinetics libraries, ``True`` to load. include_nist (bool, optional): Whether to include the NIST kinetics libraries, ``True`` to include, default is ``False`` """ thermo_libraries = thermo_libraries if thermo_libraries is not None else list( ) reaction_libraries = reaction_libraries if reaction_libraries is not None else list( ) if isinstance(thermo_libraries, str): thermo_libraries.replace(' ', '') thermo_libraries = [lib for lib in thermo_libraries.split(',')] if isinstance(reaction_libraries, str): reaction_libraries.replace(' ', '') reaction_libraries = [lib for lib in reaction_libraries.split(',')] reaction_libraries = [reaction_libraries] if kinetics_families not in ('default', 'all', 'none'): if not isinstance(kinetics_families, list): raise InputError( "kinetics_families should be either 'default', 'all', 'none', or a list of names, e.g.," " ['H_Abstraction','R_Recombination'] or ['!Intra_Disproportionation']." ) if not thermo_libraries: thermo_path = os.path.join(db_path, 'thermo', 'libraries') for thermo_library_path in os.listdir(thermo_path): # Avoid reading .DS_store files for compatibility with Mac OS if not thermo_library_path.startswith('.'): thermo_library, _ = os.path.splitext( os.path.basename(thermo_library_path)) thermo_libraries.append(thermo_library) # prioritize libraries thermo_priority = [ 'BurkeH2O2', 'thermo_DFT_CCSDTF12_BAC', 'DFT_QCI_thermo', 'Klippenstein_Glarborg2016', 'primaryThermoLibrary', 'primaryNS', 'NitrogenCurran', 'NOx2018', 'FFCM1(-)', 'SulfurLibrary', 'SulfurGlarborgH2S' ] indices_to_pop = [] for i, lib in enumerate(thermo_libraries): if lib in thermo_priority: indices_to_pop.append(i) for i in reversed( range(len(thermo_libraries)) ): # pop starting from the end, so other indices won't change if i in indices_to_pop: thermo_libraries.pop(i) thermo_libraries = thermo_priority + thermo_libraries if not reaction_libraries: kinetics_path = os.path.join(db_path, 'kinetics', 'libraries') # Avoid reading .DS_store files for compatibility with Mac OS reaction_libraries = [ library for library in os.listdir(kinetics_path) if not library.startswith('.') ] indices_to_pop = list() second_level_libraries = list() for i, library in enumerate(reaction_libraries): if not os.path.isfile( os.path.join(kinetics_path, library, 'reactions.py')) and library != 'Surface': indices_to_pop.append(i) # Avoid reading .DS_store files for compatibility with Mac OS (`if not second_level.startswith('.')`) second_level_libraries.extend([ library + '/' + second_level for second_level in os.listdir( os.path.join(kinetics_path, library)) if not second_level.startswith('.') ]) for i in reversed( range(len(reaction_libraries)) ): # pop starting from the end, so other indices won't change if i in indices_to_pop or reaction_libraries[i] == 'Surface': reaction_libraries.pop(i) reaction_libraries.extend(second_level_libraries) if not load_kinetic_libs: reaction_libraries = list() if not load_thermo_libs: thermo_libraries = list() # reaction_libraries = list() # empty library list for debugging logger.info('\n\nLoading the RMG database...') kinetics_depositories = ['training', 'NIST' ] if include_nist else ['training'] rmgdb.load( path=db_path, thermo_libraries=thermo_libraries, transport_libraries=['PrimaryTransportLibrary', 'NOx2018', 'GRI-Mech'], reaction_libraries=reaction_libraries, seed_mechanisms=list(), kinetics_families=kinetics_families, kinetics_depositories=kinetics_depositories, depository=False, ) for family in rmgdb.kinetics.families.values(): try: family.add_rules_from_training(thermo_database=rmgdb.thermo) except KineticsError: logger.info('Could not train family {0}'.format(family)) else: family.fill_rules_by_averaging_up(verbose=False) logger.info('\n\n')
def initialize_job_types( job_types: dict, specific_job_type: str = '', ) -> dict: """ A helper function for initializing job_types. Returns the comprehensive (default values for missing job types) job types for ARC. Args: job_types (dict): Keys are job types, values are booleans of whether or not to consider this job type. specific_job_type (str): Specific job type to execute. Legal strings are job types (keys of job_types dict). Returns: dict An updated (comprehensive) job type dictionary. """ if specific_job_type: logger.info( f'Specific_job_type {specific_job_type} was requested by the user.' ) if job_types: logger.warning( 'Both job_types and specific_job_type were given, use only specific_job_type to ' 'populate the job_types dictionary.') job_types = {job_type: False for job_type in default_job_types.keys()} try: job_types[specific_job_type] = True except KeyError: raise InputError( f'Specified job type {specific_job_type} is not supported.') if specific_job_type == 'bde': bde_default = { 'opt': True, 'fine_grid': True, 'freq': True, 'sp': True } job_types.update(bde_default) defaults_to_true = [ 'conformers', 'fine', 'freq', 'irc', 'opt', 'rotors', 'sp' ] defaults_to_false = ['bde', 'onedmin', 'orbitals'] if job_types is None: job_types = default_job_types logger.info("Job types were not specified, using ARC's defaults") else: logger.debug(f'the following job types were specified: {job_types}.') if 'lennard_jones' in job_types: job_types['onedmin'] = job_types['lennard_jones'] del job_types['lennard_jones'] if 'fine_grid' in job_types: job_types['fine'] = job_types['fine_grid'] del job_types['fine_grid'] for job_type in defaults_to_true: if job_type not in job_types: # set default value to True if this job type key is missing job_types[job_type] = True for job_type in defaults_to_false: if job_type not in job_types: # set default value to False if this job type key is missing job_types[job_type] = False for job_type in job_types.keys(): if job_type not in defaults_to_true and job_type not in defaults_to_false: if job_type == '1d_rotors': logging.error( "Note: The `1d_rotors` job type was renamed to simply `rotors`. " "Please modify your input accordingly (see ARC's documentation for examples)." ) raise InputError( f"Job type '{job_type}' is not supported. Check the job types dictionary " "(either in ARC's input or in default_job_types under settings)." ) job_types_report = [job_type for job_type, val in job_types.items() if val] logger.info(f'\nConsidering the following job types: {job_types_report}\n') return job_types