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