OpenStructure
scoring.py
Go to the documentation of this file.
1 import os
2 from ost import mol
3 from ost import seq
4 from ost import io
5 from ost import conop
6 from ost import settings
7 from ost import geom
8 from ost import LogScript, LogInfo, LogDebug
9 from ost.mol.alg import lddt
10 from ost.mol.alg import qsscore
11 from ost.mol.alg import chain_mapping
12 from ost.mol.alg import stereochemistry
13 from ost.mol.alg import dockq
14 from ost.mol.alg.lddt import lDDTScorer
15 from ost.mol.alg.qsscore import QSScorer
16 from ost.mol.alg.contact_score import ContactScorer
17 from ost.mol.alg.contact_score import ContactEntity
18 from ost.mol.alg import GDT
19 from ost.mol.alg import Molck, MolckSettings
20 from ost import bindings
21 from ost.bindings import cadscore
22 from ost.bindings import tmtools
23 import numpy as np
24 
25 def _GetAlignedResidues(aln, s1_ent, s2_ent):
26  """ Yields aligned residues
27 
28  :param aln: The alignment with 2 sequences defining a residue-by-residue
29  relationship.
30  :type aln: :class:`ost.seq.AlignmentHandle`
31  :param s1_ent: Structure representing first sequence in *aln*.
32  One chain must be named after the first sequence and the
33  number of residues must match the number of non-gap
34  characters.
35  :type s1_ent: :class:`ost.mol.EntityHandle`/:class:`ost.mol.EntityView`
36  :param s2_ent: Same for second sequence in *aln*.
37  :type s2_ent: :class:`ost.mol.EntityHandle`/:class:`ost.mol.EntityView`
38  """
39  s1_ch = s1_ent.FindChain(aln.GetSequence(0).GetName())
40  s2_ch = s2_ent.FindChain(aln.GetSequence(1).GetName())
41 
42  if not s1_ch.IsValid():
43  raise RuntimeError("s1_ent lacks required chain in _GetAlignedResidues")
44 
45  if not s2_ch.IsValid():
46  raise RuntimeError("s2_ent lacks required chain in _GetAlignedResidues")
47 
48  if len(aln.GetSequence(0).GetGaplessString()) != s1_ch.GetResidueCount():
49  raise RuntimeError("aln/chain mismatch in _GetAlignedResidues")
50  if len(aln.GetSequence(1).GetGaplessString()) != s2_ch.GetResidueCount():
51  raise RuntimeError("aln/chain mismatch in _GetAlignedResidues")
52 
53  s1_res = s1_ch.residues
54  s2_res = s2_ch.residues
55 
56  s1_res_idx = 0
57  s2_res_idx = 0
58 
59  for col in aln:
60  if col[0] != '-' and col[1] != '-':
61  yield (s1_res[s1_res_idx], s2_res[s2_res_idx])
62  if col[0] != '-':
63  s1_res_idx += 1
64  if col[1] != '-':
65  s2_res_idx += 1
66 
68  """Scorer specific for a reference/model pair
69 
70  Finds best possible binding site representation of reference in model given
71  LDDT score. Uses :class:`ost.mol.alg.chain_mapping.ChainMapper` to deal with
72  chain mapping.
73 
74  :param reference: Reference structure
75  :type reference: :class:`ost.mol.EntityView`/:class:`ost.mol.EntityHandle`
76  :param model: Model structure
77  :type model: :class:`ost.mol.EntityView`/:class:`ost.mol.EntityHandle`
78  :param residue_number_alignment: Passed to ChainMapper constructor
79  :type residue_number_alignment: :class:`bool`
80  """
81  def __init__(self, reference, model,
82  residue_number_alignment=False):
83  self.chain_mapperchain_mapper = chain_mapping.ChainMapper(reference,
84  resnum_alignments=residue_number_alignment)
85  self.refref = self.chain_mapperchain_mapper.target
86  self.mdlmdl = model
87 
88  def ScoreBS(self, ligand, radius = 4.0, lddt_radius=10.0):
89  """Computes binding site LDDT score given *ligand*. Best possible
90  binding site representation is selected by LDDT but other scores such as
91  CA based RMSD and GDT are computed too and returned.
92 
93  :param ligand: Defines the scored binding site, i.e. provides positions
94  to perform proximity search
95  :type ligand: r'((Residue)|(Chain)|(Entity))((View)|(Handle))'
96  :param radius: Reference residues with any atom position within *radius*
97  of *ligand* consitute the scored binding site
98  :type radius: :class:`float`
99  :param lddt_radius: Passed as *inclusion_radius* to
100  :class:`ost.mol.alg.lddt.lDDTScorer`
101  :type lddt_radius: :class:`float`
102  :returns: Object of type :class:`ost.mol.alg.chain_mapping.ReprResult`
103  containing all atom LDDT score and mapping information.
104  None if no representation could be found.
105  """
106 
107  # create view of reference binding site
108  ref_residues_hashes = set() # helper to keep track of added residues
109  for ligand_at in ligand.atoms:
110  close_atoms = self.refref.FindWithin(ligand_at.GetPos(), radius)
111  for close_at in close_atoms:
112  ref_res = close_at.GetResidue()
113  h = ref_res.handle.GetHashCode()
114  if h not in ref_residues_hashes:
115  ref_residues_hashes.add(h)
116 
117  # reason for doing that separately is to guarantee same ordering of
118  # residues as in underlying entity. (Reorder by ResNum seems only
119  # available on ChainHandles)
120  ref_bs = self.refref.CreateEmptyView()
121  for ch in self.refref.chains:
122  for r in ch.residues:
123  if r.handle.GetHashCode() in ref_residues_hashes:
124  ref_bs.AddResidue(r, mol.ViewAddFlag.INCLUDE_ALL)
125 
126  # gogogo
127  bs_repr = self.chain_mapperchain_mapper.GetRepr(ref_bs, self.mdlmdl,
128  inclusion_radius = lddt_radius)
129  if len(bs_repr) >= 1:
130  return bs_repr[0]
131  else:
132  return None
133 
134 
135 class Scorer:
136  """ Helper class to access the various scores available from ost.mol.alg
137 
138  Deals with structure cleanup, chain mapping, interface identification etc.
139  Intermediate results are available as attributes.
140 
141  :param model: Model structure - a deep copy is available as :attr:`~model`.
142  Additionally, :func:`ost.mol.alg.Molck` using *molck_settings*
143  is applied.
144  :type model: :class:`ost.mol.EntityHandle`/:class:`ost.mol.EntityView`
145  :param target: Target structure - a deep copy is available as :attr:`~target`.
146  Additionally, :func:`ost.mol.alg.Molck` using *molck_settings*
147  is applied.
148  :type target: :class:`ost.mol.EntityHandle`/:class:`ost.mol.EntityView`
149  :param resnum_alignments: Whether alignments between chemically equivalent
150  chains in *model* and *target* can be computed
151  based on residue numbers. This can be assumed in
152  benchmarking setups such as CAMEO/CASP.
153  :type resnum_alignments: :class:`bool`
154  :param molck_settings: Settings used for Molck on *model* and *target*, if
155  set to None, a default object is constructed by
156  setting everything except rm_zero_occ_atoms and
157  colored to True in
158  :class:`ost.mol.alg.MolckSettings` constructor.
159  :type molck_settings: :class:`ost.mol.alg.MolckSettings`
160  :param cad_score_exec: Explicit path to voronota-cadscore executable from
161  voronota installation from
162  https://github.com/kliment-olechnovic/voronota. If
163  not given, voronota-cadscore must be in PATH if any
164  of the CAD score related attributes is requested.
165  :type cad_score_exec: :class:`str`
166  :param custom_mapping: Provide custom chain mapping between *model* and
167  *target*. Dictionary with target chain names as key
168  and model chain names as value.
169  :attr:`~mapping` is constructed from this.
170  :type custom_mapping: :class:`dict`
171  :param custom_rigid_mapping: Provide custom chain mapping between *model*
172  and *target*. Dictionary with target chain
173  names as key and model chain names as value.
174  :attr:`~rigid_mapping` is constructed from
175  this.
176  :type custom_rigid_mapping: :class:`dict`
177  :param usalign_exec: Explicit path to USalign executable used to compute
178  TM-score. If not given, TM-score will be computed
179  with OpenStructure internal copy of USalign code.
180  :type usalign_exec: :class:`str`
181  :param lddt_no_stereochecks: Whether to compute LDDT without stereochemistry
182  checks
183  :type lddt_no_stereochecks: :class:`bool`
184  :param n_max_naive: Parameter for chain mapping. If the number of possible
185  mappings is <= *n_max_naive*, the full
186  mapping solution space is enumerated to find the
187  the optimum. A heuristic is used otherwise. The default
188  of 40320 corresponds to an octamer (8! = 40320).
189  A structure with stoichiometry A6B2 would be
190  6!*2! = 1440 etc.
191  :type n_max_naive: :class:`int`
192  :param oum: Override USalign Mapping. Inject rigid_mapping of
193  :class:`Scorer` object into USalign to compute TM-score.
194  Experimental feature with limitations.
195  :type oum: :class:`bool`
196  :param min_pep_length: Relevant parameter if short peptides are involved in
197  scoring. Minimum peptide length for a chain in the
198  target structure to be considered in chain mapping.
199  The chain mapping algorithm first performs an all vs.
200  all pairwise sequence alignment to identify \"equal\"
201  chains within the target structure. We go for simple
202  sequence identity there. Short sequences can be
203  problematic as they may produce high sequence identity
204  alignments by pure chance.
205  :type min_pep_length: :class:`int`
206  :param min_nuc_length: Relevant parameter if short nucleotides are involved
207  in scoring. Minimum nucleotide length for a chain in
208  the target structure to be considered in chain
209  mapping. The chain mapping algorithm first performs
210  an all vs. all pairwise sequence alignment to
211  identify \"equal\" chains within the target
212  structure. We go for simple sequence identity there.
213  Short sequences can be problematic as they may
214  produce high sequence identity alignments by pure
215  chance.
216  :type min_nuc_length: :class:`int`
217  :param lddt_add_mdl_contacts: LDDT specific flag. Only using contacts in
218  LDDT that are within a certain distance
219  threshold in the target does not penalize
220  for added model contacts. If set to True, this
221  flag will also consider target contacts
222  that are within the specified distance
223  threshold in the model but not necessarily in
224  the target. No contact will be added if the
225  respective atom pair is not resolved in the
226  target.
227  :type lddt_add_mdl_contacts: :class:`bool`
228  :param dockq_capri_peptide: Flag that changes two things in the way DockQ
229  and its underlying scores are computed which is
230  proposed by the CAPRI community when scoring
231  peptides (PMID: 31886916).
232  ONE: Two residues are considered in contact if
233  any of their atoms is within 5A. This is
234  relevant for fnat and fnonat scores. CAPRI
235  suggests to lower this threshold to 4A for
236  protein-peptide interactions.
237  TWO: irmsd is computed on interface residues. A
238  residue is defined as interface residue if any
239  of its atoms is within 10A of another chain.
240  CAPRI suggests to lower the default of 10A to
241  8A in combination with only considering CB atoms
242  for protein-peptide interactions.
243  This flag has no influence on patch_dockq
244  scores.
245  :type dockq_capri_peptide: :class:`bool`
246  :param lddt_symmetry_settings: Passed as *symmetry_settings* parameter to
247  LDDT scorer. Default: None
248  :type lddt_symmetry_settings: :class:`ost.mol.alg.lddt.SymmetrySettings`
249  :param lddt_inclusion_radius: LDDT inclusion radius.
250  :param pep_seqid_thr: Parameter that affects identification of identical
251  chains in target - see
252  :class:`ost.mol.alg.chain_mapping.ChainMapper`
253  :type pep_seqid_thr: :class:`float`
254  :param nuc_seqid_thr: Parameter that affects identification of identical
255  chains in target - see
256  :class:`ost.mol.alg.chain_mapping.ChainMapper`
257  :type nuc_seqid_thr: :class:`float`
258  :param mdl_map_pep_seqid_thr: Parameter that affects mapping of model chains
259  to target chains - see
260  :class:`ost.mol.alg.chain_mapping.ChainMapper`
261  :type mdl_map_pep_seqid_thr: :class:`float`
262  :param mdl_map_nuc_seqid_thr: Parameter that affects mapping of model chains
263  to target chains - see
264  :class:`ost.mol.alg.chain_mapping.ChainMapper`
265  :type mdl_map_nuc_seqid_thr: :class:`float`
266  :param seqres: Parameter that affects identification of identical chains in
267  target - see :class:`ost.mol.alg.chain_mapping.ChainMapper`
268  :type seqres: :class:`ost.seq.SequenceList`
269  :param trg_seqres_mapping: Parameter that affects identification of identical
270  chains in target - see
271  :class:`ost.mol.alg.chain_mapping.ChainMapper`
272  :type trg_seqres_mapping: :class:`dict`
273  """
274  def __init__(self, model, target, resnum_alignments=False,
275  molck_settings = None, cad_score_exec = None,
276  custom_mapping=None, custom_rigid_mapping=None,
277  usalign_exec = None, lddt_no_stereochecks=False,
278  n_max_naive=40320, oum=False, min_pep_length = 6,
279  min_nuc_length = 4, lddt_add_mdl_contacts=False,
280  dockq_capri_peptide=False, lddt_symmetry_settings = None,
281  lddt_inclusion_radius = 15.0,
282  pep_seqid_thr = 95., nuc_seqid_thr = 95.,
283  mdl_map_pep_seqid_thr = 0.,
284  mdl_map_nuc_seqid_thr = 0.,
285  seqres = None,
286  trg_seqres_mapping = None):
287 
288  self._target_orig_target_orig = target
289  self._model_orig_model_orig = model
290 
291  # lazily computed versions of target_orig and model_orig
292  self._pepnuc_target_pepnuc_target = None
293  self._pepnuc_model_pepnuc_model = None
294 
295  if isinstance(self._model_orig_model_orig, mol.EntityView):
296  self._model_model = mol.CreateEntityFromView(self._model_orig_model_orig, False)
297  else:
298  self._model_model = self._model_orig_model_orig.Copy()
299 
300  if isinstance(self._target_orig_target_orig, mol.EntityView):
301  self._target_target = mol.CreateEntityFromView(self._target_orig_target_orig, False)
302  else:
303  self._target_target = self._target_orig_target_orig.Copy()
304 
305  if molck_settings is None:
306  molck_settings = MolckSettings(rm_unk_atoms=True,
307  rm_non_std=False,
308  rm_hyd_atoms=True,
309  rm_oxt_atoms=True,
310  rm_zero_occ_atoms=False,
311  colored=False,
312  map_nonstd_res=True,
313  assign_elem=True)
314  LogScript("Cleaning up input structures")
315  Molck(self._model_model, conop.GetDefaultLib(), molck_settings)
316  Molck(self._target_target, conop.GetDefaultLib(), molck_settings)
317 
318  if resnum_alignments:
319  # If we're dealing with resnum alignments, we ensure that
320  # consecutive peptide and nucleotide residues are connected based
321  # on residue number information. The conop.Processor only connects
322  # these things if the bonds are actually feasible which can lead to
323  # weird behaviour in stereochemistry checks. Let's say N and C are
324  # too close, it's reported as a clash. If they're too far apart,
325  # they're not reported at all. If we're not dealing with resnum
326  # alignments, we're out of luck as we have no direct residue
327  # connectivity information from residue numbers
328  self._resnum_connect_resnum_connect(self._model_model)
329  self._resnum_connect_resnum_connect(self._target_target)
330 
331  self._model_model = self._model_model.Select("peptide=True or nucleotide=True")
332  self._target_target = self._target_target.Select("peptide=True or nucleotide=True")
333 
334  # catch models which have empty chain names
335  for ch in self._model_model.chains:
336  if ch.GetName().strip() == "":
337  raise RuntimeError("Model chains must have valid chain names")
338  if ch.GetName().strip() == "'" or ch.GetName().strip() == '"':
339  raise RuntimeError("Model chains cannot be named with quote signs (' or \"\")")
340 
341  # catch targets which have empty chain names
342  for ch in self._target_target.chains:
343  if ch.GetName().strip() == "":
344  raise RuntimeError("Target chains must have valid chain names")
345  if ch.GetName().strip() == "'" or ch.GetName().strip() == '"':
346  raise RuntimeError("Target chains cannot be named with quote signs (' or \"\")")
347 
348  if resnum_alignments:
349  # In case of resnum_alignments, we have some requirements on
350  # residue numbers in the chain mapping: 1) no ins codes 2) strictly
351  # increasing residue numbers.
352  for ch in self._model_model.chains:
353  ins_codes = [r.GetNumber().GetInsCode() for r in ch.residues]
354  if len(set(ins_codes)) != 1 or ins_codes[0] != '\0':
355  raise RuntimeError("Residue numbers in each model chain "
356  "must not contain insertion codes if "
357  "resnum_alignments are enabled")
358  nums = [r.GetNumber().GetNum() for r in ch.residues]
359  if not all(i < j for i, j in zip(nums, nums[1:])):
360  raise RuntimeError("Residue numbers in each model chain "
361  "must be strictly increasing if "
362  "resnum_alignments are enabled")
363 
364  for ch in self._target_target.chains:
365  ins_codes = [r.GetNumber().GetInsCode() for r in ch.residues]
366  if len(set(ins_codes)) != 1 or ins_codes[0] != '\0':
367  raise RuntimeError("Residue numbers in each target chain "
368  "must not contain insertion codes if "
369  "resnum_alignments are enabled")
370  nums = [r.GetNumber().GetNum() for r in ch.residues]
371  if not all(i < j for i, j in zip(nums, nums[1:])):
372  raise RuntimeError("Residue numbers in each target chain "
373  "must be strictly increasing if "
374  "resnum_alignments are enabled")
375 
376  if usalign_exec is not None:
377  if not os.path.exists(usalign_exec):
378  raise RuntimeError(f"USalign exec ({usalign_exec}) "
379  f"not found")
380  if not os.access(usalign_exec, os.X_OK):
381  raise RuntimeError(f"USalign exec ({usalign_exec}) "
382  f"is not executable")
383 
384  self.resnum_alignmentsresnum_alignments = resnum_alignments
385  self.cad_score_execcad_score_exec = cad_score_exec
386  self.usalign_execusalign_exec = usalign_exec
387  self.lddt_no_stereocheckslddt_no_stereochecks = lddt_no_stereochecks
388  self.n_max_naiven_max_naive = n_max_naive
389  self.oumoum = oum
390  self.min_pep_lengthmin_pep_length = min_pep_length
391  self.min_nuc_lengthmin_nuc_length = min_nuc_length
392  self.lddt_add_mdl_contactslddt_add_mdl_contacts = lddt_add_mdl_contacts
393  self.dockq_capri_peptidedockq_capri_peptide = dockq_capri_peptide
394  self.lddt_symmetry_settingslddt_symmetry_settings = lddt_symmetry_settings
395  self.lddt_inclusion_radiuslddt_inclusion_radius = lddt_inclusion_radius
396  self.pep_seqid_thrpep_seqid_thr = pep_seqid_thr
397  self.nuc_seqid_thrnuc_seqid_thr = nuc_seqid_thr
398  self.mdl_map_pep_seqid_thrmdl_map_pep_seqid_thr = mdl_map_pep_seqid_thr
399  self.mdl_map_nuc_seqid_thrmdl_map_nuc_seqid_thr = mdl_map_nuc_seqid_thr
400  self.seqresseqres = seqres
401  self.trg_seqres_mappingtrg_seqres_mapping = trg_seqres_mapping
402 
403  # lazily evaluated attributes
404  self._stereochecked_model_stereochecked_model = None
405  self._stereochecked_target_stereochecked_target = None
406  self._model_clashes_model_clashes = None
407  self._model_bad_bonds_model_bad_bonds = None
408  self._model_bad_angles_model_bad_angles = None
409  self._target_clashes_target_clashes = None
410  self._target_bad_bonds_target_bad_bonds = None
411  self._target_bad_angles_target_bad_angles = None
412  self._trimmed_model_trimmed_model = None
413  self._chain_mapper_chain_mapper = None
414  self._mapping_mapping = None
415  self._rigid_mapping_rigid_mapping = None
416  self._model_interface_residues_model_interface_residues = None
417  self._target_interface_residues_target_interface_residues = None
418  self._aln_aln = None
419  self._stereochecked_aln_stereochecked_aln = None
420  self._pepnuc_aln_pepnuc_aln = None
421  self._trimmed_aln_trimmed_aln = None
422 
423  # lazily constructed scorer objects
424  self._lddt_scorer_lddt_scorer = None
425  self._bb_lddt_scorer_bb_lddt_scorer = None
426  self._qs_scorer_qs_scorer = None
427  self._contact_scorer_contact_scorer = None
428  self._trimmed_contact_scorer_trimmed_contact_scorer = None
429 
430  # lazily computed scores
431  self._lddt_lddt = None
432  self._local_lddt_local_lddt = None
433  self._aa_local_lddt_aa_local_lddt = None
434  self._bb_lddt_bb_lddt = None
435  self._bb_local_lddt_bb_local_lddt = None
436  self._ilddt_ilddt = None
437 
438  self._qs_global_qs_global = None
439  self._qs_best_qs_best = None
440  self._qs_target_interfaces_qs_target_interfaces = None
441  self._qs_model_interfaces_qs_model_interfaces = None
442  self._qs_interfaces_qs_interfaces = None
443  self._per_interface_qs_global_per_interface_qs_global = None
444  self._per_interface_qs_best_per_interface_qs_best = None
445 
446  self._contact_target_interfaces_contact_target_interfaces = None
447  self._contact_model_interfaces_contact_model_interfaces = None
448  self._native_contacts_native_contacts = None
449  self._model_contacts_model_contacts = None
450  self._trimmed_model_contacts_trimmed_model_contacts = None
451  self._ics_precision_ics_precision = None
452  self._ics_recall_ics_recall = None
453  self._ics_ics = None
454  self._per_interface_ics_precision_per_interface_ics_precision = None
455  self._per_interface_ics_recall_per_interface_ics_recall = None
456  self._per_interface_ics_per_interface_ics = None
457  self._ips_precision_ips_precision = None
458  self._ips_recall_ips_recall = None
459  self._ips_ips = None
460  self._per_interface_ips_precision_per_interface_ips_precision = None
461  self._per_interface_ips_recall_per_interface_ips_recall = None
462  self._per_interface_ips_per_interface_ips = None
463 
464  # subset of contact scores that operate on trimmed model
465  # i.e. no contacts from model residues that are not present in
466  # target
467  self._ics_trimmed_ics_trimmed = None
468  self._ics_precision_trimmed_ics_precision_trimmed = None
469  self._ics_recall_trimmed_ics_recall_trimmed = None
470  self._per_interface_ics_precision_trimmed_per_interface_ics_precision_trimmed = None
471  self._per_interface_ics_recall_trimmed_per_interface_ics_recall_trimmed = None
472  self._per_interface_ics_trimmed_per_interface_ics_trimmed = None
473  self._ips_trimmed_ips_trimmed = None
474  self._ips_precision_trimmed_ips_precision_trimmed = None
475  self._ips_recall_trimmed_ips_recall_trimmed = None
476  self._per_interface_ips_precision_trimmed_per_interface_ips_precision_trimmed = None
477  self._per_interface_ips_recall_trimmed_per_interface_ips_recall_trimmed = None
478  self._per_interface_ips_trimmed_per_interface_ips_trimmed = None
479 
480  self._dockq_target_interfaces_dockq_target_interfaces = None
481  self._dockq_interfaces_dockq_interfaces = None
482  self._fnat_fnat = None
483  self._fnonnat_fnonnat = None
484  self._irmsd_irmsd = None
485  self._lrmsd_lrmsd = None
486  self._nnat_nnat = None
487  self._nmdl_nmdl = None
488  self._dockq_scores_dockq_scores = None
489  self._dockq_ave_dockq_ave = None
490  self._dockq_wave_dockq_wave = None
491  self._dockq_ave_full_dockq_ave_full = None
492  self._dockq_wave_full_dockq_wave_full = None
493 
494  self._mapped_target_pos_mapped_target_pos = None
495  self._mapped_model_pos_mapped_model_pos = None
496  self._mapped_target_pos_full_bb_mapped_target_pos_full_bb = None
497  self._mapped_model_pos_full_bb_mapped_model_pos_full_bb = None
498  self._transformed_mapped_model_pos_transformed_mapped_model_pos = None
499  self._n_target_not_mapped_n_target_not_mapped = None
500  self._transform_transform = None
501 
502  self._rigid_mapped_target_pos_rigid_mapped_target_pos = None
503  self._rigid_mapped_model_pos_rigid_mapped_model_pos = None
504  self._rigid_mapped_target_pos_full_bb_rigid_mapped_target_pos_full_bb = None
505  self._rigid_mapped_model_pos_full_bb_rigid_mapped_model_pos_full_bb = None
506  self._rigid_transformed_mapped_model_pos_rigid_transformed_mapped_model_pos = None
507  self._rigid_n_target_not_mapped_rigid_n_target_not_mapped = None
508  self._rigid_transform_rigid_transform = None
509 
510  self._gdt_window_sizes_gdt_window_sizes = [7, 9, 12, 24, 48]
511  self._gdt_05_gdt_05 = None
512  self._gdt_1_gdt_1 = None
513  self._gdt_2_gdt_2 = None
514  self._gdt_4_gdt_4 = None
515  self._gdt_8_gdt_8 = None
516  self._gdtts_gdtts = None
517  self._gdtha_gdtha = None
518  self._rmsd_rmsd = None
519 
520  self._cad_score_cad_score = None
521  self._local_cad_score_local_cad_score = None
522 
523  self._patch_qs_patch_qs = None
524  self._patch_dockq_patch_dockq = None
525 
526  self._tm_score_tm_score = None
527  self._usalign_mapping_usalign_mapping = None
528 
529  if custom_mapping is not None:
530  self._mapping_mapping = self._construct_custom_mapping_construct_custom_mapping(custom_mapping)
531 
532  if custom_rigid_mapping is not None:
533  self._rigid_mapping_rigid_mapping = \
534  self._construct_custom_mapping_construct_custom_mapping(custom_rigid_mapping)
535  LogDebug("Scorer sucessfully initialized")
536 
537  @property
538  def model(self):
539  """ Model with Molck cleanup
540 
541  :type: :class:`ost.mol.EntityHandle`
542  """
543  return self._model_model
544 
545  @property
546  def model_orig(self):
547  """ The original model passed at object construction
548 
549  :type: :class:`ost.mol.EntityHandle`/:class:`ost.mol.EntityView`
550  """
551  return self._model_orig_model_orig
552 
553  @property
554  def pepnuc_model(self):
555  """ A selection of :attr:`~model_orig`
556 
557  Only contains peptide and nucleotide residues
558 
559  :type: :class:`ost.mol.EntityView`
560  """
561  if self._pepnuc_model_pepnuc_model is None:
562  query = "peptide=true or nucleotide=true"
563  self._pepnuc_model_pepnuc_model = self.model_origmodel_orig.Select(query)
564  return self._pepnuc_model_pepnuc_model
565 
566  @property
567  def target(self):
568  """ Target with Molck cleanup
569 
570  :type: :class:`ost.mol.EntityHandle`
571  """
572  return self._target_target
573 
574  @property
575  def target_orig(self):
576  """ The original target passed at object construction
577 
578  :type: :class:`ost.mol.EntityHandle`/:class:`ost.mol.EntityView`
579  """
580  return self._target_orig_target_orig
581 
582  @property
583  def pepnuc_target(self):
584  """ A selection of :attr:`~target_orig`
585 
586  Only contains peptide and nucleotide residues
587 
588  :type: :class:`ost.mol.EntityView`
589  """
590  if self._pepnuc_target_pepnuc_target is None:
591  query = "peptide=true or nucleotide=true"
592  self._pepnuc_target_pepnuc_target = self.target_origtarget_orig.Select(query)
593  return self._pepnuc_target_pepnuc_target
594 
595  @property
596  def aln(self):
597  """ Alignments of :attr:`~model`/:attr:`~target` chains
598 
599  Alignments for each pair of chains mapped in :attr:`~mapping`.
600  First sequence is target sequence, second sequence the model sequence.
601 
602  :type: :class:`list` of :class:`ost.seq.AlignmentHandle`
603  """
604  if self._aln_aln is None:
605  self._compute_aln_compute_aln()
606  return self._aln_aln
607 
608  @property
609  def stereochecked_aln(self):
610  """ Stereochecked equivalent of :attr:`~aln`
611 
612  The alignments may differ, as stereochecks potentially remove residues
613 
614  :type: :class:`list` of :class:`ost.seq.AlignmentHandle`
615  """
616  if self._stereochecked_aln_stereochecked_aln is None:
617  self._compute_stereochecked_aln_compute_stereochecked_aln()
618  return self._stereochecked_aln_stereochecked_aln
619 
620  @property
621  def pepnuc_aln(self):
622  """ Alignments of :attr:`~model_orig`/:attr:`~target_orig` chains
623 
624  Selects for peptide and nucleotide residues before sequence
625  extraction. Includes residues that would be removed by molck in
626  structure preprocessing.
627 
628  :type: :class:`list` of :class:`ost.seq.AlignmentHandle`
629  """
630  if self._pepnuc_aln_pepnuc_aln is None:
631  self._compute_pepnuc_aln_compute_pepnuc_aln()
632  return self._pepnuc_aln_pepnuc_aln
633 
634  @property
635  def trimmed_aln(self):
636  """ Alignments of :attr:`~trimmed_model`/:attr:`~target` chains
637 
638  Alignments for each pair of chains mapped in :attr:`~mapping`.
639  First sequence is target sequence, second sequence the model sequence.
640 
641  :type: :class:`list` of :class:`ost.seq.AlignmentHandle`
642  """
643  if self._trimmed_aln_trimmed_aln is None:
644  self._trim_model_trim_model()
645  return self._trimmed_aln_trimmed_aln
646 
647  @property
649  """ View of :attr:`~model` that has stereochemistry checks applied
650 
651  First, a selection for peptide/nucleotide residues is performed,
652  secondly peptide sidechains with stereochemical irregularities are
653  removed (full residue if backbone atoms are involved). Irregularities
654  are clashes or bond lengths/angles more than 12 standard deviations
655  from expected values.
656 
657  :type: :class:`ost.mol.EntityView`
658  """
659  if self._stereochecked_model_stereochecked_model is None:
660  self._do_stereochecks_do_stereochecks()
661  return self._stereochecked_model_stereochecked_model
662 
663  @property
664  def model_clashes(self):
665  """ Clashing model atoms
666 
667  :type: :class:`list` of :class:`ost.mol.alg.stereochemistry.ClashInfo`
668  """
669  if self._model_clashes_model_clashes is None:
670  self._do_stereochecks_do_stereochecks()
671  return self._model_clashes_model_clashes
672 
673  @property
674  def model_bad_bonds(self):
675  """ Model bonds with unexpected stereochemistry
676 
677  :type: :class:`list` of
678  :class:`ost.mol.alg.stereochemistry.BondViolationInfo`
679  """
680  if self._model_bad_bonds_model_bad_bonds is None:
681  self._do_stereochecks_do_stereochecks()
682  return self._model_bad_bonds_model_bad_bonds
683 
684  @property
685  def model_bad_angles(self):
686  """ Model angles with unexpected stereochemistry
687 
688  :type: :class:`list` of
689  :class:`ost.mol.alg.stereochemistry.AngleViolationInfo`
690  """
691  if self._model_bad_angles_model_bad_angles is None:
692  self._do_stereochecks_do_stereochecks()
693  return self._model_bad_angles_model_bad_angles
694 
695  @property
697  """ Same as :attr:`~stereochecked_model` for :attr:`~target`
698 
699  :type: :class:`ost.mol.EntityView`
700  """
701  if self._stereochecked_target_stereochecked_target is None:
702  self._do_stereochecks_do_stereochecks()
703  return self._stereochecked_target_stereochecked_target
704 
705  @property
706  def target_clashes(self):
707  """ Clashing target atoms
708 
709  :type: :class:`list` of :class:`ost.mol.alg.stereochemistry.ClashInfo`
710  """
711  if self._target_clashes_target_clashes is None:
712  self._do_stereochecks_do_stereochecks()
713  return self._target_clashes_target_clashes
714 
715  @property
716  def target_bad_bonds(self):
717  """ Target bonds with unexpected stereochemistry
718 
719  :type: :class:`list` of
720  :class:`ost.mol.alg.stereochemistry.BondViolationInfo`
721  """
722  if self._target_bad_bonds_target_bad_bonds is None:
723  self._do_stereochecks_do_stereochecks()
724  return self._target_bad_bonds_target_bad_bonds
725 
726  @property
727  def target_bad_angles(self):
728  """ Target angles with unexpected stereochemistry
729 
730  :type: :class:`list` of
731  :class:`ost.mol.alg.stereochemistry.AngleViolationInfo`
732  """
733  if self._target_bad_angles_target_bad_angles is None:
734  self._do_stereochecks_do_stereochecks()
735  return self._target_bad_angles_target_bad_angles
736 
737  @property
738  def trimmed_model(self):
739  """ :attr:`~model` trimmed to target
740 
741  Removes residues that are not covered by :class:`target` given
742  :attr:`~mapping`. In other words: no model residues without experimental
743  evidence from :class:`target`.
744 
745  :type: :class:`ost.mol.EntityView`
746  """
747  if self._trimmed_model_trimmed_model is None:
748  self._trim_model_trim_model()
749  return self._trimmed_model_trimmed_model
750 
751  @property
752  def chain_mapper(self):
753  """ Chain mapper object for given :attr:`~target`
754 
755  :type: :class:`ost.mol.alg.chain_mapping.ChainMapper`
756  """
757  if self._chain_mapper_chain_mapper is None:
758  self._chain_mapper_chain_mapper = chain_mapping.ChainMapper(self.targettarget,
759  n_max_naive=1e9,
760  resnum_alignments=self.resnum_alignmentsresnum_alignments,
761  min_pep_length=self.min_pep_lengthmin_pep_length,
762  min_nuc_length=self.min_nuc_lengthmin_nuc_length,
763  pep_seqid_thr=self.pep_seqid_thrpep_seqid_thr,
764  nuc_seqid_thr=self.nuc_seqid_thrnuc_seqid_thr,
765  mdl_map_pep_seqid_thr=self.mdl_map_pep_seqid_thrmdl_map_pep_seqid_thr,
766  mdl_map_nuc_seqid_thr=self.mdl_map_nuc_seqid_thrmdl_map_nuc_seqid_thr,
767  seqres=self.seqresseqres,
768  trg_seqres_mapping=self.trg_seqres_mappingtrg_seqres_mapping)
769  return self._chain_mapper_chain_mapper
770 
771  @property
772  def mapping(self):
773  """ Full chain mapping result for :attr:`~target`/:attr:`~model`
774 
775  Computed with :func:`ost.mol.alg.ChainMapper.GetMapping`
776 
777  :type: :class:`ost.mol.alg.chain_mapping.MappingResult`
778  """
779  if self._mapping_mapping is None:
780  LogScript("Computing chain mapping")
781  self._mapping_mapping = \
782  self.chain_mapperchain_mapper.GetMapping(self.modelmodel,
783  n_max_naive = self.n_max_naiven_max_naive)
784  return self._mapping_mapping
785 
786  @property
787  def rigid_mapping(self):
788  """ Full chain mapping result for :attr:`~target`/:attr:`~model`
789 
790  Computed with :func:`ost.mol.alg.ChainMapper.GetRMSDMapping`
791 
792  :type: :class:`ost.mol.alg.chain_mapping.MappingResult`
793  """
794  if self._rigid_mapping_rigid_mapping is None:
795  LogScript("Computing rigid chain mapping")
796  self._rigid_mapping_rigid_mapping = \
797  self.chain_mapperchain_mapper.GetRMSDMapping(self.modelmodel)
798  return self._rigid_mapping_rigid_mapping
799 
800  @property
802  """ Interface residues in :attr:`~model`
803 
804  Thats all residues having a contact with at least one residue from
805  another chain (CB-CB distance <= 8A, CA in case of Glycine)
806 
807  :type: :class:`dict` with chain names as key and and :class:`list`
808  with residue numbers of the respective interface residues.
809  """
810  if self._model_interface_residues_model_interface_residues is None:
811  self._model_interface_residues_model_interface_residues = \
812  self._get_interface_residues_get_interface_residues(self.modelmodel)
813  return self._model_interface_residues_model_interface_residues
814 
815  @property
817  """ Same as :attr:`~model_interface_residues` for :attr:`~target`
818 
819  :type: :class:`dict` with chain names as key and and :class:`list`
820  with residue numbers of the respective interface residues.
821  """
822  if self._target_interface_residues_target_interface_residues is None:
823  self._target_interface_residues_target_interface_residues = \
824  self._get_interface_residues_get_interface_residues(self.targettarget)
825  return self._target_interface_residues_target_interface_residues
826 
827  @property
828  def lddt_scorer(self):
829  """ LDDT scorer for :attr:`~target`/:attr:`~stereochecked_target`
830 
831  Depending on :attr:`~lddt_no_stereocheck` and
832  :attr:`~lddt_symmetry_settings`.
833 
834  :type: :class:`ost.mol.alg.lddt.lDDTScorer`
835  """
836  if self._lddt_scorer_lddt_scorer is None:
837  if self.lddt_no_stereocheckslddt_no_stereochecks:
838  self._lddt_scorer_lddt_scorer = lDDTScorer(self.targettarget,
839  symmetry_settings = self.lddt_symmetry_settingslddt_symmetry_settings,
840  inclusion_radius = self.lddt_inclusion_radiuslddt_inclusion_radius)
841  else:
842  self._lddt_scorer_lddt_scorer = lDDTScorer(self.stereochecked_targetstereochecked_target,
843  symmetry_settings = self.lddt_symmetry_settingslddt_symmetry_settings,
844  inclusion_radius = self.lddt_inclusion_radiuslddt_inclusion_radius)
845  return self._lddt_scorer_lddt_scorer
846 
847  @property
848  def bb_lddt_scorer(self):
849  """ LDDT scorer for :attr:`~target`, restricted to representative
850  backbone atoms
851 
852  No stereochecks applied for bb only LDDT which considers CA atoms
853  for peptides and C3' atoms for nucleotides.
854 
855  :type: :class:`ost.mol.alg.lddt.lDDTScorer`
856  """
857  if self._bb_lddt_scorer_bb_lddt_scorer is None:
858  self._bb_lddt_scorer_bb_lddt_scorer = lDDTScorer(self.targettarget, bb_only=True,
859  symmetry_settings = self.lddt_symmetry_settingslddt_symmetry_settings,
860  inclusion_radius = self.lddt_inclusion_radiuslddt_inclusion_radius)
861  return self._bb_lddt_scorer_bb_lddt_scorer
862 
863  @property
864  def qs_scorer(self):
865  """ QS scorer constructed from :attr:`~mapping`
866 
867  The scorer object is constructed with default parameters and relates to
868  :attr:`~model` and :attr:`~target` (no stereochecks).
869 
870  :type: :class:`ost.mol.alg.qsscore.QSScorer`
871  """
872  if self._qs_scorer_qs_scorer is None:
873  self._qs_scorer_qs_scorer = QSScorer.FromMappingResult(self.mappingmapping)
874  return self._qs_scorer_qs_scorer
875 
876  @property
877  def contact_scorer(self):
878  if self._contact_scorer_contact_scorer is None:
879  self._contact_scorer_contact_scorer = ContactScorer.FromMappingResult(self.mappingmapping)
880  return self._contact_scorer_contact_scorer
881 
882  @property
884  if self._trimmed_contact_scorer_trimmed_contact_scorer is None:
885  self._trimmed_contact_scorer_trimmed_contact_scorer = ContactScorer(self.mappingmapping.target,
886  self.mappingmapping.chem_groups,
887  self.trimmed_modeltrimmed_model,
888  self.trimmed_alntrimmed_aln)
889  return self._trimmed_contact_scorer_trimmed_contact_scorer
890 
891  @property
892  def lddt(self):
893  """ Global LDDT score in range [0.0, 1.0]
894 
895  Computed based on :attr:`~stereochecked_model`. In case of oligomers,
896  :attr:`~mapping` is used.
897 
898  :type: :class:`float`
899  """
900  if self._lddt_lddt is None:
901  self._compute_lddt_compute_lddt()
902  return self._lddt_lddt
903 
904  @property
905  def local_lddt(self):
906  """ Per residue LDDT scores in range [0.0, 1.0]
907 
908  Computed based on :attr:`~stereochecked_model` but scores for all
909  residues in :attr:`~model` are reported. If a residue has been removed
910  by stereochemistry checks, the respective score is set to 0.0. If a
911  residue is not covered by the target or is in a chain skipped by the
912  chain mapping procedure (happens for super short chains), the respective
913  score is set to None. In case of oligomers, :attr:`~mapping` is used.
914 
915  :type: :class:`dict`
916  """
917  if self._local_lddt_local_lddt is None:
918  self._compute_lddt_compute_lddt()
919  return self._local_lddt_local_lddt
920 
921  @property
922  def aa_local_lddt(self):
923  """ Per atom LDDT scores in range [0.0, 1.0]
924 
925  Computed based on :attr:`~stereochecked_model` but scores for all
926  atoms in :attr:`~model` are reported. If an atom has been removed
927  by stereochemistry checks, the respective score is set to 0.0. If an
928  atom is not covered by the target or is in a chain skipped by the
929  chain mapping procedure (happens for super short chains), the respective
930  score is set to None. In case of oligomers, :attr:`~mapping` is used.
931 
932  :type: :class:`dict`
933  """
934  if self._aa_local_lddt_aa_local_lddt is None:
935  self._compute_lddt_compute_lddt()
936  return self._aa_local_lddt_aa_local_lddt
937 
938  @property
939  def bb_lddt(self):
940  """ Global LDDT score restricted to representative backbone atoms in
941  range [0.0, 1.0]
942 
943  Computed based on :attr:`~model` on representative backbone atoms only.
944  This is CA for peptides and C3' for nucleotides. No stereochecks are
945  performed. In case of oligomers, :attr:`~mapping` is used.
946 
947  :type: :class:`float`
948  """
949  if self._bb_lddt_bb_lddt is None:
950  self._compute_bb_lddt_compute_bb_lddt()
951  return self._bb_lddt_bb_lddt
952 
953  @property
954  def bb_local_lddt(self):
955  """ Per residue LDDT scores restricted to representative backbone atoms
956  in range [0.0, 1.0]
957 
958  Computed based on :attr:`~model` on representative backbone atoms only.
959  This is CA for peptides and C3' for nucleotides. No stereochecks are
960  performed. If a residue is not covered by the target or is in a chain
961  skipped by the chain mapping procedure (happens for super short
962  chains), the respective score is set to None. In case of oligomers,
963  :attr:`~mapping` is used.
964 
965  :type: :class:`dict`
966  """
967  if self._bb_local_lddt_bb_local_lddt is None:
968  self._compute_bb_lddt_compute_bb_lddt()
969  return self._bb_local_lddt_bb_local_lddt
970 
971  @property
972  def ilddt(self):
973  """ Global interface LDDT score in range [0.0, 1.0]
974 
975  This is LDDT only based on inter-chain contacts. Value is None if no
976  such contacts are present. For example if we're dealing with a monomer.
977  Computed based on :attr:`~stereochecked_model` and :attr:`~mapping` for
978  chain mapping.
979 
980  :type: :class:`float`
981  """
982  if self._ilddt_ilddt is None:
983  # the whole None business kind of invalidates the idea of lazy
984  # evaluation. The assumption is that this is called only once...
985  self._compute_ilddt_compute_ilddt()
986  return self._ilddt_ilddt
987 
988 
989  @property
990  def qs_global(self):
991  """ Global QS-score
992 
993  Computed based on :attr:`~model` using :attr:`~mapping`
994 
995  :type: :class:`float`
996  """
997  if self._qs_global_qs_global is None:
998  self._compute_qs_compute_qs()
999  return self._qs_global_qs_global
1000 
1001  @property
1002  def qs_best(self):
1003  """ Global QS-score - only computed on aligned residues
1004 
1005  Computed based on :attr:`~model` using :attr:`~mapping`. The QS-score
1006  computation only considers contacts between residues with a mapping
1007  between target and model. As a result, the score won't be lowered in
1008  case of additional chains/residues in any of the structures.
1009 
1010  :type: :class:`float`
1011  """
1012  if self._qs_best_qs_best is None:
1013  self._compute_qs_compute_qs()
1014  return self._qs_best_qs_best
1015 
1016  @property
1018  """ Interfaces in :attr:`~target` with non-zero contribution to
1019  :attr:`~qs_global`/:attr:`~qs_best`
1020 
1021  Chain names are lexicographically sorted.
1022 
1023  :type: :class:`list` of :class:`tuple` with 2 elements each:
1024  (trg_ch1, trg_ch2)
1025  """
1026  if self._qs_target_interfaces_qs_target_interfaces is None:
1027  self._qs_target_interfaces_qs_target_interfaces = self.qs_scorerqs_scorer.qsent1.interacting_chains
1028  self._qs_target_interfaces_qs_target_interfaces = \
1029  [(min(x[0],x[1]), max(x[0],x[1])) for x in self._qs_target_interfaces_qs_target_interfaces]
1030  return self._qs_target_interfaces_qs_target_interfaces
1031 
1032  @property
1034  """ Interfaces in :attr:`~model` with non-zero contribution to
1035  :attr:`~qs_global`/:attr:`~qs_best`
1036 
1037  Chain names are lexicographically sorted.
1038 
1039  :type: :class:`list` of :class:`tuple` with 2 elements each:
1040  (mdl_ch1, mdl_ch2)
1041  """
1042  if self._qs_model_interfaces_qs_model_interfaces is None:
1043  self._qs_model_interfaces_qs_model_interfaces = self.qs_scorerqs_scorer.qsent2.interacting_chains
1044  self._qs_model_interfaces_qs_model_interfaces = \
1045  [(min(x[0],x[1]), max(x[0],x[1])) for x in self._qs_model_interfaces_qs_model_interfaces]
1046 
1047  return self._qs_model_interfaces_qs_model_interfaces
1048 
1049  @property
1050  def qs_interfaces(self):
1051  """ Interfaces in :attr:`~qs_target_interfaces` that can be mapped
1052  to :attr:`~model`.
1053 
1054  Target chain names are lexicographically sorted.
1055 
1056  :type: :class:`list` of :class:`tuple` with 4 elements each:
1057  (trg_ch1, trg_ch2, mdl_ch1, mdl_ch2)
1058  """
1059  if self._qs_interfaces_qs_interfaces is None:
1060  self._qs_interfaces_qs_interfaces = list()
1061  flat_mapping = self.mappingmapping.GetFlatMapping()
1062  for i in self.qs_target_interfacesqs_target_interfaces:
1063  if i[0] in flat_mapping and i[1] in flat_mapping:
1064  self._qs_interfaces_qs_interfaces.append((i[0], i[1],
1065  flat_mapping[i[0]],
1066  flat_mapping[i[1]]))
1067  return self._qs_interfaces_qs_interfaces
1068 
1069  @property
1071  """ QS-score for each interface in :attr:`~qs_interfaces`
1072 
1073  :type: :class:`list` of :class:`float`
1074  """
1075  if self._per_interface_qs_global_per_interface_qs_global is None:
1076  self._compute_per_interface_qs_scores_compute_per_interface_qs_scores()
1077  return self._per_interface_qs_global_per_interface_qs_global
1078 
1079  @property
1081  """ QS-score for each interface in :attr:`~qs_interfaces`
1082 
1083  Only computed on aligned residues
1084 
1085  :type: :class:`list` of :class:`float`
1086  """
1087  if self._per_interface_qs_best_per_interface_qs_best is None:
1088  self._compute_per_interface_qs_scores_compute_per_interface_qs_scores()
1089  return self._per_interface_qs_best_per_interface_qs_best
1090 
1091  @property
1092  def native_contacts(self):
1093  """ Native contacts
1094 
1095  A contact is a pair or residues from distinct chains that have
1096  a minimal heavy atom distance < 5A. Contacts are specified as
1097  :class:`tuple` with two strings in format:
1098  <cname>.<rnum>.<ins_code>
1099 
1100  :type: :class:`list` of :class:`tuple`
1101  """
1102  if self._native_contacts_native_contacts is None:
1103  self._native_contacts_native_contacts = self.contact_scorercontact_scorer.cent1.hr_contacts
1104  return self._native_contacts_native_contacts
1105 
1106  @property
1107  def model_contacts(self):
1108  """ Same for :attr:`~model`
1109  """
1110  if self._model_contacts_model_contacts is None:
1111  self._model_contacts_model_contacts = self.contact_scorercontact_scorer.cent2.hr_contacts
1112  return self._model_contacts_model_contacts
1113 
1114  @property
1116  """ Same for :attr:`~trimmed_model`
1117  """
1118  if self._trimmed_model_contacts_trimmed_model_contacts is None:
1119  self._trimmed_model_contacts_trimmed_model_contacts = self.trimmed_contact_scorertrimmed_contact_scorer.cent2.hr_contacts
1120  return self._trimmed_model_contacts_trimmed_model_contacts
1121 
1122  @property
1124  """ Interfaces in :class:`target` which have at least one contact
1125 
1126  Contact as defined in :attr:`~native_contacts`,
1127  chain names are lexicographically sorted.
1128 
1129  :type: :class:`list` of :class:`tuple` with 2 elements each
1130  (trg_ch1, trg_ch2)
1131  """
1132  if self._contact_target_interfaces_contact_target_interfaces is None:
1133  tmp = self.contact_scorercontact_scorer.cent1.interacting_chains
1134  tmp = [(min(x[0],x[1]), max(x[0],x[1])) for x in tmp]
1135  self._contact_target_interfaces_contact_target_interfaces = tmp
1136  return self._contact_target_interfaces_contact_target_interfaces
1137 
1138  @property
1140  """ Interfaces in :class:`model` which have at least one contact
1141 
1142  Contact as defined in :attr:`~native_contacts`,
1143  chain names are lexicographically sorted.
1144 
1145  :type: :class:`list` of :class:`tuple` with 2 elements each
1146  (mdl_ch1, mdl_ch2)
1147  """
1148  if self._contact_model_interfaces_contact_model_interfaces is None:
1149  tmp = self.contact_scorercontact_scorer.cent2.interacting_chains
1150  tmp = [(min(x[0],x[1]), max(x[0],x[1])) for x in tmp]
1151  self._contact_model_interfaces_contact_model_interfaces = tmp
1152  return self._contact_model_interfaces_contact_model_interfaces
1153 
1154  @property
1155  def ics_precision(self):
1156  """ Fraction of model contacts that are also present in target
1157 
1158  :type: :class:`float`
1159  """
1160  if self._ics_precision_ics_precision is None:
1161  self._compute_ics_scores_compute_ics_scores()
1162  return self._ics_precision_ics_precision
1163 
1164  @property
1165  def ics_recall(self):
1166  """ Fraction of target contacts that are correctly reproduced in model
1167 
1168  :type: :class:`float`
1169  """
1170  if self._ics_recall_ics_recall is None:
1171  self._compute_ics_scores_compute_ics_scores()
1172  return self._ics_recall_ics_recall
1173 
1174  @property
1175  def ics(self):
1176  """ ICS (Interface Contact Similarity) score
1177 
1178  Combination of :attr:`~ics_precision` and :attr:`~ics_recall`
1179  using the F1-measure
1180 
1181  :type: :class:`float`
1182  """
1183  if self._ics_ics is None:
1184  self._compute_ics_scores_compute_ics_scores()
1185  return self._ics_ics
1186 
1187  @property
1189  """ Per-interface ICS precision
1190 
1191  :attr:`~ics_precision` for each interface in
1192  :attr:`~contact_target_interfaces`
1193 
1194  :type: :class:`list` of :class:`float`
1195  """
1196  if self._per_interface_ics_precision_per_interface_ics_precision is None:
1197  self._compute_ics_scores_compute_ics_scores()
1198  return self._per_interface_ics_precision_per_interface_ics_precision
1199 
1200 
1201  @property
1203  """ Per-interface ICS recall
1204 
1205  :attr:`~ics_recall` for each interface in
1206  :attr:`~contact_target_interfaces`
1207 
1208  :type: :class:`list` of :class:`float`
1209  """
1210  if self._per_interface_ics_recall_per_interface_ics_recall is None:
1211  self._compute_ics_scores_compute_ics_scores()
1212  return self._per_interface_ics_recall_per_interface_ics_recall
1213 
1214  @property
1216  """ Per-interface ICS (Interface Contact Similarity) score
1217 
1218  :attr:`~ics` for each interface in
1219  :attr:`~contact_target_interfaces`
1220 
1221  :type: :class:`float`
1222  """
1223 
1224  if self._per_interface_ics_per_interface_ics is None:
1225  self._compute_ics_scores_compute_ics_scores()
1226  return self._per_interface_ics_per_interface_ics
1227 
1228  @property
1229  def ips_precision(self):
1230  """ Fraction of model interface residues that are also interface
1231  residues in target
1232 
1233  :type: :class:`float`
1234  """
1235  if self._ips_precision_ips_precision is None:
1236  self._compute_ips_scores_compute_ips_scores()
1237  return self._ips_precision_ips_precision
1238 
1239  @property
1240  def ips_recall(self):
1241  """ Fraction of target interface residues that are also interface
1242  residues in model
1243 
1244  :type: :class:`float`
1245  """
1246  if self._ips_recall_ips_recall is None:
1247  self._compute_ips_scores_compute_ips_scores()
1248  return self._ips_recall_ips_recall
1249 
1250  @property
1251  def ips(self):
1252  """ IPS (Interface Patch Similarity) score
1253 
1254  Jaccard coefficient of interface residues in target and their mapped
1255  counterparts in model
1256 
1257  :type: :class:`float`
1258  """
1259  if self._ips_ips is None:
1260  self._compute_ips_scores_compute_ips_scores()
1261  return self._ips_ips
1262 
1263  @property
1264  def ics_trimmed(self):
1265  """ Same as :attr:`~ics` but with trimmed model
1266 
1267  Model is trimmed to residues which can me mapped to target in order
1268  to not penalize contacts in the model for which we have no experimental
1269  evidence.
1270 
1271  :type: :class:`float`
1272  """
1273  if self._ics_trimmed_ics_trimmed is None:
1274  self._compute_ics_scores_trimmed_compute_ics_scores_trimmed()
1275  return self._ics_trimmed_ics_trimmed
1276 
1277  @property
1279  """ Same as :attr:`~ics_precision` but with trimmed model
1280 
1281  Model is trimmed to residues which can me mapped to target in order
1282  to not penalize contacts in the model for which we have no experimental
1283  evidence.
1284 
1285  :type: :class:`float`
1286  """
1287  if self._ics_precision_trimmed_ics_precision_trimmed is None:
1288  self._compute_ics_scores_trimmed_compute_ics_scores_trimmed()
1289  return self._ics_precision_trimmed_ics_precision_trimmed
1290 
1291  @property
1293  """ Same as :attr:`~ics_recall` but with trimmed model
1294 
1295  Model is trimmed to residues which can me mapped to target in order
1296  to not penalize contacts in the model for which we have no experimental
1297  evidence.
1298 
1299  :type: :class:`float`
1300  """
1301  if self._ics_recall_trimmed_ics_recall_trimmed is None:
1302  self._compute_ics_scores_trimmed_compute_ics_scores_trimmed()
1303  return self._ics_recall_trimmed_ics_recall_trimmed
1304 
1305  @property
1307  """ Same as :attr:`~per_interface_ics_precision` but with :attr:`~trimmed_model`
1308 
1309  :attr:`~ics_precision_trimmed` for each interface in
1310  :attr:`~contact_target_interfaces`
1311 
1312  :type: :class:`list` of :class:`float`
1313  """
1314  if self._per_interface_ics_precision_trimmed_per_interface_ics_precision_trimmed is None:
1315  self._compute_ics_scores_trimmed_compute_ics_scores_trimmed()
1316  return self._per_interface_ics_precision_trimmed_per_interface_ics_precision_trimmed
1317 
1318 
1319  @property
1321  """ Same as :attr:`~per_interface_ics_recall` but with :attr:`~trimmed_model`
1322 
1323  :attr:`~ics_recall_trimmed` for each interface in
1324  :attr:`~contact_target_interfaces`
1325 
1326  :type: :class:`list` of :class:`float`
1327  """
1328  if self._per_interface_ics_recall_trimmed_per_interface_ics_recall_trimmed is None:
1329  self._compute_ics_scores_trimmed_compute_ics_scores_trimmed()
1330  return self._per_interface_ics_recall_trimmed_per_interface_ics_recall_trimmed
1331 
1332  @property
1334  """ Same as :attr:`~per_interface_ics` but with :attr:`~trimmed_model`
1335 
1336  :attr:`~ics` for each interface in
1337  :attr:`~contact_target_interfaces`
1338 
1339  :type: :class:`float`
1340  """
1341 
1342  if self._per_interface_ics_trimmed_per_interface_ics_trimmed is None:
1343  self._compute_ics_scores_trimmed_compute_ics_scores_trimmed()
1344  return self._per_interface_ics_trimmed_per_interface_ics_trimmed
1345 
1346  @property
1347  def ips_trimmed(self):
1348  """ Same as :attr:`~ips` but with trimmed model
1349 
1350  Model is trimmed to residues which can me mapped to target in order
1351  to not penalize contacts in the model for which we have no experimental
1352  evidence.
1353 
1354  :type: :class:`float`
1355  """
1356  if self._ips_trimmed_ips_trimmed is None:
1357  self._compute_ips_scores_trimmed_compute_ips_scores_trimmed()
1358  return self._ips_trimmed_ips_trimmed
1359 
1360  @property
1362  """ Same as :attr:`~ips_precision` but with trimmed model
1363 
1364  Model is trimmed to residues which can me mapped to target in order
1365  to not penalize contacts in the model for which we have no experimental
1366  evidence.
1367 
1368  :type: :class:`float`
1369  """
1370  if self._ips_precision_trimmed_ips_precision_trimmed is None:
1371  self._compute_ips_scores_trimmed_compute_ips_scores_trimmed()
1372  return self._ips_precision_trimmed_ips_precision_trimmed
1373 
1374  @property
1376  """ Same as :attr:`~ips_recall` but with trimmed model
1377 
1378  Model is trimmed to residues which can me mapped to target in order
1379  to not penalize contacts in the model for which we have no experimental
1380  evidence.
1381 
1382  :type: :class:`float`
1383  """
1384  if self._ips_recall_trimmed_ips_recall_trimmed is None:
1385  self._compute_ips_scores_trimmed_compute_ips_scores_trimmed()
1386  return self._ips_recall_trimmed_ips_recall_trimmed
1387 
1388  @property
1390  """ Per-interface IPS precision
1391 
1392  :attr:`~ips_precision` for each interface in
1393  :attr:`~contact_target_interfaces`
1394 
1395  :type: :class:`list` of :class:`float`
1396  """
1397  if self._per_interface_ips_precision_per_interface_ips_precision is None:
1398  self._compute_ips_scores_compute_ips_scores()
1399  return self._per_interface_ips_precision_per_interface_ips_precision
1400 
1401  @property
1403  """ Per-interface IPS recall
1404 
1405  :attr:`~ips_recall` for each interface in
1406  :attr:`~contact_target_interfaces`
1407 
1408  :type: :class:`list` of :class:`float`
1409  """
1410  if self._per_interface_ics_recall_per_interface_ics_recall is None:
1411  self._compute_ips_scores_compute_ips_scores()
1412  return self._per_interface_ips_recall_per_interface_ips_recall
1413 
1414  @property
1416  """ Per-interface IPS (Interface Patch Similarity) score
1417 
1418  :attr:`~ips` for each interface in
1419  :attr:`~contact_target_interfaces`
1420 
1421  :type: :class:`list` of :class:`float`
1422  """
1423 
1424  if self._per_interface_ips_per_interface_ips is None:
1425  self._compute_ips_scores_compute_ips_scores()
1426  return self._per_interface_ips_per_interface_ips
1427 
1428  @property
1430  """ Same as :attr:`~per_interface_ips_precision` but with :attr:`~trimmed_model`
1431 
1432  :attr:`~ips_precision_trimmed` for each interface in
1433  :attr:`~contact_target_interfaces`
1434 
1435  :type: :class:`list` of :class:`float`
1436  """
1437  if self._per_interface_ips_precision_trimmed_per_interface_ips_precision_trimmed is None:
1438  self._compute_ips_scores_trimmed_compute_ips_scores_trimmed()
1439  return self._per_interface_ips_precision_trimmed_per_interface_ips_precision_trimmed
1440 
1441 
1442  @property
1444  """ Same as :attr:`~per_interface_ips_recall` but with :attr:`~trimmed_model`
1445 
1446  :attr:`~ics_recall_trimmed` for each interface in
1447  :attr:`~contact_target_interfaces`
1448 
1449  :type: :class:`list` of :class:`float`
1450  """
1451  if self._per_interface_ips_recall_trimmed_per_interface_ips_recall_trimmed is None:
1452  self._compute_ips_scores_trimmed_compute_ips_scores_trimmed()
1453  return self._per_interface_ips_recall_trimmed_per_interface_ips_recall_trimmed
1454 
1455  @property
1457  """ Same as :attr:`~per_interface_ips` but with :attr:`~trimmed_model`
1458 
1459  :attr:`~ics` for each interface in
1460  :attr:`~contact_target_interfaces`
1461 
1462  :type: :class:`float`
1463  """
1464 
1465  if self._per_interface_ips_trimmed_per_interface_ips_trimmed is None:
1466  self._compute_ips_scores_trimmed_compute_ips_scores_trimmed()
1467  return self._per_interface_ips_trimmed_per_interface_ips_trimmed
1468 
1469  @property
1471  """ Interfaces in :attr:`~target` that are relevant for DockQ
1472 
1473  All interfaces in :attr:`~target` with non-zero contacts that are
1474  relevant for DockQ. Includes protein-protein, protein-nucleotide and
1475  nucleotide-nucleotide interfaces. Chain names for each interface are
1476  lexicographically sorted.
1477 
1478  :type: :class:`list` of :class:`tuple` with 2 elements each:
1479  (trg_ch1, trg_ch2)
1480  """
1481  if self._dockq_target_interfaces_dockq_target_interfaces is None:
1482 
1483  # interacting chains are identified with ContactEntity
1484  contact_d = 5.0
1485  if self.dockq_capri_peptidedockq_capri_peptide:
1486  contact_d = 4.0
1487  cent = ContactEntity(self.targettarget, contact_mode = "aa",
1488  contact_d = contact_d)
1489 
1490  # fetch lexicographically sorted interfaces
1491  interfaces = cent.interacting_chains
1492  interfaces = [(min(x[0],x[1]), max(x[0],x[1])) for x in interfaces]
1493 
1494  pep_seqs = set([s.GetName() for s in self.chain_mapperchain_mapper.polypep_seqs])
1495  nuc_seqs = set([s.GetName() for s in self.chain_mapperchain_mapper.polynuc_seqs])
1496 
1497  seqs = pep_seqs.union(nuc_seqs)
1498  self._dockq_target_interfaces_dockq_target_interfaces = list()
1499  for interface in interfaces:
1500  if interface[0] in seqs and interface[1] in seqs:
1501  self._dockq_target_interfaces_dockq_target_interfaces.append(interface)
1502 
1503  return self._dockq_target_interfaces_dockq_target_interfaces
1504 
1505  @property
1506  def dockq_interfaces(self):
1507  """ Interfaces in :attr:`~dockq_target_interfaces` that can be mapped
1508  to model
1509 
1510  Target chain names are lexicographically sorted
1511 
1512  :type: :class:`list` of :class:`tuple` with 4 elements each:
1513  (trg_ch1, trg_ch2, mdl_ch1, mdl_ch2)
1514  """
1515  if self._dockq_interfaces_dockq_interfaces is None:
1516  self._dockq_interfaces_dockq_interfaces = list()
1517  flat_mapping = self.mappingmapping.GetFlatMapping()
1518  for i in self.dockq_target_interfacesdockq_target_interfaces:
1519  if i[0] in flat_mapping and i[1] in flat_mapping:
1520  self._dockq_interfaces_dockq_interfaces.append((i[0], i[1],
1521  flat_mapping[i[0]],
1522  flat_mapping[i[1]]))
1523  return self._dockq_interfaces_dockq_interfaces
1524 
1525  @property
1526  def dockq_scores(self):
1527  """ DockQ scores for interfaces in :attr:`~dockq_interfaces`
1528 
1529  :class:`list` of :class:`float`
1530  """
1531  if self._dockq_scores_dockq_scores is None:
1532  self._compute_dockq_scores_compute_dockq_scores()
1533  return self._dockq_scores_dockq_scores
1534 
1535  @property
1536  def fnat(self):
1537  """ fnat scores for interfaces in :attr:`~dockq_interfaces`
1538 
1539  fnat: Fraction of native contacts that are also present in model
1540 
1541  :class:`list` of :class:`float`
1542  """
1543  if self._fnat_fnat is None:
1544  self._compute_dockq_scores_compute_dockq_scores()
1545  return self._fnat_fnat
1546 
1547  @property
1548  def nnat(self):
1549  """ N native contacts for interfaces in :attr:`~dockq_interfaces`
1550 
1551  :class:`list` of :class:`int`
1552  """
1553  if self._nnat_nnat is None:
1554  self._compute_dockq_scores_compute_dockq_scores()
1555  return self._nnat_nnat
1556 
1557  @property
1558  def nmdl(self):
1559  """ N model contacts for interfaces in :attr:`~dockq_interfaces`
1560 
1561  :class:`list` of :class:`int`
1562  """
1563  if self._nmdl_nmdl is None:
1564  self._compute_dockq_scores_compute_dockq_scores()
1565  return self._nmdl_nmdl
1566 
1567  @property
1568  def fnonnat(self):
1569  """ fnonnat scores for interfaces in :attr:`~dockq_interfaces`
1570 
1571  fnat: Fraction of model contacts that are not present in target
1572 
1573  :class:`list` of :class:`float`
1574  """
1575  if self._fnonnat_fnonnat is None:
1576  self._compute_dockq_scores_compute_dockq_scores()
1577  return self._fnonnat_fnonnat
1578 
1579  @property
1580  def irmsd(self):
1581  """ irmsd scores for interfaces in :attr:`~dockq_interfaces`
1582 
1583  irmsd: RMSD of interface (RMSD computed on backbone atoms) which
1584  consists of each residue that has at least one heavy atom within 10A of
1585  other chain. Backbone atoms for proteins: "CA","C","N","O", for
1586  nucleotides: "P", "OP1", "OP2", "O2'", "O3'", "O4'", "O5'", "C1'",
1587  "C2'", "C3'", "C4'", "C5'".
1588 
1589  :class:`list` of :class:`float`
1590  """
1591  if self._irmsd_irmsd is None:
1592  self._compute_dockq_scores_compute_dockq_scores()
1593  return self._irmsd_irmsd
1594 
1595  @property
1596  def lrmsd(self):
1597  """ lrmsd scores for interfaces in :attr:`~dockq_interfaces`
1598 
1599  lrmsd: The two chains involved in the interface are superposed based on
1600  the receptor (rigid min RMSD superposition) and the ligand RMSD is
1601  reported. Receptor is the chain with more residues. Superposition and
1602  RMSD is computed on same backbone atoms as :attr:`~irmsd`.
1603 
1604  :class:`list` of :class:`float`
1605  """
1606  if self._lrmsd_lrmsd is None:
1607  self._compute_dockq_scores_compute_dockq_scores()
1608  return self._lrmsd_lrmsd
1609 
1610  @property
1611  def dockq_ave(self):
1612  """ Average of DockQ scores in :attr:`~dockq_scores`
1613 
1614  In its original implementation, DockQ only operates on single
1615  interfaces. Thus the requirement to combine scores for higher order
1616  oligomers.
1617 
1618  :type: :class:`float`
1619  """
1620  if self._dockq_ave_dockq_ave is None:
1621  self._compute_dockq_scores_compute_dockq_scores()
1622  return self._dockq_ave_dockq_ave
1623 
1624  @property
1625  def dockq_wave(self):
1626  """ Same as :attr:`~dockq_ave`, weighted by native contacts
1627 
1628  :type: :class:`float`
1629  """
1630  if self._dockq_wave_dockq_wave is None:
1631  self._compute_dockq_scores_compute_dockq_scores()
1632  return self._dockq_wave_dockq_wave
1633 
1634  @property
1635  def dockq_ave_full(self):
1636  """ Same as :attr:`~dockq_ave` but penalizing for missing interfaces
1637 
1638  Interfaces that are not covered in model are added as 0.0
1639  in average computation.
1640 
1641  :type: :class:`float`
1642  """
1643  if self._dockq_ave_full_dockq_ave_full is None:
1644  self._compute_dockq_scores_compute_dockq_scores()
1645  return self._dockq_ave_full_dockq_ave_full
1646 
1647  @property
1648  def dockq_wave_full(self):
1649  """ Same as :attr:`~dockq_ave_full`, but weighted
1650 
1651  Interfaces that are not covered in model are added as 0.0 in
1652  average computations and the respective weights are derived from
1653  number of contacts in respective target interface.
1654  """
1655  if self._dockq_wave_full_dockq_wave_full is None:
1656  self._compute_dockq_scores_compute_dockq_scores()
1657  return self._dockq_wave_full_dockq_wave_full
1658 
1659  @property
1661  """ Mapped representative positions in target
1662 
1663  Thats CA positions for peptide residues and C3' positions for
1664  nucleotides. Has same length as :attr:`~mapped_model_pos` and mapping
1665  is based on :attr:`~mapping`.
1666 
1667  :type: :class:`ost.geom.Vec3List`
1668  """
1669  if self._mapped_target_pos_mapped_target_pos is None:
1670  self._extract_mapped_pos_extract_mapped_pos()
1671  return self._mapped_target_pos_mapped_target_pos
1672 
1673  @property
1675  """ Mapped representative positions in target
1676 
1677  Thats the equivalent of :attr:`~mapped_target_pos` but containing more
1678  backbone atoms (N, CA, C for peptide residues and O5', C5', C4', C3', O3
1679  for nucleotide residues). mapping is based on :attr:`~mapping`.
1680 
1681  :type: :class:`ost.geom.Vec3List`
1682  """
1683  if self._mapped_target_pos_full_bb_mapped_target_pos_full_bb is None:
1684  self._extract_mapped_pos_full_bb_extract_mapped_pos_full_bb()
1685  return self._mapped_target_pos_full_bb_mapped_target_pos_full_bb
1686 
1687  @property
1688  def mapped_model_pos(self):
1689  """ Mapped representative positions in model
1690 
1691  Thats CA positions for peptide residues and C3' positions for
1692  nucleotides. Has same length as :attr:`~mapped_target_pos` and mapping
1693  is based on :attr:`~mapping`.
1694 
1695  :type: :class:`ost.geom.Vec3List`
1696  """
1697  if self._mapped_model_pos_mapped_model_pos is None:
1698  self._extract_mapped_pos_extract_mapped_pos()
1699  return self._mapped_model_pos_mapped_model_pos
1700 
1701  @property
1703  """ Mapped representative positions in model
1704 
1705  Thats the equivalent of :attr:`~mapped_model_pos` but containing more
1706  backbone atoms (N, CA, C for peptide residues and O5', C5', C4', C3', O3
1707  for nucleotide residues). mapping is based on :attr:`~mapping`.
1708 
1709  :type: :class:`ost.geom.Vec3List`
1710  """
1711  if self._mapped_model_pos_full_bb_mapped_model_pos_full_bb is None:
1712  self._extract_mapped_pos_full_bb_extract_mapped_pos_full_bb()
1713  return self._mapped_model_pos_full_bb_mapped_model_pos_full_bb
1714 
1715  @property
1717  """ :attr:`~mapped_model_pos` with :attr:`~transform` applied
1718 
1719  :type: :class:`ost.geom.Vec3List`
1720  """
1721  if self._transformed_mapped_model_pos_transformed_mapped_model_pos is None:
1722  self._transformed_mapped_model_pos_transformed_mapped_model_pos = \
1723  geom.Vec3List(self.mapped_model_posmapped_model_pos)
1724  self._transformed_mapped_model_pos_transformed_mapped_model_pos.ApplyTransform(self.transformtransform)
1725  return self._transformed_mapped_model_pos_transformed_mapped_model_pos
1726 
1727  @property
1729  """ Number of target residues which have no mapping to model
1730 
1731  :type: :class:`int`
1732  """
1733  if self._n_target_not_mapped_n_target_not_mapped is None:
1734  self._extract_mapped_pos_extract_mapped_pos()
1735  return self._n_target_not_mapped_n_target_not_mapped
1736 
1737  @property
1738  def transform(self):
1739  """ Transform: :attr:`~mapped_model_pos` onto :attr:`~mapped_target_pos`
1740 
1741  Computed using Kabsch minimal rmsd algorithm. If number of positions
1742  is too small (< 3), :attr:`~mapped_model_pos_full_bb` and
1743  :attr:`~mapped_target_pos_full_bb` are used.
1744 
1745  :type: :class:`ost.geom.Mat4`
1746  """
1747  if self._transform_transform is None:
1748  if len(self.mapped_model_posmapped_model_pos) < 3:
1749  if len(self.mapped_model_pos_full_bbmapped_model_pos_full_bb) >=3:
1750  res = mol.alg.SuperposeSVD(self.mapped_model_pos_full_bbmapped_model_pos_full_bb,
1751  self.mapped_target_pos_full_bbmapped_target_pos_full_bb)
1752  self._transform_transform = res.transformation
1753  else:
1754  # there is really nothing we can do => set identity matrix
1755  self._transform_transform = geom.Mat4()
1756  else:
1757  res = mol.alg.SuperposeSVD(self.mapped_model_posmapped_model_pos,
1758  self.mapped_target_posmapped_target_pos)
1759  self._transform_transform = res.transformation
1760  return self._transform_transform
1761 
1762  @property
1764  """ Mapped representative positions in target
1765 
1766  Thats CA positions for peptide residues and C3' positions for
1767  nucleotides. Has same length as :attr:`~rigid_mapped_model_pos` and mapping
1768  is based on :attr:`~rigid_mapping`.
1769 
1770  :type: :class:`ost.geom.Vec3List`
1771  """
1772  if self._rigid_mapped_target_pos_rigid_mapped_target_pos is None:
1773  self._extract_rigid_mapped_pos_extract_rigid_mapped_pos()
1774  return self._rigid_mapped_target_pos_rigid_mapped_target_pos
1775 
1776  @property
1778  """ Mapped representative positions in target
1779 
1780  Thats the equivalent of :attr:`~rigid_mapped_target_pos` but containing
1781  more backbone atoms (N, CA, C for peptide residues and O5', C5', C4',
1782  C3', O3 for nucleotide residues). mapping is based on :attr:`~mapping`.
1783 
1784  :type: :class:`ost.geom.Vec3List`
1785  """
1786  if self._rigid_mapped_target_pos_full_bb_rigid_mapped_target_pos_full_bb is None:
1787  self._extract_rigid_mapped_pos_full_bb_extract_rigid_mapped_pos_full_bb()
1788  return self._rigid_mapped_target_pos_full_bb_rigid_mapped_target_pos_full_bb
1789 
1790  @property
1792  """ Mapped representative positions in model
1793 
1794  Thats CA positions for peptide residues and C3' positions for
1795  nucleotides. Has same length as :attr:`~mapped_target_pos` and mapping
1796  is based on :attr:`~rigid_mapping`.
1797 
1798  :type: :class:`ost.geom.Vec3List`
1799  """
1800  if self._rigid_mapped_model_pos_rigid_mapped_model_pos is None:
1801  self._extract_rigid_mapped_pos_extract_rigid_mapped_pos()
1802  return self._rigid_mapped_model_pos_rigid_mapped_model_pos
1803 
1804  @property
1806  """ Mapped representative positions in model
1807 
1808  Thats the equivalent of :attr:`~rigid_mapped_model_pos` but containing
1809  more backbone atoms (N, CA, C for peptide residues and O5', C5', C4',
1810  C3', O3 for nucleotide residues). mapping is based on :attr:`~mapping`.
1811 
1812  :type: :class:`ost.geom.Vec3List`
1813  """
1814  if self._rigid_mapped_model_pos_full_bb_rigid_mapped_model_pos_full_bb is None:
1815  self._extract_rigid_mapped_pos_full_bb_extract_rigid_mapped_pos_full_bb()
1816  return self._rigid_mapped_model_pos_full_bb_rigid_mapped_model_pos_full_bb
1817 
1818  @property
1820  """ :attr:`~rigid_mapped_model_pos` with :attr:`~rigid_transform` applied
1821 
1822  :type: :class:`ost.geom.Vec3List`
1823  """
1824  if self._rigid_transformed_mapped_model_pos_rigid_transformed_mapped_model_pos is None:
1825  self._rigid_transformed_mapped_model_pos_rigid_transformed_mapped_model_pos = \
1826  geom.Vec3List(self.rigid_mapped_model_posrigid_mapped_model_pos)
1827  self._rigid_transformed_mapped_model_pos_rigid_transformed_mapped_model_pos.ApplyTransform(self.rigid_transformrigid_transform)
1828  return self._rigid_transformed_mapped_model_pos_rigid_transformed_mapped_model_pos
1829 
1830  @property
1832  """ Number of target residues which have no rigid mapping to model
1833 
1834  :type: :class:`int`
1835  """
1836  if self._rigid_n_target_not_mapped_rigid_n_target_not_mapped is None:
1837  self._extract_rigid_mapped_pos_extract_rigid_mapped_pos()
1838  return self._rigid_n_target_not_mapped_rigid_n_target_not_mapped
1839 
1840  @property
1841  def rigid_transform(self):
1842  """ Transform: :attr:`~rigid_mapped_model_pos` onto :attr:`~rigid_mapped_target_pos`
1843 
1844  Computed using Kabsch minimal rmsd algorithm. If number of positions
1845  is too small (< 3), :attr:`~rigid_mapped_model_pos_full_bb` and
1846  :attr:`~rigid_mapped_target_pos_full_bb` are used.
1847 
1848  :type: :class:`ost.geom.Mat4`
1849  """
1850  if self._rigid_transform_rigid_transform is None:
1851  if len(self.rigid_mapped_model_posrigid_mapped_model_pos) < 3:
1852  if len(self.rigid_mapped_model_pos_full_bbrigid_mapped_model_pos_full_bb) >= 3:
1853  res = mol.alg.SuperposeSVD(self.rigid_mapped_model_pos_full_bbrigid_mapped_model_pos_full_bb,
1854  self.rigid_mapped_target_pos_full_bbrigid_mapped_target_pos_full_bb)
1855  self._rigid_transform_rigid_transform = res.transformation
1856  else:
1857  # there is really nothing we can do => set identity matrix
1858  self._rigid_transform_rigid_transform = geom.Mat4()
1859  else:
1860  res = mol.alg.SuperposeSVD(self.rigid_mapped_model_posrigid_mapped_model_pos,
1861  self.rigid_mapped_target_posrigid_mapped_target_pos)
1862  self._rigid_transform_rigid_transform = res.transformation
1863  return self._rigid_transform_rigid_transform
1864 
1865  @property
1866  def gdt_05(self):
1867  """ Fraction CA (C3' for nucleotides) that can be superposed within 0.5A
1868 
1869  Uses :attr:`~rigid_mapped_model_pos` and :attr:`~rigid_mapped_target_pos`.
1870  Similar iterative algorithm as LGA tool
1871 
1872  :type: :class:`float`
1873  """
1874  if self._gdt_05_gdt_05 is None:
1875  N = list()
1876  wsizes = self._gdt_window_sizes_gdt_window_sizes + [len(self.rigid_mapped_model_posrigid_mapped_model_pos)]
1877  for window_size in wsizes:
1878  n = GDT(self.rigid_mapped_model_posrigid_mapped_model_pos,
1879  self.rigid_mapped_target_posrigid_mapped_target_pos,
1880  window_size, 1000, 0.5)[0]
1881  N.append(n)
1882  n = max(N)
1883  n_full = len(self.rigid_mapped_target_posrigid_mapped_target_pos) + self.rigid_n_target_not_mappedrigid_n_target_not_mapped
1884  if n_full > 0:
1885  self._gdt_05_gdt_05 = float(n) / n_full
1886  else:
1887  self._gdt_05_gdt_05 = 0.0
1888  return self._gdt_05_gdt_05
1889 
1890  @property
1891  def gdt_1(self):
1892  """ Fraction CA (C3' for nucleotides) that can be superposed within 1.0A
1893 
1894  Uses :attr:`~rigid_mapped_model_pos` and :attr:`~rigid_mapped_target_pos`.
1895  Similar iterative algorithm as LGA tool
1896 
1897  :type: :class:`float`
1898  """
1899  if self._gdt_1_gdt_1 is None:
1900  N = list()
1901  wsizes = self._gdt_window_sizes_gdt_window_sizes + [len(self.rigid_mapped_model_posrigid_mapped_model_pos)]
1902  for window_size in wsizes:
1903  n = GDT(self.rigid_mapped_model_posrigid_mapped_model_pos,
1904  self.rigid_mapped_target_posrigid_mapped_target_pos,
1905  window_size, 1000, 1.0)[0]
1906  N.append(n)
1907  n = max(N)
1908  n_full = len(self.rigid_mapped_target_posrigid_mapped_target_pos) + self.rigid_n_target_not_mappedrigid_n_target_not_mapped
1909  if n_full > 0:
1910  self._gdt_1_gdt_1 = float(n) / n_full
1911  else:
1912  self._gdt_1_gdt_1 = 0.0
1913  return self._gdt_1_gdt_1
1914 
1915  @property
1916  def gdt_2(self):
1917  """ Fraction CA (C3' for nucleotides) that can be superposed within 2.0A
1918 
1919  Uses :attr:`~rigid_mapped_model_pos` and :attr:`~rigid_mapped_target_pos`.
1920  Similar iterative algorithm as LGA tool
1921 
1922 
1923  :type: :class:`float`
1924  """
1925  if self._gdt_2_gdt_2 is None:
1926  N = list()
1927  wsizes = self._gdt_window_sizes_gdt_window_sizes + [len(self.rigid_mapped_model_posrigid_mapped_model_pos)]
1928  for window_size in wsizes:
1929  n = GDT(self.rigid_mapped_model_posrigid_mapped_model_pos,
1930  self.rigid_mapped_target_posrigid_mapped_target_pos,
1931  window_size, 1000, 2.0)[0]
1932  N.append(n)
1933  n = max(N)
1934  n_full = len(self.rigid_mapped_target_posrigid_mapped_target_pos) + self.rigid_n_target_not_mappedrigid_n_target_not_mapped
1935  if n_full > 0:
1936  self._gdt_2_gdt_2 = float(n) / n_full
1937  else:
1938  self._gdt_2_gdt_2 = 0.0
1939  return self._gdt_2_gdt_2
1940 
1941  @property
1942  def gdt_4(self):
1943  """ Fraction CA (C3' for nucleotides) that can be superposed within 4.0A
1944 
1945  Uses :attr:`~rigid_mapped_model_pos` and :attr:`~rigid_mapped_target_pos`.
1946  Similar iterative algorithm as LGA tool
1947 
1948  :type: :class:`float`
1949  """
1950  if self._gdt_4_gdt_4 is None:
1951  N = list()
1952  wsizes = self._gdt_window_sizes_gdt_window_sizes + [len(self.rigid_mapped_model_posrigid_mapped_model_pos)]
1953  for window_size in wsizes:
1954  n = GDT(self.rigid_mapped_model_posrigid_mapped_model_pos,
1955  self.rigid_mapped_target_posrigid_mapped_target_pos,
1956  window_size, 1000, 4.0)[0]
1957  N.append(n)
1958  n = max(N)
1959  n_full = len(self.rigid_mapped_target_posrigid_mapped_target_pos) + self.rigid_n_target_not_mappedrigid_n_target_not_mapped
1960  if n_full > 0:
1961  self._gdt_4_gdt_4 = float(n) / n_full
1962  else:
1963  self._gdt_4_gdt_4 = 0.0
1964  return self._gdt_4_gdt_4
1965 
1966  @property
1967  def gdt_8(self):
1968  """ Fraction CA (C3' for nucleotides) that can be superposed within 8.0A
1969 
1970  Similar iterative algorithm as LGA tool
1971 
1972  :type: :class:`float`
1973  """
1974  if self._gdt_8_gdt_8 is None:
1975  N = list()
1976  wsizes = self._gdt_window_sizes_gdt_window_sizes + [len(self.rigid_mapped_model_posrigid_mapped_model_pos)]
1977  for window_size in wsizes:
1978  n = GDT(self.rigid_mapped_model_posrigid_mapped_model_pos,
1979  self.rigid_mapped_target_posrigid_mapped_target_pos,
1980  window_size, 1000, 8.0)[0]
1981  N.append(n)
1982  n = max(N)
1983  n_full = len(self.rigid_mapped_target_posrigid_mapped_target_pos) + self.rigid_n_target_not_mappedrigid_n_target_not_mapped
1984  if n_full > 0:
1985  self._gdt_8_gdt_8 = float(n) / n_full
1986  else:
1987  self._gdt_8_gdt_8 = 0.0
1988  return self._gdt_8_gdt_8
1989 
1990 
1991  @property
1992  def gdtts(self):
1993  """ avg GDT with thresholds: 8.0A, 4.0A, 2.0A and 1.0A
1994 
1995  :type: :class:`float`
1996  """
1997  if self._gdtts_gdtts is None:
1998  LogScript("Computing GDT-TS score")
1999  self._gdtts_gdtts = (self.gdt_1gdt_1 + self.gdt_2gdt_2 + self.gdt_4gdt_4 + self.gdt_8gdt_8) / 4
2000  return self._gdtts_gdtts
2001 
2002  @property
2003  def gdtha(self):
2004  """ avg GDT with thresholds: 4.0A, 2.0A, 1.0A and 0.5A
2005 
2006  :type: :class:`float`
2007  """
2008  if self._gdtha_gdtha is None:
2009  LogScript("Computing GDT-HA score")
2010  self._gdtha_gdtha = (self.gdt_05gdt_05 + self.gdt_1gdt_1 + self.gdt_2gdt_2 + self.gdt_4gdt_4) / 4
2011  return self._gdtha_gdtha
2012 
2013  @property
2014  def rmsd(self):
2015  """ RMSD
2016 
2017  Computed on :attr:`~rigid_transformed_mapped_model_pos` and
2018  :attr:`~rigid_mapped_target_pos`
2019 
2020  :type: :class:`float`
2021  """
2022  if self._rmsd_rmsd is None:
2023  LogScript("Computing RMSD")
2024  self._rmsd_rmsd = \
2025  self.rigid_mapped_target_posrigid_mapped_target_pos.GetRMSD(self.rigid_transformed_mapped_model_posrigid_transformed_mapped_model_pos)
2026  return self._rmsd_rmsd
2027 
2028  @property
2029  def cad_score(self):
2030  """ The global CAD atom-atom (AA) score
2031 
2032  Computed based on :attr:`~model`. In case of oligomers, :attr:`~mapping`
2033  is used.
2034 
2035  :type: :class:`float`
2036  """
2037  if self._cad_score_cad_score is None:
2038  self._compute_cad_score_compute_cad_score()
2039  return self._cad_score_cad_score
2040 
2041  @property
2042  def local_cad_score(self):
2043  """ The per-residue CAD atom-atom (AA) scores
2044 
2045  Computed based on :attr:`~model`. In case of oligomers, :attr:`~mapping`
2046  is used.
2047 
2048  :type: :class:`dict`
2049  """
2050  if self._local_cad_score_local_cad_score is None:
2051  self._compute_cad_score_compute_cad_score()
2052  return self._local_cad_score_local_cad_score
2053 
2054  @property
2055  def patch_qs(self):
2056  """ Patch QS-scores for each residue in :attr:`~model_interface_residues`
2057 
2058  Representative patches for each residue r in chain c are computed as
2059  follows:
2060 
2061  * mdl_patch_one: All residues in c with CB (CA for GLY) positions within
2062  8A of r and within 12A of residues from any other chain.
2063  * mdl_patch_two: Closest residue x to r in any other chain gets
2064  identified. Patch is then constructed by selecting all residues from
2065  any other chain within 8A of x and within 12A from any residue in c.
2066  * trg_patch_one: Chain name and residue number based mapping from
2067  mdl_patch_one
2068  * trg_patch_two: Chain name and residue number based mapping from
2069  mdl_patch_two
2070 
2071  Results are stored in the same manner as
2072  :attr:`~model_interface_residues`, with corresponding scores instead of
2073  residue numbers. Scores for residues which are not
2074  :class:`mol.ChemType.AMINOACIDS` are set to None. Additionally,
2075  interface patches are derived from :attr:`~model`. If they contain
2076  residues which are not covered by :attr:`~target`, the score is set to
2077  None too.
2078 
2079  :type: :class:`dict` with chain names as key and and :class:`list`
2080  with scores of the respective interface residues.
2081  """
2082  if self._patch_qs_patch_qs is None:
2083  self._compute_patchqs_scores_compute_patchqs_scores()
2084  return self._patch_qs_patch_qs
2085 
2086  @property
2087  def patch_dockq(self):
2088  """ Same as :attr:`~patch_qs` but for DockQ scores
2089  """
2090  if self._patch_dockq_patch_dockq is None:
2091  self._compute_patchdockq_scores_compute_patchdockq_scores()
2092  return self._patch_dockq_patch_dockq
2093 
2094  @property
2095  def tm_score(self):
2096  """ TM-score computed with USalign
2097 
2098  USalign executable can be specified with usalign_exec kwarg at Scorer
2099  construction, an OpenStructure internal copy of the USalign code is
2100  used otherwise.
2101 
2102  :type: :class:`float`
2103  """
2104  if self._tm_score_tm_score is None:
2105  self._compute_tmscore_compute_tmscore()
2106  return self._tm_score_tm_score
2107 
2108  @property
2109  def usalign_mapping(self):
2110  """ Mapping computed with USalign
2111 
2112  Dictionary with target chain names as key and model chain names as
2113  values. No guarantee that all chains are mapped. USalign executable
2114  can be specified with usalign_exec kwarg at Scorer construction, an
2115  OpenStructure internal copy of the USalign code is used otherwise.
2116 
2117  :type: :class:`dict`
2118  """
2119  if self._usalign_mapping_usalign_mapping is None:
2120  self._compute_tmscore_compute_tmscore()
2121  return self._usalign_mapping_usalign_mapping
2122 
2123  def _aln_helper(self, target, model):
2124  # perform required alignments - cannot take the alignments from the
2125  # mapping results as we potentially remove stuff there as compared
2126  # to self.model and self.target
2127  trg_seqs = dict()
2128  for ch in target.chains:
2129  cname = ch.GetName()
2130  s = ''.join([r.one_letter_code for r in ch.residues])
2131  s = seq.CreateSequence(ch.GetName(), s)
2132  trg_seqs[ch.GetName()] = s
2133  mdl_seqs = dict()
2134  for ch in model.chains:
2135  cname = ch.GetName()
2136  s = ''.join([r.one_letter_code for r in ch.residues])
2137  s = seq.CreateSequence(cname, s)
2138  mdl_seqs[ch.GetName()] = s
2139 
2140  alns = list()
2141  trg_pep_chains = [s.GetName() for s in self.chain_mapperchain_mapper.polypep_seqs]
2142  trg_nuc_chains = [s.GetName() for s in self.chain_mapperchain_mapper.polynuc_seqs]
2143  trg_pep_chains = set(trg_pep_chains)
2144  trg_nuc_chains = set(trg_nuc_chains)
2145  for trg_ch, mdl_ch in self.mappingmapping.GetFlatMapping().items():
2146  if mdl_ch in mdl_seqs and trg_ch in trg_seqs:
2147  if trg_ch in trg_pep_chains:
2148  stype = mol.ChemType.AMINOACIDS
2149  elif trg_ch in trg_nuc_chains:
2150  stype = mol.ChemType.NUCLEOTIDES
2151  else:
2152  raise RuntimeError("Chain name inconsistency... ask "
2153  "Gabriel")
2154  if self.resnum_alignmentsresnum_alignments:
2155  aln = self.chain_mapperchain_mapper.ResNumAlign(trg_seqs[trg_ch],
2156  mdl_seqs[mdl_ch],
2157  target, model)
2158  else:
2159  aln = self.chain_mapperchain_mapper.NWAlign(trg_seqs[trg_ch],
2160  mdl_seqs[mdl_ch],
2161  stype)
2162 
2163  alns.append(aln)
2164  return alns
2165 
2166  def _compute_aln(self):
2167  self._aln_aln = self._aln_helper_aln_helper(self.targettarget, self.modelmodel)
2168 
2169  def _compute_stereochecked_aln(self):
2170  # lets not redo the alignment and derive it from self.aln
2171  alns = list()
2172  for a in self.alnaln:
2173  trg_s = a.GetSequence(0)
2174  mdl_s = a.GetSequence(1)
2175  trg_ch = self.targettarget.FindChain(trg_s.name)
2176  mdl_ch = self.modelmodel.FindChain(mdl_s.name)
2177 
2178  sc_trg_olc = ['-'] * len(trg_s)
2179  sc_mdl_olc = ['-'] * len(mdl_s)
2180 
2181  sc_trg_ch = self.stereochecked_targetstereochecked_target.FindChain(trg_s.name)
2182  if sc_trg_ch.IsValid():
2183  # there is the theoretical possibility that the full chain
2184  # has been removed in stereochemistry checks...
2185  trg_residues = trg_ch.residues
2186  res_idx = 0
2187  for olc_idx, olc in enumerate(trg_s):
2188  if olc != '-':
2189  r = trg_residues[res_idx]
2190  sc_r = sc_trg_ch.FindResidue(r.GetNumber())
2191  if sc_r.IsValid():
2192  sc_trg_olc[olc_idx] = sc_r.one_letter_code
2193  res_idx += 1
2194 
2195  sc_mdl_ch = self.stereochecked_modelstereochecked_model.FindChain(mdl_s.name)
2196  if sc_mdl_ch.IsValid():
2197  # there is the theoretical possibility that the full chain
2198  # has been removed in stereochemistry checks...
2199  mdl_residues = mdl_ch.residues
2200  res_idx = 0
2201  for olc_idx, olc in enumerate(mdl_s):
2202  if olc != '-':
2203  r = mdl_residues[res_idx]
2204  sc_r = sc_mdl_ch.FindResidue(r.GetNumber())
2205  if sc_r.IsValid():
2206  sc_mdl_olc[olc_idx] = sc_r.one_letter_code
2207  res_idx += 1
2208 
2209  sc_trg_s = seq.CreateSequence(trg_s.name, ''.join(sc_trg_olc))
2210  sc_mdl_s = seq.CreateSequence(mdl_s.name, ''.join(sc_mdl_olc))
2211  new_a = seq.CreateAlignment()
2212  new_a.AddSequence(sc_trg_s)
2213  new_a.AddSequence(sc_mdl_s)
2214  alns.append(new_a)
2215 
2216  self._stereochecked_aln_stereochecked_aln = alns
2217 
2218  def _compute_pepnuc_aln(self):
2219  self._pepnuc_aln_pepnuc_aln = self._aln_helper_aln_helper(self.pepnuc_targetpepnuc_target,
2220  self.pepnuc_modelpepnuc_model)
2221 
2222  def _compute_lddt(self):
2223  LogScript("Computing all-atom LDDT")
2224  # LDDT requires a flat mapping with mdl_ch as key and trg_ch as value
2225  flat_mapping = self.mappingmapping.GetFlatMapping(mdl_as_key=True)
2226 
2227  # make alignments accessible by mdl seq name
2228  alns = dict()
2229  for aln in self.alnaln:
2230  mdl_seq = aln.GetSequence(1)
2231  alns[mdl_seq.name] = aln
2232 
2233  # score variables to be set
2234  lddt_score = None
2235  local_lddt = None
2236  aa_local_lddt = None
2237 
2238  if self.lddt_no_stereocheckslddt_no_stereochecks:
2239  lddt_chain_mapping = dict()
2240  for mdl_ch, trg_ch in flat_mapping.items():
2241  if mdl_ch in alns:
2242  lddt_chain_mapping[mdl_ch] = trg_ch
2243  lddt_score = self.lddt_scorerlddt_scorer.lDDT(self.modelmodel,
2244  chain_mapping = lddt_chain_mapping,
2245  residue_mapping = alns,
2246  check_resnames=False,
2247  local_lddt_prop="lddt",
2248  add_mdl_contacts = self.lddt_add_mdl_contactslddt_add_mdl_contacts,
2249  set_atom_props=True)[0]
2250  local_lddt = dict()
2251  aa_local_lddt = dict()
2252  for r in self.modelmodel.residues:
2253 
2254  cname = r.GetChain().GetName()
2255  if cname not in local_lddt:
2256  local_lddt[cname] = dict()
2257  aa_local_lddt[cname] = dict()
2258 
2259  rnum = r.GetNumber()
2260  if rnum not in aa_local_lddt[cname]:
2261  aa_local_lddt[cname][rnum] = dict()
2262 
2263  if r.HasProp("lddt"):
2264  score = round(r.GetFloatProp("lddt"), 3)
2265  local_lddt[cname][rnum] = score
2266  else:
2267  # not covered by trg or skipped in chain mapping procedure
2268  # the latter happens if its part of a super short chain
2269  local_lddt[cname][rnum] = None
2270 
2271  for a in r.atoms:
2272  if a.HasProp("lddt"):
2273  score = round(a.GetFloatProp("lddt"), 3)
2274  aa_local_lddt[cname][rnum][a.GetName()] = score
2275  else:
2276  # not covered by trg or skipped in chain mapping
2277  # procedure the latter happens if its part of a
2278  # super short chain
2279  aa_local_lddt[cname][rnum][a.GetName()] = None
2280 
2281 
2282  else:
2283  # keep track what chains we have in the stereochecked model
2284  # there might be really wild cases where a full model
2285  # chain is removed in the stereochemistry checks.
2286  # We need to adapt lddt chain mapping in these cases
2287  mdl_chains = set([ch.name for ch in self.stereochecked_modelstereochecked_model.chains])
2288 
2289  # make alignments accessible by mdl seq name
2290  stereochecked_alns = dict()
2291  for aln in self.stereochecked_alnstereochecked_aln:
2292  mdl_seq = aln.GetSequence(1)
2293  if mdl_seq.GetName() in mdl_chains:
2294  stereochecked_alns[mdl_seq.name] = aln
2295 
2296  lddt_chain_mapping = dict()
2297  for mdl_ch, trg_ch in flat_mapping.items():
2298  if mdl_ch in stereochecked_alns:
2299  lddt_chain_mapping[mdl_ch] = trg_ch
2300 
2301 
2302  lddt_score = self.lddt_scorerlddt_scorer.lDDT(self.stereochecked_modelstereochecked_model,
2303  chain_mapping = lddt_chain_mapping,
2304  residue_mapping = stereochecked_alns,
2305  check_resnames=False,
2306  local_lddt_prop="lddt",
2307  add_mdl_contacts = self.lddt_add_mdl_contactslddt_add_mdl_contacts,
2308  set_atom_props=True)[0]
2309  local_lddt = dict()
2310  aa_local_lddt = dict()
2311  for r in self.modelmodel.residues:
2312 
2313  cname = r.GetChain().GetName()
2314  if cname not in local_lddt:
2315  local_lddt[cname] = dict()
2316  aa_local_lddt[cname] = dict()
2317 
2318  rnum = r.GetNumber()
2319  if rnum not in aa_local_lddt[cname]:
2320  aa_local_lddt[cname][rnum] = dict()
2321 
2322  if r.HasProp("lddt"):
2323  score = round(r.GetFloatProp("lddt"), 3)
2324  local_lddt[cname][rnum] = score
2325 
2326  trg_r = None # represents stereochecked trg res
2327  mdl_r = None # represents stereochecked mdl res
2328 
2329  for a in r.atoms:
2330  if a.HasProp("lddt"):
2331  score = round(a.GetFloatProp("lddt"), 3)
2332  aa_local_lddt[cname][rnum][a.GetName()] = score
2333  else:
2334  # the target residue is there since we have a score
2335  # for the residue.
2336  # opt 1: The atom was never there in the
2337  # stereochecked target => None
2338  # opt 2: The atom has been removed in the model
2339  # stereochecks but is there in stereochecked
2340  # target => 0.0
2341  if trg_r is None:
2342  # let's first see if we find that target residue
2343  # in the non-stereochecked target
2344  tmp = None
2345  if cname in flat_mapping:
2346  for x, y in _GetAlignedResidues(alns[cname],
2347  self.targettarget,
2348  self.modelmodel):
2349  if y.number == r.number:
2350  tmp = x
2351  break
2352  if tmp is not None:
2353  # we have it in the non-stereochecked target!
2354  tmp_cname = tmp.GetChain().GetName()
2355  tmp_rnum = tmp.GetNumber()
2356  tmp = self.stereochecked_targetstereochecked_target.FindResidue(tmp_cname,
2357  tmp_rnum)
2358  if tmp.IsValid():
2359  # And it's there in the stereochecked target too!
2360  trg_r = tmp
2361 
2362  if mdl_r is None:
2363  tmp = self.stereochecked_modelstereochecked_model.FindResidue(cname, rnum)
2364  if tmp.IsValid():
2365  mdl_r = tmp
2366 
2367  if trg_r is None:
2368  # opt 1 - the whole target residue is not there
2369  # this is actually an impossibility, as we have
2370  # a score for the full mdl residue set
2371  aa_local_lddt[cname][rnum][a.GetName()] = None
2372  elif trg_r is not None and not trg_r.FindAtom(a.GetName()).IsValid():
2373  # opt 1 - the target residue is there but not the atom
2374  aa_local_lddt[cname][rnum][a.GetName()] = None
2375  elif trg_r is not None and trg_r.FindAtom(a.GetName()).IsValid() and \
2376  mdl_r is None:
2377  # opt 2 - trg atom is there but full model residue is removed
2378  # this is actuall an impossibility, as we have
2379  # a score for the full mdl residue set
2380  aa_local_lddt[cname][rnum][a.GetName()] = 0.0
2381  elif trg_r is not None and trg_r.FindAtom(a.GetName()).IsValid() and \
2382  mdl_r is not None and not mdl_r.FindAtom(a.GetName()).IsValid():
2383  # opt 2 - trg atom is there but model atom is removed
2384  aa_local_lddt[cname][rnum][a.GetName()] = 0.0
2385  else:
2386  # unknown issue
2387  aa_local_lddt[cname][rnum][a.GetName()] = None
2388 
2389  else:
2390  mdl_res = self.stereochecked_modelstereochecked_model.FindResidue(cname, rnum)
2391  if mdl_res.IsValid():
2392  # not covered by trg or skipped in chain mapping procedure
2393  # the latter happens if its part of a super short chain
2394  local_lddt[cname][rnum] = None
2395  for a in r.atoms:
2396  aa_local_lddt[cname][rnum][a.GetName()] = None
2397  else:
2398  # opt 1: removed by stereochecks => assign 0.0
2399  # opt 2: removed by stereochecks AND not covered by ref
2400  # => assign None
2401 
2402  # fetch trg residue from non-stereochecked aln
2403  trg_r = None
2404  if cname in flat_mapping:
2405  tmp = None
2406  for x, y in _GetAlignedResidues(alns[cname],
2407  self.targettarget,
2408  self.modelmodel):
2409  if y.number == r.number:
2410  tmp = x
2411  break
2412  if tmp is not None:
2413  # we have it in the non-stereochecked target!
2414  tmp_cname = tmp.GetChain().GetName()
2415  tmp_rnum = tmp.GetNumber()
2416  tmp = self.stereochecked_targetstereochecked_target.FindResidue(tmp_cname,
2417  tmp_rnum)
2418  if tmp.IsValid():
2419  # And it's there in the stereochecked target too!
2420  trg_r = tmp
2421 
2422  if trg_r is None:
2423  local_lddt[cname][rnum] = None
2424  for a in r.atoms:
2425  aa_local_lddt[cname][rnum][a.GetName()] = None
2426  else:
2427  local_lddt[cname][rnum] = 0.0
2428  for a in r.atoms:
2429  if trg_r.FindAtom(a.GetName()).IsValid():
2430  aa_local_lddt[cname][rnum][a.GetName()] = 0.0
2431  else:
2432  aa_local_lddt[cname][rnum][a.GetName()] = None
2433 
2434  self._lddt_lddt = lddt_score
2435  self._local_lddt_local_lddt = local_lddt
2436  self._aa_local_lddt_aa_local_lddt = aa_local_lddt
2437 
2438  def _compute_bb_lddt(self):
2439  LogScript("Computing backbone LDDT")
2440  # make alignments accessible by mdl seq name
2441  alns = dict()
2442  for aln in self.alnaln:
2443  mdl_seq = aln.GetSequence(1)
2444  alns[mdl_seq.name] = aln
2445 
2446  # LDDT requires a flat mapping with mdl_ch as key and trg_ch as value
2447  flat_mapping = self.mappingmapping.GetFlatMapping(mdl_as_key=True)
2448  lddt_chain_mapping = dict()
2449  for mdl_ch, trg_ch in flat_mapping.items():
2450  if mdl_ch in alns:
2451  lddt_chain_mapping[mdl_ch] = trg_ch
2452 
2453  lddt_score = self.bb_lddt_scorerbb_lddt_scorer.lDDT(self.modelmodel,
2454  chain_mapping = lddt_chain_mapping,
2455  residue_mapping = alns,
2456  check_resnames=False,
2457  local_lddt_prop="bb_lddt",
2458  add_mdl_contacts = self.lddt_add_mdl_contactslddt_add_mdl_contacts)[0]
2459  local_lddt = dict()
2460  for r in self.modelmodel.residues:
2461  cname = r.GetChain().GetName()
2462  if cname not in local_lddt:
2463  local_lddt[cname] = dict()
2464  if r.HasProp("bb_lddt"):
2465  score = round(r.GetFloatProp("bb_lddt"), 3)
2466  local_lddt[cname][r.GetNumber()] = score
2467  else:
2468  # not covered by trg or skipped in chain mapping procedure
2469  # the latter happens if its part of a super short chain
2470  local_lddt[cname][r.GetNumber()] = None
2471 
2472  self._bb_lddt_bb_lddt = lddt_score
2473  self._bb_local_lddt_bb_local_lddt = local_lddt
2474 
2475  def _compute_ilddt(self):
2476  LogScript("Computing all-atom iLDDT")
2477  # LDDT requires a flat mapping with mdl_ch as key and trg_ch as value
2478  flat_mapping = self.mappingmapping.GetFlatMapping(mdl_as_key=True)
2479 
2480  if self.lddt_no_stereocheckslddt_no_stereochecks:
2481  alns = dict()
2482  for aln in self.alnaln:
2483  mdl_seq = aln.GetSequence(1)
2484  alns[mdl_seq.name] = aln
2485  lddt_chain_mapping = dict()
2486  for mdl_ch, trg_ch in flat_mapping.items():
2487  if mdl_ch in alns:
2488  lddt_chain_mapping[mdl_ch] = trg_ch
2489  self._ilddt_ilddt = self.lddt_scorerlddt_scorer.lDDT(self.modelmodel,
2490  chain_mapping = lddt_chain_mapping,
2491  residue_mapping = alns,
2492  check_resnames=False,
2493  local_lddt_prop="lddt",
2494  add_mdl_contacts = self.lddt_add_mdl_contactslddt_add_mdl_contacts,
2495  no_intrachain=True)[0]
2496  else:
2497 
2498  # keep track what chains we have in the stereochecked model
2499  # there might be really wild cases where a full model
2500  # chain is removed in the stereochemistry checks.
2501  # We need to adapt lddt chain mapping in these cases
2502  mdl_chains = set([ch.name for ch in self.stereochecked_modelstereochecked_model.chains])
2503 
2504  # make alignments accessible by mdl seq name
2505  stereochecked_alns = dict()
2506  for aln in self.stereochecked_alnstereochecked_aln:
2507  mdl_seq = aln.GetSequence(1)
2508  if mdl_seq.GetName() in mdl_chains:
2509  stereochecked_alns[mdl_seq.name] = aln
2510 
2511  lddt_chain_mapping = dict()
2512  for mdl_ch, trg_ch in flat_mapping.items():
2513  if mdl_ch in stereochecked_alns:
2514  lddt_chain_mapping[mdl_ch] = trg_ch
2515 
2516  self._ilddt_ilddt = self.lddt_scorerlddt_scorer.lDDT(self.stereochecked_modelstereochecked_model,
2517  chain_mapping = lddt_chain_mapping,
2518  residue_mapping = stereochecked_alns,
2519  check_resnames=False,
2520  local_lddt_prop="lddt",
2521  add_mdl_contacts = self.lddt_add_mdl_contactslddt_add_mdl_contacts,
2522  no_intrachain=True)[0]
2523 
2524 
2525  def _compute_qs(self):
2526  LogScript("Computing global QS-score")
2527  qs_score_result = self.qs_scorerqs_scorer.Score(self.mappingmapping.mapping)
2528  self._qs_global_qs_global = qs_score_result.QS_global
2529  self._qs_best_qs_best = qs_score_result.QS_best
2530 
2531  def _compute_per_interface_qs_scores(self):
2532  LogScript("Computing per-interface QS-score")
2533  self._per_interface_qs_global_per_interface_qs_global = list()
2534  self._per_interface_qs_best_per_interface_qs_best = list()
2535 
2536  for interface in self.qs_interfacesqs_interfaces:
2537  trg_ch1 = interface[0]
2538  trg_ch2 = interface[1]
2539  mdl_ch1 = interface[2]
2540  mdl_ch2 = interface[3]
2541  qs_res = self.qs_scorerqs_scorer.ScoreInterface(trg_ch1, trg_ch2,
2542  mdl_ch1, mdl_ch2)
2543  self._per_interface_qs_best_per_interface_qs_best.append(qs_res.QS_best)
2544  self._per_interface_qs_global_per_interface_qs_global.append(qs_res.QS_global)
2545 
2546  def _compute_ics_scores(self):
2547  LogScript("Computing ICS scores")
2548  contact_scorer_res = self.contact_scorercontact_scorer.ScoreICS(self.mappingmapping.mapping)
2549  self._ics_precision_ics_precision = contact_scorer_res.precision
2550  self._ics_recall_ics_recall = contact_scorer_res.recall
2551  self._ics_ics = contact_scorer_res.ics
2552  self._per_interface_ics_precision_per_interface_ics_precision = list()
2553  self._per_interface_ics_recall_per_interface_ics_recall = list()
2554  self._per_interface_ics_per_interface_ics = list()
2555  flat_mapping = self.mappingmapping.GetFlatMapping()
2556  for trg_int in self.contact_target_interfacescontact_target_interfaces:
2557  trg_ch1 = trg_int[0]
2558  trg_ch2 = trg_int[1]
2559  if trg_ch1 in flat_mapping and trg_ch2 in flat_mapping:
2560  mdl_ch1 = flat_mapping[trg_ch1]
2561  mdl_ch2 = flat_mapping[trg_ch2]
2562  res = self.contact_scorercontact_scorer.ScoreICSInterface(trg_ch1, trg_ch2,
2563  mdl_ch1, mdl_ch2)
2564  self._per_interface_ics_precision_per_interface_ics_precision.append(res.precision)
2565  self._per_interface_ics_recall_per_interface_ics_recall.append(res.recall)
2566  self._per_interface_ics_per_interface_ics.append(res.ics)
2567  else:
2568  self._per_interface_ics_precision_per_interface_ics_precision.append(None)
2569  self._per_interface_ics_recall_per_interface_ics_recall.append(None)
2570  self._per_interface_ics_per_interface_ics.append(None)
2571 
2572  def _trim_model(self):
2573  trimmed_mdl = mol.CreateEntityFromView(self.mappingmapping.model, True)
2574  trimmed_aln = dict()
2575 
2576  for trg_cname, mdl_cname in self.mappingmapping.GetFlatMapping().items():
2577  mdl_ch = trimmed_mdl.FindChain(mdl_cname)
2578  aln = self.mappingmapping.alns[(trg_cname, mdl_cname)]
2579 
2580  # some limited test that stuff matches
2581  assert(mdl_ch.GetResidueCount() == \
2582  len(aln.GetSequence(1).GetGaplessString()))
2583 
2584  mdl_residues = mdl_ch.residues
2585  mdl_res_idx = 0
2586  aligned_mdl_seq = ['-'] * aln.GetLength()
2587  for col_idx, col in enumerate(aln):
2588  if col[0] != '-' and col[1] != '-':
2589  mdl_res = mdl_residues[mdl_res_idx]
2590  mdl_res.SetIntProp("aligned", 1)
2591  aligned_mdl_seq[col_idx] = col[1]
2592  if col[1] != '-':
2593  mdl_res_idx += 1
2594  aligned_mdl_seq = ''.join(aligned_mdl_seq)
2595  aligned_mdl_seq = seq.CreateSequence(mdl_cname, aligned_mdl_seq)
2596 
2597  new_aln = seq.CreateAlignment()
2598  new_aln.AddSequence(aln.GetSequence(0))
2599  new_aln.AddSequence(aligned_mdl_seq)
2600  trimmed_aln[(trg_cname, mdl_cname)] = new_aln
2601 
2602  self._trimmed_model_trimmed_model = trimmed_mdl.Select("graligned:0=1")
2603  self._trimmed_aln_trimmed_aln = trimmed_aln
2604 
2605  def _compute_ics_scores_trimmed(self):
2606  LogScript("Computing ICS scores trimmed")
2607 
2608  # this is an ugly hack without any efficiency in mind
2609  # we're simply taking the entities from mapper and construct
2610  # a new contact scorer from scratch
2611 
2612  contact_scorer_res = self.trimmed_contact_scorertrimmed_contact_scorer.ScoreICS(self.mappingmapping.mapping)
2613  self._ics_trimmed_ics_trimmed = contact_scorer_res.ics
2614  self._ics_precision_trimmed_ics_precision_trimmed = contact_scorer_res.precision
2615  self._ics_recall_trimmed_ics_recall_trimmed = contact_scorer_res.recall
2616 
2617  self._per_interface_ics_precision_trimmed_per_interface_ics_precision_trimmed = list()
2618  self._per_interface_ics_recall_trimmed_per_interface_ics_recall_trimmed = list()
2619  self._per_interface_ics_trimmed_per_interface_ics_trimmed = list()
2620  flat_mapping = self.mappingmapping.GetFlatMapping()
2621  for trg_int in self.contact_target_interfacescontact_target_interfaces:
2622  trg_ch1 = trg_int[0]
2623  trg_ch2 = trg_int[1]
2624  if trg_ch1 in flat_mapping and trg_ch2 in flat_mapping:
2625  mdl_ch1 = flat_mapping[trg_ch1]
2626  mdl_ch2 = flat_mapping[trg_ch2]
2627  res = self.trimmed_contact_scorertrimmed_contact_scorer.ScoreICSInterface(trg_ch1, trg_ch2,
2628  mdl_ch1, mdl_ch2)
2629  self._per_interface_ics_precision_trimmed_per_interface_ics_precision_trimmed.append(res.precision)
2630  self._per_interface_ics_recall_trimmed_per_interface_ics_recall_trimmed.append(res.recall)
2631  self._per_interface_ics_trimmed_per_interface_ics_trimmed.append(res.ics)
2632  else:
2633  self._per_interface_ics_precision_trimmed_per_interface_ics_precision_trimmed.append(None)
2634  self._per_interface_ics_recall_trimmed_per_interface_ics_recall_trimmed.append(None)
2635  self._per_interface_ics_trimmed_per_interface_ics_trimmed.append(None)
2636 
2637  def _compute_ips_scores(self):
2638  LogScript("Computing IPS scores")
2639  contact_scorer_res = self.contact_scorercontact_scorer.ScoreIPS(self.mappingmapping.mapping)
2640  self._ips_precision_ips_precision = contact_scorer_res.precision
2641  self._ips_recall_ips_recall = contact_scorer_res.recall
2642  self._ips_ips = contact_scorer_res.ips
2643 
2644  self._per_interface_ips_precision_per_interface_ips_precision = list()
2645  self._per_interface_ips_recall_per_interface_ips_recall = list()
2646  self._per_interface_ips_per_interface_ips = list()
2647  flat_mapping = self.mappingmapping.GetFlatMapping()
2648  for trg_int in self.contact_target_interfacescontact_target_interfaces:
2649  trg_ch1 = trg_int[0]
2650  trg_ch2 = trg_int[1]
2651  if trg_ch1 in flat_mapping and trg_ch2 in flat_mapping:
2652  mdl_ch1 = flat_mapping[trg_ch1]
2653  mdl_ch2 = flat_mapping[trg_ch2]
2654  res = self.contact_scorercontact_scorer.ScoreIPSInterface(trg_ch1, trg_ch2,
2655  mdl_ch1, mdl_ch2)
2656  self._per_interface_ips_precision_per_interface_ips_precision.append(res.precision)
2657  self._per_interface_ips_recall_per_interface_ips_recall.append(res.recall)
2658  self._per_interface_ips_per_interface_ips.append(res.ips)
2659  else:
2660  self._per_interface_ips_precision_per_interface_ips_precision.append(None)
2661  self._per_interface_ips_recall_per_interface_ips_recall.append(None)
2662  self._per_interface_ips_per_interface_ips.append(None)
2663 
2664  def _compute_ips_scores_trimmed(self):
2665  LogScript("Computing IPS scores trimmed")
2666 
2667  # this is an ugly hack without any efficiency in mind
2668  # we're simply taking the entities from mapper and construct
2669  # a new contact scorer from scratch
2670  contact_scorer_res = self.trimmed_contact_scorertrimmed_contact_scorer.ScoreIPS(self.mappingmapping.mapping)
2671  self._ips_precision_trimmed_ips_precision_trimmed = contact_scorer_res.precision
2672  self._ips_recall_trimmed_ips_recall_trimmed = contact_scorer_res.recall
2673  self._ips_trimmed_ips_trimmed = contact_scorer_res.ips
2674 
2675  self._per_interface_ips_precision_trimmed_per_interface_ips_precision_trimmed = list()
2676  self._per_interface_ips_recall_trimmed_per_interface_ips_recall_trimmed = list()
2677  self._per_interface_ips_trimmed_per_interface_ips_trimmed = list()
2678  flat_mapping = self.mappingmapping.GetFlatMapping()
2679  for trg_int in self.contact_target_interfacescontact_target_interfaces:
2680  trg_ch1 = trg_int[0]
2681  trg_ch2 = trg_int[1]
2682  if trg_ch1 in flat_mapping and trg_ch2 in flat_mapping:
2683  mdl_ch1 = flat_mapping[trg_ch1]
2684  mdl_ch2 = flat_mapping[trg_ch2]
2685  res = self.trimmed_contact_scorertrimmed_contact_scorer.ScoreIPSInterface(trg_ch1, trg_ch2,
2686  mdl_ch1, mdl_ch2)
2687  self._per_interface_ips_precision_trimmed_per_interface_ips_precision_trimmed.append(res.precision)
2688  self._per_interface_ips_recall_trimmed_per_interface_ips_recall_trimmed.append(res.recall)
2689  self._per_interface_ips_trimmed_per_interface_ips_trimmed.append(res.ips)
2690  else:
2691  self._per_interface_ips_precision_trimmed_per_interface_ips_precision_trimmed.append(None)
2692  self._per_interface_ips_recall_trimmed_per_interface_ips_recall_trimmed.append(None)
2693  self._per_interface_ips_trimmed_per_interface_ips_trimmed.append(None)
2694 
2695  def _compute_dockq_scores(self):
2696  LogScript("Computing DockQ")
2697 
2698  if self.dockq_capri_peptidedockq_capri_peptide and len(self.chain_mapperchain_mapper.polynuc_seqs) > 0:
2699  raise RuntimeError("Cannot compute DockQ for reference structures "
2700  "with nucleotide chains if dockq_capri_peptide "
2701  "is enabled.")
2702 
2703  # lists with values in contact_target_interfaces
2704  self._dockq_scores_dockq_scores = list()
2705  self._fnat_fnat = list()
2706  self._fnonnat_fnonnat = list()
2707  self._irmsd_irmsd = list()
2708  self._lrmsd_lrmsd = list()
2709  self._nnat_nnat = list()
2710  self._nmdl_nmdl = list()
2711 
2712  dockq_alns = dict()
2713  for aln in self.alnaln:
2714  trg_s = aln.GetSequence(0)
2715  mdl_s = aln.GetSequence(1)
2716  dockq_alns[(trg_s.GetName(), mdl_s.GetName())] = aln
2717 
2718  for interface in self.dockq_interfacesdockq_interfaces:
2719  trg_ch1 = interface[0]
2720  trg_ch2 = interface[1]
2721  mdl_ch1 = interface[2]
2722  mdl_ch2 = interface[3]
2723  aln1 = dockq_alns[(trg_ch1, mdl_ch1)]
2724  aln2 = dockq_alns[(trg_ch2, mdl_ch2)]
2725  if self.dockq_capri_peptidedockq_capri_peptide:
2726  res = dockq.DockQ(self.modelmodel, self.targettarget, mdl_ch1, mdl_ch2,
2727  trg_ch1, trg_ch2, ch1_aln=aln1,
2728  ch2_aln=aln2, contact_dist_thresh = 4.0,
2729  interface_dist_thresh=8.0,
2730  interface_cb = True)
2731  else:
2732  res = dockq.DockQ(self.modelmodel, self.targettarget, mdl_ch1, mdl_ch2,
2733  trg_ch1, trg_ch2, ch1_aln=aln1,
2734  ch2_aln=aln2)
2735 
2736  self._fnat_fnat.append(res["fnat"])
2737  self._fnonnat_fnonnat.append(res["fnonnat"])
2738  self._irmsd_irmsd.append(res["irmsd"])
2739  self._lrmsd_lrmsd.append(res["lrmsd"])
2740  self._dockq_scores_dockq_scores.append(res["DockQ"])
2741  self._nnat_nnat.append(res["nnat"])
2742  self._nmdl_nmdl.append(res["nmdl"])
2743 
2744  # keep track of native counts in target interfaces which are
2745  # not covered in model in order to compute
2746  # dockq_ave_full/dockq_wave_full in the end
2747  not_covered_counts = list()
2748  proc_trg_interfaces = set([(x[0], x[1]) for x in self.dockq_interfacesdockq_interfaces])
2749  for interface in self.dockq_target_interfacesdockq_target_interfaces:
2750  if interface not in proc_trg_interfaces:
2751  # let's run DockQ with trg as trg/mdl in order to get the native
2752  # contacts out - no need to pass alns as the residue numbers
2753  # match for sure
2754  trg_ch1 = interface[0]
2755  trg_ch2 = interface[1]
2756 
2757  if self.dockq_capri_peptidedockq_capri_peptide:
2758  res = dockq.DockQ(self.targettarget, self.targettarget,
2759  trg_ch1, trg_ch2, trg_ch1, trg_ch2,
2760  contact_dist_thresh = 4.0,
2761  interface_dist_thresh=8.0,
2762  interface_cb = True)
2763  else:
2764  res = dockq.DockQ(self.targettarget, self.targettarget,
2765  trg_ch1, trg_ch2, trg_ch1, trg_ch2)
2766 
2767  not_covered_counts.append(res["nnat"])
2768 
2769  # there are 4 types of combined scores
2770  # - simple average
2771  # - average weighted by native_contacts
2772  # - the two above including nonmapped_contact_interfaces => set DockQ to 0.0
2773  scores = np.array(self._dockq_scores_dockq_scores)
2774  weights = np.array(self._nnat_nnat)
2775  if len(scores) > 0:
2776  self._dockq_ave_dockq_ave = np.mean(scores)
2777  else:
2778  self._dockq_ave_dockq_ave = 0.0
2779  self._dockq_wave_dockq_wave = np.sum(np.multiply(weights/np.sum(weights), scores))
2780  scores = np.append(scores, [0.0]*len(not_covered_counts))
2781  weights = np.append(weights, not_covered_counts)
2782  if len(scores) > 0:
2783  self._dockq_ave_full_dockq_ave_full = np.mean(scores)
2784  else:
2785  self._dockq_ave_full_dockq_ave_full = 0.0
2786  self._dockq_wave_full_dockq_wave_full = np.sum(np.multiply(weights/np.sum(weights),
2787  scores))
2788 
2789  def _extract_mapped_pos(self):
2790  self._mapped_target_pos_mapped_target_pos = geom.Vec3List()
2791  self._mapped_model_pos_mapped_model_pos = geom.Vec3List()
2792  self._n_target_not_mapped_n_target_not_mapped = 0
2793  processed_trg_chains = set()
2794  for trg_ch, mdl_ch in self.mappingmapping.GetFlatMapping().items():
2795  processed_trg_chains.add(trg_ch)
2796  aln = self.mappingmapping.alns[(trg_ch, mdl_ch)]
2797  n_mapped = 0
2798  for trg_res, mdl_res in _GetAlignedResidues(aln,
2799  self.mappingmapping.target,
2800  self.mappingmapping.model):
2801  trg_at = trg_res.FindAtom("CA")
2802  mdl_at = mdl_res.FindAtom("CA")
2803  if not trg_at.IsValid():
2804  trg_at = trg_res.FindAtom("C3'")
2805  if not mdl_at.IsValid():
2806  mdl_at = mdl_res.FindAtom("C3'")
2807  self._mapped_target_pos_mapped_target_pos.append(trg_at.GetPos())
2808  self._mapped_model_pos_mapped_model_pos.append(mdl_at.GetPos())
2809  n_mapped += 1
2810  self._n_target_not_mapped_n_target_not_mapped += (len(aln.GetSequence(0).GetGaplessString())-n_mapped)
2811  # count number of trg residues from non-mapped chains
2812  for ch in self.mappingmapping.target.chains:
2813  if ch.GetName() not in processed_trg_chains:
2814  self._n_target_not_mapped_n_target_not_mapped += ch.GetResidueCount()
2815 
2816  def _extract_mapped_pos_full_bb(self):
2817  self._mapped_target_pos_full_bb_mapped_target_pos_full_bb = geom.Vec3List()
2818  self._mapped_model_pos_full_bb_mapped_model_pos_full_bb = geom.Vec3List()
2819  exp_pep_atoms = ["N", "CA", "C"]
2820  exp_nuc_atoms = ["O5'", "C5'", "C4'", "C3'", "O3'"]
2821  trg_pep_chains = [s.GetName() for s in self.chain_mapperchain_mapper.polypep_seqs]
2822  trg_nuc_chains = [s.GetName() for s in self.chain_mapperchain_mapper.polynuc_seqs]
2823  for trg_ch, mdl_ch in self.mappingmapping.GetFlatMapping().items():
2824  aln = self.mappingmapping.alns[(trg_ch, mdl_ch)]
2825  trg_ch = aln.GetSequence(0).GetName()
2826  if trg_ch in trg_pep_chains:
2827  exp_atoms = exp_pep_atoms
2828  elif trg_ch in trg_nuc_chains:
2829  exp_atoms = exp_nuc_atoms
2830  else:
2831  # this should be guaranteed by the chain mapper
2832  raise RuntimeError("Unexpected error - contact OST developer")
2833  for trg_res, mdl_res in _GetAlignedResidues(aln,
2834  self.mappingmapping.target,
2835  self.mappingmapping.model):
2836  for aname in exp_atoms:
2837  trg_at = trg_res.FindAtom(aname)
2838  mdl_at = mdl_res.FindAtom(aname)
2839  if not (trg_at.IsValid() and mdl_at.IsValid()):
2840  # this should be guaranteed by the chain mapper
2841  raise RuntimeError("Unexpected error - contact OST "
2842  "developer")
2843  self._mapped_target_pos_full_bb_mapped_target_pos_full_bb.append(trg_at.GetPos())
2844  self._mapped_model_pos_full_bb_mapped_model_pos_full_bb.append(mdl_at.GetPos())
2845 
2846 
2847  def _extract_rigid_mapped_pos(self):
2848  self._rigid_mapped_target_pos_rigid_mapped_target_pos = geom.Vec3List()
2849  self._rigid_mapped_model_pos_rigid_mapped_model_pos = geom.Vec3List()
2850  self._rigid_n_target_not_mapped_rigid_n_target_not_mapped = 0
2851  processed_trg_chains = set()
2852  for trg_ch, mdl_ch in self.rigid_mappingrigid_mapping.GetFlatMapping().items():
2853  processed_trg_chains.add(trg_ch)
2854  aln = self.rigid_mappingrigid_mapping.alns[(trg_ch, mdl_ch)]
2855  n_mapped = 0
2856  for trg_res, mdl_res in _GetAlignedResidues(aln,
2857  self.rigid_mappingrigid_mapping.target,
2858  self.rigid_mappingrigid_mapping.model):
2859  trg_at = trg_res.FindAtom("CA")
2860  mdl_at = mdl_res.FindAtom("CA")
2861  if not trg_at.IsValid():
2862  trg_at = trg_res.FindAtom("C3'")
2863  if not mdl_at.IsValid():
2864  mdl_at = mdl_res.FindAtom("C3'")
2865  self._rigid_mapped_target_pos_rigid_mapped_target_pos.append(trg_at.GetPos())
2866  self._rigid_mapped_model_pos_rigid_mapped_model_pos.append(mdl_at.GetPos())
2867  n_mapped += 1
2868 
2869  self._rigid_n_target_not_mapped_rigid_n_target_not_mapped += (len(aln.GetSequence(0).GetGaplessString())-n_mapped)
2870  # count number of trg residues from non-mapped chains
2871  for ch in self.rigid_mappingrigid_mapping.target.chains:
2872  if ch.GetName() not in processed_trg_chains:
2873  self._rigid_n_target_not_mapped_rigid_n_target_not_mapped += ch.GetResidueCount()
2874 
2875  def _extract_rigid_mapped_pos_full_bb(self):
2876  self._rigid_mapped_target_pos_full_bb_rigid_mapped_target_pos_full_bb = geom.Vec3List()
2877  self._rigid_mapped_model_pos_full_bb_rigid_mapped_model_pos_full_bb = geom.Vec3List()
2878  exp_pep_atoms = ["N", "CA", "C"]
2879  exp_nuc_atoms = ["O5'", "C5'", "C4'", "C3'", "O3'"]
2880  trg_pep_chains = [s.GetName() for s in self.chain_mapperchain_mapper.polypep_seqs]
2881  trg_nuc_chains = [s.GetName() for s in self.chain_mapperchain_mapper.polynuc_seqs]
2882  for trg_ch, mdl_ch in self.rigid_mappingrigid_mapping.GetFlatMapping().items():
2883  aln = self.rigid_mappingrigid_mapping.alns[(trg_ch, mdl_ch)]
2884  trg_ch = aln.GetSequence(0).GetName()
2885  if trg_ch in trg_pep_chains:
2886  exp_atoms = exp_pep_atoms
2887  elif trg_ch in trg_nuc_chains:
2888  exp_atoms = exp_nuc_atoms
2889  else:
2890  # this should be guaranteed by the chain mapper
2891  raise RuntimeError("Unexpected error - contact OST developer")
2892  for trg_res, mdl_res in _GetAlignedResidues(aln,
2893  self.rigid_mappingrigid_mapping.target,
2894  self.rigid_mappingrigid_mapping.model):
2895  for aname in exp_atoms:
2896  trg_at = trg_res.FindAtom(aname)
2897  mdl_at = mdl_res.FindAtom(aname)
2898  if not (trg_at.IsValid() and mdl_at.IsValid()):
2899  # this should be guaranteed by the chain mapper
2900  raise RuntimeError("Unexpected error - contact OST "
2901  "developer")
2902  self._rigid_mapped_target_pos_full_bb_rigid_mapped_target_pos_full_bb.append(trg_at.GetPos())
2903  self._rigid_mapped_model_pos_full_bb_rigid_mapped_model_pos_full_bb.append(mdl_at.GetPos())
2904 
2905  def _compute_cad_score(self):
2906  if not self.resnum_alignmentsresnum_alignments:
2907  raise RuntimeError("CAD score computations rely on residue numbers "
2908  "that are consistent between target and model "
2909  "chains, i.e. only work if resnum_alignments "
2910  "is True at Scorer construction.")
2911  try:
2912  LogScript("Computing CAD score")
2913  cad_score_exec = \
2914  settings.Locate("voronota-cadscore",
2915  explicit_file_name=self.cad_score_execcad_score_exec)
2916  except Exception as e:
2917  raise RuntimeError("voronota-cadscore must be in PATH for CAD "
2918  "score scoring") from e
2919  cad_bin_dir = os.path.dirname(cad_score_exec)
2920  m = self.mappingmapping.GetFlatMapping(mdl_as_key=True)
2921  cad_result = cadscore.CADScore(self.modelmodel, self.targettarget,
2922  mode = "voronota",
2923  label="localcad",
2924  old_regime=False,
2925  cad_bin_path=cad_bin_dir,
2926  chain_mapping=m)
2927 
2928  local_cad = dict()
2929  for r in self.modelmodel.residues:
2930  cname = r.GetChain().GetName()
2931  if cname not in local_cad:
2932  local_cad[cname] = dict()
2933  if r.HasProp("localcad"):
2934  score = round(r.GetFloatProp("localcad"), 3)
2935  local_cad[cname][r.GetNumber()] = score
2936  else:
2937  local_cad[cname][r.GetNumber()] = None
2938 
2939  self._cad_score_cad_score = cad_result.globalAA
2940  self._local_cad_score_local_cad_score = local_cad
2941 
2942  def _get_repr_view(self, ent):
2943  """ Returns view with representative peptide atoms => CB, CA for GLY
2944 
2945  Ensures that each residue has exactly one atom with assertions
2946 
2947  :param ent: Entity for which you want the representative view
2948  :param ent: :class:`ost.mol.EntityHandle`/:class:`ost.mol.EntityView`
2949  :returns: :class:`ost.mol.EntityView` derived from ent
2950  """
2951  repr_ent = ent.Select("(aname=\"CB\" or (rname=\"GLY\" and aname=\"CA\"))")
2952  for r in repr_ent.residues:
2953  assert(len(r.atoms) == 1)
2954  return repr_ent
2955 
2956  def _get_interface_residues(self, ent):
2957  """ Get interface residues
2958 
2959  Thats all residues having a contact with at least one residue from another
2960  chain (CB-CB distance <= 8A, CA in case of Glycine)
2961 
2962  :param ent: Model for which to extract interface residues
2963  :type ent: :class:`ost.mol.EntityView`
2964  :returns: :class:`dict` with chain names as key and and :class:`list`
2965  with residue numbers of the respective interface residues.
2966  """
2967  # select for representative positions => CB, CA for GLY
2968  repr_ent = self._get_repr_view_get_repr_view(ent)
2969  result = {ch.GetName(): list() for ch in ent.chains}
2970  for ch in ent.chains:
2971  cname = ch.GetName()
2972  sel = repr_ent.Select(f"(cname={mol.QueryQuoteName(cname)} and 8 <> [cname!={mol.QueryQuoteName(cname)}])")
2973  result[cname] = [r.GetNumber() for r in sel.residues]
2974  return result
2975 
2976  def _do_stereochecks(self):
2977  """ Perform stereochemistry checks on model and target
2978  """
2979  LogInfo("Performing stereochemistry checks on model and target")
2980  data = stereochemistry.GetDefaultStereoData()
2981  l_data = stereochemistry.GetDefaultStereoLinkData()
2982 
2983  a, b, c, d = stereochemistry.StereoCheck(self.modelmodel, stereo_data = data,
2984  stereo_link_data = l_data)
2985  self._stereochecked_model_stereochecked_model = a
2986  self._model_clashes_model_clashes = b
2987  self._model_bad_bonds_model_bad_bonds = c
2988  self._model_bad_angles_model_bad_angles = d
2989 
2990  a, b, c, d = stereochemistry.StereoCheck(self.targettarget, stereo_data = data,
2991  stereo_link_data = l_data)
2992 
2993  self._stereochecked_target_stereochecked_target = a
2994  self._target_clashes_target_clashes = b
2995  self._target_bad_bonds_target_bad_bonds = c
2996  self._target_bad_angles_target_bad_angles = d
2997 
2998  def _get_interface_patches(self, mdl_ch, mdl_rnum):
2999  """ Select interface patches representative for specified residue
3000 
3001  The patches for specified residue r in chain c are selected as follows:
3002 
3003  * mdl_patch_one: All residues in c with CB (CA for GLY) positions within 8A
3004  of r and within 12A of residues from any other chain.
3005  * mdl_patch_two: Closest residue x to r in any other chain gets identified.
3006  Patch is then constructed by selecting all residues from any other chain
3007  within 8A of x and within 12A from any residue in c.
3008  * trg_patch_one: Chain name and residue number based mapping from
3009  mdl_patch_one
3010  * trg_patch_two: Chain name and residue number based mapping from
3011  mdl_patch_two
3012 
3013  :param mdl_ch: Name of chain in *self.model* of residue of interest
3014  :type mdl_ch: :class:`str`
3015  :param mdl_rnum: Residue number of residue of interest
3016  :type mdl_rnum: :class:`ost.mol.ResNum`
3017  :returns: Tuple with 5 elements: 1) :class:`bool` flag whether all residues
3018  in *mdl* patches are covered in *trg* 2) mtl_patch_one
3019  3) mdl_patch_two 4) trg_patch_one 5) trg_patch_two
3020  """
3021  # select for representative positions => CB, CA for GLY
3022  repr_mdl = self._get_repr_view_get_repr_view(self.modelmodel.Select("peptide=true"))
3023 
3024  # get position for specified residue
3025  r = self.modelmodel.FindResidue(mdl_ch, mdl_rnum)
3026  if not r.IsValid():
3027  raise RuntimeError(f"Cannot find residue {mdl_rnum} in chain {mdl_ch}")
3028  if r.GetName() == "GLY":
3029  at = r.FindAtom("CA")
3030  else:
3031  at = r.FindAtom("CB")
3032  if not at.IsValid():
3033  raise RuntimeError("Cannot find interface views for res without CB/CA")
3034  r_pos = at.GetPos()
3035 
3036  # mdl_patch_one contains residues from the same chain as r
3037  # => all residues within 8A of r and within 12A of any other chain
3038 
3039  # q1 selects for everything in same chain and within 8A of r_pos
3040  q1 = f"(cname={mol.QueryQuoteName(mdl_ch)} and 8 <> {{{r_pos[0]},{r_pos[1]},{r_pos[2]}}})"
3041  # q2 selects for everything within 12A of any other chain
3042  q2 = f"(12 <> [cname!={mol.QueryQuoteName(mdl_ch)}])"
3043  mdl_patch_one = self.modelmodel.CreateEmptyView()
3044  sel = repr_mdl.Select(" and ".join([q1, q2]))
3045  for r in sel.residues:
3046  mdl_r = self.modelmodel.FindResidue(r.GetChain().GetName(), r.GetNumber())
3047  mdl_patch_one.AddResidue(mdl_r, mol.ViewAddFlag.INCLUDE_ALL)
3048 
3049  # mdl_patch_two contains residues from all other chains. In detail:
3050  # the closest residue to r is identified in any other chain, and the
3051  # patch is filled with residues that are within 8A of that residue and
3052  # within 12A of chain from r
3053  sel = repr_mdl.Select(f"(cname!={mol.QueryQuoteName(mdl_ch)})")
3054  close_stuff = sel.FindWithin(r_pos, 8)
3055  min_pos = None
3056  min_dist = 42.0
3057  for close_at in close_stuff:
3058  dist = geom.Distance(r_pos, close_at.GetPos())
3059  if dist < min_dist:
3060  min_pos = close_at.GetPos()
3061  min_dist = dist
3062 
3063  # q1 selects for everything not in mdl_ch but within 8A of min_pos
3064  q1 = f"(cname!={mol.QueryQuoteName(mdl_ch)} and 8 <> {{{min_pos[0]},{min_pos[1]},{min_pos[2]}}})"
3065  # q2 selects for everything within 12A of mdl_ch
3066  q2 = f"(12 <> [cname={mol.QueryQuoteName(mdl_ch)}])"
3067  mdl_patch_two = self.modelmodel.CreateEmptyView()
3068  sel = repr_mdl.Select(" and ".join([q1, q2]))
3069  for r in sel.residues:
3070  mdl_r = self.modelmodel.FindResidue(r.GetChain().GetName(), r.GetNumber())
3071  mdl_patch_two.AddResidue(mdl_r, mol.ViewAddFlag.INCLUDE_ALL)
3072 
3073  # transfer mdl residues to trg
3074  flat_mapping = self.mappingmapping.GetFlatMapping(mdl_as_key=True)
3075  full_trg_coverage = True
3076  trg_patch_one = self.mappingmapping.target.CreateEmptyView()
3077  for r in mdl_patch_one.residues:
3078  trg_r = None
3079  mdl_cname = r.GetChain().GetName()
3080  if mdl_cname in flat_mapping:
3081  aln = self.mappingmapping.alns[(flat_mapping[mdl_cname], mdl_cname)]
3082  for x,y in _GetAlignedResidues(aln,
3083  self.mappingmapping.target,
3084  self.mappingmapping.model):
3085  if y.GetNumber() == r.GetNumber():
3086  trg_r = x
3087  break
3088 
3089  if trg_r is not None:
3090  trg_patch_one.AddResidue(trg_r.handle,
3091  mol.ViewAddFlag.INCLUDE_ALL)
3092  else:
3093  full_trg_coverage = False
3094 
3095  trg_patch_two = self.mappingmapping.target.CreateEmptyView()
3096  for r in mdl_patch_two.residues:
3097  trg_r = None
3098  mdl_cname = r.GetChain().GetName()
3099  if mdl_cname in flat_mapping:
3100  aln = self.mappingmapping.alns[(flat_mapping[mdl_cname], mdl_cname)]
3101  for x,y in _GetAlignedResidues(aln,
3102  self.mappingmapping.target,
3103  self.mappingmapping.model):
3104  if y.GetNumber() == r.GetNumber():
3105  trg_r = x
3106  break
3107 
3108  if trg_r is not None:
3109  trg_patch_two.AddResidue(trg_r.handle,
3110  mol.ViewAddFlag.INCLUDE_ALL)
3111  else:
3112  full_trg_coverage = False
3113 
3114  return (full_trg_coverage, mdl_patch_one, mdl_patch_two,
3115  trg_patch_one, trg_patch_two)
3116 
3117  def _compute_patchqs_scores(self):
3118  LogScript("Computing patch QS-scores")
3119  self._patch_qs_patch_qs = dict()
3120  for cname, rnums in self.model_interface_residuesmodel_interface_residues.items():
3121  scores = list()
3122  for rnum in rnums:
3123  score = None
3124  r = self.modelmodel.FindResidue(cname, rnum)
3125  if r.IsValid() and r.GetChemType() == mol.ChemType.AMINOACIDS:
3126  full_trg_coverage, mdl_patch_one, mdl_patch_two, \
3127  trg_patch_one, trg_patch_two = \
3128  self._get_interface_patches_get_interface_patches(cname, rnum)
3129  if full_trg_coverage:
3130  score = self._patchqs_patchqs(mdl_patch_one, mdl_patch_two,
3131  trg_patch_one, trg_patch_two)
3132  scores.append(score)
3133  self._patch_qs_patch_qs[cname] = scores
3134 
3135  def _compute_patchdockq_scores(self):
3136  LogScript("Computing patch DockQ scores")
3137  self._patch_dockq_patch_dockq = dict()
3138  for cname, rnums in self.model_interface_residuesmodel_interface_residues.items():
3139  scores = list()
3140  for rnum in rnums:
3141  score = None
3142  r = self.modelmodel.FindResidue(cname, rnum)
3143  if r.IsValid() and r.GetChemType() == mol.ChemType.AMINOACIDS:
3144  full_trg_coverage, mdl_patch_one, mdl_patch_two, \
3145  trg_patch_one, trg_patch_two = \
3146  self._get_interface_patches_get_interface_patches(cname, rnum)
3147  if full_trg_coverage:
3148  score = self._patchdockq_patchdockq(mdl_patch_one, mdl_patch_two,
3149  trg_patch_one, trg_patch_two)
3150  scores.append(score)
3151  self._patch_dockq_patch_dockq[cname] = scores
3152 
3153  def _patchqs(self, mdl_patch_one, mdl_patch_two, trg_patch_one, trg_patch_two):
3154  """ Score interface residue patches with QS-score
3155 
3156  In detail: Construct two entities with two chains each. First chain
3157  consists of residues from <x>_patch_one and second chain consists of
3158  <x>_patch_two. The returned score is the QS-score between the two
3159  entities
3160 
3161  :param mdl_patch_one: Interface patch representing scored residue
3162  :type mdl_patch_one: :class:`ost.mol.EntityView`
3163  :param mdl_patch_two: Interface patch representing scored residue
3164  :type mdl_patch_two: :class:`ost.mol.EntityView`
3165  :param trg_patch_one: Interface patch representing scored residue
3166  :type trg_patch_one: :class:`ost.mol.EntityView`
3167  :param trg_patch_two: Interface patch representing scored residue
3168  :type trg_patch_two: :class:`ost.mol.EntityView`
3169  :returns: PatchQS score
3170  """
3171  qs_ent_mdl = self._qs_ent_from_patches_qs_ent_from_patches(mdl_patch_one, mdl_patch_two)
3172  qs_ent_trg = self._qs_ent_from_patches_qs_ent_from_patches(trg_patch_one, trg_patch_two)
3173 
3174  alnA = seq.CreateAlignment()
3175  s = ''.join([r.one_letter_code for r in mdl_patch_one.residues])
3176  alnA.AddSequence(seq.CreateSequence("A", s))
3177  s = ''.join([r.one_letter_code for r in trg_patch_one.residues])
3178  alnA.AddSequence(seq.CreateSequence("A", s))
3179 
3180  alnB = seq.CreateAlignment()
3181  s = ''.join([r.one_letter_code for r in mdl_patch_two.residues])
3182  alnB.AddSequence(seq.CreateSequence("B", s))
3183  s = ''.join([r.one_letter_code for r in trg_patch_two.residues])
3184  alnB.AddSequence(seq.CreateSequence("B", s))
3185  alns = {("A", "A"): alnA, ("B", "B"): alnB}
3186 
3187  scorer = QSScorer(qs_ent_mdl, [["A"], ["B"]], qs_ent_trg, alns)
3188  score_result = scorer.Score([["A"], ["B"]])
3189 
3190  return score_result.QS_global
3191 
3192  def _patchdockq(self, mdl_patch_one, mdl_patch_two, trg_patch_one,
3193  trg_patch_two):
3194  """ Score interface residue patches with DockQ
3195 
3196  In detail: Construct two entities with two chains each. First chain
3197  consists of residues from <x>_patch_one and second chain consists of
3198  <x>_patch_two. The returned score is the QS-score between the two
3199  entities
3200 
3201  :param mdl_patch_one: Interface patch representing scored residue
3202  :type mdl_patch_one: :class:`ost.mol.EntityView`
3203  :param mdl_patch_two: Interface patch representing scored residue
3204  :type mdl_patch_two: :class:`ost.mol.EntityView`
3205  :param trg_patch_one: Interface patch representing scored residue
3206  :type trg_patch_one: :class:`ost.mol.EntityView`
3207  :param trg_patch_two: Interface patch representing scored residue
3208  :type trg_patch_two: :class:`ost.mol.EntityView`
3209  :returns: DockQ score
3210  """
3211  m = self._qs_ent_from_patches_qs_ent_from_patches(mdl_patch_one, mdl_patch_two)
3212  t = self._qs_ent_from_patches_qs_ent_from_patches(trg_patch_one, trg_patch_two)
3213  dockq_result = dockq.DockQ(t, m, "A", "B", "A", "B")
3214  if dockq_result["nnat"] > 0:
3215  return dockq_result["DockQ"]
3216  return 0.0
3217 
3218  def _qs_ent_from_patches(self, patch_one, patch_two):
3219  """ Constructs Entity with two chains named "A" and "B""
3220 
3221  Blindly adds all residues from *patch_one* to chain A and residues from
3222  patch_two to chain B.
3223  """
3224  ent = mol.CreateEntity()
3225  ed = ent.EditXCS()
3226  added_ch = ed.InsertChain("A")
3227  for r in patch_one.residues:
3228  added_r = ed.AppendResidue(added_ch, r.GetName())
3229  added_r.SetChemClass(str(r.GetChemClass()))
3230  for a in r.atoms:
3231  ed.InsertAtom(added_r, a.handle)
3232  added_ch = ed.InsertChain("B")
3233  for r in patch_two.residues:
3234  added_r = ed.AppendResidue(added_ch, r.GetName())
3235  added_r.SetChemClass(str(r.GetChemClass()))
3236  for a in r.atoms:
3237  ed.InsertAtom(added_r, a.handle)
3238  return ent
3239 
3240  def _construct_custom_mapping(self, mapping):
3241  """ constructs a full blown MappingResult object from simple dict
3242 
3243  :param mapping: mapping with trg chains as key and mdl ch as values
3244  :type mapping: :class:`dict`
3245  """
3246  LogInfo("Setting custom chain mapping")
3247 
3248  chain_mapper = self.chain_mapperchain_mapper
3249  chem_mapping, chem_group_alns, mdl_chains_without_chem_mapping, mdl = \
3250  chain_mapper.GetChemMapping(self.modelmodel)
3251 
3252  # now that we have a chem mapping, lets do consistency checks
3253  # - check whether chain names are unique and available in structures
3254  # - check whether the mapped chains actually map to the same chem groups
3255  if len(mapping) != len(set(mapping.keys())):
3256  raise RuntimeError(f"Expect unique trg chain names in mapping. Got "
3257  f"{mapping.keys()}")
3258  if len(mapping) != len(set(mapping.values())):
3259  raise RuntimeError(f"Expect unique mdl chain names in mapping. Got "
3260  f"{mapping.values()}")
3261 
3262  trg_chains = set([ch.GetName() for ch in chain_mapper.target.chains])
3263  mdl_chains = set([ch.GetName() for ch in mdl.chains])
3264  for k,v in mapping.items():
3265  if k not in trg_chains:
3266  raise RuntimeError(f"Target chain \"{k}\" is not available "
3267  f"in target processed for chain mapping "
3268  f"({trg_chains})")
3269  if v not in mdl_chains:
3270  raise RuntimeError(f"Model chain \"{v}\" is not available "
3271  f"in model processed for chain mapping "
3272  f"({mdl_chains})")
3273 
3274  for trg_ch, mdl_ch in mapping.items():
3275  trg_group_idx = None
3276  mdl_group_idx = None
3277  for idx, group in enumerate(chain_mapper.chem_groups):
3278  if trg_ch in group:
3279  trg_group_idx = idx
3280  break
3281  for idx, group in enumerate(chem_mapping):
3282  if mdl_ch in group:
3283  mdl_group_idx = idx
3284  break
3285  if trg_group_idx is None or mdl_group_idx is None:
3286  raise RuntimeError("Could not establish a valid chem grouping "
3287  "of chain names provided in custom mapping.")
3288 
3289  if trg_group_idx != mdl_group_idx:
3290  raise RuntimeError(f"Chem group mismatch in custom mapping: "
3291  f"target chain \"{trg_ch}\" groups with the "
3292  f"following chemically equivalent target "
3293  f"chains: "
3294  f"{chain_mapper.chem_groups[trg_group_idx]} "
3295  f"but model chain \"{mdl_ch}\" maps to the "
3296  f"following target chains: "
3297  f"{chain_mapper.chem_groups[mdl_group_idx]}")
3298 
3299  pairs = set([(trg_ch, mdl_ch) for trg_ch, mdl_ch in mapping.items()])
3300  ref_mdl_alns = \
3301  chain_mapping._GetRefMdlAlns(chain_mapper.chem_groups,
3302  chain_mapper.chem_group_alignments,
3303  chem_mapping,
3304  chem_group_alns,
3305  pairs = pairs)
3306 
3307  # translate mapping format
3308  final_mapping = list()
3309  for ref_chains in chain_mapper.chem_groups:
3310  mapped_mdl_chains = list()
3311  for ref_ch in ref_chains:
3312  if ref_ch in mapping:
3313  mapped_mdl_chains.append(mapping[ref_ch])
3314  else:
3315  mapped_mdl_chains.append(None)
3316  final_mapping.append(mapped_mdl_chains)
3317 
3318  alns = dict()
3319  for ref_group, mdl_group in zip(chain_mapper.chem_groups,
3320  final_mapping):
3321  for ref_ch, mdl_ch in zip(ref_group, mdl_group):
3322  if ref_ch is not None and mdl_ch is not None:
3323  aln = ref_mdl_alns[(ref_ch, mdl_ch)]
3324  trg_view = chain_mapper.target.Select(f"cname={mol.QueryQuoteName(ref_ch)}")
3325  mdl_view = mdl.Select(f"cname={mol.QueryQuoteName(mdl_ch)}")
3326  aln.AttachView(0, trg_view)
3327  aln.AttachView(1, mdl_view)
3328  alns[(ref_ch, mdl_ch)] = aln
3329 
3330  return chain_mapping.MappingResult(chain_mapper.target, mdl,
3331  chain_mapper.chem_groups,
3332  chem_mapping,
3333  mdl_chains_without_chem_mapping,
3334  final_mapping, alns)
3335 
3336  def _compute_tmscore(self):
3337  res = None
3338  if self.usalign_execusalign_exec is None:
3339  LogScript("Computing TM-score with built-in USalign")
3340  if self.oumoum:
3341  flat_mapping = self.rigid_mappingrigid_mapping.GetFlatMapping()
3342  LogInfo("Overriding TM-score chain mapping")
3343  res = res = bindings.WrappedMMAlign(self.modelmodel, self.targettarget,
3344  mapping=flat_mapping)
3345  else:
3346  res = bindings.WrappedMMAlign(self.modelmodel, self.targettarget)
3347  else:
3348  LogScript("Computing TM-score with USalign exectuable")
3349  if self.oumoum:
3350  LogInfo("Overriding TM-score chain mapping")
3351  flat_mapping = self.rigid_mappingrigid_mapping.GetFlatMapping()
3352  res = tmtools.USAlign(self.modelmodel, self.targettarget,
3353  usalign = self.usalign_execusalign_exec,
3354  custom_chain_mapping = flat_mapping)
3355  else:
3356  res = tmtools.USAlign(self.modelmodel, self.targettarget,
3357  usalign = self.usalign_execusalign_exec)
3358 
3359  self._tm_score_tm_score = res.tm_score
3360  self._usalign_mapping_usalign_mapping = dict()
3361  for a,b in zip(res.ent1_mapped_chains, res.ent2_mapped_chains):
3362  self._usalign_mapping_usalign_mapping[b] = a
3363 
3364  def _resnum_connect(self, ent):
3365  ed = None
3366  for ch in ent.chains:
3367  res_list = ch.residues
3368  for i in range(len(res_list) - 1):
3369  ra = res_list[i]
3370  rb = res_list[i+1]
3371  if ra.GetNumber().GetNum() + 1 == rb.GetNumber().GetNum():
3372  if ra.IsPeptideLinking() and rb.IsPeptideLinking():
3373  c = ra.FindAtom("C")
3374  n = rb.FindAtom("N")
3375  if c.IsValid() and n.IsValid() and not mol.BondExists(c, n):
3376  if ed is None:
3377  ed = ent.EditXCS(mol.BUFFERED_EDIT)
3378  ed.Connect(c,n,1)
3379  elif ra.IsNucleotideLinking() and rb.IsNucleotideLinking():
3380  o = ra.FindAtom("O3'")
3381  p = rb.FindAtom("P")
3382  if o.IsValid() and p.IsValid()and not mol.BondExists(o, p):
3383  if ed is None:
3384  ed = ent.EditXCS(mol.BUFFERED_EDIT)
3385  ed.Connect(o,p,1)
3386 
3387 
3388 # specify public interface
3389 __all__ = ('lDDTBSScorer', 'Scorer',)
definition of EntityView
Definition: entity_view.hh:86
def target_interface_residues(self)
Definition: scoring.py:816
def _patchqs(self, mdl_patch_one, mdl_patch_two, trg_patch_one, trg_patch_two)
Definition: scoring.py:3153
def _get_repr_view(self, ent)
Definition: scoring.py:2942
def rigid_mapped_model_pos(self)
Definition: scoring.py:1791
def _compute_per_interface_qs_scores(self)
Definition: scoring.py:2531
def model_interface_residues(self)
Definition: scoring.py:801
def stereochecked_target(self)
Definition: scoring.py:696
def per_interface_ips_precision(self)
Definition: scoring.py:1389
def rigid_mapped_target_pos_full_bb(self)
Definition: scoring.py:1777
def _aln_helper(self, target, model)
Definition: scoring.py:2123
def per_interface_ics_recall(self)
Definition: scoring.py:1202
def transformed_mapped_model_pos(self)
Definition: scoring.py:1716
def _compute_patchqs_scores(self)
Definition: scoring.py:3117
def _compute_stereochecked_aln(self)
Definition: scoring.py:2169
def rigid_transformed_mapped_model_pos(self)
Definition: scoring.py:1819
def trimmed_model_contacts(self)
Definition: scoring.py:1115
def _compute_ips_scores_trimmed(self)
Definition: scoring.py:2664
def per_interface_ips_trimmed(self)
Definition: scoring.py:1456
def contact_model_interfaces(self)
Definition: scoring.py:1139
def _patchdockq(self, mdl_patch_one, mdl_patch_two, trg_patch_one, trg_patch_two)
Definition: scoring.py:3193
def per_interface_ips_recall(self)
Definition: scoring.py:1402
def per_interface_ics_precision(self)
Definition: scoring.py:1188
def _resnum_connect(self, ent)
Definition: scoring.py:3364
def _get_interface_residues(self, ent)
Definition: scoring.py:2956
def __init__(self, model, target, resnum_alignments=False, molck_settings=None, cad_score_exec=None, custom_mapping=None, custom_rigid_mapping=None, usalign_exec=None, lddt_no_stereochecks=False, n_max_naive=40320, oum=False, min_pep_length=6, min_nuc_length=4, lddt_add_mdl_contacts=False, dockq_capri_peptide=False, lddt_symmetry_settings=None, lddt_inclusion_radius=15.0, pep_seqid_thr=95., nuc_seqid_thr=95., mdl_map_pep_seqid_thr=0., mdl_map_nuc_seqid_thr=0., seqres=None, trg_seqres_mapping=None)
Definition: scoring.py:286
def mapped_target_pos_full_bb(self)
Definition: scoring.py:1674
def rigid_mapped_target_pos(self)
Definition: scoring.py:1763
def dockq_target_interfaces(self)
Definition: scoring.py:1470
def trimmed_contact_scorer(self)
Definition: scoring.py:883
def per_interface_ics_recall_trimmed(self)
Definition: scoring.py:1320
def _compute_patchdockq_scores(self)
Definition: scoring.py:3135
def _get_interface_patches(self, mdl_ch, mdl_rnum)
Definition: scoring.py:2998
def _extract_rigid_mapped_pos(self)
Definition: scoring.py:2847
def contact_target_interfaces(self)
Definition: scoring.py:1123
def per_interface_qs_global(self)
Definition: scoring.py:1070
def _qs_ent_from_patches(self, patch_one, patch_two)
Definition: scoring.py:3218
def rigid_mapped_model_pos_full_bb(self)
Definition: scoring.py:1805
def _extract_rigid_mapped_pos_full_bb(self)
Definition: scoring.py:2875
def per_interface_ips_precision_trimmed(self)
Definition: scoring.py:1429
def _compute_ics_scores_trimmed(self)
Definition: scoring.py:2605
def rigid_n_target_not_mapped(self)
Definition: scoring.py:1831
def per_interface_ics_precision_trimmed(self)
Definition: scoring.py:1306
def per_interface_ips_recall_trimmed(self)
Definition: scoring.py:1443
def mapped_model_pos_full_bb(self)
Definition: scoring.py:1702
def per_interface_ics_trimmed(self)
Definition: scoring.py:1333
def _construct_custom_mapping(self, mapping)
Definition: scoring.py:3240
def _extract_mapped_pos_full_bb(self)
Definition: scoring.py:2816
def ScoreBS(self, ligand, radius=4.0, lddt_radius=10.0)
Definition: scoring.py:88
def __init__(self, reference, model, residue_number_alignment=False)
Definition: scoring.py:82
Real DLLEXPORT_OST_GEOM Distance(const Line2 &l, const Vec2 &v)
void Molck(ost::mol::EntityHandle &ent, ost::conop::CompoundLibPtr lib, const MolckSettings &settings, bool prune=true)
void GDT(const geom::Vec3List &mdl_pos, const geom::Vec3List &ref_pos, int window_size, int max_windows, Real distance_thresh, int &n_superposed, geom::Mat4 &transform)