Vai al contenuto principale

Integrare risorse quantistiche esterne con Qiskit

L'SDK Qiskit è progettato per supportare le terze parti nella creazione di provider esterni di risorse quantistiche.

Ciò significa che qualsiasi organizzazione che sviluppa o distribuisce risorse di calcolo quantistico può integrare i propri servizi in Qiskit e accedere alla sua base di utenti.

Per farlo è necessario creare un pacchetto che gestisca le richieste di risorse di calcolo quantistico e le restituisca all'utente.

Inoltre, il pacchetto deve consentire agli utenti di inviare job e recuperare i risultati tramite un'implementazione degli oggetti qiskit.primitives.

Fornire l'accesso ai backend​

Affinché gli utenti possano transpilare ed eseguire oggetti QuantumCircuit usando risorse esterne, devono istanziare un oggetto contenente un Target che fornisce informazioni sui vincoli di un QPU, come la connettività, i gate di base e il numero di qubit. Questo può essere reso disponibile tramite un'interfaccia simile a QiskitRuntimeService, attraverso la quale un utente può fare richieste per un QPU. Questo oggetto dovrebbe contenere almeno un Target, ma un approccio più semplice sarebbe restituire un'istanza di BackendV2.

Un esempio di implementazione potrebbe avere questo aspetto:

from qiskit.transpiler import Target
from qiskit.providers import BackendV2

class ProviderService:
""" Class for interacting with a provider's service"""

def __init__(
self,
#Receive arguments for authentication/instantiation
):
""" Initiate a connection with the provider service, given some method
of authentication """

def return_target(name: Str) -> Target:
""" Interact with the service and return a Target object """
return target

def return_backend(name: Str) -> BackendV2:
""" Interact with the service and return a BackendV2 object """
return backend

Fornire un'interfaccia per l'esecuzione​

Oltre a mettere a disposizione un servizio che restituisce le configurazioni hardware, un servizio che fornisce accesso a risorse QPU esterne potrebbe anche supportare l'esecuzione di carichi di lavoro quantistici. È possibile esporre questa capacità creando implementazioni delle interfacce dei primitive Qiskit; ad esempio BasePrimitiveJob, BaseEstimatorV2 e BaseSamplerV2 tra gli altri. Come minimo, queste interfacce devono fornire un metodo per l'esecuzione, la verifica dello stato del job e la restituzione dei risultati del job.

Per gestire lo stato e i risultati dei job, l'SDK Qiskit mette a disposizione gli oggetti DataBin, PubResult, PrimitiveResult e BasePrimitiveJob.

Per ulteriori informazioni consulta la documentazione API di qiskit.primitives nonché le implementazioni di riferimento BackendEstimatorV2 e BackendSamplerV2.

Un esempio di implementazione del primitive Estimator potrebbe avere questo aspetto:

from qiskit.primitives import BaseEstimatorV2, BaseSamplerV2, EstimatorPubLike
from qiskit.primitives import DataBin, PubResult, PrimitiveResult, BasePrimitiveJob
from qiskit.providers import BackendV2

class EstimatorImplementation(BaseEstimatorV2):
""" Class for interacting with the provider's Estimator service """

def __init__(
self,
*,
backend: BackendV2,
options: dict
# Receive other arguments to instantiate an Estimator primitive with the service
):
self._backend = backend
self._options = options
self._default_precision = 0.01

@property
def backend(self) -> BackendV2:
""" Return the backend """
return self._backend

def run(
self, pubs: Iterable[EstimatorPubLike], *, precision: float | None = None
) -> BasePrimitiveJob[PrimitiveResult[PubResult]]:
""" Steps to implement:
1. Define a default precision if none is given
2. Validate pub format
3. Instantiate an object which inherits from BasePrimitiveJob
containing pub and runtime information
4. Send the job to the execution service of the provider
"""
job = BasePrimitiveJob(pubs, precision)
job_with_results = job.submit()
return job_with_results

E un'implementazione del primitive Sampler potrebbe avere questo aspetto:

class SamplerImplementation(BaseSamplerV2):
""" Class for interacting with the provider's Sampler service """

def __init__(
self,
*,
backend: BackendV2,
options: dict
# Receive other arguments to instantiate an Estimator primitive with the service
):
self._backend = backend
self._options = options
self._default_shots = 1024

@property
def backend(self) -> BackendV2:
""" Return the Sampler's backend """
return self._backend

def run(
self, pubs: Iterable[SamplerPubLike], *, shots: int | None = None
) -> BasePrimitiveJob[PrimitiveResult[SamplerPubResult]]:
""" Steps to implement:
1. Define a default number of shots if none is given
2. Validate pub format
3. Instantiate an object which inherits from BasePrimitiveJob
containing pub and runtime information
4. Send the job to the execution service of the provider
5. Return the data in some format
"""
job = BasePrimitiveJob(pubs, shots)
job_with_results = job.submit()
return job_with_results