Metadata-Version: 2.4
Name: gasfir
Version: 1.0.2
Summary: General approximator for strong-field ionization rates — retrieval of sub-cycle ionization dynamics from ab initio or experimental probabilities (Agarwal, Scrinzi & Yakovlev, PRA 113, L021101, 2026)
Project-URL: Homepage, https://gitlab.mpcdf.mpg.de/gaf/gasfir
Project-URL: Documentation, https://gasfir.readthedocs.io
Project-URL: Repository, https://gitlab.mpcdf.mpg.de/gaf/gasfir.git
Project-URL: Issues, https://gitlab.mpcdf.mpg.de/gaf/gasfir/issues
Author-email: Manoram Agarwal <manoram.agarwal@mpq.mpg.de>
Maintainer-email: Manoram Agarwal <manoram.agarwal@mpq.mpg.de>
License: MIT License
        
        Copyright (c) 2025 Manoram Agarwal
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
License-File: LICENSE
Keywords: atomic,ionization,laser,molecular,physics
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Science/Research
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Scientific/Engineering :: Physics
Requires-Python: >=3.10
Requires-Dist: numba>=0.61.2
Requires-Dist: numpy<3,>=1.26.4
Requires-Dist: pandas>=1.5.0
Requires-Dist: scipy>=1.15.3
Provides-Extra: dev
Requires-Dist: black==26.5.1; extra == 'dev'
Requires-Dist: flake8==7.3.0; extra == 'dev'
Requires-Dist: isort==8.0.1; extra == 'dev'
Requires-Dist: mypy==2.1.0; extra == 'dev'
Requires-Dist: pre-commit>=3.0.0; extra == 'dev'
Requires-Dist: pytest-cov>=4.0.0; extra == 'dev'
Requires-Dist: pytest>=7.0.0; extra == 'dev'
Requires-Dist: tabulate>=0.9.0; extra == 'dev'
Provides-Extra: docs
Requires-Dist: myst-parser>=0.18.0; extra == 'docs'
Requires-Dist: sphinx-autodoc-typehints>=1.18.0; extra == 'docs'
Requires-Dist: sphinx-copybutton>=0.5.0; extra == 'docs'
Requires-Dist: sphinx-rtd-theme>=1.0.0; extra == 'docs'
Requires-Dist: sphinx>=4.0.0; extra == 'docs'
Provides-Extra: retrieval
Requires-Dist: asteval>=1.0.6; extra == 'retrieval'
Requires-Dist: cma>=3.0.0; extra == 'retrieval'
Requires-Dist: corner>=2.2.0; extra == 'retrieval'
Requires-Dist: emcee>=3.1.0; extra == 'retrieval'
Requires-Dist: h5py>=3.0.0; extra == 'retrieval'
Requires-Dist: lmfit>=1.3.3; extra == 'retrieval'
Requires-Dist: tqdm>=4.60.0; extra == 'retrieval'
Requires-Dist: uncertainties>=3.2.3; extra == 'retrieval'
Description-Content-Type: text/markdown

# GASFIR: General Approximator for Strong-Field Ionization Rates

