Skip to content

API reference

Everything below is generated from the docstrings in limbo_quantum.

Compiler

client

Top-level master interface for the SDK.

DistributedCompilerSDK is the single object a developer instantiates to drive the entire client-side workflow. It owns the active execution provider, holds the parametric template cache, and orchestrates the four-step compilation lifecycle:

Step A — Ingest:    foreign circuit object → standardized IR
                    (CircuitParser.from_qiskit / from_cirq /
                     from_openqasm3, auto-detected by input type).

Step B — Lint:      LocalStaticLinter runs the capacity audit and
                    hotspot detection over the IR. Capacity
                    rejections short-circuit the call BEFORE any
                    network traffic.

Step C — Template:  look up the parametric cache by structural
                    signature. Cache hit ⇒ bind new floats onto
                    the cached unbound circuit locally. Cache miss
                    ⇒ register the circuit, full compile path.

Step D — Dispatch:  hand the resulting QASM payload(s) to the
                    registered QuantumProvider (local sim or
                    cloud HTTP driver) and wait for measurement
                    counts. The provider's own telemetry feeds
                    into the same logger so the call site sees
                    one continuous timeline.

The SDK is the only file end-users need to import. Internally it delegates to ingestion / linter / optimizer / provider modules — they remain individually importable for advanced use cases (custom IR inspection, standalone linting, etc.) but most workflows route through this client.

CompileResult dataclass

Everything one .compile() call produces — the artifact bundle the developer's workspace receives.

Fields

source_framework, num_qubits, num_gates Quick-look metadata extracted in Step A. ir Full standardized IR dict from CircuitParser. Useful for programmatic post-compile inspection. lint_report Output of LocalStaticLinter.run_all() — capacity + hotspot sections. {"skipped": True} when lint was bypassed. structural_signature SHA-256 over the unbound topology. Stable across parameter sweeps; changes when the circuit structure does. The template-cache key. template_hit True when this compile reused a cached TopologicalTemplate (Step C streaming path). False on the first call OR when the circuit isn't parametric. compiled_qasm List of OpenQASM-2 source strings — one per sub-circuit when partitioned, one for the whole circuit otherwise. This is the "multi-file OpenQASM" delivery. job_result JobResult from the provider's run/submit. None when execute=False was passed. counts Convenience pass-through of job_result.counts. telemetry_summary TelemetryLogger.summary() snapshot. Each step duration + metrics in chronological order.

DistributedCompilerSDK

One object, one workflow.

Configure once at construction (provider, capacity constraints, telemetry), call .compile() as many times as you like. The template cache persists across calls so a VQE loop that re-submits the same parametric circuit with different floats benefits automatically — no extra wrapper class required.

Parameters:

Name Type Description Default
provider QuantumProvider

A QuantumProvider instance — either LocalSimulationProvider, CloudProductionProvider, or a third-party subclass.

required
num_qpus Optional[int]

Number of physical QPUs in the target multi-chip envelope. Used by the capacity linter. None disables the capacity check entirely.

None
max_capacity_per_qpu Optional[int]

Qubit count per QPU. Together with num_qpus defines the upper bound on circuit size the linter will accept.

None
hotspot_threshold_ratio float

Fraction of total interaction degree that flags a qubit as a hub during hotspot detection. Default 0.30 (a qubit holding >30% of total degree is reported).

0.3
telemetry Optional[TelemetryLogger]

Optional TelemetryLogger for step timings and progress output. Defaults to a silent logger.

None
skip_lint bool

Skip the capacity + hotspot lint pass. Useful when iterating on a known-good circuit and you don't want the overhead. Default False.

False
skip_template_cache bool

Disable the parametric template cache. Forces a full compile on every call. Default False.

False

Example::

from limbo_quantum import (
    DistributedCompilerSDK, LocalSimulationProvider, TelemetryLogger,
)

sdk = DistributedCompilerSDK(
    provider=LocalSimulationProvider(),
    num_qpus=4,
    max_capacity_per_qpu=8,
    telemetry=TelemetryLogger(),
)
result = sdk.compile(my_qiskit_circuit, shots=1024)
print(result.counts)

