# Qiskit Sampler 与 Estimator

Sampler 和 Estimator 都接收 Qiskit 线路：Sampler 返回测量分布，Estimator 返回观测量的期望值及估计标准误。本页从一个 Sampler 实验开始，再介绍 Estimator、参数数组和多观测量。

| 目标 | 选择 | 结果入口 |
| --- | --- | --- |
| 拿到测量结果，每个参数点都有自己的 counts | QuantumSourceSampler / SamplerV2 | result[pub_index].data.<经典寄存器名>.get_counts(...) |
| 估计 Pauli 观测量的期望值 | QuantumSourceEstimator / EstimatorV2 | result[pub_index].data.evs 和 data.stds |
| 只提交一条普通测量线路，直接取 Qiskit Result | QuantumSourceBackend | job.result().get_counts()；见 Qiskit 入门 |

每个实验按 prepare → 查看报价 → confirm → result 的顺序运行。prepare 准备报价，confirm 确认执行，result 读取结果；run 将准备和确认合并为一步。确认真机实验会使用设备资源，并按报价计费。

- [先完成安装与认证](connection.html)
- [普通线路从这里开始](qiskit.html)
- [先查询所选设备的选项](capabilities-qiskit.html)

## 1. 创建 Service 和所选设备

先配置平台 API 地址、平台 SDK 凭据和设备目录中的 short_code。以下实验共用同一个 service 与 backend。

```python
import os
from qiskit import QuantumCircuit
from quantum_source_qiskit import QuantumSourceService

service = QuantumSourceService(
    base_url=os.environ["QUANTUM_SOURCE_API_URL"],
    currency="OPEN",
)
backend = service.return_backend(os.environ["QUANTUM_SOURCE_BACKEND"])
print(backend.name)
print(backend.limits)
```

Service 负责认证、钱包和客户端连接，backend 代表当前设备。需要使用 FULL 时，在创建 Service 时选择 currency="FULL"。通过 limits 查看 shots 等设备限制。

## 2. Sampler：准备一个 Bell 采样实验

这里创建名为 c 的经典寄存器；最终结果也从 data.c 读取。Sampler 的输入必须包含经典测量寄存器，并应将需要采样的量子位测量到其中。

```python
bell = QuantumCircuit(2, 2)
bell.h(0)
bell.cx(0, 1)
bell.measure([0, 1], [0, 1])

sampler = service.create_sampler(
    backend=backend,
    options={"default_shots": 128},
)
sample_prepared = sampler.prepare([bell])
sample_prepared.quote
```

sampler.prepare([bell]) 返回 PreparedQuantumSourcePrimitive，保存本次实验的完整报价。quote.task_count、quote.total_shots 和金额分别显示任务数量、总采样次数和费用。options.default_shots 设置采样器的默认采样次数。

```python
print(sample_prepared.quote.to_dict())
```

审阅上面的设备、任务数、采样总量、金额和币种后，只有决定执行这一次实验时才运行下一单元。

```python
sample_job = sample_prepared.confirm()
```

确认后返回 QuantumSourcePrimitiveJob，在本机后台等待平台结果并整理 Qiskit 数据。若提交仍待平台确认，返回的 PreparedSubmissionReceipt 可按本页恢复步骤继续查询。

## 3. 从原生结果中读取 counts、寄存器和参数点

```python
sample_result = sample_job.result(timeout=300)
pub_result = sample_result[0]
counts = pub_result.data.c.get_counts()
print(counts)
print("实际采样数：", pub_result.data.c.num_shots)
print("平台任务号：", sample_result.metadata.get("platform_job_ids", []))
```

sample_result 是 Qiskit PrimitiveResult。[0] 选择第一个 PUB，pub_result.data 是 DataBin，data.c 选择经典寄存器 c 对应的 BitArray。get_counts() 返回比特串到测量次数的映射。

| 线路的写法 | 对应结果字段 | 原因 |
| --- | --- | --- |
| QuantumCircuit(2, 2)，然后 measure(...) | pub_result.data.c | 自动创建的经典寄存器名为 c |
| 没有经典寄存器的 QuantumCircuit(2)，然后 measure_all() | pub_result.data.meas | measure_all 创建名为 meas 的经典寄存器 |
| 自己添加 ClassicalRegister(..., "readout") | pub_result.data.readout | 数据字段跟随实际寄存器名，不固定为 c 或 meas |
| 一个 PUB 含两个参数点 | data.c.get_counts(0) 与 get_counts(1) | 选择该寄存器的不同参数点 |

