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