# Small-Signal Noise Analysis

`Noise` (`annoise.h`/`annoise.cpp`) computes the small-signal output-referred
noise power spectral density (PSD) of a circuit, broken down by device
instance and (optionally) by individual noise source, along with the power
gain from a chosen input source to a chosen output. It is one of the three
`SmallSignal<CoreClass, DataMixin>` instantiations (see
[Small-Signal Analysis Template](ansmsig.md)); its computational core is
`NoiseCore` (`corenoise.h`/`corenoise.cpp`).

## Class and Data Structures

`Noise` is the typedef `SmallSignal<NoiseCore, NoiseData>`. `NoiseData`
supplies the members every `SmallSignal` instantiation needs plus the ones
specific to noise analysis:

- `acMatrix` (`KluComplexMatrix`) - the complex small-signal system matrix.
- `acSolution` (`Vector<Complex>`) - RHS/solution vector for one excitation
  (input source or a single noise source).
- `contributionOffset` (`unordered_map<pair<Id, Id>, size_t>`) - maps
  `(instance, noiseSourceName)` (or `(instance, Id())` for the instance's
  total) to its slot in `results`; rebuilt by `resolveOutputDescriptors()`
  from the accumulated `n`/`nc`/`default`/`full` save descriptors.
- `results` (`Vector<double>`) - per-slot output-noise PSD contribution, one
  entry per `contributionOffset` slot.
- `powerGain` (`double`) - squared magnitude of the transfer function from
  the `in` source to the `out` node(s) at the current frequency.
