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

教程:使用 Q# 探索量子纠缠

在本教程中,你将编写一个 Q# 程序,该程序将两个量子比特准备成特定的量子状态,对量子比特进行操作以使它们相互纠缠,并通过测量来展示叠加和纠缠的效果。 逐步构建你的Q#程序,引入量子比特状态、量子操作和测量。

在开始之前,请查看以下量子计算概念:

  • 经典位保存单个二进制值(如 0 或 1),但量子比特可以叠加两种状态(0 和 1)。 每个可能的量子比特状态都由一组概率振幅描述。
  • 测量量子比特的状态时,始终获得 0 或 1。 每个结果的概率取决于在进行度量时定义叠加状态的概率振幅。
  • 可以纠缠多个量子比特,这样就不能彼此独立地描述它们。 在纠缠对中测量一个量子比特时,还可以获取有关另一个量子比特的信息,而无需测量它。

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

  • 创建 Q# 操作以将量子比特初始化为所需状态。
  • 将量子比特置于叠加状态。
  • 纠缠一对量子比特。
  • 测量量子比特并观察结果。

提示

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

先决条件

若要使用 Copilot for Azure Quantum 运行代码示例,必须具有Microsoft(MSA)电子邮件帐户。

有关适用于 Azure Quantum 的 Copilot 的详细信息,请参阅 探索 Azure Quantum

将量子位初始化为已知状态

第一步是定义一个将Q#量子位初始化为所需经典比特状态(0 或 1)的操作。 该操作对一般量子态的量子比特进行测量,它返回Q#Result类型值,该值要么是Zero,要么是One。 如果度量结果与所需状态不同,则操作将翻转状态,以便操作每次返回所需状态。

打开 Copilot for Azure Quantum,清除默认代码,然后将以下代码复制到代码编辑器窗口中。 无法单独运行此代码,因为它尚不是完整的 Q# 程序。

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

该代码示例引入了两个标准Q#操作:MX,用于转换量子比特的状态。

下面是关于SetQubitState操作工作原理的详细说明:

  1. 采用两个参数:一个 Result 类型参数 desired,它表示量子比特应达到的状态(ZeroOne),以及一个 Qubit 类型参数。
  2. 执行度量运算, M它测量量子比特(ZeroOne)的状态,并将结果与你传递 desired的值进行比较。
  3. 如果测量结果与值 desired 不匹配,则对量子比特施加 X 操作。 此操作将翻转量子比特的状态,使得测量 ZeroOne 的概率倒转。

编写测试操作以测试 Bell 状态

要在Q#程序中调用SetQubitState操作,请创建另一个名为Main的操作。 此作分配两个量子比特,调用 SetQubitState 将第一个量子比特设置为已知状态,然后测量量子比特以查看结果。

在执行SetQubitState 操作后,将以下代码复制到代码编辑器窗口中。

operation Main() : (Int, Int, Int, Int) {
    mutable numOnesQ1 = 0;
    mutable numOnesQ2 = 0;
    let count = 1000;
    let initial = One;

    // 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' returned:
        if resultQ1 == One {
            numOnesQ1 += 1;
        }
        if resultQ2 == One {
            numOnesQ2 += 1;
        }
    }

    // reset the qubits
    SetQubitState(Zero, q1);             
    SetQubitState(Zero, q2);
    

    // Display the times that |0> is returned, and times that |1> is returned
    Message($"Q1 - Zeros: {count - numOnesQ1}");
    Message($"Q1 - Ones: {numOnesQ1}");
    Message($"Q2 - Zeros: {count - numOnesQ2}");
    Message($"Q2 - Ones: {numOnesQ2}");
    return (count - numOnesQ1, numOnesQ1, count - numOnesQ2, numOnesQ2 );
}

在代码中,变量countinitial分别设置为1000One分别。 这会将第一个量子比特初始化为 One,并测量每个量子比特 1000 次。

