Vai al contenuto principale

Introduzione alle primitive

Versioni dei pacchetti

Il codice in questa pagina è stato sviluppato utilizzando i seguenti requisiti. Consigliamo di usare queste versioni o versioni più recenti.

qiskit[all]~=2.3.0
qiskit-ibm-runtime~=0.43.1
Nuovo modello di esecuzione, ora in versione beta

La versione beta di un nuovo modello di esecuzione è ora disponibile. Il modello di esecuzione diretta offre maggiore flessibilità nella personalizzazione del flusso di lavoro per la mitigazione degli errori. Consulta la guida sul Modello di esecuzione diretta per ulteriori informazioni.

Perché Qiskit ha introdotto le primitive?​

Analogamente ai primi tempi dei computer classici, quando gli sviluppatori dovevano manipolare direttamente i registri della CPU, la prima interfaccia per le QPU restituiva semplicemente i dati grezzi dall'elettronica di controllo. Questo non era un problema quando le QPU si trovavano nei laboratori e consentivano l'accesso diretto solo ai ricercatori. Riconoscendo che la maggior parte degli sviluppatori non dovrebbe né potrebbe familiarizzare con la decodifica di tali dati grezzi in 0 e 1, Qiskit ha introdotto backend.run, una prima astrazione per accedere alle QPU nel cloud. Ciò ha permesso agli sviluppatori di lavorare su un formato di dati familiare e di concentrarsi sul quadro generale.

Con la diffusione sempre più ampia dell'accesso alle QPU e lo sviluppo di nuovi algoritmi quantistici, è emersa nuovamente la necessità di un'astrazione di livello superiore. In risposta, Qiskit ha introdotto l'interfaccia delle primitive, ottimizzata per due compiti fondamentali nello sviluppo di algoritmi quantistici: la stima del valore di aspettazione (Estimator) e il campionamento di circuiti (Sampler). L'obiettivo è ancora una volta aiutare gli sviluppatori a concentrarsi maggiormente sull'innovazione e meno sulla conversione dei dati. L'interfaccia delle primitive sostituisce l'interfaccia backend.run, poiché Sampler fornisce lo stesso accesso diretto all'hardware offerto da backend.run.

Che cos'è una primitiva?​

I sistemi di elaborazione sono costruiti su più livelli di astrazione. Le astrazioni ti permettono di concentrarti su un particolare livello di dettaglio rilevante per il compito in questione. Più ti avvicini all'hardware, più basso è il livello di astrazione di cui hai bisogno (ad esempio, potrebbe essere necessario spostare o manipolare dati a livello di istruzione CPU). Più complesso è il compito che vuoi eseguire, più alto sarà il livello delle astrazioni (ad esempio, potresti usare una libreria di programmazione per eseguire calcoli algebrici).

In questo contesto, una primitiva è la più piccola istruzione di elaborazione, il blocco costruttivo più semplice da cui si può creare qualcosa di utile per un dato livello di astrazione.

I recenti progressi nel calcolo quantistico hanno aumentato la necessità di lavorare a livelli di astrazione più elevati. Man mano che il settore si sposta verso unità di elaborazione quantistica (QPU) più grandi e flussi di lavoro più complessi, il focus si sposta dall'interazione con i segnali dei singoli qubit alla considerazione dei dispositivi quantistici come sistemi che eseguono compiti necessari.

I due compiti più comuni per i computer quantistici sono il campionamento degli stati quantistici e il calcolo dei valori di aspettazione. Questi compiti hanno motivato la progettazione delle primitive Qiskit: Estimator e Sampler.

  • Estimator calcola i valori di aspettazione degli osservabili rispetto agli stati preparati dai circuiti quantistici.
  • Sampler campiona il registro di output dall'esecuzione di circuiti quantistici.

In sintesi, il modello computazionale introdotto dalle primitive Qiskit avvicina la programmazione quantistica di un passo a dove si trova oggi la programmazione classica, dove il focus è meno sui dettagli hardware e più sui risultati che si vogliono ottenere.

