Corte de circuito para condições de contorno periódicas
Estimativa de uso: Dois minutos em um processador Eagle (OBSERVAÇÃO: esta é apenas uma estimativa. Seu tempo de execução pode variar)
Segundo plano
Neste caderno, consideramos a simulação de uma cadeia periódica de qubits em que há duas operações de qubit entre cada dois qubits adjacentes, incluindo o primeiro e o último. As cadeias periódicas são frequentemente encontradas em problemas de física e química, como modelos de Ising e simulação molecular.
Os dispositivos IBM Quantum® atuais são planares. É possível incorporar algumas cadeias periódicas na topologia diretamente onde o primeiro e o último qubits são vizinhos. No entanto, para problemas grandes o suficiente, o primeiro e o último qubits podem estar distantes, exigindo assim muitas portas SWAP para a operação de 2 qubits entre esses dois qubits. Esse problema de limite periódico foi estudado em este documento.
Neste caderno, mostramos o uso do corte de circuitos para lidar com esse problema de cadeia periódica em escala de utilidade em que o primeiro e o último qubits não são vizinhos. O corte dessa conectividade de longo alcance evita os portões SWAP extras ao custo da execução de várias instâncias do circuito e de algum pós-processamento clássico. Em resumo, o corte pode ser incorporado para calcular logicamente as operações de 2 qubits de longa distância. Em outras palavras, essa abordagem leva a um aumento efetivo na conectividade do mapa de acoplamento, resultando em um número menor de portas SWAP.
Observe que há dois tipos de cortes - cortar o fio de um circuito (chamado wire cutting) ou substituir uma porta de 2 qubits por várias operações de um único qubit (chamado gate cutting). Neste caderno, vamos nos concentrar no corte de porta. Para obter mais detalhes sobre o corte de portões, consulte o site materiais explicativos em qiskit-addon-cutting e as referências correspondentes. Para obter mais detalhes sobre o corte de fios, consulte o tutorial Corte de fios para estimativa de valores de expectativa ou os tutoriais em qiskit-addon-cutting.
Requisitos
Antes de iniciar este tutorial, verifique se você tem os seguintes itens instalados:
- Qiskit SDK v1.2 ou mais tarde (
pip install qiskit) - Qiskit Runtime v0.3 ou mais tarde (
pip install qiskit-ibm-runtime) - Complemento Qiskit para corte de circuitos v.9.0 ou posterior (
pip install qiskit-addon-cutting)
Instalação
import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl
from qiskit.transpiler import PassManager
from qiskit.transpiler.passes import (
BasisTranslator,
Optimize1qGatesDecomposition,
)
from qiskit.circuit.equivalence_library import (
SessionEquivalenceLibrary as sel,
)
from qiskit.converters import circuit_to_dag, dag_to_circuit
from qiskit.result import sampled_expectation_value
from qiskit.quantum_info import SparsePauliOp
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit.circuit.library import TwoLocal
from qiskit_addon_cutting import (
cut_gates,
generate_cutting_experiments,
reconstruct_expectation_values,
)
from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_ibm_runtime import SamplerV2, SamplerOptions, BatchPasso 1: Mapear entradas clássicas para um problema quântico
Aqui, geraremos um circuito TwoLocal e definiremos alguns observáveis.
- Entrada: Parâmetros para criar um circuito
- Saída: Circuito abstrato e observáveis
Consideramos um entangler map eficiente em termos de hardware para o circuito TwoLocal com conectividade periódica entre o último e o primeiro qubits do entangler map. Essa interação de longo alcance pode levar a portas SWAP adicionais durante a transpilação, aumentando assim a profundidade do circuito.
Selecione o backend e o layout inicial
service = QiskitRuntimeService()
backend = service.least_busy(
operational=True, simulator=False, min_num_qubits=127
)Para este notebook, consideraremos uma cadeia 1D periódica de 109 qubits, que é a cadeia 1D mais longa na topologia de um dispositivo IBM Quantum de 127 qubits. Não é possível organizar uma cadeia periódica de 109 qubits em um dispositivo de 127 qubits de modo que o primeiro e o último qubits sejam vizinhos sem incorporar portas SWAP adicionais.
init_layout = [
13,
12,
11,
10,
9,
8,
7,
6,
5,
4,
3,
2,
1,
0,
14,
18,
19,
20,
21,
22,
23,
24,
25,
26,
27,
28,
29,
30,
31,
32,
36,
51,
50,
49,
48,
47,
46,
45,
44,
43,
42,
41,
40,
39,
38,
37,
52,
56,
57,
58,
59,
60,
61,
62,
63,
64,
65,
66,
67,
68,
69,
70,
74,
89,
88,
87,
86,
85,
84,
83,
82,
81,
80,
79,
78,
77,
76,
75,
90,
94,
95,
96,
97,
98,
99,
100,
101,
102,
103,
104,
105,
106,
107,
108,
112,
126,
125,
124,
123,
122,
121,
120,
119,
118,
117,
116,
115,
114,
113,
]
# the number of qubits in the circuit is governed by the length of the initial layout
num_qubits = len(init_layout)
num_qubitsOutput:
109
Construa o mapa do entrelaçador para o circuito TwoLocal
coupling_map = [(i, i + 1) for i in range(0, len(init_layout) - 1)]
coupling_map.append(
(len(init_layout) - 1, 0)
) # adding in the periodic connectivityTwoLocal o circuito permite a repetição do rotation_blocks e do entangler map várias vezes. Nesse caso, o número de repetições determina o número de portas periódicas que precisam ser cortadas. Como a sobrecarga de amostragem aumenta exponencialmente com o número de cortes (consulte o tutorial Wire cutting for expectation values estimation para obter mais detalhes), vamos fixar o número de repetições em 2 neste notebook.
num_reps = 2
entangler_map = []
for even_edge in coupling_map[0 : len(coupling_map) : 2]:
entangler_map.append(even_edge)
for odd_edge in coupling_map[1 : len(coupling_map) : 2]:
entangler_map.append(odd_edge)ansatz = TwoLocal(
num_qubits=num_qubits,
rotation_blocks="rx",
entanglement_blocks="cx",
entanglement=entangler_map,
reps=num_reps,
).decompose()
ansatz.draw("mpl", fold=-1)Output:
Para verificar a qualidade do resultado usando o corte de circuitos, precisamos conhecer o resultado ideal. O circuito atual escolhido está além da simulação clássica de força bruta. Portanto, fixamos os parâmetros do circuito cuidadosamente para torná-lo clifford.
Atribuiremos o valor do parâmetro para as duas primeiras camadas de portas Rx e o valor para a última camada. Isso garante que o resultado ideal desse circuito seja , sendo o número de qubits. Portanto, os valores de expectativa de e , em que é o índice do qubit, são e , respectivamente.
params_last_layer = [np.pi] * ansatz.num_qubits
params = [0] * (ansatz.num_parameters - ansatz.num_qubits)
params.extend(params_last_layer)
ansatz.assign_parameters(params, inplace=True)Selecione observáveis
Para quantificar os benefícios do corte de porta, medimos os valores de expectativa dos observáveis e . Conforme discutido anteriormente, os valores de expectativa ideais são e , respectivamente.
observables = []
for i in range(num_qubits):
obs = "I" * (i) + "Z" + "I" * (num_qubits - i - 1)
observables.append(obs)
for i in range(num_qubits):
if i == num_qubits - 1:
obs = "Z" + "I" * (num_qubits - 2) + "Z"
else:
obs = "I" * i + "ZZ" + "I" * (num_qubits - i - 2)
observables.append(obs)
observables = SparsePauliOp(observables)
paulis = observables.paulis
coeffs = observables.coeffsEtapa 2: Otimizar o problema para execução em hardware quântico
- Entrada: Circuito abstrato e observáveis
- Saída: Circuito-alvo e observáveis produzidos pelo corte de portas de longo alcance
Transpilar o circuito
Observe que o circuito pode ser transpilado nesse estágio ou após o corte. Se fizermos a transpilação após o corte, será necessário fazer a transpilação de cada um dos subexperimentos gerados devido à sobrecarga de amostragem. Portanto, é mais prudente transpilar nesse estágio para reduzir a sobrecarga da transpilação.
No entanto, se a transpilação for feita nesse estágio com conectividade de hardware nativa, o transpilador anexará várias portas SWAP para colocar a operação periódica de 2 qubits, ofuscando os benefícios do corte do circuito. Para evitar esse problema, podemos aproveitar o fato de conhecermos os portões exatos que precisam ser cortados. Especificamente, podemos criar um mapa de acoplamento virtual adicionando conexões virtuais entre qubits distantes para acomodar essas portas periódicas de 2 qubits. Isso garantirá que o circuito possa ser transpilado nesse estágio sem incorporar as portas SWAP adicionais.
coupling_map = backend.configuration().coupling_map
# create a virtual coupling map with long range connectivity
virtual_coupling_map = coupling_map.copy()
virtual_coupling_map.append([init_layout[-1], init_layout[0]])
virtual_coupling_map.append([init_layout[0], init_layout[-1]])pm_virtual = generate_preset_pass_manager(
optimization_level=1,
coupling_map=virtual_coupling_map,
initial_layout=init_layout,
basis_gates=backend.configuration().basis_gates,
)
virtual_mapped_circuit = pm_virtual.run(ansatz)
virtual_mapped_circuit.draw("mpl", fold=-1, idle_wires=False)Output:
Corte as conectividades periódicas de longo alcance
Agora, cortamos as portas no circuito transpilado. Observe que as portas de 2 qubits que precisam ser cortadas são as que conectam o último e o primeiro qubits do layout.
# Find the indices of the distant gates
cut_indices = [
i
for i, instruction in enumerate(virtual_mapped_circuit.data)
if {virtual_mapped_circuit.find_bit(q)[0] for q in instruction.qubits}
== {init_layout[-1], init_layout[0]}
]Aplicaremos o layout do circuito transpilado ao observável.
trans_observables = observables.apply_layout(virtual_mapped_circuit.layout)Por fim, os subexperimentos são gerados por amostragem em diferentes bases de medição e preparação.
qpd_circuit, bases = cut_gates(virtual_mapped_circuit, cut_indices)
subexperiments, coefficients = generate_cutting_experiments(
circuits=qpd_circuit,
observables=trans_observables.paulis,
num_samples=np.inf,
)Observe que o corte das interações de longo alcance leva à execução de várias amostras do circuito que diferem nas bases de medição e preparação. Mais informações sobre isso podem ser encontradas em Construção de uma porta virtual de dois qubits por meio da amostragem de operações de um único qubit e Circuitos de corte com vários unitários de dois qubits.
O número de portas periódicas a serem cortadas é igual ao número de repetições da camada TwoLocal , definida como num_reps acima. A sobrecarga de amostragem do corte do portão é 6. Portanto, o número total de subexperimentos será .
print(f"Number of subexperiments is {len(subexperiments)} = 6**{num_reps}")Output:
Number of subexperiments is 36 = 6**2
Transpilar as subexperiências
Nesse ponto, os subexperimentos contêm circuitos com algumas portas de 1 qubit que não estão no conjunto de portas da base. Isso ocorre porque os qubits cortados são medidos em bases diferentes, e as portas de rotação usadas para isso não pertencem necessariamente ao conjunto de portas de base. Por exemplo, a medição na base X implica a aplicação de uma porta Hadamard antes da medição usual na base Z. Mas Hadamard não faz parte do conjunto de portas de base.
Em vez de aplicar todo o processo de transpilação em cada um dos circuitos nos subexperimentos, podemos usar passagens específicas de transpilação. Consulte esta documentação para obter uma descrição detalhada de todas as passagens de transpilação disponíveis.
Aplicaremos BasisTranslator e depois Optimize1qGatesDecomposition passes para garantir que todas as portas desses circuitos pertençam ao conjunto de portas da base. O uso dessas duas passagens é mais rápido do que todo o processo de transpilação, pois outras etapas, como roteamento e seleção do layout inicial, não são executadas novamente.
pass_ = PassManager(
[Optimize1qGatesDecomposition(basis=backend.configuration().basis_gates)]
)
subexperiments = pass_.run(
[
dag_to_circuit(
BasisTranslator(sel, target_basis=backend.basis_gates).run(
circuit_to_dag(circ)
)
)
for circ in subexperiments
]
)Passo 3: Execute usando Qiskit primitives
- Entrada: Circuitos-alvo
- Saída: Distribuições de quase-probabilidade
Usamos a primitiva SamplerV2 para a execução dos circuitos de corte. Desativamos dynamical decoupling e twirling para que qualquer melhoria que obtivermos no resultado se deva exclusivamente à aplicação efetiva do corte de porta para esse tipo de circuito.
options = SamplerOptions()
options.default_shots = 10000
options.dynamical_decoupling.enable = False
options.twirling.enable_gates = False
options.twirling.enable_measure = FalseAgora, enviaremos os trabalhos usando o modo em lote.
with Batch(backend=backend) as batch:
sampler = SamplerV2(options=options)
cut_job = sampler.run(subexperiments)
print(f"Job ID {cut_job.job_id()}")Output:
Job ID cwxf7wq60bqg008pvt8g
result = cut_job.result()Etapa 4: Pós-processamento e retorno do resultado no formato clássico desejado
- Entrada: Distribuições de quase-probabilidade
- Saída: Valores de expectativa reconstruídos
reconstructed_expvals = reconstruct_expectation_values(
result,
coefficients,
paulis,
)Agora calculamos a média dos observáveis do tipo Z de weight-1 e weight-2.
cut_weight_1 = np.mean(reconstructed_expvals[:num_qubits])
cut_weight_2 = np.mean(reconstructed_expvals[num_qubits:])
print(f"Average of weight-1 expectation values is {cut_weight_1}")
print(f"Average of weight-2 expectation values is {cut_weight_2}")Output:
Average of weight-1 expectation values is -0.741733944954063
Average of weight-2 expectation values is 0.6968862385320495
Verificação cruzada: Obter valor esperado não cortado
É útil verificar a vantagem da técnica de corte de circuito em relação à técnica sem corte. Aqui, calcularemos os valores de expectativa sem cortar o circuito. Observe que esse circuito sem cortes sofrerá com um grande número de portas SWAP necessárias para implementar a operação de 2 qubits entre o primeiro e o último qubits. Usaremos a função sampled_expectation_value para obter os valores de expectativa do circuito sem cortes depois de obter a distribuição de probabilidade por meio de SamplerV2. Isso permite um uso homogêneo do primitivo em todas as instâncias. No entanto, observe que também poderíamos ter usado o site EstimatorV2 para calcular diretamente os valores de expectativa.
if ansatz.num_clbits == 0:
ansatz.measure_all()
pm_uncut = generate_preset_pass_manager(
optimization_level=1, backend=backend, initial_layout=init_layout
)
transpiled_circuit = pm_uncut.run(ansatz)sampler = SamplerV2(mode=backend, options=options)
uncut_job = sampler.run([transpiled_circuit])uncut_job_id = uncut_job.job_id()
print(f"The job id for the uncut clifford circuit is {uncut_job_id}")Output:
The job id for the uncut clifford circuit is cwxfads2ac5g008jhe7g
uncut_result = uncut_job.result()[0]
uncut_counts = uncut_result.data.meas.get_counts()Agora, calcularemos os valores de expectativa média de todos os observáveis do tipo weight-1 e weight-2 Z sem corte.
uncut_expvals = [
sampled_expectation_value(uncut_counts, obs) for obs in paulis
]
uncut_weight_1 = np.mean(uncut_expvals[:num_qubits])
uncut_weight_2 = np.mean(uncut_expvals[num_qubits:])
print(f"Average of weight-1 expectation values is {uncut_weight_1}")
print(f"Average of weight-2 expectation values is {uncut_weight_2}")Output:
Average of weight-1 expectation values is -0.32494128440366965
Average of weight-2 expectation values is 0.32340917431192656
Visualizar
Vamos agora visualizar o aprimoramento obtido para os observáveis weight-1 e weight-2 ao usar o corte de porta para o circuito de cadeia periódica
mpl.rcParams.update(mpl.rcParamsDefault)
fig = plt.subplots(figsize=(12, 8), dpi=200)
width = 0.25
labels = ["Weight-1", "Weight-2"]
x = np.arange(len(labels))
ideal = [-1, 1]
cut = [cut_weight_1, cut_weight_2]
uncut = [uncut_weight_1, uncut_weight_2]
br1 = np.arange(len(ideal))
br2 = [x + width for x in br1]
br3 = [x + width for x in br2]
plt.bar(
br1, ideal, width=width, edgecolor="k", label="Ideal", color="#4589ff"
)
plt.bar(br2, cut, width=width, edgecolor="k", label="Cut", color="#a56eff")
plt.bar(
br3, uncut, width=width, edgecolor="k", label="Uncut", color="#009d9a"
)
plt.axhline(y=0, color="k", linestyle="-")
plt.xticks([r + width for r in range(len(ideal))], labels, fontsize=14)
plt.yticks(fontsize=14)
plt.legend(fontsize=14)
plt.show()Output:
Resumo
Em resumo, calculamos os valores médios de expectativa dos observáveis dos tipos weight-1 e weight-2 Z para uma cadeia 1D periódica de 109 qubits. Para isso, nós
- criamos um mapa de acoplamento virtual adicionando uma conectividade de longo alcance entre o primeiro e o último qubits da cadeia 1D e transpilamos o circuito.
- a transpilação nesse estágio nos permitiu evitar a sobrecarga de transpilar cada subexperimento separadamente após o corte,
- o uso do mapa de acoplamento virtual nos permitiu evitar portas SWAP adicionais para a operação de 2 qubits entre o primeiro e o último qubits.
- removeu a conectividade de longo alcance do circuito transpilado por meio de corte de porta.
- converteu os circuitos cortados em um conjunto de portas de base aplicando passagens de transpilação apropriadas.
- executou os circuitos de corte no dispositivo IBM Quantum usando uma primitiva
SamplerV2. - obteve o valor esperado reconstruindo os resultados dos circuitos cortados.
Inferência
Observamos nos resultados que a média dos observáveis do tipo weight-1 e weight-2 melhora significativamente com o corte das portas periódicas. Observe que este estudo não inclui nenhuma técnica de supressão ou atenuação de erros. A melhoria observada se deve exclusivamente ao uso adequado do corte de porta para esse problema. Os resultados poderiam ter sido aprimorados ainda mais com o uso de técnicas de mitigação e supressão.
Este estudo mostra um exemplo do uso eficaz do corte de portas para melhorar o desempenho da computação.
Pesquisa tutorial
Responda a esta breve pesquisa para fornecer feedback sobre este tutorial. Suas percepções nos ajudarão a melhorar nossas ofertas de conteúdo e a experiência do usuário.