open-standards/wai/wai-quantum-py/python/wai_quantum/widgets.py
Transaction Science 22fdc7a3cb wai-quantum-py: WAI Quantum in Python and notebooks, in-process
A new crate, wai/wai-quantum-py, built by maturin 1.15.0 into one abi3 wheel
per platform (pyo3 0.29.3, Python 3.9 on), module wai_quantum. Its extension
module wraps the shared job engine and nothing else: run, plan, lint, draw
(text and the engine SVG), layout, critique, improve, shortcuts,
equivalent, verify, seal, canonical, digest, methods, accuracy_classes and
history, values crossing one to one with JSON (NaN, integers past 64 bits
and non-string keys refused). A job is the service form as a plain dict
with helpers (counts, probabilities, expectations, amplitudes, method,
accuracy, receipt); Key reads and writes the command line key files and
finds the same user key; bloch gives each qubit reduced state from the
engine expectation values. A refusal raises EngineError with the engine
message, its line, and a code slot for the engine diagnostic codes.

Service is a client in the standard library alone for the job service
(submit, status, result, cancel, run), naming itself
wai-quantum-python/<version>, its receipts checked here offline.

Widgets (the widgets extra): Circuit (the engine drawing, gates clickable,
authorship marks from a history), Result, Receipt (the engine verdict) and
Bloch (drag to turn), each a plain ES module, each with static HTML (and
SVG) fallbacks that render without the widget library.

Five doc notebooks run in CI and are held to their stored outputs.
ci/check.sh is the whole check; 62 pytest tests hold the package to the
command line (same results, plans, lints, critiques, drawings, methods,
equivalence verdicts; receipts verified both ways; the same user key; the
digest recomputed from Python canonical bytes), to the service run locally,
and run the front ends under node on the payloads; Python 3.9 smoke with
the wheel alone. Workflow wai-quantum-py.yml runs it on linux and macos.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-27 19:51:08 -04:00

149 lines
5.6 KiB
Python

"""Notebook widgets: ``Circuit``, ``Result``, ``Receipt`` and ``Bloch``.
Each shows what the engine said, never its own reckoning: a circuit is the
engine's drawing; a result's histogram is the engine's counts, with the
method and the accuracy class it names; a receipt's badge is the engine's
check, made here; a Bloch vector is the engine's expectation values.
With the ``widgets`` extra installed (``anywidget``), each is an interactive
widget whose front end is a small plain ES module (``static/*.js``): a gate
clicked in a ``Circuit`` sets ``selected``, a ``Result`` switches between
counts and frequencies, a ``Bloch`` sphere turns when dragged and keeps its
``view``. Without it, or in a front end without the widget extension, each
still renders, as static HTML (``_repr_html_``), and a ``Circuit`` also as
SVG (``_repr_svg_``).
Each widget's data is one dict, ``data``, built by ``circuit_data``,
``result_data``, ``receipt_data`` and ``bloch_data``: plain values, so a
page or a test can read exactly what is drawn.
"""
from __future__ import annotations
from pathlib import Path
from typing import Any, Dict, List, Mapping, Optional, Sequence, Union
from . import _api, _render
from ._data import bloch_data, circuit_data, receipt_data, result_data
__all__ = ["Circuit", "Result", "Receipt", "Bloch", "circuit_data", "result_data", "receipt_data", "bloch_data", "HAVE_WIDGETS"]
_STATIC = Path(__file__).parent / "static"
try: # The interactive front end is optional.
import anywidget as _anywidget
import traitlets as _traitlets
HAVE_WIDGETS = True
except ImportError: # pragma: no cover - exercised in a process without it
_anywidget = None
_traitlets = None
HAVE_WIDGETS = False
# -- the widgets ------------------------------------------------------------
if HAVE_WIDGETS:
class _Base(_anywidget.AnyWidget):
_css = _STATIC / "widgets.css"
data = _traitlets.Dict().tag(sync=True)
def __init__(self, data: Dict[str, Any], **traits: Any) -> None:
super().__init__(data=data, **traits)
else:
class _Base: # type: ignore[no-redef]
def __init__(self, data: Dict[str, Any], **traits: Any) -> None:
self.data = data
for k, v in traits.items():
setattr(self, k, v)
class Circuit(_Base):
"""A program's circuit, the engine's drawing. Clicking a gate sets
``selected`` to its operation's index (``None`` when nothing is). Pass
``hands=`` (one per operation) or ``history=`` (and ``version=``) to mark
each gate with whose hand it is in."""
if HAVE_WIDGETS:
_esm = _STATIC / "circuit.js"
selected = _traitlets.Int(None, allow_none=True).tag(sync=True)
def __init__(self, program: _api.Program, *, hands: Optional[Sequence[str]] = None, history: Optional[Mapping[str, Any]] = None, version: Optional[str] = None) -> None:
traits = {} if HAVE_WIDGETS else {"selected": None}
super().__init__(circuit_data(program, hands=hands, history=history, version=version), **traits)
def _repr_html_(self) -> str:
return _render.circuit_html(self.data)
def _repr_svg_(self) -> str:
return self.data["svg"]
def __repr__(self) -> str:
lay = self.data["layout"]
items = sum(len(c) for c in lay["columns"])
return f"Circuit({lay['qubits']} qubits, {items} operations in {len(lay['columns'])} columns)"
class Result(_Base):
"""A job's result: its counts as a histogram (or their sampled
frequencies), its expectation values and amplitudes, with the method,
the accuracy class and the receipt's check."""
if HAVE_WIDGETS:
_esm = _STATIC / "result.js"
frequencies = _traitlets.Bool(False).tag(sync=True)
top = _traitlets.Int(_render.TOP).tag(sync=True)
def __init__(self, job: Mapping[str, Any], *, frequencies: bool = False, top: int = _render.TOP) -> None:
super().__init__(result_data(job), frequencies=frequencies, top=top)
def _repr_html_(self) -> str:
return _render.result_html(self.data)
def __repr__(self) -> str:
d = self.data
return f"Result({d['method']}, accuracy {d['accuracy']}, {d['shots']} shots, {len(d['expectations'])} expectation values)"
class Receipt(_Base):
"""A job's receipt: who signed it, its digest, and whether it seals the
request and the result, as the engine checks it here."""
if HAVE_WIDGETS:
_esm = _STATIC / "receipt.js"
def __init__(self, job: Mapping[str, Any]) -> None:
super().__init__(receipt_data(job))
@property
def valid(self) -> bool:
return self.data["valid"] is True
def _repr_html_(self) -> str:
return _render.receipt_html(self.data)
def __repr__(self) -> str:
return f"Receipt({'valid' if self.valid else 'INVALID'}, signed by {self.data['signer']})"
class Bloch(_Base):
"""Each qubit's reduced state as a Bloch vector on its sphere, from the
engine. Dragging a sphere turns the view (``view``: azimuth and
elevation, degrees). Takes a program (and ``bloch``'s keywords) or what
``wai_quantum.bloch`` returned."""
if HAVE_WIDGETS:
_esm = _STATIC / "bloch.js"
view = _traitlets.List(list(_render.VIEW)).tag(sync=True)
def __init__(self, program: Union[_api.Program, Mapping[str, Any]], **kw: Any) -> None:
super().__init__(bloch_data(program, **kw), view=list(_render.VIEW))
def _repr_html_(self) -> str:
return _render.bloch_html(self.data)
def __repr__(self) -> str:
return f"Bloch({len(self.data['qubits'])} qubits, {self.data['method']}, accuracy {self.data['accuracy']})"