# Analysis Base Class

`an.h`/`an.cpp` define `Analysis`, the abstract base class every concrete analysis (`OperatingPoint`,
`Tran`, `AC`, ...) derives from. `Analysis` owns the pieces common to all analyses - sweep handling,
output-descriptor bookkeeping, simulator-option overrides, and the coroutine that drives a full
analysis run - and dispatches the analysis-specific work (running the actual numerics) to one or
more [`AnalysisCore`](core.md)-derived "cores" that a concrete subclass owns as plain members.

## Role and Relation to `AnalysisCore`

`Analysis` is the outer driver: it applies sweep points, converts `save` directives into output
descriptors, opens/closes output files, and steps a core (or several) through one solve. The
numerical work itself - Jacobian, solution vector, the NR solver - lives in a core
([Analysis Core Base Class](core.md)). A concrete analysis class combines the two: it derives from
`Analysis`, owns one or more concrete cores by value, and implements `Analysis`'s pure-virtual hooks
by forwarding to those cores' matching methods. `Analysis` itself never holds a core through a
pointer or a common base handle - `analysisCore()` returns whichever core the concrete analysis
considers primary (e.g. for progress installation), but the hook methods below (`rebuildCores()`,
`resolveOutputDescriptors()`, ...) are implemented per concrete analysis, one call per owned core.

`Analysis` also implements `OutputDescriptorResolver` (see [Analysis Core Base Class](core.md)),
so a core can delegate resolving a descriptor it does not recognize (e.g. a sweep-variable
descriptor, `OutdSweepvar`) back up to the owning analysis via `resolveOutputDescriptor()`.

## Data Structures

- **`AnalysisState`**: the enum an analysis's coroutine yields - `Uninitilized` (default/never
  started), `Aborted`, `Stopped`, `Finished`, `SweepPoint` (a new sweep point is about to run,
  yielded only if the `sweep_pointmarker` option is set).
- **`AnalysisCoroutine`**: `Generator<AnalysisState>` (see [Coroutine Generator](generator.md)) -
  the coroutine type returned by `Analysis::coroutine()` and driven by `start()`/`resume()`/`finish()`/`run()`.
- **`AnalysisFactory`**: `Analysis* (*)(PTAnalysis&, Circuit&, Status&)`, the function-pointer type
  each concrete analysis registers under its type name (`registerFactory()`), used by
  `Analysis::create()` to instantiate the right concrete class from a parsed `analysis` statement's
  type name.

## Control Flow

### Construction

`Analysis::create()` looks up the registered factory for `ptAnalysis.typeName()`, constructs the
concrete analysis, sets its parameters from the parsed `analysis` statement (`Parameterized::setParameters()`,
see [Parameterized Objects](parameterized.md)), and allocates one analysis-state-storage slot per
sweep point (`allocateAnalysisStateStorage(sweepCount())`) - this is the first allocation, so
state slots start at index 0. The `sweeper` member (a `ParameterSweeper`, see [Sweep Support](answeep.md))
is constructed at the same time from `ptAnalysis.sweeps()`.

### Running: `start()`/`resume()`/`finish()`/`run()`

`Analysis::coroutine()` is a single large coroutine function containing the entire run sequence;
`start()` creates it (via `coroutine(s)`), `resume()` pulls the next `AnalysisState`, `finish()`
performs post-run cleanup (finalizing or deleting output files, restoring pre-sweep parameter
values), and `run()` is the convenience wrapper most callers use: `start()`, then `resume()` in a
loop until `Aborted`, `Finished`, or `Stopped` is seen, then `finish()` (skipped on `Stopped`,
since a stopped analysis can be resumed later - `run()` reports this back as `can_resume`).
Resuming a coroutine that is not valid or already done is a usage error and throws
`std::runtime_error` rather than failing silently.

### Inside the coroutine

`coroutine()` (`lib/an.cpp`) runs, in order, once per invocation:

1. **Setup**: snapshot the circuit's current simulator options (`originalSimOptions`), overlay
   the analysis's own option overrides (`analysisOptions`, from `add(PTParameters)`/`add(PTParameterMap)`),
   clear per-instance flags (`HasDeviceHistory`, `OutputConverged`, `Bypassed`), reset `CommonData`
   (`commons.fromOptions()`) and rebuild static tolerances and the natures enumeration.
2. **Branch on whether the analysis has `sweep` clauses** (`ptAnalysis.sweeps().size()>0`):
   - **No sweep**: apply analysis options once via `circuit.elaborateChanges()`, build output
     descriptors (`addOutputDescriptors()`), rebuild cores (`rebuildCores()`), resolve output
     descriptors (`resolveOutputDescriptors()`), initialize outputs, run `circuit.preAnalysis()`,
     then drive one core coroutine (`coreCoroutine(false)`, continuation off) to completion.
   - **Sweep present**: run `sweeper.setup()`/`bind()`/`storeState()`/`reset()` once (see
     [Sweep Support](answeep.md)), then loop: apply the current sweep point
     (`circuit.elaborateChanges()` with the sweeper), re-bind the sweeper if the hierarchy changed,
     rebuild output descriptors and cores only the first time or when the elaboration reports
     `needsCoreRebuild`, restore analysis state for the sweep level that last advanced
     (`restoreState(advancedSweepIndex)`, enabling NR continuation across sweep points), run
     `circuit.preAnalysis()`, drive the core coroutine (continuation mode on if state was
     restored), then `sweeper.advance()` and store analysis state for the sweep levels that
     changed, before looping back until the sweep is exhausted. When neither the topology nor the
     core changed between two consecutive sweep points, the coroutine sets
     `commons.allowContinueStateBypass` so the analysis may skip the first NR iteration (bypassing
     it reuses the last computed load) - this is opt-in per analysis (see `nr_contbypass`).
