Trajectory Module#

The Trajectory class provides lazy, memory-efficient reading of multi-frame LAMMPS dump files. It only reads frame data when you access a specific timestep.

Key features:

  • Lazy reading: only the requested frame is loaded into memory

  • Slicing: traj[start:stop] creates sub-trajectories without copying data

  • Concatenation: t1 + t2 joins timeslices

  • ASE integration: timeslice.to_atoms() converts to list of ASE Atoms

from pyscal import Trajectory

Loading a Trajectory#

Create a Trajectory object by passing the path to a LAMMPS dump file.

traj = Trajectory("traj.light")
print(f"Trajectory: {traj.nblocks} frames, {traj.natoms} atoms per frame")
print(repr(traj))
Trajectory: 10 frames, 500 atoms per frame
Trajectory of 10 slices with 500 atoms

Accessing Frames#

Access individual frames by index. Each index returns a Timeslice object. Call .to_atoms() to get a list of ASE Atoms objects.

# Access first frame
ts = traj[0]
print(repr(ts))

# Convert to ASE Atoms (returns a list, one per frame in the slice)
atoms_list = ts.to_atoms()
atoms = atoms_list[0]
print(f"ASE Atoms: {len(atoms)} atoms, cell = {atoms.cell.lengths()}")
Trajectory slice
 0-0
 natoms=500

ASE Atoms: 500 atoms, cell = [18.21922 18.22509 18.36899]

Computing Descriptors on Trajectory Frames#

import pyscal

# Compute CNA for each frame
for i in range(traj.nblocks):
    ts = traj[i]
    atoms_list = ts.to_atoms()
    atoms = atoms_list[0]
    cna = pyscal.common_neighbor_analysis(atoms)
    print(f"Frame {i}: CNA = {cna}")
Frame 0: CNA = {'others': 499, 'fcc': 0, 'hcp': 1, 'bcc': 0, 'ico': 0}
Frame 1: CNA = {'others': 500, 'fcc': 0, 'hcp': 0, 'bcc': 0, 'ico': 0}
Frame 2: CNA = {'others': 498, 'fcc': 0, 'hcp': 1, 'bcc': 1, 'ico': 0}
Frame 3: CNA = {'others': 499, 'fcc': 1, 'hcp': 0, 'bcc': 0, 'ico': 0}
Frame 4: CNA = {'others': 494, 'fcc': 1, 'hcp': 3, 'bcc': 2, 'ico': 0}
Frame 5: CNA = {'others': 496, 'fcc': 0, 'hcp': 3, 'bcc': 1, 'ico': 0}
Frame 6: CNA = {'others': 496, 'fcc': 0, 'hcp': 3, 'bcc': 0, 'ico': 1}
Frame 7: CNA = {'others': 498, 'fcc': 1, 'hcp': 1, 'bcc': 0, 'ico': 0}
Frame 8: CNA = {'others': 498, 'fcc': 0, 'hcp': 1, 'bcc': 1, 'ico': 0}
Frame 9: CNA = {'others': 498, 'fcc': 0, 'hcp': 2, 'bcc': 0, 'ico': 0}

Slicing Trajectories#

# Take a subset of frames
sub = traj[:2]
atoms_list = sub.to_atoms()
print(f"Sub-trajectory: {len(atoms_list)} frames")
Sub-trajectory: 2 frames

Writing Frames#

You can write individual frames back to LAMMPS dump format.

import tempfile, os

# Write first frame to a temporary file
ts = traj[0]
tmpfile = os.path.join(tempfile.gettempdir(), "test_frame.dump")
ts.to_file(tmpfile)

# Verify the file was written
size = os.path.getsize(tmpfile)
print(f"Wrote frame to {tmpfile} ({size} bytes)")
os.remove(tmpfile)
Wrote frame to /tmp/test_frame.dump (32069 bytes)

Concatenating Timeslices#

# Join two sub-trajectories
t1 = traj[:1]
t2 = traj[1:]
combined = t1 + t2
atoms_list = combined.to_atoms()
print(f"Combined: {len(atoms_list)} frames")
Combined: 10 frames