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 |
required |
num_qpus
|
Optional[int]
|
Number of physical QPUs in the target
multi-chip envelope. Used by the
capacity linter. |
None
|
max_capacity_per_qpu
|
Optional[int]
|
Qubit count per QPU. Together with
|
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 |
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
|
skip_template_cache
|
bool
|
Disable the parametric template cache.
Forces a full compile on every call.
Default |
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
|
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 |
{}
|
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
¶
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
¶
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
¶
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")
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.
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 work and return a Job handle. Provider-specific options (device, credentials, etc.) flow through **options.
list_devices
abstractmethod
¶
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'
|
device_name
|
str
|
Logical device name returned in result metadata.
Defaults to |
'aer_simulator'
|
telemetry
|
Optional[TelemetryLogger]
|
Optional |
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
|
|
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 |
None
|
Request lifecycle
submit()POSTs the compiled QASM to/compilewith anX-API-Keyheader. On transient failures (timeout, 5xx) it retries with exponential backoff.- The server returns one or more job IDs. We wrap each in a
Joband return the first. 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.
|
None
|
channel
|
str
|
|
'ibm_quantum_platform'
|
telemetry
|
Optional[TelemetryLogger]
|
Optional |
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.
|
None
|
s3_folder
|
Optional[tuple[str, str]]
|
Optional |
None
|
telemetry
|
Optional[TelemetryLogger]
|
Optional |
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. |
required |
target_name
|
str
|
Backend target within the workspace, e.g.
|
required |
telemetry
|
Optional[TelemetryLogger]
|
Optional |
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
¶
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
¶
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
¶
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
( |
|
min_qubits_per_qpu |
The smallest per-QPU capacity that would
have made the layout feasible, or |
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
¶
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
¶
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 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
¶
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 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
¶
Read-only view of the cached compile result. None until the first run() invocation.
reset_cache
¶
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
¶
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.