Skip to content

Packer

Molpack drives the GENCAN-based three-phase optimizer. All tuning is through with_* builder methods — the constructor takes no arguments.

Constructor

from molpack import Molpack

packer = Molpack()

All defaults match Packmol's reference behaviour. Override any of them via the builders below.

Builder methods

Every builder returns a new Molpack:

packer = (
    Molpack()
    .with_tolerance(2.0)            # minimum allowed pairwise distance (Å)
    .with_precision(0.01)           # convergence threshold on fdist and frest
    .with_inner_iterations(20)      # GENCAN inner-loop cap (Packmol `maxit`)
    .with_init_passes(0)            # initial compaction passes (Packmol `nloop0`; 0 = auto)
    .with_init_box_half_size(1000)  # hard bound on init placement (Packmol `sidemax`)
    .with_perturb_fraction(0.05)    # fraction of atoms re-sampled per stall
    .with_random_perturb(False)     # randomize perturbation target selection
    .with_perturb(True)             # enable the stall-perturbation heuristic
    .with_seed(42)                  # deterministic RNG
    .with_parallel_eval(False)      # rayon-backed pair-kernel eval (opt-in)
    .with_lammps_output(True)       # attach LAMMPS-style screen output
    .with_log_level("progress")     # quiet | summary | progress | verbose
    .with_log_frequency(1)          # print every N outer steps
)

Use .with_lammps_output(False) or .with_log_level("quiet") to run silently (the default). .with_progress() is kept as a compatibility alias for enabling/disabling progress output.

A few more builders cover specific needs:

packer = (
    packer
    .with_periodic_box([0, 0, 0], [30, 30, 30])  # fully-periodic cell (Packmol `pbc`)
    .with_avoid_overlap(True)                     # reject init placements onto a fixed molecule
    .with_xyz_output("traj.xyz", every=5)         # record the packing trajectory
)

Global restraints

Attach a single restraint to every target in a pack:

packer = packer.with_global_restraint(
    InsideBoxRestraint([0, 0, 0], [40, 40, 40])
)

Semantically equivalent to calling .with_restraint(r) on every target.

Handlers

Attach any object implementing some subset of on_start(ntotat, ntotmol), on_step(info) -> bool | None, on_finish():

class MyHandler:
    def on_step(self, info):
        print(f"phase={info.phase} loop={info.loop_idx} fdist={info.fdist:.3f}")
        return None  # or True to request early stop

packer = packer.with_handler(MyHandler())

Returning True from on_step halts the run at the next check. See the Handler Protocol in molpack.

Periodic boundaries

PBC can be declared per-axis on an InsideBoxRestraint, or as a fully-periodic cell directly on the packer via .with_periodic_box(min, max). See Periodic boundaries.

Running

frame = packer.pack(targets, max_loops=200)
  • targets — list of Target objects (must be non-empty).
  • max_loops — per-phase outer-iteration budget.

Raises one of the typed PackError subclasses on failure (NoTargetsError, InvalidPBCBoxError, ConflictingPeriodicBoxesError, …).

pack() returns a molrs.Frame. To retrieve structured diagnostics, use pack_with_report():

result = packer.pack_with_report(targets, max_loops=200)

PackResult

result.positions   # (N, 3) float64 ndarray — packed coordinates
result.frame       # molrs.Frame — topology-complete packed frame
result.elements    # list[str]  — one entry per atom
result.natoms      # int
result.converged   # bool — True iff both fdist and frest < precision
result.fdist       # float — final distance-violation sum
result.frest       # float — final restraint-violation sum

Inspect convergence:

if not result.converged:
    print(f"not converged: fdist={result.fdist:.4f} frest={result.frest:.4f}")

PackResult.frame is the same Frame returned by pack(). Pass it to a writer of your choice (e.g. molrs.write_pdb). molpack does not provide writers.

Reproducibility

Packing is deterministic for a given (targets, tolerance, precision, seed) tuple. Capture the builder chain and max_loops to reproduce a result later.