Pular para o conteúdo principal

Bits quânticos, portas e circuitos

nota

Kifumi Numata (19 abr 2024)

Clique aqui para baixar o PDF da aula original. Alguns trechos de código podem estar desatualizados, pois são imagens estáticas.

Tempo aproximado de QPU para rodar esse experimento é de 5 segundos.

1. Introdução

Bits, portas e circuitos são os blocos fundamentais da computação quântica. Você vai aprender computação quântica com o modelo de circuitos usando bits e portas quânticas, além de revisar superposição, medição e emaranhamento.

Nesta aula você vai aprender:

  • Portas de qubit único
  • Esfera de Bloch
  • Superposição
  • Medição
  • Portas de dois qubits e estado emaranhado

Ao final desta aula, você vai aprender sobre profundidade de circuito, que é essencial para a computação quântica em escala de utilidade.

2. Computação como diagrama

Quando trabalhamos com qubits ou bits, precisamos manipulá-los para transformar as entradas que temos nas saídas que precisamos. Para os programas mais simples, com poucos bits, é útil representar esse processo num diagrama chamado de diagrama de circuito.

A figura inferior esquerda é um exemplo de circuito clássico, e a figura inferior direita é um exemplo de circuito quântico. Em ambos os casos, as entradas estão à esquerda e as saídas à direita, enquanto as operações são representadas por símbolos. Os símbolos usados para as operações são chamados de "portas" (gates), principalmente por razões históricas.

"circuito lógico clássico e circuito quântico"

3. Porta quântica de qubit único

3.1 Estado quântico e esfera de Bloch

O estado de um qubit é representado como uma superposição de 0|0\rangle e 1|1\rangle. Um estado quântico arbitrário é representado como

ψ=α0+β1|\psi\rangle =\alpha|0\rangle+ \beta|1\rangle

onde α\alpha e β\beta são números complexos tais que α2+β2=1|\alpha|^2+|\beta|^2=1.

0|0\rangle e 1|1\rangle são vetores no espaço vetorial complexo de duas dimensões:

0=(10),1=(01)|0\rangle = \begin{pmatrix} 1 \\0 \end{pmatrix}, |1\rangle = \begin{pmatrix} 0\\1 \end{pmatrix}

Portanto, um estado quântico arbitrário também pode ser representado como

ψ=α(10)+β(01)=(αβ)|\psi\rangle = \alpha\begin{pmatrix} 1 \\ 0 \end{pmatrix} + \beta\begin{pmatrix}0\\ 1 \end{pmatrix} = \begin{pmatrix} \alpha \\ \beta \end{pmatrix}

A partir disso, vemos que o estado de um bit quântico é um vetor unitário num espaço de produto interno complexo de duas dimensões com uma base ortonormal 0|0\rangle e 1|1\rangle. Ele é normalizado para 1.

ψψ=(αβ)(αβ)=1\langle\psi|\psi\rangle = \begin{pmatrix} \alpha^* & \beta^* \end{pmatrix} \begin{pmatrix} \alpha \\ \beta \end{pmatrix} = 1

|\psi\rangle =\begin\{pmatrix\} \alpha \\ \beta \end\{pmatrix\} também é chamado de vetor de estado (statevector).

Um estado quântico de qubit único também pode ser representado como

ψ=cosθ20+eiφsinθ21=((cosθ2eiφsinθ2))|\psi\rangle =\cos\frac{\theta}{2}|0\rangle+e^{i\varphi}\sin\frac{\theta}{2}|1\rangle =\left( \begin{pmatrix} \cos\frac{\theta}{2}\\ e^{i\varphi}\sin\frac{\theta}{2} \end{pmatrix}\right)

onde θ\theta e φ\varphi são os ângulos da esfera de Bloch mostrada na figura a seguir.

Esfera de Bloch

Nas próximas células de código, vamos construir cálculos básicos a partir dos componentes do Qiskit. Vamos criar um circuito vazio e depois adicionar operações quânticas, discutindo as portas e visualizando seus efeitos conforme avançamos. Você pode rodar a célula com "Shift" + "Enter". Importe as bibliotecas primeiro.

# Added by doQumentation — required packages for this notebook
!pip install -q qiskit qiskit-aer qiskit-ibm-runtime
# Import the qiskit library
from qiskit import QuantumCircuit
from qiskit_aer import AerSimulator
from qiskit.quantum_info import Statevector
from qiskit.visualization import plot_bloch_multivector
from qiskit_ibm_runtime import Sampler
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit.visualization import plot_histogram

Preparando o circuito quântico

Vamos criar e desenhar um circuito de qubit único.

# Create the single-qubit quantum circuit
qc = QuantumCircuit(1)

