def test_from_smiles(smiles): """ Make sure hydrogens are added to a molecule when needed. """ mol = Ligand.from_smiles(smiles_string=smiles, name="methane") # count the number of hydrogens hs = sum([1 for atom in mol.atoms if atom.atomic_symbol == "H"]) assert hs == 4
def test_torsion_finder_multiple(): """ Find non hydrogen torsions for multiple rotatable bonds. """ mol = Ligand.from_smiles("CCO", "ethanol") bonds = mol.find_rotatable_bonds() for bond in bonds: torsion = find_heavy_torsion(molecule=mol, bond=bond) check_proper_torsion(torsion=torsion, molecule=mol)
def test_to_rdkit_complicated_stereo(): """ Make sure we can convert a complicated molecule with multiple stereo centres to rdkit. """ mol = Ligand.from_smiles( "[H][C@]1([C@@]([C@](O[C@@]1([H])C([H])([H])OP(=O)(O[H])O[H])([H])N2C(=C([N+](C2([H])[H])([H])[H])C(=O)N([H])[H])O[H])([H])O[H])O[H]", name="complicated", ) mol.to_rdkit()
def test_torsion_special_case_double(): """ Make sure special cases changes the scan range for a bond. """ mol = Ligand.from_smiles("CO", "methanol") t_scan = TorsionScan1D() t_scan.clear_avoided_torsions() # add the special case with non-default range t_scan.add_special_torsion(smirks="[*:1]-[OH1:2]", scan_range=(0, 180)) # get the one rotatable bond bond = mol.find_rotatable_bonds()[0] scan_range = t_scan._get_scan_range(molecule=mol, bond=bond) assert scan_range == (0, 180)
def test_to_rdkit_kekule(molecule): """ Make sure we can correctly convert a molecule to rdkit when it has mixed aromatic and non-aromatic rings. This test is due to an kekule error where non aromatic atoms were sometimes given aromatic bonds. """ mol = Ligand.from_smiles(molecule, name="test") rd_mol = mol.to_rdkit() for atom in rd_mol.GetAtoms(): qb_atom = mol.atoms[atom.GetIdx()] assert atom.GetIsAromatic() is qb_atom.aromatic for bond in rd_mol.GetBonds(): qb_bond = mol.bonds[bond.GetIdx()] assert qb_bond.aromatic is bond.GetIsAromatic() assert qb_bond.bond_order == bond.GetBondTypeAsDouble()
def run( input_file: Optional[str] = None, smiles: Optional[str] = None, name: Optional[str] = None, multiplicity: int = 1, end: Optional[str] = None, skip_stages: Optional[List[str]] = None, config: Optional[str] = None, protocol: Optional[str] = None, cores: Optional[int] = None, memory: Optional[int] = None, ): """Run the QUBEKit parametrisation workflow on an input molecule.""" # make sure we have an input or smiles not both if input_file is not None and smiles is not None: raise RuntimeError( "Please supply either the name of the input file or a smiles string not both." ) # load the molecule if input_file is not None: molecule = Ligand.from_file(file_name=input_file, multiplicity=multiplicity) else: if name is None: raise RuntimeError( "Please also pass a name for the molecule when starting from smiles." ) molecule = Ligand.from_smiles(smiles_string=smiles, name=name, multiplicity=multiplicity) # load workflow workflow = prep_config(config_file=config, memory=memory, cores=cores, protocol=protocol) # move into the working folder and run with folder_setup( f"QUBEKit_{molecule.name}_{datetime.now().strftime('%Y_%m_%d')}"): # write the starting molecule molecule.to_file(file_name=f"{molecule.name}.pdb") workflow.new_workflow(molecule=molecule, skip_stages=skip_stages, end=end)
def test_double_dihedral(tmpdir): """Test running a molecule with two rotatable bonds.""" with tmpdir.as_cwd(): mol = Ligand.from_smiles("CCO", "ethanol") # build a scanner with grid spacing 60 and clear out avoided methyl tdrive = TorsionDriver( program="rdkit", method="uff", basis=None, cores=1, memory=1, n_workers=1, grid_spacing=60, ) t_scan = TorsionScan1D(torsion_driver=tdrive) t_scan.clear_avoided_torsions() result_mol = t_scan.run(molecule=mol) assert len(result_mol.qm_scans) == 2 # make sure input molecule coords were not changed assert np.allclose(mol.coordinates, result_mol.coordinates)
def mol_47(): return Ligand.from_smiles("CC(C)(O)CCC(C)(C)O", "mol_47")
def run( bulk_file: str, skip_stages: Optional[List[str]] = None, end: Optional[str] = None, restart: Optional[str] = None, config: Optional[str] = None, protocol: Optional[str] = None, cores: Optional[int] = None, memory: Optional[int] = None, ) -> None: """Run the QUBEKit parametrisation workflow on a collection of molecules in serial. Loop over the molecules in order of the CSV file. """ import glob import os from qubekit.utils.helpers import mol_data_from_csv home = os.getcwd() # load all inputs bulk_data = mol_data_from_csv(bulk_file) # start main molecule loop for name, mol_data in bulk_data.items(): print(f"Analysing: {name}") try: if restart is not None or mol_data["restart"] is not None: # we are trying to restart a run, find the folder # should only be one fname = name.split(".")[0] folder = glob.glob(f"QUBEKit_{fname}_*")[0] with folder_setup(folder): results = WorkFlowResult.parse_file("workflow_result.json") if config is None: # if we have no new config load from results workflow = prep_config( results=results, cores=cores, memory=memory, protocol=None ) else: # load the new config file workflow = prep_config( config_file=config, cores=cores, memory=memory ) workflow.restart_workflow( start=restart or mol_data["restart"], skip_stages=skip_stages, end=end or mol_data["end"], result=results, ) else: if mol_data["smiles"] is not None: molecule = Ligand.from_smiles( smiles_string=mol_data["smiles"], name=name ) else: molecule = Ligand.from_file(file_name=name) # load the CLI config or the csv config, else default workflow = prep_config( config_file=config or mol_data["config_file"], memory=memory, cores=cores, protocol=protocol, ) # move into the working folder and run with folder_setup( f"QUBEKit_{molecule.name}_{datetime.now().strftime('%Y_%m_%d')}" ): # write the starting molecule molecule.to_file(file_name=f"{molecule.name}.pdb") workflow.new_workflow( molecule=molecule, skip_stages=skip_stages, end=end or mol_data["end"], ) except WorkFlowExecutionError: os.chdir(home) print( f"An error was encountered while running {name} see folder for more info." ) continue