# Partial Circuit Elaboration Protocol

Source: `circuit.h`, `cirparams.cpp` (`Circuit::elaborateChanges()`,
`Circuit::propagateDownHierarchy()`).

This is the protocol run to bring an **already-elaborated** `Circuit` back
into a consistent state after something changed — circuit variables,
instance/model parameters, a sweep step, simulator options, or analysis
parameters — without redoing the full [initial elaboration
protocol](protoelaborate.md). It is driven by a set of `CircuitFlags` sticky
bits (`VariablesChanged`, `HierarchyAffectingOptionsChanged`,
`ParametrizationAffectingOptionsChanged`, `MappingAffectingOptionsChanged`,
`TolerancesAffectingOptionsChanged`, `HierarchyParametersChanged`) that
record *what kind* of change happened since the last elaboration; each stage
of the protocol is skipped unless the flags relevant to it are set, so a
small change (e.g. one instance parameter) does much less work than a full
`elaborate()`.

There are three call shapes, all funnelling into the same core:
- `elaborateChanges(commons, sweeper, what, an, opt, optionsMap, devReq, s)`
  — the full form, called from inside an analysis during a sweep
  (`Analysis`/core code); `commons` and the analysis are already available.
- `elaborateChanges(optionsMap, devReq, s)` (`circuit.h:317-341`) — for the
  command interpreter's `options` command; builds a throwaway `CommonData`,
  applies `optionsMap` to a clean default `IStruct<SimulatorOptions>`, and
  forwards to the full form with no sweeper/analysis.
- `elaborateChanges(devReq, s)` (`circuit.h:345-350`) — for API callers that
  just want the circuit's current variables/options/parameters applied;
  forwards to the previous overload with `optionsMap=nullptr`.

Any failure aborts immediately, returns `(false, false, false)`, and
sets/extends the `Status`; unlike `elaborate()`, a failed
`elaborateChanges()` does **not** reset the circuit to empty — it can be
left in a partially updated state, and `Flags::Elaborated` stays cleared
until a subsequent call succeeds.

## Protocol

1. Bump the `chgelab` accounting counter and start wall-clock timing; clear
   `Flags::Elaborated` — the circuit is provisionally "not elaborated" while
   changes are being applied.
2. If a `sweeper` was given, write the current sweep's variable values into
   the circuit (`sweeper->write(ParameterFamily::Variable, what, s)`); this
   may set `Flags::VariablesChanged` as a side effect of `setVariable()`.
3. Read `variablesChanged` from `Flags::VariablesChanged` — this reflects
   both step 2 and any direct `Circuit::setVariable()` calls made by the
   caller before invoking `elaborateChanges()`.
4. If there's a sweeper and variables changed, propagate the change to
   nested sweeps via `an->updateSweeper(s)` (inner sweep bounds/step values
   can themselves be expressions of outer sweep variables).
5. If variables changed: rebuild the global parameter-evaluation context
   with `updateGlobalContext()`, apply the expression-valued entries of
   `optionsMap` onto `optPtr` (`optPtr->setParameters(*optionsMap, ...,
   Write::Expressions, s)`), and, if an analysis was given, update the
   analysis's own expression-valued parameters via
   `an->updateParameterExpressions()`, recording `analysisParametersChanged`.
   `optPtr` (`cirparams.cpp:291`) is a local alias picking the write target:
   `optPtr = options ? options : &simOptions` — the caller-supplied options
   struct (the `opt` parameter of `elaborateChanges()`, named `options` in
   the `.cpp` definition) if one was given, otherwise the circuit's own
   persistent options member `simOptions` (what `circuit.simulatorOptions()`
   exposes) directly.
6. If there's a sweeper, write the current sweep's option values
   (`sweeper->write(ParameterFamily::Option, what, s)`) onto `optPtr`.
7. If a caller-supplied options struct (`options`, non-null) was given,
   write it into the circuit with `setOptions(*options)` — this is what
   actually diffs it against the circuit's live options and sets
   `HierarchyAffectingOptionsChanged` / `ParametrizationAffectingOptionsChanged`
   / `MappingAffectingOptionsChanged` / `TolerancesAffectingOptionsChanged`
   for whatever differs. Snapshot all four flags right after. When `options`
   is null, `optPtr` was `&simOptions` already, so steps 5–6 wrote straight
   into the circuit's live options and this step is skipped — there is
   nothing left to commit.
8. If there's a sweeper, write the current sweep's instance/model parameter
   values (`sweeper->write(ParameterFamily::Instance | ParameterFamily::Model, what, s)`);
   if anything changed, set `Flags::HierarchyParametersChanged`. Snapshot
   `hierarchyParametersChanged` — this also reflects any direct
   `Circuit::setInstanceParameter()`/`setModelParameter()` calls the caller
   made earlier.
