Hierarchical Views

This part describes how to use hierarchical views. We start by importing everything from the ProDy package:

In [1]: from prody import *

Hierarchical Views

Then we parses a structure to get an AtomGroup instance which has a plain view of atoms:

In [2]: structure = parsePDB('3mkb')

In [3]: structure
Out[3]: <AtomGroup: 3mkb (4776 atoms)>

A hierarchical view of the structure can be simply get by calling the AtomGroup.getHierView() method:

In [4]: hv = structure.getHierView()

In [5]: hv
Out[5]: <HierView: AtomGroup 3mkb (4 chains, 946 residues)>

Indexing

Indexing HierView instances return Chain:

In [6]: hv['A']
Out[6]: <Chain: A from 3mkb (254 residues, 1198 atoms)>

In [7]: hv['B']
Out[7]: <Chain: B from 3mkb (216 residues, 1193 atoms)>

In [8]: hv['Z'] # This will return None, which means chain Z does not exist

The length of the hv variable gives the number of chains in the structure:

In [9]: len(hv)
Out[9]: 4

In [10]: hv.numChains()
Out[10]: 4

It is also possible to get a Residue by directly indexing the HierView instance:

In [11]: hv['A', 100]
Out[11]: <Residue: MET 100 from Chain A from 3mkb (8 atoms)>

Insertion codes can also be passed:

In [12]: hv['A', 100, 'B']

This does not return anything, since residue 100B does not exist.

Iterations

One can iterate over HierView instances to get chains:

In [13]: for chain in hv:
   ....:     print(chain)
   ....: 
Chain A
Chain B
Chain C
Chain D

It is also possible to get a list() of chains simply as follows:

In [14]: chains = list(hv)

In [15]: chains
Out[15]: 
[<Chain: A from 3mkb (254 residues, 1198 atoms)>,
 <Chain: B from 3mkb (216 residues, 1193 atoms)>,
 <Chain: C from 3mkb (245 residues, 1189 atoms)>,
 <Chain: D from 3mkb (231 residues, 1196 atoms)>]

Residues

In addition, one can also iterate over all residues:

In [16]: for i, residue in enumerate(hv.iterResidues()):
   ....:     if i == 4: break
   ....:     print(residue)
   ....: 
ALA 1
PHE 2
THR 3
GLY 4

Chains

We obtain a Chain object for chain A by indexing the HierView object.

In [17]: chA = hv['A']

In [18]: chA
Out[18]: <Chain: A from 3mkb (254 residues, 1198 atoms)>

The length of the chain is equal to the number of residues in it:

In [19]: len(chA)
Out[19]: 254

In [20]: chA.numResidues()
Out[20]: 254

Indexing

Indexing a Chain instance with a scalar returns an Atom like indexing an Atomgroup:

In [21]: chA[1]
Out[21]: <Atom: CA from 3mkb (index 1)>

In [22]: chA[1000]
Out[22]: <Atom: C from 3mkb (index 1000)>

Indexing a Chain instance with a tuple returns an Residue. This can also be achieved with the prody.atomic.chain.Chain.getResidue() method.

In [23]: chA[(1,)]
Out[23]: <Residue: ALA 1 from Chain A from 3mkb (5 atoms)>

In [24]: chA.getResidue(1)
Out[24]: <Residue: ALA 1 from Chain A from 3mkb (5 atoms)>

The use of a tuple is necessary to allow inclusion of insertion codes. In this case there aren’t any so None is returned.

In [25]: chA[1, 'A'] # Residue 1 with insertion code A also does not exist

Iterations

Iterating over a chain also yields residues:

In [26]: for i, residue in enumerate(chA):
   ....:     if i == 4: break
   ....:     print(residue)
   ....: 
ALA 1
PHE 2
THR 3
GLY 4

Note that water atoms, each constituting a residue, are also part of a chain if they are labeled with that chain’s identifier.

This enables getting a list() of residues simply as follows:

In [27]: chA_residues = list(chA)

In [28]: chA_residues[:4]
Out[28]: 
[<Residue: ALA 1 from Chain A from 3mkb (5 atoms)>,
 <Residue: PHE 2 from Chain A from 3mkb (11 atoms)>,
 <Residue: THR 3 from Chain A from 3mkb (7 atoms)>,
 <Residue: GLY 4 from Chain A from 3mkb (4 atoms)>]

In [29]: chA_residues[-4:]
Out[29]: 
[<Residue: HOH 471 from Chain A from 3mkb (1 atoms)>,
 <Residue: HOH 485 from Chain A from 3mkb (1 atoms)>,
 <Residue: HOH 490 from Chain A from 3mkb (1 atoms)>,
 <Residue: HOH 493 from Chain A from 3mkb (1 atoms)>]

