Indexing Angles#

Angles represent the angle between two chemical bonds between atoms in a molecule. There are three atoms and two bonds contained in an angle.

Angle is a molecular container that contains the three atoms and two bonds that make up the angle.

For example, let’s look at the aladip system again.

>>> import sire as sr
>>> mols = sr.load(sr.expand(sr.tutorial_url, ["ala.top", "ala.crd"]))
>>> mol = mols[0]
>>> print(mol)
Molecule( ACE:2   num_atoms=22 num_residues=3 )

We can get all of the angles using the angles() function.

>>> print(mol.angles())
SelectorAngle( size=36
0: Angle( HH31:1 <= CH3:2 => HH32:3 )
1: Angle( HH31:1 <= CH3:2 => C:5 )
2: Angle( HH31:1 <= CH3:2 => HH33:4 )
3: Angle( CH3:2 <= C:5 => N:7 )
4: Angle( CH3:2 <= C:5 => O:6 )
...
31: Angle( N:17 <= CH3:19 => HH32:21 )
32: Angle( H:18 <= N:17 => CH3:19 )
33: Angle( HH31:20 <= CH3:19 => HH33:22 )
34: Angle( HH31:20 <= CH3:19 => HH32:21 )
35: Angle( HH32:21 <= CH3:19 => HH33:22 )
)

The result (a SelectorAngle) is a molecular container for angles. Like all molecular containers, it can be indexed,

>>> print(mol.angles()[0])
Angle( HH31:1 <= CH3:2 => HH32:3 )

sliced

>>> print(mol.angles()[0:5])
SelectorAngle( size=5
0: Angle( HH31:1 <= CH3:2 => HH32:3 )
1: Angle( HH31:1 <= CH3:2 => C:5 )
2: Angle( HH31:1 <= CH3:2 => HH33:4 )
3: Angle( CH3:2 <= C:5 => N:7 )
4: Angle( CH3:2 <= C:5 => O:6 )
)

or accessed by a list of indicies

>>> print(mol.angles()[ [0, 2, 4, 6, 8] ])
SelectorAngle( size=5
0: Angle( HH31:1 <= CH3:2 => HH32:3 )
1: Angle( HH31:1 <= CH3:2 => HH33:4 )
2: Angle( CH3:2 <= C:5 => O:6 )
3: Angle( HH32:3 <= CH3:2 => HH33:4 )
4: Angle( C:5 <= N:7 => CA:9 )
)

The Angle object is also a molecular container, so can be indexed, searched and sliced just like any other container.

>>> angle = mol.angles()[0]
>>> print(angle[0])
Atom( HH31:1  [  18.45,    3.49,   12.44] )
>>> print(angle[1])
Atom( CH3:2   [  18.98,    3.45,   13.39] )
>>> print(angle.bonds()[0])
Bond( HH31:1 => CH3:2 )

Accessing angles by atom or bond#

You can also find angles by looking for their constituent atoms. For example,

>>> print(mol.angles("atomnum 1", "atomnum 2", "atomnum 3"))
SelectorAngle( size=1
0: Angle( HH31:1 <= CH3:2 => HH32:3 )
)

Returns the angles between atoms with numbers 1, 2 and 3. If there are no angles that match, then an empty list is returned.

>>> print(mol.angles("atomnum 1", "atomnum 2", "atomnum 10"))
SelectorAngle::empty

If you are sure that there is only a single angle that matches, then you can use the angle() function

>>> print(mol.angle("atomnum 1", "atomnum 2", "atomnum 3"))
Angle( HH31:1 <= CH3:2 => HH32:3 )

This will raise a KeyError if multiple angles match, or if no angles match.

You can use any valid atom identifier to identify the atoms. This includes search strings, e.g. here we can find all angles that have carbon only in the center

>>> print(mol.angles("not element C", "element C", "not element C"))
SelectorAngle( size=15
0: Angle( HH31:1 <= CH3:2 => HH32:3 )
1: Angle( HH31:1 <= CH3:2 => HH33:4 )
2: Angle( HH32:3 <= CH3:2 => HH33:4 )
3: Angle( O:6 <= C:5 => N:7 )
4: Angle( N:7 <= CA:9 => HA:10 )
...
10: Angle( N:17 <= CH3:19 => HH32:21 )
11: Angle( N:17 <= CH3:19 => HH33:22 )
12: Angle( HH31:20 <= CH3:19 => HH32:21 )
13: Angle( HH31:20 <= CH3:19 => HH33:22 )
14: Angle( HH32:21 <= CH3:19 => HH33:22 )
)

Passing in three atom identifiers, as above, will search for angles by atom. Passing in two atom identifiers will search for angles that contain the corresponding bond.

For example, here we can find all of the angles involving bonds between carbon and hydrogen atoms,

