Usar pós-seleção em cargas de trabalho
Versões dos pacotes
O código desta página foi desenvolvido usando os seguintes requisitos. Recomendamos usar essas versões ou versões mais recentes.
qiskit[all]~=2.4.0
qiskit-ibm-runtime~=0.46.1
qiskit-addon-utils~=0.3.1
Ao otimizar a estratégia de mitigação de erros de uma carga de trabalho, é frequentemente útil filtrar medições que sabidamente foram contaminadas por processos de ruído não-Markovianos (correlacionados). Um método para isso envolve acrescentar ao Circuit uma etapa de pós-processamento que mede qubits ativos e "espectadores" adjacentes, aplica uma rotação lenta a cada qubit e os mede novamente. Nos casos em que as duas medições não confirmam um qubit com flip esperado, o shot é descartado aplicando uma máscara aos resultados.
O pacote Qiskit addon utilities fornece um conjunto de passes de Transpiler e uma função de pós-seleção para aplicar a máscara. Esta página fornece orientações sobre como incorporar a pós-seleção em suas cargas de trabalho quânticas, usando um estado GHZ de quatro qubits como exemplo.
Criar carga de trabalho
Comece preparando o circuito a ser executado e transpilado contra um Backend que suporte gates fracionários.
# Added by doQumentation — required packages for this notebook
!pip install -q qiskit qiskit-addon-utils qiskit-ibm-runtime
from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit.circuit import QuantumCircuit
from qiskit.transpiler import generate_preset_pass_manager
circuit = QuantumCircuit(4)
circuit.h(0)
circuit.cx(0, 1)
circuit.cx(1, 2)
circuit.cx(2, 3)
circuit.measure_all()
service = QiskitRuntimeService()
backend = service.least_busy(use_fractional_gates=True)
pm = generate_preset_pass_manager(optimization_level=3, backend=backend)
transpiled_circuit = pm.run(circuit)
transpiled_circuit.draw("mpl")
Adicionar passes de Transpiler de pós-seleção
Em seguida, crie um gerenciador de passes predefinido que inclua os passes AddPostSelectionMeasures e AddSpectatorMeasures do pacote qiskit-addon-utils. Isso acrescentará ao Circuit uma sequência de pequenas rotações RX angulares (efetivamente produzindo um gate X longo) junto com um segundo conjunto de medições.
from qiskit.transpiler import PassManager
from qiskit_addon_utils.noise_management.post_selection import PostSelector
from qiskit_addon_utils.noise_management.post_selection.transpiler.passes import (
AddPostSelectionMeasures,
AddSpectatorMeasures,
)
post_selection_pm = PassManager(
[
AddSpectatorMeasures(backend.coupling_map, add_barrier=True),
AddPostSelectionMeasures(x_pulse_type="rx"),
]
)
template_circuit_ps = post_selection_pm.run(transpiled_circuit)
template_circuit_ps.draw("mpl", fold=-1, idle_wires=False)
Executar programa quântico
Em seguida, prepare um objeto QuantumProgram contendo o circuito a ser executado.
from qiskit_ibm_runtime import QuantumProgram, Executor
shots = 4000
program = QuantumProgram(shots=shots)
program.append_circuit_item(template_circuit_ps)
# Initialize the Executor job and run
executor = Executor(backend)
executor_job = executor.run(program)
print(f"Job ID: {executor_job.job_id()}")
Job ID: d82dumugbeec73alm5g0
Agora você pode interpretar os resultados. O resultado do executor é um dicionário com várias chaves.
executor_result = executor_job.result()[0]
executor_result.keys()
dict_keys(['meas', 'spec', 'meas_ps', 'spec_ps'])
Essas chaves correspondem aos qubits ativos e espectadores antes das instruções rx (meas e spec) e após as instruções rx (meas_ps e spec_ps). Cada uma delas é um array de arrays baseado no número de shots e qubits. Neste caso, o formato é (1000, 4).
Criar máscara de pós-seleção
A partir dessas medições, você pode criar uma máscara usando a classe PostSelector do qiskit-addon-utils. Esta máscara é um array booleano onde cada shot é marcado como True ou False com base em uma de duas estratégias de pós-seleção. A primeira estratégia, node, usa informações de qubit para decidir se um shot de medição deve ser descartado — e a segunda, edge, usa informações de conectividade com vizinhos mais próximos para tomar essa decisão.
post_selector = PostSelector.from_circuit(
circuit=template_circuit_ps, coupling_map=backend.coupling_map
)
mask_node = post_selector.compute_mask(executor_result, strategy="node")
mask_edge = post_selector.compute_mask(executor_result, strategy="edge")
Tanto a estratégia de nó quanto a de aresta frequentemente descartam shots diferentes. Você pode escolher qualquer uma delas. Este notebook realiza um AND bit a bit, que é uma estratégia conservadora que retém um shot somente se ele for aprovado por ambas as estratégias de nó e aresta.
mask = mask_node & mask_edge
print(f"The combined mask: {mask}")
count_retained = 0
for m in mask:
count_retained += m
print(
f"Percentage of the shots retained is after post selection "
f"{100 * count_retained / shots}"
)
The combined mask: [ True True True ... True True True]
Percentage of the shots retained is after post selection 75.225
Compare a distribuição de probabilidade com e sem pós-seleção. O trecho a seguir calcula a distribuição de probabilidade antes e após a pós-seleção, bem como a distância entre as distribuições medidas e ideais.
counts = {}
counts_ps = {}
for idx, measurement in enumerate(executor_result["meas"]):
bitstring = ""
for bit in measurement:
bitstring += str(int(bit))
if bitstring in counts:
counts[bitstring] += 1
else:
counts[bitstring] = 1
# Compute count data for postselected shots based on the mask
if mask[idx]:
bitstring = ""
for bit in measurement:
bitstring += str(int(bit))
if bitstring in counts_ps:
counts_ps[bitstring] += 1
else:
counts_ps[bitstring] = 1
for key, val in counts.items():
counts[key] = val / shots
for key, val in counts_ps.items():
counts_ps[key] = float(val / count_retained)
Para demonstrar como a pós-seleção mudou seus resultados, calcule a distância entre a distribuição de probabilidade ideal e as medidas.
import itertools
from qiskit.visualization import plot_histogram
bitstrings = ["".join(i) for i in itertools.product("01", repeat=4)]
counts_ideal = {}
for bitstring in bitstrings:
counts_ideal[bitstring] = 0.0
counts_ideal["1111"] = 0.5
counts_ideal["0000"] = 0.5
prob_distance = 0.0
prob_distance_ps = 0.0
for bitstring in counts_ideal.keys():
dist = 0.0
dist_ps = 0.0
if bitstring in counts:
dist = abs(counts[bitstring] - counts_ideal[bitstring])
if bitstring in counts_ps:
dist_ps = abs(counts_ps[bitstring] - counts_ideal[bitstring])
prob_distance += dist
prob_distance_ps += dist_ps
print(
f"Distance from ideal distribution before postselection: "
f"{1-prob_distance*0.5}"
)
print(
f"Distance from ideal distribution before after-selection: "
f"{1-prob_distance_ps*0.5}"
)
plot_histogram([counts, counts_ps], legend=["Normal", "Post selected"])
Distance from ideal distribution before postselection: 0.9015
Distance from ideal distribution before after-selection: 0.9416749750747756
Embora a pós-seleção possa melhorar significativamente a qualidade dos resultados filtrando medições de resultado afetadas por ruído não-Markoviano, ela não é uma solução completa para mitigação de erros por si só. A pós-seleção reduz o impacto de certos erros descartando resultados de medição inválidos, mas isso vem ao custo de maior sobrecarga de amostragem e não aborda todos os mecanismos de erro presentes no hardware quântico de curto prazo. Como resultado, é provável que seja insuficiente confiar apenas na pós-seleção para Circuits mais complexos ou mais profundos. Em vez disso, a pós-seleção é mais eficaz quando usada como parte de uma estratégia de mitigação de erros mais ampla — complementando técnicas como mitigação de erros de medição, compilação de circuito sensível ao ruído ou cancelamento probabilístico de erros — para melhorar a confiabilidade das cargas de trabalho quânticas equilibrando precisão e custo de recursos.
Próximos passos
- Entenda como incorporar o aprendizado de ruído em uma carga de trabalho quântica.
- Leia outras técnicas de mitigação e supressão de erros disponíveis.
- Aprenda como usar códigos de espaço-tempo para uma abordagem de baixa sobrecarga para detecção de erros