Main 操作执行以下操作:

  1. 设置镜头数(count)和初始量子比特状态(One)的变量。
  2. 调用 use 语句以初始化两个量子比特。
  3. 循环实验 count 次。
  4. 在循环中,调用 SetQubitState 以设置第一个量子位上的指定 initial 值,然后再次调用 SetQubitState 以将第二个量子位设置为 Zero 状态。
  5. 在循环中应用 M 操作来测量每个量子比特,然后存储返回 One 的每个量子比特的测量次数。
  6. 循环完成后,再次调用 SetQubitState 以将量子比特重置为已知状态(Zero)。 必须重置使用语句分配的 use 量子位。
  7. 调用函数 Message 以在输出窗口中打印结果。

在 Copilot for Azure Quantum 中运行代码

在编写叠加和纠缠代码之前,请测试当前程序以查看量子比特的初始化和度量。

若要将代码作为独立程序运行, Q# Copilot 中的编译器需要知道从何处启动程序。 由于未指定命名空间,编译器会将默认入口点识别为 Main 作。 有关详细信息,请参阅 Projects 和隐式命名空间

程序 Q# 现在如下所示:

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

operation Main() : (Int, Int, Int, Int) {
    mutable numOnesQ1 = 0;
    mutable numOnesQ2 = 0;
    let count = 1000;
    let initial = One;

    // 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' returned:
        if resultQ1 == One {
            numOnesQ1 += 1;
        }
        if resultQ2 == One {
            numOnesQ2 += 1;
        }
    }

    // reset the qubits
    SetQubitState(Zero, q1);             
    SetQubitState(Zero, q2);
        
    
    // Display the times that |0> is returned, and times that |1> is returned
    Message($"Q1 - Zeros: {count - numOnesQ1}");
    Message($"Q1 - Ones: {numOnesQ1}");
    Message($"Q2 - Zeros: {count - numOnesQ2}");
    Message($"Q2 - Ones: {numOnesQ2}");
    return (count - numOnesQ1, numOnesQ1, count - numOnesQ2, numOnesQ2 );
}

将完整的代码示例复制并粘贴到 Copilot for Azure Quantum 代码窗口中,将拍摄次数的滑块设置为“1”,然后选择“ 运行”。 结果显示在直方图和 结果 字段中。

Q1 - Zeros: 0
Q1 - Ones: 1000
Q2 - Zeros: 1000
Q2 - Ones: 0

程序尚未修改量子比特状态,因此第一个量子比特的度量始终返回 One,第二个量子比特始终返回 Zero

如果将值 initial 更改为 Zero 并再次运行程序,则第一个量子位也始终返回 Zero

Q1 - Zeros: 1000
Q1 - Ones: 0
Q2 - Zeros: 1000
Q2 - Ones: 0

将量子位放入叠加状态

目前,程序中的量子比特处于经典状态(1 或 0)就像常规计算机上的位一样。 若要纠缠量子比特,必须先将其中一个量子位置于相等叠加状态。 在相等叠加状态下测量量子比特时,返回 Zero 和 返回 One 的几率均为 50%。

若要将量子比特置于叠加状态,请使用 Hadamard 运算Q#H。 该H操作将处于纯ZeroOne状态的量子比特转换为介于ZeroOne之间的一种中间状态。

修改操作中的 Main 代码。 将初始值重置为One,然后插入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); 
        ...

再次运行程序。 由于第一个量子位在测量时处于相等的叠加态,因此 ZeroOne 的结果接近 50/50。 例如,输出如下所示:

Q1 - Zeros: 523
Q1 - Ones: 477
Q2 - Zeros: 1000
Q2 - Ones: 0

每次运行程序时,第一个量子比特的结果会略有不同,但接近 50% One 和 50% Zero,而第二个量子比特的结果仍始终存在 Zero

将第一个量子比特初始化为 Zero 而不是 One 再次运行程序。 你获得类似的结果,因为H操作将纯Zero状态和纯One状态转换为相等的叠加态。

注意

若要查看叠加结果在拍摄分布上的变化,请移动适用于 Azure Quantum 的 Copilot 中的滑块并增加拍摄次数。

纠缠两个量子比特

纠缠量子比特相互关联,无法相互独立描述。 测量一个纠缠量子比特的状态时,还知道另一个量子比特的状态而不测量它。 本教程使用包含两个纠缠量子比特的示例,但也可以纠缠三个或多个量子比特。

