Skip to main content
IBM Quantum Platform

Experimento a escala industrial I

Note

Tamiya Onodera (5 de julio de 2024)

Descargue el pdf de la conferencia original. Tenga en cuenta que algunos fragmentos de código pueden quedar obsoletos, ya que se trata de imágenes estáticas.

El tiempo aproximado de QPU para ejecutar este experimento es de 45 segundos.


1. Introducción al documento de utilidad

En esta lección, ejecutamos un circuito a escala de utilidad que aparece en lo que llamamos informalmente "el artículo de utilidad" publicado en Nature Vol 618, 15 de junio de 2023. El artículo trata de la evolución temporal del modelo de Ising de campo transversal 2D. En particular, consideran la dinámica temporal del hamiltoniano,

H=HZZ+HX=J(i,j)ZiZj+hiXiH = H_{ZZ} + H_X = - J \sum_{(i,j)} Z_i Z_j + h \sum_{i} X_i

en el que J>0J > 0 es el acoplamiento de los espines del vecino más cercano con i<ji < j y hh es el campo transversal global.

Simulan la dinámica del espín a partir de un estado inicial mediante la descomposición de Trotter de primer orden del operador tiempo-evolución,

exp(iHZZδt)=(i,j)exp(iJδtZiZj)=(i,j)RZiZj(2Jδt)exp(iHXδt)=iexp(ihδtXi)=iRXi(2hδt)\begin{aligned} \exp(-i H_{ZZ} \delta t) &= \prod_{(i,j)} \exp (i J \delta t Z_i Z_j) = \prod_{(i,j)} \mathrm{R}_{Z_i Z_j} ( - 2 J \delta t) \\ \exp(-i H_X \delta t) &= \prod_{i} \exp (-i h \delta t X_i ) = \prod_{i} \mathrm{R}_{X_i} ( 2 h \delta t) \end{aligned}

en el que el tiempo de evolución TT se discretiza en T/δtT / \delta t pasos de Trotter y RZiZj(θJ)\mathrm{R}_{Z_i Z_j}(\theta_J) y RXi(θh)\mathrm{R}_{X_i}(\theta_h) son ZZZZ y XX puertas de rotación, respectivamente.

Realizaron experimentos en un procesador IBM Quantum® Eagle, que es un dispositivo de 127 qubits con conectividad heavy-hex, aplicando interacciones XX a todos los qubits e interacciones ZZZZ para todos los bordes del mapa de acoplamiento. Tenga en cuenta que todas las interacciones de ZZZZ no pueden aplicarse simultáneamente debido a la "dependencia de los datos". De ahí que coloreen el mapa de acoplamiento para agruparlos en capas. A los de una capa se les asigna el mismo color y pueden aplicarse en paralelo.

Además, por simplicidad experimental, se centraron en el caso θJ=π/2\theta_J=-\pi /2.

La aportación novedosa del artículo es que construyeron circuitos cuánticos a una escala superior a la simulación de vectores de estado, los ejecutaron en ordenadores cuánticos ruidosos y consiguieron extraer resultados fiables. Es decir, demostraron la utilidad de los ordenadores cuánticos ruidosos. Para ello, aplicaron la extrapolación de ruido cero (ZNE) con amplificación probabilística de errores (PEA) para mitigar los errores de los dispositivos ruidosos.

A partir de entonces, llamamos a estos experimentos y circuitos "a escala de servicios públicos".

1.1 Tu objetivo

Tu objetivo en esta lección es construir un circuito a escala de servicios públicos y ejecutarlo en un procesador Eagle. Está fuera del alcance de este cuaderno extraer resultados fiables, en parte porque PEA es una característica experimental de Qiskit en el momento de escribir esto y en parte porque aplicar ZNE con PEA llevará bastante tiempo.

Concretamente, se le pide que construya y ponga en marcha el circuito correspondiente a la figura 4b del documento, y que trace los puntos "no mitigados" de los suyos. Como ves, es un circuito de 127-qubit ×\times 60-capas (20 pasos Trotter) con el Z62\langle Z_{62} \rangle como observable.

image.png

