Zum Hauptinhalt springen

Externe Quantenressourcen mit Qiskit integrieren

Das Qiskit SDK wurde entwickelt, um Drittanbieter bei der Erstellung externer Provider für Quantenressourcen zu unterstützen.

Dies bedeutet, dass jede Organisation, die Quantenrechenressourcen entwickelt oder bereitstellt, ihre Dienste in Qiskit integrieren und dessen Nutzerbasis erschließen kann.

Dazu ist es erforderlich, ein Paket zu erstellen, das Anfragen für Quantenrechenressourcen unterstützt und diese an den Benutzer zurückgibt.

Zusätzlich muss das Paket es Benutzern ermöglichen, Jobs zu übermitteln und deren Ergebnisse über eine Implementierung der qiskit.primitives-Objekte abzurufen.

Zugriff auf Backends bereitstellen

Damit Benutzer QuantumCircuit-Objekte unter Verwendung externer Ressourcen transpilieren und ausführen können, müssen sie ein Objekt instanziieren, das ein Target enthält, welches Informationen über die Einschränkungen einer QPU wie deren Konnektivität, Basis-Gates und Anzahl der Qubits bereitstellt. Dies kann über eine Schnittstelle bereitgestellt werden, die dem QiskitRuntimeService ähnelt, über die ein Benutzer Anfragen für eine QPU stellen kann. Dieses Objekt sollte mindestens ein Target enthalten, aber ein einfacherer Ansatz wäre die Rückgabe einer BackendV2-Instanz.

Eine Beispielimplementierung könnte wie folgt aussehen:

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

Eine Schnittstelle für die Ausführung bereitstellen

Zusätzlich zur Bereitstellung eines Service, der Hardware-Konfigurationen zurückgibt, kann ein Service, der Zugriff auf externe QPU-Ressourcen bietet, auch die Ausführung von Quanten-Workloads unterstützen. Diese Fähigkeit kann durch Implementierungen der Qiskit Primitives-Schnittstellen verfügbar gemacht werden; zum Beispiel BasePrimitiveJob, BaseEstimatorV2 und BaseSamplerV2 unter anderem. Diese Schnittstellen sollten mindestens eine Methode zur Ausführung, zur Abfrage des Job-Status und zur Rückgabe der Job-Ergebnisse bereitstellen können.

Zur Handhabung von Job-Status und -Ergebnissen stellt das Qiskit SDK die Objekte DataBin, PubResult, PrimitiveResult und BasePrimitiveJob bereit, die verwendet werden sollten.

Siehe die qiskit.primitives API-Dokumentation sowie die Referenzimplementierungen BackendEstimatorV2 und BackendSampleV2 für weitere Informationen.

Eine Beispielimplementierung des Estimator-Primitives könnte wie folgt aussehen:

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

Und eine Implementierung des Sampler-Primitives könnte wie folgt aussehen:

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