若要创建纠缠状态,请使用 Q#CNOT 或“Controlled-NOT”操作。 在将 CNOT 应用于两个量子比特时,一个是控制量子比特,另一个是目标量子比特。 如果控制量子比特的状态为 One,则 CNOT 操作将翻转目标量子比特的状态。 否则,CNOT 对量子位不执行任何作用。

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

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

operation Main() : (Int, Int, Int, Int) {
    mutable numOnesQ1 = 0;
    mutable numOnesQ2 = 0;
    let count = 1000;
    let initial = Zero;

    // 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' returned:
        if resultQ1 == One {
            numOnesQ1 += 1;
        }
        if resultQ2 == One {
            numOnesQ2 += 1;
        }
    }

    // reset the qubits
    SetQubitState(Zero, q1);             
    SetQubitState(Zero, q2);
    

    // Display the times that |0> is returned, and times that |1> is returned
    Message($"Q1 - Zeros: {count - numOnesQ1}");
    Message($"Q1 - Ones: {numOnesQ1}");
    Message($"Q2 - Zeros: {count - numOnesQ2}");
    Message($"Q2 - Ones: {numOnesQ2}");
    return (count - numOnesQ1, numOnesQ1, count - numOnesQ2, numOnesQ2 );
 }

运行程序并查看输出。 每次运行程序时,结果都会略有变化。

Q1 - Zeros: 502
Q1 - Ones: 498
Q2 - Zeros: 502
Q2 - Ones: 498

第一个量子比特的统计信息仍然显示大约 50% 的几率测量 OneZero,但第二个量子比特的测量结果现在并不总是 Zero。 每个量子比特的Zero结果数与One结果数相同。 第二个量子比特的度量结果始终与第一个量子比特的结果相同,因为两个量子比特纠缠在一起。 如果第一个量子比特被测量为Zero,则纠缠的量子比特也必须是Zero。 如果第一个量子位被测量为 One ,那么纠缠量子位也必须是 One

先决条件

若要在本地开发环境中开发和运行代码示例,请安装以下工具:

创建新 Q# 文件

  1. 在 VS Code 中,打开 “文件 ”菜单,然后选择“ 新建文本文件 ”以创建新文件。
  2. 将文件另存为 CreateBellStates.qs。 此文件是编写程序代码的位置 Q# 。

将量子位初始化为已知状态

第一步是定义一个Q# 操作,将量子位初始化为所需的经典状态(0 或 1)。 该操作测量处于普遍量子态的量子比特,并返回ZeroOne类型的Q#Result值。 如果度量结果与所需状态不同,则操作将翻转状态,以使操作在 100% 的情况下返回所需状态。

打开 CreateBellStates.qs 并复制以下代码:

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

该代码示例引入了两个标准 Q# 操作,MX,用于转换量子比特的状态。

以下是SetQubitState操作工作原理的详细说明:

  1. 采用两个参数:一个名为 desiredResult 类型参数,表示量子比特的期望状态(ZeroOne),以及一个 Qubit 类型参数。
  2. 执行度量运算, M它测量量子比特(ZeroOne)的状态,并将结果与你传递 desired的值进行比较。
  3. 如果度量结果与desired 的值不匹配,则对量子比特应用X 操作。 此操作会翻转量子比特的状态,以便 ZeroOne 的测量概率互换。

编写测试操作以测试 Bell 状态

若要调用Q#程序中的SetQubitState操作,请创建名为Main的另一个操作。 此作分配两个量子比特,调用 SetQubitState 将第一个量子比特设置为已知状态,然后测量量子比特以查看结果。

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

