Skip to content

Periodic boundaries

By default the packer works under free boundary conditions — atoms are not wrapped and the only geometric limits come from the restraints you attach. Use periodic boundaries (PBC) when packing for MD input.

Enabling PBC

The periodic box is declared on the engine (Packmol's pbc keyword). with_periodic_box is a shared builder, so it reads the same on GencanPack, CbmcGrow and LatticeGrow:

from molpack import GencanPack

packer = GencanPack().with_periodic_box([0.0, 0.0, 0.0], [30.0, 30.0, 30.0])

This builder declares an orthorhombic, fully periodic cell. A triclinic cell is the .inp keyword cell a b c alpha beta gamma, or Rust PackEngine::with_cell / with_cell_matrix. The Python binding has no with_cell.

A region does not declare periodicity — it only confines. To keep every atom centre inside the cell as well, attach a molrs.core.Cuboid with the same bounds (or broadcast it with with_global_restraint):

import molrs

cell = molrs.core.Cuboid([0.0, 0.0, 0.0], [30.0, 30.0, 30.0])   # origin, lengths
target = target.with_restraint(cell)

Semantics

Under PBC, the pairwise distance evaluator applies minimum-image wrapping on the periodic axes, so atoms near opposite faces of the cell "see" each other through the periodic images. The tolerance setting still applies and is checked against the wrapped distance.

Regions are evaluated in the unwrapped frame — they describe the solid as defined, regardless of the periodic cell. The one exception is molrs.core.SphereUnion built with a box: its spheres are minimum-image on the box's periodic axes, so a void computed from beads in a periodic frame is periodic too.

Errors

A zero-length axis on a periodic box, or max < min on any axis, raises InvalidPbcBoxError at run() time:

from molpack import InvalidPbcBoxError

try:
    packer.run(targets, max_loops=200)
except InvalidPbcBoxError as e:
    ...

The periodic box is declared in exactly one place — on the engine — so there is no second declaration to conflict with it. Typed errors inherit from molpack.PackError (which itself is a RuntimeError subclass), so a blanket except PackError catches any packing failure.

Choosing a box

A common pattern: one periodic cell on the engine, and the same cuboid on every target that must stay inside it:

cell_min = [0.0, 0.0, 0.0]
cell_len = [30.0, 30.0, 30.0]

box = molrs.core.Cuboid(cell_min, cell_len)
target = target.with_restraint(box)

result = (
    GencanPack()
    .with_seed(42)
    .with_periodic_box(cell_min, [a + b for a, b in zip(cell_min, cell_len)])
    .run([target], max_loops=200)
)

Or broadcast the cuboid globally via GencanPack.with_global_restraint(box) when several species share the same cell.