Metadata-Version: 2.4
Name: cvve
Version: 0.1.0
Summary: Read, write, and analyze cube, CHGCAR, and PARCHG volumetric grids with explicit units.
Author: Mingi Kang
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: numpy>=1.22
Provides-Extra: test
Requires-Dist: pytest>=8; extra == "test"
Provides-Extra: ase
Requires-Dist: ase>=3.22; extra == "ase"
Provides-Extra: pymatgen
Requires-Dist: pymatgen>=2024; extra == "pymatgen"
Provides-Extra: interop
Requires-Dist: ase>=3.22; extra == "interop"
Requires-Dist: pymatgen>=2024; extra == "interop"
Provides-Extra: docs
Requires-Dist: mkdocs-material>=9; extra == "docs"
Dynamic: license-file

# cvve

A Python toolkit for reading, writing, and analyzing volumetric grid data from
electronic-structure calculations — Gaussian/ORCA cube files and VASP
CHGCAR/PARCHG grids, unified under a single `GridField` model with explicit
units.

The package is distributed as `cvve` on PyPI. Both `import cvve` and
`import cube` expose the same public API, and both `cvve` and `cube` console
commands are installed.

---

## Installation

```bash
uv add cvve                      # NumPy only
uv add "cvve[ase]"               # + ASE adapter
uv add "cvve[pymatgen]"          # + pymatgen adapter
uv add "cvve[interop]"           # + both
```

Development setup:

```bash
git clone https://github.com/kangmg/cube
cd cube
uv sync --extra test
uv run pytest
```

---

## Reading volumetric files

`read_grid_field` is the main entry point. It infers the format from the
filename and returns a `GridField`.

```python
import cvve as cube

# Gaussian / ORCA cube file
homo = cube.read_grid_field("HOMO.cube")
lumo = cube.read_grid_field("LUMO.cube")

print(homo.grid.shape)       # (nx, ny, nz)  e.g. (80, 80, 80)
print(homo.length_unit)      # "bohr" or "angstrom"
print(homo.voxel_volume)     # dV in length_unit^3
print(homo.integral())       # ∫ ψ dV
```

```python
# VASP CHGCAR (total density)
field = cube.read_grid_field("CHGCAR")

# Spin-polarized: select a dataset
mag = cube.read_grid_field("CHGCAR_SPIN", format="chgcar", chgcar_dataset="magnetization")
```

Multi-dataset cube files (Gaussian `DSET_IDS`):

```python
# first dataset by default; select by name or numeric id
field = cube.read_grid_field("multi.cube", cube_dataset="HOMO")
field = cube.read_grid_field("multi.cube", cube_dataset=44)
```

---

## Computing delta-density

The most common workflow: normalize two orbitals, square them into densities,
then take the difference.

```python
import cvve as cube

homo = cube.read_grid_field("HOMO.cube", kind="orbital")
lumo = cube.read_grid_field("LUMO.cube", kind="orbital")

# normalize and convert to density
homo_n, _, _ = cube.normalize_orbital_field(homo)
lumo_n, _, _ = cube.normalize_orbital_field(lumo)

rho_homo = cube.density_from_orbital_field(homo_n)
rho_lumo = cube.density_from_orbital_field(lumo_n)

# ρ_HOMO − ρ_LUMO
delta = cube.delta_grid_field(rho_homo, rho_lumo)
```

Write the result back as a cube file, keeping the original header and atom
block as a template:

```python
doc = cube.read_grid_document("HOMO.cube", kind="orbital")
doc.write("delta_density.cube", delta)
```

---

## Overlap and charge-transfer metrics

`analyze_orbital_pair_fields` normalizes, squares, and computes all overlap
metrics in one call:

```python
import cvve as cube

homo = cube.read_grid_field("HOMO.cube", kind="orbital")
lumo = cube.read_grid_field("LUMO.cube", kind="orbital")

result = cube.analyze_orbital_pair_fields(homo, lumo, normalize=True)

print(f"S (signed MO overlap)    = {result.overlaps['S_signed']:.6f}")
print(f"S (|ψ₁||ψ₂| overlap)    = {result.overlaps['S_abs_psi']:.6f}")
print(f"D_overlap (density)      = {result.overlaps['D_overlap']:.6f}")
print(f"charge-transfer index    = {result.delta_checks['half_abs_integral']:.6f}")
```

`result` also carries the full field arrays (`rho1`, `rho2`, `delta_rho`, …)
so you can write any of them without recomputing.

---

## CHGCAR delta-density (VASP)

```python
import cvve as cube

doc1 = cube.read_grid_document("CHGCAR_ref",  format="chgcar")
doc2 = cube.read_grid_document("CHGCAR_calc", format="chgcar")

delta = cube.delta_grid_field(doc1.field, doc2.field)

# write with doc1's structure block / augmentation text as template
doc1.write("delta.CHGCAR", delta)
```

The writer preserves non-selected datasets, augmentation text, Selective
Dynamics flags, and POSCAR scale factors.

---

## Unit conversion

`GridField` keeps length units explicit. Convert between bohr and Angstrom
with optional density rescaling:

```python
# cube files read in bohr; convert geometry and rescale density values
field_ang = homo.to_length_unit("angstrom", value_scaling="density")

print(field_ang.length_unit)    # "angstrom"
print(field_ang.grid.value_unit)  # "electron/angstrom^3"
```

---

## Optional adapters (ASE / pymatgen)

ASE and pymatgen are only imported when you call a conversion helper.

```python
# → ASE Atoms
atoms = cube.read_grid_field("HOMO.cube").to_atoms()

# → pymatgen VolumetricData
from cube.adapters import grid_field_to_pymatgen_volumetric
vol = grid_field_to_pymatgen_volumetric(field)
```

---

## CLI

Compare two MO cube files and write density / delta-density outputs:

```bash
uv run cvve HOMO.cube LUMO.cube
```

`cube` is installed as a command alias for existing scripts.

Output files written by default:

| File | Contents |
|---|---|
| `psi1_density.cub` | ρ₁ = ψ₁² |
| `psi2_density.cub` | ρ₂ = ψ₂² |
| `delta_density.cub` | ρ₁ − ρ₂ |

Common options:

```
--out-prefix PREFIX          prefix all output filenames
--extra-header-lines N       extra header lines after atom block (0, 1, or auto)
--delta-sign {1-2,2-1}       sign of delta-density (default: 1-2)
--no-normalize               skip orbital normalization
--write-z-profiles           write z-integrated .dat profiles
--write-summary              write scalar metrics to a .dat file
--write-overlap-cubes        also write |ψ₁||ψ₂| and ρ₁ρ₂ cube files
```

CHGCAR comparison:

```bash
uv run cvve --input-format chgcar CHGCAR_ref CHGCAR_calc --chgcar-dataset total
```

---

## Limitations

- GPU arrays (CuPy, etc.) are normalized to NumPy at the model boundary and are not preserved.
- CHGCAR augmentation blocks are preserved as opaque text; PAW occupancy contents are not interpreted.
- Noncollinear CHGCAR is covered by a synthetic fixture only; real VASP noncollinear output still needs validation.
- WAVECAR is out of scope; use CHGCAR/PARCHG-like real-space grids instead.
- Gaussian and ORCA support is cube-file based; log-file parsing is not part of this project.
