Saltar al contenido principal

Corte de circuitos para condiciones de contorno periódicas

Estimación de uso: Dos minutos en un procesador Eagle (NOTA: Esto es solo una estimación. Su tiempo de ejecución podría variar.)

Antecedentes

En este cuaderno, consideramos la simulación de una cadena periódica de qubits donde existe una operación de dos qubits entre cada par de qubits adyacentes, incluyendo el primero y el último. Las cadenas periódicas se encuentran frecuentemente en problemas de física y química como los modelos de Ising y la simulación molecular.

Los dispositivos actuales de IBM Quantum® son planares. Es posible incrustar algunas cadenas periódicas en la topología directamente donde el primer y el último qubit son vecinos. Sin embargo, para problemas suficientemente grandes, el primer y el último qubit pueden estar muy alejados, requiriendo así muchas compuertas SWAP para la operación de 2 qubits entre estos dos qubits. Este problema de contorno periódico ha sido estudiado en este artículo.

En este cuaderno mostramos el uso del corte de circuitos para abordar un problema de cadena periódica a escala de utilidad donde el primer y el último qubit no son vecinos. Cortar esta conectividad de largo alcance evita las compuertas SWAP adicionales a costa de ejecutar múltiples instancias del circuito, y algo de posprocesamiento clásico. En resumen, el corte puede incorporarse para calcular lógicamente las operaciones de 2 qubits a larga distancia. En otras palabras, este enfoque conduce a un aumento efectivo en la conectividad del mapa de acoplamiento, lo que resulta en un menor número de compuertas SWAP.

Tenga en cuenta que existen dos tipos de cortes: cortar el cable de un circuito (llamado wire cutting), o reemplazar una compuerta de 2 qubits con múltiples operaciones de un solo qubit (llamado gate cutting). En este cuaderno, nos centraremos en el corte de compuertas. Para más detalles sobre el corte de compuertas, consulte los materiales explicativos en qiskit-addon-cutting, y las referencias correspondientes. Para más detalles sobre el corte de cables, consulte el tutorial Corte de cables para la estimación de valores esperados, o los tutoriales en qiskit-addon-cutting.

Requisitos

Antes de comenzar este tutorial, asegúrese de tener instalado lo siguiente:

  • Qiskit SDK v1.2 o posterior (pip install qiskit)
  • Qiskit Runtime v0.3 o posterior (pip install qiskit-ibm-runtime)
  • Complemento de corte de circuitos de Qiskit v.9.0 o posterior (pip install qiskit-addon-cutting)

Configuración

# Added by doQumentation — required packages for this notebook
!pip install -q matplotlib numpy qiskit qiskit-addon-cutting qiskit-ibm-runtime
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, Batch

Paso 1: Mapear entradas clásicas a un problema cuántico

Aquí, generaremos un circuito TwoLocal y definiremos algunos observables.

  • Entrada: Parámetros para crear un circuito
  • Salida: Circuito abstracto y observables

Consideramos un entangler map eficiente en hardware para el circuito TwoLocal con conectividad periódica entre el último y el primer qubit del entangler map. Esta interacción de largo alcance puede generar compuertas SWAP adicionales durante la transpilación, aumentando así la profundidad del circuito.

Seleccionar backend y disposición inicial

service = QiskitRuntimeService()
backend = service.least_busy(
operational=True, simulator=False, min_num_qubits=127
)

Para este cuaderno consideraremos una cadena 1D periódica de 109 qubits, que es la cadena 1D más larga en la topología de un dispositivo IBM Quantum de 127 qubits. No es posible organizar una cadena periódica de 109 qubits en un dispositivo de 127 qubits de manera que el primer y el último qubit sean vecinos sin incorporar compuertas SWAP adicionales.

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_qubits
109

Construir el mapa de entrelazamiento para el 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 connectivity

El circuito TwoLocal permite la repetición de los rotation_blocks y el entangler map múltiples veces. Para este caso, el número de repeticiones determina el número de compuertas periódicas que necesitan ser cortadas. Dado que la sobrecarga de muestreo aumenta exponencialmente con el número de cortes (consulte el tutorial Corte de cables para la estimación de valores esperados para más detalles), fijaremos el número de repeticiones en 2 en este cuaderno.

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)

Salida de la celda de código anterior

Para verificar la calidad del resultado utilizando corte de circuitos, necesitamos conocer el resultado ideal. El circuito actual está más allá de la simulación clásica por fuerza bruta. Por lo tanto, fijamos los parámetros del circuito cuidadosamente para hacerlo Clifford.

