├── .gitattributes ├── .gitignore ├── .travis.yml ├── LICENCE ├── Makefile ├── README.md ├── docs ├── README.md ├── analysis.md ├── atoms.md ├── labels.md ├── measure.md ├── molecules.md └── representations.md ├── pylintrc ├── pyvmd ├── __init__.py ├── analysis.py ├── analyzer.py ├── atoms.py ├── collectors.py ├── datasets.py ├── labels.py ├── measure.py ├── molecules.py ├── representations.py └── tests │ ├── __init__.py │ ├── data │ ├── coords.dat │ ├── dataset.dat │ ├── geometry.dat │ ├── rmsd.dat │ ├── water.1.dcd │ ├── water.2.dcd │ ├── water.pdb │ └── water.psf │ ├── test_analysis.py │ ├── test_analyzer.py │ ├── test_atoms.py │ ├── test_collectors.py │ ├── test_coloring_methods.py │ ├── test_datasets.py │ ├── test_drawing_methods.py │ ├── test_labels.py │ ├── test_measure.py │ ├── test_molecules.py │ ├── test_representations.py │ ├── test_utils.py │ └── utils.py ├── setup.cfg └── setup.py /.gitattributes: -------------------------------------------------------------------------------- 1 | * whitespace=trailing-space,tab-in-indent,tabwidth=4 2 | 3 | /Makefile whitespace=space-before-tab,indent-with-non-tab,tabwidth=4 4 | /pyvmd/tests/data/*.psf whitespace=-trailing-space 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # Unit test / coverage reports 6 | htmlcov/ 7 | .coverage 8 | .cache 9 | coverage.xml 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | sudo: false 3 | python: 4 | - "2.7" 5 | install: 6 | - pip install isort 7 | - pip install flake8 8 | - pip list 9 | script: 10 | - make check-isort 11 | - make check-flake8 12 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | VMD = vmd 2 | 3 | # All targets are phony 4 | .PHONY: test coverage pylint pepify isort check-isort check-flake8 5 | 6 | test: 7 | ${VMD} -python -dispdev none -e pyvmd/tests/__init__.py -args discover 8 | 9 | coverage: 10 | python-coverage erase 11 | -rm -r htmlcov 12 | -COVERAGE=1 ${VMD} -python -dispdev none -e pyvmd/tests/__init__.py -args discover 13 | -python-coverage html -d htmlcov 14 | 15 | pylint: 16 | -PYTHONPATH="/usr/lib/vmd/scripts/python" pylint pyvmd --reports=no 17 | 18 | pepify: pylint check-flake8 19 | 20 | isort: 21 | isort --recursive pyvmd 22 | 23 | check-isort: 24 | isort --check-only --diff --recursive pyvmd 25 | 26 | check-flake8: 27 | flake8 --format=pylint pyvmd 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | pyvmd 2 | ===== 3 | 4 | Python tools for VMD (Visual Molecular Dynamics) 5 | 6 | Pyvmd aims to provide high-level pythonic interface to VMD. 7 | 8 | ### Requirements ### 9 | * VMD with python support 10 | * numpy 11 | * setuptools - for installation only 12 | * mock - for tests only 13 | 14 | ### Installation ### 15 | Pyvmd has standard `setup.py` based on setuptools. Install using `python setup.py install`. 16 | 17 | ### Usage ### 18 | Run `vmd -python` to start VMD with python shell. Then you can use pyvmd as any other python library. 19 | 20 | Python scripts for VMD can be run using `vmd -python -e my_script.py`. You can pass arguments to the script using 21 | `-args` option. 22 | 23 | ### Documentation ### 24 | Documentation with examples can be found in [docs](docs). 25 | Detailed documentation of API can be found using python's builtin `help` function. 26 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Topics # 2 | * [Molecules](molecules.md) 3 | * [Atoms](atoms.md) 4 | * [Measurements](measure.md) 5 | * [Trajectory analysis](analysis.md) 6 | * [Labels](labels.md) 7 | * [Representations](representations.md) 8 | -------------------------------------------------------------------------------- /docs/analysis.md: -------------------------------------------------------------------------------- 1 | # Trajectory analysis # 2 | 3 | Pyvmd attempts to provides rather flexible tools for trajectory analysis. 4 | 5 | ## Analyzer ## 6 | Analyzer is the basic object in trajectory analysis. 7 | It loads trajectory files iteratively, thus allowing analysis for trajectories bigger than available memory. 8 | 9 | You can register any number of callbacks to be run on every snapshot of the trajectory. 10 | Each callback will be provided a `step` object, which contains information about the ongoing analysis. 11 | 12 | ### Examples ### 13 | ```python 14 | from pyvmd.analyzer import Analyzer 15 | from pyvmd.molecule import Molecule 16 | 17 | def my_callback(step): 18 | print "The molecule:", step.molecule 19 | print "Total frame number", step.frame 20 | 21 | mol = Molecule(0) # Molecule for analysis. It will be modified. 22 | analyzer = Analyzer(mol, ['foo.dcd', 'bar.dcd']) 23 | # Set the callbacks to be run on every frame 24 | analyzer.add_callback(my_callback) 25 | # Run the analysis 26 | analyzer.analyze() 27 | ``` 28 | 29 | Callbacks can receive any additional arguments that are passed upon their registration. 30 | 31 | ```python 32 | def my_callback(step, extra_arg, extra_keyword): 33 | print step.frame, extra_arg, extra_keyword 34 | 35 | analyzer.add_callback(my_callback, extra_arg, extra_keyword=extra_value) 36 | ``` 37 | 38 | 39 | ## Datasets and collectors ## 40 | Some analyses like RMSD or atom distances are done on regular basis. 41 | For such analyses pyvmd provides tools which extract the data. 42 | There are two basic objects - datasets and collectors. 43 | 44 | * Datasets are containers for data collected during the analysis. 45 | When the analysis is finished dataset provides data either in numpy array or it can write it to the file-like object. 46 | * Collectors performs the specified analysis operation at each snapshot of the trajectory and stores the result into dataset. 47 | 48 | ## List of collectors ## 49 | Every collector takes optinal argument `name`, which is used in the column header if dataset writes data into a file. 50 | If the `name` isn't specified it is generated in form `data12345`. 51 | * `XCoordCollector(selection, name=None)` - Collects X coordinate of an atom or geometric center of selection. 52 | * `YCoordCollector(selection, name=None)` - Collects Y coordinate of an atom or geometric center of selection. 53 | * `ZCoordCollector(selection, name=None)` - Collects Z coordinate of an atom or geometric center of selection. 54 | * `DistanceCollector(selection1, selection2, name=None)` - Collects distance between two atoms or geometric centers of selections. 55 | * `AngleCollector(selection1, selection2, selection3, name=None)` - 56 | Collects angle between three atoms or geometric centers of selections. 57 | * `DihedralCollector(selection1, selection2, selection3, selection4, name=None)` - 58 | Collects dihedral of improper dihedral angle of four atoms or geometric centers of selections. 59 | * `RMSDCollector(selection, reference, name=None)` - Collects RMSD between selection and reference. 60 | The selection is fitted to the reference prior to measuring the RMSD. 61 | 62 | ### Examples ### 63 | ```python 64 | from pyvmd import collectors 65 | from pyvmd.analyzer import Analyzer 66 | from pyvmd.atoms import Selection 67 | from pyvmd.datasets import DataSet 68 | from pyvmd.molecule import Molecule 69 | 70 | dset = DataSet() 71 | # Register collector for distance between protein and LIG residue. 72 | dset.add_collector(collectors.DistanceCollector('protein', 'resname LIG') 73 | ref = Molecule(0) # Reference molecule for RMSD 74 | # Register collector for protein backbone RMSD named 'backbone'. 75 | dset.add_collector( 76 | collectors.RMSDCollector('protein and backbone', 77 | Selection('protein and backbone', ref), 78 | name='backbone') 79 | ) 80 | 81 | mol = Molecule(1) # Molecule for analysis. It will be modified. 82 | analyzer = Analyzer(mol, ['foo.dcd', 'bar.dcd']) 83 | # Set the callbacks to be run on every frame 84 | analyzer.add_dataset(dset) 85 | # Run the analysis 86 | analyzer.analyze() 87 | 88 | # Get the data in numpy array 89 | dset.data #>>> array([[0, 12.45, 0.0], ...]) 90 | # Write data into a file 91 | dset.write('rmsd.dat') 92 | # Write data into a file descriptor 93 | import sys 94 | dset.write(sys.stdout) 95 | ``` 96 | -------------------------------------------------------------------------------- /docs/atoms.md: -------------------------------------------------------------------------------- 1 | # Atoms # 2 | 3 | The most used objects in VMD are probably atoms and selections. Pyvmd provides interface which emulates chemical 4 | objects, such as atoms, residues, chains and segments. 5 | 6 | ## Common features ## 7 | All atom based objects has common features. These are: 8 | * `molecule` 9 | - When creating an object, molecule can be defined. Default is top molecule. 10 | - Object has a `molecule` property which returns its molecule. 11 | * `frame` 12 | - When creating an object, frame can be defined. Default is molecule's active frame (NOW). 13 | - Object has a `frame` property which returns its frame or can be used to change the frame. 14 | * `atomsel` 15 | - Object has a `atomsel` property which returns respective `VMD.atomsel.atomsel` object if its interface is required. 16 | * Container methods 17 | - All objects except `Atom` has basic container features. Function `len()` returns size of the object in atoms, 18 | `in` returns whether atom belongs to a object and object also works as iterable over its atoms. 19 | * Comparison 20 | - All objects have defined equality and inequality operators. 21 | * Hashability 22 | - Objects `Atom` and `Residue` are hashable, so they can be used in sets or as keys in dictionaries. 23 | 24 | ### Examples ### 25 | ```python 26 | from pyvmd.atoms import Selection, NOW 27 | 28 | # Create selection 29 | sel = Selection('protein and noh') 30 | # Create selection defining molecule and frame 31 | sel = Selection('protein and noh', molecule=Molecule(3), frame=17) 32 | 33 | # Get selection's molecule 34 | sel.molecule #>>> Molecule(3) 35 | 36 | # Get selection's frame 37 | sel.frame #>>> 17 38 | # Set selection's frame 39 | sel.frame = 25 40 | # Set selection's frame to molecule's active frame 41 | sel.frame = NOW 42 | 43 | # Get number of atoms in selection 44 | len(sel) #>>> 2048 45 | 46 | # Iterate through atoms in selection 47 | from atom in sel: 48 | do_something() 49 | 50 | # Check if atom is in selection 51 | Atom(789) in sel #>>> True 52 | ``` 53 | 54 | ## Atom ## 55 | Object representing the basic element of a structure. 56 | 57 | ### Atom properties table ### 58 | | Property | Type | VMD keyword | 59 | |-----------|------------------------|-------------| 60 | | index | `int` | index | 61 | | x | `float` | x | 62 | | y | `float` | y | 63 | | z | `float` | z | 64 | | coords | `numpy.array(x, y, z)` | | 65 | | name | `str` | name | 66 | | type | `str` | type | 67 | | element | `str` | element | 68 | | beta | `float` | beta | 69 | | occupancy | `float` | occupancy | 70 | | mass | `float` | mass | 71 | | charge | `float` | charge | 72 | | radius | `float` | radius | 73 | | residue | `pyvmd.Residue` | | 74 | | chain | `pyvmd.Chain` | | 75 | | segment | `pyvmd.Segment` | | 76 | 77 | ### Examples ### 78 | ```python 79 | from pyvmd.atoms import Atom 80 | 81 | # Get atom using its index, the `index` value 82 | atom = Atom(12000) 83 | 84 | # Find atom using selection 85 | atom = Atom.pick('residue 25 and name CA') 86 | # If selection returns none or more than one atom, ValueError is raised 87 | Atom.pick('none') # ValueError 88 | Atom.pick('all') # ValueError 89 | 90 | # Get atom's index 91 | atom.index #>>> 167 92 | 93 | # Atom's coordinates 94 | a.x #>>> 5.3 95 | a.y #>>> 2.5 96 | a.z #>>> 17.89 97 | # All three coordinates can be obtained at once in numpy array 98 | a.coords #>>> array([5.3, 2.5, 17.89]) 99 | # Setters are also available 100 | a.x = 34 101 | a.y = -90.5 102 | a.z = 0.0 103 | a.coords = (5.6, -9, 0.003) 104 | 105 | # Iterate through bonded atoms 106 | for bonded in atom.bonded: 107 | do_something() 108 | 109 | # Get atom's residue 110 | atom.residue #>>> Residue(25) 111 | 112 | # Get atom's chain 113 | atom.chain #>>> Chain('C') 114 | # Move atom to a different chain 115 | atom.chain = Chain('D') 116 | 117 | # Get atom's segment 118 | atom.segment #>>> Segment('S') 119 | # Move atom to a different segment 120 | atom.segment = Segment('U') 121 | ``` 122 | 123 | ## Residue ## 124 | Object representing single residue. 125 | 126 | ### Residue properties table ### 127 | | Property | Type | VMD keyword | 128 | |----------|------------------------|-------------| 129 | | index | `int` | residue | 130 | | name | `str` | resname | 131 | | number | `int` | resid | 132 | 133 | ### Examples ### 134 | ```python 135 | from pyvmd.atoms import Residue 136 | 137 | # Find residue using its index (`residue` value) 138 | res = Residue(42) 139 | 140 | # Get index of the residue, the `residue` value. 141 | res.index #>>> 42 142 | # Get number of the residue, the `resid` value. 143 | res.number #>>> 157 144 | # Change the number of the residue 145 | res.number = 456 146 | ``` 147 | 148 | ## Chains and segments ## 149 | These objects represent single chain and segment, respectively. 150 | 151 | ### Examples ### 152 | ```python 153 | from pyvmd.atoms import Chain, Segment 154 | 155 | # Find chain using its name (`chain` value) 156 | chain = Chain('A') 157 | 158 | # Get name of the chain, the `chain` value. 159 | chain.name #>>> 'A' 160 | # Change the name of the chain 161 | chain.name = 'B' 162 | 163 | # Find segment using its name (`segname` value) 164 | segment = Segment('S') 165 | 166 | # Get name of the segment, the `segname` value. 167 | segment.name #>>> 'S' 168 | # Change the name of the segment 169 | segment.name = 'U' 170 | ``` 171 | 172 | ## Selections ## 173 | Pyvmd also supports generic selections similar to `VMD.atomsel.atomsel`. Selection is automatically updated. 174 | 175 | ### Examples ### 176 | ```python 177 | from pyvmd.atoms import Selection 178 | 179 | # Make a selection 180 | sel = Selection('protein and noh') 181 | 182 | # Get selection text 183 | sel.selection #>>> 'protein and noh' 184 | 185 | # Iterate through contacts between two selections 186 | for atom1, atom2 in sel.contacts(other_sel, 3.0): 187 | do_something() 188 | 189 | # Selections are automatically updated if frame is set to NOW 190 | sel = Selection('ions within 5 of protein') 191 | len(sel) #>>> 10 192 | sel.molecule.frame = 17 193 | len(sel) #>>> 12 194 | ``` 195 | -------------------------------------------------------------------------------- /docs/labels.md: -------------------------------------------------------------------------------- 1 | # Labels # 2 | 3 | Labels allow display of specific atoms, bonds, angles and dihedrals in graphical window. 4 | Module [`pyvmd.labels`](../pyvmd/labels.py) provides interface for labels. 5 | 6 | ## Label objects ## 7 | There are 4 different objects for atoms, bond, angles and dihedrals which are `AtomLabel`, `BondLabel`, `AngleLabel` and `DihedralLabel` respectively. 8 | All of them are proxy object for labels in VMD. 9 | 10 | ### Label creation ### 11 | New labels are created by `create` class method. Creation of the existing label doesn't raise an error. 12 | 13 | ### Properties ### 14 | Label objects have following properties 15 | * `category` - Returns one of constants `ATOM`, `BOND`, `ANGLE`, `DIHEDRAL`. 16 | * `atoms` - Tuple of atoms used to create the label 17 | * `visible` - Gets and sets whether label is visible in graphical window. 18 | 19 | Specific properties 20 | * `AtomLabel.atom` - Returns atom which is labeled 21 | * `BondLabel.distances` - Returns distances between atoms throughout the trajectory 22 | * `AngleLabel.angles` - Returns angles of the atoms throughout the trajectory 23 | * `DihedralLabel.dihedrals` - Returns dihedral angles of the atoms throughout the trajectory 24 | 25 | ### Examples ### 26 | ```python 27 | from pyvmd.atoms import Atom 28 | from pyvmd.labels import AtomLabel, BondLabel, AngleLabel, DihedralLabel 29 | 30 | # Create new label 31 | atom_label = AtomLabel.create(Atom(0)) 32 | 33 | # Check label is visible 34 | atom_label.visible #>>> True 35 | 36 | # Hide the label 37 | atom_label.visible = False 38 | 39 | # Show the label 40 | atom_label.visible = True 41 | 42 | # Delete the label 43 | atom_label.delete() 44 | 45 | # Create a bond label 46 | bond_label = BondLabel.create(Atom(0), Atom(1)) 47 | 48 | # Get distances between atoms 49 | bond_label.distances #>>> [1.569, 1.678, ...] 50 | 51 | # Labels supports equality operators 52 | bond_label == BondLabel(Atom(0), Atom(1)) #>>> True 53 | bond_label != BondLabel(Atom(0), Atom(1)) #>>> False 54 | bond_label == BondLabel.create(Atom(0), Atom(2)) #>>> False 55 | bond_label != BondLabel.create(Atom(0), Atom(2)) #>>> True 56 | ``` 57 | 58 | ## Label managers ## 59 | There are 4 managers to manager all label of given category - `ATOM_LABELS`, `BOND_LABELS`, `ANGLE_LABELS`, `DIHEDRAL_LABELS`. 60 | All of them have simple container API. 61 | 62 | ### Examples ### 63 | ```python 64 | from pyvmd.labels import ATOM_LABELS, BOND_LABELS, ANGLE_LABELS, DIHEDRAL_LABELS 65 | 66 | # Get number of labels in each category 67 | len(ATOM_LABELS) #>>> 4 68 | len(BOND_LABELS) #>>> 3 69 | len(ANGLE_LABELS) #>>> 1 70 | len(DIHEDRAL_LABELS) #>>> 0 71 | 72 | # Iterate through all labels 73 | for label in ATOM_LABELS: 74 | do_something() 75 | ``` 76 | -------------------------------------------------------------------------------- /docs/measure.md: -------------------------------------------------------------------------------- 1 | # Measurements # 2 | 3 | Among other utilities pyvmd provides also functions to measure properties of system. 4 | The utilities reside in module `pyvmd.measure`. 5 | 6 | ## List of functions ## 7 | * `distance(a, b)` - returns distance between two atoms `a` and `b`. 8 | * `angle(a, b, c)` - returns angle between three atoms `a`, `b` and `c`. 9 | * `dihedral(a, b, c, d)` - returns dihedral or improper dihedral angle of four atoms `a`, `b`, `c` and `d`. 10 | * `center(selection)` - returns coordinates of geometric center of the selection or iterable of atoms. 11 | 12 | ### Examples ### 13 | ```python 14 | from pyvmd import measure 15 | from pyvmd.atoms import Atom, Residue, Selection 16 | 17 | # Measure distance between atoms 18 | measure.distance(Atom(0), Atom(42)) #>>> 4.578 19 | 20 | # Measure angle between atoms 21 | measure.angle(Atom(0), Atom(42), Atom(1)) #>>> 73.278 22 | 23 | # Measure dihedral angle of atoms 24 | measure.dihedral(Atom(0), Atom(42), Atom(1), Atom(2)) #>>> -58.378 25 | 26 | # Measure center of selection 27 | measure.center(Selection('all and noh')) #>>> array([0.25, 0.78, 80.58]) 28 | # Measure center of residue 29 | measure.center(Residue(12)) #>>> array([12.89, 57.32, 75.38]) 30 | # Measure center of atom iterable 31 | my_atoms = (Atom(i) for i in xrange(10)) 32 | measure.center(my_atoms) #>>> array([8.95, 3.9, -59.3]) 33 | # Center of atom is equal to its coordinates 34 | measure.center(Atom(0)) == Atom(0).coords #>>> array([ True, True, True]) 35 | ``` 36 | -------------------------------------------------------------------------------- /docs/molecules.md: -------------------------------------------------------------------------------- 1 | # Molecules # 2 | 3 | One of the most basic objects in VMD are molecules. Module [`pyvmd.molecules`](../pyvmd/molecules.py) provides interface 4 | for molecule operations. 5 | 6 | ## Molecule ## 7 | Similar to VMD's `Molecule.Molecule` Molecule object is a proxy for molecule loaded into VMD. 8 | 9 | ### Loading files ### 10 | Method `Molecule.load` has only one required argument which is a filename. But it has several other optional arguments: 11 | * `filetype` - Format of the file. By default it is determined from filename extension, but it can be defined manually. 12 | Preferably using one of `FORMAT_*` constants in `molecule` module. If constant is not available string defining the 13 | filetype can be used. 14 | * `start` - First frame to be read. Default is first. 15 | * `stop` - Last frame to be read. Default is last. 16 | * `step` - Load only every step'th frame. Default is every frame. 17 | * `wait` - Whether to wait until the file is completely read. Default is True. 18 | * `volsets` - Volumetric data sets to be read. 19 | 20 | ### Examples ### 21 | ```python 22 | from pyvmd.molecules import Molecule 23 | 24 | # Create Molecule for existing molecule 25 | mol = Molecule(3) # molid is required 26 | 27 | # Create new empty molecule 28 | mol = Molecule.create() 29 | # Load a file. 30 | mol.load('my_structure.psf') 31 | # Load a trajectory. 32 | mol.load('my_structure.dcd') 33 | 34 | # Get active frame 35 | mol.frame #>>> 15 36 | # Set active frame 37 | mol.frame = 1 38 | 39 | # Get molecule visibility 40 | mol.visible #>>> True 41 | # Hide molecule 42 | mol.visible = False 43 | 44 | # Get molecule's name 45 | mol.name #>>> 'molecule' 46 | # Rename the molecule 47 | mol.name = 'My precious' 48 | 49 | # If you need a missing interface, `molecule` property returns instance of 50 | # VMD's `Molecule.Molecule` object. 51 | vmd_mol = mol.molecule 52 | 53 | # Delete the molecule 54 | mol.delete() 55 | 56 | # Molecules supports equality operators 57 | Molecule(0) == Molecule(0) #>>> True 58 | Molecule(0) != Molecule(0) #>>> False 59 | Molecule(0) == Molecule(1) #>>> False 60 | Molecule(0) != Molecule(1) #>>> True 61 | ``` 62 | 63 | ## Molecule's frames ## 64 | Interface for molecule's frames is grouped in `frames` descriptor. 65 | 66 | ### Examples ### 67 | ```python 68 | # Get number of frames 69 | len(mol.frames) #>>> 16 70 | 71 | # Frames can be used as iterable 72 | for frame in mol.frames: 73 | do_something() 74 | 75 | # Duplicate active frame 76 | mol.frames.copy() 77 | # Duplicate frame 4 78 | mol.frames.copy(4) 79 | 80 | # Delete second frame 81 | del mol.frames[1] 82 | # Delete all but last 4 frames 83 | del mol.frames[:-4] 84 | # Delete every third frame 85 | del mol.frames[::3] 86 | ``` 87 | 88 | ## Application molecules ## 89 | The interface to manipulate all molecules in application is also present. It is available through 90 | `pyvmd.molecules.MOLECULES` and has a usual container-like interface. 91 | 92 | ### Examples ### 93 | ```python 94 | from pyvmd.molecules import MOLECULES 95 | 96 | # Get number of molecules 97 | len(MOLECULES) #>>> 2 98 | 99 | # Iterate through all molecules 100 | for mol in MOLECULES: 101 | do_something() 102 | 103 | # Get top molecule 104 | MOLECULES.top #>>> Molecule(0) 105 | # Set top molecule 106 | MOLECULES.top = Molecule(4) 107 | 108 | # Get molecule by molid 109 | mol = MOLECULES[0] 110 | # Get molecule by name. This may not be reliable if multiple molecules with the same name is present. 111 | mol = MOLECULES['My precious'] 112 | 113 | # Molecules can be also deleted using indexes 114 | del MOLECULES[0] 115 | del MOLECULES['My precious'] 116 | 117 | # Check if molecule still exists 118 | mol in MOLECULES #>>> False 119 | ``` 120 | -------------------------------------------------------------------------------- /docs/representations.md: -------------------------------------------------------------------------------- 1 | # Representations # 2 | 3 | Molecule representations specify display options of molecules in graphical window. 4 | Module [`pyvmd.representations`](../pyvmd/representations.py) provides interface for representations. 5 | 6 | ## Representation objects ## 7 | Object `Representation` contains all details about single representation. All representations are referenced by their name, which is usually in the form `rep`, but unlike index it doesn't change. 8 | 9 | ### Properties ### 10 | Representation objects have following properties 11 | * `molecule` - Returns represented molecule 12 | * `name` - Returns name of the representation 13 | * `style` - Returns Style object 14 | * `color` - Returns Color object 15 | * `selection` - Returns or sets representation selection 16 | * `visible` - Returns or sets visibility 17 | * `update_selection` - Returns or sets automatic selection update with change of frame 18 | * `update_color` - Returns or sets automatic color update with change of frame 19 | 20 | ### Examples ### 21 | ```python 22 | from pyvmd.molecules import Molecule 23 | from pyvmd.representations import Representation 24 | 25 | # Get representation from top molecule using its name 26 | rep = Representation('rep4') 27 | # Get representation from particular molecule 28 | rep = Representation('rep7', Molecule(15)) 29 | 30 | # Get molecule 31 | rep.molecule #>>> Molecule(15) 32 | # Get name 33 | rep.name #>>> 'rep7' 34 | 35 | # Get selection 36 | rep.selection #>>> Selection('protein', Molecule(15)) 37 | # Change selection 38 | rep.selection = Selection('not water') 39 | 40 | # Get visibility 41 | rep.visible #>>> True 42 | # Hide 43 | rep.visible = False 44 | # Show 45 | rep.visible = True 46 | 47 | # Create new representation for top molecule 48 | rep = Representations.create() 49 | # Create new representation for particular molecule 50 | rep = Representations.create(Molecule(15)) 51 | 52 | # Delete representation 53 | rep.delete() 54 | ``` 55 | 56 | ## Style objects ## 57 | Styles in VMD are composed from the drawing method and its parameters. Because of this complexity, styles have their own objects. 58 | 59 | All drawing methods which can be available in VMD are defined as constants. 60 | Names of the constants are based on the name of the methods in VMD and have prefix `DRAW_`. 61 | Depending on compile options, not all of them may be available in all VMD binaries. 62 | 63 | ### Examples ### 64 | ```python 65 | from pyvmd.molecules import Molecule 66 | from pyvmd.representations import Representation, DRAW_CPK 67 | 68 | rep = Representation('rep4') 69 | 70 | # Get drawing method 71 | rep.style.method #>>> DrawingMethod('Lines', ...) 72 | # Get parameters of the drawing method 73 | rep.style.get_parameters() #>>> {'size': 1.0} 74 | 75 | # Change drawing method 76 | rep.style.method = DRAW_CPK 77 | # List available parameters for the method 78 | rep.style.method.parameters #>>> ('size', 'bond_radius', 'resolution', 'bond_resolution') 79 | # Modify drawing parameters 80 | rep.style.set_parameters(resolution=50, bond_resolution=50) 81 | ``` 82 | 83 | ## Color objects ## 84 | Colors are similar to the styles. They are also composed from the drawing method and its parameters, and thus have API similar to the styles. 85 | 86 | All coloring methods which can be available in VMD are defined as constants. 87 | Names of the constants are based on the name of the methods in VMD and have prefix `COLOR_`. 88 | 89 | Currently only the color and volume methods have any parameters. 90 | 91 | ### Examples ### 92 | ```python 93 | from pyvmd.molecules import Molecule 94 | from pyvmd.representations import Representation, COLOR_COLOR 95 | 96 | rep = Representation('rep4') 97 | 98 | # Get coloring method 99 | rep.color.method #>>> ColoringMethod('Name', ...) 100 | # Get parameters of the coloring method 101 | rep.color.get_parameters() #>>> {} 102 | 103 | # Change coloring method 104 | rep.color.method = COLOR_COLOR 105 | # List available parameters for the method 106 | rep.color.method.parameters #>>> ('color', ) 107 | # Modify coloring parameters 108 | rep.color.set_parameters(color=4) 109 | ``` 110 | 111 | ## Molecule representations ## 112 | 113 | An interface to manage all molecule representations is also provided. 114 | 115 | ### Examples ### 116 | ```python 117 | from pyvmd.molecules import Molecule 118 | 119 | mol = Molecule(15) 120 | 121 | # Get number of representations 122 | len(mol.representations) #>>> 5 123 | 124 | # Iterate through representations 125 | for rep in mol.representations: 126 | do_something(rep) 127 | 128 | # Get representation by its name 129 | mol.representations['rep0'] #>>> Representation('rep0', ...) 130 | # Get representation by its index 131 | mol.representations[3] #>>> Representation('rep3', ...) 132 | # Get representation by negative index 133 | mol.representations[-1] #>>> Representation('rep6', ...) 134 | 135 | # Get slices, e.g. list representations backwards 136 | mol.representations[::-1] #>>> [Representation('rep6', ...), ...] 137 | ``` 138 | -------------------------------------------------------------------------------- /pylintrc: -------------------------------------------------------------------------------- 1 | [REPORTS] 2 | # Set the output format. Available formats are text, parseable, colorized, msvs 3 | # (visual studio) and html 4 | output-format=colorized 5 | 6 | # Include message's id in output 7 | include-ids=yes 8 | 9 | 10 | [BASIC] 11 | # Good variable names which should always be accepted, separated by a comma 12 | # a,b,c,d are for atoms 13 | # i,j,k are for indexes 14 | # x,y,z are for coordinates 15 | # setUp and tearDown are used in unittest 16 | good-names=a,b,c,d,i,j,k,x,y,z,_,setUp,tearDown 17 | 18 | # Regular expression which should only match functions or classes name which do 19 | # not require a docstring 20 | # 21 | # Docstrings are not required for private and magic methods 22 | # No docstrings for tests, it is easier to find test without docstring. 23 | no-docstring-rgx=(_|test).* 24 | 25 | 26 | [FORMAT] 27 | # Maximum number of characters on a single line. 28 | max-line-length=120 29 | -------------------------------------------------------------------------------- /pyvmd/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | pyvmd - Python tools for VMD 3 | """ 4 | __version__ = '0.1' 5 | -------------------------------------------------------------------------------- /pyvmd/analysis.py: -------------------------------------------------------------------------------- 1 | """ 2 | Utilities for structure analysis. 3 | """ 4 | from . import measure 5 | from .atoms import Selection 6 | 7 | __all__ = ['hydrogen_bonds', 'HydrogenBond'] 8 | 9 | 10 | class HydrogenBond(object): 11 | """ 12 | Represents hydrogen bond. 13 | 14 | @ivar: Hydrogen donor atom 15 | @ivar: Hydrogen atom 16 | @ivar: Hydrogen acceptor atom 17 | """ 18 | def __init__(self, donor, hydrogen, acceptor): 19 | self.donor = donor 20 | self.hydrogen = hydrogen 21 | self.acceptor = acceptor 22 | 23 | def __repr__(self): 24 | return '<%s: %s--%s..%s>' % (type(self).__name__, self.donor, self.hydrogen, self.acceptor) 25 | 26 | def __eq__(self, other): 27 | return type(self) == type(other) and self.donor == other.donor and self.hydrogen == other.hydrogen and \ 28 | self.acceptor == other.acceptor 29 | 30 | def __ne__(self, other): 31 | return not self.__eq__(other) 32 | 33 | 34 | def _get_bonds(donor, acceptor, donor_hydrogens, angle): 35 | # Utility function which finds hydrogen bonds between donor atom and acceptor atom 36 | hydrogens = (a for a in donor.bonded if a in donor_hydrogens) 37 | for hydrogen in hydrogens: 38 | # Check the angle. If it's big enough, then it is a hydrogen bond 39 | if measure.angle(donor, hydrogen, acceptor) >= angle: 40 | yield HydrogenBond(donor, hydrogen, acceptor) 41 | 42 | 43 | def hydrogen_bonds(donors, acceptors=None, distance=3.0, angle=135): 44 | """ 45 | Returns iterator of hydrogen bonds between the selections. 46 | 47 | @param donors: Hydrogen donors selection 48 | @type donors: Selection 49 | @param acceptors: Hydrogen acceptors selection 50 | @type donors: Selection or None 51 | @param distance: Maximal distance between donor and acceptor 52 | @type distance: Non-negative number 53 | @param angle: Minimal angle in degrees between donor, hydrogen and acceptor 54 | @type angle: Number between 0 and 180 55 | @rtype: Generator of HydrogenBond objects 56 | """ 57 | assert isinstance(donors, Selection) 58 | assert acceptors is None or isinstance(acceptors, Selection) 59 | assert distance >= 0 60 | assert 0 <= angle <= 180 61 | 62 | # Remove hydrogen atoms from selection. This can be done safely, hydrogens are never donors. 63 | donor_heavy = Selection('(%s) and noh' % donors.selection, donors.molecule, donors.frame) 64 | # Create selections of hydrogens for donor molecule. This will be used to find the hydrogen involved in the bond. 65 | donor_hydrogens = Selection('hydrogen', donors.molecule, donors.frame) 66 | if acceptors is None: 67 | # Acceptor is same as donor, just copy 68 | acceptor_heavy = donor_heavy 69 | acceptor_hydrogens = donor_hydrogens 70 | else: 71 | # Acceptor is not the same as donor. Make same selections as for donor. 72 | acceptor_heavy = Selection('(%s) and noh' % acceptors.selection, acceptors.molecule, acceptors.frame) 73 | acceptor_hydrogens = Selection('hydrogen', acceptors.molecule, acceptors.frame) 74 | 75 | for donor, acceptor in donor_heavy.contacts(acceptor_heavy, distance): 76 | for hbond in _get_bonds(donor, acceptor, donor_hydrogens, angle): 77 | yield hbond 78 | # If acceptors and donors share atoms, contacts return pair only once. 79 | # Check if donor and acceptors can have opposite roles. 80 | if donor in acceptor_heavy and acceptor in donor_heavy: 81 | # Donor can be acceptor and acceptor can be donor, check the hydrogen bonds. 82 | for hbond in _get_bonds(acceptor, donor, acceptor_hydrogens, angle): 83 | yield hbond 84 | -------------------------------------------------------------------------------- /pyvmd/analyzer.py: -------------------------------------------------------------------------------- 1 | """ 2 | Analyzer - performs analysis throughout trajectory. 3 | """ 4 | import logging 5 | from collections import namedtuple 6 | 7 | from .molecules import Molecule 8 | 9 | __all__ = ['Analyzer', 'Step'] 10 | 11 | 12 | LOGGER = logging.getLogger(__name__) 13 | 14 | 15 | class Step(object): 16 | """ 17 | Container with information about ongoing analysis. 18 | 19 | @ivar molecule: Molecule object 20 | @ivar frame: Currently analyzed frame (total count). 21 | """ 22 | def __init__(self, molecule): 23 | self.molecule = molecule 24 | # Total frame count 25 | self.frame = -1 26 | # Frame number of the currently loaded frame 27 | self._chunk_frame = -1 28 | 29 | def __repr__(self): 30 | return '<%s: %d>' % (type(self).__name__, self.frame) 31 | 32 | def __str__(self): 33 | return 'Step %d' % self.frame 34 | 35 | def next_chunk(self): 36 | """ 37 | New chunk is loaded. 38 | """ 39 | self._chunk_frame = -1 40 | 41 | def next_frame(self): 42 | """ 43 | Move to the next frame. 44 | """ 45 | self.frame += 1 46 | self._chunk_frame += 1 47 | self.molecule.frame = self._chunk_frame 48 | 49 | 50 | # Internal structure which maintains callback information in analyzer 51 | Callback = namedtuple('Callback', ('function', 'args', 'kwargs')) 52 | 53 | 54 | class Analyzer(object): 55 | """ 56 | Iteratively loads the trajectory files and performs analysis. 57 | """ 58 | def __init__(self, molecule, traj_files, step=1, chunk=100): 59 | """ 60 | @param molecule: Molecule used for loading the trajectory. 61 | @param traj_files: List of trajectory files 62 | @param step: Load every 'step'th frame from trajectory. 63 | @type step: Positive integer 64 | @param chunk: Number of frames to load at once 65 | @type chunk: Positive integer 66 | """ 67 | assert isinstance(molecule, Molecule) 68 | assert step > 0 69 | assert chunk > 0 70 | self.molecule = molecule 71 | self.traj_files = traj_files 72 | self.step = step 73 | self.chunk = chunk 74 | self._callbacks = [] 75 | 76 | def add_callback(self, callback, *args, **kwargs): 77 | """ 78 | Add callback to be called on every frame. 79 | 80 | @param callback: A function to be called on every step. It must expect `Step` object as first argument. 81 | @param *args: Additional positional arguments a function is called with. 82 | @param **kwargs: Additional keyword arguments a function is called with. 83 | """ 84 | self._callbacks.append(Callback(callback, args, kwargs)) 85 | 86 | def add_dataset(self, dataset): 87 | """ 88 | Registers dataset for analysis. 89 | """ 90 | self._callbacks.append(Callback(dataset.collect, (), {})) 91 | 92 | def analyze(self): 93 | """ 94 | Run the analysis. 95 | """ 96 | # Clear the molecule frames 97 | del self.molecule.frames[:] 98 | 99 | step = Step(self.molecule) 100 | for filename in self.traj_files: 101 | start = 0 102 | while True: 103 | # Load 'chunk' frames 104 | stop = start + self.step * self.chunk - 1 105 | LOGGER.debug('Loading %s from %d to %d, every %d', filename, start, stop, self.step) 106 | self.molecule.load(filename, start=start, stop=stop, step=self.step) 107 | loaded = len(self.molecule.frames) 108 | if not loaded: 109 | # No frames were loaded 110 | break 111 | 112 | # Call the callback 113 | step.next_chunk() 114 | for dummy in xrange(0, loaded): 115 | step.next_frame() 116 | LOGGER.info('Analyzing frame %d', step.frame) 117 | for callback in self._callbacks: 118 | callback.function(step, *callback.args, **callback.kwargs) 119 | 120 | # Prepare for next iteration - delete all frames 121 | del self.molecule.frames[:] 122 | if loaded < self.chunk: 123 | # Nothing else to be loaded for this filename 124 | break 125 | start += self.step * self.chunk 126 | LOGGER.info('Analyzed %s frames.', step.frame + 1) 127 | -------------------------------------------------------------------------------- /pyvmd/atoms.py: -------------------------------------------------------------------------------- 1 | """ 2 | Objects for manipulation with VMD atoms. 3 | """ 4 | import itertools 5 | 6 | from atomsel import atomsel as _atomsel 7 | from numpy import array 8 | from VMD import molecule as _molecule 9 | 10 | from .molecules import Molecule, MOLECULES 11 | 12 | __all__ = ['Atom', 'Chain', 'Residue', 'Segment', 'Selection', 'NOW'] 13 | 14 | 15 | # Constant which always references active frame 16 | NOW = -1 17 | 18 | 19 | class SelectionBase(object): 20 | """ 21 | Base class for selection-like objects. 22 | """ 23 | # Large amounts of these objects can be created, slots has some performance benefits. 24 | __slots__ = ('_molecule', '_frame', '_atomsel') 25 | 26 | def __init__(self, molecule=None, frame=NOW): 27 | """ 28 | Creates selection-like object. 29 | 30 | @param molecule: Molecule to select from. Top if not provider. 31 | @type molecule: Molecule or None 32 | @param frame: Selection frame 33 | @type frame: Non-negative integer or NOW 34 | """ 35 | if molecule is None: 36 | molecule = MOLECULES.top 37 | else: 38 | assert isinstance(molecule, Molecule) 39 | assert frame == NOW or frame >= 0 40 | self._molecule = molecule 41 | self._frame = frame 42 | self._atomsel = None 43 | 44 | @property 45 | def molecule(self): 46 | "Molecule" 47 | return self._molecule 48 | 49 | def _get_frame(self): 50 | return self._frame 51 | 52 | def _set_frame(self, frame): 53 | """ 54 | Sets active frame 55 | 56 | @type frame: Non-negative integer or NOW 57 | """ 58 | assert frame == NOW or frame >= 0 59 | self.atomsel.frame = frame 60 | self._frame = frame 61 | 62 | frame = property(_get_frame, _set_frame, doc="Frame") 63 | 64 | @property 65 | def atomsel(self): 66 | """ 67 | Returns respective 'VMD.atomsel' instance. Derived class must implement this property. 68 | """ 69 | raise NotImplementedError 70 | 71 | # Basic getters and setters for selection data 72 | def _getter(self, name): 73 | # The getter should be used only for values which are the same through the selection. 74 | return self.atomsel.get(name)[0] 75 | 76 | def _setter(self, name, value): 77 | # The setter should be used only for values which are the same through the selection. 78 | self.atomsel.set(name, value) 79 | 80 | 81 | class IterableSelectionMixin(object): 82 | """ 83 | Base class for selection-based objects which represent multiple atoms. 84 | """ 85 | # Large amounts of these objects can be created, slots has some performance benefits. 86 | __slots__ = () 87 | 88 | def __len__(self): 89 | return len(self.atomsel) 90 | 91 | def __iter__(self): 92 | for index in self.atomsel: 93 | yield Atom(index, self._molecule, self._frame) 94 | 95 | def __contains__(self, atom): 96 | assert isinstance(atom, Atom) 97 | return bool(self.molecule == atom.molecule and self.frame == atom.frame and self.atomsel[atom.index]) 98 | 99 | 100 | class Selection(IterableSelectionMixin, SelectionBase): 101 | """ 102 | Selection of atoms. 103 | 104 | This class is a proxy to a selection in VMD. 105 | Coordinate based selections are automatically updated. 106 | """ 107 | def __init__(self, selection, molecule=None, frame=NOW): 108 | """ 109 | Creates selection. 110 | 111 | @param selection: Selection text 112 | @param molecule: Molecule to select from. Top if not provider. 113 | @type molecule: Molecule or None 114 | @param frame: Selection frame 115 | @type frame: Non-negative integer or NOW 116 | """ 117 | super(Selection, self).__init__(molecule=molecule, frame=frame) 118 | self._selection = selection 119 | # No need to delay creation of the atomsel. This also checks if the selection text makes sense. 120 | self._atomsel = _atomsel(selection, frame=frame, molid=self._molecule.molid) 121 | # _update_frame is the frame that have been used to filter coordinate based selection in last update. 122 | self._update_frame = self._get_active_frame() 123 | 124 | def __eq__(self, other): 125 | return type(self) == type(other) and self._selection == other.selection and \ 126 | self._molecule == other.molecule and self._frame == other.frame 127 | 128 | def __ne__(self, other): 129 | return not self.__eq__(other) 130 | 131 | def _get_active_frame(self): 132 | # Return which frame is used to filter coordinate based selection. 133 | if self._frame == NOW: 134 | return self._molecule.frame 135 | else: 136 | return self._frame 137 | 138 | def __repr__(self): 139 | return "<%s: '%s' of '%r' at %d>" % (type(self).__name__, self._selection, self._molecule, self._frame) 140 | 141 | @property 142 | def selection(self): 143 | "Selection text" 144 | return self._selection 145 | 146 | @property 147 | def atomsel(self): 148 | """ 149 | Returns respective 'VMD.atomsel' instance. 150 | """ 151 | active_frame = self._get_active_frame() 152 | # Selection can be coordinate-based. If update frame and active frame differ, update selection. 153 | if active_frame != self._update_frame: 154 | self._atomsel.update() 155 | self._update_frame = active_frame 156 | return self._atomsel 157 | 158 | ############################################################################ 159 | # Useful methods 160 | def contacts(self, other, distance): 161 | """ 162 | Returns iterator of atom pairs which are closer than distance. 163 | 164 | @param distance: Maximal distance between atoms in result. 165 | @type distance: Non-negative number 166 | """ 167 | assert isinstance(other, Selection) 168 | assert distance >= 0 169 | atoms_self, atoms_other = self.atomsel.contacts(other.atomsel, distance) 170 | return ((Atom(a, self._molecule, self._frame), Atom(b, other.molecule, other.frame)) 171 | for a, b in itertools.izip(atoms_self, atoms_other)) 172 | 173 | 174 | class StaticSelection(SelectionBase): 175 | """ 176 | Base class for `Atom` and `Residue`. 177 | 178 | Atoms and residues are static objects in VMD. They can't be created or deleted. They have unique identifier which 179 | does not change. Atoms can not be moved to a different residue. 180 | """ 181 | # Large amounts of these objects can be created, slots has some performance benefits. 182 | __slots__ = ('_index', ) 183 | 184 | def __init__(self, index, molecule=None, frame=NOW): 185 | """ 186 | Creates the object. 187 | 188 | @param index: Index of the selection which can not be changed and provide unique identification. 189 | @type index: Non-negative integer 190 | @param molecule: Selection's molecule. Top if not provider. 191 | @type molecule: Molecule or None 192 | @param frame: Selection's frame 193 | @type frame: Non-negative integer or NOW 194 | """ 195 | assert index >= 0 196 | super(StaticSelection, self).__init__(molecule=molecule, frame=frame) 197 | self._index = index 198 | 199 | def __repr__(self): 200 | return "<%s: %d of '%r' at %d>" % (type(self).__name__, self._index, self._molecule, self._frame) 201 | 202 | def __hash__(self): 203 | # We can use unique identifier for objects which have one. 204 | return self._index 205 | 206 | def __eq__(self, other): 207 | return type(self) == type(other) and self._index == other.index and self._molecule == other.molecule and \ 208 | self._frame == other.frame 209 | 210 | def __ne__(self, other): 211 | return not self.__eq__(other) 212 | 213 | @property 214 | def index(self): 215 | "Index" 216 | return self._index 217 | 218 | 219 | def _object_property(keyword, doc): 220 | """Utility function to map VMD keywords to objects properties.""" 221 | def _getter_wrapper(self): 222 | return self._getter(keyword) 223 | 224 | def _setter_wrapper(self, value): 225 | self._setter(keyword, value) 226 | 227 | return property(_getter_wrapper, _setter_wrapper, doc=doc) 228 | 229 | 230 | class Atom(StaticSelection): 231 | """ 232 | Atom representation. 233 | 234 | This class is a proxy to a atom in molecule loaded into VMD. 235 | """ 236 | # Large amounts of these objects can be created, slots has some performance benefits. 237 | __slots__ = () 238 | 239 | def __init__(self, index, molecule=None, frame=NOW): 240 | """ 241 | Creates atom representation. 242 | 243 | @param index: Index of the atom 244 | @param molecule: Atom's molecule. Top if not provider. 245 | @type molecule: Molecule or None 246 | @param frame: Atom's frame 247 | @type frame: Non-negative integer or NOW 248 | """ 249 | super(Atom, self).__init__(index, molecule=molecule, frame=frame) 250 | # Check if index makes sense 251 | if index >= _molecule.numatoms(self._molecule.molid): 252 | raise ValueError("Atom %d doesn't exist in '%s' at %s" % (index, self._molecule, frame)) 253 | 254 | @classmethod 255 | def pick(cls, selection, molecule=None, frame=NOW): 256 | """ 257 | Creates atom from selection text. 258 | 259 | @param selection: Selection text 260 | @param molecule: Molecule to select from. Top if not defined. 261 | @type molecule: Molecule 262 | @param frame: Atom's frame 263 | @type frame: Non-negative integer or NOW 264 | """ 265 | if molecule is None: 266 | molecule = MOLECULES.top 267 | else: 268 | assert isinstance(molecule, Molecule) 269 | assert frame == NOW or frame >= 0 270 | 271 | sel = _atomsel(selection, frame=frame, molid=molecule.molid) 272 | if len(sel) != 1: 273 | raise ValueError("Selection '%s' doesn't define single atom in '%s' at %s" % (selection, molecule, frame)) 274 | self = cls(sel.get('index')[0], molecule, frame) 275 | return self 276 | 277 | @property 278 | def atomsel(self): 279 | """ 280 | Returns respective 'VMD.atomsel' instance. 281 | """ 282 | if self._atomsel is None: 283 | self._atomsel = _atomsel('index %d' % self._index, frame=self._frame, molid=self._molecule.molid) 284 | return self._atomsel 285 | 286 | ############################################################################ 287 | # Atom's data 288 | # Coordinates 289 | x = _object_property('x', doc="Coordinate in 'x' dimension.") 290 | y = _object_property('y', doc="Coordinate in 'y' dimension.") 291 | z = _object_property('z', doc="Coordinate in 'z' dimension.") 292 | 293 | def _get_coords(self): 294 | # XXX: This is unintuitive, but very fast. The atom's center is the location of the atom. 295 | # Apparently getting the coordinates all at once has lower overhead than underlying conputation of the center. 296 | return array(self.atomsel.center()) 297 | 298 | def _set_coords(self, value): 299 | self.x, self.y, self.z = value 300 | 301 | coords = property(_get_coords, _set_coords, doc="Array of (x, y, z) coordinates.") 302 | 303 | # Other data 304 | name = _object_property('name', doc="Atom name.") 305 | type = _object_property('type', doc="Atom type.") 306 | element = _object_property('element', doc="Atom element.") 307 | beta = _object_property('beta', doc="Atom beta factor.") 308 | occupancy = _object_property('occupancy', doc="Atom occupancy.") 309 | mass = _object_property('mass', doc="Atom mass.") 310 | charge = _object_property('charge', doc="Atom charge.") 311 | radius = _object_property('radius', doc="Atom radius.") 312 | 313 | ############################################################################ 314 | # Connections to other objects 315 | @property 316 | def bonded(self): 317 | """ 318 | Returns iterator over Atoms bonded to this one. 319 | """ 320 | return (Atom(i, self._molecule, self._frame) for i in self.atomsel.bonds[0]) 321 | 322 | @property 323 | def residue(self): 324 | """ 325 | Returns atom's residue. 326 | """ 327 | return Residue(self._getter('residue'), self._molecule, self._frame) 328 | 329 | def _get_chain(self): 330 | return Chain(self._getter('chain'), self._molecule, self._frame) 331 | 332 | def _set_chain(self, chain): 333 | assert isinstance(chain, Chain) 334 | self._setter('chain', chain.name) 335 | 336 | chain = property(_get_chain, _set_chain, doc="Atom chain") 337 | 338 | def _get_segment(self): 339 | return Segment(self._getter('segname'), self._molecule, self._frame) 340 | 341 | def _set_segment(self, segment): 342 | assert isinstance(segment, Segment) 343 | self._setter('segname', segment.name) 344 | 345 | segment = property(_get_segment, _set_segment, doc="Atom segment") 346 | 347 | 348 | class Residue(IterableSelectionMixin, StaticSelection): 349 | """ 350 | Residue representation. 351 | 352 | This class is a proxy to a residue in molecule loaded into VMD. 353 | The residue is identified by 'residue' value from VMD. 354 | """ 355 | # Large amounts of these objects can be created, slots has some performance benefits. 356 | __slots__ = () 357 | 358 | # TODO: Check if index makes sense in __init__ 359 | 360 | @property 361 | def atomsel(self): 362 | """ 363 | Returns respective 'VMD.atomsel' instance. 364 | """ 365 | if self._atomsel is None: 366 | self._atomsel = _atomsel('residue %d' % self._index, frame=self._frame, molid=self._molecule.molid) 367 | return self._atomsel 368 | 369 | ############################################################################ 370 | # Residue's data 371 | number = _object_property('resid', doc="Residue number.") 372 | name = _object_property('resname', doc="Residue name.") 373 | 374 | 375 | class DynamicSelection(IterableSelectionMixin, SelectionBase): 376 | """ 377 | Base class for `Chain` and `Segment`. 378 | 379 | Unlike residues, chains and segments are not static in VMD. They can be created and deleted. They have identifiers, 380 | but they can be changed. Atoms can be moved between chains and segments. 381 | """ 382 | # Large amounts of these objects can be created, slots has some performance benefits. 383 | __slots__ = ('_name', ) 384 | 385 | def __init__(self, name, molecule=None, frame=NOW): 386 | """ 387 | Creates the object. 388 | 389 | @param name: Name of the selection. Also acts as an identifier. 390 | @type name: String 391 | @param molecule: Selection's molecule. Top if not provider. 392 | @type molecule: Molecule or None 393 | @param frame: Selection's frame 394 | @type frame: Non-negative integer or NOW 395 | """ 396 | super(DynamicSelection, self).__init__(molecule=molecule, frame=frame) 397 | self._name = name 398 | 399 | def __repr__(self): 400 | return "<%s: '%s' of '%r' at %d>" % (type(self).__name__, self.name, self._molecule, self._frame) 401 | 402 | def __eq__(self, other): 403 | return type(self) == type(other) and self.name == other.name and self._molecule == other.molecule and \ 404 | self._frame == other.frame 405 | 406 | def __ne__(self, other): 407 | return not self.__eq__(other) 408 | 409 | 410 | class Chain(DynamicSelection): 411 | """ 412 | Chain representation. 413 | 414 | This class is a proxy to a chain in molecule loaded into VMD. 415 | The chain is identified by 'chain' value from VMD. 416 | """ 417 | # Large amounts of these objects can be created, slots has some performance benefits. 418 | __slots__ = () 419 | 420 | @property 421 | def atomsel(self): 422 | """ 423 | Returns respective 'VMD.atomsel' instance. 424 | """ 425 | if self._atomsel is None: 426 | self._atomsel = _atomsel('chain "%s"' % self.name, frame=self._frame, molid=self._molecule.molid) 427 | return self._atomsel 428 | 429 | def _get_name(self): 430 | return self._name 431 | 432 | def _set_name(self, value): 433 | # Rename the segment in all its atoms 434 | self._setter('chain', value) 435 | self._name = value 436 | 437 | name = property(_get_name, _set_name, doc="Chain name") 438 | 439 | 440 | class Segment(DynamicSelection): 441 | """ 442 | Segment representation. 443 | 444 | This class is a proxy to a segment in molecule loaded into VMD. 445 | The segment is identified by 'segname' value from VMD. 446 | """ 447 | # Large amounts of these objects can be created, slots has some performance benefits. 448 | __slots__ = () 449 | 450 | @property 451 | def atomsel(self): 452 | """ 453 | Returns respective 'VMD.atomsel' instance. 454 | """ 455 | if self._atomsel is None: 456 | self._atomsel = _atomsel('segname "%s"' % self.name, frame=self._frame, molid=self._molecule.molid) 457 | return self._atomsel 458 | 459 | def _get_name(self): 460 | return self._name 461 | 462 | def _set_name(self, value): 463 | # Rename the segment in all its atoms 464 | self._setter('segname', value) 465 | self._name = value 466 | 467 | name = property(_get_name, _set_name, doc="Segment name") 468 | -------------------------------------------------------------------------------- /pyvmd/collectors.py: -------------------------------------------------------------------------------- 1 | """ 2 | Data collectors for trajectory analysis. 3 | """ 4 | import logging 5 | 6 | from . import measure 7 | from .atoms import Selection 8 | 9 | __all__ = ['AngleCollector', 'Collector', 'DihedralCollector', 'DistanceCollector', 'FrameCollector', 'RMSDCollector', 10 | 'XCoordCollector', 'YCoordCollector', 'ZCoordCollector'] 11 | 12 | 13 | LOGGER = logging.getLogger(__name__) 14 | 15 | 16 | class Collector(object): 17 | """ 18 | Base class for collectors. Collects data from trajectory and writes them into file. 19 | """ 20 | # Format of the column header in the output 21 | header_fmt = '%10s' 22 | # Format of the data in the output 23 | data_fmt = '%10.4f' 24 | 25 | # Counter for automatic name generation. 26 | auto_name_counter = 0 27 | 28 | def __init__(self, name=None): 29 | """ 30 | Create the collector. 31 | 32 | @param name: Name of the collector. If not provided, it is generated in form 'data#####'. 33 | """ 34 | if name is None: 35 | Collector.auto_name_counter += 1 36 | name = 'data%05d' % Collector.auto_name_counter 37 | self.name = name 38 | 39 | def collect(self, step): 40 | """ 41 | Performs the analysis on the frame. 42 | 43 | Derived class must implement this method. 44 | """ 45 | raise NotImplementedError 46 | 47 | 48 | class FrameCollector(Collector): 49 | """ 50 | Utility collector which collects frame number. 51 | """ 52 | header_fmt = '%8s' 53 | data_fmt = '%8d' 54 | 55 | def collect(self, step): 56 | return step.frame 57 | 58 | 59 | def _selection_center(selection_text, molecule): 60 | """ 61 | Utility method to get center of selection. 62 | """ 63 | sel = Selection(selection_text, molecule) 64 | if not len(sel): 65 | raise ValueError("Selection '%s' doesn't match any atoms." % selection_text) 66 | return measure.center(sel) 67 | 68 | 69 | class BaseCoordCollector(Collector): 70 | """ 71 | Base class for collectors of X, Y and Z coordinates. 72 | """ 73 | def __init__(self, selection, name=None): 74 | """ 75 | Creates coordinate collector. 76 | 77 | @param selection: Selection text for collector. 78 | @type selection: String 79 | """ 80 | super(BaseCoordCollector, self).__init__(name) 81 | self.selection = selection 82 | 83 | 84 | class XCoordCollector(BaseCoordCollector): 85 | """ 86 | Collects X coordinate of atom or center of selection. 87 | """ 88 | def collect(self, step): 89 | return _selection_center(self.selection, step.molecule)[0] 90 | 91 | 92 | class YCoordCollector(BaseCoordCollector): 93 | """ 94 | Collects Y coordinate of atom or center of selection. 95 | """ 96 | def collect(self, step): 97 | return _selection_center(self.selection, step.molecule)[1] 98 | 99 | 100 | class ZCoordCollector(BaseCoordCollector): 101 | """ 102 | Collects Z coordinate of atom or center of selection. 103 | """ 104 | def collect(self, step): 105 | return _selection_center(self.selection, step.molecule)[2] 106 | 107 | 108 | class DistanceCollector(Collector): 109 | """ 110 | Collects distance between two atoms or centers of atoms. 111 | """ 112 | def __init__(self, selection1, selection2, name=None): 113 | """ 114 | Creates distance collector. 115 | 116 | @param selection1: Selection text for collector. 117 | @type selection1: String 118 | @param selection2: Selection text for collector. 119 | @type selection2: String 120 | """ 121 | super(DistanceCollector, self).__init__(name) 122 | self.selection1 = selection1 123 | self.selection2 = selection2 124 | 125 | def collect(self, step): 126 | center1 = _selection_center(self.selection1, step.molecule) 127 | center2 = _selection_center(self.selection2, step.molecule) 128 | return measure.coords_distance(center1, center2) 129 | 130 | 131 | class AngleCollector(Collector): 132 | """ 133 | Collects angle between three atoms or centers of atoms. 134 | """ 135 | def __init__(self, selection1, selection2, selection3, name=None): 136 | """ 137 | Creates distance collector. 138 | 139 | @param selection1: Selection text for collector. 140 | @type selection1: String 141 | @param selection2: Selection text for collector. 142 | @type selection2: String 143 | @param selection3: Selection text for collector. 144 | @type selection3: String 145 | """ 146 | super(AngleCollector, self).__init__(name) 147 | self.selection1 = selection1 148 | self.selection2 = selection2 149 | self.selection3 = selection3 150 | 151 | def collect(self, step): 152 | center1 = _selection_center(self.selection1, step.molecule) 153 | center2 = _selection_center(self.selection2, step.molecule) 154 | center3 = _selection_center(self.selection3, step.molecule) 155 | return measure.coords_angle(center1, center2, center3) 156 | 157 | 158 | class DihedralCollector(Collector): 159 | """ 160 | Collects dihedral angle of four atoms or centers of atoms. 161 | """ 162 | def __init__(self, selection1, selection2, selection3, selection4, name=None): 163 | """ 164 | Creates distance collector. 165 | 166 | @param selection1: Selection text for collector. 167 | @type selection1: String 168 | @param selection2: Selection text for collector. 169 | @type selection2: String 170 | @param selection3: Selection text for collector. 171 | @type selection3: String 172 | @param selection4: Selection text for collector. 173 | @type selection4: String 174 | """ 175 | super(DihedralCollector, self).__init__(name) 176 | self.selection1 = selection1 177 | self.selection2 = selection2 178 | self.selection3 = selection3 179 | self.selection4 = selection4 180 | 181 | def collect(self, step): 182 | center1 = _selection_center(self.selection1, step.molecule) 183 | center2 = _selection_center(self.selection2, step.molecule) 184 | center3 = _selection_center(self.selection3, step.molecule) 185 | center4 = _selection_center(self.selection4, step.molecule) 186 | return measure.coords_dihedral(center1, center2, center3, center4) 187 | 188 | 189 | class RMSDCollector(Collector): 190 | """ 191 | Collects RMSD data. 192 | """ 193 | def __init__(self, selection, reference, name=None): 194 | """ 195 | Creates RMSD collector. 196 | 197 | @param selection: Selection text for RMSD 198 | @type selection: String 199 | @param reference: Reference for RMSD 200 | @type reference: Selection 201 | """ 202 | assert isinstance(reference, Selection) 203 | super(RMSDCollector, self).__init__(name) 204 | self.selection = selection 205 | self.reference = reference 206 | 207 | def collect(self, step): 208 | # Active frame number of the molecule. 209 | cur_frame = step.molecule.frame 210 | # Duplicate the trajectory frame because we will modify the coordinates. 211 | # This also sets the molecule frame to the duplicated frame. 212 | step.molecule.frames.copy() 213 | # Duplicated frame number 214 | dup_frame = step.molecule.frame 215 | 216 | all_atoms = Selection('all', step.molecule) 217 | sel = Selection(self.selection, step.molecule) 218 | 219 | # Align coordinates to the reference 220 | all_atoms.atomsel.move(sel.atomsel.fit(self.reference.atomsel)) 221 | 222 | # Measure RMSD 223 | rmsd = sel.atomsel.rmsd(self.reference.atomsel) 224 | 225 | # Delete the duplicated frame and reset trajectory frame 226 | del step.molecule.frames[dup_frame] 227 | step.molecule.frame = cur_frame 228 | 229 | # Return the RMSD value 230 | return rmsd 231 | -------------------------------------------------------------------------------- /pyvmd/datasets.py: -------------------------------------------------------------------------------- 1 | """ 2 | Data sets to collect and store data from trajectory analysis. 3 | """ 4 | import logging 5 | 6 | import numpy 7 | 8 | from .collectors import Collector, FrameCollector 9 | 10 | __all__ = ['DataSet'] 11 | 12 | LOGGER = logging.getLogger(__name__) 13 | 14 | 15 | class DataSet(object): 16 | """ 17 | Basic data set. Collects and stores data extracted from trajectory. 18 | """ 19 | # Number of rows added when increasing size of data array 20 | step = 1000 21 | 22 | def __init__(self): 23 | # Array with data 24 | self._data = None 25 | # List of collectors 26 | self.collectors = [] 27 | # Number of rows filled with data 28 | self._rows = 0 29 | 30 | # Register the frame collector 31 | self.add_collector(FrameCollector('frame')) 32 | 33 | @property 34 | def data(self): 35 | """ 36 | Returns collected data. 37 | """ 38 | # Return only the collected data, not the pre-allocated space. 39 | return self._data[:self._rows] 40 | 41 | def add_collector(self, collector): 42 | """ 43 | Registers collector into this dataset. 44 | """ 45 | assert isinstance(collector, Collector) 46 | self.collectors.append(collector) 47 | LOGGER.debug("Added collector '%s' to dataset '%s'", collector.name, self) 48 | 49 | def collect(self, step): 50 | """ 51 | Retrieves data from collectors and store them. Callback for Analyzer. 52 | """ 53 | row = [collector.collect(step) for collector in self.collectors] 54 | self._add_row(row) 55 | 56 | def _add_row(self, row): 57 | """ 58 | Stores new row with data. 59 | """ 60 | num_columns = len(self.collectors) 61 | num_rows = self._rows + 1 62 | 63 | # Create array if it doesn't exist 64 | if self._data is None: 65 | shape = (self.step, num_columns) 66 | LOGGER.debug("Creating array %s", shape) 67 | self._data = numpy.empty(shape) 68 | # Check if the array is large enough. If not, enlarge 69 | # Get number of rows in data array 70 | data_rows = self._data.shape[0] 71 | if num_rows > data_rows: 72 | # The existing array is not big enough. Create new larger one and copy data. 73 | shape = (data_rows + self.step, num_columns) 74 | LOGGER.debug("Enlarging array %s to %s", self._data.shape, shape) 75 | new_data = numpy.empty(shape) 76 | new_data[:data_rows] = self._data 77 | self._data = new_data 78 | 79 | # Store the new data 80 | self._data[self._rows] = row # Since arrays use 0-based index, row number equals the old number of rows. 81 | self._rows = num_rows 82 | 83 | def write(self, output): 84 | """ 85 | Writes data into output. 86 | 87 | @param output: Filename or file-like object 88 | """ 89 | if isinstance(output, basestring): 90 | out = open(output, 'w') 91 | else: 92 | out = output 93 | 94 | # Write header 95 | header_fmt = ' '.join([c.header_fmt for c in self.collectors]) 96 | out.write(header_fmt % tuple(c.name for c in self.collectors)) 97 | out.write('\n') 98 | 99 | # Write data 100 | formats = [c.data_fmt for c in self.collectors] 101 | numpy.savetxt(out, self.data, formats) 102 | -------------------------------------------------------------------------------- /pyvmd/labels.py: -------------------------------------------------------------------------------- 1 | """ 2 | Interface for VMD labels. 3 | """ 4 | from VMD import label as _label 5 | 6 | from .atoms import Atom 7 | from .molecules import Molecule 8 | 9 | __all__ = ['AngleLabel', 'AtomLabel', 'BondLabel', 'DihedralLabel', 'ANGLE', 'ANGLE_LABELS', 'ATOM', 'ATOM_LABELS', 10 | 'BOND', 'BOND_LABELS', 'DIHEDRAL', 'DIHEDRAL_LABELS'] 11 | 12 | 13 | ATOM = _label.ATOM 14 | BOND = _label.BOND 15 | ANGLE = _label.ANGLE 16 | DIHEDRAL = _label.DIHEDRAL 17 | # String representation of category 18 | CATEGORY_NAMES = { 19 | ATOM: 'ATOM', 20 | BOND: 'BOND', 21 | ANGLE: 'ANGLE', 22 | DIHEDRAL: 'DIHEDRAL', 23 | } 24 | 25 | 26 | def get_label_data(category, atomids, molids): 27 | """ 28 | Utility function to get VMD data for label. 29 | """ 30 | rev_atomids = atomids[::-1] 31 | rev_molids = molids[::-1] 32 | for hit in _label.listall(category): 33 | if hit['atomid'] == atomids and hit['molid'] == molids: 34 | # Found it, return data 35 | return hit 36 | # The lists of atomids and molids may be reversed 37 | elif hit['atomid'] == rev_atomids and hit['molid'] == rev_molids: 38 | # Found it, return data 39 | return hit 40 | 41 | 42 | class BaseLabel(object): 43 | """ 44 | Representation of atom label. 45 | """ 46 | _category = None 47 | 48 | def __init__(self, *atoms): 49 | self._atoms = tuple(atoms) 50 | # Cache tuples of atom IDs and molecule IDs for VMD interface 51 | self._atomids = tuple(a.index for a in self._atoms) 52 | self._molids = tuple(a.molecule.molid for a in self._atoms) 53 | if get_label_data(self._category, self._atomids, self._molids) is None: 54 | raise ValueError("%s for %s doesn't exist" % (type(self).__name__, atoms)) 55 | 56 | def __repr__(self): 57 | return "<%s: %r>" % (type(self).__name__, self._atoms) 58 | 59 | def __eq__(self, other): 60 | if type(self) != type(other) or self.category != other.category: 61 | return False 62 | # Labels are independent on frames, so we can't use atom comparison, we need to check atom and molecule IDs 63 | self_signature = zip(self._atomids, self._molids) 64 | other_signature = [(a.index, a.molecule.molid) for a in other.atoms] 65 | # Labels are equal if atom order is reversed 66 | return self_signature == other_signature or self_signature == other_signature[::-1] 67 | 68 | def __ne__(self, other): 69 | return not self.__eq__(other) 70 | 71 | @property 72 | def category(self): 73 | """ 74 | Label category 75 | """ 76 | return self._category 77 | 78 | @property 79 | def atoms(self): 80 | """ 81 | Labeled atoms 82 | """ 83 | return self._atoms 84 | 85 | @classmethod 86 | def create(cls, *atoms): 87 | """ 88 | Creates the label if it doesn't exist. 89 | """ 90 | atomids = tuple(a.index for a in atoms) 91 | molids = tuple(a.molecule.molid for a in atoms) 92 | _label.add(cls._category, molids, atomids) 93 | return cls(*atoms) 94 | 95 | def delete(self): 96 | """ 97 | Delete the label. 98 | """ 99 | _label.delete(self._category, {'molid': self._molids, 'atomid': self._atomids}) 100 | 101 | def _get_visible(self): 102 | data = get_label_data(self._category, self._atomids, self._molids) 103 | if data is None: 104 | raise ValueError("%s for %s doesn't exist" % (type(self).__name__, self._atoms)) 105 | # Found it, return 'on' key converted to boolean 106 | return bool(data['on']) 107 | 108 | def _set_visible(self, value): 109 | if value: 110 | _label.show(self._category, {'molid': self._molids, 'atomid': self._atomids}) 111 | else: 112 | _label.hide(self._category, {'molid': self._molids, 'atomid': self._atomids}) 113 | 114 | visible = property(_get_visible, _set_visible, doc="Label's visibility") 115 | 116 | 117 | class AtomLabel(BaseLabel): 118 | _category = ATOM 119 | 120 | def __init__(self, a): 121 | assert isinstance(a, Atom) 122 | super(AtomLabel, self).__init__(a) 123 | 124 | @classmethod 125 | def create(cls, a): 126 | return super(AtomLabel, cls).create(a) 127 | 128 | @property 129 | def atom(self): 130 | """ 131 | Labeled atom. 132 | """ 133 | return self._atoms[0] 134 | 135 | 136 | class BondLabel(BaseLabel): 137 | _category = BOND 138 | 139 | def __init__(self, a, b): 140 | assert isinstance(a, Atom) 141 | assert isinstance(b, Atom) 142 | super(BondLabel, self).__init__(a, b) 143 | 144 | @property 145 | def distances(self): 146 | """ 147 | Returns distances between the atoms throughout the loaded trajectory. 148 | """ 149 | return _label.getvalues(self._category, {'molid': self._molids, 'atomid': self._atomids}) 150 | 151 | 152 | class AngleLabel(BaseLabel): 153 | _category = ANGLE 154 | 155 | def __init__(self, a, b, c): 156 | assert isinstance(a, Atom) 157 | assert isinstance(b, Atom) 158 | assert isinstance(c, Atom) 159 | super(AngleLabel, self).__init__(a, b, c) 160 | 161 | @property 162 | def angles(self): 163 | """ 164 | Returns angles between the atoms throughout the loaded trajectory. 165 | """ 166 | return _label.getvalues(self._category, {'molid': self._molids, 'atomid': self._atomids}) 167 | 168 | 169 | class DihedralLabel(BaseLabel): 170 | _category = DIHEDRAL 171 | 172 | def __init__(self, a, b, c, d): 173 | assert isinstance(a, Atom) 174 | assert isinstance(b, Atom) 175 | assert isinstance(c, Atom) 176 | assert isinstance(d, Atom) 177 | super(DihedralLabel, self).__init__(a, b, c, d) 178 | 179 | @property 180 | def dihedrals(self): 181 | """ 182 | Returns dihedral angles between the atoms throughout the loaded trajectory. 183 | """ 184 | return _label.getvalues(self._category, {'molid': self._molids, 'atomid': self._atomids}) 185 | 186 | 187 | class LabelManager(object): 188 | """ 189 | Manages all labels in the category. 190 | """ 191 | def __init__(self, category, label_cls): 192 | assert category in (ATOM, BOND, ANGLE, DIHEDRAL) 193 | self.category = category 194 | self._label_cls = label_cls 195 | 196 | def __repr__(self): 197 | return "<%s: %s>" % (type(self).__name__, CATEGORY_NAMES[self.category]) 198 | 199 | def __len__(self): 200 | return len(_label.listall(self.category)) 201 | 202 | def __iter__(self): 203 | for label in _label.listall(self.category): 204 | yield self._label_cls(*(Atom(a, Molecule(m)) for a, m in zip(label['atomid'], label['molid']))) 205 | 206 | def __contains__(self, label): 207 | # Check for category is fast 208 | if label.category != self.category: 209 | return False 210 | 211 | # Get ids for VMD interface 212 | atomids = tuple(a.index for a in label.atoms) 213 | molids = tuple(a.molecule.molid for a in label.atoms) 214 | data = get_label_data(self.category, atomids, molids) 215 | return data is not None 216 | 217 | 218 | ATOM_LABELS = LabelManager(ATOM, AtomLabel) 219 | BOND_LABELS = LabelManager(BOND, BondLabel) 220 | ANGLE_LABELS = LabelManager(ANGLE, AngleLabel) 221 | DIHEDRAL_LABELS = LabelManager(DIHEDRAL, DihedralLabel) 222 | -------------------------------------------------------------------------------- /pyvmd/measure.py: -------------------------------------------------------------------------------- 1 | """ 2 | Utilities for simple strucural analysis. 3 | """ 4 | import math 5 | 6 | from numpy import array, cross 7 | from numpy.linalg import norm 8 | 9 | from .atoms import Atom, SelectionBase 10 | 11 | __all__ = ['angle', 'center', 'dihedral', 'distance'] 12 | 13 | 14 | def coords_distance(a, b): 15 | """ 16 | Returns distance between two coordinates. 17 | """ 18 | assert a.shape == (3, ) 19 | assert b.shape == (3, ) 20 | return norm(a - b) 21 | 22 | 23 | def distance(a, b): 24 | """ 25 | Returns distance between two atoms. 26 | """ 27 | assert isinstance(a, Atom) 28 | assert isinstance(b, Atom) 29 | return coords_distance(a.coords, b.coords) 30 | 31 | 32 | def coords_angle(a, b, c): 33 | """ 34 | Returns angle between three coordinates a--b--c in degrees. 35 | """ 36 | assert a.shape == (3, ) 37 | assert b.shape == (3, ) 38 | assert c.shape == (3, ) 39 | # Get vectors b-->a and b-->c 40 | vec_1 = a - b 41 | vec_2 = c - b 42 | # Compute angle between the two vectors 43 | cross_prod = cross(vec_1, vec_2) 44 | sine = math.sqrt(cross_prod.dot(cross_prod)) 45 | cosine = vec_1.dot(vec_2) 46 | return math.degrees(math.atan2(sine, cosine)) 47 | 48 | 49 | def angle(a, b, c): 50 | """ 51 | Returns angle between three atoms a--b--c in degrees. 52 | """ 53 | assert isinstance(a, Atom) 54 | assert isinstance(b, Atom) 55 | assert isinstance(c, Atom) 56 | return coords_angle(a.coords, b.coords, c.coords) 57 | 58 | 59 | def coords_dihedral(a, b, c, d): 60 | """ 61 | Returns dihedral angle of four coordinates a--b--c--d in degrees. 62 | """ 63 | assert a.shape == (3, ) 64 | assert b.shape == (3, ) 65 | assert c.shape == (3, ) 66 | assert d.shape == (3, ) 67 | # Get vectors a-->b, b-->c and c-->d 68 | vec_1 = b - a 69 | vec_2 = c - b 70 | vec_3 = d - c 71 | # Compute the dihedral of the three vectors 72 | norm_1 = cross(vec_1, vec_2) 73 | norm_2 = cross(vec_2, vec_3) 74 | sine = norm_1.dot(vec_3) * norm(vec_2) 75 | cosine = norm_1.dot(norm_2) 76 | return math.degrees(math.atan2(sine, cosine)) 77 | 78 | 79 | def dihedral(a, b, c, d): 80 | """ 81 | Returns dihedral or improper dihedral angle of four atoms in degrees. 82 | 83 | Atoms are considered in structure a--b--c--d for dihedral angle or 84 | 85 | a--b--c 86 | | 87 | d 88 | 89 | for improper dihedral angle. 90 | """ 91 | assert isinstance(a, Atom) 92 | assert isinstance(b, Atom) 93 | assert isinstance(c, Atom) 94 | assert isinstance(d, Atom) 95 | return coords_dihedral(a.coords, b.coords, c.coords, d.coords) 96 | 97 | 98 | def center(selection): 99 | """ 100 | Returns geometic center of selection or atom iterable. 101 | 102 | @type selection: Selection, Residue or iterable of Atoms. 103 | """ 104 | if hasattr(selection, 'atomsel'): 105 | assert isinstance(selection, SelectionBase) 106 | # It's a selection-like object, let VMD's atomsel do the job. 107 | return array(selection.atomsel.center()) 108 | else: 109 | # It's other kind of iterable, compute the center. 110 | sum_coords = array((0., 0., 0.)) 111 | count = 0 112 | for atom in selection: 113 | assert isinstance(atom, Atom) 114 | sum_coords += atom.coords 115 | count += 1 116 | return sum_coords / count 117 | -------------------------------------------------------------------------------- /pyvmd/molecules.py: -------------------------------------------------------------------------------- 1 | """ 2 | Objects for manipulation with VMD molecules. 3 | """ 4 | import logging 5 | import os.path 6 | 7 | from Molecule import Molecule as _Molecule 8 | from VMD import molecule as _molecule, molrep as _molrep 9 | 10 | __all__ = ['Frames', 'Molecule', 'FORMAT_DCD', 'FORMAT_PARM7', 'FORMAT_PDB', 'FORMAT_PSF', 'FORMATS', 'MOLECULES'] 11 | 12 | 13 | LOGGER = logging.getLogger(__name__) 14 | 15 | 16 | # File formats 17 | FORMAT_DCD = 'dcd' 18 | FORMAT_PARM7 = 'parm7' 19 | FORMAT_PDB = 'pdb' 20 | FORMAT_PSF = 'psf' 21 | # Dictionary to translate file extensions to file formats 22 | FORMATS = { 23 | 'dcd': FORMAT_DCD, 24 | 'pdb': FORMAT_PDB, 25 | 'psf': FORMAT_PSF, 26 | 'prmtop': FORMAT_PARM7, 27 | } 28 | 29 | 30 | def guess_file_format(filename): 31 | """ 32 | Returns format of the file by guess. 33 | 34 | If format can't be detected returns None. 35 | """ 36 | dummy, ext = os.path.splitext(filename) 37 | if not ext or ext == '.': 38 | return None 39 | ext = ext[1:] 40 | # If the extension is not found in dictionary, return it as is 41 | return FORMATS.get(ext, ext) 42 | 43 | 44 | class Frames(object): 45 | """ 46 | Wrapper for molecules' frames. 47 | """ 48 | def __init__(self, molecule): 49 | """ 50 | @param molecule: Respective molecule 51 | @type molecule: Molecule 52 | """ 53 | # Use molecule instance instead of molid for possible callbacks 54 | assert isinstance(molecule, Molecule) 55 | self.molecule = molecule 56 | 57 | def __len__(self): 58 | return _molecule.numframes(self.molecule.molid) 59 | 60 | def __delitem__(self, key): 61 | # XXX: For some reason, 'skip' in the 'delframe' function means which frames are left when deleting 62 | # That is not consistent with python slicing, so we have to avoid that argument 63 | if isinstance(key, slice): 64 | start, stop, step = key.indices(len(self)) 65 | # We will delete one by one, so we have to that in reversed order 66 | frames = reversed(xrange(start, stop, step)) 67 | elif isinstance(key, int): 68 | if key < 0: 69 | frames = [len(self) + key] 70 | else: 71 | frames = [key] 72 | else: 73 | raise TypeError("%s indices must be integers, not %s" % (type(self), type(key))) 74 | 75 | for frame in frames: 76 | LOGGER.debug("Deleting frame %d", frame) 77 | _molecule.delframe(self.molecule.molid, beg=frame, end=frame) 78 | 79 | def __iter__(self): 80 | # Return the iterator over frames 81 | return iter(xrange(len(self))) 82 | 83 | def copy(self, frame=None): 84 | """ 85 | Copies frame and moves the molecule to the new frame. 86 | 87 | @param frame: Frame to be copied. If not defined or `None`, active frame is copied. 88 | @type frame: Non-negative integer or `None` 89 | """ 90 | if frame is None: 91 | frame = self.molecule.frame 92 | else: 93 | assert frame >= 0 94 | _molecule.dupframe(self.molecule.molid, frame) 95 | 96 | 97 | class RepresentationManager(object): 98 | """ 99 | Manager of molecule representations. 100 | """ 101 | def __init__(self, molecule): 102 | """ 103 | @param molecule: Respective molecule 104 | @type molecule: Molecule 105 | """ 106 | # Use molecule instance instead of molid for possible callbacks 107 | assert isinstance(molecule, Molecule) 108 | self.molecule = molecule 109 | 110 | def __len__(self): 111 | return _molrep.num(self.molecule.molid) 112 | 113 | def __iter__(self): 114 | from pyvmd.representations import Representation 115 | for i in xrange(len(self)): 116 | yield Representation(_molrep.get_repname(self.molecule.molid, i), self.molecule) 117 | 118 | def __getitem__(self, key): 119 | from pyvmd.representations import Representation 120 | length = len(self) 121 | if isinstance(key, slice): 122 | start, stop, step = key.indices(length) 123 | # Use recursion for individual representations 124 | return [self[i] for i in xrange(*key.indices(length))] 125 | elif isinstance(key, int): 126 | if key < 0: 127 | index = length + key 128 | else: 129 | index = key 130 | if not 0 <= index < length: 131 | raise IndexError("Index out of range") 132 | return Representation(_molrep.get_repname(self.molecule.molid, index), self.molecule) 133 | elif isinstance(key, basestring): 134 | try: 135 | return Representation(key, self.molecule) 136 | except ValueError: 137 | raise KeyError(key) 138 | else: 139 | raise TypeError("%s indices must be integers, not %s" % (type(self), type(key))) 140 | 141 | 142 | class Molecule(object): 143 | """ 144 | Molecule representation. 145 | 146 | This class is a proxy for molecule loaded into VMD. 147 | """ 148 | def __init__(self, molid): 149 | """ 150 | Creates a new molecule. 151 | 152 | @param molid: ID of existing molecule 153 | @type molid: Non-negative integer 154 | """ 155 | assert molid >= 0 156 | if not _molecule.exists(molid): 157 | raise ValueError("Molecule %d does not exist." % molid) 158 | self.molid = molid 159 | self._molecule = None 160 | 161 | def __repr__(self): 162 | return "<%s: %s(%d)>" % (type(self).__name__, self.name, self.molid) 163 | 164 | def __eq__(self, other): 165 | return type(self) == type(other) and self.molid == other.molid 166 | 167 | def __ne__(self, other): 168 | return not self.__eq__(other) 169 | 170 | @classmethod 171 | def create(cls, name=None): 172 | """ 173 | Creates new molecule. 174 | 175 | @name: Name of the molecule. 176 | """ 177 | molid = _molecule.new(name or 'molecule') 178 | return cls(molid) 179 | 180 | def delete(self): 181 | """ 182 | Deletes the molecule. 183 | """ 184 | _molecule.delete(self.molid) 185 | 186 | def load(self, filename, filetype=None, start=0, stop=-1, step=1, wait=True, volsets=None): 187 | """ 188 | Loads data from file into the molecule. 189 | 190 | @param filename: Name of file to be loaded 191 | @type filename: String 192 | @param filetype: Format of file. If not present or `None` it is guessed. 193 | @type filetype: One of `FORMAT_` constants, string or `None` 194 | @param start: First frame to be loaded. Default is first frame in the file. 195 | @type start: Non-negative integer 196 | @param stop: Last frame to be loaded. Default (-1) is last frame in the file. 197 | @type stop: Non-negative integer or -1 198 | @param step: Load every step'th frame. Default is every frame. 199 | @type step: Positive integer 200 | @param wait: Whather to wait until file is completely loaded. 201 | @type wait: Boolean 202 | """ 203 | assert start >= 0 204 | assert stop >= -1 205 | assert step > 0 206 | if filetype is None: 207 | filetype = guess_file_format(filename) 208 | if filetype is None: 209 | raise ValueError("Cannot detect filetype for '%s'" % filename) 210 | waitfor = wait and -1 or 0 211 | volsets = volsets or [] 212 | _molecule.read(self.molid, filetype, filename, beg=start, end=stop, skip=step, waitfor=waitfor, 213 | volsets=volsets) 214 | 215 | @property 216 | def molecule(self): 217 | """ 218 | Returns respective VMD.Molecule instance. 219 | """ 220 | return _Molecule(id=self.molid) 221 | 222 | def _get_frame(self): 223 | return _molecule.get_frame(self.molid) 224 | 225 | def _set_frame(self, frame): 226 | """ 227 | Sets the active frame 228 | 229 | @type frame: Non-negative integer 230 | """ 231 | assert frame >= 0 232 | _molecule.set_frame(self.molid, frame) 233 | 234 | frame = property(_get_frame, _set_frame, doc="Molecule's frame") 235 | 236 | @property 237 | def frames(self): 238 | """ 239 | Returns frames descriptor. 240 | """ 241 | return Frames(self) 242 | 243 | def _get_name(self): 244 | return _molecule.name(self.molid) 245 | 246 | def _set_name(self, name): 247 | _molecule.rename(self.molid, name) 248 | 249 | name = property(_get_name, _set_name, doc="Molecule's name") 250 | 251 | def _get_visible(self): 252 | return _molecule.get_visible(self.molid) 253 | 254 | def _set_visible(self, value): 255 | """ 256 | Sets molecule visibility 257 | 258 | @type value: Boolean 259 | """ 260 | _molecule.set_visible(self.molid, value) 261 | 262 | visible = property(_get_visible, _set_visible, doc="Visibility") 263 | 264 | @property 265 | def representations(self): 266 | """ 267 | Returns molecule representations manager. 268 | """ 269 | return RepresentationManager(self) 270 | 271 | 272 | class MoleculeManager(object): 273 | """ 274 | Manager of all molecules. 275 | """ 276 | def __init__(self): 277 | self._names = {} 278 | # Fill the cache 279 | self._update() 280 | 281 | def _update(self): 282 | # Update the name cache 283 | cache = {} 284 | for molid in _molecule.listall(): 285 | name = _molecule.name(molid) 286 | cache.setdefault(name, molid) 287 | self._names = cache 288 | 289 | def __len__(self): 290 | return _molecule.num() 291 | 292 | def __getitem__(self, key): 293 | """ 294 | Returns molecule specified by name or molid. 295 | 296 | If name is not unique, the molecule returned is not defined. 297 | 298 | @type key: int or str 299 | @rtype: Molecule 300 | """ 301 | if isinstance(key, int): 302 | assert key >= 0 303 | if _molecule.exists(key): 304 | return Molecule(key) 305 | else: 306 | raise ValueError("Molecule %d doesn't exist." % key) 307 | elif isinstance(key, basestring): 308 | # First check the cached names 309 | if key in self._names: 310 | molid = self._names[key] 311 | if _molecule.exists(molid): 312 | return Molecule(molid) 313 | else: 314 | # The record in cache is obsolete 315 | del self._names[key] 316 | 317 | # No luck so far, update the cache 318 | self._update() 319 | 320 | if key in self._names: 321 | # We found it after update. Do not check the existence again, we just updated the cache. 322 | return Molecule(self._names[key]) 323 | else: 324 | raise ValueError("Molecule '%s' doesn't exist." % key) 325 | else: 326 | raise TypeError("%s indices must be integers or strings, not %s" % (type(self), type(key))) 327 | 328 | def __delitem__(self, key): 329 | """ 330 | Deletes molecule specified by name or molid. 331 | 332 | If name is not unique, the molecule deleted is not defined. 333 | 334 | @type key: int or str 335 | """ 336 | # Use __getitem__ to find out which molecule is to be deleted. 337 | molecule = self.__getitem__(key) 338 | # Clean the cache 339 | self._names.pop(molecule.name) 340 | # Delete molecule 341 | _molecule.delete(molecule.molid) 342 | 343 | def __iter__(self): 344 | for molid in _molecule.listall(): 345 | yield Molecule(molid) 346 | 347 | def __contains__(self, molecule): 348 | return _molecule.exists(molecule.molid) 349 | 350 | def _get_top(self): 351 | molid = _molecule.get_top() 352 | # If there are no molecules, VMD returns -1 as molid. 353 | if molid < 0: 354 | raise ValueError("There are no molecules.") 355 | return Molecule(molid) 356 | 357 | def _set_top(self, molecule): 358 | _molecule.set_top(molecule.molid) 359 | 360 | top = property(_get_top, _set_top, doc="Top molecule") 361 | 362 | 363 | MOLECULES = MoleculeManager() 364 | -------------------------------------------------------------------------------- /pyvmd/representations.py: -------------------------------------------------------------------------------- 1 | """ 2 | Objects for manipulation with VMD molecule representations. 3 | """ 4 | from collections import namedtuple 5 | 6 | from VMD import molrep as _molrep 7 | 8 | from pyvmd.atoms import Selection 9 | from pyvmd.molecules import Molecule, MOLECULES 10 | 11 | __all__ = ['COLOR_BACKBONE', 'COLOR_BETA', 'COLOR_CHAIN', 'COLOR_CHARGE', 'COLOR_COLOR', 'COLOR_CONFORMATION', 12 | 'COLOR_ELEMENT', 'COLOR_FRAGMENT', 'COLOR_INDEX', 'COLOR_MASS', 'COLOR_MOLECULE', 'COLOR_NAME', 13 | 'COLOR_OCCUPANCY', 'COLOR_PHYSICAL_TIME', 'COLOR_POS', 'COLOR_POS_X', 'COLOR_POS_Y', 'COLOR_POS_Z', 14 | 'COLOR_RESID', 'COLOR_RESNAME', 'COLOR_RESTYPE', 'COLOR_SEGNAME', 'COLOR_STRUCTURE', 'COLOR_THROB', 15 | 'COLOR_TIMESTEP', 'COLOR_TYPE', 'COLOR_USER', 'COLOR_USER_2', 'COLOR_USER_3', 'COLOR_USER_4', 16 | 'COLOR_VELOCITY', 'COLOR_VOLUME', 'COLORING_METHODS', 'DRAW_BEADS', 'DRAW_BONDS', 'DRAW_CARTOON', 'DRAW_CPK', 17 | 'DRAW_DOTTED', 'DRAW_DYNAMIC_BONDS', 'DRAW_FIELD_LINES', 'DRAW_HBONDS', 'DRAW_ISOSURFACE', 'DRAW_LICORICE', 18 | 'DRAW_LINES', 'DRAW_MSMS', 'DRAW_NEW_CARTOON', 'DRAW_NEW_RIBBONS', 'DRAW_ORBITAL', 'DRAW_PAPER_CHAIN', 19 | 'DRAW_POINTS', 'DRAW_POLYHEDRA', 'DRAW_QUICKSURF', 'DRAW_RIBBONS', 'DRAW_SOLVENT', 'DRAW_SURF', 'DRAW_TRACE', 20 | 'DRAW_TUBE', 'DRAW_TWISTER', 'DRAW_VDW', 'DRAW_VOLUME_SLICE', 'DRAWING_METHODS', 'Representation'] 21 | 22 | 23 | def _modrep(representation, **kwargs): 24 | """ 25 | Utility wrapper over `modrep` to raise exception in case of an error. 26 | """ 27 | result = _molrep.modrep(representation.molecule.molid, representation.repindex, **kwargs) 28 | if not result: 29 | raise ValueError("Error occured while changing molecule representation.") 30 | 31 | 32 | ################################################################################ 33 | # Drawing methods 34 | # 35 | # XXX: There is no way to get default parameters for styles from VMD, so we have to define them ourselves. 36 | # name: VMD style keyword 37 | # parameters: List of paramater names. Customized because there is no definition of those in VMD. 38 | # defaults: Dictionary with default values. Values are copied from AtomRep.C. 39 | DrawingMethod = namedtuple('DrawingMethod', ('name', 'parameters', 'defaults')) 40 | # TODO: Even though VMD handles all parameters as floats, some of them actually behaves differently, e.g. as a bool. 41 | # High level interface implemented here should honor the actual behavior of the parameters and interpret float values 42 | # correctly. 43 | 44 | # Drawing methods as defined in AtomRep.C 45 | DRAW_LINES = DrawingMethod('Lines', ('size', ), {'size': 1.}) 46 | DRAW_BONDS = DrawingMethod('Bonds', ('size', 'resolution'), {'size': 0.3, 'resolution': 10}) 47 | DRAW_DYNAMIC_BONDS = DrawingMethod('DynamicBonds', ('cutoff', 'size', 'resolution'), 48 | {'cutoff': 3., 'size': 0.3, 'resolution': 10}) 49 | DRAW_HBONDS = DrawingMethod('HBonds', ('cutoff', 'angle_cutoff', 'size'), 50 | {'cutoff': 3., 'angle_cutoff': 20., 'size': 1.}) 51 | DRAW_POINTS = DrawingMethod('Points', ('size', ), {'size': 1.}) 52 | DRAW_VDW = DrawingMethod('VDW', ('size', 'resolution'), {'size': 1., 'resolution': 12}) 53 | DRAW_CPK = DrawingMethod('CPK', ('size', 'bond_radius', 'resolution', 'bond_resolution'), 54 | {'size': 1., 'bond_radius': 0.3, 'resolution': 12, 'bond_resolution': 10}) 55 | DRAW_LICORICE = DrawingMethod('Licorice', ('size', 'resolution', 'bond_resolution'), 56 | {'size': 0.3, 'resolution': 10, 'bond_resolution': 10}) 57 | DRAW_POLYHEDRA = DrawingMethod('Polyhedra', ('cutoff', ), {'cutoff': 3.}) 58 | DRAW_TRACE = DrawingMethod('Trace', ('size', 'resolution'), {'size': 0.3, 'resolution': 10}) 59 | DRAW_TUBE = DrawingMethod('Tube', ('size', 'resolution'), {'size': 0.3, 'resolution': 10}) 60 | DRAW_RIBBONS = DrawingMethod('Ribbons', ('margin_size', 'resolution', 'size'), 61 | {'margin_size': 0.3, 'resolution': 10, 'size': 2.}) 62 | # TODO: spline constants 63 | DRAW_NEW_RIBBONS = DrawingMethod('NewRibbons', ('thickness', 'resolution', 'width', 'spline'), 64 | {'thickness': 0.3, 'resolution': 10, 'width': 3., 'spline': 0}) 65 | DRAW_CARTOON = DrawingMethod('Cartoon', ('helix_size', 'resolution', 'sheet_size'), 66 | {'helix_size': 2.1, 'resolution': 12, 'sheet_size': 5.}) 67 | # TODO: spline constants 68 | DRAW_NEW_CARTOON = DrawingMethod('NewCartoon', ('thickness', 'resolution', 'width', 'spline'), 69 | {'thickness': 0.3, 'resolution': 10, 'width': 4.5, 'spline': 0}) 70 | DRAW_PAPER_CHAIN = DrawingMethod('PaperChain', ('height', 'max_ring_size'), {'height': 1., 'max_ring_size': 10}) 71 | # TODO: start, hide_shared_links constants 72 | DRAW_TWISTER = DrawingMethod( 73 | 'Twister', 74 | ('start', 'hide_shared_links', 'steps', 'width', 'height', 'max_ring_size', 'linking_distance'), 75 | {'start': 1, 'hide_shared_links': 0, 'steps': 10, 'width': 0.3, 'height': 0.05, 'max_ring_size': 10, 76 | 'linking_distance': 5}, 77 | ) 78 | DRAW_QUICKSURF = DrawingMethod('QuickSurf', ('sphere_scale', 'density_isovalue', 'grid_spacing', 'resolution'), 79 | {'sphere_scale': 1., 'density_isovalue': 0.5, 'grid_spacing': 1., 'resolution': 0}) 80 | # TODO: selection, method constants 81 | DRAW_MSMS = DrawingMethod('MSMS', ('probe_radius', 'density', 'atoms', 'method'), 82 | {'probe_radius': 1.5, 'density': 1.5, 'atoms': 0, 'method': 0}) 83 | # TODO: method constant 84 | DRAW_SURF = DrawingMethod('Surf', ('probe_radius', 'method'), {'probe_radius': 1.4, 'method': 0}) 85 | # TODO: axis, quality constants 86 | DRAW_VOLUME_SLICE = DrawingMethod('VolumeSlice', ('slice', 'volume_id', 'axis', 'quality'), 87 | {'slice': 0.5, 'volume_id': 0, 'axis': 0, 'quality': 2}) 88 | # TODO: display, method constants 89 | DRAW_ISOSURFACE = DrawingMethod('Isosurface', ('isovalue', 'volume_id', 'display', 'method', 'step', 'size'), 90 | {'isovalue': 0.5, 'volume_id': 0, 'display': 2, 'method': 2, 'step': 1, 'size': 1}) 91 | DRAW_FIELD_LINES = DrawingMethod('FieldLines', ('volume_id', 'gradient', 'min_length', 'max_length', 'size'), 92 | {'volume_id': 0, 'gradient': 1.8, 'min_length': 10, 'max_length': 50, 'size': 1}) 93 | # TODO: display, method, wavefunction, spin constants 94 | DRAW_ORBITAL = DrawingMethod( 95 | 'Orbital', 96 | ('isovalue', 'orbital_id', 'display', 'method', 'grid_spacing', 'size', 'wavefunction', 'spin', 'excitation', 97 | 'step'), 98 | {'isovalue': 0.05, 'orbital_id': 0, 'display': 0, 'method': 0, 'grid_spacing': 0.075, 'size': 1, 'wavefunction': 0, 99 | 'spin': 0, 'excitation': 0, 'step': 1} 100 | ) 101 | DRAW_BEADS = DrawingMethod('Beads', ('size', 'resolution'), {'size': 1., 'resolution': 12}) 102 | DRAW_DOTTED = DrawingMethod('Dotted', ('size', 'resolution'), {'size': 1., 'resolution': 12}) 103 | # TODO: method constants 104 | DRAW_SOLVENT = DrawingMethod('Solvent', ('probe_radius', 'detail', 'method'), 105 | {'probe_radius': 0, 'detail': 7, 'method': 1}) 106 | 107 | DRAWING_METHODS = (DRAW_LINES, DRAW_BONDS, DRAW_DYNAMIC_BONDS, DRAW_HBONDS, DRAW_POINTS, DRAW_VDW, DRAW_CPK, 108 | DRAW_LICORICE, DRAW_POLYHEDRA, DRAW_TRACE, DRAW_TUBE, DRAW_RIBBONS, DRAW_NEW_RIBBONS, DRAW_CARTOON, 109 | DRAW_NEW_CARTOON, DRAW_PAPER_CHAIN, DRAW_TWISTER, DRAW_QUICKSURF, DRAW_MSMS, DRAW_SURF, 110 | DRAW_VOLUME_SLICE, DRAW_ISOSURFACE, DRAW_FIELD_LINES, DRAW_ORBITAL, DRAW_BEADS, DRAW_DOTTED, 111 | DRAW_SOLVENT) 112 | DRAWING_METHODS_MAP = {m.name: m for m in DRAWING_METHODS} 113 | 114 | 115 | def _parse_raw_style(raw_style): 116 | """ 117 | Parses raw style string and returns drawing method and options. 118 | """ 119 | assert raw_style 120 | chunks = raw_style.split() 121 | method_name = chunks.pop(0) 122 | if method_name not in DRAWING_METHODS_MAP: 123 | raise ValueError('Unknown style: %s' % raw_style) 124 | method = DRAWING_METHODS_MAP[method_name] 125 | # If parameters are set to default, some of them may be missing from `raw_style`. Return complete set of parameters. 126 | params = method.defaults.copy() 127 | params.update({p: float(v) for p, v in zip(method.parameters, chunks)}) 128 | return method, params 129 | 130 | 131 | class Style(object): 132 | """ 133 | Represents drawing method and its parameters of a particular representaiton. 134 | 135 | This class is a proxy for molecule represenation style in VMD. 136 | """ 137 | def __init__(self, representation): 138 | assert isinstance(representation, Representation) 139 | self.representation = representation 140 | 141 | def __repr__(self): 142 | return "<%s: '%r'>" % (type(self).__name__, self.representation) 143 | 144 | def __eq__(self, other): 145 | return type(self) == type(other) and self.representation == other.representation 146 | 147 | def __ne__(self, other): 148 | return not self.__eq__(other) 149 | 150 | ############################################################################ 151 | # Style properties 152 | def _get_method(self): 153 | raw_style = _molrep.get_style(self.representation.molecule.molid, self.representation.repindex) 154 | return _parse_raw_style(raw_style)[0] 155 | 156 | def _set_method(self, method): 157 | # Only assert. There is no function to get the list of available drawing methods, so just send it to VMD. 158 | assert method in DRAWING_METHODS 159 | _modrep(self.representation, style=method.name) 160 | 161 | method = property(_get_method, _set_method, doc="Drawing method") 162 | 163 | def get_parameters(self): 164 | """Returns drawing parameters""" 165 | raw_style = _molrep.get_style(self.representation.molecule.molid, self.representation.repindex) 166 | return _parse_raw_style(raw_style)[1] 167 | 168 | def set_parameters(self, **kwargs): 169 | """Sets drawing parameters""" 170 | if not kwargs: 171 | raise ValueError('At least one parameter is required.') 172 | method = self.method 173 | if set(kwargs) - set(method.parameters): 174 | raise ValueError('Unknown parameters: %s' % (set(kwargs) - set(method.parameters))) 175 | # Get current parameters and update with modifications 176 | params = self.get_parameters() 177 | params.update(kwargs) 178 | # Construct style string 179 | data = (str(params[p]) for p in method.parameters) 180 | style_str = '%s %s' % (method.name, ' '.join(data)) 181 | # Set the style in VMD 182 | _modrep(self.representation, style=style_str) 183 | 184 | 185 | ################################################################################ 186 | # Color methods 187 | # 188 | # name: VMD color keyword 189 | # parameters: List of paramater names. Customized because there is no definition of those in VMD. 190 | # defaults: Dictionary with default values. 191 | ColoringMethod = namedtuple('ColoringMethod', ('name', 'parameters', 'defaults')) 192 | 193 | # Color methods as defined in AtomColor.C 194 | COLOR_NAME = ColoringMethod("Name", (), {}) 195 | COLOR_TYPE = ColoringMethod("Type", (), {}) 196 | COLOR_ELEMENT = ColoringMethod("Element", (), {}) 197 | COLOR_RESNAME = ColoringMethod("ResName", (), {}) 198 | COLOR_RESTYPE = ColoringMethod("ResType", (), {}) 199 | COLOR_RESID = ColoringMethod("ResID", (), {}) 200 | COLOR_CHAIN = ColoringMethod("Chain", (), {}) 201 | COLOR_SEGNAME = ColoringMethod("SegName", (), {}) 202 | COLOR_CONFORMATION = ColoringMethod("Conformation", (), {}) 203 | COLOR_MOLECULE = ColoringMethod("Molecule", (), {}) 204 | COLOR_STRUCTURE = ColoringMethod("Structure", (), {}) 205 | COLOR_COLOR = ColoringMethod("ColorID", ('color', ), {'color': 1}) 206 | COLOR_BETA = ColoringMethod("Beta", (), {}) 207 | COLOR_OCCUPANCY = ColoringMethod("Occupancy", (), {}) 208 | COLOR_MASS = ColoringMethod("Mass", (), {}) 209 | COLOR_CHARGE = ColoringMethod("Charge", (), {}) 210 | COLOR_POS = ColoringMethod("Pos", (), {}) 211 | COLOR_POS_X = ColoringMethod("PosX", (), {}) 212 | COLOR_POS_Y = ColoringMethod("PosY", (), {}) 213 | COLOR_POS_Z = ColoringMethod("PosZ", (), {}) 214 | COLOR_USER = ColoringMethod("User", (), {}) 215 | COLOR_USER_2 = ColoringMethod("User2", (), {}) 216 | COLOR_USER_3 = ColoringMethod("User3", (), {}) 217 | COLOR_USER_4 = ColoringMethod("User4", (), {}) 218 | COLOR_FRAGMENT = ColoringMethod("Fragment", (), {}) 219 | COLOR_INDEX = ColoringMethod("Index", (), {}) 220 | COLOR_BACKBONE = ColoringMethod("Backbone", (), {}) 221 | COLOR_THROB = ColoringMethod("Throb", (), {}) 222 | COLOR_PHYSICAL_TIME = ColoringMethod("PhysicalTime", (), {}) 223 | COLOR_TIMESTEP = ColoringMethod("Timestep", (), {}) 224 | COLOR_VELOCITY = ColoringMethod("Velocity", (), {}) 225 | COLOR_VOLUME = ColoringMethod("Volume", ('volume_id', ), {'volume_id': 0}) 226 | 227 | COLORING_METHODS = (COLOR_NAME, COLOR_TYPE, COLOR_ELEMENT, COLOR_RESNAME, COLOR_RESTYPE, COLOR_RESID, COLOR_CHAIN, 228 | COLOR_SEGNAME, COLOR_CONFORMATION, COLOR_MOLECULE, COLOR_STRUCTURE, COLOR_COLOR, COLOR_BETA, 229 | COLOR_OCCUPANCY, COLOR_MASS, COLOR_CHARGE, COLOR_POS, COLOR_POS_X, COLOR_POS_Y, COLOR_POS_Z, 230 | COLOR_USER, COLOR_USER_2, COLOR_USER_3, COLOR_USER_4, COLOR_FRAGMENT, COLOR_INDEX, COLOR_BACKBONE, 231 | COLOR_THROB, COLOR_PHYSICAL_TIME, COLOR_TIMESTEP, COLOR_VELOCITY, COLOR_VOLUME) 232 | COLORING_METHODS_MAP = {m.name: m for m in COLORING_METHODS} 233 | 234 | 235 | def _parse_raw_color(raw_color): 236 | """ 237 | Parses raw color string and returns coloring method and options. 238 | """ 239 | assert raw_color 240 | chunks = raw_color.split() 241 | method_name = chunks.pop(0) 242 | if method_name not in COLORING_METHODS_MAP: 243 | raise ValueError('Unknown color: %s' % raw_color) 244 | method = COLORING_METHODS_MAP[method_name] 245 | params = {p: int(v) for p, v in zip(method.parameters, chunks)} 246 | return method, params 247 | 248 | 249 | class Color(object): 250 | """ 251 | Represents coloring method and its parameters of a particular representaiton. 252 | 253 | This class is a proxy for molecule represenation color in VMD. 254 | """ 255 | def __init__(self, representation): 256 | assert isinstance(representation, Representation) 257 | self.representation = representation 258 | 259 | def __repr__(self): 260 | return "<%s: '%r'>" % (type(self).__name__, self.representation) 261 | 262 | def __eq__(self, other): 263 | return type(self) == type(other) and self.representation == other.representation 264 | 265 | def __ne__(self, other): 266 | return not self.__eq__(other) 267 | 268 | ############################################################################ 269 | # Color properties 270 | def _set_color(self, method, parameters): 271 | # Utility method to format and set the color with all parameters. 272 | # Construct color string 273 | if method.parameters: 274 | data = (str(parameters[p]) for p in method.parameters) 275 | color_str = '%s %s' % (method.name, ' '.join(data)) 276 | else: 277 | color_str = method.name 278 | # Set the style in VMD 279 | _modrep(self.representation, color=color_str) 280 | 281 | def _get_method(self): 282 | raw_color = _molrep.get_color(self.representation.molecule.molid, self.representation.repindex) 283 | return _parse_raw_color(raw_color)[0] 284 | 285 | def _set_method(self, method): 286 | # Only assert. There is no function to get the list of available coloring methods, so just send it to VMD. 287 | assert method in COLORING_METHODS 288 | self._set_color(method, method.defaults) 289 | 290 | method = property(_get_method, _set_method, doc="Coloring method") 291 | 292 | def get_parameters(self): 293 | """Returns coloring parameters""" 294 | raw_color = _molrep.get_color(self.representation.molecule.molid, self.representation.repindex) 295 | return _parse_raw_color(raw_color)[1] 296 | 297 | def set_parameters(self, **kwargs): 298 | """Sets coloring parameters""" 299 | if not kwargs: 300 | raise ValueError('At least one parameter is required.') 301 | method = self.method 302 | if set(kwargs) - set(method.parameters): 303 | raise ValueError('Unknown parameters: %s' % (set(kwargs) - set(method.parameters))) 304 | # Get current parameters and update with modifications 305 | params = self.get_parameters() 306 | params.update(kwargs) 307 | self._set_color(method, params) 308 | 309 | 310 | ################################################################################ 311 | # Representation 312 | # 313 | class Representation(object): 314 | """ 315 | Representation of molecule graphical representation. 316 | 317 | This class is a proxy for molecule represenation in VMD. 318 | """ 319 | def __init__(self, name, molecule=None): 320 | """ 321 | Creates a molecule representation. 322 | 323 | @param name: Representation identifier. 324 | @type name: String 325 | @param molecule: Molecule to select from. Top if not provided. 326 | @type molecule: Molecule or None 327 | """ 328 | if molecule is None: 329 | molecule = MOLECULES.top 330 | else: 331 | assert isinstance(molecule, Molecule) 332 | self._molecule = molecule 333 | 334 | self._name = name 335 | # If the representation does not exists, complain immediately 336 | self.repindex 337 | 338 | def __repr__(self): 339 | return "<%s: '%s' of '%r'>" % (type(self).__name__, self._name, self._molecule) 340 | 341 | def __eq__(self, other): 342 | return type(self) == type(other) and self._molecule == other.molecule and self._name == other.name 343 | 344 | def __ne__(self, other): 345 | return not self.__eq__(other) 346 | 347 | @property 348 | def name(self): 349 | """Index""" 350 | return self._name 351 | 352 | @property 353 | def molecule(self): 354 | """Molecule""" 355 | return self._molecule 356 | 357 | @property 358 | def repindex(self): 359 | """Utility property to facilitate the VMD calls.""" 360 | repindex = _molrep.repindex(self._molecule.molid, self._name) 361 | if repindex < 0: 362 | raise ValueError("Molecule %s does not have a representation '%s'." % (self._molecule, self._name)) 363 | return repindex 364 | 365 | @classmethod 366 | def create(cls, molecule=None): 367 | """ 368 | Creates new molecule representation. 369 | 370 | @param molecule: Molecule to select from. Top if not provided. 371 | @type molecule: Molecule or None 372 | """ 373 | if molecule is None: 374 | molecule = MOLECULES.top 375 | else: 376 | assert isinstance(molecule, Molecule) 377 | _molrep.addrep(molecule.molid) 378 | # XXX: `addrep` has no return value, get the index of the newly created representation from their number. 379 | index = _molrep.num(molecule.molid) - 1 380 | name = _molrep.get_repname(molecule.molid, index) 381 | 382 | return cls(name, molecule) 383 | 384 | def delete(self): 385 | """ 386 | Deletes the molecule representation. 387 | """ 388 | _molrep.delrep(self._molecule.molid, self.repindex) 389 | 390 | ############################################################################ 391 | # Representation properties 392 | def _get_selection(self): 393 | return Selection(_molrep.get_selection(self._molecule.molid, self.repindex), self._molecule) 394 | 395 | def _set_selection(self, selection): 396 | assert isinstance(selection, Selection) 397 | assert self._molecule == selection.molecule 398 | _modrep(self, sel=selection.selection) 399 | 400 | selection = property(_get_selection, _set_selection, doc="Selection") 401 | 402 | def _get_visible(self): 403 | return _molrep.get_visible(self._molecule.molid, self.repindex) 404 | 405 | def _set_visible(self, value): 406 | """ 407 | Sets visibility 408 | 409 | @type value: Boolean 410 | """ 411 | _molrep.set_visible(self._molecule.molid, self.repindex, value) 412 | 413 | visible = property(_get_visible, _set_visible, doc="Visibility") 414 | 415 | def _get_update_selection(self): 416 | return _molrep.get_autoupdate(self._molecule.molid, self.repindex) 417 | 418 | def _set_update_selection(self, value): 419 | """ 420 | Sets selection update 421 | 422 | @type value: Boolean 423 | """ 424 | _molrep.set_autoupdate(self._molecule.molid, self.repindex, value) 425 | 426 | update_selection = property(_get_update_selection, _set_update_selection, doc="Update selection every frame") 427 | 428 | def _get_update_color(self): 429 | return _molrep.get_colorupdate(self._molecule.molid, self.repindex) 430 | 431 | def _set_update_color(self, value): 432 | """ 433 | Sets color update 434 | 435 | @type value: Boolean 436 | """ 437 | _molrep.set_colorupdate(self._molecule.molid, self.repindex, value) 438 | 439 | update_color = property(_get_update_color, _set_update_color, doc="Update color every frame") 440 | 441 | @property 442 | def style(self): 443 | """Graphical style""" 444 | return Style(self) 445 | 446 | @property 447 | def color(self): 448 | """Color""" 449 | return Color(self) 450 | -------------------------------------------------------------------------------- /pyvmd/tests/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Unittests for pyvmd. 3 | """ 4 | import os 5 | import sys 6 | import unittest 7 | 8 | if not hasattr(unittest, 'skip'): 9 | # XXX: Use unittest2 to use python 2.7 unittest features. 10 | import unittest2 as unittest 11 | 12 | 13 | def cover_main(): 14 | """ 15 | Run tests with coverage. 16 | """ 17 | import coverage 18 | cov = coverage.coverage(branch=True, source=['pyvmd']) 19 | cov.start() 20 | unit = unittest.main(exit=False) 21 | cov.stop() 22 | cov.save() 23 | 24 | if not hasattr(unit, 'result'): 25 | # Unittest help exit 26 | sys.exit(2) 27 | else: 28 | sys.exit(not unit.result.wasSuccessful()) 29 | 30 | 31 | if __name__ == '__main__': 32 | if os.environ.get('COVERAGE'): 33 | cover_main() 34 | else: 35 | unittest.main() 36 | -------------------------------------------------------------------------------- /pyvmd/tests/data/coords.dat: -------------------------------------------------------------------------------- 1 | frame data00001 data00002 data00003 data00004 data00005 data00006 2 | 0 -1.4912 -0.0017 1.9193 -0.0030 1.2571 0.0019 3 | 1 -1.4851 -0.0054 1.9324 -0.0119 1.2582 0.0066 4 | 2 -1.4858 -0.0066 1.9499 -0.0290 1.2598 0.0075 5 | 3 -1.4774 -0.0129 1.9872 -0.0436 1.2580 0.0065 6 | 4 -1.4746 -0.0243 2.0238 -0.0541 1.2560 0.0008 7 | 5 -1.4673 -0.0367 2.0644 -0.0589 1.2558 -0.0096 8 | 6 -1.4536 -0.0440 2.0939 -0.0643 1.2573 -0.0266 9 | 7 -1.4307 -0.0597 2.1291 -0.0617 1.2584 -0.0392 10 | 8 -1.4121 -0.0783 2.1494 -0.0559 1.2542 -0.0433 11 | 9 -1.3853 -0.0916 2.1623 -0.0481 1.2405 -0.0381 12 | 10 -1.3675 -0.0921 2.1716 -0.0464 1.2224 -0.0252 13 | 11 -1.3422 -0.0890 2.1780 -0.0451 1.1937 -0.0003 14 | -------------------------------------------------------------------------------- /pyvmd/tests/data/dataset.dat: -------------------------------------------------------------------------------- 1 | frame first second last 2 | 0 5 3.5000 2.9000 3 | 1 0 0.5000 5.9000 4 | 2 -42 0.0100 8.9000 5 | 3 -204 0.1000 15.8900 6 | -------------------------------------------------------------------------------- /pyvmd/tests/data/geometry.dat: -------------------------------------------------------------------------------- 1 | frame data00001 data00002 data00003 data00004 data00005 data00006 2 | 0 0.9646 2.6949 38.1194 40.5905 57.0542 -78.1901 3 | 1 0.9611 2.6712 38.2041 40.1118 61.5406 -78.7416 4 | 2 0.9662 2.6469 38.3418 39.8520 68.0926 -78.1408 5 | 3 0.9626 2.6293 38.3855 39.3075 75.0311 -78.3754 6 | 4 0.9650 2.6179 38.1109 39.0481 81.3051 -78.4223 7 | 5 0.9611 2.6195 37.7457 39.0767 86.0759 -78.0710 8 | 6 0.9658 2.6194 37.9470 39.5399 89.2662 -76.4035 9 | 7 0.9604 2.6107 38.6991 39.6788 93.1088 -75.8776 10 | 8 0.9672 2.5806 38.7137 40.0755 99.3121 -75.5436 11 | 9 0.9617 2.5403 38.4580 40.8015 110.2715 -75.5999 12 | 10 0.9633 2.4997 38.6055 42.0542 125.2943 -75.1918 13 | 11 0.9573 2.4476 39.1369 42.5542 142.6539 -76.4072 14 | -------------------------------------------------------------------------------- /pyvmd/tests/data/rmsd.dat: -------------------------------------------------------------------------------- 1 | frame data00001 data00002 noh 2 | 0 0.0290 0.0180 0.0180 3 | 1 0.0647 0.0273 0.0273 4 | 2 0.1138 0.0393 0.0393 5 | 3 0.1789 0.0645 0.0645 6 | 4 0.2454 0.0837 0.0837 7 | 5 0.3130 0.1041 0.1041 8 | 6 0.3830 0.1204 0.1204 9 | 7 0.4584 0.1453 0.1453 10 | 8 0.5350 0.1664 0.1664 11 | 9 0.6188 0.1941 0.1941 12 | 10 0.7013 0.2146 0.2146 13 | 11 0.7847 0.2464 0.2464 14 | -------------------------------------------------------------------------------- /pyvmd/tests/data/water.1.dcd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ziima/pyvmd/4f462f432258343b8af905a98c0da2f0e5187c80/pyvmd/tests/data/water.1.dcd -------------------------------------------------------------------------------- /pyvmd/tests/data/water.2.dcd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ziima/pyvmd/4f462f432258343b8af905a98c0da2f0e5187c80/pyvmd/tests/data/water.2.dcd -------------------------------------------------------------------------------- /pyvmd/tests/data/water.pdb: -------------------------------------------------------------------------------- 1 | REMARK original generated coordinate pdb file 2 | ATOM 1 OH2 TIP3W 1 -1.493 1.900 1.280 1.00 0.00 W1 O 3 | ATOM 2 H1 TIP3W 1 -1.736 1.952 0.301 1.00 0.00 W1 H 4 | ATOM 3 H2 TIP3W 1 -0.792 2.570 1.320 1.00 0.00 W1 H 5 | ATOM 4 OH2 TIP3W 2 0.337 -1.680 2.035 1.00 0.00 W1 O 6 | ATOM 5 H1 TIP3W 2 1.258 -1.583 1.863 1.00 0.00 W1 H 7 | ATOM 6 H2 TIP3W 2 0.232 -2.668 2.103 1.00 0.00 W1 H 8 | ATOM 7 OH2 TIP3W 3 0.452 1.432 -1.237 1.00 0.00 W1 O 9 | ATOM 8 H1 TIP3W 3 0.857 0.842 -1.865 1.00 0.00 W1 H 10 | ATOM 9 H2 TIP3W 3 1.206 1.500 -0.626 1.00 0.00 W1 H 11 | ATOM 10 OH2 TIP3W 4 -1.781 -0.509 -1.202 1.00 0.00 W1 O 12 | ATOM 11 H1 TIP3W 4 -1.519 -1.351 -1.608 1.00 0.00 W1 H 13 | ATOM 12 H2 TIP3W 4 -0.983 0.055 -1.213 1.00 0.00 W1 H 14 | ATOM 13 OH2 TIP3W 5 2.236 0.859 1.195 1.00 0.00 W2 O 15 | ATOM 14 H1 TIP3W 5 3.108 0.551 1.577 1.00 0.00 W2 H 16 | ATOM 15 H2 TIP3W 5 1.919 1.535 1.856 1.00 0.00 W2 H 17 | ATOM 16 OH2 TIP3Y 6 1.938 -0.616 -2.556 1.00 0.00 Y1 O 18 | ATOM 17 H1 TIP3Y 6 2.530 -0.448 -3.311 1.00 0.00 Y1 H 19 | ATOM 18 H2 TIP3Y 6 2.605 -0.877 -1.882 1.00 0.00 Y1 H 20 | ATOM 19 OH2 TIP3Y 7 -3.514 -1.504 0.854 1.00 0.00 Y1 O 21 | ATOM 20 H1 TIP3Y 7 -2.715 -1.095 0.443 1.00 0.00 Y1 H 22 | ATOM 21 H2 TIP3Y 7 -4.149 -0.855 0.670 1.00 0.00 Y1 H 23 | END 24 | -------------------------------------------------------------------------------- /pyvmd/tests/data/water.psf: -------------------------------------------------------------------------------- 1 | PSF 2 | 3 | 3 !NTITLE 4 | REMARKS original generated structure x-plor psf file 5 | REMARKS topology water_autopsf-temp.top 6 | REMARKS segment W1 { first NONE; last NONE; auto angles dihedrals } 7 | 8 | 21 !NATOM 9 | 1 W1 1 TIP3 OH2 OT -0.834000 15.9994 0 10 | 2 W1 1 TIP3 H1 HT 0.417000 1.0080 0 11 | 3 W1 1 TIP3 H2 HT 0.417000 1.0080 0 12 | 4 W1 2 TIP3 OH2 OT -0.834000 15.9994 0 13 | 5 W1 2 TIP3 H1 HT 0.417000 1.0080 0 14 | 6 W1 2 TIP3 H2 HT 0.417000 1.0080 0 15 | 7 W1 3 TIP3 OH2 OT -0.834000 15.9994 0 16 | 8 W1 3 TIP3 H1 HT 0.417000 1.0080 0 17 | 9 W1 3 TIP3 H2 HT 0.417000 1.0080 0 18 | 10 W1 4 TIP3 OH2 OT -0.834000 15.9994 0 19 | 11 W1 4 TIP3 H1 HT 0.417000 1.0080 0 20 | 12 W1 4 TIP3 H2 HT 0.417000 1.0080 0 21 | 13 W2 5 TIP3 OH2 OT -0.834000 15.9994 0 22 | 14 W2 5 TIP3 H1 HT 0.417000 1.0080 0 23 | 15 W2 5 TIP3 H2 HT 0.417000 1.0080 0 24 | 16 Y1 6 TIP3 OH2 OT -0.834000 15.9994 0 25 | 17 Y1 6 TIP3 H1 HT 0.417000 1.0080 0 26 | 18 Y1 6 TIP3 H2 HT 0.417000 1.0080 0 27 | 19 Y1 7 TIP3 OH2 OT -0.834000 15.9994 0 28 | 20 Y1 7 TIP3 H1 HT 0.417000 1.0080 0 29 | 21 Y1 7 TIP3 H2 HT 0.417000 1.0080 0 30 | 31 | 14 !NBOND: bonds 32 | 1 2 1 3 4 5 4 6 33 | 7 8 7 9 10 11 10 12 34 | 13 14 13 15 16 17 16 18 35 | 19 20 19 21 36 | 37 | 7 !NTHETA: angles 38 | 2 1 3 5 4 6 8 7 9 39 | 11 10 12 14 13 15 17 16 18 40 | 20 19 21 41 | 42 | 0 !NPHI: dihedrals 43 | 44 | 45 | 0 !NIMPHI: impropers 46 | 47 | 48 | 0 !NDON: donors 49 | 50 | 51 | 0 !NACC: acceptors 52 | 53 | 54 | 0 !NNB 55 | 56 | 0 0 0 0 0 0 0 0 57 | 0 0 0 0 0 0 0 0 58 | 0 0 0 0 0 59 | 60 | 1 0 !NGRP 61 | 0 0 0 62 | 63 | -------------------------------------------------------------------------------- /pyvmd/tests/test_analysis.py: -------------------------------------------------------------------------------- 1 | """ 2 | Tests for analysis. 3 | """ 4 | import VMD 5 | 6 | from pyvmd.analysis import hydrogen_bonds, HydrogenBond 7 | from pyvmd.atoms import Atom, Selection 8 | 9 | from .utils import data, PyvmdTestCase 10 | 11 | 12 | class TestAnalysis(PyvmdTestCase): 13 | """ 14 | Test analysis utilities. 15 | """ 16 | def setUp(self): 17 | molid = VMD.molecule.load('psf', data('water.psf'), 'pdb', data('water.pdb')) 18 | self.molid = molid 19 | 20 | def test_hydrogen_bonds(self): 21 | # Test `hydrogen_bonds` function 22 | sel = Selection('noh') 23 | 24 | result = [HydrogenBond(Atom(18), Atom(19), Atom(9)), HydrogenBond(Atom(9), Atom(11), Atom(6)), 25 | HydrogenBond(Atom(6), Atom(7), Atom(15))] 26 | self.assertEqual(list(hydrogen_bonds(sel)), result) 27 | self.assertEqual(list(hydrogen_bonds(sel, sel)), result) 28 | result = [HydrogenBond(Atom(18), Atom(19), Atom(9)), HydrogenBond(Atom(9), Atom(11), Atom(6)), 29 | HydrogenBond(Atom(6), Atom(7), Atom(9)), HydrogenBond(Atom(6), Atom(7), Atom(15))] 30 | self.assertEqual(list(hydrogen_bonds(sel, angle=75)), result) 31 | self.assertEqual(list(hydrogen_bonds(sel, angle=180)), []) 32 | self.assertEqual(list(hydrogen_bonds(sel, distance=2)), []) 33 | 34 | # If the selections do not share same atoms, check the hydrogen bonds are returned only in correct direction 35 | sel1 = Selection('index 6') 36 | sel2 = Selection('index 9') 37 | self.assertEqual(list(hydrogen_bonds(sel1, sel2, angle=75)), [HydrogenBond(Atom(6), Atom(7), Atom(9))]) 38 | -------------------------------------------------------------------------------- /pyvmd/tests/test_analyzer.py: -------------------------------------------------------------------------------- 1 | """ 2 | Tests for trajectory analysis utilities. 3 | """ 4 | import VMD 5 | from mock import sentinel 6 | 7 | from pyvmd.analyzer import Analyzer 8 | from pyvmd.molecules import Molecule 9 | 10 | from .utils import data, PyvmdTestCase 11 | 12 | 13 | class TestAnalyzer(PyvmdTestCase): 14 | """ 15 | Test `Analyzer` class. 16 | """ 17 | def setUp(self): 18 | self.mol = Molecule.create() 19 | self.mol.load(data('water.psf')) 20 | # Storage for callback data 21 | self.coords = [] 22 | self.frames = [] 23 | 24 | def test_analyze_callback_args(self): 25 | # Test callback is called with extra arguments 26 | steps = [] 27 | 28 | def callback(step, *args, **kwargs): 29 | self.assertEqual(step.molecule, self.mol) 30 | self.assertEqual(args, (sentinel.arg1, sentinel.arg2)) 31 | self.assertEqual(kwargs, {'key': sentinel.value, 'another': sentinel.junk}) 32 | steps.append(step.frame) 33 | 34 | analyzer = Analyzer(self.mol, [data('water.1.dcd')]) 35 | analyzer.add_callback(callback, sentinel.arg1, sentinel.arg2, key=sentinel.value, another=sentinel.junk) 36 | analyzer.analyze() 37 | 38 | self.assertEqual(steps, range(12)) 39 | 40 | def _get_status(self, status): 41 | # Callback to collect status data 42 | self.frames.append(status.frame) 43 | 44 | def _get_x(self, status): 45 | # Callback to collect data 46 | self.coords.append(VMD.atomsel.atomsel('index 0', molid=status.molecule.molid).get('x')[0]) 47 | 48 | def test_analyze(self): 49 | # Test analyzer works correctly with default parameters 50 | analyzer = Analyzer(self.mol, [data('water.1.dcd'), data('water.2.dcd')]) 51 | analyzer.add_callback(self._get_status) 52 | analyzer.add_callback(self._get_x) 53 | analyzer.analyze() 54 | result = [-1.4911567, -1.4851371, -1.4858487, -1.4773947, -1.4746015, -1.4673382, -1.4535547, -1.4307435, 55 | -1.4120502, -1.3853478, -1.3674825, -1.3421925, -1.3177859, -1.2816998, -1.2579591, -1.2262495, 56 | -1.2036057, -1.1834533, -1.174916, -1.1693807, -1.1705244, -1.1722997, -1.1759951, -1.175245] 57 | self.assertAlmostEqualSeqs(self.coords, result) 58 | self.assertEqual(self.frames, range(24)) 59 | 60 | def test_analyze_params(self): 61 | # Test load every other frame, all 12 at once 62 | self.coords = [] 63 | self.frames = [] 64 | analyzer = Analyzer(self.mol, [data('water.1.dcd'), data('water.2.dcd')], step=2, chunk=12) 65 | analyzer.add_callback(self._get_status) 66 | analyzer.add_callback(self._get_x) 67 | analyzer.analyze() 68 | result = [-1.4911567, -1.4858487, -1.4746015, -1.4535547, -1.4120502, -1.3674825, -1.3177859, -1.2579591, 69 | -1.2036057, -1.174916, -1.1705244, -1.1759951] 70 | self.assertAlmostEqualSeqs(self.coords, result) 71 | self.assertEqual(self.frames, range(12)) 72 | -------------------------------------------------------------------------------- /pyvmd/tests/test_atoms.py: -------------------------------------------------------------------------------- 1 | """ 2 | Tests for atom objects. 3 | """ 4 | import VMD 5 | from numpy import ndarray 6 | 7 | from pyvmd.atoms import Atom, Chain, NOW, Residue, Segment, Selection 8 | from pyvmd.molecules import Molecule 9 | 10 | from .utils import data, PyvmdTestCase 11 | 12 | 13 | class MoleculePropertyTest(PyvmdTestCase): 14 | """ 15 | Test `molecule` property in atom objects. 16 | """ 17 | def _test_molecule_property(self, obj_type, obj_id): 18 | # Test molecule property works correctly 19 | molid_1 = VMD.molecule.load('psf', data('water.psf'), 'pdb', data('water.pdb')) 20 | mol_1 = Molecule(molid_1) 21 | molid_2 = VMD.molecule.load('psf', data('water.psf'), 'pdb', data('water.pdb')) 22 | mol_2 = Molecule(molid_2) 23 | VMD.molecule.set_top(molid_1) 24 | 25 | # If molecule is not defined, top molecule is used 26 | obj = obj_type(obj_id) 27 | self.assertEqual(obj.molecule, mol_1) 28 | 29 | # If molecule is defined, it is used 30 | obj = obj_type(obj_id, mol_2) 31 | self.assertEqual(obj.molecule, mol_2) 32 | 33 | # Molecule can't be changed 34 | with self.assertRaises(AttributeError): 35 | obj.molecule = mol_2 36 | 37 | def test_atom(self): 38 | self._test_molecule_property(Atom, 0) 39 | 40 | def test_residue(self): 41 | self._test_molecule_property(Residue, 0) 42 | 43 | def test_chain(self): 44 | self._test_molecule_property(Chain, 'W') 45 | 46 | def test_segment(self): 47 | self._test_molecule_property(Segment, 'W1') 48 | 49 | def test_selection(self): 50 | self._test_molecule_property(Selection, 'index < 10') 51 | 52 | 53 | class FramePropertyTest(PyvmdTestCase): 54 | """ 55 | Test `frame` property in atom objects. 56 | """ 57 | def _test_frame_property(self, obj_type, obj_id, getter): 58 | # Test frame property works correctly 59 | molid = VMD.molecule.load('psf', data('water.psf'), 'pdb', data('water.pdb')) 60 | VMD.molecule.read(molid, 'dcd', data('water.1.dcd'), waitfor=-1) 61 | mol = Molecule(molid) 62 | # To check frame works correctly, check the 'x' coordinate of the object's first atom. 63 | 64 | # Create residue linked to the molecule frame 65 | obj = obj_type(obj_id) 66 | self.assertEqual(obj.frame, NOW) 67 | self.assertAlmostEqual(getter(obj), -1.3421925) 68 | # Change the molecule frame, the object will follow 69 | mol.frame = 0 70 | self.assertEqual(obj.frame, NOW) 71 | self.assertAlmostEqual(getter(obj), -1.493) 72 | mol.frame = 4 73 | self.assertEqual(obj.frame, NOW) 74 | self.assertAlmostEqual(getter(obj), -1.4773947) 75 | 76 | # Define the object's frame 77 | obj.frame = 9 78 | self.assertEqual(obj.frame, 9) 79 | self.assertAlmostEqual(getter(obj), -1.4120502) 80 | # Change the molecule frame, the object keeps its frame 81 | mol.frame = 11 82 | self.assertEqual(obj.frame, 9) 83 | self.assertAlmostEqual(getter(obj), -1.4120502) 84 | 85 | # Set the object's frame back to the molecule's frame 86 | obj.frame = NOW 87 | self.assertEqual(obj.frame, NOW) 88 | self.assertAlmostEqual(getter(obj), -1.3674825) 89 | 90 | def test_atom(self): 91 | self._test_frame_property(Atom, 0, lambda obj: obj.x) 92 | 93 | def test_residue(self): 94 | self._test_frame_property(Residue, 0, lambda obj: list(obj)[0].x) 95 | 96 | def test_chain(self): 97 | self._test_frame_property(Chain, 'W', lambda obj: list(obj)[0].x) 98 | 99 | def test_segment(self): 100 | self._test_frame_property(Segment, 'W1', lambda obj: list(obj)[0].x) 101 | 102 | def test_selection(self): 103 | self._test_frame_property(Selection, 'index < 10', lambda obj: list(obj)[0].x) 104 | 105 | 106 | class ComparisonTest(PyvmdTestCase): 107 | """ 108 | Test comparison of atom objects. 109 | """ 110 | def _test_equality(self, obj_type, obj_id1, obj_id2): 111 | molid_1 = VMD.molecule.load('psf', data('water.psf'), 'pdb', data('water.pdb')) 112 | mol_1 = Molecule(molid_1) 113 | molid_2 = VMD.molecule.load('psf', data('water.psf'), 'pdb', data('water.pdb')) 114 | mol_2 = Molecule(molid_2) 115 | VMD.molecule.set_top(molid_1) 116 | 117 | obj1 = obj_type(obj_id1) 118 | obj2 = obj_type(obj_id1) 119 | other = obj_type(obj_id2) 120 | frame = obj_type(obj_id1, frame=0) 121 | same_mol = obj_type(obj_id1, mol_1) 122 | other_mol = obj_type(obj_id1, mol_2) 123 | 124 | self.assertEqual(obj1, obj1) 125 | self.assertEqual(obj1, obj2) 126 | self.assertEqual(obj1, same_mol) 127 | self.assertNotEqual(obj1, other) 128 | self.assertNotEqual(obj2, other) 129 | self.assertNotEqual(obj1, frame) 130 | self.assertNotEqual(obj1, other_mol) 131 | 132 | def test_atom(self): 133 | self._test_equality(Atom, 0, 1) 134 | 135 | def test_residue(self): 136 | self._test_equality(Residue, 0, 1) 137 | 138 | def test_chain(self): 139 | self._test_equality(Chain, 'W', 'X') 140 | 141 | def test_segment(self): 142 | self._test_equality(Segment, 'W1', 'X') 143 | 144 | def test_selection(self): 145 | # Even though the selection texts differ only literally, there is no interface that we can use to 146 | # tell us whether the two selection texts corresponds to the same selection, so we consider them different. 147 | self._test_equality(Selection, 'all', '(all)') 148 | 149 | def test_different_objects(self): 150 | VMD.molecule.load('psf', data('water.psf'), 'pdb', data('water.pdb')) 151 | 152 | self.assertNotEqual(Atom(0), Residue(0)) 153 | self.assertNotEqual(Chain('X'), Segment('X')) 154 | 155 | 156 | class HashabilityTest(PyvmdTestCase): 157 | """ 158 | Test hashability of atoms and residues. 159 | """ 160 | def _test_hashability(self, obj_type): 161 | # Test objects are correctly understood by sets 162 | molid_1 = VMD.molecule.load('psf', data('water.psf'), 'pdb', data('water.pdb')) 163 | mol_1 = Molecule(molid_1) 164 | molid_2 = VMD.molecule.load('psf', data('water.psf'), 'pdb', data('water.pdb')) 165 | mol_2 = Molecule(molid_2) 166 | VMD.molecule.set_top(molid_1) 167 | 168 | # Same objects 169 | self.assertEqual({obj_type(0), obj_type(0)}, {obj_type(0)}) 170 | self.assertEqual({obj_type(0, frame=0), obj_type(0, frame=0)}, {obj_type(0, frame=0)}) 171 | self.assertEqual({obj_type(0, mol_1), obj_type(0, mol_1)}, {obj_type(0, mol_1)}) 172 | # Top molecule explicitely and implicitly 173 | self.assertEqual({obj_type(0), obj_type(0, mol_1)}, {obj_type(0)}) 174 | # Frame differs 175 | self.assertEqual({obj_type(0), obj_type(0, frame=0)}, {obj_type(0), obj_type(0, frame=0)}) 176 | # Molecule differs 177 | self.assertEqual({obj_type(0, mol_1), obj_type(0, mol_2)}, {obj_type(0, mol_1), obj_type(0, mol_2)}) 178 | 179 | def test_atom(self): 180 | self._test_hashability(Atom) 181 | 182 | def test_residue(self): 183 | self._test_hashability(Residue) 184 | 185 | 186 | class ContainerTest(PyvmdTestCase): 187 | """ 188 | Test container API for objects which supports it. 189 | """ 190 | def _test_container(self, obj_type, obj_id, length, atom_ids, out_ids): 191 | molid_1 = VMD.molecule.load('psf', data('water.psf'), 'pdb', data('water.pdb')) 192 | molid_2 = VMD.molecule.load('psf', data('water.psf'), 'pdb', data('water.pdb')) 193 | mol_2 = Molecule(molid_2) 194 | VMD.molecule.set_top(molid_1) 195 | 196 | obj = obj_type(obj_id) 197 | 198 | # Check __len__ 199 | self.assertEqual(len(obj), length) 200 | 201 | # Check __iter__ 202 | self.assertEqual(list(obj), [Atom(i) for i in atom_ids]) 203 | 204 | # Check __contains__ 205 | for atom_id in atom_ids: 206 | self.assertIn(Atom(atom_id), obj) 207 | for atom_id in atom_ids: 208 | # Frame differs 209 | self.assertNotIn(Atom(atom_id, frame=0), obj) 210 | for atom_id in atom_ids: 211 | # Molecule differs 212 | self.assertNotIn(Atom(atom_id, mol_2), obj) 213 | for atom_id in out_ids: 214 | self.assertNotIn(Atom(atom_id), obj) 215 | 216 | def test_residue(self): 217 | self._test_container(Residue, 0, 3, range(3), range(4, 21)) 218 | 219 | def test_chain(self): 220 | self._test_container(Chain, 'W', 15, range(15), range(16, 21)) 221 | 222 | def test_empty_chain(self): 223 | self._test_container(Chain, 'X', 0, [], range(21)) 224 | 225 | def test_segment(self): 226 | self._test_container(Segment, 'W1', 12, range(12), range(13, 21)) 227 | 228 | def test_empty_segment(self): 229 | self._test_container(Segment, 'X', 0, [], range(21)) 230 | 231 | def test_selection(self): 232 | self._test_container(Selection, 'resid 1 to 3', 9, range(9), range(10, 21)) 233 | 234 | def test_empty_selection(self): 235 | self._test_container(Selection, 'none', 0, [], range(21)) 236 | 237 | 238 | class TestAtom(PyvmdTestCase): 239 | """ 240 | Test `Atom` class. 241 | """ 242 | def test_properties(self): 243 | # Test getters and setters 244 | molid = VMD.molecule.load('psf', data('water.psf'), 'pdb', data('water.pdb')) 245 | 246 | atom = Atom(0) 247 | # Test getters 248 | self.assertEqual(atom.index, 0) 249 | self.assertAlmostEqual(atom.x, -1.493) 250 | self.assertAlmostEqual(atom.y, 1.900) 251 | self.assertAlmostEqual(atom.z, 1.280) 252 | self.assertIsInstance(atom.coords, ndarray) 253 | self.assertAlmostEqualSeqs(list(atom.coords), [-1.493, 1.9, 1.28]) 254 | self.assertEqual(atom.name, 'OH2') 255 | self.assertEqual(atom.type, 'OT') 256 | self.assertEqual(atom.element, 'O') 257 | self.assertAlmostEqual(atom.beta, 0.0) 258 | self.assertAlmostEqual(atom.occupancy, 1.0) 259 | self.assertAlmostEqual(atom.mass, 15.9994, places=6) 260 | self.assertAlmostEqual(atom.charge, -0.834) 261 | self.assertAlmostEqual(atom.radius, 1.52) 262 | self.assertEqual(list(atom.bonded), [Atom(1), Atom(2)]) 263 | self.assertEqual(atom.residue, Residue(0)) 264 | self.assertEqual(atom.chain, Chain('W')) 265 | self.assertEqual(atom.segment, Segment('W1')) 266 | 267 | # Test setters 268 | with self.assertRaises(AttributeError): 269 | atom.index = 42 270 | with self.assertRaises(AttributeError): 271 | atom.residue = Residue(4) 272 | 273 | sel = VMD.atomsel.atomsel('index 0', molid=molid) 274 | 275 | # Test setters for coordinates 276 | atom.x = 23.9 277 | atom.y = -200.45 278 | atom.z = 0 279 | # XXX: There are some troubles with rounding in set 280 | self.assertAlmostEqualSeqs(sel.get('x'), [23.9], places=6) 281 | self.assertAlmostEqualSeqs(sel.get('y'), [-200.45], places=5) 282 | self.assertAlmostEqualSeqs(sel.get('z'), [0]) 283 | self.assertAlmostEqual(atom.x, 23.9, places=6) 284 | self.assertAlmostEqual(atom.y, -200.45, places=5) 285 | self.assertAlmostEqual(atom.z, 0) 286 | self.assertAlmostEqualSeqs(list(atom.coords), [23.9, -200.45, 0], places=5) 287 | 288 | # Set complete coordinates 289 | atom.coords = (-90.56, 42, 17.85) 290 | # XXX: There are some troubles with rounding in set 291 | self.assertAlmostEqualSeqs(sel.get('x'), [-90.56], places=5) 292 | self.assertAlmostEqualSeqs(sel.get('y'), [42]) 293 | self.assertAlmostEqualSeqs(sel.get('z'), [17.85], places=6) 294 | self.assertAlmostEqual(atom.x, -90.56, places=5) 295 | self.assertAlmostEqual(atom.y, 42) 296 | self.assertAlmostEqual(atom.z, 17.85, places=6) 297 | self.assertAlmostEqualSeqs(list(atom.coords), [-90.56, 42, 17.85], places=5) 298 | 299 | # Test setters for other attributes 300 | atom.name = 'NEW' 301 | self.assertEqual(atom.name, 'NEW') 302 | self.assertEqual(sel.get('name'), ['NEW']) 303 | atom.type = 'N18' 304 | self.assertEqual(atom.type, 'N18') 305 | self.assertEqual(sel.get('type'), ['N18']) 306 | atom.element = 'Y' 307 | self.assertEqual(atom.element, 'Y') 308 | self.assertEqual(sel.get('element'), ['Y']) 309 | atom.beta = 2.5 310 | self.assertEqual(atom.beta, 2.5) 311 | self.assertEqual(sel.get('beta'), [2.5]) 312 | atom.occupancy = -3.8 313 | self.assertAlmostEqual(atom.occupancy, -3.8) 314 | self.assertAlmostEqualSeqs(sel.get('occupancy'), [-3.8]) 315 | atom.mass = 42.89 316 | self.assertAlmostEqual(atom.mass, 42.89, places=5) 317 | self.assertAlmostEqualSeqs(sel.get('mass'), [42.89], places=5) 318 | atom.charge = -7.05 319 | self.assertAlmostEqual(atom.charge, -7.05, places=6) 320 | self.assertAlmostEqualSeqs(sel.get('charge'), [-7.05], places=6) 321 | atom.radius = 4.9 322 | self.assertAlmostEqual(atom.radius, 4.9, places=6) 323 | self.assertAlmostEqualSeqs(sel.get('radius'), [4.9], places=6) 324 | 325 | atom.chain = Chain('A') 326 | # Ensure only this atom was moved to the new chain 327 | self.assertEqual(VMD.atomsel.atomsel('chain A').get('index'), [0]) 328 | self.assertEqual(atom.chain, Chain('A')) 329 | 330 | atom.segment = Segment('S') 331 | # Ensure only this atom was moved to the new segment 332 | self.assertEqual(VMD.atomsel.atomsel('segname S').get('index'), [0]) 333 | self.assertEqual(atom.segment, Segment('S')) 334 | 335 | def test_constructors(self): 336 | # Test various ways of Atom instance creation 337 | molid1 = VMD.molecule.load('psf', data('water.psf'), 'pdb', data('water.pdb')) 338 | VMD.molecule.read(molid1, 'dcd', data('water.1.dcd'), waitfor=-1) 339 | mol1 = Molecule(molid1) 340 | molid2 = VMD.molecule.load('psf', data('water.psf'), 'pdb', data('water.pdb')) 341 | mol2 = Molecule(molid2) 342 | VMD.molecule.set_top(molid2) 343 | 344 | # Top molecule and NOW 345 | atom = Atom(0) 346 | self.assertEqual(atom.molecule, mol2) 347 | self.assertEqual(atom.frame, NOW) 348 | self.assertAlmostEqual(atom.x, -1.493) 349 | 350 | # Molecule and frame 351 | atom = Atom(0, molecule=mol1, frame=5) 352 | self.assertEqual(atom.molecule, mol1) 353 | self.assertEqual(atom.frame, 5) 354 | self.assertAlmostEqual(atom.x, -1.4746015) 355 | 356 | # Get atom using selection string 357 | atom = Atom.pick('resid 2 and name OH2') 358 | self.assertEqual(atom.molecule, mol2) 359 | self.assertEqual(atom.frame, NOW) 360 | self.assertAlmostEqual(atom.x, 0.337) 361 | 362 | # Get atom using selection string, molecule and frame 363 | atom = Atom.pick('resid 2 and name OH2', molecule=mol1, frame=8) 364 | self.assertEqual(atom.molecule, mol1) 365 | self.assertEqual(atom.frame, 8) 366 | self.assertAlmostEqual(atom.x, 0.3521036) 367 | 368 | # Atom which does not exist 369 | with self.assertRaises(ValueError): 370 | Atom(8947) 371 | 372 | # Selection which returns none or too many atoms 373 | with self.assertRaises(ValueError): 374 | Atom.pick('all') 375 | with self.assertRaises(ValueError): 376 | Atom.pick('none') 377 | 378 | 379 | class TestResidue(PyvmdTestCase): 380 | """ 381 | Test `Residue` class. 382 | """ 383 | def test_properties(self): 384 | # Test getters and setters 385 | molid = VMD.molecule.load('psf', data('water.psf'), 'pdb', data('water.pdb')) 386 | 387 | res = Residue(0) 388 | # Test getters 389 | self.assertEqual(res.index, 0) 390 | self.assertEqual(res.number, 1) 391 | self.assertEqual(res.name, 'TIP3') 392 | 393 | # Test setters 394 | with self.assertRaises(AttributeError): 395 | res.index = 42 396 | 397 | res.number = 42 398 | sel = VMD.atomsel.atomsel('residue 0', molid=molid) 399 | self.assertEqual(sel.get('resid'), [42, 42, 42]) 400 | self.assertEqual(res.number, 42) 401 | 402 | res.name = 'WAT' 403 | self.assertEqual(sel.get('resname'), ['WAT', 'WAT', 'WAT']) 404 | self.assertEqual(res.name, 'WAT') 405 | 406 | 407 | class TestChain(PyvmdTestCase): 408 | """ 409 | Test `Chain` class. 410 | """ 411 | def test_properties(self): 412 | # Test getters and setters 413 | molid = VMD.molecule.load('psf', data('water.psf'), 'pdb', data('water.pdb')) 414 | 415 | chain = Chain('W') 416 | # Test getters 417 | self.assertEqual(chain.name, 'W') 418 | 419 | # Test name setter - when chain is renamed, all atoms still belong to the chain 420 | chain.name = 'A' 421 | sel = VMD.atomsel.atomsel('chain A', molid=molid) 422 | self.assertEqual(sel.get('chain'), ['A'] * 15) 423 | self.assertEqual(chain.name, 'A') 424 | 425 | 426 | class TestSegment(PyvmdTestCase): 427 | """ 428 | Test `Segment` class. 429 | """ 430 | def test_properties(self): 431 | # Test getters and setters 432 | molid = VMD.molecule.load('psf', data('water.psf'), 'pdb', data('water.pdb')) 433 | 434 | segment = Segment('W1') 435 | # Test getters 436 | self.assertEqual(segment.name, 'W1') 437 | 438 | # Test name setter - when segment is renamed, all atoms still belong to the segment 439 | segment.name = 'A' 440 | sel = VMD.atomsel.atomsel('segname A', molid=molid) 441 | self.assertEqual(sel.get('segname'), ['A'] * 12) 442 | self.assertEqual(segment.name, 'A') 443 | 444 | 445 | class TestSelection(PyvmdTestCase): 446 | """ 447 | Test `Selection` class. 448 | """ 449 | def test_properties(self): 450 | # Test basic properties 451 | VMD.molecule.load('psf', data('water.psf'), 'pdb', data('water.pdb')) 452 | 453 | sel = Selection('resid 1 to 3') 454 | # Test getters 455 | self.assertEqual(sel.selection, 'resid 1 to 3') 456 | # Test setters 457 | with self.assertRaises(AttributeError): 458 | sel.selection = 'none' 459 | 460 | def test_selection_updates(self): 461 | # Test selection updates if frame is changed 462 | molid = VMD.molecule.load('psf', data('water.psf'), 'pdb', data('water.pdb')) 463 | VMD.molecule.read(molid, 'dcd', data('water.1.dcd'), waitfor=-1) 464 | mol = Molecule(molid) 465 | 466 | # Create selection linked to the molecule frame 467 | sel = Selection('x < -1.4') 468 | self.assertEqual(list(sel), [Atom(9), Atom(18), Atom(19), Atom(20)]) 469 | # Change the molecule frame, the selection should follow 470 | mol.frame = 0 471 | self.assertEqual(list(sel), [Atom(0), Atom(1), Atom(9), Atom(10), Atom(18), Atom(19), Atom(20)]) 472 | mol.frame = 4 473 | self.assertEqual(list(sel), [Atom(0), Atom(1), Atom(9), Atom(18), Atom(19), Atom(20)]) 474 | 475 | # Define the selection's frame 476 | result = [Atom(0, frame=9), Atom(1, frame=9), Atom(9, frame=9), Atom(18, frame=9), Atom(19, frame=9), 477 | Atom(20, frame=9)] 478 | sel.frame = 9 479 | self.assertEqual(list(sel), result) 480 | # Change the molecule frame, the selection keeps its frame 481 | mol.frame = 11 482 | self.assertEqual(list(sel), result) 483 | 484 | # Set the selection's frame back to the molecule's frame 485 | sel.frame = NOW 486 | self.assertEqual(list(sel), [Atom(9), Atom(18), Atom(19), Atom(20)]) 487 | 488 | def test_contacts(self): 489 | # Test `contacts` method 490 | VMD.molecule.load('psf', data('water.psf'), 'pdb', data('water.pdb')) 491 | 492 | sel1 = Selection('resid 1 to 3 and noh') 493 | sel2 = Selection('hydrogen') 494 | 495 | self.assertEqual(list(sel1.contacts(sel2, 1.0)), []) 496 | self.assertEqual(list(sel1.contacts(sel2, 2.0)), [(Atom(6), Atom(11))]) 497 | self.assertEqual(list(sel2.contacts(sel1, 2.0)), [(Atom(11), Atom(6))]) 498 | -------------------------------------------------------------------------------- /pyvmd/tests/test_collectors.py: -------------------------------------------------------------------------------- 1 | """ 2 | Tests for data collectors. 3 | """ 4 | from cStringIO import StringIO 5 | 6 | from pyvmd.analyzer import Analyzer 7 | from pyvmd.atoms import Selection 8 | from pyvmd.collectors import (AngleCollector, DihedralCollector, DistanceCollector, RMSDCollector, XCoordCollector, 9 | YCoordCollector, ZCoordCollector) 10 | from pyvmd.datasets import DataSet 11 | from pyvmd.molecules import Molecule 12 | 13 | from .utils import data, PyvmdTestCase 14 | 15 | 16 | class TestCollectors(PyvmdTestCase): 17 | """ 18 | Test collectors. 19 | """ 20 | def setUp(self): 21 | # Prepare molecule 22 | self.mol = Molecule.create() 23 | self.mol.load(data('water.psf')) 24 | 25 | def test_coordinate_collectors(self): 26 | # Test coordinate collector 27 | dset = DataSet() 28 | dset.add_collector(XCoordCollector('index 0')) 29 | dset.add_collector(XCoordCollector('all')) 30 | dset.add_collector(YCoordCollector('index 0')) 31 | dset.add_collector(YCoordCollector('all')) 32 | dset.add_collector(ZCoordCollector('index 0')) 33 | dset.add_collector(ZCoordCollector('all')) 34 | analyzer = Analyzer(self.mol, [data('water.1.dcd')]) 35 | analyzer.add_dataset(dset) 36 | analyzer.analyze() 37 | 38 | # Write data to check result 39 | buf = StringIO() 40 | dset.write(buf) 41 | # Check the result 42 | self.assertEqual(buf.getvalue(), open(data('coords.dat')).read()) 43 | 44 | def test_geometry_collectors(self): 45 | # Test geometry collectors - distance, angle, dihedral and improper. 46 | dset = DataSet() 47 | dset.add_collector(DistanceCollector('index 0', 'index 1')) 48 | dset.add_collector(DistanceCollector('resid 1', 'all')) 49 | dset.add_collector(AngleCollector('index 0', 'index 1', 'index 2')) 50 | dset.add_collector(AngleCollector('resid 1', 'resid 2', 'resid 3')) 51 | dset.add_collector(DihedralCollector('index 0', 'index 1', 'index 2', 'index 3')) 52 | dset.add_collector(DihedralCollector('resid 1', 'resid 2', 'resid 3', 'resid 4')) 53 | analyzer = Analyzer(self.mol, [data('water.1.dcd')]) 54 | analyzer.add_dataset(dset) 55 | analyzer.analyze() 56 | 57 | # Write data to check result 58 | buf = StringIO() 59 | dset.write(buf) 60 | # Check the result 61 | self.assertEqual(buf.getvalue(), open(data('geometry.dat')).read()) 62 | 63 | def _test_collector_error(self, collector): 64 | dset = DataSet() 65 | dset.add_collector(collector) 66 | analyzer = Analyzer(self.mol, [data('water.1.dcd')]) 67 | analyzer.add_dataset(dset) 68 | self.assertRaises(ValueError, analyzer.analyze) 69 | 70 | def test_selection_errors(self): 71 | # Test collectors raises errors on empty selections 72 | self._test_collector_error(XCoordCollector('none')) 73 | self._test_collector_error(YCoordCollector('none')) 74 | self._test_collector_error(ZCoordCollector('none')) 75 | self._test_collector_error(DistanceCollector('none', 'index 0')) 76 | self._test_collector_error(DistanceCollector('index 0', 'none')) 77 | self._test_collector_error(AngleCollector('none', 'index 0', 'index 1')) 78 | self._test_collector_error(AngleCollector('index 0', 'none', 'index 1')) 79 | self._test_collector_error(AngleCollector('index 0', 'index 1', 'none')) 80 | self._test_collector_error(DihedralCollector('none', 'index 0', 'index 1', 'index 2')) 81 | self._test_collector_error(DihedralCollector('index 0', 'none', 'index 1', 'index 2')) 82 | self._test_collector_error(DihedralCollector('index 0', 'index 1', 'none', 'index 2')) 83 | self._test_collector_error(DihedralCollector('index 0', 'index 1', 'index 2', 'none')) 84 | 85 | def test_rmsd_collector(self): 86 | # Test RMSD collector 87 | ref = Molecule.create() 88 | ref.load(data('water.psf')) 89 | ref.load(data('water.pdb')) 90 | dset = DataSet() 91 | dset.add_collector(RMSDCollector('all', Selection('all', ref))) 92 | dset.add_collector(RMSDCollector('all and name OH2', Selection('all and name OH2', ref))) 93 | dset.add_collector(RMSDCollector('all and noh', Selection('all and noh', ref), name='noh')) 94 | analyzer = Analyzer(self.mol, [data('water.1.dcd')]) 95 | analyzer.add_dataset(dset) 96 | analyzer.analyze() 97 | 98 | # Write data to check result 99 | buf = StringIO() 100 | dset.write(buf) 101 | # Check the result 102 | self.assertEqual(buf.getvalue(), open(data('rmsd.dat')).read()) 103 | -------------------------------------------------------------------------------- /pyvmd/tests/test_coloring_methods.py: -------------------------------------------------------------------------------- 1 | """ 2 | Tests for coloring methods of molecule representations. 3 | """ 4 | from VMD import molecule as _molecule, molrep as _molrep 5 | 6 | from pyvmd.representations import (COLOR_BACKBONE, COLOR_BETA, COLOR_CHAIN, COLOR_CHARGE, COLOR_COLOR, 7 | COLOR_CONFORMATION, COLOR_ELEMENT, COLOR_FRAGMENT, COLOR_INDEX, COLOR_MASS, 8 | COLOR_MOLECULE, COLOR_NAME, COLOR_OCCUPANCY, COLOR_PHYSICAL_TIME, COLOR_POS, 9 | COLOR_POS_X, COLOR_POS_Y, COLOR_POS_Z, COLOR_RESID, COLOR_RESNAME, COLOR_RESTYPE, 10 | COLOR_SEGNAME, COLOR_STRUCTURE, COLOR_THROB, COLOR_TIMESTEP, COLOR_TYPE, COLOR_USER, 11 | COLOR_USER_2, COLOR_USER_3, COLOR_USER_4, COLOR_VELOCITY, COLOR_VOLUME, 12 | Representation) 13 | 14 | from .utils import data, PyvmdTestCase 15 | 16 | 17 | class TestColoringMethods(PyvmdTestCase): 18 | """ 19 | Test coloring methods are defined correctly. 20 | """ 21 | def setUp(self): 22 | self.molid = _molecule.load('psf', data('water.psf'), 'pdb', data('water.pdb')) 23 | self.rep = Representation('rep0') 24 | self.color = self.rep.color 25 | 26 | def test_name(self): 27 | self.color.method = COLOR_NAME 28 | self.assertEqual(self.color.method, COLOR_NAME) 29 | self.assertEqual(self.color.get_parameters(), {}) 30 | self.assertEqual(_molrep.get_color(self.molid, 0), 'Name') 31 | 32 | def test_type(self): 33 | self.color.method = COLOR_TYPE 34 | self.assertEqual(self.color.method, COLOR_TYPE) 35 | self.assertEqual(self.color.get_parameters(), {}) 36 | self.assertEqual(_molrep.get_color(self.molid, 0), 'Type') 37 | 38 | def test_element(self): 39 | self.color.method = COLOR_ELEMENT 40 | self.assertEqual(self.color.method, COLOR_ELEMENT) 41 | self.assertEqual(self.color.get_parameters(), {}) 42 | self.assertEqual(_molrep.get_color(self.molid, 0), 'Element') 43 | 44 | def test_resname(self): 45 | self.color.method = COLOR_RESNAME 46 | self.assertEqual(self.color.method, COLOR_RESNAME) 47 | self.assertEqual(self.color.get_parameters(), {}) 48 | self.assertEqual(_molrep.get_color(self.molid, 0), 'ResName') 49 | 50 | def test_restype(self): 51 | self.color.method = COLOR_RESTYPE 52 | self.assertEqual(self.color.method, COLOR_RESTYPE) 53 | self.assertEqual(self.color.get_parameters(), {}) 54 | self.assertEqual(_molrep.get_color(self.molid, 0), 'ResType') 55 | 56 | def test_resid(self): 57 | self.color.method = COLOR_RESID 58 | self.assertEqual(self.color.method, COLOR_RESID) 59 | self.assertEqual(self.color.get_parameters(), {}) 60 | self.assertEqual(_molrep.get_color(self.molid, 0), 'ResID') 61 | 62 | def test_chain(self): 63 | self.color.method = COLOR_CHAIN 64 | self.assertEqual(self.color.method, COLOR_CHAIN) 65 | self.assertEqual(self.color.get_parameters(), {}) 66 | self.assertEqual(_molrep.get_color(self.molid, 0), 'Chain') 67 | 68 | def test_segname(self): 69 | self.color.method = COLOR_SEGNAME 70 | self.assertEqual(self.color.method, COLOR_SEGNAME) 71 | self.assertEqual(self.color.get_parameters(), {}) 72 | self.assertEqual(_molrep.get_color(self.molid, 0), 'SegName') 73 | 74 | def test_conformation(self): 75 | self.color.method = COLOR_CONFORMATION 76 | self.assertEqual(self.color.method, COLOR_CONFORMATION) 77 | self.assertEqual(self.color.get_parameters(), {}) 78 | self.assertEqual(_molrep.get_color(self.molid, 0), 'Conformation') 79 | 80 | def test_molecule(self): 81 | self.color.method = COLOR_MOLECULE 82 | self.assertEqual(self.color.method, COLOR_MOLECULE) 83 | self.assertEqual(self.color.get_parameters(), {}) 84 | self.assertEqual(_molrep.get_color(self.molid, 0), 'Molecule') 85 | 86 | def test_structure(self): 87 | self.color.method = COLOR_STRUCTURE 88 | self.assertEqual(self.color.method, COLOR_STRUCTURE) 89 | self.assertEqual(self.color.get_parameters(), {}) 90 | self.assertEqual(_molrep.get_color(self.molid, 0), 'Structure') 91 | 92 | def test_color(self): 93 | self.color.method = COLOR_COLOR 94 | self.assertEqual(self.color.method, COLOR_COLOR) 95 | self.assertEqual(self.color.get_parameters(), {'color': 1}) 96 | self.assertEqual(_molrep.get_color(self.molid, 0), 'ColorID 1') 97 | 98 | self.color.set_parameters(color=3) 99 | self.assertEqual(self.color.method, COLOR_COLOR) 100 | self.assertEqual(self.color.get_parameters(), {'color': 3}) 101 | self.assertEqual(_molrep.get_color(self.molid, 0), 'ColorID 3') 102 | 103 | def test_beta(self): 104 | self.color.method = COLOR_BETA 105 | self.assertEqual(self.color.method, COLOR_BETA) 106 | self.assertEqual(self.color.get_parameters(), {}) 107 | self.assertEqual(_molrep.get_color(self.molid, 0), 'Beta') 108 | 109 | def test_occupancy(self): 110 | self.color.method = COLOR_OCCUPANCY 111 | self.assertEqual(self.color.method, COLOR_OCCUPANCY) 112 | self.assertEqual(self.color.get_parameters(), {}) 113 | self.assertEqual(_molrep.get_color(self.molid, 0), 'Occupancy') 114 | 115 | def test_mass(self): 116 | self.color.method = COLOR_MASS 117 | self.assertEqual(self.color.method, COLOR_MASS) 118 | self.assertEqual(self.color.get_parameters(), {}) 119 | self.assertEqual(_molrep.get_color(self.molid, 0), 'Mass') 120 | 121 | def test_charge(self): 122 | self.color.method = COLOR_CHARGE 123 | self.assertEqual(self.color.method, COLOR_CHARGE) 124 | self.assertEqual(self.color.get_parameters(), {}) 125 | self.assertEqual(_molrep.get_color(self.molid, 0), 'Charge') 126 | 127 | def test_pos(self): 128 | self.color.method = COLOR_POS 129 | self.assertEqual(self.color.method, COLOR_POS) 130 | self.assertEqual(self.color.get_parameters(), {}) 131 | self.assertEqual(_molrep.get_color(self.molid, 0), 'Pos') 132 | 133 | def test_pos_x(self): 134 | self.color.method = COLOR_POS_X 135 | self.assertEqual(self.color.method, COLOR_POS_X) 136 | self.assertEqual(self.color.get_parameters(), {}) 137 | self.assertEqual(_molrep.get_color(self.molid, 0), 'PosX') 138 | 139 | def test_pos_y(self): 140 | self.color.method = COLOR_POS_Y 141 | self.assertEqual(self.color.method, COLOR_POS_Y) 142 | self.assertEqual(self.color.get_parameters(), {}) 143 | self.assertEqual(_molrep.get_color(self.molid, 0), 'PosY') 144 | 145 | def test_pos_z(self): 146 | self.color.method = COLOR_POS_Z 147 | self.assertEqual(self.color.method, COLOR_POS_Z) 148 | self.assertEqual(self.color.get_parameters(), {}) 149 | self.assertEqual(_molrep.get_color(self.molid, 0), 'PosZ') 150 | 151 | def test_user(self): 152 | self.color.method = COLOR_USER 153 | self.assertEqual(self.color.method, COLOR_USER) 154 | self.assertEqual(self.color.get_parameters(), {}) 155 | self.assertEqual(_molrep.get_color(self.molid, 0), 'User') 156 | 157 | def test_user_2(self): 158 | self.color.method = COLOR_USER_2 159 | self.assertEqual(self.color.method, COLOR_USER_2) 160 | self.assertEqual(self.color.get_parameters(), {}) 161 | self.assertEqual(_molrep.get_color(self.molid, 0), 'User2') 162 | 163 | def test_user_3(self): 164 | self.color.method = COLOR_USER_3 165 | self.assertEqual(self.color.method, COLOR_USER_3) 166 | self.assertEqual(self.color.get_parameters(), {}) 167 | self.assertEqual(_molrep.get_color(self.molid, 0), 'User3') 168 | 169 | def test_user_4(self): 170 | self.color.method = COLOR_USER_4 171 | self.assertEqual(self.color.method, COLOR_USER_4) 172 | self.assertEqual(self.color.get_parameters(), {}) 173 | self.assertEqual(_molrep.get_color(self.molid, 0), 'User4') 174 | 175 | def test_fragment(self): 176 | self.color.method = COLOR_FRAGMENT 177 | self.assertEqual(self.color.method, COLOR_FRAGMENT) 178 | self.assertEqual(self.color.get_parameters(), {}) 179 | self.assertEqual(_molrep.get_color(self.molid, 0), 'Fragment') 180 | 181 | def test_index(self): 182 | self.color.method = COLOR_INDEX 183 | self.assertEqual(self.color.method, COLOR_INDEX) 184 | self.assertEqual(self.color.get_parameters(), {}) 185 | self.assertEqual(_molrep.get_color(self.molid, 0), 'Index') 186 | 187 | def test_backbone(self): 188 | self.color.method = COLOR_BACKBONE 189 | self.assertEqual(self.color.method, COLOR_BACKBONE) 190 | self.assertEqual(self.color.get_parameters(), {}) 191 | self.assertEqual(_molrep.get_color(self.molid, 0), 'Backbone') 192 | 193 | def test_throb(self): 194 | self.color.method = COLOR_THROB 195 | self.assertEqual(self.color.method, COLOR_THROB) 196 | self.assertEqual(self.color.get_parameters(), {}) 197 | self.assertEqual(_molrep.get_color(self.molid, 0), 'Throb') 198 | 199 | def test_physical_time(self): 200 | self.color.method = COLOR_PHYSICAL_TIME 201 | self.assertEqual(self.color.method, COLOR_PHYSICAL_TIME) 202 | self.assertEqual(self.color.get_parameters(), {}) 203 | self.assertEqual(_molrep.get_color(self.molid, 0), 'PhysicalTime') 204 | 205 | def test_timestep(self): 206 | self.color.method = COLOR_TIMESTEP 207 | self.assertEqual(self.color.method, COLOR_TIMESTEP) 208 | self.assertEqual(self.color.get_parameters(), {}) 209 | self.assertEqual(_molrep.get_color(self.molid, 0), 'Timestep') 210 | 211 | def test_velocity(self): 212 | self.color.method = COLOR_VELOCITY 213 | self.assertEqual(self.color.method, COLOR_VELOCITY) 214 | self.assertEqual(self.color.get_parameters(), {}) 215 | self.assertEqual(_molrep.get_color(self.molid, 0), 'Velocity') 216 | 217 | def test_volume(self): 218 | self.color.method = COLOR_VOLUME 219 | self.assertEqual(self.color.method, COLOR_VOLUME) 220 | self.assertEqual(self.color.get_parameters(), {'volume_id': 0}) 221 | self.assertEqual(_molrep.get_color(self.molid, 0), 'Volume 0') 222 | 223 | self.color.set_parameters(volume_id=42) 224 | self.assertEqual(self.color.method, COLOR_VOLUME) 225 | self.assertEqual(self.color.get_parameters(), {'volume_id': 42}) 226 | self.assertEqual(_molrep.get_color(self.molid, 0), 'Volume 42') 227 | -------------------------------------------------------------------------------- /pyvmd/tests/test_datasets.py: -------------------------------------------------------------------------------- 1 | """ 2 | Tests for data sets. 3 | """ 4 | import os 5 | from cStringIO import StringIO 6 | from tempfile import mkstemp 7 | 8 | import numpy 9 | from mock import Mock 10 | 11 | from pyvmd.collectors import Collector 12 | from pyvmd.datasets import DataSet 13 | 14 | from .utils import data, PyvmdTestCase 15 | 16 | 17 | class SimpleTestCollector(Collector): 18 | """ 19 | Simple collector which returns given data. 20 | """ 21 | def __init__(self, results, name): 22 | super(SimpleTestCollector, self).__init__(name) 23 | self.results = results 24 | 25 | def collect(self, step): 26 | return self.results.pop(0) 27 | 28 | 29 | class TestDataSet(PyvmdTestCase): 30 | """ 31 | Test `DataSet` object. 32 | """ 33 | def setUp(self): 34 | dummy, filename = mkstemp(prefix='pyvmd_test_') 35 | self.tmpfile = filename 36 | self.addCleanup(lambda: os.unlink(self.tmpfile)) 37 | 38 | def test_dataset(self): 39 | # Test basic dataset workflow - add columns, add data, get data 40 | dset = DataSet() 41 | # Register few collectors 42 | dset.add_collector(SimpleTestCollector([5.0, -0.9, -42.0, -204.54], 'first')) 43 | dset.add_collector(SimpleTestCollector([3.5, 0.5, 0.01, 0.1], 'second')) 44 | dset.add_collector(SimpleTestCollector([2.9, 5.9, 8.9, 15.89], 'last')) 45 | 46 | # Collect the data 47 | dset.collect(Mock(frame=0)) 48 | dset.collect(Mock(frame=1)) 49 | dset.collect(Mock(frame=2)) 50 | dset.collect(Mock(frame=42)) 51 | 52 | result = numpy.array(([0, 5.0, 3.5, 2.9], 53 | [1, -0.9, 0.5, 5.9], 54 | [2, -42.0, 0.01, 8.9], 55 | [42, -204.54, 0.1, 15.89])) 56 | self.assertTrue(numpy.array_equal(dset.data, result)) 57 | 58 | def test_data_resize(self): 59 | # Test adding so many rows, that the data array is resized. 60 | dset = DataSet() 61 | dset.step = 2 # Lower the array size step so we can easily enforce resizing 62 | 63 | # Register few collectors 64 | dset.add_collector(SimpleTestCollector([5.0, -0.9, -42.0, -204.54, -15.4], 'first')) 65 | dset.add_collector(SimpleTestCollector([3.5, 0.5, 0.01, 0.1, 0.5], 'second')) 66 | 67 | # Collect the data 68 | dset.collect(Mock(frame=0)) 69 | dset.collect(Mock(frame=1)) 70 | dset.collect(Mock(frame=2)) 71 | dset.collect(Mock(frame=3)) 72 | dset.collect(Mock(frame=4)) 73 | 74 | result = numpy.array(([0, 5.0, 3.5], 75 | [1, -0.9, 0.5], 76 | [2, -42.0, 0.01], 77 | [3, -204.54, 0.1], 78 | [4, -15.4, 0.5])) 79 | self.assertTrue(numpy.array_equal(dset.data, result)) 80 | 81 | def test_write(self): 82 | dset = DataSet() 83 | 84 | # Register few collectors 85 | first = SimpleTestCollector([5.0, -0.9, -42.0, -204.54], 'first') 86 | # Set custom format for first collector 87 | first.header_fmt = '%20s' 88 | first.data_fmt = '% 20d' 89 | dset.add_collector(first) 90 | dset.add_collector(SimpleTestCollector([3.5, 0.5, 0.01, 0.1], 'second')) 91 | dset.add_collector(SimpleTestCollector([2.9, 5.9, 8.9, 15.89], 'last')) 92 | 93 | # Collect the data 94 | dset.collect(Mock(frame=0)) 95 | dset.collect(Mock(frame=1)) 96 | dset.collect(Mock(frame=2)) 97 | dset.collect(Mock(frame=3)) 98 | 99 | # Test filename 100 | dset.write(self.tmpfile) 101 | self.assertEqual(open(self.tmpfile).read(), open(data('dataset.dat')).read()) 102 | 103 | # Test file-like object 104 | buf = StringIO() 105 | dset.write(buf) 106 | self.assertEqual(buf.getvalue(), open(data('dataset.dat')).read()) 107 | -------------------------------------------------------------------------------- /pyvmd/tests/test_labels.py: -------------------------------------------------------------------------------- 1 | """ 2 | Tests for labels utilities. 3 | """ 4 | import VMD 5 | 6 | from pyvmd.atoms import Atom 7 | from pyvmd.labels import (ANGLE, ANGLE_LABELS, AngleLabel, ATOM, ATOM_LABELS, AtomLabel, BOND, BOND_LABELS, BondLabel, 8 | DIHEDRAL, DIHEDRAL_LABELS, DihedralLabel) 9 | from pyvmd.molecules import Molecule, MOLECULES 10 | 11 | from .utils import data, PyvmdTestCase 12 | 13 | 14 | def clear_labels(): 15 | """ 16 | Utility to delete all labels. 17 | """ 18 | for category in ATOM, BOND, ANGLE, DIHEDRAL: 19 | for label in VMD.label.listall(category): 20 | VMD.label.delete(category, label) 21 | 22 | 23 | class TestLabels(PyvmdTestCase): 24 | """ 25 | Test labels classes. 26 | """ 27 | def setUp(self): 28 | self.mol = Molecule.create() 29 | self.mol.load(data('water.psf')) 30 | self.mol.load(data('water.pdb')) 31 | self.mol.load(data('water.1.dcd')) 32 | self.mol.frame = 0 33 | 34 | self.other = Molecule.create() 35 | self.other.load(data('water.psf')) 36 | self.other.load(data('water.pdb')) 37 | 38 | MOLECULES.top = self.mol 39 | 40 | self.addCleanup(clear_labels) 41 | 42 | def test_atom_label(self): 43 | # Test atom labels 44 | a = Atom(0) 45 | b = Atom(4) 46 | VMD.label.add(ATOM, (self.mol.molid, ), (0, )) 47 | VMD.label.add(ATOM, (self.mol.molid, ), (4, )) 48 | VMD.label.add(ATOM, (self.other.molid, ), (0, )) 49 | 50 | # Check label 51 | label_1 = AtomLabel(a) 52 | self.assertEqual(label_1.category, ATOM) 53 | self.assertEqual(label_1.atoms, (a, )) 54 | self.assertEqual(label_1.atom, a) 55 | self.assertEqual(label_1, label_1) 56 | 57 | # Create other instance of the same label 58 | label_2 = AtomLabel(a) 59 | self.assertEqual(label_2, label_1) 60 | 61 | # Check other label 62 | label_3 = AtomLabel(b) 63 | self.assertEqual(label_3.category, ATOM) 64 | self.assertEqual(label_3.atoms, (b, )) 65 | self.assertEqual(label_3.atom, b) 66 | self.assertNotEqual(label_3, label_1) 67 | 68 | # Check label for other instance of the same atom 69 | label_4 = AtomLabel(Atom(0)) 70 | self.assertEqual(label_4, label_1) 71 | 72 | # Check label for atom in another frame - it's the same label, because labels are independent on frames 73 | label_5 = AtomLabel(Atom(0, frame=4)) 74 | self.assertEqual(label_5, label_1) 75 | 76 | # Check label to atom from other molecule is different 77 | label_6 = AtomLabel(Atom(0, self.other)) 78 | self.assertNotEqual(label_6, label_1) 79 | 80 | # Check ValueError if label doesn't exist 81 | with self.assertRaises(ValueError): 82 | AtomLabel(Atom(5)) 83 | 84 | def test_bond_label(self): 85 | # Test bond labels 86 | a = Atom(0) 87 | b = Atom(4) 88 | c = Atom(7) 89 | VMD.label.add(BOND, (self.mol.molid, self.mol.molid), (0, 4)) 90 | VMD.label.add(BOND, (self.mol.molid, self.mol.molid), (4, 7)) 91 | VMD.label.add(BOND, (self.other.molid, self.other.molid), (0, 4)) 92 | 93 | # Check label 94 | label_1 = BondLabel(a, b) 95 | self.assertEqual(label_1.category, BOND) 96 | self.assertEqual(label_1.atoms, (a, b)) 97 | self.assertEqual(label_1, label_1) 98 | 99 | # Create other instance of the same label 100 | label_2 = BondLabel(a, b) 101 | self.assertEqual(label_2, label_1) 102 | 103 | # Check other label 104 | label_3 = BondLabel(b, c) 105 | self.assertEqual(label_3.category, BOND) 106 | self.assertEqual(label_3.atoms, (b, c)) 107 | self.assertNotEqual(label_3, label_1) 108 | 109 | # Check label with opposite order of atoms 110 | label_4 = BondLabel(b, a) 111 | self.assertEqual(label_4.atoms, (b, a)) 112 | self.assertEqual(label_4, label_1) 113 | 114 | # Create label for atom in another frame - it's the same label, because labels are independent on frames 115 | label_5 = BondLabel(Atom(0, frame=4), Atom(4, frame=8)) 116 | self.assertEqual(label_5, label_1) 117 | 118 | # Check label to atom from other molecule is different 119 | label_6 = BondLabel(Atom(0, self.other), Atom(4, self.other)) 120 | self.assertNotEqual(label_6, label_1) 121 | 122 | # Check ValueError if label doesn't exist 123 | with self.assertRaises(ValueError): 124 | BondLabel(Atom(5), Atom(6)) 125 | 126 | def test_angle_label(self): 127 | # Test angle labels 128 | a = Atom(0) 129 | b = Atom(4) 130 | c = Atom(7) 131 | VMD.label.add(ANGLE, (self.mol.molid, self.mol.molid, self.mol.molid), (0, 4, 7)) 132 | VMD.label.add(ANGLE, (self.mol.molid, self.mol.molid, self.mol.molid), (4, 7, 0)) 133 | VMD.label.add(ANGLE, (self.other.molid, self.other.molid, self.other.molid), (0, 4, 7)) 134 | 135 | # Check label 136 | label_1 = AngleLabel(a, b, c) 137 | self.assertEqual(label_1.category, ANGLE) 138 | self.assertEqual(label_1.atoms, (a, b, c)) 139 | self.assertEqual(label_1, label_1) 140 | 141 | # Create other instance of the same label 142 | label_2 = AngleLabel(a, b, c) 143 | self.assertEqual(label_2, label_1) 144 | 145 | # Check other label 146 | label_3 = AngleLabel(b, c, a) 147 | self.assertEqual(label_3.category, ANGLE) 148 | self.assertEqual(label_3.atoms, (b, c, a)) 149 | self.assertNotEqual(label_3, label_1) 150 | 151 | # Check label with opposite order of atoms 152 | label_4 = AngleLabel(c, b, a) 153 | self.assertEqual(label_4.atoms, (c, b, a)) 154 | self.assertEqual(label_4, label_1) 155 | 156 | # Create label for atom in another frame - it's the same label, because labels are independent on frames 157 | label_5 = AngleLabel(Atom(0, frame=4), Atom(4, frame=8), Atom(7, frame=2)) 158 | self.assertEqual(label_5, label_1) 159 | 160 | # Check label to atom from other molecule is different 161 | label_6 = AngleLabel(Atom(0, self.other), Atom(4, self.other), Atom(7, self.other)) 162 | self.assertNotEqual(label_6, label_1) 163 | 164 | # Check ValueError if label doesn't exist 165 | with self.assertRaises(ValueError): 166 | AngleLabel(Atom(5), Atom(6), Atom(7)) 167 | 168 | def test_dihedral_label(self): 169 | # Test dihedral labels 170 | a = Atom(0) 171 | b = Atom(4) 172 | c = Atom(7) 173 | d = Atom(10) 174 | VMD.label.add(DIHEDRAL, (self.mol.molid, self.mol.molid, self.mol.molid, self.mol.molid), (0, 4, 7, 10)) 175 | VMD.label.add(DIHEDRAL, (self.mol.molid, self.mol.molid, self.mol.molid, self.mol.molid), (4, 7, 10, 0)) 176 | VMD.label.add(DIHEDRAL, (self.other.molid, self.other.molid, self.other.molid, self.other.molid), (0, 4, 7, 10)) 177 | 178 | # Check label 179 | label_1 = DihedralLabel(a, b, c, d) 180 | self.assertEqual(label_1.category, DIHEDRAL) 181 | self.assertEqual(label_1.atoms, (a, b, c, d)) 182 | self.assertEqual(label_1, label_1) 183 | 184 | # Create other instance of the same label 185 | label_2 = DihedralLabel(a, b, c, d) 186 | self.assertEqual(label_2, label_1) 187 | 188 | # Check other label 189 | label_3 = DihedralLabel(b, c, d, a) 190 | self.assertEqual(label_3.category, DIHEDRAL) 191 | self.assertEqual(label_3.atoms, (b, c, d, a)) 192 | self.assertNotEqual(label_3, label_1) 193 | 194 | # Check label with opposite order of atoms 195 | label_4 = DihedralLabel(d, c, b, a) 196 | self.assertEqual(label_4.atoms, (d, c, b, a)) 197 | self.assertEqual(label_4, label_1) 198 | 199 | # Create label for atom in another frame - it's the same label, because labels are independent on frames 200 | label_5 = DihedralLabel(Atom(0, frame=4), Atom(4, frame=8), Atom(7, frame=2), Atom(10, frame=3)) 201 | self.assertEqual(label_5, label_1) 202 | 203 | # Check label to atom from other molecule is different 204 | label_6 = DihedralLabel(Atom(0, self.other), Atom(4, self.other), Atom(7, self.other), Atom(10, self.other)) 205 | self.assertNotEqual(label_6, label_1) 206 | 207 | # Check ValueError if label doesn't exist 208 | with self.assertRaises(ValueError): 209 | DihedralLabel(Atom(5), Atom(6), Atom(7), Atom(8)) 210 | 211 | def test_unequal_types(self): 212 | VMD.label.add(ATOM, (self.mol.molid, ), (0, )) 213 | atom_label = AtomLabel(Atom(0)) 214 | VMD.label.add(BOND, (self.mol.molid, self.mol.molid), (0, 4)) 215 | bond_label = BondLabel(Atom(0), Atom(4)) 216 | VMD.label.add(ANGLE, (self.mol.molid, self.mol.molid, self.mol.molid), (0, 4, 7)) 217 | angle_label = AngleLabel(Atom(0), Atom(4), Atom(7)) 218 | VMD.label.add(DIHEDRAL, (self.mol.molid, self.mol.molid, self.mol.molid, self.mol.molid), (0, 4, 7, 10)) 219 | dihedral_label = DihedralLabel(Atom(0), Atom(4), Atom(7), Atom(10)) 220 | 221 | self.assertNotEqual(atom_label, bond_label) 222 | self.assertNotEqual(atom_label, angle_label) 223 | self.assertNotEqual(atom_label, dihedral_label) 224 | self.assertNotEqual(bond_label, angle_label) 225 | self.assertNotEqual(bond_label, dihedral_label) 226 | self.assertNotEqual(angle_label, dihedral_label) 227 | 228 | def test_create(self): 229 | atom_label = AtomLabel.create(Atom(0)) 230 | self.assertEqual(VMD.label.listall(ATOM), 231 | [{'atomid': (0, ), 'molid': (self.mol.molid, ), 'on': 1, 'value': 0.0}]) 232 | self.assertTrue(atom_label.visible) 233 | 234 | bond_label = BondLabel.create(Atom(0), Atom(4)) 235 | data_1 = {'atomid': (0, 4), 'molid': (self.mol.molid, self.mol.molid), 'on': 1, 'value': 4.476513862609863} 236 | self.assertEqual(VMD.label.listall(BOND), [data_1]) 237 | self.assertTrue(bond_label.visible) 238 | 239 | angle_label = AngleLabel.create(Atom(0), Atom(4), Atom(7)) 240 | data_2 = {'atomid': (0, 4, 7), 'molid': (self.mol.molid, self.mol.molid, self.mol.molid), 'on': 1, 241 | 'value': 54.093936920166016} 242 | self.assertEqual(VMD.label.listall(ANGLE), [data_2]) 243 | self.assertTrue(angle_label.visible) 244 | 245 | dihedral_label = DihedralLabel.create(Atom(0), Atom(4), Atom(7), Atom(10)) 246 | data_3 = {'atomid': (0, 4, 7, 10), 'molid': (self.mol.molid, self.mol.molid, self.mol.molid, self.mol.molid), 247 | 'on': 1, 'value': -80.11302947998047} 248 | self.assertEqual(VMD.label.listall(DIHEDRAL), [data_3]) 249 | self.assertTrue(dihedral_label.visible) 250 | 251 | def test_delete(self): 252 | VMD.label.add(ATOM, (self.mol.molid, ), (0, )) 253 | atom_label = AtomLabel(Atom(0)) 254 | atom_label.delete() 255 | self.assertEqual(VMD.label.listall(ATOM), []) 256 | # Label can't be deleted twice 257 | with self.assertRaises(ValueError): 258 | atom_label.delete() 259 | 260 | VMD.label.add(BOND, (self.mol.molid, self.mol.molid), (0, 4)) 261 | bond_label = BondLabel(Atom(0), Atom(4)) 262 | bond_label.delete() 263 | self.assertEqual(VMD.label.listall(BOND), []) 264 | # Label can't be deleted twice 265 | with self.assertRaises(ValueError): 266 | bond_label.delete() 267 | # Check label values 268 | with self.assertRaises(ValueError): 269 | bond_label.distances 270 | 271 | VMD.label.add(ANGLE, (self.mol.molid, self.mol.molid, self.mol.molid), (0, 4, 7)) 272 | angle_label = AngleLabel(Atom(0), Atom(4), Atom(7)) 273 | angle_label.delete() 274 | self.assertEqual(VMD.label.listall(ANGLE), []) 275 | # Label can't be deleted twice 276 | with self.assertRaises(ValueError): 277 | angle_label.delete() 278 | # Check label values 279 | with self.assertRaises(ValueError): 280 | angle_label.angles 281 | 282 | VMD.label.add(DIHEDRAL, (self.mol.molid, self.mol.molid, self.mol.molid, self.mol.molid), (0, 4, 7, 10)) 283 | dihedral_label = DihedralLabel(Atom(0), Atom(4), Atom(7), Atom(10)) 284 | dihedral_label.delete() 285 | self.assertEqual(VMD.label.listall(DIHEDRAL), []) 286 | # Label can't be deleted twice 287 | with self.assertRaises(ValueError): 288 | dihedral_label.delete() 289 | # Check label values 290 | with self.assertRaises(ValueError): 291 | dihedral_label.dihedrals 292 | 293 | def test_visibility(self): 294 | # Test `visible` property 295 | VMD.label.add(ATOM, (self.mol.molid, ), (0, )) 296 | label = AtomLabel(Atom(0)) 297 | self.assertEqual(VMD.label.listall(ATOM), 298 | [{'atomid': (0, ), 'molid': (self.mol.molid, ), 'on': 1, 'value': 0.0}]) 299 | self.assertTrue(label.visible) 300 | 301 | # Show visible label doesn't change anything 302 | label.visible = True 303 | self.assertEqual(VMD.label.listall(ATOM), 304 | [{'atomid': (0, ), 'molid': (self.mol.molid, ), 'on': 1, 'value': 0.0}]) 305 | self.assertTrue(label.visible) 306 | 307 | # Hide label 308 | label.visible = False 309 | self.assertEqual(VMD.label.listall(ATOM), 310 | [{'atomid': (0, ), 'molid': (self.mol.molid, ), 'on': 0, 'value': 0.0}]) 311 | self.assertFalse(label.visible) 312 | 313 | # Hide again doesn't change anything 314 | label.visible = False 315 | self.assertEqual(VMD.label.listall(ATOM), 316 | [{'atomid': (0, ), 'molid': (self.mol.molid, ), 'on': 0, 'value': 0.0}]) 317 | self.assertFalse(label.visible) 318 | 319 | # Check direct changes to visibility are followed 320 | VMD.label.show(ATOM, {'atomid': (0, ), 'molid': (self.mol.molid, )}) 321 | self.assertTrue(label.visible) 322 | VMD.label.hide(ATOM, {'atomid': (0, ), 'molid': (self.mol.molid, )}) 323 | self.assertFalse(label.visible) 324 | 325 | label.delete() 326 | # Deleted label raises errors 327 | with self.assertRaises(ValueError): 328 | label.visible 329 | with self.assertRaises(ValueError): 330 | label.visible = True 331 | 332 | def test_values(self): 333 | VMD.label.add(BOND, (self.mol.molid, self.mol.molid), (0, 1)) 334 | bond_label = BondLabel(Atom(0), Atom(1)) 335 | distances = [1.0100465, 0.9646406, 0.9611206, 0.9662452, 0.9626279, 0.9649956, 0.9611024, 0.9658498, 0.9603688, 336 | 0.9671795, 0.961661, 0.9632944, 0.9573071] 337 | self.assertAlmostEqualSeqs(bond_label.distances, distances) 338 | 339 | VMD.label.add(ANGLE, (self.mol.molid, self.mol.molid, self.mol.molid), (0, 1, 2)) 340 | angle_label = AngleLabel(Atom(0), Atom(1), Atom(2)) 341 | angles = [38.9131393, 38.1194229, 38.2041321, 38.341835, 38.3854752, 38.1108742, 37.7457161, 37.9469795, 342 | 38.6991272, 38.7136955, 38.4580612, 38.6055489, 39.1369514] 343 | self.assertAlmostEqualSeqs(angle_label.angles, angles) 344 | 345 | VMD.label.add(DIHEDRAL, (self.mol.molid, self.mol.molid, self.mol.molid, self.mol.molid), (0, 1, 2, 3)) 346 | dihedral_label = DihedralLabel(Atom(0), Atom(1), Atom(2), Atom(3)) 347 | dihedrals = [54.5871239, 57.054245, 61.5406532, 68.0926056, 75.0311661, 81.3050842, 86.0759125, 89.2662735, 348 | 93.1087952, 99.3121338, 110.2715073, 125.2944031, 142.6539612] 349 | self.assertAlmostEqualSeqs(dihedral_label.dihedrals, dihedrals) 350 | 351 | 352 | class TestLabelManagers(PyvmdTestCase): 353 | """ 354 | Test label managers. 355 | """ 356 | def setUp(self): 357 | self.addCleanup(clear_labels) 358 | 359 | def test_managers(self): 360 | molid = VMD.molecule.load('psf', data('water.psf'), 'pdb', data('water.pdb')) 361 | 362 | # There are no labels 363 | self.assertEqual(len(ATOM_LABELS), 0) 364 | self.assertEqual(len(BOND_LABELS), 0) 365 | self.assertEqual(len(ANGLE_LABELS), 0) 366 | self.assertEqual(len(DIHEDRAL_LABELS), 0) 367 | 368 | # Create a few labels directly in VMD 369 | VMD.label.add(ATOM, (molid, ), (0, )) 370 | VMD.label.add(ATOM, (molid, ), (1, )) 371 | VMD.label.add(ATOM, (molid, ), (2, )) 372 | VMD.label.add(ATOM, (molid, ), (3, )) 373 | VMD.label.add(BOND, (molid, molid), (2, 3)) 374 | VMD.label.add(BOND, (molid, molid), (3, 4)) 375 | VMD.label.add(BOND, (molid, molid), (4, 5)) 376 | VMD.label.add(ANGLE, (molid, molid, molid), (2, 4, 6)) 377 | VMD.label.add(ANGLE, (molid, molid, molid), (3, 5, 7)) 378 | VMD.label.add(DIHEDRAL, (molid, molid, molid, molid), (8, 5, 3, 2)) 379 | 380 | # Check manager's properties 381 | self.assertEqual(len(ATOM_LABELS), 4) 382 | self.assertEqual(len(BOND_LABELS), 3) 383 | self.assertEqual(len(ANGLE_LABELS), 2) 384 | self.assertEqual(len(DIHEDRAL_LABELS), 1) 385 | 386 | label_1 = AtomLabel(Atom(0)) 387 | self.assertIn(label_1, ATOM_LABELS) 388 | self.assertNotIn(label_1, BOND_LABELS) 389 | self.assertNotIn(label_1, ANGLE_LABELS) 390 | self.assertNotIn(label_1, DIHEDRAL_LABELS) 391 | 392 | self.assertEqual(list(ATOM_LABELS), [label_1, AtomLabel(Atom(1)), AtomLabel(Atom(2)), AtomLabel(Atom(3))]) 393 | self.assertEqual(list(BOND_LABELS), 394 | [BondLabel(Atom(2), Atom(3)), BondLabel(Atom(3), Atom(4)), BondLabel(Atom(4), Atom(5))]) 395 | self.assertEqual(list(ANGLE_LABELS), 396 | [AngleLabel(Atom(2), Atom(4), Atom(6)), AngleLabel(Atom(3), Atom(5), Atom(7))]) 397 | self.assertEqual(list(DIHEDRAL_LABELS), [DihedralLabel(Atom(8), Atom(5), Atom(3), Atom(2))]) 398 | -------------------------------------------------------------------------------- /pyvmd/tests/test_measure.py: -------------------------------------------------------------------------------- 1 | """ 2 | Tests for measure. 3 | """ 4 | import VMD 5 | 6 | from pyvmd.atoms import Atom, Residue, Selection 7 | from pyvmd.measure import angle, center, dihedral, distance 8 | 9 | from .utils import data, PyvmdTestCase 10 | 11 | 12 | class TestMeasure(PyvmdTestCase): 13 | """ 14 | Test measure utilities. 15 | """ 16 | def setUp(self): 17 | molid = VMD.molecule.load('psf', data('water.psf'), 'pdb', data('water.pdb')) 18 | self.molid = molid 19 | 20 | def test_distance(self): 21 | # Test `distance` function 22 | a = Atom(0) 23 | b = Atom(4) 24 | c = Atom(7) 25 | 26 | self.assertAlmostEqual(distance(a, b), 4.4765141) 27 | self.assertAlmostEqual(distance(b, a), 4.4765141) 28 | self.assertAlmostEqual(distance(a, c), 4.0660655) 29 | self.assertAlmostEqual(distance(c, a), 4.0660655) 30 | self.assertAlmostEqual(distance(b, c), 4.4653567) 31 | self.assertAlmostEqual(distance(c, b), 4.4653567) 32 | 33 | def test_angle(self): 34 | # Test `angle` function 35 | a = Atom(0) 36 | b = Atom(4) 37 | c = Atom(7) 38 | 39 | self.assertAlmostEqual(angle(a, b, c), 54.0939249) 40 | self.assertAlmostEqual(angle(c, b, a), 54.0939249) 41 | self.assertAlmostEqual(angle(b, c, a), 63.0930652) 42 | self.assertAlmostEqual(angle(a, c, b), 63.0930652) 43 | self.assertAlmostEqual(angle(c, a, b), 62.8130099) 44 | self.assertAlmostEqual(angle(b, a, c), 62.8130099) 45 | 46 | def test_dihedrals(self): 47 | # Test `dihedral` function 48 | a = Atom(0) 49 | b = Atom(4) 50 | c = Atom(7) 51 | d = Atom(10) 52 | 53 | self.assertAlmostEqual(dihedral(a, b, c, d), -80.113001) 54 | self.assertAlmostEqual(dihedral(d, c, b, a), -80.113001) 55 | self.assertAlmostEqual(dihedral(a, c, b, d), 80.113001) 56 | self.assertAlmostEqual(dihedral(d, b, c, a), 80.113001) 57 | 58 | def test_center(self): 59 | # Test `center` function. 60 | sel = Selection('all') 61 | # Center of selection 62 | self.assertAlmostEqualSeqs(list(center(sel)), [-0.0001906, 0.0004762, -0.0001429]) 63 | 64 | res = Residue(0) 65 | # Center of residue 66 | self.assertAlmostEqualSeqs(list(center(res)), [-1.3403333, 2.1406667, 0.967]) 67 | 68 | atom = Atom(0) 69 | # Center of atom - atom coordinates 70 | self.assertAlmostEqualSeqs(list(center(atom)), [-1.493, 1.9, 1.28]) 71 | 72 | # Center of atom iterables 73 | # The manuall computation seems to differ a bit from the `atomsel.center` 74 | self.assertAlmostEqualSeqs(list(center(iter(sel))), [-0.0001905, 0.0004762, -0.0001429]) 75 | self.assertAlmostEqualSeqs(list(center((Atom(i) for i in xrange(10)))), [-0.146, 0.3756, 0.3972]) 76 | -------------------------------------------------------------------------------- /pyvmd/tests/test_molecules.py: -------------------------------------------------------------------------------- 1 | """ 2 | Tests for molecule utilities. 3 | """ 4 | import VMD 5 | from Molecule import Molecule as _Molecule 6 | 7 | from pyvmd.molecules import FORMAT_PDB, Molecule, MoleculeManager 8 | from pyvmd.representations import Representation 9 | 10 | from .utils import data, PyvmdTestCase 11 | 12 | 13 | class TestMolecule(PyvmdTestCase): 14 | """ 15 | Test `Molecule` class. 16 | """ 17 | def setUp(self): 18 | molid = VMD.molecule.load('psf', data('water.psf'), 'pdb', data('water.pdb')) 19 | VMD.molecule.read(molid, 'dcd', data('water.1.dcd'), waitfor=-1) 20 | self.molid = molid 21 | 22 | def test_molecule_properties(self): 23 | # Test basic properties of Molecule 24 | mol = Molecule(self.molid) 25 | 26 | # Check frame property 27 | mol.frame = 3 28 | self.assertEqual(VMD.molecule.get_frame(self.molid), 3) 29 | self.assertEqual(mol.frame, 3) 30 | 31 | mol.frame = 8 32 | self.assertEqual(VMD.molecule.get_frame(self.molid), 8) 33 | self.assertEqual(mol.frame, 8) 34 | 35 | # Check name property 36 | mol.name = 'My precious' 37 | self.assertEqual(mol.name, 'My precious') 38 | self.assertEqual(VMD.molecule.name(self.molid), 'My precious') 39 | mol.name = 'The Ring' 40 | self.assertEqual(mol.name, 'The Ring') 41 | self.assertEqual(VMD.molecule.name(self.molid), 'The Ring') 42 | 43 | # Check molecule property 44 | self.assertIsInstance(mol.molecule, _Molecule) 45 | 46 | # Check error if molecule does not exists 47 | self.assertRaises(ValueError, Molecule, 66000) 48 | 49 | def test_visible_property(self): 50 | # Test `visible` property 51 | mol = Molecule(self.molid) 52 | 53 | self.assertTrue(mol.visible) 54 | 55 | mol.visible = False 56 | self.assertFalse(mol.visible) 57 | self.assertFalse(VMD.molecule.get_visible(self.molid)) 58 | 59 | mol.visible = True 60 | self.assertTrue(mol.visible) 61 | self.assertTrue(VMD.molecule.get_visible(self.molid)) 62 | 63 | def test_molecule_create(self): 64 | # Test molecule creation 65 | old_molids = VMD.molecule.listall() 66 | created = Molecule.create() 67 | self.assertNotIn(created.molid, old_molids) 68 | self.assertTrue(VMD.molecule.exists(created.molid)) 69 | self.assertEqual(VMD.molecule.get_filenames(created.molid), []) 70 | self.assertEqual(VMD.molecule.get_filetypes(created.molid), []) 71 | 72 | # Check create with name 73 | other = Molecule.create('The One') 74 | self.assertEqual(other.name, 'The One') 75 | self.assertTrue(VMD.molecule.exists(other.molid)) 76 | self.assertEqual(VMD.molecule.get_filenames(other.molid), []) 77 | self.assertEqual(VMD.molecule.get_filetypes(other.molid), []) 78 | 79 | def test_molecule_delete(self): 80 | # Test molecule deletion 81 | mol = Molecule(self.molid) 82 | mol.delete() 83 | self.assertFalse(VMD.molecule.exists(self.molid)) 84 | 85 | def test_molecule_load(self): 86 | # Test file loading 87 | mol = Molecule.create() 88 | mol.load(data('water.psf')) 89 | self.assertEqual(VMD.molecule.get_filenames(mol.molid), [data('water.psf')]) 90 | self.assertEqual(VMD.molecule.get_filetypes(mol.molid), ['psf']) 91 | mol.load(data('water.pdb'), FORMAT_PDB) 92 | self.assertEqual(VMD.molecule.get_filenames(mol.molid), [data('water.psf'), data('water.pdb')]) 93 | self.assertEqual(VMD.molecule.get_filetypes(mol.molid), ['psf', 'pdb']) 94 | mol.load(data('water.1.dcd')) 95 | self.assertEqual(VMD.molecule.get_filenames(mol.molid), 96 | [data('water.psf'), data('water.pdb'), data('water.1.dcd')]) 97 | self.assertEqual(VMD.molecule.get_filetypes(mol.molid), ['psf', 'pdb', 'dcd']) 98 | 99 | self.assertRaises(ValueError, mol.load, 'no_extension') 100 | 101 | def test_molecule_comparison(self): 102 | # Test molecule comparison 103 | mol1 = Molecule(self.molid) 104 | mol2 = Molecule(self.molid) 105 | other = Molecule.create() 106 | 107 | self.assertEqual(mol1, mol1) 108 | self.assertEqual(mol1, mol2) 109 | self.assertNotEqual(mol1, other) 110 | self.assertNotEqual(mol2, other) 111 | 112 | def test_frames(self): 113 | # Test frames wrapper 114 | 115 | # Utility to get x coordinate through the trajectory to check results 116 | sel = VMD.atomsel.atomsel('index 0', molid=self.molid) 117 | 118 | def _get_x_coord(): 119 | # Utility to get x coordinate through trajectory 120 | values = [] 121 | for frame in xrange(VMD.molecule.numframes(self.molid)): 122 | sel.frame = frame 123 | values.append(sel.get('x')[0]) 124 | return values 125 | 126 | # Check number of frames and iterator 127 | mol = Molecule(self.molid) 128 | self.assertEqual(len(mol.frames), 13) 129 | self.assertEqual(list(mol.frames), range(13)) 130 | 131 | # Test frame duplication - duplicate focused frame 132 | mol.frame = 3 133 | mol.frames.copy() 134 | 135 | # Check there is one more frame 136 | self.assertEqual(len(mol.frames), 14) 137 | coords = [-1.493, -1.4911567, -1.4851371, -1.4858487, -1.4773947, -1.4746015, -1.4673382, -1.4535547, 138 | -1.4307435, -1.4120502, -1.3853478, -1.3674825, -1.3421925, -1.4858487] 139 | self.assertAlmostEqualSeqs(_get_x_coord(), coords) 140 | # Check molecule is focused to the new frame 141 | self.assertEqual(mol.frame, 13) 142 | 143 | # Test frame duplication - duplicate defined frame 144 | mol.frames.copy(4) 145 | # Check there is one more frame 146 | self.assertEqual(len(mol.frames), 15) 147 | coords = [-1.493, -1.4911567, -1.4851371, -1.4858487, -1.4773947, -1.4746015, -1.4673382, -1.4535547, 148 | -1.4307435, -1.4120502, -1.3853478, -1.3674825, -1.3421925, -1.4858487, -1.4773947] 149 | self.assertAlmostEqualSeqs(_get_x_coord(), coords) 150 | # Molecule is focused to the new frame 151 | self.assertEqual(mol.frame, 14) 152 | 153 | # Test frame deletion - positive index 154 | del mol.frames[14] 155 | self.assertEqual(len(mol.frames), 14) 156 | coords = [-1.493, -1.4911567, -1.4851371, -1.4858487, -1.4773947, -1.4746015, -1.4673382, -1.4535547, 157 | -1.4307435, -1.4120502, -1.3853478, -1.3674825, -1.3421925, -1.4858487] 158 | self.assertAlmostEqualSeqs(_get_x_coord(), coords) 159 | 160 | # Test frame deletion - negative index 161 | del mol.frames[-2] 162 | self.assertEqual(len(mol.frames), 13) 163 | coords = [-1.493, -1.4911567, -1.4851371, -1.4858487, -1.4773947, -1.4746015, -1.4673382, -1.4535547, 164 | -1.4307435, -1.4120502, -1.3853478, -1.3674825, -1.4858487] 165 | self.assertAlmostEqualSeqs(_get_x_coord(), coords) 166 | 167 | # Check deletion of frame slice 168 | del mol.frames[2:11:3] 169 | self.assertEqual(len(mol.frames), 10) 170 | coords = [-1.493, -1.4911567, -1.4858487, -1.4773947, -1.4673382, -1.4535547, -1.4120502, -1.3853478, 171 | -1.3674825, -1.4858487] 172 | self.assertAlmostEqualSeqs(_get_x_coord(), coords) 173 | 174 | # Invalid key for slice 175 | with self.assertRaises(TypeError): 176 | del mol.frames[None] 177 | 178 | 179 | class TestMoleculeManager(PyvmdTestCase): 180 | """ 181 | Test `MoleculeManager` class. 182 | """ 183 | def test_no_molecules(self): 184 | man = MoleculeManager() 185 | 186 | self.assertEqual(len(man), 0) 187 | with self.assertRaises(ValueError): 188 | man[0] 189 | with self.assertRaises(ValueError): 190 | man['molecule'] 191 | with self.assertRaises(TypeError): 192 | man[None] 193 | with self.assertRaises(ValueError): 194 | del man[0] 195 | with self.assertRaises(ValueError): 196 | del man['molecule'] 197 | self.assertEqual(list(man), []) 198 | with self.assertRaises(ValueError): 199 | man.top 200 | 201 | def test_molecules(self): 202 | mol1 = Molecule(VMD.molecule.new('Mine')) 203 | mol2 = Molecule(VMD.molecule.new('Other')) 204 | VMD.molecule.set_top(mol1.molid) 205 | man = MoleculeManager() 206 | 207 | self.assertEqual(len(man), 2) 208 | self.assertEqual(man[mol1.molid], mol1) 209 | self.assertEqual(man['Mine'], mol1) 210 | self.assertEqual(man[mol2.molid], mol2) 211 | self.assertEqual(man['Other'], mol2) 212 | self.assertEqual(list(man), [mol1, mol2]) 213 | self.assertIn(mol1, man) 214 | self.assertIn(mol2, man) 215 | # Check top property 216 | self.assertEqual(man.top, mol1) 217 | man.top = mol2 218 | self.assertEqual(VMD.molecule.get_top(), mol2.molid) 219 | self.assertEqual(man.top, mol2) 220 | 221 | # Try deletion - using name 222 | del man['Mine'] 223 | self.assertFalse(VMD.molecule.exists(mol1.molid)) 224 | # Check the other attributes 225 | self.assertEqual(len(man), 1) 226 | with self.assertRaises(ValueError): 227 | man[mol1.molid] 228 | with self.assertRaises(ValueError): 229 | man['Mine'] 230 | self.assertEqual(man[mol2.molid], mol2) 231 | self.assertEqual(man['Other'], mol2) 232 | self.assertEqual(list(man), [mol2]) 233 | self.assertNotIn(mol1, man) 234 | self.assertIn(mol2, man) 235 | 236 | # Try deletion - using molid 237 | del man[mol2.molid] 238 | self.assertFalse(VMD.molecule.exists(mol2.molid)) 239 | # Check the other attributes 240 | self.assertEqual(len(man), 0) 241 | with self.assertRaises(ValueError): 242 | man[mol2.molid] 243 | with self.assertRaises(ValueError): 244 | man['Other'] 245 | self.assertEqual(list(man), []) 246 | self.assertNotIn(mol1, man) 247 | self.assertNotIn(mol2, man) 248 | 249 | # Check second deletions raises ValueError 250 | with self.assertRaises(ValueError): 251 | del man[mol1.molid] 252 | with self.assertRaises(ValueError): 253 | del man[mol2.molid] 254 | with self.assertRaises(ValueError): 255 | del man['Mine'] 256 | with self.assertRaises(ValueError): 257 | del man['Other'] 258 | 259 | def test_remote_actions(self): 260 | # Test manager can cope with molecule changes which happen without its knowledge 261 | man = MoleculeManager() 262 | 263 | # Create a molecule 264 | mol1 = Molecule(VMD.molecule.new('Unique')) 265 | 266 | # Manager finds it exists and adds it into name cache 267 | self.assertEqual(man['Unique'], mol1) 268 | 269 | # Delete the molecule and create other with the same name 270 | VMD.molecule.delete(mol1.molid) 271 | mol2 = Molecule(VMD.molecule.new('Unique')) 272 | 273 | # Manager correctly returns the new molecule 274 | self.assertEqual(man['Unique'], mol2) 275 | 276 | 277 | class TestRepresentationManager(PyvmdTestCase): 278 | """ 279 | Test `RepresentationManager` class. 280 | """ 281 | def setUp(self): 282 | molid = VMD.molecule.load('psf', data('water.psf'), 'pdb', data('water.pdb')) 283 | self.mol = Molecule(molid) 284 | 285 | def test_property(self): 286 | # Test Molecule.representations property 287 | man = self.mol.representations 288 | 289 | self.assertEqual(man.molecule, self.mol) 290 | 291 | with self.assertRaises(AttributeError): 292 | self.mol.representations = Representation('rep0') 293 | 294 | def test_len(self): 295 | self.assertEqual(len(self.mol.representations), 1) 296 | VMD.molrep.addrep(self.mol.molid) 297 | self.assertEqual(len(self.mol.representations), 2) 298 | 299 | def test_getitem(self): 300 | VMD.molrep.addrep(self.mol.molid) 301 | 302 | # Test positive indexes 303 | self.assertEqual(self.mol.representations[0], Representation('rep0')) 304 | self.assertEqual(self.mol.representations[1], Representation('rep1')) 305 | with self.assertRaises(IndexError): 306 | self.mol.representations[2] 307 | # Check negative indexes 308 | self.assertEqual(self.mol.representations[-2], Representation('rep0')) 309 | self.assertEqual(self.mol.representations[-1], Representation('rep1')) 310 | with self.assertRaises(IndexError): 311 | self.mol.representations[-3] 312 | 313 | # Test representation names 314 | self.assertEqual(self.mol.representations['rep0'], Representation('rep0')) 315 | self.assertEqual(self.mol.representations['rep1'], Representation('rep1')) 316 | with self.assertRaises(KeyError): 317 | self.mol.representations['junk'] 318 | 319 | # Test slices 320 | self.assertEqual(self.mol.representations[::-1], [Representation('rep1'), Representation('rep0')]) 321 | self.assertEqual(self.mol.representations[:10], [Representation('rep0'), Representation('rep1')]) 322 | 323 | # Test type error 324 | with self.assertRaises(TypeError): 325 | self.mol.representations[None] 326 | 327 | def test_iter(self): 328 | self.assertEqual(list(self.mol.representations), [Representation('rep0')]) 329 | 330 | VMD.molrep.addrep(self.mol.molid) 331 | 332 | self.assertEqual(list(self.mol.representations), [Representation('rep0'), Representation('rep1')]) 333 | -------------------------------------------------------------------------------- /pyvmd/tests/test_representations.py: -------------------------------------------------------------------------------- 1 | """ 2 | Tests for molecule representations. 3 | """ 4 | from VMD import molecule as _molecule, molrep as _molrep 5 | 6 | from pyvmd.atoms import Selection 7 | from pyvmd.molecules import Molecule 8 | from pyvmd.representations import (Color, COLOR_COLOR, COLOR_NAME, COLOR_TYPE, DRAW_LINES, DRAW_POINTS, Representation, 9 | Style) 10 | 11 | from .utils import data, PyvmdTestCase 12 | 13 | 14 | class TestRepresentation(PyvmdTestCase): 15 | """ 16 | Test `Representation` class. 17 | """ 18 | def setUp(self): 19 | self.molid = _molecule.load('psf', data('water.psf'), 'pdb', data('water.pdb')) 20 | self.other_molid = _molecule.load('psf', data('water.psf'), 'pdb', data('water.pdb')) 21 | _molecule.set_top(self.molid) 22 | 23 | def test_init(self): 24 | # Test molecule is used if defined 25 | rep = Representation('rep0', Molecule(self.other_molid)) 26 | self.assertEqual(rep.molecule, Molecule(self.other_molid)) 27 | 28 | def test_init_top(self): 29 | # Test top molecule is used if molecule isn't defined 30 | rep = Representation('rep0') 31 | self.assertEqual(rep.molecule, Molecule(self.molid)) 32 | 33 | def test_init_non_existing(self): 34 | # Test top molecule is used if molecule isn't defined 35 | with self.assertRaises(ValueError): 36 | Representation('JUNK') 37 | 38 | def test_comparison(self): 39 | rep_1 = Representation('rep0') 40 | rep_2 = Representation('rep0') 41 | rep_explicit = Representation('rep0', Molecule(self.molid)) 42 | rep_new = Representation.create() 43 | rep_mol2 = Representation('rep0', Molecule(self.other_molid)) 44 | 45 | self.assertEqual(rep_1, rep_1) 46 | self.assertEqual(rep_1, rep_2) 47 | self.assertEqual(rep_1, rep_explicit) 48 | self.assertNotEqual(rep_1, rep_new) 49 | self.assertNotEqual(rep_1, rep_mol2) 50 | 51 | def test_create(self): 52 | # Test new representaion is created 53 | Representation.create(Molecule(self.other_molid)) 54 | self.assertEqual(_molrep.num(self.other_molid), 2) 55 | 56 | def test_create_top(self): 57 | # Test new representation is created in top molecule 58 | Representation.create() 59 | self.assertEqual(_molrep.num(self.molid), 2) 60 | 61 | def test_delete(self): 62 | rep = Representation('rep0') 63 | rep.delete() 64 | self.assertEqual(_molrep.num(self.molid), 0) 65 | 66 | def test_name(self): 67 | rep = Representation('rep0') 68 | self.assertEqual(rep.name, 'rep0') 69 | with self.assertRaises(AttributeError): 70 | rep.name = 'new_name' 71 | 72 | def test_molecule(self): 73 | rep = Representation('rep0') 74 | self.assertEqual(rep.molecule, Molecule(self.molid)) 75 | with self.assertRaises(AttributeError): 76 | rep.molecule = Molecule(self.molid) 77 | 78 | def test_selection(self): 79 | rep = Representation('rep0') 80 | self.assertEqual(rep.selection, Selection('all')) 81 | 82 | rep.selection = Selection('resid 1 to 4') 83 | self.assertEqual(_molrep.get_selection(self.molid, 0), 'resid 1 to 4') 84 | self.assertEqual(rep.selection, Selection('resid 1 to 4')) 85 | 86 | def test_visible(self): 87 | rep = Representation('rep0') 88 | self.assertTrue(rep.visible) 89 | 90 | rep.visible = False 91 | self.assertFalse(_molrep.get_visible(self.molid, 0)) 92 | self.assertFalse(rep.visible) 93 | 94 | def test_update_selection(self): 95 | rep = Representation('rep0') 96 | self.assertFalse(rep.update_selection) 97 | 98 | rep.update_selection = True 99 | self.assertTrue(_molrep.get_autoupdate(self.molid, 0)) 100 | self.assertTrue(rep.update_selection) 101 | 102 | def test_update_color(self): 103 | rep = Representation('rep0') 104 | self.assertFalse(rep.update_color) 105 | 106 | rep.update_color = True 107 | self.assertTrue(_molrep.get_colorupdate(self.molid, 0)) 108 | self.assertTrue(rep.update_color) 109 | 110 | def test_style(self): 111 | rep = Representation('rep0') 112 | self.assertEqual(rep.style, Style(Representation('rep0'))) 113 | with self.assertRaises(AttributeError): 114 | rep.style = Style(Representation('rep0')) 115 | 116 | def test_color(self): 117 | rep = Representation('rep0') 118 | self.assertEqual(rep.color, Color(Representation('rep0'))) 119 | with self.assertRaises(AttributeError): 120 | rep.style = Color(Representation('rep0')) 121 | 122 | 123 | class TestStyle(PyvmdTestCase): 124 | """ 125 | Test `Style` class. 126 | """ 127 | def setUp(self): 128 | self.molid = _molecule.load('psf', data('water.psf'), 'pdb', data('water.pdb')) 129 | self.rep = Representation('rep0') 130 | self.rep2 = Representation.create() 131 | 132 | def test_comparison(self): 133 | style_1 = Style(self.rep) 134 | style_2 = Style(self.rep) 135 | style_rep2 = Style(self.rep2) 136 | 137 | self.assertEqual(style_1, style_1) 138 | self.assertEqual(style_1, style_2) 139 | self.assertNotEqual(style_1, style_rep2) 140 | 141 | def test_method(self): 142 | style = Style(self.rep) 143 | self.assertEqual(style.method, DRAW_LINES) 144 | 145 | style.method = DRAW_POINTS 146 | self.assertEqual(style.method, DRAW_POINTS) 147 | self.assertEqual(_molrep.get_style(self.molid, 0), 'Points') 148 | 149 | def test_get_parameters(self): 150 | style = Style(self.rep) 151 | self.assertEqual(style.get_parameters(), {'size': 1.}) 152 | 153 | def test_set_parameters(self): 154 | style = Style(self.rep) 155 | style.set_parameters(size=4) 156 | self.assertEqual(_molrep.get_style(self.molid, 0), 'Lines 4') 157 | self.assertEqual(style.get_parameters(), {'size': 4}) 158 | 159 | def test_set_parameters_no_kwargs(self): 160 | style = Style(self.rep) 161 | with self.assertRaises(ValueError): 162 | style.set_parameters() 163 | 164 | def test_set_parameters_invalid_kwargs(self): 165 | style = Style(self.rep) 166 | with self.assertRaises(ValueError): 167 | style.set_parameters(unknown=78) 168 | 169 | 170 | class TestColor(PyvmdTestCase): 171 | """ 172 | Test `Color` class. 173 | """ 174 | def setUp(self): 175 | self.molid = _molecule.load('psf', data('water.psf'), 'pdb', data('water.pdb')) 176 | self.rep = Representation('rep0') 177 | self.rep2 = Representation.create() 178 | 179 | def test_comparison(self): 180 | color_1 = Color(self.rep) 181 | color_2 = Color(self.rep) 182 | color_rep2 = Color(self.rep2) 183 | 184 | self.assertEqual(color_1, color_1) 185 | self.assertEqual(color_1, color_2) 186 | self.assertNotEqual(color_1, color_rep2) 187 | 188 | def test_method(self): 189 | color = Color(self.rep) 190 | self.assertEqual(color.method, COLOR_NAME) 191 | 192 | color.method = COLOR_TYPE 193 | self.assertEqual(color.method, COLOR_TYPE) 194 | self.assertEqual(_molrep.get_color(self.molid, 0), 'Type') 195 | 196 | def test_get_parameters(self): 197 | color = Color(self.rep) 198 | self.assertEqual(color.get_parameters(), {}) 199 | 200 | color.method = COLOR_COLOR 201 | self.assertEqual(color.get_parameters(), {'color': 1}) 202 | 203 | def test_set_parameters(self): 204 | color = Color(self.rep) 205 | color.method = COLOR_COLOR 206 | color.set_parameters(color=4) 207 | self.assertEqual(_molrep.get_color(self.molid, 0), 'ColorID 4') 208 | self.assertEqual(color.get_parameters(), {'color': 4}) 209 | 210 | def test_set_parameters_no_kwargs(self): 211 | color = Color(self.rep) 212 | with self.assertRaises(ValueError): 213 | color.set_parameters() 214 | 215 | def test_set_parameters_invalid_kwargs(self): 216 | color = Color(self.rep) 217 | with self.assertRaises(ValueError): 218 | color.set_parameters(unknown=78) 219 | -------------------------------------------------------------------------------- /pyvmd/tests/test_utils.py: -------------------------------------------------------------------------------- 1 | """ 2 | Tests for utilities. 3 | """ 4 | import VMD 5 | 6 | from .utils import PyvmdTestCase 7 | 8 | 9 | class TestPyvmdTestCase(PyvmdTestCase): 10 | """ 11 | Test `PyvmdTestCase` class. 12 | """ 13 | def test_teardown_deletes_molecules(self): 14 | VMD.molecule.new('foo') 15 | VMD.molecule.new('bar') 16 | 17 | class Foo(PyvmdTestCase): 18 | def runTest(self): 19 | pass 20 | 21 | Foo().tearDown() 22 | self.assertEqual(VMD.molecule.num(), 0) 23 | 24 | def test_assert_almost_equal_seqs(self): 25 | self.assertAlmostEqualSeqs([], []) 26 | self.assertAlmostEqualSeqs((), []) 27 | self.assertAlmostEqualSeqs(set(), []) 28 | self.assertAlmostEqualSeqs([1.0], [1.0]) 29 | self.assertRaises(self.failureException, self.assertAlmostEqualSeqs, [1.0], []) 30 | self.assertRaises(self.failureException, self.assertAlmostEqualSeqs, [], [1.0]) 31 | 32 | self.assertAlmostEqualSeqs([1.00000001], [1.0]) 33 | self.assertRaises(self.failureException, self.assertAlmostEqualSeqs, [1.0000001], [1.0]) 34 | 35 | self.assertAlmostEqualSeqs([1.0], [1.1], places=0) 36 | self.assertRaises(self.failureException, self.assertAlmostEqualSeqs, [1.0], [1.1], places=1) 37 | 38 | self.assertAlmostEqualSeqs([1.0], [1.0], delta=0.5) 39 | self.assertAlmostEqualSeqs([1.0], [1.1], delta=0.5) 40 | self.assertAlmostEqualSeqs([1.1], [1.0], delta=0.5) 41 | self.assertRaises(self.failureException, self.assertAlmostEqualSeqs, [1.0], [1.1], delta=0.05) 42 | 43 | self.assertRaises(TypeError, self.assertAlmostEqualSeqs, [1.0], [1.1], places=2, delta=0.5) 44 | 45 | self.assertAlmostEqualSeqs([1.0, -10, 42], [1.0, -10, 42]) 46 | self.assertRaises(self.failureException, self.assertAlmostEqualSeqs, [1.0, -10, 42], [1.0, -10, 41]) 47 | 48 | self.assertRaises(self.failureException, self.assertAlmostEqualSeqs, None, []) 49 | self.assertRaises(self.failureException, self.assertAlmostEqualSeqs, [], None) 50 | -------------------------------------------------------------------------------- /pyvmd/tests/utils.py: -------------------------------------------------------------------------------- 1 | """ 2 | Utilities for tests. 3 | """ 4 | import difflib 5 | import os 6 | import pprint 7 | import unittest 8 | from unittest.util import safe_repr 9 | 10 | import VMD 11 | 12 | from pyvmd.collectors import Collector 13 | 14 | 15 | def data(filename): 16 | """ 17 | Return full filename of the test datafile. 18 | """ 19 | return os.path.join(os.path.dirname(__file__), 'data', filename) 20 | 21 | 22 | class PyvmdTestCase(unittest.TestCase): 23 | """ 24 | Enhanced test case for pyvmd tests. 25 | """ 26 | def tearDown(self): 27 | # Delete all molecule when we're finished 28 | for molid in VMD.molecule.listall(): 29 | VMD.molecule.delete(molid) 30 | # Restore Collector's auto_name_counter 31 | Collector.auto_name_counter = 0 32 | 33 | def assertAlmostEqualSeqs(self, seq1, seq2, places=None, msg=None, delta=None): 34 | """ 35 | An assertion for ordered sequences. Compares items one by one with 36 | their differences rounded to the given number of decimal places (default 7). 37 | 38 | Note that decimal places (from zero) are usually not the same 39 | as significant digits (measured from the most signficant digit). 40 | 41 | If the two objects compare equal then they will automatically 42 | compare almost equal. 43 | """ 44 | # This method is copied from unittest's assertSequenceEqual method. 45 | seq_type_name = "sequence" 46 | differing = None 47 | try: 48 | len1 = len(seq1) 49 | except (TypeError, NotImplementedError): 50 | differing = 'First %s has no length. Non-sequence?' % ( 51 | seq_type_name) 52 | 53 | if differing is None: 54 | try: 55 | len2 = len(seq2) 56 | except (TypeError, NotImplementedError): 57 | differing = 'Second %s has no length. Non-sequence?' % ( 58 | seq_type_name) 59 | 60 | if differing is None: 61 | if seq1 == seq2: 62 | return 63 | 64 | seq1_repr = safe_repr(seq1) 65 | seq2_repr = safe_repr(seq2) 66 | if len(seq1_repr) > 30: 67 | seq1_repr = seq1_repr[:30] + '...' 68 | if len(seq2_repr) > 30: 69 | seq2_repr = seq2_repr[:30] + '...' 70 | elements = (seq_type_name.capitalize(), seq1_repr, seq2_repr) 71 | differing = '%ss differ: %s != %s\n' % elements 72 | 73 | for i in xrange(min(len1, len2)): 74 | try: 75 | item1 = seq1[i] 76 | except (TypeError, IndexError, NotImplementedError): 77 | differing += ('\nUnable to index element %d of first %s\n' % 78 | (i, seq_type_name)) 79 | break 80 | 81 | try: 82 | item2 = seq2[i] 83 | except (TypeError, IndexError, NotImplementedError): 84 | differing += ('\nUnable to index element %d of second %s\n' % 85 | (i, seq_type_name)) 86 | break 87 | 88 | try: 89 | self.assertAlmostEqual(item1, item2, places=places, delta=delta) 90 | except self.failureException, error: 91 | differing += ('\nFirst differing element %d:\n%s\n' % 92 | (i, error)) 93 | break 94 | else: 95 | if len1 == len2: 96 | # The sequences are the same. 97 | return 98 | 99 | if len1 > len2: 100 | differing += ('\nFirst %s contains %d additional ' 101 | 'elements.\n' % (seq_type_name, len1 - len2)) 102 | try: 103 | differing += ('First extra element %d:\n%s\n' % 104 | (len2, seq1[len2])) 105 | except (TypeError, IndexError, NotImplementedError): 106 | differing += ('Unable to index element %d ' 107 | 'of first %s\n' % (len2, seq_type_name)) 108 | elif len1 < len2: 109 | differing += ('\nSecond %s contains %d additional ' 110 | 'elements.\n' % (seq_type_name, len2 - len1)) 111 | try: 112 | differing += ('First extra element %d:\n%s\n' % 113 | (len1, seq2[len1])) 114 | except (TypeError, IndexError, NotImplementedError): 115 | differing += ('Unable to index element %d ' 116 | 'of second %s\n' % (len1, seq_type_name)) 117 | standardMsg = differing 118 | diffMsg = '\n' + '\n'.join( 119 | difflib.ndiff(pprint.pformat(seq1).splitlines(), 120 | pprint.pformat(seq2).splitlines())) 121 | standardMsg = self._truncateMessage(standardMsg, diffMsg) 122 | msg = self._formatMessage(msg, standardMsg) 123 | self.fail(msg) 124 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-line-length=120 3 | 4 | [isort] 5 | line_length = 120 6 | order_by_type = false 7 | combine_as_imports = true 8 | # Mark as third party: 9 | # python-mock - 'mock' 10 | # python-numpy - 'numpy' 11 | # VMD modules - 'VMD', 'atomsel', 'Molecule' 12 | known_third_party = mock,numpy,VMD,atomsel,Molecule 13 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from setuptools import setup, find_packages 3 | 4 | from pyvmd import __version__ as version 5 | 6 | 7 | CLASSIFIERS = [ 8 | 'Development Status :: 3 - Alpha', 9 | 'Intended Audience :: Science/Research', 10 | 'License :: OSI Approved :: GNU General Public License v3 (GPLv3)', 11 | 'Operating System :: OS Independent', 12 | 'Programming Language :: Python', 13 | 'Topic :: Scientific/Engineering :: Bio-Informatics', 14 | 'Topic :: Scientific/Engineering :: Chemistry', 15 | 'Topic :: Scientific/Engineering :: Information Analysis', 16 | 'Topic :: Scientific/Engineering :: Visualization', 17 | ] 18 | 19 | 20 | if __name__ == '__main__': 21 | setup( 22 | name='PyVMD', 23 | version=version, 24 | packages=find_packages(exclude=['*.tests', '*.tests.*']), 25 | install_requires=['numpy'], 26 | author="Vlastimil Zíma", 27 | author_email="vlastimil.zima@gmail.com", 28 | description="Python tools for Visual Molecular Dynamics", 29 | url="https://github.com/ziima/pyvmd", 30 | classifiers=CLASSIFIERS, 31 | license='GPLv3', 32 | ) 33 | --------------------------------------------------------------------------------