部分振幅模拟器(Partial-Amplitude Simulator)¶
定义与概述¶
部分振幅模拟器(Partial-Amplitude Simulator)是一类量子计算的经典仿真器,它在模拟量子电路时,并不存储和计算整个量子态向量(state vector)的所有振幅(amplitude),也不仅仅像单振幅模拟器那样只计算一个特定状态的振幅,而是在任意时刻仅追踪一部分目标计算所需的量子态振幅。
换句话说:
- 全振幅模拟器:储存并更新所有 $2^n$ 个状态振幅(适合小规模 n)。
- 单振幅模拟器:只计算一个量子基态的振幅(内存需求极小,但信息量有限)。
- 部分振幅模拟器:在两者之间折中,只追踪一个子集的振幅集合,从而减少内存压力,同时保留足够信息来完成目标任务。
它的核心意义在于:
在保证任务需求的前提下,以可接受的内存消耗和计算时间,模拟规模更大的量子电路。
要解决的问题¶
为什么需要部分振幅模拟器?主要出于以下考虑:
内存瓶颈
- 全振幅模拟器需要存储长度为 $2^n$ 的复数数组。即便使用 16 字节的复数表示,30 个量子比特就需要 ~16 GB,40 个比特则需要 ~16 TB —— 对大多数研究者和实验室这是不可行的。
计算效率
- 单振幅模拟器虽然内存消耗小,但计算每一个振幅都需单独执行一次电路的演化,且无法直接得出全局性质(如态的稀疏分布)。
任务适配
- 在某些研究任务中,我们仅需一部分态的振幅(例如求期望值、概率分布的一个片段、惰性稀疏态的非零部分),没必要追踪整个量子态。这时,部分振幅模拟器能在内存和速度之间取得良好平衡。
核心原理¶
与状态向量表示法的区别¶
- 状态向量(State Vector)方法:完整存储 $|\psi\rangle = \sum_{i=0}^{2^n-1} \alpha_i |i\rangle$,每个基态 $|i\rangle$ 都有对应的复振幅 $\alpha_i$。
- 部分振幅方法:只存储目标任务涉及的基态子集 $\mathcal{S} \subset \{0,1,\dots,2^n-1\}$ 的 $\alpha_i$,并在逻辑和存储上优化其更新流程。
“部分振幅”的含义¶
部分振幅指的是量子态向量中被选择性跟踪的一组分量。
- 这组分量通常是根据任务需求预先确定的(例如,某些测量结果、某个子空间)。
- 在电路演化中,这些振幅需要更新——更新时可能要临时计算与它们直接相连的振幅(通过量子门作用连接)。
振幅管理与存储¶
- 稀疏存储:用键值表(例如哈希表)或稀疏向量结构,存储“索引 → 振幅”映射。参考稀疏状态向量。
- 局部更新:对于局域量子门(单比特或双比特),只会影响振幅索引的特定位组合,因此更新过程可局部化。
模拟过程示例¶
假设我们有 3 个量子比特,初态 $|000\rangle$,电路为:
- 对第 0 个量子比特施加 Hadamard(H 门)。
- 对第 (0,1) 比特施加 CNOT。
- 对第 2 个量子比特施加 H 门。
全振幅更新会得到长度 $2^3=8$ 的完整向量。但在部分振幅模拟中:
- 任务:只需$|000\rangle, |011\rangle, |100\rangle$ 这三个基态的概率。
- 初始:仅追踪 $\alpha_{000}$(=1.0)。
- H(0) 后:需要用到 $\alpha_{100}$ 来更新 $\alpha_{000}$ 与 $\alpha_{100}$。
- CNOT(0→1) 后:$|100\rangle \rightarrow |110\rangle$,因此若我们需要 $|110\rangle$ 的信息,才会计算它。
- H(2) 后:某些振幅会在 $|xx0\rangle \leftrightarrow |xx1\rangle$ 间混合,只针对涉及的索引进行更新。
通过按需生成与更新,避免了维护多余的 $2^3$(一般是 $2^n$)个振幅。
优势与局限性¶
优势¶
- 显著节省内存
- 只存需要的振幅,空间复杂度远低于 $O(2^n)$。
- 计算速度可控
- 对于稀疏态或部分测量任务,比全振幅方法快。
- 灵活性
- 易于针对特定指标(如期望值、部分概率分布)优化。
- 支持中等规模比特数
- 可以模拟到 40-50 比特甚至更高(取决于稀疏度和任务)。
局限性¶
- 不适合高度纠缠且稠密的态
- 如果几乎所有基态都非零,部分振幅退化为全振幅,性能优势消失。
- 更新时的依赖传递
- 有些门会引入大量新振幅,导致存储量快速膨胀。
- 不可直接获得完整态信息
- 需要的振幅必须显式指定,否则无法恢复全局态。
典型应用场景¶
- 变分量子算法(VQE、QAOA)
只需计算某些测量基上的期望值,不必得出完整态向量。 - 特定观测量的期望值计算
特别是在稀疏可观测量情形下,仅跟踪必要的振幅。 - 噪声模拟
对噪声作用下的部分态演化进行追踪。 - 大规模概率估计
只关心部分事件的概率。
工具与实现¶
在主流量子计算框架中:
- Qiskit
- 提供 QasmSimulator 的partial statevector 模式(与 Aer 程序结合),用户可通过
save_amplitudes指定振幅索引集合。
- 提供 QasmSimulator 的partial statevector 模式(与 Aer 程序结合),用户可通过
- Intel Quantum Simulator (IQS)
- 提供分布式部分振幅模拟器,支持在超算上并行存储和计算。
小结¶
部分振幅模拟器本质上是内存与计算的折中方案:它避免了全振幅模拟的指数级内存需求,又比单振幅模拟器保留更多全局信息。
它尤其适用于只需部分态信息、稀疏态模拟、大规模期望值评估等研究任务。在量子计算逐步走向数十乃至上百比特规模的过程中,部分振幅方法仍将是连接理论与硬件实验的重要辅助工具。
部分振幅模拟器 (Partial Amplitude Simulator) 原理详解¶
量子计算经典模拟的挑战¶
在深入部分振幅模拟器之前,我们先回顾一下经典的量子态向量模拟器(Statevector Simulator)是如何工作的,以及它所面临的挑战。
一个包含 $n$ 个量子比特的量子系统,其量子态由一个 $2^n$ 维的复数向量表示,称为量子态向量(Statevector)。这个向量中的每一个元素,称为一个振幅 (Amplitude),代表了测量时对应到某个计算基态(如 $|0110...\rangle$)的概率幅。
状态向量的表示:
$$|\psi\rangle = \sum_{x \in \{0,1\}^n} \alpha_x |x\rangle$$
其中,$|x\rangle$ 是计算基态,$\alpha_x$ 是对应的复数振幅,且 $\sum_{x} |\alpha_x|^2 = 1$。
量子门操作的模拟:
当一个量子门(如 Hadamard 门 $H$ 或 CNOT 门)作用于量子态时,实际上是对这个 $2^n$ 维的状态向量进行一个 $2^n \times 2^n$ 的酉矩阵乘法。
挑战:
- 内存占用 (Memory Footprint): 存储一个 $2^n$ 维的复数向量,即使每个复数只占 16 字节(双精度浮点数),对于 $n=30$ 的量子比特,就需要 $2^{30} \times 16$ 字节 $\approx 17$ GB 的内存。对于 $n=40$,则需要 $\approx 17$ TB 的内存,这远远超出了大多数单台计算机的内存容量。
- 计算复杂度 (Computational Complexity): 每次量子门操作都需要与 $2^n \times 2^n$ 的矩阵进行乘法,这需要 $O((2^n)^2)$ 的计算量,即 $O(4^n)$。尽管现代 HPC 集群可以通过并行计算来加速,但指数级的增长仍然使其在模拟较大规模系统时变得不可行。
部分振幅模拟器的核心思想¶
部分振幅模拟器的核心思想在于:在模拟过程中,并非所有 $2^n$ 个振幅都需要被完整地存储和更新。
它基于以下观察:
- 绝大多数量子门操作只影响状态向量的一小部分振幅。 例如,作用在第 $i$ 个量子比特上的单比特门(如 $X, Y, Z, H, S, T$ 门)只会影响那些在第 $i$ 个量子比特上处于 $|0\rangle$ 或 $|1\rangle$ 状态的基态所对应的振幅。这意味着,如果我们将量子比特的计算基态映射到其二进制表示,单比特门只会在这个表示中翻转一个比特,因此只影响一部分振幅。
- 多比特门(如 CNOT, CZ)虽然影响的振幅范围更广,但仍然可以根据控制和目标量子比特的状态来确定受影响的振幅集合。
部分振幅模拟器并不存储整个 $2^n$ 维的状态向量,而是只存储和更新那些“非零”或“可能发生变化”的振幅。 这种方法也常被称为 “稀疏状态表示” (Sparse State Representation) 或 “列表型模拟器” (List-based Simulator)。
实现原理与数据结构¶
部分振幅模拟器通常使用一种数据结构来存储非零振幅。最常见的一种是 “振幅列表” (Amplitude List)。
振幅列表的数据结构:
振幅列表通常存储为一系列 (索引, 振幅) 对。
- 索引 (Index): 一个整数,代表了对应的计算基态 $|x\rangle$ 的二进制表示。例如,对于 $n$ 个量子比特,索引 $i$ 可以唯一地映射到计算基态 $|i\rangle$。
- 振幅 (Amplitude): 对应的复数值 $\alpha_i$。
示例 (n=2):
考虑一个 $n=2$ 的量子态 $|\psi\rangle = \alpha_{00}|00\rangle + \alpha_{01}|01\rangle + \alpha_{10}|10\rangle + \alpha_{11}|11\rangle$。
一个完整的状态向量模拟会存储 $(\alpha_{00}, \alpha_{01}, \alpha_{10}, \alpha_{11})$。
一个部分振幅模拟器可能只存储:
amplitude_list = [(0, alpha_00), (1, alpha_01), (2, alpha_10), (3, alpha_11)]
这里,索引 0 对应 $|00\rangle$,索引 1 对应 $|01\rangle$,索引 2 对应 $|10\rangle$,索引 3 对应 $|11\rangle$。
当量子门作用在量子态上时:
确定受影响的振幅集合: 对于一个给定的量子门(例如,作用在量子比特 $q$ 上的 $U$ 门),模拟器会计算出哪些基态 $|x\rangle$ 的振幅 $\alpha_x$ 会被改变。
- 单比特门 (作用在第 $q$ 个比特):
- 如果 $|x\rangle$ 的第 $q$ 个比特是 0,则 $U$ 门作用在 $|x\rangle$ 的一部分,可能会将 $|x\rangle$ 映射到另一个基态 $|x'\rangle$(如果 $U$ 门不是单位矩阵),或者改变其振幅。
- 如果 $|x\rangle$ 的第 $q$ 个比特是 1,则 $U$ 门作用在 $|x\rangle$ 的另一部分,可能会将 $|x\rangle$ 映射到另一个基态 $|x''\rangle$(如果 $U$ 门不是单位矩阵),或者改变其振幅。
- 多比特门 (例如 CNOT,控制在 $c$,目标在 $t$):
- CNOT 门只改变那些控制比特 $c$ 为 1 的基态。对于这些基态 $|x\rangle$,其目标比特 $t$ 会被翻转。例如,如果 $|x\rangle = |x_n...x_c...x_t...x_1\rangle$ 且 $x_c=1$,那么 CNOT(|x>) = $|x_n...x_c...(1-x_t)...x_1\rangle$。这会导致 $|x\rangle$ 的振幅 $\alpha_x$ 移动到新的索引 $x'$ 对应的振幅 $\alpha_{x'}$。
- 单比特门 (作用在第 $q$ 个比特):
更新振幅列表:
- 振幅移动 (Amplitude Migration): 如果一个操作将基态 $|x\rangle$ 的振幅 $\alpha_x$ 移动到基态 $|x'\rangle$,并且 $|x'\rangle$ 原来不在振幅列表中,那么需要将 $(\alpha_x, x')$ 加入列表。如果 $|x'\rangle$ 已经在列表中,则更新其振幅。
- 振幅消失/新增: 如果一个操作导致某个振幅 $\alpha_x$ 变为零,并且 $|x\rangle$ 在列表中,那么可以将其从列表中移除,以节省内存。反之,如果某个基态 $|y\rangle$ 原来振幅为零,但由于门操作而获得一个非零振幅 $\alpha_y$,那么需要将其加入列表。
- 振幅组合 (Amplitude Combination): 有时,多个不同的基态由于门操作后会映射到同一个目标基态。此时,它们的振幅需要合并(相加)到该目标基态的振幅上。
优化技术与挑战¶
部分振幅模拟器并非没有代价。虽然它极大地降低了内存需求,但在某些情况下,其计算效率可能不如完整的状态向量模拟。
关键优化点:
高效的索引查找与更新:
- 使用哈希表 (Hash Table) 或其他高效的数据结构(如平衡二叉搜索树)来存储振幅列表,可以实现 $O(\log N_{nz})$ 或平均 $O(1)$ 的查找和插入/删除操作,其中 $N_{nz}$ 是当前非零振幅的数量。
- 位串操作: 利用高效的位串操作(如按位与
&、按位或|、按位异或^、左移<<、右移>>)来计算受影响的基态索引,这是模拟器性能的关键。
受影响振幅的计算:
- 基于门类型的遍历: 对每种量子门,都有专门的算法来计算受影响的振幅集合。例如,对于作用在第 $q$ 个比特上的 $U$ 门,可以遍历所有在振幅列表中的 $(index, amplitude)$ 对。如果 $index$ 的第 $q$ 个比特是 0,则 $U$ 作用在 $|index\rangle$ 的上半部分;如果第 $q$ 个比特是 1,则 $U$ 作用在 $|index\rangle$ 的下半部分。
- 预计算受影响的基态: 更高级的优化是在执行门操作前,一次性计算出所有受影响的基态,然后批量更新。
并行化:
- 当受影响的振幅数量很大时,可以将更新操作分配到多个 CPU 核心或 GPU 上进行并行处理。这需要仔细处理数据同步和锁机制。
面临的挑战:
- 动态数据结构开销: 频繁地添加、删除和查找列表中的元素,相比于固定大小的内存块,会带来额外的计算开销。
- 振幅密度的增长: 即使我们从一个稀疏状态开始,随着量子算法的执行,越来越多的振幅可能会变成非零,导致振幅列表变得越来越密集。当振幅密度接近 1 时,部分振幅模拟器的优势就会大大减弱,甚至可能比状态向量模拟器更慢。
- 门操作的复杂性: 某些复杂的量子门(例如,需要分解为多个基本门的操作)可能会影响到非常多的振幅,使得计算受影响的振幅集合变得复杂。
- 测量操作: 测量操作需要对所有振幅进行概率累加,这仍然需要访问所有非零振幅。
适用于何种场景?¶
部分振幅模拟器特别适合以下场景:
- 早期量子算法的模拟: 许多著名的量子算法,如 Shor 算法或 Grover 算法,在初始阶段可能倾向于稀疏的量子态。
- 特定量子硬件的模拟: 模拟那些具有特定连接性或局部操作的量子系统时,部分振幅模拟器也可能表现出色。
- 内存受限的环境: 当内存是主要瓶颈时,即使计算速度稍慢,部分振幅模拟器也可能是唯一的选择。
与其他模拟器类型的对比¶
| 模拟器类型 | 核心思想 | 内存需求 | 计算复杂度 (每门) | 适用场景 |
|---|---|---|---|---|
| 状态向量模拟器 | 存储和更新整个 $2^n$ 维的复数向量 | $O(2^n)$ | $O((2^n)^2)$ | 小型量子系统 (<25-30 量子比特),需要高精度和速度的验证 |
| 部分振幅模拟器 | 只存储和更新非零或可能变化的振幅 | $O(N_{nz})$ (通常 $N_{nz} \ll 2^n$) | $O(N_{nz} \times gate_{cost})$ | 中等规模量子系统,尤其是初始阶段振幅稀疏的情况;内存受限的环境 |
| 量子线路抽样器 | 不显式计算完整状态向量,直接模拟测量结果 | $O(n)$ (如采样结果) | 依赖于采样算法 | 只需要模拟最终测量结果,不需要访问中间状态;适用于特定概率分布的采样 |
| 张量网络模拟器 | 将量子态表示为一系列连接的张量 | 依赖于张量网络结构 | 依赖于张量收缩 | 具有特定结构的量子态(如短程纠缠),通常对量子比特的连接性敏感 |
Python/C++ 实现中的考量¶
在 Python/C++ 中实现部分振幅模拟器,需要考虑:
Python:
- 可以使用
dict或collections.defaultdict来实现振幅列表。 numpy库可以用于高效的数值计算,但需要将其与动态的字典结构结合。- 对于性能敏感的部分,可以考虑使用
Cython或直接调用 C++ 库。 Qiskit等量子计算框架提供了实现好的部分振幅模拟器(如AerSimulator中的method='statevector'但其内部可以启用稀疏选项)。
- 可以使用
C++:
- 可以使用
std::unordered_map(哈希表) 或std::map(红黑树) 来实现振幅列表。 - 需要手动管理复数运算,并为各种量子门实现高效的位操作逻辑。
- 可以利用多线程 (
std::thread, OpenMP) 或 GPU 加速 (CUDA) 来并行化更新过程。 - 对于底层的数学库,可以使用 Eigen 或 Armadillo 等。
- 可以使用
示例性伪代码 (Pythonic 风格):
import cmath # For complex numbers
import collections
class PartialAmplitudeSimulator:
def __init__(self, num_qubits):
self.num_qubits = num_qubits
# amplitude_list stores {index: amplitude}
self.amplitude_list = {0: 1.0 + 0.0j} # Start with |00...0>
def apply_gate(self, gate_matrix, target_qubits):
# Simplified example for a single qubit gate
# In reality, target_qubits would be a list, and gate_matrix might be larger
new_amplitude_list = collections.defaultdict(complex)
# Iterate over existing non-zero amplitudes
for current_index, current_amplitude in list(self.amplitude_list.items()):
# --- This is the core logic where gate application is determined ---
# For a single qubit gate at target_qubit_idx:
# We need to figure out how the matrix transforms the basis states.
# A single qubit gate U on qubit q affects basis states differently
# based on the q-th bit of the index.
# Example: U gate on qubit q
q_bit_mask = 1 << target_qubits[0] # Assuming single qubit for simplicity
# Case 1: q-th bit of current_index is 0
if not (current_index & q_bit_mask):
# Simulate gate on |...0> component
# Let's say U = [[a, b], [c, d]]
# U|0> = a|0> + c|1>
# The original state |current_index> now contributes to:
# - index (current_index) with amplitude current_amplitude * a
# - index (current_index | q_bit_mask) with amplitude current_amplitude * c
# Example with Hadamard H = [[1, 1], [1, -1]]
# For U=H on q:
new_amplitude_list[current_index] += current_amplitude * 0.5 # H|0> contributes 0.5|0>
new_amplitude_list[current_index | q_bit_mask] += current_amplitude * 0.5 # H|0> contributes 0.5|1>
# Case 2: q-th bit of current_index is 1
else:
# Simulate gate on |...1> component
# U|1> = b|0> + d|1>
# The original state |current_index> now contributes to:
# - index (current_index & ~q_bit_mask) with amplitude current_amplitude * b
# - index (current_index) with amplitude current_amplitude * d
# Example with Hadamard H = [[1, 1], [1, -1]]
# For U=H on q:
new_amplitude_list[current_index & ~q_bit_mask] += current_amplitude * 0.5 # H|1> contributes 0.5|0>
new_amplitude_list[current_index] += current_amplitude * -0.5 # H|1> contributes -0.5|1>
# --- More complex gates like CNOT require more sophisticated index manipulation ---
# For CNOT(control_qubit, target_qubit):
# If control_qubit of current_index is 1:
# flip the target_qubit of current_index to get next_index
# new_amplitude_list[next_index] += current_amplitude
# Else:
# new_amplitude_list[current_index] += current_amplitude
# Remove zero amplitudes to maintain sparsity (optional but good practice)
self.amplitude_list = {idx: amp for idx, amp in new_amplitude_list.items() if abs(amp) > 1e-12} # Threshold for floating point noise
def measure(self, qubit_index):
# Simplified measure implementation
# Needs to calculate probability of |0> and |1> for the given qubit across all states
prob_0 = 0
prob_1 = 0
q_bit_mask = 1 << qubit_index
for index, amplitude in self.amplitude_list.items():
if index & q_bit_mask: # If the qubit is in state |1>
prob_1 += abs(amplitude)**2
else: # If the qubit is in state |0>
prob_0 += abs(amplitude)**2
# For simplicity, assume a random outcome based on probabilities
import random
if random.random() < prob_0:
print(f"Measured qubit {qubit_index}: 0")
# Collapse the state: zero out all amplitudes where qubit is 1
self.amplitude_list = {idx: amp for idx, amp in self.amplitude_list.items() if not (idx & q_bit_mask)}
else:
print(f"Measured qubit {qubit_index}: 1")
# Collapse the state: zero out all amplitudes where qubit is 0
self.amplitude_list = {idx: amp for idx, amp in self.amplitude_list.items() if (idx & q_bit_mask)}
# Re-normalize after collapse (important for correct probabilities in subsequent steps)
total_amplitude_sq = sum(abs(amp)**2 for amp in self.amplitude_list.values())
scale_factor = 1.0 / cmath.sqrt(total_amplitude_sq)
self.amplitude_list = {idx: amp * scale_factor for idx, amp in self.amplitude_list.items()}
def get_state(self):
# Returns the full state vector if needed, padding with zeros
state_vector = [0.0 + 0.0j] * (1 << self.num_qubits)
for idx, amp in self.amplitude_list.items():
state_vector[idx] = amp
return state_vector
# Example usage:
# simulator = PartialAmplitudeSimulator(3)
# # Apply gates...
# simulator.apply_gate(Hadamard_matrix, [0])
# simulator.apply_gate(CNOT_matrix, [0, 1])
# # Measure...
# simulator.measure(1)
结论¶
部分振幅模拟器是经典 HPC 在模拟量子计算中的一项关键技术。它通过利用量子算法中量子态的稀疏性,极大地扩展了我们能够模拟的量子比特数量,尤其是在内存受限的情况下。通过精心设计的数据结构和高效的位操作,它能够在合理的时间内对中等规模的量子系统进行仿真。然而,我们也必须认识到其局限性:当量子态变得稠密时,其性能可能会下降。因此,选择何种模拟器取决于具体的量子算法、所需的模拟规模以及可用的计算资源。作为一名软件工程师,理解并能够实现这些不同的模拟技术,是我们构建强大量子软件生态系统的重要一环。
好的,作为量子计算编译与模拟框架的核心开发人员,我将深入剖析部分振幅模拟器(Partial Amplitude Simulator, PAS)在计算可观察量期望值和特定计算基态概率这两个核心功能上的实现原理。我们将聚焦于算法细节和数学表达,力求专业和深入。
部分振幅模拟器(PAS)核心功能实现原理剖析¶
部分振幅模拟器(PAS)旨在高效地从量子态中提取特定信息,而无需显式地模拟整个态向量。这在处理大规模量子系统时尤其重要,因为完整的态向量会占用指数级的内存。
计算可观察量的期望值¶
在量子计算中,一个可观察量(Observable)通常由一个厄米算符(Hermitian Operator) $\hat{O}$ 表示。系统的期望值(Expectation Value) $\langle \hat{O} \rangle$ 表示在给定量子态 $|\psi\rangle$ 下测量该可观察量时,其平均值。
PAS通过采样(Sampling)技术来估计期望值,而不是直接计算。其核心思想是:如果我们可以高效地从量子态 $|\psi\rangle$ 中采样出计算基态 $|b\rangle$,那么可观察量的期望值可以表示为:
$$ \langle \hat{O} \rangle = \sum_{b \in \{0,1\}^n} \text{Tr}(\hat{O} \hat{\rho}_b) = \sum_{b \in \{0,1\}^n} \langle b | \hat{O} | b \rangle P(b) $$
其中,$n$ 是量子比特的数量,$|b\rangle$ 是一个 $n$ 量子比特的计算基态,$\hat{\rho}_b = |b\rangle \langle b|$ 是该基态的密度算符,$P(b) = |\langle b | \psi \rangle|^2$ 是系统处于基态 $|b\rangle$ 的概率。
PAS 的优势在于,它能够选择性地(partially)生成或估计与目标可观察量相关的振幅,而无需计算所有振幅。
基于采样的期望值估计¶
一种直接的基于采样的策略是:
- 采样基态: 从量子态 $|\psi\rangle$ 中采样一系列计算基态 $|b^{(1)}\rangle, |b^{(2)}\rangle, \dots, |b^{(M)}\rangle$。
- 计算局部期望值: 对于每个采样到的基态 $|b^{(i)}\rangle$,计算 $\langle b^{(i)} | \hat{O} | b^{(i)} \rangle$。
- 平均: 期望值的估计值为: $$ \langle \hat{O} \rangle \approx \frac{1}{M} \sum_{i=1}^{M} \langle b^{(i)} | \hat{O} | b^{(i)} \rangle $$
这种方法的关键在于如何从 $|\psi\rangle$ 中高效地采样。如果 $|\psi\rangle$ 是通过一系列量子门作用于初始态(例如 $|0\rangle^{\otimes n}$)得到的,即 $|\psi\rangle = U|0\rangle^{\otimes n}$,那么采样 $|b\rangle$ 的概率就是 $|\langle b | U | 0 \rangle|^2$。
实现细节:
- 采样过程: PAS 需要模拟器能够执行量子态的采样。这通常通过在模拟量子电路的末端添加测量(Measurement)操作来实现。当对所有量子比特进行测量时,得到的是一个计算基态 $|b\rangle$,其出现的概率正是 $|\langle b | \psi \rangle|^2$。
- 可观察量算符的处理: 对于一个给定的可观察量 $\hat{O}$,它通常可以表示为泡利算符的线性组合(Pauli decomposition): $$ \hat{O} = \sum_j c_j \hat{P}_j $$ 其中,$c_j$ 是复系数,$\hat{P}_j$ 是泡利算符(如 $I, X, Y, Z$ 的张量积)。 则期望值为: $$ \langle \hat{O} \rangle = \sum_j c_j \langle \hat{P}_j \rangle $$ 因此,问题转化为如何估计单个泡利算符的期望值。
估计单个泡利算符的期望值¶
考虑估计单个泡利算符 $\hat{P}$(例如 $Z_0 \otimes X_1 \otimes \dots$)的期望值 $\langle \hat{P} \rangle$ 在量子态 $|\psi\rangle$ 下。
$$ \langle \hat{P} \rangle = \sum_{b \in \{0,1\}^n} \langle b | \hat{P} | b \rangle P(b) $$
$\langle b | \hat{P} | b \rangle$ 的计算: 泡利算符 $\hat{P}$ 作用在计算基态 $|b\rangle$ 上,其结果是 $\hat{P}|b\rangle$。由于 $\hat{P}$ 也是一个由泡利算符组成的张量积,其作用在 $|b\rangle$ 上可以分解为对每个量子比特上对应泡利算符的独立作用。 例如,如果 $\hat{P} = P_0 \otimes P_1 \otimes \dots \otimes P_{n-1}$,其中 $P_k \in \{I, X, Y, Z\}$。 那么 $\langle b | \hat{P} | b \rangle = \langle b_0 | P_0 | b_0 \rangle \otimes \langle b_1 | P_1 | b_1 \rangle \otimes \dots \otimes \langle b_{n-1} | P_{n-1} | b_{n-1} \rangle$。 这里的 $\langle b_k | P_k | b_k \rangle$ 是一个数值(-1, 0, 1)。
- 对于 $P_k = I$,$\langle b_k | I | b_k \rangle = 1$。
- 对于 $P_k = Z$,$\langle b_k | Z | b_k \rangle = (-1)^{b_k}$,其中 $b_k$ 是 $|b\rangle$ 中第 $k$ 个比特的值(0 或 1)。
- 对于 $P_k = X$ 或 $P_k = Y$,如果 $P_k$ 作用在 $|b_k\rangle$ 上,并且 $P_k \neq \sigma_z$(即 $P_k$ 不是 Z 算符),那么 $\langle b_k | P_k | b_k \rangle = 0$。这是因为 $|b_k\rangle$ 是 Z 算符的本征态,而 X 和 Y 算符在 Z 算符的本征态上不产生零期望值,但它们会改变基态,因此 $\langle b_k | P_k | b_k \rangle$ 本身是 0。
因此,$\langle b | \hat{P} | b \rangle$ 仅在 $\hat{P}$ 的所有分量都是 Z 算符时才有非零值(即 $\hat{P}$ 是 $\pm Z^{\otimes n}$ 的一个乘积)。 在这种情况下,$\langle b | \hat{P} | b \rangle = \prod_{k=0}^{n-1} \langle b_k | P_k | b_k \rangle = \prod_{k \in \text{support}(P)} (-1)^{b_k}$,其中 $\text{support}(P)$ 是 $\hat{P}$ 中非单位泡利算符的那些量子比特的索引集合。
PAS 的高级采样策略 (例如,基于量子硬件的采样):
对于一个可观察量 $\hat{O} = \sum_j c_j \hat{P}_j$,我们只需要估计每个 $\langle \hat{P}_j \rangle$。 对于一个特定的泡利算符 $\hat{P}_j$,我们执行以下步骤:
量子变换: 应用一系列量子门 $V$ 到量子态 $|\psi\rangle = U|0\rangle^{\otimes n}$,使得测量结果的概率分布与 $\langle \hat{P}_j \rangle$ 的计算相关。
- 对于 $\hat{P}_j$ 是 Z 算符的张量积(如 $Z_0, Z_1, Z_0 \otimes Z_1$ 等): 期望值 $\langle Z \rangle = P(0) - P(1)$。我们可以通过测量在 Z 基(计算基)下的期望值来计算。
- 对于 $\hat{P}_j$ 包含 X 或 Y 算符: 这需要一个基变换(Basis Transformation)。例如,要测量 $\langle X \rangle$,我们需要将量子态变换到 X 的本征基($+1$ 态 $|+\rangle = \frac{1}{\sqrt{2}}(|0\rangle + |1\rangle)$ 和 $-1$ 态 $|-\rangle = \frac{1}{\sqrt{2}}(|0\rangle - |1\rangle)$)下进行测量。这可以通过在每个量子比特上应用 Hadamard 门 ($H$) 来实现,因为 $HZH = X$ 且 $HZH| \pm \rangle = \pm | \pm \rangle$。
- 如果 $\hat{P}_j = X_k \otimes \dots$, 则在量子电路末端对第 $k$ 个量子比特应用 $H$ 门。
- 如果 $\hat{P}_j = Y_k \otimes \dots$, 则在量子电路末端对第 $k$ 个量子比特应用 $S^\dagger H$ 门(或 $H S$ 门,取决于约定)。
- 对所有分量进行变换: 如果 $\hat{P}_j = P_0 \otimes P_1 \otimes \dots \otimes P_{n-1}$,则对所有 $k$ 使得 $P_k \in \{X, Y\}$ 的量子比特应用相应的基变换门。
采样 (测量): 在变换后的量子态上执行计算基测量。
- 如果 $\hat{P}_j$ 是 Z 算符的张量积,测量结果的概率分布直接反映了 $\langle \hat{P}_j \rangle$ 的符号。
- 如果 $\hat{P}_j$ 包含 X 或 Y 算符,通过测量基变换后的量子比特(此时它们处于 X 或 Y 的本征基中),我们得到一个计算基测量结果 $b = (b_0, b_1, \dots, b_{n-1})$。
- 对于一个泡利算符 $\hat{P}_j$,其期望值可以表示为: $$ \langle \hat{P}_j \rangle = \sum_{b \in \{0,1\}^n} \left(\prod_{k: P_k \neq I} \text{sign}(b_k \text{ in } P_k \text{'s eigenbasis}) \right) P(b|\text{transformed state}) $$ 其中 $\text{sign}(b_k \text{ in } P_k \text{'s eigenbasis})$ 取决于测量的计算基比特值 $b_k$ 在 $P_k$ 的本征基中的对应值。例如,对于 X 算符,$|+\rangle$ 映射到 $|0\rangle$(+1 贡献),$|-\rangle$ 映射到 $|1\rangle$(-1 贡献)。
- 更直接的采样估计: 对于一个特定的泡利算符 $\hat{P}_j$(假设 $\hat{P}_j = \bigotimes_k P_k$),我们可以在量子态 $|\psi\rangle$ 上应用相应的基变换门 $V_j$(例如,对 X 作用的量子比特应用 $H$)。然后,我们从变换后的态 $V_j |\psi\rangle$ 中采样 $M$ 个计算基态 $|b^{(1)}\rangle, \dots, |b^{(M)}\rangle$。
则 $\langle \hat{P}_j \rangle$ 的估计为:
$$
\langle \hat{P}_j \rangle \approx \frac{1}{M} \sum_{i=1}^M \prod_{k=0}^{n-1} \langle b^{(i)}_k | P_k | b^{(i)}_k \rangle
$$
其中 $\langle b^{(i)}_k | P_k | b^{(i)}_k \rangle$ 是第 $i$ 个采样结果中第 $k$ 个量子比特的测量值,其计算方式为:
- 如果 $P_k = I$, 值为 1。
- 如果 $P_k = Z$, 值为 $(-1)^{b^{(i)}_k}$。
- 如果 $P_k = X$, 值为 $(-1)^{b^{(i)}_k}$ (因为 $H|0\rangle \to |+\rangle$ 对应 +1, $H|1\rangle \to |-\rangle$ 对应 -1)。
- 如果 $P_k = Y$, 值为 $(-1)^{b^{(i)}_k}$ (对于 $Y$ 算符,通过 $S^\dagger H$ 变换,其本征态 $|+i\rangle, |-i\rangle$ 分别映射到 $|0\rangle, |1\rangle$ 态,因此测量值同样是 $(-1)^{b^{(i)}_k}$,但需要注意的是,这里的 $(-1)^{b^{(i)}_k}$ 的解释是根据 $Y$ 算符的本征值和对应基态映射的符号)。
组合: 将每个泡利分量的期望值加权求和: $$ \langle \hat{O} \rangle = \sum_j c_j \langle \hat{P}_j \rangle_{\text{estimated}} $$
PAS 的优势体现在:
- 不直接计算或存储整个态向量: PAS 避免了存储 $2^n$ 个复数振幅。
- 仅模拟所需的计算: 对于计算期望值,它只需要模拟对量子态进行分组(例如,泡利张量积的分解)并执行采样。
- 张量网络表示: 如果量子态 $|\psi\rangle$ 可以用张量网络(如 MPS, PEPS)表示,PAS 可以利用张量网络的收缩算法来高效地计算所需的期望值。例如,对于 MPS 表示的态 $|\psi\rangle$, 期望值 $\langle \hat{P} \rangle$ 的计算可以通过在张量网络中“穿透”泡利算符 $\hat{P}$ 来完成,这涉及张量网络的收缩。PAS 的核心在于优化这个收缩过程,使其只计算与 $\hat{P}$ 相关的部分。
计算系统处于某个特定计算基态的概率¶
我们现在关注如何计算系统处于一个特定计算基态 $|b\rangle$ 的概率,即 $P(b) = |\langle b | \psi \rangle|^2$。
直接采样与后处理¶
最直观的方法是:
- 采样: 从量子态 $|\psi\rangle$ 中进行 $M$ 次独立采样,得到一系列计算基态 $|b^{(1)}\rangle, |b^{(2)}\rangle, \dots, |b^{(M)}\rangle$。
- 计数: 统计采样结果中等于目标基态 $|b\rangle$ 的次数 $N_b$。
- 估计概率: 概率 $P(b)$ 的估计值为: $$ P(b) \approx \frac{N_b}{M} $$
实现细节:
- 采样过程: 在模拟器中,这对应于执行模拟的量子电路,并在最后对所有量子比特进行测量。重复执行 $M$ 次模拟和测量,即可获得 $M$ 个采样结果。
- PAS 的独特之处: PAS 不仅限于直接模拟整个电路并测量。它可以采用更高级的技术来放大或聚焦于目标基态 $|b\rangle$ 的振幅。
部分振幅估计 (Partial Amplitude Estimation)¶
PAS 的核心设计理念之一是部分振幅估计,它允许我们估计特定振幅的模平方(即概率)。
考虑一个量子态 $|\psi\rangle = \sum_{x} \alpha_x |x\rangle$。我们要计算 $P(b) = |\alpha_b|^2$。
PAS 可以通过以下步骤来估计 $P(b)$:
构造“目标”算符: 构造一个算符,该算符作用在量子态上时,会放大目标基态 $|b\rangle$ 的振幅,同时抑制其他基态的振幅。一个常见的策略是使用一个相位估计算法(Phase Estimation Algorithm, PEA)的变种,或者更直接地,利用振幅放大(Amplitude Amplification)的思想。
使用振幅放大思想:
- 初始态: 目标态 $|\psi\rangle = U|0\rangle^{\otimes n}$。
- 目标标记: 设想一个Oracle $O_b$,它作用在量子态 $|x\rangle$ 上,当 $|x\rangle = |b\rangle$ 时,施加一个负号(或一个相位),例如 $O_b |x\rangle = (-1)^{\delta_{x,b}} |x\rangle$。
- 扩散算符: 经典的扩散算符 $D = 2|\psi\rangle\langle\psi| - I$。
- 迭代: 重复应用 $G = D O_b$ 将目标振幅 $| \alpha_b |$ 放大。 然而,直接构造 $O_b$ 需要能够识别 $|b\rangle$ 并对其应用相位,这通常需要 $|\psi\rangle$ 的显式表示。PAS 的方法是近似或通过采样实现类似效果。
基于采样和条件概率的方法 (更符合 PAS 的思路): PAS 的一个关键优势在于,它可以直接通过采样来估计概率,而不需要显式地放大振幅。
- 构造采样电路: 模拟器会构建一个量子电路,该电路在模拟完 $| \psi \rangle = U|0\rangle^{\otimes n}$ 的演化后,不对所有量子比特进行测量,或者通过某种方式过滤测量结果,使得我们能够估计 $|\langle b | \psi \rangle|^2$。
- 使用“条件”采样: 假设我们有能力执行一个操作,该操作能够“选择性地”(probabilistically)保留包含 $|b\rangle$ 的部分,或者将 $|b\rangle$ 的振幅“投影”到某个可观测的量上。
- 关键技术:振幅估计器(Amplitude Estimator)/ 采样放大器(Sampler Amplifier): PAS 可以利用一些更精密的采样技术,例如 GKP 编码、基于连续变量(CVQC)或离散变量(DVQC)的模拟器,或者基于量子信号处理的技术。
具体的 PAS 方法 (基于振幅放大的变种):
考虑量子态 $|\psi\rangle = \sum_x \alpha_x |x\rangle$。我们想计算 $|\alpha_b|^2$。
- 准备一个辅助量子比特: 引入一个辅助量子比特 $|a\rangle$。
- 构造受控操作: 构造一个操作 $C_b$,它能够识别目标状态 $|b\rangle$。这通常不是通过显式的 $|\psi\rangle$ 表示,而是通过能够“检查”量子比特是否处于 $|b\rangle$ 的状态(通过某种测量或纠缠)来实现。
- 振幅估计电路:
- 初始化辅助量子比特 $|a\rangle$ 为 $|-\rangle = \frac{1}{\sqrt{2}}(|0\rangle - |1\rangle)$。
- 应用一个量子变换 $V$ 使量子态变为 $|\phi\rangle = \frac{1}{\sqrt{2}}|\psi\rangle \otimes |0\rangle + \frac{1}{\sqrt{2}}|\psi\rangle \otimes |1\rangle$ (这是一个简单的例子,实际的 V 会更复杂)。
- 应用一个“Oracle” $O_b$:$O_b |\psi\rangle |a\rangle = (-1)^{\delta_{x,b}} |\psi\rangle |a\rangle$ (如果 $|x\rangle$ 是计算基态)。
- 应用一个“扩散”算符 $D$: $D$ 可以是 $2|\psi\rangle\langle\psi| \otimes I - I \otimes I$ 的变种,但 PAS 的目标是避免显式计算 $|\psi\rangle\langle\psi|$。
- PAS 的实际实现: PAS 模拟器会构造一个电路,该电路能够近似地实现振幅估计。核心是,它可以通过采样来估计 $|\langle b | \psi \rangle|^2$。
- 采样策略: PAS 模拟器通过执行以下步骤来估计 $P(b)$:
- 模拟 $U$ 作用于 $|0\rangle^{\otimes n}$ 得到 $|\psi\rangle$.
- 使用一个特殊的“测量”或“采样”过程,该过程旨在估算 $|\langle b | \psi \rangle|^2$。
- 一种有效的 PAS 方法是基于“量子计数”(Quantum Counting)的变种:通过对 $|\psi\rangle$ 的演化电路执行多次采样,并对采样结果应用特定的后处理,来估计 $|\langle b | \psi \rangle|^2$。
- 直接采样估计: 最简单直接的方式就是多次采样 $|\psi\rangle$,然后计算采样到 $|b\rangle$ 的频率。PAS 模拟器只是提供了一个框架来执行这样的采样。
- 基于张量网络(如果适用): 如果 $|\psi\rangle$ 可以表示为张量网络,例如 MPS。计算 $|\langle b | \psi \rangle|^2$ 可以表示为:
$$
|\langle b | \psi \rangle|^2 = \sum_{b'} |\langle b | \text{MPS}_b' \rangle|^2
$$
其中 $\text{MPS}_b'$ 是态向量的 MPS 表示。计算 $|\langle b | \text{MPS} \rangle|^2$ 涉及到在张量网络中“插入” $|b\rangle \langle b|$ 算符并进行收缩。PAS 可以优化这个收缩过程,使其只计算目标基态 $|b\rangle$ 的贡献。
- MPO 表示: 状态 $|\psi\rangle$ 可以用 MPS 表示,算符 $|b\rangle\langle b|$ 可以用 MPO (Matrix Product Operator) 表示。计算 $|\langle b | \psi \rangle|^2$ 就是计算 $\text{Tr}(\text{MPS} \cdot \text{MPO}_{|b\rangle\langle b|})$。PAS 的核心在于高效地计算这个张量网络收缩。
代码示例(概念性,模拟采样的 Python 代码):
import numpy as np
# 假设我们有一个模拟器,能够从量子态生成采样
class QuantumSimulator:
def __init__(self, num_qubits):
self.num_qubits = num_qubits
self.statevector = np.zeros(2**num_qubits, dtype=complex)
self.statevector[0] = 1.0 # 初始态 |0...0>
def apply_gate(self, gate_matrix, target_qubits):
# 这是一个简化的模拟,实际应用门操作非常复杂
# 实际PAS模拟器会更高效地处理门操作
pass
def simulate_circuit(self, circuit):
# circuit 是由一系列门操作组成的列表
# 模拟电路演化,更新 self.statevector
# ... 实际模拟逻辑 ...
pass
def sample(self, num_samples):
# 从当前的 statevector 中采样计算基态
probabilities = np.abs(self.statevector)**2
# 归一化概率,防止数值误差
probabilities /= np.sum(probabilities)
samples = []
for _ in range(num_samples):
# 随机选择一个基态,根据其概率
sampled_index = np.random.choice(2**self.num_qubits, p=probabilities)
# 将索引转换为二进制字符串(计算基态表示)
binary_representation = bin(sampled_index)[2:].zfill(self.num_qubits)
samples.append(binary_representation)
return samples
# --- 计算特定计算基态概率 ---
def estimate_basis_state_probability(simulator: QuantumSimulator, target_basis_state: str, num_samples: int):
"""
使用采样估计特定计算基态的概率。
:param simulator: 已模拟完量子电路的 QuantumSimulator 实例。
:param target_basis_state: 目标计算基态,例如 "0110"。
:param num_samples: 用于估计的采样次数。
:return: 目标基态的估计概率。
"""
# 确保 target_basis_state 的长度与量子比特数匹配
if len(target_basis_state) != simulator.num_qubits:
raise ValueError("Target basis state length mismatch.")
# 从模拟器中获取采样
sampled_states = simulator.sample(num_samples)
# 计数目标基态出现的次数
count = sampled_states.count(target_basis_state)
# 计算概率
probability = count / num_samples
return probability
# --- 示例用法 ---
# num_qubits = 2
# simulator = QuantumSimulator(num_qubits)
# # 假设电路模拟已经完成了,statevector 已计算
# # simulator.simulate_circuit(...)
#
# # 假设模拟的最终态是 |psi> = 0.5|00> + 0.5|01> + 0.5|10> + 0.5|11>
# # 对应 statevector: [0.5, 0.5, 0.5, 0.5]
# simulator.statevector = np.array([0.5, 0.5, 0.5, 0.5])
#
# target_state = "01"
# num_estimation_samples = 10000
#
# estimated_prob = estimate_basis_state_probability(simulator, target_state, num_estimation_samples)
# print(f"Estimated probability of state |{target_state}>: {estimated_prob}")
# # 期望输出接近 0.25 (因为 |01> 的振幅是 0.5, 概率是 |0.5|^2 = 0.25)
PAS 的优势总结:
- 目标导向: PAS 的设计目标就是高效地计算特定信息,而不是模拟整个量子态。
- 采样优化: 它可能采用比简单采样的更复杂的采样策略,例如稀疏采样、拒绝采样、或者利用量子算法(如 QAE 的变种)来提高目标振幅的采样概率,或者直接估计振幅。
- 张量网络集成: 如果底层模拟器使用张量网络,PAS 的核心工作在于如何利用张量网络的收缩算法来高效地计算特定部分的振幅或期望值,避免全局收缩。
希望以上对部分振幅模拟器(PAS)在计算可观察量期望值和特定计算基态概率这两个核心功能上的实现原理的深入剖析,能够满足您的专业要求。如果您有更具体的问题或需要进一步的细节,请随时提出。