# algorithms

## `eigenkit.algorithms.HHL`

Bases: `BaseQuantumSolver`

Quantum linear solver based on the algorithm of Harrow, Hassidim and Lloyd (HHL).

Solves `Ax = b` for any invertible square matrix `A`. If `A` is Hermitian, it is used directly, even when it is not positive semidefinite. Any other matrix is converted to the normal equations `A†Ax = A†b`, which have the same solution and a Hermitian, positive semidefinite system matrix. The cost of this conversion is that the condition number `κ` is squared.

The circuit itself inverts the eigenvalues. It applies controlled `RY` rotations to the ancilla, with one fixed angle for each state of the clock register, so the reciprocal `1/λ` of each eigenvalue is encoded in the amplitudes without any classical division by the eigenvalues. The readout is different: it uses the simulator's full statevector and keeps only the part where the ancilla is in `|1⟩` and the clock register is in `|0⟩`. HHL produces the quantum state `|x⟩`, so reading a classical vector from it is only possible on a simulator. See HHLResult.

Warning

The backend must provide `statevector()` on its compiled circuit. The built-in numpy simulator, PennyLane `default.qubit` and `lightning.qubit`, and Qiskit Aer all do. A custom backend registered with register() must implement it, otherwise `solve()` raises `NotImplementedError`.

**Scope and scaling.** HHL is efficient only when three conditions hold: `A` is a sum of a few short Pauli terms, `A` is well conditioned, and the state `|b⟩` can be prepared efficiently.

- *Hamiltonian simulation.* `e^{iAt}` is Trotterised from the Pauli decomposition of `A`, so its cost follows the number of Pauli terms. This means sparsity in the Pauli basis, which is different from sparsity of the matrix. The module `algorithms/_hamiltonian_sim.py` explains what this means in practice. In short, a sparse matrix is not necessarily cheap to simulate here.
- *State preparation.* `|b⟩` is prepared with AmplitudeEncoding, which is treated as an oracle of unit cost. Loading general amplitudes costs `O(N)` gates, so a real speedup assumes that `b` comes from a formula, from an earlier quantum routine, or from a quantum random-access memory (qRAM).
- *Conditioning.* The condition number `κ` is never an input. It only bounds the achievable success probability and the size of the clock register.
- *Inversion angles.* The gates are elementary (see `_uniformly_controlled_ry()`), so the circuit built in `"honest"` mode contains no unitary-matrix gate. However, the `2^n_l` angles, where `n_l` is the number of clock qubits, are listed one by one instead of being computed by the circuit. The classical description of the circuit therefore has size `Θ(2^n_l)`. The circuit can be built from elementary gates, but its description is not compact.

Solvers for dense matrices, for example the one of Wossnig, Zhao and Prakash, reach a quadratic speedup when the spectral norm is bounded. They need a specialised memory architecture that this package does not provide.

**Real and complex inputs.** For real `A` and `b`, the readout discards the imaginary part. The leading error of the Trotter eigenvectors enters the raw vector as a purely imaginary component, so discarding the imaginary part removes it. This matters at `trotter_order=1`, where it improves the convergence by one order: the measured error falls as `steps⁻²` for real inputs, and as `steps⁻¹` for a complex Hermitian `A` or a complex `b`, which keep that error. At `trotter_order=2`, the default, the first-order commutator already cancels by construction, so there is nothing left to remove, and real and complex inputs both converge as `steps⁻²`. This advantage was not designed. It depends on the test for real inputs in solve(), so a change to that test changes the convergence rate at order 1.

Parameters:

