本章内容涵盖:
- 理解肖尔算法及其相关性
- 使用经典和量子计算技术解决整数因子分解问题
在本章中,我们讨论目前已知的最著名的量子算法之一。比该算法的结果更重要的是达到该算法的方法。图11.1中展示的思维模型概述了本章内容。
一个快速的例子
在我们解释和讨论肖尔算法之前,让我们先看一些在量子计算机模拟器上调用肖尔算法的真实Java代码。在ch11/quantumfactor示例中,包含了你所需要的一切。我们稍后会讨论这个示例,但现在重要的是知道我们使用Strange来模拟真实量子计算机的行为。如果你运行这个示例,你将会看到以下输出:
将15因式分解为3和5 就是这样。在本书的最后一章中,主要应用程序将15因式分解为3和5。虽然你可以轻松地在经典计算机甚至脑海中完成这个过程,但这是量子计算机可以真正产生影响的一个很好的例子,也是为什么要使用量子计算的一个很好的例子。正如我们之前所说,本章代码的结果并不令人印象深刻。但我们非常强调这个算法有两个重要原因:
一旦拥有具备足够高质量量子比特的量子计算机,肖尔算法的结果将非常惊人,并且实际上对许多当前的加密技术构成威胁。 肖尔采取的在量子计算机上实现这个问题的方法,可能会帮助其他人找到类似的方法来解决不同的问题。 注意:你并不需要量子计算机或量子计算机模拟器来发现15 = 3 × 5这个结果。然而,通过了解量子计算机如何进行整数因子分解,你可以处理类似的问题,并在未来当量子计算机真正强大时受益于量子优势。例如,优化算法和一些机器学习算法可能会使用本章中描述的相同的量子技术。
营销炒作
在关于量子计算的讨论中,人们经常提出一个问题,即量子计算预计会在哪些领域产生重大变革。其中最常见的答案之一就是加密领域。当人们被问及对量子计算的了解时,常常会回答:“它将破解加密。”虽然这并不完全是错误的答案,但它应该放在正确的背景下。显然,这个答案会引发讨论,因此通常会增加对量子计算的兴趣。但有几个要注意的地方:
除了破解加密外,量子计算还有许多令人印象深刻的应用目标。 预计还需要几年时间,量子计算机的能力才足够强大,能够破解当今最常用的加密技术。 第二个要注意的地方也应该谨慎对待。目前的量子计算机绝不可能解密使用2048位RSA密钥发送的消息。然而,这些加密消息今天可以被存储在磁盘上,一旦量子计算机的能力足够强大,它们就可以被解密。可能在2030年代,一些今天的秘密将会被揭示出来。
关于“量子计算将破解当前的加密”这一说法的基本思想是,当今许多加密技术都依赖于这样的假设:对大整数进行因式分解是极其困难的。到目前为止,被分解的最大数字有829位,并且这个过程需要使用Intel Xeon Gold 6130 CPU进行约2700个核心年的计算。由于目前表现最好的算法仍处于次指数时间复杂度类别,增加一个比特几乎使得经典计算机对目标进行因式分解的难度成指数级增长。因此,人们普遍认为,例如2048位密钥是非常安全的。
然而,1994年,Peter Shor发表了一篇题为“Algorithms for Quantum Computation: Discrete Logarithms and Factoring”的论文(ieeexplore.ieee.org/document/36…),在这篇论文中,他解释了量子计算机将如何比最好的经典计算机更快地(特别是更可扩展地)对整数进行因式分解。该算法以其发明者之名被称为Shor算法,已经在量子计算机模拟器(如Strange)和真实量子计算机上实现。尽管结果可能看起来不太令人印象深刻,但由于该算法具有多项式时间复杂度,其真正的好处只有在问题变得更具挑战性且量子计算机中有更稳定的量子比特时才会变得明显。
注意:我们希望确保您没有不切实际的期望。在物理学家中并没有一致的共识认为我们将能够创建一个具备足够稳定量子比特的量子计算机来因式分解2048位密钥。尽管存在这种讨论,但Shor算法的方法非常有趣,它可以作为其他算法和思想的基础。因此,它值得解释一番。
经典因子分解与量子因子分解
许多加密算法都依赖于这样的假设:计算机对于大数的因式分解非常困难。但这真的那么难吗?
由于破解加密是一项有价值的任务,人们进行了大量研究,以找到最佳的因式分解大数的算法。目前,已知的最佳算法属于次指数时间复杂度类。
注意:我们在第1章中讨论了时间复杂度。可能值得重新阅读那部分内容,因为在本章中我们会谈到多项式和指数时间复杂度。
肖尔算法以多项式时间复杂度解决了这个问题。具体的绝对数值取决于许多因素,但从图11.2中,我们可以比较次指数曲线和多项式曲线来了解一般的想法。(坐标轴上的数值不相关,仅为示意)。
这个比较的主要观察结果如下:
对于小位数的数字,次指数方法(例如经典算法)表现得非常好,甚至可能比多项式方法(量子算法)更好。 一旦位数变得足够大,相比于使用量子算法,使用经典算法增加一个比特会使问题变得更加困难。 总的来说,当我们需要对大数进行因式分解时,即通常在处理加密时,肖尔算法显示出其强大的优势。这需要比当今量子计算机上可用的量子比特更多,因此我们尚未看到真正的好处。
一个跨学科的问题
你可以从几个角度来看待肖尔算法。显然,它利用了量子物理学特有的性质,否则它将无法从量子计算中受益。算法本身基于线性代数和数学方程。最后,为了实际应用,它应该用编程语言编写,并与其他软件组件集成。这种跨学科的方法在图11.3中显示出来。
肖尔算法的成功主要与其有前途的性能密切相关。最终的性能结合了图11.3所示三个领域的特性。许多研究工作已经进行,以找到最佳方法,考虑到许多特征:
需要多少量子比特? 需要多少基本门操作? 算法的深度是多少(与可以并行执行多少门操作相关)? 这些问题的答案与我们需要对其进行因式分解的整数的位数相关。可以证明,所需的量子比特数和门操作数都与位数呈多项式关系。
在本章中,我们专注于肖尔算法的软件开发部分。算法的数学和物理背景非常复杂;你可以在Stephane Beauregard的以下论文中找到更多信息:“Circuit for Shor’s Algorithm using 2n+3 qubits” (arxiv.org/abs/quant-p…)。
问题描述
许多加密技术的核心概念是质数。一个整数是质数,如果它只能被1或它本身整除。例如,7是一个质数,而6不是——因为6可以被1、2、3和6整除。
假设你知道两个质数,7和11。计算这两个质数的乘积很容易:
反向操作更加复杂:给定一个由两个未知质数相乘得到的数字,找出这两个质数。在之前的简单情况下,仍然很容易
我们说77可以被分解为7和11。对于这个问题,你甚至不需要计算器。
然而,一旦质数变得更大,问题就变得更加复杂。一个稍微更难的数是64,507。你能快速判断这个数是否可以被分解为两个质数吗?这个问题比分解77要难得多。相反的问题要简单得多:251和257相乘的结果是多少?
这是加密的基本原则之一:从A到B(从因子到数字)很容易,但从B到A(从数字到因子)非常困难。因此,在本章中,我们试图解决的核心问题是:给定一个整数N,找到两个整数a和b,使得N = a * b,并且a和b都大于1。
我们首先以纯粹经典的方式解决这个问题。这对应于思维模型中显示的第一种方法,如图11.4所示。
通常情况下,经典的整数因子分解方法很直接,如图11.5所示。这种方法立即尝试找出给定整数的因子并返回它们。虽然这听起来非常明显,但我们稍后将展示量子方法走了一条不同的路径。
在示例ch11/classicfactor中提供了一个简单的方法来实现这一点。主要方法如下所示。
public static void main (String[] args) {
int target = (int)(10000 * Math.random()); ❶
int f = factor (target); ❷
System.out.println("Factored "+target+" in "
+ f + " and "+target/f); ❸
}
❶ 选择一个介于0和10000之间的随机整数。
❷ 调用factor方法以获取所选整数的一个因子。
❸ 打印获取的因子以及相应的因子,它们相乘得到最初选择的整数。
主方法将工作委托给factor方法,其代码如下所示:
public static int factor (int n) {
int i = 1;
int max = (int) Math.sqrt(n); ❶
while (i++ < max ) { ❷
if (n%i == 0) return i; ❸
}
return n; ❹
}
❶ 我们不需要检查大于原始数的平方根的潜在因子,因为这些因子将对应于小于原始数的平方根的因子。
❷ 尝试每个整数,从2开始,小于原始数的平方根。
❸ 如果候选数能够整除原始整数(此时除法的余数为0),则返回该候选数。
❹ 如果没有找到能够整除的数,返回原始数。
很明显,这是一种非常天真的方法,存在更高效的方法。
肖尔算法的基本原理是什么?
肖尔算法的数学细节超出了本书的范围。如果你感兴趣,可以参考之前提到的Stephane Beauregard的论文,其中包含了实现该算法的电路的详细说明。
然而,这些细节背后的基本原理非常重要,因为它适用于许多潜在的量子算法。肖尔算法将原始问题转化为另一个问题:寻找函数的周期性。我们现在深入研究这个问题,并接下来解释这些问题之间的关系。我们展示如何通过找到特定函数(模指数运算)的周期性来帮助我们找到一个整数的因子。
周期函数
如果一个函数的值在规律的间隔内重复出现,则称该函数为周期函数。这个间隔的长度被称为函数的周期性。
一个有趣的周期函数是模指数运算,其定义如下:
其中a和N是参数,满足a < N。由于模运算符,该函数的结果始终小于N。
为了让你更好地了解这个函数的样子,考虑当a = 7且N = 15时的情况。在这种情况下,图11.6显示了其值为:
这个函数存在一个重复出现的模式。每当我们将x增加4,函数的值与原始值相同。下面的表格显示了x从0到8变化时y的值:
根据这些观察,这个函数的周期性是4。
解决一个不同的问题
找到函数的周期性是一个可以在多项式时间内由量子计算机解决的问题。在进行了量子计算部分后,结果需要再次转换回原始问题。这个过程如图11.7所示。
注意:量子计算机可以为某些算法提供巨大的加速,但并非所有算法都如此。因此,创建量子应用程序的关键通常在于找到一种将原始问题转化为量子计算机可以轻松解决的问题的方法(例如,在多项式时间内而不是指数时间内解决),然后再将其转换回原始领域。
原始问题是找到两个整数,使得它们相乘等于我们想要因式分解的整数N。我们实际上在量子计算机上解决的问题看起来很不同,并且被表述如下:给定整数a和整数N,找到函数周期性。
虽然这个问题看起来与原始问题非常不同,但可以数学证明它们是相关的。一旦我们找到了这个函数的周期性,我们就可以轻松地找到N的因子。
我们不会提供数学证明,但我们将通过查看一些Java代码来展示周期性和因子之间的关系。这段代码遵循的流程如图11.8所示。
在下一节中,我们将解释关键部分——使用量子算法找到模指数运算的周期性是如何实现的。在那之前,我们将使用经典计算编写完整的算法。这是我们思维模型中的第二种方法,如图11.9所示。
在ch11/semiclassicfactor目录中的代码示例包含了Shor算法的经典实现。在查看代码之前,让我们先运行一下这个示例:
mvn compile javafx:run
结果将类似于这样:
We need to factor 493
Pick a random number a, a < N: 6
calculate gcd(a, N):1
period of f = 112
Factored 493 in 17 and 29
这意味着该算法发现493可以被写成17和29的乘积。
现在让我们看一下这个示例的主要方法:
public static void main (String[] args) {
int target = (int)(10000 * Math.random());
int f = factor (target);
System.out.println("Factored "+target+" in "+f+ " and "+target/f);
}
这段代码很简单,与列表11.1中显示的主方法完全相同。它生成一个介于0和10,000之间的随机整数,然后调用factor方法来找到这个整数的一个因子。最后,打印出这个因子和另一个因子。
这个方法将大部分工作委托给factor方法,所以让我们来看一下那个方法。在查看代码时,同时注意图11.8。
public static int factor (int N) {
// PREPROCESSING ❶
System.out.println("We need to factor "+N);
int a = 1+ (int)((N-1) * Math.random()); ❷
System.out.println("Pick a random
number a, a < N: "+a);
int gcdan = gcd(N,a); ❸
System.out.println("calculate gcd(a, N):"+ gcdan);
if (gcdan != 1) return gcdan; ❹
// PERIOD FINDING
int p = findPeriod (a, N); ❺
// POSTPROCESSING
System.out.println("period of f = "+p);
if (p%2 == 1) { ❻
System.out.println("odd period, restart.");
return -1;
}
int md = (int)(Math.pow(a, p/2) +1); ❼
int m2 = md%N;
if (m2 == 0) {
System.out.println("m^p/2 + 1 = 0 mod N,
restart");
return -1;
}
int f2 = (int)Math.pow(a, p/2) -1;
return gcd(N, f2);
}
❶ 预处理部分开始
❷ 选择一个介于1和N之间的随机数a
❸ 计算a和N之间的最大公约数(GCD)
❹ 如果GCD不等于1,那么我们完成了,因为这意味着GCD是N的一个因子。
❺ 找到模指数运算函数的周期性。这是大部分工作,并且在下一个清单中详细说明。
❻ 如果周期性是奇数,我们无法使用它,必须重复这个过程。我们返回-1,通知调用者操作失败。
❼ 对周期性进行一些较小的数学运算,以得到N的一个因子。这仍有可能失败,在这种情况下我们向调用者返回-1。
factor方法调用findPeriod方法来获取函数ax mod N的周期性。这个函数在量子计算机上可以快速执行。
注意:当我们说这个函数可以快速执行时,我们指的是它在多项式时间内完成。
经典的周期性寻找方法
当然,我们也可以在经典计算机上实现这一点,但是对于大数值,这样做会花费很长时间。然而,对于小数值(例如,在我们的代码中小于10,000的数值),这种方法在经典计算机上效果非常好。
从图11.6中可以看出,模指数函数具有明显的周期性。对该函数进行第一次评估,即x = 0时,得到一个值为1。将x增加1返回一个新值,然后出现一个模式。在某个时刻,函数再次评估为1,模式重复。因此,一旦我们确定了应用函数时使值为1的x的值,我们就知道了周期。下面是一个简单的经典Java函数的朴素方法。
public static int findPeriod(int a, int N) {
int r = 1; ❶
long mp = (long) (Math.pow(a,r)) % N; ❷
BigInteger bn = BigInteger.valueOf(N);
BigInteger bi = BigInteger.valueOf(a);
while (mp != 1) { ❸
r++;
BigInteger mpd = bi.pow(r); ❹
BigInteger mpb = mpd.mod(bn); ❺
mp = mpb.longValue();
}
return r; ❻
}
❶ 函数的周期性至少为1。注意对于r = 0,任何ar mod N的值始终为1。
❷ 计算的第一个结果。
❸ 只要结果不是1(即a^0 mod N的结果),我们需要继续增加r并继续计算。
❹ 计算下一个ar的值。
❺ 计算此值与N的模数。
❻ 一旦模数为1,我们得到与r = 0相同的结果,因此r现在保存了周期性的值。
这个函数(找到模指数运算的周期性)也可以在量子计算机上实现,使用具有量子特性的量子算法。这是肖尔算法的核心,我们将在下一节中讨论它。
后处理步骤
虽然算法的最关键部分是找到函数的周期性,但我们仍然需要能够将这个周期性转换为一个因子。这是在后处理步骤中完成的。我们不会为算法提供严格的数学证明,而是会展示不同的步骤和一些值,以便你能够理解发生了什么。
首先,让我们回顾一下我们正在处理的参数:
- N是我们要因式分解的数字:例如,N = 493。
- a是我们用来初始化周期查找部分的随机数,小于N。在我们的示例中,使用的随机数是a = 6。
- p是我们从算法的周期查找部分获得的函数ax mod N的周期。在我们的例子中,p = 112。
由于周期函数的定义,我们知道:
对于x = 0,这意味着:
或者
根据我们拥有的数字,这变成了:
在使用这个结果来找到493的因子之前,我们首先检查它是否正确。虽然可以创建一个Java应用程序来处理这种情况,但通常更容易直接使用jshell,它是Java SDK的一部分,所以您的系统中应该已经有了。JShell是Java的REPL工具,您可以在mng.bz/raYy找到更多关于它的信息。
示例存储库中的ch11/jshell目录中包含一个名为checkperiod的脚本,可以加载到jshell中。该脚本的内容如下:
int N = 493; ❶
int a = 6;
int p = 112;
int u = 1; ❷
for (int i = 0; i < p; i++) {
u = (u * p) % N; ❸
}
System.out.println("This should be 1: " + u); ❹
❶ 将值初始化为我们在示例中使用的值。
❷ 将变量u初始化为1。它将保存计算的结果。
❸ 在这个循环内,执行模指数运算作为多个模乘法。这使得结果保持在N以下,否则结果很快就会变得太大,无法适应Integer值。
❹ 打印结果,我们希望结果等于1。
您可以在脚本中输入条目,也可以在jshell中使用/open命令加载脚本。在这两种情况下,结果都将被打印出来。以下是在shell中执行此操作的结果。请注意,我们在最后添加了一个/list命令,以显示执行的命令列表:
| Welcome to JShell -- Version 17
| For an introduction type: /help intro
jshell> /open jshell/checkperiod
This should be 1: 1
jshell> /list
1 : int N = 493;
2 : int a = 6;
3 : int p = 112;
4 : int u = 1;
5 : for (int i = 0; i < p; i++) {
u = (u * p) % N;
}
6 : System.out.println("This should be 1: " + u);
jshell>
显然,我们走在正确的轨道上。从......开始
由此可见,......
其中k也是一个整数。我们现在使用以下方程式......
然后我们可以将前面的方程式写为......
让我们将其简化如下:
同时
通过将左边和右边的项展开为它们的因子,我们可以得出结论:N的因子(如果有因子的话,如果N是质数则不是)也应该在等式的左侧。因此,N和u + 1的最大公约数(gcd)应该是N的一个因子。
让我们用我们示例中的值来验证这一点。包含checkperiodjshell脚本的同一目录还有一个名为calculatef的脚本,它确切地按照我们所描述的做:
int gcd(int a, int b) { ❶
int x = a > b ? a : b;
int y = x == a ? b : a;
int z = 0;
while (y != 0) {
z = x % y;
x = y;
y = z;
}
return x;
}
int N = 493; ❷
int a = 6;
int p = 112;
int u = 1;
for (int i = 0; i < p/2; i++) { ❸
u = (u * p) % N;
}
System.out.println("This is u mod N: " + u); ❹
System.out.println("This is gcd: " + gcd(u + 1, N)); ❺
❶ 定义一个计算两个整数的最大公约数的函数。我们不详细介绍这个函数,但您可以通过使用您知道的数字调用它来检查它是否有效。
❷ 再次定义了我们在这个例子中使用的值
❸ 按照前面所述计算u的值
❹ 打印u的值
❺ 计算u + 1和N的最大公约数,以找到N的一个因子,并打印它。
在jshell中运行此脚本,通过调用/load jshell/calculatef,结果如下所示:
| Welcome to JShell -- Version 16-ea
| For an introduction type: /help intro
jshell> /open jshell/calculatef
This is u mod N: 407
This is gcd: 17
确实,17是原始值N = 493的一个因子:
在本节中,您成功地计算出了N的一个因子,假设您可以计算函数的周期。目前,周期性的计算仍然是以经典的方式进行的,但在下一节中,我们将介绍量子实现。在这一步中解释的后处理步骤保持不变。
基于量子的实现
让我们回到本章的原始示例,位于ch11/quantumfactor目录中。该示例有一个非常简单的main方法:
public static void main (String[] args) {
int target = 15;
int f = Classic.qfactor (target);
System.out.println("QFactored "+target+" in "+f+ " and "+target/f);
}
这个方法中唯一真正的工作是调用Classic.qfactor API。Strange中的qfactor方法返回所提供整数的一个因子。这个单个因子允许我们计算出另一个因子。例如,如果我们请求整数15的一个因子,并且返回值为3,我们知道5是另一个因子,因为它等于15/3。
qfactor方法在预处理和后处理步骤中使用经典计算,类似于我们在前面的部分所做的。但是在这个方法中,findPeriod方法的实现方式非常不同。在我们的心智模型中,我们将讨论第三种方法,如图11.10所示。
Classic.qfactor的实现可以在Strange的源代码中找到,但它与列表11.2中显示的代码片段非常相似。唯一的区别是findPeriod方法的实现。接下来,我们将解释在Strange中如何使用量子算法实现这个方法。
从列表11.2的代码片段中,我们面临的挑战是找到以下函数的周期性:
不再调用列表11.3中显示的经典函数findPeriod,而是使用在Classic.findPeriod (int a, int mod)中找到的量子实现。请注意,该方法的签名与包含经典实现的方法完全相同。
我们在这里再次应用的一般技巧是:我们尝试一次性评估0到N之间的所有整数的函数,通过创建一个叠加态,然后操纵系统,以便当我们测量结果时,获得一个有用的值。
我们将这个挑战分为两个问题:
- 创建一个周期函数,并进行测量。
- 基于测量计算周期性。
其流程示意如图11.11所示。
第一部分(创建周期函数和进行测量)是使用量子代码完成的,而第二部分则完全使用经典代码。findPeriod函数的实现说明了这种方法:
public static int findPeriod(int a, int mod) {
int p = 0;
while (p == 0) {
p = measurePeriod(a, mod); ❶
}
int period = Computations.fraction(p, mod); ❷
return period;
}
❶ 准备周期函数并进行测量
❷ 使用测量值计算周期性
请注意,第一部分可能不会返回一个有用的结果。在这种情况下,measurePeriod函数返回0,并且再次调用该函数。
Shor算法的重要部分在第一部分完成。我们现在来讨论它,将之前学到的几乎所有内容结合起来。
使用量子门创建周期函数
正如之前所说,我们不会深入探讨证明Shor算法正确性的数学细节。在本节中,我们将提供一种直观的方法,解释为什么这种方法有效。
流程和电路图
算法的这一部分也包含了多个步骤。我们通过展示算法的流程、实现每个步骤的量子电路和代码来强调这些步骤,并对不同的步骤进行稍微详细的讨论。
创建周期函数的流程如图11.12所示。由于这部分是使用量子算法完成的,涉及到了一个量子电路;请参见图11.13。
在这个方案中涉及的量子比特被分为两个寄存器,其中一个寄存器是一组共同具有概念意义的量子比特。顶部寄存器称为输入寄存器,底部寄存器称为辅助寄存器。接下来展示创建周期函数和进行测量的代码。请记住,Strange正在积极开发中,实现可能会发生变化。因此,您在书中看到的代码可能与在Strange或示例中找到的代码不同。
private static int measurePeriod(int a, int mod) { ❶
int length = (int) Math.ceil
(Math.log(mod) / Math.log(2));
int offset = length + 1;
Program p = new Program(2 * length + 3 + offset);
Step prep = new Step();
for (int i = 0; i < offset; i++) {
prep.addGate(new Hadamard(i)); ❷
}
Step prepAnc = new Step(new X(length +1 + offset));
p.addStep(prep);
p.addStep(prepAnc);
for (int i = length - 1;
i > length - 1 - offset; i--) { ❸
int m = 1;
for (int j = 0; j < 1 << i; j++) {
m = m * a % mod;
}
MulModulus mul =
new MulModulus(length, 2 * length, m, mod);
ControlledBlockGate cbg =
new ControlledBlockGate(mul, offset, i);
p.addStep(new Step(cbg));
}
p.addStep(new Step(new InvFourier(offset, 0))); ❹
System.err.println("Calculate periodicity");
Result result = qee.runProgram(p);
Qubit[] q = result.getQubits();
int answer = 0;
for (int i = 0; i < offset; i++) {
answer = answer + q[i].measure()*(1<< i); ❺
}
return answer;
}
❶ 创建一个周期函数 a x % mod 并返回测量结果
❷ 这一步在第一个量子比特寄存器上创建一个叠加态。
❸ 这一步在辅助寄存器上添加执行模指数运算的量子门。
❹ 将第一个寄存器转换为频域
❺ 测量第一个寄存器的值
步骤
在本节中,我们简要讨论在measurePeriod函数中执行的步骤。
创建叠加态
首先,我们对输入寄存器中的每个量子比特应用Hadamard门。这将将输入寄存器带入一个叠加态,因此接下来的计算可以使用输入寄存器可能包含的所有可能值的组合来完成。
这在经典计算机上是无法实现的,这是为什么量子计算机可以更快地处理这个问题的直观指示。然而,请记住,虽然我们可以创建所有可能值的叠加态,但我们只能对系统进行一次测量。因此,我们需要采取一些巧妙的步骤,以使我们获得的单次测量实际上是有用的。
执行模指数运算
接下来,输入寄存器用于计算模指数运算 ax mod N,并将结果计算并存储在辅助寄存器中。由于这个操作的结果,输入寄存器现在成为一个周期函数,其周期为 r,其中 r 是 ax mod N 的周期。
这是有趣的,因为我们现在有一个周期函数。然而,我们只能进行一次测量,而无论我们测量了什么,我们都不会得到关于函数周期性的太多信息。
应用逆量子傅里叶变换(Inverse Quantum Fourier Transform,逆QFT) 通过应用逆量子傅里叶变换,周期函数被转换为一个具有特定“频率”峰值的函数。可以证明概率向量恰好有r个峰值,其中第一个峰值出现在值|0>处,其他峰值均均匀分布。在此之后,概率矩阵具有多个峰值,如图11.14所示。
注意:可以证明我们创建的函数的周期等于应用逆量子傅里叶变换后的峰值数量。这是Shor算法的关键元素。
由于我们有兴趣找到函数的周期,我们需要能够计算峰值的数量。但是我们该如何做到呢?我们只能进行一次测量。这样是否足以确定峰值的数量(因而是函数的周期)?正如我们在下一节中所解释的那样,单次测量确实有合理的机会实现这一点。
计算周期性
如果我们能测量概率向量,我们就能够计算出它有多少个峰值。不幸的是,我们无法做到这一点。我们只能测量量子比特,而单次测量对应概率向量中的单个条目。前面的步骤,创建周期性函数并进行测量,只得到了单个值。该值并不是函数的周期性,但它揭示了足够的信息,我们有希望能推断出周期性。
通过观察图11.14中的概率分布,我们知道测量的值很有可能在概率向量的某个峰值上。基于这一信息,我们有很大可能性能够确定峰值的数量。
我们使用连分数展开算法来实现这一点。该算法以测量的值和答案的最大值作为输入,并返回周期性。该算法在Computations中实现,其签名为:
public static int fraction (double d, int max);
在该算法中,d是测量值除以最大值的结果,因此它是一个介于0和1之间的值,而max是可能返回的最大值。基于测量值在概率分布的峰值上或附近的认识,该算法的结果返回了概率分布中峰值的数量。
有时候我们可能运气不佳,例如,如果测量值恰好在第一个峰值上,即值为0。在这种情况下,我们无法获取关于周期性的任何信息,必须重新进行实验。这意味着我们需要再次执行量子算法。但即使我们不得不多次重复这个实验,算法的复杂性不会改变。如果测量0的几率随着量子比特的数量增加而增加,情况将更糟糕,但实际并非如此。
现在我们知道了周期性,可以用与半量子方法中相同的方式计算出原始数的因子,就像在11.2节的代码示例中所示。现在我们可以继续从后处理步骤开始,计算并打印出这些因子。
注意:恭喜!您成功地应用了Shor算法来分解一个整数!重要的是要记住,虽然该算法的一部分是由量子算法完成的,但恰恰是这部分极大地加快了整体计算时间。
实施挑战
Shor的算法严重依赖于模幂运算。尽管这可能看起来只是一个实现细节,但实际上是一个重大挑战。
在量子计算机上进行数学运算并不是轻而易举的,因为所有门都需要是可逆的,正如我们之前解释的那样。例如,经典电路中的加法运算可以如图11.15所示实现。该门的输入是两个值x和y,它们可能是位(bits),输出是一个值x + y。
这在量子世界中行不通,因为我们无法从 x + y 回到 x 和 y。例如,如果 x + y = 1,我们不知道 x 是 0 而 y 是 1,还是相反,即 y 是 0 而 x 是 1。因此,实现了一个量子加法门,如图11.16所示。
量子加法门的结果保留了原始的 y 值,因此基于 x + y 和 y,可以获取原始的 x 值。因此,该门有一个逆门,可以恢复到原始状态。我们在第7章讨论过量子加法门,所以如果您想了解它的工作原理的细节,可以在那里阅读。
加法门是乘法门的基础,乘法门又是指数门的基础。此外,算术操作还涉及算术运算的模数方面。如果我们想要创建一个用于模指数运算的电路,我们需要能够进行模乘法运算,这意味着我们必须能够进行模加法运算。
虽然在量子计算机上进行基本的算术运算没有直接的优势,但重要的是要意识到这些操作是可用的。例如,正如您刚刚了解到的,Shor's 算法在很大程度上依赖于模指数运算。模指数运算需要模乘法运算,而模乘法运算又需要模加法运算,而模加法运算需要常规的加法运算,这就是我们在这里展示的。
因此,如果您的算法需要这些运算,您可以使用 org.redfx.strange.gate 包中的算术操作。这样,您的算法可以使用 Strange 模拟器进行量子算术运算,但相同的算法也可以在真实的量子计算机上运行,如果有一台可用的话。
总结
- 寻找整数的因子是许多IT领域中一个非常流行的挑战。
- 使用传统方法,在目标(需要进行因子分解的整数)变得更大时,所需的时间将呈指数级增长。
- Shor算法的目标是找到一个整数的因子。
- Shor算法表明,将一个特定问题转化为另一个问题可能是有益的:即通过量子计算机更容易(更快)解决的问题。具体而言,Shor算法将因子分解问题转化为寻找周期函数的周期性问题。