# Estimators

eigenkit includes quantum estimators that are compatible with sklearn and built on PyTorch. They are hybrid classifiers and regressors, their recurrent versions, wrappers around your own networks, and adapters for time series. Every estimator follows the standard sklearn interface, `fit(X, y)`, `predict(X)` and `score(X, y)`, and works with `GridSearchCV`, `Pipeline`, `clone` and the rest of sklearn. Import them from the top level: `from eigenkit import HQNC`.

**Install:** these estimators need the `torch` extra (`pip install eigenkit[torch]`). They run on the built-in NumPy simulator. Another backend needs its own extra, for example `eigenkit[pennylane]` for `provider="pennylane"`.

You can save a fitted model to a **`.eigenkit`** bundle, which is a zip file, and load it again with `save()` and `load()`, or with `load_model()`. See [Model persistence](https://docs.kit.eigenorbit.com/user-guide/model-persistence/index.md) for the format of the bundle, the supported classes, and how the benchmark uses `save_dir`.

How to get the trained network: `model_`

`fit()` trains a deep copy of the network, so it never changes the module that you gave to the estimator. After training, the object that you passed to `GenericClassifier(model=...)` therefore still has its initial weights, whatever the network did. This comes from the copy, and says nothing about the model. `model_` returns the network that was actually trained. It exists on every estimator that trains a PyTorch network (all the estimators of this page except the time-series wrappers), and it raises `NotFittedError` before `fit()`.

```python
from torch import nn
from eigenkit import Basic, GenericClassifier
from eigenkit.datasets import load_dataset

data = load_dataset("iris")
X_train, y_train = data["X_train"], data["y_train"]

layer = Basic(n_qubits=4, depth=2)
clf = GenericClassifier(model=nn.Sequential(layer, nn.Linear(4, 3)), in_features=4)
clf.fit(X_train, y_train)

layer.state_dict()  # unchanged, this object never trained
clf.model_[0].state_dict()  # the trained weights
```

## Classifiers

`HQNC` uses the PyTorch loss `CrossEntropyLoss`, and `train_losses_` stores the cross-entropy of each epoch. When you pass validation data with `fit(..., X_val=..., y_val=...)`, `validation_losses_` stores the matching validation curve. `n_classes` is optional on every classifier of this section. If you leave it out, the set of labels is inferred from `y` when you call `fit()`.

The examples of this section use the built-in `iris` dataset. It has `4` features and `3` classes, which is what the constructors below are sized for:

```python
from eigenkit.datasets import load_dataset

data = load_dataset("iris")
X_train, y_train = data["X_train"], data["y_train"]
X_test, y_test = data["X_test"], data["y_test"]
```

### HQNC

`HQNC` is the hybrid quantum neural network classifier. It places a quantum layer in a small `nn.Sequential` pipeline, and trains the whole pipeline together with the cross-entropy loss.

```python
from eigenkit import HQNC

model = HQNC(
    in_features=4,
    depth=2,
    n_classes=3,
    epochs=30,
)
model.fit(X_train, y_train)
predictions = model.predict(X_test)
print(model.score(X_test, y_test))  # accuracy
print(model.train_losses_)  # per-epoch loss
```

You choose the quantum layer with `layer_type`:

| `layer_type`        | Quantum layer                                 | Notes                                       |
| ------------------- | --------------------------------------------- | ------------------------------------------- |
| `"Basic"` (default) | One encoding block and one variational block. | It needs `n_qubits == in_quantum`.          |
| `"Reupload"`        | Standard data reuploading.                    | It needs `n_qubits == in_quantum`.          |
| `"Ensemble"`        | `N` independent circuits.                     | It needs `n_qubits` to divide `in_quantum`. |

Note: if `n_qubits` and `in_quantum` differ for `Basic` or `Reupload`, a linear adapter `Linear(in_features, n_qubits)` is added in front automatically, and a `UserWarning` is raised.

`enc_rotation`, `var_rotation`, `variational_type` and `basis` are passed to that layer unchanged. Their default is `None`, which means "the value that the layer you named uses". So the architecture that an estimator builds is the one that the [layer page](https://docs.kit.eigenorbit.com/user-guide/layers/index.md) describes, and if you build it yourself from that page, you get the same model. Pass any of these four arguments to replace the default of the layer.

```python
# Ensemble of 4-qubit circuits over 16 features.
model = HQNC(in_features=16, n_qubits=4, layer_type="Ensemble", depth=2)
```

### RecurrentHQNC

`RecurrentHQNC` is a hybrid classifier for sequences. An encoder that is a recurrent neural network (RNN), of type `"rnn"`, `"gru"` or `"lstm"`, reads the sequence and passes the encoded vector to a quantum layer like the one of `HQNC`. The whole model is trained together, with one optimiser and one loss, so the RNN and the quantum block are updated at the same time.

`X` must be 3-D, with shape `(n_samples, seq_len, seq_feature_dim)`. By default, only the last time step of the RNN goes to the quantum block. To flatten the whole sequence instead, set `rnn_return_last_timestep=False` and also give `sequence_length`.

```python
import numpy as np
from eigenkit import RecurrentHQNC

X = np.random.randn(32, 10, 4).astype(np.float32)
y = np.where(X[:, -1, 0] > 0, "up", "down")

model = RecurrentHQNC(
    seq_feature_dim=4,
    rnn_type="gru",
    rnn_hidden_dim=16,
    depth=2,
    epochs=30,
)
model.fit(X, y)
predictions = model.predict(X)
probabilities = model.predict_proba(X)
```

The quantum layer takes the same arguments as in `HQNC`: `layer_type`, `n_qubits`, `enc_rotation`, `var_rotation`, `variational_type` and `basis`. See the [table of `layer_type` values of `HQNC`](#hqnc) above. The other options of the RNN are `rnn_num_layers`, `rnn_dropout` and `rnn_bidirectional`.

### CustomQuantumClassifier

`CustomQuantumClassifier` builds its architecture from any list of blocks. Use it when none of the `layer_type` presets fits:

```python
from eigenkit import (
    Encoding,
    Variational,
    StrongVariational,
    all_z,
)
from eigenkit import CustomQuantumClassifier

blocks = [
    Encoding(in_features=4, rotation="RY"),
    StrongVariational(depth=2),
    Variational(depth=1, rotation="RX"),
]
model = CustomQuantumClassifier(
    blocks=blocks,
    observables=all_z(4),
    n_classes=2,
    epochs=30,
)

# This estimator is configured for two classes, so keep iris labels 0 and 1.
binary = y_train < 2
model.fit(X_train[binary], y_train[binary])
predictions = model.predict(X_test)
```

`observables=` accepts the same forms as `Circuit(observables=...)` (see [Observables](https://docs.kit.eigenorbit.com/user-guide/observables/index.md)). When the number of features of the data differs from the input size of the circuit, an `nn.Linear` adapter is added in front automatically. The input size of the circuit is inferred from the first `Encoding` or `Reuploading` block.

## Regressors

The examples of this section use the built-in `concrete` dataset. It has `8` features and a continuous target. The examples use only part of the data, so that they run quickly:

```python
from eigenkit.datasets import load_dataset

data = load_dataset("concrete")
X_train, y_train = data["X_train"][:200], data["y_train"][:200]
X_test, y_test = data["X_test"][:50], data["y_test"][:50]
```

### HQNR

`HQNR` is the regression version of `HQNC`. Its output is a single neuron, trained with the regression loss that you choose.

```python
from eigenkit import HQNR

model = HQNR(in_features=8, depth=2, epochs=50)
model.fit(X_train, y_train)
predictions = model.predict(X_test)
print(model.score(X_test, y_test))  # R^2
```

It takes the same `layer_type` and `n_qubits` arguments as `HQNC`. You choose the loss with `loss_func`: `"mse"`, `"mae"` or `"huber"`.

### RecurrentHQNR

`RecurrentHQNR` is a hybrid regressor for sequences. An RNN encoder, of type `"rnn"`, `"gru"` or `"lstm"`, reads the sequence and passes the encoded vector to a quantum layer like the one of `HQNR`. The whole model is trained together, with one optimiser and one loss, so the RNN and the quantum block are updated at the same time.

`X` must be 3-D, with shape `(n_samples, seq_len, seq_feature_dim)`. By default, only the last time step of the RNN goes to the quantum block. To flatten the whole sequence instead, set `rnn_return_last_timestep=False` and also give `sequence_length`.

```python
import numpy as np
from eigenkit import RecurrentHQNR

X = np.random.randn(32, 10, 4).astype(np.float32)
last = X[:, -1, :]
y = (
    0.5 * last[:, 0]
    + 0.3 * np.sin(last[:, 1])
    + 0.2 * np.cos(2 * np.pi * last[:, 2])
    + 0.1 * last[:, 3] * last[:, 0]
).astype(np.float32)

model = RecurrentHQNR(
    seq_feature_dim=4,
    rnn_type="gru",
    rnn_hidden_dim=16,
    depth=2,
    epochs=30,
)
model.fit(X, y)
predictions = model.predict(X)
```

### CustomQuantumRegressor

`CustomQuantumRegressor` is the regression version of `CustomQuantumClassifier`. It builds the circuit from a list of blocks, and adds a `Linear` head with a single neuron, trained with the regression loss that you choose.

## Generic estimators

`GenericClassifier` and `GenericRegressor` are wrappers, compatible with sklearn, that train **any** `torch.nn.Module` through the standard `fit` and `predict` interface. `HQNC` and `HQNR` build the architecture of the model for you. The generic estimators instead expect a complete network, output head included, so you have full control over the architecture.

Pre-trained weights and the behaviour of `fit()`

`fit()` does not start from pre-trained weights. It copies the `nn.Module`, and then calls `reset_parameters()` on every submodule that has this method (such as `nn.Linear` and the quantum layers of eigenkit), seeded with `random_state`. So the weights of these layers are drawn again before training, even if the module that you pass already holds trained weights. Each later call to `fit()` (for example inside cross-validation, or after `clone()`) also starts again from new weights, not from the weights of the previous `fit()`. To keep the trained weights, use the fitted estimator, or save it with `save()` and load it again with `load()`, and do not call `fit()` on it again.

`random_state` does not reseed your process

Every estimator applies `random_state` inside `fit()` and `load()` on a private copy of the state of the PyTorch random number generators, that is the generator of the central processing unit (CPU) and the generator of every CUDA device. Two runs with the same `random_state` give the same model, on the CPU and on the graphics processing unit (GPU), and neither run changes a seed that you set yourself.

Only `model` is required. If you leave out `in_features` (default `None`), the number of features is taken from the data. `sequence_input` (default `False`) tells the estimator that `X` has shape `(batch, timesteps, features)` instead of `(batch, features)`. The training arguments are `lr` (`0.01`), `optimizer` (`"adam"`), `optimizer_kwargs` (`None`), `epochs` (`50`), `batch_size` (`32`), `show_progress` (`True`) and `random_state` (`42`). `GenericClassifier` adds `n_classes`, and `GenericRegressor` adds `loss_func`, `huber_delta` and `huber_reduction`. None of these arguments is about the circuit, because the network is yours.

The examples below use `moons` for the classifier and `diabetes` for the regressor, which match the input sizes of their networks:

```python
from eigenkit.datasets import load_dataset

moons = load_dataset("moons")
X_train, y_train = moons["X_train"], moons["y_train"]
X_test, y_test = moons["X_test"], moons["y_test"]

diabetes = load_dataset("diabetes")
X_reg, y_reg = diabetes["X_train"][:200], diabetes["y_train"][:200]
X_reg_test, y_reg_test = diabetes["X_test"][:50], diabetes["y_test"][:50]
```

### GenericClassifier

```python
from torch import nn
from eigenkit import GenericClassifier
from eigenkit import Basic

N_QUBITS, N_CLASSES = 4, 2

clf_net = nn.Sequential(
    nn.Linear(2, N_QUBITS),
    nn.Tanh(),
    Basic(n_qubits=N_QUBITS, depth=2),
    nn.Linear(N_QUBITS, N_CLASSES),
)

clf = GenericClassifier(
    model=clf_net,
    in_features=2,
    epochs=30,
    lr=0.01,
    batch_size=16,
    random_state=42,
)
clf.fit(X_train, y_train)
print(clf.score(X_test, y_test))  # accuracy
labels = clf.predict(X_test)
probs = clf.predict_proba(X_test)  # shape (n_samples, n_classes)
```

The `forward()` method of the network must return logits of shape `(batch, n_classes)`. The wrapper encodes the labels automatically, so `y` can hold integers, strings or any other hashable labels.

`in_features` is optional but recommended. When it is set, `fit` raises an error at once if the shape of the data does not match.

### GenericRegressor

```python
from torch import nn
from eigenkit import GenericRegressor
from eigenkit import Basic

N_QUBITS_REG, IN_FEATURES = 6, 10

reg_net = nn.Sequential(
    nn.Linear(IN_FEATURES, N_QUBITS_REG),
    nn.Tanh(),
    Basic(n_qubits=N_QUBITS_REG, depth=2),
    nn.Linear(N_QUBITS_REG, 1),
)

reg = GenericRegressor(
    model=reg_net,
    in_features=IN_FEATURES,
    loss_func="mse",
    epochs=50,
    lr=0.01,
    batch_size=32,
    random_state=42,
)
reg.fit(X_reg, y_reg)
print(reg.score(X_reg_test, y_reg_test))  # R²
preds = reg.predict(X_reg_test)  # float32 array, shape (n_samples,)
```

The `forward()` method of the network must return shape `(batch,)` or `(batch, 1)`. The losses available by name are `"mse"`, `"mae"` and `"huber"`. Any callable `(output, target) -> scalar_tensor` is also accepted, but a model trained with a callable loss cannot be saved.

### Save and load

Generic estimators use the same `.eigenkit` bundle format as the other estimators, with one difference: the bundle does not store the architecture. You must pass a module with the **same `nn.Module` structure** to `.load()`:

```python
clf.save("clf_hybrid.eigenkit")

clf_net_fresh = nn.Sequential(
    nn.Linear(2, N_QUBITS),
    nn.Tanh(),
    Basic(n_qubits=N_QUBITS, depth=2),
    nn.Linear(N_QUBITS, N_CLASSES),
)

clf_loaded = GenericClassifier.load("clf_hybrid.eigenkit", model=clf_net_fresh)
```

`load_model()` also works for generic estimators, but the `model=` argument is required, because the bundle does not store the architecture and it cannot be inferred. If you call either method without `model=`, it raises `SerializationError`.

### sklearn compatibility

`GenericClassifier` inherits from `ClassifierMixin` and `GenericRegressor` from `RegressorMixin`, so both work with `Pipeline`, `cross_val_score`, `GridSearchCV` and `clone`:

```python
from sklearn.base import clone
from sklearn.model_selection import cross_val_score

scores = cross_val_score(clf, X_train, y_train, cv=3, scoring="accuracy")
cloned = clone(clf)  # unfitted copy with the same hyperparameters
```

See the [Generic Estimators example](https://docs.kit.eigenorbit.com/examples/generic-estimators/index.md) for a complete example.

## Time series

`QuantumTimeSeriesRegressor` and `QuantumTimeSeriesClassifier` wrap an inner estimator and apply a sliding window to data in time order. Each row of `X` is one time step. With a window of length `in_size`, the inner model receives a 3-D batch of shape `(n_windows, in_size, n_features)`, and the window that starts at row `i` predicts `y[i + in_size - 1 + shift]`. With `autoregressive=True`, an extra channel is added to each time step, and it holds the target value at that step.

For tabular estimators (`HQNR`, `CustomQuantumRegressor`, and so on), the wrapper flattens each window before it calls `fit` or `predict`. For `RecurrentHQNR` and `RecurrentHQNC`, and for the generic estimators with `sequence_input=True`, the windows stay 3-D, so that the RNN sees the time axis.

```python
import numpy as np
from eigenkit import (
    QuantumTimeSeriesRegressor,
    RecurrentHQNR,
)

n_feat = 5
window = 10
X = np.random.randn(200, n_feat).astype(np.float32)
y = X[:, 0] + 0.1 * np.random.randn(200).astype(np.float32)

inner = RecurrentHQNR(
    seq_feature_dim=n_feat,
    rnn_type="gru",
    rnn_hidden_dim=16,
    depth=2,
)
model = QuantumTimeSeriesRegressor(estimator=inner, in_size=window, shift=1)
model.fit(X, y)
predictions = model.predict(X)  # length n_timesteps - in_size - shift + 1
```

Classification uses the same alignment of the windows:

```python
import numpy as np
from eigenkit import (
    QuantumTimeSeriesClassifier,
    RecurrentHQNC,
)

n_feat = 5
window = 10
X = np.random.randn(200, n_feat).astype(np.float32)
y = np.where(X[:, 0] > 0, "up", "down")

inner = RecurrentHQNC(
    seq_feature_dim=n_feat,
    rnn_type="gru",
    rnn_hidden_dim=16,
    depth=2,
)
model = QuantumTimeSeriesClassifier(estimator=inner, in_size=window, shift=1)
model.fit(X, y)
predictions = model.predict(X)  # length n_timesteps - in_size - shift + 1
probabilities = model.predict_proba(X)
```

**Autoregressive mode.** Set `autoregressive=True` to add the past values of the target to each window, as an extra channel of features. This extra channel counts as a feature: the inner estimator must be built with `seq_feature_dim = n_feat + 1`, and if you reuse the one above, you get `ValueError: Last dimension of X must equal seq_feature_dim=5, got 6`. When you predict, you must pass the known history as `y_history`:

```python
# The appended target channel makes it n_feat + 1 inputs, so this needs its
# own inner estimator.
inner_ar = RecurrentHQNC(
    seq_feature_dim=n_feat + 1,
    rnn_type="gru",
    rnn_hidden_dim=16,
    depth=2,
)

# Fit on the head of the series, predict on the tail.
X_hist, y_hist = X[:150], y[:150]
X_future, y_future = X[150:], y[150:]

model_ar = QuantumTimeSeriesClassifier(
    estimator=inner_ar,
    in_size=window,
    shift=1,
    autoregressive=True,
)
model_ar.fit(X_hist, y_hist)
predictions = model_ar.predict(X_future, y_history=y_future)
probabilities = model_ar.predict_proba(X_future, y_history=y_future)
```

## sklearn compatibility

Every algorithm class is a subclass of `sklearn.base.BaseEstimator`, and stores each argument of its constructor as an attribute of the same name on `self`, as sklearn expects. This is what makes `clone`, `Pipeline`, `cross_val_score` and `GridSearchCV` work without extra code.

```python
from sklearn.base import clone
from sklearn.model_selection import cross_val_score

cloned = clone(model)
scores = cross_val_score(model, X, y, cv=5)
```

```python
from sklearn.model_selection import GridSearchCV
from eigenkit import HQNC
from eigenkit.datasets import load_dataset

iris = load_dataset("iris")
binary = iris["y_train"] < 2  # the estimator below declares two classes
X_iris, y_iris = iris["X_train"][binary], iris["y_train"][binary]

grid = GridSearchCV(
    HQNC(in_features=4, n_classes=2),
    param_grid={"depth": [1, 2], "epochs": [20, 40]},
    cv=3,
)
grid.fit(X_iris, y_iris)
best = grid.best_estimator_
```

`BaseQuantumClassifier` inherits from `ClassifierMixin`, so its `score()` returns the accuracy. `BaseQuantumRegressor` inherits from `RegressorMixin`, so its `score()` returns `R²`.

Import the base classes when you build your own estimators:

```python
from eigenkit import (
    BaseQuantumClassifier,
    BaseQuantumEstimator,
    BaseQuantumRegressor,
)
```

## Choosing a backend

Every estimator accepts the same `provider=` argument as `Circuit`: the name of a registered backend as a string, a configuration dataclass, or a `Backend` instance.

```python
from eigenkit import PennyLaneConfig

# String. Requires the matching extra, here `qiskit`.
HQNC(in_features=4, depth=2, provider="qiskit")

# Config: forwards device-level options. Requires the `pennylane` extra.
HQNC(
    in_features=4,
    depth=2,
    provider=PennyLaneConfig(device_name="lightning.qubit", shots=None),
)
```

## API reference

### `eigenkit.qml.BaseQuantumEstimator`

Bases: `BaseEstimator`, `ABC`

Base class, compatible with scikit-learn, for all quantum algorithms.

Parameters:

| Name           | Type           | Description                                                                                                                                                                                                   | Default                                                                                                                                                                                                                                                                                                                                                                  |
| -------------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `provider`     | `ProviderSpec` | The backend. It can be a registered name ("pennylane", "qiskit", "braket", or any name passed to register()), a configuration instance (for example PennyLaneConfig(device_name=...)), or a backend instance. | `'numpy'`                                                                                                                                                                                                                                                                                                                                                                |
| `random_state` | \`int          | None\`                                                                                                                                                                                                        | The seed for reproducibility, applied inside fit() and load(). What it seeds depends on the estimator. The PyTorch estimators isolate the random number generator of PyTorch, so seeding one of them does not reseed the random stream of the caller. The kernel estimators are deterministic, and they take it only so that all the estimators have the same interface. |

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

Fit the estimator on `X` and `y`.

Parameters:

| Name | Type      | Description                                        | Default    |
| ---- | --------- | -------------------------------------------------- | ---------- |
| `X`  | `ndarray` | The samples, of shape (n_samples, n_features).     | *required* |
| `y`  | `ndarray` | One target for each sample, of shape (n_samples,). | *required* |

Returns:

| Type                     | Description                                   |
| ------------------------ | --------------------------------------------- |
| `'BaseQuantumEstimator'` | This estimator, so that calls can be chained. |

#### `load(path, *, provider=None)`

Load a fitted model from a `.eigenkit` bundle.

Load only bundles that you trust. A bundle can contain joblib pickles, and unpickling runs code, as with `torch.load` or `joblib.load`.

Parameters:

| Name       | Type           | Description | Default                                 |
| ---------- | -------------- | ----------- | --------------------------------------- |
| `path`     | \`str          | Path\`      | The path of the bundle.                 |
| `provider` | \`ProviderSpec | None\`      | A provider that replaces the saved one. |

Returns:

| Type                     | Description           |
| ------------------------ | --------------------- |
| `'BaseQuantumEstimator'` | The loaded estimator. |

Raises:

| Type        | Description                                                                                                                  |
| ----------- | ---------------------------------------------------------------------------------------------------------------------------- |
| `TypeError` | If the method is called on a subclass and the bundle holds a different class. BaseQuantumEstimator.load() accepts any class. |

#### `predict(X)`

Predict a value 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 for each sample. |

#### `save(path)`

Save a fitted model to a `.eigenkit` bundle.

The bundles of models that hold scikit-learn objects (`QSVC`, `QSVR`, the reservoir estimators and the time-series estimators) contain joblib pickles, so loading one can run code. Share them only with people who trust you.

Parameters:

| Name   | Type  | Description | Default                                                                   |
| ------ | ----- | ----------- | ------------------------------------------------------------------------- |
| `path` | \`str | Path\`      | The path of the bundle. The suffix .eigenkit is added when it is missing. |

### `eigenkit.qml.BaseQuantumClassifier`

Bases: `ClassifierMixin`, `BaseQuantumEstimator`

Base class for quantum classifiers. Its `score()` returns the accuracy.

#### `predict_proba(X)`

Predict the class probabilities.

Subclasses should return the probabilities in the order of `classes_`, with shape `(n_samples, n_classes)`. The default implementation raises a `NotImplementedError`.

Parameters:

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

Returns:

| Type      | Description                                                                                     |
| --------- | ----------------------------------------------------------------------------------------------- |
| `ndarray` | The probabilities, of shape (n_samples, n_classes), with the columns in the order of classes\_. |

### `eigenkit.qml.BaseQuantumRegressor`

Bases: `RegressorMixin`, `BaseQuantumEstimator`

Base class for quantum regressors. Its `score()` returns `R²`.

### `eigenkit.torch.estimators`

The PyTorch estimators: differentiable hybrid quantum-classical models.

The estimator base classes compatible with scikit-learn, the quantum kernels, the support vector machines and the saving and loading of models are in a separate package, `eigenkit.qml`, which does not need PyTorch. The base classes and `load_model` are also exported here for convenience, because the hybrid estimators build on them.

#### `BaseQuantumClassifier`

Bases: `ClassifierMixin`, `BaseQuantumEstimator`

Base class for quantum classifiers. Its `score()` returns the accuracy.

##### `predict_proba(X)`

Predict the class probabilities.

Subclasses should return the probabilities in the order of `classes_`, with shape `(n_samples, n_classes)`. The default implementation raises a `NotImplementedError`.

Parameters:

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

Returns:

| Type      | Description                                                                                     |
| --------- | ----------------------------------------------------------------------------------------------- |
| `ndarray` | The probabilities, of shape (n_samples, n_classes), with the columns in the order of classes\_. |

#### `BaseQuantumEstimator`

Bases: `BaseEstimator`, `ABC`

Base class, compatible with scikit-learn, for all quantum algorithms.

Parameters:

| Name           | Type           | Description                                                                                                                                                                                                   | Default                                                                                                                                                                                                                                                                                                                                                                  |
| -------------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `provider`     | `ProviderSpec` | The backend. It can be a registered name ("pennylane", "qiskit", "braket", or any name passed to register()), a configuration instance (for example PennyLaneConfig(device_name=...)), or a backend instance. | `'numpy'`                                                                                                                                                                                                                                                                                                                                                                |
| `random_state` | \`int          | None\`                                                                                                                                                                                                        | The seed for reproducibility, applied inside fit() and load(). What it seeds depends on the estimator. The PyTorch estimators isolate the random number generator of PyTorch, so seeding one of them does not reseed the random stream of the caller. The kernel estimators are deterministic, and they take it only so that all the estimators have the same interface. |

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

Fit the estimator on `X` and `y`.

Parameters:

| Name | Type      | Description                                        | Default    |
| ---- | --------- | -------------------------------------------------- | ---------- |
| `X`  | `ndarray` | The samples, of shape (n_samples, n_features).     | *required* |
| `y`  | `ndarray` | One target for each sample, of shape (n_samples,). | *required* |

Returns:

| Type                     | Description                                   |
| ------------------------ | --------------------------------------------- |
| `'BaseQuantumEstimator'` | This estimator, so that calls can be chained. |

##### `load(path, *, provider=None)`

Load a fitted model from a `.eigenkit` bundle.

Load only bundles that you trust. A bundle can contain joblib pickles, and unpickling runs code, as with `torch.load` or `joblib.load`.

Parameters:

| Name       | Type           | Description | Default                                 |
| ---------- | -------------- | ----------- | --------------------------------------- |
| `path`     | \`str          | Path\`      | The path of the bundle.                 |
| `provider` | \`ProviderSpec | None\`      | A provider that replaces the saved one. |

Returns:

| Type                     | Description           |
| ------------------------ | --------------------- |
| `'BaseQuantumEstimator'` | The loaded estimator. |

Raises:

| Type        | Description                                                                                                                  |
| ----------- | ---------------------------------------------------------------------------------------------------------------------------- |
| `TypeError` | If the method is called on a subclass and the bundle holds a different class. BaseQuantumEstimator.load() accepts any class. |

##### `predict(X)`

Predict a value 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 for each sample. |

##### `save(path)`

Save a fitted model to a `.eigenkit` bundle.

The bundles of models that hold scikit-learn objects (`QSVC`, `QSVR`, the reservoir estimators and the time-series estimators) contain joblib pickles, so loading one can run code. Share them only with people who trust you.

Parameters:

| Name   | Type  | Description | Default                                                                   |
| ------ | ----- | ----------- | ------------------------------------------------------------------------- |
| `path` | \`str | Path\`      | The path of the bundle. The suffix .eigenkit is added when it is missing. |

#### `BaseQuantumRegressor`

Bases: `RegressorMixin`, `BaseQuantumEstimator`

Base class for quantum regressors. Its `score()` returns `R²`.

#### `CustomQuantumClassifier`

Bases: `ModelAccessMixin`, `BaseQuantumClassifier`

Classifier compatible with scikit-learn, built from your own list of blocks.

Give any sequence of blocks (`Encoding`, `Variational`, `StrongVariational`, `Reuploading`, or a custom block from `CircuitBuilder.to_block()`) and an `observables` specification, as for `Circuit`. The estimator connects them into a quantum layer followed by a classical `nn.Linear` head, and trains the whole model with the cross-entropy loss. When the number of features of the data differs from the number of inputs that the circuit expects, an `nn.Linear` adapter is added in front automatically.

Parameters:

| Name               | Type             | Description                                                                                               | Default                                                                                                   |
| ------------------ | ---------------- | --------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- |
| `blocks`           | `list[Block]`    | The list of blocks, in order.                                                                             | *required*                                                                                                |
| `observables`      | `ObservableLike` | The observables. It takes the same options as Circuit(observables=...). Default: None (Z on every qubit). | `None`                                                                                                    |
| `provider`         | `ProviderSpec`   | The backend. Default: "numpy".                                                                            | `'numpy'`                                                                                                 |
| `in_features`      | \`int            | None\`                                                                                                    | The number of features of the data. If None, it is inferred from the first Encoding or Reuploading block. |
| `n_classes`        | \`int            | None\`                                                                                                    | The number of output classes. If None, it is inferred from the training labels.                           |
| `lr`               | `float`          | The learning rate of the optimiser.                                                                       | `0.01`                                                                                                    |
| `optimizer`        | `str`            | The name of the optimiser. The supported names are "adam", "adamw", "rmsprop" and "sgd".                  | `'adam'`                                                                                                  |
| `optimizer_kwargs` | \`dict           | None\`                                                                                                    | Extra keyword arguments passed to the optimiser.                                                          |
| `epochs`           | `int`            | The number of training epochs.                                                                            | `50`                                                                                                      |
| `batch_size`       | `int`            | The size of each mini-batch.                                                                              | `32`                                                                                                      |
| `show_progress`    | `bool`           | If True, a tqdm progress bar is shown.                                                                    | `True`                                                                                                    |
| `random_state`     | \`int            | None\`                                                                                                    | The seed.                                                                                                 |

Attributes:

| Name                 | Type | Description                                                                                  |
| -------------------- | ---- | -------------------------------------------------------------------------------------------- |
| `train_losses_`      |      | The cross-entropy loss of each epoch, set by fit().                                          |
| `validation_losses_` |      | The validation cross-entropy loss of each epoch, set by fit() when validation data is given. |

Example

```python
import numpy as np
from eigenkit import (
    CustomQuantumClassifier, Encoding, Variational, all_z,
)

rng = np.random.default_rng(0)
X_train, X_test = rng.uniform(0, np.pi, (20, 4)), rng.uniform(0, np.pi, (5, 4))
y_train = np.array([0, 1] * 10)

blocks = [
    Encoding(in_features=4, rotation="RY"),
    Variational(depth=2, rotation="RY"),
]
model = CustomQuantumClassifier(
    blocks=blocks, observables=all_z(4), epochs=10,
)
model.fit(X_train, y_train)
predictions = model.predict(X_test)
```

##### `fit(X, y, X_val=None, y_val=None)`

Fit the classifier on training data.

Parameters:

| Name    | Type      | Description                                                                                                | Default                                                                         |
| ------- | --------- | ---------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- |
| `X`     | `ndarray` | The samples, of shape (n_samples, in_features).                                                            | *required*                                                                      |
| `y`     | `ndarray` | The class labels, of shape (n_samples,). They can be of any type that scikit-learn's LabelEncoder accepts. | *required*                                                                      |
| `X_val` | \`ndarray | None\`                                                                                                     | Optional validation samples, used to compute the validation loss of each epoch. |
| `y_val` | \`ndarray | None\`                                                                                                     | Optional validation labels, used to compute the validation loss of each epoch.  |

Returns:

| Type                        | Description                                   |
| --------------------------- | --------------------------------------------- |
| `'CustomQuantumClassifier'` | This estimator, so that calls can be chained. |

Raises:

| Type         | Description                                                    |
| ------------ | -------------------------------------------------------------- |
| `ValueError` | If in_features is set and X does not have in_features columns. |

##### `predict(X)`

Predict the class labels of `X`.

Parameters:

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

Returns:

| Type      | Description                     |
| --------- | ------------------------------- |
| `ndarray` | One prediction for each sample. |

##### `predict_proba(X)`

Predict the probability of each class, with a softmax over the logits.

It returns a float32 array of shape `(n_samples, n_classes)`.

Parameters:

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

Returns:

| Type      | Description                                                                                     |
| --------- | ----------------------------------------------------------------------------------------------- |
| `ndarray` | The probabilities, of shape (n_samples, n_classes), with the columns in the order of classes\_. |

#### `CustomQuantumRegressor`

Bases: `ModelAccessMixin`, `BaseQuantumRegressor`

Regressor compatible with scikit-learn, built from your own list of blocks.

It is the regression counterpart of CustomQuantumClassifier. It wraps your quantum `Circuit` with an `nn.Linear` head of one neuron, trained with the chosen regression loss. An `nn.Linear` input adapter is added when the number of features of the data differs from the number of inputs of the circuit.

Parameters:

| Name               | Type             | Description                                                             | Default                                                                                                   |
| ------------------ | ---------------- | ----------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- |
| `blocks`           | `list[Block]`    | The list of blocks, in order.                                           | *required*                                                                                                |
| `observables`      | `ObservableLike` | The observables. It takes the same options as Circuit(observables=...). | `None`                                                                                                    |
| `provider`         | `ProviderSpec`   | The backend. Default: "numpy".                                          | `'numpy'`                                                                                                 |
| `in_features`      | \`int            | None\`                                                                  | The number of features of the data. If None, it is inferred from the first Encoding or Reuploading block. |
| `lr`               | `float`          | The learning rate of the optimiser.                                     | `0.01`                                                                                                    |
| `optimizer`        | `str`            | The name of the optimiser: "adam", "adamw", "rmsprop" or "sgd".         | `'adam'`                                                                                                  |
| `optimizer_kwargs` | \`dict           | None\`                                                                  | Extra keyword arguments passed to the optimiser.                                                          |
| `epochs`           | `int`            | The number of training epochs.                                          | `50`                                                                                                      |
| `batch_size`       | `int`            | The size of each mini-batch.                                            | `32`                                                                                                      |
| `loss_func`        | `str`            | The regression loss: "mse", "mae" or "huber".                           | `'mse'`                                                                                                   |
| `huber_delta`      | `float`          | The threshold of the Huber loss.                                        | `1.0`                                                                                                     |
| `huber_reduction`  | `str`            | The reduction of the Huber loss: "mean" or "sum".                       | `'mean'`                                                                                                  |
| `show_progress`    | `bool`           | If True, a tqdm progress bar is shown.                                  | `True`                                                                                                    |
| `random_state`     | \`int            | None\`                                                                  | The seed.                                                                                                 |

Attributes:

| Name                 | Type | Description                                                                    |
| -------------------- | ---- | ------------------------------------------------------------------------------ |
| `train_losses_`      |      | The training loss of each epoch, set by fit().                                 |
| `validation_losses_` |      | The validation loss of each epoch, set by fit() when validation data is given. |

##### `fit(X, y, X_val=None, y_val=None)`

Fit the regressor on training data.

Parameters:

| Name    | Type      | Description                                     | Default                                                                         |
| ------- | --------- | ----------------------------------------------- | ------------------------------------------------------------------------------- |
| `X`     | `ndarray` | The samples, of shape (n_samples, in_features). | *required*                                                                      |
| `y`     | `ndarray` | The real-valued targets, of shape (n_samples,). | *required*                                                                      |
| `X_val` | \`ndarray | None\`                                          | Optional validation samples, used to compute the validation loss of each epoch. |
| `y_val` | \`ndarray | None\`                                          | Optional validation targets, used to compute the validation loss of each epoch. |

Returns:

| Type                       | Description                                   |
| -------------------------- | --------------------------------------------- |
| `'CustomQuantumRegressor'` | This estimator, so that calls can be chained. |

##### `predict(X)`

Predict the continuous targets of `X`. It returns a float32 array.

Parameters:

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

Returns:

| Type      | Description                     |
| --------- | ------------------------------- |
| `ndarray` | One prediction for each sample. |

#### `GenericClassifier`

Bases: `ModelAccessMixin`, `BaseQuantumClassifier`

Classifier compatible with scikit-learn that wraps any `torch.nn.Module`.

You supply a complete model whose `forward(X)` accepts a float32 tensor of shape `(batch, in_features)` and returns logits of shape `(batch, n_classes)`. For sequence models (for example a long short-term memory network), set `sequence_input=True`. The wrapper then passes a 3-D tensor of shape `(batch, seq_len, in_features)` as it is, instead of flattening it.

The wrapper trains with `CrossEntropyLoss` and encodes the labels automatically.

Parameters:

| Name               | Type     | Description                                                                                                                                                                               | Default                                                                                                                                                                                |
| ------------------ | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `model`            | `Module` | Any nn.Module. It must include its own classification head, which gives n_classes logits.                                                                                                 | *required*                                                                                                                                                                             |
| `in_features`      | \`int    | None\`                                                                                                                                                                                    | The expected number of features at each time step (the last dimension of the input). When it is given, fit raises an error if X.shape[-1] does not match. Pass None to skip the check. |
| `sequence_input`   | `bool`   | If True, the estimator tells QuantumTimeSeriesClassifier that it expects a 3-D tensor of shape (batch, seq_len, in_features), so the windowed data must not be flattened. Default: False. | `False`                                                                                                                                                                                |
| `n_classes`        | \`int    | None\`                                                                                                                                                                                    | The number of output classes. If None, it is inferred from the training labels. If it is given, it must equal the number of distinct labels in y.                                      |
| `lr`               | `float`  | The learning rate of the optimiser.                                                                                                                                                       | `0.01`                                                                                                                                                                                 |
| `optimizer`        | `str`    | The name of the optimiser: "adam", "adamw", "rmsprop" or "sgd".                                                                                                                           | `'adam'`                                                                                                                                                                               |
| `optimizer_kwargs` | \`dict   | None\`                                                                                                                                                                                    | Extra keyword arguments passed to the constructor of the optimiser.                                                                                                                    |
| `epochs`           | `int`    | The number of full passes over the training data.                                                                                                                                         | `50`                                                                                                                                                                                   |
| `batch_size`       | `int`    | The size of each mini-batch.                                                                                                                                                              | `32`                                                                                                                                                                                   |
| `show_progress`    | `bool`   | If True, a tqdm progress bar is shown during training.                                                                                                                                    | `True`                                                                                                                                                                                 |
| `random_state`     | \`int    | None\`                                                                                                                                                                                    | The seed, applied before the first training step.                                                                                                                                      |

Attributes:

| Name                 | Type | Description                                                                                  |
| -------------------- | ---- | -------------------------------------------------------------------------------------------- |
| `train_losses_`      |      | The average cross-entropy loss of each epoch, set by fit().                                  |
| `validation_losses_` |      | The validation cross-entropy loss of each epoch, set by fit() when validation data is given. |

Example

```python
import numpy as np
from torch import nn
from eigenkit.torch.estimators import GenericClassifier
from eigenkit.torch.layers import Basic

rng = np.random.default_rng(0)
X_train, X_test = rng.normal(size=(30, 4)), rng.normal(size=(5, 4))
y_train = np.arange(30) % 3

# A plain PyTorch network, with a head for 3 classes.
net = nn.Sequential(nn.Linear(4, 8), nn.ReLU(), nn.Linear(8, 3))
clf = GenericClassifier(model=net, in_features=4, epochs=20)
clf.fit(X_train, y_train)
labels = clf.predict(X_test)
probs  = clf.predict_proba(X_test)

# A quantum layer with a classification head.
q = Basic(n_qubits=4, depth=2)
hybrid = nn.Sequential(q, nn.Linear(4, 3))
clf2 = GenericClassifier(model=hybrid, in_features=4)
```

##### `accepts_sequence_input`

Whether the model expects a 3-D input `(batch, seq_len, features)`.

##### `fit(X, y, X_val=None, y_val=None)`

Fit the model on training data.

Parameters:

| Name    | Type      | Description                                                                                                                    | Default                                                                         |
| ------- | --------- | ------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------- |
| `X`     | `ndarray` | The samples, of shape (n_samples, in_features) for flat models, or (n_samples, seq_len, in_features) when sequence_input=True. | *required*                                                                      |
| `y`     | `ndarray` | The class labels, of shape (n_samples,).                                                                                       | *required*                                                                      |
| `X_val` | \`ndarray | None\`                                                                                                                         | Optional validation samples, used to compute the validation loss of each epoch. |
| `y_val` | \`ndarray | None\`                                                                                                                         | Optional validation labels, used to compute the validation loss of each epoch.  |

Returns:

| Type                  | Description                                   |
| --------------------- | --------------------------------------------- |
| `'GenericClassifier'` | This estimator, so that calls can be chained. |

Raises:

| Type         | Description                                                                                                                                                                  |
| ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ValueError` | If X.shape[-1] or X_val.shape[-1] differs from in_features, if n_classes differs from the number of distinct labels, or if the model does not give one logit for each class. |

##### `load(path, *, model=None, provider=None)`

Load a fitted `GenericClassifier` from a `.eigenkit` bundle.

The architecture of the model cannot be rebuilt automatically, so you must give it. The saved weights and the label encoder are restored automatically.

Parameters:

| Name       | Type           | Description | Default                                                                                                                                                                            |
| ---------- | -------------- | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `path`     | \`str          | Path\`      | The path of the .eigenkit bundle.                                                                                                                                                  |
| `model`    | \`Module       | None\`      | An nn.Module with the same architecture as the one used in training. Its weights are not changed: the saved weights go into a deep copy, which is model\_ of the loaded estimator. |
| `provider` | \`ProviderSpec | None\`      | It is ignored. It is kept so that the signature matches the other estimators.                                                                                                      |

Returns:

| Type                  | Description           |
| --------------------- | --------------------- |
| `'GenericClassifier'` | The loaded estimator. |

Raises:

| Type                 | Description                            |
| -------------------- | -------------------------------------- |
| `SerializationError` | If model is not given.                 |
| `TypeError`          | If the bundle holds a different class. |

Example

```python
from torch import nn
from eigenkit.torch.estimators import GenericClassifier

net = nn.Sequential(nn.Linear(4, 8), nn.ReLU(), nn.Linear(8, 3))
clf = GenericClassifier.load("checkpoint.eigenkit", model=net)
```

##### `predict(X)`

Predict the class labels of `X`.

Parameters:

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

Returns:

| Type      | Description                     |
| --------- | ------------------------------- |
| `ndarray` | One prediction for each sample. |

##### `predict_proba(X)`

Predict the probability of each class, with a softmax over the logits.

It returns a float32 array of shape `(n_samples, n_classes)`.

Parameters:

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

Returns:

| Type      | Description                                                                                     |
| --------- | ----------------------------------------------------------------------------------------------- |
| `ndarray` | The probabilities, of shape (n_samples, n_classes), with the columns in the order of classes\_. |

#### `GenericRegressor`

Bases: `ModelAccessMixin`, `BaseQuantumRegressor`

Regressor compatible with scikit-learn that wraps any `torch.nn.Module`.

You supply a complete model whose `forward(X)` accepts a float32 tensor of shape `(batch, in_features)` and returns a tensor of shape `(batch, 1)` or `(batch,)`. For sequence models (for example a long short-term memory network), set `sequence_input=True`. The wrapper then passes a 3-D tensor of shape `(batch, seq_len, in_features)` as it is, instead of flattening it.

The wrapper trains the model with the standard `train_loop()` of the library, and returns the predictions as a float32 NumPy array.

Parameters:

| Name               | Type     | Description                                                                                                                                                                              | Default                                                                                                                                                                                |
| ------------------ | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `model`            | `Module` | Any nn.Module. It must include its own output head.                                                                                                                                      | *required*                                                                                                                                                                             |
| `in_features`      | \`int    | None\`                                                                                                                                                                                   | The expected number of features at each time step (the last dimension of the input). When it is given, fit raises an error if X.shape[-1] does not match. Pass None to skip the check. |
| `sequence_input`   | `bool`   | If True, the estimator tells QuantumTimeSeriesRegressor that it expects a 3-D tensor of shape (batch, seq_len, in_features), so the windowed data must not be flattened. Default: False. | `False`                                                                                                                                                                                |
| `loss_func`        | \`str    | Callable\`                                                                                                                                                                               | The loss: "mse", "mae", "huber", or any callable (output, target) -> scalar_tensor.                                                                                                    |
| `huber_delta`      | `float`  | The threshold of the Huber loss. It is ignored for the other losses.                                                                                                                     | `1.0`                                                                                                                                                                                  |
| `huber_reduction`  | `str`    | The reduction of the Huber loss: "mean" or "sum".                                                                                                                                        | `'mean'`                                                                                                                                                                               |
| `lr`               | `float`  | The learning rate of the optimiser.                                                                                                                                                      | `0.01`                                                                                                                                                                                 |
| `optimizer`        | `str`    | The name of the optimiser: "adam", "adamw", "rmsprop" or "sgd".                                                                                                                          | `'adam'`                                                                                                                                                                               |
| `optimizer_kwargs` | \`dict   | None\`                                                                                                                                                                                   | Extra keyword arguments passed to the constructor of the optimiser.                                                                                                                    |
| `epochs`           | `int`    | The number of full passes over the training data.                                                                                                                                        | `50`                                                                                                                                                                                   |
| `batch_size`       | `int`    | The size of each mini-batch.                                                                                                                                                             | `32`                                                                                                                                                                                   |
| `show_progress`    | `bool`   | If True, a tqdm progress bar is shown during training.                                                                                                                                   | `True`                                                                                                                                                                                 |
| `random_state`     | \`int    | None\`                                                                                                                                                                                   | The seed, applied before the first training step.                                                                                                                                      |

Attributes:

| Name                 | Type | Description                                                                                |
| -------------------- | ---- | ------------------------------------------------------------------------------------------ |
| `train_losses_`      |      | The list of the average loss of each epoch, set by fit().                                  |
| `validation_losses_` |      | The list of the validation loss of each epoch, set by fit() when validation data is given. |

Example

```python
import numpy as np
from torch import nn
from eigenkit.torch.estimators import GenericRegressor
from eigenkit.torch.layers import Basic

rng = np.random.default_rng(0)
X_train, X_test = rng.normal(size=(30, 8)), rng.normal(size=(5, 8))
y_train = X_train.sum(axis=1)

# A plain PyTorch network.
net = nn.Sequential(nn.Linear(8, 16), nn.ReLU(), nn.Linear(16, 1))
reg = GenericRegressor(model=net, in_features=8, epochs=30)
reg.fit(X_train, y_train)
preds = reg.predict(X_test)

# A quantum layer with a classical head.
q = Basic(n_qubits=4, depth=2)
hybrid = nn.Sequential(q, nn.Linear(4, 1))
reg2 = GenericRegressor(model=hybrid, in_features=4)
```

##### `accepts_sequence_input`

Whether the model expects a 3-D input `(batch, seq_len, features)`.

##### `fit(X, y, X_val=None, y_val=None)`

Fit the model on training data.

Parameters:

| Name    | Type      | Description                                                                                                                    | Default                                                                         |
| ------- | --------- | ------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------- |
| `X`     | `ndarray` | The samples, of shape (n_samples, in_features) for flat models, or (n_samples, seq_len, in_features) when sequence_input=True. | *required*                                                                      |
| `y`     | `ndarray` | The real-valued targets, of shape (n_samples,).                                                                                | *required*                                                                      |
| `X_val` | \`ndarray | None\`                                                                                                                         | Optional validation samples, used to compute the validation loss of each epoch. |
| `y_val` | \`ndarray | None\`                                                                                                                         | Optional validation targets, used to compute the validation loss of each epoch. |

Returns:

| Type                 | Description                                   |
| -------------------- | --------------------------------------------- |
| `'GenericRegressor'` | This estimator, so that calls can be chained. |

Raises:

| Type         | Description                                                                                                                           |
| ------------ | ------------------------------------------------------------------------------------------------------------------------------------- |
| `ValueError` | If X.shape[-1] or X_val.shape[-1] differs from in_features, or if the output of the model does not have shape (batch,) or (batch, 1). |

##### `load(path, *, model=None, provider=None)`

Load a fitted `GenericRegressor` from a `.eigenkit` bundle.

The architecture of the model cannot be rebuilt automatically, so you must give it. The saved weights are loaded into a copy of it.

Parameters:

| Name       | Type           | Description | Default                                                                                                                                                                            |
| ---------- | -------------- | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `path`     | \`str          | Path\`      | The path of the .eigenkit bundle.                                                                                                                                                  |
| `model`    | \`Module       | None\`      | An nn.Module with the same architecture as the one used in training. Its weights are not changed: the saved weights go into a deep copy, which is model\_ of the loaded estimator. |
| `provider` | \`ProviderSpec | None\`      | It is ignored. It is kept so that the signature matches the other estimators.                                                                                                      |

Returns:

| Type                 | Description           |
| -------------------- | --------------------- |
| `'GenericRegressor'` | The loaded estimator. |

Raises:

| Type                 | Description                            |
| -------------------- | -------------------------------------- |
| `SerializationError` | If model is not given.                 |
| `TypeError`          | If the bundle holds a different class. |

Example

```python
from torch import nn
from eigenkit.torch.estimators import GenericRegressor

net = nn.Sequential(nn.Linear(8, 16), nn.ReLU(), nn.Linear(16, 1))
reg = GenericRegressor.load("checkpoint.eigenkit", model=net)
```

##### `predict(X)`

Predict the continuous targets of `X`. It returns a float32 array.

Parameters:

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

Returns:

| Type      | Description                     |
| --------- | ------------------------------- |
| `ndarray` | One prediction for each sample. |

#### `HQNC`

Bases: `ModelAccessMixin`, `BaseQuantumClassifier`

Hybrid quantum neural network classifier (HQNC).

The model is `MinMaxNorm -> quantum_layer -> nn.Linear(q_out, n_classes)`. If the number of features of the data differs from the resolved input size of the quantum layer (derived from `in_quantum`, `n_qubits` and `layer_type`), an `nn.Linear` adapter is added in front automatically.

Parameters:

| Name               | Type                         | Description                                                                              | Default                                                                                                                                                                                |
| ------------------ | ---------------------------- | ---------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `in_features`      | `int`                        | The number of input features.                                                            | *required*                                                                                                                                                                             |
| `in_quantum`       | \`int                        | None\`                                                                                   | The number of inputs that the quantum layer expects. It defaults to in_features.                                                                                                       |
| `n_qubits`         | \`int                        | None\`                                                                                   | The number of qubits in each circuit. It defaults to in_quantum.                                                                                                                       |
| `depth`            | `int`                        | The number of repetitions of the variational layer.                                      | `2`                                                                                                                                                                                    |
| `n_classes`        | \`int                        | None\`                                                                                   | The number of output classes. If None, it is inferred from the training labels.                                                                                                        |
| `layer_type`       | `str`                        | The quantum layer. It is one of "Basic", "Reupload" and "Ensemble".                      | `'Basic'`                                                                                                                                                                              |
| `enc_rotation`     | \`Literal['RX', 'RY', 'RZ']  | None\`                                                                                   | The encoding rotation: "RX", "RY", "RZ" or None.                                                                                                                                       |
| `var_rotation`     | \`Literal['RX', 'RY', 'RZ']  | None\`                                                                                   | The variational rotation of the "basic" ansatz: "RX", "RY", "RZ" or None.                                                                                                              |
| `variational_type` | \`Literal['basic', 'strong'] | None\`                                                                                   | The type of variational block: "basic", "strong" or None.                                                                                                                              |
| `basis`            | \`Literal['X', 'Y', 'Z']     | None\`                                                                                   | The measurement basis: "X", "Y", "Z" or None. For these four arguments, None means "use the default of the layer named by layer_type", which is where each of these values is decided. |
| `provider`         | `ProviderSpec`               | The backend. It takes the same options as Circuit(provider=...).                         | `'numpy'`                                                                                                                                                                              |
| `lr`               | `float`                      | The learning rate of the optimiser.                                                      | `0.01`                                                                                                                                                                                 |
| `optimizer`        | `str`                        | The name of the optimiser. The supported names are "adam", "adamw", "rmsprop" and "sgd". | `'adam'`                                                                                                                                                                               |
| `optimizer_kwargs` | \`dict                       | None\`                                                                                   | Extra keyword arguments passed to the optimiser.                                                                                                                                       |
| `epochs`           | `int`                        | The number of training epochs.                                                           | `50`                                                                                                                                                                                   |
| `batch_size`       | `int`                        | The size of each mini-batch.                                                             | `32`                                                                                                                                                                                   |
| `show_progress`    | `bool`                       | If True, a tqdm progress bar is shown.                                                   | `True`                                                                                                                                                                                 |
| `random_state`     | \`int                        | None\`                                                                                   | The seed for the initialisation of the weights.                                                                                                                                        |

Attributes:

| Name                 | Type | Description                                                                                  |
| -------------------- | ---- | -------------------------------------------------------------------------------------------- |
| `train_losses_`      |      | The average cross-entropy loss of each epoch, set by fit().                                  |
| `validation_losses_` |      | The validation cross-entropy loss of each epoch, set by fit() when validation data is given. |

Example

```python
import numpy as np
from eigenkit.torch.estimators import HQNC

rng = np.random.default_rng(0)
X_train, X_test = rng.normal(size=(30, 4)), rng.normal(size=(5, 4))
y_train = np.arange(30) % 3

model = HQNC(in_features=4, depth=2, n_classes=3)
model.fit(X_train, y_train)
predictions = model.predict(X_test)
```

##### `__init__(in_features, in_quantum=None, n_qubits=None, depth=2, n_classes=None, layer_type='Basic', enc_rotation=None, var_rotation=None, variational_type=None, basis=None, provider='numpy', lr=0.01, optimizer='adam', optimizer_kwargs=None, epochs=50, batch_size=32, show_progress=True, random_state=42)`

Initialise a hybrid quantum neural network classifier (HQNC).

Parameters:

| Name               | Type                         | Description                                                                              | Default                                                                                                                                                                                |
| ------------------ | ---------------------------- | ---------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `in_features`      | `int`                        | The number of input features that fit() expects.                                         | *required*                                                                                                                                                                             |
| `in_quantum`       | \`int                        | None\`                                                                                   | The number of inputs that the quantum layer expects. It defaults to in_features.                                                                                                       |
| `n_qubits`         | \`int                        | None\`                                                                                   | The number of qubits in each circuit. It defaults to in_quantum.                                                                                                                       |
| `depth`            | `int`                        | The number of repetitions of the variational layer.                                      | `2`                                                                                                                                                                                    |
| `n_classes`        | \`int                        | None\`                                                                                   | The number of output classes. If None, it is inferred from the labels passed to fit().                                                                                                 |
| `layer_type`       | `str`                        | The family of the quantum layer. It is one of "Basic", "Reupload" and "Ensemble".        | `'Basic'`                                                                                                                                                                              |
| `enc_rotation`     | \`Literal['RX', 'RY', 'RZ']  | None\`                                                                                   | The encoding rotation: "RX", "RY", "RZ" or None.                                                                                                                                       |
| `var_rotation`     | \`Literal['RX', 'RY', 'RZ']  | None\`                                                                                   | The variational rotation of the basic ansatz: "RX", "RY", "RZ" or None.                                                                                                                |
| `variational_type` | \`Literal['basic', 'strong'] | None\`                                                                                   | The type of variational block: "basic", "strong" or None.                                                                                                                              |
| `basis`            | \`Literal['X', 'Y', 'Z']     | None\`                                                                                   | The measurement basis: "X", "Y", "Z" or None. For these four arguments, None means "use the default of the layer named by layer_type", which is where each of these values is decided. |
| `provider`         | `ProviderSpec`               | The backend. It takes the same options as Circuit(provider=...).                         | `'numpy'`                                                                                                                                                                              |
| `lr`               | `float`                      | The learning rate of the optimiser.                                                      | `0.01`                                                                                                                                                                                 |
| `optimizer`        | `str`                        | The name of the optimiser. The supported names are "adam", "adamw", "rmsprop" and "sgd". | `'adam'`                                                                                                                                                                               |
| `optimizer_kwargs` | \`dict                       | None\`                                                                                   | Extra keyword arguments passed to the optimiser.                                                                                                                                       |
| `epochs`           | `int`                        | The number of training epochs.                                                           | `50`                                                                                                                                                                                   |
| `batch_size`       | `int`                        | The size of each mini-batch.                                                             | `32`                                                                                                                                                                                   |
| `show_progress`    | `bool`                       | If True, a tqdm progress bar is shown during training.                                   | `True`                                                                                                                                                                                 |
| `random_state`     | \`int                        | None\`                                                                                   | The seed for the initialisation of the weights.                                                                                                                                        |

##### `fit(X, y, X_val=None, y_val=None)`

Fit the classifier on training data.

Parameters:

| Name    | Type      | Description                                                                                                | Default                                                                         |
| ------- | --------- | ---------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- |
| `X`     | `ndarray` | The samples, of shape (n_samples, in_features).                                                            | *required*                                                                      |
| `y`     | `ndarray` | The class labels, of shape (n_samples,). They can be of any type that scikit-learn's LabelEncoder accepts. | *required*                                                                      |
| `X_val` | \`ndarray | None\`                                                                                                     | Optional validation samples, used to compute the validation loss of each epoch. |
| `y_val` | \`ndarray | None\`                                                                                                     | Optional validation labels, used to compute the validation loss of each epoch.  |

Returns:

| Type   | Description                                   |
| ------ | --------------------------------------------- |
| `HQNC` | This estimator, so that calls can be chained. |

Raises:

| Type         | Description                             |
| ------------ | --------------------------------------- |
| `ValueError` | If X does not have in_features columns. |

##### `predict(X)`

Predict the class labels of `X`.

Parameters:

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

Returns:

| Type      | Description                     |
| --------- | ------------------------------- |
| `ndarray` | One prediction for each sample. |

##### `predict_proba(X)`

Predict the probability of each class, with a softmax over the logits.

It returns a float32 array of shape `(n_samples, n_classes)`. Each row sums to `1`.

Parameters:

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

Returns:

| Type      | Description                                                                                     |
| --------- | ----------------------------------------------------------------------------------------------- |
| `ndarray` | The probabilities, of shape (n_samples, n_classes), with the columns in the order of classes\_. |

#### `HQNR`

Bases: `ModelAccessMixin`, `BaseQuantumRegressor`

Hybrid quantum neural network regressor (HQNR).

It is the regression counterpart of `HQNC`. The model is `MinMaxNorm -> quantum_layer -> nn.Linear(q_out, 1)`. If the number of features of the data differs from the resolved input size of the quantum layer, an `nn.Linear` adapter is added in front automatically.

Parameters:

| Name               | Type                         | Description                                                                              | Default                                                                                                                                                                                |
| ------------------ | ---------------------------- | ---------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `in_features`      | `int`                        | The number of input features.                                                            | *required*                                                                                                                                                                             |
| `in_quantum`       | \`int                        | None\`                                                                                   | The number of inputs that the quantum layer expects. It defaults to in_features.                                                                                                       |
| `n_qubits`         | \`int                        | None\`                                                                                   | The number of qubits in each circuit. It defaults to in_quantum.                                                                                                                       |
| `depth`            | `int`                        | The number of repetitions of the variational layer.                                      | `2`                                                                                                                                                                                    |
| `layer_type`       | `str`                        | The quantum layer. It is one of "Basic", "Reupload" and "Ensemble".                      | `'Basic'`                                                                                                                                                                              |
| `enc_rotation`     | \`Literal['RX', 'RY', 'RZ']  | None\`                                                                                   | The encoding rotation: "RX", "RY", "RZ" or None.                                                                                                                                       |
| `var_rotation`     | \`Literal['RX', 'RY', 'RZ']  | None\`                                                                                   | The variational rotation of the "basic" ansatz: "RX", "RY", "RZ" or None.                                                                                                              |
| `variational_type` | \`Literal['basic', 'strong'] | None\`                                                                                   | The type of variational block: "basic", "strong" or None.                                                                                                                              |
| `basis`            | \`Literal['X', 'Y', 'Z']     | None\`                                                                                   | The measurement basis: "X", "Y", "Z" or None. For these four arguments, None means "use the default of the layer named by layer_type", which is where each of these values is decided. |
| `provider`         | `ProviderSpec`               | The backend. It takes the same options as Circuit(provider=...).                         | `'numpy'`                                                                                                                                                                              |
| `lr`               | `float`                      | The learning rate of the optimiser.                                                      | `0.01`                                                                                                                                                                                 |
| `optimizer`        | `str`                        | The name of the optimiser. The supported names are "adam", "adamw", "rmsprop" and "sgd". | `'adam'`                                                                                                                                                                               |
| `optimizer_kwargs` | \`dict                       | None\`                                                                                   | Extra keyword arguments passed to the optimiser.                                                                                                                                       |
| `epochs`           | `int`                        | The number of training epochs.                                                           | `50`                                                                                                                                                                                   |
| `batch_size`       | `int`                        | The size of each mini-batch.                                                             | `32`                                                                                                                                                                                   |
| `show_progress`    | `bool`                       | If True, a tqdm progress bar is shown.                                                   | `True`                                                                                                                                                                                 |
| `loss_func`        | `str`                        | The regression loss: "mse", "mae" or "huber".                                            | `'mse'`                                                                                                                                                                                |
| `huber_delta`      | `float`                      | The threshold of the Huber loss.                                                         | `1.0`                                                                                                                                                                                  |
| `huber_reduction`  | `str`                        | The reduction of the Huber loss: "mean" or "sum".                                        | `'mean'`                                                                                                                                                                               |
| `random_state`     | \`int                        | None\`                                                                                   | The seed.                                                                                                                                                                              |

Attributes:

| Name                 | Type | Description                                                                    |
| -------------------- | ---- | ------------------------------------------------------------------------------ |
| `train_losses_`      |      | The training loss of each epoch, set by fit().                                 |
| `validation_losses_` |      | The validation loss of each epoch, set by fit() when validation data is given. |

Example

```python
import numpy as np
from eigenkit.torch.estimators import HQNR

rng = np.random.default_rng(0)
X_train, X_test = rng.normal(size=(30, 8)), rng.normal(size=(5, 8))
y_train = X_train.sum(axis=1)

model = HQNR(in_features=8, depth=2)
model.fit(X_train, y_train)
predictions = model.predict(X_test)
```

##### `fit(X, y, X_val=None, y_val=None)`

Fit the regressor on training data.

Parameters:

| Name    | Type      | Description                                     | Default                                                                         |
| ------- | --------- | ----------------------------------------------- | ------------------------------------------------------------------------------- |
| `X`     | `ndarray` | The samples, of shape (n_samples, in_features). | *required*                                                                      |
| `y`     | `ndarray` | The real-valued targets, of shape (n_samples,). | *required*                                                                      |
| `X_val` | \`ndarray | None\`                                          | Optional validation samples, used to compute the validation loss of each epoch. |
| `y_val` | \`ndarray | None\`                                          | Optional validation targets, used to compute the validation loss of each epoch. |

Returns:

| Type   | Description                                   |
| ------ | --------------------------------------------- |
| `HQNR` | This estimator, so that calls can be chained. |

Raises:

| Type         | Description                             |
| ------------ | --------------------------------------- |
| `ValueError` | If X does not have in_features columns. |

##### `predict(X)`

Predict the continuous targets of `X`. It returns a float32 array.

Parameters:

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

Returns:

| Type      | Description                     |
| --------- | ------------------------------- |
| `ndarray` | One prediction for each sample. |

#### `QuantumTimeSeriesClassifier`

Bases: `BaseQuantumClassifier`

Supervised wrapper for time-series classification.

It builds sliding windows over the rows of `X` (one row for each time step). Each window has shape `(in_size, n_features)`. Stacked along the batch axis, they form an array of shape `(n_windows, in_size, n_features)`, ready for sequence models such as `RecurrentHQNC`. When `autoregressive=True`, an extra last channel holds the target value of each timestep, so each step has `n_features + 1` values. For a window that starts at row `i`, the classification target is `y[i + in_size - 1 + shift]`.

Parameters:

| Name             | Type   | Description                                                                                                                                                                                                                                                   | Default                                                               |
| ---------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------- |
| `estimator`      | `Any`  | The inner estimator. It must have fit(X, y) and predict(X).                                                                                                                                                                                                   | *required*                                                            |
| `in_size`        | \`int  | None\`                                                                                                                                                                                                                                                        | The length of the window, in time steps. If None, it is treated as 1. |
| `shift`          | `int`  | How many steps after the last timestep of the window the target is. It must be zero or more. shift=0 predicts the target at the last timestep.                                                                                                                | `1`                                                                   |
| `autoregressive` | `bool` | If True, a channel with y[i + k] for k = 0, ..., in_size - 1 is added to each window. With shift=0, this leaks the target into the inputs, and a UserWarning is raised. When autoregressive=True, predict() and predict_proba() require a y_history argument. | `False`                                                               |

Attributes:

| Name            | Type | Description                                                                                                                                                       |
| --------------- | ---- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `classes_`      |      | The distinct class labels seen by fit().                                                                                                                          |
| `train_losses_` |      | The training loss of each epoch, copied from the inner estimator if it has train_losses\_ (HQNC, RecurrentHQNC), and an empty list otherwise. It is set by fit(). |

Example

```python
import numpy as np
from eigenkit.torch.estimators import (
    QuantumTimeSeriesClassifier, RecurrentHQNC,
)

rng = np.random.default_rng(0)
X_train, X_test = rng.normal(size=(40, 4)), rng.normal(size=(15, 4))
y_train = np.arange(40) % 2

inner = RecurrentHQNC(seq_feature_dim=4, rnn_hidden_dim=4, depth=2)
model = QuantumTimeSeriesClassifier(estimator=inner, in_size=10, shift=1)
model.fit(X_train, y_train)
predictions = model.predict(X_test)
```

##### `output`

The number of classes.

After `fit()`, it is the number of classes seen. Before, it is the `n_classes` of the inner estimator, or None if it has none.

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

Fit the inner classifier on the windows and their labels.

Parameters:

| Name | Type      | Description                                                     | Default    |
| ---- | --------- | --------------------------------------------------------------- | ---------- |
| `X`  | `ndarray` | The features in time order, of shape (n_timesteps, n_features). | *required* |
| `y`  | `ndarray` | The class labels, of shape (n_timesteps,) or (n_timesteps, 1).  | *required* |

Returns:

| Type                            | Description                                   |
| ------------------------------- | --------------------------------------------- |
| `'QuantumTimeSeriesClassifier'` | This estimator, so that calls can be chained. |

##### `load(path, *, provider=None, model=None)`

Load a fitted time-series classifier wrapper from a `.eigenkit` bundle.

Parameters:

| Name       | Type           | Description                                                                                                                                 | Default                                                                                |
| ---------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- |
| `path`     | \`str          | Path\`                                                                                                                                      | The path of the .eigenkit bundle.                                                      |
| `provider` | \`ProviderSpec | None\`                                                                                                                                      | An optional provider that replaces the saved one. It is passed to the inner estimator. |
| `model`    | `Any`          | The nn.Module architecture, required when the inner estimator is GenericClassifier. Pass the same architecture as the one used in training. | `None`                                                                                 |

Returns:

| Type                            | Description         |
| ------------------------------- | ------------------- |
| `'QuantumTimeSeriesClassifier'` | The loaded wrapper. |

Raises:

| Type                 | Description                                             |
| -------------------- | ------------------------------------------------------- |
| `TypeError`          | If the bundle holds a different class.                  |
| `SerializationError` | If the inner estimator needs model and it is not given. |

##### `predict(X, y_history=None)`

Predict the class labels of the windows of `X`.

Parameters:

| Name        | Type      | Description                                                     | Default                                                                                                                                                                                                                                                             |
| ----------- | --------- | --------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `X`         | `ndarray` | The features in time order, of shape (n_timesteps, n_features). | *required*                                                                                                                                                                                                                                                          |
| `y_history` | \`ndarray | None\`                                                          | The past class labels, aligned with the rows of X. They fill the autoregressive channel of each window. It is required when autoregressive=True, and it must have at least n_timesteps - shift elements (that is, n_timesteps - in_size - shift + 1 + in_size - 1). |

Returns:

| Type      | Description                                                          |
| --------- | -------------------------------------------------------------------- |
| `ndarray` | The predicted labels, of shape (n_timesteps - in_size - shift + 1,). |

##### `predict_proba(X, y_history=None)`

Return the estimated class probabilities of the windows of `X`.

Parameters:

| Name        | Type      | Description                                                     | Default                                                                        |
| ----------- | --------- | --------------------------------------------------------------- | ------------------------------------------------------------------------------ |
| `X`         | `ndarray` | The features in time order, of shape (n_timesteps, n_features). | *required*                                                                     |
| `y_history` | \`ndarray | None\`                                                          | The past class labels. It is required when autoregressive=True. See predict(). |

Returns:

| Type      | Description                                      |
| --------- | ------------------------------------------------ |
| `ndarray` | A float32 array of shape (n_windows, n_classes). |

Raises:

| Type                  | Description                                  |
| --------------------- | -------------------------------------------- |
| `NotImplementedError` | If the inner estimator has no predict_proba. |

##### `score(X, y, *, return_dict=False)`

```python
score(
    X: np.ndarray,
    y: np.ndarray,
    *,
    return_dict: Literal[False] = False,
) -> float
```

```python
score(
    X: np.ndarray,
    y: np.ndarray,
    *,
    return_dict: Literal[True],
) -> dict[str, float]
```

Score the estimator on the window targets, aligned as in `predict`.

By default, it returns the accuracy, for compatibility with scikit-learn (`cross_val_score`, `GridSearchCV`). Pass `return_dict=True` to get a dict with `accuracy`, `f1` (weighted), `precision` (weighted) and `recall` (weighted).

Note

When `autoregressive=True`, the true labels `y` are used as the autoregressive history channel (teacher forcing). This is the correct behaviour for held-out evaluation, but it means that `score` cannot be called without the true labels of all the timesteps in `X`.

Parameters:

| Name          | Type      | Description                                                                                    | Default    |
| ------------- | --------- | ---------------------------------------------------------------------------------------------- | ---------- |
| `X`           | `ndarray` | The samples, of shape (n_samples, n_features). The windows are built inside.                   | *required* |
| `y`           | `ndarray` | One target for each sample, of shape (n_samples,).                                             | *required* |
| `return_dict` | `bool`    | If True, every metric is returned in a dict, instead of the single main score. Default: False. | `False`    |

Returns:

| Type    | Description        |
| ------- | ------------------ |
| \`float | dict[str, float]\` |

#### `QuantumTimeSeriesRegressor`

Bases: `BaseQuantumRegressor`

Supervised wrapper with a single output, for time-series regression.

It builds sliding windows over the rows of `X` (one row for each time step). Each window has shape `(in_size, n_features)`. Stacked along the batch axis, they form an array of shape `(n_windows, in_size, n_features)`, ready for sequence models such as `torch.nn.LSTM`. When `autoregressive=True`, an extra last channel holds the target value of each timestep, so each step has `n_features + 1` values. For a window that starts at row `i`, the regression target is `y[i + in_size - 1 + shift]`.

Parameters:

| Name             | Type   | Description                                                                                                                                                             | Default                                                               |
| ---------------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------- |
| `estimator`      | `Any`  | The inner estimator. It must have fit(X, y) and predict(X).                                                                                                             | *required*                                                            |
| `in_size`        | \`int  | None\`                                                                                                                                                                  | The length of the window, in time steps. If None, it is treated as 1. |
| `shift`          | `int`  | How many steps after the last timestep of the window the target is. It must be zero or more. shift=0 predicts the target at the last timestep.                          | `1`                                                                   |
| `autoregressive` | `bool` | If True, a channel with y[i + k] for k = 0, ..., in_size - 1 is added to each window. With shift=0, this leaks the target into the inputs, and a UserWarning is raised. | `False`                                                               |

Attributes:

| Name            | Type | Description                                                                                                                                                       |
| --------------- | ---- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `train_losses_` |      | The training loss of each epoch, copied from the inner estimator if it has train_losses\_ (HQNR, RecurrentHQNR), and an empty list otherwise. It is set by fit(). |

Example

```python
import numpy as np
from eigenkit.torch.estimators import QuantumTimeSeriesRegressor, RecurrentHQNR

rng = np.random.default_rng(0)
X_train, X_test = rng.normal(size=(40, 3)), rng.normal(size=(15, 3))
y_train = np.sin(np.arange(40) / 4)

# The inner estimator should accept X of shape (batch, in_size, n_features).
inner = RecurrentHQNR(seq_feature_dim=3, rnn_hidden_dim=4, epochs=5)
model = QuantumTimeSeriesRegressor(estimator=inner, in_size=10, shift=1)
model.fit(X_train, y_train)
predictions = model.predict(X_test)
```

##### `output`

The number of outputs, which is always `1`.

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

Fit the inner estimator on the windows and their targets.

Parameters:

| Name | Type      | Description                                                     | Default    |
| ---- | --------- | --------------------------------------------------------------- | ---------- |
| `X`  | `ndarray` | The features in time order, of shape (n_timesteps, n_features). | *required* |
| `y`  | `ndarray` | The targets, of shape (n_timesteps,) or (n_timesteps, 1).       | *required* |

Returns:

| Type                           | Description                                   |
| ------------------------------ | --------------------------------------------- |
| `'QuantumTimeSeriesRegressor'` | This estimator, so that calls can be chained. |

##### `load(path, *, provider=None, model=None)`

Load a fitted time-series wrapper from a `.eigenkit` bundle.

Parameters:

| Name       | Type           | Description                                                                                                                                | Default                                                                                |
| ---------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------- |
| `path`     | \`str          | Path\`                                                                                                                                     | The path of the .eigenkit bundle.                                                      |
| `provider` | \`ProviderSpec | None\`                                                                                                                                     | An optional provider that replaces the saved one. It is passed to the inner estimator. |
| `model`    | `Any`          | The nn.Module architecture, required when the inner estimator is GenericRegressor. Pass the same architecture as the one used in training. | `None`                                                                                 |

Returns:

| Type                           | Description         |
| ------------------------------ | ------------------- |
| `'QuantumTimeSeriesRegressor'` | The loaded wrapper. |

Raises:

| Type                 | Description                                             |
| -------------------- | ------------------------------------------------------- |
| `TypeError`          | If the bundle holds a different class.                  |
| `SerializationError` | If the inner estimator needs model and it is not given. |

##### `predict(X, y_history=None)`

Predict the regression targets of the windows of `X`.

Parameters:

| Name        | Type      | Description                                                     | Default                                                                                                                                     |
| ----------- | --------- | --------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `X`         | `ndarray` | The features in time order, of shape (n_timesteps, n_features). | *required*                                                                                                                                  |
| `y_history` | \`ndarray | None\`                                                          | The past targets, aligned with the rows of X. They fill the autoregressive channel of each window. It is required when autoregressive=True. |

Returns:

| Type      | Description                                                     |
| --------- | --------------------------------------------------------------- |
| `ndarray` | The predictions, of shape (n_timesteps - in_size - shift + 1,). |

##### `score(X, y, *, return_dict=False)`

```python
score(
    X: np.ndarray,
    y: np.ndarray,
    *,
    return_dict: Literal[False] = False,
) -> float
```

```python
score(
    X: np.ndarray,
    y: np.ndarray,
    *,
    return_dict: Literal[True],
) -> dict[str, float]
```

Score the estimator on the window targets, aligned as in `predict`.

By default, it returns `R²`, for compatibility with scikit-learn (`cross_val_score`, `GridSearchCV`). Pass `return_dict=True` to get a dict with `r2`, `mse`, `mae`, `rmse` and `mape`.

Parameters:

| Name          | Type      | Description                                                                                    | Default    |
| ------------- | --------- | ---------------------------------------------------------------------------------------------- | ---------- |
| `X`           | `ndarray` | The samples, of shape (n_samples, n_features). The windows are built inside.                   | *required* |
| `y`           | `ndarray` | One target for each sample, of shape (n_samples,).                                             | *required* |
| `return_dict` | `bool`    | If True, every metric is returned in a dict, instead of the single main score. Default: False. | `False`    |

Returns:

| Type    | Description        |
| ------- | ------------------ |
| \`float | dict[str, float]\` |

#### `RecurrentHQNC`

Bases: `ModelAccessMixin`, `BaseQuantumClassifier`

Hybrid classifier: a recurrent encoder, a quantum layer and a linear class head.

The recurrent encoder is a recurrent neural network (RNN). The estimator expects 3-D inputs of shape `(n_samples, seq_len, seq_feature_dim)`. By default, only the last timestep of the RNN output goes to the quantum layer. Set `rnn_return_last_timestep=False`, together with `sequence_length`, to use the full RNN output (flattened) instead. The quantum part is the same as in HQNC: `MinMaxNorm`, the quantum layer and an `nn.Linear` head.

Parameters:

| Name                       | Type                            | Description                                                                                                            | Default                                                                                                                                                                                                                                            |
| -------------------------- | ------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `seq_feature_dim`          | `int`                           | The number of features at each time step (the last axis of X).                                                         | *required*                                                                                                                                                                                                                                         |
| `rnn_type`                 | `Literal['rnn', 'gru', 'lstm']` | The type of recurrent layer: "rnn", "gru" (gated recurrent unit) or "lstm" (long short-term memory).                   | `'gru'`                                                                                                                                                                                                                                            |
| `rnn_hidden_dim`           | `int`                           | The hidden size in each direction.                                                                                     | `32`                                                                                                                                                                                                                                               |
| `rnn_num_layers`           | `int`                           | The number of stacked RNN layers.                                                                                      | `1`                                                                                                                                                                                                                                                |
| `rnn_dropout`              | `float`                         | The dropout between the RNN layers. It applies only if rnn_num_layers > 1.                                             | `0.0`                                                                                                                                                                                                                                              |
| `rnn_bidirectional`        | `bool`                          | If True, the RNN is bidirectional, and its output size doubles.                                                        | `False`                                                                                                                                                                                                                                            |
| `rnn_return_last_timestep` | `bool`                          | If True, only the last output step is kept. Otherwise, the full sequence is flattened, which requires sequence_length. | `True`                                                                                                                                                                                                                                             |
| `sequence_length`          | \`int                           | None\`                                                                                                                 | The number of time steps in each sample. It is required when rnn_return_last_timestep=False, and it must equal X.shape[1] in fit.                                                                                                                  |
| `in_quantum`               | \`int                           | None\`                                                                                                                 | The number of inputs of the quantum layer. It defaults to the size of the RNN output that reaches the quantum layer: rnn_hidden_dim, doubled when the RNN is bidirectional, and multiplied by sequence_length when rnn_return_last_timestep=False. |
| `n_qubits`                 | \`int                           | None\`                                                                                                                 | The number of qubits in each circuit. It defaults to in_quantum.                                                                                                                                                                                   |
| `depth`                    | `int`                           | The number of repetitions of the variational layer.                                                                    | `2`                                                                                                                                                                                                                                                |
| `n_classes`                | \`int                           | None\`                                                                                                                 | The number of output classes. If None, it is inferred from the labels passed to fit(). If it is given, it must equal the number of distinct labels in y.                                                                                           |
| `layer_type`               | `str`                           | The quantum layer. It is one of "Basic", "Reupload" and "Ensemble".                                                    | `'Basic'`                                                                                                                                                                                                                                          |
| `enc_rotation`             | \`Literal['RX', 'RY', 'RZ']     | None\`                                                                                                                 | The encoding rotation: "RX", "RY", "RZ" or None.                                                                                                                                                                                                   |
| `var_rotation`             | \`Literal['RX', 'RY', 'RZ']     | None\`                                                                                                                 | The variational rotation of the basic ansatz: "RX", "RY", "RZ" or None.                                                                                                                                                                            |
| `variational_type`         | \`Literal['basic', 'strong']    | None\`                                                                                                                 | The type of variational block: "basic", "strong" or None.                                                                                                                                                                                          |
| `basis`                    | \`Literal['X', 'Y', 'Z']        | None\`                                                                                                                 | The measurement basis: "X", "Y", "Z" or None. For these four arguments, None means "use the default of the layer named by layer_type", which is where each of these values is decided.                                                             |
| `provider`                 | `ProviderSpec`                  | The backend. It takes the same options as Circuit(provider=...).                                                       | `'numpy'`                                                                                                                                                                                                                                          |
| `lr`                       | `float`                         | The learning rate of the optimiser.                                                                                    | `0.01`                                                                                                                                                                                                                                             |
| `optimizer`                | `str`                           | The name of the optimiser. The supported names are "adam", "adamw", "rmsprop" and "sgd".                               | `'adam'`                                                                                                                                                                                                                                           |
| `optimizer_kwargs`         | \`dict                          | None\`                                                                                                                 | Extra keyword arguments passed to the optimiser.                                                                                                                                                                                                   |
| `epochs`                   | `int`                           | The number of training epochs.                                                                                         | `50`                                                                                                                                                                                                                                               |
| `batch_size`               | `int`                           | The size of each mini-batch.                                                                                           | `32`                                                                                                                                                                                                                                               |
| `show_progress`            | `bool`                          | If True, a tqdm progress bar is shown during training.                                                                 | `True`                                                                                                                                                                                                                                             |
| `random_state`             | \`int                           | None\`                                                                                                                 | The seed for the initialisation of the weights.                                                                                                                                                                                                    |

Attributes:

| Name                 | Type | Description                                                                                  |
| -------------------- | ---- | -------------------------------------------------------------------------------------------- |
| `train_losses_`      |      | The cross-entropy loss of each epoch, set by fit().                                          |
| `validation_losses_` |      | The validation cross-entropy loss of each epoch, set by fit() when validation data is given. |

##### `fit(X, y, X_val=None, y_val=None)`

Fit the estimator on `X` and `y`.

Parameters:

| Name    | Type      | Description                                                    | Default                                                               |
| ------- | --------- | -------------------------------------------------------------- | --------------------------------------------------------------------- |
| `X`     | `ndarray` | The sequences, of shape (n_samples, seq_len, seq_feature_dim). | *required*                                                            |
| `y`     | `ndarray` | One label for each sequence, of shape (n_samples,).            | *required*                                                            |
| `X_val` | \`ndarray | None\`                                                         | Optional validation sequences, of the same shape as X. Default: None. |
| `y_val` | \`ndarray | None\`                                                         | The labels of X_val. Default: None.                                   |

Returns:

| Type            | Description                                   |
| --------------- | --------------------------------------------- |
| `RecurrentHQNC` | This estimator, so that calls can be chained. |

##### `predict(X)`

Predict a label for every sample in `X`.

Parameters:

| Name | Type      | Description                                                    | Default    |
| ---- | --------- | -------------------------------------------------------------- | ---------- |
| `X`  | `ndarray` | The sequences, of shape (n_samples, seq_len, seq_feature_dim). | *required* |

Returns:

| Type      | Description                                              |
| --------- | -------------------------------------------------------- |
| `ndarray` | One prediction for each sequence, of shape (n_samples,). |

##### `predict_proba(X)`

Predict the class probabilities of every sample in `X`.

Parameters:

| Name | Type      | Description                                                    | Default    |
| ---- | --------- | -------------------------------------------------------------- | ---------- |
| `X`  | `ndarray` | The sequences, of shape (n_samples, seq_len, seq_feature_dim). | *required* |

Returns:

| Type      | Description                                                                                     |
| --------- | ----------------------------------------------------------------------------------------------- |
| `ndarray` | The probabilities, of shape (n_samples, n_classes), with the columns in the order of classes\_. |

#### `RecurrentHQNR`

Bases: `ModelAccessMixin`, `BaseQuantumRegressor`

Hybrid regressor: a recurrent encoder, a quantum layer and a linear head.

The recurrent encoder is a recurrent neural network (RNN). The estimator expects 3-D inputs of shape `(n_samples, seq_len, seq_feature_dim)`. By default, only the last timestep of the RNN output goes to the quantum layer. Set `rnn_return_last_timestep=False`, together with `sequence_length`, to use the full RNN output (flattened) instead. The quantum part is the same as in HQNR: `MinMaxNorm`, the quantum layer and an `nn.Linear` head.

Parameters:

| Name                       | Type                            | Description                                                                                                            | Default                                                                                                                                                                                                                                            |
| -------------------------- | ------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `seq_feature_dim`          | `int`                           | The number of features at each time step (the last axis of X).                                                         | *required*                                                                                                                                                                                                                                         |
| `rnn_type`                 | `Literal['rnn', 'gru', 'lstm']` | The type of recurrent layer: "rnn", "gru" (gated recurrent unit) or "lstm" (long short-term memory).                   | `'gru'`                                                                                                                                                                                                                                            |
| `rnn_hidden_dim`           | `int`                           | The hidden size in each direction.                                                                                     | `32`                                                                                                                                                                                                                                               |
| `rnn_num_layers`           | `int`                           | The number of stacked RNN layers.                                                                                      | `1`                                                                                                                                                                                                                                                |
| `rnn_dropout`              | `float`                         | The dropout between the RNN layers. It applies only if rnn_num_layers > 1.                                             | `0.0`                                                                                                                                                                                                                                              |
| `rnn_bidirectional`        | `bool`                          | If True, the RNN is bidirectional, and its output size doubles.                                                        | `False`                                                                                                                                                                                                                                            |
| `rnn_return_last_timestep` | `bool`                          | If True, only the last output step is kept. Otherwise, the full sequence is flattened, which requires sequence_length. | `True`                                                                                                                                                                                                                                             |
| `sequence_length`          | \`int                           | None\`                                                                                                                 | The number of time steps in each sample. It is required when rnn_return_last_timestep=False, and it must equal X.shape[1] in fit.                                                                                                                  |
| `in_quantum`               | \`int                           | None\`                                                                                                                 | The number of inputs of the quantum layer. It defaults to the size of the RNN output that reaches the quantum layer: rnn_hidden_dim, doubled when the RNN is bidirectional, and multiplied by sequence_length when rnn_return_last_timestep=False. |
| `n_qubits`                 | \`int                           | None\`                                                                                                                 | The number of qubits in each circuit. It defaults to in_quantum.                                                                                                                                                                                   |
| `depth`                    | `int`                           | The number of repetitions of the variational layer.                                                                    | `2`                                                                                                                                                                                                                                                |
| `layer_type`               | `str`                           | The quantum layer. It is one of "Basic", "Reupload" and "Ensemble".                                                    | `'Basic'`                                                                                                                                                                                                                                          |
| `enc_rotation`             | \`Literal['RX', 'RY', 'RZ']     | None\`                                                                                                                 | The encoding rotation: "RX", "RY", "RZ" or None.                                                                                                                                                                                                   |
| `var_rotation`             | \`Literal['RX', 'RY', 'RZ']     | None\`                                                                                                                 | The variational rotation of the "basic" ansatz: "RX", "RY", "RZ" or None.                                                                                                                                                                          |
| `variational_type`         | \`Literal['basic', 'strong']    | None\`                                                                                                                 | The type of variational block: "basic", "strong" or None.                                                                                                                                                                                          |
| `basis`                    | \`Literal['X', 'Y', 'Z']        | None\`                                                                                                                 | The measurement basis: "X", "Y", "Z" or None. For these four arguments, None means "use the default of the layer named by layer_type", which is where each of these values is decided.                                                             |
| `provider`                 | `ProviderSpec`                  | The backend. It takes the same options as Circuit(provider=...).                                                       | `'numpy'`                                                                                                                                                                                                                                          |
| `lr`                       | `float`                         | The learning rate of the optimiser.                                                                                    | `0.01`                                                                                                                                                                                                                                             |
| `optimizer`                | `str`                           | The name of the optimiser.                                                                                             | `'adam'`                                                                                                                                                                                                                                           |
| `optimizer_kwargs`         | \`dict                          | None\`                                                                                                                 | Extra keyword arguments passed to the optimiser.                                                                                                                                                                                                   |
| `epochs`                   | `int`                           | The number of training epochs.                                                                                         | `50`                                                                                                                                                                                                                                               |
| `batch_size`               | `int`                           | The size of each mini-batch.                                                                                           | `32`                                                                                                                                                                                                                                               |
| `show_progress`            | `bool`                          | If True, a tqdm progress bar is shown.                                                                                 | `True`                                                                                                                                                                                                                                             |
| `loss_func`                | `str`                           | The regression loss: "mse", "mae" or "huber".                                                                          | `'mse'`                                                                                                                                                                                                                                            |
| `huber_delta`              | `float`                         | The threshold of the Huber loss.                                                                                       | `1.0`                                                                                                                                                                                                                                              |
| `huber_reduction`          | `str`                           | The reduction of the Huber loss: "mean" or "sum".                                                                      | `'mean'`                                                                                                                                                                                                                                           |
| `random_state`             | \`int                           | None\`                                                                                                                 | The seed.                                                                                                                                                                                                                                          |

Attributes:

| Name                 | Type | Description                                                                    |
| -------------------- | ---- | ------------------------------------------------------------------------------ |
| `train_losses_`      |      | The training loss of each epoch, set by fit().                                 |
| `validation_losses_` |      | The validation loss of each epoch, set by fit() when validation data is given. |

##### `fit(X, y, X_val=None, y_val=None)`

Fit the estimator on `X` and `y`.

Parameters:

| Name    | Type      | Description                                                            | Default                                                               |
| ------- | --------- | ---------------------------------------------------------------------- | --------------------------------------------------------------------- |
| `X`     | `ndarray` | The sequences, of shape (n_samples, seq_len, seq_feature_dim).         | *required*                                                            |
| `y`     | `ndarray` | One target for each sequence, of shape (n_samples,) or (n_samples, 1). | *required*                                                            |
| `X_val` | \`ndarray | None\`                                                                 | Optional validation sequences, of the same shape as X. Default: None. |
| `y_val` | \`ndarray | None\`                                                                 | The targets of X_val. Default: None.                                  |

Returns:

| Type            | Description                                   |
| --------------- | --------------------------------------------- |
| `RecurrentHQNR` | This estimator, so that calls can be chained. |

##### `predict(X)`

Predict a value for every sample in `X`.

Parameters:

| Name | Type      | Description                                                    | Default    |
| ---- | --------- | -------------------------------------------------------------- | ---------- |
| `X`  | `ndarray` | The sequences, of shape (n_samples, seq_len, seq_feature_dim). | *required* |

Returns:

| Type      | Description                                                                 |
| --------- | --------------------------------------------------------------------------- |
| `ndarray` | One prediction for each sequence, as a float32 array of shape (n_samples,). |

#### `load_model(path, *, provider=None, model=None)`

Load any saved model. The class of the model is read from `metadata.json`.

Load only bundles that you trust. A bundle can contain joblib pickles, and unpickling runs code, as with `torch.load` or `joblib.load`.

Parameters:

| Name       | Type           | Description                                                                                                                    | Default                                                                                     |
| ---------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------- |
| `path`     | \`str          | Path\`                                                                                                                         | The path to the .eigenkit bundle.                                                           |
| `provider` | \`ProviderSpec | None\`                                                                                                                         | A provider that replaces the saved one. It is passed to the load() method of the estimator. |
| `model`    | `Any`          | The nn.Module architecture, for checkpoints of GenericRegressor and GenericClassifier. It is ignored for the other estimators. | `None`                                                                                      |

Returns:

| Type                   | Description                                                         |
| ---------------------- | ------------------------------------------------------------------- |
| `BaseQuantumEstimator` | The loaded estimator, an instance of the class named in the bundle. |
