Skip to content

Backends API

This page documents the quantum computing backend implementations and utilities.

Backend Base Classes

BaseBackend

BaseBackend(device=None, shots=1024, **kwargs)

Bases: ABC

Abstract base class for quantum computing backends.

All backend implementations should inherit from this class and implement the required abstract methods.

Parameters:

Name Type Description Default
device str

Specific device/simulator to use

None
shots int

Number of measurement shots

1024
Source code in quantum_data_embedding_suite\backends\base.py
def __init__(self, device: Optional[str] = None, shots: int = 1024, **kwargs):
    # License validation for all backend classes
    validate_license_for_class(self.__class__)

    self.device = device
    self.shots = shots
    self.params = kwargs
    self._initialized = False

Attributes

name abstractmethod property

Get the backend name.

Functions

compute_expectation(circuit, observable) abstractmethod

Compute expectation value of an observable.

Parameters:

Name Type Description Default
circuit quantum circuit object

Backend-specific quantum circuit

required
observable observable object

Backend-specific observable

required

Returns:

Name Type Description
expectation float

Expectation value

Source code in quantum_data_embedding_suite\backends\base.py
@abstractmethod
def compute_expectation(
    self, 
    circuit: Any, 
    observable: Any
) -> float:
    """
    Compute expectation value of an observable.

    Parameters
    ----------
    circuit : quantum circuit object
        Backend-specific quantum circuit
    observable : observable object
        Backend-specific observable

    Returns
    -------
    expectation : float
        Expectation value
    """
    pass

compute_fidelity(circuit1, circuit2)

Compute fidelity between two quantum circuits.

Parameters:

Name Type Description Default
circuit1 quantum circuit objects

Backend-specific quantum circuits

required
circuit2 quantum circuit objects

Backend-specific quantum circuits

required

Returns:

Name Type Description
fidelity float

Fidelity between the two circuits

Source code in quantum_data_embedding_suite\backends\base.py
def compute_fidelity(self, circuit1: Any, circuit2: Any) -> float:
    """
    Compute fidelity between two quantum circuits.

    Parameters
    ----------
    circuit1, circuit2 : quantum circuit objects
        Backend-specific quantum circuits

    Returns
    -------
    fidelity : float
        Fidelity between the two circuits
    """
    self.ensure_initialized()

    # Get statevectors
    psi1 = self.get_statevector(circuit1)
    psi2 = self.get_statevector(circuit2)

    # Compute fidelity |⟨ψ1|ψ2⟩|²
    overlap = np.abs(np.vdot(psi1, psi2)) ** 2
    return float(overlap)

compute_trace_distance(circuit1, circuit2)

Compute trace distance between two quantum circuits.

Parameters:

Name Type Description Default
circuit1 quantum circuit objects

Backend-specific quantum circuits

required
circuit2 quantum circuit objects

Backend-specific quantum circuits

required

Returns:

Name Type Description
trace_distance float

Trace distance between the two circuits

Source code in quantum_data_embedding_suite\backends\base.py
def compute_trace_distance(self, circuit1: Any, circuit2: Any) -> float:
    """
    Compute trace distance between two quantum circuits.

    Parameters
    ----------
    circuit1, circuit2 : quantum circuit objects
        Backend-specific quantum circuits

    Returns
    -------
    trace_distance : float
        Trace distance between the two circuits
    """
    fidelity = self.compute_fidelity(circuit1, circuit2)
    return 1.0 - fidelity

create_observable(pauli_string, qubits) abstractmethod

Create an observable from a Pauli string.

Parameters:

Name Type Description Default
pauli_string str

Pauli string (e.g., 'XYZ', 'ZZI')

required
qubits list of int

Qubits to apply the observable to

required

Returns:

Name Type Description
observable backend-specific observable object
Source code in quantum_data_embedding_suite\backends\base.py
@abstractmethod
def create_observable(self, pauli_string: str, qubits: List[int]) -> Any:
    """
    Create an observable from a Pauli string.

    Parameters
    ----------
    pauli_string : str
        Pauli string (e.g., 'XYZ', 'ZZI')
    qubits : list of int
        Qubits to apply the observable to

    Returns
    -------
    observable : backend-specific observable object
    """
    pass

ensure_initialized()

Ensure the backend is initialized.

Source code in quantum_data_embedding_suite\backends\base.py
def ensure_initialized(self) -> None:
    """Ensure the backend is initialized."""
    if not self._initialized:
        self.initialize()
        self._initialized = True

execute_circuit(circuit, shots=None) abstractmethod

Execute a quantum circuit.

Parameters:

Name Type Description Default
circuit quantum circuit object

Backend-specific quantum circuit

required
shots int

Number of shots (overrides default if provided)

None

Returns:

Name Type Description
result dict

Execution results containing counts, probabilities, etc.

Source code in quantum_data_embedding_suite\backends\base.py
@abstractmethod
def execute_circuit(self, circuit: Any, shots: Optional[int] = None) -> Dict[str, Any]:
    """
    Execute a quantum circuit.

    Parameters
    ----------
    circuit : quantum circuit object
        Backend-specific quantum circuit
    shots : int, optional
        Number of shots (overrides default if provided)

    Returns
    -------
    result : dict
        Execution results containing counts, probabilities, etc.
    """
    pass

get_info()

Get information about the backend.

Returns:

Name Type Description
info dict

Dictionary containing backend information

Source code in quantum_data_embedding_suite\backends\base.py
def get_info(self) -> Dict[str, Any]:
    """
    Get information about the backend.

    Returns
    -------
    info : dict
        Dictionary containing backend information
    """
    return {
        "name": self.name,
        "device": self.device,
        "shots": self.shots,
        "initialized": self._initialized,
        "params": self.params,
    }

