# JSON Output Reference

pdb2reaction provides machine-readable JSON output for programmatic consumption by AI agents, scripts, and downstream tools.

## `--out-json` flag

Every MLIP-based subcommand supports `--out-json / --no-out-json` (default: off).
When enabled, a `result.json` file is written to the output directory alongside the normal outputs.

```bash
pdb2reaction opt -i r.pdb -q -1 --out-json --out-dir result_opt
cat result_opt/result.json | python -m json.tool
```

The `all` and `path-search` commands always write `summary.json` (no `--out-json` flag needed).

## Common envelope

Every `result.json` automatically includes:

| Field | Type | Description |
|-------|------|-------------|
| `command` | string | Subcommand name (e.g. `"opt"`) |
| `pdb2reaction_version` | string | Package version |
| `elapsed_seconds` | float | Wall-clock time (seconds) |
| `environment` | object | Hardware info (see below) |

**`environment`**:

| Field | Type | Example |
|-------|------|---------|
| `device` | string | `"cuda"` or `"cpu"` |
| `gpu_name` | string | `"NVIDIA GeForce RTX 5080"` |
| `gpu_vram_gb` | float | `16.6` |
| `cuda_version` | string | `"12.9"` |
| `cpu` | string | `"AMD Ryzen 9 7950X 16-Core Processor"` |
| `n_cpus` | int | `32` |
| `ram_gb` | float | `133.7` |

## Error handling

If a job fails (e.g., crash, OOM, convergence failure leading to `sys.exit`), **no `result.json` is written**. The absence of `result.json` indicates failure. Check the `.out` log file for error details.

For jobs that complete but did not converge, `result.json` is written with `"status": "not_converged"` and the final force/step values, allowing an AI agent to decide whether to retry with more cycles.

## Subcommand schemas

### `opt`

| Field | Type | Description |
|-------|------|-------------|
| `status` | string | `"converged"` or `"not_converged"` |
| `energy_hartree` | float | Final energy (Hartree) |
| `n_opt_cycles` | int | Optimization cycles completed |
| `opt_mode` | string | `"grad"` or `"hess"` |
| `backend` | string | MLIP backend (`"uma"`, `"orb"`, `"mace"`, `"aimnet2"`) |
| `charge` | int | System charge |
| `spin` | int | Spin multiplicity |
| `model` | string | MLIP model identifier |
| `n_atoms` | int | Total atoms |
| `n_freeze_atoms` | int | Frozen atoms |
| `solvent` | string | Implicit solvent or `"none"` |
| `thresh` | string | Convergence threshold preset |
| `max_cycles` | int | Maximum allowed cycles |
| `input_file` | string | Input filename |
| `final_max_force` | float | Last max gradient (Hartree/Bohr) |
| `final_rms_force` | float | Last RMS gradient |
| `final_max_step` | float | Last max displacement (Bohr) |
| `final_rms_step` | float | Last RMS displacement |
| `convergence_thresholds` | object | `{max_force_thresh, rms_force_thresh, max_step_thresh, rms_step_thresh}` (Hartree/Bohr) |
| `files` | object | Output file map |

### `tsopt`

All fields from `opt`, plus:

| Field | Type | Description |
|-------|------|-------------|
| `n_imaginary_modes` | int | Number of imaginary frequencies |
| `imaginary_frequencies_cm` | float[] | Imaginary frequencies (cm⁻¹, negative) |
| `opt_mode` | string | `"rsirfo"` or `"dimer"` |

The `files` object may include `imaginary_mode_files` (list of vib file paths).
Convergence details are available for rsirfo mode; dimer mode provides `n_opt_cycles` only.

### `freq`

| Field | Type | Description |
|-------|------|-------------|
| `status` | string | `"completed"` |
| `n_modes` | int | Total normal modes |
| `n_imaginary` | int | Imaginary frequency count |
| `frequencies_cm` | float[] | All frequencies (cm⁻¹) |
| `imaginary_frequencies_cm` | float[] | Negative frequencies only |
| `thermochemistry` | object\|null | Thermodynamic data (see below) |
| `backend` | string | MLIP backend |
| `charge` | int | System charge |
| `spin` | int | Spin multiplicity |
| `model` | string | Model identifier |
| `n_atoms` | int | Total atoms |
| `n_freeze_atoms` | int | Frozen atoms |
| `temperature_K` | float | Temperature (K) |
| `pressure_atm` | float | Pressure (atm) |
| `files` | object | `{"frequencies_txt": "frequencies_cm-1.txt"}` |

