Qiskit汉化-3.2 Deutsch-Jozsa Algorithm

Deutsch-Jozsa算法

在本节中,我们首先介绍Deutsch-Jozsa问题,以及求解该问题的经典算法和量子算法。然后我们使用Qiskit实现量子算法,并在模拟器和设备上运行它。

1.介绍

在参考文献[1]中首次引入的Deutsch-Jozsa算法是第一个性能优于最优经典算法的量子算法实例。它表明,使用量子计算机作为特定问题的计算工具可能有优势。

1.1 Deutsch-Jozsa问题

我们得到了一个隐藏的布尔函数 f ,它接受一个比特字符串作为输入,并返回 01 ,即:

f(\{x_0,x_1,x_2,...\}) \rightarrow 0 \textrm{ or } 1 \textrm{ , where } x_n \textrm{ is } 0 \textrm{ or } 1

给定布尔函数的性质是保证它要么是平衡的,要么是常数的。常数函数对于任何输入返回全 0 或全 1 ,而平衡函数对于所有输入的一半返回 0 ,另一半返回 1 。我们的任务是确定给定的函数是平衡的还是常数的。

注意,Deutsch-Jozsa问题是单个Deutsch问题的 n 比特扩展。

1.2 经典解决方法

通常,在最好的情况下,对oracle进行两次查询就可以确定隐藏的布尔函数 f(x) 是平衡的:如果我们同时得到 f(0,0,0,…)\rightarrow 0f(1,0,0,…)\rightarrow 1 ,那么我们就知道函数是平衡的,因为我们得到了两个不同的输出。

在最坏的情况下,如果我们尝试的每个输入都看到相同的输出,我们将必须检查所有可能输入的正好一半加上1,以确定 f(x) 是常数。由于可能的输入总数是 2^n ,这意味着我们需要 2^{n-1} + 1 试验输入,以确保 f(x) 在最坏情况下是常数。例如,对于一个 4 位字符串,如果我们检查了 16 可能的组合中的 8 ,得到所有 0 ,仍然有可能 9^{th} 输入返回 1 ,并且 f(x) 是平衡的。从概率上讲,这是一个非常不可能的事件。事实上,如果我们连续地得到相同的结果,我们可以把一个 k 输入的函数是常数函数的概率表示为:

P_\textrm{constant}(k) = 1 - \frac{1}{2^{k-1}} \qquad \textrm{for } 1 < k \leq 2^{n-1}

实际上,如果置信度超过x%,我们可以选择提前截断经典算法。但如果我们想要100%确定,就需要检查 2^{n-1}+1个 输入。

1.3量子解决方案

使用量子计算机,只要调用一次函数 f(x) ,我们就可以100%地解决这个问题,只要我们将函数 f 实现为量子oracle,它将状态 \vert x\rangle \vert y\rangle 映射到 \vert x\rangle \vert y\ oplus f(x)\rangle , 其中 \oplus 是对 2 求模的加法。下面是Deutsch-Jozsa算法的通用电路。

现在,让我们看一下算法的步骤:

1.准备两个量子寄存器。第一个是初始化为 |0⟩n -量子比特寄存器,第二个是初始化为 |1⟩ 的单量子比特寄存器:

\vert \psi_0 \rangle = \vert0\rangle^{\otimes n} \vert 1\rangle

2.对每个量子比特应用哈达玛门:

\vert \psi_1 \rangle = \frac{1}{\sqrt{2^{n+1}}}\sum_{x=0}^{2^n-1} \vert x\rangle \left(|0\rangle - |1 \rangle \right)

3.应用量子oracle \vert x\rangle \vert y\rangle\vert x\rangle \vert y\ oplus f(x)\rangle:

\begin{aligned} \lvert \psi_2 \rangle & = \frac{1}{\sqrt{2^{n+1}}}\sum_{x=0}^{2^n-1} \vert x\rangle (\vert f(x)\rangle - \vert 1 \oplus f(x)\rangle) \\ & = \frac{1}{\sqrt{2^{n+1}}}\sum_{x=0}^{2^n-1}(-1)^{f(x)}|x\rangle ( |0\rangle - |1\rangle ) \end{aligned}

因为对于每个 x,f(x) 要么是 0 ,要么是 1

4.此时,第二个单量子比特寄存器可以被忽略。对第一个寄存器中的每个量子比特应用哈达玛门:

\begin{aligned} \lvert \psi_3 \rangle & = \frac{1}{2^n}\sum_{x=0}^{2^n-1}(-1)^{f(x)} \left[ \sum_{y=0}^{2^n-1}(-1)^{x \cdot y} \vert y \rangle \right] \\ & = \frac{1}{2^n}\sum_{y=0}^{2^n-1} \left[ \sum_{x=0}^{2^n-1}(-1)^{f(x)}(-1)^{x \cdot y} \right] \vert y \rangle \end{aligned}

其中 x \cdot y = x_0y_0 \oplus x_1y_1 \oplus \ldots \oplus x_{n-1}y_{n-1} 是按位积的和。

5.测量第一个寄存器。请注意,计算 \vert 0 \rangle ^{\otimes n} = \lvert \frac{1}{2^n}\sum_{x=0}^{2^n-1}(-1)^{f(x)} \rvert^2 的概率,如果 f(x) 是常数,则计算为 1 ;如果 f(x) 是平衡的,则计算为 0

1.4 为什么会这样呢?

  • 常数Oracle

当oracle是常量时,它对输入的量子比特没有影响(直到一个全局相位),并且查询oracle前后的量子态是相同的。由于H门是它自己的逆,在步骤4中,我们反转步骤2以在第一个寄存器中获得 |00\dots 0\rangle 的初始量子态。

H^{\otimes n}\begin{bmatrix} 1 \\ 0 \\ 0 \\ \vdots \\ 0 \end{bmatrix} = \tfrac{1}{\sqrt{2^n}}\begin{bmatrix} 1 \\ 1 \\ 1 \\ \vdots \\ 1 \end{bmatrix} \quad \xrightarrow{\text{after } U_f} \quad H^{\otimes n}\tfrac{1}{\sqrt{2^n}}\begin{bmatrix} 1 \\ 1 \\ 1 \\ \vdots \\ 1 \end{bmatrix} = \begin{bmatrix} 1 \\ 0 \\ 0 \\ \vdots \\ 0 \end{bmatrix}
  • 平衡Oracle

在第2步之后,我们的输入寄存器是所有状态在计算基础上的平等叠加。当oracle是平衡的时候,相位反冲正好给这些状态的一半增加了一个负相位:

U_f \tfrac{1}{\sqrt{2^n}}\begin{bmatrix} 1 \\ 1 \\ 1 \\ \vdots \\ 1 \end{bmatrix} = \tfrac{1}{\sqrt{2^n}}\begin{bmatrix} -1 \\ 1 \\ -1 \\ \vdots \\ 1 \end{bmatrix}

查询oracle后的量子态与查询oracle前的量子态正交。因此,在步骤4中,当应用H门时,我们必须最终得到一个与 |00\dots 0\rangle 正交的量子态。这意味着我们永远不应该测量全零状态。

2. 举例

让我们看一个2比特平衡函数的具体例子:
考虑一个2比特函数 f(x_0,x_1)=x_0 \oplus x_1 ,使得

f(0,0)=0\\\\ f(0,1)=1\\\\ f(1,0)=1\\\\ f(1,1)=0

对应的两比特位的相位oracle是

U_f \lvert x_1, x_0 \rangle = (-1)^{f(x_1, x_0)}\lvert x \rangle

我们现在将通过一个示例状态来检查此oracle是否按预期工作

\lvert \psi_0 \rangle = \lvert 0 0 \rangle_{01} \otimes \lvert 1 \rangle_{2}
  1. 两个量子比特的第一个寄存器初始化为 |00⟩ ,第二个寄存器量子比特初始化为 |1⟩ (注意,我们使用下标0、1和2来索引量子比特。下标“01”表示包含量子比特0和1的寄存器的状态)
\lvert \psi_0 \rangle = \lvert 0 0 \rangle_{01} \otimes \lvert 1 \rangle_{2}
  1. 对所有量子比特应用哈达玛
\lvert \psi_1 \rangle = \frac{1}{2} \left( \lvert 0 0 \rangle + \lvert 0 1 \rangle + \lvert 1 0 \rangle + \lvert 1 1 \rangle \right)_{01} \otimes \frac{1}{\sqrt{2}} \left( \lvert 0 \rangle - \lvert 1 \rangle \right)_{2}
  1. oracle函数可以实现为 \text{Q}_f = CX_{02}CX_{12}
