Metadata-Version: 2.4
Name: mofchecker-next
Version: 0.1.2
Classifier: License :: OSI Approved :: GNU General Public License v2 (GPLv2)
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Rust
Classifier: Topic :: Scientific/Engineering :: Chemistry
Requires-Dist: numpy>=1.26
Requires-Dist: structuregraph-helpers>=0.0.9
Requires-Dist: element-coder>=0.0.8
Requires-Dist: libconeangle>=0.1.2
Requires-Dist: rustworkx>=0.14
Requires-Dist: pymatgen>=2024.8 ; extra == 'parity'
Requires-Dist: pytest>=8 ; extra == 'test'
Requires-Dist: maturin>=1.7,<2 ; extra == 'test'
Provides-Extra: parity
Provides-Extra: test
License-File: LICENSE
Summary: Incremental Rust/Python geometry kernels for MOF checking
Author: Hendrik Kraß
License: GPL-2.0-only
Requires-Python: >=3.10
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
Project-URL: Repository, https://github.com/henk789/mofchecker-next

<h1 align="center">
  mofchecker-next
</h1>

<p align="center">
  <b>A fast, drop-in replacement for <a href="https://github.com/lamalab-org/mofchecker">MOFChecker</a> 2.0</b> — same diagnostics, same API, <b>~12× faster</b> on the geometric diagnostics (~7× with open-metal-site detection), built on Rust kernels and rustworkx graph algorithms.
</p>

<p align="center">
    <a href="https://github.com/henk789/mofchecker-next/actions/workflows/release.yml">
        <img alt="CI" src="https://github.com/henk789/mofchecker-next/actions/workflows/release.yml/badge.svg" />
    </a>
    <a href="https://pypi.org/project/mofchecker-next/">
        <img alt="PyPI" src="https://img.shields.io/pypi/v/mofchecker-next" />
    </a>
    <a href="https://pypi.org/project/mofchecker-next/">
        <img alt="Python versions" src="https://img.shields.io/pypi/pyversions/mofchecker-next" />
    </a>
    <a href="https://github.com/henk789/mofchecker-next/blob/main/py/mofchecker_next/eqeq/LICENSE">
        <img alt="License" src="https://img.shields.io/pypi/l/mofchecker-next" />
    </a>
    <img alt="Speedup" src="https://img.shields.io/badge/vs%20MOFChecker%202.0-~12%C3%97%20faster-brightgreen" />
    <img alt="Built with Rust" src="https://img.shields.io/badge/built%20with-Rust%20%2B%20PyO3-orange" />
</p>

Designed for the workloads where the original is painful: **validating thousands of model-generated MOFs** (e.g. from a diffusion model), where the slow paths — floating-solvent extraction and dimensionality — dominate.

## 💪 Getting started

```python
from mofchecker_next import MOFChecker

mc = MOFChecker.from_cif("structure.cif")     # also .from_ase(atoms) / MOFChecker(structure)
mc.has_atomic_overlaps, mc.has_lone_molecule, mc.has_oms, mc.metal_number
descriptors = mc.get_mof_descriptors()        # OrderedDict of every diagnostic
```

Validating many structures? `mofchecker_next.batch` parallelizes across structures, builds each graph once, and never aborts the run on a single bad structure:

```python
from mofchecker_next.batch import check_structures

# inputs may be CIF paths, pymatgen Structures, ASE Atoms, or a mix
results = check_structures(inputs, n_workers=16)          # all CPUs by default
bad = [r for r in results if r["has_atomic_overlaps"]]

# subset to skip work: composition-only descriptors skip graph construction entirely
fast = check_structures(inputs, descriptors=["has_atomic_overlaps", "has_overcoordinated_c"])
```

Each result is a dict with `index`, `id`, `n_atoms`, and the requested descriptors. `DEFAULT_DESCRIPTORS` is the in-scope diagnostic suite (including bit-exact `has_high_charges`); `ALL_DESCRIPTORS` adds metadata, symmetry, and graph hashes. A structure that fails gets an `error` field (`on_error="record"`) instead of aborting the batch.

