def from_dict(self, reaction_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( 'Cannot determine reactants and/or products labels for reaction {0}' .format(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( 'All species in a reaction must be labeled (and the labels must correspond' ' to respective Species in ARC). If an RMG Reaction object was passes, make' ' sure that all species in the reactants and products are correctly labeled.' ' Problematic reaction: {0}'.format(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()
def rdkit_conf_from_mol(mol, coordinates): """ Generate an RDKit Conformer object from an RMG Molecule object Args: mol (Molecule): The RMG Molecule object. coordinates (list, str): The coordinates (in any format) of the conformer, atoms must be ordered as in the molecule. Returns: Conformer: An RDKit Conformer object. Returns: RDMol: An RDKit Molecule object. Returns: dict: Atom index map. Keys are atom indices in the RMG Molecule, values are atom indices in the RDKit Molecule. """ if coordinates is None or not coordinates: raise InputError('Cannot process empty coordinates, got: {0}'.format(coordinates)) if isinstance(coordinates[0], (str, unicode)) and isinstance(coordinates, list): raise InputError('The coordinates argument seem to be of wrong type. Got a list of strings:\n{0}'.format( coordinates)) if isinstance(coordinates, (str, unicode)): coordinates = get_xyz_matrix(xyz=coordinates)[0] rd_mol, rd_indices = to_rdkit_mol(mol=mol, remove_h=False, return_mapping=True) Chem.AllChem.EmbedMolecule(rd_mol) index_map = dict() for xyz_index, atom in enumerate(mol.atoms): # generate an atom index mapping dictionary index_map[xyz_index] = rd_indices[atom] conf = rd_mol.GetConformer(id=0) for i in range(rd_mol.GetNumAtoms()): conf.SetAtomPosition(index_map[i], coordinates[i]) # reset atom coordinates return conf, rd_mol, index_map
def check_xyz_species_for_drawing(xyz, species): """A helper function to avoid repetitive code""" if species is not None and xyz is None: xyz = xyz if xyz is not None else species.final_xyz if species is not None and not isinstance(species, ARCSpecies): raise InputError('Species must be an ARCSpecies instance. Got {0}.'.format(type(species))) if species is not None and species.final_xyz is None: raise InputError('Species {0} has an empty final_xyz attribute.'.format(species.label)) return xyz
def __init__(self, label='', reactants=None, products=None, ts_label=None, rmg_reaction=None, ts_methods=None, ts_xyz_guess=None, multiplicity=None, charge=0, reaction_dict=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 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( ) if len(self.reactants) > 3 or len(self.products) > 3: raise ReactionError( 'An ARC Reaction can have up to three reactants / products. got {0} reactants' ' and {1} products for reaction {2}.'.format( len(self.reactants), len(self.products), self.label))
def get_xyz_matrix(xyz): """ Convert a string xyz form:: C 0.6616514836 0.4027481525 -0.4847382281 N -0.6039793084 0.6637270105 0.0671637135 H -1.4226865648 -0.4973210697 -0.2238712255 H -0.4993010635 0.6531020442 1.0853092315 H -2.2115796924 -0.4529256762 0.4144516252 H -1.8113671395 -0.3268900681 -1.1468957003 into a list of lists xyz form:: [[0.6616514836, 0.4027481525, -0.4847382281], [-0.6039793084, 0.6637270105, 0.0671637135], [-1.4226865648, -0.4973210697, -0.2238712255], [-0.4993010635, 0.6531020442, 1.0853092315], [-2.2115796924, -0.4529256762, 0.4144516252], [-1.8113671395, -0.3268900681, -1.1468957003]] Args: xyz (str): The xyz coordinates to conver. Returns: list: Array-style coordinates. Returns: list: Chemical symbols of the elements in xyz, order preserved. Returns: list: X axis values. Returns: list: Y axis values. Returns: list: Z axis values. """ if not isinstance(xyz, (str, unicode)): raise InputError('Can only convert sting or unicode to list, got: {0}'.format(xyz)) xyz = standardize_xyz_string(xyz) x, y, z, symbols = list(), list(), list(), list() for line in xyz.split('\n'): if line: line_split = line.split() if len(line_split) != 4: raise InputError('Expecting each line in an xyz string to have 4 values, e.g.:\n' 'C 0.1000 0.2000 -0.3000\nbut got {0} values:\n{1}'.format( len(line_split), line)) atom, xx, yy, zz = line.split() x.append(float(xx)) y.append(float(yy)) z.append(float(zz)) symbols.append(atom) coords = list() for i, _ in enumerate(x): coords.append([x[i], y[i], z[i]]) return coords, symbols, x, y, z
def upload_file(self, remote_file_path, local_file_path='', file_string=''): """ Upload `local_file_path` or the contents of `file_string` to `remote_file_path`. Either `file_string` or `local_file_path` must be given. """ if local_file_path and not os.path.isfile(local_file_path): raise InputError('Cannot upload a non-existing file.' ' Check why file in path {0} is missing.'.format( local_file_path)) sftp, ssh = self.connect() times_tried = 0 max_times_to_try = 10 success = False while not success and times_tried < max_times_to_try: times_tried += 1 try: write_file(sftp, remote_file_path, local_file_path, file_string) except IOError: pass else: success = True if times_tried == max_times_to_try: raise ServerError('Could not write file {0} on {1}'.format( remote_file_path, self.server)) sftp.close() ssh.close()
def get_center_of_mass(xyz=None, coords=None, symbols=None): """ Get the center of mass of xyz coordinates. Assumes arc.converter.standardize_xyz_string() was already called for xyz. Note that xyz from ESS output is usually already centered at the center of mass (to some precision). Either xyz or coords and symbols must be given. Args: xyz (string, optional): The xyz coordinates in a string format. coords (list, optional): The xyz coordinates in an array format. symbols (list, optional): The chemical element symbols corresponding to `coords`. Returns: tuple: The center of mass coordinates. """ if xyz is not None: masses, coords = list(), list() for line in xyz.splitlines(): if line.strip(): splits = line.split() masses.append(get_element_mass(str(splits[0]))[0]) coords.append([float(splits[1]), float(splits[2]), float(splits[3])]) elif coords is not None and symbols is not None: masses = [get_element_mass(str(symbol))[0] for symbol in symbols] else: raise InputError('Either xyz or coords and symbols must be given') cm_x, cm_y, cm_z = 0, 0, 0 for coord, mass in zip(coords, masses): cm_x += coord[0] * mass cm_y += coord[1] * mass cm_z += coord[2] * mass cm_x /= sum(masses) cm_y /= sum(masses) cm_z /= sum(masses) return cm_x, cm_y, cm_z
def parse_frequencies(path, software): if not os.path.isfile(path): raise InputError('Could not find file {0}'.format(path)) freqs = np.array([], np.float64) if software.lower() == 'qchem': with open(path, 'rb') as f: for line in f: if ' Frequency:' in line: items = line.split() for i, item in enumerate(items): if i: freqs = np.append(freqs, [(float(item))]) elif software.lower() == 'gaussian': with open(path, 'rb') as f: line = f.readline() while line != '': if 'Frequencies --' in line: freqs = np.append(freqs, [float(frq) for frq in line.split()[2:]]) line = f.readline() else: raise ValueError( 'parse_frequencies() can curtrently only parse QChem and gaussian files,' ' got {0}'.format(software)) logging.debug( 'Using parser.parse_frequencies. Determined frequencies are: {0}'. format(freqs)) return freqs
def translate_to_center_of_mass(xyz=None, coords=None, symbols=None): """ Translate coordinates to their center of mass. Must give either xyz or coords along with symbols. Args: xyz (str, optional): A molecule's coordinates in string-format. coords (list): A molecule's coordinates in array-format. symbols (list): The matching elemental symbols for the coordinates. Returns: list or str: The translated coordinates in the input format. """ if xyz is not None: coords, symbols, _, _, _ = get_xyz_matrix(xyz) if coords is None or symbols is None: raise InputError('Could not translate coordinates to center of mass. Got coords = {0} and ' 'symbols = {1}.'.format(coords, symbols)) cm_x, cm_y, cm_z = get_center_of_mass(coords=coords, symbols=symbols) x = [coord[0] - cm_x for coord in coords] y = [coord[1] - cm_y for coord in coords] z = [coord[2] - cm_z for coord in coords] translated_coords = [[xi, yi, zi] for xi, yi, zi in zip(x, y, z)] if xyz is not None: return get_xyz_string(coords=translated_coords, symbols=symbols) else: return translated_coords
def _get_lines_from_file(path): """A helper function for getting a list of lines from the file at `path`""" if os.path.isfile(path): with open(path, 'r') as f: lines = f.readlines() else: raise InputError('Could not find file {0}'.format(path)) return lines
def parse_t1(path): """ Parse the T1 parameter from a Molpro coupled cluster calculation """ if not os.path.isfile(path): raise InputError('Could not find file {0}'.format(path)) t1 = None with open(path, 'rb') as f: for line in f: if 'T1 diagnostic:' in line: t1 = float(line.split()[-1]) return t1
def parse_e_elect(path, zpe_scale_factor=1.): """ Parse the zero K energy, E0, from an sp job output file. """ if not os.path.isfile(path): raise InputError('Could not find file {0}'.format(path)) log = determine_qm_software(fullpath=path) try: e_elect = log.loadEnergy(zpe_scale_factor) * 0.001 # convert to kJ/mol except Exception: logger.warning('Could not read e_elect from {0}'.format(path)) e_elect = None return e_elect
def parse_e0(path): """ Parse the zero K energy, E0, from an sp job output file """ if not os.path.isfile(path): raise InputError('Could not find file {0}'.format(path)) log = determine_qm_software(fullpath=path) try: e0 = log.loadEnergy(frequencyScaleFactor=1.) * 0.001 # convert to kJ/mol except Exception: logging.warning('Could not read E0 from {0}'.format(path)) e0 = None return e0
def read_yaml_file(path): """ 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. Returns: dict or list: The content read from the file. """ if not os.path.isfile(path): raise InputError('Could not find the YAML file {0}'.format(path)) with open(path, 'r') as f: content = yaml.load(stream=f, Loader=yaml.FullLoader) return content
def parse_xyz_from_file(path): """ Parse xyz coordinated from: .xyz - XYZ file .gjf - Gaussian input file .out or .log - ESS output file (Gaussian, QChem, Molpro) other - Molpro or QChem input file """ with open(path, 'r') as f: lines = f.readlines() _, file_extension = os.path.splitext(path) xyz = None relevant_lines = list() if file_extension == '.xyz': relevant_lines = lines[2:] elif file_extension == '.gjf': for line in lines[5:]: if line and line != '\n' and line != '\r\n': relevant_lines.append(line) else: break elif 'out' in file_extension or 'log' in file_extension: log = Log(path='') log.determine_qm_software(fullpath=path) coord, number, mass = log.software_log.loadGeometry() xyz = get_xyz_string(xyz=coord, number=number) else: record = False for line in lines: if '$end' in line or '}' in line: break if record and len(line.split()) == 4: relevant_lines.append(line) elif '$molecule' in line: record = True elif 'geometry={' in line: record = True if not relevant_lines: raise InputError( 'Could not parse xyz coordinates from file {0}'.format(path)) if xyz is None and relevant_lines: xyz = ''.join([line for line in relevant_lines if line]) return xyz
def determine_ess(log_file): """ Determine the ESS to which the log file belongs. Args: log_file (str): The ESS log file path. Returns: str: The ESS (either 'gaussian', 'qchem', or 'molpro'. """ log = determine_qm_software(log_file) if isinstance(log, GaussianLog): return 'gaussian' if isinstance(log, QChemLog): return 'qchem' if isinstance(log, MolproLog): return 'molpro' raise InputError('Could not identify the log file in {0} as belonging to Gaussian, QChem, or Molpro.')
def get_atom_radius(symbol): """ Get the atom covalent radius in Angstroms, data in the ATOM_RADII dict. (Change to QCElemental after transitioning to Py3) Args: symbol (str): The atomic symbol. Returns: float: The atomic covalent radius (None if not found). """ if not isinstance(symbol, (str, unicode)): raise InputError('the symbol argument must be string, got {0} which is a {1}'.format(symbol, type(symbol))) if symbol in ATOM_RADII: return ATOM_RADII[symbol] else: return None
def load_families_only(rmgdb, kinetics_families='default'): """ A helper function for loading kinetic families from RMG's database """ if kinetics_families not in ('default', 'all', 'none'): if not isinstance(kinetics_families, list): raise InputError("kineticsFamilies should be either 'default', 'all', 'none', or a list of names, e.g.," " ['H_Abstraction','R_Recombination'] or ['!Intra_Disproportionation'].") logging.debug('\n\nLoading only kinetic families from the RMG database...') rmgdb.load( path=db_path, thermoLibraries=list(), transportLibraries='none', reactionLibraries=list(), seedMechanisms=list(), kineticsFamilies=kinetics_families, kineticsDepositories=['training'], depository=False, )
def get_xyz_matrix(xyz): """ Convert a string xyz form: C 0.6616514836 0.4027481525 -0.4847382281 N -0.6039793084 0.6637270105 0.0671637135 H -1.4226865648 -0.4973210697 -0.2238712255 H -0.4993010635 0.6531020442 1.0853092315 H -2.2115796924 -0.4529256762 0.4144516252 H -1.8113671395 -0.3268900681 -1.1468957003 into a list of lists xyz form: [[0.6616514836, 0.4027481525, -0.4847382281], [-0.6039793084, 0.6637270105, 0.0671637135], [-1.4226865648, -0.4973210697, -0.2238712255], [-0.4993010635, 0.6531020442, 1.0853092315], [-2.2115796924, -0.4529256762, 0.4144516252], [-1.8113671395, -0.3268900681, -1.1468957003]] Returns xyz as well as atom symbols, x, y, and z separately """ xyz = standardize_xyz_string(xyz) x, y, z, symbols = [], [], [], [] for line in xyz.split('\n'): if line: line_split = line.split() if len(line_split) != 4: raise InputError( 'Expecting each line in an xyz string to have 4 elements, e.g.:\n' 'C 0.1000 0.2000 -0.3000\nbut got {0} elements:\n{1}' .format(len(line_split), line)) atom, xx, yy, zz = line.split() x.append(float(xx)) y.append(float(yy)) z.append(float(zz)) symbols.append(atom) xyz = [] for i, _ in enumerate(x): xyz.append([x[i], y[i], z[i]]) return xyz, symbols, x, y, z
def initialize_job_types(job_types): """ 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. Returns: job_types (dict): An updated (comprehensive) job type dictionary. """ defaults_to_true = ['conformers', 'opt', 'fine', 'freq', 'sp', '1d_rotors'] defaults_to_false = ['onedmin', 'orbitals', 'bde'] if job_types is None: job_types = default_job_types if 'lennard_jones' in job_types: # rename lennard_jones to OneDMin job_types['onedmin'] = job_types['lennard_jones'] del job_types['lennard_jones'] if 'fine_grid' in job_types: # rename fine_grid to fine 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: raise InputError("Job type '{0}' not supported. Check the job types dictionary " "(either in ARC's input or in default_job_types under settings)".format(job_type)) job_types_report = [job_type for job_type, val in job_types.items() if val] logger.info('\nConsidering the following job types: {0}\n'.format(job_types_report)) return job_types
def upload_file(self, remote_file_path, local_file_path='', file_string=''): """ Upload `local_file_path` or the contents of `file_string` to `remote_file_path`. Either `file_string` or `local_file_path` must be given. """ if local_file_path and not os.path.isfile(local_file_path): raise InputError('Cannot upload a non-existing file.' ' Check why file in path {0} is missing.'.format( local_file_path)) sftp, ssh = self.connect() i, max_times_to_try = 1, 30 success = False sleep_time = 10 # seconds while i < 30: try: write_file(sftp, remote_file_path, local_file_path, file_string) except IOError: logger.error('Could not upload file {0} to {1}!'.format( local_file_path, self.server)) logger.error( 'ARC is sleeping for {0} seconds before re-trying,' ' please check your connectivity.'.format(sleep_time * i)) logger.info('ZZZZZ..... ZZZZZ.....') time.sleep(sleep_time * i) # in seconds else: success = True i = 1000 i += 1 if not success: raise ServerError( 'Could not write file {0} on {1}. Tried {2} times.'.format( remote_file_path, self.server, max_times_to_try)) sftp.close() ssh.close()
def load_rmg_database(rmgdb, thermo_libraries=None, reaction_libraries=None, kinetics_families='default', load_thermo_libs=True, load_kinetic_libs=True): """ A helper function for loading the RMG database """ 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, unicode)): 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( "kineticsFamilies 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): 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') reaction_libraries = os.listdir(kinetics_path) 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')): indices_to_pop.append(i) second_level_libraries.extend([library + '/' + second_level\ for second_level in os.listdir(os.path.join(kinetics_path, library))]) 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: reaction_libraries.pop(i) reaction_libraries.extend(second_level_libraries) # set library to be represented by a string rather than a unicode, # this might not be needed after a full migration to Py3 thermo_libraries = [str(lib) for lib in thermo_libraries] reaction_libraries = [str(lib) for lib in reaction_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 logging.info('\n\nLoading the RMG database...') rmgdb.load( path=db_path, thermoLibraries=thermo_libraries, transportLibraries=['PrimaryTransportLibrary', 'NOx2018', 'GRI-Mech'], reactionLibraries=reaction_libraries, seedMechanisms=list(), kineticsFamilies=kinetics_families, kineticsDepositories=['training'], depository=False, ) for family in rmgdb.kinetics.families.values(): try: family.addKineticsRulesFromTrainingSet(thermoDatabase=rmgdb.thermo) except KineticsError: logging.info('Could not train family {0}'.format(family)) else: family.fillKineticsRulesByAveragingUp(verbose=False) logging.info('\n\n')
def determine_scaling_factors(levels_of_theory, ess_settings=None, init_log=True): """ Determine the zero-point energy, harmonic frequencies, and fundamental frequencies scaling factors for a given frequencies level of theory. Args: levels_of_theory (list, str): A list of frequencies levels of theory for which scaling factors are determined. A string can also be passed for just one level of theory. ess_settings (dict, optional): A dictionary of available ESS (keys) and a corresponding server list (values). init_log (bool, optional): Whether to initialize the logger. True to initialize. Should be True when called as a stand alone, and False when called within ARC. Returns: str: The modified level of theory """ if init_log: initialize_log(log_file='scaling_factor.log', project='Scaling Factors') if isinstance(levels_of_theory, (str, unicode)): levels_of_theory = [levels_of_theory] if not isinstance(levels_of_theory, list): raise InputError( 'levels_of_theory must be a list (or a string if only one level is desired). Got: {0}' .format(type(levels_of_theory))) t0 = time.time() logger.info('\n\n\n') logger.info(HEADER) logger.info('\n\nstarting ARC...\n') # only run opt (fine) and freq job_types = initialize_job_types( dict()) # get the defaults, so no job type is missing job_types = {job_type: False for job_type in job_types.keys()} job_types['opt'], job_types['fine'], job_types['freq'] = True, True, True lambda_zpes, zpe_dicts, times = list(), list(), list() for level_of_theory in levels_of_theory: t1 = time.time() logger.info( '\nComputing scaling factors at the {0} level of theory...\n\n'. format(level_of_theory)) renamed_level = rename_level(level_of_theory) project = 'scaling_' + renamed_level project_directory = os.path.join(arc_path, 'Projects', 'scaling_factors', project) species_list = get_species_list() if '//' in level_of_theory: raise InputError( 'Level of theory should either be a composite method or in a method/basis-set format. ' 'Got {0}'.format(level_of_theory)) if '/' not in level_of_theory: # assume this is a composite method freq_level = '' composite_method = level_of_theory.lower() job_types['freq'] = False else: freq_level = level_of_theory.lower() composite_method = '' job_types['freq'] = True ess_settings = check_ess_settings(ess_settings or global_ess_settings) Scheduler(project=project, project_directory=project_directory, species_list=species_list, composite_method=composite_method, opt_level=freq_level, freq_level=freq_level, ess_settings=ess_settings, job_types=job_types, allow_nonisomorphic_2d=True) zpe_dict = dict() for spc in species_list: try: zpe = get_zpe( os.path.join(project_directory, 'output', 'Species', spc.label, 'geometry', 'freq.out')) except Exception: zpe = None zpe_dict[spc.label] = zpe zpe_dicts.append(zpe_dict) lambda_zpes.append( calculate_truhlar_scaling_factors(zpe_dict, level_of_theory)) times.append(time_lapse(t1)) summarize_results(lambda_zpes, levels_of_theory, zpe_dicts, times, time_lapse(t0)) logger.info('\n\n\n') logger.info(HEADER) harmonic_freq_scaling_factors = [ lambda_zpe * 1.014 for lambda_zpe in lambda_zpes ] return harmonic_freq_scaling_factors