Expérience à l'échelle industrielle I
Tamiya Onodera (5 juillet 2024)
Télécharger le pdf de la conférence originale. Notez que certains extraits de code peuvent devenir obsolètes car il s'agit d'images statiques.
La durée approximative de cette expérience est de 45 secondes.
1. Introduction au document utilitaire
Dans cette leçon, nous faisons fonctionner un circuit à l'échelle de l'utilité qui apparaît dans ce que nous appelons officieusement "l'article sur l'utilité" publié dans Nature Vol 618, 15 juin 2023. L'article traite de l'évolution temporelle du modèle d'Ising à champ transverse 2D. En particulier, ils considèrent la dynamique temporelle de l'hamiltonien,
où est le couplage des spins les plus proches avec et est le champ transversal global.
Ils simulent la dynamique du spin à partir d'un état initial au moyen d'une décomposition de Trotter du premier ordre de l'opérateur d'évolution temporelle,
dans lequel le temps d'évolution est discrétisé en étapes de Trotter et et sont respectivement les portes de rotation et .
Ils ont réalisé des expériences sur un processeur Quantum® Eagle ( IBM ), qui est un dispositif de 127 qubits avec une connectivité heavy-hex, en appliquant les interactions à tous les qubits et les interactions à tous les bords de la carte de couplage. Notez que toutes les interactions du site ne peuvent pas être appliquées simultanément en raison de la "dépendance des données". C'est pourquoi ils colorent la carte de couplage pour les regrouper en couches. Ceux qui se trouvent dans une même couche se voient attribuer la même couleur et peuvent être appliqués en parallèle.
En outre, pour des raisons de simplicité expérimentale, ils se sont concentrés sur le cas .
La nouveauté de l'article réside dans le fait qu'ils ont construit des circuits quantiques à une échelle dépassant la simulation de vecteurs d'état, qu'ils les ont exécutés sur des ordinateurs quantiques bruyants et qu'ils sont parvenus à extraire des résultats fiables. En d'autres termes, ils ont démontré l'utilité des ordinateurs quantiques bruyants. Pour ce faire, ils ont appliqué l'extrapolation à bruit nul (ZNE) avec l'amplification probabiliste des erreurs (PEA) afin d'atténuer les erreurs provenant des dispositifs bruyants.
À partir de ce moment-là, nous avons appelé ces expériences et circuits "utility-scale".
1.1 Votre objectif
L'objectif de cette leçon est de construire un circuit utilitaire et de le faire fonctionner sur un processeur Eagle. L'extraction de résultats fiables dépasse le cadre de ce carnet, d'une part parce que la PEA est une fonctionnalité expérimentale de Qiskit au moment où nous écrivons ces lignes, et d'autre part parce que l'application des ZNE avec la PEA prendra beaucoup de temps.
Concrètement, il vous est demandé de construire et de faire fonctionner le circuit correspondant à la figure 4b du document, et de tracer les points "non atténués" de votre choix. Comme vous le voyez, il s'agit d'un circuit de 127 qubits à 60 couches (20 étapes de Trotter) avec le site comme observable.
Cela vous semble important? Ne vous inquiétez pas. Les trois dernières leçons de ce cours constituent des tremplins. Pour commencer, nous ferons la démonstration d'une expérience à petite échelle qui consiste à construire et à faire fonctionner sur un faux appareil un circuit de 27 qubits à 6 couches (2 étapes de Trotter) avec le site comme observable.
C'est tout pour l'introduction. Partons à l'aventure dans le domaine des services d'utilité publique!
import qiskit
qiskit.__version__Output:
'2.0.2'
#!pip install qiskit_ibm_runtime
#!pip install qiskit_aerimport 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 AerSimulatorservice = QiskitRuntimeService()2. Préparation
2.1 Construire RZZ(- /2)
Tout d'abord, il convient d'observer que la porte RZZ nécessite en général deux portes .
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:
Comme mentionné ci-dessus, nous nous concentrons sur la porte RZZ avec un angle spécifique, - / 2, pour cette expérience. Comme le montre l'article, il peut être réalisé avec une seule porte .
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:
Pour la suite, nous définirons une porte en fonction de ce circuit.
rzz = qc2.to_gate(label="RZZ")Faisons un usage aléatoire de la nouvelle définition 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:
Avant d'aller plus loin, vérifions l'équivalence logique de qc1 (la porte RZZ) pour -pi/2 et notre porte rzz ou qc2 nouvellement définie :
from qiskit.quantum_info import Operator
op1 = Operator(qc1.assign_parameters([-np.pi / 2]))
op2 = Operator(qc2)
op1.equiv(op2)Output:
True
2.2 Colorer la carte de couplage
Voyons comment nous colorons la carte de couplage d'un backend. Ceci est nécessaire pour regrouper les interactions de en couches.
Pour commencer, visualisons la carte de couplage d'un backend. Notez que les cartes de couplage sont fortement hexagonales pour tous les dispositifs Quantum actuels ( IBM ).
backend = service.least_busy(operational=True, simulator=False)
backend.coupling_map.draw()Output:
Pour colorer une carte de couplage, nous utilisons rustworkx, qui est un paquetage Python pour travailler avec des graphes et des réseaux complexes. Il fournit plusieurs algorithmes de coloration, qui sont tous heuristiques et ne garantissent donc pas de trouver une coloration minimale.
Cela dit, comme les graphes lourds-hex sont bipartis, nous prenons graph_bipartite_edge_color, qui devrait trouver une coloration minimale pour ces graphes.
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_mapLes graphes fortement hexagonaux doivent être peints en trois couleurs. Vérifions cela pour la carte de couplage ci-dessus.
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.
Oui, c'est le cas!
Pour le plaisir, peignons la carte de couplage sur la coloration obtenue, en utilisant la fonctionnalité de visualisation 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:
3. Résoudre l'évolution temporelle trottisée d'un modèle d'Ising à 2D.
Définissons une routine pour construire un circuit du papier utilitaire pour l'évolution temporelle d'un modèle d'Ising 2D. La routine prend trois paramètres, un backend, un entier indiquant le nombre de pas de Trotter, et un booléen contrôlant l'insertion de la barrière.
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 qcVeuillez noter que nous avons déjà effectué manuellement le mappage et le routage des qubits pour le circuit construit. Ainsi, lorsque nous transposons le circuit ultérieurement, nous ne demandons pas (ne devrions pas demander) au transpileur d'effectuer le mappage et le routage des qubits. Comme vous le verrez bientôt, nous l'invoquons avec un niveau d'optimisation de 1 et une méthode de mise en page "triviale".
Ensuite, nous définissons une routine simple qui permet d'obtenir les informations sur le circuit construit pour une vérification rapide.
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()])}"""
)Exerçons ces routines. Vous devriez voir un circuit de 27 qubits 15 couches (5 étapes de Trotter). Puisque le faux dispositif a 28 arêtes, il devrait y avoir 28*5 gages d'enchevêtrement.
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:
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. Résolvez la version à 27 qubits du problème.
Nous démontrons maintenant une version à plus petite échelle de l'expérience d'utilité. Nous construisons un circuit de 27 qubits à 6 couches (2 étapes de Trotter) avec comme observable, et nous l'exécutons à la fois sur AerSimulator et sur un faux dispositif.
Bien entendu, nous suivons notre flux de travail en quatre étapes, le "modèle Qiskit", qui consiste en un mappage, une optimisation, une exécution et un post-traitement. Plus concrètement,
- Faire correspondre des entrées classiques à un calcul quantique.
- Optimiser les circuits pour l'informatique quantique.
- Exécuter des circuits à l'aide de primitives.
- Post-traitement et retour des résultats dans un format classique.
Dans ce qui suit, nous présentons l'étape de Map pour créer un circuit pour une expérience à plus petite échelle. Nous disposons alors d'un ensemble d'optimisations et d'exécutions pour AerSimulator et d'un autre pour un faux appareil. Enfin, l'étape Post-Process permet de tracer les résultats.
4.1 Étape 1 : Cartographier
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 Étapes 2 et 3 : Optimisation et exécution (simulateur)
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 utilisateur a exécuté la cellule suivante en utilisant un MacBook Pro avec 2.3 GHz quad-core Intel Core i7 Processor équipé de 32GB 3LPDDR4X RAM, qui fonctionne sous macOS 14.5. Il a pris 161ms en temps masqué. Chaque ordinateur portable est légèrement différent.
%%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 Étapes 2 et 3 : Optimisation et exécution (appareil factice)
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
Lorsque le même utilisateur a exécuté la cellule suivante avec le même environnement que ci-dessus, il a fallu 2min 19s en Wall Time. L'exécution d'un circuit sur un faux dispositif invoque une simulation bruyante qui prend beaucoup plus de temps qu'une simulation exacte. Nous vous recommandons de ne pas exécuter un circuit plus important (tel qu'un circuit de 27 qubits 9 couches avec 3 étapes de Trotter) sur un faux appareil.
%%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 Étape 4 : Post-traitement
Nous présentons les résultats des simulations exactes et bruyantes. Vous voyez les effets graves du bruit sur 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:
5. Résolvez la version à 127 qubits du problème
Votre objectif est de réaliser l'expérience à l'échelle de l'utilité publique mentionnée au début. Vous allez créer et exécuter un circuit de 127 qubits et 60 couches (20 étapes de Trotter) avec comme observable. Nous vous recommandons d'essayer de le faire vous-même, en utilisant le code de la version à 27 qubits le cas échéant. Mais la solution est fournie ici.
La solution :
5.1 Étape 1 : Cartographier
# 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 Étapes 2 et 3 : Optimiser et exécuter
Nous constatons que la carte de couplage du processeur Eagle comporte 144 arêtes.
# 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 Post-traitement
Nous fournissons les valeurs des points "atténués" dans la figure 4b du document sur les services publics. Tracez-les avec vos résultats.
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:
Vos résultats sont-ils similaires aux résultats "non atténués" de la figure 4b? Elles peuvent être très différentes, en fonction de l'appareil et de son état au moment de l'expérience. Ne vous préoccupez pas des résultats eux-mêmes. Ce que nous vérifierons, c'est si vous avez effectué le codage correctement. Si c'est le cas, félicitations, vous avez atteint la ligne de départ de l'ère des services publics.
Comme dans l'article sur les services publics, les scientifiques du monde entier ont fait preuve d'une grande ingéniosité pour extraire des résultats significatifs même en présence de bruit. L'objectif final de cet effort collectif est l'avantage quantique : un état dans lequel les ordinateurs quantiques peuvent résoudre certains problèmes utiles à l'industrie plus rapidement, avec une plus grande fidélité ou à moindre coût que les ordinateurs classiques. Il ne s'agira probablement pas d'un événement unique, mais plutôt d'une ère au cours de laquelle la reproduction classique des résultats quantiques prend de plus en plus de temps, jusqu'à ce qu'à un moment donné, le délai quantique devienne d'une importance cruciale. Une chose est claire en ce qui concerne l'avantage quantique : nous n'y parviendrons qu'au moyen d'expériences à grande échelle. Si ce cours vous incite à participer à la quête, qui est pleine de défis et d'amusement, nous en serions très heureux.
Compte de référence
- Kim, Y., Eddins, A., Anand, S. et al. Preuve de l'utilité de l'informatique quantique pour la tolérance aux pannes. Nature 618, 500-505 (2023). https://doi.org/10.1038/s41586-023-06096-3