biotite 1.1.0__cp313-cp313-macosx_11_0_arm64.whl → 1.2.0__cp313-cp313-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.

Files changed (155) hide show
  1. biotite/application/application.py +3 -3
  2. biotite/application/autodock/app.py +1 -1
  3. biotite/application/blast/webapp.py +1 -1
  4. biotite/application/clustalo/app.py +1 -1
  5. biotite/application/localapp.py +2 -2
  6. biotite/application/msaapp.py +10 -10
  7. biotite/application/muscle/app3.py +3 -3
  8. biotite/application/muscle/app5.py +3 -3
  9. biotite/application/sra/app.py +0 -5
  10. biotite/application/util.py +21 -1
  11. biotite/application/viennarna/rnaalifold.py +8 -8
  12. biotite/application/viennarna/rnaplot.py +3 -1
  13. biotite/application/viennarna/util.py +1 -1
  14. biotite/application/webapp.py +1 -1
  15. biotite/database/afdb/__init__.py +12 -0
  16. biotite/database/afdb/download.py +191 -0
  17. biotite/database/entrez/dbnames.py +10 -0
  18. biotite/database/entrez/download.py +9 -10
  19. biotite/database/entrez/key.py +1 -1
  20. biotite/database/entrez/query.py +5 -4
  21. biotite/database/pubchem/download.py +6 -6
  22. biotite/database/pubchem/error.py +10 -0
  23. biotite/database/pubchem/query.py +12 -23
  24. biotite/database/rcsb/download.py +3 -2
  25. biotite/database/rcsb/query.py +2 -3
  26. biotite/database/uniprot/check.py +2 -2
  27. biotite/database/uniprot/download.py +2 -5
  28. biotite/database/uniprot/query.py +3 -4
  29. biotite/file.py +14 -2
  30. biotite/interface/__init__.py +19 -0
  31. biotite/interface/openmm/__init__.py +16 -0
  32. biotite/interface/openmm/state.py +93 -0
  33. biotite/interface/openmm/system.py +227 -0
  34. biotite/interface/pymol/__init__.py +198 -0
  35. biotite/interface/pymol/cgo.py +346 -0
  36. biotite/interface/pymol/convert.py +185 -0
  37. biotite/interface/pymol/display.py +267 -0
  38. biotite/interface/pymol/object.py +1226 -0
  39. biotite/interface/pymol/shapes.py +178 -0
  40. biotite/interface/pymol/startup.py +169 -0
  41. biotite/interface/rdkit/__init__.py +15 -0
  42. biotite/interface/rdkit/mol.py +490 -0
  43. biotite/interface/version.py +71 -0
  44. biotite/interface/warning.py +19 -0
  45. biotite/sequence/align/__init__.py +0 -4
  46. biotite/sequence/align/alignment.py +33 -11
  47. biotite/sequence/align/banded.cpython-313-darwin.so +0 -0
  48. biotite/sequence/align/banded.pyx +21 -21
  49. biotite/sequence/align/cigar.py +2 -2
  50. biotite/sequence/align/kmeralphabet.cpython-313-darwin.so +0 -0
  51. biotite/sequence/align/kmeralphabet.pyx +2 -2
  52. biotite/sequence/align/kmersimilarity.cpython-313-darwin.so +0 -0
  53. biotite/sequence/align/kmertable.cpython-313-darwin.so +0 -0
  54. biotite/sequence/align/kmertable.pyx +6 -6
  55. biotite/sequence/align/localgapped.cpython-313-darwin.so +0 -0
  56. biotite/sequence/align/localgapped.pyx +47 -47
  57. biotite/sequence/align/localungapped.cpython-313-darwin.so +0 -0
  58. biotite/sequence/align/localungapped.pyx +10 -10
  59. biotite/sequence/align/matrix.py +12 -3
  60. biotite/sequence/align/multiple.cpython-313-darwin.so +0 -0
  61. biotite/sequence/align/pairwise.cpython-313-darwin.so +0 -0
  62. biotite/sequence/align/pairwise.pyx +35 -35
  63. biotite/sequence/align/permutation.cpython-313-darwin.so +0 -0
  64. biotite/sequence/align/selector.cpython-313-darwin.so +0 -0
  65. biotite/sequence/align/selector.pyx +2 -2
  66. biotite/sequence/align/statistics.py +1 -1
  67. biotite/sequence/align/tracetable.cpython-313-darwin.so +0 -0
  68. biotite/sequence/alphabet.py +2 -2
  69. biotite/sequence/annotation.py +19 -13
  70. biotite/sequence/codec.cpython-313-darwin.so +0 -0
  71. biotite/sequence/codon.py +1 -2
  72. biotite/sequence/graphics/alignment.py +25 -39
  73. biotite/sequence/graphics/dendrogram.py +4 -2
  74. biotite/sequence/graphics/features.py +2 -2
  75. biotite/sequence/graphics/logo.py +10 -12
  76. biotite/sequence/io/fasta/convert.py +1 -2
  77. biotite/sequence/io/fasta/file.py +1 -1
  78. biotite/sequence/io/fastq/file.py +3 -3
  79. biotite/sequence/io/genbank/file.py +3 -3
  80. biotite/sequence/io/genbank/sequence.py +2 -0
  81. biotite/sequence/io/gff/convert.py +1 -1
  82. biotite/sequence/io/gff/file.py +1 -2
  83. biotite/sequence/phylo/nj.cpython-313-darwin.so +0 -0
  84. biotite/sequence/phylo/tree.cpython-313-darwin.so +0 -0
  85. biotite/sequence/phylo/upgma.cpython-313-darwin.so +0 -0
  86. biotite/sequence/profile.py +19 -25
  87. biotite/sequence/search.py +0 -1
  88. biotite/sequence/seqtypes.py +12 -5
  89. biotite/sequence/sequence.py +1 -2
  90. biotite/structure/__init__.py +2 -0
  91. biotite/structure/alphabet/i3d.py +1 -2
  92. biotite/structure/alphabet/pb.py +1 -2
  93. biotite/structure/alphabet/unkerasify.py +8 -2
  94. biotite/structure/atoms.py +35 -27
  95. biotite/structure/basepairs.py +26 -26
  96. biotite/structure/bonds.cpython-313-darwin.so +0 -0
  97. biotite/structure/bonds.pyx +8 -5
  98. biotite/structure/box.py +19 -21
  99. biotite/structure/celllist.cpython-313-darwin.so +0 -0
  100. biotite/structure/celllist.pyx +83 -67
  101. biotite/structure/chains.py +5 -37
  102. biotite/structure/charges.cpython-313-darwin.so +0 -0
  103. biotite/structure/compare.py +420 -13
  104. biotite/structure/density.py +1 -1
  105. biotite/structure/dotbracket.py +27 -28
  106. biotite/structure/filter.py +8 -8
  107. biotite/structure/geometry.py +15 -15
  108. biotite/structure/hbond.py +17 -19
  109. biotite/structure/info/atoms.py +11 -2
  110. biotite/structure/info/ccd.py +0 -2
  111. biotite/structure/info/components.bcif +0 -0
  112. biotite/structure/info/groups.py +0 -3
  113. biotite/structure/info/misc.py +0 -1
  114. biotite/structure/info/radii.py +92 -22
  115. biotite/structure/info/standardize.py +1 -2
  116. biotite/structure/integrity.py +4 -6
  117. biotite/structure/io/general.py +2 -2
  118. biotite/structure/io/gro/file.py +8 -9
  119. biotite/structure/io/mol/convert.py +1 -1
  120. biotite/structure/io/mol/ctab.py +33 -28
  121. biotite/structure/io/mol/mol.py +1 -1
  122. biotite/structure/io/mol/sdf.py +39 -13
  123. biotite/structure/io/pdb/convert.py +2 -3
  124. biotite/structure/io/pdb/file.py +11 -22
  125. biotite/structure/io/pdb/hybrid36.cpython-313-darwin.so +0 -0
  126. biotite/structure/io/pdbqt/file.py +4 -4
  127. biotite/structure/io/pdbx/bcif.py +22 -7
  128. biotite/structure/io/pdbx/cif.py +20 -7
  129. biotite/structure/io/pdbx/component.py +6 -0
  130. biotite/structure/io/pdbx/compress.py +2 -2
  131. biotite/structure/io/pdbx/convert.py +222 -33
  132. biotite/structure/io/pdbx/encoding.cpython-313-darwin.so +0 -0
  133. biotite/structure/io/trajfile.py +9 -6
  134. biotite/structure/io/util.py +38 -0
  135. biotite/structure/mechanics.py +0 -1
  136. biotite/structure/molecules.py +0 -15
  137. biotite/structure/pseudoknots.py +7 -13
  138. biotite/structure/repair.py +2 -4
  139. biotite/structure/residues.py +13 -24
  140. biotite/structure/rings.py +335 -0
  141. biotite/structure/sasa.cpython-313-darwin.so +0 -0
  142. biotite/structure/sasa.pyx +2 -1
  143. biotite/structure/segments.py +68 -9
  144. biotite/structure/sequence.py +0 -1
  145. biotite/structure/sse.py +0 -2
  146. biotite/structure/superimpose.py +74 -62
  147. biotite/structure/tm.py +581 -0
  148. biotite/structure/transform.py +12 -25
  149. biotite/structure/util.py +3 -3
  150. biotite/version.py +9 -4
  151. biotite/visualize.py +111 -1
  152. {biotite-1.1.0.dist-info → biotite-1.2.0.dist-info}/METADATA +5 -3
  153. {biotite-1.1.0.dist-info → biotite-1.2.0.dist-info}/RECORD +155 -135
  154. {biotite-1.1.0.dist-info → biotite-1.2.0.dist-info}/WHEEL +0 -0
  155. {biotite-1.1.0.dist-info → biotite-1.2.0.dist-info}/licenses/LICENSE.rst +0 -0
@@ -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
- first dimension of this array is equal to the amount of
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. The length of axis=0 must be equal to
197
- the amount of different residue IDs in `array`.
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`. Length is the same as
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
- start_indices : ndarray, dtype=int, shape=(k,)
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
- return segment_iter(array, starts)
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
@@ -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:`Bondi1964`
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
 
@@ -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
- output_data = np.zeros(starts[-1], dtype=input_data.dtype)
70
- for i in range(len(starts) - 1):
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 " f"an atom array with length {length}"
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 " f"an atom array with length {length}"
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 " f"an atom array with length {length}"
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]]
@@ -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