## `eigenkit.core.backends.braket_backend`

The Amazon Braket backend.

The `amazon-braket-sdk` package is an optional dependency (`pip install eigenkit[braket]`). It is imported only when it is needed, so the rest of the library works without it. If it is missing, `compile()` raises an `ImportError` with a clear message.

`BraketConfig` selects one of three modes:

- The local simulator, the default, with `device_arn=None`. The circuit runs on `LocalSimulator`, in the same process. Exact values (`shots=0`) are supported. Gradients use the general parameter-shift method.
- A cloud simulator, when `device_arn` names SV1, DM1 or TN1. Amazon Web Services bills each task, and a single `CostWarning` is issued when the circuit is compiled.
- A quantum processing unit (QPU), when `device_arn` contains `/qpu/` (Rigetti, IonQ, IQM, AQT and others). `shots > 0` is required. Gradients are turned off (`gradient_method="none"`), and calling `.backward()` raises an error whose message recommends training on a simulator and then using the weights on the device. `enable_gradients=True` turns on parameter-shift gradients on the QPU, and compiling then issues a `CostWarning` that gives the number of tasks of one backward pass.

Both cloud modes can submit without waiting, through `submit()`, which returns a `BraketJobHandle`. Its `result()` waits for the task, and its `status()` checks the task without waiting. `submit()` can run `run`, `sample`, `probabilities` or `zero_state_probability` (see `BraketCompiled.submit_methods`), and it can add Amazon Web Services (AWS) tags to the task. `attach()` rebuilds a handle from the Amazon Resource Name (ARN) of a task submitted earlier, for example after the process that submitted it restarted.