- `outputNoise` (`double`) - total output-noise PSD at the current frequency
  (sum of every instance's total contribution, regardless of whether that
  instance's contribution was individually saved).

`Noise`'s constructor, `resolveSave()`, and `dump()` are template
specializations of `SmallSignal<NoiseCore, NoiseData>` (declared in
`ansmsig.h`, defined here), following the pattern shared by every
`SmallSignal` instantiation.

## Core Algorithm

As with the other small-signal cores, `NoiseCore` first uses
`OperatingPointCore` (`opCore_`) to solve for the DC operating point `x0`
(`f(x0) = 0`), then linearizes: it evaluates the resistive Jacobian `Jr` and
reactive Jacobian `Jc` at `x0`, and additionally evaluates every instance's
noise sources at `x0` (`evaluateNoise = true` in the initial `EvalSetup`).
For each frequency it solves

```
(Jr + omega*Jc) X = U
```

with `omega = 2*pi*frequency`, once per excitation. `Jr` is copied into the
real part of `acMatrix` once, outside the frequency loop; only the imaginary
part is reloaded and the matrix refactored per frequency point.

**Per-frequency work.** At each frequency, after refactoring:

1. **Power gain.** The system is solved once with the `in` source's unity
   excitation as RHS. The transfer function from source to the `out` node
   (or node pair) gives `powerGain = |tf|^2`. A source's `$mfactor` scales
   the excitation, so for `$mfactor != 1` the gain is effectively measured
   from the source's `mag` value, not from one physical instance's
   contribution alone (a comment in `corenoise.cpp` spells this out for
   voltage vs. current sources).
2. **Per-source noise contributions.** For every instance with at least one
   noise source, `NoiseCore` calls `inst->loadNoise()` to get each source's
   PSD at the current frequency, then, for every individual noise source,
   solves the linearized system once more with a unit excitation applied at
   that source's excitation nodes. The resulting transfer function to the
   output gives a gain `|tf|^2`; the source's contribution to output-noise
   PSD is `gain * abs(noiseDensity)` (absolute value because a noise PSD
   value coming out of a compact model may be negative - see the
   Coram et al. reference cited in `corenoise.cpp`). Contributions are
   summed per instance (`totalInstanceContribution`) and into the running
   `outputNoise` total; individual-source contributions are stored into
   `results` only for sources that were actually requested via `save`
   (`contributionOffset` lookup misses are simply dropped, since computing
   them is unavoidable while summing the instance total anyway).

`resolveOutputDescriptors()` resolves `OutdNoiseContribInst`/
`OutdNoiseContribInstPartial` descriptors against `contributionOffset`
(inserting new slots as needed and clearing/rebuilding the map each time
descriptors are resolved), `OutdOutputNoise` against `outputNoise`,
`OutdPowerGain` against `powerGain`, and `OutdFrequency` against the core's
own scalar; anything else is delegated to the owning `Noise` analysis
(which in turn forwards operating-point saves to `opCore`).

## Parameters

Fields of `NoiseParameters` (`corenoise.h`), read from the analysis's own
argument list plus the exposed operating-point parameters:

| Parameter | Type | Description |
|---|---|---|
| `out` | Value | Output node, or a two-element node-pair vector for a differential output. |
| `in` | Id | Input source instance used for the power-gain computation. |
| `from` | Real | Start frequency for `step` and `dec`/`oct`/`lin` sweeps. |
| `to` | Real | Stop frequency for `step` and `dec`/`oct`/`lin` sweeps. |
| `step` | Real | Step size for a stepped frequency sweep. |
| `mode` | Id | Sweep mode for `dec`/`oct`/`lin` sweeps. |
| `points` | Int | Point (interval) count for `dec`/`oct`/`lin` sweeps. |
| `values` | Value | Explicit vector of frequency values. |
| `write` | Int | Write results to a file (default on). |
| `writeop` (`opParams.write`) | Int | Write the operating-point solution to its own output file (off by default for this analysis). |
| `nodeset` (`opParams.nodeset`) | Value | Nodesets for the initial operating-point solve; see [Operating Point Analysis](anop.md). |
| `store` (`opParams.store`) | String | Name of the stored-solution slot the operating point is written to. |

The `from`/`to`/`step`/`mode`/`points`/`values` fields configure a
`ScalarSweep` exactly as for plain AC analysis; see
[Sweep Support](answeep.md) for the shared stepped/lin/log/value sweep
machinery.

## Save Directives

`Noise::resolveSave()` recognizes noise-specific directives first, falling
back to the operating-point directives (delegated to `opCore` through
`resolveOpSave()`, see [Small-Signal Analysis Template](ansmsig.md)) for
anything it does not recognize:

| Directive | Resolves to |
|---|---|
| `default` | Total noise contribution of every instance that has at least one noise source (`addAllNoiseContribInst(details=false)`). |
| `full` | As `default`, plus one descriptor per individual noise source of every such instance (`addAllNoiseContribInst(details=true)`). |
| `n(<inst>)` | Total noise contribution of one named instance. `n(<inst>,<src>)` (two arguments) selects one specific noise source. |
| `nc(<inst>)` | Total noise contribution of one named instance, plus a descriptor for each of its individual noise sources (`addNoiseContribInst(details=true)`). |
| `opdefault` | All circuit unknowns (representative nodes) of the operating point. |
| `opfull` | All non-ground nodes of the operating point. |
| `v(<node>)` or `v(<node1>,<node2>)` | Operating-point node (or node-pair difference) voltage. |
| `i(<inst>:flow(<br>))` | Operating-point branch current of an instance. |
| `p(<inst>.<outvar>)` | Operating-point instance output variable. |

Every noise-contribution descriptor is named `n(<inst>)` or
`n(<inst>,<src>)` in the output regardless of which directive produced it
(`nc` differs from `n` only in that it also expands to per-source
descriptors). If no `save` directive produces any descriptor,
`NoiseCore::addDefaultOutputDescriptors()` falls back to the `default` set
(total per-instance contributions). `frequency`, `onoise` (`outputNoise`),
and `gain` (`powerGain`) are always added by
`NoiseCore::addCoreOutputDescriptors()` whenever `write` is nonzero,
independent of `save`.

## Related Documentation

- [Small-Signal Analysis Template](ansmsig.md) - the `SmallSignal<CoreClass,
  DataMixin>` template `Noise` instantiates, and the AC linearization it
  shares with `ACSP` and `ACStb`.
- [AC Small-Signal S-Parameter Analysis](anacsp.md) - sibling small-signal
  analysis computing S-parameters instead of noise.
- [AC Stability Analysis](anacstb.md) - sibling small-signal analysis
  computing loop-gain/stability figures instead of noise.
- [Operating Point Analysis](anop.md) - `OperatingPointCore`, nodesets, and
  the DC solve every small-signal analysis starts from.
- [Analysis Core Base Class](core.md) - `AnalysisCore`, the save-directive
  helpers (`addAllNoiseContribInst()`, `addNoiseContribInst()`,
  `addAllUnknowns()`, `addNode()`, `addFlow()`, `addInstanceOutvar()`, ...)
  reused by `resolveSave()`/`resolveOpSave()`.
- [Sweep Support](answeep.md) - `ScalarSweep`, the frequency-sweep
  machinery shared by `from`/`to`/`step`/`mode`/`points`/`values`.
