# IOP: fine-grained options

The public signatures (`chk.new`, `scf`, `ints`, …) stay focused on the common controls. The rare, low-level
**numerical knobs** are collected into a single **`iop={...}`** dictionary — a cross-cutting channel inspired
by Gaussian's `IOp`, but with readable namespaced names and typed values instead of numeric IDs.

## Setting IOP keys

Pass `iop=` on `qc.chk.new(...)`, or overlay per calculation with `mychk.with_iop({...})` (which returns a new
checkpoint):

```python
import qc
m = qc.chk.new(
    atom="O 0 0 0; H 0 0.76 0.59; H 0 -0.76 0.59",
    ao="def2-svp",
    iop={"ri.metric_cutoff": 1e-10, "integral.screen.schwarz_tol": 1e-12},
)

m2 = m.with_iop({"scf.conv_tol_grad": 1e-7})   # overlay, returns a new checkpoint
m2.scf(ref="r").run()
```

Keys are dotted **`<area>.<subsystem>.<knob>`** names validated against a central registry. **Unknown keys
are an error** (with a suggestion), not silently ignored:

```python
m.with_iop({"not.a.real.key": 1})
# ValueError: unknown iop key "not.a.real.key"
```

## Discovering keys: `qc.iop`

The registry is introspectable — you never have to guess a key or its default:

```python
qc.iop.list()                       # all 47 keys (a set of dotted names)
qc.iop.defaults()                   # {key: default_value} for every key
qc.iop.describe("scf.conv_tol_grad")
# {'type': 'float', 'default': 1e-06, 'min': 0.0, 'max': 1.0,
#  'consumer': 'scf', 'scientific': False,
#  'doc': 'Commutator-RMS convergence threshold (the SCF gradient tolerance).'}
```

`describe(key)` returns the value type, default, allowed range, which subsystem consumes it, and a
one-line doc.

## The key registry by area

There are **47 keys** in **9 areas**. The most commonly reached-for ones:

### `scf.*` — SCF solver

| key | meaning |
|---|---|
| `scf.conv_tol_grad` | commutator-RMS gradient convergence threshold (default `1e-6`) |
| `scf.diis.variant` / `.max_vectors` / `.start_cycle` | DIIS flavor, subspace size, first cycle |
| `scf.incremental` / `.incremental.reset` | incremental Fock build on/off, reset period |
| `scf.qc_start` / `scf.soscf_start` | when the 2nd-order ladder / SOSCF engages |
| `scf.qc.max_conventional` / `.max_nr` / `.full_linear` | QC-family ladder controls |
| `scf.trah.max_step`, `scf.yqc.stabilize_tol` | TRAH / YQC tuning |
| `scf.symmetrize_fock`, `scf.salc_cutoff`, `scf.orbital_ordering`, `scf.orthogonalization_cutoff` | symmetry / orthogonalization |
| `scf.adaptive_screening` | adaptive integral screening in the SCF |

### `integral.*` — integral assembly & screening

| key | meaning |
|---|---|
| `integral.screen.schwarz_tol` | Cauchy–Schwarz quartet screening tolerance (default `1e-12`, loss-free) |
| `integral.screen.density_tol` / `.nuclear_tol` / `.qqr` / `.qqr_extent_tau` | density / nuclear / QQR screening |
| `integral.precision` | integral precision target |
| `integral.disk.sparse_layout` / `.io_mode` / `.restart` | out-of-core (`4c-disk`) layout, I/O, restart |
| `integral.screen.disk_block_tol` / `.disk_int_tol` | disk-backend drop tolerances |
| `integral.incore.*` (`k_kernel`, `rank_balance`, `s4_max_gb`, `screened_fill`) | incore-4c tuning |

### `ri.*` — RI / density fitting

| key | meaning |
|---|---|
| `ri.metric_cutoff` | RI metric eigenvalue cutoff (default `1e-10`) |
| `ri.ram.kdist` | `ri-ram` store-`B` K distribution (`p-transpose` / `mu-stream`) |
| `ri.recomp.occ_batch` / `.kcomm` / `.resort_max_mb` | `ri-recomp` occ-batching, exchange comm, resort budget |
| `ri.store.sparse` | sparse store-`B` |

### `lct.*` — correlation (RI-MP2)

| key | meaning |
|---|---|
| `lct.frozen_core` | freeze core orbitals in the correlation sum |
| `lct.rohf_mp2` | ROHF-MP2 convention (`"pyscf"` / `"qcrs"`) |
| `lct.rimp2_fp32` | single-precision RI-MP2 |
| `lct.laplace_points` | Laplace quadrature points (SOS-MP2) |

### Other areas

| key | meaning |
|---|---|
| `guess.gwh.scale` | the GWH guess scaling factor |
| `pcm.discretization` | PCM cavity discretization |
| `parallel.strict` | oversubscription policy (`True` = error, `False` = warn) |
| `log.timing` | emit timing spans |

For the exact default, type, and range of any key, call `qc.iop.describe(key)`. The registry is the single
source of truth (`crates/qc-workflow/src/iop.rs`); this list mirrors it.

## Which controls are IOP vs public kwargs

Some knobs migrated from public keyword arguments to IOP keys to keep the main signatures focused — e.g.
`scf.conv_tol_grad`, `scf.symmetrize_fock`, `scf.salc_cutoff`, `scf.orbital_ordering`,
`integral.screen.nuclear_tol`, `integral.precision` are IOP keys, read from the checkpoint `iop` by
`scf(...)` / `ints(...)`. The everyday controls (`ref`, `xc`, `grid`, `conv_tol`, `algorithm`, `pcm`, …) stay
as direct keyword arguments (the [SCF chapter](../20-guide/scf.md)).