9. If any of {`variablesChanged`, `hierarchyParametersChanged`,
   `hierarchyAffectingOptionsChanged`, `parametrizationAffectingOptionsChanged`}
   is set, run `propagateDownHierarchy()` — pushes new parameter values down
   the instance tree and detects whether any instance's subhierarchy needs
   to be rebuilt, reporting `hierarchyChanged` (see [Hierarchy
   propagation](#hierarchy-propagation) below).
10. If `hierarchyChanged`: rebuild entity lists (`buildEntityLists()`) and
    node ordering (`nodeOrdering()`) — the same calls as steps 7–8 of the
    initial elaboration protocol, run here only because the hierarchy
    actually changed. (Sweeper rebinding to the new/changed instances is
    handled separately, in `an.cpp`, not here.)
11. Refill `commons` from the circuit's current simulator options
    (`commons.fromOptions(simOptions.core())`) — `setup()` needs
    `gmin`/`gshunt` and friends.
12. If any of {`hierarchyChanged`, `variablesChanged`,
    `hierarchyParametersChanged`, `parametrizationAffectingOptionsChanged`,
    `mappingAffectingOptionsChanged`} is set, run a device setup pass,
    `setup(commons, forceFull=mappingAffectingOptionsChanged, devReq, s)`.
    Only a mapping-affecting options change forces every device to redo a
    *full* setup; otherwise each `Device::setup()` decides internally how
    much work is actually needed. Returns `unknownsChanged`/`sparsityChanged`.
13. If an analysis was given, call `an->preMapping()` so the analysis can
    report whether it needs extra sparsity map entries / state slots
    (`analysisNeedsSparsity`). Checking whether the analysis *core* needs a
    full rebuild is deliberately deferred to `an.cpp` — that check can be
    expensive (e.g. HB recomputing its frequency set and transforms) and
    isn't part of this protocol.
14. Compute `mapUnknownsNeeded = hierarchyChanged || unknownsChanged`; if
    true, run `mapUnknowns()` (same as initial-elaboration step 10).
15. Compute `buildNeeded = mapUnknownsNeeded || analysisNeedsSparsity`; if
    true, run `buildSparsityAndStates()` (same as initial-elaboration step
    11 — this is also where CCCS/CCVS and behavioral sources bind to their
    controlling flow nodes, see [protoelaborate.md](protoelaborate.md#binding-to-controlling-flow-nodes)).
16. If an analysis was given and `analysisNeedsSparsity`, call
    `an->populateStructures()` to let the analysis add its own sparsity map
    entries and state slots on top of the per-device ones.
17. If `buildNeeded`, run `enumerateSystem()` (same as initial-elaboration
    step 12).
18. If `unknownsChanged || tolerancesAffectingOptionsChanged`: rebuild
    static tolerances (`setStaticTolerances(commons, s)`) and re-enumerate
    natures (`commons.enumerateNatures(s)`).
19. Mark the circuit consistent: clear `VariablesChanged`,
    `HierarchyAffectingOptionsChanged`, `ParametrizationAffectingOptionsChanged`,
    `MappingAffectingOptionsChanged`, `HierarchyParametersChanged`, and
    `TolerancesAffectingOptionsChanged`; set `Flags::Elaborated`.
20. Accumulate wall-clock accounting and return
    `(true, hierarchyChanged, mapUnknownsNeeded || buildNeeded || analysisParametersChanged)`.
    The third value tells the caller whether `Analysis::bind()` must be
    re-run — unknowns/sparsity changed, or the analysis's own parameters
    changed in a way that could affect binding.

## Hierarchy propagation

`propagateDownHierarchy()` (`cirparams.cpp:10-230`) pushes parameter changes
down the instance tree and figures out, instance by instance, whether a
change is just a value update (cheap: `propagateParameters()` to immediate
children) or requires tearing down and rebuilding a whole subhierarchy
(expensive: `deleteHierarchy()` + `buildHierarchy()`, e.g. because a
parameter controls a `generate`/conditional instantiation). It does not
touch node ordering, unknowns, or sparsity — those are handled afterward, in
steps 10–17 above, only if `hierarchyChanged` or `unknownsChanged` came back
true.

1. Two force flags are computed up front: `forceHierarchyChangeCheck`
   (`HierarchyAffectingOptionsChanged` is set — such an option could affect
   hierarchy decisions *anywhere*, regardless of which instance's own
   parameters changed) and `forceParameterPropagation`
   (`VariablesChanged || ParametrizationAffectingOptionsChanged` — a global
   change that must be propagated to every instance, not only ones flagged
   `Instance::Flags::ParamsChanged`).
2. **Toplevel scan**: for each toplevel instance in order, enter its saved
   `Context` (recomputing it only if propagation is forced), and — if
   propagation or the hierarchy check is forced — call
   `subhierarchyChanged()`. The index of the *first* toplevel instance whose
   subhierarchy changed is recorded as `changeAt`; the scan then stops
   (toplevel instances are processed in a fixed dependency order because the
   default toplevel instance's context stays on the global search path for
   the others, so once one toplevel instance's structure changes, its
   effect on everything after it can't be reasoned about incrementally).
3. **Teardown**: if `changeAt<n`, every toplevel instance from the last one
   down to `changeAt` (reverse order) has its subhierarchy deleted via
   `deleteHierarchy()`.
4. **Incremental propagation**: for toplevel instances `0..changeAt-1`
   (unaffected by the teardown), walk each one's instance subtree
   (`hierarchyBegin()`/`hierarchyEnd()`). For every instance where
   propagation is forced or `Instance::Flags::ParamsChanged` is set: enter
   its context, call `subhierarchyChanged()`; if it changed, delete and
   rebuild just that instance's subhierarchy
   (`deleteHierarchy()`+`buildHierarchy()`) and stop descending further into
   it (`it.stopDescent()`, since `buildHierarchy()` already rebuilt
   everything below); otherwise propagate parameter values to its immediate
   children only (`propagateParameters()`) and keep walking down into them.
   `hierarchyChanged` accumulates any subhierarchy change seen here too.
5. **Rebuild**: toplevel instances `changeAt..n-1` (torn down in step 3) are
   fully rebuilt with `buildHierarchy()`.
6. The parameter-evaluation context stack is unwound back to the marker
   recorded at entry, and `(ok, hierarchyChanged)` is returned.

## Functions involved

- **`Circuit::elaborateChanges()`** (three overloads) — orchestrator; entry
  point for the whole protocol.
- **`ParameterSweeper::write()`** — writes the sweeper's current (or stored)
  values for one parameter family (variable/option/instance+model) into the
  circuit; reports whether anything changed.
- **`Analysis::updateSweeper()`** — re-derives a nested sweep's settings
  after an outer-sweep variable change.
- **`Circuit::updateGlobalContext()`** — clears and reinitializes the
  parameter-evaluator's context stack with the circuit's `variables`
  context (`cirvariables.cpp:12-22`); also used by the `Circuit` constructor.
- **`IStruct<SimulatorOptions>::setParameters()`** — applies
  expression-valued entries from a parameter map onto an options struct.
- **`Analysis::updateParameterExpressions()`** — re-evaluates the
  analysis's own expression-valued parameters.
- **`Circuit::setOptions()`** — writes an options struct into the circuit,
  setting the four `*AffectingOptionsChanged` flags for whatever actually
  changed.
- **`Circuit::propagateDownHierarchy()`** — see [Hierarchy
  propagation](#hierarchy-propagation) above.
- **`Instance::enterContext()` / `Instance::revertContext()`** — push/pop an
  instance's evaluation context on the parameter-evaluator's context stack;
  used throughout hierarchy propagation.
- **`Instance::subhierarchyChanged()`** (virtual) — reports whether current
  parameter values require the instance's subhierarchy to be rebuilt.
- **`Instance::propagateParameters()`** (virtual) — pushes parameter values
  to immediate children without changing structure; clears
  `Flags::ParamsChanged`.
- **`Instance::deleteHierarchy()`** / **`Instance::buildHierarchy()`**
  (virtual) — tear down / (re)construct an instance's subhierarchy.
- **`Circuit::buildEntityLists()`** — same as in initial elaboration; rerun
  here only if the hierarchy changed.
- **`Circuit::nodeOrdering()`** — same as in initial elaboration; rerun here
  only if the hierarchy changed.
- **`CommonData::fromOptions()`** — refreshes `commons` from the circuit's
  current simulator options before `setup()`.
- **`Circuit::setup()`** — same per-device `Device::setup()` sweep as
  initial elaboration, but `forceFull` is only true when
  `mappingAffectingOptionsChanged`.
- **`Analysis::preMapping()`** (virtual) — lets the analysis report whether
  it needs extra sparsity map entries / state slots, without triggering a
  potentially expensive full core-rebuild check.
- **`Circuit::mapUnknowns()`** — same as initial elaboration; rerun only if
  `mapUnknownsNeeded`.
- **`Circuit::buildSparsityAndStates()`** — same as initial elaboration;
  rerun only if `buildNeeded`. This is where CCCS/CCVS and behavioral
  sources (re-)resolve their controlling flow nodes if their own
  `populateStructuresCore()` runs again.
- **`Analysis::populateStructures()`** (virtual) — adds the analysis's own
  Jacobian entries / state slots on top of the per-device ones.
- **`Circuit::enumerateSystem()`** — same as initial elaboration; rerun only
  if `buildNeeded`.
- **`Circuit::setStaticTolerances()`** — rebuilds per-unknown/per-equation
  tolerances; rerun only if `unknownsChanged || tolerancesAffectingOptionsChanged`.
- **`CommonData::enumerateNatures()`** — re-enumerates the natures subset
  used by tolerance bookkeeping; rerun under the same condition as
  `setStaticTolerances()`.