compile

compile(circuit: Any, *, parameter_values: Optional[ParameterArray] = None, shots: int = 1024, execute: bool = True, timeout: float = 300.0, **provider_options: Any) -> CompileResult

Run the full client-side workflow on circuit.

Parameters:

Name Type Description Default
circuit Any

One of: - qiskit.QuantumCircuit - cirq.Circuit - OpenQASM-3 source string The type is auto-detected; the right CircuitParser method is dispatched.

required
parameter_values Optional[ParameterArray]

For parametric circuits only. Either a list[float] in circuit.parameters order, or a dict[str, float] keyed by Parameter name. Required when execute=True and circuit.parameters is non-empty.

None
shots int

number of measurement shots.

1024
execute bool

If False, run Steps A–C only and return the compiled QASM bundle without invoking the provider. Useful for offline inspection / cost-estimation.

True
timeout float

maximum seconds to wait for the provider job.

300.0
**provider_options Any

Forwarded to provider.submit(...). For CloudProductionProvider this is where device=, credentials=, etc. land.

{}

Returns:

Type Description
CompileResult

CompileResult with the full set of artifacts.

Raises:

Type Description
TopologyCapacityError

When the Step B capacity audit rejects the layout.

TypeError / ValueError

On malformed inputs caught at each stage.

reset_template_cache

reset_template_cache() -> None

Drop every entry in the parametric template cache. Next .compile() call will treat all circuits as fresh. Useful when a user has structurally changed circuits between calls.

cached_signatures

cached_signatures() -> list[str]

Read-only list of structural signatures currently cached.

quickstart

quickstart(*, num_qpus: Optional[int] = None, max_capacity_per_qpu: Optional[int] = None, telemetry: Optional[TelemetryLogger] = None) -> DistributedCompilerSDK

One-liner SDK construction with a local simulator backend.

Equivalent to:

DistributedCompilerSDK(
    provider=LocalSimulationProvider(),
    num_qpus=num_qpus,
    max_capacity_per_qpu=max_capacity_per_qpu,
    telemetry=telemetry,
)

Use this for laptop development. For production swap in CloudProductionProvider via the regular constructor.

Providers

provider

Environment management + execution drivers.

The SDK's compilation pipeline (ingestion → linter → optimizer) is intentionally independent of WHERE the compiled circuit ultimately runs. This module is the boundary layer: a thin abstract base (QuantumProvider) plus two concrete drivers — one that runs on the developer's laptop and one that ships work to a remote cloud backend.

QuantumProvider              abstract execution context. Defines the
                             submit/poll/run contract every driver
                             must satisfy. Compiler code calls into
                             this interface and stays unaware of
                             whether the work is local or remote.

LocalSimulationProvider      qiskit-aer state-vector or matrix-product-
                             state simulator running in-process. Zero
                             network, zero credentials, intended for
                             fast debugging cycles before a developer
                             spends budget on cloud time.

CloudProductionProvider      authenticated HTTP client for the
                             hosted Limbo compile service. Handles
                             credential headers, request timeouts,
                             exponential-backoff retry on transient
                             failures, and async job polling. Sign
                             up at https://limbosys.dev to get an
                             API key.

TelemetryLogger              unified status reporter. Detects whether
                             it's being driven from a Jupyter cell
                             vs a plain terminal and formats step
                             timings, payload sizes, and phase
                             transitions consistently in either
                             host. Drop-in optional — providers run
                             silently when no logger is attached.

TimedStep

Context manager returned from TelemetryLogger.step(). Times the enclosed block, accumulates child metrics, and emits a one-line summary when the block exits.

metric

metric(name: str, value: Any, unit: str = '') -> None

Attach a metric to this step. Reported immediately AND rolled up in the closing summary.

TelemetryLogger

Unified terminal + Jupyter status display.

