biotite 1.1.0__cp313-cp313-win_amd64.whl → 1.3.0__cp313-cp313-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of biotite might be problematic. Click here for more details.
- biotite/application/application.py +3 -3
- biotite/application/autodock/app.py +1 -1
- biotite/application/blast/webapp.py +1 -1
- biotite/application/clustalo/app.py +1 -1
- biotite/application/localapp.py +2 -2
- biotite/application/msaapp.py +10 -10
- biotite/application/muscle/app3.py +3 -3
- biotite/application/muscle/app5.py +3 -3
- biotite/application/sra/app.py +0 -5
- biotite/application/util.py +21 -1
- biotite/application/viennarna/rnaalifold.py +8 -8
- biotite/application/viennarna/rnaplot.py +10 -8
- biotite/application/viennarna/util.py +1 -1
- biotite/application/webapp.py +1 -1
- biotite/database/afdb/__init__.py +12 -0
- biotite/database/afdb/download.py +191 -0
- biotite/database/entrez/dbnames.py +10 -0
- biotite/database/entrez/download.py +9 -10
- biotite/database/entrez/key.py +1 -1
- biotite/database/entrez/query.py +5 -4
- biotite/database/pubchem/download.py +6 -6
- biotite/database/pubchem/error.py +10 -0
- biotite/database/pubchem/query.py +12 -23
- biotite/database/rcsb/download.py +3 -2
- biotite/database/rcsb/query.py +2 -3
- biotite/database/uniprot/check.py +2 -2
- biotite/database/uniprot/download.py +2 -5
- biotite/database/uniprot/query.py +3 -4
- biotite/file.py +14 -2
- biotite/interface/__init__.py +19 -0
- biotite/interface/openmm/__init__.py +20 -0
- biotite/interface/openmm/state.py +93 -0
- biotite/interface/openmm/system.py +227 -0
- biotite/interface/pymol/__init__.py +201 -0
- biotite/interface/pymol/cgo.py +346 -0
- biotite/interface/pymol/convert.py +185 -0
- biotite/interface/pymol/display.py +267 -0
- biotite/interface/pymol/object.py +1226 -0
- biotite/interface/pymol/shapes.py +178 -0
- biotite/interface/pymol/startup.py +169 -0
- biotite/interface/rdkit/__init__.py +19 -0
- biotite/interface/rdkit/mol.py +490 -0
- biotite/interface/version.py +94 -0
- biotite/interface/warning.py +19 -0
- biotite/sequence/align/__init__.py +0 -4
- biotite/sequence/align/alignment.py +33 -11
- biotite/sequence/align/banded.cp313-win_amd64.pyd +0 -0
- biotite/sequence/align/banded.pyx +22 -22
- biotite/sequence/align/cigar.py +2 -2
- biotite/sequence/align/kmeralphabet.cp313-win_amd64.pyd +0 -0
- biotite/sequence/align/kmeralphabet.pyx +2 -2
- biotite/sequence/align/kmersimilarity.cp313-win_amd64.pyd +0 -0
- biotite/sequence/align/kmertable.cp313-win_amd64.pyd +0 -0
- biotite/sequence/align/kmertable.pyx +6 -6
- biotite/sequence/align/localgapped.cp313-win_amd64.pyd +0 -0
- biotite/sequence/align/localgapped.pyx +47 -47
- biotite/sequence/align/localungapped.cp313-win_amd64.pyd +0 -0
- biotite/sequence/align/localungapped.pyx +10 -10
- biotite/sequence/align/matrix.py +12 -3
- biotite/sequence/align/multiple.cp313-win_amd64.pyd +0 -0
- biotite/sequence/align/multiple.pyx +1 -2
- biotite/sequence/align/pairwise.cp313-win_amd64.pyd +0 -0
- biotite/sequence/align/pairwise.pyx +37 -39
- biotite/sequence/align/permutation.cp313-win_amd64.pyd +0 -0
- biotite/sequence/align/selector.cp313-win_amd64.pyd +0 -0
- biotite/sequence/align/selector.pyx +2 -2
- biotite/sequence/align/statistics.py +1 -1
- biotite/sequence/align/tracetable.cp313-win_amd64.pyd +0 -0
- biotite/sequence/alphabet.py +2 -2
- biotite/sequence/annotation.py +19 -13
- biotite/sequence/codec.cp313-win_amd64.pyd +0 -0
- biotite/sequence/codon.py +1 -2
- biotite/sequence/graphics/alignment.py +25 -39
- biotite/sequence/graphics/dendrogram.py +4 -2
- biotite/sequence/graphics/features.py +2 -2
- biotite/sequence/graphics/logo.py +10 -12
- biotite/sequence/io/fasta/convert.py +1 -2
- biotite/sequence/io/fasta/file.py +1 -1
- biotite/sequence/io/fastq/file.py +3 -3
- biotite/sequence/io/genbank/file.py +3 -3
- biotite/sequence/io/genbank/sequence.py +2 -0
- biotite/sequence/io/gff/convert.py +1 -1
- biotite/sequence/io/gff/file.py +1 -2
- biotite/sequence/phylo/nj.cp313-win_amd64.pyd +0 -0
- biotite/sequence/phylo/tree.cp313-win_amd64.pyd +0 -0
- biotite/sequence/phylo/upgma.cp313-win_amd64.pyd +0 -0
- biotite/sequence/profile.py +19 -25
- biotite/sequence/search.py +0 -1
- biotite/sequence/seqtypes.py +12 -5
- biotite/sequence/sequence.py +1 -2
- biotite/structure/__init__.py +2 -0
- biotite/structure/alphabet/i3d.py +1 -2
- biotite/structure/alphabet/pb.py +1 -2
- biotite/structure/alphabet/unkerasify.py +8 -2
- biotite/structure/atoms.py +35 -27
- biotite/structure/basepairs.py +39 -40
- biotite/structure/bonds.cp313-win_amd64.pyd +0 -0
- biotite/structure/bonds.pyx +8 -5
- biotite/structure/box.py +159 -23
- biotite/structure/celllist.cp313-win_amd64.pyd +0 -0
- biotite/structure/celllist.pyx +83 -68
- biotite/structure/chains.py +17 -55
- biotite/structure/charges.cp313-win_amd64.pyd +0 -0
- biotite/structure/compare.py +420 -13
- biotite/structure/density.py +1 -1
- biotite/structure/dotbracket.py +31 -32
- biotite/structure/filter.py +8 -8
- biotite/structure/geometry.py +15 -15
- biotite/structure/graphics/rna.py +19 -16
- biotite/structure/hbond.py +18 -21
- biotite/structure/info/atoms.py +11 -2
- biotite/structure/info/ccd.py +0 -2
- biotite/structure/info/components.bcif +0 -0
- biotite/structure/info/groups.py +0 -3
- biotite/structure/info/misc.py +0 -1
- biotite/structure/info/radii.py +92 -22
- biotite/structure/info/standardize.py +1 -2
- biotite/structure/integrity.py +4 -6
- biotite/structure/io/general.py +2 -2
- biotite/structure/io/gro/file.py +8 -9
- biotite/structure/io/mol/convert.py +1 -1
- biotite/structure/io/mol/ctab.py +33 -28
- biotite/structure/io/mol/mol.py +1 -1
- biotite/structure/io/mol/sdf.py +39 -13
- biotite/structure/io/pdb/convert.py +86 -5
- biotite/structure/io/pdb/file.py +90 -24
- biotite/structure/io/pdb/hybrid36.cp313-win_amd64.pyd +0 -0
- biotite/structure/io/pdbqt/file.py +4 -4
- biotite/structure/io/pdbx/bcif.py +22 -7
- biotite/structure/io/pdbx/cif.py +20 -7
- biotite/structure/io/pdbx/component.py +6 -0
- biotite/structure/io/pdbx/compress.py +71 -34
- biotite/structure/io/pdbx/convert.py +429 -77
- biotite/structure/io/pdbx/encoding.cp313-win_amd64.pyd +0 -0
- biotite/structure/io/pdbx/encoding.pyx +39 -23
- biotite/structure/io/trajfile.py +9 -6
- biotite/structure/io/util.py +38 -0
- biotite/structure/mechanics.py +0 -1
- biotite/structure/molecules.py +0 -15
- biotite/structure/pseudoknots.py +13 -19
- biotite/structure/repair.py +2 -4
- biotite/structure/residues.py +20 -48
- biotite/structure/rings.py +335 -0
- biotite/structure/sasa.cp313-win_amd64.pyd +0 -0
- biotite/structure/sasa.pyx +30 -30
- biotite/structure/segments.py +123 -9
- biotite/structure/sequence.py +0 -1
- biotite/structure/spacegroups.json +1567 -0
- biotite/structure/spacegroups.license +26 -0
- biotite/structure/sse.py +0 -2
- biotite/structure/superimpose.py +75 -253
- biotite/structure/tm.py +581 -0
- biotite/structure/transform.py +232 -26
- biotite/structure/util.py +3 -3
- biotite/version.py +9 -4
- biotite/visualize.py +111 -1
- {biotite-1.1.0.dist-info → biotite-1.3.0.dist-info}/METADATA +8 -36
- {biotite-1.1.0.dist-info → biotite-1.3.0.dist-info}/RECORD +160 -138
- {biotite-1.1.0.dist-info → biotite-1.3.0.dist-info}/WHEEL +1 -1
- {biotite-1.1.0.dist-info → biotite-1.3.0.dist-info}/licenses/LICENSE.rst +0 -0
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
# This source code is part of the Biotite package and is distributed
|
|
2
|
+
# under the 3-Clause BSD License. Please see 'LICENSE.rst' for further
|
|
3
|
+
# information.
|
|
4
|
+
|
|
5
|
+
"""
|
|
6
|
+
This module provides functions related to aromatic rings.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
__name__ = "biotite.structure"
|
|
10
|
+
__author__ = "Patrick Kunzmann"
|
|
11
|
+
__all__ = ["find_aromatic_rings", "find_stacking_interactions", "PiStacking"]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
from enum import IntEnum
|
|
15
|
+
import networkx as nx
|
|
16
|
+
import numpy as np
|
|
17
|
+
from biotite.structure.bonds import BondType
|
|
18
|
+
from biotite.structure.error import BadStructureError
|
|
19
|
+
from biotite.structure.geometry import displacement
|
|
20
|
+
from biotite.structure.util import norm_vector, vector_dot
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class PiStacking(IntEnum):
|
|
24
|
+
"""
|
|
25
|
+
The type of pi-stacking interaction.
|
|
26
|
+
|
|
27
|
+
- ``PARALLEL``: parallel pi-stacking (also called *staggered* or *Sandwich*)
|
|
28
|
+
- ``PERPENDICULAR``: perpendicular pi-stacking (also called *T-shaped*)
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
PARALLEL = 0
|
|
32
|
+
PERPENDICULAR = 1
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def find_aromatic_rings(atoms):
|
|
36
|
+
"""
|
|
37
|
+
Find (anti-)aromatic rings in a structure.
|
|
38
|
+
|
|
39
|
+
Parameters
|
|
40
|
+
----------
|
|
41
|
+
atoms : AtomArray or AtomArrayStack
|
|
42
|
+
The atoms to be searched for aromatic rings.
|
|
43
|
+
Requires an associated :class:`BondList`.
|
|
44
|
+
|
|
45
|
+
Returns
|
|
46
|
+
-------
|
|
47
|
+
rings : list of ndarray
|
|
48
|
+
The indices of the atoms that form aromatic rings.
|
|
49
|
+
Each ring is represented by a list of indices.
|
|
50
|
+
Only rings with minimum size are returned, i.e. two connected rings
|
|
51
|
+
(e.g. in tryptophan) are reported as separate rings.
|
|
52
|
+
|
|
53
|
+
Notes
|
|
54
|
+
-----
|
|
55
|
+
This function does not distinguish between aromatic and antiaromatic rings.
|
|
56
|
+
All cycles containing atoms that are completely connected by aromatic bonds
|
|
57
|
+
are considered aromatic rings.
|
|
58
|
+
|
|
59
|
+
The PDB *Chemical Component Dictionary* (CCD) does not identify aromatic rings in
|
|
60
|
+
all compounds as such.
|
|
61
|
+
Prominent examples are the nucleobases, where the 6-membered rings are not
|
|
62
|
+
flagged as aromatic.
|
|
63
|
+
|
|
64
|
+
Examples
|
|
65
|
+
--------
|
|
66
|
+
|
|
67
|
+
>>> nad = residue("NAD")
|
|
68
|
+
>>> rings = find_aromatic_rings(nad)
|
|
69
|
+
>>> print(rings)
|
|
70
|
+
[array([41, 37, 36, 35, 43, 42]), array([19, 18, 16, 15, 21, 20]), array([12, 13, 14, 15, 21])]
|
|
71
|
+
>>> for atom_indices in rings:
|
|
72
|
+
... print(np.sort(nad.atom_name[atom_indices]))
|
|
73
|
+
['C2N' 'C3N' 'C4N' 'C5N' 'C6N' 'N1N']
|
|
74
|
+
['C2A' 'C4A' 'C5A' 'C6A' 'N1A' 'N3A']
|
|
75
|
+
['C4A' 'C5A' 'C8A' 'N7A' 'N9A']
|
|
76
|
+
"""
|
|
77
|
+
if atoms.bonds is None:
|
|
78
|
+
raise BadStructureError("Structure must have an associated BondList")
|
|
79
|
+
bond_array = atoms.bonds.as_array()
|
|
80
|
+
# To detect aromatic rings, only keep bonds that are aromatic
|
|
81
|
+
aromatic_bond_array = bond_array[
|
|
82
|
+
np.isin(
|
|
83
|
+
bond_array[:, 2],
|
|
84
|
+
[
|
|
85
|
+
BondType.AROMATIC,
|
|
86
|
+
BondType.AROMATIC_SINGLE,
|
|
87
|
+
BondType.AROMATIC_DOUBLE,
|
|
88
|
+
BondType.AROMATIC_TRIPLE,
|
|
89
|
+
],
|
|
90
|
+
),
|
|
91
|
+
# We can omit the bond type now
|
|
92
|
+
:2,
|
|
93
|
+
]
|
|
94
|
+
aromatic_bond_graph = nx.from_edgelist(aromatic_bond_array.tolist())
|
|
95
|
+
# Find the cycles with minimum size -> cycle basis
|
|
96
|
+
rings = nx.cycle_basis(aromatic_bond_graph)
|
|
97
|
+
return [np.array(ring, dtype=int) for ring in rings]
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def find_stacking_interactions(
|
|
101
|
+
atoms,
|
|
102
|
+
centroid_cutoff=6.5,
|
|
103
|
+
plane_angle_tol=np.deg2rad(30.0),
|
|
104
|
+
shift_angle_tol=np.deg2rad(30.0),
|
|
105
|
+
):
|
|
106
|
+
"""
|
|
107
|
+
Find pi-stacking interactions between aromatic rings.
|
|
108
|
+
|
|
109
|
+
Parameters
|
|
110
|
+
----------
|
|
111
|
+
atoms : AtomArray
|
|
112
|
+
The atoms to be searched for aromatic rings.
|
|
113
|
+
Requires an associated :class:`BondList`.
|
|
114
|
+
centroid_cutoff : float
|
|
115
|
+
The cutoff distance for ring centroids.
|
|
116
|
+
plane_angle_tol : float
|
|
117
|
+
The tolerance for the angle between ring planes that must be either
|
|
118
|
+
parallel or perpendicular.
|
|
119
|
+
Given in radians.
|
|
120
|
+
shift_angle_tol : float
|
|
121
|
+
The tolerance for the angle between the ring plane normals and the
|
|
122
|
+
centroid difference vector.
|
|
123
|
+
Given in radians.
|
|
124
|
+
|
|
125
|
+
Returns
|
|
126
|
+
-------
|
|
127
|
+
interactions : list of tuple(ndarray, ndarray, PiStacking)
|
|
128
|
+
The stacking interactions between aromatic rings.
|
|
129
|
+
Each element in the list represents one stacking interaction.
|
|
130
|
+
The first two elements of each tuple represent atom indices of the stacked
|
|
131
|
+
rings.
|
|
132
|
+
The third element of each tuple is the type of stacking interaction.
|
|
133
|
+
|
|
134
|
+
See Also
|
|
135
|
+
--------
|
|
136
|
+
find_aromatic_rings : Used for finding the aromatic rings in this function.
|
|
137
|
+
|
|
138
|
+
Notes
|
|
139
|
+
-----
|
|
140
|
+
This function does not distinguish between aromatic and antiaromatic rings.
|
|
141
|
+
Furthermore, it does not distinguish between repulsive and attractive stacking:
|
|
142
|
+
Usually, stacking two rings directly above each other is repulsive, as the pi
|
|
143
|
+
orbitals above the rings repel each other, so a slight horizontal shift is
|
|
144
|
+
usually required to make the interaction attractive.
|
|
145
|
+
However, in details this is strongly dependent on heteroatoms and the exact
|
|
146
|
+
orientation of the rings.
|
|
147
|
+
Hence, this function aggregates all stacking interactions to simplify the
|
|
148
|
+
conditions for pi-stacking.
|
|
149
|
+
|
|
150
|
+
The conditions for pi-stacking are :footcite:`Wojcikowski2015` :
|
|
151
|
+
|
|
152
|
+
- The ring centroids must be within cutoff `centroid_cutoff` distance.
|
|
153
|
+
While :footcite:`Wojcikowski2015` uses a cutoff of 5.0 Å, 6.5 Å was
|
|
154
|
+
adopted from :footcite:`Bouysset2021` to better identify perpendicular
|
|
155
|
+
stacking interactions.
|
|
156
|
+
- The planes must be parallel or perpendicular to each other within a default
|
|
157
|
+
tolerance of 30°.
|
|
158
|
+
- The angle between the plane normals and the centroid difference vector must be
|
|
159
|
+
be either 0° or 90° within a default tolerance of 30°, to check for lateral
|
|
160
|
+
shifts.
|
|
161
|
+
|
|
162
|
+
References
|
|
163
|
+
----------
|
|
164
|
+
|
|
165
|
+
.. footbibliography::
|
|
166
|
+
|
|
167
|
+
Examples
|
|
168
|
+
--------
|
|
169
|
+
|
|
170
|
+
Detect base stacking interactions in a DNA helix
|
|
171
|
+
|
|
172
|
+
>>> from os.path import join
|
|
173
|
+
>>> dna_helix = load_structure(
|
|
174
|
+
... join(path_to_structures, "base_pairs", "1qxb.cif"), include_bonds=True
|
|
175
|
+
... )
|
|
176
|
+
>>> interactions = find_stacking_interactions(dna_helix)
|
|
177
|
+
>>> for ring_atom_indices_1, ring_atom_indices_2, stacking_type in interactions:
|
|
178
|
+
... print(
|
|
179
|
+
... dna_helix.res_id[ring_atom_indices_1[0]],
|
|
180
|
+
... dna_helix.res_id[ring_atom_indices_2[0]],
|
|
181
|
+
... PiStacking(stacking_type).name
|
|
182
|
+
... )
|
|
183
|
+
17 18 PARALLEL
|
|
184
|
+
17 18 PARALLEL
|
|
185
|
+
5 6 PARALLEL
|
|
186
|
+
5 6 PARALLEL
|
|
187
|
+
5 6 PARALLEL
|
|
188
|
+
"""
|
|
189
|
+
rings = find_aromatic_rings(atoms)
|
|
190
|
+
if len(rings) == 0:
|
|
191
|
+
return []
|
|
192
|
+
|
|
193
|
+
ring_centroids = np.array(
|
|
194
|
+
[atoms.coord[atom_indices].mean(axis=0) for atom_indices in rings]
|
|
195
|
+
)
|
|
196
|
+
ring_normals = np.array(
|
|
197
|
+
[_get_ring_normal(atoms.coord[atom_indices]) for atom_indices in rings]
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
# Create an index array that contains the Cartesian product of all rings
|
|
201
|
+
indices = np.stack(
|
|
202
|
+
[
|
|
203
|
+
np.repeat(np.arange(len(rings)), len(rings)),
|
|
204
|
+
np.tile(np.arange(len(rings)), len(rings)),
|
|
205
|
+
],
|
|
206
|
+
axis=-1,
|
|
207
|
+
)
|
|
208
|
+
# Do not include duplicate pairs
|
|
209
|
+
indices = indices[indices[:, 0] > indices[:, 1]]
|
|
210
|
+
|
|
211
|
+
## Condition 1: Ring centroids are close enough to each other
|
|
212
|
+
diff = displacement(ring_centroids[indices[:, 0]], ring_centroids[indices[:, 1]])
|
|
213
|
+
# Use squared distance to avoid time consuming sqrt computation
|
|
214
|
+
sq_distance = vector_dot(diff, diff)
|
|
215
|
+
is_interacting = sq_distance < centroid_cutoff**2
|
|
216
|
+
indices = indices[is_interacting]
|
|
217
|
+
|
|
218
|
+
## Condition 2: Ring planes are parallel or perpendicular
|
|
219
|
+
plane_angles = _minimum_angle(
|
|
220
|
+
ring_normals[indices[:, 0]], ring_normals[indices[:, 1]]
|
|
221
|
+
)
|
|
222
|
+
is_parallel = _is_within_tolerance(plane_angles, 0, plane_angle_tol)
|
|
223
|
+
is_perpendicular = _is_within_tolerance(plane_angles, np.pi / 2, plane_angle_tol)
|
|
224
|
+
is_interacting = is_parallel | is_perpendicular
|
|
225
|
+
indices = indices[is_interacting]
|
|
226
|
+
# Keep in sync with the shape of the filtered indices,
|
|
227
|
+
# i.e. after filtering, `is_parallel==False` means a perpendicular interaction
|
|
228
|
+
is_parallel = is_parallel[is_interacting]
|
|
229
|
+
|
|
230
|
+
## Condition 3: The ring centroids are not shifted too much
|
|
231
|
+
## (in terms of normal-centroid angle)
|
|
232
|
+
diff = displacement(ring_centroids[indices[:, 0]], ring_centroids[indices[:, 1]])
|
|
233
|
+
norm_vector(diff)
|
|
234
|
+
angles = np.stack(
|
|
235
|
+
[_minimum_angle(ring_normals[indices[:, i]], diff) for i in range(2)]
|
|
236
|
+
)
|
|
237
|
+
is_interacting = (
|
|
238
|
+
# For parallel stacking, the lateral shift may not exceed the tolerance
|
|
239
|
+
(is_parallel & np.any(_is_within_tolerance(angles, 0, shift_angle_tol), axis=0))
|
|
240
|
+
# For perpendicular stacking, one ring must be above the other,
|
|
241
|
+
# but from the perspective of the other ring, the first ring is approximately
|
|
242
|
+
# in the same plane
|
|
243
|
+
| (
|
|
244
|
+
~is_parallel
|
|
245
|
+
& (
|
|
246
|
+
(
|
|
247
|
+
_is_within_tolerance(angles[0], 0, shift_angle_tol)
|
|
248
|
+
& _is_within_tolerance(angles[1], np.pi / 2, shift_angle_tol)
|
|
249
|
+
)
|
|
250
|
+
| (
|
|
251
|
+
_is_within_tolerance(angles[0], np.pi / 2, shift_angle_tol)
|
|
252
|
+
& _is_within_tolerance(angles[1], 0, shift_angle_tol)
|
|
253
|
+
)
|
|
254
|
+
)
|
|
255
|
+
)
|
|
256
|
+
)
|
|
257
|
+
indices = indices[is_interacting]
|
|
258
|
+
is_parallel = is_parallel[is_interacting]
|
|
259
|
+
|
|
260
|
+
# Only return pairs of rings where all conditions were fulfilled
|
|
261
|
+
return [
|
|
262
|
+
(
|
|
263
|
+
rings[ring_i],
|
|
264
|
+
rings[ring_j],
|
|
265
|
+
PiStacking.PARALLEL if is_parallel[i] else PiStacking.PERPENDICULAR,
|
|
266
|
+
)
|
|
267
|
+
for i, (ring_i, ring_j) in enumerate(indices)
|
|
268
|
+
]
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def _get_ring_normal(ring_coord):
|
|
272
|
+
"""
|
|
273
|
+
Get the normal vector perpendicular to the ring plane.
|
|
274
|
+
|
|
275
|
+
Parameters
|
|
276
|
+
----------
|
|
277
|
+
ring_coord : ndarray
|
|
278
|
+
The coordinates of the atoms in the ring.
|
|
279
|
+
|
|
280
|
+
Returns
|
|
281
|
+
-------
|
|
282
|
+
normal : ndarray
|
|
283
|
+
The normal vector of the ring plane.
|
|
284
|
+
"""
|
|
285
|
+
# Simply use any three atoms in the ring to calculate the normal vector
|
|
286
|
+
# We can also safely assume that there are at least three atoms in the ring,
|
|
287
|
+
# as otherwise it would not be a ring
|
|
288
|
+
normal = np.cross(ring_coord[1] - ring_coord[0], ring_coord[2] - ring_coord[0])
|
|
289
|
+
norm_vector(normal)
|
|
290
|
+
return normal
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
def _minimum_angle(v1, v2):
|
|
294
|
+
"""
|
|
295
|
+
Get the minimum angle between two vectors, i.e. the possible angle range is
|
|
296
|
+
``[0, pi/2]``.
|
|
297
|
+
|
|
298
|
+
Parameters
|
|
299
|
+
----------
|
|
300
|
+
v1, v2 : ndarray, shape=(n,3), dtype=float
|
|
301
|
+
The vectors to measure the angle between.
|
|
302
|
+
|
|
303
|
+
Returns
|
|
304
|
+
-------
|
|
305
|
+
angle : ndarray, shape=(n,), dtype=float
|
|
306
|
+
The minimum angle between the two vectors.
|
|
307
|
+
|
|
308
|
+
Notes
|
|
309
|
+
-----
|
|
310
|
+
This restriction is added here as the normal vectors of the ring planes
|
|
311
|
+
have no 'preferred side'.
|
|
312
|
+
"""
|
|
313
|
+
# Do not distinguish between the 'sides' of the rings -> take absolute of cosine
|
|
314
|
+
return np.arccos(np.abs(vector_dot(v1, v2)))
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def _is_within_tolerance(angles, expected_angle, tolerance):
|
|
318
|
+
"""
|
|
319
|
+
Check if the angles are within a certain tolerance.
|
|
320
|
+
|
|
321
|
+
Parameters
|
|
322
|
+
----------
|
|
323
|
+
angles : ndarray, shape=x, dtype=float
|
|
324
|
+
The angles to check.
|
|
325
|
+
expected_angle : float
|
|
326
|
+
The expected angle.
|
|
327
|
+
tolerance : float
|
|
328
|
+
The tolerance.
|
|
329
|
+
|
|
330
|
+
Returns
|
|
331
|
+
-------
|
|
332
|
+
is_within_tolerance : ndarray, shape=x, dtype=bool
|
|
333
|
+
True if the angles are within the tolerance, False otherwise.
|
|
334
|
+
"""
|
|
335
|
+
return np.abs(angles - expected_angle) < tolerance
|
|
Binary file
|
biotite/structure/sasa.pyx
CHANGED
|
@@ -35,39 +35,38 @@ def sasa(array, float probe_radius=1.4, np.ndarray atom_filter=None,
|
|
|
35
35
|
point_number=1000, point_distr="Fibonacci", vdw_radii="ProtOr")
|
|
36
36
|
|
|
37
37
|
Calculate the Solvent Accessible Surface Area (SASA) of a protein.
|
|
38
|
-
|
|
38
|
+
|
|
39
39
|
This function uses the Shrake-Rupley ("rolling probe")
|
|
40
40
|
algorithm :footcite:`Shrake1973`:
|
|
41
41
|
Every atom is occupied by a evenly distributed point mesh. The
|
|
42
42
|
points that can be reached by the "rolling probe", are surface
|
|
43
43
|
accessible.
|
|
44
|
-
|
|
44
|
+
|
|
45
45
|
Parameters
|
|
46
46
|
----------
|
|
47
47
|
array : AtomArray
|
|
48
48
|
The protein model to calculate the SASA for.
|
|
49
49
|
probe_radius : float, optional
|
|
50
|
-
The VdW-radius of the solvent molecules
|
|
50
|
+
The VdW-radius of the solvent molecules.
|
|
51
51
|
atom_filter : ndarray, dtype=bool, optional
|
|
52
52
|
If this parameter is given, SASA is only calculated for the
|
|
53
53
|
filtered atoms.
|
|
54
54
|
ignore_ions : bool, optional
|
|
55
|
-
If true, all monoatomic ions are removed before SASA calculation
|
|
56
|
-
(default: True).
|
|
55
|
+
If true, all monoatomic ions are removed before SASA calculation.
|
|
57
56
|
point_number : int, optional
|
|
58
57
|
The number of points in the mesh occupying each atom for SASA
|
|
59
|
-
calculation
|
|
60
|
-
proportional to the amount of sphere points.
|
|
58
|
+
calculation.
|
|
59
|
+
The SASA calculation time is proportional to the amount of sphere points.
|
|
61
60
|
point_distr : str or function, optional
|
|
62
61
|
If a function is given, the function is used to calculate the
|
|
63
62
|
point distribution for the mesh (the function must take `float`
|
|
64
63
|
*n* as parameter and return a *(n x 3)* :class:`ndarray`).
|
|
65
64
|
Alternatively a string can be given to choose a built-in
|
|
66
65
|
distribution:
|
|
67
|
-
|
|
66
|
+
|
|
68
67
|
- **Fibonacci** - Distribute points using a golden section
|
|
69
68
|
spiral.
|
|
70
|
-
|
|
69
|
+
|
|
71
70
|
By default *Fibonacci* is used.
|
|
72
71
|
vdw_radii : str or ndarray, dtype=float, optional
|
|
73
72
|
Indicates the set of VdW radii to be used. If an `array`-length
|
|
@@ -76,33 +75,34 @@ def sasa(array, float probe_radius=1.4, np.ndarray atom_filter=None,
|
|
|
76
75
|
SASA calculation (e.g. solvent atoms) can have arbitrary values
|
|
77
76
|
(e.g. `NaN`). If instead a string is given, one of the
|
|
78
77
|
built-in sets is used:
|
|
79
|
-
|
|
78
|
+
|
|
80
79
|
- **ProtOr** - A set, which does not require hydrogen atoms
|
|
81
80
|
in the model. Suitable for crystal structures.
|
|
82
81
|
:footcite:`Tsai1999`
|
|
83
82
|
- **Single** - A set, which uses a defined VdW radius for
|
|
84
83
|
every single atom, therefore hydrogen atoms are required
|
|
85
84
|
in the model (e.g. NMR elucidated structures).
|
|
86
|
-
:footcite:`
|
|
87
|
-
|
|
85
|
+
Values for main group elements are taken from :footcite:`Mantina2009`,
|
|
86
|
+
and for relevant transition metals from the :footcite:`RDKit`.
|
|
87
|
+
|
|
88
88
|
By default *ProtOr* is used.
|
|
89
|
-
|
|
90
|
-
|
|
89
|
+
|
|
90
|
+
|
|
91
91
|
Returns
|
|
92
92
|
-------
|
|
93
93
|
sasa : ndarray, dtype=bool, shape=(n,)
|
|
94
|
-
Atom-wise SASA. `NaN` for atoms where SASA has not been
|
|
94
|
+
Atom-wise SASA. `NaN` for atoms where SASA has not been
|
|
95
95
|
calculated
|
|
96
96
|
(solvent atoms, hydrogen atoms (ProtOr), atoms not in `filter`).
|
|
97
|
-
|
|
97
|
+
|
|
98
98
|
References
|
|
99
99
|
----------
|
|
100
|
-
|
|
100
|
+
|
|
101
101
|
.. footbibliography::
|
|
102
|
-
|
|
102
|
+
|
|
103
103
|
"""
|
|
104
104
|
cdef int i=0, j=0, k=0, adj_atom_i=0, rel_atom_i=0
|
|
105
|
-
|
|
105
|
+
|
|
106
106
|
cdef np.ndarray sasa_filter
|
|
107
107
|
cdef np.ndarray occl_filter
|
|
108
108
|
if atom_filter is not None:
|
|
@@ -121,7 +121,7 @@ def sasa(array, float probe_radius=1.4, np.ndarray atom_filter=None,
|
|
|
121
121
|
filter = ~filter_monoatomic_ions(array)
|
|
122
122
|
sasa_filter = sasa_filter & filter
|
|
123
123
|
occl_filter = occl_filter & filter
|
|
124
|
-
|
|
124
|
+
|
|
125
125
|
cdef np.ndarray sphere_points
|
|
126
126
|
if callable(point_distr):
|
|
127
127
|
sphere_points = point_distr(point_number)
|
|
@@ -130,7 +130,7 @@ def sasa(array, float probe_radius=1.4, np.ndarray atom_filter=None,
|
|
|
130
130
|
else:
|
|
131
131
|
raise ValueError(f"'{point_distr}' is not a valid point distribution")
|
|
132
132
|
sphere_points = sphere_points.astype(np.float32)
|
|
133
|
-
|
|
133
|
+
|
|
134
134
|
cdef np.ndarray radii
|
|
135
135
|
if isinstance(vdw_radii, np.ndarray):
|
|
136
136
|
radii = vdw_radii.astype(np.float32)
|
|
@@ -158,17 +158,17 @@ def sasa(array, float probe_radius=1.4, np.ndarray atom_filter=None,
|
|
|
158
158
|
raise KeyError(f"'{vdw_radii}' is not a valid radii set")
|
|
159
159
|
# Increase atom radii by probe size ("rolling probe")
|
|
160
160
|
radii += probe_radius
|
|
161
|
-
|
|
161
|
+
|
|
162
162
|
# Memoryview for filter
|
|
163
163
|
# Problem with creating boolean memoryviews
|
|
164
164
|
# -> Type uint8 is used
|
|
165
165
|
cdef np_bool[:] sasa_filter_view = np.frombuffer(sasa_filter,
|
|
166
166
|
dtype=np.uint8)
|
|
167
|
-
|
|
167
|
+
|
|
168
168
|
cdef np.ndarray occl_r = radii[occl_filter]
|
|
169
169
|
# Atom array containing occluding atoms
|
|
170
170
|
occl_array = array[occl_filter]
|
|
171
|
-
|
|
171
|
+
|
|
172
172
|
# Memoryviews for coordinates of entire (main) array
|
|
173
173
|
# and for coordinates of occluding atom array
|
|
174
174
|
cdef float32[:,:] main_coord = array.coord.astype(np.float32,
|
|
@@ -190,10 +190,10 @@ def sasa(array, float probe_radius=1.4, np.ndarray atom_filter=None,
|
|
|
190
190
|
cdef float32[:] occl_radii_sq = occl_r * occl_r
|
|
191
191
|
# Memoryview for atomwise SASA
|
|
192
192
|
cdef float32[:] sasa = np.full(len(array), np.nan, dtype=np.float32)
|
|
193
|
-
|
|
193
|
+
|
|
194
194
|
# Area of a sphere point on a unit sphere
|
|
195
195
|
cdef float32 area_per_point = 4.0 * np.pi / point_number
|
|
196
|
-
|
|
196
|
+
|
|
197
197
|
# Define further statically typed variables
|
|
198
198
|
# that are needed for SASA calculation
|
|
199
199
|
cdef int n_accesible = 0
|
|
@@ -212,8 +212,8 @@ def sasa(array, float probe_radius=1.4, np.ndarray atom_filter=None,
|
|
|
212
212
|
cdef float32 occl_y = 0
|
|
213
213
|
cdef float32 occl_z = 0
|
|
214
214
|
cdef float32[:,:] relevant_occl_coord = None
|
|
215
|
-
|
|
216
|
-
# Cell size is as large as the maximum distance,
|
|
215
|
+
|
|
216
|
+
# Cell size is as large as the maximum distance,
|
|
217
217
|
# where two atom can intersect.
|
|
218
218
|
# Therefore intersecting atoms are always in the same or adjacent cell.
|
|
219
219
|
cell_list = CellList(occl_array, np.max(radii[occl_filter])*2)
|
|
@@ -226,7 +226,7 @@ def sasa(array, float probe_radius=1.4, np.ndarray atom_filter=None,
|
|
|
226
226
|
cell_indices = cell_list.get_atoms_in_cells(array.coord)
|
|
227
227
|
cell_indices_view = cell_indices
|
|
228
228
|
max_adj_list_length = cell_indices.shape[0]
|
|
229
|
-
|
|
229
|
+
|
|
230
230
|
# Later on, this array stores coordinates for actual
|
|
231
231
|
# occluding atoms for a certain atom to calculate the
|
|
232
232
|
# SASA for
|
|
@@ -236,7 +236,7 @@ def sasa(array, float probe_radius=1.4, np.ndarray atom_filter=None,
|
|
|
236
236
|
# adjacent atoms
|
|
237
237
|
relevant_occl_coord = np.zeros((max_adj_list_length, 4),
|
|
238
238
|
dtype=np.float32)
|
|
239
|
-
|
|
239
|
+
|
|
240
240
|
# Actual SASA calculation
|
|
241
241
|
for i in range(array_length):
|
|
242
242
|
# First level: The atoms to calculate SASA for
|