def test_parameter_round_trip(method, tmpdir): """ Check we can parametrise a molecule then write out the same parameters. """ with tmpdir.as_cwd(): mol = Ligand.from_file(get_data("acetone.sdf")) method(mol) # write out params mol.write_parameters(name="test") # make a second mol mol2 = Ligand.from_file(get_data("acetone.sdf")) XML(mol2, "test.xml") assert mol.AtomTypes == mol2.AtomTypes for bond in mol.HarmonicBondForce.keys(): assert mol.HarmonicBondForce[bond] == pytest.approx( mol2.HarmonicBondForce[bond], abs=1e-6 ) for angle in mol.HarmonicAngleForce.keys(): assert mol.HarmonicAngleForce[angle] == pytest.approx( mol2.HarmonicAngleForce[angle], abs=1e-6 ) for atom in range(mol.n_atoms): assert ( pytest.approx(mol.NonbondedForce[atom], abs=1e-6) == mol2.NonbondedForce[atom] ) for dihedral, terms in mol.PeriodicTorsionForce.items(): try: other_dih = mol2.PeriodicTorsionForce[dihedral] except KeyError: other_dih = mol2.PeriodicTorsionForce[tuple(reversed(dihedral))] assert np.allclose(terms[:4], other_dih[:4])
def handle_bulk(self): """ Getting and setting configs for bulk runs is a little different, requiring this method. The configs are taken from the .csv, then the .ini, then the terminal. This is repeated for each molecule in the bulk run, then Execute is called. Configs cannot be changed between molecule analyses as config data is only loaded once at the start; -restart is required for that. """ csv_file = self.args.bulk_run # mol_data_from_csv handles defaults if no argument is given bulk_data = mol_data_from_csv(csv_file) names = list(bulk_data) home = os.getcwd() for name in names: printf(f'Analysing: {name}\n') # Get pdb from smiles or name if no smiles is given if bulk_data[name]['smiles'] is not None: smiles_string = bulk_data[name]['smiles'] rdkit = RDKit() self.file = rdkit.smiles_to_pdb(smiles_string, name) else: self.file = f'{name}.pdb' # Initialise molecule, ready to add configs to it self.molecule = Ligand(self.file) # Read each row in bulk data and set it to the molecule object for key, val in bulk_data[name].items(): setattr(self.molecule, key, val) setattr(self.molecule, 'skip', []) # Using the config file from the .csv, gather the .ini file configs file_configs = Configure.load_config(self.molecule.config) for key, val in file_configs.items(): setattr(self.molecule, key, val) # Handle configs which are changed by terminal commands for key, val in vars(self.args).items(): if val is not None: setattr(self.molecule, key, val) # Now that all configs are stored correctly: execute. Execute(self.molecule) os.chdir(home) sys.exit( 'Bulk analysis complete.\nUse QUBEKit -progress to view the completion progress of your molecules' )
def setUpClass(cls): cls.files_folder = os.path.join( os.path.dirname(os.path.abspath(__file__)), 'files') os.mkdir('temp') os.chdir('temp') copy(os.path.join(cls.files_folder, 'acetone.pdb'), 'acetone.pdb') cls.molecule_pdb = Ligand('acetone.pdb') copy(os.path.join(cls.files_folder, 'acetone.mol2'), 'acetone.mol2') cls.molecule_mol2 = Ligand('acetone.mol2') os.chdir('../')
def __init__(self): # First make sure the config folder has been made missing for conda and pip home = str(Path.home()) config_folder = f'{home}/QUBEKit_configs/' if not os.path.exists(config_folder): os.makedirs(config_folder) print(f'Making config folder at: {home}') self.args = self.parse_commands() # If it's a bulk run, handle it separately # TODO Add .sdf as possible bulk_run, not just .csv if self.args.bulk_run: self.handle_bulk() if self.args.restart is not None: # Find the pickled checkpoint file and load it as the molecule try: self.molecule = unpickle()[self.args.restart] except FileNotFoundError: raise FileNotFoundError('No checkpoint file found!') else: if self.args.smiles: self.file = RDKit().smiles_to_pdb(*self.args.smiles) else: self.file = self.args.input # Initialise molecule self.molecule = Ligand(self.file) # Find which config file is being used self.molecule.config = self.args.config_file # Handle configs which are in a file file_configs = Configure.load_config(self.molecule.config) for name, val in file_configs.items(): setattr(self.molecule, name, val) # Although these may be None always, they need to be explicitly set anyway. setattr(self.molecule, 'restart', None) setattr(self.molecule, 'end', None) setattr(self.molecule, 'skip', None) # Handle configs which are changed by terminal commands for name, val in vars(self.args).items(): if val is not None: setattr(self.molecule, name, val) # If restarting put the molecule back into the checkpoint file with the new configs if self.args.restart is not None: self.molecule.pickle(state=self.args.restart) # Now that all configs are stored correctly: execute. Execute(self.molecule)
def setUpClass(cls): """ Write the big string in test_structures to a file to be used for testing. Cannot use actual files as pathing causes issues. """ with open('acetone.pdb', 'w+') as pdb_test_file: pdb_test_file.write(acetone) with open('acetone.mol2', 'w+') as mol2_test_file: mol2_test_file.write(acetone_mol2) cls.molecule_pdb = Ligand('acetone.pdb') cls.molecule_mol2 = Ligand('acetone.mol2')
def test_no_rotatable_bonds(): """ If there are no dihedrals in the molecule make sure we return None. """ mol = Ligand.from_file(file_name=get_data("water.pdb")) assert mol.rotatable_bonds is None assert mol.n_rotatable_bonds == 0
def test_no_impropers(): """ Make sure we return None when no impropers are found in the molecule. """ mol = Ligand.from_file(file_name=get_data("water.pdb")) assert mol.improper_torsions is None assert mol.n_improper_torsions == 0
def test_no_dihedrals(): """ Make sure we return None when no dihedrals are found in the molecule. """ mol = Ligand.from_file(file_name=get_data("water.pdb")) assert mol.dihedrals is None assert mol.n_dihedrals == 0
def test_smiles(self): # create a new molecule from a smiles string molecule_smiles = Ligand('CCC', 'ethane') # check the internal structures angles = { (1, 0, 3): 113.51815048217622, (1, 0, 4): 108.585923222101, (1, 0, 5): 106.72547221240829, (3, 0, 4): 108.67471750338844, (3, 0, 5): 109.86966536530876, (4, 0, 5): 109.3960638804494, (0, 1, 2): 112.47821537702777, (0, 1, 6): 106.25702918976113, (0, 1, 7): 113.72590402122567, (2, 1, 6): 106.3390387838715, (2, 1, 7): 111.69729882714941, (6, 1, 7): 105.65819247884409, (1, 2, 8): 108.59874810898711, (1, 2, 9): 112.19545440609062, (1, 2, 10): 111.67294842834627, (8, 2, 9): 111.33448705926884, (8, 2, 10): 107.47750840394838, (9, 2, 10): 105.46240504563437 } self.assertEqual(angles, molecule_smiles.angle_values)
def test_to_rdkit(molecule): """ Make sure we can convert to rdkit. We test on bace which has a chiral center and 12-dichloroethene which has a stereo bond. """ from rdkit import Chem mol = Ligand.from_file(file_name=get_data(molecule)) rd_mol = mol.to_rdkit() # make sure the atom and bond stereo match for atom in rd_mol.GetAtoms(): qb_atom = mol.atoms[atom.GetIdx()] assert atom.GetIsAromatic() is qb_atom.aromatic if qb_atom.stereochemistry is not None: if qb_atom.stereochemistry == "S": assert atom.GetChiralTag() == Chem.CHI_TETRAHEDRAL_CCW else: assert atom.GetChiralTag() == Chem.CHI_TETRAHEDRAL_CW 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() if qb_bond.stereochemistry is not None: if qb_bond.stereochemistry == "E": assert bond.GetStereo() == Chem.BondStereo.STEREOE else: assert bond.GetStereo() == Chem.BondStereo.STEREOZ
def test_smiles(self): """Ensure molecule is correctly generated from a smiles string.""" molecule_smiles = Ligand('CCC', 'propane') angles = { (1, 0, 3): 113.51815048217622, (1, 0, 4): 108.585923222101, (1, 0, 5): 106.72547221240829, (3, 0, 4): 108.67471750338844, (3, 0, 5): 109.86966536530876, (4, 0, 5): 109.3960638804494, (0, 1, 2): 112.47821537702777, (0, 1, 6): 106.25702918976113, (0, 1, 7): 113.72590402122567, (2, 1, 6): 106.3390387838715, (2, 1, 7): 111.69729882714941, (6, 1, 7): 105.65819247884409, (1, 2, 8): 108.59874810898711, (1, 2, 9): 112.19545440609062, (1, 2, 10): 111.67294842834627, (8, 2, 9): 111.33448705926884, (8, 2, 10): 107.47750840394838, (9, 2, 10): 105.46240504563437 } # loop over the angles and make sure they are almost equal to 7 d.p # the dictionaries will always be made in the same order unless the RDKit ordering changes. for angle, value in angles.items(): self.assertAlmostEqual(molecule_smiles.angle_values[angle], value)
def __call__(self, pars, namespace, values, option_string=None): """This function is executed when Torsion maker is called.""" # TODO Should this be here? # load in the ligand molecule mol = Ligand(values) # Prompt the user for the scan order scanner = TorsionScan(mol) scanner.find_scan_order() # Write out the scan file with open('QUBE_torsions.txt', 'w+') as qube: qube.write( '# dihedral definition by atom indices starting from 1\n# i j k l\n' ) for scan in mol.scan_order: scan_di = mol.dihedrals[scan][0] qube.write( f' {scan_di[0]:2} {scan_di[1]:2} {scan_di[2]:2} {scan_di[3]:2}\n' ) printf('QUBE_torsions.txt made.') sys.exit()
def setUp(self): """ Set up the ligand testing class, make temp folder and copy the pdb and mol2 over """ self.home = os.getcwd() self.test_folder = os.path.join(os.path.dirname(__file__), 'files') # Make the temp folder and move there with the required files with tempfile.TemporaryDirectory() as temp: os.chdir(temp) copy(os.path.join(self.test_folder, 'acetone.pdb'), 'acetone.pdb') self.molecule_pdb = Ligand('acetone.pdb') copy(os.path.join(self.test_folder, 'acetone.mol2'), 'acetone.mol2') self.molecule_mol2 = Ligand('acetone.mol2')
def test_xml_sites_roundtrip(tmpdir): """ If we load in an xml with sites make sure we can write it back out. """ with tmpdir.as_cwd(): mol = Ligand.from_file(get_data("pyridine.pdb")) XML(mol, input_file=get_data("pyridine.xml")) mol.write_parameters(name="test") mol2 = Ligand.from_file(get_data("pyridine.pdb")) XML(mol2, input_file="test.xml") assert mol.AtomTypes == mol2.AtomTypes for bond in mol.HarmonicBondForce.keys(): assert ( pytest.approx(mol.HarmonicBondForce[bond], abs=1e-6) == mol2.HarmonicBondForce[bond] ) for angle in mol.HarmonicAngleForce.keys(): assert ( pytest.approx(mol.HarmonicAngleForce[angle], abs=1e-6) == mol2.HarmonicAngleForce[angle] ) for atom in range(mol.n_atoms): assert ( pytest.approx(mol.NonbondedForce[atom], abs=1e-6) == mol2.NonbondedForce[atom] ) # make sure the virtual site was round tripped mol_site = mol.extra_sites[0] mol2_site = mol2.extra_sites[0] assert mol_site.charge == mol2_site.charge assert mol_site.parent_index == mol2_site.parent_index assert mol_site.closest_a_index == mol2_site.closest_a_index assert mol_site.closest_b_index == mol2_site.closest_b_index assert mol_site.p1 == mol2_site.p1 assert mol_site.p2 == mol2_site.p2 assert mol_site.p3 == mol2_site.p3 for dihedral, terms in mol.PeriodicTorsionForce.items(): try: other_dih = mol2.PeriodicTorsionForce[dihedral] except KeyError: other_dih = mol2.PeriodicTorsionForce[tuple(reversed(dihedral))] assert np.allclose(terms[:4], other_dih[:4])
def setUpClass(cls): """ Create temp working directory and copy across test files. Initialise test molecule (acetone) with Ligand(). """ cls.files_folder = os.path.join( os.path.dirname(os.path.abspath(__file__)), 'files') os.mkdir('temp') os.chdir('temp') copy(os.path.join(cls.files_folder, 'acetone.pdb'), 'acetone.pdb') cls.molecule_pdb = Ligand('acetone.pdb') cls.molecule_pdb.testing = True copy(os.path.join(cls.files_folder, 'acetone.mol2'), 'acetone.mol2') cls.molecule_mol2 = Ligand('acetone.mol2') cls.molecule_mol2.testing = True os.chdir('../')
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_add_conformers(file_name): """ Load up the bace pdb and then add conformers to it from other file types. """ mol = Ligand.from_file(file_name=get_data("bace0.pdb")) mol.coordinates = None mol.add_conformer(file_name=get_data(file_name)) assert mol.coordinates.shape == (mol.n_atoms, 3)
def test_to_mapped_smiles(): """ Make sure the the mapped smiles flag is respected. """ mol = Ligand.from_file(file_name=get_data("bace0.sdf")) no_map = mol.to_smiles(isomeric=True, explicit_hydrogens=True, mapped=False) mapped = mol.to_smiles(isomeric=True, explicit_hydrogens=True, mapped=True) assert no_map != mapped
def test_make_unique_names(): """ After loading a molecule with non unique atom names make sure a unique set is automatically generated. """ # load the molecule with missing names mol = Ligand.from_file(get_data("missing_names.pdb")) # make sure they have been converted assert mol.has_unique_atom_names is True
def setUpClass(cls): cls.files_folder = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'files') # Make the temp folder and move there with the required files os.mkdir('temp') os.chdir('temp') copy(os.path.join(cls.files_folder, 'acetone.pdb'), 'acetone.pdb') cls.molecule = Ligand('acetone.pdb')
def test_ligand_from_file(file_name): """ For the given file type make sure rdkit can parse it and return the molecule. """ mol = Ligand.from_file(file_name=get_data(file_name)) assert mol.n_atoms > 1 assert mol.n_bonds > 1 assert mol.name is not None
def setUpClass(cls): """ Write the big string above to a file to be used for testing. Cannot use actual files as pathing causes issues. """ with open('acetone.pdb', 'w+') as pdb_test_file: pdb_test_file.write(acetone) cls.molecule = Ligand('acetone.pdb')
def test_rotatable_bonds(): """ Make sure we can find true rotatable bonds for a molecule. """ mol = Ligand.from_file(file_name=get_data("biphenyl.pdb")) assert mol.rotatable_bonds == [ (3, 4), ] assert mol.n_rotatable_bonds == 1
def load_molecule(self): """Load the molecule into the gui and make an instance of the Ligand class.""" # Open up the file explorer filename = self.load_file(["pdb", "mol2", "mol", "sdf"]) if ".pdb" in filename or ".mol2" in filename or "mol" in filename: # Instance the QUBEKit class self.molecule = Ligand(filename) self.viewer.load_molecule(filename) self.ligand_name.setText(f"{self.molecule.name}")
def load_molecule(self): """Load the molecule into the gui and make an instance of the Ligand class.""" # Open up the file explorer filename = self.load_file(['pdb', 'mol2', 'mol', 'sdf']) if '.pdb' in filename or '.mol2' in filename or 'mol' in filename: # Instance the QUBEKit class self.molecule = Ligand(filename) self.viewer.load_molecule(filename) self.ligand_name.setText(f'{self.molecule.name}')
def test_parametrise_none(tmpdir): """ If no engine is passed make sure we init the parameter holders but store nothing. """ with tmpdir.as_cwd(): mol = Ligand.from_file(get_data("acetone.pdb")) mol.parameter_engine = "none" param_mol = Execute.parametrise(molecule=mol, verbose=False) for i in range(param_mol.n_atoms): assert param_mol.NonbondedForce[i] == [0, 0, 0]
def test_parametrise_missing_file(tmpdir): """ If a missing file is provided make sure an error is raised. """ with tmpdir.as_cwd(): mol = Ligand.from_file(get_data("acetone.pdb")) mol.home = os.getcwd() mol.parameter_engine = "xml" with pytest.raises(FileNotFoundError): _ = Execute.parametrise(molecule=mol, verbose=False)
def test_to_topology(molecule): """ Make sure that a topology generated using qubekit matches an openff one. """ mol = Ligand.from_file(file_name=get_data(molecule)) offmol = OFFMolecule.from_file(file_path=get_data(molecule)) assert ( nx.algorithms.isomorphism.is_isomorphic(mol.to_topology(), offmol.to_networkx()) is True )
def test_to_smiles_isomeric(): """ Make sure we can write out smiles strings with the correct settings. """ # use bace as it has a chiral center mol = Ligand.from_file(file_name=get_data("bace0.sdf")) smiles = mol.to_smiles(isomeric=True, explicit_hydrogens=False, mapped=False) assert "@@" in smiles smiles = mol.to_smiles(isomeric=False, explicit_hydrogens=False, mapped=False) assert "@" not in smiles
def setUpClass(cls): """Create temp working directory and copy across test files.""" cls.files_folder = os.path.join( os.path.dirname(os.path.abspath(__file__)), 'files') os.mkdir('temp') os.chdir('temp') copy(os.path.join(cls.files_folder, 'acetone.pdb'), 'acetone.pdb') cls.molecule = Ligand('acetone.pdb') cls.molecule.testing = True