Three event types: section header, timed step (context manager), and ad-hoc metric. The renderer auto-switches between plain-print (terminal) and an indented-line display (Jupyter) — Jupyter doesn't actually need a different code path here because plain print() output flushes cleanly into the cell output area.

Usage:

log = TelemetryLogger(verbose=True)
log.section("Compilation")
with log.step("Hypergraph slicing") as s:
    ...
    s.metric("cut_size", 14)
    s.metric("payload_kb", 87.3, unit="KiB")
with log.step("Transpile per QPU") as s:
    ...
log.section("Execution")
with log.step("Cloud submit") as s:
    s.metric("api_round_trip_ms", 240, unit="ms")

section

section(title: str) -> None

Visual divider for a group of related steps.

step

step(label: str) -> TimedStep

Context-managed timed step. Auto-prints start/end lines.

metric

metric(name: str, value: Any, unit: str = '') -> None

Free-standing metric, not associated with a step.

summary

summary() -> dict[str, Any]

Aggregate everything the logger has seen so far. Useful for Jupyter cells that want to surface a single end-of-compile report rather than the play-by-play.

JobResult dataclass

Result of a single execution. status is the lifecycle state; counts is populated only when status == 'completed'.

Job

Handle to a submitted execution.

Provider.submit() returns a Job. The provider is responsible for implementing _fetch_status() — Job itself is provider-agnostic and just orchestrates the poll loop.

status

status() -> str

Force a status refresh and return the lifecycle state.

result

result(*, timeout: float = DEFAULT_TIMEOUT_SECONDS, poll_interval: float = DEFAULT_POLL_INTERVAL) -> JobResult

Block until the job reaches a terminal state, then return the full result. Raises TimeoutError if the deadline is hit.

For LocalSimulationProvider the first call returns immediately because execution was synchronous; the poll loop adds no meaningful latency.

QuantumProvider

Bases: ABC

Common interface for any execution context the SDK can drive.

Subclasses implement three primitives:

submit(payload, *, shots, **options) -> Job
_fetch_status(job_id)                 -> JobResult
list_devices()                        -> list[dict]

plus the public run() helper inherits a default poll-and-return implementation. The contract is intentionally narrow so future providers (vendor-specific cloud, on-prem cluster, etc.) can slot in without restructuring the SDK.

submit abstractmethod

submit(payload: Any, *, shots: int = 1024, **options: Any) -> Job

Submit work and return a Job handle. Provider-specific options (device, credentials, etc.) flow through **options.

list_devices abstractmethod

list_devices() -> list[dict[str, Any]]

Available execution targets on this provider. Each entry is a dict with at least {'name', 'num_qubits', 'simulator': bool}.

run

run(payload: Any, *, shots: int = 1024, timeout: float = 300.0, poll_interval: float = DEFAULT_POLL_INTERVAL, **options: Any) -> JobResult

Submit and block until done. The hot loop in parametric_runtime.run() should prefer the asynchronous submit→poll pattern; this is a convenience for one-shot scripts / Jupyter cells.

LocalSimulationProvider

Bases: QuantumProvider

In-process execution driver backed by qiskit-aer.

Zero credentials, zero network, results returned synchronously. Use this for development and to validate that your circuit pipeline works before pointing at a real hardware backend.

Parameters:

Name Type Description Default
method str

Simulator method passed to Aer. One of:

       ``"automatic"`` (default) — Aer picks based on
       the circuit structure.
       ``"statevector"`` — full-amplitude, up to ~30
       qubits.
       ``"matrix_product_state"`` — tensor-network,
       scales to 50+ qubits for low-entanglement
       circuits.
       ``"density_matrix"`` — for noise simulation.
       ``"stabilizer"`` — for Clifford-only circuits.
'automatic'
device_name str

Logical device name returned in result metadata. Defaults to "aer_simulator". Purely cosmetic; does not affect execution semantics.

'aer_simulator'
telemetry Optional[TelemetryLogger]

Optional TelemetryLogger for progress output.

None

CloudProductionProvider

Bases: QuantumProvider

