Getting Started¶
This chapter walks through the minimum you need to run a packing job and the most common customization points. Read the reference docs of individual types on the crate landing page for full details.
CLI¶
The molpack binary accepts the same .inp script format as the
original Packmol, making it a drop-in replacement for the command-line
workflow. See Install for setup.
Running a script¶
# File argument — paths inside the script resolve relative to its directory
molpack mixture.inp
# Stdin — compatible with the classic Packmol invocation
molpack < mixture.inp
cat mixture.inp | molpack
Script format¶
The .inp format is line-oriented. Lines starting with # are comments.
Global keywords come first; each molecule type occupies a
structure … end structure block.
# Global settings
tolerance 2.0 # minimum atom–atom distance in Å (default 2.0)
seed 42 # random seed for reproducibility (optional)
filetype pdb # input format for all structure files (optional)
output packed.pdb # output file; format inferred from extension
nloop 400 # max outer-loop iterations (default 400)
pbc 40.0 40.0 40.0 # optional: fully-periodic box [0,0,0]..[40,40,40]
# One block per molecule type
structure water.pdb
number 1000
inside box 0. 0. 0. 40. 40. 40.
end structure
structure urea.pdb
number 400
inside box 0. 0. 0. 40. 40. 40.
end structure
pbc keyword — declares a fully-periodic box (every axis wraps),
matching Packmol's pbc. Two forms are accepted:
pbc X Y Z—min = [0, 0, 0],max = [X, Y, Z]pbc X0 Y0 Z0 X1 Y1 Z1— explicitmin/max
When a script sets pbc, the packer's cell grid is built directly from
that box. Without either a pbc directive or an inside-style
restraint the packer falls back to inferring a box from the initial
random placement (half-width defaults to 1000 Å), which drives the cell
grid to tens of millions of cells — so always give the packer a
spatial constraint.
Unknown keywords are rejected. The parser returns a
ScriptError::UnknownKeyword rather than
silently dropping them — a silent pbc drop used to burn 40+ GB of
RAM before the packer even started.
Restraint keywords (all Packmol restraint types are supported):
| Keyword | Scope | Description |
|---|---|---|
inside box x0 y0 z0 x1 y1 z1 |
molecule / atoms | Axis-aligned box |
inside sphere cx cy cz r |
molecule / atoms | Sphere |
outside sphere cx cy cz r |
molecule / atoms | Exclusion sphere |
over plane nx ny nz d |
molecule / atoms | Half-space (above) |
below plane nx ny nz d |
molecule / atoms | Half-space (below) |
Per-atom-subset restraints use an atoms … end atoms sub-block:
structure palmitoil.pdb
number 10
inside box 0. 0. 0. 40. 40. 14.
atoms 31 32 # 1-based atom indices
below plane 0. 0. 1. 2.
end atoms
atoms 1 2
over plane 0. 0. 1. 12.
end atoms
end structure
Fixed-position placement:
structure protein.pdb
number 1
center
fixed 0. 0. 0. 0. 0. 0. # x y z euler_x euler_y euler_z
end structure
Extended format support¶
molpack extends Packmol's PDB/XYZ input support with the additional readers
from molrs-io. The filetype keyword or file extension selects the format:
| Format | Read | Write | Extension / filetype keyword |
|---|---|---|---|
| PDB | ✓ | ✓ | .pdb / pdb |
| XYZ | ✓ | ✓ | .xyz / xyz |
| SDF / MOL | ✓ | — | .sdf, .mol / sdf |
| LAMMPS dump | ✓ | ✓ | .lammpstrj / lammps_dump |
| LAMMPS data | ✓ | — | .data / lammps_data |
The output format is always inferred from the extension of the output path.
Running the canonical Packmol examples via CLI¶
The five canonical workloads each ship with a .inp file:
molpack examples/pack_mixture/mixture.inp
molpack examples/pack_bilayer/bilayer-comment.inp
molpack examples/pack_interface/interface.inp
molpack examples/pack_solvprotein/solvprotein.inp
molpack examples/pack_spherical/spherical-comment.inp
Rust library¶
Add the dependency (see Install for the full toolchain). Then write a packing job in code.
First pack: one molecule type in a box¶
use molpack::{InsideBoxRestraint, Molpack, Target};
let water_positions = [
[0.0, 0.0, 0.0],
[0.96, 0.0, 0.0],
[-0.24, 0.93, 0.0],
];
let water_radii = [1.52, 1.20, 1.20];
let target = Target::from_coords(&water_positions, &water_radii, 100)
.with_name("water")
.with_restraint(InsideBoxRestraint::new([0.0; 3], [40.0; 3], [false; 3]));
// Every tuning knob has a Packmol-matching default, so `new().pack(...)`
// is a complete call; `200` is the outer-loop budget.
let frame = Molpack::new().pack(&[target], 200)?;
let natoms = frame.get("atoms").and_then(|b| b.nrows()).unwrap_or(0);
println!("packed {natoms} atoms");
# Ok::<(), molpack::PackError>(())
Arguments to Molpack::pack:
&[Target]— one entry per molecule type.max_loops: usize— outer-iteration budget per phase.
Seed and every other tuning knob live on the builder (.with_seed(n),
.with_tolerance(t) etc.). pack() returns the packed, topology-complete
molrs::Frame — atom coordinates plus element and mol_id, with each
molecule's topology replayed from its target template. Use
.with_lammps_output(true), .with_log_level(level), and
.with_log_frequency(n) for screen summaries/progress. Advanced callers
that need structured convergence fields (fdist, frest, converged)
can call pack_with_report, whose
PackResult::frame is the same packed frame.
Restraint scopes¶
Restraints can be attached at three granularities. All of them use the
same AtomRestraint trait underneath.
Per-target, all atoms — the most common case:
# use molpack::{InsideBoxRestraint, Target};
# let (pos, rad) = (&[[0.0; 3]][..], &[1.0][..]);
let target = Target::from_coords(pos, rad, 100)
.with_restraint(InsideBoxRestraint::new([0.0; 3], [40.0; 3], [false; 3]));
Per-target, atom subset — restrict to specific atoms of every
molecule copy (0-based, Rust convention — if you are porting from a
Packmol .inp file, subtract 1):
# use molpack::{BelowPlaneRestraint, Target};
# let (pos, rad) = (&[[0.0; 3]][..], &[1.0][..]);
let target = Target::from_coords(pos, rad, 100)
.with_atom_restraint(&[0, 1], BelowPlaneRestraint::new([0.0, 0.0, 1.0], 5.0));
Global, all targets — broadcast. Semantically equivalent to
calling with_restraint on every target:
# use molpack::{InsideSphereRestraint, Molpack, Target};
# let (pos, rad) = (&[[0.0; 3]][..], &[1.0][..]);
# let t_a = Target::from_coords(pos, rad, 100);
# let t_b = Target::from_coords(pos, rad, 100);
let frame = Molpack::new()
.with_global_restraint(InsideSphereRestraint::new([20.0; 3], 30.0))
.pack(&[t_a, t_b], 200)?;
# Ok::<(), molpack::PackError>(())
The scope-equivalence law is formal: molpack.with_global_restraint(r)
is implemented as for t in targets { t.with_restraint(r.clone()) }.
There is no separate "global-restraint" storage path.
Screen output and handlers¶
Use the builder to enable LAMMPS-style screen output:
# use molpack::{InsideBoxRestraint, Molpack, MolpackLogLevel, Target};
# let (pos, rad) = (&[[0.0; 3]][..], &[1.0][..]);
# let target = Target::from_coords(pos, rad, 100).with_restraint(InsideBoxRestraint::new([0.0; 3], [40.0; 3], [false; 3]));
let mut packer = Molpack::new()
.with_log_level(MolpackLogLevel::Progress)
.with_log_frequency(10);
# let _ = packer.pack(&[target], 200);
Attach Handler implementations for trajectory output,
custom observation, or early-stop logic. Built-ins:
# use molpack::{EarlyStopHandler, InsideBoxRestraint, Molpack, Target, XYZHandler};
# let (pos, rad) = (&[[0.0; 3]][..], &[1.0][..]);
# let target = Target::from_coords(pos, rad, 100).with_restraint(InsideBoxRestraint::new([0.0; 3], [40.0; 3], [false; 3]));
let mut packer = Molpack::new()
.with_handler(XYZHandler::new("traj.xyz", /* every = */ 10))
.with_handler(EarlyStopHandler::new(/* threshold = */ 1e-4));
# let _ = packer.pack(&[target], 200);
Write your own — see the extending module.
Relaxers (in-loop conformation sampling)¶
Flexible molecules benefit from torsion-MC relaxation between outer
optimizer calls. Attach a Relaxer to a target:
# use molrs::system::atomistic::Atomistic;
# use molpack::{InsideSphereRestraint, Target, TorsionMcRelaxer};
# let (pos, rad) = (&[[0.0; 3]][..], &[1.0][..]);
# let graph = Atomistic::new();
let target = Target::from_coords(pos, rad, 1) // relaxers require count == 1
.with_restraint(InsideSphereRestraint::new([0.0; 3], 20.0))
.with_relaxer(
TorsionMcRelaxer::new(&graph)
.with_temperature(0.5)
.with_steps(20)
);
Free versus periodic boundary¶
Free boundary is the default. There are two ways to declare PBC:
On the packer (fully periodic box, equivalent to the script's
pbc keyword):
On an InsideBoxRestraint (per-axis control, e.g. for slab
geometries):
# use molpack::{InsideBoxRestraint, Target};
# let (pos, rad) = (&[[0.0; 3]][..], &[1.0][..]);
// Fully-periodic (Packmol-style 3D PBC) via a restraint:
let target = Target::from_coords(pos, rad, 100).with_restraint(
InsideBoxRestraint::new([0.0; 3], [30.0; 3], [true; 3]),
);
Slab geometries with only some axes wrapping (e.g. X/Y periodic slab, Z confined):
# use molpack::{InsideBoxRestraint, Target};
# let (pos, rad) = (&[[0.0; 3]][..], &[1.0][..]);
let target = Target::from_coords(pos, rad, 100).with_restraint(
InsideBoxRestraint::new([0.0; 3], [30.0; 3], [true, true, false]),
);
When a pack() call resolves PBC from both a packer-level
with_periodic_box and a restraint-level declaration, the two
must match exactly (bounds + per-axis flags) or
PackError::ConflictingPeriodicBoxes is returned.
Zero-length axes return PackError::InvalidPBCBox.
Running the canonical examples¶
Five Packmol-equivalent workloads are checked in under examples/:
cargo run -p molcrafts-molpack --release --features io --example pack_mixture
cargo run -p molcrafts-molpack --release --features io --example pack_bilayer
cargo run -p molcrafts-molpack --release --features io --example pack_interface
cargo run -p molcrafts-molpack --release --features io --example pack_solvprotein
cargo run -p molcrafts-molpack --release --features io --example pack_spherical
Set MOLRS_PACK_EXAMPLE_PROGRESS=1 to enable the built-in
ProgressHandler. Set MOLRS_PACK_EXAMPLE_XYZ=1 to dump a trajectory
under out/.
Python bindings¶
A PyO3 binding ships under python/:
import molrs
from molpack import InsideBoxRestraint, Molpack, Target
frame = molrs.read_pdb("water.pdb")
water = (
Target(frame, count=100)
.with_name("water")
.with_restraint(InsideBoxRestraint([0, 0, 0], [40, 40, 40]))
)
frame = Molpack().pack([water], max_loops=200)
The Python API mirrors the Rust builder surface one-for-one. The
binding is I/O-free — use
molcrafts-molrs (or any
Frame-compatible loader) to read templates, and write the returned Frame
back out with the same library. Use pack_with_report() if you need
converged, fdist, or frest as Python properties.
The Python binding documentation is the Python section of this site, with
sources under
docs/python/;
runnable examples are in
python/examples/.
Next steps¶
- Concepts — the abstractions in one place.
- Architecture — system design, data flow, loops, hot path.
- Extending — write your own
Restraint/Region/Handler/Relaxer.