get_statevector(circuit) abstractmethod

Get the statevector from a quantum circuit.

Parameters:

Name Type Description Default
circuit quantum circuit object

Backend-specific quantum circuit

required

Returns:

Name Type Description
statevector ndarray

Complex amplitudes of the quantum state

Source code in quantum_data_embedding_suite\backends\base.py
@abstractmethod
def get_statevector(self, circuit: Any) -> np.ndarray:
    """
    Get the statevector from a quantum circuit.

    Parameters
    ----------
    circuit : quantum circuit object
        Backend-specific quantum circuit

    Returns
    -------
    statevector : ndarray
        Complex amplitudes of the quantum state
    """
    pass

initialize() abstractmethod

Initialize the backend.

Source code in quantum_data_embedding_suite\backends\base.py
@abstractmethod
def initialize(self) -> None:
    """Initialize the backend."""
    pass

Qiskit Backend

QiskitBackend

QiskitBackend(device=None, shots=1024, optimization_level=1, **kwargs)

Bases: BaseBackend

Qiskit backend for quantum circuit execution.

Supports various Qiskit simulators and real quantum devices.

Parameters:

Name Type Description Default
device str

Qiskit device name (e.g., 'qasm_simulator', 'statevector_simulator', 'ibmq_qasm_simulator')

None
shots int

Number of measurement shots

1024
optimization_level int

Qiskit transpiler optimization level (0-3)

1
**kwargs

Additional Qiskit-specific parameters

{}
Source code in quantum_data_embedding_suite\backends\qiskit_backend.py
def __init__(
    self, 
    device: Optional[str] = None, 
    shots: int = 1024,
    optimization_level: int = 1,
    **kwargs
):
    super().__init__(device=device, shots=shots, **kwargs)
    self.optimization_level = optimization_level
    self._backend = None
    self._provider = None

    # Default device
    if device is None:
        self.device = "qasm_simulator"

Attributes

name property

Get the backend name.

Functions

compute_expectation(circuit, observable)

Compute expectation value of an observable.

Parameters:

Name Type Description Default
circuit QuantumCircuit

Qiskit quantum circuit

required
observable SparsePauliOp or similar

Qiskit observable

required

Returns:

Name Type Description
expectation float

Expectation value

Source code in quantum_data_embedding_suite\backends\qiskit_backend.py
def compute_expectation(
    self, 
    circuit: Any, 
    observable: Any
) -> float:
    """
    Compute expectation value of an observable.

    Parameters
    ----------
    circuit : QuantumCircuit
        Qiskit quantum circuit
    observable : SparsePauliOp or similar
        Qiskit observable

    Returns
    -------
    expectation : float
        Expectation value
    """
    self.ensure_initialized()

    # Get statevector
    psi = self.get_statevector(circuit)

    # Compute expectation value ⟨ψ|O|ψ⟩
    if hasattr(observable, 'to_matrix'):
        obs_matrix = observable.to_matrix()
    else:
        obs_matrix = observable

    expectation = np.real(np.conj(psi) @ obs_matrix @ psi)
    return float(expectation)

create_observable(pauli_string, qubits)

Create a Qiskit observable from a Pauli string.

Parameters:

Name Type Description Default
pauli_string str

Pauli string (e.g., 'XYZ', 'ZZI')

required
qubits list of int

Qubits to apply the observable to

required

Returns:

Name Type Description
observable SparsePauliOp

Qiskit observable object

Source code in quantum_data_embedding_suite\backends\qiskit_backend.py
def create_observable(self, pauli_string: str, qubits: List[int]) -> Any:
    """
    Create a Qiskit observable from a Pauli string.

    Parameters
    ----------
    pauli_string : str
        Pauli string (e.g., 'XYZ', 'ZZI')
    qubits : list of int
        Qubits to apply the observable to

    Returns
    -------
    observable : SparsePauliOp
        Qiskit observable object
    """
    try:
        from qiskit.quantum_info import SparsePauliOp
    except ImportError:
        raise ImportError("qiskit.quantum_info is required for observables")

    # Create Pauli string for specific qubits
    n_qubits = max(qubits) + 1 if qubits else len(pauli_string)
    full_pauli = ['I'] * n_qubits

    for i, pauli in enumerate(pauli_string):
        if i < len(qubits):
            full_pauli[qubits[i]] = pauli

    pauli_str = ''.join(full_pauli)
    return SparsePauliOp.from_list([(pauli_str, 1.0)])

execute_circuit(circuit, shots=None)

Execute a quantum circuit using Qiskit.

Parameters:

Name Type Description Default
circuit QuantumCircuit

Qiskit quantum circuit

required
shots int

Number of shots (overrides default if provided)

None

Returns:

Name Type Description
result dict

Execution results containing counts, probabilities, etc.

