Przejdź do głównej treści

Integracja zewnętrznych zasobów kwantowych z Qiskit

Qiskit SDK zostało zaprojektowane z myślą o wspieraniu podmiotów zewnętrznych w tworzeniu providerów zasobów kwantowych.

Oznacza to, że każda organizacja rozwijająca lub wdrażająca kwantowe zasoby obliczeniowe może zintegrować swoje usługi z Qiskit i skorzystać z jego bazy użytkowników.

Wymaga to stworzenia pakietu, który obsługuje żądania dotyczące kwantowych zasobów obliczeniowych i zwraca je do użytkownika.

Ponadto pakiet musi umożliwiać użytkownikom wysyłanie zadań i pobieranie ich wyników poprzez implementację obiektów qiskit.primitives.

Udostępnianie dostępu do Backend

Aby użytkownicy mogli transpilować i wykonywać obiekty QuantumCircuit przy użyciu zewnętrznych zasobów, muszą tworzyć instancję obiektu zawierającego Target, który dostarcza informacji o ograniczeniach QPU, takich jak jego łączność, bramki bazowe i liczba qubitów. Można to zapewnić poprzez interfejs podobny do QiskitRuntimeService, za pomocą którego użytkownik może składać żądania dotyczące QPU. Obiekt ten powinien zawierać co najmniej Target, jednak prostszym podejściem byłoby zwrócenie instancji BackendV2.

Przykładowa implementacja może wyglądać mniej więcej tak:

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

Udostępnianie interfejsu do wykonywania obliczeń

Oprócz udostępniania usługi zwracającej konfiguracje sprzętowe, usługa zapewniająca dostęp do zewnętrznych zasobów QPU może również obsługiwać wykonywanie kwantowych zadań obliczeniowych. Udostępnienie tej funkcjonalności można osiągnąć poprzez tworzenie implementacji interfejsów prymitywów Qiskit; na przykład BasePrimitiveJob, BaseEstimatorV2 i BaseSamplerV2 oraz innych. Interfejsy te powinny co najmniej udostępniać metodę wykonania, odpytywania statusu zadania oraz zwracania wyników zadania.

Do obsługi statusu zadania i wyników Qiskit SDK udostępnia obiekty DataBin, PubResult, PrimitiveResult oraz BasePrimitiveJob, z których należy korzystać.

Więcej informacji znajdziesz w dokumentacji API qiskit.primitives, a także w referencyjnych implementacjach BackendEstimatorV2 i BackendSampleV2.

Przykładowa implementacja prymitywu Estimator może wyglądać tak:

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

A przykładowa implementacja prymitywu Sampler może wyglądać tak:

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