# Draw the circuit
qc.draw("mpl")

Saída da célula de código anterior

Porta X

A porta X é uma rotação de π\pi em torno do eixo xx da esfera de Bloch. Aplicar a porta X a 0|0\rangle resulta em 1|1\rangle, e aplicar a porta X a 1|1\rangle resulta em 0|0\rangle; portanto, é uma operação similar à porta NOT clássica, também conhecida como inversão de bit. A representação matricial da porta X está abaixo.

X=(0110)X = \begin{pmatrix} 0 & 1 \\ 1 & 0 \\ \end{pmatrix}
qc = QuantumCircuit(1)  # Prepare the single-qubit quantum circuit

# Apply a X gate to qubit 0
qc.x(0)

# Draw the circuit
qc.draw("mpl")

Saída da célula de código anterior

No IBM Quantum®, o estado inicial é definido como 0|0\rangle, então o circuito quântico acima em representação matricial é

X0=(0110)(10)=(01)=1X|0\rangle= \begin{pmatrix} 0 & 1 \\ 1 & 0 \end{pmatrix} \begin{pmatrix} 1 \\ 0 \end{pmatrix} =\begin{pmatrix} 0 \\ 1 \end{pmatrix} = |1\rangle

A seguir, vamos rodar esse circuito usando um simulador de vetor de estado.

# See the statevector
out_vector = Statevector(qc)
print(out_vector)

# Draw a Bloch sphere
plot_bloch_multivector(out_vector)
Statevector([0.+0.j, 1.+0.j],
dims=(2,))

Saída da célula de código anterior

O vetor coluna é exibido como vetor linha, com números complexos (a parte imaginária é indexada por jj).

Porta H

A porta Hadamard é uma rotação de π\pi em torno de um eixo a meio caminho entre os eixos xx e zz na esfera de Bloch. Aplicar a porta H a 0|0\rangle cria um estado de superposição como 0+12\frac{|0\rangle + |1\rangle}{\sqrt{2}}. A representação matricial da porta H está abaixo.

H=12(1111)H = \frac{1}{\sqrt{2}}\begin{pmatrix} 1 & 1 \\ 1 & -1 \\ \end{pmatrix}
qc = QuantumCircuit(1)  # Create the single-qubit quantum circuit

# Apply an Hadamard gate to qubit 0
qc.h(0)

# Draw the circuit
qc.draw(output="mpl")

Saída da célula de código anterior

# See the statevector
out_vector = Statevector(qc)
print(out_vector)

# Draw a Bloch sphere
plot_bloch_multivector(out_vector)
Statevector([0.70710678+0.j, 0.70710678+0.j],
dims=(2,))

Saída da célula de código anterior

Isso é

H0=12(1111)(10)=12(11)=(0.7070.707)=12(0+1)H|0\rangle= \frac{1}{\sqrt{2}} \begin{pmatrix} 1 & 1 \\ 1 & -1 \end{pmatrix} \begin{pmatrix} 1 \\0 \end{pmatrix} =\frac{1}{\sqrt{2}}\begin{pmatrix} 1 \\ 1 \end{pmatrix} =\begin{pmatrix} 0.707 \\ 0.707 \end{pmatrix} =\frac{1}{\sqrt{2}}(|0\rangle+|1\rangle)

Esse estado de superposição é tão comum e importante que recebe seu próprio símbolo:

+12(0+1).|+\rangle \equiv \frac{1}{\sqrt{2}}(|0\rangle+|1\rangle).

Ao aplicar a porta HH em 0|0\rangle, criamos uma superposição de 0|0\rangle e 1|1\rangle onde a medição na base computacional (ao longo de z na esfera de Bloch) daria cada estado com probabilidades iguais.

Estado |-\rangle

Você provavelmente já imaginou que existe um estado |-\rangle correspondente:

012.|-\rangle \equiv \frac{|0\rangle -|1\rangle}{\sqrt{2}}.

Para criar esse estado, aplique primeiro uma porta X para obter 1|1\rangle, e depois aplique uma porta H.

qc = QuantumCircuit(1)  # Create the single-qubit quantum circuit

# Apply a X gate to qubit 0
qc.x(0)

# Apply an Hadamard gate to qubit 0
qc.h(0)

# draw the circuit
qc.draw(output="mpl")

Saída da célula de código anterior

# See the statevector
out_vector = Statevector(qc)
print(out_vector)

# Draw a Bloch sphere
plot_bloch_multivector(out_vector)
Statevector([ 0.70710678+0.j, -0.70710678+0.j],
dims=(2,))

Saída da célula de código anterior

Isso é

