# Circuit

`Circuit` (`circuit.h`, implemented in `circuit.cpp`) is the central data structure of the simulator. It owns every [`Device`, `Model`, and `Instance`](devbase.md) and every [`Node`](node.md) built from a parsed netlist, and it drives the pipeline that turns that ownership graph into the flat set of unknowns, Jacobian sparsity pattern, and state-vector slots an analysis solves against. Everything else in the simulator - the command interpreter, analyses, the parameter sweeper - operates on the circuit through this class.

## Ownership

- `devices`: a `vector<unique_ptr<Device>>`, indexed by `deviceIndex` (name to position). One `Device` per device type (builtin, OSDI-loaded, or the builtin `HierarchicalDevice` used for subcircuits, see [Hierarchical devices](hierdevice.md)); index 0 is always the hierarchical device.
- `instanceMap`/`modelMap`: `unordered_map<Id, unique_ptr<...>>` owning every `Instance`/`Model` by name.
- `nodePool`/`nodeMap`: nodes are allocated from a `RecyclingTypedPoolAllocator<Node>` (see [Memory Pools](pool.md)) and looked up by name via `nodeMap`.

Each `Model` pointer additionally appears under its owning `Device` and under its parent hierarchical instance's list of child models; each `Instance` pointer appears under its owning `Model` and its parent's list of child instances - except toplevel models/instances, which have no parent and are held directly by the circuit (`toplevelModels_`/`toplevelInstances_`). This triple-bookkeeping scheme is documented at the top of `circuit.h`.

## `CircuitFlags`

`Circuit` derives from `FlagBase<CircuitFlags>` (see [Flags](flags.md)) and uses six flags to track what has changed since the last elaboration, so `elaborateChanges()` (see [Elaboration and Partial Elaboration](cirparams.md)) can redo only the necessary work:

- `VariablesChanged` - a circuit variable was added or modified (see [Circuit Variables Management](cirvariables.md)).
- `HierarchyAffectingOptionsChanged`/`ParametrizationAffectingOptionsChanged`/`MappingAffectingOptionsChanged`/`TolerancesAffectingOptionsChanged` - a simulator option in the corresponding category changed (see [Options and Options Management](options.md)).
- `HierarchyParametersChanged` - an instance or model parameter was changed through one of the circuit's setters; code that mutates instance/model parameters directly (bypassing the circuit) must set this flag manually.
- `Elaborated` - the circuit is currently in a consistent, fully elaborated state.

`needsElaboration()` is true whenever `Elaborated` is clear or any of the other five flags is set.

## Supporting classes

- **`SourceCompiler`**: an abstract single-method interface (`compile()`) the circuit's constructor uses to compile OSDI source files (e.g. Verilog-A via OpenVAF, see [OpenVAF Compiler Wrapper](openvafcomp.md)) encountered in `load` directives into a loadable binary, before opening them with [`OsdiFile::open()`](osdifile.md).
- **`OptionsResolver`**: a `Resolver` (see [Context](context.md)) that exposes selected `SimulatorOptions` fields (currently `temp` and `scale`) as the `$temp`/`$scale` netlist expression variables; installed on the parameter evaluator's context stack via `installCustomResolver()`. See [Options and Options Management](options.md).
- **`UnknownNameResolver`**: implements `NameResolver` to map a matrix row/column (`MatrixEntryIndex`) back to a node name, for diagnostics involving the sparse linear system.

## Construction

The constructor takes the [`ParserTables`](parsertables.md) produced by parsing the netlist and an optional `SourceCompiler`. It performs work that is common to every elaboration of this circuit and does not depend on which toplevel subcircuit(s) will eventually be built:

1. Installs `OptionsResolver` on `paramEvaluator_`'s context stack and enters `variables` into `variableEvaluator_`'s context stack.
2. Creates the builtin `HierarchicalDevice` (always device index 0) and all other builtin devices (see [Device Registration](devbuiltins.md)).
3. Processes every `load` directive from the parser tables: locates the named OSDI file (absolute path, directory of the referencing file, current directory, or the module search path, in that order), compiles it if a `SourceCompiler` was given and compilation is needed, opens it, and adds the devices it exports - optionally renamed/filtered through the directive's `map` parameter (`["*", "*"]` loads everything unchanged, `["*", "pfx_*"]` prefixes every name, `[]` loads nothing).
4. Calls `updateGlobalContext()` to make circuit variables visible to the parameter evaluator.

`isValid()` reflects whether construction succeeded; a failed construction still runs the destructor's cleanup.

## Building a circuit: `elaborate()`

`elaborate()` performs a full (non-incremental) build from the parser tables and is called once, before an analysis run starts (see [Elaboration and Partial Elaboration](cirparams.md) for the incremental re-elaboration used while an analysis is running). Its steps, also summarized in the block comment at the top of `circuit.h`:

1. `clear()` any previous circuit state.
2. Build the default toplevel subcircuit definition (`processSubcircuitDefinition()`), plus one `HierarchicalModel` for every subcircuit named in `toplevelDefinitions` (definitions nested inside a requested toplevel definition are treated as if defined directly in the toplevel scope, not prefixed).
3. Create ground nodes (`addGround()`) and global nodes (`addGlobal()`) from the parser tables.
4. Instantiate one `HierarchicalInstance` per toplevel model - the default subcircuit's instance first, then the additional ones in the order requested - via `buildTopInstance()`. The default toplevel instance's parameter context is entered globally so it is visible from every other toplevel instance.
5. `buildEntityLists()`: populate each `Device`'s list of `Model`s and each `Model`'s list of `Instance`s.
6. `nodeOrdering()`: sort all nodes, ground nodes first, then alphabetically by name, and reject a circuit with no nodes or no ground node.
7. `setup(commons, forceFull=true, ...)`: call `Device::setup()` (which fans out to `Model::setup()`/`Instance::setup()`, see [Device, Instance, and Model Base Classes](devbase.md)) for every device, unconditionally.
8. `mapUnknowns()`, `buildSparsityAndStates()`, `enumerateSystem()` (below).
9. Sanity checks: at least one node, one unknown, and one sparsity entry.

On success `Elaborated` is set and the variable/options/hierarchy-parameter change flags are cleared.

## Node ordering, unknown mapping, and node collapsing

- **`nodeOrdering()`** rebuilds `nodeOrder` (a flat, sorted `vector<Node*>`) from `nodeMap`: ground nodes sort first, everything else follows in lexicographic order by name. This determines each node's `NodeIndex`.
- **`mapUnknowns()`** first assigns a provisional `UnknownIndex` to every node (0 for all ground nodes, 1.. for the rest in `nodeOrder`), then calls `Device::collapseNodes()` on every device (which calls `Instance::collapseNodes()`, which calls back into `Circuit::collapseNodes()` for each pair of nodes to merge - e.g. across a zero-resistance branch). `Circuit::collapseNodes()` merges two unknowns by relocating every node mapped to the higher unknown index into the multimap bucket of the lower one (`unknownToNodes`), so an unknown groups every node collapsed onto it. After collapsing, unknown indices are renumbered densely (`unknownCountExcludingGround` is the result, excluding the ground slot at index 0), and `unknownToReprNode` is filled with one representative `Node*` per unknown - preferring a non-internal node (see `InternalDeviceNode` in [Nodes](node.md)) with the lowest node index, since elaboration must never expose a device's private internal node as the "public face" of an unknown.
- `resistiveResidualContribCount`/`reactiveResidualContribCount` count, per unknown, how many device contributions land on that equation's resistive/reactive residual (capped at 2), used by `checkDcResidual()`/`checkResidual()` to flag unknowns with fewer than two contributions - a sign of a floating or under-constrained node.

## Sparsity map and states

`buildSparsityAndStates()` clears the circuit's `SparsityMap` (`sparsityMap()`, declared in `klumatrix.h`) and calls `Device::populateStructures()` on every device, which requests Jacobian entries via `createJacobianEntry(ne, nu, flags)` (a no-op that still reports success when either node maps to ground - ground never gets a matrix entry) and state-vector slots via `allocateStates()`/`allocateDeviceStates()` (simple bump allocators that hand back the previous `statesCount_`/`deviceStatesCount_` as the new entries' base offset). A diagonal `Resistive` entry is then force-created for every unknown. `enumerateSystem()` finalizes the map by calling `SparsityMap::enumerate()`, which assigns each entry its position in the flat sparse Jacobian vector; actual binding to a `KluMatrixAccess` happens later, per-analysis, via `Circuit::bind()`.

## Setup, static tolerances, and pre-analysis

- **`setup(commons, forceFull, devReq, s)`** fans `Device::setup()` out across all devices and reports whether unknowns or sparsity changed, so callers can decide whether `mapUnknowns()`/`buildSparsityAndStates()` are actually needed.
- **`setStaticTolerances(commons, s)`** resets `commons`' tolerance tables and calls `Device::setStaticTolerances()` on every device; when `tolmode` is `mixed`, it additionally defaults every unknown's SPICE-legacy tolerance pairing (voltage/flux for a flow node's unknown and current/charge for its residual, or the reverse for a potential node - see `PotentialNode`/`FlowNode` in [Nodes](node.md)) before scaling everything by `tolscale`.
- **`preAnalysis(s)`** fans `Device::preAnalysis()` out across all devices, for computations that must run after every instance/model is set up but before an analysis starts.
- **`bind(matResist, ..., matReact, ..., s)`** fans `Device::bind()` out across all devices, wiring each device's Jacobian contributions to positions in a resistive and/or reactive [`KluMatrixAccess`](devbase.md).

## Variables, parameters, and options

`Circuit` exposes the netlist-facing mutation API other subsystems use to keep the circuit in sync with the command interpreter and analyses:

- Circuit variables: `getVariable()`/`setVariable()`/`clearVariables()`, backed by the `variables` `Context` - see [Circuit Variables Management](cirvariables.md).
- Instance/model parameters: `setInstanceParameter()`/`setModelParameter()`/`setInstanceParameters()`/`setModelParameters()` and their `...Parameter()` getters, all setting `HierarchyParametersChanged` on an actual change - implemented in [Elaboration and Partial Elaboration](cirparams.md).
- Simulator options: `setOption()`/`setOptions()`/`clearOptions()` - see [Options and Options Management](options.md).
- Incremental re-elaboration after any of the above: `elaborateChanges()`, also implemented in [Elaboration and Partial Elaboration](cirparams.md), together with `propagateDownHierarchy()`, the routine that reconciles parameter and hierarchy changes across the subcircuit tree.

## Stored solutions

`newStoredSolution(name)`/`storedSolution(name)` manage `solutionRepository`, a name-keyed map of `AnnotatedSolution` objects used to hold node-set, initial-condition, and HB-assisted-HB starting solutions across analyses.
