128 lines
6.0 KiB
Plaintext
128 lines
6.0 KiB
Plaintext
---
|
|
title: Create a provider
|
|
description: A short guide on integrating Qiskit into an external provider's quantum resources.
|
|
---
|
|
# Integrate external quantum resources with Qiskit
|
|
|
|
The Qiskit SDK is built to support third parties in creating external providers of quantum resources.
|
|
|
|
This means that any organization which develops or deploys quantum compute resources can integrate their services into Qiskit and tap into its userbase.
|
|
|
|
Doing so requires creating a package which supports requests for quantum compute resources and returns them to the user.
|
|
|
|
Additionally, the package must allow users to submit jobs and retrieve their results through an implementation of the `qiskit.primitives` objects.
|
|
|
|
## Providing access to backends
|
|
In order for users to transpile and execute `QuantumCircuit` objects using external resources, they need to instantiate an object containing a [`Target`](../api/qiskit/qiskit.transpiler.Target) which provides information about a QPU's constraints such as its connectivity, basis gates, and number of qubits. This can be provided through an interface similar to the [`QiskitRuntimeService`](../api/qiskit-ibm-runtime/qiskit-runtime-service) through which a user can make requests for a QPU. This object should, at minimum, contain a `Target`, but a simpler approach would be to return a [`BackendV2`](../api/qiskit/qiskit.providers.BackendV2) instance.
|
|
|
|
An example implementation may look something like:
|
|
|
|
```python
|
|
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
|
|
|
|
```
|
|
|
|
## Providing an interface for execution
|
|
In addition to providing a service returning hardware configurations, a service providing access to external QPU resources also might also support the execution of quantum workloads. Exposing that capability can be done by creating implementations of the Qiskit primitives interfaces; for example the [`BasePrimitiveJob`](../api/qiskit/qiskit.primitives.BasePrimitiveJob), [`BaseEstimatorV2`](../api/qiskit/qiskit.primitives.BaseEstimatorV2) and [`BaseSamplerV2`](../api/qiskit/qiskit.primitives.BaseSamplerV2) among others. At minimum, these interfaces should be able to provide a method for execution, querying job status, and returning the job results.
|
|
|
|
To handle job status and results, the Qiskit SDK provides a [`DataBin`](../api/qiskit/qiskit.primitives.DataBin), [`PubResult`](../api/qiskit/qiskit.primitives.PubResult), [`PrimitiveResult`](../api/qiskit/qiskit.primitives.PrimitiveResult), and [`BasePrimitiveJob`](../api/qiskit/qiskit.primitives.BasePrimitiveJob) objects should be used.
|
|
|
|
See the `qiskit.primitives` [API documentation](../api/qiskit/primitives) as well as the reference implementations [`BackendEstimatorV2`](../api/qiskit/qiskit.primitives.BackendEstimatorV2) and [`BackendSampleV2`](../api/qiskit/qiskit.primitives.BackendSampler) for more information.
|
|
|
|
An example implementation of the Estimator primitive may look like:
|
|
|
|
```python
|
|
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
|
|
```
|
|
|
|
And an implementation of the Sampler primitive may look like:
|
|
|
|
```python
|
|
class SamplerImplentation(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
|
|
``` |