Get data

All methods defined for AtomGroup class are also defined for Chain and Residue classes:

In [30]: chA.getCoords()
Out[30]: 
array([[ -2.139,  17.026, -13.287],
       [ -1.769,  15.572, -13.111],
       [ -0.296,  15.257, -13.467],
       ...,
       [ -5.843,  17.181, -16.86 ],
       [-13.199,  -9.21 , -49.692],
       [ -0.459,   0.378, -46.156]])

In [31]: chA.getBetas()
Out[31]: array([59.35, 59.14, 58.5 , ..., 57.79, 47.77, 40.77])

Selections

Finally, you can select atoms from a Chain instance:

In [32]: chA_backbone = chA.select('backbone')

In [33]: chA_backbone
Out[33]: <Selection: '(backbone) and (chain A)' from 3mkb (560 atoms)>

In [34]: chA_backbone.getSelstr()
Out[34]: '(backbone) and (chain A)'

As you see, the selection string passed by the user is augmented with “chain” keyword and identifier automatically to provide internal consistency:

In [35]: structure.select(chA_backbone.getSelstr())
Out[35]: <Selection: '(backbone) and (chain A)' from 3mkb (560 atoms)>

Residues

In [36]: chA_res1 = chA.getResidue(1)

In [37]: chA_res1
Out[37]: <Residue: ALA 1 from Chain A from 3mkb (5 atoms)>

Indexing

Residue instances can be indexed to get individual atoms:

In [38]: chA_res1['CA']
Out[38]: <Atom: CA from 3mkb (index 1)>

In [39]: chA_res1['CB']
Out[39]: <Atom: CB from 3mkb (index 4)>

In [40]: chA_res1['X'] # if atom does not exist, None is returned

Iterations

Iterating over a residue instance yields Atom instances:

In [41]: for i, atom in enumerate(chA_res1):
   ....:     if i == 4: break
   ....:     print(atom)
   ....: 
Atom N (index 0)
Atom CA (index 1)
Atom C (index 2)
Atom O (index 3)

This makes it easy to get a list() of atoms:

In [42]: list(chA_res1)
Out[42]: 
[<Atom: N from 3mkb (index 0)>,
 <Atom: CA from 3mkb (index 1)>,
 <Atom: C from 3mkb (index 2)>,
 <Atom: O from 3mkb (index 3)>,
 <Atom: CB from 3mkb (index 4)>]

Get data

All methods defined for AtomGroup class are also defined for Residue class:

In [43]: chA_res1.getCoords()
Out[43]: 
array([[ -2.139,  17.026, -13.287],
       [ -1.769,  15.572, -13.111],
       [ -0.296,  15.257, -13.467],
       [  0.199,  14.155, -13.155],
       [ -2.752,  14.639, -13.898]])

In [44]: chA_res1.getBetas()
Out[44]: array([59.35, 59.14, 58.5 , 59.13, 59.02])

Selections

Finally, you can select atoms from a Residue instance:

In [45]: chA_res1_bb = chA_res1.select('backbone')

In [46]: chA_res1_bb
Out[46]: <Selection: '(backbone) and ... and (chain A))' from 3mkb (4 atoms)>

In [47]: chA_res1_bb.getSelstr()
Out[47]: '(backbone) and (resnum 1 and (chain A))'

Again, the selection string is augmented with the chain identifier and residue number (resnum).

Atoms

The lowest level of the hierarchical view contains Atom instances.

In [48]: chA_res1_CA = chA_res1['CA']

In [49]: chA_res1_CA
Out[49]: <Atom: CA from 3mkb (index 1)>

Get atomic data

All methods defined for AtomGroup class are also defined for Atom class with the difference that method names are singular (except for coordinates):

In [50]: chA_res1_CA.getCoords()
Out[50]: array([ -1.769,  15.572, -13.111])

In [51]: chA_res1_CA.getBeta()
Out[51]: 59.14

State Changes

A HierView instance represents the state of an AtomGroup instance at the time it is built. When chain identifiers or residue numbers change, the state that hierarchical view represents may not match the current state of the atom group:

In [52]: chA.setChid('X')

In [53]: chA
Out[53]: <Chain: X from 3mkb (254 residues, 1198 atoms)>

In [54]: hv['X'] # returns None, since hierarchical view is not updated

In [55]: hv.update() # this updates hierarchical view

In [56]: hv['X']
Out[56]: <Chain: X from 3mkb (254 residues, 1198 atoms)>

When this is the case, HierView.update() method can be used to update hierarchical view.