Mitigação de erros de leitura para a primitiva Sampler usando M3
Estimativa de uso: menos de um minuto em um processador Heron r2 (OBSERVAÇÃO: esta é apenas uma estimativa. Seu tempo de execução pode variar)
Segundo plano
Ao contrário da primitiva Estimator, a primitiva Sampler não tem suporte integrado para atenuação de erros. Vários dos métodos suportados pelo Estimator são projetados especificamente para valores de expectativa e, portanto, não são aplicáveis à primitiva Sampler. Uma exceção é a atenuação de erros de leitura, que é um método altamente eficaz que também se aplica à primitiva Sampler.
O complemento M3 Qiskit implementa um método eficiente de atenuação de erros de leitura. Este tutorial explica como usar o complemento M3 Qiskit para atenuar o erro de leitura da primitiva Sampler.
O que é erro de leitura?
Imediatamente antes da medição, o estado de um registro de qubit é descrito por uma superposição de estados de base computacional, ou por uma matriz de densidade. A medição do registro de qubit em um registro de bit clássico é feita em duas etapas. Primeiro, é realizada a medição quântica propriamente dita. Isso significa que o estado do registro do qubit é projetado em uma única base de estado que é caracterizada por uma sequência de s e s. A segunda etapa consiste em ler a cadeia de bits que caracteriza esse estado básico e gravá-la na memória clássica do computador. Chamamos essa etapa de leitura. Acontece que a segunda etapa (leitura) incorre em mais erros do que a primeira etapa (projeção nos estados básicos). Isso faz sentido quando você se lembra que a leitura requer a detecção de um estado quântico microscópico microscópico e amplificá-lo para o reino macroscópico. Um ressonador de leitura é acoplado ao ao qubit (transmônio), experimentando assim uma mudança de frequência muito pequena. Um pulso de micro-ondas é então refletido no ressonador e, por sua vez, sofre pequenas alterações em suas características. O pulso refletido é então amplificado e analisado. Esse é um processo delicado processo delicado e está sujeito a uma série de erros.
O ponto importante é que, embora tanto a medição quântica quanto a leitura estejam sujeitas a erros, a a última incorre no erro dominante, chamado de erro de leitura, que é o foco deste tutorial.
Fundamentos teóricos
Se a cadeia de bits amostrada (armazenada na memória clássica) for diferente da cadeia de bits que caracteriza o o estado quântico projetado, dizemos que ocorreu um erro de leitura. Observa-se que esses erros são aleatórios e não correlacionados de amostra para amostra. Foi comprovado que é útil modelar o erro de leitura como um canal clássico com ruído. Ou seja, para cada par de bitstrings e , há uma probabilidade fixa de que um valor verdadeiro de seja lido incorretamente como seja lido incorretamente como .
Mais precisamente, para cada par de bitstrings , há uma probabilidade (condicional) de que seja lido, dado que o valor verdadeiro é Isto é,
em que é o número de bits no registro de leitura. Para fins de concretude, presumimos que é um número inteiro decimal cuja representação binária é a cadeia de bits que rotula os estados da base computacional. Chamamos a matriz de matriz de atribuição. Para um valor verdadeiro fixo , a soma da probabilidade de todos os resultados com ruído deve dar . Ou seja
Uma matriz sem entradas negativas que satisfaça (1) é chamada esquerda-estocástica. Uma matriz estocástica à esquerda também é chamada de estocástica de coluna porque cada uma de suas colunas soma . Determinamos experimentalmente valores aproximados para cada elemento preparando repetidamente cada estado da base e, em seguida, calculando as frequências da ocorrência de cadeias de bits amostradas.
Se um experimento envolver a estimativa de uma distribuição de probabilidade sobre cadeias de bits de saída por amostragem repetida, poderemos usar o site para atenuar o erro de leitura no nível da distribuição. A primeira etapa é repetir várias vezes um circuito fixo de interesse, criando um histograma de cadeias de bits amostradas. O histograma normalizado é a distribuição de probabilidade medida sobre as possíveis cadeias de bits, que denotamos por . A probabilidade (estimada) de amostragem da cadeia de bits é igual à soma de todas as cadeias de bits verdadeiras , cada uma ponderada pela a probabilidade de ser confundida com . Essa afirmação em forma de matriz é
em que é a distribuição verdadeira. Em outras palavras, o erro de leitura tem o efeito de multiplicar a distribuição ideal sobre bitstrings pela matriz de atribuição para produzir a distribuição observada . Medimos e , mas não temos acesso direto a . Em princípio, obteremos obter a verdadeira distribuição de bitstrings para nosso circuito resolvendo a equação (2) para numericamente.
Antes de prosseguirmos, vale a pena observar alguns recursos importantes dessa abordagem ingênua.
- Na prática, a equação (2) não é resolvida com a inversão de . As rotinas de álgebra linear em bibliotecas de software empregam métodos que são mais estáveis, precisos e eficientes.
- Ao estimar , presumimos que ocorreram apenas erros de leitura. Em particular, presumimos que não houve erros de preparação de estado e de medição quântica - ou ou, pelo menos, que eles foram atenuados de outra forma. Na medida em que essa é uma boa suposição, realmente representa apenas um erro de leitura. Mas quando usamos o site para corrigir uma distribuição medida sobre cadeias de bits, não fazemos essa suposição. De fato, esperamos que um circuito interessante interessante introduza ruído, por exemplo, erros de porta. A distribuição "verdadeira" ainda inclui efeitos de quaisquer erros que não sejam atenuados de outra forma.
Esse método, embora útil em algumas circunstâncias, sofre de algumas limitações.
Os recursos de espaço e tempo necessários para estimar crescem exponencialmente em :
- A estimativa de e está sujeita a erro estatístico devido à amostragem finita. Esse ruído pode ser tão pequeno quanto desejado ao custo de mais disparos (até a escala de tempo dos parâmetros de hardware de desvio que resultam em erros sistemáticos em ). Entretanto, se não forem feitas suposições sobre as cadeias de bits observadas ao realizar a atenuação, o número de disparos necessários para estimar cresce pelo menos exponencialmente em .
- é uma matriz . Quando , a quantidade de memória necessária para armazenar é é maior do que a memória disponível em um laptop potente.
Outras limitações são:
- A distribuição recuperada pode ter uma ou mais probabilidades negativas (embora ainda somando um). Uma solução é minimizar o sujeito à restrição de que cada entrada em seja não-negativa. No entanto, o tempo de execução desse é ordens de magnitude maior do que a resolução direta da equação (2).
- Esse procedimento de atenuação funciona no nível de uma distribuição de probabilidade sobre cadeias de bits. Em particular, ele não pode corrigir um erro em uma bitstring observada.
Complemento Qiskit M3 : dimensionamento para cadeias de bits mais longas
A solução da equação (2) usando rotinas de álgebra linear numérica padrão é limitada a cadeias de bits de no máximo 10 bits. M3 no entanto, pode lidar com cadeias de bits muito mais longas. Duas propriedades importantes do site M3 que tornam isso possível são:
- As correlações no erro de leitura de ordem três e superior entre coleções de bits são consideradas insignificantes e são ignoradas. Em princípio, ao custo de mais disparos, também seria possível estimar correlações mais altas.
- Em vez de construir explicitamente o , usamos uma matriz efetiva muito menor que registra que registra as probabilidades apenas das cadeias de bits coletadas na construção de .
Em um nível elevado, o procedimento funciona da seguinte forma.
Primeiro, criamos blocos de construção a partir dos quais podemos construir uma descrição simplificada e eficaz de . Em seguida, executamos repetidamente o circuito de interesse e coletamos bitstrings que usamos para construir e, com a ajuda dos blocos de construção, uma eficaz.
Mais precisamente,
-
As matrizes de atribuição de um único qubit são estimadas para cada qubit. Para fazer isso, repetidamente preparamos o registro do qubit no estado totalmente zero e depois no estado totalmente um , e registramos a probabilidade de cada qubit ser lido incorretamente.
-
As correlações de ordem três e superiores são consideradas insignificantes e são ignoradas.
Em vez disso, construímos um número de matrizes de atribuição de um único qubit e um número de matrizes de atribuição de dois qubits de dois qubits. Essas matrizes de atribuição de um e dois qubits são armazenadas para uso posterior uso posterior.
-
Depois de amostrar repetidamente um circuito para construir , construímos uma aproximação efetiva para usando apenas bitstrings que são amostrados na construção de . Essa matriz efetiva é construída usando as matrizes de um e dois qubits descritas no item anterior. A dimensão linear dessa matriz é, no máximo, da ordem do número de de disparos usados na construção de , que é muito menor do que a dimensão da matriz de atribuição completa .
Para obter detalhes técnicos sobre M3, consulte Mitigação escalável de erros de medição em computadores quânticos.
Aplicação de M3 a um algoritmo quântico
Aplicaremos a atenuação de leitura do M3 ao problema de deslocamento oculto. O problema do deslocamento oculto e os problemas intimamente relacionados, como o problema do subgrupo oculto, foram originalmente concebidos em uma configuração tolerante a falhas (mais precisamente, antes de se provar que as QPUs tolerantes a falhas eram possíveis!) Mas eles também são estudados com os processadores disponíveis. Um exemplo de aceleração exponencial algorítmica obtida para uma variante do problema de deslocamento oculto obtido em QPUs IBM® de 127 qubits pode ser encontrado neste documento ( versão arXiv ).
A seguir, toda a aritmética é booleana. Ou seja, para , adição, é a função XOR lógica. Além disso, a multiplicação (ou ) é a função lógica AND. Para , é definido pela aplicação bit a bit do XOR. O produto escalar é definido por .
Operador de Hadamard e transformada de Fourier
Na implementação de algoritmos quânticos, é muito comum usar o operador Hadamard como uma transformada de Fourier. Os estados da base computacional às vezes são chamados de estados clássicos. Elas estão em uma relação de um para um com as bitstrings clássicas. O operador Hadamard de -qubit em estados clássicos pode ser visto como uma transformação de Fourier no hipercubo booleano:
Considere um estado correspondente a um bitstring fixo . Aplicando , e usando , vemos que a transformada de Fourier de pode ser escrita como
O Hadamard é seu próprio inverso, ou seja, . Assim, a transformada de Fourier inversa é o mesmo operador, . Explicitamente, temos,
O problema da mudança oculta
Consideramos um exemplo simples de um problema de deslocamento oculto. O problema é identificar uma mudança constante na entrada de uma função. A função que consideramos é o produto escalar. É o membro mais simples de uma grande classe de funções que admitem uma aceleração quântica para o problema do deslocamento oculto por meio de técnicas semelhantes às apresentadas a seguir.
Seja uma cadeia de bits de comprimento . Definimos por
Seja uma cadeia de bits fixa de comprimento . Além disso, definimos por
em que e são parâmetros (ocultos). Temos duas caixas pretas, uma implementando e a outra . Supomos que sabemos que elas calculam as funções definidas acima, exceto pelo fato de que não sabemos nem nem . O jogo é determinar os bitstrings ocultos (turnos) e fazendo consultas em e . Está claro que, se jogarmos o jogo de forma clássica, precisamos de consultas a para determinar e . Por exemplo, podemos consultar com todos os pares de cadeias de caracteres, de modo que um elemento do par seja todo zero e o outro elemento tenha exatamente um elemento definido como . Em cada consulta, aprendemos um elemento de ou . No entanto, veremos que, se as caixas pretas forem implementadas como circuitos quânticos, poderemos determinar e com uma única consulta a cada um dos sites e .
No contexto da complexidade algorítmica, uma caixa preta é chamada de oráculo. Além de ser opaco, um oráculo tem a propriedade de consumir a entrada e produzir a saída instantaneamente, sem acrescentar nada ao orçamento de complexidade do algoritmo produz a saída instantaneamente, sem acrescentar nada ao orçamento de complexidade do algoritmo no qual ele está inserido. De fato, no caso em questão, os oráculos que implementam e serão vistos como eficientes.
Circuitos quânticos para e
Precisamos dos seguintes ingredientes para implementar e como circuitos quânticos.
Para estados clássicos de um único qubit , com , a porta controlada pode ser escrita como
Operaremos com os portões CZ, um em , outro em , e assim por diante, até . Chamamos esse operador de .
é uma versão quântica de :
Também precisamos implementar uma mudança de bitstring. Denotamos o operador no registro por e da mesma forma no registro . Esses operadores aplicam sempre que um único bit for , e a identidade sempre que for . Então, temos
A segunda caixa preta é implementada pela unidade , dada por
Para ver isso, aplicamos os operadores da direita para a esquerda ao estado . Primeiramente
Então,
Finalmente,
que é, de fato, a versão quântica de .
O algoritmo de mudança oculta
Agora, juntamos as peças para resolver o problema do deslocamento oculto. Começamos aplicando Hadamards aos registros inicializados no estado totalmente zero.
Em seguida, consultamos o oráculo para chegar a
Na última linha, omitimos o fator de fase global constante , e denotamos a igualdade até uma fase por . Em seguida, a aplicação do oráculo introduz outro fator de , cancelando o que já estava presente presente. Temos então:
A etapa final é aplicar a transformada inversa de Fourier, , resultando em
O circuito está concluído. Na ausência de ruído, a amostragem dos registros quânticos retornará as cadeias de bits com probabilidade .
O produto interno booleano é um exemplo das chamadas funções dobradas. Não definiremos funções de curvatura aqui mas apenas observaremos que elas "são extremamente resistentes a ataques que buscam explorar uma dependência de dos resultados em algum subespaço linear das entradas" Essa citação é do artigo Quantum algorithms for highly non-linear Boolean functions (Algoritmos quânticos para funções booleanas altamente não lineares ), que fornece algoritmos eficientes de deslocamento oculto para várias classes de funções dobradas. O algoritmo deste tutorial aparece na Seção 3.1 do artigo.
No caso mais geral, o circuito para encontrar um deslocamento oculto é
No caso geral, e são funções de uma única variável. Nosso exemplo do produto interno tem essa forma se deixarmos , com igual à concatenação de e , e igual à concatenação de e . O caso geral requer exatamente dois oráculos: Um oráculo para e outro para , em que o último é uma função conhecida como o dual da função dobrada . A função de produto interno tem a propriedade autodual .
Em nosso circuito para a mudança oculta no produto interno, omitimos a camada intermediária de Hadamards que aparece no circuito para o caso geral. Embora no caso geral essa camada seja necessária, economizamos um pouco de profundidade ao omiti-la, às custas de um pouco de pós-processamento de pós-processamento, pois o resultado é em vez do desejado .
Requisitos
Antes de iniciar este tutorial, verifique se você tem os seguintes itens instalados:
- Qiskit SDK v2.1 ou posterior, com suporte para visualização
- Qiskit Runtime v0.41 ou posterior (
pip install qiskit-ibm-runtime) - M3 Qiskit addon v3.0 (
pip install mthree)
Instalação
from collections.abc import Iterator, Sequence
from random import Random
from qiskit.circuit import (
CircuitInstruction,
QuantumCircuit,
QuantumRegister,
Qubit,
)
from qiskit.circuit.library import CZGate, HGate, XGate
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit_ibm_runtime import QiskitRuntimeService
import timeit
import matplotlib.pyplot as plt
from qiskit_ibm_runtime import SamplerV2 as Sampler
import mthreePasso 1: Mapear entradas clássicas para um problema quântico
Primeiro, escrevemos as funções para implementar o problema de deslocamento oculto como um QuantumCircuit.
def apply_hadamards(qubits: Sequence[Qubit]) -> Iterator[CircuitInstruction]:
"""Apply a Hadamard gate to every qubit."""
for q in qubits:
yield CircuitInstruction(HGate(), [q], [])
def apply_shift(
qubits: Sequence[Qubit], shift: int
) -> Iterator[CircuitInstruction]:
"""Apply X gates where the bits of the shift are equal to 1."""
for i, q in zip(range(shift.bit_length()), qubits):
if shift >> i & 1:
yield CircuitInstruction(XGate(), [q], [])
def oracle_f(qubits: Sequence[Qubit]) -> Iterator[CircuitInstruction]:
"""Apply the f oracle."""
for i in range(0, len(qubits) - 1, 2):
yield CircuitInstruction(CZGate(), [qubits[i], qubits[i + 1]])
def oracle_g(
qubits: Sequence[Qubit], shift: int
) -> Iterator[CircuitInstruction]:
"""Apply the g oracle."""
yield from apply_shift(qubits, shift)
yield from oracle_f(qubits)
yield from apply_shift(qubits, shift)
def determine_hidden_shift(
qubits: Sequence[Qubit], shift: int
) -> Iterator[CircuitInstruction]:
"""Determine the hidden shift."""
yield from apply_hadamards(qubits)
yield from oracle_g(qubits, shift)
# We omit this layer in exchange for post processing
# yield from apply_hadamards(qubits)
yield from oracle_f(qubits)
yield from apply_hadamards(qubits)
def run_hidden_shift_circuit(n_qubits, rng):
hidden_shift = rng.getrandbits(n_qubits)
qubits = QuantumRegister(n_qubits, name="q")
circuit = QuantumCircuit.from_instructions(
determine_hidden_shift(qubits, hidden_shift), qubits=qubits
)
circuit.measure_all()
# Format the hidden shift as a string.
hidden_shift_string = format(hidden_shift, f"0{n_qubits}b")
return (circuit, hidden_shift, hidden_shift_string)
def display_circuit(circuit):
return circuit.remove_final_measurements(inplace=False).draw(
"mpl", idle_wires=False, scale=0.5, fold=-1
)Começaremos com um pequeno exemplo:
n_qubits = 6
random_seed = 12345
rng = Random(random_seed)
circuit, hidden_shift, hidden_shift_string = run_hidden_shift_circuit(
n_qubits, rng
)
print(f"Hidden shift string {hidden_shift_string}")
display_circuit(circuit)Output:
Hidden shift string 011010
Etapa 2: Otimizar circuitos para execução em hardware quântico
job_tags = [
f"shift {hidden_shift_string}",
f"n_qubits {n_qubits}",
f"seed = {random_seed}",
]
job_tagsOutput:
['shift 011010', 'n_qubits 6', 'seed = 12345']
# Uncomment this to run the circuits on a quantum computer on IBMCloud.
service = QiskitRuntimeService()
backend = service.least_busy(
operational=True, simulator=False, min_num_qubits=100
)
# from qiskit_ibm_runtime.fake_provider import FakeMelbourneV2
# backend = FakeMelbourneV2()
# backend.refresh(service)
print(f"Using backend {backend.name}")
def get_isa_circuit(circuit, backend):
pass_manager = generate_preset_pass_manager(
optimization_level=3, backend=backend, seed_transpiler=1234
)
isa_circuit = pass_manager.run(circuit)
return isa_circuit
isa_circuit = get_isa_circuit(circuit, backend)
display_circuit(isa_circuit)Output:
Using backend ibm_kingston
Passo 3: Execute circuitos usando Qiskit primitives
# submit job for solving the hidden shift problem using the Sampler primitive
NUM_SHOTS = 50_000
def run_sampler(backend, isa_circuit, num_shots):
sampler = Sampler(mode=backend)
sampler.options.environment.job_tags
pubs = [(isa_circuit, None, NUM_SHOTS)]
job = sampler.run(pubs)
return job
def setup_mthree_mitigation(isa_circuit, backend):
# retrieve the final qubit mapping so mthree knows which qubits to calibrate
qubit_mapping = mthree.utils.final_measurement_mapping(isa_circuit)
# submit jobs for readout error calibration
mit = mthree.M3Mitigation(backend)
mit.cals_from_system(qubit_mapping, rep_delay=None)
return mit, qubit_mappingjob = run_sampler(backend, isa_circuit, NUM_SHOTS)
mit, qubit_mapping = setup_mthree_mitigation(isa_circuit, backend)Etapa 4: Pós-processamento e retorno dos resultados em formato clássico
Na discussão teórica acima, determinamos que, para a entrada , esperamos a saída . Uma complicação adicional é que, para ter um circuito mais simples (pré-transpilado), inserimos as portas CZ necessárias entre pares vizinhos de qubits. Isso equivale a intercalar as cadeias de bits e como . A cadeia de caracteres de saída será intercalada de forma semelhante: . A função unscramble abaixo transforma a cadeia de caracteres de saída de para de modo que as cadeias de caracteres de entrada e saída possam ser comparadas diretamente.
# retrieve bitstring counts
def get_bitstring_counts(job):
result = job.result()
pub_result = result[0]
counts = pub_result.data.meas.get_counts()
return counts, pub_resultcounts, pub_result = get_bitstring_counts(job)A distância de Hamming entre duas cadeias de bits é o número de índices em que os bits diferem.
def hamming_distance(s1, s2):
weight = 0
for c1, c2 in zip(s1, s2):
(c1, c2) = (int(c1), int(c2))
if (c1 == 1 and c2 == 1) or (c1 == 0 and c2 == 0):
weight += 1
return weight# Replace string of form a1b1a2b2... with b1a1b2a1...
# That is, reverse order of successive pairs of bits.
def unscramble(bitstring):
ps = [bitstring[i : i + 2][::-1] for i in range(0, len(bitstring), 2)]
return "".join(ps)
def find_hidden_shift_bitstring(counts, hidden_shift_string):
# convert counts to probabilities
probs = {
unscramble(bitstring): count / NUM_SHOTS
for bitstring, count in counts.items()
}
# Retrieve the most probable bitstring.
most_probable = max(probs, key=lambda x: probs[x])
print(f"Expected hidden shift string: {hidden_shift_string}")
if most_probable == hidden_shift_string:
print("Most probable bitstring matches hidden shift 😊.")
else:
print("Most probable bitstring didn't match hidden shift ☹️.")
print("Top 10 bitstrings and their probabilities:")
display(
{
k: (v, hamming_distance(hidden_shift_string, k))
for k, v in sorted(
probs.items(), key=lambda x: x[1], reverse=True
)[:10]
}
)
return probs, most_probableprobs, most_probable = find_hidden_shift_bitstring(
counts, hidden_shift_string
)Output:
Expected hidden shift string: 011010
Most probable bitstring matches hidden shift 😊.
Top 10 bitstrings and their probabilities:
{'011010': (0.9743, 6),
'001010': (0.00812, 5),
'010010': (0.0063, 5),
'011000': (0.00554, 5),
'011011': (0.00492, 5),
'011110': (0.00044, 5),
'001000': (0.00012, 4),
'010000': (8e-05, 4),
'001011': (6e-05, 4),
'000010': (6e-05, 4)}
Vamos registrar a probabilidade do bitstring mais provável antes de aplicar a atenuação do erro de leitura com M3.
max_probability_before_M3 = probs[most_probable]
max_probability_before_M3Output:
0.9743
Agora aplicamos a correção de leitura aprendida por M3 às contagens.
A função apply_corrections retorna uma distribuição de quase-probabilidade. Esta é uma lista de float objetos que somam , mas alguns valores podem ser negativos.
def perform_mitigation(mit, counts, qubit_mapping):
# mitigate readout error
quasis = mit.apply_correction(counts, qubit_mapping)
# print results
most_probable_after_m3 = unscramble(max(quasis, key=lambda x: quasis[x]))
is_hidden_shift_identified = most_probable_after_m3 == hidden_shift_string
if is_hidden_shift_identified:
print("Most probable bitstring matches hidden shift 😊.")
else:
print("Most probable bitstring didn't match hidden shift ☹️.")
print("Top 10 bitstrings and their quasi-probabilities:")
topten = {
unscramble(k): f"{v:.2e}"
for k, v in sorted(quasis.items(), key=lambda x: x[1], reverse=True)[
:10
]
}
max_probability_after_M3 = float(topten[most_probable_after_m3])
display(topten)
return max_probability_after_M3, is_hidden_shift_identifiedprint(f"Expected hidden shift string: {hidden_shift_string}")
max_probability_after_M3, is_hidden_shift_identified = perform_mitigation(
mit, counts, qubit_mapping
)Output:
Expected hidden shift string: 011010
Most probable bitstring matches hidden shift 😊.
Top 10 bitstrings and their quasi-probabilities:
{'011010': '1.01e+00',
'001010': '8.75e-04',
'001000': '7.38e-05',
'010000': '4.51e-05',
'111000': '2.18e-05',
'001011': '1.74e-05',
'000010': '6.42e-06',
'011001': '-7.18e-06',
'011000': '-4.53e-04',
'010010': '-1.28e-03'}
Compare a identificação da sequência de caracteres oculta antes e depois de aplicar uma correção d M3
def compare_before_and_after_M3(
max_probability_before_M3,
max_probability_after_M3,
is_hidden_shift_identified,
):
is_probability_improved = (
max_probability_after_M3 > max_probability_before_M3
)
print(f"Most probable probability before M3: {max_probability_before_M3}")
print(f"Most probable probability after M3: {max_probability_after_M3}")
if is_hidden_shift_identified and is_probability_improved:
print("Readout error mitigation effective! 😊")
else:
print("Readout error mitigation not effective. ☹️")compare_before_and_after_M3(
max_probability_before_M3,
max_probability_after_M3,
is_hidden_shift_identified,
)Output:
Most probable probability before M3: 0.9743
Most probable probability after M3: 1.01
Readout error mitigation effective! 😊
Planeje como o tempo de CPU necessário para um M3 e varia de acordo com os disparos
# Collect samples for numbers of shots varying from 5000 to 25000.
shots_range = range(5000, NUM_SHOTS + 1, 2500)
times = []
for shots in shots_range:
print(f"Applying M3 correction to {shots} shots...")
t0 = timeit.default_timer()
_ = mit.apply_correction(
pub_result.data.meas.slice_shots(range(shots)).get_counts(),
qubit_mapping,
)
t1 = timeit.default_timer()
print(f"\tDone in {t1 - t0} seconds.")
times.append(t1 - t0)
fig, ax = plt.subplots()
ax.plot(shots_range, times, "o--")
ax.set_xlabel("Shots")
ax.set_ylabel("Time (s)")
ax.set_title("Time to apply M3 correction")Output:
Applying M3 correction to 5000 shots...
Done in 0.003321983851492405 seconds.
Applying M3 correction to 7500 shots...
Done in 0.004425413906574249 seconds.
Applying M3 correction to 10000 shots...
Done in 0.006366567220538855 seconds.
Applying M3 correction to 12500 shots...
Done in 0.0071477219462394714 seconds.
Applying M3 correction to 15000 shots...
Done in 0.00860048783943057 seconds.
Applying M3 correction to 17500 shots...
Done in 0.010026784148067236 seconds.
Applying M3 correction to 20000 shots...
Done in 0.011459112167358398 seconds.
Applying M3 correction to 22500 shots...
Done in 0.012727141845971346 seconds.
Applying M3 correction to 25000 shots...
Done in 0.01406092382967472 seconds.
Applying M3 correction to 27500 shots...
Done in 0.01546052098274231 seconds.
Applying M3 correction to 30000 shots...
Done in 0.016769016161561012 seconds.
Applying M3 correction to 32500 shots...
Done in 0.019537431187927723 seconds.
Applying M3 correction to 35000 shots...
Done in 0.019739801064133644 seconds.
Applying M3 correction to 37500 shots...
Done in 0.021093040239065886 seconds.
Applying M3 correction to 40000 shots...
Done in 0.022840639110654593 seconds.
Applying M3 correction to 42500 shots...
Done in 0.023974396288394928 seconds.
Applying M3 correction to 45000 shots...
Done in 0.026412792038172483 seconds.
Applying M3 correction to 47500 shots...
Done in 0.026364430785179138 seconds.
Applying M3 correction to 50000 shots...
Done in 0.02820305060595274 seconds.
Text(0.5, 1.0, 'Time to apply M3 correction')
Interpretando o enredo
O gráfico acima mostra que o tempo necessário para aplicar a correção M3 é escalonado linearmente no número de fotos.
Escalando para cima
n_qubits = 80
rng = Random(12345)
circuit, hidden_shift, hidden_shift_string = run_hidden_shift_circuit(
n_qubits, rng
)
print(f"Hidden shift string {hidden_shift_string}")Output:
Hidden shift string 00000010100110101011101110010001010000110011101001101010101001111001100110000111
isa_circuit = get_isa_circuit(circuit, backend)job = run_sampler(backend, isa_circuit, NUM_SHOTS)
mit, qubit_mapping = setup_mthree_mitigation(isa_circuit, backend)counts, pub_result = get_bitstring_counts(job)probs, most_probable = find_hidden_shift_bitstring(
counts, hidden_shift_string
)Output:
Expected hidden shift string: 00000010100110101011101110010001010000110011101001101010101001111001100110000111
Most probable bitstring matches hidden shift 😊.
Top 10 bitstrings and their probabilities:
{'00000010100110101011101110010001010000110011101001101010101001111001100110000111': (0.50402,
80),
'00000010100110101011101110010001010000110011100001101010101001111001100110000111': (0.0396,
79),
'00000010100110101011101110010001010000110011101001101010101001111001100100000111': (0.0323,
79),
'00000010100110101011101110010001010000110011101001101010101001101001100110000111': (0.01936,
79),
'00000010100110101011101110010011010000110011101001101010101001111001100110000111': (0.01432,
79),
'00000010100110101011101110010001010000110011101001101010101001011001100110000111': (0.0101,
79),
'00000010100110101011101110010001010000110011101001101010101001110001100110000111': (0.00924,
79),
'00000010100110101011101110010001010000010011101001101010101001111001100110000111': (0.00908,
79),
'00000010100110101011100110010001010000110011101001101010101001111001100110000111': (0.00888,
79),
'00000010100110101011101110010001010000110011101001100010101001111001100110000111': (0.0082,
79)}
Vemos que a string de deslocamento oculto correta foi encontrada. Além disso, as nove cadeias de bits mais prováveis a seguir estão erradas em apenas uma posição.
Registre a probabilidade mais provável:
max_probability_before_M3 = probs[most_probable]
max_probability_before_M3Output:
0.50402
print(f"Expected hidden shift string: {hidden_shift_string}")
max_probability_after_M3, is_hidden_shift_identified = perform_mitigation(
mit, counts, qubit_mapping
)Output:
Expected hidden shift string: 00000010100110101011101110010001010000110011101001101010101001111001100110000111
Most probable bitstring matches hidden shift 😊.
Top 10 bitstrings and their quasi-probabilities:
{'00000010100110101011101110010001010000110011101001101010101001111001100110000111': '9.85e-01',
'00000010100110101011101110010001010000110011100001101010101001111001100110000111': '6.84e-03',
'00000010100110101011100110010001010000110011101001101010101001111001100110000111': '3.87e-03',
'00000010100110101011101110010011010000110011101001101010101001111001100110000111': '3.42e-03',
'00000010100110101011101110010001010000110011101001101010101001111001100100000111': '3.30e-03',
'00000010100110101011101110010001010000110011101001101010101001110001100110000111': '3.28e-03',
'00000010100010101011101110010001010000110011101001101010101001111001100110000111': '2.62e-03',
'00000010100110101011101110010001010000110011101001101010101001101001100110000111': '2.43e-03',
'00000010100110101011101110010000010000110011101001101010101001111001100110000111': '1.73e-03',
'00000010100110101011101110010001010000110011101001101010101001111001000110000111': '1.63e-03'}
compare_before_and_after_M3(
max_probability_before_M3,
max_probability_after_M3,
is_hidden_shift_identified,
)Output:
Most probable probability before M3: 0.54348
Most probable probability after M3: 0.99
Readout error mitigation effective! 😊
Os resultados mostram que o erro de leitura foi a fonte dominante de erro e que a atenuação do M3 foi eficaz.