# Core concepts: the checkpoint & workflow model

In the [Quickstart](quickstart.md) you wrote `qc.chk.new(...).scf(ref="r").run()` and read
`mychk.scf.energy`. That one line already used every big idea in qc-rs. This chapter slows right down and
explains the model in detail, because once it clicks, the *whole* toolkit becomes predictable — every
feature is just another step you add or another result you read.

## The checkpoint

Everything revolves around one object, the **checkpoint**. Think of it as a small laboratory notebook for
one molecule. It holds three things:

1. **The molecule** — the geometry, basis set, charge, and spin you gave to `qc.chk.new(...)`. This is
   fixed input.
2. **The current electronic state** — the result of the *latest* step that produced electrons: the
   **molecular orbitals** (`current_mo`) and the **electron density** (`current_density`). A guess, an SCF,
   or a CASSCF all update this. Properties read *this* state.
3. **Result records** — the outcomes of the steps you have run: the SCF energy and whether it converged,
   computed properties, an optimization trajectory, and so on.

A freshly created checkpoint has a molecule but **no electronic state yet** — nothing has been computed.

## Two phases: *build*, then *run*

A qc-rs calculation happens in two clearly separated phases. This separation is the single most important
thing to understand.

### 1. Build — add pending steps

Calls like `qc.scf(mychk, ...)` (or the method form `mychk.scf(...)`) do **not** compute anything. They
return a *new* checkpoint carrying a **pending step** — a note that says "an SCF is wanted here." The
original checkpoint is untouched (checkpoints are effectively immutable — a step gives you a new one):

```python
base    = qc.chk.new(atom="O 0 0 0.1173; H 0 0.7572 -0.4692; H 0 -0.7572 -0.4692",
                     ao="cc-pvdz", unit="angstrom")
pending = base.scf(ref="r")     # a NEW checkpoint with a pending SCF step

pending is base          # False — base is unchanged
pending.scf.energy       # None — nothing has run yet
```

Because building is cheap and side-effect-free, you can assemble a multi-step calculation freely — chain
several steps, branch, or pass a checkpoint into a function — before anything runs.

### 2. Run — materialize the results

`.run()` actually does the work. Afterwards the result accessors return real values:

```python
done = pending.run()
done.scf.energy          # -76.026772
done.scf.converged       # True
```

:::{important} Pending step vs. result accessor
The same name means different things before and after `.run()`. **`mychk.scf(...)` adds** a step (it takes
arguments and returns a new checkpoint); **`mychk.scf.energy` reads** the finished step's result (and is
`None` until you run). Keeping "asking for work" separate from "reading the answer" is exactly what lets a
calculation be composed freely and re-run safely.
:::

## What `.run()` really does

The pending steps form a small **dependency graph** — a DAG (directed acyclic graph). When you call
`.run()`, qc-rs performs a sequence of well-defined actions:

1. **Resolve dependencies.** It works out what each step needs. An SCF needs a *starting guess* for the
   orbitals and the *one-electron integrals*; a property needs a converged density; and so on.
2. **Auto-insert sensible defaults.** For any dependency you did not request explicitly, qc-rs inserts a
   safe default. In particular, if an SCF has no starting state it auto-inserts the **`sad` guess**
   (superposition of atomic densities), and it inserts a default **integrals** step. This is why
   `mychk.scf(ref="r").run()` works on its own — you almost never write `guess(...)` or `ints(...)` by
   hand.
3. **Order and execute.** It topologically orders the graph, runs each step in turn, and **records** the
   result on the checkpoint.
4. **Skip what is already done.** Any result that is still valid is **reused, not recomputed**; only
   missing or *stale* steps run.
5. **Prune.** Completed pending nodes are cleared, so the checkpoint is ready to accept more steps.

Two consequences are worth committing to memory:

- **`.run()` is safe to call again.** Valid results are reused, so re-running never repeats finished work.
  This is also how a **restart** works after an interruption: load the checkpoint and `.run()` continues
  from where it stopped.
- **You specify only what you care about.** The guess and integrals are filled in for you. If you *do* want
  control — a different guess, a specific integral strategy — you add those steps explicitly, and `.run()`
  uses them instead of the defaults.

:::{note} Staleness
Results carry a notion of *generation*. If you change something a step depends on, downstream results are
marked **stale** and recomputed on the next `.run()`, while everything unaffected is kept. You get
correctness (nothing outdated survives) without paying to redo unaffected work.
:::

## Two ways to write the same thing

Every workflow verb has a **functional** form and a **method-chain** form. They are equivalent — pick
whichever reads better in context:

```python
# functional                      # method-chain
qc.scf(mychk, ref="r").run()      mychk.scf(ref="r").run()
```

The method-chain form shines in one-liners; the functional form can read more clearly when a checkpoint
flows through a helper function.

## Reading results, and computing properties

After `.run()`, results live on named accessors that mirror the step:

```python
done.scf.energy       # the total energy
done.scf.converged    # did the SCF converge?
```

Analysis is different: it lives in the `qc.prop.<group>.<leaf>` namespace and is computed **lazily** the
first time you ask, then cached. It reads the checkpoint's *current electronic state*:

```python
qc.prop.chrg.mulliken(done)       # {'charges': [...], 'atom_labels': [...]}
done.prop.chrg.mulliken()         # the same thing, method-chain form
```

There are fourteen property groups (charges, bond orders, aromaticity, orbitals, QTAIM, ELF, multipoles,
spin, conceptual DFT, ESP, real-space fields, spectra, geometry, …) — the whole suite is
[Molecular properties](../20-guide/properties/index.md). You can also compute a bundle eagerly while
running, with `scf(prop=...)`.

## Saving and loading

A checkpoint — the molecule, the current electronic state, and every result — can be written to disk and
restored later, with **no recomputation**:

```python
done.save("water")                # persist the checkpoint to disk (an HDF5 .qch5 file)
later = qc.chk.load("water")      # restore it, e.g. in a fresh session
later.scf.energy                  # -76.026772 — read straight back, nothing recomputed
```

Because a loaded checkpoint carries its results, you can also add *more* steps to it and `.run()` — the
existing results are reused, and only the new work runs.

## Why this design?

Separating *build* from *run*, and pending steps from results, buys three things a beginner comes to
appreciate:

- **Composability** — a calculation is a short pipeline you extend one step at a time; building is cheap and
  free of surprises.
- **Restartability** — `.run()` reuses valid results, so long jobs resume instead of restarting, and a
  saved checkpoint is a resumable snapshot.
- **Provenance** — the checkpoint records which electronic state produced which result, so an analysis is
  never ambiguous about *what* it was computed from.

With this model in hand, the rest of the manual is essentially a catalogue: **steps** you can add (guess,
ints, scf, opt, lct, td, …) and **results** you can read (energies, gradients, and the property suite).
Next, get the theory behind them in [Part II — Foundations](../10-foundations/index.md), or dive into the
[User guide](../20-guide/molecular-input.md).