| Name                  | Type           | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             | Default                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| --------------------- | -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `n_eigenvalue_qubits` | `int`          | The number of qubits in the clock register. Eigenvalues are resolved into 2\*\*n_eigenvalue_qubits buckets. The value must be at least 1, and at least 2 in "honest" mode. More qubits give a finer resolution of the spectrum and a larger circuit. The default is 4.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  | `4`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| `t`                   | \`float        | None\`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  | The evolution time for U = e^{iAt}. If given, it must be positive and finite. If None, it is chosen automatically: from the spectrum in "reference" mode, and from a Gershgorin bound in "honest" mode. The default is None.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| `epsilon`             | `float`        | The relative eigenvalue cutoff, in \[0, 1). For a Hermitian A, buckets whose estimated eigenvalue is below epsilon * λ_max are treated as zero. For the normal equations, the cutoff applies to the singular values instead, and it is implemented as epsilon² on the squared spectrum. If no eigenvalue is above the cutoff, solve() raises an error. The default is 0.01.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             | `0.01`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| `provider`            | `ProviderSpec` | The backend to use: a registered name ("numpy", "pennylane", "qiskit" and so on), a config instance, or a backend object. It must provide statevector(). The default is "numpy".                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        | `'numpy'`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `mode`                | `str`          | How the circuit is built. This argument is keyword-only. "reference" (the default): the eigenvalues are computed classically with eigvalsh, e^{iAt} is a dense QubitUnitary computed with expm, and t is tuned to the known spectrum. This mode is fast and exact, but the O(N³) precomputation and the dense unitary mean that its cost does not follow the scaling of HHL. "honest": the circuit is built as it would be on hardware. No eigendecomposition sets any circuit parameter: t comes from a Gershgorin bound, the scaling constant C of the inversion comes from the resolution of the clock register (see \_honest_qpe_params()), and e^{iAt} is Trotterised into elementary gates. This mode requires n_eigenvalue_qubits >= 2. Input validation still runs an O(N³) rank check. This check makes the solver safer to use and is not part of the algorithm; see solve(). | `'reference'`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| `trotter_steps`       | \`int          | Literal['auto']\`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       | The number of Trotter steps for the base evolution time in "honest" mode. It is ignored in "reference" mode. With "auto", the default, the solver picks the smallest number of steps for which a computable bound on the Trotter drift ‖A_eff − A‖ stays below trotter_tolerance × C, which is below the eigenvalue resolution of the clock register. This matters because, with a fixed step size, the circuit is exact HHL for an effective matrix A_eff whose eigenvectors are rotated even when its eigenvalues are exact. That changes the direction of the answer while every check based on the spectrum reports success. An integer turns off the automatic choice, and the solver warns if the drift bound then exceeds C/2. Above that value, an extreme eigenvalue can be captured by the Nyquist bucket or wrap around the branch cut. The automatic choice is limited by a gate budget, and the solver says so when the limit is reached. The default is "auto". |
| `trotter_order`       | `int`          | The Trotter order, 1 or 2. The default is 2. The symmetric product of order 2 is a palindrome, so the first-order commutator cancels, the error falls from O(τ) to O(τ²), and A_eff stays real for a real A. At order 1, A_eff picks up Y-type terms, and a real system returns a complex state. Order 1 is kept for cost comparisons.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  | `2`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| `trotter_tolerance`   | `float`        | The target for trotter_steps="auto". This argument is keyword-only. The drift bound must not exceed trotter_tolerance × C. The default, 0.25, keeps the drift at a quarter of a bucket, below the C/2 threshold at which the Nyquist bucket can capture an eigenvalue. At order 2, the cost grows as steps ∝ 1/√tolerance.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              | `_AUTO_TROTTER_TOLERANCE`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |

Raises:

| Type         | Description                                                                                                |
| ------------ | ---------------------------------------------------------------------------------------------------------- |
| `ValueError` | If n_eigenvalue_qubits is below 1, if t is not a positive finite number, or if epsilon is outside \[0, 1). |

Example

This example solves a `2 × 2` Hermitian system:

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

A = np.array([[2, 1], [1, 2]], dtype=float)
b = np.array([1, 0], dtype=float)
result = HHL(n_eigenvalue_qubits=6).solve(A, b)
print(result.solution)
print(result.residual)
```

This example solves the same system on a faster simulator:

```python
from eigenkit import HHL, PennyLaneConfig

solver = HHL(
    n_eigenvalue_qubits=6,
    provider=PennyLaneConfig(device_name="lightning.qubit"),
)
result = solver.solve(A, b)
```

### `solve(A, b)`

Solve the linear system `Ax = b`.

Parameters:

| Name | Type      | Description                                                                                                             | Default    |
| ---- | --------- | ----------------------------------------------------------------------------------------------------------------------- | ---------- |
| `A`  | `ndarray` | A real or complex square matrix, which must be invertible. A non-Hermitian matrix is converted to the normal equations. | *required* |
| `b`  | `ndarray` | The right-hand side, of length A.shape[0].                                                                              | *required* |

