量子计算机编程:从入门到实践
上QQ阅读APP看书,第一时间看更新

第 2 章 单个量子比特

传统比特仅有 1 个二进制参数,可以将其初始化为状态 0 或状态 1。尽管二进制的逻辑运算已足够简单,但是我们仍然可以用空心圆和实心圆直观地表示状态值,如表 2-1 所示。

表2-1:用空心圆和实心圆表示传统比特

在某种意义上,量子比特与传统比特非常相似:每当读取一个量子比特的值时,总会得到 0 或 1。因此,在读出一个量子比特之后,总是可以像表 2-1 所示的那样来描述它。但是在 读取之前,量子比特的值并非黑白分明,需要用更复杂的方式来描述。在读取之前,量子比特处于叠加态(superposition)。

我们很快就会了解叠加态的含义,但首先来了解一下它的强大作用。请注意,在被读取之前,单个量子比特存在无穷多个叠加态,表 2-2 仅列出了其中的一些。尽管最终读出的值总会是 0 或 1,但如果善用一些技巧,这些可用的额外状态便能帮助我们执行一些非常强大的运算任务。

表2-2:量子比特的一些可能的值

 在表 2-2 中,我们将 0 和 1 分别表示为 |0⟩ 和 |1⟩。这种符号被称为狄拉克符号(bra-ket notation),通常用于量子计算。一个简单的经验法则是,狄拉克符号中的数字表示在读取时可能会得到的量子比特值。当谈到一个量子比特已被读出的值时,我们只使用数字来表示。

表 2-2 中的前两行展示了与传统比特的状态等价的量子态,其中完全没有出现量子叠加态。在状态 |0⟩ 下制备的量子比特等价于传统比特 0(在读取时总是给出值 0),在状态 |1⟩ 下同理。如果永远只是非 |0⟩ 即 |1⟩,那么这些仅仅等价于传统比特的量子比特可够昂贵的。

如何才能获得如表 2-2 中后三行所示的那些更奇妙的量子叠加态呢?为了理解量子叠加态,不妨先花上一点点时间思考量子比特到底是什么 1

1本书尽力避免过多地解释量子比特的含义。尽管这似乎有点令人扫兴,但请记住,传统编程指南几乎从不讲比特和字节迷人的物理特性。实际上,正是能够从信息的物理本质中抽离出来,才使得复杂程序的编写过程变得容易。

2.1 物理量子比特概览

一个有助于阐释量子叠加态的例子是单光子。为了说明这一点,让我们先回退一步,假设要使用光子的位置来表示传统比特。在图 2-1 所示的设备中,带开关的镜子(可以通过开关调整为反射镜或透视镜)让我们能够控制光子走两条路径中的一条。其中,两条路径分别对应 0 和 1。

图 2-1:把光子用作传统比特

在数字通信领域,确实存在图 2-1 所示的设备。不过显然,单光子处理起来非常困难(一个原因是,它不会在任何地方长时间停留)。为了使用这个设备演示量子比特的某些特性,我们把决定光子路径的那面带开关的镜子替换为半镀银的镜子。

如图 2-2 所示,半镀银的镜子(也叫作分束器)有一个半反射的表面,它有 50% 的概率将光偏转到值 1 相应的路径上,同时有 50% 的概率让光直接沿着值 0 相应的路径向前,只有这两种可能情况。

图 2-2:光量子比特的一种简单实现

当一个不可分割的光子撞击半镀银的镜子表面时,它会遭遇某种身份危机,也即出现一种不合常理的效应:光子处于一种既受路径 0 影响、又受路径 1 影响的状态。我们将这种状态称为叠加态,意思是光子可能处于每一条路径上。换句话说,我们拥有的不再是一个传统比特,而是一个量子比特,它处于由 0 态和 1 态叠加的状态。

人们很容易误解叠加态的本质,就像许多介绍量子计算的流行文章所描述的那样。那种认为光子同时处在路径 0 和路径 1 上的说法并不正确。因为光子只有一个,所以如果像图 2-2 所示的那样在每条路径上都放置探测器,那么只有一个探测器会探测到光子。当这种情况出现后,量子比特将降维到数字比特,并给出确定的结果:非 0 即 1。然而,正如我们稍后将探讨的那样,在需要通过探测读取值之前,QPU 与处于叠加态的量子比特的交互将对计算大有帮助。

