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

如何调试和测试量子代码

与经典编程一样,能够检查量子程序是否按预期方式运行,并且能够诊断不正确的行为是至关重要的。 本文讨论 Azure Quantum Development Kit 提供用于测试和调试量子程序的工具。

测试和调试在量子编程中同样重要,因为它们在经典编程中同样重要。 本文介绍如何在 Visual Studio Code(VS Code)和 Jupyter Notebook 中使用 Azure Quantum Development Kit (QDK)调试和测试量子程序。

调试量子代码

QDK 提供了多种工具来调试代码。 如果在 VS Code 中编写 Q# 或 OpenQASM 程序,则可以使用 VS Code 调试器在程序中设置断点并分析代码。 QDK 还提供一组转储函数,可用于获取程序中不同点的信息。

如何使用 VS Code 调试器

在 VS Code 中使用 QDK 扩展,您可以使用调试器单步执行代码并进入每个函数或操作,跟踪局部变量的值,并观察量子比特的量子态。

以下示例演示如何使用调试器来运行Q#程序。 有关 VS Code 调试器的完整信息,请参阅 VS Code 网站上的 调试

  1. 在 VS Code 中,使用以下代码创建并保存新 .qs 文件:

    import Std.Arrays.*;
    import Std.Convert.*;
    
    operation Main() : Result {
        use qubit = Qubit();
        H(qubit);
        let result = M(qubit);
        Reset(qubit);
        return result;
    }
    
  2. 在第 6 行中, H(qubit)单击行号左侧以设置断点。 此时会显示一个红色圆圈。

  3. 在主侧栏中,选择调试器图标以打开调试器窗格,然后选择 “运行”和“调试”。 此时会显示调试器控件栏。

  4. F5 启动调试器,继续执行直到达到断点。 在调试器窗格的“ 变量 ”菜单中,展开 “量子状态 ”下拉列表,查看量子比特已初始化为 $\ket{0}$ 状态。

  5. F11 进入 H 操作。 将显示 H 操作的源代码。 请注意,执行操作时,量子态更改为叠加态。

  6. F10 跳过 M 操作。 请注意, 量子状态 在度量后解析为 $\ket{0}$ 或 $\ket{1}$ 。 变量 result 也显示在 “局部变量”下。

  7. 再次按 F10 以单步跳过 Reset 操作。 请注意, Quantum State 已重置为 $\ket{0}$。

浏览完调试器后,按 *Ctrl + F5 退出调试器。

注意

VS Code 调试器仅适用于 Q# (.qs) 和 OpenQASM (.qasm) 文件。 不能在 Jupyter Notebook 中的单元格上使用 Q# VS Code 调试器。

如何使用 QDK 转储函数进行调试

QDK 提供了多个 Q# 和 Python 函数,这些函数在调用时会转储有关程序当前状态的信息。 使用这些转储函数中的信息来检查您的程序的行为是否符合预期。

函数Q#DumpMachine

DumpMachine 是一个 Q# 函数,可用于在程序运行时将有关量子比特系统的当前状态的信息转储到控制台。 DumpMachine 在运行时不会停止或中断程序。

以下示例在Q#程序中的两个点调用DumpMachine,并查看输出。

  1. 在 VS Code 中,使用以下代码创建并保存新 .qs 文件:

    import Std.Diagnostics.*;
    
    operation Main() : Unit {
        use qubits = Qubit[2];
        X(qubits[1]);
        H(qubits[1]);
        DumpMachine();
    
        R1Frac(1, 2, qubits[0]);
        R1Frac(1, 3, qubits[1]);
        DumpMachine();
    
        ResetAll(qubits);
    }
    
  2. Ctrl + Shift + Y 打开 调试控制台

  3. Ctrl + F5 运行程序。 调试DumpMachine中将显示以下输出

    Basis | Amplitude      | Probability | Phase
    -----------------------------------------------
     |00⟩ |  0.7071+0.0000𝑖 |    50.0000% |   0.0000
     |01⟩ | −0.7071+0.0000𝑖 |    50.0000% |  -3.1416
    
    Basis | Amplitude      | Probability | Phase
    -----------------------------------------------
     |00⟩ |  0.7071+0.0000𝑖 |    50.0000% |   0.0000
     |01⟩ | −0.6533−0.2706𝑖 |    50.0000% |  -2.7489
    

DumpMachine输出显示量子比特系统在每组门后的状态如何变化。

注意

输出 DumpMachine 使用大端字节序排序。

Python dump_machine 函数

dump_machine 函数是 Python 库中的 qsharp 函数。 此函数返回当前已分配的量子比特数量和包含量子位系统稀疏状态振幅的字典。

