diff --git a/actions/ost-compare-structures b/actions/ost-compare-structures index 81d105baca7f20a122262f6a9eff9bb81d667510..139631e21f4afe18ea757ea66a6ebed8cbdf93bb 100644 --- a/actions/ost-compare-structures +++ b/actions/ost-compare-structures @@ -265,6 +265,15 @@ def _ParseArgs(): "nucleotides. Per-residue scores are accessible as described for " "local_lddt.")) + parser.add_argument( + "--ilddt", + dest="ilddt", + default=False, + action="store_true", + help=("Compute global lDDT score which is solely based on inter-chain " + "contacts and store as key \"ilddt\". Same stereochemical " + "irregularities as for lddt apply.")) + parser.add_argument( "--cad-score", dest="cad_score", @@ -792,6 +801,9 @@ def _Process(model, reference, args, model_format, reference_format): if args.bb_local_lddt: out["bb_local_lddt"] = _LocalScoresToJSONDict(scorer.bb_local_lddt) + if args.ilddt: + out["ilddt"] = _RoundOrNone(scorer.ilddt) + if args.cad_score: out["cad_score"] = scorer.cad_score diff --git a/modules/mol/alg/pymod/scoring.py b/modules/mol/alg/pymod/scoring.py index c18c5593b6710a5f7cf4a696a64d290a8cc387d5..f1148dcc1bff05ede3497372b90f107cc42bdb9a 100644 --- a/modules/mol/alg/pymod/scoring.py +++ b/modules/mol/alg/pymod/scoring.py @@ -300,6 +300,7 @@ class Scorer: self._local_lddt = None self._bb_lddt = None self._bb_local_lddt = None + self._ilddt = None self._qs_global = None self._qs_best = None @@ -711,6 +712,24 @@ class Scorer: self._compute_bb_lddt() return self._bb_local_lddt + @property + def ilddt(self): + """ Global interface lDDT score in range [0.0, 1.0] + + This is lDDT only based on inter-chain contacts. Value is None if no + such contacts are present. For example if we're dealing with a monomer. + Computed based on :attr:`~stereochecked_model` and :attr:`~mapping` for + chain mapping. + + :type: :class:`float` + """ + if self._ilddt is None: + # the whole None business kind of invalidates the idea of lazy + # evaluation. The assumption is that this is called only once... + self._compute_ilddt() + return self._ilddt + + @property def qs_global(self): """ Global QS-score @@ -1726,6 +1745,45 @@ class Scorer: self._bb_lddt = lddt_score self._bb_local_lddt = local_lddt + def _compute_ilddt(self): + LogScript("Computing all-atom ilDDT") + # lDDT requires a flat mapping with mdl_ch as key and trg_ch as value + flat_mapping = self.mapping.GetFlatMapping(mdl_as_key=True) + + if self.lddt_no_stereochecks: + alns = dict() + for aln in self.aln: + mdl_seq = aln.GetSequence(1) + alns[mdl_seq.name] = aln + lddt_chain_mapping = dict() + for mdl_ch, trg_ch in flat_mapping.items(): + if mdl_ch in alns: + lddt_chain_mapping[mdl_ch] = trg_ch + self._ilddt = self.lddt_scorer.lDDT(self.model, + chain_mapping = lddt_chain_mapping, + residue_mapping = alns, + check_resnames=False, + local_lddt_prop="lddt", + add_mdl_contacts = self.lddt_add_mdl_contacts, + no_intrachain=True)[0] + else: + alns = dict() + for aln in self.stereochecked_aln: + mdl_seq = aln.GetSequence(1) + alns[mdl_seq.name] = aln + lddt_chain_mapping = dict() + for mdl_ch, trg_ch in flat_mapping.items(): + if mdl_ch in alns: + lddt_chain_mapping[mdl_ch] = trg_ch + self._ilddt = self.lddt_scorer.lDDT(self.stereochecked_model, + chain_mapping = lddt_chain_mapping, + residue_mapping = alns, + check_resnames=False, + local_lddt_prop="lddt", + add_mdl_contacts = self.lddt_add_mdl_contacts, + no_intrachain=True)[0] + + def _compute_qs(self): LogScript("Computing global QS-score") qs_score_result = self.qs_scorer.Score(self.mapping.mapping)