Pular para o conteúdo principal

Integrar recursos quânticos externos com o Qiskit

O SDK do Qiskit foi desenvolvido para permitir que terceiros criem provedores externos de recursos quânticos.

Isso significa que qualquer organização que desenvolva ou implante recursos de computação quântica pode integrar seus serviços ao Qiskit e aproveitar sua base de usuários.

Para isso, é necessário criar um pacote que aceite requisições de recursos de computação quântica e os retorne ao usuário.

Além disso, o pacote deve permitir que os usuários enviem jobs e recuperem seus resultados por meio de uma implementação dos objetos qiskit.primitives.

Fornecendo acesso a backends

Para que os usuários possam transpilar e executar objetos QuantumCircuit usando recursos externos, é necessário instanciar um objeto contendo um Target, que fornece informações sobre as restrições de uma QPU, como sua conectividade, portas de base e número de qubits. Isso pode ser disponibilizado por meio de uma interface semelhante ao QiskitRuntimeService, através do qual o usuário pode fazer requisições para uma QPU. Esse objeto deve conter, no mínimo, um Target, mas uma abordagem mais simples seria retornar uma instância de BackendV2.

Um exemplo de implementação pode ser algo como:

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

Fornecendo uma interface para execução

Além de oferecer um serviço que retorna configurações de hardware, um serviço que fornece acesso a recursos externos de QPU também pode suportar a execução de cargas de trabalho quânticas. Essa capacidade pode ser exposta criando implementações das interfaces de primitivos do Qiskit; por exemplo, BasePrimitiveJob, BaseEstimatorV2 e BaseSamplerV2, entre outros. No mínimo, essas interfaces devem fornecer um método para execução, consulta do status do job e retorno dos resultados do job.

Para lidar com o status e os resultados dos jobs, o SDK do Qiskit fornece os objetos DataBin, PubResult, PrimitiveResult e BasePrimitiveJob, que devem ser utilizados.

Consulte a documentação da API do qiskit.primitives, bem como as implementações de referência BackendEstimatorV2 e BackendSampleV2 para mais informações.

Um exemplo de implementação do primitivo Estimator pode ser:

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

E um exemplo de implementação do primitivo Sampler pode ser:

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