Source code in quantum_data_embedding_suite\backends\qiskit_backend.py
def execute_circuit(
    self, 
    circuit: Any, 
    shots: Optional[int] = None
) -> Dict[str, Any]:
    """
    Execute a quantum circuit using Qiskit.

    Parameters
    ----------
    circuit : QuantumCircuit
        Qiskit quantum circuit
    shots : int, optional
        Number of shots (overrides default if provided)

    Returns
    -------
    result : dict
        Execution results containing counts, probabilities, etc.
    """
    self.ensure_initialized()

    from qiskit import transpile, execute

    # Use provided shots or default
    exec_shots = shots if shots is not None else self.shots

    # Add measurements if not present (for sampling-based backends)
    if not hasattr(circuit, 'cregs') or len(circuit.cregs) == 0:
        from qiskit import ClassicalRegister
        if self.device != "statevector_simulator":
            circuit.add_register(ClassicalRegister(circuit.num_qubits))
            circuit.measure_all()

    # Transpile circuit
    transpiled = transpile(
        circuit, 
        backend=self._backend,
        optimization_level=self.optimization_level
    )

    # Execute circuit
    job = execute(transpiled, self._backend, shots=exec_shots)
    result = job.result()

    # Extract results
    output = {}

    if hasattr(result, 'get_counts'):
        try:
            counts = result.get_counts()
            output['counts'] = counts

            # Convert to probabilities
            total_shots = sum(counts.values())
            probs = {state: count/total_shots for state, count in counts.items()}
            output['probabilities'] = probs
        except:
            pass

    if hasattr(result, 'get_statevector'):
        try:
            statevector = result.get_statevector()
            output['statevector'] = np.array(statevector.data)
        except:
            pass

    return output

get_device_properties()

Get properties of the current device.

Returns:

Name Type Description
properties dict

Device properties and capabilities

Source code in quantum_data_embedding_suite\backends\qiskit_backend.py
def get_device_properties(self) -> Dict[str, Any]:
    """
    Get properties of the current device.

    Returns
    -------
    properties : dict
        Device properties and capabilities
    """
    self.ensure_initialized()

    props = {
        "name": self.device,
        "type": "simulator" if "simulator" in self.device else "hardware",
        "shots_supported": True,
        "statevector_supported": "statevector" in self.device,
    }

    if hasattr(self._backend, 'configuration'):
        config = self._backend.configuration()
        props.update({
            "n_qubits": getattr(config, 'n_qubits', None),
            "coupling_map": getattr(config, 'coupling_map', None),
            "basis_gates": getattr(config, 'basis_gates', None),
        })

    return props

get_statevector(circuit)

Get the statevector from a quantum circuit.

Parameters:

Name Type Description Default
circuit QuantumCircuit

Qiskit quantum circuit

required

Returns:

Name Type Description
statevector ndarray

Complex amplitudes of the quantum state

Source code in quantum_data_embedding_suite\backends\qiskit_backend.py
def get_statevector(self, circuit: Any) -> np.ndarray:
    """
    Get the statevector from a quantum circuit.

    Parameters
    ----------
    circuit : QuantumCircuit
        Qiskit quantum circuit

    Returns
    -------
    statevector : ndarray
        Complex amplitudes of the quantum state
    """
    self.ensure_initialized()

    from qiskit import Aer, transpile, execute

    # Use statevector simulator
    sv_backend = Aer.get_backend('statevector_simulator')

    # Remove any measurements
    circuit_copy = circuit.copy()
    circuit_copy.remove_final_measurements(inplace=True)

    # Transpile and execute
    transpiled = transpile(circuit_copy, backend=sv_backend)
    job = execute(transpiled, sv_backend)
    result = job.result()

    # Get statevector
    statevector = result.get_statevector()
    return np.array(statevector.data)

initialize()

Initialize the Qiskit backend.

Source code in quantum_data_embedding_suite\backends\qiskit_backend.py
def initialize(self) -> None:
    """Initialize the Qiskit backend."""
    try:
        import qiskit
        from qiskit import Aer
        from qiskit.providers.aer import AerSimulator
    except ImportError:
        raise ImportError("Qiskit is required for QiskitBackend")

    # Handle different device types
    if self.device in ["qasm_simulator", "statevector_simulator", "unitary_simulator"]:
        # Use Aer simulators
        self._backend = Aer.get_backend(self.device)
    elif self.device.startswith("aer_"):
        # AerSimulator with specific method
        method = self.device.replace("aer_", "")
        self._backend = AerSimulator(method=method)
    elif self.device.startswith("ibmq_") or self.device.startswith("ibm_"):
        # IBM Quantum device
        self._initialize_ibm_backend()
    else:
        # Try to find the device in available backends
        try:
            self._backend = Aer.get_backend(self.device)
        except Exception:
            # Fallback to default simulator
            print(f"Warning: Device '{self.device}' not found, using qasm_simulator")
            self._backend = Aer.get_backend("qasm_simulator")
            self.device = "qasm_simulator"

PennyLane Backend

PennyLaneBackend

PennyLaneBackend(device=None, shots=1024, **kwargs)

Bases: BaseBackend

PennyLane backend for quantum circuit execution.

Supports various PennyLane devices including simulators and hardware.

Parameters:

Name Type Description Default
device str

PennyLane device name (e.g., 'default.qubit', 'qiskit.aer', 'cirq.simulator')

None
shots int

Number of measurement shots

1024
**kwargs

Additional PennyLane-specific parameters

{}
Source code in quantum_data_embedding_suite\backends\pennylane_backend.py
def __init__(
    self, 
    device: Optional[str] = None, 
    shots: int = 1024,
    **kwargs
):
    super().__init__(device=device, shots=shots, **kwargs)
    self._device = None
    self._qnode_cache = {}

    # Default device
    if device is None:
        self.device = "default.qubit"

Attributes

name property

Get the backend name.

Functions

compute_expectation(circuit, observable)

Compute expectation value of an observable.

Parameters:

Name Type Description Default
circuit callable

PennyLane quantum function

required
observable Hamiltonian or Observable

PennyLane observable

required

Returns:

Name Type Description
expectation float

Expectation value

