diff --git a/modules/mol/alg/pymod/ligand_scoring.py b/modules/mol/alg/pymod/ligand_scoring.py index 4c7ebdf546ec6f8dc0a1b7ce029c7308d709ad8d..ce98690ddbf60f5d7511bee9dfb0492e5e5b2232 100644 --- a/modules/mol/alg/pymod/ligand_scoring.py +++ b/modules/mol/alg/pymod/ligand_scoring.py @@ -695,11 +695,6 @@ class LigandScorer: # Disconnected graph is handled elsewhere continue - substructure_match = len(symmetries[0][0]) != len(model_ligand.atoms) - coverage = len(symmetries[0][0]) / len(model_ligand.atoms) - self._assignment_match_coverage[target_id, model_id] = coverage - self._assignment_isomorphisms[target_id, model_id] = 1. - ################################################################ # Compute best rmsd/lddt-pli by naively enumerating symmetries # ################################################################ @@ -713,13 +708,25 @@ class LigandScorer: ########################################### # Extend results by symmetry related info # ########################################### + if (rmsd_result is None) != (lddt_pli_result is None): + # Ligand assignment makes assumptions here, and is likely + # to not work properly if this differs. There is no reason + # it would ever do, so let's just check it + raise Exception("Ligand scoring bug: discrepency between " + "RMSD and lDDT-PLI definition.") if rmsd_result is not None: + # Now we assume both rmsd_result and lddt_pli_result are defined + # Add coverage + substructure_match = len(symmetries[0][0]) != len(model_ligand.atoms) + coverage = len(symmetries[0][0]) / len(model_ligand.atoms) + self._assignment_match_coverage[target_id, model_id] = coverage + self._assignment_isomorphisms[target_id, model_id] = 1. + # Add RMSD rmsd_result["substructure_match"] = substructure_match rmsd_result["coverage"] = coverage if self.unassigned: rmsd_result["unassigned"] = False - - if lddt_pli_result is not None: + # Add lDDT-PLI lddt_pli_result["substructure_match"] = substructure_match lddt_pli_result["coverage"] = coverage if self.unassigned: diff --git a/modules/mol/alg/tests/test_ligand_scoring.py b/modules/mol/alg/tests/test_ligand_scoring.py index 0eb5037878a84b921b61415d975c2dbce557ad8b..6755b0e8ee10c4724c4730a5acbcc0d44661794b 100644 --- a/modules/mol/alg/tests/test_ligand_scoring.py +++ b/modules/mol/alg/tests/test_ligand_scoring.py @@ -731,6 +731,35 @@ class TestLigandScoring(unittest.TestCase): sc.unassigned_target_ligands["F"][1] == "symmetries" + def test_no_binding_site(self): + """ + Test the behavior when there's no binding site in proximity of + the ligand. This test was introduced to identify some subtle issues + with the ligand assignment that can cause it to enter an infinite + loop when the data matrices are not filled properly. + """ + trg = _LoadMMCIF("1r8q.cif.gz").Copy() + mdl = trg.Copy() + + trg_zn = trg.FindResidue("H", 1) + trg_g3d = trg.FindResidue("F", 1) + + + # Move the zinc out of the reference binding site... + ed = trg.EditXCS() + ed.SetAtomPos(trg_zn.FindAtom("ZN"), + trg_zn.FindAtom("ZN").pos + geom.Vec3(6, 0, 0)) + # Remove some atoms from G3D to decrease coverage. This messed up + # the assignment in the past. + ed.DeleteAtom(trg_g3d.FindAtom("O6")) + ed.UpdateICS() + + sc = LigandScorer(mdl, trg, target_ligands=[trg_zn, trg_g3d], + coverage_delta=0, substructure_match=True) + self.assertTrue(np.isnan(sc.rmsd_matrix[0, 3])) + self.assertEqual(sc.unassigned_target_ligands["H"][1], "binding_site") + + def test_no_lddt_pli_contact(self): """ Test behaviour where a binding site has no lDDT-PLI contacts.