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

教程:在 中实现量子傅立叶变换 Q#

注意

2024 年 6 月 30 日之后,将不再支持 Microsoft Quantum Development Kit (经典 QDK) 。 如果你是现有的 QDK 开发人员,建议过渡到新的 Azure Quantum Development Kit (新式 QDK) ,以继续开发量子解决方案。 有关详细信息,请参阅 将 Q# 代码迁移到新式 QDK

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

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

在本教程中,将了解如何:

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

注意

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

提示

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

先决条件

Create新Q#文件

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

在 中编写 QFT 线路 Q#

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

你将逐步生成运算。 将以下部分中的代码复制并粘贴到 QFTcircuit.qs 文件中。

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

用于访问其他 Q# 运算的命名空间

在 Q# 文件中定义由编译器的命名空间 NamespaceQFT。 为了使此运算能够使用现有的 Q# 运算,请打开相关的 Microsoft.Quantum.* 命名空间。

namespace NamespaceQFT {
    open Microsoft.Quantum.Intrinsic;
    open Microsoft.Quantum.Diagnostics;
    open Microsoft.Quantum.Math;
    open Microsoft.Quantum.Arrays;

    // operations go here
}

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

接下来,定义 Perform3qubitQFT 运算:

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

目前,运算不采用任何参数并返回一个 Unit 对象,这类似于在 C# 中返回 void 或在 Python 中返回空元组 Tuple[()]。 稍后,你将修改运算来返回测量结果的数组。

分配量子比特

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

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

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

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

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

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

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

显示三个量子比特 QFT 到第一个 Hadamard 的线路的关系图。

若要将运算应用于寄存器中的特定量子比特(例如,数组 Qubit[] 中的单个 Qubit),请使用标准索引表示法。 因此,将 H 运算应用于寄存器 qs 的第一个量子比特采用以下形式:

H(qs[0]);

除了将 H 运算应用于单个量子比特外,QFT 线路主要包括可控的 R1 旋转。 一般情况下,操作R1(θ, <qubit>)会使量子比特的 $\ket$ 组件保持不变,同时对 $\ket{0}{1}$ 组件应用$e^{i\theta}$ 的旋转。

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 弧度定义旋转。

应用 SWAP 操作

将相关 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 文件现在应如下所示:

namespace NamespaceQFT {
    open Microsoft.Quantum.Intrinsic;
    open Microsoft.Quantum.Diagnostics;
    open Microsoft.Quantum.Math;
    open Microsoft.Quantum.Arrays;

    operation Perform3qubitQFT() : 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 线路

目前,该 Perform3qubitQFT 操作不返回任何值 - 该操作返回 Unit 值。 稍后,你将修改操作以 () Result[] 返回一个度量结果数组。

  1. 运行Q#程序时,需要向文件添加 。EntryPointQ# 此属性告知编译器此操作是程序的入口点。 在操作之前Perform3qubitQFT,将以下行添加到文件的顶部Q#:

    @EntryPoint()
    operation Perform3qubitQFT() : Unit {
    
  2. 在运行程序之前,需要将 target 配置文件设置为 “不受限制”。 选择“ 视图 -> 命令面板”,搜索“QIR”,选择“ Q#设置 Azure Quantum QIR target 配置文件”,然后选择“ Q#不受限制”。

  3. 若要运行程序,请从右上角的播放图标下拉列表中选择“ 运行 Q# 文件 ”,或按 Ctrl+F5。 程序在默认模拟器上运行标记为 @EntryPoint() 属性的操作或函数。

  4. MessageDumpMachine 输出显示在调试控制台中。

注意

target如果配置文件未设置为“不受限制”,则运行程序时将收到错误。

了解 QFT 线路的输出

在全状态模拟器上进行调用时,DumpMachine() 提供量子状态的波函数的多种表示形式。 $n$-qubit 系统可能的状态可以表示为 $2^n$ 计算基础状态,每种状态具有相应的复杂系数(幅度和相位)。 计算基础状态对应于长度 $n$ 的所有可能的二进制字符串,即量子比特状态 $\ket{0}$ 和 $\ket{1}$ 的所有可能组合,其中每个二进制数字对应于单个量子比特。

第一行提供了一个注释,其中包含按重要顺序显示的相应量子比特的 ID。 “最重要”的量子比特 2 是指在基本状态向量 $\ket{i}$ 的二进制表示形式中,量子比特 2 的状态对应于最左侧的数字。 例如,$\ket{6} = \ket{110}$ 包含 $\ket{1}$ 中的量子比特 21,以及 $\ket{0}$ 中的量子比特 0

其余的行描述了以笛卡尔和极坐标格式测量基准状态向量 $\ket{i}$ 的概率幅度。 检查输入状态 $\ket{000}$ 的第一行:

  • |0>: 此行对应于 0 计算基础状态(假设分配后初始状态为 $\ket{000}$,则预计这是此时具有概率幅度的唯一状态)。
  • 1.000000 + 0.000000 i:以笛卡尔格式表示的概率幅度。
  • ==:分隔两种等效表示形式的 equal 符号。
  • ********************:幅度的图形表示形式。 * 的数量与此状态向量的测量概率成正比。
  • [ 1.000000 ]:幅度的数值。
  • ---:量级阶段的图形表示形式。
  • [ 0.0000 rad ]:相位的数值(以弧度为单位)。

量级和阶段都以图形表示形式显示。 幅度的表示形式非常直接:它显示一个 * 条形,概率越高,条形就越长。

显示的输出演示了编程运算进行以下状态变换

$$ \ket{\psi}_{initial} = \ket{000} $$

to

$$ \begin{align} \ket{\psi}_{final} &= \frac{1}{\sqrt{8}} \left ( \ket{000} + \ket{001} + \ket{010} + \ket + \ket + \ket{011} + \ket{100} + \ket{101} + \ket{110} + \ket{111} \right) \\ &= \frac{1}{\sqrt{2^n}}\sum_{j=0}^{2^n-1} \ket{j}, \end{align} $$

这就是三量子比特傅立叶变换的确切行为。

如果你对其他输入状态如何受到影响感到好奇,建议在变换之前试着应用其他量子比特运算。

向 QFT 线路添加度量值

DumpMachine 函数的输出显示了运算结果,但遗憾的是,量子机制的基石指出,真正的量子系统不能有这样的 DumpMachine 函数。 取而代之的是,信息是通过测量提取的,一般来说,这不仅不能提供有关完整量子状态的信息,而且还可能极大程度地改变系统本身。

量子测量多种多样,但此处的示例重点说明最基本的测量:单个量子比特的投影测量。 在给定基础上进行测量(例如,计算基础 $ { \ket{0}, \ket{1} } $)时,量子比特状态将投影到测量的任何基础状态,从而破坏两者之间的任何叠加。

修改 QFT 操作

若要在 Q# 程序内实施测量,请使用 M 运算,该运算返回 Result 类型。

首先,修改 Perform3QubitQFT 运算来返回测量结果的数组 Result[],而不是 Unit

operation Perform3QubitQFT() : Result[] {

定义并初始化 Result[] 数组

在分配量子比特之前,声明并绑定一个三元素数组, (每个量子比特) 一个 Result

mutable resultArray = [Zero, size = 3];

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

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

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

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

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

注意

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

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

返回 resultArray

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

return resultArray;

使用度量运行 QFT 线路

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

namespace NamespaceQFT {
    open Microsoft.Quantum.Intrinsic;
    open Microsoft.Quantum.Diagnostics;
    open Microsoft.Quantum.Math;
    open Microsoft.Quantum.Arrays;

    operation Perform3QubitQFT() : 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) {
            set resultArray w/= i <- M(qs[i]);
        }

        Message("After measurement: ");
        DumpMachine();

        ResetAll(qs);
        Message("Post-QFT measurement results [qubit0, qubit1, qubit2]: ");
        return resultArray;

    }
}

提示

在再次运行之前,每次对代码引入更改时,请重新调用 以保存文件。