Source code in quantum_data_embedding_suite\backends\pennylane_backend.py
def compute_expectation(
    self, 
    circuit: Any, 
    observable: Any
) -> float:
    """
    Compute expectation value of an observable.

    Parameters
    ----------
    circuit : callable
        PennyLane quantum function
    observable : pennylane.Hamiltonian or pennylane.operation.Observable
        PennyLane observable

    Returns
    -------
    expectation : float
        Expectation value
    """
    self.ensure_initialized()

    import pennylane as qml

    @qml.qnode(self._device)
    def qnode():
        circuit()
        return qml.expval(observable)

    expectation = qnode()
    return float(expectation)

create_observable(pauli_string, qubits)

Create a PennyLane observable from a Pauli string.

Parameters:

Name Type Description Default
pauli_string str

Pauli string (e.g., 'XYZ', 'ZZI')

required
qubits list of int

Qubits to apply the observable to

required

Returns:

Name Type Description
observable Hamiltonian

PennyLane observable object

Source code in quantum_data_embedding_suite\backends\pennylane_backend.py
def create_observable(self, pauli_string: str, qubits: List[int]) -> Any:
    """
    Create a PennyLane observable from a Pauli string.

    Parameters
    ----------
    pauli_string : str
        Pauli string (e.g., 'XYZ', 'ZZI')
    qubits : list of int
        Qubits to apply the observable to

    Returns
    -------
    observable : pennylane.Hamiltonian
        PennyLane observable object
    """
    try:
        import pennylane as qml
    except ImportError:
        raise ImportError("PennyLane is required for observables")

    # Map Pauli characters to PennyLane operators
    pauli_map = {
        'I': qml.Identity,
        'X': qml.PauliX,
        'Y': qml.PauliY,
        'Z': qml.PauliZ,
    }

    # Build tensor product of Pauli operators
    if len(pauli_string) == 1:
        # Single Pauli operator
        return pauli_map[pauli_string](wires=qubits[0])
    else:
        # Tensor product of multiple Pauli operators
        ops = []
        for i, pauli in enumerate(pauli_string):
            if i < len(qubits) and pauli != 'I':
                ops.append(pauli_map[pauli](wires=qubits[i]))

        if len(ops) == 0:
            # All identity operators
            return qml.Identity(wires=qubits[0])
        elif len(ops) == 1:
            return ops[0]
        else:
            # Tensor product
            result = ops[0]
            for op in ops[1:]:
                result = result @ op
            return result

create_qnode(circuit, interface='numpy')

Create a PennyLane QNode.

Parameters:

Name Type Description Default
circuit callable

PennyLane quantum function

required
interface str

Differentiation interface

'numpy'

Returns:

Name Type Description
qnode QNode

PennyLane QNode object

Source code in quantum_data_embedding_suite\backends\pennylane_backend.py
def create_qnode(self, circuit: Any, interface: str = "numpy") -> Any:
    """
    Create a PennyLane QNode.

    Parameters
    ----------
    circuit : callable
        PennyLane quantum function
    interface : str, default='numpy'
        Differentiation interface

    Returns
    -------
    qnode : pennylane.QNode
        PennyLane QNode object
    """
    self.ensure_initialized()

    import pennylane as qml

    return qml.QNode(circuit, self._device, interface=interface)

execute_circuit(circuit, shots=None)

Execute a quantum circuit using PennyLane.

Parameters:

Name Type Description Default
circuit callable

PennyLane quantum function

required
shots int

Number of shots (overrides default if provided)

None

Returns:

Name Type Description
result dict

Execution results containing probabilities, samples, etc.

Source code in quantum_data_embedding_suite\backends\pennylane_backend.py
def execute_circuit(
    self, 
    circuit: Any, 
    shots: Optional[int] = None
) -> Dict[str, Any]:
    """
    Execute a quantum circuit using PennyLane.

    Parameters
    ----------
    circuit : callable
        PennyLane quantum function
    shots : int, optional
        Number of shots (overrides default if provided)

    Returns
    -------
    result : dict
        Execution results containing probabilities, samples, etc.
    """
    self.ensure_initialized()

    import pennylane as qml

    # Create QNode for execution
    @qml.qnode(self._device)
    def qnode():
        circuit()
        return qml.probs(wires=range(self._device.num_wires))

    # Execute and get probabilities
    probs = qnode()

    # Convert to numpy array
    probs = np.array(probs)

    # Generate sample counts if shots are specified
    output = {'probabilities': probs}

    if shots is not None and shots > 0:
        # Sample from probability distribution
        n_states = len(probs)
        states = np.arange(n_states)
        samples = np.random.choice(states, size=shots, p=probs)

        # Count occurrences
        unique, counts = np.unique(samples, return_counts=True)
        counts_dict = {}
        for state, count in zip(unique, counts):
            # Convert to binary string
            binary = format(state, f'0{self._device.num_wires}b')
            counts_dict[binary] = int(count)

        output['counts'] = counts_dict
        output['samples'] = samples

    return output

get_device_properties()

Get properties of the current device.

Returns:

Name Type Description
properties dict

Device properties and capabilities

Source code in quantum_data_embedding_suite\backends\pennylane_backend.py
def get_device_properties(self) -> Dict[str, Any]:
    """
    Get properties of the current device.

    Returns
    -------
    properties : dict
        Device properties and capabilities
    """
    self.ensure_initialized()

    props = {
        "name": self.device,
        "type": "simulator" if "default" in self.device else "hardware",
        "n_qubits": self._device.num_wires,
        "shots_supported": hasattr(self._device, 'shots'),
        "analytic_supported": getattr(self._device, 'analytic', False),
    }

    if hasattr(self._device, 'capabilities'):
        caps = self._device.capabilities()
        props.update({
            "operations": caps.get('operations', []),
            "observables": caps.get('observables', []),
            "supports_finite_shots": caps.get('supports_finite_shots', True),
            "supports_tensor_observables": caps.get('supports_tensor_observables', True),
        })

    return props

