## `eigenkit.reservoir.readout`

A pure-numpy ridge (Tikhonov) linear readout — the classic QRC head.

Frozen-reservoir QRC trains *only* the readout, and the canonical readout is a linear layer fit by ridge regression. That has a closed-form solution, so no iterative optimizer (and no torch/sklearn) is needed — this stays framework-free and lives in the numpy domain ring. For a trainable non-linear readout, use the estimator layer (an MLP head), not this.

### `LinearReadout`

Ridge regression in closed form, with a bias term.

It solves `W = (XᵀX + λI)⁻¹ XᵀY`, and the bias column is not regularised. `Y` can have one output or several. `ridge` is the strength `λ` of the L2 penalty, which adds `λ‖W‖²` to the squared error of the fit. A small positive value also keeps the solution numerically stable when the reservoir features are collinear.

Parameters:

| Name    | Type    | Description                                                                                                                                                                   | Default |
| ------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- |
| `ridge` | `float` | The strength λ of the L2 penalty. Raise it when the readout overfits or the feature matrix is nearly singular, and lower it when the readout underfits. The default is 1e-06. | `1e-06` |

#### `fit(X, y)`

Fit the readout to `X` and `y`.

Parameters:

| Name | Type      | Description                                                                               | Default    |
| ---- | --------- | ----------------------------------------------------------------------------------------- | ---------- |
| `X`  | `ndarray` | The samples, of shape (n_samples, n_features).                                            | *required* |
| `y`  | `ndarray` | The targets, of shape (n_samples,) for one output, or (n_samples, n_outputs) for several. | *required* |

Returns:

| Type              | Description                                 |
| ----------------- | ------------------------------------------- |
| `'LinearReadout'` | This readout, so that calls can be chained. |

#### `predict(X)`

Predict the target for every sample in `X`.

Parameters:

| Name | Type      | Description                                    | Default    |
| ---- | --------- | ---------------------------------------------- | ---------- |
| `X`  | `ndarray` | The samples, of shape (n_samples, n_features). | *required* |

Returns:

| Type      | Description                                                                                           |
| --------- | ----------------------------------------------------------------------------------------------------- |
| `ndarray` | One prediction per sample, with one column per output when the readout was fitted on several outputs. |
