Python-IBM-Quantum-量子计算学习指南-全-
Python IBM Quantum 量子计算学习指南(全)
原文:
zh.annas-archive.org/md5/c49dac206fb26b25455a54bfd5f0f3b7译者:飞龙
前言
自从 IBM 在 2016 年免费在云上发布他们的第一个商业量子系统以来,许多研究人员、开发者、教师、学生和量子爱好者已经在真实的量子计算机上运行他们的实验。从那时起,学术界和工业界都投入了时间和人力来调查量子计算所具有的潜力。量子之旅的第一步是教育,这也是你购买这本书的原因。在这本书中,你将学习量子计算的基本原理以及如何利用这些原理来创建量子算法并在 IBM 强大的量子计算机上运行它们。
这本书通过IBM 量子平台为你提供了量子计算的逐步介绍。你将学习如何自己构建量子程序,以便你可以在自己的行业或领域中发现早期用例并掌握量子计算技能。
你将从简单的程序开始,这些程序说明了量子计算原理与经典计算的不同之处,然后逐渐过渡到更复杂的程序和算法,这些程序和算法利用了高级量子计算算法。
我们将要探索的量子计算原理包括叠加、纠缠和干涉,然后你将熟悉这些原理是如何用于创建量子电路的,你可以在 IBM 量子系统上运行这些电路。
然后,你将了解量子门以及它们如何在量子比特上操作,并发现量子信息科学套件(QISKIT,根据你询问的人不同,发音为 KISS-kit)及其电路库和高级功能,这些可以帮助你构建量子算法。
接下来,你将掌握德意志-约萨、西蒙、格罗弗和肖尔等量子算法,同时可视化如何在 IBM 量子平台上托管的任何量子计算机上创建量子电路并运行算法。
之后,你将通过了解 Qiskit 运行时来探索你的电路如何在量子硬件上运行的基础知识,它具有许多功能来帮助你优化量子电路。
在本书结束时,你将学会自己构建量子程序,并将获得可以应用于你所在行业或领域的实用量子计算技能。
本书面向的对象
这本书是为对量子计算感兴趣并希望利用 Qiskit 扩展他们解决经典难以解决的问题能力的 Python 开发者所写的。需要一些计算机科学和 Python 的背景知识。虽然建议有物理学和线性代数的背景,但这不是完全必需的。
本书涵盖的内容
第一章,探索 IBM 量子工具,将向你介绍 IBM 量子平台上所有可用的工具。这些工具将帮助你快速轻松地开始,无需安装或购买任何东西。
第二章,使用 IBM Quantum Composer 创建量子电路,讨论了这个易于使用的用户界面,这是一个可视化各种量子门和操作如何影响每个量子比特并帮助建立经典计算与量子计算之间差异的直观理解的优秀工具。
第三章,介绍和安装 Qiskit,探讨了量子信息科学套件(Qiskit)及其高级特性,用于开发和实现各种量子算法和噪声模型。Qiskit 具有各种功能,可以帮助您轻松构建量子电路、算法和应用,并允许您在本地模拟器和真实量子系统上运行它们。
第四章,理解基本量子计算原理,通过讨论叠加、纠缠和干涉等基本量子计算原理开始了我们的量子之旅,这些原理被许多量子算法所使用。这也有助于您理解量子计算与经典计算的区别。
第五章,理解量子比特,涵盖了量子比特是什么以及您如何使用各种门和算符在量子系统上操作它,以及如何可视化这些操作的结果。
第六章,理解量子逻辑门,深入探讨了用于改变量子比特和您的量子电路状态的各种量子门和操作。
第七章,使用 Qiskit 编程,是我们开始深入挖掘使用我们迄今为止所学的量子门和操作创建量子电路的地方。您还将了解这些门和操作是如何转换为微波脉冲时序的,这是用于在量子系统上操作量子比特的。
第八章,优化和可视化量子电路,讨论了将您的量子程序中的指令发送到量子系统上运行的过程,其中在后台有一些有趣的工作正在进行。哪些量子比特最适合运行您的电路?我们应该在量子比特之间选择哪些连接以最小化门数?所有这些都将由预设的传递管理器生成器处理,我们将在本章中介绍。
第九章,模拟量子系统和噪声模型,解释了所有量子系统,无论使用什么技术创建,都必须处理噪声问题。在本章中,您将了解这些噪声是什么,以及如何创建模拟它们的模型,以更好地理解它们对您的量子电路产生的影响。
第十章,抑制和减轻量子噪声,解释了如何减轻噪声对量子系统产生的各种影响。
第十一章,理解量子算法深入探讨了基本量子算法,以帮助理解叠加、纠缠和干涉等量子计算原理是如何被使用的。我们还将回顾和编码一些基本概念和算法,这将帮助我们理解更复杂的算法。
第十二章,应用量子算法将带你了解如何将我们所学到的量子计算原理和概念应用到一些复杂的量子算法中。
第十三章,理解量子效用和 Qiskit 模式涵盖了量子效用是什么以及为什么它是我们接近量子优势的关键。它还将概述 Qiskit 模式以及它们如何简化你构建复杂量子电路的开发体验。
附录 A:资源提供了一列你可以用来更详细地探索本书涵盖主题的进一步资源。
附录 B,评估提供了你在每一章末尾找到的问题的答案。
要充分利用本书
-
你需要互联网访问来访问你可用的 IBM 量子系统。由于该平台托管在云上,你将不需要除浏览器和注册免费账户之外的其他任何东西。
-
你还需要一个最新的浏览器(Firefox、Chrome、Safari)
-
操作系统要求(仅当本地安装软件时):Windows、Mac 和 Linux。
下载示例代码文件
本书代码包托管在 GitHub 上,网址为github.com/PacktPublishing/Learning-Quantum-Computing-with-Python-and-IBM-Quantum-Second-Edition。我们还有其他丰富的图书和视频的代码包,可在github.com/PacktPublishing/找到。查看它们吧!
下载彩色图像
我们还提供了一个包含本书中使用的截图/图表彩色图像的 PDF 文件。你可以从这里下载:packt.link/gbp/9781803244808。
使用的约定
本书使用了多种文本约定。
CodeInText:表示文本中的代码单词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 昵称。例如;“只需从你的命令行或 Python 环境中运行pip install qiskit-aer,这就应该足够了。”
代码块应如下设置:
from qiskit_ibm_runtime import QiskitRuntimeService
# Save your IBM Quantum account to allow you to use systems:
QiskitRuntimeService.save_account(channel="ibm_quantum", token='PASTE-API-TOKEN-HERE', set_as_default=True)
当我们希望引起你对代码块中特定部分的注意时,相关的行或项目将被设置为粗体:
import numpy as np
#Bind the parameters with a value, in this case 2π
qc = qc.assign_parameters(parameters={param_theta: 2*np.pi})
#Draw the circuit with the set parameter values
**qc.draw(output='mpl')**
任何命令行输入或输出都应如下编写:
pip install qiskit
粗体:表示新术语、重要单词或您在屏幕上看到的单词,例如在菜单或对话框中,也会在文本中这样显示。例如:“从量子力学中出现的更受欢迎的实验之一是双缝实验。”
警告或重要提示会像这样显示。
小贴士和技巧会像这样显示。
联系我们
我们始终欢迎读者的反馈。
一般反馈:请通过电子邮件feedback@packtpub.com发送,并在邮件主题中提及书籍标题。如果您对本书的任何方面有疑问,请通过电子邮件questions@packtpub.com联系我们。
勘误:尽管我们已经尽一切努力确保内容的准确性,但错误仍然可能发生。如果您在此书中发现错误,我们将不胜感激,如果您能向我们报告,我们将不胜感激。请访问www.packtpub.com/submit-errata,选择您的书籍,点击勘误提交表单链接,并输入详细信息。
盗版:如果您在互联网上以任何形式遇到我们作品的非法副本,如果您能提供地址或网站名称,我们将不胜感激。请通过电子邮件copyright@packtpub.com与我们联系,并提供材料的链接。
如果您有兴趣成为作者:如果您在某个领域有专业知识,并且您有兴趣撰写或为书籍做出贡献,请访问authors.packtpub.com。
留下评论!
感谢您从 Packt Publishing 购买此书——我们希望您喜欢它!您的反馈对我们来说是无价的,它帮助我们改进和成长。一旦您阅读完毕,请花一点时间在亚马逊上留下评论;这只需一分钟,但对像您这样的读者来说意义重大。
扫描下面的二维码以获得您选择的免费电子书。

下载此书的免费 PDF 副本
感谢您购买此书!
您喜欢在路上阅读,但无法携带您的印刷书籍到处走吗?
您的电子书购买是否与您选择的设备不兼容?
请放心,现在,每购买一本 Packt 书籍,您都可以免费获得该书的 DRM 免费 PDF 版本。
在任何地方、任何设备上阅读。直接从您喜欢的技术书籍中搜索、复制和粘贴代码到您的应用程序中。
优惠不会就此结束,您还可以获得独家折扣、时事通讯和每天收件箱中的精彩免费内容。
按照以下简单步骤获取好处:
- 扫描下面的二维码或访问以下链接:

packt.link/free-ebook/9781803244808
-
提交您的购买证明。
-
就这样!我们将直接将您的免费 PDF 和其他好处发送到您的电子邮件。
第一章:探索 IBM 量子工具
近年来,量子计算越来越受欢迎,尤其是在 2016 年 5 月 IBM 发布了第一台商业可用的云量子计算机之后,当时被称为 IBM 量子体验,现在更名为IBM 量子平台(IQP)。这次发布是同类中的第一次,托管在云上,为世界提供了免费实验量子设备的机会。该平台包括一个用户界面(UI),允许任何人在真实的量子计算机上运行实验。最近还增加了直接访问所有文档和学习资源的功能,例如教程和课程,直接从平台访问,使您在学习过程中更容易运行电路。
本章的目标是首先向您介绍 IBM 量子平台,该平台包含了您学习如何在真实量子系统上创建和运行量子电路所需的一切。它还为您提供课程和教程,以便您实验现有的量子算法和应用。IBM 量子平台由以下三个应用程序组成,您可以在平台的右上角找到这些应用程序的列表(见图 1.1):
-
平台:列出您在平台上可以访问的所有作业和系统(仪表板、系统和作业)。
-
文档:提供一系列资源,帮助您入门。资源包括如何设置您的开发环境,在量子系统上构建/测试/执行量子电路。它还提供了量子信息科学套件(Qiskit)开源代码最新版本的 API 文档。
-
学习:为不同水平的用户提供量子课程和教程。这些课程涵盖了量子计算的基础、变分算法设计,以及新增内容:量子安全加密!
您可以通过点击您头像旁边的左上角应用程序图标来选择和切换每个应用程序,切换器在以下图中显示:

图 1.1:应用程序选择
本章将帮助您了解每个应用程序中可用的操作和信息,我们将在后面的章节中更详细地介绍每个应用程序,以给您一个概览,了解所有内容的位置。这包括创建电路、在模拟器和真实量子设备上运行电路、查看您的个人资料和可用的后端系统信息,以及可视化实验结果。那么,让我们开始吧!
本章将涵盖以下主题:
-
开始使用 IBM 量子平台
-
使用文档快速入门
-
理解 IBM 量子工具
技术要求
在本书中,我们期望您在 Python 开发方面有一些经验,尽管这不是必需的,但一些经典和量子力学的基本知识将有所帮助。大部分信息将在每个章节中提供,所以如果您对经典或量子力学没有知识,我们将在这里介绍您需要了解的内容。对于那些在这个领域已有知识的人,这里的信息将作为一个有用的复习。
本书所使用的 Python 编辑器是 Jupyter Notebook。当然,您可以使用任何您选择的 Python 编辑器。这可以包括 Watson Studio、PyCharm、Spyder、Visual Studio Code 等等。
这是本书中使用的源代码:github.com/PacktPublishing/Learning-Quantum-Computing-with-Python-and-IBM-Quantum-Second-Edition
开始使用 IBM 量子平台
如前所述,IBM 量子平台应用程序是您登录后通常会看到的高级视图。在此提及,由于平台在写作时间之后随着技术的发展而更新,工具可能会有更新,因此某些可视化结果可能会有所不同。该平台聚合了您可以看到的多个视图,这有助于您了解您有权访问哪些机器以及您有哪些待处理、正在运行或已完成的工作。
在本节中,我们将介绍注册步骤。让我们开始吧。
注册到 IBM 量子平台
在本节中,我们将注册并解释您首次注册到 IBM 量子平台后后台会发生什么。这将帮助您了解注册后可用的功能和配置。
注册步骤如下:
-
第一步是访问以下链接的 IBM 量子平台网站:
quantum.ibm.com/ -
您应该会看到登录界面,如图 图 1.2 所示。您的具体情况将决定您如何继续操作:
-
如果您已经有了账户或者已经登录,您可以登录并跳过这一部分。
-
如果您尚未注册,您可以从登录界面选择您喜欢的登录方式。如您所见,您可以使用各种方式注册,例如使用您的 IBMid、Google、GitHub、Twitter、LinkedIn 或通过电子邮件。
-
如果您没有上述任何账户类型,您可以简单地注册一个 IBMid 账户并使用该账户登录:
-

图 1.2:IBM 量子平台登录页面
-
一旦您选择了您偏好的登录方式,您将看到该方法的登录屏幕。只需填写信息(如果尚未填写),然后选择登录。
-
登录后,您将进入 IBM 量子平台的主页。这是您每次登录时看到的第一个页面:

图 1.3:IBM 量子主页
现在您已经注册了 IBM 量子平台,让我们快速浏览一下,并深入了解构成主页的一些功能。
首先请注意,在平台应用程序页面的顶部有三个选项卡:仪表板、系统和作业。每个选项卡都为您提供各种信息,我们将在以下章节中详细说明。但在我们开始之前,让我们看看管理账户设置视图。
理解管理账户设置视图
首先,让我们回顾一下主页,特别是管理账户设置视图。您可以通过页面右上角的头像访问您的用户账户和设置视图(如图1.3所示)。
此视图提供了登录用户的个人资料设置,如图1.4所示:

图 1.4:管理账户设置视图
此视图还提供了有关您个人资料的操作,例如设置您的密码、电子邮件、API 令牌和在应用内通知设置。
API 令牌由提供商用于确定您为您的账户配置了哪些系统。当您使用平台时,它会自动生成;但是,如果您决定在平台之外运行笔记本,那么您需要将 API 令牌本地保存。我们将在第七章,使用 Qiskit 和 Qiskit 运行时服务编程中描述如何保存和加载您的账户详情。
在管理账户设置下方是实例列表,它允许您查看您是哪些实例的成员。实例用于确定您根据哪个枢纽、项目或组可以访问哪些 IBM 量子系统,如图1.4所示。此外,在实例列表下方还有删除账户选项,这将删除您所有的账户数据。

图 1.5:实例和删除账户视图
最后,在个人资料设置的底部,您将看到您的通知设置,您可以根据您的偏好启用它们。
熟悉计算资源视图
计算资源视图为您提供了一切可用的量子服务列表,包括量子系统。您可以通过选择位于 IBM 量子平台视图左上角的网格图标,并选择平台选项卡来查看所有可用服务。
当您打开计算资源页面时,您将看到以下图中通过一个框突出显示的下拉选择器对系统的分组。这些分组是您可访问的系统以及所有系统的列表,包括为高级用户预留的系统,如图图 1.6所示:

图 1.6:计算资源视图
该视图包含您可用的所有系统。
每张卡(或如果以表格模式查看则为每行)代表一个不同的量子系统,以及描述每个系统的详细信息。在上一张图中,您可以看到它列出了详细的规格。旁边有锁形图标的系统是为高级用户预留的,例如 IBM 量子网络的活跃成员。
您可以看到的每个系统的详细信息包括:
-
量子比特,即可用的量子比特数量
-
每层门错误(EPLG),这是衡量量子系统质量和性能的最新指标
-
每秒电路层操作数(CLOPS)
-
状态,即系统的可用性(在线、维护等)
-
总待处理作业,即等待在系统(队列)上运行的作业数量
-
处理器类型,即系统的处理器类型和版本
这些信息使您能够可视化所有系统和它们的指标,以便您可以选择一个理想的系统来运行您的量子电路。
第二个选择器用于查看所有实例;这列出了您可用的所有量子系统,包括开放系统以及如果您是量子网络成员,还包括高级量子系统。要将列表以表格形式查看,而不是以卡片形式,您可以选择表格视图(位于系统选择器上方),如图图 1.7所示:

图 1.7:所有可用系统的表格视图 – 显示所有可用的量子系统,包括高级系统
在此视图中,与您的资源视图相同,每一行代表每个量子系统属性(如状态、处理器类型、量子比特数量、量子体积和CLOPS)的详细信息。
CLOPS 是用于确定量子计算机性能的测量之一。它衡量量子处理器执行电路层的速度,类似于用于测量量子计算机量子体积的参数化模型电路。
系统的名称不代表设备的地理位置;城市名称起源于 IBM 研究实验室的位置,并已扩展到包括世界各地 IBM 的办事处所在的城市。在撰写本文时,IBM 量子系统遍布世界各地。最大的群体位于纽约州 Yorktown Heights 的 IBM 研究实验室和 Poughkeepsie。现在在许多地点都有许多本地系统,例如德国 IBM Ehningen(通过与弗劳恩霍夫协会的合作)、东京大学。最近在克利夫兰诊所安装了第一个不在 IBM 网站上的本地系统,还有更多计划在其他国家如西班牙和韩国安装。
在行上方有几个功能;一个是搜索窗口,帮助你找到特定的系统,旁边是过滤器选项,允许你根据提供者、状态或处理器类型缩小可见系统列表。如果你选择表格右上角的卡片选择,你还可以将它们视为卡片。
要查看每个量子系统的更多详细信息,让我们选择一个系统;在这种情况下,我将选择ibm-brisbane,如图 1.8所示。请注意,系统将继续发展,并在撰写本文时可能被替换,所以如果你在这本书中提到的一个系统你没有看到,不要担心;只需选择你喜欢的任何系统,因为您看到的细节将因设备而异。

图 1.8:设备详细信息视图(所有 127 个量子比特的截断视图)
在这个视图中,你可以更详细地检查每个系统。每个量子系统都有一组你可以查看的属性。如果你希望在你的量子程序上运行的系统类型有一些限制或要求,这会非常有帮助——例如,量子比特之间的连接性、每个量子比特及其物理连接的错误率、基本门以及其他我们将在本书的各个章节中详细介绍的细节。
这种视图还允许你下载一个包含所有这些属性的 CSV 文件,这样你就可以使用你想要的任何分析工具来分析它们。要下载属性,只需点击位于校准数据部分最后校准时间下方的下载图标。
你还会注意到,在系统的量子比特映射上方有两个下拉选项可用,其中一个为量子比特提供一组选项,另一个为量子比特之间的连接。这些选项为你提供了查看你希望为每个量子比特和连接渲染的属性的能力。在量子比特选项中,你可以选择查看每个量子比特的详细信息,例如其频率、T1/T2 时间、非简谐性和读出分配错误。连接选项允许你查看每个物理连接量子比特之间的 CNOT 错误和门时间(以纳秒为单位)。
当然,你也可以使用 Qiskit 代码以其他方式编程地获取这些属性,但我们将在此书的后面部分介绍这一点。现在,这只是为了提高意识,所以当你在后面的章节中了解更多关于它们的信息时,你会知道信息所在的位置。
我们现在熟悉了将要运行量子电路的系统及其细节;让我们看看在量子系统完成其任务后,我们如何查看结果。
了解待处理和最新作业
当你将电路发送到模拟器或量子系统运行时,你将想知道电路的状态。这就是作业视图派上用场的地方。要进入作业视图,请返回仪表板左上角的网格图标,并从视图列表中选择作业。一旦视图打开,你将看到一个表格,如图 1.9 所示,其中包含在模拟器或后端设备上待完成的作业的完整列表。你可以使用此视图查看你的电路或程序的状态、作业 ID、使用的提供者和服务以及其他每个提交的作业的详细信息:

图 1.9:作业视图列表
作业 ID 列出,以便你可以在以后调用该作业的详细信息。每个作业最初按创建日期排序,但也可以按状态(完成、待处理或返回)、会话 ID(作业运行的会话的唯一 ID)、计算资源(使用了哪个模拟器或量子系统)或使用情况(表示作业运行时间的时长)进行筛选。
关于作业对象的相关细节将在第七章,使用 Qiskit 编程中介绍。
在本节中,你学习了如何从 IBM 量子平台上的各种视图中找到有关你的实验的信息,以及量子设备的硬件细节。还有一些视图为你提供了开始编程和运行量子计算机上电路所需的工具,这些工具以易于使用的格式提供,无需安装任何软件。
在下一节中,我们将回顾文档应用为你提供的内容,以帮助你开始使用本节中刚刚学到的系统。
使用文档快速入门
在本章的早期部分,我们介绍了你可以访问的系统以及每个系统的详细信息。在本节中,我们将回顾文档应用,它将为你提供有关如何快速启动并运行量子计算机上的量子电路的信息和指导。
首先,从应用选择器中选择文档应用。这将打开如图 1.10 所示的文档页面:

图 1.10:文档应用视图
让我们看看页面顶部,你将看到七个快捷方式,每个快捷方式都能帮助你开始。它们描述如下:
-
概述:当你首次到达包含所有选项的文档页面时看到的视图
-
开始:如何设置你的本地系统并安装 Qiskit
-
构建:如何设计和开发你的第一个量子电路的说明
-
转换:如何优化你的电路映射到所选设备的过程,以确保最高质量和性能
-
验证:如标题所示,如何测试、验证和评估你的量子电路
-
运行:在量子系统上执行你的测试量子电路
-
API 参考:快速链接到常见对象和函数的关键文档
在页面上的开始使用 Qiskit和API 参考部分下方,你还会看到针对那些已经设置好系统并想立即开始运行量子计算机上的量子算法的用户的各种教程。每个图块代表一个不同的教程,它们是相互独立的,所以你可以选择你喜欢的任何一个开始,无需担心任何其他教程的依赖性。
现在你已经知道了如何找到帮助你快速启动所需的文档,让我们继续探索你可以使用 IBM Quantum Composer 生成量子电路的工具。
理解 IBM 量子工具
理解系统和了解我们的电路作业的状态是很好的,但了解如何创建这些电路并运行它们显然是一个重要的步骤。在本节中,我们将回顾你可用到的两种工具。使用应用程序切换器,选择列出的最后一个应用程序,学习。
下图,图 1.11是 IBM 量子学习应用程序视图。这个视图为你提供了一个一站式商店的资源。一开始,你会看到顶部突出显示的是最新发布的课程。在撰写本文时,它是量子算法基础。下面是平台上可用的课程目录,主题从量子信息科学的基础到量子安全加密。

图 1.11:IBM 量子学习应用程序视图
在课程下方,你将看到另外三个部分。第一个是教程列表。这是你在上一节中看到的相同列表。为了完整性,它被复制在这个部分,因为它是当然的学习应用。底部是资源部分,列出了其他有用的学习资源,如图所示:


图 1.12:课程、教程、工具和资源部分。
我们有两种方式来启动每个工具。首先,如图 1.12所示,你可以在工具部分点击每个工具来启动它。第二种方式是在页面顶部选择它,如图 1.11所示。
编曲器是一个图形用户界面,你可以通过将量子门拖放到量子电路上来创建你的量子电路。编曲器还提供了各种结果的可视化表示,例如电路的状态和预期的概率测量。这使得编曲器成为帮助你直观理解各种量子门和属性如何影响量子位本身和整体量子状态结果的绝佳工具。这是一个我强烈推荐你从它开始使用的工具,因为它包含一些非常不错的入门教程,你可以按照这些教程创建你的第一个量子电路并在实际的量子计算机上运行它。我们将在第二章,使用 IBM 量子编曲器创建量子电路中创建一个简单的电路并在量子计算机上运行它。
现在我们已经完成了对 IBM 量子工具的游览,我们准备开始工作。在接下来的章节中,我们将进一步探讨编曲器,并逐步编写量子程序。
摘要
在本章中,我们回顾了 IBM 量子平台,它提供了大量信息,帮助你了解整个情况。你现在知道在哪里可以找到有关你的个人资料、你拥有的每个设备的具体信息、每个设备的状态以及你的实验的状态和结果的信息。某些视图可能根据你的提供商级别而略有不同。我选择在整个书中使用免费开放提供商,以便所有用户都可以看到一般视图。如果你是高级或合作伙伴用户,那么你的视图可能包含更多或更具体的信息和选项,这些信息和选项针对你的提供商。关于这些差异的详细信息超出了本书的范围;然而,你可以咨询你的 IBM 量子代表以获取有关附加视图和角色的详细信息。
知道在哪里找到这些信息将帮助你监控你的实验,并使你能够通过审查你的后端服务、监控队列时间以及查看你的结果队列来理解你的实验状态。
在下一章中,我们将详细了解编曲器,并运行我们第一个量子电路。
问题
-
哪个应用程序包含你的 API 令牌?
-
在你的资源列表中,哪个设备具有最少的量子位?
-
哪个应用程序能为你提供一个量子系统的量子位图?
加入我们的 Discord
加入我们社区的 Discord 空间,与作者和其他读者进行讨论:
packt.link/3FyN1

第二章:使用 IBM 量子作曲家创建量子电路
在本章中,您将学习如何使用 IBM 量子作曲家 以及其每个组件的功能,这些功能与创建和运行实验相关。作曲家将帮助您通过其内置 UI 可视化创建量子电路,这反过来又帮助您可视化量子力学的基本原理,这些原理用于优化您的实验。您还将学习如何预览每个实验的结果并创建您的第一个量子电路。
本章将涵盖以下主题:
-
开始使用量子作曲家
-
使用量子作曲家创建量子电路
到本章结束时,您将了解如何使用 作曲家 创建量子电路,并创建模拟经典门和量子门的实验。您还将学习如何检查实验的各种结果,例如状态向量和它们的概率。这将帮助您了解某些量子门操作如何影响每个量子比特。
技术要求
在本章中,假设读者具备一些计算机基础知识,例如理解经典计算系统中的基本门。
这是本书中使用的完整源代码:github.com/PacktPublishing/Learning-Quantum-Computing-with-Python-and-IBM-Quantum-Second-Edition
开始使用 IBM 量子作曲家
在本节中,我们将回顾 IBM 量子作曲家(以下简称作曲家)布局,以便您了解其在创建或编辑量子电路时的功能和行为。在这里,您还将创建几个电路,利用作曲家的可视化功能使您更容易理解量子电路是如何创建的。因此,让我们从开始:启动作曲家。
启动作曲家
要创建量子电路,首先让我们从打开作曲家开始。要打开作曲家视图,请点击 IBM 量子学习(learning.quantum.ibm.com) 应用程序顶部的 作曲家 按钮,如下截图所示:

图 2.1:启动作曲家
现在您已经打开了作曲家,让我们来看看作曲家编辑器中的每个组件提供了什么。
熟悉作曲家组件
在本节中,我们将熟悉构成作曲家的每个组件。这些组件允许您以各种方式查看实验结果。可视化量子电路的构建将帮助您理解每个量子门如何影响量子比特。
理解作曲家
在本节中,我们将回顾各种功能,以确保您对您可用的所有不同功能有良好的理解。
在图 2.2中,您可以看到作曲家视图的着陆页:

图 2.2:IBM 量子作曲家视图
从前一个屏幕截图,您可以看到作曲家视图,包含三个量子位(q[0],q[1]和q[2])。当您第一次启动作曲家时,这可能看起来并不相同。如果您想添加或删除量子位,您可以简单地通过点击它来选择一个量子位,例如q[1],然后选择加号图标或垃圾桶图标,它将出现在特定的量子位上。
如果有任何视图不可见,这仅仅意味着它们尚未启用。您可以通过选择位于作曲家顶部的视图下拉菜单,悬停在面板上,并选择不可见的视图来启用它们:例如,状态向量视图。
要复制本章中使用的视图,只需添加或删除量子位,直到只剩三个量子位为止。您可以通过点击量子位标签来添加/删除。默认为三个。
现在您已经设置了视图,让我们继续到作曲家视图本身。在下面的屏幕截图中,您可以看到一系列门和操作:

图 2.3:门和操作
所示的每个组件都有特定的功能或操作,它作用于量子位(s),我们将在第六章,理解量子逻辑门中详细讨论。
如以下屏幕截图所示,我们还有电路编辑器本身,这是作曲家的一部分,我们将通过放置各种门和操作来创建我们的量子电路:

图 2.4:电路编辑器
如前一个屏幕截图所示,默认电路包括三个量子位(尽管这可能会随时间而改变),每个量子位都标记为q,并从左到右按顺序附加索引(在这种情况下,q[2],q[1]和q[0])。当我们想要将量子电路的结果映射时,这将很重要。在运行实验之前,每个量子位都初始化为0的初始状态。
最后一行是经典位,这是我们将其映射到每个量子位上的内容,以便当我们完成量子电路的运行时,结果将根据映射传递到经典位。默认情况下,量子位到位的映射是基于量子位的索引。例如,q[0]测量结果将通过测量算子映射到 c[0],我们将在运行量子电路时看到它。您可以通过与量子位相同的方式添加或删除经典位。
在量子位旁边,您将看到一条线,它看起来像从每个量子位延伸出来的导线,在电路编辑器中:

图 2.5:量子位和电路导线
这些线条是您将在这里创建电路的地方,通过在这些线条上放置各种门、操作和屏障。这个电路有三条线,每条线都与量子计算机上的三个量子位之一相关。它被称为作曲家的主要原因是因为这些线条看起来非常类似于音乐家用来作曲的音乐五线谱。在我们的情况下,音乐五线谱上的音符由最终用于创建量子算法的门和操作来表示。
在下一节中,我们将回顾您可用于自定义作曲家视图的各种选项。这将确保您在创建量子电路时只能看到您想看到的内容。
自定义您的视图
在我们的作曲家之旅中继续前进,在作曲家视图的顶部是电路菜单选项,允许您保存您的电路、清除电路或共享您的量子电路:

图 2.6:作曲家菜单选项
首先,我们将介绍如何保存您的电路。为此,只需点击作曲器顶部左边的默认文本,当前显示为未命名电路,并输入您想要的任何标题。理想情况下,选择与实验相关的名称。在这个例子中,让我们称它为MyFirstCircuit,并通过按Enter键或点击标题右侧的勾选图标来保存,如下所示:

图 2.7:重命名电路
在作曲器的顶部,您将看到一系列下拉菜单选项。前面截图中的菜单项有以下选项:
-
文件提供创建和打开电路、复制、导出、共享或删除当前电路的选项。
-
编辑允许您管理您的电路并清除门和算子。
-
视图启用各种视图选项,我们将在以下几节中查看。
现在,让我们在接下来的几节中查看各个不同的视图。
图形编辑器视图
图形编辑器视图包含一些用于创建量子电路的组件:

图 2.8:图形编辑器视图选项
组件包括以下内容:
-
电路作曲家:用于创建量子电路的 UI 组件。
-
操作:一个可用的拖放门和算子列表,用于生成量子电路。
-
选项:设置对齐方式并开启检查功能的能力,这允许您像在 IDE 或浏览器上调试代码一样逐步通过每个门和操作。
-
磁盘:位于电路末端的磁盘,用作添加门和操作时每个量子比特的视觉表示。
现在我们知道了在哪里创建量子电路,让我们继续到显示部分,它提供了各种方式来可视化量子电路的结果。
状态向量视图
状态向量视图允许您预览状态向量结果,也就是说,您的量子电路的量子状态结果。状态向量视图以几种不同的方式展示了量子电路的计算基态。为了简化视图,我已经移除了除了一个量子比特之外的所有量子比特,这样更容易读取值。
如果您愿意,可以这样做,否则您的 x 轴可能不仅仅只有 0 和 1 这两种状态,如下面的图所示:

图 2.9:状态向量视图
首先,我们看到振幅条形图,它表示计算基态的振幅。在这种情况下,如前所述,为了简化,我们将量子比特的数量减少到只有一个量子比特,对于这个量子比特有两个计算基态,0 和 1。这些表示在x轴上。每个基态振幅的值表示在y轴上。在这种情况下,由于我们的电路中没有门或算子,状态向量表示是初始(基态)状态。初始状态表示所有量子比特都设置为 0(零)状态,由振幅值为 1 表示。
在状态向量视图的底部,我们看到代表每个计算基态复数值的输出状态。在这种情况下,由于我们处于初始状态,我们看到1 + 0j处的 0 状态和0 + 0j处的 1 状态。
在左下角是相位轮。相位轮是每个基态相位的颜色视觉表示,其范围在 0 到 2π之间。由于我们没有应用任何相位门,我们看到默认相位 0 由蓝色表示。当您对每个量子比特应用相位偏移时,条形的颜色将根据相位的颜色表示更新。
我们将在后面的章节中更详细地介绍相位,但到目前为止,只需知道它们在哪里以及如何表示,无论是通过状态向量结果中的值还是通过相位轮中的颜色表示。
状态向量信息只是您量子电路的视觉表示之一。在继续之前,我们还想参观几个其他的内容。
概率视图
下一个视图是概率视图。这个视图展示了量子电路(在量子比特上添加单个测量算子)的预期概率结果。如前所述,并在以下屏幕截图所示,由于电路中没有任何算子,显示的结果都是初始状态 0:

图 2.10:概率视图
概率视图是基于期望值的一般结果表示,而不是你将从量子系统中获得的实际结果。这个视图目前表示 Composer 正在经典上计算的内容,因为我们还没有在实际量子计算机上运行这个电路。当我们创建这个电路时,你将看到的结果是由经典系统计算的,而不是由量子系统计算的。量子系统的结果将在我们发送完成的电路运行后接收。
Q 球视图
最后,我们必须回顾的最后一种状态可视化是Q 球视图。Q 球看起来类似于 Bloch 球,它用于表示量子比特当前状态的状态向量。然而,Bloch 球确实有一些局限性,尤其是它只能表示单个量子比特的状态。另一方面,Q 球可以用来在一个球体中直观地表示单个量子比特或多个量子比特的状态信息,包括相位信息。以下截图显示了三个量子比特的电路表示,它们都处于初始状态:

图 2.11:Q 球视图
Q 球视图有两个组成部分;第一个是 Q 球本身,它表示多量子比特状态的状态向量,这个向量以球心为起点。向量的末端是一个较小的球体,它通过 Q 球表面的顶部圆的半径来表示状态的概率。当鼠标悬停在这些小球体上时,可以看到它们所表示的状态。上一张截图展示了初始状态下的三个量子比特,概率为 1,相位角为 0。
数字周围的这些花哨的符号被称为 Ket;我们将在本书的后面学习它们。现在,只需将它们视为标签,以区分数字 0 和量子态|0〉,例如。
第二个组成部分位于左下角,是一个图例,描述了状态相位。由于小球体代表相位角为 0,球体的颜色是蓝色,这与图例对 0 相位所指示的颜色相同。如果状态具有π的相位值,那么球体的颜色将是红色。
这里有多种选项;在右上角你可以看到一个省略号,你可以选择它,提供不同的选项来下载不同图像格式的可视化,以及将视图移动到左侧或右侧。在右下角你可以选择是否启用 Q 球的状态或相位角信息。
好的,我们已经了解了构成 Composer 视图的所有各种视图和组件,所以现在让我们进入有趣的部分,开始创建我们的第一个量子电路!
使用 Quantum Composer 创建量子电路
现在我们已经知道了 Composer 中的所有内容的位置,我们将创建我们的第一个量子电路。这将帮助您更好地理解所有这些组件是如何协同工作的,并且将向您展示这些组件是如何在您构建第一个量子实验时提供诸如当前状态及其概率估计等见解的。
构建具有经典比特行为的量子电路
我们都熟悉一些基本的经典比特门,例如NOT、AND、OR和XOR。这些经典门在比特上执行的行为可以使用量子门在量子电路中重现。我们的第一个实验将涵盖这些基本构建块,这将帮助您理解量子算法和经典算法之间的相关性。
我们的第一个实验将是模拟一个经典门,具体是一个NOT门。NOT门用于改变量子比特的值,在这种情况下,从|0〉状态变为|1〉状态,反之亦然。我们将在第六章,理解量子逻辑门中详细介绍这个门在量子比特上的操作细节。
要在量子电路中模拟NOT门,请按照以下步骤操作:
-
如果您还没有这样做,请从之前创建并命名为
MyFirstCircuit的开放 Composer 编辑器中减少量子比特和经典比特的数量,每个只保留一个。这将简化我们结果的可视化。您可能需要重新打开其他视图,如 qsphere,以获取更新的更改。 -
接下来,点击并拖动
NOT门,该门以
符号表示,从门列表拖到第一个量子比特上,如下面的截图所示:

图 2.12:向第一个量子比特添加 X(NOT)门
- 接下来,点击并拖动测量操作到
NOT门之后的第一个量子比特,q[0]:

图 2.13:向第一个量子比特添加测量算子
- 通过对量子比特进行测量并将其值发送到相关的经典比特,我们实际上是在读取量子比特的状态。您可以通过测量算子和经典比特之间的连接箭头来看到这一点。它还包括经典比特的索引,测量算子将写入的结果,在这种情况下是位置 0 的比特。
注意,结果比特,类似于量子比特,将从左到右排序:例如,c[2]c[1]c[0]。
当您想要观察量子比特的状态时,就会发生测量。这意味着我们将量子比特的状态折叠到0或1。在这个例子中,当我们在NOT门之后测量量子比特时,读数将是1。这是因为初始状态被设置为0,应用NOT门将把它从0翻转到1。
在我们运行这个实验之前,让我们记下几点。首先,请注意经典比特都在一行上。这主要是为了节省空间。接下来是注意,随着我们添加门和算子,所有视图都会更新。如前所述,这是系统在计算这些经典值以提供理想结果。我们尚未指定要在哪台量子计算机上运行此电路,因此您看到的结果是经典系统正在计算的结果,而不是量子计算机的实时结果。
-
选择位于 Composer 视图右上角的设置和运行按钮。这将显示运行设置,如下所示:
![计算机屏幕截图 自动生成的描述]()
图 2.14:运行设置视图
-
运行对话框提示您执行两个步骤:
-
首先,选择您要在其上运行实验的量子系统。选择您希望运行的任何选项。在这个例子中,我们将选择
ibm_brisbane。 -
第二步首先允许您选择提供商。有不同的提供商——
ibm-q/open/main用于开放的免费量子设备,如果您是 IBM 量子网络的成员,那么您将有一个分配给您可用高级量子设备的提供商。
目前,请保持默认设置。此步骤还会提示您选择要运行的量子电路的射击次数。这意味着您希望在实验中让量子电路运行多少次以获得可靠的总体结果。目前,让我们将其设置为
8000。 -
-
现在您已经选择了运行选项,让我们运行电路。点击在 ibm_brisbane 上运行。如果您选择了不同的设备,它将相应地指示。
一旦实验开始,您应该在 Composer 视图左侧面板的作曲家作业视图中看到该实验的条目,表明您的实验处于挂起状态。在作业挂起期间,它将显示作业的状态,相应地。
注意,根据所选设备有多忙,您可能需要等待一段时间才能完成作业。
一旦完成,您将看到指定作业的状态为完成,如下所示:

图 2.15:Composer 作业视图显示所选电路的作业状态
- 完成后,通过点击作业从列表中打开您的实验。这会打开作业结果视图:

图 2.16:作业结果视图
- 一旦打开作业,您可以看到有关作业的一些基本信息,例如顶部的作业 ID,然后是作业完成的日期和时间,运行的底层以及包含有关作业本身详细信息的三个视图,例如状态、详细信息和结果。您还会在右上角看到一个按钮,它将在单独的窗口中提供相同的信息。接下来,让我们回顾一下这些视图。
首先,我们有状态时间线视图,如图 2.17 所示:

图 2.17:作业状态时间线视图
在这里,您可以查看表示完成您的电路所需时间的时间线。每个步骤代表您的电路在量子系统上执行时完成的不同的过程:
创建:作业实例被添加到队列以在特定量子系统上运行的时间和日期。
排队中:您的作业在量子系统上运行之前在队列中的时间长度。
运行:从离开队列并在量子系统上运行,直到返回结果的时间。系统内时间是指电路在量子系统上实际运行的时间,与在经典组件上的时间分开。例如,将电路从数字转换为模拟和从模拟转换为数字不包括在系统内时间值中。
完成:作业在量子系统上完成运行的时间和日期。
接下来是详细信息视图,如图所示,它提供了作业的详细信息;在这种情况下,它是从MyFirstCircuit发送的。它还提供了有关程序、射击次数、电路数量和实例的信息。实例是量子系统的提供;由于我们使用的是开放免费设备,因此这被归类为开放系统。
如果您是高级用户,您可能会以特定于您的提供商的模式运行,您可以从您的管理提供商处获得有关这些细节的信息。

图 2.18:详细信息视图
最后,结果 – 直方图视图,如图 2.19 所示,显示了您的实验结果,以直方图的形式呈现。

图 2.19:作业结果 – 直方图视图
在此视图中,x轴代表每次射击后电路产生的每个状态的频率。y轴代表每个有结果的状态。
所有这些视图都可以通过点击报告右上角的查看更多详细信息按钮在单独的页面上查看。这将提供有关您实验的相同详细信息,还包括转换后的电路图。转换后的图将显示相同的电路,但它将使用指定量子系统的基门。我们将在后面的章节中介绍基门是什么以及它们是如何转换到电路中的。现在,将其视为使用特定于量子系统的门的电路,如下面的屏幕截图所示:

图 2.20:包含原始电路(左侧)和转换后电路(右侧)的详细信息视图
电路图只是电路的三种表示方法之一。其他两个标签将显示Qasm和Qiskit表示。请注意,根据您运行此设备的尺寸,您可能看到所有量子比特的列表(可能超过 100 个量子比特)。在这种情况下,我截断了视图,以便您只能看到几个量子比特以节省空间。
现在我们已经运行了我们的第一个量子电路并得到了结果,让我们更仔细地看看我们的结果,看看我们得到了什么。
检查您的结果
图 2.19中的直方图结果提供了我们实验结果的信息。有些部分可能看起来很简单,但让我们来回顾一下细节。现在可能看起来微不足道,但当我们后来处理更复杂的量子算法时,理解这些结果将证明是无价的。
结果有两个轴。在y轴上,我们有电路的所有可能状态(或测量结果)。这是测量操作在测量量子比特时观察到的。回想一下,我们测量了第一个量子比特,所以从最不重要的位(最右侧)开始,q[0]在每个可能的状态结果中位于最右侧。因此,当我们添加更多量子比特时,它们将附加到前一个量子比特的左侧。例如,一个三量子比特系统将按以下顺序设置,q[2],q[1S],q[0]。我们知道我们的可能结果![img/B18420_02_003.png]是正确的,因为我们在一个量子比特上放置了一个NOT门,这将其状态从 0 变为 1。如果我们再添加两个量子比特,那么第二个和第三个量子比特将简单地执行一个等于测量初始状态的测量,我们知道初始状态是 0,从而产生一个可能的结果![img/B18420_02_004.png]。
x 轴提供了每个可能状态的输出结果。由于我们进行了8000次实验,结果显示我们有很大的可能性第一个量子比特将处于1的状态。结果不是 100%的原因是由于量子设备的噪声。我们将在后面的章节中介绍噪声的话题,但现在我们可以根据我们的结果有信心地认为 NOT 门是有效的。
在本节中,我们在量子设备上模拟了一个简单的NOT门操作,并运行了电路。
摘要
在本章中,你了解了 IBM 量子 Composer 及其许多组件。你创建了一个模拟经典 NOT 门的实验。然后你在直方图上查看结果,并基于结果读取概率。
这为你提供了实验其他门操作的技能,以查看每个操作对每个量子比特的影响以及基于操作结果可能确定或使用的信息。当我们查看一些量子算法以及这些操作如何被利用来解决某些问题时,这将非常有帮助。
在下一组章节中,我们将从 UI 的点击和拖拽操作中移开,而是使用 Jupyter Notebook 创建实验,并开始使用 Python 编写量子电路。
问题
-
从 Composer 中,你可以在哪里找到在量子计算机上运行你的电路所需的时间?
-
你如何在 Composer 中移除或添加一个量子比特到你的电路中?
-
在哪个视图中你会指定运行你的电路的量子系统?
-
哪个球体最适合用来观察三个量子比特在单个球体中的量子状态?
加入我们的 Discord
加入我们的社区 Discord 空间,与作者和其他读者进行讨论:
packt.link/3FyN1

第三章:介绍和安装 Qiskit
在本章中,您将了解量子信息科学套件(Qiskit)及其高级功能,用于开发和管理各种量子算法、量子应用模块和噪声模型。Qiskit(发音为 kiss-kit)包含各种功能,可以帮助您轻松构建量子电路、算法和应用,并允许您在经典模拟器和真实量子系统上运行它们,并可视化结果。在本章中,您还将看到如何在您的本地机器上安装 Qiskit 以创建量子电路并在量子计算机上运行它们的说明。
本章还将讨论如何为开源社区和未来量子应用的开发做出贡献,以及如何通过Qiskit 社区连接到其他志同道合的开发者和爱好者。
本章将涵盖以下主题:
-
理解量子与经典系统互连
-
理解 Qiskit API
-
在您的本地机器上安装和配置 Qiskit
-
从 Qiskit 社区获取支持
技术要求
推荐了解 GitHub,因为我们将在本章中回顾如何为托管在 GitHub 上的 Qiskit 开源项目做出贡献。推荐采用敏捷和开源开发实践,但不是必需的。以下是本书中使用的源代码:github.com/PacktPublishing/Learning-Quantum-Computing-with-Python-and-IBM-Quantum-Second-Edition。
理解量子与经典系统互连
在本节中,我们将回顾量子计算系统如何与经典系统集成。由于量子计算机没有存储量子比特状态的方法,因此需要依赖经典系统为发送到或从量子计算机接收的信息提供持久存储。
由于大多数数据源自经典来源,无论是来自数据存储库还是远程传感器,因此需要准备数据以便在量子系统中使用。同样,量子系统的结果需要以二进制形式返回,而不是量子状态,以便它们可以被读取回经典系统进行任何所需的后期处理。
本节将回顾经典系统和量子系统之间的这种混合或互连性,以便您了解这两个系统如何协同工作以提供最优化结果。Qiskit Runtime,这是 2022 年初引入的新功能,有一些很好的示例您可以尝试。有许多论文描述了其他形式集成经典和量子资源的方法,例如这篇论文,用于量子计算的 Serverless 云集成:arxiv.org/abs/2107.02007。
查看 Qiskit 库
如果您已经学习了前面的一些章节,那么您就会注意到我们使用了 Qiskit 来创建示例电路,我们用这些电路来描述一些量子概念。作为 Python 开发者,您也会注意到 Qiskit 在功能上与使用任何其他 Python 库(如NumPy、scikit-learn等)并无不同。我们在 Python 笔记本中使用它的方式也与使用任何其他包相同,我们可以导入整个包或仅导入类和函数的子集。通过在 Python 中提供 Qiskit 模块,这使得我们能够将经典算法和应用集成到量子系统中。利用 Qiskit 中的库创建在经典开发环境(如 Python)上执行的量子电路,使得与现有经典应用的集成非常顺畅且简单。
Qiskit 与其他大多数开源项目一样,设置起来非常简单,无论是作为 Python 的包,还是如果您只是作为贡献者,作为分支或分叉。它非常紧凑,在本地机器上运行时对资源的需求不大。
将其创建为现有平台(如 Python)的包的另一个优点是无需安装单独的集成开发环境或设置复杂的构建系统,这些系统具有令人困惑的依赖关系。对于那些已经安装了当前支持版本的 Python 的用户,您可以使用简单的pip命令安装 Qiskit:
pip install qiskit
但我们不要过于急躁。现在我们已经了解了 Qiskit 的目的和其一般功能,我们将通过首先了解其组织结构来逐步安装 Qiskit。然后,我们将介绍它是如何与您的经典系统(如笔记本电脑、服务器或云应用)交互的。
理解如何组织和与 Qiskit 交互
如果您的大部分开发工作都是使用 Python,根据这本书的标题,我假设您确实如此,那么您会理解大多数包都是以某种形式创建的层次结构。在最高级别,有应用模块,而较低级别则指每个模块内的组件。
Qiskit 有诸如类或对象之类的组件,在每个组件下,您都有函数和成员。在组织方式上,Qiskit 与其他大多数包并无不同,这使得找到某些功能变得非常容易。
在基础层,您有硬件连接器;这些是连接 Qiskit 到各种量子提供商的东西。除了硬件连接器之外,还有经典模拟器;在这本书中,我们将使用 Qiskit Runtime 服务来运行我们的电路,这包括一些本地安装的 Qiskit 模拟器。当然,由于这些模拟器在经典系统上运行,它们在模拟量子电路的资源数量上是有限的。随着量子电路变得更加复杂,资源成本增长相当快。这一点已在各种出版物中得到强调,其中某些复杂的量子电路可以在经典系统上模拟;然而,各种经典结果之间的准确性存在差异。这就是为什么拥有可供运行复杂电路的硬件连接器很重要,因为它允许您将复杂电路从经典模拟器卸载到运行在实用规模的真正量子系统。您可能会问自己,“什么使电路变得复杂?”好吧,我可以这样说,这并不涉及关系状态。这更多关于电路的宽度,它与量子比特的数量相关,以及电路的深度,这与操作的深度相关,尤其是将两个或更多量子比特纠缠在一起的多量子比特门的数量。随着您在本书中的进展,我们将更详细地介绍这些细节。
在连接器和模拟器之上,是研究人员和开发者所依赖的基本构建块,即 Qiskit Runtime。我们将在后续章节中更深入地探讨 Qiskit Runtime 是什么,但在此阶段,只需将其视为一个提供大量功能以创建、运行和优化您的量子电路的运行时。
在 Qiskit 的先前版本中,模块的设置使得每个算法的领域分布较广:我的意思是,这些模块涵盖了纠错、模拟器、门和电路组件以及应用。在这种情况下,算法开发者必须学习如何将多个模块结合在一起,因此开发者必须理解基础层面的各种组件和模块,以便将它们集成到他们的应用中。自从引入 Qiskit 的最新代码更改后,这种情况已经发生了变化。
首先,我们需要介绍三个开发层或部分,这些层允许开发者相互提供模块以帮助创建量子应用。这三个层与经典应用开发没有太大区别,使得经典开发者可以专注于自己的层,从而消除了完全理解另一层发生的必要。让我们快速看一下这三个开发层。
-
内核开发者是在三个层次中开发代码的人。他们主要工作于创建量子电路、门组合、硬件脉冲级控制和其他接近硬件的功能。Qiskit Terra 是内核开发者将与之合作的模块,其中包括电路库,使他们能够创建新电路或使用现有电路。
-
算法开发者是利用内核开发者创建的电路来创建量子算法的人。这些算法可以提供将经典数据编码成量子状态的方法。例如,像素的信息可以表示为一个量子状态,其中量子比特将代表图像中每个像素的位置和颜色值。他们还可以创建一个不需要编码任何经典数据的量子算法。算法开发者还可以包括一些最新的 Qiskit 特性,如 Qiskit Runtime,以提供基础设施优化,从而使经典和量子系统的集成更加稳健。
-
模型开发者是将算法应用于创建解决现实世界问题的应用程序的人。这些模型开发者可以是领域或行业专家,他们了解经典系统可能难以处理的问题,并确定如何将这些量子算法应用于这些问题。通过为某些问题创建模型,模型开发者可以提供一个量子应用程序,例如,可以作为大型工作流程的一部分或作为服务提供商,经典应用程序可以根据需要调用。
这三个层次共同工作,使开发者能够专注于其层内开发组件,这些组件可以为其他人提供他们需要增强应用程序的工具。这也促进了开发者之间的协作,因为他们可以相互提供反馈,以进一步优化他们的组件。
下面的图示说明了层以及 Qiskit 库或组件通常适用于每种开发者类型。

图 3.1:内核、算法和模型开发者层次
当然,前面的信息是基于 Qiskit 当前版本的。在未来,像许多项目一样,这可能会改变。我强烈建议您保持与当前应用程序编程接口(API)文档的最新同步,以确保在编写代码时使用正确的调用。此 API 可以在 Qiskit 文档页面上找到,网址为www.qiskit.org/documentation/。
文档页面提供了有关四个可用模块的最新信息,通常由于它们的名称而被称为元素,如本章开头所述。
到目前为止,我们已经定义了三个不同的开发层次,这些层次有助于简化开发者跳入编程量子应用的过程,而无需过多的学习曲线。例如,一个模型开发者可能只需要了解量子算法是如何工作的,以便为经典应用创建一个模型,而不是还需要学习创建算法所使用的哪些门。
以下部分将描述 API 参考,以便您了解如何在代码中利用它们。开发的具体细节将在未来的章节中介绍,我们将讨论每个 API 可以提供的功能和操作。
理解 Qiskit API
Qiskit 是为任何想在各个层次和领域与量子计算机一起工作的人而构建的。通过这种方式,我们是指,如果量子研究人员想研究量子设备上脉冲的安排方式,他们可以非常容易地做到这一点。同样,对于只想将他们的应用程序扩展以利用量子计算机来计算信息的人来说也是如此。
在本节中,我们将了解 Qiskit 当今可用的各种 API。如前所述,Qiskit 拥有多种层次,任何领域的专家都可以利用这些层次开始在其应用中使用量子计算。
以化学研究人员为例,他们希望计算两个分子的能量状态,但不想费心学习量子门和脉冲。他们只想将他们的数据集经典地加载到量子算法中,并透明地获得结果。Qiskit 被构建为一个全栈开源软件包,以促进这些以及其他更多用户场景的应用模块。
量子物理学家可以通过研究如何安排脉冲到单比特和多比特硬件级别进行实验。量子研究人员可以致力于开发量子电路,以最小化噪声,这将优化您的量子电路的结果。
算法研究人员和开发者通常致力于创建量子算法,这些算法可以被各个领域和行业使用,要么更快地解决问题,要么提供更准确的结果。
最后,领域研究人员,如化学家、数据科学家、经济学家以及许多人,可以将他们的经典应用集成到量子系统中,利用各种功能,如 Qiskit Runtime,以更优化或更准确地计算复杂问题。
在撰写本文时,Qiskit 已经发布了其最新版本,Qiskit 1.0。这个新版本为您提供了许多出色的功能,将把您的开发技能提升到新的水平。尤其是针对量子实用性的新时代!
让我们从了解 Qiskit 中可用的内置模拟器开始。模拟器非常适合入门,但随着你的开发技能和对量子计算的深入理解,你对运行更复杂电路的需求也会增加,而经典模拟器,如本文中描述的,可能存在一些限制。
Aer
在深入了解 Aer 之前,我们首先确保已经安装了它,因为 Aer 已经迁移到其独立的组件中,并且当开始运行一些模拟时,将其安装到你的系统中是有意义的,因为它现在是你计划在经典系统上运行本地模拟的必要条件。只需从你的命令行或 Python 环境中运行 pip install qiskit-aer 即可,这样应该就足够了。Aer 提供了一个框架,可用于开发调试工具和创建噪声模型。这些工具通过模拟影响不仅仅是量子比特,还包括环境和计算的噪声,从而帮助复制量子系统的大量特性。在 Aer 中通常有五种高度高效的编译模拟器类别可用;它们是:
-
AerProvider,包含所有模拟器的主要类
-
QasmSimulator,一个允许进行模拟方法和选项的量子模拟器
-
StatevectorSimulator,一个理想的量子状态矢量模拟器,用于从你的电路中产生无噪声的结果
-
UnitarySimulator,一个理想的量子单位算子模拟器
-
Pulse,一个用于生成和调度脉冲操作的模拟器
注意,关于Pulse是否将在不久的将来被移除存在讨论。它被包含在这里是为了完整性,但它可能并不总是可用。因此,我们不会在本章中进一步探讨 Pulse。
我们将在接下来的章节中查看每个模拟器类别之间的差异,并在第九章“模拟量子系统和噪声模型”中,你将了解每个类别中特定的模拟器,包括 Aer 中的那些模拟器。
Aer 模拟器
Aer 本身也是一个类别,其中包含其自己的模拟器列表,这些模拟器专门用于获取有关量子电路的特定信息,例如密度矩阵、矩阵乘积状态和多稳定器模拟器。这个模拟器是主要的模拟器,用于再现实际后端系统可能的行为,并包括一个 options 对象,该对象可用于提供如密度矩阵等参数,以再现量子系统中通常发现的噪声。
Qasm 模拟器
Qasm模拟器允许我们在干净和有噪声的模拟环境中运行我们的电路。两者之间的区别是您希望应用到模拟器中的噪声量。一方面,它可以作为一个无错误的理想系统运行,您可以使用它来确认电路的计算结果。另一方面,您可以通过包含噪声模型的模拟器运行您的电路,以便您可以复制噪声并了解它如何影响您的计算。我们将在第十章理解和缓解量子噪声中了解更多关于噪声和噪声模型的内容。
Qasm 模拟器还具有多功能能力和模拟电路的方法,例如statevector、density_matrix、stabilizer、matrix_product_state等。通过允许您使用这些方法中的任何一种来配置 Qasm 模拟器,您可以期望从测量的电路中获得理想的结果,以及您希望结合的任何模型。
Qasm 模拟器还提供了一组后端选项,您可以使用这些选项来执行您的量子电路。这些选项包括设置阈值值以截断结果或设置浮点精度值和执行电路的最大值约束。这些功能使 Aer 成为那些希望开发理想或复制的有噪声系统的理想组件。通常,研究人员使用 Aer 来开发噪声缓解或错误校正技术。
状态向量模拟器
状态向量模拟器,正如其名称所暗示的,是一个状态向量模拟器,它提供电路的最终状态向量,而不包括最后的测量操作。
可以通过利用量子状态的多种可视化工具来可视化状态向量模拟器的结果,例如直方图和城市景观。城市景观选项提供了一个关于密度矩阵的实部和虚部的 3D 视图(
)。其他可视化图包括Hinton图、Pauli 向量图和Bloch 球等。其中一些,如 Bloch 球、qsphere 和其他可视化工具将在未来的章节中介绍,因为它们将帮助您可视化门对量子比特产生的一些效果。
单元模拟器
单元模拟器非常简单,就是那样——它通过计算电路的整体矩阵来提供电路的单元矩阵结果。其理念是,一个仅由单元算子/门组成、每个门操作量子比特子集的电路可以表示为一个单一的单元算子。这可以通过将电路中所有算子矩阵相乘来实现,从而得到一个单一的总体矩阵/算子。
这对于您想要确认对量子比特应用的操作与您的预期计算相匹配非常有帮助。
当您开始处理具有许多算子的多个量子位时,您可以想象这将多么有帮助。单元模拟器有助于提供状态信息,以确保结果符合您的预期。
在您的本地机器上安装和配置 Qiskit
在本节中,我们将向您介绍 Qiskit 的安装过程。需要注意的是,为了完成本书中的示例,您需要在您的机器上本地安装 Qiskit,因为 IBM 量子平台不再有实验室,也没有任何云上运行的电路模拟器,只有实际的量子系统,您将只能有限地访问。安装将包括安装 Anaconda,这是许多 Qiskit 开发者用来安装 Python、Jupyter Notebooks、Qiskit 以及许多其他数据科学包的工具。它还提供了一种简单的方式来管理包以及它们在本地机器上的安装。在我们的案例中,它将帮助我们安装我们将需要的预包装依赖项,例如 Python、Jupyter Notebooks、pip 以及许多其他。
安装完成后,您可以创建一个包含所有依赖项和功能的特定于量子开发的虚拟环境。通过本地安装,您可以从本地系统运行电路到本地设备上的模拟器。
准备安装
Qiskit 是一个开源项目,对所有人免费提供。它遵循 Apache 2.0 许可协议 (apache.org/licenses/LICENSE-2.0)。每个 Qiskit 模块(例如,www.github.com/Qiskit/qiskit/blob/master/LICENSE.txt)中也包含了一份副本。这允许您使用源代码,以及根据许可证定义的所有权利和特权。
Qiskit 的安装相当简单,尤其是如果你已经熟悉名为 pip 的包管理应用。要查看 Qiskit 元数据包信息,例如其当前稳定版本、构建状态和其他细节,请访问 pypi.org/project/qiskit。
我们已经强调,您应该安装完整版本,因为迷你版本存在一些问题。当然,您可以尝试安装任意一个版本,但如果迷你版本出现问题,建议您安装完整版本。
安装 Anaconda
Anaconda (www.anaconda.com/distribution) 是一个开源的跨平台 Python 发行版。它允许用户创建独立的虚拟环境,以便他们可以安装多个 Python 版本。这对你们这些已经在机器上安装了 Python 版本的 Python 开发者来说非常有用。
通过使用 Anaconda 创建一个单独的环境,你可以消除由于安装不同版本的 Python 可能引起的问题,这可能会影响你的现有 Python 项目或应用程序。拥有单独的环境还使你能够拥有多个版本的 Qiskit。在你安装更新时,你需要有一个正在运行的 Qiskit 版本,以便你可以测试你的量子应用程序是否支持最新的发布,而不用担心依赖性问题。
建议遵循 Anaconda 网站上的安装说明。Anaconda 的安装步骤还包括 Jupyter Notebook 的版本,这很有用,因为 Qiskit 笔记本将不会在本地可用。然而,由于 Qiskit 笔记本是基于 Jupyter Notebook 构建的,你不应该期望两者之间有太大的区别。
在使用支持的 Python 版本安装 Anaconda(在撰写本文时,当前支持的版本是 3.9)后,务必在你的安装中创建一个环境,并在继续安装 Qiskit 之前切换到该环境。否则,它将安装在你的基础环境中。在成功完成安装并创建你的 Anaconda 环境后,你现在就可以安装 Qiskit 了!
安装 Qiskit
在安装 Qiskit 之前,务必检查安装页面(docs.quantum.ibm.com/start/install),查看有关安装或配置步骤的任何更新,因为事情可能会发生变化。以下步骤将引导你完成安装过程:
-
我们首先确保你处于你创建的环境中。确定这一点最好的方法是启动你的命令行并输入以下内容:
conda info --envs
前面的代码将列出你系统上的所有环境。你会看到一个标题为base的,另一个是你创建的环境的名称。当前环境通过一个星号标识,如下面的截图所示:

图 3.2:当前环境命令的输出
如前一个截图所示,识别环境的一种方法是查看机器名之前命令行最左侧的内容。在那里,括号内显示的是当前环境。在前一个截图中,我创建了一个名为QiskitEnv的环境。现在,让我们在命令行中运行以下命令来激活环境,以便我们可以启用它并开始安装过程:
conda activate QiskitEnv
这将现在激活你的机器上的环境。
对于详细信息,我建议查看这里关于如何开始使用conda的文档:docs.conda.io/projects/conda/en/latest/user-guide/getting-started.html。
-
在上一步激活 Qiskit 环境后,您现在可以运行
pip命令来安装 Qiskit:>pip install qiskit
根据您的机器和网络速度,这可能需要几分钟时间。
-
完成后,您可以通过在命令行中输入以下内容来验证安装:
>pip list | grep qiskit
这将列出已安装的 Qiskit 包及其相应的版本,您应该看到包括所有各种 Qiskit 库。要查看可选包的最新列表,请访问 Qiskit 元数据包信息页面 pypi.org/project/qiskit。
到此为止,您已经安装并验证了 Qiskit 已安装在本地的设备上。现在,您可以启动一个 Jupyter Notebook 并开始使用 Qiskit!
等等!不要这么快。在我们开始编码和运行电路之前,还有一些步骤需要介绍。我们想确保您的本地机器已配置好。首先,您需要确保您的 令牌 ID 已保存在本地设备上。这样,当您准备好在真实设备或云上的模拟器上运行实验时,您可以非常容易地做到这一点。
配置您的本地 Qiskit 环境
接下来,我们需要安装一些新功能,以利用最新的构建块和可视化包。IBM 运行时包和可选的可视化包将允许您在量子系统上高效地运行电路,并分别可视化您的电路结果。后续步骤包括将您的账户信息保存到您的本地机器上,这将用于连接和使用 IBM 量子系统。
启动并运行所需的步骤如下。注意对于 Mac 用户,括号中的字符串,在本例中为 visualization,需要用单引号括起来(即 'visualization')——否则您将得到一个错误:
-
打开您的终端并运行以下每个命令:
pip install qiskit-ibm-runtime pip install qiskit[visualization]
安装完成后,您可以继续下一步,即在您的本地机器上设置账户信息,通过复制您的账户 API 令牌。
- 您可以从两个地方获取您的 API 令牌;第一个是从 IBM 量子平台页面上的仪表板 (
quantum.ibm.com),如图所示:

图 3.3:仪表板上的 API 令牌
- 获取您的 API 令牌的第二种方式是通过 管理账户 页面。要转到您的账户页面,只需点击页面右上角的头像,然后从下拉列表中选择 管理账户,如图所示:

图 3.4:管理账户页面上的 API 令牌
- 账户页面打开后,点击位于 API 令牌 字段右侧的 复制令牌 图标,如图所示:

图 3.5:复制您的账户 API 令牌
现在您已经复制了您的API 令牌,让我们将其保存在您的本地机器上。
-
通过在命令行中输入以下内容来启动Jupyter Notebook:
jupyter notebook -
由于我们是在本地启动,这里没有创建新笔记本的启动器,因此我们必须自己创建一个。现在让我们通过点击 Jupyter Notebook 右上角的新建 | Python 3来完成,如图所示:

图 3.6:创建新的 Python 3 笔记本
这将创建一个新的 Python 3 笔记本。请注意,这创建了一个空白笔记本,因此我们也没有导入大量常用类和函数的漂亮模板单元格。我们将不得不根据需要导入它们,或者您也可以直接从包含模板单元格的先前创建的笔记本中复制/粘贴模板代码。在以下内容中,我们将根据需要添加类和函数。
-
一旦启动,请将以下内容输入到第一个单元格中。您还希望这样做“
setup_save_account.ipynb"文件,该文件包含您可访问的代码示例。此文件将在早期章节中导入和使用,以便请确保您也更新那里的 API 令牌信息;否则,在运行示例代码时您将看到错误:from qiskit_ibm_runtime import QiskitRuntimeService # Save your IBM Quantum account to allow you to use systems: QiskitRuntimeService.save_account(channel="ibm_quantum", token='PASTE-API-TOKEN-HERE', set_as_default=True)请确保在参数周围包含单引号(
''),否则您可能会收到错误。
现在我们已经将 API 令牌保存在本地,除非我们删除或更改 API 令牌值,否则我们不需要再次将其保存在本地系统中。请记住,按照前面的命令复制您的令牌。
重要提示
注意,您只需运行此命令一次。如果不幸忘记并再次运行上述功能,您可能会收到警告。您可以在 Qiskit API 文档中找到其他账户设置命令:docs.quantum-computing.ibm.com/start/setup-channel#select-and-set-up-an-ibm-quantum-channel。
恭喜!您已成功配置了您的本地 Qiskit 版本!
您现在可以在系统上本地运行电路。当您无法获得网络访问时,现在可以在模拟器上本地创建和执行电路。当然,一旦您重新上线,您可以使用本地版本在真实设备上执行电路。这也允许您轻松地将自己的应用程序或系统集成。通过能够在本地运行代码,您可以轻松地将新代码集成到自己的本地应用程序中。
在本节中,你学习了如何安装 Anaconda,它包括安装 Qiskit 所需的许多依赖项;如何创建量子电路;如何在模拟器上执行电路;以及如何在量子计算机上执行电路。现在,我们将学习如何贡献、协作,并从 Qiskit 全球社区获得支持。
从 Qiskit 社区获得支持
Qiskit 社区是一个全球性的开发者、研究人员和几乎所有对量子计算感兴趣的人组成的群体,他们聚集在一起,协作并相互支持,以帮助所有社区成员构建知识。它还用于让每个人都能了解量子研究、教育、活动和更新的最新动态:www.ibm.com/quantum/events。最近新增的功能是可以通过Qiskit 开发者认证考试获得 Qiskit 开发者认证。目前有一个基于 Qiskit 1.x 版本的更新课程将在 2024 年晚些时候推出。
在本节中,你将了解社区、其众多项目以及你如何贡献并成为Qiskit 活动家(www.ibm.com/quantum/community#advocates)。Qiskit 活动家是 Qiskit 社区的成员,他们已经通过了一项严格的考试,对 Qiskit 社区做出了许多贡献,并在旅途中帮助了许多其他人。让我们首先向你介绍社区本身。
介绍 Qiskit 社区
自从 Qiskit 首次作为开源项目部署以来,开源社区已经贡献了许多功能和改进,它随着时间的推移而不断改进。本身的发展生态系统已经繁荣发展,以至于它被用于世界各地的大学、工业和政府,甚至在南极洲!
Qiskit 社区的成员,通常被称为Qiskitters,通常作为一个稳固的多元化团队一起工作,以确保每个人都得到支持。无论是量子计算的新手还是经验丰富的量子研究人员,他们都对在各种项目上进行协作和连接有着共同的热情。有关 Qiskit 和社区的信息链接可以在 www.ibm.com/quantum/qiskit 找到,在那里你可以在页面顶部和底部找到各种教程链接,以及如何加入 Qiskit 社区和成为世界上最大的量子生态系统的一部分。
早期的一个项目是为那些刚开始接触量子计算的人创建资源。这些资源包括从生成启用材料到YouTube视频系列。这些主题涵盖了硬件和软件,描述了后端发生的事情,以及描述其他人正在进行的新的研究。除了资源之外,还有在世界各地任何给定时间都计划举行的活动。这包括研讨会等活动,社区成员可以亲自或虚拟地参加,以了解量子计算的最新进展。
其他活动还包括黑客马拉松和编码营,其中最大的活动是Qiskit Camp,IBM 量子团队在每个季度在世界不同大陆举办。为期 3 到 4 天的营通常包括在非常异国他乡的住宿、餐食、机场往返交通等。来自IBM 研究的研究人员也作为讲师、教练和评委参与。团队被创建出来,在周末共同构思项目想法,他们一起工作,竞争并赢得奖品。这非常类似于黑客马拉松。
最近,Qiskit 社区启动了Qiskit 倡导者计划。该计划旨在为那些积极参与 Qiskit 社区并在一段时间内做出贡献的个人提供支持。要成为 Qiskit 倡导者,您需要在线申请(www.ibm.com/quantum/community#advocates),在那里您将接受考试以测试您对 Qiskit 的了解,并至少指定三个社区贡献。当然,这些资格可能会随时间而变化,因此建议您检查网站以获取任何更新和申请截止日期。
一旦被接受加入 Qiskit 倡导者计划,您将有机会与其他专家建立联系,并访问 Qiskit 开发团队的核心成员。您还将通过 Qiskit 社区从 IBM 获得支持和认可,并收到参加 Qiskit 暑期学校、研讨会和其他重大活动的邀请,在这些活动中,您不仅可以与其他人合作,还可以担任领导或导师。
为 Qiskit 社区做出贡献
成员间的支持至关重要,不仅对于 Qiskit 倡导者,对于所有成员也是如此。Qiskit 社区已经建立了各种渠道,为社区的所有成员提供支持。他们有一个非常活跃的Slack 工作空间(qisk.it/join-slack),拥有多个频道,成员可以提问、发布活动更新,或者只是讨论最近发表的量子研究。还有其他 Qiskit 通过的合作资源。当前的合作工具列表可以在主要量子社区页面上找到:www.ibm.com/quantum/community。
在 Qiskit 社区中专业化你的技能集
关于为 Qiskit 社区做出贡献,尤其是那些有兴趣成为 Qiskit 拥趸的人提出的最常见问题之一是,你可以通过哪些不同的方式做出贡献? 你可以通过许多方式为 Qiskit 社区做出贡献。理想情况下,你希望熟悉不同的贡献形式,如下所示:
-
代码贡献:如果你是开发者,添加新功能、优化函数性能和修复错误是一些好的开始方式。如果你是编程新手,Qiskit 开发团队为此创建了一个标签,称为 good first issue。这是一个涵盖对新代码库新手理想的议题的通用术语。
-
在你的地区或虚拟举办 Qiskit 活动:你可以举办活动并邀请 Qiskit 拥趸进行研讨会或与一群人讨论 Qiskit 的最新更新。
-
帮助他人:你可以通过回答其他社区成员提出的问题、报告错误、识别可能增强电路开发的功能等方式来帮助他人。
在噪声缓解、错误纠正或算法设计等领域专业化是社区的一个优势。Qiskit Slack 社区有几个专注于量子计算特定领域的频道:量子系统、IBM Quantum 平台、Qiskit Runtime、量子算法和应用、Qiskit 在 Raspberry Pi 上,等等。如果你在这些领域中的任何一个领域专业化,你可以加入 Slack 群组,并在许多技术和主题上进行合作。
在本节中,你了解了开源贡献流程以及如何找到适合初学者和专家的任务,以便每个人都能做出贡献。
摘要
在本章中,你了解了 Qiskit 提供的通用功能和特性,以便你可以创建高效的量子算法。然后,你学习了如何在本地安装 Qiskit,以及如何为 Qiskit 社区做出贡献并寻求支持。
我们更多地了解了 Qiskit 以及它在开发堆栈中的位置,并概述了 Qiskit 库中提供的应用模块和模拟器。这为你提供了创建电路的一般技能和功能,然后你可以使用这些电路通过门和算子对量子比特应用各种操作。
然后,我们学习了 Aer,它允许我们创建更好的模拟器,以及如何在本地和 IBM Quantum 平台上执行它们。
你学习了如何在你的平台上使用 Anaconda 安装你自己的 Qiskit 版本。最后,我们了解了 Qiskit 社区及其对所有人,尤其是那些对量子计算新手需要一点支持以理解一些具有挑战性的内容或找到合作者以拓宽视野的人的优势。
现在,你已经拥有了在本地机器上安装和配置 Qiskit 的技能,以便在离线模式下创建和执行量子电路。
在下一章中,我们将开始深入了解量子计算的基本原理,以便我们学习如何创建和执行量子电路。
提问
-
用你自己的话描述内核开发者和应用程序开发者之间的区别。
-
如果你想要获取电路的单位矩阵,哪个模拟器会提供单位矩阵的结果?
-
请用你自己的话命名并描述 Aer 提供的五个模拟器类别。
-
你需要导入哪个模块来绘制直方图?
加入我们的 Discord 社区
加入我们社区的 Discord 空间,与作者和其他读者进行讨论:
packt.link/3FyN1

第四章:理解基本量子计算原理
量子计算,尤其是其算法,利用了三个量子计算原理,即叠加、纠缠和干涉。在本章中,我们将回顾这些原理,以便我们了解它们各自提供的内容,它们对每个量子比特的影响,以及如何使用我们提供的量子门集来表示它们。在IBM 量子平台上运行的量子计算机利用各种量子门,其中一些你在本书的前面部分已经使用过。
本章将涵盖以下主题:
-
介绍量子计算
-
理解叠加
-
理解纠缠
-
理解干涉
-
探索贝尔态
技术要求
在本章中,建议你具备一些物理学的一般知识;然而,我的目标是让解释帮助你理解量子原理,而无需你注册物理课程。以下是本书中使用的完整源代码:github.com/PacktPublishing/Learn-Quantum-Computing-with-Python-and-IBM-Quantum-Experience。
介绍量子计算
量子计算并不是像学习代数或阅读一些文学经典那样常见的主题。然而,对于大多数科学家和工程师,或者任何包括学习物理在内的其他领域的人来说,量子计算是课程的一部分。对于我们那些不太记得物理学习内容,或者从未学习过物理的人来说,不必担心,因为本节旨在提供信息,以刷新你对这个主题的记忆,或者至少,也许,帮助你理解量子计算中使用的每个原理的含义。让我们从量子力学的一般定义开始。
量子力学,如大多数文本所定义的,是研究自然在其最小尺度上的学问——在这种情况下,是亚原子尺度。量子力学的研究并非新鲜事物。它的增长始于 20 世纪初,得益于许多物理学家的努力,他们的名字至今仍在许多当前的理论和实验中回响。这些物理学家的名字包括埃尔温·薛定谔、马克斯·普朗克、维尔纳·海森堡、马克斯·玻恩、保罗·狄拉克和阿尔伯特·爱因斯坦等。随着时间的推移,许多其他科学家在量子力学的基础上进行了扩展,并开始进行实验,挑战了许多经典理论,如光电效应以及更现代的方法,如波函数,它被用来提供粒子的各种物理属性。
从量子力学中涌现出的更受欢迎的实验之一是双缝实验。尽管这在经典力学中是存在的,但它被引用在量子计算中,用来描述量子比特(qubit)的行为。正是在这个实验中,研究人员能够证明光(或光子)可以同时被描述为波和粒子。
这些年来,许多不同的实验已经进行了,以说明这一现象,其中之一是将光子粒子一个接一个地通过双缝发射,在双缝的另一侧,有一个屏幕捕捉每个粒子击中的位置,作为一个点。当只有一个狭缝打开时,所有粒子都会以点堆的形式出现在狭缝后面,如下面的图所示:

图 4.1:单缝实验(图片来源:https://commons.wikimedia.org/wiki/File:SingleSlitDiffraction.GIF)
从前面的图中,你可以看到所有粒子都被捕获在直接穿过狭缝的区域。在这里,角度θ
表示从狭缝(图案)中心到第一个最小强度的角度。
然而,当第二个狭缝打开时,想象中屏幕上会有一个相同的点堆,因此有两个堆。但事实并非如此,因为捕捉到的图像似乎与从粒子那里预期的完全不同。实际上,它具有波的特性,因为屏幕上的点似乎显示出衍射图案,如下面的图所示:

图 4.2:双缝实验(图片来源:https://commons.wikimedia.org/wiki/File:Double-slit.PNG)
从前面的图中,你可以看到所有粒子都从中心向外扩散,形成干涉条纹。
这种衍射图案是由通过狭缝的光波相互干涉造成的。在这里,屏幕中心比观察屏幕的外端有更多的点。这种单个粒子的干涉是现在所知的波粒二象性的基础,它通常推断光子可以同时表现出波和粒子的特性。这种特性被用于量子计算,特别是在 Grover 算法和 Shor 算法等算法中。
这种波粒二象性现象催生了大量有趣的研究和开发,如哥本哈根解释、多世界解释和德布罗意-波姆理论。
这说明光以一定的概率出现在板上的某些区域,形成光带。通过观察前面的图示,你可以看到电子从枪中发射后更有可能落在屏幕的中心带,而不是外带,如较深的灰色阴影所示。此外,由于干涉,捕获电子的带之间的空间概率较低(带之间的空白区域)。
本章我们将涵盖这些波干涉和概率效应,但首先,我们将从电子本身开始,以理解叠加原理。
理解叠加原理
叠加是我们通常无法用肉眼看到的。它被定义为同时发生的两种相似但不同的现象的组合:例如,同时吹口哨和哼歌。两者相同之处在于它们都是可听见的波,但不同之处在于它们的声音。你可以单独吹口哨而不哼歌,反之亦然;然而,同时做这两件事就是将它们置于叠加状态,因为你同时创造了两种不同的声音组合。
在量子力学中,我们通常讨论的是电子的叠加。由于电子非常小,而且数量众多,即使使用高倍显微镜也难以区分它们。它们通常被称为基本粒子。然而,在经典世界中,有一些类比可以帮助我们说明叠加原理。例如,旋转的硬币是大多数文本用来描述叠加原理的例子。
当硬币旋转时,我们可以认为它同时处于正面和反面的状态。直到硬币坍缩,我们才能看到硬币的最终状态。这从概率的角度解释了叠加;然而,正式的定义通常可以在任何经典物理书中找到,当描述电子的自旋时。我将留给你去搜索,因为网上有大量的参考资料和资源可以深入了解。
在本章中,我们将使用旋转硬币的类比,只是为了帮助你理解叠加原理的一般原理。然而,一旦我们开始构建量子电路,你将看到叠加及其在经典世界和量子世界中的概率行为之间的差异。
让我们先回顾一下在经典世界中狭缝实验中观察到的随机效应。
了解经典随机性
之前,我们以旋转硬币的随机性为例进行了讨论。然而,旋转硬币及其结果并不像我们想象的那样随机。仅仅因为我们无法在桌子上旋转硬币或在空中抛掷硬币时猜出正确答案,并不意味着它是随机的。让我们相信它是随机的是这样一个事实:我们没有所有必要的信息来了解、预测或实际上确定硬币会落在正面还是反面。
所有相关的信息,例如硬币的重量、其形状、旋转硬币所需的力、空气阻力、硬币滚动平台的摩擦力等等,以及环境本身的信息,我们都不了解,以便我们确定旋转硬币后的结果会是什么。正是因为这种信息不足,我们才假设硬币的旋转是随机的。如果我们有一个能够计算所有这些信息的函数,那么我们就可以始终成功地确定旋转硬币的结果。
这同样适用于随机数生成器。例如,当我们触发计算机生成随机数时,计算机使用各种信息来计算和生成所谓的随机数。这些参数可以包括请求触发时的当前时间、关于用户或系统本身的信息等等。
这类随机数生成器通常被称为伪随机数生成器(PSRN)或确定性随机比特(DRB)生成器。它们的随机性仅限于允许的计算或种子值。例如,如果我们知道用于生成这个随机数的参数以及它们是如何被使用的,那么我们就可以确定每次生成的随机数。
现在我不想让你担心任何人确定你可能生成的计算或加密密钥。我们使用这些 PSRN 生成器是因为它们包含的精度和粒度可以生成这样的数字,任何偏差都会极大地改变结果。
那么,为什么还要回顾旋转硬币的概率和随机性质呢? 一,是为了解释经典世界中的随机性,或者说我们认为是随机性,与量子世界中的随机性之间的区别。二,在某个时候,我们将需要放弃任何形式的经典类比,并接受量子行为并不明显也不容易测量的这一事实。如果您希望更深入地了解这些现象,这些现象我们无法轻易用经典物理学或类比来描述,我建议阅读海森堡不确定性原理。
在经典世界中,我们了解到如果我们拥有所有可用信息,我们很可能确定一个结果。然而,在上一节中,当我们描述双缝实验时,我们看到我们无法确定电子会在屏幕上的哪个位置击中。我们根据实验理解了它可能落下的概率。但即使如此,我们也不能确定地识别电子在屏幕上的确切位置。当我们在下一节创建我们的叠加电路时,你将看到这个例子。
对于那些想了解更多关于这个光子现象的人来说,我建议阅读著名物理学家理查德·费曼所著的书籍 QED: 光和物质的奇异理论。
准备一个处于叠加态的量子比特
在本节中,我们将创建一个包含单个量子比特的电路,并在量子比特上设置一个操作员以将其置于叠加态。但在我们这样做之前,让我们快速定义什么是叠加态。
我们将量子比特定义为具有两个基态能量,其中一个基态是基态(0)状态,另一个基态是激发态(1)状态,如图 4.3 所示。每个基态的状态值名称可以是任何我们选择的,但由于我们的电路结果将反馈到经典系统中,我们将使用二进制值来定义我们的状态——在这种情况下,二进制值 0 和 1。说两个状态的叠加是同时处于 0 和 1是不正确的。正确表述量子比特处于叠加态的方式是说它处于一个复杂线性组合的状态中,在这种情况下,状态是 0 和 1。
这个简单的类比可能就是将比特想象成一个标准的开关。在一个位置上,灯是亮的,在另一个位置上,灯是关的。它要么是其中一个,要么是另一个。这与比特类似,要么是0,要么是1(分别对应关和开)。现在考虑一个调光开关(技术上称为变阻器),其中开关可以旋转到开的位置,然后旋转到底部关的位置。你还可以用调光开关将开关滑动到开和关之间的任何位置,这反过来会调整发出的光的强度或振幅。现在,当你将调光开关置于开和关之间时,你不会说灯“同时开和关”吧?当然不会。这更多的是两种状态的结合。当然,记住,这并不是量子比特的定义,它只是说明说某物同时是两件事并不完全正确的一个简单例子。
可视化量子比特的状态通常很困难,尤其是多量子比特状态,它们涉及多个量子比特和多个量子态。早期开发的一种可视化模型是 2 态球体,它提供了一个二维量子力学系统(称为布洛赫球)的几何表示。以下是一个布洛赫球的示例,它代表一个量子比特及其两个正交基态,这些基态位于相反的极点。在北极,我们有基态![img/B18420_04_006.png],而在南极,我们有基态![img/B18420_04_007.png]。围绕基态值的符号是大多数量子计算文本中常用的符号。这被称为狄拉克符号,它是以英国理论物理学家保罗·狄拉克的名字命名的,他首先构想了这个符号,他称之为括号符号。括号和狄拉克符号通常可以互换使用,因为它们指的是同一件事,我们稍后会看到。每个都有其独特的形式,括号具有以下形式,![img/_eqn_004.png],而括号具有以下形式,![img/B18420_04_006.png],其中每个分别表示一个数学线性形式和向量。

图 4.3:在布洛赫球上的一个量子比特的两个基态
好的,那么让我们停止谈论,开始编码。我们将创建一个包含单个量子比特的量子电路。然后我们将执行该电路,以便我们可以获得与前面截图相同的结果,即量子比特的初始状态,状态![img/B18420_04_006.png]。
在我们开始之前,首先将辅助文件导入到你的工作目录中。它包含一系列函数,将帮助我们以两种方式。首先,它将提供一系列函数,我们可以快速使用它们来执行电路,而不必立即覆盖任何细节。但不用担心,随着我们继续阅读本书,你将学习到这些细节,辅助文件的依赖性肯定会消失。其次,这也有助于保持代码库的更新,因为新功能和更改发生时,可以更新代码以保持远超当前版本。辅助文件的位置在本章开头提到的 GitHub 仓库中,标题为helper_file_1.0.ipynb。请注意,如果你想使用量子系统而不是设备上的本地模拟器,你可能需要设置你的账户。如果是这样,请打开setup_save_account.ipynb文件,并在指定的属性中输入你的 API 令牌并运行该文件。一旦这样做,它将在你的本地机器上保存你的 API,因此你不必每次都设置。
打开一个新的 Qiskit 笔记本,并在下一个空单元格中输入以下代码:
# Load helper file
%run helper_file_1.0.ipynb
# Create a simple circuit
qc = QuantumCircuit(1,1)
# Get the state vector of the circuit
stateVectorResult = Statevector(qc)
# Display the state vector results onto a Bloch sphere
plot_bloch_multivector(stateVectorResult)
第一行将加载辅助文件到工作笔记本中。该文件包含我们将使用的函数,例如用于在模拟器和后端系统上执行电路并返回电路结果的函数。下一行创建一个包含 1 个量子比特和 1 个经典比特的量子电路,在下一行我们将量子电路传递给Statevector对象,这将生成量子电路的状态向量。这将返回结果对象,其中将包含状态向量结果。最后,我们在 Bloch 球体上显示结果,应该显示你在图 4.3中看到的内容。
每个量子比特,如前所述,由两个基态组成,在这个例子中,它们位于 Bloch 球体的相反极点。这两个基态就是我们提交给经典系统的结果——要么是一个,要么是另一个。代表这两个点的向量起源于 Bloch 球体的原点,正如你可以在之前的图中或实验结果中看到的那样。如果我们将其表示为一个向量,我们将写成以下形式:
![img/B18420_04_002.png]
由于相反的情况适用于相反的极点,我们将如下表示:
![img/B18420_04_003.png]
从观察向量值中,你可以看到翻转向量的值就像经典比特翻转一样。现在我们理解了量子比特的向量表示,让我们继续并设置量子比特处于叠加态:
-
在当前笔记本的底部插入一个新单元格,并输入以下代码:
# Place the qubit in a superposition state # by adding a Hadamard (H) gate qc.h(0) # Draw the circuit qc.draw(output='mpl')- 第一行将Hadamard(H)门放置在第一个量子比特上,该量子比特由量子比特的索引值(
0)标识。然后它调用draw()函数,这将绘制电路图;请注意,添加了output参数只是为了获得更好的输出。如果你想要包括这些可视化功能,请确保从 pip 中安装qiskit[visualization](pip install qiskit[visualization])。否则,你可以删除该参数并获取标准的文本可视化输出。
- 第一行将Hadamard(H)门放置在第一个量子比特上,该量子比特由量子比特的索引值(
运行上一个单元格后,你应该看到以下电路图像,它表示将 Hadamard 门添加到量子比特和其下方的经典比特:

图 4.4:添加了 Hadamard(H)门的量子比特电路
Hadamard 门(H 门)是一个量子门,它将量子比特置于叠加态,或者更具体地说,是基态的复线性组合,这意味着当我们测量量子比特时,它将以相等的概率测量到 0 或 1。或者换句话说,它将塌缩到基态值之一,如
或![img/B18420_02_003.png]。
从数学上讲,叠加状态是通过应用 Hadamard 门获得的,其结果表示在以下两个叠加方程中,正如你所看到的,这取决于在应用 Hadamard 门之前它处于哪个基态,
或
。第一个叠加方程
如下,起源于
状态,通常被称为正
叠加状态:

第二个叠加方程
,起源于
状态,如下所示,通常被称为负
叠加状态:

在 Bloch 球上,这等于对 Bloch 球的X和Z轴进行π⁄2 旋转。这些旋转是笛卡尔旋转,围绕指定的轴逆时针旋转,在这种情况下,是X和Z轴。
-
现在,让我们获取电路的状态向量,并查看结果量子状态将是什么样子,以及状态向量在 Bloch 球上的位置。在下面的代码中,你将调用
Statevector对象并将量子电路传递给constructor参数,这将改变 qubit 的状态,从初始状态变为叠加状态,你将在结果 Bloch 球输出中看到:# Get the state vector of the circuit stateVectorResult = Statevector(qc) # Display the Bloch sphere plot_bloch_multivector(stateVectorResult)
你现在应该能看到结果在 Bloch 球上以
和
的叠加形式绘制出来,如图所示:

图 4.5:90°旋转后的 qubit 的叠加状态
如前一个屏幕截图所示,这已经将向量放置在正X轴上,正如之前在从
基态添加 H 门时描述的那样。需要注意的是,从视觉上看,这也可以通过将Y轴旋转 90 度来完成。
-
现在,让我们通过重新创建具有相同名称的
QuantumCircuit对象来清除电路。这次,我们将首先将 qubit 初始化为
状态,然后应用 Hadamard 门来观察向量会发生什么。将qubit初始化为
状态并将其置于叠加状态。在应用 Hadamard 门之前,清除电路并将 qubit 初始化为1:#Reset our quantum circuit qc = QuantumCircuit(1) #Rotate the qubit from 0 to 1 using the X (NOT) gate qc.x(0) #Add a Hadamard gate qc.h(0) #Draw the circuit qc.draw(output='mpl')
你现在应该看到以下电路;在这种情况下,我们在构建量子电路时省略了经典比特,这就是为什么你不会在下面的图中看到经典比特:

图 4.6:对基态
应用 H 门叠加
- 现在,让我们执行电路,并使用与执行先前电路相同的代码在布洛赫球上绘制结果:

图 4.7:从
状态进行 90°绕 X 和 Z 轴旋转后的量子比特叠加
你能看出在
*状态(图 4.5)中向一个量子比特添加一个 H 门与在先前的图中向
*状态中的量子比特添加它之间的区别吗?
当然,区别在于它落在 X 轴上的位置!因为当对
状态应用哈达玛门时,向量落在正 X 轴上,这通常表示为
。从逻辑上讲,这意味着当对
状态应用哈达玛门时,向量落在负 X 轴上。这通常表示为
。
现在,看看叠加方程的右侧,并注意其中的符号:


注意,符号与哈达玛门应用后向量落点的方向相匹配。从
状态,它移动到 X 轴的正方向(+),而从
状态,它移动到 X 轴的负方向(-)。
这种差异被称为两个结果之间的相位差。这在本书的后续章节中将会非常重要,因为相位差在许多量子算法中扮演着重要角色,并且它将自身融入到我们很快将要学习的干涉主题中。
在我们继续之前,我们最后要讨论的一件事是现在回顾一下我们之前关于概率的讨论。现在我们已经学会了电路和布洛赫球上叠加看起来是什么样子,让我们执行并看看当我们测量处于叠加状态的量子比特时的概率是什么。如您从我们的第一次类比——抛硬币或旋转硬币——中回忆的那样,我们说过,一旦硬币开始旋转,它就处于正面或反面(或在这个例子中,0 或 1)的叠加状态。
一旦我们观察到了结果,硬币的结果将是其中一个。然而,在经典计算中,这是伪随机的,正如我们所学的。但在量子计算中,电子检测是真正的随机,因为没有方法在不干扰它的前提下确定其结果,这是由于海森堡不确定性原理。
海森堡不确定性原理,由 Werner Heisenberg 于 1927 年提出,描述了当位置更精确地确定时,不可能从初始条件预测粒子的动量。同样,对于反向情况,如果动量更精确地确定,则不可能从初始条件预测粒子的位置。
这与测量量子比特相同;本质上,我们正在测量它,因此迫使它折叠到两个基态之一。
-
然后,在量子比特处于叠加态后进行测量,并重新创建电路。让我们从
状态开始,并应用一个 Hadamard 门,就像我们之前做的那样:# Recreate the circuit with a single qubit and classical bit qc = QuantumCircuit(1,1) # Add a Hadamard gate qc.h(0) -
现在,使用我们的辅助函数,让我们创建一个包含测量算子的电路,这样我们就可以测量量子比特,它将折叠到两种状态之一,如下所示:
# Create a measurement circuit with 1 qubit and 1 bit measurement_circuit = create_circuit(1,True) # Concatenate the circuits together full_circuit = qc.compose(measurement_circuit) # Draw the full circuit full_circuit.draw(output='mpl')
在之前的代码中,我们创建了一个包含测量操作的测量电路,该操作基本上将量子比特从其当前状态折叠到 0 或 1。代码的第二行然后将第一个电路 qc 和这个新的 measurement_circuit 连接起来,创建了一个新的电路,称为 full_circuit,其绘制如下:

图 4.8:从量子比特(q)到经典比特(c)的完整电路,包括旋转和测量
之前的图示说明了我们的完整电路,你现在可以看到它包括两个新的组件,第一个是量子寄存器下面的经典寄存器。第二个组件是测量算子,它将提取量子比特的结果并将其传递给经典比特。结果将使量子比特的状态折叠到 1 或 0。
-
现在,让我们运行这个电路,看看我们得到什么结果。我们将添加一些
shots并查看结果。Shots指的是多次运行实验并汇总其结果。我们将使用我们的辅助文件来帮助我们运行这个电路:# Run the quantum circuit and obtain results transpiled_QC, result, stateVectorResult = simulate_on_sampler(full_circuit, None, None) counts = result[0].data.c.get_counts() print(counts)
之前的代码现在将使用我们辅助文件中的不同后端,即 BasicSimulator 而不是 Statevector,这将使我们能够获得电路的测量结果。在这种情况下,我们将提取 counts,它存储了在 1024 次射击中测量结果为 0 或 1 的次数。
之前代码的结果如下:
{'1': 478, '0': 546}
注意,结果几乎为 50%,这说明了对于每次射击,落在 0 或 1 状态上的概率是相等的!
注意,你实际的结果可能与之前显示的不同,但概率应该接近 50%。尝试多次运行代码,并调整 shots 的数量,看看是否能得到任何差异。shots 的限制可以在每个模拟器和量子系统的 max_shots 值中找到。
我们运行电路多次的原因是为了获得足够的测量值,从而获得对叠加态上测量操作的准确统计。系统中的噪声导致计数中 50/50 的完美统计出现偏差,因为目前使用的近端量子设备还不是容错的。容错设备是具有逻辑量子比特的设备,可以由一个或多个物理量子比特组成,用于最小化错误,以便操作按量子电路指定的方式完成。它们表现出非常低的错误率和大的量子体积,我们将在第九章,优化和可视化电路中介绍。当前的近端设备需要运行多次以提供良好的概率结果。
构建抛硬币实验
如果你曾经上过概率和统计学课程,你可能见过抛硬币的例子。在这个例子中,你被给了一个公正的硬币进行多次抛掷,并跟踪每次抛掷(实验)的结果,无论是正面还是反面。这个实验说明的是,使用公正的硬币和足够的样本,你会看到正面或反面的概率开始收敛到大约 50%。
这意味着,在运行足够多的实验后,硬币落在正面和反面的次数变得非常接近。
让我们在 IBM 量子作曲家上试一试,以更好地可视化正在发生的事情(注意,后端系统可能不可用或不同,所以使用你看到的任何后端。像本书中的大多数后端一样,我们将使用撰写本文时可用的一些后端:
-
打开作曲家编辑器并创建一个新的空白电路。
-
为了简单起见,让我们移除除了一个量子比特之外的所有量子比特。这将简化我们的结果。
-
点击并拖动 Hadamard 门到第一个量子比特上。
-
在 H 门之后点击并拖动测量操作到第一个量子比特上。这将表明你希望测量这个量子比特的值,并将它的结果值(1 或 0)分配给相应的经典比特;在这种情况下,位置为 0 的比特,如下面的截图所示:

图 4.9:抛硬币实验
-
将你的电路命名为
Coin flip并保存。 -
点击设置和运行以展开选项。
-
选择一个后端设备,并将运行次数设置为
1024。这将运行实验 1,024 次。注意,在撰写本文时,有一些变化正在进行,可能会改变使用 Composer 运行电路的方式。如果发生这种情况,请参阅平台上的说明以了解任何更改。 -
点击在‘所选设备’上运行。
-
完成后,点击作曲家作业列表中的完成实验。
测量结果将现在显示两种不同的状态。记住,计算基态表示在 X 轴上,你可以看到它是 0 或 1:

图 4.10:抛硬币结果
另一点需要注意的是,每个状态的频率(X 轴)。每次运行实验时,这都会有所不同,因为它代表每次射击结果为 0 或 1 的次数。
从前面的截图你可以注意到,每次运行实验,结果都会接近 50%。再运行几次实验,亲自检查结果。使用 Hadamard 门可以使你将一个量子位放置在电路中,使其处于两个基态 0 和 1 的线性组合中。如前所述,这有助于利用叠加。
理解纠缠
量子计算机使用的第二个量子计算原理是纠缠。通过纠缠两个或更多量子位,我们本质上是在将一个量子位的值与一个或多个其他量子位同步。通过同步,我们指的是,如果我们测量(观察)纠缠量子位中的一个的值,那么我们可以确信另一个量子位将具有相同的值,无论我们是在同一时间测量还是稍后测量。
纠缠可能是三个量子计算原理中最有趣的一个。这主要是因为它至今仍然让物理学家感到困惑,许多人在讨论中采取了不同的哲学立场。我不会让你感到厌烦,但我会尽力提供足够的信息,让你理解纠缠是什么,但不会提供一种证明纠缠如何工作以创建量子算法和应用的方法。是的,听起来很复杂,但请相信我,魔鬼藏在细节中,我们根本没有足够的空间来制定一个全面的答案来解释纠缠是如何工作的。但这些都足够了——让我们开始工作吧!
量子纠缠,或简称纠缠,简单定义为当两个或更多粒子具有相关状态时发生的量子力学现象。本质上,这意味着如果你有两个粒子,或者在我们的情况下,量子位,它们是纠缠的,这意味着当我们测量一个量子位时,我们可以根据第一个量子位的测量结果确定另一个量子位的测量结果。
如你所回忆的,在我们的前一个例子中,如果我们把一个量子位放在叠加态,并测量这个量子位,那么这个量子位会以 50/50 的概率坍缩到两个状态之一,
或
。
现在,如果同样的量子比特与另一个量子比特纠缠在一起,并且我们测量其中一个量子比特,那么这个量子比特将会是
或
。然而,如果我们测量第二个量子比特,无论是精确地在同一时间还是稍后,它也将具有与第一个我们测量的量子比特相同的值!
有一个需要注意的事情是,如果你选择的话,这也可以是相反的情况。例如,假设你在纠缠之前将第二个量子比特设置为
状态。你现在已经纠缠了相反的状态。这些纠缠状态的组合将在我们讨论第十二章“应用量子算法”时更详细地介绍。
你可能正在想,“这怎么可能呢?”如果我们取两个量子比特并将它们置于叠加态,然后分别测量它们,我们会正确地看到每个量子比特都会塌缩到 1 或 0 的值,每次我们单独测量量子比特时,它们可能不会同时塌缩到相同的值。这意味着如果我们一次运行一个实验,我们有时会看到第一个量子比特测量为 0,而第二个量子比特可能测量为 0 或 1。
两者都是独立的,并且在测量之前、测量过程中或测量之后都不知道彼此的值。然而,如果我们纠缠这两个量子比特并重复相同的实验,我们会看到量子比特每次都会测量出完全相同的值!每个结果都将导致四种不同的结果之一:00、11、01 或 10。这四种结果中的每一种都是基于所谓的贝尔态,这将在本章后面介绍。
你会说,“这不可能?”好吧,对我们来说,现在有一个量子计算机可以运行并尝试这个实验是好事!
实现纠缠量子比特的行为
在下面的代码中,我们将看到当量子比特没有纠缠时,它们的结果是这样的,我们不能根据另一个量子比特的结果推断出一个量子比特的结果。由于我们正在测量两个量子比特,我们的结果将以两位值的形式列出:
-
首先,我们将创建一个新的电路,包含两个量子比特,将它们各自置于叠加态,并测量它们:
#Create a circuit with 2 qubits and 2 classical bits qc = QuantumCircuit(2,2) #Add an H gate to each qc.h(0) qc.h(1) #Measure the qubits to the classical bit qc.measure([0,1],[0,1]) #Draw the circuit qc.draw(output='mpl')
在前面的代码中,我们创建了一个包含两个量子比特的量子电路,为每个量子比特添加了一个 H 门,以便我们可以将每个量子比特置于叠加态,最后,为每个量子比特添加了对其相应比特的测量。
之前代码的结果应该显示以下电路,其中我们可以看到每个量子比特都有一个与其相应的经典比特寄存器进行测量的 H 门;也就是说,量子比特 0 对应比特 0,量子比特 1 对应比特 1:

图 4.11:两个叠加的量子比特及其相应的经典比特
-
然后,我们执行电路并显示结果:
# Run the quantum circuit transpiledQC, result, stateVectorResult = simulate_on_sampler(qc, None, None) #Obtain the results and display on a histogram counts = result[0].data.c.get_counts() plot_histogram(counts)
在之前的代码中,我们创建了一个后端,以1000次射击运行在模拟器上,并将结果绘制在直方图中以供审查。
注意以下结果,每个量子位的输出都非常随机,这正是我们所期望的。我还想就符号顺序提一点,即量子位的顺序。在书写时,量子位的顺序与位顺序略有不同。在量子符号中,第一个量子位也列在左侧,而后续的量子位则添加到右侧。然而,在二进制表示法中,第一个位位于右侧,而后续的位则添加到左侧。
例如,如果我们想表示数字 5 的 3 量子位值,我们会使用
来表示,这与相同数字的位表示相同。然而,这里的量子位顺序是不同的,因为第一个量子位位于左侧位置(q[0]),第二个量子位(q[1])位于中间位置,最后一个量子位(q[2])位于右侧位置。
另一方面,在二进制表示法中,第一个位(b[0])位于右侧,并按顺序向上移动到左侧。在测量时,我们将量子位的结果链接到位(如前一个截图所示),这正确地将每个量子位的结果映射到其相应的二进制位置,以便我们的结果按照预期的位顺序排列。
绘制的直方图如下截图所示:

图 4.12:两个量子位的所有组合的随机结果
在之前的截图里,每个量子位都坍缩到了 0 或 1 的状态,因此既然有两个量子位,我们应该期望看到所有四种随机结果,即00、01、10和11。你的概率结果可能会有所不同,但总体上,它们都应该接近 25%的概率。
- 这是预期的,所以让我们纠缠这两个量子位,看看会发生什么。为此,我们将纠缠这两个量子位并重新运行实验。
让我们通过添加一个多量子位门,称为控制 非(CNOT)门来纠缠这两个量子位。在我们将其包含到电路中之前,让我先解释一下这个门是什么。
CNOT 门是一个多量子位门,它根据另一个量子位的值来操作一个量子位。这意味着量子位门有两个连接点——一个称为控制,另一个称为目标。目标通常是一个算子,例如非(X)门,它会将量子位从 0 翻转到 1,或者相反。
然而,目标算子也可以是几乎任何操作,例如 H 门、Y 门(它在Y轴周围翻转 180°)等等。甚至还可以是另一个控制,但我们将会在第六章,理解量子逻辑门中详细介绍这些花哨的门。
CNOT 门以这种方式起作用,当连接到控制的量子比特设置为 0 时,目标量子比特的值不会改变,这意味着目标操作将不会被启用。然而,如果控制量子比特的值为 1,这将触发目标操作。因此,在 CNOT 门的情况下,这将启用目标量子比特上的 NOT 操作,使其围绕X轴从当前位置翻转 180°。这如图图 4.13所示,其中量子比特 0 是控制比特,量子比特 1 是目标比特;在这种情况下,目标是 NOT 门,因此这是一个 CNOT 门。
下面的逻辑表表示基于 CNOT 门控制比特的值,控制比特和目标比特值更新的状态,以及 CNOT 门前后状态:

表 4.1:两个量子比特 CNOT 逻辑表
现在我们已经看到了 CNOT 门在两个量子比特上的工作方式,我们将更新我们的电路,以便我们可以将量子比特纠缠在一起。在下面的代码中,我们将创建一个包含 2 个量子比特的电路,我们将对第一个量子比特应用 Hadamard 门,然后使用 CNOT 门将第一个量子比特与第二个量子比特纠缠起来:
# Create a circuit with 2 qubits and 2 classic bits
qc = QuantumCircuit(2,2)
# Add an H gate to just the first qubit
qc.h(0)
# Add the CNOT gate to entangle the two qubits,
# where the first qubit is the control, and the
# second qubit is the target.
qc.cx(0,1)
# Measure the qubits to the classical bit
qc.measure([0,1],[0,1])
# Draw the circuit
qc.draw(output='mpl')
电路的结果图应该看起来如下所示:

图 4.13:两个量子比特的纠缠
之前的截图显示,这次我们只在第一个量子比特上放置一个 Hadamard 门,而第二个量子比特只由 CNOT 门操作。由于量子比特 1(q[1])被设置为目标,它将依赖于控制量子比特,在这种情况下,是量子比特 0(q[0])。
-
现在,我们将运行实验并绘制结果。这与我们之前完成的实验类似,我们将执行电路,提取结果计数,并将它们绘制在直方图上以可视化结果:
# Run the quantum circuit transpiledQC, result = run_qasm_circuit(qc, None, None) counts = result.get_counts(qc) plot_distribution(counts)
下面的截图显示的结果展示了两个量子计算原理——量子比特 0 和 1 的叠加,以及纠缠——其中两个量子比特(控制比特和目标比特)的结果强烈相关,要么是00,要么是11:

图 4.14:两个纠缠量子比特的结果
让我们通过添加另一枚硬币并将它们纠缠在一起来扩展我们的抛硬币示例,这样当我们运行实验时,我们可以确定一枚硬币的值,而无需测量另一枚。
将两个硬币纠缠在一起
与我们之前的实验一样,每个量子比特将代表一枚硬币。为了做到这一点,我们将使用 CNOT 门,它连接两个量子比特,其中一个作为源,另一个作为目标。
让我们尝试将我们的硬币(量子比特)纠缠起来,看看这是如何工作的:
-
打开 Composer 并创建一个新的空白电路,包含 2 个量子位。提醒一下,你可以通过选择量子位并点击 + 或垃圾桶图标来增加或减少量子位的数量,分别添加或删除量子位。
-
点击并拖动一个 Hadamard 门到第一个量子位,q[0]。
-
点击并拖动 CNOT 门(蓝色背景上的白色圆门,带有交叉线)到第一个量子位,q[1]。这将控制量子位分配给第一个量子位。在选择 CNOT 门时,你放置它的第一个量子位将被设置为控制位。从视觉上看,CNOT 门的源控制是一个实心点,位于被拖动门到的量子位上(参见 图 4.15)。
默认情况下,目标会自动设置为下一个量子位。在这种情况下,它将降至量子位 2。从视觉上看,CNOT 的目标是中间带有十字的大型圆点,设计得像靶子。
- 点击并拖动一个测量算子到两个第一个量子位,如下截图所示:

图 4.15:表示纠缠硬币的纠缠量子位电路
-
将你的实验命名为
Entangled coins并保存。 -
在电路中点击 Setup and run 以启动 Setup and run 对话框。
-
从后端选择中选取任何设备作为后端设备,并将
shots值设置为1024。这将运行实验 1,024 次,这是默认值,如果需要可以更改。 -
点击 Run on 并选择你在上一步中选择的设备。
-
完成后,从
Completed jobs列表中点击Entangled coins实验项目。
现在我们来回顾一下结果,看看当我们纠缠两个量子位时会发生什么:

图 4.16:纠缠硬币的结果
如前一个截图所示,结果仍然有两个状态,就像上一个实验中一样。然而,这里要注意的是两个量子位的结果。请注意,两个量子位的状态要么是 00,要么是 11。
使这个实验变得有趣的是,当我们翻转上一个实验中的一个硬币时,你看到结果是 50%(0 或 1)。然而,现在我们正在运行相同的实验,但我们正在纠缠另一个硬币。实际上,这导致两个硬币都纠缠在一起,因此它们的状态将始终相同。这意味着如果我们翻转两个硬币并观察其中一个硬币的值,那么我们知道另一个纠缠硬币的值将相同。
现在你已经熟悉了叠加和纠缠,让我们继续到最后一个量子计算原理,即干涉。
理解干涉
量子计算的一个好处是它能够以这种方式交织这些原理,通常在解释一个原理的同时,你能够非常容易地描述另一个原理。我们在本章 earlier 部分已经用关于干涉的例子做了这样的说明。现在让我们回顾一下,看看我们迄今为止在哪里遇到过这种现象及其应用。
首先,回想一下,在本章的开头,我们描述了双缝实验。在那里,我们讨论了电子如何同时作为波和粒子存在。当它像波一样行动时,我们看到实验展示了电子如何移动并在观察屏幕上的特定位置着陆。它显示的图案通常是我们在经典物理学中认识到的波干涉图案。
模式在篮板上显示出概率性结果,如图 图 4.2 中的观察屏幕所示,屏幕中心有最多的电子,而两侧的空白区域电子最少甚至没有。这是由于粒子波的两类干涉,即建设性和破坏性。当两个波的峰值相加时,结果振幅等于两个单独波的总体正和时,发生建设性干涉。
破坏性干涉与建设性干涉发生的方式相似,只是波的振幅是相反的,在将它们相加时,两个波会相互抵消。
下面的图示说明了当两个波叠加时,它们的建设性和破坏性波干涉:

图 4.17:建设性(左)和破坏性(右)的波干涉(图片来源:https://commons.wikimedia.org/wiki/File:Interference_of_two_waves.svg)
前面的图示说明了两个波如何以建设性和破坏性的方式相互干涉。图示底部的两个波代表每个波的单独振幅,而顶部的一行代表叠加的振幅值,这些值代表了两个波之间干涉的结果。
现在你已经理解了建设性和破坏性干涉之间的区别,我们如何将这一知识应用到我们迄今为止所学的内容中呢? 好吧,如果你还记得,之前当我们将量子比特置于叠加态时,我们得到了两个不同的结果。
一个是从基态
出发,而另一个是从基态
出发。你还记得我们最初是从这两个量子基态之一开始的吗?在量子比特的 X 轴上,Hadamard 变换会落在哪个位置?从
出发,它会落在 X 轴的正侧,但如果我们从
状态开始将量子比特置于叠加态,它就会落在负 X 轴上。
能够将量子比特状态矢量放置在正或负 X 轴上,为我们提供了将量子比特放置在正或负状态的方法。这与前一个图中展示的波非常相似,这些波具有正(峰值)和负(谷值)振幅,量子比特也可以表示类似的状态。让我们通过重新引入两个狄拉克符号值,
和
,来简化这一点,其中
状态代表正 X 轴上的状态矢量,而
状态代表负 X 轴上的状态矢量。
这些新的矢量定义,代表叠加态中一个量子比特的矢量状态,将被某些算法用作识别特定值并使用干涉来对其做出反应的技术,例如振幅估计和搜索算法如Grover 算法。
在本节中,我们回顾了量子计算中的干涉原理。随着你了解这些原理如何在量子算法中提供潜在的速度提升,这些原理(叠加、纠缠和干涉)将非常有用。为了做到这一点,我们将回顾一个我们将贯穿整本书的例子,以了解所有量子算法的坚实基础,即贝尔态。
探索贝尔态
在本书的大部分例子中,你会发现我们重复使用一个简单的双量子比特量子电路来运行许多实验。这个电路包含两个门,一个单量子比特门和一个多量子比特门,分别是 Hadamard 和 CNOT。
选择这个的原因并非随机。事实上,这个电路有一个名字,贝尔态。贝尔态最初由约翰·贝尔在 1964 年发表的一篇理论论文中描述,描述了两个处于叠加态的量子比特之间存在四种最大纠缠量子状态。这四种状态通常被称为贝尔态。
在这一点上,你可能想知道这为什么如此重要。好吧,如果我们能将量子比特准备到特定状态,在这种情况下,最大纠缠状态,这有助于简化各种量子电路和算法的创建。为了了解更多关于这方面的信息,让我们首先准备四个贝尔态,也许在这个过程中,你可能会看到其重要性,并理解其在量子隐形传态或超密集编码等用例中的重要性。
准备贝尔态
我们将首先准备我们将贯穿整本书使用的贝尔态。
我们将在创建每个状态时对其进行标记,这个第一个状态被标记为
。准备贝尔态包括三个简单步骤:
- 准备你的双量子比特输入值。对于这个第一个状态,
,我们将使用初始化状态
:

- 接下来,向第一个量子比特添加一个哈达玛门。这将使第一个量子比特处于叠加态:

- 最后,添加一个 CNOT 门,其中控制位设置为叠加态的量子比特。在这种情况下,第一个量子比特和目标位都设置为第二个量子比特。这样做将确保当第一个量子比特为 1 时,这将触发目标量子比特绕 X 轴从
状态旋转到
状态,否则它将保持在
状态。这给我们带来了最终状态:

这个最终状态是第一个贝尔态
,这将导致出现相等概率的
或
。
准备第一个贝尔态与其他态之间的唯一区别仅在于 步骤 1,在那里你需要准备你的输入。步骤 2 和 步骤 3 对所有态都是相同的。这意味着对于双量子比特电路,步骤 1 中准备剩余的输入状态是
,
,和
。幸运的是,以下公式可以帮助我们识别剩余的贝尔态:

通过使用这个公式,我们可以计算出所有四个贝尔态如下:
- 对于输入状态
,我们得到以下方程:

- 对于输入状态
,我们得到以下方程:

- 对于输入状态
,我们得到以下方程:

- 对于输入状态
,我们得到以下方程:

现在,让我们通过在模拟器和量子计算机上执行所有贝尔态来创建这些电路。
实现贝尔态
在本节中,我们将创建前两个初始状态
和
,并留给你创建剩余的输入状态:
-
我们将从创建第一个贝尔态
开始。让我们创建一个双量子比特 QuantumCircuit电路,并准备输入状态
。由于所有量子电路都初始化为状态
,我们不需要对电路进行任何操作。我们将添加一个屏障来指示步骤之间的分隔:# State 1: |/+> state1 = QuantumCircuit(2) # Initialize input to |0,0> state1.barrier() -
然后,向第一个量子比特添加一个哈达玛门:
# Prepare the Bell state state1.h(0) -
添加一个 CNOT 门,其中控制位是第一个量子比特,目标位是第二个量子比特:
state1.cx(0,1) -
最后,向所有量子比特添加测量并绘制电路:
state1.measure_all() state1.draw(output='mpl')
这将生成我们第一个贝尔态
的最终电路,如下所示:

图 4.18:制备好的贝尔态 
-
现在,让我们使用我们的辅助函数执行这个电路。设置
simulator参数来指定你想要在模拟器还是量子系统上执行它。为了避免结果中的任何噪声,在这个例子中,我们将电路在量子模拟器上运行以验证我们的结果是否符合预期:# Execute the Bell state |/+> transpiledQC, result, stateVectorResult = simulate_on_sampler(state1, None, None) # Obtain the results and display on a histogram counts = result[0].data.meas.get_counts() plot_histogram(counts)
这个实验的结果产生了以下熟悉的输出,这证实了第一个贝尔态,00:

图 4.19:第一个状态的结果,
- 我们现在将继续表示下一个状态,
,并像之前一样确认结果。
如前所述,四个贝尔态之间的唯一区别在于第一步,即准备输入状态。在这种情况下,我们的输入状态是
。在给第二个量子比特添加一个X门之后,我们可以遵循之前的相同步骤:
# State 2: |/+>
state2 = QuantumCircuit(2)
# Initialize input state to |1,0>
state2.x(1)
state2.barrier()
# Prepare the Bell state
state2.h(0)
state2.cx(0,1)
state2.measure_all()
state2.draw(output='mpl')
这将导致以下电路,它与第一个电路非常相似,只是在准备步骤中添加了一个X门:

图 4.20:准备好的贝尔态,
-
与第一个贝尔态一样,让我们执行这个电路并观察结果:
# Execute the Bell state |/+> transpiledQC, result, stateVectorResult = simulate_on_sampler(state1, None, None) # Obtain the results and display on a histogram counts = result[0].data.meas.get_counts() plot_histogram(counts)
执行前面电路的结果如下:

图 4.21:贝尔态的结果,
在审查了这两项结果之后,我们应该注意以下几点。首先,我们可以从第一个贝尔态中看到,两个量子比特是同等纠缠的,也就是说,如果你要测量一个量子比特,比如说第一个,那么你就会知道第二个量子比特应该处于相同的状态。因此,如果你测量第一个量子比特,结果是 0,那么你无需测量就能知道第二个量子比特的状态。
无论你是在同一时间还是稍后测量第二个量子比特,对于第二个贝尔态,情况也是一样的;唯一的区别在于,如果你测量一个量子比特,那么你就知道另一个将导致相反的基态值。因此,如果第一个量子比特的结果是 0,那么第二个量子比特的结果将是 1,反之亦然。
这两个量子比特之间的相关性是两个著名量子应用——量子隐形传态和超密集编码的基础,在这些应用中,每个应用都有两个处于纠缠态的量子比特。这两个量子比特的准备状态由贝尔态表示,这种准备可以是之前描述的四个贝尔态中的任何一个。
当阅读描述量子隐形传态用例的文章时,你会听到一个与此类似的例子:爱娃准备了一对纠缠量子比特,并将其中一个发送给爱丽丝,另一个发送给鲍勃;你现在将知道爱娃是如何准备这对纠缠量子比特的。
现在我们已经了解了贝尔态及其在量子隐形传态和超密集编码等应用中的应用,我们将在后面的章节中继续我们的旅程,以说明量子算法如何提供比经典系统更优越的计算优势。
摘要
在本章中,你学习了量子计算中使用的三个原理。你创建了一个量子电路,并在量子电路中将一个量子比特置于叠加态,并在两个量子比特之间处于纠缠态。
你还理解了两种类型的干涉,即建设性和破坏性,并学习了它们如何通过将它们放置在叠加中以创建
和
模拟来表示和表示为量子比特。
你还通过利用一些量子门,如 Hadamard 门和 CNOT 门,以及测量等操作,提前了解了一些 Qiskit 开发技能。这将为你准备未来章节,届时你将创建电路,在这些电路中,这些门和操作在各种算法中普遍使用。这很有意义,因为这些门和操作代表了我们所学的核心量子计算原理。
你还进行了一些实验:第一个实验是模拟抛硬币的实验,其中使用 Hadamard 门创建了一个电路,利用了叠加原理。第二个实验也模拟了抛硬币,只是我们将两个硬币都纠缠在一起。这是第二个电路的扩展,其中包含了你的第一个多门,一个 CNOT 门。这些使你能够检查叠加和纠缠的结果如何从你的量子电路映射到经典比特输出。我们还了解了贝尔态,它展示了量子纠缠的使用和优势。这四个特殊状态代表了叠加态的线性组合,将在本书后面的章节中学习量子算法时使用。
在下一章中,我们将学习所有其他门,包括单门和多门,以了解它们在每个量子比特上执行的操作。
问题
-
你将如何创建一个纠缠两个不同量子比特(即 01、10)的电路?
-
创建一个包含多量子比特门(如受控 Hadamard 门)的电路。
-
在电路中创建所有 4 个贝尔态。
-
量子计算的三项原理是什么?
加入我们 Discord
加入我们社区的 Discord 空间,与作者和其他读者进行讨论:
packt.link/3FyN1

第五章:理解量子比特
我们都非常熟悉经典比特,或者简单地说是比特,在当前的计算机硬件系统中。它是用于计算从简单的数学问题,如加法和乘法,到涉及大量信息的更复杂算法的基本单元。
量子计算机有一个类似的基本单元,称为量子比特,或qubit,这是它的常用称呼。在本章中,我们将从数学(计算)和硬件的角度描述什么是 qubit,以帮助您了解它们是如何用于计算信息的。我们将涵盖 qubit 和比特之间的差异,特别是关于计算是如何定义的。然后,本章将从单量子比特状态过渡到多量子比特状态,并讨论多量子比特状态的优势。
我们还将概述量子比特的硬件实现以及量子比特是如何用于计算信息的。由于我们将使用 Qiskit Runtime 服务来运行我们的实验,您将使用可用的超导量子比特系统。描述和计算是硬件无关的;我们将涵盖的大部分信息将适用于大多数其他可用的量子硬件系统。
最后,我们将讨论量子系统如何从经典系统中读取、操作和控制量子比特的信息流动。
本章将涵盖以下主题:
-
比较经典比特和量子比特
-
可视化量子比特的状态向量
-
可视化多个量子比特的状态向量
-
在超导系统中实现量子比特
技术要求
在本章中,一些基本的计算机架构知识、基本的线性代数和二进制逻辑可能会很有用。了解比特是如何用于计算的将是有用的,但不是硬性要求,因为重点将主要放在量子比特上。以下是本书中使用的源代码:github.com/PacktPublishing/Learning-Quantum-Computing-with-Python-and-IBM-Quantum-Second-Edition。
比较经典比特和量子比特
在本节中,我们将比较和回顾经典比特的构建块以及通过经典门对这些构建块执行的一些操作。然后,我们将了解量子计算机的基本单元——量子比特,以及它如何与比特相似,但由于我们在上一章中学到的量子计算原理,它比比特具有更大的计算空间。
回顾经典比特
在我们深入探讨量子比特是什么以及它是如何被使用之前,让我们花一点时间来回顾一下经典比特。正如量子比特是量子算法的基本构建块一样,比特在经典计算系统中扮演着同样的角色。
在计算系统中,比特被用来定义逻辑状态,通常指的是开启或关闭、真或假,或者最常用的选项,1 或 0。状态的转换可以通过物理方式应用,要么是在操作触发后,例如AND 门的结果,要么是外部实体输入的结果,例如从外部数据源读取。它通常使用晶体管来表示,晶体管检测电压差异,通常包含一个阈值,用于确定晶体管处于低(0)或高(1)状态。电压阈值,通常称为晶体管-晶体管逻辑(TTL)电压,通常在 0 到 0.5 伏之间表示低,在 2 到 5 伏之间表示高。
下图展示了在比特上执行 NOT 操作的简单过程。比特首先被初始化或设置为一种状态,要么是 0 要么是 1。然后,对比特执行操作,根据操作的结果,比特的状态将改变或保持不变。然后,信息就可以被读取和/或存储。在这个例子中,NOT 操作将状态从 0 变为 1 或相反:

图 5.1:比特的 NOT 操作
比特的实现可以有多种形式:触发器、TTL 等。信息可以通过将值写入持久数据存储库来存储,以便稍后读取。使用比特的计算通常使用比特串进行,它是一组单独的比特组合,用来表示一系列的 1 和 0,通常如下所示:

这表明x是一个 4 位的比特串,其中每个比特可以是1或0;例如,0010或1101。
使用比特的计算通常使用二进制逻辑。例如,假设我们想要加两个数;比如说,2 和 3。我们只需将值 2 和 3 分配给一个变量,该变量以二进制形式存储。然后,我们使用二进制加法将两个数相加,并传递值,这将得到 5,如下所示。请创建一个新的笔记本并输入以下内容:
%run helper_file_1.0.ipynb
#Adding two binary numbers
two = 0b010
three = 0b011
answer = two + three
print(bin(answer))
0b101. The code reads the binary values of 2 and 3 and returns the result as a binary called answer, which is printed as a binary using the bin() function. In order to obtain the results of adding two numbers together, classical systems use Modulo 2 arithmetic, which is the result of using logic gates, in this case, an XOR gate. When XORing two input bits, that is, , the input values can also be written as two binary numbers, *x*1 and *x*2; that is, . Note that the result will work for the following input values of x1 and *x*2: 0+0, 0+1, and 1+0. However, when the input values of *x*1 and *x*2 are 1+1, we will need a second qubit to carry the value, hence the result for 1+1 will be 10.
那么,我们为什么要通过这样一个简单的例子呢? 目的不是让你对简单的二进制计算感到厌烦;目的是提供一个关于在经典系统上计算时门级发生机制的知识更新。这样,在描述量子系统时,它将帮助你比较和对比信息创建、计算和存储方面的差异。有了这个,我们将继续到下一部分,并描述什么是量子比特。
理解量子比特
与我们之前描述的比特类似,量子比特是量子信息科学中的基本单位。量子比特与比特相似,因为它可以表示相同的状态,即 0 和 1,尽管量子比特表示量子态。量子比特的值是可以读取的。这里的“读取”意味着我们可以测量结果,这我们在 第四章,理解基本量子计算原理 中已经讨论过。
它们也可以被操作以推导出基于对每个量子比特执行的操作的计算。回想一下,比特的状态可以用 0 或 1 来表示。量子比特也可以表示为 0 和 1 的复线性组合。为了防止混淆并区分比特和量子比特,我们将使用 狄拉克符号,
和
,来分别表示上述 0 和 1 状态的量子版本。让我们先可视化一些东西,以帮助我们看到两种状态之间的差异。
首先,量子比特的状态通常表示为一个数组或向量,它描述了量子比特的计算基态,在 希尔伯特空间 中,这通常表示为
。
希尔伯特空间本质上是一个包含所有可能实数和复数的向量空间。希尔伯特空间通常应用于无限维向量空间中,而欧几里得空间,例如,指的是具有内积的有限维线性空间。
量子态可以表示为两个相互垂直的基矢量,如下所示:

第二个矢量如下所示:

如我们所见,比特和量子比特在它们可以表示两个基态方面是相似的,在这种情况下,是 0 和 1。量子比特与经典比特的不同之处在于,量子比特总是处于基态的线性组合中,也就是说,它们总是处于
和
的叠加态。更正式地说,这可以用以下格式表示:

从前面的方程中,我们可以看出
和
是复杂的,因为它们的幅值之和等于 1,每个平方系数代表概率幅值,它代表测量对应基态的
或
的概率:

关于量子力学,还有一点需要了解,那就是我们无法获得
和
的值,即使是在测量量子比特时。测量量子比特需要量子比特坍缩到 0 或 1 的基态之一。
![img/B18420_05_017.png]和![img/B18420_05_018.png]仅仅提供了一些关于结果是一还是另一的概率信息,但这并不确定。这是量子力学的一个谜团。目前,你可以将测量量子比特的概念化地理解为观察或折叠一个旋转的硬币以揭示它是正面还是反面。一旦测量过,或者折叠过,你就无法在不重新启动实验的情况下让硬币继续旋转,因此所有信息都会丢失。你必须再次执行旋转硬币的完整操作。
使用简单的二维平面可以可视化量子比特的状态,其中x轴用于表示![img/B18420_05_004.png]状态,而y轴用于表示![img/B18420_02_003.png]状态。因此,向量可以用来表示每个状态的概率,这些概率的总和应为 1。
在本节中,我们介绍了比特和量子比特之间的区别。在下一节中,我们将学习如何使用状态向量来可视化量子比特及其状态。
可视化量子比特的状态向量
另一种表示量子比特及其状态的视觉方法是布洛赫球面,以物理学家费利克斯·布洛赫命名。布洛赫球面是一个普通的三个维度球面,通常用作量子比特的几何表示。通过这种方式,我们指的是球面可以表示布洛赫球面表面上的任意一点来代表量子比特的状态。正如前一章所描述的,基态由南北两极表示。
传统上,布洛赫球面的北极表示![img/B18420_04_006.png]状态,而南极表示![img/B18420_02_003.png]状态。布洛赫球面上的任何一点都可以表示为从中心(原点)到布洛赫球面表面的单位向量,代表状态的线性组合。
由于我们受到量子力学的约束,即向量的总概率必须等于 1,我们得到以下公式:
![img/B18420_05_023.png]
然后,向量只能通过以下表示在布洛赫球面的![img/B18420_05_024.png]和![img/B18420_05_025.png]轴周围旋转:
![img/B18420_05_026.png]
在这里,
(表示量子比特的振幅)和
(表示量子比特的相位)的值(极限)分别是
和![img/B18420_05_030.png]。这表明,只要
和![img/B18420_05_028.png]的值本身是唯一的,球面上的任何一点都是唯一的,其中![img/B18420_05_027.png]表示指向z轴的余纬,![img/B18420_05_028.png]表示从x轴起的经度。量子态
是在应用![img/B18420_05_027.png]和![img/B18420_05_028.png]的旋转之后,从初始态
设置的,如下面的图所示:

图 5.2:量子比特布洛赫球
(图片来源:https://commons.wikimedia.org/wiki/File: Sphere_bloch.jpg)
为了继续描述量子比特,我们将使用视觉来帮助说明一些可以在布洛赫球上看到的关键概念。这也有助于为你提供更多的动手练习。
可视化量子比特的表示
在本节中,我们将使用两个可视化绘图器,布洛赫球和qsphere,来可视化量子比特状态的表示。我们将首先创建初始态
的量子比特的布洛赫球,以便我们可以可视化量子比特的状态向量和相位:
-
创建一个新的笔记本,我们就像处理所有我们的笔记本一样,首先加载我们的辅助文件。当然,如果你希望重用之前的笔记本,你可以这样做并跳过此步骤:
# Load the helper file %run helper_file_1.0.ipynb -
接下来,我们将创建一个只有一个量子比特的简单电路,并使用我们导入的可视化工具来可视化量子比特的状态。我们将导入第一个,其初始状态为
。
finished, whereas the qasm simulator returns count information. Finally, we will execute our circuit and get the state vector results:
#Create a simple circuit with just one qubit
qc = QuantumCircuit(1)
-
接下来,我们将在状态向量模拟器上运行我们的电路,并通过将
statevectorResult对象传递给plot_bloch_multivector函数的参数来在布洛赫球上查看结果:# Get the state vector result from the circuit stateVectorResult = Statevector(qc) print('state vector results', stateVectorResult)
在前面的单元格执行完毕后,你应该会在你的控制台上看到如下打印出的状态向量结果。
state vector results: Statevector([1.+0.j, 0.+0.j],
dims=(2,))
接下来,我们将首先使用Statevector对象的draw函数在布洛赫球上显示。这个函数与我们之前使用的可视化方法非常相似,只不过在这种情况下,我们可以包含一个参数来描述要使用哪个球来显示状态向量信息。在这个例子中,我们使用bloch来表示布洛赫球;我们将继续使用它,以便在布洛赫球和 qsphere 之间切换时简化操作。请注意,我们应该期望看到我们的状态向量在初始态
中,因为我们没有对量子比特执行任何操作。
# Display the Bloch sphere
stateVectorResult.draw('bloch')
前面函数的输出将是布洛赫球,量子比特状态指向北极或
状态,如下所示:

图 5.3:量子比特布洛赫球状态向量初始化为 
-
接下来,我们将在 qsphere 上显示状态向量结果。在这个可视化中,您将看到状态向量与前面图中显示的布洛赫球处于相同的状态:
stateVectorResult.draw('qsphere')
您还会看到,它包括由右下角的彩色阴影球体表示的状态向量的相位,如下面的输出所示:

图 5.4:量子比特状态向量初始化为
且相位为 0
从前面的图中可以看出,状态向量在 qsphere 的表面指向北极,表明它处于
状态。它也被蓝色阴影覆盖,以表示量子比特的相位;在这种情况下,因为我们没有改变相位,所以它被设置为默认相位 0(蓝色,如 qsphere 右下角的图例轮所示)。
注意,所有图像都可以在以下地址以彩色形式获取:[ADD COLOR IMAGE PACK URL]
这是为了表示状态向量的相位。前面图中右下角的彩色图是状态向量相位的参考,目前为 0。
-
现在我们已经熟悉了量子比特的状态向量,让我们来实际操作一下。我们将首先使用 NOT 门将向量从初始状态
翻转到
状态,然后重新运行我们的状态向量并绘制结果:qc = QuantumCircuit(1) qc.x(0) #Run circuit using state vector and display results stateVectorResult = Statevector(qc) stateVectorResult.draw('qsphere')
如您所见,我们现在处于
状态,相位仍然为 0,如下面的图所示:

图 5.5:量子比特状态向量设置为
且相位为 0
-
接下来,我们将通过添加一个哈达玛门并再次执行电路来将量子比特置于叠加态。我们将创建一个新的电路,并包括一个哈达玛门,如下面的代码片段所示,然后执行电路并绘制状态向量结果的布洛赫球,这表明状态向量的位置。在这种情况下,它位于赤道:
qc = QuantumCircuit(1) qc.h(0) #Run the circuit using the state vector and display results stateVectorResult = Statevector(qc) stateVectorResult.draw('bloch')
注意,状态向量是
和
的精确线性组合:

图 5.6:布洛赫球叠加表示,是
和
的线性组合
让我们通过绘制状态向量结果来看看 qsphere 上的样子。
-
在 qsphere 上绘制状态向量结果:
stateVectorResult.draw('qsphere')
你可以在以下图中看到上一段代码片段的输出:

图 5.7:量子比特状态向量设置为
和
的线性组合,叠加
结果可能有点令人困惑。你可能想知道为什么只有一个量子比特时却有两个向量,以及为什么它们基于 Bloch 球的结果。难道我们不应该只看到一个吗? 好吧,区别在于 qsphere 可视化的是 Bloch 球无法显示的内容;即每个可能状态的振幅的视觉表示。如果你观察在执行
或
状态时 qsphere 先前结果的球体大小,你会发现球体的直径比前一张图表面的两个球体大得多。这是因为振幅在这两种状态下是相等的,所以大小被分成了两部分,而在先前的例子中,振幅完全存在于两个状态中的一个。
在本节中,我们了解到量子比特可以通过使用 0 和 1 的两个基态来表示自己。我们还看到它可以表示为两个基态的线性组合,即振幅(纵向)和相位(横向)。
正是通过利用这些特性,量子算法才能提供比使用经典比特更优化的计算解决方案的潜力。我们也看到了如何使用两个 Qiskit 可视化函数,即 Bloch 球和 qsphere,来可视化量子比特的状态,这些函数提供了诸如振幅和相位等信息。
在下一节中,我们将探讨如何表示多个量子比特,以及如何可视化并绘制它们的实部和虚部。
可视化多个量子比特的状态向量
到目前为止,我们已经学习了表示量子比特的各种方法,无论是作为向量
还是可视化在 Bloch 球上。我们在 qsphere 上也做了类似的事情。在本节中,我们将学习如何表示多个量子比特以及如何表示它们的通用状态。我们将首先对符号进行轻微的更新。单个量子比特表示为以下向量:

因此,我们可以以类似的方式表示两个量子比特,如下所示:

从前面的方程中,你可以看到状态
用于表示多个量子比特,而
用于表示单个量子比特。区别在于大小写:单个量子比特使用小写,多量子比特使用大写。因此,概率振幅以及由归一化约束的 1 可以表示如下:

让我们来看一个包含两个量子比特的例子,第一个量子比特处于
状态,如下所示:

另一个量子比特,处于
状态,如下所示:

结合这两个状态意味着取它们的张量积,用于描述多个子系统的系统,用符号
表示的两个量子比特状态如下:

交叉相乘,我们得到以下结果:

这导致振幅向量如下:

最后,通过它们的张量积表示多量子比特的另一种方式是通过它们的乘积状态。在这里,n 个量子比特的乘积状态是一个大小为 2^n 的向量。我们将使用之前描述的相同两个向量示例。第一个是 00 状态:

01 状态如下:

10 状态如下:

最后,11 状态如下:

从前面的方程中,我们可以得出结论,我们可以将两个量子比特分别表示为两个 2 x 1 列向量。然而,当我们想要表示整个系统的联合状态时,我们用张量积来表示它们,这产生了之前展示的 4 x 1 列向量。这是量子状态的数学表示,也称为双量子比特系统的计算基态。
在下一节中,我们将简要讨论在 IQP 系统上实现量子比特的方法,并讨论其他用于实现量子比特的技术。
在超导系统中实现量子比特
在本章的开头,我们了解到经典比特可以通过各种平台实现,这些平台可以检测电压或电流的相位之间的差异,或者通过触发器的状态。正如比特有用于其实现的平台一样,量子比特也是如此。
一些更常见的量子比特平台包括中性原子、量子点、钻石中的氮空位(NV)中心、俘获离子和超导量子比特。在这些平台中,用于 IQP 上量子设备的超导量子比特被使用。因此,在本节中,我们将介绍这个平台。
如果你想了解更多关于其他平台的信息,你可以阅读迈克尔·尼尔森和伊萨克·丘恩的书籍《量子计算与量子信息》,该书详细介绍了这些内容。
超导体是由铌和铝的混合物制成的材料,它没有电阻,但这通常只能在非常低的温度下实现,通常在 20 毫开尔文左右。因此,超导体上的电子被用作构成一对电子的基本电荷载体,更常见的是称为库珀对。这与其他导体不同,其他导体通常使用单个电子。关于库珀对的量子力学或超导行为的细节讨论超出了本书的范围。然而,如果你感兴趣,可以在附录 A中找到各种参考资料。现在,我们可以将超导体视为构成量子比特的超导电路的组成部分之一。
现在我们已经介绍了如何使用状态向量模拟器来可视化量子比特的状态,并在 Bloch 球和 qsphere 上显示它,我们可以继续到下一章,该章将描述所有量子比特门操作符及其相互之间的作用。
摘要
在本章中,你学习了比特和量子比特之间的区别以及它们的数学和视觉表示方式。你还看到了单量子比特和多量子比特系统是如何表示的,包括它们的数学表示,以及它们的构建和操作方式。我们还介绍了如何将量子比特可视化为 Bloch 球和 qsphere。
你现在有了表示单量子比特和多量子比特的矢量状态的能力。你也理解了使用量子比特的张量积将多个量子比特表示为单独的实体或作为完整系统的一部分之间的区别。这将帮助你实现和操作 IBM 量子系统上的量子比特。
在下一章中,我们将介绍如何对单量子比特和多量子比特进行操作,以及这些操作如何在真实设备上的量子比特上触发。
问题
-
哪个会提供关于量子比特相位的视觉信息——Bloch 球还是 qsphere?
-
你能在 Bloch 球上可视化多个量子比特吗?如果不能,请描述你为什么不能。
-
将三个量子比特状态的张量积以所有形式写出来。
-
三量子比特系统的概率振幅是什么?
加入我们的 Discord 社区
加入我们社区的 Discord 空间,与作者和其他读者进行讨论:

第六章:理解量子逻辑门
量子逻辑门与它们的经典对应物非常相似,因为它们通过操纵量子比特以提供解决方案的方式来执行操作。当然,比较到此为止。经典门通过单一操作将比特的状态从一种转换到另一种,在这种情况下,将比特值从 0 翻转到 1,或反之亦然。量子门,有时也称为量子比特门,部分不同之处在于它们在复杂向量空间中对一个或多个量子比特执行线性变换,以将它们从一个状态转换到另一个状态。
本章将涵盖以下主题:
-
复习经典逻辑门
-
理解单位算子
-
理解单量子比特门
-
理解多量子比特门
-
理解不可逆算子
阅读本章后,您将了解可以对单个和多个量子比特执行的基本操作。但在我们深入探讨之前,让我们先讨论我将尝试解释每个量子比特门的格式。首先,从学习角度来看,有些人倾向于在内容仅以数学形式呈现时学得更快;其他人更喜欢图表等视觉辅助工具;还有一些人更喜欢更具直观性的方法,例如类比和示例。
考虑到这一点,我将尽最大努力确保通过尽可能多地结合这些学习风格来展示每个门。这将通过提供每个量子比特门的数学表示、视觉表示以及当然,运行量子比特门操作及其结果的源代码来实现。
技术要求
在本章中,我们将讨论希尔伯特空间中矩阵的线性变换,因此强烈建议您了解线性代数的基础知识。
了解量子比特及其状态如何在布洛赫球面、QSphere或数学上表示是推荐的,因为本章将执行这些量子比特状态的复杂线性转换。了解基本的经典单比特和多比特门也是推荐的,但不是必需的,因为如果需要,会有复习内容。
这是本书中使用的完整源代码:github.com/PacktPublishing/Learning-Quantum-Computing-with-Python-and-IBM-Quantum-Second-Edition。
复习经典逻辑门
本节将作为经典逻辑门(如与、或、或非等)的复习。如果您熟悉这个主题,您可以快速浏览以刷新记忆,或者完全跳过并跳到下一节。否则,让我们开始逻辑思考!
逻辑门被定义为一种设备,无论是电子的还是其他类型的,它实现了逻辑操作(通常是布尔操作)。单位门和双位门分别有一个或两个输入。每个输入位值是 0 或 1 的状态值。对输入的操作因门类型而异。每个门操作通常使用逻辑真值表来描述,如下表所示:
| 门 | 操作 | 输入A B | 输出Y | 图形表示 |
|---|---|---|---|---|
| Buffer | 输出与输入相同的值 | 01 | 01 | |
| NOT | 反转输入状态 | 01 | 10 | |
| AND | 仅当两个输入都是 1 时输出 1,否则输出 0 | 0 00 11 01 1 | 0001 | |
| OR | 如果没有输入是 1,则输出 0,否则输出 1 | 0 00 11 01 1 | 0111 | |
| XOR | 仅当两个输入不同时输出 1,否则输出 0 | 0 00 11 01 1 | 0110 | |
| NAND | 仅当两个输入都是 1 时输出 0,否则输出 1 | 0 00 11 01 1 | 1110 | |
| NOR | 仅当两个输入都是 0 时输出 1,否则输出 0 | 0 00 11 01 1 | 1000 | |
| XNOR | 仅当输入都是 0 或 1 时输出 1,否则输出 0 | 0 00 11 01 1 | 1001 |
表 6.1:经典逻辑门
上述表格列出了一些常见的经典门,描述了每个门对输入状态的操作,门操作的结果(输出),以及它们的图形表示。
让我们考虑一些关于经典比特的注意事项,这有助于你以后理解它们与量子比特(qubits)相比的不同之处。首先,只有两个单比特门,即缓冲门和非门。在这两个门中,只有非门对经典比特执行布尔操作,通过翻转输入的比特值,所以如果非门的输入是 0,则输出将是 1。另一方面,缓冲门简单地输出与输入相同的值。所有其他门都操作于两个输入比特值,输出一个值,该值由门的布尔操作确定。例如,如果一个与门(AND gate)的两个输入值都是 1,它将输出 1。否则,输出将是 0。
然而,有一个问题,尤其是关于两位门的问题,那就是如果您只能访问输出,那么关于输入的信息就会丢失。例如,如果您从 AND 位得到结果,并且值是 0,您能说出 A 和 B(输入)的输入值是什么吗?不幸的是,这个问题的答案是不了。输入信息丢失,因为输出没有包含任何关于输入值的信息,这使得门不可逆。同样,对于其他两位门,如果我只给你门的输出值,你也不能 100%确定输入值是什么。
可逆性是量子比特门的一个独特属性,即您可以反转量子比特门的操作以获得先前状态。这也是因为量子力学的第二个公设指出,量子状态之间的变换必须是单位变换,因此是可逆的。当我们对一个基态 0 的单个量子比特应用 Hadamard 门时,我们就会看到这一点;如果我们对第一个 Hadamard 门之后应用另一个 Hadamard 门,那么量子比特的状态就会返回到基态 0。
最后,为了结束我们对经典门的讨论,我们将讨论通用逻辑门。这些门是用于创建其他逻辑门的那种门。NOR和NAND门是通用门的良好例子,因为它们可以用来创建 NOT 和 AND 门。让我们看看以下图表,它展示了如何使用 NAND 门创建一个 NOT 门(反相器):

图 6.1:使用 NAND 门创建 NOT 门
如您所见,通过将 NAND 门的两个输入连接在一起,形成一个单一输入(A),这逻辑上创建了一个 NOT 门,它会翻转输入的值。拥有通用门的计算系统是一个重要特性,因为它提供了组合复杂逻辑电路以解决问题的能力。这当然导致了集成电路的创造,这些是用于计算问题或执行特定操作(如加法器或计数器)的专用电路。
现在我们已经回顾了经典门的功能,我们可以继续到下一节,在那里我们将介绍量子逻辑门的基础。在那里,我们还将看到它们与经典比特的一些相似之处和一些独特属性。
理解量子单位算子
幺正算子定义为希尔伯特空间刚性旋转的幺正变换。当这些幺正算子作用于希尔伯特空间的基本态,例如,
和
态时,它们会改变状态向量的位置,但不会改变其长度。让我们看看这对一个量子比特意味着什么。量子比特的基本态按照 第五章,理解量子比特,
和
的描述映射到希尔伯特空间
,其中
和
是在幺正变换下保持正交性的线性变换。我们可以通过首先从数学角度来理解这个定义,来更好地把握这个定义。
在复向量空间上的线性变换可以用一个 2x2 矩阵 U 来描述:

此外,如果我们得到矩阵 U 的复共轭转置,记为
,通过转置矩阵 U 并应用复共轭,如图所示:

然后,我们可以说,如果矩阵 U 满足
,则 U 是幺正的,其中 I 代表单位矩阵
,如图所示:

一种直观的想法是,将幺正变换简单地想象为保持原始向量长度的复向量空间的旋转。复向量空间的旋转进一步确保量子变换不仅是幺正操作,而且是可逆操作,因为它们会围绕一个指定的轴旋转。
量子门的可逆性是通过幺正变换实现的。如前一个幺正方程所示,如果您通过一个门将幺正算子 U 应用到一个量子比特上,那么通过第二个门将幺正算子的复共轭
应用到量子比特上,结果将等同于对原始向量应用单位矩阵。
一个例子是,如果您触发一个将向量空间绕 x 轴旋转角度 π 的操作,然后应用该操作的复共轭,那么您将返回到您开始的位置。这种可逆功能是之前提到的某些经典比特门所不具备的,例如 AND 门。
在量子幺正变换中,信息不会丢失。如果您需要返回到之前的状态,只需使用它们的共轭转置,并按相反的顺序应用,您就可以回到最初的位置。我们将看到所有门中可逆性的有趣例子。
有一个特殊情况的操作是不可逆的,即测量算子,我们将在 理解不可逆算子 部分学习它。
现在我们已经理解了幺正和可逆算符,我们可以开始学习量子门了。
理解单量子比特门
在我们开始深入量子门的描述之前,让我们简化格式,使其易于理解和参考。直观地说,想象每个门操作的最容易方法是围绕指定的轴旋转在 Bloch 球表面上结束的矢量。同时回忆一下,Bloch 球始终从将单位矢量设置为初始状态开始。初始状态是在量子电路首次创建时设置的;在这种情况下,它被初始化为基态
(Bloch 球的北极),如下面的图所示:

图 6.2:基态
的 Bloch 球表示
要帮助我们理解在门真值表中看到的一些标签,我们可以定义每个轴的值,其中每个轴被称为基元素。例如,我们可以从之前的图中看到,z 轴的北极标记为
,南极标记为
。这两个点形成了基态向量
和
的计算基元素。然而,我们还没有为 x 或 y 轴定义标签。现在让我们定义它们。
每个基元素(轴)都有一个正负两侧,它们起源于 Bloch 球的中心。每个基都与每个轴相关联一个名称:
-
Computational 对 z 轴
-
Hadamard 对 x 轴
-
Circular 对 y 轴
x 基础的标签定义如下:

-x 基础的标签定义如下:

-y 基础的标签定义如下:

-y 基础的标签定义如下:

z 基础的标签定义如下:

-z 基础的标签定义如下:

标签也在以下 Bloch 球图的每个轴的末端进行了说明,其中虚线表示轴的负方向:

图 6.3:Bloch 球每个轴的基态标签
在代码片段中应用的每个门都会作用于从初始 |
状态开始的量子比特。你会看到一些门,我们将使用 H 门将它们制备成叠加态以观察效果。
在这种情况下,通过将向量过渡到x轴,然后应用 Z 门旋转,你可以更清楚地看到旋转的效果。如何做到这一点将在 Z 门的描述中详细介绍。但就现在而言,让我们打开辅助文件并回顾一个将帮助我们可视化门而无需编写太多代码,并处理一些重复性功能(如执行和可视化电路)的功能。这样,我们只需创建量子电路,添加门,并使用一个函数执行电路,该函数将返回结果和图像以可视化结果和电路图。首先,让我们回顾一下辅助文件中名为execute_circuit_sv的函数,该函数将处理此操作:
# Will run the circuit on the state vector (sv) simulator
# Returns state vector results, circuit diagram, BlochSphere and QSphere
def execute_circuit_sv(quantum_circuit):
#Get the state vector results
statevectorResults = run_sv_circuit(quantum_circuit)
#Draw the circuit diagram
circuit_diagram = quantum_circuit.draw(output="mpl")
#Draw the QSphere
q_sphere = statevectorResults.draw('qsphere')
#Draw the Bloch sphere bloch_sphere = statevectorResults.draw('bloch')
#Return the results, circuit diagram, and QSphere return statevectorResults, circuit_diagram, q_sphere, bloch_sphere
上述代码将返回四个组件:状态向量结果、电路图以及 QSphere 和 Bloch 球。我们将使用这些来展示每个状态向量结果、电路上的每个门以及可视化表示。
现在我们可以专注于量子门及其对量子比特的影响,而不太关注执行电路或显示结果。
霍达德(H)门
H门是最常用的量子门之一。这并不奇怪,因为这个门将量子比特的量子状态置于两个基态的复杂线性叠加中。这就是建立了大多数量子算法所利用的所有量子比特的叠加。它表示如下:

以下真值表说明该操作将量子比特的状态向量沿x轴和z轴旋转 90°(
),导致状态向量处于
和![img/B18420_06_018.png]的复杂线性叠加中:

表 6.2:霍达门操作的真值表
让我们继续,并创建一个新的笔记本,按照以下步骤添加一个电路:
-
首先,我们向量子比特添加一个 H 门,并在后端执行它,就像我们在前面的例子中所做的那样:
# Load helper file %run helper_file_1.0.ipynb #H-gate #Create the single qubit circuit qc = QuantumCircuit(1) #Add an H gate to the qubit qc.h(0) #Execute the circuit and capture all the results result, img, qsphere, bloch_sphere = execute_circuit_sv(qc) -
让我们通过运行以下单元格来检查状态向量结果:
result
这将打印出以下量子比特的状态向量值:
Statevector([0.70710678+0.j, 0.70710678+0.j], dims=(2,))
-
要绘制 H 门的电路图,请在单元格中运行以下代码:
img
这显示了添加了H门的量子比特的电路图,如下所示:

图 6.4:带有 H 门的电路图
-
现在要查看 Bloch 球表示,请在单元格中运行以下代码:
bloch_sphere -
Bloch 球表示已变为叠加态,这意味着它将具有等概率的结果为
或
。

图 6.5:H 门 Bloch 球
-
要查看 QSphere 表示,请在单元格中运行以下代码:
qsphere
正如你所见,QSphere 有相等的机会是
或![img/B18420_06_018.png]。正如你将注意到的,向量的尖端具有相同的直径,从视觉上表明两者具有相等的概率:

图 6.6:H 门 QSphere 表示
霍尔丹(H)门是一个独特的门。我们将在本章和未来的章节中多次看到这个门——具有否定状态向量,也称为相位踢的能力,在许多量子算法中非常有用。
泡利门
我们将要讨论的第一组单量子比特门通常被称为泡利矩阵门,以物理学家沃尔夫冈·泡利命名。这四个门,I、X、Y和Z的复数矩阵表示,定义为2 x 2复数矩阵,它们既是厄米矩阵也是幺正矩阵,分别用希腊字母西格玛表示
。当一个复数 N x N 矩阵的共轭转置等于其自身时,这样的矩阵被称为厄米矩阵。
注意,恒等矩阵的下标为 0,x, y, z下标也可以表示为
。
我们将从最简单的门开始,即恒等门。
恒等(I)泡利门
I 门,也称为恒等门,是一个不对量子比特执行任何操作的门。它不会改变量子比特的状态。在数学上,这表示为恒等矩阵,因此得名该门。该方程如下所示:

这个门的真实表显示输入和输出状态相同:

表 6.3:恒等门真值表
恒等门的概念通常在数学上用来说明操作的一些性质,正如我们在本章前面所做的那样,以证明幺正算子是可逆的。在那个例子中,恒等矩阵被用来说明将幺正算子与其复共轭相乘会产生与不对量子比特进行操作,或使用恒等矩阵对量子比特进行操作相同的结果。
让我们继续到下一个门部分。
非(X)泡利门
X 门也称为非门,因为它对基态的影响与它的经典比特门对应物相似。一个显著的区别是 X 门将状态向量从一个基态移动到另一个基态,如表 6.4所示。通过布洛赫球结果可视化此操作可以看到从初始状态
的向量旋转。由于其球面表示,我们称这些操作为绕某个轴的旋转,在这种情况下,X 门是绕x轴的π(180°)旋转,这由泡利 X 门算子如下表示:

以下真值表说明了该操作将输入绕x轴旋转
(180 度),因此如果输入是
,则输出是
,反之亦然:

表 6.4:X(非)门真值表
现在,让我们按照以下步骤创建一个电路,并将其包含在我们的笔记本中:
-
首先,向其中添加一个 X 门,并使用我们的辅助函数执行它,以为我们做繁重的工作:
#X-gate #Create the single qubit circuit qc = QuantumCircuit(1) #Add an X gate to the qubit qc.x(0) #Execute the circuit and capture all the results result, img, qsphere, bloch_sphere = execute_circuit_sv(qc) -
让我们通过运行以下单元格来检查状态向量结果:
result
这将打印出量子比特的状态向量值,我们预计它应该代表
,其中a=0 和b=1,如下所示的结果:
Statevector([0.+0.j, 1.+0.j], dims=(2,))
-
然后,为了绘制 X 门的电路图,请在单元格中运行以下代码:
img
这显示了添加到量子比特的X门的电路图,如下所示:

图 6.7:X 门
-
现在,要查看布洛赫球表示,请在单元格中运行以下代码:
bloch_sphere -
布洛赫球表示已将量子比特的状态从
变为
:

图 6.8:X 门布洛赫球
-
现在,要查看 QSphere 表示,请在单元格中运行以下代码:
qsphere
QSphere 表示量子比特从
到
的量子状态转换:

图 6.9:X 门 QSphere
如我们所见,X 门是一个很好的例子,说明量子门也可以执行与经典门相同的操作。从 QSphere 结果中,你还会注意到代表状态向量相位的彩色轮,在这种情况下,它呈蓝色,表示它在相位(0)。
Y 泡利门
Y 门是绕y轴旋转
(180°),如下所示:

在这里,以下真值表说明了操作将输入绕y轴旋转了
(180°),因此如果门的输入是
,那么门的输出是
,反之亦然;注意在
处有i相位,而
有一个相位偏移,用-i表示:

表 6.5:表示 y 轴相位旋转的真值表
现在,让我们按照以下步骤创建一个电路:
-
首先,向其中添加一个 Y 门,并使用我们的辅助函数执行它,该函数提供了我们执行的每个量子电路及其可视化表示:
#Y-gate operation on a qubit #Create the single qubit circuit qc = QuantumCircuit(1) #Add a Y gate to the qubit qc.y(0) #Execute the circuit and capture all the results returned result, img, qsphere, bloch_sphere = execute_circuit_sv(qc) -
通过运行以下单元格来检查状态向量结果:
result
这将打印出量子比特的状态向量值:
Statevector([0.-0.j, 0.+1.j], dims=(2,))
-
要绘制 Y 门的电路图,请在单元格中运行以下代码:
img
前面的代码显示了添加到量子比特的 Y 门的电路图,如下所示:

图 6.10:Y 门
-
现在,要查看布洛赫球面表示,请在单元格中运行以下代码:
bloch_sphere -
布洛赫球面表示已从
变为
;然而,旋转是围绕y轴而不是x轴进行的,在这种情况下,结果是相同的。

图 6.11:Y 门布洛赫球面
-
要查看 QSphere 表示,请在单元格中运行以下代码。当然,您也可以使用
bloch_sphere来查看布洛赫球面:qsphere
如您所见,QSphere 将量子比特的状态从|
转换到|
。请注意,表示状态的色彩可能根据您的系统设置而不同,或者随着时间的推移在代码本身中可视化时可能发生变化:

图 6.12:Y 门 QSphere
从结果中我们可以看到,Y 门与 X 门操作非常相似,至少当状态向量的原点是相同的时候。
现在,让我们继续到最后一个泡利门。
Z 门
Z 门也常被称为相位门,主要是因为它不像 X 和 Y 门那样沿着垂直轴旋转,而是沿着希尔伯特空间的经度旋转,因此产生了希尔伯特空间的相位。这表示如下:

以下真值表说明操作将输入绕z轴旋转
(180°)。如果旋转从
基态开始,则相位不变;然而,如果输入从
状态开始,则输出是相位为p到
的相移。这种否定是一个非常重要的特性,您将在许多量子算法中看到:

表 6.6:围绕 x 轴相移的真值表
现在让我们创建一个用于 Z 门的电路:
-
首先,我们使用 H 门将量子比特置于叠加态,然后向其添加一个 Z 门算子:
#Z-gate #Create the single qubit circuit qc = QuantumCircuit(1) #Add an H gate to the qubit to set the qubit in #superposition qc.h(0) #Add a Z gate to the qubit to rotate out of phase by π/2 qc.z(0) #Execute the circuit and capture all the results result, img, qsphere, bloch_sphere = execute_circuit_sv(qc) -
让我们通过运行以下单元格来检查状态向量结果:
result
这将打印出量子比特的状态向量值。请注意,根据您的设置,非常小的值可能被截断为 0。如果没有设置,您可能会看到一个非常小的值,例如0.00000000e+00j:
Statevector([ 0.70710678+0.j, -0.70710678+0.j], dims=(2,))
-
要绘制 Z 门的电路图,请在单元格中运行以下代码:
img
这显示了移除 H 门后的电路图,所以不要认为你必须包含 H 门才能使用 Z 门——如前所述,H 门只是添加来展示门的操作效果:

图 6.13:包含 Z 门的电路图
-
现在,要查看布洛赫球面表示,请在单元格中运行以下代码:
bloch_sphere -
布洛赫球面表示已经变成了在
和
之间的叠加态;然而,它位于x轴的负侧。

图 6.14:Z 门布洛赫球面
-
要查看 QSphere 表示,请在单元格中运行以下代码:
qsphere
如您所见,QSphere 有等概率处于|
和|
状态;然而,您所看到的|
状态相位差为
,如下面的输出所示:

图 6.15:应用第一个 H 门后的 Z 门 QSphere 表示
如前图所示,Z 门提供了一种对量子比特执行相移的方法,导致量子比特的状态从正变为负。如果您想亲自体验,请尝试以下操作。
回想一下你之前运行的执行 X 门的代码。在那个例子中,我们从一个初始基态|
的量子比特开始,然后应用了一个导致状态|
的 X 门。现在,在添加 X 门之后添加另一行,并包括 Z 门。你会注意到结果是相同的,|
,但现在你会注意到状态结果是负的。我将留给你自己尝试并观察差异。
让我们继续到下一节,我们将讨论相位门。
相位门
相位门是我们用来将|
映射到|
|
的工具,其中
是欧拉公式。这不会影响测量|
或|
的概率;然而,它确实会影响量子状态的相位。这可能现在还不清楚,但一旦你开始学习一些利用相移的高级特性,它将会非常清晰。现在,让我们学习操作量子比特上各种相移的门。
S 门
S 门就像一个 Z 门;唯一的区别是状态向量旋转的量。对于 S 门,这个旋转是
。S 门的矩阵表示在这里描述:

以下真值表说明了操作旋转输入绕z轴 90°(
),因此如果输入是|
,则输出是相位偏移
:

表 6.7:表示相位旋转 S 的真值表
我们将遵循以下步骤来创建一个包含 S 门的电路:
-
通过将向量首先放置在x轴上,可以用真值表最好地说明;我们首先添加一个 H 门,然后再添加 S 门:
#S-gate #Create the single qubit circuit qc = QuantumCircuit(1) #Add an H gate to the qubit to drop the vector onto the #X-axis qc.h(0) #Add an S gate to the qubit qc.s(0) #Execute the circuit and capture all the results result, img, qsphere, bloch_sphere = execute_circuit_sv(qc) -
通过运行以下单元格,让我们检查状态向量结果:
result
这将打印出量子比特的状态向量值:
Statevector([7.07106781e-01+0.j, 4.32978028e-17+0.70710678j], dims=(2,))
-
要绘制 S 门的电路图,请在单元格中运行以下代码:
img
这显示了添加了 H 门以诱导叠加,然后对量子比特应用S门的电路图,如下所示:

图 6.16:包含 S 门的电路
-
现在,要查看 Bloch 球表示,请在单元格中运行以下代码:
bloch_sphere -
Bloch 球表示已经变成了叠加态,这意味着它将有一个相等的概率结果为
或
,但有一个相移
:

图 6.17:S 门 Bloch 球
-
要查看 QSphere 表示,请在单元格中运行以下代码:
qsphere
如您所见,QSphere 有相等的概率是
和
,相位差为
:

图 6.18:S 门,QSphere 上的
相位旋转
当 S 门通过
将状态转换到正 Z 轴时,我们现在将看到如何通过
将状态转换到负 Z 轴:
S†(求逆)门
门与 S 门相同,只是它沿相反方向或负方向旋转。因此,结果相同,但取反。矩阵表示通过包括负相位差来说明这一点:

以下真值表说明该操作将输入绕 z 轴旋转
(-90)。与 S 门一样,如果输入是
状态,则输出是
,但如果输入是
状态,则输出是负方向的相位旋转:

表 6.8:相位门
的真值表表示
这最好通过首先使用 H 门将量子比特置于叠加态来展示。然后,我们通过以下步骤创建
门的电路图:
-
在附加
(sdg) 之前,我们首先添加一个 H 门:#Sdg-gate #Create the single qubit circuit qc = QuantumCircuit(1) #Add an H gate to the qubit to drop the vector onto the #X-axis qc.h(0) #Add an S† gate to the qubit qc.sdg(0) #Execute the circuit and capture all the results result, img, qsphere, bloch_sphere = execute_circuit_sv(qc) -
让我们通过运行以下单元格来检查状态向量结果:
result
这将打印出量子比特的状态向量值:
Statevector([0.70710678+0.j, 4.32978028e-17-0.70710678j], dims=(2,))
-
要绘制
门的电路图,请在单元格中运行以下代码:img
这显示了添加到量子比特的
门的电路图,如下所示:

图 6.19:带有
门的电路
-
现在,要查看 Bloch 球表示,请在单元格中运行以下代码:
bloch_sphere -
Bloch 球表示已变为叠加态,这意味着它将以
或
的相等概率结果,相位差为
或
。

图 6.20:
门 Bloch 球
-
要查看 QSphere 表示,请在单元格中运行以下代码:
qsphere
如您所见,QSphere 有相等的概率是
和
,相位差为
或
:

图 6.21:
门,QSphere 上的
相位旋转
现在我们已经创建了一个包含
门的电路,并且可以沿相反方向移动量子比特的相位,而不受限于应用相位移动的单个方向,我们将继续到下一节,该节将帮助我们了解如何使用 T 门创建电路。
T 门
T 门与 S 门相同,只是旋转方向不同
。该门的矩阵表示如下:

下面的真值表说明了该操作将输入绕z轴旋转
(45°),因此如果输入是
状态,则输出将与输入相同。然而,如果输入是
,则输出将是一个
的相位旋转:

表 6.9:相位门 T 的真值表表示
与所有相位门一样,最好从叠加态开始,因此我们将首先包括一个 Hadamard 门,然后我们将使用 T 门创建一个电路,如下面的步骤所示:
-
首先,我们在添加 T 门之前添加一个 H 门:
#T-gate #Create the single qubit circuit qc = QuantumCircuit(1) #Add an H gate to the qubit to drop the vector onto the #X-axis qc.h(0) #Add a T gate to the qubit qc.t(0) #Execute the circuit and capture all the results result, img, qsphere, bloch_sphere = execute_circuit_sv(qc) -
我们然后通过运行以下单元格来检查状态向量结果:
result
这将打印出量子比特的状态向量值:
Statevector([0.70710678+0.j , 0.5+0.5j], dims=(2,))
-
要为 T 门绘制电路图,请在单元格中运行以下命令:
img
这显示了添加了T门的量子比特的电路图,如下所示:

图 6.22:T 门的电路表示
-
现在,要查看 Bloch 球表示,请在单元格中运行以下命令:
bloch_sphere -
Bloch 球表示已变为叠加态,这意味着它将具有
或
的相等概率结果,并且通过
转换了状态的相位。

图 6.23:
门 Bloch 球
-
要查看 QSphere 表示,请在单元格中运行以下命令:
qsphere
正如你所见,QSphere 通过
转换了状态的相位:

图 6.24:T 门,QSphere 上的
相位旋转
与 S 门类似,我们希望沿所有方向旋转,因此让我们看看一个将量子比特状态转换到相反方向的相位门。
T†(共轭)门
门与 T 门具有相同的相位旋转,即
,只是在相反方向。其矩阵表示如下:

下面的真值表说明了该操作将输入绕z轴旋转
(-45°),因此如果输入是|
,则输出是|
。如果输入是|
,则输出是
的负旋转:

表 6.10:相位门
的真值表表示
这最好通过首先将向量放置在x轴上来说明,因此我们将通过以下步骤创建一个电路,使用
门:
-
首先,我们在添加
(tdg)门之前添加一个 H 门:#Tdg-gate #Create the single qubit circuit qc = QuantumCircuit(1) #Add an H gate to the qubit to drop the vector onto the #X-axis qc.h(0) #Add a Tdg gate to the qubit qc.tdg(0) #Execute the circuit and capture all the results result, img, qsphere, bloch_sphere = execute_circuit_sv(qc) -
接下来,我们通过运行以下单元格来检查状态向量结果:
result
这将打印出量子比特的状态向量值,其中你会注意到虚数现在是负数:
Statevector([0.70710678+0.j , 0.5 -0.5j], dims=(2,))
-
要绘制
门的电路图,请在单元格中运行以下代码:img
这显示了添加到量子比特的
门的电路图,如下所示:

图 6.25:使用
门的电路表示
-
现在,要查看 Bloch 球表示,请在单元格中运行以下代码:
bloch_sphere -
Bloch 球表示已经变成了叠加态,这意味着它将具有
或
的等概率结果,并且通过
转换了量子比特的状态。

图 6.26:
门,
相位旋转在 Bloch 球上
-
要查看 QSphere 表示,请在单元格中运行以下代码:
qsphere
如你所见,QSphere 已经通过
转换了量子比特的状态:

图 6.27:
门,
相位转换在 QSphere 上
前面的门,正如你可能注意到的,具有从水平或垂直轴预定的旋转角度,以帮助你设置量子比特的状态。如果你希望自行指定旋转角度,以下旋转门允许你指定围绕给定轴旋转的角度。像其他门一样,这些旋转门也是可逆的和幺正的。
Rx 门
你可以将Rx 门视为自定义旋转门。请注意,我使用了术语rotate而不是flip。这是因为量子门的操作通常是通过 QSphere 来可视化的。
由于其球面表示,我们称操作为绕轴旋转:
![img/B18420_06_153.png]
通过应用 Y 旋转,我们得到以下公式:
![img/B18420_06_154.png]
最后,一个 Z 旋转将产生以下公式:
![img/B18420_06_155.png]
我们将使用旋转门之一来创建一个电路——让我们选择 Rz 门:
-
首先,我们将沿着z轴旋转,如图所示 ![img/B18420_06_156.png]。我们将使用
math库来导入``pi,并且我们的友好 H 门将被应用来帮助说明相移:#Rz-gate #Create the single qubit circuit qc = QuantumCircuit(1) #Import pi from the math library from math import pi #Add an H gate to help visualize phase rotation qc.h(0) #Add an RZ gate with an arbitrary angle theta of pi/6 qc.rz(pi/6, 0) #Execute the circuit and capture all the results result, img, qsphere, bloch_sphere = execute_circuit_sv(qc) -
接下来,我们通过运行以下单元格来检查状态向量结果:
result
这将打印出量子比特的状态向量值:
Statevector([0.06830127-0.1830127j, 0.6830127+0.1830127j], dim=(2,))
-
要绘制 Rz 门的电路图,请在单元格中运行以下代码:
img
这显示了添加了Rz门的量子比特的电路图,如下所示:

图 6.28:使用 Rz 门的电路表示
-
现在,要查看 Bloch 球表示,请在单元格中运行以下代码:
bloch_sphere -
Bloch 球表示已经变成了叠加态,这意味着它将具有![img/B18420_05_004.png]或![img/B18420_05_005.png]的等概率结果,并且通过一个相移![img/B18420_06_156.png]旋转了状态。

图 6.29:RZ 门 Bloch 球
-
要查看 QSphere 表示,请在单元格中运行以下代码:
qsphere
如您所见,QSphere 通过一个相移![img/B18420_06_156.png]转换了状态:

图 6.30:RZ 门 QSphere 转换了π/6 的相移
这些旋转门帮助我们提供围绕每个轴的特定门旋转。
有其他通用门,它们通过更通用的替代方案模仿自定义门的功能,所以让我们接下来回顾它们。
通用 U 门
如前所述,U 门用于定义一个通用量子系统,其中您需要确保量子系统符合某些标准,其中最流行的是DiVincenzo 标准,其中之一指出它应该有一个通用的量子门集。
我们讨论了在经典系统中,NOR 和 NAND 门都被认为是经典通用门。在量子系统中,U门被定义为通用门,因为它能够为量子比特的 Hilbert 空间提供多个自由度来旋转。U 门有参数字段,用于确定状态向量应该在给定轴上移动多少。让我们首先单独查看它们,然后我们将每个门应用到量子比特上以检查结果。
U门有三个参数,分别应用于所有轴上的旋转,即x轴、y轴和z轴。U 门的矩阵表示定义为以下:
![img/B18420_06_161.png]
在前面的方程式
,
,和
中,角度是以弧度表示的,如前所述的
方程式。请注意,为了使 U 门保持为单位运算,即
,角度必须限制在
和
的范围内。我们也可以在 U 矩阵中看到这些范围,这些值位于矩阵的参数中,这使相位 l 也有一个范围
。
让我们创建一个实现 U 门的电路:
-
首先,我们将创建一个单比特电路,并将 U 门应用于它,每个角度设置为![img/B18420_06_029.png]。我们将重用我们的状态向量辅助函数
execute_circuit_sv,以便提取状态向量结果,并使用 QSphere 来可视化状态向量:#U-gate from math import pi #Create a single qubit circuit qc = QuantumCircuit(1) #Add a U gate and rotate all parameters by pi/2, and #apply it to the qubit qc.u(pi/2, pi/2, pi/2, 0) #Execute the circuit and capture all the results result, img, qsphere, bloch_sphere = execute_circuit_sv(qc)
我们将看到的值设置为以下:
Statevector([7.07106781e-01+0.j, 4.32978028e-17+0.70710678j], dims=(2,))
注意,为了方便起见,我将结果中的值4.32978028e-17替换为0,因为该数字太小,不具意义。
- U门的预期电路图如下,参数列于底部:

图 6.31:将所有旋转参数设置为![img/B18420_06_029.png]的 U 门
-
现在,要查看 Bloch 球面表示,请在单元格中运行以下命令:
bloch_sphere
Bloch 球面表示已更改,状态是在所有参数应用指定的旋转后设置的。

图 6.32:U 门,Bloch 球面表示,所有参数设置为![img/B18420_06_029.png]
执行qsphere命令产生的 QSphere 表示如下:

图 6.33:将所有参数设置为![img/B18420_06_029.png]的 U 门的 QSphere 表示
如您所见,您可以对 U 门应用各种角度,以便将量子比特的状态设置为希尔伯特空间中的任意一点。这也允许您使用 U 门初始化量子比特的状态。
现在您知道单比特门提供了许多方法来创建其基态的复杂线性组合,但仅操纵单个量子比特不足以执行大多数量子算法所需的功能。一个这样的例子是量子纠缠。这就是多比特门发挥作用的地方。让我们在下一节中了解这些内容。
理解多比特门
如第五章中所述,两个或更多的量子比特可以通过它们的张量积组合它们的状态,有时也称为克罗内克积。
在本节中,我们将讨论多量子比特门以及它们如何与单量子比特门类似地操作量子比特,包括它们是幺正的和可逆的。
为了保持描述和示例的一致性,以下多量子比特门的描述将以与单量子比特门相同的方式进行展示。我们将再次打开辅助文件并回顾另一个名为 execute_circuit_returns 的函数。辅助函数将有一些不同之处,首先是使用 Qiskit 原语 Sampler 来运行我们的电路。我们目前使用它是为了确保你不会用完这些简单电路分配的量子硬件时间。为书中后面更有趣的工作保留硬件。同样,就像我们之前用于单量子比特门的辅助函数一样,这个函数将返回多个值,包括:总计数、电路图和结果直方图(或分布)。让我们回顾一下代码:
# Will execute the circuit on the Sampler primitive
# Returns results, circuit diagram, and histogram
def execute_circuit_returns(quantum_circuit):
from qiskit.primitives import Sampler
sampler = Sampler()
result = sampler.run(quantum_circuit, shots=1024).result()
quasi_dists = result.quasi_dists
#Get the counts
counts = quasi_dists[0].binary_probabilities()
circuit_diagram = quantum_circuit.draw(output="mpl")
#Create a histogram of the results
histogram = plot_distribution(counts)
#Return the results, circuit diagram, and histogram
return counts, circuit_diagram, histogram
转向 Sampler 原语的目的并不是我们不能使用状态向量模拟器;主要是为了我们可以观察我们电路和门的一些有趣特性。对于那些希望使用状态向量模拟器的人来说,不用担心。在本章末尾的问题部分将有一些挑战,这将允许你使用它。
你还会看到另一个不同之处在于,我们不再使用 Bloch 球或 QSphere 来可视化量子状态。相反,我们将用准分布的直方图来代替它们,准分布是一个类似于字典的类,用于表示准概率。对于我们将要创建的每个电路,我们将包括一个以上的量子比特,因为这些多量子比特门都在两个或更多的量子比特上操作。
现在我们已经回顾了我们的辅助函数,让我们继续到下一组门,即多量子比特门,特别是 2 量子比特门。这些包括以下内容:
-
CNOT 门
-
Toffoli 门
-
Swap 门
我们将在接下来的章节中学习这些门。
CNOT 二量子比特门
CNOT 门,通常被称为控制非门,与经典位运算中的异或门相似,即如果你提供两个输入状态,要么是 0 要么是 1,结果将与输入状态通过异或门运行的结果相同。CNOT 门由两部分组成。
第一部分是控制,它连接到一个量子比特上,并且是触发 CNOT 门对连接到 CNOT 门另一端的另一个量子比特执行操作的触发器,即目标。
目标是对其他量子比特执行的操作;在这种情况下,它是一个NOT操作。回想一下之前关于单量子比特门的部分,NOT 门将量子比特绕x轴旋转。CNOT 门是更常用的多量子比特门之一,因为它是量子比特纠缠的方式。
CNOT 门也被称为控制-X(CX)门,因为目标通常编码为 X 操作。您将在运行以下示例时看到这个 CX 门约定。
由于两个量子比特的张量积,CNOT 门的矩阵表示是一个4 x 4矩阵,如下所示:

注意,CNOT 矩阵的左上角2 x 2象限代表一个单位矩阵 I,右下角2 x 2象限代表X矩阵。这个矩阵描述了第一个量子比特 q[0]是目标,第二个量子比特 q[1]是控制的状态。
下面的真值表说明了当控制量子比特(输入向量的左侧)为 0 时,目标量子比特(输入向量的右侧)没有变化。当控制量子比特设置为 1 时,则目标量子比特的操作被启用,因此将目标量子比特绕x轴旋转π(即 180°):

表 6.11:CNOT 门的真值表表示
现在我们来创建一个电路,添加一个 CNOT 门,并执行它:
-
我们将首先创建一个双量子比特量子电路,并在第一个量子比特上应用 Hadamard 门,在两个量子比特上应用 CNOT 门,其中控制设置为第一个量子比特,目标设置为第二个量子比特:
#CNOT-gate #Create a two-qubit circuit qc = QuantumCircuit(2) #Add an H gate to the qubit qc.h(0) #Add an CNOT gate where, control = first, target = second #qubit qc.cx(0,1) #Measure all qubits and send results to classical bits qc.measure_all() #Execute the circuit and capture all the results counts, img, histogram = execute_circuit_returns(qc) -
然后,我们将通过运行以下单元格来回顾结果计数:
counts
这将打印出计数结果:
{'11': 526, '00': 498}
-
要绘制 CNOT 门的电路图,请在单元格中运行以下代码:
img
下面的电路图说明了 CNOT 门,其中控制是 q[0],目标是 q[1]:

图 6.34:使用 CNOT 门的电路表示
-
要查看执行之前电路后的计数直方图结果,请在单元格中输入以下内容:
histogram
下面的图示包括一个 H 门的结果。下面的图表显示了结果为 00 或 11 的概率:

图 6.35:电路 CNOT 结果的历史图表示,其中 y 轴表示结果的准概率。
之前电路的结果说明了 CNOT 门如何用于纠缠两个量子比特,其中一个量子比特可以控制另一个量子比特的操作,在这种情况下,对目标量子比特应用 NOT 门。
在下一节中,我将阐明多个量子比特的纠缠意味着什么。还有其他控制门实现其他操作,例如Control-Y(CY)、Control-Z(CZ)、Control-H(CH)等。这些门都与 CNOT(CX,Control-X)门具有相同的特征,即它们都有一个源和目标。正如你可以想象的那样,主要区别在于目标会执行的操作。对于 CNOT 门,目标会与 X 门一起操作,而自然地,一个 Control-Y 门会在目标量子比特上执行 Y 门。自己尝试几个,看看结果如何不同。注意,操作将与单独运行到目标门的操作相同。
我们将要关注的最后一个多量子比特门,它也被用于各种量子算法中,是Toffoli门。
Toffoli 多量子比特门
Toffoli门是以 Tommaso Toffoli 的名字命名的,他是波士顿大学计算机和电气工程系的意大利裔美国教授。这个门与前面提到的多量子比特控制门非常相似,只是这个门有多个控制和一个单一的目标,在这种情况下是一个 NOT 门。为了简化多控制门的描述,它们以下列格式写出:CCX。这是为了表明它是一个双控制控制非门,而CCCX是一个三控制控制非门。
Toffoli 门的通用矩阵表示是一个8 x 8矩阵,因为三个量子比特的张量积,如下面的矩阵所示。注意,前三个对角线2 x 2矩阵块是单位矩阵,最后一个2 x 2矩阵(右下角)是 NOT 门的表示,它翻转量子比特。注意,Qiskit 中 Toffoli 门的矩阵略有不同,因为它将矩阵大小增加了 2^n,其中n是应用该门的量子比特数量。在这种情况下,我们有 2³,因为我们有一个三量子比特门:

让我们运行这个门来看看它在我们的量子电路上的结果:
-
我们将首先创建一个三量子比特量子电路,并应用一个
CCX(Toffoli)门,其中前两个量子比特是控制量子比特,第三个量子比特是目标量子比特:#Toffoli (CCX)-gate #Create a three-qubit circuit qc = QuantumCircuit(3) #Enable the Control qubits, first two qubits, of the Toffoli gate qc.x(range(2)) #Add the Toffoli gate (CCX) qc.ccx(0,1,2) #Execute the circuit and capture all the results result, img, qsphere, bloch_sphere = execute_circuit_sv(qc)
执行这个电路的结果不会令人惊讶,由于我们是在一个三量子比特电路上运行一切,因此有 8 种可能的状态,这意味着 2³个基态,其中最后一个状态 111 是唯一被设置的:
[0.+0.00000000e+00j 0.+0.00000000e+00j 0.+0.00000000e+00j
0.+0.00000000e+00j 0.+0.00000000e+00j 0.+0.00000000e+00j
0.+0.00000000e+00j 1.-3.05311332e-16j]
-
让我们在一个新的单元格中可视化 Bloch 球:
bloch_sphere

图 6.36:Toffoli(CCX)门电路结果的 Bloch 球表示
- 现在,让我们使用
qsphere命令在 QSphere 上查看我们的结果:

图 6.37:Toffoli(CCX)门电路结果的 Qsphere 表示
从上述结果中我们可以看到,最终状态是
,正如预期的那样,因为量子比特最初都处于
状态,但由于控制量子比特被分别设置为
,这设置了目标量子比特(一个非门),使其启用并因此改变目标量子比特 q[2]的状态到
状态,因此得到结果
。
Toffoli 门的电路图如下:

图 6.38:Toffoli(CCX)门的电路表示
-
让我们看看如何使用基本门来创建这个三量子比特门。在一个新的单元中,运行量子电路的
decompose函数:qc_decomposed = qc.decompose() qc_decomposed.draw(output="mpl")
这将导致以下所有创建单个 Toffoli 门所需的门的示意图。请注意,这可能与写作时的显示不同:

图 6.39:创建 Toffoli(CCX)门所需的门
这看起来非常复杂。您可以看到,用于表示这个门的各个单量子比特和多量子比特门的使用相当复杂。在这个例子中,您可以看到 H、CNOT 和
门的使用。还有其他多量子比特门,它们利用单量子比特和双量子比特门来操作。Toffoli 门允许我们在多个量子比特上操作,我们将在开始深入研究量子算法时看到这一点。
让我们看看我们用来在量子比特之间交换信息的门。
交换门
交换门用于交换两个量子比特的值。交换门的矩阵表示如下:

让我们创建一个电路,并通过交换两个量子比特来实现这一点:
-
我们将第一个量子比特设置为
状态,第二个量子比特设置为
状态。然后,我们将使用swap门在这两个量子比特之间进行交换,并验证每个量子比特的结果:#Swap-gate from math import pi #Create a two-qubit circuit qc = QuantumCircuit(2) #Qubit 0 is initialized to |0> state #Prepare qubit 1 to the |1> state qc.x(1) #Now swap gates qc.swap(0,1) #Execute the circuit and capture all the results result, img, qsphere, bloch_sphere = execute_circuit_sv(qc)
通过查看电路的结果图(img),您将看到交换门的电路图,正如这里所示,就在我们为了比较而包含的 X 门之后:

图 6.40:X 门后跟交换门的电路图
- 在查看每个量子比特的 Bloch 球和 QSphere 结果之前,让我们花点时间回顾一下我们期望看到的内容。我们的两个量子比特首先被初始化为
状态,然后我们对第二个量子比特(q1)应用 X 门,将其状态改变为
。最后,我们添加了一个交换门来交换 q[0]和 q[1]的值,这将导致
和
。让我们看看 Bloch 球和 QSphere 的结果:

图 6.41:交换门的布洛赫球表示
- 在这里,你看到的结果是交换了一个量子比特的值与另一个量子比特,最终结果是量子比特 0 具有我们设置的值或量子比特 1,反之亦然。现在让我们看看 QSphere 的结果:

图 6.42:应用交换门后的 QSphere 表示
太棒了!正如我们可以在之前的图中看到的结果,显示两个量子比特的状态向量都按照预期设置,分别是
和
。
这是一个描述量子比特结果顺序在括号表示法中显示的好机会。注意从之前图中的 QSphere 值,位置 0(最右边的值)被设置为 1,位置 1 的量子比特被设置为 0。这就是括号量子比特的顺序。因此,随着更多量子比特的添加,它们被附加到之前量子比特的左侧,例如,
在本节中,我们学习了多量子比特门,即 CNOT 和 Toffoli 门。我们还学习了一个额外的门,即交换门。
现在我们已经熟悉了单量子比特和多量子比特门,让我们回顾一下不可逆测量算子。
理解测量算子
不可逆算子是应用于量子比特(s)的算子,如果再次在相同的量子比特上应用相同的算子,则结果不会将量子比特返回到应用算子之前的状态。
本节将涵盖不可逆算子,特别是测量算子,以及为什么它们与之前讨论的其他算子一样重要。
测量是一个指令量子系统测量系统的量子状态的算子。在我们深入探讨如何在我们的量子电路中包含测量指令之前,让我们首先定义一下测量系统量子状态的含义。
注意,测量结果通常是概率性的。我们失去的信息是每个计算基态的复振幅,我们可以将其编码信息。在最理想的情况下,我们可以多次重新运行和测量电路,至少得到统计数据。
我们从量子力学中知道,关于量子系统的信息是无法访问的,特别是对量子比特复振幅的测量。例如,假设我们有一个处于叠加态
的量子比特,其中复振幅之和为 1:

对前面的测量无法提供
中的复振幅信息。量子比特的测量返回的是标准基中状态
的基
,其概率为
。
我们在描述 Hadamard 门时已经查看了一个这样的例子。当我们设置量子比特为 a|
和 b|
的复线性组合时,其中 a 和 b 是基态的复振幅,测量结果基于测量
的概率
和测量|
的概率
,对于 Hadamard 门结果是 50%,或
。
关于测量系统状态的一个重要注意事项是,一旦你测量它,系统的量子信息就会丢失。这意味着通过测量量子比特,状态将坍缩到两个基态之一,
或
,这取决于量子状态的分量幅度,
和
。测量之后,你就不再拥有包含在 a 和 b 中的信息来做其他任何事情了。
如果你尝试测量你刚刚测量的相同量子比特,结果将与第一次测量相同,但不会将量子比特设置回测量之前的量子状态。因此,测量是一个不可逆算子。
一旦完成测量,结果就会被发送到经典比特,这将信息返回到经典系统。现在我们了解了测量是如何工作的以及测量的结果是什么,让我们运行一些代码来看看它是如何工作的!
在这个例子中,我们将创建一个贝尔态电路(我们在第四章,理解基本量子计算原理中详细介绍了这些),它包含一个 Hadamard 门后跟一个 CNOT 门:
-
首先,我们将在电路的末尾添加测量函数
measure_all(),它将自动将测量量子比特的结果映射到它们各自的经典比特。我们还将添加 Hadamard 门和 CNOT 门:#CNOT-gate #Create a two-qubit circuit qc = QuantumCircuit(2) #Add an H gate to the qubit qc.h(0) #Add a CNOT gate where, control = first, target = second #qubit qc.cx(0,1) #Measure qubits and map to classical bits qc.measure_all() #Execute the circuit and capture all the results result, img, histogram = execute_circuit_returns(qc) -
现在我们通过在新的单元格中输入以下内容来查看我们的结果:
Result
我们的结果如下:
{'11': 448, '00': 512}
-
我们的辅助函数还包括了
histogram图,这有助于可视化前面的结果。要查看直方图,请在下一个单元格中输入以下内容:histogram
输出是以下直方图:

图 6.43:测量结果的直方图
-
现在,让我们看看添加了测量算子后的电路看起来是什么样子。在另一个单元格中运行以下内容:
img
在以下图中所示电路的末尾,你会看到测量算符被添加到了所有量子比特上。你会看到经典比特的标签被命名为measure,量子比特被映射到它们各自的比特上,这些比特由索引数字标识,测量终止于经典比特:

图 6.44:添加到量子电路中的测量算符
障碍只是为了方便而添加的,以可视化电路操作结束和测量开始的位置。
-
measure_all()函数是一种方便的方法,可以将测量算符应用于你的量子电路。你也可以分别和不同时间对每个量子比特应用一个measurement算符,或者如果你希望改变量子比特到经典比特的分配,可以使用列表来安排映射。让我们再次重写我们的函数,但这次我们将为第一个电路(qc1)单独添加测量算符,然后使用列表以相同的方式对第二个电路(qc2)进行操作。这样你可以看到应用测量算符的各种方式,要么一次性应用,要么只测量某些量子比特:#Measurement operator #Create two separate two-qubit, #and two classical bit circuits qc1 = QuantumCircuit(2,2) qc2 = QuantumCircuit(2,2) #In the first circuit (qc1), measure qubits individually qc1.measure(0,0) qc1.measure(1,1) #In the second circuit (qc2) measure using a list qc2.measure([0,1],[0,1]) #Execute the circuit and capture all the results result, img, histogram = execute_circuit_returns(qc1) result2, img2, histogram2 = execute_circuit_returns(qc2)
执行代码后,在单独的单元格中显示两个图像(img 和 img2),并注意从测量算符的角度来看,两个电路看起来是相同的。
在本节中,你学习了非可逆算符。我们还使用测量算符创建了一个简单的双量子比特电路。
摘要
在本章中,你学习了所有可以操作单量子比特和多量子比特的各种方法。这些操作提供了各种向量状态,每个量子比特都可以旋转到这些状态。你还学习了如何在电路中可视化门,并学习了如何将它们分解为通用门,以便你可以实现传递给量子系统的信息。
你现在已经理解了这些门如何在量子比特上操作。你现在拥有的技能将极大地帮助你理解如何在许多量子算法中使用门,以帮助解决各种问题。
在下一章中,我们将学习关于量子信息科学套件(Qiskit),发音为kiss-kit(根据你问的人不同,它也可能被发音为kwis-kit)。Qiskit 提供了我们迄今为止用来操作量子比特的许多对象和函数,以及其他有助于创建量子算法、减轻近期设备中发现的噪声,并产生用户可以利用的量子算法的功能。
问题
-
对于多量子比特门,尝试翻转源和目标。你在分解电路时看到有区别吗?
-
将单量子比特和多量子比特电路的所有门都分解开。你注意到通用门是如何构建的?
-
在一个三量子比特电路的中心量子比特上实现 Toffoli 门。
-
将 Toffoli 门分解。总共需要使用多少个门来构建它?
-
将 Toffoli 门和 Hadamard 门应用于状态向量模拟器,并将结果与 Sampler 原语的结果进行比较。你看到了哪些差异,为什么?
-
如果你想以相反的方向对三个量子比特进行排序,你会使用哪些门,以及它们的顺序是什么?
加入我们的 Discord
加入我们社区的 Discord 空间,与作者和其他读者进行讨论:
packt.link/3FyN1

第七章:使用 Qiskit 进行编程
Qiskit 拥有多种库,这些库将量子系统的核心硬件与创建量子电路所需的工具连接起来,这些工具通过代码堆栈向上扩展。因此,它是创建量子电路以及从电路到硬件设备生成和调度脉冲的基础。其他功能,如优化器和转换器,用于确保电路优化以减少退相干并提高性能。在本章中,我们将探讨 Qiskit 中所有关键功能,以帮助您创建自己的电路、优化器和脉冲时序。
本章将涵盖以下主题:
-
理解量子电路
-
在硬件上生成脉冲时序
-
理解作业组件
Qiskit 具有许多功能和增强功能,要描述它们需要整本书。为了涵盖尽可能多的功能,我们将创建一个量子电路,并带您了解各种功能。阅读本章后,您将能够理解如何从基本电路到自定义电路,包括用户定义的标签以帮助您理解电路。您还将了解作业组件,该组件用于在量子系统上运行您的电路。
我们甚至将深入硬件以在量子比特上调度脉冲操作,以便更好地理解电路是如何从数字信号转换为模拟信号以在量子比特上执行操作,然后从量子比特读取信息并将信号从模拟转换为数字。
听起来很激动人心?太好了!让我们开始吧!
技术要求
在本章中,我们假设您熟悉前面章节中描述的量子电路基础知识,例如创建和执行量子电路、可视化电路图以及了解量子比特逻辑门。
本书所使用的源代码如下:github.com/PacktPublishing/Learning-Quantum-Computing-with-Python-and-IBM-Quantum-Second-Edition。
定制和优化量子电路
在前面的章节中,您已经接触了一些量子电路操作,以了解一些基本量子组件。这些基本操作包括创建量子电路、将量子门应用于电路以及在模拟器上执行电路。
现在,我们将更深入地研究量子电路,以更好地了解我们可用的属性和功能,不仅要在实际设备上执行这些电路,而且要尽可能优化地执行。在本节中,我们将学习如何提取电路属性,例如电路深度、宽度和大小,以及获取实际操作器的数量。让我们首先回顾创建量子电路的各种形式。
量子电路的组件
Qiskit 提供了多种创建量子电路的方法,每种方法都取决于你在电路中需要多少信息。到目前为止,我们一直在使用单个构造函数创建电路,该构造函数会自动创建所需的电路寄存器。在这种形式中,参数表示量子寄存器和经典寄存器的量子比特和比特数量,分别:
# Load helper file
%run helper_file_1.0.ipynb
qc = QuantumCircuit(2,2)
在本节中,我们将描述创建电路的其他方法,并讨论使用某种形式或另一种形式的优点。
构建量子电路类(QuantumCircuit)的另一种方法是独立于量子电路构造函数创建量子寄存器和经典寄存器。在这里,我们将首先创建量子寄存器和经典寄存器,每个寄存器分别包含两个量子比特和两个比特,然后绘制电路。构造函数允许我们自定义寄存器的标签,这在之前的形式中是无法做到的:
# Import registers
from qiskit import QuantumRegister, ClassicalRegister
qr = QuantumRegister(2, 'my_QR')
cr = ClassicalRegister(2, 'my_CR')
qc = QuantumCircuit(qr,cr)
qc.draw(output='mpl')
从前面的代码中,注意第二个参数:即寄存器构造函数的name属性:允许我们为每个寄存器添加标签,如下面的截图所示:

图 7.1:自定义量子寄存器和经典寄存器标签
自定义寄存器的标签简化了电路的阅读,尤其是在电路变得复杂时,多个寄存器执行不同的过程。你可能希望有一个固定数量的量子比特的寄存器,另一个动态寄存器,其中定义量子比特的数量将基于某些预处理步骤。你将在本章后面创建复合体时看到这一点的重要性。
当然,如果需要,你也可以在一行中将创建寄存器和电路构造函数合并:
qc = QuantumCircuit(QuantumRegister(2, 'my_QR'), ClassicalRegister(2, 'my_CR'))
假设现在你有两个量子电路,并且你想将它们连接在一起。以下示例将说明如何将两个电路连接成一个,而无需基于两个现有的量子电路显式地重新创建一个:
-
在以下代码中,我们将创建第一个电路,并在量子寄存器和经典寄存器上包含标签,以便我们可以监控它们实际上是否已合并:
#Create the quantum and classical registers, each with #labels qr1 = QuantumRegister(2, name='qr1') cr1 = ClassicalRegister(2, name='cr1') #Create the quantum circuit using the registers qc1 = QuantumCircuit(qr1, cr1) #Draw the circuit qc1.draw(output='mpl')
以下截图显示了运行上一段代码后应显示的内容:

图 7.2:我们将加入的两个量子电路中的第一个
-
接下来,我们将创建第二个电路,它与第一个非常相似,只是我们将更新标签以识别它是第二个:
#Create two Quantum and Classical registers qr2 = QuantumRegister(2, name='qr2') cr2 = ClassicalRegister(2, name='cr2') #Create a second circuit using the registers created #above qc2 = QuantumCircuit(qr2, cr2) #Draw the second quantum circuit qc2.draw(output='mpl')
代码的结果应该不会令人惊讶:它与第一个相同,只是标签按预期更新:

图 7.3:我们将加入的两个量子电路中的第二个
-
现在,让我们通过将一个电路与另一个电路结合来完成,也就是说,我们将电路宽度从 2 扩展到 4 个量子比特。为此,我们将使用
add_register函数,该函数将量子寄存器和经典寄存器组合成一个单独的电路。在这里,我们将电路qc2和qc1组合成一个新的电路,标题为qc_combined,然后绘制结果:#Concatenate the two previous circuits to create a new #circuit #Create an empty quantum circuit qc_combined = QuantumCircuit() #Add the two previous quantum and classical #registers to the empty quantum circuit qc_combined.add_register(qr1, qr2, cr1, cr2) #Draw the concatenated circuit qc_combined.draw(output='mpl')
如以下截图所示,结果现在是两个先前量子电路通过组合寄存器合并成一个新的电路:

图 7.4:两个量子电路的连接
我们最初创建了两个单独的量子电路,每个电路有两个量子寄存器和两个经典寄存器。然后我们将它们连接起来,创建了一个包含四个量子寄存器和四个经典寄存器的量子电路。量子电路的顺序是基于它们连接的顺序。作为额外的练习,重复之前的连接代码,并切换顺序以确认或创建更多的量子电路并将它们组合在一起。
我想分享的最后一个电路创建对象是随机电路生成器,正如其名所示,它将为你生成一个随机电路。拥有创建随机电路的能力可以帮助你根据一组参数(如电路宽度和深度)创建测试电路或示例。如下面的代码块所示,random_circuit 对象需要两个参数。它们是你想要随机电路包含的量子比特数量和电路的深度,分别:其中深度表示每个量子比特随机添加的标准门数量,这些门来自 Qiskit 电路扩展列表,如 API 文档中所述。你也可以指示你是否想要电路包含测量操作符:
#Import the random_circuit class
from qiskit.circuit.random import random_circuit
#Construct the random circuit with the number of qubits = 3
#with a depth = 2, and include the measurement operator for
#each qubit
qc = random_circuit(3, 2, measure=True)
#Draw the circuit
qc.draw(output='mpl')
随机电路的结果当然会因每次执行而变化,这是应该的。不会变化的是参数选项,尤其是量子比特的数量和深度计数。在这种情况下,你的结果应该包含三个量子比特和两个操作符的深度。以下随机电路是运行前面代码的结果。请注意,测量操作符不包括在深度计数中:

图 7.5:使用 3 个量子比特和深度 2 生成的随机电路
现在你已经熟悉了生成量子电路的各种方法,我们将继续并看看我们可以从创建的电路中提取哪些属性。这些属性可以用来分析生成的电路,并确保通过利用 Qiskit 中可用的某些优化功能来优化它。
获取电路属性和分析
一旦开始构建电路,构建电路可能会变得非常复杂,尤其是如果你创建门和组合它们以形成更大的门。如果你需要分析结果,你将想要在过程中获取有关你的电路的一些信息。
对于我们来说,好事是 Qiskit 已经为我们处理了一些这方面的工作,通过使我们能够访问许多这些属性。让我们从一些基本属性开始。假设我们想知道我们的电路中有多少量子比特。正如我们在上一节中学到的,我们知道我们可以将两个或多个电路连接在一起。当我们添加更多的电路时,确定我们连接的电路将具有多少量子比特和门变得困难或繁琐。正是在这里,宽度、深度和算子计数函数变得非常有用。
在下面的代码中,我们将创建两个双量子比特随机电路,每个电路具有不同的门计数。然后我们将一个电路附加到另一个电路上,并使用我们的电路属性函数来帮助我们获取总宽度、深度和算子计数。所有附加的电路都需要使用此方法具有相同数量的量子比特:
#Import the random circuit class
from qiskit.circuit.random import random_circuit
#Create two random circuits, each with 2 qubit registers and
#random gate operator counts.
qc1 = random_circuit(2,2)
qc2 = random_circuit(2,4)
#Concatenate the two random circuits into one
qc = qc1.compose(qc2, [0,1])
#Draw the circuit
qc.draw(output='mpl')
结果应该是一个具有随机门算子集的双量子比特电路,总深度为 6。我们知道这一点,因为我们创建了它们,并且可以从random_circuit构造函数中看到这些值:

图 7.6:深度为 6 的随机生成的双量子比特电路
现在,让我们使用我们的电路属性函数来获取电路的宽度、深度、大小和算子计数。为了简化这一点,我们将创建一个辅助函数,该函数将打印出我们将作为参数传递的量子电路的电路属性:
#Define function to print circuit properties:
def print_circuit_props(qc):
width = qc.width()
depth = qc.depth()
num_operators = qc.count_ops()
circuit_size = qc.size()
print('Width = ', width)
print('Depth = ', depth)
print('Circuit size = ', circuit_size)
print('Number of operators = ', num_operators)
现在,我们可以通过我们的辅助函数运行我们的电路,该函数将打印出我们需要的所有属性:
#Pass our quantum circuit to print out the circuit properties
print_circuit_props(qc)
我们的结果应该对于Width和Depth具有相同的值。然而,由于我们正在使用随机电路,我们的电路大小和算子数量将不同,因为它们基于随机选择的门。然而,通过观察电路,你会看到size()和count_ops()的结果值是相同的。这两者之间的区别在于,电路大小返回电路中的总门数,而算子计数列出每种门类型的名称以及电路中每种门类型的总数:
Width = 2
Depth = 6
Circuit size = 7
Number of operators = OrderedDict([('cu3', 2), ('z', 1), ('tdg', 1), ('crz', 1), ('cz', 1), ('ch', 1)])
现在,让我们尝试添加一些经典寄存器、测量和障碍,看看我们会得到什么。我们可以使用measure_all()快捷方式来包含测量算子,这将附加一个障碍、每个量子比特的测量以及与我们的电路量子寄存器中量子比特数量相匹配的经典寄存器:
#Use measure_all() to automatically add the barrier,
#measurement, and classical register to our existing circuit.
qc.measure_all()
#Draw the circuit
qc.draw(output='mpl')
现在的结果包括了测量和读取我们的量子比特所需的所有经典组件。这些包括标记为measure的两个位经典寄存器、一个将量子门与测量操作符分开的屏障以及测量操作符,如下面的屏幕截图所示:

图 7.7:添加经典组件的随机电路
现在,让我们打印我们的电路属性函数,以查看更新的计数:
#Print out the circuit properties
print_circuit_props(qc)
结果显示了我们一般会预期的内容。由于添加了两个位的经典寄存器,Width计数增加了 2。由于添加了屏障,Depth计数增加了 1。请注意,测量操作符不包括在大小或操作符计数中,如下所示:
Width = 4
Depth = 7
Circuit size = 9
Number of operators = OrderedDict([('cu3', 2), ('measure', 2), ('z', 1), ('tdg', 1), ('crz', 1), ('cz', 1), ('ch', 1), ('barrier', 1)])
在进入下一节之前,让我们看看我们电路属性函数的一个有趣的注意事项。大多数门是由特定于所使用量子计算机的基门创建的。对于大多数量子系统,有一组基门用于创建其他门。
然而,一些门,如Toffoli和Swap门,不仅需要多个量子比特,而且由多个基门组成。让我们以 Toffoli 门为例:
-
我们将创建一个包含 3 个量子比特的量子电路,并向其中添加一个 Toffoli 门,如图所示:
qc = QuantumCircuit(3) qc.ccx(0,1,2) qc.draw(output='mpl')
这里,我们看到了预期的 Toffoli 门,0 和 1 源量子比特纠缠,量子比特 2 为目标:

图 7.8:量子电路上的 Toffoli 门
-
我们打印出带有 Toffoli 门的量子电路的电路属性:
#Print out the circuit properties print_circuit_props(qc)
如我们所见,结果并不令人惊讶,因为这些值也不令人惊讶:一个宽度为3、深度为1的三量子比特门:
Width = 3
Depth = 1
Circuit size = 1
Number of operators = OrderedDict([('ccx', 1)])
-
现在,让我们打印我们的电路属性,但这次,让我们分解我们的量子电路以查看结果。如您所回忆的,当我们对量子电路调用
decompose()函数时,我们请求电路分解为其基门,这些基门用于创建我们电路中的门。在这种情况下,我们指的是用于创建 Toffoli 门的基门:#Print out the circuit properties print_circuit_props(qc.decompose())
注意区别吗? 确实令人惊讶!通过观察结果,我们看到 Toffoli 门需要 15 个操作符,这些操作符由各种门组成,例如 T、![img/B18420_07_001.png]、H 和 CNOT:
Width = 3
Depth = 11
Circuit size = 15
Number of operators = OrderedDict([('cx', 6), ('t', 4), ('tdg', 3), ('h', 2)])
我之所以想提到这一点,是为了让你意识到所使用的某些门不是基门,而是用于生成所需门功能的基门的组合。在分析你的电路与量子比特噪声或退相干相关时,了解这一点是很有用的。
尝试同样的练习,但这次尝试创建一个具有 Swap 门的二量子比特电路,看看你得到什么结果。
现在你已经熟悉了创建量子电路的各种形式,让我们看看我们如何以模块化的方式重复使用这些电路,使其易于组合和理解。
定制和参数化电路库
有时候,你可能需要在多个场合重复使用一个电路。为了简化这个过程,你可以创建操作符的复合体并在整个电路中重复使用它们。这不仅简化了从模块创建电路的过程,而且使其他人很容易理解这些复合体中电路的功能。Qiskit 根据指令集或量子电路创建这些复合体。
在以下步骤中,我们将创建一个由多个量子位和门组成的复合门:
-
首先,我们创建一个两量子位的量子电路,给它起个名字,并将其转换为通用量子指令:
#Create a custom two-qubit composite gate #Create the quantum register qr = QuantumRegister(2, name='qr_c') #Generate quantum circuit which will make up the #composite gate comp_qc = QuantumCircuit(qr, name='My-composite') #Add any gates you wish to your composite gate comp_qc.h(0) comp_qc.cx(0, 1) #Create the composite instructions by converting #the QuantumCircuit to a list of Instructions composite_inst = comp_qc.to_instruction() #Draw the circuit which will represent the composite gate comp_qc.draw(output='mpl')
上述代码将创建以下两个量子位的电路,我们将将其用作复合门:

图 7.9:将表示复合门的量子电路
-
现在,让我们创建一个量子电路,将复合门附加到我们创建的电路上:
#Create your 2-qubit circuit to generate your composite gate qr2 = QuantumRegister(3, 'qr') #Create a quantum circuit using the quantum register qc = QuantumCircuit(qr2) #Add any arbitrary gates that would represent the function #of the composite gate qc.h(0) qc.cx(0,1) qc.cx(0,2) #Draw the composite circuit qc.draw(output='mpl')
上述代码将创建电路,我们在包含复合门之前预先填充了一些门:

图 7.10:我们将附加到复合门上的量子电路
-
由于我们的复合门由两个量子位组成,我们需要指明将我们的二量子位复合门附加到哪三个量子位上。对于这个例子,我们将它附加到前两个量子位上:
#Append your composite gate to the specified qubits. qc.append(composite_inst, [qr2[0], qr2[1]]) #Draw the complete circuit qc.draw(output='mpl')
从结果中我们可以看到,我们的复合门已成功附加到第一和第二个量子位上。它还包括复合门的名字,这使得任何人,包括你自己,都能轻松阅读电路并理解复合门在电路中的功能:

图 7.11:具有预定义电路复合门表示的量子电路
与将两个量子电路简单连接起来相比,这使得阅读你的电路变得更加容易。
当然,如果你有一个可以立即运行的电路,这是理想的。然而,有时你可能希望控制你生成的复合门中某些门的旋转量。这就是复合门参数化的作用所在。我们现在将创建另一个复合门,但这一次它将包括向你的复合门添加参数的能力,使其更加动态。这通常被称为参数化量子电路(PQC)。
-
为了参数化一个门,我们需要创建一个
Parameter类并将其设置为旋转门;在这个例子中,我们将参数应用到 R[Z]门上:#Import the Parameter object from qiskit.circuit import Parameter #Construct the Parameter set to Theta param_theta = Parameter('Ө') #Create a two-qubit quantum circuit and add some gates qc = QuantumCircuit(2) qc.h(0) qc.cx(0, 1) #Include a rotation gate which we wish to apply #the Parameter value qc.rz(param_theta, 0) qc.rz(param_theta, 1) #Draw the circuit qc.draw(output='mpl')
注意,参数值定义为
,但不是设置为显式值。它只是预留了Parameter值,以便稍后包含一个旋转值
:

图 7.12:将 R[z]门的参数设置为θ
-
让我们将我们门的
Parameter值绑定到
并绘制电路:import numpy as np #Bind the parameters with a value, in this case 2π qc = qc.assign_parameters(parameters={param_theta: 2*np.pi}) #Draw the circuit with the set parameter values qc.draw(output='mpl')
注意,我们的旋转门其 theta 值被设置为
,正如预期的那样:

图 7.13:旋转门 R[z]现在将参数值θ设置为 2π
我们的电路现在已准备好运行,并绑定了参数值。通过这个特性,我们可以在循环中迭代它,并在需要时绑定多个值,这样我们就可以迭代所有这些值,而无需手动更新绑定的值。这极大地优化了我们运行和分析电路每个迭代的运行结果的能力。
在本节中,我们了解了使用 Qiskit 提供的类和函数创建量子电路的各种形式和方法,以帮助我们分析电路属性和结构。我们还学习了如何重用创建的电路。
在下一节中,我们将更深入地探讨操纵量子比特,但这次不是从基本门开始,而是直接使用硬件本身来操纵量子比特!
在硬件上生成脉冲计划
到目前为止,您已经学习了如何创建量子电路,添加操纵电路中量子比特的门,并执行电路。在本节中,我们将进一步探讨量子电路是如何从数字指令转换为脉冲指令的,这些脉冲指令根据量子电路的指令,通过微波脉冲物理地操纵量子比特。微波脉冲是操纵量子设备上量子比特的元素。一个信号被生成并调整到每个量子比特的特定频率,以确保信号只影响脉冲调整到的量子比特。
本节的大部分内容将假设您对信号处理有一定的了解,特别是关于类似 transmon 的超导量子比特和微波脉冲。为了参考,这是一篇非常棒的论文,详细介绍了相关内容,arxiv.org/pdf/1904.06560.pdf,其中第4.C和4.D部分概述了如何使用微波信号来操纵量子比特。
到目前为止,您可能想知道为什么您应该有脉冲访问来操作量子比特。首先,了解超导量子比特是通过微波脉冲来操作的这一点很重要。这些脉冲使用量子比特的载波频率将信号发送到每个量子比特,以确保脉冲操作只会影响调谐到该特定频率的量子比特。这样,如果您是内核开发者,例如,配置脉冲的能力让您能够自行微调门操作,而不是默认的脉冲形状。这些微调不仅可以应用于单门操作,还可以应用于像 CNOT 门这样的双量子比特操作。
我们将首先说明硬件组件是如何连接到各种脉冲通道的。IBM Quantum 以与其他大多数云上可用的量子系统不同的方式为您提供对机器的访问。Qiskit 包括一个Pulse库,允许您控制发送到控制设备的脉冲。根据OpenPulse文档(arxiv.org/abs/1809.03452),它专门提供生成用于控制量子比特的脉冲信号的功能。
为了理解脉冲功能的工作原理,我们将首先描述您将使用的四个主要组件:
-
指令
-
脉冲库
-
通道
-
时间表
在接下来的章节中,我们将学习关于前面组件的内容。
在我们进入下一节之前,我们将使用以下代码,该代码将导入我们直接创建、安排和触发量子设备上的脉冲所需的所有内容:
#Import pulse classes
from qiskit.pulse import Waveform, DriveChannel, Play, Schedule
现在我们已经导入了所需的文件,我们将进入下一节,关于指令。
了解指令
脉冲程序,或者,如 Qiskit API 文档所述,时间表,是一组用于描述量子系统电子组件控制的指令。qiskit.pulse库中包含各种指令对象,它们具有调制脉冲信号频率和相位的特性。
pulse本质上为您提供指定量子电路中每个操作动态的能力,这样您就可以以尽可能减少噪声的方式对每个操作进行微调。pulse具有各种功能,让您能够访问发送和接收每个量子比特信息的通道,并包括您可以用作基础并相应修改的脉冲库。
您还可以延迟指令的触发,类似于大多数编程语言中的sleep()函数。最后,它还赋予您通过播放和捕获微波脉冲信号来触发操作和启用获取通道的能力。
理解以下每个指令都取决于理解为什么你想使用这些函数。例如,设置频率很重要,因为每个量子比特都调谐到特定的频率,因此任何脉冲操作都应该在指定量子比特的载波频率上发送;否则,操作将不会工作,或者更糟,它可能会对您不想更新的量子比特进行操作。现在,让我们描述每个指令及其参数:
-
SetFrequency(frequency, channel, name),其中frequency以 Hz 为单位,channel表示频率将应用到的通道,name是你可以为指令设置的名称。SetFrequency指令的默认持续时间为0。这非常简单地设置了通道的频率,以便应用于通道的脉冲相应地调谐。如果你在创建特定量子比特的脉冲时没有指定频率,将使用驱动通道上量子比特的默认频率。 -
ShiftPhase(phase, channel, name),其中phase是旋转角度(以弧度为单位),channel表示频率将应用到的通道,name参数是你可以为指令设置的名称。此指令通过增加提供的弧度数来增加旋转角度,从而移动脉冲的相位。 -
Delay(duration, channel, name),其中duration是延迟的长度(在文档中,这也被称为时间步长或dt),channel表示将应用延迟的通道,name表示你可以为指令设置的名称。Delay指令通常用于将脉冲与其他脉冲指令对齐。例如,如果您希望发送两个脉冲并在脉冲之间包含一个时间间隔,您可以通过添加一个具有所需时间间隔的Delay指令来指定时间间隔。 -
Play(pulse, channel, name),其中pulse是你希望应用的脉冲波形,channel表示脉冲将应用到的通道,name是你可以为指令设置的名称。Play指令将脉冲输出应用于指定的通道,其中脉冲输出之前已使用SetFrequency和SetPhase指令进行调制。 -
Acquire(duration, channel, mem_slot, reg_slot, kernel, discriminator, name),其中duration是获取数据信息的时间步数(dt),channel表示从哪个通道获取数据,mem_slot是存储每个返回结果的经典内存槽,reg_slot是用于存储分类和读出结果的寄存器槽。kernel参数用于对每个槽的原始数据进行积分,discriminator用于将核化的 IQ 数据分类为0或1结果,name表示你可以为指令设置的名称。
每条指令都包含一个将应用于指定通道的操作符。操作符包括脉冲调制器、延迟和从通道读取。在我们讨论通道之前,让我们使用 Qiskit Pulse 库创建一些脉冲。
理解脉冲和 Pulse 库
每个微波脉冲都是由一个任意波形发生器(AWG)生成的,它指定了脉冲信号的频率和相位。频率和相位分别由我们之前学过的SetFrequency和ShiftPhase指令设置。
重要提示
Qiskit Pulse 提供了一个很好的波形库,可以简化创建我们需要在量子比特上操作的脉冲。以下是在编写本章时可用波形的类型:Constant、Drag、Discrete、Gaussian、GaussianSquare和Waveform。这些波形中的每一个都有其特定的功能,例如,Drag用于减少量子比特到
状态的泄漏,并使其保持在
和
状态。
Pulse 库中提供的每个波形都有其特定的功能。以下我们将介绍其中的一些,但我鼓励你阅读 Qiskit 文档docs.quantum.ibm.com/api/qiskit/pulse,其中包含了每个波形的详细描述。
Waveform允许你通过提供一个复数值样本数组作为参数来自定义脉冲。这些样本每个都有一个预定义的时间步长,dt,这是每个样本播放的时间周期,它根据指定的后端而变化。以下代码是一个简单正弦波形的 128 个样本脉冲的示例:
#Import numpy and generate the sine sample values
import numpy as np
x = np.linspace(0,2*np.pi,64)
data = np.sin(x)
#Generate our Waveform
waveform = Waveform(data, name="sine_64_pulse")
#Draw the generated waveform
waveform.draw()
以下截图是我们创建的正弦波形样本脉冲的结果:

图 7.14:正弦波形的样本脉冲
现在我们尝试从 Pulse 库中生成一个波形。
Pulse 库提供了一系列不同的波形,例如Gaussian、GaussianSquare、Constant和Drag(仅举几个例子)。每个都有其独特的形状,我们可以利用这些形状来微调我们想要的任何脉冲。
让我们创建一个GaussianSquare脉冲,它是一种两端带有高斯边缘的方形脉冲,而不是截断边缘的方形脉冲:
#Import the Gaussian Square pulse from Pulse Library
from qiskit.pulse.library import GaussianSquare
#Create a Gaussian Square pulse
#Args: duration, amp, sigma, width, name
gaussian_square_pulse = GaussianSquare(128, 1, 2, 112, name="gaussian square")
gaussian_square_pulse.draw()
前面的代码将产生以下脉冲,其中脉冲持续时间(dt)为128,放大最大值为1,sigma 设置为2,脉冲峰值的宽度为112(dt):

图 7.15:高斯平方脉冲
如您所见,我们可用的参数允许以多种方式调整波形。在高斯平方波形样本中,我们能够调整幅度、宽度和其 sigma,这因此为我们提供了更多控制脉冲创建的能力,这可能会减少噪声或任何其他可能来自基于量子电路指令创建的标准脉冲的影响。
现在我们能够创建脉冲,让我们了解一下我们将通过这些通道传输脉冲的通道。
利用通道传输和接收指令
Qiskit Pulse 中有两种类型的通道:
-
第一种类型是脉冲通道,它将生成的脉冲传输到量子设备。这些包括驱动通道、控制通道和测量通道。
-
另一种类型的通道是采集通道。目前,这种类型仅包括采集通道,这是接收来自量子设备脉冲的通道。
所有通道只有一个参数,即索引,用于分配通道。以下列表描述了所有通道:
-
驱动通道是用于将脉冲信号传输到量子比特以执行门操作的通道。当显示时,它具有前缀D。
-
控制通道通常用于多量子比特门操作,如
Control-Not、Control-Phase等。它们通常通过驱动通道提供对量子比特的辅助控制。当显示时,它具有前缀U。 -
测量通道向量子比特传输测量刺激脉冲,以从量子比特进行读出。当显示时,它具有前缀M。
-
采集通道是唯一用于从设备接收信息的通道。它用于收集量子设备的数据。当显示时,它具有前缀A。
到目前为止,我们已经了解到脉冲程序是由用于在量子设备上执行门操作的波形脉冲组成的指令。我们还介绍了可用于在量子设备之间传输和接收信息的不同通道。有了这些信息,我们现在可以看看如何安排这些指令在真实设备上执行。
生成和执行调度
脉冲调度是一组通过指定通道发送到量子设备上执行的指令。Schedule类可以由指令或其他调度的组合组成。这意味着您可以使用我们之前学到的其中一个指令创建一个调度,或者将调度组合或附加到现有调度中。我们将在本节中完成所有这些操作。
我们将利用本章迄今为止学到的知识来构建一个时间表。首先,我们将构建一个时间表,并将脉冲库中的一个脉冲插入其中,该脉冲将在time = 0时触发。然后,我们将创建另一个时间表,并将脉冲库中的不同脉冲插入其中。第二个时间表将被附加到第一个时间表上,然后进行平移,以便在第一个脉冲完成后在某个时间触发。然后,我们将在量子设备上执行时间表并获取其结果:
-
让我们继续使用我们迄今为止一直在使用的笔记本来创建我们的第一个时间表,并将其命名为
schedule_1。我们还将使用Play指令插入我们之前生成的高斯方波脉冲,并将时间表分配给驱动通道0:#Create the first schedule with our Gaussian Square pulse schedule_1 = Schedule(name='Schedule 1') schedule_1 = schedule_1.insert(0, Play(gaussian_square_pulse, DriveChannel(0))) #Draw the schedule schedule_1.draw()
我们看到的结果是我们的高斯方波脉冲被添加到从time = 0开始的时间表中,如下所示:

图 7.16:时间表 1:高斯方波脉冲
-
现在,让我们继续创建第二个时间表,
schedule_2,我们将使用我们之前生成的样本脉冲:#Create a second schedule with our sample pulse schedule_2 = Schedule(name='Schedule 2') schedule_2 = schedule_2.insert(0, Play(waveform, DriveChannel(0))) #Draw the schedule schedule_2.draw()
这导致以下时间表:注意我们的样本脉冲的持续时间为 64,而高斯方波脉冲的持续时间为 128:

图 7.17:时间表 2:样本(正弦波形)脉冲
-
接下来,我们将创建第三个时间表,
schedule_3,我们将通过插入schedule_1和schedule_2,并在两者之间留出 5 个时间步长(dt)的间隔来构建它:#Let's create a third schedule, where we add the first #schedule and second schedules and shift the second # to the right by a time of 5 after the first schedule_3 = schedule_1.insert(schedule_1.duration+5, schedule_2) schedule_3.draw()
结果是schedule_1从time = 0开始,然后我们在第一个时间表之后 5 个时间单位插入schedule_2。注意使用duration变量确保脉冲不会与第一个重叠。因此,时间表 3 的总时间为两个脉冲加上两者之间的 5 个时间单位,总计 197,如下图所示:

图 7.18:时间表 3,在两者之间有 5 个时间单位差异地组合时间表 1 和时间表 2
-
当然,还有其他方法可以组合脉冲。如果您想在两者之间没有间隔的情况下组合两个时间表,那么您只需简单地使用
append函数将它们组合即可:#We could have also combined the two using the append operator #The two schedules are appended immediately after one #another schedule_3_append = schedule_1.append(schedule_2) schedule_3_append.draw()
上述代码产生以下输出。注意,总时间单位等于两个脉冲的总时间单位,两个脉冲之间没有额外的 5 个时间单位:

图 7.19:时间表 3,在两者之间没有时间间隔地附加两个时间表
到目前为止,我们能够生成脉冲,将其应用于指令,并安排在指定的通道上运行以操纵量子位。生成一组脉冲可以让你了解我们如何将两个不同的门操作顺序地安排在指定的通道上的单个量子位。
在下一节中,我们将看到我们如何在提交量子电路后找到其状态。
理解作业组件
我们将要讨论的最后一个组件是Job组件。Job组件基本上是在后端上执行过的电路的实例,并包含有关执行电路(如结果、后端、持续时间等)的信息。这意味着一旦你将电路发送到后端以执行,后端将生成Job实例并附加有关作业的信息:如状态、结果、作业标识符等信息。以下是一个可用的Job函数列表:
-
backend()提供了作业正在运行的后端。 -
status()提供作业的状态。 -
result()在后台执行完成后提供作业结果。 -
cancel()提供了取消作业的能力。 -
job_id()提供了字母数字作业标识符。
为了简单起见,我们将重用我们之前创建的电路transpiled_qc,这样我们就可以查看Job对象及其函数。让我们首先导入job_monitor类并启动 Qiskit 作业监视器,然后在一个后端上运行电路并运行作业监视器以查看作业的输出状态:
#Run the simple quantum circuit on local Sampler
from qiskit.primitives import Sampler
sampler = Sampler()
#Let's create a simple circuit
qc = QuantumCircuit(1,1)
qc.h(0)
qc.measure([0],[0])
#Let's run a circuit with the Sampler primitive
job = sampler.run(qc, shots=1024)
print(job.status())
上述代码将执行一个电路,以下将显示Job对象的详细信息;例如,它将指示作业是否在队列中、正在运行或成功完成。在这种情况下,状态的结果表明作业已成功运行:
Job Status: JobStatus.DONE
还可以从Job对象中提取其他信息。作业 ID是正在运行的作业的唯一标识符。这个 ID 在多种情况下非常有用,例如跟踪结果,如果你在后台运行的作业遇到问题,你可以向支持团队提及作业 ID,这将帮助团队找到该作业。可以使用status()和result()函数分别显式调用状态和结果。result对象提供了在后台运行作业的所有信息。让我们运行这些函数来查看我们刚刚运行的作业的结果:
#From the previous output of executed jobs, obtain its job id
#and print out information about the Job.
print('Print Job ID: ', job.job_id())
print('Print Job Status: ', job.status())
print('Print Job Result: ', job.result())
代码片段中每个函数的结果如下:
Print Job ID: dc25c6bd-2ce6-41d5-9caf-f1326f70b90d
Print Job Status: JobStatus.DONE
Print Job Result: SamplerResult(quasi_dists=[{0: 0.5107421875, 1: 0.4892578125}], metadata=[{'shots': 1024}])
在这里,我们可以提取特定的结果信息,我们可以使用这些信息来可视化作业的状态。例如,以下代码将从结果中提取计数并显示在直方图中:
job_result = job.result()
quasi_dists = job_result.quasi_dists
# Get result counts
counts = quasi_dists[0].binary_probabilities()
#Print and plot results
print(counts)
plot_distribution(counts)
结果将生成以下直方图,该直方图涉及每个射击的结果,作为一个总概率(准概率)如下:
{'0': 547, '1': 477}
直方图将显示如下:

图 7.20:概率结果作为直方图
job 对象帮助我们获取有关我们已发送到量子系统上运行的电路的详细信息。这些信息可用于将结果返回到我们的应用程序,并相应地可视化结果。它还提供了状态和其他信息,我们可以使用这些信息来跟踪作业,并与他人分享有关结果的其他详细信息,例如支持团队,这样他们可以轻松地在后端识别作业。
摘要
在本章中,我们仅介绍了 Qiskit 库中包含的许多功能之一。我们回顾了创建量子电路,以及如何在模拟器和真实量子设备上执行它们。我们还回顾了如何连接电路,以便您可以将它们组合并创建复合电路,这包括绑定参数以允许调整各种门。
我们介绍了如何使用 Qiskit 的 Pulse 库将电路转换为脉冲,并创建了计划,这些计划是发送脉冲信息通过各种通道到底层硬件的程序,并查看如何获取后端上运行的作业的详细信息。
在下一章中,我们将通过回顾通过传递管理器和优化器提供的功能来介绍优化您的量子电路的技术。我们还将学习各种可视化您的量子电路和监控您在量子后端系统上执行作业的方法。
问题
-
构建一个宽度为 4 且深度为 9 的随机量子电路。
-
创建另一个与你在 问题 1 中创建的电路宽度相同的随机量子电路,并将其连接起来,以便在你在前面创建的随机量子电路之前添加。
-
打印 问题 3 中连接的量子电路的电路属性,并指定操作符的总数,不包括任何测量操作符。
-
创建一个具有参数化 R[Y] 门的电路,该门将旋转一个角度为
。
加入我们的 Discord 社区
加入我们社区的 Discord 空间,与作者和其他读者进行讨论:
packt.link/3FyN1

第八章:优化和可视化量子电路
在上一章中,你学习了如何使用Qiskit进行编程,包括电路和脉冲时序。在本章中,我们将继续探讨电路的话题,特别是通过减少在重计算周期中经典和量子系统之间的开销,来优化和加速端到端过程的一些新特性。
幸运的是,Qiskit 提供了大量功能,使我们能够轻松地做到这一点。此外,Qiskit 还提供了一套类和功能,用于优化和增强电路的可视化。了解这些功能将有助于优化你的电路结果,并允许你以各种风格和表示方式渲染电路,例如有向无环图(DAG)。
我们将在本章中介绍以下主题:
-
使用预设 Passmanager 优化电路
-
可视化和增强电路图
在阅读本章之后,你将能够通过使用后端系统的各种视觉和程序表示以及可视化工具来优化你的电路。你还将对可帮助优化电路针对特定量子后端系统的转换的各种转换器特性有所了解。你将学习到预设 Passmanagers以及它们如何被利用来生成用于在转换器中执行电路转换的自定义 pass managers,这些转换器在不同的优化级别上运行。
技术要求
在本章中,我们假设你已经熟悉在模拟器和量子计算机上创建和执行量子电路。了解量子硬件,如量子比特和量子比特之间的连接性,也是推荐的。如果你在运行某些单元格时需要,你可能还需要在 Python 开发环境中安装Graphviz、LaTeX以及可能的其他依赖项。
这里是本书中使用的完整源代码:github.com/PacktPublishing/Learning-Quantum-Computing-with-Python-and-IBM-Quantum-Second-Edition.
使用预设 Passmanager 优化电路
我们需要确保当量子电路映射到量子系统(转换)时,是以最有效和最有效的方式进行。这包括将每个量子比特映射到量子系统上,以最小化由退相干或任何可能引入错误的其他噪声源影响的噪声。为了实现这一点,我们将学习预设 Passmanager、其用法以及它为我们提供的各种功能,以创建和执行最优电路。通过将电路的执行与量子设备的拓扑结构相匹配,我们减少了噪声及其对我们结果的影响。
在本节中,我们将学习如何将量子电路转换为与量子设备最佳匹配的形式。我们还将学习如何通过使用布局优化器来优化电路。然后我们将了解后端配置及其优化,以及传递管理器和传递。
编译量子电路
当你在量子设备上创建一个电路并运行它时,在你将电路发送到量子设备执行和结果返回之间,会发生很多事情。在我们讨论 第七章,使用 Qiskit 编程 中的 Qiskit 运行时的时候,我们查看了一些那些步骤。在本章中,我们将查看 Qiskit 运行时包含的各种功能,包括在 Qiskit 1.0 最新版本中引入的功能。首先,让我们看看在编译电路时发生的一些基本过程概述。
下面的流程图说明了电路编译的一般过程,以便它可以在指定的后端上运行并根据提供的设置进行优化:

图 8.1:从初始电路到编译器过程的电路,包含传递
我们将从介绍执行过程中显示在前面流程图中的基本步骤开始:
-
首先,编译器将电路中的门展开到指定后端系统的基门。
-
接下来,它将电路布局设置为设备。布局的一个例子是平凡布局,其中电路将电路上的量子比特映射到量子设备上的相同物理量子比特。例如,你的电路上的量子比特 0 映射到相同的量子比特索引,在这种情况下,物理设备上的量子比特 0。
-
接下来,它将电路映射到硬件拓扑。这是为了确保你的电路逻辑映射到量子硬件上。例如,假设你有一个连接控制量子比特到目标量子比特的 CNOT。然而,如果控制量子比特和目标量子比特之间没有直接连接,那么编译器将添加一些 SWAP 门以确保通过在控制量子比特和目标量子比特之间交换量子比特之间的信息来建立两个量子比特之间的连接。
-
最后,电路映射将被优化以确保对于给定的量子系统,电路深度被限制在最小值。步骤 2 和 3 可以映射到物理设备上的任意组合的量子比特;正如你可以想象的那样,找到正确的量子比特组合以减少电路深度是非常困难的。幸运的是,编译器为我们处理了这一点。
编译器由两个主要组件组成——即传递和传递管理器:
-
transpiler 的pass是转换电路从当前状态到新状态的组件。例如,前述步骤中提到的某些 pass 专注于布局选择、布线、优化、电路分析等。要查看所有可用 pass 的详尽列表,您可以运行以下命令:
# Load helper file %run helper_file_1.0.ipynb # Import the transpiler passes object from qiskit.transpiler import passes # List out all the passes available print(dir(passes))上述代码将列出所有可用的 pass。要详细了解 Pass Manager,我建议您查看
qiskit.transpiler.PassManager下的 API 文档。为确保您拥有最新的代码信息,请检查以下主 API 文档页面:docs.quantum.ibm.com/api/qiskit/transpiler。还可以在以下指南中找到更多示例:
docs.quantum.ibm.com/guides/transpile。 -
pass manager是您可以用来指定希望使用的 pass 的组件。pass manager 还允许 pass 之间相互通信。这在一种 pass 需要从其他 pass 提供或获取信息以确保最终电路符合任何配置或优化要求的情况下是理想的。pass manager 还有一些预设的 pass,它使电路优化简化。
生成预设的 passmanagers
为了简化这些 pass 和 passmanagers 的使用,Qiskit 提供了一些预构建的转换管道(在撰写本文时共有四个)。每个都可以在qiskit.transpiler.preset_passmanagers模块中找到。这些预构建的预设 pass manager 基于选定的优化级别。这四个目前编号为 0 到 3。使用generate_preset_pass_manager()函数生成这些 pass manager 也简化了。预设 pass manager 是transpile()函数使用的默认 pass manager,该函数构建一个与transpile()函数相对应的独立 passmanager。
在接下来的部分,我们将创建一个简单的电路,并使用预设的 passmanager 生成器对其进行转换,该生成器利用现有的预设 pass manager 管道。我们还将使用两个不同的优化级别运行相同的电路,以查看生成的电路如何区分这两种情况。最后,我们将创建一个自定义拓扑来转换电路,并将该结果与通过预设优化器创建的电路进行比较。这将说明选择未经优化的布局所带来的后果。
比较不同后端设备上的电路映射
为了在后端可视化信息,我们之前了解到可以通过调用配置和属性函数来输出所有信息。如果我们想从结果中提取特定数据,这可能会很有用;然而,阅读起来相当困难。这就是可视化工具大显身手的地方。让我们首先挑选两个后端系统进行比较。在这个例子中,我将选择ibm_brisbane和ibm_nazca,但你可以通过运行service.backends()来获取可用设备列表,以选择你拥有的任何量子设备:
# Get a list of all available backend devices
service.backends()
# From the list of backends, select two.
# Get the backend device: ibm_brisbane
backend_brisbane = service.get_backend('ibm_brisbane')
# Get the backend device: ibm_nazca
backend_nazca = service.get_backend('ibm_nazca')
现在我们已经选择了两个后端视图,让我们看看使用耦合图来表示门和它们如何通过耦合连接的物理连接的视觉表示。你可以通过调用以下代码片段中所示的plot_gate_map可视化函数来查看耦合图:
# Visualize the coupling directional map between the qubits
plot_gate_map(backend_brisbane, plot_directed=True)
这将打印出指定后端所有量子比特的完整连接图,如图下所示:

图 8.2:ibm_brisbane 连接图的裁剪视图
在图 8.2中,使用ibm_brisbane(我们将从现在开始将这个后端设备称为Brisbane以简化说明),我们可以看到它不仅是一个 127 量子比特的设备(裁剪以便更容易阅读),而且量子比特以重六边形的形式连接。你可以在这里找到有关此信息的详细信息:www.ibm.com/quantum/blog/heavy-hex-lattice。由于处理器的尺寸,一些量子比特的编号可能不清楚,因此你可能需要放大以近距离查看这些内容。此外,请注意,信号可以通过连接在各个量子比特之间双向传输。
现在,让我们可视化ibm_nazca的耦合方向图,之后我们将仅称为 Nazca。运行以下单元格:
# Visualize the coupling directional map between the qubits
plot_gate_map(backend_nazca, plot_directed=True)
现在,我们可以看到带有每个量子比特之间耦合方向映射的门图,如图所示:

图 8.3:启用耦合方向图的量子比特图视图(ibm_nazca)
你可能会注意到耦合图在这两个系统之间有一些相似之处。
由于量子比特的不同配置,当我们把量子电路上的量子比特映射到硬件设备上的量子比特时,我们创建的量子电路中量子比特的布局可能不是最优的。幸运的是,我们有编译功能,其中包括一个参数设置,允许我们设置电路布局的优化级别。下一节将介绍可用的各种传递,以及用于管理它们使用的传递管理器。
理解传递和传递管理器
传递通常用于转换电路,以便它们能够以最佳方式运行。有五种通用的传递类型可以转换电路:
-
布局选择确定量子位布局映射如何与所选的后端配置对齐。
-
路由根据所选的交换映射类型将 SWAP 门的位置映射到电路中,这可以通过提供耦合图或后端拓扑结构来设置,或者通过使用随机方法来实现,在这种方法中,系统不会假设电路的输入状态是基态/零态,以简化电路。
-
基变换提供各种方法来分解或展开门,直到后端的基础门或使用电路的分解规则。
-
优化通过移除冗余门(例如,将两个相同的可逆门,如 Hadamard 门,连续放置,从而将量子位恢复到原始状态)来优化电路本身。
-
电路分析提供电路信息,例如深度、宽度、操作数量以及有关电路的其他细节。
-
附加过程是那些提供某种其他形式的优化,例如各种检查映射,这些映射检查 CNOT 门的布局是否与耦合图中声明的方向一致,并在必要时重新排列方向。
我们在第七章,使用 Qiskit 编程中涵盖了大多数电路分析信息,以检测电路的大小、宽度和操作数量。让我们看看第一种过程类型,布局选择,看看我们如何利用提供的布局并了解不同优化级别之间的差异。
了解布局选择类型
让我们深入探讨这些类型之一,以获取更多细节。由于我们必须每次将量子位从电路映射到指定的硬件,并且我们希望在量子系统上执行我们的电路,我们将查看布局过程。当您想要将量子位映射到物理设备时,可以选择各种布局过程。这样做的原因可能包括希望您的量子位尽可能靠近以避免有多个 SWAP 门,或者如果您正在从事错误纠正或缓解工作,那么您可能希望将您的门精确映射到物理量子位上,以确保您的实验是一致的。让我们看看一些基本的布局选项:
-
TrivialLayout:这种布局将电路量子位按原量子电路中声明的顺序分配给物理设备的量子位。因此,量子位 0 直接映射到物理设备上的量子位 0。 -
DenseLayout:这种布局选择具有最多连接量子位子集的布局。如果需要许多纠缠量子位,这种布局将找到一个量子位彼此紧密连接的子集,以避免长距离和交换。 -
Sabre:这种布局利用一种量子位映射技术,它查看量子电路的可逆性,它将尝试解释全局电路信息以选择最佳初始布局。
对于上述描述的每个传递,转换器的优化级别都有一个不同的默认设置。优化级别由 4 个级别组成,范围从 0 到 3,指定是否完全不优化电路(级别 0)到完全优化(级别 3)。优化级别越高,生成的电路就越优化;然而,当然需要更多的时间来运行。
在本节中,您将学习如何应用和区分每个级别。
应用默认布局
让我们从默认布局开始。我们将在 transpiler 函数参数中设置各种优化级别,并在以下步骤中将它应用于两个后端设备,Brisbane和Nazca。需要注意的是,在撰写本文时,所有可用的系统在量子比特之间都有相同的连接性,并且具有非常相似的性质。然而,到本书出版时,将会有具有不同性质的系统,因此,尽管这里的结果可能看起来相同,但您看到的结果(如果您比较的系统不同)可能会有所不同:
-
我们将重用之前创建的相同的四量子比特量子电路。我将再次包括它,以便于您的使用:
# Quantum circuit with a single and multi-qubit gates qc = QuantumCircuit(4) qc.h(0) qc.cx(0,1) qc.cx(0,2) qc.cx(0,3) circuit_drawer(qc)
这将导致我们的基本电路,其中每个 4 个量子比特都通过 CX 门与第一个量子比特,q[0]纠缠。
我们将从Brisbane开始,将优化级别设置为0,这意味着我们不会在电路中使用任何优化技术。这将导致我们量子电路中的量子比特 [0,1,2,3] 映射到后端设备Brisbane [0,1,2,…,126]上的相同的量子比特索引值。
-
在以下代码中,我们将利用
qc电路,并使用它来测试各种优化级别。然后我们将打印出转换后的电路深度,并使用映射的量子比特作为叠加在后台设备上的覆盖来可视化布局。这将说明不同的方法是如何应用于后台设备的:注意,由于转换器的随机部分,您得到的结果可能会有所不同。因此,无论您得到什么结果,都是执行时的最佳结果。为了确保您得到相同的结果,您需要将
transpiler()函数的seed_transpiler参数设置为一个随机整数值。
# Generate a preset pass manager for Brisbane
pass_manager = generate_preset_pass_manager(backend=backend_brisbane, optimization_level = 0)
# Transpile the circuit with an optimization level = 0
qc_brisbane_0 = pass_manager.run(qc)
# Print out the depth of the circuit
print('Depth:', qc_brisbane_0.depth())
# Plot the resulting layout of the quantum circuit after # Layout
plot_circuit_layout(qc_brisbane_0, backend_brisbane)
结果如下,正如预期的那样,其中量子比特没有进行布局优化,并且量子电路到硬件设备的量子比特有直接的映射。请注意,深度为6464:

图 8.4:优化级别为 0 的 Brisbane 上的转换量子电路;直接量子比特映射,无变化
-
现在,让我们在
Brisbane上绘制转换后的电路:# Draw the transpiled circuit pertaining to Brisbane circuit_drawer(qc_brisbane_0, idle_wires=False, output='mpl')
这将使用Brisbane上可用的基门渲染转换后的电路;以下图中的电路已被截断以节省空间。

图 8.5:在 Brisbane 上的基本门编译电路
请注意,由于我们在调用circuit_drawer()函数时使用了idle_wires=False参数,因此未使用的量子比特不可见。如果我们移除此参数,你将看到所有量子比特,即使那些未使用的量子比特。此外,请注意,量子比特映射在电路图的左侧表示,其中电路映射指向物理设备的量子比特。在前面的图中,我们可以看到从电路中,q[0]映射到物理设备上的 0^(th)量子比特(如指示器 q[0] -> 0 所示)。
-
现在,让我们在
Nazca上以相同的优化级别设置为0运行相同的内容。我们应该看到相同的结果,即编译电路映射到与我们的量子电路相同的量子比特:# Generate a preset pass manager for Brisbane pass_manager = generate_preset_pass_manager(backend=backend_brisbane, optimization_level = 0) # Transpile the circuit with an optimization level = 0 qc_brisbane_0 = pass_manager.run(qc) print('Depth:', qc_nazca_0.depth()) plot_circuit_layout(qc_nazca_0, backend_nazca)
上述代码将产生以下深度信息:
Depth: 4040
如下所示,结果布局映射也显示出来:

图 8.6:在 Nazca 上的编译电路
现在,让我们看看Nazca量子设备的编译电路。
-
我们现在将使用以下代码绘制编译电路:
# Draw the transpiled circuit pertaining to Nazca circuit_drawer(qc_nazca_0, idle_wires=False, output='mpl')
上述代码将显示以下电路:

图 8.7:在 Nazca 上的编译电路
需要注意的是,在第一组单量子比特门之后,q[0]和 q[1]之间的多量子比特门集合。这些多量子比特门可以用来创建一个 CNOT、SWAP 或 ROTX 门,由标记为Ecr的红色方块表示。这种量子比特之间的信息路由是路由传递的结果,它寻找量子比特之间最优化连接。下图显示了分解后的 SWAP 门。

图 8.8:SWAP 门分解
-
现在,让我们将优化级别最大化到
3,这将对量子电路进行最高优化:# Generate a preset pass manager for Brisbane: pass_manager = generate_preset_pass_manager(backend=backend_brisbane, optimization_level=3) # Transpile the circuit with the optimization level = 3 qc_transpiled_brisbane= pass_manager.run(qc) # Print the depth of the transpiled circuit print('Depth:', qc_transpiled_brisbane.depth()) # Print the number of operations of the transpiled # circuit print('Ops count: ', qc_transpiled_brisbane.count_ops()) # Plot the layout mapping of the transpiled circuit plot_circuit_layout(qc_transpiled_brisbane, backend_brisbane)
上述代码将在编译电路中打印出总电路深度和操作符总数(Ops count),以及量子比特映射到Brisbane的渲染。
Depth: 133
Ops count: OrderedDict([('rz',14), ('sx', 7), ('ecr', 3), ('x', 1)])
在这里,你会注意到一些你可能之前没有见过的门;你可以在 Qiskit API 文档页面上的电路库中找到每个门的详细信息:docs.quantum-computing.ibm.com/api/qiskit/circuit_library。
下图显示了之前提到的量子比特映射的渲染:

图 8.9:将优化级别设置为 3 的 Brisbane 编译电路
如您在先前的图中所见,为了优化量子电路,量子位顺序从Brisbane上的先前的示例中调整。这是由于将optimization_level参数设置为 3(最高),它包括映射到最优化量子位。这也突出了两个电路深度之间的差异。
-
现在让我们绘制编译后的电路,以审查电路布局到后端:
# Redraw the transpiled circuit at new level circuit_drawer(qc_transpiled_brisbane, idle_wires=False, output='mpl')
结果,如下所示,是与图 8.6中的电路相同的电路,但现在映射到不同的量子位上。这个电路与上一个电路的不同之处仅仅在于,编译器将最大优化级别设置为,因此它将量子位操作符映射到最优化量子位上。例如,我们可以看到量子位 0 被映射到量子位 4,如下所示:

图 8.10:将优化级别设置为 3 的编译电路
-
我们将在
Nazca上设置相同的优化级别为3,然后编译电路:# Generate a preset pass manager for Nazca: pass_manager = generate_preset_pass_manager(backend=backend_nazca, optimization_level=3) # Transpile the circuit with the optimization level = 3 qc_transpiled_nazca= pass_manager.run(qc) # Get the depth and operation count of the transpiled # circuit. print('Depth:', qc_transpiled_nazca.depth()) print('Ops count: ', qc_transpiled_nazca.count_ops()) # Print the circuit layout plot_circuit_layout(qc_transpiled_nazca, backend_nazca)
这里,总深度相同,操作符的数量和类型也相同:
Depth: 1919
Ops count: OrderedDict([('rz', 23), ('sx', 13), ('ecr', 3)])
然而,请注意布局不一定是线性的;它看起来像T 形,其中量子位 0 连接到 3 个量子位,就像Brisbane那样:

图 8.11:将优化级别设置为 3 的编译电路
如您所见,为了优化电路深度并减少噪声,量子位顺序与在Nazca上运行的先前的示例相反。
-
让我们使用编译后的电路绘制电路,看看这种映射与先前的电路映射相比如何:
# Draw the transpiled circuit circuit_drawer(qc_transpiled_nazca, idle_wires=False, output='mpl')

图 8.12:将优化级别设置为 3 的编译电路
注意使用量子位0,现在硬件上的量子位44,它被映射为电路末尾 ECR(环境控制反转)中连接到其他量子位0、3和2的量子位。这是一个很好的例子,其中优化器将 ECR 门映射到物理量子位上,例如,量子位45映射到设备上的量子位44、46和54,以优化除路由之外的其他传递。这种情况的一个原因可能是其中一个传递考虑了量子位的相干时间(弛豫时间,T1),这是量子位可以保持其量子状态的时间,在电路编译时,相干时间最长的量子位是量子位1。
应用自定义布局
最后,现在让我们创建我们自己的自定义映射,或称为拓扑:
-
让我们首先回顾现有设备的耦合图;在这种情况下,让我们继续使用具有 127 个量子位的
brisbane,我们首先将审查后端的配置:# Set the ibm_brisbane backend device to obtain #configuration backend = service.get_backend('ibm_brisbane') backend
上述代码设置了后端,以便我们可以获取量子设备的配置和属性值。
-
让我们通过调用后端配置的
coupling_map字段来检查ibm_brisbane的耦合映射:# View the backend coupling map, displayed as CNOTs # (Control-Target) # Extract the coupling map from the backend ibm_brisbane_coupling_map = backend.configuration().coupling_map # List out the extracted coupling map ibm_brisbane_coupling_map
上述代码将导致显示 ibm_brisbane 的耦合布局数组。您可以通过将其与后端视图进行比较来验证这一点:
[[1, 0],
[2, 1],
[3, 2],
[4, 3],
…
[125.126]]
-
接下来,我们将绘制耦合映射,以查看默认映射下电路的效率:
# Generate a preset pass manager for Brisbane # Set the backend to None so it will force using the coupling map provided: pass_manager = generate_preset_pass_manager(backend=None, optimization_level=3, coupling_map=ibm_brisbane_coupling_map) # Transpile the circuit with the pass manager qc_custom = pass_manager.run(qc) # Draw the resulting custom topology circuit. circuit_drawer(qc_custom, idle_wires=False, output='mpl')
使用此拓扑,我们的电路现在与我们在 图 8.10 中看到的 brisbane 不同。在这里,我们看到相同的电路现在基于 brisbane 拓扑的早期结果进行编译,如下所示(请注意,这可能会根据系统的校准而变化):
描述自动生成
图 8.13:使用 ibm_brisbane 耦合映射提供的拓扑的自定义电路
-
到目前为止,您已经从现有的后端系统中提取了耦合映射,在本例中为
ibm_brisbane。 -
现在,让我们创建我们自己的自定义拓扑。为了简单起见,我们将创建一个简单的线性拓扑,其中量子比特以直线形式连接,如下所示:
# Create our own coupling map (custom topology) custom_linear_topology = [[0,1],[1,2],[2,3],[3,4]] # Generate a preset pass manager # Set the backend to None so it will force using the coupling map provided: pass_manager = generate_preset_pass_manager(backend=None, optimization_level=3, coupling_map=custom_linear_topology) # Transpile the circuit with the pass manager qc_custom = pass_manager.run(qc) # Draw the resulting custom topology circuit. circuit_drawer(qc_custom, idle_wires=False, output='mpl')
上述电路代码的结果显然并不理想:
描述自动生成
图 8.14:我们电路的自定义线性拓扑
电路需要许多门,并且相当深,这增加了产生噪声结果的风险。这是优化器重要性的一个很好的说明,优化器处理了许多这些潜在问题。毫不奇怪,在寻找更好的方法来优化电路以避免低效和噪声电路方面,有如此多的研究。然而,拥有自定义拓扑的能力允许您创建最优通行,这可能找到独特和有效的方法来优化电路的映射,用于测试和实验目的。
有许多通行可以优化电路(我们刚刚覆盖了布局,因为它很容易看出差异)。如果您查看通行的完整列表,您会看到,如果您更改通行的顺序,它将改变映射到物理设备时的电路结果。为了解决这个问题,我们需要查看通行管理器。
利用通行管理器
通行管理器允许通行之间进行通信,安排哪些通行应该首先执行,并允许将自定义通行包括在通行列表中。这并不像听起来那么简单,因为如果先使用一个通行然后再使用另一个,或者可能无法与其他通行通信,可能会有很大的差异。我们将通过以下步骤使用一个简单的例子来结束本节,创建通行管理器:
-
我们首先将
TrivialLayout添加到PassManager并执行电路:# Import the PassManager and a few Passes from qiskit.transpiler import PassManager, CouplingMap from qiskit.transpiler.passes import TrivialLayout, BasicSwap pm = PassManager() # Create a TrivialLayout based on the ibm_brisbane coupling map trivial = TrivialLayout(CouplingMap(ibm_brisbane_coupling_map)) # Append the TrivialLayout to the PassManager pm.append(trivial) # Run the PassManager and draw the resulting circuit tv_qc = pm.run(qc) circuit_drawer(tv_qc, idle_wires=False, output='mpl')
结果电路如下。注意这个电路的细节,因为我们将会比较这个电路和即将到来的电路(图 8.16)的布局差异:

图 8.15:附加了 TrivialLayout 传递的 PassManager 电路
-
很快我们将通过路由传递类型探索传递管理器的功能。当添加 SWAP 门来连接未直接连接在物理设备上的量子位时,我们看到了一些这方面的内容。在以下步骤中,我们将查看优化这些 SWAP 门路由的传递。
-
在以下代码中,我们将创建一个
BasicSwap传递,在电路上重新运行PassManager,并将结果与之前的电路进行比较:# Create a BasicSwap based on the ibm_brisbane coupling # map we used earlier basic_swap = BasicSwap(CouplingMap(ibm_brisbane_coupling_map)) #Add the BasicSwap to the PassManager pm = PassManager(basic_swap) # Run the PassManager and draw the results new_qc = pm.run(qc) circuit_drawer(new_qc, idle_wires=False, output='mpl')
之前的代码将在构造时创建一个BasicSwap路由器并将其添加到PassManager。执行后的电路结果如下:

图 8.16:带有 BasicSwap 路由器传递的 PassManager 电路
如您所见,电路将适应从PassManager调用的每个传递过程 - 在这种情况下,我们在代码中将BasicSwap传递添加到PassManager中,然后以一种形式渲染了电路映射,而TrivialLayout传递则以不同的形式渲染。这样做可以让你在PassManager的优化过程中对各种传递的执行顺序进行排序。拥有这种能力为你提供了测试如何优化电路在后台设备上的映射和运行方式的方法。
让我们花点时间消化一下我们到目前为止所学的内容。当使用optimization_level选项在后台执行我们的电路时,PassManager会根据所选的具体级别使用预设的传递(即,0、1、2 或 3)。我们在这里做到的是自定义要使用哪些传递以及它们的顺序,就像我们在之前的代码中添加 BasicSwap 传递时做的那样。拥有这种能力不仅为你提供了实验各种传递序列的灵活性,你还可以创建自己的传递并与现有的传递进行比较。
现在你已经熟悉了传递管理器,你可以看到,如果你想以某种方式组合使用传递,从而在修改过程中提高电路的优化,它们可以非常有帮助。
在本节中,我们还学习了编译器以及它如何提供优化电路的方法。我们还学习了使用布局优化器转换和优化电路。
本章的下一段内容更加直观,通过以不同的样式和操作流程(如DAGs)渲染电路。这些功能提供了一种以不同形式查看电路及其功能的方法,而不是默认的电路视图,这是我们迄今为止所看到的方式。
可视化和增强电路图
本节将重点介绍 Qiskit 中可用的各种可视化。我们迄今为止所使用的图表来自 Qiskit 的默认可视化库。然而,我们可以指定其他更适合你文档目的的绘图工具。比如说,如果你正在编写一篇使用LaTeX的研究论文,并且想使用 LaTeX 内容。
通过简单地添加来自 Qiskit 可视化库的样式参数,然后你可以利用可视化库中包含的许多功能。我们现在将介绍其中的一些,以帮助你入门。
了解定制可视化电路
在渲染电路时,通常需要或方便以适合你文档格式的格式呈现结果。这就是 Qiskit 的circuit_drawer功能派上用场的地方,它具有各种功能。让我们从一个简单的量子电路开始,以说明各种视觉渲染示例:
-
首先,让我们创建一个包含各种操作符的量子电路,以获得各种格式中所有视觉组件的良好表示:
# Sample quantum circuit qc = QuantumCircuit(4) qc.h(0) qc.cx(0,1) qc.barrier() qc.cx(0,2) qc.cx(0,3) qc.barrier() qc.cz(3,0) qc.h(0) qc.measure_all() # Draw the circuit using the default renderer circuit_drawer(qc, output='mpl')
这将渲染以下电路图,它只是门的一个随机表示。这个电路并没有做任何特别的事情;它只是用来表示各种组件。作为一个选项,你可以使用random_circuit方法来创建一个随机电路:

图 8.17:使用默认库进行电路渲染
-
接下来,我们将使用
latex渲染前面的电路:circuit_drawer(qc, output='latex')
这将渲染电路的latex版本:
如果你在这个平台上运行的不是本地机器,你可能会收到一些警告或错误,表明你需要安装一些文件依赖项,例如安装pylatexenc。要安装这个库,你需要在单元格中首先运行pip install pylatexenc,然后重新启动内核。

图 8.18:使用 latex 库进行电路渲染
-
如果你计划将你的电路发布到网站、博客或社交媒体上,并且想在图片中包含一些样式,你也可以通过传递样式内容作为参数来实现,例如
backgroundcolor、gatetextcolor和fontsize,仅举几个例子:# Define the style to render the circuit and components style = {'backgroundcolor': 'lightblue','gatefacecolor': 'white', 'gatetextcolor': 'black', 'fontsize': 9} # Draw the mpl with the specified style circuit_drawer(qc, style=style, output='mpl')
前面的代码结果是对背景、门颜色方案和字体大小进行调整,如图所示:

图 8.19:在 matplotlib 上使用自定义样式字典渲染的电路
要使用样式设置,你必须使用输出matplotlib,因为这这是唯一支持样式的库。
可用样式列表的详细信息可以在 Qiskit API 文档的样式字典详细信息部分找到(docs.quantum-computing.ibm.com/api/qiskit/qiskit.visualization.circuit_drawer)。
最后,我们将涵盖电路作为 DAG 的完整视图,这将有助于将电路作为图来理解其流程。
绘制电路的有向无环图
随着电路变大,它们自然会变得更加复杂,甚至可视化电路也可能变得复杂。想象一下有数千个量子比特且深度超过 1,000 的电路。这将很难渲染,几乎不可能阅读。这就是 DAG 可能有所帮助的地方。让我们基于之前创建的电路创建一个 DAG 来展示渲染,并看看该电路的 DAG 看起来如何。
在下面的代码中,你需要两个组件;第一个是电路到 DAG 转换器。这将把电路转换成一个 DAG。第二个组件是 DAG 绘制器,它将绘制出 DAG,其中节点表示量子寄存器、经典寄存器、量子门、屏障和测量算子。边是有方向的,这说明了电路的流程:
# Import the Circuit to DAG converter
from qiskit.converters import circuit_to_dag
# Import the DAG drawer
from qiskit.visualization import dag_drawer
# Convert the circuit into a DAG
dag = circuit_to_dag(qc)
# Draw the DAG of the circuit
dag_drawer(dag)
这导致了以下 DAG 的渲染:

图 8.20:量子电路的 DAG 表示
DAG 可以帮助从上到下说明电路的流程和预期路径,其中顶层是第一个操作,并且随着你遍历图,每个操作都会附加。例如,前面的图从顶部的绿色量子比特开始,然后跟随图,我们看到每个量子比特(由节点之间的边标签表示)都受到一个指定的操作(由节点表示)的作用。图在红色的末端结束,其中对量子比特应用的测量映射到指定的经典比特,由参数值表示。
在本节中,我们学习了在自定义可视化电路的帮助下可视化电路图。我们还学习了如何使用 DAG 来增强我们的电路图并可视化电路的路径。
摘要
在本章中,你学习了在当前存在的许多量子计算机上运行电路时优化电路的各种方法。你还了解了可用于优化指定量子设备上电路执行的不同遍历。这包括生成遍历管理器,它允许你根据所选的优化级别自定义要利用的遍历,并允许你选择它们的顺序。
我们随后介绍了拓扑和耦合图,这有助于你理解了解设备配置的重要性,尤其是当你想要创建自己的传递函数时。通过以各种格式可视化电路,你现在拥有了自定义图像渲染的技能,尤其是如果你在记录你的工作并希望保持某种外观和感觉时。最后,我们通过使用 DAGs 来介绍了电路操作流程的替代渲染方式。
在下一章中,我们将学习与量子系统相关的各种噪声,以了解我们如何创建可以用于模拟和识别减轻和运行更高效算法的噪声模型。
问题
-
你能说出 transpiler 的两个组件吗?
-
哪个组件允许你指定要使用的传递函数?
-
运行 transpile() 函数时,默认的
optimization_level值是多少? -
列出三个布局选择传递函数。
加入我们的 Discord
加入我们社区的 Discord 空间,与作者和其他读者进行讨论:
packt.link/3FyN1

第九章:模拟量子系统和噪声模型
Qiskit是高性能后端提供商,可用于执行量子电路。可用的各种后端模拟器可以以独特的方式使用,每个都可以提供有关你的电路的不同信息。Qiskit 还提供各种工具,可以用来构建噪声模型以模拟真实量子设备上发生的各种错误。如果你需要比较理想模拟器和复制量子设备噪声效应的模拟器之间的结果差异,这些工具非常有帮助。
模拟器和如噪声模型之类的工具将帮助你理解一些结果上的原因,以及如果你以后想自己减轻这些错误,它们将提供见解。
本章将涵盖以下主题:
-
理解模拟器之间的差异
-
生成噪声模型
-
构建你自己的噪声模型
-
使用自定义噪声模型执行量子电路
在本章中,我们将回顾 Qiskit 模拟器,并理解它们之间的差异以及每个模拟器提供的独特功能。我们还将深入研究基于指定后端设备可以生成的 Qiskit 噪声模型,以便我们能够在理想的 Qiskit 模拟器上模拟噪声。
在阅读本章后,你将能够在模拟器上重现类似的噪声效应。这将使你能够观察噪声如何影响我们的结果,这将使我们能够模拟真实的量子设备。最后,我们将介绍如何创建你自己的噪声模型并将它们应用于你的电路。
技术要求
在本章中,我们假设你已经熟悉了前几章中描述的量子电路的基本知识,例如创建和执行量子电路、获取后端属性和配置、定制和可视化电路图,并且你应该了解量子比特逻辑门操作符和状态。此外,对噪声效应(如退相时间)有一定的了解将非常理想;然而,我们将在本章中涵盖一些基础知识作为复习。你需要安装qiskit-aer的最新版本来运行本章中的笔记本;详细信息可以在 Qiskit 文档中找到:qiskit.github.io/qiskit-aer/getting_started.html.
本书所使用的完整源代码如下:github.com/PacktPublishing/Learning-Quantum-Computing-with-Python-and-IBM-Quantum-Second-Edition.
理解模拟器之间的差异
在本节中,您将了解 Qiskit Aer 中包含的各种模拟器后端,包括它们之间的差异和独特的特性。请注意,您需要单独安装qiskit-aer,因为它不是 Qiskit 基础安装的一部分。
这些特性包括生成噪声模型和配置模拟器后端,允许您利用修改其行为和特性以满足您的需求。
我们将了解以下模拟器和它们的关键特性:
-
Aer 模拟器,通过多次射击执行量子电路以模拟有噪声的后端量子系统
-
Statevector 模拟器,提供量子电路的状态向量
-
Unitary 模拟器,提供正在执行的量子电路的单位矩阵
让我们继续,看看量子系统,在此处简称为后端。
查看所有可用的后端
如果您已经阅读了本书的前几章,那么您应该已经了解我们使用的一些模拟器。让我们从显示来自各种来源的每个可用模拟器开始。
我们将在 IQP 上创建一个新的笔记本,并运行自动生成的单元格,以确保您已加载一些基础类和方法,并加载您的账户信息,以便我们可以访问 IQP:
-
我们将首先导入一些有用的类和函数,包括辅助文件中的那些:
# Load helper file and import functions: %run helper_file_1.0.ipynb from qiskit import QuantumCircuit, transpile from qiskit_aer import AerProvider, AerSimulator, QasmSimulator, StatevectorSimulator, UnitarySimulator from qiskit.visualization import * from qiskit_ibm_runtime import QiskitRuntimeService, Sampler, Estimator, Session, Options # Load your IBM Quantum account(s) service = QiskitRuntimeService(channel="ibm_quantum") -
接下来,我们将使用以下代码显示 Qiskit Aer 库中所有可用的模拟器:
# View all available backends provider = AerProvider() provider.backends()
这将显示所有可用的模拟器列表:
[AerSimulator('aer_simulator'),
AerSimulator('aer_simulator_statevector'),
AerSimulator('aer_simulator_density_matrix'),
AerSimulator('aer_simulator_stabilizer'),
AerSimulator('aer_simulator_matrix_product_state'),
AerSimulator('aer_simulator_extended_stabilizer'),
AerSimulator('aer_simulator_unitary'),
AerSimulator('aer_simulator_superop'),
QasmSimulator('qasm_simulator'),
StatevectorSimulator('statevector_simulator'),
UnitarySimulator('unitary_simulator')])]
-
以下代码将列出
Qiskit-Aer库中的模拟器。如果您不想安装 Aer 而只想使用 Qiskit,这些模拟器也作为 Python 内置模拟器可用。为了简单性和性能考虑,我们将在这本书中使用 Qiskit 模拟器。然而,您当然可以根据需要将 Aer 模拟器与service中列出的实际量子系统进行交换。但由于我们想要节省使用时间,让我们继续使用这些基本电路和学习概念。 -
最后,我们可以列出从 Qiskit Runtime Service 可用的所有量子系统:
# View all available IBM Quantum backends service.backends()
这不仅会列出模拟器,还会根据您的账户列出您可用的真实量子设备。所列出的设备将根据可用设备和升级而变化,自本写作以来:
[<IBMBackend('ibm_brisbane')>,
<IBMBackend('ibm_kyiv')>,
<IBMBackend('ibm_nazca')>,
<IBMBackend('ibm_sherbrooke')>,
<IBMBackend('ibm_kyoto')>,
]
由于本章专注于模拟器,我们将从 Qiskit 库中安装的本地模拟器开始学习。我们将从 Aer 模拟器开始,我们可以使用它来执行小型电路。
在 Aer 模拟器上运行电路
Aer 模拟器不仅用于执行量子电路,而且由于其能够应用各种模拟方法和配置选项,因此非常灵活。
一些可用的模拟方法如下所述:
-
statevector: 这是 Aer 库中的状态向量模拟,允许在量子电路末尾进行理想测量。此外,每个执行电路的射击都可以从噪声模型中采样随机噪声,以提供有噪声的模拟。 -
density_matrix: 此方法提供密度矩阵模拟,类似于状态向量,在每个电路末尾给出测量值来采样量子电路。 -
matrix_product_state: 这是一个张量网络状态向量模拟器,它利用矩阵乘积状态作为状态的表示。 -
automatic: 如果没有设置方法,则此方法将根据量子比特数、量子电路和噪声模型自动选择一个。
可用的后端选项有很多;下面是backend_options可用选项的一个子集:
-
device: 此选项设置模拟设备,默认设置为CPU。然而,statevector、unitary和density_matrix模拟器也可以在配备 Nvidia 图形处理单元(GPU)的系统上运行。要将模拟器配置为 GPU,只需设置选项参数device='GPU'。 -
precision: 此选项将浮点数设置为单精度或双精度;默认为double。将精度设置为single将减半后端所需的内存,这可能在某些系统上提供一些性能提升。 -
zero_threshold: 此选项将小值截断为 0,并将非常小的值截断。默认截断值设置为 1e-10,但可以根据开发者的需求进行调整。 -
validation_threshold: 此阈值用于验证量子电路的初始状态向量是否有效,默认值设置为 1x10^(-8)。 -
max_parallel_threads: 将此参数设置为(默认值)0将使模拟器能够在所有可用核心上运行。 -
max_parallel_experiments: 这是qobj(QASM 对象)的最大数量,它代表 Qiskit 提供程序并行运行电路的单个有效负载。最大值不能超过max_parallel_threads值。如果最大值设置为0,则将其设置为max_parallel_threads值。 -
max_parallel_threads: 此选项设置并行化的最大 CPU 核心数。默认值设置为0,这意味着它将设置为 CPU 核心的最大数量。 -
max_memory_mb: 将此参数设置为0将使模拟器最大化系统内存的大小以存储状态向量;默认值设置为0。如果需要更多内存,将抛出错误。作为参考,n 个量子比特的状态向量使用 2^n 个大约 16 字节的复数值。
现在您已经了解了模拟方法和后端选项,我们将创建一个简单的电路,并使用 Aer 的QasmSimulator类执行它。对于这个例子,我们将创建我们迄今为止一直在使用的相同电路示例,由 Hadamard 和 CX 门组成,这使量子电路处于叠加态并将两个量子比特纠缠在一起:
在这里,我们创建了一个 2 量子比特和 2 位电路;当使用measure_all()函数时,我们需要将add_bits参数设置为False,这样它就不会添加经典比特,因为我们已经在QuantumCircuit构造函数中添加了它们。
# Create a quantum circuit
qc = QuantumCircuit(2, 2)
qc.h(0)
qc.cx(0, 1)
qc.measure_all(add_bits=False)
现在,让我们使用get_backend()函数创建 Aer 模拟器:
# Instantiate the QASM simulator from the Aer provider
backend_simulator = QasmSimulator()
# Transpile the circuit transpiled_qc = transpile(qc, backend_simulator)
# Run the circuit using the transpiled circuit
job = backend_simulator.run(transpiled_qc)
# Print out the result counts
result = job.result()
counts = result.get_counts(qc)
print(counts)
这将打印出在 Aer 模拟器上执行量子电路的结果,方法设置为状态向量,获得结果计数。
如您所见,这运行的结果与您运行aer_simulator如下所示:
# Get the Aer simulator and set the backend options
aer_simulator = AerSimulator(method='statevector')
# Transpile the circuit
transpiled_qc = transpile(qc, aer_simulator) # Run the circuit with the Aer simulator
job = aer_simulator.run(transpiled_qc)
print(job.result().get_counts(qc))
这两种形式都以相同的方式执行电路,当然结果中的值会有所不同。在这里,您可以查看结果,它们总共运行了 1024 次射击,这是默认设置的:
{'00': 501, '11': 523}
我们将在下一节继续扩展后端选项,包括我们可能发现有用的其他参数,例如射击和内存。
向后端选项添加参数
我们可能已经熟悉了shots参数,它指定了在后端上执行电路的次数。然而,如前一个示例所示,返回的计数是所有射击的总值,而不是每个结果返回的顺序。可能存在您希望按时间顺序检查每个射击结果的情况。
要检查存储在各个内存槽中的测量结果,您需要在后端选项中设置memory参数。让我们重新运行之前的电路;然而,这次我们将memory标志设置为True并显示结果。这次我们将只运行 10 次以避免输出一个非常长的字符串:
# Run the circuit on the simulator and set the memory to True
# Run the transpiled circuit using the backend options created
job = backend_simulator.run(transpiled_qc, shots=10, memory=True)
result = job.result()
# Pull the memory slots from the results
memory = result.get_memory(transpiled_qc)
# Print the results from the memory slots
print('Memory results: ', memory)
这将输出电路执行的 10 个内存槽条目结果。请注意,结果为00和11的变体组合,正如电路所预期的:
Memory results: ['00', '11', '11', '11', '00', '00', '00', '11', '00', '11']
将内存功能集成到 Aer 模拟器中,您将能够可视化电路计数的每个结果。下一节将说明如何初始化和设置所有或仅部分量子比特。
在电路中初始化量子比特
如我们早期所学的,每个量子比特被初始化为基态,或称为
状态。然而,有时我们可能希望设置一个不同的初始状态。幸运的是,Aer 模拟器允许我们将电路的状态初始化为除所有
状态之外的某个状态,
。
我们将按照以下步骤初始化量子比特:
-
在上一个示例中,我们创建了一个包含 Hadamard 门和控制非门的电路,以获得
或
的纠缠态结果。在这个示例中,我们将初始化我们的电路,以便结果相同,而无需添加任何门:# Construct a 2 qubit quantum circuit qc_init = QuantumCircuit(2, 2) # Import numpy to simplify some math for us import numpy as np # Select the qubits by their index which you wish to initialize init_qubits = [0, 1] # Inititialize qubit states qc_init.initialize([1, 0, 0, 1] / np.sqrt(2), init_qubits) # Add measurements and draw the initialized circuit qc_init.measure(range(2), range(2)) qc_init.decompose() qc_init.draw(output="mpl")
这导致了以下电路图:

图 9.1:初始化量子位到非零初始状态
注意,量子位被集体初始化到
的状态。现在,这个电路有一个初始化状态,如果您希望电路从非基态/零态开始,可以应用于任何电路。在需要每次运行时更新以优化其结果的变分量子算法中,初始化状态可能是必要的。
-
现在,让我们执行这个电路并观察每个结果:
# Set the memory to True so we can observe each result result = backend_simulator.run(qc_init, shots=10, memory=True).result() # Retrieve the individual results from the memory slots memory = result.get_memory(qc_init) # Print the memory slots print(memory)
这将输出以下结果:
['11', '11', '00', '11', '11', '00', '00', '00', '11', '00']
如您从结果中观察到的,我们只得到了预期的两种初始化状态结果,即
或
。
-
现在,您不必初始化电路中的所有量子位;您也可以指定要初始化的一组量子位,如下面的代码所示:
# Create a 4 qubit circuit qc_init2 = QuantumCircuit(4, 4) # Initialize only the last 3 qubits initialized_qubits = [1, 2, 3] # Set the initial state, remember that the sum of # amplitudes-squared must equal 1 qc_init2.initialize([0, 1, 0, 1, 0, 1, 0, 1] / np.sqrt(4), initialized_qubits) # Add a barrier so it is easier to read qc_init2.barrier(range(4)) # Measure qubits, decompose and draw circuit qc_init2.measure(range(4), range(4)) qc_init2.decompose() qc_init2.draw(output='mpl')
这导致了以下电路,它初始化了 q_1 到 q_3 量子位的状态,而所有其他初始化的量子位仍然处于基态/零态:

图 9.2:最后三个量子位的初始化
在这里,我们的 3 量子位初始化状态设置为
。然而,由于我们正在执行一个 4 量子位电路,并且我们已经初始化了最后 3 个量子位,我们的结果应该包括第四个量子位(q[0]),这将向最低有效位添加一个 0。
-
让我们运行实验,看看部分量子位的初始状态是否成功:
# Execute the circuit and print results and histogram result = backend_simulator.run(qc_init2).result() counts = result.get_counts(qc_init2) print(counts) plot_distribution(counts)
如预期的那样,我们的结果如下(请注意,由于电路的随机性,实际结果可能会有所不同):
{'0010': 275, '1010': 250, '0110': 255, '1110': 244}
我们还得到了以下输出图:

图 9.3:初始化量子电路的结果
如您所见,结果与我们预期的完全一致。请注意,最低有效位(最右边的位)始终设置为 0,因为它不是初始化的量子位之一。其他需要注意的事情是其他位正好如我们预期的那样,
,其中粗体表示初始化的位,如果您将它们全部组合起来,它们将提供显示的结果。
-
现在我们已经初始化了一个电路,我们可以根据需要应用任何门。唯一的区别是,初始化后应用到电路上的门将应用于每个量子比特的初始化状态,而不是默认的初始化状态
。让我们通过向所有量子比特添加一个 NOT(X)门来测试这一点。这应该会导致所有值翻转:# Create a 4-qubit circuit qc_init_x = QuantumCircuit(4, 4) # Import numpy import numpy as np # Initialize the last 3 qubits, same as before initialized_qubits = [1, 2, 3] qc_init_x.initialize([0, 1, 0, 1, 0, 1, 0, 1] / np.sqrt(4), initialized_qubits) # Add a barrier so it is easier to read qc_init_x.barrier(range(4)) # Include an X gate on all qubits for idx in range(4): qc_init_x.x(idx) # Measure and draw the circuit qc_init_x.measure(range(4), range(4)) # Decompose the circuit down a level qc_init_x.decompose() # Draw the completed circuit qc_init_x.draw(output='mpl')
这导致了以下电路:

图 9.4:在测量之前对所有量子比特应用 X 门的初始化量子电路
注意初始化的量子比特与之前相同,只是在测量之前我们添加了所有量子比特的 X 门。这应该会导致所有位从 0 翻转到 1,反之亦然。
-
让我们使用以下代码执行电路并显示结果:
# Execute and get counts result = backend_simulator.run(qc_init_x).result() counts = result.get_counts(qc_init_x) print(counts) plot_distribution(counts)
结果完全符合预期;结果是基于初始化状态,然后是所有量子比特上应用的 NOT 门:
{'0101': 256, '0001': 268, '1101': 232, '1001': 244}
我们还可以看到以下图表:

图 9.5:对所有量子比特应用 X 门后的初始化电路的结果
Aer 模拟器的灵活性和可配置性意味着能够创建具有初始化量子比特状态能力的自定义电路是一个很大的优势。我们将在第十二章“理解量子算法”中更详细地看到这一点,我们将看到这是如何应用于变分或其他混合量子算法的。
现在我们已经熟悉了 Aer 模拟器,让我们继续介绍状态向量模拟器,看看我们有哪些独特的功能可用。
在状态向量模拟器上运行电路
状态向量模拟器,就像 Aer 模拟器一样,允许您初始化和执行量子电路。当然,有一些明显的区别,其中之一是它通过单次执行返回量子电路的状态向量。这允许您捕获状态向量的快照,以便您可以在某种程度上计算或观察量子比特上的预期结果。因为状态向量模拟器模拟了量子电路的理想执行,并在模拟结束时产生设备的最终量子状态向量,所以这个结果可以用于调试或教育目的。
我们还将利用一些 Aer 可视化工具来帮助显示量子比特和量子电路的状态信息。我们将按照以下步骤进行:
-
首先,让我们创建一个简单的 1 量子比特电路,并向其中添加一个 Hadamard 门,以便我们有一个处于叠加态的量子比特:
# Construct quantum circuit qc = QuantumCircuit(1) # Place qubit in superposition qc.h(0) qc.draw(output='mpl')
结果如下,我们有一个处于叠加态的单个量子比特,即
和 ![img/B18420_05_005.png] 的复数线性组合:

图 9.6:具有 Hadamard 门的单个量子比特电路
- 接下来,我们想查看电路的状态向量表示。在编码之前,让我们回顾一下相关的数学知识。我们知道每个基态都由状态向量表示,例如以下为
状态的状态向量:

同样,
状态可以用以下状态向量表示:

- 量子比特的初始状态为
。Hadamard 门通常将 Hadamard 矩阵应用于量子比特的当前状态,从而使量子比特处于叠加态。因此,如果将 Hadamard 门应用于处于
状态的量子比特,操作如下:

将矩阵乘以向量得到以下结果:

-
现在,让我们使用状态向量模拟器执行我们的电路,并输出状态向量值:
# Select the Statevector simulator from the Aer provider simulator = StatevectorSimulator() # Transpile the circuit transpiled_qc = transpile(qc, simulator) # Run the transpiled circuit result = simulator.run(transpiled_qc).result() # Get the state vector and display the results statevector = result.get_statevector(transpiled_qc) statevector
从结果中,我们可以通过简单地从Job对象中提取它来获得量子电路的状态向量,在这种情况下,result.get_statevector()。
这应该产生以下输出,正确匹配我们的预期结果,包括状态向量矩阵维度信息,并且结果中的振幅值正好是
。此外,如果我们平方振幅,结果将为我们提供获得 0 或 1 的概率。状态向量结果表示当我们按照第 3 步中描述的将 Hadamard 应用于初始状态向量
时预期的结果:
Statevector([0.70710678+0.j, 0.70710678+0.j], dims=(2,))
-
让我们通过添加另一个叠加量子比特来扩展这个例子:
# Construct quantum circuit qc = QuantumCircuit(2) # Place both qubits in superposition qc.h(0) qc.h(1) qc.draw()
该电路的结果与之前类似,只是增加了一个额外的量子比特:

图 9.7:两个叠加的量子比特
-
让我们使用状态向量模拟器运行这个电路,并打印出我们状态向量的结果:
# Transpile the circuit transpiled_qc = transpile(qc, simulator) # Run the circuit using the state vector simulator result = simulator.run(transpiled_qc).result() # Extract the state vector of the circuit from the # results statevector = result.get_statevector(transpiled_qc) # Output the state vector values statevector
这产生了以下输出,表示所有 4 种可能状态具有相等的振幅,
,和
:
Statevector([0.5+0.j, 0.5+0.j, 0.5+0.j, 0.5+0.j], dims=(2,2))
在这里,如果我们对每个值进行平方以获得概率测量,我们会看到每个都有 25%的正确概率。回想一下,所有概率的总和必须为 1。
-
最后,让我们将量子比特纠缠起来,看看当我们对第一个量子比特应用 Hadamard 门时,状态向量结果会是什么:
# Construct quantum circuit qc = QuantumCircuit(2) # Place the first qubit in superposition qc.h(0) # Entangle the two qubits together using a CNOT gate, # where the first is the control and the second qubit is # the target qc.cx(0, 1) # Transpile the circuit transpiled_qc = transpile(qc, simulator) # Run the circuit on the state vector simulator result = simulator.run(transpiled_qc).result() # Obtain the state vector of the circuit statevector = result.get_statevector(transpiled_qc) # Output the state vector values statevector
状态向量结果符合预期,00 和 11 的振幅值相等,01 和 10 状态没有值
:
Statevector([0.70710678+0.j, 0.+0.j, 0.+0.j, 0.70710678+0.j], dims=(2,2))
-
我们还可以寻求可视化工具的帮助,以帮助我们说明我们刚刚执行的电路的状态向量结果。我们将添加
plot_state_city向量函数:# Display state vector plot_state_city(statevector)
结果与之前看到的是相同的值,只是在这里我们可以看到实部(左)和虚部(右)的振幅。当我们对结果的振幅进行平方时,我们将得到 00 和 11 状态的 50% 概率,这正是我们在以下状态矢量图中看到的。状态矢量图是描述 3D 柱状图视图(通常是二维)的术语。这个术语被用来描述二维图上的柱状图看起来像城市中的建筑。在这种情况下,我们有两个:一个代表状态矢量的实数值,另一个代表虚数值。
注意,在我们的结果中,状态矢量 1.+0.j 的任何虚部都没有值:

图 9.8:包含实部(左)和虚部(右)的状态矢量图
状态矢量图有助于可视化 2-比特的密度矩阵,该矩阵本身具有 16 个复数比特振幅。密度矩阵的话题超出了本书的范围,但简而言之,它通常是一种表达所有量子状态(如
和
)的替代方法,这里与预期的测量结果相同,但允许我们使用密度矩阵来数学地描述这些状态。
描述密度矩阵及其使用的详细信息可以在以下文档中找到:docs.quantum.ibm.com/api/qiskit/qiskit.quantum_info.DensityMatrix。
状态矢量图并不是我们拥有的唯一可视化工具。另一个可用的出色工具是 Qiskit qsphere。它将状态矢量绘制到二维图上,并包括独特的可视化功能,允许您看到对状态矢量振幅进行平方时的概率结果和相位。
-
让我们在 qsphere 上绘制来自相同状态矢量的概率结果:
# Import the plot_state_qsphere class from qiskit.visualization import plot_state_qsphere %matplotlib inline # Create quantum circuit qc = QuantumCircuit(1) # Place the qubit in a superposition state qc.h(0) # Execute the circuit on the statevector simulator backend = StatevectorSimulator() # Tanspile and run the circuit on the statevector simulator transpiled_qc = transpile(qc, backend) result = backend.run(transpiled_qc).result() # Display the QSphere with results from the previous cell plot_state_qsphere(transpiled_qc)
让我们回顾一下结果以及它们在 qsphere 中的显示方式:

图 9.9:对状态矢量结果进行平方后的 Qsphere 表示
首先,注意向量指向北方
状态和南方
状态,每个向量的末端球体具有相同的直径。这是为了说明结果要么是 0 要么是 1 的概率是相等的,因此它们处于叠加态,正如预期的那样。
接下来,每个球体的颜色与状态轮中描述的状态对应的颜色相匹配,该状态轮位于 qsphere 的右下角。这表明每个向量处于相位(0°),对应蓝色。这里的结果与我们之前推导出的预期方程相符,其中我们将量子比特置于叠加态;然而,我们尚未应用任何相位旋转,在这种情况下是围绕Z轴的旋转:

让我们通过引入相位偏移来做些有趣的事情。正如我们在前面的屏幕截图中所见,当我们应用 H 门时,向量从|0⟩移动到|+⟩,相位为(0°)。现在我们将包括一个 Z 门,也称为相位门,它将向量绕z轴旋转
的角度。和之前一样,我们首先回顾一下数学,以确认我们应该期待看到什么。回想一下,当状态向量起源于|0ñ时,我们如何描述应用 H 门的效果。以下是将 H 门应用于|1ñ状态的情况:

将矩阵与向量相乘的结果如下:

-
我们将创建一个起源于
状态的电路,并对其应用 H 门以确认前面的向量结果:# Create a quantum circuit qc = QuantumCircuit(1) # Rotate the state from |0> to |1> by applying an X gate qc.x(0) # Place qubit in a superposition from the |1> state qc.h(0) # Transpile the circuit transpiled_qc = transpile(qc, backend) # Run the circuit result = backend.run(transpiled_qc).result() # Extract the state vector results and plot them onto the # QSphere plot_state_qsphere(result.get_statevector(transpiled_qc))
结果 qsphere 现在与之前相同的概率;然而,由于旋转起源于
状态,它现在位于
侧,因此相位差为
,我们可以通过观察以下相位颜色图来确认(请注意,颜色表示可能会随时间变化。只需将颜色与颜色相位轮进行比较,以确定颜色到颜色的映射):

图 9.10:一个相位差为
的叠加态
-
现在,让我们尝试相同的事情,这次起源于
状态:# Create a quantum circuit qc = QuantumCircuit(1) # Place qubit in a superposition from the |0> state qc.h(0) # Apply a Z (phase) gate, to rotate it by # an angle π around the Z axis qc.z(0) # Transpile the circuit transpiled_qc = transpile(qc, backend) # Run the circuit result = backend.run(transpiled_qc).result() # Extract the state vector results and plot them onto the # QSphere plot_state_qsphere(result.get_statevector(transpiled_qc))
结果,正如我们所看到的,是相同的——两个状态
和
之间存在
的相对相位差。

图 9.11:一个叠加态且相位差为
的状态向量
我们可以看到,状态向量表示说明了我们在数学上看到的内容,在这种情况下如下所示:

从前面的方程中,负值表示相位差分量。我们将在第十二章“理解量子算法”中看到,各种量子算法如何利用这一点来利用干涉效应。
到目前为止,我们已经看到我们可以使用状态向量模拟器从我们的量子电路中获取和可视化量子状态信息。这使我们能够确定我们的电路状态是否是我们所期望的,这在尝试调试电路的结果时很有帮助。我们还可以使用各种图表来检查我们的量子电路,以审查如密度矩阵和基于状态向量结果的概率结果等信息。接下来,我们将探讨如何通过单元模拟器获取我们电路的单元矩阵。
在单元模拟器上运行电路
单元模拟器提供了构建电路的单元矩阵U。单元矩阵是量子电路的操作符表示,它允许你确定电路是否代表你试图复制或创建的操作符。单元模拟器通过遍历电路并应用每个门到电路的初始状态来构建单元矩阵。如 API 文档(qiskit.github.io/qiskit-aer/stubs/qiskit_aer.UnitarySimulator.html)所述,语义验证将验证qobj和后端选项的约束,具体如下:
-
射击次数设置为 1,因此将只运行一次以计算单元矩阵。
-
电路不能包含任何重置或测量。由于单元模拟器的任务只是计算单元矩阵,因此没有必要确定电路的测量。
-
不能应用任何噪声模型,因为这需要与非单元一起工作。
-
如果电路超出任何先前的约束,它将引发一个
AerError。
我们将利用之前为状态向量示例创建的相同电路来运行单元模拟器,以便我们可以比较和对比结果:
- 首先,让我们验证我们应该在数学上期望看到什么。由于我们将应用单个 Hadamard 门,因此确定单元矩阵应该相当简单。从初始状态开始,我们将对电路应用一个 H 门:

-
现在,我们将我们的电路运行在单元模拟器上,我们将创建一个量子电路并添加一个 Hadamard 门,然后将模拟器设置为 Aer 提供的单元模拟器。我们应该看到相同的结果:
# Create a quantum circuit and add a Hadamard gate qc = QuantumCircuit(1) qc.h(0) # Set the simulator to the UnitarySimulator from the Aer # provider simulator = UnitarySimulator() # Transpile the circuit transpiled_qc = transpile(qc, simulator) # Run the circuit on the unitary simulator result = simulator.run(transpiled_qc).result() # Extract the unitary matrix from the results unitary = result.get_unitary(transpiled_qc) # Print out the unitary matrix representation of the circuit print("Unitary of the circuit:\n", unitary)
你的单元结果应该与我们通过数学计算得到的结果相匹配;你可以忽略虚部中的显著小的数字:
Unitary of the circuit:
[[ 0.70710678+0.00000000e+00j 0.70710678-8.65956056e-17j]
[ 0.70710678+0.00000000e+00j -0.70710678+8.65956056e-17j]]
-
现在,让我们创建另一个电路,但这次,在我们将电路放入叠加态之后,让我们应用一个相移。让我们通过在 H 门之后添加一个 Z 相移门来实现这一点:
# Create a new circuit, adding an H gate followed by a Z gate qc = QuantumCircuit(1) qc.h(0) qc.z(0) # Transpile the circuit transpiled_qc = transpile(qc, simulator) # Run the circuit on the unitary simulator result = simulator.run(transpiled_qc).result() # Retrieve the unitary matrix from the results unitary = result.get_unitary(transpiled_qc) # Print the unitary matrix representation of the circuit print("Unitary of the circuit:\n", unitary) qc.draw(output='mpl')
这将产生我们创建的量子电路的以下单元矩阵表示。注意符号的差异:
Unitary of the circuit:
[[ 0.70710678+0.00000000e+00j 0.70710678-8.65956056e- 17j]
[-0.70710678+0.00000000e+00j 0.70710678-8.65956056e- 17j]]
这也将给我们以下电路图:

图 9.12:应用 H 门后跟 Z 门的 2 门电路
我们可以使用一些线性代数来确认这一点。需要注意的是,当我们对电路上的门进行操作并可视化它们时,我们通常从左到右应用它们,如前一个电路图所示,其中我们首先看到H门,然后是Z门。
- 然而,在计算幺正矩阵时,我们将每个添加的门从右到左放置幺正矩阵。这是描述将操作应用于初始状态顺序的书写惯例的顺序。例如,在这个电路中,我们按照以下顺序计算了幺正矩阵:首先应用 H 门,然后是 Z 门,因此从右到左结果是ZH = U,其中U是幺正矩阵解。现在让我们计算这个矩阵:

如前一个方程所示,我们现在已经确认了这是我们从该电路的幺正模拟器获得的相同结果。
与之前的模拟器一样,我们也可以使用给定的幺正矩阵初始化幺正模拟器。让我们使用之前示例的结果作为我们的初始幺正矩阵:
# Create a quantum circuit
qc_init = QuantumCircuit(1)
# Set the initial unitary using the result from the
# previous example and apply it to q[0]
qc_init.unitary(unitary, [0])
# Transpile the circuit
transpiled_qc = transpile(qc_init, simulator)
# Execute and obtain the unitary matrix of the circuit
result = simulator.run(transpiled_qc).result()
# Retrieve the unitary matrix from the result
unitary_result = result.get_unitary(transpiled_qc)
# Print the unitary matrix results representing the
# circuit
print("Unitary of the circuit:\n", unitary_result)
初始化电路的结果现在与之前的电路相同,无需添加任何用于生成该幺正矩阵的门。输出结果表示为一个幺正算子:
Unitary of the circuit:
Operator([[ 0.70710678+0.00000000e+00j, 0.70710678+8.65956056e-17j],
[-0.70710678-8.65956056e-17j, 0.70710678+1.73191211e-16j]],
input_dims=(2,), output_dims=(2,))
我们已经看到了幺正模拟器是一个在你想使用预定义的幺正矩阵进行实验时非常出色的组件。使用幺正模拟器,我们学习了如何从给定的量子电路中计算幺正算子,以及如何从幺正矩阵创建电路。
现在我们对各种模拟器和它们之间的差异有了更好的理解,我们将使用它们来模拟在真实量子设备上运行电路时获得的噪声。你还了解了每个模拟器提供的各种选项和参数,这样你就可以以多种方式利用每个模拟器来获得各种结果,例如计数和状态向量信息,从提供的量子电路中获取。这将有助于模拟噪声模型影响结果的电路结果,而不是在理想、无噪声模拟器上运行的结果。因此,让我们在下一节中生成噪声模型。
考虑量子电路中的噪声
噪声模型用于表示导致量子电路中错误的多种噪声效应。噪声的来源来自量子系统内的许多来源。根据目前可用的设备和即将到来的设备,设备上的错误数量可能会很大,这取决于在它们上执行的量子电路。
在本节中,我们将回顾可能影响量子比特、门和读出的各种错误类型。我们还将学习如何根据真实设备的配置信息生成噪声模型,或者创建我们自己生成的噪声模型,以便使用 Aer 模拟器模拟真实设备。
实现 Aer 噪声模型
我们将首先演示如何实现 Aer 噪声库中预构建的各种类型的噪声模型,这将有助于使我们的实验看起来更加真实:
-
我们将创建我们在大多数示例中使用的相同简单电路,即贝尔态,它包括一个 Hadamard 门和一个 CNOT 门,随后是一组测量算子,并在理想模拟器上执行,没有错误。这应该会产生预期的值 00 和 11:
# Create a 2-qubit circuit qc = QuantumCircuit(2, 2) # Add some arbitrary gates and measurement operators qc.h(0) qc.cx(0, 1) qc.measure([0, 1], [0, 1]) backend = AerSimulator() # Transpile the circuit transpiled_qc= transpile(qc, backend) # Run the circuit on the Aer simulator result = backend.run(transpiled_qc).result() # Obtain and counts counts = result.get_counts(transpiled_qc) # Plot the count results on a histogram plot_distribution(counts)
在理想模拟器上运行此电路的结果如下。请注意,我们只获得了预期的两个值,00 和 11:

图 9.13:无噪声效果的理想模拟器结果
-
现在,我们将在一个实际设备上而不是模拟器上执行相同的电路。为了节省时间,让我们找到最不繁忙且拥有足够量子比特来运行我们的实验的设备。请注意,在量子系统上完成此操作的时间可能会根据您在队列中的位置而有所不同:
# Let's set the number of qubits needed and get the least busy num_qubits = 2 backend = service.least_busy(min_num_qubits = num_qubits, simulator = False, operational=True) # Transpile the circuit transpiled_qc = transpile(qc, backend) # Run the circuit options = Options(optimization_level=0) shots=1000 with Session(service=service, backend=backend) as session: sampler = Sampler(session=session, options=options) job = sampler.run(circuits=transpiled_qc, shots=shots) result = job.result() print('Quasi dist results: ', result.quasi_dists[0])
结果与之前在模拟器上的执行结果非常相似,但这次,请注意结果中存在一些错误。我们不仅获得了0和3的结果,还看到了一些1和2的实例。这是后端噪声对电路结果的影响:
Quasi distribution results: {0: 0.49323295547592433, 1: 0.01753366164023622, 2: -0.009464115736336355, 3: 0.49869749862017565}
- 现在,让我们做一些有趣的事情。让我们利用基于特定后端设备特性的噪声模型。Aer 的
NoiseModel提供了通过简单方法调用来实现这一点的功能。
ibmq_kyoto and its properties: coupling_map, which describes how the qubits are physically connected to each other on the physical device, and the available basis gates. When executing the quantum circuit, we will provide the noise model, coupling_map, and basis gates. This way, when executing the quantum circuit on the simulator, it will simulate the results as experiencing the same effects that would occur when running the circuit on a real device, noise and all:
# Import the NoiseModel
from qiskit_aer.noise import NoiseModel
# Obtain an available backend to simulate
backend = service.get_backend('ibm_kyoto')
# Create the noise model based on the backend properties
noise_model = NoiseModel.from_backend(backend)
# Get coupling map from backend
coupling_map = backend.configuration().coupling_map
# Get basis gates from noise model
basis_gates = noise_model.basis_gates
# Get the Aer simulator to apply noise model
noisy_simulator = AerSimulator()
# Execute the circuit on the simulator with the backend
# properties, and generated noise model
result = noisy_simulator.run(transpiled_qc,
coupling_map=coupling_map,
basis_gates=basis_gates,
noise_model=noise_model).result()
# Obtain and print results
counts = result.get_counts()
plot_distribution(counts)
如您所见,前面代码结果的以下图表并不像之前那样理想。我们可以在这里观察到一些错误:

图 9.14:基于指定后端噪声效果的模拟器结果
现在我们能够模拟后端系统对模拟器产生的噪声效果,让我们来了解这些噪声效应的成因。
跟踪噪声来源
在实际设备上执行量子电路时,会有各种效应导致我们的计算出现错误。在本节中,我们将回顾这些效应中的一些,以便在您生成或构建噪声模型时,您将更好地理解它们如何影响每个量子比特。
理解退相干
退相干是指由于量子系统与其环境的物理相互作用而导致的量子相干性的丧失。退相干以多种方式影响每个量子位,其中之一是当每个量子位从
基态开始时,我们对量子位进行操作,将其从
状态移动到
状态。例如,我们说量子位已经从基态
转变到激发态
。对此的一个类比是想象你自己安静地、完美地静止坐着。这个平静的放松时刻就是你处于基态时的状态。
然后,想象一下有人突然从某个地方跳出来对你大喊!你的心率立刻上升,肾上腺素激增,你立刻感到震惊。这就是你现在处于兴奋状态的你。现在,在告诉那个吓到你的人不要再这样做之后,你设法喘过气来,将心率降下来。你开始放松,让你的身体回到之前那种稳定的地面状态。从兴奋状态到稳定状态所需的时间,巧合地被称为弛豫时间。当然,对我们人类来说,快速回到放松状态是理想的,但对于量子系统来说,理想的情况是尽可能长时间地保持设定的状态。这将确保我们放置在任意状态的量子位尽可能长时间地保持该状态。
弛豫时间,表示为 T[1],是信号强度纵向损失(沿 z 轴方向)的时间常数。另一种退相干效应是去相位,表示为 T[2],其中相位信息广泛扩散,以至于相位信息丢失。我们将在下一章中详细介绍信息丢失的细节。一个例子是,如果我们将量子位设置为
状态。去相位时间是一个衰减常数时间,其中初始状态衰减到
和
的混合状态,在这种情况下,很难预测系统的状态,并且系统并不完全处于叠加态,而是倾向于坍缩。
测量 T[1] 和 T[2] 的退相干时间有两种方法:
-
要测量 T[1],你需要施加一系列间隔固定时间延迟的脉冲,并捕捉状态从
到
变化时的统计结果。在施加不同长度或幅度的脉冲后,测量中出现的振荡被称为拉比振荡。 -
要测量 T[2],你需要将量子比特的状态设置为
或
,然后按照序列应用
脉冲以施加相位旋转。在经过一段时间应用特定的脉冲序列后,状态应该返回到其原始位置,即
或
。如果发生去相位,那么结果返回其原始起始位置的概率将降低。这种测量 T[2] 的技术被称为自旋回波。
现在我们对噪声的来源有了一些了解,让我们将讨论转向导致退相干的因素以及它们如何根据其来源而变化。通常有两种来源类型 – 内部和外部:
-
内部噪声通常被认为具有固有的性质,来源于系统内部,如温度或系统中的缺陷,因此基本上是材料或缺陷。
-
外部噪声来源于环境耦合系统,如波干涉、振动和电磁场。
让我们快速运行一个关于一对量子比特的热弛豫的例子,因为这通常与量子比特的本质相关。在下面的例子中,我们将以秒为单位定义我们的 T[1] 和 T[2] 值,并将它们应用于所有量子比特的基门集合。这些值可以设置为任何你希望的时间常数;后端属性中提供的时间值被声明为平均值,因此你的结果可能不会每次都相同。需要注意的是,你设置的参数必须对以下内容为真,
,否则将抛出错误。然后我们将运行一个包含这些热弛豫误差的示例电路,以查看差异。我们将创建并执行的电路将与我们之前创建并在模拟器上运行的贝尔态电路相同,因此我们可以比较结果:
# Initialize your T1 and T2 time constant values in seconds
t1 = 0.0125
t2 = 0.0025
# Apply the T1 and T2 to create the thermal relaxation error
from qiskit_aer.noise import thermal_relaxation_error
t_error = thermal_relaxation_error(t1, t2, 0.01)
# Add the errors to a noise model
# and apply to all basis gates on all qubits
noise_model = NoiseModel()
noise_model.add_all_qubit_quantum_error(t_error, ['id', 'rz', 'sx','u1', 'u2', 'u3'])
# Print out the noise model
print(noise_model)
#Create the same 2-qubit quantum circuit as before
qc_error = QuantumCircuit(2,2)
qc_error.h(0)
qc_error.cx(0,1)
qc_error.measure(range(2), range(2))
# Set the simulator
simulator = QasmSimulator()
# Transpile the circuit
transpiled_qc = transpile(qc_error, simulator)
# Apply the noise model we created and run the circuit
result = simulator.run(transpiled_qc, shots=1024, basis_gates=noise_model.basis_gates, noise_model=noise_model).result()
# Obtain result counts
counts = result.get_counts(transpiled_qc)
# Plot the result counts
plot_distribution(counts)
NoiseModel 输出通过指示哪些基门可用、哪些门指令会受到噪声的影响以及哪些基门错误应用于量子比特来描述噪声模型。请记住,结果可能会根据可用的后端系统而变化:
NoiseModel:
Basis gates: ['cx', 'id', 'rz', 'sx', 'u1', 'u2', 'u3']
Instructions with noise: ['sx', 'id', 'u1', 'u3', 'u2', 'rz']
All-qubits errors: ['id', 'rz', 'sx', 'u1', 'u2', 'u3']
正如你所看到的,在模拟器上执行此电路并生成噪声后的结果与之前并不完全相同。在早期的情况下,没有错误,我们在 00 和 11 之间有一个非常接近的 50/50 分割。然而,正如你在下面的屏幕截图中所看到的,结果是 00 和 11 之间大约是 75/25 的分割。这当然是由于我们添加到模拟器中的热弛豫误差,导致许多结果从激发态弛豫到基态,如下面的图表所示:

图 9.15:具有热弛豫误差的模拟器结果
T[1]和 T[2]都是作用于量子比特及其保持状态能力的环境效应。其他对系统整体噪声有贡献的效应是由操作量子比特的门引起的。现在让我们看看其中的一些。
理解单门、多门和读出错误
单门错误和多门错误通常是在量子比特被系统上的各种门操作时引入的。这些错误基于门应用于量子比特可能不会按预期精确操作的几率。例如,如果我们将 5%的门错误概率应用于单个量子比特门,如 NOT 门,那么操作的最终结果有 5%的概率不会得到预期的值。单门错误可能来自门保真度差,而多门错误可能来自相邻量子比特的串扰或旁观者噪声,或者来自量子比特之间的物理连接。Aer 库提供了一系列噪声模型方法供我们选择,包括Pauli 错误、去极化错误、幅度衰减错误等等。
单门和多门错误可以使用NoiseModel类中的add_all_qubit_quantum_error()方法一次性应用于所有量子比特。此方法将量子错误对象应用于指定基门的噪声模型,然后应用于所有量子比特。第一个参数是量子错误,第二个是应用错误的基门列表。
读出错误是在触发测量和获取以读取量子比特的值时发生的。在测量和获取量子比特信号的运算过程中,可能存在可能干扰量子比特测量结果的错误。《NoiseModel》类也提供了将读出错误添加到噪声模型的方法。
让我们在电路中构建自己的噪声模型,包括单量子比特、多量子比特和读出错误,以观察这些错误对我们量子电路的影响。
构建自己的噪声模型
有时候你可能希望构建自己的自定义噪声模型。无论是为了生成特定的错误来测试你的错误缓解方法,还是为了创建类似特定设备的东西,能够自定义自己的噪声模型是一个非常有用的功能。
在以下步骤中,我们将设置单量子比特和多量子比特错误并查看结果。然后我们将设置读出错误,并在同一电路中运行它们,只是为了可视化当将测量错误引入理想系统时结果之间的差异。单量子比特错误将有一个幅度衰减错误,多量子比特错误将有一个去极化错误,读出错误将应用于电路中的两个量子比特:
-
我们将首先定义我们的量子电路,并用模拟器进行编译:
# Create quantum circuit qc_error = QuantumCircuit(2,2)qc_error.h(0)qc_error.cx(0,1)qc_error.measure(range(2), range(2))# Let's get the qasm simulator simulator = QasmSimulator()# Transpile the circuit transpiled_qc = transpile(qc_error, simulator) -
接下来,我们将首先定义单比特和多比特概率错误值,然后初始化并设置去极化错误,首先是对单比特,然后是对多比特错误:
# Import the error classes and methods from qiskit_aer.noise import depolarizing_error from qiskit_aer.noise import ReadoutError # Single and multi-qubit probability error single_qubit_gate_p = 0.25 multi_qubit_gate_p = 0.1 # Apply the depolarizing quantum errors single_error = depolarizing_error(single_qubit_gate_p, 1) multi_error = depolarizing_error(multi_qubit_gate_p, 2) -
接下来,我们将创建我们的
NoiseModel对象,并添加单比特和多比特错误。单比特错误将被分配给基门u2,多比特错误将被分配给 CNOT(cx)门:# Add the single and multi-qubit errors to the noise # model noise_model = NoiseModel() noise_model.add_all_qubit_quantum_error(single_error, ['u2']) noise_model.add_all_qubit_quantum_error(multi_error, ['cx']) # Print out the noise model print(noise_model) -
现在,我们将打印出
NoiseModel以确认我们的噪声模型条目已设置。从噪声模型的输出中我们可以看到,我们有所有可用基门的列表,有分配了噪声的指令列表,以及将影响我们电路中所有量子比特的所有基态列表:NoiseModel: Basis gates: ['cx', 'id', 'rz', 'sx', 'u2'] Instructions with noise: ['u2', 'cx'] All-qubits errors: ['u2', 'cx'] -
现在,让我们将这个错误添加到我们的模拟器中,并使用单比特和多比特噪声模型运行它:
# Run the circuit on the simulator with the noise model result = simulator.run(transpiled_qc, shots=1024, basis_gates=noise_model.basis_gates, noise_model=noise_model).result() # Obtain the counts and plot the results counts = result.get_counts(transpiled_qc)plot_distribution(counts) -
这将导致以下直方图:

图 9.16:具有单比特和多比特错误的模拟器结果
-
如您所见,我们引入的错误对我们的结果产生了影响,我们现在得到了一些意想不到的值,01 和 10,这是我们之前在没有噪声的系统上运行理想情况时没有预期或看到的。然而,即使有噪声,我们也可以看到我们的结果是正确的,因为我们仍然有高概率的状态 00 和 11。所以在这里,噪声是最小的,这样我们就可以得到一些好的结果。让我们尝试不同的错误并增加噪声,看看当我们有非常嘈杂的读出错误时我们会得到什么。
-
接下来,让我们包括一些读出错误。读出错误在 Aer API 文档中的定义如下:
经典读出错误由一个赋值概率向量列表 P(A|B)指定,其中 A 是记录的经典比特值,B 是从测量中返回的真实比特值。
这意味着预期值的概率将被记录并用于根据我们传递给噪声模型的概率值应用读出错误。
单比特读出概率向量的方程定义如下:

在构建ReadoutError类时,P(A|B)作为参数提供。对于我们的示例,我们将提供给定 1 的概率为0.7,给定 0 的概率为0.2。我们还将把我们的读出错误添加到噪声模型中,并打印出结果,如下面的代码所示:
注意,当然,我们设置的这些值非常高,并不一定需要加起来等于 1。我正在使用它们来强调噪声在这种情况下对结果的影响,以一种非常夸张但视觉上可识别的方式,以证明错误实际上是将结果从预期值拉向偏差值。
# Set the readout error probabilities for 0 given 1,
# & 1 given 0,
p0_1 = 0.7
p1_0 = 0.2
p0 = 1 – p1_0
p1 = 1 - p1_1 # Construct a noise model
noise_model = NoiseModel()
# Construct the ReadoutError with the probabilities
readout_error = ReadoutError([[p0, p1_0], [p0_1, p1]])
# Apply the readout error to all qubits
noise_model.add_all_qubit_readout_error(readout_error)
# Print the noise model
print(noise_model)
我们将在结果中看到一些指令和量子比特的列表。第一行指定了基本门,下一行是添加了带噪声的指令的列表。请注意,现在它只包括测量指令。接下来,我们看到指定了特定噪声的量子比特——在这种情况下,我们向所有量子比特的测量算子添加了读出错误:
NoiseModel:
Basis gates: ['cx', 'id', 'rz', 'sx']
Instructions with noise: ['measure']
All-qubits errors: ['measure']
现在我们已经完成了读出噪声模型,我们将添加我们的自定义噪声模型,并在 Aer 模拟器上运行它以查看结果。
与之前包含热弛豫噪声模型的示例类似,我们将以同样的方式提供读出噪声模型:
# Run the circuit with the readout error noise model
result = simulator.run(transpiled_qc, shots=1024, basis_gates=noise_model.basis_gates, noise_model=noise_model).result()
# Obtain the result counts and print
counts = result.get_counts(transpiled_qc)
plot_distribution(counts)
如您在以下图表中可以看到的结果,现在并不像以前那样理想:

图 9.17:量子电路中自定义噪声模型的效果结果
我们可以观察到各种由读出噪声引起的错误。首先,我们的预期结果00和11不再容易从我们的结果中看出。从这个意义上说,我们看到每个量子比特的读出在给定 1 的情况下有更高的 0 的概率,因此导致00、01和10的概率结果更高。这是由于应用于所有量子比特的读出错误造成的,这大大降低了 1 的概率。
所有这些噪声的优势在于,你可以根据我们包含的噪声类型以及应用于指定量子比特(s)的噪声量,对噪声的成因有一个洞察。这允许你在希望研究一些噪声缓解技术时模拟某些噪声效果。
通过应用噪声并理解其影响,你可以创建噪声缓解技术,并在模拟器上验证结果。通过这样做,你可以测试各种噪声效果的组合,这有助于降低某些算法的错误率,从而提高量子计算机的性能。我们将在下一章中探讨噪声缓解技术。
摘要
在本章中,我们介绍了各种模拟器。你现在拥有了利用各种模拟器在量子计算机上模拟运行电路并从电路中获取特定内容(如状态向量和幺正矩阵)的技能。
我们还介绍了各种可视化技术。你获得的能力将帮助你可视化来自模拟器的各种信息,例如使用 qsphere 可视化量子比特的状态和相位信息,以及绘制状态向量图。
最后,我们探讨了 Qiskit 提供的噪声模型,无论是从现有的量子计算机中提取噪声,还是创建我们自己的噪声模型并将其应用于模拟器。
在下一章中,我们将学习如何表征和减轻噪声。这将使我们能够优化量子计算机的性能并提高其计算能力。
问题
-
你能列出 Qiskit Aer 模块中找到的所有模拟器吗?
-
在负 y 轴上创建一个量子比特的 qsphere 表示,创建状态
,仅使用一个哈达德门和相位门。 -
当在一个电路中初始化一组量子比特时,所有状态的总概率必须是多少?
-
你能使用 qsphere 可视化量子比特的相位和概率信息吗?
-
如果你将去极化错误值设置得接近 1,会发生什么?
-
如果你将读出错误均匀地应用于所有量子比特,你期望得到什么结果,为什么?
加入我们的 Discord
加入我们社区的 Discord 空间,与作者和其他读者进行讨论:
packt.link/3FyN1

第十章:抑制和减轻量子噪声
在本书的前一版中,你可能还记得一个章节,该章节详细讨论了影响各种量子系统的不同类型的噪声。从那时起,有大量的研究不仅发展了硬件,也发展了软件。由于那时以来的变化,包括 Qiskit 的一些主要重构迭代,例如 Ignis 及其许多测试电路库的弃用,我认为现在是时候转向最新的内容,而不是花太多时间讨论如何测试系统,而是理解如何利用最新的错误抑制和减轻技术。当然,我不想让你太迷茫,所以我将涵盖一些关于这些噪声是什么以及它们如何影响你的实验的基本知识。然而,我确实想确保我们涵盖当前量子实用性的时代(www.nature.com/articles/s41586-023-06096-3),这包括使用错误抑制和减轻技术来帮助你找到一些有用的量子应用。我们应该期待随着系统的演变和扩展(更多的量子位)以及深化(具有数千个 2 量子位门的复杂量子电路),我们将看到一些进步,这可以被视为朝着无需等待容错量子计算机就能实现有用量子应用迈出的巨大一步。
早期的量子系统通常被称为近端设备,这通常意味着它们接近成为完全容错的量子系统。其中一个原因是,所有当前的量子系统,无论它们使用什么技术来创建量子位,都受到某种形式的噪声的影响,这增加了这些系统的错误率。为了解决这个问题,并相应地最小化错误率,了解这些错误的原因以及我们如何抑制或减轻它们是有帮助的。请记住,对以下任何一个示例中的错误的研究都很容易成为一个研究课题,这就是为什么我们只是简单触及一些示例,如果你对更多细节感兴趣,我将在本章末尾提供一些参考资料。目前,本章仅是一个概述,包含一些示例,帮助你了解如何使用 Qiskit 提供的功能来创建一些噪声模型,以及如何使用 Qiskit Runtime 中的功能来抑制和减轻它们。这里的目的是帮助你优化量子电路的有效性,从而帮助你设计尽可能抵抗各种形式错误的应用程序。
本章将涵盖以下主题:
-
理解 Qiskit Runtime 服务
-
理解会话
-
理解 Qiskit Runtime 选项
-
理解原语
-
理解 Sampler 原语
-
理解退相干的噪声效应
-
错误抑制、减轻和纠正之间的区别
在本章中,我们将介绍大多数量子系统面临的一个挑战:噪声。到本章结束时,你将了解一些不同的噪声效应,例如弛豫和去相位。然后,你将概述 Qiskit 运行时服务的最新进展,并了解在任意后端系统上高效运行电路的构建块。最后,你将了解错误抑制和错误减轻技术,以及如何将它们应用于你的复杂量子电路。
在 Qiskit 的早期版本中,有一个名为 Ignis 的模块,该模块包含用于表征和减轻噪声的库。从那时起,Ignis 库已被弃用,因此建议你阅读 Qiskit 迁移指南,位于 Qiskit GitHub 页面(github.com/Qiskit/qiskit-ignis#migration-guide),特别是如果你想使用许多最新的进展。
在量子系统中,噪声来源于各种来源:电子的热量、退相干、去相位、串扰或信号损失。在这里,我们将了解如何测量噪声对量子比特的影响,以及如何减轻读出误差噪声以优化我们的结果。最后,我们将比较差异,以更好地理解其影响以及使用其他技术(如动态去耦)来减轻它们的方法,动态去耦用于帮助减少量子比特状态闲置时间过长时由于退相干引起的噪声。
在我们开始所有这些之前,我们首先需要熟悉将帮助我们将所有这些技术带到指尖并允许我们根据需要调整它们的最新的一个特性:Qiskit 运行时服务。
技术要求
在本章中,我们将回顾一些关于模拟噪声的内容,所以如果你对信号与噪声理论有所了解,这将有所帮助。如果没有,请回顾第九章,模拟量子系统和噪声模型,以了解影响量子系统的各种噪声形式。这将帮助你了解如何使用 Qiskit 运行时在量子计算机上抑制和减轻错误。
这里是本书中使用的完整源代码:github.com/PacktPublishing/Learning-Quantum-Computing-with-Python-and-IBM-Quantum-Second-Edition。
理解 Qiskit 运行时服务
对于那些使用过 Qiskit 早期版本的人来说,你们可能使用过execute()或backend.run()函数来在量子系统上运行量子电路。这对于在小于 100 个量子比特的小型量子系统上运行一些基本的量子电路用于学习目的来说是很好的。然而,如果我们想要开始考虑未来,以及我们如何为数百、数千甚至数百万个量子比特创建电路,那么我们需要考虑如何最优地做到这一点,而不仅仅是将一个大的电路扔到一台机器上。这就是Qiskit Runtime 服务大有用武之地的地方。它不仅包括许多新选项,例如选择优化和弹性级别,我们将在本章后面学习这些内容,而且还包括我们之前覆盖的所有编译功能,所以我们不会失去我们迄今为止所学到的。在本节中,我们将介绍 Qiskit Runtime 是什么以及如何使用它来执行你的电路。了解如何使用 Qiskit Runtime 也将帮助你在此章后面学习各种错误抑制和缓解技术以及它们是如何应用于你的量子电路的。
让我们先来了解 Qiskit Runtime 服务,它与执行函数有何不同,以及我们可以使用哪些新功能来优化在量子系统上执行量子电路。
首先,让我们创建一个新的笔记本,并导入一些 Qiskit Runtime 对象、函数,并使用我们的辅助文件实例化QiskitRuntimeService类。
注意,在撰写本文时,已经从当前的 Sampler 版本切换到 SamplerV2。在下面的代码中,假设“V2”将被移除。如果由于某些原因它没有被移除,那么请将Sampler更新为SamplerV2以确保你使用的是 Sampler 的最新版本。
另一个重要的步骤是,你必须使用token参数来设置你的 IBM Quantum 或 IBM Cloud API 令牌,如下面的代码片段所示。如果你不设置这个参数,你可能会遇到错误,并且当尝试访问 Qiskit Runtime 服务时,它将无法工作。
# Loading your IBM Quantum account(s)
service = QiskitRuntimeService(channel="ibm_quantum", token="API_TOKEN")
现在你已经设置了你的笔记本,让我们深入探讨一些执行模式描述。
理解会话
会话本质上是一组作业,保证在后台运行而不会受到其他用户作业的干扰,尤其是那些需要多次迭代的作业,例如基于变分算法的作业:变分量子本征求解器(VQE)和量子近似优化算法(QAOA)。换句话说,“无干扰”意味着 Qiskit Runtime 将确保每个作业都能完成,包括在经典资源和量子资源之间调整变量的期间,在过去,这段时间被用来在队列中交错处理来自另一个用户的作业。
为了理解这种格式的实用性,想象一下站在一个长长的队伍中等待轮到您提交表格,比如更新您的驾驶执照。等了几小时后,您终于到了窗口,递上您的表格,却被告知需要调整表格中的某些值。这就是差异所在。在之前的版本中,您必须站在一旁更新表格字段;同时,另一个人会走到窗口前。现在,如果您恰好准备好了,您现在必须等待那个人完成,然后才能继续。当然,这可能会花费很长时间,让您等待更久。会话允许您在完成更新表格并提交之前,保持窗口空闲并可供您使用。
关于交互式和最大时间值的默认值和最大时间,我建议您参考 Qiskit 运行时文档docs.quantum.ibm.com/api/qiskit-ibm-runtime/runtime_service,因为这些值可能会随时间而变化。然而,队列中的时间会计算到会话的最大时间之内。空闲时间可以用来根据作业的结果执行任何经典操作,并为会话中的后续作业做准备。
每个作业都可以设置为作业批次的一部分,以确保它们在相同的设备上或紧密地一起运行,以避免它们在单独的系统或相隔很远时运行可能引起的问题,这可能会由于设备特性或设备漂移而使我们的结果出现异常。要了解更多关于设备漂移的信息,请参阅本章末尾的进一步阅读部分。在撰写本文时,有一些即将推出的功能将进一步优化电路的执行,因此请密切关注文档和信息源以获取详细信息示例。
在我们开始编码之前,让我们先熟悉其他类,这样将简化我们后续的编码体验。在下一节中,我们将查看可以设置到Session类的选项。Options类在抑制和缓解错误时也非常重要。
理解 Qiskit 运行时选项
RuntimeOptions是一个用于设置 Qiskit 运行时执行选项的各种参数的类。参数用于选择使用哪个量子系统以及使用哪种优化或弹性级别,这可以启用各种错误抑制或错误缓解功能。以下是用于各种 Qiskit 运行时原语的一些Options参数列表。请记住,就像往常一样,您应该检查 Qiskit API 文档的最新版本,以确保您的代码始终保持最新:
-
环境参数,如
log_level(DEBUG、INFO等),以及用于任何中间或最终结果的callback,它将接收两个位置参数:作业 ID 和作业结果 -
执行参数用于执行时间选项,如
shots(int)(整数)和init_qubits(bool)(布尔值),这将重置每个射击的量子位到基态(默认为true) -
max_execution_time是作业取消的最大时间,以秒为单位。如果没有设置,则默认为原始的时限。如果设置了,则时间必须在 300 秒和模拟器或设备设置的最大的执行时间之间,这可以在 Qiskit API 文档中找到:docs.quantum.ibm.com/guides/max-execution-time。 -
optimization_level设置电路的优化级别。级别越高,优化程度越高,因此编译时间越长。这些优化级别还包括动态去耦等错误抑制,我们将在本章后面介绍。有四个optimization_level设置。默认值设置为最高值3:-
0 – 无优化,使用基本翻译,使用指定的任何布局,并路由(默认使用随机交换)
-
1 – 轻度优化,路由(使用 SabreSWAP、1Q 门优化和动态去耦以抑制错误)
-
2 – 中度优化,布局和路由与第 1 级相同,但包括具有更大搜索深度和优化及动态去耦尝试的启发式优化
-
3 – 高度优化,布局和路由与第 2 级相同,并包括更大努力/尝试的进一步优化,2-量子比特 KAK 优化和动态去耦以抑制错误
-
-
resilience_level设置缓解错误的恢复级别。恢复级别越高,结果精度越高。更高的结果类似于优化级别,这将导致更长的编译时间。这些恢复级别从 0 到 2,其中级别 1 是默认值,并且仅适用于估计原始。重要的是要注意,随着技术的进步,这些技术的实现和缓解措施也在不断发展。请参考最新的文档,以确保您的代码正在运行最新版本 (docs.quantum.ibm.com/guides/configure-error-mitigation)。-
0 – 无缓解。
-
1 – (默认)最小缓解,最小化读出错误。估计原始使用 Twirled Readout Error Extinction。
-
2 – 中度缓解,用于最小化电路中的偏差,但表示不能保证为零,使用与级别 1 相同的方法,并包括零噪声外推。
-
现在,我们已经熟悉了可以在我们的后端系统上设置以优化和运行电路的所有选项,让我们继续前进,了解原始类是什么。
理解原始类
原语,如 Qiskit 文档中定义的,通常是“设计和优化量子工作负载的基础构建块。”在撰写本文时,Qiskit Runtime 中有两个原语:采样器和估计器。每个都提供了一种简单的方式来封装你的电路,并利用其每个功能在执行过程中优化工作负载,以便在多个量子系统上大规模运行。如果你还记得在前面章节中,当我们构建并应用优化级别到我们的电路时,这涉及到相当多的工作,并且优化是基于我们选择的单个量子系统来运行的。你可以想象,如果我们需要为每个执行我们电路的不同系统做这件事,需要多少步骤和开销。通过原语提供的接口,很多工作都由我们通过应用一些选项设置和选择来处理。然后,运行时将相应地应用这些设置,因为它选择多个系统来执行电路。每个原语执行特定的任务,并作为 Qiskit Runtime 服务的入口点。让我们逐一查看每个原语,以了解它们的功能以及它们之间的差异。在本章中,我们将重点关注采样器原语。IBM 量子学习平台有一些非常好的示例、课程和教程,你可以从中学习到比我在本章中提供的更详细的内容。
理解采样器原语
采样器原语与我们在这本书中使用的类似,它以量子电路作为输入并生成一个准概率结果。这个结果也被误差缓解,以确保结果尽可能精确。采样器原语电路的例子包括 Grover 搜索和 Deutsch-Jozsa。采样器可以被修改以允许更改,例如后端(本地模拟器或量子系统),这简化了你的电路管理以及它们在根据实验需求的后端上运行的方式。这非常有帮助,因为每个原语在量子系统上执行时都有自己操作量子电路的方式,因此某些属性或缓解技术可能不会在某些原语上工作,这取决于它们执行任务的方式。
好吧,到目前为止,我们已经阅读了很多内容,为什么不回到一些编码上来呢?让我们创建一个新的 Qiskit 笔记本,并使用采样器运行一个简单的电路来尝试一下。我们还将包括前面提到的类,这样我们就可以将所有内容封装在一个简单的电路结构中。
首先,我们将创建一个简单的电路:
# Create a simple circuit:
from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister,
transpile
from qiskit.visualization import plot_distribution
q_reg = QuantumRegister(4, name='qr')
c_reg = ClassicalRegister(4, name='cr')
qc = QuantumCircuit(q_reg, c_reg)
qc.h(0)
qc.cx(0, 1)
qc.cx(1, 2)
qc.cx(2, 3)
qc.draw(output="mpl")
这将显示我们希望运行的以下电路:

图 10.1:要在系统上运行的简单电路
现在,从这里开始就变得有趣了!让我们首先遵循 Qiskit 模式,这样我们不仅确保我们的电路是高效的,而且我们的开发模式也是高效的。
Qiskit 模式是一个 4 步过程,我们可以使用它来帮助我们集成我们的量子计算电路到更大的应用层。这 4 个步骤是映射、优化、执行和后处理。我们将逐一介绍这些步骤,以便您熟悉在量子系统上以最佳方式运行电路。我们将从我们刚刚做的事情开始:将我们的问题映射到量子电路。
第 1 步:映射
将问题映射到量子电路可以有多种方式。我们可能可以写出大量的书籍来专门讨论这一点。这是因为这不仅取决于我们试图解决的问题,还取决于我们如何将我们的输入数据和问题解决方案最佳地映射到量子状态或初始状态。这类似于规划一次旅行。如果我要请你帮我规划一次去纽约的团队旅行,可能会有很多选择,甚至更多关于如何到达那里的问题?大家会在哪里?他们是否在旅行的开始时都在同一个地点。他们会有多长时间可用?这些问题是我们需要考虑的。我们如何将数据传输到量子系统中?在那个时刻,所有数据在哪里,数据会持续多久?一旦我们获得了输入数据,下一步就是将其从经典信息转换为量子状态或电路的输入。现在让我们用一个简单的例子来做这件事,即本章前面构建的电路。在第十三章中,我们将讨论一个更复杂的问题,即 Grover 搜索。现在,为了让我们专注于整个过程,我们将继续使用简单的电路和一些随机电路。
到目前为止,我们已经将问题映射到了量子状态,在这种情况下是上面构建的电路。这是第一步,映射,已完成。
现在让我们使用Statevector类来运行这个电路,在量子系统上运行之前获取预期的结果。由于这是一个小型且简单的量子电路,我们可以在一个经典系统上轻松模拟,比如你在家或办公室里的系统。如果电路或问题涉及大量比特,你无法在经典系统上运行,那么首选的方法是将问题规模减小到可以在经典系统上运行的大小,这样你可以在较小的规模上确认预期的结果,然后运行在量子系统上以获得全面的结果。
from qiskit.quantum_info import Statevector
# Pass the circuit instructions into the Statevector
results = Statevector.from_instruction(qc).probabilities_dict()
plot_distribution(results)
这将在以下图中显示预期的结果:

图 10.2:使用 Statevector 从电路中得到的预期概率结果
接下来,我们可以准备在真实后端上运行。为此,我们需要在我们的电路中包含测量算子,我们将这样做,然后获取最不繁忙的后端系统来运行这个电路:
# Add measurements to our circuit
qc.measure_all(add_bits=False)
# Select the least busy backend system to run circuit
backend = service.least_busy(simulator=False, operational=True)
print("Least busy backend: ", backend)
这将打印出所选的后端,这是在调用时最不繁忙的后端。
第 2 步:优化
现在,我们可以通过相应的转译来准备电路在后台运行。为此,我们将使用新的preset_passmanager函数,这是 Qiskit transpiler库的新增功能,允许您设置、运行和绘制电路转译后的结果。将电路与目标后端对齐是理想的,因为它将通过应用不仅通用的传递,还包括引入进一步改进的 AI 生成优化器来优化电路到所选的后端。
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
target = backend.target
pm = generate_preset_pass_manager(target=target, optimization_level=3)
transpiled_qc = pm.run(qc)
transpiled_qc.draw(output="mpl", idle_wires=False, style="iqp")
这显示了针对所选后端目标的特定转译电路。在这里,您将注意到与原始电路的两个不同之处。首先,请注意量子比特映射已更改;量子比特 0 现在分配给量子比特 73。在您的情况下,这取决于您选择了哪个系统,它可能指向其他量子比特。接下来,您还会注意到添加了更多的门。这是由于必须使用基门和量子比特之间的连接性(使用 ECR 门:docs.quantum.ibm.com/api/qiskit/qiskit.circuit.library.ECRGate)来连接可能不相邻的量子比特。结果可能不同,因为您所选的后端可能得到不同的结果。

图 10.3:转译电路
现在我们知道了如何转译电路并设置优化级别值,让我们现在切换到一个更复杂的电路,这样我们就可以进一步推动优化器,并看到结果电路之间的差异。我们将使用一个简单的 Grover 算子来生成电路。由于我们还没有详细讨论 Grover 算法,让我们先简单地说,我们将使用它来标记电路要找到的状态。在这种情况下,我们将使用二进制表示110。Grover 算法将在第十三章中讨论。
from qiskit.circuit.library import GroverOperator
oracle = Statevector.from_label("110")
grover_qc = QuantumCircuit(3)
grover_qc.h(range(3))
grover_qc = grover_qc.compose(GroverOperator(oracle))
grover_qc.draw(output="mpl")
这将产生以下电路,其中我们有一系列 Hadamard 门,后面跟着 Grover 算子复合体。请注意,这是一个复合体,它将一系列门表示为单个名为 Q 的块,以简化电路的可视化。您可以通过多次运行decompose方法将其分解为基门。
在接下来的步骤中,我们将使用不同的优化级别值进行转译,并比较这两种方法,以便您可以看到转译器优化器对电路的影响。

图 10.4:Grover 算子电路
下面的内容将突出显示优化级别之间的差异,以及许多电路伴随的复杂性。为了更具体地进行比较,我们将观察ECR门的数量,这是Echoed Cross-Resonance门的缩写。它们类似于 CX 门,但有一个回声过程,可以减少一些不希望的项。关于这个门的具体信息可以在 Qiskit 电路库中找到。ECR 门或任何多源/多目标门相当复杂,因为它们需要额外的门来生成连接性。在这个例子中,我们将使用 ECR 门的数量来比较电路。
让我们首先从当前电路中捕获Statevector结果,这样我们就可以将它们与转换后在量子系统上运行的结果进行比较:
results = Statevector.from_instruction(grover_qc).probabilities_dict()
接下来,我们将向这个电路添加测量门并创建多个副本,每个副本具有不同的optimization_level值以进行比较:
grover_qc.measure_all()
transpiled_grover_qc = pm.run(grover_qc)
transpiled_grover_qc.draw(output="mpl", idle_wires=False, style="iqp")
这里得到的结果是我们创建的原始 Grover 电路的转换版本。我在下面的图中包括了它的截断部分:

图 10.5:转换后的 Grover 算子电路(截断)
如您所见,它要复杂得多,并且包含相当多的额外门,包括 ECR 门。
现在,让我们遍历电路并为每个分配不同的optimization_level值。在这种情况下,我们将比较极端的水平,一个在 0,也就是说完全没有优化,另一个在 3,即完全优化。然后我们将它添加到circuits数组中,稍后在量子系统上运行。
circuits=[]
for optimization_level in [0,3]:
transpiled_grover_qc = transpile(grover_qc, backend, optimization_level=optimization_level, seed_transpiler=1000)
print(f"ECR (Optimization level={optimization_level}): ", transpiled_grover_qc.count_ops()["ecr"])
circuits.append(transpiled_grover_qc)
这将显示每个电路的 ECR 门数量。注意,与没有优化(级别 0)和完全优化(级别 3)相比,两个电路(它们将根据系统而变化)之间的差异相当大。
ECRs (optimization_level=0): 24
ECRs (optimization_level=3): 14
现在我们已经通过将电路转换为具有特定优化级别的设备来优化了我们的电路,现在让我们运行这两个电路,并在实际的量子系统上比较结果,包括模拟结果。
这完成了优化步骤,我们接下来进行下一个 Qiskit 模式,执行优化后的电路。
第 3 步:执行
在这里,我们现在将在目标后端上运行我们的电路,但这次有一些变化。我们将使用 Qiskit Runtime 的新功能 Batch。这将允许我们提供一批操作在量子系统上运行。
要执行我们创建的电路,我们首先需要创建一个批次,这是 Qiskit Runtime(docs.quantum.ibm.com/api/qiskit-ibm-runtime/qiskit_ibm_runtime.Batch)的执行模式之一,首先将我们的电路和后端信息加载到批次中,然后在批次内部,我们将在后端上运行 Sampler 原语并打印结果。
with Batch(service=service, backend=backend):
sampler = Sampler()
job = sampler.run(
circuits=circuits,
skip_transpilation=True,
shots=8000
)
result = job.result()
这可能需要一段时间,具体取决于你在队列中的位置。但一旦完成,你应该看到实验的以下结果,包括准概率结果和实验的元数据,在这种情况下是获取结果所使用的射击次数。
SamplerResult(quasi_dists=[{0: 0.136596417234717, 1: 0.057482039100449, 2: 0.03807773583055, 3: 0.051080549862785, 4: 0.028575374054239, 5: 0.068434342226471, 6: 0.540689745421478, 7: 0.079063796269311}, {0:
0.013241830253957, 1: 0.044991185036454, 2: 0.066493715476242, 3: 0.033981414166734, 4: 0.077452855226391, 5: 0.045273457404358, 6: 0.654480551636603, 7: 0.064084990799261}], metadata=[{'shots': 8000, 'circuit_metadata': {}, 'readout_mitigation_overhead': 1.2784314899265776, 'readout_mitigation_time': 0.03348202304914594}, {'shots': 8000, 'circuit_metadata': {}, 'readout_mitigation_overhead': 1.0800027370262115, 'readout_mitigation_time': 0.0965154911391437}])
让我们更深入地看看结果。首先,你会看到一组quasi_dists。这是我们实验中准分布的结果。由于我们运行了一个 3 量子比特电路,我们应该看到 8 种可能的结果,0-7,以及每个结果的准概率。然后是一些关于每个电路读出开销的详细信息,这仅仅是读取每个电路结果所需的时间。
这现在帮助我们进入 Qiskit 模式的最后一步,后处理。
第 4 步:后处理
在 Qiskit 模式的最后一步,我们将从量子系统中提取结果并进行一些后处理。后处理究竟是什么?简单来说,就是你想让它成为的样子。你可以外推结果并将其返回到你的经典应用程序中的下一个任务,以继续特定的混合经典-量子工作流程,或者你可以简单地提供结果作为另一个经典或量子计算任务的输入并继续。在这种情况下,我们将保持简单,只以可视化的方式显示结果以进行比较。我们将比较我们之前计算的状态向量结果,并将它们与我们在量子系统上运行的两个电路的结果进行比较。以下将结果显示在直方图上:
qc_results = [quasi_dist.binary_probabilities() for quasi_dist in result.quasi_dists]
plot_histogram(
qc_results + [results],
legend=[
"optimization_level=0",
"optimization_level=3",
"Simulated results"
],
bar_labels=False
)
结果图将如下所示:

图 10.6:后处理结果,可视化比较
在这里,你可以看到我们的结果是预期的,其中在更高层次优化(3)的结果更接近我们的模拟结果,而没有进行任何优化的电路的结果则不太准确。
这就引出了我们需要讨论的下一个主题:噪声。所有量子系统,无论技术如何,都存在某种形式的噪声或与噪声相关的效应。有些技术可以将量子比特与任何环境噪声隔离;然而,这也带来一个缺点,即当我们想要改变量子比特的状态时,速度和保真度可能比其他技术慢。在这本书中,由于我们使用 IBM 量子系统,我们将重点关注超导量子比特以及噪声对它们的影响。当然,我们还将涵盖错误抑制和错误缓解的最新进展,这些进展随着时间的推移可能会为我们提供有用的量子应用。在下一节中,我们将首先了解不同类型噪声之间的差异以及它们如何影响量子系统。
理解退相干噪声效应
在第九章,模拟量子系统和噪声模型中,我们了解到我们可以生成基于指定量子计算机配置的各种噪声模型。在提取配置信息后,我们可以将一系列错误函数中的任何一个应用到本地模拟器上,这将重现类似于从量子计算机获得的类似错误效应。
在本节中,我们将进一步探讨这些错误如何随时间影响我们的电路。我们将回顾的两个效应是近期量子系统中发现的两个最常见问题:弛豫和失相。这些是关键错误,因为它们可以影响量子状态信息,从而导致错误响应。
在本章的后面部分,我们还将探讨读出错误,这是噪声的另一个常见来源,它发生在系统应用测量脉冲的同时,并行地监听采集通道。从模拟脉冲到数字值(要么是 0 要么是 1)的转换也可能引入许多错误,我们将尝试减轻这些错误。
我们将首先了解量子系统中噪声最常见和最重要的效应之一:去相干。去相干主要有三种类型:T1、T2和T2。每一种都代表了对量子比特的去相干效应。我们将逐一研究它们,以了解它们之间的差异,以及如何在真实量子设备上运行时抑制和减轻这些效应。
如果您对学习如何分析弛豫(T1)和失相(T2/T2)的效果有浓厚的兴趣,第九章,模拟量子系统和噪声模型展示了创建包含复制每种三种去相干效应的噪声模型的电路的方法。这将帮助您运行实验以分析设备的表征。
下一个部分将简要概述这两者,以便让您了解在讨论缓解功能时,哪一项受到影响。
理解去相干错误
T[1],正如我们在第九章,模拟量子系统和噪声模型中提到的,通常被称为弛豫时间。弛豫时间指的是量子比特的能量从激发态(
)衰减回其基态(
)所需的时间,如下面的图表所示,其中
表示概率为1,
是概率为0。T[1]时间定义为P(t) = 1/e时的值(参见图表):

图 10.7:T1 定义为能量状态达到 1/e 的概率衰减时间
当将此应用于量子系统时,如果你想要确定任何量子比特达到 T[1] 所需的时间量,你需要创建一个测试电路,将量子比特置于激发态,
。我们知道如何通过简单地对一个量子比特应用 X 门,然后等待一段时间再测量该量子比特来实现这一点。如果你要在一定数量的时间间隔内这样做,你可能会开始看到结果从激发态跃迁到基态。
那么,这一切意味着什么呢?让我们深入探讨一下,看看这如何应用于随着时间的推移和复杂电路上的量子系统。
我们将从简单的类比开始,为你提供一些直观的概念理解。对于那些已经了解这些概念的人来说,请自由地跳到下一节。
想象一下,就在新年除夕前后,或者不久之后,我们大多数人包括我自己,每年都会做出同样的承诺,那就是去健身房锻炼,恢复体型,尤其是在假期享受盛宴之后。然后,通常在一月的第一周或第二周,我们会去健身房,我们的私人教练直接带我们去举重,并要求我们选择一个重量。当然,一开始可能会觉得有点重,但我们还是鼓起勇气。然后,我们的教练想要测试我们的力量,并要求我们用伸直的胳膊将重量高举过头顶。然后我们自信地将重量举过头顶,我们感到兴奋,这并不太糟糕。让我们称我们能够将重量举过头顶的能力为激发态。好,现在假设我们的教练要求我们尽可能长时间地保持这个姿势。我们会注意到,我们的力量只能维持这个姿势一段时间。然后,我们会开始感觉到肌肉的疲劳,这会慢慢地,或者在我的情况下,比较快地开始降低到原始位置,即重量靠近地面的位置,在那里我不再感觉到肌肉的紧张。让我们称这个位置为放松状态,基态。
量子比特在很大程度上遵循这个概念,它将从基态开始,然后我们将在一段时间后从基态放大量子比特到某个激发态。现在,就像我们一样,随着时间的推移,我们将回到基态以放松。这种从激发态到基态的放松,从逻辑上讲,被称为振幅弛豫,这可能听起来很熟悉,因为我们之前在创建振幅衰减噪声模型来表示退相干时学过这个概念。
回顾图 10.8,你可以想象这就是环境效应(T[1])对量子比特的影响。我们所说的环境是什么意思?我很高兴你问了这个问题!量子比特不仅仅是一个带有约瑟夫森结的单个铝铌组件。
哦不,不,不,这里还有更多的事情在发生!当然,根据量子比特所依赖的技术,如超导、离子阱、光子学等,量子比特并不是孤立的。它通常被各种环境变量所包围,这些变量可能会干扰量子比特,有时甚至能加速其退相干。例如,超导量子比特必须处于一定的温度下,大约 15 毫开尔文,才能正常工作并确保基态和第一激发态之间的能级精确,以确定量子比特的状态。然后,发送给量子比特的脉冲必须具有正确的幅度、持续时间、频率等,以便将量子比特置于正确的状态。记住,这可能很困难,因为量子系统可能非常复杂。例如,我们在前面的例子中定义一个幅度值作为激发态,只是比之前的位置高一点:将重物举过头顶。但如果我们需要多个幅度来编码我们的数据呢?那么我们门控的精度就需要非常精确,这意味着门控的保真度也可能影响我们的系统。这就是退相干,也称为弛豫率或 T[1],如果不被抑制,会导致我们失去电路的量子状态信息,从而导致我们在读取电路结果时出现错误。
理解去相干错误
T[1],我们在上一节中了解到,是指量子比特随时间衰减时引起的退相干或弛豫率。另一方面,去相干错误发生在量子比特的相位变得模糊时,通常被称为 T[2]或 T[2]*。去相干与退相干相似,我们也会随着时间的推移失去状态信息,只是方式略有不同。
简单来说,定义去相干就是失去量子比特的相位信息。
去相干噪声有许多不同的来源,每个来源都有不同的特性。一些例子包括:
-
白噪声,当信号在变化频率上具有相等强度且数量相等时。
-
粉红噪声,通常被称为闪烁噪声,通常出现在电子设备中,其中功率密度随着频率的增加而减少,即 1/f,其中 f 是频率。
-
磁通噪声,出现在超导量子干涉器件(SQUID)的表面,通常被称为SQUID,其来源是 SQUID 表面的磁自旋。
虽然不能将这本书从开发者手册转变为工程手册,但如果您想了解更多关于这些内容,我将在本章末尾提供一些参考。
现在我们对噪声有了基本的了解,让我们学习一下错误抑制、错误缓解和错误纠正之间的区别,以及我们如何使用它们来创建有用的量子应用。
错误抑制、缓解和纠正之间的区别
在上一节中,我们讨论了一些在量子系统上运行量子电路时可能引入的错误来源。在这里,我们将定义从量子电路中消除这些错误的各种方法,以获得最佳结果。我们将首先定义抑制、缓解和纠正错误的区别。
错误抑制正如其名所示,就是抑制给定电路的噪声,以保持量子状态完整。这里的特定噪声可能是量子比特的退相干时间(振幅衰减)或去相位。正如我们之前所学的,如果一个量子比特被放置在特定的状态,并在一段时间内保持在那里,那么这个量子比特最终会回到基态,或者某个与设定状态不同的状态。我们在这里想做的就是通过保持电路在当前状态来抑制这种情况。当然,说起来容易做起来难。让我们首先从直觉上考虑这个问题,然后再深入细节。
回到我们之前提到的健身房场景,现在假设你正从卧推凳上的躺姿开始,举起非常重的哑铃进行卧推。这里存在一个风险,随着时间的推移,由于肌肉的紧张,哑铃开始感觉越来越重。这就是为什么大多数做这个动作的人都会站在哑铃非常近的地方,有一个保护者。这个人会在你疲劳需要一点帮助时帮助你,或者在你肌肉突然放弃时帮助你将哑铃从胸部抬起。我相信你可以在网上找到很多视频,可以看到没有保护者在场时的可怕后果,但就现在而言,让我们详细说明这与错误抑制有什么关系。
如果你想象你需要长时间保持头顶上的重量,你会意识到随着时间的推移,你的肌肉会感到紧张;我们将这比作之前的退相干。为了帮助解决这个问题,你的保护者可以通过轻轻地帮助你将杠铃重新举起来,这样你就可以保持你的初始位置或状态。这种来自保护者的帮助可以看作是错误抑制,因为它帮助保持了你的状态,而没有改变状态本身,这意味着你没有把它举得比预期更高或更低,以算作一次举起。现在,让我们离开健身房,将我们的背景切换回量子,看看这如何帮助我们理解与量子电路相关的错误抑制。
当一个门或一组门作用于电路中的量子比特时,该电路可能在一段时间内以多种方式失去其状态,其中一些我们在之前提到过。我们需要做的是应用一种技术,帮助我们保持电路的状态。是的,这听起来像是纠错定义,但让我花点时间来详细说明,以便识别其中的区别。为此,我将解释一个新添加到 Qiskit 中的 Pass(回想一下,Pass 是一个用于优化电路的对象,例如找到最优布局映射或最优量子比特)。这个 Pass 叫做动态去耦(DD)。动态去耦是一种已知的技巧,由麻省理工学院的 Dr. Seth Lloyd 和 Dr. Lorenza Viola 在 1998 年描述(arxiv.org/abs/quant-ph/9803057)。他们将其描述为一种使用控制脉冲来最小化量子比特状态扩散的形式。这通常是通过应用相同的门操作两次来完成的,这模仿了一个恒等门。我们之前在解释大多数通用量子门都是可逆的时候了解到这一点,因此通过连续应用两个可逆门,你最终会得到一个恒等门。那么,这是如何工作的呢?你问得好,下面我们就来探讨一下!
如果你还记得第七章,使用 Qiskit 编程;理解脉冲和脉冲库部分,当我们描述在量子计算机上运行你的电路时,你必须首先创建一个脉冲时序,这是通过按照脉冲时序的顺序来指导控制系统向量子比特发送脉冲的。你可能也记得,脉冲时序包括有关脉冲的信息,但还包括脉冲的持续时间以及脉冲之间的时间间隔。幸运的是,我们了解到所有这些都有 Transpiler 和PassManager为我们完成。一旦创建了在特定量子系统上运行的脉冲时序,动态去耦 Pass 就会在系统运行之前遍历它。DD Pass 寻找的是脉冲信号之间的空闲时间间隔,并在这些间隔中插入一系列门。记住,由于门是可逆的,我们不希望改变电路的状态或相位,因此它将应用一对门来确保它持续执行一个幺正门的作用。让我们看看代码,看看这是如何实现的,以便更好地理解。
首先,我们将导入一些我们需要开始工作的对象和函数。
%run helper_file_1.0.ipynb
import numpy as np
from qiskit.circuit.library import XGate
from qiskit.transpiler import PassManager, InstructionDurations
from qiskit.transpiler.passes import ALAPScheduleAnalysis, PadDynamicalDecoupling
from qiskit.visualization import timeline_drawer
让我们回顾一下我们在这里导入的内容。首先,从 Transpiler 中,我们引入了PassManager和InstructionDurations。PassManager是用来管理在编译过程中优化我们的量子电路所使用的 Pass 的。InstructionDurations基本上就是那样;它存储了所有门的持续时间、时间尺度(dt)到所选后端系统。让我们拉取一个随机后端系统的持续时间来尝试一下。检查哪些后端可用,因为系统可能会随时间而变化。
# Select a random backend you have access to, or pick the least busy:
backend = service.get_backend('ibm_kyoto')
# Pull and print the duration times of the backend system for each get per qubit:
dur = InstructionDurations.from_backend(backend)
print(dur)
以下是一个输出截断的示例,在这种情况下是 X 门。由于ibm_kyoto是一个 127 量子比特的设备,我们看到显示的持续时间是按索引排列的,以识别 x 门及其对应量子比特的持续时间。请注意,实际结果将是所有量子比特之间所有门持续时间的一个列表;以下只是结果的一个子集:
x(0,): 3.5555555555555554e-08 s
x(1,): 3.5555555555555554e-08 s
x(2,): 3.5555555555555554e-08 s
x(3,): 3.5555555555555554e-08 s
x(4,): 3.5555555555555554e-08 s
Qiskit 还为你提供了为每个门设置持续时间的能力。这允许你在需要为特定设备修改它们时拥有更多控制权。以下是如何为所有门执行此操作的说明:
# Set the duration times for each gate.
custom_duration_times = InstructionDurations([('x', None, 100),
('h', 0, 100),
("cx", [0, 1], 200),
("cx", [1, 2], 200),
("cx", [2, 3], 200),
("measure", None, 500
)])
# Print the timescales for each gate we set:
print(custom_duration_times)
在这里,我们为每个门设置了以下自定义持续时间:
| 门 | 持续时间 |
|---|---|
| x | 100 |
| h | 100 |
| cx[0,1] | 200 |
| cx[1,2] | 200 |
| cx[2,3] | 200 |
| measure | 500 |
表 10.1:自定义门持续时间
接下来,我们将以一个漂亮的时序图来可视化电路的持续时间。为了可视化电路及其时序,我们需要使用一种调度方法将电路编译到一个后端。调度方法是指transpiler如何调度操作到量子比特上,例如,当量子比特准备好下一个指令时尽可能快地进行,或者你可以将其调度到稍后,这基本上是在可能的情况下保持量子比特处于基态。在这些示例中,我们将使用alap调度方法,它是尽可能晚的缩写。你可以在 Qiskit API 文档中找到有关各种调度方法的详细信息。
#Let's transpile the circuit and view the default timeline
transpiled_qc = transpile(qc, backend, scheduling_method='alap',
layout_method='trivial')
timeline_drawer(transpiled_qc, time_range=[0,5500], show_idle=False)
这将打印出门持续时间的可视化表示。它看起来非常像我们之前学到的脉冲调度;然而,这是电路而不是脉冲调度。你可以通过我们看到左边的量子比特标签而不是驱动器来辨别。
通道。此外,请注意,在这种情况下,CX 门被标记为ecr。

图 10.8:电路时序可视化
如图 10.8所示,这是基于所选后端指定的每个门的持续时间。我们将使用这个持续时间来可视化 Qiskit 中包含的一些错误抑制功能,特别是动态去耦。
动态去耦的工作方式是,它将在您的电路中寻找空闲时间,并在那些空闲时间槽中插入一系列动态去耦门,以确保量子位的状态不被改变。所使用的可逆门对将等效于恒等门,因此不会改变电路的状态。让我们使用我们的自定义持续时间和一个 X 门对来创建动态去耦的可逆门序列。
# Create the Dynamical Decoupling sequence of reversible gates, let's use XGates:
rev_gates = [XGate(), XGate()]
# Set the PassManager with the Dynamical Decoupling sequence and custom duration times
pm = PassManager([ALAPScheduleAnalysis(custom_duration_times),
PadDynamicalDecoupling(custom_duration_times, rev_gates)])
我们所定义的是模仿恒等门的可逆门对。在这种情况下,我们使用一对 X 门。然后,我们实例化了我们的PassManager,这是在编译阶段管理使用哪些 Pass 来优化电路的东西。在这种情况下,我们将alap调度方法和动态去耦时间设置为之前创建的自定义持续时间。还包括将可逆门作为构造函数的参数。
接下来,我们想要运行我们的编译电路通过我们刚刚创建的PassManager,以产生包含动态去耦的修改后的电路:
# Run the circuit through the PassManager to add the DD to the circuit
qc_dynamical_decoupling = pm.run(transpiled_qc)
现在,让我们可视化这个电路,并将其与上一电路中的图 10.9进行比较:
timeline_drawer(qc_dynamical_decoupling, show_idle=False)
这将在添加了动态去耦错误抑制的情况下绘制电路。您可以看到添加到第一个量子位,q[0]的 X 门。

图 10.9:动态去耦电路的可视化
在这里,我们可以观察到一些事情。首先,由于我们设置的定制持续时间比之前的电路小,因此门持续时间更短。其次,您还会注意到插入的 X 门仅添加到第一个量子位,而没有添加到第二个量子位之后的空位,q[1]。这是因为空闲时间间隔比两个 X 门短,因此没有添加门对。让我们快速但显著地调整定制持续时间,通过将持续时间从 50 增加到 1500,在第二个量子位之后包括一对 X 门。由于我们只更新一个门的价值,我们可以使用update()函数仅更新该门的价值,而无需重新声明其他门,如下面的代码所示:
# Update durations
updated_values = [('x', None, 1500)]
updated_durations = custom_duration_times.update(updated_values)
print(updated_durations)
现在,让我们重试并看看这次我们得到了什么。在这里,我们将使用更新的值设置参数,重新运行PassManager并可视化结果:
# Set the PassManager with the Dynamical Decoupling sequence and custom duration times
pm = PassManager([ALAPScheduleAnalysis(updated_durations),
PadDynamicalDecoupling(updated_durations, rev_gates)])
qc_dynamical_decoupling = pm.run(transpiled_qc)
timeline_drawer(qc_dynamical_decoupling, show_idle=False)
这将现在产生两组可逆门,一组与之前相同,现在还有另一组,我们可以在第二个量子位之后看到。由于我们已经减少了持续时间,它现在可以适应第二个量子位结束时的空闲时间间隔,如下面的图所示:

图 10.10:更新持续时间后的电路可视化
如图 10.10所示,我们可以设置每个门的持续时间。在这种情况下,我们增加了持续时间,以便我们可以看到变化。然而,在许多情况下,你需要将其减少,以便尝试将可逆门挤入空闲时间间隙,以满足需要。但是,当然,就像其他任何事情一样,我们并不一定需要自己进行所有这些精细调整。幸运的是,Qiskit 运行时包括优化和弹性级别,我们可以设置这些级别以启用这些功能,正如本章前面在讨论我们可以在选项中设置的各个优化级别时所述,其中包括启用动态去耦。
现在我们已经介绍了一个错误抑制的例子,让我们继续并看看误差缓解。
首先,在我们开始定义什么是误差缓解之前,让我先介绍一下什么是错误纠正以及它们之间的区别。一般来说,错误纠正包括两个步骤:首先识别出错误已经发生,其次,使用各种错误纠正技术来纠正这个错误。而另一方面,误差缓解则是利用错误来计算和确定一个结果,这个结果可以减少错误,但并不一定消除错误本身。
存在着各种误差缓解技术,并且目前还有很多研究正在进行,以寻找更优化的方法来减少错误。在撰写本文时,三种用于缓解错误并包含在 Qiskit 运行时弹性级别选项中的技术是:TREX、ZNE和PEC。关于每种技术的详细信息,请参考本章前面的链接,其中我们定义了弹性级别。这些技术中的每一种都使用经典资源来执行其任务。这当然会给我们的应用程序引入额外的开销。然而,定期发布的进步大大减少了这些开销,以优化速度、质量和可扩展性。通过持续这样做,我们肯定会在未来达到一个量子效用水平,这意味着实现复杂量子电路的成本可能会比经典模拟低得多。
幸运的是,Qiskit 使我们能够通过弹性级别轻松设置误差缓解的级别,以激活特定的误差缓解技术。在下面的代码片段中,我们首先运行一个没有错误抑制或缓解技术的电路,然后我们将创建一个使用动态去耦进行错误抑制和 TREX 进行误差缓解的电路。
我们将从没有错误抑制或缓解开始:
least_busy_backend = service.least_busy(simulator=False, operational=True,
min_num_qubits=transpiled_qc.num_qubits)
# Let's transpile the circuit to this new backend:
transpiled_qc=transpile(qc, backend=least_busy_backend)
options = Options()
options.execution.shots = 1000
options.optimization_level = 0 # No error suppression
options.resilience_level = 0 # No error mitigation
with Session(service=service, backend=least_busy_backend) as session:
sampler = Sampler(session=session, options=options)
job_sim_0 = sampler.run(transpiled_qc)
print(job_sim_0.result())
session.close()
plot_distribution(job_sim_0.result().quasi_dists)
运行 Sampler 的结果如下,你应该也能看到结果的视觉表示:
SamplerResult(quasi_dists=[{0: 0.463, 1: 0.011, 2: 0.006, 3: 0.008, 4: 0.008, 6: 0.001, 7: 0.009, 8: 0.01, 9: 0.001, 10: 0.008, 11: 0.01, 12: 0.006, 13: 0.014, 14: 0.099, 15: 0.346}], metadata=[{'shots': 1000}])
接下来,我们将启用动态去耦和 TREX:
options.execution.shots = 1000
options.optimization_level = 3 # Levels 1-3 use Dynamical Decoupling
options.resilience_level = 1 # Level 1 uses TREX for error mitigation
with Session(service=service, backend=least_busy_backend) as session:
sampler = Sampler(session=session, options=options)
job_sim_1 = sampler.run(transpiled_qc)
print(job_sim_1.result())
session.close()
plot_distribution(job_sim_1.result().quasi_dists)
在这里,我们将optimization_level设置为3,这将启用动态去耦,并将resilience_level设置为1,这使用 TREX 进行错误缓解。注意结果差异更加精细,并且元数据包括增加的开销和缓解时间:
SamplerResult(quasi_dists=[{0: 0.4741007821539706, 1: -0.00328801216329741, 2: -0.006989853410409116, 3: 0.0018169643386157586, 4: -0.005096477885881826, 5: 0.00010104383589854057, 6: 0.0016676295409465852, 7: 0.0017229622606998135, 8: 0.0038884349758400295, 11: -0.0025386992448873155, 12: 0.0014124334608473367, 13: 0.0009761688074426505, 14: 3.0231076968467426e-05, 15: 0.5321963922532457}], metadata=[{'shots': 1000, 'readout_mitigation_overhead': 1.6922534955446735, 'readout_mitigation_time': 0.059042368084192276}])
随着研究人员试图了解噪声的影响并确定减轻噪声的最佳方法,以加速我们从量子效用走向量子优势的进程,许多错误抑制和缓解方法在研究人员中越来越受欢迎。
摘要
在本章中,我们讨论了噪声对量子计算系统的影响,包括量子比特的特定影响以及通过读出错误与量子系统本身的相对外部影响。我们发现了如何使用 Qiskit 运行时服务通过称为原语的基本构建块来运行我们的电路。最后,我们学习了如何将错误抑制和缓解技术应用于我们的量子电路,以从量子设备中过滤出噪声结果,这显著减少了错误并提供了更准确的复杂量子电路结果。
在下一章中,我们将学习如何使用 Qiskit 中提供的众多功能来创建量子应用程序。我们将探讨创建量子算法,并最终为您提供创建自己的量子算法和量子应用程序所需的所有工具。
问题
-
列出 Qiskit 运行时服务使用的三种主要错误缓解技术。
-
用于错误缓解的哪些容错级别被使用?
-
你可以使用哪些可逆门来使用动态去耦填充量子比特的空闲时间?
-
哪种类型的噪声会导致量子比特振幅衰减?
-
哪种类型的噪声会导致量子比特去相干?
进一步阅读
-
IBM 量子学习平台:
learning.quantum.ibm.com -
在容错之前量子计算实用性的证据:
www.nature.com/articles/s41586-023-06096-3 -
减少采样开销的准概率分解:
www.nature.com/articles/s41534-022-00517-3 -
二态量子系统中退相干的动力学抑制:
arxiv.org/pdf/quant-ph/9803057.pdf -
短深度电路中的错误缓解:
arxiv.org/pdf/1612.02058.pdf -
Sutor, B., 与量子比特共舞,Packt 出版公司:
www.packtpub.com/data/dancing-with-qubits -
Nielsen, M. & Chuang, I., 量子计算与量子信息,剑桥大学出版社:
www.cambridge.org/us/academic/subjects/physics/quantum-physics-quantum-information-and-quantum-computation/quantum-computation-and-quantum-information-10th-anniversary-edition -
Wootton, J., 什么是量子纠错?,Medium 系列:
decodoku.medium.com/1-what-is-quantum-error-correction-4ab6d97cb398
加入我们的 Discord
加入我们社区的 Discord 空间,与作者和其他读者进行讨论:
packt.link/3FyN1

第十一章:理解量子算法
如果你一直在阅读有关量子计算的新闻,你会注意到来自各种公司(无论大小)的大量文章,它们都在从事与量子计算相关的不同项目。原因很大程度上是基于量子系统与经典系统相比所提供的潜在计算能力。提供加速、质量和可扩展性的潜力是大多数公司和研究机构目前重点关注的领域。
通过掌握各种量子算法的复杂性,并学习如何将它们应用于特定的问题集或行业,研究人员和开发者可以进一步扩展他们对小问题的理解,并将它们应用于大型现实世界的企业解决方案。使用量子计算机解决经典计算机难以处理的现实世界问题这一时代被称为量子优势。目前,大部分工作集中在理解和创建量子计算算法上,这些算法通常集中在较小的玩具问题上,因为它们通常被称为。
然而,在 2021 年,IBM Quantum 推出了一款 127 量子比特处理器,打破了 100 量子比特的壁垒。这标志着向前迈出了重要的一步,因为它代表了一个壁垒,在这个壁垒中,经典模拟可能再也无法模仿同等规模的量子计算机。2023 年早期,这种迹象在 127 量子比特的 Eagle 处理器中得到了证实,它能够对超越经典穷举法的计算问题进行精确求解。关于这一点的详细信息可以在《自然》杂志的文章《在容错之前量子计算的有用性证据》中找到(www.nature.com/articles/s41586-023-06096-3)。
这使我们更接近于大家都在竞相实现的量子优势阶段。当然,这会因问题而异,有些问题可能需要比其他问题更多的量子计算能力,但,随着时间的推移,不同的行业最终会很快实现这一点。为了让自己准备好并加入这场竞赛,你需要了解一些基础量子算法以及它们是如何应用于解决一般问题的。
本章将涵盖以下主题:
-
理解超越经典系统的意义
-
了解 Deutsch 算法
-
理解 Deutsch-Jozsa 算法
-
了解基于基础预言机量子算法
在本章中,我们将回顾目前使用的各种量子算法。在学习量子算法时,最难以克服的障碍之一是它并非是从经典到量子的一蹴而就。简单地将经典算法的步骤从经典系统转移到量子系统,例如一个简单的加法器,并不会自动使其成为量子加速算法。这比那还要复杂一些。
在第五章,理解量子比特中,我们讨论了量子状态的操控方法,而在第七章,使用 Qiskit 编程中,我们介绍了如何在量子系统上运行电路。现在,我们将把这些部分放在一起,学习并创建量子算法,并展示它们如何在本章中优于经典算法。我们将首先提供一个量子算法的例子,这个算法是基础性的,并通过回顾Deutsch和Deutsch-Jozsa算法来展示量子系统如何通过操作更快。接下来,我们将介绍更通用的算法,这些算法专注于使用Bernstein-Vazirani算法解决简单问题。
这绝不是量子算法的详尽列表,但本章将为你提供一些早期的基础算法,这些算法将帮助你理解高级算法以及它们与经典算法的比较。如果你想看到一个更完整的算法列表,请参考附录 A,资源,其中包含一些跟踪量子算法和研究的网站链接。当然,随着技术和算法的进步,可能会发现新的算法,这些算法可能具有与以下不同的方法,但在所有这些算法中,理想的情况是理解基础知识,这样你就可以快速上手,而不必深入研究物理学。
技术要求
在本章中,我们期望你已经具备线性代数的基本知识,以便理解每个算法的方程。你还应该有一些编程基本电路并在本地模拟器和IBM Quantum平台上的量子设备上执行这些电路的经验。最后,你应该熟悉经典比特表示法和逻辑、量子狄拉克表示法(或括号表示法),以及理解前几章中涵盖的基本量子计算原理,如叠加、纠缠和干涉。
在本书中使用的完整源代码如下:github.com/PacktPublishing/Learning-Quantum-Computing-with-Python-and-IBM-Quantum-Second-Edition.
理解优于经典系统的意义
在本节中,我们将通过研究一些早期示例来了解量子系统相对于经典系统的潜在优势,尽管其中一些示例只是简单说明了优势,而这些优势本身并没有任何实际用途。
声称量子系统可能在快速速度上解决方程式,超过经典系统,或者具有更大的计算空间的能力,这些都听起来非常吸引人。然而,回想一下,在撰写本章时,还没有可用的量子系统能够在解决现实世界的商业问题方面超越当前的经典系统。“那么为什么会有这么多议论?”你可能会问。
答案很简单——潜力。从理论上讲,存在描述量子加速问题解决方案的量子算法,例如肖尔算法。然而,我们预计在看到量子加密的突破之前,我们将先看到量子优势。这是因为达到量子优势是基于特定问题的。它不会针对所有特定数据;它将随着技术能够计算非常庞大和复杂的电路而发展。为了实现复杂的电路或算法,我们需要包含错误缓解、抑制,最终是纠正的系统,以获得准确的结果。当然,这可能与大多数新技术相似。一个很好的例子是视频流。
多媒体压缩已经存在了几十年,视频流是在 20 世纪 90 年代初发明的。当视频压缩首次商业化时,互联网带宽已经增加,并且更广泛地可用,尽管视频和音频的质量不如今天丰富;分辨率大约是 150 x 76 像素,刷新率大约是每秒 8-12 帧,音频质量较差。那时的限制是压缩技术降低多媒体质量以及带宽将多媒体内容流式传输到多个观众同时观看。
确保适当解压缩和最小化信息损失的基础设施依赖于纠错,以及一个适当的协议来避免低质量和经常抖动的分辨率。当然,仅仅二十多年后,我们可以看到进步:我们可以以低错误率和高分辨率流式传输实时多媒体事件。现在,将视频流到拥有大尺寸 4K 高清屏幕的家庭影院系统,你不必太担心视频质量,这已经成为一种常态。
量子系统拥有相同的路线图,我们现在有硬件(量子系统)和算法以中等分辨率做事。这里的区别在于,我们有一些当时没有的东西:一个全球性的基础设施,任何人,无论何地,都可以通过云访问量子系统。IBM 量子计算机任何人都可以通过简单地注册免费账户来访问。
在视频流媒体早期,很少有人能够访问带宽。那些能够访问的人受到基础设施的限制,无法协作。通过拥有云访问的系统,许多行业和学术机构正在对量子硬件和算法进行更多研究。当然,在多媒体流媒体早期,被解决的问题被归类为玩具问题。然而,不要被这个名字欺骗。这些玩具问题远非仅仅是用来玩耍和向同事炫耀的东西。它们是通往现实世界解决方案的垫脚石。
例如,如果你找到一个解决方案,它展示了量子速度相对于经典的速度,只需要几个量子比特和非常小的量子体积,那么这可能对解决许多当今的商业或现实世界问题没有用。
它所提供的是将你的解决方案扩展到具有必要量子体积以解决实际问题的系统的基本信息。要理解通往量子优势的道路是什么,在哪里存在一个量子解决方案可以比经典系统更有效地解决实际问题时,首先理解基本的量子算法以及它们不仅与经典算法不同,而且比它们提供优势,这一点很重要。这将简化你对其他更复杂算法的理解以及它们如何被用于解决各个行业的各种问题。
在下一节中,我们将讨论各种基础量子算法,从最初展示对经典系统优势的算法开始。
了解德施特算法
大卫·德施特,牛津大学的物理学家,首先发现了一个可以用量子计算机比经典计算机更快解决的问题。这个问题本身在计算机问题中没有任何重要性或用途,但它确实用来说明量子计算相对于经典计算的优势。让我们在下一节中理解这个问题。
理解问题
这个问题非常简单。我们将用一个简单的类比来解释它。想象有人在每个手里藏了一枚硬币。每枚硬币,当揭示时,要么是正面要么是反面。由于有两枚硬币,一枚在手,一枚在手,所以有四种可能的结果,如下表所示:
| 事件 | 左手 | 右手 |
|---|---|---|
| 1 | 正面 | 正面 |
| 2 | 正面 | 反面 |
| 3 | 反面 | 正面 |
| 4 | 反面 | 反面 |
表 11.1:所有四种可能的结果
从前面的事件列表中,我们可以看到有两种类型。第一和第四个事件是恒定结果的一个例子,其中左手和右手都产生相同的结果,要么是正面要么是反面。第二和第三个事件是平衡结果的一个例子。
在这里,事件结果是相互对立的,这表明如果一个结果是正面,那么另一个将是反面,反之亦然。使用这个相同的类比,如果我要逐个揭示一只手,比如说左手,那么仅通过查看左手的结果,你将没有足够的信息来确定结果将是恒定还是平衡,因为你还需要知道另一只手的内容。
现在,想象你面前有 100 只手,你必须逐个检查每只手,以确定是否有平衡数量的正面和反面,或者所有手都只包含正面或只包含反面。在最佳情况下,你会在前两次尝试中得出结论,这意味着如果第一只手是正面,第二只手是反面,你就可以得出结论,其他手的结果将是平衡的。另一方面(有意为之),如果前两只手显示相同,无论是正面还是反面,那么你不能得出它是平衡的还是恒定的结论。
在最坏的情况下,你可能需要继续检查,直到第 51 只手被揭示,因为如果前 50 只手都是正面,那么第 51 只手将表明整个集合是恒定的(如果第 51 只是正面)还是平衡的(如果第 51 只是反面)。然而,我们有点超前了,所以让我们专注于当前的问题范围,即只有两个事件。
使用德鲁茨提出的量子算法来解决这个问题,相当于一次性打开所有手并确定前两个量子位是恒定还是平衡的。有趣,不是吗? 让我们看看它是如何工作的!
我们将从将问题的类比迁移到数学方程式开始。这将简化稍后对解决方案的描述:
-
首先,将正面和反面分别用二进制数 0 和 1 表示。
-
接下来,我们将每个手的结果称为一个函数 ![img/B18420_11_002.png],其中自变量可以指左或右,![img/B18420_11_003.png] 或 ![img/B18420_11_004.png],分别。
因此,结果如下。在这种情况下,函数 ![img/B18420_11_002.png] 是 ![img/B18420_11_006.png],其中自变量 x 可以是 0 或 1(左或右)。结果表示每个具有不同结果的事件,其中每个事件要么是平衡的,要么是恒定的:
| 事件 | ![img/B18420_11_007.png] | ![img/B18420_11_008.png] | 结果 |
|---|---|---|---|
| 1 | 0 | 0 | 恒定 |
| 2 | 0 | 1 | 平衡 |
| 3 | 1 | 0 | 平衡 |
| 4 | 1 | 1 | 恒定 |
表 11.2:结果的数学表示
如前表所示,现在我们可以将我们的问题重新表述为一个函数
,它将单个比特 {0,1} 映射到 {0,1} 的结果,如果
和
的结果相同,例如 事件 1 和 事件 4(来自前表),则结果将是常数;否则,结果将是平衡的。现在我们理解了问题,让我们来找出解决方案。
定义问题
我们现在知道,如果
=
,那么我们说
是 常数;否则,
是 平衡的。如果我们引入一个 黑盒,有时被称为神谕,对我们来说是隐藏的,那么问题就变得有趣了。我们不知道隐藏在黑盒中的函数是常数还是平衡的,这正是我们被要求解决的问题。以下图示是我们输入值 x 进入黑盒函数
,并输出结果值
的图形示例:

图 11.1:我们问题的黑盒表示
如前图所示,这个问题可以用经典方法解决。但是,它需要两个查询来确定
是常数还是平衡的,其中每个查询都会查看
和
的结果,以判断它是常数还是平衡的。当使用德意志的量子算法时,我们将看到是否可以通过利用叠加原理仅用一个查询来确定
。让我们在下一节中看看。
将问题描述为一个量子问题
由于我们正在处理量子计算,我们首先需要将我们的函数和值表示为矢量。因此,我们的常数函数,其中两个输入都产生相同的输出,可以用以下矢量形式表示:

该函数对不同输入产生相同结果的输出可以表示如下:

函数
可以用以下矩阵表示:

同样,以下是一个平衡函数的例子,其结果是两个输入值的相反数:


将成为我们的黑盒,或称为神谕函数。为此,我们需要扩展我们之前的图示,包括创建我们的神谕所需的额外组件:
-
首先,我们将我们的输入和输出寄存器转换为狄拉克 矢量表示法
。 -
接下来,我们将创建两个输入寄存器,
和
,其中输入寄存器将输入到我们的黑盒,或或然函数
,而
寄存器用作辅助量子比特。辅助量子比特是额外的位,用于存储可能稍后使用的信息或在整个量子电路中跟踪信息。 -
最后,我们将定义我们的两个输出寄存器:一个与输入
相同,另一个是输入寄存器 x 和与函数
XORed 的输入寄存器 x 的 XOR,如
。
因此,我们现在可以定义或然函数如下:

这如下所示:

图 11.2:Deutsch 算法的图形表示
另一个要求是该函数应该是可逆的,我们可以通过反向工作来测试它:

现在我们已经将函数定义为我们的问题的量子函数,我们将看到 Deutsch 算法是如何工作的。
实现 Deutsch 算法
在本节中,我们将以平衡函数为例实现算法,但将代码更新以实现常量函数的任务留给你。我们将检查 Deutsch 算法,并在构建 IQL 算法时逐步执行每个任务如下:
-
在你已安装 Qiskit 的新 Jupyter 笔记本中打开,并在第一个单元中包含辅助文件:
%run helper_file_1.0.ipynb -
接下来,我们将创建一个双量子比特电路,并为每个输入准备,第一个到
,第二个到
。我们将使用恒等门来表示
,即初始状态,以及 X 门来表示
的初始状态:# Implement Deutsch's algorithm for a balanced function qc = QuantumCircuit(2,1) # Prepare the input qubits, where q0=0, q1=1 print('Step 1: Prepare the input qubits, where q0=0, q1=1') qc.id(0) qc.x(1) qc.barrier() qc.draw(output='mpl')
这导致了以下电路图:

图 11.3:初始化量子比特为 0 和 1
如前图所示,q[0] 被设置为
,q[1] 被设置为
,这创建了第一个状态在障碍(
)处为
。使用障碍只是为了在遍历电路中的每个操作时指示检查点。
-
现在我们已经设置了输入,我们将使用 Hadamard 门将它们置于叠加态。这将使我们能够利用所有四个状态进行一次迭代,而不是逐个迭代:
# Place each qubit in superposition by applying a # Hadamard print('Step 2: Place each qubit in superposition by applying a Hadamard') qc.h(0) qc.h(1) qc.barrier() qc.draw(output='mpl')
前述代码的结果如下所示。障碍用于分隔每个步骤,以便简化电路的阅读:

图 11.4:对两个量子比特应用 Hadamard 门
如前图所示,Hadamard 门将每个量子比特的基向量转换为以下形式:

这在第二个势垒处生成以下状态
,其中
描述了一个单个量子比特:

- 在量子比特应用了前面的 Hadamard 门之后,量子寄存器的结果值将如下所示:

这里有一点需要注意,我们现在将第二个量子比特置于
超叠加状态,
。这允许我们定义
中的第一个和第二个量子比特,如下所示:

从前一个方程中,你可以看到第二个量子比特,分组在第二组括号中,具有相同的值,即
超叠加状态,
。
然而,我们看到的第一个量子比特有一个有趣的结果。让我们深入挖掘以了解这意味着什么。
在这里,我们看到如果
是恒定的,我们将得到以下结果:

如果
是平衡的,那么我们将得到以下结果:

注意,第二个量子比特始终相同,但第一个量子比特如果恒定则会有正相位回弹,如果平衡则会有负相位回弹。这种相位回弹是许多量子算法中常用的技巧,所以请放心,我们还会再次看到它。
- 接下来,通过将 Hadamard 门应用于第一个量子比特,我们可以看到一些有趣的结果。让我们逐个来看。
对于一个恒定函数,第一个量子比特被设置为以下状态:

我们回忆一下,将 Hadamard 门应用于这个超叠加状态将带我们回到
状态。
对于平衡函数,第一个量子比特被设置为以下超叠加状态:

我们还可以回忆一下,将 Hadamard 门应用于前面的超叠加状态将带我们回到
状态。
这意味着在将 Hadamard 门应用于它之后仅测量第一个量子比特将为我们提供以下结果状态之一:
或
,分别是恒定或平衡状态。
- 让我们使用我们的 Qiskit 笔记本来实现这一点。
这是我们希望设置一个量子门来操作 q[1],它代表y值,基于 q[0]的值,它代表x值的地方。因此,这个操作符,我们将称之为
,将有输入(x, y)。我们将使用表示这个的控制非(CNOT)门。使用这个平衡函数的原因是因为它产生 Bell 态 01 和 10,输入量子比特。当然,通过添加 X 门,这可以切换到其他两个 Bell 态,00 和 11,这将使其成为一个常数函数。
在这种情况下,我们正在努力创建一个平衡函数,一对一,这相当于以下内容:

为了完成这个,我们需要定义我们的状态算符
,如下所示:

现在,我们将放置一个控制位在第一个量子比特q[0],目标在第二个量子比特q[1]上的 CNOT 门:
# Add a CNOT gate with the Control on q0 and Target on q1
qc.cx(0,1)
# Draw the circuit
qc.draw(output='mpl')
这现在应该包括生成函数类型(平衡)的 CNOT 门,并呈现以下图示:

图 11.5:定义函数类型(平衡)
-
接下来,我们将向所有量子比特添加 Hadamard 门,并将测量算符添加到第一个量子比特:
# Add the Hadamard gates to all qubits qc.h(0) qc.h(1) qc.barrier()
如我们之前方程中看到的,我们只需要对第一个量子比特应用 Hadamard 门,因为我们只测量一个量子比特:

图 11.6:在测量之前对量子比特应用 Hadamard 门
这导致以下状态,
:




现在,让我们对结果应用一些代数来简化它们:

由于我们只测量第一个量子比特,我们可以丢弃第二个量子比特,或者根本不测量它,因为在这种情况下它只是一个辅助量子比特。
-
让我们对第一个量子比特进行测量,如下所示,其结果将确定函数的类别,要么是平衡(
1),要么是常数(0):# Add measurement operator to the first qubit qc.measure(0,0)
我们已经从之前的方程中知道,这应该等于一个平衡函数:

图 11.7:仅对第一个量子比特应用测量算符
-
接下来,为了简化一些事情,让我们定义一个函数来在我们的本地机器上安装的采样器上运行我们的电路。我们将使用以下函数,该函数利用
StatevectorSampler:# Run on a Sampler def run_on_sampler(circuit): from qiskit.primitives import StatevectorSampler # Construct a Statevector Sampler sampler = StatevectorSampler() # Run using the Sampler result = sampler.run([circuit]).result() return result -
现在我们可以运行前面的电路,并使用以下代码验证我们的结果:
# Execute the quantum circuit on the simulator first to # confirm our results. print('Step 6: Execute the circuit to view results.') result = run_on_sampler(qc) counts = result[0].data.c.get_counts() # Print and plot our results print(counts) plot_distribution(counts, title='Balanced function')
如前所述计算,这个实验的结果表明这是一个平衡函数,如结果1所示,而不是0。
这导致以下输出:

图 11.8:值为 1 的结果,表示平衡函数
如预期的那样,我们看到结果是 1,表明这是一个平衡函数。
注意,要从结果中检索计数,我们需要映射到数据对象,然后映射到我们希望从中提取计数的经典寄存器的名称。
从前面的输出中,结果显示与我们所期望的给定函数相同,在这种情况下,是一个平衡函数,其中 1 的概率更高。在 问题 部分有一个练习,要求你创建一个常数函数。
我们在这里展示的是量子算法执行操作比经典系统更快的能力,否则它需要为每个输入按顺序计算每个函数。当然,这个练习并不提供任何实际应用,但它有助于理解这些系统具有潜在的加速特性。在下一节中,我们将通过将其应用于多个量子比特来推广这个例子。
理解 Deutsch-Jozsa 算法
在上一节中,Deutsch 算法为我们提供了一个量子加速的例子,我们使用了两个量子比特,但只测量了一个量子比特。在这里,Deutsch-Jozsa 算法提供了一个更通用的算法形式。它可以应用于多个量子比特。该问题最初由 David Deutsch 和 Richard Jozsa 在 1992 年提出,并在 1998 年由 Richard Cleve、Artur Ekert、Chiara Macchiavello 和 Michele Mosca 进行了改进,问题仍然是相同的,但正如我们在上一节末提到的,问题现在扩展到了不仅仅是单个量子比特。Deutsch-Jozsa 算法将同时作用于多个量子比特,当然,与经典计算相比,它仍然会提供量子加速,因为它需要按顺序计算每个事件,正如我们将在下一节中看到的。
理解 Deutsch-Jozsa 问题
在这个例子中,我们将扩展之前的问题定义。之前,我们在单比特值函数上定义了我们的问题,以确定一个函数是常数还是平衡的,如下所示:

在这个例子中,我们将问题扩展到包含多个比特作为输入,以便:

您可以从前面的方程式中看到,如果对于所有情况
是相同的,即
,那么
是常数的。否则,如果对于 x 的一半
和 x 的另一半
,那么
是平衡的。例如,如果我们把输入值中的 n 设置为 2,即
,那么这将产生四个不同的输入值,即 00、01、10 和 11。
根据这四个可能的输入值 x,为了创建一个平衡函数,我们可以将结果的前半部分设置为 0,如下所示:
| 输入 1 | 输入 2 | 输出 |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 0 |
我们可以将结果的后半部分设置为 1:
| 输入 1 | 输入 2 | 输出 |
|---|---|---|
| 1 | 0 | 1 |
| 1 | 1 | 1 |
如果我们用经典方法来解决这个问题,我们需要
次查询来确定结果是否恒定或平衡。另一方面,Deutsch-Jozsa 算法只需要一次查询就能确定函数是否恒定或平衡,就像在 Deutsch 算法中一样。
使用 Deutsch-Jozsa 算法生成量子解
要生成实现 Deutsch-Jozsa 算法的量子电路,我们将使用之前的一些相同组件:
- 让我们从黑盒(或门)的输入开始。第一个输入寄存器是一个 n 比特字符串,表示输入 X。我们用大写 X 表示,因为大多数文本用小写变量(如
)来引用单个量子比特或比特值,而多量子比特用大写变量(如
)表示。
第二个输入寄存器是一个表示输入 y 的单比特字符串,正如之前一样,它被初始化为 1。这通常被称为 辅助量子比特。
- 接下来,我们定义类似之前形式的或门函数。然而,这里的区别在于
现在是一个多量子比特输入,
。
定义如下:

因此,我们的输出将类似于两个输出。第一个与第一个输入
相同,第二个输出是我们的函数
。这导致了以下图形表示:

图 11.9:Deutsch-Jozsa 算法的图形表示
现在我们已经定义了我们的组件,让我们在下一节中将这个框图实现为一个电路。
实现 Deutsch-Jozsa 算法
在这个例子中,我们将实现 Deutsch-Jozsa 算法,以确定给定的函数在一次查询中是恒定的,而在经典系统中确定相同的结果将需要多次查询,从而说明使用量子计算可以提供加速。
为了实现 Deutsch-Jozsa 算法,创建一个新的 Qiskit 笔记本并运行模板单元格以加载所有 Qiskit 模块。一旦设置完成,让我们一步一步地创建我们的电路,并看看它如何随着我们的进展解决我们的问题:
-
首先,让我们设置我们的输入值。我们将从创建一个具有两个输入的量子电路开始,第一个设置为
X,我们将创建一个 4 量子比特输入,然后是一个表示y的单个量子比特,我们将它初始化为1。然后我们将 Hadamard 门应用于所有输入量子比特:# Create the quantum circuit with both input registers X, # and y input_qubits = 4 # Refers to our X input register, #4-qubits ancilla_qubit = 1 # Refers to our y input register, #1--qubit # Total qubits in our quantum circuit total_qubits = input_qubits + ancilla_qubit # Generate the circuit qc = QuantumCircuit(total_qubits, input_qubits) # Set the X qubits in superposition for idx in range(input_qubits): qc.h(idx) # Set the y qubit to 1, then apply a Hadamard qc.x(input_qubits) qc.h(input_qubits) qc.barrier() qc.draw(output='mpl')
这将导致以下图:

图 11.10:准备我们的量子电路的输入值
输入状态的结果如下:

当我们将 Hadamard 门应用于前面的方程时,它分解为以下方程:

当我们将 Hadamard 门应用于单个量子比特
时,这给我们以下方程:

简化
和
得到以下方程:

- 接下来,我们将为我们的电路创建预言机函数
,类似于我们在上一节关于 Deutsch 算法的部分中创建它的方式。我们将在这里使用相同的,只是这次,我们有了 ket X,它包含的信息不止一个比特:

x 的值是位串 X 的 0 或 1 的位表示。
-
现在,让我们使用位串来设置我们的输入状态,以表示平衡的
函数——在这种情况下,'1010',我们通过放置一个 X 门到设置位和其余的恒等门来构建它。这将使我们能够确定输入是平衡的还是常数——在这种情况下,因为我们有相等数量的 1 和 0,它是平衡的。你也可以不添加恒等门,但为了现在,我们将添加一个,以便直观地表示位串的0值:# Set the bit string which we wish to evaluate, # in this case set '1010', where I indicates value 0, # and x indicates value 1. qc.id(0) qc.x(1) qc.id(2) qc.x(3) qc.barrier() qc.draw(output='mpl')
这将在我们的电路中添加以下部分,其中添加的部分表示根据位串 1010 设置输入状态
:

图 11.11:位串 1010 的状态表示 
-
接下来,我们将应用我们的预言机。在这种情况下,我们将它设置为常数输出,其中所有输出都应该是 1,0 的概率为零。我们将通过添加 CNOT 门来实现这一点,其中控制位应用于每个量子比特,目标设置为最后一个量子比特:
# Set oracle to either constant (output = 0s) # or balanced (output = 1s) # In this example we will choose a balanced function for idx in range(input_qubits): qc.cx(idx, input_qubits) qc.barrier() qc.draw(output='mpl')
这个结果应该是这样的,其中我们设置了每个 CNOT 门的控制位到所有量子比特,目标到我们的辅助量子比特 q[4]:

图 11.12:添加的平衡预言机的表示
-
接下来,我们将设置关闭的位串,我们用它来封装我们的预言机——在这种情况下,
'1010':# Set the closing bit string we selected earlier to # evaluate qc.id(0) qc.x(1) qc.id(2) qc.x(3) qc.barrier() qc.draw(output='mpl')
前面的代码将给出以下图,正如我们所期望的,其中预言机被位串绑定:

图 11.13:由位串表示限定的 Oracle
-
接下来,我们将应用 Hadamard 门到所有的量子位上:
# Add the Hadamard gates to complete wrapping the oracle for idx in range(4): qc.h(idx) qc.barrier() qc.draw(output='mpl')
这个结果如下所示:

图 11.14:平衡函数的 Deutsch-Jozsa 算法的完整量子电路
-
最后,我们将添加我们的测量,以便我们可以读取结果。我们只将对前四个量子位应用测量:
# Add measurements only to our inputs qc.measure(range(4),range(4)) # Draw the circuit qc.draw(output='mpl')
因此,我们的最终量子电路应该是这样的。创建 Deutsch-Jozsa 算法的每一步都由壁垒分开,第一步是准备,第二步是设置位串 1010,第三步是设置我们的 Oracle ![img/B18420_11_028.png],然后我们反转前两步,接着进行测量:

图 11.15:Deutsch-Jozsa 算法的最终电路
-
现在我们已经创建了 Deutsch-Jozsa 算法的量子电路,让我们首先在模拟器上执行电路,以可视化我们得到的结果:
# Run the circuit result = run_on_sampler(qc) counts = result[0].data.c.get_counts() # Print and plot results print(counts) plot_distribution(counts)
如预期的那样,我们的结果返回了一个平衡电路 100% 的 1s 概率:

图 11.16:平衡函数模拟器的结果
如预期的那样,我们看到我们有一个高准概率的 1111。这是通过单次查询计算的,而不是我们通常在经典计算中需要的多次查询。
现在我们已经完成了 Deutsch 和 Deutsch-Jozsa 算法,我们可以看到与经典系统相比,它们确实有一定的加速。然而,我们也可以看到,目前还没有实际或现实世界的例子可以应用这些算法。话虽如此,我们已经了解了如何使用叠加和纠缠来加速某些功能,与经典技术相比。在下一节中,我们将扩展我们对算法的理解,将其扩展到更通用的量子算法,即 Bernstein-Vazirani 算法。
了解基于 Oracle 的基础量子算法
在上一节中,我们了解到非常早期的量子算法展示了量子速度相对于经典系统在简单问题上的加速。在本节中,我们将扩展这一点,以查看一个更复杂的问题。为此,我们将学习另一个基于 Oracle 的算法,即 Bernstein-Vazirani 算法。与之前的基算法相比,Bernstein-Vazirani 算法将使用 Oracle 函数在单次查询中识别隐藏的位串。
了解 Bernstein-Vazirani 算法
原创于 1992 年由 Ethan Bernstein 和 Umesh Vazirani 发明,Bernstein-Vazirani 算法将 Deutsch-Jozsa 算法扩展到寻找未知或秘密比特字符串的泛化。在 Deutsch-Jozsa 算法旨在解决确定给定函数是否为常数或平衡的问题时,Bernstein-Vazirani 算法通过应用将输入映射到其输出的函数来确定秘密数字。
理解 Bernstein-Vazirani 问题
Bernstein-Vazirani 算法解决的问题非常直接,与先前的问题类似。给定一个未知函数,或黑盒(预言机),类似于 Deutsch-Jozsa 预言机,一个比特字符串的输入会产生 0 或 1 的输出。一个简单的例子可能是一个逻辑表达式,将输入值映射到单个 0 或 1 的输出值:

对于这个函数
,我们可以保证以下成立:

从前面的方程中,s是一个未知或秘密字符串,使得:

因此,问题是要找到秘密值 s。
类似于前面的例子,用经典方法解决这个问题,我们需要逐位检查每个值以确定秘密值,s。然而,正如我们前面所看到的,我们可以使用执行单个查询的量子算法来解决这个问题。让我们通过 Bernstein-Vazirani 算法的例子来了解如何解决这个问题。
使用 Bernstein-Vazirani 算法生成量子解决方案
Bernstein-Vazirani 算法与 Deutsch-Jozsa 算法非常相似,因为它执行相同的步骤来创建算法的量子电路:
-
将所有n个输入量子比特初始化为基态
。 -
将辅助量子比特初始化为激发态
。 -
对所有输入量子比特和辅助量子比特应用 Hadamard 门,
。 -
使用 CNOT 门根据秘密字符串值查询预言机以应用相变。
-
对输入量子比特应用另一组 Hadamard 门。
-
测量输入量子比特以获得秘密字符串。
如前所述,算法非常相似。然而,这里的主要区别在于步骤 4和步骤 5。当一个量子比特遇到秘密密钥时,我们就会应用相移,即当
,其中s[i]是秘密字符串的第i个项。然后,在步骤 5中,当我们应用第二组 Hadamard 门时,如果
,量子比特将从
返回到
,或者如果
,则从
返回到
。
让我们一步一步地实现这些步骤并回顾状态的变化。像以前一样,我们将使用屏障来分隔每个步骤,这样我们就可以在过程中可视化每个步骤。
实现 Bernstein-Vazirani 算法
以下步骤是创建Bernstein-Vazirani算法的逐步指南,并描述每个步骤的结果,以帮助您了解每个步骤如何影响状态,最终产生秘密字符串:
- 首先,创建一个新的 Qiskit 笔记本,包含通常的样板单元格,该单元格将加载许多基础 Qiskit 模块和我们的账户,这样我们就可以在实际量子计算机上执行量子电路。
首先,我们将创建我们的量子电路,它将由四个量子比特和一个辅助量子比特组成,我们将定义我们的秘密比特字符串(shh):
# Create your secret number
shh = '1010'
# Set the number of qubits to represent secret number and
# an ancilla qubit
input_qubits = len(shh)
ancilla_qubit = 1
total_qubits = input_qubits + ancilla_qubit
# Create the quantum circuit
qc = QuantumCircuit(total_qubits, input_qubits)
上述代码创建我们的基础量子电路qc,我们将用它来构建 Bernstein-Vazirani 算法。输入量子比特的长度必须至少与我们的秘密字符串长度相同,在这个例子中是值1010。我们的输入寄存器长度至少需要这么多量子比特。然后我们添加了一个辅助量子比特,在之前的例子中我们称之为输出量子比特。从现在开始,我们将开始将这个量子比特称为辅助量子比特,因为它更像是工具量子比特,它不会被测量或输出到我们的结果中。
-
接下来,我们将向输入量子比特添加 Hadamard 门,以确保所有输入量子比特都设置为叠加态:
# Add Hadamard gates to the input qubits for idx in range(input_qubits): qc.h(idx) # Draw the input circuit qc.draw(output='mpl')
这将渲染我们的量子电路如下:

图 11.17:将输入量子比特的状态从![img/B18420_06_015.png]初始化为叠加态![img/B18420_11_124.png]
-
接下来,我们需要准备我们的辅助量子比特q[4],就像我们之前做的那样,首先将其初始化为![img/B18420_06_018.png]状态,然后是一个 Hadamard 门,这将准备辅助量子比特的状态为![img/B18420_11_056.png]:
# Prepare the ancilla qubit of the circuit qc.x(total_qubits-1) qc.h(total_qubits-1) qc.barrier() # Draw the prepared circuit qc.draw(output='mpl')
前面的代码将渲染以下电路,我们看到这与我们之前的电路初始化相同。这是大多数量子算法初始化的方式,允许处理所有可能的量子比特状态组合。屏障的添加只是为了观察各种状态变化:

图 11.18:所有量子比特的初始化
第一个屏障处的状态现在设置为以下内容,其中输入量子比特如下:
![img/B18420_11_127.png]
辅助量子比特被设置为:
![img/B18420_11_128.png]
-
接下来,在我们应用预言函数之前,我们需要进行快速位顺序调整。由于量子比特是从右到左排序的,因此我们需要反转我们的秘密数字的顺序:
# Before creating the oracle, we need to adjust the # qubits. Since they are ordered from left to right, # we will reverse the secret number's current value print('Secret before reverse: ', shh) # Reverse order shh = shh[::-1] print('Secret after reverse: ', shh)
如您从以下输出中可以看到,现在的顺序是0101,因此我们现在可以应用我们的预言函数:
Secret before reverse: 1010
Secret after reverse: 0101
-
为了应用量子计算器函数,我们希望在秘密字符串中每次遇到'1'时触发相移。为此,我们将对每个量子比特应用一个 CNOT 门,其中控制位设置为每个量子比特,目标位连接到辅助位。在我们的情况下,秘密字符串在量子比特 1(q[1])和量子比特 3(q[3])上设置了'1':
# Now that we have the right order, # let's create the oracle by applying a CNOT, # where the qubits set to '1' are the source # and the target would be the ancilla qubit for idx in range(input_qubits): if shh[idx] == '1': qc.cx(idx, input_qubits) qc.barrier() qc.draw(output='mpl')
前面的代码生成了直到量子计算器的量子电路:

图 11.19:量子计算器应用 CNOT,其中秘密字符串设置为‘1’
由于所有我们的量子比特都处于叠加态,通过根据秘密字符串|S⟩应用相移,我们得到以下方程:
![img/B18420_11_129.png]
因此,根据前面的方程,我们的秘密字符串![img/B18420_11_130.png]将对设置为字符串的每个量子比特应用相移。这将使![img/B18420_11_124.png]在输入比特 x 和秘密字符串 s 等于 1 时转换为![img/B18420_11_056.png]。
- 最后,在我们对输入量子比特应用测量之前,我们应用另一组 Hadamard 门。这组 Hadamard 门达到的效果是,它将每个量子比特的状态返回到![img/B18420_06_015.png]或![img/B18420_06_018.png]状态。
这完全取决于量子比特在通过量子计算器时是否经历了相移。如果没有,那么状态将从![img/B18420_11_124.png]变为![img/B18420_06_015.png],或者从![img/B18420_11_056.png]变为![img/B18420_06_018.png]:
# Now let's close up our circuit with Hadamard gates
# applied to the input qubits
for idx in range(input_qubits):
qc.h(idx)
qc.barrier()
# Finally, let's add measurements to our input qubits
qc.measure(range(input_qubits), range(input_qubits))
qc.draw(output='mpl')
这将生成以下电路图,它完成了实现 Bernstein-Vazirani 算法以及测量算子的步骤:

图 11.20:实现 Bernstein-Vazirani 算法的最终电路
-
现在电路已经完成并准备就绪,我们可以在本地模拟器上执行电路,然后在实际量子设备上执行:
# Execute the circuit and plot the results result = run_on_sampler(qc) counts = result[0].data.c.get_counts() plot_distribution(counts)
结果应该有 100%的概率对应于我们秘密字符串的值,如下面的直方图所示:

图 11.21:结果以 100%的概率识别出我们的秘密字符串的值
如前所述的结果所示,像早期的量子算法一样,我们可以通过单次查询解决某些问题,而经典系统需要多次查询才能解决。这些问题利用了相回弹,我们使用相移来解决函数是否平衡或常数的问题。
回顾我们应用最后层 Hadamard 门的那一步,看起来控制量子比特被翻转了,而不是其他量子比特。
在本节中,我们了解了基于预言机的基础算法以及它们如何展示量子系统相对于经典系统在解决问题上的优势。我们还学习了如何利用预言机和辅助量子比特来获得一些解决方案,这反过来又可以帮助你在扩展知识和研究时理解更复杂的算法。尽管这些是简单的没有商业价值的问题,但它们确实成功地激发了对量子信息科学领域的兴趣,而这个领域至今仍在不断发展。
摘要
在本章中,我们介绍了一些使用在许多其他量子算法中常见的技术的量子算法。
本章的目标是系统地探索每一个,以便你能对每个算法解决的问题有一个良好的理解。这里讨论的主题当然是基础的和基于预言机的,尽管这些技术也常见于许多其他量子算法中。
在下一章中,我们将从基于预言机的基础算法转向另一种解决类似问题的算法形式。然而,它们将不会使用相位来识别解决方案,而是利用周期性,这也是它们被称为周期算法的主要原因。
问题
-
你会用哪个算法来确定一个 n-位字符串是否平衡?
-
实现伯恩斯坦-瓦齐拉尼算法以找到状态
170。 -
有多少个预言机函数?
加入我们的 Discord
加入我们社区的 Discord 空间,与作者和其他读者进行讨论:
packt.link/3FyN1

第十二章:应用量子算法
在上一章中,我们考虑了强调经典和量子系统之间差异的基本原理,特别是叠加和纠缠的使用。在本章中,我们将关注那些有可能解决更实用问题的算法,例如周期性和搜索。这些算法与早期算法的不同之处在于它们被用于各种领域,并包含在许多现代量子算法中。这些量子算法的几个例子包括量子振幅估计、变分量子本征值求解器和量子支持向量机算法。对这些算法有良好的理解将有助于您在学习或创建自己的算法时,因为所使用的技术可以应用于许多行业。在本章中,我们将介绍这些更现代、更复杂的算法所依赖的一些基本原理和技术,以帮助您更好地理解它们。
周期算法可以用来解决因式分解或相位估计问题。搜索算法也可以通过利用振幅放大来找到指定条目,从而在速度上比经典算法有所提升。
本章将涵盖以下主题:
-
理解周期量子算法
-
理解量子傅里叶变换算法
-
理解 Grover 搜索算法
完成本章后,您将能够掌握这些算法的概念,并利用Qiskit中已提供的算法,这样您就可以使用它们而无需重新发明轮子。
技术要求
本章假设您已经熟悉一些基本的量子算法组件,例如叠加、预言机、相位回冲以及使用 Qiskit 进行编程。您还应该理解基本的线性代数,例如矩阵乘法、矩阵的复共轭和内积。还假设您了解一些高级数学,例如傅里叶变换。
本书所使用的源代码如下:github.com/PacktPublishing/Learning-Quantum-Computing-with-Python-and-IBM-Quantum-Second-Edition。
理解周期量子算法
在第十一章“理解量子算法”中,我们介绍了使用相位回冲来解决各种问题的算法。
在本节中,我们将首先理解周期量子算法。周期函数是指随时间重复其值的函数。例如,您的手表就是周期性的,因为每一分钟有 60 秒,每一小时有 60 分钟,每一天有 24 小时。
如果你将你的手表设置为从 1 到 12 小时,那么你的手表每天有 2 个周期,因为你的手表将在一天内两次循环通过数字 1 到 12。当然,这独立于 AM 和 PM 指示器,无论是上午还是下午。周期函数以许多方式出现在我们周围,因此理解如何将它们与量子电路联系起来是理解许多量子算法的关键,包括最著名的Grover 算法之一。
但现在,我们将从扩展我们对周期函数的理解开始,特别是我们如何理解和实现量子傅里叶变换(QFT)算法。
学习 QFT 算法
QFT 与离散傅里叶变换(DFT)相关,因为它也能从一个域转换到另一个域。
DFT将有限序列的样本转换成频率的复值函数,用于分析许多应用,如图像处理和信号处理,还可以用来帮助解决偏微分方程。
DFT 用于将信号从时域转换到频域,或者用更广义的描述来说,将一个域x映射到另一个域,
,以下公式:

类似地,我们可以定义量子变换为从一个基到另一个基的变换。例如,到目前为止,我们在这本书中所做的所有计算都是根据Z基进行的。这意味着我们的基态已经被设置在量子比特的Z-轴上,状态
和
分别对应于 Bloch 球上Z-轴的正负两端。
当然,如果需要,我们还可以转换到其他基态。一个例子是量子比特的X-轴,那里的基态是
和
,分别对应于 Bloch 球上X-轴的正负两端。QFT 会在这两个基态之间转换。QFT 被许多量子算法使用,包括 Shor 算法,因为它已被证明在经典离散傅里叶变换的实现上有所改进。
在本节中,我们将通过一个简单的 QFT 算法示例来加深我们对它的理解,当我们看到它在许多其他量子算法中使用时。
我们将首先将 QFT 应用于一个简单的三量子比特量子态。
理解 QFT 算法
在深入细节之前,让我们首先了解每个轴代表什么。如您从玻色球体回忆的那样,它是量子比特的一种视觉表示,由三个轴组成,即 X、Y 和 Z 轴。绕 X 和 Y 轴的旋转是我们用来调整量子比特振幅的方法,即沿着玻色球体的经度(北极到南极)。绕 Z-轴的旋转是我们用来调整量子比特相位的,即沿着玻色球体的纬度。
每个轴都是一个基态,由轴命名,即 X-基态、Y-基态和 Z-基态。在量子计算中,Z-轴通常被称为计算基,而 X 和 Y-轴可以是傅里叶基。在这个例子中,我们将 X-基设为傅里叶基。QFT 变换通常是从一个基到另一个基的变换,在这种情况下,是从计算基(Z)到傅里叶基(X)。
为了将我们的量子函数从一个基态转换到另一个基态,我们需要应用 QFT,如下所示:

在前面的方程中,Z – basis 指的是 Z-轴上的基态,
和
,而 X – basis 指的是 X-轴上的基态(通常被称为相位态,因为绕它们的旋转指的是绕 Z-轴的旋转),
和
。Qiskit 文档([docs.quantum.ibm.com/](https://docs.quantum.ibm.com/))使用波浪号(~)表示傅里叶基,其中 QFT 是应用于状态
的 QFT 变换,如下所示:

这可以等同于以下公式,其中变换由 QFT 在 x[j] 和 y[k] 的振幅之间表示:

现在,让我们看看我们如何在量子电路中实现 QFT。
实现量子傅里叶变换(QFT)算法
让我们从基于输入状态
的实现推导开始。
另一种方法是按顺序将其应用于以下公式,随着我们从量子比特到量子比特的移动。对于这个例子,我们将按以下方式操作;给定一个状态
,我们将应用一个哈达玛门,其中我们根据状态
添加相位,其中每个值,j[i],都附加到相位上,如下所示:

在以下练习中,我们将实现
的 QFT,其中
。重要的是要注意,指数的分数部分是二进制,而不是十进制:
-
我们将首先打开一个新的 Jupyter 笔记本,导入一些常用对象并运行我们的辅助文件:
# Importing standard Qiskit libraries from qiskit import QuantumCircuit, transpile from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager from qiskit.visualization import * from qiskit_ibm_runtime import QiskitRuntimeService, Sampler, Estimator, Session, Options service = QiskitRuntimeService(channel="ibm_quantum") -
接下来,我们将创建一个量子电路,其宽度等于我们的状态值长度,
'110':# Initialize the 3-qubit quantum circuit # Set the state '110' s = '110' num_qubits = len(s) qc = QuantumCircuit(num_qubits) -
现在我们已经创建了我们自己的量子电路,让我们将状态,s,初始化为
。由于我们从最不显著的位置开始写,我们将相应地反转s:# Set reverse ordering s = s[::-1] # Construct the state 110 for idx in range(num_qubits): if s[idx] == '1': qc.x(idx) qc.barrier() qc.draw(output='mpl')
前面的代码将初始化并渲染我们的电路如下:

图 12.1:初始化状态,s,为 
- 现在我们已经准备好了我们的状态,我们可以开始使用 QFT 对其进行变换。
让我们回顾一下我们的变换方程,使用我们的状态
:

这表示对于每个我们应用 Hadamard 门的量子位,在遍历从量子位到最不显著的量子位的过程中,我们需要包括旋转——因此,
。当我们向下遍历,量子位的状态会逐度减少。这意味着每个受控相位旋转,控制旋转(CROT),基于以下矩阵表示:

在前面的方程中,CROT(q)k 是 CU[1]门,参数q设置如下:

因此,我们将从我们的状态
中的最显著的量子位,q[2],开始,如下所示。
-
从最显著的量子位开始,我们将向电路中添加一个 Hadamard 门:
# Import the value pi for our rotations from numpy import pi # Always start from the most significant qubit, # in this case it's q2. # Step 1, add a Hadamard gate qc.h(2) -
现在我们有了第一步,下一步是添加 CROT(![添加 CROT 门,从最显著的到最不显著的,从))门,从k=2开始,这是最显著的量子位位置,q[2]的索引,我们的参数
从以下开始:

我们从最显著的量子位开始,添加 CROT 门,从最显著的到最不显著的,从
开始,并且随着我们向下移动到每个量子位,参数的分母加倍:
# Step 2, add CROT gates from most significant qubit
qc.cp(pi/2, 1, 2)
-
然后,我们重复这个过程,当我们从当前量子位遍历到下一个量子位时——在这种情况下,q[0]:
# Step 3, add another CROT from 2 to the next qubit down, # while doubling the phase denominator qc.cp(pi/4, 0, 2) # Draw the circuit qc.draw(output='mpl')
当我们向下遍历,参数的分母也在加倍,下一个参数
如下(注意,所有量子位都初始化为
的状态):

这渲染了以下电路,现在包括 Hadamard 门和两个 CROT 门:

图 12.2:从最显著的量子位开始的第一个变换集
-
这完成了第一级,处理了最显著的量子位。我们现在将向下移动到下一个量子位(第二最显著的量子位),并重复添加 Hadamard 门的过程,然后添加 CROT(q)门,其中相位旋转随着我们向下遍历每个量子位而减小。让我们继续到下一个量子位:
# Now that we finished from 2 down to 0 # We'll drop to the next least significant qubit and # start again, # Step 1, add a Hadamard gate qc.h(1) -
这与步骤 4添加 Hadamard 门相同;现在,我们以与之前相同的方式应用控制旋转门,然后绘制电路:
# Step 2, add Control Rotation (CROT) gates from most # significant towards # least significant starting a pi/2, and doubling the # denominator # as you go down each qubit. qc.cp(pi/2, 0, 1) # Draw the circuit qc.draw(output='mpl') # Now that we finished from 1 down to 0 # We'll drop to the next least significant qubit and # start again.
这将完成第二次变换,这将生成以下电路,它以 Hadamard 门开始,然后附加 CROT 门:

图 12.3:从下一个量子比特开始的下一个变换集
-
接下来,我们将对最后一个量子比特运行变换,然后绘制电路:
# Step 1, add a Hadamard gate qc.h(0) # Since we are at the least significant qubit, # we are done! # Draw the circuit qc.draw(output='mpl')
由于这是最后一个量子比特,也是最低有效位量子比特,它没有更低的级别,所以我们完成了 QFT 的 CROT 相位。这生成了以下电路:

图 12.4:我们 QFT 电路的最终变换
- 最后,一旦我们设置了所有旋转,我们需要应用交换门来反转我们的结果。我们需要这样做以完成 QFT 并按正确的顺序设置值。交换是从最外层的量子比特向内进行,直到达到中间的最后两个量子比特(如果量子比特总数是偶数),或者直到达到中间的最后两个带有单个量子比特的配对(如果量子比特总数是奇数)。
为了简化这个过程,我们可以创建一个函数来交换外层量子比特,并逐步向中间工作。在这种情况下,因为我们只有三个量子比特,所以我们只会交换外层的两个量子比特,如下所示:
# Define a function which will add the swap gates to the
# outer pair of qubits
def add_swap_gates(qc_swaps, qubits):
for qubit in range(qubits//2):
qc_swaps.swap(qubit, qubits-qubit-1)
return qc_swaps
-
现在,我们可以通过
add_swap_gates函数运行我们的量子电路,并完成电路:qft_circuit = add_swap_gates(qc, num_qubits) qft_circuit.draw(output='mpl')
这将生成我们的 QFT 电路,它编码了我们的'110'值,如下所示:

图 12.5:编码‘110’的 QFT 电路
-
让我们包含一些辅助函数,类似于我们在上一章中创建的函数,这些函数可以在模拟器或量子系统上运行。这次,我们将包括一个选项来选择射击次数。
# Run on a local Sampler def run_on_sampler(circuit, shots): from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager from qiskit_ibm_runtime import SamplerV2 as Sampler from qiskit_ibm_runtime.fake_provider import FakeManilaV2 # Run the sampler job locally using FakeManilaV2 fake_manila = FakeManilaV2() pass_manager = generate_preset_pass_manager(backend=fake_manila, optimization_level=1) transpiled_qc = pass_manager.run(circuit) # To ensure we get fixed results, set seed options = {"simulator": {"seed_simulator": 10258}} sampler = Sampler(mode=fake_manila, options=options) result = sampler.run([transpiled_qc], shots=shots).result()[0] return result # Run on the least busy quantum computer def run_on_qc(circuit, shots): # At the time of this writing we are using the latest version of # the Sampler primitive (V2), please review the documentation to follow updates if you are using a previous version. from qiskit_ibm_runtime import SamplerV2 as Sampler2 # Assign least busy device to backend backend = service.least_busy(min_num_qubits=circuit.num_qubits, simulator=False, operational=True) #Print the least busy device print('The least busy device: {}'.format(backend)) result = {} transpiler = generate_preset_pass_manager(backend=backend, optimization_level=3) transpiled_qc = transpiler.run(circuit) sampler = Sampler2(backend) job = sampler.run([transpiled_qc], shots=shots) job_result = job.result() # Extract the results result = job_result[0] return result -
为了可视化我们的量子场论(QFT)结果,我们可以使用状态向量模拟器执行前面的电路,以查看每个量子比特的最终 QFT 编码:
# Get the state vector simulator to view our final QFT # state from qiskit.quantum_info import Statevector statevector = Statevector(qft_circuit) plot_bloch_multivector(statevector)
前面的代码为每个量子比特生成以下编码:

图 12.6:编码值为‘110’的 QFT 的布洛赫球体表示
注意,q[0](交换前的 q[2])旋转了
(这是一个 Hadamard(H)加上
旋转),q[1]旋转了
(H),而q[2](交换前的 q[0])旋转了 0,这主要是因为交换前 q[0]的值为 0,因为它被初始化为
。
注意,每个量子比特都处于叠加态,并且根据编码的'110'值按相位变化。我们还可以使用qsphere对象来表示这一点,它将包含相同的信息,只是在一个单独的球体对象中表示:
plot_state_qsphere(statevector)
在下面的图中,我们可以看到信息被编码到 QSphere 中,并且其编码表示分别由颜色轮和 QSphere 指示的相位和状态向量:

图 12.7:'110'状态的 QFT 表示的 QSphere 表示
注意,颜色表示结果中每个状态的相位,在撰写本文时,绿色表示对旋转状态‘000’和‘100’进行-度相位旋转![img/B18420_12_036.png]
恭喜!你已经完成了你的第一个 QFT 编码!这是一个你将在许多依赖周期性功能的算法中看到的算法。
在本节中,我们学习了 QFT 算法,并实现了它。有一件事我们没有涉及,但很重要,那就是 QFT 还有一个逆函数,称为逆量子傅里叶变换(IQFT)。这很简单,就是 QFT 的逆过程,本质上是将量子状态从量子傅里叶基转换回计算基。这是通过反转在转换 QFT 时执行的功能来完成的,包括反向旋转。也就是说,如果我们旋转了一个量子比特![img/B18420_12_041.png],那么在执行 IQFT 时,我们会以相同的角度旋转量子比特,但这次是相反的方向,即![img/B18420_12_042.png]。
通过理解状态转换的基础,你现在能够利用这在许多周期性函数和算法中,例如估计特征值或单位矩阵,以及分解离散对数。
接下来,我们将探讨一个更著名的搜索算法:"Grover 算法"。
学习 Grover 搜索算法
搜索算法的独特之处在于,它们可以被各种算法利用来查找信息,无论是在数据存储库还是在值列表中,例如图像中的特征。当然,量子计算的优势在于加速搜索的潜力。"Grover 算法"就是一个这样的例子。它使用一种众所周知的技术,允许使用干涉来放大我们量子电路中的某些状态,从而增加我们正在搜索的值的振幅,并减少我们不需要的值。让我们像往常一样,先描述问题,其中每个状态都类似于无序列表中的一个条目。
理解问题
这里的问题也非常简单:我们给定了一组状态,其中所有状态都设置为 0,除了一个状态设置为 1。我们希望识别出哪个状态被设置为 1。
在经典情况下,如果第一个值被设置,这可以在最佳情况下一步完成。在最坏的情况下,需要N步,其中N是总状态数,最后一个状态被设置。这意味着平均来说,需要N/2步来找到值,因为我们需要逐个检查每个值。
显然,如果我们的集合是一个非常长的列表,这并不是理想的。我们需要找到一种更好的方法来找到我们的值。这就是在 1996 年,Lov Grover 出现并发现了一种使用他现在著名的量子算法来解决这个问题的方法。当我们尝试在一个三量子比特电路中搜索一个值时,我们将逐步实现 Grover 算法。
要使用函数描述这个问题,我们可以陈述以下内容,给定一个函数:

从前面的方程中,对于所有除了特定情况x以外的x的情况,
。找出x的值。由于我们将使用量子比特,让我们选择一个值N,使得N = 2^n。
现在我们已经定义了我们的问题,让我们逐步通过 Grover 的搜索算法。
理解 Grover 的搜索算法
Grover 算法与 Deutsch-Jozsa 和 Bernstein-Vazirani 算法类似,因为它也利用了预言机。然而,请注意,这里给出的例子展示了一个非常简单的预言机,我们事先知道标记的状态。这是为了演示目的;然而,在实际应用中,预言机不仅会更复杂,而且我们也不知道哪个状态会被标记。
Deutsch-Josza 和 Grover 算法之间的另一个相似之处是,Grover 也以某种方式利用了干涉,它会增加我们正在搜索的状态的振幅,同时减少所有其他状态,这反过来又通过
增加了速度,其中N是要搜索的状态数。这意味着我们不需要迭代 N 中的每个值,而是获得指数级的加速,类似于我们在 Deutsch-Jozsa 中做的那样。
我们将首先解释 Grover 的搜索过程,以便了解它是如何工作的。对于这个背后的数学的更深入描述,我推荐 Robert S. Sutor 的书籍《与量子比特共舞》,它对此有更详细的介绍。
Grover 的搜索算法可以分为两个主要组件——如果你把初始化所有量子比特到叠加态和在最后添加测量算子算作第三个组件的话,那么就是三个——但这是大多数量子算法都会做的事情,所以我们将只关注两个主要点。第一个被称为Grover 的预言机,第二个是Grover 扩散算子。
在这个例子中,我们将描述一个双量子比特系统,当通过给每个量子比特应用 Hadamard 门将其置于叠加态时,提供四种可能的状态——00、01、10和11——如下所示:

图 12.8:两个处于叠加状态的反转量子比特
当处于这种状态时,平均值等于概率振幅,在这种情况下是0.25,如每个状态顶部的虚线所示。
对于这个例子,我们假设我们想要搜索的状态是状态'10'。
第一部分是oracles,U[f]。这是我们通常标记我们正在寻找的值的地方。通过标记,我的意思是我们将发出信号,即我们正在寻找的状态将通过简单地改变状态的符号从正变为负来识别。过渡将如下所示:

图 12.9:将状态符号改为负
现在我们已经改变了符号,不幸的是,我们在这个点上不能只是进行测量然后继续——主要是因为,正如我们所知,概率振幅是平方的,所以我们的结果仍然都会相等,这并没有给我们提供任何关于我们正在寻找的信息的新信息。然而,由于我们正在处理振幅,我们可以通过增加我们标记的状态的振幅并减少其他状态的振幅来利用干涉。我们如何做到这一点? 通过结合 Grover 搜索的第二部分,即扩散算子。
Grover 算法的第二部分是Grover 扩散算子。在这里,我们将执行一个称为关于平均值的反转的数学步骤。这样做的作用是反转每个状态的平均值与峰值之间的距离。这相当于每个状态相对于平均值进行反射翻转。直观上看,过渡将如下所示:

图 12.10:关于平均值的反转以建设性和破坏性方式放大状态
从对平均值进行反转操作的结果中我们可以看出,标记状态的放大现在显著高于其他状态。如果我们现在进行测量,我们会看到结果中概率更高的状态就是我们正在寻找的状态。当然,记住,这一切都是通过对我们量子电路的单次查询完成的!
需要注意的一点是,当状态的数量,N,很大时,这意味着我们需要多次重复扩散算子步骤(而不是或 acles 构造步骤),这是构建和破坏振幅的过程。优化结果所需的次数是
,其中 n 是量子比特的数量。
让我们接下来实现 Grover 搜索算法。
实现 Grover 搜索算法
如同往常一样,当我们逐步通过算法时,我们将解释上一节中描述的每个步骤。首先,为这个例子创建一个新的 Qiskit 笔记本,并逐步完成以下步骤:
-
我们首先声明我们想要设置的值。让我们将值设置为
110。这样,我们可以使用一个三量子比特电路来实现 Grover 算法,并通过在每个量子比特上添加一个 H 门来使所有量子比特处于叠加态:# Set the state we wish to search N = '110' num_qubits = len(N) # Create the quantum circuit qc = QuantumCircuit(num_qubits) # Set all qubits in superposition qc.h(range(num_qubits)) qc.barrier() #Draw the circuit qc.draw(output='mpl')
这将生成我们的初始化电路:

图 12.11:初始化的叠加量子电路
-
接下来,我们想要编码我们想要搜索的态——在这种情况下,它是态
。在这里,我们将反转态并在电路中编码N:# Reverse the state so it's in proper qubit ordering N = N[::-1] # Encode N into our circuit for idx in range(num_qubits): if N[idx] == '0': qc.x(idx) qc.barrier() # Draw the circuit qc.draw(output='mpl')
对于每一步,我们都会添加一个障碍,以便我们可以看到渲染的过程:

图 12.12:编码我们的状态‘110’,我们用 X 门标记状态中的‘0’量子比特
-
接下来,我们将创建 Grover 的或门。我们在这里要做的首先是设置最显著量子比特的叠加态,然后是一个目标为最显著量子比特、源为所有其他量子比特的 CNOT 门。然后,在最显著量子比特上放置另一个 H 门来完成或门。这将否定我们在上一个源单元格中设置的态,
:# Create the Grover oracle for our 3-qubit quantum circuit qc.h(2) qc.ccx(0, 1, 2) qc.h(2) qc.barrier() # Draw the circuit qc.draw(output='mpl')
上述代码生成了以下电路,我们可以看到它设置了围绕最显著量子比特的H门的我们或门中的两个 CNOT 门:

图 12.13:将 Grover 的或门应用于电路
-
现在,我们想要将电路中我们正在搜索的态重置,使其返回到叠加值:
# Reset the value after the oracle for idx in range(num_qubits): if N[idx] == '0': qc.x(idx) qc.barrier() # Draw the circuit qc.draw(output='mpl')
上述代码完成了我们之前描述的 Grover 或门,它是 Grover 搜索算法的第一个组件:

图 12.14:Grover 搜索算法的第一个组件
-
接下来,我们将实现第二个组件,Grover 扩散算子。我们首先将所有量子比特置于叠加态:
# Set all qubits in superposition qc.h(range(num_qubits)) qc.x(range(num_qubits)) qc.barrier() # Draw the circuit qc.draw(output='mpl')
这将生成以下叠加态,然后是 Grover 的或门:

图 12.15:Grover 扩散算子的第一步:对所有量子比特应用 H 门
-
接下来,我们将翻转所有 0 态量子比特到它们的负相。在这里,最显著量子比特被设置为两个 CNOT 门的目标:
# Apply another oracle, same as the previous qc.h(2) qc.ccx(0, 1, 2) qc.h(2) qc.barrier() # Draw the circuit qc.draw(output='mpl')
这将渲染扩散算器的下一步——即关于平均值的反转:

图 12.16:扩散算子的第二步:关于平均值的反转
-
最后,我们通过逆向应用第一步来总结 Grover 扩散算子。由于我们在所有量子位上应用了一组 H 门,随后又应用了一组 X 门,也是跨所有量子位,我们将在以下方式中逆转这一过程。首先在所有量子位上应用 X 门,然后应用所有量子位上的 H 门:
# Reapply the X rotations on all qubits qc.x(range(num_qubits)) qc.barrier() # Reapply Hadamard gates to all qubits qc.h(range(num_qubits)) # Draw the circuit qc.draw(output='mpl')
上述代码完成了量子电路的 Grover 扩散算子组件:

图 12.17:完整的 Grover 算法电路
为了确定重复扩散算子的理想次数 n,我们只需按照以下方式计算 n,

其中 N 是量子位数量(log N),对于两个量子位,N=4;在这个例子中,我们应该添加第二个扩散算子,将错误率从 3% 降低到 1%。
-
现在,我们将添加测量算子并准备在后端运行电路,但首先在您的设备上的本地模拟器上:
# Add measurement operators qc.measure_all() # Draw the circuit qc.draw(output='mpl')
上述代码将准备以下量子电路,以便在模拟器或量子计算机上运行:

图 12.18:准备在模拟器或量子系统上运行的完整量子电路
-
我们将首先运行我们创建的用于执行电路的函数,使用 Sampler 原语:
# Run on the sampler result = run_on_sampler(qc, shots=4000) counts = result.data.meas.get_counts() # Print and plot results print(counts) plot_distribution(counts)
执行电路后,这将按照以下方式打印和绘制我们的结果:
{'010': 274, '110': 2237, '000': 198, '100': 499, '101': 175, '111': 238, '001': 191, '011': 188}
在以下图中,我们可以看到我们正在搜索的状态具有更高的概率,准概率为 0.559%,而所有其他状态的概率显著较低,约为 0.05%:

图 12.19:在 Sampler 上执行 Grover 搜索状态 110 的结果
成功!正如预期的那样,我们的 Grover 算法实现在一个查询内找到了状态。
-
现在,让我们在一个量子设备上尝试。我们将选择最不繁忙且可操作的量子计算机,并且具有运行我们的量子电路所需的量子位数量。
# Execute the circuit on the least busy quantum computer backend = service.least_busy(min_num_qubits = num_qubits, simulator = False, operational = True) print("Set backend: ", backend)
上述代码将打印出最不繁忙的量子计算机并将其分配给 backend 变量。
-
我们现在可以像之前使用模拟器一样执行它,然后打印和绘制结果:
# Run the circuit on the backend shots = 1000 results = run_on_qc(qc, shots) counts = results.data.meas.get_counts() # Print results print(counts) -
一旦完成,你应该会看到以下类似的输出:
The least busy device: <IBMBackend('ibm_osaka')> {'000': 85, '100': 52, '111': 219, '010': 61, '110': 373, '001': 73, '011': 71, '101': 66} -
我们现在可以使用以下方法在图上绘制结果的准分布:
# Plot results plot_distribution(counts)
这将显示以下输出:

图 12.20:概率分布
当然,这取决于设备本身,因为每个系统都彼此不同。然而,结果应该是清晰的,即概率最高的状态就是我们正在寻找的状态——在这种情况下,
。
正如我们所看到的,概率最高的状态就是我们正在寻找的状态,其他状态的概率较低。我们可以观察到每个状态之间的足够差异,以观察到我们实现的 Grover 搜索算法确实识别了我们正在寻找的状态。您也可以尝试使用更大的值 N,这样您就可以观察到结果。
恭喜!您已经成功实现了多种量子算法,这些算法是理解量子计算机如何与经典系统在解决问题上的不同,以及它们如何有可能解决现实世界问题的基石。
摘要
有许多算法实现了我们在本章和上一章量子算法章节中讨论的许多技术,其中许多您将在其他算法中看到使用,例如量子振幅估计和变分量子本征值求解器算法等。
我强烈建议您亲自尝试这些算法的变体,以更好地了解它们的工作原理。
在下一章和最后一章中,我们将探讨 Qiskit 内置的功能,这些功能允许您作为研究人员或开发者利用它们来创建自己的量子算法。您将获得将算法集成到现有研究或应用中的技能,而无需担心开发电路、降低噪声或构成 Qiskit 中算法的其他任何组件。这本书已经为您做了大量工作,因此您可以简单地实现算法,并按您的需求处理结果。
问题
-
使用周期函数可以解决哪些其他问题?
-
在五量子比特状态上实现 QFT——例如,
'10110'。 -
使用 Grover 算法找到以下状态:
'101','001',和'010'。 -
您需要运行多少次 Grover 的扩散算子才能找到
的状态? -
重新运行 Grover 搜索示例。只重复 Grover 的扩散算子两次,并注意结果的不同。您看到了什么不同之处?如果您运行超过三次,您预计会发生什么变化?
加入我们的 Discord
加入我们社区的 Discord 空间,与作者和其他读者进行讨论:
packt.link/3FyN1

第十三章:理解量子实用工具和 Qiskit 模式
我们在这里,这是最后一章。如果你已经走到这一步,那么恭喜你保持专注和决心,你的时间投资得很值得!当我们结束这一章,以及这本书时,我想确保我不仅仅留给你一些基础知识,并祝愿你一切顺利。我发现大多数技术书籍似乎都是这样做的。这并不是说有什么不对,但我总觉得大多数书籍没有提供关于下一步或更好的行动指南。我想将这一章作为一个从教育到赋能的转变。换句话说,从这里去哪里以及如何去做。随着 Qiskit 1.0(以及未来的更新),我们应该将其视为真正让你提前一步,获得适当的工具和模式,以将你的当前实验演变为理想的实用应用候选者的过渡。这里的目的是,这本书不仅会为你提供开始学习的教育,还会为你提供一些指导,让你知道随着技术的发展和新功能的发布,你可以期待什么并如何发展。
到目前为止,我们已经介绍了一种“自下而上”的方法来理解量子计算,我们从基本的量子计算属性、门和电路开始。然后我们向上移动,将它们结合起来以实现各种量子算法。我们还介绍了如何使用各种模拟器、生成噪声模型以及减轻读出错误。所有这些都帮助我们理解量子计算的基本复杂性以及它们是如何被用来创建既有效又最优的算法的。
然而,要求一个开发者、解决方案架构师或系统集成人员学习所有内部运作机制,只是为了理解如何将量子计算集成到他们的应用程序或工作流程中,这实在要求太高了。实际上,往往很难找到那些想要深入了解量子算法的“螺丝钉”的人。一般来说,我们大多数人只想把数据加载到算法中,在量子系统上执行它,获取结果,然后继续我们的实验。
这种“自上而下”的方法正是 Qiskit 模式出现的地方。
本章将涵盖以下主题:
-
理解量子实用工具
-
理解 Qiskit 模式
在本章中,你将了解量子实用工具的含义以及为什么它是我们接近量子优势的关键。
我们还将介绍 Qiskit 模式以及它如何简化你构建复杂量子电路的开发体验。随着大多数量子算法和应用随着时间的推移变得越来越复杂,尤其是我们现在正进入量子实用时代,我们需要了解它们是什么以及我们如何最好地使用这些构建块来创建可扩展和高效的量子算法。
最后,我们将通过一个快速示例结束本章,该示例使用 Qiskit 模式结合了我们在上一章中学到的 Grover 算法,因为我不想在没有提供一些结合经典和量子代码的情况下结束本章。
技术要求
对于本章,我们期望你了解使用 Python 创建量子电路和通用应用开发。
以下是我们将在整本书中使用的源代码:github.com/PacktPublishing/Learning-Quantum-Computing-with-Python-and-IBM-Quantum-Second-Edition。
理解量子效用
之前,我提到了“量子效用”这个术语。我想花点时间更详细地描述一下这意味着什么,更重要的是,它不意味着什么。首先,让我们定义另一个术语,“量子优势”。量子优势是一个术语,描述了量子计算机能够解决科学或商业中的实际用例,而这些用例对于经典系统来说是难以处理的。
这种例子之一可能是 Shor 算法,它用于寻找整数的质因数。由于任务的复杂性,目前经典系统难以解决这个问题,这就是为什么它被用来加密我们的大部分数字内容,例如密码。在 Shor 算法的例子中,有人估计使用经典计算机解决该问题可能需要数百万年,而有些人说在容错系统上可能只需要几个小时。当然,在撰写本文时,量子优势还有几年才能实现,所以你现在花时间学习这项新技术是明智的,这样当这些系统达到量子优势时,你将能够领先一步解决有趣的问题。还重要的是要注意,量子优势不会一蹴而就;它将是渐进的,并且将根据问题的类型和与经典系统运行的成本相比而变化。随着技术超越量子优势,解决经典上难以解决的问题的解决方案也将随之发展。
既然我们已经对量子优势有了概念,让我们来看看量子效用意味着什么。回到 2023 年 6 月,IBM 和加州大学伯克利分校的研究人员发表了一篇题为《在容错之前量子计算的效用证据》的论文(www.nature.com/articles/s41586-023-06096-3)。在这篇论文中,作者能够展示量子计算机可以解决超出蛮力经典模拟规模的难题。再次强调,这并不是说这就是量子优势,主要是因为还有一些受量子启发的经典方法,它们使用除蛮力之外的其他技术提供了经典近似。这篇论文使用了一个拥有近 3000 个 CX 门的 127 量子比特量子计算机,这是一个相当复杂的电路,使用蛮力方法进行模拟。当然,之后也发布了一些受量子启发的基于经典方法的论文,它们设法重现了解决方案;然而,并非每个实验的所有结果在特定参数上都一致。因此,准确性开始受到影响,一些结果彼此之间大约相差 20%。此外,之后还发表了超过 100 量子比特且包含数千个 CX 门的许多其他论文。这突出了这样一个事实,即我们正在从最初的小规模电路,这些电路很容易用经典方法模拟,发展到超过 100 量子比特且包含数千个 CX 门的电路。随着电路规模的增长,无论是宽度(量子比特数量)还是深度(CX 门数量),用经典方法模拟电路的成本也在增加。这里的成本可以指精度损失或速度。这个时代,在我们达到量子优势之前的时间,被称为量子效用。此外,使用本书前面讨论的错误缓解技术,可以在 100+量子比特系统上运行这些复杂电路。效用时代意味着我们可以找到有用的量子应用来运行,而无需等待容错量子计算机。
现在我们已经熟悉了量子效用是什么,让我们来看看作为开发者我们应该理解什么,以便利用量子技术这一最新进展来构建更复杂的量子电路。
理解 Qiskit 模式
2023 年 IBM 量子峰会上的另一个公告是介绍了 Qiskit 模式。Qiskit 模式源于这样一个想法:随着电路变得更大、更复杂,计算科学家不应关心硬件层面发生的事情。计算科学家不需要了解在特定量子比特上使用哪个门,或者当将电路转换为硬件时使用哪个优化器是理想的。计算科学家应该有可以使用的工具,这些工具可以为他们提供最新的硬件和软件,并简化这些 100 多量子比特系统的使用。这些工具应该为科学家提供一种生成代码或函数的方法,以解决特定问题或一系列问题,而不是一个量子比特一个量子比特、一个门一个门地创建电路。因此,Qiskit 模式的目的就是为计算科学家提供一种将量子计算例程注入他们现有应用程序和工作流程的方法。
既然我们已经知道了 Qiskit 模式的目的,让我们来看看它们是什么以及如何使用它们。在深入细节之前,我确实想强调这是一个一直在改进的新功能。代码及其使用可能会有很多变化。我这里写的是写作时的当前状态。正如我们所知,所有编程语言都会有更新或变化,所以我敦促您首先查看文档,以便了解最新的发展。即使代码有变化,整体步骤的概念仍然应该是相同的。
简而言之,Qiskit 模式由 4 个步骤组成,用于在量子计算机上运行算法,并提供给您作为应用程序和/或工作流程一部分的结果。这四个步骤是:映射、优化、执行和后处理。我们将学习每个步骤的作用,并使用一个简单的电路运行一个示例。当然,您可以使用任何您想要的电路。
第 1 步,映射
在这一步,我们希望将问题映射到一个量子电路中。这涉及到将问题和输入编码到量子电路或状态中。我们可以使用的简单例子是编码一个二进制图像。这是一种每个像素要么是黑色要么是白色,分别对应 0 或 1 的图像。读取每个像素很简单,如果像素是黑色,我们就保持状态为 0;如果是白色,我们添加一个 X 门将其改变为 1 状态。当然,这意味着我们需要与像素数量相同的量子比特,所以这并不容易扩展到拥有数百万像素的大图像。
因此,我们需要找到方法来编码这些图像,这样它们就不需要数百万个量子比特。这就是编码派上用场的地方。如果我们能找到一种方法将像素编码成量子状态,那么这将非常有效。当然,编码图像的方法也有很多,多得无法在此一一列举,但通过搜索量子图像处理,你应该能找到许多形式。其中一些最早的形式是NEQR,代表数字图像的新型增强量子表示,以及FRQI,代表量子图像的灵活表示。使用 FRQI 的映射将像素的强度值映射到单个量子比特上,比如说一个 256 位的灰度图像,即π/256 代表像素值。在 NEQR 中,你会使用 8 个量子比特来映射值,其中每个量子比特被设置为二进制值,因此量子比特的强度不是由 1 个量子比特(如在 FRQI 中)表示,而是由 8 个量子比特(8 个量子比特映射到表示从 0 到 256 所需的 8 个二进制值)表示。这些只是其中两种,但还有很多更多,每种都有其自身的优缺点。这意味着,作为计算科学家,你大量的工作就是选择你认为最适合你实验的编码或映射。这种映射应确保你的问题不仅被编码到量子状态中,而且是以一种提供最佳方式来表示你希望解决的问题的方式编码的。一旦你选择了问题的适当映射和输入,就会生成一个电路。我们现在可以进入 Qiskit 模式中的下一步,即优化。
第 2 步,优化你的电路
正如我们在前面的章节中提到的,优化电路不仅仅是将电路映射到量子硬件上的一个步骤;它有很多不同的方面。幸运的是,其中大部分都是自动为我们完成的,但它也为我们提供了改变它们的方法,无论是通过设置optimization_level选项值来指定要执行优化类型,还是通过最近添加的resilience_level选项值,这允许我们选择要应用到电路上的错误抑制和错误缓解类型。由于这些大多数都需要一些经典资源,这为我们提供了设置使用多少这些经典资源的方法。编译和优化的结果是称为量子指令集架构(QISA)。这个优化电路是针对我们选择的执行此电路的量子硬件的,这使我们进入下一步,即执行。
第 3 步,执行你的电路
需要注意的一点是,当我们说执行一个电路时,这并不意味着我们将在量子系统上执行这个电路,尤其是在运行需要执行过程中进行一些经典交互的变分量子算法时。这就是为什么像估计器和采样器这样的原语是很好的构建块,因为它们提供了执行这些电路所需的必要上下文,以便在执行时间允许经典和量子交互。执行一个电路可以使用多种模式之一,例如会话、作业和批量。
我们在第十章中讨论了这些问题,抑制和缓解量子噪声,其中我们介绍了电路如何在量子系统上运行。现在我们已经在一个量子系统上执行了我们的电路,我们现在已经达到了 Qiskit 模式中的最后一步,即后处理。
第 4 步,后处理
这只是获取我们从执行电路中获得的结果并将它们处理成我们的经典应用程序或工作流程所期望的格式的问题。到目前为止,我们一直将结果显示为图表或文本,以便我们可以查看并理解这些结果的意义。但是,当与应用程序或工作流程集成时,结果可能需要以某种方式格式化或与其他系统中的各种结果汇总为一个集合。在两种情况下,后处理步骤只是处理结果并以一种便于将其集成到应用程序本身的方式将其传递回调用系统的问题。这当然取决于结果在下一步或显示中的使用方式。
实质上,这 4 个步骤简单直接,相当简单。但话又说回来,这正是重点!理想情况下,作为开发者、计算科学家或量子爱好者,你不应该需要为如何创建、执行和从量子系统中获取结果而挣扎。这个过程应该是非常直接和简单的。说到简化过程,在峰会期间还有另一个宣布,那就是很快将包含人工智能,它能够自动生成量子代码。尚未宣布何时以及其他人工智能功能将上线,但代码很快将包含这一功能。这应该有助于那些刚开始的新手,因为它可以作为工具询问如何创建某些电路,例如格罗弗、西蒙等,当然也应该帮助那些希望将现有电路扩展到这些更大、更复杂的电路中的经验丰富的研究人员,这可能会将他们的解决方案推向量子优势的道路。
现在,话虽如此,我不会让您失望!我确实在这里留了一些编程内容,以免在没有代码的章节中结束。我们将实现上一章中构建的内容,使用 Grover 算法确定最佳结果的简单逻辑表达式。这是一个简单的例子,我们将从一个逻辑表达式开始,我们可以将其称为经典数据输入。然后我们将继续使用 Sampler 原语,以便我们可以在本地运行它,定义问题,并使用 Grover 算法类来解决问题。这次,我们将使用方法输入问题并提供最佳结果,然后我们将打印出来并直观地显示,从而完成混合经典-量子应用。让我们开始吧!
实现逻辑表达式预言机
逻辑表达式通常用于描述问题,尤其是那些有某些约束条件的问题。这些逻辑表达式可以用来构建电路并在各种算法上执行。让我们从一个简单的问题开始。请注意,名字已经更改以保护那些难以保持团结的乐队的隐私。
梅尔巴,一位音乐制作人,被委以组建下一个大型摇滚乐队,基于目前与唱片公司有合约的音乐家。以下音乐家可供选择:
-
伊万娜是一位嗓音出色的歌手,可以尽快进行巡演。
-
卡拉也是一位嗓音出色的歌手,也可以立即进行巡演。
-
莱克斯是一位可以演奏任何风格的吉他手,有自己的巡演巴士。
-
利奥是一位与每个人都相处得很好的鼓手,在业界非常受欢迎。
现在,这就是音乐制作人要求您解决的问题:伊万娜和卡拉在巡演中往往相处不来,并且在创作音乐时也出现过创意分歧。另一方面,莱克斯和利奥在录音室和巡演中相处得很好。然而,伊万娜和莱克斯在最近一次巡演后分手了,因此我们可以有以下几种选择:只有伊万娜或利奥可以参加,或者两者都不参加,简单地用他们替换。
您需要做的是确定这四位音乐家中哪一组合最适合您组建乐队,然后根据他们共同的历史,尽量减少问题地进行巡演。
为了解决这个问题,让我们将其写成逻辑表达式:
-
我们将每位音乐家映射到一个变量,例如:
A = 伊万娜,B = 卡拉,C = 莱克斯,和D = 利奥。 -
接下来,我们将使用逻辑运算符创建一个逻辑表达式来展示约束条件。首先,我们知道伊万娜和卡拉相处不来,因此我们可以用以下方式表示,其中^表示异或。这意味着我们需要至少其中一人参与,但不能同时两人都参与:
。 -
接下来,我们知道 Lex 和 Leo 相处得很好,因此我们可以用 AND 运算符来表示它们,如下所示:
. -
最后,我们知道 Ivana 和 Lex 刚刚结束了他们的关系,所以他们可能不会愿意一起工作和巡演。我们将它们表示为 NAND,如下所示。这表明他们不能一起工作,但它将允许他们都不参加:
. -
将这些放在一起,我们这个例子中完整的逻辑表达式如下所示:
.
现在我们已经定义了我们的逻辑表达式,让我们在逻辑表达式中创建一个 oracle,这样我们就可以使用Grover 算法来搜索最佳结果。
-
我们将首先导入执行这些步骤所需的所有必要模块和类,并定义我们的逻辑表达式:
# Import the necessary modules and classes from qiskit import QuantumCircuit from qiskit.visualization import * from qiskit_algorithms import Grover, AmplificationProblem from qiskit.circuit.library.phase_oracle import PhaseOracle # State the SAT problem into a logical expression # A = Ivana, B = Karla, C = Leo, D = Lex expression = '((A ^ B) & (C & D) & ~(A & C))' -
现在我们已经将问题定义为逻辑表达式,让我们使用这个逻辑表达式来创建我们的
oracle,如您所回忆的那样,这是我们用来描述我们希望解决的问题的工具。在这种情况下,它代表逻辑表达式。请注意,在代码中我包含了一个用于安装 PhaseOracle 依赖项的单元格(pip install tweedledum),因为你可能也需要安装 Qiskit 算法(pip install qiskit-algorithms)。我已经将该行注释掉,以防你已安装;然而,如果你在执行需要依赖项的单元格时遇到错误,只需使用命令行指令在你的开发环境中安装,然后重新启动内核。这样就可以完成安装,然后你可以运行代码而不会出现依赖项错误:# Create a PhaseOracle based on the logical expression oracle = PhaseOracle(expression) -
现在我们已经从逻辑表达式中创建了一个
oracle,我们可以通过调用AmplificationProblem()方法并传递我们希望解决的表达式作为其参数来创建问题:# Construct the amplification problem from the oracle problem = AmplificationProblem(oracle, is_good_state=oracle.evaluate_bitstring) problem.grover_operator.oracle.draw()
上述代码从 Grover 运算符中调用 oracle,其中包含 oracle 的量子电路表示,如下所示:

图 13.1:表示逻辑表达式 oracle 的量子电路
-
我们可以看到,这个预言者描述了逻辑表达式,其中 q[0]、q[1]、q[2]和 q[3]代表我们的逻辑表达式,其中 q[0] = Ivana,q[1] = Karla,q[2] = Lex,和 q[3] = Leo。注意,q[0]中的控制位没有填写。这是为了表示它在 q[0]的状态值为![img/B18420_13_005.png]时被触发,而与其他不同,它们是在量子位处于![img/B18420_13_006.png]状态时被触发的。我们现在可以使用这个预言者来执行任何基于预言者(预言性)算法。由于我们正在寻找这个摇滚乐队问题的解决方案,让我们使用 Grover 算法。首先,我们将设置一个会话并使用 Sampler 原语来演示这个例子,然后使用采样器作为原语创建 Grover 算法类。然后,我们将之前单元格中定义的问题传递给
amplify方法,这个方法本质上会运行 Grover 算法,提供结果,并显示它:# Using the Sampler to run circuit from qiskit.primitives import Sampler # Set the Options sampler = Sampler()grover = Grover(sampler=sampler) result = grover.amplify(problem) plot_distribution(result.circuit_results[0])
上述代码产生以下输出,它代表了我们要寻找的值:1110。请注意,位置 0 的量子位表示为最低有效位(最右边)。这意味着结果1110等于 D=1,C=1,B=1,A=0:
我们还获得了以下准分布:

图 13.2:基于逻辑表达式预言者的 Grover 解决方案结果
如前所述的结果所示,该算法表明我们的解决方案是1110。这意味着 Karla、Leo 和 Lex 是下一个乐队项目的三位理想音乐家。Melba 已经决定招募 Ivana 作为独唱歌手。
当然,这是一个简单的例子。正如您所想象的,如果您的表达式更复杂,那么 Grovers 搜索将有助于在几行代码中确定这一点。正如您所看到的,您可以将由经典系统上的逻辑表达式定义的问题,通过利用 Qiskit 运行时和 Grover 算法类,以各种方式准备该问题,所有这些都可以在量子系统上执行。所有这些都可以在不深入研究量子算法底层(即量子门、算子、错误缓解等)的情况下完成。
随着 Qiskit 继续添加更多功能、算法、实用工具和其他工件,这将帮助您创建灵活且模块化的量子应用程序,满足您的所有需求。
摘要
在本章中,我们介绍了 Qiskit 模式,这些模式专注于帮助您快速开发健壮的量子电路,以便将其包含到您的应用程序和工作流程中。随着 Qiskit 1.0 的发布和未来的变化,我也强烈建议您跟上最新的变化。Qiskit 1.0 有一个很好的路线图,其中包含对原语、模式和许多功能的更新,这些更新不仅可以帮助您跟上开发过程,还可以确保您的电路可以扩展到更大的实用电路规模。
现在你已经掌握了理解如何开始创建量子应用程序和使用 Qiskit 模式简化创建过程的一般技能,你可以开始将这些功能应用到现有的电路中,或者如果你是初学者,可以应用到新的电路中。
最后,我们查看了一个需要使用 Grover 量子算法解决的问题,而不必真正理解它们是如何构建的,这意味着我们不必创建量子电路、预言机或扩散算子来运行该算法。重点是表示问题,在这种情况下是一个逻辑表达式,并通过算法提供的可用类和方法组合来应用它,以呈现问题并解决问题。只需记住,随着我们朝着量子实用性的方向发展,将会有更高效和现代的方法来实现这一点以及未来的算法。正如本书中提到的,这些只是帮助你入门的原则和一般编程实践,但随着量子计算的发展,算法以及我们思考编程的方式也将随之发展。这就是你应该从本书中吸取的,这是一个起点,让你可以继续推动你的独创性和想象力。但就目前而言…
恭喜!你已经迈出了非常关键的一步,开始学习和构建量子应用程序。最初,我们遵循自下而上的方法,即首先在本地安装的模拟器和量子计算机上创建和运行量子电路。这样做帮助你理解量子电路在量子系统上构建和执行的内幕。接下来,你回顾了区分经典和量子应用程序的各种算法和量子计算原理。通过运行这些应用程序,你还可视化了结果,当然还有各种环境效应引起的影响,这反过来又帮助你了解减轻量子计算机噪声效应的各种功能。最后,你查看 Qiskit Runtime 提供的自上而下的方法,帮助你快速了解这些算法在各个领域如何用于解决小问题。希望这本书为你提供了一个简单的入门途径,简化你进入量子计算世界的旅程,并且,一如既往,我期待阅读你的研究论文或可能是你自己的教科书!
欢迎来到量子世界!
问题
-
Qiskit 模式有哪四个步骤?
-
哪个 Qiskit 模式处理将量子电路映射到硬件?
-
当将你的问题编码到量子状态时,你处于哪个 Qiskit 模式步骤?
-
为什么我们在 Grover 示例中使用采样器而不是估算器原语?
加入我们的 Discord
加入我们社区的 Discord 空间,与作者和其他读者进行讨论:
packt.link/3FyN1

附录 A:资源
除了这本书之外,还有很多其他资源可供您使用,我强烈推荐您使用这些资源来深入理解量子计算以及如何找到最新的研究。正如您将注意到的,量子技术正在以非常快的速度发展,以至于在撰写本书时,跟上它的步伐相当困难。信不信由你,更新和保持这一版本的更新所花费的时间比撰写第一版的时间还要长!但请相信我,等待是值得的,因为量子计算的现状不仅改变了我们的编程方式,也改变了我们构建和运行电路的方式。过去难以实现的事情,比如在门级别创建电路,现在使用 Qiskit Runtime 的原语和其他功能要容易得多。
以下资源将帮助您理解不仅基础知识和技术,还能让您跟上最新的功能和发布。实际上,在撰写本书期间发布的 Qiskit 1.0 版本包含的内容如此之多,以至于可能需要一本完全关于它的书!附录中的内容远非资源列表的详尽无遗,但它确实包含了关键链接和信息:
-
IBM 量子学习平台:您可以在
learning.quantum.ibm.com找到各种适合所有水平的学习资源。在这里,您将找到各种学习材料、教程和在线课程,包括量子计算以外的主题,例如量子密码学的介绍。所有课程都非常适合初学者和经验丰富的研究人员和开发者;每门课程都会逐步引导您从基础知识到高级主题。 -
文档:完整文档的链接是
docs.quantum.ibm.com/。这是本书讨论的所有主题的主要文档页面。您会希望将此链接添加到书签中,因为它将是您的指南,有时还能节省大量时间,帮助您将代码迁移到新版本或熟悉新功能。 -
Qiskit GitHub 仓库:Qiskit 的 GitHub 仓库链接是
github.com/Qiskit。这是您将找到 Qiskit 开源代码的 Qiskit GitHub 仓库。像大多数其他开源 GitHub 项目一样,您可以通过解决任何问题或实现设计请求来分叉并贡献开源项目。您还可以提交增强请求或您可能发现的任何问题。如果您是第一次,建议您从标记为“good first issue”的问题开始工作。 -
Qiskit Slack 社区:加入 Qiskit Slack 社区的链接为
qisk.it/join-slack。这是 Qiskit 社区的全世界 Slack 频道。在这里,真正的乐趣开始了!你不仅会找到量子研究人员、教授、学生和量子爱好者,还会找到讨论量子及 Qiskit 各方面内容的各种频道,从硬件到软件,到几乎你能想到的任何量子项目。这是你可以遇到与你相似的人的地方,他们要么刚刚开始,要么想要提高他们的知识。 -
量子算法动物园:正如欢迎页面所示,本网站是一个量子算法的全面目录。它提供了关于算法类型、加速和算法描述的详细信息。它们被分为诸如代数和数论、预言机、近似和模拟等类型。如果你想要快速了解一个算法及其工作原理,这将非常有用。本页面的链接为
quantumalgorithmzoo.org/。 -
Arxiv:量子物理:这是一个开放获取的学术论文和文章文档库,位于
arxiv.org/archive/quant-ph。这个资源专门用于涵盖量子物理主题的文章,该主题还包括许多量子计算论文。 -
IBM 量子网络量子论文:这个资源提供了一份由 IBM 量子研究团队和IBM 量子网络合作伙伴及成员发布的所有研究论文的列表:
ibm.biz/quantum-network-arxiv。
该链接指向一个AirTable,一个在线混合电子表格数据库,列出了与特定项目相关的行业领域,例如金融、优化、化学、机器学习、错误缓解、量子效用等。它还包括公司名称以及他们在研究期间使用的量子计算机。还包括所有列出的论文的 Arxiv 链接。这是一个确保你跟上最新研究以及更广泛的量子研究领域的绝佳资源。
-
Qiskit YouTube 频道:该频道的 URL 为
www.youtube.com/Qiskit。这是官方 Qiskit YouTube 频道,上面有关于研究开发的几个系列,经常安排上传,以保持观众了解 IBM 量子研究团队、IBM 量子网络合作伙伴及成员的最新内容和研究成果。 -
SciRate:这个网站(
scirate.com/)汇集了每个类别中评分最高的 Arxiv 论文,包括量子物理。如果你有时间阅读大多数人从 Arxiv 上阅读的内容,这是一个很好的资源。
加入我们的 Discord
加入我们社区的 Discord 空间,与作者和其他读者进行讨论:
packt.link/3FyN1

附录 B:评估
第一章:– 探索 IBM 量子工具
第 1 题
哪个应用程序包含你的 API 令牌?
答案
有两个地方可以获取你的 API 令牌。第一个是账户设置视图,其中可以找到 API 令牌,并且如果需要可以生成不同的令牌。另一个是在主仪表板上。
第 2 题
在你的资源列表中,哪个设备量子比特最少?
答案
最少量子比特的设备可用性将根据可用的量子设备而变化。然而,在撰写本文时,有至少 127 个量子比特的系统。下一个系统将是 133 个量子比特或更多。这是由于最近转向 IBM 量子平台以提供量子实用系统(超过 100 个量子比特)。
第 3 题
哪个应用程序会提供量子系统的量子比特图?
答案
IBM 量子平台提供了计算资源应用程序视图,该视图显示所有可用的量子系统。
第二章:– 使用 IBM 量子作曲家创建量子电路
第 1 题
从作曲家,你可以在哪里找到在量子计算机上运行你的电路所需的时间?
答案
你可以在作曲家工作视图的状态时间线视图中找到它。
第 2 题
你如何在作曲家上添加或删除量子比特到你的电路?
答案
你会点击一个量子比特并选择+或垃圾桶图标来分别添加或从电路中删除量子比特。
第 3 题
在哪个视图中指定要运行电路的量子系统?
答案
你可以使用设置和运行视图,该视图可通过作曲家视图访问,来选择系统。
第 4 题
哪个球体最适合在单个球体中查看三个量子比特的量子状态?
答案
你会使用 qsphere 来表示单个球体中的多个量子状态。
第三章:– 介绍和安装 Qiskit
第 1 题
用你自己的话描述内核开发者和应用程序开发者之间的区别。
答案
通常,内核开发者可以被视为为量子系统创建特定电路的开发者。这与经典汇编开发者类似。应用程序开发者会将量子算法集成到经典应用程序或工作流程中。
第 2 题
如果你想获取电路的单位矩阵,哪个模拟器会提供单位矩阵结果?
答案
来自 Qiskit Aer 库的单元模拟器。
第 3 题
你能用自己的话描述 Aer 提供的五个模拟器类别中的每一个吗?
答案
每个细节都在章节中描述;这个问题是为了检查你对每个模拟器的直观理解。例如,状态向量模拟器不需要测量,因为它只计算电路的最终状态。
第 4 题
你需要导入哪个模块来绘制直方图?
答案
Qiskit 可视化模块。
第四章:– 理解基本量子计算原理
问题 1
你会如何创建一个纠缠两个量子位的电路,其中每个量子位都不同(即01,10)?
答案
我们可以使用以下代码创建一个纠缠两个量子位的电路:
qc = QuantumCircuit(2,2)
qc.h(0)
qc.x(1)
qc.cx(0,1)
qc.measure([0,1], [0,1])
qc.draw()
问题 2
创建一个包含多量子位门的电路,例如受控 Hadamard 门。
答案
电路只需确保有两个量子位,并包含一个受控 Hadamard 门(ch)。在以下示例中,第一个量子位是控制位,第二个量子位是目标位。我们向控制量子位添加一个 Hadamard 门,以确保我们得到0或1;否则,控制位永远不会被设置:
qc = QuantumCircuit(2,2)
qc.h(0)
qc.ch(0,1)
qc.draw()
问题 3
在电路中创建所有 4 个贝尔态。
答案
电路只需有两个量子位,并包含一个受控-X 门(cx),其中第一个参数是控制位,第二个是目标位。在以下示例中,第一个量子位是控制位,第二个量子位是目标位。我们向控制量子位添加一个 Hadamard 门,以确保我们得到0或1;否则,控制位永远不会被设置:
qc = QuantumCircuit(2,2)
qc.h(0)
qc.cx(0,1)
qc.draw()
后续电路只需改变cx门控制位和目标位的方向,并确保 Hadamard 门与控制位在同一量子位上。
问题 4
三个量子计算原理是什么?
答案
三个量子计算原理是叠加、干涉和纠缠。叠加和干涉在经典物理学中描述,而纠缠在量子物理学中描述。
第五章:– 理解量子位
问题 1
哪个可以提供关于量子位相位的视觉信息——Bloch 球体还是 qsphere?
答案
都有。Qiskit 球体(q-sphere)包括一个复选框来可视化相位信息。Bloch 球体通过向量在相位不同时的位置来表示相位,也就是说,它是绕z轴旋转的。
问题 2
你能在 Bloch 球体上可视化多个量子位吗?如果不能,请描述为什么不能。
答案
不,我们不能像在 qsphere 上那样在 Bloch 球体上可视化多量子位。Bloch 球体通常用于表示给定状态的单一量子位向量位置,而 qsphere 包括相位。要使用 Bloch 球体表示多个量子位,你需要为每个量子位有一个 Bloch 球体。
问题 3
将三个量子态的张量积以所有形式写出来。
答案
对于一个三量子位系统,结果基态为
。
问题 4
三量子位系统的概率振幅是什么?
答案
三量子位系统的振幅为
,其中n是量子位的数量,结果为
。
第六章:– 理解量子逻辑门
问题 1
对于多量子位门,尝试翻转源和目标。当你分解电路时,你看到有什么区别吗?
答案
不,没有看到任何差异,只是现在源被分配到了相反的量子比特上。
问题 2
将单量子比特和多量子比特电路的所有门分解。您注意到了关于通用门如何构建的哪些方面?
答案
单个门现在通过其各自的基门显示,包括任何旋转值。多量子比特门,如 Toffoli 门,也被分解为用于构建指定量子比特之间 Toffoli 门操作的特定门。
问题 3
实现目标为三量子比特电路中心量子比特的 Toffoli 门。
答案
使用以下代码实现 Toffoli 门,其中目标是三量子比特电路的中心量子比特:
qc = QuantumCircuit(3)
qc.ccx(0,2,1)
qc.draw()
问题 4
分解 Toffoli 门。总共使用了多少个门来构建它?
答案
在将 Toffoli 门分解为 Hadamard、T、T dagger 和 CX 门时,总共有 15 个门,并且深度运行 11 次。值得注意的是,如果开发出更有效的 Toffoli 门组合方式,深度可能会改变。
问题 5
将 Toffoli 门和 Hadamard 门应用于状态向量模拟器,并将结果与采样原语的结果进行比较。您看到了哪些差异,为什么?
答案
采样原语默认运行 1024 次,因此将产生大约 50% 的000和 50% 的001的结果,假设 Hadamard 门放置在第一个量子比特上。另一方面,仅运行一次的态向量模拟器将产生状态000或001;结果将取决于您放置 Hadamard 门的量子比特。
问题 6
如果您想要以相反的方向对三个量子比特进行排序,您将使用哪些门以及它们的顺序?
答案
您可以使用swap门在任意两个量子比特之间切换每个量子比特的值(以下是一个两个量子比特的示例):
qc = QuantumCircuit(2)
qc.x(0)
# current state is '01'
qc.swap(0,1)
# current state is reversed, '10'
第七章:– 使用 Qiskit 编程
问题 1
构建一个宽度为4、深度为9的随机量子电路。
答案
from qiskit.circuit.random import random_circuit
#Circuit with a width = 4, a depth = 9
qc = random_circuit(4, 9, measure=True)
问题 2
创建另一个与您在问题 1中创建的电路具有相同宽度的随机量子电路,并将其连接,以便在您创建的随机量子电路之前添加。
答案
qc1 = random_circuit(2,2)
qc_combined = qc.compose(qc1, [0,1], front=True)
问题 3
打印从问题 3连接的量子电路的电路属性,并指定操作符的总数,不包括任何测量操作符。
答案
qc_combined.draw()
qc_combined.count_ops()
问题 4
创建一个参数化的 R[Y] 门电路,使其旋转角度为
。
答案
import numpy as np
from qiskit.circuit import Parameter
param_theta = Parameter('Ɵ')
qc = QuantumCircuit(2)
qc.rz(param_theta,0)
qc = qc.assign_parameters({param_theta: np.pi/2})
qc.draw()
第八章:– 优化和可视化量子电路
问题 1
您能说出翻译器组件的两个组件吗?
答案
Pass和PassManager。
问题 2
哪个组件允许您指定要使用的步骤?
答案
PassManager用于指定使用哪些步骤以及哪些步骤可以与其他步骤通信。
问题 3
当运行 transpile() 函数时,默认的 optimization_level 值是什么?
答案
优化级别 1。
问题 4
列出三个布局选择传递。
答案
显而易见,密集,和 Sabre。
第九章:– 模拟量子系统和噪声模型
问题 1
你可以列出 Qiskit Aer 模块中找到的所有模拟器吗?
答案
可以使用 Aer.backends() 函数生成模拟器的列表。
问题 2
使用单个哈达玛门和相位门,在负 Y 轴上创建一个量子比特的 qsphere 表示,创建状态
。
答案
为了完成这个任务,你需要将量子比特设置在叠加态。这可以通过使用哈达玛门(H)来完成,这将量子比特置于状态
。之后,我们将不得不从
状态运行一个相位偏移到
状态,这意味着我们需要一个相位门来将状态偏移一个
的相位,如下所示:
qc = QuantumCircuit(1)
qc.h(0)
qc.sdg(0)
simulator = Aer.get_backend('statevector_simulator')
transpiled_qc = transpile(qc, backend=simulator)
result = simulator.run(transpiled_qc).result()
statevector = result.get_statevector(transpiled_qc)
statevector
问题 3
在初始化电路中的一组量子比特时,所有状态的总概率必须是多少?
答案
在 initialize 函数参数中,param 值的总平方和必须加起来等于 1,如下例所示,其中
设置了两次。因此,如果你取平方和,它将等于 1:
import numpy as np
qc = QuantumCircuit(2, 2)
init_qubits = [0, 1]
qc.initialize([1, 0, 0, 1] / np.sqrt(2), init_qubits)
问题 4
你可以使用 qsphere 来可视化量子比特的相位和概率信息吗?
答案
是的,相位的给出由状态向量的颜色决定,概率通过状态向量尖端的大小来可视化。直径越大,概率越高。
问题 5
如果你将去极化错误值设置得接近 1,会发生什么?
答案
这将设置
的值为 1,因此完全去极化通道。
问题 6
如果你将读出错误平均地应用于所有量子比特,你期望的结果是什么,为什么?
答案
在模拟器上运行时,而不是导致理想条件(无错误),你将看到错误,其中错误的显著性基于设置的 ReadoutError() 参数。
第十章:– 抑制和缓解量子噪声
问题 1
列出 Qiskit 运行时服务使用的三种主要错误缓解技术。
答案
最小缓解,中等缓解,和重度缓解。
问题 2
用于错误缓解的哪些容错级别被使用?
答案
级别 0 到 2,其中级别 1 是默认值。
问题 3
你可以使用哪些其他可逆门来使用动态去耦填充量子比特的空闲时间?
答案
任何可逆门,例如 XGate 或 YGate。
问题 4
哪种类型的噪声会导致量子比特振幅衰减?
答案
热噪声。
问题 5
哪种类型的噪声会导致量子比特去相干?
答案
白噪声,粉红噪声,和磁通噪声。
第十一章:– 理解量子算法
问题 1
你会使用哪个算法来确定一个n-位字符串是否平衡?
答案
Deutsch-Jozsa 算法可以用来确定一个函数是否是常数或平衡的。
问题 2
实现伯恩斯坦-瓦齐拉尼算法以找到状态170。
答案
要创建电路,你需要 170 的二进制表示。然后,在所有量子比特(除了辅助量子比特)应用 Hadamard 门之后,首先对该辅助量子比特应用 NOT 门,然后应用 Hadamard 门,对表示 170 的二进制值(表示为值10101010)的每个量子比特应用 CX 门,170 的二进制值表示为10101010。因此,您将对每个奇数量子比特应用 CX 门,其中每个 CX 门的控制应设置为量子比特1、3、5和7,每个 CX 门的目标是辅助量子比特。然后,对除了辅助量子比特之外的所有量子比特应用 Hadamard 门,然后应用测量算子。
问题 3
有多少个 oracle 函数?
答案
通常,大多数算法都有一个 oracle 函数;然而,有些算法需要多个,或者函数的多次运行,例如 Grover 算法,它根据电路的量子比特数重复 oracle 和扩散算子函数。
第十二章:– 应用量子算法
问题 1
你可以使用周期函数解决哪些其他问题?
答案
量子傅里叶变换(QFT)是解决周期函数中较为流行的算法之一。
问题 2
在 5 个量子比特的状态上实现 QFT——例如:‘10110’。
答案
使用书中相同的示例,只需通过添加另一个量子比特来扩展另一个交换门的重复次数。
问题 3
使用 Grover 算法,找到以下状态,‘101’,‘001’,和‘010’。
答案
简单地将参数上的数值更改为上述值,然后观察电路的 oracle 表示每个三个值的变化。由于所有值都是 3 个量子比特的长度,每个值的重复次数将相同(仅一次)。
问题 4
你需要运行 Grover 的扩散算子多少次才能找到状态
?
答案
使用以下内容来确定重复函数的次数,
,其中 N 是量子比特的数量。这里,N=4。
问题 5
重新运行 Grover 的搜索示例。仅重复 Grover 的扩散算子两次,并注意结果中的差异。你看到了什么不同?如果你运行它超过三次,你会期望它如何改变?
答案
我们正在寻找的值与其他值之间的振幅差异将不同,要么增加,要么减少。
第十三章:– 理解量子效用和 Qiskit 模式
问题 1
Qiskit 模式的四个步骤是什么?
答案
映射、优化、执行和后处理。
问题 2
哪个 Qiskit 模式处理将量子电路映射到硬件?
答案
优化步骤处理将电路映射到硬件的过程。
问题 3
当您将问题编码成量子态时,您处于哪个 Qiskit 模式步骤?
答案
您正在将经典数据的输入值映射到量子态。
问题 4
为什么我们在 Grover 示例中使用了采样器而不是估计器原语?
答案
我们不需要使用任何可观察量来解决 Grover 算法。只需返回结果的准分布就能提供答案。
加入我们的 Discord
加入我们社区的 Discord 空间,与作者和其他读者进行讨论:
packt.link/3FyN1



符号表示,从门列表拖到第一个量子比特上,如下面的截图所示:
,我们将使用初始化状态
。让我们看看 Bloch 球和 QSphere 的结果:
,仅使用一个哈达德门和相位门。
。
,其中输入寄存器将输入到我们的黑盒,或或然函数
。
。
。在这里,我们将反转态并在电路中编码
的状态?
。
.
.
.
浙公网安备 33010602011771号