Skip to content

Commit a667885

Browse files
authored
Merge pull request #19 from devqubit-labs/refactor/api-refactor
refactor(api): establish UEC as source-of-truth and redesign public API surface
2 parents 7c8d774 + 194304b commit a667885

124 files changed

Lines changed: 6442 additions & 6589 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,15 @@
88

99
**Local-first experiment tracking for quantum computing.** Capture circuits, backend context, and configuration so runs are reproducible, comparable, and easy to share.
1010

11-
> **Status:** Alpha APIs and bundle formats may evolve in `0.x` releases.
11+
> **Status:** Alpha APIs may evolve in `0.x` releases.
1212
1313
## Why devqubit?
1414

15-
General-purpose experiment trackers (MLflow, Weights & Biases, DVC) are great for logging parameters, metrics, and artifacts. But quantum workloads often need *extra structure* that isnt first-class there by default: capturing what actually executed (program + compilation), where it executed (backend/device), and how it executed (runtime options).
15+
General-purpose experiment trackers (MLflow, Weights & Biases, DVC) are great for logging parameters, metrics, and artifacts. But quantum workloads often need *extra structure* that isn't first-class there by default: capturing what actually executed (program + compilation), where it executed (backend/device), and how it executed (runtime options).
1616

1717
| Challenge | MLflow / W&B / DVC | devqubit |
1818
|-----------|-------------------|----------|
19-
| **Circuit artifacts** | Generic file logging | Autormatic OpenQASM 3, and SDK-native formats (first-class) |
19+
| **Circuit artifacts** | Generic file logging | Automatic OpenQASM 3, and SDK-native formats (first-class) |
2020
| **Device context** | Must be done manually | Automatic backend snapshots, calibration/noise context (first-class) |
2121
| **Reproducibility** | Depends on what you choose to log | Automatic program + device + execution fingerprints to detect what changed |
2222
| **Result comparison** | Metric/table-oriented comparisons | Distribution/structural/drift-aware diffs |
@@ -27,13 +27,13 @@ General-purpose experiment trackers (MLflow, Weights & Biases, DVC) are great fo
2727

2828
## Features
2929

30-
- **Automatic circuit capture** QPY, OpenQASM 3, and native SDK formats
31-
- **SDK adapters** Qiskit, Qiskit Runtime, Amazon Braket, Cirq, PennyLane
32-
- **Content-addressable storage** deduplicated artifacts with SHA-256 digests
33-
- **Reproducibility fingerprints** detect changes in program, device, or configuration
34-
- **Run comparison** TVD analysis, structural diff, drift detection
35-
- **CI/CD verification** verify runs against baselines with configurable policies
36-
- **Portable bundles** export/import runs as self-contained ZIPs
30+
- **Automatic circuit capture** QPY, OpenQASM 3, and native SDK formats
31+
- **SDK adapters** Qiskit, Qiskit Runtime, Amazon Braket, Cirq, PennyLane
32+
- **Content-addressable storage** deduplicated artifacts with SHA-256 digests
33+
- **Reproducibility fingerprints** detect changes in program, device, or configuration
34+
- **Run comparison** TVD analysis, structural diff, drift detection
35+
- **CI/CD verification** verify runs against baselines with configurable policies
36+
- **Portable bundles** export/import runs as self-contained ZIPs
3737

3838
## Documentation
3939

@@ -108,15 +108,15 @@ devqubit diff 01JD7X... 01JD8Y...
108108
Verify that a run matches an established baseline:
109109

110110
```python
111-
from devqubit import verify_against_baseline
111+
from devqubit import verify_baseline
112112
from devqubit.compare import VerifyPolicy
113113

114114
policy = VerifyPolicy(
115115
tvd_threshold=0.05,
116116
require_same_device=False,
117117
)
118118

119-
result = verify_against_baseline(
119+
result = verify_baseline(
120120
candidate_run_id,
121121
project="vqe-hydrogen",
122122
policy=policy,
@@ -199,4 +199,4 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for full guidelines.
199199

200200
## License
201201

202-
Apache 2.0 see [LICENSE](LICENSE).
202+
Apache 2.0 see [LICENSE](LICENSE).

changelog.d/17.changed.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Redesigned public API surface: added high-level `verify_baseline()`, new `devqubit.runs` module for run navigation, `devqubit.errors` for public exceptions, `devqubit.adapters` for extension API, moved low-level symbols to appropriate submodules.

docs/concepts/uec.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ The adapter creates the ExecutionEnvelope and logs it as an artifact. Summary in
135135

136136
```python
137137
import json
138-
from devqubit import create_store, create_registry
138+
from devqubit.storage import create_store, create_registry
139139

