biotite 1.5.0__cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_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-312-x86_64-linux-gnu.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-312-x86_64-linux-gnu.so +0 -0
  78. biotite/sequence/align/kmeralphabet.pyx +595 -0
  79. biotite/sequence/align/kmersimilarity.cpython-312-x86_64-linux-gnu.so +0 -0
  80. biotite/sequence/align/kmersimilarity.pyx +233 -0
  81. biotite/sequence/align/kmertable.cpython-312-x86_64-linux-gnu.so +0 -0
  82. biotite/sequence/align/kmertable.pyx +3411 -0
  83. biotite/sequence/align/localgapped.cpython-312-x86_64-linux-gnu.so +0 -0
  84. biotite/sequence/align/localgapped.pyx +892 -0
  85. biotite/sequence/align/localungapped.cpython-312-x86_64-linux-gnu.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-312-x86_64-linux-gnu.so +0 -0
  181. biotite/sequence/align/multiple.pyx +619 -0
  182. biotite/sequence/align/pairwise.cpython-312-x86_64-linux-gnu.so +0 -0
  183. biotite/sequence/align/pairwise.pyx +585 -0
  184. biotite/sequence/align/permutation.cpython-312-x86_64-linux-gnu.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-312-x86_64-linux-gnu.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-312-x86_64-linux-gnu.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-312-x86_64-linux-gnu.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-312-x86_64-linux-gnu.so +0 -0
  244. biotite/sequence/phylo/nj.pyx +221 -0
  245. biotite/sequence/phylo/tree.cpython-312-x86_64-linux-gnu.so +0 -0
  246. biotite/sequence/phylo/tree.pyx +1169 -0
  247. biotite/sequence/phylo/upgma.cpython-312-x86_64-linux-gnu.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-312-x86_64-linux-gnu.so +0 -0
  266. biotite/structure/bonds.pyx +2036 -0
  267. biotite/structure/box.py +724 -0
  268. biotite/structure/celllist.cpython-312-x86_64-linux-gnu.so +0 -0
  269. biotite/structure/celllist.pyx +864 -0
  270. biotite/structure/chains.py +310 -0
  271. biotite/structure/charges.cpython-312-x86_64-linux-gnu.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-312-x86_64-linux-gnu.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-312-x86_64-linux-gnu.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-312-x86_64-linux-gnu.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,736 @@