图 2-2 所示的这种叠加态对利用 QPU 的量子计算能力至关重要。因此,我们需要以量化程度更高的方式描述和控制量子叠加态。当光子处于多路径叠加的状态时,它与每条路径都有一个相应的振幅(amplitude)。这些振幅有两个重要特性——强度和相对相位。它们就像两个旋转开关,我们可以利用这两个开关来改变量子叠加态的特殊配置。

● 与每条路径相关联的强度(magnitude)是一个模拟值,用于衡量光子在每条路径上的扩散程度。路径的强度与在该路径上检测到光子的概率有关。具体来说,强度的平方决定了在给定路径上检测到光子的概率。在图 2-2 中,可以通过改变半镀银镜子的反射程度来调整与每条路径相关联的振幅强度。

● 不同路径之间的相对相位(relative phase)表示光子在一条路径上相对于另一条路径的延迟程度。相对相位也是一个模拟值,可以通过设置光子沿着路径 0 和路径 1 传播的距离之差来控制。需要注意的是,改变相对相位不会影响光子在每条路径上被检测到的概率 2

2虽然本书基于光的相对传播距离来引入相对相位的概念,但这是一个普适的概念,它适用于所有类型的量子比特:光子、电子、超导体等。

再次强调,振幅包含两个特性,即与量子叠加态的某个值相关联的强度和相对相位。

 对数学感兴趣的读者不妨了解这样一点:与叠加态中的不同路径相关联的振幅通常用复数来表示。振幅的强度正好是这个复数的模(复数与其共轭复数乘积的平方根),振幅的相对相位则是复数以极坐标表示时的辐角。如果你对数学不感兴趣,也不必担心,我们很快将介绍一种图形记法。

在计算时,强度和相对相位是可以利用的值,不妨认为它们被编码在量子比特中。但是如果要从中读取任何信息,就必须使光子撞击某种探测器。此时,这两个模拟值都消失了——量子比特的量子特性消失了。这就是量子计算的关键所在:找到一种方法来利用这些虚幻的值,使得在进行读取这一破坏性行为之后,一些有用的值仍然得以保留。

 在将光子用作量子比特的情况下,图 2-2 中的设置与稍后将在示例 2-1 中介绍的代码示例等效。

好了,用光子做的介绍就到这里了!本书是程序员指南,不是物理学教科书。让我们从物理学中抽身,抛开光子和量子物理,看看如何描述和可视化量子比特,就像抛开电子和半导体物理去研究二进制逻辑一样。

2.2 圆形表示法

我们现在已经知道了何谓叠加态,但目前所知的内容仅与光子的特有行为密切相关。接下来需要找到一种描述叠加态的抽象方式,从而只关注抽象的信息。

在成熟的量子物理学中,数学描述提供了这样一种抽象,但从表 2-2 左列可以看出,这种数学描述远不如传统比特简单的二进制计算直观和方便。

幸运的是,表 2-2 右列的圆形表示法是一种更直观的方法。由于我们的目标是在无须深入了解晦涩的数学知识的前提下,直观地理解 QPU 的内部原理,因此从现在开始,我们将完全基于圆形表示法来思考量子比特。

我们通过光子实验发现,在 QPU 中,量子比特的一般状态有两个特性需要关注:叠加振幅的强度和振幅之间的相对相位。这些参数与圆形表示法的关系如下。

● 与量子比特可能取的每个值(目前为止是 |0⟩ 和 |1⟩)相关联的振幅的强度与每个 |0⟩ 圆和 |1⟩ 圆中的已填充区域的半径相关。

● 这些值的振幅之间的相对相位由 |1⟩ 圆相对于 |0⟩ 圆的旋转表示(在圆中画一条颜色较深的线,以表示旋转)。

因为整本书都将依赖圆形表示法,所以有必要更详细地了解如何通过圆的大小和旋转来表示上述概念。

2.2.1 圆的大小

如前所述,与 |0⟩ 或 |1⟩ 相关联的强度的平方决定了在读取时得到该值的概率。圆的填充半径表示强度,这意味着如果读取量子比特,那么每个圆中的已填充面积(或者更通俗地说,圆的大小)与得到该圆所对应的值(0 或 1)的概率成正比。图 2-3 展示了不同量子比特状态的圆形表示法以及在每种情况下读出值 1 的概率。