¿Te parece gran cosa? No se preocupe. Las tres últimas lecciones de este curso sirven de trampolín. Para empezar, demostraremos un experimento a menor escala que consiste en construir y ejecutar en un dispositivo falso un circuito de 27-qubit ×\times de 6 capas (2 pasos de Trotter) con el Z13\langle Z_{13} \rangle como observable.

Eso es todo en cuanto a la introducción. Emprendamos una aventura a escala comercial

import qiskit

qiskit.__version__

Output:

'2.0.2'
#!pip install qiskit_ibm_runtime
#!pip install qiskit_aer
import matplotlib.pyplot as plt
import numpy as np
import rustworkx as rx

from qiskit import QuantumCircuit, transpile
from qiskit.circuit import Parameter
from qiskit.circuit.library import YGate
from qiskit.quantum_info import SparsePauliOp
from qiskit_ibm_runtime import (
    QiskitRuntimeService,
    fake_provider,
    EstimatorV2 as Estimator,
)
from qiskit_aer import AerSimulator
service = QiskitRuntimeService()

2. Preparación

2.1 Construir RZZ(- π\pi /2)

En primer lugar, observe que la puerta RZZ requiere en general dos puertas CXCX.

from qiskit.circuit.library import RZZGate

θ_h = Parameter("$\\theta_h$")
qc1 = QuantumCircuit(2)
qc1.append(RZZGate(θ_h), [0, 1])
qc1.decompose(reps=1).draw("mpl")

Output:

Output of the previous code cell

Como se ha mencionado anteriormente, para este experimento nos centramos en la puerta RZZ con un ángulo específico, - π\pi / 2. Como se muestra en el artículo, puede realizarse con una sola puerta CXCX.

qc2 = QuantumCircuit(2)

qc2.sdg([0, 1])
qc2.append(YGate().power(1 / 2), [1])
qc2.cx(0, 1)
qc2.append(YGate().power(1 / 2).adjoint(), [1])

qc2.draw("mpl")

Output:

Output of the previous code cell

Definimos una puerta en términos de este circuito para futuras referencias.

rzz = qc2.to_gate(label="RZZ")

Hagamos un uso aleatorio de la recién definida rzz.

qc3 = QuantumCircuit(3)
qc3.append(rzz, [0, 1])
qc3.append(rzz, [0, 2])
display(qc3.draw("mpl"))
# display(qc.decompose(reps=1).draw("mpl"))

Output:

Output of the previous code cell

Antes de seguir utilizándola, verifiquemos la equivalencia lógica de qc1 (la puerta RZZ) para -pi/2 y nuestra recién definida puerta rzz o qc2 :

from qiskit.quantum_info import Operator

op1 = Operator(qc1.assign_parameters([-np.pi / 2]))
op2 = Operator(qc2)

op1.equiv(op2)

Output:

True

2.2 Colorea el mapa de acoplamiento

Estudiemos cómo coloreamos el mapa de acoplamiento de un backend. Esto es necesario para agrupar las interacciones de ZZZZ en capas.

Para empezar, visualicemos el mapa de acoplamiento de un backend. Obsérvese que los mapas de acoplamiento son hexagonales pesados para todos los dispositivos actuales de IBM Quantum.

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

backend.coupling_map.draw()

Output:

Output of the previous code cell

Para colorear un mapa de acoplamiento, utilizamos rustworkx, que es un paquete de Python para trabajar con grafos y redes complejas. Proporciona múltiples algoritmos de coloreado, que son todos heurísticos y, por tanto, no garantizan que se encuentre un coloreado mínimo.

Dicho esto, ya que los grafos pesados-hex son bipartitos, tomamos grafo_bipartito_borde_color, que debería encontrar una coloración mínima para estos grafos.

def color_coupling_map(backend):
    graph = backend.coupling_map.graph
    undirected_graph = graph.to_undirected(multigraph=False)
    edge_color_map = rx.graph_bipartite_edge_color(undirected_graph)
    if edge_color_map is None:
        edge_color_map = rx.graph_greedy_edge_color(undirected_graph)
    # build a map from color to a list of edges
    edge_index_map = undirected_graph.edge_index_map()
    color_edges_map = {color: [] for color in edge_color_map.values()}
    for edge_index, color in edge_color_map.items():
        color_edges_map[color].append(
            (edge_index_map[edge_index][0], edge_index_map[edge_index][1])
        )
    return edge_color_map, color_edges_map

