├── .gitignore ├── README.md ├── backup ├── Image │ ├── ABC.png │ ├── BOperation.png │ ├── BlochSphere.png │ ├── ControlledG.png │ ├── ControlledGateG.png │ ├── HGate.png │ ├── MaxTangleQuantumCircut.png │ ├── Measure.png │ ├── NewQSharpProject.png │ ├── QuantumStateVector.png │ ├── Teleportation.png │ └── XZ.png ├── README.md ├── SUMMARY.md ├── book.json ├── chapter0.md ├── chapter1.md ├── chapter2.md ├── chapter3.md └── question.md ├── image ├── BlochSphere.jpg ├── CNOT.jpg ├── ClassicalGatesAndQuantumGates.jpg ├── ControlledZ.jpg ├── ControlledZReverse.jpg ├── CreateQSharpApp.png ├── DiyAdjointAndControlled.png ├── Hadamard门可视化.jpg ├── NAND.jpg ├── PowerOf2.jpg ├── QuantumCircuit.jpg ├── QuantumCircuitDIvide.jpg ├── QuantumGatesAndMatrices.jpg ├── Toffoli门.jpg ├── TrivialGates.jpg ├── UniversalGateSet.jpg ├── cat.jpg ├── teleportation.jpg ├── toffoli表.png ├── 二维空间态矢量.jpg └── 量子线路符号.jpg ├── index.html └── q-sharp-learning.pdf /.gitignore: -------------------------------------------------------------------------------- 1 | # Node rules: 2 | ## Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 3 | .grunt 4 | 5 | ## Dependency directory 6 | ## Commenting this out is preferred by some people, see 7 | ## https://docs.npmjs.com/misc/faq#should-i-check-my-node_modules-folder-into-git 8 | node_modules 9 | 10 | # Book build output 11 | _book 12 | 13 | # eBook build output 14 | *.epub 15 | *.mobi 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | *There are much many text of Latex Mathtype in this document,yet it is hard for Github to render these Mathtype.So I strongly recommend you to browse this html page : 2 | [HTML](https://swardsman.github.io/learning-q-sharp/) 3 | for a perfect experience.* 4 | 5 | # 写给开发者的量子计算入门教程 6 | 7 | ## --- 基于Q#语言描述 8 | 9 | 10 | 11 | **$version: 0.1.3$** 12 | 13 | **$date:2019-09-25$** 14 | 15 | 16 | 17 | ## 前言 18 | 19 | 写作这份文档时,我只是一个对计算机、对量子计算充满好奇心、有着极大兴趣的人。从始至终,我深知那些深奥的物理定理和数学分析对初学者而言意味着怎样的难度和挑战,因此,我写这份文档的目的就是希望它能稍稍降低量子计算的门槛,如果我的创作真的帮助到了你,那么我将十分荣幸。 20 | 21 | 在这份文档中,我不会涉及过多和过深的物理学、量子力学以及数学等相关内容,但希望读者能够有一定的线性代数和概率论的基础,这个基础不需要多深,只要了解过基本的矩阵、向量、空间等知识即可。为了达到降低门槛通俗易懂的目的,我极力地削减了文档中与数学及其他学科有关的内容,但是还是有一些内容不得不引入一些数学计算、证明等,但是我不希望你纠结于这些内容,作为开发者,底层的东西对我们而言应该是透明的,我们只需要知道它是什么,给它一个输入它会产生什么样的输出即可,所以我们应该重点关注那些结论性的东西,数学证明之类的只是为了辅助理解,不应该成为我们的重点。 22 | 23 | 从上世纪二十年代到现在,量子力学已经诞生了百年时间,相信很多人都听说过“测不准原理”和“薛定谔的猫”,量子力学就是以这样违背我们正常认知的特性让人类爱恨交加。量子计算最早是由[Paul Benioff在](https://en.wikipedia.org/wiki/Paul_Benioff)在上世纪80年代提出的,之后经过了[Richard Feynman](https://en.wikipedia.org/wiki/Richard_Feynman)等人的发展和补充,量子计算开始真正进入人们的视野,目前为止,对量子计算的研究已经有了一定的成果,加拿大[D-Wave](https://en.wikipedia.org/wiki/D-Wave_Systems)系统已经研制成功16位量子比特的超导量子计算机,Google、微软等国际巨头也已经开始在量子计算方面发力,国内的阿里巴巴、中科院等也取得了一定的成绩,除了硬件上的成果,量子计算所需要的开发工具也已经逐渐成熟,Qbsolv、QCL、 LIQU等许多量子编程语言已经得到了实际的应用。 24 | 25 | 这份文档所专注的部分就是量子程序开发,并且使用微软公司推出的量子开发工具:Q#语言。Q#语言是由微软在2017年推出的新的量子计算开发工具,相比早先的量子开发语言,它引入了更多比较现代的编程元素,对开发者也更友好。 26 | 27 | 这份文档的内容主要分两大部分: 28 | 29 | - 量子计算基础知识 30 | - Q#量子开发工具介绍和使用 31 | 32 | 还有第三部分**量子算法**会逐步进行编写和完善,但目前不会出现在文档中。文档内容主要参考了一些书籍、论文、维基百科以及[微软公司官方文档](https://docs.microsoft.com/en-us/quantum/?view=qsharp-preview)等,具体请见参考目录。 33 | 34 | 量子计算是一个广阔的领域,也是一门非常复杂的学科,它与物理、数学、计算机、信息学等学科有着较大的交联,因此学习它绝不是一件容易的事情,我也希望能与更多的人一起学习,一起进步。如果你在阅读文档的过程中发现有任何错误、不妥或疑惑,希望你能联系我,我的邮箱是:1036014410@qq.com 。 35 | 36 | ## 目录 37 | 38 | [TOC] 39 | 40 | ## 第一部分 量子计算的基础知识 41 | 42 | 在经典计算中,信息存储和处理的基本单位是bit,每一个bit要么表示确定的0,要么表示确定的1。与之相似,量子计算中信息存储和处理的基本单位是qubit(量子比特,Quantum Bit),它也有两个状态$|0 \rangle$ 和$|1 \rangle$ ,与经典bit不同的是,qubit并不处于一个特定的状态下,而是处于两个基本状态$|0\rangle$ 和$|1\rangle$ 的量子叠加态中,或者说它同时处于$|0\rangle$ 和$|1\rangle$ 状态。 43 | 44 | 正是这种诡异的特性,使得量子计算在展现着强大能力的同时,也因其太过违反人们的认知而常常让我们陷入困惑。量子计算与传统的计算机程序设计、开发之间的存在着显著的差别,在经典计算领域里,一切都是确定的,你可以精确地操纵每一个bit来为你服务,可是,一旦进入量子领域,主宰这个世界的就不再是你,每一个qubit都是一副既不可远观(观察造成量子态坍缩)也不可亵玩(量子纠缠)的高冷范儿。理解量子的行为模式、演进过程是学习量子编程的第一步,这一部分的内容主要是对量子、量子态、量子系统等量子计算基础内容的介绍,学习该内容需要对线性代数和概率论的相关概念有基本的了解,不过放心,对于其中的数学我会尽可能地使用简单、便于理解的语言进行描述,不会对学习带来困扰。通过本章的学习,希望能帮助你建立对量子系统的初步印象和认知。 45 | 46 | ## 第一章 单量子系统 47 | 48 | ### qubit 49 | 50 | qubit是量子计算中信息存储和处理的基本单位。对于开发者而言,我们不是物理学家,也不是数学家,我们不需要深究量子的内部结构和演变原理,一个qubit对我们而言就是一个抽象的数学模型,它的状态称为**量子态**,由一个含有两个元素的向量来描述,这个向量叫做**量子态向量**(也叫做**态矢**)。如下所示的向量都表示了某一个特定的量子态。 51 | 52 | ​ $\begin{bmatrix} 1 \\ 0 \end{bmatrix}$ ,$\begin{bmatrix} 0 \\ 1 \end{bmatrix}$ ,$\begin{bmatrix} \frac{1}{\sqrt{2}} \\ \frac{1}{\sqrt{2}} \end{bmatrix}$ ,$\begin{bmatrix} \frac{1}{\sqrt{2}} \\ \frac{-1}{\sqrt{2}} \end{bmatrix}$ ,$\begin{bmatrix} \frac{1}{\sqrt{2}} \\ \frac{i}{\sqrt{2}} \end{bmatrix}$ 53 | 54 | ​ 图1.1 态矢量 55 | 56 | 当然,并不是任意的二维向量都能用来描述一个qubit的状态,描述量子态的向量的模长必须为1,所谓向量的模长定义如下: 57 | 58 | ​ $M(v) = \sqrt{|a_{1}|^{2} + |a_{2}|^{2} + ... + |a_{n}|^{2}}$ 59 | 60 | > 某些资料上使用术语范数,实际上范数有多种形式,为了避免混淆,此处使用了模长的概念。 61 | 62 | 63 | 64 | 其中,$v$ 是一个$n$ 维的矢量,$a_{i}$ 是$v$ 的元素。 65 | 66 | 图1中的几个向量中,$\begin{bmatrix} 1 \\ 0 \end{bmatrix}$ 和$\begin{bmatrix} 0 \\ 1 \end{bmatrix}$ 有着特殊的意义,它们代表着一个量子的基态,任意一个量子态都可以由它们的线性叠加来表示。实际上,一个量子的所有状态构成了一个**量子态空间**,$\begin{bmatrix} 1 \\ 0 \end{bmatrix}$ 和$\begin{bmatrix} 0 \\ 1 \end{bmatrix}$ 是这个空间的一组基底,它们也对应着经典计算中一个bit值的0和1,在量子计算中,我们更常使用符号$|0\rangle$ 和$|1\rangle$ 来表示这一组基态矢量,这种符号叫做Dirac符号,我们会在后文中详细介绍。当然了,学过线性代数的同学肯定知道一个空间的基底不止一组,对于量子态空间而言同样如此。 67 | 68 | ### 1.2 单量子态的测量 69 | 70 | 在经典世界中,我们能够清楚地知晓一个对象的状态,例如一张桌子的大小,一栋楼的高矮,一个电平的高低等,但在量子系统中,一切都显得扑朔迷离。正如海森堡不确定性原理所言,“我们不可能同时知道一个粒子的位置和它的速度”。也就是说我们无法得到一个量子的确切状态。 71 | 72 | ​ ![薛定谔的猫](./image/cat.jpg) 73 | 74 | ​ 图1.2 薛定谔的猫 75 | 76 | 相信很多人都听说过“薛定谔的猫”,当我们未对其进行观察时,猫处于既死又活的叠加态,一旦我们进行了观察,猫就立即处于要么死要么活的确定态。量子也是这样,根据量子力学原理,当对量子进行测量或观察(位置、动量等)时,必然会导致量子态**坍缩**。我们只能得到测量后的一种确定的状态,而无从知晓测量前量子的真正状态。 77 | 78 | 测量会导致量子态坍缩为某个基态($|0 \rangle$ 或$|1 \rangle$ )。一个状态为$|\psi \rangle$ 的量子,其状态可以表示为$|\psi \rangle = \alpha |0 \rangle + \beta |1 \rangle$ ,其中$\alpha$ 和$\beta$ 均为复数,对其进行测量后得到状态$|0 \rangle$ 的概率为$| \alpha |^2$ ,得到状态$|1 \rangle$ 的概率为$ | \beta |^2$ ,根据概率论的知识,我们可以得出$\alpha$ 和$\beta$ 满足$| \alpha |^2 + | \beta |^2 = 1$ 。 79 | 80 | 上面的描述似乎说明了我们永远也无法得到一个量子的确切状态,无法得到确切状态的系统怎么会有用呢?并且又怎么为我们所用呢? 81 | 82 | 不要着急,我们慢慢揭开她神秘的面纱。 83 | 84 | ### 1.3 量子门 85 | 86 | 在经典计算中,对bit的处理和操作是通过一系列各种各样的门(Gate)来实现的。常见的有与门、非门、或门和异或门等,我们日常所写的程序最终都由这些门来具体执行。量子计算也遵循这一套路,通过各种**量子门**对量子进行相应的操作,常用的量子门有$X$、$Y$、$Z$、$S$、$H$和$T$门等。各种常见的门对应的符号如下图所示,其中左侧为bit门,右侧为量子门。 87 | 88 | ![经典门与量子门](./image/ClassicalGatesAndQuantumGates.jpg) 89 | 90 | ​ 图1.3 经典门与量子门 91 | 92 | 在上面的量子门中,X门对一个量子态取反,从这一点上来说,X门与经典的非门非常相似,不同之处在于X门作用于一个状态为$\alpha |0 \rangle + \beta |1 \rangle$量子时,得到的结果为$\beta |0 \rangle + \alpha |1 \rangle$ ,也就是交换了两个基态的系数,并不是改变它们的符号。 93 | 94 | 实际上,量子门与经典计算中的门操作之间并没有清晰明确的对应关系,并且对于每个量子门,其输入的qubit数量与输出的qubit数量必须保持一致(上图中量子门左右两侧的一条横线就代表着一个qubit),这一点与经典计算大不相同,我们都知道,与门、或门等都是接受两个bit输入而产生一个bit输出,这一基本差别也说明了虽然我们可以借鉴经典计算中的某些概念来加强对量子计算的理解,但始终要记住这两个领域存在本质的不同,千万不能生搬硬套。 95 | 96 | 我们使用向量来表示一个qubit的状态,那么自然而然地,每一个量子门操作也对应着一个矩阵。常见的量子门与对应的矩阵如下图所示。 97 | 98 | ​ ![量子门与对应的矩阵](./image/QuantumGatesAndMatrices.jpg) 99 | 100 | ​ 图1.4 量子门与对应矩阵 101 | 102 | 实际上,我们熟知的大小为$2 \times 2$ 的单位矩阵$I$ 也对应一个量子门操作,当然这个门对量子态不产生任何影响。$I$ 、$X(Pauli-X)$ 、$Y(Pauli-Y)$ 和$Z(Pauli-Z)$ 门构成了Pauli量子门集,但我们通常会忽略$I$ 门。在上图中的几种量子门中,$Z$ 门作用于$|0 \rangle$ 时仍然得到$|0 \rangle$ ,作用于$|1 \rangle$ 时得到$- | 1 \rangle$ ,$H$ 门(Hadamard门)作用于$|0 \rangle$ 时得到$(|0 \rangle + |1 \rangle) / \sqrt{2}$ ,作用于$|1 \rangle$ 时得到$(|0 \rangle - |1 \rangle) / \sqrt{2}$ ,细心的你一定注意到了,$T$ 门($\pi / 8$ 门)实际上是$S$ 门(Phase门)的平方根,因为$e ^{i \pi / 4}$ 是$i$ 的平方根(之所以称其为$\pi/8$ 而非$\pi/4$ 主要是历史原因)。这几种量子门在量子计算中非常重要,并且在后面的内容以及实际运用中也会频繁出现。 103 | 104 | 除了上面提到的量子门,那么还有其它的量子门吗?我负责任地告诉你,不仅有,而且有无穷多个。之所以有无穷多个量子门,是因为量子态空间是连续的,不像经典计算中,bit空间是离散的只有0、1两个值,因此量子态之间的转换关系、映射关系也是无穷无尽的。 105 | 106 | 每一个量子门操作对对应着一个矩阵,科学家已经证明,这个矩阵所需要满足的唯一条件就是它必须是**幺正矩阵**,同时这句话反过来也是成立的:**只要一个矩阵是幺正矩阵,那么它就能表示一个量子门操作**。那么什么是幺正矩阵呢? 107 | 108 | 109 | 110 | > 定义一、一个$n$ 维复方阵的共轭转置矩阵与其逆矩阵相等,那么这个矩阵就是幺正矩阵。 111 | 112 | 113 | 114 | 以上面的$S$ 门$\begin{bmatrix} \quad 1\qquad 0 \quad \\ 0\qquad i \end{bmatrix}$为例,我们使用$S^{\dagger }$ 表示$S$ 的共轭转置矩阵,那么$S ^{\dagger} = \begin{bmatrix} \quad 1\qquad 0 \quad \\ 0\quad -i \end{bmatrix}$,计算可得$SS ^{\dagger} = I$ ,也就是说$S ^{\dagger} = \bar{S}$ ,这里的$\bar{S}$ 表示$S$ 的逆矩阵,因此矩阵$S$ 就是一个幺正矩阵,或者也可以说矩阵$S$ 是幺正的。通过简单的验算,我们可以证明上面提到的X、Y、Z门等都是幺正的。 115 | 116 | 那么为什么量子门矩阵必须是幺正的呢?这是因为,根据量子力学的原理,量子态的演变必须是幺正的。式1是[Schrodinger 方程](https://en.wikipedia.org/wiki/Schr%C3%B6dinger_equation),它描述了一个量子态在时域上的演进,式中的$h$ 是[普朗克常量](https://en.wikipedia.org/wiki/Planck_constant),H是一个特定的[汉密尔顿算子(Hamiltonian)](https://www.encyclopediaofmath.org/index.php/Hamilton_operator),这里我们不必对其进行深究。对式1进行积分运算后我们最终可以得到$t1$ 和$t2$ 时刻的量子态之间的演进过程,如式3所示。可以证明式3中的$exp[\frac {-iH(t_{2} - t_{1})} {h}] $ 是幺正的,它对应的幺正矩阵用$U(t_1,t_2)$ 表示【详细证明过程请《Quantum_Computation_and_Quantum_Information》第82到83页】,因此量子门必须是幺正的。从另一个更直观的角度来看,量子空间是一个连续空间,一个量子态可以演进为任意其它量子态,同时演进后的量子态依然能回归最初的状态,这意味着量子门操作必须是可逆的,在这个过程中不能有信息的丢失,幺正矩阵恰恰就代表着这样的可逆过程。例如有一个状态为$|\psi \rangle = \frac{1}{\sqrt{2}}\begin{bmatrix} 1 \\ 1\end{bmatrix}$ 的量子,先对其应用$S$ 门,再应用$S ^{\dagger}$ 门,因为对量子应用量子门操作相当于态矢量左乘相应的矩阵,因此上述过程可表述为$S ^{\dagger} ( S\cdot |\psi \rangle) = (S ^{\dagger} \cdot S) \cdot |\psi \rangle = I \cdot |\psi \rangle = |\psi \rangle$ ,经过了两次门操作后,量子又回到了最初的状态。 117 | 118 | ​ $ih \frac{d|\psi \rangle}{dt} = H|\psi \rangle$ (1) 119 | 120 | ​ $\int_{|\psi_{t_1} \rangle}^{|\psi_{t_2} \rangle} \frac{ihd|\psi \rangle}{H|\psi \rangle} = \int_{t_{1}}^{t_{2}}dt$ (2) 121 | 122 | ​ $|\psi_{t_1}\rangle = exp[\frac {-iH(t_{2} - t_{1})} {h}] |\psi (t_{2}) \rangle= U(t_{1},t_{2}) | \psi (t_{1}) \rangle$ (3) 123 | 124 | 幺正矩阵有着许多非常有用而又有趣的性质。例如它作用于一个向量时不会改变该向量的模长,构成它的列向量或行向量同时也是一组标准正交基等,这些性质我们不再赘述,以后用到时我们会详细说明,感兴趣的同学也可以查阅相关资料。 125 | 126 | ### 1.4 Dirac符号 127 | 128 | 前面的章节中,描述一个量子的状态我们使用了向量和Dirac符号相结合的方式,现在我们对Dirac符号进行详细的讲解。 129 | 130 | [Dirac符号](https://en.wikipedia.org/wiki/Bra%E2%80%93ket_notation)是以其提出者狄拉克的名字命名的,它在1939年由狄拉克提出之后,随即便与[希尔伯特空间](https://en.wikipedia.org/wiki/Hilbert_space)一起构成了量子力学的基本分析工具。Dirac将希尔伯特空间一分为二,成为两个相互对偶的空间,用右矢表示量子态矢量,形式为$|\cdot \rangle$ ,用左矢表示量子态对偶矢量,形式为$\langle \cdot |$ ,右矢是一个列向量,左矢是右矢的共轭,它是一个行向量。这一符号体系非常简洁明了,右矢即态矢,其符号箭头指向右侧,左矢就是其共轭矢量,符号箭头指向左侧,我们使用$|0 \rangle$ 和$|1 \rangle$ 分别表示量子态的两个基态$\begin{bmatrix} 1 \\ 0\end{bmatrix}$ 和$\begin{bmatrix} 0 \\ 1\end{bmatrix}$ ,注意$|0 \rangle$ 并不是0向量($\begin{bmatrix} 0 \\ 0\end{bmatrix}$ )。Dirac符号也叫做“$bra-ket$"符号,其提出者狄拉克将”括号(bracket)“这个单词一分为二,左边为”$bra$",右边为“$ket$",因此左矢$\langle \cdot |$又叫做”$bra$",右矢$|\cdot \rangle$又叫做“$ket$"。 131 | 132 | 我们将量子计算中一些常用的运算总结在了表1.1中,其中包含了使用Dirac符号进行内积、外积等运算的形式。 133 | 134 | 135 | 136 | *表1.1 常用运算* 137 | 138 | | 运算 | 功能 | 139 | | :-------------------------------------------: | :----------------------------------------------------------: | 140 | | $Z ^*$ | 复数$Z$ 的共轭,如$(1 + i) ^* = (1 - i)$ | 141 | | $|\psi \rangle$ | 右矢,也叫做$ket$ | 142 | | $\langle \psi|$ | 左矢(右矢的对偶矢量),也叫做$bra$ | 143 | | $\langle \varphi | \psi \rangle$ | 两个矢量$| \varphi \rangle$ 和$|\psi \rangle$ 的内积 | 144 | | $| \varphi \rangle \langle \psi |$ | 两个矢量$| \varphi \rangle$ 和$|\psi \rangle$ 的外积 | 145 | | $| \varphi \rangle \bigotimes | \psi \rangle$ | 两个矢量$| \varphi \rangle$ 和$|\psi \rangle$ 的张量积 | 146 | | $| \varphi \rangle | \psi \rangle$ | 矢量张量积的简写形式 | 147 | | $A ^*$ | 矩阵$A$ 的共轭 | 148 | | $A ^{T}$ | 矩阵$A$ 的转置 | 149 | | $A ^{\dagger}$ | 矩阵$A$ 的转置共轭,$\begin{bmatrix} \quad a\qquad b \quad \\ c\qquad d \end{bmatrix} ^{\dagger} = \begin{bmatrix} \quad a^*\qquad c^* \quad \\ b^*\qquad d* \end{bmatrix}$ | 150 | | $A \bigotimes B$ | 两个矩阵$A$ 和$B$ 的张量积 | 151 | | $\langle \varphi | A | \psi \rangle$ | $| \varphi \rangle$ 与$A | \psi \rangle$ 的内积,等价于$A ^{\dagger} | \varphi \rangle$ 与$| \psi \rangle$ 的内积 | 152 | 153 | 我们重点讲解一下内积、外积与张量积运算,这几种运算广泛应用于量子计算、量子程序设计、量子门分解等各种场合中,知悉这些运算的内涵和方法是学习量子计算的基础,但是请放心,我会以非常简单的形式告诉你这些运算的内涵,绝对不会让你为繁杂的数学运算所困扰。 154 | 155 | 首先来看矢量的内积运算。矢量的内积也叫做点积,假设我们有两个矢量$| \alpha \rangle$ 和$| \beta \rangle$ ,这两个矢量拥有相同的元素个数$N$ ,那么它们的内积定义为: 156 | 157 | ​ $|\alpha \rangle \cdot | \beta \rangle = \alpha_{1}^*\beta_{1} + \alpha_{2}^*\beta_{2} + \cdots + \alpha_{N}^*\beta_{N} = \sum_{i=1}^{N}\alpha_{i}^* \beta_{i} $ 158 | 159 | 其中$\alpha_{i}$ 和$\beta_{i}$ 分别是这两个矢量的元素。从上面的等式中可以看出,两个矢量的内积就是第一个矢量中各元素的共轭与第二个矢量对应元素乘积的加和,使用Dirac符号可以非常方便地将内积表示为:$\langle \alpha | \beta \rangle$ ,其左侧部分$\langle \alpha |$ 正是矢量$| \alpha \rangle$ 的复数共轭,且与$|\alpha \rangle$ 相互对偶。 160 | 161 | 两个矢量$| \alpha \rangle$ 和$| \beta \rangle$ 的外积定义为$ |\alpha \rangle \langle \beta|$ ,因为$|\alpha \rangle$ 和$\langle \beta|$ 分别为列向量和行向量,因此矢量的外积是一个方阵,这个方阵的大小为$N \times N$ 。 162 | 163 | 两个矩阵$A$ 和$B$ 的张量积表示为$A \bigotimes B$ ,它的定义如下所示: 164 | 165 | ​ $A \bigotimes B = \begin{bmatrix} A_{11}B \quad A_{12}B \quad \cdots \quad A_{1n}B \\ A_{21}B \quad A_{22}B \quad \cdots \quad A_{2n}B \\ \vdots \quad \qquad \vdots \quad \qquad \vdots \quad \qquad \vdots \\ A_{m1}B \quad A_{m2}B \quad \cdots \quad A_{mn}B\end{bmatrix}$ 166 | 167 | 其中,矩阵$A$ 的大小为$m \times n$ 。矢量可以看做是只有一列元素的矩阵,因此矢量之间的张量积也遵循着上面的运算形式,两个矢量$| \alpha \rangle$ 和$| \beta \rangle$ 的张量积为: 168 | 169 | ​ $| \alpha \rangle \bigotimes | \beta \rangle = \begin{bmatrix} \alpha_{1} |\beta \rangle \\ \alpha_{2} |\beta \rangle \\ \vdots \\ \alpha_{N}|\beta \rangle \end{bmatrix}$ 170 | 171 | ### 1.5 量子态可视化 172 | 173 | 目前为止,我们对量子态以及量子门这些基本对象的描述都是通过数学工具进行的,这一节里我们使用另一种更直观的方式对它们进行可视化的描述。 174 | 175 | 首先,一个量子态可以表示为$|\psi \rangle = \alpha |0 \rangle + \beta |1 \rangle$ ,这个形式使我们自然而然地想到使用一个二维空间来描述一个量子态,$|0 \rangle$ 和$|1 \rangle$ 分别是这个二维空间的坐标轴,态矢量$|\psi \rangle$ 为一条由原点出发指向坐标$(\alpha , \beta)$ 的有向线段,图1.5所示为态矢量$|\psi \rangle$ 和$X$ 门作用于$|\psi \rangle$ 的示意图。 176 | 177 | ![二维空间态矢量](./image/%E4%BA%8C%E7%BB%B4%E7%A9%BA%E9%97%B4%E6%80%81%E7%9F%A2%E9%87%8F.jpg) 178 | 179 | ​ 图1.5 量子态在二维平面内的示意图 180 | 181 | 但是由于量子态空间是一个复数空间,使用上面的二维平面并不能完备地对这个空间进行描述。我们现在重新审视量子态$|\psi \rangle = \alpha |0 \rangle + \beta |1 \rangle$ ,因为$\alpha$ 和$\beta$ 是复数,根据[欧拉公式](http://www.baike.com/wiki/%E6%AC%A7%E6%8B%89%E5%85%AC%E5%BC%8F)和三维空间坐标变换等数学工具,$|\psi \rangle$ 可以表示为: 182 | 183 | ​ $|\psi \rangle = e^{ir}(cos \frac{\theta }{2}|0 \rangle + e^{i\phi }sin \frac {\theta}{2} | 1 \rangle)$ *[公式的推导可以参考https://en.wikipedia.org/wiki/3-sphere]* 184 | 185 | 其中$r$ 、$\theta$ 和$\phi$ 均为实数,$r$ 在这里是一个全局的相位变换,通常我们不用理会它,所以得到$|\psi \rangle = cos \frac{\theta }{2}|0 \rangle + e^{i\phi }sin \frac {\theta}{2} | 1 \rangle$ ,这个式子可以用一个单位球面进行展现,如图1.6所示。 186 | 187 | ![Bloch Sphere](./image/BlochSphere.jpg) 188 | 189 | ​ 图1.6 Bloch Sphere 190 | 191 | 这个三维单位球面就是Bloch Sphere,它的上、下两个极点分别表示$|0 \rangle$ 和$|1 \rangle$ 。Bloch Sphere不仅能用来表示量子态,同时能用来演示量子门作用于量子时量子态的演进。假设我们有一个状态为$\frac{1}{\sqrt{2}}(|0 \rangle + |1 \rangle)$ 的量子,对其应用$H$ 门,演进过程可以用下面的Bloch Sphere展示出来,首先该态矢量绕$y$ 轴向下旋转$90^{\circ}$ ,最后绕$x$ 轴旋转$180 ^\circ$ 得到结果$|0 \rangle$ 。 192 | 193 | ![H门演示](./image/Hadamard%E9%97%A8%E5%8F%AF%E8%A7%86%E5%8C%96.jpg) 194 | 195 | ​ 图1.7 $H$门应用于$\frac{1}{\sqrt{2}}(|0 \rangle + |1 \rangle)$ 的演进过程 196 | 197 | $H$ 门是最重要的量子门之一,它作用于$|0 \rangle$ 时得到$|\frac{1}{\sqrt{2}}(|0 \rangle + |1 \rangle)$ ,作用于$|1 \rangle$ 时得到$\frac{1}{\sqrt{2}}(|0 \rangle - |1 \rangle)$ ,这两个结果都是$|0 \rangle$ 和$| 1 \rangle$ 的叠加态,因此$H$ 的一个重要作用就是创造量子叠加。根据量子测量一节的知识,对这两个量子态测量得到$|0 \rangle$ 和$|1 \rangle$ 的概率都是$\frac{1}{2}$ ,也就是说测量结果是完全随机的,我们在开发过程中经常会用一些随机数生成算法,但这些算法实质上都是“伪随机”的,而上面的量子测量是“真随机”的,物理原理为这种随机性提供了强力保证,这也正是$H$ 门的重要性所在,在很多量子算法中,都会有使用$H$ 门的场景。 198 | 199 | 在量子态空间中,有无穷多的量子门,就有无穷多对应的幺正矩阵,那么随之而来的一个问题就是,每次应用新的量子门时我们都要重新定义一个幺正矩阵吗?每个量子门操作实际上最终都要由相应的硬件来完成,那面对这无穷多的量子门,我相信世界上最伟大的工程师也会望而却步。幸运的是,人们已经证明,存在一个量子门集合,这个集合中只有几种常见的量子门,它们经过不同的组合可以构建出任意的量子门,这个集合也叫做**通用量子门集合**(后文会有更详细的论述)。并且,任意的幺正矩阵都能分解成如下形式(下面的这些数学运算不需要理解或记忆,只要知道一个幺正矩阵可以被分解即可): 200 | 201 | ​ $U = e^{i\alpha}R_z(\beta)R_y(\gamma )R_x(\delta )$ 202 | 203 | 其中, 204 | 205 | ​ $R_z(\beta) = e^{-i\beta Z/2} = cos \frac{\beta}{2}I - isin{\frac {\beta}{2}}Z = \begin{vmatrix}e^{-i\beta/2} & 0 \\ 0 & e^{i\beta/2} {2}\end{vmatrix}$ 206 | 207 | ​ $R_y(\gamma) = e^{-i\gamma Y/2} = cos \frac{\gamma}{2}I - isin{\frac {\gamma}{2}}Y = \begin{vmatrix}cos \frac{\gamma}{2} & -sin \frac{\gamma}{2}\\ sin \frac{\gamma}{2} & cos \frac{\gamma}{2}\end{vmatrix}$ 208 | 209 | ​ $R_x(\delta) = e^{-i\delta X/2} = cos \frac{\delta}{2}I - isin{\frac {\delta}{2}}X = \begin{vmatrix}cos \frac{\delta}{2} & -i sin \frac{\delta}{2} \\ -isin \frac{\delta}{2} & cos \frac{\delta}{2}\end{vmatrix}$ 210 | 211 | 它们分别是由$X$ 、$Y$ 和$Z$ 门等经过旋转等变换得到的,所以任意的量子门都可以通过其它的门构建出来。Bloch Sphere虽然可以很好的展现量子和量子门操作,但它也有自己的局限,比如在面对接下来要讲的多量子系统时就无能为力了。 212 | 213 | ## 第二章 多量子系统 214 | 215 | 一个量子系统的状态用一个向量来表示,假设有一个由两个qubit组成的双量子系统,系统中每一个qubit都有两个基本状态$|0 \rangle$ 和$|1 \rangle$ ,那么显而易见这个双量子系统的基本状态就有4种:$|0 \rangle |0 \rangle,|0 \rangle |1 \rangle,|1 \rangle |0 \rangle,|1 \rangle |1 \rangle$ ,简写为:$|00 \rangle,|01 \rangle,|10 \rangle,|11 \rangle$ 。事实上,这几种双量子系统的基本状态就是每一个量子基本状态的张量积,根据前面所讲的张量积的计算方法,这几种基态实际上就是: 216 | 217 | ​ $|00 \rangle = |0 \rangle \bigotimes |0 \rangle = \begin{vmatrix} 1 \\ 0 \\ 0 \\ 0 \end{vmatrix}$ ,$|01 \rangle = |0 \rangle \bigotimes |1 \rangle = \begin{vmatrix}0 \\ 1 \\ 0 \\ 0 \end{vmatrix}$ 218 | 219 | ​ $|10 \rangle = |1 \rangle \bigotimes |0 \rangle = \begin{vmatrix} 0 \\ 0 \\ 1 \\ 0 \end{vmatrix}$ ,$|11 \rangle = |1 \rangle \bigotimes |1 \rangle = \begin{vmatrix} 0 \\ 0 \\ 0 \\ 1 \end{vmatrix}$ 220 | 221 | 这四个向量构成了双量子状态空间的基底,这个空间中其它向量都能由它们的线性叠加表示出来。所以一个双量子系统的状态$|\psi \rangle$ 可以表示为$|\psi \rangle = \alpha_{00}|00 \rangle + \alpha_{01} |01 \rangle + \alpha_{10}|10 \rangle + \alpha_{11}|11 \rangle$ ,并且$| \alpha_{00} |^2 + | \alpha_{01} |^2 + | \alpha_{10} |^2 + | \alpha_{11} |^2 = 1$ ,$\alpha_{x}^2$ 为测量这个系统得到相应输出结果的概率。 当qubit数量为$n(n > 1)$ 时,多量子系统的基态就是系统内每个量子基态的张量积,$n$ 量子系统拥有$2^{n}$个基态,它们构成了多量子系统量子态空间的基底,下面所示的就是3量子系统的基态。 222 | 223 | ​ $|000 \rangle = \begin{vmatrix} 1 \\ 0 \\ 0 \\ 0 \\ 0 \\ 0 \\ 0 \\ 0 \end{vmatrix}$ ,$|001 \rangle = \begin{vmatrix} 0 \\ 1 \\ 0 \\ 0 \\ 0 \\ 0 \\ 0 \\ 0 \end{vmatrix}$ ,$|010 \rangle = \begin{vmatrix} 0 \\ 0 \\ 1 \\ 0 \\ 0 \\ 0 \\ 0 \\ 0 \end{vmatrix}$ ,$|011 \rangle = \begin{vmatrix} 0 \\ 0 \\ 0 \\ 1 \\ 0 \\ 0 \\ 0 \\ 0 \end{vmatrix}$ 224 | 225 | ​ $|100 \rangle = \begin{vmatrix} 0 \\ 0 \\ 0 \\ 0 \\ 1 \\ 0 \\ 0 \\ 0 \end{vmatrix}$ ,$|101 \rangle = \begin{vmatrix} 0 \\ 0 \\ 0 \\ 0 \\ 0 \\ 1 \\ 0 \\ 0 \end{vmatrix}$ ,$|110 \rangle = \begin{vmatrix} 0 \\ 0 \\ 0 \\ 0 \\ 0 \\ 0 \\ 1 \\ 0 \end{vmatrix}$ ,$|111 \rangle = \begin{vmatrix} 0 \\ 0 \\ 0 \\ 0 \\ 0 \\ 0 \\ 0 \\ 1 \end{vmatrix}$ 226 | 227 | 随着$n$ 的增长,多量子系统的基态数量呈指数级增加,$n = 500$ 时,这个量子系统所包含的基态数量甚至超过了宇宙中所有粒子的数量,这将是一个多么广阔的量子态空间!可以说,量子计算的强大能力正是来源于此,我们一步步去发现它背后广阔的天地。 228 | 229 | ### 2.1 量子测量 230 | 231 | 在前面的单量子系统中,我们也曾经提到过量子测量的概念。回想一下,测量会导致量子态向其基态坍缩,一个状态为$|\psi \rangle = \alpha |0\rangle + \beta |1 \rangle$ 的量子,经过测量后得到$|0 \rangle$ 或$|1 \rangle$ 的概率分别为$| \alpha | ^{2}$ 和$| \beta | ^{2}$ ,同样,测量一个状态为$|\psi \rangle = \alpha_{00}|00 \rangle + \alpha_{01} |01 \rangle + \alpha_{10}|10 \rangle + \alpha_{11}|11 \rangle$ 的双量子系统,得到结果$\{|00 \rangle , |01 \rangle ,|10 \rangle,|11 \rangle\}$ 的概率分别为$\{ |\alpha_{00}|^{2},|\alpha_{01}|^{2},|\alpha_{10}|^{2},|\alpha_{11}|^{2}\}$ 。 232 | 233 | 量子测量得到的是确定的经典世界的结果,这个结果可以看做是量子系统在经典世界上的投影。 234 | 235 | 量子系统的测量操作用符号$M$表示,一个由$n$ 个量子组成系统,其基态有$2^{n}$个,这也就意味着有$2^{n}$个不同的测量操作符,这些测量操作构成了一个测量集合$\{M_{m}\}$ ,索引$m$ 表示经过测量后所得到的结果。一个量子态为$|\psi \rangle$ 的系统,在经过$M_{m}$ 测量操作测量后,产生结果$m$ 的概率为: 236 | 237 | ​ $p(m) = \langle \psi | M_{m}^{\dagger}M_{m} |\psi \rangle$ 238 | 239 | 在测量后,系统所处的状态为: 240 | 241 | ​ $\frac {M_{m} | \psi \rangle} {\sqrt {\langle | M_{m}^\dagger M_{m} |\psi \rangle}}$ 242 | 243 | 并且,测量操作符必须遵循以下约束: 244 | 245 | ​ $\sum_{m} M_{m}^\dagger M_{m} = I$ 246 | 247 | 同时,所有可能的测量记过的概率和必须是1,因此: 248 | 249 | ​ $1 = \sum_{m}p(m) = \sum_{m}{\langle \psi | M_{m}^\dagger M_{m} |\psi \rangle}$ 250 | 251 | 从数学角度来看,量子操作符是量子态系统没个基底与自身的外积。我们以单量子系统为例,构建单量子系统的两个测量操作符为:$M_{0}=|0 \rangle \langle 0|,M_{1}=|1 \rangle \langle 1|$ ,观察这两个操作符,我们可以发现$M_{0}^\dagger M_{0} = M_{0},M_{1}^\dagger M_{1} = M_{1}$ ,所以$M_{0}^\dagger M_{0} + M_{1}\dagger M_{1} = M_{0} + M_{1} = I$ ,因此这两个操作符符合上面的要求,假设一个单量子系统的状态为$|\psi \rangle = \alpha |0 \rangle + \beta |1 \rangle$ ,分别应用这两个测量操作,我们得到: 252 | 253 | ​ $p(0) = \langle \psi | M_{0}^\dagger M_{0} |\psi \rangle = \langle \psi | M_{0} |\psi \rangle = | \alpha |^{2}$ 254 | 255 | ​ $p(1) = \langle \psi | M_{1}^\dagger M_{1} | \psi \rangle = \langle \psi | M_{1} | \psi \rangle = | \beta |^{2}$ 256 | 257 | 这就是我们在前面提到的测量一个单量子时得到$|0 \rangle$ 和$|1 \rangle$ 的概率分别为$| \alpha |^{2}$ 和$| \beta |^{2}$ 的由来,同时在测量后,这个量子所处的状态就变成: 258 | 259 | ​ $\frac {M_{0} |\psi \rangle} {|\alpha |} = \frac{\alpha}{| \alpha |} |0 \rangle$ 260 | 261 | ​ $\frac{M_{1} |\psi \rangle}{| \beta |} = \frac {\beta} {| \beta |} |1 \rangle$ 262 | 263 | 实际上结果就是$|0 \rangle$ 和$|1 \rangle$ 。 264 | 265 | 在多量子系统中,我们也可以仅测量其中一部分量子的状态。在这里我们要引入一个非常重要的双量子系统:Bell State(也叫做EPR pair)【引用由来】,这个系统的量子态为: 266 | 267 | ​ $\frac {|00 \rangle + |11 \rangle} {\sqrt{2}}$ 268 | 269 | 该系统有一个非常有趣的特性:测量第一个量子时,得到$| 0 \rangle$ 的概率为$\frac {1}{2}$ ,此时系统测量后的状态为$| 00 \rangle$ ,得到$|1 \rangle$ 的概率同样为$\frac{1}{2}$,此时系统测量后的状态为$|11 \rangle$ ,也就是说,测得第一个量子的状态后,剩下的量子其状态与第一个是一致的,呈现出非常强的关联性。人们已经证明,Bell State中的这种强关联性甚至强于经典系统中这样的性质。这种性质的存在进一步说明了量子系统用于进行量子计算以及其他领域的可能性。【《Quantum_Computation_and_Quantum_Information》第17页】 270 | 271 | ### 2.2 多量子门 272 | 273 | 多量子门指的是作用于多量子系统上的受控的量子门,所谓受控指的是多量子门作用于系统时,系统中一部分量子状态的变换能影响其它的量子态。多量子门一般分为两个部分:控制部分和目标部分。控制部分的状态决定了目标部分的状态。 274 | 275 | #### 2.2.1 controlled-NOT门 276 | 277 | 最基本的多量子门是 $controlled-NOT$ (受控非门)门,也叫$CNOT$ 门,以双量子系统为例,图2.1所示为双量子系统的$CNOT$ 门和其对应的量子门矩阵,图中,$CNOT$ 门上方的直线代表了控制量子,下方的直线代表着目标量子,当控制量子的装填为$|1 \rangle$ 时,$X$ 门作用于目标量子,为$| 0 \rangle$ 时,目标量子不受任何影响。 278 | 279 | ![controlled-NOT](./image/CNOT.jpg) 280 | 281 | ​ 图2.1 $CNOT$门 282 | 283 | 双量子$CNOT$ 门输入也输出量子态的对应关系为: 284 | 285 | ​ $| 00 \rangle \rightarrow |00 \rangle ; |01 \rangle \rightarrow |01 \rangle ; |10 \rangle \rightarrow |11 \rangle ; |11 \rangle \rightarrow |10 \rangle$ 286 | 287 | 从另一个角度看,$CNOT$ 门的作用相当于对控制量子和目标量子进行模二加运算(异或运算),也就是说,对于状态为$|A,B \rangle$ 的双量子系统,经$CNOT$ 门作用后,其状态为$|A , A \oplus B \rangle$ ,并且模二加运算的结果放在了目标量子中,上图中间部分的图像就是对这一过程的描述。 288 | 289 | #### 2.2.2 Toffoli门 290 | 291 | 严格地说,$Toffoli$门并不属于量子计算的范畴。在前面我们曾经说过,经典计算中的部分门操作不是幺正的,或者说不是可逆的,而量子门是可逆的并且可以用其他的量子门进行表示,那么在经典世界中是否存在一个门,这个门可以用来表示其它的门并且它还是可逆的? 292 | 293 | 答案就是$Toffoli$门。$Toffoli$门有三个输入和三个输出,其中两个输入bit是$controlled-bit$ (控制bit),剩下的一个是$target-bit$ (目标bit),当两个控制bit都为$1$ 时,目标bit会被取反,相反则目标bit保持本身状态不变。下面的图就是$Toffoli$门的基本形式和其输入输出对照表。 294 | 295 | ![Toffoli门](./image/Toffoli%E9%97%A8.jpg) 296 | 297 | ​ 图2.2 $Toffoli$门 298 | 299 | $Toffoli$可以用来实现任意经典计算中的逻辑门,比如下面这个用$Toffoli$门实现的与非门(NAND Gate)。 300 | 301 | ![NAND](./image/NAND.jpg) 302 | 303 | ​ 图2.3 $Toffoli$门实现与非门 304 | 305 | $Toffoli$门非常重要,之所以非常重要在于虽然它本身是经典计算中的产物,但同样能够用于量子计算中。如前文所述,量子门中输入与输出的qubit数量必须是一致的,$Toffoli$门完美地契合这一点,同时,下面的矩阵是$Toffoli$门的矩阵表示形式,可以证明这个矩阵是幺正的,从而$Toffoli$门也能应用于量子计算中。 306 | 307 | ​ $\begin{bmatrix} 1\quad 0 \quad 0\quad 0\quad 0\quad 0\quad 0 \quad 0 \\ 0 \quad 1 \quad 0 \quad 0 \quad 0 \quad 0 \quad 0 \quad 0 \\ 0 \quad 0 \quad 1 \quad 0 \quad 0 \quad 0 \quad 0 \quad 0 \\ 0 \quad 0 \quad 0 \quad 1 \quad 0 \quad 0 \quad 0 \quad 0 \\ 0 \quad 0 \quad 0 \quad 0 \quad 1 \quad 0 \quad 0 \quad 0 \\ 0 \quad 0 \quad 0 \quad 0 \quad 0 \quad 1 \quad 0 \quad 0 \\ 0 \quad 0 \quad 0 \quad 0 \quad 0 \quad 0 \quad 0 \quad 1 \\ 0 \quad 0 \quad 0 \quad 0 \quad 0 \quad 0 \quad 1 \quad 0 \end{bmatrix}$ 308 | 309 | $Toffoli$门说明了很重要的一点:**量子计算和经典计算可以通过某种形式得到形式上的统一**,从现实世界的角度来看,这一点也是完全合理的,毕竟所有的物理系统都可以用量子力学进行描述,同时,人们也已经证明量子计算机也符合图灵机的标准,尽管对人们来说它难以立即。这种统一的观点能够为量子计算的发展带来很大裨益。 310 | 311 | #### 2.2.3 通用量子门集合 312 | 313 | 假设有一个量子门集合,其中包含了几种量子门,如果其他的量子门都能有这个集合中的元素通过不同的组合而实现【包括满足一定误差的条件】,那么这个量子门集合就是**通用量子门集合**。 314 | 315 | 目前,这样的通用量子门集合有两个,一个包含量子门:$CNOT$ 、$Hadamard$ 、$Phase$ 和$\pi / 8$ ,另一个则包含:$CNOT$ 、$Hadamard$ 、$Phase$ 和$Toffoli$ 。 316 | 317 | ![UniversalGateSet](./image/UniversalGateSet.jpg) 318 | 319 | ## 第三章 量子线路 320 | 321 | 单独的量子门、量子系统并不能够完成真正的计算,只有将它们通过一定的规则和形式组合起来才能进行有用的工作,这样的组合就是**量子线路**,量子计算机等价于量子线路。 322 | 323 | 目前来看,我认为理解量子线路是进行量子计算和量子编程开发的敲门砖。量子编程与传统编程的区别的一部分也表现在这里。我们平时所做的开发工作,所接触到的编程语言、编程模型,无外乎就是分支、循环、递归等等程序设计元素的组合,但在量子计算中一切都不相同,有时候很简单的工作,比如计算$1 + 1 = 2$ ,如果用量子计算机去实现也会大费周折,但是量子计算的魅力也正在于此,从不同的视角和观点重新审视这个世界,就会有不同的感受和收获。 324 | 325 | 量子线路中主要包括量子门、输入和输出qubit等,每种元素在量子线路中都有其特定的符号表示,我们将常用的符号总结在了下面的图中。 326 | 327 | ![量子线路符号](./image/%E9%87%8F%E5%AD%90%E7%BA%BF%E8%B7%AF%E7%AC%A6%E5%8F%B7.jpg) 328 | 329 | ### 3.1 量子线路的特点 330 | 331 | 现在我们来看一个量子线路的示例: 332 | 333 | ​ ![teleportation](./image/teleportation.jpg) 334 | 335 | ​ 图3.1 量子线路图示例 336 | 337 | 这是一个有三个输入的量子线路,三个输入qubit分别为一个为止状态的$|\varphi \rangle$ 和两个$|0 \rangle$,我们将$\varphi \rangle$ 表示为$|\varphi \rangle = \alpha |0 \rangle + |1 \rangle$ 在图中,我们将线路中某一位置的量子态用虚线进行了标示。首先,两个$| 0 \rangle$ 经过一个$H$ 门和一个$CNOT$ 门的作用后,状态变为$ \frac {1} {\sqrt{2}}(| 00 \rangle + |11 \rangle)$,此时与第一个量子$| \varphi \rangle$ 构成的三量子系统的状态为: 338 | 339 | ​ $| \varphi_{0} \rangle = |\varphi \rangle \otimes [\frac {1} {\sqrt{2}}(| 00 \rangle + | 11 \rangle)] = \frac{1}{\sqrt{2}}[\alpha |0 \rangle (| 00 \rangle + | 11 \rangle) + \beta |1 \rangle (| 00 \rangle + | 11 \rangle)]$ 340 | 341 | 之后,再又一个$CNOT$ 门和$H$ 门的作用下,我们得到这个三量子系统的状态为: 342 | 343 | ​ $| \varphi_{1} \rangle = \frac{1}{\sqrt{2}}[\alpha (|00 \rangle + |11 \rangle) + \beta|1 \rangle (|10 \rangle + |01 \rangle)]$ 344 | 345 | ​ $|\varphi_{2}\rangle = \frac{1}{2}[\alpha(|0 \rangle + |1 \rangle)(|00 \rangle + |11 \rangle) + \beta(|0 \rangle - |1 \rangle)(|10 \rangle + |01 \rangle)]$ 346 | 347 | 状态$|\varphi_{2} \rangle$ 又可以写做: 348 | 349 | ​ $| \varphi_{2} \rangle = \frac{1}{2}[|00 \rangle (\alpha |0 \rangle + \beta |1 \rangle) + |01 \rangle (\alpha |1 \rangle + \beta |0 \rangle) + |10 \rangle (\alpha |0 \rangle - \beta |1 \rangle) + |11 \rangle (\alpha |1 \rangle - \beta |0 \rangle)]$ 350 | 351 | 然后,使用测量$M_{1}$ 和$M_{2}$ 分别对前两个量子进行测量,测量导致量子态坍缩输出的是经典的bit,这一点线路图上也有相应的显示。 352 | 353 | 那么从这个线路图上我们能够得到什么呢? 354 | 355 | 1. 量子线路图的输入和输出量子数量必须是一致的。这一点我们在将量子门时已经说过,既然量子线路是由量子门组成的,那么量子线路必然也要遵循量子门输入输出量子数一致这一原则。 356 | 2. 量子线路中不能存在环状结构,也就是说在其中不能有循环。 357 | 3. 在电路图中,我们可以将几根电线合并在一起或者从一根电线中分出多跟电线,但在量子线路中,这是不允许也是行不通的。【前面应当补充no-coloning原理】。 358 | 359 | 这就是量子线路的特点,从中可以看出,它与我们日常所见到的电子线路非常不同。 360 | 361 | 我们继续讨论上面的量子线路图。在经过了两个测量符测量后,我们对第三个量子进行进一步的转换操作。每一个测量操作会得到两个结果:$0$ 或者$1$ ,因此我们就会得到4中测量结果,我们将每种结果以及对应的转换后的结果写在下面的表中: 362 | 363 | *表1.2 测量结果【矩阵从右至左产生作用,因此$X$写在右边】* 364 | 365 | | $M_{1}$ | $M_{2}$ | 转换操作 | 结果 | 366 | | :-----: | :-----: | :----------: | :------------------------------------: | 367 | | 0 | 0 | $Z^{0}X^{0}$ | $\alpha |0 \rangle + \beta |1 \rangle$ | 368 | | 0 | 1 | $Z^{0}X^{1}$ | $\alpha |1 \rangle + \beta |0 \rangle$ | 369 | | 1 | 0 | $Z^{1}X^{0}$ | $\alpha |0 \rangle - \beta |1 \rangle$ | 370 | | 1 | 1 | $Z^{1}X^{0}$ | $\alpha |1 \rangle - \beta |0 \rangle$ | 371 | 372 | 现在我们知道了这个线路的输出结果,也知道了这个线路的工作过程,但是!这个线路到底有什么作用呢?从这些结果中也看不出个所以然来。那么我告诉你,我们刚刚进行了一次**量子通信**,你相信吗? 373 | 374 | 我们现在来设想一下,假设有两个人A和B,我们首先使用$H$ 门和$CNOT$ 门作用于两个量子态$|0 \rangle$ ,这时我们会得到他们的叠加态,也就是图中的$| \varphi_{0} \rangle$ ,然后将处于叠加态的两个量子分别交由A和B,随后A和B两个分道扬镳,许多年后,A想起了当年青梅竹马的B,他想送给B一个量子表达自己的思念之情(不要问我为什么要送量子),但是B并不知道A送给他的量子到底是什么状态,并且如果B要测量这个量子,那这个量子立马就会发生坍缩,从而导致B永远也不能知道A到底传送给了他什么,这时候他们当年分离之时各自拥有的那个量子就起作用了,A测量自己拥有的量子和传送的量子,得到一个结果,这个结果由两个bit构成(我们上面的测量结果),分别是:$00 、01、10、11$ ,A同时将这个测量结果传送给B,根据这个结果和上面的表格,B就能够知道A到底传送给了他什么。这就是**量子隐形传态**,实际上A并不需要真的给B传送一个量子,他只需要完成测量并将测量结果传送给B就可以了。 375 | 376 | 量子隐形传态描述的是一种量子之间存在的一种超距作用,处于纠缠态的量子,无论它们相隔多远,只要之中一个量子的状态发生了变换,另一个会立即产生相应的变化。但是要特别指出的是,量子隐形传态并不能超光速,以上面的通信过程为例,A在传给B量子的同时还要传输经典bit信息,而这一过程是不能超光速的,如果没有这些进店bit信息,B也就无从知晓传输的量子态,也就无法完成通信过程。因此量子隐形传态并不能超光速。 377 | 378 | 同时,上面的结果似乎能够让我们克隆一个量子$|\varphi \rangle$ ,这么一来就违背了不可克隆原理,但实际上最初的量子$| \varphi \rangle$ 在测量后发生了量子态坍缩,我们得到的结果是对其他量子进行一定的量子门操作的结果,因此量子并没有被克隆。 379 | 380 | ### 3.2 量子线路的矩阵表示 381 | 382 | 量子门可以用矩阵进行表示,同样,由量子门构成的量子线路也能表示为一个矩阵。本节内容就是讲量子线路与矩阵之间的转换关系。 383 | 384 | #### 3.2.1 受控量子门与矩阵 385 | 386 | 受控量子门的矩阵表示很简单,以双量子系统来说,受控量子门对应矩阵是一个$4 \times 4$ 大小的矩阵,矩阵的左上角是一个$2 \times 2$ 的单位矩阵,左下角就是相应的被控量子门矩阵了,其他部分都为0。例如我们学过的受控量子门$CNOT$ ,它的矩阵表示形式就是: 387 | 388 | ​ $\begin{bmatrix} 1 \quad 0 \quad 0 \quad 0 \\ 0 \quad 1 \quad 0 \quad 0 \\ 0 \quad 0 \quad 0 \quad 1 \\ 0 \quad 0 \quad 1 \quad 0 \end{bmatrix}$ 389 | 390 | 根据这个规则,我们再来构建其他受控门的矩阵形式,例如受控$Z$ 门,它的符号表示和矩阵形式如下图所示: 391 | 392 | ![ControlledZ](./image/ControlledZ.jpg) 393 | 394 | ​ 图3.2 $Controlled-Z$ 门 395 | 396 | 在上面的$Controlled-Z$ 门中,控制qubit在位于上方,目标qubit在下方,如果我们将它们的位置交换一下,那么相应的矩阵会改变吗?答案是不会,我们在下面的图中分别画出了两种线路图和它们的输入输出之间的关系,结果表明,上下位置的颠倒并不会对量子线路的结果造成影响,因此它们的矩阵形式是一样的。 397 | 398 | ![ControlledZReverse](./image/ControlledZReverse.jpg) 399 | 400 | 图3.3 翻转$Controlled-Z$门 401 | 402 | #### 3.2.2 量子线路与矩阵 403 | 404 | 我们以下面的量子线路为例讨论量子线路与矩阵之间的转换方法。 405 | 406 | ![QuantumCircuit](./image/QuantumCircuit.jpg) 407 | 408 | ​ 图3.4 量子线路示例 409 | 410 | 对于这样一个量子线路,我们首先将其以门为单位进行纵向的切分,就像图3.5中所示那样,每个分区内部的量子门矩阵的张量积就是这个分区的矩阵表示。 411 | 412 | ![QuantumCircuitDIvide](./image/QuantumCircuitDIvide.jpg) 413 | 414 | ​ 图3.5 量子线路分区 415 | 416 | 得到每个分区的矩阵后,整个线路的矩阵表示就是所有分区矩阵按从左到右的顺序相乘所得的矩阵乘积,那么上面的量子线路最终的矩阵表示为: 417 | 418 | $(H \otimes I)(CNOT)(I \otimes Z)(H \otimes H)(CNOT)(I \otimes H)$ 419 | 420 | 虽然上面是双量子系统的线路图,但无论多少个量子,线路图矩阵的构造方法是一样的。 421 | 422 | 但是有时候我们也会遇到一些麻烦,比如下面这样的多量子门线路,它门对应的矩阵是什么呢? 423 | 424 | ![TrivialGates](./image/TrivialGates.jpg) 425 | 426 | ​ 图3.6 $3-qubit$量子线路 427 | 428 | 这样的线路麻烦之处在于,控制量子和目标量子之间跨过了另外的量子,直接用上面分区中的方法是不行的,为此我们要寻找其它方法。 429 | 430 | 一个量子线路对应一个矩阵,我们将这个矩阵设为$A$ ,矩阵作用输入的量子态向量得到相应的输出,这个过程可以描述为: 431 | 432 | $A|\varphi \rangle = | \varphi^{'} \rangle$ 433 | 434 | 以图3.6中右侧的量子门线路为例,我们来求取矩阵$A$ 。这个量子门的作用是交换第$1$ 和第$3$ 个量子态而第二个量子态不受影响,我们将这个三量子系统的输入输出写在表3.1中: 435 | 436 | *表3.1 输入输出表* 437 | 438 | | 输入 | 输出 | 439 | | :------------: | :--------------: | 440 | | $|000 \rangle$ | $|000 \rangle$ | 441 | | $|001 \rangle$ | $|100 \rangle$ | 442 | | $|010\rangle$ | $ | 010 \rangle$ | 443 | | $|011 \rangle$ | $|110 \rangle$ | 444 | | $|100 \rangle$ | $|001 \rangle$ | 445 | | $|101 \rangle$ | $|101 \rangle$ | 446 | | $|110 \rangle$ | $|011 \rangle$ | 447 | | $|111 \rangle$ | $|111 \rangle$ | 448 | 449 | 将上表的量子态用矩阵运算的形式可以表示为: 450 | 451 | ​ $A \begin{bmatrix} 1 \quad 0 \quad 0 \quad 0 \quad 0 \quad 0 \quad 0 \quad 0 \\ 0 \quad 1 \quad 0 \quad 0 \quad 0 \quad 0 \quad 0 \quad 0 \\ 0 \quad 0 \quad 1 \quad 0 \quad 0 \quad 0 \quad 0 \quad 0 \\ 0 \quad 0 \quad 0 \quad 1 \quad 0 \quad 0 \quad 0 \quad 0 \\ 0 \quad 0 \quad 0 \quad 0 \quad 1 \quad 0 \quad 0 \quad 0 \\ 0 \quad 0 \quad 0 \quad 0 \quad 0 \quad 1 \quad 0 \quad 0 \\ 0 \quad 0 \quad 0 \quad 0 \quad 0 \quad 0 \quad 1 \quad 0 \\ 0 \quad 0 \quad 0 \quad 0 \quad 0 \quad 0 \quad 0 \quad 1 \end{bmatrix} = \begin{bmatrix} 1 \quad 0 \quad 0 \quad 0 \quad 0 \quad 0 \quad 0 \quad 0 \\ 0 \quad 0 \quad 0 \quad 0 \quad 1 \quad 0 \quad 0 \quad 0 \\ 0 \quad 0 \quad 1 \quad 0 \quad 0 \quad 0 \quad 0 \quad 0 \\ 0 \quad 0 \quad 0 \quad 0 \quad 0 \quad 0 \quad 1 \quad 0 \\ 0 \quad 1 \quad 0 \quad 0 \quad 0 \quad 0 \quad 0 \quad 0 \\ 0 \quad 0 \quad 0 \quad 0 \quad 0 \quad 1 \quad 0 \quad 0 \\ 0 \quad 0 \quad 0 \quad 1 \quad 0 \quad 0 \quad 0 \quad 0 \\ 0 \quad 0 \quad 0 \quad 0 \quad 0 \quad 0 \quad 0 \quad 1 \end{bmatrix}$ 452 | 453 | 上面等式左侧的矩阵中,每一列代表一个输入的量子态向量,右侧矩阵中每一列代表一个输出的量子态向量。那么答案很明显了,矩阵$A$ 就是上面等式中右边的部分,因为$A$实际上右乘了一个单位矩阵。 454 | 455 | 因此面对上面两个比较麻烦的量子门线路,这样的方法也能很快求出相应的矩阵。实际上,这个方法是通用的,任何的量子线路矩阵都能使用这种方法求出来,只要正确地写出了输入和输出的量子态向量。 456 | 457 | ## 第二部分 微软Q#量子计算开发工具 458 | 459 | ## 第四章 Q#语言概览 460 | 461 | **Q#是一门量子编程语言之一,它最大的特点就是它是微软的儿子(^_^)。** 462 | 463 | 目前看来,相比量子计算的硬件发展水平,与量子计算有关的软件设施发展的要好很多,Qbsolv、QCL、 LIQU等许多量子编程语言已经得到了实际的应用,Q#就是其中之一。 464 | 465 | Q#是一门非常年轻的语言,它由微软在2017年推出。Q#从Python、C#、F#等语言中汲取了许多元素,这使得相比于它的前辈们,它看起来更现代化,更时髦,同时对开发者也更友好,使用也更方便。Q#语言是一门抽象程度很高的语言,它对量子计算机的相关逻辑进行了高度封装,可以方便地在不同架构的量子计算机上进行移植,这也极大地减轻了开发人员的负担。 466 | 467 | 从宏观上来看,Q#语言遵循了量子计算机与传统计算机的分立模型,即量子计算机仅负责量子计算相关部分,其它任务完全由经典计算机来完成,两者之间通过一定的方式进行通讯。一个Q#程序分为两个部分,使用C#编写的驱动部分,这一部分即是对经典计算机的抽象,另一部分就是用Q#语言编写的量子计算程序,这种分立模型简化了程序的开发和维护,并且也符合现实需要。所以在Q#程序中实际上分了三个层次: 468 | 469 | 1. 经典计算。完成数据的输入/输出,设置和触发量子计算机并处理计算结果等经典计算机能够处理的任务。 470 | 2. 量子计算。直接在量子计算设备上应用量子算法完成计算任务。 471 | 3. 量子计算在处理过程中调用经典计算。 472 | 473 | 接下来,就让我们走进这个年轻而又魔幻的Q#世界。 474 | 475 | ### 4.1 安装Q#开发环境 476 | 477 | #### 4.1.1 安装前的准备 478 | 479 | - Q#开发环境可以运行在Windows、Mac和Linux操作系统上,但系统必须是64位且安装了 [.NET Core SDK 2.0](https://www.microsoft.com/net/learn/get-started) 或以上版本。 480 | - Q#语言的模拟器应用了AVX(高级矢量扩展)指令集,在支持AVX的CPU(Intel `Sandy Bridge` 架构和之后的CPU)上,Q#模拟器可以发挥出最大的性能,如果你的CPU不支持AVX指令集,Q#模拟器一样能够运行,但是性能估计会大打折扣。 481 | - 开发环境 482 | 1. 在Windows上,使用 [Visual Studio 2017](https://www.visualstudio.com/) 和 [Microsoft Quantum Development Kit](https://marketplace.visualstudio.com/items?itemName=quantum.DevKit) 进行开发 483 | 2. 在Linux和Mac上,使用 [Visual Studio Code](https://code.visualstudio.com/) 和 [Microsoft Quantum Development Kit for Visual Studio Code](https://marketplace.visualstudio.com/items?itemName=quantum.quantum-devkit-vscode) 进行开发 484 | 485 | 我们以Windows系统为例,如果没有安装Visual Studio 2017,可以通过以下步骤安装免费的 Community版Visual Studio 2017: 486 | 487 | 1. 访问[Visual Studio下载页面](https://visualstudio.microsoft.com/zh-hans/downloads/) ,并选择Community版本进行下载(如果有钱也可以下载另外的版本) 488 | 2. 下载完毕后双击安装文件 489 | 3. **注意**:安装开始前程序会让你选择特定的开发环境和工具,记得一定要选上 `Universal Windows Platform development` **和** `.NET desktop development` 490 | 4. 在选择好需要的环境和工具后点击“安装”即可。 491 | 492 | #### 4.1.2 构建Q#语言开发环境 493 | 494 | 使用Q#语言需要安装Q#语言的开发工具包,安装过程也是很简单的。 495 | 496 | 1. 访问[微软Q#语言主页](https://marketplace.visualstudio.com/items?itemName=quantum.DevKit),点击页面上的 "Download" 按钮,此时页面将跳转至Q#开发工具包的下载界面 497 | 2. 在下载界面的右侧,填写一些必要的信息,包括你的名字、联系方式等等,之后点击右下角的 "Download Now" 按钮,就会开始下载开发工具包。工具包是一个vsix格式的文件,大小只有1M左右。 498 | 3. 双击下载好的文件,稍等片刻Q#语言的开发工具就安装在了Visual Studio2017中。 499 | 500 | #### 4.1.3 验证刚刚安装的开发环境 501 | 502 | 我们使用微软提供的一些例子和库对刚刚安装的环境进行检测,以验证我们是否正确安装好了Q#语言的开发环境。 503 | 504 | 1. 克隆GitHub上微软官方提供的[Q#语言例程](https://github.com/microsoft/quantum) 505 | 506 | ```c 507 | git clone https://github.com/Microsoft/Quantum.git 508 | ``` 509 | 510 | 2. 使用Visual Studio打开 `QsharpLibraries.sln` 1. 如果此时弹出 "**Install Missing Features"** 的面板,点击“安装” 511 | 512 | 3. 选择 Teleport 示例程序并运行,如果出现了类似下面这样的输出,那么说明我们的Q#语言开发环境安装正确,你可以开始在量子世界里的挑战了! 513 | 514 | ``` 515 | Round 0: Sent True, got True. 516 | Teleportation successful!! 517 | Round 1: Sent False, got False. 518 | Teleportation successful!! 519 | ... 520 | Round 6: Sent True, got True. 521 | Teleportation successful!! 522 | Round 7: Sent False, got False. 523 | Teleportation successful!! 524 | ``` 525 | 526 | **如果出现了与NuGet有关的错误,使用**[**NuGet package restore**](#)**中的方法重置安装包即可.** 527 | 528 | ### 4.2 第一个Q#语言程序 529 | 530 | 这一节的主要内容包括: 531 | 532 | - 在Visual Studio中设置Q#语言工程和解决方案 533 | - Q#语言中一个`operation`的组成 534 | - 如何从C#中调用Q#语言的 `operation` 535 | - 如何构建和运行Q#程序 536 | 537 | #### 4.2.1 在Q#中创建一个贝尔态(Bell State) 538 | 539 | 现在,我们已经安装好了Q#语言的开发环境,开始编写我们的第一个量子计算程序,在这个程序中,我们以一个初始态为`|0>`的量子比特为起点,对其进行一些操作,向你展示Q#程序的基本面貌。 540 | 541 | **(1) 创建工程和解决方案** 542 | 543 | 打开Visual Studio 2017,依次点击“文件-->新建-->项目”,在出现的新建项目对话框中,选中左侧栏目中的“`Visual C#`”,此时在对话框中部会出现许多条目,找到“`Q# Application`”并选中,设置项目名称为`Bell`,如下图所示。如果没有找到“`Q# Application`”的条目,检查对话框上部列表选择框中是否选中了“`.NET Framework 4.6.1`”。 544 | 545 | ![CreateASharpApp](./image/CreateQSharpApp.png) 546 | 547 | **(2) 编辑Q#代码** 548 | 549 | 在完成工程创建后,此时会有两个文件:`Driver.cs`和`Operation.qs`,前者就是使用C#编写的驱动程序,后者才是真正的Q#代码文件。 550 | 551 | 首先将`Operation.qs`文件重命名为`Bell.qs`,Visual Studio在创建工程时会自动生成一部分代码,此时`Bell.qs`中的内容类似于这样: 552 | 553 | ```c# 554 | namespace Quantum.Bell 555 | { 556 | open Microsoft.Quantum.Primitive; 557 | open Microsoft.Quantum.Canon; 558 | operation Operation () : () 559 | { 560 | body 561 | { 562 | } 563 | } 564 | } 565 | ``` 566 | 567 | 然后将代码中第二个“`Operation`”改为`Set`,并且将其后面第一个括号中的内容改为`desired: Result, q1:Qubit`,此时`Bell.qs`中的内容应为: 568 | 569 | ```c# 570 | namespace Quantum.Bell 571 | { 572 | open Microsoft.Quantum.Primitive; 573 | open Microsoft.Quantum.Canon; 574 | operation Set (desired: Result, q1:Qubit) : () 575 | { 576 | body 577 | { 578 | } 579 | } 580 | } 581 | ``` 582 | 583 | 现在,将以下代码键入`body`后的大括号中: 584 | 585 | ```c# 586 | let current = M(q1); 587 | if (desired != current) 588 | { 589 | X(q1); 590 | } 591 | ``` 592 | 593 | `Bell.qs`中的代码为: 594 | 595 | ```c# 596 | namespace Quantum.Bell 597 | { 598 | open Microsoft.Quantum.Primitive; 599 | open Microsoft.Quantum.Canon; 600 | operation Set (desired: Result, q1:Qubit) : () 601 | { 602 | body 603 | { 604 | //测量qubit状态 605 | let current = M(q1); 606 | if (desired != current) 607 | { 608 | //翻转 609 | X(q1); 610 | } 611 | } 612 | } 613 | } 614 | ``` 615 | 616 | 现在我们来解释一下上面的代码。上面的代码中定义了一个Q#语言中的 `operation`,`operation`是Q#语言中一个基本的执行单元,它相当于其他编程语言如C/C++、C#和Java中的函数。一个`operation`的参数是一个元组,在`operation`名字之后的括号中指定各个参数,参数之间用逗号分隔,参数的定义形式为:“arg-name : arg-type",`operation`的返回值与其参数形式类似,在参数列表之后的括号中指定,两个括号之间由一个冒号分隔。Q#语言中,`operation`可以指定多个返回值,也可以将括号留空表示没有返回结果。 617 | 618 | 一个`operation`中包含一个`body`段,在`body`段中的代码就是该`operation`功能的实现代码。所以上面代码所完成的事就是,定义了一个名为Set的`operation`,它接受两个名称分别为`desired`和`q1`的参数,没有返回值。这个`operation`首先测量`q1`的量子态(M),如果结果与`desired`的量子态相同,则返回,否则就对`q1`应用$X$门,使其状态翻转。 619 | 620 | 现在来编写一个测试程序调用这个operation并观察结果。在`Bell.qs`中添加如下代码,可以看到这也是一个`operation`,将其添加在`Set`之后。 621 | 622 | ```c# 623 | operation BellTest (count : Int, initial : Result) : (Int, Int) 624 | { 625 | body 626 | { 627 | mutable numOnes = 0; 628 | using (qubits = Qubit[1]) 629 | { 630 | for (test in 1..count) 631 | { 632 | Set (initial, qubits[0]); 633 | let res = M (qubits[0]); 634 | // Count the number of ones we saw: 635 | if (res == One) 636 | { 637 | set numOnes = numOnes + 1; 638 | } 639 | } 640 | Set(Zero, qubits[0]); 641 | } 642 | // Return number of times we saw a |0> and number of times we saw a |1> 643 | return (count-numOnes, numOnes); 644 | } 645 | ``` 646 | 647 | 上面的`operation BellTest`中定义了一个循环`count`次的控制结构,在每次循环中,首先将一个量子比特设置为`initial`,然后测量这个量子的状态,如果为1,将变量`numOnes`加一,所有循环结束后,我们将得到在这个过程中共有多少次测量的量子比特状态为$|1 \rangle$,多少次为$|0 \rangle$。在最后,我们把量子比特重新设置为了$|0 \rangle$,使其处于一个特定的状态。 648 | 649 | 从上面的代码中可以看出,Q#语言使用了与C#相似的分号、括号等来标示程序的结构,并且Q#也拥有与C#类似的`if`控制语句。 650 | 651 | 默认情况下,Q#中的变量一旦被绑定,其值就不能再改变,关键字let用来将值绑定在一个变量上,同时Q#中`operation`的参数也是不可变的。 652 | 653 | 如果需要使用可变的变量,就需要使用`mutable`关键字对变量进行声明,就像上面的程序中那样,经过`mutable`关键字声明的变量,其可以使用也只能使用`set`语句重新赋值。 654 | 655 | Q#中定义变量不需要明确指明数据类型,变量的类型由编译器根据其具体值进行自动推断。 656 | 657 | `using`语句在Q#中有着特定的用途,它用于为程序分配qubit。在Q#中,所有的qubit都是动态分配和释放的,并且每个qubit的初始状态都为$|0\rangle$ 。`using`语句在代码段的起始处分配qubit,并在代码段结束后自动释放它们。 658 | 659 | Q#中的`for`循环在一个范围之内进行迭代,范围在Q#中是一种特殊的数据类型,一个范围可以直接由起始和终止的值来指定,两个值之间使用`..`连接。例如`1..10`就表示1,2,3,4,5,6,7,8,9,10,默认步长为1,如果需要不同的步长值,则直接指定即可,其语法为`1..2..10`,这个范围表示的数值就是1,3,5,7,9。值得注意的是,Q#中的范围是一个**闭区间**,也就是包含起始值和终止值。 660 | 661 | Q#中使用元组来传递多个变量,上面的`operation BellTest`的返回值就是由两个`Int`数据`(Int, Int)`组成的元组。 662 | 663 | **(3) C#驱动代码** 664 | 665 | 现在我们将目光转向`Driver.cs`文件中,在创建项目时,Visual Studio会自动生成一部分C#代码,内容如下: 666 | 667 | ```c# 668 | using Microsoft.Quantum.Simulation.Core; 669 | using Microsoft.Quantum.Simulation.Simulators; 670 | namespace Quantum.Bell 671 | { 672 | class Driver 673 | { 674 | static void Main(string[] args) 675 | { 676 | } 677 | } 678 | } 679 | ``` 680 | 681 | 在`Main`方法中,我们输入以下代码: 682 | 683 | ```c# 684 | using (var sim = new QuantumSimulator()) 685 | { 686 | // Try initial values 687 | Result[] initials = new Result[] { Result.Zero, Result.One }; 688 | foreach (Result initial in initials) 689 | { 690 | var res = BellTest.Run(sim, 1000, initial).Result; 691 | var (numZeros, numOnes) = res; 692 | System.Console.WriteLine($"Init:{initial,-4} 0s={numZeros,-4} 1s={numOnes,-4}"); 693 | } 694 | } 695 | System.Console.WriteLine("Press any key to continue..."); 696 | System.Console.ReadKey(); 697 | ``` 698 | 699 | 上面的C#代码包含4个部分: 700 | 701 | - 构造一个量子模拟器,即代码中的`sim`,Q#量子程序均需要由这个模拟器来运行。 702 | - 计算量子算法需要的参数,上面的代码中,BellTest需要的`count`为1000,`initial`是量子比特需要的初始值,上面代码中定义的`initials`是一个数组,其中包含两个值`Zero`和`One`,它们相当于C语言中的枚举类型,相信你也猜到了,它们分别代表了一个qubit的基本量子态$|0 \rangle$ 和$|1 \rangle$ 。 703 | - 运行量子算法程序。每一个`Q# operation`都会生成一个名字相同的C#类,这个类拥有一个名为`run`的方法,它返回最终`operation`执行的结果。 704 | - 处理`operation`返回的结果。在上面的程序中,`res`接受`operation`产生的结果。 705 | 706 | **(4) 构建和运行** 707 | 708 | 现在点击F5键,程序将会被自动构建和运行。稍后片刻,我们就能看到如下的输出结果 709 | 710 | ```c# 711 | Init:Zero 0s=1000 1s=0 712 | Init:One 0s=0 1s=1000 713 | Press any key to continue... 714 | ``` 715 | 716 | 如果你得到了这样的结果,那么恭喜你完成了第一个Q#量子程序! 717 | 718 | ### 4.3 Q#程序结构 719 | 720 | 在完成了第一个Q#程序后,我们来看一看Q#代码的主要特点。 721 | 722 | #### 4.3.1 语句 723 | 724 | 语句是Q#代码中最基本的单位,处理流程控制语句(if,for等),每一条语句都使用分号`;`结尾。如下所示皆为合法的语句: 725 | 726 | ``` 727 | let a = 1; //赋值语句 728 | set b = b + 1; //数学运算语句 729 | M(q); //量子态测量语句 730 | X(1); //调用量子门语句 731 | ``` 732 | 733 | #### 4.3.2 注释 734 | 735 | Q#中有两种注释: 736 | 737 | - 单行注释 738 | 739 | 以//开头后跟注释内容,这样的注释在其他语言中也应用广泛,如C/C++、Java等。 740 | 741 | - 文档注释 742 | 743 | 以///开头的注释为文档注释,文档注释出现在`operation`、`function`和类型定义之前,他们会得到编译器的特殊关照,注释内容会作为可调用类型或用户自定义类型的说明文档。 744 | 745 | 文档注释的文本是API文档的一部分,它使用[Markdown文档格式](#),文档中不同的部分会使用相应的命名标题标识出来。作为对Markdown文档格式的扩展,对`operation`、`function`和用户自定义类型的交叉引用可以使用`@""`来引入,其中``是要引用的类型的全限定名称。 746 | 747 | 以下代码展示了文档注释的模样: 748 | 749 | ``` 750 | /// # Summary 751 | /// Given an operation and a target for that operation, 752 | /// applies the given operation twice. 753 | /// 754 | /// # Input 755 | /// ## op 756 | /// The operation to be applied. 757 | /// ## target 758 | /// The target to which the operation is to be applied. 759 | /// 760 | /// # Type Parameters 761 | /// ## 'T 762 | /// The type expected by the given operation as its input. 763 | /// 764 | /// # Example 765 | /// ```Q# 766 | /// // Should be equivalent to the identity. 767 | /// ApplyTwice(H, qubit); 768 | /// ``` 769 | /// 770 | /// # See Also 771 | /// - Microsoft.Quantum.Primitive.H 772 | operation ApplyTwice(op : (Qubit => ()), target : Qubit) : () { 773 | body { 774 | op(target); 775 | op(target); 776 | } 777 | } 778 | ``` 779 | 780 | 下面的名称是文档注释中的命名标题。 781 | 782 | - `Summary`:对定义的`operation`、`function`和自定义类型的简要概括 783 | - `Input`:对`operation`、`function`输入数据的描述,在其后面可以跟子段落以对各个输入参数进行简要说明,如上面代码中的`##op`、`##target` 784 | - `Output`:对输出数据的描述 785 | - `Type Parameter`:这是一个空段,它包含了对泛型参数类型进行描述的子段落 786 | - `Example`:一个简短的说明`operation`、`function`和自定义类型如何使用的示例 787 | - `Remarks`:描述`operation`、`function`和类型的某些方面的文字 788 | - `See Also`:列出相关联的`operation`、`function`和类型的全限定名称 789 | - `Reference`:记录引用和参考到的资料 790 | 791 | #### 4.3.3 命名空间 792 | 793 | Q#中,每一个`operation`、`function`和用户自定义类型等都定义在某个命名空间中,Q#遵循与其他.NET语言相同的命名规则,但Q#中不支持嵌套的命名空间。特别地,对于两个定义的命名空间`NS.Name1`和`NS.Name2`,只有使用全部名称时才能被打开,直接打开`open NS`是错误的。 794 | 795 | 每个Q#文件必须包含至少一个命名空间声明,声明命名空间时以`namespace`关键字引出,后跟名称和一对大括号`{}`,除了注释以外,所有代码都必须写在这对大括号里面。 796 | 797 | `open`关键字用来打开一个命名空间。使用时在`open`指令后跟要打开的命名空间。`open`指令必须出现在任何`operation`、`function`和`newtype`指令之前,且`open`指令的作用域为整个命名空间。如果一个命名空间未被打开,当要引用其中的结构时必须使用全限定名称,例如`X.Y`命名空间中有一个名为`Op`的`operation`,要引用该`operation`,则必须指定其全限定名称:`X.Y.Op`,如果这个命名空间已被打开,那么可以直接使用`Op`。 798 | 799 | 一般来说,我们更倾向于使用`open`指令打开一个命名空间,但这可能带来潜在的命名冲突问题,在这种情况下,就要使用全限定名称来引用其他命名空间中的结构了。 800 | 801 | `open`指令以及`namespace`的用法如下面中示例所示: 802 | 803 | ``` 804 | namespace Quantum.Bell 805 | { 806 | open Microsoft.Quantum.Primitive; 807 | open Microsoft.Quantum.Canon; 808 | 809 | ... 810 | } 811 | ``` 812 | 813 | ### 4.4 创建量子叠加态 814 | 815 | 上面的程序中,我们所操作的qubit均处于基本状态$|0\rangle$ 或$| 1 \rangle$,这导致最后的结果仍然与经典计算无异,没有明显的量子计算的特征,现在我们应用$H$门来使qubit处于叠加态,再来看看最后的结果。将下面的代码输入`BellTest`中: 816 | 817 | ```c# 818 | H(qubits[0]); 819 | let res = M (qubits[0]); 820 | ``` 821 | 822 | 此时`BellTest`代码如下: 823 | 824 | ```c# 825 | operation BellTest (count : Int, initial : Result) : (Int, Int) 826 | { 827 | body 828 | { 829 | mutable numOnes = 0; 830 | using (qubits = Qubit[1]) 831 | { 832 | for (test in 1..count) 833 | { 834 | Set (initial, qubits[0]); 835 | H(qubits[0]); 836 | let res = M (qubits[0]); 837 | // Count the number of ones we saw: 838 | if (res == One) 839 | { 840 | set numOnes = numOnes + 1; 841 | } 842 | } 843 | Set(Zero, qubits[0]); 844 | } 845 | // Return number of times we saw a |0> and number of times we saw a |1> 846 | return (count-numOnes, numOnes); 847 | } 848 | ``` 849 | 850 | 现在重新运行程序,将会得到类似于下面这样的输出结果: 851 | 852 | ```c# 853 | Init:Zero 0s=484 1s=516 854 | Init:One 0s=522 1s=478 855 | ``` 856 | 857 | 从结果可以看出,测量经过$H$ 门作用的量子后得到的测量结果$|0\rangle$ 和$|1 \rangle$ 的概率均在0.5左右。 858 | 859 | ### 4.5 创建量子纠缠 860 | 861 | 回想我们在前面讲过的处于`Bell State`的量子系统,当测得第一个量子的状态后,剩下的量子其状态与第一个保持一致,有着非常强的关联性。 862 | 863 | 现在我们使用Q#来创建量子纠缠态`Bell State`。首先我们要做的就是分配两个qubit而非上面程序中的1个。 864 | 865 | ```c# 866 | using (qubits = Qubit[2]) 867 | ``` 868 | 869 | 在进行测量操作之前,我们对这个两个qubit应用`CNOT`门: 870 | 871 | ```c# 872 | Set (initial, qubits[0]); 873 | Set (Zero, qubits[1]); 874 | H(qubits[0]); 875 | CNOT(qubits[0],qubits[1]); 876 | let res = M (qubits[0]); 877 | ``` 878 | 879 | 在上面的代码中,我们新增了一个`Set operation`将第二个量子比特初始化为`Zero`,并在其被释放之前将其重置为`Zero`: 880 | 881 | ```c# 882 | Set(Zero, qubits[1]); 883 | ``` 884 | 885 | 现在`BellTest`代码如下所示: 886 | 887 | ```c# 888 | operation BellTest (count : Int, initial: Result) : (Int,Int) 889 | { 890 | body 891 | { 892 | mutable numOnes = 0; 893 | using (qubits = Qubit[2]) 894 | { 895 | for (test in 1..count) 896 | { 897 | Set (initial, qubits[0]); 898 | Set (Zero, qubits[1]); 899 | H(qubits[0]); 900 | CNOT(qubits[0],qubits[1]); 901 | let res = M (qubits[0]); 902 | // Count the number of ones we saw: 903 | if (res == One) 904 | { 905 | set numOnes = numOnes + 1; 906 | } 907 | } 908 | //将两个qubit的状态重置,以保证在其他地方使用时处于正确地初始状态。 909 | Set(Zero, qubits[0]); 910 | Set(Zero, qubits[1]); 911 | } 912 | // Return number of times we saw a |0> and number of times we saw a |1> 913 | return (count-numOnes, numOnes); 914 | } 915 | } 916 | ``` 917 | 918 | 这份代码将产生与之前代码同样的结果,然而,这里我们真正感兴趣的是在第一个量子比特被测量后,第二个量子比特将会有何反应。为此,我们对`BellTest`进行修改,向其中加入统计相关的代码: 919 | 920 | ```c# 921 | operation BellTest (count : Int, initial: Result) : (Int,Int,Int) 922 | { 923 | body 924 | { 925 | mutable numOnes = 0; 926 | mutable agree = 0; 927 | using (qubits = Qubit[2]) 928 | { 929 | for (test in 1..count) 930 | { 931 | Set (initial, qubits[0]); 932 | Set (Zero, qubits[1]); 933 | H(qubits[0]); 934 | CNOT(qubits[0],qubits[1]); 935 | let res = M (qubits[0]); 936 | if (M (qubits[1]) == res) 937 | { 938 | set agree = agree + 1; 939 | } 940 | // Count the number of ones we saw: 941 | if (res == One) 942 | { 943 | set numOnes = numOnes + 1; 944 | } 945 | } 946 | Set(Zero, qubits[0]); 947 | Set(Zero, qubits[1]); 948 | } 949 | // Return number of times we saw a |0> and number of times we saw a |1> 950 | return (count-numOnes, numOnes, agree); 951 | } 952 | } 953 | ``` 954 | 955 | 在上面代码中,我们增加了一个新的返回值`agree`,他将记录第一个qubit的测量值与第二个qubit的测量值相等的次数。 956 | 957 | 我们相应的修改`Driver.cs`代码: 958 | 959 | ```c# 960 | using (var sim = new QuantumSimulator()) 961 | { 962 | // Try initial values 963 | Result[] initials = new Result[] { Result.Zero, Result.One }; 964 | foreach (Result initial in initials) 965 | { 966 | var res = BellTest.Run(sim, 1000, initial).Result; 967 | var (numZeros, numOnes, agree) = res; 968 | System.Console.WriteLine( 969 | $"Init:{initial,-4} 0s={numZeros,-4} 1s={numOnes,-4} agree={agree,-4}"); 970 | } 971 | } 972 | System.Console.WriteLine("Press any key to continue..."); 973 | System.Console.ReadKey(); 974 | ``` 975 | 976 | 现在我们来看一看运行结果,惊不惊喜?意不意外?开不开心?: 977 | 978 | ```c# 979 | Init:Zero 0s=499 1s=510 agree=1000 980 | Init:One 0s=490 1s=510 agree=1000 981 | ``` 982 | 983 | 从结果看,对于第一个量子比特而言,统计结果并没有改变(50%的概率为$|1 \rangle$ ,50%的概率为$| 0 \rangle$),但是我们对第二个量子比特的测量值总是与第一个量子比特的测量值保持一致,这个结果也充分展现了处于`Bell State`的量子系统的特征。 984 | 985 | 在将要结束本章之前,我们再来回顾一下上面的代码示例,体会一下Q#语言独有的特性。我们发现,Q#已经为我们提供了基本的量子门操作,比如我们用到的`H`门、`X`门操作等,并且使用`M()`就能完成对量子态的测量。Q#提供的这些设施,保持了与我们所学习的量子计算中相关符号的一致,这使得我们应用Q#的难度大大降低,使用起来更加得心应手。 986 | 987 | ## 第五章 数据类型和控制结构 988 | 989 | 一门编程语言的要素一般包括数据类型、控制结构、运行模式等等,Q#语言也不例外,但因为它是面向量子计算机的,所以必有其特殊性存在。这一章中我们主要介绍Q#语言的数据类型和控制结构。 990 | 991 | ### 5.1 数据类型 992 | 993 | #### 5.1.1 基本数据类型 994 | 995 | Q#提供的基本数据类型如下,除去这些基本类型,其他类型都属于结构化类型。 996 | 997 | - `Int`:64位有符号整型 998 | - `Double`:双精度浮点数类型 999 | - `Bool`:布尔类型,取值为`true`或`false` 1000 | - `Qubit`:`Qubit`表示qubit,只能通过使用`operation`对其进行各种操作,Qubit是引用类型,与它相关的资源管理等工作由开发环境负责,用户在使用时必须申请所需要的qubit。 1001 | - `Pauli`:表示`Pauli`门中的一个,量子计算中,一共有四个`Pauli`门:`PauliX`、`PauliY`、`PauliZ`和`PauliI`。 1002 | - `Result`:`Result`表示测量一个qubit可能得到的结果,它是由两个可能的取值构成的结构体,这两个值分别是:`One`和`Zero`,`Zero`表示特征值$| 0 \rangle$,`One`表示特征值$| 1 \rangle$ 。 1003 | - `Range`:`Range`表示整数序列,如`1..5`这个`Range`就表示`(1,2,3,4,5)`这个整数序列,注意在Q#中,`Range`所表示的范围是一个闭区间,包含头尾的数值。 1004 | - `String`:Unicode字符串,在Q#中,`String`主要用于当程序抛出异常或出现错误时向用户提供信息。 1005 | - `operation`:可调用类型,用来定义和执行量子操作,相当于其他高级语言中的函数。 1006 | - `function`:可调用类型,与`operation`类似,相当于其他语言中的函数,但其内部不能进行量子操作。 1007 | 1008 | #### 5.1.2 元组(Tuple) 1009 | 1010 | 元组中能够包含任意数量、不同类型的数据。在Q#中,我们使用下面的形式来表示一个元组: 1011 | 1012 | ```c# 1013 | (T0, T1, ..., Tn) 1014 | ``` 1015 | 1016 | 其中,`T0,T1...Tn`表示不同的数据类型,所有元素的类型构成的元组就是这个元组的真实类型。例如元组`(1,false)`的类型即为`(Int,Bool)`。 1017 | 1018 | 元组中的元素同样可以是元组,例如`(1,(2,false))`就是一个类型为`(Int,(Int,Bool))`的元组。 1019 | 1020 | 空元组使用一对空括号`()`来表示。 1021 | 1022 | 元组在Q#中有着非常重要的作用,它将不同类型的数据组合在一起成为了一个单独的类型,对于一个函数而言,我们就可以仅使用一个变量表示它的参数和返回值,这将给我们带来巨大便利。 1023 | 1024 | **元组一旦定义就不能改变其内容。** 1025 | 1026 | 同时需要注意一下只含有一个元素的元组`(T)`,在Q#中,单个元素的元组与这个元素本身是等价的,例如`(5)`和`([1,2,3])`,`(5)`和5以及`([1,2,3])`和`[1,2,3]`没有任何区别,同样的`(((5)))`与5,`(5,(6))`与`(5,6)`也都是一样的。这个性质在Q#中称为**单元素元组等价**(`Singleton Tuple Equivalence`)。 1027 | 1028 | Q#中,元组没有提供诸如使用下标访问元素的方法,元组元素的访问需要将其赋值给相应的变量来完成的,这样的方式又叫做解构。如下所示: 1029 | 1030 | ```c# 1031 | let (a,b) = (1,2); //a = 1;b = 2 1032 | let (a,(b,c)) = (1,(2,3)); //a = 1;b = 2;c = 3 1033 | ``` 1034 | 1035 | #### 5.1.3 数组(Array) 1036 | 1037 | 任何一个合法的Q#数据类型都可以构成数组,表示为`T[]`,其中`T`为任意合法类型,例如`Qubit[]`是元素类型为Qubit的一维数组,`Int[][]`是元素类型为`Int`的二维数组,元组类型的数据也可以作为数组元素,例如`(Int,Bool)[]`就表示元素数据类型为`(Int,Bool)`的数组。 1038 | 1039 | 数组可以使用`new`操作符定义,并在`[]`中指定数组的大小,就像下面这样: 1040 | 1041 | ```c# 1042 | let zeros = new Int[13]; 1043 | // Q#中可以定义一个长度为0的数组 1044 | let emptyRegister = new Qubit[0]; 1045 | ``` 1046 | 1047 | 数组被创建时,其中的元素也同时进行初始化,初始化值根据具体的类型来确定,通常默认的初始化值都为0。对于qubit类型而言,因其是引用类型,因此它被初始化为一个非法的引用,如果直接使用将会导致运行错误,这类似于C#或Java中的null,要正确使用它们,必须使用set语句对每个元素进行赋值。 1048 | 1049 | 下标列出了部分类型的默认值: 1050 | 1051 | | 类型 | 默认值 | 1052 | | -------- | ---------------- | 1053 | | Int | 0 | 1054 | | Double | 0.0 | 1055 | | Bool | false | 1056 | | String | "" | 1057 | | Qubit | invalid qubit | 1058 | | Pauli | PauliI | 1059 | | Result | Zero | 1060 | | Range | 空范围:1..1..0 | 1061 | | Callable | invalid callable | 1062 | | Array[T] | T[0] | 1063 | 1064 | 同时,数组也可以在定义时直接将元素显示的表达出来,这样就不需要使用`new`操作符,在数组中,元素之间使用分号进行分隔,示例如下: 1065 | 1066 | ```c# 1067 | let arr = [10; 11; 36; 49]; 1068 | ``` 1069 | 1070 | 数组可以使用下标进行访问,Q#的数组下标索引值是从0开始的,同时数组可以通过使用`Range`表达式一次性抽取多个元素,访问数组元素的方法如下所示: 1071 | 1072 | ```c# 1073 | let ten = arr[0]; // 10 1074 | let odds = arr[1..2..4]; // [11; 49],使用范围表达式抽取多个元素 1075 | ``` 1076 | 1077 | 数组创建完成后,使用`Length`函数就能够得到数组的长度。 1078 | 1079 | ```c# 1080 | Length(arr); //结果为4 1081 | ``` 1082 | 1083 | 但是必须注意的是,在Q#中,变量一旦绑定后,它的值就不能再改变,这个限制针对的对象包括数组。因此,在上面的数组用法示例中,无论使用`new`操作符还是直接指定元素,在定义完成后,数组中的元素就不能再有任何变化。那么需要改变元素怎么办呢?这就要使用`mutable`修饰符进行修饰,被`mutable`修饰过的数组,可以使用`set`关键字改变其中的元素,例如: 1084 | 1085 | ```c# 1086 | mutable arr1[] = new Int[4]; 1087 | for (i in 0..3) { 1088 | set arr1[i] = i; //给数组元素重新赋值 1089 | } 1090 | ``` 1091 | 1092 | #### 5.1.4 用户自定义类型 1093 | 1094 | 除了以上介绍的基本数据类型和数组、元组等类型,Q#也提供了用户自定义类型的机制。用户自定义类型在形式上与元组非常接近,但需要使用`newtype`关键字对新的类型进行声明。 1095 | 1096 | ```c# 1097 | //T1,T2是任意合法的Q#数据类型,但不能是当前自定义类型 1098 | newtype TypeName = (T1,T2,...); 1099 | newtype IntPair = (Int,Int); 1100 | ``` 1101 | 1102 | 用户自定义类型有两个限制,一是不能递归定义,也就是说新的类型结构中不能包含该类型本身,如下定义的类型就是非法的递归结构类型: 1103 | 1104 | ```c# 1105 | newtype Type1 = (Int,Type1); 1106 | ``` 1107 | 1108 | 二是定义的类型之间不能存在循环依赖,这一点可以通过以下代码来说明: 1109 | 1110 | ```c# 1111 | //非法,存在循环依赖 1112 | newtype TypeA = (Int, TypeB); 1113 | newtype TypeB = (Double, TypeC); 1114 | newtype TypeC = (TypeA, Range); 1115 | ``` 1116 | 1117 | 现在我们来定义一个行的类型:`Complex`。 1118 | 1119 | ```c# 1120 | newtype Complex = (Double, Double); 1121 | ``` 1122 | 1123 | 当要创建用户自定义类型的变量时,直接使用类型名,后面使用括号将需要的值括起来即可。比如这里,我么想要定义`Complex`类型的变量,我们可以像下面这样: 1124 | 1125 | ```c# 1126 | let realUnit = Complex(1.0, 0.0); 1127 | let imaginaryUnit = Complex(0.0, 1.0); 1128 | ``` 1129 | 1130 | 同时,我们可以定义相应的函数来进行一些操作,比如下面的代码中,我们定义了两个函数,一个用来得到`Complex`类型的实部,另一个则得到虚部。 1131 | 1132 | ```c# 1133 | //获取实部 1134 | function Re(z : Complex) : Double { 1135 | let (re, im) = z; 1136 | return re; 1137 | } 1138 | //获取虚部 1139 | function Im(z : Complex) : Double { 1140 | let (re, im) = z; 1141 | return im; 1142 | } 1143 | ``` 1144 | 1145 | #### 5.1.5 用户自定义类型的兼容性 1146 | 1147 | 用户自定义类型是其父类的子类,例如上面的例子中,`IntPair`是`(Int,Int)`的子类,`(Int,Int)`是`IntPair`的父类。在Q#中,凡是可以使用其父类的地方也同样能够使用该自定义类型,并且这个性质能够一直向后传递,例如类型`IntPair`基于`(Int,Int)`定义,`IntPair2`基于`NewPair`定义,那么凡是可以使用`(Int,Int)`、`NewPair`的地方都能使用`NewPair2`。 1148 | 1149 | 但是如果两个自定义类型拥有相同的父类类型,但它们之间是兄弟关系仅仅名字不同,这样的类型之间是不兼容的。例如假设一个新的类型`IntPair3`也基于`(Int,Int)`进行定义,它与`IntPair`之间没有上下的继承关系,尽管它们的父类都是`(Int,Int)`,但在需要使用`IntPair`的地方,并不能够使用`IntPair3`进行代替。 1150 | 1151 | ### 5.2 变量 1152 | 1153 | 变量使用关键字`let`进行定义,Q#中变量定义不需要指定类型,编译器会根据其所绑定的值的类型自动推断出该变的类型。我们来看几个变量定义的示例: 1154 | 1155 | ```C# 1156 | let a = 1; // a的类型为Int 1157 | let b = (1,2); //b的类型为(Int,Int) 1158 | ``` 1159 | 1160 | #### 5.2.1 变量的可变性 1161 | 1162 | 但是比较搞笑的是,Q#中变量一旦经过定义,它的值就不能发生变化,也不能重新为其绑定别的值,即使是数组类型的变量同样要遵循这项约束。所以类似于下面这样的代码都是错误的: 1163 | 1164 | ```C# 1165 | let a = 1; 1166 | a = 2; 1167 | let b = [1;2]; 1168 | b[0] = 3; 1169 | ``` 1170 | 1171 | 为了是变量的值可变,必须使用关键字`mutable`再变量定义时对其进行声明,并且此时也不需要使用`let`关键字了,将上面变量赋值的代码修改一下就是正确的了: 1172 | 1173 | ```C# 1174 | mutable a = 1; 1175 | a = 2; 1176 | mutable b = [1;2]; 1177 | b[0] = 3; 1178 | ``` 1179 | 1180 | #### 5.2.2 代码块 1181 | 1182 | 代码块是指由一条或多条语句组成的一个整体。Q#中,一对大括号和其中的代码就构成了一个代码块,代码块中还可以包含其他代码块,这些被包含的语句块叫做子代码块,也叫作内部代码块,而一个内部代码块所在代码块就是相对于其的外部代码块。 1183 | 1184 | 一个代码块构成了一个相对独立的部分,在代码块中定义的变量,其作用域便被限定在当前代码块中,外部代码块或其他代码块中不能访问其中定义的变量、`operation`、`function`等,但内部代码块可以访问外部代码块中定义的变量等内容。 1185 | 1186 | #### 5.2.3 变量作用域 1187 | 1188 | 一般而言,一个变量的声明周期被限定在其被定义时的代码块中,在代码段的结尾处变量就不能再被操作,但这里有一个特例: 1189 | 1190 | - 一个`repeat/until`循环的三部分(循环体、测试条件、修正操作)被看做是一个作用域,所以循环体中定义的变量,在测试条件和修正操作中都是可见的。 1191 | 1192 | 内部代码块会继承在外部代码块中定义的符号;在一个代码块中,一个符号只能被定义一次。下面的代码是合法的: 1193 | 1194 | ```C# 1195 | if a == b { 1196 | ... 1197 | let n = 5; 1198 | ... // n is 5 1199 | } 1200 | let n = 8; 1201 | ... // n is 8 1202 | ``` 1203 | 1204 | ```C# 1205 | if a == b { 1206 | ... 1207 | let n = 5; 1208 | ... // n is 5 1209 | } else { 1210 | ... 1211 | let n = 8; 1212 | ... // n is 8 1213 | } 1214 | ``` 1215 | 1216 | 而下面的代码是非法的: 1217 | 1218 | ```C# 1219 | let n = 5; 1220 | ... // n is 5 1221 | let n = 8; // Error!! 1222 | ... 1223 | ``` 1224 | 1225 | ```C# 1226 | let n = 5; 1227 | ... // n is 5 1228 | let n = 8; // Error!! 1229 | ... 1230 | ``` 1231 | 1232 | ### 5.3 表达式 1233 | 1234 | #### 5.3.1 括号表达式 1235 | 1236 | 给定任意一个合法的表达式,将其用一对括号括起来构成一个新的表达式,那么这两个表达式在Q#中具有相同的类型。例如`(7)`是一个`Int`型的表达式,`([1;2;3])`的类型是`Int[]`型的数组,`((1,2))`是`(Int,Int)`型的元组。 1237 | 1238 | #### 5.3.2 代数表达式 1239 | 1240 | 代数表达式完成代数运算功能。Q#中代数表达式的类型只能是`Int`或`Double`。 1241 | 1242 | `Int`型数据的字面量在Q#和C#中是一样的,不过Q#中不需要在数值字面量后加`l`或`L`来表明其是`long`类型的数据,因为Q#中`Int`型数据天生就是64位的,如果要表示16进制数,则需要在字面量前加0x前缀。 1243 | 1244 | `Double`型数据字面量在Q#和C#中也是一样的,不过在Q#中不需要在数值字面量后加`d`或`D`来表明其实双精度浮点型,因为Q#中`Double`类型天生就是双精度浮点型的。 1245 | 1246 | 对于一个任意类型的数组,内置函数`Length`返回该数组的元素数量(`Int`型)。例如,`a`是一个数组,`Length(a)`返回`a`中元素的数量,`b`的类型为`Int[][]`,那么`Length(b)`返回`b`中字数组的数量,`Length(b[1])`返回`b`中第二个子数组中元素的数量。 1247 | 1248 | 代数运算中运算符`+、-、*、/`与操作数一起构成新的代数表达式,如果操作数中有一个是`Double`类型那么这个表达式的类型也是`Double`类型,如果两个操作数都是`Int`类型,那么该表达式也是`Int`类型。 1249 | 1250 | 对于整型数而言,其支持的运算和运算符还包括:`%`(取模)、`^`(乘方)、`&&&`(按位与)、`|||`(按位或)、`^^^`(按位异或)、`~~~`(按位取反)、`<<<`(算术右移)、`>>>`(算术左移)。在移位操作中,第二个操作数必须大于等于0,如果小于0,对应的行为是未定义的。 1251 | 1252 | #### 5.3.3 qubit表达式(Qubit expressions) 1253 | 1254 | Q#中qubit没有对应的字面量,将qubit或qubit数组绑定到一个符号上就构成了一个量子位表达式。 1255 | 1256 | #### 5.3.4 泡利表达式(Pauli expressions) 1257 | 1258 | 四个`Pauli`值:`PauliI`、`PauliX`、`PauliY`和`PauliZ`都是合法的泡利表达式,将这些值或这些值组成的数组绑定至一个符号上也构成合法的泡利表达式。 1259 | 1260 | #### 5.3.5 结果表达式(Result expressions) 1261 | 1262 | `Result`有两个取值:`One`和`Zero`,除了这两个值,将这两个值或由其组成的数组绑定至一个符号也构成合法的结果表达式。需要注意的是,这里的`One`并不等于整数1,两者之间也不存在任何直接的转换方法,但是`Zero`与0是相等的。 1263 | 1264 | #### 5.3.6 范围表达式(Range expressions) 1265 | 1266 | 使用三个整数就能构成一个范围表达式,这三个整数分别表示起始值、步长和终止值,范围表达式形式为:`start..step..stop`。范围表达式的其实值就是`first`,第二个值是`first+step`,第三个值是`first+step*2`,以此类推,直到数值到达或超过了`stop`。范围表达式也可以表示一个空的范围,例如当`step`为正并且`start>> | 双目 | 算数右移,算数左移 | 1304 | | <,<=,>,>= | 双目 | 小于,小于等于,大于,大于等于 | 1305 | | ==,!= | 双目 | 相等,不等 | 1306 | | &&& | 双目 | 按位与 | 1307 | | ^^^ | 双目 | 按位异或 | 1308 | | \|\|\| | 双目 | 按位或 | 1309 | | && | 双目 | 逻辑与 | 1310 | | \|\| | 双目 | 逻辑或 | 1311 | 1312 | ### 5.4 控制结构 1313 | 1314 | #### 5.4.1 for循环 1315 | 1316 | `for`循环在一个整数范围中进行迭代,一个`for`循环由关键字`for`引出,后跟一个括号,括号中分为三部分:循环变量的变量名、关键字`in`和一个`Range`表达式,括号后就是相应的代码段。如果`Range`表达式代表了一个空范围,那么`for`循环语句将不会做任何事。 1317 | 1318 | `for`循环中,`Range`表达式在循环开始之前完成全部的求值,所以在循环过程中,`Range`表达式所代表的范围是不会产生变化的。 1319 | 1320 | 下面展示了一个`for`循环代码块: 1321 | 1322 | ``` 1323 | for (index in 0 .. n-2) { 1324 | set results[index] = Measure([PauliX], [qubits[index]]); 1325 | } 1326 | ``` 1327 | 1328 | #### 5.4.2 Repeat-Until-Success循环 1329 | 1330 | `repeat`语句支持量子计算中的`“repeat until success”`模型,它包括`repeat`关键字,后跟一个代码块,该代码块就是要执行的循环体,在循环体后是`until`关键字和一个条件表达式,最后是`fixup`关键字和另一个代码块。`fixup`代码块是必须要有的,如果`fixup`代码块不做任何工作,那么只需在其中写一个空的表达式`()`即可。 1331 | 1332 | `repeat`语句的执行过程为:首先执行循环体,然后测试条件表达式是否为`true`,如果为`true`,那么这个`repeat`语句就执行完毕了,如果为`false`,则执行`fixup`语句中的内容,然后重新开始`repeat`语句的执行,如此循环直至结束。 1333 | 1334 | 下面的代码展示了`repeat`语句的使用,该代码定义了量子门$V3=(I+2iZ)/√5$ 。 1335 | 1336 | ```C# 1337 | using ancilla = Qubit[1] { 1338 | repeat { 1339 | let anc = ancilla[0]; 1340 | H(anc); 1341 | T(anc); 1342 | CNOT(target,anc); 1343 | H(anc); 1344 | (Adjoint T)(anc); 1345 | H(anc); 1346 | T(anc); 1347 | H(anc); 1348 | CNOT(target,anc); 1349 | T(anc); 1350 | Z(target); 1351 | H(anc); 1352 | let result = M([anc],[PauliZ]); 1353 | } until result == Zero 1354 | fixup { 1355 | ();//空语句 1356 | } 1357 | } 1358 | ``` 1359 | 1360 | #### 5.4.3 if语句 1361 | 1362 | `if`语句完成按一定条件选择性执行的功能,后跟一个布尔表达式和相应的代码块,在if语句块之后可以继续添加其他条件表达式,使用`elif`关键字引出,然后是相应的布尔表达式和语句块,在最后,可以使用`else`引出不满足上面所有条件时要执行的操作。 1363 | 1364 | 条件表达式的使用请看下面的代码: 1365 | 1366 | ```C# 1367 | if (result == One) { 1368 | X(target); 1369 | } else { 1370 | Z(target); 1371 | } 1372 | ``` 1373 | 1374 | ```C# 1375 | if (i == 1) { 1376 | X(target); 1377 | } elif (i == 2) { 1378 | Y(target); 1379 | } else { 1380 | Z(target); 1381 | } 1382 | ``` 1383 | 1384 | ## 第六章 operation 和 function 1385 | 1386 | Q#中,`operation`是量子计算子程序,也就是说它是一个可调用的包含量子操作的例程。 1387 | 1388 | 而`function`则是经典计算子程序,它完成经典计算任务。 1389 | 1390 | `operation`和`function`在Q#中都是 **可调用类型** ,他们统称为`callable`。它们两者之间的区别在于,只有`operation`可以操作qubit,并且`operation`中可以调用`function`,但`function`中不能对`operation`进行调用也不能对qubit进行操作。 1391 | 1392 | Q#中所有的可调用对象都可以看作是接收一个输入然后产生一个输出,这里的输入输出都由元组进行表示,当然也可以认为,没有输入参数的可调用对象以空元组为输入,不产生结果的可调用对象返回空元组。 1393 | 1394 | ### 6.1 定义operation 1395 | 1396 | operation是Q#中完成量子计算的子程序,一个operation可以用于其他operation中, 也可以被C#量子驱动程序直接调用并获得结果。定义一个operation的形式如下: 1397 | 1398 | ```C# 1399 | operation opName ([args]) : ([return-values]) 1400 | { 1401 | body 1402 | { 1403 | ... 1404 | } 1405 | } 1406 | ``` 1407 | 1408 | 关键字`operation`引出表示要定义一个`operation`,后跟`operation`的名称。`args`表示`operation`的参数列表,参数声明的形式为:`arg-name : arg-type`,即首先指出参数名,然后指出参数类型,中间用冒号分隔,后面括号中的`return-values`表示返回值,它的声明形式与参数列表相同,参数列表和返回值之间同样以冒号分隔,如果一个`operation`不需要输入参数或没有输出结果,那么只需将参数列表和返回值留空仅使用空的括号即可。 1409 | 1410 | 每一个`operation`中都有一个`body`代码段,其中是真正要进行的量子操作,包括量子门的应用、qubit的管理和测量等。但这并不是说所有的代码必须写在`body`体中,在`body`体之外的地方仍然可以进行变量定义等操作。 1411 | 1412 | 下面的代码定义了一个名为`BitFlip`的`operation`,从名字就可以看出这个`operation`的作用是对量子态进行翻转。它的参数列表中只有一个名为`target`类型为`Qubit`变量,并且没有返回值,在`body`体中,只有一个应用$X$门的操作,完成量子态的翻转。 1413 | 1414 | ```C# 1415 | operation BitFlip(target : Qubit) : () { 1416 | body { 1417 | X(target); 1418 | } 1419 | } 1420 | ``` 1421 | 1422 | 我们再来定义一个更复杂的`operation`,下面是一个名为`Superdense`的`operation`,它的参数列表中有两个参数,并且它的返回结果也包括两个值,在它的`body`体中,应用了`H`和`CNOT`两个量子门。在这里我们需要说明一点,虽然参数列表和返回结果中都有两个值,但在Q#中将它们统统视为一个元组,不管有多少个参数和结果,莫不如此,因此,我们有时候也说`operation`就是接受一个输入产生一个输出的操作。 1423 | 1424 | ```C# 1425 | operation Superdense(here : Qubit, there : Qubit) : (Result, Result) { 1426 | body { 1427 | CNOT(there, here); 1428 | H(there); 1429 | 1430 | let firstBit = M(there); 1431 | let secondBit = M(here); 1432 | 1433 | return (firstBit, secondBit); 1434 | } 1435 | } 1436 | ``` 1437 | 1438 | ### 6.2 operation的变体:Adjoint和Controlled 1439 | 1440 | 在量子计算基础部分中,我们说过每一个量子门实际上都是一个幺正矩阵,在Q#语言中,我们可以用`operation`来定义新的量子门操作,定义的量子门除了在逻辑上要满足幺正矩阵的特征外,同时要满足Q#语言的对其施加的约束:**不能有返回值**。下面的代码就通过组合`H`门和`CNOT`门定义了一个名为`PrepareEntangledPair`的新量子门。 1441 | 1442 | ```C# 1443 | operation PrepareEntangledPair(here : Qubit, there : Qubit) : () { 1444 | body { 1445 | H(here); 1446 | CNOT(here, there); 1447 | } 1448 | } 1449 | ``` 1450 | 1451 | 因为每一个量子门都代表着一个幺正矩阵,所以它的逆矩阵就是它自身的复数共轭。在Q#中,我们使用`(Adjoint U)`表示一个量子门`U`的复数共轭。 1452 | 1453 | 我们也讲过受控门的概念,在多量子系统中,一部分量子作为控制量子,另一部分量子作为目标量子,目标量子状态的变化有控制量子决定。在Q#中,我们使用`(Controlled U)`表示一个量子门的受控形式。 1454 | 1455 | 但是最妙的是,在Q#中,我们不需要重新定义一个量子门的复数共轭形式和受控形式,相反,我们只需要在定义新的量子门操作时进行相应的声明即可。比如上面的`PrepareEntangledPair`,我们可以像下面这样改写,以提供它复数共轭和受控形式。 1456 | 1457 | ```C# 1458 | operation PrepareEntangledPair(here : Qubit, there : Qubit) : () { 1459 | body { 1460 | H(here); 1461 | CNOT(here, there); 1462 | } 1463 | adjoint auto 1464 | controlled auto 1465 | controlled adjoint auto 1466 | } 1467 | ``` 1468 | 1469 | 在上面代码中,我们使用关键字`auto`指示由编译器自动为我们生成量子门操作的复数共轭和受控形式,以及其复数共轭的受控形式。 1470 | 1471 | 复数共轭和受控形式的量子门使用时很简单,使用关键在`Adjoint`和`Controlled`指定即可。如下所示为我们使用`PrepareEntangledPair`的`Adjoint`形式重新定义`Superdense`。 1472 | 1473 | ```C# 1474 | operation Superdense(here : Qubit, there : Qubit) : (Result, Result) { 1475 | body { 1476 | (Adjoint PrepareEntangledPair)(there, here); 1477 | 1478 | let firstBit = M(there); 1479 | let secondBit = M(here); 1480 | 1481 | return (firstBit, secondBit); 1482 | } 1483 | } 1484 | ``` 1485 | 1486 | ### 6.3 定义function 1487 | 1488 | 从定义形式上而言,`function`与`operation`之间的差别很小,只不过在`function`中没有`body`体,直接写代码就可以,从这个角度来看,`function`更接近经典计算机编程语言中的函数或方法。下面的代码我们定义了一个名为`Square`的`function`,它的功能是计算一个数的平方。 1489 | 1490 | ```C# 1491 | function Square(x : Double) : (Double) { 1492 | return x * x; 1493 | } 1494 | ``` 1495 | 1496 | `function`的定义由关键字`function`引出,后跟名称、参数列表和返回值列表等。 1497 | 1498 | 虽然从功能上来说,`function`能完成的操作,`operation`同样能够完成,但Q#还是提供了这两个不同的对象,原因就在于将量子计算和经典计算分离,在`function`中我们定义经典计算中的操作,而在`operation`中我们专注于量子计算相关的功能逻辑。 1499 | 1500 | ### 6.4 return语句 1501 | 1502 | `return`语句结束`operation`和`function`的执行,并将结果返回给调用者。``return``语句以`return`关键字开始,后跟一个表达式。一个返回值为空`()`的`operation`或`function`不需要`return`语句,如果需要在一定条件下提前结束执行,可以使用`return ();`来返回一个空值。`return`语句的使用如下面代码所示: 1503 | 1504 | ```c# 1505 | return 1; 1506 | ``` 1507 | 1508 | ```c# 1509 | return ();//返回空值 1510 | ``` 1511 | 1512 | ```c# 1513 | return (results, qubits); 1514 | ``` 1515 | 1516 | ### 6.5 fail语句 1517 | 1518 | `fail`语句结束一个`operation`的执行,并向调用者返回一个出错信息。`fail`语句由`fail`关键字和一个代表错误信息的字符串组成。下面的代码展示了`fail`语句的用法: 1519 | 1520 | ```c# 1521 | fail $"Impossible state reached"; 1522 | ``` 1523 | 1524 | ```C# 1525 | fail $"Syndrome {syn} is incorrect"; 1526 | ``` 1527 | 1528 | ### 6.6 operation和function的类型 1529 | 1530 | 在Q#中,`operation`和`function`除了具有可调用的特性外,它们实际上也是一种数据类型,甚至可以将它们当成普通的数据类型那样进行赋值等操作,比如下面这个例子: 1531 | 1532 | ```C# 1533 | operation FirstClassExample(target : Qubit) : () { 1534 | body { 1535 | let ourH = H; 1536 | ourH(target); 1537 | } 1538 | } 1539 | ``` 1540 | 1541 | 上面的代码中,我们将`H`赋值给了变量`ourH`,变量`ourH`仍然可以像正常的`operation`那样进行调用。 1542 | 1543 | 我们在来看一个通过参数传递`operation`类型对象的例子: 1544 | 1545 | 既然`operation`和`function`是一级对象,那么它们的类型又是什么呢? 1546 | 1547 | `operation`和`function`的类型又它们的**签名式**决定。一个`function`的签名式形式为:`(([args-type]) -> ([return-type]))`,即将参数列表和返回类型放在一起,中间用" `->` "连接,如果参数列表或返回结果为空,那么只需用`()`表示即可。相应的,一个`operation`的签名式为:`(([args-type]) => ([return-type]))`,即`operation`的签名式相比`function`的区别就是将`->`换成了`=>`。 1548 | 1549 | 所以我们上面定义的一些`operation`和`function`的签名式分别为: 1550 | 1551 | ```C# 1552 | ((Qubit) => ()); //FirstClassExample的签名式 1553 | ((Double) -> (Double)); //Square的签名式 1554 | ((Qubit,Qubit) => (Result, Result)); //Superdense的签名式 1555 | ``` 1556 | 1557 | 现在我们再来看一个通过参数传递`operation`类型对象的例子: 1558 | 1559 | ```C# 1560 | operation ApplyTwice(op : ((Qubit) => ()), target : Qubit) : () { 1561 | body { 1562 | op(target); 1563 | op(target); 1564 | } 1565 | } 1566 | ``` 1567 | 1568 | 在上面的例子中,我们定义了一个名为`ApplyTwice`新的`operation`,它有两个参数,一个是需要操作的目标qubit,另一个则是签名式为`((Qubit) => ())`的`operation`,`ApplyTwice`的作用就是对一个qubit应用两次`operation`,而该`operation`是通过开发者指定的,任意一个类型为`((Qubit) => ())`的`operation`均可以被传入参数中。 1569 | 1570 | 上面这些都是签名式的一般写法,因为Q#中`operation`有`Adjoint`、`Controlled`等变体,所以对于`operation`来说,其完整的签名式还需要指明其变体是否存在,如果存在是哪种变体。方法很简单,就是在普通的签名式之后使用一个冒号指明变体类型即可,如果其两种变体都存在,则在两个变体中间加上逗号。如下就是一些`operation`带有变体的签名式示例: 1571 | 1572 | ```c# 1573 | ((here : Qubit, there : Qubit) => (Result, Result)):Adjoint, Controlled; //operation:PrepareEntangledPair的完整签名式 1574 | ((Qubit) => ()) : Adjoint; //拥有Adjoint变体的operation签名式 1575 | ((Qubit) => ()) : Controlled; //拥有Controlled变体的签名式 1576 | ``` 1577 | 1578 | 1579 | 1580 | ### 6.7 递归 1581 | 1582 | Q#中的可调用对象支持递归,这也就是说,一个`operation`或`function`可以直接调用其自身。使用递归时,有两个注意事项: 1583 | 1584 | 1. 递归的使用有可能妨碍对程序进行优化,这个对算法的运行时间会造成实质性的影响,此时编译器应该向用户发出警告。 1585 | 2. 当在一个特定的量子计算设备上使用递归程序时,如果递归深度较大亏导致运行时错误,特别的是,在Q#中,编译和运行时尾递归都不能被识别出来也不会对其进行优化。 1586 | 1587 | ### 6.8 部分调用 1588 | 1589 | 给定一个`operation`或`function`,可以传递给它一部分参数从而构造一个新的可调用对象,这就叫做**部分调用** 。部分调用类似于C++中的默认参数。 1590 | 1591 | 在Q#中通过使用下划线:_作为一个占位符代替一个可调用对象的参数,其它部分保持不变来表示一个部分调用的`operation`或`function`,这个新的表达式拥有和原表达式相同的返回类型。 1592 | 1593 | 我们看一个部分调用的例子,还使将上面定义的`ApplyTwice`改写为下面这样: 1594 | 1595 | ```C# 1596 | operation PartialApplicationExample(op : ((Qubit) => ()), target : Qubit) : () { 1597 | body { 1598 | let twiceOp = ApplyTwice(op, _); 1599 | twiceOp(target); 1600 | } 1601 | } 1602 | ``` 1603 | 1604 | 在代码中,我们第一步定义了一个新的部分调用对象`twiceOp`,它通过占位符来代替第二个参数,第二部调用`twiceOp`,此时,我们只需要指定第二个qubit参数即可,因为在第一步中,`twiceOp`的第一个参数已经确定了。 1605 | 1606 | ## 第七章 与qubit共舞 1607 | 1608 | 量子计算的核心是对qubit的操作,作为一门量子计算编程语言,对qubit的管理、操作和控制是Q#区别于其他编程语言的最显著特征。 1609 | 1610 | ### 7.1 分配qubit 1611 | 1612 | `using`语句用于在代码块中为用户分配所需要的qubit。`using`语句包括以下几个部分:首先由关键字`using`引出,其后跟随一对括号,括号里是一个赋值语句,指明需要分配的qubit的数量及其绑定到的变量符号。 1613 | 1614 | ```c# 1615 | using (register = Qubit[5]) { 1616 | // Do stuff... 1617 | } 1618 | ``` 1619 | 1620 | 使用`using`分配得到的每一个qubit,其初始状态均为`Zero`,上面的代码中,我们使用`using`像系统申请了5个qubit,它们组成了一个$5-qubit$的量子系统,且该系统的初始状态为:$|0 \rangle \otimes | 0 \rangle \otimes | 0 \rangle \otimes | 0 \rangle \otimes | 0 \rangle$ 。需要注意的是,在当前代码段结束时,这些分配的qubit会立即被释放,并且不能再在程序的其它地方使用。 1621 | 1622 | 在这里需要强调一下qubit的管理方式,在系统中,所有的qubit被统一管理,我们使用`using`语句分配得到的qubit实际上是系统中真正的qubit的引用,我们对其施加的每一次操作,都直接改变着系统底层这些qubit的状态。通常而言,我们希望每一次分配得到的qubit均处于确定的$|0 \rangle$中,因此,为了保证这一点,在每次使用完qubit后,我们都应该将其重新恢复到$|0 \rangle$,以保证下次申请时,qubit仍处于确定的状态$| 0 \rangle$ 。在Q#中,可以使用`Reset`将qubit的状态恢复为$| 0 \rangle$ 。 1623 | 1624 | ### 7.2 基本量子门 1625 | 1626 | 我们已经学习过量子计算中常见的量子门,包括$PauliX、PauliY、PauliZ、H、S、T、CNOT、SWAT$等,Q#为我们直接提供了这些门,它们都是`operation`类型且均为Q#标准库的一部分。我们将这些基本的量子门以及它们的签名式列出如下: 1627 | 1628 | | 名称 | 签名式 | 量子门矩阵 | 1629 | | :----: | :--------------------------------------------: | :----------------------------------------------------------: | 1630 | | $X$ | `(Qubit => () : Adjoint, Controlled)` | $\begin{bmatrix} 0 \quad 1 \\ 1 \quad 0 \end{bmatrix}$ | 1631 | | $Y$ | `(Qubit => () : Adjoint, Controlled)` | $\begin{bmatrix} 0 \quad -i \\ i \qquad 0 \end{bmatrix}$ | 1632 | | $Z$ | `(Qubit => () : Adjoint, Controlled)` | $\begin{bmatrix} 1 \qquad 0 \\ 0 \quad -1 \end{bmatrix}$ | 1633 | | $H$ | `(Qubit => () : Adjoint, Controlled)` | $\frac {1} {\sqrt{2}} \begin{bmatrix} 1 \qquad 1 \\ 1 \quad -1 \end{bmatrix}$ | 1634 | | $S$ | `(Qubit => () : Adjoint, Controlled)` | $\begin{bmatrix} 1 \quad 0 \\ 0 \quad i \end{bmatrix}$ | 1635 | | $T$ | `(Qubit => () : Adjoint, Controlled)` | $\begin{bmatrix} 1 \qquad 0 \\ 0 \quad e^{i \pi / 4} \end{bmatrix}$ | 1636 | | $CNOT$ | `((Qubit, Qubit) => () : Adjoint, Controlled)` | $\begin{bmatrix} 1 \quad 0 \quad 0 \quad 0 \\ 0 \quad 1 \quad 0 \quad 0 \\ 0 \quad 0 \quad 0 \quad 1 \\ 1 \quad 0 \quad 1 \quad 0 \end{bmatrix}$ | 1637 | | $SWAP$ | `((Qubit, Qubit) => () : Adjoint, Controlled)` | $\begin{bmatrix} 1 & 0 & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix}$ | 1638 | 1639 | 我们曾经说个,任意一个单量子门,都能由其它量子门通过一定的变换表示出来,其中的一种变换为: 1640 | 1641 | ​ $U = e^{i\alpha}R_z(\beta)R_y(\gamma )R_x(\delta )$ 1642 | 1643 | 其中,$U$为任意的单量子门, 1644 | 1645 | ​ $R_z(\beta) = e^{-i\beta Z/2} = cos \frac{\beta}{2}I - isin{\frac {\beta}{2}}Z = \begin{vmatrix}e^{-i\beta/2} & 0 \\ 0 & e^{i\beta/2} {2}\end{vmatrix}$ 1646 | 1647 | ​ $R_y(\gamma) = e^{-i\gamma Y/2} = cos \frac{\gamma}{2}I - isin{\frac {\gamma}{2}}Y = \begin{vmatrix}cos \frac{\gamma}{2} & -sin \frac{\gamma}{2}\\ sin \frac{\gamma}{2} & cos \frac{\gamma}{2}\end{vmatrix}$ 1648 | 1649 | ​ $R_x(\delta) = e^{-i\delta X/2} = cos \frac{\delta}{2}I - isin{\frac {\delta}{2}}X = \begin{vmatrix}cos \frac{\delta}{2} & -i sin \frac{\delta}{2} \\ -isin \frac{\delta}{2} & cos \frac{\delta}{2}\end{vmatrix}$ 1650 | 1651 | 上面的$R_{z}(\beta)、R_{y}(\gamma)、R_{x}(\delta)$ 均是将量子门绕不同的坐标轴旋转一定的角度实现的。Q#中同样提供了这几个旋转操作对应的`operation`。 1652 | 1653 | $Rx$:`((Double, Qubit) => () : Adjoint, Controlled)` 1654 | 1655 | $Ry$:`((Double, Qubit) => () : Adjoint, Controlled)` 1656 | 1657 | $Rz$:`((Double, Qubit) => () : Adjoint, Controlled)` 1658 | 1659 | 上面三个`operation`中,第一个参数为`Double`类型,指出旋转的角度。除了这三个用于旋转的`operation`,Q#还提供另外几种旋转操作: 1660 | 1661 | $R$:`((Pauli, Double, Qubit) => () : Adjoint, Controlled)` 1662 | 1663 | $R$是Q#中最基本的旋转操作,其他所有的旋转都能够由它实现。它的实际作用可以表示为: 1664 | 1665 | $\qquad \qquad \qquad\begin{equation} R(\sigma, \phi) \mathrel{=} e^{-i \phi \sigma / 2},\end{equation}=cos \frac{\sigma}{2}I - isin \frac{\sigma}{2}\phi$ 1666 | 1667 | 其中,$\sigma$是将要旋转的角度,$\phi$则表示需要应用的量子门。 1668 | 1669 | $R1$:`((Double, Qubit) => () : Adjoint, Controlled)` 1670 | 1671 | $R1$ 表示将qubit绕$| 1 \rangle$旋转一定的角度。 1672 | 1673 | ### 7.3 测量 1674 | 1675 | Q#中提供了两个用来进行测量的`operation`,分别是`M`和`MultiM`。`M`用于测量单一qubit的状态,它的签名式为:`(Qubit => Result)`。`MultiM`则用来测量多个qubit组成的量子系统的状态,它的返回结果是每一个qubit测量结果组成的数组,它的签名式为:`Qubit[] => Result[])`。从逻辑上来说,`MultiM`相当于对多个qubit分别测量得到的结果集合,用代码表示如下: 1676 | 1677 | ```C# 1678 | mutable rs = new Result[Length(qs)]; 1679 | for (index in 0..Length(qs)-1) 1680 | { 1681 | set rs[index] = M(qs[index]); 1682 | } 1683 | return rs; 1684 | ``` 1685 | 1686 | ### 7.4 扩展操作 1687 | 1688 | 除了上面提到的这些基本的量子操作,Q#还提供了许多其他有用的`operation`和`function`,比如定义了各种数学运算的库: [Microsoft.Quantum.Extensions.Math](https://docs.microsoft.com/en-us/qsharp/api/prelude/microsoft.quantum.extensions.math) 以及用于日志记录的`Log`函数等。Q#的`Math`库中主要定义的数学运算有: 1689 | 1690 | | 名称 | 作用 | 1691 | | :----------------------------------------------------------: | :----------------------------------------------------------- | 1692 | | [AbsD](https://docs.microsoft.com/en-us/qsharp/api/prelude/microsoft.quantum.extensions.math.absd?view=qsharp-preview) | Returns the absolute value of a double-precision floating-point number. | 1693 | | [AbsI](https://docs.microsoft.com/en-us/qsharp/api/prelude/microsoft.quantum.extensions.math.absi?view=qsharp-preview) | Returns the absolute value of an integer. | 1694 | | [ArcCos](https://docs.microsoft.com/en-us/qsharp/api/prelude/microsoft.quantum.extensions.math.arccos?view=qsharp-preview) | Returns the angle whose cosine is the specified number. | 1695 | | [ArcSin](https://docs.microsoft.com/en-us/qsharp/api/prelude/microsoft.quantum.extensions.math.arcsin?view=qsharp-preview) | Returns the angle whose sine is the specified number. | 1696 | | [ArcTan](https://docs.microsoft.com/en-us/qsharp/api/prelude/microsoft.quantum.extensions.math.arctan?view=qsharp-preview) | Returns the angle whose tangent is the specified number. | 1697 | | [ArcTan2](https://docs.microsoft.com/en-us/qsharp/api/prelude/microsoft.quantum.extensions.math.arctan2?view=qsharp-preview) | Returns the angle whose tangent is the quotient of two specified numbers. | 1698 | | [Ceiling](https://docs.microsoft.com/en-us/qsharp/api/prelude/microsoft.quantum.extensions.math.ceiling?view=qsharp-preview) | Returns the smallest integer greater than or equal to the specified number. | 1699 | | [Cos](https://docs.microsoft.com/en-us/qsharp/api/prelude/microsoft.quantum.extensions.math.cos?view=qsharp-preview) | Returns the cosine of the specified angle. | 1700 | | [Cosh](https://docs.microsoft.com/en-us/qsharp/api/prelude/microsoft.quantum.extensions.math.cosh?view=qsharp-preview) | Returns the cosine of the specified angle. | 1701 | | [E](https://docs.microsoft.com/en-us/qsharp/api/prelude/microsoft.quantum.extensions.math.e?view=qsharp-preview) | Returns the natural logarithmic base e | 1702 | | [ExpD](https://docs.microsoft.com/en-us/qsharp/api/prelude/microsoft.quantum.extensions.math.expd?view=qsharp-preview) | Returns e raised to the specified power. | 1703 | | [Floor](https://docs.microsoft.com/en-us/qsharp/api/prelude/microsoft.quantum.extensions.math.floor?view=qsharp-preview) | Returns the largest integer less than or equal to the specified number. | 1704 | | [IEEERemainder](https://docs.microsoft.com/en-us/qsharp/api/prelude/microsoft.quantum.extensions.math.ieeeremainder?view=qsharp-preview) | Returns the remainder resulting from the division of a specified number by another specified number. | 1705 | | [Log](https://docs.microsoft.com/en-us/qsharp/api/prelude/microsoft.quantum.extensions.math.log?view=qsharp-preview) | Returns the natural (base e ) logarithm of a specified number. | 1706 | | [Log10](https://docs.microsoft.com/en-us/qsharp/api/prelude/microsoft.quantum.extensions.math.log10?view=qsharp-preview) | Returns the base 10 logarithm of a specified number. | 1707 | | [MaxD](https://docs.microsoft.com/en-us/qsharp/api/prelude/microsoft.quantum.extensions.math.maxd?view=qsharp-preview) | Returns the larger of two specified numbers. | 1708 | | [MaxI](https://docs.microsoft.com/en-us/qsharp/api/prelude/microsoft.quantum.extensions.math.maxi?view=qsharp-preview) | Returns the larger of two specified numbers. | 1709 | | [MinD](https://docs.microsoft.com/en-us/qsharp/api/prelude/microsoft.quantum.extensions.math.mind?view=qsharp-preview) | Returns the smaller of two specified numbers. | 1710 | | [MinI](https://docs.microsoft.com/en-us/qsharp/api/prelude/microsoft.quantum.extensions.math.mini?view=qsharp-preview) | Returns the smaller of two specified numbers. | 1711 | | [PI](https://docs.microsoft.com/en-us/qsharp/api/prelude/microsoft.quantum.extensions.math.pi?view=qsharp-preview) | Represents the ratio of the circumference of a circle to its diameter, specified by the constant, π. | 1712 | | [PowD](https://docs.microsoft.com/en-us/qsharp/api/prelude/microsoft.quantum.extensions.math.powd?view=qsharp-preview) | Returns the number x raised to the power y. | 1713 | | [Round](https://docs.microsoft.com/en-us/qsharp/api/prelude/microsoft.quantum.extensions.math.round?view=qsharp-preview) | Rounds a value to the nearest integer or to the specified number of fractional digits. | 1714 | | [SignD](https://docs.microsoft.com/en-us/qsharp/api/prelude/microsoft.quantum.extensions.math.signd?view=qsharp-preview) | Returns an integer that indicates the sign of a number. | 1715 | | [SignI](https://docs.microsoft.com/en-us/qsharp/api/prelude/microsoft.quantum.extensions.math.signi?view=qsharp-preview) | Returns an integer that indicates the sign of a number. | 1716 | | [Sin](https://docs.microsoft.com/en-us/qsharp/api/prelude/microsoft.quantum.extensions.math.sin?view=qsharp-preview) | Returns the sine of the specified angle. | 1717 | | [Sinh](https://docs.microsoft.com/en-us/qsharp/api/prelude/microsoft.quantum.extensions.math.sinh?view=qsharp-preview) | Returns the hyperbolic sine of the specified angle. | 1718 | | [Sqrt](https://docs.microsoft.com/en-us/qsharp/api/prelude/microsoft.quantum.extensions.math.sqrt?view=qsharp-preview) | Returns the square root of a specified number. | 1719 | | [Tan](https://docs.microsoft.com/en-us/qsharp/api/prelude/microsoft.quantum.extensions.math.tan?view=qsharp-preview) | Returns the tangent of the specified angle. | 1720 | | [Tanh](https://docs.microsoft.com/en-us/qsharp/api/prelude/microsoft.quantum.extensions.math.tanh?view=qsharp-preview) | Returns the hyperbolic tangent of the specified angle. | 1721 | | [Truncate](https://docs.microsoft.com/en-us/qsharp/api/prelude/microsoft.quantum.extensions.math.truncate?view=qsharp-preview) | Calculates the integral part of a number. | 1722 | 1723 | ### 7.5 borrowing 1724 | 1725 | `borrowing`语句用于在代码块中分配临时使用的量子位,用户应保证这些借来的量子位与借来时的状态相同。关于附属量子位的用法可以参看这个示例:[*Factoring using 2n+2 qubits with Toffoli based modular multiplication*](https://arxiv.org/pdf/1611.07995v1.pdf) 。 1726 | 1727 | `borrowing`语句的使用方法如下所示: 1728 | 1729 | ``` 1730 | borrowing qubits = Qubit[bits * 2 + 3] { 1731 | ... 1732 | } 1733 | ``` 1734 | 1735 | 当借用量子位时,系统会首先使用那些正在使用中但在当前`borrowing`语句所在语句块中不可访问的量子位来填充请求,如果这些量子位不足,系统会重新分配量子位以满足需求。 1736 | 1737 | ## 第八章 Q#量子计算模拟器 1738 | 1739 | 量子程序的运行需要借助量子计算机,Q#中提供了虚拟的量子计算模拟器来完成量子程序的执行。在Q#中,有两种不同的模拟器,一个是`QuantumSimulator`,另一个是`QCTraceSimulator`,前者主要用来执行量子程序,后者则主要完成计算资源的估算、程序漏洞检测等功能。 1740 | 1741 | ### 8.1 QuantumSimulator 1742 | 1743 | 一个量子计算程序包含两个部分:由C#编写的驱动不封和由Q#编写的量子计算部分。在前面我们编写的程序中,量子程序最终都需要交给一个`QuantumSimulator`类去具体执行,在执行一个量子计算的`operation`时,必须向其传递一个`QuantumSimulator`的实例,如下程序所示: 1744 | 1745 | ```c# 1746 | using (var sim = new QuantumSimulator()) 1747 | { 1748 | var res = myOperation.Run(sim).Result; 1749 | ///... 1750 | } 1751 | ``` 1752 | 1753 | 每一个Q#的`operation`都会被编译成一个提供了静态`Run`方法的C#,调用这个`Run`方法时,处理该`operation`本身所需要的参数,必须传递给其一个`QuantumSimulator`实例,且该实例是`Run`方法的第一个参数。 1754 | 1755 | 同时将参数传递给Run方法时有一些细节需要注意: 1756 | 1757 | - 数组必须包装在`Microsoft.Quantum.Simulation.Core.QArray`类型中。`QArray`类型有一个能够接受任意排序集合对象`(IEnumerable)`的构造函数 1758 | - 空元祖`()`在C#中使用`QVoid.Instance`来表示 1759 | - 非空元祖使用.NET的`ValueType`实例来表示 1760 | - Q#的用户自定义类型作为它们的基类来传递 1761 | - 为了将`operation`和`function`传入`Run`方法中,必须创建一个该`operation`或`function`的实例 1762 | 1763 | #### 8.1.1 处理结果 1764 | 1765 | 量子算法的结果由`Run`方法返回,由于`Run`方法是异步执行的,它返回一个`Task`实例,有几种方法来获取真正的执行结果,最简单的方法是使用`Task`类型的`Result`属性: 1766 | 1767 | ```c# 1768 | var res = BellTest.Run(sim, 1000, initial).Result; 1769 | ``` 1770 | 1771 | 但其他的方法,如使用[`Wait`](#)方法或C#中的[`await`](#)方法也是可行的。 1772 | 1773 | 许多的量子算法需要大量的后期处理来提取有用的结果,例如Shor算法中的量子部分仅仅是因数分解算法的开始。在大部分情况下,将这些后期处理任务放在经典驱动中是最简单方便的。 1774 | 1775 | #### 8.1.2 fail语句 1776 | 1777 | 当在Q#`operation`中遇到`fail`语句时一个`ExecutionFailException`会被抛出。 1778 | 1779 | 因为在`Run`方法中使用了`System.Task`,`fail`语句抛出的异常会被包装成为一个`System.AggregateException`,要找出产生异常的真实原因,需要在`AggregateExecption`中迭代`InnerException`,例如以下代码: 1780 | 1781 | ```c# 1782 | try 1783 | { 1784 | using(var sim = new QuantumSimulator()) 1785 | { 1786 | /// call your operations here... 1787 | } 1788 | } 1789 | catch (AggregateException e) 1790 | { 1791 | // Unwrap AggregateException to get the message from Q# fail statement. 1792 | // Go through all inner exceptions. 1793 | foreach (Exception inner in e.InnerExceptions) 1794 | { 1795 | // If the exception of type ExecutionFailException 1796 | if (inner is ExecutionFailException failException) 1797 | { 1798 | // Print the message it contains 1799 | Console.WriteLine($" {failException.Message}"); 1800 | } 1801 | } 1802 | } 1803 | ``` 1804 | 1805 | #### 8.1.3 IDisposable 1806 | 1807 | 类`QuantumSimulator`实现了`IDissposable`接口,因此一个`QuantumSimulator`的实例不再被使用时,应调用方法`Dispose`。使用`QuantumSimulator`最好的方法是使用`using`语句,就像上面的例子中那样,这样可以不用显式调用`Dispose`方法,减少出错的概率。 1808 | 1809 | #### 8.1.4 Seed 1810 | 1811 | 类`QuantumSimulator`使用了一个随机数生成器来模拟量子力学中的随机性。为了方便调试,有时候需要得到一个确定的结果,此时可以通过向`QuantumSimulator`的构造函数中传入一个seed来实现,这个seed传给了参数`randomNumberGeneratorSeed`: 1812 | 1813 | ```c# 1814 | using (var sim = new QuantumSimulator(randomNumberGeneratorSeed: 42)) 1815 | { 1816 | var res = myOperationTest.Run(sim).Result; 1817 | ///... 1818 | } 1819 | ``` 1820 | 1821 | #### 8.1.5 Thread 1822 | 1823 | 类`QuantumSimulator`使用[OpenMP](#)实现所需要的线性代数的并行计算。默认情况下,OpenMP使用所有能用的硬件线程来进行计算,这也意味着有着较少数量量子比特的程序运行起来会比较慢,因为所需要的线程之间的同步、通信等工作相比真正的计算工作占比较大。这个问题可以通过将环境变量`OPM_NUM_THREADS`设置为一个较小的数字来解决。一个常用的策略是,1个线程对应至多4个量子比特是较好的,然后每增加一个量子比特,就增加一个线程,当然了,你也可以根据自己的算法进行更好地调整。 1824 | 1825 | ### 8.2 QCTraceSimulator 1826 | 1827 | `QCTraceSimulator`并不是通过模拟量子计算机的行为来运行一个量子程序,并且`QCTraceSimulator`的主要作用也不是执行量子程序。 1828 | 1829 | 得益于`QCTraceSimulator`不会模拟量子计算机这一特性,它可以高效地运行一个使用了成百上千个qubit的量子程序,也因此它的主要作用在于: 1830 | 1831 | 1. 方便调试量子程序中的经典计算代码 1832 | 2. 估算在量子计算机上运行一个量子程序所需要的资源 1833 | 1834 | #### 8.2.1 提供测量输出结果的概率 1835 | 1836 | 有时候对量子系统的测量,用户只关心测量输出不同结果的概率,此时可以使用`Microsoft.Quantum.Primitive`命名空间中的`AssertProb`来表达这一信息,如下所示: 1837 | 1838 | ```c# 1839 | operation Teleportation (source : Qubit, target : Qubit) : () { 1840 | body { 1841 | using (ancillaRegister = Qubit[1]) { 1842 | let ancilla = ancillaRegister[0]; 1843 | H(ancilla); 1844 | CNOT(ancilla, target); 1845 | CNOT(source, ancilla); 1846 | H(source); 1847 | AssertProb([PauliZ], [source], Zero, 0.5, "Outcomes must be equally likely", 1e-5); 1848 | AssertProb([PauliZ], [ancilla], Zero, 0.5, "Outcomes must be equally likely", 1e-5); 1849 | if (M(source) == One) { Z(target); X(source); } 1850 | if (M(ancilla) == One) { X(target); X(ancilla); } 1851 | } 1852 | } 1853 | } 1854 | ``` 1855 | 1856 | 当`QCTraceSimulator`运行`AssertProb`时,它将`source`和`ancilla`上测量得到`Zero`的概率标记为0.5,在这之后,当模拟器运行`M`时,它就会查询所标记的输出概率,并且`M`返回`Zero`和`One`的概率为0.5。 1857 | 1858 | #### 8.2.2 使用`QCTraceSimulator`运行程序 1859 | 1860 | `QCTraceSimulator`也可以被视为另一种量子计算机的抽象,在这上面所使用的C#驱动程序与其他模拟器类似,如下所示: 1861 | 1862 | ```c# 1863 | using Microsoft.Quantum.Simulation.Core; 1864 | using Microsoft.Quantum.Simulation.Simulators; 1865 | using Microsoft.Quantum.Simulation.Simulators.QCTraceSimulators; 1866 | 1867 | 1868 | namespace Quantum.MyProgram 1869 | { 1870 | class Driver 1871 | { 1872 | static void Main(string[] args) 1873 | { 1874 | QCTraceSimulator sim = new QCTraceSimulator(); 1875 | var res = MyQuantumProgram.Run().Result; 1876 | System.Console.WriteLine("Press any key to continue..."); 1877 | System.Console.ReadKey(); 1878 | } 1879 | } 1880 | } 1881 | ``` 1882 | 1883 | 注意,如果代码中有至少一个测量没有使用`AssertProb`或`ForceMeasure`标记,模拟器将会抛出`UnconstrainMeasurementException`异常。 1884 | 1885 | 除了运行量子程序,`QCTraceSimulator`还有5个组件用于检测程序中的bug和进行量子程序资源估算,它们是: 1886 | 1887 | - 输入检查器(Distinct Inputs Checker) 1888 | - 非法的量子比特使用检查器(Invalidated Qubits Use Checker) 1889 | - 基本操作计数器(Primitive Operations Counter) 1890 | - 量子线路深度计数器(Circuit Depth Counter) 1891 | - 量子线路宽度计数器(Circuit Width Counter) 1892 | 1893 | 每一个组件都可以在`QCTraceSimulatorConfiguration`中设置合适的标志进行激活。下面的部分我们将对这些部分进行详细介绍。 1894 | 1895 | #### 8.2.3 输入检查器 1896 | 1897 | `Distinct Inputs Checker`是`QCTraceSimulator`的一部分,它用来检测代码中可能存在的bug。考虑以下Q#代码: 1898 | 1899 | ```c# 1900 | operation DoBoth( q1 : Qubit, q2 : Qubit, op1 : (Qubit=>()), op2 : (Qubit=>()) ) : () { 1901 | body { 1902 | op1(q1); 1903 | op2(q2); 1904 | } 1905 | } 1906 | ``` 1907 | 1908 | 当用户看到这段代码时,他们会假定参数中的`op1`和`op2`被调用的顺序是没有影响的,因为`q1`和`q2`是两个不同的量子比特,并且作用于不同量子比特上的操作也是明确的。现在来看下面的例子,例子中使用了上面定义的操作: 1909 | 1910 | ```c# 1911 | operation DisctinctQubitCaptured2Test () : () { 1912 | body { 1913 | using( q = Qubit[3] ) { 1914 | let op1 = CNOT(_,q[1]); 1915 | let op2 = CNOT(q[1],_); 1916 | DoBoth( q[0],q[2],op1,op2); 1917 | } 1918 | } 1919 | } 1920 | ``` 1921 | 1922 | 现在`op1`和`op2`都通过局部应用来获取,并且共享一个量子比特,当用户调用`DoBoth`时,运行结果将依赖于`op1`和`op2`在`DoBoth`中出现的顺序,这绝对不会是用户希望发生的。`Distinct Inputs Checker`能够检测这样的情况并抛出`DisctinctInputsCheckerException`。 1923 | 1924 | 下面的代码展示了如何在C#代码中使用配置了`Distinct Inputs Checker`的`QCTraceSimulator`。 1925 | 1926 | ```c# 1927 | using Microsoft.Quantum.Simulation.Core; 1928 | using Microsoft.Quantum.Simulation.Simulators; 1929 | using Microsoft.Quantum.Simulation.Simulators.QCTraceSimulators; 1930 | 1931 | 1932 | namespace Quantum.MyProgram 1933 | { 1934 | class Driver 1935 | { 1936 | static void Main(string[] args) 1937 | { 1938 | var traceSimCfg = new QCTraceSimulatorConfiguration(); 1939 | traceSimCfg.useDistinctInputsChecker = true; //enables distinct inputs checker 1940 | QCTraceSimulator sim = new QCTraceSimulator(traceSimCfg); 1941 | var res = MyQuantumProgram.Run().Result; 1942 | System.Console.WriteLine("Press any key to continue..."); 1943 | System.Console.ReadKey(); 1944 | } 1945 | } 1946 | } 1947 | ``` 1948 | 1949 | 上面的代码中,类`QCTraceSimulatorConfiguration`存储`QCTraceSimulator`的配置,并且能够作为`QCTraceSimulator`构造函数的参数。当`useDistinctInputsChecker`被置为`true`时,`Distinct Inputs Checker`将被激活。 1950 | 1951 | #### 8.2.4 非法量子比特使用检测器 1952 | 1953 | `Invalidated Qubits Use Checker`是`QCTraceSimulator`的一部分,它被用来检测代码中潜在的bug。考虑下面的Q#代码: 1954 | 1955 | ```c# 1956 | operation UseReleasedQubitTest () : () { 1957 | body { 1958 | mutable q = new Qubit[1]; 1959 | using( ans = Qubit[1] ) { 1960 | set q[0]= ans[0]; 1961 | } 1962 | H(q[0]); 1963 | } 1964 | } 1965 | ``` 1966 | 1967 | 在上面的代码中,当`H`被应用到`q[0]`时,它指向了一个已经被释放的量子比特,这将导致未定义行为。当`Invalidated Qubits Use Checker`被激活时,它将抛出`InvalidatedQubitsUseCheckerException`异常。 1968 | 1969 | 下面的代码展示了如何在C#程序中使用配置了`Invalidated Qubits Use Checker`的`QCTraceSimulator`。 1970 | 1971 | ```c# 1972 | using Microsoft.Quantum.Simulation.Core; 1973 | using Microsoft.Quantum.Simulation.Simulators; 1974 | using Microsoft.Quantum.Simulation.Simulators.QCTraceSimulators; 1975 | 1976 | 1977 | namespace Quantum.MyProgram 1978 | { 1979 | class Driver 1980 | { 1981 | static void Main(string[] args) 1982 | { 1983 | var traceSimCfg = new QCTraceSimulatorConfiguration(); 1984 | traceSimCfg.useInvalidatedQubitsUseChecker = true; // enables useInvalidatedQubitsUseChecker 1985 | QCTraceSimulator sim = new QCTraceSimulator(traceSimCfg); 1986 | var res = MyQuantumProgram.Run().Result; 1987 | System.Console.WriteLine("Press any key to continue..."); 1988 | System.Console.ReadKey(); 1989 | } 1990 | } 1991 | } 1992 | ``` 1993 | 1994 | 上面的代码中,类`QCTraceSimulatorConfiguration`存储`QCTraceSimulator`的配置,并且能够作为`QCTraceSimulator`构造函数的参数。当`useInvalidatedQubitsUseChecker`被置为`true`时,`Invalidated Qubits Use Checker`将被激活。 1995 | 1996 | #### 8.2.5 基本操作计数器(Primitive Operations Counter) 1997 | 1998 | `Primitive Operations Counter`是`QCTraceSimulator`的一部分。它对量子程序中每一个`operation`执行的基本操作进行计数,统计的结果被保存在一个操作调用图的边缘上。现在让我们来看看使用一个`CCNOT`操作需要多少的`T`门。我们首先定义一个名为`CCNOTDriver`的操作: 1999 | 2000 | ```c# 2001 | open Microsoft.Quantum.Primitive; 2002 | operation CCNOTDriver() : () { 2003 | body { 2004 | using( qubits = Qubit[3] ) { 2005 | CCNOT(qubits[0],qubits[1],qubits[2]); 2006 | T(qubits[0]); 2007 | } 2008 | } 2009 | } 2010 | ``` 2011 | 2012 | 为了计算`CCNOT`操作和`CCNOTDriver`操作中到底需要多少个`T`门,我们使用下面的C#代码: 2013 | 2014 | ```c# 2015 | // using Microsoft.Quantum.Simulation.Simulators.QCTraceSimulators; 2016 | // using System.Diagnostics; 2017 | var config = new QCTraceSimulatorConfiguration(); 2018 | config.usePrimitiveOperationsCounter = true; 2019 | var sim = new QCTraceSimulator(config); 2020 | var res = CCNOTDriver.Run(sim).Result; 2021 | 2022 | 2023 | double tCountAll = sim.GetMetric(PrimitiveOperationsGroupsNames.T); 2024 | double tCount = sim.GetMetric(PrimitiveOperationsGroupsNames.T); 2025 | ``` 2026 | 2027 | 上面代码中,第一部分运行`CCNOTDriver`,第二部分我们使用`QCTraceSimulator.GetMetric`方法来获得`CCNOTDriver`中运行`T`门的次数。上面程序的输出结果为`CCNOT`执行了7此`T`门,而`CCNOTDriver`执行了8此`T`门。 2028 | 2029 | 当使用两个类型参数来调用`GetMetric`时,它返回与给定调用图边缘相关联的数值,在上面的例子中,`Primitive.CCNOT`在`CCNOTDriver`中被调用,因此调用图中包含边缘``。 2030 | 2031 | 如果要获取`CNOT`门被使用的次数,那么我们可以使用以下代码: 2032 | 2033 | ```c# 2034 | double cxCount = sim.GetMetric(PrimitiveOperationsGroupsNames.CX); 2035 | ``` 2036 | 2037 | 最后,我们可以使用以下代码将所有的统计结果输出为CSV格式: 2038 | 2039 | ```c# 2040 | double cxCount = sim.GetMetric(PrimitiveOperationsGroupsNames.CX); 2041 | ``` 2042 | 2043 | #### 8.2.6 深度计数器(Depth Counter) 2044 | 2045 | 深度计数器是`QCTraceSimulator`的一部分,它用来采集量子程序中调用的每一个操作的深度。默认情况下,`T`门的深度为1,其他所有门的深度为0,这也意味着默认情况下只有`T`门的深度才会被计算,同时用户也可以自行设定每一个基本操作的深度。下面的Q#和C#代码用来演示深度计数器。 2046 | 2047 | ```c# 2048 | open Microsoft.Quantum.Primitive; 2049 | operation CCNOTDriver() : () { 2050 | body { 2051 | using( qubits = Qubit[3] ) { 2052 | CCNOT(qubits[0],qubits[1],qubits[2]); 2053 | T(qubits[0]); 2054 | } 2055 | } 2056 | } 2057 | ``` 2058 | 2059 | 下面是相应的C#驱动代码: 2060 | 2061 | ```c# 2062 | using Microsoft.Quantum.Simulation.Simulators.QCTraceSimulators; 2063 | using System.Diagnostics; 2064 | var config = new QCTraceSimulatorConfiguration(); 2065 | config.useDepthCounter = true; 2066 | var sim = new QCTraceSimulator(config); 2067 | var res = CCNOTDriver.Run(sim).Result; 2068 | 2069 | 2070 | double tDepth = sim.GetMetric(DepthCounter.Metrics.Depth); 2071 | double tDepthAll = sim.GetMetric(DepthCounter.Metrics.Depth); 2072 | ``` 2073 | 2074 | 上面程序中第一部分运行了`CCNOTDriver`操作,第二部分我们使用`QCTraceSimulator.GetMetric`方法来获取`CCNOT`和`CCNOTDriver`中`T`的深度: 2075 | 2076 | ```c# 2077 | double tDepth = sim.GetMetric(DepthCounter.Metrics.Depth); 2078 | double tDepthAll = sim.GetMetric(DepthCounter.Metrics.Depth); 2079 | ``` 2080 | 2081 | 最后,我们可以使用下面的代码将`Depth Counter`采集的数据用CSV的格式输出: 2082 | 2083 | ```c# 2084 | string csvSummary = sim.ToCSV()[MetricCalculatorsNames.depthCounter]; 2085 | ``` 2086 | 2087 | #### 8.2.7 宽度计数器(Width Counter) 2088 | 2089 | 宽度计数器对每个操作中分配的和借用的量子比特进行计数,一些操作还会分配额外的量子比特,例如受控的`X`门和受控的`T`门。下面我们来统计实现一个多量子受控`X`门所需要额外分配的量子比特的数目。 2090 | 2091 | ```c# 2092 | open Microsoft.Quantum.Primitive; 2093 | operation MultiControlledXDriver( numberOfQubits : Int ) : () { 2094 | body { 2095 | using( qubits = Qubit[numberOfQubits] ) { 2096 | (Controlled X)(qubits[ 1 .. numberOfQubits - 1] ,qubits[0]); 2097 | } 2098 | } 2099 | } 2100 | ``` 2101 | 2102 | 下面是用来进行量子比特数量统计的C#代码,它展示了一个作用域5个量子比特的多量子受控`X`门还需要分配2个辅助量子比特,并且它的输入宽度为5. 2103 | 2104 | ```C# 2105 | var config = new QCTraceSimulatorConfiguration(); 2106 | config.useWidthCounter = true; 2107 | var sim = new QCTraceSimulator(config); 2108 | int totalNumberOfQubits = 5; 2109 | var res = MultiControlledXDriver.Run(sim, totalNumberOfQubits).Result; 2110 | 2111 | 2112 | double allocatedQubits = 2113 | sim.GetMetric( 2114 | WidthCounter.Metrics.ExtraWidth, 2115 | functor: OperationFunctor.Controlled); 2116 | 2117 | 2118 | double inputWidth = 2119 | sim.GetMetric( 2120 | WidthCounter.Metrics.InputWidth, 2121 | functor: OperationFunctor.Controlled); 2122 | ``` 2123 | 2124 | 上面程序中第一部分运行了`MultiControlledXDriver`,第二部分使用`QCTraceSimulator.GetMetric`方法來获取受控的`X`门操作中分配的量子比特数和输入其中的量子数量。最后,我们可以使用以下代码得到CSV格式的输出结果: 2125 | 2126 | ```c# 2127 | string csvSummary = sim.ToCSV()[MetricCalculatorsNames.widthCounter]; 2128 | ``` 2129 | 2130 | ## 参考资料 2131 | 2132 | Michael A. Nielsen & Isaac L. Chuang 《Quantum Computation and Quantum Information》 2133 | 2134 | N. David Mermin . 《Quantum Computer Science》 2135 | 2136 | [Wikipedia](https://en.wikipedia.org/wiki/Quantum_computer) 2137 | 2138 | 曹天元 . 《上帝掷骰子吗?》 2139 | 2140 | [Microsoft Q# offical document](https://docs.microsoft.com/en-us/quantum/quantum-techniques-5-workingwithqubits?view=qsharp-preview) 2141 | 2142 | [剑桥大学量子计算讲义](https://www.cl.cam.ac.uk/teaching/1213/QuantComp/) 2143 | 2144 | [Tennessee大学讲义](https://web.eecs.utk.edu/~mclennan/Classes/494-594-UC-F12/handouts/) 2145 | 2146 | [MIT量子计算讲义](https://ocw.mit.edu/courses/mathematics/18-435j-quantum-computation-fall-2003/lecture-notes/) 2147 | 2148 | [Southern California大学量子计算讲义](http://www-bcf.usc.edu/~tbrun/Course/) 2149 | 2150 | [University of Minnesota Twin Cities大学量子计算讲义](http://d.umn.edu/~vvanchur/2015PHYS4071/) 2151 | 2152 | [Ann Arbor . Tutorial: Basic Concepts in Quantum Circuits . University of Michigan](http://vlsicad.eecs.umich.edu/BK/Slots/cache/www.eecs.umich.edu/~jhayes/JPH_DACslides_Jun03.pdf) 2153 | 2154 | [Mario Sracic . Quantum Circuits for Matrix Multiplication . kansas state university](https://www.math.ksu.edu/reu/sumar/QuantumAlgorithms.pdf) 2155 | 2156 | [Parasa, Vamsi, and Marek A. Perkowski. “Quantum Phase Estimation Using Multivalued Logic.” *2011 41st IEEE International Symposium on Multiple-Valued Logic*, 2011, pp. 224–229.](https://pdxscholar.library.pdx.edu/cgi/viewcontent.cgi?referer=https://cn.bing.com/&httpsredir=1&article=1201&context=ece_fac) 2157 | 2158 | [Wei, L. F., and Franco Nori. “Quantum Phase Estimation Algorithms with Delays: Effects of Dynamical Phases.” *Journal of Physics A*, vol. 37, no. 16, 2004, pp. 4607–4617.](https://deepblue.lib.umich.edu/bitstream/handle/2027.42/48842/a4_16_010.pdf?sequence=2) 2159 | 2160 | [Vedral, Vlatko, et al. “Quantum Networks for Elementary Arithmetic Operations.” *Physical Review A*, vol. 54, no. 1, 1996, pp. 147–153.](https://arxiv.org/pdf/quant-ph/9511018.pdf) 2161 | 2162 | [Artur Ekert , Alastair Kay . Phase estimation and Shor’s algorithm](http://www.arturekert.org/quantum/lecturenotes/note6.pdf) 2163 | 2164 | [Li, Chi-Kwong, et al. “DECOMPOSITION OF UNITARY MATRICES AND QUANTUM GATES.” *International Journal of Quantum Information*, vol. 11, no. 1, 2013, p. 1350015.](http://cklixx.people.wm.edu/li-roberts-yin.pdf) 2165 | 2166 | [Quantum Measurement Theory](http://www.quantum.umb.edu/Jacobs/QMT/QMT_Chapter1.pdf) 2167 | 2168 | 2169 | 2170 | ## to-do 2171 | 2172 | **1. 补充量子算法相关内容,包括算法的分类以及常用的量子算法** 2173 | 2174 | - 量子傅里叶变换 2175 | - 辅助量子的概念等 2176 | 2177 | **2. Q#中自定义量子门的Adjoint和Controlled** 2178 | 2179 | ![DiyAdjointAndControlled](./image/DiyAdjointAndControlled.png) 2180 | 2181 | **3. 补充更多代码示例** 2182 | 2183 | **4. Q#中的参数类型好泛型** 2184 | 2185 | **5. 量子测量部分需要重新编写,漏洞较多** 2186 | 2187 | 2188 | 2189 | 2190 | 2191 | ### 2192 | 2193 | ### 2194 | 2195 | 2196 | 2197 | -------------------------------------------------------------------------------- /backup/Image/ABC.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siyu-npc/learning-q-sharp/642affce3d6981a3bd5694a28b489f58800f91a8/backup/Image/ABC.png -------------------------------------------------------------------------------- /backup/Image/BOperation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siyu-npc/learning-q-sharp/642affce3d6981a3bd5694a28b489f58800f91a8/backup/Image/BOperation.png -------------------------------------------------------------------------------- /backup/Image/BlochSphere.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siyu-npc/learning-q-sharp/642affce3d6981a3bd5694a28b489f58800f91a8/backup/Image/BlochSphere.png -------------------------------------------------------------------------------- /backup/Image/ControlledG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siyu-npc/learning-q-sharp/642affce3d6981a3bd5694a28b489f58800f91a8/backup/Image/ControlledG.png -------------------------------------------------------------------------------- /backup/Image/ControlledGateG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siyu-npc/learning-q-sharp/642affce3d6981a3bd5694a28b489f58800f91a8/backup/Image/ControlledGateG.png -------------------------------------------------------------------------------- /backup/Image/HGate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siyu-npc/learning-q-sharp/642affce3d6981a3bd5694a28b489f58800f91a8/backup/Image/HGate.png -------------------------------------------------------------------------------- /backup/Image/MaxTangleQuantumCircut.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siyu-npc/learning-q-sharp/642affce3d6981a3bd5694a28b489f58800f91a8/backup/Image/MaxTangleQuantumCircut.png -------------------------------------------------------------------------------- /backup/Image/Measure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siyu-npc/learning-q-sharp/642affce3d6981a3bd5694a28b489f58800f91a8/backup/Image/Measure.png -------------------------------------------------------------------------------- /backup/Image/NewQSharpProject.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siyu-npc/learning-q-sharp/642affce3d6981a3bd5694a28b489f58800f91a8/backup/Image/NewQSharpProject.png -------------------------------------------------------------------------------- /backup/Image/QuantumStateVector.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siyu-npc/learning-q-sharp/642affce3d6981a3bd5694a28b489f58800f91a8/backup/Image/QuantumStateVector.png -------------------------------------------------------------------------------- /backup/Image/Teleportation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siyu-npc/learning-q-sharp/642affce3d6981a3bd5694a28b489f58800f91a8/backup/Image/Teleportation.png -------------------------------------------------------------------------------- /backup/Image/XZ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siyu-npc/learning-q-sharp/642affce3d6981a3bd5694a28b489f58800f91a8/backup/Image/XZ.png -------------------------------------------------------------------------------- /backup/README.md: -------------------------------------------------------------------------------- 1 | # 前言 2 | 3 | Q\#语言是微软公司推出的一款用于量子计算的编程语言,它能够使开发者在目前的通用型计算机上实现量子算法、开发量子程序,并且其从Python、C\#、F\#等语言中汲取了许多元素,使得相比其他已出现的量子编程语言,它对开发者更友好,使用也更方便。 4 | 5 | 本书以微软官方的Q\\#文档为基础进行翻译,力求在准确详实的基础上做到通俗易懂。由于本人能力、精力有限,书中必定存在疏漏和错误,如有发现请及时告知。当然,翻译的内容相比原版有可能会有信息的损失,所以遇到问题时我强烈建议大家还是应该以官方原版文档为准。 6 | 7 | 8 | 9 | **欢迎大家也能加入这个翻译项目中。** 10 | 11 | 个人联系QQ(微信): **1036014410** 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /backup/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | * [前言](README.md) 4 | * [第一章 快速入门](chapter1.md) 5 | * [第〇章 量子计算中的常用概念](chapter0.md) 6 | * [第二章 Q\#编程语言](chapter2.md) 7 | * [第三章 量子设备和驱动管理](chapter3.md) 8 | 9 | -------------------------------------------------------------------------------- /backup/book.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins":["mathjax"] 3 | } 4 | -------------------------------------------------------------------------------- /backup/chapter0.md: -------------------------------------------------------------------------------- 1 | # 第0章 量子计算中的常用概念 2 | 3 | ### 什么是量子计算 4 | 5 | 在过去的几年中,出现了一系列新的计算机技术,其中量子计算可能是最需要开发者转变开发模式的技术。量子计算机是在上世纪80年代,由Richard Feynman和Yuri Manin提出的。经过了多年的发展,人类虽然在科学技术上取得了非凡的成就,但我们依然面对着一个尴尬的情景:非凡的科学进展却对简单的系统无能为力。这也是量子计算背后的灵感和驱动力的来源。如你所见,早在1900~1925年间,量子力学的框架就已经被建立起来了,它是化学、凝聚态物理和从计算机芯片到LED照明再到其他技术的基石。尽管取得了很大的成就,但是利用量子力学去对一些非常简单的系统进行建模时仍然超出了人类的能力水平,因为即使是模拟几十个粒子相互作用的系统,所需要的计算能力也超过当今任何计算机持续计算数千年以上所能提供的计算能力。 6 | 7 | 量子力学为何难以模拟?在量子水平上,同时存在着一系列可能的不同架构(也叫状态,量子态),不同于经典的概率论,这些能够被潜在观察到的状态可能会像水池中的波纹一样相互干扰,这种干扰阻碍了使用统计抽样来获取量子态的架构,并且,如果我们想要理解量子演进,那就必须追踪一个量子系统中所有的可能状态。 8 | 9 | 设想一个由电子组成的系统,其中电子可以出现在40个位置中的任何一个上,那么这个系统就存在$$2 ^ {40}$$种状态(每个位置上可能有也可能没有电子存在),在一个传统的计算机存储器上存储这些状态就需要130Gb的空间,这个存储当今的一些计算机还是可以满足的,但是如果将40变为41,多了一个位置就会导致存储空间的翻倍。如果我们继续按照常规办法这么做下去,这个增加位置的游戏马上就会进入死局,因为世界上最强大的存储设备也无法满足这样的指数增长。将电子数量扩大到数百个,那么所需要的存储空间甚至会超过宇宙中粒子的数量,因此使用传统的计算机无法模拟量子力学模型。 10 | 11 | 这一问题引导着那些对量子计算有着早期愿景的人们思考这样一个问题:我们能否将这一挑战转变为一个机遇。量子力学难以模拟,那如果我们利用量子特性来建造量子硬件会发生什么呢?我们能否直接利用量子力学的机制来模拟相互作用的量子系统呢?正是这些问题催生了量子计算。 12 | 13 | 量子计算的基础核心问题是是将信息存储在物质的量子态中,并使用量子门操作在这些信息上利用量子干涉效应进行计算。一个早期的利用对量子干涉效应编程来解决问题的例子是由 Peter Shor在1994年完成的,这是一个因式分解问题,对一个较大的数进行因式分解对传统计算机来说是困难的,解决因式分解问题能使现今电子商务安全基础上的许多公钥密码体制失效,例如RSA和ECDLP。到了那个时候,快速高效的量子算法被开发出来,能为我们解决许多经典计算机难以解决的难题:模拟化学、物理和材料科学中的物理系统,搜索无序数据库,求解线性方程组和机器学习等。 14 | 15 | 设计一个利用量子干涉的程序听起来像是一个艰巨的挑战,事实也确实如此,但目前已经有许多技术和工具包括微软的量子开发工具包使量子程序和算法的开发更便捷。现实中有一些基本的对计算有用的策略可以被用来操控量子干涉,同时不会造成量子纠缠中的解丢失。量子编程是一种与经典编程截然不同的艺术,它需要用完全不同的工具去理解和表达编程思想,实际上,如果没有合适的工具来帮助开发者解决量子编程中的问题,量子程序的开发就会变得异常复杂困难。 16 | 17 | 我们推出了微软量子程序开发工具包来推动正在成长的量子编程社区的发展,工具包中包含了多种针对具体任务、问题和解决方案的工具。我们的高级程序语言Q\#旨在解决量子信息处理的挑战,它集成在一个软件栈中,使得量子算法能被编译成量子计算机的基本操作。在走进Q\#之前,回顾一下量子计算的基本概念是很有帮助的,在本书中,我们将把量子计算的基本规则作为公理,而不会详述量子力学的基础,并且我们假定读者有一定的线性代数基础(向量、矩阵等),如果你需要更深入地了解量子计算的历史和原理,请参考[https://docs.microsoft.com/zh-cn/quantum/quantum-formoreinfo?view=qsharp-preview。](https://docs.microsoft.com/zh-cn/quantum/quantum-formoreinfo?view=qsharp-preview。) 18 | 19 | ### 量子比特 20 | 21 | 比特是经典计算中信息的基本对象,量子比特则是量子计算中信息的基本对象。 22 | 23 | #### 量子比特的表示 24 | 25 | 一个二进制比特的值可以是0或1,而一个量子比特的值除了可以是0或1,还可以是这两个值的量子叠加,一个量子比特的状态可以用一个二维的单位向量来描述,这个向量叫做**量子态向量**,它保存了要描述一个单量子系统状态所需要的全部信息。 26 | 27 | 任意一个范数为1的二维实向量或复向量都能表示一个量子位的状态,所以以下向量都属于量子态向量。 28 | 29 | ![](/Image/QuantumStateVector.png) 30 | 31 | 在所有量子态向量中,$$\begin{bmatrix} 1 \\ 0 \end{bmatrix}$$和$$\begin{bmatrix} 0 \\ 1 \end{bmatrix}$$ 有着特殊的作用,它们构成了描述量子状态的向量空间的基,这意味着任何一个量子态向量都可以用这两个基的代数和来表示,例如,向量$$\begin{bmatrix} x \\ y \end{bmatrix}$$ 可以写做$$x \begin{bmatrix} 1 \\ 0 \end{bmatrix} + y \begin{bmatrix} 0 \\ 1 \end{bmatrix}$$ ,因此,这两个向量又被成为**计算基底**。 32 | 33 | 同时,我们将上述两个基所代表的量子态与经典计算中的比特位相对应,它们之间的关系为: 34 | 35 | $$0\equiv \begin{bmatrix} 1 \\ 0 \end{bmatrix}\qquad 1 \equiv \begin{bmatrix} 0 \\ 1 \end{bmatrix}$$ 36 | 37 | #### 量子态的测量 38 | 39 | 我们已经知道了如何表示一个量子比特,那么一个量子比特的状态到底代表了什么呢?现在我们通过探讨量子态测量的概念来建立一个对量子态的认识。量子态的测量就是对量子状态的观察,根据量子力学的原理,观察一个量子会使其状态坍缩至两个经典状态$$\begin{bmatrix} 1 \\ 0 \end{bmatrix}$$和$$\begin{bmatrix} 0 \\ 1 \end{bmatrix}$$中的一个,当一个状态为$$\begin{bmatrix} \alpha \\ \beta \end{bmatrix}$$的量子被测量后,我们得到$$0$$的概率为$$\|\alpha\|^2$$,得到$$1$$的概率为$$\|\beta\|^2$$,并且$$\|\alpha\|^2 + \|\beta\|^2 = 1$$ 。根据上述量子态测量的性质,我们可以知道一个量子态矩阵与其符号是不相关的,这个向量与其反向量等价:$$\alpha \rightarrow -\alpha$$ , $$\beta \rightarrow -\beta$$ 。这是因为测量到$$0$$和$$1$$的概率取决于$$\alpha$$和$$\beta$$的平法的大小,在他们之前插入一个符号并不改变测量结果的概率分布。 40 | 41 | 有关测量的最后一个重要性质是测量并不会使所有量子态都发生改变。如果我们测量一个状态为$$\begin{bmatrix} 1 \\ 0 \end{bmatrix}$$的量子,相当于经典计算中的比特$$0$$,那么我们得到的测量结果仍然为$$0$$,且量子本身的状态也不会改变。从这个意义上讲,如果我们只拥有经典的比特位,我们对这些比特位进行测量也不会改变它们状态,这意味着我们能够将经典计算的数据复制到量子计算机上,然后像在经典计算机上那样对其进行处理。 42 | 43 | ### 利用布罗兹球面可视化量子态和其转换 44 | 45 | 量子位也可以使用布罗兹球面3D化显示。布罗兹球面用三维的实值向量来描述单个量子的量子态。如上所述,单个量子的状态由一个二维向量来描述。布罗兹球面使得一个量子的状态可视化,正因此,它在我们理解多量子状态时也有极大的作用。可视化的布罗兹球面如下所示: 46 | 47 | ![](/Image/BlochSphere.png) 48 | 图0.1 布罗兹球面 49 | 50 | 图中的箭头指示了量子态向量的方向,箭头的每一次变换都可以看作是一个基本轴的旋转,那么自然而然我们可以将量子态的变换看做是一系列的旋转变换。当然使用这种思想来设计和描述量子算法也是一个不小的挑战,但Q\#为我们提供了能够方便地描述这些旋转操作的语言,从而简化了我们解决问题的负担。 51 | 52 | ### 单个量子位的操作 53 | 54 | 量子计算机通过应用一组能模拟量子态向量旋转的量子门来处理数据,量子门的概念与传统计算机中的门的概念类似,如果每一个输入比特的变换都可以用有限长的量子门来执行,则对应的量子门集合被认为是通用的。 55 | 56 | 在量子计算中,我们能够在量子比特上执行的有效的变换包括幺正变化和测量。共轭操作也叫复共轭转置,他对量子计算有着至关重要的作用,因为在量子信息翻转中经常需要用到它。Q\#能自动将量子门序列编译成它们的共轭矩阵,将开发者从手工编码的泥潭中解放出来,大大减轻了开发者的负担。 57 | 58 | 在经典计算中,只有四种操作将一个比特映射到另一个比特(与、或、非、异或),但在量子计算机中,变换一个量子比特状态的操作是无穷的,因此在量子计算中,不存在一个有限的基本操作集合(门集合)能完全覆盖量子计算中有效而无穷的幺正变换,这也意味着,量子计算机不可能像经典计算机那样使用有限的门操作来实现所有的量子算法,所以量子计算机不会像经典计算机那样通用。当我们谈到一个门集合对量子计算是通用的时候,我们实际要表达的意思要比完全通用弱化。对于通用性,我们要求量子计算机只在一个有限的误差内使用一个有限长的门序列来逼近任意幺正矩阵即可,或者说,只要任意的幺正变换在误差允许范围内能够写作一个有限长的门操作序列的张量积,那么这个门集合就是通用的,如下所示: 59 | 60 | $$G_N G_{N-1} \cdots G_2 G_1 \approx U.$$ 61 | 62 | 注意因为矩阵乘法是从右向左计算的,因此上面公式中的$$G_{N}$$实际上是最后一个被应用到的门操作。更正规地说,对于误差$$\epsilon > 0$$,存在$$G_1,\ldots, G_N$$使得$$G_N\ldots G_1$$ 和 $$U$$的偏差不超过$$\epsilon$$,那么我们就说集合$$G_1,\ldots, G_N$$是通用的。 63 | 64 | 现实中,这样的通用门集合是什么样的呢?对单量子门而言,这样的集合中只包含两个门:Hadamard门(H门)和T门(也叫做$$\pi/8$$门): 65 | $$H=\frac{1}{\sqrt{2}}\begin{bmatrix} 1 ; 1 \\ 1 ;-1 \end{bmatrix},\qquad T=\begin{bmatrix} 1 ; 0 \\ 0 ; e^{i\pi/4} \end{bmatrix}.$$ 66 | 67 | 但是考虑到量子计算的实用性,一个更大的集合能够带来更多的便利,集合中其他的门可以由H和T门生成,我们将这些量子门分为两类:Clifford门和T门,这样分类是因为Clifford门能够很方便地应用在许多量子纠错方案中,且他们只需要很少的资源就能实现较好的容错率,然而非Clifford门的消耗就非常大了。在Q\#中,标准的单量子Clifford门包括: 68 | $$H=\frac{1}{\sqrt{2}}\begin{bmatrix} 1 ; 1 \\ 1 ;-1 \end{bmatrix} ,\qquad S =\begin{bmatrix} 1 ; 0 \\ 0 ; i \end{bmatrix}= T^2,\qquad X=\begin{bmatrix} 0 ;1 \\ 1 ; 0 \end{bmatrix}= HT^4H,$$$$Y = \begin{bmatrix} 0 ; -i \\ i ; 0 \end{bmatrix}=T^2HT^4 HT^6, \qquad Z=\begin{bmatrix}1;0\\ 0;-1 \end{bmatrix}=T^4.$$ 69 | 70 | 这些门中,X、Y和Z应用的非常广泛,它们也被成为Pauli操作符,这些门操作和非Clifford门一起就能组合出任意幺正变换作用于单个量子上。下面的例子展示了如何用这些基本操作构建一个幺正变换,图0.1中的三个变换对应于以下门操作序列: 71 | $$\begin{bmatrix} 1 \\ 0 \end{bmatrix} \mapsto HZH \begin{bmatrix} 1 \\ 0 \end{bmatrix} = \begin{bmatrix} 0 \\ 1 \end{bmatrix}$$ 72 | 73 | 前面的门操作在堆栈逻辑级别上构成了最基本的原语(逻辑级别等同与量子算法级别),但在算法级别较少地考虑基本操作会带来更大的便利,比如在编程中应多使用近似函数级别的操作。幸运的是Q\#中包含有很多用于实现高层次幺正变换的方法,有了他们我们在实现更高层次的算法时就不需要将其分解为基本的Clifford和T门。 74 | 75 | 最简单的原语是单量子旋转操作。将三个单量子旋转用$$R_{x}$$、$$R_{y}$$和$$R_{z}$$表示,为了可视化旋转操作$$R_x(\theta)$$的行为,将右手大拇指指向布罗兹球面$$x$$轴的方向,然后右手旋转$$\theta/2$$弧度,相应的幺正变换为: 76 | $$R_z(\theta) = \begin{bmatrix} e^{-i\theta/2} ; 0\\ 0 ; e^{i\theta/2} \end{bmatrix},\qquad R_x(\theta) = H R_z(\theta) H, \qquad R_y(\theta) = SHR_z(\theta)HS^\dagger.$$ 77 | 78 | 正如将任意三个旋转操作组合起来就能完成三维空间中的任意旋转,布罗兹球面所表示的任意幺正矩阵也能写成由三个旋转操作组成的序列,特别地,对每一个幺正矩阵$$U$$都有$$\alpha,\beta,\gamma,\delta$$使得$$U= e^{i\alpha} R_x(\beta)R_z(\gamma)R_x(\delta)$$。因此$$R_x(\theta)$$和H门也可以构成一个通用的量子门集合,当然了,因为$$\theta$$可以是任意值,因此构成的量子门集合就不是离散的了,并且考虑到量子模拟的应用场景,连续的量子门对量子计算是至关重要的,特别是在量子算法的设计层面上。最终,这些操作会被编译成离散的满足误差要求的量子门序列实现这些旋转操作。 79 | 80 | ### 多量子比特 81 | 82 | 虽然单量子比特拥有一些反直观的特性,例如在某一时刻同时存在多种状态,但是如果一个量子计算机中只有单量子比特门,那它所能提供的运算能力甚至不如现在的一台小小的计算器,更不用说超级计算机了。只有增加量子数,量子计算的真正能力才能体现出来,这种能力的增长,部分原因来自与量子态向量空间的维数随量子数的增加而指数上升。这也意味着单量子系统建模比较容易,而对50个量子进行模拟就会对目前的超级计算机造成压力,每增加一个量子比特,所需要的存储空间和计算时间就会翻倍。 83 | 84 | 这一节的目标是在单量子态之外重新审视构建多量子态的规则,同时我们也会探讨要构建一台通用的量子计算机,需要那些门操作。Q\#中提供了一些工具使我们是理解多量子门的过程中绝对需要的,它们也会帮助我们理解为什么应用了量子效应如量子纠缠和量子干涉后就能使量子计算机比传统计算机强大那么多。 85 | 86 | #### 双量子比特的表示 87 | 88 | 单量子比特与双量子比特之间的主要区别在于双量子态向量是4维而单量子态向量是2维,这是因为双量子态的计算基底是通过单量子态向量的张量积得到的,如下所示: 89 | $$\begin{align} 90 | 00 \equiv \begin{bmatrix}1 \\ 0 \end{bmatrix}\otimes \begin{bmatrix}1 \\ 0 \end{bmatrix} ;= \begin{bmatrix}1 \\ 0\\ 0\\ 0 \end{bmatrix},\qquad 01 \equiv \begin{bmatrix}1 \\ 0 \end{bmatrix}\otimes \begin{bmatrix}0 \\ 1 \end{bmatrix} = \begin{bmatrix}0 \\ 1\\ 0\\ 0 \end{bmatrix},\\ 91 | 10 \equiv \begin{bmatrix}0 \\ 1 \end{bmatrix}\otimes \begin{bmatrix}1 \\ 0 \end{bmatrix} ;= \begin{bmatrix}0 \\ 0\\ 1\\ 0 \end{bmatrix},\qquad 11 \equiv \begin{bmatrix}0 \\ 1 \end{bmatrix}\otimes \begin{bmatrix}0 \\ 1 \end{bmatrix} = \begin{bmatrix}0 \\ 0\\ 0\\ 1 \end{bmatrix}. 92 | \end{align}$$ 93 | 94 | 容易知道,$$n$$个量子的量子态向量可以通过$$2^{n}$$维的单位向量用同样的方法进行构造。向量$$\begin{bmatrix} \alpha_{00} \\ \alpha_{01} \\ \alpha_{10} \\ \alpha_{11} \end{bmatrix}$$如果满足$$|\alpha_{00}|^2+|\alpha_{01}|^2+|\alpha_{10}|^2+|\alpha_{11}|^2=1$$,那么它就代表了一个双量子系统的量子态。与单量子类似,多量子态向量保存着描述多量子系统状态的所有信息。 95 | 96 | 如果给出两个独立的量子比特,一个状态为$$\begin{bmatrix} \alpha \\ \beta \end{bmatrix}$$,另一个状态为$$\begin{bmatrix} \gamma \\ \delta \end{bmatrix}$$,那么这两个量子比特组成的双量子系统的状态就是: 97 | $$\begin{bmatrix} \alpha \\ \beta \end{bmatrix} \otimes \begin{bmatrix} \gamma \\ \delta \end{bmatrix} 98 | =\begin{bmatrix} \alpha \begin{bmatrix} \gamma \\ \delta \end{bmatrix} \\ \beta \begin{bmatrix}\gamma \\ \delta \end{bmatrix} \end{bmatrix} 99 | = \begin{bmatrix} \alpha\gamma \\ \alpha\delta \\ \beta\gamma \\ \beta\delta \end{bmatrix},$$ 100 | 101 | 上面的$$\otimes$$符号表示向量的张量积(克罗内克积)。需要注意的是,虽然我们总能使用两个单量子态向量来构建一个双量子系统的量子态向量,但并非所有的双量子系统状态都能表示成两个单量子态向量的张量积。例如,不存在这样两个向量$$\psi=\begin{bmatrix} \alpha \ \beta \end{bmatrix}$ and $\phi=\begin{bmatrix} \gamma \ \delta \end{bmatrix}$$,使得他们的张量积为:$$\psi\otimes \phi = \begin{bmatrix} 1/\sqrt{2} \ 0 \ 0 \ 1/\sqrt{2} \end{bmatrix}.$$ 102 | 103 | 不能用两个单量子态向量的张量积来表示的双量子态叫做“纠缠态”,这两个量子比特被称为纠缠的。不严格地说,双量子态不能被认为是两个单量子比特的张量积,这个状态所持有的信息也不局限于两个单量子态中的一个,而是非局部地存储于两个量子态之间的联系中,这种信息的非局部性是量子计算和传统计算之间的主要区别之一,其对许多量子协议也是必不可少的,比如量子隐形传态和量子纠错。 104 | 105 | #### 双量子态的测量 106 | 107 | 双量子比特量子态的测量与单量子类似。测量一个状态为: 108 | $$\begin{bmatrix} \alpha_{00} \\ \alpha_{01} \\ \alpha_{10} \\ \alpha_{11} \end{bmatrix} $$ 109 | 的双量子系统,有$$|\alpha_{00}|^2$$的概率得到结果$$00$$,$$|\alpha_{01}|^2$$的概率得到结果$$01$$,$$|\alpha_{10}|^2$$的概率得到结果$$10$$,$$|\alpha_{11}|^2$$的概率得到结果$$11$$。在测量之后如果得到的结果是$$00$$,那么此时双量子系统的量子态已经坍塌为: 110 | $$ 00 \equiv \begin{bmatrix} 1 \\ 0 \\ 0 \\ 0 \end{bmatrix}. $$ 111 | 112 | 测量一个双量子系统中的某个量子的状态也是可行的,在只测量一个量子位的情况下,测量的影响是稍微不同的,因为整个系统的状态不会坍塌到计算基底状态,而是坍塌到一个子系统,或者说,测量一个量子位只是让双量子系统中的一个子系统坍缩而不是整个系统。为了理解这个现象,考虑测量如下所示状态中的第一个量子: 113 | $$H^{\otimes 2} \left( \begin{bmatrix}1 \\ 0 \end{bmatrix}\otimes \begin{bmatrix}1 \\ 0 \end{bmatrix} \right) = \frac{1}{2}\begin{bmatrix}1\\ 1\\ 1\\ 1\end{bmatrix}\mapsto \begin{cases}\text{outcome }=0 ; \frac{1}{\sqrt{2}}\begin{bmatrix}1\\ 1\\ 0\\ 0 \end{bmatrix}\\ \text{outcome }=1 ; \frac{1}{\sqrt{2}}\begin{bmatrix}0\\ 0\\ 1\\ 1 \end{bmatrix}\\ \end{cases}.$$ 114 | 115 | 两个输出结果各占50%的概率。测量第一个或第二个量子比特状态的数学规则很简单,假设我们让$$e_{k}$$表示第$$k$$个基向量,让$$S$$表示所有第$$k$$个元素为$$1$$的向量的集合,例如我们要测量第一个量子的状态,那么$$S$$中包括$$e_2\equiv 10$$和$$e_3\equiv 11$$,如果要测量第二个量子的状态,那么$$S$$就由$$e_1\equiv 01$$和$$e_3 \equiv 11$$组成。对于量子态向量为$$\psi$$的量子,测量得到其状态为$$1$$的概率为: 116 | $$P(\text{outcome}=1)= \sum_{k \text{ in the set } S}\psi^\dagger e_k e_k^\dagger \psi.$$ 117 | 118 | 因为测量量子比特所得的结果只能是$$0$$或者$$1$$,得到$$0$$的概率就是$$1-P(\text{outcome}=1)$$。这样的测量行为可以用数学形式表述为: 119 | $$ \psi \mapsto \frac{\sum_{k \text{ in the set } S} e_k e_k^\dagger \psi}{\sqrt{P(\text{outcome}=1)}}. $$ 120 | 121 | 如果我们将上面的向量$$ \psi $$看做单位向量,那么测量第一个量子得到$$1$$的概率为: 122 | $$ P(\text{measurement of first qubit}=1) = (\psi^\dagger e_2)(e_2^\dagger \psi)+(\psi^\dagger e_3)(e_3^\dagger \psi)=|e_2^\dagger \psi|^2+|e_3^\dagger \psi|^2. $$ 123 | 124 | 注意,这只是测量结果的两个概率的总和,$$10$$和$$11$$是所有要测量的量子,对我们上面的例子而言,计算时是这样的: 125 | $$ \frac{1}{4}\left|\begin{bmatrix}0;0 ;1 ;0\end{bmatrix}\begin{bmatrix}1\\ 1\\ 1\\ 1\end{bmatrix} \right|^2+\frac{1}{4}\left|\begin{bmatrix}0 ;0 ;0 ;1\end{bmatrix}\begin{bmatrix}1\\ 1\\ 1\\ 1\end{bmatrix} \right|^2=\frac{1}{2}. $$ 126 | 127 | 同时,对应的量子态也可以写作: 128 | $$ \frac{\frac{e_2}{2}+\frac{e_3}{2}}{\sqrt{\frac{1}{2}}}=\frac{1}{\sqrt{2}}\begin{bmatrix} 0\\ 0\\ 1\\ 1\end{bmatrix} $$ 129 | 130 | #### 双量子比特操作 131 | 132 | 在单量子情况下,任何幺正变换都是有效的,而施加于多量子系统上的幺正变换是一个大小为$$ 2 ^ {n} \times 2 ^{n} $$的矩阵(所以它作用与大小为$$2^{n}$$的向量上)并且$$U^{-1} = U^\dagger$$。例如,CNOT(controlled-NOT)门是一个用途广泛的双量子门,它使用如下单位矩阵表示: 133 | $$\operatorname{CNOT} = \begin{bmatrix} 1\ 0\ 0\ 0 \\ 0\ 1\ 0\ 0 \\ 0\ 0\ 0\ 1 \\ 0\ 0\ 1\ 0 \end{bmatrix}$$ 134 | 135 | 我们也可用单量子门操作来构造双量子门。假设对一个双量子系统,我们对其第一个和第二个量子比特分别应用下面两个门: 136 | $$ \begin{bmatrix} a\ b\\ c\ d \end{bmatrix} \begin{bmatrix} e\ f\\ g\ h \end{bmatrix} $$ 137 | 138 | 这等价于对双量子态向量应用这两个门的张量积: 139 | $$\begin{bmatrix} 140 | a\ b\\ c\ d 141 | \end{bmatrix} 142 | \otimes 143 | \begin{bmatrix} 144 | e\ f\\ g\ h 145 | \end{bmatrix}= 146 | \begin{bmatrix} 147 | ae\ af\ be\ bf \\ 148 | ag\ ah\ bg\ bh \\ 149 | ce\ cf\ de\ df \\ 150 | cg\ ch\ dg\ dh 151 | \end{bmatrix}.$$ 152 | 153 | 因此,我们可以使用已知的单量子门来构建双量子门,这样的例子包括:$$H \otimes H$$, $$X \otimes 1$$,和 $$X \otimes Z$$。 154 | 155 | 虽然两个单量子门的张量积可以定义一个双量子门,但所有的双量子门并非都能由单量子门构建而成,不能由两个单量子门的张量积表示的双量子门叫做纠缠门,CNOT门就是一个纠缠门。 156 | 157 | 对于CNOT门的认知可以推广到任意的门。一个受控的门一般来说扮演着身份验证的角色除非量子系统中一个特定的量子比特是$$1$$。我们将应用于量子比特$$x$$上的受控的幺正变换标记为$$\Lambda_x(U)$$,那么对于它有:$$\Lambda_0(U) e_{1}\otimes {\psi}=e_{1}\otimes U{\psi}$$和$$\Lambda_0(U) e_{0}\otimes {\psi}=e_{0}\otimes{\psi}$$,这里的$$e_{0}$$和$$e_{1}$$是量子态为$$0$$和$$1$$对应的单量子态基向量,例如对于下面的受控$$Z$$门,我们可以将其表示为: 158 | $$ \Lambda_0(Z)= \begin{bmatrix}1 0 0 0 \\ 0 1 0 0 \\ 0 0 1 0 \\ 0 0 0 -1 \end{bmatrix}=(1 \otimes H)\operatorname{CNOT}(1 \otimes H). $$ 159 | 160 | 用有效的方式构建受控的幺正变换是一个主要的挑战,实现这一点的最简单的方法是建立一个基础门操作的受控版本的数据库,并在最初的幺正变换中用受控操作替代对应的门操作。但这样做是相当浪费的,有一些灵巧的办法可以只替换某些门操作来达到同样的效果。在微软量子开发框架中,我们提供了原始的、受控的方法,并且也允许用户自定义一个幺正变换的受控版本。 161 | 162 | 量子门也可以受经典信息的控制。例如,一个经典的受控非门只在比特位为$$1$$时才起作用,根据这一点来看,经典的受控门可以看做代码中的一个if语句,只在某一分支上起作用。 163 | 164 | 与单量子类似,对于一个幺正矩阵,如果其可以使用一个双量子门集合中任何大小为$$4 \times 4$$量子门的向量积近似表示,那么这个双量子门集合就被认为是通用的。一个通用门集合的例子是由Hadamard门、T门和CNOT门组成的集合,通过这些门操作的向量积我们可以近似拟合出任意的作用于双量子系统的幺正矩阵。 165 | 166 | #### 多量子系统 167 | 168 | 我们遵循在双量子系统中探索到的规则来构建多量子系统,多量子系统的状态由较小系统的张量积构建而来。例如,在量子计算机中将字符串“1011001”编码的方式为: 169 | $$ 1011001 \equiv \begin{bmatrix} 0 \\ 1 \end{bmatrix}\otimes \begin{bmatrix} 1 \\ 0 \end{bmatrix}\otimes \begin{bmatrix} 0 \\ 1 \end{bmatrix}\otimes \begin{bmatrix} 0 \\ 1 \end{bmatrix} \otimes \begin{bmatrix} 1 \\ 0 \end{bmatrix}\otimes \begin{bmatrix} 1 \\ 0 \end{bmatrix}\otimes \begin{bmatrix} 0 \\ 1 \end{bmatrix}. $$ 170 | 171 | 量子门的工作方式与上面相同。例如,我们对多量子系统中的第一个量子比特应用$$X$$门,在第二个和第三个量子比特之间应用CNOT门,那么整个变换可以表示为: 172 | $$\begin{align} (X \otimes \operatorname{CNOT}_{12}\otimes 1 \otimes 1 \otimes 1) \begin{bmatrix} 0 \\ 1 \end{bmatrix}\otimes \begin{bmatrix} 1 \\ 0 \end{bmatrix}\otimes \begin{bmatrix} 0 \\ 1 \end{bmatrix}\otimes \begin{bmatrix} 0 \\ 1 \end{bmatrix} \otimes \begin{bmatrix} 1 \\ 0 \end{bmatrix}\otimes \begin{bmatrix} 1 \\ 0 \end{bmatrix}\otimes \begin{bmatrix} 0 \\ 1 \end{bmatrix}\\ \qquad\qquad\equiv 0011001. \end{align}$$ 173 | 174 | 在多量子系统中,经常需要分配和释放计算机中临时的量子比特所占用的空间,这些临时的量子变量又叫做“附属(ancilla)”量子。默认情况下,新分配的量子比特都被初始化为$$e_{0}$$状态,我们进一步假定在释放之前,这个量子比特的状态重新回到$$e_{0}$$。这个假定很重要,因为如果一个附属量子在被释放时与其他量子寄存器纠缠,那么对它的释放就会破坏其他量子的状态,因此,我们认定一个量子比特被释放时要回到最初的状态。 175 | 176 | 最后,对于双量子系统来说,构建一个通用的量子门集合需要向集合中添加新的门,但对多量子系统来说这是不必要的。H、T和CNOT门就构成了多量子系统的一个通用门集合,因为任何一个幺正变换都可以分解为一系列的双量子的旋转。因此当面对多量子系统时,我们可以继续应用双量子系统中的相关理论。 177 | 178 | 目前我们都是使用线性代数中的符号来描述多量子系统,但随着量子数量的增多,这些表述将会变得非常繁琐,例如对于一个7位长的位串,其对应的量子态向量的维数就达到了128,因此,接下来我们将引入一个新的符号体系来描述量子系统,其能精确表达量子状态,同时使用起来非常便捷。 179 | 180 | ### 狄拉克符号 181 | 182 | 在线性代数中,列向量符号是无处不在的,但在量子计算中,特别是处理多量子系统时,向量符号就显得笨拙而繁琐。例如,我们定义两个向量$$\psi$$和$$\phi$$,但是我们并不知道它们是列向量还是行向量,也不知道它们的大小分别是多少。除了向量的形态,使用向量符号表示哪怕是非常简单的量子态也是一件繁琐的工作。比如,我们想要表达一个$$n$$量子比特的系统状态,这些量子比特处于$$0$$状态,那么正规的表示形式为: 183 | $$\begin{bmatrix}1 \\ 0 \end{bmatrix}\otimes \cdots \otimes\begin{bmatrix}1 \\ 0 \end{bmatrix}. $$ 184 | 185 | 当然,对这个张量积求值是不切实际的,因为向量都位于指数级别增长的空间中。 186 | 187 | 狄拉克符号是一种新的能够满足量子计算精确需求的语言,因此我们建议读者不要把本节中的例子看作是描述量子态的严格公式,而是鼓励读者把这些看作是可以用来简明表达量子思想的建议。 188 | 189 | 在狄拉克符号中有两种向量:左矢(Bra vector)和右失(Ket vector),之所以这样命名,是因为将两个向量放在一起时构成一个“braket”(狄拉克符号也叫braket符号),也就是向量内积内积。如果$$\psi$$是一个列向量,我们可以用狄拉克符号$$|\psi \rangle$$来表示,此处$$|\cdot \rangle$$表示一个单位列向量,比如右失就是这样的向量,类似的行向量$$\psi^\dagger$$可以表示为$$\langle \psi |$$,而符号$$\langle \psi |\psi \rangle$$就表示了向量$$\psi$$与其自身的内积,根据定义其值为$$1$$。 190 | 191 | 更一般的,如果$$\psi$$和$$\phi$$是量子态向量,他们的内积为:$$\langle \phi | \psi \rangle$$,并且测量量子态$$|{\psi}\rangle$$结果为$$|{\phi}\rangle$$的概率为$$|\langle \phi|\psi\rangle|^2$$。 192 | 193 | 下面所示是量子态$$0$$和$$1$$与狄拉克符号之间的关系: 194 | $$ \begin{bmatrix} 1 \\ 0 \end{bmatrix} = |0\rangle,\qquad \begin{bmatrix} 0 \\ 1 \end{bmatrix} = |1\rangle. $$ 195 | 196 | 下面的符号通常用来描述对$$|0\rangle$$和$$|1\rangle$$应用Hadamard门所产生的状态(对应于布罗兹球面中的$$+x$$和$$-x$$方向): 197 | $$ \frac{1}{\sqrt{2}}\begin{bmatrix} 1 \\ 1 \end{bmatrix}=H|0\rangle = |{+}\rangle,\qquad \frac{1}{\sqrt{2}}\begin{bmatrix} 1 \\ -1 \end{bmatrix} =H|1\rangle = |{-}\rangle . $$ 198 | 199 | 这些状态也可以使用狄拉克符号来表示: 200 | $$ |+\rangle = \frac{1}{\sqrt{2}}(|0\rangle + |1\rangle),\qquad |-\rangle = \frac{1}{\sqrt{2}}(|0\rangle - |1\rangle). $$ 201 | 202 | 从上面的关系可以明白为什么这些状态被称为“计算基底”,因为任何一个量子态都能通过这些基底的代数和来表示,并且这些代数和形式能方便的使用狄拉克符号来表达。上面的转换关系反过来也是成立的,从下面的关系中可以看出,$$|+\rangle$$和$$|-\rangle$$构成了量子态基底。 203 | 204 | 作为一个使用狄拉克符号的例子,考虑$$0$$和$$1$$的内积,其可以使用狄拉克符号表示为: 205 | $$ |0\rangle = \frac{1}{\sqrt{2}}(|+\rangle + |-\rangle),\qquad |1\rangle = \frac{1}{\sqrt{2}}(|+\rangle - |-\rangle). $$ 206 | 207 | 上面的例子说明了$$|0\rangle$$和$$|1\rangle$$是正交向量,即$$\langle 0 |1\rangle = \langle 1 | 0\rangle =0$$,同时,根据定义可以得到:$$\langle 0 | 0 \rangle = \langle 1 | 1\rangle=1$$,说明两个计算基底是标准正交的。在下面的例子中,可以看到标准正交性质的作用,假设存在状态$$|\psi\rangle = {\frac{3}{5}} |1\rangle + {\frac{4}{5}} |0\rangle$$,因为$$\langle 1 | 0\rangle =0$$,测量结果为1的概率为: 208 | $$\big|\langle 1 | \psi\rangle \big|^2= \left|\frac{3}{5}\langle 1 | 1\rangle +\frac{4}{5}\langle 1 |0 \rangle\right|^2=\frac{9}{25}.$$ 209 | 210 | 狄拉克符号还包含一个隐式的张量积结构。这是非常重要的,因为在量子计算中,使用两个不相关的量子寄存器描述的量子态向量是这两个不相关量子态的张量积。如果你想解释量子计算,那么能够简明描述张量积结构是非常重要的,狄拉克符号体系中,两个向量$$\psi$$和$$\phi$$的张量积$$\psi \otimes \phi$$可以写为$$|\psi\rangle |\phi\rangle$$,有时也写作:$$|\psi\rangle \otimes |\phi\rangle$$,例如: 211 | $$ \begin{bmatrix} 1 \\ 0 \\ 0 \\ 0 \end{bmatrix}= \begin{bmatrix} 1 \\ 0 \end{bmatrix} \otimes \begin{bmatrix} 1 \\ 0 \end{bmatrix} = |0\rangle \otimes |0\rangle= |0\rangle |0\rangle. $$ 212 | 213 | 类似地,状态$$|p \rangle$$表示一个使用整数$$p$$的二进制编码表示的量子态,例如,我们想使用二进制编码来表示整数5,那么下面的这几个表示是等同的: 214 | $$ |{1}\rangle|{0}\rangle|{1}\rangle = |{101}\rangle = |{5}\rangle. $$ 215 | 216 | 在上面的等式中,$$|0\rangle$$不必是一个量子比特,而可以是一个存储了状态为$$0$$的量子比特的寄存器,这之间的区别可以根据上下文的环境进行区分,同时,这种转换在简化某些表达上是很有用处的,例如本节中的第一个例子就可以写作: 217 | $$ \begin{bmatrix}1 \\ 0 \end{bmatrix}\otimes \cdots \otimes\begin{bmatrix}1 \\ 0 \end{bmatrix} = |0\rangle \otimes \cdots \otimes |0\rangle= |0\cdots 0\rangle = |0\rangle^{\otimes n} = |0\rangle. $$ 218 | 219 | 另一个使用狄拉克符号来表示量子态的例子如下所示,在每一个长度为$$n$$的位串上写入一个相等的叠加态: 220 | $$H^{\otimes n} |{0}\rangle = \frac{1}{2^{n/2}} \sum_{j=0}^{2^n-1} |{j}\rangle=|+\rangle^{\otimes n}.$$ 221 | 222 | 这里你可能会想知道为什么对于$$n$$个比特,加和的范围是$$0$$到$$2^{n}-1$$。首先,注意对n个比特,可能的构型有$$2^{n}$$种,因为每个比特都有两个可选的值。同时要说明的是,在这个例子中,我们没有像$$|0\rangle^{\otimes n} = |0\rangle$$一样将$$|+\rangle^{\otimes n}$$简化为$$|+\rangle^{\otimes n}=|+\rangle$$,因为这个转换惯例通常是保留给每个初始状态为$$0$$的计算基底的,虽然$$|+\rangle^{\otimes n}=|+\rangle$$这样表达也是有意义的,但是并没有被引入量子计算的书面表达中。 223 | 224 | 除了简明的优点,狄拉克符号的另一个优点就是其是线性的。例如,对于下面向量的张量积,我们可以写为: 225 | $$(\alpha |\psi\rangle +\beta|\phi\rangle)\otimes (\gamma |\chi\rangle + \delta |\omega\rangle)= \alpha\gamma |\psi\rangle|\chi\rangle + \alpha\delta |\psi\rangle|\omega\rangle+\beta\gamma|\phi\rangle|\chi\rangle+\beta\omega|\phi\rangle|\omega\rangle.$$ 226 | 227 | 从上面的例子可以看出,将张量积用狄拉克符号表示时,就像是普通的乘法一样简单明了。 228 | 229 | 左失遵循与右失类似的惯例。例如,向量$$\langle\psi|\langle \phi|$$与状态向量$$\psi^\dagger \otimes \phi^\dagger=(\psi\otimes \phi)^\dagger$$相等。如果右失$$|\psi\rangle$$是$$\alpha |0\rangle + \beta |1\rangle$$,那么对应的左失就是$$\langle{\psi}|=|\psi\rangle^\dagger = (\langle 0|\alpha^* +\langle 1 |\beta^*)$$。举一个例子,假设我们要计算使用一个量子测量程序测量$$|\psi\rangle = \frac{3}{5} |1\rangle + \frac{4}{5} |0\rangle$$的状态为$$|+\rangle$$或$$|-\rangle$$的概率,那么所得的概率为: 230 | $$|\langle - |\psi\rangle|^2= \left|\frac{1}{\sqrt{2}}(\langle 0| - \langle 1|)(\frac{3}{5} |1\rangle + \frac{4}{5} |0\rangle) \right|^2=\left|-\frac{3}{5\sqrt{2}} + \frac{4}{5\sqrt{2}}\right|^2=\frac{1}{50}.$$ 231 | 232 | 在概率计算中出现负号是量子干涉的一种表现形式,量子干涉也是量子计算之所以优于传统计算的机制之一。 233 | 234 | 狄拉克符号体系中,最后一项值得被讨论的优点是*ketbra*,也就是外积。用狄拉克符号表示外积的形式为$$|\psi\rangle \langle \phi|$$,它也被叫做*ketbra*,可以看到它与内积*braket*中的符号是相反的。对于两个量子态向量$$\psi$$和$$\phi$$,它们的外积是通过矩阵乘法定义的:$$|\psi\rangle \langle \phi| = \psi \phi^\dagger$$,一个很简单的关于外积的例子如下所示: 235 | $$|0\rangle \langle 0| = \begin{bmatrix}1\\ 0 \end{bmatrix}\begin{bmatrix}1&0 \end{bmatrix}= \begin{bmatrix}1 &0\\ 0 &0\end{bmatrix} \qquad |1\rangle \langle 1| = \begin{bmatrix}0\\ 1 \end{bmatrix}\begin{bmatrix}0&1 \end{bmatrix}= \begin{bmatrix}0 &0\\ 0 &1\end{bmatrix}.$$ 236 | 237 | 外积也被称为投影,因为他们将一个量子态向量投影到了一个固定值上。因为这些操作不是幺正的(也不会保持一个向量的范数),那么毫无疑问,量子计算机不能确定地进行投影操作,然而,投影能很好地描述测量操作对量子比特的作用,例如我们测量一个状态为$$\psi$$的量子得到的结果为$$0$$,那么作为测量结果的状态所经历的变换为: 238 | $$|\psi \rangle \rightarrow \frac{(|0\rangle \langle 0|)|\psi\rangle}{|\langle 0|\psi\rangle|}= |0\rangle,$$ 239 | 240 | 上面的结果是测量一个量子所得结果为$$|0\rangle$$的期望。重申一下,这样的投影不能确定地作用在量子计算机中,相反,他们至多以一定的概率随机与量子态$$|0\rangle$$发生作用。这种测量结果的概率可以写成量子态中量子投影的期望值: 241 | $$\langle \psi| (|0\rangle \langle 0|)|\psi\rangle = |\langle \psi|0\rangle|^2,$$ 242 | 这也说明了投影给出了一个新的测量量子状态的方法。 243 | 244 | 假设我们想要测量一个多量子系统中第一个量子比特的状态,那么可以只用投影和狄拉克符号方便地表示为: 245 | $$P(\text{first qubit = 1})= \langle\psi|\left(|1\rangle\langle{1}|\otimes \mathbf{1}^{\otimes n-1}\right) |\psi\rangle.$$ 246 | 247 | 这里的单位矩阵可以使用狄拉克符号表示为: 248 | $$\mathbf{1}= |0\rangle \langle 0|+|1\rangle \langle 1|= \begin{bmatrix}1&0\\ 0&1 \end{bmatrix}.$$ 249 | 250 | 对于双量子系统,投影可以被扩展为: 251 | $$|1\rangle \langle 1| \otimes \mathbf{1} = |1\rangle\langle 1 \otimes (|0\rangle \langle 0|+|1\rangle \langle 1|)= |10\rangle\langle 10| + |11\rangle\langle 11|.$$ 252 | 253 | 可以看出这与我们对使用列向量符号描述多量子系统状态测量结果的可能性的讨论是一致的: 254 | $$P(\text{first qubit = 1})= \psi^\dagger (e_{10}e_{10}^\dagger + e_{11}e_{11}^\dagger)\psi = |e_{10}^\dagger \psi|^2 + |e_{11}^\dagger \psi|^2,$$ 255 | 上面的式子与多量子状态测量的探讨结果是一致的。然而,对于多量子系统,上述结果的推广用狄拉克符号表示比使用列向量符号更直接。 256 | 257 | 在狄拉克符号体系中,其他有用的操作符还有状态操作符。对于一个量子态向量来说,状态操作符的形式为:$$\rho = |\psi\rangle \langle \psi|$$,这个将量子态描述为矩阵而非向量的方法是很方便的,因为它给出了一个简便的表示概率计算的方法,同时也允许我们使用相同的形式既可以表示统计学上的不确定性,也可以表达量子计算的不确定性。通用的量子操作符(非向量)在量子计算的某些领域中是普遍存在的,但理解该领域的基础知识并不是必须的,对于感兴趣的读者可以在[这里](https://docs.microsoft.com/en-us/quantum/quantum-formoreinfo?view=qsharp-preview)找到更多信息。 258 | 259 | 在本文开始时,量子态是量子计算中的基本对象,但是Q#中并没有直接表示量子态的符号,相反,所有的状态都通过对量子比特的筹备操作来描述。前面的例子对此是一个很好的说明,对于在寄存器中的每一个量子位串,表达对它们进行平均叠加操作,我们可以将结果表述为:$$H^{\otimes n} |{0}\rangle$$,这个简短的指数式表达不仅使我们可以方便的解释它,同时它也定义了实现一个算法需要的通过软件栈来传播的操作。因此,Q#被设计为触发量子门序列而不是量子态,当然,理论层面上来讲,它们是等价的。 260 | 261 | ### 泡利测量 262 | 263 | 在前面的讨论中,我们重点关注了计算基底测量,实际上在量子计算中还有其他一些常用的测量方式,从符号学的观点来说,这些测量使用计算基底的方式表达起来也很方便,在这些方式之中,泡利测量最常用。 264 | 265 | 在深入研究抛离测量之前,考虑一下在量子计算机中,测量一个量子比特时到底发生了什么?想象一下我们有一个$$n$$比特量子态,它所处于的状态可能有$$2^{n}$$中,然后测量其中一个量子比特,此时该多量子态的状态就被排除了一半,也就是说,测量操作将量子态投影到了两个半空间中的一个。 266 | 267 | 为了简洁地确认这些子空间,我们需要一个描述它们的语言。一个方法是使用一个只有两个特征值的矩阵来区分它们,这两个特征值一般取$$\pm 1$$,最简单的例子就是: 268 | $$Z= \begin{bmatrix}1&0\\ 0&-1\end{bmatrix}. $$ 269 | 270 | Pauli-Z矩阵有两个特征向量$$|0\rangle$$和$$|1\rangle$$分别对应特征值$$\pm 1$$。如果测量一个量子比特得到的结果为$$|0\rangle$$那么我们就处于Z的特征值为$$+1$$的特征空间中,想法如果测量得到了$$|1\rangle$$,那么就处于Z的特征值为$$-1$$的特征空间中。这样的处理对应于泡利测量语言中的“测量泡利Z(measuring Pauli Z)”并且与执行计算基底测量是等价的。 271 | 272 | 当然,任何一个$$2 \times 2$$大小的Z的幺正变换都符合上面的标准。这也就是说,对于任意幺正矩阵$$U$$,我们可以使用矩阵$$A=U^\dagger Z U$$来定义与其特征空间相对应的测量的输出。泡利测量中使用符号$$X$$、$$Y$$和$$Z$$表示这一过程,如下所示: 273 | $$ 274 | \begin{array}{|c|c|} 275 | \text{Pauli Measurement} ; U\\ 276 | Z ; \mathbf{1}\\ 277 | X ; H\\ 278 | Y ; HS^\dagger\\ 279 | \end{array} 280 | $$ 281 | 282 | 同样,使用相同的语言,"measure Y"与先应用$$HS^\dagger$$然后在计算基底上测量是等价的,其中$$S$$也叫做相位门: 283 | $$ 284 | \begin{bmatrix}1 ;0\\ 0;i\end{bmatrix}. 285 | $$ 286 | 287 | 对量子态向量应用$$HS$\dagger$$然后测量$$Z$$也是很方便的,正确的状态可以在这之后应用$$SH$$获得。 288 | 289 | 多量子泡利测量操作符的定义是类似的,如下所示: 290 | $$ 291 | Z\otimes Z = \begin{bmatrix}1 ;0 ;0;0\\ 0;-1;0;0\\ 0;0;-1;0\\ 0;0;0;1\end{bmatrix}. 292 | $$ 293 | 294 | 因此,两个Pauli-Z操作符的张量积构成了一个由两个空间构成的矩阵,而这两个空间的特征值为$$+1$$和$$-1$$。与单量子时相同,一半的向量空间属于特征值为$$+1$$的特征空间,另一半属于特征值为$$-1$$的向量空间。从张量积的定义中可以看出Pauli-Z操作符与单位矩阵的张量积也遵循这个规则,例如: 295 | $$ 296 | Z\otimes\mathbf{1}=\begin{bmatrix} 1;0;0;0\\ 0;1;0;0\\ 0;0;-1;0\\ 0;0;0;-1\end{bmatrix}. 297 | $$ 298 | 在之前,任意一个该矩阵的幺正变换也都描述了一个特征值为$$\pm 1$$的两个半空间。类似于单量子系统,对于$$4 \times 4$$大小的幺正矩阵,所有双量子泡利测量都能被写成$$U^\dagger (Z\otimes \mathbf{1}) U$$的形式。在下面的列表中,我们列举了为了方便交换门而引入的变换,所谓交换门就是交换量子$$1$$和量子$$0$$的一种操作门:$$\operatorname{SWAP}=\operatorname{CNOT}_{01}\operatorname{CNOT}_{10}\operatorname{CNOT}_{01}$$ 299 | $$ 300 | \begin{array}{|c|c|} 301 | \text{Pauli Measurement} ; U\\ 302 | \hline 303 | Z\otimes \mathbf{1} ; \mathbf{1}\otimes \mathbf{1}\\ 304 | X\otimes \mathbf{1} ; H\otimes \mathbf{1}\\ 305 | Y\otimes \mathbf{1} ; HS^\dagger\otimes \mathbf{1}\\ 306 | \mathbf{1} \otimes Z ; \operatorname{SWAP}\\ 307 | \mathbf{1} \otimes X ; (H\otimes \mathbf{1})\operatorname{SWAP}\\ 308 | \mathbf{1} \otimes Y ; (HS^\dagger\otimes \mathbf{1})\operatorname{SWAP}\\ 309 | Z\otimes Z ; \operatorname{CNOT}_{10}\\ 310 | X\otimes Z ; \operatorname{CNOT}_{10}(H\otimes \mathbf{1})\\ 311 | Y\otimes Z ; \operatorname{CNOT}_{10}(HS^\dagger\otimes \mathbf{1})\\ 312 | Z\otimes X ; \operatorname{CNOT}_{10}(\mathbf{1}\otimes H)\\ 313 | X\otimes X ; \operatorname{CNOT}_{10}(H\otimes H)\\ 314 | Y\otimes X ; \operatorname{CNOT}_{10}(HS^\dagger\otimes H)\\ 315 | Z\otimes Y ; \operatorname{CNOT}_{10}(\mathbf{1} \otimes HS^\dagger)\\ 316 | X\otimes Y ; \operatorname{CNOT}_{10}(H\otimes HS^\dagger)\\ 317 | Y\otimes Y ; \operatorname{CNOT}_{10}(HS^\dagger\otimes HS^\dagger)\\ 318 | \end{array} 319 | $$ 320 | 321 | 额外需要注意的是,我们可能会将测量$$Z\otimes Z$$与先测量$$Z \otimes \mathbf{1}$$然后再测量$$mathbf{1} \otimes Z$$看做是相同的,而实际上这并不一定是正确的。原因在于测量$$Z \otimes Z$$将量子态映射到特征值为$$+1$$或$$-1$$的特征空间中,而先测量$$Z \otimes \mathbf{1}$$将量子态先映射到$$Z \otimes \mathbf{1}$$对应的半空间中,然后测量$$mathbf{1} \otimes Z$$又将量子态映射到$$\mathbf{1} \otimes Z$$所对应的半空间中。执行这两个计算时有四个计算基底向量,导致量子态会被映射如四分之一空间中,这与直接测量$$Z \otimes Z$$是不对应的。 322 | 323 | 另一种理解泡利测量张量积(比如$$X \otimes X$$或者$$Z \otimes Z$$)在于,这些测量能让你看到存储在两个量子联系中的信息。测量$$X \otimes \mathbf{1}$$能让你看到局部地存储在第一个量子中的信息。虽然这些测量方法在量子计算中有同样的价值,但前一个体现了量子计算的能力,它揭示了在量子计算中,你想要了解的信息往往不会仅存储在单一的量子比特中,而是同时非局部地存储在所有量子中,并且只有通过与$$Z \otimes Z$$联合测量,这些信息才会显现。 324 | 325 | 任意的泡利测量符都能被测量,例如$$X \otimes Y \otimes Z \otimes \mathbf{1}$$。所有泡利操作符的张量积都只有两个特征值$$\pm 1$$,两个特征空间也构成了整个向量空间的半空间。 326 | 327 | 在Q#中,如果测量在$$(-1)^j$$所对应的特征空间产生了结果,那么测量的输出就为$$j$$。因为测量泡利操作符需要一个很长的受控的非门组成的测量链,并且描述对角化的$$U$$门的变换需要将其表达为$$Z$$和$$\mathbf{1}$$的张量积,因此这个特性是很有帮助的,Q#会帮你解决所有的基础变换。 328 | 329 | #### 不可克隆定理 330 | 331 | 量子计算有着强大的能力,它能是我们解决想大数因式分解这样的难题,以及高效地模拟相关电子系统。然而,它的能力在某些情况下也会收到限制,其中之一便是不可克隆定理。 332 | 333 | 如其名字所示,不可克隆定理指的是不能够克隆量子计算机的通用量子态。这个原理的证明是非常直观明了的,在这里我们以没有附属量子的量子计算机为例对其进行说明。对于这样一个量子计算机,我们禁止测量操作,因为这会破坏我们需要克隆的量子态,克隆操作一定是一个幺正矩阵,且对于任何量子太$$\psi$$,这个矩阵有如下性质: 334 | $$ 335 | U|\psi\rangle|0\rangle=|\psi\rangle|\psi\rangle, 336 | $$ 337 | 338 | 根据矩阵乘法的线性性质,可以推断出对于任何量子次二态$$|\phi\rangle$$,有如下性质: 339 | $$\begin{align} 340 | ;U\left[\frac{1}{\sqrt{2}}\left(|\phi\rangle+|\psi\rangle \right)\right]=\frac{1}{\sqrt{2}}\left(|\phi\rangle|\phi\rangle+|\psi\rangle|\psi\rangle\right)\\ 341 | ;\qquad\qquad\ne \left(\frac{1}{\sqrt{2}}\left(|\phi\rangle+|\psi\rangle \right)\right)\otimes\left(\frac{1}{\sqrt{2}}\left(|\phi\rangle+|\psi\rangle \right)\right). 342 | \end{align}$$ 343 | 344 | 这给出了不可克隆原理背后的基本认识:任何复制未知状态量子态的设备都会对其所复制的量子产生一些影响。对于完整的证明,请参考[这个网址](https://docs.microsoft.com/en-us/quantum/quantum-formoreinfo?view=qsharp-preview)。 345 | 346 | 不可克隆原理对于定性地理解量子计算是很重要的,如果你能够毫不费力地克隆量子态,那么你将获得从量子态中学习的近乎神奇的能力,但这样的话,就违背了海森堡的不确定性原理,或者你可以使用一个优化的克隆方法从复杂的量子分布中获得一个单独的采样,并从这个单独的采样中对这个量子系统分布进行所有可能的了解,这就类似于你抛出一个硬币,并观察正面,然后告诉你的朋友结果并让你的朋友做出“硬币一定符合伯努利分布并且$$p = 0.512643$$,这个结果意义并不大,因为对于多比特的系统而言,仅仅一个比特所能提供的信息在没有实质性的先验信息之前,远远不能给出多比特系统的信息。简单地说,没有先验信息,我们就不能完美地克隆一个量子态。 347 | 348 | 量子计算中,信息不是免费的,每一个被测量的量子给出了单个比特的信息,而不可克隆定理表明,没有后门能绕过系统信息获取和因而产生的干扰这两个之间的矛盾。 349 | 350 | ### 量子线路 351 | 352 | 考虑幺正变换$$\text{ CNOT}_{01}(H\otimes 1)$$。这个门序列对于量子计算有着基础的重要意义,因为它创造了最大纠缠的双量子态: 353 | $$\mathrm{CNOT}_{01}(H\otimes 1)|00\rangle = \frac{1}{\sqrt{2}} \left(|00\rangle + |11\rangle \right),$$ 354 | 同样复杂或比这个更复杂的操作在量子算法和量子纠错中中无处不在,所以为了更直观的表达它们,我们使用”量子线路图“。上面的最大纠缠量子态对应的线路图为: 355 | ![](./Image/MaxTangleQuantumCircut.png) 356 | 357 | 这种针对量子操作的可视化的语言,只要你理解用它表示量子操作的规则,那么它比使用矩阵更加容易理解和消化。下面我们来看一下量子线路图的规则。 358 | 359 | 在一张线路图中,每一条实线表示一个量子比特或者量子比特寄存器,根据惯例,顶部的实线用$$0$$标记,其他实线根据顺序进行标号。上面的例子显示了对两个量子比特的操作。 360 | 361 | 对一个或多个量子操作的门使用方框来表示。例如,下面的符号 362 | ![](./Image/HGate.png) 363 | 就是操作一个量子比特的H门。 364 | 365 | 量子门根据时间顺序多量子进行处理,最左侧的量子门首先作用于量子,也就是说下面的线路图: 366 | ![](./Image/ABC.png) 367 | 代表了幺正矩阵$$CBA$$。矩阵乘法遵循相反的规则:最右侧的矩阵首先作用。而量子线路图中最左侧的量子门首先作用,这种差别具有一定的迷惑性,使用时要特别注意。 368 | 369 | 前面的例子中,一个量子门的输入直线的数量与输出直线的数量都是相等的,也许你会想如果两者不等也是正确的,但是在量子计算中,这种情况不可能发生。因为所有量子操作,除了测量,都是幺正的,因此也都是可逆的,如果输出的数量与输入不相等,那么就产生了矛盾。因此,量子两路中,所有的方框也就是量子门操作都必须有相同的输入输出数量。 370 | 371 | 多量子系统遵循与单量子类似的规则。下面的例子中,我们定义了一个双量子幺正矩阵$$B = HS \otimes X$$: 372 | ![](./Image/B.png) 373 | 根据上下文背景,我们可以将$$B$$看做一个对双量子系统产生作用的操作,也可以将其看做对两个单量子比特产生作用的操作。这种抽象的量子线路图最有用的性质就是能将复杂繁琐的基础门操作描述成为更高级别的抽象操作,这也让你在不用理解算法中的每一个细节操作而对量子算法的流程有一个清晰直观的认识。 374 | 375 | 另一个构建在量子线路图中的是控制结构。一个量子单控门$$\Lambda(G)$$,表示单量子的值控制$$G$$的应用。以输入量子态积$$\Lambda(G) (\alpha |0\rangle + \beta |1\rangle) |\psi\rangle = \alpha |0\rangle |\psi\rangle + \beta |1\rangle G|\psi \rangle$$来解释,例子中,受控量子门只有在控制比特值为$$1$$时,才对存储量子比特$$\psi$$的寄存器应用$$G$$操作,使用量子线路图表示为: 376 | ![](./Image/ControlledGateG.png) 377 | 378 | 图中黑色圆点量子门的控制量子,垂直的直线表示控制量子为$$1$$时应用到量子比特上的幺正变换。对于两种特殊的情况$$G = X$$和$$G = Z$$,我们引入了下面两种符号表示$$G$$门的受控版本(注意受控的X门就是CNOT门): 379 | ![](./Image/XZ.png) 380 | 381 | 测量操作在量子线路图中对应的符号如下所示: 382 | ![](./Image/Measure.png) 383 | 384 | 它测量一个量子寄存器,然后输出测量结果。 385 | 386 | 类似的还有子线路: 387 | ![](./Image/ControlledG) 388 | 它表示一个受经典信息控制控的量子门,只有在经典控制比特的值为$$1$$时,$$G$$门才会被应用与量子比特。 389 | 390 | 量子隐形传态也许是说明上面这些组件最好的量子算法示例了。量子隐形传态是在量子计算机中或在由量子网络连接的远距离主机之间利用量子纠缠和测量来移动数据的方法。有趣的是,在不知道量子比特值的情况下,却能够移动一个量子态。这对根据量子力学的原理进行工作的协议是必须的。下面我们给出了量子隐形传态的线路图和注释来说明如何读取量子线路。 391 | ![](./Image/Teleportation.png) 392 | 393 | -------------------------------------------------------------------------------- /backup/chapter1.md: -------------------------------------------------------------------------------- 1 | # 第一章 快速入门 2 | 3 | 微软的Q\#语言是专门为量子编程打造的一款利器,它包含了开发量子程序所需要的各种工具。如果你有一定的Visual Studio使用经验,那么无论你是初学者还是经验丰富的研究人员,Q\#语言都能让你快速、高效地开发量子计算程序。 4 | 5 | 在这一章中,我们将学习如何安装和搭建Q\#语言的开发环境,并通过一个具体的程序来展示Q\#如何实现一个量子程序以及Q\#语言的一些结构和特点,同时也会讲解Q\#语言如何模拟量子计算机中的量子门完成一些量子计算任务,比如量子叠加、量子纠缠等。 6 | 7 | ## 1.1 安装Q\#开发环境 8 | 9 | #### 1.1.1 安装前的准备 10 | 11 | * Q\#语言的模拟器应用了高级矢量扩展指令集(AVX),Intel系列CPU中 `Sandy Bridge` 架构和之后的CPU支持这种指令集,因此要使用Q\#语言请确保你的CPU支持AVX指令集,微软也将在未来推出适用于早期CPU架构的Q\#语言开发工具。 12 | * Q\#语言必须运行在64为Windows操作系统上。 13 | * 使用Visual Studio 2017进行开发 14 | 15 | 如果没有安装Visual Studio 2017,可以通过以下步骤安装免费的 Community版Visual Studio 2017: 16 | 17 | 1. 访问[Visual Studio下载页面](https://www.visualstudio.com/downloads/) ,并选择Community版本进行下载(如果有钱也可以下载另外的版本) 18 | 2. 下载完毕后双击安装文件 19 | 3. **注意**:安装开始前程序会让你选择特定的开发环境和工具,记得一定要选上 `Universal Windows Platform development`** 和 **`.NET desktop development` 20 | 4. 在选择好需要的环境和工具后点击“安装”即可。 21 | 22 | #### 1.1.2 构建Q\#语言开发环境 23 | 24 | 使用Q\#语言需要安装Q\#语言的开发工具包,安装过程也是很简单的。 25 | 26 | 1. 访问[微软Q\#语言主页](https://www.microsoft.com/en-us/quantum/development-kit),点击左上方的 "Download Now" 按钮,此时页面将跳转至Q\#开发工具包的下载界面 27 | 2. 在下载界面的右侧,填写一些必要的信息,包括你的名字、联系方式等等,之后点击右下角的 "Download Now" 按钮,就会开始下载开发工具包。工具包是一个vsix格式的文件,大小只有1M左右。 28 | 3. 双击下载好的文件,稍等片刻Q\#语言的开发工具就安装在了Visual Studio2017中。 29 | 30 | #### 1.1.3 验证刚刚安装的开发环境 31 | 32 | 我们使用微软为我们提供的一些例子和库对刚刚安装的环境进行检测,以验证我们是否正确安装好了Q\#语言的开发环境。 33 | 34 | 1. 克隆GitHub上微软官方提供的[Q\#语言例程](https://github.com/microsoft/quantum) 35 | 2. 使用Visual Studio打开 `QsharpLibraries.sln` 36 | 1. 如果此时弹出 "**Install Missing Features" **的面板,点击“安装” 37 | 38 | 3. 选择 Teleport 示例程序并运行,如果出现了类似下面这样的输出,那么说明我们的Q\#语言开发环境安装正确,你可以开始在量子世界里的挑战了! 39 | 40 | ``` 41 | Round 0: Sent True, got True. 42 | Teleportation successful!! 43 | Round 1: Sent False, got False. 44 | Teleportation successful!! 45 | ... 46 | Round 6: Sent True, got True. 47 | Teleportation successful!! 48 | Round 7: Sent False, got False. 49 | Teleportation successful!! 50 | ``` 51 | 52 | **如果出现了与NuGet有关的错误,使用**[**NuGet package restore**](https://docs.microsoft.com/en-us/nuget/consume-packages/package-restore)**中的方法重置安装包即可.** 53 | 54 | ### 1.2 第一个Q\#语言程序 55 | 56 | 这一节的主要内容包括: 57 | 58 | * 在Visual Studio中设置Q\#语言工程和解决方案 59 | * Q\#语言中一个`operation`的组成 60 | * 如何从C\#中调用Q\#语言的 `operation` 61 | * 如何构建和运行Q\#程序 62 | 63 | #### 1.2.1 在Q\#中创建一个贝尔态(Bell State) 64 | 65 | 现在,我们已经安装好了Q\#语言的开发环境。作为开始,我们将构建一个非常简单的程序来展示量子叠加和量子纠缠。在这个程序中,我们以一个初始态为`|0>`的量子比特为起点,对其进行一些操作,并测量其状态。 66 | 67 | ##### 1. 创建工程和解决方案 68 | 69 | 打开Visual Studio 2017,依次点击“文件-->新建-->项目”,在出现的新建项目对话框中,选中左侧栏目中的“`Visual C#`”,此时在对话框中部会出现许多条目,找到“`Q# Application`”并选中,设置项目名称为`Bell`,如下图所示。如果没有找到“`Q# Application`”的条目,检查对话框上部列表选择框中是否选中了“`.NET Framework 4.6.1`”。![](/Image/NewQSharpProject.png) 70 | 71 | ##### 2. 输入Q\#代码 72 | 73 | 在完成工程创建后,此时会有两个文件:`Driver.cs`和`Operation.qs`,前者是驱动Q\#程序的C\#代码,后者才是真正的Q\#代码文件。 74 | 75 | 首先将`Operation.qs`文件重命名为`Bell.qs`,Visual Studio在创建工程时会自动生成一部分代码,此时`Bell.qs`中的内容类似于这样: 76 | 77 | ``` 78 | namespace Quantum.Bell 79 | { 80 | open Microsoft.Quantum.Primitive; 81 | open Microsoft.Quantum.Canon; 82 | 83 | operation Operation () : () 84 | { 85 | body 86 | { 87 | } 88 | } 89 | } 90 | ``` 91 | 92 | 然后将代码中的`Operation`改为`Set`,并且将其后面第一个括号中的内容改为`desired: Result, q1:Qubit`,此时`Bell.qs`中的内容应为: 93 | 94 | ``` 95 | namespace Quantum.Bell 96 | { 97 | open Microsoft.Quantum.Primitive; 98 | open Microsoft.Quantum.Canon; 99 | 100 | operation Set (desired: Result, q1:Qubit) : () 101 | { 102 | body 103 | { 104 | } 105 | } 106 | } 107 | ``` 108 | 109 | 现在,将以下代码键入`body`后的大括号中: 110 | 111 | ``` 112 | let current = M(q1); 113 | 114 | if (desired != current) 115 | { 116 | X(q1); 117 | } 118 | ``` 119 | 120 | `Bell.qs`中的代码为: 121 | 122 | ``` 123 | namespace Quantum.Bell 124 | { 125 | open Microsoft.Quantum.Primitive; 126 | open Microsoft.Quantum.Canon; 127 | 128 | operation Set (desired: Result, q1:Qubit) : () 129 | { 130 | body 131 | { 132 | //测量量子比特状态 133 | let current = M(q1); 134 | if (desired != current) 135 | { 136 | //翻转 137 | x(q1); 138 | } 139 | } 140 | } 141 | } 142 | ``` 143 | 144 | 以上代码(`operation`)所做的工作就是设置一个已知状态的量子位。我们首先测量量子比特的状态`M(q1)`,如果与我们所期待的状态相符,那么不在进行进一步的操作,否则使用X门将此量子比特翻转。 145 | 146 | 现在我们来解释一下上面的代码。上面的代码中定义了一个Q\#语言中的 `operation`,`operation`是Q\#语言中一个基本的执行单元,它与其他编程语言如C/C++、Python中的函数,C\#和Java中的`static`函数是一样的。一个`operation`的参数是一个元祖,在`operation`名字之后的括号中指定各个参数之间用逗号分隔,`operation`的返回值与其参数形式类似,在之后的括号中指定,两个括号之间由一个冒号分隔,Q\#语言中,`operation`可以指定多个返回值,也可以将括号留空,此时该`operation`不返回任何数据,在这种情况下,一对空的括号“\(\)”就相当于C语言中的void或F\#语言中的`unit`。例如上面代码中,定义了一个名为Set的`operation`,它接受两个参数,没有返回值。一个`operation`中包含一个`body`段,在`body`段中的代码就是该`operation`功能的实现代码,`operation`中还可以包含共轭、伴随以及共轭伴随等部分,关于这些操作的详细内容,我们将在以后进行介绍。 147 | 148 | 有了上面的`operation`,我们现在来编写一个测试程序来调用这个operation并观察结果。在`Bell.qs`中添加如下代码,可以看到这也是一个`operation`,将其添加在`Set`之后。 149 | 150 | ``` 151 | operation BellTest (count : Int, initial : Result) : (Int, Int) 152 | { 153 | body 154 | { 155 | mutable numOnes = 0; 156 | using (qubits = Qubit[1]) 157 | { 158 | for (test in 1..count) 159 | { 160 | Set (initial, qubits[0]); 161 | let res = M (qubits[0]); 162 | // Count the number of ones we saw: 163 | if (res == One) 164 | { 165 | set numOnes = numOnes + 1; 166 | } 167 | } 168 | Set(Zero, qubits[0]); 169 | } 170 | // Return number of times we saw a |0> and number of times we saw a |1> 171 | return (count-numOnes, numOnes); 172 | } 173 | ``` 174 | 175 | 上面的`operation BellTest`中定义了一个循环`count`次的控制结构,在每次循环中,首先将一个量子比特设置为`initial`,然后测量这个量子的状态,如果为1,将变量`numOnes`加一,所有循环结束后,我们将得到在这个过程中共有多少次测量的量子比特状态为1,多少次为0。在最后,我们把量子比特重新设置为了0,使其处于一个特定的状态。 176 | 177 | 从上面的代码中可以看出,Q\#语言使用了与C\#相似的分好、括号等来标示程序的结构,并且Q\#也拥有与C\#类似的`if`控制语句。 178 | 179 | Q\#中只有一种方法来处理变量。默认情况下,Q\#中的变量是不可改变的,当变量已被绑定时,它们的值就不会再改变。Q\#中,关键字let用来只是绑定一个不可变的变量,同时,`operation`中的参数也是不可变的。 180 | 181 | 如果需要使用值可变的变量就需要像上面的程序中那样,使用`mutable`关键字对变量进行声明,经过`mutable`关键字声明的变量,其可以使用`set`语句重新赋值。 182 | 183 | Q\#不是一种强类型的编程语言。在定义一个新的变量时,变量的类型由编译器根据其具体值进行推断。 184 | 185 | `using`语句也是Q\#中特有的,它用于在一个代码段中开辟一个量子比特数组。在Q\#中,所有的量子比特都是动态分配和释放的,`using`语句在代码段的起始处分配量子比特,在代码段结束后自动释放这些量子比特。 186 | 187 | Q\#中的`for`循环在一个范围之内进行迭代,它不同于C语言风格的for循环,当然在Q\#中实际上也不存在类似C语言中的`for`循环结构。`for`循环中, 其范围可以直接由起始和终止的值来指定,两个值之间使用`..`连接。例如`1..10`就表示1,2,3,4,5,6,7,8,9,10,默认步长为1,如果需要不同的步长值,则直接指定即可,其语法为`1..2..10`,这个范围表示的数值就是1,3,5,7,9。需要注意的是,Q\#中的范围是一个闭区间,也就是包含起始值和终止值。 188 | 189 | Q\#中使用元组来传递多个变量,上面的`operation BellTest`的返回值就是由两个`Int`数据`(Int, Int)`组成的元组。 190 | 191 | ##### 3. C\#驱动代码 192 | 193 | 将目光转换到`Driver.cs`文件中,在创建项目时,Visual Studio会自动生成一部分C\#代码,内容如下: 194 | 195 | ``` 196 | using Microsoft.Quantum.Simulation.Core; 197 | using Microsoft.Quantum.Simulation.Simulators; 198 | 199 | namespace Quantum.Bell 200 | { 201 | class Driver 202 | { 203 | static void Main(string[] args) 204 | { 205 | 206 | } 207 | } 208 | } 209 | ``` 210 | 211 | 在`Main`方法中,我们输入以下代码: 212 | 213 | ``` 214 | using (var sim = new QuantumSimulator()) 215 | { 216 | // Try initial values 217 | Result[] initials = new Result[] { Result.Zero, Result.One }; 218 | foreach (Result initial in initials) 219 | { 220 | var res = BellTest.Run(sim, 1000, initial).Result; 221 | var (numZeros, numOnes) = res; 222 | System.Console.WriteLine($"Init:{initial,-4} 0s={numZeros,-4} 1s={numOnes,-4}"); 223 | } 224 | } 225 | System.Console.WriteLine("Press any key to continue..."); 226 | System.Console.ReadKey(); 227 | ``` 228 | 229 | 上面的C\#代码包含4个部分: 230 | 231 | * 构造一个量子模拟器,即代码中的`sim` 232 | * 计算量子算法需要的参数,上面的代码中,BellTest需要的`count`为1000,`initial`是量子比特需要的初始值 233 | * 运行量子算法程序。每一个`Q# operation`都会生成一个名字相同的C\#类,这个类拥有一个`run`方法异步地执行相应的操作,该方法之所以是异步操作,原因在于在实际的硬件设备上相应的操作是异步的,这也造成了另一个结果,那就是我们要获取`operation`的返回值时将会阻塞程序直到程序运行完成并返回结果。 234 | * 处理`operation`返回的结果。在上面的程序中,`res`接受`operation`产生的结果。 235 | 236 | ##### 4. 构建和运行 237 | 238 | 只需点击F5键,程序将会被自动构建和运行。稍后片刻,将会得到如下的结果: 239 | 240 | ``` 241 | Init:Zero 0s=1000 1s=0 242 | Init:One 0s=0 1s=1000 243 | Press any key to continue... 244 | ``` 245 | 246 | ##### 5. 创建量子叠加 247 | 248 | 现在有了上面的结果,是不是迫不及待想要操作一下量子比特了?首先,我们尝试在测量量子比特之前先对其进行翻转,这个操作可以使用`X`门方便地进行,将下面的代码输入`BellTest`中: 249 | 250 | ``` 251 | X(qubits[0]); 252 | let res = M (qubits[0]); 253 | ``` 254 | 255 | 此时`BellTest`代码如下: 256 | 257 | ``` 258 | operation BellTest (count : Int, initial : Result) : (Int, Int) 259 | { 260 | body 261 | { 262 | mutable numOnes = 0; 263 | using (qubits = Qubit[1]) 264 | { 265 | for (test in 1..count) 266 | { 267 | Set (initial, qubits[0]); 268 | X(qubits[0]); 269 | let res = M (qubits[0]); 270 | // Count the number of ones we saw: 271 | if (res == One) 272 | { 273 | set numOnes = numOnes + 1; 274 | } 275 | } 276 | Set(Zero, qubits[0]); 277 | } 278 | // Return number of times we saw a |0> and number of times we saw a |1> 279 | return (count-numOnes, numOnes); 280 | } 281 | ``` 282 | 283 | 现在重新运行程序,得到下面的输出结果: 284 | 285 | ``` 286 | Init:Zero 0s=0 1s=1000 287 | Init:One 0s=1000 1s=0 288 | ``` 289 | 290 | 目前为止,我们通过上面的这些程序得到了一些结果,但这些结果都是经典的而非量子的,要想获得量子的结果,只需要将上面的X门替换为`H`门(Hadamard Gate),相比于之前`X`门对量子比特由0直接翻转为1或由1直接翻转为0,`H`门对量子比特进行半翻转。代码中相应的部分为: 291 | 292 | ``` 293 | H(qubits[0]); 294 | let res = M (qubits[0]); 295 | ``` 296 | 297 | 现在我们得到的结果如下: 298 | 299 | ``` 300 | Init:Zero 0s=484 1s=516 301 | Init:One 0s=522 1s=478 302 | ``` 303 | 304 | 在每次对一个量子比特的测量中,我们希望得到一个经典的值,但量子比特的值处于0和1之间,所以从统计意义来讲,我们有一半概率得到0一半概率得到1,这种现象就是量子叠加,上面的代码和结果也给了我们对于量子状态的初步印象。 305 | 306 | ##### 6. 创建量子纠缠 307 | 308 | 现在我们回到本程序最初要产生的`Bell State`上并创建量子纠缠。首先我们要做的就是分配两个量子比特而非当前程序中的1个。 309 | 310 | ``` 311 | using (qubits = Qubit[2]) 312 | ``` 313 | 314 | 两个量子比特能够使我们在测量量子状态前应用一个新的`CNOT`门: 315 | 316 | ``` 317 | Set (initial, qubits[0]); 318 | Set (Zero, qubits[1]); 319 | 320 | H(qubits[0]); 321 | CNOT(qubits[0],qubits[1]); 322 | let res = M (qubits[0]); 323 | ``` 324 | 325 | 在上面的代码中,我们新增了一个`Set operation`将第一个量子比特初始化为`Zero`。 326 | 327 | 同样我们也需要在第二个量子比特被释放之前将其重置为`Zero`: 328 | 329 | ``` 330 | Set(Zero, qubits[1]); 331 | ``` 332 | 333 | 现在`BellTest`代码如下所示: 334 | 335 | ``` 336 | operation BellTest (count : Int, initial: Result) : (Int,Int) 337 | { 338 | body 339 | { 340 | mutable numOnes = 0; 341 | using (qubits = Qubit[2]) 342 | { 343 | for (test in 1..count) 344 | { 345 | Set (initial, qubits[0]); 346 | Set (Zero, qubits[1]); 347 | 348 | H(qubits[0]); 349 | CNOT(qubits[0],qubits[1]); 350 | let res = M (qubits[0]); 351 | 352 | // Count the number of ones we saw: 353 | if (res == One) 354 | { 355 | set numOnes = numOnes + 1; 356 | } 357 | } 358 | Set(Zero, qubits[0]); 359 | Set(Zero, qubits[1]); 360 | } 361 | // Return number of times we saw a |0> and number of times we saw a |1> 362 | return (count-numOnes, numOnes); 363 | } 364 | } 365 | ``` 366 | 367 | 这份代码将产生与之前代码同样的结果,然而,这里我们真正感兴趣的是在第一个量子比特被测量后,第二个量子比特将会有何反应。为此,我们对`BellTest`进行修改,向其中加入统计相关的代码: 368 | 369 | ``` 370 | operation BellTest (count : Int, initial: Result) : (Int,Int,Int) 371 | { 372 | body 373 | { 374 | mutable numOnes = 0; 375 | mutable agree = 0; 376 | using (qubits = Qubit[2]) 377 | { 378 | for (test in 1..count) 379 | { 380 | Set (initial, qubits[0]); 381 | Set (Zero, qubits[1]); 382 | 383 | H(qubits[0]); 384 | CNOT(qubits[0],qubits[1]); 385 | let res = M (qubits[0]); 386 | 387 | if (M (qubits[1]) == res) 388 | { 389 | set agree = agree + 1; 390 | } 391 | 392 | // Count the number of ones we saw: 393 | if (res == One) 394 | { 395 | set numOnes = numOnes + 1; 396 | } 397 | } 398 | Set(Zero, qubits[0]); 399 | Set(Zero, qubits[1]); 400 | } 401 | // Return number of times we saw a |0> and number of times we saw a |1> 402 | return (count-numOnes, numOnes, agree); 403 | } 404 | } 405 | ``` 406 | 407 | 在上面代码中,我们增加了一个新的返回值`agree`,他将记录第一个量子比特的测量值与第二个量子比特的测量值相等的次数。 408 | 409 | 我们相应的修改`Driver.cs`代码: 410 | 411 | ``` 412 | using (var sim = new QuantumSimulator()) 413 | { 414 | // Try initial values 415 | Result[] initials = new Result[] { Result.Zero, Result.One }; 416 | foreach (Result initial in initials) 417 | { 418 | var res = BellTest.Run(sim, 1000, initial).Result; 419 | var (numZeros, numOnes, agree) = res; 420 | System.Console.WriteLine( 421 | $"Init:{initial,-4} 0s={numZeros,-4} 1s={numOnes,-4} agree={agree,-4}"); 422 | } 423 | } 424 | System.Console.WriteLine("Press any key to continue..."); 425 | System.Console.ReadKey(); 426 | ``` 427 | 428 | 现在我们来看一看运行结果,非常令人惊讶: 429 | 430 | ``` 431 | Init:Zero 0s=499 1s=501 agree=1000 432 | Init:One 0s=490 1s=510 agree=1000 433 | ``` 434 | 435 | 从结果看,对于第一个量子比特而言,统计结果并没有变换(50%的概率为1,50%的概率为0),但是我们对第二个量子比特的测量值总是与第一个量子比特的测量值保持一致。`CNOT`门是两个量子比特产生量子纠缠,所以两个量子比特中任意一个发生变化,另一个也将发生相同的变化,改变两个量子比特的测量顺序所得到的结果也同样如此。也就是说,第一次测量值是随机的,而第二次测量值与第一次相同,同进同退。 436 | 437 | -------------------------------------------------------------------------------- /backup/chapter2.md: -------------------------------------------------------------------------------- 1 | ## 第二章 Q\#编程语言 2 | 3 | 谈到量子计算,一个自然而然的观点是将量子计算机看作一种协处理器,就像如今的GPU、FPGA一样,程序运行时,经典的通用计算机完成基本的逻辑控制,在特定的情况下,调用运行在协处理器上的子程序处理相应的任务,协处理器完成任务后主程序获取子程序的运行结果,然后进行后续处理。 4 | 5 | 6 | 在这种主机+协处理器的模型中,计算被分为了三个层次: 7 | 8 | 1. 经典计算。完成数据的输入/输出,设置和触发量子计算机并处理计算结果等经典计算机能够处理的任务。 9 | 2. 量子计算。直接在量子计算设备上应用量子算法完成计算任务。 10 | 3. 量子计算在处理过程中调用经典计算。 11 | 12 | 以上三个层次并不需要全部使用同一种语言来实现,实际上,由于量子计算机有着独特的控制结构和资源管理机制,因此采用专门的编程语言在量子算法中实现通用的模型可能更自然流畅。 13 | 14 | 将经典计算与量子计算分离的模型意味着量子编程语言可能会受到更多更严格的限制,但是这些限制也给优化程序使程序运行更快创造了条件。 15 | 16 | Q\#(`Q-Sharp`)语言就是上面模型下的产物,它是一种用来表达量子算法的特定领域编程语言,用来编写运行与量子协处理器上的子程序,并受经典计算机和程序操控。 17 | 18 | Q\#提供了一个小的基本数据类型集合和两种创建新的结构体类型的方法,它同时也支持基本的循环和条件分支结构Q\#的顶层结构包含了用户自定义类型、`operation`和函数。 19 | 20 | 本章接下来的内容包括以下部分: 21 | 22 | * Q\#语言的类型模型 23 | * 表达式 24 | * 语句 25 | * 文件结构 26 | 27 | ### 2.1 类型模型 28 | 29 | #### 2.1.1 基本数据类型 30 | 31 | Q\#提供的基本数据类型如下,出去这些基本类型,其他类型都属于结构化类型。 32 | 33 | * `Int`:64位有符号整型 34 | * `Double`:双精度浮点数类型 35 | * `Bool`:布尔类型,取值为`true`或`false` 36 | * `Qubit`:`Qubit`表示一个量子比特或量子位。`Qubit`对用户是不透明的,除了将它们传递给另一个`operation`外,唯一的操作可能就是验证其相同性了。最终,所有在`Qubit`上的操作都需要通过Q\#的标准库来实现。 37 | * `Pauli`:`Pauli`表示一个单量子位群中的一个元素, 这种类型用于表示旋转的基本操作,并指定测量的基准,并且它是由四个可能的取值构成的可区分的联合体,这四个值分别是:`PauliI`、`PauliX`、`PauliY`和`PauliZ`。 38 | * `Result`:`Result`表示测量得到的结果,它是由两个可能的取值构成的可区分的联合体,这两个值分别是:`One`和`Zero`,`Zero`表示特征值+1,`One`表示特征值-1。 39 | * `Range`:`Range`表示整数序列,如`1..5`这个`Range`就表示`(1,2,3,4,5)`这个整数序列,注意在Q\#中,`Range`所表示的范围是一个闭区间,包含头尾的数值。 40 | * `String`:Unicode字符序列。 41 | 42 | #### 2.1.2 数组(Array) 43 | 44 | 任何一个合法的Q\#数据类型都可以构成数组,表示为`T[]`,其中`T`为任意合法类型,例如`Qubit[]`、`Int[][]`,注意这里的`Int[][]`并不像其他编程语言中的二维数组是一个矩形,在Q\#中,这个二维数组是锯齿形的,也就是说其每一行的数据数量是不同的,目前来看,Q\#中并不支持矩形的二维数组。 45 | 46 | #### 2.1.3 元组(Tuple) 47 | 48 | 给定任意数量合法的Q\#数据类型,他们能够组成元组类型,表示为`(T1,T2,T3,...)`,其中`T1,T2,T3`是任意合法的数据类型,除了基本类型,它们还可以是`Array`、`Tuple`等,一个空的括号`()`表示一个空的元组。相信熟悉`Python`的朋友对元组应该不陌生。 49 | 50 | ** 元组一旦定义就不能改变其内容。 ** 51 | 52 | 同时需要注意一下只含有一个元素的元组`(T)`,在Q\#中,单个元素的元组与这个元素本身是等价的,例如`(5)`和`([1,2,3])`,`(5)`和5以及`([1,2,3])`和`[1,2,3]`没有任何区别,同样的`(((5)))`与5,`(5,(6))`与`(5,6)`也都是一样的。这个性质在Q\#中称为**单元素元组等价**(`Singleton Tuple Equivalence`) 53 | 54 | #### 2.1.4 用户自定义类型 55 | 56 | Q\#中可以基于基本类型和合法的用户自定义类型来创建新的类型结构,其语法如下: 57 | 58 | ``` 59 | //T1,T2位基本类型或用户定义的其他类型 60 | newtype TypeName = (T1,T2,...); 61 | ``` 62 | 63 | 用户自定义类型有两个限制,一是不能定义递归结构的类型,也就是说新的类型结构中不能包含该类型本身,如下定义的类型就是非法的递归结构类型: 64 | 65 | ``` 66 | newtype Type1 = (Int,Type1); 67 | ``` 68 | 69 | 二是定义的类型之间不能存在循环依赖,这一点可以通过以下代码来说明: 70 | 71 | ``` 72 | //非法,存在循环依赖 73 | newtype TypeA = (Int, TypeB); 74 | newtype TypeB = (Double, TypeC); 75 | newtype TypeC = (TypeA, Range); 76 | ``` 77 | 78 | 用户自定义类型的可变性由其基类型决定,如果其基类为不可变类型,例如元组,那么这个类型也是不可变的,如果其基类是可变类型,如数组,那么这个类型也是可变的。 79 | 80 | --- 81 | 82 | ##### 问题:用户自定义类型是否必须是元组的形式,上面所说的不可变性中提到数组是否意味着自定义类型也可以是数组的形式或者其他 83 | 84 | --- 85 | 86 | ##### 用户自定义类型的兼容性 87 | 88 | 用户自定义类型是其基类的子类,因此凡是可以使用其基类的地方也能使用这个自定义类型,并且这个性质是递归定义的,例如一个新的类型`IntPair`基于`(Int,Int)`定义,`IntPair2`基于`NewPair`定义,那么凡是可以使用`(Int,Int)`、`NewPair`的地方都能使用`NewPair2`。 89 | 90 | 但是不同的用户自定义类型是不同的不兼容的,即使它们基于相同的基类进行定义。例如假设一个新的类型`IntPair3`也基于`(Int,Int)`进行定义,那么能够使用上面例子中`IntPair`的地方并不能够使用`IntPair3`。 91 | 92 | #### 2.1.5 operation和function 93 | 94 | --- 95 | 96 | **问题:这里并没有给出operation和function的具体定义形式,容易曹成误解。以后需要补充。** 97 | 98 | --- 99 | 100 | Q\#中,`operation`是一个量子计算子例程,也就是说它是一个可调用的包含量子操作的例程。 101 | 102 | `function`则是用于量子计算中的经典子例程,它可以包含经典程序代码甚至其中可以没有量子操作代码,在`function`中可能不会分配量子位,也不会调用`operation`,然而,量子位和`operation`可能会被传进`function`中进行处理。 103 | 104 | `operation`和`function`在Q\#中都是** 可调用类型 **,他们统称为`callable`。 105 | 106 | Q\#中所有的可调用对象都可以看作是接收一个输入然后产生一个输出,这里的输入输出都有元组进行表示,当然,没有输入的可调用对象以空元组为输入,不产生结果的可调用对象返回空元组。 107 | 108 | 可调用对象的基签名式为`('Tinput => 'Tresult)`或`('Tinput -> 'Tresult)`,其中`'Tinput`和`'Tresult`是相应的数据类型,第一种形式用于`operation`,第二种形式用于`function`。例如,`((Qubit,Pauli) => Result)`表示一个单量子位的测量`operation`。 109 | 110 | `function`类型完全由其签名式决定,例如一个计算某个角度`sine`值的函数可表示为`(Double -> Double)`。`operation`类型则由其签名式和其支持的`functor`共同决定(关于`functor`马上就会讲到),`operation`允许应用一个或多个functor。例如`Pauli X operation`的类型表示为:`(Qubit => () : Adjoint, Controlled)`,一个不支持任何functor的operation只需要写出其签名式即可,不需要之后的冒号以及`functor`列表。 111 | 112 | ##### 参数化类型的operation和function 113 | 114 | 可调用类型的签名式中可以包含参数化类型。参数化类型中使用一个单引号:`'`作为前缀,例如`'A`就是一个合法的类型参数。这样的类型参数有些类似于其他编程语言中的泛型,但实际上Q\#并没有提供对泛型的完全支持。 115 | 116 | 类型参数可以在一个签名式中多次出现。例如,一个`function`对数组中的元素应用另一个`function`,并返回运行结果组成的数组,那么这个`function`的签名式就是:`(('A[], 'A -> 'A) -> 'A[])`,类似的,一个返回由两个`operation`组合而成的结果的`function`其签名式可能为:`((('A => 'B), ('B => 'C)) -> ('A => 'C))`。 117 | 118 | 调用参数化类型的operation和function时,所有拥有相同参数类型的参数类型也必须是相同的,或者是兼容的。 119 | 120 | 对于一个类型参数,Q\#没有提供相应的机制来约束可以替换该参数的类型,因此,参数化类型对于作用于数组的`function`和组合的可调用类型更有用。 121 | 122 | ##### 可调用对象的类型兼容性 123 | 124 | * 一个支持多个`functor`的`operation`可以用在使用另一个支持较少`functor`的`operation`的场合,只要这两个`operation`拥有相同的签名式。例如,一个类型为`(Qubit => () : Adjoint)`的`operation`可以用于使用类型为`(Qubit => ())`的`operation`的场合。 125 | 126 | * 一个返回类型为`'A`可调用类型与接受相同输入参数类型而`'A`与其返回结果兼容的可调用类型是兼容的,这叫做返回值协变性。 127 | 128 | * 一个输入参数类型为`'A`的可调用类型与另一个拥有相同返回类型且输入参数与`'A`兼容的可调用类型是兼容的,这叫做输入类型逆变性。 129 | 130 | 根据以上所述,给出下面几个类型: 131 | 132 | ``` 133 | newtype IntPair : (Int, Int) 134 | newtype IntPairTransform : ((Int, Int) -> (Int, Int)) 135 | newType IntPairTransform2 : ((Int, Int) -> IntPair) 136 | newType IntPairTransform3 : (IntPair -> (Int, Int)) 137 | ``` 138 | 139 | 其中: 140 | 141 | * 一个类型为`IntPairTransform`的可调用对象可以被拥有一个`IntPair`参数的类型调用 142 | * 调用一个类型为`IntPairTransform`的`function`得到的结果可能无法用于需要`IntPair`类型的场合 143 | * 一个类型为`IntPairTransform2`的`function`可以用于需要`IntPairTransform`的场合中,但反过来就不行 144 | * 一个类型为`IntPairTransform`的`function`可以用于需要`IntPairTransform3`的场合中,但反过来就不行 145 | 146 | #### 2.1.6 functor 147 | 148 | 在Q\#中,`functor`是根据一个`operation`生成另一个`operation`的工厂。当实现一个新的`operation`时,`functor`可以访问基本`operation`的实现,从而能够执行比传统的高层次函数更复杂的功能。 149 | 150 | 一个`functor`能应用于一个`operation`并返回一个新的`operation`。例如,一个通过将`functor Adjoint`应用于`Y operation`而得到的`operation`可以写作:`(Adjoint Y)`,这个新的`operation`可以像其他`operation`一样被调用。因此,`(Adjoint Y)(q1)`就是讲`Adjoint`应用于`Y`产生一个新的operation,并用这个新的`operation`作用于`q1`得到最终的结果。类似的还有`(Controlled X)(controls, target)`等。 151 | 152 | 在Q\#中,有两个标准的`functor`:`Adjoint`和`Controlled`。 153 | 154 | ##### Adjoint 155 | 156 | 在量子计算中,一个操作的伴随矩阵是该操作的复共轭矩阵,对于实现幺正运算的操作,其伴随矩阵就是逆矩阵,对于只是调用了一系列作用于一组量子位上的幺正运算的操作,其伴随矩阵可以以相反的顺序,通过应用在同一量子位上的子运算而计算出来。 157 | 158 | 给定一个`operation`表达式,通过使用`Adjoint`修饰并用括号括起来就形成了一个新的`operation`。这个新的`operation`拥有与其父`operation`相同的签名式,且允许使用`Adjoint`继续生成新的`operation`,但只有在父`operation`支持`Controlled`时,新的`operation`才支持`Controlled`。 159 | 160 | 例如,`(Adjoint QFT)`指定了`QFT operation`的伴随矩阵。 161 | 162 | ##### Controlled 163 | 164 | 一个`operation`的受控形式是一个能够高效地将这个`operation`应用于所有量子位的新的`operation`,但是这些量子位必须处于同一个状态。如果控制量子位处于叠加状态,那么最初的`operation`将被连贯地用于适当的叠加部分,因此受控`operation`经常用于产生纠缠态。 165 | 166 | 在Q\#中,受控`operation`总是采用一个量子位数组,并且对于控制量子位,它们总是处于指定的`One`状态:`|1>`。对处于其他状态的量子位进行操控可以通过在受控`operation`发生作用前应用合适的`Clifford operation`来完成,并在受控`operation`作用后应用逆`Clifford operation`。例如,对于一个控制量子位,在受控operation作用前后对其施加X operation将导致控制在Zero状态下进行,应用`H operation`将导致控制在`PauliX Zero`状态`|+> := ((|0⟩+|1⟩)/√2` 下执行而非`PauliZ Zero`状态。 167 | 168 | 与`Adjoint`类似,通过用`Controlled`对一个`operation`进行修饰并且使用括号括起来就产生了一个新的`operation`,新的`operation`的签名式基于父`operation`,其结果类型与父`operation`相同,但其输入参数类型为由两个元素组成的元组,第一个元素是由控制量子位组成的数组,父`operation`的输入参数为其第二个元素,如果父`operation`的输入参数为空`()`,那么新的`operation`的参数仅有控制量子位组成的数组构成。新的`operation`允许使用`Controlled`继续生成新的`operation`,但只有在父`operation`支持`Adjoint`时,新的`operation`才支持`Adjoint`修饰。 169 | 170 | 如果父`operation`只接受单个参数作为输入,那么**单元素元组等价**就会在此时起作用。例如,`Controlled(X)`是`X operation`的受控形式,`X`的类型为:`(Qubit => () : Adjoint, Controlled)`,那么`Controlled(X)`的类型为:`((Qubit[], (Qubit)) => () : Adjoint, Controlled)`,因为单元素元组等价的原因,Controlled\(X\)的类型与`((Qubit[], Qubit) => () : Adjoint, Controlled)`等价。类似的,`Controlled(Rz)`是`Rz`operation的受控形式,Rz的类型为`((Double, Qubit) => () : Adjoint, Controlled)`,所以`Controlled(Rz)`的类型为`((Qubit[], (Double, Qubit)) => () : Adjoint, Controlled)`,那么`((Controlled(Rz))(controls, (0.1, target))`就是一个合法的`Controlled(Rz)`调用。 171 | 172 | ### 2.2 表达式 173 | 174 | #### 2.2.1 分组 175 | 176 | 给定任意一个合法的表达式,将其用一对括号括起来构成一个新的表达式,那么这两个表达式在Q\#中具有相同的类型。例如`(7)`是一个`Int`型的表达式,`([1;2;3])`的类型是`Int`型的数组,`((1,2))`是`(Int,Int)`型的元组。 177 | 178 | #### 2.2.2 符号 179 | 180 | 将一个类型为`'T`值绑定或赋值给一个符号,那么这个符号的类型也是`'T`,例如,将5赋值给`count`,那么`count`就是整数型的表达式。 181 | 182 | #### 2.2.3 代数表达式 183 | 184 | Q\#中代数表达式的类型是只能是`Int`或`Double`。 185 | 186 | `Int`型数据的字面量在Q\#和C\#中是一样的,不过Q\#中不需要在数值字面量后加`l`或`L`。16进制数需要在字面量前加0x前缀。 187 | 188 | `Double`型数据字面量在Q\#和C\#中也是一样的,不过在Q\#中不需要在数值字面量后加`d`或`D`。 189 | 190 | 对于一个任意类型的数组,内置函数`Length`返回该数组的元素数量(`Int`型)。例如,`a`是一个数组,`Length(a)`返回`a`中元素的数量,`b`的类型为`Int[][]`,那么`Length(b)`返回`b`中自数组的数量,`Length(b[1])`返回`b`中第二个子数组中元素的数量。 191 | 192 | 代数运算中运算符`+、-、*、/`与操作数一起构成新的代数表达式,如果操作数中有一个是`Double`类型那么这个表达式的类型也是`Double`类型,如果两个操作数都是`Int`类型,那么该表达式也是`Int`类型。 193 | 194 | 对于整型数而言,其支持的运算和运算符还包括:`%`(取模)、`^`(乘方)、`&&&`(按位与)、`|||`(按位或)、`^^^`(按位异或)、`~~~`(按位取反)、`<<<`(算术右移)、`>>>`(算术左移)。在移位操作中,第二个操作数必须大于等于0,如果小于0,对应的行为是未定义的。 195 | 196 | #### 2.2.4 量子位表达式(Qubit expressions) 197 | 198 | Q\#中量子位没有对应的字面量,将量子位或量子位数组绑定到一个符号上就构成了一个量子位表达式。 199 | 200 | #### 2.2.5 泡利表达式(Pauli expressions) 201 | 202 | 四个`Pauli`值:`PauliI`、`PauliX`、`PauliY`和`PauliZ`都是合法的泡利表达式,将这些值或这些值组成的数组绑定至一个符号上也构成合法的泡利表达式。 203 | 204 | #### 2.2.6 结果表达式(Result expressions) 205 | 206 | `Result`有两个取值:`One`和`Zero`,除了这两个值,将这两个值或由其组成的数组绑定至一个符号也构成合法的结果表达式。需要注意的是,这里的`One`并不等于整数1,两者之间也不存在任何直接的转换方法,但是`Zero`与0是相等的。 207 | 208 | #### 2.2.7 范围表达式(Range expressions) 209 | 210 | 使用三个整数就能构成一个范围表达式,这三个整数分别表示起始值、步长和终止值,范围表达式形式为:`start..step..stop`。范围表达式的其实值就是`first`,第二个值是`first+step`,第三个值是`first+step*2`,以此类推,知道数值到达或超过了`stop`。范围表达式也可以表示一个空的范围,例如当`step`为正并且`start Double)`的`operation`,`Op(3,qubit1)`就是对`Op`的调用,这个表达式的类型为`Double`。 230 | 231 | 如果一个可调用表达式的返回值仍然是一个可调用对象,那么调用这个返回值时需要多加一对括号,如下代码所示: 232 | 233 | ``` 234 | (Adjoint(Op))(3, qubit1) 235 | ``` 236 | 237 | ##### 部分调用 238 | 239 | 给定一个可调用表达式,可以传递给它一部分参数从而构造一个新的可调用对象,这就叫做** 部分调用** 。 240 | 241 | 在Q\#中是通过使用下划线\_代替一个可调用对象的参数,其它部分保持不变来表示一个部分调用的表达式的,这个新的表达式拥有和原表达式相同的返回类型,如果远表达式一个个`operation`,那么新的表达式有相同的参数变量,对于这个新的表达式,其输入参数类型是除去指定参数类型后剩下的部分,下面的例子说明了输入参数的类型与原表达式的关系。 242 | 243 | Op的类型为:`((Int, ((Qubit,Qubit), Double)) => ():Adjoint)`,那么: 244 | 245 | * `Op(5,(_,_))`和`Op(5,_)`的类型均为:`(((Qubit,Qubit), Double) => ():Adjoint)` 246 | * `Op(_,(_,1.0))`的类型为:`((Int, (Qubit,Qubit)) => ():Adjoint)` 247 | * `Op(_,((q1,q2),_))`的类型为:`((Int,Double) => ():Adjoint)` 248 | 249 | ##### 递归 250 | 251 | Q\#中的可调用对象支持递归,这也就是说,一个`operation`或`function`可以直接调用其自身。使用递归时,有两个注意事项: 252 | 253 | 1. 递归的使用有可能妨碍对程序进行优化,这个对算法的运行时间会造成实质性的影响,此时编译器应该向用户发出警告。 254 | 2. 当在一个特定的量子计算设备上使用递归程序时,如果递归深度较大亏导致运行时错误,特别的是,在Q\#中,编译和运行时尾递归都不能被识别出来也不会对其进行优化。 255 | 256 | #### 2.2.9 元组表达式 257 | 258 | 一个元组的字面量是由括号括起来的一系列各种类型数据的组合,数据之间用逗号进行分隔。例如`(1,One)`就是一个类型为`(Int,Result)`的元组表达式。 259 | 260 | 除了上面所说的字面量,如果一个符号绑定了元组表达式、元组数组中的元组元素以及返回元组类型的可调用对象都是合法的元组表达式。 261 | 262 | #### 2.2.10 用户自定义类型表达式 263 | 264 | 用户自定义类型的字面量由元组表达式和在其中的类型名称组成。例如,用户自定义类型`IntPair`基于`(Int,Int)`,那么`IntPair(2,3)`就是一个合法的`IntPair`类型的字面量。 265 | 266 | #### 2.2.11 数组表达式 267 | 268 | 一个数组的字面量是由中括号括起来的一系列特定类型数据的组合,数据之间由分好分隔。数组中所有的类型必须与数组定义时的类型兼容,如果数组定义的元素类型是`operation`或`function`,那么每个元素必须拥有相同的签名式,对于`operation`而言还必须拥有支持相同的`functor`。 269 | 270 | `[]`在Q\#中不是一个空的数组且其是非法的表达形式,要想定义一个长度为0的数组,使用以下形式:`new ★[0]`,此处`★`是类型的占位符。 271 | 272 | 给定两个相同元素类型的数组,操作符+将第二个数组中的元素附加在第一个数组后面,从而构造出一个新的数组。例如:`[1;2;3] + [4;5;6]`得到新数组:`[1;2;3;4;5;6]` 273 | 274 | ##### 数组的创建 275 | 276 | 给出一个类型T和一个整型表达式,使用`new`操作符就能创建一个元素类型为`T`,长度为整型表达式值的数组。数组中元素根据类型而被初始化为相应的值,通常默认的初始化值都为0。对于量子位和可调用类型而言,因为他们是引用类型,不存在一个合理的默认值,因此它们被初始化为一个非法的引用,如果直接使用将会导致运行错误,这类似于C\#或Java中的`null`,要正确使用它们,必须使用`set`语句对每个元素进行赋值。 277 | 278 | 数组类型只有被声明为`mutable`时才能被赋值,例如:`mutable array = new Int[5]`,数组被当做参数传递时是不可改变的。 279 | 280 | 下标列出了部分类型的默认值: 281 | 282 | | 类型 | 默认值 | 283 | | :--- | :--- | 284 | | Int | 0 | 285 | | Double | 0.0 | 286 | | Bool | false | 287 | | String | "" | 288 | | Qubit | invalid qubit | 289 | | Pauli | PauliI | 290 | | Result | Zero | 291 | | Range | 空范围:1..1..0 | 292 | | Callable | invalid callable | 293 | | Array\['T\] | 'T\[0\] | 294 | 295 | 元组类型中元素一个接一个被初始化。 296 | 297 | ##### 数组切片 298 | 299 | Q\#中数组元素的次序是从0开始的。给定一个数组和一个范围表达式,可以得到该数组中在此范围中的元素构成的新的数组,这就是数组的切片,其表达形式为:`a[range expression]`,其中`a`为一个数组,得到的数组拥有与原数组相同的类型。例如,假设`a`是一个元素类型为`Double`的数组,`a[3..1..0]`得到一个新的数组,新数组中包含原数组的前四个元素,但是这四个元素的顺序与原来相反。如果范围表达式是一个空范围,那么得到的将会是一个长度为0的数组。 300 | 301 | 如果只想得到数组中某个位置的单个值,那么直接在中括号中指定元素的次序即可:`a[1]`就得到了数组`a`的第二个元素。 302 | 303 | #### 2.2.12 布尔表达式 304 | 305 | 布尔类型的两个字面量是`true`和`false`。 306 | 307 | 对于两个与相同的基本类型兼容的变量而言,`==`和`!=`等比较运算符会得到一个布尔类型的结果。 308 | 309 | 对于大小和相同性的比较,用户自定义类型会被转换为它们的父类型然后进行比较。 310 | 311 | 对量子位类型进行比较时,实际比较的是它们是否是同一个量子位,量子位的状态不会被该操作比较、访问、测量和改变。 312 | 313 | 对`Double`类型进行比较时,要注意精度损失造成的结果不准确的问题。 314 | 315 | `&&`和`||`是逻辑与或操作符,其规则与C\#相同。 316 | 317 | `!`为逻辑非操作符。 318 | 319 | ##### 操作符优先级 320 | 321 | 下标列出了Q\#中操作符的优先级,从上到下优先级依次降低 322 | 323 | | 操作符 | 元数 | 简介 | 324 | | :--- | :--- | :--- | 325 | | -,~~~,! | 单目 | 负数,按位取反,逻辑非 | 326 | | ^ | 双目 | 乘方 | 327 | | /,\*,% | 双目 | 除法,乘法,取模 | 328 | | +,- | 双目 | 加减,数组相加,字符串相加 | 329 | | <<<,>>> | 双目 | 算数右移,算数左移 | 330 | | <,<=,>,>= | 双目 | 小于,小于等于,大于,大于等于 | 331 | | ==,!= | 双目 | 相等,不等 | 332 | | &&& | 双目 | 按位与 | 333 | | ^^^ | 双目 | 按位异或 | 334 | | \|\|\| | 双目 | 按位或 | 335 | | && | 双目 | 逻辑与 | 336 | | \|\| | 双目 | 逻辑或 | 337 | 338 | #### 2.2.13 字符串表达式 339 | 340 | Q\#中字符串可以用于`fail`语句和`Log`函数中,Q\#中字符串的语法是C\#中字符串语法的子集,这里不再进行过多介绍。 341 | 342 | ### 2.3 语句和其他结构 343 | 344 | #### 2.3.1 注释 345 | 346 | Q\#中有两种注释: 347 | 348 | ##### 单行注释 349 | 350 | 以//开头后跟注释内容,这样的注释在其他语言中也应用广泛,如C/C++、Java等。 351 | 352 | ##### 文档注释 353 | 354 | 以///开头的注释为文档注释,文档注释出现在`operation`、`function`和类型定义之前,他们会得到编译器的特殊关照,注释内容会作为可调用类型或用户自定义类型的说明文档。 355 | 356 | 文档注释的文本是API文档的一部分,它使用[Markdown文档格式](https://daringfireball.net/projects/markdown/syntax),文档中不同的部分会使用相应的命名标题标识出来。作为对Markdown文档格式的扩展,对`operation`、`function`和用户自定义类型的交叉引用可以使用`@""`来引入,其中``是要引用的类型的全限定名称。作为可选择的功能,文档引擎也可以支持其他Markdown格式的扩展。 357 | 358 | 以下代码展示了文档注释的模样: 359 | 360 | /// # Summary 361 | /// Given an operation and a target for that operation, 362 | /// applies the given operation twice. 363 | /// 364 | /// # Input 365 | /// ## op 366 | /// The operation to be applied. 367 | /// ## target 368 | /// The target to which the operation is to be applied. 369 | /// 370 | /// # Type Parameters 371 | /// ## 'T 372 | /// The type expected by the given operation as its input. 373 | /// 374 | /// # Example 375 | /// ```Q# 376 | /// // Should be equivalent to the identity. 377 | /// ApplyTwice(H, qubit); 378 | /// ``` 379 | /// 380 | /// # See Also 381 | /// - Microsoft.Quantum.Primitive.H 382 | operation ApplyTwice<'T>(op : ('T => ()), target : 'T) : () { 383 | body { 384 | op(target); 385 | op(target); 386 | } 387 | } 388 | 389 | 下面的名称是文档注释中的命名标题。 390 | 391 | * `Summary`:对定义的`operation`、`function`和自定义类型的简要概括 392 | * `Input`:对`operation`、`function`输入数据的描述,在其后面可以跟子段落以对各个输入参数进行简要说明,如上面代码中的`##op`、`##target` 393 | * `Output`:对输出数据的描述 394 | * `Type Parameter`:这是一个空段,它包含了对泛型参数类型进行描述的子段落 395 | * `Example`:一个简短的说明`operation`、`function`和自定义类型如何使用的示例 396 | * `Remarks`:描述`operation`、`function`和类型的某些方面的文字 397 | * `See Also`:列出相关联的`operation`、`function`和类型的全限定名称 398 | * `Reference`:记录引用和参考到的资料 399 | 400 | ### 2.3.2 命名空间 401 | 402 | Q\#中,每一个`operation`、`function`和用户自定义类型都定义在某个命名空间中,Q\#遵循与其他.NET语言相同的命名规则,但Q\#中不支持嵌套的命名空间。特别地,对于两个定义的命名空间`NS.Name1`和`NS.Name2`,只用使用全部名称时才能被打开,直接打开`open NS`是错误的。 403 | 404 | 每个Q\#文件必须包含至少一个命名空间声明,声明命名空间时以`namespace`关键字引出,后跟名称和一对大括号`{}`,除了注释以外,所有代码都必须写在大括号里面。 405 | 406 | 在一个命名空间中,`open`指令可以允许缩短对其他命名空间中结构的引用。使用时在`open`指令后跟要打开的命名空间。`open`指令必须出现在任何`operation`、`function`和`newtype`指令之前,且`open`指令的作用域为整个命名空间。如果一个命名空间未被打开,当要引用其中的结构时必须使用全限定名称,例如`X.Y`命名空间中有一个名为`Op`的`operation`,要引用该`operation`,则必须指定其全限定名称:`X.Y.Op`,如果这个命名空间已被打开,那么可以直接引用`Op`。 407 | 408 | 一般来说,我们更倾向于使用`open`指令打开一个命名空间,但这可能带来潜在的命名冲突问题,在这种情况下,就要使用全限定名称来引用其他命名空间中的结构了。 409 | 410 | ### 2.3.4 格式化 411 | 412 | 大部分Q\#中的语句和指令都使用分号;作为结束符号。后跟代码块的语句和指令,例如`operation`和`for`语句不需要使用分号来终结。 413 | 414 | ### 2.3.5 语句块 415 | 416 | Q\#的语句以语句块进行分组,一对大括号和其中的代码就构成了一个语句块,语句块中还可以包含其他语句块,这些被包含的语句块叫做子语句块,也叫作内部语句块,而包含内部语句块的语句块就是外部语句块。 417 | 418 | ### 2.3.6 符号绑定和赋值 419 | 420 | Q\#中包括可变和不可变的符号,通常而言,我们更倾向于使用不可变的符号因为编译器能对其进行更好的优化。 421 | 422 | 对数组来说,可变性和不可变性共同作用于数组整体和其中的元素,这意味着,一个不可变的数组中的元素也是不可变的。 423 | 424 | 元组和基于元组的用户自定义类型,无论被绑定的符号本身是否是可变的,他们中的内容都是不可变的。如果一个元组绑定到了一个可变的符号上,那么这个符号可以被重新复制为其他元组,但最初的元组中的内容也是不可变的。 425 | 426 | 一个`operation`和`function`的参数是不可变的,这也意味着Q\#中不存在“传出参数”这样的概念,而且作为参数传递给`operation`或`function`的数组也是不可变的。 427 | 428 | ##### 不可变符号 429 | 430 | 不可变符号使用`let`进行绑定,这种操作大体与C\#中的变量声明和初始化相同,但在Q\#中这个符号一旦被绑定,其就是不可变的。 431 | 432 | 下面的代码展示了如何使用`let`对一个符号进行绑定,这句代码将符号`i`绑定到了一个值为5的`Int`型数据。 433 | 434 | ``` 435 | let i = 5; 436 | ``` 437 | 438 | 如果代码右侧是一个元组或用户自定义类型,那么可以使用一种扩展语法对其进行方便的解构,如下代码所示: 439 | 440 | ``` 441 | let (i, f) = (5, 0.1); 442 | ``` 443 | 444 | 上面代码将5绑定到了`i`,将0.1绑定到了`f`。 445 | 446 | ##### 可变符号 447 | 448 | 可变符号使用`mutable`关键字进行声明和初始化,如下面代码所示: 449 | 450 | ``` 451 | mutable counter = 0; 452 | ``` 453 | 454 | 上面代码定义了一个可变符号`counter`,并将其赋值为0。 455 | 456 | `mutable`语句不支持对元组进行解构。 457 | 458 | 如果一个可变符号被绑定到了一个不可变的数组上,此时会产生一个该数组的副本赋值给这个可变符号,因此对该数据进行更改不会对原数组造成影响。 459 | 460 | ##### 更新可变符号 461 | 462 | 使用`set`语句可以改变一个可变符号的值,如下代码所示对可变符号`counter`的值增加了1: 463 | 464 | ``` 465 | set counter = counter + 1; 466 | ``` 467 | 468 | `set`语句也可以对可变数组中的元素重新赋值: 469 | 470 | ``` 471 | set result[1] = One; 472 | ``` 473 | 474 | 以上代码将数组`result`的第二个元素的值改为`One`,此处的`result`必须是一个声明为`mutable`的数组。 475 | 476 | ##### 作用域 477 | 478 | 一般而言,一个符号在在其被定义的代码块的结尾处超出作用范围并且不能再被操作,但这里有两个例外: 479 | 480 | * `for`循环中绑定的循环变量作用域`for`循环体中,而不是`for`循环结束的地方。 481 | * 一个`repeat/until`循环的三部分(循环体、测试条件、修正操作)被看做是一个作用域,所以循环体中定义的符号,在测试条件和修正操作中都是可见的。 482 | 483 | 内部代码块会继承在外部代码块中定义的符号;在一个代码块中,一个符号只能被定义一次。下面的代码是合法的: 484 | 485 | ``` 486 | if a == b { 487 | ... 488 | let n = 5; 489 | ... // n is 5 490 | } 491 | let n = 8; 492 | ... // n is 8 493 | ``` 494 | 495 | ``` 496 | if a == b { 497 | ... 498 | let n = 5; 499 | ... // n is 5 500 | } else { 501 | ... 502 | let n = 8; 503 | ... // n is 8 504 | } 505 | ``` 506 | 507 | 而下面的代码是非法的: 508 | 509 | ``` 510 | let n = 5; 511 | ... // n is 5 512 | let n = 8; // Error!! 513 | ... 514 | ``` 515 | 516 | ``` 517 | let n = 5; 518 | ... // n is 5 519 | let n = 8; // Error!! 520 | ... 521 | ``` 522 | 523 | ### 2.3 控制结构 524 | 525 | #### 2.3.1 for循环 526 | 527 | `for`循环在一个整数范围中进行迭代,一个`for`循环由关键字`for`引出,后跟一个括号,括号中分为三部分,一是循环变量的名字,二是`in`关键字,最后是一个`Range`表达式,括号后紧接着就是相应的代码段。如果`Range`表达式代表了一个空范围,那么`for`循环语句将不会做任何事。 528 | 529 | `for`循环中,`Range`表达式在循环开始之前完成全部的求值,所以在循环过程中,`Range`表达式所代表的范围是不会产生变化的。 530 | 531 | 下面展示了一个`for`循环代码块: 532 | 533 | ``` 534 | for (index in 0 .. n-2) { 535 | set results[index] = Measure([PauliX], [qubits[index]]); 536 | } 537 | ``` 538 | 539 | #### 2.3.2 Repeat-Until-Success循环 540 | 541 | `repeat`语句支持量子计算中的`“repeat until success”`(重复直至成功)模型,它包括`repeat`关键字,后跟一个代码块,该代码块就是要执行的循环体,在循环体后是`until`关键字和一个条件表达式,最后是`fixup`关键字和另一个代码块。`fixup`代码块是必须要有的,无论其是否为空,如果`fixup`代码块不做任何工作,那么只需在其中写一个空的表达式`()`即可。 542 | 543 | `repeat`语句的执行过程为:首先执行循环体,然后测试条件表达式是否为`true`,如果为`true`,那么这个`repeat`语句就执行完毕了,如果为`false`,则执行`fixup`语句中的内容,然后重新开始`repeat`语句的执行,如此循环直至结束。 544 | 545 | 下面的代码展示了`repeat`语句的使用,该代码用 `Hadamard`门和`T`门实现了重要的翻转门`V3=(1+2iZ)/√5`,并应用该翻转门完成了一个[概率电路](http://oai.dtic.mil/oai/oai?verb=getRecord&metadataPrefix=html&identifier=AD0603249)。 546 | 547 | ``` 548 | using ancilla = Qubit[1] { 549 | repeat { 550 | let anc = ancilla[0]; 551 | H(anc); 552 | T(anc); 553 | CNOT(target,anc); 554 | H(anc); 555 | (Adjoint T)(anc); 556 | H(anc); 557 | T(anc); 558 | H(anc); 559 | CNOT(target,anc); 560 | T(anc); 561 | Z(target); 562 | H(anc); 563 | let result = M([anc],[PauliZ]); 564 | } until result == Zero 565 | fixup { 566 | ();//空语句 567 | } 568 | } 569 | ``` 570 | 571 | #### 2.3.3 条件表达式 572 | 573 | `if`语句支持条件执行,条件表达式由if引出,后跟一个布尔表达式和相应的代码块,一个if语句块之后可以继续添加其他条件表达式,使用`elif`关键字引出,然后是相应的布尔表达式和语句块,在最后,可以使用`else`引出不满足上面所有条件时要执行的操作。 574 | 575 | 条件表达式的使用请看下面的代码: 576 | 577 | ``` 578 | if (result == One) { 579 | X(target); 580 | } else { 581 | Z(target); 582 | } 583 | ``` 584 | 585 | ``` 586 | if (i == 1) { 587 | X(target); 588 | } elif (i == 2) { 589 | Y(target); 590 | } else { 591 | Z(target); 592 | } 593 | ``` 594 | 595 | #### 2.3.4 return语句 596 | 597 | `return`语句结束`operation`和`function`的执行,并将结果返回给调用者。`return`语句以`return`关键字开始,后跟一个表达式和分号。一个返回值为空`()`的可调用对象不需要`return`语句,如果需要在一定条件下提前结束执行,可以使用`return ()`来返回一个空值。`return`语句的使用如下面代码所示: 598 | 599 | ``` 600 | return 1; 601 | ``` 602 | 603 | ``` 604 | return ();//返回空值 605 | ``` 606 | 607 | ``` 608 | return (results, qubits); 609 | ``` 610 | 611 | #### 2.3.5 fail语句 612 | 613 | `fail`语句技术一个`operation`的执行,并向调用者返回一个出错信息。`fail`语句由`fail`关键字和一个代表错误信息的字符串组成。下面的代码展示了`fail`语句的写法: 614 | 615 | ``` 616 | fail $"Impossible state reached"; 617 | ``` 618 | 619 | ``` 620 | fail $"Syndrome {syn} is incorrect"; 621 | ``` 622 | 623 | ### 2.4 量子位管理 624 | 625 | 接下来所讲的量子位管理相关语句不能用于`function`中。 626 | 627 | #### 2.4.1 干净的量子位 628 | 629 | `using`语句用于在语句块中获得新的量子位。这些新获得的量子位被保证处于可计算的`Zero`状态,并且这些量子位在语句块结束时也应该确保处于可计算的`Zero`状态。模拟器对这一点也被鼓励强制执行。 630 | 631 | `using`语句包括以下几个部分:首先由关键字`using`引出,随后是量子位将要绑定到的符号以及用于赋值的等号,随后是一个元素类型为`Qubit`的数组,如下代码所示: 632 | 633 | ``` 634 | using (qubits = Qubit[bits * 2 + 3]) { 635 | ... 636 | } 637 | ``` 638 | 639 | #### 2.4.2 附属量子位 640 | 641 | `borrowing`语句用于在代码块中分配临时使用的量子位,用户应保证这些借来的量子位与借来时的状态相同。关于附属的量子位的用法可以参看这个示例:[_Factoring using 2n+2 qubits with Toffoli based modular multiplication_](https://arxiv.org/abs/1611.07995) 。 642 | 643 | `borrowing`语句的使用方法如下所示: 644 | 645 | ``` 646 | borrowing qubits = Qubit[bits * 2 + 3] { 647 | ... 648 | } 649 | ``` 650 | 651 | 当借用量子位时,系统会首先使用那些正在使用中但在当前`borrowing`语句所在语句块中不可访问的量子位来填充请求,如果这些量子位不足,系统会重新分配量子位以满足需求。 652 | 653 | ### 2.5 文件结构 654 | 655 | 一个Q\#文件中包含一个或多个命名空间,每一个命名空间中都包含有operation、function和用户自定义类型等的定义,一个命名空间中可以有多个这样的定义,但是最少必须有一个。 656 | 657 | #### 2.5.1 用户自定义类型 658 | 659 | 关于用户自定义类型,请参考章节2.1.4。 660 | 661 | #### 2.5.2 operation定义 662 | 663 | operation是Q\#的核心,大体相当于其它语言中的函数。 664 | 665 | 使用关键字operation定义一个新的operation,后面紧跟operation的名称,名称之后是两个元组,第一个元组为operation的输入参数列表,第二个元组描述了operation的结果的类型,两个元组中间使用冒号:分隔,最后是一对大括号{},在这对大括号中是operation的主体代码,代码分为4个可选的组成部分: 666 | 667 | * Body主体代码部分 668 | * Adjoint定义部分 669 | * Controlled定义部分 670 | * Controlled Adjoint定义部分 671 | 672 | 一个operation只有在其同事定义了受控变量和伴随变量时才应该定义Controlled Adjoint变量。 673 | 674 | 下面分别对这几个部分进行说明。 675 | 676 | ##### Body 677 | 678 | operation的主体是实现这个operation的Q\#代码,一个operation中也可以不包含任何主体代码,例如Pauli门和 Hadamard门等一些基本操作就是以这种方式实现的。 679 | 680 | 主体部分有body关键字和紧随其后的语句块组成。 681 | 682 | ##### Adjoint 683 | 684 | 一个operation的adjoint指出了如何实现该operation的复共轭转置,adjoint对operation不是必须的,例如基本的测量操作就没有实现其adjoint功能,operation是否应该定义adjoint取决于其是否含有Adjoint声明。 685 | 686 | Adjoint定义由关键字Adjoint引出,后面的内容可以是几个部分中的一个: 687 | 688 | * 关键字self指明该operation是其自身的adjoint 689 | * 关键字auto表示由Q\#编译器根据该operation的body主体部分自动生成相应的adjoint,自动生成的adjoint是以相反的顺序应用body中的各个operation的adjoint来实现的 690 | * 自己定义实现adjoint的语句块 691 | 692 | 以下几段代码分别对应上面的几种情况: 693 | 694 | ``` 695 | adjoint self 696 | ``` 697 | 698 | ``` 699 | adjoint auto 700 | ``` 701 | 702 | ``` 703 | adjoint { 704 | // Code for the adjoint goes here 705 | } 706 | ``` 707 | 708 | 如果一个operation的body中包含repeat循环、set语句、and/or测量操作或者调用了不支持adjoint的其他operation,那么该operation的adjoint不能使用auto关键字来实现。 709 | 710 | 如果一个operation没有body主体,但其应有adjoint定义,那么应使用adjoint self或adjoint auto来实现。 711 | 712 | ##### Controlled 713 | 714 | 一个operation的受控版本指出了该operation的量子受控版本如何实现。这一部分也是选择实现的,例如基本测量操作就没有实现controlled版本因为其是不可控的,operation是否应该定义controlled取决于其是否含有Controlled声明。 715 | 716 | Controlled部分的定义由关键字controlled引出,其后可以跟下面几个中的某一项: 717 | 718 | * 关键字auto表示由Q\#编译器根据该operation的body主体部分自动生成相应的controlled部分 719 | * 一对包含了控制量子位数组符号的括号\(\)和一个实现controlled的语句块 720 | 721 | 下面几段代码展示了如何定义controlled: 722 | 723 | ``` 724 | controlled auto 725 | ``` 726 | 727 | ``` 728 | controlled (controls) { 729 | // Code for the controlled version goes here. 730 | // "controls" is bound to the array of control qubits. 731 | } 732 | ``` 733 | 734 | 如果一个operation的body中包含repeat循环、测量操作或者调用了不支持controlled的其他operation,那么该operation的controlled不能使用auto关键字来实现。 735 | 736 | 如果一个operation没有body主体,但其应有controlled定义,那么应使用controlled auto来实现。 737 | 738 | ##### Controlled Adjoint 739 | 740 | 一个operation的controlled adjoint版本指出了该operation的adjoint如何实现其量子受控版本。这一部分也是选择实现的,例如基本测量操作就没有实现controlled adjoint。operation是否应该定义controlled adjoint取决于其是否同时声明了Controlled和Adjoint。 741 | 742 | 一个controlled adjoint的定义包括关键字adjoint,随后是关键字controlled,其后可以跟下面项目中的一个: 743 | 744 | * 关键字auto指示Q\#编译器应该根据该operation的body部分生成controlled adjoint 745 | * 一对包含了控制量子位数组符号的括号\(\)和一个实现controlled adjoint的语句块 746 | 747 | 如果一个operation的body中包含repeat循环、set语句、and/or测量操作或者调用了不支持controlled adjoint的其他operation,那么该operation的controlled adjoint不能使用auto关键字来实现。 748 | 749 | 如果一个operation没有body主体,但其应有controlled定义,那么应使用controlled auto来实现。 750 | 751 | 如果一个operation的Controlled或Adjoint的部分是通过自定义的语句块实现的,同时该operation使用auto实现了controlled adjoint,那么controlled adjoint会通过自定义的代码块实现的。 752 | 753 | 如果一个operation的同时用自定义语句块实现了Controlled和Adjoint版本,同时该operation使用auto实现了controlled adjoint,那么controlled adjoint会根据自定义的controlled版本语句块来实现。 754 | 755 | 如果一个operation没有body主体,但其应有controlled adjoint定义,那么应使用controlled adjoint auto来实现。 756 | 757 | 通过以上的描述,下面几段代码展示了如何定义operation: 758 | 759 | ``` 760 | operation X (q : Qubit) : () { 761 | adjoint self 762 | controlled auto 763 | adjoint controlled auto 764 | } 765 | ``` 766 | 767 | ``` 768 | ///实现teleport操作 769 | namespace Microsoft.Quantum.Samples { 770 | // Entangle two qubits. 771 | // Assumes that both qubits are in the |0> state. 772 | operation EPR (q1 : Qubit, q2 : Qubit) : () { 773 | body 774 | { 775 | H(q2); 776 | CNOT(q2, q1); 777 | } 778 | } 779 | 780 | // Teleport the quantum state of the source to the target. 781 | // Assumes that the target is in the |0> state. 782 | operation Teleport (source : Qubit, target : Qubit) : () { 783 | body { 784 | // Get a temporary for the Bell pair 785 | using (ancilla = Qubit[1]) { 786 | let temp = ancilla[0]; 787 | 788 | // Create a Bell pair between the temporary and the target 789 | EPR(target, temp); 790 | 791 | // Do the teleportation 792 | CNOT(source, temp); 793 | H(source); 794 | if (M(source) == One) { 795 | X(target); 796 | } 797 | if (M(temp) == One) { 798 | Z(target); 799 | } 800 | } 801 | } 802 | } 803 | } 804 | ``` 805 | 806 | #### 2.5.3 function定义 807 | 808 | function是Q\#中的经典例程。function的定义形式与operation大体相同,使用function关键字引出,后跟function名称、输入参数、返回结果以及语句块,如下面代码所示: 809 | 810 | ``` 811 | function DotProduct(a : Double[], b : Double[]) : Double { 812 | if (Length(a) != Length(b) { 813 | fail "Arrays are not compatible"; 814 | } 815 | mutable accum = 0.0; 816 | for (i in 0..Length(a)-1) { 817 | set accum = accum + a[i] * b[i]; 818 | } 819 | return accum; 820 | } 821 | ``` 822 | 823 | 824 | 825 | -------------------------------------------------------------------------------- /backup/chapter3.md: -------------------------------------------------------------------------------- 1 | # 第三章 量子设备和驱动管理 2 | 3 | 本章的内容将涉及: 4 | 5 | * 一个量子算法是如何被执行的 6 | * 量子模拟器是什么样的 7 | * 如何用C\#语言写量子算法的驱动 8 | 9 | 10 | ### 3.1 量子开发工具包执行模型 11 | 12 | 在前面所写的程序中,我们通过将一个`QuantumSimulator`对象传递给算法程序来记性量子算法,`QuantumSimulator`对象能够完整模拟量子态向量,从而来执行我们的量子算法。 13 | 14 | 其他的机器也可以用来执行量子算法,这些机器负责提供原始量子原语的实现,包括基础的H门、CNOT门等操作和量子测量方法,以及量子比特的管理和追踪。不同的量子机器代表了不同的运行模型,每一种量子机器也可能提供这些原语的不同实现,例如,微软量子开发包中的量子计算机追踪模拟器实际上不执行任何模拟操作,它只是跟踪量子门、量子比特以及量子算法中用到的其他资源。 15 | 16 | #### 量子机器 17 | 18 | 在未来,我们会定义更多的量子机器类型来支持其他种类的的模拟器,并支持程序在拓扑量子计算机上执行。允许算法保持不变,同时改变底层的机器实现能够使得更方便地在模拟器中开发和测试程序,最后在真正的量子计算机上运行它。 19 | 20 | 在微软量子开发包中包含两种模拟器类型,它们都定义在命名空间`Microsoft.Quantum.Simulation.Simulators`中: 21 | - `QuantumSimulator`:完整的量子态向量模拟器 22 | - `QCTraceSimulator`:资源追踪模拟器 23 | 24 | #### 编写经典的驱动程序 25 | 26 | 在前面的程序中,我们编写了一个简单的C#程序来驱动`teleport`算法。一个C#驱动有4个主要的功能: 27 | 28 | 1. 构建目标机器 29 | 2. 计算量子算法所需要的参数 30 | 3. 使用模拟器运行量子算法 31 | 4. 对运行结果进行处理 32 | 33 | 下面我们分别对这些功能进行探讨。 34 | 35 | #### 构建目标机器 36 | 37 | Q#中的量子机器是一个常规的.NET类型的实例,它们通过自身的构造函数进行创建以及初始化。一些模拟器包括`QuantumSimulator`实现了.NET的`IDisposable`接口,因此需要使用`using`语句进行声明。 38 | 39 | #### 计算量子算法所需要的参数 40 | 41 | 在我们的`Teleport`程序示例中,我们为量子算法计算了一些需要的相关人造数据,但是通常量子算法需要大量重要的数据,这些数据由其驱动来提供是比较方便的。 42 | 43 | 例如,在模拟化学变化时,量子算法需要一个大的分子轨道相互作用的积分表,这些数据通常存储在一个文件中,但是Q#并不能读取文件系统的数据,因此,为了将这些数据传递给量子算法,就需要驱动程序来完成数据的读取以及传递工作。 44 | 45 | 另一个驱动程序扮演关键角色的例子是变分法。在这一类算法中,一个量子态基于一些经典参数进行筹备,然后这个量子态被用来计算一些有价值的数据。这些参数基于一些爬山算法或者机器学习算法进行调整,然后运行我们的量子算法。对于这类算法,将爬山算法设计成经典的算法是最合适的,然后子驱动程序中进行调用,最后将其结果传入量子算法之中。 46 | 47 | #### 运行量子算法 48 | 49 | 这一部分的内容是非常直观的。每一个Q#操作都会被编译进一个提供了静态`Run`方法的类中,传入这个方法的参数由经过平整的Q#操作的参数元祖给出,外加一个指向模拟器的变量作为`Run`方法的第一个参数。例如对于一个类型为`(String, (double, Double))`的元祖,它经过平整处理后的类型变为`(String, Double, Double)`。 50 | 将参数传递给`Run`方法时有一些细节需要注意: 51 | 52 | - 数组必须包装在`Microsoft.Quantum.Simulation.Core.QArray`类型中。`QArray`类型有一个能够接受任意排序集合对象`(IEnumerable)`的构造函数 53 | - 空元祖`()`在C#中使用`QVoid.Instance`来表示 54 | - 非空元祖使用.NET的`ValueType`实例来表示 55 | - Q#的用户自定义类型作为它们的基类来传递 56 | - 为了将`operation`和`function`传入`Run`方法中,必须创建一个该`operation`或`function`的实例 57 | 58 | #### 处理结果 59 | 60 | 量子算法的结果由`Run`方法返回,由于`Run`方法是异步执行的,它返回一个`Task`实例,有几种方法来获取真正的执行结果,最简单的方法是使用`Task`类型的`Result`属性: 61 | ``` 62 | var res = BellTest.Run(sim, 1000, initial).Result; 63 | ``` 64 | 但其他的方法,如使用[`Wait`](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.task.wait?view=netframework-4.7.1)方法或C#中的[`await`](https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/await)方法也是可行的。 65 | 66 | 许多的量子算法需要大量的后期处理来提取有用的结果,例如Shor算法中的量子部分仅仅是因数分解算法的开始。在大部分情况下,将这些后期处理任务放在经典驱动中是最简单方便的,毕竟只有精短驱动能将结果汇报给用户或写入磁盘中。 67 | 68 | #### 执行失败 69 | 70 | 当在Q#`operation`中遇到`fail`语句时一个`ExecutionFailException`会被抛出。 71 | 72 | 因为在`Run`方法中使用了`System.Task`,`fail`语句抛出的异常会被包装成为一个`System.AggregateException`,要找出产生异常的真实原因,需要在`AggregateExecption`中迭代`InnerException`,例如以下代码: 73 | ``` 74 | try 75 | { 76 | using(var sim = new QuantumSimulator()) 77 | { 78 | /// call your operations here... 79 | } 80 | } 81 | catch (AggregateException e) 82 | { 83 | // Unwrap AggregateException to get the message from Q# fail statement. 84 | // Go through all inner exceptions. 85 | foreach (Exception inner in e.InnerExceptions) 86 | { 87 | // If the exception of type ExecutionFailException 88 | if (inner is ExecutionFailException failException) 89 | { 90 | // Print the message it contains 91 | Console.WriteLine($" {failException.Message}"); 92 | } 93 | } 94 | } 95 | ``` 96 | 97 | ### 量子计算机模拟器 98 | 99 | 微软量子开发包提供了一个全状态的量子计算机模拟器,使用它你可以在自己的计算机上运行和调试使用Q#语言开发的程序。 100 | 101 | 该模拟器通过类`QuantumSimulator`引入,使用模拟器时,只需要定义一个该类的实例并将这个实例传入`Run`方法的第一个参数中即可,如下代码所示: 102 | 103 | ``` 104 | using (var sim = new QuantumSimulator()) 105 | { 106 | var res = myOperation.Run(sim).Result; 107 | ///... 108 | } 109 | ``` 110 | 111 | #### IDisposable 112 | 113 | 类`QuantumSimulator`实现了接口`IDissposable`接口,因此一个`QuantumSimulator`的实例不再被使用时,应调用方法`Dispose`。使用`QuantumSimulator`最好的方法是使用`using`语句,就像上面的例子中那样,这样可以不用显式调用`Dispose`方法,也减少了出错的概率。 114 | 115 | #### Seed 116 | 117 | 类`QuantumSimulator`使用了一个随机数生成器来模拟量子力学中的随机性。为了方便调试,有时候需要得到一个确定的结果,此时可以通过向`QuantumSimulator`的构造函数中传入一个seed来实现,这个seed传给了参数`randomNumberGeneratorSeed`: 118 | 119 | ``` 120 | using (var sim = new QuantumSimulator(randomNumberGeneratorSeed: 42)) 121 | { 122 | var res = myOperationTest.Run(sim).Result; 123 | ///... 124 | } 125 | ``` 126 | 127 | #### Thread 128 | 129 | 类`QuantumSimulator`使用[OpenMP](http://www.openmp.org/)实现所需要的线性代数的并行计算。默认情况下,OpenMP使用所有能用的硬件线程来进行计算,这也意味着有着较少数量量子比特的程序运行起来会比较慢,因为所需要的协调工作相比真正的工作占据了大部分资源。这个问题可以通过将环境变量`OPM_NUM_THREADS`设置为一个较小的数字来解决。一个常用的策略是,1个线程对应至多4个量子比特是较好的,然后每增加一个量子比特,就增加一个线程,当然了,你也可以根据自己的算法进行更好地调整。 130 | 131 | 132 | ### 量子轨迹模拟器 133 | 134 | 微软量子轨迹模拟器并不是靠真实地模拟量子计算机的状态来运行程序,因此轨迹模拟器可以运行一个使用了成百上千个量子比特的量子程序。这一点有两个好处: 135 | 1. 方便调试量子程序中的经典代码 136 | 2. 方便估算在量子计算机上运行一个量子程序所需要的资源 137 | 138 | #### 提供测量输出结果的概率 139 | 140 | 在量子算法中主要有两种测量,第一类通常其辅助作用,这样的情况通常是用户知道测量输出结果的概率,此时用户可以使用`Microsoft.Quantum.Primitive`命名空间中的`AssertProb`来表达这一信息,如下所示: 141 | 142 | ``` 143 | operation Teleportation (source : Qubit, target : Qubit) : () { 144 | body { 145 | using (ancillaRegister = Qubit[1]) { 146 | let ancilla = ancillaRegister[0]; 147 | 148 | H(ancilla); 149 | CNOT(ancilla, target); 150 | 151 | CNOT(source, ancilla); 152 | H(source); 153 | 154 | AssertProb([PauliZ], [source], Zero, 0.5, "Outcomes must be equally likely", 1e-5); 155 | AssertProb([PauliZ], [ancilla], Zero, 0.5, "Outcomes must be equally likely", 1e-5); 156 | 157 | if (M(source) == One) { Z(target); X(source); } 158 | if (M(ancilla) == One) { X(target); X(ancilla); } 159 | } 160 | } 161 | } 162 | ``` 163 | 164 | 当轨迹模拟器运行`AssertProb`时,它将记录在`source`和`ancilla`上测量`PauliZ`得到`Zero`结果的概率为$$0.5$$,在这之后,当模拟器运行`M`时,它就会查询所记录的输出概率,并且`M`返回`Zero`和`One`的概率为$$0.5$$。当同样的代码在一个跟踪着相同量子状态的模拟器中运行时,这个模拟器会检查`AssertProb`中提供的概率是否正确。 165 | 166 | #### 使用量子轨迹模拟器运行程序 167 | 168 | 量子轨迹模拟器可以被视为另一种目标机器,在这上面所使用的C#驱动程序与其他模拟器类似,如下所示: 169 | 170 | ``` 171 | using Microsoft.Quantum.Simulation.Core; 172 | using Microsoft.Quantum.Simulation.Simulators; 173 | using Microsoft.Quantum.Simulation.Simulators.QCTraceSimulators; 174 | 175 | namespace Quantum.MyProgram 176 | { 177 | class Driver 178 | { 179 | static void Main(string[] args) 180 | { 181 | QCTraceSimulator sim = new QCTraceSimulator(); 182 | var res = MyQuantumProgram.Run().Result; 183 | System.Console.WriteLine("Press any key to continue..."); 184 | System.Console.ReadKey(); 185 | } 186 | } 187 | } 188 | ``` 189 | 190 | 注意,如果代码中有至少一个测量没有使用`AssertProb`或`ForceMeasure`标记,模拟器将会抛出`UnconstrainMeasurementException`异常。 191 | 192 | 除了运行量子程序,轨迹模拟器还有5个组件用于检测程序中的bug和进行量子程序资源估算,它们是: 193 | - 明确的输入检查器(Distinct Inputs Checker) 194 | - 非法的量子比特使用检查器(Invalidated Qubits Use Checker) 195 | - 基本操作计数器(Primitive Operations Counter) 196 | - 量子线路深度计数器(Circuit Depth Counter) 197 | - 量子线路宽度计数器(Circuit Width Counter) 198 | 199 | 每一个组件都可以在`QCTraceSimulatorConfiguration`中设置合适的标志进行激活。下面的部分我们将对这些部分进行详细介绍。 200 | 201 | #### 明确的输入检查器 202 | 203 | `Distinct Inputs Checker`是轨迹模拟器的一部分,它用来检测代码中可能存在的bug。考虑以下Q#代码: 204 | ``` 205 | operation DoBoth( q1 : Qubit, q2 : Qubit, op1 : (Qubit=>()), op2 : (Qubit=>()) ) : () { 206 | body { 207 | op1(q1); 208 | op2(q2); 209 | } 210 | } 211 | ``` 212 | 213 | 当用户看到这段代码时,他们会假定参数中的`op1`和`op2`被调用的顺序是没有影响的,因为`q1`和`q2`是两个不同的量子比特,并且作用于不同量子比特上的操作也是明确的。现在来看下面的例子,例子中使用了上面定义的操作: 214 | ``` 215 | operation DisctinctQubitCaptured2Test () : () { 216 | body { 217 | using( q = Qubit[3] ) { 218 | let op1 = CNOT(_,q[1]); 219 | let op2 = CNOT(q[1],_); 220 | DoBoth( q[0],q[2],op1,op2); 221 | } 222 | } 223 | } 224 | ``` 225 | 226 | 现在`op1`和`op2`都通过局部应用来获取,并且共享一个量子比特,当用户调用`DoBoth`时,运行结果将依赖于`op1`和`op2`在`DoBoth`中出现的顺序,这绝对不会是用户希望发生的。`Distinct Inputs Checker`能够检测这样的情况并抛出`DisctinctInputsCheckerException`。 227 | 228 | ##### 在C#程序中使用`Distinct Inputs Checker` 229 | 230 | 下面的代码展示了如何在C#代码中使用配置了`Distinct Inputs Checker`的轨迹模拟器。 231 | ``` 232 | using Microsoft.Quantum.Simulation.Core; 233 | using Microsoft.Quantum.Simulation.Simulators; 234 | using Microsoft.Quantum.Simulation.Simulators.QCTraceSimulators; 235 | 236 | namespace Quantum.MyProgram 237 | { 238 | class Driver 239 | { 240 | static void Main(string[] args) 241 | { 242 | var traceSimCfg = new QCTraceSimulatorConfiguration(); 243 | traceSimCfg.useDistinctInputsChecker = true; //enables distinct inputs checker 244 | QCTraceSimulator sim = new QCTraceSimulator(traceSimCfg); 245 | var res = MyQuantumProgram.Run().Result; 246 | System.Console.WriteLine("Press any key to continue..."); 247 | System.Console.ReadKey(); 248 | } 249 | } 250 | } 251 | ``` 252 | 253 | 上面的代码中,类`QCTraceSimulatorConfiguration`存储轨迹模拟器的配置,并且能够作为`QCTraceSimulator`构造函数的参数。当`useDistinctInputsChecker`被置为`true`时,`Distinct Inputs Checker`将被激活。 254 | 255 | ##### 非法量子比特使用检测器 256 | 257 | `Invalidated Qubits Use Checker`是轨迹模拟器的一部分,它被用来检测代码中潜在的bug。考虑下面的Q#代码: 258 | ``` 259 | operation UseReleasedQubitTest () : () { 260 | body { 261 | mutable q = new Qubit[1]; 262 | using( ans = Qubit[1] ) { 263 | set q[0]= ans[0]; 264 | } 265 | H(q[0]); 266 | } 267 | } 268 | ``` 269 | 270 | 在上面的代码中,当`H`被应用到`q[0]`时,它指向了一个已经被释放的量子比特,这将导致未定义行为。当`Invalidated Qubits Use Checker`被激活时,它将抛出`InvalidatedQubitsUseCheckerException`异常。 271 | 272 | ##### 在C#程序中使用`Invalidated Qubits Use Checker` 273 | 274 | 下面的代码展示了如何在C#程序中使用配置了`Invalidated Qubits Use Checker`的轨迹模拟器。 275 | 276 | ``` 277 | using Microsoft.Quantum.Simulation.Core; 278 | using Microsoft.Quantum.Simulation.Simulators; 279 | using Microsoft.Quantum.Simulation.Simulators.QCTraceSimulators; 280 | 281 | namespace Quantum.MyProgram 282 | { 283 | class Driver 284 | { 285 | static void Main(string[] args) 286 | { 287 | var traceSimCfg = new QCTraceSimulatorConfiguration(); 288 | traceSimCfg.useInvalidatedQubitsUseChecker = true; // enables useInvalidatedQubitsUseChecker 289 | QCTraceSimulator sim = new QCTraceSimulator(traceSimCfg); 290 | var res = MyQuantumProgram.Run().Result; 291 | System.Console.WriteLine("Press any key to continue..."); 292 | System.Console.ReadKey(); 293 | } 294 | } 295 | } 296 | ``` 297 | 298 | 上面的代码中,类`QCTraceSimulatorConfiguration`存储轨迹模拟器的配置,并且能够作为`QCTraceSimulator`构造函数的参数。当`useInvalidatedQubitsUseChecker`被置为`true`时,`Invalidated Qubits Use Checker`将被激活。 299 | 300 | ##### 基本操作计数器(Primitive Operations Counter) 301 | 302 | `Primitive Operations Counter`是轨迹模拟器的一部分。它对量子程序中每一个`operation`执行的基本操作进行计数,统计的结果被保存在一个操作调用图的边缘上。现在让我们来看看使用一个`CCNOT`操作需要多少的`T`门。我们首先定义一个名为`CCNOTDriver`的操作: 303 | ``` 304 | open Microsoft.Quantum.Primitive; 305 | operation CCNOTDriver() : () { 306 | body { 307 | using( qubits = Qubit[3] ) { 308 | CCNOT(qubits[0],qubits[1],qubits[2]); 309 | T(qubits[0]); 310 | } 311 | } 312 | } 313 | ``` 314 | 315 | 为了计算`CCNOT`操作和`CCNOTDriver`操作中到底需要多少个`T`门,我们使用下面的C#代码: 316 | ``` 317 | // using Microsoft.Quantum.Simulation.Simulators.QCTraceSimulators; 318 | // using System.Diagnostics; 319 | var config = new QCTraceSimulatorConfiguration(); 320 | config.usePrimitiveOperationsCounter = true; 321 | var sim = new QCTraceSimulator(config); 322 | var res = CCNOTDriver.Run(sim).Result; 323 | 324 | double tCountAll = sim.GetMetric(PrimitiveOperationsGroupsNames.T); 325 | double tCount = sim.GetMetric(PrimitiveOperationsGroupsNames.T); 326 | ``` 327 | 328 | 上面代码中,第一部分运行`CCNOTDriver`,第二部分我们使用`QCTraceSimulator.GetMetric`方法来获得`CCNOTDriver`中运行`T`门的次数。上面程序的输出结果为`CCNOT`执行了7此`T`门,而`CCNOTDriver`执行了8此`T`门。 329 | 330 | 当使用两个类型参数来调用`GetMetric`时,它返回与给定调用图边缘相关联的数值,在上面的例子中,`Primitive.CCNOT`在`CCNOTDriver`中被调用,因此调用图中包含边缘``。 331 | 332 | 如果要获取`CNOT`门被使用的次数,那么我们可以使用以下代码: 333 | ``` 334 | double cxCount = sim.GetMetric(PrimitiveOperationsGroupsNames.CX); 335 | ``` 336 | 337 | 最后,我们可以使用以下代码将所有的统计结果输出为CSV格式: 338 | ``` 339 | double cxCount = sim.GetMetric(PrimitiveOperationsGroupsNames.CX); 340 | ``` 341 | 342 | ##### 深度计数器(Depth Counter) 343 | 344 | 深度计数器是轨迹模拟器的一部分,它用来采集量子程序中调用的每一个操作的深度。默认情况下,`T`门的深度为1,其他所有门的深度为0,这也意味着默认情况下只有`T`门的深度才会被计算,同时用户也可以自行设定每一个基本操作的深度。下面的Q#和C#代码用来演示深度计数器。 345 | 346 | ``` 347 | open Microsoft.Quantum.Primitive; 348 | operation CCNOTDriver() : () { 349 | body { 350 | using( qubits = Qubit[3] ) { 351 | CCNOT(qubits[0],qubits[1],qubits[2]); 352 | T(qubits[0]); 353 | } 354 | } 355 | } 356 | ``` 357 | 358 | 下面是相应的C#驱动代码: 359 | ``` 360 | using Microsoft.Quantum.Simulation.Simulators.QCTraceSimulators; 361 | using System.Diagnostics; 362 | var config = new QCTraceSimulatorConfiguration(); 363 | config.useDepthCounter = true; 364 | var sim = new QCTraceSimulator(config); 365 | var res = CCNOTDriver.Run(sim).Result; 366 | 367 | double tDepth = sim.GetMetric(DepthCounter.Metrics.Depth); 368 | double tDepthAll = sim.GetMetric(DepthCounter.Metrics.Depth); 369 | ``` 370 | 371 | 上面程序中第一部分运行了`CCNOTDriver`操作,第二部分我们使用`QCTraceSimulator.GetMetric`方法来获取`CCNOT`和`CCNOTDriver`中`T`的深度: 372 | ``` 373 | double tDepth = sim.GetMetric(DepthCounter.Metrics.Depth); 374 | double tDepthAll = sim.GetMetric(DepthCounter.Metrics.Depth); 375 | ``` 376 | 377 | 最后,我们可以使用下面的代码将`Depth Counter`采集的数据用CSV的格式输出: 378 | ``` 379 | string csvSummary = sim.ToCSV()[MetricCalculatorsNames.depthCounter]; 380 | ``` 381 | 382 | ##### 宽度计数器(Width Counter) 383 | 384 | 宽度计数器对每个操作中分配的和借用的量子比特进行计数,一些操作还会分配额外的量子比特,例如受控的`X`门和受控的`T`门。下面我们来统计实现一个多量子受控`X`门所需要额外分配的量子比特的数目。 385 | 386 | ``` 387 | open Microsoft.Quantum.Primitive; 388 | operation MultiControlledXDriver( numberOfQubits : Int ) : () { 389 | body { 390 | using( qubits = Qubit[numberOfQubits] ) { 391 | (Controlled X)(qubits[ 1 .. numberOfQubits - 1] ,qubits[0]); 392 | } 393 | } 394 | } 395 | ``` 396 | 397 | 下面是用来进行量子比特数量统计的C#代码,它展示了一个作用域5个量子比特的多量子受控`X`门还需要分配2个辅助量子比特,并且它的输入宽度为5. 398 | 399 | ``` 400 | var config = new QCTraceSimulatorConfiguration(); 401 | config.useWidthCounter = true; 402 | var sim = new QCTraceSimulator(config); 403 | int totalNumberOfQubits = 5; 404 | var res = MultiControlledXDriver.Run(sim, totalNumberOfQubits).Result; 405 | 406 | double allocatedQubits = 407 | sim.GetMetric( 408 | WidthCounter.Metrics.ExtraWidth, 409 | functor: OperationFunctor.Controlled); 410 | 411 | double inputWidth = 412 | sim.GetMetric( 413 | WidthCounter.Metrics.InputWidth, 414 | functor: OperationFunctor.Controlled); 415 | ``` 416 | 417 | 上面程序中第一部分运行了`MultiControlledXDriver`,第二部分使用`QCTraceSimulator.GetMetric`方法來获取受控的`X`门操作中分配的量子比特数和输入其中的量子数量。最后,我们可以使用以下代码得到CSV格式的输出结果: 418 | 419 | ``` 420 | string csvSummary = sim.ToCSV()[MetricCalculatorsNames.widthCounter]; 421 | ``` 422 | -------------------------------------------------------------------------------- /backup/question.md: -------------------------------------------------------------------------------- 1 | - computational basis measurement? 2 | 3 | - discuss measuring a Pauli operator 4 | 5 | - two half-space 6 | -------------------------------------------------------------------------------- /image/BlochSphere.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siyu-npc/learning-q-sharp/642affce3d6981a3bd5694a28b489f58800f91a8/image/BlochSphere.jpg -------------------------------------------------------------------------------- /image/CNOT.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siyu-npc/learning-q-sharp/642affce3d6981a3bd5694a28b489f58800f91a8/image/CNOT.jpg -------------------------------------------------------------------------------- /image/ClassicalGatesAndQuantumGates.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siyu-npc/learning-q-sharp/642affce3d6981a3bd5694a28b489f58800f91a8/image/ClassicalGatesAndQuantumGates.jpg -------------------------------------------------------------------------------- /image/ControlledZ.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siyu-npc/learning-q-sharp/642affce3d6981a3bd5694a28b489f58800f91a8/image/ControlledZ.jpg -------------------------------------------------------------------------------- /image/ControlledZReverse.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siyu-npc/learning-q-sharp/642affce3d6981a3bd5694a28b489f58800f91a8/image/ControlledZReverse.jpg -------------------------------------------------------------------------------- /image/CreateQSharpApp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siyu-npc/learning-q-sharp/642affce3d6981a3bd5694a28b489f58800f91a8/image/CreateQSharpApp.png -------------------------------------------------------------------------------- /image/DiyAdjointAndControlled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siyu-npc/learning-q-sharp/642affce3d6981a3bd5694a28b489f58800f91a8/image/DiyAdjointAndControlled.png -------------------------------------------------------------------------------- /image/Hadamard门可视化.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siyu-npc/learning-q-sharp/642affce3d6981a3bd5694a28b489f58800f91a8/image/Hadamard门可视化.jpg -------------------------------------------------------------------------------- /image/NAND.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siyu-npc/learning-q-sharp/642affce3d6981a3bd5694a28b489f58800f91a8/image/NAND.jpg -------------------------------------------------------------------------------- /image/PowerOf2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siyu-npc/learning-q-sharp/642affce3d6981a3bd5694a28b489f58800f91a8/image/PowerOf2.jpg -------------------------------------------------------------------------------- /image/QuantumCircuit.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siyu-npc/learning-q-sharp/642affce3d6981a3bd5694a28b489f58800f91a8/image/QuantumCircuit.jpg -------------------------------------------------------------------------------- /image/QuantumCircuitDIvide.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siyu-npc/learning-q-sharp/642affce3d6981a3bd5694a28b489f58800f91a8/image/QuantumCircuitDIvide.jpg -------------------------------------------------------------------------------- /image/QuantumGatesAndMatrices.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siyu-npc/learning-q-sharp/642affce3d6981a3bd5694a28b489f58800f91a8/image/QuantumGatesAndMatrices.jpg -------------------------------------------------------------------------------- /image/Toffoli门.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siyu-npc/learning-q-sharp/642affce3d6981a3bd5694a28b489f58800f91a8/image/Toffoli门.jpg -------------------------------------------------------------------------------- /image/TrivialGates.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siyu-npc/learning-q-sharp/642affce3d6981a3bd5694a28b489f58800f91a8/image/TrivialGates.jpg -------------------------------------------------------------------------------- /image/UniversalGateSet.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siyu-npc/learning-q-sharp/642affce3d6981a3bd5694a28b489f58800f91a8/image/UniversalGateSet.jpg -------------------------------------------------------------------------------- /image/cat.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siyu-npc/learning-q-sharp/642affce3d6981a3bd5694a28b489f58800f91a8/image/cat.jpg -------------------------------------------------------------------------------- /image/teleportation.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siyu-npc/learning-q-sharp/642affce3d6981a3bd5694a28b489f58800f91a8/image/teleportation.jpg -------------------------------------------------------------------------------- /image/toffoli表.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siyu-npc/learning-q-sharp/642affce3d6981a3bd5694a28b489f58800f91a8/image/toffoli表.png -------------------------------------------------------------------------------- /image/二维空间态矢量.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siyu-npc/learning-q-sharp/642affce3d6981a3bd5694a28b489f58800f91a8/image/二维空间态矢量.jpg -------------------------------------------------------------------------------- /image/量子线路符号.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siyu-npc/learning-q-sharp/642affce3d6981a3bd5694a28b489f58800f91a8/image/量子线路符号.jpg -------------------------------------------------------------------------------- /q-sharp-learning.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siyu-npc/learning-q-sharp/642affce3d6981a3bd5694a28b489f58800f91a8/q-sharp-learning.pdf --------------------------------------------------------------------------------