本教程演示如何编写 Q# 程序来操作和测量量子比特并演示叠加和纠缠效果。

  • 经典位保存单个二进制值(如 0 或 1),而 量子比特 的状态则可以是 0 和 1 的双量子状态的 叠加 。 每种可能的量子状态具有一个关联的概率幅度。
  • 测量 量子比特的行为生成具有特定概率的二进制结果 0 或 1,并更改量子比特的叠加状态。
  • 多个量子比特可以 纠缠 ,使得它们不能相互独立地描述。 也就是说,无论纠缠对中的一个量子比特发生什么情况,另一个量子比特也会发生相同的情况。
  • 在本教程中,你将准备两个处于特定量子状态的量子比特来了解如何通过 Q# 运算量子比特来更改其状态并演示叠加和量子比特纠缠效果。 你将逐步生成 Q# 程序以了解量子比特状态、运算和测量。

    若要完成本教程,你需要:

  • 具有活动订阅的 Azure 帐户。 如果没有 Azure 帐户,请免费注册并注册 即用即付订阅
  • Azure Quantum 工作区。 有关详细信息,请参阅 创建 Azure Quantum 工作区
  • 本教程介绍以下操作:

  • 创建 Q# 运算以测量量子比特并将其初始化为所需状态。
  • 创建量子比特并测试程序。
  • 将量子比特置于叠加状态。
  • 纠缠一对量子比特。
  • 在工作区中创建一个新笔记本

  • 登录到 Azure 门户 ,并选择在上一步中创建的工作区。
  • 在左侧边栏选项卡中,选择“笔记本”。
  • 单击“我的笔记本”,然后单击“新增”。
  • 在“内核类型”中选择“IQ#”。
  • 键入文件的名称(例如 Entanglement.ipynb),然后单击“创建文件”。
  • 当你的新笔记本打开时,它会根据订阅和工作区信息自动为第一个单元格创建代码。

    %azure.connect "/subscriptions/\<subscription ID>/\<resource group>/providers/Microsoft.Quantum/Workspaces/\<workspace>" \<location>
    

    %azure.connect 是一个 IQ# magic 命令,它是帮助简化 Jupyter Notebook 中的任务的一组命令。

    如果运行此单元,它应该对你的订阅进行身份验证并显示可用提供商及其目标的列表。

    使用测量初始化量子比特

    第一步是定义一个 Q# 运算,用于将量子比特初始化为已知状态。 可以调用此运算以将量子比特设置为经典状态,这意味着它要么在 100% 的时间返回 Zero,要么在 100% 的时间返回 OneZeroOne 是表示对量子比特进行测量时仅有的两个可能结果的 Q# 值。

    单击“+代码”添加新的单元并添加以下代码:

    operation SetQubitState(desired : Result, target : Qubit) : Unit {
        if desired != M(target) {
            X(target);
    

    Microsoft.Quantum.IntrinsicMicrosoft.Quantum.Canon 命名空间由此代码中的运算使用,将在 Azure Quantum 笔记本的每个单元中自动打开。

    该代码示例引入了用于变换量子比特状态的两个标准运算(MX)。

    SetQubitState 运算:

  • 采用两个参数:一个名为 desired 的类型 Result,表示量子比特的所需状态(0 或 1);类型 Qubit
  • 执行测量运算 M,以测量量子比特的状态(ZeroOne),并将结果与 desired 中指定的值进行比较。
  • 如果测量结果与比较值不匹配,则运行 X 运算,以将量子比特的状态翻转为返回 ZeroOne 的测量的概率反转位置。 这样,SetQubitState 会始终将目标量子比特置于所需状态下。
  • 接下来,为了演示 SetQubitState 运算的效果,请创建名为 TestBellState 的另一个运算。

    添加另一个新单元并添加以下代码:

    operation TestBellState(count : Int, initial : Result) : (Int, Int, Int, Int) {
        mutable numOnesQ1 = 0;
        mutable numOnesQ2 = 0;
        // allocate the qubits
        use (q1, q2) = (Qubit(), Qubit());   
        for test in 1..count {
            SetQubitState(initial, q1);
            SetQubitState(Zero, q2);
            // measure each qubit
            let resultQ1 = M(q1);            
            let resultQ2 = M(q2);           
            // Count the number of 'Ones':
            if resultQ1 == One {
                set numOnesQ1 += 1;
            if resultQ2 == One {
                set numOnesQ2 += 1;
        // reset the qubits
        SetQubitState(Zero, q1);             
        SetQubitState(Zero, q2);
        // Return number of |0> states, number of |1> states
        Message("q1:Zero, One  q2:Zero, One");
        return (count - numOnesQ1, numOnesQ1, count - numOnesQ2, numOnesQ2 );
    

    TestBellState 运算:

  • 采用两个参数:count,即测量的运行次数;initial,即要将量子比特初始化到的所需状态。
  • 调用 use 语句以初始化两个量子比特。
  • 循环 count 迭代。 对于每次循环,它将
    1. 调用 SetQubitState 以便对第一个量子比特设置指定的 initial 值。
    2. 再次调用 SetQubitState 以将第二个量子比特设置为 Zero 状态。
    3. 使用 M 运算来测量每个量子比特。
    4. 存储每个量子比特的、返回 One 的测量数目。
    5. 循环完成后,它会再次调用 SetQubitState 以将量子比特重置为已知状态 (Zero),使其他对象能够以已知状态分配量子比特。 这是 use 语句所必需的。
    6. 最后,它使用 Message 函数在返回结果之前将消息显示在控制台中。
    7. 在继续执行叠加和纠缠过程之前,测试到目前为止的代码,以查看量子比特的初始化和测量。

      若要运行 TestBellState 运算,请使用 %simulate magic 命令调用 Azure Quantum 全状态模拟器。 需要指定 countinitial 参数,例如 count=1000initial=1。 这会将第一个量子比特初始化为 One,并测量每个量子比特 1000 次。 使用以下命令添加一个新单元,然后单击“全部运行”:

      %simulate TestBellState count=1000 initial=1
      

      你应会看到以下输出:

      q1:Zero, One  q2:Zero, One
      (0, 1000, 1000, 0)
      

      由于尚未操作量子比特,因此它们保留了初始值:第一个量子比特每次返回 One,第二个量子比特返回 Zero

      如果再次使用 initial=0 运行该单元,则应会注意到,第一个量子比特每次也返回 Zero

      %simulate TestBellState count=1000 initial=0
      
      q1:Zero, One q2:Zero, One
      (1000, 0, 1000, 0)
      

      将量子比特置于叠加状态

      目前,程序中的量子比特都处于经典状态,即,要么为 1,要么为 0。 之所以知道这一点,是因为该程序会将量子比特初始化为已知状态,并且你未添加任何过程来操作量子比特。 在纠缠量子比特之前,需要将第一个量子比特置于叠加状态,在此状态下,量子比特的测量会在 50% 的时间返回 Zero,在 50% 的时间返回 One。 从概念上讲,可将量子比特视为 ZeroOne 之间所有状态的线性组合。

      Q# 提供了 H (Hadamard) 运算用于将量子比特置于叠加状态。 回顾前面的使用测量初始化量子比特过程中的 X 运算,它将量子比特从 0 翻转为 1(或反之);H 运算将量子比特中途翻转为 0 或 1 相等概率状态。 测量时,叠加的量子比特应返回大致相等数量的 ZeroOne 结果。

      在前一个具有 TestBellState 的单元中,在 for 循环内添加 H 运算:

          for test in 1..count {
              use (q1, q2) = (Qubit(), Qubit());   
              for test in 1..count {
                  SetQubitState(initial, q1);
                  SetQubitState(Zero, q2);
                  H(q1);                // Add the H operation after initialization and before measurement
                  // measure each qubit
                  let resultQ1 = M(q1);            
                  let resultQ2 = M(q2); 
      

      %simulate 命令中再次将量子比特初始化为 1,单击“全部运行”,然后可以看到第一个量子比特的叠加结果:

      %simulate TestBellState count=1000 initial=1
      
      q1:Zero, One  q2:Zero, One
      (523, 477, 1000, 0)      // results will vary
      

      每次运行该程序,第一个量子比特的结果略有不同,但在大约 50% 的时间为 One,大约 50% 的时间为 Zero,而第二个量子比特的结果始终保持为 Zero

      Q1:Zero/One  Q2:Zero/One
      (510, 490, 1000, 0)
      

      将第一个量子比特初始化为 Zero 会返回类似的结果。

      %simulate TestBellState count=1000 initial=0
      
      Q1:Zero/One  Q2:Zero/One
      (504, 496, 1000, 0)
      

      纠缠两个量子比特

      如前所述,纠缠的量子比特的连接方式使得它们不能相互独立地描述。 也就是说,无论纠缠对中的一个量子比特发生了什么运算,另一个量子比特也会发生相同的运算。 这样,只需测量一个量子比特的状态,就能知道另一个量子比特的最终状态,而无需测量它。 (此示例使用两个量子比特;但是,也可以纠缠三个或更多量子比特)。

      Q# 提供了 CNOT(Controlled-NOT 的缩写)运算用于实现纠缠。 对两个量子比特运行此操作的结果是,在第一个量子比特是 One 的情况下翻转第二个量子比特。

      for 循环中紧接在 H 运算之后添加 CNOT 运算。 TestBellState 运算现在应如下所示:

      operation TestBellState(count : Int, initial : Result) : (Int, Int, Int, Int) {
          mutable numOnesQ1 = 0;
          mutable numOnesQ2 = 0;
          // allocate the qubits
          use (q1, q2) = (Qubit(), Qubit());   
          for test in 1..count {
              SetQubitState(initial, q1);
              SetQubitState(Zero, q2);
              H(q1);
              CNOT(q1, q2);                   // added CNOT operation
              // measure each qubit
              let resultQ1 = M(q1);            
              let resultQ2 = M(q2);           
              // Count the number of 'Ones':
              if resultQ1 == One {
                  set numOnesQ1 += 1;
              if resultQ2 == One {
                  set numOnesQ2 += 1;
          // reset the qubits
          SetQubitState(Zero, q1);             
          SetQubitState(Zero, q2);
          // Return number of |0> states, number of |1> states
          Message("q1:Zero, One  q2:Zero, One");
          return (count - numOnesQ1, numOnesQ1, count - numOnesQ2, numOnesQ2 );
      

      单击“全部运行”以运行更新的运算,应会看到:

      Q1:Zero/One  Q2:Zero/One
      (502, 498, 502, 498)      // actual results will vary
      

      第一个量子比特的统计信息没有变化(测量后返回 ZeroOne 的概率各为50%),但第二个量子比特的测量结果始终与第一个量子比特的测量结果相同,而不管该量子比特初始化到了哪种状态。 CNOT 运算已将两个量子比特纠缠在一起,因此,无论其中一个量子比特发生了什么运算,另一个量子比特也会发生这种运算。

    8. 使用首选语言和开发环境安装 Quantum 开发工具包 (QDK)
    9. 如果已安装 QDK,请确保将其更新至最新版本。
    10. Q# 应用程序C# 宿主程序创建一个名为 Bell 的 Q# 项目。 或者,可以直接在 Juptyer Notebook 中或从 Python 宿主程序运行 Q# 代码。
    11. 本教程介绍以下操作:

    12. 创建 Q# 运算以测量量子比特并将其初始化为所需状态。
    13. 创建量子比特并测试程序。
    14. 将量子比特置于叠加状态。
    15. 纠缠一对量子比特。
    16. 使用测量初始化量子比特

      第一步是定义一个 Q# 运算,用于将量子比特初始化为已知状态。 可以调用此运算以将量子比特设置为经典状态,这意味着它要么在 100% 的时间返回 Zero,要么在 100% 的时间返回 OneZeroOne 是表示对量子比特进行测量时仅有的两个可能结果的 Q# 值。

      在项目中,将 Program.qs 的内容替换为以下代码:

         namespace Bell {
             open Microsoft.Quantum.Intrinsic;
             open Microsoft.Quantum.Canon;
             operation SetQubitState(desired : Result, target : Qubit) : Unit {
                 if desired != M(target) {
                     X(target);
      

      该代码示例引入了用于变换量子比特状态的两个标准运算(MX)。

      SetQubitState 运算:

    17. 采用两个参数:一个名为 desired 的类型 Result,表示量子比特的所需状态(0 或 1);类型 Qubit
    18. 执行测量运算 M,以测量量子比特的状态(ZeroOne),并将结果与 desired 中指定的值进行比较。
    19. 如果测量结果与比较值不匹配,则运行 X 运算,以将量子比特的状态翻转为返回 ZeroOne 的测量的概率反转位置。 这样,SetQubitState 会始终将目标量子比特置于所需状态下。
    20. 接下来,为了演示 SetQubitState 运算的效果,请创建名为 TestBellState 的另一个运算。

      将以下运算添加到 Program.qs 文件中的 SetQubitState 运算后面:

      operation TestBellState(count : Int, initial : Result) : (Int, Int, Int, Int) {
          mutable numOnesQ1 = 0;
          mutable numOnesQ2 = 0;
          // allocate the qubits
          use (q1, q2) = (Qubit(), Qubit());   
          for test in 1..count {
              SetQubitState(initial, q1);
              SetQubitState(Zero, q2);
              // measure each qubit
              let resultQ1 = M(q1);            
              let resultQ2 = M(q2);           
              // Count the number of 'Ones' we saw:
              if resultQ1 == One {
                  set numOnesQ1 += 1;
              if resultQ2 == One {
                  set numOnesQ2 += 1;
          // reset the qubits
          SetQubitState(Zero, q1);             
          SetQubitState(Zero, q2);
          // Return times we saw |0>, times we saw |1>
          Message("q1:Zero, One  q2:Zero, One");
          return (count - numOnesQ1, numOnesQ1, count - numOnesQ2, numOnesQ2 );
      

      TestBellState 运算:

    21. 采用两个参数:count,即测量的运行次数;initial,即要将量子比特初始化到的所需状态。
    22. 调用 use 语句以初始化两个量子比特。
    23. 循环 count 迭代。 对于每次循环,它将
      1. 调用 SetQubitState 以便对第一个量子比特设置指定的 initial 值。
      2. 再次调用 SetQubitState 以将第二个量子比特设置为 Zero 状态。
      3. 使用 M 运算来测量每个量子比特。
      4. 存储每个量子比特的、返回 One 的测量数目。
      5. 循环完成后,它会再次调用 SetQubitState 以将量子比特重置为已知状态 (Zero),使其他对象能够以已知状态分配量子比特。 这是 use 语句所必需的。
      6. 最后,它使用 Message 函数在返回结果之前将消息输出到控制台。
      7. 通过命令提示符运行代码

        在继续执行叠加和纠缠过程之前,测试到目前为止的代码,以查看量子比特的初始化和测量。

        若要将代码作为独立程序运行,当你运行 dotnet run 命令时,Q# 编译器需要知道在何处启动程序。 这是通过在 Q# 文件中你要运行的运算前面直接添加 @EntryPoint() 来完成的:在本例中为 TestBellState 运算。

        只有独立的 Q# 程序才需要 @EntryPoint()。 在 Jupyter Notebook 中运行 Q# 程序或者从 Python.NET 宿主文件调用 Q# 程序时,不需要此参数,如果包含它,会引发错误。

        program.qs 文件现在应如下所示:

        namespace Bell {
            open Microsoft.Quantum.Intrinsic;
            open Microsoft.Quantum.Canon;
               operation SetQubitState(desired : Result, target : Qubit) : Unit {
                   if desired != M(target) {
                       X(target);
            @EntryPoint()
            operation TestBellState(count : Int, initial : Result) : (Int, Int, Int, Int) {
                mutable numOnesQ1 = 0;
                mutable numOnesQ2 = 0;
                // allocate the qubits
                use (q1, q2) = (Qubit(), Qubit());   
                for test in 1..count {
                    SetQubitState(initial, q1);
                    SetQubitState(Zero, q2);
                    // measure each qubit
                    let resultQ1 = M(q1);            
                    let resultQ2 = M(q2);           
                    // Count the number of 'Ones' we saw:
                    if resultQ1 == One {
                        set numOnesQ1 += 1;
                    if resultQ2 == One {
                        set numOnesQ2 += 1;
                // reset the qubits
                SetQubitState(Zero, q1);             
                SetQubitState(Zero, q2);
                // Return times we saw |0>, times we saw |1>
                Message("q1:Zero, One  q2:Zero, One");
                return (count - numOnesQ1, numOnesQ1, count - numOnesQ2, numOnesQ2 );
        

        若要运行该程序,需要从命令提示符指定 countinitial 参数。 例如,--count 1000--initial One 将第一个量子比特初始化为 One 并测量每个量子比特 1000 次。 运行以下命令:

        dotnet run --count 1000 --initial One
        

        你应会看到以下输出:

        Q1:Zero/One  Q2:Zero/One
        (0, 1000, 1000, 0)
        

        由于尚未操作量子比特,因此它们保留了初始值:第一个量子比特每次返回 One,第二个量子比特返回 Zero

        如果使用 --initial Zero 运行该程序,则应会注意到,第一个量子比特每次也返回 Zero

        dotnet run --count 1000 --initial Zero
        
        Q1:Zero/One  Q2:Zero/One
        (1000, 0, 1000, 0)
        

        将量子比特置于叠加状态

        目前,程序中的量子比特都处于经典状态,即,要么为 1,要么为 0。 之所以知道这一点,是因为该程序会将量子比特初始化为已知状态,并且你未添加任何过程来操作量子比特。 在纠缠量子比特之前,需要将第一个量子比特置于叠加状态,在此状态下,量子比特的测量会在 50% 的时间返回 Zero,在 50% 的时间返回 One。 从概念上讲,可将量子比特视为介于 ZeroOne 之间的一种中间状态。

        Q# 提供了 H (Hadamard) 运算用于将量子比特置于叠加状态。 回顾前面的使用测量初始化量子比特过程中的 X 运算,它将量子比特从 0 翻转为 1(或反之);H 运算将量子比特中途翻转为 0 或 1 相等概率状态。 测量时,叠加的量子比特应返回大致相等数量的 ZeroOne 结果。

        修改 TestBellState 运算中的代码以包含 H 运算:

            for test in 1..count {
                use (q1, q2) = (Qubit(), Qubit());   
                for test in 1..count {
                    SetQubitState(initial, q1);
                    SetQubitState(Zero, q2);
                    H(q1);                // Add the H operation after initialization and before measurement
                    // measure each qubit
                    let resultQ1 = M(q1);            
                    let resultQ2 = M(q2); 
        

        现在,当你运行该程序时,可以看到第一个量子比特的叠加结果:

        dotnet run --count 1000 --initial One
        
        Q1:Zero/One  Q2:Zero/One
        (523, 477, 1000, 0)      // results will vary
        

        每次运行该程序,第一个量子比特的结果略有不同,但在大约 50% 的时间为 One,大约 50% 的时间为 Zero,而第二个量子比特的结果始终保持为 Zero

        dotnet run --count 1000 --initial One
        
        Q1:Zero/One  Q2:Zero/One
        (510, 490, 1000, 0)
        

        将第一个量子比特初始化为 Zero 会返回类似的结果。

        dotnet run --count 1000 --initial Zero
        
        Q1:Zero/One  Q2:Zero/One
        (504, 496, 1000, 0)
        

        纠缠两个量子比特

        如前所述,纠缠的量子比特的连接方式使得它们不能相互独立地描述。 也就是说,无论一个量子比特发生了什么运算,纠缠的量子比特也会发生这种运算。 这样,只需测量一个量子比特的状态,就能知道另一个量子比特的最终状态,而无需测量它。 (此示例使用两个量子比特;但是,也可以纠缠三个或更多量子比特)。

        Q# 提供了 CNOT(Controlled-NOT 的缩写)运算用于实现纠缠。 对两个量子比特运行此操作的结果是,在第一个量子比特是 One 的情况下翻转第二个量子比特。

        在程序中紧接在 H 运算的后面添加 CNOT 运算。 完整程序应如下所示:

        namespace Bell {
            open Microsoft.Quantum.Intrinsic;
            open Microsoft.Quantum.Canon;
               operation SetQubitState(desired : Result, target : Qubit) : Unit {
                   if desired != M(target) {
                       X(target);
            @EntryPoint()
            operation TestBellState(count : Int, initial : Result) : (Int, Int, Int, Int) {
                mutable numOnesQ1 = 0;
                mutable numOnesQ2 = 0;
                // allocate the qubits
                use (q1, q2) = (Qubit(), Qubit());   
                for test in 1..count {
                    SetQubitState(initial, q1);
                    SetQubitState(Zero, q2);
                    H(q1);            
                    CNOT(q1, q2);      // Add the CNOT operation after the H operation
                    // measure each qubit
                    let resultQ1 = M(q1);            
                    let resultQ2 = M(q2);           
                    // Count the number of 'Ones' we saw:
                    if resultQ1 == One {
                        set numOnesQ1 += 1;
                    if resultQ2 == One {
                        set numOnesQ2 += 1;
                // reset the qubits
                SetQubitState(Zero, q1);             
                SetQubitState(Zero, q2);
                // Return times we saw |0>, times we saw |1>
                Message("q1:Zero, One  q2:Zero, One");
                return (count - numOnesQ1, numOnesQ1, count - numOnesQ2, numOnesQ2 );
        

        现在,当你运行该程序时:

        dotnet run --count 1000 --initial One
        
        Q1:Zero/One  Q2:Zero/One
        (502, 498, 502, 498)
        

        第一个量子比特的统计信息没有变化(测量后返回 ZeroOne 的概率各为50%),但第二个量子比特的测量结果始终与第一个量子比特的测量结果相同。 CNOT 运算已将两个量子比特纠缠在一起,因此,无论其中一个量子比特发生了什么运算,另一个量子比特也会发生这种运算。

        继续探索其他量子算法和技术:

      8. 教程实现 Grover 的搜索算法介绍了如何编写一个使用 Grover 搜索算法解决图形颜色问题的 Q# 程序。
      9. 教程在 Q# 中编写和模拟量子比特级程序介绍了如何编写一个直接寻址特定量子比特的 Q# 程序。
      10. Quantum Katas 是基于 Jupyter Notebook 的自定进度的教程和编程练习,旨在同时教授量子计算的元素和 Q# 编程。
  •