biotite 1.6.0__cp314-cp314-win_amd64.whl

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