get_statevector(circuit)

Get the statevector from a quantum circuit.

Parameters:

Name Type Description Default
circuit callable

PennyLane quantum function

required

Returns:

Name Type Description
statevector ndarray

Complex amplitudes of the quantum state

Source code in quantum_data_embedding_suite\backends\pennylane_backend.py
def get_statevector(self, circuit: Any) -> np.ndarray:
    """
    Get the statevector from a quantum circuit.

    Parameters
    ----------
    circuit : callable
        PennyLane quantum function

    Returns
    -------
    statevector : ndarray
        Complex amplitudes of the quantum state
    """
    self.ensure_initialized()

    import pennylane as qml

    # Create a statevector device if current device doesn't support it
    if hasattr(self._device, 'analytic') and self._device.analytic:
        device = self._device
    else:
        # Create temporary statevector device
        device = qml.device('default.qubit', wires=self._device.num_wires)

    @qml.qnode(device)
    def qnode():
        circuit()
        return qml.state()

    statevector = qnode()
    return np.array(statevector)

initialize()

Initialize the PennyLane backend.

Source code in quantum_data_embedding_suite\backends\pennylane_backend.py
def initialize(self) -> None:
    """Initialize the PennyLane backend."""
    try:
        import pennylane as qml
    except ImportError:
        raise ImportError("PennyLane is required for PennyLaneBackend")

    # Create PennyLane device
    n_qubits = self.params.get('n_qubits', 10)  # Default number of qubits

    device_kwargs = {
        'wires': n_qubits,
        'shots': self.shots if self.shots > 0 else None,
    }

    # Add device-specific parameters
    if self.device.startswith('qiskit'):
        device_kwargs.update({
            'backend': self.params.get('qiskit_backend', 'qasm_simulator'),
        })
    elif self.device.startswith('cirq'):
        # Cirq-specific parameters
        pass
    elif self.device == 'lightning.qubit':
        # Lightning-specific parameters
        device_kwargs['batch_obs'] = self.params.get('batch_obs', False)

    self._device = qml.device(self.device, **device_kwargs)

sample_circuit(circuit, shots)

Sample from a quantum circuit.

Parameters:

Name Type Description Default
circuit callable

PennyLane quantum function

required
shots int

Number of samples

required

Returns:

Name Type Description
samples ndarray

Array of measurement samples

Source code in quantum_data_embedding_suite\backends\pennylane_backend.py
def sample_circuit(self, circuit: Any, shots: int) -> np.ndarray:
    """
    Sample from a quantum circuit.

    Parameters
    ----------
    circuit : callable
        PennyLane quantum function
    shots : int
        Number of samples

    Returns
    -------
    samples : ndarray
        Array of measurement samples
    """
    self.ensure_initialized()

    import pennylane as qml

    # Create sampling device
    sample_device = qml.device(
        self.device, 
        wires=self._device.num_wires, 
        shots=shots
    )

    @qml.qnode(sample_device)
    def qnode():
        circuit()
        return qml.sample(wires=range(self._device.num_wires))

    samples = qnode()
    return np.array(samples)

Backend Utilities

get_available_backends

get_backend(backend, device=None, shots=1024, **kwargs)

Factory function to create backend instances.

Parameters:

Name Type Description Default
backend str

Backend name ('qiskit', 'pennylane')

required
device str

Specific device/simulator to use

None
shots int

Number of measurement shots

1024
**kwargs

Additional backend-specific parameters

{}

Returns:

Name Type Description
backend_instance BaseBackend

Initialized backend instance

Source code in quantum_data_embedding_suite\backends\__init__.py
def get_backend(backend: str, device: str = None, shots: int = 1024, **kwargs) -> BaseBackend:
    """
    Factory function to create backend instances.

    Parameters
    ----------
    backend : str
        Backend name ('qiskit', 'pennylane')
    device : str, optional
        Specific device/simulator to use
    shots : int, default=1024
        Number of measurement shots
    **kwargs
        Additional backend-specific parameters

    Returns
    -------
    backend_instance : BaseBackend
        Initialized backend instance
    """
    if backend not in BACKEND_REGISTRY:
        raise ValueError(f"Unknown backend: {backend}. Available: {list(BACKEND_REGISTRY.keys())}")

    backend_class = BACKEND_REGISTRY[backend]
    return backend_class(device=device, shots=shots, **kwargs)

create_backend

BACKEND_REGISTRY = {'qiskit': QiskitBackend, 'pennylane': PennyLaneBackend} module-attribute

Usage Examples

Basic Backend Creation

from quantum_data_embedding_suite.backends import (
    QiskitBackend, PennyLaneBackend, create_backend
)

# Create Qiskit backend
qiskit_backend = QiskitBackend(
    device_name="qasm_simulator",
    shots=1024,
    seed=42
)

# Create PennyLane backend
pennylane_backend = PennyLaneBackend(
    device_name="default.qubit",
    shots=1024,
    seed=42
)

# Create backend using factory function
backend = create_backend(
    backend_type="qiskit",
    device_name="aer_simulator",
    shots=2048
)

print(f"Backend type: {type(backend).__name__}")
print(f"Device: {backend.device_name}")
print(f"Shots: {backend.shots}")

Backend Discovery and Selection

from quantum_data_embedding_suite.backends import get_available_backends, backend_info

# Discover available backends
available = get_available_backends()

print("Available Backends:")
for backend_type, devices in available.items():
    print(f"\n{backend_type.upper()}:")
    for device in devices:
        info = backend_info(backend_type, device)
        print(f"  - {device}: {info.get('description', 'No description')}")

        # Show device capabilities
        if 'capabilities' in info:
            caps = info['capabilities']
            print(f"    Qubits: {caps.get('max_qubits', 'Unknown')}")
            print(f"    Noise: {'Yes' if caps.get('supports_noise') else 'No'}")
            print(f"    GPU: {'Yes' if caps.get('gpu_support') else 'No'}")

