def test_full_calc_with_xtb(): sn2_neb = neb.NEB( initial_species=Species(name='inital', charge=-1, mult=0, atoms=xyz_file_to_atoms('sn2_init.xyz')), final_species=Species(name='final', charge=-1, mult=0, atoms=xyz_file_to_atoms('sn2_final.xyz')), num=14) sn2_neb.interpolate_geometries() xtb = XTB() # Don't run the NEB without a working XTB install if shutil.which('xtb') is None or not shutil.which('xtb').endswith('xtb'): return xtb.path = shutil.which('xtb') sn2_neb.calculate(method=xtb, n_cores=2) # There should be a peak in this surface assert len(list(sn2_neb.get_species_saddle_point())) > 0 assert all(image.energy is not None for image in sn2_neb.images) energies = [image.energy for image in sn2_neb.images] path_energy = sum(energy - min(energies) for energy in energies) assert 0.35 < path_energy < 0.45
def test_get_ts_guess_neb(): reactant = Reactant(name='inital', charge=-1, mult=0, solvent_name='water', atoms=xyz_file_to_atoms(init_xyz)) product = Reactant(name='final', charge=-1, mult=0, solvent_name='water', atoms=xyz_file_to_atoms(final_xyz)) xtb = XTB() # Don't run the NEB without a working XTB install if shutil.which('xtb') is None or not shutil.which('xtb').endswith('xtb'): return xtb.path = shutil.which('xtb') ts_guess = neb.get_ts_guess_neb(reactant, product, method=xtb, n=10) assert ts_guess is not None # Approximate distances at the TS guess assert 1.8 < ts_guess.get_distance(0, 2) < 2.2 # C-F assert 1.9 < ts_guess.get_distance(2, 1) < 2.3 # C-Cl if os.path.exists('NEB'): shutil.rmtree('NEB') if os.path.exists('neb.xyz'): os.remove('neb.xyz')
def test_isomorphic_no_active(): os.chdir(os.path.join(here, 'data')) ts_syn = Conformer(name='syn_ts', charge=-1, mult=0, atoms=xyz_file_to_atoms('E2_ts_syn.xyz')) mol_graphs.make_graph(ts_syn) mol_graphs.set_active_mol_graph(ts_syn, active_bonds=[(8, 5), (0, 5), (1, 2)]) ts_anti = Conformer(name='anti_ts', charge=-1, mult=0, atoms=xyz_file_to_atoms('E2_ts.xyz')) mol_graphs.make_graph(ts_anti) assert mol_graphs.is_isomorphic(ts_syn.graph, ts_anti.graph, ignore_active_bonds=True) os.chdir(here)
def test_graph_no_other_bonds(): reac = Reactant(name='r', atoms=xyz_file_to_atoms('h_shift_correct_ts_mode.xyz')) br = BondRearrangement(breaking_bonds=[(1, 10)], forming_bonds=[(5, 10)]) calc = Calculation(name='h_shift', molecule=reac, method=orca, keywords=orca.keywords.opt_ts, n_cores=1) calc.output.filename = 'h_shift_correct_ts_mode.out' calc.output.file_lines = open('h_shift_correct_ts_mode.out', 'r').readlines() f_ts = Species(name='f_displaced', charge=0, mult=1, atoms=get_displaced_atoms_along_mode(calc, mode_number=6, disp_magnitude=1.0)) b_ts = Species(name='b_displaced', charge=0, mult=1, atoms=get_displaced_atoms_along_mode(calc, mode_number=6, disp_magnitude=-1.0)) assert not imag_mode_generates_other_bonds(ts=reac, f_species=f_ts, b_species=b_ts, bond_rearrangement=br)
def __init__(self, xyz_filename=None, charge=0, spin_multiplicity=1, gmx_itp_filename=None, atoms=None): """Molecule e.g. H2O ----------------------------------------------------------------------- :param xyz_filename: (str) :param charge: (int) :param spin_multiplicity: (int) :param gmx_itp_filename: (str) Filename(path) of the GROMACS .itp file containing MM parameters required to simulate :param atoms: (list(autode.atoms.Atom)) """ if xyz_filename is not None: atoms = xyz_file_to_atoms(xyz_filename) super().__init__(charge=charge, spin_multiplicity=spin_multiplicity, atoms=atoms) self.itp_filename = gmx_itp_filename self.name = str(self) logger.info(f'Initialised {self.name}\n' f'Number of atoms = {self.n_atoms}\n' f'GROMACS itp filename = {self.itp_filename}')
def test_translate_rotate(): reactant = ReactantComplex( Reactant(name='F-', charge=-1, atoms=[Atom('F')]), Reactant(name='alkeneCl', atoms=xyz_file_to_atoms('alkene.xyz'))) assert len(reactant.molecules) == 2 # Initially the geometry is not sensible assert reactant.get_distance(0, 2) < 1.0 # SN2' bond rearrangement bond_rearr = BondRearrangement(forming_bonds=[(0, 1)], breaking_bonds=[(3, 4)]) translate_rotate_reactant(reactant, bond_rearr, shift_factor=1.5) assert len(reactant.atoms) == 10 os.remove('complex.xyz') # The geometry should now be sensible for i in range(1, 10): assert reactant.get_distance(0, i) > 2.0 # Should be closer to the end carbon than the middle assert reactant.get_distance(0, 1) < reactant.get_distance(0, 2)
def get_atoms_from_generated_file(species, xyz_filename): """ Get atoms from a previously generated .xyz file, if the atoms match Arguments: species (autode.species.Species): xyz_filename (str): Returns: (list(autode.atoms.Atoms)) or None: Atoms from file """ if not os.path.exists(xyz_filename): return None atoms = xyz_file_to_atoms(filename=xyz_filename) if len(atoms) != species.n_atoms: return None all_atoms_match = all(atoms[i].label == species.atoms[i].label for i in range(species.n_atoms)) if all_atoms_match: logger.info('Conformer has already been generated') return atoms return None
def test_get_ts_guess_neb(): reactant = Reactant(name='inital', charge=-1, mult=0, solvent_name='water', atoms=xyz_file_to_atoms('sn2_init.xyz')) product = Reactant(name='final', charge=-1, mult=0, solvent_name='water', atoms=xyz_file_to_atoms('sn2_final.xyz')) xtb = XTB() # Don't run the NEB without a working XTB install if shutil.which('xtb') is None or not shutil.which('xtb').endswith('xtb'): return xtb.path = shutil.which('xtb') ts_guess = get_ts_guess_neb(reactant, product, method=xtb, n=10) assert ts_guess is not None # Approximate distances at the TS guess assert 1.8 < ts_guess.distance(0, 2) < 2.3 # C-F assert 1.9 < ts_guess.distance(2, 1) < 2.5 # C-Cl if os.path.exists('NEB'): shutil.rmtree('NEB') if os.path.exists('neb.xyz'): os.remove('neb.xyz') # Trying to get a TS guess with an unavailable method should return None # as a TS guess orca = ORCA() orca.path = None orca_ts_guess = get_ts_guess_neb(reactant, product, method=orca, n=10) assert orca_ts_guess is None
def _init_xyz_file(self, xyz_filename): """Initialise a molecule from a .xyz file""" logger.info('Generating species from .xyz file') self.set_atoms(atoms=xyz_file_to_atoms(xyz_filename)) # Override the default name with something more descriptive if self.name == 'molecule' or self.name.endswith('.xyz'): self.name = xyz_filename.rstrip('.xyz') return None
def test_xyz_file_incorrect_first_line(): with open('test.xyz', 'w') as xyz_file: print('XXX', 'wrong first line', 'H 0.0 0.0 0.0', sep='\n', file=xyz_file) with pytest.raises(XYZfileWrongFormat): _ = xyz_file_to_atoms('test.xyz') os.remove('test.xyz')
def test_xyz_file_incorrect_n_atoms(): with open('test.xyz', 'w') as xyz_file: print('2', 'wrongly declared number of atoms', 'H 0.0 0.0 0.0', sep='\n', file=xyz_file) with pytest.raises(XYZfileWrongFormat): _ = xyz_file_to_atoms('test.xyz') os.remove('test.xyz')
def test_xyz_file_to_atoms(): atoms = xyz_file_to_atoms(filename='opt_orca.xyz') assert len(atoms) == 5 assert type(atoms) == list assert type(atoms[0]) == Atom assert atoms[0].coord[0] == -0.137572 with pytest.raises(XYZfileDidNotExist): xyz_file_to_atoms(filename='test') with pytest.raises(XYZfileWrongFormat): xyz_file_to_atoms(filename='opt_orca_broken.xyz') with pytest.raises(XYZfileWrongFormat): xyz_file_to_atoms(filename='opt_orca_broken2.xyz') with pytest.raises(XYZfileWrongFormat): xyz_file_to_atoms(filename='wrong_ext.mol')
def test_large_truncation(): mol = ReactantComplex( Reactant(name='product', atoms=xyz_file_to_atoms('product.xyz'))) bond_rearr = BondRearrangement(breaking_bonds=[(7, 8), (14, 18)]) assert mol.n_atoms == 50 truncated = get_truncated_complex(r_complex=mol, bond_rearrangement=bond_rearr) assert truncated.n_atoms == 27 assert truncated.graph.number_of_edges() == 28
def test_attack_cost(): subst_centers = substitution.get_substitution_centres(reactant=reac_complex, bond_rearrangement=bond_rearr, shift_factor=2) ideal_complex = ReactantComplex(f, ch3cl) ideal_complex.set_atoms(atoms=xyz_file_to_atoms(os.path.join(here, 'data', 'sn2_reac_complex.xyz'))) attack_cost = substitution.attack_cost(reac_complex, subst_centers, attacking_mol_idx=0) ideal_attack_cost = substitution.attack_cost(ideal_complex, subst_centers, attacking_mol_idx=0) # The cost function should be larger for the randomly located reaction complex compared to the ideal assert attack_cost > ideal_attack_cost
def test_two_component_truncation(): propylbromide = Reactant(name='RBr', atoms=xyz_file_to_atoms('RBr.xyz')) chloride = Reactant(name='Cl', smiles='[Cl-]') mol = ReactantComplex(chloride, propylbromide) bond_rearr = BondRearrangement(forming_bonds=[(0, 3)], breaking_bonds=[(3, 4)]) truncated = get_truncated_complex(r_complex=mol, bond_rearrangement=bond_rearr) # Should truncate to ethylbromide + Cl- assert truncated.n_atoms == 9
def set_atoms(self, xyz_filename=None, atoms=None): """ Set self.atoms from either an xyz file or a list of atoms :param xyz_filename: (str) :param atoms: (list(autode.atoms.Atom)) """ if xyz_filename is not None: self.atoms = xyz_file_to_atoms(xyz_filename) if atoms is not None: self.atoms = atoms self.forces = None return None
def test_subst(): reactant = Reactant(name='sn2_r', atoms=xyz_file_to_atoms('reactant.xyz')) # SN2' bond rearrangement bond_rearr = BondRearrangement(forming_bonds=[(0, 1)], breaking_bonds=[(3, 4)]) subst_centers = get_substitution_centres(reactant, bond_rearr, shift_factor=1.0) assert len(subst_centers) == 1 # get_substitution_centres should add a dummy atom so the ACX angle is # defined assert len(reactant.atoms) == 11
def test_xyz_file_to_atoms(): os.chdir(os.path.join(here, 'data')) atoms = xyz_file_to_atoms(filename='opt_orca.xyz') assert len(atoms) == 5 assert type(atoms) == list assert type(atoms[0]) == Atom assert atoms[0].coord[0] == -0.137572 with pytest.raises(XYZfileDidNotExist): xyz_file_to_atoms(filename='test') with pytest.raises(XYZfileWrongFormat): xyz_file_to_atoms(filename='opt_orca_broken.xyz') with pytest.raises(XYZfileWrongFormat): xyz_file_to_atoms(filename='opt_orca_broken2.xyz') with pytest.raises(XYZfileWrongFormat): xyz_file_to_atoms(filename='opt_orca.out') os.chdir(here)
def test_optts_no_reactants_products(): da_ts_guess = TSguess(atoms=xyz_file_to_atoms('da_TS_guess.xyz')) da_ts = TransitionState(da_ts_guess) da_ts.optimise() assert len(da_ts.imaginary_frequencies) == 1 imag_freq = da_ts.imaginary_frequencies[0] assert -500 < imag_freq < -300 # cm-1 # Should raise exceptions for TSs not initialised with reactants and # products with pytest.raises(ValueError): _ = da_ts.could_have_correct_imag_mode() with pytest.raises(ValueError): _ = da_ts.has_correct_imag_mode()
def test_random_grid_positions(): system = System(box_size=[10, 12, 14]) methane = Molecule(os.path.join(here, 'data', 'methane.xyz')) system.add_molecules(methane, n=5) config = system.random(grid=True) config.save(filename='test_random.xyz') # Minimum pairwise distance should be ~ the C-H distance (1.109 Å) atoms = xyz_file_to_atoms('test_random.xyz') coords = np.array([atom.coord for atom in atoms]) dist_matrix = distance_matrix(coords, coords) # Distance matrix has zeros along the diagonals so add the identity assert np.min(dist_matrix + 9 * np.identity(len(coords))) > 1.1 os.remove('test_random.xyz')
def get_and_copy_unique_confs(xyz_filenames, only_heavy_atoms, threshold_rmsd): """For each xyz file generate a species and copy it to a folder if it is unique based on an RMSD threshold""" molecules = [ Species(name=fn.rstrip('.xyz'), atoms=xyz_file_to_atoms(fn), charge=0, mult=1) for fn in xyz_filenames ] if only_heavy_atoms: molecules = get_molecules_no_hydrogens(molecules) unique_mol_ids = [] for i in range(len(molecules)): mol = molecules[i] is_unique = True for j in unique_mol_ids: rmsd = calc_rmsd(coords1=mol.get_coordinates(), coords2=molecules[j].get_coordinates()) if rmsd < threshold_rmsd: is_unique = False break if is_unique: unique_mol_ids.append(i) print('Number of unique molecules = ', len(unique_mol_ids)) # Copy all the unique .xyz files to a new folder if not os.path.exists(folder_name): os.mkdir(folder_name) for i in unique_mol_ids: xyz_filename = xyz_filenames[i] copyfile(xyz_filename, os.path.join(folder_name, xyz_filename)) return None
def has_correct_mode(name, fbonds, bbonds): reac = Reactant(name='r', atoms=xyz_file_to_atoms(f'{name}.xyz')) calc = Calculation(name=name, molecule=reac, method=orca, keywords=orca.keywords.opt_ts, n_cores=1) calc.output.filename = f'{name}.out' calc.output.file_lines = open(f'{name}.out', 'r').readlines() bond_rearr = BondRearrangement(breaking_bonds=bbonds, forming_bonds=fbonds) # Don't require all bonds to be breaking/making in a 'could be ts' function return imag_mode_has_correct_displacement(calc, bond_rearr, delta_threshold=0.05, req_all=False)
def test_removing_active_bonds(): filepath = os.path.join(here, 'data', 'neb', 'co_complex.xyz') mol = Molecule(name='co_complex', atoms=xyz_file_to_atoms(filepath)) bbond = BreakingBond(atom_indexes=(1, 2), species=mol) fbond = FormingBond(atom_indexes=(3, 1), species=mol) bonds = neb.active_bonds_no_rings(mol, fbonds=[fbond], bbonds=[bbond]) # Should remove the breaking bond as it forms a ring with an already # present atom pair assert len(bonds) == 1 assert isinstance(bonds[0], FormingBond) # Also check that the number of images to generate are somewhat reasonable n_images = neb.calc_n_images(fbonds=[fbond], bbonds=[bbond], average_spacing=0.1) assert 0 < n_images < 20
def test_mp2_numerical_gradients(): calc = Calculation( name='tmp', molecule=Molecule(atoms=xyz_file_to_atoms('tmp_orca.xyz')), method=method, keywords=method.keywords.grad) calc.output.filename = 'tmp_orca.out' calc.output.file_lines = open(calc.output.filename, 'r').readlines() gradients = calc.get_gradients() assert len(gradients) == 6 expected = np.array([-0.00971201, -0.00773534, -0.02473580 ]) / Constants.a02ang assert np.linalg.norm(expected - gradients[0]) < 1e-6 # Test for different printing with numerical.. calc.output.filename = 'numerical_orca.out' calc.output.file_lines = open(calc.output.filename, 'r').readlines() gradients = calc.get_gradients() assert len(gradients) == 6 expected = np.array([0.012397372, 0.071726232, -0.070942743 ]) / Constants.a02ang assert np.linalg.norm(expected - gradients[0]) < 1e-6
from autode import Molecule from autode.input_output import xyz_file_to_atoms from autode.conformers import conf_gen, Conformer from autode.methods import XTB # Initialise the complex from a .xyz file containing a square planar structure vaskas = Molecule(name='vaskas', atoms=xyz_file_to_atoms('vaskas.xyz')) # Set up some distance constraints where the keys are the atom indexes and # the value the distance in Å. Fixing the Cl-P, Cl-P and Cl-C(=O) distances # enforces a square planar geometry distance_constraints = { (1, 2): vaskas.get_distance(1, 2), (1, 3): vaskas.get_distance(1, 3), (1, 4): vaskas.get_distance(1, 4) } # Generate 5 conformers for n in range(5): # Apply random displacements to each atom and minimise under a bonded + # repulsive forcefield including the distance constraints atoms = conf_gen.get_simanl_atoms(species=vaskas, dist_consts=distance_constraints, conf_n=n) # Generate a conformer from these atoms then optimise with XTB conformer = Conformer(name=f'vaskas_conf{n}', atoms=atoms) conformer.optimise(method=XTB()) conformer.print_xyz_file()
Product(name='diene2', smiles='C/C=C/CC/C=C/C'), name='cope_rearrangement') da = Reaction(Reactant(name='butadiene', smiles='C=CC=C'), Reactant(name='ethene', smiles='C=C'), Product(name='cyclohexene', smiles='C1C=CCCC1'), name='diels_alder') h_shift = Reaction(Reactant(name='radical1', smiles='CC[C]([H])[H]'), Product(name='radical2', smiles='C[C]([H])C'), name='h_shift') int1_xyz = os.path.join(data_path, 'INT1.xyz') int2_xyz = os.path.join(data_path, 'INT2.xyz') mig_insert = Reaction(Reactant(name='INT1', atoms=xyz_file_to_atoms(int1_xyz)), Product(name='INT2', atoms=xyz_file_to_atoms(int2_xyz)), name='h_insert') print(f'Name v_imag / cm-1 Time / min Success') for reaction in [sn2, cope, da, h_shift, mig_insert]: start_time = time() reaction.locate_transition_state() freq = reaction.ts.imaginary_frequencies[0] print(f'{reaction.name:.15}' f'{freq:15.1f}' f'{(time()- start_time)/60:15.1f}' f'{"✓" if freq < -100 else "✗":.15s}')