Qdislib.core.backend package

The pluggable execution layer. Every target – a CPU simulator (Qibo / Qiskit / CUDA-Q), a GPU, or a real QPU – is a BaseBackend; the cutting algorithms never branch on the backend, they hand each subcircuit to the object that build_execution_backend() selects from the software / gpu / qpu arguments.

Submodules

Qdislib.core.backend.base_backend module

Backend abstraction for Qdislib.

The cutting algorithms are backend-agnostic: they produce subcircuits and ask a backend to sample them (counts) or estimate an observable (expectation value). A backend is anything implementing BaseBackend – a simulator (Aer, Qibo, CUDA-Q) or real hardware (IQM, IBM, a BSC QPU). New backends are registered in registry and can be added by users without touching the algorithms.

class Qdislib.core.backend.base_backend.BackendConfig(num_qubits, extra=<factory>)[source]

Bases: object

Static configuration for a backend (its size and any extra options).

extra: dict
num_qubits: int
class Qdislib.core.backend.base_backend.BaseBackend(name, config)[source]

Bases: ABC

Common interface for all Qdislib backends.

FIFO queue management is centralised here via the Template Method pattern: subclasses implement _execute_samples / _execute_estimator / transpile (the hooks), while run_samples / run_estimator orchestrate the full enqueue -> execute -> dequeue lifecycle. The queue length is what QdBackEnd uses to load-balance.

can_run(circuit)[source]
Return type:

bool

evaluate_expectation(dag, num_qubits, prep_qubits=(), shots=1024, method='automatic')[source]

Evaluate the expectation of a Qdislib subcircuit DAG on this backend.

This is the seam the cutting algorithms delegate to instead of branching on the backend name: they hand over the backend-neutral DAG (its Pauli observable rides along as Observable I markers) and the set of prep_qubits (cut qubits measured for the decomposition, whose sign is recovered from the backend’s own mid-circuit outcome). The backend builds its native circuit, executes it, and reconstructs the value. Backends that only support run_samples() / run_estimator() may leave this unimplemented.

Parameters:
  • dag (Any) – A renumbered connected-component DAG of the cut circuit.

  • num_qubits (int) – Qubit count of the component.

  • prep_qubits (Any) – Renumbered cut qubits measured for the decomposition.

  • shots (int) – Number of measurement shots.

  • method (str) – Backend-specific simulation method hint.

Return type:

float

Returns:

The subcircuit’s expectation value.

evaluate_joint_counts(dag, num_qubits, cut_point_qubits=(), shots=1024, seed=None)[source]

Sample a gate-cut subcircuit, capturing the JOINT final/cut-point outcomes.

The seam the sampling reconstruction delegates to – the distribution counterpart of evaluate_expectation(). Gate-cut sampling needs, per shot, the final measurement and the mid-circuit cut-point outcomes of cut_point_qubits kept correlated, so a cut qubit that keeps evolving after the cut still reconstructs correctly (its final bit no longer equals its cut-point bit). Returns {(final_bits, cut_point_bits): count} with final_bits qubit-0-first over the component and cut_point_bits over cut_point_qubits in order.

Backends whose execution yields only aggregated final counts (most real QPUs) cannot report the mid-circuit outcome and leave this unimplemented.

Parameters:
  • dag (Any) – A renumbered connected-component DAG of the cut circuit.

  • num_qubits (int) – Qubit count of the component.

  • cut_point_qubits (Any) – Renumbered cut qubits measured for the decomposition.

  • shots (int) – Number of measurement shots.

Return type:

dict

Returns:

{(final_bits, cut_point_bits): count}.

evaluate_samples(dag, num_qubits, shots=1024, seed=None)[source]

Sample a Qdislib subcircuit DAG and return counts in a standard convention.

Used by the sampling reconstruction of gate cutting. Every backend returns keys as qubit-0-first bitstrings (key[i] is qubit i), regardless of the SDK’s native endianness, so the reconstruction combines components uniformly.

Parameters:
  • dag (Any) – A renumbered connected-component DAG of the cut circuit.

  • num_qubits (int) – Qubit count of the component.

  • shots (int) – Number of measurement shots.

Return type:

dict[str, int]

Returns:

{qubit-0-first bitstring: count}.

property num_qubits: int
property queue_length: int
property queue_snapshot: list[Job]

Return an immutable copy of the current queue (useful for logging / monitoring).

run_estimator(circuit, transpile, observable, shots)[source]

Compute the expectation value of an observable, updating the FIFO queue.

Return type:

ExpectationJobResult

run_samples(circuit, transpile, shots)[source]

Sample the circuit, updating the FIFO queue before and after execution.

Return type:

SampleJobResult

abstractmethod transpile(circuit)[source]

