# PennyLane 使用示范

使用 PennyLane 编写线路和测量，使用 QuantumFerry 执行实验。插件注册了 quantum_source.device：你可以保留 qml.Hadamard、qml.CNOT、qml.expval 等熟悉的写法，由插件完成格式转换、平台任务和测量结果还原。

## 插件如何工作

| 阶段 | 插件负责的工作 | 你使用的对象 |
| --- | --- | --- |
| 编写线路 | 接收 QNode 构造出的 QuantumScript，保留测量定义和 shots。 | QNode / tape |
| 准备实验 | 预处理测量与参数，生成逻辑 OpenQASM 2，准备实际任务及其报价。 | dev.prepare(tape) |
| 执行实验 | 批准已准备的任务，通过 Core SDK 提交并跟踪平台状态。 | prepared.confirm(...) |
| 返回结果 | 取得平台计数，按原来的测量定义恢复 counts、probs、expval 等结果。 | prepared.result(...) |

本页用 2 比特 Bell 线路完成一次有限 shots 实验。第 1–6 步组成单任务流程，插件负责把 PennyLane 线路转换为平台程序。后续章节介绍独立设备、钱包选择和进阶测量，可按需要选用。

- [先查这台设备的 configuration_data 选项](capabilities-pennylane.html)
- [门、测量返回值、shots 展开与梯度示例](pennylane-measurements.html)

## 1. 建立设备连接

先安装 SDK，并设置 QUANTUM_SOURCE_API_URL、QUANTUM_SOURCE_BACKEND 和 QUANTUM_SOURCE_ORGANIZATION_ID。认证使用平台 SDK Key：设置 QUANTUM_SOURCE_SDK_KEY 为密钥 JSON 文件路径，或同时设置 QUANTUM_SOURCE_CLIENT_ID 与 QUANTUM_SOURCE_CLIENT_SECRET。下面的示例从环境读取这些配置。

安装 SDK 的 pennylane 扩展后，PennyLane 会自动识别 quantum_source.device；当前依赖范围为 PennyLane >=0.45.1,<0.46。本例显式传入一个共享 SchedulerClient，便于同时查询设备和管理提交。

```python
import os
import pennylane as qml
from quantum_source_sdk import SchedulerClient

scheduler = SchedulerClient(
    base_url=os.environ["QUANTUM_SOURCE_API_URL"],
    currency="OPEN",  # 设备继承此选择；使用 FULL 时在准备前改为 "FULL"
)
dev = qml.device(
    "quantum_source.device",
    wires=2,
    backend=os.environ["QUANTUM_SOURCE_BACKEND"],
    organization_id=os.environ["QUANTUM_SOURCE_ORGANIZATION_ID"],
    scheduler=scheduler,
    auto_approve_quote=False,
)
```

| 参数 | 本例取值 | 用途 |
| --- | --- | --- |
| 第一个参数 | quantum_source.device | 选择 QuantumFerry 插件。 |
| wires | 2 | 指定线路包含两个逻辑 wire。也可使用显式标签。 |
| backend | 环境中的设备标识 | 选择平台设备的 ID、short_code 或无歧义名称。 |
| organization_id | 环境中的组织 ID | 指定任务所属的组织。 |
| scheduler | 共享 SchedulerClient | 复用已有的平台连接与认证。 |
| currency | 本例省略，继承 scheduler.currency | 与注入客户端同时显式填写时必须一致；冲突在本地报错。 |
| auto_approve_quote | False | 直接执行 QNode 时要求先处理报价；本教程使用显式 prepare / confirm。 |

auto_approve_quote=False 适合先看报价、再执行的流程：先构造 tape，调用 dev.prepare(tape) 查看报价，再调用 prepared.confirm() 确认。本页采用这种方式。需要在程序中自动批准时，将 auto_approve_quote 设为 True，然后直接调用 QNode。

## 独立设备与共享客户端的钱包

共享客户端的设备继承 scheduler.currency。使用 FULL 时，在创建 scheduler 时设置 currency="FULL"，设备沿用该选择。dev.currency 可查看当前钱包。下面演示独立设备的写法：由设备创建客户端，并为新报价选择 FULL。

```python
dev = qml.device(
    "quantum_source.device",
    wires=2,
    backend=os.environ["QUANTUM_SOURCE_BACKEND"],
    organization_id=os.environ["QUANTUM_SOURCE_ORGANIZATION_ID"],
    base_url=os.environ["QUANTUM_SOURCE_API_URL"],
    currency="FULL",
    auto_approve_quote=False,
)
dev.currency
```

直接创建 QuantumSourceDevice(..., currency="FULL") 采用相同规则。独立设备默认使用 OPEN；钱包在创建设备时选定，随后生成的报价使用该币种。

