Metadata-Version: 2.4
Name: riper
Version: 0.2.8
Summary: ASE Calculator for TURBOMOLE's RIPER module
Project-URL: Homepage, https://www.turbomole.org
Author-email: Manas Sharma <manassharma07@live.com>
Keywords: ase,calculator,dft,periodic,riper,turbomole
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Science/Research
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Scientific/Engineering :: Chemistry
Classifier: Topic :: Scientific/Engineering :: Physics
Requires-Python: >=3.7
Requires-Dist: ase>=3.22
Requires-Dist: numpy>=1.20
Description-Content-Type: text/markdown

# RIPER: ASE Calculator for TURBOMOLE

`riper` is an [ASE](https://wiki.fysik.dtu.dk/ase/) calculator interface for the
RIPER module in [TURBOMOLE](https://www.turbomole.org/). It lets you run RIPER
DFT calculations from normal ASE workflows for molecules, chains, slabs, and
bulk periodic systems.

The calculator writes TURBOMOLE `coord` and `control` files, runs `riper`, parses
energies, forces, stresses, and the final Fermi-level band gap, and returns the
results in ASE units.

## Features

- Molecular and periodic RIPER calculations through ASE.
- Automatic periodicity detection from `atoms.pbc`.
- 0D, 1D, 2D, and 3D systems.
- Energies in eV.
- Forces in eV/Angstrom.
- Stress in eV/Angstrom^3 for periodic systems with `$optcell`.
- Final band gap parsing from the RIPER `Fermi Level Statistics (eV)` block.
- Band gap stored in both `calc.results["band_gap"]` and `atoms.info["band_gap"]`, so ASE extXYZ exports include it automatically.
- DFT-D3 support through `disp3=True`.
- Density cube generation through `plot_dens=True`.
- UKS calculations through `uks=True`.
- Gaussian smearing through `smear=True` and `sigma=...`.
- Arbitrary RIPER keywords through `extra_keywords`.
- Works with normal ASE optimizers, cell filters, trajectories, NEB workflows, and file I/O.

## Installation

From PyPI:

```bash
pip install riper
```

For development from this repository:

```bash
pip install -e .
```

or, with `uv`:

```bash
uv pip install -e .
```

## TURBOMOLE Environment

Before using the calculator, make sure TURBOMOLE is configured in the shell where
Python runs. For example:

```bash
export TURBODIR=/path/to/TURBOMOLE
export TURBOIMG=/path/to/TURBOMOLE
source $TURBODIR/Config_turbo_env
command -v riper_smp
```

The calculator does not ship TURBOMOLE. It calls the executable selected with
`riper_command`, for example `riper`, `riper_smp`, `riper_omp`, or a full path.

## Quick Start: Water Single Point

```python
from ase.build import molecule
from riper import RIPERCalculator

water = molecule("H2O")
water.calc = RIPERCalculator(
    functional="pbe",
    basis="def2-SVP",
    auxbasis="universal",
    riper_command="riper_smp",
    directory="calculations/water_pbe",
)

energy = water.get_potential_energy()
forces = water.get_forces()
band_gap = water.calc.get_band_gap()

print(f"Energy: {energy:.8f} eV")
print(f"Band gap: {band_gap:.6f} eV")
print(forces)
```

When a calculation succeeds, the band gap is also available as:

```python
water.calc.results["band_gap"]
water.info["band_gap"]
```

That means ASE extXYZ output includes `band_gap` automatically:

```python
from ase.io import write

write("water.extxyz", water)
```

## Periodic Example: Graphene With SCAN0

```python
from ase.io import read, write
from riper import RIPERCalculator

graphene = read("examples/graphene_scan0.extxyz")

graphene.calc = RIPERCalculator(
    functional="scan0",
    basis="pob-TZVP-rev2",
    auxbasis="universal",
    nkpoints=[15, 15, 1],
    scfconv=8,
    scfiterlimit=100,
    riper_command="riper_smp",
    directory="calculations/graphene_scan0",
)

energy = graphene.get_potential_energy()
forces = graphene.get_forces()

print(f"Energy per atom: {energy / len(graphene):.8f} eV")
print(f"Band gap: {graphene.info['band_gap']:.6f} eV")
print(f"Max force component: {abs(forces).max():.6f} eV/Angstrom")

write("graphene_scan0.extxyz", graphene)
```

For 2D systems, only the first two entries of `nkpoints` are written to the RIPER
control file. For 1D systems, only the first entry is used.

## Geometry Optimization: B3LYP Water

```python
from ase.build import molecule
from ase.io import write
from ase.optimize import LBFGS
from riper import RIPERCalculator

water = molecule("H2O")
water.calc = RIPERCalculator(
    functional="b3-lyp",
    basis="def2-SVP",
    auxbasis="universal",
    scfconv=8,
    scfiterlimit=100,
    riper_command="riper_smp",
    directory="calculations/water_b3lyp_opt",
)

opt = LBFGS(
    water,
    trajectory="calculations/water_b3lyp_opt.traj",
    logfile="calculations/water_b3lyp_opt.log",
)
opt.run(fmax=0.02)

print(f"Final energy: {water.get_potential_energy():.8f} eV")
print(f"Band gap: {water.info['band_gap']:.6f} eV")

write("calculations/water_b3lyp_optimized.extxyz", water)
```

## 1D Periodic Example: LiH Chain

```python
from ase import Atoms
from riper import RIPERCalculator

a = 3.2
lih = Atoms(
    symbols="LiH",
    positions=[[0.0, 0.0, 0.0], [a / 2, 0.0, 0.0]],
    cell=[[a, 0.0, 0.0], [0.0, 20.0, 0.0], [0.0, 0.0, 20.0]],
    pbc=[True, False, False],
)

lih.calc = RIPERCalculator(
    functional="pbe",
    basis="pob-DZVP-rev2",
    auxbasis="universal",
    nkpoints=[8, 1, 1],
    riper_command="riper_smp",
    directory="calculations/lih_chain",
)

print(f"Energy: {lih.get_potential_energy():.8f} eV")
print(f"Band gap: {lih.info['band_gap']:.6f} eV")
```

## Stress Example: Bulk Silicon

```python
from ase.build import bulk
from riper import RIPERCalculator

si = bulk("Si", "diamond", a=5.43)
si.calc = RIPERCalculator(
    functional="pbe",
    basis="pob-DZVP-rev2",
    auxbasis="universal",
    nkpoints=[4, 4, 4],
    calculate_stress=True,
    riper_command="riper_smp",
    directory="calculations/si_stress",
)

energy = si.get_potential_energy()
stress = si.get_stress()

print(f"Energy: {energy:.8f} eV")
print("Stress in ASE Voigt order [xx, yy, zz, yz, xz, xy]:")
print(stress)
```

Stress parsing uses the RIPER `stress tensor, raw:` output, divides by the cell
volume, and returns ASE Voigt order in eV/Angstrom^3.

## D3 Dispersion Example

```python
from ase import Atoms
from riper import RIPERCalculator

water_dimer = Atoms(
    symbols="OHHOHH",
    positions=[
        [0.000, 0.000, 0.117],
        [0.000, 0.757, -0.469],
        [0.000, -0.757, -0.469],
        [2.900, 0.000, 0.117],
        [2.900, 0.757, -0.469],
        [2.900, -0.757, -0.469],
    ],
)

water_dimer.calc = RIPERCalculator(
    functional="pbe",
    basis="def2-SVP",
    auxbasis="universal",
    disp3=True,
    riper_command="riper_smp",
    directory="calculations/water_dimer_d3",
)

print(f"D3 energy: {water_dimer.get_potential_energy():.8f} eV")
```

## Density Cube Example

```python
from ase.build import molecule
from riper import RIPERCalculator

water = molecule("H2O")
water.calc = RIPERCalculator(
    functional="pbe",
    basis="def2-SVP",
    auxbasis="universal",
    plot_dens=True,
    riper_command="riper_smp",
    directory="calculations/water_density",
)

water.get_potential_energy()
print("Density cube written by RIPER as calculations/water_density/td.cub")
```

`plot_dens=True` writes:

```text
$pointvalper fmt=cub
   dens
```

into the control file.

## UKS and Fixed Unpaired Electrons: Fe `desnue` Scan

RIPER-specific keywords can be passed through `extra_keywords`. For example,
`desnue` fixes the target number of unpaired electrons per unit cell when used
with UKS and smearing.

```python
from ase.io import read
from riper import RIPERCalculator

fe0 = read("examples/Fe_primitive.cif")

for desnue in [0.0, 1.0, 2.0, 3.0, 4.0]:
    fe = fe0.copy()
    fe.calc = RIPERCalculator(
        functional="pbe",
        basis="pob-DZVP-rev2",
        auxbasis="universal",
        nkpoints=[18, 18, 18],
        uks=True,
        smear=True,
        sigma=0.001,
        scfconv=8,
        scfiterlimit=200,
        extra_keywords={"desnue": f"{desnue:g}"},
        riper_command="riper_smp",
        directory=f"calculations/fe_desnue_{desnue:g}",
    )
    energy = fe.get_potential_energy()
    print(f"desnue={desnue:3.1f}  energy={energy:.8f} eV")
```

## Interlayer Scan Example: Graphene/hBN

The examples directory contains a manual graphene/hBN interlayer-spacing scan.
It shifts the hBN layer, computes a potential-energy curve, writes an extXYZ
trajectory, and saves a PNG plot.

```bash
cd examples
python3 example13_graphene_hbn_interlayer_scan_scan0_disp3.py
```

## Climbing-Image NEB Example

Because `RIPERCalculator` is a normal ASE calculator, it can be used in ASE NEB
workflows. Each image should use a separate calculation directory.

```python
from ase.mep.neb import NEB
from ase.optimize import BFGS
from riper import RIPERCalculator

# images = [reactant, image1, image2, ..., product]
neb = NEB(images, climb=True)
neb.interpolate("idpp")

for i, image in enumerate(images):
    image.calc = RIPERCalculator(
        functional="pbe",
        basis="def2-SVP",
        auxbasis="universal",
        riper_command="riper_smp",
        directory=f"calculations/neb_image_{i:02d}",
    )

opt = BFGS(neb, trajectory="calculations/neb.traj")
opt.run(fmax=0.05)
```

See `examples/example09_neb_ethane_torsional_barrier_staggerd_eclipsed.py` for a
complete ethane torsional-barrier example.

## Calculator Parameters

```python
RIPERCalculator(
    basis=None,
    auxbasis="universal",
    functional="pbe",
    nkpoints=None,
    charge=0,
    uks=False,
    smear=False,
    sigma=0.01,
    riper_command="riper",
    directory=".",
    extra_keywords=None,
    convergence_check="error",
    scfiterlimit=50,
    scfconv=7,
    denconv=None,
    disp3=False,
    plot_dens=False,
)
```

### Common Options

| Option | Type / units | Meaning |
| --- | --- | --- |
| `basis` | `str` or `None` | Orbital basis. If `None`, defaults to `def2-SVP` for 0D and `pob-DZVP-rev2` for periodic systems. |
| `auxbasis` | `str` | Auxiliary basis, default `universal`. Written as `jbas` in `$atoms`. |
| `functional` | `str` | TURBOMOLE functional keyword, for example `pbe`, `b3-lyp`, `scan0`. Written under `$dft`. |
| `nkpoints` | list of `int` | Monkhorst-Pack style RIPER k-point counts. Only periodic directions are written: first 1 entry for 1D, first 2 for 2D, first 3 for 3D. |
| `charge` | `int`, electrons | Total system charge. Nonzero values write an `$atomdens` charge block. |
| `uks` | `bool` | Enables unrestricted Kohn-Sham by writing `$uhf`. |
| `smear` | `bool` | Enables Gaussian smearing by writing `sigma` inside `$riper`. |
| `sigma` | `float`, Hartree | Gaussian smearing width. This is a RIPER energy in Hartree, not eV. |
| `extra_keywords` | `dict[str, str]` | Additional key/value entries written inside `$riper`, for example `{"desnue": "4"}`. Values are passed through as text. |
| `scfiterlimit` | `int`, cycles | Writes `$scfiterlimit`, the maximum number of SCF iterations. |
| `scfconv` | `int`, Hartree exponent | Writes `$scfconv N`, meaning an SCF energy convergence threshold of about `10^-N` Hartree. For example `scfconv=8` means `1e-8 Ha`, not eV. |
| `denconv` | `str` or `None`, RIPER/TURBOMOLE notation | Optional `$denconv` value, for example `"1.0d-7"`. This is passed through exactly. |
| `disp3` | `bool` | Adds `$disp3` for DFT-D3 dispersion correction. |
| `plot_dens` | `bool` | Requests density cube output by writing `$pointvalper fmt=cub` and `dens`. RIPER writes `td.cub`. |
| `convergence_check` | `str` enum | One of `"error"`, `"warning"`, or `"ignore"`; controls how detected RIPER non-convergence is handled. |
| `riper_command` | `str`, shell command | Command executed in the calculation directory, for example `riper`, `riper_smp`, `riper_omp`, or a full path. |
| `directory` | `str` path | Working directory where `coord`, `control`, `riper.out`, and other RIPER files are written. |

Calculator outputs use ASE units: energy in eV, forces in eV/Angstrom, stress in
eV/Angstrom^3, and band gap in eV. Input convergence and smearing options follow
TURBOMOLE/RIPER conventions, so energy thresholds such as `scfconv` and `sigma`
are in Hartree.

## Files Written

For each calculation directory, the calculator writes at least:

- `coord`: TURBOMOLE coordinates in Bohr.
- `control`: TURBOMOLE control file.
- `riper.out`: RIPER stdout/stderr.

RIPER/TURBOMOLE may also create files such as `statistics`, `ddens`, `errvec`,
`oldfock`, `td.cub`, `EIGS`, and updated gradient data in `control`.

## Band Gap

After a successful calculation, call:

```python
gap = atoms.calc.get_band_gap()
```

The value is parsed from the final RIPER output block:

```text
---------------- Fermi Level Statistics (eV) ----------------
    Lowest unoccupied band = ...
    Highest occupied band  = ...
    Band gap               = ...
    Band gap middle        = ...
    Fermi level            = ...
-------------------------------------------------------------
```

The parsed gap is stored in eV.

## Included Examples

The repository includes these scripts:

- `examples/example01_0D_water.py`: water single-point energy and forces.
- `examples/example02_0D_water_geom_opt.py`: water geometry optimization.
- `examples/example03_1D_LiH.py`: 1D LiH chain and lattice scan.
- `examples/example04_adjust_conv_criteria.py`: tighter SCF and density convergence.
- `examples/example05_dispersion_correction.py`: D3 dispersion correction.
- `examples/example06_gen_density_cube.py`: density cube output.
- `examples/example07_calculate_stress.py`: stress tensor for bulk silicon.
- `examples/example08_bulk_Si_unit_cell+geom_opt.py`: cell and geometry optimization.
- `examples/example09_neb_ethane_torsional_barrier_staggerd_eclipsed.py`: CI-NEB ethane rotation.
- `examples/example10_0D_water_b3lyp_geom_opt.py`: B3LYP water optimization.
- `examples/example11_graphene_scan0.py`: graphene with SCAN0 and `pob-TZVP-rev2`.
- `examples/example12_Fe_desnue_scan.py`: UKS Fe `desnue` scan.
- `examples/example13_graphene_hbn_interlayer_scan_scan0_disp3.py`: graphene/hBN interlayer scan.

## Notes and Limitations

- Periodicity is inferred from `atoms.pbc` by counting periodic axes.
- The current lattice writer assumes leading periodic directions: x for 1D, x/y for 2D, x/y/z for 3D.
- `riper_command` is executed in the selected calculation directory.
- Forces are read from the `$grad` block in the updated `control` file.
- Stress requires periodic systems and RIPER output containing `stress tensor, raw:`.
- `extra_keywords` are written inside the `$riper` section as simple `key value` lines.

## Contact

Manas Sharma

- manas.sharma@turbomole.org
- manas.sharma@unni-jena.de
- manassharma07@live.com