Transpile the circuit into the backend’s native format.

Return type:

Any

class Qdislib.core.backend.base_backend.ExpectationJobResult(expectation_value, shots, backend_name, job_id, obs='', metadata=<factory>)[source]

Bases: object

Result of an estimator run: an expectation value plus provenance metadata.

backend_name: str
expectation_value: float
job_id: str
metadata: dict
obs: str = ''
shots: int
class Qdislib.core.backend.base_backend.Job(job_id, kind, n_qubits, shots, submitted_at=<factory>)[source]

Bases: object

A unit of work tracked in a backend’s FIFO queue.

job_id: str
kind: Literal['samples', 'estimator']
n_qubits: int
shots: int | None
submitted_at: datetime
class Qdislib.core.backend.base_backend.SampleJobResult(counts, backend_name, shots, job_id, metadata=<factory>)[source]

Bases: object

Result of a sampling run: measurement counts plus provenance metadata.

backend_name: str
counts: dict[str, int]
job_id: str
metadata: dict
shots: int
Qdislib.core.backend.base_backend.SupportedCircuit

A circuit in any of the supported source formats (Qiskit / Qibo / a CUDA-Q kernel or its (dag, num_qubits) payload). Annotated as Any so importing the backend layer never pulls in a specific SDK – import Qdislib must work with only one backend installed (Qibo-only / Qiskit-only / CUDA-Q-only).

Qdislib.core.backend.registry module

Lazy, user-extensible registry of Qdislib backends.

Backends are registered by name -> (module path, class name) and imported only on first use, so a backend’s optional third-party dependencies (qiskit-aer, iqm, …) are not required until that backend is actually built.

Qdislib.core.backend.registry.available_backends()[source]

Return the names of all registered backends.

Return type:

list[str]

Qdislib.core.backend.registry.build_backend(name, config)[source]

Lazily import and instantiate a registered backend.

The backend module – and its third-party dependencies – are imported only at this point, not at registry import time.

Return type:

BaseBackend

Qdislib.core.backend.registry.build_execution_backend(software=None, num_qubits=1, *, gpu=False, gpu_min_qubits=None, qpu=False, qpu_dict=None, max_qubit=None, batch=None, ibm_backend=None, **extra)[source]

Select and build the backend that evaluates a cut subcircuit.

The single place that turns the cutting algorithms’ execution knobs (software, gpu, qpu/qpu_dict) into a concrete backend, so the algorithms never branch on the backend/device. Selection order: a large-enough configured QPU (BSC MN_Ona -> BSC_QPU_BACKEND, IBM_Quantum -> IBM_BACKEND), then GPU-eligible Aer, then the software backend (qibo/qiskit/cudaq or the name of any registered backend). Extra keyword arguments (e.g. platform for Qibo) flow into the BackendConfig.

Return type:

BaseBackend

Qdislib.core.backend.registry.register_backend(name, module_path, class_name)[source]

Register a backend without importing it.

Intended for user-defined backends or third-party extensions:

register_backend("MY_BACKEND", "my_package.my_backend", "MyBackend")
Return type:

None

Qdislib.core.backend.unified module

The unified backend: one object the algorithms ask to sample / estimate.

QdBackEnd holds one or more BaseBackend instances and routes each job to the most suitable one – shortest queue first, qubit-count compatible, with fallback to the next candidate on failure. This is what decouples the cutting algorithms from any specific SDK or device.

class Qdislib.core.backend.unified.QdBackEnd(qpu_dict=None)[source]

Bases: object

Unified, load-balancing backend for Qdislib.

Parameters:

qpu_dict

Maps backend names to their config, e.g.:

QdBackEnd({
    "AER_BACKEND": {"num_qubits": 30},
    "IQM_BACKEND": {"num_qubits": 20, "url": "...", "tokens_path": "..."},
})

With no argument, a single default Aer backend is used.

property backends: list[BaseBackend]
get_expectation_value(circuit, observable, shots=1024, transpile=True, backend_name=None)

Estimate <observable>; falls back past backends without estimator support.

Return type:

ExpectationJobResult

sample_circuit(circuit, shots=1024, transpile=True, backend_name=None)

Sample the circuit on the most suitable backend (or a named one).

Return type:

SampleJobResult

Qdislib.core.backend.circuit_converter module

Convert circuits between the supported source formats (Qiskit / Qibo).

Qiskit and Qibo interconvert cleanly through OpenQASM 2.0. CUDA-Q kernels have no stable QASM round-trip, so Qdislib feeds CUDA-Q via its backend-neutral DAG instead; CircuitConverter.num_qubits() still understands a CUDA-Q kernel and the (dag, num_qubits) payload the cutting code uses for it.