Los gráficos hexagonales pesados deben pintarse en tres colores. Comprobémoslo para el mapa de acoplamiento anterior.

edge_color_map, color_edges_map = color_coupling_map(backend)
print(
    f"{backend.name}, {backend.num_qubits}-qubit device, {len(color_edges_map.keys())} colors assigned."
)

Output:

ibm_strasbourg, 127-qubit device, 3 colors assigned.

¡Sí que lo es!

Por diversión, pintemos el mapa de acoplamiento a la coloración obtenida, utilizando la característica de visualización de rustworks.

color_str_map = {0: "green", 1: "red", 2: "blue"}

undirected_graph = backend.coupling_map.graph.to_undirected(multigraph=False)
for i in undirected_graph.edge_indices():
    undirected_graph.get_edge_data_by_index(i)["color"] = color_str_map[
        edge_color_map[i]
    ]

rx.visualization.graphviz_draw(
    undirected_graph, method="neato", edge_attr_fn=lambda edge: {"color": edge["color"]}
)

Output:

Output of the previous code cell

3. Resolver la evolución temporal trotterizada de un modelo de Ising e 2D.

Definamos una rutina para construir un circuito del papel de utilidad para la evolución temporal de un modelo 2D Ising. La rutina toma tres parámetros, un backend, un entero que indica el número de pasos Trotter y un booleano que controla la inserción de la barrera.

def get_utility_circuit(backend, num_steps: int, barrier: bool = False):
    num_qubits = backend.num_qubits
    _, color_edges_map = color_coupling_map(backend)
    θ_h = Parameter("$\\theta_h$")
    qc = QuantumCircuit(num_qubits)

    for i in range(num_steps):
        qc.rx(θ_h, range(num_qubits))

        for _, edge_list in color_edges_map.items():
            for edge in edge_list:
                qc.append(rzz, edge)

        if barrier:
            qc.barrier()
    return qc

Tenga en cuenta que ya hemos realizado manualmente el mapeo y enrutamiento de qubits para el circuito construido. Por lo tanto, cuando transpilemos el circuito más tarde, no pediremos (ni deberíamos pedir) al transpilador que realice el mapeado y enrutado de qubits. Como verás enseguida, lo invocamos con el nivel de optimización como 1 y el método de diseño como "trivial".

A continuación definimos una rutina sencilla para obtener la información sobre el circuito construido para una comprobación rápida.

def get_circuit_info(qc: QuantumCircuit, reps: int = 0):
    qc0 = qc.decompose(reps=reps)
    return (
        f"{qc0.num_qubits} qubits × {qc0.depth(lambda x: x.operation.num_qubits == 2)} layers ({qc0.depth()}-depth)"
        + ",  "
        + f"""Gate breakdown: {", ".join([f"{k.upper()} {v}" for k, v in qc0.count_ops().items()])}"""
    )

Ejercitemos estas rutinas. Debería ver un circuito de 27 qubit ×\times 15 capas (5 pasos Trotter). Como el dispositivo falso tiene 28 aristas, debería haber 28*5 enredos.

backend = fake_provider.FakeTorontoV2()
num_steps = 5
qc = get_utility_circuit(backend, num_steps, True)

display(qc.draw(output="mpl", fold=-1))
print(get_circuit_info(qc, reps=0))
print(get_circuit_info(qc, reps=1))

Output:

Output of the previous code cell
27 qubits × 15 layers (20-depth),  Gate breakdown: CIRCUIT-165 140, RX 135, BARRIER 5
27 qubits × 15 layers (60-depth),  Gate breakdown: SDG 280, UNITARY 280, CX 140, R 135, BARRIER 5

4. Resuelve la versión de 27 qubits del problema.

Ahora demostramos una versión a menor escala del experimento de utilidad. Construimos un circuito de 27-qubit ×\times de 6 capas (2 pasos Trotter) con Z13\langle Z_{13} \rangle como observable, y lo ejecutamos tanto en AerSimulator como en un dispositivo falso.

Por supuesto, seguimos nuestro flujo de trabajo de cuatro pasos, "patrón Qiskit", que consiste en Mapear, Optimizar, Ejecutar y Postprocesar. Más concretamente,

  • Asignar entradas clásicas a un cálculo cuántico.
  • Optimizar circuitos para computación cuántica.
  • Ejecutar circuitos utilizando primitivas.
  • Post-proceso y devolución de resultados en formato clásico.