HTTP driver for the hosted Limbo compile service.

Sign up at https://limbosys.dev to get an API key. The service holds your vendor credentials (IBM / AWS Braket / Azure Quantum) encrypted server-side and submits jobs on your behalf, so you only need one credential in your client code.

Parameters:

Name Type Description Default
api_key str

lmb_live_... token from your dashboard.

required
server_url str

Base URL of the API. Defaults to the hosted service; override if you're running a self- hosted backend.

required
timeout_seconds float

Per-request HTTP timeout. Default 60s.

DEFAULT_TIMEOUT_SECONDS
max_retries int

Retry budget for transient failures (timeouts, 5xx). Default 3.

DEFAULT_MAX_RETRIES
backoff_factor float

Exponential-backoff base. Default 0.5s.

DEFAULT_BACKOFF_FACTOR
telemetry Optional[TelemetryLogger]

Optional TelemetryLogger for progress output.

None
Request lifecycle
  1. submit() POSTs the compiled QASM to /compile with an X-API-Key header. On transient failures (timeout, 5xx) it retries with exponential backoff.
  2. The server returns one or more job IDs. We wrap each in a Job and return the first.
  3. Job.result() polls /jobs/{id} at the configured interval until a terminal state.

auto_provider

auto_provider(*, api_key: Optional[str] = None, server_url: Optional[str] = None, telemetry: Optional[TelemetryLogger] = None) -> QuantumProvider

Pick a provider based on what credentials are reachable.

If api_key AND server_url are supplied (either as args or via LIMBO_API_KEY / LIMBO_SERVER_URL env vars), return a CloudProductionProvider. Otherwise fall back to LocalSimulationProvider.

This is the recommended default for end-user scripts that want to transparently route to the cloud when credentials are configured and to the local sim during development.

vendors

First-class vendor providers.

Three ready-to-use QuantumProvider subclasses that wrap the official vendor SDKs:

IBMProvider     - qiskit-ibm-runtime
AWSProvider     - amazon-braket-sdk
AzureProvider   - azure-quantum

Each one lazy-imports its vendor dependency at construction time, so a user who only has Qiskit installed can still import limbo_quantum without errors and only pays the import cost when they actually instantiate the vendor they need. Install the corresponding extra to pull the SDK in::

pip install limbo-quantum[ibm]      # qiskit-ibm-runtime
pip install limbo-quantum[aws]      # amazon-braket-sdk
pip install limbo-quantum[azure]    # azure-quantum

You're not limited to these three. Anything that can ingest QASM and return measurement counts can be plugged in by subclassing QuantumProvider directly — see provider.py for the (small) contract.

IBMProvider

Bases: QuantumProvider

IBM Quantum execution backend via qiskit-ibm-runtime.

Parameters:

Name Type Description Default
token str

IBM Quantum API token (from quantum.ibm.com).

required
backend_name Optional[str]

Name of the IBM backend to target (e.g. "ibm_brisbane", "ibm_torino"). If None, defaults to "ibmq_qasm_simulator".

None
channel str

"ibm_quantum_platform" (default) or "ibm_cloud". IBM deprecated the old "ibm_quantum" channel in 2024.

'ibm_quantum_platform'
telemetry Optional[TelemetryLogger]

Optional TelemetryLogger.

None

Example::

from limbo_quantum import DistributedCompilerSDK, IBMProvider

sdk = DistributedCompilerSDK(
    provider=IBMProvider(token="...", backend_name="ibm_brisbane"),
    num_qpus=1, max_capacity_per_qpu=127,
)
result = sdk.compile(my_circuit, shots=1024)

AWSProvider

Bases: QuantumProvider

AWS Braket execution backend via amazon-braket-sdk.

Parameters:

Name Type Description Default
device_arn Optional[str]

ARN of the Braket device, e.g. "arn:aws:braket:us-east-1::device/qpu/ionq/Aria-1". Defaults to the SV1 cloud simulator.

None
s3_folder Optional[tuple[str, str]]