图 2-3:从圆形表示法表示的不同叠加态中读出值 1 的概率

 读取量子比特会破坏信息。若对图 2-3 中的所有状态读取量子比特,结果将非 0 即 1。在读取之后,量子比特会改变其状态,以匹配所读取的值。因此,假设一个量子比特最初处于复杂的状态,可一旦读出了值 1,那么即便立即再次尝试读取,也只会得到值 1。

请注意,|0⟩ 圆中的已填充区域越大,读出值 0 的概率就越大,当然这意味着得到值 1 的概率越小。在图 2-3 的最后一个例子中,读出值 0 的概率是 90%,读出值 1 的概率则是 10%。3 在圆形表示法中,我们用圆的已填充区域来表示叠加态中的强度。虽然这看起来像是一个让人烦恼的技术细节,但务必记住,强度与已填充区域的半径相关。为了在视觉上更直观,即使将两者等同起来也没有关系。

3寄存器振幅的平方之和必须总是等于 1,这个要求被称为规范化。要了解更多信息,请参阅 9.3.1 节。

有一点很容易被忽略,但请谨记,那就是在圆形表示法中,与给定结果相关联的圆的大小并不能完全代表叠加态的振幅,其中缺少的重要信息是叠加态的相对相位。

2.2.2 圆的旋转

利用一些 QPU 指令,可以改变 |0⟩ 圆和 |1⟩ 圆的相对旋转情况,即量子比特的相对相位。量子比特状态的相对相位可以取 0°和 360°之间的任意值,图 2-4 展示了几个例子。

图 2-4:单个量子比特的相对相位示例

 本书用圆形表示法旋转圆的惯例是,逆时针旋转,且角度值为正,如图 2-4 所示。

在前面的所有例子中,我们都只旋转了 |1⟩ 圆。为什么不旋转 |0⟩ 圆呢?顾名思义,只有量子比特叠加态的相对相位才彰显不同。因此,只有圆之间的相对旋转才有意义。4 如果 QPU 指令对两个圆都进行旋转,那么总是可以考虑等效的效果,即只旋转 |1⟩ 圆,使相对旋转效果看起来更明显,如图 2-5 所示。

4基于控制量子比特的量子力学定律,只有相对相位才是重要的。

图 2-5:在圆形表示法中,只有相对旋转才是重要的。图中两种状态是等价的,这是因为两个圆的相对相位相同

注意,相对相位可以独立于叠加态的强度而变化,反之亦然。比较图 2-3 中的第 3 个和第 4 个例子,可以看到,单个量子比特的不同结果之间的相对相位对读出某个结果的概率没有直接影响。

由于量子比特的相对相位对叠加态的强度没有影响,因此它不会直接影响可观测的读取结果。这可能会使相对相位这个特性看起来无关紧要,但事实并非如此!在涉及多个量子比特的量子计算中,可以利用旋转来巧妙、间接地影响最终读出不同值的概率。实际上,精心设计的相对相位可以提供惊人的计算优势。接下来将介绍一些量子运算,特别是那些只对单个量子比特起作用的运算。我们将使用圆形表示法将运算效果可视化。

 与传统比特明显的数字特性不同,量子比特的强度和相对相位是具有连续自由度的特性。这导致许多人误以为,量子计算类似于命运多舛的模拟计算(analog computing)。但需要指出的是,尽管量子计算具有连续自由度,但 QPU 计算导致的误差仍然可以用数字方式纠正。这就是 QPU 比模拟计算设备更稳健的原因。

2.3 第一批QPU指令

与 CPU 的同类运算一样,针对单个量子比特的 QPU 运算将输入信息转换为目标输出。当然,QPU 运算的输入和输出是量子比特,而不是传统比特。许多 QPU 指令 5 有相应的逆指令,了解它们是有用的。这样的 QPU 运算被称为可逆,这意味着在进行这些运算时不会丢失或舍弃任何信息。然而,一些 QPU 运算是不可逆的,因此它们会导致信息丢失。后文会解释,了解运算是否可逆对我们如何应用它有重要的影响。

5在本书中,“QPU 指令”和“QPU 运算”是等义的,经常交替使用。

