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 
26  """Scorer specific for a reference/model pair
27 
28  Finds best possible binding site representation of reference in model given
29  lDDT score. Uses :class:`ost.mol.alg.chain_mapping.ChainMapper` to deal with
30  chain mapping.
31 
32  :param reference: Reference structure
33  :type reference: :class:`ost.mol.EntityView`/:class:`ost.mol.EntityHandle`
34  :param model: Model structure
35  :type model: :class:`ost.mol.EntityView`/:class:`ost.mol.EntityHandle`
36  :param residue_number_alignment: Passed to ChainMapper constructor
37  :type residue_number_alignment: :class:`bool`
38  """
39  def __init__(self, reference, model,
40  residue_number_alignment=False):
41  self.chain_mapperchain_mapper = chain_mapping.ChainMapper(reference,
42  resnum_alignments=residue_number_alignment)
43  self.refref = self.chain_mapperchain_mapper.target
44  self.mdlmdl = model
45 
46  def ScoreBS(self, ligand, radius = 4.0, lddt_radius=10.0):
47  """Computes binding site lDDT score given *ligand*. Best possible
48  binding site representation is selected by lDDT but other scores such as
49  CA based RMSD and GDT are computed too and returned.
50 
51  :param ligand: Defines the scored binding site, i.e. provides positions
52  to perform proximity search
53  :type ligand: r'((Residue)|(Chain)|(Entity))((View)|(Handle))'
54  :param radius: Reference residues with any atom position within *radius*
55  of *ligand* consitute the scored binding site
56  :type radius: :class:`float`
57  :param lddt_radius: Passed as *inclusion_radius* to
58  :class:`ost.mol.alg.lddt.lDDTScorer`
59  :type lddt_radius: :class:`float`
60  :returns: Object of type :class:`ost.mol.alg.chain_mapping.ReprResult`
61  containing all atom lDDT score and mapping information.
62  None if no representation could be found.
63  """
64 
65  # create view of reference binding site
66  ref_residues_hashes = set() # helper to keep track of added residues
67  for ligand_at in ligand.atoms:
68  close_atoms = self.refref.FindWithin(ligand_at.GetPos(), radius)
69  for close_at in close_atoms:
70  ref_res = close_at.GetResidue()
71  h = ref_res.handle.GetHashCode()
72  if h not in ref_residues_hashes:
73  ref_residues_hashes.add(h)
74 
75  # reason for doing that separately is to guarantee same ordering of
76  # residues as in underlying entity. (Reorder by ResNum seems only
77  # available on ChainHandles)
78  ref_bs = self.refref.CreateEmptyView()
79  for ch in self.refref.chains:
80  for r in ch.residues:
81  if r.handle.GetHashCode() in ref_residues_hashes:
82  ref_bs.AddResidue(r, mol.ViewAddFlag.INCLUDE_ALL)
83 
84  # gogogo
85  bs_repr = self.chain_mapperchain_mapper.GetRepr(ref_bs, self.mdlmdl,
86  inclusion_radius = lddt_radius)
87  if len(bs_repr) >= 1:
88  return bs_repr[0]
89  else:
90  return None
91 
92 
93 class Scorer:
94  """ Helper class to access the various scores available from ost.mol.alg
95 
96  Deals with structure cleanup, chain mapping, interface identification etc.
97  Intermediate results are available as attributes.
98 
99  :param model: Model structure - a deep copy is available as :attr:`model`.
100  Additionally, :func:`ost.mol.alg.Molck` using *molck_settings*
101  is applied.
102  :type model: :class:`ost.mol.EntityHandle`/:class:`ost.mol.EntityView`
103  :param target: Target structure - a deep copy is available as :attr:`target`.
104  Additionally, :func:`ost.mol.alg.Molck` using *molck_settings*
105  is applied.
106  :type target: :class:`ost.mol.EntityHandle`/:class:`ost.mol.EntityView`
107  :param resnum_alignments: Whether alignments between chemically equivalent
108  chains in *model* and *target* can be computed
109  based on residue numbers. This can be assumed in
110  benchmarking setups such as CAMEO/CASP.
111  :type resnum_alignments: :class:`bool`
112  :param molck_settings: Settings used for Molck on *model* and *target*, if
113  set to None, a default object is constructed by
114  setting everything except rm_zero_occ_atoms and
115  colored to True in
116  :class:`ost.mol.alg.MolckSettings` constructor.
117  :type molck_settings: :class:`ost.mol.alg.MolckSettings`
118  :param cad_score_exec: Explicit path to voronota-cadscore executable from
119  voronota installation from
120  https://github.com/kliment-olechnovic/voronota. If
121  not given, voronota-cadscore must be in PATH if any
122  of the CAD score related attributes is requested.
123  :type cad_score_exec: :class:`str`
124  :param custom_mapping: Provide custom chain mapping between *model* and
125  *target*. Dictionary with target chain names as key
126  and model chain names as value.
127  :attr:`~mapping` is constructed from this.
128  :type custom_mapping: :class:`dict`
129  :param custom_rigid_mapping: Provide custom chain mapping between *model*
130  and *target*. Dictionary with target chain
131  names as key and model chain names as value.
132  :attr:`~rigid_mapping` is constructed from
133  this.
134  :type custom_rigid_mapping: :class:`dict`
135  :param usalign_exec: Explicit path to USalign executable used to compute
136  TM-score. If not given, TM-score will be computed
137  with OpenStructure internal copy of USalign code.
138  :type usalign_exec: :class:`str`
139  :param lddt_no_stereochecks: Whether to compute lDDT without stereochemistry
140  checks
141  :type lddt_no_stereochecks: :class:`bool`
142  :param n_max_naive: Parameter for chain mapping. If the number of possible
143  mappings is <= *n_max_naive*, the full
144  mapping solution space is enumerated to find the
145  the optimum. A heuristic is used otherwise. The default
146  of 40320 corresponds to an octamer (8! = 40320).
147  A structure with stoichiometry A6B2 would be
148  6!*2! = 1440 etc.
149  :type n_max_naive: :class:`int`
150  :param oum: Override USalign Mapping. Inject rigid_mapping of
151  :class:`Scorer` object into USalign to compute TM-score.
152  Experimental feature with limitations.
153  :type oum: :class:`bool`
154  :param min_pep_length: Relevant parameter if short peptides are involved in
155  scoring. Minimum peptide length for a chain in the
156  target structure to be considered in chain mapping.
157  The chain mapping algorithm first performs an all vs.
158  all pairwise sequence alignment to identify \"equal\"
159  chains within the target structure. We go for simple
160  sequence identity there. Short sequences can be
161  problematic as they may produce high sequence identity
162  alignments by pure chance.
163  :type min_pep_length: :class:`int`
164  :param min_nuc_length: Relevant parameter if short nucleotides are involved
165  in scoring. Minimum nucleotide length for a chain in
166  the target structure to be considered in chain
167  mapping. The chain mapping algorithm first performs
168  an all vs. all pairwise sequence alignment to
169  identify \"equal\" chains within the target
170  structure. We go for simple sequence identity there.
171  Short sequences can be problematic as they may
172  produce high sequence identity alignments by pure
173  chance.
174  :type min_nuc_length: :class:`int`
175  :param lddt_add_mdl_contacts: lDDT specific flag. Only using contacts in
176  lDDT that are within a certain distance
177  threshold in the target does not penalize
178  for added model contacts. If set to True, this
179  flag will also consider target contacts
180  that are within the specified distance
181  threshold in the model but not necessarily in
182  the target. No contact will be added if the
183  respective atom pair is not resolved in the
184  target.
185  :type lddt_add_mdl_contacts: :class:`bool`
186  :param dockq_capri_peptide: Flag that changes two things in the way DockQ
187  and its underlying scores are computed which is
188  proposed by the CAPRI community when scoring
189  peptides (PMID: 31886916).
190  ONE: Two residues are considered in contact if
191  any of their atoms is within 5A. This is
192  relevant for fnat and fnonat scores. CAPRI
193  suggests to lower this threshold to 4A for
194  protein-peptide interactions.
195  TWO: irmsd is computed on interface residues. A
196  residue is defined as interface residue if any
197  of its atoms is within 10A of another chain.
198  CAPRI suggests to lower the default of 10A to
199  8A in combination with only considering CB atoms
200  for protein-peptide interactions.
201  This flag has no influence on patch_dockq
202  scores.
203  :type dockq_capri_peptide: :class:`bool`
204  """
205  def __init__(self, model, target, resnum_alignments=False,
206  molck_settings = None, cad_score_exec = None,
207  custom_mapping=None, custom_rigid_mapping=None,
208  usalign_exec = None, lddt_no_stereochecks=False,
209  n_max_naive=40320, oum=False, min_pep_length = 6,
210  min_nuc_length = 4, lddt_add_mdl_contacts=False,
211  dockq_capri_peptide=False):
212 
213  self._target_orig_target_orig = target
214  self._model_orig_model_orig = model
215 
216  if isinstance(self._model_orig_model_orig, mol.EntityView):
217  self._model_model = mol.CreateEntityFromView(self._model_orig_model_orig, False)
218  else:
219  self._model_model = self._model_orig_model_orig.Copy()
220 
221  if isinstance(self._target_orig_target_orig, mol.EntityView):
222  self._target_target = mol.CreateEntityFromView(self._target_orig_target_orig, False)
223  else:
224  self._target_target = self._target_orig_target_orig.Copy()
225 
226  if molck_settings is None:
227  molck_settings = MolckSettings(rm_unk_atoms=True,
228  rm_non_std=False,
229  rm_hyd_atoms=True,
230  rm_oxt_atoms=True,
231  rm_zero_occ_atoms=False,
232  colored=False,
233  map_nonstd_res=True,
234  assign_elem=True)
235  LogScript("Cleaning up input structures")
236  Molck(self._model_model, conop.GetDefaultLib(), molck_settings)
237  Molck(self._target_target, conop.GetDefaultLib(), molck_settings)
238  self._model_model = self._model_model.Select("peptide=True or nucleotide=True")
239  self._target_target = self._target_target.Select("peptide=True or nucleotide=True")
240 
241  # catch models which have empty chain names
242  for ch in self._model_model.chains:
243  if ch.GetName().strip() == "":
244  raise RuntimeError("Model chains must have valid chain names")
245  if ch.GetName().strip() == "'" or ch.GetName().strip() == '"':
246  raise RuntimeError("Model chains cannot be named with quote signs (' or \"\")")
247 
248  # catch targets which have empty chain names
249  for ch in self._target_target.chains:
250  if ch.GetName().strip() == "":
251  raise RuntimeError("Target chains must have valid chain names")
252  if ch.GetName().strip() == "'" or ch.GetName().strip() == '"':
253  raise RuntimeError("Target chains cannot be named with quote signs (' or \"\")")
254 
255  if resnum_alignments:
256  # In case of resnum_alignments, we have some requirements on
257  # residue numbers in the chain mapping: 1) no ins codes 2) strictly
258  # increasing residue numbers.
259  for ch in self._model_model.chains:
260  ins_codes = [r.GetNumber().GetInsCode() for r in ch.residues]
261  if len(set(ins_codes)) != 1 or ins_codes[0] != '\0':
262  raise RuntimeError("Residue numbers in each model chain "
263  "must not contain insertion codes if "
264  "resnum_alignments are enabled")
265  nums = [r.GetNumber().GetNum() for r in ch.residues]
266  if not all(i < j for i, j in zip(nums, nums[1:])):
267  raise RuntimeError("Residue numbers in each model chain "
268  "must be strictly increasing if "
269  "resnum_alignments are enabled")
270 
271  for ch in self._target_target.chains:
272  ins_codes = [r.GetNumber().GetInsCode() for r in ch.residues]
273  if len(set(ins_codes)) != 1 or ins_codes[0] != '\0':
274  raise RuntimeError("Residue numbers in each target chain "
275  "must not contain insertion codes if "
276  "resnum_alignments are enabled")
277  nums = [r.GetNumber().GetNum() for r in ch.residues]
278  if not all(i < j for i, j in zip(nums, nums[1:])):
279  raise RuntimeError("Residue numbers in each target chain "
280  "must be strictly increasing if "
281  "resnum_alignments are enabled")
282 
283  if usalign_exec is not None:
284  if not os.path.exists(usalign_exec):
285  raise RuntimeError(f"USalign exec ({usalign_exec}) "
286  f"not found")
287  if not os.access(usalign_exec, os.X_OK):
288  raise RuntimeError(f"USalign exec ({usalign_exec}) "
289  f"is not executable")
290 
291  self.resnum_alignmentsresnum_alignments = resnum_alignments
292  self.cad_score_execcad_score_exec = cad_score_exec
293  self.usalign_execusalign_exec = usalign_exec
294  self.lddt_no_stereocheckslddt_no_stereochecks = lddt_no_stereochecks
295  self.n_max_naiven_max_naive = n_max_naive
296  self.oumoum = oum
297  self.min_pep_lengthmin_pep_length = min_pep_length
298  self.min_nuc_lengthmin_nuc_length = min_nuc_length
299  self.lddt_add_mdl_contactslddt_add_mdl_contacts = lddt_add_mdl_contacts
300  self.dockq_capri_peptidedockq_capri_peptide = dockq_capri_peptide
301 
302  # lazily evaluated attributes
303  self._stereochecked_model_stereochecked_model = None
304  self._stereochecked_target_stereochecked_target = None
305  self._model_clashes_model_clashes = None
306  self._model_bad_bonds_model_bad_bonds = None
307  self._model_bad_angles_model_bad_angles = None
308  self._target_clashes_target_clashes = None
309  self._target_bad_bonds_target_bad_bonds = None
310  self._target_bad_angles_target_bad_angles = None
311  self._chain_mapper_chain_mapper = None
312  self._mapping_mapping = None
313  self._rigid_mapping_rigid_mapping = None
314  self._model_interface_residues_model_interface_residues = None
315  self._target_interface_residues_target_interface_residues = None
316  self._aln_aln = None
317  self._stereochecked_aln_stereochecked_aln = None
318  self._pepnuc_aln_pepnuc_aln = None
319 
320  # lazily constructed scorer objects
321  self._lddt_scorer_lddt_scorer = None
322  self._bb_lddt_scorer_bb_lddt_scorer = None
323  self._qs_scorer_qs_scorer = None
324  self._contact_scorer_contact_scorer = None
325 
326  # lazily computed scores
327  self._lddt_lddt = None
328  self._local_lddt_local_lddt = None
329  self._bb_lddt_bb_lddt = None
330  self._bb_local_lddt_bb_local_lddt = None
331  self._ilddt_ilddt = None
332 
333  self._qs_global_qs_global = None
334  self._qs_best_qs_best = None
335  self._qs_target_interfaces_qs_target_interfaces = None
336  self._qs_model_interfaces_qs_model_interfaces = None
337  self._qs_interfaces_qs_interfaces = None
338  self._per_interface_qs_global_per_interface_qs_global = None
339  self._per_interface_qs_best_per_interface_qs_best = None
340 
341  self._contact_target_interfaces_contact_target_interfaces = None
342  self._contact_model_interfaces_contact_model_interfaces = None
343  self._native_contacts_native_contacts = None
344  self._model_contacts_model_contacts = None
345  self._ics_precision_ics_precision = None
346  self._ics_recall_ics_recall = None
347  self._ics_ics = None
348  self._per_interface_ics_precision_per_interface_ics_precision = None
349  self._per_interface_ics_recall_per_interface_ics_recall = None
350  self._per_interface_ics_per_interface_ics = None
351  self._ips_precision_ips_precision = None
352  self._ips_recall_ips_recall = None
353  self._ips_ips = None
354  self._per_interface_ics_precision_per_interface_ics_precision = None
355  self._per_interface_ics_recall_per_interface_ics_recall = None
356  self._per_interface_ics_per_interface_ics = None
357 
358  self._dockq_target_interfaces_dockq_target_interfaces = None
359  self._dockq_interfaces_dockq_interfaces = None
360  self._fnat_fnat = None
361  self._fnonnat_fnonnat = None
362  self._irmsd_irmsd = None
363  self._lrmsd_lrmsd = None
364  self._nnat_nnat = None
365  self._nmdl_nmdl = None
366  self._dockq_scores_dockq_scores = None
367  self._dockq_ave_dockq_ave = None
368  self._dockq_wave_dockq_wave = None
369  self._dockq_ave_full_dockq_ave_full = None
370  self._dockq_wave_full_dockq_wave_full = None
371 
372  self._mapped_target_pos_mapped_target_pos = None
373  self._mapped_model_pos_mapped_model_pos = None
374  self._transformed_mapped_model_pos_transformed_mapped_model_pos = None
375  self._n_target_not_mapped_n_target_not_mapped = None
376  self._transform_transform = None
377 
378  self._rigid_mapped_target_pos_rigid_mapped_target_pos = None
379  self._rigid_mapped_model_pos_rigid_mapped_model_pos = None
380  self._rigid_transformed_mapped_model_pos_rigid_transformed_mapped_model_pos = None
381  self._rigid_n_target_not_mapped_rigid_n_target_not_mapped = None
382  self._rigid_transform_rigid_transform = None
383 
384  self._gdt_05_gdt_05 = None
385  self._gdt_1_gdt_1 = None
386  self._gdt_2_gdt_2 = None
387  self._gdt_4_gdt_4 = None
388  self._gdt_8_gdt_8 = None
389  self._gdtts_gdtts = None
390  self._gdtha_gdtha = None
391  self._rmsd_rmsd = None
392 
393  self._cad_score_cad_score = None
394  self._local_cad_score_local_cad_score = None
395 
396  self._patch_qs_patch_qs = None
397  self._patch_dockq_patch_dockq = None
398 
399  self._tm_score_tm_score = None
400  self._usalign_mapping_usalign_mapping = None
401 
402  if custom_mapping is not None:
403  self._mapping_mapping = self._construct_custom_mapping_construct_custom_mapping(custom_mapping)
404 
405  if custom_rigid_mapping is not None:
406  self._rigid_mapping_rigid_mapping = \
407  self._construct_custom_mapping_construct_custom_mapping(custom_rigid_mapping)
408  LogDebug("Scorer sucessfully initialized")
409 
410  @property
411  def model(self):
412  """ Model with Molck cleanup
413 
414  :type: :class:`ost.mol.EntityHandle`
415  """
416  return self._model_model
417 
418  @property
419  def model_orig(self):
420  """ The original model passed at object construction
421 
422  :type: :class:`ost.mol.EntityHandle`/:class:`ost.mol.EntityView`
423  """
424  return self._model_orig_model_orig
425 
426  @property
427  def target(self):
428  """ Target with Molck cleanup
429 
430  :type: :class:`ost.mol.EntityHandle`
431  """
432  return self._target_target
433 
434  @property
435  def target_orig(self):
436  """ The original target passed at object construction
437 
438  :type: :class:`ost.mol.EntityHandle`/:class:`ost.mol.EntityView`
439  """
440  return self._target_orig_target_orig
441 
442  @property
443  def aln(self):
444  """ Alignments of :attr:`model`/:attr:`target` chains
445 
446  Alignments for each pair of chains mapped in :attr:`mapping`.
447  First sequence is target sequence, second sequence the model sequence.
448 
449  :type: :class:`list` of :class:`ost.seq.AlignmentHandle`
450  """
451  if self._aln_aln is None:
452  self._compute_aln_compute_aln()
453  return self._aln_aln
454 
455  @property
456  def stereochecked_aln(self):
457  """ Stereochecked equivalent of :attr:`aln`
458 
459  The alignments may differ, as stereochecks potentially remove residues
460 
461  :type: :class:`list` of :class:`ost.seq.AlignmentHandle`
462  """
463  if self._stereochecked_aln_stereochecked_aln is None:
464  self._compute_stereochecked_aln_compute_stereochecked_aln()
465  return self._stereochecked_aln_stereochecked_aln
466 
467  @property
468  def pepnuc_aln(self):
469  """ Alignments of :attr:`model_orig`/:attr:`target_orig` chains
470 
471  Selects for peptide and nucleotide residues before sequence
472  extraction. Includes residues that would be removed by molck in
473  structure preprocessing.
474 
475  :type: :class:`list` of :class:`ost.seq.AlignmentHandle`
476  """
477  if self._pepnuc_aln_pepnuc_aln is None:
478  self._compute_pepnuc_aln_compute_pepnuc_aln()
479  return self._pepnuc_aln_pepnuc_aln
480 
481  @property
483  """ View of :attr:`~model` that has stereochemistry checks applied
484 
485  First, a selection for peptide/nucleotide residues is performed,
486  secondly peptide sidechains with stereochemical irregularities are
487  removed (full residue if backbone atoms are involved). Irregularities
488  are clashes or bond lengths/angles more than 12 standard deviations
489  from expected values.
490 
491  :type: :class:`ost.mol.EntityView`
492  """
493  if self._stereochecked_model_stereochecked_model is None:
494  self._do_stereochecks_do_stereochecks()
495  return self._stereochecked_model_stereochecked_model
496 
497  @property
498  def model_clashes(self):
499  """ Clashing model atoms
500 
501  :type: :class:`list` of :class:`ost.mol.alg.stereochemistry.ClashInfo`
502  """
503  if self._model_clashes_model_clashes is None:
504  self._do_stereochecks_do_stereochecks()
505  return self._model_clashes_model_clashes
506 
507  @property
508  def model_bad_bonds(self):
509  """ Model bonds with unexpected stereochemistry
510 
511  :type: :class:`list` of
512  :class:`ost.mol.alg.stereochemistry.BondViolationInfo`
513  """
514  if self._model_bad_bonds_model_bad_bonds is None:
515  self._do_stereochecks_do_stereochecks()
516  return self._model_bad_bonds_model_bad_bonds
517 
518  @property
519  def model_bad_angles(self):
520  """ Model angles with unexpected stereochemistry
521 
522  :type: :class:`list` of
523  :class:`ost.mol.alg.stereochemistry.AngleViolationInfo`
524  """
525  if self._model_bad_angles_model_bad_angles is None:
526  self._do_stereochecks_do_stereochecks()
527  return self._model_bad_angles_model_bad_angles
528 
529  @property
531  """ Same as :attr:`~stereochecked_model` for :attr:`~target`
532 
533  :type: :class:`ost.mol.EntityView`
534  """
535  if self._stereochecked_target_stereochecked_target is None:
536  self._do_stereochecks_do_stereochecks()
537  return self._stereochecked_target_stereochecked_target
538 
539  @property
540  def target_clashes(self):
541  """ Clashing target atoms
542 
543  :type: :class:`list` of :class:`ost.mol.alg.stereochemistry.ClashInfo`
544  """
545  if self._target_clashes_target_clashes is None:
546  self._do_stereochecks_do_stereochecks()
547  return self._target_clashes_target_clashes
548 
549  @property
550  def target_bad_bonds(self):
551  """ Target bonds with unexpected stereochemistry
552 
553  :type: :class:`list` of
554  :class:`ost.mol.alg.stereochemistry.BondViolationInfo`
555  """
556  if self._target_bad_bonds_target_bad_bonds is None:
557  self._do_stereochecks_do_stereochecks()
558  return self._target_bad_bonds_target_bad_bonds
559 
560  @property
561  def target_bad_angles(self):
562  """ Target angles with unexpected stereochemistry
563 
564  :type: :class:`list` of
565  :class:`ost.mol.alg.stereochemistry.AngleViolationInfo`
566  """
567  if self._target_bad_angles_target_bad_angles is None:
568  self._do_stereochecks_do_stereochecks()
569  return self._target_bad_angles_target_bad_angles
570 
571  @property
572  def chain_mapper(self):
573  """ Chain mapper object for given :attr:`target`
574 
575  :type: :class:`ost.mol.alg.chain_mapping.ChainMapper`
576  """
577  if self._chain_mapper_chain_mapper is None:
578  self._chain_mapper_chain_mapper = chain_mapping.ChainMapper(self.targettarget,
579  n_max_naive=1e9,
580  resnum_alignments=self.resnum_alignmentsresnum_alignments,
581  min_pep_length=self.min_pep_lengthmin_pep_length,
582  min_nuc_length=self.min_nuc_lengthmin_nuc_length)
583  return self._chain_mapper_chain_mapper
584 
585  @property
586  def mapping(self):
587  """ Full chain mapping result for :attr:`target`/:attr:`model`
588 
589  Computed with :func:`ost.mol.alg.ChainMapper.GetMapping`
590 
591  :type: :class:`ost.mol.alg.chain_mapping.MappingResult`
592  """
593  if self._mapping_mapping is None:
594  LogScript("Computing chain mapping")
595  self._mapping_mapping = \
596  self.chain_mapperchain_mapper.GetMapping(self.modelmodel,
597  n_max_naive = self.n_max_naiven_max_naive)
598  return self._mapping_mapping
599 
600  @property
601  def rigid_mapping(self):
602  """ Full chain mapping result for :attr:`target`/:attr:`model`
603 
604  Computed with :func:`ost.mol.alg.ChainMapper.GetRMSDMapping`
605 
606  :type: :class:`ost.mol.alg.chain_mapping.MappingResult`
607  """
608  if self._rigid_mapping_rigid_mapping is None:
609  LogScript("Computing rigid chain mapping")
610  self._rigid_mapping_rigid_mapping = \
611  self.chain_mapperchain_mapper.GetRMSDMapping(self.modelmodel)
612  return self._rigid_mapping_rigid_mapping
613 
614  @property
616  """ Interface residues in :attr:`~model`
617 
618  Thats all residues having a contact with at least one residue from
619  another chain (CB-CB distance <= 8A, CA in case of Glycine)
620 
621  :type: :class:`dict` with chain names as key and and :class:`list`
622  with residue numbers of the respective interface residues.
623  """
624  if self._model_interface_residues_model_interface_residues is None:
625  self._model_interface_residues_model_interface_residues = \
626  self._get_interface_residues_get_interface_residues(self.modelmodel)
627  return self._model_interface_residues_model_interface_residues
628 
629  @property
631  """ Same as :attr:`~model_interface_residues` for :attr:`~target`
632 
633  :type: :class:`dict` with chain names as key and and :class:`list`
634  with residue numbers of the respective interface residues.
635  """
636  if self._target_interface_residues_target_interface_residues is None:
637  self._target_interface_residues_target_interface_residues = \
638  self._get_interface_residues_get_interface_residues(self.targettarget)
639  return self._target_interface_residues_target_interface_residues
640 
641  @property
642  def lddt_scorer(self):
643  """ lDDT scorer for :attr:`~stereochecked_target` (default parameters)
644 
645  :type: :class:`ost.mol.alg.lddt.lDDTScorer`
646  """
647  if self._lddt_scorer_lddt_scorer is None:
648  if self.lddt_no_stereocheckslddt_no_stereochecks:
649  self._lddt_scorer_lddt_scorer = lDDTScorer(self.targettarget)
650  else:
651  self._lddt_scorer_lddt_scorer = lDDTScorer(self.stereochecked_targetstereochecked_target)
652  return self._lddt_scorer_lddt_scorer
653 
654  @property
655  def bb_lddt_scorer(self):
656  """ Backbone only lDDT scorer for :attr:`~target`
657 
658  No stereochecks applied for bb only lDDT which considers CA atoms
659  for peptides and C3' atoms for nucleotides.
660 
661  :type: :class:`ost.mol.alg.lddt.lDDTScorer`
662  """
663  if self._bb_lddt_scorer_bb_lddt_scorer is None:
664  self._bb_lddt_scorer_bb_lddt_scorer = lDDTScorer(self.targettarget, bb_only=True)
665  return self._bb_lddt_scorer_bb_lddt_scorer
666 
667  @property
668  def qs_scorer(self):
669  """ QS scorer constructed from :attr:`~mapping`
670 
671  The scorer object is constructed with default parameters and relates to
672  :attr:`~model` and :attr:`~target` (no stereochecks).
673 
674  :type: :class:`ost.mol.alg.qsscore.QSScorer`
675  """
676  if self._qs_scorer_qs_scorer is None:
677  self._qs_scorer_qs_scorer = QSScorer.FromMappingResult(self.mappingmapping)
678  return self._qs_scorer_qs_scorer
679 
680  @property
681  def contact_scorer(self):
682  if self._contact_scorer_contact_scorer is None:
683  self._contact_scorer_contact_scorer = ContactScorer.FromMappingResult(self.mappingmapping)
684  return self._contact_scorer_contact_scorer
685 
686 
687  @property
688  def lddt(self):
689  """ Global lDDT score in range [0.0, 1.0]
690 
691  Computed based on :attr:`~stereochecked_model`. In case of oligomers,
692  :attr:`~mapping` is used.
693 
694  :type: :class:`float`
695  """
696  if self._lddt_lddt is None:
697  self._compute_lddt_compute_lddt()
698  return self._lddt_lddt
699 
700  @property
701  def local_lddt(self):
702  """ Per residue lDDT scores in range [0.0, 1.0]
703 
704  Computed based on :attr:`~stereochecked_model` but scores for all
705  residues in :attr:`~model` are reported. If a residue has been removed
706  by stereochemistry checks, the respective score is set to 0.0. If a
707  residue is not covered by the target or is in a chain skipped by the
708  chain mapping procedure (happens for super short chains), the respective
709  score is set to None. In case of oligomers, :attr:`~mapping` is used.
710 
711  :type: :class:`dict`
712  """
713  if self._local_lddt_local_lddt is None:
714  self._compute_lddt_compute_lddt()
715  return self._local_lddt_local_lddt
716 
717  @property
718  def bb_lddt(self):
719  """ Backbone only global lDDT score in range [0.0, 1.0]
720 
721  Computed based on :attr:`~model` on backbone atoms only. This is CA for
722  peptides and C3' for nucleotides. No stereochecks are performed. In case
723  of oligomers, :attr:`~mapping` is used.
724 
725  :type: :class:`float`
726  """
727  if self._bb_lddt_bb_lddt is None:
728  self._compute_bb_lddt_compute_bb_lddt()
729  return self._bb_lddt_bb_lddt
730 
731  @property
732  def bb_local_lddt(self):
733  """ Backbone only per residue lDDT scores in range [0.0, 1.0]
734 
735  Computed based on :attr:`~model` on backbone atoms only. This is CA for
736  peptides and C3' for nucleotides. No stereochecks are performed. If a
737  residue is not covered by the target or is in a chain skipped by the
738  chain mapping procedure (happens for super short chains), the respective
739  score is set to None. In case of oligomers, :attr:`~mapping` is used.
740 
741  :type: :class:`dict`
742  """
743  if self._bb_local_lddt_bb_local_lddt is None:
744  self._compute_bb_lddt_compute_bb_lddt()
745  return self._bb_local_lddt_bb_local_lddt
746 
747  @property
748  def ilddt(self):
749  """ Global interface lDDT score in range [0.0, 1.0]
750 
751  This is lDDT only based on inter-chain contacts. Value is None if no
752  such contacts are present. For example if we're dealing with a monomer.
753  Computed based on :attr:`~stereochecked_model` and :attr:`~mapping` for
754  chain mapping.
755 
756  :type: :class:`float`
757  """
758  if self._ilddt_ilddt is None:
759  # the whole None business kind of invalidates the idea of lazy
760  # evaluation. The assumption is that this is called only once...
761  self._compute_ilddt_compute_ilddt()
762  return self._ilddt_ilddt
763 
764 
765  @property
766  def qs_global(self):
767  """ Global QS-score
768 
769  Computed based on :attr:`model` using :attr:`mapping`
770 
771  :type: :class:`float`
772  """
773  if self._qs_global_qs_global is None:
774  self._compute_qs_compute_qs()
775  return self._qs_global_qs_global
776 
777  @property
778  def qs_best(self):
779  """ Global QS-score - only computed on aligned residues
780 
781  Computed based on :attr:`model` using :attr:`mapping`. The QS-score
782  computation only considers contacts between residues with a mapping
783  between target and model. As a result, the score won't be lowered in
784  case of additional chains/residues in any of the structures.
785 
786  :type: :class:`float`
787  """
788  if self._qs_best_qs_best is None:
789  self._compute_qs_compute_qs()
790  return self._qs_best_qs_best
791 
792  @property
794  """ Interfaces in :attr:`~target` with non-zero contribution to
795  :attr:`~qs_global`/:attr:`~qs_best`
796 
797  Chain names are lexicographically sorted.
798 
799  :type: :class:`list` of :class:`tuple` with 2 elements each:
800  (trg_ch1, trg_ch2)
801  """
802  if self._qs_target_interfaces_qs_target_interfaces is None:
803  self._qs_target_interfaces_qs_target_interfaces = self.qs_scorerqs_scorer.qsent1.interacting_chains
804  self._qs_target_interfaces_qs_target_interfaces = \
805  [(min(x[0],x[1]), max(x[0],x[1])) for x in self._qs_target_interfaces_qs_target_interfaces]
806  return self._qs_target_interfaces_qs_target_interfaces
807 
808  @property
810  """ Interfaces in :attr:`~model` with non-zero contribution to
811  :attr:`~qs_global`/:attr:`~qs_best`
812 
813  Chain names are lexicographically sorted.
814 
815  :type: :class:`list` of :class:`tuple` with 2 elements each:
816  (mdl_ch1, mdl_ch2)
817  """
818  if self._qs_model_interfaces_qs_model_interfaces is None:
819  self._qs_model_interfaces_qs_model_interfaces = self.qs_scorerqs_scorer.qsent2.interacting_chains
820  self._qs_model_interfaces_qs_model_interfaces = \
821  [(min(x[0],x[1]), max(x[0],x[1])) for x in self._qs_model_interfaces_qs_model_interfaces]
822 
823  return self._qs_model_interfaces_qs_model_interfaces
824 
825  @property
826  def qs_interfaces(self):
827  """ Interfaces in :attr:`~qs_target_interfaces` that can be mapped
828  to :attr:`~model`.
829 
830  Target chain names are lexicographically sorted.
831 
832  :type: :class:`list` of :class:`tuple` with 4 elements each:
833  (trg_ch1, trg_ch2, mdl_ch1, mdl_ch2)
834  """
835  if self._qs_interfaces_qs_interfaces is None:
836  self._qs_interfaces_qs_interfaces = list()
837  flat_mapping = self.mappingmapping.GetFlatMapping()
838  for i in self.qs_target_interfacesqs_target_interfaces:
839  if i[0] in flat_mapping and i[1] in flat_mapping:
840  self._qs_interfaces_qs_interfaces.append((i[0], i[1],
841  flat_mapping[i[0]],
842  flat_mapping[i[1]]))
843  return self._qs_interfaces_qs_interfaces
844 
845  @property
847  """ QS-score for each interface in :attr:`qs_interfaces`
848 
849  :type: :class:`list` of :class:`float`
850  """
851  if self._per_interface_qs_global_per_interface_qs_global is None:
852  self._compute_per_interface_qs_scores_compute_per_interface_qs_scores()
853  return self._per_interface_qs_global_per_interface_qs_global
854 
855  @property
857  """ QS-score for each interface in :attr:`qs_interfaces`
858 
859  Only computed on aligned residues
860 
861  :type: :class:`list` of :class:`float`
862  """
863  if self._per_interface_qs_best_per_interface_qs_best is None:
864  self._compute_per_interface_qs_scores_compute_per_interface_qs_scores()
865  return self._per_interface_qs_best_per_interface_qs_best
866 
867  @property
868  def native_contacts(self):
869  """ Native contacts
870 
871  A contact is a pair or residues from distinct chains that have
872  a minimal heavy atom distance < 5A. Contacts are specified as
873  :class:`tuple` with two strings in format:
874  <cname>.<rnum>.<ins_code>
875 
876  :type: :class:`list` of :class:`tuple`
877  """
878  if self._native_contacts_native_contacts is None:
879  self._native_contacts_native_contacts = self.contact_scorercontact_scorer.cent1.hr_contacts
880  return self._native_contacts_native_contacts
881 
882  @property
883  def model_contacts(self):
884  """ Same for model
885  """
886  if self._model_contacts_model_contacts is None:
887  self._model_contacts_model_contacts = self.contact_scorercontact_scorer.cent2.hr_contacts
888  return self._model_contacts_model_contacts
889 
890  @property
892  """ Interfaces in :class:`target` which have at least one contact
893 
894  Contact as defined in :attr:`~native_contacts`,
895  chain names are lexicographically sorted.
896 
897  :type: :class:`list` of :class:`tuple` with 2 elements each
898  (trg_ch1, trg_ch2)
899  """
900  if self._contact_target_interfaces_contact_target_interfaces is None:
901  tmp = self.contact_scorercontact_scorer.cent1.interacting_chains
902  tmp = [(min(x[0],x[1]), max(x[0],x[1])) for x in tmp]
903  self._contact_target_interfaces_contact_target_interfaces = tmp
904  return self._contact_target_interfaces_contact_target_interfaces
905 
906  @property
908  """ Interfaces in :class:`model` which have at least one contact
909 
910  Contact as defined in :attr:`~native_contacts`,
911  chain names are lexicographically sorted.
912 
913  :type: :class:`list` of :class:`tuple` with 2 elements each
914  (mdl_ch1, mdl_ch2)
915  """
916  if self._contact_model_interfaces_contact_model_interfaces is None:
917  tmp = self.contact_scorercontact_scorer.cent2.interacting_chains
918  tmp = [(min(x[0],x[1]), max(x[0],x[1])) for x in tmp]
919  self._contact_model_interfaces_contact_model_interfaces = tmp
920  return self._contact_model_interfaces_contact_model_interfaces
921 
922  @property
923  def ics_precision(self):
924  """ Fraction of model contacts that are also present in target
925 
926  :type: :class:`float`
927  """
928  if self._ics_precision_ics_precision is None:
929  self._compute_ics_scores_compute_ics_scores()
930  return self._ics_precision_ics_precision
931 
932  @property
933  def ics_recall(self):
934  """ Fraction of target contacts that are correctly reproduced in model
935 
936  :type: :class:`float`
937  """
938  if self._ics_recall_ics_recall is None:
939  self._compute_ics_scores_compute_ics_scores()
940  return self._ics_recall_ics_recall
941 
942  @property
943  def ics(self):
944  """ ICS (Interface Contact Similarity) score
945 
946  Combination of :attr:`~ics_precision` and :attr:`~ics_recall`
947  using the F1-measure
948 
949  :type: :class:`float`
950  """
951  if self._ics_ics is None:
952  self._compute_ics_scores_compute_ics_scores()
953  return self._ics_ics
954 
955  @property
957  """ Per-interface ICS precision
958 
959  :attr:`~ics_precision` for each interface in
960  :attr:`~contact_target_interfaces`
961 
962  :type: :class:`list` of :class:`float`
963  """
964  if self._per_interface_ics_precision_per_interface_ics_precision is None:
965  self._compute_ics_scores_compute_ics_scores()
966  return self._per_interface_ics_precision_per_interface_ics_precision
967 
968 
969  @property
971  """ Per-interface ICS recall
972 
973  :attr:`~ics_recall` for each interface in
974  :attr:`~contact_target_interfaces`
975 
976  :type: :class:`list` of :class:`float`
977  """
978  if self._per_interface_ics_recall_per_interface_ics_recall is None:
979  self._compute_ics_scores_compute_ics_scores()
980  return self._per_interface_ics_recall_per_interface_ics_recall
981 
982  @property
983  def per_interface_ics(self):
984  """ Per-interface ICS (Interface Contact Similarity) score
985 
986  :attr:`~ics` for each interface in
987  :attr:`~contact_target_interfaces`
988 
989  :type: :class:`float`
990  """
991 
992  if self._per_interface_ics_per_interface_ics is None:
993  self._compute_ics_scores_compute_ics_scores()
994  return self._per_interface_ics_per_interface_ics
995 
996 
997  @property
998  def ips_precision(self):
999  """ Fraction of model interface residues that are also interface
1000  residues in target
1001 
1002  :type: :class:`float`
1003  """
1004  if self._ips_precision_ips_precision is None:
1005  self._compute_ips_scores_compute_ips_scores()
1006  return self._ips_precision_ips_precision
1007 
1008  @property
1009  def ips_recall(self):
1010  """ Fraction of target interface residues that are also interface
1011  residues in model
1012 
1013  :type: :class:`float`
1014  """
1015  if self._ips_recall_ips_recall is None:
1016  self._compute_ips_scores_compute_ips_scores()
1017  return self._ips_recall_ips_recall
1018 
1019  @property
1020  def ips(self):
1021  """ IPS (Interface Patch Similarity) score
1022 
1023  Jaccard coefficient of interface residues in target and their mapped
1024  counterparts in model
1025 
1026  :type: :class:`float`
1027  """
1028  if self._ips_ips is None:
1029  self._compute_ips_scores_compute_ips_scores()
1030  return self._ips_ips
1031 
1032  @property
1034  """ Per-interface IPS precision
1035 
1036  :attr:`~ips_precision` for each interface in
1037  :attr:`~contact_target_interfaces`
1038 
1039  :type: :class:`list` of :class:`float`
1040  """
1041  if self._per_interface_ips_precision_per_interface_ips_precision is None:
1042  self._compute_ips_scores_compute_ips_scores()
1043  return self._per_interface_ips_precision_per_interface_ips_precision
1044 
1045 
1046  @property
1048  """ Per-interface IPS recall
1049 
1050  :attr:`~ips_recall` for each interface in
1051  :attr:`~contact_target_interfaces`
1052 
1053  :type: :class:`list` of :class:`float`
1054  """
1055  if self._per_interface_ics_recall_per_interface_ics_recall is None:
1056  self._compute_ips_scores_compute_ips_scores()
1057  return self._per_interface_ips_recall_per_interface_ips_recall
1058 
1059  @property
1061  """ Per-interface IPS (Interface Patch Similarity) score
1062 
1063  :attr:`~ips` for each interface in
1064  :attr:`~contact_target_interfaces`
1065 
1066  :type: :class:`list` of :class:`float`
1067  """
1068 
1069  if self._per_interface_ips_per_interface_ips is None:
1070  self._compute_ips_scores_compute_ips_scores()
1071  return self._per_interface_ips_per_interface_ips
1072 
1073  @property
1075  """ Interfaces in :attr:`target` that are relevant for DockQ
1076 
1077  All interfaces in :attr:`~target` with non-zero contacts that are
1078  relevant for DockQ. Chain names are lexicographically sorted.
1079 
1080  :type: :class:`list` of :class:`tuple` with 2 elements each:
1081  (trg_ch1, trg_ch2)
1082  """
1083  if self._dockq_target_interfaces_dockq_target_interfaces is None:
1084 
1085  # interacting chains are identified with ContactEntity
1086  contact_d = 5.0
1087  if self.dockq_capri_peptidedockq_capri_peptide:
1088  contact_d = 4.0
1089  cent = ContactEntity(self.targettarget, contact_mode = "aa",
1090  contact_d = contact_d)
1091 
1092  # fetch lexicographically sorted interfaces
1093  interfaces = cent.interacting_chains
1094  interfaces = [(min(x[0],x[1]), max(x[0],x[1])) for x in interfaces]
1095 
1096  # select the ones with only peptides involved
1097  pep_seqs = set([s.GetName() for s in self.chain_mapperchain_mapper.polypep_seqs])
1098  self._dockq_target_interfaces_dockq_target_interfaces = list()
1099  for interface in interfaces:
1100  if interface[0] in pep_seqs and interface[1] in pep_seqs:
1101  self._dockq_target_interfaces_dockq_target_interfaces.append(interface)
1102 
1103  return self._dockq_target_interfaces_dockq_target_interfaces
1104 
1105  @property
1106  def dockq_interfaces(self):
1107  """ Interfaces in :attr:`dockq_target_interfaces` that can be mapped
1108  to model
1109 
1110  Target chain names are lexicographically sorted
1111 
1112  :type: :class:`list` of :class:`tuple` with 4 elements each:
1113  (trg_ch1, trg_ch2, mdl_ch1, mdl_ch2)
1114  """
1115  if self._dockq_interfaces_dockq_interfaces is None:
1116  self._dockq_interfaces_dockq_interfaces = list()
1117  flat_mapping = self.mappingmapping.GetFlatMapping()
1118  for i in self.dockq_target_interfacesdockq_target_interfaces:
1119  if i[0] in flat_mapping and i[1] in flat_mapping:
1120  self._dockq_interfaces_dockq_interfaces.append((i[0], i[1],
1121  flat_mapping[i[0]],
1122  flat_mapping[i[1]]))
1123  return self._dockq_interfaces_dockq_interfaces
1124 
1125  @property
1126  def dockq_scores(self):
1127  """ DockQ scores for interfaces in :attr:`~dockq_interfaces`
1128 
1129  :class:`list` of :class:`float`
1130  """
1131  if self._dockq_scores_dockq_scores is None:
1132  self._compute_dockq_scores_compute_dockq_scores()
1133  return self._dockq_scores_dockq_scores
1134 
1135  @property
1136  def fnat(self):
1137  """ fnat scores for interfaces in :attr:`~dockq_interfaces`
1138 
1139  fnat: Fraction of native contacts that are also present in model
1140 
1141  :class:`list` of :class:`float`
1142  """
1143  if self._fnat_fnat is None:
1144  self._compute_dockq_scores_compute_dockq_scores()
1145  return self._fnat_fnat
1146 
1147  @property
1148  def nnat(self):
1149  """ N native contacts for interfaces in :attr:`~dockq_interfaces`
1150 
1151  :class:`list` of :class:`int`
1152  """
1153  if self._nnat_nnat is None:
1154  self._compute_dockq_scores_compute_dockq_scores()
1155  return self._nnat_nnat
1156 
1157  @property
1158  def nmdl(self):
1159  """ N model contacts for interfaces in :attr:`~dockq_interfaces`
1160 
1161  :class:`list` of :class:`int`
1162  """
1163  if self._nmdl_nmdl is None:
1164  self._compute_dockq_scores_compute_dockq_scores()
1165  return self._nmdl_nmdl
1166 
1167  @property
1168  def fnonnat(self):
1169  """ fnonnat scores for interfaces in :attr:`~dockq_interfaces`
1170 
1171  fnat: Fraction of model contacts that are not present in target
1172 
1173  :class:`list` of :class:`float`
1174  """
1175  if self._fnonnat_fnonnat is None:
1176  self._compute_dockq_scores_compute_dockq_scores()
1177  return self._fnonnat_fnonnat
1178 
1179  @property
1180  def irmsd(self):
1181  """ irmsd scores for interfaces in :attr:`~dockq_interfaces`
1182 
1183  irmsd: RMSD of interface (RMSD computed on N, CA, C, O atoms) which
1184  consists of each residue that has at least one heavy atom within 10A of
1185  other chain.
1186 
1187  :class:`list` of :class:`float`
1188  """
1189  if self._irmsd_irmsd is None:
1190  self._compute_dockq_scores_compute_dockq_scores()
1191  return self._irmsd_irmsd
1192 
1193  @property
1194  def lrmsd(self):
1195  """ lrmsd scores for interfaces in :attr:`~dockq_interfaces`
1196 
1197  lrmsd: The interfaces are superposed based on the receptor (rigid
1198  min RMSD superposition) and RMSD for the ligand is reported.
1199  Superposition and RMSD are based on N, CA, C and O positions,
1200  receptor is the chain contributing to the interface with more
1201  residues in total.
1202 
1203  :class:`list` of :class:`float`
1204  """
1205  if self._lrmsd_lrmsd is None:
1206  self._compute_dockq_scores_compute_dockq_scores()
1207  return self._lrmsd_lrmsd
1208 
1209  @property
1210  def dockq_ave(self):
1211  """ Average of DockQ scores in :attr:`dockq_scores`
1212 
1213  In its original implementation, DockQ only operates on single
1214  interfaces. Thus the requirement to combine scores for higher order
1215  oligomers.
1216 
1217  :type: :class:`float`
1218  """
1219  if self._dockq_ave_dockq_ave is None:
1220  self._compute_dockq_scores_compute_dockq_scores()
1221  return self._dockq_ave_dockq_ave
1222 
1223  @property
1224  def dockq_wave(self):
1225  """ Same as :attr:`dockq_ave`, weighted by native contacts
1226 
1227  :type: :class:`float`
1228  """
1229  if self._dockq_wave_dockq_wave is None:
1230  self._compute_dockq_scores_compute_dockq_scores()
1231  return self._dockq_wave_dockq_wave
1232 
1233  @property
1234  def dockq_ave_full(self):
1235  """ Same as :attr:`~dockq_ave` but penalizing for missing interfaces
1236 
1237  Interfaces that are not covered in model are added as 0.0
1238  in average computation.
1239 
1240  :type: :class:`float`
1241  """
1242  if self._dockq_ave_full_dockq_ave_full is None:
1243  self._compute_dockq_scores_compute_dockq_scores()
1244  return self._dockq_ave_full_dockq_ave_full
1245 
1246  @property
1247  def dockq_wave_full(self):
1248  """ Same as :attr:`~dockq_ave_full`, but weighted
1249 
1250  Interfaces that are not covered in model are added as 0.0 in
1251  average computations and the respective weights are derived from
1252  number of contacts in respective target interface.
1253  """
1254  if self._dockq_wave_full_dockq_wave_full is None:
1255  self._compute_dockq_scores_compute_dockq_scores()
1256  return self._dockq_wave_full_dockq_wave_full
1257 
1258  @property
1260  """ Mapped representative positions in target
1261 
1262  Thats CA positions for peptide residues and C3' positions for
1263  nucleotides. Has same length as :attr:`~mapped_model_pos` and mapping
1264  is based on :attr:`~mapping`.
1265 
1266  :type: :class:`ost.geom.Vec3List`
1267  """
1268  if self._mapped_target_pos_mapped_target_pos is None:
1269  self._extract_mapped_pos_extract_mapped_pos()
1270  return self._mapped_target_pos_mapped_target_pos
1271 
1272  @property
1273  def mapped_model_pos(self):
1274  """ Mapped representative positions in model
1275 
1276  Thats CA positions for peptide residues and C3' positions for
1277  nucleotides. Has same length as :attr:`~mapped_target_pos` and mapping
1278  is based on :attr:`~mapping`.
1279 
1280  :type: :class:`ost.geom.Vec3List`
1281  """
1282  if self._mapped_model_pos_mapped_model_pos is None:
1283  self._extract_mapped_pos_extract_mapped_pos()
1284  return self._mapped_model_pos_mapped_model_pos
1285 
1286  @property
1288  """ :attr:`~mapped_model_pos` with :attr:`~transform` applied
1289 
1290  :type: :class:`ost.geom.Vec3List`
1291  """
1292  if self._transformed_mapped_model_pos_transformed_mapped_model_pos is None:
1293  self._transformed_mapped_model_pos_transformed_mapped_model_pos = \
1294  geom.Vec3List(self.mapped_model_posmapped_model_pos)
1295  self._transformed_mapped_model_pos_transformed_mapped_model_pos.ApplyTransform(self.transformtransform)
1296  return self._transformed_mapped_model_pos_transformed_mapped_model_pos
1297 
1298  @property
1300  """ Number of target residues which have no mapping to model
1301 
1302  :type: :class:`int`
1303  """
1304  if self._n_target_not_mapped_n_target_not_mapped is None:
1305  self._extract_mapped_pos_extract_mapped_pos()
1306  return self._n_target_not_mapped_n_target_not_mapped
1307 
1308  @property
1309  def transform(self):
1310  """ Transform: :attr:`~mapped_model_pos` onto :attr:`~mapped_target_pos`
1311 
1312  Computed using Kabsch minimal rmsd algorithm
1313 
1314  :type: :class:`ost.geom.Mat4`
1315  """
1316  if self._transform_transform is None:
1317  try:
1318  res = mol.alg.SuperposeSVD(self.mapped_model_posmapped_model_pos,
1319  self.mapped_target_posmapped_target_pos)
1320  self._transform_transform = res.transformation
1321  except:
1322  self._transform_transform = geom.Mat4()
1323  return self._transform_transform
1324 
1325  @property
1327  """ Mapped representative positions in target
1328 
1329  Thats CA positions for peptide residues and C3' positions for
1330  nucleotides. Has same length as :attr:`~rigid_mapped_model_pos` and mapping
1331  is based on :attr:`~rigid_mapping`.
1332 
1333  :type: :class:`ost.geom.Vec3List`
1334  """
1335  if self._rigid_mapped_target_pos_rigid_mapped_target_pos is None:
1336  self._extract_rigid_mapped_pos_extract_rigid_mapped_pos()
1337  return self._rigid_mapped_target_pos_rigid_mapped_target_pos
1338 
1339  @property
1341  """ Mapped representative positions in model
1342 
1343  Thats CA positions for peptide residues and C3' positions for
1344  nucleotides. Has same length as :attr:`~mapped_target_pos` and mapping
1345  is based on :attr:`~rigid_mapping`.
1346 
1347  :type: :class:`ost.geom.Vec3List`
1348  """
1349  if self._rigid_mapped_model_pos_rigid_mapped_model_pos is None:
1350  self._extract_rigid_mapped_pos_extract_rigid_mapped_pos()
1351  return self._rigid_mapped_model_pos_rigid_mapped_model_pos
1352 
1353  @property
1355  """ :attr:`~rigid_mapped_model_pos` with :attr:`~rigid_transform` applied
1356 
1357  :type: :class:`ost.geom.Vec3List`
1358  """
1359  if self._rigid_transformed_mapped_model_pos_rigid_transformed_mapped_model_pos is None:
1360  self._rigid_transformed_mapped_model_pos_rigid_transformed_mapped_model_pos = \
1361  geom.Vec3List(self.rigid_mapped_model_posrigid_mapped_model_pos)
1362  self._rigid_transformed_mapped_model_pos_rigid_transformed_mapped_model_pos.ApplyTransform(self.rigid_transformrigid_transform)
1363  return self._rigid_transformed_mapped_model_pos_rigid_transformed_mapped_model_pos
1364 
1365  @property
1367  """ Number of target residues which have no rigid mapping to model
1368 
1369  :type: :class:`int`
1370  """
1371  if self._rigid_n_target_not_mapped_rigid_n_target_not_mapped is None:
1372  self._extract_rigid_mapped_pos_extract_rigid_mapped_pos()
1373  return self._rigid_n_target_not_mapped_rigid_n_target_not_mapped
1374 
1375  @property
1376  def rigid_transform(self):
1377  """ Transform: :attr:`~rigid_mapped_model_pos` onto :attr:`~rigid_mapped_target_pos`
1378 
1379  Computed using Kabsch minimal rmsd algorithm
1380 
1381  :type: :class:`ost.geom.Mat4`
1382  """
1383  if self._rigid_transform_rigid_transform is None:
1384  try:
1385  res = mol.alg.SuperposeSVD(self.rigid_mapped_model_posrigid_mapped_model_pos,
1386  self.rigid_mapped_target_posrigid_mapped_target_pos)
1387  self._rigid_transform_rigid_transform = res.transformation
1388  except:
1389  self._rigid_transform_rigid_transform = geom.Mat4()
1390  return self._rigid_transform_rigid_transform
1391 
1392  @property
1393  def gdt_05(self):
1394  """ Fraction CA (C3' for nucleotides) that can be superposed within 0.5A
1395 
1396  Uses :attr:`~rigid_mapped_model_pos` and :attr:`~rigid_mapped_target_pos`.
1397  Similar iterative algorithm as LGA tool
1398 
1399  :type: :class:`float`
1400  """
1401  if self._gdt_05_gdt_05 is None:
1402  n, m = GDT(self.rigid_mapped_model_posrigid_mapped_model_pos, self.rigid_mapped_target_posrigid_mapped_target_pos, 7, 1000, 0.5)
1403  n_full = len(self.rigid_mapped_target_posrigid_mapped_target_pos) + self.rigid_n_target_not_mappedrigid_n_target_not_mapped
1404  if n_full > 0:
1405  self._gdt_05_gdt_05 = float(n) / n_full
1406  else:
1407  self._gdt_05_gdt_05 = 0.0
1408  return self._gdt_05_gdt_05
1409 
1410  @property
1411  def gdt_1(self):
1412  """ Fraction CA (C3' for nucleotides) that can be superposed within 1.0A
1413 
1414  Uses :attr:`~rigid_mapped_model_pos` and :attr:`~rigid_mapped_target_pos`.
1415  Similar iterative algorithm as LGA tool
1416 
1417  :type: :class:`float`
1418  """
1419  if self._gdt_1_gdt_1 is None:
1420  n, m = GDT(self.rigid_mapped_model_posrigid_mapped_model_pos, self.rigid_mapped_target_posrigid_mapped_target_pos, 7, 1000, 1.0)
1421  n_full = len(self.rigid_mapped_target_posrigid_mapped_target_pos) + self.rigid_n_target_not_mappedrigid_n_target_not_mapped
1422  if n_full > 0:
1423  self._gdt_1_gdt_1 = float(n) / n_full
1424  else:
1425  self._gdt_1_gdt_1 = 0.0
1426  return self._gdt_1_gdt_1
1427 
1428  @property
1429  def gdt_2(self):
1430  """ Fraction CA (C3' for nucleotides) that can be superposed within 2.0A
1431 
1432  Uses :attr:`~rigid_mapped_model_pos` and :attr:`~rigid_mapped_target_pos`.
1433  Similar iterative algorithm as LGA tool
1434 
1435 
1436  :type: :class:`float`
1437  """
1438  if self._gdt_2_gdt_2 is None:
1439  n, m = GDT(self.rigid_mapped_model_posrigid_mapped_model_pos, self.rigid_mapped_target_posrigid_mapped_target_pos, 7, 1000, 2.0)
1440  n_full = len(self.rigid_mapped_target_posrigid_mapped_target_pos) + self.rigid_n_target_not_mappedrigid_n_target_not_mapped
1441  if n_full > 0:
1442  self._gdt_2_gdt_2 = float(n) / n_full
1443  else:
1444  self._gdt_2_gdt_2 = 0.0
1445  return self._gdt_2_gdt_2
1446 
1447  @property
1448  def gdt_4(self):
1449  """ Fraction CA (C3' for nucleotides) that can be superposed within 4.0A
1450 
1451  Uses :attr:`~rigid_mapped_model_pos` and :attr:`~rigid_mapped_target_pos`.
1452  Similar iterative algorithm as LGA tool
1453 
1454  :type: :class:`float`
1455  """
1456  if self._gdt_4_gdt_4 is None:
1457  n, m = GDT(self.rigid_mapped_model_posrigid_mapped_model_pos, self.rigid_mapped_target_posrigid_mapped_target_pos, 7, 1000, 4.0)
1458  n_full = len(self.rigid_mapped_target_posrigid_mapped_target_pos) + self.rigid_n_target_not_mappedrigid_n_target_not_mapped
1459  if n_full > 0:
1460  self._gdt_4_gdt_4 = float(n) / n_full
1461  else:
1462  self._gdt_4_gdt_4 = 0.0
1463  return self._gdt_4_gdt_4
1464 
1465  @property
1466  def gdt_8(self):
1467  """ Fraction CA (C3' for nucleotides) that can be superposed within 8.0A
1468 
1469  Similar iterative algorithm as LGA tool
1470 
1471  :type: :class:`float`
1472  """
1473  if self._gdt_8_gdt_8 is None:
1474  n, m = GDT(self.rigid_mapped_model_posrigid_mapped_model_pos, self.rigid_mapped_target_posrigid_mapped_target_pos, 7, 1000, 8.0)
1475  n_full = len(self.rigid_mapped_target_posrigid_mapped_target_pos) + self.rigid_n_target_not_mappedrigid_n_target_not_mapped
1476  if n_full > 0:
1477  self._gdt_8_gdt_8 = float(n) / n_full
1478  else:
1479  self._gdt_8_gdt_8 = 0.0
1480  return self._gdt_8_gdt_8
1481 
1482 
1483  @property
1484  def gdtts(self):
1485  """ avg GDT with thresholds: 8.0A, 4.0A, 2.0A and 1.0A
1486 
1487  :type: :class:`float`
1488  """
1489  if self._gdtts_gdtts is None:
1490  LogScript("Computing GDT-TS score")
1491  self._gdtts_gdtts = (self.gdt_1gdt_1 + self.gdt_2gdt_2 + self.gdt_4gdt_4 + self.gdt_8gdt_8) / 4
1492  return self._gdtts_gdtts
1493 
1494  @property
1495  def gdtha(self):
1496  """ avg GDT with thresholds: 4.0A, 2.0A, 1.0A and 0.5A
1497 
1498  :type: :class:`float`
1499  """
1500  if self._gdtha_gdtha is None:
1501  LogScript("Computing GDT-HA score")
1502  self._gdtha_gdtha = (self.gdt_05gdt_05 + self.gdt_1gdt_1 + self.gdt_2gdt_2 + self.gdt_4gdt_4) / 4
1503  return self._gdtha_gdtha
1504 
1505  @property
1506  def rmsd(self):
1507  """ RMSD
1508 
1509  Computed on :attr:`~rigid_transformed_mapped_model_pos` and
1510  :attr:`rigid_mapped_target_pos`
1511 
1512  :type: :class:`float`
1513  """
1514  if self._rmsd_rmsd is None:
1515  LogScript("Computing RMSD")
1516  self._rmsd_rmsd = \
1517  self.rigid_mapped_target_posrigid_mapped_target_pos.GetRMSD(self.rigid_transformed_mapped_model_posrigid_transformed_mapped_model_pos)
1518  return self._rmsd_rmsd
1519 
1520  @property
1521  def cad_score(self):
1522  """ The global CAD atom-atom (AA) score
1523 
1524  Computed based on :attr:`~model`. In case of oligomers, :attr:`~mapping`
1525  is used.
1526 
1527  :type: :class:`float`
1528  """
1529  if self._cad_score_cad_score is None:
1530  self._compute_cad_score_compute_cad_score()
1531  return self._cad_score_cad_score
1532 
1533  @property
1534  def local_cad_score(self):
1535  """ The per-residue CAD atom-atom (AA) scores
1536 
1537  Computed based on :attr:`~model`. In case of oligomers, :attr:`~mapping`
1538  is used.
1539 
1540  :type: :class:`dict`
1541  """
1542  if self._local_cad_score_local_cad_score is None:
1543  self._compute_cad_score_compute_cad_score()
1544  return self._local_cad_score_local_cad_score
1545 
1546  @property
1547  def patch_qs(self):
1548  """ Patch QS-scores for each residue in :attr:`model_interface_residues`
1549 
1550  Representative patches for each residue r in chain c are computed as
1551  follows:
1552 
1553  * mdl_patch_one: All residues in c with CB (CA for GLY) positions within
1554  8A of r and within 12A of residues from any other chain.
1555  * mdl_patch_two: Closest residue x to r in any other chain gets
1556  identified. Patch is then constructed by selecting all residues from
1557  any other chain within 8A of x and within 12A from any residue in c.
1558  * trg_patch_one: Chain name and residue number based mapping from
1559  mdl_patch_one
1560  * trg_patch_two: Chain name and residue number based mapping from
1561  mdl_patch_two
1562 
1563  Results are stored in the same manner as
1564  :attr:`model_interface_residues`, with corresponding scores instead of
1565  residue numbers. Scores for residues which are not
1566  :class:`mol.ChemType.AMINOACIDS` are set to None. Additionally,
1567  interface patches are derived from :attr:`model`. If they contain
1568  residues which are not covered by :attr:`target`, the score is set to
1569  None too.
1570 
1571  :type: :class:`dict` with chain names as key and and :class:`list`
1572  with scores of the respective interface residues.
1573  """
1574  if self._patch_qs_patch_qs is None:
1575  self._compute_patchqs_scores_compute_patchqs_scores()
1576  return self._patch_qs_patch_qs
1577 
1578  @property
1579  def patch_dockq(self):
1580  """ Same as :attr:`patch_qs` but for DockQ scores
1581  """
1582  if self._patch_dockq_patch_dockq is None:
1583  self._compute_patchdockq_scores_compute_patchdockq_scores()
1584  return self._patch_dockq_patch_dockq
1585 
1586  @property
1587  def tm_score(self):
1588  """ TM-score computed with USalign
1589 
1590  USalign executable can be specified with usalign_exec kwarg at Scorer
1591  construction, an OpenStructure internal copy of the USalign code is
1592  used otherwise.
1593 
1594  :type: :class:`float`
1595  """
1596  if self._tm_score_tm_score is None:
1597  self._compute_tmscore_compute_tmscore()
1598  return self._tm_score_tm_score
1599 
1600  @property
1601  def usalign_mapping(self):
1602  """ Mapping computed with USalign
1603 
1604  Dictionary with target chain names as key and model chain names as
1605  values. No guarantee that all chains are mapped. USalign executable
1606  can be specified with usalign_exec kwarg at Scorer construction, an
1607  OpenStructure internal copy of the USalign code is used otherwise.
1608 
1609  :type: :class:`dict`
1610  """
1611  if self._usalign_mapping_usalign_mapping is None:
1612  self._compute_tmscore_compute_tmscore()
1613  return self._usalign_mapping_usalign_mapping
1614 
1615  def _aln_helper(self, target, model):
1616  # perform required alignments - cannot take the alignments from the
1617  # mapping results as we potentially remove stuff there as compared
1618  # to self.model and self.target
1619  trg_seqs = dict()
1620  for ch in target.chains:
1621  cname = ch.GetName()
1622  s = ''.join([r.one_letter_code for r in ch.residues])
1623  s = seq.CreateSequence(ch.GetName(), s)
1624  s.AttachView(target.Select(f"cname={mol.QueryQuoteName(cname)}"))
1625  trg_seqs[ch.GetName()] = s
1626  mdl_seqs = dict()
1627  for ch in model.chains:
1628  cname = ch.GetName()
1629  s = ''.join([r.one_letter_code for r in ch.residues])
1630  s = seq.CreateSequence(cname, s)
1631  s.AttachView(model.Select(f"cname={mol.QueryQuoteName(cname)}"))
1632  mdl_seqs[ch.GetName()] = s
1633 
1634  alns = list()
1635  trg_pep_chains = [s.GetName() for s in self.chain_mapperchain_mapper.polypep_seqs]
1636  trg_nuc_chains = [s.GetName() for s in self.chain_mapperchain_mapper.polynuc_seqs]
1637  trg_pep_chains = set(trg_pep_chains)
1638  trg_nuc_chains = set(trg_nuc_chains)
1639  for trg_ch, mdl_ch in self.mappingmapping.GetFlatMapping().items():
1640  if mdl_ch in mdl_seqs and trg_ch in trg_seqs:
1641  if trg_ch in trg_pep_chains:
1642  stype = mol.ChemType.AMINOACIDS
1643  elif trg_ch in trg_nuc_chains:
1644  stype = mol.ChemType.NUCLEOTIDES
1645  else:
1646  raise RuntimeError("Chain name inconsistency... ask "
1647  "Gabriel")
1648  alns.append(self.chain_mapperchain_mapper.Align(trg_seqs[trg_ch],
1649  mdl_seqs[mdl_ch],
1650  stype))
1651  alns[-1].AttachView(0, trg_seqs[trg_ch].GetAttachedView())
1652  alns[-1].AttachView(1, mdl_seqs[mdl_ch].GetAttachedView())
1653  return alns
1654 
1655  def _compute_pepnuc_aln(self):
1656  query = "peptide=true or nucleotide=true"
1657  pep_nuc_target = self.target_origtarget_orig.Select(query)
1658  pep_nuc_model = self.model_origmodel_orig.Select(query)
1659  self._pepnuc_aln_pepnuc_aln = self._aln_helper_aln_helper(pep_nuc_target, pep_nuc_model)
1660 
1661  def _compute_aln(self):
1662  self._aln_aln = self._aln_helper_aln_helper(self.targettarget, self.modelmodel)
1663 
1664  def _compute_stereochecked_aln(self):
1665  self._stereochecked_aln_stereochecked_aln = self._aln_helper_aln_helper(self.stereochecked_targetstereochecked_target,
1666  self.stereochecked_modelstereochecked_model)
1667 
1668  def _compute_lddt(self):
1669  LogScript("Computing all-atom lDDT")
1670  # lDDT requires a flat mapping with mdl_ch as key and trg_ch as value
1671  flat_mapping = self.mappingmapping.GetFlatMapping(mdl_as_key=True)
1672 
1673  # make alignments accessible by mdl seq name
1674  stereochecked_alns = dict()
1675  for aln in self.stereochecked_alnstereochecked_aln:
1676  mdl_seq = aln.GetSequence(1)
1677  stereochecked_alns[mdl_seq.name] = aln
1678  alns = dict()
1679  for aln in self.alnaln:
1680  mdl_seq = aln.GetSequence(1)
1681  alns[mdl_seq.name] = aln
1682 
1683  # score variables to be set
1684  lddt_score = None
1685  local_lddt = None
1686 
1687  if self.lddt_no_stereocheckslddt_no_stereochecks:
1688  lddt_chain_mapping = dict()
1689  for mdl_ch, trg_ch in flat_mapping.items():
1690  if mdl_ch in alns:
1691  lddt_chain_mapping[mdl_ch] = trg_ch
1692  lddt_score = self.lddt_scorerlddt_scorer.lDDT(self.modelmodel,
1693  chain_mapping = lddt_chain_mapping,
1694  residue_mapping = alns,
1695  check_resnames=False,
1696  local_lddt_prop="lddt",
1697  add_mdl_contacts = self.lddt_add_mdl_contactslddt_add_mdl_contacts)[0]
1698  local_lddt = dict()
1699  for r in self.modelmodel.residues:
1700  cname = r.GetChain().GetName()
1701  if cname not in local_lddt:
1702  local_lddt[cname] = dict()
1703  if r.HasProp("lddt"):
1704  score = round(r.GetFloatProp("lddt"), 3)
1705  local_lddt[cname][r.GetNumber()] = score
1706  else:
1707  # not covered by trg or skipped in chain mapping procedure
1708  # the latter happens if its part of a super short chain
1709  local_lddt[cname][r.GetNumber()] = None
1710 
1711  else:
1712  lddt_chain_mapping = dict()
1713  for mdl_ch, trg_ch in flat_mapping.items():
1714  if mdl_ch in stereochecked_alns:
1715  lddt_chain_mapping[mdl_ch] = trg_ch
1716  lddt_score = self.lddt_scorerlddt_scorer.lDDT(self.stereochecked_modelstereochecked_model,
1717  chain_mapping = lddt_chain_mapping,
1718  residue_mapping = stereochecked_alns,
1719  check_resnames=False,
1720  local_lddt_prop="lddt",
1721  add_mdl_contacts = self.lddt_add_mdl_contactslddt_add_mdl_contacts)[0]
1722  local_lddt = dict()
1723  for r in self.modelmodel.residues:
1724  cname = r.GetChain().GetName()
1725  if cname not in local_lddt:
1726  local_lddt[cname] = dict()
1727  if r.HasProp("lddt"):
1728  score = round(r.GetFloatProp("lddt"), 3)
1729  local_lddt[cname][r.GetNumber()] = score
1730  else:
1731  # rsc => residue stereo checked...
1732  mdl_res = self.stereochecked_modelstereochecked_model.FindResidue(cname, r.GetNumber())
1733  if mdl_res.IsValid():
1734  # not covered by trg or skipped in chain mapping procedure
1735  # the latter happens if its part of a super short chain
1736  local_lddt[cname][r.GetNumber()] = None
1737  else:
1738  # opt 1: removed by stereochecks => assign 0.0
1739  # opt 2: removed by stereochecks AND not covered by ref
1740  # => assign None
1741  # fetch trg residue from non-stereochecked aln
1742  trg_r = None
1743  if cname in flat_mapping:
1744  for col in alns[cname]:
1745  if col[0] != '-' and col[1] != '-':
1746  if col.GetResidue(1).number == r.number:
1747  trg_r = col.GetResidue(0)
1748  break
1749  if trg_r is None:
1750  local_lddt[cname][r.GetNumber()] = None
1751  else:
1752  local_lddt[cname][r.GetNumber()] = 0.0
1753 
1754  self._lddt_lddt = lddt_score
1755  self._local_lddt_local_lddt = local_lddt
1756 
1757  def _compute_bb_lddt(self):
1758  LogScript("Computing backbone lDDT")
1759  # make alignments accessible by mdl seq name
1760  alns = dict()
1761  for aln in self.alnaln:
1762  mdl_seq = aln.GetSequence(1)
1763  alns[mdl_seq.name] = aln
1764 
1765  # lDDT requires a flat mapping with mdl_ch as key and trg_ch as value
1766  flat_mapping = self.mappingmapping.GetFlatMapping(mdl_as_key=True)
1767  lddt_chain_mapping = dict()
1768  for mdl_ch, trg_ch in flat_mapping.items():
1769  if mdl_ch in alns:
1770  lddt_chain_mapping[mdl_ch] = trg_ch
1771 
1772  lddt_score = self.bb_lddt_scorerbb_lddt_scorer.lDDT(self.modelmodel,
1773  chain_mapping = lddt_chain_mapping,
1774  residue_mapping = alns,
1775  check_resnames=False,
1776  local_lddt_prop="bb_lddt",
1777  add_mdl_contacts = self.lddt_add_mdl_contactslddt_add_mdl_contacts)[0]
1778  local_lddt = dict()
1779  for r in self.modelmodel.residues:
1780  cname = r.GetChain().GetName()
1781  if cname not in local_lddt:
1782  local_lddt[cname] = dict()
1783  if r.HasProp("bb_lddt"):
1784  score = round(r.GetFloatProp("bb_lddt"), 3)
1785  local_lddt[cname][r.GetNumber()] = score
1786  else:
1787  # not covered by trg or skipped in chain mapping procedure
1788  # the latter happens if its part of a super short chain
1789  local_lddt[cname][r.GetNumber()] = None
1790 
1791  self._bb_lddt_bb_lddt = lddt_score
1792  self._bb_local_lddt_bb_local_lddt = local_lddt
1793 
1794  def _compute_ilddt(self):
1795  LogScript("Computing all-atom ilDDT")
1796  # lDDT requires a flat mapping with mdl_ch as key and trg_ch as value
1797  flat_mapping = self.mappingmapping.GetFlatMapping(mdl_as_key=True)
1798 
1799  if self.lddt_no_stereocheckslddt_no_stereochecks:
1800  alns = dict()
1801  for aln in self.alnaln:
1802  mdl_seq = aln.GetSequence(1)
1803  alns[mdl_seq.name] = aln
1804  lddt_chain_mapping = dict()
1805  for mdl_ch, trg_ch in flat_mapping.items():
1806  if mdl_ch in alns:
1807  lddt_chain_mapping[mdl_ch] = trg_ch
1808  self._ilddt_ilddt = self.lddt_scorerlddt_scorer.lDDT(self.modelmodel,
1809  chain_mapping = lddt_chain_mapping,
1810  residue_mapping = alns,
1811  check_resnames=False,
1812  local_lddt_prop="lddt",
1813  add_mdl_contacts = self.lddt_add_mdl_contactslddt_add_mdl_contacts,
1814  no_intrachain=True)[0]
1815  else:
1816  alns = dict()
1817  for aln in self.stereochecked_alnstereochecked_aln:
1818  mdl_seq = aln.GetSequence(1)
1819  alns[mdl_seq.name] = aln
1820  lddt_chain_mapping = dict()
1821  for mdl_ch, trg_ch in flat_mapping.items():
1822  if mdl_ch in alns:
1823  lddt_chain_mapping[mdl_ch] = trg_ch
1824  self._ilddt_ilddt = self.lddt_scorerlddt_scorer.lDDT(self.stereochecked_modelstereochecked_model,
1825  chain_mapping = lddt_chain_mapping,
1826  residue_mapping = alns,
1827  check_resnames=False,
1828  local_lddt_prop="lddt",
1829  add_mdl_contacts = self.lddt_add_mdl_contactslddt_add_mdl_contacts,
1830  no_intrachain=True)[0]
1831 
1832 
1833  def _compute_qs(self):
1834  LogScript("Computing global QS-score")
1835  qs_score_result = self.qs_scorerqs_scorer.Score(self.mappingmapping.mapping)
1836  self._qs_global_qs_global = qs_score_result.QS_global
1837  self._qs_best_qs_best = qs_score_result.QS_best
1838 
1839  def _compute_per_interface_qs_scores(self):
1840  LogScript("Computing per-interface QS-score")
1841  self._per_interface_qs_global_per_interface_qs_global = list()
1842  self._per_interface_qs_best_per_interface_qs_best = list()
1843 
1844  for interface in self.qs_interfacesqs_interfaces:
1845  trg_ch1 = interface[0]
1846  trg_ch2 = interface[1]
1847  mdl_ch1 = interface[2]
1848  mdl_ch2 = interface[3]
1849  qs_res = self.qs_scorerqs_scorer.ScoreInterface(trg_ch1, trg_ch2,
1850  mdl_ch1, mdl_ch2)
1851  self._per_interface_qs_best_per_interface_qs_best.append(qs_res.QS_best)
1852  self._per_interface_qs_global_per_interface_qs_global.append(qs_res.QS_global)
1853 
1854  def _compute_ics_scores(self):
1855  LogScript("Computing ICS scores")
1856  contact_scorer_res = self.contact_scorercontact_scorer.ScoreICS(self.mappingmapping.mapping)
1857  self._ics_precision_ics_precision = contact_scorer_res.precision
1858  self._ics_recall_ics_recall = contact_scorer_res.recall
1859  self._ics_ics = contact_scorer_res.ics
1860  self._per_interface_ics_precision_per_interface_ics_precision = list()
1861  self._per_interface_ics_recall_per_interface_ics_recall = list()
1862  self._per_interface_ics_per_interface_ics = list()
1863  flat_mapping = self.mappingmapping.GetFlatMapping()
1864  for trg_int in self.contact_target_interfacescontact_target_interfaces:
1865  trg_ch1 = trg_int[0]
1866  trg_ch2 = trg_int[1]
1867  if trg_ch1 in flat_mapping and trg_ch2 in flat_mapping:
1868  mdl_ch1 = flat_mapping[trg_ch1]
1869  mdl_ch2 = flat_mapping[trg_ch2]
1870  res = self.contact_scorercontact_scorer.ScoreICSInterface(trg_ch1, trg_ch2,
1871  mdl_ch1, mdl_ch2)
1872  self._per_interface_ics_precision_per_interface_ics_precision.append(res.precision)
1873  self._per_interface_ics_recall_per_interface_ics_recall.append(res.recall)
1874  self._per_interface_ics_per_interface_ics.append(res.ics)
1875  else:
1876  self._per_interface_ics_precision_per_interface_ics_precision.append(None)
1877  self._per_interface_ics_recall_per_interface_ics_recall.append(None)
1878  self._per_interface_ics_per_interface_ics.append(None)
1879 
1880  def _compute_ips_scores(self):
1881  LogScript("Computing IPS scores")
1882  contact_scorer_res = self.contact_scorercontact_scorer.ScoreIPS(self.mappingmapping.mapping)
1883  self._ips_precision_ips_precision = contact_scorer_res.precision
1884  self._ips_recall_ips_recall = contact_scorer_res.recall
1885  self._ips_ips = contact_scorer_res.ips
1886 
1887  self._per_interface_ips_precision_per_interface_ips_precision = list()
1888  self._per_interface_ips_recall_per_interface_ips_recall = list()
1889  self._per_interface_ips_per_interface_ips = list()
1890  flat_mapping = self.mappingmapping.GetFlatMapping()
1891  for trg_int in self.contact_target_interfacescontact_target_interfaces:
1892  trg_ch1 = trg_int[0]
1893  trg_ch2 = trg_int[1]
1894  if trg_ch1 in flat_mapping and trg_ch2 in flat_mapping:
1895  mdl_ch1 = flat_mapping[trg_ch1]
1896  mdl_ch2 = flat_mapping[trg_ch2]
1897  res = self.contact_scorercontact_scorer.ScoreIPSInterface(trg_ch1, trg_ch2,
1898  mdl_ch1, mdl_ch2)
1899  self._per_interface_ips_precision_per_interface_ips_precision.append(res.precision)
1900  self._per_interface_ips_recall_per_interface_ips_recall.append(res.recall)
1901  self._per_interface_ips_per_interface_ips.append(res.ips)
1902  else:
1903  self._per_interface_ips_precision_per_interface_ips_precision.append(None)
1904  self._per_interface_ips_recall_per_interface_ips_recall.append(None)
1905  self._per_interface_ips_per_interface_ips.append(None)
1906 
1907  def _compute_dockq_scores(self):
1908  LogScript("Computing DockQ")
1909  # lists with values in contact_target_interfaces
1910  self._dockq_scores_dockq_scores = list()
1911  self._fnat_fnat = list()
1912  self._fnonnat_fnonnat = list()
1913  self._irmsd_irmsd = list()
1914  self._lrmsd_lrmsd = list()
1915  self._nnat_nnat = list()
1916  self._nmdl_nmdl = list()
1917 
1918  dockq_alns = dict()
1919  for aln in self.alnaln:
1920  trg_s = aln.GetSequence(0)
1921  mdl_s = aln.GetSequence(1)
1922  dockq_alns[(trg_s.GetName(), mdl_s.GetName())] = aln
1923 
1924  for interface in self.dockq_interfacesdockq_interfaces:
1925  trg_ch1 = interface[0]
1926  trg_ch2 = interface[1]
1927  mdl_ch1 = interface[2]
1928  mdl_ch2 = interface[3]
1929  aln1 = dockq_alns[(trg_ch1, mdl_ch1)]
1930  aln2 = dockq_alns[(trg_ch2, mdl_ch2)]
1931  if self.dockq_capri_peptidedockq_capri_peptide:
1932  res = dockq.DockQ(self.modelmodel, self.targettarget, mdl_ch1, mdl_ch2,
1933  trg_ch1, trg_ch2, ch1_aln=aln1,
1934  ch2_aln=aln2, contact_dist_thresh = 4.0,
1935  interface_dist_thresh=8.0,
1936  interface_cb = True)
1937  else:
1938  res = dockq.DockQ(self.modelmodel, self.targettarget, mdl_ch1, mdl_ch2,
1939  trg_ch1, trg_ch2, ch1_aln=aln1,
1940  ch2_aln=aln2)
1941 
1942  self._fnat_fnat.append(res["fnat"])
1943  self._fnonnat_fnonnat.append(res["fnonnat"])
1944  self._irmsd_irmsd.append(res["irmsd"])
1945  self._lrmsd_lrmsd.append(res["lrmsd"])
1946  self._dockq_scores_dockq_scores.append(res["DockQ"])
1947  self._nnat_nnat.append(res["nnat"])
1948  self._nmdl_nmdl.append(res["nmdl"])
1949 
1950  # keep track of native counts in target interfaces which are
1951  # not covered in model in order to compute
1952  # dockq_ave_full/dockq_wave_full in the end
1953  not_covered_counts = list()
1954  proc_trg_interfaces = set([(x[0], x[1]) for x in self.dockq_interfacesdockq_interfaces])
1955  for interface in self.dockq_target_interfacesdockq_target_interfaces:
1956  if interface not in proc_trg_interfaces:
1957  # let's run DockQ with trg as trg/mdl in order to get the native
1958  # contacts out - no need to pass alns as the residue numbers
1959  # match for sure
1960  trg_ch1 = interface[0]
1961  trg_ch2 = interface[1]
1962 
1963  if self.dockq_capri_peptidedockq_capri_peptide:
1964  res = dockq.DockQ(self.targettarget, self.targettarget,
1965  trg_ch1, trg_ch2, trg_ch1, trg_ch2,
1966  contact_dist_thresh = 4.0,
1967  interface_dist_thresh=8.0,
1968  interface_cb = True)
1969  else:
1970  res = dockq.DockQ(self.targettarget, self.targettarget,
1971  trg_ch1, trg_ch2, trg_ch1, trg_ch2)
1972 
1973  not_covered_counts.append(res["nnat"])
1974 
1975  # there are 4 types of combined scores
1976  # - simple average
1977  # - average weighted by native_contacts
1978  # - the two above including nonmapped_contact_interfaces => set DockQ to 0.0
1979  scores = np.array(self._dockq_scores_dockq_scores)
1980  weights = np.array(self._nnat_nnat)
1981  if len(scores) > 0:
1982  self._dockq_ave_dockq_ave = np.mean(scores)
1983  else:
1984  self._dockq_ave_dockq_ave = 0.0
1985  self._dockq_wave_dockq_wave = np.sum(np.multiply(weights/np.sum(weights), scores))
1986  scores = np.append(scores, [0.0]*len(not_covered_counts))
1987  weights = np.append(weights, not_covered_counts)
1988  if len(scores) > 0:
1989  self._dockq_ave_full_dockq_ave_full = np.mean(scores)
1990  else:
1991  self._dockq_ave_full_dockq_ave_full = 0.0
1992  self._dockq_wave_full_dockq_wave_full = np.sum(np.multiply(weights/np.sum(weights),
1993  scores))
1994 
1995  def _extract_mapped_pos(self):
1996  self._mapped_target_pos_mapped_target_pos = geom.Vec3List()
1997  self._mapped_model_pos_mapped_model_pos = geom.Vec3List()
1998  self._n_target_not_mapped_n_target_not_mapped = 0
1999  processed_trg_chains = set()
2000  for trg_ch, mdl_ch in self.mappingmapping.GetFlatMapping().items():
2001  processed_trg_chains.add(trg_ch)
2002  aln = self.mappingmapping.alns[(trg_ch, mdl_ch)]
2003  for col in aln:
2004  if col[0] != '-' and col[1] != '-':
2005  trg_res = col.GetResidue(0)
2006  mdl_res = col.GetResidue(1)
2007  trg_at = trg_res.FindAtom("CA")
2008  mdl_at = mdl_res.FindAtom("CA")
2009  if not trg_at.IsValid():
2010  trg_at = trg_res.FindAtom("C3'")
2011  if not mdl_at.IsValid():
2012  mdl_at = mdl_res.FindAtom("C3'")
2013  self._mapped_target_pos_mapped_target_pos.append(trg_at.GetPos())
2014  self._mapped_model_pos_mapped_model_pos.append(mdl_at.GetPos())
2015  elif col[0] != '-':
2016  self._n_target_not_mapped_n_target_not_mapped += 1
2017  # count number of trg residues from non-mapped chains
2018  for ch in self.mappingmapping.target.chains:
2019  if ch.GetName() not in processed_trg_chains:
2020  self._n_target_not_mapped_n_target_not_mapped += len(ch.residues)
2021 
2022 
2023  def _extract_rigid_mapped_pos(self):
2024  self._rigid_mapped_target_pos_rigid_mapped_target_pos = geom.Vec3List()
2025  self._rigid_mapped_model_pos_rigid_mapped_model_pos = geom.Vec3List()
2026  self._rigid_n_target_not_mapped_rigid_n_target_not_mapped = 0
2027  processed_trg_chains = set()
2028  for trg_ch, mdl_ch in self.rigid_mappingrigid_mapping.GetFlatMapping().items():
2029  processed_trg_chains.add(trg_ch)
2030  aln = self.rigid_mappingrigid_mapping.alns[(trg_ch, mdl_ch)]
2031  for col in aln:
2032  if col[0] != '-' and col[1] != '-':
2033  trg_res = col.GetResidue(0)
2034  mdl_res = col.GetResidue(1)
2035  trg_at = trg_res.FindAtom("CA")
2036  mdl_at = mdl_res.FindAtom("CA")
2037  if not trg_at.IsValid():
2038  trg_at = trg_res.FindAtom("C3'")
2039  if not mdl_at.IsValid():
2040  mdl_at = mdl_res.FindAtom("C3'")
2041  self._rigid_mapped_target_pos_rigid_mapped_target_pos.append(trg_at.GetPos())
2042  self._rigid_mapped_model_pos_rigid_mapped_model_pos.append(mdl_at.GetPos())
2043  elif col[0] != '-':
2044  self._rigid_n_target_not_mapped_rigid_n_target_not_mapped += 1
2045  # count number of trg residues from non-mapped chains
2046  for ch in self.rigid_mappingrigid_mapping.target.chains:
2047  if ch.GetName() not in processed_trg_chains:
2048  self._rigid_n_target_not_mapped_rigid_n_target_not_mapped += len(ch.residues)
2049 
2050 
2051  def _compute_cad_score(self):
2052  if not self.resnum_alignmentsresnum_alignments:
2053  raise RuntimeError("CAD score computations rely on residue numbers "
2054  "that are consistent between target and model "
2055  "chains, i.e. only work if resnum_alignments "
2056  "is True at Scorer construction.")
2057  try:
2058  LogScript("Computing CAD score")
2059  cad_score_exec = \
2060  settings.Locate("voronota-cadscore",
2061  explicit_file_name=self.cad_score_execcad_score_exec)
2062  except Exception as e:
2063  raise RuntimeError("voronota-cadscore must be in PATH for CAD "
2064  "score scoring") from e
2065  cad_bin_dir = os.path.dirname(cad_score_exec)
2066  m = self.mappingmapping.GetFlatMapping(mdl_as_key=True)
2067  cad_result = cadscore.CADScore(self.modelmodel, self.targettarget,
2068  mode = "voronota",
2069  label="localcad",
2070  old_regime=False,
2071  cad_bin_path=cad_bin_dir,
2072  chain_mapping=m)
2073 
2074  local_cad = dict()
2075  for r in self.modelmodel.residues:
2076  cname = r.GetChain().GetName()
2077  if cname not in local_cad:
2078  local_cad[cname] = dict()
2079  if r.HasProp("localcad"):
2080  score = round(r.GetFloatProp("localcad"), 3)
2081  local_cad[cname][r.GetNumber()] = score
2082  else:
2083  local_cad[cname][r.GetNumber()] = None
2084 
2085  self._cad_score_cad_score = cad_result.globalAA
2086  self._local_cad_score_local_cad_score = local_cad
2087 
2088  def _get_repr_view(self, ent):
2089  """ Returns view with representative peptide atoms => CB, CA for GLY
2090 
2091  Ensures that each residue has exactly one atom with assertions
2092 
2093  :param ent: Entity for which you want the representative view
2094  :param ent: :class:`ost.mol.EntityHandle`/:class:`ost.mol.EntityView`
2095  :returns: :class:`ost.mol.EntityView` derived from ent
2096  """
2097  repr_ent = ent.Select("(aname=\"CB\" or (rname=\"GLY\" and aname=\"CA\"))")
2098  for r in repr_ent.residues:
2099  assert(len(r.atoms) == 1)
2100  return repr_ent
2101 
2102  def _get_interface_residues(self, ent):
2103  """ Get interface residues
2104 
2105  Thats all residues having a contact with at least one residue from another
2106  chain (CB-CB distance <= 8A, CA in case of Glycine)
2107 
2108  :param ent: Model for which to extract interface residues
2109  :type ent: :class:`ost.mol.EntityView`
2110  :returns: :class:`dict` with chain names as key and and :class:`list`
2111  with residue numbers of the respective interface residues.
2112  """
2113  # select for representative positions => CB, CA for GLY
2114  repr_ent = self._get_repr_view_get_repr_view(ent)
2115  result = {ch.GetName(): list() for ch in ent.chains}
2116  for ch in ent.chains:
2117  cname = ch.GetName()
2118  sel = repr_ent.Select(f"(cname={mol.QueryQuoteName(cname)} and 8 <> [cname!={mol.QueryQuoteName(cname)}])")
2119  result[cname] = [r.GetNumber() for r in sel.residues]
2120  return result
2121 
2122  def _do_stereochecks(self):
2123  """ Perform stereochemistry checks on model and target
2124  """
2125  LogInfo("Performing stereochemistry checks on model and target")
2126  data = stereochemistry.GetDefaultStereoData()
2127  l_data = stereochemistry.GetDefaultStereoLinkData()
2128 
2129  a, b, c, d = stereochemistry.StereoCheck(self.modelmodel, stereo_data = data,
2130  stereo_link_data = l_data)
2131  self._stereochecked_model_stereochecked_model = a
2132  self._model_clashes_model_clashes = b
2133  self._model_bad_bonds_model_bad_bonds = c
2134  self._model_bad_angles_model_bad_angles = d
2135 
2136  a, b, c, d = stereochemistry.StereoCheck(self.targettarget, stereo_data = data,
2137  stereo_link_data = l_data)
2138  self._stereochecked_target_stereochecked_target = a
2139  self._target_clashes_target_clashes = b
2140  self._target_bad_bonds_target_bad_bonds = c
2141  self._target_bad_angles_target_bad_angles = d
2142 
2143 
2144  def _get_interface_patches(self, mdl_ch, mdl_rnum):
2145  """ Select interface patches representative for specified residue
2146 
2147  The patches for specified residue r in chain c are selected as follows:
2148 
2149  * mdl_patch_one: All residues in c with CB (CA for GLY) positions within 8A
2150  of r and within 12A of residues from any other chain.
2151  * mdl_patch_two: Closest residue x to r in any other chain gets identified.
2152  Patch is then constructed by selecting all residues from any other chain
2153  within 8A of x and within 12A from any residue in c.
2154  * trg_patch_one: Chain name and residue number based mapping from
2155  mdl_patch_one
2156  * trg_patch_two: Chain name and residue number based mapping from
2157  mdl_patch_two
2158 
2159  :param mdl_ch: Name of chain in *self.model* of residue of interest
2160  :type mdl_ch: :class:`str`
2161  :param mdl_rnum: Residue number of residue of interest
2162  :type mdl_rnum: :class:`ost.mol.ResNum`
2163  :returns: Tuple with 5 elements: 1) :class:`bool` flag whether all residues
2164  in *mdl* patches are covered in *trg* 2) mtl_patch_one
2165  3) mdl_patch_two 4) trg_patch_one 5) trg_patch_two
2166  """
2167  # select for representative positions => CB, CA for GLY
2168  repr_mdl = self._get_repr_view_get_repr_view(self.modelmodel.Select("peptide=true"))
2169 
2170  # get position for specified residue
2171  r = self.modelmodel.FindResidue(mdl_ch, mdl_rnum)
2172  if not r.IsValid():
2173  raise RuntimeError(f"Cannot find residue {mdl_rnum} in chain {mdl_ch}")
2174  if r.GetName() == "GLY":
2175  at = r.FindAtom("CA")
2176  else:
2177  at = r.FindAtom("CB")
2178  if not at.IsValid():
2179  raise RuntimeError("Cannot find interface views for res without CB/CA")
2180  r_pos = at.GetPos()
2181 
2182  # mdl_patch_one contains residues from the same chain as r
2183  # => all residues within 8A of r and within 12A of any other chain
2184 
2185  # q1 selects for everything in same chain and within 8A of r_pos
2186  q1 = f"(cname={mol.QueryQuoteName(mdl_ch)} and 8 <> {{{r_pos[0]},{r_pos[1]},{r_pos[2]}}})"
2187  # q2 selects for everything within 12A of any other chain
2188  q2 = f"(12 <> [cname!={mol.QueryQuoteName(mdl_ch)}])"
2189  mdl_patch_one = self.modelmodel.CreateEmptyView()
2190  sel = repr_mdl.Select(" and ".join([q1, q2]))
2191  for r in sel.residues:
2192  mdl_r = self.modelmodel.FindResidue(r.GetChain().GetName(), r.GetNumber())
2193  mdl_patch_one.AddResidue(mdl_r, mol.ViewAddFlag.INCLUDE_ALL)
2194 
2195  # mdl_patch_two contains residues from all other chains. In detail:
2196  # the closest residue to r is identified in any other chain, and the
2197  # patch is filled with residues that are within 8A of that residue and
2198  # within 12A of chain from r
2199  sel = repr_mdl.Select(f"(cname!={mol.QueryQuoteName(mdl_ch)})")
2200  close_stuff = sel.FindWithin(r_pos, 8)
2201  min_pos = None
2202  min_dist = 42.0
2203  for close_at in close_stuff:
2204  dist = geom.Distance(r_pos, close_at.GetPos())
2205  if dist < min_dist:
2206  min_pos = close_at.GetPos()
2207  min_dist = dist
2208 
2209  # q1 selects for everything not in mdl_ch but within 8A of min_pos
2210  q1 = f"(cname!={mol.QueryQuoteName(mdl_ch)} and 8 <> {{{min_pos[0]},{min_pos[1]},{min_pos[2]}}})"
2211  # q2 selects for everything within 12A of mdl_ch
2212  q2 = f"(12 <> [cname={mol.QueryQuoteName(mdl_ch)}])"
2213  mdl_patch_two = self.modelmodel.CreateEmptyView()
2214  sel = repr_mdl.Select(" and ".join([q1, q2]))
2215  for r in sel.residues:
2216  mdl_r = self.modelmodel.FindResidue(r.GetChain().GetName(), r.GetNumber())
2217  mdl_patch_two.AddResidue(mdl_r, mol.ViewAddFlag.INCLUDE_ALL)
2218 
2219  # transfer mdl residues to trg
2220  flat_mapping = self.mappingmapping.GetFlatMapping(mdl_as_key=True)
2221  full_trg_coverage = True
2222  trg_patch_one = self.targettarget.CreateEmptyView()
2223  for r in mdl_patch_one.residues:
2224  trg_r = None
2225  mdl_cname = r.GetChain().GetName()
2226  if mdl_cname in flat_mapping:
2227  aln = self.mappingmapping.alns[(flat_mapping[mdl_cname], mdl_cname)]
2228  for col in aln:
2229  if col[0] != '-' and col[1] != '-':
2230  if col.GetResidue(1).GetNumber() == r.GetNumber():
2231  trg_r = col.GetResidue(0)
2232  break
2233  if trg_r is not None:
2234  trg_patch_one.AddResidue(trg_r.handle,
2235  mol.ViewAddFlag.INCLUDE_ALL)
2236  else:
2237  full_trg_coverage = False
2238 
2239  trg_patch_two = self.targettarget.CreateEmptyView()
2240  for r in mdl_patch_two.residues:
2241  trg_r = None
2242  mdl_cname = r.GetChain().GetName()
2243  if mdl_cname in flat_mapping:
2244  aln = self.mappingmapping.alns[(flat_mapping[mdl_cname], mdl_cname)]
2245  for col in aln:
2246  if col[0] != '-' and col[1] != '-':
2247  if col.GetResidue(1).GetNumber() == r.GetNumber():
2248  trg_r = col.GetResidue(0)
2249  break
2250  if trg_r is not None:
2251  trg_patch_two.AddResidue(trg_r.handle,
2252  mol.ViewAddFlag.INCLUDE_ALL)
2253  else:
2254  full_trg_coverage = False
2255 
2256  return (full_trg_coverage, mdl_patch_one, mdl_patch_two,
2257  trg_patch_one, trg_patch_two)
2258 
2259  def _compute_patchqs_scores(self):
2260  LogScript("Computing patch QS-scores")
2261  self._patch_qs_patch_qs = dict()
2262  for cname, rnums in self.model_interface_residuesmodel_interface_residues.items():
2263  scores = list()
2264  for rnum in rnums:
2265  score = None
2266  r = self.modelmodel.FindResidue(cname, rnum)
2267  if r.IsValid() and r.GetChemType() == mol.ChemType.AMINOACIDS:
2268  full_trg_coverage, mdl_patch_one, mdl_patch_two, \
2269  trg_patch_one, trg_patch_two = \
2270  self._get_interface_patches_get_interface_patches(cname, rnum)
2271  if full_trg_coverage:
2272  score = self._patchqs_patchqs(mdl_patch_one, mdl_patch_two,
2273  trg_patch_one, trg_patch_two)
2274  scores.append(score)
2275  self._patch_qs_patch_qs[cname] = scores
2276 
2277  def _compute_patchdockq_scores(self):
2278  LogScript("Computing patch DockQ scores")
2279  self._patch_dockq_patch_dockq = dict()
2280  for cname, rnums in self.model_interface_residuesmodel_interface_residues.items():
2281  scores = list()
2282  for rnum in rnums:
2283  score = None
2284  r = self.modelmodel.FindResidue(cname, rnum)
2285  if r.IsValid() and r.GetChemType() == mol.ChemType.AMINOACIDS:
2286  full_trg_coverage, mdl_patch_one, mdl_patch_two, \
2287  trg_patch_one, trg_patch_two = \
2288  self._get_interface_patches_get_interface_patches(cname, rnum)
2289  if full_trg_coverage:
2290  score = self._patchdockq_patchdockq(mdl_patch_one, mdl_patch_two,
2291  trg_patch_one, trg_patch_two)
2292  scores.append(score)
2293  self._patch_dockq_patch_dockq[cname] = scores
2294 
2295  def _patchqs(self, mdl_patch_one, mdl_patch_two, trg_patch_one, trg_patch_two):
2296  """ Score interface residue patches with QS-score
2297 
2298  In detail: Construct two entities with two chains each. First chain
2299  consists of residues from <x>_patch_one and second chain consists of
2300  <x>_patch_two. The returned score is the QS-score between the two
2301  entities
2302 
2303  :param mdl_patch_one: Interface patch representing scored residue
2304  :type mdl_patch_one: :class:`ost.mol.EntityView`
2305  :param mdl_patch_two: Interface patch representing scored residue
2306  :type mdl_patch_two: :class:`ost.mol.EntityView`
2307  :param trg_patch_one: Interface patch representing scored residue
2308  :type trg_patch_one: :class:`ost.mol.EntityView`
2309  :param trg_patch_two: Interface patch representing scored residue
2310  :type trg_patch_two: :class:`ost.mol.EntityView`
2311  :returns: PatchQS score
2312  """
2313  qs_ent_mdl = self._qs_ent_from_patches_qs_ent_from_patches(mdl_patch_one, mdl_patch_two)
2314  qs_ent_trg = self._qs_ent_from_patches_qs_ent_from_patches(trg_patch_one, trg_patch_two)
2315 
2316  alnA = seq.CreateAlignment()
2317  s = ''.join([r.one_letter_code for r in mdl_patch_one.residues])
2318  alnA.AddSequence(seq.CreateSequence("A", s))
2319  s = ''.join([r.one_letter_code for r in trg_patch_one.residues])
2320  alnA.AddSequence(seq.CreateSequence("A", s))
2321 
2322  alnB = seq.CreateAlignment()
2323  s = ''.join([r.one_letter_code for r in mdl_patch_two.residues])
2324  alnB.AddSequence(seq.CreateSequence("B", s))
2325  s = ''.join([r.one_letter_code for r in trg_patch_two.residues])
2326  alnB.AddSequence(seq.CreateSequence("B", s))
2327  alns = {("A", "A"): alnA, ("B", "B"): alnB}
2328 
2329  scorer = QSScorer(qs_ent_mdl, [["A"], ["B"]], qs_ent_trg, alns)
2330  score_result = scorer.Score([["A"], ["B"]])
2331 
2332  return score_result.QS_global
2333 
2334  def _patchdockq(self, mdl_patch_one, mdl_patch_two, trg_patch_one,
2335  trg_patch_two):
2336  """ Score interface residue patches with DockQ
2337 
2338  In detail: Construct two entities with two chains each. First chain
2339  consists of residues from <x>_patch_one and second chain consists of
2340  <x>_patch_two. The returned score is the QS-score between the two
2341  entities
2342 
2343  :param mdl_patch_one: Interface patch representing scored residue
2344  :type mdl_patch_one: :class:`ost.mol.EntityView`
2345  :param mdl_patch_two: Interface patch representing scored residue
2346  :type mdl_patch_two: :class:`ost.mol.EntityView`
2347  :param trg_patch_one: Interface patch representing scored residue
2348  :type trg_patch_one: :class:`ost.mol.EntityView`
2349  :param trg_patch_two: Interface patch representing scored residue
2350  :type trg_patch_two: :class:`ost.mol.EntityView`
2351  :returns: DockQ score
2352  """
2353  m = self._qs_ent_from_patches_qs_ent_from_patches(mdl_patch_one, mdl_patch_two)
2354  t = self._qs_ent_from_patches_qs_ent_from_patches(trg_patch_one, trg_patch_two)
2355  dockq_result = dockq.DockQ(t, m, "A", "B", "A", "B")
2356  if dockq_result["nnat"] > 0:
2357  return dockq_result["DockQ"]
2358  return 0.0
2359 
2360  def _qs_ent_from_patches(self, patch_one, patch_two):
2361  """ Constructs Entity with two chains named "A" and "B""
2362 
2363  Blindly adds all residues from *patch_one* to chain A and residues from
2364  patch_two to chain B.
2365  """
2366  ent = mol.CreateEntity()
2367  ed = ent.EditXCS()
2368  added_ch = ed.InsertChain("A")
2369  for r in patch_one.residues:
2370  added_r = ed.AppendResidue(added_ch, r.GetName())
2371  added_r.SetChemClass(str(r.GetChemClass()))
2372  for a in r.atoms:
2373  ed.InsertAtom(added_r, a.handle)
2374  added_ch = ed.InsertChain("B")
2375  for r in patch_two.residues:
2376  added_r = ed.AppendResidue(added_ch, r.GetName())
2377  added_r.SetChemClass(str(r.GetChemClass()))
2378  for a in r.atoms:
2379  ed.InsertAtom(added_r, a.handle)
2380  return ent
2381 
2382  def _construct_custom_mapping(self, mapping):
2383  """ constructs a full blown MappingResult object from simple dict
2384 
2385  :param mapping: mapping with trg chains as key and mdl ch as values
2386  :type mapping: :class:`dict`
2387  """
2388  LogInfo("Setting custom chain mapping")
2389 
2390  chain_mapper = self.chain_mapperchain_mapper
2391  chem_mapping, chem_group_alns, mdl = \
2392  chain_mapper.GetChemMapping(self.modelmodel)
2393 
2394  # now that we have a chem mapping, lets do consistency checks
2395  # - check whether chain names are unique and available in structures
2396  # - check whether the mapped chains actually map to the same chem groups
2397  if len(mapping) != len(set(mapping.keys())):
2398  raise RuntimeError(f"Expect unique trg chain names in mapping. Got "
2399  f"{mapping.keys()}")
2400  if len(mapping) != len(set(mapping.values())):
2401  raise RuntimeError(f"Expect unique mdl chain names in mapping. Got "
2402  f"{mapping.values()}")
2403 
2404  trg_chains = set([ch.GetName() for ch in chain_mapper.target.chains])
2405  mdl_chains = set([ch.GetName() for ch in mdl.chains])
2406  for k,v in mapping.items():
2407  if k not in trg_chains:
2408  raise RuntimeError(f"Target chain \"{k}\" is not available "
2409  f"in target processed for chain mapping "
2410  f"({trg_chains})")
2411  if v not in mdl_chains:
2412  raise RuntimeError(f"Model chain \"{v}\" is not available "
2413  f"in model processed for chain mapping "
2414  f"({mdl_chains})")
2415 
2416  for trg_ch, mdl_ch in mapping.items():
2417  trg_group_idx = None
2418  mdl_group_idx = None
2419  for idx, group in enumerate(chain_mapper.chem_groups):
2420  if trg_ch in group:
2421  trg_group_idx = idx
2422  break
2423  for idx, group in enumerate(chem_mapping):
2424  if mdl_ch in group:
2425  mdl_group_idx = idx
2426  break
2427  if trg_group_idx is None or mdl_group_idx is None:
2428  raise RuntimeError("Could not establish a valid chem grouping "
2429  "of chain names provided in custom mapping.")
2430 
2431  if trg_group_idx != mdl_group_idx:
2432  raise RuntimeError(f"Chem group mismatch in custom mapping: "
2433  f"target chain \"{trg_ch}\" groups with the "
2434  f"following chemically equivalent target "
2435  f"chains: "
2436  f"{chain_mapper.chem_groups[trg_group_idx]} "
2437  f"but model chain \"{mdl_ch}\" maps to the "
2438  f"following target chains: "
2439  f"{chain_mapper.chem_groups[mdl_group_idx]}")
2440 
2441  pairs = set([(trg_ch, mdl_ch) for trg_ch, mdl_ch in mapping.items()])
2442  ref_mdl_alns = \
2443  chain_mapping._GetRefMdlAlns(chain_mapper.chem_groups,
2444  chain_mapper.chem_group_alignments,
2445  chem_mapping,
2446  chem_group_alns,
2447  pairs = pairs)
2448 
2449  # translate mapping format
2450  final_mapping = list()
2451  for ref_chains in chain_mapper.chem_groups:
2452  mapped_mdl_chains = list()
2453  for ref_ch in ref_chains:
2454  if ref_ch in mapping:
2455  mapped_mdl_chains.append(mapping[ref_ch])
2456  else:
2457  mapped_mdl_chains.append(None)
2458  final_mapping.append(mapped_mdl_chains)
2459 
2460  alns = dict()
2461  for ref_group, mdl_group in zip(chain_mapper.chem_groups,
2462  final_mapping):
2463  for ref_ch, mdl_ch in zip(ref_group, mdl_group):
2464  if ref_ch is not None and mdl_ch is not None:
2465  aln = ref_mdl_alns[(ref_ch, mdl_ch)]
2466  trg_view = chain_mapper.target.Select(f"cname={mol.QueryQuoteName(ref_ch)}")
2467  mdl_view = mdl.Select(f"cname={mol.QueryQuoteName(mdl_ch)}")
2468  aln.AttachView(0, trg_view)
2469  aln.AttachView(1, mdl_view)
2470  alns[(ref_ch, mdl_ch)] = aln
2471 
2472  return chain_mapping.MappingResult(chain_mapper.target, mdl,
2473  chain_mapper.chem_groups,
2474  chem_mapping,
2475  final_mapping, alns)
2476 
2477  def _compute_tmscore(self):
2478  res = None
2479  if self.usalign_execusalign_exec is None:
2480  LogScript("Computing patch TM-score with USalign exectuable")
2481  if self.oumoum:
2482  flat_mapping = self.rigid_mappingrigid_mapping.GetFlatMapping()
2483  LogInfo("Overriding TM-score chain mapping")
2484  res = res = bindings.WrappedMMAlign(self.modelmodel, self.targettarget,
2485  mapping=flat_mapping)
2486  else:
2487  res = bindings.WrappedMMAlign(self.modelmodel, self.targettarget)
2488  else:
2489  LogScript("Computing patch TM-score with built-in USalign")
2490  if self.oumoum:
2491  LogInfo("Overriding TM-score chain mapping")
2492  flat_mapping = self.rigid_mappingrigid_mapping.GetFlatMapping()
2493  res = tmtools.USAlign(self.modelmodel, self.targettarget,
2494  usalign = self.usalign_execusalign_exec,
2495  custom_chain_mapping = flat_mapping)
2496  else:
2497  res = tmtools.USAlign(self.modelmodel, self.targettarget,
2498  usalign = self.usalign_execusalign_exec)
2499 
2500  self._tm_score_tm_score = res.tm_score
2501  self._usalign_mapping_usalign_mapping = dict()
2502  for a,b in zip(res.ent1_mapped_chains, res.ent2_mapped_chains):
2503  self._usalign_mapping_usalign_mapping[b] = a
2504 
2505 # specify public interface
2506 __all__ = ('lDDTBSScorer', 'Scorer',)
definition of EntityView
Definition: entity_view.hh:86
def target_interface_residues(self)
Definition: scoring.py:630
def _patchqs(self, mdl_patch_one, mdl_patch_two, trg_patch_one, trg_patch_two)
Definition: scoring.py:2295
def _get_repr_view(self, ent)
Definition: scoring.py:2088
def qs_target_interfaces(self)
Definition: scoring.py:793
def rigid_mapped_model_pos(self)
Definition: scoring.py:1340
def _compute_per_interface_qs_scores(self)
Definition: scoring.py:1839
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)
Definition: scoring.py:211
def model_interface_residues(self)
Definition: scoring.py:615
def stereochecked_target(self)
Definition: scoring.py:530
def per_interface_ips_precision(self)
Definition: scoring.py:1033
def _aln_helper(self, target, model)
Definition: scoring.py:1615
def per_interface_ics_recall(self)
Definition: scoring.py:970
def transformed_mapped_model_pos(self)
Definition: scoring.py:1287
def _compute_patchqs_scores(self)
Definition: scoring.py:2259
def _compute_stereochecked_aln(self)
Definition: scoring.py:1664
def rigid_transformed_mapped_model_pos(self)
Definition: scoring.py:1354
def contact_model_interfaces(self)
Definition: scoring.py:907
def _patchdockq(self, mdl_patch_one, mdl_patch_two, trg_patch_one, trg_patch_two)
Definition: scoring.py:2335
def per_interface_ips_recall(self)
Definition: scoring.py:1047
def per_interface_ics_precision(self)
Definition: scoring.py:956
def _get_interface_residues(self, ent)
Definition: scoring.py:2102
def rigid_mapped_target_pos(self)
Definition: scoring.py:1326
def dockq_target_interfaces(self)
Definition: scoring.py:1074
def _compute_patchdockq_scores(self)
Definition: scoring.py:2277
def _get_interface_patches(self, mdl_ch, mdl_rnum)
Definition: scoring.py:2144
def _extract_rigid_mapped_pos(self)
Definition: scoring.py:2023
def contact_target_interfaces(self)
Definition: scoring.py:891
def per_interface_qs_global(self)
Definition: scoring.py:846
def _qs_ent_from_patches(self, patch_one, patch_two)
Definition: scoring.py:2360
def per_interface_qs_best(self)
Definition: scoring.py:856
def rigid_n_target_not_mapped(self)
Definition: scoring.py:1366
def _construct_custom_mapping(self, mapping)
Definition: scoring.py:2382
def ScoreBS(self, ligand, radius=4.0, lddt_radius=10.0)
Definition: scoring.py:46
def __init__(self, reference, model, residue_number_alignment=False)
Definition: scoring.py:40
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)