Improper Views and Properties¶
The presence of bonds, angles and dihedrals are implied
by the connectivity
property, and can be linked back to
real chemical features of a molecule. In contrast, impropers are
fixes to molecular mechanics forcefields that aim to either
prevent streroisomer flipping during a simulation, or to
keep the atoms around sp2-type centers (e.g. around amide bonds)
in a mostly-planar arrangement.
As such, impropers are derived from the molecular mechanics forcefield
parameters that are held in a molecule’s improper
property.
Improper views¶
The impropers defined for the forcefield of a molecule can be obtained
using the .impropers()
function, e.g.
>>> print(mol.impropers())
SelectorImproper( size=4
0: Improper( CH3:2 <= C:5 => N:7 -- O:6 )
1: Improper( C:5 <= N:7 => CA:9 -- H:8 )
2: Improper( CA:9 <= C:15 => N:17 -- O:16 )
3: Improper( C:15 <= N:17 => CH3:19 -- H:18 )
)
Notice how each Improper
is printed with the central atom
written as the second atom. The other three atoms are all bonded to
this central atom.
Each Improper
object is a molecule view of the four atoms
that comprise that improper. It has additional functions that make it easy
to extract molecule properties that are associated with that improper.
For example, .atom0()
, .atom1()
, .atom2()
and .atom3()
can
be used to get the atoms involved in the improper, and from their the
coordinates of those atoms.
>>> improper = mol.impropers()[0]
>>> print(improper)
Improper( CH3:2 <= C:5 => N:7 -- O:6 )
>>> print(improper.atom0().coordinates(),
... improper.atom1().coordinates(),
... improper.atom2().coordinates(),
... improper.atom3().coordinates())
( 18.9818 Å, 3.44823 Å, 13.3886 Å ) ( 18.4805 Å, 4.54971 Å, 14.3514 Å )
( 17.2214 Å, 4.31498 Å, 14.7128 Å ) ( 19.1866 Å, 5.44143 Å, 14.7584 Å )
The improper object is a molecular container, so can be indexed and searched like any other container, e.g.
>>> print(improper[0].coordinates(),
... improper[1].coordinates(),
... improper[2].coordinates(),
... improper[3].coordinates())
( 18.9818 Å, 3.44823 Å, 13.3886 Å ) ( 18.4805 Å, 4.54971 Å, 14.3514 Å )
( 17.2214 Å, 4.31498 Å, 14.7128 Å ) ( 19.1866 Å, 5.44143 Å, 14.7584 Å )
The .size()
function is a convenience function that calculates
the size of the improper based on the coordinates of the atoms.
>>> print(improper.size())
-3.82426°
Note how this is reported with units. Most values calculated using sire are returned together with their units.
You can convert to any compatible units you want using the .to()
function, e.g.
>>> from sire.units import radians
>>> print(improper.size().to(radians))
-0.06674592039289437
Note
The result of converting a unit is a plain double precision number.
The units system helps ensure that any calculations made in sire make physical sense, while also reducing the risk of errors caused by mixing units.
For example, the .energy()
function on a Improper
uses
the algebraic expressions held in the molecule’s improper
property to calculate the
energy of the improper. This is reported in units of kilocalories per mole.
>>> print(improper.energy())
0.0934184 kcal mol-1
This could be converted to kilojoules per mole…
>>> from sire.units import kJ_per_mol
>>> print(improper.energy().to(kJ_per_mol))
0.39086262536239597
You can also get the sizes and energies of all impropers in a view, e.g. to get the sizes of all impropers in the molecule you could use;
>>> print(mol.impropers().sizes())
[-3.82426°, -0.0353552°, 4.3041°, 5.92025°]
or to get the energies of all impropers with a central nitrogen atom you could use
>>> print(mol.impropers("*", "element N", "*", "*").energies())
[8.3952e-07 kcal mol-1, 0.0234049 kcal mol-1]
You can also use the .energy()
function on a collection to get
the total energy of all impropers in a molecule…
>>> print(mol.impropers().energy())
0.235105 kcal mol-1
…or even of all impropers in the molecules that have been loaded from the file.
>>> print(mols.impropers().energy())
0.235105 kcal mol-1
Just as for bonds, we can use a loop to find all of the impropers that have a high energy, e.g.
>>> from sire.units import kcal_per_mol
>>> for improper in mols.impropers():
... if improper.energy() > 0.05 * kcal_per_mol:
... print(f"{improper} {improper.energy()}")
Improper( CH3:2 <= C:5 => N:7 -- O:6 ) 0.0934184 kcal mol-1
Improper( CA:9 <= C:15 => N:17 -- O:16 ) 0.118281 kcal mol-1
Improper properties¶
Just like bonds, impropers can also have their own per-improper properties. We don’t know of any molecular file formats that set per-improper properties. But that doesn’t stop you from setting your own!
The best way to do this is to use a cursor on the improper, e.g.
>>> cursor = improper.cursor()
>>> cursor["energy_kJ"] = improper.energy().to(kJ_per_mol)
>>> print(cursor["energy_kJ"])
0.390863
You can loop over lots of impropers to set their property, e.g.
>>> cursor = mol.cursor()
>>> for improper in cursor.impropers():
... improper["energy_kJ"] = improper.view().energy().to(kJ_per_mol)
>>> mol = cursor.commit()
>>> print(mol.impropers()[0].property("energy_kJ"))
0.390863
Just for other properties, you can also use .apply()
instead
of a loop.
>>> mol = mol.cursor().impropers().apply(
... lambda improper: improper.set("energy_kJ", improper.view().energy().to(kJ_per_mol))
... ).commit()
>>> print(mol.impropers()[0].property("energy_kJ"))
0.390863