Перейти до основного вмісту

Інтеграція зовнішніх квантових ресурсів із Qiskit

Qiskit SDK створено з підтримкою третіх сторін, які хочуть стати зовнішніми провайдерами квантових ресурсів.

Це означає, що будь-яка організація, яка розробляє або розгортає квантові обчислювальні ресурси, може інтегрувати свої сервіси в Qiskit і отримати доступ до його користувацької бази.

Для цього потрібно створити пакет, який підтримує запити до квантових обчислювальних ресурсів і повертає їх користувачу.

Крім того, пакет має дозволяти користувачам відправляти завдання та отримувати їхні результати через реалізацію об'єктів qiskit.primitives.

Надання доступу до Backend

Щоб користувачі могли транспілювати та виконувати об'єкти QuantumCircuit з використанням зовнішніх ресурсів, їм потрібно створити екземпляр об'єкта, що містить Target, який надає інформацію про обмеження QPU: його з'єднання, базові вентилі та кількість кубітів. Це можна реалізувати через інтерфейс, подібний до QiskitRuntimeService, за допомогою якого користувач може робити запити до QPU. Цей об'єкт повинен містити щонайменше Target, але простішим підходом було б повертати екземпляр BackendV2.

Приклад реалізації може виглядати приблизно так:

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

Надання інтерфейсу для виконання

Окрім надання сервісу, що повертає конфігурації обладнання, сервіс із доступом до зовнішніх ресурсів QPU також може підтримувати виконання квантових робочих навантажень. Розкрити цю можливість можна шляхом створення реалізацій інтерфейсів примітивів Qiskit; наприклад, BasePrimitiveJob, BaseEstimatorV2 та BaseSamplerV2 та інших. Ці інтерфейси мають щонайменше надавати метод для виконання, запиту статусу завдання та повернення результатів завдання.

Для обробки статусу та результатів завдань Qiskit SDK надає об'єкти DataBin, PubResult, PrimitiveResult та BasePrimitiveJob, які слід використовувати.

Докладніше дивись у документації API qiskit.primitives, а також у референсних реалізаціях BackendEstimatorV2 та BackendSampleV2.

Приклад реалізації примітива 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

А реалізація примітива 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