某些 QPU 指令看上去有些奇怪,而且用法不明,但在简单了解之后,我们将很快开始使用它们。

2.3.1 QPU指令:NOT

NOT 与传统的逻辑非指令类似。应用它之后,0 会变为 1,反之亦然。然而,与传统的逻辑非指令不同的是,QPU 的 NOT 指令可以对处于叠加态的量子比特进行操作。

用圆形表示法可以简单地表示 NOT 运算的结果,只需交换 |0⟩ 圆和 |1⟩ 圆即可,如图 2-6 所示。

图 2-6:用圆形表示法表示 NOT 运算

可逆性:就像传统的数字逻辑运算一样,NOT 运算的逆运算是它自己。两次应用它会将一个量子比特恢复到初始值。

2.3.2 QPU指令:HAD

HAD 是 Hadamard 的缩写形式,该运算本质上是为某个呈 |0⟩ 态或 |1⟩ 态的量子比特创建相等的叠加态。它就像一把钥匙,为我们开启量子叠加态那既奇妙又微妙的并行性之门!与 NOT 运算不同,HAD 没有针对传统比特的等价运算。

在用圆形表示法表示时,HAD 输出的 |0⟩ 圆和 |1⟩ 圆的已填充面积相同,如图 2-7 所示。

图 2-7:针对基本状态进行 HAD 运算

HAD 运算会在量子比特中产生均匀叠加效果,即产生每个结果出现的概率相同的叠加态。注意,HAD 运算对初始状态为 |0⟩ 和 |1⟩ 的量子比特稍有不同:对 |1⟩ 态应用 HAD,会在一个圆中产生非零旋转(相对相位),而对 |0⟩ 态应用 HAD 则不会如此。

你可能会好奇,如果针对已经处于叠加态的量子比特应用 HAD,结果会如何。找到答案的最佳方法就是做实验!通过实验,你很快就会注意到以下现象。

● 根据图 2-7 所示的规则,HAD 分别作用于 |0⟩ 态和 |1⟩ 态。

● 生成的 |0⟩ 值和 |1⟩ 值被组合起来,并根据初始叠加态的振幅加权 6

6关于 HAD 和其他常见运算背后的数学细节,请参考第 14 章。

可逆性:与 NOT 运算类似,HAD 运算的逆运算也是它自己。两次应用它会将一个量子比特恢复到初始值。

2.3.3 QPU指令:READ和WRITE

READ 运算是前文介绍的读取过程的形式化表示。它在 QPU 指令集中与众不同,这是因为它是唯一可能返回随机结果的指令。

WRITE 运算能够在操作 QPU 寄存器之前将它初始化,这是一个具有确定性的过程。

将 READ 指令应用于单个量子比特将返回 0 或 1,每个结果出现的概率由量子比特状态中相应振幅的强度的平方决定(忽略相对相位)。应用 READ 指令之后,如果得到的结果为 0,那么量子比特将维持 |0⟩ 态,反之则维持 |1⟩ 态。换句话说,任何叠加态都将被不可逆转地破坏。

因为圆中的已填充面积体现了相应结果出现的概率,所以在用圆形表示法表示 READ 运算的结果时,与结果对应的圆将被完全填充,而其余的变为空心圆。图 2-8 展示了针对不同的叠加态应用 READ 指令的场景。

图 2-8:READ 运算的随机结果

 在图 2-8 的第 2 个示例中,READ 运算删除了全部有意义的相对相位信息。因此,我们重新调整状态,使圆中的线垂直。

使用 READ 和 NOT,可以编写简单的 WRITE 指令,从而制备目标状态为 |0⟩ 或 |1⟩ 的量子比特。首先针对量子比特应用 READ 指令,之后,如果所得的值与计划写入的值不同,则应用 NOT 指令。请注意,WRITE 运算不能制备处于任意叠加态(具有任意强度和相对相位)的量子比特,它制备的量子比特只能处于 |0⟩ 态或 |1⟩ 态 7

7我们在后文中会看到,获得任意的叠加态很难,但很有用,在量子机器学习应用中尤其如此。第 13 章将介绍一种方法。

可逆性:READ 和 WRITE 是不可逆的。它们会破坏叠加态,并导致信息丢失。叠加态一旦遭到破坏,量子比特的模拟值(强度和相对相位)就会永远消失。