  1. 在操作 之前Perform3qubitQFT添加 :EntryPoint

    @EntryPoint()
    operation Perform3qubitQFT() : Unit {
    
  2. 将 target 配置文件设置为 “无限制”。 单击 VS Code 窗口底部的 “QIR: Base ”按钮,然后从下拉菜单中选择“ 无限制 ”。 target如果配置文件未设置为“不受限制”,则运行程序时将收到错误。

  3. 若要运行程序,请从右上角的播放图标下拉列表中选择“ 运行 Q# 文件 ”,或按 Ctrl+5。 程序在默认模拟器上运行标记为 @EntryPoint() 属性的操作或函数。

  4. MessageDumpMachine 输出显示在调试控制台中。

输出应类似于输出:

Before measurement: 
# wave function for qubits with ids (least to most significant): 0;1;2
|0>:     0.353553 +  0.000000 i  ==     ***                  [ 0.125000 ]     --- [  0.00000 rad ]
|1>:     0.353553 +  0.000000 i  ==     ***                  [ 0.125000 ]     --- [  0.00000 rad ]
|2>:     0.353553 +  0.000000 i  ==     ***                  [ 0.125000 ]     --- [  0.00000 rad ]
|3>:     0.353553 +  0.000000 i  ==     ***                  [ 0.125000 ]     --- [  0.00000 rad ]
|4>:     0.353553 +  0.000000 i  ==     ***                  [ 0.125000 ]     --- [  0.00000 rad ]
|5>:     0.353553 +  0.000000 i  ==     ***                  [ 0.125000 ]     --- [  0.00000 rad ]
|6>:     0.353553 +  0.000000 i  ==     ***                  [ 0.125000 ]     --- [  0.00000 rad ]
|7>:     0.353553 +  0.000000 i  ==     ***                  [ 0.125000 ]     --- [  0.00000 rad ]
After measurement:
# wave function for qubits with ids (least to most significant): 0;1;2
|0>:     0.000000 +  0.000000 i  ==                          [ 0.000000 ]
|1>:     0.000000 +  0.000000 i  ==                          [ 0.000000 ]
|2>:     0.000000 +  0.000000 i  ==                          [ 0.000000 ]
|3>:     1.000000 +  0.000000 i  ==     ******************** [ 1.000000 ]     --- [  0.00000 rad ]
|4>:     0.000000 +  0.000000 i  ==                          [ 0.000000 ]
|5>:     0.000000 +  0.000000 i  ==                          [ 0.000000 ]
|6>:     0.000000 +  0.000000 i  ==                          [ 0.000000 ]
|7>:     0.000000 +  0.000000 i  ==                          [ 0.000000 ]

Post-QFT measurement results [qubit0, qubit1, qubit2]: 
[One,One,Zero]

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

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

如果多次重复此运算,就会看到结果统计信息开始表示后 QFT 状态的同等加权叠加,它每次会产生一个随机结果。 然而,除了效率低和仍然不完善之外,这只会再现基础状态的相对振幅,而不是它们之间的相对相位。 在此示例中,后者不是问题,但如果为 QFT 提供比 {000} 更复杂的输入,则会看到相对阶段。

使用 Q# 操作简化 QFT 线路

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

命名空间Q#Microsoft.Quantum.Canon包含 ApplyQFT 操作,可以使用该操作并应用于任意数量的量子比特。

  1. 若要访问ApplyQFT操作,请在文件开头添加 open 命名空间的 Q# 语句Microsoft.Quantum.Canon

    open Microsoft.Quantum.Canon;
    
  2. 将从第一个 HSWAP 替换的所有内容替换为:

    ApplyQFT(qs);
    
  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#编程的元素。