我写了段代码,结果它既在 0 又在 1,只好从头学量子门

事情是这样的。

上周三晚上,我窝在沙发上写一个量子计算的 demo,用的 Qiskit 0.45。就三行代码:初始化一个量子比特,过一遍 Hadamard 门,然后测量。跑完一看输出——0 和 1 各占一半。我盯着屏幕愣了两秒,脑子里冒出来的第一反应是:这不对吧,一个比特怎么能既是 0 又是 1?我是不是把随机数生成器写进去了?

不是。是我脑子里还装着经典逻辑门的模型,那玩意儿在量子世界根本不 work。

经典逻辑门的思维惯性

我们写代码的人,对逻辑门有一种肌肉记忆。AND、OR、NOT,输入什么输出什么,真值表背得比乘法口诀还熟。一个比特要么是 0,要么是 1,没有第三种可能。你给 NOT 门输入 0,它就输出 1,干净利落,像拧开关一样。

这种确定性让人安心。debug 的时候你至少知道,某个点上的值不对,那肯定是前面的逻辑链出了问题。你顺着电路往回摸,总能摸到那个坏掉的与非门。

但量子门不跟你玩这套。

我第一次看到 Hadamard 门的矩阵表示时,整个人是懵的。那个 1/√2 是什么鬼?为什么一个"门"操作完,我的比特就变成了一种"一半 0 一半 1"的状态?这在经典世界里相当于什么呢——相当于你按了一下开关,灯既亮着又灭着,你得走过去看一眼才能确定。在你走过去之前,它就是两种状态的叠加。

这不叫 bug,这叫 superposition。

Hadamard 门到底干了什么

Hadamard 门是量子计算里最简单也最反直觉的一个门。它只有一个输入,一个输出,但它做的事情在经典逻辑门里找不到任何对应物。

从矩阵上看,H 门就是把 |0⟩ 变成 (|0⟩ + |1⟩)/√2,把 |1⟩ 变成 (|0⟩ - |1⟩)/√2。注意那个 √2,它是个归一化因子,保证概率加起来等于 1。你测量的时候,有 50% 的概率看到 0,50% 的概率看到 1。

我当时就在想:这不就是个公平硬币吗?我干嘛不直接调 random.randint(0,1)?

区别在于,random.randint 是真的随机,它背后没有结构。而 H 门输出的这个叠加态,是有相位的。那个正负号——|0⟩ + |1⟩ 和 |0⟩ - |1⟩——在测量概率上完全一样,但如果你在测量之前再给它过一个 H 门,前者会变回 |0⟩,后者会变回 |1⟩。这就是量子干涉,经典随机做不到。

用程序员的话说:H 门不是生成了一个随机数,它是把量子比特放进了一个"两边都有振幅"的状态里,后续的操作可以在振幅上做文章,最后测量的时候才坍缩成确定值。你中间能玩的花样,比单纯扔硬币多得多。

CNOT 门与经典 XOR 的微妙差异

CNOT 门看起来就亲切多了。两个输入,两个输出,控制位是 0 就不动目标位,控制位是 1 就翻转目标位。这不就是个带条件的 NOT 吗?跟代码里的 if (control) { target = !target; } 一模一样。

但这里有个坑。

经典 XOR 是可逆的吗?不是。你看到输出是 1,你不知道输入是两个 1 还是一个 0 一个 1,信息丢了。CNOT 门不一样,它是可逆的——你把输出再过一遍完全相同的 CNOT,就能恢复输入。这在物理上对应一个事实:量子门必须是酉变换,不能丢信息,因为量子力学的基本演化方程是可逆的。

这个差异在实际写量子电路的时候会产生一个反直觉的现象。经典电路里,你从输入到输出,比特数可以变化——两个输入进 AND 门,出来一个输出,少了一个比特,没问题。量子电路不行,你进去几个量子比特,出来就得几个,一个都不能少。Qiskit 里你定义 QuantumCircuit(2,2) 的时候,那两个经典寄存器是给测量用的,量子寄存器本身从头到尾都是 2 个 qubit,门操作只是改变它们的状态,不会增减数量。

我第一次写 CNOT 的时候,下意识觉得控制位会被"消耗"掉,就像函数调用传了个引用进去,出来的时候引用可能变了。但实际上控制位原封不动——它只是参与了纠缠。纠缠是另一个大坑,但那是后话了。

从代码的角度重新理解量子门

后来我换了种方式想这件事。

经典逻辑门是纯函数:输入确定,输出确定,没有副作用,没有状态。量子门更像是状态转换器——它操作的是一个复数向量。一个 n 量子比特的系统,状态是 2^n 维向量,每个量子门就是这个向量空间里的一个酉矩阵。H 门是 2×2 的,CNOT 是 4×4 的,你把它乘到状态向量上,就完成了状态演化。

用 NumPy 写的话,大概长这样:

import numpy as np

# Hadamard 门矩阵
H = np.array([[1, 1], [1, -1]]) / np.sqrt(2)

# CNOT 门矩阵 (4x4)
CNOT = np.array([
    [1, 0, 0, 0],
    [0, 1, 0, 0],
    [0, 0, 0, 1],
    [0, 0, 1, 0]
])

# |0> 状态向量
state = np.array([1, 0])

# 过 H 门
state_after_H = H @ state
print(state_after_H)  # [0.70710678 0.70710678]

看到输出的那一瞬间我突然就懂了。那个 [0.707, 0.707] 就是叠加态——它不是"既 0 又 1"这么玄学,它就是数学上一个向量,两个分量都有值。测量的时候按分量模长的平方来概率塌缩,就这么简单。

我之前觉得反直觉,不是因为它真的反直觉,是因为我非要用经典比特的模型去套它。就像你非要用字典去理解哈希表——能理解个大概,但底层完全是两码事。

那天晚上我又跑了一遍那段代码,看着终端里交替出现的 0 和 1,心里踏实多了。不是代码有 bug,是我脑子里的"逻辑门"该升级了。


常见问题

我写了个量子电路,测量结果为什么每次都不一样?

因为量子力学里的测量本质上是概率性的。除非你的量子态恰好是某个计算基态(|0⟩ 或 |1⟩),否则每次测量都会按照振幅模平方的概率随机坍缩到 0 或 1。这不是代码的 bug,是底层物理就是这样的。想看到稳定的概率分布,需要多次运行(比如 shots=1024),然后看统计直方图。

Hadamard 门和直接把 qubit 设成随机状态有什么区别?

区别巨大。H 门生成的叠加态有确定的相对相位(系数是 +1/√2 和 +1/√2),后续再过一个 H 门可以精确地回到 |0⟩。随机状态没有这种相干性,过 H 门之后的结果是不可预测的。量子算法靠的就是这种相位相干性来做干涉,随机混进去的态会破坏整个计算。

CNOT 门能不能用来实现经典的 if-else 逻辑?

能,但要小心。CNOT 确实实现了"如果控制位是 1 就翻转目标位"的逻辑,但如果控制位处于叠加态,目标位翻转的幅度也是叠加的,两个 qubit 会纠缠在一起。这时候你再单独看控制位或目标位,各自的状态都变成了混态,已经不是纯的叠加态了。经典 if-else 假设条件本身是确定的,量子 CNOT 不要求这个前提,所以它能做的事情比经典分支多得多——代价是你不能在中途"偷看"控制位的值。