2.3.4 实践:完全随机的比特

在介绍更多的单量子比特运算之前,让我们休息片刻,来看看如何利用 HAD、READ 和 WRITE 创建一个强大的程序。该程序能够生成真正的随机比特,这在任何传统计算机上都不可能实现。

纵观计算史,人们花费了大量的时间和精力来开发 伪随机数生成器(pseudo-random number generator,PRNG),这些系统在密码学和天气预报等领域有着广泛的应用。PRNG 是伪随机的,这是因为如果知道计算机内存的内容和 PRNG 算法,原则上就可以预测下一个要生成的数字。

根据已知的物理定律,针对处于叠加态的量子比特,读取结果本质上是完全不可预测的。因此,借助 QPU,我们就能创建世界上最好的随机数生成器,只需要制备一个处于 |0⟩ 态的量子比特,对其应用 HAD 指令,然后读取它的值。可以使用量子电路(quantum circuit)来展示 QPU 指令组合,如图 2-9 所示。

图 2-9:用 QPU 生成完全随机的比特

虽然简单得难以置信,但是我们已经有了第一个 QPU 程序:量子随机数生成器(quantum random number generator,QRNG)。可以使用示例 2-1 中的代码片段来模拟这个过程。如果在 QCEngine 上反复运行这 4 行代码,就会得到一个随机的二进制数字串。当然,像 QCEngine 这种以 CPU 驱动的 QPU 模拟器只是用 PRNG 来模拟 QRNG,但是在真正的 QPU 中运行等价的代码将生成完全随机的二进制数字串。

示例代码

请在 http://oreilly-qc.github.io?p=2-1 上运行本示例。

示例2-1 随机比特

qc.reset(1);             // 分配一个量子比特
qc.write(0);             // 写入值0
qc.had();                // 使量子比特处于0和1的叠加态
var result = qc.read();  // 读取数字比特

 

 可以在 http://oreilly-qc.github.io 上找到本书中的所有示例代码,这些代码既可以在 QPU 模拟器上运行,也可以在真正的 QPU 硬件上运行。运行这些示例代码是学习 QPU 编程的重要一环。若想了解更多信息,请参阅第 1 章。

这可能是你的第一个量子程序,祝贺你!让我们逐行分析,了解每一行的意义。

● qc.reset(1) 设置 QPU 模拟器,请求分配一个量子比特。我们为 QCEngine 编写的所有程序都将用类似这样的一行代码去初始化一个或一组量子比特。

● qc.write(0) 将单个量子比特初始化为 |0⟩ 态,这相当于将传统比特设置为值 0。

● qc.had() 对量子比特应用 HAD 指令,使其处于 |0⟩ 和 |1⟩ 的叠加态,如图 2-7 所示。

● var result = qc.read() 在计算结束时将量子比特的值作为随机数字比特读出,并将该值赋给变量 result。

本例所做的一切似乎不过是用一种非常昂贵的方式抛硬币,但是这种想法低估了 HAD 的威力。如果深入探究 HAD 的原理,就会发现这既不是伪随机数生成器,也不是基于硬件的随机数生成器。与它们不同,量子物理定律保证了 HAD 运算的不可预测性。在已知的宇宙中,即便知道用来生成随机数的指令,也没有人能够猜出从应用了 HAD 指令的量子比特中读出的随机数是 0 还是 1。

虽然我们在第 3 章中才会正式学习多量子比特的内容,但是现在可以很容易地并行运行 8 次单量子比特程序,从而生成随机字节,如图 2-10 所示。

图 2-10:生成随机字节

示例 2-2 展示了用于生成随机字节的代码,它与示例 2-1 相差不大。

示例代码

请在 http://oreilly-qc.github.io?p=2-2 上运行本示例。

示例2-2 随机字节

qc.reset(8);
qc.write(0);
qc.had();
var result = qc.read();
qc.print(result);

请注意,我们利用了这样一个事实:除非明确地指定要操作的量子比特,否则 QCEngine 指令(如 WRITE 和 HAD)将默认应用于所有经过初始化的量子比特。

 尽管示例 2-2 用了多个量子比特,但实际上没有进行将多个量子比特作为输入的运算。该程序只能被序列化为针对单个量子比特运行。

2.3.5 QPU指令:PHASE(θ)