A continuación, el paso de Map para crear un circuito para un experimento a menor escala. A continuación, tenemos un conjunto de Optimizar y Ejecutar para AerSimulator y otro para un dispositivo falso. Por último, la etapa de postprocesamiento permite representar gráficamente los resultados.

4.1 Paso 1: Mapa

backend = fake_provider.FakeTorontoV2()  # a 27 qubit fake device.
num_steps = 2
qc = get_utility_circuit(backend, num_steps)
obs = SparsePauliOp.from_sparse_list(
    [("Z", [13], 1)], num_qubits=backend.num_qubits
)  # Falcon
angles = [
    0,
    0.1,
    0.2,
    0.3,
    0.4,
    0.5,
    0.6,
    0.7,
    0.8,
    1.0,
    np.pi / 2,
]  # We try 11 angles for theta_h.

4.2 Pasos 2 y 3: Optimizar y ejecutar (simulador)

backend_sim = AerSimulator()
transpiled_qc_sim = transpile(
    qc, backend_sim, optimization_level=1, layout_method="trivial"
)
transpiled_obs_sim = obs.apply_layout(layout=transpiled_qc_sim.layout)

print(get_circuit_info(qc, reps=1))
print(get_circuit_info(transpiled_qc_sim, reps=1))

Output:

27 qubits × 6 layers (23-depth),  Gate breakdown: SDG 112, UNITARY 112, CX 56, R 54
27 qubits × 6 layers (16-depth),  Gate breakdown: U3 80, CX 56, R 54, U1 32, U 28

Un usuario ejecutó la siguiente célula utilizando un MacBook Pro con procesador Intel Core i7 de cuatro núcleos a 2.3 GHz equipado con 32GB 3LPDDR4X RAM, que se ejecuta macOS 14.5. Tomó 161ms en tiempo de pared. Cada portátil será ligeramente diferente.

%%time
params = [[p] for p in angles]
estimator = Estimator(mode=backend_sim)
pub = (transpiled_qc_sim, transpiled_obs_sim, params)
result_sim = estimator.run([pub]).result()

Output:

CPU times: user 231 ms, sys: 186 ms, total: 417 ms
Wall time: 111 ms

4.3 Pasos 2 y 3: Optimizar y ejecutar (dispositivo falso)

backend_fake = fake_provider.FakeTorontoV2()
transpiled_qc_fake = transpile(
    qc, backend_fake, optimization_level=1, layout_method="trivial"
)
transpiled_obs_fake = obs.apply_layout(layout=transpiled_qc_fake.layout)

print(get_circuit_info(qc, reps=1))
print(get_circuit_info(transpiled_qc_fake, reps=1))

Output:

27 qubits × 6 layers (23-depth),  Gate breakdown: SDG 112, UNITARY 112, CX 56, R 54
27 qubits × 6 layers (49-depth),  Gate breakdown: SDG 324, U1 274, H 162, CX 56, U3 14

Cuando el mismo usuario ejecutó la siguiente celda con el mismo entorno que el anterior, tardó 2min 19s en Wall Time. Ejecutar un circuito en un dispositivo falso invoca la simulación ruidosa, que tarda mucho más que la simulación exacta. Le recomendamos que no ejecute un circuito más grande (como uno de 27 qubits ×\times 9 capas con 3 pasos Trotter) en un dispositivo falso.

%%time
params = [[p] for p in angles]
estimator = Estimator(mode=backend_fake)
pub = (transpiled_qc_fake, transpiled_obs_fake, params)
result_fake = estimator.run([pub]).result()

Output:

CPU times: user 4min 42s, sys: 9.35 s, total: 4min 51s
Wall time: 38.3 s

4.4 Paso 4: Postprocesamiento

Trazamos los resultados de simulaciones exactas y ruidosas. Puede ver los graves efectos del ruido en FakeToronto.

plt.plot(angles, result_fake[0].data.evs, "o", label="Fake Device")
plt.plot(angles, result_sim[0].data.evs, "o", label="AerSimulator")
plt.xlabel("$\\mathrm{R_x}$ angle $\\theta_h$")
plt.title("$\\langle Z_{13} \\rangle$")
plt.legend()
plt.show()

