molcrafts-molpack 0.1.1

Faithful Rust port of Packmol molecular packing (split off from molrs)
Documentation
# 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

PBC is declared on the `InsideBoxRestraint` via the `periodic` keyword:

```python
from molpack import InsideBoxRestraint

# Fully periodic orthorhombic cell
box = InsideBoxRestraint(
    [0.0, 0.0, 0.0],
    [30.0, 30.0, 30.0],
    periodic=(True, True, True),
)
```

`periodic` is a 3-tuple of booleans — one per axis. Only orthorhombic
cells are supported.

Per-axis PBC is possible — e.g. slab geometry with in-plane PBC and
open Z:

```python
slab = InsideBoxRestraint(
    [0.0, 0.0, 0.0],
    [30.0, 30.0, 100.0],
    periodic=(True, True, False),
)
```

### Declaring PBC on the packer

For a **fully-periodic** cell you can skip the restraint and declare the
box directly on the packer (Packmol's `pbc` keyword):

```python
from molpack import Molpack

packer = Molpack().with_periodic_box([0.0, 0.0, 0.0], [30.0, 30.0, 30.0])
```

This is equivalent to a single `InsideBoxRestraint(..., periodic=(True,
True, True))` broadcast over the system. Use the restraint form when you
need per-axis control or a soft confining region; use `with_periodic_box`
when the cell is simply fully periodic.

## 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.

Restraints themselves (`InsideBoxRestraint`, `InsideSphereRestraint`,
…) are evaluated in the **unwrapped** frame — they describe the
geometric region as defined, regardless of the periodic cell.

## System-wide PBC derivation

At `pack()` time the packer scans every restraint on every target for
a declared periodic box. The rules are:

- **Zero declarations** — non-periodic run.
- **One declaration** — its bounds define the system PBC.
- **Multiple declarations** — they must all agree (same bounds, same
  per-axis flags). Any mismatch raises `ConflictingPeriodicBoxesError`.

## Errors

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

```python
from molpack import InvalidPBCBoxError

try:
    packer.pack(...)
except InvalidPBCBoxError as e:
    ...
```

Both typed errors inherit from `molpack.PackError` (which itself is
a `RuntimeError` subclass) — a blanket `except PackError` catches any
packing failure.

## Choosing a box

A common pattern: pack into a single periodic `InsideBoxRestraint`
matching the desired cell. The restraint confines atoms softly and
simultaneously declares the PBC:

```python
cell_min = [0.0, 0.0, 0.0]
cell_max = [30.0, 30.0, 30.0]

box = InsideBoxRestraint(cell_min, cell_max, periodic=(True, True, True))
target = target.with_restraint(box)

result = Molpack().with_seed(42).pack([target], max_loops=200)
```

Or broadcast it globally via `Molpack.with_global_restraint(box)` when
several species share the same cell.