# AC Small-Signal S-Parameter Analysis

`ACSP` (`anacsp.h`/`anacsp.cpp`) computes scattering (S) parameters of a
multi-port circuit over a frequency sweep. It is one of the three
`SmallSignal<CoreClass, DataMixin>` instantiations (see
[Small-Signal Analysis Template](ansmsig.md)); its computational core is
`ACSPCore` (`coreacsp.h`/`coreacsp.cpp`).

## Class and Data Structures

`ACSP` is the typedef `SmallSignal<ACSPCore, ACSPData>`. `ACSPData` is the
mixin that supplies the two members every `SmallSignal` instantiation needs
plus one specific to S-parameters:

- `acMatrix` (`KluComplexMatrix`) - the complex small-signal system matrix.
- `acSolution` (`Vector<Complex>`) - RHS/solution vector for one excitation.
- `stMatrix` (`DenseMatrix<Complex>`) - the transposed S matrix, `portCount x
  portCount`; also exposed as an output source for the `s(i,j)` save
  descriptors.

`ACSP`'s constructor, `resolveSave()`, and `dump()` are template
specializations of `SmallSignal<ACSPCore, ACSPData>` (declared in
`ansmsig.h`, defined here), following the same specialization pattern used
by every `SmallSignal` instantiation.

`ACSPCore` owns, in addition to the references shared with other
small-signal cores (`dcJacobian`, `dcSolution`, `dcStates`, `acMatrix`,
`acSolution`), the per-port bookkeeping built by `rebuild()`:
`sourceVector`/`resistorVector` (the port-defining instance pairs),
`terminalsVector` (interface-plane node pairs), `z0` (characteristic
impedances), and a scratch `atMatrix`/`rowPerm_` pair used to solve for S
once the wave matrices are collected.

## Core Algorithm

Like the other small-signal cores, `ACSPCore` first uses `OperatingPointCore`
(`opCore_`) to solve for the DC operating point `x0` (`f(x0) = 0`, the
resistive residual), then linearizes: it evaluates the resistive Jacobian
`Jr` and reactive Jacobian `Jc` at `x0` and, for each frequency, solves

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

with `omega = 2*pi*frequency`. `Jr` is reused unchanged from the operating
point (copied into the real part of `acMatrix` once, outside the frequency
loop); only the imaginary part is reloaded and the matrix refactored at each
frequency.

**Port definition.** A port is a voltage source plus a series resistor
connected to the source's positive terminal. The resistor's node not shared
with the source is the port's positive interface-plane node; the source's
negative terminal is the port's negative interface-plane node. The port's
characteristic impedance is the resistor's `r` divided by its `$mfactor`
(`rebuild()` validates that each named resistor has `r`, `$mfactor`, and
`noisy` parameters, is not itself a source, has exactly two terminals, and
that the source's positive node actually connects to one of the resistor's
nodes).

**Per-frequency sweep.** For each swept frequency, `ACSPCore` excites one
port at a time (unit current injected between the port's positive
interface-plane node and ground, all other ports left unexcited) and solves
the linear system. From the resulting interface-plane voltage `vp` and
scaled source current `ip` at every port `j`, it computes incident and
reflected waves

```
a = (vp + z0*ip) / (2*sqrt(z0))
b = (vp - z0*ip) / (2*sqrt(z0))
```

storing them (transposed) into `atMatrix` (excitation `i`, response `j`) and
`stMatrix`. Once all ports have been excited, the definition `B = S*A`
becomes, in transposed form, `A^T S^T = B^T`; `ACSPCore` solves this dense
system in place with `atMatrix.factorAndLuSolve(stMatrix, ...)` (LAPACK LU,
see [Dense Matrices](densematrix.md)), leaving the transposed S matrix in
`stMatrix`. Y-parameter computation is a TODO in the source and not yet
implemented.

`resolveOutputDescriptors()` reads `s(i,j)` entries directly out of
`stMatrix` (transposed indexing, `stMatrix.indexOf(ndx2, ndx1)`) and
`frequency` from the core's own scalar; anything else is delegated to the
owning `ACSP` analysis (which in turn forwards operating-point saves to
`opCore`).

## Parameters

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

| Parameter | Type | Description |
|---|---|---|
| `ports` | Value (string vector) | Ports, given as a flat list of alternating voltage-source and series-resistor instance names; must have a positive, even element count. |
| `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

`ACSP::resolveSave()` recognizes only the operating-point save directives,
delegated verbatim to `opCore` through `resolveOpSave()` (see
[Small-Signal Analysis Template](ansmsig.md) and
[Analysis Core Base Class](core.md)):

| Directive | Resolves to |
|---|---|
| `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. |

The S-parameter results themselves are not selected through `save` at all:
as long as `write` is nonzero, `ACSPCore::addCoreOutputDescriptors()`
unconditionally adds one `s(i,j)` descriptor per pair of ports (1-based) and
a `frequency` descriptor, independent of what (if anything) was saved.

## Related Documentation

- [Small-Signal Analysis Template](ansmsig.md) - the `SmallSignal<CoreClass,
  DataMixin>` template `ACSP` instantiates, and the AC linearization it
  shares with `ACStb` and `Noise`.
- [AC Stability Analysis](anacstb.md) - sibling small-signal analysis using
  the same operating-point-then-linearize approach for a loop-gain probe
  instead of a port sweep.
- [Small-Signal Noise Analysis](annoise.md) - sibling small-signal analysis
  computing noise contributions instead of S-parameters.
- [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 (`addAllUnknowns()`, `addNode()`, `addFlow()`,
  `addInstanceOutvar()`, ...) reused by `resolveOpSave()`.
- [Sweep Support](answeep.md) - `ScalarSweep`, the frequency-sweep
  machinery shared by `from`/`to`/`step`/`mode`/`points`/`values`.
- [Dense Matrices](densematrix.md) - `DenseMatrix`, `factorAndLuSolve()`
  used to solve for the S matrix.
