你当前正在访问 Microsoft Azure Global Edition 技术文档网站。 如果需要访问由世纪互联运营的 Microsoft Azure 中国技术文档网站,请访问 https://docs.azure.cn

教程:在 中实现 Quantum Fourier 转换 Q#

本教程介绍如何编写和模拟在单个量子比特级别运行的基本量子程序。

尽管 Q# 主要是为大规模的量子程序创建的高级编程语言,但它也可以用于探索更低级别的量子编程,即,直接寻址特定的量子比特。 具体而言,本教程深入探究量子傅立叶变换 (QFT),它是许多更大型的量子算法不可或缺的一个子例程。

在本教程中,您将学习如何:

  • 在 . 中 Q#定义量子运算
  • 编写 Quantum Fourier 转换线路
  • 模拟量子比特分配到度量输出的量子运算。
  • 观察量子系统的模拟波形函数在整个操作过程中是如何演变的。

注意

这种量子信息处理的低级视图通常用量子线路来描述,它表示按顺序向系统的特定量子比特应用逻辑门或运算。 因此,可以在线路图中轻松表示按顺序应用的单个和多个量子比特运算。 例如,本教程中使用的完整三量子量子傅立叶变换将以下表示形式作为线路: 量子傅立叶变换线路示意图。

提示

若要加速量子计算之旅,请查看 Azure Quantum 代码,这是 Azure Quantum 网站的独特功能。 在这里,你可以运行内置Q#示例或你自己的Q#程序,通过提示生成新Q#代码,在 VS Code for the Web打开并运行代码,只需单击一下,并询问 Copilot 有关量子计算的任何问题。

先决条件

创建新 Q# 文件

  1. 在 VS Code 中,选择“文件>新建文本文件”
  2. 将文件另存为 QFTcircuit.qs。 此文件包含程序的 Q# 代码。
  3. 打开 QFTcircuit.qs

本教程的第一部分包括定义 Q# 运算 Main,该运算对三个量子比特执行量子傅立叶变换。 DumpMachine 函数用于观察三量子比特系统的模拟波函数在整个运算中的演变方式。 在本教程的第二部分中,添加度量功能,并比较量子比特的前后测量状态。

您可以分步生成操作。 将以下部分中的代码复制并粘贴到 QFTcircuit.qs 文件中。

可以查看 此部分的完整 Q# 代码 作为参考。

导入所需的 Q# 库

在 Q# 文件中,导入相关的 Microsoft.Quantum.* 命名空间。

import Microsoft.Quantum.Diagnostics.*;
import Microsoft.Quantum.Math.*;
import Microsoft.Quantum.Arrays.*;

// operations go here

定义具有参数和返回对象的运算

接下来,定义 Main 运算:

operation Main() : Unit {
    // do stuff
}

Main()操作从不采用参数,现在返回一个Unit对象(类似于在 C# 或空元组void中返回Tuple[()]),在 Python 中。 稍后,修改操作以返回度量结果数组。

分配量子比特

在该操作中 Q# ,使用关键字分配三个量子位的 use 寄存器。 使用 use,将以 $\ket{0}$ 状态自动分配量子比特。

use qs = Qubit[3]; // allocate three qubits

Message("Initial state |000>:");
DumpMachine();

与实际量子计算一样,Q# 不允许直接访问量子比特状态。 但是,DumpMachine 操作会打印 target 计算机的当前状态,因此,当与完整状态模拟器一起使用时,它可以为调试和学习提供有价值的见解。

应用单量子比特和受控操作

接下来,应用构成 Main 操作本身的操作。 Q# 已在命名空间中包含 Microsoft.Quantum.Intrinsic 其中的许多操作和其他基本量子操作。

注意

请注意, Microsoft.Quantum.Intrinsic 未在其他命名空间的早期代码片段中导入,因为编译器会自动为所有 Q# 程序加载它。

应用的第一个运算是对第一个量子比特的 H (Hadamard) 运算:

显示三个量子比特 QFT 到第一个 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 Microsoft.Quantum.Diagnostics.*;
import Microsoft.Quantum.Math.*;
import Microsoft.Quantum.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[])。

  1. 在运行程序之前,请在 VS Code target 底部的状态栏中验证配置文件是否设置为 Q#:不受限制。 若要更改 target 配置文件,请在状态栏中选择 target 配置文件,然后从下拉菜单中选择“ 不受限制 ”。 如果 target 配置文件未设置为 不受限制,则运行程序时会出现错误。
  2. 若要运行程序,请从右上角的播放图标下拉列表中选择 “运行 Q# 文件 ”,或按 Ctrl+F5。 程序 Main() 在默认模拟器上运行操作。
  3. 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];

