# Python 版 wai-quantum > 本文为机器辅助翻译,尚待熟练的读者审校。英文原文:[README.md](README.md)。 在 Python 和笔记本中以进程内方式使用 WAI Quantum。这个包内含 WAI Quantum 作业引擎(Rust crate `wai-quantum` 的 `engine`,通过一个 pyo3 扩展模块提供),因此在这里规划、运行并封存的作业,与 `wai-quantum` 命令行、作业服务、MCP 服务器、Studio 和桌面应用给出相同的计划、相同的结果和相同的回执。 - **引擎**:`run`、`plan`、`lint`、`draw`(文本和 SVG)、`layout`、`critique`、`improve`、`shortcuts`、`equivalent`、`verify`、`seal`、`canonical`、`digest`、`methods`、`accuracy_classes`、`bloch`、`history`、`estimate` 和 `verify_estimate`。 - **回执**:每个结果都用你传入的密钥(`wai_quantum.Key`)封存;任何回执(无论是在这里、由命令行、由作业服务还是在浏览器标签页中生成的)都可以在这里离线检查。 - **作业服务**:`wai_quantum.Service`,一个只用标准库实现的客户端,用于 `https://wai.transaction.science/quantum-jobs/v1`。 - **笔记本小组件**:`Circuit`、`Result`、`Receipt` 和 `Bloch`,附有可在任何笔记本中渲染的静态 HTML 和 SVG 后备显示。 ## 安装 从 Python 包索引安装。每个平台一个 wheel,适用于 3.9 及以后的所有 Python 版本(稳定 ABI,`abi3`):macOS arm64 和 x86_64、Linux x86_64 和 arm64(manylinux 2.17)、Windows x86_64。 ```sh norun=install pip install wai-quantum # 引擎和作业服务客户端:没有其他依赖 pip install "wai-quantum[widgets]" # 另加笔记本小组件(anywidget) ``` 同样的 wheel 也附在代码托管平台(forge)上包含它们的 `wai-quantum-cli-v` 发布版中,每个都附有 SHA-256: ```sh norun=install pip install wai_quantum-0.1.4-cp39-abi3-.whl ``` 要自己构建 wheel,需要 Rust 1.98 和 maturin 1.15.0: ```sh norun=install cd wai/wai-quantum-py maturin build --release --locked -o dist pip install dist/wai_quantum-*.whl ``` ## 使用 ```python runnable import wai_quantum as wq bell = "OPENQASM 3.0; qubit[2] q; bit[2] c; h q[0]; cx q[0], q[1]; c = measure q;" key = wq.Key.generate() # 或 Key.from_file(...)、Key.demo();默认使用这个用户的密钥 job = wq.run(bell, shots=1000, key=key) job.method, job.accuracy # ('stabilizer', 'exact') job.counts # {'00': 522, '11': 478} job.probabilities() # 每个计数除以测量次数:采样得到的频率 wq.verify(job).valid # True:回执封存了这个请求和这个结果 ``` 作业就是作业服务所用的形式,表示为一个普通的 `dict`(`status`、`method`、`why`、`estimate`、`request`,以及带有 `receipt` 的 `result`),因此 `json.dumps(job)` 写出的就是作业服务会发送的内容。它的辅助方法读取最常用的部分:`counts`、`shots`、`probabilities()`、`expectations`、`amplitudes`、`method`、`accuracy`、`receipt`、`verify()`。 `run` 接受 OpenQASM 3(或 2)文本、此类文件的路径或一个作业 dict,并以关键字参数接受作业的各字段:`shots`、`seed`、`method`(默认为 `auto`)、`observables`(`"Z0 Z1"` 或 `{"pauli": "X3 Y4", "coeff": 0.5}`)、`amplitudes`(比特串,量子比特 n − 1 在前)、`noise`(`depolarizing_1q`、`depolarizing_2q`、`amplitude_damping`、`dephasing`、`readout`)以及 `options`(因方法而异;`wq.methods()` 会列出它们)。`memory_gb`(16,即命令行的默认值)限定作业在这里可以占用的内存,因此作业在两者上会规划到同一种方法。 ```python runnable src = "OPENQASM 3.0; qubit[2] q; h q[0]; t q[0]; t q[0]; cx q[0], q[1];" a, b = "qubit[1] q; h q[0]; x q[0]; h q[0];", "qubit[1] q; z q[0];" job = wq.run(bell, shots=100, intent={"distribution": {"00": 0.5, "11": 0.5}}, key=key) job.verdict["level"], job.verdict["holds"] # ('proof', True):引擎检查到了哪一级,已封存 job.verdict["layer"], job.verdict["limits"] # 由什么判定('exact_simulation'),以及它不能说明什么 wq.verify(job).answer["again"] # 'reproduced':已封存的裁定在此处重新得出 wq.verdict(job, {"distribution": {"00": 1.0}}) # 另一个意图,在这里得出:注明未经封存 wq.signoff(job, {"outcomes": ["00", "11"]}, key=key).verdict["holds"] # 为已封存的作业签核:其摘要、意图和裁定封存于同一回执之下 fair = {"distribution": {"00": 0.5, "11": 0.5}} wq.check(bell, fair)["next"] # 签核循环:每个用例由最强的层判定,并说明下一步;不封存任何内容 g = wq.grade(bell, {"title": "A Bell pair", "intent": fair}, key=key) # 评分作为作业封存,任何人都可以重新运行 wq.verify(g).answer["again"] # 'reproduced':评分在这里重新得出 wq.attest(job, {"outcomes": ["00", "11"]}, key=key)["attested"]["held_to"] # 作业先像 verify 那样核验,然后签核 wq.repair("OPENQASM 3.0;\nqubit[1] q\nh q[0];\n") # 对被标出的行的修正,以及需要人来处理的部分 wq.plan(bell) # 方法及其原因,不运行 wq.lint("qubit[1] q;\nfoo q[0];") # {'ok': False, 'error': 'line 2: unknown gate foo', 'line': 2} print(wq.draw(bell)) # 文本线路图,与 `wai-quantum draw` 打印的相同 svg = wq.draw(bell, format="svg") # Studio 中的图,作为一个 SVG 文档 wq.critique(src); wq.improve(src) # 度量与问题模式;经证明等价的变体 wq.equivalent(a, b) # {'verdict': 'equivalent', 'how': 'tableau', 'why': ...} wq.bloch("OPENQASM 3.0; qubit[1] q; ry(0.3) q[0];", key=key) # 每个量子比特的约化态,来自引擎 e = wq.estimate(src, qubit="ns-e4", budget=1e-3, key=key) # 容错量子计算机需要什么,每个估算都已封存 wq.verify_estimate(e) # 封存签名、每一份回执,以及在这里重新运行的请求 wq.estimate(counts={"numQubits": 100, "cczCount": 5000}) # 或者一份逻辑计数(或 routine=)文档 ``` `estimate` 给出程序的逻辑计数(逻辑量子比特,包括多控制门所需的辅助量子比特;测量;T 门;Toffoli 门;旋转门及其合成所得门序列中的 T 门;T 深度和深度)、错误预算中分给合成的份额及每个旋转门所占的部分、根据引擎自己的解码器对码模型所做的校准、在该物理量子比特上的估算,以及物理量子比特数与运行时间之间的前沿,每个估算都在资源估算回执族中封存。`export="counts"` 或 `"routine"` 会把计数另外作为一份文档给出。每个数字的含义和假设,见[估算页面](../wai-quantum/docs/estimate.md)。 ```python runnable c = wq.qec_code("color:7", key=key) # 它的校验、k、可用的解码器,以及经过认证的距离 wq.qec_code("color:7", certificate=c) # 重新运行证书的见证和搜索 wq.qec_decode("surface:5", error=[3, 12]) # 解码其校验子,并判定残差 wq.qec_decode(memory=(5, 5, 0.003), shots=20000, decoder="correlated") # 线路层面的失败率 wq.qec_threshold(distances=[3, 5, 7], p=[0.002, 0.004], shots=20000) # 失败率及其误差 job = wq.run(src, observables=["Z0 Z1"], noise={"depolarizing_2q": 0.02}, mitigate="zne", key=key) job["result"]["mitigation"] # 每个值的原始值和缓解后的值及其误差棒,以及该方法的假设 ``` `qec_code`、`qec_decode` 和 `qec_threshold` 是引擎的纠错功能,与命令行的 `wai-quantum qec` 以及两个 MCP 服务器的 `qec_*` 工具给出的回答相同,每个回答都以作业的形式封存(`wq.verify` 检查它)。距离证书在每个扇区中给出找到的逻辑算符的最小权重、该权重的一个见证,以及经穷举搜索排除的每个更轻的支撑;被 `bound` 截止的搜索对其之外的情况不作结论。每个解码器保证什么,以及每种缓解方法的偏差和方差,见[纠错页面](../wai-quantum/docs/qec.md)。 ### 密钥与回执 `Key.demo()` 是公开的演示密钥(即命令行的 `--demo-key`):用它签名的回执在任何地方都逐字节重现,只能证明结果未被篡改。`Key.generate()` 生成一个密钥;`key.save(path)` 以命令行所用的格式(64 位十六进制数字)把它写出,且仅其所有者可读,因此 `wai-quantum run --key path` 会用同一个密钥签名。未给出密钥时,`run` 用这个用户的密钥签名,也就是命令行自己用的那个(`$WAI_QUANTUM_KEY`,否则为 `$XDG_CONFIG_HOME/wai/quantum-key` 或 `~/.config/wai/quantum-key`,在 Unix 类 shell 之外的 Windows 上为 `%APPDATA%\wai\quantum-key`,首次使用时生成)。 回执是对请求和结果的 RFC 8785 规范化 JSON 所求 BLAKE3 摘要的 Ed25519 签名。`wq.canonical(value)` 给出这些字节,`wq.digest(request, result)` 给出摘要;`wq.seal(request, result, key=...)` 可封存任何结果。拒绝会引发 `wq.EngineError`,附带引擎自身的消息、消息指明了行号时的 `line`,以及其 `code`:读取器的(`Q103`)或作业层的(`J011`),与消息末尾所附的相同。对于改动了没有任何诊断标出的行的建议,`wq.repair(program, proposed=...)` 会引发一个带 `J021` 的此类错误。[判定页面](../wai-quantum/docs/verdicts.md)列出了各个级别和规则。 ### 作业服务 ```python runnable svc = wq.Service() # $WAI_QUANTUM_SERVICE,或 https://wai.transaction.science/quantum-jobs/v1 job = svc.run(bell, shots=1000) # 提交并等待 rec = svc.submit(bell, shots=1000) # 或者:先提交,再 status / result / cancel job = svc.result(rec["id"]) svc.verify(job) # 在这里离线检查;把它的密钥与 svc.describe()["receipt_key"] 对比 ``` 客户端使用 `urllib`,并在每个请求的 `User-Agent` 中自称 `wai-quantum-python/`:作业服务的边缘节点会拒绝带有通用库默认用户代理的请求。拒绝、失败或被取消的作业,或无法连通的作业服务,都会引发 `wq.ServiceError`,附带 HTTP `status` 和作业服务的原话。 ### 笔记本 ```python runnable h = wq.history("start", {"source": src, "by": {"kind": "person"}, "at": 1}, key=key)["history"] program = "OPENQASM 3.0; qubit[2] q; ry(0.3) q[0]; h q[1];" wq.Circuit(bell) # 引擎的线路图;点击一个门:.selected 就是它的操作 wq.Circuit(src, history=h) # 每个门都标出出自谁之手(wq.history) wq.Result(job) # 以条形(或频率)显示的计数、方法、精度类别、回执的检查结果 wq.Receipt(job) # 签名者、摘要,以及引擎在这里计算出的 verified / INVALID 徽章 wq.Bloch(program, key=key) # 每个量子比特的 Bloch 矢量;拖动可旋转视角 ``` 每个小组件的前端都是一个小巧的普通 ES 模块(`python/wai_quantum/static/`)。若未安装 `widgets` 附加依赖,或前端没有小组件扩展,每个小组件都渲染为静态 HTML(`Circuit` 渲染为 SVG);`Job` 显示为其结果的卡片。每个小组件只显示引擎给出的内容,自己不计算任何东西:它的 `data` 就是引擎的回答。 `notebooks/` 中的文档笔记本在 CI 中运行,并须与其存储的输出一致:`getting-started`、`bell-receipt`(一个 Bell 对及其回执)、`equivalence`、`noisy-run`,以及 `service-job`(在为这次运行而启动的作业服务上执行的作业;没有这样的服务时,它的单元格会跳过)。 ## 测试 `ci/check.sh` 就是完整的检查,与 CI 的运行方式相同(`.github/workflows/wai-quantum-py.yml`,在 `linux` 和 `macos` 上):它从这份检出构建命令行和作业服务,用 maturin 构建 wheel,在一个全新的 venv 中用固定版本并附哈希的测试依赖(`tests/requirements.lock`)安装该 wheel,运行 pytest 并要求每个测试都执行,针对本地作业服务运行各笔记本,并在 Python 3.9 上只用该 wheel 运行 `tests/smoke.py`。这些测试要求这个包与其他入口保持一致:在这里运行的作业与命令行和作业服务的作业相同(各主机的时间及其关于能耗的说明除外),在这里封存的回执能通过 `wai-quantum verify` 验证,命令行的回执也能在这里验证,两者用同一个用户密钥签名,而小组件的前端会在 node 下以这个包构建的数据运行。 能耗:在这里的运行不计量;其结果会注明这一点(`energy.unmetered`),与命令行和浏览器标签页中的运行一样。作业服务会为每个作业的 worker 计量。 许可证:Apache-2.0。