Skip to content

Data Model

molrs uses a table-oriented molecular model. A Frame is the unit that moves through I/O, analysis, and visualization. Inside a frame, each named Block holds columns of equal row count. The conventional atoms block stores one row per atom, while optional blocks such as bonds, angles, and dihedrals describe topology.

This structure is intentionally less rigid than a fixed molecule class. A PDB reader can attach atom names and residue identifiers without forcing every simulation pipeline to carry them. A trajectory analysis can add derived columns without rewriting the topology. The contract is local: columns in the same block agree on row count, and code that needs specific columns checks for them at the subsystem boundary.

A frame holds named blocks of columns

Frame is a dictionary-like container. Block is a column store backed by typed arrays. In Python those columns are NumPy arrays; in WASM they appear as typed arrays or memory-backed views; in Rust they are ndarray-backed columns. The same column names carry across bindings. Coordinates use x, y, and z columns on the atoms block.

The scientific precision contract is simple: floating-point data is f64. Older feature flags that suggested switching the workspace to f32 are now deprecated no-ops. This matters for interop because external packages often default to single precision for visualization, while molrs algorithms assume double precision for geometry and force-field work.

A box adds periodicity to a frame

A simulation box is attached to a frame when periodic boundary conditions matter. The box stores the lattice matrix, origin, and per-axis periodic flags. Neighbor search, wrapping, distance calculations, and RDF normalization read the same box metadata, so changing it changes the physical interpretation of the coordinates.

Frames without a simulation box are still valid. Several analysis paths can fall back to a free-boundary box inferred from coordinates and padding. That is useful for isolated molecules and point clouds, but periodic systems should carry an explicit box to avoid accidental finite-volume assumptions.

Worked Example: Build a Frame by Hand

This example creates a water molecule as a coordinate table. It does not create a full molecular graph; it creates the data shape that readers, writers, and analysis routines expect.

import numpy as np
import molrs

atoms = molrs.Block()
atoms.insert("element", ["O", "H", "H"])
atoms.insert("x", np.array([0.0000, 0.9572, -0.2399], dtype=np.float64))
atoms.insert("y", np.array([0.0000, 0.0000,  0.9266], dtype=np.float64))
atoms.insert("z", np.array([0.0000, 0.0000,  0.0000], dtype=np.float64))
atoms.insert("mass", np.array([15.999, 1.008, 1.008], dtype=np.float64))

frame = molrs.Frame()
frame["atoms"] = atoms
frame.simbox = molrs.Box.cube(30.0)
frame.meta = {"name": "water", "source": "docs"}

frame.validate()
print(frame.keys())
print(frame["atoms"].nrows)
print(frame.simbox.lengths())

Expected output shape:

['atoms']
3
[30. 30. 30.]

The coordinate columns are separate because many file formats and simulation engines store them that way. If another library wants an N x 3 matrix, construct it at the boundary:

xyz = np.column_stack(
    [atoms.view("x"), atoms.view("y"), atoms.view("z")]
).astype(np.float64, copy=False)
print(xyz.shape)  # (3, 3)

Add a Topology Block

Topology in a frame is just another block. Bond indices are zero-based because they refer to row positions in the atoms block.

bonds = molrs.Block()
bonds.insert("i", np.array([0, 0], dtype=np.uint32))
bonds.insert("j", np.array([1, 2], dtype=np.uint32))
bonds.insert("order", np.array([1.0, 1.0], dtype=np.float64))
frame["bonds"] = bonds

print(frame.keys())
print(frame["bonds"].view("i"), frame["bonds"].view("j"))

This representation is deliberately plain. A force-field typifier can inspect the graph-level topology, while writers can serialize frame-level topology without needing to understand every chemistry operation that created it.

Common Mistakes

The most common shape error is mixing column lengths in one block. Block allows a two-dimensional column such as a (N, 3) position matrix, but its leading dimension must still be N, the row count shared with the other columns.

Another common mistake is using float32 because a visualization library returns it. molrs accepts some numeric inputs flexibly, but scientific algorithms are written around double precision. Convert to np.float64 before storing coordinates used for geometry, force fields, or analysis.