>>> print(mol.angles("element C", "element H"))
SelectorAngle( size=21
0: Angle( HH31:1 <= CH3:2 => HH32:3 )
1: Angle( HH31:1 <= CH3:2 => HH33:4 )
2: Angle( HH31:1 <= CH3:2 => C:5 )
3: Angle( HH32:3 <= CH3:2 => HH33:4 )
4: Angle( HH32:3 <= CH3:2 => C:5 )
...
16: Angle( N:17 <= CH3:19 => HH32:21 )
17: Angle( N:17 <= CH3:19 => HH33:22 )
18: Angle( HH31:20 <= CH3:19 => HH32:21 )
19: Angle( HH31:20 <= CH3:19 => HH33:22 )
20: Angle( HH32:21 <= CH3:19 => HH33:22 )
)

This would also work using atom identifying types, e.g. looking for angles that contains the bond between atoms HH31:1 and CH3:2.

>>> print(mol.angles(sr.atomid("HH31", 1), sr.atomid("CH3", 2)))
SelectorAngle( size=3
0: Angle( HH31:1 <= CH3:2 => HH32:3 )
1: Angle( HH31:1 <= CH3:2 => HH33:4 )
2: Angle( HH31:1 <= CH3:2 => C:5 )
)

You can even use complex search strings, here finding the angles involving the bonds between atoms connecting two residues

>>> print(mol.angles("atoms in residx 0", "atoms in residx 1"))
SelectorAngle( size=4
0: Angle( CH3:2 <= C:5 => N:7 )
1: Angle( C:5 <= N:7 => H:8 )
2: Angle( C:5 <= N:7 => CA:9 )
3: Angle( O:6 <= C:5 => N:7 )
)

or mixing and matching searches

>>> print(mol.angles(sr.atomid("C", 5), "element N"))
SelectorAngle( size=4
0: Angle( CH3:2 <= C:5 => N:7 )
1: Angle( C:5 <= N:7 => H:8 )
2: Angle( C:5 <= N:7 => CA:9 )
3: Angle( O:6 <= C:5 => N:7 )
)

Passing in a single atom identifier will return all of the angles that involve that atom (or atoms).

>>> print(mol.angles("atomnum 2"))
SelectorAngle( size=8
0: Angle( HH31:1 <= CH3:2 => HH32:3 )
1: Angle( HH31:1 <= CH3:2 => HH33:4 )
2: Angle( HH31:1 <= CH3:2 => C:5 )
3: Angle( CH3:2 <= C:5 => O:6 )
4: Angle( CH3:2 <= C:5 => N:7 )
5: Angle( HH32:3 <= CH3:2 => HH33:4 )
6: Angle( HH32:3 <= CH3:2 => C:5 )
7: Angle( HH33:4 <= CH3:2 => C:5 )
)

This has returned all of the angles that involve atom number 2, while

>>> print(mol.angles("element C"))
SelectorAngle( size=36
0: Angle( HH31:1 <= CH3:2 => HH32:3 )
1: Angle( HH31:1 <= CH3:2 => HH33:4 )
2: Angle( HH31:1 <= CH3:2 => C:5 )
3: Angle( CH3:2 <= C:5 => O:6 )
4: Angle( CH3:2 <= C:5 => N:7 )
...
31: Angle( N:17 <= CH3:19 => HH33:22 )
32: Angle( H:18 <= N:17 => CH3:19 )
33: Angle( HH31:20 <= CH3:19 => HH32:21 )
34: Angle( HH31:20 <= CH3:19 => HH33:22 )
35: Angle( HH32:21 <= CH3:19 => HH33:22 )
)

gets all of the angles that involve carbon.

Note that you can also use "*" to match anything, so

>>> print(mol.angles("*", "element C", "*"))
SelectorAngle( size=30
0: Angle( HH31:1 <= CH3:2 => HH32:3 )
1: Angle( HH31:1 <= CH3:2 => HH33:4 )
2: Angle( HH31:1 <= CH3:2 => C:5 )
3: Angle( CH3:2 <= C:5 => O:6 )
4: Angle( CH3:2 <= C:5 => N:7 )
...
25: Angle( N:17 <= CH3:19 => HH32:21 )
26: Angle( N:17 <= CH3:19 => HH33:22 )
27: Angle( HH31:20 <= CH3:19 => HH32:21 )
28: Angle( HH31:20 <= CH3:19 => HH33:22 )
29: Angle( HH32:21 <= CH3:19 => HH33:22 )
)

returns all of the angles that have carbon as the central atom.

Accessing angles by residue#

You can also access angles by residue, by passing in residue identifiers. Passing in two residue identifiers, such as here

>>> print(mol.angles("residx 0", "residx 1"))
SelectorAngle( size=4
0: Angle( CH3:2 <= C:5 => N:7 )
1: Angle( C:5 <= N:7 => H:8 )
2: Angle( C:5 <= N:7 => CA:9 )
3: Angle( O:6 <= C:5 => N:7 )
)

gives all of the angles that involve bonds that are between those two residues.

While passing in a single residue identifier