3. Each core coroutine step yields a `CoreState` (see [Analysis Core Base Class](core.md)):
   `Aborted` aborts the whole analysis (and sweep); `Finished` ends only the current core run (the
   analysis keeps going if there are more sweep points, or is itself `Finished` in the no-sweep
   case); `Stopped` yields `AnalysisState::Stopped` up to the caller, who may `resume()` again
   later to continue the same core coroutine.
4. The coroutine's own last statement is `co_yield AnalysisState::Finished` - reached only once
   the whole run (all sweep points, in the swept case) is done.

### Output descriptor pipeline

`addOutputDescriptors()` (called once per full run, from inside `coroutine()`) is the shared driver
for turning `save` directives into resolved outputs:

1. `clearOutputDescriptors()` - pure virtual, clears every owned core's descriptor list.
2. Sweep variables are added as `OutdSweepvar` descriptors via `addCommonOutputDescriptor()`, one
   per `sweep` clause, named after the clause.
3. `addCoreOutputDescriptors()` - pure virtual, adds descriptors intrinsic to each core (e.g. time
   for a transient core, frequency for an AC core) regardless of what was saved.
4. Every accumulated `save` directive (`commonSaves`, populated by `add(const PTSave&)`/`add(const PTSaves&)`)
   is interpreted by `resolveSave()` - pure virtual, reimplemented per concrete analysis to dispatch
   a save to the core(s) that understand it. A semantic error in a save directive is always an
   error; an unrecognized directive is silently ignored unless `strictsave` requests otherwise.
5. `addDefaultOutputDescriptors()` - pure virtual, adds each core's fallback descriptor set if no
   save directive produced any descriptors for it.

Later, once cores are rebuilt, `resolveOutputDescriptors()` - pure virtual - converts the
descriptor lists into resolved `Output::SourcesList` entries (see [Abstract Output Support](output.md)).
`Analysis::resolveOutputDescriptor()` (the `OutputDescriptorResolver` override) is the fallback a
core calls into for descriptor types it does not itself understand; the base implementation
handles only `OutdSweepvar`, resolving it against the `sweeper`.

## Sweep-Point Continuation and Homotopy State

`Analysis` provides a parallel, analysis-level state-storage mechanism (distinct from - but used
alongside - the per-core one in `AnalysisCore`): `analysisStateStorageSize()`,
`allocateAnalysisStateStorage()`, `deallocateAnalysisStateStorage()`, `storeState()`,
`restoreState()`, and `makeStateIncoherent()`. The base defaults are all no-ops; a concrete
analysis overrides them if it needs to remember something across sweep points at the analysis
level rather than (or in addition to) the core level. `storeState(ndx, storeDetails)`'s
`storeDetails=false` variant is used by homotopy algorithms that need to stash/restore state
rapidly without paying for storing names.

## Parameters

`Analysis` itself defines no analysis-specific parameter struct - `parameters()` is pure virtual,
returning whatever `Parameterized`-derived struct the concrete analysis (or its core) declares,
per the project convention that analysis parameters live in a struct defined in the owning core's
header. `Analysis` does hold two option-related members of its own:

| Member | Type | Purpose |
|---|---|---|
| `analysisOptions` | `PTParameterMap` | Simulator-option overrides given directly in the `analysis` statement (e.g. `analysis tran1 tran ... reltol=1e-6`), applied on top of the circuit's simulator options for the duration of this analysis only, restored by `finish()`. |
| `sweeper` | `ParameterSweeper` | Drives any `sweep` clauses attached to the analysis; see [Sweep Support](answeep.md) for its own parameter table (`SweepSettings`). |

## Save Directives

`Analysis` defines no save directives of its own beyond the sweep-variable descriptor
(`OutdSweepvar`) it adds automatically for each `sweep` clause - this is not something a user
requests via `save`. Each concrete analysis documents its own recognized `save` names in its own
doc, next to the `an<suffix>.h`/`core<suffix>.h` pair that defines them (see [Analysis Core Base
Class](core.md) for the shared save-directive helper functions every core can use).

## Related Documentation

- [Analysis Core Base Class](core.md) - `AnalysisCore`, the per-core counterpart `Analysis` drives.
- [SmallSignal Analysis Template](ansmsig.md) - the shared template several small-signal analyses instantiate.
- [Operating Point Analysis](anop.md) - a concrete, single-core `Analysis` subclass.
- [Sweep Support](answeep.md) - `ParameterSweeper`, owned by every `Analysis` as `sweeper`.
- [Coroutine Generator](generator.md) - the `Generator` template behind `AnalysisCoroutine`.
- [Abstract Output Support](output.md) - `OutputDescriptor`/`OutputSource`/`Output`.
- [Progress Reporting](progress.md) - `ProgressReporter`, installed on the sweeper or the primary core.
- [Parameterized Objects](parameterized.md) - `Parameterized`, `PTParameterMap`, `setParameters()`.
</content>
