diff --git a/actions/ost-compare-structures b/actions/ost-compare-structures index 6756cfe538a4f849dc424893d24e2213e85da7a6..2d02695cb083906eee1a1eab816b436c44ad1d6f 100644 --- a/actions/ost-compare-structures +++ b/actions/ost-compare-structures @@ -251,6 +251,19 @@ def _ParseArgs(): action="store_true", help=("Make alignment based on residue number instead of using\n" "a global BLOSUM62-based alignment.")) + parser.add_argument( + "--qs-max-mappings-extensive", + dest="qs_max_mappings_extensive", + type=int, + default=1000000, + help=("Maximal number of chain mappings to test for 'extensive'\n" + "chain mapping scheme which is used as a last resort if\n" + "other schemes failed. The extensive chain mapping search\n" + "must in the worst case check O(N!) possible mappings for\n" + "complexes with N chains. Two octamers without symmetry\n" + "would require 322560 mappings to be checked. To limit\n" + "computations, no scores are computed if we try more than\n" + "the maximal number of chain mappings.")) # # lDDT options # @@ -683,13 +696,19 @@ def _Main(): qs_scorer = qsscoring.QSscorer(reference, model, opts.residue_number_alignment) + qs_scorer.max_mappings_extensive = opts.qs_max_mappings_extensive if opts.chain_mapping is not None: ost.LogInfo( "Using custom chain mapping: %s" % str( opts.chain_mapping)) qs_scorer.chain_mapping = opts.chain_mapping else: - qs_scorer.chain_mapping # just to initialize it + try: + qs_scorer.chain_mapping # just to initialize it + except qsscoring.QSscoreError as ex: + ost.LogError('Chain mapping failed:', str(ex)) + ost.LogError('Skipping comparison') + continue ost.LogInfo("-" * 80) ost.LogInfo("Checking consistency between %s and %s" % ( model_name, reference_name)) diff --git a/modules/doc/actions.rst b/modules/doc/actions.rst index 8c31b7d4108e670f8c6091059438f82e4a8bb5e9..20858ec9b306e5ef9bbd3025b80ad4c23cbe46d4 100644 --- a/modules/doc/actions.rst +++ b/modules/doc/actions.rst @@ -24,7 +24,9 @@ lDDT scores between two complexes from the command line with: [-rs REFERENCE_SELECTION] [-ms MODEL_SELECTION] [-ca] [-ft] [-cl COMPOUND_LIBRARY] [-qs] [-c CHAIN_MAPPING [CHAIN_MAPPING ...]] - [--qs-rmsd] [-rna] [-l] [-ir INCLUSION_RADIUS] + [--qs-rmsd] [-rna] + [--qs-max-mappings-extensive QS_MAX_MAPPINGS_EXTENSIVE] + [-l] [-ir INCLUSION_RADIUS] [-ss SEQUENCE_SEPARATION] [-spr] [-ml] [-rm REMOVE [REMOVE ...]] [-ce] [-mn] [-sc] [-p PARAMETER_FILE] [-bt BOND_TOLERANCE] diff --git a/modules/mol/alg/pymod/qsscoring.py b/modules/mol/alg/pymod/qsscoring.py index b4fbdba09a92e88b166e6ff86edb3a120d26cdef..7c7cb3cfe1d8073152c5850ad807c9fa0376a28c 100644 --- a/modules/mol/alg/pymod/qsscoring.py +++ b/modules/mol/alg/pymod/qsscoring.py @@ -142,6 +142,19 @@ class QSscorer: :type: :class:`int` + .. attribute:: max_mappings_extensive + + Maximal number of chain mappings to test for 'extensive' + :attr:`chain_mapping_scheme`. The extensive chain mapping search must in the + worst case check O(N^2) * O(N!) possible mappings for complexes with N + chains. Two octamers without symmetry would require 322560 mappings to be + checked. To limit computations, a :class:`QSscoreError` is thrown if we try + more than the maximal number of chain mappings. + The value must be set before the first use of :attr:`chain_mapping`. + By default it is set to 100000. + + :type: :class:`int` + .. attribute:: res_num_alignment Forces each alignment in :attr:`alignments` to be based on residue numbers @@ -173,6 +186,7 @@ class QSscorer: self.res_num_alignment = res_num_alignment self.calpha_only = self.qs_ent_1.calpha_only or self.qs_ent_2.calpha_only self.max_ca_per_chain_for_cm = 100 + self.max_mappings_extensive = 100000 # init cached stuff self._chem_mapping = None self._ent_to_cm_1 = None @@ -360,12 +374,13 @@ class QSscorer: :type: :class:`dict` with key / value = :class:`str` (chain names, key for :attr:`ent_to_cm_1`, value for :attr:`ent_to_cm_2`) :raises: :class:`QSscoreError` if there are too many combinations to check - to find a chain mapping. + to find a chain mapping (see :attr:`max_mappings_extensive`). """ if self._chain_mapping is None: self._chain_mapping, self._chain_mapping_scheme = \ _GetChainMapping(self.ent_to_cm_1, self.ent_to_cm_2, self.symm_1, - self.symm_2, self.chem_mapping) + self.symm_2, self.chem_mapping, + self.max_mappings_extensive) LogInfo('Mapping found: %s' % str(self._chain_mapping)) return self._chain_mapping @@ -1810,7 +1825,8 @@ def _FindSymmetry(qs_ent_1, qs_ent_2, ent_to_cm_1, ent_to_cm_2, chem_mapping): return [], [] -def _GetChainMapping(ent_1, ent_2, symm_1, symm_2, chem_mapping): +def _GetChainMapping(ent_1, ent_2, symm_1, symm_2, chem_mapping, + max_mappings_extensive): """ :return: Tuple with mapping from *ent_1* to *ent_2* (see :attr:`QSscorer.chain_mapping`) and scheme used (see @@ -1821,6 +1837,7 @@ def _GetChainMapping(ent_1, ent_2, symm_1, symm_2, chem_mapping): :param symm_1: See :attr:`QSscorer.symm_1` :param symm_2: See :attr:`QSscorer.symm_2` :param chem_mapping: See :attr:`QSscorer.chem_mapping` + :param max_mappings_extensive: See :attr:`QSscorer.max_mappings_extensive` """ LogInfo('Symmetry-groups used in %s: %s' % (ent_1.GetName(), str(symm_1))) LogInfo('Symmetry-groups used in %s: %s' % (ent_2.GetName(), str(symm_2))) @@ -1860,7 +1877,7 @@ def _GetChainMapping(ent_1, ent_2, symm_1, symm_2, chem_mapping): LogInfo('Inter Symmetry-group mappings to check: %s' \ % count['inter']['mappings']) nr_mapp = count['intra']['mappings'] + count['inter']['mappings'] - if nr_mapp > 100000: # 322560 is octamer vs octamer + if nr_mapp > max_mappings_extensive: raise QSscoreError('Too many possible mappings: %s' % nr_mapp) # to speed up the computations we cache chain views and RMSDs