# Select optimal backend for task
def select_optimal_backend(n_qubits, require_noise=False, prefer_gpu=False):
    """Select the best available backend for given requirements"""

    available = get_available_backends()
    best_backend = None
    best_score = -1

    for backend_type, devices in available.items():
        for device in devices:
            info = backend_info(backend_type, device)
            caps = info.get('capabilities', {})

            # Check requirements
            if caps.get('max_qubits', 0) < n_qubits:
                continue

            if require_noise and not caps.get('supports_noise', False):
                continue

            # Calculate score
            score = 0
            score += caps.get('max_qubits', 0) * 0.1  # More qubits is better
            score += 10 if caps.get('supports_noise', False) else 0
            score += 5 if caps.get('gpu_support', False) and prefer_gpu else 0
            score += 3 if backend_type == 'qiskit' else 0  # Slight preference for Qiskit

            if score > best_score:
                best_score = score
                best_backend = (backend_type, device)

    return best_backend

# Find optimal backend
optimal = select_optimal_backend(n_qubits=6, require_noise=False, prefer_gpu=True)
if optimal:
    backend_type, device = optimal
    backend = create_backend(backend_type, device)
    print(f"\nSelected optimal backend: {backend_type}/{device}")
else:
    print("\nNo suitable backend found")

Advanced Configuration

# Advanced Qiskit configuration
from qiskit import IBMQ
from qiskit.providers.aer.noise import NoiseModel
from qiskit.providers.aer.noise.errors import pauli_error, depolarizing_error

# Configure with noise model
def create_noisy_qiskit_backend():
    """Create Qiskit backend with realistic noise"""

    # Create noise model
    noise_model = NoiseModel()

    # Add depolarizing error to gates
    error_1q = depolarizing_error(0.001, 1)  # 0.1% error rate
    error_2q = depolarizing_error(0.01, 2)   # 1% error rate

    noise_model.add_all_qubit_quantum_error(error_1q, ['u1', 'u2', 'u3'])
    noise_model.add_all_qubit_quantum_error(error_2q, ['cx'])

    # Add measurement error
    from qiskit.providers.aer.noise.errors import ReadoutError
    readout_error = ReadoutError([[0.98, 0.02], [0.03, 0.97]])
    noise_model.add_all_qubit_readout_error(readout_error)

    # Create backend with noise
    backend = QiskitBackend(
        device_name="aer_simulator",
        shots=2048,
        noise_model=noise_model,
        basis_gates=noise_model.basis_gates,
        coupling_map=None
    )

    return backend

noisy_backend = create_noisy_qiskit_backend()

# Advanced PennyLane configuration
import pennylane as qml

def create_advanced_pennylane_backend():
    """Create PennyLane backend with advanced features"""

    # Create device with custom configuration
    device = qml.device(
        'default.qubit',
        wires=8,
        shots=1024,
        analytic=False,  # Use sampling
        seed=42
    )

    backend = PennyLaneBackend(
        device=device,
        differentiable=True,
        interface='autograd'
    )

    return backend

advanced_pennylane = create_advanced_pennylane_backend()

Backend Performance Benchmarking

import time
import numpy as np
from quantum_data_embedding_suite.embeddings import AngleEmbedding

def benchmark_backend(backend, embedding, X, n_trials=10):
    """Benchmark backend performance"""

    times = []

    for trial in range(n_trials):
        start_time = time.time()

        try:
            # Execute embedding on multiple data points
            for x in X[:5]:  # Test with 5 data points
                circuit = embedding.embed(x)
                backend.execute(circuit)

            end_time = time.time()
            times.append(end_time - start_time)

        except Exception as e:
            print(f"Error in trial {trial}: {e}")
            continue

    if not times:
        return None

    results = {
        'mean_time': np.mean(times),
        'std_time': np.std(times),
        'min_time': np.min(times),
        'max_time': np.max(times),
        'n_successful_trials': len(times),
        'throughput': len(X[:5]) / np.mean(times)  # circuits per second
    }

    return results

# Benchmark different backends
embedding = AngleEmbedding(n_qubits=4)
X = np.random.randn(10, 4)

backends_to_test = {
    'qiskit_simulator': QiskitBackend("qasm_simulator", shots=1024),
    'pennylane_default': PennyLaneBackend("default.qubit", shots=1024)
}

benchmark_results = {}

print("Backend Benchmarking Results:")
print("=" * 50)

for name, backend in backends_to_test.items():
    print(f"\nTesting {name}...")

    results = benchmark_backend(backend, embedding, X)

    if results:
        benchmark_results[name] = results

        print(f"  Mean execution time: {results['mean_time']:.4f}s")
        print(f"  Std deviation: {results['std_time']:.4f}s")
        print(f"  Throughput: {results['throughput']:.2f} circuits/s")
        print(f"  Successful trials: {results['n_successful_trials']}/10")
    else:
        print(f"  Benchmark failed for {name}")

# Find fastest backend
if benchmark_results:
    fastest = min(benchmark_results.items(), key=lambda x: x[1]['mean_time'])
    print(f"\nFastest backend: {fastest[0]} ({fastest[1]['mean_time']:.4f}s)")

Hardware Integration

Real Quantum Hardware Access

from qiskit import IBMQ
from qiskit.providers.ibmq import least_busy