Returns:

| Type        | Description                                                                                                                                             |
| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `HHLResult` | An HHLResult. Its solution is read from the simulator's statevector and completed with a classical least-squares rescaling. See the notes on HHLResult. |

Raises:

| Type                  | Description                                                                                                                                        |
| --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ValueError`          | If A is not square or is singular, if b has the wrong length, is zero or is not finite, or if t causes aliasing in quantum phase estimation (QPE). |
| `NotImplementedError` | If the backend does not implement statevector(), or if it returns something else, such as a density matrix.                                        |

Note

Validation runs one `O(N³)` singular value decomposition (SVD) to reject a singular `A` and to compute a condition number for the warnings. No quantity derived from it reaches `t`, `C`, the rotation angles or the number of Trotter steps, and changing it would leave the circuit exactly the same. `mode="honest"` therefore keeps its defining property, but `solve()` itself still does `O(N³)` work.

## `eigenkit.algorithms.HHLResult`

Bases: `ValueEquality`

Output of HHL.solve().

The result is read-only. Assigning to a field raises `FrozenInstanceError`, and writing into an array field raises `ValueError`. This prevents a mistyped `=` (instead of `==`) or an in-place edit from leaving a result whose scalar fields describe a different run from the arrays it holds. To get a writable copy of an array, use `np.array(result.field)`.

Results compare by value, including their arrays. They are not hashable, because a result is a collection of numbers and not a key, and a correct hash of a float array is expensive to compute.

Attributes:

| Name                  | Type      | Description                                                                                                                                             |
| --------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `solution`            | `ndarray` | The best-fit x with Ax ≈ b. It is float64 for real inputs, and complex128 when A or b has a non-zero imaginary part.                                    |
| `euclidean_norm`      | `float`   | The norm ‖solution‖ of the returned solution. It is computed after the classical least-squares rescaling, so it is not a norm that HHL itself measures. |
| `residual`            | `float`   | The relative error ‖Ax − b‖ / ‖b‖, computed with the original A and b.                                                                                  |
| `success_probability` | `float`   | The probability that the ancilla is in                                                                                                                  |
| `readout_probability` | `float`   | The probability of the joint event from which the solution is read: the ancilla in                                                                      |
| `raw_solution`        | \`ndarray | None\`                                                                                                                                                  |

Note

HHL produces the quantum state `|x⟩`, not a vector of numbers. The full array in this result is read from the simulator's statevector, which only a simulator can provide. On hardware, only expectation values can be measured, and extracting all `N` components of `|x⟩` costs `O(N)` measurements, which cancels the speedup. In practice, `|x⟩` is passed to another quantum routine, or used to estimate one observable. Recovering the sign of `1/λ` needs a separate protocol. All of this applies to both values of `mode`.

Warning

`solution` is completed by two classical steps that hardware cannot perform. For real inputs, the imaginary part is discarded. The magnitude and the sign then come from a least-squares projection, `x = raw · ⟨A·raw, b⟩ / ‖A·raw‖²`, which uses the full classical `A` and `b`. Only the direction of `raw` comes from the quantum computation. The rescaling can change `residual` by an order of magnitude, and for an indefinite `A` its sign correction is not reliable, because `⟨Ax, b⟩ ≥ 0` does not imply `⟨x, A⁻¹b⟩ ≥ 0`.

### `__post_init__()`

Make the array fields read-only.

Freezing the dataclass prevents a field from being reassigned, but it does not prevent writing into the array that the field holds, and a result that has been written into still reports the scalar values of the original run. The result therefore keeps its own copy, so the array it was built from can still be changed by the caller. To get a writable copy, use `np.array(result.solution)`.

Both array fields are frozen. `raw_solution` is optional, and `freeze_arrays` would turn a `None` into a zero-dimensional object array, so it is only frozen when the run recorded one.

## `eigenkit.algorithms.IQAE`

Bases: `BaseQuantumSolver`

Iterative Quantum Amplitude Estimation (IQAE).

Estimates `a = |⟨ψ₁|A|0⟩|²` to an additive precision `epsilon`, with a failure probability of at most `alpha`. It uses the Grover operator `Q = A S₀ A† S_ψ₁`, built from the oracle `A` that you provide.

The oracle can be given in two forms:

- A **PennyLane callable** `oracle(wires)`, which applies the circuit `A` in place. The last wire (`wires[-1]`) must be the ancilla qubit:

```python
def my_oracle(wires):
    qml.RY(theta, wires=wires[0])
    qml.CNOT(wires=[wires[0], wires[-1]])
