Truncar os termos de Pauli durante a retropropagação
Este guia explica como configurar o mecanismo de truncamento do termo de Pauli fornecido pelo módulo qiskit_addon_obp.utils.truncating.
A retropropagação de operadores (OBP) pode ser usada para reduzir a profundidade dos circuitos quânticos, em troca de um observável mais complexo. Para obter resultados significativos com o OBP, geralmente é necessário truncar termos da variável observável, a fim de evitar que ela se torne excessivamente grande. Uma maneira de permitir uma retropropagação mais profunda no circuito, ao mesmo tempo em que se evita que o operador se torne muito grande, é truncar os termos com coeficientes pequenos, em vez de adicioná-los ao operador. O truncamento de termos pode resultar em um número menor de circuitos quânticos a serem executados, mas isso causa algum erro no cálculo do valor esperado final, proporcional à magnitude dos coeficientes dos termos truncados.
O método de retropropagação aceita um parâmetro opcional chamado TruncationErrorBudget, que configura o truncamento dos termos de Pauli de baixo peso para cada observável após a retropropagação bem-sucedida de cada fatia. A quantidade de termos que são truncados depende de vários parâmetros de configuração especificados pelo usuário. Até o momento, há apenas uma estratégia de truncamento disponível: o método truncate_binary_search. Dado um observável e um determinado orçamento, o algoritmo realizará uma busca binária nos termos de Pauli e nos coeficientes desse observável para encontrar o limiar ótimo, de modo que a soma dos coeficientes truncados seja máxima, mas inferior ao orçamento.
Observação : Por padrão, a norma L1 é usada para avaliar e limitar o erro de truncamento; no entanto, a configuração p_norm permite especificar a norma Lp a ser utilizada.
Para obter mais informações sobre como usar essa configuração, consulte o guia “Usar diferentes normas Lp para o truncamento do termo de Pauli ”.
Os exemplos a seguir ilustram várias maneiras de criar um arquivo TruncationErrorBudget utilizando a função setup_budget que o acompanha.
Construa um circuito de exemplo
Este guia utiliza as seguintes fatias de circuito:
import rustworkx.generators
from qiskit.synthesis import LieTrotter
from qiskit_addon_utils.problem_generators import (
PauliOrderStrategy,
generate_time_evolution_circuit,
generate_xyz_hamiltonian,
)
from qiskit_addon_utils.slicing import combine_slices, slice_by_gate_types
# Generate a linear chain of 10 qubits
linear_chain = rustworkx.generators.path_graph(10)
# Use an arbitrary XY model
hamiltonian = generate_xyz_hamiltonian(
linear_chain,
coupling_constants=(0.05, 0.02, 0.0),
ext_magnetic_field=(0.02, 0.08, 0.0),
pauli_order_strategy=PauliOrderStrategy.InteractionThenColor,
)
# Evolve for some time
circuit = generate_time_evolution_circuit(
hamiltonian, synthesis=LieTrotter(reps=3), time=2.0
)
# slice the circuit by gate type
slices = slice_by_gate_types(circuit)
# for visualization purposes, recombine the slices with barriers between them and draw the resulting circuit
combine_slices(slices, include_barriers=True).draw("mpl", fold=50, scale=0.6)Output:
Vamos analisar um único observável simples:
from qiskit.quantum_info import SparsePauliOp
obs = SparsePauliOp("IIIIIZIIII")O caso mais simples: um orçamento fixo de truncamento para cada fatia
O orçamento disponível para o truncamento dos termos de Pauli pode variar em cada etapa da retropropagação. Para entender como isso funciona, vamos primeiro analisar o caso mais simples: um orçamento de truncamento fixo, conforme especificado pelo usuário.
A maneira mais simples de definir o limite de truncamento é usando o argumento max_error_per_slice .
Na verdade, é isso que está sendo feito no tutorial “Reduzindo a profundidade dos circuitos com retropropagação de operadores ”. Ao float definir como max_error_per_slice , cada fatia recebe um orçamento igual a esse valor.
No exemplo abaixo, definimos esse valor como, o que 0.001 garante um erro de truncamento implícito de, no máximo 0.018,, caso todas as 18 fatias fossem submetidas à retropropagação.
Observe que qualquer margem de erro remanescente após a retropropagação de uma fatia e o truncamento de termos com coeficientes pequenos será sempre somada à margem de erro da fatia seguinte.
from qiskit_addon_obp.utils.truncating import setup_budget
truncation_error_budget = setup_budget(max_error_per_slice=0.001)
print(truncation_error_budget)Output:
TruncationErrorBudget(per_slice_budget=[0.001], max_error_total=inf, p_norm=1)
from qiskit_addon_obp import backpropagate
from qiskit_addon_obp.utils.simplify import OperatorBudget
op_budget = OperatorBudget(max_qwc_groups=10)
bp_obs, remaining_slices, metadata = backpropagate(
obs,
slices,
operator_budget=op_budget,
truncation_error_budget=truncation_error_budget,
)
reduced_circuit = combine_slices(remaining_slices)
print(f"Backpropagated {len(slices) - len(remaining_slices)} circuit slices.")
print(
f"New observable contains {len(bp_obs)} terms and {len(bp_obs.group_commuting(qubit_wise=True))} commuting groups."
)Output:
Backpropagated 11 circuit slices.
New observable contains 29 terms and 10 commuting groups.
Em seguida, utilizamos a instância OBPMetadata e as ferramentas fornecidas pelo módulo de visualização para visualizar o processo de retropropagação.
- O gráfico no canto superior esquerdo mostra que temos orçamento suficiente para começar a truncar os termos observáveis após a retropropagação da terceira fatia. A partir da terceira fatia, sabemos que truncamos pelo menos um termo de cada fatia que submetemos à retropropagação, pois estamos incorrendo em algum erro de truncamento após cada fatia.
- O gráfico no canto superior direito mostra que o orçamento de erro aumenta para
.003na terceira fatia. Observamos uma queda acentuada no orçamento restante, o que significa que alguns termos foram omitidos dos dados observáveis. Isso está de acordo com o que deduzimos do gráfico no canto superior esquerdo. - O gráfico no canto inferior esquerdo mostra que, à medida que eliminamos termos da nossa variável observável, nosso erro acumulado total cresce de forma monotônica. Este gráfico também confirma que nenhum termo foi truncado até que a terceira fatia tivesse sido submetida à retropropagação.
- O gráfico no canto inferior direito mostra que o número de grupos de Pauli comutativos em nosso observável atingiu o limite especificado de
10. Este gráfico também mostra como a retropropagação em mais uma camada faria com que nossa variável observável ultrapassasse o limite especificado, como se pode observar pelo cruzamento das linhas preta e vermelha.
Observe que, em todos esses gráficos, o eixo x enumera as fatias submetidas à retropropagação; porém, como o OBP atua no final do circuito, slice 1 é a última fatia, slice 2 é a anterior a ela e assim por diante.
from matplotlib import pyplot as plt
from qiskit_addon_obp.utils.visualization import (
plot_accumulated_error,
plot_left_over_error_budget,
plot_num_qwc_groups,
plot_slice_errors,
)
fig, axes = plt.subplots(2, 2, figsize=(20, 10))
plot_slice_errors(metadata, axes[(0, 0)])
plot_left_over_error_budget(metadata, axes[(0, 1)])
plot_accumulated_error(metadata, axes[(1, 0)])
plot_num_qwc_groups(metadata, axes[(1, 1)])Output:
Especifique o orçamento da fatia explicitamente
Se você souber como atribuir um orçamento a cada fatia, de forma a otimizar o desempenho da retropropagação, talvez seja recomendável atribuir explicitamente um orçamento para cada fatia. Para fins de demonstração, atribuiremos orçamento zero às três primeiras fatias e utilizaremos um orçamento de .001 por fatia para as demais fatias.
# Zero out the first 3 slices' budgets
max_error_per_slice = [0.0] * 3 + [0.001] * (len(slices) - 3)
truncation_error_budget = setup_budget(
max_error_per_slice=max_error_per_slice
)
print(truncation_error_budget)Output:
TruncationErrorBudget(per_slice_budget=[0.0, 0.0, 0.0, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001], max_error_total=inf, p_norm=1)
bp_obs, remaining_slices, metadata = backpropagate(
obs,
slices,
operator_budget=op_budget,
truncation_error_budget=truncation_error_budget,
)
reduced_circuit = combine_slices(remaining_slices)
print(f"Backpropagated {len(slices) - len(remaining_slices)} circuit slices.")
print(
f"New observable contains {len(bp_obs)} terms and {len(bp_obs.group_commuting(qubit_wise=True))} commuting groups."
)Output:
Backpropagated 11 circuit slices.
New observable contains 32 terms and 10 commuting groups.
A remoção do orçamento das três primeiras camadas fez com que nenhum termo fosse truncado até depois da quarta camada, como pode ser confirmado em três desses gráficos. O que pode ser um pouco surpreendente é que, embora a quarta fatia não tenha recebido nenhum saldo orçamentário remanescente, um termo ainda foi truncado com o uso do orçamento .001 alocado. Isso fica evidente no gráfico do canto superior esquerdo, mas também pode ser observado no gráfico do canto superior direito, já que a curva do orçamento restante se torna mais plana entre as fatias três e quatro. Outro detalhe digno de nota é que, a partir deste ponto, pelo menos um termo foi truncado, assim como no exemplo anterior.
A principal conclusão é que, embora tenhamos incorrido em menos erro de truncamento no segundo exemplo devido à zeragem dos orçamentos de algumas fatias, conseguimos realizar a retropropagação no mesmo número de fatias, e nosso observável contém o mesmo número de grupos de Pauli comutativos. Podemos confirmar que o limite do nosso erro é menor no segundo exemplo ao examinar o gráfico no canto inferior esquerdo.
fig, axes = plt.subplots(2, 2, figsize=(20, 10))
plot_slice_errors(metadata, axes[(0, 0)])
plot_left_over_error_budget(metadata, axes[(0, 1)])
plot_accumulated_error(metadata, axes[(1, 0)])
plot_num_qwc_groups(metadata, axes[(1, 1)])Output:
Especifique o orçamento periodicamente
Se você tiver um circuito com algum padrão repetitivo, como um circuito de Trotter, talvez queira definir um orçamento para esse subconjunto repetido de fatias e fazer com que esse orçamento seja utilizado em todas as repetições subsequentes dessas fatias.
Mais especificamente, o circuito de exemplo que estamos usando tem seis fatias que se repetem três vezes, totalizando 18 fatias. Atribuiremos arbitrariamente um orçamento zero às camadas RYY de um único qubit e, e um orçamento .003 a cada uma das RXX camadas. Veremos como a especificação do orçamento como uma sequência “ length-6 ” faz com que o orçamento seja aplicado a todas as 18 fatias de forma cíclica.
Mais uma vez, observe que as fatias são submetidas à retropropagação na ordem inversa (ou seja, começando pelo fim). Portanto, a primeira entrada do nosso orçamento cíclico é, na verdade, utilizada para a última parcela, a segunda entrada para a parcela anterior a essa e assim por diante.
# Specify a length-6 per-slice budget.
# This will be cycled over three times to be applied to the 18 slices
max_error_per_slice = [0.0] * 4 + [0.003] * 2
truncation_error_budget = setup_budget(
max_error_per_slice=max_error_per_slice
)
print(truncation_error_budget)Output:
TruncationErrorBudget(per_slice_budget=[0.0, 0.0, 0.0, 0.0, 0.003, 0.003], max_error_total=inf, p_norm=1)
op_budget = OperatorBudget(max_qwc_groups=20)
bp_obs, remaining_slices, metadata = backpropagate(
obs,
slices,
operator_budget=op_budget,
truncation_error_budget=truncation_error_budget,
)
reduced_circuit = combine_slices(remaining_slices)
print(f"Backpropagated {len(slices) - len(remaining_slices)} circuit slices.")
print(
f"New observable contains {len(bp_obs)} terms and {len(bp_obs.group_commuting(qubit_wise=True))} commuting groups."
)Output:
Backpropagated 13 circuit slices.
New observable contains 49 terms and 14 commuting groups.
Conforme pode ser visto nas imagens do canto superior esquerdo e do canto inferior esquerdo, não foi realizado nenhum truncamento nos quatro primeiros cortes, uma vez que não lhes foi atribuído nenhum orçamento de erro. As faixas cinco e seis tiveram parte de seus prazos reduzidos à medida que o orçamento foi sendo liberado.
Conforme pode ser visto na imagem no canto superior esquerdo, ocorreu um erro relativamente grande após a retropropagação da fatia sete, embora não tivesse sido alocado nenhum orçamento para essa fatia. Isso ocorre porque apenas cerca de .002 do orçamento total .006 alocado às faixas cinco e seis foi utilizado; assim, o restante foi transferido para a faixa sete e, em grande parte, gasto, conforme pode ser visto na imagem no canto superior direito.
As imagens no canto superior esquerdo e no canto superior direito mostram que o pequeno montante restante do orçamento é esgotado entre as faixas 8 e 10, e que um novo orçamento fica disponível na faixa 11, conforme esperado. As imagens no canto superior esquerdo, no canto superior direito e no canto inferior esquerdo demonstram o comportamento cíclico do max_error_per_slice argumento quando seu comprimento é menor que o número de fatias. Esse comportamento cíclico teria continuado por todas as fatias, mas o critério de parada max_qwc_groups foi atingido após a retropropagação de 13 fatias, como pode ser visto na imagem no canto inferior direito.
Outro ponto interessante é que o número de grupos de Pauli, na verdade, diminui após a disponibilização da segunda rodada de verba na fatia 11. Isso ocorre porque havia grupos com coeficientes pequenos que não puderam ser truncados até que houvesse mais orçamento após a retropropagação da fatia 11; assim, eles se acumularam no observável por várias iterações. Esse caso também destaca que max_qwc_groups deve ser ultrapassado para que o algoritmo termine.
fig, axes = plt.subplots(2, 2, figsize=(20, 10))
plot_slice_errors(metadata, axes[(0, 0)])
plot_left_over_error_budget(metadata, axes[(0, 1)])
plot_accumulated_error(metadata, axes[(1, 0)])
plot_num_qwc_groups(metadata, axes[(1, 1)])Output:
Limitar o erro total
Além de definir o limite de erro por fatia, é possível especificar o valor máximo de erro que pode ser incorrido devido ao truncamento. Assim que esse limite for atingido, não serão realizados mais truncamentos; no entanto, a retropropagação continuará até que a observável se torne muito grande e um dos critérios de parada seja atendido.
Em seguida, repetimos o experimento acima, definindo um limite máximo para max_error_total de modo que o orçamento de erro seja esgotado após a retropropagação da fatia sete.
# Specify a length-6 per-slice budget.
# This will be cycled over 3 times to be applied to the 18 slices
max_error_per_slice = [0.0] * 4 + [0.003] * 2
truncation_error_budget = setup_budget(
max_error_per_slice=max_error_per_slice, max_error_total=0.006
)
print(truncation_error_budget)Output:
TruncationErrorBudget(per_slice_budget=[0.0, 0.0, 0.0, 0.0, 0.003, 0.003], max_error_total=0.006, p_norm=1)
bp_obs, remaining_slices, metadata = backpropagate(
obs,
slices,
operator_budget=op_budget,
truncation_error_budget=truncation_error_budget,
)
reduced_circuit = combine_slices(remaining_slices)
print(f"Backpropagated {len(slices) - len(remaining_slices)} circuit slices.")
print(
f"New observable contains {len(bp_obs)} terms and {len(bp_obs.group_commuting(qubit_wise=True))} commuting groups."
)Output:
Backpropagated 10 circuit slices.
New observable contains 67 terms and 20 commuting groups.
Como era de se esperar, nosso erro de truncamento atinge seu valor máximo em .006 (gráfico inferior esquerdo ). É importante destacar que, nesta execução, não conseguimos realizar a retropropagação da fatia 11. Isso ocorre porque não tínhamos orçamento suficiente para truncar os termos, e o número de grupos de Pauli comutativos ultrapassou o limite de 20, conforme pode ser visto na imagem no canto inferior direito.
fig, axes = plt.subplots(2, 2, figsize=(20, 10))
plot_slice_errors(metadata, axes[(0, 0)])
plot_left_over_error_budget(metadata, axes[(0, 1)])
plot_accumulated_error(metadata, axes[(1, 0)])
plot_num_qwc_groups(metadata, axes[(1, 1)])Output:
Pode ser aconselhável simplesmente estabelecer um limite máximo para o orçamento geral, sem max_error_total especificar max_error_per_slice.
truncation_error_budget = setup_budget(max_error_total=0.018)
print(truncation_error_budget)Output:
TruncationErrorBudget(per_slice_budget=[0.018], max_error_total=0.018, p_norm=1)
O resultado da célula acima pode ser um pouco surpreendente, pois o per_slice_budget está definido como max_error_total. Isso indica que todo o orçamento disponível será consumido avidamente.
Você pode pensar da seguinte maneira: todo o orçamento está disponível para cada fatia (porque fazemos um loop sobre o per_slice_budget). No entanto, qualquer verba que já tenha sido utilizada será deduzida do orçamento disponível naquele determinado momento do algoritmo.
op_budget = OperatorBudget(max_qwc_groups=10)
bp_obs, remaining_slices, metadata = backpropagate(
obs,
slices,
operator_budget=op_budget,
truncation_error_budget=truncation_error_budget,
)
reduced_circuit = combine_slices(remaining_slices)
print(f"Backpropagated {len(slices) - len(remaining_slices)} circuit slices.")
print(
f"New observable contains {len(bp_obs)} terms and {len(bp_obs.group_commuting(qubit_wise=True))} commuting groups."
)Output:
Backpropagated 9 circuit slices.
New observable contains 25 terms and 9 commuting groups.
A imagem no canto superior direito mostra como todo o orçamento de erro .018 é disponibilizado para a primeira fatia. Não é realizado nenhum truncamento até a terceira fatia; portanto, o orçamento restante permanece constante. O orçamento diminui de forma monótona, à medida que o orçamento total é disponibilizado a cada fatia submetida à retropropagação até que seja esgotado.
É digno de nota que este experimento produziu duas fatias com retropropagação a menos em comparação com o primeiro experimento deste caderno, que é quase idêntico. Isso demonstra que, para alguns problemas, distribuir o orçamento de maneira uniforme pode ser a solução ideal. Para outros problemas, permitir que as fatias utilizem avidamente todo o orçamento pode resultar em melhor desempenho.
fig, axes = plt.subplots(2, 2, figsize=(20, 10))
plot_slice_errors(metadata, axes[(0, 0)])
plot_left_over_error_budget(metadata, axes[(0, 1)])
plot_accumulated_error(metadata, axes[(1, 0)])
plot_num_qwc_groups(metadata, axes[(1, 1)])Output:
Limitar conjuntamente o número de fatias submetidas à retropropagação e o erro total
Se você não quiser distribuir o orçamento de erro por todo o circuito, mas também não quiser gastá-lo de forma excessiva, é possível especificar o número de fatias que pretende submeter à retropropagação (num_slices), juntamente com um orçamento de erro total (max_error_total). Isso distribuirá o orçamento de erro uniformemente (de acordo com p_norm) entre as fatias de entrada.
Aqui, vamos limitar o número de fatias que podemos sub-propagar a 12, e manteremos o mesmo orçamento total de erro.
num_slices = 12
truncation_error_budget = setup_budget(
max_error_total=0.018, num_slices=num_slices, p_norm=1
)
print(truncation_error_budget)Output:
TruncationErrorBudget(per_slice_budget=[0.0014999999999999998], max_error_total=0.018, p_norm=1)
Agora, vamos tentar realizar a retropropagação das 12 fatias para as quais alocamos parte do orçamento na etapa anterior. Fazemos isso simplesmente passando as 12 fatias finais pelo nosso circuito. Com p_norm=1, cada uma das 12 fatias tem um orçamento disponível de 0.018 / num_slices = 0.0015.
bp_obs, remaining_slices, metadata = backpropagate(
obs,
slices[-num_slices:],
operator_budget=op_budget,
truncation_error_budget=truncation_error_budget,
)Como passamos um subconjunto de nossas fatias (no código slices[-num_slices:]) para backpropagate, precisamos combinar as fatias restantes após a retropropagação com as fatias que nunca foram enviadas para a retropropagação (no código slices[:-num_slices]).
Depois de combinarmos todas as fatias restantes, podemos usar o combine_slices para gerar o QuantumCircuit com profundidade reduzida. Em seguida, analisamos quantas fatias passaram pela retropropagação em comparação com o aumento no tamanho do nosso observável.
# Recombine the slices remaining after backprop with the rest of the original circuit
reduced_circuit = combine_slices(slices[:-num_slices] + remaining_slices)
print(f"Backpropagated {num_slices - len(remaining_slices)} circuit slices.")
print(
f"New observable contains {len(bp_obs)} terms and {len(bp_obs.group_commuting(qubit_wise=True))} commuting groups."
)Output:
Backpropagated 12 circuit slices.
New observable contains 29 terms and 9 commuting groups.
Os gráficos mostram que conseguimos realizar com sucesso a retropropagação em todas as 12 fatias, mantendo nosso observável dentro de 10 grupos de Pauli comutativos. Também podemos observar que o uso de num_slices juntamente com max_error_total resulta em uma distribuição dos orçamentos entre as faixas, e o orçamento não utilizado é novamente repassado para a próxima faixa. Isso fica mais evidente no gráfico do canto superior direito, já que o orçamento é tanto gasto quanto reabastecido ao longo da retropropagação.
É importante destacar que esse método de distribuição de erros produziu os melhores resultados (mais fatias retropropagadas), em comparação com o primeiro experimento deste caderno e com o exemplo imediatamente anterior a este. Todos esses exemplos atribuíram um “orçamento de .018 erro”, mas a retropropagação apresentou um desempenho diferente dependendo de como esse orçamento foi distribuído.
fig, axes = plt.subplots(2, 2, figsize=(20, 10))
plot_slice_errors(metadata, axes[(0, 0)])
plot_left_over_error_budget(metadata, axes[(0, 1)])
plot_accumulated_error(metadata, axes[(1, 0)])
plot_num_qwc_groups(metadata, axes[(1, 1)])Output:
Trabalhar com vários observáveis
O método qiskit_addon_obp.backpropagate permite passar uma sequência de observáveis. Isso simplifica o fluxo de trabalho ao lidar com vários observáveis-alvo.
Mencionamos isso explicitamente aqui para mostrar como a estratégia de truncamento lida com esse tipo de caso. Para fins deste exemplo, vamos adicionar um observável a mais ao que temos usado até agora:
obs = [SparsePauliOp("IIIIIZIIII"), SparsePauliOp("IIIIIXIIII")]Por fim, repetimos o primeiro experimento deste tutorial., mas com duas variáveis observáveis para a retropropagação do circuito.
Neste exemplo, isso não afeta o número de fatias que poderiam ser submetidas à retropropagação. No entanto, podemos observar que os dois observáveis resultaram em números diferentes de termos de Pauli e de grupos comutativos.
truncation_error_budget = setup_budget(max_error_per_slice=0.001)
print(truncation_error_budget)Output:
TruncationErrorBudget(per_slice_budget=[0.001], max_error_total=inf, p_norm=1)
bp_obs, remaining_slices, metadata = backpropagate(
obs,
slices,
operator_budget=op_budget,
truncation_error_budget=truncation_error_budget,
)
reduced_circuit = combine_slices(remaining_slices)
print(f"Backpropagated {len(slices) - len(remaining_slices)} circuit slices.")
print(
f"The new first observable contains {len(bp_obs[0])} terms and {len(bp_obs[0].group_commuting(qubit_wise=True))} commuting groups."
)
print(
f"The new second observable contains {len(bp_obs[1])} terms and {len(bp_obs[1].group_commuting(qubit_wise=True))} commuting groups."
)Output:
Backpropagated 11 circuit slices.
The new first observable contains 29 terms and 10 commuting groups.
The new second observable contains 23 terms and 8 commuting groups.
Os gráficos abaixo ajudam a ilustrar como o algoritmo de retropropagação lida com múltiplas variáveis observáveis.
Em primeiro lugar, os gráficos no canto superior esquerdo, no canto superior direito e no canto inferior esquerdo mostram que o orçamento para o truncamento de termos é definido individualmente para cada variável observável. Em outras palavras, ambas as observáveis têm a capacidade de truncar termos, assumindo um erro de por 0.001 fatia retropropagada.
Devido à natureza diferente das variáveis observáveis, isso resulta em um consumo diferente do orçamento. Neste exemplo, eles apresentam bastante sobreposição, o que nem sempre ocorre.
O gráfico no canto inferior direito mostra que max_qwc_groups leva em consideração todas as variáveis observáveis. Isso significa que os termos de todas as grandezas observáveis são agrupados para se chegar a um número final de grupos comutativos por qubit, que é comparado com max_qwc_paulis. O mesmo é feito para o limiar max_paulis (que não é abordado neste caderno), o que permite definir um limite para o número de termos de Pauli em todas as observáveis.
fig, axes = plt.subplots(2, 2, figsize=(20, 10))
plot_slice_errors(metadata, axes[(0, 0)])
plot_left_over_error_budget(metadata, axes[(0, 1)])
plot_accumulated_error(metadata, axes[(1, 0)])
plot_num_qwc_groups(metadata, axes[(1, 1)])Output: