Molecular editing¶
In this tutorial, you will use BioSimSpace and Sire to edit force field parameters for a small molecule. Sire is a molecular simulation framework that BioSimSpace is built upon, and provides a wide range of functionality for manipulating and editing molecules.
Editing existing molecules¶
In this section you will learn how to edit the force field parameters of an existing molecule, e.g. if you want to adjust charges, or modify bonded parameters.
First, let’s import the necessary modules and use BioSimSpace to create a parameterised ethanol molecule from a SMILES string:
>>> import BioSimSpace as BSS
>>> import sire as sr
>>> ethanol = BSS.Parameters.gaff("CCO").getMolecule()
Forcefield parameters are stored as molecular properties. These can be accessed by using the appropriate property key on the underlying Sire molecule, e.g. for the charges on the atoms.
>> print(ethanol._sire_object.property(“charge”)) SireMol::AtomCharges( size=9 0: -0.1361 |e| 1: 0.1264 |e| 2: -0.5998 |e| 3: 0.0423669 |e| 4: 0.0423669 |e| 5: 0.0423669 |e| 6: 0.0431999 |e| 7: 0.0431999 |e| 8: 0.396 |e| )
Note
BioSimSpace provides wrappers for several Sire classes, including the
Molecule class used here. The underlying Sire object can be accessed
via the _sire_object attribute. The underscore signifies that this is
a “private” attribute, and so should be used with care, i.e. the operations
described here would typically only be performed by an expert user since they
directly modify the state of an internal attribute of the Molecule object.
Bonded terms are stored internally as expressions using a buit-in computer algebra system:
>>> for bond in ethanol._sire_object.property("bond").potentials():
...     print(bond)
TwoAtomFunction( {CGIdx(0),Index(0)} <-> {CGIdx(0),Index(3)} : 330.6 [r - 1.0969]^2 )
TwoAtomFunction( {CGIdx(0),Index(0)} <-> {CGIdx(0),Index(1)} : 300.9 [r - 1.5375]^2 )
TwoAtomFunction( {CGIdx(0),Index(1)} <-> {CGIdx(0),Index(2)} : 316.7 [r - 1.4233]^2 )
TwoAtomFunction( {CGIdx(0),Index(1)} <-> {CGIdx(0),Index(7)} : 330.6 [r - 1.0969]^2 )
TwoAtomFunction( {CGIdx(0),Index(0)} <-> {CGIdx(0),Index(5)} : 330.6 [r - 1.0969]^2 )
TwoAtomFunction( {CGIdx(0),Index(1)} <-> {CGIdx(0),Index(6)} : 330.6 [r - 1.0969]^2 )
TwoAtomFunction( {CGIdx(0),Index(0)} <-> {CGIdx(0),Index(4)} : 330.6 [r - 1.0969]^2 )
TwoAtomFunction( {CGIdx(0),Index(2)} <-> {CGIdx(0),Index(8)} : 371.4 [r - 0.973]^2 )
Each TwoAtomFunction stores the two atoms involved in the bond, as well as the
expression used to compute the bonded energy:
>>> print(bond.atom0(), bond.atom1(), bond.function())
{CGIdx(0),Index(2)} {CGIdx(0),Index(8)} 371.4 [r - 0.973]^2
On write, these expressions are converted into the appropriate functional form for the parser in question, e.g. for conversion to AMBER format:
>>> from sire.legacy.CAS import Symbol
>>> from sire.legacy.MM import AmberBond
>>> amber_bond = AmberBond(bond.function(), Symbol("r"))
We can now print the components of the bond function, e.g. the force constant and the equilibrium bond length:
>>> print(amber_bond.k(), amber_bond.r0())
371.4 0.973
To go the other way, we can convert the AmberBond back into an Expression:
>>> print(amber_bond.asExpression(Symbol("r")))
371.4 [r - 0.973]^2
Editing charges¶
To edit atomic properties, such as charges, we can use the new Sire Python API:
>>> cursor = ethanol._sire_object.cursor()
>>> for atom in cursor.atoms():
...     atom["charge"] = 10 * sr.units.mod_electron
>>> ethanol._sire_object = cursor.commit()
>>> print(ethanol._sire_object.property("charge"))
SireMol::AtomCharges( size=9
0: 10 |e|
1: 10 |e|
2: 10 |e|
3: 10 |e|
4: 10 |e|
5: 10 |e|
6: 10 |e|
7: 10 |e|
8: 10 |e|
)
Note
To see the full list of properties available for a molecule, you can use the
propertyKeys() method on the underlying Sire molecule object, e.g.
ethanol._sire_object.propertyKeys().
Editing bonds¶
For molecule properties, such as bonded parameters, we currently need to use the legacy Sire API. Here we will create a new set of bonds by nulling the force constant for all existing bonds:
>>> from sire.legacy.MM import TwoAtomFunctions
>>> bonds = TwoAtomFunctions(ethanol._sire_object.info())
>>> for bond in ethanol._sire_object.property("bond").potentials():
...     amber_bond = AmberBond(0, 0)
...     bonds.set(bond.atom0(), bond.atom1(), amber_bond.toExpression(Symbol("r")))
>>> cursor = ethanol._sire_object.cursor()
>>> cursor["bond"] = bonds
>>> ethanol._sire_object = cursor.commit()
Now lets print the new bonds:
>>> for bond in ethanol._sire_object.property("bond").potentials():
...     print(bond)
TwoAtomFunction( {CGIdx(0),Index(0)} <-> {CGIdx(0),Index(3)} : 0 )
TwoAtomFunction( {CGIdx(0),Index(0)} <-> {CGIdx(0),Index(1)} : 0 )
TwoAtomFunction( {CGIdx(0),Index(1)} <-> {CGIdx(0),Index(2)} : 0 )
TwoAtomFunction( {CGIdx(0),Index(1)} <-> {CGIdx(0),Index(7)} : 0 )
TwoAtomFunction( {CGIdx(0),Index(0)} <-> {CGIdx(0),Index(5)} : 0 )
TwoAtomFunction( {CGIdx(0),Index(1)} <-> {CGIdx(0),Index(6)} : 0 )
TwoAtomFunction( {CGIdx(0),Index(0)} <-> {CGIdx(0),Index(4)} : 0 )
TwoAtomFunction( {CGIdx(0),Index(2)} <-> {CGIdx(0),Index(8)} : 0 )
Note
Here we’ve adjusted the parameters for the existing bonding, but we could
also adjust which atoms are bonded by ommitting or adding new bonds when
constructing the new TwoAtomFunctions object.
Editing angles¶
Angles are stored as ThreeAtomFunction objects, which can be accessed via the
angle property key. Here we will modify the angle whose central atom is named
O3.
First, let’s create a ThreeAtomFunctions container to store the potentials:
>>> from sire.legacy.MM import AmberAngle, ThreeAtomFunctions
>>> angles = ThreeAtomFunctions(ethanol._sire_object.info())
Next, let’s loop over the existing potentials, adding each in turn, and modifying the desired angle:
>>> for angle in ethanol._sire_object.property("angle").potentials():
...     if ethanol._sire_object.atom(angle.atom1()).name().value() == "O3":
...         amber_angle = AmberAngle(100, 1.5)
...         angles.set(angle.atom0(), angle.atom1(), angle.atom2(), amber_angle.toExpression(Symbol("theta")))
...     else:
...         angles.set(angle.atom0(), angle.atom1(), angle.atom2(), angle.function())
Now we can set the new angles on the molecule:
>>> cursor = ethanol._sire_object.cursor()
>>> cursor["angle"] = angles
>>> ethanol._sire_object = cursor.commit()
Let’s print the new angles to check that the change has been made:
>>> for angle in ethanol._sire_object.property("angle").potentials():
...     print(angle)
ThreeAtomFunction( {CGIdx(0),Index(1)} <- {CGIdx(0),Index(0)} -> {CGIdx(0),Index(3)} : 46.3 [theta - 1.91637]^2 )
ThreeAtomFunction( {CGIdx(0),Index(0)} <- {CGIdx(0),Index(1)} -> {CGIdx(0),Index(2)} : 67.5 [theta - 1.92318]^2 )
ThreeAtomFunction( {CGIdx(0),Index(2)} <- {CGIdx(0),Index(1)} -> {CGIdx(0),Index(7)} : 50.9 [theta - 1.9244]^2 )
ThreeAtomFunction( {CGIdx(0),Index(3)} <- {CGIdx(0),Index(0)} -> {CGIdx(0),Index(5)} : 39.4 [theta - 1.87763]^2 )
ThreeAtomFunction( {CGIdx(0),Index(2)} <- {CGIdx(0),Index(1)} -> {CGIdx(0),Index(6)} : 50.9 [theta - 1.9244]^2 )
ThreeAtomFunction( {CGIdx(0),Index(3)} <- {CGIdx(0),Index(0)} -> {CGIdx(0),Index(4)} : 39.4 [theta - 1.87763]^2 )
ThreeAtomFunction( {CGIdx(0),Index(0)} <- {CGIdx(0),Index(1)} -> {CGIdx(0),Index(7)} : 46.4 [theta - 1.91218]^2 )
ThreeAtomFunction( {CGIdx(0),Index(1)} <- {CGIdx(0),Index(0)} -> {CGIdx(0),Index(5)} : 46.3 [theta - 1.91637]^2 )
ThreeAtomFunction( {CGIdx(0),Index(0)} <- {CGIdx(0),Index(1)} -> {CGIdx(0),Index(6)} : 46.4 [theta - 1.91218]^2 )
ThreeAtomFunction( {CGIdx(0),Index(6)} <- {CGIdx(0),Index(1)} -> {CGIdx(0),Index(7)} : 39.2 [theta - 1.89298]^2 )
ThreeAtomFunction( {CGIdx(0),Index(1)} <- {CGIdx(0),Index(0)} -> {CGIdx(0),Index(4)} : 46.3 [theta - 1.91637]^2 )
ThreeAtomFunction( {CGIdx(0),Index(4)} <- {CGIdx(0),Index(0)} -> {CGIdx(0),Index(5)} : 39.4 [theta - 1.87763]^2 )
ThreeAtomFunction( {CGIdx(0),Index(1)} <- {CGIdx(0),Index(2)} -> {CGIdx(0),Index(8)} : 100 [theta - 1.5]^2 )
Editing dihedrals¶
Dihedrals are a bit more complex to edit, since a single dihedral can contain multiple terms, or parts. For example, let’s look at the dihedrals in our ethanol molecule:
>>> dihedrals = ethanol._sire_object.property("dihedral").potentials()
>>> for i, dihedral in enumerate(dihedrals):
...     print(i, dihedral)
0 FourAtomFunction( {CGIdx(0),Index(0)} <- {CGIdx(0),Index(1)} - {CGIdx(0),Index(2)} -> {CGIdx(0),Index(8)} : 0.25 cos(phi) + 0.16 cos(3 phi) + 0.41 )
1 FourAtomFunction( {CGIdx(0),Index(7)} <- {CGIdx(0),Index(1)} - {CGIdx(0),Index(2)} -> {CGIdx(0),Index(8)} : 0.166667 cos(3 phi) + 0.166667 )
2 FourAtomFunction( {CGIdx(0),Index(2)} <- {CGIdx(0),Index(1)} - {CGIdx(0),Index(0)} -> {CGIdx(0),Index(3)} : 0.25 cos(phi) + 0.25 )
3 FourAtomFunction( {CGIdx(0),Index(6)} <- {CGIdx(0),Index(1)} - {CGIdx(0),Index(2)} -> {CGIdx(0),Index(8)} : 0.166667 cos(3 phi) + 0.166667 )
4 FourAtomFunction( {CGIdx(0),Index(3)} <- {CGIdx(0),Index(0)} - {CGIdx(0),Index(1)} -> {CGIdx(0),Index(7)} : 0.155556 cos(3 phi) + 0.155556 )
5 FourAtomFunction( {CGIdx(0),Index(3)} <- {CGIdx(0),Index(0)} - {CGIdx(0),Index(1)} -> {CGIdx(0),Index(6)} : 0.155556 cos(3 phi) + 0.155556 )
6 FourAtomFunction( {CGIdx(0),Index(2)} <- {CGIdx(0),Index(1)} - {CGIdx(0),Index(0)} -> {CGIdx(0),Index(5)} : 0.25 cos(phi) + 0.25 )
7 FourAtomFunction( {CGIdx(0),Index(2)} <- {CGIdx(0),Index(1)} - {CGIdx(0),Index(0)} -> {CGIdx(0),Index(4)} : 0.25 cos(phi) + 0.25 )
8 FourAtomFunction( {CGIdx(0),Index(5)} <- {CGIdx(0),Index(0)} - {CGIdx(0),Index(1)} -> {CGIdx(0),Index(7)} : 0.155556 cos(3 phi) + 0.155556 )
9 FourAtomFunction( {CGIdx(0),Index(4)} <- {CGIdx(0),Index(0)} - {CGIdx(0),Index(1)} -> {CGIdx(0),Index(7)} : 0.155556 cos(3 phi) + 0.155556 )
10 FourAtomFunction( {CGIdx(0),Index(5)} <- {CGIdx(0),Index(0)} - {CGIdx(0),Index(1)} -> {CGIdx(0),Index(6)} : 0.155556 cos(3 phi) + 0.155556 )
11 FourAtomFunction( {CGIdx(0),Index(4)} <- {CGIdx(0),Index(0)} - {CGIdx(0),Index(1)} -> {CGIdx(0),Index(6)} : 0.155556 cos(3 phi) + 0.155556 )
Let’s consider the first FourAtomFunction, which has multiple terms, and convert it to
an ``AmberDihedral``object.
- … Note::
- The containers used for bonded functions don’t preserver order, so the dihedrals shown above may not be in the same order as you see when you run the code. 
>>> from sire.legacy.MM import AmberDihedral
>>> from sire.legacy.CAS import Symbol
>>> amber_dihedral = AmberDihedral(dihedrals[0].function(), Symbol("phi"))
We can now print the terms in the dihedral:
>>> print(amber_dihedral.terms())
[AmberDihPart( k = 0.25, periodicity = 1, phase = 0 ), AmberDihPart( k = 0.16, periodicity = 3, phase = 0 )]
It’s not currently possible to use AmberDihPart objects directly as a means
of building an AmberDihedral. This is because this part of the legacy Sire
API was never intended to be used directly from Python, rather AmberDihedral
objects would be created directly from expressions that are parsed from AMBER
topology files in the C++ API. However, it is easy enough to create multi-term
objects by writing custom expressions. The AmberDihedral code is written to
be robust against different AMBER-style dihedral representations from common
format. For example:
A regular AMBER-style dihedral series where all terms have positive cosine factors:
>>> from sire.legacy.CAS import Cos, Expression, Symbol
>>> Phi = Symbol("phi")
>>> f = Expression(0.3 * (1 + Cos(Phi)) + 0.8 * (1 + Cos(4 * Phi)))
>>> d = AmberDihedral(f, Phi)
>>> print("AMBER:", d)
AMBER: AmberDihedral( k[0] = 0.3, periodicity[0] = 1, phase[0] = 0, k[1] = 0.8, periodicity[1] = 4, phase[1] = 0 )
>>> assert d.toExpression(Phi) == f
An AMBER-style dihedral containing positive and negative cosine factors, which can appear in the CHARMM force field:
>>> f = Expression(0.3 * (1 + Cos(Phi)) - 0.8 * (1 - Cos(4 * Phi)))
>>> d = AmberDihedral(f, Phi)
>>> print("CHARMM:", d)
CHARMM: AmberDihedral( k[0] = 0.3, periodicity[0] = 1, phase[0] = 0, k[1] = -0.8, periodicity[1] = 4, phase[1] = 0 )
>>> assert d.toExpression(Phi) == f
An AMBER-style dihedral containing positive and negative cosine factors, with
the negative of the form k [1 - Cos(Phi)] rather than -k [1 + Cos(Phi)].
These can appear in the GROMACS force field:
>>> f = Expression(0.3 * (1 + Cos(Phi)) + 0.8 * (1 - Cos(4 * Phi)))
>>> d = AmberDihedral(f, Phi)
>>> print("GROMACS:", d)
GROMACS: AmberDihedral( k[0] = 0.3, periodicity[0] = 1, phase[0] = 0, k[1] = 0.8, periodicity[1] = 4, phase[1] = -3.14159 )
>>> from math import isclose
>>> from sire.legacy.CAS import SymbolValue, Values
>>> val = Values(SymbolValue(Phi.ID(), 2.0))
>>> assert isclose(f.evaluate(val), d.toExpression(Phi).evaluate(val))
Finally, a three-term expression that mixes all formats:
>>> # Try a three-term expression that mixes all formats.
>>> f = Expression(
...     0.3 * (1 + Cos(Phi))
...     - 1.2 * (1 + Cos(3 * Phi))
...     + 0.8 * (1 - Cos(4 * Phi))
... )
>>> d = AmberDihedral(f, Phi)
>>> assert isclose(f.evaluate(val), d.toExpression(Phi).evaluate(val))
Note
Impropers are also stored as FourAtomFunction objects, which can be
accessed via the improper property key.
Creating new molecules¶
In this section you will learn how to create a new molecule from scratch. For simplicity, we will assume that we have a reference molecule to serve as a template, i.e. we already have a set of molecular properties to apply. In practice, you might want to set the properties manually.
First we need to build the topology of our molecule. To do so, we will create a new molecule and build the structure of residues and atoms from our ethanol template. Sire works with residue-based cutting groups, so we create a new cut-group for each residue in our template, then add atoms to them:
>> mol = sr.legacy.Mol.Molecule(“ethanol”) >>> for i, res in enumerate(ethanol._sire_object.residues()): … new_res = mol.edit().add(sr.legacy.Mol.ResNum(i+1)) … new_res.rename(res.name()) … cg = new_res.molecule().add(sr.legacy.Mol.CGName(f{“i}”)) … for j, atom in enumerate(res.atoms()): … new_atom = cg.add(atom.name()) … new_atom.renumber(sr.legacy.Mol.AtomNum(j+1)) … new_atom.reparent(sr.legacy.Mol.ResIdx(i)) … mol = cg.molecule().commit()
Note
A cut-group is a logical grouping of atoms into a single group that is considered for intermolecular non-bonded cutting, and for periodic boundaries.
We now have the basic structure of our molecule:
>>> for res in mol.residues():
...     print(res)
...     for atom in res.atoms():
...         print(f"  {atom}")
Residue( LIG:1   num_atoms=9 )
  Atom( C1:1 )
  Atom( C2:2 )
  Atom( O3:3 )
  Atom( H4:4 )
  Atom( H5:5 )
  Atom( H6:6 )
  Atom( H7:7 )
  Atom( H8:8 )
  Atom( H9:9 )
