Atom Groups

The example below shows how to build an AtomGroup from scratch. We start by importing everything from the ProDy package and the NumPy package:

In [1]: from prody import *

In [2]: from numpy import *

In [3]: from pylab import *

In [4]: ion()

Building an Atom Group

The best way to start constructing an AtomGroup is by setting the coordinates first. Number of atoms will be automatically set according to the size of the coordinate data array:

In [5]: wtr1 = AtomGroup('Water')

In [6]: wtr1
Out[6]: <AtomGroup: Water (0 atoms; no coordinates)>

In [7]: coords = array([[1, 0, 0], [0, 0, 0], [0, 0, 1]], dtype=float)

In [8]: coords
Out[8]: 
array([[1., 0., 0.],
       [0., 0., 0.],
       [0., 0., 1.]])

In [9]: wtr1.setCoords(coords)

In [10]: wtr1
Out[10]: <AtomGroup: Water (3 atoms)>

Attributes

Attributes must be passed in a list or an array whose size is the same as the number of atoms.

In [11]: wtr1.setNames(['H', 'O', 'H'])

In [12]: wtr1.setResnums([1, 1, 1])

In [13]: wtr1.setResnames(['WAT', 'WAT', 'WAT'])

Accessing data will return a copy of the data. This means you can manipulate the return values safely without affecting the original values. For example:

In [14]: names = wtr1.getNames()

In [15]: names[0] = names[0] + '1'

In [16]: names
Out[16]: array(['H1', 'O', 'H'], dtype='|S6')
In [17]: wtr1.getNames()
Out[17]: array(['H', 'O', 'H'], dtype='|S6')

Atoms

Atoms are represented by instance of Atom.

Iteration

Atoms in an AtomGroup can be iterated over

In [18]: for a in wtr1:
   ....:     print(a)
   ....: 
Atom H (index 0)
Atom O (index 1)
Atom H (index 2)

Indexing

Atoms in an atom group can be accessed via indexing:

In [19]: a = wtr1[0]

In [20]: a
Out[20]: <Atom: H from Water (index 0)>
In [21]: a.getCoords()
Out[21]: array([1., 0., 0.])

Coordinate sets

Let’s add another coordinate set to the atom group:

In [22]: wtr1.addCoordset(array([[0, 1, 0], [0, 0, 0], [0, 0, 1.1]], dtype=float))

