RESUMO EM CONSTRUÇÃO
Chips compõem-se bilhões de transistores (semicondutores microscópicos que controlam fluxo de elétrons binários - não passa energia é 0, passa energia é 1). Em 1975, Gordon Moore, cofundador da Intel, observou que quantidade de transistores em chip de circuito integrado dobra aproximadamente a cada 2 anos (Lei de Moore), resultando chips menores e mais rápidos. Atualmente, transistores de silício chegaram à escala de nanômetros, com dimensões próximas ao tamanho de átomos individuais (miniaturização extrema). Ocasionará problemas físicos em túnel quântico (tunelamento quântico - elétrons começam atravessar barreiras microscópicas, causando vazamentos de corrente), aquecimento (quanto mais transistores, mais calor, limitando desempenho), custo e energia. Computação clássica está chegando ao limite físico do silício (espaço físico para encolher e acelerar chips está fisicamente lotado - problema do caixeiro-viajante). Moore prevê tal limite em meados de 2025.
Chip de processamento quântico é minúsculo (muitas vezes menor que moeda), mas necessita de grande sistema de suporte para funcionamento íntegro, pois qubits são extremamente sensíveis (qualquer calor, luz, som ou vibração pode destruir o estado quântico). Planos que compõem hardware quântico:
Sequência de portas quânticas usadas em cálculo.
Computação quântica complementará coexistência a computação clássica (integração híbrida), pois dispositivos computacionais ainda utilizam demais componentes de interpretação binária clássica. Computação quântica é utilizada em longos processamentos de dados, como descriptografia, pesquisas científicas, simulações moleculares, IAs avançadas, etc. Criptografia pós-quântica (PQC), novos algoritmos clássicos resistentes a ataques quânticos e clássicos, são desenvolvidos como resposta à ameaça da computação quântica. Distribuição quântica de chaves (QKD) é baseada em princípios quânticos para comunicação inviolável. Protocolo BB84 permite criar chave secreta entre 2 partes. Tentativas de espionagem alteram estados quânticos e são detectadas. Problemas de otimização em finanças e engenharia, onde computação quântica, via algoritmos QAOA ou recozimento quântico, pode encontrar soluções melhores e mais rápidas para problema do caixeiro-viajante, roteamento de veículos, e otimização de cadeias de suprimentos.
Método alternativo ao modelo de portas, usado para resolver problemas de otimização e amostragem. Busca estado de energia mínima de sistema físico representando problema. Qubits evoluem de superposição inicial para configuração final, usando tunelamento quântico para atravessar barreiras de energia e evitar ótimos locais, aumentando chance de alcançar solução global. Aplicações incluem otimização combinatória, finanças, rotas e machine learning. Problema da não linearidade, na aprendizagem clássica, ocorre quando métodos lineares não captam relações complexas. No ML quântico, embora operações sejam lineares, feature maps introduzem não linearidades eficazes. Circuitos variacionais (VQC) com portas parametrizadas aprendem funções altamente não lineares, semelhantes a redes neurais profundas.
Átomo de Rydberg corresponde a átomo excitado que possui, em média, 1 ou mais elétrons distantes do núcleo. Átomos de Rydberg têm várias propriedades peculiares, incluindo resposta exagerada a campos elétricos e magnéticos, bem como vida longa. Quando usados como qubits, oferecem interações atômicas fortes e controláveis ajustáveis ao selecionar diferentes estados.
Dado quântico armazenado nos spins nucleares dos átomos em moléculas, e portas lógicas manipulam essa informação via radiação eletromagnética. Pósitron ou elétron podem ter spin 'para cima', 'para baixo', ou ambos simultaneamente, representando estados do qubit. Momentos magnéticos nucleares fazem movimento natural de precessão na presença de campos magnéticos. Estados quânticos dos núcleos podem ser manipulados irradiando núcleos com pulsos de rádio frequência sintonizados na frequência de precessão dos mesmos.
Ato de transformar possibilidade (superposição) em realidade (0 ou 1). Qubits estão constantemente em superposição (probabilidades % de estar em 0 à 1), e tornam-se definidos (0 ou 1) após medição. Exemplo, em superposição, um qubit pode estar 70% em 0 e 30% em 1, mas após medição será 0 ou 1.
Esparçador Pauli (Sparse Pauli) adventa matrizes de Pauli, onde são aplicadas 3 matrizes X, Y e Z em, além de I (identidade I), em um qubit, para medir seu estado.
Em 3 qubits, operação X ⊗ Z ⊗ I (ou "XZI" - Pauli String, sequência de Paulis) aplica X no qubit 1, Z no qubit 2, e I (nada) no qubit 3. SparsePauliOp (Sparse Pauli Operator) é forma compacta (esparsa) de guardar combinações sem ocupar tanta memória. Estado quântico é "como sistema está", SparsePauli é "tipo de óculos com que você observa". Você escolhe "conjunto de lentes" (matrizes Pauli) para olhar seu sistema e obter informações (medições). Pauli noise (ruído de Pauli) é um modelo de ruído quântico que descreve erros aleatórios aplicados a qubits usando matrizes de Pauli (X, Y, Z). Usadas para simular imperfeições reais nas transformações de qubits, em computadores quânticos. X-error inverte estado (Bit-flip noise), Z-error muda fase (Phase-flip noise), e Y-error combina erros X e Z (inversão + mudança de fase, Bit-phase-flip noise). Depolarizing noise destaca forma simétrica de Pauli noise na aplicação de X, Y ou Z com mesma probabilidade.
Avaliam desempenho e utilidade dos computadores quânticos, sem implicar vantagem comprovada sobre métodos clássicos. Fidelidade de camada mede capacidade geral do processador em executar circuitos, revelando detalhes sobre qubits, portas e interferências. CLOPS (operações de camada de circuito por segundo) mede velocidade de execução de circuitos de volume quântico, combinando desempenho quântico e clássico. Ambas permitem comparar sistemas e acompanhar ganhos de performance. Profundidade do circuito indica quantas operações paralelas podem ser executadas antes da decoerência, determinando complexidade dos circuitos possíveis.
Linguagens utilizadas especificamente para processamento quântico. Ambientes disponíveis para desenvolvimento são IBM Quantum Experience, Azure Quantum, Google Quantum AI e AWS Braket. Plataformas IBM Quantum Platform, Azure Quantum, Google Quantum AI e AWS Braket oferecem ferramentas e recursos para desenvolvimento quântico.
open Microsoft.Quantum.Intrinsic; // Importa operações quânticas básicas
open Microsoft.Quantum.Measurement; // Importa operações de medição
open Microsoft.Quantum.Canon; // Importa operações canônicas
operation CreateBellPair() : (Result, Result) {
use qubits = Qubit[2]; // Aloca array de 2 qubits
H(qubits[0]); // Aplica porta quântica Hadamard no 1º qubit (superposição)
CNOT(qubits[0], qubits[1]); // Aplica porta quântica CNOT (Controlled-NOT, entrelaçamento)
let result1 = M(qubits[0]); // Mede 1º qubit
let result2 = M(qubits[1]); // Mede 2º qubit
Reset(qubits[0]); // Reseta 1º qubit
Reset(qubits[1]); // Reseta 2º qubit
return (result1, result2); // Retorna resultados das medições (sempre iguais, exemplo, ambos 0 ou ambos 1)
}
pip3 install qiskit qiskit-ibm-runtime matplotlib qiskit[visualization] jupyter
from qiskit_ibm_runtime import QiskitRuntimeService
QiskitRuntimeService.save_account(
channel="ibm_quantum_platform",
token="SEU-TOKEN-IBM-QUANTUM-AQUI",
overwrite=True
) # Usar apenas 1 vez para salvar conta
service = QiskitRuntimeService() # Usar para conectar conta em cada script
print("Conta conectada com sucesso!")
# Instalar runtime (via terminal): pip3 install qiskit qiskit-ibm-runtime matplotlib qiskit[visualization] jupyter
# Conexão com IBM Quantum (única vez no ambiente):
from qiskit_ibm_runtime import QiskitRuntimeService
QiskitRuntimeService.save_account(token="SEU-TOKEN-IBM-QUANTUM-AQUI")
from qiskit import QuantumCircuit
from qiskit.quantum_info import SparsePauliOp
from qiskit.transpiler import generate_preset_pass_manager
from qiskit_ibm_runtime import EstimatorV2 as Estimator, QiskitRuntimeService
service = QiskitRuntimeService() # Conecta conta IBM Quantum, utilizar em cada script
qc = QuantumCircuit(2) # Cria circuito quântico com 2 qubits
qc.h(0) # Aplica porta Hadamard no qubit 0 (superposição)
qc.cx(0, 1) # Aplica porta CNOT (CX) com qubit 0 controlando qubit 1 (entrelaçamento)
qc.measure_all() # Mede todos qubits
qc.draw("mpl") # Desenha circuito
print(qc)
from qiskit import QuantumCircuit
from qiskit_ibm_runtime import QiskitRuntimeService
service = QiskitRuntimeService() # Conectar conta IBM Quantum
from qiskit import QuantumCircuit
from qiskit.quantum_info import Statevector
from qiskit.visualization import plot_state_qsphere, plot_bloch_multivector
import matplotlib.pyplot as plt
from qiskit_ibm_runtime import QiskitRuntimeService
service = QiskitRuntimeService()
ghz = QuantumCircuit(3) # Circuito com 3 qubits
ghz.h(0) # Hadamard (colocar qubit 0 em superposição)
ghz.cx(0, 1) # CNOT (entrelaçar qubit 0 com qubit 1)
ghz.cx(0, 2) # CNOT (entrelaçar qubit 0 com qubit 2)
print("Circuito GHZ (desenho em texto):")
print(ghz.draw("text")) # Qubits entrelaçados em estado GHZ
state_ghz = Statevector.from_instruction(ghz) # Vetor de estado do circuito GHZ
print("Vetor de estado GHZ resultante:")
print(state_ghz)
# Visualização QSphere — mostra amplitudes e fases
fig = plot_state_qsphere(state_ghz)
fig.figure.savefig("ghz_qsphere.png")
plt.imshow(fig.figure.canvas.renderer.buffer_rgba())
plt.show() # Estados com diferentes pesos/amplitudes em lados opostos da esfera, mas com mesma fase em coerência quântica (superposição coerente) e fase relativa zero entre eles
# Visualização nos vetores de Bloch dos qubits individuais
fig = plot_bloch_multivector(state_ghz)
fig.figure.savefig("ghz_bloch.png")
plt.imshow(fig.figure.canvas.renderer.buffer_rgba())
plt.show() # Cada qubit individualmente está em estado misto (completamente indefinido), mas sistema global está em estado puro GHZ entrelaçado
from qiskit import QuantumCircuit
from qiskit.quantum_info import Statevector
from qiskit.visualization import plot_state_qsphere, plot_bloch_multivector
import matplotlib.pyplot as plt
import math
import numpy as np
from qiskit_ibm_runtime import QiskitRuntimeService
service = QiskitRuntimeService()
w_vec = np.array([0, 1, 1, 0, 1, 0, 0, 0], dtype=complex) / math.sqrt(3) # Vetor de estado W de 3 qubits, onde |001>, |010>, |100> têm amplitude 1/√3
w = QuantumCircuit(3, name="W_state") # Circuito de 3 qubits em estado W
w.initialize(w_vec, [0, 1, 2]) # Inicializa 3 qubits no vetor w_vec
print("Circuito W (desenho em texto):")
print(w.draw("text"))
print("Circuito W (desenho em Matplotlib):")
fig = w.draw("mpl")
fig.savefig("w_circuit.png")
plt.imshow(fig.canvas.renderer.buffer_rgba())
plt.axis("off")
plt.show()
# QSphere: amplitudes e fases (3 bases computacionais têm amplitude diferente de zero, todas iguais a 1√3 ≈ 0.577)
fig = plot_state_qsphere(Statevector.from_instruction(w))
fig.figure.savefig("w_qsphere.png")
plt.imshow(fig.figure.canvas.renderer.buffer_rgba())
plt.axis("off")
plt.show()
# Bloch multivector: vetores de Bloch dos qubits individuais (3 qubits estão em estados individuais mistos, não puros)
# Chance de ser 1 (seta para baixo): Qubit só é 1 em 1 dos 3 termos da superposição, probabilidade = 1/3 (≅ 33%)
# Chance de ser 0 (seta para cima): Qubit é "0" nos outros 2 termos, probabilidade = 2/3 (≅ 67%)
fig2 = plot_bloch_multivector(Statevector.from_instruction(w))
fig2.figure.savefig("w_bloch.png")
plt.imshow(fig2.figure.canvas.renderer.buffer_rgba())
plt.axis("off")
plt.show()
# pip3 install qiskit qiskit-ibm-runtime qiskit-aer matplotlib qiskit[visualization]
# pip3 install qiskit-aer
from qiskit_ibm_runtime import QiskitRuntimeService
# QiskitRuntimeService.save_account(token="SEU_TOKEN_AQUI")
import math
import numpy as np
import matplotlib.pyplot as plt
from qiskit import QuantumCircuit, transpile
from qiskit.quantum_info import Statevector
from qiskit.visualization import plot_state_qsphere, plot_bloch_multivector
from qiskit_aer import AerSimulator
from qiskit_ibm_runtime import QiskitRuntimeService
service = QiskitRuntimeService()
def show_fig(fig):
fig.figure.savefig("tmp_fig.png")
plt.imshow(fig.figure.canvas.renderer.buffer_rgba())
plt.axis("off")
plt.show()
qc_super = QuantumCircuit(1, name="superposition") # Superposição básica
qc_super.h(0) # Hadamard cria superposição (|0> + |1>)/√2
print("Circuito superposição (Hadamard):")
print(qc_super.draw("text"))
fig = qc_super.draw(output="mpl")
show_fig(fig)
# Vetor de estado resultante (sem medida)
state = Statevector.from_instruction(qc_super)
fig_qs = plot_state_qsphere(state)
show_fig(fig_qs)
fig_bloch = plot_bloch_multivector(state)
show_fig(fig_bloch) # Bloch sphere, onde porta H leva |0> ao equador (positivo, superposição igual de |0> e |1>)
# Interferência básica HZH vs HIH
qc_id = QuantumCircuit(1, name="H-I-H") # circuito identidade (identidade não altera estado)
qc_id.h(0)
qc_id.h(0)
qc_z = QuantumCircuit(1, name="H-Z-H") # circuito com interferência de fase Z no meio
qc_z.h(0)
qc_z.z(0)
qc_z.h(0)
print("Circuito H-I-H (texto):")
print(qc_id.draw("text"))
print("Circuito H-Z-H (texto):")
print(qc_z.draw("text"))
state_id = Statevector.from_instruction(qc_id) # vetor de estado H-I-H
state_z = Statevector.from_instruction(qc_z) # vetor de estado H-Z-H
print("Vetor de estado (H-I-H):", state_id.data)
print("Vetor de estado (H-Z-H):", state_z.data)
fig_z = plot_state_qsphere(state_z) # QSphere para H-Z-H (mostra interferência causada pela porta Z, seta para baixo, informando +1 em |1>)
show_fig(fig_z)
fig_z_bloch = plot_bloch_multivector(state_z) # Bloch para H-Z-H (mostra que estado final é |1>, devido à interferência destrutiva em |0>)
show_fig(fig_z_bloch)
sim = AerSimulator() # Simulador local de medições (sem ruído) para validar probabilidades
qc_meas = QuantumCircuit(1,1) # Medição do circuito original de superposição
qc_meas.h(0)
qc_meas.measure(0,0)
qc_id_meas = qc_id.copy() # Medições para H-I-H e H-Z-H
qc_id_meas.measure_all()
qc_z_meas = qc_z.copy()
qc_z_meas.measure_all()
for circuit, name in [(qc_meas, "H"), (qc_id_meas, "H-I-H"), (qc_z_meas, "H-Z-H")]: # Compile e execute (shots)
t_qc = transpile(circuit, sim)
result = sim.run(t_qc, shots=1024).result()
print(f"\nContagens ({name}): {result.get_counts()}")
# H cria superposição (|0> + |1>)/√2, amplitudes iguais, medições ~50/50
# H-I-H é identidade, retorna |0>
# H-Z-H muda fase via aplicação de Z, interferindo e invertendo, cancelando |0> e reforçando |1>
# Medições confirmam probabilidades calculadas pelo vetor de estado
# O simulador rodou 1024 vezes, sendo 0 (522 vezes) e 1 (502 vezes). Portanto, ~50% para 0 e ~50% para 1
# Em H-I-H, resultado foi 0 (1024 vezes) e 1 (0 vezes), confirmando identidade
# Em H-Z-H, resultado foi 1 (1024 vezes) e 0 (0 vezes), confirmando interferência que inverteu estado de |0> para |1>
1º algoritmo quântico da história. Resolve problema de Deutsch em etapa única, via computação quântica. Dada função 'f:{0,1}->{0,1}', queremos saber se a mesma é constante (f(0) = f(1)) ou balanceada (f(0) ≠ f(1)).
A solução, na física clássica, avaliam-se ambas f(0) e f(1). Na física quântica, avaliam-se simultaneamente, via superposição e interferência, em única resposta (diferente de paralelismo clássico), extraindo propriedades da função, não valores individuais. Oráculo (porta Uf) é função escondida que algoritmo pode invocar, onde sabe-se apenas quais entradas e saídas, não seu código interno. Algoritmo quântico resolve Deutsch com apenas 1 consulta ao oráculo. Superposição avalia f(0) e f(1) simultaneamente, enquanto Interferência elimina resultados inúteis. Medição estratégica extrai propriedade global da função. Função constante resulta em estado |0⟩, enquanto função balanceada resulta em estado |1⟩.
# pip3 install qiskit qiskit-ibm-runtime qiskit-aer matplotlib qiskit[visualization]
from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit import QuantumCircuit, transpile
from qiskit_aer import Aer, AerSimulator
QiskitRuntimeService.save_account(token="SEU_TOKEN_AQUI")
# Função oráculo, cria circuito quântico de função clássica f: {0,1} -> {0,1}
def deutsch_function(case: int):
# case 1: f(x) = 0 (constante)
# case 2: f(x) = x (balanceada)
# case 3: f(x) = ¬x (balanceada)
# case 4: f(x) = 1 (constante)
if case not in [1, 2, 3, 4]:
raise ValueError("`case` must be 1, 2, 3, or 4.")
f = QuantumCircuit(2) # circuito quântico de 2 qubits (1 de entrada, 1 de saída)
if case in [2, 3]:
f.cx(0, 1) # CNOT (f(x) = x ou f(x) = ¬x)
if case in [3, 4]:
f.x(1) # NOT (f(x) = ¬x ou f(x) = 1)
return f
# Exemplo (case 3): f(x) = ¬x (balanceada)
display(deutsch_function(3).draw(output="mpl"))
# q0 (qubit de entrada): |0⟩ ou |1⟩
# q1 (qubit de saída): |1⟩ (inicializado em |1⟩ para permitir interferência)
# Porta CNOT aplicada de q0 para q1 (inverte q1 se q0 for |1⟩)
# Porta X (NOT) aplicada em q1 (inverte q1)
# Resultado final: f(0) = 1 e f(1) = 0
def compile_circuit(function: QuantumCircuit):
n = function.num_qubits - 1
qc = QuantumCircuit(n + 1, n) # n qubits de entrada + 1 qubit de saída
qc.x(n) # Porta X em qubit de saída (inicializa em |1⟩)
qc.h(range(n + 1)) # Portas Hadamard em todos qubits (superposição, exceto saída. q0 é superposição de |0⟩ e |1⟩ simultaneamente
qc.barrier()
qc.compose(function, inplace=True) # Oráculo quântico (CNOT + X), Porta CNOT (q0 é controle, q1 é alvo). Se q0 = 1 então q1 é invertido, se q0 = 0 então nada acontece. Porta X em q1 (inverte q1)
qc.barrier()
qc.h(range(n)) # Porta Hadamard em q0 (interferência - interferência construtiva em |0⟩, interferência destrutiva em |1⟩)
qc.measure(range(n), range(n)) # Medição dos qubits de entrada q0 (resultado: 0 = constante, 1 = balanceada)
return qc
# Exemplo de compilação do circuito Deutsch para f(x) = ¬x (case 3). c é bit clássico, resultado da medição
display(compile_circuit(deutsch_function(3)).draw(output="mpl"))
# Algoritmo de Deutsch, determina se função é constante (0) ou balanceada (1)
def deutsch_algorithm(function: QuantumCircuit):
qc = compile_circuit(function)
result = AerSimulator().run(qc, shots=1, memory=True).result()
measurements = result.get_memory()
if measurements[0] == "0":
return "constant"
return "balanced"
f = deutsch_function(3)
display(deutsch_algorithm(f))
Expansão do algoritmo de Deutsch para funções com múltiplos qubits de entrada, determinando se função é constante ou balanceada via única consulta ao oráculo. Deutsch estipula categorizações de funções quando qubits de entrada n for 0 ou 1. Para valores n maiores que 1, Deutsch-Jozsa considera entradas "indiferentes", sendo promessas de constante ou balanceada.
Após aplicar portas Hadamard (superposição) nos qubits de entrada, aplicação de 2ª camada de portas Hadamard alterarão estado quântico dos qubits de entrada.
# pip3 install qiskit qiskit-ibm-runtime qiskit-aer matplotlib qiskit[visualization]
from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit import QuantumCircuit, transpile
from qiskit_aer import Aer, AerSimulator
from qiskit.visualization import plot_histogram
from IPython.display import display
QiskitRuntimeService.save_account(token="SEU_TOKEN_AQUI")
# Função oráculo para Deutsch-Jozsa
def deutsch_jozsa_oracle(n, oracle_type="balanced"):
oracle = QuantumCircuit(n + 1) # n qubits de entrada + 1 qubit auxiliar de saída (fase)
if oracle_type == "constant": # Se função é constante (f(x) = 0), então nada ocorre
pass
elif oracle_type == "balanced": # Se função é balanceada (f(x) = 1 para metade dos inputs), então aplica CX (portas CNOTs para cada qubit, invertendo estado do qubit auxiliar)
for qubit in range(n):
oracle.cx(qubit, n)
return oracle
# Construir circuito Deutsch-Jozsa
def deutsch_jozsa_circuit(n, oracle):
qc = QuantumCircuit(n + 1, n) # n qubits de entrada + 1 qubit auxiliar de saída + n bits clássicos para medição
# Colocar qubit auxiliar em |1>
qc.x(n) # Aplica porta X (NOT) no qubit auxiliar
qc.h(n) # Aplica porta Hadamard no qubit auxiliar
# Aplica porta Hadamard nos qubits de entrada
for qubit in range(n):
qc.h(qubit)
qc.barrier()
qc.compose(oracle, inplace=True) # Aplica oráculo ao circuito
qc.barrier()
# Aplica 2ª camada de portas Hadamard nos qubits de entrada
for qubit in range(n):
qc.h(qubit)
# Mede qubits de entrada nos bits clássicos correspondentes
for qubit in range(n):
qc.measure(qubit, qubit)
return qc
n = 3 # Quantidade de qubits de entrada
oracle_type = "balanced" # Tipo de função ('constant' ou 'balanced')
oracle = deutsch_jozsa_oracle(n, oracle_type) # Cria oráculo
qc = deutsch_jozsa_circuit(n, oracle) # Cria circuito Deutsch-Jozsa
display(qc.draw(output="mpl"))
simulator = Aer.get_backend("aer_simulator") # Usa simulador AER
compiled = transpile(qc, simulator) # Transpila circuito para backend do simulador
result = simulator.run(compiled, shots=1024).result() # Executa circuito no simulador
counts = result.get_counts() # Obtém resultados da medição
display(plot_histogram(counts))
print("Resultados:", counts) # Exibe resultados da medição (000 indica função constante, qualquer outro resultado indica função balanceada)
Também chamado de problema de amostragem de Fourier, promete identificar string oculta via consulta única ao oráculo, ao invés de somente rótulos constante e balanceada.
# pip3 install qiskit qiskit-ibm-runtime qiskit-aer matplotlib qiskit[visualization]
from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit import QuantumCircuit, transpile
from qiskit_aer import Aer, AerSimulator
from qiskit.visualization import plot_histogram
from IPython.display import display
import matplotlib.pyplot as plt
QiskitRuntimeService.save_account(token="SEU_TOKEN_AQUI")
s = "1011" # String oculta 's'
n = len(s) # Quantidade de qubits de entrada
# Função oráculo Uf para Bernstein-Vazirani
def bernstein_vazirani_oracle(secret_string: str) -> QuantumCircuit:
n = len(secret_string)
oracle = QuantumCircuit(n + 1) # n qubits de entrada + 1 qubit auxiliar
for i, bit in enumerate(secret_string):
if bit == "1":
oracle.cx(i, n) # Aplica CNOT do qubit i de entrada para qubit auxiliar
oracle.name = "U_f"
return oracle
qc = QuantumCircuit(n + 1, n) # Circuito com n qubits de entrada, 1 qubit auxiliar e n bits clássicos
qc.x(n) # Aplica X (porta NOT) no qubit auxiliar, colocando-o em |1>
qc.h(range(n + 1)) # Aplica Hadamard em todos qubits
oracle = bernstein_vazirani_oracle(s) # Aplica oráculo ao circuito
qc.append(oracle, range(n + 1)) # Aplica oráculo aos qubits do circuito
qc.h(range(n)) # Aplica Hadamard novamente nos qubits de entrada
qc.measure(range(n), range(n)) # Mede qubits de entrada nos bits clássicos
display(qc.draw(output="mpl"))
simulator = AerSimulator()
tqc = transpile(qc, simulator) # Transpila circuito para simulador
result = simulator.run(tqc, shots=1024).result() # Executa circuito no simulador
counts = result.get_counts() # Obtém resultados da medição
print("Resultados da medição:", counts) # Exibe resultados (se houver mais de um, o mais frequente é a string oculta 's')
measured = list(counts.keys())[0]
print("Resultado quântico:", measured[::-1]) # Inverte string para corresponder à ordem dos qubits (q0 é o bit menos significativo)
Similar ao algoritmo de Bernstein-Vazirani, mas encontrar string oculta com propriedade de simetria específica, de forma indireta, via deduções a partir de restrições.
# pip3 install qiskit qiskit-ibm-runtime qiskit-aer qiskit[visualization]
import numpy as np
from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister, transpile
from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_aer import AerSimulator
from qiskit.visualization import plot_histogram
QiskitRuntimeService.save_account(token="SEU_TOKEN_AQUI")
s = "1000" # String oculta
n = len(s) # Quantidade de qubits de entrada
assert n >= 2 and any(c == "1" for c in s) # Verifica s válido
qr = QuantumRegister(2*n) # Circuito com 2n qubits
cr = ClassicalRegister(n) # Registrador clássico para n bits
qc = QuantumCircuit(qr, cr) # Inicialização do circuito
qc.h(range(n)) # Aplicar porta Hadamard nos qubits de entrada
qc.barrier()
# Oráculo de Simon:
for i in range(n):
qc.cx(qr[i], qr[n+i]) # Aplicar porta CNOT (entrelaçamento) nos qubits de entrada e saída
j = s.find("1") # Encontrar posição do qubit de controle (primeiro "1" em s)
for i, bit in enumerate(s):
if bit == "1":
qc.cx(qr[j], qr[n+i]) # Aplicar porta CNOT para criar função oculta
perm = list(np.random.permutation(n)) # Permutação aleatória dos qubits de entrada
init = list(range(n))
i = 0
while i < n:
if init[i] != perm[i]:
k = perm.index(init[i]) # Encontrar posição do qubit a ser trocado
qc.swap(qr[n+i], qr[n+k]) # Aplicar porta SWAP para permutar qubits de saída
init[i], init[k] = init[k], init[i] # Atualizar lista de inicialização
else:
i += 1
for i in range(n):
if np.random.random() > 0.5:
qc.x(qr[n+i]) # Aplicar porta X (NOT) para criar ruído
qc.barrier()
qc.h(range(n)) # Aplicar porta Hadamard novamente nos qubits de entrada
qc.measure(range(n), range(n)) # Medir qubits de entrada e armazenar resultados no registrador clássico
display(qc.draw("mpl"))
sim = AerSimulator()
tqc = transpile(qc, sim) # Transpilar circuito para simulador local
result = sim.run(tqc, shots=2048).result() # Executar circuito e obter resultados
counts = result.get_counts()
print("Contagens:", counts)
plot_histogram(counts)
# Resolver Simon (GF2):
Y = []
for bitstr, c in counts.items():
if bitstr != "0"*n: # Se resultado não for vetor nulo
y = [int(b) for b in bitstr[::-1]] # Converter string de bits para lista de inteiros (revertendo ordem)
if y not in Y:
Y.append(y)
Y = np.array(Y)
def gf2_rref(A):
A = A.copy() % 2 # Garantir que A seja binária
rows, cols = A.shape # Obter número de linhas e colunas
r = 0
pivots = [] # Lista para armazenar índices das colunas pivô
for c in range(cols):
pivot = None
for i in range(r, rows):
if A[i,c] == 1: # Encontrar linha com 1 na coluna c
pivot = i
break
if pivot is None:
continue
A[[r,pivot]] = A[[pivot,r]] # Trocar linha r com linha pivot
pivots.append(c) # Adicionar índice da coluna pivô à lista
for i in range(rows):
if i != r and A[i,c] == 1:
A[i] ^= A[r] # Subtrair linha r da linha i (XOR para GF(2))
r += 1
if r == rows:
break
return A, pivots
R, pivots = gf2_rref(Y) # Obter forma reduzida por linhas e índices dos pivôs
free_cols = [c for c in range(n) if c not in pivots] # Colunas livres correspondem variáveis que podem ser escolhidas livremente
s_found = np.zeros(n, dtype=int) # Inicializar vetor s encontrado com zeros
if len(free_cols) == 0:
print("Nenhuma variável livre - oráculo degenerado")
else:
s_found[free_cols[0]] = 1 # Escolher primeira variável livre como 1 (pode ser qualquer combinação linear das variáveis livres)
for r, c in enumerate(pivots):
if R[r, free_cols[0]] == 1: # Se linha r tem 1 na coluna da variável livre escolhida, então s[c] deve ser 1 para satisfazer a equação
s_found[c] = 1 # Definir s[c] como 1 para satisfazer equação Y·s = 0 mod 2
s_found_str = "".join(map(str, s_found)) # Converter vetor s encontrado para string de bits (valor final encontrado)
def dot_mod2(a,b):
return sum(x*y for x,y in zip(a,b)) % 2 # Função para calcular produto escalar mod 2 entre dois vetores
ok = all(dot_mod2(row, s_found) == 0 for row in Y) # Verificar se Y·s_found = 0 mod 2 para todas linhas de Y
print("Sistema Y:", Y)
print("s encontrado:", s_found_str)
print("s real:", s)
print("Verificação Y·s = 0 mod2:", ok)
Protocolo quântico para transferir estado quântico de qubit a outro, via comunicação clássica de 2 bits, resultante de emaranhamento quântico pós-medição de Bell AB. Matéria física não é teletransportada, apenas informação (estado quântico). Alice (remetente) possui qubit A, e Bob (receptor) possui qubit B emaranhado com Alice (Bell, par emaranhado AB), possibilitando transmitir estado quântico entre eles. Alice mede AB e passa a ter 3º qubit Q, com estado desconhecido, que deseja teletransportar ao Bob. Alice mede AB, obtendo 2 bits clássicos, e envia a Bob. Bob aplica correções em B com base nos bits recebidos, recuperando estado original de Q em B. Etapas do teletransporte quântico:
Após teletransporte, é impossível clonar estado quântico de Q, pois mudou-se do valor original. O estado de A também foi modificado, não estando mais emaranhado com B. Fluxo do teletransporte quântico:
# pip3 install qiskit qiskit-ibm-runtime qiskit-aer qiskit[visualization]
from qiskit_ibm_runtime import QiskitRuntimeService, SamplerV2 as Sampler
from qiskit import QuantumCircuit, transpile, ClassicalRegister, QuantumRegister
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit_aer import AerSimulator
from qiskit_aer.noise import NoiseModel
from qiskit.primitives import BackendSamplerV2
from qiskit.visualization import plot_histogram
from IPython.display import display
import numpy as np
QiskitRuntimeService.save_account(token="SEU_TOKEN_AQUI")
secret = QuantumRegister(1, "Q") # Qubit Q, que contém estado quântico a ser teletransportado
Alice = QuantumRegister(1, "A") # Qubit A de Alice (remetente)
Bob = QuantumRegister(1, "B") # Qubit B de Bob (destinatário)
cr = ClassicalRegister(3, "c") # Bits clássicos para armazenar resultados das medições de Alice
qc = QuantumCircuit(secret, Alice, Bob, cr) # Circuito quântico que implementa protocolo de teletransporte
qc.h(Alice) # Alice aplica porta Hadamard (superposição) em A
qc.cx(Alice, Bob) # Alice aplica porta CNOT (entrelaçamento) entre A e B
qc.barrier()
np.random.seed(42) # Criar estado quântico aleatório para Q
theta = np.random.uniform(0.0, 1.0) * np.pi # de 0 a pi
varphi = np.random.uniform(0.0, 2.0) * np.pi # de 0 a 2*pi
qc.u(theta, varphi, 0.0, secret) # Alice aplica porta U (rotação unitária - prepara estado quântico possível do qubit a partir dos ângulos dados) em Q, parametrizada por theta e varphi
qc.barrier()
qc.cx(secret, Alice) # Alice aplica porta CNOT em Bell QA
qc.h(secret) # Alice aplica porta Hadamard em Q
qc.barrier()
qc.measure(Alice, cr[1]) # Alice mede A e armazena resultado em bit clássico cr[1]
qc.measure(secret, cr[0]) # Alice mede Q e armazena resultado em bit clássico cr[0]
# Condição para Bob aplicar correção em B, conforme das medições de Alice (cr[1] e cr[0])
with qc.if_test((cr[1], 1)):
qc.x(Bob) # Se cr[1] for 1, Bob aplica porta Pauli X (NOT) em B
with qc.if_test((cr[0], 1)):
qc.z(Bob) # Se cr[0] for 1, Bob aplica porta Pauli Z (fase) em B
qc.draw(output="mpl")
qc.barrier()
qc.u(theta, varphi, 0.0, Bob).inverse() # Bob aplica inversa da porta U para verificar se recuperou estado original de Q
qc.measure(Bob, cr[2]) # Bob mede B e armazena resultado em bit clássico cr[2]
qc.draw(output="mpl")
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False, min_num_qubits=127) # Encontra backend físico disponível com mínimo 127 qubits
print(backend.name)
target = backend.target
pm = generate_preset_pass_manager(target=target, optimization_level=3)
qc_isa = pm.run(qc) # Transpilação do circuito quântico para backend escolhido, otimizando para nível 3 (máximo)
sampler = Sampler(mode=backend)
noise_model = NoiseModel.from_backend(backend) # Extrai modelo de ruído do backend físico para simulação realista
backend_sim = AerSimulator(noise_model=noise_model)
sampler_sim = BackendSamplerV2(backend=backend_sim) # Simulador com modelo de ruído do backend físico
job = sampler.run([qc_isa]) # Executa circuito transpilado no backend físico via sampler
res = job.result() # Obtém resultados da execução do circuito no backend físico
counts = res[0].data.c.get_counts() # Extrai contagem de resultados das medições (bits clássicos) para análise
plot_histogram(counts) # Medição de Bob (cr[2])=0, indicando que estado original de Q foi teletransportado com sucesso para B
Protocolo quântico para transmitir 2 bits clássicos enviando apenas 1 qubit, desde que Alice e Bob compartilhem previamente par emaranhado (par de Bell). Em vez de permitir transmissão de qubit via 2 bits clássicos de comunicação, permite transmitir 2 bits clássicos qubit de comunicação quântica. Remetente (Alice) possui qubit A, e destinatário (Bob) possui qubit B. Juntos, possuem par de Bell AB. Alice quer enviar 2 bits clássicos (c e d) para Bob, via qubit. Conforme teorema de Holevo, emaranhamento compartilhado permite dobrar capacidade de transmissão de informação clássica dos qubits enviados. Etapas:
Em síntese, Alice escolhe qual estado de Bell deseja compartilhar com Bob, envia seu qubit para Bob, e Bob mede para determinar qual estado de Bell Alice escolheu.
# pip3 install qiskit qiskit-ibm-runtime qiskit-aer qiskit[visualization]
from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit import QuantumCircuit, ClassicalRegister, QuantumRegister
from qiskit_aer import AerSimulator
from qiskit.visualization import plot_histogram
from IPython.display import display
QiskitRuntimeService.save_account(token="SEU_TOKEN_AQUI")
c = "1"
d = "0"
qc = QuantumCircuit(2)
# Preparar estado de Bell entre Alice e Bob
qc.h(0) # Aplica Hadamard (superposição) no qubit de Alice
qc.cx(0, 1) # Aplica CNOT (entrelaçamento) para criar estado de Bell entre Alice (qubit 0) e Bob (qubit 1)
qc.barrier()
# Operações de Alice, dependendo dos bits c e d:
if d == "1":
qc.z(0) # Se d é 1, aplica Z (inversão de fase) no qubit de Alice
if c == "1":
qc.x(0) # Se c é 1, aplica X (inversão de bit) no qubit de Alice
qc.barrier()
# Operações de Bob para decodificar bits de Alice:
qc.cx(0, 1) # Aplica CNOT para desfazer entrelaçamento entre Alice e Bob
qc.h(0) # Aplica Hadamard para transformar estado de Bob em estado mensurável
qc.measure_all() # Mede ambos qubits para obter bits de Alice
display(qc.draw(output="mpl"))
result = AerSimulator().run(qc).result()
statistics = result.get_counts()
for outcome, frequency in statistics.items():
print(f"Measured {outcome} with frequency {frequency}") # Exibe resultados de medição (cd)
display(plot_histogram(statistics)) # Exibe resultados de medição (cd) para teste de superdense coding
rbg = QuantumRegister(1, "coin") # 'coin' qubit para gerar bits de Alice
ebit0 = QuantumRegister(1, "A") # Qubit de Alice
ebit1 = QuantumRegister(1, "B") # Qubit de Bob
Alice_c = ClassicalRegister(1, "Alice c") # Registrador clássico para bit c de Alice
Alice_d = ClassicalRegister(1, "Alice d") # Registrador clássico para bit d de Alice
test = QuantumCircuit(rbg, ebit0, ebit1, Alice_d, Alice_c) # Circuito para teste de superdense coding
# Preparar estado de Bell entre Alice e Bob
test.h(ebit0) # Aplica Hadamard (superposição) no qubit de Alice
test.cx(ebit0, ebit1) # Aplica CNOT (entrelaçamento) para criar estado de Bell entre Alice (ebit0) e Bob (ebit1)
test.barrier()
# Use the 'coin' qubit twice to generate Alice's bits c and d.
test.h(rbg) # Aplica Hadamard para criar superposição no 'coin' qubit
test.measure(rbg, Alice_c) # Mede 'coin' qubit para obter bit c de Alice
test.h(rbg) # Aplica Hadamard novamente para preparar 'coin' qubit para bit d
test.measure(rbg, Alice_d) # Mede 'coin' qubit para obter bit d de Alice
test.barrier()
# Operações de Alice, dependendo dos bits c e d:
with test.if_test((Alice_d, 1), label="Z"):
test.z(ebit0) # Se d é 1, aplica Z (inversão de fase) no qubit de Alice
with test.if_test((Alice_c, 1), label="X"):
test.x(ebit0) # Se c é 1, aplica X (inversão de bit) no qubit de Alice
test.barrier()
# Operações de Bob:
test.cx(ebit0, ebit1) # Aplica CNOT para desfazer entrelaçamento entre Alice e Bob
test.h(ebit0) # Aplica Hadamard para transformar estado de Bob em estado mensurável
test.barrier()
Bob_c = ClassicalRegister(1, "Bob c") # Registrador clássico para bit c de Bob
Bob_d = ClassicalRegister(1, "Bob d") # Registrador clássico para bit d de Bob
test.add_register(Bob_d) # Adiciona registrador clássico para bit d de Bob
test.add_register(Bob_c) # Adiciona registrador clássico para bit c de Bob
test.measure(ebit0, Bob_d) # Mede qubit de Alice para obter bit d de Bob
test.measure(ebit1, Bob_c) # Mede qubit de Bob para obter bit c de Bob
display(test.draw(output="mpl"))
result = AerSimulator().run(test).result()
statistics = result.get_counts()
display(plot_histogram(statistics)) # Exibe resultados de medição (cd) para teste de superdense coding
Protocolo quântico para distribuição segura de chaves criptográficas entre 2 partes (Alice e Bob). Protocolo BB84 (Bennett & Brassard), Alice envia qubits em estados de polarização específicos, e Bob mede esses qubits usando bases de medição aleatórias. Após transmissão, Alice e Bob comparam suas bases de medição publicamente, descartando casos onde bases não coincidem. Bits restantes formam chave compartilhada. Se terceiro (Eve) tentar interceptar qubits, natureza quântica da informação fará com que presença de Eve seja detectada (Teorema da Não-Clonagem, No-cloning theorem), garantindo segurança da chave distribuída. Etapas de QKD:
Resistência a interceptação e detecção de eavesdropping garantem segurança da chave distribuída, contra métodos de interceptação para descriptografia, conhecida por eavesdropper (espião, bisbilhoteiro) 'Eve'. Eve deve adivinhar base usada na codificação de cada bit. Eve não consegue criar cópia perfeita dos qubits devido ao Teorema da Não-Clonagem. Exemplo de tentativa de eavesdropper:
Exemplo de QKD, via protocolo BB84:
# pip3 install qiskit qiskit-ibm-runtime qiskit-aer matplotlib qiskit[visualization]
from qiskit_ibm_runtime import QiskitRuntimeService, SamplerV2 as Sampler
from qiskit import QuantumCircuit, ClassicalRegister, QuantumRegister
from qiskit_aer import AerSimulator
from qiskit_aer.noise import NoiseModel
from qiskit.primitives import BackendSamplerV2
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
import numpy as np
QiskitRuntimeService.save_account(token="SEU_TOKEN_AQUI")
# SEM EAVESDROPPER:
rng = np.random.default_rng()
bit_num = 20
qc = QuantumCircuit(bit_num, bit_num)
# QKD 1 - Alice prepara bits e bases
abits = np.round(rng.random(bit_num))
abase = np.round(rng.random(bit_num))
# Alice prepara estados conforme tabela 1:
for n in range(bit_num):
if abits[n] == 0:
if abase[n] == 1:
qc.h(n)
if abits[n] == 1:
if abase[n] == 0:
qc.x(n)
if abase[n] == 1:
qc.x(n)
qc.h(n)
qc.barrier()
# QKD 2 - Bob escolhe bases aleatórias para medir
bbase = np.round(rng.random(bit_num))
for m in range(bit_num):
if bbase[m] == 1:
qc.h(m)
qc.measure(m, m)
print("Alice's bits are ", abits)
print("Alice's bases are ", abase)
print("Bob's bases are ", bbase)
display(qc.draw("mpl"))
# Executar circuito em backend físico com ruído
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False, min_num_qubits = 127)
print(backend.name)
# Transpilar circuito para backend escolhido, otimizando para nível 3 (máximo)
noise_model = NoiseModel.from_backend(backend)
backend_sim = AerSimulator(noise_model=noise_model)
sampler_sim = BackendSamplerV2(backend=backend_sim)
target = backend.target
pm = generate_preset_pass_manager(target=target, optimization_level=3)
qc_isa = pm.run(qc)
# Executar circuito transpilado no backend físico via sampler
sampler = Sampler(mode=backend)
job = sampler.run([qc_isa], shots=1)
counts = job.result()[0].data.c.get_counts()
countsint = job.result()[0].data.c.get_int_counts()
# Analisar resultados de medição de Bob, comparando com bits e bases de Alice para determinar quais bits formam chave compartilhada
keys = counts.keys()
key = list(keys)[0]
bmeas = list(key)
bmeas_ints = []
for n in range(bit_num):
bmeas_ints.append(int(bmeas[n]))
bbits = bmeas_ints[::-1]
print(bbits)
# QKD 3 - Pós-processamento para gerar chave compartilhada
agoodbits = []
bgoodbits = []
match_count = 0
for n in range(bit_num):
# Check whether bases matched.
if abase[n] == bbase[n]:
agoodbits.append(int(abits[n]))
bgoodbits.append(bbits[n])
# If bits match when bases matched, increase count of matching bits
if int(abits[n]) == bbits[n]:
match_count += 1
print(agoodbits)
print(bgoodbits)
print("Fidelity:", match_count / len(agoodbits))
print("Loss:", 1 - match_count / len(agoodbits))
# COM EAVESDROPPER:
bit_num = 20
qr = QuantumRegister(bit_num, "q")
cr = ClassicalRegister(bit_num, "c")
qc = QuantumCircuit(qr, cr)
abits = np.round(rng.random(bit_num))
abase = np.round(rng.random(bit_num))
for n in range(bit_num):
if abits[n] == 0:
if abase[n] == 1:
qc.h(n)
if abits[n] == 1:
if abase[n] == 0:
qc.x(n)
if abase[n] == 1:
qc.x(n)
qc.h(n)
qc.barrier()
# Eavesdropper (Eve) intercepta e mede os qubits
ebase = np.round(rng.random(bit_num))
for m in range(bit_num):
if ebase[m] == 1:
qc.h(m)
qc.measure(qr[m], cr[m])
target = backend.target
pm = generate_preset_pass_manager(target=target, optimization_level=3)
qc_isa = pm.run(qc)
job = sampler_sim.run([qc_isa], shots=1)
counts = job.result()[0].data.c.get_counts()
countsint = job.result()[0].data.c.get_int_counts()
# Analisar resultados de medição de Eve, comparando com bits e bases de Alice para determinar quais bits formam chave compartilhada
keys = counts.keys()
key = list(keys)[0]
emeas = list(key)
emeas_ints = []
for n in range(bit_num):
emeas_ints.append(int(emeas[n]))
ebits = emeas_ints[::-1]
print(ebits)
qr = QuantumRegister(bit_num, "q")
cr = ClassicalRegister(bit_num, "c")
qc = QuantumCircuit(qr, cr)
for n in range(bit_num):
if ebits[n] == 0:
if ebase[n] == 1:
qc.h(n)
if ebits[n] == 1:
if ebase[n] == 0:
qc.x(n)
if ebase[n] == 1:
qc.x(n)
qc.h(n)
qc.barrier()
# Bob escolhe bases aleatórias para medir
bbase = np.round(rng.random(bit_num))
for m in range(bit_num):
if bbase[m] == 1:
qc.h(m)
qc.measure(qr[m], cr[m])
target = backend.target
pm = generate_preset_pass_manager(target=target, optimization_level=3)
qc_isa = pm.run(qc)
job = sampler_sim.run([qc_isa], shots=1)
counts = job.result()[0].data.c.get_counts()
countsint = job.result()[0].data.c.get_int_counts()
keys = counts.keys()
key = list(keys)[0]
bmeas = list(key)
bmeas_ints = []
for n in range(bit_num):
bmeas_ints.append(int(bmeas[n]))
bbits = bmeas_ints[::-1]
print(bbits)
agoodbits = []
bgoodbits = []
match_count = 0
for n in range(bit_num):
if abase[n] == bbase[n]:
agoodbits.append(int(abits[n]))
bgoodbits.append(bbits[n])
if int(abits[n]) == bbits[n]:
match_count += 1
print(agoodbits)
print(bgoodbits)
print("Fidelity:", match_count / len(agoodbits))
print("Loss:", 1 - match_count / len(agoodbits))
# Com Eve, fidelidade e perda são piores, indicando que presença de Eve foi detectada, comprometendo segurança da chave compartilhada, alterando valor final da chave, e permitindo que Alice e Bob descartem chave comprometida, garantindo segurança da comunicação.
A Transformação de Fourier quântica (Quantum Fourier Transform, QFT) é operação fundamental em circuitos quânticos que transforma estado quântico em seu domínio de Fourier (transformar base em outra). É a versão quântica da Transformada Discreta de Fourier (DFT). Enquanto DFT clássica transforma vetor de nºs, QFT transforma amplitudes de estado quântico. Principais pares de bases conectados pela transformada de Fourier são posição e momento, e tempo e frequência. Transformada de Fourier é usada para representar função como combinação linear de novo conjunto das "funções de base". Transformações de base também são feitas regularmente em estados de qubit. Estados de Qubit também podem ser expressos na base de Fourier, sendo estado é expresso em termos de combinação linear ordenados (00,|00...00⟩ até 1,|11...11⟩). Fases dos componentes variam de 0 a 2π. Cada estado tem uma fase que é 2π/4 radianos mais alta do que estado anterior. Etapas da QFT, atuando como "detector de periodicidade":
Componentes da QFT:
QFT é implementada via portas Hadamard (H), Portas de fase controladas (CP, CRZ), e SWAP (para reverter ordem dos qubits). Para cada qubit: H → rotações controladas → próximo qubit. Exemplo de circuito lógico contendo 3 qubits:
Complexidade QFT para n qubits:
# pip3 install qiskit qiskit-ibm-runtime qiskit-aer qiskit[visualization]
import numpy as np
from qiskit import QuantumCircuit
from qiskit.visualization import plot_histogram
from qiskit.circuit.library import QFTGate
from qiskit_ibm_runtime import QiskitRuntimeService, SamplerV2 as Sampler
from qiskit.primitives import BackendSamplerV2
from qiskit_aer import AerSimulator
from qiskit_aer.noise import NoiseModel
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
QiskitRuntimeService.save_account(token="SEU_TOKEN_AQUI")
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False, min_num_qubits = 127)
print(backend.name)
noise_model = NoiseModel.from_backend(backend)
backend_sim = AerSimulator(noise_model=noise_model)
sampler_sim = BackendSamplerV2(backend=backend_sim)
# ALGORITMO 1: Transformar único estado de base computacional:
# Passo 1: Mapear estado de base computacional aleatório
qubits = 4
N = 2**qubits
qc = QuantumCircuit(qubits)
# Inverter estado de qubits aleatórios para colocar em estado de base computacional único aleatório
for i in range(1, qubits):
if np.random.randint(0, 2):
qc.x(i)
# Criar uma cópia do circuito acima (para ser usado quando aplicarmos QFT na próxima parte)
qc_qft = qc.copy()
qc.measure_all()
qc.draw("mpl")
# Passo 2: Transpilar circuito com QFT
target = backend.target
pm = generate_preset_pass_manager(target=target, optimization_level=3)
qc_isa = pm.run(qc)
# Passo 3: Executar job em computador quântico
sampler = Sampler(mode=backend)
pubs = [qc_isa]
job = sampler.run(pubs, shots=1000)
res = job.result()
counts = res[0].data.meas.get_counts()
plot_histogram(counts)
# Conclusão: Resultado deve ser estado de base computacional único aleatório mapeado no passo 1
# Transformação de Fourier de único estado via QFTGate:
# Paso 1: Mapear estado de base computacional aleatório
qc_qft.compose(QFTGate(qubits), inplace=True)
qc_qft.measure_all()
qc_qft.draw("mpl")
# Passo 2: Transpilar circuito com QFT
target = backend.target
pm = generate_preset_pass_manager(target=target, optimization_level=3)
qc_isa = pm.run(qc_qft)
# Passo 3: Executar job em computador quântico
sampler = Sampler(mode=backend)
pubs = [qc_isa]
job = sampler.run(pubs, shots=1000)
res = job.result()
counts = res[0].data.meas.get_counts()
plot_histogram(counts)
# Transformar 2 estados de base computacional:
# Passo 1: Mapear estado de base computacional aleatório
qubits = 4
N = 2**qubits
qc = QuantumCircuit(qubits)
qc.h(qubits - 1)
qc_qft = qc.copy()
qc.measure_all()
qc.draw("mpl")
# Passo 2: Transpilar circuito com QFT
target = backend.target
pm = generate_preset_pass_manager(target=target, optimization_level=3)
qc_isa = pm.run(qc)
# Passo 3: Executar job em computador quântico
sampler = Sampler(mode=backend)
pubs = [qc_isa]
job = sampler.run(pubs, shots=1000)
res = job.result()
counts = res[0].data.meas.get_counts()
plot_histogram(counts)
# Transformação de Fourier de 2 estados via QFTGate:
# Passo 1: Mapear estado de base computacional aleatório
qc_qft.compose(QFTGate(qubits), inplace=True)
qc_qft.measure_all()
qc_qft.draw("mpl")
# Passo 2: Transpilar circuito com QFT
target = backend.target
pm = generate_preset_pass_manager(target=target, optimization_level=3)
qc_isa = pm.run(qc_qft)
# Passo 3: Executar job em computador quântico
sampler = Sampler(mode=backend)
pubs = [qc_isa]
job = sampler.run(pubs, shots=1000)
res = job.result()
counts = res[0].data.meas.get_counts()
plot_histogram(counts)
# Analisando algoritmo QFT:
qc = QuantumCircuit(1)
qc.compose(QFTGate(1), inplace=True)
qc.decompose().draw("mpl")
qc = QuantumCircuit(2)
qc.compose(QFTGate(2), inplace=True)
qc.decompose().draw("mpl")
qc = QuantumCircuit(3)
qc.compose(QFTGate(3), inplace=True)
qc.decompose().draw("mpl")
qc = QuantumCircuit(4)
qc.compose(QFTGate(4), inplace=True)
qc.decompose().draw("mpl")
Quantum Phase Estimation é algoritmo para estimar fase (autovalor) a vetor próprio de operador unário, sendo útil para encontrar valores próprios de matrizes unitárias (converte fase em bits). Geralmente, utiliza-se QPE juntamente com QFT, usada para extrair informação de fase do estado quântico. QPE usa Hadamard (superposição), Portas controladas U²k, Transformada de Fourier Quântica Inversa (QFT⁻¹) e medição. Estrutura de circuito QPE:
Fluxo de funcionamento do QPE:
# pip3 install qiskit qiskit-ibm-runtime qiskit-aer qiskit[visualization]
from qiskit.circuit.library import QFT
from qiskit_ibm_runtime import QiskitRuntimeService, Sampler
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit.visualization import plot_histogram
from qiskit import QuantumCircuit, transpile
from qiskit_aer import AerSimulator, Aer
from qiskit_aer.noise import NoiseModel
import numpy as np
from numpy import pi
QiskitRuntimeService.save_account(token="SEU_TOKEN_AQUI")
# Algoritmo 1: Noções de QPE
qpe = QuantumCircuit(4, 3) # Criar circuito com 4 qubits (3 para contagem e 1 para o estado eigen) e 3 bits clássicos para leitura
qpe.x(3) # Aplicar porta X (NOT) no qubit para prepará-lo no estado |1>, que é autovetor do operador U que queremos estimar
display(qpe.draw("mpl"))
for qubit in range(3):
qpe.h(qubit) # Aplicar porta Hadamard (superposição) nos 3 qubits de contagem
display(qpe.draw("mpl"))
repetitions = 1
for counting_qubit in range(3):
for i in range(repetitions):
qpe.cp(pi / 4, counting_qubit, 3) # Realizamos operações unitárias controladas (C-U)
repetitions *= 2
display(qpe.draw("mpl"))
qpe.append(QFT(3, inverse=True), [0, 1, 2]) # Aplicar QFT inversa nos qubits de contagem
display(qpe.draw("mpl"))
for n in range(3):
qpe.measure(n, n) # Medir qubits
display(qpe.draw("mpl"))
aer_sim = AerSimulator()
shots = 2048
pm = generate_preset_pass_manager(backend=aer_sim, optimization_level=1)
t_qpe = pm.run(qpe)
# Algoritmo 2: QPE para 3 qubits de contagem e 1 qubit para estado
qpe = QuantumCircuit(4, 3)
for qubit in range(3):
qpe.h(qubit)
qpe.x(3)
angle = 2 * pi / 3
repetitions = 1
for counting_qubit in range(3):
for i in range(repetitions):
qpe.cp(angle, counting_qubit, 3)
repetitions *= 2
qpe.append(QFT(3, inverse=True), [0, 1, 2])
for n in range(3):
qpe.measure(n, n)
display(qpe.draw("mpl"))
aer_sim = AerSimulator()
shots = 4096
pm = generate_preset_pass_manager(backend=aer_sim, optimization_level=1)
t_qpe = pm.run(qpe)
sampler = Sampler(mode=aer_sim)
job = sampler.run([t_qpe], shots=shots)
result = job.result()
answer = result[0].data.c.get_counts()
plot_histogram(answer)
# Mapear problema para circuitos e operadores quânticos:
service = QiskitRuntimeService()
qpe = QuantumCircuit(4, 3)
qpe.x(3)
for qubit in range(3):
qpe.h(qubit)
repetitions = 1
for counting_qubit in range(3):
for i in range(repetitions):
qpe.cp(pi / 4, counting_qubit, 3)
repetitions *= 2
qpe.append(QFT(3, inverse=True), [0, 1, 2])
for n in range(3):
qpe.measure(n, n)
display(qpe.draw("mpl"))
backend = service.least_busy(simulator=False, operational=True, min_num_qubits=4)
print(backend.name)
pm = generate_preset_pass_manager(backend=backend, optimization_level=2)
qc_compiled = pm.run(qpe)
qc_compiled.draw("mpl", idle_wires=False)
real_sampler = Sampler(mode=backend)
job = real_sampler.run([qc_compiled], shots=1024)
job_id = job.job_id()
print("Job id:", job_id)
job = service.job(job_id)
job.status()
result_real = job.result()
print(result_real)
plot_histogram(result_real[0].data.c.get_counts())
Amplificação de erro probabilística (PEA) é técnica para reconstrução de ruído e amplificação precisa de sinais quânticos, para mitigar erros de hardware. Em vez de reduzir ruído diretamente, PEA amplifica erro, de forma controlada, medindo circuito em múltiplos níveis de ruído, concluindo (extrapolação) com estimativa do resultado sem ruído, via Zero-Noise Extrapolation (ZNE). Inicia aprendendo modelo giratório de cada camada de portas emaranhadas no circuito quântico. Após cada fase de aprendizado, circuitos são executados em cada fator de ruído, amplificando cada camada de emaranhamento pela injeção probabilística de ruído de único qubit proporcional. Estágios do PEA:
Etapas de execução PEA:
# pip3 install qiskit qiskit-ibm-runtime qiskit-aer qiskit[visualization]
from qiskit_ibm_runtime import EstimatorV2 as Estimator, QiskitRuntimeService
from qiskit import QuantumCircuit
from qiskit.quantum_info import SparsePauliOp
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
QiskitRuntimeService.save_account(token="SEU_TOKEN_AQUI")
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False, min_num_qubits=127)
print(backend.name)
qc = QuantumCircuit(2)
qc.h(0) # Aplicar porta Hadamard (superposição) no qubit 0
qc.cx(0, 1) # Aplicar porta CNOT (entrelaçamento) entre qubit 0 e 1
qc.rx(0.4, 0) # Rotação em X (0.4) no qubit 0
qc.ry(0.7, 1) # Rotação em Y (0.7) no qubit 1
display(qc.draw("mpl"))
observable = SparsePauliOp("ZZ") # Define observável "ZZ" (correlação entre qubits) para medir correlação entre 2 qubits
estimator = Estimator(mode=backend)
estimator.options.resilience.zne_mitigation = True # Habilitar mitigação de erro Zero Noise Extrapolation (ZNE) para melhorar precisão dos resultados em dispositivos quânticos ruidosos
estimator.options.resilience.zne.amplifier = "pea" # Define amplificador de erro como "pea" para aumentar ruído de forma controlada
estimator.options.resilience.zne.noise_factors = [1, 3, 5] # Define fatores de escala de ruído para ZNE, permitindo extrapolação a partir de múltiplos níveis de ruído
estimator.options.resilience.zne.extrapolator = "linear" # Define método de extrapolação como "linear" para estimar valor esperado no limite de ruído zero a partir dos resultados obtidos com diferentes níveis de ruído
print(estimator.options.resilience.zne) # Exibe as configurações de mitigação ZNE para verificar se estão corretas
pm = generate_preset_pass_manager(
backend=backend,
optimization_level=1
)
isa_qc = pm.run(qc)
isa_observable = observable.apply_layout(isa_qc.layout)
job = estimator.run([(isa_qc, isa_observable)])
result = job.result()
print(result[0].data.evs) # exibe os valores esperados com mitigação ZNE aplicada (correlações entre qubits. Perto -1: correlação fraca, perto de 1: correlação forte)
Também chamado Iterative quantum phase estimation (IQPE). Versão iterativa da estimativa de fase PEA, usando 1 qubit de controle repetidas vezes (iterações), medindo 1 qubit da fase por ciclo/iteração (qubit a qubit), compondo várias iterações. Diferente do PEA, IPEA dispensa QFT completa. Ideal para circuito quântico menor e mais profundo. Enquanto PEA descobre todos qubits da fase em única iteração, IPEA descobre 1 qubit, e usa feedback para descobrir próximo, sucessivamente. Etapas do IPEA:
# pip3 install qiskit qiskit-aer qiskit-ibm-runtime numpy
import numpy as np
from random import random
from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister, transpile
from qiskit_aer import AerSimulator
from qiskit_ibm_runtime import QiskitRuntimeService
QiskitRuntimeService.save_account(token="SEU_TOKEN_AQUI")
backend = AerSimulator()
q = QuantumRegister(1, "q")
a = QuantumRegister(1, "anc")
c = ClassicalRegister(1, "c")
E_1, E_2 = (2 * np.pi * random(), 2 * np.pi * random()) # Autovalores simulados
t = 1 # Tempo de evolução simulado
print("E_2 real =", E_2)
unitary = QuantumCircuit(q, name="U") # Operador unitário U = exp(iHt) simulado via portas quânticas
unitary.p(E_2 * t, q[0]) # Simula evolução sob H por tempo t, aplicando fase E_2 * t
unitary.x(q[0]) # Aplica X (NOT, inverte |0> e |1>), simulando autovetor |1> de H
unitary.p(E_1 * t, q[0]) # Simula evolução sob H por tempo t, aplicando fase E_1 * t, mas como |1> é autovetor de H, não altera fase, mantendo |1> como autovetor
unitary.x(q[0]) # Aplica X novamente para restaurar |1> (autovetor) e garantir que U|1> = exp(iE_2t)|1>, simulando autovetor com autovalor E_2
print(unitary.draw())
control_u = unitary.to_gate().control(1) # Cria versão controlada de U, onde controle é qubit ancilla a[0] e alvo é q[0]
num_bits_estimate = 8
phase = 0
for k_precision in reversed(range(num_bits_estimate)): # Loop para cada iteração, começando do bit mais significativo
qc = QuantumCircuit(q, a, c)
qc.x(q[0]) # Aplicar porta X (NOT, inverter estado |0> para |1>) no qubit de trabalho
qc.h(a[0]) # Aplicar porta H (Hadamard, superposição) no qubit ancilla (a[0]) para criar superposição de estados |0> e |1>
for _ in range(2 ** k_precision):
qc.append(control_u, [a[0], q[0]]) # Aplicar porta controlada U^(2^k) via circuito control_u, onde a[0] é controle e q[0] é alvo
phase_shift = 2 * np.pi * phase * (2 ** k_precision) # Calcula deslocamento de fase necessário para corrigir fase acumulada com base na estimativa atual
qc.p(-phase_shift, a[0]) # Aplicar porta de fase (P) para corrigir fase acumulada, onde -phase_shift é ângulo de correção e a[0] é qubit alvo
qc.h(a[0])
qc.measure(a[0], c[0])
tqc = transpile(qc, backend)
job = backend.run(tqc, shots=2000) # Executar circuito no backend para obter estatísticas de medição
counts = job.result().get_counts() # Obter contagem de resultados de medição, onde '0' e '1' representam estados medidos do qubit ancilla
bit = int(max(counts, key=counts.get)) # Determinar bit mais provável (0 ou 1) com base nas contagens, onde max(counts, key=counts.get) retorna chave ('0' ou '1') com maior contagem
phase += bit / (2 ** (k_precision + 1)) # Atualizar estimativa de fase acumulada com base no bit medido, ajustando fase para refletir contribuição do bit atual
print(f"bit {k_precision} =", bit)
eigenvalue_est = 2 * np.pi * phase / t # Converter fase estimada de volta para autovalor usando E = phase * (2π / t)
print(qc.draw())
print("Autovalor real:", E_2)
print("Autovalor estimado:", eigenvalue_est)
Amplificação de amplitude quântica (Grover generalizado + operador de preparação arbitrário) é generalização do algoritmo de Grover, para amplificar probabilidade (amplitude) dos resultados desejados. QAA aplica sequência de operadores em circuito quântico, com oráculo para marcação de estados corretos, produzindo rotação no espaço de amplitudes, aumentando quadraticamente probabilidade de medir solução. Componentes do QAA:
# pip3 install qiskit qiskit-aer qiskit-algorithms qiskit-ibm-runtime
from qiskit import QuantumCircuit, transpile
from qiskit_aer import AerSimulator
from qiskit_algorithms import Grover, AmplificationProblem
from qiskit_ibm_runtime import QiskitRuntimeService, SamplerV2 as Sampler
from qiskit.visualization import plot_histogram
QiskitRuntimeService.save_account(token="SEU_TOKEN_AQUI")
oracle = QuantumCircuit(2) # Oráculo marcando |11>
oracle.cz(0,1) # Aplica porta CZ (control-Z, inversão de fase em |11> do qubit 0 em relação ao 1) para marcar estado |11>
A = QuantumCircuit(2)
A.h([0,1]) # Aplica porta Hadamard (superposição) aos 2 qubits
A.ry(0.3, 0) # Aplica porta de rotação em torno do eixo Y (0.3 radianos) no qubit 0 para criar superposição ponderada, aumentando probabilidade de medir |11> após amplificação
problem = AmplificationProblem(
oracle=oracle,
state_preparation=A
) # Problema de amplificação definido com oráculo e preparação de estado personalizada
grover = Grover(iterations=1) # Algoritmo de Grover configurado para 1 iteração, suficiente para amplificar probabilidade de medir o estado marcado |11>
qc = grover.construct_circuit(problem) # Construir circuito de Grover para problema definido
qc.measure_all()
display(qc.draw('mpl'))
backend = AerSimulator()
tqc = transpile(qc, backend)
sampler = Sampler(backend)
job = sampler.run([tqc], shots=2000)
result = job.result()
counts = result[0].data.meas.get_counts()
print(counts) # Imprime resultado das contagens de medição, mostrando a frequência de cada estado medido, onde estado marcado |11> possuirá maior contagem devido à amplificação de Grover
plot_histogram(counts)
Algoritmo para fatoração de inteiros (Peter Shor, 1994), baseado em transformação quântica de Fourier (QFT), permitindo fatorar nºs inteiros em tempo polinomial, consideravelmente mais rápido que algoritmos clássicos. Consiste em 3 etapas principais:
Etapas do algoritmo de Shor:
Conceitos que envolvem Shor:
Exemplo Qiskit de implementação do algoritmo de Shor para fatorar N=15, com a=2:
# pip3 install qiskit qiskit-aer qiskit-ibm-runtime qiskit[visualization] numpy
import numpy as np
from math import floor, gcd
from fractions import Fraction
from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister, transpile
from qiskit.circuit.library import QFT
from qiskit_aer import AerSimulator
from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit.visualization import plot_histogram
QiskitRuntimeService.save_account(token="SEU_TOKEN_AQUI")
# Função para calcular a^(2^k) mod N
def a2kmodN(a, k, N):
for _ in range(k):
a = int(np.mod(a**2, N))
return a
# Funções para criar operadores de multiplicação modular (M2 e M4 em mod15)
def M2mod15():
circ = QuantumCircuit(4)
circ.swap(2,3) # Aplica porta SWAP (troca estados entre qubits, 1º passa ao 2º, e 2º ao 1º)
circ.swap(1,2)
circ.swap(0,1)
gate = circ.to_gate()
gate.name = "M2_mod15"
return gate
def M4mod15():
circ = QuantumCircuit(4)
circ.swap(1,3)
circ.swap(0,2)
gate = circ.to_gate()
gate.name = "M4_mod15"
return gate
N = 15
a = 2
num_target = floor(np.log2(N-1)) + 1 # Quantidade de qubits para representar estados alvo (4 para N=15)
num_control = 2 * num_target
control = QuantumRegister(num_control, "ctrl") # Registrador de controle para superposição e medição (8 qubits para precisão suficiente na estimativa do período r)
target = QuantumRegister(num_target, "targ") # Registrador alvo para representar estados de multiplicação modular (4 qubits para N=15)
output = ClassicalRegister(num_control, "out") # Registrador clássico para armazenar resultados da medição dos qubits de controle (8 bits para precisão suficiente na estimativa do período r)
qc = QuantumCircuit(control, target, output)
qc.x(target[0]) # Aplica porta X (NOT) para preparar estado |1> no registrador alvo
for qubit in control:
qc.h(qubit) # Aplica porta Hadamard (superposição) nos qubits de controle
for k, qubit in enumerate(control):
b = a2kmodN(a, k, N) # Calcula a^(2^k) mod N para determinar qual operador aplicar
if b == 2:
qc.compose(M2mod15().control(), [qubit] + target[:], inplace=True) # Aplica operador controlado M2mod15 se b=2
elif b == 4:
qc.compose(M4mod15().control(), [qubit] + target[:], inplace=True) # Aplica operador controlado M4mod15 se b=4
qc.compose(QFT(num_control, inverse=True), control, inplace=True) # Aplica QFT inversa nos qubits de controle
qc.measure(control, output)
display(qc.draw("mpl")) # print(qc.draw())
simulator = AerSimulator()
compiled_circuit = transpile(qc, simulator)
job = simulator.run(compiled_circuit, shots=1024)
result = job.result()
counts = result.get_counts()
print(counts)
plot_histogram(counts)
# Estimativa do período r a partir dos resultados da medição (pós-processamento clássico)
for bitstring in counts:
decimal = int(bitstring, 2)
phase = decimal / (2**num_control)
frac = Fraction(phase).limit_denominator(N)
r = frac.denominator
if r % 2 == 0:
p = gcd(pow(a, r//2) - 1, N) # Calcula p usando valor de r encontrado
q = gcd(pow(a, r//2) + 1, N) # Calcula q usando valor de r encontrado
if p * q == N and p != 1 and q != 1:
print(f"Fatores encontrados: {p} e {q}")
break
# Conclusão: Shor é capaz de fatorar N=15 em fatores p=3 e q=5, demonstrando capacidade de encontrar fatores não-triviais usando QFT e QPE, ilustrando potencial dos algoritmos quânticos para resolver problemas difíceis de fatoração que são intractáveis para algoritmos clássicos eficientes
Grover Search, algoritmo quântico de busca quadrática (Lov Grover, 1996) em conjunto de dados não estruturados N. Opera em tarefa de pesquisa f(x), onde x são itens de pesquisa. Se há resposta em x, então f(x)=1. Senão, f(x)=0. Etapas do algoritmo de Grover:
# pip3 install qiskit qiskit-ibm-runtime matplotlib qiskit[visualization]
import math
from qiskit import QuantumCircuit
from qiskit.circuit.library import grover_operator, MCMTGate, ZGate
from qiskit.visualization import plot_distribution
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit_ibm_runtime import QiskitRuntimeService, SamplerV2 as Sampler
QiskitRuntimeService.save_account(token="SEU_TOKEN_AQUI")
mcmt_ex = QuantumCircuit(3)
mcmt_ex.compose(MCMTGate(ZGate(), 3 - 1, 1), inplace=True) # Aplicar porta MCMT (Multi-Controlled Toffoli, objetivo de inverter estado, sob condição) com ZGate como alvo, 2 controles e 1 alvo
mcmt_ex.draw(output="mpl", style="iqp")
# Função oráculo O para Grover, que marca estados-alvo invertendo fase:
def grover_oracle(marked_states):
if not isinstance(marked_states, list):
marked_states = [marked_states]
num_qubits = len(marked_states[0]) # Determinar nº de qubits a partir do comprimento dos estados-alvo
qc = QuantumCircuit(num_qubits)
for target in marked_states: # Iterar sobre cada estado-alvo a ser marcado
rev_target = target[::-1]
zero_inds = [
ind for ind in range(num_qubits) if rev_target.startswith("0", ind)
] # Identificar índices dos bits '0' no estado-alvo (revertido) para aplicar portas X posteriormente
qc.x(zero_inds)
qc.compose(MCMTGate(ZGate(), num_qubits - 1, 1), inplace=True) # Aplicar porta MCMT para inverter fase do estado-alvo, usando ZGate como alvo e todos outros qubits como controles
qc.x(zero_inds)
return qc
marked_states = ["1110"]
oracle = grover_oracle(marked_states) # Criar oráculo para marcar estado "1110" (inverter fase desse estado)
oracle.draw(output="mpl", style="iqp")
grover_op = grover_operator(oracle) # Criar operador de Grover a partir do oráculo, que inclui etapas de difusão e amplificação de amplitude
grover_op.decompose(reps=0).draw(output="mpl", style="iqp")
optimal_num_iterations = math.floor(
math.pi / (4 * math.asin(math.sqrt(len(marked_states) / 2**grover_op.num_qubits)))
) # Nº otimizado de iterações para aplicar Grover operator
print(optimal_num_iterations)
qc = QuantumCircuit(grover_op.num_qubits)
qc.h(range(grover_op.num_qubits)) # Aplicar porta Hadamard (H) para criar superposição uniforme de todos estados possíveis
qc.compose(grover_op.power(optimal_num_iterations), inplace=True) # Aplicar operador de Grover para amplificar amplitude dos estados marcados
qc.measure_all()
qc.draw(output="mpl", style="iqp")
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)
backend.name
target = backend.target
pm = generate_preset_pass_manager(target=target, optimization_level=3)
qc_iza = pm.run(qc)
print("The total depth is ", qc_iza.depth())
print(
"The depth of two-qubit gates is ",
qc_iza.depth(lambda instruction: instruction.operation.num_qubits == 2),
)
sampler = Sampler(mode=backend)
sampler.options.default_shots = 10_000
result = sampler.run([qc_iza]).result()
dist = result[0].data.meas.get_counts()
print(dist)
plot_distribution(dist)
Two-Phase Grover é variação do algoritmo de Grover que aplica Divide and Conquer (dividir e conquistar) em grande espaço de busca, dividindo-o em fases menores para otimizar quantidade de iterações necessárias, onde Oracle pode ser estruturado hierarquicamente. Etapas:
# pip3 install qiskit qiskit-ibm-runtime qiskit-aer qiskit[visualization]
from qiskit import QuantumCircuit, transpile
from qiskit_aer import AerSimulator
from qiskit.circuit.library import GroverOperator
import math
from qiskit_ibm_runtime import QiskitRuntimeService
QiskitRuntimeService.save_account(token="SEU_TOKEN_AQUI")
# Objetivo: encontrar |1011>, via 4 qubits em 2 blocos (n1=2, n2=2)
n_total = 4
# Fase 1: Procurar 10 no bloco A (qubits 0 e 1)
oracle_phase1 = QuantumCircuit(2)
oracle_phase1.x(0)
oracle_phase1.cz(0, 1)
oracle_phase1.x(0)
grover_phase1 = GroverOperator(oracle_phase1)
t1 = 1
qc1 = QuantumCircuit(2)
qc1.h([0,1])
qc1.compose(grover_phase1.power(t1), inplace=True)
qc1.measure_all()
display(qc1.draw("mpl"))
sim = AerSimulator()
compiled1 = transpile(qc1, sim)
result1 = sim.run(compiled1, shots=1024).result()
counts1 = result1.get_counts()
print("Fase 1:", counts1)
# Fase 2: Procurar 11 no bloco B (qubits 2 e 3), condicionado ao resultado da fase 1
oracle_phase2 = QuantumCircuit(2)
oracle_phase2.cz(0, 1)
grover_phase2 = GroverOperator(oracle_phase2)
t2 = 1
qc2 = QuantumCircuit(2)
qc2.h([0,1])
qc2.compose(grover_phase2.power(t2), inplace=True)
qc2.measure_all()
display(qc2.draw("mpl"))
compiled2 = transpile(qc2, sim)
result2 = sim.run(compiled2, shots=1024).result()
counts2 = result2.get_counts()
print("Fase 2:", counts2)
print("Resultado final esperado: |1011>")
print("Resultado final obtido:", counts1, counts2)
Elaborado por Mateus Schwede
ubsocial.github.io