140140
store = create_store()
141141
registry = create_registry()

docs/guides/comparison.md

Lines changed: 32 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,7 @@ Most users start with the CLI: {doc}`../reference/cli` → `devqubit diff` and `
1717
Compare two runs to detect differences in parameters, programs, metrics, and results:
1818

1919
```python
20-
from devqubit import diff, create_registry
21-
22-
registry = create_registry()
20+
from devqubit import diff
2321

2422
result = diff("RUN_BASELINE", "RUN_CANDIDATE")
2523

@@ -190,20 +188,17 @@ This is more robust than simple O(√k/n) heuristics, especially for non-uniform
190188
Verify a candidate run against the project's baseline:
191189

192190
```python
193-
from devqubit import create_registry
194-
from devqubit import verify_against_baseline
191+
from devqubit import verify_baseline
195192
from devqubit.compare import VerifyPolicy
196193

197-
registry = create_registry()
198-
199194
policy = VerifyPolicy(
200195
params_must_match=True,
201196
program_must_match=True,
202197
noise_factor=1.0, # Use bootstrap-calibrated threshold
203198
)
204199

205-
result = verify_against_baseline(
206-
candidate=registry.load("RUN_CANDIDATE"),
200+
result = verify_baseline(
201+
"RUN_CANDIDATE",
207202
project="vqe-h2",
208203
policy=policy,
209204
)
@@ -242,13 +237,13 @@ policy = VerifyPolicy(
242237
| `EITHER` | Pass if exact OR structural matches | General use (default) |
243238

244239
```python
245-
from devqubit.compare import VerifyPolicy
240+
from devqubit.compare import VerifyPolicy, ProgramMatchMode
246241

247242
# Strict reproducibility
248-
policy = VerifyPolicy(program_match_mode="exact")
243+
policy = VerifyPolicy(program_match_mode=ProgramMatchMode.EXACT)
249244

250245
# VQE-friendly (ignore parameter values)
251-
policy = VerifyPolicy(program_match_mode="structural")
246+
policy = VerifyPolicy(program_match_mode=ProgramMatchMode.STRUCTURAL)
252247
```
253248

254249
### TVD Thresholds
@@ -281,26 +276,36 @@ policy = VerifyPolicy(tvd_max=0.05, noise_factor=1.2)
281276
## Setting Baselines
282277

283278
```python
284-
from devqubit import create_registry
285-
286-
registry = create_registry()
279+
from devqubit.runs import get_baseline, set_baseline, clear_baseline
287280

288281
# Set baseline
289-
registry.set_baseline("vqe-h2", "RUN_PRODUCTION_V1")
282+
set_baseline("vqe-h2", "RUN_PRODUCTION_V1")
290283

