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