Optional (bucket, prefix) tuple for Braket to write results to. If omitted, Braket uses the account default (recommended for hosted simulators).

None
telemetry Optional[TelemetryLogger]

Optional TelemetryLogger.

None

AWS credentials are read from the standard boto3 chain: AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY env vars, ~/.aws/credentials, or an IAM role attached to the runtime.

Example::

from limbo_quantum import DistributedCompilerSDK, AWSProvider

sdk = DistributedCompilerSDK(
    provider=AWSProvider(
        device_arn="arn:aws:braket:::device/quantum-simulator/amazon/sv1",
    ),
    num_qpus=1, max_capacity_per_qpu=34,
)

AzureProvider

Bases: QuantumProvider

Azure Quantum execution backend via azure-quantum.

Parameters:

Name Type Description Default
subscription_id str

Azure subscription ID.

required
resource_group str

Resource group hosting the Quantum workspace.

required
workspace_name str

Quantum workspace name.

required
location str

Azure region, e.g. "eastus".

required
target_name str

Backend target within the workspace, e.g. "ionq.qpu", "quantinuum.qpu.h1-1".

required
telemetry Optional[TelemetryLogger]

Optional TelemetryLogger.

None

Authentication uses DefaultAzureCredential from azure-identity — i.e. az login on the local machine, a managed identity in production, or service-principal env vars.

Example::

from limbo_quantum import DistributedCompilerSDK, AzureProvider

sdk = DistributedCompilerSDK(
    provider=AzureProvider(
        subscription_id="...",
        resource_group="quantum-rg",
        workspace_name="my-workspace",
        location="eastus",
        target_name="ionq.qpu",
    ),
    num_qpus=1, max_capacity_per_qpu=23,
)

Ingestion

ingestion

Multi-framework circuit ingestion + AST translation.

CircuitParser translates Qiskit / Cirq / OpenQASM-3 inputs into a standardized intermediate representation (IR) dictionary that the rest of the SDK operates on. All parsing happens on the developer's machine — no network calls, no cloud round-trips.

Framework SDKs (qiskit, cirq, openqasm3) are imported LAZILY at method call time so this module imports cleanly with only the Python standard library available; the developer only needs the framework that matches the circuit they actually feed in.

IR schema (versioned):

{
    "version":           "1.0",
    "source_framework":  "qiskit" | "cirq" | "openqasm3",
    "num_qubits":        int,
    "num_clbits":        int,
    "gates": [
        {
            "name":   str,            # gate identifier, lowercase
            "qubits": list[int],      # global qubit indices
            "params": list[float | {"symbolic": str}]
        },
        ...
    ],
    "interaction_pairs": [
        {"a": int, "b": int, "count": int}   # a < b, multi-qubit edges
        ...
    ],
    "qubit_degrees":   dict[int, int],
    "parameters":      dict[str, dict]       # symbolic parameter registry
}

The IR is JSON-serializable end-to-end (no Python-specific types) so the linter and any downstream cloud-API call can consume it without extra conversion.

IRSchema module-attribute

IRSchema = {'version': '1.0', 'required_keys': ('version', 'source_framework', 'num_qubits', 'num_clbits', 'gates', 'interaction_pairs', 'qubit_degrees', 'parameters')}

IR schema descriptor. See block comment above for the shape contract.

CircuitParser

AST translation engine from foreign circuit objects to our IR.

All methods are staticmethods — there's no parser state to carry between calls. Each method is the entry point for one source framework and is fully isolated from the others.

from_qiskit staticmethod

from_qiskit(circuit_obj: Any) -> dict[str, Any]

Convert a qiskit.QuantumCircuit into the standardized IR.

Raises:

Type Description
ImportError

if qiskit isn't installed.

TypeError

if circuit_obj is not a qiskit.QuantumCircuit.

from_cirq staticmethod

from_cirq(circuit_obj: Any) -> dict[str, Any]

Convert a cirq.Circuit into the standardized IR.

Raises:

Type Description
ImportError

