Comment envoyer des circuits mis en forme spécifiques à Azure Quantum
Découvrez comment utiliser le azure-quantum
Python package pour envoyer des circuits dans des formats spécifiques au service Azure Quantum. Cet article vous montre comment envoyer des circuits dans les formats suivants :
Pour plus d’informations, consultez la page Circuits quantiques.
Prérequis
Pour exécuter vos circuits dans un notebook dans Portail Azure, vous avez besoin des éléments suivants :
- Un compte Azure avec un abonnement actif. Si vous n’avez pas de compte Azure, inscrivez-vous gratuitement et inscrivez-vous à un abonnement de paiement à l’utilisation.
- Un espace de travail Azure Quantum. Pour plus d’informations, consultez Créer un espace de travail Azure Quantum.
Pour développer et exécuter vos circuits dans Visual Studio Code, vous avez également besoin des éléments suivants :
Un Python environnement avec Python et Pip installé.
VS Code avec le Kit de développement Azure Quantum et Pythonles extensions Jupyter installées.
Azure Quantum
qsharp
,azure-quantum
etipykernel
packages.python -m pip install --upgrade qsharp azure-quantum ipykernel
Créer un notebook Jupyter Notebook
Vous pouvez créer un notebook dans VS Code ou directement dans le portail Azure Quantum.
- Connectez-vous au portail Azure et sélectionnez l’espace de travail de l’étape précédente.
- Dans le panneau de gauche, sélectionnez Notebooks.
- Cliquez sur Mes notebooks, puis sur Ajouter nouveau.
- Dans Type de noyau, sélectionnez IPython.
- Tapez un nom pour le fichier, puis cliquez sur Créer un fichier.
Lorsque votre nouveau notebook s’ouvre, il crée automatiquement le code de la première cellule, en fonction de vos informations d’abonnement et d’espace de travail.
from azure.quantum import Workspace
workspace = Workspace (
resource_id = "", # Your resource_id
location = "" # Your workspace location (for example, "westus")
)
Soumettre des circuits au format QIR
QIR (Quantum Intermediate Representation) est une représentation intermédiaire qui sert d’interface commune entre les langages/frameworks de programmation quantique et les plateformes de calcul quantique ciblées. Pour plus d’informations, consultez Quantum Intermediate Representation.
Créez le circuit QIR. Par exemple, le code suivant crée un circuit d’inanglement simple.
QIR_routine = """%Result = type opaque %Qubit = type opaque define void @ENTRYPOINT__main() #0 { call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 0 to %Qubit*)) call void @__quantum__qis__cx__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Qubit* inttoptr (i64 1 to %Qubit*)) call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 2 to %Qubit*)) call void @__quantum__qis__cz__body(%Qubit* inttoptr (i64 2 to %Qubit*), %Qubit* inttoptr (i64 0 to %Qubit*)) call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 2 to %Qubit*)) call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 3 to %Qubit*)) call void @__quantum__qis__cz__body(%Qubit* inttoptr (i64 3 to %Qubit*), %Qubit* inttoptr (i64 1 to %Qubit*)) call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 3 to %Qubit*)) call void @__quantum__qis__mz__body(%Qubit* inttoptr (i64 2 to %Qubit*), %Result* inttoptr (i64 0 to %Result*)) #1 call void @__quantum__qis__mz__body(%Qubit* inttoptr (i64 3 to %Qubit*), %Result* inttoptr (i64 1 to %Result*)) #1 call void @__quantum__rt__tuple_record_output(i64 2, i8* null) call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 0 to %Result*), i8* null) call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 1 to %Result*), i8* null) ret void } declare void @__quantum__qis__ccx__body(%Qubit*, %Qubit*, %Qubit*) declare void @__quantum__qis__cx__body(%Qubit*, %Qubit*) declare void @__quantum__qis__cy__body(%Qubit*, %Qubit*) declare void @__quantum__qis__cz__body(%Qubit*, %Qubit*) declare void @__quantum__qis__rx__body(double, %Qubit*) declare void @__quantum__qis__rxx__body(double, %Qubit*, %Qubit*) declare void @__quantum__qis__ry__body(double, %Qubit*) declare void @__quantum__qis__ryy__body(double, %Qubit*, %Qubit*) declare void @__quantum__qis__rz__body(double, %Qubit*) declare void @__quantum__qis__rzz__body(double, %Qubit*, %Qubit*) declare void @__quantum__qis__h__body(%Qubit*) declare void @__quantum__qis__s__body(%Qubit*) declare void @__quantum__qis__s__adj(%Qubit*) declare void @__quantum__qis__t__body(%Qubit*) declare void @__quantum__qis__t__adj(%Qubit*) declare void @__quantum__qis__x__body(%Qubit*) declare void @__quantum__qis__y__body(%Qubit*) declare void @__quantum__qis__z__body(%Qubit*) declare void @__quantum__qis__swap__body(%Qubit*, %Qubit*) declare void @__quantum__qis__mz__body(%Qubit*, %Result* writeonly) #1 declare void @__quantum__rt__result_record_output(%Result*, i8*) declare void @__quantum__rt__array_record_output(i64, i8*) declare void @__quantum__rt__tuple_record_output(i64, i8*) attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="base_profile" "required_num_qubits"="4" "required_num_results"="2" } attributes #1 = { "irreversible" } ; module flags !llvm.module.flags = !{!0, !1, !2, !3} !0 = !{i32 1, !"qir_major_version", i32 1} !1 = !{i32 7, !"qir_minor_version", i32 0} !2 = !{i32 1, !"dynamic_qubit_management", i1 false} !3 = !{i32 1, !"dynamic_result_management", i1 false} """
Créez une
submit_qir_job
fonction d’assistance pour envoyer le circuit QIR à un target. Notez que les formats de données d’entrée et de sortie sont spécifiés commeqir.v1
etmicrosoft.quantum-results.v1
, respectivement.# Submit the job with proper input and output data formats def submit_qir_job(target, input, name, count=100): job = target.submit( input_data=input, input_data_format="qir.v1", output_data_format="microsoft.quantum-results.v1", name=name, input_params = { "entryPoint": "ENTRYPOINT__main", "arguments": [], "count": count } ) print(f"Queued job: {job.id}") job.wait_until_completed() print(f"Job completed with state: {job.details.status}") #if job.details.status == "Succeeded": result = job.get_results() return result
Sélectionnez un target circuit QIR et envoyez le circuit QIR à Azure Quantum. Par exemple, pour soumettre le circuit QIR au simulateur targetIonQ :
target = workspace.get_targets(name="ionq.simulator") result = submit_qir_job(target, QIR_routine, "QIR routine") result
{'Histogram': ['(0, 0)', 0.5, '(1, 1)', 0.5]}
Envoyer un circuit avec un format spécifique au fournisseur à Azure Quantum
Outre les langages QIR, tels que Q# ou Qiskit, vous pouvez envoyer des circuits quantiques dans des formats spécifiques au fournisseur à Azure Quantum. Chaque fournisseur a son propre format pour représenter des circuits quantiques.
Envoyer un circuit à IonQ à l’aide du format JSON
Créez un circuit quantique à l’aide du format JSON indépendant du langage pris en charge par l’IonQtargets, comme décrit dans la documentation de l’API IonQ. Par exemple, l’exemple suivant crée une superposition entre trois qubits :
circuit = { "qubits": 3, "circuit": [ { "gate": "h", "target": 0 }, { "gate": "cnot", "control": 0, "target": 1 }, { "gate": "cnot", "control": 0, "target": 2 }, ] }
Envoyez le circuit à l’IonQ target. L’exemple suivant utilise le simulateur IonQ qui renvoie un objet
Job
.target = workspace.get_targets(name="ionq.simulator") job = target.submit(circuit)
Attendez que le travail soit terminé, puis extrayez les résultats.
results = job.get_results() print(results)
..... {'duration': 8240356, 'histogram': {'0': 0.5, '7': 0.5}}
Vous pouvez ensuite visualiser les résultats à l’aide de Matplotlib.
import pylab as pl pl.rcParams["font.size"] = 16 hist = {format(n, "03b"): 0 for n in range(8)} hist.update({format(int(k), "03b"): v for k, v in results["histogram"].items()}) pl.bar(hist.keys(), hist.values()) pl.ylabel("Probabilities")
Avant d’exécuter un travail sur le QPU, vous devez estimer le coût d’exécution d’un travail.
Remarque
Pour obtenir les dernières informations tarifaires, consultez Tarifs IonQ, ou recherchez votre espace de travail et consultez les options tarifaires sous l’onglet « Fournisseur » de votre espace de travail via : aka.ms/aq/myworkspaces.
Envoyer un circuit à PASQAL à l’aide du Kit de développement logiciel (SDK) Pulser
Pour soumettre un circuit à PASQAL, vous pouvez utiliser le SDK Pulser pour créer des séquences d’impulsions et les soumettre à la PASQAL target.
Installer le Kit de développement logiciel (SDK) Pulser
Pulser est une infrastructure pour la composition, la simulation et l’exécution de séquences d’impulsions pour les appareils quantiques neutres à atomes. Il est conçu par PASQAL comme pass-through pour soumettre des expériences quantiques à leurs processeurs quantiques. Pour plus d’informations, consultez la documentation Pulser.
Pour soumettre les séquences d’impulsions, installez d’abord les packages du Kit de développement logiciel (SDK) Pulser :
try:
import pulser
except ImportError:
!pip -q install pulser
Créer un registre quantique
Vous devez définir à la fois un registre et une disposition avant de continuer. Le registre spécifie où les atomes seront disposés, tandis que la disposition spécifie le positionnement des pièges nécessaires pour capturer et structurer ces atomes dans le registre.
Pour plus d’informations sur les dispositions, consultez la documentation Pulser.
Tout d’abord, vous créez un objet « devices » pour importer l’ordinateur targetquantique PASQAL, Fresnel.
from pulser_pasqal import PasqalCloud devices = PasqalCloud().fetch_available_devices() QPU = devices["FRESNEL"]
Dispositions pré-étalonnées
L’appareil définit une liste de dispositions pré-étalonnées. Vous pouvez créer votre registre à partir de l’une de ces dispositions.
Il s’agit de l’option recommandée, car elle améliore les performances du QPU.
Option 1 : Définir votre registre à l’aide de dispositions pré-étalonnées
Inspectez les dispositions disponibles sur Fresnel et définissez votre registre à partir de cette disposition. Consultez la documentation du pulsateur pour plus d’informations sur la façon de procéder.
Exemple :
# let's say we are interested in the first layout available on the device layout = QPU.pre_calibrated_layouts[0] # Select traps 1, 3 and 5 of the layout to define the register traps = [1,3,5] reg = layout.define_register(*traps) # You can draw the resulting register to verify it matches your expectations reg.draw()
Dispositions arbitraires
Si les dispositions pré-étalonnées ne répondent pas aux exigences de votre expérience, vous pouvez créer une disposition personnalisée.
Pour tout registre arbitraire donné, un QPU neutre-atome place des pièges en fonction de la disposition, qui doit ensuite subir un étalonnage. Étant donné que chaque étalonnage nécessite du temps, il est généralement conseillé de réutiliser une disposition étalonnée existante dans la mesure du possible.
Option 2 : dériver automatiquement une disposition de votre registre défini
Cette option permet la génération automatique d’une disposition basée sur un registre spécifié. Toutefois, pour les registres volumineux, ce processus peut générer des solutions sous-optimales en raison des limitations de l’algorithme utilisé pour créer la disposition.
qubits = { "q0": (0, 0), "q1": (0, 10), "q2": (8, 2), "q3": (1, 15), "q4": (-10, -3), "q5": (-8, 5), } reg = Register(qubits).with_automatic_layout(device)
Option 3 : Définir votre registre à l’aide d’une disposition définie manuellement
- Créer une disposition arbitraire avec 20 pièges positionnés de façon aléatoire dans un plan 2D
import numpy as np # Generating random coordinates np.random.seed(301122) # Keeps results consistent between runs traps = np.random.randint(0, 30, size=(20, 2)) traps = traps - np.mean(traps, axis=0) # Creating the layout layout = RegisterLayout(traps, slug="random_20")
- Définir votre registre avec des ID d’interruption spécifiques
trap_ids = [4, 8, 19, 0] reg = layout.define_register(*trap_ids, qubit_ids=["a", "b", "c", "d"]) reg.draw()
Écrire une séquence d’impulsions
Les atomes neutres sont contrôlés avec des impulsions laser. Le SDK Pulser vous permet de créer des séquences d’impulsions à appliquer au registre quantique.
Tout d’abord, vous définissez les attributs de séquence d’impulsions en déclarant les canaux qui seront utilisés pour contrôler les atomes. Pour créer un
Sequence
, vous devez fournir uneRegister
instance avec l’appareil sur lequel la séquence sera exécutée. Par exemple, le code suivant déclare un canal :ch0
.Remarque
Vous pouvez utiliser l’appareil
QPU = devices["FRESNEL"]
ou importer un appareil virtuel à partir de Pulser pour plus de flexibilité. L’utilisation d’uneVirtualDevice
fonctionnalité permet la création de séquences moins contrainte par les spécifications de l’appareil, ce qui le rend adapté à l’exécution sur un émulateur. Pour plus d’informations, consultez la documentation Pulser.from pulser import Sequence seq = Sequence(reg, QPU) # print the available channels for your sequence print(seq.available_channels) # Declare a channel. In this example we will be using `rydberg_global` seq.declare_channel("ch0", "rydberg_global")
Ajoutez des impulsions à votre séquence. Pour ce faire, vous créez et ajoutez des impulsions aux canaux que vous avez déclarés. Par exemple, le code suivant crée une impulsion et l’ajoute au canal
ch0
.from pulser import Pulse from pulser.waveforms import RampWaveform, BlackmanWaveform import numpy as np amp_wf = BlackmanWaveform(1000, np.pi) det_wf = RampWaveform(1000, -5, 5) pulse = Pulse(amp_wf, det_wf, 0) seq.add(pulse, "ch0") seq.draw()
L’image suivante montre la séquence d’impulsions.
Convertir la séquence en chaîne JSON
Pour soumettre les séquences d’impulsions, vous devez convertir les objets Pulser en une chaîne JSON qui peut être utilisée comme données d’entrée.
import json
# Convert the sequence to a JSON string
def prepare_input_data(seq):
input_data = {}
input_data["sequence_builder"] = json.loads(seq.to_abstract_repr())
to_send = json.dumps(input_data)
return to_send
Envoyer la séquence d’impulsions à PASQAL target
Tout d’abord, vous devez définir les formats de données d’entrée et de sortie appropriés. Par exemple, le code suivant définit le format de données d’entrée sur
pasqal.pulser.v1
et le formatpasqal.pulser-results.v1
de données de sortie sur .# Submit the job with proper input and output data formats def submit_job(target, seq, shots): job = target.submit( input_data=prepare_input_data(seq), # Take the JSON string previously defined as input data input_data_format="pasqal.pulser.v1", output_data_format="pasqal.pulser-results.v1", name="PASQAL sequence", shots=shots # Number of shots ) print(f"Queued job: {job.id}") return job
Remarque
Le temps nécessaire pour exécuter un travail sur le QPU dépend des heures de file d’attente actuelles. Vous pouvez afficher le temps moyen de file d’attente pour un en target sélectionnant le panneau Fournisseurs de votre espace de travail.
Soumettez le programme à PASQAL. Avant de soumettre votre code à du matériel quantique réel, vous pouvez tester votre code à l’aide de l’émulateur
pasqal.sim.emu-tn
en tant que target.target = workspace.get_targets(name="pasqal.sim.emu-tn") # Change to "pasqal.qpu.fresnel" to use Fresnel QPU job = submit_job(target, seq, 10) job.wait_until_completed() print(f"Job completed with state: {job.details.status}") result = job.get_results() print(result)
{ "1000000": 3, "0010000": 1, "0010101": 1 }
Envoyer un circuit à Quantinuum à l’aide d’OpenQASM
Créez un circuit quantique dans la représentation OpenQASM. L’exemple suivant crée un circuit de téléportation :
circuit = """OPENQASM 2.0; include "qelib1.inc"; qreg q[3]; creg c0[3]; h q[0]; cx q[0], q[1]; cx q[1], q[2]; measure q[0] -> c0[0]; measure q[1] -> c0[1]; measure q[2] -> c0[2]; """
Si vous le souhaitez, vous pouvez charger le circuit à partir d’un fichier :
with open("my_teleport.qasm", "r") as f: circuit = f.read()
Envoyez le circuit au Quantinuum target. L’exemple suivant utilise le validateur d’API Quantinuum qui retourne un objet
Job
.target = workspace.get_targets(name="quantinuum.sim.h1-1sc") job = target.submit(circuit, shots=500)
Attendez que le travail soit terminé, puis extrayez les résultats.
results = job.get_results() print(results)
........ {'c0': ['000', '000', '000', '000', '000', '000', '000', ... ]}
Vous pouvez ensuite visualiser les résultats à l’aide de Matplotlib.
import pylab as pl pl.hist(results["c0"]) pl.ylabel("Counts") pl.xlabel("Bitstring")
En examinant l’histogramme, vous pouvez remarquer que le générateur de nombres aléatoires a retourné 0 chaque fois, ce qui n’est pas très aléatoire. En effet, le validateur d’API garantit que votre code s’exécute correctement sur le matériel Quantinuum, mais retourne également 0 pour chaque mesure quantique. Pour un générateur de vrais nombres aléatoires, vous devez exécuter votre circuit sur du matériel quantique.
Avant d’exécuter un travail sur le QPU, vous devez estimer le coût d’exécution d’un travail.
Remarque
Pour obtenir les informations de tarification les plus actuelles, consultez la tarification Azure Quantum ou recherchez votre espace de travail et affichez les options de tarification sous l’onglet « Fournisseur » de votre espace de travail via : aka.ms/aq/myworkspaces.
Soumettre un circuit à Rigetti à l’aide de Quil
Le moyen le plus simple de soumettre des travaux Quil utilise le package pyquil-for-azure-quantum , car il vous permet d’utiliser les outils et la documentation de la bibliothèque pyQuil . Sans ce package, pyQuil peut être utilisé pour construire des programmes Quil, mais pas pour les soumettre à Azure Quantum.
Vous pouvez également construire manuellement des programmes Quil et les envoyer directement à l’aide du package azure-quantum
.
- Utiliser pyquil-for-azure-quantum
- Utiliser le Kit de développement logiciel (SDK) Azure-quantum Python
Tout d’abord, chargez les importations requises.
from pyquil.gates import CNOT, MEASURE, H from pyquil.quil import Program from pyquil.quilbase import Declare from pyquil_for_azure_quantum import get_qpu, get_qvm
Utilisez ou
get_qvm
get_qpu
la fonction pour obtenir une connexion à QVM ou QPU.qc = get_qvm() # For simulation # qc = get_qpu("Ankaa-9Q-3") for submitting to a QPU
Créez un programme Quil. Tout programme Quil valide est accepté, mais la lecture doit être nommée
ro
.program = Program( Declare("ro", "BIT", 2), H(0), CNOT(0, 1), MEASURE(0, ("ro", 0)), MEASURE(1, ("ro", 1)), ).wrap_in_numshots_loop(5) # Optionally pass to_native_gates=False to .compile() to skip the compilation stage result = qc.run(qc.compile(program)) data_per_shot = result.readout_data["ro"]
data_per_shot
Voici unnumpy
tableau, ce qui vous permet d’utiliser desnumpy
méthodes.assert data_per_shot.shape == (5, 2) ro_data_first_shot = data_per_shot[0] assert ro_data_first_shot[0] == 1 or ro_data_first_shot[0] == 0
Imprimez toutes les données.
print("Data from 'ro' register:") for i, shot in enumerate(data_per_shot): print(f"Shot {i}: {shot}")
Important
L’envoi de plusieurs circuits sur un seul travail n’est pas actuellement pris en charge. Une solution de contournement consiste à appeler la méthode backend.run
pour envoyer chaque circuit de manière asynchrone, puis à extraire les résultats de chaque travail. Par exemple :
jobs = []
for circuit in circuits:
jobs.append(backend.run(circuit, shots=N))
results = []
for job in jobs:
results.append(job.result())