Source code for qiskit_quantuminspire.qi_backend

import logging
import math
from pprint import PrettyPrinter
from typing import Any, List, Union

from compute_api_client import BackendType
from qiskit.circuit import Instruction, Measure, QuantumCircuit
from qiskit.circuit.library import (
    CCXGate,
    CPhaseGate,
    CXGate,
    IGate,
    RXGate,
    RYGate,
    SdgGate,
    TdgGate,
    get_standard_gate_name_mapping,
)
from qiskit.circuit.parameter import Parameter
from qiskit.providers import BackendV2 as Backend
from qiskit.providers.options import Options
from qiskit.transpiler import CouplingMap, Target

from qiskit_quantuminspire.qi_jobs import QIJob
from qiskit_quantuminspire.utils import is_coupling_map_complete

# Used for parameterizing Qiskit gates in the gate mapping
_THETA = Parameter("ϴ")

# Custom gate mapping for gates whose name do not match between cQASM and Qiskit
_CQASM_QISKIT_GATE_MAPPING: dict[str, Instruction] = {
    "i": IGate(),
    "x90": RXGate(math.pi / 2),
    "mx90": RXGate(-math.pi / 2),
    "y90": RYGate(math.pi / 2),
    "my90": RYGate(-math.pi / 2),
    "toffoli": CCXGate(),
    "sdag": SdgGate(),
    "tdag": TdgGate(),
    "cr": CPhaseGate(_THETA),
    "cnot": CXGate(),
    "measure_z": Measure(),
}

_IGNORED_GATES: list[str] = [
    # Prep not viewed as separate gates in Qiskit
    "prep_x",
    "prep_y",
    "prep_z",
    # Measure x and y not natively supported https://github.com/Qiskit/qiskit/issues/3967
    "measure_x",
    "measure_y",
    "measure_all",
    # May be supportable through parameterized CPhaseGate.
    # For now, direct usage of CPhaseGate is required
    "crk",
]

_ALL_SUPPORTED_GATES: list[str] = list(get_standard_gate_name_mapping().keys()) + list(
    _CQASM_QISKIT_GATE_MAPPING.keys()
)


# Ignore type checking for QIBackend due to missing Qiskit type stubs,
# which causes the base class 'Backend' to be treated as 'Any'.
[docs] class QIBackend(Backend): # type: ignore[misc] """A wrapper class for QuantumInspire backendtypes to integrate with Qiskit's Backend interface.""" _max_shots: int def __init__(self, backend_type: BackendType, **kwargs: Any): super().__init__(name=backend_type.name, description=backend_type.description, **kwargs) self._id: int = backend_type.id self._max_shots: int = backend_type.max_number_of_shots # Construct options self._options = self._default_options() self.set_options(shots=backend_type.default_number_of_shots) if not backend_type.supports_raw_data: self._options.set_validator("memory", [False]) # Construct coupling map native_gates = [gate.lower() for gate in backend_type.gateset] available_gates = [gate for gate in native_gates if gate in _ALL_SUPPORTED_GATES] unknown_gates = set(native_gates) - set(_ALL_SUPPORTED_GATES) - set(_IGNORED_GATES) coupling_map = CouplingMap(backend_type.topology) coupling_map_complete = is_coupling_map_complete(coupling_map) if len(unknown_gates) > 0: logging.warning(f"Ignoring unknown native gate(s) {unknown_gates} for backend {backend_type.name}") if "toffoli" in available_gates and not coupling_map_complete: available_gates.remove("toffoli") logging.warning( f"Native toffoli gate in backend {backend_type.name} not supported for non-complete topology" ) self._target = Target().from_configuration( basis_gates=available_gates, num_qubits=backend_type.nqubits, coupling_map=None if coupling_map_complete else coupling_map, custom_name_mapping=_CQASM_QISKIT_GATE_MAPPING, ) def __repr_pretty__(self, p: PrettyPrinter) -> None: p.pprint(f"QIBackend(name={self.name}, id={self.id})") def __repr__(self) -> str: module_name = self.__class__.__module__ s = f"<{module_name}.{self.__class__.__name__} object at 0x{id(self):x} (name={self.name}, id={self.id})>" return s @classmethod def _default_options(cls) -> Options: """Only options defined here are supported by the backend. shots: int: Number of shots for the job. """ options = Options(shots=1024, seed_simulator=None, memory=False) # Seed_simulator is included in options to enable use of BackendEstimatorV2 in Qiskit, # but is not actually supported by the backend so any other value than none raises an error. options.set_validator("seed_simulator", [None]) options.set_validator("shots", int) options.set_validator("memory", bool) return options @property def target(self) -> Target: return self._target @property def max_shots(self) -> int: return self._max_shots @property def max_circuits(self) -> Union[int, None]: return None @property def id(self) -> int: return self._id
[docs] def run(self, run_input: Union[QuantumCircuit, List[QuantumCircuit]], **options: Any) -> QIJob: """Create and run a (batch)job on an QuantumInspire Backend. Args: run_input: A single or list of Qiskit QuantumCircuit objects or hybrid algorithms. Returns: QIJob: A reference to the batch job that was submitted. """ self.set_options(**options) job = QIJob(run_input=run_input, backend=self) job.submit() return job