Asignaremos el valor del parámetro 00 para las dos primeras capas de compuertas Rx, y el valor π\pi para la última capa. Esto asegura que el resultado ideal de este circuito sea 1n|1\rangle^{\otimes n}, siendo nn el número de qubits. Por lo tanto, los valores esperados de Zi\langle Z_i \rangle y ZiZi+1\langle Z_i Z_{i+1} \rangle, donde ii es el índice del qubit, son 1-1 y +1+1 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)

Seleccionar observables

Para cuantificar los beneficios del corte de compuertas, medimos los valores esperados de los observables 1ni=1nZi\frac{1}{n}\sum_{i=1}^n \langle Z_i \rangle y 1n1i=1n1ZiZi+1\frac{1}{n-1}\sum_{i=1}^{n-1} \langle Z_i Z_{i+1} \rangle. Como se discutió anteriormente, los valores esperados ideales son 1-1 y +1+1 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.coeffs

Paso 2: Optimizar el problema para la ejecución en hardware cuántico

  • Entrada: Circuito abstracto y observables
  • Salida: Circuito objetivo y observables producidos al cortar compuertas de largo alcance

Transpilar el circuito

Tenga en cuenta que el circuito puede transpilarse en esta etapa o después del corte. Si transpilamos después del corte, eso requerirá que transpilemos cada uno de los subexperimentos generados debido a la sobrecarga de muestreo. Por lo tanto, es más prudente transpilar en esta etapa para reducir la sobrecarga de la transpilación.

Sin embargo, si la transpilación se realiza en esta etapa con la conectividad nativa del hardware, el transpilador añadirá múltiples compuertas SWAP para colocar la operación periódica de 2 qubits, lo que oscurecería los beneficios del corte de circuitos. Para evitar este problema, podemos aprovechar que conocemos las compuertas exactas que necesitan ser cortadas. Específicamente, podemos crear un mapa de acoplamiento virtual añadiendo conexiones virtuales entre qubits distantes para acomodar estas compuertas periódicas de 2 qubits. Esto asegurará que el circuito pueda transpilarse en esta etapa sin incorporar las compuertas SWAP adicionales.

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)

Salida de la celda de código anterior

Cortar las conectividades periódicas de largo alcance

Ahora cortamos las compuertas en el circuito transpilado. Tenga en cuenta que las compuertas de 2 qubits que necesitan ser cortadas son las que conectan el último y el primer qubit del diseño.

# 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 el diseño del circuito transpilado al observable.

trans_observables = observables.apply_layout(virtual_mapped_circuit.layout)

Finalmente, los subexperimentos se generan muestreando sobre diferentes bases de medición y preparación.

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,
)

Tenga en cuenta que cortar las interacciones de largo alcance conduce a la ejecución de múltiples muestras del circuito que difieren en las bases de medición y preparación. Puede encontrar más información sobre esto en Constructing a virtual two-qubit gate by sampling single-qubit operations y Cutting circuits with multiple two-qubit unitaries.

El número de compuertas periódicas a cortar es igual al número de repeticiones de la capa TwoLocal, definido como num_reps anteriormente. La sobrecarga de muestreo del corte de compuertas es 6. Por lo tanto, el número total de subexperimentos será 6num_reps6^{num\_reps}.

print(f"Number of subexperiments is {len(subexperiments)} = 6**{num_reps}")
Number of subexperiments is 36 = 6**2

Transpilar los subexperimentos

En este punto, los subexperimentos contienen circuitos con algunas compuertas de 1 qubit que no están en el conjunto de compuertas base. Esto se debe a que los qubits cortados se miden en diferentes bases, y las compuertas de rotación utilizadas para esto no necesariamente pertenecen al conjunto de compuertas base. Por ejemplo, la medición en la base X implica aplicar una compuerta Hadamard antes de la medición habitual en la base Z. Pero Hadamard no forma parte del conjunto de compuertas base.

En lugar de aplicar todo el proceso de transpilación en cada uno de los circuitos de los subexperimentos, podemos utilizar pases de transpilación específicos. Consulte esta documentación para una descripción detallada de todos los pases de transpilación disponibles.

Aplicaremos BasisTranslator y luego Optimize1qGatesDecomposition para asegurar que todas las compuertas en estos circuitos pertenezcan al conjunto de compuertas base. Utilizar estos dos pases es más rápido que el proceso completo de transpilación, ya que otros pasos como el enrutamiento y la selección del diseño inicial no se realizan de nuevo.

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
]
)

Paso 3: Ejecutar utilizando primitivas de Qiskit

  • Entrada: Circuitos objetivo
  • Salida: Distribuciones de cuasi-probabilidad