class Qdislib.core.backend.circuit_converter.CircuitConverter[source]

Bases: object

Converts circuits between the supported formats and reports their size.

static num_qubits(circuit)[source]

Qubit count for any supported circuit, including CUDA-Q payloads.

Return type:

int

static to_qibo(circuit)[source]
static to_qiskit(circuit)[source]
static to_qiskit_pauli(observable)[source]

Reorder a qubit-0-first Pauli string to Qiskit’s little-endian labels.

Qdislib observable strings are qubit-0-first (char i acts on qubit i), matching the Qibo/CUDA-Q backends; a Qiskit Pauli / SparsePauliOp label is little-endian (char 0 = highest qubit). Reversing keeps the convention consistent across backends – centralised here so the Qiskit-family backends (Aer, IQM) cannot drift apart.

Return type:

str

Module contents

Pluggable, user-swappable execution backends for Qdislib.

Public surface:

from Qdislib import QdBackEnd, register_backend

be = QdBackEnd({"AER_BACKEND": {"num_qubits": 30}})
result = be.sample_circuit(circuit, shots=4096)      # -> SampleJobResult
result = be.get_expectation_value(circuit, "ZZI")    # -> ExpectationJobResult

Add your own backend by subclassing BaseBackend and registering it:

register_backend("MY_BACKEND", "my_pkg.my_backend", "MyBackend")

Concrete backend modules import their SDKs lazily, so only the extras you use are required.

class Qdislib.core.backend.BackendConfig(num_qubits, extra=<factory>)[source]

Bases: object

Static configuration for a backend (its size and any extra options).

extra: dict
num_qubits: int
class Qdislib.core.backend.BaseBackend(name, config)[source]

Bases: ABC

Common interface for all Qdislib backends.

FIFO queue management is centralised here via the Template Method pattern: subclasses implement _execute_samples / _execute_estimator / transpile (the hooks), while run_samples / run_estimator orchestrate the full enqueue -> execute -> dequeue lifecycle. The queue length is what QdBackEnd uses to load-balance.

can_run(circuit)[source]
Return type:

bool

evaluate_expectation(dag, num_qubits, prep_qubits=(), shots=1024, method='automatic')[source]

Evaluate the expectation of a Qdislib subcircuit DAG on this backend.

This is the seam the cutting algorithms delegate to instead of branching on the backend name: they hand over the backend-neutral DAG (its Pauli observable rides along as Observable I markers) and the set of prep_qubits (cut qubits measured for the decomposition, whose sign is recovered from the backend’s own mid-circuit outcome). The backend builds its native circuit, executes it, and reconstructs the value. Backends that only support run_samples() / run_estimator() may leave this unimplemented.

Parameters:
  • dag (Any) – A renumbered connected-component DAG of the cut circuit.

  • num_qubits (int) – Qubit count of the component.

  • prep_qubits (Any) – Renumbered cut qubits measured for the decomposition.

  • shots (int) – Number of measurement shots.

  • method (str) – Backend-specific simulation method hint.

Return type:

float

Returns:

The subcircuit’s expectation value.

evaluate_joint_counts(dag, num_qubits, cut_point_qubits=(), shots=1024, seed=None)[source]

Sample a gate-cut subcircuit, capturing the JOINT final/cut-point outcomes.

The seam the sampling reconstruction delegates to – the distribution counterpart of evaluate_expectation(). Gate-cut sampling needs, per shot, the final measurement and the mid-circuit cut-point outcomes of cut_point_qubits kept correlated, so a cut qubit that keeps evolving after the cut still reconstructs correctly (its final bit no longer equals its cut-point bit). Returns {(final_bits, cut_point_bits): count} with final_bits qubit-0-first over the component and cut_point_bits over cut_point_qubits in order.

Backends whose execution yields only aggregated final counts (most real QPUs) cannot report the mid-circuit outcome and leave this unimplemented.

Parameters:
  • dag (Any) – A renumbered connected-component DAG of the cut circuit.

  • num_qubits (int) – Qubit count of the component.

  • cut_point_qubits (Any) – Renumbered cut qubits measured for the decomposition.

  • shots (int) – Number of measurement shots.

Return type:

dict

Returns:

{(final_bits, cut_point_bits): count}.

evaluate_samples(dag, num_qubits, shots=1024, seed=None)[source]

Sample a Qdislib subcircuit DAG and return counts in a standard convention.

Used by the sampling reconstruction of gate cutting. Every backend returns keys as qubit-0-first bitstrings (key[i] is qubit i), regardless of the SDK’s native endianness, so the reconstruction combines components uniformly.

Parameters:
  • dag (Any) – A renumbered connected-component DAG of the cut circuit.

  • num_qubits (int) – Qubit count of the component.

  • shots (int) – Number of measurement shots.