## 2. 构造 Bell 电路

先建立 Bell 线路，再把 QNode 构造成可供准备的 tape。这一步只构造 Python 线路对象，不使用量子计算资源。

```python
@qml.set_shots(128)
@qml.qnode(dev, diff_method=None)
def bell():
    qml.Hadamard(0)
    qml.CNOT(wires=[0, 1])
    return qml.counts(wires=[0, 1])

tape = bell.construct((), {})
```

| 代码 | 含义 |
| --- | --- |
| @qml.set_shots(128) | 把 128 次采样写入这条线路；新版 PennyLane 推荐在线路上设置 shots。 |
| @qml.qnode(dev, diff_method=None) | 把线路绑定到 dev；本例只读计数，不计算梯度。 |
| qml.Hadamard(0) 与 qml.CNOT(...) | 创建两个逻辑比特的 Bell 纠缠线路。 |
| qml.counts(wires=[0, 1]) | 请求按这两个 wire 的顺序返回位串频数。 |
| bell.construct((), {}) | 构造无参数调用对应的 QuantumScript；不调用 bell() 执行任务。 |

## 3. 准备本次任务

将 tape 交给设备，取得 PreparedPennyLaneBatch，它保存本次线路经过预处理后生成的实验及报价。

```python
prepared = dev.prepare(tape)
```

dev.prepare 会展开当前线路、转换为 OpenQASM 2、上传并取得报价，此时尚未执行。本例通常是一条任务；参数广播或不同测量基可能产生多条任务，每条都有自己的 shots。

| prepared 的属性 | 返回内容 | 用途 |
| --- | --- | --- |
| quote | 整批报价摘要 | 查看任务数量、总 shots、金额和有效期。 |
| task_count | 实际准备的任务数 | 检查一个用户线路展开成多少次平台任务。 |
| total_shots | 展开后所有任务的 shots 总和 | 了解本次批次的总采样量。 |
| programs | OpenQASM 程序字符串组成的元组 | 需要核对插件转换结果时查看。 |

## 4. 查看报价

```python
prepared.quote
```

本例包含 1 条任务、总计 128 shots，费用由平台返回。若测量分成 3 条线路且每条 128 shots，总采样量为 384。梯度计算会生成额外线路，各次执行分别准备和计价。

当前平台使用 public + stand 单通道，按 dev.currency 选择 OPEN 或 FULL 报价。prepared 保存这一次线路的金额和币种。需要切换钱包时，使用新币种的客户端或设备重新准备报价。

## 5. 确认执行

核对报价后执行本段，批准并提交本次整批任务；这一步开始使用计算资源。

```python
submission = prepared.confirm(wait=False)
```

prepared.confirm(wait=False) 提交后返回原 PreparedPennyLaneBatch，下一步使用 prepared.result(...) 读取测量结果。也可以用 counts = prepared.confirm() 一次完成确认和等待，直接取得 PennyLane 测量结果。

## 6. 读取 PennyLane 结果

```python
counts = prepared.result(timeout=300)
counts
```

counts 是位串到整数频数的字典，本例所有频数之和为 128。prepared.result(timeout=300) 读取已确认任务的结果，并还原为第 2 步请求的 PennyLane 测量类型。

| 调用方式 | 返回结果 | 适用场景 |
| --- | --- | --- |
| dev.prepare(tape) | PreparedPennyLaneBatch | 查看这条 tape 实际展开的完整报价。 |
| prepared.confirm() | 原 tape 对应的测量值 / 元组 | 确认后等待结果，一步完成。 |
| prepared.confirm(wait=False) | 同一个 prepared 对象 | 确认后先返回，后续再调 result。 |
| prepared.result(timeout=300) | 原 tape 对应的测量值 / 元组 | 只恢复原批次；不补发未提交项。 |
| dev.prepare((tape_a, tape_b)) 后 confirm() | 按输入 tape 顺序排列的结果元组 | 多个具体实验一起报价，保持各自测量结构。 |

## 如何返回不同测量结果

修改 QNode 的 return 即可选择测量类型。有限 shots 下，插件根据观测量加入测量基变换，读取平台 counts，再由 PennyLane 计算相应的测量结果。调整线路后，重新准备本次实验的报价。

| 请求 | 当前行为 |
| --- | --- |
| counts | 有限整数 shots，返回平台计数通道；来源以设备和 Adapter 为准。 |
| probs | 有限 shots 时由平台 counts 得到频率向量；两 wire 顺序为 00、01、10、11。 |
| expval、var | 有限 shots，必要时变换测量基并分组，再由平台 counts 还原。 |
| sample | 从平台 counts 重建频数一致的统计样本，按位串频数组织。 |
| state、density_matrix | 当前插件支持的测量类型为 counts、probs、expval、var 和 sample。 |
| 多组 shots | 分别构造带整数 shots 的 tapes，再批量准备实验。 |

