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)
Passeios quânticos são análogos quânticos dos random walks (passeios aleatórios clássicos) para domínio da mecânica quântica. São utilizados para modelar e resolver problemas de busca e otimização em contextos quânticos, sendo "passo a passo" de trajetória para resolução de problemas, via técnicas quânticas como amplitudes de probabilidades. Tipos de quantum walks:
# pip3 install qiskit qiskit-ibm-runtime qiskit-aer qiskit[visualization]
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")
# 1 qubit para moeda, 2 qubits para posição
coin_qr = QuantumRegister(1, name="coin")
pos_qr = QuantumRegister(2, name="pos")
cr = ClassicalRegister(2, name="c")
qc = QuantumCircuit(coin_qr, pos_qr, cr)
# Inicialização: posição inicial |00⟩ e moeda em superposição
coin = coin_qr[0]
p0 = pos_qr[0]
p1 = pos_qr[1]
# Se coin = |0⟩, move para direita (posição +1 mod 4, incrementa posição)
def shift_right(qc):
qc.cx(coin, p0)
qc.ccx(coin, p0, p1)
# Se coin = |1⟩, move para esquerda (posição -1 mod 4, decrementa posição)
def shift_left(qc):
qc.x(coin)
qc.ccx(coin, p0, p1)
qc.cx(coin, p0)
qc.x(coin)
def step(qc):
qc.h(coin)
shift_right(qc)
shift_left(qc)
n_steps = 2
for _ in range(n_steps): # Realiza 2 passos do quantum walk
step(qc)
qc.measure(pos_qr, cr)
display(qc.draw(output="mpl"))
sim = AerSimulator()
tqc = transpile(qc, sim)
result = sim.run(tqc, shots=1000).result()
counts = result.get_counts()
print("Distribuição final de posições após Quantum Walk:", counts)
# pip3 install qiskit qiskit-ibm-runtime qiskit-aer qiskit[visualization]
import numpy as np
from scipy.linalg import expm
from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister, transpile
from qiskit.circuit.library import UnitaryGate
from qiskit_aer import AerSimulator
from qiskit_ibm_runtime import QiskitRuntimeService
QiskitRuntimeService.save_account(token="SEU_TOKEN_AQUI")
# Posições representadas por 2 qubits (4 posições: 00, 01, 10, 11)
pos_qr = QuantumRegister(2, name="pos")
cr = ClassicalRegister(2, name="c")
qc = QuantumCircuit(pos_qr, cr)
# Matriz de adjacência para um ciclo de 4 posições (0-1-2-3-0)
H = np.array([
[0,1,0,1],
[1,0,1,0],
[0,1,0,1],
[1,0,1,0]
], dtype=complex)
t = 1.0 # Tempo de evolução contínua
U = expm(-1j * H * t) # Operador de evolução unitário U = e^{-iHt}
U_gate = UnitaryGate(U, label="CTQW") # Converter em porta unitária
qc.append(U_gate, pos_qr) # Aplicar evolução
qc.measure(pos_qr, cr)
display(qc.draw(output="mpl"))
sim = AerSimulator()
tqc = transpile(qc, sim)
result = sim.run(tqc, shots=1000).result()
counts = result.get_counts()
print("Distribuição final de posições após CTQW:", counts) # Nota-se transferência de amplitude entre posições
Trotterização, em simulação quântica, é técnica, baseada em fórmula Trotter-Suzuki, de aplicação sucessiva de 1 ou mais portas quânticas, escolhidas para aproximar evolução temporal do sistema em intervalo de tempo. Evolução de sistema é descrito por Hamiltoniano H, com operador unitário U, sendo H soma de termos Pauli, com Pj representando produto tensorial dos mesmos atuando em n qubits. Quando Hamiltonianos H combinam-se, em série Taylor. Quando não combinam-se, termos não podem ser reorganizados na série Taylor, onde deve-se diminuir intervalo de tempo t, para que termo de 1ª ordem na expansão de Taylor seja dominante, de forma evolutiva no estado, via pequenas atualizações. Então, 't/r' é etapa de evolução, onde resultado é porta de aplicação r vezes (passos de Trotter). Menor t gera aproximação mais precisa. Erro de Trotter é O(dt^2) para Lie-Trotter (Trotterização de ordem 1) e O(dt^3) para Suzuki-Trotter (Trotterização de ordem 2).
Via modelo de Ising (H = Σ Jᵢⱼ σᵢ σⱼ), evolução temporal em redes lineares N=2 e N=6, em matriz de spinn σᵢ que interagem apenas com vizinhos mais próximos, possuindo direção subida (magnetização +1) e descida (magnetização -1), onde J é energia da iteração, e h magnitude de campo externo. Exemplo de aplicação de Trotterização Lie-Trotter (ordem 1), em modelo Ising:
# pip3 install qiskit qiskit-ibm-runtime qiskit[visualization]
import numpy as np
import matplotlib.pylab as plt
import warnings
from qiskit import QuantumCircuit
from qiskit.circuit.library import PauliEvolutionGate
from qiskit.primitives import StatevectorEstimator
from qiskit.quantum_info import SparsePauliOp
from qiskit.synthesis import SuzukiTrotter, LieTrotter
from qiskit_ibm_runtime import QiskitRuntimeService
QiskitRuntimeService.save_account(token="SEU_TOKEN_AQUI")
warnings.filterwarnings("ignore")
# Função Hamiltoniano do modelo de Ising, com acoplamento J, campo externo h, e ângulo alpha.
def get_hamiltonian(nqubits, J, h, alpha):
ZZ_tuples = [("ZZ", [i, i + 1], -J) for i in range(0, nqubits - 1)]
Z_tuples = [("Z", [i], -h * np.sin(alpha)) for i in range(0, nqubits)]
X_tuples = [("X", [i], -h * np.cos(alpha)) for i in range(0, nqubits)]
hamiltonian = SparsePauliOp.from_sparse_list(
[*ZZ_tuples, *Z_tuples, *X_tuples], num_qubits=nqubits
)
return hamiltonian.simplify()
n_qubits = 6
hamiltonian = get_hamiltonian(nqubits=n_qubits, J=0.2, h=1.2, alpha=np.pi / 8.0)
hamiltonian
num_timesteps = 60
evolution_time = 30.0
dt = evolution_time / num_timesteps
product_formula_lt = LieTrotter() # Trotterização de ordem 1, com erro de ordem O(dt^2)
product_formula_st2 = SuzukiTrotter(order=2) # Trotterização de ordem 2, com erro de ordem O(dt^3)
product_formula_st4 = SuzukiTrotter(order=4) # Trotterização de ordem 4, com erro de ordem O(dt^5)
initial_circuit = QuantumCircuit(n_qubits)
initial_circuit.prepare_state("001100")
initial_circuit.decompose(reps=1).draw("mpl")
single_step_evolution_gates_lt = PauliEvolutionGate(
hamiltonian, dt, synthesis=product_formula_lt
)
single_step_evolution_lt = QuantumCircuit(n_qubits)
single_step_evolution_lt.append(
single_step_evolution_gates_lt, single_step_evolution_lt.qubits
)
print("Step de Trotter com Lie-Trotter:")
print(f"Depth: {single_step_evolution_lt.decompose(reps=3).depth()}")
print(f"Gate count: {len(single_step_evolution_lt.decompose(reps=3))}")
print(f"Nonlocal gate count: {single_step_evolution_lt.decompose(reps=3).num_nonlocal_gates()}")
print(f"Gate breakdown: {', '.join([f'{k.upper()}: {v}' for k, v in single_step_evolution_lt.decompose(reps=3).count_ops().items()])}")
single_step_evolution_lt.decompose(reps=3).draw("mpl", fold=-1)
# Construção dos operadores de magnetização e correlação para modelo de Ising
magnetization = (
SparsePauliOp.from_sparse_list(
[("Z", [i], 1.0) for i in range(0, n_qubits)], num_qubits=n_qubits
)
/ n_qubits
)
correlation = SparsePauliOp.from_sparse_list(
[("ZZ", [i, i + 1], 1.0) for i in range(0, n_qubits - 1)], num_qubits=n_qubits
) / (n_qubits - 1)
print("Magentização : ", magnetization)
print("Correlação : ", correlation)
# Simulação da evolução temporal do modelo de Ising
evolved_state = QuantumCircuit(initial_circuit.num_qubits)
evolved_state.append(initial_circuit, evolved_state.qubits)
estimator = StatevectorEstimator()
shots = 10000
precision = np.sqrt(1 / shots)
energy_list = []
mag_list = []
corr_list = []
job = estimator.run(
[(evolved_state, [hamiltonian, magnetization, correlation])], precision=precision
)
evs = job.result()[0].data.evs
energy_list.append(evs[0])
mag_list.append(evs[1])
corr_list.append(evs[2])
# Loop de evolução temporal (expansão de cada step de evolução e estimação dos valores esperados)
for n in range(num_timesteps):
evolved_state.append(single_step_evolution_gates_lt, evolved_state.qubits)
job = estimator.run(
[(evolved_state, [hamiltonian, magnetization, correlation])],
precision=precision,
)
evs = job.result()[0].data.evs
energy_list.append(evs[0])
mag_list.append(evs[1])
corr_list.append(evs[2])
energy_array = np.array(energy_list)
mag_array = np.array(mag_list)
corr_array = np.array(corr_list)
# Evolução temporal dos observáveis do modelo de Ising
fig, axes = plt.subplots(3, sharex=True)
times = np.linspace(0, evolution_time, num_timesteps + 1)
axes[0].plot(
times,
energy_array,
label="First order",
marker="x",
c="darkmagenta",
ls="-",
lw=0.8,
)
axes[1].plot(
times, mag_array, label="First order", marker="x", c="darkmagenta", ls="-", lw=0.8
)
axes[2].plot(
times, corr_array, label="First order", marker="x", c="darkmagenta", ls="-", lw=0.8
)
axes[0].set_ylabel("Energy")
axes[1].set_ylabel("Magnetization")
axes[2].set_ylabel("Mean spin correlation")
axes[2].set_xlabel("Time")
fig.suptitle("Evolução dos observáveis")
# Evolução temporal dos observáveis (Ising) simulada via Trotterização de Lie-Trotter de ordem 1
# Para resultados mais precisos, comparar com Trotterizações de Suzuki-Trotter de ordem 2 e 4
Transformação de Jordan-Wigner é técnica para mapear operadores de fermiônicos (férmions - que obedecem estatística de Fermi-Dirac) em operadores de spin (qubits), permitindo simulação de sistemas quânticos fermiônicos em computadores quânticos. JWT é usada para simular moléculas e materiais, onde partículas são fermiônicas. JWT mapeia operadores de criação e aniquilação de fermiônicos em produtos tensoriais de operadores de Pauli, onde cada modo fermiônico é representado por qubit. JWT é eficiente para sistemas unidimensionais, mas pode introduzir não-localidade em sistemas multidimensionais, exigindo técnicas adicionais para otimização. Exemplo de aplicação do JWT para simular modelo de Hubbard (modelo de elétrons em rede), usando Trotterization (evolução temporal);:
# pip3 install qiskit qiskit-aer qiskit-nature qiskit-ibm-runtime
from qiskit import QuantumCircuit, transpile
from qiskit_aer import AerSimulator
from qiskit.quantum_info import SparsePauliOp
from qiskit.circuit.library import PauliEvolutionGate
from qiskit_nature.second_q.operators import FermionicOp
from qiskit_nature.second_q.mappers import JordanWignerMapper
from qiskit_ibm_runtime import QiskitRuntimeService
QiskitRuntimeService.save_account(token="SEU_TOKEN_AQUI")
# Parâmetros (modelo de Hubbard)
t = 1.0 # hopping
U = 4.0 # interação local
num_sites = 2
num_spin_orbitals = 2 * num_sites # up/down para cada sítio
fermion_terms = {} # Hamiltoniano fermiônico
# Termos de hopping (0=up e 1=down)
for spin in [0, 1]:
i = 0 * 2 + spin
j = 1 * 2 + spin
fermion_terms[f"+_{i} -_{j}"] = -t
fermion_terms[f"+_{j} -_{i}"] = -t
# Termo de interação local
for site in range(num_sites):
up = site * 2
down = site * 2 + 1
fermion_terms[f"+_{up} -_{up} +_{down} -_{down}"] = U
# Operador fermiônico
fermionic_hamiltonian = FermionicOp(
fermion_terms,
num_spin_orbitals=num_spin_orbitals
)
mapper = JordanWignerMapper()
qubit_hamiltonian: SparsePauliOp = mapper.map(fermionic_hamiltonian)
print("Hamiltoniano em Pauli:", qubit_hamiltonian)
# Evolução temporal (Trotterização)
evolution_time = 2.0
num_steps = 50
dt = evolution_time / num_steps
qc = QuantumCircuit(num_spin_orbitals)
qc.x(0) # Estado inicial: 1 elétron spin-up no sítio 0
# Evolução unitária e^{-iHt}
for _ in range(num_steps):
evo_gate = PauliEvolutionGate(qubit_hamiltonian, time=dt)
qc.append(evo_gate, qc.qubits)
qc.measure_all()
sim = AerSimulator()
tqc = transpile(qc, sim)
job = sim.run(tqc, shots=1000)
result = job.result()
counts = result.get_counts()
print("Distribuição final:", counts)
Fisicamente, elétron "tunelou" para outro sítio, onde 0100 é Q3(0), Q2(1), Q1(0), Q0(0), elétron spin-up foi para sítio 1, e sistema evoluiu via hopping. 0001 é estado em que elétron está no último qubit (site 1 ↓). Efeito quântico gerado devido Hamiltoniano e evolução Trotterizada.
Técnica de mapeamento de operadores fermiônicos em operadores de spin (qubits), alternativa ao Jordan-Wigner, que reduz não-localidade em sistemas multidimensionais. BK usa estrutura hierárquica para mapear modos fermiônicos, onde cada qubit representa paridade e ocupação de modos. Enquanto JW armazena ocupação completa à esquerda (string longa de Z), BK armazena informação de paridade e ocupação distribuída em árvore binária. Exemplo de aplicação do BK para simular modelo de Hubbard, usando Trotterization:
# pip3 install qiskit qiskit-aer qiskit-nature qiskit-ibm-runtime
from qiskit import QuantumCircuit, transpile
from qiskit_aer import AerSimulator
from qiskit.quantum_info import SparsePauliOp
from qiskit.circuit.library import PauliEvolutionGate
from qiskit_nature.second_q.operators import FermionicOp
from qiskit_nature.second_q.mappers import BravyiKitaevMapper
from qiskit_ibm_runtime import QiskitRuntimeService
QiskitRuntimeService.save_account(token="SEU_TOKEN_AQUI")
# Parâmetros (modelo de Hubbard)
t = 1.0 # hopping
U = 4.0 # interação local
num_sites = 2
num_spin_orbitals = 2 * num_sites # up/down para cada sítio
fermion_terms = {} # Hamiltoniano fermiônico
# Termos de hopping (0=up e 1=down)
for spin in [0, 1]:
i = 0 * 2 + spin
j = 1 * 2 + spin
fermion_terms[f"+_{i} -_{j}"] = -t
fermion_terms[f"+_{j} -_{i}"] = -t
# Termo de interação local
for site in range(num_sites):
up = site * 2
down = site * 2 + 1
fermion_terms[f"+_{up} -_{up} +_{down} -_{down}"] = U
# Operador fermiônico
fermionic_hamiltonian = FermionicOp(
fermion_terms,
num_spin_orbitals=num_spin_orbitals
)
mapper = BravyiKitaevMapper()
qubit_hamiltonian: SparsePauliOp = mapper.map(fermionic_hamiltonian)
print("Hamiltoniano em Pauli:", qubit_hamiltonian)
# Evolução temporal (Trotterização)
evolution_time = 2.0
num_steps = 50
dt = evolution_time / num_steps
qc = QuantumCircuit(num_spin_orbitals)
qc.x(0) # Estado inicial: 1 elétron spin-up no sítio 0
# Evolução unitária e^{-iHt}
for _ in range(num_steps):
evo_gate = PauliEvolutionGate(qubit_hamiltonian, time=dt)
qc.append(evo_gate, qc.qubits)
qc.measure_all()
sim = AerSimulator()
tqc = transpile(qc, sim)
job = sim.run(tqc, shots=1000)
result = job.result()
counts = result.get_counts()
print("Distribuição final:", counts)
Resultado similar ao JWT, mas com estrutura de mapeamento diferente, onde BK reduz não-localidade em sistemas multidimensionais, sendo mais eficiente para simulação de sistemas quânticos fermiônicos complexos.
Planaltos áridos (planícies áridas) são regiões do espaço de parâmetros em circuito quântico variacional, onde gradiente da função custo aproxima-se de 0 conforme aumento de qubits. Ocasionado por profundidade do circuito, entanglement aleatório, e falta de estrutura nos ansatzes. Causa ineficácia aos algoritmos variacionais quânticos. Pode ser do tipo custo global, local (subconjunto de qubits) ou induzido por ruído quântico. Exemplo de barren plateau, onde observa-se decaimento do gradiente conforme aumento de qubits:
# pip3 install qiskit qiskit-aer qiskit-ibm-runtime matplotlib
import numpy as np
import matplotlib.pyplot as plt
from qiskit import QuantumCircuit
from qiskit.circuit import Parameter
from qiskit.quantum_info import SparsePauliOp
from qiskit_aer import AerSimulator
from qiskit_ibm_runtime import EstimatorV2, QiskitRuntimeService
QiskitRuntimeService.save_account(token="SEU_TOKEN_AQUI")
# Função custo (Z0) para observar decaimento do gradiente com aumento de qubits
def create_cost_operator(n):
pauli_string = "Z" + "I"*(n-1)
return SparsePauliOp(pauli_string)
# Circuito variacional profundo
def create_random_ansatz(n, depth):
qc = QuantumCircuit(n)
theta = Parameter("θ")
for _ in range(depth):
for i in range(n):
qc.ry(theta, i) # RY com parâmetro compartilhado entre camadas
for i in range(n-1):
qc.cx(i, i+1) # Entanglement linear entre qubits adjacentes
return qc, theta
# Parameter-shift para estimar gradiente
def parameter_shift_gradient(estimator, circuit, operator, theta_value):
shift = np.pi / 2
forward = estimator.run([
(circuit, operator, [theta_value + shift])
]).result()[0].data.evs
backward = estimator.run([
(circuit, operator, [theta_value - shift])
]).result()[0].data.evs
return 0.5 * (forward - backward)
backend = AerSimulator(method="statevector")
estimator = EstimatorV2(mode=backend)
qubit_list = [2, 4, 6, 8]
depth = 20
mean_gradients = []
for n in qubit_list:
grads = []
operator = create_cost_operator(n)
circuit, theta = create_random_ansatz(n, depth)
for _ in range(20):
val = np.random.uniform(0, 2*np.pi)
grad = parameter_shift_gradient(estimator, circuit, operator, val)
grads.append(abs(grad))
mean_val = np.mean(grads)
mean_gradients.append(mean_val)
print(f"Gradiente médio com {n} qubits: média |grad| = {mean_val}") # Quanto mais qubits, menor gradiente médio, indicando barren plateau
plt.figure()
plt.plot(qubit_list, mean_gradients, marker='o')
plt.xlabel("Número de Qubits")
plt.ylabel("Média |Gradiente|")
plt.title("Barren Plateau - decaimento do gradiente")
plt.yscale("log")
plt.show()
Algoritmo híbrido quântico-clássico, para estimar menor valor próprio/autovalor (ground state) energético de Hamiltoniano H (energia fundamental de sistema quântico). Componentes do VQE:
Objetivo do VQE é encontrar melhores parâmetros θ que resultam na menor energia possível (próxima do valor verdadeiro). Passo a passo do VQE:
# pip3 install qiskit qiskit-ibm-runtime qiskit-aer qiskit-algorithms
from qiskit import QuantumCircuit
from qiskit.quantum_info import SparsePauliOp
from qiskit_aer import AerSimulator
from qiskit_ibm_runtime import EstimatorV2, QiskitRuntimeService
from qiskit_algorithms.minimum_eigensolvers import VQE
from qiskit_algorithms.optimizers import COBYLA
from qiskit.circuit import Parameter
QiskitRuntimeService.save_account(token="SEU_TOKEN_AQUI")
hamiltonian = SparsePauliOp.from_list([("Z", 1.0)]) # Hamiltoniano simples (H=Z)
# Circuito Ansatz simples com 1 qubit e parametro θ para rotação Y
qc_ansatz = QuantumCircuit(1)
theta = Parameter("θ")
qc_ansatz.ry(theta, 0)
# Estimador via simulador Aer (Statevector)
backend = AerSimulator(method="statevector")
estimator = EstimatorV2(mode=backend)
optimizer = COBYLA(maxiter=100) # Otimização com COBYLA, com máximo de 100 iterações
# Criar objeto VQE parametrizado
vqe = VQE(
estimator=estimator,
ansatz=qc_ansatz,
optimizer=optimizer,
)
result = vqe.compute_minimum_eigenvalue(hamiltonian) # Executar VQE para encontrar valor mínimo do Hamiltoniano (Ground State)
print("Energia encontrada (Ground State VQE):", result.eigenvalue.real)
print("Melhor vetor de parâmetros:", result.optimal_parameters)
Algoritmo de Otimização Aproximada Quântica, tipo iterativo híbrido quântico-clássico, para resolver problemas de otimização combinatória, onde objetivo é minimizar função custo, em espaço de soluções. Componentes:
Passo a passo:
Max-Cut (corte máximo) é problema NP-hard de otimização combinatória, para dividir/particionar vértices de grafo em 2 grupos, maximizando nº de arestas entre eles. QAOA em Max-Cut formula Hamiltoniano de custo, onde qubits i e j (par de nós/nodes) iguais (arestas não cortadas) são penalizadas, e qubits diferentes (arestas cortadas) são recompensados. Hamiltoniano de mistura promove transições entre estados, permitindo exploração do espaço de soluções. Exemplo de aplicação do QAOA para resolver Max-Cut em grafo simples, com 5 nós/nodes:
# pip3 install qiskit qiskit-aer qiskit-ibm-runtime matplotlib scipy networkx
import matplotlib.pyplot as plt
import networkx as nx
from scipy.optimize import minimize
from qiskit import QuantumCircuit, transpile
from qiskit.circuit import Parameter
from qiskit.quantum_info import SparsePauliOp
from qiskit_aer import AerSimulator
from qiskit_ibm_runtime import EstimatorV2, QiskitRuntimeService
QiskitRuntimeService.save_account(token="SEU_TOKEN_AQUI")
# Grafo cíclico de 4 vértices:
edges = [(0,1),(1,2),(2,3),(3,0)]
n_qubits = 4
G = nx.Graph()
G.add_edges_from(edges)
nx.draw(G, with_labels=True)
plt.title("Grafo para MaxCut:")
plt.show()
# Hamiltoniano HC:
paulis = []
coeffs = []
for i,j in edges:
z_string = ["I"] * n_qubits
z_string[i] = "Z"
z_string[j] = "Z"
paulis.append("".join(z_string))
coeffs.append(0.5)
paulis.append("I"*n_qubits)
coeffs.append(-0.5)
cost_operator = SparsePauliOp.from_list(list(zip(paulis, coeffs)))
# Parâmetros variacionais:
gamma = Parameter("γ")
beta = Parameter("β")
# Circuito QAOA:
def create_qaoa():
qc = QuantumCircuit(n_qubits)
qc.h(range(n_qubits))
# Custo unitário:
for i,j in edges:
qc.cx(i,j)
qc.rz(2*gamma, j)
qc.cx(i,j)
# Custo de mistura:
for q in range(n_qubits):
qc.rx(2*beta, q)
return qc
# Estimator:
backend = AerSimulator(method="statevector")
estimator = EstimatorV2(mode=backend)
energy_history = []
# Função custo do MaxCut:
def energy(params):
g, b = params
qc = create_qaoa()
qc = qc.assign_parameters({
gamma:g,
beta:b
})
job = estimator.run(
[(qc, cost_operator)]
)
result = job.result()[0]
energy_val = result.data.evs
energy_history.append(energy_val)
return energy_val
# Otimização clássica:
initial = [0.5,0.5] # valores iniciais para gamma e beta
opt_result = minimize(
energy,
initial,
method="COBYLA"
)
print("Gamma =",opt_result.x[0])
print("Beta =",opt_result.x[1])
print("Energia mínima:", opt_result.fun)
# Gráfico de convergência (energia ao longo das iterações):
plt.plot(energy_history)
plt.xlabel("Iteração")
plt.ylabel("Energia")
plt.title("Convergência do QAOA")
plt.show()
# Circuíto QAOA para MaxCut:
final_circuit = create_qaoa().assign_parameters({
gamma:opt_result.x[0],
beta:opt_result.x[1]
})
display(final_circuit.draw(output="mpl"))
# Medir circuito final:
measured = final_circuit.copy()
measured.measure_all()
backend = AerSimulator()
compiled = transpile(measured, backend)
job = backend.run(compiled, shots=2000)
counts = job.result().get_counts()
print("Distribuição de bitstrings:", counts)
print("Cortes encontrados:")
for bit in counts:
cut = compute_cut(bit, edges)
print(bit, ":", cut)
# Melhor bitstring encontrado:
best_bitstring = max(counts, key=counts.get)
print("Bitstring mais provável:", best_bitstring)
# Calcular Max-Cut conforme bitstring encontrado:
def compute_cut(bitstring, edges):
bitstring = bitstring[::-1]
cut = 0
for i,j in edges:
if bitstring[i] != bitstring[j]:
cut += 1
return cut
maxcut_value = compute_cut(best_bitstring, edges)
print("Valor do MaxCut encontrado:", maxcut_value)
QML inclui algoritmos e dados quânticos para aprendizado de máquina, juntamente com processamento clássico, devido à falta de QRAM e leitura de dados quânticos. QML encontra mapeamento do conjunto de recursos de dimensão superior, separando pontos de dados para classificar novos pontos. A alta dimensionalidade dos estados quânticos emaranhados não é paralelismo exponencial e não é condição suficiente para aumentar potência do ML. Desquantização (dequantization) é substituição de algoritmo quântico por equivalente clássico, sem perda de eficiência, mostrando que nem todo algoritmo quântico é superior. Exemplo de desquantização é sobrecarga de carregamento de dados, onde leitura de dados quânticos é ineficiente, e algoritmos quânticos que dependem disso podem ser desquantizados para equivalentes clássicos eficientes. Exemplo QML Qiskit:
# Arquivo CSV de dataset: https://raw.githubusercontent.com/qiskit-community/prototype-quantum-kernel-training/main/data/dataset_graph7.csv
# pip3 install qiskit qiskit-aer qiskit-ibm-runtime pandas numpy
from qiskit import transpile
from qiskit.circuit import Parameter, ParameterVector, QuantumCircuit
from qiskit.circuit.library import unitary_overlap
from qiskit.primitives import StatevectorSampler
from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_aer import AerSimulator
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
import pandas as pd
import numpy as np
# Etapa 1: mapear entradas clássicas para problema quântico
def get_training_data():
df = pd.read_csv("dataset_graph7.csv", sep=",", header=None) # Ler dados de treino do dataset CSV
training_data = df.values[:20, :]
ind = np.argsort(training_data[:, -1])
X_train = training_data[ind][:, :-1]
return X_train
X_train = get_training_data() # Obter dados de treino do dataset CSV
num_samples = np.shape(X_train)[0]
# Quantidade de qubits é metade do nº de recursos, pois cada recurso é codificado em par de rotações RX e RZ
num_features = np.shape(X_train)[1]
num_qubits = int(num_features / 2)
entangler_map = [[0, 2], [3, 4], [2, 5], [1, 4], [2, 3], [4, 6]]
fm = QuantumCircuit(num_qubits)
training_param = Parameter("θ")
feature_params = ParameterVector("x", num_qubits * 2)
fm.ry(training_param, fm.qubits)
for cz in entangler_map:
fm.cz(cz[0], cz[1])
for i in range(num_qubits):
fm.rz(-2 * feature_params[2 * i + 1], i)
fm.rx(-2 * feature_params[2 * i], i)
# Escolher 2 pontos de dados para comparar via circuito de sobreposição unitária (overlap)
x1 = 14
x2 = 19
unitary1 = fm.assign_parameters(list(X_train[x1]) + [np.pi / 2])
unitary2 = fm.assign_parameters(list(X_train[x2]) + [np.pi / 2])
# Criar circuito de sobreposição unitária
overlap_circ = unitary_overlap(unitary1, unitary2)
overlap_circ.measure_all()
overlap_circ.draw(scale=0.6, style="iqp")
# Etapa 2: Otimizar problema para execução quântica
backend = AerSimulator()
pm = generate_preset_pass_manager(optimization_level=3, backend=backend)
job = pm.run(overlap_circ)
# Etapa 3: Executar usando Qiskit em ambiente de simulação
num_shots = 10_000
sampler = StatevectorSampler()
counts = (
sampler.run([overlap_circ], shots=num_shots).result()[0].data.meas.get_int_counts()
)
# Etapa 4: Análise e pós-processamento
counts.get(0, 0.0) / num_shots # Probabilidade de medir 0, sobreposição entre estados quânticos codificados pelos pontos x1 e x2
Codificação/transformação de dados clássicos em formato quântico, para uso em QML. A normalização quântica consiste em escalar dados para que norma 2 (comprimento redimensionado) do vetor seja 1 (resultante da soma das probabilidades de medição), permitindo representação como estado quântico. Métodos de codificação de dados quânticos:
# pip3 install qiskit qiskit-aer qiskit-ibm-runtime matplotlib scipy networkx
from qiskit import QuantumCircuit
x = 5 # binário 0101
y = 7 # binário 0111
z = 0 # binário 0000
# Converter cada recurso para vetor com 4 bits
x_bits = [int(b) for b in format(x, "04b")]
y_bits = [int(b) for b in format(y, "04b")]
z_bits = [int(b) for b in format(z, "04b")]
# Combinar todos bits em vetor único
all_bits = x_bits + y_bits + z_bits # [0,1,0,1,0,1,1,1,0,0,0,0]
qc = QuantumCircuit(12)
# Aplicar portas X (NOT) nos bits 1
for idx, bit in enumerate(all_bits):
if bit == 1:
qc.x(idx)
qc.draw("mpl")
import math
desired_state = [
1 / math.sqrt(105) * 4,
1 / math.sqrt(105) * 8,
1 / math.sqrt(105) * 5,
1 / math.sqrt(105) * 0,
]
qc = QuantumCircuit(2)
qc.initialize(desired_state, [0, 1])
qc.decompose(reps=5).draw(output="mpl")
from qiskit.quantum_info import Statevector
from qiskit.visualization.bloch import Bloch
from qiskit.visualization.state_visualization import _bloch_multivector_data
from math import pi
import numpy as np
qc = QuantumCircuit(1)
state1 = Statevector.from_instruction(qc)
qc.ry(pi / 2, 0) # Aplicar porta pauli RY (rotação no eixo Y)
state2 = Statevector.from_instruction(qc)
states = state1, state2
# Função para plotar N estados em 1 Bloch Sphere visual
def plot_Nstates(states, axis, plot_trace_points=True):
bloch_vecs = [_bloch_multivector_data(s)[0] for s in states]
if axis is None:
bloch_plot = Bloch()
else:
bloch_plot = Bloch(axes=axis)
bloch_plot.add_vectors(bloch_vecs)
if len(states) > 1:
def rgba_map(x, num):
g = (0.95 - 0.05) / (num - 1)
i = 0.95 - g * num
y = g * x + i
return (0.0, y, 0.0, 0.7)
num = len(states)
bloch_plot.vector_color = [rgba_map(x, num) for x in range(1, num + 1)]
bloch_plot.vector_width = 3
bloch_plot.vector_style = "simple"
if plot_trace_points:
def trace_points(bloch_vec1, bloch_vec2):
# bloch_vec = (x,y,z)
n_points = 15
thetas = np.arccos([bloch_vec1[2], bloch_vec2[2]])
phis = np.arctan2(
[bloch_vec1[1], bloch_vec2[1]], [bloch_vec1[0], bloch_vec2[0]]
)
if phis[1] < 0:
phis[1] = phis[1] + 2 * pi
angles0 = np.linspace(phis[0], phis[1], n_points)
angles1 = np.linspace(thetas[0], thetas[1], n_points)
xp = np.cos(angles0) * np.sin(angles1)
yp = np.sin(angles0) * np.sin(angles1)
zp = np.cos(angles1)
pnts = [xp, yp, zp]
bloch_plot.add_points(pnts)
bloch_plot.point_color = "k"
bloch_plot.point_size = [4] * len(bloch_plot.points)
bloch_plot.point_marker = ["o"]
for i in range(len(bloch_vecs) - 1):
trace_points(bloch_vecs[i], bloch_vecs[i + 1])
bloch_plot.sphere_alpha = 0.05
bloch_plot.frame_alpha = 0.15
bloch_plot.figsize = [4, 4]
bloch_plot.render()
plot_Nstates(states, axis=None, plot_trace_points=True)
qc = QuantumCircuit(1)
qc.h(0) # Aplicar porta Hadamard (superposição) para colocar estado no equador da Bloch Sphere
state1 = Statevector.from_instruction(qc)
qc.p(pi / 2, 0) # Porta pauli RZ (rotação no eixo Z) no ângulo de rotação pi/2
state2 = Statevector.from_instruction(qc)
states = state1, state2
qc.draw("mpl", scale=1)
plot_Nstates(states, axis=None, plot_trace_points=True)
qc = QuantumCircuit(1)
state1 = Statevector.from_instruction(qc)
qc.ry(3 * pi / 8, 0)
state2 = Statevector.from_instruction(qc)
qc.rz(7 * pi / 4, 0)
state3 = Statevector.from_instruction(qc)
states = state1, state2, state3
plot_Nstates(states, axis=None, plot_trace_points=True)
Mapas de recursos integrados (feature maps) são circuitos quânticos parametrizados, usados para mapear dados clássicos em espaço de Hilbert de alta dimensão, onde cada ponto de dado é representado como estado quântico. Objetivo é criar representação quântica que permita separação linear dos dados, facilitando tarefas de classificação. Métodos de encoding com mapas de recursos integrados:
# pip3 install qiskit matplotlib pylatexenc
from qiskit import QuantumCircuit
from qiskit.circuit.library import efficient_su2
from qiskit.circuit.library import z_feature_map
from qiskit.circuit.library import zz_feature_map
from qiskit.circuit.library import pauli_feature_map
from qiskit.visualization import circuit_drawer
import matplotlib.pyplot as plt
from math import pi
circuit = efficient_su2(num_qubits=2, reps=1, insert_barriers=True) # Circuito SU2 com 2 qubits e 1 repetição
circuit.decompose().draw()
x = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.1, 1.2] # Vetor de parâmetros, onde cada elemento corresponde rotação em porta RX ou RZ
circuit = efficient_su2(num_qubits=3, reps=1, insert_barriers=True)
encode = circuit.assign_parameters(x)
encode.decompose().draw()
qc0 = QuantumCircuit(1)
qc1 = QuantumCircuit(1)
qc0.h(0)
qc0.p(pi / 2, 0)
qc1.h(0)
qc1.p(pi / 3, 0)
# Combinar circuitos qc0 e qc1 em 1 circuito
qc = QuantumCircuit(2)
qc.compose(qc0, [0], inplace=True)
qc.compose(qc1, [1], inplace=True)
qc.draw()
zfeature_map = z_feature_map(feature_dimension=2, reps=3) # Criar zfeature_map com 2 dimensões e 3 repetições
zfeature_map = zfeature_map.assign_parameters([(1 / 2) * pi / 2, (1 / 2) * pi / 3]) # Atribuir parâmetros do zfeature_map com valores dos circuitos qc0 e qc1
zfeature_map.decompose().draw()
qc = QuantumCircuit(2)
qc.rzz(pi, 0, 1)
qc.draw()
qc.decompose().draw()
feature_dim = 2 # Quantidade de características
zzfeature_map = zz_feature_map(feature_dimension=feature_dim, entanglement="linear", reps=1) # Criar circuito de mapeamento de características
zzfeature_map.decompose(reps=1).draw()
feature_dim = 4
zzfeature_map = zz_feature_map(feature_dimension=feature_dim, entanglement="linear", reps=1)
zzfeature_map.decompose().draw()
feature_dim = 3
pfmap = pauli_feature_map(
feature_dimension=feature_dim, entanglement="linear", reps=1, paulis=["Y", "XX"]
) # Criar pauli feature map com dimensão de recurso 3, entrelaçamento linear, 1 repetição, usando portas Y e XX
pfmap.decompose().draw()
feature_dim = 2
fig, axs = plt.subplots(9, 2)
i_plot = 0
for paulis in [
["I"],
["X"],
["Y"],
["Z"],
["XX"],
["XY"],
["XZ"],
["YY"],
["YZ"],
["ZZ"],
["X", "ZZ"],
["Y", "ZZ"],
["Z", "ZZ"],
["X", "YZ"],
["Y", "YZ"],
["Z", "YZ"],
["YY", "ZZ"],
["XY", "ZZ"],
]:
pfmap = pauli_feature_map(feature_dimension=feature_dim, paulis=paulis, reps=1)
circuit_drawer(
pfmap.decompose(),
output="mpl",
style={"backgroundcolor": "#EEEEEE"},
ax=axs[int((i_plot - i_plot % 2) / 2), i_plot % 2],
)
axs[int((i_plot - i_plot % 2) / 2), i_plot % 2].title.set_text(paulis)
i_plot += 1
fig.set_figheight(16)
fig.set_figwidth(16)
Núcleo quântico é kernel matrix e entradas na mesma, onde quantum kernel method é método que usa processador quântico para estimar kernel. Função de kernel quântico visa similaridade entre pontos de dados, onde cada ponto é codificado como estado quântico via feature maps, e kernel é calculado como sobreposição (overlap) entre estados quânticos. Os códigos Qiskit abaixo exibem profundidade dos circuitos quânticos de codificação pré-codificados que usam emaranhamento substancial. Passo a passo:
Código Qiskit para Single kernel matrix:
# pip3 install qiskit qiskit-aer qiskit-ibm-runtime scikit-learn pandas numpy matplotlib
from qiskit.circuit.library import z_feature_map, unitary_overlap
from qiskit.circuit import Parameter, ParameterVector, QuantumCircuit
from qiskit.primitives import StatevectorSampler
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit_aer import AerSimulator
from qiskit_ibm_runtime import QiskitRuntimeService, SamplerV2 as Sampler
from qiskit.visualization import plot_distribution
from sklearn.svm import SVC
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
small_data = [
[-0.194, 0.114, -0.006, 0.301, -0.359, -0.088, -0.156, 0.342, -0.016, 0.143, 1],
[-0.1, 0.002, 0.244, 0.127, -0.064, -0.086, 0.072, 0.043, -0.053, 0.02, -1],
] # Dados de treino, com rótulos de categoria
train_data = [small_data[0][:-1], small_data[1][:-1]] # Remover rótulos de categoria para calcular produto interno
fm = z_feature_map(feature_dimension=np.shape(train_data)[1]) # Criar feature map, via ZFM
unitary1 = fm.assign_parameters(train_data[0]) # Gerar circuito quântico para 1º dado de treino
unitary2 = fm.assign_parameters(train_data[1]) # Gerar circuito quântico para 2º dado de treino
overlap_circ = unitary_overlap(unitary1, unitary2) # Unir circuitos quânticos para calcular produto interno
overlap_circ.measure_all()
print("Profundidade do circuito:", overlap_circ.decompose().depth())
overlap_circ.decompose().draw()
backend = AerSimulator()
pm = generate_preset_pass_manager(optimization_level=3, backend=backend)
overlap_ibm = pm.run(overlap_circ)
print("Profundidade do circuito:", overlap_ibm.decompose().depth())
overlap_ibm.decompose().depth(lambda instr: len(instr.qubits) > 1)
num_shots = 10000
sampler = Sampler(mode=backend)
results = sampler.run([overlap_ibm], shots=num_shots).result()
counts_bit = results[0].data.meas.get_counts()
counts = results[0].data.meas.get_int_counts()
print("Contagem de bits:", counts_bit)
print("Contagem de inteiros:", counts)
print("Valor do produto interno:", counts.get(0, 0.0) / num_shots)
plot_distribution(counts_bit)
def visualize_counts(probs, num_qubits):
zero_prob = probs.get(0, 0.0)
top_10 = dict(sorted(probs.items(), key=lambda item: item[1], reverse=True)[:10])
top_10.update({0: zero_prob})
by_key = dict(sorted(top_10.items(), key=lambda item: item[0]))
xvals, yvals = list(zip(*by_key.items()))
xvals = [bin(xval)[2:].zfill(num_qubits) for xval in xvals]
plt.bar(xvals, yvals)
plt.xticks(rotation=75)
plt.title("Resultados do produto interno")
plt.xlabel("Bitstrings medidos")
plt.ylabel("Contagens")
plt.show()
visualize_counts(counts, overlap_circ.num_qubits)
Código Qiskit para full kernel matrix
# Dataset CSV: https://raw.githubusercontent.com/qiskit-community/prototype-quantum-kernel-training/main/data/dataset_graph7.csv
df = pd.read_csv("dataset_graph7.csv", sep=",", header=None)
# Preparar dados de treino e teste:
train_size = 90
X_train = df.values[0:train_size, :-1]
train_labels = df.values[0:train_size, -1]
test_size = 30
X_test = df.values[train_size : train_size + test_size, :-1]
test_labels = df.values[train_size : train_size + test_size, -1]
# Kernel matrix vazia:
num_samples = np.shape(X_train)[0]
kernel_matrix = np.full((num_samples, num_samples), np.nan)
test_matrix = np.full((test_size, num_samples), np.nan)
# Preparar feature map para processar overlap
num_features = np.shape(X_train)[1]
num_qubits = int(num_features / 2)
entangler_map = [[0, 2], [3, 4], [2, 5], [1, 4], [2, 3], [4, 6]]
fm = QuantumCircuit(num_qubits)
training_param = Parameter("θ")
feature_params = ParameterVector("x", num_qubits * 2)
fm.ry(training_param, fm.qubits) # Rotação Y nos qubits
for cz in entangler_map: # Aplicar CZ (inversão de fase) entre qubits no map
fm.cz(cz[0], cz[1])
for i in range(num_qubits): # Aplicar rotações Z e X nos qubits
fm.rz(-2 * feature_params[2 * i + 1], i)
fm.rx(-2 * feature_params[2 * i], i)
num_shots = 10000
sampler = StatevectorSampler()
for x1 in range(0, train_size):
for x2 in range(x1 + 1, train_size):
unitary1 = fm.assign_parameters(list(X_train[x1]) + [np.pi / 2])
unitary2 = fm.assign_parameters(list(X_train[x2]) + [np.pi / 2])
overlap_circ = unitary_overlap(unitary1, unitary2)
overlap_circ.measure_all()
counts = (
sampler.run([overlap_circ], shots=num_shots)
.result()[0]
.data.meas.get_int_counts()
)
kernel_matrix[x1, x2] = counts.get(0, 0.0) / num_shots
kernel_matrix[x2, x1] = counts.get(0, 0.0) / num_shots
kernel_matrix[x1, x1] = 1
print("Treinamento concluído")
for x1 in range(0, test_size):
for x2 in range(0, train_size):
unitary1 = fm.assign_parameters(list(X_test[x1]) + [np.pi / 2])
unitary2 = fm.assign_parameters(list(X_train[x2]) + [np.pi / 2])
overlap_circ = unitary_overlap(unitary1, unitary2)
overlap_circ.measure_all()
counts = (
sampler.run([overlap_circ], shots=num_shots)
.result()[0]
.data.meas.get_int_counts()
)
test_matrix[x1, x2] = counts.get(0, 0.0) / num_shots
print("Matriz de teste concluída")
qml_svc = SVC(kernel="precomputed") # Aplicar algoritmo ML Support Vector Classifier em kernel matrix pré-computada
qml_svc.fit(kernel_matrix, train_labels) # Treinar modelo via kernel matrix, e rótulos de treino como entrada
qml_score_precomputed_kernel = qml_svc.score(test_matrix, test_labels)
print("Score de teste em kernel classificado:", qml_score_precomputed_kernel) # Se 1.0, então Score 100%
Elaborado por Mateus Schwede
ubsocial.github.io