Qiskit Backend#

Context and Purpose#

This adapter exposes Qiskit’s qiskit.primitives.Sampler through the QuantumBackend interface. It builds (or accepts) a parameterized circuit that encodes the last input vector x with per-qubit RY rotations and returns one signed average per measured wire (expectation-like value derived from bitstring counts).

Conceptual Foundations#

  1. Shape stability (output–wire alignment): The constructor enforces num_qubits == output_dim and raises ValueError otherwise, ensuring each output index corresponds to a wire.

  2. Minimal default ansatz: The fallback circuit applies one RY(angle) per wire and a final barrier, then calls measure_all() to ensure classical bits are present for Sampler validation. Measurement can be implicit in some flows, but explicit measure_all() keeps versions compatible.

  3. Signed averages from counts: The execution aggregates bitstrings into a per-wire signed mean (bit '1' → +1, bit '0' → −1). Because Qiskit bitstrings are big-endian, the code reverses the bitstring to map to wire indices 0..N-1.

  4. Bounded future projections: project_future() generates K branches by applying smooth additive perturbations in a linear range and squashing with tanh for numeric stability.

Integration Guidelines#

  • Use this backend when you need shot-based sampling with a uniform interface across simulators and (optionally) providers that expose the Sampler API.

  • Provide a custom circuit_builder(x) -> QuantumCircuit to introduce entanglement or domain-specific structure; the default circuit has no entangling gates.

  • Keep output_dim and num_qubits equal to preserve shape invariants.

  • Set a fixed random seed and an ideal simulator if you need repeatable sampling.

Installation#

Qiskit is included as an optional dependency of qml-hcs. However, it is recommended to install it manually if you wish to test a specific version or ensure compatibility with your preferred Qiskit release.

Choose the appropriate installation depending on your environment:

  • Qiskit 1.x (legacy Sampler API):

pip install "qiskit>=1.2,<2.0"
  • Qiskit 2.x (modern StatevectorSampler API):

pip install "qiskit>=2.0"

The backend automatically detects the available version of Qiskit and imports the correct primitive:

  • For Qiskit 1.x → Sampler

  • For Qiskit 2.x → StatevectorSampler (used as a drop-in replacement)

Ensure Python ≥ 3.9 and that your environment includes qiskit.primitives.

Version Compatibility#

  • Qiskit ≤1.x: uses the classic qiskit.primitives.Sampler. Results are typically exposed as quasi_dists (probabilities).

  • Qiskit ≥2.x: the adapter falls back to qiskit.primitives.StatevectorSampler. Circuits are passed as a list ([qc]). The default circuit adds measure_all() to provide classical bits required by stricter validators. Results are read from quasi_dists when present or from data.meas['counts'] otherwise.

Note

This backend is not strictly deterministic. Results depend on stochastic sampling via Sampler. Determinism can only be approximated with an ideal simulator, fixed seeds, and sufficiently large shot counts. When config.shots is not provided, execution defaults to 1024 shots.

Performance & Scaling Notes#

  • Circuit depth: linear in qubit count (one RY per wire + barrier).

  • Sampling cost: dominated by the number of shots; empirical variance decreases roughly as \(O(1/\sqrt{N_{shots}})\).

  • Memory footprint: compact, since post-processing stores wire-wise aggregates, not full distributions.

Extension Pathways#

  • Custom ansatz: pass circuit_builder to modify the circuit while keeping the backend contract unchanged.

  • Sampler/backend choice: swap the underlying Sampler backend depending on your environment (e.g., Aer simulators, hardware providers that support Sampler).

  • Capability reporting: override capabilities() to expose provider- specific features (the default advertises name/version, qubit count, shots/noise flags, batch support, and PARAMETER_SHIFT).

Interoperability & Testing#

  • Endianness handling: the implementation reverses big-endian bitstrings when aggregating counts, preserving the mapping between bit position and wire index.

  • No extra branch validation: project_future() validates the input state and returns stacked branches directly; downstream checks are expected at higher layers.

Design Guarantees#

  • Enforced output–wire alignment (constructor validation).

  • Minimal, auditable default circuit (RY per wire + barrier).

  • Expectation-like signed averages computed from counts with explicit endianness handling.

  • Bounded multi-branch projections via smooth perturbations and tanh squashing.