Definizione e implementazioni delle primitive​

Esistono due tipi di primitive Qiskit: le classi base e le loro implementazioni. Le primitive Qiskit sono definite da classi base primitive open source che risiedono nel Qiskit SDK (nel modulo qiskit.primitives). I provider (come Qiskit Runtime) possono utilizzare queste classi base per derivare le proprie implementazioni di Sampler e Estimator. La maggior parte degli utenti interagirà con le implementazioni dei provider, non con le primitive base.

Classi base​

BaseEstimatorV2 e BaseSamplerV2 — Classi base astratte che definiscono un'interfaccia comune per l'implementazione delle primitive. Tutte le altre classi nel modulo qiskit.primitives ereditano da queste classi base. Gli sviluppatori dovrebbero usarle se sono interessati a creare il proprio modello di esecuzione basato su primitive per un provider specifico. Queste classi potrebbero essere utili anche per chi vuole eseguire elaborazioni altamente personalizzate e trova che le implementazioni esistenti delle primitive siano troppo semplici per le proprie esigenze. Gli utenti generali non utilizzeranno direttamente le classi base.

Implementazioni​

Di seguito sono riportate le implementazioni delle classi base delle primitive:

  • Le primitive Qiskit Runtime (EstimatorV2 e SamplerV2) forniscono un'implementazione più sofisticata (ad esempio, includendo la mitigazione degli errori) come servizio basato su cloud. Questa implementazione delle primitive base viene utilizzata per accedere all'hardware IBM Quantum®. Si accede a esse tramite IBM Qiskit Runtime.

  • StatevectorEstimator e StatevectorSampler — Implementazioni di riferimento delle primitive che utilizzano il simulatore integrato in Qiskit. Sono costruite con il modulo Qiskit quantum_info, producendo risultati basati su simulazioni ideali del vettore di stato. Si accede a esse tramite Qiskit.

  • BackendEstimatorV2 e BackendSamplerV2 — Puoi usare queste classi per "avvolgere" qualsiasi risorsa di calcolo quantistico in una primitiva. Questo ti permette di scrivere codice in stile primitiva per provider che non dispongono ancora di un'interfaccia basata su primitive. Queste classi possono essere usate esattamente come Sampler e Estimator normali, tranne per il fatto che devono essere inizializzate con un argomento backend aggiuntivo per selezionare su quale computer quantistico eseguire. Si accede a esse tramite Qiskit.

Vantaggi delle primitive Qiskit​

Con le primitive, gli utenti Qiskit possono scrivere codice quantistico per una QPU specifica senza dover gestire esplicitamente ogni dettaglio. Inoltre, grazie al livello aggiuntivo di astrazione, potresti riuscire ad accedere più facilmente alle funzionalità hardware avanzate di un determinato provider. Ad esempio, con le primitive Qiskit Runtime, puoi sfruttare i più recenti progressi nella mitigazione e soppressione degli errori attivando opzioni come il resilience_level della primitiva, anziché costruire la tua implementazione di queste tecniche.

Per i provider hardware, implementare le primitive in modo nativo significa poter offrire ai propri utenti un modo più "pronto all'uso" per accedere alle funzionalità hardware, come le tecniche avanzate di post-elaborazione. È quindi più facile per i tuoi utenti beneficiare delle migliori capacità del tuo hardware.

Dettagli sulle primitive​

Come descritto in precedenza, tutte le primitive sono create a partire dalle classi base; di conseguenza, hanno la stessa struttura e modalità d'uso generale. Ad esempio, il formato dell'input per tutte le primitive Estimator è lo stesso. Tuttavia, esistono differenze nelle implementazioni che le rendono uniche.

nota

Poiché la maggior parte degli utenti accede alle primitive Qiskit Runtime, gli esempi nel resto di questa sezione sono basati sulle primitive Qiskit Runtime.

