# Logging & output

Every run in this guide has quietly produced a **transcript** — the system summary, the SCF cycle table, the
convergence check. This chapter is about *seeing, replaying, and saving* that output: how to stream it live,
render it after the fact, get it as machine-readable data, and persist a whole calculation to disk.

## One event stream behind everything

qc-rs's logging and display share a **single structured event stream**. As a run proceeds it emits typed
events — a *System* summary, a *plan*, per-cycle SCF records, a *result* — and every way of viewing output is
just a different rendering of that one stream. That is why the live log, the replayed transcript, and the raw
JSON all show the *same* information: they are the same events, formatted differently. Because it is
structured (not free-text `print`s), you can render it as a human transcript **or** consume it as data.

## Live logging: `run(log=...)`

`.run()` is **silent by default** — it computes and returns. Pass **`log=`** to stream the transcript as the
run happens (introduced in the [quickstart](../00-intro/quickstart.md)):

```python
import qc
water = "O 0 0 0.117; H 0 0.757 -0.469; H 0 -0.757 -0.469"

m = qc.chk.new(atom=water, ao="cc-pvdz", unit="angstrom").scf(ref="r").run(log="stdout")
```

The `log=` target can be:

| `log=` | effect |
|---|---|
| `"stdout"` | stream to the terminal / notebook cell as the run proceeds |
| a **file path** | write the rendered transcript to that file |
| a **file-like object** | write to any stream you pass |

Two more controls shape the live output:

- **`log_style="modern"`** (default) or **`"orca"`** — the visual style of the transcript (a compact modern
  layout, or one reminiscent of ORCA output).
- **`plot=True`** — draw the SCF convergence plot inline as it runs (needs matplotlib; see
  [visualization](visualization.md)).

:::{tip} MPI runs
On a parallel run, `log_rank="root"` (default) logs only rank 0; `"all"`, `"gather"`, or a list of ranks
control which ranks report. This keeps a many-rank run from flooding the output. Parallelism itself is
[Part IV](../30-hpc/index.md).
:::

## Replaying a finished run: `log()` and `show()`

The transcript is **stored on the checkpoint**, so you can render it *after* the run without recomputing:

```python
m.log()                    # replay the transcript as text
m.log(format="markdown")   # ... as Markdown (nice in a notebook)
m.log(format="jsonl")      # ... as one JSON object per line
m.show("result")           # a rendered snapshot of the current state + results
```

- **`log(format=...)`** re-renders the stored event stream — `"text"` (default), `"markdown"`, or `"jsonl"`.
- **`show(...)`** renders a *snapshot* (state and results) rather than the running transcript — useful for a
  quick "what does this checkpoint hold right now" view.

Because these read the *stored* events, they cost nothing to call and give the identical transcript every
time — no need to re-run with `log="stdout"` just to see the output again.

## Output as data: `run_events()`

For programmatic use — testing, dashboards, harvesting numbers — **`run_events()`** returns the raw event
stream as a list of **JSON strings**, one per event:

```python
import json
events = m.run_events()             # list[str], each a JSON object
len(events)                         # 18

first = json.loads(events[0])       # the System summary event
first["event"]                      # e.g. "system"
first["nao"], first["nuclear_repulsion"]   # structured fields, ready to use
[json.loads(e).get("kind") for e in events]   # e.g. the auto-inserted 'sad' guess shows up here
```

Each event is a dict with an **`event`** field naming its type and the structured payload for that type
(atom list, basis size, per-cycle energies, …). This is the stream the human transcript is rendered *from*,
so nothing shown in the log is hidden from the data.

## Saving and loading a calculation

A checkpoint — molecule, current electronic state, results, and transcript — persists to an **HDF5 `.qch5`**
file, and reloads into a checkpoint you can query or extend:

```python
m.save("water.qch5")                 # persist the whole checkpoint
r = qc.chk.load("water.qch5")        # restore it

r.scf.energy                         # -76.026794   results survive the round-trip
r.scf(xc="b3lyp").run()              # ... and you can add more steps to the loaded checkpoint
```

`save()`/`load()` are how you **stop and resume** work: run an expensive SCF once, save it, and later load it
to compute properties, optimize, or restart — no recomputation. (This is also the file behind
`guess("read", source=...)` from the [initial-guess chapter](initial-guess.md).)

## Worked example: run quietly, inspect afterwards

```python
import qc, json
water = "O 0 0 0.117; H 0 0.757 -0.469; H 0 -0.757 -0.469"

m = qc.chk.new(atom=water, ao="cc-pvdz", unit="angstrom").scf(ref="r").run()   # silent

m.log()                              # ... now render the transcript
n_events = len(m.run_events())       # 18 structured events available as data
m.save("water.qch5")                 # keep it for next time
print("events:", n_events, "| E:", round(m.scf.energy, 6))
```

:::{exercise}
:label: ex-logging

1. You ran `m = chk.scf(ref="r").run()` (no `log=`) and now want to see the SCF cycle table without paying
   for another SCF. What do you call?
2. You need the per-cycle energies in a Python script to make your own plot. Which method gives you the data,
   and in what form?
3. An expensive optimization finished. How do you make sure you never have to repeat it, and how would you
   later reuse its converged orbitals as the starting guess for a bigger-basis run?
:::

:::{solution} ex-logging
:class: dropdown

1. **`m.log()`** — it replays the transcript stored on the checkpoint, at no compute cost (`log(format=...)`
   for Markdown/JSONL).
2. **`m.run_events()`** — a list of JSON strings; `json.loads` each and pull the per-cycle fields from the
   SCF-cycle events.
3. **`m.save("opt.qch5")`** persists it; later `qc.chk.load("opt.qch5")` restores it with results intact. To
   reuse its orbitals, `qc.chk.new(..., ao="<bigger>").guess("read", source="opt.qch5").scf(...).run()`.
:::

That completes the day-to-day workflow. The rest of Part III is the large
[molecular-properties suite](properties/index.md) — turning a converged wavefunction into charges, bond
orders, topological analysis, and the real-space fields you met in [visualization](visualization.md).
