__all__ = ["System"]
[docs]class System:
"""
This class holds a complete system of molecules. This is normally
created by reading in molecules from an input file.
This provides a "MoleculeView"-style interface to the molecules,
acting very similarly to a sire.mol.SelectorMol object.
You can convert this to a sire.mol.SelectorMol object by
calling the System.molecules() function.
"""
def __init__(self, system=None):
from ..legacy.System import System as _System
if system is None:
from ..vol import Cartesian
from ..units import picosecond
from ..base import wrap
self._system = _System()
self._system.add_shared_property("space", Cartesian())
self._system.add_shared_property("time", wrap(0 * picosecond))
else:
if _System not in type(system).mro():
raise TypeError(
"You can only construct from a sire.legacy.System.System, "
f"not a {type(system)}"
)
if type(system) == System:
self._system == system._system
else:
self._system = system
self._molecules = None
[docs] @staticmethod
def is_system(obj):
"""
Return whether the passed object is a System class
(either a new or legacy System)
"""
from ..legacy.System import System as _System
return type(obj) == System or type(obj) == _System
def _to_legacy_system(self):
"""
Internal function used to convert this back to a legacy system
"""
return self._system
def __copy__(self):
other = System()
other._system = self._system.clone()
other._molecules = None
return other
def __deepcopy__(self, memo):
return self.__copy__()
def __str__(self):
return str(self._system)
def __repr__(self):
return self.__str__()
def __getitem__(self, key):
return self.molecules()[key]
def __iadd__(self, molecules):
self.add(molecules)
return self
def __add__(self, molecules):
ret = self.__copy__()
ret.add(molecules)
return ret
def __radd__(self, molecules):
return self.__add__(molecules)
def __isub__(self, molecules):
self.remove(molecules)
return self
def __sub__(self, molecules):
ret = self.__copy__()
ret.remove(molecules)
return ret
[docs] def clone(self):
"""Return a copy (clone) of this System"""
s = System()
s._system = self._system.clone()
s._molecules = None
return s
[docs] def count(self):
"""Return the number of items in this System"""
return self.__len__()
[docs] def size(self):
"""Return the number of items in this System"""
return self.__len__()
def __len__(self):
return len(self.molecules())
[docs] def num_atoms(self):
"""Return the number of atoms in this System"""
return self._system.num_atoms()
[docs] def num_residues(self):
"""Return the number of residues in this System"""
return self._system.num_residues()
[docs] def num_chains(self):
"""Return the number of chains in this System"""
return self._system.num_chains()
[docs] def num_segments(self):
"""Return the number of segments in this System"""
return self._system.num_segments()
[docs] def num_molecules(self):
"""Return the number of molecules in this System"""
return self._system.num_molecules()
[docs] def find(self, views):
"""Return the index(es) of the molecule(s) that are in `views`"""
return self.molecules().find(views)
[docs] def names(self):
"""Return the names of all of the molecules in this System"""
return self.molecules().names()
[docs] def numbers(self):
"""Return the numbers of all of the molecules in this System"""
return self.molecules().numbers()
[docs] def make_whole(self, map=None):
"""
Make all of the molecules in this system whole. This
maps each molecule into the current space, such that no
molecule is broken across a periodic box boundary
"""
if map is None:
self._system.make_whole()
else:
from ..base import create_map
self._system.make_whole(map=create_map(map))
self._molecules = None
[docs] def num_frames(self, map=None):
"""Return the number of trajectory frames for this System"""
from ..base import create_map
return self._system.num_frames(map=create_map(map))
[docs] def load_frame(self, i, map=None):
"""Load the ith frame into this System"""
from ..base import create_map
self._system.load_frame(i, map=create_map(map))
self._molecules = None
[docs] def save_frame(self, i=None, map=None):
"""Save the current coordinates to the ith frame of this System.
If i is not specfied then this adds the frame onto the
end of the trajectory
"""
from ..base import create_map
map = create_map(map)
if i is None:
self._system.save_frame(map=map)
else:
self._system.save_frame(i, map=map)
self._molecules = None
[docs] def delete_frame(self, i, map=None):
"""Delete the ith frame from the trajectory"""
from ..base import create_map
self._system.delete_frame(i, map=create_map(map))
self._molecules = None
[docs] def delete_all_frames(self, map=None):
"""Delete all the frames from the trajectory"""
from ..base import create_map
self._system.delete_all_frames(map=create_map(map))
self._molecules = None
[docs] def to_molecule_group(self):
"""Return this System converted to a sire.mol.MoleculeGroup"""
return self.molecules().to_molecule_group()
[docs] def molecules(self, *args, **kwargs):
"""Return this System converted to a sire.mol.SelectorMol.
You can pass in arguments to search or index so that you
limit the number of molecules returned.
"""
if self._molecules is not None:
return self._molecules.molecules(*args, **kwargs)
import sire.mol
self._molecules = sire.mol.SelectorMol(self._system)
if self._molecules.num_atoms() != self._system.num_atoms():
# oh dear - this is an edge case where the System does
# not contain complete molecules. We need to extract
# the molecules and re-add them
raise NotImplementedError(
"sire.system.System does not yet support Systems that hold "
"partial molecules!. Let us know that you have hit this "
"bug and we will add support."
)
return self.molecules(*args, **kwargs)
[docs] def segments(self, *args, **kwargs):
"""Return all segments in this System (or those that match
the passed index, if supplied)
"""
return self.molecules().segments(*args, **kwargs)
[docs] def chains(self, *args, **kwargs):
"""Return all chains in this System (or those that match
the passed index, if supplied)
"""
return self.molecules().chains(*args, **kwargs)
[docs] def residues(self, *args, **kwargs):
"""Return all residues in this System (or those that match
the passed index, if supplied)
"""
return self.molecules().residues(*args, **kwargs)
[docs] def atoms(self, *args, **kwargs):
"""Return all atoms in this System (or those that match
the passed index, if supplied)
"""
return self.molecules().atoms(*args, **kwargs)
[docs] def bonds(self, *args, **kwargs):
"""Return all bonds in this System (or those that match
the passed index, if supplied)
"""
return self.molecules().bonds(*args, **kwargs)
[docs] def angles(self, *args, **kwargs):
"""Return all angles in this System (or those that match
the passed index, if supplied)
"""
return self.molecules().angles(*args, **kwargs)
[docs] def dihedrals(self, *args, **kwargs):
"""Return all dihedrals in this System (or those that match
the passed index, if supplied)
"""
return self.molecules().dihedrals(*args, **kwargs)
[docs] def impropers(self, *args, **kwargs):
"""Return all impropers in this System (or those that match
the passed index, if supplied)
"""
return self.molecules().impropers(*args, **kwargs)
[docs] def molecule(self, *args, **kwargs):
"""Return the molecule that matches the passed index/search"""
return self.molecules().molecule(*args, **kwargs)
[docs] def segment(self, *args, **kwargs):
"""Return the segment that matches the passed index/search"""
return self.molecules().segment(*args, **kwargs)
[docs] def chain(self, *args, **kwargs):
"""Return the chain that matches the passed index/search"""
return self.molecules().chain(*args, **kwargs)
[docs] def residue(self, *args, **kwargs):
"""Return the residue that matches the passed index/search"""
return self.molecules().residue(*args, **kwargs)
[docs] def atom(self, *args, **kwargs):
"""Return the atom that matches the passed index/search"""
return self.molecules().atom(*args, **kwargs)
[docs] def bond(self, *args, **kwargs):
"""Return the bond that matches the passed index/search"""
return self.molecules().bond(*args, **kwargs)
[docs] def angle(self, *args, **kwargs):
"""Return the angle that matches the passed index/search"""
return self.molecules().angle(*args, **kwargs)
[docs] def dihedral(self, *args, **kwargs):
"""Return the dihedral that matches the passed index/search"""
return self.molecules().dihedral(*args, **kwargs)
[docs] def improper(self, *args, **kwargs):
"""Return the improper that matches the passed index/search"""
return self.molecules().improper(*args, **kwargs)
[docs] def trajectory(self, *args, **kwargs):
"""
Return an iterator over the trajectory of frames of this view.
align:
Pass in a selection string to select atoms against which
every frame will be aligned. These atoms will be moved
to the center of the periodic box (if a periodic box
is used). If 'True' is passed then this will align
against all of the atoms in the view.
smooth:
Pass in the number of frames to smooth (average) the view
over. If 'True' is passed, then the recommended number
of frames will be averaged over
wrap: bool
Whether or not to wrap the coordinates into the periodic box
"""
from ..mol._trajectory import TrajectoryIterator
return TrajectoryIterator(self, *args, **kwargs)
[docs] def minimisation(self, map=None):
"""
Return a Minimisation object that can be used to minimise the energy
of the molecule(s) in this view.
"""
from ..mol import Minimisation
return Minimisation(self, map=map)
[docs] def dynamics(self, *args, **kwargs):
"""
Return a Dynamics object that can be used to perform
dynamics of the molecule(s) in this view
"""
from ..mol import _dynamics
return _dynamics(self, *args, **kwargs)
[docs] def energy(self, *args, **kwargs):
"""Calculate and return the energy of this System
(or of the matching index/search subset of this System)
"""
return self.molecules().energy(*args, **kwargs)
[docs] def energies(self, *args, **kwargs):
"""Calculate and return the individual energies of the
contents of this System (or of the matching index/search
subset of this System)
"""
return self.molecules().energies(*args, **kwargs)
[docs] def charge(self, *args, **kwargs):
"""Return the total charge of this System (or of the matching
index/search subset of this System)
"""
return self.molecules().charge(*args, **kwargs)
[docs] def mass(self, *args, **kwargs):
"""Return the total mass of this System (or of the matching
index/search subset of this System)
"""
return self.molecules().mass(*args, **kwargs)
[docs] def coordinates(self, *args, **kwargs):
"""Return the center of geometry of this System (or of the matching
index/search subset of this System)
"""
return self.molecules().coordinates(*args, **kwargs)
[docs] def space(self, map=None):
"""
Return the space used for this system
"""
try:
if map is None:
return self._system.property("space")
else:
from ..base import create_map
map = create_map(map)
return self._system.property(map["space"])
except Exception:
from ..vol import Cartesian
return Cartesian()
[docs] def time(self, map=None):
"""
Return the current system time
"""
try:
if map is None:
return self._system.property("time")
else:
from ..base import create_map
map = create_map(map)
return self._system.property(map["time"])
except Exception:
from ..units import picosecond
return 0 * picosecond
[docs] def set_space(self, space, map=None):
"""
Set the space to be used to hold all of the molecules
in this system
"""
from ..legacy.Vol import Space
if not issubclass(type(space), Space):
raise TypeError(
"You can only set the space to a type derived from sire.vol.Space, "
"e.g. sire.vol.PeriodicBox, sire.vol.TriclinicBox or "
f"sire.vol.Cartesian. You cannot use a {type(space)}."
)
if map is None:
self._system.set_property("space", space)
else:
from ..base import create_map
map = create_map(map)
space_property = map["space"]
if space_property.has_source():
self._system.set_property(space_property.source(), space)
self._molecules = None
[docs] def set_time(self, time, map=None):
"""
Set the current time for the system
"""
from ..units import picosecond
from ..base import wrap
if time == 0:
time = wrap(0 * picosecond)
else:
if not hasattr(time, "has_same_units"):
raise TypeError(
"You can only set the time to a value with units 'time', e.g. "
f"5 * sire.units.picosecond. YOu cannot use a {type(time)}."
)
if not time.has_same_units(picosecond):
raise TypeError(
"You can only set the time to a value of units time. You "
f"cannot use {time}."
)
time = wrap(time)
if map is None:
self._system.set_property("time", time)
else:
from ..base import create_map
map = create_map(map)
time_property = map["time"]
if time_property.has_source():
self._system.set_property(time_property.source(), time)
self._molecules = None
[docs] def evaluate(self, *args, **kwargs):
"""Return an evaluator for this Systme (or of the matching
index/search subset of this System)"""
return self.molecules().evaluate(*args, **kwargs)
[docs] def has_property(self, *args, **kwargs):
"""Return whether or not this system has the passed property"""
return self._system.contains_property(*args, **kwargs)
[docs] def property(self, *args, **kwargs):
"""Return the System property that matches the passed key/arguments"""
return self._system.property(*args, **kwargs)
[docs] def set_property(self, *args, **kwargs):
"""Set the System property according to the passed arguments"""
self._system.set_property(*args, **kwargs)
self._molecules = None
[docs] def set_shared_property(self, name, value):
"""Set the shared System property according to the passed arguments"""
from ..base import wrap
self._system.set_shared_property(name, wrap(value))
self._molecules = None
[docs] def add_shared_property(self, name, value=None):
"""
Add the shared System property called 'name' to this system.
If 'value' is supplied, then this also sets the property too.
"""
if value is None:
self._system.add_shared_property(name)
else:
from ..base import wrap
self._system.add_shared_property(name, wrap(value))
self._molecules = None
[docs] def remove_shared_property(self, *args, **kwargs):
"""
Completely remove the specified shared property, and set this
as a non-shared property for the future
"""
self._system.remove_shared_property(*args, **kwargs)
self._molecules = None
[docs] def remove_all_shared_properties(self, *args, **kwargs):
"""
Completely remove all shared properties, and set no properties
as being shared from this system
"""
self._system.remove_all_shared_properties(*args, **kwargs)
self._molecules = None
[docs] def properties(self):
"""Return all of the System-level properties of this System"""
return self._system.properties()
[docs] def property_keys(self):
"""Return the keys (IDs) of all of the System-level properties
of this System
"""
return self._system.property_keys()
[docs] def shared_properties(self):
"""
Return all of the System properties that are being shared
(copied) to all contained molecules
"""
return self._system.shared_properties()
[docs] def cursor(self):
"""
Return a sire.mol.Cursor that can be used to edit
the molecules in this System
"""
from ..mol._cursor import CursorsM
return CursorsM(self)
[docs] def smiles(self, *args, **kwargs):
"""
Return the molecule views in this container as smiles strings. Include
hydrogens in 'include_hydrogens' is True. This returns a list
of smiles strings, in the same order as the views in the container
"""
return self.molecules().smiles(*args, **kwargs)
[docs] def smarts(self, *args, **kwargs):
"""
Return the molecule views in this container as smarts strings. Include
hydrogens in 'include_hydrogens' is True. This returns a list
of smarts strings, in the same order as the views in the container
"""
return self.molecules().smarts(*args, **kwargs)
[docs] def view(self, *args, **kwargs):
"""
View this System (or the matching index/search subset)
via a nglview viewer. Only works in an interactive
notebook session, e.g. in a Jupyter notebook
"""
return self.molecules().view(*args, **kwargs)
[docs] def view2d(self, *args, **kwargs):
"""
Create a 2D representation of the molecules in this system.
If 'filename' is set then this will be written to that file. Otherwise
this will be returned for visualisation in a jupyter notebook.
"""
return self.molecules().view2d(*args, **kwargs)
[docs] def add(self, molecules):
"""
Add the passed molecules to this system. This will only
add molecules that don't already exist in this system.
"""
if type(molecules) is list:
for molecule in molecules:
self.add(molecule)
return
from ..legacy.Mol import MGName
mgid = MGName("all")
if hasattr(molecules, "molecules"):
# rather convoluted way to get a 'Molecules' object...
mols = molecules.molecules().to_molecule_group().molecules()
self._system.add_if_unique(mols, mgid)
else:
self._system.add_if_unique(molecules, mgid)
self._molecules = None
[docs] def remove(self, molecules):
"""
Remove the passed molecules from this system.
"""
if type(molecules) is list:
for molecule in molecules:
self.remove(molecule)
return
molnums = None
if hasattr(molecules, "molecules"):
molnums = molecules.molecules().mol_nums()
else:
molnums = [molecule.molecule().number()]
for molnum in molnums:
self._system.remove(molnum)
self._molecules = None
[docs] def update(self, molecules):
"""
Update the molecules in this system so that they have
the same versions and data as the new molecules contained
in 'value'
"""
if type(molecules) is list:
for molecule in molecules:
self.update(molecule)
return
if hasattr(molecules, "molecules"):
self._system.update(molecules.molecules())
else:
self._system.update(molecules)
self._molecules = None
[docs] def apply(self, *args, **kwargs):
"""
Apply (perform / map) the passed function (with associated arguments)
on all of the molecules in this System
"""
return self.molecules().apply(*args, **kwargs)
[docs] def apply_reduce(self, *args, **kwargs):
"""
Apply (perform / map) the passed function (with associated arguments)
on all of the molecules in this System, reducing the result
using the passed reduction function
"""
return self.molecules().apply_reduce(*args, **kwargs)