An on-the-fly transformation is a function that silently modifies the dynamic data contained in a trajectory
Timestep (typically coordinates) as it is loaded into memory. It is called for each current time step to transform data into your desired representation. A transformation function must also return the current
Timestep, as transformations are often chained together.
In : import MDAnalysis as mda In : from MDAnalysis.tests.datafiles import TPR, XTC In : from MDAnalysis import transformations as trans In : u = mda.Universe(TPR, XTC) In : protein = u.select_atoms('protein') In : align_transform = trans.fit_rot_trans(protein, protein, weights=protein.masses) In : u.trajectory.add_transformations(align_transform)
Other implemented transformations include functions to
AtomGroup to a reference, and
wrap or unwrap groups in the unit cell. (Please see the MDAnalysis on-the-fly transformations blog post contains a more complete introduction to these fitting and wrapping functions.)
Although you can only call
add_transformations() once, you can pass in multiple transformations in a list, which will be executed in order.
There is a transformations tutorial that shows in more detail how to use transformations. A few simple examples are given below.
The workflow below
makes all molecules whole (unwraps them over periodic boundary conditions),
centers the protein in the center of the box,
wraps water back into the box.
# create new Universe for new transformations In : u = mda.Universe(TPR, XTC) In : protein = u.select_atoms('protein') In : water = u.select_atoms('resname SOL') In : workflow = [trans.unwrap(u.atoms), ....: trans.center_in_box(protein, center='geometry'), ....: trans.wrap(water, compound='residues')] ....: In : u.trajectory.add_transformations(*workflow)
If your transformation does not depend on something within the
Universe (e.g. a chosen
AtomGroup), you can also create a
Universe directly with transformations. The code below translates coordinates 1 angstrom up on the z-axis:
In : u = mda.Universe(TPR, XTC, transformations=[trans.translate([0, 0, 1])])
If you need a different transformation, it is easy to implement your own.
At its core, a transformation function must only take a
Timestep as its input and return the
Timestep as the output.
In : import numpy as np In : def up_by_2(ts): ....: """Translates atoms up by 2 angstrom""" ....: ts.positions += np.array([0.0, 0.0, 0.2]) ....: return ts ....: In : u = mda.Universe(TPR, XTC, transformations=[up_by_2])
If your transformation needs other arguments, you will need to wrap your core transformation with a wrapper function that can accept the other arguments.
In : def up_by_x(x): ....: """Translates atoms up by x angstrom""" ....: def wrapped(ts): ....: """Handles the actual Timestep""" ....: ts.positions += np.array([0.0, 0.0, float(x)]) ....: return ts ....: return wrapped ....: # load Universe with transformations that move it up by 7 angstrom In : u = mda.Universe(TPR, XTC, transformations=[up_by_x(5), up_by_x(2)])
Alternatively, you can use
functools.partial() to substitute the other arguments.
In : import functools In : def up_by_x(ts, x): ....: ts.positions += np.array([0.0, 0.0, float(x)]) ....: return ts ....: In : up_by_5 = functools.partial(up_by_x, x=5) In : u = mda.Universe(TPR, XTC, transformations=[up_by_5])
On-the-fly transformation functions can be applied to any property of a Timestep, not just the atom positions. For example, to give each frame of a trajectory a box:
In : def set_box(ts): ....: ts.dimensions = [10, 20, 30, 90, 90, 90] ....: return ts ....: In : u = mda.Universe(TPR, XTC, transformations=[set_box])