Skip to content

Commit 644d76f

Browse files
authored
Merge pull request #50 from devqubit-labs/refactor/adapres-hardening
refactor(adapters): eliminate redundant computation, dead code, and add missing test coverage across adapters
2 parents bc8e848 + a007dd9 commit 644d76f

38 files changed

Lines changed: 2482 additions & 421 deletions

File tree

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

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
from dataclasses import dataclass, field
2929
from typing import Any
3030

31-
from devqubit_braket.circuits import compute_parametric_hash, compute_structural_hash
31+
from devqubit_braket.circuits import compute_circuit_hashes
3232
from devqubit_braket.envelope import create_envelope, log_submission_failure
3333
from devqubit_braket.execution import TrackedTask, TrackedTaskBatch
3434
from devqubit_braket.serialization import is_braket_circuit
@@ -175,7 +175,10 @@ def _materialize_task_spec(
175175
if is_braket_circuit(task_specification):
176176
return task_specification, [task_specification], True, None
177177

178-
if isinstance(task_specification, (list, tuple)):
178+
if isinstance(task_specification, list):
179+
return task_specification, task_specification, False, None
180+
181+
if isinstance(task_specification, tuple):
179182
circuit_list = list(task_specification)
180183
return circuit_list, circuit_list, False, None
181184

@@ -353,7 +356,11 @@ def run_batch(
353356
"""
354357
device_name = get_backend_name(self.device)
355358
submitted_at = utc_now_iso()
356-
circuits_for_logging = list(task_specifications)
359+
circuits_for_logging = (
360+
task_specifications
361+
if isinstance(task_specifications, list)
362+
else list(task_specifications)
363+
)
357364

358365
# Prepare execution context
359366
ctx = self._prepare_execution_context(
@@ -428,10 +435,11 @@ def _prepare_execution_context(
428435
self._execution_count += 1
429436
exec_count = self._execution_count
430437

431-
# Compute hashes
432-
structural_hash = compute_structural_hash(circuits_for_logging)
438+
# Compute both hashes in one pass (avoids 2× circuit_to_op_stream)
433439
inputs = kwargs.get("inputs")
434-
parametric_hash = compute_parametric_hash(circuits_for_logging, inputs)
440+
structural_hash, parametric_hash = compute_circuit_hashes(
441+
circuits_for_logging, inputs
442+
)
435443

436444
is_new_circuit = (
437445
structural_hash and structural_hash not in self._seen_circuit_hashes

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

Lines changed: 35 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
from devqubit_braket.utils import braket_version, get_adapter_version, get_backend_name
3434
from devqubit_engine.circuit.models import CircuitFormat
3535
from devqubit_engine.storage.types import ArtifactRef
36+
from devqubit_engine.uec.errors import EnvelopeValidationError
3637
from devqubit_engine.uec.models.device import DeviceSnapshot
3738
from devqubit_engine.uec.models.envelope import ExecutionEnvelope
3839
from devqubit_engine.uec.models.execution import ExecutionSnapshot, ProducerInfo
@@ -60,6 +61,15 @@
6061

6162
_serializer = BraketCircuitSerializer()
6263

64+
# Pre-computed base CountsFormat dict for Braket (source=braket, cbit0_left).
65+
# Dynamic fields (measured_qubits) are overlaid per-call.
66+
_BRAKET_COUNTS_FORMAT: dict[str, Any] = CountsFormat(
67+
source_sdk="braket",
68+
source_key_format="bitstring",
69+
bit_order="cbit0_left",
70+
transformed=False,
71+
).to_dict()
72+
6373

6474
# =============================================================================
6575
# Counts Format
@@ -95,7 +105,7 @@ def _get_braket_counts_format(
95105
bit_order="cbit0_left",
96106
transformed=transformed,
97107
)
98-
result = fmt.to_dict()
108+
result = fmt.to_dict() if transformed else dict(_BRAKET_COUNTS_FORMAT)
99109

100110
if measured_qubits is not None:
101111
result["measured_qubits"] = measured_qubits
@@ -344,6 +354,7 @@ def _create_result_snapshot(
344354
raw_result_ref: ArtifactRef | None,
345355
shots: int | None,
346356
error: Exception | None = None,
357+
counts_payload: dict[str, Any] | None = None,
347358
) -> ResultSnapshot:
348359
"""
349360
Create a ResultSnapshot from Braket result.
@@ -358,6 +369,9 @@ def _create_result_snapshot(
358369
Number of shots used.
359370
error : Exception or None
360371
Exception if execution failed.
372+
counts_payload : dict or None
373+
Pre-extracted counts payload. When provided, skips redundant
374+
extraction. Callers should extract once and pass through.
361375
362376
Returns
363377
-------
@@ -379,11 +393,12 @@ def _create_result_snapshot(
379393
# Extract measured qubits if available (for accurate bit-order semantics)
380394
measured_qubits = _extract_measured_qubits(result)
381395

382-
# Check if result is already a combined payload dict (from batch)
383-
if isinstance(result, dict) and "experiments" in result:
384-
counts_payload = result
385-
else:
386-
counts_payload = extract_counts_payload(result)
396+
# Use pre-computed counts_payload if provided
397+
if counts_payload is None:
398+
if isinstance(result, dict) and "experiments" in result:
399+
counts_payload = result
400+
else:
401+
counts_payload = extract_counts_payload(result)
387402

388403
if counts_payload and counts_payload.get("experiments"):
389404
format_dict = _get_braket_counts_format(measured_qubits=measured_qubits)
@@ -632,27 +647,31 @@ def finalize_envelope(
632647
except Exception as e:
633648
logger.warning("Failed to log error: %s", e)
634649

650+
# Extract counts once (used for both result snapshot and separate logging)
651+
counts_payload: dict[str, Any] | None = None
652+
if result is not None and error is None:
653+
try:
654+
if isinstance(result, dict) and "experiments" in result:
655+
counts_payload = result # Batch: already structured
656+
else:
657+
counts_payload = extract_counts_payload(result)
658+
except Exception as e:
659+
logger.debug("Failed to extract counts payload: %s", e)
660+
635661
# Create result snapshot
636-
result_snapshot = _create_result_snapshot(result, raw_result_ref, shots, error)
662+
result_snapshot = _create_result_snapshot(
663+
result, raw_result_ref, shots, error, counts_payload=counts_payload
664+
)
637665

638666
# Update execution snapshot with completion time
639667
if envelope.execution:
640668
envelope.execution.completed_at = utc_now_iso()
641669

642670
envelope.result = result_snapshot
643671

644-
# Extract counts for separate logging
645-
counts_payload = None
646-
if result is not None:
647-
try:
648-
counts_payload = extract_counts_payload(result)
649-
except Exception as e:
650-
logger.debug("Failed to extract counts payload: %s", e)
651-
652672
# Validate and log envelope
653673
# EnvelopeValidationError is raised by log_envelope for adapter runs with
654674
# invalid envelopes - this MUST propagate to enforce UEC contract
655-
from devqubit_engine.uec.errors import EnvelopeValidationError
656675

657676
try:
658677
tracker.log_envelope(envelope=envelope)

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

Lines changed: 1 addition & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,7 @@ def circuits_to_text(circuits: list[Any]) -> str:
297297
# =============================================================================
298298

299299

300-
def summarize_circuit(circuit: Any) -> CircuitSummary:
300+
def summarize_braket_circuit(circuit: Any) -> CircuitSummary:
301301
"""
302302
Generate a summary of a Braket circuit.
303303
@@ -532,45 +532,3 @@ def serialize(
532532
if fmt == CircuitFormat.OPENQASM3:
533533
return serialize_openqasm(circuit, name=name, index=index)
534534
raise SerializerError(f"Unsupported format: {fmt}")
535-
536-
537-
def summarize_braket_circuit(circuit: Any) -> CircuitSummary:
538-
"""
539-
Generate a summary of a Braket circuit.
540-
541-
Extracts gate counts, depth, qubit count, and classification
542-
information from the circuit.
543-
544-
Parameters
545-
----------
546-
circuit : braket.circuits.Circuit
547-
Braket circuit to summarize.
548-
549-
Returns
550-
-------
551-
CircuitSummary
552-
Circuit summary with statistics and gate counts.
553-
"""
554-
gate_counts: Counter[str] = Counter()
555-
556-
for instr in circuit.instructions:
557-
op = instr.operator
558-
gate_name = getattr(op, "name", type(op).__name__).lower()
559-
gate_counts[gate_name] += 1
560-
561-
# Classify gates using the classifier
562-
stats = _classifier.classify_counts(dict(gate_counts))
563-
564-
return CircuitSummary(
565-
num_qubits=circuit.qubit_count,
566-
depth=circuit.depth,
567-
gate_count_1q=stats["gate_count_1q"],
568-
gate_count_2q=stats["gate_count_2q"],
569-
gate_count_multi=stats["gate_count_multi"],
570-
gate_count_measure=stats["gate_count_measure"],
571-
gate_count_total=sum(gate_counts.values()),
572-
gate_types=dict(gate_counts),
573-
is_clifford=stats["is_clifford"],
574-
source_format=CircuitFormat.JAQCD,
575-
sdk=SDK.BRAKET,
576-
)

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

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@
1818
# =============================================================================
1919

2020

21+
_braket_version: str | None = None
22+
23+
2124
def braket_version() -> str:
2225
"""
2326
Get the installed Amazon Braket SDK version.
@@ -27,13 +30,24 @@ def braket_version() -> str:
2730
str
2831
Braket SDK version string (e.g., "1.70.0"), or "unknown" if
2932
Braket is not installed or version cannot be determined.
33+
34+
Notes
35+
-----
36+
Result is cached: SDK version is immutable during process lifetime.
3037
"""
38+
global _braket_version
39+
if _braket_version is not None:
40+
return _braket_version
3141
try:
3242
import braket
3343

34-
return getattr(braket, "__version__", "unknown")
44+
_braket_version = getattr(braket, "__version__", "unknown")
3545
except ImportError:
36-
return "unknown"
46+
_braket_version = "unknown"
47+
return _braket_version
48+
49+
50+
_adapter_version: str | None = None
3751

3852

3953
def get_adapter_version() -> str:
@@ -44,13 +58,21 @@ def get_adapter_version() -> str:
4458
-------
4559
str
4660
Adapter version string, or "unknown" if not installed.
61+
62+
Notes
63+
-----
64+
Result is cached: adapter version is immutable during process lifetime.
4765
"""
66+
global _adapter_version
67+
if _adapter_version is not None:
68+
return _adapter_version
4869
try:
4970
from importlib.metadata import version
5071

51-
return version("devqubit-braket")
72+
_adapter_version = version("devqubit-braket")
5273
except Exception:
53-
return "unknown"
74+
_adapter_version = "unknown"
75+
return _adapter_version
5476

5577

5678
# =============================================================================
@@ -255,19 +277,15 @@ def obj_to_dict(x: Any) -> dict[str, Any] | None:
255277
try:
256278
if isinstance(x, dict):
257279
return x
280+
from devqubit_engine.utils.serialization import to_jsonable
281+
258282
# pydantic v1 style
259283
if hasattr(x, "dict") and callable(getattr(x, "dict")):
260-
from devqubit_engine.utils.serialization import to_jsonable
261-
262284
return to_jsonable(x.dict())
263285
# pydantic v2 or custom style
264286
if hasattr(x, "to_dict") and callable(getattr(x, "to_dict")):
265-
from devqubit_engine.utils.serialization import to_jsonable
266-
267287
return to_jsonable(x.to_dict())
268288
# Fallback: attempt generic conversion
269-
from devqubit_engine.utils.serialization import to_jsonable
270-
271289
return to_jsonable(x)
272290
except Exception:
273291
return None

0 commit comments

Comments
 (0)