291284
# Get current baseline
292-
baseline = registry.get_baseline("vqe-h2")
293-
print(baseline["run_id"])
285+
baseline = get_baseline("vqe-h2")
286+
if baseline:
287+
print(baseline["run_id"])
288+
289+
# Clear baseline
290+
clear_baseline("vqe-h2")
291+
```
292+
293+
Or via CLI:
294+
295+
```bash
296+
devqubit baseline set vqe-h2 RUN_PRODUCTION_V1
297+
devqubit baseline get vqe-h2
298+
devqubit baseline clear vqe-h2
294299
```
295300

296301
## Auto-Promote on Pass
297302

298303
```python
299-
result = verify_against_baseline(
300-
candidate=candidate,
304+
from devqubit import verify_baseline
305+
306+
result = verify_baseline(
307+
"RUN_CANDIDATE",
301308
project="vqe-h2",
302-
store=store,
303-
registry=registry,
304309
policy=policy,
305310
promote_on_pass=True, # Update baseline if verification passes
306311
)
@@ -313,7 +318,9 @@ result = verify_against_baseline(
313318
Calibration drift is automatically detected during comparison:
314319

315320
```python
316-
result = diff("RUN_A", "RUN_B", registry=registry, store=store)
321+
from devqubit import diff
322+
323+
result = diff("RUN_A", "RUN_B")
317324

318325
if result.device_drift and result.device_drift.significant_drift:
319326
print("! Significant calibration drift detected")
@@ -343,10 +350,10 @@ if result.device_drift and result.device_drift.significant_drift:
343350
### JUnit Output
344351
345352
```python
346-
from devqubit import verify_against_baseline
353+
from devqubit import verify_baseline
347354
from devqubit.ci import write_junit
348355

349-
result = verify_against_baseline(...)
356+
result = verify_baseline("RUN_CANDIDATE", project="vqe-h2")
350357
write_junit(result, "results.xml")
351358
```
352359

docs/guides/configuration.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ import logging
146146
logging.getLogger("devqubit_engine").setLevel(logging.DEBUG)
147147

148148
# Or for specific modules
149-
logging.getLogger("devqubit_engine.core.run").setLevel(logging.DEBUG)
149+
logging.getLogger("devqubit_engine.tracking.run").setLevel(logging.DEBUG)
150150
```
151151

152152
Log levels:

docs/guides/remote_storage.md

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ export DEVQUBIT_REGISTRY_URL="s3://my-bucket/devqubit"
3737

3838
**Programmatic:**
3939
```python
40-
from devqubit import create_store, create_registry
40+
from devqubit.storage import create_store, create_registry
4141

4242
store = create_store("s3://my-bucket/devqubit/objects")
4343
registry = create_registry("s3://my-bucket/devqubit")
@@ -95,6 +95,8 @@ export AWS_DEFAULT_REGION="us-east-1"
9595
Use the `endpoint_url` parameter for S3-compatible services:
9696

9797
```python
98+
from devqubit.storage import create_store
99+
98100
# MinIO
99101
store = create_store(
100102
"s3://my-bucket/devqubit/objects",
@@ -140,7 +142,7 @@ export DEVQUBIT_REGISTRY_URL="gs://my-bucket/devqubit"
140142

141143
**Programmatic:**
142144
```python
143-
from devqubit import create_store, create_registry
145+
from devqubit.storage import create_store, create_registry
144146

145147
store = create_store("gs://my-bucket/devqubit/objects")
146148
registry = create_registry("gs://my-bucket/devqubit")
@@ -169,8 +171,8 @@ export GOOGLE_APPLICATION_CREDENTIALS="/path/to/service-account-key.json"
169171

170172
Assign one of these roles to your service account or user:
171173

172-
- `roles/storage.objectAdmin` Full control of objects
173-
- `roles/storage.objectCreator` + `roles/storage.objectViewer` Create and read objects
174+
- `roles/storage.objectAdmin` Full control of objects
175+
- `roles/storage.objectCreator` + `roles/storage.objectViewer` Create and read objects
174176

175177
Or create a custom role with these permissions:
176178
- `storage.objects.create`
@@ -206,7 +208,7 @@ Both S3 and GCS use the same object layout:
206208
You can use different backends for store and registry:
207209

208210
```python
209-
from devqubit import create_store, create_registry
211+
from devqubit.storage import create_store, create_registry
210212

211213
# Objects in S3, metadata locally (faster queries)
212214
store = create_store("s3://my-bucket/objects")

packages/devqubit-braket/src/devqubit_braket/adapter.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
-------
1313
>>> from braket.circuits import Circuit
1414
>>> from braket.devices import LocalSimulator
15-
>>> from devqubit_engine.core.run import track
15+
>>> from devqubit_engine.tracking.run import track
1616
>>>
1717
>>> circuit = Circuit().h(0).cnot(0, 1)
1818
>>>
@@ -36,10 +36,10 @@
3636
from devqubit_braket.serialization import is_braket_circuit
3737
from devqubit_braket.tracked import TrackedTask, TrackedTaskBatch
3838
from devqubit_braket.utils import extract_task_id, get_backend_name
39-
from devqubit_engine.core.run import Run
40-
from devqubit_engine.uec.envelope import ExecutionEnvelope
39+
from devqubit_engine.tracking.run import Run
40+
from devqubit_engine.uec.models.envelope import ExecutionEnvelope
41+
from devqubit_engine.utils.common import utc_now_iso
4142
from devqubit_engine.utils.serialization import to_jsonable
42-
from devqubit_engine.utils.time_utils import utc_now_iso
4343

4444

4545
logger = logging.getLogger(__name__)

packages/devqubit-braket/src/devqubit_braket/calibration.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,12 @@
2525
from typing import Any
2626

2727
from devqubit_braket.utils import get_nested, obj_to_dict, to_float
28-
from devqubit_engine.uec.calibration import (
28+
from devqubit_engine.uec.models.calibration import (
2929
DeviceCalibration,
3030
GateCalibration,
3131
QubitCalibration,
3232
)
33-
from devqubit_engine.utils.time_utils import utc_now_iso
33+
from devqubit_engine.utils.common import utc_now_iso
3434

3535

3636
def _parse_qubit_key(qubits_key: str) -> list[int]:

packages/devqubit-braket/src/devqubit_braket/envelope.py

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -32,32 +32,29 @@
3232
from devqubit_braket.snapshot import create_device_snapshot
3333
from devqubit_braket.utils import braket_version, get_adapter_version
3434
from devqubit_engine.circuit.models import CircuitFormat
35-
from devqubit_engine.uec.device import DeviceSnapshot
36-
from devqubit_engine.uec.envelope import ExecutionEnvelope
37-
from devqubit_engine.uec.execution import ExecutionSnapshot
38-
from devqubit_engine.uec.producer import ProducerInfo
39-
from devqubit_engine.uec.program import (
35+
from devqubit_engine.storage.types import ArtifactRef
36+
from devqubit_engine.uec.models.device import DeviceSnapshot
37+
from devqubit_engine.uec.models.envelope import ExecutionEnvelope
38+
from devqubit_engine.uec.models.execution import ExecutionSnapshot, ProducerInfo
39+
from devqubit_engine.uec.models.program import (
4040
ProgramArtifact,
41+
ProgramRole,
4142
ProgramSnapshot,
4243
TranspilationInfo,
44+
TranspilationMode,
4345
)
44-
from devqubit_engine.uec.result import (
46+
from devqubit_engine.uec.models.result import (
4547
CountsFormat,
4648
ResultError,
4749
ResultItem,
4850
ResultSnapshot,
4951
)
50-
from devqubit_engine.uec.types import (
51-
ArtifactRef,
52-
ProgramRole,
53-
TranspilationMode,
54-
)
52+
from devqubit_engine.utils.common import utc_now_iso
5553
from devqubit_engine.utils.serialization import to_jsonable
56-
from devqubit_engine.utils.time_utils import utc_now_iso
5754

5855

5956
if TYPE_CHECKING:
60-
from devqubit_engine.core.run import Run
57+
from devqubit_engine.tracking.run import Run
6158

6259

6360
logger = logging.getLogger(__name__)

packages/devqubit-braket/src/devqubit_braket/snapshot.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,12 @@
3030
get_nested,
3131
obj_to_dict,
3232
)
33-
from devqubit_engine.uec.device import DeviceSnapshot
34-
from devqubit_engine.utils.time_utils import utc_now_iso
33+
from devqubit_engine.uec.models.device import DeviceSnapshot
34+
from devqubit_engine.utils.common import utc_now_iso
3535

3636

3737
if TYPE_CHECKING:
38-
from devqubit_engine.core.run import Run
38+
from devqubit_engine.tracking.run import Run
3939

4040
logger = logging.getLogger(__name__)
4141

0 commit comments

Comments
 (0)