H1=12(11 11)(0 1)=12(1 1)=(0.707 0.707)=12(01)=H|1\rangle= \frac{1}{\sqrt{2}} \begin{pmatrix} 1 & 1 \\\ 1 & -1 \end{pmatrix} \begin{pmatrix} 0 \\\ 1 \end{pmatrix} =\frac{1}{\sqrt{2}}\begin{pmatrix} 1 \\\ -1 \end{pmatrix} =\begin{pmatrix} 0.707 \\\ -0.707 \end{pmatrix} =\frac{1}{\sqrt{2}}(|0\rangle-|1\rangle) = |-\rangle

Aplicar a porta HH em 1|1\rangle resulta numa superposição igual de 0|0\rangle e 1|1\rangle, mas o sinal de 1|1\rangle é negativo.

3.2 Estado quântico de qubit único e evolução unitária

As ações de todas as portas que vimos até agora são unitárias, o que significa que podem ser representadas por um operador unitário. Em outras palavras, o estado de saída pode ser obtido atuando no estado inicial com uma matriz unitária:

ψ=Uψ|\psi^{'}\rangle = U|\psi\rangle

Uma matriz unitária é uma matriz que satisfaz

UU=UU=I.U^{\dagger}U =U U^{\dagger} = I.

Em termos de operação de computador quântico, diríamos que aplicar uma porta quântica ao qubit faz o estado quântico evoluir. As portas de qubit único mais comuns incluem as seguintes.

Portas de Pauli:

X=(0110)=01+10X = \begin{pmatrix} 0 & 1 \\ 1 & 0 \\ \end{pmatrix} = |0\rangle \langle 1|+|1\rangle \langle 0| Y=(0ii0)=i01+i10Y = \begin{pmatrix} 0 & -i \\ i & 0 \\ \end{pmatrix} = -i|0\rangle \langle 1|+i|1\rangle \langle 0| Z=(1001)=0011Z = \begin{pmatrix} 1 & 0 \\ 0 & -1 \\ \end{pmatrix} = |0\rangle \langle 0|-|1\rangle \langle 1|

onde o produto externo foi calculado da seguinte forma:

00=[10][10]=[1000],10=[01][10]=[0010],|0\rangle \langle 0|= \begin{bmatrix} 1 \\ 0 \end{bmatrix} \begin{bmatrix} 1 & 0 \end{bmatrix} =\begin{bmatrix} 1 & 0 \\ 0 & 0 \\ \end{bmatrix}, \quad |1\rangle \langle 0|= \begin{bmatrix} 0 \\ 1 \end{bmatrix} \begin{bmatrix} 1 & 0 \end{bmatrix} =\begin{bmatrix} 0 & 0 \\ 1 & 0 \\ \end{bmatrix}, \quad 01=[10][01]=[0100],11=[01][01]=[0001],|0\rangle \langle 1|= \begin{bmatrix} 1 \\ 0 \end{bmatrix} \begin{bmatrix} 0 & 1 \end{bmatrix} =\begin{bmatrix} 0 & 1 \\ 0 & 0 \\ \end{bmatrix}, \quad |1\rangle \langle 1|= \begin{bmatrix} 0 \\ 1 \end{bmatrix} \begin{bmatrix} 0 & 1 \end{bmatrix} =\begin{bmatrix} 0 & 0 \\ 0 & 1 \\ \end{bmatrix}, \quad

Outras portas típicas de qubit único:

H=12[1111],S=[100i],T=[100exp(iπ/4)]H= \frac{1}{\sqrt{2}}\begin{bmatrix} 1 & 1 \\ 1 & -1 \\ \end{bmatrix},\quad S = \begin{bmatrix} 1 & 0 \\ 0 & i \\ \end{bmatrix}, \quad T = \begin{bmatrix} 1 & 0 \\ 0 & exp(i\pi/4) \\ \end{bmatrix} Rx(θ)=eiθX/2=cosθ2Iisinθ2X=[cosθ2isinθ2isinθ2cosθ2]R_x(\theta) = e^{-i\theta X/2} = cos\frac{\theta}{2}I - i sin \frac{\theta}{2}X = \begin{bmatrix} cos\frac{\theta}{2} & -i sin \frac{\theta}{2} \\ -i sin \frac{\theta}{2} & cos\frac{\theta}{2} \\ \end{bmatrix} Ry(θ)=eiθY/2=cosθ2Iisinθ2Y=[cosθ2sinθ2sinθ2cosθ2]R_y(\theta) = e^{-i\theta Y/2} = cos\frac{\theta}{2}I - i sin \frac{\theta}{2}Y = \begin{bmatrix} cos\frac{\theta}{2} & - sin \frac{\theta}{2} \\ sin \frac{\theta}{2} & cos\frac{\theta}{2} \\ \end{bmatrix} Rz(θ)=eiθZ/2=cosθ2Iisinθ2Z=[eiθ/200eiθ/2]R_z(\theta) = e^{-i\theta Z/2} = cos\frac{\theta}{2}I - i sin \frac{\theta}{2}Z = \begin{bmatrix} e^{-i\theta /2} & 0 \\ 0 & e^{i\theta /2} \\ \end{bmatrix}

O significado e o uso dessas portas são descritos em mais detalhes no curso Basics of Quantum Information.

Exercício 1

Use o Qiskit para criar circuitos quânticos que preparem os estados descritos abaixo. Em seguida, rode cada circuito usando o simulador de vetor de estado e exiba o estado resultante na esfera de Bloch. Como bônus, veja se você consegue antecipar qual deve ser o estado final com base na intuição sobre as portas e rotações na esfera de Bloch.

(1) XX0XX|0\rangle

(2) HH0HH|0\rangle

(3) HZH0HZH|0\rangle

Dica: a porta Z pode ser usada com

qc.z(0)

Solução:

### (1) XX|0> ###

# Create the single-qubit quantum circuit
qc = QuantumCircuit(1) ##your code goes here##

# Add a X gate to qubit 0
qc.x(0) ##your code goes here##

# Add a X gate to qubit 0
qc.x(0) ##your code goes here##

# Draw a circuit
qc.draw(output="mpl")

Saída da célula de código anterior

# See the statevector
out_vector = Statevector(qc)
print(out_vector)

# Draw a Bloch sphere
plot_bloch_multivector(out_vector)
Statevector([1.+0.j, 0.+0.j],
dims=(2,))

Saída da célula de código anterior

### (2) HH|0> ###
##your code goes here##
qc = QuantumCircuit(1)
qc.h(0)
qc.h(0)
qc.draw("mpl")

Saída da célula de código anterior

# See the statevector
out_vector = Statevector(qc)
print(out_vector)

# Draw a Bloch sphere
plot_bloch_multivector(out_vector)
Statevector([1.+0.j, 0.+0.j],
dims=(2,))

Saída da célula de código anterior

### (3) HZH|0> ###
##your code goes here##
qc = QuantumCircuit(1)
qc.h(0)
qc.z(0)
qc.h(0)
qc.draw("mpl")

Saída da célula de código anterior

# See the statevector
out_vector = Statevector(qc)
print(out_vector)

# Draw a Bloch sphere
plot_bloch_multivector(out_vector)
Statevector([0.+0.j, 1.+0.j],
dims=(2,))

Saída da célula de código anterior

3.3 Medição

A medição é teoricamente um tema bastante complexo. Mas em termos práticos, realizar uma medição ao longo de zz (como todos os computadores quânticos IBM® fazem) simplesmente força o estado do qubit α0+β1(s.t.α2+β2=1)\alpha|0\rangle+\beta|1\rangle \quad (s.t.|\alpha|^2+|\beta|^2=1) a colapsar para 0|0\rangle ou 1|1\rangle, e observamos o resultado.

  • α2|\alpha|^2 é a probabilidade de obtermos 0|0\rangle ao medir.
  • β2|\beta|^2 é a probabilidade de obtermos 1|1\rangle ao medir.

Portanto, α\alpha e β\beta são chamados de amplitudes de probabilidade. (veja a "Regra de Born")

Por exemplo, 220+221\frac{\sqrt{2}}{2}|0\rangle+\frac{\sqrt{2}}{2}|1\rangle tem probabilidade igual de se tornar 0|0\rangle ou 1|1\rangle após a medição. 32012i1\frac{\sqrt{3}}{2}|0\rangle-\frac{1}{2}i|1\rangle tem 75% de chance de se tornar 0|0\rangle.

Simulador Qiskit Aer

A seguir, vamos medir um circuito que prepara a superposição de probabilidade igual acima. Precisamos adicionar as portas de medição, pois o simulador Qiskit Aer simula um hardware quântico ideal (sem ruído) por padrão. Nota: O simulador Aer também pode aplicar um modelo de ruído baseado em um computador quântico real. Voltaremos aos modelos de ruído mais adiante.

# Create a new circuit with one qubits (first argument) and one classical bits (second argument)
qc = QuantumCircuit(1, 1)
qc.h(0)
qc.measure(0, 0) # Add the measurement gate

qc.draw(output="mpl")

Saída da célula de código anterior

Agora estamos prontos para rodar nosso circuito no simulador Aer. Neste exemplo, vamos usar o padrão shots=1024, o que significa que vamos medir 1024 vezes. Em seguida, vamos plotar essas contagens em um histograma.

# Run the circuit on a simulator to get the results
# Define backend
backend = AerSimulator()

# Transpile to backend
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_qc = pm.run(qc)

# Run the job
sampler = Sampler(mode=backend)
job = sampler.run([isa_qc])
result = job.result()

# Print the results
counts = result[0].data.c.get_counts()
print(counts)

# Plot the counts in a histogram
plot_histogram(counts)
{'0': 521, '1': 503}

Saída da célula de código anterior

Vemos que 0s e 1s foram medidos com uma probabilidade de quase 50% cada. Embora o ruído não tenha sido simulado aqui, os estados ainda são probabilísticos. Portanto, embora esperemos uma distribuição de aproximadamente 50-50, raramente encontraremos exatamente isso. Assim como 100 lançamentos de uma moeda raramente resultariam em exatamente 50 ocorrências de cada lado.

4. Portas quânticas de múltiplos qubits e emaranhamento

4.1 Circuito quântico de múltiplos qubits

Podemos criar um circuito quântico de dois qubits com o seguinte código. Vamos aplicar uma porta H a cada qubit.

# Create the two qubits quantum circuit
qc = QuantumCircuit(2)

# Apply an H gate to qubit 0
qc.h(0)

# Apply an H gate to qubit 1
qc.h(1)

# Draw the circuit
qc.draw(output="mpl")

Saída da célula de código anterior

# See the statevector
out_vector = Statevector(qc)
print(out_vector)
Statevector([0.5+0.j, 0.5+0.j, 0.5+0.j, 0.5+0.j],
dims=(2, 2))

Observação: ordenação de bits no Qiskit

O Qiskit usa a notação Little Endian para ordenar qubits e bits, o que significa que o qubit 0 é o bit mais à direita nas cadeias de bits. Exemplo: 01|01\rangle significa que q0 está em 1|1\rangle e q1 está em 0|0\rangle. Atenção: parte da literatura em computação quântica usa a notação Big Endian (qubit 0 é o bit mais à esquerda), assim como grande parte da literatura de mecânica quântica.

Outro detalhe importante: ao representar um circuito quântico, q0|q_0\rangle é sempre colocado no topo do circuito. Com isso em mente, o estado quântico do circuito acima pode ser escrito como um produto tensorial de estados quânticos de um único qubit.

q1q0=(a0+b1)(c0+d1)|q1\rangle \otimes|q0\rangle = (a|0\rangle+b|1\rangle) \otimes (c|0\rangle+d|1\rangle)

=ac00+ad01+bc10+bd11= ac|0\rangle|0\rangle+ad|0\rangle|1\rangle+bc|1\rangle|0\rangle+bd|1\rangle|1\rangle

=ac00+ad01+bc10+bd11= ac|00\rangle+ad|01\rangle+bc|10\rangle+bd|11\rangle

( ac2+ad2+bc2+bd2=1|ac|^2+ |ad|^2+ |bc|^2+ |bd|^2=1 )

O estado inicial do Qiskit é 00=00|0\rangle|0\rangle=|00\rangle, portanto ao aplicar HH a cada qubit, o estado muda para uma superposição de igual probabilidade.

H0H0=12(0+1)12(0+1)=12(00+01+10+11)H|0\rangle \otimes H|0\rangle=\frac{1}{\sqrt{2}}(|0\rangle+|1\rangle) \otimes \frac{1}{\sqrt{2}}(|0\rangle+|1\rangle) = \frac{1}{2}(|00\rangle+|01\rangle+|10\rangle+|11\rangle)

=12((11)(11))=12(1111)=12((1000)+(0100)+(0010)+(0001))=\frac{1}{2}\left( \begin{pmatrix} 1 \\ 1 \end{pmatrix} \otimes \begin{pmatrix} 1 \\ 1 \end{pmatrix}\right) = \frac{1}{2}\begin{pmatrix} 1 \\ 1 \\ 1 \\ 1 \end{pmatrix}=\frac{1}{2}\left(\begin{pmatrix} 1 \\ 0 \\ 0 \\ 0 \end{pmatrix}+\begin{pmatrix} 0 \\ 1 \\ 0 \\ 0 \end{pmatrix}+\begin{pmatrix} 0 \\ 0 \\ 1 \\ 0 \end{pmatrix}+\begin{pmatrix} 0 \\ 0 \\ 0 \\ 1 \end{pmatrix}\right)

A regra de medição é a mesma que no caso de um único qubit: a probabilidade de medir 00|00\rangle é ac2|ac|^2.

# Draw a Bloch sphere
plot_bloch_multivector(out_vector)

Saída da célula de código anterior

Agora, vamos medir esse circuito.

# Create a new circuit with two qubits (first argument) and two classical bits (second argument)
qc = QuantumCircuit(2, 2)

# Apply the gates
qc.h(0)
qc.h(1)

# Add the measurement gates
qc.measure(0, 0) # Measure qubit 0 and save the result in bit 0
qc.measure(1, 1) # Measure qubit 1 and save the result in bit 1

# Draw the circuit
qc.draw(output="mpl")

Saída da célula de código anterior

Agora vamos usar o simulador Aer novamente para verificar experimentalmente que as probabilidades relativas de todos os estados de saída possíveis são aproximadamente iguais.

# Run the circuit on a simulator to get the results
# Define backend
backend = AerSimulator()

# Transpile to backend
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_qc = pm.run(qc)

# Run the job
sampler = Sampler(mode=backend)
job = sampler.run([isa_qc])
result = job.result()

# Print the results
counts = result[0].data.c.get_counts()
print(counts)

# Plot the counts in a histogram
plot_histogram(counts)
{'10': 262, '01': 246, '00': 265, '11': 251}

Saída da célula de código anterior

Como esperado, os estados 00|00\rangle, 01|01\rangle, 10|10\rangle, 11|11\rangle foram medidos com aproximadamente 25% cada.

4.2 Portas quânticas de múltiplos qubits

Porta CNOT

A porta CNOT ("NOT controlado" ou CX) é uma porta de dois qubits, ou seja, sua ação envolve dois qubits ao mesmo tempo: o qubit de controle e o qubit alvo. A CNOT inverte o qubit alvo somente quando o qubit de controle está em 1|1\rangle.

Entrada (alvo, controle)Saída (alvo, controle)
0000
0111
1010
1101

Vamos primeiro simular a ação dessa porta de dois qubits quando q0 e q1 estão ambos em 0|0\rangle, e obter o vetor de estado de saída. A sintaxe Qiskit usada é qc.cx(qubit de controle, qubit alvo).

# Create a circuit with two quantum registers and two classical registers
qc = QuantumCircuit(2, 2)

# Apply the CNOT (cx) gate to a |00> state.
qc.cx(0, 1) # Here the control is set to q0 and the target is set to q1.

# Draw the circuit
qc.draw(output="mpl")

Saída da célula de código anterior

# See the statevector
out_vector = Statevector(qc)
print(out_vector)
Statevector([1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j],
dims=(2, 2))

Como esperado, aplicar uma porta CNOT em 00|00\rangle não alterou o estado, pois o qubit de controle estava no estado 0|0\rangle. Vamos voltar à nossa operação CNOT. Desta vez, vamos aplicar uma porta CNOT em 01|01\rangle e ver o que acontece.

qc = QuantumCircuit(2, 2)

# q0=1, q1=0
qc.x(0) # Apply a X gate to initialize q0 to 1
qc.cx(0, 1) # Set the control bit to q0 and the target bit to q1.

# Draw the circuit
qc.draw(output="mpl")

Saída da célula de código anterior

# See the statevector
out_vector = Statevector(qc)
print(out_vector)
Statevector([0.+0.j, 0.+0.j, 0.+0.j, 1.+0.j],
dims=(2, 2))

Ao aplicar uma porta CNOT, o estado 01|01\rangle se tornou 11|11\rangle.

Vamos verificar esses resultados executando o circuito em um simulador.

# Add measurements
qc.measure(0, 0)
qc.measure(1, 1)

# Draw the circuit
qc.draw(output="mpl")

Saída da célula de código anterior

# Run the circuit on a simulator to get the results
# Define backend
backend = AerSimulator()

# Transpile to backend
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_qc = pm.run(qc)

# Run the job
sampler = Sampler(backend)
job = sampler.run([isa_qc])
result = job.result()

# Print the results
counts = result[0].data.c.get_counts()
print(counts)

# Plot the counts in a histogram
plot_histogram(counts)
{'11': 1024}

Saída da célula de código anterior

Os resultados mostram que 11|11\rangle foi medido com 100% de probabilidade.

4.3 Emaranhamento quântico e execução em um dispositivo quântico real

Vamos começar apresentando um estado emaranhado específico que é particularmente importante na computação quântica, e depois definir o termo "emaranhado":

1200+1211\frac{1}{\sqrt{2}}|00\rangle + \frac{1}{\sqrt{2}}|11\rangle

e esse estado é chamado de estado de Bell.

Um estado emaranhado é um estado ψAB|\psi_{AB}\rangle composto por estados quânticos ψA|\psi_A\rangle e ψB|\psi_B\rangle que não pode ser representado como um produto tensorial de estados quânticos individuais.

Se ψAB|\psi_{AB}\rangle abaixo tem dois estados ψA|\psi\rangle_A e ψB|\psi\rangle_B;

ψAB=12(00+11)=12(0A0B+1A1B)|\psi_{AB}\rangle = \frac{1}{\sqrt{2}}(|00\rangle +|11\rangle) = \frac{1}{\sqrt{2}}(|0\rangle_A|0\rangle_B +|1\rangle_A|1\rangle_B) ψA=a00+a11|\psi\rangle_A = a_0|0\rangle+a_1|1\rangle ψB=b00+b11|\psi\rangle_B = b_0|0\rangle+b_1|1\rangle

o produto tensorial desses dois estados é o seguinte

ψAψB=a0b000+a0b101+a1b010+a1b111|\psi\rangle _A\otimes |\psi\rangle _B = a_0 b_0|00\rangle+a_0 b_1|01\rangle+a_1 b_0|10\rangle+a_1 b_1|11\rangle

mas não existem coeficientes a0,a1,b0a_0, a_1, b_0 e b1b_1 que satisfaçam essas duas equações. Portanto, ψAB|\psi_{AB}\rangle não pode ser representado como um produto tensorial de estados quânticos individuais, ψA|\psi\rangle_A e ψB|\psi\rangle_B, o que significa que ψAB=12(00+11)|\psi_{AB}\rangle = \frac{1}{\sqrt{2}}(|00\rangle +|11\rangle) é um estado emaranhado.

Vamos criar o estado de Bell e executá-lo em um computador quântico real. Para isso, vamos seguir os quatro passos para escrever um programa quântico, chamados de Qiskit patterns (padrões Qiskit):

  1. Mapear o problema para circuitos e operadores quânticos
  2. Otimizar para o hardware alvo
  3. Executar no hardware alvo
  4. Pós-processar os resultados

Passo 1. Mapear o problema para circuitos e operadores quânticos

Em um programa quântico, os circuitos quânticos são o formato nativo para representar instruções quânticas. Ao criar um circuito, você geralmente cria um novo objeto QuantumCircuit e vai adicionando instruções em sequência.

A célula de código a seguir cria um circuito que prepara um estado de Bell, o estado emaranhado de dois qubits que vimos acima.

qc = QuantumCircuit(2, 2)

qc.h(0)
qc.cx(0, 1)

qc.measure(0, 0)
qc.measure(1, 1)

qc.draw("mpl")

Saída da célula de código anterior

Passo 2. Otimizar para o hardware alvo

O Qiskit converte circuitos abstratos em circuitos QISA (Quantum Instruction Set Architecture) que respeitam as restrições do hardware alvo e otimiza o desempenho do circuito. Antes da otimização, precisamos especificar o hardware alvo. Se você não tem o qiskit-ibm-runtime, precisa instalá-lo primeiro. Para mais informações sobre o Qiskit Runtime, consulte a referência da API.

# Install
# !pip install qiskit-ibm-runtime

Vamos especificar o hardware alvo.

from qiskit_ibm_runtime import QiskitRuntimeService

service = QiskitRuntimeService()
service.backends()
# You can specify the device
# backend = service.backend('ibm_kingston')
# You can also identify the least busy device
backend = service.least_busy(operational=True)
print("The least busy device is ", backend)

A transpilação do circuito é um processo bastante complexo. De forma simplificada, ela reescreve o circuito em um equivalente lógico usando "portas nativas" (portas que um computador quântico específico consegue implementar) e mapeia os qubits do seu circuito para os qubits reais mais adequados no computador quântico alvo. Para mais detalhes sobre transpilação, veja esta documentação.

# Transpile the circuit into basis gates executable on the hardware
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
target_circuit = pm.run(qc)

target_circuit.draw("mpl", idle_wires=False)

Você pode ver que na transpilação o circuito foi reescrito usando novas portas. Para mais informações, consulte a documentação do ECRGate.

Passo 3. Executar o circuito alvo

Agora vamos executar o circuito alvo no dispositivo real.

sampler = Sampler(backend)
job_real = sampler.run([target_circuit])

job_id = job_real.job_id()
print("job id:", job_id)

A execução no dispositivo real pode exigir espera em uma fila, já que os computadores quânticos são recursos valiosos e muito procurados. O job_id é usado para verificar o status da execução e os resultados do job posteriormente.

# Check the job status (replace the job id below with your own)
job_real.status(job_id)

Você também pode verificar o status do job pelo seu painel IBM Quantum: https://quantum.cloud.ibm.com/workloads

# If the Notebook session got disconnected you can also check your job status by running the following code
from qiskit_ibm_runtime import QiskitRuntimeService

service = QiskitRuntimeService()
job_real = service.job(job_id) # Input your job-id between the quotations
job_real.status()
# Execute after job has successfully run
result_real = job_real.result()
print(result_real[0].data.c.get_counts())

Passo 4. Pós-processar os resultados

Por fim, precisamos pós-processar os resultados para criar saídas no formato esperado, como valores ou gráficos.

plot_histogram(result_real[0].data.c.get_counts())

Como você pode ver, 00|00\rangle e 11|11\rangle são os estados observados com maior frequência. Há alguns resultados além dos esperados, e eles se devem ao ruído e à decoerência dos qubits. Aprenderemos mais sobre erros e ruído em computadores quânticos nas lições seguintes deste curso.

4.4 Estado GHZ

O conceito de emaranhamento pode ser estendido a sistemas com mais de dois qubits. O estado GHZ (estado de Greenberger-Horne-Zeilinger) é um estado maximamente emaranhado de três ou mais qubits. O estado GHZ para três qubits é definido como

12(000+111)\frac{1}{\sqrt 2}(|000\rangle + |111\rangle)

Ele pode ser criado com o seguinte circuito quântico.

qc = QuantumCircuit(3, 3)

qc.h(0)
qc.cx(0, 1)
qc.cx(1, 2)

qc.measure(0, 0)
qc.measure(1, 1)
qc.measure(2, 2)

qc.draw("mpl")

Saída da célula de código anterior

A "profundidade" de um circuito quântico é uma métrica útil e comum para descrever circuitos quânticos. Trace um caminho pelo circuito quântico, movendo da esquerda para a direita, trocando de qubit apenas quando eles estiverem conectados por uma porta de múltiplos qubits. Conte o número de portas ao longo desse caminho. O número máximo de portas em qualquer caminho pelo circuito é a profundidade. Em computadores quânticos modernos com ruído, circuitos de baixa profundidade têm menos erros e tendem a retornar bons resultados. Circuitos muito profundos, não.

Usando QuantumCircuit.depth(), podemos verificar a profundidade do nosso circuito quântico. A profundidade do circuito acima é 4. O qubit do topo tem apenas três portas, incluindo a medição. Mas existe um caminho que vai do qubit do topo até o qubit 1 ou o qubit 2, passando por mais uma porta CNOT.

qc.depth()
4

Exercício 2

O estado GHZ de um sistema de 8 qubits é

12(00000000+11111111)\frac{1}{\sqrt 2}(|00000000\rangle + |11111111\rangle)

Escreva um código para preparar esse estado com o circuito mais raso possível. A profundidade do circuito quântico mais raso é 5, incluindo as portas de medição.

Solução:

# Step 1
qc = QuantumCircuit(8, 8)

##your code goes here##
qc.h(0)
qc.cx(0, 4)
qc.cx(4, 6)
qc.cx(6, 7)

qc.cx(4, 5)

qc.cx(0, 2)
qc.cx(2, 3)

qc.cx(0, 1)
qc.barrier() # for visual separation

# measure
for i in range(8):
qc.measure(i, i)

qc.draw("mpl")
# print(qc.depth())

Saída da célula de código anterior

print(qc.depth())
5
from qiskit.visualization import plot_histogram
# Step 2
# For this exercise, the circuit and operators are simple, so no optimizations are needed.

# Step 3
# Run the circuit on a simulator to get the results
backend = AerSimulator()

pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_qc = pm.run(qc)

sampler = Sampler(mode=backend)
job = sampler.run([isa_qc], shots=1024)
result = job.result()

counts = result[0].data.c.get_counts()
print(counts)

# Step 4
# Plot the counts in a histogram

plot_histogram(counts)
{'11111111': 535, '00000000': 489}

Saída da célula de código anterior

5. Resumo

Você aprendeu computação quântica com o modelo de circuitos usando bits e portas quânticas, e revisou superposição, medição e emaranhamento. Também aprendeu como executar o circuito quântico em um dispositivo quântico real.

No exercício final para criar um circuito GHZ, você tentou reduzir a profundidade do circuito, que é um fator importante para obter uma solução em escala de utilidade em um computador quântico com ruído. Nas lições seguintes deste curso, você aprenderá sobre ruído e sobre métodos de mitigação de erros em detalhes. Nesta lição, como introdução, consideramos a redução da profundidade do circuito em um dispositivo ideal, mas na realidade precisamos levar em conta as restrições do dispositivo real, como a conectividade entre qubits. Você aprenderá mais sobre isso nas lições subsequentes deste curso.

# See the version of Qiskit
import qiskit

qiskit.__version__
'2.0.2'