PHASE(θ) 也没有针对传统比特的等价运算。它使得我们能够直接操作量子比特的相对相位,将其改变某个特定的角度。因此,除了要操作的量子比特之外,PHASE(θ) 还需要一个额外的数值参数,即旋转角度。例如,PHASE(45) 表示进行 45° 的旋转运算。

在使用圆形表示法表示时,PHASE(θ) 的作用就是简单地以指定的角度旋转 |1⟩ 圆。图 2-11 展示了 PHASE(45) 的效果。

图 2-11:PHASE(45) 的效果

请注意,PHASE(θ) 仅旋转 |1⟩ 圆,因此它对处于 |0⟩ 态的量子比特没有影响。

可逆性:PHASE(θ) 是可逆的,不过逆运算通常不是它自己。通过应用与原始角度相反的 PHASE(θ),可以反转相对相位旋转效果。在圆形表示法中,这相当于通过反向旋转抵消旋转效果。

使用 HAD 和 PHASE,可以创建一些常用的单量子比特状态,如图 2-12 所示。这些状态分 别被命名为 |+⟩、|–⟩、|+Y⟩ 和 |–Y⟩。如果你想使用 QPU 小试身手,请看看能否用 HAD 和 PHASE 产生这些状态(每个叠加态在 |0⟩ 态和 |1⟩ 态中都具有相等的强度)。

图 2-12:十分常用的 4 种单量子比特状态

尽管产生上述状态的一种方法是使用 HAD 和 PHASE,但也可以将它们理解为所谓的单量子比特旋转运算的结果。

2.3.6 QPU指令:ROTX(θ)和ROTY(θ)

我们已经了解了 PHASE(θ) 指令的用途,即旋转量子比特的相对相位,在圆形表示法中,该指令旋转的是 |1⟩ 圆。另外两个与 PHASE(θ) 相关的常见指令是 ROTX(θ) 和 ROTY(θ),它们会以稍微不同的方式旋转量子比特。

图 2-13 用圆形表示法展示了 ROTX(45) 和 ROTY(45) 在 |0⟩ 态和 |1⟩ 态上的应用效果。

图 2-13:在 |0⟩ 态和 |1⟩ 态上应用 ROTX 和 ROTY

图 2-13 中的效果不太容易理解,至少不像 PHASE 那样直观。这两个指令的名称来源于单量子比特状态的另一种常见的可视化表示,即布洛赫球(Bloch sphere)。在布洛赫球表示法中,单个量子比特是由三维球面上的某个点表示的。本书没有使用布洛赫球表示法,而是使用了圆形表示法,这是由于布洛赫球表示法不能很好地表示多个量子比特。但是为了满足你的好奇心,我们简单介绍一下。如果用布洛赫球表示一个量子比特,那么 ROTX 和 ROTY 分别表示围绕球体的 x 轴和 y 轴旋转量子比特。由于本书在表示量子比特时使用两个圆而不是球体,因此 ROTX 和 ROTY 在圆形表示法中就失去了意义。实际上,PHASE 对应于绕 z 轴旋转,它在布洛赫球表示法中相当于 ROTZ。

2.4 复制:缺失的指令

有一个指令可用于传统计算机,但不能在 QPU 中实现。尽管可以通过反复创建得到一个已知状态的多个副本 8,但是在状态不确定的情况下,无法通过量子计算来部分复制某个状态。这种约束是由控制量子比特的基本物理定律所导致的。

8如果状态是 |0⟩ 或 |1⟩,就可以简单地通过 WRITE 指令来实现。

缺失复制指令无疑会带来不便,但正如我们将在后文中了解到的,QPU 的其他能力足以弥补这一不足。

2.5 组合QPU指令

我们已经了解的指令有 NOT、HAD、READ、WRITE 和 PHASE。值得一提的是,和传统的逻辑运算指令一样,可以结合这些指令来实现其中每一个指令,甚至创建出全新的指令。假设某种 QPU 提供 HAD 指令和 PHASE 指令,但缺失 NOT 指令。将 PHASE(180) 与两个 HAD 相结合,就能产生与 NOT 完全相同的结果,如图 2-14 所示。反之,PHASE(180) 也可以通过 HAD 和 NOT 来实现。

图 2-14:构建等效运算

