def test_xyz_format_conversion(self): """Test conversions from string to list xyz formats""" xyz_str0 = """N 2.24690600 -0.00006500 0.11597700 C -1.05654800 1.29155000 -0.02642500 C -1.05661400 -1.29150400 -0.02650600 C -0.30514100 0.00000200 0.00533200 C 1.08358900 -0.00003400 0.06558000 H -0.39168300 2.15448600 -0.00132500 H -1.67242600 1.35091400 -0.93175000 H -1.74185400 1.35367700 0.82742800 H -0.39187100 -2.15447800 0.00045500 H -1.74341400 -1.35278100 0.82619100 H -1.67091600 -1.35164600 -0.93286400 """ xyz_list, atoms, x, y, z = get_xyz_matrix(xyz_str0) # test all forms of input for get_xyz_string(): xyz_str1 = get_xyz_string(coords=xyz_list, symbols=atoms) xyz_str2 = get_xyz_string(coords=xyz_list, numbers=[7, 6, 6, 6, 6, 1, 1, 1, 1, 1, 1]) mol, _ = molecules_from_xyz(xyz_str0) xyz_str3 = get_xyz_string(coords=xyz_list, mol=mol) self.assertEqual(xyz_str0, xyz_str1) self.assertEqual(xyz_str1, xyz_str2) self.assertEqual(xyz_str2, xyz_str3) self.assertEqual(atoms, ['N', 'C', 'C', 'C', 'C', 'H', 'H', 'H', 'H', 'H', 'H']) self.assertEqual(x, [2.246906, -1.056548, -1.056614, -0.305141, 1.083589, -0.391683, -1.672426, -1.741854, -0.391871, -1.743414, -1.670916]) self.assertEqual(y[1], 1.29155) self.assertEqual(z[-1], -0.932864)
def main(reaction_label=None, reaction_family=None): """ Run AutoTST to generate a TS guess Currently only works for H Abstraction """ if reaction_label is None: # Parse the command-line arguments (requires the argparse module) args = parse_command_line_arguments() reaction_label = str(args.reaction_label) reaction_family = str(args.reaction_family) reaction_family = str( 'H_Abstraction') if reaction_family is None else reaction_family try: reaction = AutoTST_Reaction(label=reaction_label, reaction_family=reaction_family) except AssertionError as e: logger.error( 'Could not generate a TS guess using AutoTST for reaction {0}'. format(reaction_label)) raise TSError('Could not generate AutoTST guess:\n{0}'.format( e.message)) else: positions = reaction.ts.ase_ts.get_positions() numbers = reaction.ts.ase_ts.get_atomic_numbers() xyz_guess = get_xyz_string(coords=positions, numbers=numbers) xyz_path = os.path.join(arc_path, 'arc', 'ts', 'auto_tst.xyz') with open(xyz_path, 'w') as f: f.write(xyz_guess)
def rotate_molecule_to_lowest_conformer( arkane_log, local_path, input_filename, multiplicity=1, charge=0, ): """ Given the input file, rotate the molecule to lowest conformer args: arkane_log: the gaussian file read by Arkane local_path (str): local path to the job input_filename (str): the name of the input file multiplicity (int) charge (int) return: xyz (str): the xyz string used in arc geometry or gaussian geometry input """ # Find the degree to rotate v_list, angle = arkane_log.loadScanEnergies() v_list = np.array(v_list, np.float64) point = np.argmin(v_list) res = 360.0 / (len(v_list) - 1) deg_increment = point * res # Find the xyz geometry coords, numbers, _ = arkane_log.loadGeometry() coords = coords.tolist() xyz = get_xyz_string(coords=coords, numbers=numbers) # Find the pivot and scan pivot, scan, _, _ = find_scan_info_from_input_file(local_path, input_filename) # Change the structure mol = molecules_from_xyz(xyz, multiplicity=multiplicity, charge=charge)[1] conf, rd_mol, indx_map = rdkit_conf_from_mol(mol, coords) rd_scan = [indx_map[i - 1] for i in scan] new_xyz = set_rdkit_dihedrals(conf, rd_mol, indx_map, rd_scan, deg_increment=deg_increment) return get_xyz_string(coords=new_xyz, numbers=numbers)
def test_get_xyz_string(self): """Test conversion of xyz array to string format""" xyz_array = [[-0.06618943, -0.12360663, -0.07631983], [-0.79539707, 0.86755487, 1.02675668], [-0.68919931, 0.25421823, -1.34830853], [0.01546439, -1.54297548, 0.44580391], [1.59721519, 0.47861334, 0.00711], [1.94428095, 0.40772394, 1.03719428], [2.20318015, -0.14715186, -0.64755729], [1.59252246, 1.5117895, -0.33908352], [-0.8785689, -2.02453514, 0.38494433], [-1.34135876, 1.49608206, 0.53295071]] symbols = ['S', 'O', 'O', 'N', 'C', 'H', 'H', 'H', 'H', 'H'] xyz_expected = """S -0.06618943 -0.12360663 -0.07631983 O -0.79539707 0.86755487 1.02675668 O -0.68919931 0.25421823 -1.34830853 N 0.01546439 -1.54297548 0.44580391 C 1.59721519 0.47861334 0.00711000 H 1.94428095 0.40772394 1.03719428 H 2.20318015 -0.14715186 -0.64755729 H 1.59252246 1.51178950 -0.33908352 H -0.87856890 -2.02453514 0.38494433 H -1.34135876 1.49608206 0.53295071 """ xyz1 = converter.get_xyz_string(xyz=xyz_array, symbol=symbols) self.assertEqual(xyz1, xyz_expected) number = [16, 8, 8, 7, 6, 1, 1, 1, 1, 1] xyz2 = converter.get_xyz_string(xyz=xyz_array, number=number) self.assertEqual(xyz2, xyz_expected) mol = Molecule().fromAdjacencyList( str("""1 S u0 p0 c0 {2,D} {3,S} {4,D} {5,S} 2 O u0 p2 c0 {1,D} 3 O u0 p2 c0 {1,S} {6,S} 4 N u0 p1 c0 {1,D} {7,S} 5 C u0 p0 c0 {1,S} {8,S} {9,S} {10,S} 6 H u0 p0 c0 {3,S} 7 H u0 p0 c0 {4,S} 8 H u0 p0 c0 {5,S} 9 H u0 p0 c0 {5,S} 10 H u0 p0 c0 {5,S}""")) xyz3 = converter.get_xyz_string(xyz=xyz_array, mol=mol) self.assertEqual(xyz3, xyz_expected)
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 """ lines = _get_lines_from_file(path) file_extension = os.path.splitext(path)[1] xyz = None relevant_lines = list() if file_extension == '.xyz': relevant_lines = lines[2:] elif file_extension == '.gjf': start_parsing = False for line in lines: if start_parsing and line and line != '\n' and line != '\r\n': relevant_lines.append(line) elif start_parsing: break else: splits = line.split() if len(splits) == 2 and all([s.isdigit() for s in splits]): start_parsing = True elif 'out' in file_extension or 'log' in file_extension: log = determine_qm_software(fullpath=path) coords, number, _ = log.loadGeometry() xyz = get_xyz_string(coords=coords, numbers=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 ParserError( '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 standardize_xyz_string(xyz)
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 trsh_negative_freq(label, log_file, neg_freqs_trshed=None, job_types=None): """ Troubleshooting cases where non-TS species have negative frequencies. We take +/-1.1 displacements, generating several new initial geometries. Args: label (str): The species label. log_file (str): The frequency job log file. neg_freqs_trshed (list, optional): A list of negative frequencies the species was troubleshooted for. job_types (list, optional): The job types used for ARC, e.g., ['opt', '1d_rotors']. Todo: * get all torsions of the molecule (if weren't already generated), identify atom/s with largest displacements (top 2) determine torsions with unique PIVOTS where these atoms are in the "scan" and "top" but not pivotal generate a 360 scan using 30 deg increments and append all 12 results as conformers (consider rotor symmetry to append less conformers?) * Write our own frequencies parsers Returns: current_neg_freqs_trshed (list): The current troubleshooted negative frequencies. Returns: conformers (list): The new conformers to try optimizing. Returns: output_errors (list): Errors to report. Returns: output_warnings (list): Warnings to report. Raises: TrshError: If a negative frequency could not be determined. """ neg_freqs_trshed = neg_freqs_trshed if neg_freqs_trshed is not None else list( ) job_types = job_types if job_types is not None else ['1d_rotors'] output_errors, output_warnings, conformers, current_neg_freqs_trshed = list( ), list(), list(), list() factor = 1.1 ccparser = cclib.io.ccopen(str(log_file), logging.CRITICAL) try: data = ccparser.parse() except AttributeError: # see https://github.com/cclib/cclib/issues/754 logger.error( 'Could not troubleshoot negative frequency for species {0}'.format( label)) output_errors.append( 'Error: Could not troubleshoot negative frequency; ') return [], [], output_errors, [] vibfreqs = data.vibfreqs vibdisps = data.vibdisps atomnos = data.atomnos atomcoords = data.atomcoords if len(neg_freqs_trshed) > 10: logger.error( 'Species {0} was troubleshooted for negative frequencies too many times.' .format(label)) if '1d_rotors' not in job_types: logger.error( 'The rotor scans feature is turned off, ' 'cannot troubleshoot geometry using dihedral modifications.') output_warnings.append('1d_rotors = False; ') logger.error('Invalidating species.') output_errors.append( 'Error: Encountered negative frequencies too many times; ') else: neg_freqs_idx = list() # store indices w.r.t. vibfreqs largest_neg_freq_idx = 0 # index in vibfreqs for i, freq in enumerate(vibfreqs): if freq < 0: neg_freqs_idx.append(i) if vibfreqs[i] < vibfreqs[largest_neg_freq_idx]: largest_neg_freq_idx = i else: # assuming frequencies are ordered, break after the first positive freq encountered break if vibfreqs[largest_neg_freq_idx] >= 0 or len(neg_freqs_idx) == 0: raise TrshError( 'Could not determine a negative frequency for species {0} ' 'while troubleshooting for it.'.format(label)) if len(neg_freqs_idx) == 1 and not len(neg_freqs_trshed): # species has one negative frequency, and has not been troubleshooted for it before logger.info( 'Species {0} has a negative frequency ({1}). Perturbing its geometry using the respective ' 'vibrational displacements'.format( label, vibfreqs[largest_neg_freq_idx])) neg_freqs_idx = [ largest_neg_freq_idx ] # indices of the negative frequencies to troubleshoot for elif len(neg_freqs_idx) == 1 and any([ np.allclose(vibfreqs[0], vf, rtol=1e-04, atol=1e-02) for vf in neg_freqs_trshed ]): # species has one negative frequency, and has been troubleshooted for it before factor = 1 + 0.1 * (len(neg_freqs_trshed) + 1) logger.info( 'Species {0} has a negative frequency ({1}) for the {2} time. Perturbing its geometry using ' 'the respective vibrational displacements, this time using a larger factor (x {3})' .format(label, vibfreqs[largest_neg_freq_idx], len(neg_freqs_trshed), factor)) neg_freqs_idx = [ largest_neg_freq_idx ] # indices of the negative frequencies to troubleshoot for elif len(neg_freqs_idx) > 1 and not any([ np.allclose(vibfreqs[0], vf, rtol=1e-04, atol=1e-02) for vf in neg_freqs_trshed ]): # species has more than one negative frequency, and has not been troubleshooted for it before logger.info( 'Species {0} has {1} negative frequencies. Perturbing its geometry using the vibrational ' 'displacements of its largest negative frequency, {2}'.format( label, len(neg_freqs_idx), vibfreqs[largest_neg_freq_idx])) neg_freqs_idx = [ largest_neg_freq_idx ] # indices of the negative frequencies to troubleshoot for elif len(neg_freqs_idx) > 1 and any([ np.allclose(vibfreqs[0], vf, rtol=1e-04, atol=1e-02) for vf in neg_freqs_trshed ]): # species has more than one negative frequency, and has been troubleshooted for it before logger.info( 'Species {0} has {1} negative frequencies. Perturbing its geometry using the vibrational' ' displacements of ALL negative frequencies'.format( label, len(neg_freqs_idx))) current_neg_freqs_trshed = [ round(vibfreqs[i], 2) for i in neg_freqs_idx ] # record trshed negative freqs atomcoords = atomcoords[ -1] # it's a list within a list, take the last geometry for neg_freq_idx in neg_freqs_idx: displacement = vibdisps[neg_freq_idx] xyz1 = atomcoords + factor * displacement xyz2 = atomcoords - factor * displacement conformers.append(get_xyz_string(coords=xyz1, numbers=atomnos)) conformers.append(get_xyz_string(coords=xyz2, numbers=atomnos)) return current_neg_freqs_trshed, conformers, output_errors, output_warnings