biotite 1.1.0__cp312-cp312-macosx_11_0_arm64.whl → 1.2.0__cp312-cp312-macosx_11_0_arm64.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 +3 -1
- 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 +16 -0
- biotite/interface/openmm/state.py +93 -0
- biotite/interface/openmm/system.py +227 -0
- biotite/interface/pymol/__init__.py +198 -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 +15 -0
- biotite/interface/rdkit/mol.py +490 -0
- biotite/interface/version.py +71 -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.cpython-312-darwin.so +0 -0
- biotite/sequence/align/banded.pyx +21 -21
- biotite/sequence/align/cigar.py +2 -2
- biotite/sequence/align/kmeralphabet.cpython-312-darwin.so +0 -0
- biotite/sequence/align/kmeralphabet.pyx +2 -2
- biotite/sequence/align/kmersimilarity.cpython-312-darwin.so +0 -0
- biotite/sequence/align/kmertable.cpython-312-darwin.so +0 -0
- biotite/sequence/align/kmertable.pyx +6 -6
- biotite/sequence/align/localgapped.cpython-312-darwin.so +0 -0
- biotite/sequence/align/localgapped.pyx +47 -47
- biotite/sequence/align/localungapped.cpython-312-darwin.so +0 -0
- biotite/sequence/align/localungapped.pyx +10 -10
- biotite/sequence/align/matrix.py +12 -3
- biotite/sequence/align/multiple.cpython-312-darwin.so +0 -0
- biotite/sequence/align/pairwise.cpython-312-darwin.so +0 -0
- biotite/sequence/align/pairwise.pyx +35 -35
- biotite/sequence/align/permutation.cpython-312-darwin.so +0 -0
- biotite/sequence/align/selector.cpython-312-darwin.so +0 -0
- biotite/sequence/align/selector.pyx +2 -2
- biotite/sequence/align/statistics.py +1 -1
- biotite/sequence/align/tracetable.cpython-312-darwin.so +0 -0
- biotite/sequence/alphabet.py +2 -2
- biotite/sequence/annotation.py +19 -13
- biotite/sequence/codec.cpython-312-darwin.so +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.cpython-312-darwin.so +0 -0
- biotite/sequence/phylo/tree.cpython-312-darwin.so +0 -0
- biotite/sequence/phylo/upgma.cpython-312-darwin.so +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 +26 -26
- biotite/structure/bonds.cpython-312-darwin.so +0 -0
- biotite/structure/bonds.pyx +8 -5
- biotite/structure/box.py +19 -21
- biotite/structure/celllist.cpython-312-darwin.so +0 -0
- biotite/structure/celllist.pyx +83 -67
- biotite/structure/chains.py +5 -37
- biotite/structure/charges.cpython-312-darwin.so +0 -0
- biotite/structure/compare.py +420 -13
- biotite/structure/density.py +1 -1
- biotite/structure/dotbracket.py +27 -28
- biotite/structure/filter.py +8 -8
- biotite/structure/geometry.py +15 -15
- biotite/structure/hbond.py +17 -19
- 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 +2 -3
- biotite/structure/io/pdb/file.py +11 -22
- biotite/structure/io/pdb/hybrid36.cpython-312-darwin.so +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 +2 -2
- biotite/structure/io/pdbx/convert.py +222 -33
- biotite/structure/io/pdbx/encoding.cpython-312-darwin.so +0 -0
- 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 +7 -13
- biotite/structure/repair.py +2 -4
- biotite/structure/residues.py +13 -24
- biotite/structure/rings.py +335 -0
- biotite/structure/sasa.cpython-312-darwin.so +0 -0
- biotite/structure/sasa.pyx +2 -1
- biotite/structure/segments.py +68 -9
- biotite/structure/sequence.py +0 -1
- biotite/structure/sse.py +0 -2
- biotite/structure/superimpose.py +74 -62
- biotite/structure/tm.py +581 -0
- biotite/structure/transform.py +12 -25
- biotite/structure/util.py +3 -3
- biotite/version.py +9 -4
- biotite/visualize.py +111 -1
- {biotite-1.1.0.dist-info → biotite-1.2.0.dist-info}/METADATA +5 -3
- {biotite-1.1.0.dist-info → biotite-1.2.0.dist-info}/RECORD +155 -135
- {biotite-1.1.0.dist-info → biotite-1.2.0.dist-info}/WHEEL +0 -0
- {biotite-1.1.0.dist-info → biotite-1.2.0.dist-info}/licenses/LICENSE.rst +0 -0
biotite/structure/residues.py
CHANGED
|
@@ -69,6 +69,9 @@ def get_residue_starts(array, add_exclusive_stop=False):
|
|
|
69
69
|
[ 0 16 35 56 75 92 116 135 157 169 176 183 197 208 219 226 250 264
|
|
70
70
|
278 292 304]
|
|
71
71
|
"""
|
|
72
|
+
if array.array_length() == 0:
|
|
73
|
+
return np.array([], dtype=int)
|
|
74
|
+
|
|
72
75
|
# These mask are 'true' at indices where the value changes
|
|
73
76
|
chain_id_changes = array.chain_id[1:] != array.chain_id[:-1]
|
|
74
77
|
res_id_changes = array.res_id[1:] != array.res_id[:-1]
|
|
@@ -123,9 +126,8 @@ def apply_residue_wise(array, data, function, axis=None):
|
|
|
123
126
|
Returns
|
|
124
127
|
-------
|
|
125
128
|
processed_data : ndarray
|
|
126
|
-
Residue-wise evaluation of `data` by `function`. The size of the
|
|
127
|
-
|
|
128
|
-
residues.
|
|
129
|
+
Residue-wise evaluation of `data` by `function`. The size of the first dimension
|
|
130
|
+
of this array is equal to the amount of residues.
|
|
129
131
|
|
|
130
132
|
Examples
|
|
131
133
|
--------
|
|
@@ -193,14 +195,15 @@ def spread_residue_wise(array, input_data):
|
|
|
193
195
|
array : AtomArray or AtomArrayStack
|
|
194
196
|
The atom array (stack) to determine the residues from.
|
|
195
197
|
input_data : ndarray
|
|
196
|
-
The data to be spread.
|
|
197
|
-
the amount of different residue IDs
|
|
198
|
+
The data to be spread.
|
|
199
|
+
The length of the 0-th axis must be equal to the amount of different residue IDs
|
|
200
|
+
in `array`.
|
|
198
201
|
|
|
199
202
|
Returns
|
|
200
203
|
-------
|
|
201
204
|
output_data : ndarray
|
|
202
|
-
Residue-wise spread `input_data`.
|
|
203
|
-
`array_length()` of `array`.
|
|
205
|
+
Residue-wise spread `input_data`.
|
|
206
|
+
Length is the same as `array_length()` of `array`.
|
|
204
207
|
|
|
205
208
|
Examples
|
|
206
209
|
--------
|
|
@@ -260,11 +263,6 @@ def get_residue_masks(array, indices):
|
|
|
260
263
|
Each array masks the atoms that belong to the same residue as
|
|
261
264
|
the atom at the given index.
|
|
262
265
|
|
|
263
|
-
See also
|
|
264
|
-
--------
|
|
265
|
-
get_residue_starts_for
|
|
266
|
-
get_residue_positions
|
|
267
|
-
|
|
268
266
|
Examples
|
|
269
267
|
--------
|
|
270
268
|
|
|
@@ -338,11 +336,6 @@ def get_residue_starts_for(array, indices):
|
|
|
338
336
|
The indices that point to the residue starts for the input
|
|
339
337
|
`indices`.
|
|
340
338
|
|
|
341
|
-
See also
|
|
342
|
-
--------
|
|
343
|
-
get_residue_masks
|
|
344
|
-
get_residue_positions
|
|
345
|
-
|
|
346
339
|
Examples
|
|
347
340
|
--------
|
|
348
341
|
|
|
@@ -382,14 +375,9 @@ def get_residue_positions(array, indices):
|
|
|
382
375
|
|
|
383
376
|
Returns
|
|
384
377
|
-------
|
|
385
|
-
|
|
378
|
+
residue_indices : ndarray, dtype=int, shape=(k,)
|
|
386
379
|
The indices that point to the position of the residues.
|
|
387
380
|
|
|
388
|
-
See also
|
|
389
|
-
--------
|
|
390
|
-
get_residue_masks
|
|
391
|
-
get_residue_starts_for
|
|
392
|
-
|
|
393
381
|
Examples
|
|
394
382
|
--------
|
|
395
383
|
>>> atom_index = [5, 42]
|
|
@@ -569,4 +557,5 @@ def residue_iter(array):
|
|
|
569
557
|
"""
|
|
570
558
|
# The exclusive stop is appended to the residue starts
|
|
571
559
|
starts = get_residue_starts(array, add_exclusive_stop=True)
|
|
572
|
-
|
|
560
|
+
for residue in segment_iter(array, starts):
|
|
561
|
+
yield residue
|
|
@@ -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 distance (default: 6.5 Å).
|
|
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
|
@@ -83,7 +83,8 @@ def sasa(array, float probe_radius=1.4, np.ndarray atom_filter=None,
|
|
|
83
83
|
- **Single** - A set, which uses a defined VdW radius for
|
|
84
84
|
every single atom, therefore hydrogen atoms are required
|
|
85
85
|
in the model (e.g. NMR elucidated structures).
|
|
86
|
-
:footcite:`
|
|
86
|
+
Values for main group elements are taken from :footcite:`Mantina2009`,
|
|
87
|
+
and for relevant transition metals from the :footcite:`RDKit`.
|
|
87
88
|
|
|
88
89
|
By default *ProtOr* is used.
|
|
89
90
|
|
biotite/structure/segments.py
CHANGED
|
@@ -27,6 +27,22 @@ def apply_segment_wise(starts, data, function, axis=None):
|
|
|
27
27
|
The sorted start indices of segments.
|
|
28
28
|
Includes exclusive stop, i.e. the length of the corresponding
|
|
29
29
|
atom array.
|
|
30
|
+
data : ndarray
|
|
31
|
+
The data, whose intervals are the parameter for `function`.
|
|
32
|
+
Must have same length as `array`.
|
|
33
|
+
function : function
|
|
34
|
+
The `function` must have either the form *f(data)* or
|
|
35
|
+
*f(data, axis)* in case `axis` is given. Every `function` call
|
|
36
|
+
must return a value with the same shape and data type.
|
|
37
|
+
axis : int, optional
|
|
38
|
+
This value is given to the `axis` parameter of `function`.
|
|
39
|
+
|
|
40
|
+
Returns
|
|
41
|
+
-------
|
|
42
|
+
processed_data : ndarray
|
|
43
|
+
Segment-wise evaluation of `data` by `function`.
|
|
44
|
+
The size of the first dimension of this array is equal to the amount of
|
|
45
|
+
residues.
|
|
30
46
|
"""
|
|
31
47
|
# The result array
|
|
32
48
|
processed_data = None
|
|
@@ -65,13 +81,19 @@ def spread_segment_wise(starts, input_data):
|
|
|
65
81
|
The sorted start indices of segments.
|
|
66
82
|
Includes exclusive stop, i.e. the length of the corresponding
|
|
67
83
|
atom array.
|
|
84
|
+
input_data : ndarray
|
|
85
|
+
The data to be spread.
|
|
86
|
+
The length of the 0-th axis must be equal to the amount of different residue IDs
|
|
87
|
+
in `array`.
|
|
88
|
+
|
|
89
|
+
Returns
|
|
90
|
+
-------
|
|
91
|
+
output_data : ndarray
|
|
92
|
+
Segment-wise spread `input_data`.
|
|
93
|
+
Length is the same as `array_length()` of `array`.
|
|
68
94
|
"""
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
start = starts[i]
|
|
72
|
-
stop = starts[i + 1]
|
|
73
|
-
output_data[start:stop] = input_data[i]
|
|
74
|
-
return output_data
|
|
95
|
+
seg_lens = starts[1:] - starts[:-1]
|
|
96
|
+
return np.repeat(input_data, seg_lens, axis=0)
|
|
75
97
|
|
|
76
98
|
|
|
77
99
|
def get_segment_masks(starts, indices):
|
|
@@ -85,6 +107,17 @@ def get_segment_masks(starts, indices):
|
|
|
85
107
|
The sorted start indices of segments.
|
|
86
108
|
Includes exclusive stop, i.e. the length of the corresponding
|
|
87
109
|
atom array.
|
|
110
|
+
indices : ndarray, dtype=int, shape=(k,)
|
|
111
|
+
These indices indicate the atoms to get the corresponding
|
|
112
|
+
segments for.
|
|
113
|
+
Negative indices are not allowed.
|
|
114
|
+
|
|
115
|
+
Returns
|
|
116
|
+
-------
|
|
117
|
+
residues_masks : ndarray, dtype=bool, shape=(k,n)
|
|
118
|
+
Multiple boolean masks, one for each given index in `indices`.
|
|
119
|
+
Each array masks the atoms that belong to the same segment as
|
|
120
|
+
the atom at the given index.
|
|
88
121
|
"""
|
|
89
122
|
indices = np.asarray(indices)
|
|
90
123
|
length = starts[-1]
|
|
@@ -95,7 +128,7 @@ def get_segment_masks(starts, indices):
|
|
|
95
128
|
if (indices >= length).any():
|
|
96
129
|
index = np.min(np.where(indices >= length)[0])
|
|
97
130
|
raise ValueError(
|
|
98
|
-
f"Index {index} is out of range for
|
|
131
|
+
f"Index {index} is out of range for an atom array with length {length}"
|
|
99
132
|
)
|
|
100
133
|
|
|
101
134
|
insertion_points = np.searchsorted(starts, indices, side="right") - 1
|
|
@@ -116,6 +149,16 @@ def get_segment_starts_for(starts, indices):
|
|
|
116
149
|
The sorted start indices of segments.
|
|
117
150
|
Includes exclusive stop, i.e. the length of the corresponding
|
|
118
151
|
atom array.
|
|
152
|
+
indices : ndarray, dtype=int, shape=(k,)
|
|
153
|
+
These indices point to the atoms to get the corresponding
|
|
154
|
+
segment starts for.
|
|
155
|
+
Negative indices are not allowed.
|
|
156
|
+
|
|
157
|
+
Returns
|
|
158
|
+
-------
|
|
159
|
+
start_indices : ndarray, dtype=int, shape=(k,)
|
|
160
|
+
The indices that point to the segment starts for the input
|
|
161
|
+
`indices`.
|
|
119
162
|
"""
|
|
120
163
|
indices = np.asarray(indices)
|
|
121
164
|
length = starts[-1]
|
|
@@ -127,7 +170,7 @@ def get_segment_starts_for(starts, indices):
|
|
|
127
170
|
if (indices >= length).any():
|
|
128
171
|
index = np.min(np.where(indices >= length)[0])
|
|
129
172
|
raise ValueError(
|
|
130
|
-
f"Index {index} is out of range for
|
|
173
|
+
f"Index {index} is out of range for an atom array with length {length}"
|
|
131
174
|
)
|
|
132
175
|
|
|
133
176
|
insertion_points = np.searchsorted(starts, indices, side="right") - 1
|
|
@@ -145,6 +188,15 @@ def get_segment_positions(starts, indices):
|
|
|
145
188
|
The sorted start indices of segments.
|
|
146
189
|
Includes exclusive stop, i.e. the length of the corresponding
|
|
147
190
|
atom array.
|
|
191
|
+
indices : ndarray, shape=(k,)
|
|
192
|
+
These indices point to the atoms to get the corresponding
|
|
193
|
+
residue positions for.
|
|
194
|
+
Negative indices are not allowed.
|
|
195
|
+
|
|
196
|
+
Returns
|
|
197
|
+
-------
|
|
198
|
+
segment_indices : ndarray, shape=(k,)
|
|
199
|
+
The indices that point to the position of the segments.
|
|
148
200
|
"""
|
|
149
201
|
indices = np.asarray(indices)
|
|
150
202
|
length = starts[-1]
|
|
@@ -156,7 +208,7 @@ def get_segment_positions(starts, indices):
|
|
|
156
208
|
if (indices >= length).any():
|
|
157
209
|
index = np.min(np.where(indices >= length)[0])
|
|
158
210
|
raise ValueError(
|
|
159
|
-
f"Index {index} is out of range for
|
|
211
|
+
f"Index {index} is out of range for an atom array with length {length}"
|
|
160
212
|
)
|
|
161
213
|
|
|
162
214
|
return np.searchsorted(starts, indices, side="right") - 1
|
|
@@ -169,10 +221,17 @@ def segment_iter(array, starts):
|
|
|
169
221
|
|
|
170
222
|
Parameters
|
|
171
223
|
----------
|
|
224
|
+
array : AtomArray or AtomArrayStack
|
|
225
|
+
The structure to iterate over.
|
|
172
226
|
starts : ndarray, dtype=int
|
|
173
227
|
The sorted start indices of segments.
|
|
174
228
|
Includes exclusive stop, i.e. the length of the corresponding
|
|
175
229
|
atom array.
|
|
230
|
+
|
|
231
|
+
Yields
|
|
232
|
+
------
|
|
233
|
+
segment : AtomArray or AtomArrayStack
|
|
234
|
+
Each residue or chain of the structure.
|
|
176
235
|
"""
|
|
177
236
|
for i in range(len(starts) - 1):
|
|
178
237
|
yield array[..., starts[i] : starts[i + 1]]
|
biotite/structure/sequence.py
CHANGED
|
@@ -58,7 +58,6 @@ def to_sequence(atoms, allow_hetero=False):
|
|
|
58
58
|
>>> sequences, chain_starts = to_sequence(atom_array)
|
|
59
59
|
>>> print(sequences)
|
|
60
60
|
[ProteinSequence("NLYIQWLKDGGPSSGRPPPS")]
|
|
61
|
-
|
|
62
61
|
"""
|
|
63
62
|
sequences = []
|
|
64
63
|
chain_start_indices = get_chain_starts(atoms, add_exclusive_stop=True)
|
biotite/structure/sse.py
CHANGED
|
@@ -48,7 +48,6 @@ def annotate_sse(atom_array):
|
|
|
48
48
|
Non-peptide residues are also allowed and obtain a ``''``
|
|
49
49
|
SSE.
|
|
50
50
|
|
|
51
|
-
|
|
52
51
|
Returns
|
|
53
52
|
-------
|
|
54
53
|
sse : ndarray
|
|
@@ -81,7 +80,6 @@ def annotate_sse(atom_array):
|
|
|
81
80
|
>>> print(sse)
|
|
82
81
|
['c' 'a' 'a' 'a' 'a' 'a' 'a' 'a' 'a' 'c' 'c' 'c' 'c' 'c' 'c' 'c' 'c' 'c'
|
|
83
82
|
'c' 'c']
|
|
84
|
-
|
|
85
83
|
"""
|
|
86
84
|
residue_starts = get_residue_starts(atom_array)
|
|
87
85
|
# Sort CA coord into the coord array at the respective residue index
|