def test_butene(tmpdir): os.chdir(tmpdir) butene = Molecule(name='z-but-2-ene', charge=0, mult=1, atoms=[ Atom('C', -1.69185, -0.28379, -0.01192), Atom('C', -0.35502, -0.40751, 0.01672), Atom('C', -2.39437, 1.04266, -0.03290), Atom('H', -2.13824, 1.62497, 0.87700), Atom('H', -3.49272, 0.88343, -0.05542), Atom('H', -2.09982, 1.61679, -0.93634), Atom('C', 0.57915, 0.76747, 0.03048), Atom('H', 0.43383, 1.38170, -0.88288), Atom('H', 1.62959, 0.40938, 0.05452), Atom('H', 0.39550, 1.39110, 0.93046), Atom('H', -2.29700, -1.18572, -0.02030), Atom('H', 0.07422, -1.40516, 0.03058) ]) butene.graph.nodes[0]['stereo'] = True butene.graph.nodes[1]['stereo'] = True # Conformer generation should retain the stereochemistry atoms = conf_gen.get_simanl_atoms(species=butene) regen = Molecule(name='regenerated_butene', atoms=atoms, charge=0, mult=1) regen.print_xyz_file() regen_coords = regen.get_coordinates() # The Z-butene isomer has a r(C_1 C_2) < 3.2 Å where C_1C=CC_2 assert np.linalg.norm(regen_coords[6] - regen_coords[2]) < 3.6 os.chdir(here)
def init_smiles(molecule, smiles): """ Initialise a molecule from a SMILES string Arguments: molecule (autode.molecule.Molecule): smiles (str): SMILES string """ # Assume that the RDKit conformer generation algorithm is not okay for # metals molecule.rdkit_conf_gen_is_fine = False parser = parse_smiles(smiles) molecule.charge = parser.charge molecule.mult = calc_multiplicity( molecule=molecule, n_radical_electrons=parser.n_radical_electrons) molecule.set_atoms(atoms=parser.atoms) make_graph(molecule, bond_list=parser.bonds) for stereocentre in parser.stereocentres: molecule.graph.nodes[stereocentre]['stereo'] = True for bond_index in parser.bond_order_dict.keys(): bond = parser.bonds[bond_index] molecule.graph.edges[bond]['pi'] = True molecule.set_atoms(atoms=get_simanl_atoms(molecule)) check_bonds(molecule, bonds=parser.bonds) 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_conf_gen(tmpdir): os.chdir(tmpdir) atoms = conf_gen.get_simanl_atoms(species=methane) assert len(atoms) == 5 assert os.path.exists('methane_conf0_siman.xyz') # Rerunning the conformer generation should read the generated .xyz file atoms = conf_gen.get_simanl_atoms(species=methane) assert len(atoms) == 5 os.remove('methane_conf0_siman.xyz') # Ensure the new graph is identical regen = Molecule(name='regenerated_methane', atoms=atoms) assert regen.graph.edges == methane.graph.edges assert regen.graph.nodes == methane.graph.nodes os.chdir(here)
def init_organic_smiles(molecule, smiles): """ Initialise a molecule from a SMILES string, set the charge, multiplicity ( if it's not already specified) and the 3D geometry using RDKit Arguments: molecule (autode.molecule.Molecule): smiles (str): SMILES string """ try: molecule.rdkit_mol_obj = Chem.MolFromSmiles(smiles) if molecule.rdkit_mol_obj is None: logger.warning('RDKit failed to initialise a molecule') return init_smiles(molecule, smiles) molecule.rdkit_mol_obj = Chem.AddHs(molecule.rdkit_mol_obj) except RuntimeError: raise RDKitFailed molecule.charge = Chem.GetFormalCharge(molecule.rdkit_mol_obj) molecule.mult = calc_multiplicity(molecule, NumRadicalElectrons(molecule.rdkit_mol_obj)) bonds = [(bond.GetBeginAtomIdx(), bond.GetEndAtomIdx()) for bond in molecule.rdkit_mol_obj.GetBonds()] # Generate a single 3D structure using RDKit's ETKDG conformer generation # algorithm method = AllChem.ETKDGv2() method.randomSeed = 0xf00d AllChem.EmbedMultipleConfs(molecule.rdkit_mol_obj, numConfs=1, params=method) molecule.atoms = atoms_from_rdkit_mol(molecule.rdkit_mol_obj, conf_id=0) make_graph(molecule, bond_list=bonds) # Revert back to RR if RDKit fails to return a sensible geometry if not are_coords_reasonable(coords=molecule.coordinates): molecule.rdkit_conf_gen_is_fine = False molecule.atoms = get_simanl_atoms(molecule, save_xyz=False) for atom, _ in Chem.FindMolChiralCenters(molecule.rdkit_mol_obj): molecule.graph.nodes[atom]['stereo'] = True for bond in molecule.rdkit_mol_obj.GetBonds(): if bond.GetBondType() != Chem.rdchem.BondType.SINGLE: molecule.graph.edges[bond.GetBeginAtomIdx(), bond.GetEndAtomIdx()]['pi'] = True if bond.GetStereo() != Chem.rdchem.BondStereo.STEREONONE: molecule.graph.nodes[bond.GetBeginAtomIdx()]['stereo'] = True molecule.graph.nodes[bond.GetEndAtomIdx()]['stereo'] = True check_bonds(molecule, bonds=molecule.rdkit_mol_obj.GetBonds()) return None
def test_chiral_rotation(tmpdir): os.chdir(tmpdir) chiral_ethane = Molecule(name='chiral_ethane', charge=0, mult=1, atoms=[ Atom('C', -0.26307, 0.59858, -0.07141), Atom('C', 1.26597, 0.60740, -0.09729), Atom('Cl', -0.91282, 2.25811, 0.01409), Atom('F', -0.72365, -0.12709, 1.01313), Atom('H', -0.64392, 0.13084, -1.00380), Atom('Cl', 1.93888, 1.31880, 1.39553), Atom('H', 1.61975, 1.19877, -0.96823), Atom('Br', 1.94229, -1.20011, -0.28203) ]) chiral_ethane.graph.nodes[0]['stereo'] = True chiral_ethane.graph.nodes[1]['stereo'] = True atoms = conf_gen.get_simanl_atoms(chiral_ethane) regen = Molecule(name='regenerated_ethane', charge=0, mult=1, atoms=atoms) regen_coords = regen.get_coordinates() coords = chiral_ethane.get_coordinates() # Atom indexes of the C(C)(Cl)(F)(H) chiral centre ccclfh = [0, 1, 2, 3, 4] # Atom indexes of the C(C)(Cl)(Br)(H) chiral centre ccclbrh = [1, 0, 5, 7, 6] for centre_idxs in [ccclfh, ccclbrh]: # Ensure the fragmented centres map almost identically # if calc_rmsd(template_coords=coords[centre_idxs], coords_to_ # fit=regen_coords[centre_idxs]) > 0.5: # chiral_ethane.print_xyz_file(filename=os.path.join(here, # 'chiral_ethane.xyz')) # regen.print_xyz_file(filename=os.path.join(here, 'regen.xyz')) # RMSD on the 5 atoms should be < 0.5 Å assert calc_rmsd(coords1=coords[centre_idxs], coords2=regen_coords[centre_idxs]) < 0.5 os.chdir(here)
def test_conf_gen_dist_const(tmpdir): os.chdir(tmpdir) hydrogen = Molecule( name='H2', charge=0, mult=1, atoms=[Atom(atomic_symbol='H'), Atom(atomic_symbol='H', z=0.7)]) # H2 at a bond length (r) of 0.7 Å has a bond assert len(hydrogen.graph.edges) == 1 # H2 at r = 1.5 Å is definitely not bonded atoms = conf_gen.get_simanl_atoms(species=hydrogen, dist_consts={(0, 1): 1.5}) long_hydrogen = Molecule(name='H2', atoms=atoms, charge=0, mult=1) assert long_hydrogen.n_atoms == 2 assert len(long_hydrogen.graph.edges) == 0 os.chdir(here)
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()