# Behavioral capacitor → C-OSDI charge device

This feature turns **behavioral (voltage- and temperature-dependent) capacitors** in a
netlist into **charge-conserving native OSDI devices**, generated as C and compiled with
the system `gcc` at parse time. It is the capacitor counterpart of the resistor generator
(`docs/osdi_res_codegen/`), and it removes the choice between *"accurate but won't
simulate"* and *"simulates but inaccurate"* described in `docs/osdi_codegen_overview/`.

A capacitor whose value depends on its terminal voltage and/or temperature, e.g. the
foundry MIM cap

```
cmim top bottom c='Cbase*(1 + v(top,bottom)*vc1 + v(top,bottom)^2*vc2)'
```
(with `Cbase` itself temperature-dependent), is compiled into a native charge device
— **no linear-cap workaround, no `vc1/vc2/tc1/tc2` model, no netlist edit.**

---

## Why capacitors are the hard case

A resistor is **resistive**: `i = f(v)` is instantaneous. The value and its Jacobian
`di/dv` are all you need, and both come straight from ngspice's expression engine.

A capacitor is **reactive**: the physical primitive is **charge** `Q`, and the current
is `i = dQ/dt`. That changes everything:

1. **It's a time derivative.** The behavioral path `C='f(v)'` makes ngspice build a
   differentiator (`E`/`C`/`B` network forming `f(v)·dv/dt`). For a nonlinear cap this
   produces gigaamp internal currents → `Timestep too small` → the run dies at the
   initial timepoint. This is the exact failure the whole effort exists to fix.

2. **You need charge, not capacitance.** A charge-conserving device stores `Q(V)`.
   The netlist may give either:
   - **charge form** `Q='q(v)'` — you already have `Q`, or
   - **capacitance form** `C='c(v)'` — you have `c = dQ/dV` and must recover
     `Q(V) = ∫₀ᵛ c(u) du`, an **integral**. ngspice can *differentiate* symbolically
     but cannot *integrate* symbolically.

## The OSDI reactive ABI makes charge conservation free

The generated device writes the node **charge** `Q` into the OSDI *reactive residual*
slot. ngspice then stores it in the state vector, calls its own numerical integrator
(`NIintegrate`) to form `dQ/dt` **charge-conservingly**, and subtracts it from the RHS.
The linearized companion terms come from the device's reactive Jacobian
`dQ/dV` (`load_jacobian_tran(alpha)` adds `alpha·dQ/dV` into the matrix). Each reactive
node uses two ngspice-managed state slots.

**Consequence:** if the device outputs `Q(V)` and `dQ/dV` instead of `i` and `di/dv`,
ngspice integrates it exactly like a hand-written charge model — no differentiator, no
gigaamp, charge-conserving by construction. So the generator reuses the resistor
emitter, just aimed at the reactive slots.

## Three paths, in order of preference

The pass picks the most accurate path each behavioral cap allows:

### 1. Charge form `Q='q(v)'` — exact, fully general
`Q` is emitted directly; `dQ/dV` is ngspice's analytic derivative of `q`. Any
differentiable charge law works and is **bit-exact**. (This is the ideal way for a
foundry to ship a charge-conserving cap.)

### 2. Capacitance form `C='c(v)'`, polynomial `c` — exact
The `vc1/vc2` family (`c = C0·(1 + vc1·v + vc2·v²)`) and any polynomial-in-`v`
capacitance has a closed-form integral. The pass recovers the polynomial coefficients by
**sampling** `c(V)` through ngspice's evaluator and solving a Vandermonde system, then
**verifies** the fit on off-grid *and negative* points (to reject `abs()`, kinks, and
anything non-polynomial), and integrates term by term:
`Q = Σ aₖ/(k+1)·V^(k+1)`. Powers are built by explicit multiplication so the sign is
correct for odd powers. This path is **bit-exact vs the native charge cap.**

### 3. Capacitance form `C='c(v)'`, arbitrary `c` — general, via quadrature
This is the significant, general result. For **any** differentiable capacitance —
`tanh`, `exp`, `sqrt`, `abs`, temperature-dependent, whatever the foundry writes — there
is no closed-form integral, so the device computes the charge by **numerical quadrature
inside `eval`**:

```
Q(V)    = (V/2) · Σ wᵢ · c(sᵢ)                         sᵢ = (V/2)(1 + xᵢ)
dQ/dV   = ½ Σ wᵢ c(sᵢ) + (V/4) Σ wᵢ (1+xᵢ) c'(sᵢ)      (exact derivative of the above)
```
with an 8-point **Gauss–Legendre** rule `(xᵢ, wᵢ)` and `c`, `c' = dc/dv` emitted as C
helpers evaluated at the Gauss nodes. This is:

- **general** — works for any differentiable `c(v, temper)`;
- **charge-conserving** — `Q` is a deterministic function of `V`;
- **consistent** — `dQ/dV` is the *exact* analytic derivative of the quadrature that
  produces `Q`, so Newton converges (it is not an approximation that fights the residual);
- **exact for polynomials** up to degree 15 (so it coincides with path 2 there);
- **temperature-correct** — `temper` enters `c` as a parameter of the integrand, carried
  through the integral automatically.

If a cap contains something genuinely non-static (`ddt`, `pwl`, `time`, `hertz`) or is a
degenerate 2-node-shorted device, the pass leaves the behavioral network in place
(fallback), so nothing regresses.

## `m` (mfactor)

`m` is a first-class parameter: charge and reactive Jacobian scale linearly with it, so
one device with `m=k` stands in for `k` parallel caps.

## Deduplication — one model per *law*, many *instances*

As with the resistors, a `.osdi` is a compiled device **type**, not a per-instance
object. Caps with the same equation **structure** share one compiled model; their
differing numbers (geometry, coefficients, temperature scaling) ride as per-instance
parameters. On the full chip, all the foundry MIM caps collapse to **one** charge model.

## Enabling / disabling

OSDI is the default. Set in `.spiceinit` (read at parse time):
```
set ngbehavior=hsa          ; for HSPICE-style c='...' syntax
                            ; OSDI cap codegen is ON by default
* set osdi_res_bsource      ; force the old behavioral path for R and C
```

## Validation

- **Linear** `Q=C0·v` = an ideal capacitor (RC step, to solver tolerance).
- **Polynomial `C`** `C0·(1+vc1·v+vc2·v²)` = the native `vc1/vc2` cap, **bit-exact** in
  transient.
- **Nonlinear `C=tanh`** (quadrature) = the exact analytic charge
  `C0·(v + 0.15·ln cosh 2v)` to **1e-8 V** in transient.
- **Temperature** `C(v,T) = C0·(1+tc·(T−25))·(1+vc1·v)` = exact charge at **25 °C and
  125 °C**, bit-exact.
- **Full chip** on the raw foundry lib: real MIM caps → one charge model, transient
  stable past the initial timepoint (where the behavioral differentiator always failed).

## How it works (internals)

```
C n1 n2 C='c(v,temper)'                (or Q='q(v,temper)')
        │  inp_compat (inpcom.c): emit a tagged card b<name>_oscapc/_oscapq
        ▼  (name-suffix tag survives numparam + subckt expansion; params become numeric)
   osdi_res_transform()  (src/frontend/osdi_rescodegen.c)
        │  Q-form  -> emit Q directly
        │  C-form  -> polynomial integral if possible, else Gauss-Legendre quadrature device
        │  emit C  ->  gcc -shared -fPIC  ->  load_osdi()   (dedup by structural signature)
        ▼
N<name> n1 n2 osdicapK m=<m> p0=.. ..   (+ ".model osdicapK osdicapK()")
```

The generated OSDI device is 2-terminal, charge on both nodes (`±Q`), reactive Jacobian
`{C,−C,−C,C}`, mfactor-scaled; `temper` is delivered as the instance temperature.

### Files
| File | Role |
|---|---|
| `src/frontend/osdi_rescodegen.c` | `gen_cap_model_c` (charge device), `gen_cap_quad_model_c` (quadrature device), `cap_charge_from_C` (polynomial integrator), the pass, the `osdigenc` command |
| `src/frontend/inpcom.c` | tag behavioral caps at the `C=`/`Q=` transformation |
| `src/frontend/inp.c` | run the pass after subckt expansion |

## `osdigenc` — manual/debug command
```
osdigenc <model> <n1> <n2> <charge-expression>
```
Emits the C for a single charge device from a `Q(v)` expression (compile with
`gcc -shared -fPIC -I<src/osdi> model.c -o model.osdi -lm`). Useful for inspection.

## Limitations
- Two-terminal caps only. A cap with both terminals on one node (`n1==n2`, e.g. layout
  dummy/matching caps) is a no-op and is left as the harmless behavioral fallback.
- `ddt`/`pwl`/`time`/`hertz` in the value → behavioral fallback.
- An explicit `tc1=`/`tc2=` *instance parameter* on the cap → behavioral fallback
  (temperature written **inside** the value expression is fully supported via quadrature).
- Generated `.c`/`.osdi` files are written to the current working directory.