**`thermochemistry`** (null if thermoanalysis unavailable):

| Field | Type | Unit |
|-------|------|------|
| `electronic_energy_ha` | float | Hartree |
| `zpe_correction_ha` | float | Hartree |
| `thermal_correction_energy_ha` | float | Hartree |
| `thermal_correction_enthalpy_ha` | float | Hartree |
| `thermal_correction_free_energy_ha` | float | Hartree |
| `sum_EE_and_ZPE_ha` | float | Hartree |
| `sum_EE_and_thermal_energy_ha` | float | Hartree |
| `sum_EE_and_thermal_enthalpy_ha` | float | Hartree |
| `sum_EE_and_thermal_free_energy_ha` | float | Hartree |
| `E_thermal_cal_per_mol` | float | cal/mol |
| `Cv_cal_per_mol_K` | float | cal/(mol K) |
| `S_cal_per_mol_K` | float | cal/(mol K) |

### `irc`

| Field | Type | Description |
|-------|------|-------------|
| `status` | string | `"completed"` |
| `n_frames_forward` | int | Forward IRC frames |
| `n_frames_backward` | int | Backward IRC frames |
| `n_frames_total` | int | Total frames |
| `energy_reactant_hartree` | float | Reactant energy |
| `energy_ts_hartree` | float | TS energy |
| `energy_product_hartree` | float | Product energy |
| `forward_converged` | bool | Forward IRC converged? |
| `backward_converged` | bool | Backward IRC converged? |
| `backend` | string | MLIP backend |
| `charge` | int | System charge |
| `spin` | int | Spin multiplicity |
| `bond_changes` | object | `{formed: [...], broken: [...]}` |
| `step_length` | float | IRC step length (Bohr) |
| `max_cycles` | int | Maximum IRC steps |
| `files` | object | Trajectory files (xyz + pdb) |

### `scan`

| Field | Type | Description |
|-------|------|-------------|
| `status` | string | `"completed"` |
| `n_stages` | int | Number of scan stages |
| `stages` | object[] | Per-stage data (see below) |
| `backend` | string | MLIP backend |
| `charge` | int | System charge |
| `spin` | int | Spin multiplicity |
| `files` | object | Output files |

**`stages[]`**:

| Field | Type | Description |
|-------|------|-------------|
| `n_steps` | int | Steps in this stage |
| `converged` | bool | Constrained optimization converged? |
| `pairs_1based` | list | Atom pairs (1-based) |
| `initial_distances_angstrom` | list | Starting distances |
| `target_distances_angstrom` | list | Target distances |
| `final_energy_hartree` | float | Energy at last step |
| `energies_hartree` | float[] | Per-step energies |
| `bond_changes` | object | Detected bond changes |

### `scan2d` / `scan3d`

| Field | Type | Description |
|-------|------|-------------|
| `status` | string | `"completed"` |
| `n_grid_points` | int | Total grid points |
| `grid_shape` | int[] | Grid dimensions |
| `pair1`, `pair2` (,`pair3`) | object | `{i, j, low, high}` with optional `label_i`, `label_j` |
| `min_energy_hartree` | float | Surface minimum energy |
| `backend` | string | MLIP backend |
| `charge` | int | System charge |
| `spin` | int | Spin multiplicity |
| `files` | object | CSV + plot files |

### `path-opt`

| Field | Type | Description |
|-------|------|-------------|
| `status` | string | `"converged"` / `"not_converged"` / `"completed"` |
| `converged` | bool | Convergence flag |
| `mep_mode` | string | `"dmf"` or `"gsm"` |
| `backend` | string | MLIP backend |
| `image_energies_hartree` | float[] | All image energies |
| `n_images` | int | Image count |
| `hei_index` | int | Highest-energy image index |
| `barrier_kcal` | float | Forward barrier (kcal/mol) |
| `delta_kcal` | float | Reaction energy (kcal/mol) |
| `files` | object | Trajectory + HEI files |