支持原生 probabilities 的设备可以用 shots=None 请求单个 qml.probs，并通过 provider_probability_shots 设置执行预算。常规采样实验使用 qml.set_shots 指定采样次数。

- [查看测量类型、非对易分组、参数广播与梯度的独立示例](pennylane-measurements.html)

## 设备选项：查询、配置和使用

通用选项在 qml.device(...) 中设置，例如组织、等待时间和是否自动批准。设备特有选项则由平台 schema 描述，放在 configuration_data 的同一层。以本源逻辑线路为例，在设备声明了对应字段时，可以在第 1 步加入 configuration_data={"mapping": True, "optimization": False}。

| 想了解或设置什么 | 对应入口 | 返回值 / 放置位置 |
| --- | --- | --- |
| 平台设备能做什么 | dev.provider_capabilities | 统一的设备能力对象，包括设备提供的门、拓扑等信息。 |
| 厂商参数有哪些、允许填什么 | scheduler.get_backend_class(code).configuration_schema | 包含 properties 等内容的配置 schema 字典。 |
| PennyLane 插件认识哪些操作 | dev.capabilities | 插件可处理的操作与测量；设备门集在 provider_capabilities 中查看。 |
| 为本次设备设置 mapping 等参数 | qml.device(..., configuration_data={...}) | 先选择设备，再按其 schema 提供参数。 |

PennyLane 的设备参数在 qml.device(..., configuration_data={...}) 中设置；可选字段通过 scheduler.get_backend_class(code).configuration_schema 查询。dev.prepare(tape) 使用已建立设备的配置。

PennyLane 使用逻辑 wire 标签，例如 wires=[1, 11] 会序列化为逻辑 q[0]、q[1]，由设备映射到物理比特。固定真机位置的实验可使用 Qiskit 的 physical_qubits 或 initial_layout 布局入口。

- [PennyLane 专章：查询平台能力、读取 schema 并准备逻辑线路](capabilities-pennylane.html)

## qml.device 创建参数参考

| 创建参数 | 默认值 | 作用 |
| --- | --- | --- |
| wires | 必填 | 线路的逻辑 wire 数量或标签。 |
| backend | 必须为非空设备标识 | 设备 ID、short_code 或无歧义名称。 |
| shots | None | 兼容旧设备级 shots 默认；新 QNode 使用 @qml.set_shots(N)，最终执行以 tape.shots 为准。 |
| scheduler | None | 注入已有 SchedulerClient，复用其连接和认证。 |
| base_url / api_url | None | 自建客户端的平台根地址；两者同时提供必须一致。 |
| client_id / client_secret | None | 成对提供平台凭据，也可通过环境配置或共享 scheduler 读取。 |
| token | None | 使用平台令牌认证，与 Client ID / Secret 认证二选一。 |
| currency | None：独立设备用 OPEN，注入 scheduler 时继承 | 可显式选择 FULL；与注入客户端冲突时报错；dev.currency 只读。 |
| organization_id | None | 任务归属组织；省略时 Core 仅在恰好一个组织的情况下自动选择。 |
| provider | None | 消除设备标识歧义或补厂商前缀；完整 short_code 通常不需要。 |
| configuration_data | None，内部整理为字典 | 设置所选设备支持的平铺选项。 |
| job_subcategory_id | oth:oth | 实际平台任务子类别。 |
| auto_confirm | True | 兼容的自动批准开关；False 关闭自动批准。 |
| auto_approve_quote | None，按 True 参与计算 | 与 auto_confirm 同时判断；任一为 False 时使用显式报价确认流程。 |
| provider_probability_shots | None | 支持原生概率的设备在 shots=None 模式下使用的执行预算。 |
| job_timeout_seconds | 86400 秒 | 默认等待时长；prepared.result(timeout=...) 可设置当前等待的时限。 |
| job_name_prefix | pennylane-job | 生成平台任务名称时使用的前缀。 |
| verbose | False | 每条准备成功后打印一条 job_prepared 事件。 |
| execution_plan / queue_priority | public / stand | 当前平台使用的通道，保持默认即可。 |

通过 prepared.result(timeout=300) 设置本次等待时长。last_execution_metadata 提供最近一次批次的 circuit_count、shots_per_circuit 和 total_provider_shots；total_credits_used 累计当前设备实例已读取的完成任务费用。账号余额和完整账单在平台钱包中查看。

## 批量线路与迭代算法