Output:

Output of the previous code cell

5. Resuelve la versión de 127 qubits del problema

Su objetivo es llevar a cabo el experimento a escala comercial mencionado al principio. Creará y ejecutará un circuito de 127 qubits y 60 capas (20 pasos Trotter) con Z62\langle Z_{62} \rangle como observable. Le recomendamos que intente hacerlo usted mismo, utilizando el código de la versión de 27 qubits cuando proceda. Pero la solución está aquí.

Solución:

5.1 Paso 1: Mapa

# backend_map = service.backend("ibm_brisbane")
backend_map = service.least_busy(operational=True, simulator=False)

num_steps = 20
qc = get_utility_circuit(backend_map, num_steps)
obs = SparsePauliOp.from_sparse_list(
    [("Z", [62], 1)], num_qubits=backend_map.num_qubits
)  # Eagle
angles = [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 1.0, np.pi / 2]

5.2 Pasos 2 y 3: Optimizar y ejecutar

Observamos que el mapa de acoplamiento del procesador Eagle tiene 144 aristas.

# backend = service.backend("ibm_brisbane")
backend = backend_map

transpiled_qc = transpile(qc, backend, optimization_level=1, layout_method="trivial")
transpiled_obs = obs.apply_layout(layout=transpiled_qc.layout)

print(get_circuit_info(qc, reps=1))
print(get_circuit_info(transpiled_qc))

Output:

156 qubits × 60 layers (221-depth),  Gate breakdown: SDG 7040, UNITARY 7040, CX 3520, R 3120
156 qubits × 60 layers (201-depth),  Gate breakdown: RZ 11933, SX 6240, CZ 3520
params = [[p] for p in angles]
estimator = Estimator(mode=backend)
pub = (transpiled_qc, transpiled_obs, params)
job = estimator.run([pub])

job_id = job.job_id()
print(f"job id={job_id}")

Output:

job id=d1479n6qf56g0081sxa0

5.3 Postprocesamiento

Proporcionamos los valores de los puntos "mitigados" en la figura 4b del documento de utilidad. Represéntalos junto con tus resultados.

result_paper = [
    1.0171,
    1.0044,
    0.9563,
    0.9602,
    0.8394,
    0.8120,
    0.5466,
    0.4556,
    0.1953,
    0.0141,
    0.0117,
]

# REPLACE WITH YOUR OWN JOB ID
job = service.job(job_id)

plt.plot(angles, job.result()[0].data.evs, "o", label=f"{job.backend().name}")
plt.plot(angles, result_paper, "o", label="Utility Paper")
plt.xlabel("$\\mathrm{R_x}$ angle $\\theta_h$")
plt.title("$\\langle Z_{62} \\rangle$")
plt.legend()
plt.show()

Output:

Output of the previous code cell

¿Son sus resultados similares a los "no mitigados" de la figura 4b? Podrían ser muy diferentes, dependiendo del aparato y de su estado en el momento del experimento. No se preocupe por los resultados en sí. Lo que comprobaremos es si ha realizado la codificación correctamente. Si lo ha hecho, enhorabuena, ha llegado a la línea de salida de la era de los servicios públicos.

Al igual que en el artículo de Utility, los científicos de todo el mundo han desplegado un enorme ingenio para extraer resultados significativos incluso en presencia de ruido. El objetivo final de este esfuerzo colectivo es la ventaja cuántica: un estado en el que los ordenadores cuánticos puedan resolver algunos problemas de utilidad en la industria más rápidamente, con mayor fidelidad o de forma más barata que los ordenadores clásicos. No es probable que se trate de un acontecimiento aislado, sino más bien de una era en la que la reproducción clásica de los resultados cuánticos tarda progresivamente más tiempo, hasta que en algún momento ese plazo cuántico adquiere una importancia crítica. Una cosa está clara sobre la ventaja cuántica: sólo llegaremos a ella a través de los experimentos a escala de utilidad. Si este curso hace que te unas a la búsqueda, que está llena de retos y diversión, estaremos más que contentos.


Referencia

¿Le ha resultado útil esta página?
Informe de un error, de una errata o solicite contenido en GitHub.