Skip to content

Commit 29658e1

Browse files
committed
Add dihedral_side_chain()
1 parent 0826672 commit 29658e1

File tree

5 files changed

+402
-28
lines changed

5 files changed

+402
-28
lines changed

doc/apidoc.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,7 @@
367367
],
368368
"Proteins" : [
369369
"dihedral_backbone",
370+
"dihedral_side_chain",
370371
"annotate_sse"
371372
],
372373
"Nucleic acids" : [

src/biotite/structure/geometry.py

Lines changed: 164 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,79 @@
1919
"dihedral",
2020
"index_dihedral",
2121
"dihedral_backbone",
22+
"dihedral_side_chain",
2223
"centroid",
2324
]
2425

26+
import functools
2527
import numpy as np
2628
from biotite.structure.atoms import AtomArray, AtomArrayStack, coord
2729
from biotite.structure.box import coord_to_fraction, fraction_to_coord, is_orthogonal
28-
from biotite.structure.filter import filter_amino_acids
30+
from biotite.structure.filter import filter_amino_acids, filter_canonical_amino_acids
31+
from biotite.structure.residues import get_residue_starts
2932
from biotite.structure.util import (
3033
coord_for_atom_name_per_residue,
3134
norm_vector,
3235
vector_dot,
3336
)
3437

38+
# The names of the atoms participating in chi angle
39+
_CHI_ATOMS = {
40+
"ARG": [
41+
("N", "CA", "CB", "CG"),
42+
("CA", "CB", "CG", "CD"),
43+
("CB", "CG", "CD", "NE"),
44+
("CG", "CD", "NE", "CZ"),
45+
],
46+
"LEU": [
47+
("N", "CA", "CB", "CG"),
48+
# By convention chi2 is defined using CD1 instead of CD2
49+
("CA", "CB", "CG", "CD1"),
50+
],
51+
"VAL": [("N", "CA", "CB", "CG1")],
52+
"ILE": [("N", "CA", "CB", "CG1"), ("CA", "CB", "CG1", "CD1")],
53+
"MET": [
54+
("N", "CA", "CB", "CG"),
55+
("CA", "CB", "CG", "SD"),
56+
("CB", "CG", "SD", "CE"),
57+
],
58+
"LYS": [
59+
("N", "CA", "CB", "CG"),
60+
("CA", "CB", "CG", "CD"),
61+
("CB", "CG", "CD", "CE"),
62+
("CG", "CD", "CE", "NZ"),
63+
],
64+
"PHE": [
65+
("N", "CA", "CB", "CG"),
66+
("CA", "CB", "CG", "CD1"),
67+
],
68+
"TRP": [
69+
("N", "CA", "CB", "CG"),
70+
("CA", "CB", "CG", "CD1"),
71+
],
72+
"TYR": [
73+
("N", "CA", "CB", "CG"),
74+
("CA", "CB", "CG", "CD1"),
75+
],
76+
"ASN": [("N", "CA", "CB", "CG"), ("CA", "CB", "CG", "OD1")],
77+
"GLN": [
78+
("N", "CA", "CB", "CG"),
79+
("CA", "CB", "CG", "CD"),
80+
("CB", "CG", "CD", "OE1"),
81+
],
82+
"ASP": [("N", "CA", "CB", "CG"), ("CA", "CB", "CG", "OD1")],
83+
"GLU": [
84+
("N", "CA", "CB", "CG"),
85+
("CA", "CB", "CG", "CD"),
86+
("CB", "CG", "CD", "OE1"),
87+
],
88+
"CYS": [("N", "CA", "CB", "SG")],
89+
"HIS": [("N", "CA", "CB", "CG"), ("CA", "CB", "CG", "ND1")],
90+
"PRO": [("N", "CA", "CB", "CG"), ("CA", "CB", "CG", "CD")],
91+
"SER": [("N", "CA", "CB", "OG")],
92+
"THR": [("N", "CA", "CB", "OG1")],
93+
}
94+
3595