多个 QuantumScript 使用 dev.prepare((tape_a, tape_b)) 批量准备。每个 tape 携带自己的 shots，prepared.quote 汇总展开后的实际任务，完成后按输入顺序还原结果。参数广播、测量分组和参数移位梯度会按计算需要生成额外线路。

自动运行优化循环时，将设备的 auto_approve_quote 设为 True，再按 PennyLane 的方式调用 QNode。每次执行都会准备报价并自动批准相应任务，使用平台余额和设备资源；训练费用由各次实际执行累积。

需要逐次审阅报价并取得完整 QNode 测量结果时，采用本页的 construct → dev.prepare → confirm 流程。自动求导或训练循环采用自动批准方式，使 PennyLane 能连续完成线路变换、执行和结果还原。

批次中部分任务已接受时，保留原 prepared，通过 job_ids 查看已建立任务，通过 unsubmitted_preparation_ids 查看尚待提交项。prepared.recover() 查询已尝试的提交；完整结果需等待本批所有必要任务成功。

## 超时与原任务恢复

等待超时后，再调用 prepared.result(timeout=300) 继续等待同一批实验。prepared.recover(wait=False) 查看当前提交进度；prepared.recover(wait=True, timeout=300) 等待这些任务结束。

```python
prepared.recover(wait=False)
prepared.job_ids
```

prepared.job_ids 保存已建立的任务 ID。任务 ID 尚未生成时，使用 prepared.attempted_preparation_ids 中的提交标识调用 scheduler.get_submission(identifier)。状态暂时无法确认时，稍后继续查询同一记录。

| 异常 | 用户应如何处理 |
| --- | --- |
| APIError（HTTP 402） | 所选钱包余额不足。补足余额后继续确认；也可切换客户端钱包并重新准备报价。批次中已接受的任务继续执行。 |
| PlatformSubmissionError | 平台暂时无法确认发送结果；保留原提交 ID，查询原记录。 |
| ProviderSubmissionError | 包含厂商返回的公开错误说明。结合说明和原提交 ID 查询本次提交的状态。 |
| PlatformStatusError | 当前状态无法确认，包含未识别的厂商状态；稍后查询同一个任务。 |
| PlatformResultError | 结果暂时无法读取；查询原任务并再次读取原结果。 |
| JobLimitReachedError | 组织当前任务名额已满；核对已有任务及本批已接受的部分。 |
| JobNotCancelableError | 当前任务已不满足取消条件；继续查询原记录。 |

QuantumSourceDeviceError 表示配置或设备契约问题，QuantumSourceCircuitError 表示线路问题，QuantumSourceResultError 表示收到的真实结果不符合测量契约。SDK 的提交、额度、超时和查询异常保留其原类型，便于沿原对象恢复。

## 取消、中断与费用

需要取消实验时，使用 dev.cancel_job(submission_id) 或 scheduler.cancel_job(submission_id)。平台尚未领取的排队项可以取消，成功返回 None；已进入发送阶段的任务继续通过查询读取进度。

Ctrl+C 或 Jupyter 中断以 KeyboardInterrupt 到达本次执行时，插件会尝试取消本次仍待发送的任务。通过 prepared.cancellation_report 的 cancelled_ids、skipped_ids 和 failures 查看处理结果。等待超时或关闭连接后，任务继续由平台管理。

当前每个组织最多同时保有 10 个尚未结束的任务，组织内成员、SDK Key 和设备共享这些名额。平台接受确认后占用名额，在确认完成、失败或取消后释放；排队、发送中和等待核实的任务持续占用。名额已满时，等待已有任务结束后再提交。每次实验同时按账号授权检查 shots 额度。

最终状态为 Failed 的任务按原币种退款；同一批次中的成功任务按各自报价结算。OPEN 退款沿用原有效期，有效期内的部分恢复为可用余额。具体费用与退款记录可在平台钱包和账单中查看。

## 完成后关闭连接

完成本次结果读取或必要的恢复后关闭设备；示例中的共享 scheduler 由调用方创建，因此还要单独关闭。

```python
dev.close()
scheduler.close()
```

## 下一步

- [查询 PennyLane 设备能力和设备专属参数](capabilities-pennylane.html)
- [PennyLane 操作、测量、shots 与梯度](pennylane-measurements.html)
- [Qiskit：需要固定物理布局时使用](qiskit.html)
- [连接与认证](connection.html)
- [结果说明](results.html)
- [下载 PennyLane Notebook](../../notebooks/sdk_pennylane_current.ipynb)

## 下载本页 Jupyter Notebook

收录本页 Python 示例与说明，便于在 Jupyter Notebook 中学习和运行；安装命令保留为说明。

[下载《PennyLane 入门与示例》Notebook（.ipynb）](../../notebooks/sdk_user_guide_pennylane.ipynb)
