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]>
149 lines
5.6 KiB
Python
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']})"
|