biotite 1.1.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 (332) hide show
  1. biotite/__init__.py +18 -0
  2. biotite/application/__init__.py +69 -0
  3. biotite/application/application.py +276 -0
  4. biotite/application/autodock/__init__.py +12 -0
  5. biotite/application/autodock/app.py +500 -0
  6. biotite/application/blast/__init__.py +14 -0
  7. biotite/application/blast/alignment.py +92 -0
  8. biotite/application/blast/webapp.py +428 -0
  9. biotite/application/clustalo/__init__.py +12 -0
  10. biotite/application/clustalo/app.py +223 -0
  11. biotite/application/dssp/__init__.py +12 -0
  12. biotite/application/dssp/app.py +159 -0
  13. biotite/application/localapp.py +342 -0
  14. biotite/application/mafft/__init__.py +12 -0
  15. biotite/application/mafft/app.py +116 -0
  16. biotite/application/msaapp.py +363 -0
  17. biotite/application/muscle/__init__.py +13 -0
  18. biotite/application/muscle/app3.py +227 -0
  19. biotite/application/muscle/app5.py +163 -0
  20. biotite/application/sra/__init__.py +18 -0
  21. biotite/application/sra/app.py +452 -0
  22. biotite/application/tantan/__init__.py +12 -0
  23. biotite/application/tantan/app.py +199 -0
  24. biotite/application/util.py +57 -0
  25. biotite/application/viennarna/__init__.py +18 -0
  26. biotite/application/viennarna/rnaalifold.py +310 -0
  27. biotite/application/viennarna/rnafold.py +254 -0
  28. biotite/application/viennarna/rnaplot.py +206 -0
  29. biotite/application/viennarna/util.py +77 -0
  30. biotite/application/webapp.py +76 -0
  31. biotite/copyable.py +71 -0
  32. biotite/database/__init__.py +23 -0
  33. biotite/database/entrez/__init__.py +15 -0
  34. biotite/database/entrez/check.py +60 -0
  35. biotite/database/entrez/dbnames.py +91 -0
  36. biotite/database/entrez/download.py +229 -0
  37. biotite/database/entrez/key.py +44 -0
  38. biotite/database/entrez/query.py +262 -0
  39. biotite/database/error.py +16 -0
  40. biotite/database/pubchem/__init__.py +21 -0
  41. biotite/database/pubchem/download.py +258 -0
  42. biotite/database/pubchem/error.py +20 -0
  43. biotite/database/pubchem/query.py +830 -0
  44. biotite/database/pubchem/throttle.py +98 -0
  45. biotite/database/rcsb/__init__.py +13 -0
  46. biotite/database/rcsb/download.py +159 -0
  47. biotite/database/rcsb/query.py +964 -0
  48. biotite/database/uniprot/__init__.py +13 -0
  49. biotite/database/uniprot/check.py +40 -0
  50. biotite/database/uniprot/download.py +129 -0
  51. biotite/database/uniprot/query.py +293 -0
  52. biotite/file.py +232 -0
  53. biotite/sequence/__init__.py +84 -0
  54. biotite/sequence/align/__init__.py +203 -0
  55. biotite/sequence/align/alignment.py +680 -0
  56. biotite/sequence/align/banded.cpython-313-darwin.so +0 -0
  57. biotite/sequence/align/banded.pyx +652 -0
  58. biotite/sequence/align/buckets.py +71 -0
  59. biotite/sequence/align/cigar.py +425 -0
  60. biotite/sequence/align/kmeralphabet.cpython-313-darwin.so +0 -0
  61. biotite/sequence/align/kmeralphabet.pyx +595 -0
  62. biotite/sequence/align/kmersimilarity.cpython-313-darwin.so +0 -0
  63. biotite/sequence/align/kmersimilarity.pyx +233 -0
  64. biotite/sequence/align/kmertable.cpython-313-darwin.so +0 -0
  65. biotite/sequence/align/kmertable.pyx +3411 -0
  66. biotite/sequence/align/localgapped.cpython-313-darwin.so +0 -0
  67. biotite/sequence/align/localgapped.pyx +892 -0
  68. biotite/sequence/align/localungapped.cpython-313-darwin.so +0 -0
  69. biotite/sequence/align/localungapped.pyx +279 -0
  70. biotite/sequence/align/matrix.py +622 -0
  71. biotite/sequence/align/matrix_data/3Di.mat +24 -0
  72. biotite/sequence/align/matrix_data/BLOSUM100.mat +31 -0
  73. biotite/sequence/align/matrix_data/BLOSUM30.mat +31 -0
  74. biotite/sequence/align/matrix_data/BLOSUM35.mat +31 -0
  75. biotite/sequence/align/matrix_data/BLOSUM40.mat +31 -0
  76. biotite/sequence/align/matrix_data/BLOSUM45.mat +31 -0
  77. biotite/sequence/align/matrix_data/BLOSUM50.mat +31 -0
  78. biotite/sequence/align/matrix_data/BLOSUM50_13p.mat +25 -0
  79. biotite/sequence/align/matrix_data/BLOSUM50_14.3.mat +25 -0
  80. biotite/sequence/align/matrix_data/BLOSUM50_5.0.mat +25 -0
  81. biotite/sequence/align/matrix_data/BLOSUM55.mat +31 -0
  82. biotite/sequence/align/matrix_data/BLOSUM60.mat +31 -0
  83. biotite/sequence/align/matrix_data/BLOSUM62.mat +31 -0
  84. biotite/sequence/align/matrix_data/BLOSUM62_13p.mat +25 -0
  85. biotite/sequence/align/matrix_data/BLOSUM62_14.3.mat +25 -0
  86. biotite/sequence/align/matrix_data/BLOSUM62_5.0.mat +25 -0
  87. biotite/sequence/align/matrix_data/BLOSUM65.mat +31 -0
  88. biotite/sequence/align/matrix_data/BLOSUM70.mat +31 -0
  89. biotite/sequence/align/matrix_data/BLOSUM75.mat +31 -0
  90. biotite/sequence/align/matrix_data/BLOSUM80.mat +31 -0
  91. biotite/sequence/align/matrix_data/BLOSUM85.mat +31 -0
  92. biotite/sequence/align/matrix_data/BLOSUM90.mat +31 -0
  93. biotite/sequence/align/matrix_data/BLOSUMN.mat +31 -0
  94. biotite/sequence/align/matrix_data/CorBLOSUM49_5.0.mat +25 -0
  95. biotite/sequence/align/matrix_data/CorBLOSUM57_13p.mat +25 -0
  96. biotite/sequence/align/matrix_data/CorBLOSUM57_14.3.mat +25 -0
  97. biotite/sequence/align/matrix_data/CorBLOSUM61_5.0.mat +25 -0
  98. biotite/sequence/align/matrix_data/CorBLOSUM66_13p.mat +25 -0
  99. biotite/sequence/align/matrix_data/CorBLOSUM67_14.3.mat +25 -0
  100. biotite/sequence/align/matrix_data/DAYHOFF.mat +32 -0
  101. biotite/sequence/align/matrix_data/GONNET.mat +26 -0
  102. biotite/sequence/align/matrix_data/IDENTITY.mat +25 -0
  103. biotite/sequence/align/matrix_data/MATCH.mat +25 -0
  104. biotite/sequence/align/matrix_data/NUC.mat +25 -0
  105. biotite/sequence/align/matrix_data/PAM10.mat +34 -0
  106. biotite/sequence/align/matrix_data/PAM100.mat +34 -0
  107. biotite/sequence/align/matrix_data/PAM110.mat +34 -0
  108. biotite/sequence/align/matrix_data/PAM120.mat +34 -0
  109. biotite/sequence/align/matrix_data/PAM130.mat +34 -0
  110. biotite/sequence/align/matrix_data/PAM140.mat +34 -0
  111. biotite/sequence/align/matrix_data/PAM150.mat +34 -0
  112. biotite/sequence/align/matrix_data/PAM160.mat +34 -0
  113. biotite/sequence/align/matrix_data/PAM170.mat +34 -0
  114. biotite/sequence/align/matrix_data/PAM180.mat +34 -0
  115. biotite/sequence/align/matrix_data/PAM190.mat +34 -0
  116. biotite/sequence/align/matrix_data/PAM20.mat +34 -0
  117. biotite/sequence/align/matrix_data/PAM200.mat +34 -0
  118. biotite/sequence/align/matrix_data/PAM210.mat +34 -0
  119. biotite/sequence/align/matrix_data/PAM220.mat +34 -0
  120. biotite/sequence/align/matrix_data/PAM230.mat +34 -0
  121. biotite/sequence/align/matrix_data/PAM240.mat +34 -0
  122. biotite/sequence/align/matrix_data/PAM250.mat +34 -0
  123. biotite/sequence/align/matrix_data/PAM260.mat +34 -0
  124. biotite/sequence/align/matrix_data/PAM270.mat +34 -0
  125. biotite/sequence/align/matrix_data/PAM280.mat +34 -0
  126. biotite/sequence/align/matrix_data/PAM290.mat +34 -0
  127. biotite/sequence/align/matrix_data/PAM30.mat +34 -0
  128. biotite/sequence/align/matrix_data/PAM300.mat +34 -0
  129. biotite/sequence/align/matrix_data/PAM310.mat +34 -0
  130. biotite/sequence/align/matrix_data/PAM320.mat +34 -0
  131. biotite/sequence/align/matrix_data/PAM330.mat +34 -0
  132. biotite/sequence/align/matrix_data/PAM340.mat +34 -0
  133. biotite/sequence/align/matrix_data/PAM350.mat +34 -0
  134. biotite/sequence/align/matrix_data/PAM360.mat +34 -0
  135. biotite/sequence/align/matrix_data/PAM370.mat +34 -0
  136. biotite/sequence/align/matrix_data/PAM380.mat +34 -0
  137. biotite/sequence/align/matrix_data/PAM390.mat +34 -0
  138. biotite/sequence/align/matrix_data/PAM40.mat +34 -0
  139. biotite/sequence/align/matrix_data/PAM400.mat +34 -0
  140. biotite/sequence/align/matrix_data/PAM410.mat +34 -0
  141. biotite/sequence/align/matrix_data/PAM420.mat +34 -0
  142. biotite/sequence/align/matrix_data/PAM430.mat +34 -0
  143. biotite/sequence/align/matrix_data/PAM440.mat +34 -0
  144. biotite/sequence/align/matrix_data/PAM450.mat +34 -0
  145. biotite/sequence/align/matrix_data/PAM460.mat +34 -0
  146. biotite/sequence/align/matrix_data/PAM470.mat +34 -0
  147. biotite/sequence/align/matrix_data/PAM480.mat +34 -0
  148. biotite/sequence/align/matrix_data/PAM490.mat +34 -0
  149. biotite/sequence/align/matrix_data/PAM50.mat +34 -0
  150. biotite/sequence/align/matrix_data/PAM500.mat +34 -0
  151. biotite/sequence/align/matrix_data/PAM60.mat +34 -0
  152. biotite/sequence/align/matrix_data/PAM70.mat +34 -0
  153. biotite/sequence/align/matrix_data/PAM80.mat +34 -0
  154. biotite/sequence/align/matrix_data/PAM90.mat +34 -0
  155. biotite/sequence/align/matrix_data/PB.license +21 -0
  156. biotite/sequence/align/matrix_data/PB.mat +18 -0
  157. biotite/sequence/align/matrix_data/RBLOSUM52_5.0.mat +25 -0
  158. biotite/sequence/align/matrix_data/RBLOSUM59_13p.mat +25 -0
  159. biotite/sequence/align/matrix_data/RBLOSUM59_14.3.mat +25 -0
  160. biotite/sequence/align/matrix_data/RBLOSUM64_5.0.mat +25 -0
  161. biotite/sequence/align/matrix_data/RBLOSUM69_13p.mat +25 -0
  162. biotite/sequence/align/matrix_data/RBLOSUM69_14.3.mat +25 -0
  163. biotite/sequence/align/multiple.cpython-313-darwin.so +0 -0
  164. biotite/sequence/align/multiple.pyx +620 -0
  165. biotite/sequence/align/pairwise.cpython-313-darwin.so +0 -0
  166. biotite/sequence/align/pairwise.pyx +587 -0
  167. biotite/sequence/align/permutation.cpython-313-darwin.so +0 -0
  168. biotite/sequence/align/permutation.pyx +313 -0
  169. biotite/sequence/align/primes.txt +821 -0
  170. biotite/sequence/align/selector.cpython-313-darwin.so +0 -0
  171. biotite/sequence/align/selector.pyx +954 -0
  172. biotite/sequence/align/statistics.py +264 -0
  173. biotite/sequence/align/tracetable.cpython-313-darwin.so +0 -0
  174. biotite/sequence/align/tracetable.pxd +64 -0
  175. biotite/sequence/align/tracetable.pyx +370 -0
  176. biotite/sequence/alphabet.py +555 -0
  177. biotite/sequence/annotation.py +830 -0
  178. biotite/sequence/codec.cpython-313-darwin.so +0 -0
  179. biotite/sequence/codec.pyx +155 -0
  180. biotite/sequence/codon.py +477 -0
  181. biotite/sequence/codon_tables.txt +202 -0
  182. biotite/sequence/graphics/__init__.py +33 -0
  183. biotite/sequence/graphics/alignment.py +1115 -0
  184. biotite/sequence/graphics/color_schemes/3di_flower.json +48 -0
  185. biotite/sequence/graphics/color_schemes/autumn.json +51 -0
  186. biotite/sequence/graphics/color_schemes/blossom.json +51 -0
  187. biotite/sequence/graphics/color_schemes/clustalx_dna.json +11 -0
  188. biotite/sequence/graphics/color_schemes/clustalx_protein.json +28 -0
  189. biotite/sequence/graphics/color_schemes/flower.json +51 -0
  190. biotite/sequence/graphics/color_schemes/jalview_buried.json +31 -0
  191. biotite/sequence/graphics/color_schemes/jalview_hydrophobicity.json +31 -0
  192. biotite/sequence/graphics/color_schemes/jalview_prop_helix.json +31 -0
  193. biotite/sequence/graphics/color_schemes/jalview_prop_strand.json +31 -0
  194. biotite/sequence/graphics/color_schemes/jalview_prop_turn.json +31 -0
  195. biotite/sequence/graphics/color_schemes/jalview_taylor.json +28 -0
  196. biotite/sequence/graphics/color_schemes/jalview_zappo.json +28 -0
  197. biotite/sequence/graphics/color_schemes/ocean.json +51 -0
  198. biotite/sequence/graphics/color_schemes/pb_flower.json +40 -0
  199. biotite/sequence/graphics/color_schemes/rainbow_dna.json +11 -0
  200. biotite/sequence/graphics/color_schemes/rainbow_protein.json +30 -0
  201. biotite/sequence/graphics/color_schemes/spring.json +51 -0
  202. biotite/sequence/graphics/color_schemes/sunset.json +51 -0
  203. biotite/sequence/graphics/color_schemes/wither.json +51 -0
  204. biotite/sequence/graphics/colorschemes.py +170 -0
  205. biotite/sequence/graphics/dendrogram.py +229 -0
  206. biotite/sequence/graphics/features.py +544 -0
  207. biotite/sequence/graphics/logo.py +104 -0
  208. biotite/sequence/graphics/plasmid.py +712 -0
  209. biotite/sequence/io/__init__.py +12 -0
  210. biotite/sequence/io/fasta/__init__.py +22 -0
  211. biotite/sequence/io/fasta/convert.py +284 -0
  212. biotite/sequence/io/fasta/file.py +265 -0
  213. biotite/sequence/io/fastq/__init__.py +19 -0
  214. biotite/sequence/io/fastq/convert.py +117 -0
  215. biotite/sequence/io/fastq/file.py +507 -0
  216. biotite/sequence/io/genbank/__init__.py +17 -0
  217. biotite/sequence/io/genbank/annotation.py +269 -0
  218. biotite/sequence/io/genbank/file.py +573 -0
  219. biotite/sequence/io/genbank/metadata.py +336 -0
  220. biotite/sequence/io/genbank/sequence.py +171 -0
  221. biotite/sequence/io/general.py +201 -0
  222. biotite/sequence/io/gff/__init__.py +26 -0
  223. biotite/sequence/io/gff/convert.py +128 -0
  224. biotite/sequence/io/gff/file.py +450 -0
  225. biotite/sequence/phylo/__init__.py +36 -0
  226. biotite/sequence/phylo/nj.cpython-313-darwin.so +0 -0
  227. biotite/sequence/phylo/nj.pyx +221 -0
  228. biotite/sequence/phylo/tree.cpython-313-darwin.so +0 -0
  229. biotite/sequence/phylo/tree.pyx +1169 -0
  230. biotite/sequence/phylo/upgma.cpython-313-darwin.so +0 -0
  231. biotite/sequence/phylo/upgma.pyx +164 -0
  232. biotite/sequence/profile.py +567 -0
  233. biotite/sequence/search.py +118 -0
  234. biotite/sequence/seqtypes.py +713 -0
  235. biotite/sequence/sequence.py +374 -0
  236. biotite/setup_ccd.py +197 -0
  237. biotite/structure/__init__.py +133 -0
  238. biotite/structure/alphabet/__init__.py +25 -0
  239. biotite/structure/alphabet/encoder.py +332 -0
  240. biotite/structure/alphabet/encoder_weights_3di.kerasify +0 -0
  241. biotite/structure/alphabet/i3d.py +110 -0
  242. biotite/structure/alphabet/layers.py +86 -0
  243. biotite/structure/alphabet/pb.license +21 -0
  244. biotite/structure/alphabet/pb.py +171 -0
  245. biotite/structure/alphabet/unkerasify.py +122 -0
  246. biotite/structure/atoms.py +1554 -0
  247. biotite/structure/basepairs.py +1404 -0
  248. biotite/structure/bonds.cpython-313-darwin.so +0 -0
  249. biotite/structure/bonds.pyx +1972 -0
  250. biotite/structure/box.py +588 -0
  251. biotite/structure/celllist.cpython-313-darwin.so +0 -0
  252. biotite/structure/celllist.pyx +849 -0
  253. biotite/structure/chains.py +314 -0
  254. biotite/structure/charges.cpython-313-darwin.so +0 -0
  255. biotite/structure/charges.pyx +520 -0
  256. biotite/structure/compare.py +274 -0
  257. biotite/structure/density.py +109 -0
  258. biotite/structure/dotbracket.py +214 -0
  259. biotite/structure/error.py +39 -0
  260. biotite/structure/filter.py +590 -0
  261. biotite/structure/geometry.py +655 -0
  262. biotite/structure/graphics/__init__.py +13 -0
  263. biotite/structure/graphics/atoms.py +243 -0
  264. biotite/structure/graphics/rna.py +295 -0
  265. biotite/structure/hbond.py +428 -0
  266. biotite/structure/info/__init__.py +24 -0
  267. biotite/structure/info/atom_masses.json +121 -0
  268. biotite/structure/info/atoms.py +81 -0
  269. biotite/structure/info/bonds.py +149 -0
  270. biotite/structure/info/ccd.py +202 -0
  271. biotite/structure/info/components.bcif +0 -0
  272. biotite/structure/info/groups.py +131 -0
  273. biotite/structure/info/masses.py +121 -0
  274. biotite/structure/info/misc.py +138 -0
  275. biotite/structure/info/radii.py +197 -0
  276. biotite/structure/info/standardize.py +186 -0
  277. biotite/structure/integrity.py +215 -0
  278. biotite/structure/io/__init__.py +29 -0
  279. biotite/structure/io/dcd/__init__.py +13 -0
  280. biotite/structure/io/dcd/file.py +67 -0
  281. biotite/structure/io/general.py +243 -0
  282. biotite/structure/io/gro/__init__.py +14 -0
  283. biotite/structure/io/gro/file.py +344 -0
  284. biotite/structure/io/mol/__init__.py +20 -0
  285. biotite/structure/io/mol/convert.py +112 -0
  286. biotite/structure/io/mol/ctab.py +415 -0
  287. biotite/structure/io/mol/header.py +120 -0
  288. biotite/structure/io/mol/mol.py +149 -0
  289. biotite/structure/io/mol/sdf.py +914 -0
  290. biotite/structure/io/netcdf/__init__.py +13 -0
  291. biotite/structure/io/netcdf/file.py +64 -0
  292. biotite/structure/io/pdb/__init__.py +20 -0
  293. biotite/structure/io/pdb/convert.py +307 -0
  294. biotite/structure/io/pdb/file.py +1290 -0
  295. biotite/structure/io/pdb/hybrid36.cpython-313-darwin.so +0 -0
  296. biotite/structure/io/pdb/hybrid36.pyx +242 -0
  297. biotite/structure/io/pdbqt/__init__.py +15 -0
  298. biotite/structure/io/pdbqt/convert.py +113 -0
  299. biotite/structure/io/pdbqt/file.py +688 -0
  300. biotite/structure/io/pdbx/__init__.py +23 -0
  301. biotite/structure/io/pdbx/bcif.py +656 -0
  302. biotite/structure/io/pdbx/cif.py +1075 -0
  303. biotite/structure/io/pdbx/component.py +245 -0
  304. biotite/structure/io/pdbx/compress.py +321 -0
  305. biotite/structure/io/pdbx/convert.py +1745 -0
  306. biotite/structure/io/pdbx/encoding.cpython-313-darwin.so +0 -0
  307. biotite/structure/io/pdbx/encoding.pyx +1031 -0
  308. biotite/structure/io/trajfile.py +693 -0
  309. biotite/structure/io/trr/__init__.py +13 -0
  310. biotite/structure/io/trr/file.py +43 -0
  311. biotite/structure/io/xtc/__init__.py +13 -0
  312. biotite/structure/io/xtc/file.py +43 -0
  313. biotite/structure/mechanics.py +73 -0
  314. biotite/structure/molecules.py +352 -0
  315. biotite/structure/pseudoknots.py +628 -0
  316. biotite/structure/rdf.py +245 -0
  317. biotite/structure/repair.py +304 -0
  318. biotite/structure/residues.py +572 -0
  319. biotite/structure/sasa.cpython-313-darwin.so +0 -0
  320. biotite/structure/sasa.pyx +322 -0
  321. biotite/structure/segments.py +178 -0
  322. biotite/structure/sequence.py +111 -0
  323. biotite/structure/sse.py +308 -0
  324. biotite/structure/superimpose.py +689 -0
  325. biotite/structure/transform.py +530 -0
  326. biotite/structure/util.py +168 -0
  327. biotite/version.py +16 -0
  328. biotite/visualize.py +265 -0
  329. biotite-1.1.0.dist-info/METADATA +190 -0
  330. biotite-1.1.0.dist-info/RECORD +332 -0
  331. biotite-1.1.0.dist-info/WHEEL +4 -0
  332. biotite-1.1.0.dist-info/licenses/LICENSE.rst +30 -0
