Measuring Distances and Angles

We have already seen that we can measure the lengths of bonds or sizes of angles using the functions of the Bond, ~sire.mm.Angle, ~sire.mm.Dihedral and Improper classes.

For example, load up the aladip system…

>>> import sire as sr
>>> mols = sr.load(sr.expand(sr.tutorial_url, ["ala.top", "ala.crd"]))
>>> mol = mols[0]

and we could then find the lengths of all of the carbon-oxygen bonds using the lengths() function;

>>> print(mols.bonds("element carbon", "element oxygen").lengths())
[1.20803 Å, 1.24385 Å]

Similarly, you could get the size of the first five hydrogen-oxygen-hydrogen angles using the sizes() function;

>>> print(mols.angles("element H", "element O", "element H")[0:5].sizes())
[104.491°, 104.491°, 104.491°, 104.491°, 104.491°]

Sire uses the synonym measure for both lengths and sizes. This lets you use the same function name for measuring bond lengths, angle sizes or dihedral torsion sizes. For example, you could use measures() in place of lengths() above, e.g.

>>> print(mols.bonds("element carbon", "element oxygen").measures())
[1.20803 Å, 1.24385 Å]

or measures() in place of sizes(),

>>> print(mols.angles("element H", "element O", "element H")[0:5].measures())
[104.491°, 104.491°, 104.491°, 104.491°, 104.491°]

Note

You can use measures instead of sizes to measure dihedral angles and improper angles too.

For individual bond, angle, dihedral, or improper objects, you could use measure instead of length or size.

Making measurements between atoms

So far, you have used length, size or measure for measuring actual class:Bond, ~sire.mm.Angle, ~sire.mm.Dihedral or Improper objects.

There are many cases where you want to measure the distance or angles between arbitrary atoms.

To do this, you use the sire.measure() function. For example, we could measure the distance between the oxygen atoms of the first two water molecules using;

>>> oxygens = mols["water and element O"]
>>> print(sr.measure(oxygens[0], oxygens[1]))
18.5067 Å

The measurement returned depends on the number of items passed to the measure() function. Passing two items, as above, will measure and return the distance. Passing three items will measure and return the angle, so

>>> print(sr.measure(oxygens[0], oxygens[1], oxygens[2]))
53.3414°

has returned the angle between the oxygens of the first three water molecules.

Passing in four items will measure the dihedral (torsion) angle, i.e.

>>> print(sr.measure(oxygens[0], oxygens[1], oxygens[2], oxygens[3]))
60.0107°

measures the torsion angle between the oxygens of the first four water molecules.

Improper angles are also measured between four items. Set improper_angle to True to get the improper angle instead;

>>> print(sr.measure(oxygens[0], oxygens[1], oxygens[2], oxygens[3],
...                  improper_angle=True))
-44.0118°

Passing in only a single item will call the .measure() function on that item. This means that this will only work for individual Bond, ~sire.mm.Angle, ~sire.mm.Dihedral or Improper objects;

>>> bond = mols[0].bonds()[0]
>>> print(bond, bond.measure())
Bond( HH31:1 => CH3:2 ) 1.09 Å
>>> print(sr.measure(bond))
1.09 Å

Making measurements between arbitray views

The measure() function calls the .coordinates() function on the items that are passed. This means that you can pass in any object that has a .coordinates() function. For example, you can calculate the distance between the first two water molecules using

>>> waters = mols["water"]
>>> print(sr.measure(waters[0], waters[1]))
18.4583 Å

This is not the same as the distance between the oxygens of these water molecules. This is because the .coordinates() function on a molecule returns the molecule’s center of mass.

If you wanted to return the distance between the molecules’ centers of geometry you would use

>>> print(sr.measure(waters[0].evaluate().center_of_geometry(),
...                  waters[1].evaluate().center_of_geometry()))
18.0674 Å

You can calculate distances between the centers of mass or geometry of arbitray views. For example, here we calculate the distance between the centers of mass of the first two residues of the first molecule;

>>> res = mols[0].residues()
>>> print(sr.measure(res[0], res[1]))
3.24294 Å

or, to get the distance between the centers of geometry

>>> print(sr.measure(res[0].evaluate().center_of_geometry(),
...                  res[1].evaluate().center_of_geometry()))
3.79671 Å

The same would work for angles, dihedrals or improper angles, e.g.

>>> print(sr.measure(res[0], res[1], res[2]))
148.946°

You can also pass in a list of views, e.g.

>>> print(sr.measure([res[0], res[1], res[2]]))
148.946°

or

>>> print(sr.measure(res[0:3]))
148.946°

Measuring against points in space

The actual coordinates of individual atoms, or of the centers of geometry or mass of molecular views, are represented as sire.maths.Vector objects. These are simple objects that hold three double precision numbers that represent the x, y, and z coordinates of a point in 3D space.

For example, here is the Vector that corresponds to the center of mass of the first molecule.

>>> print(mols[0].coordinates())
( 16.5471 Å, 4.50102 Å, 15.6589 Å )

You access the individual x, y, and z components either via the x(), y() and z() functions, or by treating the Vector as a container, e.g.

>>> v = mols[0].coordinates()
>>> print(v.x(), v.y(), v.z())
16.5471 Å 4.50102 Å 15.6589 Å
>>> print(v[0], v[1], v[2])
16.5471 Å 4.50102 Å 15.6589 Å

You construct Vector objects by passing in the values of the x, y, and z components. For example, here we calculate the distance between two points in space;

>>> print(sr.measure(sr.maths.Vector(0, 0, 0),
...                  sr.maths.Vector(5, 0, 0)))
5 Å

Notice how the distance is returned in angstroms. This is because the units of distance, if unspecified, are in the default length unit that has been set (this defaults to angstrom).

You can change the default length unit using, e.g.

>>> from sire.units import picometer
>>> sr.units.set_length_unit(picometer)
>>> print(sr.measure(sr.maths.Vector(0, 0, 0),
...                  sr.maths.Vector(5, 0, 0)))
5 pm

You can change to a full set of SI units using

>>> sr.units.set_si_units()
>>> print(sr.measure(sr.maths.Vector(0, 0, 0),
...                  sr.maths.Vector(5, 0, 0)))
5 nm

As you can see, sire uses nanometers as the SI unit of length. You can find the default units for any dimension using the get_default() function on each unit, e.g.

>>> picometer.get_default()
1 nm

This shows that the current default unit of length is one nanometer.

You can reset to the default units for sire using

>>> sr.units.set_internal_units()

These use angstroms for length,

>>> picometer.get_default()
1 Å

You can always specify the units if something other than the default is desired, or you want to make sure that your script is robust to changes in the default.

>>> print(sr.measure(sr.maths.Vector(0, 0, 0),
...                  sr.maths.Vector(5 * picometer, 0, 0)))
0.05 Å

You can also pass in a tuple or list of three values, e.g.

>>> print(sr.measure( (0,0,0), (5,0,0) ))
5 Å
>>> print(sr.measure( (0,0,0), (5*picometer,0,0) ))
0.05 Å

Using Vector enables you to calculate distances, angles etc. between atoms or molecule views to arbitrary points in space.

For example, here is the distance from the origin to the center of first molecule

>>> print(sr.measure( (0,0,0), mols[0] ))
23.2221 Å

Or the angle between the oxygen in the first water molecule and the x axis

>>> print(sr.measure( (0,0,0), (1,0,0), mols["water and element O"][0] ))
135.775°