def test_get_bond_rearrangs(): if os.path.exists('test_bond_rearrangs.txt'): os.remove('test_bond_rearrangs.txt') # ethane --> Ch3 + Ch3 reac = Molecule(smiles='CC') prod = Molecule(atoms=[Atom('C', -8.3, 1.4, 0.0), Atom('C', 12, 1.7, -0.0), Atom('H', -8.6, 0.5, -0.5), Atom('H', -8.6, 2.3, -0.4), Atom('H', -8.6, 1.3, 1), Atom('H', 12.3, 1.7, -1.0), Atom('H', 12.4, 0.8, 0.4), Atom('H', 12.3, 2.5, 0.5)]) assert br.get_bond_rearrangs(ReactantComplex(reac), ProductComplex(prod), name='test') == [br.BondRearrangement(breaking_bonds=[(0, 1)])] # Rerunning the get function should read test_bond_rearrangs.txt, so modify it, swapping 0 and 1 in the breaking # bond then reopen with open('test_bond_rearrangs.txt', 'w') as rearr_file: print('fbond\n' 'bbonds\n' '1 0\n' 'end', file=rearr_file) rearr = br.get_bond_rearrangs(ReactantComplex(reac), ProductComplex(prod), name='test')[0] assert rearr == BondRearrangement(breaking_bonds=[(1, 0)]) assert br.get_bond_rearrangs(ReactantComplex(prod), ProductComplex(reac), name='test2') is None # If reactants and products are identical then the rearrangement is undetermined assert br.get_bond_rearrangs(ReactantComplex(reac), ProductComplex(reac), name='test3') is None os.remove('test_bond_rearrangs.txt')
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 test_calculate_reaction_profile_energies(): test_reac = Reactant(name='test', smiles='C') test_reac.energy = -1 test_prod = Product(name='test', smiles='C') test_prod.energy = -1.03187251 tsguess = TSguess(atoms=test_reac.atoms, reactant=ReactantComplex(test_reac), product=ProductComplex()) tsguess.bond_rearrangement = BondRearrangement() ts = TransitionState(tsguess) ts.energy = -0.96812749 reaction = Reaction(test_reac, test_prod) reaction.ts = ts energies = plotting.calculate_reaction_profile_energies(reactions=[reaction], units=KcalMol) # Energies have been set to ∆E = -20 and ∆E‡ = 20 kcal mol-1 respectively assert energies[0] == 0 assert 19 < energies[1] < 21 assert -21 < energies[2] < -19 # Copying the reaction should give relative energies [0, 20, -20, 0, -40] energies = plotting.calculate_reaction_profile_energies(reactions=[reaction, deepcopy(reaction)], units=KcalMol) # Energies have been set to ∆E = -20 and ∆E‡ = 20 kcal mol-1 respectively assert energies[0] == 0 assert -0.1 < energies[3] < 0.1 assert -41 < energies[4] < -39
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 test_reaction_warnings(): test_reac = Reactant(name='test', smiles='C') test_reac.energy = -1 test_prod = Product(name='test', smiles='C') test_prod.energy = -1.03187251 tsguess = TSguess(atoms=test_reac.atoms, reactant=ReactantComplex(test_reac), product=ProductComplex()) tsguess.bond_rearrangement = BondRearrangement() ts = TransitionState(tsguess) ts.energy = -0.98 ts.imaginary_frequencies = [-100] reaction = Reaction(test_reac, test_prod) reaction.ts = None # Should be some warning with no TS assert len( plotting.get_reaction_profile_warnings(reactions=[reaction])) > 10 # Should be no warnings with a TS that exists and has an energy and one # imaginary freq reaction.ts = ts warnings = plotting.get_reaction_profile_warnings(reactions=[reaction]) assert 'None' in warnings
def test_calc_delta_e(): r1 = reaction.Reactant(name='h', atoms=[Atom('H', 0.0, 0.0, 0.0)]) r1.energy = -0.5 r2 = reaction.Reactant(name='h', atoms=[Atom('H', 0.0, 0.0, 0.0)]) r2.energy = -0.5 tsguess = TSguess(atoms=None, reactant=ReactantComplex(r1), product=ProductComplex(r2)) tsguess.bond_rearrangement = BondRearrangement() ts = TransitionState(tsguess) ts.energy = -0.8 p = reaction.Product( name='hh', atoms=[Atom('H', 0.0, 0.0, 0.0), Atom('H', 1.0, 0.0, 0.0)]) p.energy = -1.0 reac = reaction.Reaction(r1, r2, p) reac.ts = ts assert -1E-6 < reac.calc_delta_e() < 1E-6 assert 0.2 - 1E-6 < reac.calc_delta_e_ddagger() < 0.2 + 1E-6
def test_plot_reaction_profile(): r = Reactant(name='reactant', smiles='C') p = Product(name='product', smiles='C') tsguess = TSguess(atoms=r.atoms, reactant=ReactantComplex(r), product=ProductComplex(p)) tsguess.bond_rearrangement = BondRearrangement() ts = TransitionState(tsguess) reaction = Reaction(r, p) reaction.ts = ts plotting.plot_reaction_profile(reactions=[reaction], units=KjMol, name='test') assert os.path.exists('test_reaction_profile.png') os.remove('test_reaction_profile.png') with pytest.raises(AssertionError): plotting.plot_reaction_profile(reactions=[reaction], units=KjMol, name='test', free_energy=True, enthalpy=True) return None
def test_ts_conformer(tmpdir): os.chdir(tmpdir) ch3cl = Reactant(charge=0, mult=1, atoms=[ Atom('Cl', 1.63664, 0.02010, -0.05829), Atom('C', -0.14524, -0.00136, 0.00498), Atom('H', -0.52169, -0.54637, -0.86809), Atom('H', -0.45804, -0.50420, 0.92747), Atom('H', -0.51166, 1.03181, -0.00597) ]) f = Reactant(charge=-1, mult=1, atoms=[Atom('F', 4.0, 0.0, 0.0)]) ch3f = Product(charge=0, mult=1, atoms=[ Atom('C', -0.05250, 0.00047, -0.00636), Atom('F', 1.31229, -0.01702, 0.16350), Atom('H', -0.54993, -0.04452, 0.97526), Atom('H', -0.34815, 0.92748, -0.52199), Atom('H', -0.36172, -0.86651, -0.61030) ]) cl = Reactant(charge=-1, mult=1, atoms=[Atom('Cl', 4.0, 0.0, 0.0)]) f_ch3cl_tsguess = TSguess(reactant=ReactantComplex(f, ch3cl), product=ProductComplex(ch3f, cl), atoms=[ Atom('F', -2.66092, -0.01426, 0.09700), Atom('Cl', 1.46795, 0.05788, -0.06166), Atom('C', -0.66317, -0.01826, 0.02488), Atom('H', -0.78315, -0.58679, -0.88975), Atom('H', -0.70611, -0.54149, 0.97313), Atom('H', -0.80305, 1.05409, 0.00503) ]) f_ch3cl_tsguess.bond_rearrangement = BondRearrangement(breaking_bonds=[ (2, 1) ], forming_bonds=[(0, 2)]) f_ch3cl_ts = TransitionState(ts_guess=f_ch3cl_tsguess) atoms = conf_gen.get_simanl_atoms( species=f_ch3cl_ts, dist_consts=get_distance_constraints(f_ch3cl_ts)) regen = Molecule(name='regenerated_ts', charge=-1, mult=1, atoms=atoms) # regen.print_xyz_file() # Ensure the making/breaking bonds retain their length regen_coords = regen.get_coordinates() assert are_coords_reasonable(regen_coords) is True assert 1.9 < np.linalg.norm(regen_coords[0] - regen_coords[2]) < 2.1 assert 2.0 < np.linalg.norm(regen_coords[1] - regen_coords[2]) < 2.2 os.chdir(here)
def test_n_membered_rings(): h2o = Molecule(atoms=[Atom('O'), Atom('H', x=-1), Atom('H', x=1)]) bond_rearr = BondRearrangement(forming_bonds=[(1, 2)]) # Forming bond over H-H should give a single 3-membered ring assert bond_rearr.n_membered_rings(h2o) == [3] bond_rearr = BondRearrangement(breaking_bonds=[(0, 1)]) assert bond_rearr.n_membered_rings(h2o) == [] # Breaking an O-H and forming a H-H should not make any rings bond_rearr = BondRearrangement(breaking_bonds=[(0, 2)], forming_bonds=[(1, 2)]) assert bond_rearr.n_membered_rings(h2o) == [3]
def test_reactant_complex_truncation(): # Non-sensical bond rearrangement bond_rearr = BondRearrangement(forming_bonds=[(0, 1)], breaking_bonds=[(0, 5)]) methane_dimer = ReactantComplex(methane, methane) # Should not truncate methane dimer at all truncated = get_truncated_complex(methane_dimer, bond_rearr) assert truncated.n_atoms == 10
def test_product_complex_truncation(): # H atom transfer from methane to ethene bond_rearr = BondRearrangement(breaking_bonds=[(0, 1)], forming_bonds=[(1, 5)]) methane_ethene = ReactantComplex(methane, ethene, name='product_complex') # Should retain all atoms truncated = get_truncated_complex(methane_ethene, bond_rearr) assert truncated.n_atoms == 11
def test_enone_truncation(): enone = Reactant(name='enone', smiles='CC(O)=CC(=O)OC') reactant = ReactantComplex(enone) bond_rearr = BondRearrangement(breaking_bonds=[(2, 11)], forming_bonds=[(11, 5)]) truncated = get_truncated_complex(reactant, bond_rearr) assert truncated.n_atoms == 10 assert truncated.graph.number_of_edges() == 9
def test_reac_to_prods(): rearrang = BondRearrangement([(0, 4)], [(3, 4)]) prod_graph = mol_graphs.reac_graph_to_prod_graph(g, rearrang) expected_edges = [(0, 1), (1, 2), (2, 0), (0, 3), (0, 4)] expected_graph = nx.Graph() for edge in expected_edges: expected_graph.add_edge(*edge) assert mol_graphs.is_isomorphic(expected_graph, prod_graph)
def test_has_correct_mode_no_calc(): bond_rearr = BondRearrangement(breaking_bonds=[(2, 1)], forming_bonds=[(0, 2)]) calc = Calculation(molecule=ts, method=method, keywords=method.keywords.opt_ts, name='tmp') # Calculation has no output so should not have the correct mode assert not imag_mode_has_correct_displacement( calc, bond_rearrangement=bond_rearr)
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_prune_small_rings3(): # Square H4 "molecule" h4 = Molecule(atoms=[ Atom('H'), Atom('H', x=0.5), Atom('H', y=0.5), Atom('H', x=0.5, y=0.5) ]) make_graph(h4, allow_invalid_valancies=True) # Some unphysical bond rearrangements three_mem = BondRearrangement(forming_bonds=[(0, 3)], breaking_bonds=[(1, 2)]) four_mem = BondRearrangement(forming_bonds=[(0, 1)], breaking_bonds=[(1, 2)]) bond_rearrs = [three_mem, four_mem] ade.Config.skip_small_ring_tss = True br.prune_small_ring_rearrs(bond_rearrs, h4) # Should not prune if there are different ring sizes assert len(bond_rearrs) == 2
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 test_core_strip(): bond_rearr = BondRearrangement() bond_rearr.active_atoms = [0] stripped = get_truncated_complex(methane, bond_rearr) # Should not strip any atoms if the carbon is designated as active assert stripped.n_atoms == 5 stripped = get_truncated_complex(ethene, bond_rearr) assert stripped.n_atoms == 6 bond_rearr.active_atoms = [1] # Propene should strip to ethene if the terminal C=C is the active atom stripped = get_truncated_complex(propene, bond_rearr) assert stripped.n_atoms == 6 assert is_isomorphic(stripped.graph, ethene.graph) # But-1-ene should strip to ethene if the terminal C=C is the active atom stripped = get_truncated_complex(but1ene, bond_rearr) assert stripped.n_atoms == 6 assert is_isomorphic(stripped.graph, ethene.graph) # Benzene shouldn't be truncated at all stripped = get_truncated_complex(benzene, bond_rearr) assert stripped.n_atoms == 12 bond_rearr.active_atoms = [0] # Ethanol with the terminal C as the active atom should not replace the OH # with a H stripped = get_truncated_complex(ethanol, bond_rearr) assert stripped.n_atoms == 9 # Ether with the terminal C as the active atom should replace the OMe with # OH stripped = get_truncated_complex(methlyethylether, bond_rearr) assert stripped.n_atoms == 9 assert is_isomorphic(stripped.graph, ethanol.graph)
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_more_forming_than_breaking(): h_a = Reactant(atoms=[Atom('H')], name='h_a') h_b = Reactant(atoms=[Atom('H')], name='h_b') h2_sep = Complex(h_a, h_b) h2 = Product(atoms=[Atom('H'), Atom('H', x=1)], name='h2') rxn = Reaction(h_a, h_b, h2) bond_rearr = BondRearrangement(forming_bonds=[(0, 1)], breaking_bonds=None) assert bond_rearr.n_fbonds > bond_rearr.n_bbonds # Number of bonds in the product needs to be the same or fewer than # the reactant currently. Will need more get_ts_guess_function_and_params # if this is to be supproted with pytest.raises(NotImplementedError): _ = get_ts(reaction=rxn, reactant=h2_sep, bond_rearr=bond_rearr)
def test_correct_imag_mode(): os.chdir(os.path.join(here, 'data')) bond_rearrangement = BondRearrangement(breaking_bonds=[(4, 1), (4, 18)], forming_bonds=[(1, 18)]) g09 = G09() g09.available = True calc = Calculation(name='tmp', molecule=ReactantComplex( Reactant(smiles='CC(C)(C)C1C=CC=C1')), method=g09, keywords=Config.G09.keywords.opt_ts) calc.output.filename = 'correct_ts_mode_g09.log' calc.output.set_lines() f_displaced_atoms = get_displaced_atoms_along_mode(calc, mode_number=6, disp_magnitude=1.0) f_species = Species(name='f_displaced', atoms=f_displaced_atoms, charge=0, mult=1) # Charge & mult are placeholders b_displaced_atoms = get_displaced_atoms_along_mode(calc, mode_number=6, disp_magnitude=-1.0) b_species = Species(name='b_displaced', atoms=b_displaced_atoms, charge=0, mult=1) # With the correct mode no other bonds are made assert not imag_mode_generates_other_bonds( ts=calc.molecule, f_species=f_species, b_species=b_species, bond_rearrangement=bond_rearrangement) calc.output.filename = 'incorrect_ts_mode_g09.log' calc.output.set_lines() assert not imag_mode_has_correct_displacement(calc, bond_rearrangement) os.chdir(here)
def test_plot_reaction_profile(): # only tests the file is created with the right name os.chdir(os.path.join(here, 'data')) r = Reactant(name='reactant', smiles='C') p = Product(name='product', smiles='C') tsguess = TSguess(atoms=r.atoms, reactant=ReactantComplex(r), product=ProductComplex(p)) tsguess.bond_rearrangement = BondRearrangement() ts = TransitionState(tsguess) reaction = Reaction(r, p) reaction.ts = ts plotting.plot_reaction_profile(reactions=[reaction], units=KjMol, name='test_reaction') assert os.path.exists('test_reaction_reaction_profile.png') os.remove('test_reaction_reaction_profile.png') os.chdir(here)
def test_plot_reaction_profile(): r = Reactant(name='reactant', smiles='C') p = Product(name='product', smiles='C') tsguess = TSguess(atoms=r.atoms, reactant=ReactantComplex(r), product=ProductComplex(p)) tsguess.bond_rearrangement = BondRearrangement() ts = TransitionState(tsguess) reaction = Reaction(r, p) reaction.ts = ts plotting.plot_reaction_profile(reactions=[reaction], units=KjMol, name='test_reaction') assert os.path.exists('test_reaction_reaction_profile.png') os.remove('test_reaction_reaction_profile.png')
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_ts_template(): # Spoof XTB install Config.XTB.path = here Config.ts_template_folder_path = os.path.join(here, 'data', 'ts_guess') bond_rearr = BondRearrangement(breaking_bonds=[(2, 1)], forming_bonds=[(0, 2)]) reac_shift = reac_complex.copy() reac_shift.set_atoms(atoms=[ Atom('F', -3.0587, -0.8998, -0.2180), Atom('Cl', 0.3842, 0.86572, -1.65507), Atom('C', -1.3741, -0.0391, -0.9719), Atom('H', -1.9151, -0.0163, -1.9121), Atom('H', -1.6295, 0.6929, -0.2173), Atom('H', -0.9389, -0.9786, -0.6534) ]) reac_shift.print_xyz_file() templates = get_ts_templates() assert len(templates) == 1 assert templates[0].graph.number_of_nodes() == 6 tsg_template = get_template_ts_guess(reac_shift, product_complex, name='template', bond_rearr=bond_rearr, method=XTB(), dist_thresh=4.0) # Reset the folder path to the default Config.ts_template_folder_path = None assert tsg_template is not None
def test_prune_small_rings(): # Cope rearrangement reactant cope_r = Molecule(atoms=[ Atom('C', -1.58954, 1.52916, -0.43451), Atom('C', -1.46263, 0.23506, 0.39601), Atom('C', -0.57752, 2.62322, -0.15485), Atom('H', -2.59004, 1.96603, -0.22830), Atom('H', -1.55607, 1.26799, -1.51381), Atom('C', 0.40039, 2.56394, 0.75883), Atom('C', -0.24032, -0.62491, 0.13974), Atom('H', -2.34641, -0.39922, 0.17008), Atom('H', -1.50638, 0.49516, 1.47520), Atom('C', 0.72280, -0.36227, -0.75367), Atom('H', -0.66513, 3.53229, -0.74242), Atom('H', 0.55469, 1.70002, 1.39347), Atom('H', 1.07048, 3.40870, 0.88117), Atom('H', -0.14975, -1.53366, 0.72733), Atom('H', 0.70779, 0.51623, -1.38684), Atom('H', 1.55578, -1.04956, -0.86026) ]) six_mem = BondRearrangement(forming_bonds=[(5, 9)], breaking_bonds=[(1, 0)]) assert six_mem.n_membered_rings(mol=cope_r) == [6] four_mem = BondRearrangement(forming_bonds=[(0, 9)], breaking_bonds=[(1, 0)]) assert four_mem.n_membered_rings(cope_r) == [4] ade.Config.skip_small_ring_tss = False bond_rearrs = [six_mem, four_mem] br.prune_small_ring_rearrs(possible_brs=bond_rearrs, mol=cope_r) # Should not prune if Config.skip_small_ring_tss = False assert len(bond_rearrs) == 2 ade.Config.skip_small_ring_tss = True br.prune_small_ring_rearrs(possible_brs=bond_rearrs, mol=cope_r) # should remove the 4-membered ring assert len(bond_rearrs) == 1
from autode import Reactant, Product, Reaction from autode.bond_rearrangement import BondRearrangement from autode.transition_states.locate_tss import get_ts r = Reactant('cope_r.xyz') p = Product('cope_p.xyz') reaction = Reaction(r, p, name='cope') # Define the bond rearrangement as tuples for each forming and breaking bond bond_rearr = BondRearrangement(forming_bonds=[(5, 9)], breaking_bonds=[(0, 1)]) ts = get_ts(reaction, r, bond_rearr) print(ts.imaginary_frequencies)
]) cl = Product(charge=-1, mult=1, atoms=[Atom('Cl', 4.0, 0.0, 0.0)]) product_complex = ProductComplex(ch3f, cl) tsguess = TSguess(reactant=reac_complex, product=product_complex, atoms=[ Atom('F', -2.66092, -0.01426, 0.09700), Atom('Cl', 1.46795, 0.05788, -0.06166), Atom('C', -0.66317, -0.01826, 0.02488), Atom('H', -0.78315, -0.58679, -0.88975), Atom('H', -0.70611, -0.54149, 0.97313), Atom('H', -0.80305, 1.05409, 0.00503) ]) tsguess.bond_rearrangement = BondRearrangement(breaking_bonds=[(2, 1)], forming_bonds=[(0, 2)]) ts = TransitionState(ts_guess=tsguess) @testutils.work_in_zipped_dir(os.path.join(here, 'data', 'ts.zip')) def test_ts_guess_class(): # Force ORCA to appear available Config.hcode = 'orca' Config.ORCA.path = here assert tsguess.reactant.n_atoms == 6 assert tsguess.product.n_atoms == 6 # C -- Cl distance should be long