## `eigenkit.core.backends.pennylane_backend`

The PennyLane backend.

It declares `gradient_method = "native"`: the PyTorch interface of PennyLane carries the gradients through the tape, so the parameter-shift rule is not needed here.

`compile()` builds the device, both qnodes (one for NumPy and one for PyTorch) and the Pauli operators once. Each `run()` only binds the parameters into the cached qnodes. A finite number of shots is set on each qnode with `qml.set_shots`, because passing `shots=` to `qml.device` was deprecated in PennyLane 0.43.

Example

```python
from eigenkit import Circuit, Encoding, Variational, all_z

blocks = [Encoding(in_features=4), Variational(depth=2)]

# by registered name
circuit = Circuit(blocks=blocks, observables=all_z(4), provider="pennylane")

# or with the config, when a device setting matters
from eigenkit.configs import PennyLaneConfig

circuit = Circuit(
    blocks=blocks,
    observables=all_z(4),
    provider=PennyLaneConfig(device_name="lightning.qubit"),
)
```

This backend needs the `pennylane` extra.

### `PennyLaneBackend`

The provider for PennyLane devices. It is registered as `"pennylane"`.

It is internal: you select this backend with `provider="pennylane"` or by passing a PennyLaneConfig, not by building this class.

#### `capabilities`

What this backend supports.

It runs every gate, evaluates a batch of inputs in one call, can sample a finite number of shots, and has no limit on the number of qubits. It also provides the two features that Iterative Quantum Amplitude Estimation (IQAE) needs: the dense unitary of a callable oracle, through `qml.matrix`, and sampling of the ancilla qubit by the provider, for `shot_mode="provider"`. See Capabilities.

#### `__init__(config=None, name='pennylane')`

Build the backend from a device configuration.

Parameters:

| Name     | Type              | Description                                                       | Default       |
| -------- | ----------------- | ----------------------------------------------------------------- | ------------- |
| `config` | `PennyLaneConfig` | The device settings. Default: None, which uses PennyLaneConfig(). | `None`        |
| `name`   | `str`             | The registry name, used in error messages. Default: "pennylane".  | `'pennylane'` |

#### `compile(circuit, observables)`

Prepare `circuit` for repeated evaluation of `observables`.

A `Circuit` calls it once and reuses the result for every call, so the expensive work belongs here.

Parameters:

| Name          | Type                   | Description                 | Default    |
| ------------- | ---------------------- | --------------------------- | ---------- |
| `circuit`     | `CircuitSpec`          | The circuit to prepare.     | *required* |
| `observables` | `Sequence[Observable]` | The observables to measure. | *required* |

Returns:

| Type                | Description                                          |
| ------------------- | ---------------------------------------------------- |
| `PennyLaneCompiled` | The prepared circuit, as a PennyLaneCompiled object. |

### `PennyLaneCompiled`

Bases: `CompiledCircuit`

A circuit prepared for PennyLane.

It is internal: PennyLaneBackend.compile() builds it and returns it, and it is never built directly. It has a `run_torch()` method, so the PyTorch interface differentiates through PennyLane itself instead of falling back to the parameter-shift rule.

#### `__init__(circuit, observables, config)`

Prepare the circuit for repeated evaluation.

`PennyLaneBackend.compile()` builds this object, so there is no reason to build one directly.

Parameters:

| Name          | Type                   | Description                 | Default    |
| ------------- | ---------------------- | --------------------------- | ---------- |
| `circuit`     | `CircuitSpec`          | The circuit to prepare.     | *required* |
| `observables` | `Sequence[Observable]` | The observables to measure. | *required* |
| `config`      | `PennyLaneConfig`      | The device settings.        | *required* |

#### `jacobian(weights, inputs, *, need_input_grad=False)`

Compute the gradient with PennyLane, using the configured `diff_method`.

The gradient code of the core package looks for a `jacobian` method, and prefers it to its own parameter-shift rule, which works on any backend. Here PennyLane differentiates the gate angles with its own engine (adjoint, backpropagation or parameter shift, as `diff_method` selects). These gate-angle gradients are then mapped onto the weights and inputs with the shared symbolic chain-rule factors, so angles built with `Scale`, `Multiply`, `Sum` or `Negate` are handled. It takes a single sample (1-D) only, and the caller loops over a batch.

Parameters:

| Name              | Type      | Description                                                                    | Default    |
| ----------------- | --------- | ------------------------------------------------------------------------------ | ---------- |
| `weights`         | `ndarray` | The parameter vector (1-D).                                                    | *required* |
| `inputs`          | `ndarray` | The input features of one sample (1-D).                                        | *required* |
| `need_input_grad` | `bool`    | If True, also compute the jacobian with respect to the inputs. Default: False. | `False`    |

Returns:

