Browse documentation
Docs / Quantum Workflow / How observation works
How Notebook Observation Works
Understand sensor 0.12.6 observation points, circuit identity, stage snapshots, and the evidence used to build a quantum workflow.
When the sensor observes
- 1Begin the cell
The IPython pre-cell hook establishes execution context and enables observation of registered Python SDK boundaries.
- 2Observe supported calls
CPython monitoring observes selected calls, returns, and exceptions. These points can preserve inputs, outputs, timing, and relationships while the relevant objects are available.
- 3Inspect the namespace
At cell completion, the sensor checks bounded notebook variables and supported containers for circuits and other recognized objects. This also runs after a cell error when IPython completes the cell hook.
- 4Deliver observations
Observation records and separate detail payloads are queued for delivery. The control plane uses received evidence to assemble workflow records and provenance.
The sensor does not wrap notebook functions, inject compiler passes, or replace a user callback. It does perform selected local queries and serialization, including circuit metrics. Capture and delivery have computational and storage costs and are bounded.
Circuit identity, versions, and aliases
Object identity distinguishes two live circuits even when their structures match. A structural fingerprint helps detect changes to a tracked circuit. Variable names are references to that object; reassignment or an additional alias is not by itself a structural change. Names, metadata, and layout are recorded as context separately from structural comparison.
When structure cannot be compared completely, a captured state may retain an unknown comparison status. An additional state in that situation is not proof that the circuit structure changed.
Supported transformation calls provide stronger derivation evidence than a namespace scan alone. The scan can discover the current state, but it cannot prove every operation that produced an object before observation.
Post-cell discovery checks direct circuit variables and circuits in built-in lists, tuples, and dictionaries to a depth of three. It also supports eligible Qiskit objects with a stored circuit field. It does not exhaust arbitrary object graphs, consume generators, or discover every temporary local inside a user function.
Intermediate stage snapshots
A supported pass-manager run exposes its input and final output. Additional snapshots can be observed when code exports an intermediate DAG through dag_to_circuit() in the current execution thread. Running a staged manager alone does not guarantee one captured circuit for every stage.
This complete local example adds a user-defined analysis pass after each enabled stage. The pass exports a copy of the current DAG as a circuit. The original DAG is not changed by the capture pass. The fake backend provides compilation configuration; this example submits no hardware job.
from qiskit import QuantumCircuit
from qiskit.transpiler import (
AnalysisPass, CouplingMap, PassManager,
generate_preset_pass_manager,
)
from qiskit.providers.fake_provider import GenericBackendV2
from qiskit.converters import dag_to_circuit
qc = QuantumCircuit(4, 4)
qc.h(0)
qc.ccx(0, 1, 2)
qc.cx(0, 3)
qc.ry(0.5, 1)
qc.cx(3, 1)
qc.cx(0, 2)
qc.h(3)
qc.measure(range(4), range(4))
backend = GenericBackendV2(
num_qubits=4,
coupling_map=CouplingMap.from_line(4, bidirectional=True),
basis_gates=["rz", "sx", "x", "cx"],
seed=42,
)
pm = generate_preset_pass_manager(
optimization_level=1,
backend=backend,
layout_method="trivial",
routing_method="sabre",
scheduling_method="asap",
seed_transpiler=42,
)
snapshots = {"original": qc.copy()}
class CaptureStage(AnalysisPass):
def __init__(self, stage_name):
super().__init__()
self.stage_name = stage_name
def run(self, dag):
snapshots[self.stage_name] = dag_to_circuit(dag)
return None
for stage in pm.stages:
if getattr(pm, stage) is None:
continue
hook_name = f"post_{stage}"
hook = getattr(pm, hook_name)
if hook is None:
hook = PassManager()
hook.append(CaptureStage(stage))
setattr(pm, hook_name, hook)
compiled = pm.run(qc)
for stage, circuit in snapshots.items():
print(stage, circuit.depth(), dict(circuit.count_ops()))The sensor associates a conversion with a stage when the executing task can be mapped unambiguously to the configured stage or hook. Labels come from that execution context, not from guessing the meaning of the dictionary key or the stage_name attribute. Shared or custom tasks can leave stage attribution unavailable.
The AnalysisPass base class identifies the intended pass category; it does not prove the behavior of arbitrary custom code. The sensor does not trace every pass or serialize the entire pass-manager property_set. Repeated conversions can produce multiple snapshot observations, and a snapshot can share circuit structure with an earlier state.
Capture boundaries
- Runtime observation covers registered Python boundaries in the cell execution thread. Work performed in another thread or subprocess does not receive complete call or stage tracing from this sensor; a returned circuit may still be discovered later.
- Built-in gate helper calls and private
_append()operations do not each generate an activity. Several mutations between observations can be represented by one changed state. - Custom QuantumCircuit subclasses that override queried accessors can be excluded. Unsupported custom operation internals can remain opaque.
- Generators, custom collections, and arbitrary nested user objects are not exhaustively inspected. Supported primitive PUB inputs are materialized lists or tuples.
- Namespace, event, structure, byte, and transport budgets can limit capture. Flat value encoding avoids recursive JSON nesting for supported value attachments; it does not remove independent circuit-nesting or payload limits.
- Recognition of a class or method is conditional on its SDK module and expected implementation. Replacing methods or using a different SDK implementation can reduce coverage.
- A missing event or field does not establish that an operation never occurred. Use the available completeness and coverage fields, and do not assume every omission produces a separate visible warning.
Captured data and local queries
Captured scientific data can include circuit metadata, parameters, supported observables, selected numerical results, and provided QASM source strings. Exact-value attachments use flat records and references. A payload is exact only within its supported type and reported capture extent.
The sensor reads available backend/target fields and does not fetch fresh calibration or submit a quantum job for inspection. It observes lifecycle calls that notebook code makes; it does not poll job.status() or call job.result() just to discover a result. Provider synchronization is a separate control-plane operation.
Cell-source capture, full QPY artifact upload, and automatic result-count queries are separate configuration choices. Their source defaults are disabled. This does not imply that circuit metadata, QASM arguments, or source snapshots captured by other Spinorio features are excluded.
Key-based redaction and supported-field restrictions reduce accidental collection of credentials. They are not a general secret detector: keep secrets out of circuit metadata, scientific values, and source text intended for capture or sharing.