# Abstract Output Support

`output.h`/`output.cpp` define the abstract machinery that sits between an analysis's internal
data (solution vectors, output variables, sweep values, ...) and a concrete output file format:
`OutputDescriptor`, a compact, format-agnostic record of "what to output" produced once by an
analysis; `OutputSource`, a compact, resolved pointer/index pair that knows how to actually fetch
one scalar value on demand; and `Output`, the abstract base class a concrete writer (see
[Rawfile Output](outrawfile.md)) implements to turn a stream of resolved `OutputSource`s into a
file. `CommonData` (see [Common Analysis Data](comdata.md)) is not directly referenced by this
layer, but the two sit side by side as the pieces every `AnalysisCore` (see
[Analysis Core Base Class](core.md)) is constructed with.

## Two-stage pipeline: descriptor then source

The header's own comments spell out the intended flow:

- A **save directive** (parsed `.save`/output request) is converted by the owning analysis into
  zero, one, or more `OutputDescriptor`s.
- Each `OutputDescriptor` is converted by the owning analysis (usually by delegating unrecognized
  descriptor types to `OutputDescriptorResolver::resolveOutputDescriptor()`, see
  [Analysis Core Base Class](core.md)) into exactly one `OutputSource`.

Descriptors are created once, right before an analysis core first runs; resolving them into
sources can happen more than once - e.g. once per re-elaboration, since the pointers a source
holds (into a solution vector, a `VectorRepository` slot, etc.) can be invalidated by circuit
changes that a descriptor, being just an `Id`/index pair, is immune to. Splitting the two stages
this way keeps the expensive, analysis-specific resolution logic out of the hot per-point output
loop: once a source is resolved, extracting its value is a single indexed load.

## `OutputDescriptor`

A small tagged union, deliberately kept to 16 bytes (`type` + a 4-byte `Id`/index union + a 4-byte
`Id` name) since an analysis can hold many of them. Its `type` byte selects which union member is
valid and is interpreted by whichever analysis created the descriptor - the same numeric type can
mean different things in different analyses, but the ranges are partitioned so descriptor types
shared across analyses do not collide with each other or with common infrastructure:

- **`0..63`** - descriptor types meaningful to more than one analysis, defined as named constants:
  `OutdSolComponent`, `OutdPinCurrent`, `OutdOutvar`, `OutdTf`, `OutdZ`, `OutdY`, `OutdSmat`,
  `OutdNoiseContribInst`, `OutdNoiseContribInstPartial` (7-8 unused so far), ...,
  `OutdGain` (59), `OutdOutputNoise` (60), `OutdPowerGain` (61), `OutdFrequency` (62), `OutdTime`
  (63).
- **`CustomMask()` = `1<<6` (64..127)** - reserved for descriptor types private to a single
  analysis; none defined yet.
- **`CommonMask()` = `2<<6` (128..255)** - descriptor types handled by the abstract analysis layer
  itself rather than by an individual core: `OutdSweepvar` (128, a sweep variable's current
  value), `OutdSimInternal` (129, a simulator-provided scalar like time or frequency),
  `OutdSimStat` (130, a simulator statistic).

The six constructors cover every payload shape a descriptor needs: no extra data, a single index
(`size_t`, range-checked against `NdxType`'s 32-bit range and thrown as `std::length_error` if
exceeded), a single `Id`, two `Id`s, an `Id` plus an index, or two indices. `name` (the output
variable's own name, e.g. as it will appear in a raw file's variable list) is always present
alongside the type-specific payload.

## `OutputSource`