1
+ # This source code is part of the Biotite package and is distributed
2
+ # under the 3-Clause BSD License. Please see 'LICENSE.rst' for further
3
+ # information.
4
+
5
+ """
6
+ This module provides different transformations#
7
+ that can be applied on structures.
8
+ """
9
+
10
+ __name__ = "biotite.structure"
11
+ __author__ = "Patrick Kunzmann", "Claude J. Rogers"
12
+ __all__ = [
13
+ "AffineTransformation",
14
+ "translate",
15
+ "rotate",
16
+ "rotate_centered",
17
+ "rotate_about_axis",
18
+ "orient_principal_components",
19
+ "align_vectors",
20
+ ]
21
+
22
+ import numpy as np
23
+ from biotite.structure.atoms import Atom, AtomArray, AtomArrayStack, coord
24
+ from biotite.structure.util import matrix_rotate, norm_vector, vector_dot
25
+
26
+
27
+ class AffineTransformation:
28
+ """
29
+ An affine transformation, consisting of translations and a rotation.
30
+
31
+ Parameters
32
+ ----------
33
+ center_translation : ndarray, shape=(3,) or shape=(m,3), dtype=float
34
+ The translation vector for moving the centroid into the
35
+ origin.
36
+ rotation : ndarray, shape=(3,3) or shape=(m,3,3), dtype=float
37
+ The rotation matrix.
38
+ target_translation : ndarray, shape=(m,3), dtype=float
39
+ The translation vector for moving the structure onto the
40
+ fixed one.
41
+
42
+ Attributes
43
+ ----------
44
+ center_translation, rotation, target_translation : ndarray
45
+ Same as the parameters.
46
+ The dimensions are always expanded to *(m,3)* or *(m,3,3)*,
47
+ respectively.
48
+ """
49
+
50
+ def __init__(self, center_translation, rotation, target_translation):
51
+ self.center_translation = _expand_dims(center_translation, 2)
52
+ self.rotation = _expand_dims(rotation, 3)
53
+ self.target_translation = _expand_dims(target_translation, 2)
54
+
55
+ def apply(self, atoms):
56
+ """
57
+ Apply this transformation on the given structure.
58
+
59
+ Parameters
60
+ ----------
61
+ atoms : AtomArray or AtomArrayStack or ndarray, shape(n,), dtype=float or ndarray, shape(m,n), dtype=float
62
+ The structure to apply the transformation on.
63
+
64
+ Returns
65
+ -------
66
+ transformed : AtomArray or AtomArrayStack or ndarray, shape(n,), dtype=float or ndarray, shape(m,n), dtype=float
67
+ A copy of the `atoms` structure, with transformations applied.
68
+ Only coordinates are returned, if coordinates were given in `atoms`.
69
+
70
+ Examples
71
+ --------
72
+
73
+ >>> coord = np.arange(15).reshape(5,3)
74
+ >>> print(coord)
75
+ [[ 0 1 2]
76
+ [ 3 4 5]
77
+ [ 6 7 8]
78
+ [ 9 10 11]
79
+ [12 13 14]]
80
+ >>> # Rotates 90 degrees around the z-axis
81
+ >>> transform = AffineTransformation(
82
+ ... center_translation=np.array([0,0,0]),
83
+ ... rotation=np.array([
84
+ ... [0, -1, 0],
85
+ ... [1, 0, 0],
86
+ ... [0, 0, 1]
87
+ ... ]),
88
+ ... target_translation=np.array([0,0,0])
89
+ ... )
90
+ >>> print(transform.apply(coord))
91
+ [[ -1. 0. 2.]
92
+ [ -4. 3. 5.]
93
+ [ -7. 6. 8.]
94
+ [-10. 9. 11.]
95
+ [-13. 12. 14.]]
96
+ """
97
+ mobile_coord = coord(atoms)
98
+ original_shape = mobile_coord.shape
99
+ mobile_coord = _reshape_to_3d(mobile_coord)
100
+ if (
101
+ self.rotation.shape[0] != 1
102
+ and mobile_coord.shape[0] != self.rotation.shape[0]
103
+ ):
104
+ raise IndexError(
105
+ f"Number of transformations is {self.rotation.shape[0]}, "
106
+ f"but number of structure models is {mobile_coord.shape[0]}"
107
+ )
108
+
109
+ superimposed_coord = mobile_coord.copy()
110
+ superimposed_coord += self.center_translation[:, np.newaxis, :]
111
+ superimposed_coord = _multi_matmul(self.rotation, superimposed_coord)
112
+ superimposed_coord += self.target_translation[:, np.newaxis, :]
113
+
114
+ superimposed_coord = superimposed_coord.reshape(original_shape)
115
+ if isinstance(atoms, np.ndarray):
116
+ return superimposed_coord
117
+ else:
118
+ superimposed = atoms.copy()
119
+ superimposed.coord = superimposed_coord
120
+ return superimposed
121
+
122
+ def as_matrix(self):
123
+ """
124
+ Get the translations and rotation as a combined 4x4
125
+ transformation matrix.
126
+
127
+ Multiplying this matrix with coordinates in the form
128
+ *(x, y, z, 1)* will apply the same transformation as
129
+ :meth:`apply()` to coordinates in the form *(x, y, z)*.
130
+
131
+ Returns
132
+ -------
133
+ transformation_matrix : ndarray, shape=(m,4,4), dtype=float
134
+ The transformation matrix.
135
+ *m* is the number of models in the transformation.
136
+
137
+ Examples
138
+ --------
139
+
140
+ >>> coord = np.arange(15).reshape(5,3)
141
+ >>> print(coord)
142
+ [[ 0 1 2]
143
+ [ 3 4 5]
144
+ [ 6 7 8]
145
+ [ 9 10 11]
146
+ [12 13 14]]
147
+ >>> # Rotates 90 degrees around the z-axis
148
+ >>> transform = AffineTransformation(
149
+ ... center_translation=np.array([0,0,0]),
150
+ ... rotation=np.array([
151
+ ... [0, -1, 0],
152
+ ... [1, 0, 0],
153
+ ... [0, 0, 1]
154
+ ... ]),
155
+ ... target_translation=np.array([0,0,0])
156
+ ... )
157
+ >>> print(transform.apply(coord))
158
+ [[ -1. 0. 2.]
159
+ [ -4. 3. 5.]
160
+ [ -7. 6. 8.]
161
+ [-10. 9. 11.]
162
+ [-13. 12. 14.]]
163
+ >>> # Use a 4x4 matrix for transformation as alternative
164
+ >>> coord_4 = np.concatenate([coord, np.ones((len(coord), 1))], axis=-1)
165
+ >>> print(coord_4)
166
+ [[ 0. 1. 2. 1.]
167
+ [ 3. 4. 5. 1.]
168
+ [ 6. 7. 8. 1.]
169
+ [ 9. 10. 11. 1.]
170
+ [12. 13. 14. 1.]]
171
+ >>> print((transform.as_matrix()[0] @ coord_4.T).T)
172
+ [[ -1. 0. 2. 1.]
173
+ [ -4. 3. 5. 1.]
174
+ [ -7. 6. 8. 1.]
175
+ [-10. 9. 11. 1.]
176
+ [-13. 12. 14. 1.]]
177
+ """
178
+ n_models = self.rotation.shape[0]
179
+ rotation_mat = _3d_identity(n_models, 4)
180
+ rotation_mat[:, :3, :3] = self.rotation
181
+ center_translation_mat = _3d_identity(n_models, 4)
182
+ center_translation_mat[:, :3, 3] = self.center_translation
183
+ target_translation_mat = _3d_identity(n_models, 4)
184
+ target_translation_mat[:, :3, 3] = self.target_translation
185
+ return target_translation_mat @ rotation_mat @ center_translation_mat
186
+
187
+ def __eq__(self, other):
188
+ if not isinstance(other, AffineTransformation):
189
+ return False
190
+ if not np.array_equal(self.center_translation, other.center_translation):
191
+ return False
192
+ if not np.array_equal(self.rotation, other.rotation):
193
+ return False
194
+ if not np.array_equal(self.target_translation, other.target_translation):
195
+ return False
196
+ return True
197
+
198
+
199
+ def _expand_dims(array, n_dims):
200
+ """
201
+ Expand the dimensions of an `ndarray` to a certain number of
202
+ dimensions.
203
+ """
204
+ while array.ndim < n_dims:
205
+ array = array[np.newaxis, ...]
206
+ return array
207
+
208
+
209
+ def _3d_identity(m, n):
210
+ """
211
+ Create an array of *m* identity matrices of shape *(n, n)*
212
+ """
213
+ matrices = np.zeros((m, n, n), dtype=float)
214
+ indices = np.arange(n)
215
+ matrices[:, indices, indices] = 1
216
+ return matrices
217
+
218
+
219
+ def _reshape_to_3d(coord):
220
+ """
221
+ Reshape the coordinate array to 3D, if it is 2D.
222
+ """
223
+ if coord.ndim < 2:
224
+ raise ValueError("Coordinates must be at least two-dimensional")
225
+ if coord.ndim == 2:
226
+ return coord[np.newaxis, ...]
227
+ elif coord.ndim == 3:
228
+ return coord
229
+ else:
230
+ raise ValueError("Coordinates must be at most three-dimensional")
231
+
232
+
233
+ def translate(atoms, vector):
234
+ """
235
+ Translate the given atoms or coordinates by a given vector.
236
+
237
+ Parameters
238
+ ----------
239
+ atoms : Atom or AtomArray or AtomArrayStack or ndarray, shape=(3,) or shape=(n,3) or shape=(m,n,3)
240
+ The atoms of which the coordinates are transformed.
241
+ The coordinates can be directly provided as :class:`ndarray`.
242
+ vector : array-like, shape=(3,) or shape=(n,3) or shape=(m,n,3)
243
+ The translation vector :math:`(x, y, z)`.
244
+
245
+ Returns
246
+ -------
247
+ transformed : Atom or AtomArray or AtomArrayStack or ndarray, shape=(3,) or shape=(n,3) or shape=(m,n,3)
248
+ A copy of the input atoms or coordinates, translated by the
249
+ given vector.
250
+ """
251
+ positions = coord(atoms).copy()
252
+ vector = np.asarray(vector)
253
+
254
+ if vector.shape[-1] != 3:
255
+ raise ValueError("Translation vector must contain 3 coordinates")
256
+ positions += vector
257
+ return _put_back(atoms, positions)
258
+
259
+
260
+ def rotate(atoms, angles):
261
+ """
262
+ Rotate the given atoms or coordinates about the *x*, *y* and *z*
263
+ axes by given angles.
264
+
265
+ The rotations are centered at the origin and are performed
266
+ sequentially in the order *x*, *y*, *z*.
267
+
268
+ Parameters
269
+ ----------
270
+ atoms : Atom or AtomArray or AtomArrayStack or ndarray, shape=(3,) or shape=(n,3) or shape=(m,n,3)
271
+ The atoms of which the coordinates are transformed.
272
+ The coordinates can be directly provided as :class:`ndarray`.
273
+ angles : array-like, length=3
274
+ The rotation angles in radians around *x*, *y* and *z*.
275
+
276
+ Returns
277
+ -------
278
+ transformed : Atom or AtomArray or AtomArrayStack or ndarray, shape=(3,) or shape=(n,3) or shape=(m,n,3)
279
+ A copy of the input atoms or coordinates, rotated by the given
280
+ angles.
281
+
282
+ See Also
283
+ --------
284
+ rotate_centered : Rotate atoms about the centroid.
285
+ rotate_about_axis : Rotate atoms about a given axis.
286
+
287
+ Examples
288
+ --------
289
+ Rotation about the *z*-axis by 90 degrees:
290
+
291
+ >>> position = np.array([2.0, 0.0, 0.0])
292
+ >>> rotated = rotate(position, angles=(0, 0, 0.5*np.pi))
293
+ >>> print(rotated)
294
+ [1.225e-16 2.000e+00 0.000e+00]
295
+ """
296
+ from numpy import cos, sin
297
+
298
+ # Check if "angles" contains 3 angles for all dimensions
299
+ if len(angles) != 3:
300
+ raise ValueError("Translation vector must be container of length 3")
301
+ # Create rotation matrices for all 3 dimensions
302
+ rot_x = np.array(
303
+ [
304
+ [1, 0, 0],
305
+ [0, cos(angles[0]), -sin(angles[0])],
306
+ [0, sin(angles[0]), cos(angles[0])],
307
+ ]
308
+ )
309
+
310
+ rot_y = np.array(
311
+ [
312
+ [cos(angles[1]), 0, sin(angles[1])],
313
+ [0, 1, 0],
314
+ [-sin(angles[1]), 0, cos(angles[1])],
315
+ ]
316
+ )
317
+
318
+ rot_z = np.array(
319
+ [
320
+ [cos(angles[2]), -sin(angles[2]), 0],
321
+ [sin(angles[2]), cos(angles[2]), 0],
322
+ [0, 0, 1],
323
+ ]
324
+ )
325
+
326
+ positions = coord(atoms).copy()
327
+ positions = matrix_rotate(positions, rot_z @ rot_y @ rot_x)
328
+
329
+ return _put_back(atoms, positions)
330
+
331
+
332
+ def rotate_centered(atoms, angles):
333
+ """
334
+ Rotate the given atoms or coordinates about the *x*, *y* and *z*
335
+ axes by given angles.
336
+
337
+ The rotations are centered at the centroid of the corresponding
338
+ structure and are performed sequentially in the order *x*, *y*, *z*.
339
+
340
+ Parameters
341
+ ----------
342
+ atoms : Atom or AtomArray or AtomArrayStack or ndarray, shape=(3,) or shape=(n,3) or shape=(m,n,3)
343
+ The atoms of which the coordinates are transformed.
344
+ The coordinates can be directly provided as :class:`ndarray`.
345
+ angles : array-like, length=3
346
+ The rotation angles in radians around axes *x*, *y* and *z*.
347
+
348
+ Returns
349
+ -------
350
+ transformed : Atom or AtomArray or AtomArrayStack or ndarray, shape=(3,) or shape=(n,3) or shape=(m,n,3)
351
+ A copy of the input atoms or coordinates, rotated by the given
352
+ angles.
353
+
354
+ See Also
355
+ --------
356
+ rotate : Rotate atoms about the origin.
357
+ rotate_about_axis : Rotate atoms about a given axis.
358
+ """
359
+ # Avoid circular import
360
+ from biotite.structure.geometry import centroid
361
+
362
+ if len(coord(atoms).shape) == 1:
363
+ # Single value -> centered rotation does not change coordinates
364
+ return atoms.copy()
365
+
366
+ # Rotation around centroid requires moving centroid to origin
367
+ center = coord(centroid(atoms))
368
+ # 'centroid()' removes the second last dimesion
369
+ # -> add dimension again to ensure correct translation
370
+ center = center[..., np.newaxis, :]
371
+ translated = translate(atoms, -center)
372
+ rotated = rotate(translated, angles)
373
+ translated_back = translate(rotated, center)
374
+ return translated_back
375
+
376
+
377
+ def rotate_about_axis(atoms, axis, angle, support=None):
378
+ """
379
+ Rotate the given atoms or coordinates about a given axis by a given
380
+ angle.
381
+
382
+ Parameters
383
+ ----------
384
+ atoms : Atom or AtomArray or AtomArrayStack or ndarray, shape=(3,) or shape=(n,3) or shape=(m,n,3)
385
+ The atoms of which the coordinates are transformed.
386
+ The coordinates can be directly provided as :class:`ndarray`.
387
+ axis : array-like, length=3
388
+ A vector representing the direction of the rotation axis.
389
+ The length of the vector is irrelevant.
390
+ angle : float
391
+ The rotation angle in radians.
392
+ support : array-like, length=3, optional
393
+ An optional support vector for the rotation axis, i.e. the
394
+ center of the rotation.
395
+ By default, the center of the rotation is at *(0,0,0)*.
396
+
397
+ Returns
398
+ -------
399
+ transformed : Atom or AtomArray or AtomArrayStack or ndarray, shape=(3,) or shape=(n,3) or shape=(m,n,3)
400
+ A copy of the input atoms or coordinates, rotated about the
401
+ given axis.
402
+
403
+ See Also
404
+ --------
405
+ rotate : Rotate atoms about the origin.
406
+ rotate_centered : Rotate atoms about the centroid.
407
+
408
+ Examples
409
+ --------
410
+ Rotation about a custom axis on the *y*-*z*-plane by 90 degrees:
411
+
412
+ >>> position = np.array([2.0, 0.0, 0.0])
413
+ >>> axis = [0.0, 1.0, 1.0]
414
+ >>> rotated = rotate_about_axis(position, axis, angle=0.5*np.pi)
415
+ >>> print(rotated)
416
+ [ 1.225e-16 1.414e+00 -1.414e+00]
417
+ """
418
+ positions = coord(atoms).copy()
419
+ if support is not None:
420
+ # Transform coordinates
421
+ # so that the axis support vector is at (0,0,0)
422
+ positions -= np.asarray(support)
423
+
424
+ # Normalize axis
425
+ axis = np.asarray(axis, dtype=np.float32).copy()
426
+ if np.linalg.norm(axis) == 0:
427
+ raise ValueError("Length of the rotation axis is 0")
428
+ norm_vector(axis)
429
+ # Save some interim values, that are used repeatedly in the
430
+ # rotation matrix, for clarity and to save computation time
431
+ sin_a = np.sin(angle)
432
+ cos_a = np.cos(angle)
433
+ icos_a = 1 - cos_a
434
+ x = axis[..., 0]
435
+ y = axis[..., 1]
436
+ z = axis[..., 2]
437
+ # Rotation matrix is taken from
438
+ # https://en.wikipedia.org/wiki/Rotation_matrix#Rotation_matrix_from_axis_and_angle
439
+ rot_matrix = np.array(
440
+ [
441
+ [
442
+ cos_a + icos_a * x**2,
443
+ icos_a * x * y - z * sin_a,
444
+ icos_a * x * z + y * sin_a,
445
+ ],
446
+ [
447
+ icos_a * x * y + z * sin_a,
448
+ cos_a + icos_a * y**2,
449
+ icos_a * y * z - x * sin_a,
450
+ ],
451
+ [
452
+ icos_a * x * z - y * sin_a,
453
+ icos_a * y * z + x * sin_a,
454
+ cos_a + icos_a * z**2,
455
+ ],
456
+ ]
457
+ )
458
+
459
+ # For proper rotation reshape into a maximum of 2 dimensions
460
+ orig_ndim = positions.ndim
461
+ if orig_ndim > 2:
462
+ orig_shape = positions.shape
463
+ positions = positions.reshape(-1, 3)
464
+ # Apply rotation
465
+ positions = np.dot(rot_matrix, positions.T).T
466
+ # Reshape back into original shape
467
+ if orig_ndim > 2:
468
+ positions = positions.reshape(*orig_shape)
469
+
470
+ if support is not None:
471
+ # Transform coordinates back to original support vector position
472
+ positions += np.asarray(support)
473
+
474
+ return _put_back(atoms, positions)
475
+
476
+
477
+ def orient_principal_components(atoms, order=None):
478
+ """
479
+ Translate and rotate the atoms to be centered at the origin with
480
+ the principal axes aligned to the Cartesian axes, as specified by
481
+ the `order` parameter. By default, x, y, z.
482
+
483
+ By default, the resulting coordinates have the highest variance in
484
+ the x-axis and the lowest variance on the z-axis. Setting the `order`
485
+ parameter will change the direction of the highest variance.
486
+ For example, ``order=(2, 1, 0)`` results in highest variance along the
487
+ z-axis and lowest along the x-axis.
488
+
489
+ Parameters
490
+ ----------
491
+ atoms : AtomArray or ndarray, shape=(n,3)
492
+ The atoms of which the coordinates are transformed.
493
+ The coordinates can be directly provided as :class:`ndarray`.
494
+ order : array-like, length=3
495
+ The order of decreasing variance. Setting `order` to ``(2, 0, 1)``
496
+ results in highest variance along the y-axis and lowest along
497
+ the x-axis. Default = ``(0, 1, 2)``.
498
+
499
+ Returns
500
+ -------
501
+ AtomArray or ndarray, shape=(n,3)
502
+ The atoms with coordinates centered at the orgin and aligned with
503
+ xyz axes.
504
+
505
+ Examples
506
+ --------
507
+ Align principal components to xyz axes (default), or specify the order
508
+ of variance.
509
+
510
+ >>> print("original variance =", atom_array.coord.var(axis=0))
511
+ original variance = [26.517 20.009 9.325]
512
+ >>> moved = orient_principal_components(atom_array)
513
+ >>> print("moved variance =", moved.coord.var(axis=0))
514
+ moved variance = [28.906 18.495 8.450]
515
+ >>> # Note the increase in variance along the x-axis
516
+ >>> # Specifying the order keyword changes the orientation
517
+ >>> moved_z = orient_principal_components(atom_array, order=(2, 1, 0))
518
+ >>> print("moved (zyx) variance =", moved_z.coord.var(axis=0))
519
+ moved (zyx) variance = [ 8.450 18.495 28.906]
520
+ """
521
+ # Check user input
522
+ coords = coord(atoms)
523
+ if coords.ndim != 2:
524
+ raise ValueError(f"Expected input shape of (n, 3), got {coords.shape}.")
525
+ row, col = coords.shape
526
+ if (row < 3) or (col != 3):
527
+ raise ValueError(
528
+ f"Expected at least 3 entries, {row} given, and 3 dimensions, {col} given."
529
+ )
530
+ if order is None:
531
+ order = np.array([0, 1, 2])
532
+ else:
533
+ order = np.asarray(order, dtype=int)
534
+ if order.shape != (3,):
535
+ raise ValueError(f"Expected order to have shape (3,), not {order.shape}")
536
+ if not (np.sort(order) == np.arange(3)).all():
537
+ raise ValueError("Expected order to contain [0, 1, 2].")
538
+
539
+ # place centroid of the atoms at the origin
540
+ centered = coords - coords.mean(axis=0)
541
+
542
+ # iterate a few times to ensure the ideal rotation has been applied
543
+ identity = np.eye(3)
544
+ MAX_ITER = 50
545
+ for _ in range(MAX_ITER):
546
+ # PCA, W is the component matrix, s ~ explained variance
547
+ _, sigma, components = np.linalg.svd(centered)
548
+
549
+ # sort 1st, 2nd, 3rd pca compontents along x, y, z
550
+ idx = sigma.argsort()[::-1][order]
551
+ ident = np.eye(3)[:, idx]
552
+
553
+ # Run the Kabsch algorithm to orient principal components
554
+ # along the x, y, z axes
555
+ v, _, wt = np.linalg.svd(components.T @ ident)
556
+
557
+ if np.linalg.det(v) * np.linalg.det(wt) < -0:
558
+ v[:, -1] *= -1
559
+ # Calculate rotation matrix
560
+ rotation = v @ wt
561
+ if np.isclose(rotation, identity, atol=1e-5).all():
562
+ break
563
+ # Apply rotation, keep molecule centered on the origin
564
+ centered = centered @ rotation
565
+ return _put_back(atoms, centered)
566
+
567
+
568
+ def align_vectors(
569
+ atoms,
570
+ origin_direction,
571
+ target_direction,
572
+ origin_position=None,
573
+ target_position=None,
574
+ ):
575
+ """
576
+ Apply a transformation to atoms or coordinates, that would transfer
577
+ a origin vector to a target vector.
578
+
579
+ At first the transformation (translation and rotation), that is
580
+ necessary to align the origin vector to the target vector is
581
+ calculated.
582
+ This means, that the application of the transformation on the
583
+ origin vector would give the target vector.
584
+ Then the same transformation is applied to the given
585
+ atoms/coordinates.
586
+
587
+ Parameters
588
+ ----------
589
+ atoms : Atom or AtomArray or AtomArrayStack or ndarray, shape=(3,) or shape=(n,3) or shape=(m,n,3)
590
+ The atoms of which the coordinates are transformed.
591
+ The coordinates can be directly provided as :class:`ndarray`.
592
+ origin_direction, target_direction : array-like, length=3
593
+ The vectors representing the direction of the origin or target,
594
+ respectively.
595
+ The length of the vectors is irrelevant.
596
+ origin_position, target_position : array-like, length=3, optional
597
+ Optional support vectors for the origin or target, respectively.
598
+ By default, origin and target start at *(0,0,0)*.
599
+
600
+ Returns
601
+ -------
602
+ transformed : Atom or AtomArray or AtomArrayStack or ndarray, shape=(3,) or shape=(n,3) or shape=(m,n,3)
603
+ A copy of the input atoms or coordinates with the applied
604
+ transformation.
605
+
606
+ Examples
607
+ --------
608
+ Align two different residues at their CA-CB bond, i.e. the CA and CB
609
+ position of both residues should be equal:
610
+
611
+ >>> first_residue = atom_array[atom_array.res_id == 1]
612
+ >>> second_residue = atom_array[atom_array.res_id == 2]
613
+ >>> first_residue_ca = first_residue[first_residue.atom_name == "CA"]
614
+ >>> first_residue_cb = first_residue[first_residue.atom_name == "CB"]
615
+ >>> second_residue_ca = second_residue[second_residue.atom_name == "CA"]
616
+ >>> second_residue_cb = second_residue[second_residue.atom_name == "CB"]
617
+ >>> first_residue = align_vectors(
618
+ ... first_residue,
619
+ ... origin_direction = first_residue_cb.coord - first_residue_ca.coord,
620
+ ... target_direction = second_residue_cb.coord - second_residue_ca.coord,
621
+ ... origin_position = first_residue_ca.coord,
622
+ ... target_position = second_residue_ca.coord
623
+ ... )
624
+ >>> # The CA and CB coordinates of both residues are now almost equal
625
+ >>> print(first_residue)
626
+ A 1 ASN N N -5.549 3.788 -1.125
627
+ A 1 ASN CA C -4.923 4.002 -2.452
628
+ A 1 ASN C C -3.877 2.949 -2.808
629
+ A 1 ASN O O -4.051 2.292 -3.825
630
+ A 1 ASN CB C -4.413 5.445 -2.618
631
+ A 1 ASN CG C -5.593 6.413 -2.670
632
+ A 1 ASN OD1 O -6.738 5.988 -2.651
633
+ A 1 ASN ND2 N -5.362 7.711 -2.695
634
+ A 1 ASN H1 H -5.854 2.830 -1.023
635
+ A 1 ASN H2 H -4.902 4.017 -0.382
636
+ A 1 ASN H3 H -6.357 4.397 -1.047
637
+ A 1 ASN HA H -5.711 3.870 -3.198
638
+ A 1 ASN HB2 H -3.781 5.697 -1.788
639
+ A 1 ASN HB3 H -3.860 5.526 -3.556
640
+ A 1 ASN HD21 H -4.440 8.115 -2.680
641
+ A 1 ASN HD22 H -6.190 8.284 -2.750
642
+ >>> print(second_residue)
643
+ A 2 LEU N N -6.379 4.031 -2.228
644
+ A 2 LEU CA C -4.923 4.002 -2.452
645
+ A 2 LEU C C -4.136 3.187 -1.404
646
+ A 2 LEU O O -3.391 2.274 -1.760
647
+ A 2 LEU CB C -4.411 5.450 -2.619
648
+ A 2 LEU CG C -4.795 6.450 -1.495
649
+ A 2 LEU CD1 C -3.612 6.803 -0.599
650
+ A 2 LEU CD2 C -5.351 7.748 -2.084
651
+ A 2 LEU H H -6.821 4.923 -2.394
652
+ A 2 LEU HA H -4.750 3.494 -3.403
653
+ A 2 LEU HB2 H -3.340 5.414 -2.672
654
+ A 2 LEU HB3 H -4.813 5.817 -3.564
655
+ A 2 LEU HG H -5.568 6.022 -0.858
656
+ A 2 LEU HD11 H -3.207 5.905 -0.146
657
+ A 2 LEU HD12 H -2.841 7.304 -1.183
658
+ A 2 LEU HD13 H -3.929 7.477 0.197
659
+ A 2 LEU HD21 H -4.607 8.209 -2.736
660
+ A 2 LEU HD22 H -6.255 7.544 -2.657
661
+ A 2 LEU HD23 H -5.592 8.445 -1.281
662
+ """
663
+ origin_direction = np.asarray(origin_direction, dtype=np.float32).squeeze()
664
+ target_direction = np.asarray(target_direction, dtype=np.float32).squeeze()
665
+ # check that original and target direction are vectors of shape (3,)
666
+ if origin_direction.shape != (3,):
667
+ raise ValueError(
668
+ f"Expected origin vector to have shape (3,), got {origin_direction.shape}"
669
+ )
670
+ if target_direction.shape != (3,):
671
+ raise ValueError(
672
+ f"Expected target vector to have shape (3,), got {target_direction.shape}"
673
+ )
674
+ if np.linalg.norm(origin_direction) == 0:
675
+ raise ValueError("Length of the origin vector is 0")
676
+ if np.linalg.norm(target_direction) == 0:
677
+ raise ValueError("Length of the target vector is 0")
678
+ if origin_position is not None:
679
+ origin_position = np.asarray(origin_position, dtype=np.float32)
680
+ if target_position is not None:
681
+ target_position = np.asarray(target_position, dtype=np.float32)
682
+
683
+ positions = coord(atoms).copy()
684
+ if origin_position is not None:
685
+ # Transform coordinates
686
+ # so that the position of the origin vector is at (0,0,0)
687
+ positions -= origin_position
688
+
689
+ # Normalize direction vectors
690
+ origin_direction = origin_direction.copy()
691
+ norm_vector(origin_direction)
692
+ target_direction = target_direction.copy()
693
+ norm_vector(target_direction)
694
+ # Formula is taken from
695
+ # https://math.stackexchange.com/questions/180418/calculate-rotation-matrix-to-align-vector-a-to-vector-b-in-3d/476311#476311
696
+ vx, vy, vz = np.cross(origin_direction, target_direction)
697
+ v_c = np.array([[0, -vz, vy], [vz, 0, -vx], [-vy, vx, 0]], dtype=float)
698
+ cos_a = vector_dot(origin_direction, target_direction)
699
+ if np.all(cos_a == -1):
700
+ raise ValueError(
701
+ "Direction vectors are point into opposite directions, "
702
+ "cannot calculate rotation matrix"
703
+ )
704
+ rot_matrix = np.identity(3) + v_c + (v_c @ v_c) / (1 + cos_a)
705
+
706
+ positions = matrix_rotate(positions, rot_matrix)
707
+
708
+ if target_position is not None:
709
+ # Transform coordinates to position of the target vector
710
+ positions += target_position
711
+
712
+ return _put_back(atoms, positions)
713
+
714
+
715
+ def _put_back(input_atoms, transformed):
716
+ """
717
+ Put the altered coordinates back into the :class:`Atom`,
718
+ :class:`AtomArray` or :class:`AtomArrayStack`, if the input was one
719
+ of these types.
720
+ """
721
+ if isinstance(input_atoms, (Atom, AtomArray, AtomArrayStack)):
722
+ moved_atoms = input_atoms.copy()
723
+ moved_atoms.coord = transformed
724
+ return moved_atoms
725
+ else:
726
+ return transformed
727
+
728
+
729
+ def _multi_matmul(matrices, vectors):
730
+ """
731
+ Calculate the matrix multiplication of m matrices
732
+ with m x n vectors.
733
+ """
734
+ return np.transpose(
735
+ np.matmul(matrices, np.transpose(vectors, axes=(0, 2, 1))), axes=(0, 2, 1)
736
+ )