2. qib Extending the Backend Interface

In this section we explain how someone could extend the backend interface of qib to support their Quantum Backend Architecture. First we explain how the current classes and relations are configured and what their role is, then we explicitly offer a how-to guide on what abstract classes should be implemented in order to support one’s custom backend.

Note: For a hands-on demonstration on how the backend module of qib can be used in order to execute circuits, one can check the WMI backend example, at 📂examples/backend/qib_backend_wmi.ipynb

2.1. The Current Configuration

The core components of qib’s backend module are represented by the following classes :

  1. QuantumProcessor (abstract): The actual representation of the given quantum processor. This class defines the functionality of interacting with the targeted quantum backend (usually over a networking request-response mechanism), in order to generate and submit experiment objects.

  2. Experiment (abstract): This component defines the behaviour of a quantum experiment performed on the given processor. It acts as both a data class and a behaviour class, defining how the experiment gets initialized, validated, controlled, and queried (i.e. how the results of the experiment are obtained).

  3. ExperimentResults (abstract): A purely data-class responsible for representing and interpreting quantum experiment results of an experiment already performed on the given processor.

  4. Options (abstract): The (configurable) options that the clients of qib are able to dynamically modify in order to run experiments in different configurations or with different properties. Each quantum processor defines its own available options.

  5. ProcessorConfiguration (abstract): The configuration of the QuantumProcessor. This class can also be extended on need, but it should more or less follow the same base principles. It represents the immutable properties of a quantum processor (such as qubits and gates configuration, if it is a simulator or not, etc.). These properties are hardcoded when defining the quantum processor, and thus should not (frequently) change.

Have a look at the architecture below for a more detailed understanding of the components and how they interconnect with each other:

qib Backend Module Architecture

2.2. How to Extend the Interface

In order to extend the currently available backend interface, one must:

2.2.1. Create Provider Sub-Module

Create a new python sub-module with the provider name:

  • Create a new folder under src/backend with the provider name (e.g. src/backend/myprov)

  • Create a __init__.py file within the folder, where to import all python implementations

📂src/backend/myprov/__init__.py

from qib.backend.myprov.myprov_options import MyProvOptions
from qib.backend.myprov.myprov_experiment import MyProvExperiment, MyProvExperimentResults

# Backend A
from qib.backend.myprov.myprov_backend_a import MyProvBackendA

# Backend B
from qib.backend.myprov.myprov_backend_b import MyProvBackendB

# ...

2.2.2. Implement the core abstract classes:

Implement qib.backend.Options

📂src/backend/myprov/myprov_options.py

from qib.backend.options import Options

class MyProvOptions(Options):
   def __init__(self,
                  option1: str = "default_value1", # required option
                  option2: str = None # optional option
                  # ...
               ):
      self.option1: str = option1
      self.option2: str = option2

   def optional(self) -> dict:
      optional: dict = {}
      if self.option2: optional['option2'] = self.option2
      return optional

Implement qib.backend.Experiment and qib.backend.ExperimentResults

📂src/backend/myprov/myprov_experiment.py

from qib.circuit import Circuit
from qib.backend.myprov import MyProvOptions
from qib.backend import ExperimentStatus, Experiment, ExperimentResults, ExperimentType

class MyProvExperiment(Experiment):
   def __init__(self, circuit: Circuit, options: MyProvOptions, exp_type: ExperimentType)
      self.circuit: Circuit = circuit
      self.options: MyProvOptions = options
      self.exp_type: ExperimentType = exp_type
      self._initialize()
      self._validate()

   def results(self) -> MyProvExperimentResults | None:
      # implement accordingly

   # ...


class MyProvExperimentResults(ExperimentResults):
   # ...

Note: Options or Experiment implementations might be also implemented separately for each backend (if this is necessary). Example: BackendAExperiment, BackendBExperiment, etc.

2.2.3. Implement Backend Processors

For each new quantum backend processor that one would like to implement within their provider module, implement qib.backend.Processor

📂src/backend/myprov/myprov_backend_a.py

from qib.circuit import Circuit
from qib.backend import QuantumProcessor, ProcessorConfiguration
from qib.backend.myprov import MyProvOptions, MyProvExperiment

class MyProvBackendA(QuantumProcessor):
    def __init__(self):
        # implement accordingly

    @staticmethod
    def configuration() -> ProcessorConfiguration:
        return ProcessorConfiguration(
            backend_name = 'BackendA'
            backend_version = 'v1.5.6'
            # ...
        )

    def submit_experiment(circuit: Circuit, options: MyProvOptions = MyProvOptions()) -> MyProvExperiment:
        # implement accordingly