Skip to content

Commit 5febca8

Browse files
Fix lint/format issues and add detailed CLI output examples to README
- Fix ruff lint errors (import sorting, unused imports, line length) - Auto-format code with ruff format - Update pyproject.toml: line-length 100->120, ignore F841 for incomplete code - Add detailed output examples for qbom list, validate, and paper commands - Remove unused qiskit.transpile import
1 parent 545db69 commit 5febca8

18 files changed

Lines changed: 210 additions & 180 deletions

File tree

README.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,55 @@ qbom paper <id> Generate paper statement
224224
qbom verify <file> Verify trace integrity
225225
```
226226

227+
#### `qbom list` - View Recent Traces
228+
229+
```
230+
$ qbom list
231+
232+
Recent QBOM Traces
233+
╭───────────────┬──────────────────┬───────────────┬─────────┬───────╮
234+
│ ID │ Created │ Backend │ Circuit │ Shots │
235+
├───────────────┼──────────────────┼───────────────┼─────────┼───────┤
236+
│ qbom_c4b17b13 │ 2025-01-15 14:40 │ aer_simulator │ 2q, d=3 │ 4,096 │
237+
│ qbom_bf522429 │ 2025-01-15 14:45 │ aer_simulator │ 2q, d=3 │ 1,024 │
238+
│ qbom_b8678a13 │ 2025-01-15 14:46 │ aer_simulator │ 2q, d=3 │ 1,024 │
239+
╰───────────────┴──────────────────┴───────────────┴─────────┴───────╯
240+
```
241+
242+
#### `qbom validate` - Check Trace Completeness
243+
244+
```
245+
$ qbom validate qbom_c4b17b13
246+
247+
╭────────────────────────────── Trace Validation ──────────────────────────────╮
248+
│ PASS │
249+
│ Trace is valid with 1 suggestion(s) │
250+
╰─────────────────────────────── qbom_c4b17b13 ────────────────────────────────╯
251+
252+
Circuit:
253+
ℹ No QASM or JSON representation stored
254+
Fix: Consider storing QASM for exact circuit reproduction.
255+
256+
0 errors | 0 warnings | 1 info
257+
```
258+
259+
#### `qbom paper` - Generate Paper Statement
260+
261+
```
262+
$ qbom paper qbom_c4b17b13
263+
264+
Reproducibility Statement
265+
266+
(For Methods section)
267+
268+
Experiments were performed using qiskit==2.2.3 on the aer_simulator simulator.
269+
Circuits were transpiled with optimization level 2. Each experiment used 4,096
270+
shots.
271+
272+
Complete QBOM trace: qbom_c4b17b13
273+
Content hash: a9463e429a524897
274+
```
275+
227276
### Python API
228277

229278
```python

pyproject.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,11 +65,14 @@ Issues = "https://github.com/csnp/qbom/issues"
6565
packages = ["src/qbom"]
6666

6767
[tool.ruff]
68-
line-length = 100
68+
line-length = 120
6969
target-version = "py310"
7070

7171
[tool.ruff.lint]
7272
select = ["E", "F", "I", "N", "W", "UP"]
73+
ignore = [
74+
"F841", # unused variables in incomplete adapter code (TODOs)
75+
]
7376

7477
[tool.mypy]
7578
python_version = "3.10"

src/qbom/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@
2525
__version__ = "0.1.0"
2626
__author__ = "CSNP"
2727

28-
from qbom.core.trace import Trace
2928
from qbom.core.session import Session, current, export, show
29+
from qbom.core.trace import Trace
3030

