Come inviare circuiti formattati specifici ad Azure Quantum
Informazioni su come usare il azure-quantum
Python pacchetto per inviare circuiti in formati specifici al servizio Azure Quantum. Questo articolo illustra come inviare circuiti nei formati seguenti:
Per altre informazioni, vedere Circuiti quantistici.
Prerequisiti
Per eseguire i circuiti in un notebook in portale di Azure, è necessario:
- Un account Azure con una sottoscrizione attiva. Se non si ha un account Azure, registrarsi gratuitamente e iscriversi per ottenere una sottoscrizione con pagamento in base al consumo.
- Un'area di lavoro di Azure Quantum. Per altre informazioni, vedere Creare un'area di lavoro di Azure Quantum.
Per sviluppare ed eseguire i circuiti in Visual Studio Code, è necessario anche:
Ambiente Python con Python e Pip installato.
VS Code con le estensioni Azure Quantum Development Kit, Pythone Jupyter installate.
Pacchetti ,
azure-quantum
eipykernel
di Azure Quantumqsharp
.python -m pip install --upgrade qsharp azure-quantum ipykernel
Creare un nuovo notebook di Jupyter
È possibile creare un notebook in VS Code o direttamente nel portale di Azure Quantum.
- Accedere al portale di Azure e selezionare l'area di lavoro creata nel passaggio precedente.
- A sinistra selezionare Notebook.
- Fare clic su Notebook personali e quindi su Aggiungi nuovo.
- In Kernel Type (Tipo di kernel) selezionare IPython.
- Digitare un nome per il file e fare clic su Crea file.
Quando si apre il nuovo notebook, viene creato automaticamente il codice per la prima cella, in base alle informazioni sulla sottoscrizione e sull'area di lavoro.
from azure.quantum import Workspace
workspace = Workspace (
resource_id = "", # Your resource_id
location = "" # Your workspace location (for example, "westus")
)
Inviare circuiti in formato QIR
La rappresentazione quantistica intermedia (QIR) è una rappresentazione intermedia che funge da interfaccia comune tra linguaggi/framework di programmazione quantistici e piattaforme di calcolo quantistico mirate. Per altre informazioni, vedere Rappresentazione intermedia quantistica.
Creare il circuito QIR. Ad esempio, il codice seguente crea un semplice circuito di entanglement.
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} """
Creare una
submit_qir_job
funzione helper per inviare il circuito QIR a un oggetto target. Si noti che i formati di dati di input e output vengono specificati rispettivamente comeqir.v1
emicrosoft.quantum-results.v1
.# 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
Selezionare un target e inviare il circuito QIR ad Azure Quantum. Ad esempio, per inviare il circuito QIR al simulatore 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]}
Inviare un circuito con un formato specifico del provider ad Azure Quantum
Oltre ai linguaggi QIR, ad esempio Q# o Qiskit, è possibile inviare circuiti quantistici in formati specifici del provider ad Azure Quantum. Ogni provider ha un proprio formato per rappresentare i circuiti quantistici.
Inviare un circuito a IonQ usando il formato JSON
Creare un circuito quantistico usando il formato JSON indipendente dal linguaggio supportato da IonQ targets, come descritto nella documentazione dell'API IonQ. Ad esempio, il campione seguente crea una sovrapposizione tra tre qubit:
circuit = { "qubits": 3, "circuit": [ { "gate": "h", "target": 0 }, { "gate": "cnot", "control": 0, "target": 1 }, { "gate": "cnot", "control": 0, "target": 2 }, ] }
Inviare il circuito a IonQ target. Nell'esempio seguente viene utilizzato il simulatore IonQ, che restituisce un oggetto
Job
.target = workspace.get_targets(name="ionq.simulator") job = target.submit(circuit)
Attendere il completamento del processo e quindi recuperare i risultati.
results = job.get_results() print(results)
..... {'duration': 8240356, 'histogram': {'0': 0.5, '7': 0.5}}
È quindi possibile visualizzare i risultati usando 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")
Prima di eseguire un processo nella QPU, è necessario stimare il costo dell'esecuzione.
Nota
Per i dettagli più aggiornati sui prezzi, vedere i prezzi di IonQ oppure trovare l'area di lavoro e visualizzare le opzioni relative ai prezzi nella scheda "Provider" dell'area di lavoro tramite: aka.ms/aq/myworkspaces.
Inviare un circuito a PASQAL usando Pulser SDK
Per inviare un circuito a PASQAL, è possibile usare Pulser SDK per creare sequenze di impulsi e inviarle a PASQAL target.
Installare Pulser SDK
Pulser è un framework per la composizione, la simulazione e l'esecuzione di sequenze di impulsi per dispositivi quantistici atom neutrali. È progettato da PASQAL come pass-through per inviare esperimenti quantistici ai processori quantistici. Per altre informazioni, vedere la documentazione di Pulser.
Per inviare le sequenze di impulsi, installare prima di tutto i pacchetti Pulser SDK:
try:
import pulser
except ImportError:
!pip -q install pulser
Creare un registro quantistico
È necessario definire sia un registro che un layout prima di procedere. Il registro specifica dove verranno disposti gli atomi, mentre il layout specifica il posizionamento delle trappole necessarie per catturare e strutturare questi atomi all'interno del registro.
Per informazioni dettagliate sui layout, vedere la documentazione di Pulser.
Prima di tutto, si crea un oggetto "devices" per importare il computer targetquantistico PASQAL, Fresnel.
from pulser_pasqal import PasqalCloud devices = PasqalCloud().fetch_available_devices() QPU = devices["FRESNEL"]
Layout pre-calibrati
Il dispositivo definisce un elenco di layout pre-calibrati. È possibile compilare la registrazione da uno di questi layout.
Questa è l'opzione consigliata perché migliorerà le prestazioni della QPU.
Opzione 1: Definire il registro usando layout pre-calibrati
Esaminare i layout disponibili in Fresnel e definire il registro da questo layout. Per altre informazioni su come eseguire questa operazione, vedere la documentazione di Pulser.
Esempio:
# 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()
Layout arbitrari
Se i layout pre-calibrati non soddisfano i requisiti dell'esperimento, è possibile creare un layout personalizzato.
Per qualsiasi registro arbitrario, una QPU neutra inserisce trappole in base al layout, che deve quindi subire la calibrazione. Poiché ogni calibrazione richiede tempo, è in genere consigliabile riutilizzare un layout calibrato esistente quando possibile
Opzione 2: derivare automaticamente un layout dal registro definito
Questa opzione consente la generazione automatica di un layout in base a un registro specificato. Tuttavia, per registri di grandi dimensioni, questo processo può produrre soluzioni sub-ottimali a causa di limitazioni nell'algoritmo usato per creare il layout.
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)
Opzione 3: Definire il registro usando un layout definito manualmente
- Creare un layout arbitrario con 20 trappole posizionate in modo casuale in un piano 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")
- Definire il registro con ID trap specifici
trap_ids = [4, 8, 19, 0] reg = layout.define_register(*trap_ids, qubit_ids=["a", "b", "c", "d"]) reg.draw()
Scrivere una sequenza di impulsi
Gli atomi neutri sono controllati con impulsi laser. Pulser SDK consente di creare sequenze di impulsi da applicare al registro quantistico.
Prima di tutto, definisci gli attributi della sequenza di impulsi dichiarando i canali che verranno usati per controllare gli atomi. Per creare un
Sequence
oggetto , è necessario fornire un'istanzaRegister
insieme al dispositivo in cui verrà eseguita la sequenza. Ad esempio, il codice seguente dichiara un canale:ch0
.Nota
È possibile usare il
QPU = devices["FRESNEL"]
dispositivo o importare un dispositivo virtuale da Pulser per una maggiore flessibilità. L'uso di unVirtualDevice
consente la creazione di sequenze meno vincolata dalle specifiche del dispositivo, rendendolo adatto per l'esecuzione in un emulatore. Per altre informazioni, vedere la documentazione di 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")
Aggiungere impulsi alla sequenza. A tale scopo, si creano e si aggiungono impulsi ai canali dichiarati. Ad esempio, il codice seguente crea un impulso e lo aggiunge al canale
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'immagine seguente mostra la sequenza di impulsi.
Convertire la sequenza in una stringa JSON
Per inviare le sequenze di impulsi, è necessario convertire gli oggetti Pulser in una stringa JSON che può essere usata come dati di input.
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
Inviare la sequenza di impulsi a PASQAL target
Prima di tutto, è necessario impostare i formati di dati di input e output appropriati. Ad esempio, il codice seguente imposta il formato dei dati di input su
pasqal.pulser.v1
e il formato dei dati di output supasqal.pulser-results.v1
.# 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
Nota
Il tempo necessario per eseguire un processo nella QPU dipende dai tempi correnti della coda. È possibile visualizzare il tempo medio della coda per un target oggetto selezionando il pannello Provider dell'area di lavoro.
Inviare il programma a PASQAL. Prima di inviare il codice all'hardware quantistico reale, è possibile testare il codice usando l'emulatore
pasqal.sim.emu-tn
come 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 }
Inviare un circuito a Quantinuum usando OpenQASM
Creare un circuito quantistico nella rappresentazione OpenQASM. Ad esempio, il campione seguente crea un circuito di teletrasporto:
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]; """
Facoltativamente, è possibile caricare il circuito da un file:
with open("my_teleport.qasm", "r") as f: circuit = f.read()
Inviare il circuito a Quantinuum target. L'esempio seguente usa il validator dell'API Quantinuum, che restituisce un oggetto
Job
.target = workspace.get_targets(name="quantinuum.sim.h1-1sc") job = target.submit(circuit, shots=500)
Attendere il completamento del processo e quindi recuperare i risultati.
results = job.get_results() print(results)
........ {'c0': ['000', '000', '000', '000', '000', '000', '000', ... ]}
È quindi possibile visualizzare i risultati usando Matplotlib.
import pylab as pl pl.hist(results["c0"]) pl.ylabel("Counts") pl.xlabel("Bitstring")
Osservando l'istogramma, è possibile notare che il generatore di numeri casuali ha restituito 0 ogni volta, che non è molto casuale. Ciò è dovuto al fatto che, il validator dell'API garantisce che il codice verrà eseguito correttamente nell'hardware Quantinuum, restituisce anche 0 per ogni misurazione quantistica. Per un vero generatore di numeri casuali, è necessario eseguire il circuito su hardware quantistico.
Prima di eseguire un processo nella QPU, è necessario stimare il costo dell'esecuzione.
Nota
Per i dettagli più aggiornati sui prezzi, vedere Prezzi di Azure Quantum o trovare l'area di lavoro e visualizzare le opzioni dei prezzi nella scheda "Provider" dell'area di lavoro tramite: aka.ms/aq/myworkspaces.
Inviare un circuito a Rigetti usando Quil
Il modo più semplice per inviare processi Quil consiste nell'usare il pacchetto pyquil-for-azure-quantum , in quanto consente di usare gli strumenti e la documentazione della libreria pyQuil . Senza questo pacchetto, pyQuil può essere usato per costruire programmi Quil, ma non per inviarli ad Azure Quantum.
È anche possibile costruire manualmente i programmi Quil e inviarli usando direttamente il azure-quantum
pacchetto.
Prima di tutto, caricare le importazioni necessarie.
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
Usare la
get_qvm
funzione oget_qpu
per ottenere una connessione alla QVM o alla QPU.qc = get_qvm() # For simulation # qc = get_qpu("Ankaa-9Q-3") for submitting to a QPU
Creare un programma Quil. Qualsiasi programma Quil valido viene accettato, ma il readout deve essere denominato
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"]
In questo caso,
data_per_shot
è unanumpy
matrice, in modo da poter usare inumpy
metodi.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
Stampa tutti i dati.
print("Data from 'ro' register:") for i, shot in enumerate(data_per_shot): print(f"Shot {i}: {shot}")
Importante
L'invio di più circuiti in un singolo processo non è attualmente supportato. Come soluzione alternativa è possibile chiamare il backend.run
metodo per inviare ogni circuito in modo asincrono, quindi recuperare i risultati di ogni processo. Ad esempio:
jobs = []
for circuit in circuits:
jobs.append(backend.run(circuit, shots=N))
results = []
for job in jobs:
results.append(job.result())