3696
def displacement(atoms1, atoms2, box=None):
3797
"""
@@ -492,7 +552,7 @@ def dihedral_backbone(atom_array):
492552
493553
Returns
494554
-------
495-
phi, psi, omega : ndarray
555+
phi, psi, omega : ndarray, shape=(m,n) or shape=(n,), dtype=float
496556
An array containing the 3 backbone dihedral angles for every CA atom.
497557
`phi` is not defined at the N-terminus, `psi` and `omega` are not defined at the
498558
C-terminus.
@@ -562,6 +622,96 @@ def dihedral_backbone(atom_array):
562622
return phi, psi, omg
563623

564624

625+
def dihedral_side_chain(atoms):
626+
r"""
627+
Measure the side chain :math:`\chi` dihedral angles of amino acid residues.
628+
629+
Parameters
630+
----------
631+
atoms : AtomArray or AtomArrayStack
632+
The protein structure to measure the side chain dihedral angles for.
633+
634+
Returns
635+
-------
636+
chi : ndarray, shape=(m, n, 4) or shape=(n, 4), dtype=float
637+
An array containing the up to four side chain dihedral angles for every
638+
amino acid residue.
639+
Trailing :math:`\chi` angles that are not defined for an amino acid are filled
640+
with :math:`NaN` values.
641+
The same is True for all residues that are not canonical amino acids.
642+
643+
Notes
644+
-----
645+
By convention, the :math:`\chi_2` angle of leucine is defined using ``CD1``
646+
instead of ``CD2``.
647+
648+
Examples
649+
--------
650+
651+
>>> res_ids, res_names = get_residues(atom_array)
652+
>>> dihedrals = dihedral_side_chain(atom_array)
653+
>>> for res_id, res_name, dihedrals in zip(res_ids, res_names, dihedrals):
654+
... print(f"{res_name.capitalize()}{res_id:<2d}:", dihedrals)
655+
Asn1 : [-1.180 -0.066 nan nan]
656+
Leu2 : [0.923 1.866 nan nan]
657+
Tyr3 : [-2.593 -1.487 nan nan]
658+
Ile4 : [-0.781 -0.972 nan nan]
659+
Gln5 : [-2.557 1.410 -1.776 nan]
660+
Trp6 : [3.117 1.372 nan nan]
661+
Leu7 : [-1.33 3.08 nan nan]
662+
Lys8 : [ 1.320 1.734 3.076 -2.022]
663+
Asp9 : [-1.623 0.909 nan nan]
664+
Gly10: [nan nan nan nan]
665+
Gly11: [nan nan nan nan]
666+
Pro12: [-0.331 0.539 nan nan]
667+
Ser13: [-1.067 nan nan nan]
668+
Ser14: [-2.514 nan nan nan]
669+
Gly15: [nan nan nan nan]
670+
Arg16: [ 1.032 -3.063 1.541 -1.568]
671+
Pro17: [ 0.522 -0.601 nan nan]
672+
Pro18: [ 0.475 -0.577 nan nan]
673+
Pro19: [ 0.561 -0.602 nan nan]
674+
Ser20: [-1.055 nan nan nan]
675+
"""
676+
is_multi_model = isinstance(atoms, AtomArrayStack)
677+
678+
chi_atoms = _all_chi_atoms()
679+
res_names = atoms.res_name[get_residue_starts(atoms)]
680+
chi_atom_coord = coord_for_atom_name_per_residue(
681+
atoms, chi_atoms, filter_canonical_amino_acids(atoms)
682+
)
683+
chi_atoms_to_coord_index = {atom_name: i for i, atom_name in enumerate(chi_atoms)}
684+
685+
if is_multi_model:
686+
shape = (atoms.stack_depth(), len(res_names), 4)
687+
else:
688+
shape = (len(res_names), 4)
689+
chi_angles = np.full(shape, np.nan, dtype=np.float32)
690+
for res_name, chi_atom_names_for_all_angles in _CHI_ATOMS.items():
691+
res_mask = res_names == res_name
692+
for chi_i, chi_atom_names in enumerate(chi_atom_names_for_all_angles):
693+
dihedrals = dihedral(
694+
chi_atom_coord[
695+
chi_atoms_to_coord_index[chi_atom_names[0]], ..., res_mask, :
696+
],
697+
chi_atom_coord[
698+
chi_atoms_to_coord_index[chi_atom_names[1]], ..., res_mask, :
699+
],
700+
chi_atom_coord[
701+
chi_atoms_to_coord_index[chi_atom_names[2]], ..., res_mask, :
702+
],
703+
chi_atom_coord[
704+
chi_atoms_to_coord_index[chi_atom_names[3]], ..., res_mask, :
705+
],
706+
)
707+
if is_multi_model:
708+
# Swap dimensions due to NumPy's behavior when using advanced indexing
709+
# (https://numpy.org/devdocs/user/basics.indexing.html#combining-advanced-and-basic-indexing)
710+
dihedrals = dihedrals.T
711+
chi_angles[..., res_mask, chi_i] = dihedrals
712+
return chi_angles
713+
714+
565715
def centroid(atoms):
566716
"""
567717
Measure the centroid of a structure.
@@ -653,3 +803,15 @@ def _displacement_triclinic_box(fractions, box, disp):
653803
disp[:] = shifted_diffs[
654804
np.arange(len(shifted_diffs)), np.argmin(sq_distance, axis=1)
655805
]
806+
807+
808+
@functools.cache
809+
def _all_chi_atoms():
810+
"""
811+
Get the names of the atoms participating in any chi angle.
812+
"""
813+
atom_names = set()
814+
for angles in _CHI_ATOMS.values():
815+
for angle in angles:
816+
atom_names.update(angle)
817+
return sorted(atom_names)
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
{
2+
"phi": [
3+
NaN,
4+
-0.7675957679748535,
5+
-1.1600481271743774,
6+
-1.1382936239242554,
7+
-1.1300407648086548,
8+
-1.2764559984207153,
9+
-1.132405161857605,
10+
-1.038622498512268,
11+
-1.3611706495285034,
12+
1.9335463047027588,
13+
0.9641932845115662,
14+
-1.0119951963424683,
15+
-1.4282735586166382,
16+
-2.165194511413574,
17+
1.1856279373168945,
18+
-2.5124294757843018,
19+
-1.2234766483306885,
20+
-1.212719202041626,
21+
-1.3485140800476074,
22+
-1.3631049394607544
23+
],
24+
"psi": [
25+
-0.9799132347106934,
26+
-0.8955070376396179,
27+
-0.5392720103263855,
28+
-0.8018860220909119,
29+
-0.5296429991722107,
30+
-0.7579015493392944,
31+
-0.7549335956573486,
32+
-0.4485180079936981,
33+
-0.15399101376533508,
34+
0.1410093754529953,
35+
-2.1706905364990234,
36+
-0.5020549297332764,
37+
0.3337954580783844,
38+
0.23389503359794617,
39+
0.4401324391365051,
40+
2.291565418243408,
41+
2.7937121391296387,
42+
2.542400598526001,
43+
2.1680984497070312,
44+
NaN
45+
],
46+
"omega": [
47+
3.0845956802368164,
48+
3.1221516132354736,
49+
3.0525054931640625,
50+
3.068862199783325,
51+
2.9939162731170654,
52+
3.09832501411438,
53+
-3.1216604709625244,
54+
3.0418734550476074,
55+
-3.1120681762695312,
56+
3.12791109085083,
57+
3.1057968139648438,
58+
-3.083376169204712,
59+
-3.0778303146362305,
60+
3.067182779312134,
61+
-3.012817144393921,
62+
-3.140730619430542,
63+
3.1097910404205322,
64+
3.076752185821533,
65+
2.995931625366211,
66+
NaN
67+
],
68+
"chi1": [
69+
-1.1804109811782837,
70+
0.9229415655136108,
71+
-2.5934557914733887,
72+
-0.7805949449539185,
73+
-2.5565571784973145,
74+
3.1165497303009033,
75+
-1.330034852027893,
76+
1.3203874826431274,
77+
-1.6227786540985107,
78+
NaN,
79+
NaN,
80+
-0.3308698534965515,
81+
-1.0671310424804688,
82+
-2.514207601547241,
83+
NaN,
84+
1.031717300415039,
85+
0.5222769975662231,
86+
0.4746994078159332,
87+
0.5613434910774231,
88+
-1.0548193454742432
89+
],
90+
"chi2": [
91+
-0.0660201907157898,
92+
1.8658562898635864,
93+
-1.4867808818817139,
94+
-0.9722598195075989,
95+
1.4097707271575928,
96+
1.3717790842056274,
97+
3.0799336433410645,
98+
1.7341870069503784,
99+
0.9091590046882629,
100+
NaN,
101+
NaN,
102+
0.53904789686203,
103+
NaN,
104+
NaN,
105+
NaN,
106+
-3.062899589538574,
107+
-0.6009901165962219,
108+
-0.5772097706794739,
109+
-0.6022951602935791,
110+
NaN
111+
],
112+
"chi3": [
113+
NaN,
114+
NaN,
115+
NaN,
116+
NaN,
117+
-1.7764254808425903,
118+
NaN,
119+
NaN,
120+
3.0758087635040283,
121+
NaN,
122+
NaN,
123+
NaN,
124+
NaN,
125+
NaN,
126+
NaN,
127+
NaN,
128+
1.5411443710327148,
129+
NaN,
130+
NaN,
131+
NaN,
132+
NaN
133+
],
134+
"chi4": [
135+
NaN,
136+
NaN,
137+
NaN,
138+
NaN,
139+
NaN,
140+
NaN,
141+
NaN,
142+
-2.022183418273926,
143+
NaN,
144+
NaN,
145+
NaN,
146+
NaN,
147+
NaN,
148+
NaN,
149+
NaN,
150+
-1.5680949687957764,
151+
NaN,
152+
NaN,
153+
NaN,
154+
NaN
155+
]
156+
}

0 commit comments

Comments
 (0)