Skip to main contentIBM Quantum Documentation

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 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 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 instance.

An example implementation may look something like:

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, BaseEstimatorV2 and 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, PubResult, PrimitiveResult, and BasePrimitiveJob objects should be used.

See the qiskit.primitives API documentation as well as the reference implementations BackendEstimatorV2 and BackendSampleV2 for more information.

An example implementation of the Estimator primitive may look like:

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:

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 
Was this page helpful?
Report a bug or request content on GitHub.