Integrar recursos cuánticos externos con Qiskit
El SDK de Qiskit está diseñado para que terceros puedan crear proveedores externos de recursos cuánticos.
Esto significa que cualquier organización que desarrolle o implemente recursos de cómputo cuántico puede integrar sus servicios en Qiskit y aprovechar su base de usuarios.
Para hacerlo es necesario crear un paquete que gestione las solicitudes de recursos de cómputo cuántico y las devuelva al usuario.
Además, el paquete debe permitir que los usuarios envíen trabajos y recuperen sus resultados mediante una implementación de los objetos qiskit.primitives.
Proporcionar acceso a backends
Para que los usuarios puedan transpilar y ejecutar objetos QuantumCircuit usando recursos externos, necesitan instanciar un objeto que contenga un Target, el cual proporciona información sobre las restricciones de una QPU, como su conectividad, puertas base y número de qubits. Esto puede ofrecerse a través de una interfaz similar a QiskitRuntimeService, mediante la cual el usuario puede hacer solicitudes a una QPU. Este objeto debe contener, como mínimo, un Target; sin embargo, un enfoque más sencillo sería devolver una instancia de BackendV2.
Un ejemplo de implementación podría verse así:
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
Proporcionar una interfaz para la ejecución
Además de ofrecer un servicio que devuelva configuraciones de hardware, un servicio que brinde acceso a recursos de QPU externos también podría permitir la ejecución de cargas de trabajo cuánticas. Esta capacidad puede exponerse creando implementaciones de las interfaces de primitivas de Qiskit; por ejemplo, BasePrimitiveJob, BaseEstimatorV2 y BaseSamplerV2, entre otras. Como mínimo, estas interfaces deben proporcionar un método para la ejecución, la consulta del estado del trabajo y la devolución de los resultados.
Para gestionar el estado y los resultados de los trabajos, el SDK de Qiskit proporciona los objetos DataBin, PubResult, PrimitiveResult y BasePrimitiveJob, que deben utilizarse.
Consulta la documentación de la API de qiskit.primitives, así como las implementaciones de referencia BackendEstimatorV2 y BackendSampleV2 para más información.
Un ejemplo de implementación de la primitiva Estimator podría verse así:
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
Y una implementación de la primitiva Sampler podría verse así:
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