Utilizamos una primitiva SamplerV2 para la ejecución de los circuitos cortados. Deshabilitamos el dynamical decoupling y el twirling para que cualquier mejora que obtengamos en el resultado se deba únicamente a la aplicación efectiva del corte de compuertas para este tipo de circuito.

options = SamplerOptions()
options.default_shots = 10000
options.dynamical_decoupling.enable = False
options.twirling.enable_gates = False
options.twirling.enable_measure = False

Ahora enviaremos los trabajos utilizando el modo por lotes.

with Batch(backend=backend) as batch:
sampler = SamplerV2(options=options)
cut_job = sampler.run(subexperiments)

print(f"Job ID {cut_job.job_id()}")
Job ID cwxf7wq60bqg008pvt8g
result = cut_job.result()

Paso 4: Post-procesar y devolver el resultado en el formato clásico deseado

  • Entrada: Distribuciones de cuasi-probabilidad
  • Salida: Valores de expectación reconstruidos
reconstructed_expvals = reconstruct_expectation_values(
result,
coefficients,
paulis,
)

Ahora calculamos el promedio de los observables de tipo Z de peso 1 y peso 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}")
Average of weight-1 expectation values is -0.741733944954063
Average of weight-2 expectation values is 0.6968862385320495

Verificación cruzada: Obtener el valor de expectación sin cortar

Es útil verificar de forma cruzada la ventaja de la técnica de corte de circuitos frente a la versión sin cortar. Aquí calcularemos los valores de expectación sin cortar el circuito. Tenga en cuenta que un circuito sin cortar sufrirá de una gran cantidad de compuertas SWAP necesarias para implementar la operación de 2 qubits entre el primer y el último qubit. Utilizaremos la función sampled_expectation_value para obtener los valores de expectación del circuito sin cortar después de obtener la distribución de probabilidad mediante SamplerV2. Esto permite un uso homogéneo de la primitiva en todas las instancias. Sin embargo, tenga en cuenta que también podríamos haber utilizado EstimatorV2 para calcular directamente los valores de expectación.

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}")
The job id for the uncut clifford circuit is cwxfads2ac5g008jhe7g
uncut_result = uncut_job.result()[0]
uncut_counts = uncut_result.data.meas.get_counts()

Ahora calcularemos los valores de expectación promedio de todos los observables de tipo Z de peso 1 y peso 2 sin cortar.

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}")
Average of weight-1 expectation values is -0.32494128440366965
Average of weight-2 expectation values is 0.32340917431192656

Visualización

Visualicemos ahora la mejora obtenida para los observables de peso 1 y peso 2 al utilizar el corte de compuertas para el circuito de cadena 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()

Salida de la celda de código anterior

Resumen

En resumen, calculamos los valores de expectación promedio de los observables de tipo Z de peso 1 y peso 2 para una cadena periódica 1D de 109 qubits. Para hacerlo:

  • Creamos un mapa de acoplamiento virtual añadiendo una conectividad de largo alcance entre el primer y el último qubit de la cadena 1D, y transpilamos el circuito.
    • La transpilación en esta etapa nos permitió evitar la sobrecarga de transpilar cada subexperimento por separado después del corte.
    • El uso del mapa de acoplamiento virtual nos permitió evitar compuertas SWAP adicionales para la operación de 2 qubits entre el primer y el último qubit.
  • Eliminamos la conectividad de largo alcance del circuito transpilado mediante el corte de compuertas.
  • Convertimos los circuitos cortados al conjunto de compuertas base aplicando los pases de transpilación apropiados.
  • Ejecutamos los circuitos cortados en un dispositivo IBM Quantum utilizando una primitiva SamplerV2.
  • Obtuvimos el valor de expectación reconstruyendo los resultados de los circuitos cortados.

Inferencia

Observamos a partir de los resultados que el promedio de los observables de tipo Z\langle Z \rangle de peso 1 y ZZ\langle ZZ \rangle de peso 2 mejora significativamente al cortar las compuertas periódicas. Tenga en cuenta que este estudio no incluye ninguna técnica de supresión o mitigación de errores. La mejora observada se debe únicamente al uso adecuado del corte de compuertas para este problema. Los resultados podrían haberse mejorado aún más utilizando técnicas de mitigación y supresión.

Este estudio muestra un ejemplo del uso efectivo del corte de compuertas para mejorar el rendimiento de la computación.

Encuesta del tutorial

Por favor, realice esta breve encuesta para proporcionar comentarios sobre este tutorial. Sus opiniones nos ayudarán a mejorar nuestro contenido y la experiencia del usuario.

Link to survey