>>> print(mol.angles("residx 0"))
SelectorAngle( size=11
0: Angle( HH31:1 <= CH3:2 => HH32:3 )
1: Angle( HH31:1 <= CH3:2 => HH33:4 )
2: Angle( HH31:1 <= CH3:2 => C:5 )
3: Angle( CH3:2 <= C:5 => O:6 )
4: Angle( CH3:2 <= C:5 => N:7 )
...
6: Angle( HH32:3 <= CH3:2 => C:5 )
7: Angle( HH33:4 <= CH3:2 => C:5 )
8: Angle( C:5 <= N:7 => H:8 )
9: Angle( C:5 <= N:7 => CA:9 )
10: Angle( O:6 <= C:5 => N:7 )
)

gives all of the angles that involve atoms in this residue (including the angles to other residues).

If you want the angles that are contained only within the residue, then use the angles function on that residue,

>>> print(mol["residx 0"].angles())
SelectorAngle( size=7
0: Angle( HH31:1 <= CH3:2 => HH32:3 )
1: Angle( HH31:1 <= CH3:2 => C:5 )
2: Angle( HH31:1 <= CH3:2 => HH33:4 )
3: Angle( CH3:2 <= C:5 => O:6 )
4: Angle( HH32:3 <= CH3:2 => C:5 )
5: Angle( HH32:3 <= CH3:2 => HH33:4 )
6: Angle( HH33:4 <= CH3:2 => C:5 )
)

Calling the angles function on any molecular container will return the angles that involve only the atoms that are fully contained in that container.

Note

We have shown searching for angles by residue. You can also search for angles by chain or segment if your molecule has chains or segments. So print(mol.angles("chainidx 0", "chainidx 1")) would print the angles between the first two chains.

Uniquely identifying an angle#

Angles are identified by their AngleID. This is a triple of AtomID identifiers, one for each of the three atoms to be identified. While the atom identifier can be any type, it is best to use atom indexes, as these uniquely identify atoms in a molecule. A AngleID comprised of three AtomIdx identifiers will uniquely identify a single angle.

You can easily construct a AngleID using the sire.angleid() function, e.g.

>>> print(sr.angleid(0, 1, 2))
Angle( AtomIdx(0), AtomIdx(1), AtomIdx(2) )

constructs a AngleID from atom indexes,

>>> print(sr.angleid("H2", "O", "H1"))
Angle( AtomName('H2'), AtomName('O'), AtomName('H1') )

constructs one from atom names, and

>>> print(sr.angleid(sr.atomid(1), sr.atomid(2), sr.atomid(3)))
Angle( AtomNum(1), AtomNum(2), AtomNum(3) )

constructs one from atom numbers.

You can mix and match the IDs if you want.

You can then use the AngleID to index, just like any other identifier class.

>>> print(mols[sr.angleid("H2", "O", "H1")])
SelectorMAngle( size=630
0: MolNum(3) Angle( H1:24 <= O:23 => H2:25 )
1: MolNum(4) Angle( H1:27 <= O:26 => H2:28 )
2: MolNum(5) Angle( H1:30 <= O:29 => H2:31 )
3: MolNum(6) Angle( H1:33 <= O:32 => H2:34 )
4: MolNum(7) Angle( H1:36 <= O:35 => H2:37 )
...
625: MolNum(628) Angle( H1:1899 <= O:1898 => H2:1900 )
626: MolNum(629) Angle( H1:1902 <= O:1901 => H2:1903 )
627: MolNum(630) Angle( H1:1905 <= O:1904 => H2:1906 )
628: MolNum(631) Angle( H1:1908 <= O:1907 => H2:1909 )
629: MolNum(632) Angle( H1:1911 <= O:1910 => H2:1912 )
)

gives all of the angles between the atoms called H2, O and H1 in all molecules, while

>>> print(mols[0][sr.angleid(0, 1, 2)])
Angle( HH31:1 <= CH3:2 => HH32:3 )

gives just the angle between the first three atoms in the first molecule, and

>>> print(mols[sr.angleid(0, 1, 2)])
SelectorMAngle( size=631
0: MolNum(2) Angle( HH31:1 <= CH3:2 => HH32:3 )
1: MolNum(3) Angle( O:23 <= H1:24 => H2:25 )
2: MolNum(4) Angle( O:26 <= H1:27 => H2:28 )
3: MolNum(5) Angle( O:29 <= H1:30 => H2:31 )
4: MolNum(6) Angle( O:32 <= H1:33 => H2:34 )
...
626: MolNum(628) Angle( O:1898 <= H1:1899 => H2:1900 )
627: MolNum(629) Angle( O:1901 <= H1:1902 => H2:1903 )
628: MolNum(630) Angle( O:1904 <= H1:1905 => H2:1906 )
629: MolNum(631) Angle( O:1907 <= H1:1908 => H2:1909 )
630: MolNum(632) Angle( O:1910 <= H1:1911 => H2:1912 )
)

gives the angle between the first three atoms in each molecule.