operation Main() : (Int, Int, Int, Int) {
    mutable numOnesQ1 = 0;
    mutable numOnesQ2 = 0;
    let count = 1000;
    let initial = One;

    // 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' returned:
        if resultQ1 == One {
            numOnesQ1 += 1;
        }
        if resultQ2 == One {
            numOnesQ2 += 1;
        }
    }

    // reset the qubits
    SetQubitState(Zero, q1);             
    SetQubitState(Zero, q2);
    

    // Display the times that |0> is returned, and times that |1> is returned
    Message($"Q1 - Zeros: {count - numOnesQ1}");
    Message($"Q1 - Ones: {numOnesQ1}");
    Message($"Q2 - Zeros: {count - numOnesQ2}");
    Message($"Q2 - Ones: {numOnesQ2}");
    return (count - numOnesQ1, numOnesQ1, count - numOnesQ2, numOnesQ2 );
}

在代码中,变量countinitial分别设置为1000One分别。 这会将第一个量子比特初始化为 One,并测量每个量子比特 1000 次。

Main 操作执行以下操作:

  1. 设置镜头数(count)和初始量子比特状态(One)的变量。
  2. 调用 use 语句以初始化两个量子比特。
  3. 循环执行试验 count 次。
  4. 在循环中,调用 SetQubitState 以设置第一个量子位上的指定 initial 值,然后再次调用 SetQubitState 以将第二个量子位设置为 Zero 状态。
  5. 在循环中,应用M操作来测量每个量子比特,然后存储每个量子比特返回One的测量次数。
  6. 循环完成后,再次调用 SetQubitState 以将量子比特重置为已知状态(Zero)。 您必须重置使用use语句分配的量子位。
  7. 调用函数 Message 以在控制台中打印结果。

运行代码

在编写叠加和纠缠代码之前,请测试当前程序以查看量子比特的初始化和度量。

若要将代码作为独立程序运行, Q# 编译器需要知道从何处启动程序。 由于未指定命名空间,编译器会将默认入口点识别为 Main 作。 有关详细信息,请参阅 Projects 和隐式命名空间

文件 CreateBellStates.qs 现在如下所示:

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

operation Main() : (Int, Int, Int, Int) {
    mutable numOnesQ1 = 0;
    mutable numOnesQ2 = 0;
    let count = 1000;
    let initial = One;

    // 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' returned:
        if resultQ1 == One {
            numOnesQ1 += 1;
        }
        if resultQ2 == One {
            numOnesQ2 += 1;
        }
    }

    // reset the qubits
    SetQubitState(Zero, q1);             
    SetQubitState(Zero, q2);
        
    
    // Display the times that |0> is returned, and times that |1> is returned
    Message($"Q1 - Zeros: {count - numOnesQ1}");
    Message($"Q1 - Ones: {numOnesQ1}");
    Message($"Q2 - Zeros: {count - numOnesQ2}");
    Message($"Q2 - Ones: {numOnesQ2}");
    return (count - numOnesQ1, numOnesQ1, count - numOnesQ2, numOnesQ2 );
}

若要运行程序,请从代码镜头中选择Main命令,或输入Ctrl + F5。 程序在默认模拟器上运行Main操作。

输出将显示在调试控制台中。

Q1 - Zeros: 0
Q1 - Ones: 1000
Q2 - Zeros: 1000
Q2 - Ones: 0

程序尚未修改量子比特状态,因此第一个量子比特的度量始终返回 One,第二个量子比特始终返回 Zero

如果将值 initial 更改为 Zero 并再次运行程序,则第一个量子位也始终返回 Zero

Q1 - Zeros: 1000
Q1 - Ones: 0
Q2 - Zeros: 1000
Q2 - Ones: 0

将量子位放入叠加状态

目前,程序中的量子比特处于经典状态(1 或 0)就像常规计算机上的位一样。 若要纠缠量子比特,必须先将其中一个量子位置于相等叠加状态。 在测量处于相等叠加态的量子比特时,有 50% 的机会返回 Zero,以及 50% 的机会返回 One

若要将量子位(qubit)置于叠加态,请使用Q#H,即Hadamard运算。 该H操作将处于纯ZeroOne状态的量子比特转换为介于ZeroOne之间的一种中间状态。

修改在操作中的 Main 代码。 将初始值重置为 One 并为操作 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); 
        ...

再次运行程序。 因为第一个量子位在测量时处于等概率叠加态,你得到的ZeroOne结果接近于50/50。 例如,输出如下所示:

Q1 - Zeros: 523
Q1 - Ones: 477
Q2 - Zeros: 1000
Q2 - Ones: 0

每次运行程序时,第一个量子比特的结果会略有不同,但接近 50% One 和 50% Zero,而第二个量子比特的结果仍始终存在 Zero

将第一个量子比特初始化为 Zero 而不是 One 再次运行程序。 你获得类似的结果,因为操作H 将纯Zero 状态和纯One 状态转换为相等的叠加状态。

纠缠两个量子比特

纠缠量子比特相互关联,无法相互独立描述。 测量一个纠缠量子比特的状态时,还知道另一个量子比特的状态而不测量它。 本教程使用包含两个纠缠量子比特的示例,但也可以纠缠三个或多个量子比特。

若要创建纠缠状态,请使用 Q#CNOT,或 Controlled-NOT 作。 当您将 CNOT 应用于两个量子比特时,一个量子比特是控制量子比特,另一个是目标量子比特。 如果控制量子比特的状态为 One,那么 CNOT 操作将翻转目标量子比特的状态。 否则,CNOT 对量子位不执行任何操作。

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

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

operation Main() : (Int, Int, Int, Int) {
    mutable numOnesQ1 = 0;
    mutable numOnesQ2 = 0;
    let count = 1000;
    let initial = Zero;

    // 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' returned:
        if resultQ1 == One {
            numOnesQ1 += 1;
        }
        if resultQ2 == One {
            numOnesQ2 += 1;
        }
    }

    // reset the qubits
    SetQubitState(Zero, q1);             
    SetQubitState(Zero, q2);
    

    // Display the times that |0> is returned, and times that |1> is returned
    Message($"Q1 - Zeros: {count - numOnesQ1}");
    Message($"Q1 - Ones: {numOnesQ1}");
    Message($"Q2 - Zeros: {count - numOnesQ2}");
    Message($"Q2 - Ones: {numOnesQ2}");
    return (count - numOnesQ1, numOnesQ1, count - numOnesQ2, numOnesQ2 );
}

运行程序并查看输出。 程序每次运行时,结果都会有非常轻微的变化。

Q1 - Zeros: 502
Q1 - Ones: 498
Q2 - Zeros: 502
Q2 - Ones: 498

第一个量子比特的统计信息仍然显示,测量OneZero的机会各约为50%,但现在第二个量子比特的测量结果并不总是Zero。 每个量子比特的Zero结果数和One结果数相同。 第二个量子比特的度量结果始终与第一个量子比特的结果相同,因为两个量子比特纠缠在一起。 如果测量第一个量子位为 Zero,那么纠缠量子比特也必须为 Zero。 如果第一个量子比特被测量为 One,则纠缠量子比特也必须是 One

绘制频率直方图

若要绘制显示多次运行程序时结果分布的频率直方图,请完成以下步骤:

  1. 在 VS Code 中打开文件 CreateBellStates.qs

  2. 打开 “视图 ”菜单,然后选择 “命令面板”。

  3. 输入 直方图 以显示 QDK:运行文件并显示直方图 选项。 或者,从位于操作之前的CodeLens选项中选择Main命令。 然后,输入一些镜头(例如 100)。 直方图将在 Q# 新选项卡中打开。

    直方图中的每个条形图对应于纠缠线路运行 1000 次时可能的结果。 条形图的高度表示结果发生的次数。 例如,下面的直方图显示具有 50 个唯一结果的分布。 请注意,对于每个结果,第一个和第二个量子比特的度量结果始终相同。

    Visual Studio Code 中直方图窗口的 Q# 屏幕截图。

    提示

    若要放大直方图,请使用鼠标滚轮或触控板手势。 若要在放大时平移图表,请按住 Alt ,同时滚动。

  4. 选择一个条形图以显示总拍摄数中达到该结果的比例。

  5. 选择左上角的设置 图标 以显示可视化效果选项。

    Visual Studio Code 中 Q# 直方图窗口的显示设置的屏幕截图。

  6. 再次运行代码,但这次运行 1000 次实验。 随着拍摄次数的增加,结果分布接近正态分布。

浏览其他 Q# 教程: