2. 复数向量与希尔伯特空间表示

在经典计算中,一个 n 位寄存器可以表示 2n 个不同的位串,但某一时刻只处于其中一个位串。量子计算的根本差异在于:n 个量子比特的联合状态由 2n 个复数振幅共同描述,这些振幅构成一个 2n 维复向量,即希尔伯特空间 ℂ2n 中的一个单位向量。本节的任务,就是把这一数学对象落实为可编程的数据结构:一个长度为 2n 的复数数组,并完成初始化与打印。

从基态编码到振幅数组

计算基态是希尔伯特空间的一组标准正交基。对 n 个量子比特,基态写作 |x⟩,其中 x 是 0 到 2n−1 的整数,其二进制展开对应各量子比特的 0/1 取值。例如 n=3 时,|5⟩ = |101⟩ 表示 q2=1、q1=0、q0=1。约定:数组下标即基态整数 x,下标 i 的二进制位从高位到低位依次对应 qn−1 … q0。这一约定必须在整个工程中保持一致,否则后续张量积、受控门和测量都会错位。

任意量子态可写成叠加形式:

|ψ⟩ = Σ_{x=0}^{2^n−1} α_x |x⟩,   α_x ∈ ℂ

在代码中,α_x 就是 state[x]。每个 α_x 是复数,含实部与虚部,因此一个 2n 维复向量需要 2n×2 个浮点数存储。n=10 时是 1024 个复数,n=20 时是 1048576 个复数,n=30 时已达约 10.7 亿个复数——这正是状态向量模拟的内存瓶颈所在,也是它只能模拟中小规模量子系统的原因。

归一化约束

量子态必须是单位向量,即所有振幅模平方之和为 1:

Σ_x |α_x|² = 1

这个约束的物理含义是:对全部基态做测量,得到某个结果的概率总和必为 1。其中 |α_x|² 是测量时坍缩到 |x⟩ 的概率。若 α = a + bi,则 |α|² = a² + b²。初始化后必须校验该值,浮点误差允许在 1e−10 量级内。若偏离过大,说明初始化逻辑或后续门操作有误。

全局相位无关性

若把整个状态向量乘以同一个单位复数 eiφ,即 α_x → eiφα_x,则所有 |α_x|² 不变,任何测量结果的概率分布不变,任意可观测量期望值也不变。因此 |ψ⟩ 与 eiφ|ψ⟩ 描述同一物理状态,这称为全局相位无关性。注意:相对相位是物理的。例如 (|0⟩+|1⟩)/√2 与 (|0⟩−|1⟩)/√2 只差一个相对符号,但前者在 X 基下确定性地测得 +,后者测得 −,二者可区分。工程实现中不必、也不应强行把全局相位归一为实数,只需保证相对相位正确。

n 量子比特状态向量:从基态编码到 2ⁿ 维复数数组 基态编码(n=3) 下标 x 二进制 q₂q₁q₀ 0000000 1001001 2010010 3011011 4100100 5101101 6110110 7111111 下标 = 基态整数 x 高位 → q₂,低位 → q₀ 映射 复数振幅数组 state[8] 索引 实部 a 虚部 b 01.0000000.000000 10.0000000.000000 20.0000000.000000 30.0000000.000000 40.0000000.000000 50.0000000.000000 60.0000000.000000 70.0000000.000000 |000⟩ 初始化:state[0] = 1 其余振幅为 0 共 2ⁿ = 8 个复数 校验 两条硬约束 归一化 Σ |αₓ|² = 1 |α|² = a² + b² 容差 ≤ 1e−10 全局相位无关 αₓ → e^{iφ} αₓ 概率分布不变 相对相位仍物理 内存 ∝ 2ⁿ n=30 → 约 10.7 亿复数

手写状态向量初始化与打印

下面用 Python 实现一个最小可用的状态向量类,不依赖任何量子计算库,只用标准库 cmath。核心字段是 n(量子比特数)与 amps(长度 2n 的复数列表)。

import cmath

class StateVector:
    def __init__(self, n, init_index=0):
        self.n = n
        self.dim = 1 << n          # 2**n
        self.amps = [0j] * self.dim
        if not (0 <= init_index < self.dim):
            raise ValueError("init_index 超出基态范围")
        self.amps[init_index] = 1+0j   # 初始化到 |init_index⟩

    def norm(self):
        return sum(abs(a)**2 for a in self.amps)

    def check(self, tol=1e-10):
        d = abs(self.norm() - 1.0)
        return d <= tol, d

    def pretty(self):
        lines = []
        for x, a in enumerate(self.amps):
            if abs(a) < 1e-12:
                continue
            bits = format(x, "0{}b".format(self.n))
            lines.append("|{}⟩  {:.6f}{:+.6f}i".format(bits, a.real, a.imag))
        return "\n".join(lines) if lines else "(全零态)"

关键点说明:1 << n 即 2n,用位运算比 2**n 更直观地表达“维度随比特数指数增长”。format(x, "0{}b".format(self.n)) 把下标 x 补零成 n 位二进制串,正好对应基态标签。打印时跳过模小于 1e−12 的振幅,避免输出大量无意义的 0,这在稀疏态(如刚初始化的基态)下尤其有用。

运行示例(n=3,初始化到 |000⟩):

sv = StateVector(3, init_index=0)
print(sv.pretty())
ok, dev = sv.check()
print("归一化:", ok, "偏差:", dev)

# 输出
# |000⟩  +1.000000+0.000000i
# 归一化: True 偏差: 0.0

再验证一个叠加态,手动构造 (|000⟩ + |101⟩)/√2,即下标 0 与 5 各为 1/√2:

sv2 = StateVector(3, 0)
sv2.amps[0] = 1/cmath.sqrt(2)
sv2.amps[5] = 1/cmath.sqrt(2)
print(sv2.pretty())
print(sv2.check())

# |000⟩  +0.707107+0.000000i
# |101⟩  +0.707107+0.000000i
# (True, 0.0)

若把 sv2.amps[5] 改为 -1/cmath.sqrt(2),打印结果变为 |000⟩ 与 −|101⟩,归一化仍为 1,但相对相位已改变,物理状态不同。这正是全局相位与相对相位区别的直接体现:整体乘 −1 无影响,单个分量变号有影响。

调试建议:每次初始化或施加门操作后调用 check()。若偏差突然增大到 1e−3 量级,通常是门矩阵未做归一化或索引映射写错,而不是浮点累积误差。

常见错误与排查

  • 维度写错:把 2n 写成 n,导致数组长度不足,访问 amps[5] 时越界。
  • 位序颠倒:把下标 x 的最低位当成 qn−1。表现为受控门作用在错误的比特上,测量分布整体镜像。
  • 归一化遗漏:手动赋值叠加态时忘记除以 √2,check() 返回偏差 0.5。
  • 误消全局相位:为“简化”而把首非零振幅强制转为实数,会破坏相对相位,导致后续干涉结果错误。

完成本节后,你应当能够:给定 n 与目标基态,写出长度为 2n 的复数数组;用 Σ|α|²=1 校验其合法性;用二进制标签打印非零振幅;并清楚说明为什么整体乘 eiφ 不改变物理状态,而单个振幅变号会改变。这套数组表示是后续所有门操作、张量积与测量的数据基础。