biotite 1.5.0__cp311-cp311-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.
- biotite/__init__.py +18 -0
- biotite/application/__init__.py +69 -0
- biotite/application/application.py +276 -0
- biotite/application/autodock/__init__.py +12 -0
- biotite/application/autodock/app.py +500 -0
- biotite/application/blast/__init__.py +14 -0
- biotite/application/blast/alignment.py +92 -0
- biotite/application/blast/webapp.py +428 -0
- biotite/application/clustalo/__init__.py +12 -0
- biotite/application/clustalo/app.py +223 -0
- biotite/application/dssp/__init__.py +12 -0
- biotite/application/dssp/app.py +216 -0
- biotite/application/localapp.py +342 -0
- biotite/application/mafft/__init__.py +12 -0
- biotite/application/mafft/app.py +116 -0
- biotite/application/msaapp.py +363 -0
- biotite/application/muscle/__init__.py +13 -0
- biotite/application/muscle/app3.py +227 -0
- biotite/application/muscle/app5.py +163 -0
- biotite/application/sra/__init__.py +18 -0
- biotite/application/sra/app.py +447 -0
- biotite/application/tantan/__init__.py +12 -0
- biotite/application/tantan/app.py +199 -0
- biotite/application/util.py +77 -0
- biotite/application/viennarna/__init__.py +18 -0
- biotite/application/viennarna/rnaalifold.py +310 -0
- biotite/application/viennarna/rnafold.py +254 -0
- biotite/application/viennarna/rnaplot.py +208 -0
- biotite/application/viennarna/util.py +77 -0
- biotite/application/webapp.py +76 -0
- biotite/copyable.py +71 -0
- biotite/database/__init__.py +23 -0
- biotite/database/afdb/__init__.py +12 -0
- biotite/database/afdb/download.py +197 -0
- biotite/database/entrez/__init__.py +15 -0
- biotite/database/entrez/check.py +60 -0
- biotite/database/entrez/dbnames.py +101 -0
- biotite/database/entrez/download.py +228 -0
- biotite/database/entrez/key.py +44 -0
- biotite/database/entrez/query.py +263 -0
- biotite/database/error.py +16 -0
- biotite/database/pubchem/__init__.py +21 -0
- biotite/database/pubchem/download.py +258 -0
- biotite/database/pubchem/error.py +30 -0
- biotite/database/pubchem/query.py +819 -0
- biotite/database/pubchem/throttle.py +98 -0
- biotite/database/rcsb/__init__.py +13 -0
- biotite/database/rcsb/download.py +161 -0
- biotite/database/rcsb/query.py +963 -0
- biotite/database/uniprot/__init__.py +13 -0
- biotite/database/uniprot/check.py +40 -0
- biotite/database/uniprot/download.py +126 -0
- biotite/database/uniprot/query.py +292 -0
- biotite/file.py +244 -0
- biotite/interface/__init__.py +19 -0
- biotite/interface/openmm/__init__.py +20 -0
- biotite/interface/openmm/state.py +93 -0
- biotite/interface/openmm/system.py +227 -0
- biotite/interface/pymol/__init__.py +201 -0
- biotite/interface/pymol/cgo.py +346 -0
- biotite/interface/pymol/convert.py +185 -0
- biotite/interface/pymol/display.py +267 -0
- biotite/interface/pymol/object.py +1228 -0
- biotite/interface/pymol/shapes.py +178 -0
- biotite/interface/pymol/startup.py +169 -0
- biotite/interface/rdkit/__init__.py +19 -0
- biotite/interface/rdkit/mol.py +490 -0
- biotite/interface/version.py +94 -0
- biotite/interface/warning.py +19 -0
- biotite/sequence/__init__.py +84 -0
- biotite/sequence/align/__init__.py +199 -0
- biotite/sequence/align/alignment.py +702 -0
- biotite/sequence/align/banded.cpython-311-x86_64-linux-gnu.so +0 -0
- biotite/sequence/align/banded.pyx +652 -0
- biotite/sequence/align/buckets.py +71 -0
- biotite/sequence/align/cigar.py +425 -0
- biotite/sequence/align/kmeralphabet.cpython-311-x86_64-linux-gnu.so +0 -0
- biotite/sequence/align/kmeralphabet.pyx +595 -0
- biotite/sequence/align/kmersimilarity.cpython-311-x86_64-linux-gnu.so +0 -0
- biotite/sequence/align/kmersimilarity.pyx +233 -0
- biotite/sequence/align/kmertable.cpython-311-x86_64-linux-gnu.so +0 -0
- biotite/sequence/align/kmertable.pyx +3411 -0
- biotite/sequence/align/localgapped.cpython-311-x86_64-linux-gnu.so +0 -0
- biotite/sequence/align/localgapped.pyx +892 -0
- biotite/sequence/align/localungapped.cpython-311-x86_64-linux-gnu.so +0 -0
- biotite/sequence/align/localungapped.pyx +279 -0
- biotite/sequence/align/matrix.py +631 -0
- biotite/sequence/align/matrix_data/3Di.mat +24 -0
- biotite/sequence/align/matrix_data/BLOSUM100.mat +31 -0
- biotite/sequence/align/matrix_data/BLOSUM30.mat +31 -0
- biotite/sequence/align/matrix_data/BLOSUM35.mat +31 -0
- biotite/sequence/align/matrix_data/BLOSUM40.mat +31 -0
- biotite/sequence/align/matrix_data/BLOSUM45.mat +31 -0
- biotite/sequence/align/matrix_data/BLOSUM50.mat +31 -0
- biotite/sequence/align/matrix_data/BLOSUM50_13p.mat +25 -0
- biotite/sequence/align/matrix_data/BLOSUM50_14.3.mat +25 -0
- biotite/sequence/align/matrix_data/BLOSUM50_5.0.mat +25 -0
- biotite/sequence/align/matrix_data/BLOSUM55.mat +31 -0
- biotite/sequence/align/matrix_data/BLOSUM60.mat +31 -0
- biotite/sequence/align/matrix_data/BLOSUM62.mat +31 -0
- biotite/sequence/align/matrix_data/BLOSUM62_13p.mat +25 -0
- biotite/sequence/align/matrix_data/BLOSUM62_14.3.mat +25 -0
- biotite/sequence/align/matrix_data/BLOSUM62_5.0.mat +25 -0
- biotite/sequence/align/matrix_data/BLOSUM65.mat +31 -0
- biotite/sequence/align/matrix_data/BLOSUM70.mat +31 -0
- biotite/sequence/align/matrix_data/BLOSUM75.mat +31 -0
- biotite/sequence/align/matrix_data/BLOSUM80.mat +31 -0
- biotite/sequence/align/matrix_data/BLOSUM85.mat +31 -0
- biotite/sequence/align/matrix_data/BLOSUM90.mat +31 -0
- biotite/sequence/align/matrix_data/BLOSUMN.mat +31 -0
- biotite/sequence/align/matrix_data/CorBLOSUM49_5.0.mat +25 -0
- biotite/sequence/align/matrix_data/CorBLOSUM57_13p.mat +25 -0
- biotite/sequence/align/matrix_data/CorBLOSUM57_14.3.mat +25 -0
- biotite/sequence/align/matrix_data/CorBLOSUM61_5.0.mat +25 -0
- biotite/sequence/align/matrix_data/CorBLOSUM66_13p.mat +25 -0
- biotite/sequence/align/matrix_data/CorBLOSUM67_14.3.mat +25 -0
- biotite/sequence/align/matrix_data/DAYHOFF.mat +32 -0
- biotite/sequence/align/matrix_data/GONNET.mat +26 -0
- biotite/sequence/align/matrix_data/IDENTITY.mat +25 -0
- biotite/sequence/align/matrix_data/MATCH.mat +25 -0
- biotite/sequence/align/matrix_data/NUC.mat +25 -0
- biotite/sequence/align/matrix_data/PAM10.mat +34 -0
- biotite/sequence/align/matrix_data/PAM100.mat +34 -0
- biotite/sequence/align/matrix_data/PAM110.mat +34 -0
- biotite/sequence/align/matrix_data/PAM120.mat +34 -0
- biotite/sequence/align/matrix_data/PAM130.mat +34 -0
- biotite/sequence/align/matrix_data/PAM140.mat +34 -0
- biotite/sequence/align/matrix_data/PAM150.mat +34 -0
- biotite/sequence/align/matrix_data/PAM160.mat +34 -0
- biotite/sequence/align/matrix_data/PAM170.mat +34 -0
- biotite/sequence/align/matrix_data/PAM180.mat +34 -0
- biotite/sequence/align/matrix_data/PAM190.mat +34 -0
- biotite/sequence/align/matrix_data/PAM20.mat +34 -0
- biotite/sequence/align/matrix_data/PAM200.mat +34 -0
- biotite/sequence/align/matrix_data/PAM210.mat +34 -0
- biotite/sequence/align/matrix_data/PAM220.mat +34 -0
- biotite/sequence/align/matrix_data/PAM230.mat +34 -0
- biotite/sequence/align/matrix_data/PAM240.mat +34 -0
- biotite/sequence/align/matrix_data/PAM250.mat +34 -0
- biotite/sequence/align/matrix_data/PAM260.mat +34 -0
- biotite/sequence/align/matrix_data/PAM270.mat +34 -0
- biotite/sequence/align/matrix_data/PAM280.mat +34 -0
- biotite/sequence/align/matrix_data/PAM290.mat +34 -0
- biotite/sequence/align/matrix_data/PAM30.mat +34 -0
- biotite/sequence/align/matrix_data/PAM300.mat +34 -0
- biotite/sequence/align/matrix_data/PAM310.mat +34 -0
- biotite/sequence/align/matrix_data/PAM320.mat +34 -0
- biotite/sequence/align/matrix_data/PAM330.mat +34 -0
- biotite/sequence/align/matrix_data/PAM340.mat +34 -0
- biotite/sequence/align/matrix_data/PAM350.mat +34 -0
- biotite/sequence/align/matrix_data/PAM360.mat +34 -0
- biotite/sequence/align/matrix_data/PAM370.mat +34 -0
- biotite/sequence/align/matrix_data/PAM380.mat +34 -0
- biotite/sequence/align/matrix_data/PAM390.mat +34 -0
- biotite/sequence/align/matrix_data/PAM40.mat +34 -0
- biotite/sequence/align/matrix_data/PAM400.mat +34 -0
- biotite/sequence/align/matrix_data/PAM410.mat +34 -0
- biotite/sequence/align/matrix_data/PAM420.mat +34 -0
- biotite/sequence/align/matrix_data/PAM430.mat +34 -0
- biotite/sequence/align/matrix_data/PAM440.mat +34 -0
- biotite/sequence/align/matrix_data/PAM450.mat +34 -0
- biotite/sequence/align/matrix_data/PAM460.mat +34 -0
- biotite/sequence/align/matrix_data/PAM470.mat +34 -0
- biotite/sequence/align/matrix_data/PAM480.mat +34 -0
- biotite/sequence/align/matrix_data/PAM490.mat +34 -0
- biotite/sequence/align/matrix_data/PAM50.mat +34 -0
- biotite/sequence/align/matrix_data/PAM500.mat +34 -0
- biotite/sequence/align/matrix_data/PAM60.mat +34 -0
- biotite/sequence/align/matrix_data/PAM70.mat +34 -0
- biotite/sequence/align/matrix_data/PAM80.mat +34 -0
- biotite/sequence/align/matrix_data/PAM90.mat +34 -0
- biotite/sequence/align/matrix_data/PB.license +21 -0
- biotite/sequence/align/matrix_data/PB.mat +18 -0
- biotite/sequence/align/matrix_data/RBLOSUM52_5.0.mat +25 -0
- biotite/sequence/align/matrix_data/RBLOSUM59_13p.mat +25 -0
- biotite/sequence/align/matrix_data/RBLOSUM59_14.3.mat +25 -0
- biotite/sequence/align/matrix_data/RBLOSUM64_5.0.mat +25 -0
- biotite/sequence/align/matrix_data/RBLOSUM69_13p.mat +25 -0
- biotite/sequence/align/matrix_data/RBLOSUM69_14.3.mat +25 -0
- biotite/sequence/align/multiple.cpython-311-x86_64-linux-gnu.so +0 -0
- biotite/sequence/align/multiple.pyx +619 -0
- biotite/sequence/align/pairwise.cpython-311-x86_64-linux-gnu.so +0 -0
- biotite/sequence/align/pairwise.pyx +585 -0
- biotite/sequence/align/permutation.cpython-311-x86_64-linux-gnu.so +0 -0
- biotite/sequence/align/permutation.pyx +313 -0
- biotite/sequence/align/primes.txt +821 -0
- biotite/sequence/align/selector.cpython-311-x86_64-linux-gnu.so +0 -0
- biotite/sequence/align/selector.pyx +954 -0
- biotite/sequence/align/statistics.py +264 -0
- biotite/sequence/align/tracetable.cpython-311-x86_64-linux-gnu.so +0 -0
- biotite/sequence/align/tracetable.pxd +64 -0
- biotite/sequence/align/tracetable.pyx +370 -0
- biotite/sequence/alphabet.py +555 -0
- biotite/sequence/annotation.py +836 -0
- biotite/sequence/codec.cpython-311-x86_64-linux-gnu.so +0 -0
- biotite/sequence/codec.pyx +155 -0
- biotite/sequence/codon.py +476 -0
- biotite/sequence/codon_tables.txt +202 -0
- biotite/sequence/graphics/__init__.py +33 -0
- biotite/sequence/graphics/alignment.py +1101 -0
- biotite/sequence/graphics/color_schemes/3di_flower.json +48 -0
- biotite/sequence/graphics/color_schemes/autumn.json +51 -0
- biotite/sequence/graphics/color_schemes/blossom.json +51 -0
- biotite/sequence/graphics/color_schemes/clustalx_dna.json +11 -0
- biotite/sequence/graphics/color_schemes/clustalx_protein.json +28 -0
- biotite/sequence/graphics/color_schemes/flower.json +51 -0
- biotite/sequence/graphics/color_schemes/jalview_buried.json +31 -0
- biotite/sequence/graphics/color_schemes/jalview_hydrophobicity.json +31 -0
- biotite/sequence/graphics/color_schemes/jalview_prop_helix.json +31 -0
- biotite/sequence/graphics/color_schemes/jalview_prop_strand.json +31 -0
- biotite/sequence/graphics/color_schemes/jalview_prop_turn.json +31 -0
- biotite/sequence/graphics/color_schemes/jalview_taylor.json +28 -0
- biotite/sequence/graphics/color_schemes/jalview_zappo.json +28 -0
- biotite/sequence/graphics/color_schemes/ocean.json +51 -0
- biotite/sequence/graphics/color_schemes/pb_flower.json +40 -0
- biotite/sequence/graphics/color_schemes/rainbow_dna.json +11 -0
- biotite/sequence/graphics/color_schemes/rainbow_protein.json +30 -0
- biotite/sequence/graphics/color_schemes/spring.json +51 -0
- biotite/sequence/graphics/color_schemes/sunset.json +51 -0
- biotite/sequence/graphics/color_schemes/wither.json +51 -0
- biotite/sequence/graphics/colorschemes.py +170 -0
- biotite/sequence/graphics/dendrogram.py +231 -0
- biotite/sequence/graphics/features.py +544 -0
- biotite/sequence/graphics/logo.py +102 -0
- biotite/sequence/graphics/plasmid.py +712 -0
- biotite/sequence/io/__init__.py +12 -0
- biotite/sequence/io/fasta/__init__.py +22 -0
- biotite/sequence/io/fasta/convert.py +283 -0
- biotite/sequence/io/fasta/file.py +265 -0
- biotite/sequence/io/fastq/__init__.py +19 -0
- biotite/sequence/io/fastq/convert.py +117 -0
- biotite/sequence/io/fastq/file.py +507 -0
- biotite/sequence/io/genbank/__init__.py +17 -0
- biotite/sequence/io/genbank/annotation.py +269 -0
- biotite/sequence/io/genbank/file.py +573 -0
- biotite/sequence/io/genbank/metadata.py +336 -0
- biotite/sequence/io/genbank/sequence.py +173 -0
- biotite/sequence/io/general.py +201 -0
- biotite/sequence/io/gff/__init__.py +26 -0
- biotite/sequence/io/gff/convert.py +128 -0
- biotite/sequence/io/gff/file.py +449 -0
- biotite/sequence/phylo/__init__.py +36 -0
- biotite/sequence/phylo/nj.cpython-311-x86_64-linux-gnu.so +0 -0
- biotite/sequence/phylo/nj.pyx +221 -0
- biotite/sequence/phylo/tree.cpython-311-x86_64-linux-gnu.so +0 -0
- biotite/sequence/phylo/tree.pyx +1169 -0
- biotite/sequence/phylo/upgma.cpython-311-x86_64-linux-gnu.so +0 -0
- biotite/sequence/phylo/upgma.pyx +164 -0
- biotite/sequence/profile.py +561 -0
- biotite/sequence/search.py +117 -0
- biotite/sequence/seqtypes.py +720 -0
- biotite/sequence/sequence.py +373 -0
- biotite/setup_ccd.py +197 -0
- biotite/structure/__init__.py +135 -0
- biotite/structure/alphabet/__init__.py +25 -0
- biotite/structure/alphabet/encoder.py +332 -0
- biotite/structure/alphabet/encoder_weights_3di.kerasify +0 -0
- biotite/structure/alphabet/i3d.py +109 -0
- biotite/structure/alphabet/layers.py +86 -0
- biotite/structure/alphabet/pb.license +21 -0
- biotite/structure/alphabet/pb.py +170 -0
- biotite/structure/alphabet/unkerasify.py +128 -0
- biotite/structure/atoms.py +1562 -0
- biotite/structure/basepairs.py +1403 -0
- biotite/structure/bonds.cpython-311-x86_64-linux-gnu.so +0 -0
- biotite/structure/bonds.pyx +2036 -0
- biotite/structure/box.py +724 -0
- biotite/structure/celllist.cpython-311-x86_64-linux-gnu.so +0 -0
- biotite/structure/celllist.pyx +864 -0
- biotite/structure/chains.py +310 -0
- biotite/structure/charges.cpython-311-x86_64-linux-gnu.so +0 -0
- biotite/structure/charges.pyx +520 -0
- biotite/structure/compare.py +683 -0
- biotite/structure/density.py +109 -0
- biotite/structure/dotbracket.py +213 -0
- biotite/structure/error.py +39 -0
- biotite/structure/filter.py +591 -0
- biotite/structure/geometry.py +817 -0
- biotite/structure/graphics/__init__.py +13 -0
- biotite/structure/graphics/atoms.py +243 -0
- biotite/structure/graphics/rna.py +298 -0
- biotite/structure/hbond.py +425 -0
- biotite/structure/info/__init__.py +24 -0
- biotite/structure/info/atom_masses.json +121 -0
- biotite/structure/info/atoms.py +98 -0
- biotite/structure/info/bonds.py +149 -0
- biotite/structure/info/ccd.py +200 -0
- biotite/structure/info/components.bcif +0 -0
- biotite/structure/info/groups.py +128 -0
- biotite/structure/info/masses.py +121 -0
- biotite/structure/info/misc.py +137 -0
- biotite/structure/info/radii.py +267 -0
- biotite/structure/info/standardize.py +185 -0
- biotite/structure/integrity.py +213 -0
- biotite/structure/io/__init__.py +29 -0
- biotite/structure/io/dcd/__init__.py +13 -0
- biotite/structure/io/dcd/file.py +67 -0
- biotite/structure/io/general.py +243 -0
- biotite/structure/io/gro/__init__.py +14 -0
- biotite/structure/io/gro/file.py +343 -0
- biotite/structure/io/mol/__init__.py +20 -0
- biotite/structure/io/mol/convert.py +112 -0
- biotite/structure/io/mol/ctab.py +420 -0
- biotite/structure/io/mol/header.py +120 -0
- biotite/structure/io/mol/mol.py +149 -0
- biotite/structure/io/mol/sdf.py +940 -0
- biotite/structure/io/netcdf/__init__.py +13 -0
- biotite/structure/io/netcdf/file.py +64 -0
- biotite/structure/io/pdb/__init__.py +20 -0
- biotite/structure/io/pdb/convert.py +389 -0
- biotite/structure/io/pdb/file.py +1380 -0
- biotite/structure/io/pdb/hybrid36.cpython-311-x86_64-linux-gnu.so +0 -0
- biotite/structure/io/pdb/hybrid36.pyx +242 -0
- biotite/structure/io/pdbqt/__init__.py +15 -0
- biotite/structure/io/pdbqt/convert.py +113 -0
- biotite/structure/io/pdbqt/file.py +688 -0
- biotite/structure/io/pdbx/__init__.py +23 -0
- biotite/structure/io/pdbx/bcif.py +674 -0
- biotite/structure/io/pdbx/cif.py +1091 -0
- biotite/structure/io/pdbx/component.py +251 -0
- biotite/structure/io/pdbx/compress.py +362 -0
- biotite/structure/io/pdbx/convert.py +2113 -0
- biotite/structure/io/pdbx/encoding.cpython-311-x86_64-linux-gnu.so +0 -0
- biotite/structure/io/pdbx/encoding.pyx +1078 -0
- biotite/structure/io/trajfile.py +696 -0
- biotite/structure/io/trr/__init__.py +13 -0
- biotite/structure/io/trr/file.py +43 -0
- biotite/structure/io/util.py +38 -0
- biotite/structure/io/xtc/__init__.py +13 -0
- biotite/structure/io/xtc/file.py +43 -0
- biotite/structure/mechanics.py +72 -0
- biotite/structure/molecules.py +337 -0
- biotite/structure/pseudoknots.py +622 -0
- biotite/structure/rdf.py +245 -0
- biotite/structure/repair.py +302 -0
- biotite/structure/residues.py +716 -0
- biotite/structure/rings.py +451 -0
- biotite/structure/sasa.cpython-311-x86_64-linux-gnu.so +0 -0
- biotite/structure/sasa.pyx +322 -0
- biotite/structure/segments.py +328 -0
- biotite/structure/sequence.py +110 -0
- biotite/structure/spacegroups.json +1567 -0
- biotite/structure/spacegroups.license +26 -0
- biotite/structure/sse.py +306 -0
- biotite/structure/superimpose.py +511 -0
- biotite/structure/tm.py +581 -0
- biotite/structure/transform.py +736 -0
- biotite/structure/util.py +160 -0
- biotite/version.py +34 -0
- biotite/visualize.py +375 -0
- biotite-1.5.0.dist-info/METADATA +162 -0
- biotite-1.5.0.dist-info/RECORD +354 -0
- biotite-1.5.0.dist-info/WHEEL +6 -0
- 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
|
+
)
|