\begin{align*} \lvert \psi_2 \rangle = \frac{1}{2\sqrt{2}} \left[ \lvert 0 0 \rangle_{01} \otimes \left( \lvert 0 \oplus 0 \oplus 0 \rangle - \lvert 1 \oplus 0 \oplus 0 \rangle \right)_{2} \\ + \lvert 0 1 \rangle_{01} \otimes \left( \lvert 0 \oplus 0 \oplus 1 \rangle - \lvert 1 \oplus 0 \oplus 1 \rangle \right)_{2} \\ + \lvert 1 0 \rangle_{01} \otimes \left( \lvert 0 \oplus 1 \oplus 0 \rangle - \lvert 1 \oplus 1 \oplus 0 \rangle \right)_{2} \\ + \lvert 1 1 \rangle_{01} \otimes \left( \lvert 0 \oplus 1 \oplus 1 \rangle - \lvert 1 \oplus 1 \oplus 1 \rangle \right)_{2} \right] \end{align*}
  1. 简化后,我们得到以下结果:
\begin{aligned} \lvert \psi_2 \rangle & = \frac{1}{2\sqrt{2}} \left[ \lvert 0 0 \rangle_{01} \otimes \left( \lvert 0 \rangle - \lvert 1 \rangle \right)_{2} - \lvert 0 1 \rangle_{01} \otimes \left( \lvert 0 \rangle - \lvert 1 \rangle \right)_{2} - \lvert 1 0 \rangle_{01} \otimes \left( \lvert 0 \rangle - \lvert 1 \rangle \right)_{2} + \lvert 1 1 \rangle_{01} \otimes \left( \lvert 0 \rangle - \lvert 1 \rangle \right)_{2} \right] \\ & = \frac{1}{2} \left( \lvert 0 0 \rangle - \lvert 0 1 \rangle - \lvert 1 0 \rangle + \lvert 1 1 \rangle \right)_{01} \otimes \frac{1}{\sqrt{2}} \left( \lvert 0 \rangle - \lvert 1 \rangle \right)_{2} \\ & = \frac{1}{\sqrt{2}} \left( \lvert 0 \rangle - \lvert 1 \rangle \right)_{0} \otimes \frac{1}{\sqrt{2}} \left( \lvert 0 \rangle - \lvert 1 \rangle \right)_{1} \otimes \frac{1}{\sqrt{2}} \left( \lvert 0 \rangle - \lvert 1 \rangle \right)_{2} \end{aligned}
  1. 在第一个寄存器上应用哈达玛
\lvert \psi_3\rangle = \lvert 1 \rangle_{0} \otimes \lvert 1 \rangle_{1} \otimes \left( \lvert 0 \rangle - \lvert 1 \rangle \right)_{2}
  1. 测量前两个量子比特将得到非零值 11 ,表明函数是平衡的。

你可以使用下面的小部件尝试类似的例子。按下按钮添加H门和oracle,重新运行单元格和/或设置case="constant"以尝试不同的oracle。

from qiskit_textbook.widgets import dj_widget
dj_widget(size="small", case="balanced")

3.创造量子Oracles

让我们看看创建量子Oracle的不同方法。

常量函数很简单:

  1. 如果f(x) = 0,则将 I 门应用于寄存器2中的量子比特。

  2. 如果f(x) = 1,则将 X 门应用于寄存器2中的量子比特。

对于平衡函数,我们可以创建许多不同的电路。我们保证电路平衡的方法之一是对寄存器1中的每个量子比特执行CNOT,以寄存器2中的量子比特为目标。例如:

2

在上图中,前三个量子比特构成输入寄存器,底部量子比特是输出寄存器。在下面的表中,我们可以看到哪些输入状态给出了哪些输出:

Input states that output 0 Input States that output 1
000 001
011 100
101 010
110 111

我们可以通过将选定的控件包装在X门中来改变结果,同时保持它们的平衡。例如,请看下面的电路及其结果表:

3

Input states that output 0 Input states that output 1
001 000
010 011
100 101
111 110

4. Qiskit实现

我们现在为3位函数实现Deutsch-Jozsa算法,使用常量和平衡oracles。首先进行导入:

# initialization
import numpy as np

# importing Qiskit
from qiskit import IBMQ, Aer
from qiskit.providers.ibmq import least_busy
from qiskit import QuantumCircuit, assemble, transpile

# import basic plot tools
from qiskit.visualization import plot_histogram

接下来,设置oracle输入寄存器的大小:

# set the length of the n-bit input string. 
n = 3

4.1 常数Oracle

让我们从创建一个常量oracle开始,在这种情况下,输入对输出没有影响,所以我们只是随机设置输出量子比特为0或1:

# set the length of the n-bit input string. 
n = 3

const_oracle = QuantumCircuit(n+1)

output = np.random.randint(2)
if output == 1:
    const_oracle.x(n)

const_oracle.draw()

4

4.2 平衡Oracle

balanced_oracle = QuantumCircuit(n+1)