```

The matrix of the circuit is extracted internally with `qml.matrix`, so this form requires `provider="pennylane"` and the `pennylane` extra.

- A CircuitSpec, which works on any backend that supports the `QubitUnitary`, `X`, `MCZ` and `Z` gates. With a provider other than PennyLane, pass `shot_mode="synthetic"` or `shot_mode="auto"`, because `shot_mode="provider"` works only with PennyLane.

Parameters:

| Name           | Type           | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       | Default                                                                                                                                         |
| -------------- | -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| `epsilon`      | `float`        | The additive precision to reach, that is, the half-width of the final confidence interval for a. It must be in (0, π/8).                                                                                                                                                                                                                                                                                                                                                                                                                                                          | *required*                                                                                                                                      |
| `alpha`        | `float`        | The total failure probability. The algorithm applies a Bonferroni correction, so the confidence interval of each round is valid at level alpha / T, where T = ceil(log2(π / (8 * epsilon))) is the maximum number of rounds. It must be in (0, 1).                                                                                                                                                                                                                                                                                                                                | *required*                                                                                                                                      |
| `n_qubits`     | `int`          | The total number of qubits, including the ancilla.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                | *required*                                                                                                                                      |
| `provider`     | `ProviderSpec` | The backend to use: a registered name ("numpy", "pennylane", "qiskit" and so on), a config instance, or a backend object. The default is "numpy", the built-in simulator.                                                                                                                                                                                                                                                                                                                                                                                                         | `'numpy'`                                                                                                                                       |
| `n_shots`      | `int`          | The number of measurement shots per round. With shot_mode="synthetic", which is used on analytic backends, it sets the size of a seeded binomial draw on top of the exact probability of the ancilla. With shot_mode="provider" or "auto", it is the number of samples per round on the hardware or simulator, unless the provider config sets its own shots, which takes precedence. For example, a device configured for 50 shots samples 50 times, whatever the value of n_shots. To control sampling on those backends, set shots on the provider config. The default is 100. | `100`                                                                                                                                           |
| `shot_mode`    | `ShotMode`     | How the ancilla measurements are obtained. With "auto", the default, the provider's shot count is used when the backend config sets a finite shots, and synthetic binomial noise is used otherwise. "provider" always samples from the backend. "synthetic" always uses exact expectation values plus seeded binomial noise.                                                                                                                                                                                                                                                      | `'auto'`                                                                                                                                        |
| `min_ratio`    | `float`        | The minimum ratio K\_{i+1} / K_i between the Grover powers of consecutive rounds. It must be greater than 1.                                                                                                                                                                                                                                                                                                                                                                                                                                                                      | `2.0`                                                                                                                                           |
| `random_state` | \`int          | None\`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            | The seed of the random number generator used for the shot noise. An integer gives reproducible results, and None uses a non-deterministic seed. |

Example

```python
import numpy as np
from eigenkit import CircuitBuilder
from eigenkit.algorithms import IQAE

# RY(pi/3) on the ancilla prepares an amplitude of sin^2(pi/6) = 0.25.
qc = CircuitBuilder(n_qubits=2)
qc.ry(np.pi / 3, qubit=1)
oracle = qc.build()

iqae = IQAE(epsilon=0.02, alpha=0.05, n_qubits=2, random_state=0)
result = iqae.solve(oracle)