The resolved counterpart: a tagged union sized to stay small (`char` type tag + one pointer + one
index, kept to 16-24 bytes per the header's own accounting) that knows how to fetch a value
without any further lookup. Its `Type` enum has one member per storage shape a value can live in:

| Type | Points at | Notes |
|---|---|---|
| `IntPtr` | `const int*` | dereferenced directly |
| `RealPtr` | `const double*` | dereferenced directly |
| `RealArray` | `const double*` + index | pointer into a flat array |
| `RealVec` | `const Vector<double>*` + index | see [Analysis Support Classes](ansupport.md) |
| `RealVecRepo` | `const VectorRepository<double>*` + index | reads the *current* history slot (`data()`), since the latest converged NR solution is always installed there before outputs are computed |
| `ComplexPtr`/`ComplexArray`/`ComplexVec`/`ComplexVecRepo` | same shapes, `Complex` element | |
| `Sweep` | `const ParameterSweeper*` + index | see [Sweep Support](answeep.md) |

Every constructor pairs a pointer type with the matching `Type` tag and an optional `Id asName` (a
per-`OutputSource` override of the descriptor's `name`, defaulting to `Id::none`); the default
constructor produces a `ComplexPtr` at a static `defaultBad` (`0+0i`) sentinel, used as a
placeholder before real resolution.

- **`getR()`** returns a `double`. For real sources it reads the value directly; for complex
  sources it returns the real part (`.real()`); for `Sweep` it calls
  `sweeperPtr->compute(index, v)` and converts the resulting `Value` to `Int`/`Real`, falling back
  to `sweeperPtr->valueIndex(index)` (the sweep's 0-based position) for value types that don't
  reduce to a scalar number. A failed `compute()` (which should never happen in practice, since an
  analysis aborts before writing output for a sweep point whose value failed to compute) returns
  `0.0`.
- **`getC()`** is the same dispatch but returns a `Complex`; real sources widen to `Complex`
  trivially, and complex sources return the value unchanged.
- **`setName(id)`**/**`name()`** get/set the resolved output's display name, independent of the
  union payload.

Because `getR()`/`getC()` are called once per source per output point, and an analysis can have
many sources and many points, the type is deliberately a plain union with no vtable - dispatch is
a `switch` on a one-byte tag, not a virtual call.

## `Output`

The abstract base every concrete writer derives from:

```cpp
class Output {
public:
    Output(const std::string& baseName, SourcesList& sources);
    void setTitle(std::string title);
    void setPlotname(std::string name);
    virtual bool prologue(Status& s=Status::ignore) = 0;
    virtual bool addPoint(Status& s=Status::ignore) = 0;
    virtual bool epilogue(Status& s=Status::ignore) = 0;
    virtual bool remove(Status& s=Status::ignore) = 0;
protected:
    std::string baseName_;
    std::ofstream outStream;
    std::string title_;
    std::string plotname_;
    SourcesList& srcs;
};
```

`Output` does not own its `SourcesList` (a `std::vector<OutputSource>`) - it holds a reference
into the analysis core's own `outputSources` vector, so newly (re-)resolved sources are visible to
the writer without any copying or re-registration. `baseName_` is the output's base file name
(without format-specific extension), and `title_`/`plotname_` default to `"Output file"`/
`"analysis"` but are normally overridden via `setTitle()`/`setPlotname()` before `prologue()`.

The four pure-virtual lifecycle methods define the contract every writer must implement, in this
call order:

1. **`prologue(s)`** - open the output file and write any header/preamble.
2. **`addPoint(s)`** - called once per output point (e.g. once per time step, per sweep point, per
   frequency); reads every source in `srcs` via `getR()`/`getC()` and appends one row/record.
3. **`epilogue(s)`** - finalize (e.g. patch a point count written as a placeholder in the
   preamble) and close the file.
4. **`remove(s)`** - delete the output file outright, used when an analysis is re-run and its
   previous output must not linger (e.g. `deleteOutputs()` on an `AnalysisCore`).

All four take a `Status&` (see [Status](status.md)) defaulting to `Status::ignore`, and return
`bool` success/failure, following the setup/eval/load convention used throughout the simulator
(see `AnalysisCore` in [Analysis Core Base Class](core.md)).

## Where this fits

`OutputDescriptor`s live in an `AnalysisCore`'s `outputDescriptors` vector, created from `.save`
directives before the core first runs. `resolveOutputDescriptors()` walks that vector once (or
again after re-elaboration) and, for each descriptor, either resolves it directly (descriptor
types the concrete core understands, e.g. `OutdSolComponent`) or delegates to
`OutputDescriptorResolver::resolveOutputDescriptor()` (for `CommonMask()` types and anything the
core doesn't special-case) - the result is appended to `outputSources`, which a concrete `Output`
instance (currently [`OutputRawfile`](outrawfile.md)) reads from on every `addPoint()` call. The
only writer implemented so far is the `.raw` file format; other formats would derive from `Output`
the same way.
