Metadata-Version: 2.4
Name: quantum-source-sdk
Version: 0.4.14+20261027.simcompat
Summary: Quantum Source HTTP client with optional Qiskit and PennyLane integrations
Requires-Python: <3.13,>=3.12
Description-Content-Type: text/markdown
Requires-Dist: requests<3,>=2.31
Provides-Extra: qiskit
Requires-Dist: qiskit<3,>=2.0; extra == "qiskit"
Requires-Dist: numpy<3,>=1.26; extra == "qiskit"
Provides-Extra: pennylane
Requires-Dist: pennylane<0.46,>=0.45.1; extra == "pennylane"
Requires-Dist: numpy<3,>=1.26; extra == "pennylane"
Provides-Extra: all
Requires-Dist: qiskit<3,>=2.0; extra == "all"
Requires-Dist: pennylane<0.46,>=0.45.1; extra == "all"
Requires-Dist: numpy<3,>=1.26; extra == "all"
Provides-Extra: secure
Requires-Dist: keyring<26,>=25; extra == "secure"

# Quantum Source SDK

客户端等待与进度修订：2026-10-20。SDK `0.4.13+20261020.waitoptions`，配套后端仍为 `0.16.9+20261019.releasealign`，Python **3.12**。本版增加 Core 的单次轮询参数和 Qiskit Backend 的可选等待设置；不修改服务端 API、数据库、厂商 Adapter、计费或取消规则。保留客户端 OPEN/FULL 选择、public/stand 单通道、物理布局和安全错误提示。本轮仅本地源码、安装包及手册更新，不自动升级现有 Notebook 环境或部署云端，不提交量子任务。

本目录仅包含 Core HTTP SDK、Qiskit 插件和 PennyLane 插件，不包含服务端源码、厂商 SDK、数据库、密钥或历史真机结果。本次本地修改不代表已经推送 GitHub、部署服务器或发布到 PyPI。

## 安装

在这份候选 SDK 源码目录中执行；不要从旧发布分支或备份的旧 dist 安装后误认为已升级到本版本：

```powershell
py -3.12 -m venv .venv
./.venv/Scripts/python.exe -m pip install ".[all,secure]"
```

只需要一个框架时可使用 `.[qiskit,secure]` 或 `.[pennylane,secure]`。`.[secure]` 仅安装 Core 和系统密钥库接口。客户端不需要安装本源 SDK 或服务端包。

在环境变量或系统密钥库中配置平台地址 `QUANTUM_SOURCE_API_URL` 及平台 SDK 凭据（`QUANTUM_SOURCE_SDK_KEY`，或 `QUANTUM_SOURCE_CLIENT_ID` / `QUANTUM_SOURCE_CLIENT_SECRET`）。本源等厂商凭据只配置在后端。

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