print(result.estimation)           # 0.246
print(result.confidence_interval)  # (0.2407, 0.2513), which contains 0.25
print(result.n_oracle_calls)       # 2600
```

### `solve(oracle)`

Estimate the amplitude `a = |⟨ψ₁|A|0⟩|²`.

Parameters:

| Name     | Type       | Description   | Default                                                                                                                                                                                      |
| -------- | ---------- | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `oracle` | \`Callable | CircuitSpec\` | Either a PennyLane callable oracle(wires) that builds the state-preparation circuit A, or a CircuitSpec produced by CircuitBuilder. In both cases, the last qubit is treated as the ancilla. |

Returns:

| Type         | Description                                                                             |
| ------------ | --------------------------------------------------------------------------------------- |
| `IQAEResult` | An IQAEResult with the point estimate, the confidence interval and the resource counts. |

Raises:

| Type         | Description                                                                                       |
| ------------ | ------------------------------------------------------------------------------------------------- |
| `ValueError` | If the selected provider does not support the gates that IQAE needs (QubitUnitary, X, MCZ and Z). |

## `eigenkit.algorithms.IQAEResult`

Bases: `ValueEquality`

Outcome of a completed Iterative Quantum Amplitude Estimation (IQAE) run.

The result is read-only. Assigning to a field raises `FrozenInstanceError`, so a mistyped `=` (instead of `==`) cannot change a result. This type holds no arrays, so nothing else needs protecting.

Results compare by value. They are not hashable, so that all result types behave in the same way: the types that hold arrays cannot be hashed both correctly and efficiently.

Attributes:

| Name                  | Type                  | Description                                                                                                                                                                                                                                                                  |
| --------------------- | --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `estimation`          | `float`               | The point estimate of the amplitude a.                                                                                                                                                                                                                                       |
| `confidence_interval` | `tuple[float, float]` | The (lower, upper) Clopper-Pearson interval at the requested confidence level 1 - alpha.                                                                                                                                                                                     |
| `n_rounds`            | `int`                 | The number of rounds that the run executed. It can also be read as n_iterations, the name that the other result types use.                                                                                                                                                   |
| `n_oracle_calls`      | `int`                 | The total number of oracle calls over all rounds.                                                                                                                                                                                                                            |
| `success`             | `bool`                | Whether the interval reached the requested precision. This is the same test that ends the loop over rounds, and the one reported by the warning that the run did not converge. False means that the run stopped at max_rounds with an interval still wider than 2 * epsilon. |

### `n_iterations`

The number of rounds, under the name that the other results use.

`n_rounds` is the name that IQAE uses, and it stays available. This property lets one loop read the number of iterations from any result.

Returns:

| Type  | Description                                 |
| ----- | ------------------------------------------- |
| `int` | The number of rounds that the run executed. |

## `eigenkit.algorithms.PortfolioRiskEstimator`

Bases: `BaseQuantumEstimator`

Quantum estimator of portfolio risk.

It estimates the Value at Risk (VaR) and the Conditional Value at Risk (CVaR) of a portfolio. The estimator can use IQAE to estimate the tail probability on a quantum circuit. The oracle for IQAE is built from the fitted return distribution with amplitude encoding.

Note

This is not a standard scikit-learn regressor. The risk at a confidence level comes from `estimate()` and `var_at()`, which take confidence levels and not feature vectors, and the estimator has no meaningful scikit-learn `score()`. It inherits only from BaseQuantumEstimator.

Not financial advice

The estimates come from a model fitted to the price history you pass in, and they carry no guarantee. They are not investment advice.

Parameters:

| Name                       | Type           | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     | Default                                       |
| -------------------------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------- |
| `n_qubits`                 | `int`          | The total number of qubits, including the ancilla. The loss distribution is discretised into 2^(n_qubits - 1) bins. The minimum is 2: one data qubit and the ancilla.                                                                                                                                                                                                                                                                                                                                                                                                                                                           | `6`                                           |
| `epsilon`                  | `float`        | The additive precision of the IQAE estimate of the tail probability.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            | `0.01`                                        |
| `alpha`                    | `float`        | The failure probability of IQAE. The confidence level is 1 − alpha.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             | `0.05`                                        |
| `horizon_days`             | `int`          | The investment horizon, in trading days, used to scale daily log-returns to the target horizon. The default is 1, which gives daily risk measures.                                                                                                                                                                                                                                                                                                                                                                                                                                                                              | `1`                                           |
| `provider`                 | `ProviderSpec` | The backend that IQAE uses: a registered name ("pennylane", "qiskit", "braket" and so on), a config instance, or a backend object. The default is "numpy". Note The Grover operator built internally uses the adjoint of an AmplitudeEncoding block, so the provider must support the adjoint of a state preparation and provide a statevector. The built-in "numpy" simulator and PennyLane both do, and their results agree to the last digit. A backend that does not raises an error when the operator is built. Calling IQAE directly with a CircuitSpec based on QubitUnitary avoids this requirement.                    | `'numpy'`                                     |
| `n_shots`                  | `int`          | The number of shots per IQAE round. The default is 100.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         | `100`                                         |
| `random_state`             | \`int          | None\`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          | The seed that makes the results reproducible. |
| `n_bisection_steps`        | `int`          | The number of binary-search steps used to locate the VaR with quantum estimates of the tail probability. With 0, the default, the VaR and CVaR are computed classically from the fitted bins. A positive value calls \_quantum_var_bisection(), which refines the threshold by running IQAE at each step of the bisection.                                                                                                                                                                                                                                                                                                      | `0`                                           |
| `measure_tail_probability` | `bool`         | When True, the default, estimate() runs IQAE once after the VaR and CVaR are computed, and reports the quantum estimate of the tail in tail_probability and confidence_interval. Set it to False to skip that final IQAE call when only the classical VaR and CVaR are needed and n_bisection_steps=0.                                                                                                                                                                                                                                                                                                                          | `True`                                        |
| `distribution`             | `str`          | The model of the loss distribution that fit() uses. "gaussian" (the default) fits the mean μ and the standard deviation σ of the scaled log-returns, and integrates the Gaussian cumulative distribution function (CDF) over each bin. The distribution is truncated at ±6σ. A VaR beyond that point is capped at the limit, and a RuntimeWarning is issued. "empirical" places each historical return in the nearest bin of equal width, without assuming a parametric form. "t" fits a Student-t distribution by maximum-likelihood estimation, and integrates its CDF over each bin. It has heavier tails than the Gaussian. | `'gaussian'`                                  |

Example

```python
import numpy as np
from eigenkit.algorithms import PortfolioRiskEstimator