以下示例运行与上 DumpMachine 一个示例相同的程序,但在 Jupyter 笔记本中而不是 .qs 文件中运行。

  1. 在 VS Code 中,按 Ctrl + Shift + P 打开 命令面板

  2. 输入 “创建:新建 Jupyter Notebook ”,然后按 Enter。 此时会打开新的 Jupyter Notebook 选项卡。

  3. 在第一个单元格中,复制并运行以下代码:

    import qsharp 
    
  4. 创建新的代码单元,然后复制并运行以下 Q# 代码:

    %%qsharp
    
    use qubits = Qubit[2];
    X(qubits[0]);
    H(qubits[1]);
    
  5. 创建新代码单元格。 复制并运行以下 Python 代码,查看程序中此时的量子比特状态:

    dump = qsharp.dump_machine()
    dump
    

    dump_machine 函数显示以下输出:

    Basis State
    (|𝜓₁…𝜓ₙ⟩)  Amplitude       Measurement Probability  Phase
    |10⟩       0.7071+0.0000𝑖   50.0000%                 ↑  0.0000
    |11⟩       0.7071+0.0000𝑖   50.0000%                 ↑  0.0000
    
  6. 创建新的代码单元,然后复制并运行以下 Q# 代码:

    %%qsharp
    
    R1Frac(1, 2, qubits[0]);
    R1Frac(1, 3, qubits[1]);
    
  7. 创建新代码单元格。 复制并运行以下 Python 代码,查看程序中此时的量子比特状态:

    dump = qsharp.dump_machine()
    dump
    

    dump_machine 函数显示以下输出:

    Basis State
    (|𝜓₁…𝜓ₙ⟩)  Amplitude      Measurement Probability  Phase
    |10⟩       0.5000+0.5000𝑖  50.0000%                 ↗  0.7854
    |11⟩       0.2706+0.6533𝑖  50.0000%                 ↗  1.1781
    
  8. 若要打印输出的缩写版本 dump_machine ,请创建新单元格并运行以下 Python 代码:

    print(dump)
    
  9. 若要获取系统中量子比特总数,请创建新的代码单元并运行以下 Python 代码:

    dump.qubit_count
    
  10. 可以访问具有非零振幅的单个量子比特状态的振幅。 例如,创建新的代码单元并运行以下 Python 代码,以获取 $\ket$ 和 $\ket{10}{11}$ 状态的各个振幅:

    print(dump[2])
    print(dump[3])
    

dump_operation 函数

dump_operation 函数是 Python 包中的 qsharp.utils 函数。 该函数接受两个输入:一个表示为字符串的操作或操作定义,以及该操作中使用的量子比特数量。 输出 dump_operation 是一个嵌套列表,表示对应于给定量子运算的复数的平方矩阵。 矩阵值以计算为基础,每个子列表表示矩阵的一行。

以下示例使用 dump_operation 显示 1量子比特和2比特系统的信息。

  1. 在 VS Code 中,按 Ctrl + Shift + P 打开 命令面板

  2. 输入 “创建:新建 Jupyter Notebook ”,然后按 Enter。 此时会打开新的 Jupyter Notebook 选项卡。

  3. 在第一个单元格中,复制并运行以下代码:

    import qsharp
    from qsharp.utils import dump_operation
    
  4. 若要显示单量子比特门的矩阵元素,请为量子比特数调用 dump_operation 并传递 1。 例如,在新代码单元中复制并运行以下 Python 代码,以获取标识门和 Hadamard 门的矩阵元素:

    res = dump_operation("qs => ()", 1)
    print("Single-qubit identity gate:\n", res)
    print()
    
    res = dump_operation("qs => H(qs[0])", 1)
    print("Single-qubit Hadamard gate:\n", res)
    
  5. 还可以调用 qsharp.eval 函数,然后在 dump_operation 引用 Q# 操作以获取相同的结果。 例如,创建新的代码单元,然后复制并运行以下 Python 代码,以打印单量子比特 Hadamard 门的矩阵元素:

    qsharp.eval("operation SingleH(qs : Qubit[]) : Unit { H(qs[0]) }")
    
    res = dump_operation("SingleH", 1)
    print("Single-qubit Hadamard gate:\n", res)
    
  6. 若要显示双量子比特门的矩阵元素,请调用 dump_operation 并传递 2 作为量子比特数。 例如,在新代码单元中复制并运行以下 Python 代码,以获取受控 Ry作的矩阵元素,其中第二个量子位是 target 量子比特:

    qsharp.eval ("operation ControlRy(qs : Qubit[]) : Unit { Controlled Ry([qs[0]], (0.5, qs[1])); }")
    
    res = dump_operation("ControlRy", 2)
    print("Controlled Ry rotation gate:\n", res)
    

有关如何使用 dump_operation 测试和调试代码的更多示例,请参阅 QDK 示例中 的测试作

测试量子代码

QDK 提供了多个 Q# 函数和操作,可以用于在代码运行时进行测试。 还可以为 Q# 程序编写单元测试。

fail 表达式

表达式 fail 立即结束程序。 若要将测试合并到代码中,请使用 fail 条件语句内的表达式。

