Reading and writing of trajectories

Reading and writing with automatic filetype detection

The garnett.read() and garnett.write() functions will automatically determine the type of a trajectory from its file extension. This can be used to quickly load and save Trajectory objects.

import garnett
# Load a GSD file...
with garnett.read('dump.gsd') as traj:
    # ...do things with the trajectory, then output a GTAR file
    garnett.write(traj, 'output.tar')

Using reader and writer classes

Readers and writers are defined in the reader and writer modules. The following code uses the PosFileReader and PosFileWriter as an example.

from garnett.reader import PosFileReader
from garnett.writer import PosFileWriter

pos_reader = PosFileReader()
pos_writer = PosFileWriter()

with open('posfile.pos') as file:
    # Access the trajectory
    traj = pos_reader.read(file)

    # Write to standard out:

    # or directly to a file:
    with open('out.pos', 'w') as posfile:
        pos_writer.write(traj, posfile)

Data access

Indexing and slicing

Once you read a trajectory, access individual frames or sub-trajectories by indexing and slicing:

# Select individual frames:
first_frame = traj[0]
last_frame = traj[-1]
n_th_frame = traj[n]
# and so on

# Create a sub-trajectory from the ith frame
# to the (j-1)th frame:
sub_trajectory = traj[i:j]

# We can use advanced slicing techniques:
every_second_frame = traj[::2]
the_last_ten_frames = traj[-10::]

The actual trajectory data is then either accessed on a per trajectory or per frame basis.

Trajectory array access

The complete trajectory may be loaded into memory by calling the load_arrays() method. This will allow access to fields such as position, orientation, and velocity across all frames and particles. Supported properties are listed below:

traj.box             # M
traj.N               # M
traj.types           # MxT
traj.type_shapes     # MxT
traj.typeid          # MxN
traj.position        # MxNx3
traj.orientation     # MxNx4
traj.velocity        # MxNx3
traj.mass            # MxN
traj.charge          # MxN
traj.diameter        # MxN
traj.moment_inertia  # MxNx3
traj.angmom          # MxNx4
traj.image           # MxNx3

# M is the number of frames
# T is the number of particle types in a frame
# N is the number of particles in a frame

Individual frame access

Individual frames can be accessed via indexing a (sub-)trajectory object:

frame = traj[i]
frame.box              # garnett.trajectory.Box object
frame.N                # scalar, number of particles
frame.types            # T, string names for each type
frame.type_shapes      # T, list of shapes for each type
frame.typeid           # N, type indices of each particle
frame.position         # Nx3
frame.orientation      # Nx4
frame.velocity         # Nx3
frame.mass             # N
frame.charge           # N
frame.diameter         # N
frame.moment_inertia   # Nx3
frame.angmom           # Nx4
frame.image            # Nx3
frame.data             # Dictionary of lists for each attribute
frame.data_key         # List of strings

Iterating over trajectories

Iterating over trajectories is the most memory-efficient form of data access. Each frame will be loaded prior to access and unloaded post access, such that there is only one frame loaded into memory at the same time.

# Iterate over a trajectory directly for read-only data access
for frame in traj:

Efficient modification of trajectories

Use a combination of reading, writing, and iteration for memory-efficient modification of large trajectory data. This is an example on how to modify frames in-place:

import numpy as np
import garnett

def center(frame):
    frame.position -= np.average(frame.position, axis=0)
    return frame

with garnett.read('in.pos') as traj:
    traj_centered = Trajectory((center(frame) for frame in traj))
    garnett.write(traj_centered, 'out.pos')

Loading trajectories into memory

The Trajectory class is designed to be memory-efficient. This means that loading all trajectory data into memory requires an explicit call of the load() or load_arrays() methods.

# Make trajectory data accessible via arrays:

# Load all frames:
frame = traj[i]
traj.position    # load() also loads arrays


In general, loading all frames with load() is more expensive than just loading arrays with load_arrays(). Loading all frames also loads the arrays.

Sub-trajectories inherit already loaded data:

sub_traj = traj[i:j]


If you are only interested in sub-trajectory data, consider to call load() or load_arrays() only for the sub-trajectory.

Example use with HOOMD-blue

The garnett frames can be used to initialize HOOMD-blue simulations by creating snapshots or copying the frame data to existing snapshots with the to_hoomd_snapshot() method:

import garnett
import hoomd

with garnett.read('cube.pos') as traj:

    # Initialize from last frame
    snapshot = traj[-1].to_hoomd_snapshot()
    system = hoomd.init.read_snapshot(snapshot)

    # Restore last frame
    snapshot = system.take_snapshot()