def setup_ibm_hardware_backend(hub=None, group=None, project=None):
    """Setup connection to IBM Quantum hardware"""

    try:
        # Load account (assumes IBMQ account is saved)
        IBMQ.load_account()

        # Get provider
        if hub and group and project:
            provider = IBMQ.get_provider(hub=hub, group=group, project=project)
        else:
            provider = IBMQ.get_provider()

        # Get available backends
        backends = provider.backends(
            filters=lambda x: x.configuration().n_qubits >= 5 and 
                             not x.configuration().simulator and 
                             x.status().operational
        )

        if not backends:
            print("No operational quantum hardware available")
            return None

        # Select least busy backend
        least_busy_backend = least_busy(backends)

        # Create our backend wrapper
        hardware_backend = QiskitHardware(
            provider=provider,
            backend_name=least_busy_backend.name(),
            shots=1024,
            optimization_level=2
        )

        print(f"Connected to {least_busy_backend.name()}")
        print(f"Qubits: {least_busy_backend.configuration().n_qubits}")
        print(f"Queue length: {least_busy_backend.status().pending_jobs}")

        return hardware_backend

    except Exception as e:
        print(f"Failed to setup IBM hardware backend: {e}")
        return None

# Setup hardware backend
hardware_backend = setup_ibm_hardware_backend()

if hardware_backend:
    # Execute on real quantum hardware
    embedding = AngleEmbedding(n_qubits=4)
    test_data = np.array([0.1, 0.2, 0.3, 0.4])

    circuit = embedding.embed(test_data)
    result = hardware_backend.execute(circuit)

    print(f"Hardware execution result: {result}")

Error Mitigation

from qiskit.ignis.mitigation.measurement import complete_meas_cal, CompleteMeasFitter

def create_error_mitigated_backend(base_backend):
    """Create backend with measurement error mitigation"""

    class ErrorMitigatedBackend:
        def __init__(self, base_backend):
            self.base_backend = base_backend
            self.calibration_fitter = None
            self._setup_calibration()

        def _setup_calibration(self):
            """Setup measurement error calibration"""
            try:
                # Create calibration circuits
                n_qubits = 5  # Assume 5 qubits for calibration
                qubits = list(range(n_qubits))

                cal_circuits, state_labels = complete_meas_cal(
                    qubit_list=qubits,
                    circlabel='cal'
                )

                # Execute calibration circuits
                cal_results = []
                for circuit in cal_circuits:
                    result = self.base_backend.execute(circuit)
                    cal_results.append(result)

                # Create fitter
                self.calibration_fitter = CompleteMeasFitter(
                    cal_results, state_labels, circlabel='cal'
                )

                print("Measurement error calibration complete")

            except Exception as e:
                print(f"Calibration failed: {e}")
                self.calibration_fitter = None

        def execute(self, circuit):
            """Execute circuit with error mitigation"""
            # Execute original circuit
            raw_result = self.base_backend.execute(circuit)

            # Apply error mitigation if calibration available
            if self.calibration_fitter:
                try:
                    mitigated_result = self.calibration_fitter.filter.apply(raw_result)
                    return mitigated_result
                except Exception as e:
                    print(f"Error mitigation failed: {e}")
                    return raw_result

            return raw_result

        def __getattr__(self, name):
            """Delegate other attributes to base backend"""
            return getattr(self.base_backend, name)

    return ErrorMitigatedBackend(base_backend)

# Create error-mitigated backend
if hardware_backend:
    mitigated_backend = create_error_mitigated_backend(hardware_backend)

Backend Comparison and Analysis

Feature Comparison Matrix

def compare_backend_features():
    """Compare features across different backends"""

    backends = [
        ('qiskit', 'qasm_simulator'),
        ('qiskit', 'statevector_simulator'),
        ('pennylane', 'default.qubit'),
        ('pennylane', 'default.mixed'),
    ]

    features = [
        'max_qubits', 'supports_noise', 'gpu_support', 'differentiable',
        'supports_measurements', 'supports_conditional', 'parallelizable'
    ]

    comparison_matrix = []

    for backend_type, device in backends:
        try:
            info = backend_info(backend_type, device)
            caps = info.get('capabilities', {})

            row = [f"{backend_type}/{device}"]
            for feature in features:
                value = caps.get(feature, 'Unknown')
                if isinstance(value, bool):
                    value = '✓' if value else '✗'
                row.append(str(value))

            comparison_matrix.append(row)

        except Exception as e:
            print(f"Error getting info for {backend_type}/{device}: {e}")
            continue

    # Create DataFrame for easy viewing
    import pandas as pd

    columns = ['Backend'] + features
    df = pd.DataFrame(comparison_matrix, columns=columns)

    return df

# Generate comparison
comparison_df = compare_backend_features()
print("Backend Feature Comparison:")
print(comparison_df.to_string(index=False))

Backend Recommendations

