Skip to content

Trajectory Analysis

Trajectory analysis reuses the frame model over time. A trajectory is a sequence of frames plus optional time or step arrays. Analyses such as RDF, MSD, clustering, centers of mass, inertia tensors, gyration tensors, PCA, and k-means all consume frames and return typed result objects.

The common pattern is to compute neighbor lists first, then pass those lists alongside frames to analyses that need distance connectivity. That keeps the cutoff and boundary model explicit. It also lets several analyses share the same neighbor search result instead of recomputing pair lists independently.

One call handles one frame or many

Python wrappers accept either one frame or a sequence of frames for several analyses. A single frame returns a single result where that is meaningful; a sequence returns results aligned with the input sequence. This mirrors how users work in notebooks while preserving the Rust-side distinction between a frame and a trajectory.

For periodic systems, attach the simulation box before computing neighbor lists or volume-normalized quantities. For isolated molecules or finite clusters, use the free-boundary behavior intentionally and document that choice near the analysis.

Worked Example: MSD Over Three Frames

This example constructs three frames with the same two particles moving along the x-axis. MSD uses frame 0 as the reference.

import numpy as np
import molrs

def make_frame(offset: float) -> molrs.Frame:
    atoms = molrs.Block()
    atoms.insert("element", ["Ar", "Ar"])
    atoms.insert("x", np.array([0.0 + offset, 1.0 + offset], dtype=np.float64))
    atoms.insert("y", np.array([0.0, 0.0], dtype=np.float64))
    atoms.insert("z", np.array([0.0, 0.0], dtype=np.float64))

    frame = molrs.Frame()
    frame["atoms"] = atoms
    frame.simbox = molrs.Box.cube(20.0)
    return frame

frames = [make_frame(0.0), make_frame(0.1), make_frame(0.2)]

from molrs.compute.msd import MSD
msd = MSD()
series = msd.compute(frames)

print("points:", len(series))
print("mean:", series.mean)
print("first frame mean:", series[0].mean)

The result is a time series aligned with the input frames. If you pass a single frame, the wrapper still follows the same analysis semantics, but most trajectory metrics are more meaningful on sequences.

Worked Example: Cluster Analysis

Clustering consumes a neighbor list. In this toy system, two close pairs form two clusters when the cutoff connects only local neighbors.

points = np.array(
    [
        [0.0, 0.0, 0.0],
        [0.5, 0.0, 0.0],
        [5.0, 0.0, 0.0],
        [5.5, 0.0, 0.0],
    ],
    dtype=np.float64,
)

atoms = molrs.Block()
atoms.insert("element", ["C", "C", "C", "C"])
atoms.insert("x", points[:, 0])
atoms.insert("y", points[:, 1])
atoms.insert("z", points[:, 2])

frame = molrs.Frame()
frame["atoms"] = atoms
frame.simbox = molrs.Box.cube(20.0)

nq = molrs.NeighborQuery(frame.simbox, points, cutoff=1.0)
nlist = nq.query_self()

from molrs.compute.cluster import Cluster, ClusterCenters
clusters = Cluster(min_cluster_size=1).compute(frame, nlist)
centers = ClusterCenters().compute(frame, clusters)

print("clusters:", clusters.num_clusters)
print("sizes:", clusters.cluster_sizes)
print("centers:", centers.centers.reshape(clusters.num_clusters, 3))

The neighbor list defines connectivity. Change the cutoff and you change the graph that clustering sees.

Transport & Electrolyte Analysis

Three further kernels target electrolyte transport properties — Onsager coefficients (transport.Onsager), Green–Kubo conductivity from the current ACF (composed from GreenKuboConductivity + RunningIntegral), and pair persistence / residence times (transport.Persist). Like the dielectric kernels they are array-based free functions: the caller assembles the per-frame collective quantities and the Rust layer does the windowed correlation. See the dedicated Transport Kernels reference for signatures, units, equations, and examples.

Choosing Batch Shape

Use one frame when you are checking a single snapshot. Use a sequence of frames when the quantity is explicitly temporal or when you want one result per snapshot. Keep arrays aligned: if frames has length N, a parallel list of neighbor lists should also have length N.

For reproducible notebooks, print the pair counts or cluster sizes before plotting. Small diagnostic prints make it obvious when a missing box or cutoff change altered the analysis.