Trajectory¶
A Trajectory is an ordered sequence of Frame objects representing a system over time.
This page explains when to use a Trajectory, how it handles data, and the common operations you will use in analysis pipelines.
Why use a Trajectory:
Memory: large trajectories can be processed without loading all frames into memory at once., Laziness: frames may be generated or loaded on demand, generators, files, or remote sources., and Composability: operations like map and slicing return new trajectory views suitable for pipelined analysis..
Notes: many core components in molpy interoperate with mp.Trajectory — prefer constructing and passing mp.Trajectory objects when working with analysis routines.
1. Creating a Trajectory¶
You can create a trajectory from an in-memory list of Frame objects or from a generator that yields Frame objects. Below are concise examples that use mp.Trajectory, preferred API.
Best practice: when possible pass an mp.Trajectory to core functions rather than raw lists — this keeps downstream code lazy and memory-efficient.
import molpy as mp
# From an in-memory list (concise)
frames = []
for i in range(5):
f = mp.Frame()
f['atoms'] = mp.Block({'x': [i], 'y': [0], 'z': [0]})
f.metadata['time'] = i * 10.0
frames.append(f)
traj_list = mp.Trajectory(frames)
# Trajectory built from a list supports fast length access
traj_list.has_length(), len(traj_list)
(True, 5)
2. Generator-based Trajectory¶
For large or streaming data, pass a generator that yields Frame objects. The trajectory will produce frames on demand and may not report a length until fully materialized.
Important: generators are consumed when iterated. If you need to read the same frames multiple times, consider materializing a small subset or creating a replayable source.
from itertools import islice
def frame_generator():
for i in range(1000):
f = mp.Frame()
f['atoms'] = mp.Block({'x': [i*2], 'y': [0], 'z': [0]})
f.metadata['time'] = i * 0.5
yield f
traj_gen = mp.Trajectory(frame_generator())
# Generator-based trajectory may not report length
traj_gen.has_length(),
# read the first two frames (this consumes them)
[f.metadata['time'] for f in islice(traj_gen, 2)]
[0.0, 0.5]
3. Analysis with map¶
Use map to apply a transformation function lazily to each frame. The returned trajectory applies the function on access; the original trajectory is not modified until frames are requested.
Keep mapping functions simple and deterministic — they run per-frame during iteration or indexing and should be fast. Avoid heavy side-effects in map functions.
def shift_x(frame):
# simple, per-frame transform: shift x by +10
new = mp.Frame()
new['atoms'] = mp.Block({'x': [x + 10.0 for x in frame['atoms']['x']], 'y': frame['atoms']['y'], 'z': frame['atoms']['z']})
new.metadata = frame.metadata.copy()
return new
shifted_traj = traj_list.map(shift_x)
# Convert to list for indexing (map returns generator)
shifted_list = list(shifted_traj)
shifted_list[0]['atoms']['x'], traj_list[0]['atoms']['x']
(array([10.]), array([0]))
4. Slicing and Indexing¶
Trajectories support standard Python indexing and slicing. Indexing returns a Frame; slicing returns a Trajectory view, sub-trajectory when possible.
Slicing with a stride, e.g. traj[::n] is a convenient way to down-sample a trajectory for quick inspection or plotting.
subset = traj_list[:2]
strided = traj_list[::2]
# results: type and length of sub-trajectory, and sampled x coordinates
[type(subset).__name__, len(subset), type(strided).__name__, len(strided), [f['atoms']['x'] for f in strided]]
['Trajectory', 2, 'Trajectory', 3, [array([0]), array([2]), array([4])]]
Summary¶
A Trajectory represents a sequence of Frame objects and is designed to support lazy, memory-efficient workflows. A trajectory can be constructed from any iterable source, such as a list, a generator, or another object that yields Frame instances. Because the underlying source may be lazy, a trajectory does not always have a known length; traj.has_length() can be used to check whether len(traj) is available without forcing materialization.
Transformations over trajectories are expressed through traj.map(func), which lazily applies a function to each frame and returns a new trajectory. This allows analysis steps to be composed without immediately loading or copying data. Individual frames can be accessed with indexing, where traj[n] returns the n-th Frame, evaluating it only when needed. Slicing is also supported: traj[a:b] returns a sub-trajectory, which may be a lightweight view or a materialized slice depending on the nature of the original source.
These primitives are intended to be combined to build concise analysis pipelines that scale to large trajectories. By keeping evaluation lazy and interoperable with the rest of the MolPy core components, Trajectory enables efficient post-processing without forcing unnecessary memory usage or eager loading.