Estimator​

La primitiva Estimator calcola i valori di aspettazione per uno o più osservabili rispetto agli stati preparati dai circuiti quantistici. I circuiti possono essere parametrizzati, a patto che i valori dei parametri siano anch'essi forniti come input alla primitiva.

L'input è un array di PUB. Ogni PUB è nel formato:

(<circuito singolo>, <uno o più osservabili>, <uno o più valori di parametro opzionali>, <precisione opzionale>),

dove il campo opzionale parameter values può essere una lista o un singolo parametro. Le diverse implementazioni di Estimator supportano varie opzioni di configurazione. Se l'input contiene misurazioni, vengono ignorate.

L'output è un PubResult che contiene i valori di aspettazione calcolati per ciascuna coppia e i relativi errori standard, in forma di PubResult. Ogni PubResult contiene sia dati che metadati.

L'Estimator combina gli elementi degli osservabili e dei valori dei parametri seguendo le regole di broadcasting di NumPy, come descritto nell'argomento Input e output delle primitive.

Esempio:

# Added by doQumentation — required packages for this notebook
!pip install -q numpy qiskit qiskit-ibm-runtime
# This cell is hidden from users, it creates the circuits and observables to run

from qiskit_ibm_runtime import EstimatorV2, SamplerV2, QiskitRuntimeService
from qiskit.circuit.random import random_circuit
from qiskit.circuit import Parameter
from qiskit.quantum_info import SparsePauliOp
from qiskit.transpiler import generate_preset_pass_manager
import numpy as np

service = QiskitRuntimeService()
backend = service.least_busy()
phi = Parameter("phi")

circuit1 = random_circuit(10, 5, seed=12345)
circuit1.rzz(phi, 1, 2)
observable1 = SparsePauliOp.from_sparse_list(
[("ZXYZ", [1, 2, 3, 4], 1)], num_qubits=10
)
param_values1 = np.random.uniform(size=5).T

circuit2 = random_circuit(10, 5, seed=12345)
circuit2.rzz(phi, 1, 2)
observable2 = SparsePauliOp.from_sparse_list(
[("XZYX", [1, 2, 3, 4], 1)], num_qubits=10
)
param_values2 = np.random.uniform(size=5).T

shots1 = 164
shots2 = 1024

pm = generate_preset_pass_manager(optimization_level=1, backend=backend)
circuit1 = pm.run(circuit1)
circuit2 = pm.run(circuit2)
observable1 = observable1.apply_layout(circuit1.layout)
observable2 = observable2.apply_layout(circuit2.layout)
estimator = EstimatorV2(mode=backend)
estimator_job = estimator.run(
[
(circuit1, observable1, param_values1),
(circuit2, observable2, param_values2),
]
)

Sampler​

Il compito principale del Sampler è campionare il registro di output dall'esecuzione di uno o più circuiti quantistici. I circuiti di input possono essere parametrizzati, a patto che i valori dei parametri siano anch'essi forniti come input alla primitiva.

L'input è uno o più PUB, nel formato:

(<circuito singolo>, <uno o più valori di parametro opzionali>, <shot opzionali>),

dove possono esserci più elementi parameter values, e ciascun elemento può essere un array o un singolo parametro, a seconda del circuito scelto. Inoltre, l'input deve contenere misurazioni.

L'output è costituito da conteggi o misurazioni per singolo shot, come oggetti PubResult, senza pesi. La classe dei risultati, tuttavia, dispone di metodi per restituire campioni pesati, come i conteggi. Consulta Input e output delle primitive per tutti i dettagli.

Esempio:

# This cell is hidden from users, add measurement instructions to circuits
circuit1.measure_active()
circuit2.measure_active()
sampler = SamplerV2(mode=backend)
sampler_job = sampler.run(
[
(circuit1, param_values1, shots1),
(circuit2, param_values2, shots2),
]
)

Passi successivi​

Raccomandazioni