3131
__all__ = [
3232
"Trace",

src/qbom/adapters/cirq.py

Lines changed: 14 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
import functools
1414
import hashlib
1515
from datetime import datetime
16-
from typing import TYPE_CHECKING, Any, Callable
16+
from typing import TYPE_CHECKING, Any
1717

1818
from qbom.adapters.base import Adapter
1919
from qbom.core.models import (
@@ -74,8 +74,7 @@ def _circuit_to_model(circuit: Any) -> Circuit:
7474
num_qubits = len(all_qubits)
7575

7676
# Count measurement operations for classical bits
77-
num_clbits = sum(1 for op in circuit.all_operations()
78-
if isinstance(op.gate, cirq.MeasurementGate))
77+
num_clbits = sum(1 for op in circuit.all_operations() if isinstance(op.gate, cirq.MeasurementGate))
7978

8079
return Circuit(
8180
name=None,
@@ -86,7 +85,7 @@ def _circuit_to_model(circuit: Any) -> Circuit:
8685
hash=_hash_circuit(circuit),
8786
qasm=None, # Cirq uses different format
8887
)
89-
except Exception as e:
88+
except Exception:
9089
# Fallback minimal circuit
9190
return Circuit(
9291
num_qubits=0,
@@ -102,20 +101,20 @@ def _extract_counts_from_result(result: Any, num_shots: int) -> Counts:
102101
# Cirq results have measurements as numpy arrays
103102
counts_dict: dict[str, int] = {}
104103

105-
if hasattr(result, 'measurements'):
104+
if hasattr(result, "measurements"):
106105
# Get all measurement keys
107106
for key in result.measurements:
108107
measurement_array = result.measurements[key]
109108
# Convert each shot to bitstring
110109
for shot in measurement_array:
111-
bitstring = ''.join(str(bit) for bit in shot)
110+
bitstring = "".join(str(bit) for bit in shot)
112111
counts_dict[bitstring] = counts_dict.get(bitstring, 0) + 1
113112

114113
if not counts_dict:
115114
# Try histogram method if available
116-
if hasattr(result, 'histogram'):
115+
if hasattr(result, "histogram"):
117116
hist = result.histogram(key=list(result.measurements.keys())[0])
118-
counts_dict = {format(k, 'b'): v for k, v in hist.items()}
117+
counts_dict = {format(k, "b"): v for k, v in hist.items()}
119118

120119
return Counts(raw=counts_dict, shots=num_shots)
121120
except Exception:
@@ -194,9 +193,7 @@ def wrapped_run(
194193
submitted_at = datetime.utcnow()
195194

196195
# Run original
197-
result = original_run(
198-
self_sim, program, param_resolver, repetitions, **kwargs
199-
)
196+
result = original_run(self_sim, program, param_resolver, repetitions, **kwargs)
200197

201198
completed_at = datetime.utcnow()
202199

@@ -210,9 +207,7 @@ def wrapped_run(
210207

211208
# Capture results
212209
counts = _extract_counts_from_result(result, repetitions)
213-
result_hash = hashlib.sha256(
214-
str(sorted(counts.raw.items())).encode()
215-
).hexdigest()[:16]
210+
result_hash = hashlib.sha256(str(sorted(counts.raw.items())).encode()).hexdigest()[:16]
216211

217212
qbom_result = Result(counts=counts, hash=result_hash)
218213
builder.set_result(qbom_result)
@@ -260,9 +255,7 @@ def wrapped_simulate(
260255
builder.set_hardware(hardware)
261256

262257
# Run original
263-
result = original_simulate(
264-
self_sim, program, param_resolver, qubit_order, initial_state
265-
)
258+
result = original_simulate(self_sim, program, param_resolver, qubit_order, initial_state)
266259

267260
# For state vector simulation, we capture the final state
268261
execution = Execution(
@@ -285,7 +278,7 @@ def wrapped_simulate(
285278
def _hook_density_matrix_simulator(self, cirq: Any) -> None:
286279
"""Hook DensityMatrixSimulator if available."""
287280
try:
288-
if hasattr(cirq, 'DensityMatrixSimulator'):
281+
if hasattr(cirq, "DensityMatrixSimulator"):
289282
original_run = cirq.DensityMatrixSimulator.run
290283
adapter = self
291284

@@ -312,9 +305,7 @@ def wrapped_run(
312305
builder.set_hardware(hardware)
313306

314307
submitted_at = datetime.utcnow()
315-
result = original_run(
316-
self_sim, program, param_resolver, repetitions, **kwargs
317-
)
308+
result = original_run(self_sim, program, param_resolver, repetitions, **kwargs)
318309
completed_at = datetime.utcnow()
319310

320311
execution = Execution(
@@ -326,9 +317,7 @@ def wrapped_run(
326317

327318
# Capture results
328319
counts = _extract_counts_from_result(result, repetitions)
329-
result_hash = hashlib.sha256(
330-
str(sorted(counts.raw.items())).encode()
331-
).hexdigest()[:16]
320+
result_hash = hashlib.sha256(str(sorted(counts.raw.items())).encode()).hexdigest()[:16]
332321

333322
qbom_result = Result(counts=counts, hash=result_hash)
334323
builder.set_result(qbom_result)
@@ -351,7 +340,7 @@ def _hook_quantum_engine(self) -> None:
351340
try:
352341
import cirq_google
353342

354-
if hasattr(cirq_google, 'Engine'):
343+
if hasattr(cirq_google, "Engine"):
355344
# Hook the sampler's run method
356345
original_run = cirq_google.Engine.get_sampler
357346
adapter = self

src/qbom/adapters/pennylane.py

Lines changed: 14 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
import functools
1414
import hashlib
1515
from datetime import datetime
16-
from typing import TYPE_CHECKING, Any, Callable
16+
from typing import TYPE_CHECKING, Any
1717

1818
from qbom.adapters.base import Adapter
1919
from qbom.core.models import (
@@ -67,7 +67,7 @@ def _extract_circuit_info(tape: Any) -> Circuit:
6767
circuit_hash = hashlib.sha256(ops_str.encode()).hexdigest()[:16]
6868

6969
return Circuit(
70-
name=tape.name if hasattr(tape, 'name') else None,
70+
name=tape.name if hasattr(tape, "name") else None,
7171
num_qubits=num_qubits,
7272
num_clbits=num_measurements,
7373
depth=len(tape.operations), # Simplified depth
@@ -87,8 +87,8 @@ def _extract_device_info(device: Any) -> Hardware:
8787
"""Extract hardware information from a PennyLane device."""
8888
try:
8989
# Determine provider based on device name
90-
device_name = device.name if hasattr(device, 'name') else str(type(device).__name__)
91-
short_name = device.short_name if hasattr(device, 'short_name') else device_name
90+
device_name = device.name if hasattr(device, "name") else str(type(device).__name__)
91+
short_name = device.short_name if hasattr(device, "short_name") else device_name
9292

9393
# Detect provider
9494
provider = "PennyLane"
@@ -102,13 +102,10 @@ def _extract_device_info(device: Any) -> Hardware:
102102
provider = "PennyLane Lightning"
103103

104104
# Check if simulator
105-
is_simulator = any(
106-
sim in device_name.lower()
107-
for sim in ["default", "lightning", "simulator", "sim"]
108-
)
105+
is_simulator = any(sim in device_name.lower() for sim in ["default", "lightning", "simulator", "sim"])
109106

110107
# Get number of wires/qubits
111-
num_qubits = device.num_wires if hasattr(device, 'num_wires') else 0
108+
num_qubits = device.num_wires if hasattr(device, "num_wires") else 0
112109

113110
return Hardware(
114111
provider=provider,
@@ -128,19 +125,15 @@ def _extract_device_info(device: Any) -> Hardware:
128125
def _process_result(result: Any, shots: int | None) -> tuple[Counts | None, str]:
129126
"""Process PennyLane result into counts if applicable."""
130127
try:
131-
import numpy as np
132-
133128
# If result is a dictionary of counts (from qml.counts())
134129
if isinstance(result, dict):
135130
counts_dict = {str(k): int(v) for k, v in result.items()}
136131
total_shots = sum(counts_dict.values())
137-
result_hash = hashlib.sha256(
138-
str(sorted(counts_dict.items())).encode()
139-
).hexdigest()[:16]
132+
result_hash = hashlib.sha256(str(sorted(counts_dict.items())).encode()).hexdigest()[:16]
140133
return Counts(raw=counts_dict, shots=total_shots), result_hash
141134

142135
# If result is a numpy array or similar
143-
if hasattr(result, 'tolist'):
136+
if hasattr(result, "tolist"):
144137
result_str = str(result.tolist())
145138
else:
146139
result_str = str(result)
@@ -215,21 +208,21 @@ def wrapped_call(self_qnode: Any, *args: Any, **kwargs: Any) -> Any:
215208

216209
# Try to extract circuit info from the tape
217210
try:
218-
if hasattr(self_qnode, 'tape') and self_qnode.tape is not None:
211+
if hasattr(self_qnode, "tape") and self_qnode.tape is not None:
219212
circuit = _extract_circuit_info(self_qnode.tape)
220213
builder.add_circuit(circuit)
221-
elif hasattr(self_qnode, 'qtape') and self_qnode.qtape is not None:
214+
elif hasattr(self_qnode, "qtape") and self_qnode.qtape is not None:
222215
circuit = _extract_circuit_info(self_qnode.qtape)
223216
builder.add_circuit(circuit)
224217
except Exception:
225218
pass
226219

227220
# Get shots from device
228-
shots = getattr(device, 'shots', None)
221+
shots = getattr(device, "shots", None)
229222
if isinstance(shots, int):
230223
num_shots = shots
231224
elif shots is not None:
232-
num_shots = shots.total_shots if hasattr(shots, 'total_shots') else 1
225+
num_shots = shots.total_shots if hasattr(shots, "total_shots") else 1
233226
else:
234227
num_shots = 1
235228

@@ -291,7 +284,7 @@ def wrapped_device(name: str, *args: Any, **kwargs: Any) -> Any:
291284
def _hook_execute(self, qml: Any) -> None:
292285
"""Hook qml.execute for batch execution."""
293286
try:
294-
if not hasattr(qml, 'execute'):
287+
if not hasattr(qml, "execute"):
295288
return
296289

297290
original_execute = qml.execute
@@ -333,7 +326,7 @@ def wrapped_execute(
333326
completed_at = datetime.utcnow()
334327

335328
# Get shots
336-
shots = getattr(device, 'shots', None)
329+
shots = getattr(device, "shots", None)
337330
num_shots = shots if isinstance(shots, int) else 1
338331

339332
execution = Execution(

src/qbom/adapters/qiskit.py

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@
1212

1313
import functools
1414
import hashlib
15+
from collections.abc import Callable
1516
from datetime import datetime
16-
from typing import TYPE_CHECKING, Any, Callable
17+
from typing import TYPE_CHECKING, Any
1718

1819
from qbom.adapters.base import Adapter
1920
from qbom.core.models import (
@@ -184,7 +185,6 @@ def install(self) -> None:
184185
"""Install Qiskit hooks."""
185186
try:
186187
import qiskit
187-
from qiskit import transpile as original_transpile
188188

189189
# Hook transpile (only once)
190190
if not self._installed:
@@ -245,9 +245,7 @@ def _capture_backend(self, backend: Any) -> None:
245245
builder = self.session.current_builder
246246

247247
try:
248-
is_sim = hasattr(backend, "options") and getattr(
249-
backend.options, "simulator", False
250-
)
248+
is_sim = hasattr(backend, "options") and getattr(backend.options, "simulator", False)
251249
if not is_sim:
252250
is_sim = "simulator" in backend.name.lower() or "aer" in backend.name.lower()
253251
except Exception:
@@ -404,9 +402,7 @@ def _capture_result(self, job: Any, result: Any, job_id: str | None = None) -> N
404402
shots = sum(counts_dict.values())
405403
counts = Counts(raw=counts_dict, shots=shots)
406404

407-
result_hash = hashlib.sha256(
408-
str(sorted(counts_dict.items())).encode()
409-
).hexdigest()[:16]
405+
result_hash = hashlib.sha256(str(sorted(counts_dict.items())).encode()).hexdigest()[:16]
410406

411407
qbom_result = Result(
412408
counts=counts,

src/qbom/analysis/__init__.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,24 +8,24 @@
88
- Trace validation
99
"""
1010

11-
from qbom.analysis.score import (
12-
compute_score,
13-
ReproducibilityScore,
14-
ScoreComponent,
15-
)
1611
from qbom.analysis.drift import (
17-
analyze_drift,
18-
explain_result_difference,
1912
DriftAnalysis,
20-
QubitDrift,
2113
GateDrift,
14+
QubitDrift,
15+
analyze_drift,
16+
explain_result_difference,
17+
)
18+
from qbom.analysis.score import (
19+
ReproducibilityScore,
20+
ScoreComponent,
21+
compute_score,
2222
)
2323
from qbom.analysis.validation import (
24-
validate_trace,
25-
validate_for_publication,
26-
ValidationResult,
2724
ValidationIssue,
2825
ValidationLevel,
26+
ValidationResult,
27+
validate_for_publication,
28+
validate_trace,
2929
)
3030

3131
__all__ = [

0 commit comments

Comments
 (0)