Intégrer des ressources quantiques externes avec Qiskit
Le SDK Qiskit est conçu pour permettre à des tiers de créer des fournisseurs externes de ressources quantiques.
Cela signifie que toute organisation qui développe ou déploie des ressources de calcul quantique peut intégrer ses services dans Qiskit et profiter de sa base d'utilisateurs.
Pour ce faire, il faut créer un package qui prend en charge les demandes de ressources de calcul quantique et les retourne à l'utilisateur.
De plus, le package doit permettre aux utilisateurs de soumettre des jobs et de récupérer leurs résultats via une implémentation des objets qiskit.primitives.
Fournir l'accès aux backends
Pour que les utilisateurs puissent transpiler et exécuter des objets QuantumCircuit à l'aide de ressources externes, ils doivent instancier un objet contenant un Target qui fournit des informations sur les contraintes d'un QPU, telles que sa connectivité, ses portes de base et son nombre de qubits. Cela peut être fourni via une interface similaire à QiskitRuntimeService, grâce à laquelle un utilisateur peut faire des demandes pour un QPU. Cet objet doit, au minimum, contenir un Target, mais une approche plus simple consiste à retourner une instance de BackendV2.
Voici à quoi pourrait ressembler un exemple d'implémentation :
from qiskit.transpiler import Target
from qsikit.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
Fournir une interface pour l'exécution
En plus de fournir un service retournant les configurations matérielles, un service donnant accès à des ressources QPU externes peut également prendre en charge l'exécution de charges de travail quantiques. Exposer cette capacité peut se faire en créant des implémentations des interfaces des primitives Qiskit ; par exemple BasePrimitiveJob, BaseEstimatorV2 et BaseSamplerV2, entre autres. Ces interfaces doivent au minimum fournir une méthode pour l'exécution, la consultation du statut du job et la récupération des résultats du job.
Pour gérer le statut et les résultats des jobs, le SDK Qiskit fournit les objets DataBin, PubResult, PrimitiveResult et BasePrimitiveJob à utiliser.
Consulte la documentation API de qiskit.primitives ainsi que les implémentations de référence BackendEstimatorV2 et BackendSampleV2 pour plus d'informations.
Voici à quoi pourrait ressembler un exemple d'implémentation de la primitive Estimator :
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
Et voici à quoi pourrait ressembler une implémentation de la primitive Sampler :
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