下面的示例使用一个 fail 语句来测试量子比特数组是否正好包含 3 个量子比特。 测试未通过时,程序以错误消息结束。

  1. 在 VS Code 中,使用以下代码创建并保存新 .qs 文件:

    operation Main() : Unit {
        use qs = Qubit[6];
        let n_qubits = Length(qs);
    
        if n_qubits != 3 {
            fail $"The system should have 3 qubits, not {n_qubits}.";
        }  
    }
    
  2. Ctrl + F5 运行程序。 程序失败,调试 控制台中会显示以下输出:

    Error: program failed: The system should have 3 qubits, not 6.
    
  3. 将代码从 Qubit[6] 编辑到 Qubit[3],保存文件,然后按下 Ctrl + F5 再次运行程序。 程序在没有错误的情况下运行,因为测试通过。

Fact 函数

还可以使用 Q#Fact 命名空间中的 Std.Diagnostics 函数来测试代码。 该 Fact 函数采用布尔表达式和 en 错误消息字符串。 如果布尔表达式为 true,则测试通过,程序继续运行。 如果布尔表达式为 false,则 Fact 结束程序并显示错误消息。

若要在前面的代码中使用 Fact 函数执行相同的数组长度测试,请按以下步骤操作:

  1. 在 VS Code 中,使用以下代码创建并保存新 .qs 文件:

    import Std.Diagnostics.Fact;
    
    operation Main() : Unit {
        use qs = Qubit[6];
        let n_qubits = Length(qs);
    
        Fact(n_qubits == 3,  $"The system should have 3 qubits, not {n_qubits}.")
    }
    
  2. Ctrl + F5 运行程序。 测试条件 Fact 未通过,错误消息会显示在 调试控制台中。

  3. 修改从 Qubit[6]Qubit[3] 的代码,保存文件,然后按 Ctrl + F5 重新运行程序。 测试条件 Fact 通过后,程序运行时不会出错。

使用Q#批注编写@Test()单元测试

在 Q# 程序中,可以将 @Test() 批注应用于可调用对象(函数或操作),以将其转换为单元测试。 这些单元测试显示在 VS Code 的 “测试 ”菜单中,以便可以利用此 VS Code 功能。 仅当可调用方不采用输入参数时,才能将可调用方转换为单元测试。

以下示例将数组长度测试代码包装在一个操作中,并将该操作转换为单元测试:

  1. 在 VS Code 中,使用以下代码创建并保存新 .qs 文件:

    import Std.Diagnostics.Fact;
    
    @Test()
    operation TestCase() : Unit {
        use qs = Qubit[3];
        let n_qubits = Length(qs);
    
        Fact(n_qubits == 3, $"The system should have 3 qubits, not {n_qubits}.");
    }
    

    TestCase 操作定义之前的 @Test() 行上的注解将该操作转换为 VS Code 单元测试。 在操作定义线上出现一个绿色箭头。

  2. 选择绿色箭头以运行 TestCase 并报告测试结果。

  3. 若要在 VS Code 测试资源管理器中操作单元测试,请选择主侧栏中的 “测试”烧瓶 图标。

  4. 编辑代码从Qubit[3]Qubit[6],然后再次运行单元测试,以查看测试信息如何变化。

可以在 VS Code 中编写和运行Q#单元测试,而无需程序中的入口操作。

注意

命名空间中的 Std.Diagnostics 可调用项与 QIR 生成不兼容,因此仅在你在模拟器上运行的代码中包含 Q# 单元测试。 如果要从 Q# 代码生成 QIR,则不要在代码中包含单元测试。

CheckZeroCheckAllZero 操作

CheckZero CheckAllZero Q#操作用于检查量子比特或量子比特数组的当前状态是否为 $\ket{0}$。 该CheckZero操作处理单个量子比特,仅当量子比特处于 $\ket{0}$ 状态时才返回true。 这些CheckAllZero操作接受量子比特数组作为输入,并且仅当数组中的所有量子比特都处于 $\ket{0}$ 状态时才返回true。 若要使用 CheckZeroCheckAllZero,请从 Std.Diagnostics 命名空间导入它们。

以下示例使用这两个操作。 CheckZero 测试 X 操作是否将第一个量子比特从 $\ket{0}$ 状态翻转为 $\ket{1}$ 状态,以及 CheckAllZero 操作是否将两个量子比特重置为 $\ket{0}$ 状态。

在 VS Code 中,使用以下代码创建并保存新 .qs 文件,然后运行程序并在 调试控制台中检查输出。

import Std.Diagnostics.*;

operation Main() : Unit {
    use qs = Qubit[2];
    X(qs[0]); 

    if CheckZero(qs[0]) {
        Message("X operation failed");
    }
    else {
        Message("X operation succeeded");
    }

    ResetAll(qs);

    if CheckAllZero(qs) {
        Message("Reset operation succeeded");
    }
    else {
        Message("Reset operation failed");
    }
}