Building an MDMC Universe
An MDMC simulation requires a configuration and a topology defined within a Universe
object. Although MDMC is somewhat flexible with regards to the order in which these are created, a suggested approach is as follows:
Restriction on creating a Universe
object: MDMC currently only supports orthorhombic boxed Universes; the dimensions
must be specified when creating (initialising) the Universe
object:
[1]:
# Import the Universe class
from MDMC.MD import Universe
# Initialise a Universe with dimensions in Ang
universe = Universe([10.0, 15.0, 20.0])
Universe created with:
Dimensions [10. 15. 20.]
To create a cubic Universe
, only a single float needs to be specified for the dimensions
:
[2]:
universe = Universe(10.0)
Universe created with:
Dimensions [10. 10. 10.]
Create an atomic configuration
Configurations can either be specified by the user or read from a CIF file.
Create an atom
Each atom is specified using an Atom
object, which possesses (amongst other things) a position
, velocity
, element
, mass
, charge
, and atom_type
. At a minimum the element
must be specified when creating an Atom
object:
[3]:
# Import the Atom class
from MDMC.MD import Atom
# Create a hydrogen atom
H1 = Atom('H')
This will create a Hydrogen atom with mass
determined from an elemental lookup table, position
set to the origin, velocity
set to (0., 0., 0.), no charge
and no atom_type
. Note that if set, the velocity of atoms in the MD engine will be scaled when creating a Simulation
in order to ensure the temperature is accurate. Otherwise, if the velocities of all Atom
objects in a Simulation
are 0, then the velocities of the atoms in the MD engine will be randomly chosen in
order to provide an accurate temperature. For more details see Running a Simulation.
Atoms can also be created by copying another atom and passing the position of the new atom:
[4]:
H2 = H1.copy(position=[1., 1., 1.])
The copied atom will have identical properties to the original attribute, except with a different ID (which is unique for all structural units) and a different position
. This includes interactions, which will apply to the copied atom in the same way as the original atom e.g. if H1 is bonded to an atom O, H2 will also be bonded to atom O. An example of this is shown in the section ‘Create bonded interactions’.
Atom type
Each atom has an atom_type
, which is used for applying non-bonded interactions between atoms; all atoms of the same atom_type
will have the same non-bonded interactions applying to them. The atom_type
can be specified when the atom is created:
[5]:
O1 = Atom('O', position=[8.0, 8.0, 8.0], atom_type=1)
# and to add the created atom to universe do
universe.add_structure(O1)
Alternatively, the atom_type
can be inferred by MDMC. This occurs when an atom with no defined atom_type
is added to a Universe:
[6]:
O2 = Atom('O', position=[9.0, 9.0, 9.0])
universe.add_structure(O2)
Assuming no other atoms have been added to the universe, this sets O2.atom_type
equal to 1. MDMC infers the atom_type
of each atom based on its element and its interactions: all atoms with the same element and the same interactions when they are added to the universe will have the same atom_type
. Once an atom’s atom_type
is set, it is immutable i.e. it cannot be changed. It is recommended that either all atom types are specified when atoms are created or none are
(i.e. either MDMC is left to infer and assign all atom types or no atom types. Only specifying some atom types could result in unexpected behaviour; if it is absolutely necessary then ensure that all atoms with specified atom types are added to the universe first).
To see what atoms have been added to the universe:
[7]:
universe.structure_list
[7]:
[<Atom
{name: 'O',
ID: 4,
element: O,
position: UnitNDArray([9., 9., 9.]) Ang,
velocity: UnitNDArray([0., 0., 0.]) Ang / fs}>,
<Atom
{name: 'O',
ID: 3,
element: O,
position: UnitNDArray([8., 8., 8.]) Ang,
velocity: UnitNDArray([0., 0., 0.]) Ang / fs}>]
You can see other attributes of the universe (or any other MDMC containers) with:
[8]:
help(universe)
Help on Universe in module MDMC.MD.simulation object:
class Universe(MDMC.MD.container.AtomContainer)
| Universe(dimensions, force_field=None, structures=None, **settings)
|
| Class where the configuration and topology are defined
|
| Parameters
| ----------
| dimensions : numpy.ndarray, list, float
| Dimensions of the ``Universe``, in units of ``Ang``. A `float` can be
| used for a cubic universe.
| force_fields : ForceField, optional
| A force field to apply to the Universe. The force fields available are:
| SPC, TIP3P, OPLSAA, TIP3PFB, SPCE. Default is None.
| structures : list, optional
| ``Structure`` objects contained in the ``Universe``. Default is None.
| **settings
| ``kspace_solver`` (`KSpaceSolver`)
| The k-space solver to be used for both electrostatic and dispersive
| interactions. If this is passed then no ``electrostatic_solver`` or
| ``dispersive_solver`` may be passed.
| ``electrostatic_solver`` (`KSpaceSolver`)
| The k-space solver to be used for electrostatic interactions.
| ``dispersive_solver`` (`KSpaceSolver`)
| The k-space solver to be used for dispersive interactions.
| ``constraint_algorithm`` (`ConstraintAlgorithm`)
| The constraint algorithm which will be applied to constrained
| ``BondedInteractions``.
| ``verbose`` (`str`)
| If the output of the class instantiation should be reported, default
| to True.
|
| Attributes
| ----------
| dimensions : numpy.ndarray, list, float
| Dimensions of the ``Universe`` in units of ``Ang``.
| configuration : Configuration
| Stores the content, i.e. configuration of atoms etc within the universe
| force_fields : ForceField or None
| Force field applied to apply to the Universe
| kspace_solver : KSpaceSolver
| The k-space solver to be used for both electrostatic and dispersive
| interactions.
| electrostatic_solver : KSpaceSolver
| The k-space solver to be used for electrostatic interactions.
| dispersive_solver : KSpaceSolver
| The k-space solver to be used for dispersive interactions.
| constraint_algorithm : ConstraintAlgorithm
| The constraint algorithm which will be applied to constrained
| ``BondedInteractions``.
| interactions
| bonded_interactions
| nonbonded_interactions
| bonded_interaction_pairs
| n_bonded
| n_nonbonded
| n_interactions
| parameters
| volume
| element_list
| element_dict
| element_lookup
| atoms
| n_atoms
| molecule_list
| n_molecules
| structure_list
| top_level_structure_list
| equivalent_top_level_structures_dict
| force_fields
| atom_types
| atom_type_interactions
| density
| solvent_density
| nbis_by_atom_type_pairs
|
| Method resolution order:
| Universe
| MDMC.MD.container.AtomContainer
| abc.ABC
| builtins.object
|
| Methods defined here:
|
| __eq__(self, other) -> bool
| Return self==value.
|
| __init__(self, dimensions, force_field=None, structures=None, **settings)
| Initialize self. See help(type(self)) for accurate signature.
|
| __repr__(self)
|
| __str__(self) -> str
| Return str(self).
|
| add_bonded_interaction_pairs(self, *bonded_interaction_pairs: 'tuple[Interaction, Atom]') -> None
| Adds one or more interaction pairs to the ``Universe``
|
| Parameters
| ----------
| *bonded_interaction_pairs
| one or more (``interaction``, ``atoms``) pairs, where ``atoms`` is a
| `tuple` of all atoms for that specific bonded ``interaction``
|
| add_force_field(self, force_field: str, *interactions: 'Interaction', **settings: dict) -> None
| Adds a force field to the specified ``interactions``. If no
| ``interactions`` are passed, the force field is applied to all
| interactions in the ``Universe``.
|
| Parameters
| ----------
| force_field : str
| The ``ForceField`` to parameterize ``interactions`` (if provided),
| or all the ``interactions`` in the ``Universe``. The available
| ``ForceField`` are:
| SPC, TIP3P, OPLSAA, TIP3PFB, SPCE
| *interactions
| ``Interaction`` objects to parameterize with the ``ForceField``
| **settings
| ``add_dispersions`` (`bool` or `list` of ``Atoms``)
| If `True`, a ``Dispersion`` interaction will be added to all
| atoms in the ``Universe``. If a list of ``Atom`` objects is
| provided, the ``Dispersion`` will be added to these instead. Any
| added ``Dispersion`` interactions (and any previously defined)
| will then be parametrized by the ``ForceField``. The
| ``Dispersion`` interactions added will only be like-like. By
| default, no ``Dispersion`` interactions are added.
|
| add_nonbonded_interaction(self, *nonbonded_interactions: 'tuple[Interaction, Atom]') -> None
| Adds one or more nonbonded interactions to the ``Universe``
|
| Parameters
| ----------
| *nonbonded_interactions
| Nonbonded interactions to be added to the ``Universe``.
| Can take any number of non-bonded interactions:
| ``Dispersion()``:
| either Lennard-Jones or Buckingham dispersion
| ``Coulombic()``:
| normal or modified Coulomb interaction
| with appropriate parameters for the interaction.
| See http://mdmcproject.org/tutorials/building-a-universe.html?highlight=interaction#Create-non-bonded-interactions
| for more details on non-bonded interactions.
|
| add_structure(self, structure: Union[MDMC.MD.structures.Structure, int], force_field: str = None, center: bool = False) -> None
| Adds a single ``Structure`` to the ``Universe``, with optional
| ``ForceField`` applying only to that ``Structure``
|
| Parameters
| ----------
| structure : Structure or int
| The ``Structure`` (or its ``ID`` as an `int`) to be added to the
| ``Universe``
| force_field : str, optional
| The force field to be applied to the structure. The available
| ``ForceField`` are:
| SPC, TIP3P, OPLSAA, TIP3PFB, SPCE
| center : bool, optional
| Whether to center `structure` within the Universe as it is
| added
|
| fill(self, structures: MDMC.MD.structures.Structure, force_field: str = None, num_density: float = None, num_struc_units: int = None) -> None
| A liquid-like filling of the ``Universe`` independent of existing atoms
|
| Adds copies of ``structures`` to existing configuration until
| ``Universe`` is full. As exclusion region is defined by the size of a
| bounding sphere, this method is most suitable for atoms or molecules
| with approximately equal dimensions.
|
| .. note:: CURRENT APPROACH RESULTS IN NUMBER DENSITY DIFFERENT TO WHAT
| IS SPECIFIED DEPENDING ON HOW CLOSE CUBE ROOT OF N_MOLECULES
| IS TO AN `int`.
|
| .. note:: CURRENT IMPLEMENTATION SHOULD NOT BE USED WITH NON-CUBIC
| UNIVERSES AS THE DENSITY MAY OR MAY NOT BE ISOTROPIC
| DEPENDING ON THE DIMENSIONS AND NUMBER OF UNITS.
|
| Parameters
| ----------
| structures : Structure or int
| The ``Structure`` with which to fill the ``Universe``
| force_field : str
| Applies a ``ForceField`` to the ``Universe``. The available
| ``ForceField`` are:
| SPC, TIP3P, OPLSAA, TIP3PFB, SPCE
| num_density: float
| Non-negative `float` specifying the number density of the
| ``Structure``, in units of ``Structure / Ang ^ -3``
| num_struc_units: int
| Non-negative `int` specifying the number of passed
| ``Structure`` objects that the universe should be filled
| with, regardless of ``Universe.dimensions``.
|
| Raises
| ------
| ValueError
| If both ``num_density`` and ``num_struc_units`` are passed
| ValueError
| If neither ``num_density`` or ``num_struc_units`` are passed
|
| solvate(self, density: float, tolerance: float = 1.0, solvent: str = 'SPCE', max_iterations: int = 100, **settings: dict) -> None
| Fills the ``Universe`` with solvent molecules according to pre-defined
| coordinates.
|
| Parameters
| ----------
| density : float
| The desired density of the ``Solvent`` that solvates the
| ``Universe``, in units of ``amu Ang ^ -3``
| tolerance : float, optional
| The +/- percentage tolerance of the density to be achieved.
| The default is 1 %. Tolerances of less than 1 % are at risk
| of not converging.
| solvent : str, optional
| A `str` specifying an inbuilt ``Solvent`` from the following:
| SPC, TIP3PFB, TIP3P, SPCE.
| The default is 'SPCE'.
| max_iterations: int, optional
| The maximum number of times to try to solvate the universe to
| within the required density before stopping. Defaults to 100.
| **settings
| ``constraint_algorithm`` (`ConstraintAlgorithm`)
| A ``ConstraintAlgorithm`` which is applied to the ``Universe``.
| If an inbuilt ``Solvent`` is selected (e.g. 'SPCE') and
| ``constraint_algorithm`` is not passed, the
| ``ConstraintAlgorithm`` will default to ``Shake(1e-4, 100)``.
|
| Raises
| ------
| ValueError
| If the ``Universe`` has already been solvated with a different
| density.
|
| ----------------------------------------------------------------------
| Readonly properties defined here:
|
| atom_type_interactions
| Get the atom types and the interactions for each atom type
|
| Returns
| -------
| dict
| ``atom_type:interactions`` pairs where ``atom_type`` is a `int`
| specifying the atom type and ``interactions`` is a `list` of
| ``Interaction`` objects acting on that ``atom_type``
|
| atom_types
| Get the atom types of atoms in the ``Universe``
|
| Returns
| -------
| list
| The atom types in the ``Universe``
|
| atoms
| Get a list of the atoms in the ``Universe``
|
| Returns
| -------
| list
| The atoms in the ``Universe``
|
| bonded_interaction_pairs
| Get the bonded interactions and the atoms they apply to
|
| Returns
| -------
| list
| The (``interaction``, ``atoms``) pairs in the ``Universe``, where
| ``atoms`` is a `tuple` of all ``Atom`` objects for that specific
| ``interaction``
|
| Example
| -------
| For an O Atom with two bonds, one to H1 and one to H2::
|
| >>> print(O.bonded_interaction_pairs)
| [(Bond, (H1, O)), (Bond, (H2, O))]
|
| bonded_interactions
| Get the bonded interactions in the ``Universe``
|
| Returns
| -------
| list
| The bonded interactions in the ``Universe``
|
| density
|
| element_dict
| Get the elements in the ``Universe`` and example ``Atom`` objects for
| each element
|
| This is required for MD engines which assign the same potential
| parameters for all identical element types.
|
| Returns
| -------
| dict
| ``element:atom pairs``, where ``atom`` is a single ``Atom`` of the
| specified ``element``.
|
| element_list
| The elements of the atoms in the ``Universe``
|
| Returns
| -------
| list
| The elements in the ``Universe``
|
| element_lookup
| Get the elements by ID in the ``Universe``
|
| This is required for MD engines which assign the same potential
| parameters for all identical element names.
|
| Returns
| -------
| dict
| ``element:atom pairs``, where ``atom`` is a single ``Atom`` of the
| specified ``element``.
|
| equivalent_top_level_structures_dict
| Get a `dict` of equivalent top level ``Structure`` objects that
| exist in the ``Universe`` as keys, and the number of ``Structure``
| that are equivalent to the key as a value. This does not include any
| ``Structure`` that are a subunit of another structure belonging
| to the ``Universe``.
|
|
| Returns
| -------
| dict
| The top level ``Structure`` objects in the ``Universe`` as
| keys, and the number of each as a value
|
| force_fields
| Get the ``ForceField`` acting on the ``Universe``
|
| The available force fields are:
| SPC, TIP3P, OPLSAA, TIP3PFB, SPCE
|
| Returns
| -------
| list
| ``ForceField`` that applies to the ``Universe``
|
| interactions
| Get the interactions in the ``Universe``
|
| Returns
| -------
| list
| The interactions in the ``Universe``
|
| molecule_list
| Get a list of the ``Molecule`` objects in the ``Universe``
|
| Returns
| -------
| list
| The ``Molecule`` objects in the ``Universe``
|
| n_atoms
| Get the number of atoms in the ``Universe``
|
| Returns
| -------
| int
| The number of atoms in the ``Universe``
|
| n_bonded
| Get the number of bonded interactions in the ``Universe``
|
| Returns
| -------
| int
| The number of bonded interactions in the ``Universe``
|
| n_interactions
| Get the number of interactions in the ``Universe``
|
| Returns
| -------
| int
| The number of interactions in the ``Universe``
|
| n_molecules
| Get the number of molecules in the ``Universe``
|
| Returns
| -------
| int
| The number of molecules in the ``Universe``
|
| n_nonbonded
| Get the number of nonbonded interactions in the ``Universe``
|
| Returns
| -------
| int
| The number of nonbonded interactions in the ``Universe``
|
| nbis_by_atom_type_pairs
| Generates a `dict` of all nonbonded interactions possessed by all
| combinations of ``atom_type`` pairs in the ``Universe``.
|
| Returns
| -------
| dict
| A `dict` of ``{pair: interactions}`` pairs, where:
| - ``pair`` is a `tuple` for a pair of ``atom_types`` in the
| ``Universe``
| - ``interactions`` is a list of nonbonded interactions that
| exist for this ``pair`` of ``atom_types``
|
| Any ``Dispersions`` in ``interactions`` are ones that exist
| explicity for this ``pair``, whereas any ``Coulombics`` in
| ``interactions`` are ones that exist for either of the
| ``atom_types`` in ``pair``.
|
| nonbonded_interactions
| Get the nonbonded interactions in the ``Universe``
|
| Returns
| -------
| list
| The nonbonded interactions in the ``Universe``
|
| parameters
| Get the parameters of the interactions that exist within the
| ``Universe``
|
| Returns
| -------
| Parameters
| The ``Parameters`` objects defined within ``Universe``
|
| solvent_density
|
| structure_list
| Get a `list` of all ``Structure`` objects that exist in the
| ``Universe``. This includes all ``Structure`` that are a subunit
| of another structure belonging to the ``Universe``.
|
|
| Returns
| -------
| list
| The ``Structure`` objects in the ``Universe``
|
| top_level_structure_list
| Get a `list` of the top level ``Structure`` objects that exist in the
| ``Universe``. This does not include any ``Structure`` that are a subunit
| of another structure belonging to the ``Universe``.
|
|
| Returns
| -------
| list
| The top level ``Structure`` objects in the ``Universe``
|
| volume
|
| ----------------------------------------------------------------------
| Data descriptors defined here:
|
| dimensions
|
| ----------------------------------------------------------------------
| Data and other attributes defined here:
|
| __abstractmethods__ = frozenset()
|
| __hash__ = None
|
| ----------------------------------------------------------------------
| Methods inherited from MDMC.MD.container.AtomContainer:
|
| __getitem__(self, index: Union[int, slice]) -> Union[ForwardRef('Atom'), ForwardRef('list[Atom]')]
| Returns
| -------
| Atom, list
| The atom (or atoms) for the specified index (or slice)
|
| ----------------------------------------------------------------------
| Data descriptors inherited from MDMC.MD.container.AtomContainer:
|
| __dict__
| dictionary for instance variables (if defined)
|
| __weakref__
| list of weak references to the object (if defined)
The same applies for methods, for example:
[9]:
help(universe.add_structure)
Help on method add_structure in module MDMC.MD.simulation:
add_structure(structure: Union[MDMC.MD.structures.Structure, int], force_field: str = None, center: bool = False) -> None method of MDMC.MD.simulation.Universe instance
Adds a single ``Structure`` to the ``Universe``, with optional
``ForceField`` applying only to that ``Structure``
Parameters
----------
structure : Structure or int
The ``Structure`` (or its ``ID`` as an `int`) to be added to the
``Universe``
force_field : str, optional
The force field to be applied to the structure. The available
``ForceField`` are:
SPC, TIP3P, OPLSAA, TIP3PFB, SPCE
center : bool, optional
Whether to center `structure` within the Universe as it is
added
Create bonded interactions
There are three bonded interaction types within MDMC: Bond
, BondAngle
and Dihedral
. Each Interaction
must have an InteractionFunction
which describes the Interaction
.
Bond
Below this is demonstrated where a Bond
with a HarmonicPotential
function is created.
[10]:
# Import Bond and HarmonicPotential
from MDMC.MD import Bond
from MDMC.MD import HarmonicPotential
# Create a Bond with a HarmonicPotential
# The first argument in the HarmonicPotential is the equilibrium state and the second is the potential strength
HH_bond = Bond(H1, H2, function=HarmonicPotential(1., 100., interaction_type='bond'))
To see which units are supported:
[11]:
from MDMC.common.units import SYSTEM
SYSTEM
[11]:
{'LENGTH': 'Ang',
'TIME': 'fs',
'MASS': 'amu',
'CHARGE': 'e',
'ANGLE': 'deg',
'TEMPERATURE': 'K',
'ENERGY': 'kJ / mol',
'FORCE': 'kJ / Ang mol',
'PRESSURE': 'Pa',
'ENERGY_TRANSFER': 'meV',
'ARBITRARY': 'arb'}
BondAngle
A BondAngle
is created in the same manner except it requires a minimum of three atoms. The central atom should be the 2nd atom. For example, a water molecule would have:
[12]:
from MDMC.MD import BondAngle
H1 = Atom('H')
H2 = Atom('H', position=[0., 1.63298, 0.])
O = Atom('O', position=[0., 0.81649, 0.57736])
HOH_angle = BondAngle(H1, O, H2)
# The following is equivalent
HOH_angle = BondAngle(H2, O, H1)
Currently HarmonicPotential
is the only InteractionFunction
that can be applied to either Bond
or BondAngle
.
All Bond
and BondAngle
can be constrained; this can either be set when creating the Bond
(or BondAngle
) or afterwards:
[13]:
HH_bond.constrained = True
HOH_angle = BondAngle(H1, O, H2, constrained=True)
For a constraint to be applied during a simulation, the Universe
must have a ConstraintAlgorithm
.
DihedralAngle
A DihedralAngle
is also created in the same manner, except it requires four atoms. A DihedralAngle
can be proper or improper, as specified by DihedralAngle.improper
. By default a DihedralAngle
is proper.
The atoms in a proper DihedralAngle
must be specified so that the 2nd and 3rd atoms are the central two atoms:
[14]:
from MDMC.MD import DihedralAngle
C1 = Atom('C', position=[5.12033922, 4.63847287, 4.94075943])
N = Atom('N', position=[5.12991894, 3.78609704, 3.79996577])
C2 = Atom('C', position=[4.91462725, 4.08992816, 2.5091264])
O = Atom('O', position=[4.67405373, 5.24130678, 2.1180462])
proper = DihedralAngle(atoms=[C1, N, C2, O])
# For a proper DihedralAngle, the equivalent atom order is:
proper = DihedralAngle(atoms=[O, C2, N, C1])
The atoms in an improper DihedralAngle
must be specific so that the 1st atom is the central atom.
[15]:
N = Atom('N', position=[4.97080909, 2.91075722, 1.57280005])
C1 = Atom('C', position=[5.12033922, 4.63847287, 4.94075943])
C2 = Atom('C', position=[5.12991894, 3.78609704, 3.79996577])
C3 = Atom('C', position=[4.91462725, 4.08992816, 2.5091264 ])
improper = DihedralAngle(atoms=[N, C1, C2, C3], improper=True)
# The following are some of the equivalent permutations
# The only unique atom location is the first location (central atom)
improper = DihedralAngle(atoms=[N, C2, C1, C3], improper=True)
improper = DihedralAngle(atoms=[N, C3, C1, C2], improper=True)
improper = DihedralAngle(atoms=[N, C1, C3, C2], improper=True)
Currently Periodic
is the only InteractionFunction
that can be applied to DihedralAngle
interactions.
Copying bonded atoms
As mentioned above in the ‘Create an atom’ section, if an Atom
with a BondedInteraction
is copied, the new atom will also have the same BondedInteraction
(and be bonded to the same atom or atoms as the original). For example:
[16]:
wH1 = Atom('H', position=(5., 5., 5.))
wO = Atom('O', position=(5., 6.63298, 5.))
wBond = Bond((wH1, wO), function=HarmonicPotential(1., 100., interaction_type='bond'))
wH2 = wH1.copy(position=(0., 0.81649, 0.57736))
Both atoms wH1
and wH2
are bonded to wO
:
[17]:
wBond.atoms
[17]:
[(<Atom
{name: 'H',
ID: 16,
element: H,
position: UnitNDArray([5., 5., 5.]) Ang,
velocity: UnitNDArray([0., 0., 0.]) Ang / fs}>,
<Atom
{name: 'O',
ID: 17,
element: O,
position: UnitNDArray([5. , 6.63298, 5. ]) Ang,
velocity: UnitNDArray([0., 0., 0.]) Ang / fs}>),
(<Atom
{name: 'H',
ID: 18,
element: H,
position: UnitNDArray([0. , 0.81649, 0.57736]) Ang,
velocity: UnitNDArray([0., 0., 0.]) Ang / fs}>,
<Atom
{name: 'O',
ID: 17,
element: O,
position: UnitNDArray([5. , 6.63298, 5. ]) Ang,
velocity: UnitNDArray([0., 0., 0.]) Ang / fs}>)]
Create a molecule
A molecule consists of two or more atoms and at least one bonded interaction:
[18]:
# Import the Atom, Molecule, Bond, BondAngle and HarmonicPotential classes
from MDMC.MD import Molecule, BondAngle
# Create a H2 molecule
H1.position = [0., 0., 0.]
H2.position = [1., 1., 1.]
H_mol = Molecule(position=[2., 1.5, 1.], atoms=[H1, H2], interactions=[HH_bond])
When a Molecule
is created, the position of the atoms relative to one another is fixed. The atoms are then moved so that the position of the molecular centre of mass is what was passed when creating the molecule. In the example above, the atoms were at [0., 0., 0.]
and [1., 1., 1.]
before H_mol
was created; therefore they will always be separated by 1.0 Ang
in each dimension, no matter where the molecule is moved to. The molecular centre of mass is set to [2., 1.5, 1.]
,
so the atom positions are changed to [2., 1.5, 1.]
and [3., 2.5, 2.]
respectively. It is also possible to copy
molecules:
[19]:
H_mol2 = H_mol.copy(position=[5., 5., 5.])
When a Molecule
is copied, each Atom
is copied, as are all of the bonded interactions between these atoms (and all of the non-bonded interactions).
One method for building molecules is to copy atoms, as the interactions are also copied. For example, to build methane:
[20]:
m_C = Atom('C')
# Define the H atom positions relative to a C at [0,0,0]
d = 0.629118
H_pos = [[d, d, d], [-d, -d, d], [d, -d, -d], [-d, d, -d]]
m_H = Atom('H', position=H_pos[0])
# Add a bond between
CH_bonds = Bond(m_C, m_H, function=HarmonicPotential(1.09, 100., interaction_type='bond'))
# Make three H atom copies (which are therefore all bonded to m_C)
H_atoms = [m_H]
for pos in H_pos[1:]:
H_atoms.append(m_H.copy(pos))
# The next two lines simply create a list of unique HCH triplets
# e.g. [(m_H1, m_C, m_H2), (m_H1, m_C, m_H3), ..., (m_H3, m_C, m_H4)].
from itertools import combinations
HCH_triplets = [(i[0], m_C, i[1]) for i in combinations(H_atoms, 2)]
# Unpack the list of triplets with * notation i.e. [(...), (...), (...)] becomes (...), (...), (...)
HCH_angles = BondAngle(*HCH_triplets, function=HarmonicPotential(109.5, 10., interaction_type='angle'))
# Create a methane molecule by adding C atom to list of H atoms
methane = Molecule(atoms=[m_C]+H_atoms)
Creating molecules using configuration files
In addition to creating molecules manually, it is also possible to create them from atomic configuration files (e.g CIF files). Please see the tutorial Reading atoms from configuration files for a detailed description on how to do this.
Add structural units to a universe
There are two methods for adding a structural unit to a universe:
[21]:
# Add an individual structural unit to the universe
universe.add_structure(H_mol)
# Fill the universe with the structural unit repeated on a cubic lattice with a specific number density
universe.fill(H_mol, num_density = 0.01)
Currently the fill
command cannot be used in conjunction with add_structure
. When using it with a cubic universe the density will be isotropic, however the exact number of structural units added by fill
may be lower than expected as it will always add a cube number. Using fill
with a non-cubic universe is not recommended as the density may or may not be isotropic depending on the dimensions of the universe and the number of units.
Create non-bonded interactions
Non-bonded interactions are applied to atoms based on their atom_type
, rather than to individual atoms. They must also have a Universe
specified, so that they know to which atoms they apply (atoms must have an atom_type
once they have been added to a universe). For example, to create a Dispersion
interaction with a LennardJones
interaction function between two atom types:
[22]:
# Import the Dispersion interaction and Lennard-Jones function
from MDMC.MD import Dispersion
from MDMC.MD import LennardJones
# Create a dispersion interaction with a Lennard-Jones function between atoms with atom_type 1 (O) and atom_type 2 (H)
# The first LJ parameter is epsilon and the second is sigma
# The cutoff for the dispersion interaction can also be set (in Ang)
LJ_HO = Dispersion(universe, [1, 2], function=LennardJones(0.65, 3.), cutoff=10.)
The exception to this is Coulombic
interactions, which can be applied either to a list of atoms or to a list of atom types. If the Coulombic interaction is applied to a list of atoms, the universe does not need to be specified:
[23]:
# Import the Coulombic interaction
from MDMC.MD import Coulombic
# Create a Coulombic interaction with a Coulomb potential and a charge of 0.42
c_H = Coulombic(atoms=[H1, H2], charge=0.42)
As Coulombic
interactions typically have the same interaction function (i.e. a Coulomb
function, where the force results in Coulomb’s law), Coulombic
interactions do not need to be specified with an interaction function (although other functions can be provided); to set the function as Coulomb
, a value for the charge can be passed. The warning highlights the Coulomb
interaction function has been automatically set. This is equivalent to:
[24]:
from MDMC.MD import Coulomb
c_H = Coulombic(atoms=[H1, H2], function=Coulomb(0.42))
As with all non-bonded interactions, a Coulombic interaction can also be created by specifying the atom types:
[25]:
c_O = Coulombic(universe, atom_types=[1], charge=0.42)
In this case a universe must be provided.
Applying a ForceField
As well as manually setting the Parameter
values for each interaction (e.g. Coulombic(atoms=[H1, H2], function=Coulomb(0.42))
to set the charge
), it is also possible to apply a ForceField
to a Universe
, in order to set the Parameter
values. Please see the tutorial Applying a ForceField for a detailed description on how to do this.
Adding kspace solvers to a universe
There are several solvers for determining the long range energy contribution for non-bonded interactions, including Ewald, particle-particle particle-mesh (PPPM), and particle-mesh Ewald (PME). If the long range contribution to the non-bonded energy (i.e. > cutoff) is to be calculated during a simulation, a kspace solver has to be added to the universe. A solver can be specified for either electrostatic or dispersive interactions, or both. This can either be during universe initialisation or afterwards:
[26]:
# Import Ewald and PPPM kspace solvers
from MDMC.MD import Ewald, PPPM
# Create an ewald solver
ewald = Ewald(accuracy=1e-5)
# Initialise a universe with an Ewald solver for both electrostatics and dispersive interactions
uni1 = Universe(10., kspace_solver=ewald)
# Initialise a universe and then add a PPPM solver for electrostatic interactions
uni2 = Universe(10.)
pppm = PPPM(accuracy=1e-4)
uni2.electrostatic_solver = pppm
# Initialise a universe with a PPPM solver for dispersive interactions
uni3 = Universe(10., dispersive_solver=PPPM)
Universe created with:
Dimensions [10. 10. 10.]
Universe created with:
Dimensions [10. 10. 10.]
Universe created with:
Dimensions [10. 10. 10.]
Not all kspace solvers are implemented for all MD engines, and they may also require different parameters to be specified - see the MD engine documentation for more information.
Adding constraint algorithms to a universe
In a similar manner to kspace solvers, constraint algorithms (e.g. SHAKE, RATTLE) can also be passed to a universe. A constraint algorithm is required if any of the bonded interactions are constrained.
[27]:
# Import Shake and Rattle
from MDMC.MD import Shake, Rattle
# Initialise a universe with the Shake algorithm
# The first Shake parameter is the accuracy and the second is the maximum number of iterations used for any constraint calculation
shake = Shake(1e-4, 100)
uni4 = Universe(10., constraint_algorithm=shake)
# Add Rattle after universe initialisation
rattle = Rattle(1e-5, 1000)
uni5 = Universe(10.)
uni5.constraint_algorithm = rattle
Universe created with:
Dimensions [10. 10. 10.]
Universe created with:
Dimensions [10. 10. 10.]
Not all constraint algorithms are implemented for all MD engines, and they may also required different parameters to be specified - see the MD engine documentation for more information.
Example Universe filled with water
For water, SPCE, SPC, TIP3P and TIP3PFB forcefields are predefined, so parameters do not need to be set when interactions are defined; instead parameters are set by adding a forcefield to the universe.
Note that all water models have (harmonic) potential strengths defined for their Bond
s and BondAngle
s. In order to create a rigid water molecule in accordance with the models, a constraint_algorithm
should be passed to the Universe
and constrained=True
passed to the relevant interactions, as shown below.
“Constraining” the ability of the bonds to oscillate during MD in this way should not be confused with constraining the value of an MDMC Parameter
to a certain numerical range during refinement, as described in Running a Refinement. It is entirely possible to have a rigid bond but allow the length of that bond to change between refinement steps, or conversely have a bond that is free to oscillate during MD but the equilibrium length is not altered as part of
the refinement.
[28]:
from MDMC.MD import *
universe = Universe(dimensions=21.75, constraint_algorithm=Shake(1e-4, 100), electrostatic_solver=PPPM(accuracy=1e-5))
H1 = Atom('H')
H2 = Atom('H', position=(0., 1.63298, 0.))
O = Atom('O', position=(0., 0.81649, 0.57736))
H_coulombic = Coulombic(atoms=[H1, H2], cutoff=10.)
O_coulombic = Coulombic(atoms=O, cutoff=10.)
water_mol = Molecule(position=(0, 0, 0),
velocity=(0, 0, 0),
atoms=[H1, H2, O],
interactions=[Bond((H1, O), (H2, O), constrained=True),
BondAngle(H1, O, H2, constrained=True)],
name='water')
universe.fill(water_mol, num_density=0.03356718472021752)
O_dispersion = Dispersion(universe, [O.atom_type, O.atom_type], cutoff=10., vdw_tail_correction=True)
universe.add_force_field('SPCE')
Universe created with:
Dimensions [21.75 21.75 21.75]
Accessing information
After creating a Universe
, the various properties and attributes of it can be accessed as follows:
Geometry
[29]:
print('The dimension(s) of the Universe are {0}, giving a volume of {1}'
''.format(universe.dimensions, universe.volume))
The dimension(s) of the Universe are [21.75 21.75 21.75] Ang, giving a volume of 10289.109375 Ang ^ 3
Forcefield
[30]:
print('The current force field applied to the Universe is:\n{}'
''.format(universe.force_fields))
The current force field applied to the Universe is:
<SPCE
{interaction_dictionary: {(<class 'MDMC.MD.interactions.Coulombic'>, ('O',)): <Coulomb
{parameters: {'charge (#54)': <Parameter
{ID: 54,
type: 'charge',
value: -0.8476 e,
unit: 'e',
fixed: False,
constraints: None,
interactions_name: None,
functions_name: None,
tied: False}>}}>, (<class 'MDMC.MD.interactions.Coulombic'>, ('H',)): <Coulomb
{parameters: {'charge (#55)': <Parameter
{ID: 55,
type: 'charge',
value: 0.4238 e,
unit: 'e',
fixed: False,
constraints: None,
interactions_name: None,
functions_name: None,
tied: False}>}}>, (<class 'MDMC.MD.interactions.Dispersion'>, ('O', 'O')): <LennardJones
{parameters: {'epsilon (#56)': <Parameter
{ID: 56,
type: 'epsilon',
value: 0.6502 kJ / mol,
unit: 'kJ / mol',
fixed: False,
constraints: None,
interactions_name: None,
functions_name: None,
tied: False}>, 'sigma (#57)': <Parameter
{ID: 57,
type: 'sigma',
value: 3.166 Ang,
unit: 'Ang',
fixed: False,
constraints: None,
interactions_name: None,
functions_name: None,
tied: False}>}}>, (<class 'MDMC.MD.interactions.Bond'>, ('H', 'O')): <HarmonicPotential
{parameters: {'equilibrium_state (#58)': <Parameter
{ID: 58,
type: 'equilibrium_state',
value: 1.0 Ang,
unit: 'Ang',
fixed: False,
constraints: None,
interactions_name: None,
functions_name: None,
tied: False}>, 'potential_strength (#59)': <Parameter
{ID: 59,
type: 'potential_strength',
value: 4637.0 kJ / mol Ang ^ 2,
unit: 'kJ / mol Ang ^ 2',
fixed: False,
constraints: None,
interactions_name: None,
functions_name: None,
tied: False}>}}>, (<class 'MDMC.MD.interactions.BondAngle'>, ('H', 'O', 'H')): <HarmonicPotential
{parameters: {'equilibrium_state (#60)': <Parameter
{ID: 60,
type: 'equilibrium_state',
value: 109.47 deg,
unit: 'deg',
fixed: False,
constraints: None,
interactions_name: None,
functions_name: None,
tied: False}>, 'potential_strength (#61)': <Parameter
{ID: 61,
type: 'potential_strength',
value: 383.0 kJ / mol rad ^ 2,
unit: 'kJ / mol rad ^ 2',
fixed: False,
constraints: None,
interactions_name: None,
functions_name: None,
tied: False}>}}>},
n_body: 3}>
Configuration of atoms and molecules
[31]:
print('There are {0} atoms in the Universe\n'
''.format(universe.n_atoms))
# For a complete list run `print(universe.atoms)`
There are 1029 atoms in the Universe
[32]:
print('There are {0} molecules in the Universe\n'
''.format(universe.n_molecules))
# For a complete list run `print(universe.molecules)`
There are 343 molecules in the Universe
Configuration of interactions
[33]:
print('There are {0} total interactions in the Universe\n'
''.format(universe.n_interactions))
# For a complete list run `print(universe.interactions)`
There are 1032 total interactions in the Universe
[34]:
print('There are {0} bonded interactions in the Universe\n'
''.format(universe.n_bonded))
# For a complete list run `print(universe.bonded_interactions)`
There are 1029 bonded interactions in the Universe
[35]:
print('There are {0} nonbonded interactions in the Universe\n'
''.format(universe.n_nonbonded))
# For a complete list run `print(universe.nonbonded_interactions)`
There are 3 nonbonded interactions in the Universe
Constraints
[36]:
print('The current constraint algorithm applied to the Universe is {}'
''.format(universe.constraint_algorithm.name))
The current constraint algorithm applied to the Universe is Shake
Solvers
Note that a kspace_solver
is mutually excusive with the other solver types.
[37]:
print('The kspace_solver is {}'.format(universe.kspace_solver and universe.kspace_solver.name))
print('The electrostatic_solver is {}'.format(universe.electrostatic_solver and universe.electrostatic_solver.name))
print('The dispersive_solver is {}'.format(universe.dispersive_solver and universe.dispersive_solver.name))
The kspace_solver is None
The electrostatic_solver is PPPM
The dispersive_solver is None
Parameters
[38]:
print('The MDMC parameters of the Universe are:\n{}'
''.format(universe.parameters))
The MDMC parameters of the Universe are:
{'equilibrium_state (#50)': <Parameter
{ID: 50,
type: 'equilibrium_state',
value: 1.0 Ang,
unit: 'Ang',
fixed: False,
constraints: None,
interactions_name: 'Bond',
functions_name: 'HarmonicPotential',
tied: False}>, 'potential_strength (#51)': <Parameter
{ID: 51,
type: 'potential_strength',
value: 4637.0 kJ / mol Ang ^ 2,
unit: 'kJ / mol Ang ^ 2',
fixed: False,
constraints: None,
interactions_name: 'Bond',
functions_name: 'HarmonicPotential',
tied: False}>, 'equilibrium_state (#44)': <Parameter
{ID: 44,
type: 'equilibrium_state',
value: 109.47 deg,
unit: 'deg',
fixed: False,
constraints: None,
interactions_name: 'BondAngle',
functions_name: 'HarmonicPotential',
tied: False}>, 'potential_strength (#45)': <Parameter
{ID: 45,
type: 'potential_strength',
value: 383.0 kJ / mol rad ^ 2,
unit: 'kJ / mol rad ^ 2',
fixed: False,
constraints: None,
interactions_name: 'BondAngle',
functions_name: 'HarmonicPotential',
tied: False}>, 'charge (#22)': <Parameter
{ID: 22,
type: 'charge',
value: -0.8476 e,
unit: 'e',
fixed: False,
constraints: None,
interactions_name: 'Coulombic',
functions_name: 'Coulomb',
tied: False}>, 'charge (#15)': <Parameter
{ID: 15,
type: 'charge',
value: 0.4238 e,
unit: 'e',
fixed: False,
constraints: None,
interactions_name: 'Coulombic',
functions_name: 'Coulomb',
tied: False}>, 'epsilon (#32)': <Parameter
{ID: 32,
type: 'epsilon',
value: 0.6502 kJ / mol,
unit: 'kJ / mol',
fixed: False,
constraints: None,
interactions_name: 'Dispersion',
functions_name: 'LennardJones',
tied: False}>, 'sigma (#33)': <Parameter
{ID: 33,
type: 'sigma',
value: 3.166 Ang,
unit: 'Ang',
fixed: False,
constraints: None,
interactions_name: 'Dispersion',
functions_name: 'LennardJones',
tied: False}>}