前附 mutableresultArray 关键字允许稍后在代码中修改变量 - 例如,添加测量结果时。

for 循环中执行测量,并向数组中添加结果

在 QFT 转换操作后,插入以下代码:

for i in IndexRange(qs) {
    resultArray w/= i <- M(qs[i]);
}

对数组(例如,量子比特数组 IndexRange)调用的 qs 函数返回数组索引的范围。 它用于 for 循环中,通过 M(qs[i]) 语句按顺序测量每个量子比特。 然后,每个测量的 Result 类型(ZeroOne)将通过更新和重新分配语句添加到 resultArray 中的相应索引位置。

注意

此语句的语法对 Q# 是唯一的,但对应于其他语言(如 F# 和 R)中类似的变量重新分配 resultArray[i] <- M(qs[i])

关键字 set 始终用于通过 mutable 重新分配变量绑定。

返回 resultArray

测量了所有三个量子比特并将结果添加到 resultArray之后,就可以像以前一样安全地重置和解除分配这些量子比特。 若要返回测量值,请插入:

return resultArray;

使用度量运行 QFT 线路

现在更改 DumpMachine 函数的位置,以输出测量前后的状态。 最终的 Q# 代码应如下所示:

import Microsoft.Quantum.Diagnostics.*;
import Microsoft.Quantum.Math.*;
import Microsoft.Quantum.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;

}

提示

请记住,每次引入代码更改之前保存文件,然后再再次运行它。

  1. 在运行程序之前,请在 VS Code target 底部的状态栏中验证配置文件是否设置为 Q#:不受限制。 若要更改 target 配置文件,请在状态栏中选择 target 配置文件,然后从下拉菜单中选择“ 不受限制 ”。 如果未将 target 配置文件设置为 不受限制的,运行程序时会出现错误。
  2. 若要运行程序,请从右上角的播放图标下拉列表中选择 “运行 Q# 文件 ”,或按 Ctrl+5。 程序 Main() 在默认模拟器上运行操作。
  3. 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]

此输出显示了一些不同的内容:

  1. 将返回的结果与预测量的 DumpMachine 进行比较时,它显然没有说明基态上的后 QFT 叠加。 测量仅返回单个基础状态,其概率由系统的波函数中该状态的幅度决定。
  2. 从测量后的 DumpMachine 中,可以看到,测量改变了状态本身,将其从初始基础状态叠加投影到与测量值相对应的单一基础状态

如果多次重复此操作,则会看到结果统计数据开始说明导致每次拍摄都会产生随机结果的后 QFT 状态的等权重叠加。 然而,除了效率低和仍然不完善之外,这只会再现基础状态的相对振幅,而不是它们之间的相对相位。 在此示例中,后者不是问题,但如果向 QFT 提供的输入比 $\ket{000}$ 更复杂,则会看到相关阶段出现。

使用 Q# 操作简化 QFT 线路

正如简介中所提到的,Q# 的强大之处在于它可以让你摆脱处理单个量子比特的烦恼。 事实上,如果你想开发全面、适用的量子程序,担心 H 运算是在特定旋转之前还是之后进行只会减慢你的速度。 Azure Quantum 提供 ApplyQFT 操作,你可以使用该操作并应用于任意数量的量子比特。

  1. 将第一个 H 操作到 SWAP 操作(包括)的所有内容替换为:

    ApplyQFT(qs);
    
  2. 代码现在应如下所示

    import Microsoft.Quantum.Diagnostics.*;
    import Microsoft.Quantum.Math.*;
    import Microsoft.Quantum.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;
    
    }
    
  3. Q#再次运行程序,并注意到输出与之前相同。

  4. 若要查看使用 Q# 操作的真正优势,请将量子比特数更改为以下其他 3方面:

mutable resultArray = [Zero, size = 4];

use qs = Qubit[4];
//...

因此,可以为任意给定数量的量子比特应用适当的 QFT,而无需担心在每个量子比特上添加新 H 的操作和轮换。

浏览其他 Q# 教程:

  • 量子随机数生成器 演示如何编写一个 Q# 程序,以在叠加中从量子比特中生成随机数。
  • Grover 的搜索算法 演示如何编写 Q# 使用 Grover 搜索算法的程序。
  • 量子纠缠 演示如何编写一个 Q# 程序来操作和测量量子比特,并演示叠加和纠缠的影响。
  • Quantum Katas 是自我节奏的教程和编程练习,旨在同时教授量子计算和Q#编程的元素。