QPU指令:RNOT

通过组合 QPU 指令,还可以创建出在传统逻辑世界中根本不存在的有趣指令,RNOT(ROOT-of-NOT)就是这样一个例子。顾名思义,它是 NOT 指令的平方根,也就是说,应用该指令两次,相当于应用一次 NOT 指令,如图 2-15 所示。

图 2-15:对于传统比特而言,这是不可能实现的运算

构建 RNOT 指令的方法不止一种,图 2-16 给出了一种简单的实现。

图 2-16:实现 RNOT

可以通过模拟器验证一下,看看应用两次 RNOT 是否确实产生了与 NOT 相同的结果,如示例 2-3 所示。

示例代码

请在 http://oreilly-qc.github.io?p=2-3 上运行本示例。

示例2-3 验证 RNOT 的效果

qc.reset(1);
qc.write(0);

// 应用RNOT

qc.had();
qc.phase(90);
qc.had();
// 应用RNOT
qc.had();
qc.phase(90);
qc.had();

可以用圆形表示法将实现 RNOT(两个 HAD 之间有一个 PHASE(90))所涉及的每个步骤都可视化,如图 2-17 所示。

图 2-17:将 RNOT 的实现过程可视化

利用圆形表示法,可以根据量子比特的演变理解 RNOT 为何是 NOT 的平方根。回顾图 2-14,我们对一个量子比特应用 HAD,然后将其相对相位旋转 180°,再应用一次 HAD,这一系列操作等同于应用一次 NOT。由于 RNOT 对量子比特执行一半的旋转(PHASE(90)),因此应用两次 RNOT 会产生 HAD-PHASE(180)-HAD 序列,等同于 NOT。乍一看,这可能有些难以理解,但是如果试着应用两次 RNOT,就会理解上述原理。(提示:HAD 是它本身的逆指令,也就是说,应用两次 HAD 相当于什么都不做。)

可逆性:虽然 RNOT 不是其本身的逆指令,但是图 2-16 中的运算可以通过使用负的相位值来逆转,如图 2-18 所示。

图 2-18:RNOT 的逆运算

尽管有些晦涩,但 RNOT 教给我们重要的一招:通过精心地将信息保存在量子比特的相对相位中,可以实现全新的计算类型。

2.6 实践:量子监听检测

为了更实际地展现控制量子比特相对相位的优势,我们用一个更复杂的程序来结束本章。示例 2-4 使用前面介绍的简单的单量子比特 QPU 指令来完成简化的量子密钥分发(quantum key distribution,QKD)。QKD 是量子密码学领域的核心协议,通过它能安全地传输信息。

假设 QPU 程序员 Alice 和 Bob 正在通过能够传输量子比特的信道相互发送数据。他们偶尔会发送示例 2-4 描述的具有特殊构造的“监听检测”量子比特,用于测试所用的信道是否被监听。

任何试图读取“监听检测”量子比特的监听者都有 12.5% 的概率被发现。因此,即使 Alice 和 Bob 在整个传输过程中只使用了 100 个这样的量子比特,监听不被发现的可能性也只有约百万分之一。

Alice 和 Bob 可以通过交换一些不需要加密的传统数字信息来检测他们的密钥是否被泄露。在交换信息后,他们测试一些量子比特,方法是读取量子比特并检查读取结果是否符合预期。如果出现不一致,就说明有人在监听。该过程如图 2-19 所示。

图 2-19:量子监听检测程序

以下给出代码。建议尝试运行示例 2-4 中的代码,并像探索任何其他代码片段一样进行调整和测试。

示例代码

请在 http://oreilly-qc.github.io?p=2-4 上运行本示例。

示例2-4 量子监听检测

qc.reset(3);
qc.discard();
var a = qint.new(1, 'alice');
var fiber = qint.new(1, 'fiber');
var b = qint.new(1, 'bob');

function random_bit(q) {
    q.write(0);
    q.had();
    return q.read();
}

// 生成2个随机比特
var send_had = random_bit(a);
var send_val = random_bit(a);

// 创建Alice的量子比特
a.write(0);
if (send_val)  // 根据随机比特判断是否设置该值
    a.not();
if (send_had)  // 根据随机比特判断是否应用HAD
    a.had();

// 发送量子比特
fiber.exchange(a);

