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