`sample` reads the measurement counts of the task, so it needs `shots > 0`. `probabilities` and `zero_state_probability` read the first result type that the device publishes for the configured shots, in the order `Probability`, `StateVector`, `Amplitude`, `DensityMatrix` (`zero_state_probability` skips `StateVector`). With `shots > 0`, that is always `Probability`. At `shots=0`, the managed simulators publish no `Probability`, so SV1 gives the exact answer through `Amplitude`, and DM1 through `DensityMatrix`. The local simulator uses `Probability` at any number of shots. Three of these paths are limited to the number of qubits that Braket accepts, and wider circuits are refused before any task is created. On a cloud device, `Probability` covers at most 15 qubits ([List of result types](https://docs.aws.amazon.com/braket/latest/developerguide/braket-result-types.html)), so both methods stop there with `shots > 0`. On the local simulator, only the memory limits `Probability`. At `shots=0`, `probabilities` on SV1 lists every basis state in the program, so it stops at 17 qubits (because of the program limit of 5 megabytes), and DM1 returns a density matrix of at most 8 qubits. `sample` has no limit.

`compile()` builds a parametric `braket.circuits.Circuit`, whose gates use `FreeParameter` symbols, and adds one expectation result type for each distinct Pauli term of all the observables. With `shots=0`, one circuit measures every term. With `shots > 0`, one task can measure only terms that commute qubit-wise, so the terms are split into such groups, with one circuit, and one task per call, for each group. Each `run` or `submit` sets the parameter values through the `inputs={...}` dictionary of the Braket package.

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="braket")

# Or with the configuration, when a device setting matters.
from eigenkit.configs import BraketConfig

circuit = Circuit(
    blocks=blocks,
    observables=all_z(4),
    provider=BraketConfig(
        device_arn="arn:aws:braket:::device/quantum-simulator/amazon/sv1"
    ),
)
```

Without a `device_arn`, this is the local simulator. With one, the circuit is submitted to AWS. The ARN of a QPU turns gradients off (unless `enable_gradients=True`) and requires shots.

### `BraketBackend`

Provider for Amazon Braket, registered as `"braket"`.

It runs on the local simulator by default, or on a cloud simulator or a quantum processing unit (QPU) once `device_arn` is set in the configuration.

This class is internal: you select the backend with `provider="braket"` or by passing a BraketConfig, not by creating this class.

#### `capabilities`

What this backend supports.

It supports every gate except `AmplitudeEncoding`, `CPHASE` and `ControlledQubitUnitary`, which this backend does not translate into Braket circuits. It can sample with a finite number of shots. It has no native batching, so `run()` loops over the samples itself. It has no limit on the number of qubits. See Capabilities.

#### `gradient_method`

How this backend computes gradients.

It is `"parameter_shift"` on a simulator. On a quantum processing unit (QPU), it is `"none"`, unless the configuration sets `enable_gradients`. A parameter-shift backward pass costs at least two paid tasks per weight on real hardware, so gradients are refused there unless you turn them on.

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

Build the backend from a device configuration.

Parameters:

| Name     | Type           | Description                                  | Default    |
| -------- | -------------- | -------------------------------------------- | ---------- |
| `config` | `BraketConfig` | The device settings.                         | `None`     |
| `name`   | `str`          | The registry name, which error messages use. | `'braket'` |

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

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

It is called once for each `Circuit`, and the handle is reused for every call, so the expensive work happens here.

Parameters:

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

Returns:

| Type             | Description              |
| ---------------- | ------------------------ |
| `BraketCompiled` | A BraketCompiled handle. |

### `BraketCompiled`

Bases: `CompiledCircuit`

A circuit prepared for Amazon Braket.

This class is internal: you get it from BraketBackend.compile(), and you never create it directly. `run()` evaluates the observables and waits for the result. `submit()` returns a BraketJobHandle for a cloud or quantum processing unit (QPU) task, without waiting. `attach()` rebuilds that handle from the Amazon Resource Name (ARN) of a task. `dry_run_info()` reports how many circuits a call runs, without submitting anything.

#### `device`

The local simulator or the `AwsDevice`, created when first read.

Authentication with Amazon Web Services (AWS) and network calls happen only when this property is first read.

#### `submit_methods = _SUBMIT_METHODS`

The methods `submit()` and `attach()` accept: `"run"`, `"sample"`, `"probabilities"` and `"zero_state_probability"`. Any method added to this adapter's async path must also be added here, or callers that check this set will refuse it.

#### `__init__(circuit, observables, config, *, warn=True)`

Prepare the circuit for repeated evaluation.

`BraketBackend.compile()` builds it, and there is no reason to create one directly.

Parameters:

| Name          | Type                   | Description                                                                                                                                | Default    |
| ------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | ---------- |
| `circuit`     | `CircuitSpec`          | The circuit to prepare.                                                                                                                    | *required* |
| `observables` | `Sequence[Observable]` | The observables that this handle measures.                                                                                                 | *required* |
| `config`      | `BraketConfig`         | The device settings.                                                                                                                       | *required* |
| `warn`        | `bool`                 | Whether to issue the cost warnings for a cloud device. It is False only for the shifted copies that compile_shifted builds. Default: True. | `True`     |

#### `attach(task_id, *, method='run')`

Rebuild a job handle for a task submitted earlier from this circuit.

Use it to pick up a task after the process that submitted it has stopped, for example after a crash. The handle decodes the results exactly as the handle returned by `submit()` would have, provided that the task was submitted from the same circuit, observables and `method`.

Parameters:

| Name      | Type  | Description                                                                                          | Default    |
| --------- | ----- | ---------------------------------------------------------------------------------------------------- | ---------- |
| `task_id` | `str` | The Amazon Resource Name (ARN) of the Braket quantum task, as returned by BraketJobHandle.task_id(). | *required* |
| `method`  | `str` | The method that the task was submitted with, one of submit_methods.                                  | `'run'`    |

Returns:

| Name     | Type        | Description                     |
| -------- | ----------- | ------------------------------- |
| `handle` | `JobHandle` | A handle for the existing task. |

Raises:

| Type         | Description                                                                                                                                                                                                                                                                          |
| ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `ValueError` | If the configuration is not a cloud configuration, if task_id is not the ARN of a Braket quantum task, if method is not in submit_methods, or if method is "run" and the observables need more than one task (see submit()). The Braket package is not called in any of these cases. |

#### `compile_shifted(circuit)`

Compile a copy of this circuit with one gate angle shifted.

The chain-rule gradient compiles one copy for each shifted gate. The compilation of this handle already issued the cost warnings, so the copy issues none.

Parameters:

| Name      | Type          | Description          | Default    |
| --------- | ------------- | -------------------- | ---------- |
| `circuit` | `CircuitSpec` | The shifted circuit. | *required* |

Returns:

| Type               | Description                          |
| ------------------ | ------------------------------------ |
| `'BraketCompiled'` | A BraketCompiled handle for circuit. |

#### `dry_run_info()`

Report the size of the circuit without submitting anything.

Use it to check a cloud submission before you submit it. It reports how many circuits a call runs. It does not estimate a price.

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

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

With `shots > 0`, it is estimated from the shot frequencies, through the `Probability` result type. With `shots=0`, it is the exact distribution: `Probability` on the local simulator, the squared amplitudes of every basis state on SV1 (`Amplitude`), and the diagonal of the density matrix on DM1 (`DensityMatrix`). It is limited to `_sampling.MAX_DENSE_QUBITS` qubits, and every cloud path has a lower limit. The limit is 15 qubits with `shots > 0`, the widest `Probability` that the managed simulators and the quantum processing units (QPUs) of Braket return ([List of result types](https://docs.aws.amazon.com/braket/latest/developerguide/braket-result-types.html)). It is 17 qubits on SV1 at `shots=0`, because the program lists all `2**n_qubits` states and must stay under the Braket limit of 5 megabytes. It is 8 qubits on DM1 at `shots=0`, the widest density matrix that DM1 returns. `sample()` has no limit on the number of qubits.

Parameters:

| Name      | Type      | Description                     | Default    |
| --------- | --------- | ------------------------------- | ---------- |
| `weights` | `ndarray` | A 1-D parameter vector.         | *required* |
| `inputs`  | `ndarray` | A 1-D vector of input features. | *required* |

Returns:

| Name            | Type      | Description                                                                                                             |
| --------------- | --------- | ----------------------------------------------------------------------------------------------------------------------- |
| `probabilities` | `ndarray` | A float64 array of shape (2\*\*n_qubits,), ordered with qubit 0 as the most significant bit, whose entries add up to 1. |

Raises:

| Type         | Description                                                                                                                                                                                              |
| ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ValueError` | If the device publishes none of the result types above for the configured shots, if the circuit has more qubits than the limit of the path taken, or if weights or inputs is not 1-D. Nothing runs then. |

#### `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. With `shots > 0`, each sample runs one task for each group of terms that commute qubit-wise (see `dry_run_info()["n_circuit_runs_per_call"]`).

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 rule as weights.         | *required* |

Returns:

| Type      | Description                                                              |
| --------- | ------------------------------------------------------------------------ |
| `ndarray` | One expectation value for each observable (for each sample, in a batch). |

#### `sample(weights, inputs)`

Measure one sample in the computational basis and return the histogram.

It runs one task with the configured shots and no result type, and reads the measurement counts of the task. A qubit that no gate acts on is reported as `0`.

Parameters:

| Name      | Type      | Description                     | Default    |
| --------- | --------- | ------------------------------- | ---------- |
| `weights` | `ndarray` | A 1-D parameter vector.         | *required* |
| `inputs`  | `ndarray` | A 1-D vector of input features. | *required* |

Returns:

| Name     | Type             | Description                                                                                                                                  |
| -------- | ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| `counts` | `dict[str, int]` | The number of shots for each measured bitstring, with qubit 0 as the first character of each key. The counts add up to the configured shots. |

Raises:

| Type         | Description                                                                                                        |
| ------------ | ------------------------------------------------------------------------------------------------------------------ |
| `ValueError` | If the configuration has shots=0 (a histogram needs shots), or if weights or inputs is not 1-D. Nothing runs then. |

#### `submit(weights, inputs, *, method='run', tags=None)`

Submit the circuit without waiting, and return a `BraketJobHandle`.

The handle holds the Braket `QuantumTask`.

Parameters:

| Name      | Type                | Description                                                                                                                                                                                                                                                                                                                                            | Default                                                                                                                                                                                                                                                                                                                                     |
| --------- | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `weights` | `ndarray`           | The parameter vector of one sample, 1-D.                                                                                                                                                                                                                                                                                                               | *required*                                                                                                                                                                                                                                                                                                                                  |
| `inputs`  | `ndarray`           | The input features of one sample, 1-D.                                                                                                                                                                                                                                                                                                                 | *required*                                                                                                                                                                                                                                                                                                                                  |
| `method`  | `str`               | What the task computes, one of submit_methods. "run" (the default) measures the observables, like run(). "sample" returns the measurement histogram, like sample(). "probabilities" returns the distribution over every basis state, like probabilities(). "zero_state_probability" measures the all-zeros probability, like zero_state_probability(). | `'run'`                                                                                                                                                                                                                                                                                                                                     |
| `tags`    | \`Mapping[str, str] | None\`                                                                                                                                                                                                                                                                                                                                                 | Amazon Web Services (AWS) tags to put on the quantum task, to find it again later. There can be at most 50 tags. Keys have 1 to 128 characters and values at most 256 characters, made of letters, digits, spaces and _ . : / = + - @. A key cannot start with aws:. Only cloud tasks are tagged, and the local simulator ignores the tags. |

Returns:

| Name     | Type        | Description                     |
| -------- | ----------- | ------------------------------- |
| `handle` | `JobHandle` | A handle for the submitted job. |

Raises:

| Type         | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ValueError` | If method is not in submit_methods, if weights or inputs is not 1-D, if tags breaks the AWS tag rules, if method is "sample" with shots=0, if the device publishes no result type that method can read at the configured shots, or if the result type it reads is limited to fewer qubits than the circuit has. The limits are 15 qubits for Probability on a cloud device (List of result types), which "probabilities" and "zero_state_probability" read with shots > 0, and the exact limits listed under probabilities(). It is also raised if method is "run" and the observables need more than one task, because a handle tracks one task. This happens with shots > 0 when the terms do not all commute qubit-wise. Use run(), or split the observables into sets that commute. Nothing is submitted in any of these cases. |

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

Return the probability of measuring all zeros at the end of the circuit.

It uses the `Probability` result type of Braket, which every Braket simulator and quantum processing unit (QPU) supports with `shots > 0`, and which the local simulator supports at any number of shots. The managed simulators and the QPUs return it for at most 15 qubits ([List of result types](https://docs.aws.amazon.com/braket/latest/developerguide/braket-result-types.html)), so a wider circuit with `shots > 0` on a cloud device is refused. In that case, use `sample()`, which has no limit on the number of qubits, and divide the all-zeros count by the shots. The local simulator has no limit. With `shots=0` on a cloud simulator, which publishes no `Probability` at `shots=0`, the value is exact instead: the squared all-zeros amplitude on SV1 (`Amplitude`), for any number of qubits, and the `[0, 0]` entry of the density matrix on DM1 (`DensityMatrix`), which DM1 returns for at most 8 qubits.

Parameters:

| Name      | Type      | Description                     | Default    |
| --------- | --------- | ------------------------------- | ---------- |
| `weights` | `ndarray` | A 1-D parameter vector.         | *required* |
| `inputs`  | `ndarray` | A 1-D vector of input features. | *required* |

Returns:

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

Raises:

| Type         | Description                                                                                                                                                                                                    |
| ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ValueError` | If the device publishes none of those result types for the configured shots, or if the circuit has more than 15 qubits on the cloud Probability path or more than 8 qubits on the DM1 path. Nothing runs then. |

### `BraketConfig`

Device configuration for the Braket backend.

Parameters:

| Name                   | Type   | Description                                                                                                                                                                                                                                                                                                                                   | Default                                                                                                                                                                                                                                                                                                                    |
| ---------------------- | ------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `device_arn`           | \`str  | None\`                                                                                                                                                                                                                                                                                                                                        | None (the default) for the local simulator, or the Amazon Resource Name (ARN) of an Amazon Web Services (AWS) device. An example for a cloud simulator is arn:aws:braket:::device/quantum-simulator/amazon/sv1. Quantum processing units (QPUs) have ARNs of the form arn:aws:braket:<region>::device/qpu/<vendor>/<name>. |
| `local_backend`        | `str`  | The LocalSimulator backend to use when device_arn is None. Common values are "braket_sv" (state vector) and "braket_dm" (density matrix).                                                                                                                                                                                                     | `'braket_sv'`                                                                                                                                                                                                                                                                                                              |
| `shots`                | `int`  | 0 means exact values (simulators only), and a positive integer turns on sampling with that number of shots. AWS QPUs require shots > 0.                                                                                                                                                                                                       | `0`                                                                                                                                                                                                                                                                                                                        |
| `s3_bucket`            | \`str  | None\`                                                                                                                                                                                                                                                                                                                                        | The Amazon Simple Storage Service (S3) bucket for the results of cloud tasks. With None, the Braket package picks the default of the account.                                                                                                                                                                              |
| `s3_prefix`            | `str`  | The key prefix inside s3_bucket.                                                                                                                                                                                                                                                                                                              | `'braket-results'`                                                                                                                                                                                                                                                                                                         |
| `poll_timeout_seconds` | `int`  | How many seconds result() waits for a submitted task.                                                                                                                                                                                                                                                                                         | `3600`                                                                                                                                                                                                                                                                                                                     |
| `enable_gradients`     | `bool` | Whether to allow parameter-shift gradients on a QPU, for PyTorch .backward(), jacobian() and gradient-based optimisers. It is off by default, because one backward pass costs at least 2 * n_weights paid tasks. When it is set, compiling for a QPU issues a CostWarning that gives this count. Simulators compute gradients in either case. | `False`                                                                                                                                                                                                                                                                                                                    |

#### `is_cloud`

Whether `device_arn` is set.

If it is, the circuit runs on Amazon Web Services (AWS), on a cloud simulator or a quantum processing unit (QPU), instead of on the local simulator.

#### `is_qpu`

Whether `device_arn` names real hardware (it contains `/qpu/`).

#### `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                               |
| ----------------- | ----------------------------------------- |
| `'BraketBackend'` | The BraketBackend for this configuration. |

### `BraketJobHandle`

A handle to a submitted Braket task.

It holds the Braket `QuantumTask` and the plan that turns the raw expectation values of the task into one value for each `Observable`. `compiled.submit(weights, inputs)` and `compiled.attach(task_id)` return one of these handles. Call `result()` to wait until the task finishes, or `status()` to check the task without waiting.

#### `method`

The method of the compiled circuit that this task runs.

It is `"run"`, `"sample"`, `"probabilities"` or `"zero_state_probability"`.

#### `__init__(task, aggregation_plan, poll_timeout_seconds, method='run', *, n_qubits=None)`

Wrap a submitted Braket task in a job handle.

`BraketCompiled.submit()` or `BraketCompiled.attach()` builds it. It is not meant to be created directly.

Parameters:

| Name                   | Type                            | Description                                                                                                                                          | Default                                                                                                                                                                                                                                                                |
| ---------------------- | ------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `task`                 | `QuantumTask`                   | The Braket quantum task returned by the Braket package.                                                                                              | *required*                                                                                                                                                                                                                                                             |
| `aggregation_plan`     | `list[list[tuple[float, int]]]` | How the raw result values map to the requested observables. For each observable, it lists the (coefficient, result index) pairs to add up.           | *required*                                                                                                                                                                                                                                                             |
| `poll_timeout_seconds` | `int`                           | How many seconds result() waits before giving up.                                                                                                    | *required*                                                                                                                                                                                                                                                             |
| `method`               | `str`                           | The method of the compiled circuit that the task runs, one of BraketCompiled.submit_methods. It decides how result() decodes the values of the task. | `'run'`                                                                                                                                                                                                                                                                |
| `n_qubits`             | \`int                           | None\`                                                                                                                                               | The number of qubits of the circuit. When it is given, result() checks that a "probabilities" or "zero_state_probability" task returned a distribution over 2\*\*n_qubits states, and it writes the keys of a "sample" histogram as bitstrings of n_qubits characters. |

Raises:

| Type         | Description                                        |
| ------------ | -------------------------------------------------- |
| `ValueError` | If method is not in BraketCompiled.submit_methods. |

#### `cancel()`

Ask Braket to cancel the quantum task. It returns without waiting.

#### `result()`

Wait until the task finishes, then decode its values.

It waits for at most `poll_timeout_seconds`. The decoding follows the `method` of this handle and the result type that the result of the task actually holds, so a handle from `attach()` decodes a task exactly as the handle returned by `submit()` would have.

Returns:

| Type      | Description      |
| --------- | ---------------- |
| \`ndarray | dict[str, int]\` |

Raises:

| Type           | Description                                                                                                                                                                |
| -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `RuntimeError` | If the task has no result, because it ended FAILED or CANCELLED, or because the wait timed out. The message names the task and its state.                                  |
| `ValueError`   | If the values of the task do not have the shape that the method and the circuit of this handle produce, for example because the handle was attached with the wrong method. |

#### `status()`

Return the state of the task, without waiting.

The state is a string such as `"CREATED"`, `"QUEUED"`, `"RUNNING"`, `"COMPLETED"`, `"FAILED"` or `"CANCELLED"`.

#### `task_id()`

Return the identifier of the Braket quantum task, to look it up in the Amazon Web Services (AWS) console.
