# Netlist Context of the Evaluator

`RpnEvaluationNetlistContext` and `MCData` (`rpnevalctx.h`/`rpnevalctx.cpp`) carry the netlist-specific state a builtin function may need beyond its arguments - in practice, this exists almost entirely to support the Monte Carlo generator functions `agauss`, `gauss`, `aunif`, `unif` (see [Context](context.md)'s builtins table and [functors](rpnfunctor.md)'s `FwGauss`/`FwAgauss`/`FwUnif`/`FwAunif`).

## Why generator identity matters

A Monte Carlo analysis re-evaluates the same instance/model parameter expressions many times: once per sample, and potentially multiple times *within* a sample (elaboration, binning, building the actual analysis structures), and possibly under different `@if` branches if a conditional depends on a binning or sweep parameter. Each call to `gauss()` or similar needs to draw *one* random number per sample, but return the *same* number every time it is re-evaluated within that sample, and that mapping must not depend on which branch of an `@if` happened to be active when the call was written. `RpnEvaluationNetlistContext`/`MCData` solve this by giving every Monte Carlo call site a stable identity and memoizing its drawn value per sample, rather than drawing fresh randomness on every evaluation.

## Generator identity

`MCData::GeneratorId` is `std::tuple<CtxType, Id, Id, RpnArity>` - `(context type, object name, parameter name, call index)`:

- **`CtxType`** distinguishes the syntactic context the expression is being evaluated in: `Condition` (an `@if` condition), `Instance`/`Model` (an instance's or model's own parameter list), `Parameters` (subcircuit-parameter propagation expressions), or `NoContext`.
- **object/parameter `Id`** identify which instance or model and which of its parameters the expression belongs to (see [identifiers](identifier.md)).
- **call index** (`RpnEvaluationNetlistContext::callIndex`, advanced by `nextCallIndex()`) distinguishes multiple Monte Carlo calls appearing in the *same* expression, which would otherwise share an identical `(CtxType, obj, param)` triple.

`RpnEvaluator::evaluate()` resets the call index to 0 before each top-level evaluation (see [Evaluator](rpneval.md)), and `mcGenerator()` (see [builtin operators and functions](rpnbuiltin.md)) advances it after every Monte Carlo call, whether or not that call actually drew a random number - so a second `gauss()` later in the same expression always gets a distinct identity even if the first one was skipped.

`GeneratorIdHash` is a standalone hash functor (passed explicitly to the `unordered_map`, rather than specializing `std::hash<GeneratorId>`) built from `hash_val()`; tuple equality is already correct element-wise via the compiler-generated `std::tuple::operator==`, so only hashing needed a custom functor.

### `Condition` context never draws randomness

`mcGenerator()` only actually consumes randomness (`ctx.mcData()->retrieveOrAdd(...)`) when `ctx.ctxType()` is `Instance`, `Model`, or `Parameters`; anywhere else - including `Condition` - it returns the nominal value untouched. This means a Monte Carlo generator call written inside an `@if` condition is evaluated for its truthiness like any other expression, but does not itself register a generator or consume a sample: conditions are evaluated for control flow, not as parameter values, so they should not participate in the sample's random draws.

## `MCData`: generator memoization

`retrieveOrAdd(genId)` is the memoization point: the first call for a given `GeneratorId` draws (and permanently records) a fresh uniform `(0,1)` sample; every later call with the same `GeneratorId`, within the same sample, returns that same stored value. `advance()` re-draws every already-registered generator's value in place and increments the sample counter - called once per Monte Carlo trial to move all instance parameters to fresh values for the next sample.

Both ordinary and Latin-hypercube sampling explicitly reject exact `0` or `1` (`while (v==0 || v==1)` in `gen()`), since the drawn value feeds `r8_normal_01_cdf_inverse()` (inverse normal CDF, from `asa241`) for the Gaussian generators, where `0` or `1` would map to +-infinity.

### Latin hypercube sampling

When enabled (`setLHSamples(n)`, `n>0`), each generator gets its own random permutation of `n` bins (`lhPerm`, computed lazily the first time that generator is created via `retrieveOrAdd`); `gen()` then draws its uniform sample from the specific bin assigned to the current sample index, `lhPerm[sourceNdx][sample_]`, instead of from the whole `(0,1)` interval - stratifying samples evenly across each generator's range instead of letting them cluster by chance, as plain Monte Carlo sampling can.

### Other operations

`setSeed()` reseeds the underlying `std::mt19937_64` generator for a reproducible run. `count()` and `sample()` report the number of registered generators and the current sample index. `dump()` prints the generator table (id, optional Latin-hypercube bin, drawn value) for debugging; `setDebugStream()`/`debugStream()` let `mcGenerator()` additionally write a one-line trace of every call (generator id, sample, drawn value) as it happens.

## `RpnEvaluationNetlistContext`

A cheap, per-evaluation struct: `cxType_`/`obj` are set once at construction (e.g. `RpnEvaluationNetlistContext(MCData::CtxType::Condition, name())` for an `@if` condition, see `hierdevice.cpp`), `param`/`callIndex` are set by the caller as it evaluates each parameter, and `mcData_` is a non-owning pointer the [evaluator](rpneval.md) wires in via `setMCData()` immediately before running. `generatorId()` packages the current `(cxType_, obj, param, callIndex)` into the `GeneratorId` a Monte Carlo builtin needs.