@@ -0,0 +1,1554 @@
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 contains the main types of the ``structure`` subpackage:
7
+ :class:`Atom`, :class:`AtomArray` and :class:`AtomArrayStack`.
8
+ """
9
+
10
+ __name__ = "biotite.structure"
11
+ __author__ = "Patrick Kunzmann"
12
+ __all__ = [
13
+ "Atom",
14
+ "AtomArray",
15
+ "AtomArrayStack",
16
+ "concatenate",
17
+ "array",
18
+ "stack",
19
+ "repeat",
20
+ "from_template",
21
+ "coord",
22
+ ]
23
+
24
+ import abc
25
+ import numbers
26
+ from collections.abc import Sequence
27
+ import numpy as np
28
+ from biotite.copyable import Copyable
29
+ from biotite.structure.bonds import BondList
30
+
31
+
32
+ class _AtomArrayBase(Copyable, metaclass=abc.ABCMeta):
33
+ """
34
+ Private base class for :class:`AtomArray` and
35
+ :class:`AtomArrayStack`.
36
+ It implements functionality for annotation arrays and also
37
+ rudimentarily for coordinates.
38
+ """
39
+
40
+ def __init__(self, length):
41
+ """
42
+ Create the annotation arrays
43
+ """
44
+ self._annot = {}
45
+ self._array_length = length
46
+ self._coord = None
47
+ self._bonds = None
48
+ self._box = None
49
+ self.add_annotation("chain_id", dtype="U4")
50
+ self.add_annotation("res_id", dtype=int)
51
+ self.add_annotation("ins_code", dtype="U1")
52
+ self.add_annotation("res_name", dtype="U5")
53
+ self.add_annotation("hetero", dtype=bool)
54
+ self.add_annotation("atom_name", dtype="U6")
55
+ self.add_annotation("element", dtype="U2")
56
+
57
+ def array_length(self):
58
+ """
59
+ Get the length of the atom array.
60
+
61
+ This value is equivalent to the length of each annotation array.
62
+ For :class:`AtomArray` it is the same as ``len(array)``.
63
+
64
+ Returns
65
+ -------
66
+ length : int
67
+ Length of the array(s).
68
+ """
69
+ return self._array_length
70
+
71
+ @property
72
+ @abc.abstractmethod
73
+ def shape(self):
74
+ """
75
+ Tuple of array dimensions.
76
+
77
+ This property contains the current shape of the object.
78
+
79
+ Returns
80
+ -------
81
+ shape : tuple of int
82
+ Shape of the object.
83
+ """
84
+ return
85
+
86
+ def add_annotation(self, category, dtype):
87
+ """
88
+ Add an annotation category, if not already existing.
89
+
90
+ Initially the new annotation is filled with the *zero*
91
+ representation of the given type.
92
+
93
+ Parameters
94
+ ----------
95
+ category : str
96
+ The annotation category to be added.
97
+ dtype : type or str
98
+ A type instance or a valid *NumPy* *dtype* string.
99
+ Defines the type of the annotation
100
+
101
+ See Also
102
+ --------
103
+ set_annotation
104
+
105
+ Notes
106
+ -----
107
+ If the annotation category already exists, a compatible dtype is chosen,
108
+ that is also able to represent the old values.
109
+ """
110
+ if category not in self._annot:
111
+ self._annot[str(category)] = np.zeros(self._array_length, dtype=dtype)
112
+ elif np.can_cast(self._annot[str(category)].dtype, dtype):
113
+ self._annot[str(category)] = self._annot[str(category)].astype(dtype)
114
+ elif np.can_cast(dtype, self._annot[str(category)].dtype):
115
+ # The existing dtype is more general
116
+ pass
117
+ else:
118
+ raise ValueError(
119
+ f"Cannot cast '{str(category)}' "
120
+ f"with dtype '{self._annot[str(category)].dtype}' into '{dtype}'"
121
+ )
122
+
123
+ def del_annotation(self, category):
124
+ """
125
+ Removes an annotation category.
126
+
127
+ Parameters
128
+ ----------
129
+ category : str
130
+ The annotation category to be removed.
131
+ """
132
+ if category in self._annot:
133
+ del self._annot[str(category)]
134
+
135
+ def get_annotation(self, category):
136
+ """
137
+ Return an annotation array.
138
+
139
+ Parameters
140
+ ----------
141
+ category : str
142
+ The annotation category to be returned.
143
+
144
+ Returns
145
+ -------
146
+ array : ndarray
147
+ The annotation array.
148
+ """
149
+ if category not in self._annot:
150
+ raise ValueError(f"Annotation category '{category}' is not existing")
151
+ return self._annot[category]
152
+
153
+ def set_annotation(self, category, array):
154
+ """
155
+ Set an annotation array. If the annotation category does not
156
+ exist yet, the category is created.
157
+
158
+ Parameters
159
+ ----------
160
+ category : str
161
+ The annotation category to be set.
162
+ array : ndarray
163
+ The new value of the annotation category. The size of the
164
+ array must be the same as the array length.
165
+
166
+ Notes
167
+ -----
168
+ If the annotation category already exists, a compatible dtype is chosen,
169
+ that is able to represent the old and new array values.
170
+ """
171
+ array = np.asarray(array)
172
+ if len(array) != self._array_length:
173
+ raise IndexError(
174
+ f"Expected array length {self._array_length}, " f"but got {len(array)}"
175
+ )
176
+ if category in self._annot:
177
+ # If the annotation already exists, find the compatible dtype
178
+ self._annot[category] = array.astype(
179
+ dtype=np.promote_types(self._annot[category].dtype, array.dtype),
180
+ copy=False,
181
+ )
182
+ else:
183
+ self._annot[category] = array
184
+
185
+ def get_annotation_categories(self):
186
+ """
187
+ Return a list containing all annotation array categories.
188
+
189
+ Returns
190
+ -------
191
+ categories : list
192
+ The list containing the names of each annotation array.
193
+ """
194
+ return list(self._annot.keys())
195
+
196
+ def _subarray(self, index):
197
+ # Index is one dimensional (boolean mask, index array)
198
+ new_coord = self._coord[..., index, :]
199
+ new_length = new_coord.shape[-2]
200
+ if isinstance(self, AtomArray):
201
+ new_object = AtomArray(new_length)
202
+ elif isinstance(self, AtomArrayStack):
203
+ new_depth = new_coord.shape[-3]
204
+ new_object = AtomArrayStack(new_depth, new_length)
205
+ new_object._coord = new_coord
206
+ if self._bonds is not None:
207
+ new_object._bonds = self._bonds[index]
208
+ if self._box is not None:
209
+ new_object._box = self._box
210
+ for annotation in self._annot:
211
+ new_object._annot[annotation] = self._annot[annotation].__getitem__(index)
212
+ return new_object
213
+
214
+ def _set_element(self, index, atom):
215
+ try:
216
+ if isinstance(index, (numbers.Integral, np.ndarray)):
217
+ for name in self._annot:
218
+ self._annot[name][index] = atom._annot[name]
219
+ self._coord[..., index, :] = atom.coord
220
+ else:
221
+ raise TypeError(f"Index must be integer, not '{type(index).__name__}'")
222
+ except KeyError:
223
+ raise KeyError("The annotations of the 'Atom' are incompatible")
224
+
225
+ def _del_element(self, index):
226
+ if isinstance(index, numbers.Integral):
227
+ for name in self._annot:
228
+ self._annot[name] = np.delete(self._annot[name], index, axis=0)
229
+ self._coord = np.delete(self._coord, index, axis=-2)
230
+ self._array_length = self._coord.shape[-2]
231
+ if self._bonds is not None:
232
+ mask = np.ones(self._bonds.get_atom_count(), dtype=bool)
233
+ mask[index] = False
234
+ self._bonds = self._bonds[mask]
235
+ else:
236
+ raise TypeError(f"Index must be integer, not '{type(index).__name__}'")
237
+
238
+ def equal_annotations(self, item, equal_nan=True):
239
+ """
240
+ Check, if this object shares equal annotation arrays with the
241
+ given :class:`AtomArray` or :class:`AtomArrayStack`.
242
+
243
+ Parameters
244
+ ----------
245
+ item : AtomArray or AtomArrayStack
246
+ The object to compare the annotation arrays with.
247
+ equal_nan: bool
248
+ Whether to count `nan` values as equal. Default: True.
249
+
250
+ Returns
251
+ -------
252
+ equality : bool
253
+ True, if the annotation arrays are equal.
254
+ """
255
+ if not isinstance(item, _AtomArrayBase):
256
+ return False
257
+ if not self.equal_annotation_categories(item):
258
+ return False
259
+ for name in self._annot:
260
+ # ... allowing `nan` values causes type-casting, which is
261
+ # only possible for floating-point arrays
262
+ allow_nan = (
263
+ equal_nan
264
+ if np.issubdtype(self._annot[name].dtype, np.floating)
265
+ else False
266
+ )
267
+ if not np.array_equal(
268
+ self._annot[name],
269
+ item._annot[name],
270
+ equal_nan=allow_nan,
271
+ ):
272
+ return False
273
+ return True
274
+
275
+ def equal_annotation_categories(self, item):
276
+ """
277
+ Check, if this object shares equal annotation array categories
278
+ with the given :class:`AtomArray` or :class:`AtomArrayStack`.
279
+
280
+ Parameters
281
+ ----------
282
+ item : AtomArray or AtomArrayStack
283
+ The object to compare the annotation arrays with.
284
+
285
+ Returns
286
+ -------
287
+ equality : bool
288
+ True, if the annotation array names are equal.
289
+ """
290
+ return sorted(self._annot.keys()) == sorted(item._annot.keys())
291
+
292
+ def __getattr__(self, attr):
293
+ """
294
+ If the attribute is an annotation, the annotation is returned
295
+ from the dictionary.
296
+ Exposes coordinates.
297
+ """
298
+ if attr == "coord":
299
+ return self._coord
300
+ if attr == "bonds":
301
+ return self._bonds
302
+ if attr == "box":
303
+ return self._box
304
+ # Call method of 'object' superclass to avoid infinite recursive
305
+ # calls of '__getattr__()'
306
+ elif attr in super().__getattribute__("_annot"):
307
+ return self._annot[attr]
308
+ else:
309
+ raise AttributeError(
310
+ f"'{type(self).__name__}' object has no attribute '{attr}'"
311
+ )
312
+
313
+ def __setattr__(self, attr, value):
314
+ """
315
+ If the attribute is an annotation, the :attr:`value` is saved
316
+ to the annotation in the dictionary.
317
+ Exposes coordinates.
318
+ :attr:`value` must have same length as :func:`array_length()`.
319
+ """
320
+ if attr == "coord":
321
+ if not isinstance(value, np.ndarray):
322
+ raise TypeError("Value must be ndarray of floats")
323
+ if isinstance(self, AtomArray):
324
+ if value.ndim != 2:
325
+ raise ValueError(
326
+ "A 2-dimensional ndarray is expected " "for an AtomArray"
327
+ )
328
+ elif isinstance(self, AtomArrayStack):
329
+ if value.ndim != 3:
330
+ raise ValueError(
331
+ "A 3-dimensional ndarray is expected " "for an AtomArrayStack"
332
+ )
333
+ if value.shape[-2] != self._array_length:
334
+ raise ValueError(
335
+ f"Expected array length {self._array_length}, "
336
+ f"but got {len(value)}"
337
+ )
338
+ if value.shape[-1] != 3:
339
+ raise TypeError("Expected 3 coordinates for each atom")
340
+ super().__setattr__("_coord", value.astype(np.float32, copy=False))
341
+
342
+ elif attr == "bonds":
343
+ if isinstance(value, BondList):
344
+ if value.get_atom_count() != self._array_length:
345
+ raise ValueError(
346
+ f"Array length is {self._array_length}, "
347
+ f"but bond list has {value.get_atom_count()} atoms"
348
+ )
349
+ super().__setattr__("_bonds", value)
350
+ elif value is None:
351
+ # Remove bond list
352
+ super().__setattr__("_bonds", None)
353
+ else:
354
+ raise TypeError("Value must be 'BondList'")
355
+
356
+ elif attr == "box":
357
+ if isinstance(value, np.ndarray):
358
+ if isinstance(self, AtomArray):
359
+ if value.ndim != 2:
360
+ raise ValueError(
361
+ "A 2-dimensional ndarray is expected " "for an AtomArray"
362
+ )
363
+ else: # AtomArrayStack
364
+ if value.ndim != 3:
365
+ raise ValueError(
366
+ "A 3-dimensional ndarray is expected "
367
+ "for an AtomArrayStack"
368
+ )
369
+ if value.shape[-2:] != (3, 3):
370
+ raise TypeError("Box must be a 3x3 matrix (three vectors)")
371
+ box = value.astype(np.float32, copy=False)
372
+ super().__setattr__("_box", box)
373
+ elif value is None:
374
+ # Remove box
375
+ super().__setattr__("_box", None)
376
+ else:
377
+ raise TypeError("Box must be ndarray of floats or None")
378
+
379
+ elif attr == "_annot":
380
+ super().__setattr__(attr, value)
381
+ elif attr in self._annot:
382
+ self.set_annotation(attr, value)
383
+ else:
384
+ super().__setattr__(attr, value)
385
+
386
+ def __dir__(self):
387
+ attr = super().__dir__()
388
+ attr.append("coord")
389
+ attr.append("bonds")
390
+ attr.append("box")
391
+ for name in self._annot.keys():
392
+ attr.append(name)
393
+ return attr
394
+
395
+ def __eq__(self, item):
396
+ """
397
+ See Also
398
+ --------
399
+ equal_annotations
400
+ """
401
+ if not self.equal_annotations(item):
402
+ return False
403
+ if self._bonds != item._bonds:
404
+ return False
405
+ if self._box is None:
406
+ if item._box is not None:
407
+ return False
408
+ else:
409
+ if not np.array_equal(self._box, item._box):
410
+ return False
411
+ return np.array_equal(self._coord, item._coord)
412
+
413
+ def __len__(self):
414
+ """
415
+ The length of the annotation arrays.
416
+
417
+ Returns
418
+ -------
419
+ length : int
420
+ Length of the annotation arrays.
421
+ """
422
+ return self._array_length
423
+
424
+ def __add__(self, array):
425
+ return concatenate([self, array])
426
+
427
+ def __copy_fill__(self, clone):
428
+ super().__copy_fill__(clone)
429
+ self._copy_annotations(clone)
430
+ clone._coord = np.copy(self._coord)
431
+
432
+ def _copy_annotations(self, clone):
433
+ for name in self._annot:
434
+ clone._annot[name] = np.copy(self._annot[name])
435
+ if self._box is not None:
436
+ clone._box = np.copy(self._box)
437
+ if self._bonds is not None:
438
+ clone._bonds = self._bonds.copy()
439
+
440
+
441
+ class Atom(Copyable):
442
+ """
443
+ A representation of a single atom.
444
+
445
+ The coordinates an annotations can be accessed directly.
446
+ A detailed description of each annotation category can be viewed
447
+ :doc:`here </apidoc/biotite.structure>`.
448
+
449
+ Parameters
450
+ ----------
451
+ coord: list or ndarray
452
+ The x, y and z coordinates.
453
+ kwargs
454
+ Atom annotations as key value pair.
455
+
456
+ Attributes
457
+ ----------
458
+ {annot} : scalar
459
+ Annotations for this atom.
460
+ coord : ndarray, dtype=float
461
+ ndarray containing the x, y and z coordinate of the atom.
462
+ shape : tuple of int
463
+ Shape of the object.
464
+ In case of an :class:`Atom`, the tuple is empty.
465
+
466
+ Examples
467
+ --------
468
+
469
+ >>> atom = Atom([1,2,3], chain_id="A")
470
+ >>> atom.atom_name = "CA"
471
+ >>> print(atom.atom_name)
472
+ CA
473
+ >>> print(atom.coord)
474
+ [1. 2. 3.]
475
+
476
+ """
477
+
478
+ def __init__(self, coord, **kwargs):
479
+ self._annot = {}
480
+ self._annot["chain_id"] = ""
481
+ self._annot["res_id"] = 0
482
+ self._annot["ins_code"] = ""
483
+ self._annot["res_name"] = ""
484
+ self._annot["hetero"] = False
485
+ self._annot["atom_name"] = ""
486
+ self._annot["element"] = ""
487
+ if "kwargs" in kwargs:
488
+ # kwargs are given directly as dictionary
489
+ kwargs = kwargs["kwargs"]
490
+ for name, annotation in kwargs.items():
491
+ self._annot[name] = annotation
492
+ coord = np.array(coord, dtype=np.float32)
493
+ # Check if coord contains x,y and z coordinates
494
+ if coord.shape != (3,):
495
+ raise ValueError("Position must be ndarray with shape (3,)")
496
+ self.coord = coord
497
+
498
+ def __repr__(self):
499
+ """Represent Atom as a string for debugging."""
500
+ # print out key-value pairs and format strings in quotation marks
501
+ annot_parts = [
502
+ f'{key}="{value}"' if isinstance(value, str) else f"{key}={value}"
503
+ for key, value in self._annot.items()
504
+ ]
505
+
506
+ annot = ", ".join(annot_parts)
507
+ return f"Atom(np.{np.array_repr(self.coord)}, {annot})"
508
+
509
+ @property
510
+ def shape(self):
511
+ return ()
512
+
513
+ def __getattr__(self, attr):
514
+ if attr in super().__getattribute__("_annot"):
515
+ return self._annot[attr]
516
+ else:
517
+ raise AttributeError(
518
+ f"'{type(self).__name__}' object has no attribute '{attr}'"
519
+ )
520
+
521
+ def __setattr__(self, attr, value):
522
+ if attr == "_annot":
523
+ super().__setattr__(attr, value)
524
+ elif attr == "coord":
525
+ super().__setattr__(attr, value)
526
+ else:
527
+ self._annot[attr] = value
528
+
529
+ def __str__(self):
530
+ hetero = "HET" if self.hetero else ""
531
+ return (
532
+ f"{hetero:3} {self.chain_id:3} "
533
+ f"{self.res_id:5d}{self.ins_code:1} {self.res_name:3} "
534
+ f"{self.atom_name:6} {self.element:2} "
535
+ f"{self.coord[0]:8.3f} "
536
+ f"{self.coord[1]:8.3f} "
537
+ f"{self.coord[2]:8.3f}"
538
+ )
539
+
540
+ def __eq__(self, item):
541
+ if not isinstance(item, Atom):
542
+ return False
543
+ if not np.array_equal(self.coord, item.coord):
544
+ return False
545
+ if self._annot.keys() != item._annot.keys():
546
+ return False
547
+ for name in self._annot:
548
+ if self._annot[name] != item._annot[name]:
549
+ return False
550
+ return True
551
+
552
+ def __ne__(self, item):
553
+ return not self == item
554
+
555
+ def __copy_create__(self):
556
+ return Atom(self.coord, **self._annot)
557
+
558
+
559
+ class AtomArray(_AtomArrayBase):
560
+ """
561
+ An array representation of a model consisting of multiple atoms.
562
+
563
+ An :class:`AtomArray` can be seen as a list of :class:`Atom`
564
+ instances.
565
+ Instead of using directly a list, this class uses an *NumPy*
566
+ :class:`ndarray` for each annotation category and the coordinates.
567
+ These
568
+ coordinates can be accessed directly via the :attr:`coord`
569
+ attribute.
570
+ The annotations are accessed either via the category as attribute
571
+ name or the :func:`get_annotation()`, :func:`set_annotation()`
572
+ method.
573
+ Usage of custom annotations is achieved via :func:`add_annotation()`
574
+ or :func:`set_annotation()`.
575
+ A detailed description of each annotation category can be viewed
576
+ :doc:`here </apidoc/biotite.structure>`.
577
+
578
+ In order to get an an subarray of an :class:`AtomArray`,
579
+ *NumPy* style indexing is used.
580
+ This includes slices, boolean arrays, index arrays and even
581
+ *Ellipsis* notation.
582
+ Using a single integer as index returns a single :class:`Atom`
583
+ instance.
584
+
585
+ Inserting or appending an :class:`AtomArray` to another
586
+ :class:`AtomArray` is done with the '+' operator.
587
+ Only the annotation categories, which are existing in both arrays,
588
+ are transferred to the new array.
589
+ For a list of :class:`AtomArray` objects, use :func:`concatenate()`.
590
+
591
+ Optionally, an :class:`AtomArray` can store chemical bond
592
+ information via a :class:`BondList` object.
593
+ It can be accessed using the :attr:`bonds` attribute.
594
+ If no bond information is available, :attr:`bonds` is ``None``.
595
+ Consequently the bond information can be removed from the
596
+ :class:`AtomArray`, by setting :attr:`bonds` to ``None``.
597
+ When indexing the :class:`AtomArray` the atom indices in the
598
+ associated :class:`BondList` are updated as well, hence the indices
599
+ in the :class:`BondList` will always point to the same atoms.
600
+ If two :class:`AtomArray` instances are concatenated, the resulting
601
+ :class:`AtomArray` will contain the merged :class:`BondList` if at
602
+ least one of the operands contains bond information.
603
+
604
+ The :attr:`box` attribute contains the box vectors of the unit cell
605
+ or the MD simulation box, respectively.
606
+ Hence, it is a *3 x 3* *ndarray* with the vectors in the last
607
+ dimension.
608
+ If no box is provided, the attribute is ``None``.
609
+ Setting the :attr:`box` attribute to ``None`` means removing the
610
+ box from the atom array.
611
+
612
+ Parameters
613
+ ----------
614
+ length : int
615
+ The fixed amount of atoms in the array.
616
+
617
+ Attributes
618
+ ----------
619
+ {annot} : ndarray
620
+ Multiple n-length annotation arrays.
621
+ coord : ndarray, dtype=float, shape=(n,3)
622
+ ndarray containing the x, y and z coordinate of the
623
+ atoms.
624
+ bonds : BondList or None
625
+ A :class:`BondList`, specifying the indices of atoms
626
+ that form a chemical bond.
627
+ box : ndarray, dtype=float, shape=(3,3) or None
628
+ The surrounding box. May represent a MD simulation box
629
+ or a crystallographic unit cell.
630
+ shape : tuple of int
631
+ Shape of the atom array.
632
+ The single value in the tuple is
633
+ the length of the atom array.
634
+
635
+ Examples
636
+ --------
637
+ Creating an atom array from atoms:
638
+
639
+ >>> atom1 = Atom([1,2,3], chain_id="A")
640
+ >>> atom2 = Atom([2,3,4], chain_id="A")
641
+ >>> atom3 = Atom([3,4,5], chain_id="B")
642
+ >>> atom_array = array([atom1, atom2, atom3])
643
+ >>> print(atom_array.array_length())
644
+ 3
645
+
646
+ Accessing an annotation array:
647
+
648
+ >>> print(atom_array.chain_id)
649
+ ['A' 'A' 'B']
650
+
651
+ Accessing the coordinates:
652
+
653
+ >>> print(atom_array.coord)
654
+ [[1. 2. 3.]
655
+ [2. 3. 4.]
656
+ [3. 4. 5.]]
657
+
658
+ *NumPy* style filtering:
659
+
660
+ >>> atom_array = atom_array[atom_array.chain_id == "A"]
661
+ >>> print(atom_array.array_length())
662
+ 2
663
+
664
+ Inserting an atom array:
665
+
666
+ >>> insert = array([Atom([7,8,9], chain_id="C")])
667
+ >>> atom_array = atom_array[0:1] + insert + atom_array[1:2]
668
+ >>> print(atom_array.chain_id)
669
+ ['A' 'C' 'A']
670
+ """
671
+
672
+ def __init__(self, length):
673
+ super().__init__(length)
674
+ if length is None:
675
+ self._coord = None
676
+ else:
677
+ self._coord = np.full((length, 3), np.nan, dtype=np.float32)
678
+
679
+ def __repr__(self):
680
+ """Represent AtomArray as a string for debugging."""
681
+ atoms = ""
682
+ for i in range(0, self.array_length()):
683
+ if len(atoms) == 0:
684
+ atoms = "\n\t" + self.get_atom(i).__repr__()
685
+ else:
686
+ atoms = atoms + ",\n\t" + self.get_atom(i).__repr__()
687
+ return f"array([{atoms}\n])"
688
+
689
+ @property
690
+ def shape(self):
691
+ """
692
+ Tuple of array dimensions.
693
+
694
+ This property contains the current shape of the
695
+ :class:`AtomArray`.
696
+
697
+ Returns
698
+ -------
699
+ shape : tuple of int
700
+ Shape of the array.
701
+ The single value in the tuple is
702
+ the :func:`array_length()`.
703
+
704
+ See Also
705
+ --------
706
+ array_length
707
+ """
708
+ return (self.array_length(),)
709
+
710
+ def get_atom(self, index):
711
+ """
712
+ Obtain the atom instance of the array at the specified index.
713
+
714
+ The same as ``array[index]``, if `index` is an integer.
715
+
716
+ Parameters
717
+ ----------
718
+ index : int
719
+ Index of the atom.
720
+
721
+ Returns
722
+ -------
723
+ atom : Atom
724
+ Atom at position `index`.
725
+ """
726
+ kwargs = {}
727
+ for name, annotation in self._annot.items():
728
+ kwargs[name] = annotation[index]
729
+ return Atom(coord=self._coord[index], kwargs=kwargs)
730
+
731
+ def __iter__(self):
732
+ """
733
+ Iterate through the array.
734
+
735
+ Yields
736
+ ------
737
+ atom : Atom
738
+ """
739
+ i = 0
740
+ while i < len(self):
741
+ yield self.get_atom(i)
742
+ i += 1
743
+
744
+ def __getitem__(self, index):
745
+ """
746
+ Obtain a subarray or the atom instance at the specified index.
747
+
748
+ Parameters
749
+ ----------
750
+ index : object
751
+ All index types *NumPy* accepts, are valid.
752
+
753
+ Returns
754
+ -------
755
+ sub_array : Atom or AtomArray
756
+ If `index` is an integer an :class:`Atom` instance is
757
+ returned.
758
+ Otherwise an :class:`AtomArray` with reduced length is
759
+ returned.
760
+ """
761
+ if isinstance(index, numbers.Integral):
762
+ return self.get_atom(index)
763
+ elif isinstance(index, tuple):
764
+ if len(index) == 2 and index[0] is Ellipsis:
765
+ # If first index is "...", just ignore the first index
766
+ return self.__getitem__(index[1])
767
+ else:
768
+ raise IndexError("'AtomArray' does not accept multidimensional indices")
769
+ else:
770
+ return self._subarray(index)
771
+
772
+ def __setitem__(self, index, atom):
773
+ """
774
+ Set the atom at the specified array position.
775
+
776
+ Parameters
777
+ ----------
778
+ index : int
779
+ The position, where the atom is set.
780
+ atom : Atom
781
+ The atom to be set.
782
+ """
783
+ self._set_element(index, atom)
784
+
785
+ def __delitem__(self, index):
786
+ """
787
+ Deletes the atom at the specified array position.
788
+
789
+ Parameters
790
+ ----------
791
+ index : int
792
+ The position where the atom should be deleted.
793
+ """
794
+ self._del_element(index)
795
+
796
+ def __len__(self):
797
+ """
798
+ The length of the array.
799
+
800
+ Returns
801
+ -------
802
+ length : int
803
+ Length of the array.
804
+ """
805
+ return self.array_length()
806
+
807
+ def __eq__(self, item):
808
+ """
809
+ Check if the array equals another :class:`AtomArray`.
810
+
811
+ Parameters
812
+ ----------
813
+ item : object
814
+ Object to campare the array with.
815
+
816
+ Returns
817
+ -------
818
+ equal : bool
819
+ True, if `item` is an :class:`AtomArray`
820
+ and all its attribute arrays equals the ones of this object.
821
+ """
822
+ if not super().__eq__(item):
823
+ return False
824
+ if not isinstance(item, AtomArray):
825
+ return False
826
+ return True
827
+
828
+ def __str__(self):
829
+ """
830
+ Get a string representation of the array.
831
+
832
+ Each line contains the attributes of one atom.
833
+ """
834
+ return "\n".join([str(atom) for atom in self])
835
+
836
+ def __copy_create__(self):
837
+ return AtomArray(self.array_length())
838
+
839
+
840
+ class AtomArrayStack(_AtomArrayBase):
841
+ """
842
+ A collection of multiple :class:`AtomArray` instances, where each
843
+ atom array has equal annotation arrays.
844
+
845
+ Effectively, this means that each atom is occuring in every array in
846
+ the stack at differing coordinates. This situation arises e.g. in
847
+ NMR-elucidated or simulated structures. Since the annotations are
848
+ equal for each array, the annotation arrays are 1-D, while the
849
+ coordinate array is 3-D (m x n x 3).
850
+ A detailed description of each annotation category can be viewed
851
+ :doc:`here </apidoc/biotite.structure>`.
852
+
853
+ Indexing works similar to :class:`AtomArray`, with the difference,
854
+ that two index dimensions are possible:
855
+ The first index dimension specifies the array(s), the second index
856
+ dimension specifies the atoms in each array (same as the index
857
+ in :class:`AtomArray`).
858
+ Using a single integer as first dimension index returns a single
859
+ :class:`AtomArray` instance.
860
+
861
+ Concatenation of atoms for each array in the stack is done using the
862
+ '+' operator.
863
+ For a list of :class:`AtomArray` objects, use :func:`concatenate()`.
864
+ For addition of atom arrays onto the stack use the
865
+ :func:`stack()` method.
866
+
867
+ The :attr:`box` attribute has the shape *m x 3 x 3*, as the cell
868
+ might be different for each frame in the atom array stack.
869
+
870
+ Parameters
871
+ ----------
872
+ depth : int
873
+ The fixed amount of arrays in the stack. When indexing, this is
874
+ the length of the first dimension.
875
+
876
+ length : int
877
+ The fixed amount of atoms in each array in the stack. When
878
+ indexing, this is the length of the second dimension.
879
+
880
+ Attributes
881
+ ----------
882
+ {annot} : ndarray, shape=(n,)
883
+ Mutliple n-length annotation arrays.
884
+ coord : ndarray, dtype=float, shape=(m,n,3)
885
+ ndarray containing the x, y and z coordinate of the
886
+ atoms.
887
+ bonds: BondList or None
888
+ A :class:`BondList`, specifying the indices of atoms
889
+ that form a chemical bond.
890
+ box: ndarray, dtype=float, shape=(m,3,3) or None
891
+ The surrounding box. May represent a MD simulation box
892
+ or a crystallographic unit cell.
893
+ shape : tuple of int
894
+ Shape of the stack.
895
+ The numbers correspond to the stack depth
896
+ and array length, respectively.
897
+
898
+ See also
899
+ --------
900
+ AtomArray
901
+
902
+ Examples
903
+ --------
904
+ Creating an atom array stack from two arrays:
905
+
906
+ >>> atom1 = Atom([1,2,3], chain_id="A")
907
+ >>> atom2 = Atom([2,3,4], chain_id="A")
908
+ >>> atom3 = Atom([3,4,5], chain_id="B")
909
+ >>> atom_array1 = array([atom1, atom2, atom3])
910
+ >>> print(atom_array1.coord)
911
+ [[1. 2. 3.]
912
+ [2. 3. 4.]
913
+ [3. 4. 5.]]
914
+ >>> atom_array2 = atom_array1.copy()
915
+ >>> atom_array2.coord += 3
916
+ >>> print(atom_array2.coord)
917
+ [[4. 5. 6.]
918
+ [5. 6. 7.]
919
+ [6. 7. 8.]]
920
+ >>> array_stack = stack([atom_array1, atom_array2])
921
+ >>> print(array_stack.coord)
922
+ [[[1. 2. 3.]
923
+ [2. 3. 4.]
924
+ [3. 4. 5.]]
925
+ <BLANKLINE>
926
+ [[4. 5. 6.]
927
+ [5. 6. 7.]
928
+ [6. 7. 8.]]]
929
+ """
930
+
931
+ def __init__(self, depth, length):
932
+ super().__init__(length)
933
+ if depth is None or length is None:
934
+ self._coord = None
935
+ else:
936
+ self._coord = np.full((depth, length, 3), np.nan, dtype=np.float32)
937
+
938
+ def __repr__(self):
939
+ """Represent AtomArrayStack as a string for debugging."""
940
+ arrays = ""
941
+ for i in range(0, self.stack_depth()):
942
+ if len(arrays) == 0:
943
+ arrays = "\n\t" + self.get_array(i).__repr__()
944
+ else:
945
+ arrays = arrays + ",\n\t" + self.get_array(i).__repr__()
946
+ return f"stack([{arrays}\n])"
947
+
948
+ def get_array(self, index):
949
+ """
950
+ Obtain the atom array instance of the stack at the specified
951
+ index.
952
+
953
+ The same as ``stack[index]``, if `index` is an integer.
954
+
955
+ Parameters
956
+ ----------
957
+ index : int
958
+ Index of the atom array.
959
+
960
+ Returns
961
+ -------
962
+ array : AtomArray
963
+ AtomArray at position `index`.
964
+ """
965
+ array = AtomArray(self.array_length())
966
+ for name in self._annot:
967
+ array._annot[name] = self._annot[name]
968
+ array._coord = self._coord[index]
969
+ if self._bonds is not None:
970
+ array._bonds = self._bonds.copy()
971
+ if self._box is not None:
972
+ array._box = self._box[index]
973
+
974
+ return array
975
+
976
+ def stack_depth(self):
977
+ """
978
+ Get the depth of the stack.
979
+
980
+ This value represents the amount of atom arrays in the stack.
981
+ It is the same as ``len(array)``.
982
+
983
+ Returns
984
+ -------
985
+ length : int
986
+ Length of the array(s).
987
+ """
988
+ return len(self)
989
+
990
+ @property
991
+ def shape(self):
992
+ """
993
+ Tuple of array dimensions.
994
+
995
+ This property contains the current shape of the
996
+ :class:`AtomArrayStack`.
997
+
998
+ Returns
999
+ -------
1000
+ shape : tuple of int
1001
+ Shape of the stack.
1002
+ The numbers correspond to the :func:`stack_depth()`
1003
+ and :func:`array_length()`, respectively.
1004
+ """
1005
+ return self.stack_depth(), self.array_length()
1006
+
1007
+ def __iter__(self):
1008
+ """
1009
+ Iterate through the array.
1010
+
1011
+ Yields
1012
+ ------
1013
+ array : AtomArray
1014
+ """
1015
+ i = 0
1016
+ while i < len(self):
1017
+ yield self.get_array(i)
1018
+ i += 1
1019
+
1020
+ def __getitem__(self, index):
1021
+ """
1022
+ Obtain the atom array instance or an substack at the specified
1023
+ index.
1024
+
1025
+ Parameters
1026
+ ----------
1027
+ index : object
1028
+ All index types *NumPy* accepts are valid.
1029
+
1030
+ Returns
1031
+ -------
1032
+ sub_array : AtomArray or AtomArrayStack
1033
+ If `index` is an integer an :class:`AtomArray` instance is
1034
+ returned.
1035
+ Otherwise an :class:`AtomArrayStack` with reduced depth and
1036
+ length is returned.
1037
+ In case the index is a tuple(int, int) an :class:`Atom`
1038
+ instance is returned.
1039
+ """
1040
+ if isinstance(index, numbers.Integral):
1041
+ return self.get_array(index)
1042
+ elif isinstance(index, tuple):
1043
+ if len(index) != 2:
1044
+ raise IndexError(
1045
+ "'AtomArrayStack' does not accept an index "
1046
+ "with more than two dimensions"
1047
+ )
1048
+ if isinstance(index[0], numbers.Integral):
1049
+ array = self.get_array(index[0])
1050
+ return array.__getitem__(index[1])
1051
+ else:
1052
+ if isinstance(index[1], numbers.Integral):
1053
+ # Prevent reduction in dimensionality
1054
+ # in second dimension
1055
+ new_stack = self._subarray(slice(index[1], index[1] + 1))
1056
+ else:
1057
+ new_stack = self._subarray(index[1])
1058
+ if index[0] is not Ellipsis:
1059
+ new_stack._coord = new_stack._coord[index[0]]
1060
+ if new_stack._box is not None:
1061
+ new_stack._box = new_stack._box[index[0]]
1062
+ return new_stack
1063
+ else:
1064
+ new_stack = AtomArrayStack(depth=0, length=self.array_length())
1065
+ self._copy_annotations(new_stack)
1066
+ new_stack._coord = self._coord[index]
1067
+ if self._box is not None:
1068
+ new_stack._box = self._box[index]
1069
+ return new_stack
1070
+
1071
+ def __setitem__(self, index, array):
1072
+ """
1073
+ Set the atom array at the specified stack position.
1074
+
1075
+ The array and the stack must have equal annotation arrays.
1076
+
1077
+ Parameters
1078
+ ----------
1079
+ index : int
1080
+ The position, where the array atom is set.
1081
+ array : AtomArray
1082
+ The atom array to be set.
1083
+ """
1084
+ if not self.equal_annotations(array):
1085
+ raise ValueError("The stack and the array have unequal annotations")
1086
+ if self.bonds != array.bonds:
1087
+ raise ValueError("The stack and the array have unequal bonds")
1088
+ if isinstance(index, numbers.Integral):
1089
+ self.coord[index] = array.coord
1090
+ if self.box is not None:
1091
+ self.box[index] = array.box
1092
+ else:
1093
+ raise TypeError(f"Index must be integer, not '{type(index).__name__}'")
1094
+
1095
+ def __delitem__(self, index):
1096
+ """
1097
+ Deletes the atom array at the specified stack position.
1098
+
1099
+ Parameters
1100
+ ----------
1101
+ index : int
1102
+ The position where the atom array should be deleted.
1103
+ """
1104
+ if isinstance(index, numbers.Integral):
1105
+ self._coord = np.delete(self._coord, index, axis=0)
1106
+ else:
1107
+ raise TypeError(f"Index must be integer, not '{type(index).__name__}'")
1108
+
1109
+ def __len__(self):
1110
+ """
1111
+ The depth of the stack, i.e. the amount of models.
1112
+
1113
+ Returns
1114
+ -------
1115
+ depth : int
1116
+ depth of the array.
1117
+ """
1118
+ # length is determined by length of coord attribute
1119
+ return self._coord.shape[0]
1120
+
1121
+ def __eq__(self, item):
1122
+ """
1123
+ Check if the array equals another :class:`AtomArray`
1124
+
1125
+ Parameters
1126
+ ----------
1127
+ item : object
1128
+ Object to campare the array with.
1129
+
1130
+ Returns
1131
+ -------
1132
+ equal : bool
1133
+ True, if `item` is an :class:`AtomArray`
1134
+ and all its attribute arrays equals the ones of this object.
1135
+ """
1136
+ if not super().__eq__(item):
1137
+ return False
1138
+ if not isinstance(item, AtomArrayStack):
1139
+ return False
1140
+ return True
1141
+
1142
+ def __str__(self):
1143
+ """
1144
+ Get a string representation of the stack.
1145
+
1146
+ :class:`AtomArray` strings eparated by blank lines
1147
+ and a line indicating the index.
1148
+ """
1149
+ string = ""
1150
+ for i, array in enumerate(self):
1151
+ string += "Model " + str(i + 1) + "\n"
1152
+ string += str(array) + "\n" + "\n"
1153
+ return string
1154
+
1155
+ def __copy_create__(self):
1156
+ return AtomArrayStack(self.stack_depth(), self.array_length())
1157
+
1158
+
1159
+ def array(atoms):
1160
+ """
1161
+ Create an :class:`AtomArray` from a list of :class:`Atom`.
1162
+
1163
+ Parameters
1164
+ ----------
1165
+ atoms : iterable object of Atom
1166
+ The atoms to be combined in an array.
1167
+ All atoms must share the same annotation categories.
1168
+
1169
+ Returns
1170
+ -------
1171
+ array : AtomArray
1172
+ The listed atoms as array.
1173
+
1174
+ Examples
1175
+ --------
1176
+
1177
+ Creating an atom array from atoms:
1178
+
1179
+ >>> atom1 = Atom([1,2,3], chain_id="A")
1180
+ >>> atom2 = Atom([2,3,4], chain_id="A")
1181
+ >>> atom3 = Atom([3,4,5], chain_id="B")
1182
+ >>> atom_array = array([atom1, atom2, atom3])
1183
+ >>> print(atom_array)
1184
+ A 0 1.000 2.000 3.000
1185
+ A 0 2.000 3.000 4.000
1186
+ B 0 3.000 4.000 5.000
1187
+ """
1188
+ # Check if all atoms have the same annotation names
1189
+ # Equality check requires sorting
1190
+ names = sorted(atoms[0]._annot.keys())
1191
+ for i, atom in enumerate(atoms):
1192
+ if sorted(atom._annot.keys()) != names:
1193
+ raise ValueError(
1194
+ f"The atom at index {i} does not share the same "
1195
+ f"annotation categories as the atom at index 0"
1196
+ )
1197
+ array = AtomArray(len(atoms))
1198
+ # Add all (also optional) annotation categories
1199
+ for name in names:
1200
+ array.add_annotation(name, dtype=type(atoms[0]._annot[name]))
1201
+ # Add all atoms to AtomArray
1202
+ for i in range(len(atoms)):
1203
+ for name in names:
1204
+ array._annot[name][i] = atoms[i]._annot[name]
1205
+ array._coord[i] = atoms[i].coord
1206
+ return array
1207
+
1208
+
1209
+ def stack(arrays):
1210
+ """
1211
+ Create an :class:`AtomArrayStack` from a list of :class:`AtomArray`.
1212
+
1213
+ Parameters
1214
+ ----------
1215
+ arrays : iterable object of AtomArray
1216
+ The atom arrays to be combined in a stack.
1217
+ All atom arrays must have an equal number of atoms and equal
1218
+ annotation arrays.
1219
+
1220
+ Returns
1221
+ -------
1222
+ stack : AtomArrayStack
1223
+ The stacked atom arrays.
1224
+
1225
+ Examples
1226
+ --------
1227
+ Creating an atom array stack from two arrays:
1228
+
1229
+ >>> atom1 = Atom([1,2,3], chain_id="A")
1230
+ >>> atom2 = Atom([2,3,4], chain_id="A")
1231
+ >>> atom3 = Atom([3,4,5], chain_id="B")
1232
+ >>> atom_array1 = array([atom1, atom2, atom3])
1233
+ >>> print(atom_array1.coord)
1234
+ [[1. 2. 3.]
1235
+ [2. 3. 4.]
1236
+ [3. 4. 5.]]
1237
+ >>> atom_array2 = atom_array1.copy()
1238
+ >>> atom_array2.coord += 3
1239
+ >>> print(atom_array2.coord)
1240
+ [[4. 5. 6.]
1241
+ [5. 6. 7.]
1242
+ [6. 7. 8.]]
1243
+ >>> array_stack = stack([atom_array1, atom_array2])
1244
+ >>> print(array_stack.coord)
1245
+ [[[1. 2. 3.]
1246
+ [2. 3. 4.]
1247
+ [3. 4. 5.]]
1248
+ <BLANKLINE>
1249
+ [[4. 5. 6.]
1250
+ [5. 6. 7.]
1251
+ [6. 7. 8.]]]
1252
+ """
1253
+ array_count = 0
1254
+ ref_array = None
1255
+ for i, array in enumerate(arrays):
1256
+ if ref_array is None:
1257
+ ref_array = array
1258
+ array_count += 1
1259
+ # Check if all arrays share equal annotations
1260
+ if not array.equal_annotations(ref_array):
1261
+ raise ValueError(
1262
+ f"The annotations of the atom array at index {i} are not "
1263
+ f"equal to the annotations of the atom array at index 0"
1264
+ )
1265
+ array_stack = AtomArrayStack(array_count, ref_array.array_length())
1266
+ for name, annotation in ref_array._annot.items():
1267
+ array_stack._annot[name] = annotation
1268
+ coord_list = [array._coord for array in arrays]
1269
+ array_stack._coord = np.stack(coord_list, axis=0)
1270
+ # Take bond list from first array
1271
+ array_stack._bonds = ref_array._bonds
1272
+ # When all atom arrays provide a box, copy the boxes
1273
+ if all([array.box is not None for array in arrays]):
1274
+ array_stack.box = np.array([array.box for array in arrays])
1275
+ return array_stack
1276
+
1277
+
1278
+ def concatenate(atoms):
1279
+ """
1280
+ Concatenate multiple :class:`AtomArray` or :class:`AtomArrayStack` objects into
1281
+ a single :class:`AtomArray` or :class:`AtomArrayStack`, respectively.
1282
+
1283
+ Parameters
1284
+ ----------
1285
+ atoms : iterable object of AtomArray or AtomArrayStack
1286
+ The atoms to be concatenated.
1287
+ :class:`AtomArray` cannot be mixed with :class:`AtomArrayStack`.
1288
+
1289
+ Returns
1290
+ -------
1291
+ concatenated_atoms : AtomArray or AtomArrayStack
1292
+ The concatenated atoms, i.e. its ``array_length()`` is the sum of the
1293
+ ``array_length()`` of the input ``atoms``.
1294
+
1295
+ Notes
1296
+ -----
1297
+ The following rules apply:
1298
+
1299
+ - Only the annotation categories that exist in all elements are transferred.
1300
+ - The box of the first element that has a box is transferred, if any.
1301
+ - The bonds of all elements are concatenated, if any element has associated bonds.
1302
+ For elements without a :class:`BondList` an empty :class:`BondList` is assumed.
1303
+
1304
+ Examples
1305
+ --------
1306
+
1307
+ >>> atoms1 = array([
1308
+ ... Atom([1,2,3], res_id=1, atom_name="N"),
1309
+ ... Atom([4,5,6], res_id=1, atom_name="CA"),
1310
+ ... Atom([7,8,9], res_id=1, atom_name="C")
1311
+ ... ])
1312
+ >>> atoms2 = array([
1313
+ ... Atom([1,2,3], res_id=2, atom_name="N"),
1314
+ ... Atom([4,5,6], res_id=2, atom_name="CA"),
1315
+ ... Atom([7,8,9], res_id=2, atom_name="C")
1316
+ ... ])
1317
+ >>> print(concatenate([atoms1, atoms2]))
1318
+ 1 N 1.000 2.000 3.000
1319
+ 1 CA 4.000 5.000 6.000
1320
+ 1 C 7.000 8.000 9.000
1321
+ 2 N 1.000 2.000 3.000
1322
+ 2 CA 4.000 5.000 6.000
1323
+ 2 C 7.000 8.000 9.000
1324
+ """
1325
+ # Ensure that the atoms can be iterated over multiple times
1326
+ if not isinstance(atoms, Sequence):
1327
+ atoms = list(atoms)
1328
+
1329
+ length = 0
1330
+ depth = None
1331
+ element_type = None
1332
+ common_categories = set(atoms[0].get_annotation_categories())
1333
+ box = None
1334
+ has_bonds = False
1335
+ for element in atoms:
1336
+ if element_type is None:
1337
+ element_type = type(element)
1338
+ else:
1339
+ if not isinstance(element, element_type):
1340
+ raise TypeError(
1341
+ f"Cannot concatenate '{type(element).__name__}' "
1342
+ f"with '{element_type.__name__}'"
1343
+ )
1344
+ length += element.array_length()
1345
+ if isinstance(element, AtomArrayStack):
1346
+ if depth is None:
1347
+ depth = element.stack_depth()
1348
+ else:
1349
+ if element.stack_depth() != depth:
1350
+ raise IndexError("The stack depths are not equal")
1351
+ common_categories &= set(element.get_annotation_categories())
1352
+ if element.box is not None and box is None:
1353
+ box = element.box
1354
+ if element.bonds is not None:
1355
+ has_bonds = True
1356
+
1357
+ if element_type == AtomArray:
1358
+ concat_atoms = AtomArray(length)
1359
+ elif element_type == AtomArrayStack:
1360
+ concat_atoms = AtomArrayStack(depth, length)
1361
+ concat_atoms.coord = np.concatenate([element.coord for element in atoms], axis=-2)
1362
+ for category in common_categories:
1363
+ concat_atoms.set_annotation(
1364
+ category,
1365
+ np.concatenate(
1366
+ [element.get_annotation(category) for element in atoms], axis=0
1367
+ ),
1368
+ )
1369
+ concat_atoms.box = box
1370
+ if has_bonds:
1371
+ # Concatenate bonds of all elements
1372
+ concat_atoms.bonds = BondList.concatenate(
1373
+ [
1374
+ element.bonds
1375
+ if element.bonds is not None
1376
+ else BondList(element.array_length())
1377
+ for element in atoms
1378
+ ]
1379
+ )
1380
+
1381
+ return concat_atoms
1382
+
1383
+
1384
+ def repeat(atoms, coord):
1385
+ """
1386
+ Repeat atoms (:class:`AtomArray` or :class:`AtomArrayStack`)
1387
+ multiple times in the same model with different coordinates.
1388
+
1389
+ Parameters
1390
+ ----------
1391
+ atoms : AtomArray, shape=(n,) or AtomArrayStack, shape=(m,n)
1392
+ The atoms to be repeated.
1393
+ coord : ndarray, dtype=float, shape=(k,n,3) or shape=(k,m,n,3)
1394
+ The coordinates to be used for the repeated atoms.
1395
+ The length of first dimension determines the number of repeats.
1396
+ If `atoms` is an :class:`AtomArray` 3 dimensions, otherwise
1397
+ 4 dimensions are required.
1398
+
1399
+ Returns
1400
+ -------
1401
+ repeated: AtomArray, shape=(n*k,) or AtomArrayStack, shape=(m,n*k)
1402
+ The repeated atoms.
1403
+ Whether an :class:`AtomArray` or an :class:`AtomArrayStack` is
1404
+ returned depends on the input `atoms`.
1405
+
1406
+ Examples
1407
+ --------
1408
+
1409
+ >>> atoms = array([
1410
+ ... Atom([1,2,3], res_id=1, atom_name="N"),
1411
+ ... Atom([4,5,6], res_id=1, atom_name="CA"),
1412
+ ... Atom([7,8,9], res_id=1, atom_name="C")
1413
+ ... ])
1414
+ >>> print(atoms)
1415
+ 1 N 1.000 2.000 3.000
1416
+ 1 CA 4.000 5.000 6.000
1417
+ 1 C 7.000 8.000 9.000
1418
+ >>> repeat_coord = np.array([
1419
+ ... [[0,0,0], [1,1,1], [2,2,2]],
1420
+ ... [[3,3,3], [4,4,4], [5,5,5]]
1421
+ ... ])
1422
+ >>> print(repeat(atoms, repeat_coord))
1423
+ 1 N 0.000 0.000 0.000
1424
+ 1 CA 1.000 1.000 1.000
1425
+ 1 C 2.000 2.000 2.000
1426
+ 1 N 3.000 3.000 3.000
1427
+ 1 CA 4.000 4.000 4.000
1428
+ 1 C 5.000 5.000 5.000
1429
+ """
1430
+ if isinstance(atoms, AtomArray) and coord.ndim != 3:
1431
+ raise ValueError(
1432
+ f"Expected 3 dimensions for the coordinate array, got {coord.ndim}"
1433
+ )
1434
+ elif isinstance(atoms, AtomArrayStack) and coord.ndim != 4:
1435
+ raise ValueError(
1436
+ f"Expected 4 dimensions for the coordinate array, got {coord.ndim}"
1437
+ )
1438
+
1439
+ repetitions = len(coord)
1440
+ orig_length = atoms.array_length()
1441
+ new_length = orig_length * repetitions
1442
+
1443
+ if isinstance(atoms, AtomArray):
1444
+ if coord.ndim != 3:
1445
+ raise ValueError(
1446
+ f"Expected 3 dimensions for the coordinate array, "
1447
+ f"but got {coord.ndim}"
1448
+ )
1449
+ repeated = AtomArray(new_length)
1450
+ repeated.coord = coord.reshape((new_length, 3))
1451
+
1452
+ elif isinstance(atoms, AtomArrayStack):
1453
+ if coord.ndim != 4:
1454
+ raise ValueError(
1455
+ f"Expected 4 dimensions for the coordinate array, "
1456
+ f"but got {coord.ndim}"
1457
+ )
1458
+ repeated = AtomArrayStack(atoms.stack_depth(), new_length)
1459
+ repeated.coord = coord.reshape((atoms.stack_depth(), new_length, 3))
1460
+
1461
+ else:
1462
+ raise TypeError(
1463
+ f"Expected 'AtomArray' or 'AtomArrayStack', "
1464
+ f"but got {type(atoms).__name__}"
1465
+ )
1466
+
1467
+ for category in atoms.get_annotation_categories():
1468
+ annot = np.tile(atoms.get_annotation(category), repetitions)
1469
+ repeated.set_annotation(category, annot)
1470
+ if atoms.bonds is not None:
1471
+ repeated_bonds = atoms.bonds.copy()
1472
+ for _ in range(repetitions - 1):
1473
+ repeated_bonds += atoms.bonds
1474
+ repeated.bonds = repeated_bonds
1475
+ if atoms.box is not None:
1476
+ repeated.box = atoms.box.copy()
1477
+
1478
+ return repeated
1479
+
1480
+
1481
+ def from_template(template, coord, box=None):
1482
+ """
1483
+ Create an :class:`AtomArrayStack` using template atoms and given
1484
+ coordinates.
1485
+
1486
+ Parameters
1487
+ ----------
1488
+ template : AtomArray, shape=(n,) or AtomArrayStack, shape=(m,n)
1489
+ The annotation arrays and bonds of the returned stack are taken
1490
+ from this template.
1491
+ coord : ndarray, dtype=float, shape=(l,n,3)
1492
+ The coordinates for each model of the returned stack.
1493
+ box : ndarray, optional, dtype=float, shape=(l,3,3)
1494
+ The box for each model of the returned stack.
1495
+
1496
+ Returns
1497
+ -------
1498
+ array_stack : AtomArrayStack
1499
+ A stack containing the annotation arrays and bonds from
1500
+ `template` but the coordinates from `coord` and the boxes from
1501
+ `boxes`.
1502
+ """
1503
+ if template.array_length() != coord.shape[-2]:
1504
+ raise ValueError(
1505
+ f"Template has {template.array_length()} atoms, but "
1506
+ f"{coord.shape[-2]} coordinates are given"
1507
+ )
1508
+
1509
+ # Create empty stack with no models
1510
+ new_stack = AtomArrayStack(0, template.array_length())
1511
+
1512
+ for category in template.get_annotation_categories():
1513
+ annot = template.get_annotation(category)
1514
+ new_stack.set_annotation(category, annot)
1515
+ if template.bonds is not None:
1516
+ new_stack.bonds = template.bonds.copy()
1517
+ if box is not None:
1518
+ new_stack.box = box.copy()
1519
+
1520
+ # After setting the coordinates the number of models is the number
1521
+ # of models in the new coordinates
1522
+ new_stack.coord = coord
1523
+
1524
+ return new_stack
1525
+
1526
+
1527
+ def coord(item):
1528
+ """
1529
+ Get the atom coordinates of the given array.
1530
+
1531
+ This may be directly and :class:`Atom`, :class:`AtomArray` or
1532
+ :class:`AtomArrayStack` or
1533
+ alternatively an (n x 3) or (m x n x 3) :class:`ndarray`
1534
+ containing the coordinates.
1535
+
1536
+ Parameters
1537
+ ----------
1538
+ item : Atom or AtomArray or AtomArrayStack or ndarray
1539
+ Returns the :attr:`coord` attribute, if `item` is an
1540
+ :class:`Atom`, :class:`AtomArray` or :class:`AtomArrayStack`.
1541
+ Directly returns the input, if `item` is a :class:`ndarray`.
1542
+
1543
+ Returns
1544
+ -------
1545
+ coord : ndarray
1546
+ Atom coordinates.
1547
+ """
1548
+
1549
+ if type(item) in (Atom, AtomArray, AtomArrayStack):
1550
+ return item.coord
1551
+ elif isinstance(item, np.ndarray):
1552
+ return item.astype(np.float32, copy=False)
1553
+ else:
1554
+ return np.array(item, dtype=np.float32)