接下来,我们创建一个平衡的oracle。正如我们在1b节中看到的,我们可以通过执行CNOTs,将每个输入量子比特作为控制,将输出比特作为目标来创建一个平衡oracle。我们可以通过将一些控制包在X门中来改变给出0或1的输入状态。让我们首先选择一个长度为n的二进制字符串,指定要包装哪些控件:

b_str = "101"

现在我们有了这个字符串,我们可以用它作为放置X门的键。对于电路中的每个量子比特,如果b_str中对应的数字为1,则放置一个X门,如果数字为0,则不执行任何操作。

balanced_oracle = QuantumCircuit(n+1)
b_str = "101"

# Place X-gates
for qubit in range(len(b_str)):
    if b_str[qubit] == '1':
        balanced_oracle.x(qubit)
balanced_oracle.draw()

6

接下来,我们进行受控非门,将每个输入量子比特作为控制,输出量子比特作为目标:

balanced_oracle = QuantumCircuit(n+1)
b_str = "101"

# Place X-gates
for qubit in range(len(b_str)):
    if b_str[qubit] == '1':
        balanced_oracle.x(qubit)

# Use barrier as divider
balanced_oracle.barrier()

# Controlled-NOT gates
for qubit in range(n):
    balanced_oracle.cx(qubit, n)

balanced_oracle.barrier()
balanced_oracle.draw()

7

最后,我们重复两个单元格的代码,以完成X门中的控件包装:

balanced_oracle = QuantumCircuit(n+1)
b_str = "101"

# Place X-gates
for qubit in range(len(b_str)):
    if b_str[qubit] == '1':
        balanced_oracle.x(qubit)

# Use barrier as divider
balanced_oracle.barrier()

# Controlled-NOT gates
for qubit in range(n):
    balanced_oracle.cx(qubit, n)

balanced_oracle.barrier()

# Place X-gates
for qubit in range(len(b_str)):
    if b_str[qubit] == '1':
        balanced_oracle.x(qubit)

# Show oracle
balanced_oracle.draw()

5

我们刚刚创造了一个平衡的oracle!剩下要做的就是看看Deutsch-Jozsa算法能否解决这个问题。

4.3 完整算法

现在让我们把所有东西放在一起。算法中的第一步是初始化状态 |+⟩ 中的输入量子比特和状态 |−⟩ 中的输出量子比特:

dj_circuit = QuantumCircuit(n+1, n)

# Apply H-gates
for qubit in range(n):
    dj_circuit.h(qubit)

# Put qubit in state |->
dj_circuit.x(n)
dj_circuit.h(n)
dj_circuit.draw()

8

接下来,让我们应用oracle。这里我们应用上面创建的balanced_oracle:

dj_circuit = QuantumCircuit(n+1, n)

# Apply H-gates
for qubit in range(n):
    dj_circuit.h(qubit)

# Put qubit in state |->
dj_circuit.x(n)
dj_circuit.h(n)

# Add oracle
dj_circuit += balanced_oracle
dj_circuit.draw()

9

最后,我们对 n 个输入量子比特执行H门,并测量我们的输入寄存器:

dj_circuit = QuantumCircuit(n+1, n)

# Apply H-gates
for qubit in range(n):
    dj_circuit.h(qubit)

# Put qubit in state |->
dj_circuit.x(n)
dj_circuit.h(n)

# Add oracle
dj_circuit += balanced_oracle

# Repeat H-gates
for qubit in range(n):
    dj_circuit.h(qubit)
dj_circuit.barrier()

# Measure
for i in range(n):
    dj_circuit.measure(i, i)

# Display circuit
dj_circuit.draw()

让我们看看输出:

# use local simulator
aer_sim = Aer.get_backend('aer_simulator')
qobj = assemble(dj_circuit, aer_sim)
results = aer_sim.run(qobj).result()
answer = results.get_counts()

plot_histogram(answer)

11

从上面的结果可以看出,测量000的可能性为0%。这正确地预测了函数是平衡的。

4.4 广义电路

下面,我们提供一个通用函数,创建Deutsch-Jozsa oracle并将其转换为量子门。它接受这样的情况(balanced constant),以及输入寄存器的大小n:

def dj_oracle(case, n):
    # We need to make a QuantumCircuit object to return
    # This circuit has n+1 qubits: the size of the input,
    # plus one output qubit
    oracle_qc = QuantumCircuit(n+1)
    
    # First, let's deal with the case in which oracle is balanced
    if case == "balanced":
        # First generate a random number that tells us which CNOTs to
        # wrap in X-gates:
        b = np.random.randint(1,2**n)
        # Next, format 'b' as a binary string of length 'n', padded with zeros:
        b_str = format(b, '0'+str(n)+'b')
        # Next, we place the first X-gates. Each digit in our binary string 
        # corresponds to a qubit, if the digit is 0, we do nothing, if it's 1
        # we apply an X-gate to that qubit:
        for qubit in range(len(b_str)):
            if b_str[qubit] == '1':
                oracle_qc.x(qubit)
        # Do the controlled-NOT gates for each qubit, using the output qubit 
        # as the target:
        for qubit in range(n):
            oracle_qc.cx(qubit, n)
        # Next, place the final X-gates
        for qubit in range(len(b_str)):
            if b_str[qubit] == '1':
                oracle_qc.x(qubit)

    # Case in which oracle is constant
    if case == "constant":
        # First decide what the fixed output of the oracle will be
        # (either always 0 or always 1)
        output = np.random.randint(2)
        if output == 1:
            oracle_qc.x(n)
    
    oracle_gate = oracle_qc.to_gate()
    oracle_gate.name = "Oracle" # To show when we display the circuit
    return oracle_gate

让我们创建一个函数,接收这个oracle门并对其执行Deutsch-Jozsa算法:

def dj_algorithm(oracle, n):
    dj_circuit = QuantumCircuit(n+1, n)
    # Set up the output qubit:
    dj_circuit.x(n)
    dj_circuit.h(n)
    # And set up the input register:
    for qubit in range(n):
        dj_circuit.h(qubit)
    # Let's append the oracle gate to our circuit:
    dj_circuit.append(oracle, range(n+1))
    # Finally, perform the H-gates again and measure:
    for qubit in range(n):
        dj_circuit.h(qubit)
    
    for i in range(n):
        dj_circuit.measure(i, i)
    
    return dj_circuit

最后,让我们使用这些函数来玩转算法:

n = 4
oracle_gate = dj_oracle('balanced', n)
dj_circuit = dj_algorithm(oracle_gate, n)
dj_circuit.draw()

看看运行这个电路的结果:

transpiled_dj_circuit = transpile(dj_circuit, aer_sim)
qobj = assemble(transpiled_dj_circuit)
results = aer_sim.run(qobj).result()
answer = results.get_counts()
plot_histogram(answer)

13

5.真实设备实验

我们可以在真实的设备上运行电路,如下所示。我们首先寻找能够处理电路的最不繁忙的设备。

该单元使用量子硬件。要在浏览器中运行它,请点击这里进入IBM Quantum体验。

# Load our saved IBMQ accounts and get the least busy backend device with greater than or equal to (n+1) qubits
IBMQ.load_account()
provider = IBMQ.get_provider(hub='ibm-q')
backend = least_busy(provider.backends(filters=lambda x: x.configuration().n_qubits >= (n+1) and
                                   not x.configuration().simulator and x.status().operational==True))
print("least busy backend: ", backend)

该单元使用量子硬件。要在浏览器中运行它,请点击这里进入IBM Quantum体验。

# Run our circuit on the least busy backend. Monitor the execution of the job in the queue
from qiskit.tools.monitor import job_monitor

transpiled_dj_circuit = transpile(dj_circuit, backend, optimization_level=3)
job = backend.run(transpiled_dj_circuit)
job_monitor(job, interval=2)

该单元使用量子硬件。要在浏览器中运行它,请点击这里进入IBM Quantum体验。

# Get the results of the computation
results = job.result()
answer = results.get_counts()

plot_histogram(answer)

可以看到,最可能的结果是1111。其他结果是由于量子计算中的误差。

6.问题

  1. 你能创造一个不同形式的平衡或常数的oracle吗?

  2. 函数dj_problem_oracle(下面)以门的形式返回n = 4的Deutsch-Jozsa oracle。门接收5个量子比特作为输入,其中最后一个量子比特(q_4)是输出量子比特(如上面的oracle示例)。通过给dj_problem_oracle指定1到5之间的不同整数,可以得到不同的oracle。使用Deutsch-Jozsa算法来决定每个oracle是平衡的还是常数的(注意:强烈建议您使用aer_simulator而不是真正的设备来尝试这个例子)。

from qiskit_textbook.problems import dj_problem_oracle
oracle = dj_problem_oracle(1)

7.参考文献

  1. David Deutsch and Richard Jozsa (1992). “Rapid solutions of problems by quantum computation”. Proceedings of the Royal Society of London A. 439: 553–558. doi:10.1098/rspa.1992.0167.

  2. R. Cleve; A. Ekert; C. Macchiavello; M. Mosca (1998). “Quantum algorithms revisited”. Proceedings of the Royal Society of London A. 454: 339–354. doi:10.1098/rspa.1998.0164.