你当前正在访问 Microsoft Azure Global Edition 技术文档网站。 如果需要访问由世纪互联运营的 Microsoft Azure 中国技术文档网站,请访问 https://docs.azure.cn。
本教程演示如何编写和模拟对单个量子比特进行作的基本量子程序。
尽管 Q# 主要是为大规模的量子程序创建的高级编程语言,但它也可以用于探索更低级别的量子编程,即,直接寻址特定的量子比特。 具体而言,本教程深入探究量子傅立叶变换 (QFT),它是许多更大型的量子算法不可或缺的一个子例程。
在本教程中,您将学习如何:
- 在Q#中定义量子运算。
- 画出量子傅里叶变换电路。
- 模拟量子比特分配到度量输出的量子运算。
- 观察量子系统的模拟波形函数在整个操作过程中是如何演变的。
注意
这种量子信息处理的较低层次的视图通常用量子线路来描述,它表示按顺序向系统的特定量子比特应用逻辑门或运算。 因此,可以在线路图中轻松表示按顺序应用的单个和多个量子比特运算。 例如,本教程中使用的完整三量子比特量子傅立叶变换可以表示为以下线路:
提示
若要加速量子计算之旅,请查看 Code with Microsoft Quantum,这是 Microsoft Quantum 网站的独特功能。 在这里,你可以运行内置 Q# 示例或你自己的 Q# 程序,通过提示生成新 Q# 代码,在 VS Code for Web 中打开并运行代码,只需单击一下,并询问有关量子计算的 Copilot 问题。
先决条件
最新版本的 Visual Studio Code(VS Code) 或打开 网页版 VS Code。
最新版本的 Microsoft Quantum Development Kit (QDK) 扩展。 有关安装详细信息,请参阅 设置 QDK 扩展。
最新的
qdkPython 包具有jupyter额外功能。 若要安装这些组件,请打开终端并运行以下命令:pip install --upgrade "qdk[jupyter]"
创建新 Q# 文件
- 在 VS Code 中,打开 “文件” 菜单,然后选择“ 新建文本文件”。
- 将文件另存为 QFTcircuit.qs。 此文件包含程序的 Q# 代码。
- 打开 QFTcircuit.qs。
在 Q# 中编写 QFT 电路
本教程的第一部分包括定义 Q# 运算 Main,该运算对三个量子比特执行量子傅立叶变换。
DumpMachine 函数用于观察三量子比特系统的模拟波函数在整个运算中的演变方式。 在本教程的第二部分中,添加度量功能,并比较量子比特的前后测量状态。
您可以分步生成操作。 将以下部分中的代码复制并粘贴到 QFTcircuit.qs 文件中。
可以查看 此部分的完整 Q# 代码 作为参考。
导入所需的 Q# 库
在 Q# 文件中,导入相关的 Std.* 命名空间。
import Std.Diagnostics.*;
import Std.Math.*;
import Std.Arrays.*;
// operations go here
定义具有参数和返回对象的运算
接下来,定义 Main 运算:
operation Main() : Unit {
// do stuff
}
该Main操作从不接受参数,目前返回一个Unit对象,这类似于在 C# 中返回void或在 Python 中返回一个空元组Tuple[()]。
稍后,修改操作以返回度量结果数组。
分配量子比特
在Q#操作中,使用use关键字分配一个由三个量子位组成的寄存器。 使用 use 时,量子比特将被自动分配为 $\ket{0}$ 状态。
use qs = Qubit[3]; // allocate three qubits
Message("Initial state |000>:");
DumpMachine();
与实际量子计算一样,Q# 不允许直接访问量子比特状态。 但是,DumpMachine 操作会打印 target 计算机的当前状态,因此,当与完整状态模拟器一起使用时,它可以为调试和学习提供有价值的见解。
应用单量子比特和受控操作
接下来,应用组成 Main 操作本身的步骤。
Q# 已在 Std.Intrinsic 命名空间中包含了许多这样的操作以及其他基本的量子操作。
注意
Std.Intrinsic 未在其他命名空间的早期代码片段中导入,因为编译器会自动为所有 Q# 程序加载它。
应用的第一个运算是对第一个量子比特的 H (Hadamard) 运算:
若要将运算应用于寄存器中的特定量子比特(例如,数组 Qubit 中的单个 Qubit[]),请使用标准索引表示法。
因此,将 H 运算应用于寄存器 qs 的第一个量子比特采用以下形式:
H(qs[0]);
除了将 H 运算应用于单个量子比特外,QFT 线路主要包括可控的 R1 旋转。 一般来说,一个 R1(θ, <qubit>) 操作会保持量子位的 $\ket{0}$ 分量不变,同时将 $e^{i\theta}$ 的旋转应用到 $\ket{1}$ 分量上。
Q# 使在一个或多个控制量子比特上运行运算的条件变得简单。 通常,调用前加上 Controlled,并且操作参数按如下所示进行更改:
Op(<normal args>) $\to$ Controlled Op([<control qubits>], (<normal args>))
请注意,控制量子比特参数必须是一个数组,即使它用于单个量子比特。
QFT 中的受控操作是 R1 对第一个量子比特执行的操作(并由第二和第三个量子比特控制):
在 Q# 文件中,结合以下语句调用这些运算:
Controlled R1([qs[1]], (PI()/2.0, qs[0]));
Controlled R1([qs[2]], (PI()/4.0, qs[0]));
PI() 函数用于根据 pi 弧度定义旋转。
应用交换操作
应用相关 H 操作并对第二和第三个量子位进行受控旋转后,电路会像这样:
//second qubit:
H(qs[1]);
Controlled R1([qs[2]], (PI()/2.0, qs[1]));
//third qubit:
H(qs[2]);
最后,对第一个和第三个量子位应用 SWAP 操作以完成线路。 此操作是必需的,因为量子傅立叶变换按反向顺序输出量子位,因此交换允许将子例程无缝集成到更大的算法中。
SWAP(qs[2], qs[0]);
该 Q# 作现在包括量子傅立叶变换的量子比特级运算:
解除分配量子比特
最后一步是再次调用 DumpMachine() 以查看运算后状态,并解除分配量子比特。 分配量子比特时,它们处于 $\ket{0}$ 状态,需要使用 ResetAll 运算将其重置为初始状态。
要求将所有量子比特显式重置为 $\ket{0}$ 是一项基本功能 Q#,因为它允许其他操作在开始使用这些相同的量子比特(稀缺资源)时确切地了解其状态。 此外,重置它们可确保它们不会与系统中的任何其他量子比特纠缠。 如果未在 use 分配块结束时执行重置,可能会引发运行时错误。
将以下行添加到 Q# 文件:
Message("After:");
DumpMachine();
ResetAll(qs); // deallocate qubits
完整的 QFT 操作
程序 Q# 已完成。 QFTcircuit.qs 文件现在应如下所示:
import Std.Diagnostics.*;
import Std.Math.*;
import Std.Arrays.*;
operation Main() : Unit {
use qs = Qubit[3]; // allocate three qubits
Message("Initial state |000>:");
DumpMachine();
//QFT:
//first qubit:
H(qs[0]);
Controlled R1([qs[1]], (PI()/2.0, qs[0]));
Controlled R1([qs[2]], (PI()/4.0, qs[0]));
//second qubit:
H(qs[1]);
Controlled R1([qs[2]], (PI()/2.0, qs[1]));
//third qubit:
H(qs[2]);
SWAP(qs[2], qs[0]);
Message("After:");
DumpMachine();
ResetAll(qs); // deallocate qubits
}
运行 QFT 电路
目前,该 Main 操作不返回任何值 - 操作返回 Unit 值。 稍后,修改操作以返回度量结果数组(Result[])。
- 若要运行程序,请从前面的Q#菜单中选择”,或按
Main。 程序在默认模拟器上运行Main操作。 - 调试控制台中显示
Message和DumpMachine输出。
如果对其他输入状态有何影响感到好奇,请尝试在转换之前应用其他量子比特作。
向 QFT 线路添加度量值
来自 DumpMachine 函数的显示显示了运算的结果,但不幸的是,量子力学的基石表明,真正的量子系统不能有这样的 DumpMachine 函数。
取而代之的是,信息是通过测量提取的,一般来说,这不仅不能提供有关完整量子状态的信息,而且还可能极大程度地改变系统本身。
量子测量多种多样,但此处的示例重点说明最基本的测量:单个量子比特的投影测量。 在给定基础上进行测量(例如,计算基础 $ { \ket{0}, \ket{1} } $)时,量子比特状态将投影到测量的任何基础状态,从而破坏两者之间的任何叠加。
修改 QFT 操作
若要在 Q# 程序内实施测量,请使用 M 运算,该运算返回 Result 类型。
首先,修改 Main 运算来返回测量结果的数组 Result[],而不是 Unit。
operation Main() : Result[] {
定义并初始化 Result[] 数组
在分配量子位之前,声明并绑定一个三元素数组(每个量子比特一个 Result ):
mutable resultArray = [Zero, size = 3];
关键字 mutable 在 resultArray 之前允许在代码的后面修改变量,例如,添加测量结果时。
在 for 循环中执行测量,并向数组中添加结果
在 QFT 转换操作后,插入以下代码:
for i in IndexRange(qs) {
resultArray w/= i <- M(qs[i]);
}
对数组(例如,量子比特数组 IndexRange)调用的 qs 函数返回数组索引的范围。
它用于 for 循环中,通过 M(qs[i]) 语句按顺序测量每个量子比特。
然后,通过更新和重新分配语句,将每个测量的 Result 类型(Zero 或 One)添加到 resultArray 中的相应索引位置。
注意
此语句的语法对 Q# 是唯一的,但对应于其他语言(如 F# 和 R)中类似的变量重新分配 resultArray[i] <- M(qs[i])。
关键字 set 始终用于重新赋值通过 mutable 绑定的变量。
返回 resultArray
测量了所有三个量子比特并将结果添加到 resultArray之后,就可以像以前一样安全地重置和解除分配这些量子比特。 若要返回测量值,请插入:
return resultArray;
使用测量运行 QFT 电路
现在更改 DumpMachine 函数的位置,以输出测量前后的状态。
最终的 Q# 代码应如下所示:
import Std.Diagnostics.*;
import Std.Math.*;
import Std.Arrays.*;
operation Main() : Result[] {
mutable resultArray = [Zero, size = 3];
use qs = Qubit[3];
//QFT:
//first qubit:
H(qs[0]);
Controlled R1([qs[1]], (PI()/2.0, qs[0]));
Controlled R1([qs[2]], (PI()/4.0, qs[0]));
//second qubit:
H(qs[1]);
Controlled R1([qs[2]], (PI()/2.0, qs[1]));
//third qubit:
H(qs[2]);
SWAP(qs[2], qs[0]);
Message("Before measurement: ");
DumpMachine();
for i in IndexRange(qs) {
resultArray w/= i <- M(qs[i]);
}
Message("After measurement: ");
DumpMachine();
ResetAll(qs);
Message("Post-QFT measurement results [qubit0, qubit1, qubit2]: ");
return resultArray;
}
提示
请记住,每次对代码进行更改后保存文件,然后再运行它。
- 若要运行程序,请从前面Q#菜单选择,或按
Main。 程序在默认模拟器上执行Main操作。 -
Message和DumpMachine输出显示在调试控制台中。
输出应如下所示:
Before measurement:
Basis | Amplitude | Probability | Phase
-----------------------------------------------
|000⟩ | 0.3536+0.0000𝑖 | 12.5000% | 0.0000
|001⟩ | 0.3536+0.0000𝑖 | 12.5000% | 0.0000
|010⟩ | 0.3536+0.0000𝑖 | 12.5000% | 0.0000
|011⟩ | 0.3536+0.0000𝑖 | 12.5000% | 0.0000
|100⟩ | 0.3536+0.0000𝑖 | 12.5000% | 0.0000
|101⟩ | 0.3536+0.0000𝑖 | 12.5000% | 0.0000
|110⟩ | 0.3536+0.0000𝑖 | 12.5000% | 0.0000
|111⟩ | 0.3536+0.0000𝑖 | 12.5000% | 0.0000
After measurement:
Basis | Amplitude | Probability | Phase
-----------------------------------------------
|010⟩ | 1.0000+0.0000𝑖 | 100.0000% | 0.0000
Post-QFT measurement results [qubit0, qubit1, qubit2]:
[Zero, One, Zero]
此输出显示了一些不同的内容:
- 在将返回的结果与预度量
DumpMachine进行比较时,可以明显看出,它 不 能展示 QFT 后在基态上的叠加态。 测量仅返回单个基础状态,其概率由系统的波函数中该状态的幅度决定。 - 从测量后,测量会更改状态本身,将其从初始基态的叠加态投射到与测量值相对应的单一基态。
如果多次重复此操作,结果统计开始显示经过 QFT 后状态的同样加权叠加,这会在每次测量中产生随机结果。 然而,除了效率低和仍然不完善之外,这只会再现基础状态的相对振幅,而不是它们之间的相对相位。 后者在本示例中不是问题,但如果给 QFT 提供比 $\ket{000}$ 更复杂的输入,相对阶段将会显现。
使用 Q# 操作简化 QFT 线路
正如简介中所提到的,Q# 的强大之处在于它可以让你摆脱处理单个量子比特的烦恼。
事实上,如果你想开发全面、适用的量子程序,担心 H 运算是在特定旋转之前还是之后进行只会减慢你的速度。 Azure Quantum 提供 ApplyQFT 操作,你可以使用该操作并应用于任意数量的量子比特。
将第一个
H操作到SWAP操作(包括)的所有内容替换为:ApplyQFT(qs);代码现在应如下所示
import Std.Diagnostics.*; import Std.Math.*; import Std.Arrays.*; operation Main() : Result[] { mutable resultArray = [Zero, size = 3]; use qs = Qubit[3]; //QFT: //first qubit: ApplyQFT(qs); Message("Before measurement: "); DumpMachine(); for i in IndexRange(qs) { resultArray w/= i <- M(qs[i]); } Message("After measurement: "); DumpMachine(); ResetAll(qs); Message("Post-QFT measurement results [qubit0, qubit1, qubit2]: "); return resultArray; }Q#再次运行程序,并注意到输出与之前相同。
若要查看使用 Q# 操作的真正优势,请将量子比特数更改为除
3以外的其他数值。
mutable resultArray = [Zero, size = 4];
use qs = Qubit[4];
//...
因此,可以为任意给定数量的量子比特应用适当的 QFT,而无需担心在每个量子比特上添加新 H 的操作和轮换。
相关内容
浏览其他 Q# 教程:
- 量子随机数生成器 演示如何编写一个 Q# 程序,以在叠加中从量子比特中生成随机数。
- Grover 的搜索算法 演示如何编写 Q# 使用 Grover 搜索算法的程序。
- 量子纠缠 演示如何编写一个 Q# 程序来操作和测量量子比特,并演示叠加和纠缠的影响。
- Quantum Katas 是自我节奏的教程和编程练习,旨在同时教授量子计算和Q#编程的元素。