```python
# 继续读取刚取得的 PUB 结果。
print("经典寄存器：", list(pub_result.data))
print("PUB 数组形状：", pub_result.data.shape)
print("样本顺序说明：", pub_result.metadata.get("sample_order"))
# 有多个经典寄存器时，可将它们按 Qiskit 规则合并后查看。
combined_counts = pub_result.join_data().get_counts()
print(combined_counts)
```

BitArray 由平台返回的整数 counts 展开得到。sample_order="counts_expansion" 表示样本按计数展开顺序排列，适合读取各位串的次数和统计分布。

## 4. PUB 与 shots：默认值和覆盖顺序

PUB 可以理解为一次计算的输入单元。Sampler 接受单个已测量线路，或 (线路, 参数值)，或 (线路, 参数值, shots)。最外层列表是多个 PUB；一个 PUB 的参数数组还可能展开成多个实际任务。

| 优先级 | 设置方式 | 影响范围 |
| --- | --- | --- |
| 最高 | (circuit, parameter_values, 128) | 只影响这个 PUB；其中每个参数点各自使用 128 shots |
| 其次 | sampler.prepare(pubs, shots=256)，run 同理 | 给本次调用中未明确 shots 的 PUB 提供默认值 |
| 其次 | create_sampler(..., options={"default_shots": 512}) | 给未提供以上两项的 PUB 提供采样器默认值 |
| 最终默认 | 未做任何上述设置 | 当前 Sampler 默认 100 shots；仍须符合目标设备限制 |

```python
# 准备一个包含两个 PUB 的实验，并查看总报价。
two_pub_prepared = sampler.prepare(
    [(bell, None, 128), bell.copy()],
    shots=256,
)
print(two_pub_prepared.quote.to_dict())
```

第一项采样 128 次，第二项使用本次默认值 256，共 384 shots。两项分别准备任务，接受整批报价后即可确认执行。

## 5. 参数数组：为每个参数点采样

数值数组最后一轴对应线路参数顺序，前面的轴构成参数点形状。先看 circuit.parameters 的实际顺序，再组织数据。本例有一个参数 theta 和两个参数点，每点 128 shots。

```python
import numpy as np
from qiskit.circuit import Parameter

theta = Parameter("theta")
parameter_circuit = QuantumCircuit(1, 1)
parameter_circuit.ry(theta, 0)
parameter_circuit.measure(0, 0)
print(list(parameter_circuit.parameters))

values = np.array([[0.0], [0.5]])  # 形状 (2, 1)：2 个参数点、每点 1 个参数
parameter_prepared = sampler.prepare([(parameter_circuit, values, 128)])
parameter_prepared.quote
```

客户端先绑定两组参数，再准备两条线路，总计 256 shots。若决定运行此独立实验，按相同的 prepare → 审价 → confirm 流程继续；下面两个单元是它自己的执行和结果步骤。

```python
parameter_job = parameter_prepared.confirm()
```

```python
parameter_result = parameter_job.result(timeout=300)
bits = parameter_result[0].data.c
print("参数形状：", bits.shape)  # (2,)
print("theta=0.0：", bits.get_counts(0))
print("theta=0.5：", bits.get_counts(1))
```

如果 values 形状为 (2, 3, 1)，结果参数形状就是 (2, 3)。用 get_counts((row, column)) 读取指定参数点，或用 get_counts() 汇总全部参数点。

## 6. Estimator：另一个独立的期望值实验

Estimator 接收状态制备线路和观测量。插件自动生成测量基变换及测量线路，平台执行这些线路后，由客户端计算期望值和标准误。

```python
from qiskit.quantum_info import SparsePauliOp

state = QuantumCircuit(1)
state.ry(0.5, 0)
observable = SparsePauliOp.from_list([("Z", 1.0)])
estimator = service.create_estimator(
    backend=backend,
    options={"default_precision": 0.1},
)
estimate_prepared = estimator.prepare([(state, observable)])
estimate_prepared.quote
```

这个 PUB 是 (线路, 观测量)。Z 的系数为 1，所求是这个状态下的 ⟨Z⟩。prepare 返回同样的 primitive 准备对象；可以先查看完整报价，再决定执行。仅当选择运行这个独立实验时，继续以下单元。

```python
estimate_job = estimate_prepared.confirm()
```

