Metadata-Version: 2.4
Name: lammps-prep
Version: 0.2.8
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Science/Research
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Rust
Classifier: Topic :: Scientific/Engineering :: Chemistry
Requires-Dist: packmol>=21.2.3
Requires-Dist: numpy>=2.0.0
Requires-Dist: raspalib>=3.1.0
Requires-Dist: pytest>=9.1 ; extra == 'dev'
Requires-Dist: pytest-cov>=7.1 ; extra == 'dev'
Provides-Extra: dev
License-File: LICENSE
Summary: Prepare LAMMPS simulation input files from RASPA3-style JSON force fields
Author-email: "V. Jelle Lagerweij" <jelle.lagerweij@tudelft.nl>
License: MIT
Requires-Python: >=3.10
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM

# lammps-prep

[![Crates.io](https://img.shields.io/crates/v/lammps_prep.svg)](https://crates.io/crates/lammps_prep)
[![PyPI](https://img.shields.io/pypi/v/lammps-prep.svg)](https://pypi.org/project/lammps-prep/)
[![GitHub Actions](https://github.com/JelleLagerweij/ff_creation/actions/workflows/rust-ci.yml/badge.svg)](https://github.com/JelleLagerweij/ff_creation/actions/workflows/rust-ci.yml)

Prepare LAMMPS simulation input files from RASPA3-style JSON force fields.

Given a `force_field.json` (pseudo-atom masses, charges, and LJ parameters)
plus per-species `<name>.json` molecule files, `lammps_prep` packs molecules at
a target mass density and writes two files:

- **`data.lmp`** — LAMMPS data file (box dimensions, atom positions, bonds,
  angles, dihedrals)
- **`forcefield.lmp`** — LAMMPS settings file (masses, pair/bond/angle/dihedral
  coefficients, charge assignments)

## Installation

```bash
pip install lammps-prep          # pre-built wheel (Python + CLI binary bundled)
```

Or build from source:

```bash
pip install maturin
git clone https://github.com/JelleLagerweij/ff_creation.git
cd ff_creation
maturin develop
```

Packmol must be installed separately and available on `PATH` for the `pack packmol` backend.
The RASPA3 Python interface (`raspalib`) must be installed for the `pack raspa` backend:

```bash
pip install raspalib
```

---

## CLI reference

### `lammps_prep pack packmol` — random geometric packing

```
lammps_prep pack packmol [OPTIONS]

Options:
  --force-field <FORCE_FIELD_JSON>
          Path to force_field.json  [default: force_field.json]
  --species <MOLECULE_JSON>...
          Molecule definition file paths, one per species
          (order sets LAMMPS type numbering)
  --counts <COUNT>...
          Number of molecules per species (same order as --species;
          0 skips a species but keeps its atom/bond types in output)
  --density <DENSITY>
          Target mixture mass density, kg/m³
  --out-dir <DIR>
          Directory to write data.lmp and forcefield.lmp into  [default: .]
  --pair-style <PAIR_STYLE>
          Pair style preset: tip4p | lj_cut | lj_cut_coul_long  [default: lj_cut]
  --explicit-lb
          Write every Lorentz-Berthelot cross-pair explicitly instead of
          relying on `pair_modify mix arithmetic`
  --tolerance <TOLERANCE>
          Packmol packing tolerance in Å (min distance between atoms from
          different molecules)  [default: 2.0]
  -h, --help
          Print help
```

**Example:**

```bash
lammps_prep pack packmol \
  --species CO2.json heptane.json \
  --counts 200 50 \
  --density 700 \
  --out-dir build/
```

---

### `lammps_prep pack raspa` — NVT MC equilibration via RASPA3

```
lammps_prep pack raspa [OPTIONS]

Options:
  --force-field <FORCE_FIELD_JSON>
          Path to force_field.json  [default: force_field.json]
  --species <MOLECULE_JSON>...
          Molecule definition file paths, one per species
  --counts <COUNT>...
          Number of molecules per species
  --density <DENSITY>
          Target mixture mass density, kg/m³
  --out-dir <DIR>
          Directory to write data.lmp and forcefield.lmp into  [default: .]
  --pair-style <PAIR_STYLE>
          Pair style preset: tip4p | lj_cut | lj_cut_coul_long  [default: lj_cut]
  --explicit-lb
          Write every Lorentz-Berthelot cross-pair explicitly
  --temperature <TEMPERATURE>
          NVT simulation temperature in K  [default: 300.0]
  --init-cycles <INIT_CYCLES>
          Initialization cycles (random placement + overlap removal)  [default: 2000]
  --equil-cycles <EQUIL_CYCLES>
          Equilibration cycles (discarded)  [default: 2000]
  --prod-cycles <PROD_CYCLES>
          Production cycles; the final frame becomes the LAMMPS starting
          config  [default: 1000]
  --cutoff <CUTOFF>
          LJ and Coulomb cutoff radius in Å  [default: 12.0]
  -h, --help
          Print help
```

**Example:**

```bash
lammps_prep pack raspa \
  --species CO2.json heptane.json \
  --counts 200 50 \
  --density 700 \
  --temperature 300 \
  --out-dir build/
```

Requires `raspalib` and `python`/`python3` on `PATH`.

---

### `lammps_prep density` — mixture density estimation (stubs)

These subcommands are reserved for future releases.  Each currently exits with
"not yet implemented".

```
lammps_prep density pr           # Peng-Robinson EOS
lammps_prep density pc-saft      # PC-SAFT EOS
lammps_prep density epc-saft     # ePC-SAFT EOS (electrolyte)
lammps_prep density laliberte    # Laliberte volumetric correlation
```

---

## Python API

```python
from lammps_prep.pack import packmol, raspa
from lammps_prep import PairSetup

# --- Packmol packing ---
summary = packmol(
    species=["CO2.json", "heptane.json"],
    counts=[200, 50],
    density=700.0,
    out_dir="build",
    pair_style=PairSetup.LjCut,
)
print(summary)

# --- RASPA3 NVT equilibration ---
summary = raspa(
    species=["CO2.json", "heptane.json"],
    counts=[200, 50],
    density=700.0,
    out_dir="build",
    temperature=300.0,
    init_cycles=2000,
    equil_cycles=2000,
    prod_cycles=1000,
)
print(summary)
```

Density stubs raise `NotImplementedError`:

```python
from lammps_prep.density import pr
pr(...)  # NotImplementedError: Peng-Robinson EOS is not yet implemented.
```

---

## Force-field file format

`lammps_prep` reads **RASPA3-style JSON** files.

### `force_field.json`

```json
{
  "MixingRule": "Lorentz-Berthelot",
  "PseudoAtoms": [
    { "name": "O_h2o", "mass": 15.9994,  "charge": -1.1128 },
    { "name": "H_h2o", "mass":  1.00794, "charge":  0.5564 }
  ],
  "SelfInteractions": [
    { "name": "O_h2o", "type": "lennard-jones", "parameters": [78.0,  3.1589] },
    { "name": "H_h2o", "type": "lennard-jones", "parameters": [ 0.0,  0.0   ] }
  ],
  "BinaryInteractions": [
    { "names": ["O_h2o", "Na"], "type": "lennard-jones",
      "parameters": [140.043, 2.8904], "source": "MadridT2023" }
  ]
}
```

`parameters` for LJ are `[ε/k_B (K), σ (Å)]`.  
`BinaryInteractions` override the Lorentz-Berthelot mixing rule for a specific pair.

### `<species>.json`

```json
{
  "pseudoAtoms": [
    ["O_h2o", [0.000,  0.000, 0.0]],
    ["H_h2o", [0.757,  0.586, 0.0]],
    ["H_h2o", [-0.757, 0.586, 0.0]]
  ],
  "Connectivity": [[0,1],[0,2]],
  "Type": "rigid",
  "Bonds": [ [["O_h2o","H_h2o"], "HARMONIC", [450000.0, 0.9572]] ],
  "Bends": [ [["H_h2o","O_h2o","H_h2o"], "HARMONIC", [55000.0, 104.52]] ],
  "CriticalTemperature": 647.1,
  "CriticalPressure": 22064000.0,
  "AcentricFactor": 0.345
}
```

- `Bonds` / `Bends` are optional for `"Type": "rigid"` molecules (LAMMPS SHAKE/RIGID placeholders are written with a warning).
- `Torsions` follow the TRAPPE potential form: `[c0, c1, c2, c3]` in K.

---

## Project structure

```
lammps_prep/
├── Cargo.toml           # Workspace root
├── pyproject.toml       # Python package configuration (maturin)
├── README.md
├── rust/
│   ├── Cargo.toml       # Rust crate (name = "lammps_prep")
│   └── src/
│       ├── lib.rs       # Library exports
│       ├── main.rs      # CLI entry point  (bin = "lammps_prep")
│       ├── cli.rs       # Subcommand parsing and dispatch
│       ├── ff.rs        # Force-field data structures
│       ├── lammps.rs    # LAMMPS file writers
│       ├── packmol.rs   # Packmol interface
│       └── python.rs    # PyO3 bindings (module = "lammps_prep._native")
└── python/
    └── lammps_prep/
        ├── __init__.py   # Package init (exports PairSetup)
        ├── pack.py       # packmol() and raspa() functions
        ├── density.py    # Density stubs (all raise NotImplementedError)
        ├── raspa_init.py # raspalib NVT MC helper
        └── _cli.py       # Console script entry point
```

---

## Development

```bash
# Build Rust library, CLI binary, and Python extension in one step
maturin develop

# Run Rust tests
cd rust && cargo test

# Run Rust linter
cd rust && cargo clippy
```