service = QuantumSourceService()
backend = service.return_backend("origin:WK_C180")
circuit = QuantumCircuit(2, 2)
circuit.h(0)
circuit.cx(0, 1)
circuit.measure([0, 1], [0, 1])
job = backend.run(circuit, shots=128)
job.result().get_counts()
```

上例会提交真实任务。`run()` 默认自动批准**服务端**报价，选择真机可能产生费用。需要先查看价格时使用 `prepared = backend.prepare(circuit, shots=128)`、`prepared.quote` 和 `prepared.confirm()`；也可以在运行入口设置 `auto_approve_quote=False`。费用、余额、权限与平台额度始终由后端核验，不再要求调用方重复填写单批预算上限。

## 可选：等待时间与进度显示

Core 使用已有真实任务号时：

```python
task = scheduler.wait_for_job(
    job_id,
    timeout=300,
    interval=2,
    verbose=True,
)
```

`timeout` 是本次等待预算，`interval` 是两次查询之间的暂停秒数；省略时沿用客户端默认值，不修改共享客户端设置。`verbose` 默认关闭，只显示简短英文阶段与状态变化。旧 `timeout_seconds=300` 仍有效；新旧名称同时传相同值可用，不同值会报参数冲突。等待不改变终态返回或异常规则，返回后仍应检查 `task.status`。只有准备号时继续使用 `wait_for_submission`，不能把准备号当作真实任务号。

Qiskit Backend 可在**新实验的** `prepare/run` 中指定默认等待设置：

```python
prepared = backend.prepare(
    circuit, shots=128, verbose=True, job_timeout_seconds=300
)
prepared.quote
```

审阅并接受报价后，才确认该实验：

```python
job = prepared.confirm()  # 真机可能收费；不要对同一实验重新 prepare/run。
result = job.result(wait=2)
```

`job_timeout_seconds` 仅在显式设置时作为后续 `result()` 的默认预算，计时从调用 `result()` 开始。可用 `job.result(timeout=600, wait=2)` 覆盖本次预算；显式 `wait` 同时用于平台排队和真实任务轮询，不新增 `interval` 别名。省略新参数保留旧行为，省略 `wait` 也保留原默认查询间隔。一次结果等待的所有线路与排队阶段共享总预算，不在取得真实任务号时重新开始计时。`prepared.recover()` 返回的句柄保留原来的运行设置。

`backend.run(..., verbose=True, job_timeout_seconds=300)` 仍然准备并确认后返回句柄，不变成等待计算完成才返回；这是本项目的现有语义，不宣称与 OpenQuantum 的阻塞提交完全兼容。新设置不进入 `configuration_data` 或设备 Options，不影响线路、shots 或报价。本轮不为 Sampler、Estimator、PennyLane 新增同款公开参数。

超时只结束本次等待，不取消、不重发、不退款或释放名额；之后继续查询原任务。HTTP 请求自身的超时仍由传输层控制。进度输出不打印完整线路、原始响应或凭据，不扩大异常公开范围。

## 创建客户端时选择钱包

省略 `currency` 默认使用 OPEN。要使用 FULL 余额，在准备新任务之前创建相应客户端：

```python
from quantum_source_qiskit import QuantumSourceService

service = QuantumSourceService(currency="FULL")
backend = service.return_backend("origin:WK_C180")
service.currency  # "FULL"，只读
```

该 service 创建的 Backend、Sampler、Estimator 和批次都使用 FULL。Core 使用 `SchedulerClient(..., currency="FULL")`，可只读查看 `scheduler.currency`；`prepare_submission`、`prepare_submissions`、`submit_job` 都由客户端把钱包传入新准备请求。

PennyLane 可独立创建 `qml.device("quantum_source.device", ..., currency="FULL")`（或 `QuantumSourceDevice(..., currency="FULL")`）。传入 `scheduler=full_scheduler` 时省略 `currency` 或传 `None` 即继承；显式同值可用，冲突值会在本地报错。`dev.currency` 只读。Qiskit 注入已有 `scheduler_client` / `management_client` 时同样继承并检查显式冲突。需要另一钱包时新建对应客户端，不修改只读属性。

钱包不属于 `JobSubmissionConfig`、Backend `config`、`configuration_data`、`Options` 或 `deviceOptions`，也不是 `prepare/run` 的逐任务参数。底层 `JobPreparationCreate(..., currency="FULL")` 保留协议级显式选择；`scheduler.prepare_job(payload)` 不用客户端默认值覆盖它。

OPEN 与 FULL 独立记账，不合并可用余额、不自动兑换，也不因余额不足自动切换。金额、币种及报价标识在 `prepare` 时固定，新建 FULL 客户端不会改变旧 `prepared`。确认若明确返回余额不足的 `APIError`（HTTP 402），该次被拒绝项不会发送到厂商；若用户决定改用 FULL，应通过 FULL 客户端重新准备并审阅新报价。批次此前已被接受的任务仍然保留。发送结果不明、等待超时等其他异常不能当作未发送，仍只查询原提交。

## 厂商提交错误

新版后端会保留厂商提交异常的安全详情；SDK 自动显示例如 `[origin] RuntimeError: parameter exception`，不需要额外打开调试选项。可以使用以下方式读取详情：

```python
from quantum_source_sdk import ProviderSubmissionError

