def test_rmsd(): # pick one molecule from docked poses mols = list( oddt.toolkit.readfile( 'sdf', os.path.join(test_data_dir, 'data/dude/xiap/actives_docked.sdf'))) mols = list(filter(lambda x: x.title == '312335', mols)) res = { 'method=None': [ 4.7536, 2.5015, 2.7942, 1.1282, 0.7444, 1.6257, 4.7625, 2.7168, 2.5504, 1.9304, 2.6201, 3.1742, 3.2254, 4.7785, 4.8035, 7.8963, 2.2385, 4.8625, 3.2037 ], 'method=hungarian': [ 0.9013, 1.0730, 1.0531, 1.0286, 0.7353, 1.4094, 0.5391, 1.3297, 1.0881, 1.7796, 2.6064, 3.1577, 3.2135, 0.8126, 1.2909, 2.5217, 2.0836, 1.8325, 3.1874 ], 'method=min_symmetry': [ 0.9013, 1.0732, 1.0797, 1.0492, 0.7444, 1.6257, 0.5391, 1.5884, 1.0935, 1.9304, 2.6201, 3.1742, 3.2254, 1.1513, 1.5206, 2.5361, 2.2385, 1.971, 3.2037 ], } kwargs_grid = [{ 'method': None }, { 'method': 'hungarian' }, { 'method': 'min_symmetry' }] for kwargs in kwargs_grid: res_key = '_'.join('%s=%s' % (k, v) for k, v in sorted(kwargs.items())) assert_array_almost_equal( [rmsd(mols[0], mol, **kwargs) for mol in mols[1:]], res[res_key], decimal=4) # test shuffled rmsd for _ in range(5): for kwargs in kwargs_grid: # dont use method=None in shuffled tests if kwargs['method'] is None: continue res_key = '_'.join('%s=%s' % (k, v) for k, v in sorted(kwargs.items())) assert_array_almost_equal([ rmsd(mols[0], shuffle_mol(mol), **kwargs) for mol in mols[1:] ], res[res_key], decimal=4)
def test_rmsd_errors(): mol = oddt.toolkit.readstring('smi', 'c1ccccc1') mol.make3D() mol.addh() mol2 = next(oddt.toolkit.readfile('sdf', os.path.join(test_data_dir, 'data/dude/xiap/actives_docked.sdf'))) for method in [None, 'hungarian', 'min_symmetry']: with pytest.raises(ValueError, match='Unequal number of atoms'): rmsd(mol, mol2, method=method) for _ in range(5): with pytest.raises(ValueError, match='Unequal number of atoms'): rmsd(shuffle_mol(mol), shuffle_mol(mol2), method=method)
def test_rmsd_errors(): mol = oddt.toolkit.readstring('smi', 'c1ccccc1') mol.make3D() mol.addh() mol2 = next( oddt.toolkit.readfile( 'sdf', os.path.join(test_data_dir, 'data/dude/xiap/actives_docked.sdf'))) for method in [None, 'hungarian', 'min_symmetry']: with pytest.raises(ValueError, match='Unequal number of atoms'): rmsd(mol, mol2, method=method) for _ in range(5): with pytest.raises(ValueError, match='Unequal number of atoms'): rmsd(shuffle_mol(mol), shuffle_mol(mol2), method=method)
def test_diverse_conformers(): # FIXME: make toolkit a module so we can import from it diverse_conformers_generator = oddt.toolkit.diverse_conformers_generator mol = oddt.toolkit.readstring( "smi", "CN1CCN(S(=O)(C2=CC=C(OCC)C(C3=NC4=C(N(C)N=C4CCC)C(N3)=O)=C2)=O)CC1") mol.make3D() if oddt.toolkit.backend == 'ob' and oddt.toolkit.__version__ < '2.4.0': assert_raises(NotImplementedError, diverse_conformers_generator, mol) return None # skip test for older OB res = [] for conf in diverse_conformers_generator(mol, seed=123456): res.append(rmsd(mol, conf)) assert_equal(len(res), 10) if oddt.toolkit.backend == 'ob': assert_array_almost_equal(res, [ 0., 3.043712, 3.897143, 3.289482, 3.066374, 2.909683, 2.913927, 3.488244, 3.70603, 3.597467 ]) # else: # if oddt.toolkit.__version__ > '2016.03.9': # assert_array_almost_equal(res, [1.237538, 2.346984, 0.900624, # 3.469511, 1.886213, 2.128909, # 2.852608, 1.312513, 1.291595, # 1.326843]) # else: # assert_array_almost_equal(res, [3.08995, 2.846358, 3.021795, # 1.720319, 2.741972, 2.965332, # 2.925344, 2.930157, 2.934049, # 3.009545]) # check all implemented methods if oddt.toolkit.backend == 'ob': methods = ['ga', 'confab'] else: methods = ['dg', 'etkdg', 'kdg', 'etdg'] for method in methods: assert_equal( len( diverse_conformers_generator(mol, seed=123456, n_conf=5, method=method)), 5) assert_equal( len( diverse_conformers_generator(mol, seed=123456, n_conf=10, method=method)), 10) assert_equal( len( diverse_conformers_generator(mol, seed=123456, n_conf=20, method=method)), 20)
def test_spatial(): """Test spatial misc computations""" mol = oddt.toolkit.readstring('smi', 'c1ccccc1') mol.make3D() mol2 = mol.clone # Test rotation assert_almost_equal(mol2.coords, rotate(mol2.coords, np.pi, np.pi, np.pi)) # Rotate perpendicular to ring mol2.coords = rotate(mol2.coords, 0, 0, np.pi) # RMSD assert_almost_equal(rmsd(mol, mol2, method=None), 2.77, decimal=1) # Hungarian must be close to zero (RDKit is 0.3) assert_almost_equal(rmsd(mol, mol2, method='hungarian'), 0, decimal=0) # Minimized by symetry must close to zero assert_almost_equal(rmsd(mol, mol2, method='min_symmetry'), 0, decimal=0)
def test_diverse_conformers(): # FIXME: make toolkit a module so we can import from it diverse_conformers_generator = oddt.toolkit.diverse_conformers_generator mol = oddt.toolkit.readstring( 'smi', 'CN1CCN(S(=O)(C2=CC=C(OCC)C(C3=NC4=C(N(C)N=C4CCC)C(N3)=O)=C2)=O)CC1') mol.make3D() res = [] for conf in diverse_conformers_generator(mol, seed=123456): res.append(rmsd(mol, conf)) assert len(res) == 10 if oddt.toolkit.backend == 'ob': if oddt.toolkit.__version__ < '0.3': assert_array_almost_equal(res, [ 0., 3.043712, 3.897143, 3.289482, 3.066374, 2.909683, 2.913927, 3.488244, 3.70603, 3.597467 ]) else: assert_array_almost_equal(res, [ 0.0, 1.372770, 2.489789, 2.759941, 2.968366, 3.228773, 3.392191, 3.921166, 3.185065, 3.283915 ]) # else: # if oddt.toolkit.__version__ > '2016.03.9': # assert_array_almost_equal(res, [1.237538, 2.346984, 0.900624, # 3.469511, 1.886213, 2.128909, # 2.852608, 1.312513, 1.291595, # 1.326843]) # else: # assert_array_almost_equal(res, [3.08995, 2.846358, 3.021795, # 1.720319, 2.741972, 2.965332, # 2.925344, 2.930157, 2.934049, # 3.009545]) # check all implemented methods if oddt.toolkit.backend == 'ob': methods = ['ga', 'confab'] else: methods = ['dg', 'etkdg', 'kdg', 'etdg'] for method in methods: assert len( diverse_conformers_generator(mol, seed=123456, n_conf=5, method=method)) == 5 assert len( diverse_conformers_generator(mol, seed=123456, n_conf=10, method=method)) == 10 assert len( diverse_conformers_generator(mol, seed=123456, n_conf=20, method=method)) == 20
def test_rmsd(): # pick one molecule from docked poses mols = list(oddt.toolkit.readfile('sdf', os.path.join(test_data_dir, 'data/dude/xiap/actives_docked.sdf'))) mols = list(filter(lambda x: x.title == '312335', mols)) res = { 'method=None': [4.7536, 2.5015, 2.7942, 1.1282, 0.7444, 1.6257, 4.7625, 2.7168, 2.5504, 1.9304, 2.6201, 3.1742, 3.2254, 4.7785, 4.8035, 7.8963, 2.2385, 4.8625, 3.2037], 'method=hungarian': [0.9013, 1.0730, 1.0531, 1.0286, 0.7353, 1.4094, 0.5391, 1.3297, 1.0881, 1.7796, 2.6064, 3.1577, 3.2135, 0.8126, 1.2909, 2.5217, 2.0836, 1.8325, 3.1874], 'method=min_symmetry': [0.9013, 1.0732, 1.0797, 1.0492, 0.7444, 1.6257, 0.5391, 1.5884, 1.0935, 1.9304, 2.6201, 3.1742, 3.2254, 1.1513, 1.5206, 2.5361, 2.2385, 1.971, 3.2037], } kwargs_grid = [{'method': None}, {'method': 'hungarian'}, {'method': 'min_symmetry'}] for kwargs in kwargs_grid: res_key = '_'.join('%s=%s' % (k, v) for k, v in sorted(kwargs.items())) assert_array_almost_equal([rmsd(mols[0], mol, **kwargs) for mol in mols[1:]], res[res_key], decimal=4) # test shuffled rmsd for _ in range(5): for kwargs in kwargs_grid: # dont use method=None in shuffled tests if kwargs['method'] is None: continue res_key = '_'.join('%s=%s' % (k, v) for k, v in sorted(kwargs.items())) assert_array_almost_equal([rmsd(mols[0], shuffle_mol(mol), **kwargs) for mol in mols[1:]], res[res_key], decimal=4)
def test_diverse_conformers(): # FIXME: make toolkit a module so we can import from it diverse_conformers_generator = oddt.toolkit.diverse_conformers_generator mol = oddt.toolkit.readstring( 'smi', 'CN1CCN(S(=O)(C2=CC=C(OCC)C(C3=NC4=C(N(C)N=C4CCC)C(N3)=O)=C2)=O)CC1' ) mol.make3D() if oddt.toolkit.backend == 'ob' and oddt.toolkit.__version__ < '2.4.0': with pytest.raises(NotImplementedError): diverse_conformers_generator(mol) return None # skip test for older OB res = [] for conf in diverse_conformers_generator(mol, seed=123456): res.append(rmsd(mol, conf)) assert len(res) == 10 if oddt.toolkit.backend == 'ob': assert_array_almost_equal(res, [0., 3.043712, 3.897143, 3.289482, 3.066374, 2.909683, 2.913927, 3.488244, 3.70603, 3.597467]) # else: # if oddt.toolkit.__version__ > '2016.03.9': # assert_array_almost_equal(res, [1.237538, 2.346984, 0.900624, # 3.469511, 1.886213, 2.128909, # 2.852608, 1.312513, 1.291595, # 1.326843]) # else: # assert_array_almost_equal(res, [3.08995, 2.846358, 3.021795, # 1.720319, 2.741972, 2.965332, # 2.925344, 2.930157, 2.934049, # 3.009545]) # check all implemented methods if oddt.toolkit.backend == 'ob': methods = ['ga', 'confab'] else: methods = ['dg', 'etkdg', 'kdg', 'etdg'] for method in methods: assert len(diverse_conformers_generator(mol, seed=123456, n_conf=5, method=method)) == 5 assert len(diverse_conformers_generator(mol, seed=123456, n_conf=10, method=method)) == 10 assert len(diverse_conformers_generator(mol, seed=123456, n_conf=20, method=method)) == 20
def test_spatial(): """Test spatial misc computations""" mol = oddt.toolkit.readstring('smi', 'c1ccccc1') mol.make3D() mol2 = mol.clone # Test rotation assert_almost_equal(mol2.coords, rotate(mol2.coords, np.pi, np.pi, np.pi)) # Rotate perpendicular to ring mol2.coords = rotate(mol2.coords, 0, 0, np.pi) # RMSD assert_almost_equal(rmsd(mol, mol2, method=None), 2.77, decimal=1) # Hungarian must be close to zero (RDKit is 0.3) assert_almost_equal(rmsd(mol, mol2, method='hungarian'), 0, decimal=0) # pick one molecule from docked poses mols = list( oddt.toolkit.readfile( 'sdf', os.path.join(test_data_dir, 'data/dude/xiap/actives_docked.sdf'))) mols = list(filter(lambda x: x.title == '312335', mols)) assert_array_almost_equal([rmsd(mols[0], mol) for mol in mols[1:]], [ 4.753552, 2.501487, 2.7941732, 1.1281863, 0.74440968, 1.6256877, 4.762476, 2.7167852, 2.5504358, 1.9303833, 2.6200771, 3.1741529, 3.225431, 4.7784939, 4.8035369, 7.8962774, 2.2385094, 4.8625236, 3.2036853 ]) assert_array_almost_equal( [rmsd(mols[0], mol, method='hungarian') for mol in mols[1:]], [ 0.90126, 1.073049, 1.053131, 1.028578, 0.735297, 1.409403, 0.539091, 1.329666, 1.088053, 1.779618, 2.606429, 3.157684, 3.213502, 0.812635, 1.290902, 2.521703, 2.083612, 1.832457, 3.187363 ])
def test_vs_docking(): """VS docking (Vina) tests""" vs = virtualscreening(n_cpu=1) vs.load_ligands('sdf', xiap_crystal_ligand) # bad docking engine with pytest.raises(ValueError): vs.dock('srina', 'prot.pdb') vs.dock(engine='autodock_vina', protein=xiap_protein, auto_ligand=xiap_crystal_ligand, exhaustiveness=1, energy_range=6, num_modes=7, size=(20, 20, 20), seed=0) mols = list(vs.fetch()) assert len(mols) == 7 mol_data = mols[0].data assert 'vina_affinity' in mol_data assert 'vina_rmsd_lb' in mol_data assert 'vina_rmsd_ub' in mol_data if oddt.toolkit.backend == 'ob': vina_scores = [-5.3, -4.0, -3.8, -3.7, -3.4, -3.4, -3.0] else: vina_scores = [-6.3, -6.0, -5.8, -5.8, -3.9, -3.0, -1.1] assert_array_equal([float(m.data['vina_affinity']) for m in mols], vina_scores) # verify the SMILES of molecules ref_mol = next(oddt.toolkit.readfile('sdf', xiap_crystal_ligand)) if oddt.toolkit.backend == 'ob': # OB 2.3.2 will fail the following, since Hs are removed, etc. # OB 2.4 recognizes the smiles chirality wrong pass else: vina_rmsd = [ 8.153314, 5.32554, 8.514586, 8.510169, 9.060128, 8.995098, 8.626776 ] assert_array_equal([mol.smiles for mol in mols], [ref_mol.smiles] * len(mols)) assert_array_almost_equal( [rmsd(ref_mol, mol, method='min_symmetry') for mol in mols], vina_rmsd)
def test_vs_docking(): """VS docking (Vina) tests""" vs = virtualscreening(n_cpu=1) vs.load_ligands('sdf', xiap_crystal_ligand) # bad docking engine with pytest.raises(ValueError): vs.dock('srina', 'prot.pdb') vs.dock(engine='autodock_vina', protein=xiap_protein, auto_ligand=xiap_crystal_ligand, exhaustiveness=1, energy_range=6, num_modes=7, size=(20, 20, 20), seed=0) mols = list(vs.fetch()) assert len(mols) == 7 mol_data = mols[0].data assert 'vina_affinity' in mol_data assert 'vina_rmsd_lb' in mol_data assert 'vina_rmsd_ub' in mol_data if oddt.toolkit.backend == 'ob': vina_scores = [-5.3, -4.0, -3.8, -3.7, -3.4, -3.4, -3.0] else: vina_scores = [-6.3, -6.0, -5.8, -5.8, -3.9, -3.0, -1.1] assert_array_equal([float(m.data['vina_affinity']) for m in mols], vina_scores) # verify the SMILES of molecules ref_mol = next(oddt.toolkit.readfile('sdf', xiap_crystal_ligand)) if oddt.toolkit.backend == 'ob': # OB 2.3.2 will fail the following, since Hs are removed, etc. # OB 2.4 recognizes the smiles chirality wrong pass else: vina_rmsd = [8.153314, 5.32554, 8.514586, 8.510169, 9.060128, 8.995098, 8.626776] assert_array_equal([mol.smiles for mol in mols], [ref_mol.smiles] * len(mols)) assert_array_almost_equal([rmsd(ref_mol, mol, method='min_symmetry') for mol in mols], vina_rmsd)
def test_spatial(): """Test spatial computations""" # Angles assert_array_almost_equal(angle(np.array((1, 0, 0)), np.array((0, 0, 0)), np.array((0, 1, 0))), 90) assert_array_almost_equal(angle(np.array((1, 0, 0)), np.array((0, 0, 0)), np.array((1, 1, 0))), 45) mol = oddt.toolkit.readstring('smi', 'c1ccccc1') mol.make3D() # Check benzene ring angle assert_array_almost_equal(angle(mol.coords[0], mol.coords[1], mol.coords[2]), 120, decimal=1) # Dihedrals assert_array_almost_equal(dihedral(np.array((1, 0, 0)), np.array((0, 0, 0)), np.array((0, 1, 0)), np.array((1, 1, 0))), 0) assert_array_almost_equal(dihedral(np.array((1, 0, 0)), np.array((0, 0, 0)), np.array((0, 1, 0)), np.array((1, 1, 1))), -45) # Check benzene ring dihedral assert_array_almost_equal(dihedral(mol.coords[0], mol.coords[1], mol.coords[2], mol.coords[3]), 0, decimal=1) mol = oddt.toolkit.readstring('smi', 'c1ccccc1') mol.make3D() mol2 = mol.clone # Test rotation assert_almost_equal(mol2.coords, rotate(mol2.coords, np.pi, np.pi, np.pi)) # Rotate perpendicular to ring mol2.coords = rotate(mol2.coords, 0, 0, np.pi) # RMSD assert_almost_equal(rmsd(mol, mol2, method=None), 2.77, decimal=1) # Hungarian must be close to zero (RDKit is 0.3) assert_almost_equal(rmsd(mol, mol2, method='hungarian'), 0, decimal=0) # pick one molecule from docked poses mols = list(oddt.toolkit.readfile('sdf', os.path.join(test_data_dir, 'data/dude/xiap/actives_docked.sdf'))) mols = list(filter(lambda x: x.title == '312335', mols)) assert_array_almost_equal([rmsd(mols[0], mol) for mol in mols[1:]], [4.753552, 2.501487, 2.7941732, 1.1281863, 0.74440968, 1.6256877, 4.762476, 2.7167852, 2.5504358, 1.9303833, 2.6200771, 3.1741529, 3.225431, 4.7784939, 4.8035369, 7.8962774, 2.2385094, 4.8625236, 3.2036853]) assert_array_almost_equal([rmsd(mols[0], mol, method='hungarian') for mol in mols[1:]], [2.5984519, 1.7295024, 1.1268076, 1.0285776, 0.73529714, 1.4094033, 2.5195069, 1.7449125, 1.5116163, 1.7796179, 2.6064286, 3.1576841, 3.2135022, 3.1675091, 2.7001681, 5.1263351, 2.0836117, 3.542397, 3.1873631])
def dock(self, ligands, protein=None): """Automated docking procedure. Parameters ---------- ligands: iterable of oddt.toolkit.Molecule objects Ligands to dock protein: oddt.toolkit.Molecule object or None Protein object to be used. If None, then the default one is used, else the protein is new default. Returns ------- ligands : array of oddt.toolkit.Molecule objects Array of ligands (scores are stored in mol.data method) """ if protein: self.set_protein(protein) if not self.protein_file: raise IOError("No receptor.") if is_molecule(ligands): ligands = [ligands] ligand_dir = mkdtemp(dir=self.tmp_dir, prefix='ligands_') output_array = [] for n, ligand in enumerate(ligands): check_molecule(ligand, force_coords=True) ligand_file = write_vina_pdbqt(ligand, ligand_dir, name_id=n) ligand_outfile = ligand_file[:-6] + '_out.pdbqt' try: scores = parse_vina_docking_output( subprocess.check_output([ self.executable, '--receptor', self.protein_file, '--ligand', ligand_file, '--out', ligand_outfile ] + self.params + ['--cpu', str(self.n_cpu)], stderr=subprocess.STDOUT)) except subprocess.CalledProcessError as e: sys.stderr.write(e.output.decode('ascii')) if self.skip_bad_mols: continue # TODO: print some warning message else: raise Exception('Autodock Vina failed. Command: "%s"' % ' '.join(e.cmd)) # docked conformations may have wrong connectivity - use source ligand if is_openbabel_molecule(ligand): if oddt.toolkits.ob.__version__ >= '2.4.0': # find the order of PDBQT atoms assigned by OpenBabel with open(ligand_file) as f: write_order = [ int(line[7:12].strip()) for line in f if line[:4] == 'ATOM' ] new_order = sorted(range(len(write_order)), key=write_order.__getitem__) new_order = [i + 1 for i in new_order] # OBMol has 1 based idx assert len(new_order) == len(ligand.atoms) else: # Openbabel 2.3.2 does not support perserving atom order. # We read back the PDBQT ligand to get "correct" bonding. ligand = next(oddt.toolkit.readfile('pdbqt', ligand_file)) if 'REMARK' in ligand.data: del ligand.data['REMARK'] docked_ligands = oddt.toolkit.readfile('pdbqt', ligand_outfile) for docked_ligand, score in zip(docked_ligands, scores): # Renumber atoms to match the input ligand if (is_openbabel_molecule(docked_ligand) and oddt.toolkits.ob.__version__ >= '2.4.0'): docked_ligand.OBMol.RenumberAtoms(new_order) # HACK: copy docked coordinates onto source ligand # We assume that the order of atoms match between ligands clone = ligand.clone clone.clone_coords(docked_ligand) clone.data.update(score) # Calculate RMSD to the input pose try: clone.data['vina_rmsd_input'] = rmsd(ligand, clone) clone.data['vina_rmsd_input_min'] = rmsd( ligand, clone, method='min_symmetry') except Exception: pass output_array.append(clone) rmtree(ligand_dir) return output_array
def dock(self, ligands, protein=None): """Automated docking procedure. Parameters ---------- ligands: iterable of oddt.toolkit.Molecule objects Ligands to dock protein: oddt.toolkit.Molecule object or None Protein object to be used. If None, then the default one is used, else the protein is new default. Returns ------- ligands : array of oddt.toolkit.Molecule objects Array of ligands (scores are stored in mol.data method) """ if protein: self.set_protein(protein) if not self.protein_file: raise IOError("No receptor.") if is_molecule(ligands): ligands = [ligands] ligand_dir = mkdtemp(dir=self.tmp_dir, prefix='ligands_') output_array = [] for n, ligand in enumerate(ligands): check_molecule(ligand, force_coords=True) ligand_file = write_vina_pdbqt(ligand, ligand_dir, name_id=n) ligand_outfile = ligand_file[:-6] + '_out.pdbqt' try: scores = parse_vina_docking_output( subprocess.check_output([self.executable, '--receptor', self.protein_file, '--ligand', ligand_file, '--out', ligand_outfile] + self.params + ['--cpu', str(self.n_cpu)], stderr=subprocess.STDOUT)) except subprocess.CalledProcessError as e: sys.stderr.write(e.output.decode('ascii')) if self.skip_bad_mols: continue # TODO: print some warning message else: raise Exception('Autodock Vina failed. Command: "%s"' % ' '.join(e.cmd)) # docked conformations may have wrong connectivity - use source ligand if is_openbabel_molecule(ligand): if oddt.toolkits.ob.__version__ >= '2.4.0': # find the order of PDBQT atoms assigned by OpenBabel with open(ligand_file) as f: write_order = [int(line[7:12].strip()) for line in f if line[:4] == 'ATOM'] new_order = sorted(range(len(write_order)), key=write_order.__getitem__) new_order = [i + 1 for i in new_order] # OBMol has 1 based idx assert len(new_order) == len(ligand.atoms) else: # Openbabel 2.3.2 does not support perserving atom order. # We read back the PDBQT ligand to get "correct" bonding. ligand = next(oddt.toolkit.readfile('pdbqt', ligand_file)) if 'REMARK' in ligand.data: del ligand.data['REMARK'] docked_ligands = oddt.toolkit.readfile('pdbqt', ligand_outfile) for docked_ligand, score in zip(docked_ligands, scores): # Renumber atoms to match the input ligand if (is_openbabel_molecule(docked_ligand) and oddt.toolkits.ob.__version__ >= '2.4.0'): docked_ligand.OBMol.RenumberAtoms(new_order) # HACK: copy docked coordinates onto source ligand # We assume that the order of atoms match between ligands clone = ligand.clone clone.clone_coords(docked_ligand) clone.data.update(score) # Calculate RMSD to the input pose clone.data['vina_rmsd_input'] = rmsd(ligand, clone) clone.data['vina_rmsd_input_min'] = rmsd(ligand, clone, method='min_symmetry') output_array.append(clone) rmtree(ligand_dir) return output_array