Next we can start adding the required properties, e.g. for the charge:
>>> cursor = mol.cursor()
>>> for new_atom, old_atom in zip(cursor.atoms(), ethanol._sire_object.atoms()):
...     new_atom["charge"] = old_atom["charge"]
>>> mol = cursor.commit()
Note
Here we’ve copied the charges from our template, but we could also set them manually. In this case we have added them atom-by-atom, but we could also copy the entire molecular property in one go.
Let’s check that the charges have been added correctly:
>>> print(mol.property("charge"))
SireMol::AtomCharges( size=9
0: 10 |e|
1: 10 |e|
2: 10 |e|
3: 10 |e|
4: 10 |e|
5: 10 |e|
6: 10 |e|
7: 10 |e|
8: 10 |e|
)
Similarly, we can set molecule properties, such as bond or angle. Here we will copy the existing angle property across:
>>> cursor = mol.cursor()
>>> cursor["angle"] = ethanol._sire_object.property("angle")
>>> mol = cursor.commit()
As before, this could also be done manually, e.g. by creating a new TheeAtomFunctions object and adding angles one-by-one. Let’s check that the angles have been added correctly:
>>> print(mol.property("angle"))
ThreeAtomFunctions( size=13
0:    C1:1-C2:2-O3:3       : 67.5 [theta - 1.92318]^2
1:    C1:1-C2:2-H7:7       : 46.4 [theta - 1.91218]^2
2:    C1:1-C2:2-H8:8       : 46.4 [theta - 1.91218]^2
3:    C2:2-C1:1-H4:4       : 46.3 [theta - 1.91637]^2
4:    C2:2-C1:1-H5:5       : 46.3 [theta - 1.91637]^2
...
8:    O3:3-C2:2-H8:8       : 50.9 [theta - 1.9244]^2
9:    H4:4-C1:1-H5:5       : 39.4 [theta - 1.87763]^2
10:    H4:4-C1:1-H6:6       : 39.4 [theta - 1.87763]^2
11:    H5:5-C1:1-H6:6       : 39.4 [theta - 1.87763]^2
12:    H7:7-C2:2-H8:8       : 39.2 [theta - 1.89298]^2
)
Adding chain identifiers¶
It may be useful to add chain identifiers to a molecule, e.g. if you plan to track specific residues during a simulation. Here we will add chain identifiers to an existing molecule, defining a new chain for each residue. (This is just an example.) The logic is almost identical to that used to create a new molecule from scratch, as shown above. The only difference is the addition of chains to the molecule prior to adding the residues and atoms. In Sire the largest structural units are added first, with the smaller ones then being added and reparented to the larger ones.
First, let’s load a molecule that has multiple residues. Here we will use the alanine dipeptide molecule that is included with the BioSimSpace tutorials:
>>> import BioSimSpace as BSS
>>> ala = BSS.IO.readMolecules(
...     BSS.IO.expand(
...         BSS.tutorialUrl(), ["ala.top", "ala.crd"]
...     )
... )[0]
Next we will create a string for the chain identifiers. Here we will use uppercase letters, but in practice you can use any character:
>>> chain_ids = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
Now we create a MolStructureEditor to build the new molecule:
>>> import sire as sr
>>> editor = sr.legacy.Mol.MolStructureEditor()
To begin with we need to add the chains to the editor:
>>> for i in range(ala.nResidues()):
...     editor.add(sr.legacy.Mol.ChainName(chain_ids[i]))
Now we can loop over the residues in the original molecule, adding them to the editor, reparenting them to the appropriate chain, then adding the atoms:
>>> for i, res in enumerate(ala._sire_object.residues()):
...     cg = editor.add(sr.legacy.Mol.CGName(str(i)))
...     new_res = editor.add(res.number())
...     new_res.rename(res.name())
...     new_res.reparent(chain_ids[i // 3])
...     for j, atom in enumerate(res.atoms()):
...         new_atom = cg.add(atom.number())
...         new_atom.rename(atom.name())
...         new_atom.reparent(res.index())
... editor = editor.commit().edit()
Next we need to copy across the molecular properties, e.g. charges and bonded terms.
>>> for prop in ala._sire_object.propertKeys():
...     editor = editor.setProperty(prop, ala._sire_object.property(prop)).molecule()
Finally, we can commit the changes to create the new molecule:
>>> bss_mol = BSS._SireWrappers.Molecule(editor.commit())
Let’s check that the new molecule has the correct number of chains:
>>> assert bss_mol.nChains() == bss_mol.nResidues()
Finally we will write to PDB format to check that the chain identifiers:
>>> BSS.IO.saveMolecules("ala_chains", bss_mol, "pdb")
The output file ala_chains.pdb should look something like this:
MODEL     1
ATOM      1 HH31 ACE A   1      13.681  13.148  15.273  1.00  0.00           H
ATOM      2  CH3 ACE A   1      13.681  14.238  15.273  1.00  0.00           C
ATOM      3 HH32 ACE A   1      13.168  14.602  16.163  1.00  0.00           H
ATOM      4 HH33 ACE A   1      13.168  14.602  14.384  1.00  0.00           H
ATOM      5  C   ACE A   1      15.109  14.789  15.273  1.00  0.00           C
ATOM      6  O   ACE A   1      16.072  14.026  15.273  1.00  0.00           O
TER       7      ACE A   1
ATOM      8  N   ALA B   2      15.237  16.118  15.273  1.00  0.00           N
ATOM      9  H   ALA B   2      14.414  16.704  15.273  1.00  0.00           H
ATOM     10  CA  ALA B   2      16.535  16.762  15.273  1.00  0.00           C
ATOM     11  HA  ALA B   2      17.089  16.464  16.163  1.00  0.00           H
ATOM     12  CB  ALA B   2      17.343  16.369  14.041  1.00  0.00           C
ATOM     13  HB1 ALA B   2      16.805  16.670  13.142  1.00  0.00           H
ATOM     14  HB2 ALA B   2      18.312  16.867  14.068  1.00  0.00           H
ATOM     15  HB3 ALA B   2      17.490  15.289  14.032  1.00  0.00           H
ATOM     16  C   ALA B   2      16.394  18.278  15.273  1.00  0.00           C
ATOM     17  O   ALA B   2      15.282  18.801  15.273  1.00  0.00           O
TER      18      ALA B   2
ATOM     19  N   NME C   3      17.527  18.983  15.273  1.00  0.00           N
ATOM     20  H   NME C   3      18.418  18.507  15.273  1.00  0.00           H
ATOM     21  CH3 NME C   3      17.527  20.432  15.273  1.00  0.00           C
ATOM     22 HH31 NME C   3      16.500  20.796  15.273  1.00  0.00           H
ATOM     23 HH32 NME C   3      18.041  20.796  16.163  1.00  0.00           H
ATOM     24 HH33 NME C   3      18.041  20.796  14.384  1.00  0.00           H
TER      25      NME C   3
ENDMDL
END