Bits quânticos, portas e circuitos
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.
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 e . Um estado quântico arbitrário é representado como
onde e são números complexos tais que .
e são vetores no espaço vetorial complexo de duas dimensões:
Portanto, um estado quântico arbitrário também pode ser representado como
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 e . Ele é normalizado para 1.
também é chamado de vetor de estado (statevector).
Um estado quântico de qubit único também pode ser representado como
onde e são os ângulos da esfera de Bloch mostrada na figura a seguir.
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")
Porta X
A porta X é uma rotação de em torno do eixo da esfera de Bloch. Aplicar a porta X a resulta em , e aplicar a porta X a resulta em ; 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.
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")
No IBM Quantum®, o estado inicial é definido como , então o circuito quântico acima em representação matricial é
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,))
O vetor coluna é exibido como vetor linha, com números complexos (a parte imaginária é indexada por ).
Porta H
A porta Hadamard é uma rotação de em torno de um eixo a meio caminho entre os eixos e na esfera de Bloch. Aplicar a porta H a cria um estado de superposição como . A representação matricial da porta H está abaixo.
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")
# 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,))
Isso é
Esse estado de superposição é tão comum e importante que recebe seu próprio símbolo:
Ao aplicar a porta em , criamos uma superposição de e onde a medição na base computacional (ao longo de z na esfera de Bloch) daria cada estado com probabilidades iguais.
Estado
Você provavelmente já imaginou que existe um estado correspondente:
Para criar esse estado, aplique primeiro uma porta X para obter , 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")
# 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,))
Isso é
Aplicar a porta em resulta numa superposição igual de e , mas o sinal de é 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:
Uma matriz unitária é uma matriz que satisfaz
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:
onde o produto externo foi calculado da seguinte forma:
Outras portas típicas de qubit único:
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)
(2)
(3)
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")
# 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,))
### (2) HH|0> ###
##your code goes here##
qc = QuantumCircuit(1)
qc.h(0)
qc.h(0)
qc.draw("mpl")
# 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,))
### (3) HZH|0> ###
##your code goes here##
qc = QuantumCircuit(1)
qc.h(0)
qc.z(0)
qc.h(0)
qc.draw("mpl")
# 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,))
3.3 Medição
A medição é teoricamente um tema bastante complexo. Mas em termos práticos, realizar uma medição ao longo de (como todos os computadores quânticos IBM® fazem) simplesmente força o estado do qubit a colapsar para ou , e observamos o resultado.
- é a probabilidade de obtermos ao medir.
- é a probabilidade de obtermos ao medir.
Portanto, e são chamados de amplitudes de probabilidade. (veja a "Regra de Born")
Por exemplo, tem probabilidade igual de se tornar ou após a medição. tem 75% de chance de se tornar .
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")
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}
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")
# 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: significa que q0 está em e q1 está em . 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, é 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.
( )
O estado inicial do Qiskit é , portanto ao aplicar a cada qubit, o estado muda para uma superposição de igual probabilidade.
A regra de medição é a mesma que no caso de um único qubit: a probabilidade de medir é .
# Draw a Bloch sphere
plot_bloch_multivector(out_vector)

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")
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}
Como esperado, os estados , , , 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 .
| Entrada (alvo, controle) | Saída (alvo, controle) |
|---|---|
| 00 | 00 |
| 01 | 11 |
| 10 | 10 |
| 11 | 01 |
Vamos primeiro simular a ação dessa porta de dois qubits quando q0 e q1 estão ambos em , 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")
# 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 não alterou o estado, pois o qubit de controle estava no estado . Vamos voltar à nossa operação CNOT. Desta vez, vamos aplicar uma porta CNOT em 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")
# 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 se tornou .
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")
# 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}
Os resultados mostram que 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":
e esse estado é chamado de estado de Bell.
Um estado emaranhado é um estado composto por estados quânticos e que não pode ser representado como um produto tensorial de estados quânticos individuais.
Se abaixo tem dois estados e ;
o produto tensorial desses dois estados é o seguinte
mas não existem coeficientes e que satisfaçam essas duas equações. Portanto, não pode ser representado como um produto tensorial de estados quânticos individuais, e , o que significa que é 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):
- Mapear o problema para circuitos e operadores quânticos
- Otimizar para o hardware alvo
- Executar no hardware alvo
- 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")
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, e 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
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")
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 é
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())
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}
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'