def recommend_backend(use_case, requirements=None):
    """Recommend optimal backend for specific use case"""

    if requirements is None:
        requirements = {}

    recommendations = {
        'development': {
            'primary': ('qiskit', 'qasm_simulator'),
            'alternative': ('pennylane', 'default.qubit'),
            'reason': 'Fast execution, good debugging tools'
        },

        'research': {
            'primary': ('qiskit', 'statevector_simulator'),
            'alternative': ('pennylane', 'default.qubit'),
            'reason': 'Exact results, noise-free environment'
        },

        'machine_learning': {
            'primary': ('pennylane', 'default.qubit'),
            'alternative': ('qiskit', 'qasm_simulator'),
            'reason': 'Automatic differentiation, ML integration'
        },

        'noise_modeling': {
            'primary': ('qiskit', 'aer_simulator'),
            'alternative': ('pennylane', 'default.mixed'),
            'reason': 'Advanced noise models, realistic simulation'
        },

        'production': {
            'primary': ('qiskit', 'ibmq_hardware'),
            'alternative': ('qiskit', 'aer_simulator'),
            'reason': 'Real quantum hardware access'
        },

        'large_scale': {
            'primary': ('qiskit', 'aer_simulator_gpu'),
            'alternative': ('pennylane', 'lightning.gpu'),
            'reason': 'GPU acceleration, high qubit count'
        }
    }

    if use_case not in recommendations:
        return {
            'recommendation': 'No specific recommendation',
            'suggested_backends': [('qiskit', 'qasm_simulator'), ('pennylane', 'default.qubit')],
            'reason': 'General-purpose backends suitable for most tasks'
        }

    rec = recommendations[use_case]

    # Check if recommended backends are available
    available = get_available_backends()

    primary_available = (rec['primary'][0] in available and 
                        rec['primary'][1] in available[rec['primary'][0]])

    alt_available = (rec['alternative'][0] in available and 
                    rec['alternative'][1] in available[rec['alternative'][0]])

    final_rec = {
        'use_case': use_case,
        'primary_recommendation': rec['primary'] if primary_available else None,
        'alternative_recommendation': rec['alternative'] if alt_available else None,
        'reason': rec['reason'],
        'requirements_met': True  # Could add requirement checking logic
    }

    return final_rec

# Get recommendations for different use cases
use_cases = ['development', 'research', 'machine_learning', 'production']

print("Backend Recommendations:")
print("=" * 50)

for use_case in use_cases:
    rec = recommend_backend(use_case)
    print(f"\n{use_case.upper()}:")

    if rec['primary_recommendation']:
        backend_type, device = rec['primary_recommendation']
        print(f"  Primary: {backend_type}/{device}")

    if rec['alternative_recommendation']:
        backend_type, device = rec['alternative_recommendation']
        print(f"  Alternative: {backend_type}/{device}")

    print(f"  Reason: {rec['reason']}")

Custom Backend Development

Creating Custom Backends

from quantum_data_embedding_suite.backends.base import BaseBackend

class CustomQuantumBackend(BaseBackend):
    """Example custom quantum backend implementation"""

    def __init__(self, custom_param=None, **kwargs):
        super().__init__(**kwargs)
        self.custom_param = custom_param
        self._setup_custom_features()

    def _setup_custom_features(self):
        """Setup custom backend features"""
        self.custom_features = {
            'special_gates': True,
            'advanced_optimization': True,
            'custom_metrics': True
        }
        print(f"Custom backend initialized with param: {self.custom_param}")

    def execute(self, circuit, **kwargs):
        """Execute quantum circuit with custom processing"""

        # Pre-processing
        optimized_circuit = self._optimize_circuit(circuit)

        # Execute (this would interface with actual quantum hardware/simulator)
        result = self._execute_circuit(optimized_circuit, **kwargs)

        # Post-processing
        processed_result = self._post_process_result(result)

        return processed_result

    def _optimize_circuit(self, circuit):
        """Apply custom circuit optimizations"""
        # Implement custom optimization logic
        print(f"Applying custom optimization to circuit with {circuit.depth()} depth")

        # Placeholder optimization
        return circuit

    def _execute_circuit(self, circuit, **kwargs):
        """Execute the circuit (placeholder implementation)"""
        # This would interface with actual quantum execution
        # For demonstration, return mock results

        n_qubits = circuit.num_qubits
        shots = kwargs.get('shots', self.shots)

        # Generate mock measurement results
        import random
        results = {}

        for _ in range(shots):
            # Generate random bit string
            bitstring = ''.join([str(random.randint(0, 1)) for _ in range(n_qubits)])
            results[bitstring] = results.get(bitstring, 0) + 1

        return results

    def _post_process_result(self, result):
        """Apply custom post-processing to results"""
        # Implement custom result processing
        print("Applying custom post-processing")

        # Example: Add metadata
        processed = {
            'counts': result,
            'metadata': {
                'backend_type': 'custom',
                'custom_param': self.custom_param,
                'post_processed': True
            }
        }

        return processed

    def get_backend_info(self):
        """Return backend information"""
        info = super().get_backend_info()
        info.update({
            'custom_features': self.custom_features,
            'custom_param': self.custom_param
        })
        return info

# Use custom backend
custom_backend = CustomQuantumBackend(
    custom_param="special_mode",
    shots=2048,
    seed=42
)

# Test custom backend
embedding = AngleEmbedding(n_qubits=3)
test_data = np.array([0.1, 0.2, 0.3])

circuit = embedding.embed(test_data)
result = custom_backend.execute(circuit)

print("\nCustom Backend Result:")
print(f"Counts: {result['counts']}")
print(f"Metadata: {result['metadata']}")

Backend Plugin System

class BackendRegistry:
    """Registry for custom backends"""

    _backends = {}

    @classmethod
    def register(cls, name, backend_class):
        """Register a custom backend"""
        cls._backends[name] = backend_class
        print(f"Registered backend: {name}")

    @classmethod
    def get_backend(cls, name, **kwargs):
        """Get registered backend instance"""
        if name not in cls._backends:
            raise ValueError(f"Backend '{name}' not registered")

        backend_class = cls._backends[name]
        return backend_class(**kwargs)

    @classmethod
    def list_backends(cls):
        """List all registered backends"""
        return list(cls._backends.keys())

# Register custom backend
BackendRegistry.register('custom', CustomQuantumBackend)

# Use registered backend
registered_backend = BackendRegistry.get_backend('custom', custom_param="registry_test")

print(f"Available backends: {BackendRegistry.list_backends()}")

Further Reading