// 监听
var spy_is_present = true;

if (spy_is_present) {
    var spy_had = 0;
    if (spy_had)
        fiber.had();
    var stolen_data = fiber.read();
    fiber.write(stolen_data);
    if (spy_had)
        fiber.had();
}

// 接收量子比特
var recv_had = random_bit(b);
fiber.exchange(b);
if (recv_had)
    b.had();
var recv_val = b.read();

// 现在Alice用邮件告诉Bob她选择的操作和值
// 如果选择的操作匹配,但值不一样,就说明通信被监听
if (send_had == recv_had)
    if (send_val != recv_val)
        qc.print('Caught a spy!\n');

在示例 2-4 中,Alice 和 Bob 各自可以访问只包含一个量子比特的简单 QPU,并且可以沿着量子信道发送量子比特。可能会有人监听该信道。在示例代码中,通过变量 spy_is_present 来控制是否存在监听者。

 量子密码可以用这么小的 QPU 来实现,这就是早在更强大的通用 QPU 出现之前,小型 QPU 就已经开始商业化应用的原因之一。

让我们逐步分析代码,看看 Alice 和 Bob 如何利用手头的简单资源实现量子监听检测。以下结合代码注释来解释。

// 生成 2 个随机比特

  Alice 将她的单量子比特 QPU 作为一个简单的 QRNG,正如我们在示例 2-2 中所做的那样,生成两个只有她知道的秘密随机比特。本例将它们定义为 send_had 和 send_val。

// 创建 Alice 的量子比特

  Alice 用她的两个随机比特来创建“监听检测”量子比特。她为其赋值,然后根据 send_had 的值判断是否应用 HAD。实际上,她的量子比特将处于以下状态之一:|0⟩、|1⟩、|+⟩、|−⟩,不过她暂时不会告诉任何人具体是哪个状态。如果 Alice 决定应用 HAD,并且 Bob 想通过读取知道 Alice 发送的是 0 还是 1,那么 Bob 在读取之前,必须应用 HAD 的逆指令(其实就是 HAD)。

// 发送量子比特

  Alice 把她的量子比特发送给 Bob。为清晰起见,本例使用另一个量子比特来表示信道。

// 监听

  如果 Alice 传输的是传统的数字比特,那么监听者只需复制即可完成任务。如果用了量子比特,复制就不可行了。回忆一下,量子比特没有复制指令,监听者唯一能做的就是读取 Alice 发送的量子比特,然后小心地将同样的量子比特发送给 Bob,以免被发现。不过请记住,读取操作将不可避免地破坏量子比特的信息,因此监听者在读取后只能获得传统比特的信息。因为监听者并不知道 Alice 是否应用了 HAD,所以他也不知道在读取前是否应该应用 HAD。如果仅执行读取操作,那么监听者并不知道他接收到的是从一个处于叠加态的量子比特中读取的随机值,还是实际上由 Alice 编码的值。这意味着,监听者不仅无法可靠地提取 Alice 的量子比特,也无法知道应该将什么状态发送给 Bob 才能避免被发现。

// 接收量子比特

  与 Alice 一样,Bob 随机生成一个 recv_had 比特,并根据这个值判断在对 Alice 的量子比特应用 READ 之前是否应用 HAD。这意味着 Bob 偶尔能正确解码 Alice 的二进制值,其他时候则不能。

// 如果选择的操作匹配,但值不一样,就说明通信被监听

  既然量子比特已经被接收,Alice 和 Bob 就可以公开地比较他们是否一致选择了应用 HAD(或选择不应用 HAD)。如果他们碰巧都应用了 HAD(概率约为 50%),那么二者的值应该一致;也就是说,Bob 能正确解码 Alice 的消息。在正确解码的消息中,如果值不一致,就可以得出结论:有人监听了他们的消息,并将不正确的量子比特发送给了 Bob。

2.7 小结

本章介绍了一种描述单个量子比特的方法,以及用于操作单个量子比特的各种 QPU 指令。READ 指令的随机性被用来构建量子随机数生成器。通过控制量子比特的相对相位,可以实现基本的量子密码。

后文将大量使用圆形表示法来可视化量子比特的状态。第 3 章将扩展圆形表示法,以表示多量子比特系统,并介绍用于处理这种系统的 QPU 指令。