try:
    job = prepared.confirm()
except ProviderSubmissionError as error:
    print(error.provider_error_type, error.provider_message)
    print(error.failure_stage, error.submission_id)
    # 仅查询原提交，不因出现异常而重新创建或发送另一条任务。
```

`ProviderSubmissionError` 仍继承 `PlatformSubmissionError` 和 `APIError`，已有捕获逻辑继续可用。平台 HTTP 状态保存在 `platform_http_status`，与厂商错误码 `provider_error_code`、厂商 HTTP 状态 `provider_http_status` 分开；没有捕获到的厂商字段为 `None`，不把平台 503 当成本源返回的状态。

后端将安全诊断保存在原提交记录中，重启或重复查询仍能读到；Key、认证头、原始请求体、堆栈和历史私有线路不向客户端公开。旧记录若没有保存安全诊断，不会凭空补出厂商原因。旧后端没有该结构时，SDK 保持原来的 `PlatformSubmissionError` 行为。显示具体错误不代表厂商肯定没接收任务：原有冻结、费用预留和禁止自动重发规则不变。详细字段见 [厂商提交错误说明](docs/PROVIDER_SUBMISSION_ERRORS.txt)。

## 简洁的设备列表

```python
devices = service.backends(online=True)
devices  # Notebook 表格；print(devices) 为相同五列的文本表格
```

默认只显示 `short_code`、`type`、`num_qubits`、`status`、`platform_active_tasks`。不展开门集、拓扑等长能力数据，也不显示 `accepting_jobs` 或厂商 `queue_depth`。`online=True` 的原有筛选规则不变，移除显示列不等于删除设备接收任务能力的内部判断。

`platform_active_tasks` 是**该设备上所有用户已被平台接受、但尚未确认终态的任务总数**：包括平台待发送/发送中、厂商排队/运行中、仍待核实的提交；仅准备线路或报价不计。平台确认成功、失败或取消即减一，无需用户下载结果；已确认成功但结果获取暂时失败也不再计入。它既不是厂商队列数，也不是某个用户的排队名次。

后端根据已持久化的平台记录只读统计，随公开设备目录匿名返回总数，不公开其他用户、任务号或线路。不为了显示总数而访问厂商、轮询任务或修复数据库；因此反映的是**平台已知状态**，不是厂商实时状态。旧后端没有此字段时显示 `—`，不使用厂商队列替代，也不把缺失值当作 0。

返回值是 `list` 的子类，每项仍是完整字典；索引、迭代和 JSON 序列化保留数据，`devices[0]["metadata"]` 仍可读取能力详情。`list(devices)` 可显式转回普通列表查看完整数据；需要类型判断时使用 `isinstance(devices, list)`，不要依赖 `type(devices) is list`。`return_backend()`、`return_target()` 和提交参数不受显示方式影响。

## 简化后的提交接口

Core、Qiskit 和 PennyLane 的逐任务提交入口删除以下 8 个额外通用参数：`execution_options`、`currency`、`confirm_hardware`、`project_id`、`on_update`、`max_tasks`、`max_total_shots`、`max_cost`。`currency` 自 0.4.12 起在客户端 / 独立 PennyLane 设备构造时选择，不恢复为逐任务参数。PennyLane 的额外预算别名 `max_circuits_per_batch` 和回调别名 `progress` 同步移除，避免换名字保留同一套参数。

- `amend`、`mapping`、`optimization` 保留为设备动态选项，不写死在通用方法签名中。先选择设备，再读取 `backend.configuration_schema` / `backend.options`；只有当前设备声明支持的字段才能作为动态关键字使用，有默认值的声明进入 Options 并可用 `backend.set_options(...)` 设置。也可使用统一的扁平 `configuration_data`。
- `physical_qubits` 和 `initial_layout` 两个布局入口均恢复。Qiskit `backend.prepare/run`、Sampler/Estimator 及 `backend.transpile` 保留原入口；二者不能在同一次调用中同时使用。也可以先 `backend.transpile(circuit, initial_layout=[1, 11])` 再提交已编译线路；编号须来自当前设备拓扑，不重复应用布局。
- 准备报价后直接 `prepared.confirm()`；不再额外选择是否确认真机。确认真机报价仍会消耗资源。
- 报价仍展示币种、金额、实际线路数和 shots。SDK 默认使用 OPEN，客户端可显式选择 FULL，余额不足不会自动切换钱包；准备后的旧报价不变，服务端的双钱包及历史账务协议不变。
- 旧 `execution_options` 参数与嵌套字典不再兼容，请将其中字段移到 `configuration_data` 顶层。其余已移除参数也应从调用中删除，而不是换一个容器继续传递。

底层 HTTP 协议模型中兼容服务器所需的内部字段、报价返回的 `currency` 和查询响应的项目标识不等于这些用户提交参数仍然存在。执行方案和队列选取保留：`execution_plan="public"`、`queue_priority="stand"`，`auto` 选择同一通道，`standard` 是 `stand` 的别名。

## 每组织最多 10 个非终态任务

同一组织的所有成员、SDK Key、项目与机器共享 **10 个尚未结束的任务名额**，模拟器也计入。`prepare()` 只准备线路和报价，不占名额；任务确认被接受后开始占用，平台 `queued`、`submitting` 及尚待平台核实的提交继续占用。同一已接受任务的重复确认不会重复占用。核实问题抛平台异常，不作为正常 Unknown 任务状态返回。

任务按服务端确定的组织范围计数；不能通过换 SDK Key、机器或组织参数绕过权限和名额核验。此次不再暴露 `project_id` 作为 SDK 提交参数，已有记录的项目归属不因此改变。

```python
prepared = backend.prepare(circuit, shots=128)
prepared.quote
```

核对报价后再单独确认：

```python
job = prepared.confirm()
```

组织已有 10 个非终态任务时，第 11 个确认被明确拒绝，返回 HTTP **429**、`job_limit_reached`。SDK 抛出 `quantum_source_sdk.JobLimitReachedError`，它继承 `APIError`；提交入口的异常保留 `.prepared`，便于保存原准备对象和已有回执。不要为此写自动确认或重试循环。先核对原任务与组织占用，再由用户明确决定后续提交；换 Key、项目或机器不会获得另一组名额。

平台确认任务已到 `Completed`、`Failed` 或 `Canceled` 时释放名额，**无需下载结果**。超时、本地中断、仅发出取消请求或平台无法核实都不等于终态。名额释放与余额结算、失败退款相互独立，不需要等待退款完成，也不能以退款状态代替任务状态。

批量线路、参数绑定或测量组展开为多个独立任务时，每个任务分别占一个名额。批次可能先接受一部分，再因名额不足停止；已接受任务继续保留，原 `prepared`、任务 ID 和提交回执用于查询与恢复，不代表整批回滚或自动补发。客户端单批上限参数的删除不改变这些服务端规则；本版尚未部署或完成真机验收。

## 取消：仅平台尚未领取的排队项

统一使用 `scheduler.cancel_job(identifier)`，其中 identifier 可以是原 `submission_id` / `preparation_id` 或真实 `job_id`。只有平台尚未领取的 `queued`（对应 OpenQuantum 的 Pending 语义）可取消；已被领取、开始发送、取得厂商任务号、处于 `submitting` 或待核实的任务均不可取消。支持解析真实 ID 不代表可以取消已发送任务，也不会退回调用厂商取消。历史取消状态查询仍保留。

沿用上面 Qiskit 返回的原 `job`，仅在确实要放弃尚未发送的实验时选择以下操作，不要在已取得结果后照做；它不是正常结果获取的必经步骤。服务端会原子判断与派发的竞态。

```python
job.cancel()
```

Core 成功返回 `None`（HTTP 204），已取消的同一提交可幂等确认；兼容的 `scheduler.cancel_submission(preparation_id)` 仍返回 `SubmissionReceipt`。不可取消时 HTTP 409 `job_not_cancelable`，SDK 抛 `JobNotCancelableError`（继承 `APIError`）；权限、网络等错误按原异常向外传播。

Qiskit 使用原句柄的 `job.cancel()`：全部成功才返回 `True`；单任务失败直接抛原异常，多任务存在失败则抛 `BatchCancellationError`，保留 `.cancelled_ids`、`.failures`、`.cancellation_report`，也可查看 `job.cancellation_report`。不吞成 `False`，不回滚已取消部分、不自动补发。`BatchCancellationError`、`CancellationReport` 可从 `quantum_source_sdk` 导入。

PennyLane 在 Ctrl+C / Jupyter 中断作为 `KeyboardInterrupt` 到达本次执行时，查询本次已尝试的提交，只对已接受且仍 `queued` 的项尝试取消。已发送、已知真实 ID、结果未知的项跳过；不触碰旧实验、不自动补发，最后仍抛出原中断。原 prepared 与异常的 `.cancellation_report` 保留 `attempted_ids`、`cancelled_ids`、`skipped_ids`、`failures`、`receipts`；异常的 `.prepared` 保留原对象。强制结束进程不保证清理能执行。`TimeoutError` 仍只停止等待，`close()` 只关闭连接。

显式使用 `dev.cancel_job(submission_id)` 可以指定原待发送项。`dev.cancel_job()` 省略参数时优先使用 `dev.last_prepared` 的最近一个已尝试 preparation ID；没有时才回退最近真实 job ID，并受同一不可取消检查。这不是取消全部历史任务。中断后只读查看报告可用 `dev.last_prepared.cancellation_report`。

确定未发送的取消成功释放原 credits、shots 预留及组织名额；OPEN 原到期时间不重置，已过期的释放不会恢复可用余额。这不是“所有厂商 Canceled 都自动退款”；最终 `Failed` 的原币退款规则独立保留。

## 平台核实异常，不返回正常 Unknown 状态

Core、Qiskit Backend/Sampler/Estimator 和 PennyLane 使用相同的异常契约，均可从 `quantum_source_sdk` 导入：

| 异常 | 服务端 error.code | 含义 |
|---|---|---|
| `PlatformSubmissionError` | `platform_submission_unconfirmed` | 原请求可能已发送，平台不能确认发送结果；只能核实原提交，不能自动重发。 |
| `PlatformStatusError` | `platform_status_unavailable` | 平台不能提供本次查询已核实的任务状态；不把缓存状态当成新查询成功。 |
| `PlatformResultError` | `platform_result_unavailable` | 平台不能读取或核实原任务结果；不生成量子成功结果。 |

三者继承 `PlatformError` / `APIError`，保留完整 `error_response`、`request_id`、`error_response.details` 和其中已有的原 `submission_id` / `job_id`。发送结果不明的 details 包含 `send_allowed=False` 与只读 `recovery_path`。提交及恢复异常保留 `.prepared` 原句柄，不创建替代任务。

这里的异常表示平台有问题，不等于厂商计算 `Failed`、可取消、应退款或应释放名额。重复 `confirm()` / `recover()` 只查询本次已尝试的原提交，不重新上传、不重新报价、不补发剩余批次。只有厂商明确终态才能沿原规则终结任务；账务补偿独立处理。

SDK 也拒收旧后端的 `Unknown` 任务状态或 `outcome_unknown` 回执：分别抛 `PlatformStatusError`、`PlatformSubmissionError`，不改成 Pending、Queued 或 Failed。历史发布说明中的 Unknown 描述是旧版本记录，不是当前契约。用未执行的示例或离线 mock 测试通过，不代表真实云部署验收完成。

## 主要变化

- Qiskit 的 `backend.transpile(..., optimization_level=0..3)` 控制客户端逻辑门优化；`backend.prepare/run(..., optimization=True/False)` 控制本源编译开关，两者独立。不指定物理位置的本源逻辑路径默认 `mapping=True, optimization=True`；显式物理位置要求两项均为 `False`，不自动重新映射。
- `backend.configuration_schema` 公开设备参数的类型、允许值和默认值；有默认值的声明自动进入 `backend.options`，使用 `backend.set_options(...)` 设置后续默认值。单次 `prepare/run` 的设备关键字优先于扁平 `configuration_data`，再优先于 Options；逻辑/物理限制内部处理，不要求用户读取 `x-execution-paths`。仅保留统一扁平配置，不再接受旧 `execution_options` 包装。布尔参数不接受整数或字符串；详见 [设备选项指南](docs/COMPILER_OPTIONS.md)。
- [编译参数 Notebook](notebooks/compiler_options_20261012.ipynb) 使用原生 `QuantumSourceService` / Backend API，分开显示能力、准备报价、确认与原生结果；未执行、无输出、无硬编码凭据。
- 真机默认 `OPEN/public/stand`；Open 每笔发放默认 90 天到期，余额不足直接拒绝，不会自动改扣 Full。
- 所有新任务统一 `execution_plan="public"`、`queue_priority="stand"`；沿用原 public/stand 报价与计费，SDK 默认 OPEN。模拟器保持免费。旧 private/privacy、prior/priority 和 instant 参数在上传前明确报错，不会静默改通道、币种或金额。
- 未经物理布局编译的逻辑线路交厂商选位。使用 `backend.transpile(..., initial_layout=...)` 明确指定物理位置时保留位置；提交时读取线路已有布局，非相邻双比特门直接拒绝，不插入 SWAP。
- 真机确认后先进入标准平台发送队列。`job_id()` 在取得厂商任务号前为 `None`，可使用 `submission_ids` 查询；`job.result()` 原生等待排队及计算，不提供优先发送选项。
- 相同准备对象的重复确认恢复原提交；明确新建实验仍可执行。发送结果不明时不自动重发，并暂停同一机器的后续发送，待核实后恢复。
- 最终 `Failed` 按原币种全额退款；历史多通道记录仍按原已确认金额处理，不改价、不改报价 hash、不重发。Open 保留原到期时间，过期退款不可消费。超时、平台核实异常不等于最终失败。
- 删除旧 RemoteClient 和过渡兼容入口；结果默认展示公开字段，不泄露厂商原始诊断。详见 [版本说明](RELEASE_NOTES.md)。

## 历史发布分支（不是本次候选的新发布）

- [独立 SDK 使用手册与 Notebook](https://github.com/hcz-sc/quantum-source/tree/quantum-source-sdk-docs-2026-09-30-credits2)
- [后端 0.16.0、迁移与部署说明](https://github.com/hcz-sc/quantum-source/tree/quantum-source-backend-2026-09-30-credits2)

## 开发与验证

```powershell
./.venv/Scripts/python.exe -m pip install -r requirements-development.txt
./.venv/Scripts/python.exe -m pytest tests
./.venv/Scripts/python.exe -m build
```

2026-10-15 对本次设备选项与布局修订 SDK 执行全套离线回归：**1008 项通过、0 失败**。10 条既有框架提示涉及 counts 重建样本顺序和设备未声明 barrier，不是失败。测试禁止外部网络；覆盖公开签名、按所选设备声明提供选项、两种布局入口、Estimator 测量位置、报价、队列、恢复、中断和取消。测试在 Python 3.12、Qiskit 2.5.2、PennyLane 0.45.1 下运行，Windows 沙盒临时文件定向到本轮工作目录。

历史参考：2026-09-30 发布副本的独立 SDK 离线回归为 **514 项通过、0 失败**，不与本次数量相加。

本分支的 `tests/` 是纯客户端离线回归子集，阻止外部网络，不包含原综合项目的服务端集成及项目布局测试。完整源码基线已有 1,874 项通过、7 项跳过；这个总数**不是**独立分支的测试数量。原完整类型检查仍有 82 个错误，不能声称全量静态检查通过。此次没有新增真机任务，没有部署服务器或迁移线上数据库。