[![PyPI version](https://badge.fury.io/py/gasfir.svg)](https://badge.fury.io/py/gasfir)
[![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Documentation Status](https://readthedocs.org/projects/gasfir/badge/?version=latest)](https://gasfir.readthedocs.io/en/latest/?badge=latest)
[![Tests](https://gitlab.mpcdf.mpg.de/gaf/gasfir/badges/main/pipeline.svg)](https://gitlab.mpcdf.mpg.de/gaf/gasfir/-/pipelines)
[![Coverage](https://gitlab.mpcdf.mpg.de/gaf/gasfir/badges/main/coverage.svg)](https://gitlab.mpcdf.mpg.de/gaf/gasfir/-/pipelines)
[![DOI](https://img.shields.io/badge/DOI-10.1103%2Fvxgm--kdtt-blue)](https://doi.org/10.1103/vxgm-kdtt)

GASFIR is a Python package implementing the ionization-rate retrieval framework
introduced in:

> **Agarwal, Scrinzi & Yakovlev** — *General approximator for strong-field ionization rates*  
> *Physical Review A* **113**, L021101 (2026) — [10.1103/vxgm-kdtt](https://doi.org/10.1103/vxgm-kdtt)

It addresses the long-standing problem of determining accurate, time-resolved
ionization rates for atoms in strong laser fields — a quantity fundamental to
attosecond science.  By fitting a five-parameter kernel to ionization
probabilities computed for a set of few-cycle laser pulses, GASFIR retrieves
sub-optical-cycle ionization dynamics within and beyond the strong-field
approximation.

Three calculation methods are provided:

1. **GASFIR** — semi-analytic kernel, Eqs. (2), (3), (7) of the paper; fast
2. **Exact SFA** — full numerical saddle-point integration of the SFA kernel,
   Eq. (6).  Selected via ``kernel_type="exact_SFA"`` and currently implemented
   for the **hydrogen** ground state only (use it with the ``H_SFA`` parameters)
3. **QS** — quasistatic (tunneling) limit, Eq. (9); connects to ADK/PPT theory

All internal quantities are in **atomic units** unless stated otherwise.

## 📖 Documentation

Full documentation — installation, user guide, worked examples, and the
complete API reference — is hosted on Read the Docs:

**→ https://gasfir.readthedocs.io**

The docs are versioned: `latest` tracks the `main` branch and each release tag
(e.g. `v1.0.0`) has its own frozen version, selectable from the flyout menu.

---

## Installation

### Core only — ionization calculations

```bash
pip install gasfir
```

Installs: `numba`, `numpy`, `scipy`, `pandas`. No fitting library required.

### With fitting & retrieval pipeline

```bash
pip install gasfir[retrieval]
```

Adds: `lmfit`, `emcee`, `cma` (CMA-ES), `tqdm`, `corner`.

### Full development install

```bash
pip install gasfir[retrieval,dev,docs]
```

### Conda environment (recommended)

```bash
conda env create -f environment.yml
conda activate gasfir
pip install -e ".[retrieval,dev,docs]"
```

To recreate `environment.yml` after changing dependencies:

```bash
conda env export -n gasfir --no-builds | grep -v "^prefix:" > environment.yml
```

---

## Quick Start

### Ionization probability

```python
from gasfir import create_pulse, get_parameters, get_diabatic_ionization_probability

# create_pulse(wavelength_nm, intensity_Wcm2, CEP_rad, duration_optical_cycles)
laser = create_pulse(800, 1e14, 0, 6)

params = get_parameters("Hydrogen_SFA")
prob = get_diabatic_ionization_probability(pulse=laser, param_dict=params)
print(f"P = {prob:.4e}")
```

### Time-resolved ionization rate

```python
from gasfir import get_diabatic_ionization_rate
import matplotlib.pyplot as plt

t = laser.get_tgrid(dt=0.25)   # time grid in atomic units
rates = get_diabatic_ionization_rate(t_grid=t, pulse=laser, param_dict=params)

plt.semilogy(t, rates)
plt.xlabel("Time (a.u.)")
plt.ylabel("Ionization rate (a.u.)")
plt.tight_layout()
plt.show()
```

### Pulse types

Any pulse object can be passed to every calculation function (`pulse=...`).

```python
from gasfir import create_pulse
from gasfir.pulse import create_pulse_ellip, SumOfPulses, TIPTOE, DataPulse
import numpy as np

# 1. Linearly polarized cos^N pulse (N=8 default) — the usual workhorse
laser = create_pulse(800, 1e14, 0, 6)          # 800 nm, 1e14 W/cm², CEP 0, 6 cycles

# 2. Elliptical / circular polarization (ellip_frac=1 → circular)
ell = create_pulse_ellip(800, 1e14, 0, 6, ellip_frac=2)

# 3. Two-colour (or arbitrary) superposition
two_colour = SumOfPulses([create_pulse(800, 1e14, 0, 6),
                          create_pulse(400, 2e13, 0, 6)])

# 4. TIPTOE pump–probe: strong pump fixed, weak probe scanned in delay
pump  = create_pulse(800, 3e14, 0, 3)
probe = create_pulse(800, 1e13, 0, 1)
scanner = TIPTOE(pump, probe)
delays  = scanner.return_delay_array()         # Nyquist-sampled delay grid
field_at_tau = scanner.at_delay(delays[0])     # a SumOfPulses at that delay

# 5. From your own data — supply E(t) OR A(t) on a time grid (atomic units;
#    A(t) must vanish at both ends). Use it like any other pulse.
t = np.linspace(-400, 400, 8001)
A = np.exp(-(t/150)**2) * np.sin(0.057 * t) / 0.057
my_pulse = DataPulse(t=t, A=A)                 # E(t) derived as -dA/dt
```

See the [Laser Fields guide](https://gasfir.readthedocs.io/en/latest/user_guide/laser_fields.html)
for full details (pump–probe scanning, boundary conditions, combining pulses).

### Available parameter sets

```python
from gasfir import get_parameters

print(get_parameters())           # list all names
params = get_parameters("He_Hacc5_QS")
```

---

## Unit Conversions — `AtomicUnits`

`AtomicUnits` provides named two-way conversions so you never have to reason about
which direction to multiply or divide.

```python
from gasfir import AtomicUnits

# Length
AtomicUnits.nm_to_au(800)          # 800 nm → a.u.
AtomicUnits.au_to_nm(1.0)          # Bohr radius in nm (≈ 0.0529)
AtomicUnits.angstrom_to_au(0.529)
AtomicUnits.m_to_au(5.29e-11)

# Time
AtomicUnits.fs_to_au(2.42)         # 2.42 fs → a.u.
AtomicUnits.au_to_fs(100)          # 100 a.u. → fs

# Energy
AtomicUnits.eV_to_au(13.6)         # hydrogen IP in a.u. (≈ 0.5)
AtomicUnits.au_to_eV(0.5)          # ≈ 13.6 eV

# Electric field
AtomicUnits.Vm_to_au(5.14e11)      # ≈ 1 a.u. of field
AtomicUnits.VA_to_au(51.4)         # same in V/Å

# Laser wavelength ↔ angular frequency
AtomicUnits.wavelength_nm_to_omega_au(800)     # ≈ 0.0570 a.u.
AtomicUnits.omega_au_to_wavelength_nm(0.0570)  # ≈ 800 nm

# Photon energy ↔ angular frequency (ħ = 1 in a.u.)
AtomicUnits.photon_energy_eV_to_omega_au(1.55)
AtomicUnits.omega_au_to_photon_energy_eV(0.057)

# Laser intensity ↔ field amplitude
AtomicUnits.intensity_Wcm2_to_field_au(1e14)   # E₀ in a.u. (≈ 0.0534)
AtomicUnits.field_au_to_intensity_Wcm2(0.0534) # back to W/cm²
AtomicUnits.intensity_Wcm2_to_au(1e14)         # intensity in a.u. (≠ field amplitude)
```

> **Note** `intensity_Wcm2_to_au` returns *intensity* in atomic units
> (`I × factor`, dimensionally I in a.u.).
> `intensity_Wcm2_to_field_au` returns the *electric field amplitude*
> (`√(I × factor)`), which is ~18× larger and what you usually need.

---

## Keldysh Parameter

The Keldysh parameter γ = ω√(2Iₚ) / E₀ separates the tunnel (γ ≪ 1) and
multiphoton (γ ≫ 1) ionization regimes.

### Compute γ for a given pulse

```python
laser = create_pulse(800, 1e14, 0, 6)
gamma = laser.get_keldysh_parameter(Ip_au=0.5)   # hydrogen
print(f"γ = {gamma:.3f}")  # ≈ 1.07 → near the tunnel/multiphoton border
```

### Design a pulse for a target γ

```python
from gasfir import AtomicUnits

Ip = 0.5   # hydrogen (a.u.)

# 10-photon resonance wavelength (ω = Ip/10)
wl = AtomicUnits.omega_au_to_wavelength_nm(Ip / 10)   # ≈ 911 nm

# Intensities for the three main regimes
I_multi  = AtomicUnits.intensity_for_keldysh(4.0,  wl, Ip)  # γ=4, multiphoton
I_border = AtomicUnits.intensity_for_keldysh(1.0,  wl, Ip)  # γ=1, border
I_tunnel = AtomicUnits.intensity_for_keldysh(0.25, wl, Ip)  # γ=0.25, deep tunnel

print(f"γ=4   →  I = {I_multi:.2e} W/cm²")
print(f"γ=1   →  I = {I_border:.2e} W/cm²")
print(f"γ=0.25→  I = {I_tunnel:.2e} W/cm²")
```

### Find the wavelength for a target γ at fixed intensity

```python
wl = AtomicUnits.wavelength_for_keldysh(gamma=1.0, intensity_Wcm2=1e14, Ip_au=0.5)
print(f"λ for γ=1 at 1e14 W/cm²: {wl:.0f} nm")   # ≈ 749 nm
```

---

## Ionization Potentials

GASFIR ships NIST first ionization potentials for all 118 elements:

```python
from gasfir import get_ionization_potential

get_ionization_potential("Ar")      # 0.5792 a.u. (15.76 eV)
get_ionization_potential("helium")  # 0.9036 a.u. — full names work too
get_ionization_potential("Diamond") # None — not an element
```

---

## Parameter Fitting & Retrieval

> **Requires** `pip install gasfir[retrieval]`
>
> Fitting functions live in `gasfir.fitting` and `gasfir.retrieval` — **not**
> in the top-level `gasfir` namespace.
>
> Results are saved to `<medium_name>/` by default (JSON, HDF5 chain, corner
> plot, trace plot, LaTeX summary).

### Known medium — full pipeline

```python
import pandas as pd
from gasfir import create_pulse
from gasfir.retrieval import RetrievalConfig, retrieve

data = pd.DataFrame({
    "pulses": [create_pulse(800, I, 0, 6) for I in intensities],
    "Y":      measured_probabilities,
})

# output saved to ./Hydrogen_SFA/
cfg = RetrievalConfig(medium_name="Hydrogen_SFA")
result = retrieve(data_NA=data, config=cfg)

for name in result.var_names:
    v = result.params[name]
    print(f"  {name} = {v.value:.4g} ± {v.stderr:.2g}")
```

After the LS phase the pipeline automatically compares fitted values against the
stored reference and prints a Nσ table. After emcee it saves:

* `Hydrogen_SFA_publication_corner.pdf` — posterior corner plot
* `Hydrogen_SFA_trace.pdf` — walker trace
* `Hydrogen_SFA_fit_summary.tex` — LaTeX parameter table + correlation matrix
* `Hydrogen_SFA_retrieval_result.json` — full result for reload

### Cold start — atom (E_g from NIST)

For elements not yet in the GASFIR parameter store, E_g is looked up
automatically from the NIST database; no initial guess is required:

```python
cfg = RetrievalConfig(
    medium_name="Kr",        # krypton — E_g auto-resolved: 0.5145 a.u.
    cma_maxiter=5000,        # broader search for an unknown system
)
result = retrieve(data_NA=data, config=cfg)
```

### Cold start — crystal (E_g must be supplied)

For crystals, the gamma-point band gap from DFT is unreliable for ultrafast
dynamics — E_g must always be provided explicitly:

```python
cfg = RetrievalConfig(
    medium_name="MyMaterial",
    initial_params={"E_g": 0.30},   # a.u. — only required field
    is_crystal=True,
    ret_electron_density=True,
    cma_maxiter=5000,
)
result = retrieve(data_NA=data, config=cfg)
```

Physical defaults are used for all other parameters; the cold-start notice
prints the energy scale and source so you always know what was assumed.

### Post-processing an existing MCMC chain

```python
from gasfir.retrieval import post_process_mcmc

# Drop a parameter, add a derived one, re-snap and re-plot
post_process_mcmc(
    medium_name="Diamond_TBmBJ",
    output_dir="Diamond_TBmBJ",
    original_stored_params={"E_g": 0.235, "a1": 14, ...},
    drop_vars=["a0"],
    derived_exprs={"a0_per_au3": f"a0 / {6.74**3}"},
    latex_mapping={"E_g": r"$E_g$ [a.u.]"},
    is_crystal=True,
)
```

### Low-level: residual function + manual lmfit

```python
import lmfit
from gasfir.fitting import ret_residual_function

residual = ret_residual_function(data, uncertainty_Nadiabatic=0.05)

p = lmfit.Parameters()
p.add("E_g", value=0.5, vary=False)
p.add("a0",  value=5.0, min=0.01)
p.add("a1",  value=3.5, min=0.1)
p.add("a2",  value=2.0, min=0.0)
p.add("a3",  value=1.0)
p.add("a4",  value=0.0, vary=False)

result = lmfit.minimize(residual, p, method="least_squares")
lmfit.report_fit(result)
```

---

## Development

### First-time contributor setup

Run once after cloning — this gets you a working environment **and** the git
hooks (auto-format on commit, test reminder on push):

```bash
git clone https://gitlab.mpcdf.mpg.de/gaf/gasfir.git
cd gasfir
conda env create -f environment.yml
conda activate gasfir
pip install -e ".[retrieval,dev,docs]"
pre-commit install --hook-type pre-commit --hook-type pre-push
```

(Minimal variant if you only need the dev tools: `pip install -e ".[dev]"`
then the same `pre-commit install` line.)

### Running tests

```bash
pytest tests/                                            # all tests
pytest tests/ --cov=src/gasfir --cov-report=html        # with coverage
pytest tests/test_gasfir.py -v                          # one file, verbose
pytest tests/test_integration.py::TestPerformanceIntegration  # performance only
```

Test pulses use physically motivated parameters defined in `tests/physics.py`
(hydrogen Ip = 0.5 a.u., 10-photon wavelength ≈ 911 nm, intensities at
γ = 0.25 / 1.0 / 4.0, 1 optical cycle).

### Code quality

```bash
black src/ tests/         # format
isort src/ tests/         # sort imports
flake8 src/ tests/        # lint
mypy src/                 # type-check
```

Linter versions are pinned in the `[dev]` extras so local and CI formatting
always agree.

### Pre-commit hooks (recommended)

Install the git hooks once per clone so formatting is fixed automatically and
you're reminded to run tests before pushing:

```bash
pre-commit install --hook-type pre-commit --hook-type pre-push
```

- **on `git commit`** — black + isort auto-format changed files and flake8
  lints them (a clean commit is guaranteed to pass the CI lint stage);
- **on `git push`** — a reminder to run the local checks before CI does.

### Building documentation

```bash
cd docs && make html
cd docs && make livehtml   # live reload
```

---

## Versioning and Releases

The version is defined **only** in `pyproject.toml`; `__init__.__version__`
reads it at runtime via `importlib.metadata`.

### Tagging a release

```bash
# 1. Bump version in pyproject.toml
# 2. Update CHANGELOG.md
# 3. Commit: git commit -m "chore: release vX.Y.Z"
# 4. Tag and push:
git tag vX.Y.Z
git push origin vX.Y.Z
```

### CI/CD pipeline (GitLab)

| Trigger | Stage | Action |
|---|---|---|
| Every branch / MR | test | lint, type-check, pytest (Python 3.10–3.12) |
| `main` push | build | wheel + sdist, Sphinx docs |
| `main` push | publish | **TestPyPI** (automatic) |
| `vX.Y.Z-alpha/beta/rc` tag | publish | **TestPyPI** (automatic) |
| `vX.Y.Z` stable tag | publish | **PyPI** (manual approval) |

Install from TestPyPI to verify before the production release:

```bash
pip install --index-url https://test.pypi.org/simple/ \
            --extra-index-url https://pypi.org/simple/ \
            gasfir==X.Y.Z
```

---

## Contributing

1. Fork and create a branch: `git checkout -b feature/my-feature`
2. Follow the code style (Black + isort + flake8 + mypy, Google-style docstrings)
3. Add tests; all new public functions need type annotations and a docstring
4. Open a merge request against `main`

Commit messages follow [Conventional Commits](https://www.conventionalcommits.org/):
`feat:`, `fix:`, `docs:`, `refactor:`, `test:`, `chore:`

---

## License

MIT — see [LICENSE](LICENSE).

## Citation

If you use GASFIR in your research, please cite the paper:

```bibtex
@article{Agarwal2026gasfir,
  title     = {General approximator for strong-field ionization rates},
  author    = {Agarwal, Manoram and Scrinzi, Armin and Yakovlev, Vladislav S.},
  journal   = {Physical Review A},
  volume    = {113},
  pages     = {L021101},
  year      = {2026},
  publisher = {American Physical Society},
  doi       = {10.1103/vxgm-kdtt},
  url       = {https://doi.org/10.1103/vxgm-kdtt}
}
```

## Support

- Documentation: <https://gasfir.readthedocs.io>
- Issues: <https://gitlab.mpcdf.mpg.de/gaf/gasfir/issues>
- Email: manoram.agarwal@mpq.mpg.de

## Acknowledgments

Developed at the Max Planck Institute for Quantum Optics (MPQ).

See the [CHANGELOG](https://gitlab.mpcdf.mpg.de/gaf/gasfir/-/blob/main/CHANGELOG.md) for a full history of changes.
