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

  1. Tout d’abord, vous créez un objet « devices » pour importer l’ordinateur targetquantique PASQAL, Fresnel. Fresnel QPU fournit des dispositions de piège prédéfinies que vous pouvez utiliser pour créer votre registre quantique. Pour configurer vos registres quantiques, vous organisez un tableau de qubits.

    from pulser_pasqal import PasqalCloud
    
    devices = PasqalCloud().fetch_available_devices()
    QPU = devices["FRESNEL"]
    
    # List all available calibrated register layouts
    for calibrated_register_layout in QPU.calibrated_register_layouts.keys():
        print(calibrated_register_layout)
    
  2. Ensuite, vous définissez la disposition de votre registre qubit. Dans cet exemple, vous utilisez une TriangularLatticeLayout(61, 5.0µm) disposition d’interruption.

    layout = QPU.calibrated_register_layouts[
        "TriangularLatticeLayout(61, 5.0µm)"
    ]
    layout.draw()
    

    L’image suivante montre l’affichage de la disposition choisie.

    Diagramme de la disposition choisie pour le registre qubit. La disposition affiche 60 points dans un tableau de 40 fois 30 micromètres.

  3. Ensuite, vous définissez le registre qubit en sélectionnant un ensemble d’interruptions dans la disposition. Dans cet exemple, la disposition comporte 60 pièges et vous sélectionnez 7 pièges à l’aide de leurs ID pour définir un registre quantique de 7 qubits.

    reg = layout.define_register(*[30, 21, 26, 35, 39, 34, 25])
    reg.draw()
    

    L’image suivante montre l’affichage final du registre qubit.

    Diagramme montrant le registre quantique final après avoir sélectionné 7 points dans la disposition de 60 points.

É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
    }
    

Explorer les fonctionnalités avancées de l’émulateur

L’émulateur DE PASQAL offre des fonctionnalités avancées qui ne sont pas encore prises en charge par Fresnel QPU. Vous pouvez organiser votre registre de manière personnalisée sans aucune limitation des dispositions pré-étalonnées. Par exemple, le code suivant crée un treillis carré 4x4 de qubits :

import numpy as np
from pulser import Register, Sequence

L = 4
square = np.array([[i, j] for i in range(L) for j in range(L)], dtype=float)
square -= np.mean(square, axis=0)
square *= 5

qubits = dict(enumerate(square))
custom_reg = Register(qubits)
custom_reg.draw()

seq = Sequence(custom_reg, QPU)

Tracé d’un treillis carré 4x4 avec 16 qubits.

Après avoir défini un registre personnalisé, vous pouvez suivre les mêmes étapes décrites dans la section précédente pour envoyer une séquence spécifiée sur notre émulateur.

Remarque

La fonctionnalité d’inscription personnalisée sera bientôt disponible sur FRESNEL.

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