| Name              | Type      | Description                               |
| ----------------- | --------- | ----------------------------------------- |
| `weight_jacobian` | `ndarray` | The jacobian with respect to the weights. |
| `input_jacobian`  | \`ndarray | None\`                                    |

#### `probabilities(weights, inputs)`

Return the full measurement distribution over the `2**n_qubits` basis states.

Qubit `0` is the most significant bit. The distribution comes from `qml.probs`, through a qnode that is built on first use and then cached.

Parameters:

| Name      | Type      | Description                             | Default    |
| --------- | --------- | --------------------------------------- | ---------- |
| `weights` | `ndarray` | The parameter vector (1-D).             | *required* |
| `inputs`  | `ndarray` | The input features of one sample (1-D). | *required* |

Returns:

| Type      | Description                                   |
| --------- | --------------------------------------------- |
| `ndarray` | The probabilities, of shape (2\*\*n_qubits,). |

#### `run(weights, inputs)`

Evaluate the circuit and return one expectation value per observable.

It follows the `CompiledCircuit` contract: the result has shape `(n_obs,)` when `weights` and `inputs` are both 1-D, and `(B, n_obs)` otherwise.

Parameters:

| Name      | Type      | Description                                                                 | Default    |
| --------- | --------- | --------------------------------------------------------------------------- | ---------- |
| `weights` | `ndarray` | The parameter vector: 1-D for one sample, or 2-D with a leading batch axis. | *required* |
| `inputs`  | `ndarray` | The input features, with the same 1-D or 2-D shape convention as weights.   | *required* |

Returns:

| Type      | Description                                      |
| --------- | ------------------------------------------------ |
| `ndarray` | The expectation values, one for each observable. |

#### `run_torch(weights, inputs)`

Evaluate the circuit on PyTorch tensors, keeping the result on the tape.

This is the path of native automatic differentiation: the gradients flow through the simulator itself instead of through the parameter-shift fallback.

Parameters:

| Name      | Type     | Description                           | Default    |
| --------- | -------- | ------------------------------------- | ---------- |
| `weights` | `Tensor` | The parameter vector, 1-D or batched. | *required* |
| `inputs`  | `Tensor` | The input features, 1-D or batched.   | *required* |

Returns:

| Type     | Description                                                                |
| -------- | -------------------------------------------------------------------------- |
| `Tensor` | A PyTorch tensor of expectation values, which can still be differentiated. |

#### `statevector(weights, inputs, initial_state=None)`

Return the full complex statevector, computed with `qml.state()`.

Qubit `0` is the most significant bit. `initial_state` is added at the start of the circuit as a `qml.StatePrep`, so the evolution starts from that state instead of `|0…0⟩`. A 2-D `inputs` batch returns an array of shape `(B, 2**n_qubits)`. The batch is evaluated in a loop, because reading the state does not use the fast path of parameter broadcasting. This method needs exact mode, because `qml.state` is not defined with a finite number of shots.

Parameters:

| Name            | Type      | Description                                                               | Default                             |
| --------------- | --------- | ------------------------------------------------------------------------- | ----------------------------------- |
| `weights`       | `ndarray` | The parameter vector (1-D).                                               | *required*                          |
| `inputs`        | `ndarray` | The input features: 1-D for one sample, or 2-D with a leading batch axis. | *required*                          |
| `initial_state` | \`ndarray | None\`                                                                    | The state to start from, instead of |

Returns:

| Type      | Description                                                                    |
| --------- | ------------------------------------------------------------------------------ |
| `ndarray` | The statevector, of shape (2\*\*n_qubits,), or (B, 2\*\*n_qubits) for a batch. |

Raises:

| Type                  | Description                                         |
| --------------------- | --------------------------------------------------- |
| `NotImplementedError` | If the configuration sets a finite number of shots. |

#### `zero_state_probability(weights, inputs)`

Return the probability of measuring `|0…0⟩` after the circuit has run.

On first use, it builds a separate qnode that returns `qml.probs(...)`, and the method returns entry `0` of that distribution. The qnode uses the same device as the expectation-value qnodes.

Parameters:

| Name      | Type      | Description                             | Default    |
| --------- | --------- | --------------------------------------- | ---------- |
| `weights` | `ndarray` | The parameter vector (1-D).             | *required* |
| `inputs`  | `ndarray` | The input features of one sample (1-D). | *required* |

Returns:

| Type    | Description                         |
| ------- | ----------------------------------- |
| `float` | The probability, a float in [0, 1]. |

### `PennyLaneConfig`

Device settings, used for every `qml.device()` call.

Pass it directly to `Circuit(provider=PennyLaneConfig(...))`, or pass it to `register()` under a name to reuse it.

Parameters:

| Name          | Type  | Description                                                                                                                                                                             | Default                                                                                                                           |
| ------------- | ----- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
| `device_name` | `str` | The identifier of the PennyLane simulator plugin: "default.qubit", "lightning.qubit", "lightning.gpu", or any other installed device. Default: "default.qubit".                         | `'default.qubit'`                                                                                                                 |
| `shots`       | \`int | None\`                                                                                                                                                                                  | None computes exact expectation values, and a positive integer samples that many shots for each expectation value. Default: None. |
| `diff_method` | `str` | The PennyLane differentiation method. With "best", PennyLane chooses for each device, usually backpropagation on a simulator and the parameter-shift rule on hardware. Default: "best". | `'best'`                                                                                                                          |

#### `make_backend()`

Build the backend that this configuration describes.

The provider layer calls it when a configuration is passed as `provider=`. There is no reason to call it directly.

Returns:

| Type                 | Description                                  |
| -------------------- | -------------------------------------------- |
| `'PennyLaneBackend'` | The PennyLaneBackend for this configuration. |