if cirq isn't installed.

TypeError

if circuit_obj is not a cirq.Circuit.

from_openqasm3 staticmethod

from_openqasm3(string_payload: str) -> dict[str, Any]

Convert an OpenQASM-3 source string into the standardized IR.

Supports the common case: qubit declarations + gate statements + measurements. Control-flow (if/for/while) and box statements are skipped silently — the IR they would produce is dynamic and out of scope for static analysis.

Raises:

Type Description
ImportError

if openqasm3 isn't installed.

TypeError

if string_payload is not a str.

ValueError

if the source fails to parse or references an undeclared qubit register.

Linting

linter

Offline static analysis over the parsed circuit IR.

LocalStaticLinter runs entirely on the developer's machine. The intent is to catch problems BEFORE the developer pays for a cloud compilation round-trip:

1. Capacity audit — does the circuit fit on the target multi-QPU
   layout? Raises TopologyCapacityError if not.
2. Hotspot detection — single-pass degree analysis to flag qubits
   that will dominate cross-QPU communication.

Zero network dependencies; pure stdlib (statistics).

TopologyCapacityError

Bases: Exception

Raised when an IR circuit demands more qubits than the target multi-QPU layout can physically provide.

Attributes:

Name Type Description
num_qubits

Qubit count the circuit asked for.

target_capacity

Total slots in the layout (num_qpus * max_capacity_per_qpu).

min_qubits_per_qpu

The smallest per-QPU capacity that would have made the layout feasible, or None if not derivable.

These attributes let calling code surface a precise error without re-deriving them from the layout config.

LocalStaticLinter

Static structural analyzer for parsed circuit IR.

Constructor parameters

ir Standardized IR dict produced by CircuitParser. Must include keys: version, num_qubits, interaction_pairs, qubit_degrees. num_qpus Target number of QPU modules in the multi-chip layout. Optional — capacity_audit() is skipped if not provided. max_capacity_per_qpu Per-QPU logical qubit budget. Optional but pairs with num_qpus.

Both diagnostics are O(n) over the gate list / interaction graph; safe to run on every developer save without measurable cost.

capacity_audit

capacity_audit() -> dict[str, Any]

Compare circuit qubit demand to the configured multi-QPU layout.

Returns:

Type Description
dict[str, Any]

