Partager via


Comment envoyer des circuits mis en forme spécifiques à Azure Quantum

Découvrez comment utiliser le azure-quantumPython 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-quantumet ipykernel 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.

  1. Connectez-vous au portail Azure et sélectionnez l’espace de travail de l’étape précédente.
  2. Dans le panneau de gauche, sélectionnez Notebooks.
  3. Cliquez sur Mes notebooks, puis sur Ajouter nouveau.
  4. Dans Type de noyau, sélectionnez IPython.
  5. 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.

  1. 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}
    """
    
  2. 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 comme qir.v1 et microsoft.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
    
  3. 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

  1. 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
            },
        ]
    }
    
  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)
    
  3. 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}}
    
  4. 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")
    

    Sortie du travail IonQ

  5. 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.

  1. 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 une Register 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’une VirtualDevice 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")
    
  2. 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. 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

  1. 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 format pasqal.pulser-results.v1de 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.

  2. 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

  1. 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()
    
  2. 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)
    
  3. 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',
    ...
    ]}
    
  4. 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")
    

    Sortie de travail Quantinuum

    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.

  5. 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.

  1. 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
    
  2. 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
    
  3. 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"]
    
  4. data_per_shot Voici un numpy tableau, ce qui vous permet d’utiliser des numpy 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
    
  5. 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())