diff --git a/modules/mol/alg/pymod/qsscore.py b/modules/mol/alg/pymod/qsscore.py index 3b32746d6efa95c220034dbdc0011efdd6e87b59..2c98d9fa71cf936854cf6838ad779f634372ea5c 100644 --- a/modules/mol/alg/pymod/qsscore.py +++ b/modules/mol/alg/pymod/qsscore.py @@ -213,11 +213,12 @@ class QSScorerResult: from `Xu et al. 2009 <https://dx.doi.org/10.1016%2Fj.jmb.2008.06.002>`_. """ def __init__(self, weighted_scores, weight_sum, weight_extra_mapped, - weight_extra_all): + weight_extra_all, complete_mapping): self._weighted_scores = weighted_scores self._weight_sum = weight_sum self._weight_extra_mapped = weight_extra_mapped self._weight_extra_all = weight_extra_all + self._complete_mapping = complete_mapping @property def weighted_scores(self): @@ -251,11 +252,30 @@ class QSScorerResult: """ return self._weight_extra_all + @property + def complete_mapping(self): + """ Whether the underlying mapping of the scored assemblies is complete + + In other words: If they have the same stoichiometry. This is relevant + for :attr:`~QS_best` and :attr:`~QS_global` in case of no contacts in + any of the scored entities. + + :type: :class:`bool` + """ + return self._complete_mapping + @property def QS_best(self): """ QS_best - the actual score as described in formula section above - Returns None if there are no contacts in the compared structures + If there are no contacts observed in any of the scored entities this + score is 1.0 if we're comparing structures with + :attr:`~complete_mapping`, 0.0 otherwise. In the example of two + monomers, no contacts can be observed but they exactly match in terms + of quaternary structure. Thus a perfect score. In terms of higher order + structure that becomes a bit more abstract but in principle they still + have the exact same quaternary structure if they match in stoichiometry + but have no single contact. :type: :class:`float` """ @@ -263,14 +283,23 @@ class QSScorerResult: denominator = self.weight_sum + self.weight_extra_mapped if denominator != 0.0: return nominator/denominator + elif self.complete_mapping: + return 1.0 else: - return None + return 0.0 @property def QS_global(self): """ QS_global - the actual score as described in formula section above - Returns None if there are no contacts in the compared structures + If there are no contacts observed in any of the scored entities this + score is 1.0 if we're comparing structures with + :attr:`~complete_mapping`, 0.0 otherwise. In the example of two + monomers, no contacts can be observed but they exactly match in terms + of quaternary structure. Thus a perfect score. In terms of higher order + structure that becomes a bit more abstract but in principle they still + have the exact same quaternary structure if they match in stoichiometry + but have no single contact. :type: :class:`float` """ @@ -278,8 +307,10 @@ class QSScorerResult: denominator = self.weight_sum + self.weight_extra_all if denominator != 0.0: return nominator/denominator + elif self.complete_mapping: + return 1.0 else: - return None + return 0.0 class QSScorer: @@ -457,6 +488,11 @@ class QSScorer: This only works for interfaces that are computed in :func:`Score`, i.e. interfaces for which the alignments are set up correctly. + As all specified chains must be present, the mapping is considered + complete which affects + :attr:`QSScorerResult.QS_global`/:attr:`QSScorerResult.QS_best` in + edge cases of no observed contacts. + :param trg_ch1: Name of first interface chain in target :type trg_ch1: :class:`str` :param trg_ch2: Name of second interface chain in target @@ -484,7 +520,10 @@ class QSScorer: trg_int = (trg_ch1, trg_ch2) mdl_int = (mdl_ch1, mdl_ch2) a, b, c, d = self._MappedInterfaceScores(trg_int, mdl_int) - return QSScorerResult(a, b, c, d) + + # complete_mapping is True by definition, as the requested chain pairs + # are both present + return QSScorerResult(a, b, c, d, True) def FromFlatMapping(self, flat_mapping): """ Same as :func:`Score` but with flat mapping @@ -530,8 +569,16 @@ class QSScorer: else: weight_extra_all += self._InterfacePenalty2(int2) + trg_chains = sorted(self.qsent1.chain_names) # should be sorted already + mdl_chains = sorted(self.qsent2.chain_names) # should be sorted already + mapped_trg_chains = sorted(flat_mapping.keys()) + mapped_mdl_chains = sorted(flat_mapping.values()) + trg_complete = trg_chains == mapped_trg_chains + mdl_complete = mdl_chains == mapped_mdl_chains + complete_mapping = trg_complete and mdl_complete + return QSScorerResult(weighted_scores, weight_sum, weight_extra_mapped, - weight_extra_all) + weight_extra_all, complete_mapping) def _MappedInterfaceScores(self, int1, int2): key_one = (int1, int2) @@ -704,4 +751,4 @@ class QSScorer: return penalty # specify public interface -__all__ = ('QSEntity', 'QSScorer') +__all__ = ('QSEntity', 'QSScorer', 'QSScorerResult')