molcrafts-molpack 0.1.0

Faithful Rust port of Packmol molecular packing (split off from molrs)
Documentation
# Targets

A `Target` describes one type of molecule to pack: its template
geometry, element symbols, and the number of copies to produce.
VdW radii are resolved automatically from element symbols via the
Bondi (1964) table.

## Construction

```python
from molpack import Target

target = Target(frame, count)
```

- `frame` — a `molrs.Frame` or `molpy.Frame`, resolved zero-copy via its
  FFI capsule. Supported sources:

  | Source | Element column |
  |--------|---------------|
  | `molrs.read_pdb(path)` | `"symbol"` |
  | `molrs.read_xyz(path)` | `"element"` |
  | `molrs.Frame.from_dict({"blocks": {"atoms": {...}}})` | `"element"` |
  | `molpy.Frame` | `"element"` |

- `count` — number of copies to produce.

A display label is optional:

```python
target = Target(frame, count).with_name("water")
```

Build a frame in memory (no PDB file) with `molrs.Frame.from_dict`:

```python
import molrs
import numpy as np

frame = molrs.Frame.from_dict({
    "blocks": {
        "atoms": {
            "x": np.array([0.00,  0.96, -0.24]),
            "y": np.array([0.00,  0.00,  0.93]),
            "z": np.zeros(3),
            "element": ["O", "H", "H"],
        }
    }
})
water = Target(frame, count=100).with_name("water")
```

## Read-only properties

```python
target.name        # Optional[str]
target.natoms      # number of template atoms
target.count       # requested copies
target.elements    # list[str]
target.radii       # list[float]
target.is_fixed    # True if placement is frozen (see below)
```

All builder methods are **immutable** — they return a new `Target`.

## Centering

The default is [`CenteringMode.AUTO`](../api-reference.md#centeringmode) — free
targets are centered on their geometric center before packing; fixed
targets are kept in place. Override explicitly:

```python
from molpack import CenteringMode

target = target.with_centering(CenteringMode.CENTER)  # always center
target = target.with_centering(CenteringMode.OFF)     # keep input coords
```

## Fixed placement

Pin a target at a specific location (e.g. a fixed reference molecule):

```python
from molpack import Angle

target = target.fixed_at([10.0, 20.0, 30.0])

# optional Euler orientation — three Angle values in Packmol's
# eulerfixed convention
target = (
    target.fixed_at([10.0, 20.0, 30.0])
    .with_orientation((
        Angle.from_degrees(0.0),
        Angle.from_radians(1.57),
        Angle.ZERO,
    ))
)
```

Fixed targets are excluded from the optimizer but still contribute to
distance exclusion against other species.

## Rotation bounds

Restrict the rotational search window about each axis:

```python
from molpack import Angle, Axis

target = (
    target
    .with_rotation_bound(Axis.X, Angle.from_degrees(0.0),  Angle.from_degrees(15.0))
    .with_rotation_bound(Axis.Y, Angle.from_degrees(90.0), Angle.from_degrees(10.0))
    .with_rotation_bound(Axis.Z, Angle.from_degrees(0.0),  Angle.from_degrees(5.0))
)
```

`Angle` makes units explicit: use `Angle.from_degrees(...)` or
`Angle.from_radians(...)` — raw floats are rejected.

## Attaching restraints

### All atoms of the target

```python
from molpack import InsideBoxRestraint

target = target.with_restraint(
    InsideBoxRestraint([0, 0, 0], [40, 40, 40])
)
```

Stack multiple restraints by calling `.with_restraint()` again:

```python
target = (
    target
    .with_restraint(InsideBoxRestraint([0, 0, 0], [40, 40, 40]))
    .with_restraint(OutsideSphereRestraint([20, 20, 20], 5.0))
)
```

### A subset of atoms

```python
from molpack import AbovePlaneRestraint, BelowPlaneRestraint

target = target.with_atom_restraint(
    [30, 31],                                     # 0-based Rust-native indices
    BelowPlaneRestraint([0.0, 0.0, 1.0], 2.0),
)
```

!!! note "0-based indexing"
    `with_atom_restraint` uses **0-based** indices, matching Rust
    convention. If you are porting from a Packmol `.inp` file (which
    uses 1-based indices), subtract 1 at the call site.

## Relaxation-assisted packing

Attach an in-loop relaxer to reshape a flexible molecule's reference
geometry *during* packing — useful for long chains that must fold to
fit. `with_relaxer` requires `count == 1` (every copy shares the
reference geometry the relaxer rewrites):

```python
from molpack import Target, TorsionMcRelaxer

chain = TorsionMcRelaxer(frame).with_steps(20).with_self_avoidance(1.5)
target = Target(frame, count=1).with_relaxer(chain)
```

Two relaxers ship built in: `TorsionMcRelaxer` (engine-free Monte-Carlo
torsion sampling, always available) and `LBFGSRelaxer` (force-field
L-BFGS, `ff` feature). See
[In-loop relaxers](../api-reference.md#in-loop-relaxers) for their
options.

## Per-target solver budget

Override the maximum perturbation budget for this target:

```python
target = target.with_perturb_budget(50)  # default: derived from count
```

Useful when one species is significantly harder to place than the rest.