Programmatic Usage¶
Overview¶
The same operations exposed by the CLI are available as Python methods on LuciHubClient, imported from anabrid.lucihub.cli.client. The client is a synchronous wrapper around the REST API; it uses httpx internally and supports use as a context manager. It pairs naturally with pybrid-computing for direct manipulation of device objects (modules, integrators, multipliers, routing) and with pyredacc for converting SymPy ODEs into analang source.
Note that the actual compiler (redacc) is currently not publicly available. Contact anabrid for more information.
Installation¶
The Python client is installed alongside lucihub-cli (see Getting Started) — there is no separate package. Bring pybrid-computing and (optionally) pyredacc as direct dependencies of your project when you need their APIs.
Construction¶
import os
from anabrid.lucihub.cli.client import LuciHubClient
hub = LuciHubClient(
base_url=os.getenv("LUCIHUB_URL", "https://redac.anabrid.com"),
timeout=30.0,
api_key=os.environ["LUCIHUB_API_KEY"],
)
| Parameter | Type | Default | Notes |
|---|---|---|---|
base_url |
str | https://redac.anabrid.com |
Base URL of the deployment. |
timeout |
float | 30.0 |
Per-request HTTP timeout in seconds. |
api_key |
str | required | Bearer token. Raises ValueError if absent. |
Use the client as a context manager (with LuciHubClient(...) as hub: ...) or call hub.close() explicitly. The HTTP connection pool is otherwise kept open for the lifetime of the object.
API surface¶
Since the lucihub-cli and lucihub-common wheels are source packages, you are free to point your IDE or your code agent at your installed packages and explore the code for further information. The functions documented below are mostly thin wrappers around the corresponding LUCIHUB API calls.
System / users / devices¶
| Method | Returns | Description |
|---|---|---|
get_system_status() |
SystemStatusResponse |
Domain, version, uptime. |
get_user_info() |
UserInfoResponse |
Profile and rate-limit quota for the authenticated user. |
get_devices() |
list[DeviceResponse] |
All registered devices, with availability and current queue length. |
get_device_entity(device_name) |
bytes |
Raw protobuf entity blob for a device. Raises LuciHubAPIError on missing device or empty entity (e.g. SIMULATOR). |
Task submission¶
| Method | Returns | Description |
|---|---|---|
submit_compile_task(ode_source, backend, module, *, k0=None) |
UUID |
Submit a compile task. backend is DeviceTypeEnum.LUCIDAC or REDAC (SIMULATOR is rejected). module is the device entity blob (bytes); the client base64-encodes it on the wire. |
submit_run_task(device_name, module, *, sample_rate=10000, op_time=0.1) |
UUID |
Submit a run task. module is the compiled APB blob (bytes). |
Task lifecycle and retrieval¶
| Method | Returns | Description |
|---|---|---|
list_tasks(state=None, type=None, limit=None, offset=None) |
list[TaskSummaryResponse] |
Paginated, filterable list scoped to the authenticated user. |
get_task_status(task_id) |
TaskStatusResponse |
Current state and full state history. |
get_task_log(task_id) |
TaskLogResponse |
Worker log lines as a list[str]. |
get_task_detail(task_id) |
CompileTaskDetailResponse | RunTaskDetailResponse |
Type-discriminated detail view (excludes blob data). |
get_task_result(task_id) |
bytes (compile) or JSON-deserialised value (run) |
Compile result is the raw APB; run result is the measurement array [run][channel][sample]. |
cancel_task(task_id) |
TaskStatusResponse |
Request cancellation. Raises StateConflictError (HTTP 409) when the task is not QUEUED. |
delete_task(task_id) |
None |
Permanently delete task and blobs. Raises StateConflictError for INPROGRESS tasks inside the 2-minute grace window. |
wait_for_task_completion(task_id, poll_interval=1.0) |
TaskStatusResponse |
Block until the task reaches a terminal state (SUCCEEDED, FAILED, CANCELLED). |
Errors¶
| Exception | Meaning |
|---|---|
LuciHubAPIError |
Generic API or transport error; raised for non-2xx responses (except 409) and connection failures. |
StateConflictError |
HTTP 409 — operation incompatible with the task's current state. Subclass of LuciHubAPIError. |
Integration with pybrid-computing¶
For a deeper understanding of pybrid-computing (commonly referred to simply as pybrid), please refer to its own documentation at https://anabrid.github.io/pybrid-computing.
pybrid-computing normally needs a direct connection to a device to download its specification and exchange protobuf messages. Lucihub bridges that gap: it caches each device's entity blob and serves it over the REST API. The Python client fetches the blob, the helper build_computer_from_entity (in anabrid.lucihub.cli.entity_tree) deserialises it into a live pybrid Computer / LUCIStack object, and you configure that object locally. The configured object is then re-serialised into a protobuf pb.File envelope and submitted to lucihub as the module of a run task.
from anabrid.lucihub.cli.entity_tree import build_computer_from_entity
import pybrid.base.proto.main_pb2 as pb
from pybrid.base.proto.versioning import ProtoVersioning
entity_bytes = hub.get_device_entity("lucistack")
lucistack = build_computer_from_entity(entity_bytes, device_type="LUCIDAC")
# configure pybrid Computer object (blocks, routing, ADC channels)
cluster = lucistack.entities[0].clusters[0]
cluster.m0block.elements[0].computation.ic = -1.0
cluster.route(m_out=1, u_out=0, c_factor=-1.0, m_in=0)
# ...
module = lucistack.get_serializer()().serialize(lucistack)
file_msg = pb.File(version=ProtoVersioning.current(), module=module)
module_bytes = file_msg.SerializeToString()
run_task_id = hub.submit_run_task(
device_name="lucistack",
module=module_bytes,
sample_rate=100_000,
op_time=0.2,
)
See examples/run_with_pybrid.py and examples/run_with_lucipy.py for end-to-end scripts. The integration currently supports single-circuit mode only; session-based pybrid workflows are not yet bridged.
TODO: the spec (specs/00_overview.md §Example) names a hub.get_pybrid_device(device_name) convenience method that would return a configured pybrid Computer directly. No such method exists on LuciHubClient in the current code; clients call get_device_entity + build_computer_from_entity themselves. Resolve before publishing — either add the convenience method or update the spec.
SymPy → analang via pyredacc¶
The compile endpoint accepts ODEs in analang format only. For workflows that start from SymPy, use pyredacc locally to translate the SymPy expression into analang text, then submit the text as ode_source.
import sympy as sp
from pyredacc import ODEAdapter, SympyODE
t = sp.Symbol("t")
X = sp.Function("X")
sympy_ode = SympyODE(
odes=sp.Eq(X(t).diff(t, 2) + X(t), 0),
initial_conditions={X(t): 1.0},
probes=[X(t), X(t).diff(t)],
name="harmonic",
)
ode_source = ODEAdapter().translate_sympy(sympy_ode)
End-to-end workflow¶
import os
import numpy as np
from anabrid.lucihub.cli.client import LuciHubClient
from anabrid.lucihub.models.enums import TaskStateEnum
hub = LuciHubClient(api_key=os.environ["LUCIHUB_API_KEY"])
DEVICE = "lucistack"
device = next(d for d in hub.get_devices() if d.name == DEVICE)
entity_bytes = hub.get_device_entity(DEVICE)
# 1. compile
compile_id = hub.submit_compile_task(
ode_source=open("harmonic.ana").read(),
backend=device.type,
module=entity_bytes,
)
status = hub.wait_for_task_completion(compile_id)
if status.state != TaskStateEnum.SUCCEEDED:
raise RuntimeError(f"compile failed: {status.error}")
module_bytes = hub.get_task_result(compile_id) # raw APB
# 2. run
run_id = hub.submit_run_task(
device_name=DEVICE,
module=module_bytes,
sample_rate=100_000,
op_time=0.2,
)
status = hub.wait_for_task_completion(run_id)
if status.state != TaskStateEnum.SUCCEEDED:
raise RuntimeError(f"run failed: {status.error}")
runs = hub.get_task_result(run_id) # [run][channel][sample]
result = np.array(runs[0])
For a SymPy-driven variant, replace open("harmonic.ana").read() with the output of ODEAdapter().translate_sympy(...) shown above. The full set of working examples lives in examples/: run_with_analang.py, run_with_sympy.py, run_with_lucipy.py, and run_with_pybrid.py.