A status dict suitable for logging / reporting: { "check": "capacity_audit", "status": "passed" | "skipped", "num_qubits": int, "total_capacity": int, "utilization": float, # 0..1 "min_qubits_per_qpu": int, # ceil(n / k) }

hotspot_detection

hotspot_detection(*, threshold_ratio: float = 0.3, verbose: bool = True) -> dict[str, Any]

Linear-time scan over qubit_degrees to flag interaction bottlenecks.

A qubit is flagged as a hotspot when its degree (total endpoint count across all multi-qubit gates) exceeds threshold_ratio × total_degree. With the default 0.30, any single qubit responsible for more than 30% of the cross-qubit traffic gets surfaced.

Parameters:

Name Type Description Default
threshold_ratio float

hotspot threshold as a fraction of total degree (0, 1].

0.3
verbose bool

whether to print the warning dashboard to stdout when hotspots are found.

True

Returns:

Type Description
dict[str, Any]

{ "check": "hotspot_detection", "status": "ok" | "warning" | "no_interactions", "hotspots": [{"qubit": int, "degree": int, "share": float}, ...], "mean_degree": float, "stddev_degree": float, "max_degree": int, "total_degree": int, "threshold_ratio": float,

dict[str, Any]

}

run_all

run_all(*, verbose: bool = True) -> dict[str, Any]

Run both diagnostics. Capacity is checked first so a hard capacity error short-circuits the (cheaper) hotspot check.

Returns a combined report. If capacity_audit() raises, the exception propagates — caller decides whether to catch it and continue or abort the workflow.

Variational runtime

optimizer

High-efficiency variational runtime for VQE / QAOA loops.

In a variational algorithm, the classical optimizer iterates 10^2–10^3 times, each step changing only the floating-point parameter array — the circuit's gate topology, qubit interaction graph, and partition layout are invariant across the entire campaign. Naively re-compiling the circuit on every iteration re-runs KaHyPar partitioning, re-transpiles against the device coupling map, and re-uploads the full QASM payload for what amounts to a few-dozen-float diff. This module fixes that.

Architecture:

TopologicalTemplate    Client-side cache. Holds the heavy structural
                       artifacts produced by the first compile pass:
                       partition map, per-QPU sub-circuit templates
                       with parameters STILL UNBOUND, sync-barrier
                       coordinates, and the structural signature
                       that validates the cache is still consistent
                       with the in-memory parametric circuit.

CompilerClient         Protocol the runtime talks to. Two methods:
                         compile_template()        — heavy, once
                         execute_bound_template()  — light, every iter
                       Two shipped implementations: in-process
                       (qiskit-aer + local transpile, no network)
                       and HTTP (talks to a hosted Limbo backend
                       with template endpoints).

ParametricRuntime      Loop wrapper. First .run() call invokes
                       client.compile_template() and caches the
                       result. Every subsequent .run() binds the new
                       parameter floats LOCALLY into the cached
                       sub-circuits and ships only the bound payloads
                       (or, in the HTTP case, only a parameter delta
                       keyed by template_id) to the execution target.

The design intent: cut per-iteration data motion from "entire circuit" down to "len(parameter_array) floats". For a 100-step VQE on a 64-qubit circuit, that's a ~99× reduction in compile-pipeline work and a similar reduction in HTTP payload size.

TopologicalTemplate dataclass

Client-side cache of one parametric circuit's structural compile.

Everything in this dataclass is independent of parameter values. The runtime never mutates these fields after the first compile pass — only last_parameter_values (a debug breadcrumb) updates.

Fields

structural_signature Deterministic hash over the gate sequence (names + qubit indices + which params are symbolic vs numeric). Used to detect when the user has changed the circuit's structure (added/removed gates, re-wired qubits) and the cache must be invalidated. parameter_names Ordered tuple of Parameter.name strings matching circuit.parameters. Establishes the canonical positional ordering that maps caller-supplied float arrays to Qiskit Parameter objects. sub_circuits_template Per-QPU QuantumCircuit objects with parameters STILL UNBOUND. Each iteration calls .assign_parameters() on these to produce executable sub-circuits — cheap dict substitution, no transpile, no partitioning. partition_map Logical qubit → QPU index map. Snapshotted from the first compile; never recomputed. sync_barriers List of {sub_circuit_index, position, paired_qubits} entries marking cross-QPU synchronization points. Carried through every iteration so the execution target can replay the same coordination plan. provider / device / num_qpus Hardware target context. Pinned to the same backend across the entire optimizer campaign — a parameter sweep that jumps providers mid-flight would invalidate the cache. template_id Server-side handle if the compile_template() implementation registered the template with a remote backend. None for purely in-process compilers. last_parameter_values Most recent parameter array bound through this template. Debug aid only — not used in any control flow.

matches_circuit

matches_circuit(parameter_names: tuple[str, ...]) -> bool

Cheap pre-flight check: does the in-memory parameter order still line up with the cache? Doesn't validate the deeper structural signature — that requires re-hashing the circuit and is the caller's job to do via signature_matches.

bind

bind(parameter_values: ParameterArray) -> list

Bind a parameter array onto every cached sub-circuit template and return the resulting numeric-only sub-circuits. PURE LOCAL — no transpile pass, no partitioner, no network. This is the hot path called once per VQE/QAOA iteration.

CompilerClient

Bases: Protocol

Backend the ParametricRuntime talks to. Two methods, one cold path + one hot path.

compile_template

compile_template(circuit: Any, *, provider: Optional[str], device: Optional[str], num_qpus: Optional[int], max_capacity_per_qpu: Optional[int], credentials: Optional[dict[str, Any]]) -> dict[str, Any]

Heavy initial compile. Returns a dict shaped:

{ "sub_circuits": list[qiskit.QuantumCircuit], # parameters STILL UNBOUND "partition_map": dict[int, int], "sync_barriers": list[dict], "provider": str, "device": str, "num_qpus": int, "template_id": str | None, # server-side handle for streaming # parameter updates, when supported "server_metadata": dict, # basis_gates, coupling_map, ... }

execute_bound_template

execute_bound_template(bound_sub_circuits: list, *, template_id: Optional[str], parameter_values: list[float], provider: str, device: str, shots: int, credentials: Optional[dict[str, Any]]) -> dict[str, Any]

Submit the parameter-bound sub-circuits for execution. Hot path — must NOT re-partition or re-transpile. Returns:

{
    "counts":   dict[str, int] | None,
    "status":   "completed" | "queued" | "running" | "failed",
    "job_ids":  list[str],
}

When template_id is non-None and the backend supports it, the client should send {template_id, parameter_values} over the wire and let the server bind on its side — the multi-megabyte QASM round-trip is avoided entirely.

InProcessCompilerClient

Pure-Python reference implementation. Runs transpile + execute in the current Python process via qiskit-aer. No network. Useful for unit tests, local development, and demonstrating that the streaming-update flow yields a real speedup even without HTTP.

HTTPCompilerClient

Thin client for a hosted Limbo backend (or any service that implements the same template endpoint contract).

This implementation assumes two endpoints exist on the server that aren't in the public API surface yet — they're the natural extension needed to make the streaming-parameter pattern actually save bandwidth:

POST /compile/template
    Body: {qasm, provider, device, num_qpus, max_capacity,
           credentials}
    Returns: {template_id, partition_map, sync_barriers,
              provider, device, num_qpus, server_metadata}
             (the actual sub-circuit QASM is decoded client-side;
              they were already attached to the response in
              full form. After this call the server caches the
              structural artifacts keyed by template_id.)

POST /compile/template/{template_id}/execute
    Body: {parameter_values: list[float], shots, credentials}
    Returns: {counts, status, job_ids}

Until those endpoints land, fall back to InProcessCompilerClient for development, or shim them on top of the existing /compile endpoint by uploading the bound QASM each iteration (slower but functional).

ParametricRuntime

Execution loop wrapper for variational algorithms.

Usage:

from qiskit.circuit import Parameter, QuantumCircuit
from limbo_quantum import ParametricRuntime, InProcessCompilerClient

theta = [Parameter(f"theta_{i}") for i in range(4)]
qc = QuantumCircuit(4)
for i, t in enumerate(theta):
    qc.ry(t, i)
for i in range(3):
    qc.cx(i, i + 1)

rt = ParametricRuntime(qc, client=InProcessCompilerClient())

def cost(params):
    out = rt.run(parameter_values=params, shots=1024)
    return compute_energy(out["counts"])

# scipy.optimize iterates 100s of times; only the FIRST call
# actually invokes the compiler. Every subsequent step binds
# parameters locally and ships only the float array.
result = scipy.optimize.minimize(cost, x0=[0.0]*4, method="COBYLA")

template property

template: Optional[TopologicalTemplate]

Read-only view of the cached compile result. None until the first run() invocation.

reset_cache

reset_cache() -> None

Discard the cache. Next .run() will trigger a fresh heavy compile. Call this when you've intentionally restructured the circuit (added gates, re-wired qubits) — the structural signature would otherwise mismatch and the runtime would refuse to bind.

run

run(*, parameter_values: ParameterArray, shots: int = 1024) -> dict[str, Any]

Execute the parametric circuit at the given parameter values.

Hot path on iterations 2+: 1. Validate the cache against the in-memory circuit. 2. Bind parameter_values into the cached sub-circuit templates locally. 3. Stream the bound payload (or, with a streaming-capable backend, just the float array) to the execution target. 4. Return measurement counts.

First-iteration cold path additionally invokes client.compile_template() and builds the TopologicalTemplate.