In [23]: wtr1
Out[23]: <AtomGroup: Water (3 atoms; active #0 of 2 coordsets)>

Note that the number of coordinate sets is now 2, but the active coordinate set index is still 0. Active coordinate set index can be changed for AtomGroup as follows:

In [24]: a.setACSIndex(1)

In [25]: a
Out[25]: <Atom: H from Water (index 0; active #1 of 2 coordsets)>

Changing active coordinate set for an atom group, does not affect the active coordinate set of the atom group:

In [26]: wtr1
Out[26]: <AtomGroup: Water (3 atoms; active #0 of 2 coordsets)>

Coordinates for the atom group will be returned from the active coordinate set

In [27]: a.getCoords()
Out[27]: array([0., 1., 0.])
In [28]: a.setACSIndex(0)

In [29]: a.getCoords()
Out[29]: array([1., 0., 0.])

Iterations

Coordinate sets can also be iterated over for Atom and AtomGroup instances:

In [30]: for xyz in a.iterCoordsets():
   ....:     print(xyz)
   ....: 
[1. 0. 0.]
[0. 1. 0.]

Copying and Merging

Now let’s make another copy of this water:

In [31]: wtr2 = wtr1.copy()

In [32]: wtr2
Out[32]: <AtomGroup: Water (3 atoms; active #0 of 2 coordsets)>

Translate copy

Let’s translate the coordinates of wtr2 so that it does not overlap with wtr1

In [33]: wtr2.setCoords(wtr2.getCoords() + 2)

In [34]: wtr2.getCoords()
Out[34]: 
array([[3., 2., 2.],
       [2., 2., 2.],
       [2., 2., 3.]])

Above operation only translated the coordinate set at index 0

In [35]: wtr2.setACSIndex(1)

In [36]: wtr2.getCoords()
Out[36]: 
array([[0. , 1. , 0. ],
       [0. , 0. , 0. ],
       [0. , 0. , 1.1]])

In [37]: wtr2.setCoords(wtr2.getCoords() + 2)  # translate the 2nd coordset as well

Change attributes

Before we merge wtr1 and wtr2, let’s change resid’s of wtr2:

In [38]: wtr2.setResnums( [2, 2, 2] )

In [39]: wtr2.getResnums()
Out[39]: array([2, 2, 2])

We can do this in an alternate way too:

In [40]: wtr2.select('all').setResnums(2)

In [41]: wtr2.getResnums()
Out[41]: array([2, 2, 2])

Merge two copies

Let’s merge the two water atom groups:

In [42]: wtrs = wtr1 + wtr2

In [43]: wtrs
Out[43]: <AtomGroup: Water + Water (6 atoms; active #0 of 2 coordsets)>

In [44]: wtrs.getCoords()
Out[44]: 
array([[1., 0., 0.],
       [0., 0., 0.],
       [0., 0., 1.],
       [3., 2., 2.],
       [2., 2., 2.],
       [2., 2., 3.]])

In [45]: wtrs.getNames()
Out[45]: array(['H', 'O', 'H', 'H', 'O', 'H'], dtype='|S6')

In [46]: wtrs.getResnums()
Out[46]: array([1, 1, 1, 2, 2, 2])

Hierarchical views

Hierarchical views of atom groups are represented by HierView.

Residues (and also chains) in an atom group can also be iterated over

In [47]: for res in wtrs.getHierView().iterResidues():
   ....:     print(res)
   ....: 
WAT 1
WAT 2

Renaming an atom group

Finally, it’s is possible to change the name of wtrs from “Water + Water” to something shorter:

In [48]: wtrs.setTitle('2Waters')

In [49]: wtrs
Out[49]: <AtomGroup: 2Waters (6 atoms; active #0 of 2 coordsets)>

Storing data in AtomGroup

Now let’s get an atom group from a PDB file:

In [50]: structure = parsePDB('5uoj')

In addition to what’s in a PDB file, you can store arbitrary atomic attributes in AtomGroup objects.

Set a new attribute

For the purposes of this example, we will manufacture atomic data by dividing the residue number of each atom by 10:

In [51]: myresnum = structure.getResnums() / 10.0

We will add this to the atom group using AtomGroup.setData() method by passing a name for the attribute and the data:

In [52]: structure.setData('myresnum', myresnum)

We can check if a custom atomic attribute exists using AtomGroup.isDataLabel() method:

In [53]: structure.isDataLabel('myresnum')
Out[53]: True

Access subset of data

Custom attributes can be accessed from selections:

In [54]: calpha = structure.calpha

In [55]: calpha.getData('myresnum')
Out[55]: 
array([ 0.5,  0.6,  0.7,  0.8,  0.9,  1. ,  1.1,  1.2,  1.3,  1.4,  1.5,
        1.6,  1.7,  1.8,  1.9,  2. ,  2.1,  2.2,  2.3,  2.4,  2.5,  2.6,
        2.7,  2.8,  2.9,  3. ,  3.1,  3.2,  3.3,  3.4,  3.5,  3.6,  3.7,
        3.8,  3.9,  4. ,  4.1,  4.2,  4.3,  4.4,  4.5,  4.6,  4.7,  4.8,
        4.9,  5. ,  5.1,  5.2,  5.3,  5.4,  5.5,  5.6,  5.7,  5.8,  5.9,
        6. ,  6.1,  6.2,  6.3,  6.4,  6.5,  6.6,  6.7,  6.8,  6.9,  7. ,
        7.1,  7.2,  7.3,  7.4,  7.5,  7.6,  7.7,  7.8,  7.9,  8. ,  8.1,
        8.2,  8.3,  8.4,  8.5,  8.6,  8.7,  8.8,  8.9,  9. ,  9.1,  9.2,
        9.3,  9.4,  9.5,  9.6,  9.7,  9.8,  9.9, 10. , 10.1, 10.2, 10.3,
       10.4, 10.5, 10.6, 10.7, 10.8, 10.9, 11. , 11.1, 11.2, 11.3, 11.4,
       11.5, 11.6, 11.7, 11.8, 11.9, 12. , 12.1, 12.2, 12.3, 12.4, 12.5,
       12.6, 12.7, 12.8, 12.9, 13. , 13.1, 13.2, 13.3, 13.4, 13.5, 13.6,
       13.7, 13.8, 13.9, 14. , 14.1, 14.2, 14.3, 14.4, 14.5, 14.6, 14.7,
       14.8, 14.9, 15. , 15.1, 15.2, 15.3, 15.4, 15.5, 15.6, 15.7, 15.8,
       15.9, 16. , 16.1, 16.2, 16.3, 16.4, 16.5, 16.6, 16.7, 16.8, 16.9,
       17. , 17.1, 17.2, 17.9, 18. , 18.1, 18.2, 18.3, 18.4, 18.5, 18.6,
       18.7, 18.8, 18.9, 19. , 19.1, 19.2, 19.3, 19.4, 19.5, 19.6, 19.7,
       19.8, 19.9, 20. , 20.1, 20.2, 20.3, 20.4, 20.5, 20.6, 20.7, 20.8,
       20.9, 21. , 21.1, 21.2, 21.3, 21.4, 21.5, 21.6, 21.7, 21.8, 21.9,
       22. , 22.1, 22.2, 22.3, 22.4, 22.5, 22.6, 22.7, 22.8, 22.9, 23. ,
       23.1, 23.2, 23.3, 23.4, 23.5, 23.6, 23.7, 23.8, 23.9, 24. , 24.1,
       24.2, 24.3, 24.4, 24.5, 24.6, 24.7, 24.8, 24.9, 25. , 25.1, 25.2,
       25.3, 25.4, 25.5, 25.6, 25.7, 25.8, 25.9, 26. , 26.1, 26.2, 26.3,
       26.4, 26.5, 26.6, 26.7, 26.8, 26.9, 27. , 27.1, 27.2, 27.3, 27.4,
       27.5, 27.6, 27.7, 27.8, 27.9, 28. , 28.1, 28.2, 28.3, 28.4, 28.5,
       28.6, 28.7, 28.8, 28.9, 29. , 29.1, 29.2, 29.3, 29.4, 29.5, 29.6,
       29.7, 29.8, 29.9, 30. , 30.1, 30.2, 30.3, 30.4, 30.5, 30.6, 30.7,
       30.8, 30.9, 31. , 31.1, 31.2, 31.3, 31.4, 31.5, 31.6, 31.7, 31.8,
       31.9, 32. , 32.1, 32.2, 32.3, 32.4, 32.5, 32.6, 32.7, 32.8, 32.9,
       33. , 33.1, 33.2, 33.3, 33.4, 33.5, 33.6, 33.7, 33.8, 33.9, 34. ,
       34.1, 34.2, 34.3, 34.4, 34.5, 34.6, 34.7, 34.8, 34.9, 35. , 35.1,
       35.2, 35.3])

Make selections

Custom atomic attributes can be used in selections:

In [56]: mysel = structure.select('0 < myresnum and myresnum < 10')

In [57]: mysel
Out[57]: <Selection: '0 < myresnum and myresnum < 10' from 5uoj (779 atoms)>

This gives the same result as the following selection:

In [58]: structure.select('0 < resnum and resnum < 100') == mysel
Out[58]: True

Save attributes

It is not possible to save custom attributes in PDB files, but saveAtoms() function can be used them to save in disk for later use:

In [59]: saveAtoms(structure)
Out[59]: '5uoj.ag.npz'

Let’s load it using loadAtoms() function:

In [60]: structure = loadAtoms('5uoj.ag.npz')

In [61]: structure.getData('myresnum')
Out[61]: array([ 0.5,  0.5,  0.5, ..., 77.3, 77.4, 77.5])

Delete an attribute

Finally, when done with an attribute, it can be deleted using AtomGroup.delData() method:

In [62]: structure.delData('myresnum')
Out[62]: array([ 0.5,  0.5,  0.5, ..., 77.3, 77.4, 77.5])