## 🚀 Installation

```bash
pip install mofchecker-next
```

Latest from source (needs a Rust toolchain):

```bash
pip install git+https://github.com/henk789/mofchecker-next.git
```

## ✨ Why use it

- ⚡ **Fast.** On 150-atom QMOF MOFs, the geometric diagnostic set runs in **~101 ms/structure** single-core vs **~1.20 s** for MOFChecker 2.0 (**~12×**); **~77 structures/s** across 10 cores. The win is the floating-solvent and 3D-connectivity paths, ported off networkx onto rustworkx; the numeric kernels (distances, contacts, connected components, OMS Voronoi/order-parameters, EQeq) are Rust.
- 🔌 **Drop-in.** `MOFChecker`-compatible class — same properties, same `get_mof_descriptors()`. Switch the import and existing code keeps working.
- ✅ **Parity-verified.** 100% agreement with MOFChecker 2.0 on real QMOFs (4500/4500 descriptor-comparisons over 250 structures × 18 descriptors; 16/16 on the reference test CIFs). See **Parity** below.
- 📦 **Built for batches.** Parallel `check_structures`, graph built once per structure, failures isolated.
- 🔋 **Bit-exact charges.** `has_high_charges` is a faithful Rust port of EQeq (bit-exact equilibrated charges).
- 🔁 **Reproducible.** `symmetry_hash` is deterministic (the reference's depends on Python hash randomization).

## ⚡ Performance

30 QMOF relaxed 150-atom MOFs, cold batch (each structure processed once), MOFChecker 2.0 vs mofchecker-next on identical inputs, dedicated compute node, 1 vs 10 cores. Reproduce with `scripts/benchmark_throughput.py` (the structure set is pinned + fingerprinted so before/after runs are comparable).

**Geometric diagnostic set** (the structural/graph checks; excludes open-metal-site detection):

| | per structure | throughput | speedup |
|---|---:|---:|---:|
| MOFChecker 2.0 — 1 core | 1201 ms | 0.8 /s | 1× |
| **mofchecker-next — 1 core** | **101 ms** | **9.9 /s** | **11.9×** |
| MOFChecker 2.0 — 10 cores | 162 ms | 6.2 /s | 1× |
| **mofchecker-next — 10 cores** | **13.1 ms** | **76.6 /s** | **12.4×** |

**Full geometric suite** (includes `has_oms` open-metal-site order parameters):

| | per structure | throughput | speedup |
|---|---:|---:|---:|
| MOFChecker 2.0 — 1 core | 1535 ms | 0.7 /s | 1× |
| **mofchecker-next — 1 core** | **140 ms** | **7.1 /s** | **10.9×** |
| MOFChecker 2.0 — 10 cores | 197 ms | 5.1 /s | 1× |
| **mofchecker-next — 10 cores** | **20.0 ms** | **49.9 /s** | **9.8×** |

Where the speedup comes from — it is concentrated in one check. MOFChecker's floating-solvent detection (`has_lone_molecule`) builds a 3×3×3 supercell graph via pymatgen `StructureGraph.__mul__` (networkx `union`/`relabel` of 27 copies, ~940 ms/structure here) and 3D-connectivity runs Larsen dimensionality over networkx. These are replaced by direct integer image-offset algorithms on a rustworkx graph — O(N+E), no supercell — making `has_lone_molecule` ~94× faster (~940 ms → ~10 ms); it dominates the reference's runtime, so it drives the speedup. `has_oms` now uses Rust for Voronoi facet-neighbor selection and pymatgen-compatible local order-parameter formulas. Speedup also grows with structure size and is higher on generated/distorted structures (more disconnected fragments).

## ⚙️ How it works

Python owns CIF/structure loading, pymatgen integration, and orchestration. The heavy lifting is delegated:

- **Rust** (`_rust` PyO3 extension): minimum-image distances, short contacts, neighbor candidates, connected components, graph degrees, OMS Voronoi/order-parameters, and the EQeq charge solve.
- **rustworkx** (`checks/_subgraph_rx.py`): floating-solvent / lone-molecule detection (finite connected components via an image-offset consistency test) and Larsen dimensionality (rank of the lattice-image vectors a component spans). These replace the networkx-heavy paths.
- **structuregraph_helpers** is retained for the logic-critical, non-hot pieces it does well: graph construction (tuned VESTA cutoffs) and the Weisfeiler–Lehman graph hashes.

The structure graph is built once per `MOFChecker` and reused across all checks.

## ✅ Parity

Verified against a MOFChecker 2.0 checkout (used only as a behavioral oracle) via the harnesses in `scripts/`:

- **Real QMOFs:** 4500/4500 descriptor-comparisons (250 structures × 18 descriptors) — 100%.
- **Reference test CIFs:** 16/16.
- **Generated (distorted) structures:** 3899/3900. The single difference is **`has_lone_molecule`, where mofchecker-next is more correct** — see Limitations.

Reproduce: `scripts/qmof_parity.py` (real QMOFs), `scripts/generated_parity.py` (generated CIFs), `scripts/validate_subgraph_rx.py` (floating-solvent port). Point them at a local QMOF CIF directory with `QMOF_DIR=...`.

## ⚠️ Limitations & deliberate differences

- **Healing not implemented.** `adding_hydrogen` / `adding_linker` raise `NotImplementedError`.
- **No porosity.** `is_porous` returns `None` (no bundled Zeo++).
- **`has_lone_molecule` is *more* correct than the reference.** MOFChecker 2.0's supercell + in-cell-filter heuristic silently misses finite molecules that wrap the unit-cell boundary (the origin-cell copy is truncated at the supercell face). mofchecker-next detects them via a topological finite-component test. This is the only descriptor that ever disagrees with the reference, only on pathological/distorted structures (0 disagreements on real QMOFs).
- **Graph construction is still the floor.** pymatgen's VESTA neighbor-finding is unchanged; the speedup is in the graph *algorithms*, not bond perception.
- **Determinism.** `symmetry_hash` is deterministic by design and will not match the reference's randomized value across runs.

## ⚖️ License

The **published package is GPLv2**, because it bundles `py/mofchecker_next/eqeq/` — a faithful translation of [EQeq](https://github.com/lsmo-epfl/EQeq) (GPLv2, see `py/mofchecker_next/eqeq/LICENSE`) — and the GPL governs the combined work. The non-eqeq sources are **MIT** (`LICENSE`); for an MIT-only build, omit the `eqeq` subpackage and the `has_high_charges` diagnostic.

The MOFChecker 2.0 checkout used as the behavioral oracle (ANCSA 1.0) is **not** redistributed; see `external/REFERENCE.md` to reproduce it locally.

## 🛠️ For developers

<details>
<summary>Build, test, and release</summary>

```bash
python -m maturin develop --release          # build the Rust extension into the venv
python -m pytest -q                          # Python tests
cargo test --release --manifest-path rust/Cargo.toml   # Rust tests
# Source builds need libclang available for qhull-sys/bindgen.
```

**Layout**

- `py/mofchecker_next/` — Python package (`checks/`, `diagnostics.py`, the `eqeq` subpackage).
- `py/mofchecker_next/checks/_subgraph_rx.py` — rustworkx floating-solvent + dimensionality.
- `rust/` — the `_rust` PyO3 extension (geometry + OMS + EQeq kernels).
- `scripts/` — parity harnesses and the speed benchmark.
- `tests/` — Rust and Python unit tests.
- `docs/DIAGNOSTIC_INVENTORY.md` — per-diagnostic parity status.

**Making a release** — wheels are built and published by `.github/workflows/release.yml` via PyPI Trusted Publishing on a version tag:

```bash
git tag v0.1.0 && git push origin v0.1.0
```

</details>