```python
estimate_result = estimate_job.result(timeout=300)
print("期望值：", estimate_result[0].data.evs)
print("估计标准误：", estimate_result[0].data.stds)
print("平台任务号：", estimate_result.metadata.get("platform_job_ids", []))
```

evs 与 stds 是 Qiskit 原生数组；单个观测量和单个参数点对应标量形状数组。evs 保存期望值，stds 保存采样估计的统计标准误。可将实际 evs 与 Ry(0.5) 后测 Z 的理论值 cos(0.5) 比较。

## 7. precision 怎样变成真实采样预算

| 优先级或选项 | 当前行为 | 为什么要看报价 |
| --- | --- | --- |
| PUB 的第 4 项 precision | (circuit, observable, parameter_values, precision)；优先于方法默认 | 同一调用中的不同 PUB 可以有不同 shots |
| prepare/run(..., precision=...) | 给本次未指定精度的 PUB 提供默认值 | 每个实际测量线路分别计算采样次数 |
| options.default_precision | 工厂 options 设置默认；Qiskit 2.5.2 默认 0.015625 | 该默认对应每个实际测量线路 4096 shots |
| options.abelian_grouping | 所核对的 Qiskit 默认 True；合并逐比特可交换的 Pauli 测量 | 是否能共享测量基会改变实际任务数 |

```python
# 查看估计器选项及采样次数公式。
import math

print(estimator.options)
for precision in (0.1, 0.05, 0.015625):
    print(precision, "->", math.ceil(1 / precision**2), "shots / 测量线路")
```

采样次数按 ceil(1 / precision**2) 计算：precision 越小，采样数越大。选择正的有限数，并使对应 shots 位于设备允许范围内。estimator.options 可查看当前安装的 Qiskit BackendEstimatorV2 所提供的默认选项。

## 8. 多观测量与参数广播

下面的观测量数组形状为 (2, 1)，参数点形状为 (2,)，广播后返回 (2, 2)：两行分别对应 Z 和 X，两列分别对应两个 theta。先准备这个实验并查看总报价。

```python
import numpy as np
from qiskit.circuit import Parameter

sweep_theta = Parameter("sweep_theta")
sweep_state = QuantumCircuit(1)
sweep_state.ry(sweep_theta, 0)
observables = [["Z"], ["X"]]
sweep_values = np.array([[0.0], [0.5]])
broadcast_prepared = estimator.prepare(
    [(sweep_state, observables, sweep_values)],
    precision=0.1,
)
print(broadcast_prepared.quote.to_dict())
```

客户端按参数点和可共享测量基展开任务。这里的 Z 和 X 分别测量，两个参数点也分别执行。quote.task_count、quote.total_shots 和 amount 显示展开后的任务数、总采样量和费用。接受报价后，继续确认并读取结果。

```python
broadcast_job = broadcast_prepared.confirm()
```

```python
broadcast_result = broadcast_job.result(timeout=300)
evs = broadcast_result[0].data.evs
stds = broadcast_result[0].data.stds
print(evs.shape)  # (2, 2)
print("Z 在两个参数点的估计：", evs[0])
print("X 在两个参数点的估计：", evs[1])
print(stds)
```

["Z", "X"] 的形状为 (2,)，与两个参数点逐项对应；[["Z"], ["X"]] 增加观测量轴，使每个观测量分别作用于每个参数点。根据实验所需的对应关系选择形状。

## 9. 直接运行、设备选项和固定布局

程序已经决定执行时，可以用 run 合并 prepare 与 confirm。下面展示 Sampler 和 Estimator 的直接执行写法，按需要选择相应入口。

```python
# Sampler 直接执行：
# sample_job = sampler.run([bell], shots=128)

# Estimator 直接执行：
# estimate_job = estimator.run([(state, observable)], precision=0.1)
```

| 设置 | 放在哪里 | 边界 |
| --- | --- | --- |
| 默认采样数或估计设置 | create_sampler/create_estimator 的 options | Sampler 仅 default_shots；Estimator 使用 Qiskit BackendEstimatorV2 选项 |
| 设备专属参数 | configuration_data 或当前 schema 声明的单次关键字 | 通过 backend.configuration_schema 查看当前设备的选项 |
| 固定物理位置 | prepare/run 的 physical_qubits 或 initial_layout | 选择其中一个入口，或直接提交已有布局的线路 |
| 审价策略 | auto_approve_quote=False 或显式 prepare | run 不自动确认时抛 QuoteApprovalRequired，准备对象位于异常 prepared 属性 |
| 钱包 | 创建 QuantumSourceService 时的 currency | 选择新报价使用的 OPEN 或 FULL 钱包 |