API Reference overview#

Qiskit Backend Adapter#

Adapter for Qiskit-based quantum execution backends that conforms to the QuantumBackend contract.

This wrapper uses Qiskit’s Sampler primitive to execute a (possibly custom) parameterized circuit that encodes the last input x via RY rotations. It returns an expectation-like vector per wire derived from measured bitstring counts.

Example

>>> import numpy as np
>>> from qmlhc.core.backend import BackendConfig
>>> from qmlhc.backends.qiskit_backend import QiskitBackend
>>> cfg = BackendConfig(output_dim=3, shots=1024)
>>> be = QiskitBackend(cfg, num_qubits=3)
>>> be.encode(np.array([0.1, 0.2, 0.3], dtype=float))
>>> s_t = be.run()
>>> fut = be.project_future(s_t, branches=5)
>>> s_t.shape
(3,)
>>> fut.shape
(5, 3)

Note

This example demonstrates the standard Qiskit-based workflow within the unified QuantumBackend API: initialize the backend, encode a numeric state, execute the sampling run, and obtain future projections.

Because execution relies on the Sampler, the numerical outputs are stochastic. While the individual expectation-like values in s_t and fut vary across runs, their dimensional structure and bounded range (within [-1, 1]) remain invariant. This behavior reflects the physical sampling nature of quantum backends rather than a computational instability.

class qmlhc.backends.qiskit_backend.QiskitBackend(config, num_qubits, circuit_builder=None)[source]#

Bases: QuantumBackend

Wrap Qiskit primitives into the QuantumBackend API.

Parameters:
  • config (BackendConfig) – Backend configuration (e.g., output_dim, shots).

  • num_qubits (int) – Number of qubits / wires. Must equal config.output_dim.

  • circuit_builder (Callable[[np.ndarray], QuantumCircuit], optional) – Custom function that builds a circuit from the encoded input vector x. If not provided, a default RY + barrier circuit is used.

Raises:

ValueError – If output_dim does not match num_qubits.

Notes

  • Execution is always shot-based; there is no analytic (deterministic) mode.

  • If config.shots is not provided, defaults to 1024.

  • The default circuit applies one RY per qubit and a barrier (no entanglement).

  • Qiskit bitstrings are big-endian and are reversed when mapping bits → wires.

capabilities()[source]#

Report merged capabilities (base + Qiskit-specific).

Returns:

Capability dictionary including backend name/version, qubit count, shot/noise support, batching, and gradient method.

Return type:

Capabilities

project_future(s_t, branches=2)[source]#

Generate future state projections by applying smooth additive perturbations.

Parameters:
  • s_t (np.ndarray) – Current state vector.

  • branches (int, optional) – Number of projected branches (K), by default 2.

Returns:

Matrix of shape (K, D) with tanh-stabilized perturbations.

Return type:

Array

run(params=None)[source]#

Execute the circuit via Qiskit’s Sampler and return an expectation-like vector.

Notes

  • encode(x) must be called before run(); this is enforced by _require_input().

  • Uses shots = config.shots or 1024 when running the sampler.

  • Computes signed per-wire averages (‘1’→+1, ‘0’→−1) after endian correction.

Note

  • Wraps the circuit as [qc] for Qiskit 2.x compatibility (accepted by 1.x as well).

  • Reads results from quasi_dists when available, otherwise from data.meas['counts'].

Parameters:

params (dict or None, optional) – Unused in this minimal adapter; reserved for future extensions.

Returns:

Vector of shape (D,) containing per-wire signed averages.

Return type:

Array

External and Internal Dependencies#

Third-Party Libraries

qiskit Provides qiskit.QuantumCircuit and qiskit.primitives.Sampler used to build/execute circuits and retrieve sampled results.

numpy Used for vector operations and utilities (e.g., linspace, stack, tanh, and array handling).

Python Standard Library

__future__ – forward references for annotations. typing – typing hints for API clarity.

QML-HCS Internal Modules

qmlhc.core.backend Defines qmlhc.core.backend.QuantumBackend and qmlhc.core.backend.BackendConfig, which this adapter implements/extends.

qmlhc.core.types Provides qmlhc.core.types.Array, qmlhc.core.types.Capabilities, and qmlhc.core.types.GradientKind used in method signatures, returns, and capability metadata.