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 dataConcatenation:
t1 + t2joins timeslicesASE integration:
timeslice.to_atoms()converts to list of ASEAtoms
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