rng = np.random.default_rng(0)
returns = rng.normal(0.0005, 0.02, size=(250, 3))
prices = 100 * np.exp(np.cumsum(returns, axis=0))

estimator = PortfolioRiskEstimator(
    n_qubits=5, provider="numpy", random_state=0
)
estimator.fit(prices, weights=np.array([0.5, 0.3, 0.2]))

risk = estimator.estimate(confidence_level=0.95)

print(risk.var)               # 0.0237: at most a 2.37% loss on 95% of days
print(risk.cvar)              # 0.0262: the average loss on the worst 5%
print(risk.tail_probability)  # 0.0659: the IQAE estimate of the tail mass
```

### `fit(X, y=None, *, weights=None)`

Fit the return distribution to historical price data.

Parameters:

| Name      | Type      | Description                                                                                                                                                        | Default                                                                                                           |
| --------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------- |
| `X`       | `ndarray` | A 2-D array of shape (T, n_assets) with daily closing prices, where T must be at least 2. A 1-D array of length T is accepted for a portfolio with a single asset. | *required*                                                                                                        |
| `y`       | \`ndarray | None\`                                                                                                                                                             | Ignored, because this estimator is unsupervised. It is present for compatibility with the scikit-learn interface. |
| `weights` | \`ndarray | None\`                                                                                                                                                             | A 1-D array of portfolio weights that sum to 1. If it is not given, all assets have equal weights.                |

Raises:

| Type         | Description                                                                                                                            |
| ------------ | -------------------------------------------------------------------------------------------------------------------------------------- |
| `ValueError` | If distribution is not one of the supported values. This can happen when distribution is changed with set_params() after construction. |

### `var_at(confidence_levels)`

Return the VaR at each confidence level.

This is the array version of `estimate()`. It runs IQAE once for each level and returns only the `var` of each result.

Parameters:

| Name                | Type      | Description                                                                                                                                      | Default    |
| ------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | ---------- |
| `confidence_levels` | `ndarray` | A 1-D array of confidence levels in (0, 1), for example [0.95, 0.99]. A scalar or an (n, 1) column is also accepted, and it is read as n levels. | *required* |

Returns:

| Type      | Description                                                                                 |
| --------- | ------------------------------------------------------------------------------------------- |
| `ndarray` | An array of VaR values, as fractions of the portfolio value lost, with one value per level. |

Raises:

| Type             | Description                                                   |
| ---------------- | ------------------------------------------------------------- |
| `ValueError`     | If the argument is not 1-D after a single column is squeezed. |
| `NotFittedError` | If fit() has not been called.                                 |

Warns:

| Type          | Description                                                                                                                                                                                       |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `UserWarning` | If any level is below 0.5. Such a level is valid, but values below 0.5 are also what a price series or a feature matrix looks like, and this warning catches the mistake of passing one of those. |

Warning

Each level runs IQAE once, so `n` levels need `n` separate quantum circuits. For a single risk measure, call `estimate()` instead.

Example

```python
estimator.var_at([0.90, 0.95, 0.99])
```

### `predict(X)`

Not supported. Use `var_at()` instead.

The estimator base class requires a `predict` method. In scikit-learn, `predict` takes samples from the same feature space that `fit` received, but the risk at a confidence level is a different question, which `var_at()` answers.

Parameters:

| Name | Type      | Description | Default    |
| ---- | --------- | ----------- | ---------- |
| `X`  | `ndarray` | Ignored.    | *required* |

Raises:

| Type                  | Description                             |
| --------------------- | --------------------------------------- |
| `NotImplementedError` | Always. The message points to var_at(). |

### `estimate(confidence_level=0.95, *, _seed_seq=None)`

Estimate the VaR and CVaR at `confidence_level`.

The VaR and CVaR come from the discretised loss distribution. When `n_bisection_steps > 0`, the VaR is refined by a bisection that uses IQAE. Otherwise, the classical quantiles of the bins are used. Unless `measure_tail_probability=False`, a final IQAE run estimates `P(loss ≥ VaR)`, for checking and diagnostics.

Parameters:

| Name               | Type           | Description                                                                                          | Default                                                                                                                                                                                                        |
| ------------------ | -------------- | ---------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `confidence_level` | `float`        | The probability level of the risk measures, for example 0.95 for the 95 % VaR. It must be in (0, 1). | `0.95`                                                                                                                                                                                                         |
| `_seed_seq`        | \`SeedSequence | None\`                                                                                               | A private override of the random seed, used by var_at() so that the shot noise of the IQAE runs for different confidence levels is independent. The default is None, which derives the seed from random_state. |

Returns:

| Type                  | Description            |
| --------------------- | ---------------------- |
| `PortfolioRiskResult` | A PortfolioRiskResult. |

Raises:

| Type           | Description                                                                      |
| -------------- | -------------------------------------------------------------------------------- |
| `RuntimeError` | If fit() has not been called.                                                    |
| `ValueError`   | If the selected provider cannot compile the oracle for the tail that IQAE needs. |

### `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. |

### `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. |

## `eigenkit.algorithms.PortfolioRiskResult`

Bases: `ValueEquality`

Outcome of a PortfolioRiskEstimator run.

The result is read-only. Assigning to a field raises `FrozenInstanceError`, so a mistyped `=` (instead of `==`) cannot change a result. This type holds no arrays, so nothing else needs protecting.

Results compare by value. They are not hashable, so that all result types behave in the same way: the types that hold arrays cannot be hashed both correctly and efficiently.

Attributes:

| Name                  | Type                  | Description                                                                                                                                   |
| --------------------- | --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| `var`                 | `float`               | The Value at Risk (VaR) at the requested confidence_level, as a fraction of the portfolio value lost. For example, 0.12 means a loss of 12 %. |
| `cvar`                | `float`               | The Conditional Value at Risk (CVaR), also called Expected Shortfall, at the same confidence level.                                           |
| `tail_probability`    | `float`               | The IQAE point estimate of P(loss ≥ VaR).                                                                                                     |
| `confidence_interval` | `tuple[float, float]` | The (lower, upper) Clopper-Pearson interval for the tail probability, at level 1 - alpha.                                                     |
| `iqae_result`         | \`IQAEResult          | None\`                                                                                                                                        |
