## `eigenkit.photonics.backends.perceval_remote`

The Quandela Cloud executor, which runs photonic circuits on remote hardware or cloud simulators.

It sends circuits to a Quandela Cloud platform through Perceval's `RemoteProcessor`. The platform is a real photonic quantum processing unit (QPU), such as `"qpu:ascella"`, or a cloud simulator (`"sim:..."`). The results are *sampled* (counts of shots), so `run` returns an **estimated** Fock distribution, and gradients are not available, as with the qubit QPU backends. Perceval 1.3 keeps `RemoteProcessor` and `RemoteJob` in `perceval.runtime.legacy` (they are still available as `pcvl.*`). Moving to Perceval's newer interface is planned. The executor follows the conventions of the library for QPUs:

- It issues a `CostWarning` at the first submission, because the backend may charge for use.
- `submit(...)` returns a `RemoteJobHandle` without waiting for the job, and the handle has `.result()` and `.status()`.
- `dry_run_info()` describes the prepared job **offline**, before anything is paid for.

The access token comes from the `token` argument, then from `$QUANDELA_TOKEN` or `$PERCEVAL_TOKEN`, then from the token that Perceval itself would use (`pcvl.RemoteConfig`, which reads its environment variable, then its saved configuration). It is never written in the code and never logged. The `RemoteProcessor`, which connects to the cloud, is built at the first `run` or `submit` and reused after that, so `compile()` and `dry_run_info()` need neither a network connection nor a token.

### `QuandelaCloudBackend`

An executor that runs on a Quandela Cloud platform.

The platform is a real quantum processing unit (QPU) or a cloud simulator.

Parameters:

| Name                   | Type    | Description                                                                                                                             | Default                                                                                                                                            |
| ---------------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| `platform`             | `str`   | The name of a Quandela Cloud platform, for example "qpu:ascella" for hardware or "sim:slos" for a cloud simulator. Default: "sim:slos". | `'sim:slos'`                                                                                                                                       |
| `token`                | \`str   | None\`                                                                                                                                  | The access token. Without it, the token comes from $QUANDELA_TOKEN or $PERCEVAL_TOKEN, then from the token that Perceval has saved. Default: None. |
| `shots`                | `int`   | The number of shots sampled in each run. Default: 10000.                                                                                | `10000`                                                                                                                                            |
| `url`                  | \`str   | None\`                                                                                                                                  | The web address of the Quandela Cloud service, if it is not Perceval's default. Default: None.                                                     |
| `min_detected_photons` | \`int   | None\`                                                                                                                                  | Keep only the shots that detect at least this many photons (post-selection). The default is the number of photons in the input state.              |
| `poll_timeout_seconds` | `float` | How long, in seconds, run and result() wait for a job before they raise TimeoutError. Default: 3600.                                    | `3600`                                                                                                                                             |

#### `compile(spec)`

Tie `spec` to this platform, offline, with no network connection or token.

Parameters:

| Name   | Type                  | Description             | Default    |
| ------ | --------------------- | ----------------------- | ---------- |
| `spec` | `PhotonicCircuitSpec` | The circuit to compile. | *required* |

Returns:

| Type             | Description                         |
| ---------------- | ----------------------------------- |
| `RemoteCompiled` | The RemoteCompiled handle for spec. |

### `RemoteCompiled`

A compiled handle tied to a Quandela Cloud platform.

It implements the `Compiled` protocol (`run`, `expectation` and `jacobian`), and adds `submit` and `dry_run_info`. `QuandelaCloudBackend.compile` builds it. Nothing connects to the cloud until the first `run` or `submit`.

Parameters:

| Name      | Type                   | Description                                                             | Default    |
| --------- | ---------------------- | ----------------------------------------------------------------------- | ---------- |
| `spec`    | `PhotonicCircuitSpec`  | The circuit that this handle submits.                                   | *required* |
| `backend` | `QuandelaCloudBackend` | The platform, the credentials and the sampling settings to submit with. | *required* |

#### `dry_run_info()`

Describe the job that *would* be submitted, offline and without cost.

Returns:

| Type   | Description                                                                                               |
| ------ | --------------------------------------------------------------------------------------------------------- |
| `dict` | A dictionary with the keys "platform", "n_modes", "n_photons", "input_state", "n_components" and "shots". |

#### `expectation(weights, inputs, observable)`

Estimate an observable from one sampled run, and wait for the result.

The observable is diagonal in the Fock basis.

Parameters:

| Name         | Type                 | Description                          | Default    |
| ------------ | -------------------- | ------------------------------------ | ---------- |
| `weights`    | `ndarray`            | The 1-D weight vector.               | *required* |
| `inputs`     | `ndarray`            | The 1-D input vector.                | *required* |
| `observable` | `PhotonicObservable` | A PhotonNumber or a FockProbability. | *required* |

Raises:

| Type         | Description                                                                                                           |
| ------------ | --------------------------------------------------------------------------------------------------------------------- |
| `ValueError` | If observable can never match an output that is kept by post-selection. This is checked before anything is submitted. |

#### `jacobian(weights, inputs, observable, *, method='parameter_shift', need_weight_grad=True, need_input_grad=True)`

Always raise, because gradients are not available on sampled hardware.

Parameters:

| Name               | Type                 | Description                          | Default             |
| ------------------ | -------------------- | ------------------------------------ | ------------------- |
| `weights`          | `ndarray`            | The 1-D weight vector.               | *required*          |
| `inputs`           | `ndarray`            | The 1-D input vector.                | *required*          |
| `observable`       | `PhotonicObservable` | A PhotonNumber or a FockProbability. | *required*          |
| `method`           | `str`                | This argument is ignored.            | `'parameter_shift'` |
| `need_weight_grad` | `bool`               | This argument is ignored.            | `True`              |
| `need_input_grad`  | `bool`               | This argument is ignored.            | `True`              |

Raises:

| Type                  | Description                                                 |
| --------------------- | ----------------------------------------------------------- |
| `NotImplementedError` | Always. Use the local PercevalBackend to compute gradients. |

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

Sample the circuit on the cloud, and return the estimated Fock distribution.

The call waits for the job to finish. The distribution is a dictionary `{photon_counts: probability}`.

Parameters:

| Name      | Type      | Description            | Default    |
| --------- | --------- | ---------------------- | ---------- |
| `weights` | `ndarray` | The 1-D weight vector. | *required* |
| `inputs`  | `ndarray` | The 1-D input vector.  | *required* |

Raises:

| Type           | Description                                                                                                                                                                                                 |
| -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `TimeoutError` | If the job has not finished within poll_timeout_seconds. The job is cancelled first, because the caller never gets its handle. The same happens on Ctrl-C. Use submit to keep a job running after the wait. |
| `RuntimeError` | If the job fails, or if post-selection discards every shot.                                                                                                                                                 |

#### `submit(weights, inputs)`

Submit the job and return a handle at once, without waiting for the job.

Parameters:

| Name      | Type      | Description            | Default    |
| --------- | --------- | ---------------------- | ---------- |
| `weights` | `ndarray` | The 1-D weight vector. | *required* |
| `inputs`  | `ndarray` | The 1-D input vector.  | *required* |

Returns:

| Type              | Description                                                   |
| ----------------- | ------------------------------------------------------------- |
| `RemoteJobHandle` | The RemoteJobHandle of the job, with .result() and .status(). |

### `RemoteJobHandle`

A handle to a Quandela Cloud job that is running.

It is the linear-optics counterpart of the qubit `JobHandle`. `RemoteCompiled.submit` returns it, and it is not meant to be built directly.

Parameters:

| Name                   | Type        | Description                                                                       | Default    |
| ---------------------- | ----------- | --------------------------------------------------------------------------------- | ---------- |
| `job`                  | `RemoteJob` | The Perceval job that was submitted.                                              | *required* |
| `n_modes`              | `int`       | The number of modes of the circuit, used to read the sampled Fock states.         | *required* |
| `poll_interval`        | `float`     | The time, in seconds, between two checks of the status in result(). Default: 2.0. | `2.0`      |
| `poll_timeout_seconds` | `float`     | How long, in seconds, result() waits before it stops. Default: 3600.              | `3600`     |

#### `cancel()`

Ask Quandela Cloud to cancel the job, without waiting.

#### `result()`

Wait until the job finishes, then return the estimated Fock distribution.

Raises:

| Type           | Description                                                                                                                                                                               |
| -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `TimeoutError` | If the job has not finished after poll_timeout_seconds. The job keeps running. Call result() again or cancel(), or find the job in the Quandela console with the id given in the message. |
| `RuntimeError` | If the job ended in error or was cancelled, or if post-selection discarded every shot.                                                                                                    |

#### `status()`

Return the state of the job as Perceval reports it, without waiting.

For example, `"RUNNING"` or `"SUCCESS"`.