### `dft`

| Field | Type | Description |
|-------|------|-------------|
| `converged` | bool | SCF converged? |
| `energy_hartree` | float | DFT energy |
| `xc_functional` | string | XC functional |
| `basis_set` | string | Basis set |
| `used_gpu` | bool | GPU acceleration used? |
| `charges` | object | `{mulliken, lowdin, iao}` per-atom arrays |
| `spin_densities` | object | `{mulliken, lowdin, iao}` per-atom arrays |
| `n_atoms` | int | Atom count |
| `grid_level` | int | DFT grid level |
| `conv_tol` | float | SCF convergence tolerance |
| `files` | object | `{"result_yaml": "result.yaml"}` |

### `extract`

| Field | Type | Description |
|-------|------|-------------|
| `status` | string | `"ok"` |
| `n_atoms_raw` | int | Atoms in input PDB |
| `n_atoms_extracted` | int | Atoms after extraction |
| `total_charge` | float | Computed total charge |
| `protein_charge` | float | Protein charge |
| `ligand_total_charge` | float | Ligand charge sum |
| `ion_total_charge` | float | Ion charge sum |
| `ion_charges` | list | `[[name, charge], ...]` |
| `unknown_residue_charges` | object | `{resname: charge}` |
| `center` | string | Center residue |
| `radius` | float | Extraction radius (angstrom) |
| `input_files` | string[] | Input PDB paths |

### `trj2fig`

| Field | Type | Description |
|-------|------|-------------|
| `status` | string | `"ok"` |
| `n_frames` | int | Number of trajectory frames |
| `min_energy_hartree` | float | Minimum energy across frames |
| `max_energy_hartree` | float | Maximum energy across frames |
| `backend` | string | MLIP backend |
| `files` | object | Output plot files |

### `energy-diagram`

| Field | Type | Description |
|-------|------|-------------|
| `status` | string | `"ok"` |
| `n_points` | int | Number of energy data points |
| `files` | object | Output diagram files |

### `bond-summary`

When `--out-json` is enabled, `bond-summary` prints JSON to **stdout** (not a file):

| Field | Type | Description |
|-------|------|-------------|
| `status` | string | `"ok"` |
| `comparisons` | object[] | Per-pair comparison with `structure_a`, `structure_b`, `bonds_formed`, `bonds_broken` |

## `summary.json` (`path-search` / `all`)

The `all` and `path-search` commands write `summary.json` with a richer structure:

| Field | Type | Description |
|-------|------|-------------|
| `status` | string | `"success"` / `"partial"` |
| `n_segments` | int | Segment count |
| `segments` | object[] | Per-segment barrier, delta, bond changes |
| `energy_diagrams` | object[] | Energy profiles with labels and kcal/mol values |
| `mlip_backend` | string | Model identifier |
| `charge` | int | System charge |
| `spin` | int | Spin multiplicity |
| `environment` | object | Hardware info |

The `all` command additionally includes:

| Field | Type | Description |
|-------|------|-------------|
| `rate_limiting_step` | object | RLS segment index and barrier |
| `overall_reaction_energy_kcal` | float | Overall reaction energy |
| `post_segments` | list | Per-segment TS/IRC/freq/DFT results |
| `key_output_files` | object | Curated output file listing |

## Usage examples

### Python

```python
import json

with open("result_opt/result.json") as f:
    result = json.load(f)

if result["status"] == "converged":
    print(f"Energy: {result['energy_hartree']:.6f} Hartree")
else:
    print(f"Not converged after {result['n_opt_cycles']} cycles")
    print(f"Max force: {result['final_max_force']:.6f}")
```

### jq

```bash
# Check convergence
jq '.status' result.json

# Get barrier from path-opt
jq '.barrier_kcal' result.json

# List imaginary frequencies from tsopt
jq '.imaginary_frequencies_cm' result.json

# Get thermochemistry from freq
jq '.thermochemistry.sum_EE_and_thermal_free_energy_ha' result.json
```
