基于码容量噪声建模的量子纠错
量子纠错(QEC)描述了一套用于检测和纠正量子计算机上量子比特发生错误的工具。本示例将介绍 CUDA-Q QEC 库如何处理量子纠错中最常见的两种对象:稳定子码和解码器。稳定子码是经典纠错中线性码的量子推广,后者使用奇偶校验来检测噪声比特上的错误。在量子纠错中,我们将在辅助量子比特上执行稳定子测量,以检查数据量子比特的奇偶性。这些稳定子测量是非破坏性的,因此允许我们在不破坏量子信息的前提下检查量子比特的相对奇偶性。
例如,如果我们将两个量子比特制备在态 Ψ=a∣00⟩+b∣11⟩ ,我们可能想要检查是否发生了比特翻转错误。我们可以测量稳定子 ZZ ,如果没有错误或偶数个错误,它将返回 0;但如果任一量子比特发生了翻转,它将返回 1。这就是我们如何在量子计算中执行奇偶校验的方法,而无需执行会破坏叠加态的破坏性测量。这些测量在物理上如何执行,可以在电路级噪声量子纠错示例中看到。
我们可以通过稳定子算符列表(如上文的 ZZ )或等价的奇偶校验矩阵来指定一个稳定子码。我们可以将奇偶校验矩阵的列视为可能发生的错误类型。在这种情况下,每个量子比特可能经历比特翻转 X 错误或相位翻转 Z 错误,因此奇偶校验矩阵将有 2N 列,其中 N 是数据量子比特的数量。每一行代表一个稳定子,或一个奇偶校验。取值为 0 或 1,其中 1 表示对应列参与该奇偶校验,0 表示不参与。因此,如果单个 X/Z 错误发生在某个量子比特上,奇偶校验矩阵中受支持的行将被触发。这被称为征状(syndrome),是一串 0 和 1,对应哪些奇偶校验被违反。稳定子码的一个特殊类别称为CSS(Calderbank-Shor-Steane)码,这意味着它们的奇偶校验矩阵的 X 分量和 Z 分量可以分离。
这就引出了解码。解码是求解以下问题的过程:给定一个征状,最可能的底层错误是什么?存在许多解码算法,但本示例将使用简单的单错误查找表。这意味着解码器将枚举每个单错误比特串对应的征状。然后给定一个征状,它将查找对应的错误串并返回结果。
最后,我们需要一种生成错误的方法。本示例将介绍码容量噪声模型,其中每个量子比特以某个概率 p 独立且同分布地发生 X 或 Z 错误。
CUDA-Q QEC 实现
以下是如何使用 CUDA-Q QEC 在 Python 和 C++ 中执行码容量噪声模型实验:
Python
import numpy as np import cudaq_qec as qec # 获取一个量子纠错码 steane = qec.get_code("steane") # 获取码的奇偶校验矩阵 # 可以获取完整的码,或者对于 CSS 码 # 仅获取 X 或 Z 分量 Hz = steane.get_parity_z() print(f"Hz:\n{Hz}") observable = steane.get_observables_z() print(f"observable:\n{observable}") # 错误概率 p = 0.1 # 获取解码器 decoder = qec.get_decoder("single_error_lut", Hz) # 执行码容量噪声模型数值实验 nShots = 10 nLogicalErrors = 0 for i in range(nShots): print(f"shot: {i}") # 生成噪声数据 data = qec.generate_random_bit_flips(Hz.shape[1], p) print(f"data: {data}") # 计算哪些征状被触发 syndrome = Hz @ data % 2 print(f"syndrome: {syndrome}") # 解码征状以预测数据发生了什么 results = decoder.decode(syndrome) convergence = results.converged result = results.result data_prediction = np.array(result, dtype=np.uint8) print(f"data_prediction: {data_prediction}") # 检查此预测是否翻转了可观测量 predicted_observable = observable @ data_prediction % 2 print(f"predicted_observable: {predicted_observable}") # 检查可观测量是否实际被翻转 actual_observable = observable @ data % 2 print(f"actual_observable: {actual_observable}") if (predicted_observable != actual_observable): nLogicalErrors += 1 # 统计解码器未能纠正错误的次数 print(f"{nLogicalErrors} logical errors in {nShots} shots\n") # 也可以通过单行代码生成征状和数据: syndromes, data = qec.sample_code_capacity(Hz, nShots, p) print("From sample function:") print("syndromes:\n", syndromes) print("data:\n", data)C++
// This example shows the primary cudaq::qec types: // decoder, code // // Compile and run with // nvq++ --enable-mlir --target=stim -lcudaq-qec code_capacity_noise.cpp // ./a.out #include <algorithm> #include <cmath> #include <random> #include "cudaq.h" #include "cudaq/qec/decoder.h" #include "cudaq/qec/experiments.h" int main() { auto steane = cudaq::qec::get_code("steane"); auto Hz = steane->get_parity_z(); std::vector<size_t> t_shape = Hz.shape(); std::cout << "Hz.shape():\n"; for (size_t elem : t_shape) std::cout << elem << " "; std::cout << "\n"; std::cout << "Hz:\n"; Hz.dump(); auto Lz = steane->get_observables_x(); std::cout << "Lz:\n"; Lz.dump(); double p = 0.2; size_t nShots = 5; auto lut_decoder = cudaq::qec::get_decoder("single_error_lut", Hz); std::cout << "nShots: " << nShots << "\n"; // May want a order-2 tensor of syndromes // access tensor by stride to write in an entire syndrome cudaqx::tensor<uint8_t> syndrome({Hz.shape()[0]}); int nErrors = 0; for (size_t shot = 0; shot < nShots; ++shot) { std::cout << "shot: " << shot << "\n"; auto shot_data = cudaq::qec::generate_random_bit_flips(Hz.shape()[1], p); std::cout << "shot data\n"; shot_data.dump(); auto observable_z_data = Lz.dot(shot_data); observable_z_data = observable_z_data % 2; std::cout << "Data Lz state:\n"; observable_z_data.dump(); auto syndrome = Hz.dot(shot_data); syndrome = syndrome % 2; std::cout << "syndrome:\n"; syndrome.dump(); auto result = lut_decoder->decode(syndrome); cudaqx::tensor<uint8_t> result_tensor; // result.result is a std::vector<float_t>, of soft information. We'll // convert this to hard information and store as a tensor<uint8_t>. cudaq::qec::convert_vec_soft_to_tensor_hard(result.result, result_tensor); std::cout << "decode result:\n"; result_tensor.dump(); // check observable result auto decoded_observable_z = Lz.dot(result_tensor); std::cout << "decoded observable:\n"; decoded_observable_z.dump(); // check how many observable operators were decoded correctly // observable_z_data == decoded_observable_z This maps onto element wise // addition (mod 2) auto observable_flips = decoded_observable_z + observable_z_data; observable_flips = observable_flips % 2; std::cout << "Logical errors:\n"; observable_flips.dump(); std::cout << "\n"; // shot counts as a observable error unless all observables are correct if (observable_flips.any()) { nErrors++; } } std::cout << "Total logical errors: " << nErrors << "\n"; // Full data gen in function call auto [syn, data] = cudaq::qec::sample_code_capacity(Hz, nShots, p); std::cout << "Numerical experiment:\n"; std::cout << "Data:\n"; data.dump(); std::cout << "Syn:\n"; syn.dump(); }代码解释
1. 量子纠错码类型
CUDA-Q QEC 的核心是
qec.code类型,它包含给定码的相关数据。特别地,这代表一组表示单个逻辑量子比特的量子比特集合。
这里我们获取最著名的量子纠错码之一——Steane 码,使用
qec.get_code函数。我们可以用
code.get_stabilizers()函数从码中获取稳定子。在本示例中,我们获取码的奇偶校验矩阵。由于 Steane 码是 CSS 码,我们可以仅提取奇偶校验矩阵的 Z 分量。
这里我们看到该矩阵有 3 行 7 列,这意味着有 7 个数据量子比特(7 种可能的单比特翻转错误)和 3 个 Z -稳定子(奇偶校验)。注意,Z 稳定子用于检测 X 型错误。
最后,我们获取码的逻辑 Z 可观测量。这将允许我们查看逻辑量子比特的 Z 可观测量是否发生翻转。
2. 解码器类型
可以通过
qec.get_decoder调用获取单错误查找表(LUT)解码器。传入奇偶校验矩阵为解码器提供将征状与底层错误机制关联所需的信息。
一旦解码器构建完成,调用
decoder.decode(syndrome)成员函数,返回给定征状的预测错误。
3. 噪声模型
为了生成噪声数据,我们调用
qec.generate_random_bit_flips(nBits, p),它将返回一个比特数组,其中每个比特以概率 p 被翻转为 1,以概率 1−p 保持为 0。由于我们使用的是 Z 奇偶校验矩阵 HZ ,我们希望模拟 7 个数据量子比特上的随机 X 错误。
4. 逻辑错误
一旦我们有了噪声数据,我们通过将噪声数据向量与奇偶校验矩阵相乘(模 2)来查看产生的征状。
从这个征状出发,我们查看解码器预测数据发生了什么错误。
要归类为逻辑错误,解码器不需要精确猜测数据发生了什么,而是需要判断逻辑可观测量是否发生翻转。
如果解码器成功猜中这一点,我们就纠正了量子错误。否则,我们就产生了逻辑错误。
5. 进一步自动化
虽然这个工作流程适合逐步观察,但
qec.sample_code_capacityAPI 提供了批量生成噪声数据及其对应征状的功能。
总结
CUDA-Q QEC 库为数值量子纠错实验提供了一个平台。qec.code可用于分析各种量子纠错码(库提供的或用户自定义的),配合各种解码器(库提供的或用户自定义的)。CUDA-Q QEC 库还提供工具来加速生成噪声数据和征状的自动化过程。
https://nvidia.github.io/cudaqx/examples_rst/qec/code_capacity_noise.html