固定两个物理位置时，先从设备能力中选择可用、直接连接且支持所需门的编号。例如 sampler.prepare([bell], shots=128, physical_qubits=[1, 11]) 将两个逻辑比特分别固定在物理 1、11。固定位置采用 mapping=False。Estimator 也接受布局参数，其测量基变换门同样按所选物理位置编译。

Primitive 的等待时间通过结果句柄的 result(timeout=...) 设置，具体用法如下。

## 10. 等待、超时与恢复原提交

| 操作 | 作用 | 后续步骤 |
| --- | --- | --- |
| primitive_prepared.confirm() | 确认实验并启动本机后台结果处理 | 保存返回的任务句柄 |
| primitive_job.result(timeout=300) | 本次调用等待后台结果最多 300 秒 | 读取返回的 PrimitiveResult |
| 当前 result 调用超时 | 后台任务继续处理 | 稍后用原 job 再调用 result |
| primitive_prepared.recover() | 查询原已尝试提交，必要时恢复本机结果处理 | 取得任务句柄后继续读取 result |

PrimitiveJob 在本机后台等待平台结果并计算 BitArray、evs/stds。读取期间保持 Notebook 内核和 service 开启。关闭内核后，云端已接受任务继续处理；跨会话读取时，保留实验定义和任务标识，通过 Core 查询平台记录并下载原始结果。完整 PUB 结果整理依赖本次会话中的实验定义和本地处理。

```python
# 只查询上面 Sampler 实验的状态；正常排队时 job_id() 可能尚无值。
print(sample_job.status())
print(sample_job.submission_ids)
print(sample_job.platform_job_ids)

# 如果仅是上一次 result 等待超时，可直接再次等待原 job：
# sample_result = sample_job.result(timeout=300)

# 若后台归约因原提交待核实等异常已经退出，可只读恢复：
# recovered = sample_prepared.recover()
# 检查 recovered 是任务句柄还是仍待核实的 PreparedSubmissionReceipt，
# 只有取得任务句柄后再调用 recovered.result(timeout=300)。
```

primitive_job.cancel() 向平台申请取消尚未领取的待发送任务。多任务批次可以分别成功或失败，通过取消报告查看各项结果；仍在处理的任务继续沿原句柄查询。

## 11. 读取设备提供的原生概率

标准 Sampler 通过 counts 构造 BitArray。对提供原生概率通道的设备，可使用 sampler.run_probabilities(...)，从 data.probabilities 中读取 QuasiDistribution。

```python
# 使用设备的原生概率通道执行实验：
# probability_job = sampler.run_probabilities([bell], shots=128)
# probability_result = probability_job.result(timeout=300)
# distribution = probability_result[0].data.probabilities
# print(distribution)
```

单个参数点的 probabilities 是一个分布；参数网格对应相同形状的一组分布。选择 counts 结果时使用标准 prepare/run，选择设备原生概率结果时使用 run_probabilities。

## 12. 完成后关闭连接

```python
# 仅在所选实验的结果等待与处理完成后运行。
service.close()
```

完成结果等待与读取后，调用 close 释放客户端连接。平台上的任务记录和结果继续保留。

## API 参考与延伸阅读

Sampler、Estimator、准备对象和结果对象的签名、参数与返回值集中收录在 API 参考中。

- [Qiskit 插件 API](api.html#qiskit)
- [接口实现与测试依据](api.html#evidence)

- [OpenQuantum 官方：Sampler & Estimator](https://docs.openquantum.com/qiskit-plugin/sampler-estimator/)
- [OpenQuantum 官方：Backends & Targets](https://docs.openquantum.com/qiskit-plugin/backends-targets/)

## 下一步

- [Qiskit 入门与 Backend 等待设置](qiskit.html)
- [设备能力、schema 与 Target](capabilities-qiskit.html)
- [固定物理比特与编译](compilation.html)
- [结果数据的来源与含义](results.html)
- [常用接口索引](api.html#primitives)

## 下载本页 Jupyter Notebook

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

[下载《Sampler 与 Estimator》Notebook（.ipynb）](../../notebooks/sdk_user_guide_qiskit_primitives.ipynb)