Return type:

dict[str, int]

Returns:

{qubit-0-first bitstring: count}.

property num_qubits: int
property queue_length: int
property queue_snapshot: list[Job]

Return an immutable copy of the current queue (useful for logging / monitoring).

run_estimator(circuit, transpile, observable, shots)[source]

Compute the expectation value of an observable, updating the FIFO queue.

Return type:

ExpectationJobResult

run_samples(circuit, transpile, shots)[source]

Sample the circuit, updating the FIFO queue before and after execution.

Return type:

SampleJobResult

abstractmethod transpile(circuit)[source]

Transpile the circuit into the backend’s native format.

Return type:

Any

class Qdislib.core.backend.CircuitConverter[source]

Bases: object

Converts circuits between the supported formats and reports their size.

static num_qubits(circuit)[source]

Qubit count for any supported circuit, including CUDA-Q payloads.

Return type:

int

static to_qibo(circuit)[source]
static to_qiskit(circuit)[source]
static to_qiskit_pauli(observable)[source]

Reorder a qubit-0-first Pauli string to Qiskit’s little-endian labels.

Qdislib observable strings are qubit-0-first (char i acts on qubit i), matching the Qibo/CUDA-Q backends; a Qiskit Pauli / SparsePauliOp label is little-endian (char 0 = highest qubit). Reversing keeps the convention consistent across backends – centralised here so the Qiskit-family backends (Aer, IQM) cannot drift apart.

Return type:

str

class Qdislib.core.backend.ExpectationJobResult(expectation_value, shots, backend_name, job_id, obs='', metadata=<factory>)[source]

Bases: object

Result of an estimator run: an expectation value plus provenance metadata.

backend_name: str
expectation_value: float
job_id: str
metadata: dict
obs: str = ''
shots: int
class Qdislib.core.backend.Job(job_id, kind, n_qubits, shots, submitted_at=<factory>)[source]

Bases: object

A unit of work tracked in a backend’s FIFO queue.

job_id: str
kind: Literal['samples', 'estimator']
n_qubits: int
shots: int | None
submitted_at: datetime
class Qdislib.core.backend.QdBackEnd(qpu_dict=None)[source]

Bases: object

Unified, load-balancing backend for Qdislib.

Parameters:

qpu_dict

Maps backend names to their config, e.g.:

QdBackEnd({
    "AER_BACKEND": {"num_qubits": 30},
    "IQM_BACKEND": {"num_qubits": 20, "url": "...", "tokens_path": "..."},
})

With no argument, a single default Aer backend is used.

property backends: list[BaseBackend]
get_expectation_value(circuit, observable, shots=1024, transpile=True, backend_name=None)

Estimate <observable>; falls back past backends without estimator support.

Return type:

ExpectationJobResult

sample_circuit(circuit, shots=1024, transpile=True, backend_name=None)

Sample the circuit on the most suitable backend (or a named one).

Return type:

SampleJobResult

class Qdislib.core.backend.SampleJobResult(counts, backend_name, shots, job_id, metadata=<factory>)[source]

Bases: object

Result of a sampling run: measurement counts plus provenance metadata.

backend_name: str
counts: dict[str, int]
job_id: str
metadata: dict
shots: int
Qdislib.core.backend.SupportedCircuit

alias of Any

Qdislib.core.backend.available_backends()[source]

Return the names of all registered backends.

Return type:

list[str]

Qdislib.core.backend.build_backend(name, config)[source]

Lazily import and instantiate a registered backend.

The backend module – and its third-party dependencies – are imported only at this point, not at registry import time.

Return type:

BaseBackend

Qdislib.core.backend.build_execution_backend(software=None, num_qubits=1, *, gpu=False, gpu_min_qubits=None, qpu=False, qpu_dict=None, max_qubit=None, batch=None, ibm_backend=None, **extra)[source]

Select and build the backend that evaluates a cut subcircuit.

The single place that turns the cutting algorithms’ execution knobs (software, gpu, qpu/qpu_dict) into a concrete backend, so the algorithms never branch on the backend/device. Selection order: a large-enough configured QPU (BSC MN_Ona -> BSC_QPU_BACKEND, IBM_Quantum -> IBM_BACKEND), then GPU-eligible Aer, then the software backend (qibo/qiskit/cudaq or the name of any registered backend). Extra keyword arguments (e.g. platform for Qibo) flow into the BackendConfig.

Return type:

BaseBackend

Qdislib.core.backend.register_backend(name, module_path, class_name)[source]

Register a backend without importing it.

Intended for user-defined backends or third-party extensions:

register_backend("MY_BACKEND", "my_package.my_backend", "MyBackend")
Return type:

None