Source code for quchip.chip.analysis

"""Dressed-state analysis owned by :class:`quchip.chip.chip.Chip`.

``ChipAnalysis`` diagonalizes the lab-frame static Hamiltonian, assigns
bare-state labels to dressed eigenvectors by overlap maximization, and
exposes derived quantities (dressed eigenenergies, transition
frequencies, dispersive shifts, effective subspace Hamiltonians). All
results cache against a structural signature and refresh automatically
when any device or coupling parameter mutates.

Dressing is always computed in the lab frame — frame selection never
alters dressed data.

References
----------
Gambetta, J., Blais, A., Schuster, D. I., Wallraff, A., Frunzio, L.,
    Majer, J., Devoret, M. H., Girvin, S. M., & Schoelkopf, R. J.
    Qubit-photon interactions in a cavity: Measurement-induced dephasing
    and number splitting. PRA 74, 042318 (2006).
Koch, J., Yu, T. M., Gambetta, J., Houck, A. A., Schuster, D. I., Majer,
    J., Blais, A., Devoret, M. H., Girvin, S. M., & Schoelkopf, R. J.
    Charge-insensitive qubit design derived from the Cooper pair box.
    PRA 76, 042319 (2007).
Blais, A., Grimsmo, A. L., Girvin, S. M., & Wallraff, A. Circuit quantum
    electrodynamics. Rev. Mod. Phys. 93, 025005 (2021), §IV on dispersive
    regime and dressed-state labeling.
"""

from __future__ import annotations

import itertools
import warnings
from dataclasses import dataclass
from typing import TYPE_CHECKING, Any, Mapping, Sequence

import jax.numpy as jnp
import numpy as np

from quchip.backend import EigensystemData, Operator, State, _backend_context
from quchip.chip.dressing import (
    BareProductReference,
    Labeling,
    assign_rowwise_greedy,
    label_eigensystem,
)
from quchip.chip.states import normalize_device_state_mapping
from quchip.devices.base import BaseDevice
from quchip.engine.stage1_frames import resolve_frame
from quchip.utils.jax_utils import contains_tracer
from quchip.utils.labeling import LabelKeyedDict, bare_label_from_mapping, resolve_label, top_components

if TYPE_CHECKING:
    from quchip.chip.chip import Chip
    from quchip.control.drive import BaseDrive


_DRESS_TRACING_ERROR = (
    "Chip.dress() returns a concrete dict-keyed DressedResult and is not "
    "traceable under jax.jit/grad/vmap. Use Chip.energy(), Chip.freq(), "
    "Chip.dispersive_shift(), or Chip.state() inside transforms — they "
    "route through the array-only kernel in quchip.chip.dressing and stay "
    "differentiable."
)


def _phase_fixed_state(state: Any, bare_index: int, xp: Any) -> Any:
    """Set one dressed vector's assigned bare overlap to a nonnegative real value."""
    anchor = state[bare_index]
    magnitude = xp.abs(anchor)
    threshold = xp.finfo(magnitude.dtype).eps
    safe_magnitude = xp.where(magnitude > threshold, magnitude, xp.asarray(1.0, dtype=magnitude.dtype))
    phase = xp.where(
        magnitude > threshold,
        xp.conj(anchor) / safe_magnitude,
        xp.asarray(1.0 + 0.0j, dtype=state.dtype),
    )
    return state * phase


[docs] @dataclass class DressedResult: """Store a frozen snapshot of a dressed-state diagonalization. Attributes ---------- eigenvalues : array-like Sorted dressed eigenvalues in GHz. eigenstates : array-like Backend eigenstate objects in the same order as ``eigenvalues``. Materialized lazily on first access from the underlying :class:`~quchip.backend.containers.EigensystemData` — the dressing / sweep hot path never touches them, so the per-column backend kets are only built when a caller actually asks for states. state_map : dict[tuple[int, ...], int] Assignment from bare product-basis label (one int per device) to the dressed eigenstate index it overlaps most with. dressed_eigenvalues : dict[tuple[int, ...], Any] Dressed eigenvalue for each assigned bare label — direct lookup path for :meth:`Chip.energy`. assignment_overlaps : dict[tuple[int, ...], float] ``|⟨bare|dressed⟩|²`` of each assignment; values below ``overlap_threshold`` flag hybridization. hybridized_labels : tuple[tuple[int, ...], ...] Bare labels whose assignment quality is below ``overlap_threshold``. A non-empty tuple triggers a user warning at dress time. bare_labels : tuple[tuple[int, ...], ...] Full canonical product-basis label set (all combinations of ``range(device.levels)`` for every device, in chip order). bare_labels_by_dressed_index : dict[int, tuple[int, ...]] Inverse of :attr:`state_map` — dressed index → assigned bare label. eigenvector_matrix : array-like or None Columns = dressed eigenvectors in the bare product basis. Used for :meth:`ChipAnalysis.operator_in_dressed_basis` and :meth:`ChipAnalysis.state_components`. overlap_threshold : float Minimum overlap for a confident assignment. labeling : str Algorithm id (currently only ``"DE"``), resolved to the confidence-ordered row-greedy overlap-matching policy actually run by :meth:`ChipAnalysis.dress` (:func:`~quchip.chip.dressing.assign_rowwise_greedy`). """ eigenvalues: Any state_map: dict[tuple[int, ...], int] dressed_eigenvalues: dict[tuple[int, ...], Any] assignment_overlaps: dict[tuple[int, ...], float] hybridized_labels: tuple[tuple[int, ...], ...] bare_labels: tuple[tuple[int, ...], ...] bare_labels_by_dressed_index: dict[int, tuple[int, ...]] eigenvector_matrix: Any = None overlap_threshold: float = 0.5 labeling: str = "DE" _eigensystem: EigensystemData | None = None @property def eigenstates(self) -> Any: """Backend eigenstate kets, materialized lazily from the eigensystem.""" if self._eigensystem is None: raise RuntimeError( "DressedResult was constructed without an EigensystemData; " "eigenstates are unavailable." ) return self._eigensystem.eigenstates
[docs] class ChipAnalysis: """Dressed-state analysis, caching, and dressed-basis helpers. Every :class:`~quchip.chip.chip.Chip` owns one ``ChipAnalysis`` as ``chip._analysis``. The chip class forwards its public dressed-state API here; users normally call methods on the chip, not this class directly. Caching: :meth:`dress` keys its cache on a structural signature covering backend identity, RWA policy, and the ``state_version`` of every device and coupling. Any mutation that bumps a version invalidates the cache on next access. """ def __init__(self, chip: "Chip") -> None: self._chip = chip self._dressed_result: DressedResult | None = None self._dressed_signature: tuple[Any, ...] | None = None self._array_cache: tuple[Any, Any, Any, Labeling] | None = None self._array_signature: tuple[Any, ...] | None = None self._bare_labels_cache: tuple[ tuple[tuple[int, ...], ...], dict[tuple[int, ...], int] ] | None = None self._bare_labels_signature: tuple[int, ...] | None = None def _analysis_signature(self) -> tuple[Any, ...]: """Hashable fingerprint covering every structural input to dressing.""" chip = self._chip return ( f"{type(chip.backend).__module__}.{type(chip.backend).__qualname__}", chip.rwa, tuple((device.label, device.state_version) for device in chip.devices), tuple( ( f"{type(coupling).__module__}.{type(coupling).__qualname__}", coupling.device_a_label, coupling.device_b_label, coupling.state_version, ) for coupling in chip.couplings ), ) def _canonical_bare_labels(self) -> tuple[tuple[int, ...], ...]: """Full product-basis label set ``⨂_d range(d.levels)`` in chip order.""" return self._bare_labels_with_index()[0] def _bare_labels_with_index( self, ) -> tuple[tuple[tuple[int, ...], ...], dict[tuple[int, ...], int]]: """Cached ``(bare_labels, label → index)`` pair, keyed on per-device dimensions.""" sig = tuple(device.levels for device in self._chip.devices) if self._bare_labels_cache is None or self._bare_labels_signature != sig: labels = tuple(itertools.product(*(range(d) for d in sig))) index_map = {label: idx for idx, label in enumerate(labels)} self._bare_labels_cache = (labels, index_map) self._bare_labels_signature = sig return self._bare_labels_cache def _state_label_from_mapping( self, device_states: Mapping[str | BaseDevice, int] | str | None = None, /, **device_state_kwargs: int, ) -> tuple[int, ...]: """Merge a ``{device: Fock}`` mapping into a full chip-ordered label tuple. Unspecified devices default to Fock index 0. A ``str`` shorthand is parsed through :func:`~quchip.chip.states.normalize_device_state_mapping`. Validates each value as a non-negative ``int`` (rejecting ``bool``) within device bounds. """ resolved = normalize_device_state_mapping(self._chip, device_states, device_state_kwargs) return self._label_from_resolved(resolved) def _label_from_resolved(self, resolved: Mapping[str, int]) -> tuple[int, ...]: """Validate an already-normalized ``{label: Fock}`` mapping into a full label tuple. Splits the validation pass out of :meth:`_state_label_from_mapping` so callers that already hold the normalized mapping (e.g. :meth:`state`) validate it without normalizing a second time. Unspecified devices default to Fock index 0; each value must be a non-negative ``int`` (rejecting ``bool``) within device bounds. The Fock-bound and type checks are layered chip-side on top of the canonical :func:`~quchip.utils.labeling.bare_label_from_mapping` spec-to-tuple builder; :meth:`Chip._resolve_device_index` rejects unknown labels first with the device-specific message. """ for device_label, value in resolved.items(): _, device = self._chip._resolve_device_index(device_label) if isinstance(value, bool): raise ValueError(f"Fock index for '{device.label}' must be an integer, got bool: {value!r}") if not isinstance(value, int): raise TypeError(f"Expected integer Fock index for '{device.label}', got {type(value).__name__}") if value < 0: raise ValueError(f"Fock index for '{device.label}' must be >= 0, got {value}") if value >= device.levels: raise ValueError( f"Fock index {value} for '{device.label}' exceeds device dimension ({device.levels} levels)" ) return bare_label_from_mapping(self._device_labels(), resolved, {}) def _label_from_plain_mapping(self, device_states: Mapping[Any, Any]) -> tuple[int, ...]: """Merge a mapping into a bare-label tuple without the full validation pass. Used by lookup-only paths (:meth:`energy`, :meth:`_dressed_state`). Unknown labels are rejected eagerly with the device-specific message; the tuple itself is assembled by the canonical :func:`~quchip.utils.labeling.bare_label_from_mapping`. """ for device_label in device_states: self._chip._resolve_device_index(device_label) return bare_label_from_mapping(self._device_labels(), device_states, {}) def _device_labels(self) -> tuple[str, ...]: """Chip device labels in tensor-product order.""" return tuple(device.label for device in self._chip.devices) def _compute_array_labeled(self) -> tuple[Any, Any, Any, Labeling]: """Pure-array path: ``(eigenvalues, eigenvector_matrix, eigenstates, labeling)``. Always returns the ``label_eigensystem`` kernel output directly. Cached against the structural signature only when the result is free of JAX tracers: under ``jit``/``grad``/``vmap`` the result is recomputed every call rather than stashing a tracer bound to a stale trace context. This is the trace-friendly primitive used by :meth:`energy`, :meth:`freq`, and :meth:`dispersive_shift`. :meth:`dress` is the eager dict-materialized view on top of this. """ chip = self._chip signature = self._analysis_signature() if ( self._array_cache is not None and self._array_signature == signature and not contains_tracer(self._array_cache) ): return self._array_cache saved_frame = chip._frame_spec chip._frame_spec = "lab" try: hamiltonian = chip.hamiltonian() finally: chip._frame_spec = saved_frame eigensystem = chip.backend.eigensystem_data(hamiltonian) eigenvalues = eigensystem.eigenvalues eigenvector_matrix = eigensystem.eigenvector_matrix evals_jax = jnp.asarray(eigenvalues) evecs_jax = jnp.asarray(eigenvector_matrix) dims = tuple(device.levels for device in chip.devices) reference = BareProductReference(dims=dims) labeling = label_eigensystem(evecs_jax, reference, policy=assign_rowwise_greedy) # The 3rd slot carries the EigensystemData (lazy eigenstates) rather than # a materialized ket list — nothing on the hot path reads it. The cache # tracer-check covers only slots (0, 1, 3); touching slot 2 would force # the lazy ``eigenstates`` property and defeat the deferral. result = (eigenvalues, eigenvector_matrix, eigensystem, labeling) if not contains_tracer((evals_jax, labeling.indices, labeling.overlaps)): self._array_cache = result self._array_signature = signature return result def _bare_label_index(self, label: tuple[int, ...]) -> int: """Position of ``label`` in canonical bare-label order (Python int).""" bare_labels, index_map = self._bare_labels_with_index() try: return index_map[label] except KeyError: available = list(bare_labels)[:10] raise KeyError( f"State label {label} is not a valid bare product-basis label. Available (first 10): {available}" ) from None @staticmethod def _array_labeled_concrete(kernel_labeling: Labeling) -> bool: """True when the kernel labeling carries no JAX tracers. The dict-materialized :class:`DressedResult` view concretizes the assignment indices, so it can only be built when the kernel output is free of tracers. Centralizes the gate shared by :meth:`dress` and :meth:`_ensure_dressed`. """ return not contains_tracer((kernel_labeling.indices, kernel_labeling.overlaps)) def _eigenvalue_of_label( self, label: tuple[int, ...], *, precomputed: tuple[Any, Any] | None = None, ) -> Any: """Dressed eigenvalue (GHz) for a bare label, gathered through the array kernel. Resolves *label* to its bare-product index and gathers ``eigenvalues[kernel_labeling.indices[bare_idx]]``. The gather stays a JAX-indexable op on the kernel output, so it is differentiable w.r.t. any traced chip parameter — no Python concretization of the eigenvalue. Pass *precomputed* = ``(eigenvalues, kernel_labeling)`` to share a single :meth:`_compute_array_labeled` across several labels (transition frequencies, dispersive shifts, anharmonicities). """ bare_idx = self._bare_label_index(label) if precomputed is None: eigenvalues, _, _, kernel_labeling = self._compute_array_labeled() else: eigenvalues, kernel_labeling = precomputed return eigenvalues[kernel_labeling.indices[bare_idx]]
[docs] def dress( self, *, overlap_threshold: float = 0.5, force: bool = False, labeling: str = "DE", ) -> DressedResult: """Diagonalize the lab-frame Hamiltonian and assign bare-state labels. Assignment goes through the ``label_eigensystem`` kernel (:mod:`quchip.chip.dressing`) with ``assign_rowwise_greedy`` — confidence-ordered row-greedy matching as a pure ``lax.scan``, ``O(D**2)`` in the Hilbert dimension versus the ``O(D**3)`` global variant. It is identical to ``assign_global_greedy`` in the dispersive / weak-hybridization regime and can differ only on strongly-hybridized chips, where the assignment is already approximate (those labels are flagged below). Bare labels whose best match is below ``overlap_threshold`` are flagged :attr:`DressedResult.hybridized_labels` and trigger a user warning. :class:`DressedResult` is the eager, dict-materialized view. Materialization concretizes the assignment indices and is **not traceable** — call :meth:`energy`, :meth:`freq`, :meth:`dispersive_shift`, or :meth:`state` from inside ``jax.jit``/``grad``/``vmap``; those route through the array kernel directly. Parameters ---------- overlap_threshold : float Confidence cutoff for the greedy assignment. force : bool Force recomputation even if the signature matches. labeling : str Currently only ``"DE"`` is implemented. Returns ------- DressedResult Cached result — mutate at the caller's own risk. """ if labeling != "DE": raise ValueError(f"Unsupported labeling {labeling!r}. Only 'DE' is implemented.") signature = self._analysis_signature() if ( not force and self._dressed_result is not None and self._dressed_signature == signature and self._dressed_result.labeling == labeling and self._dressed_result.overlap_threshold == float(overlap_threshold) ): return self._dressed_result eigenvalues, eigenvector_matrix, eigensystem, kernel_labeling = self._compute_array_labeled() if not self._array_labeled_concrete(kernel_labeling): raise RuntimeError(_DRESS_TRACING_ERROR) # The kernel already materialized the canonical product-basis keys in # Kronecker order (``BareProductReference``); reuse them instead of a # second ``itertools.product`` over the device dims. bare_labels = kernel_labeling.keys indices_np = np.asarray(kernel_labeling.indices) overlaps_np = np.asarray(kernel_labeling.overlaps) state_map: dict[tuple[int, ...], int] = {} bare_labels_by_dressed_index: dict[int, tuple[int, ...]] = {} assignment_overlaps: dict[tuple[int, ...], float] = {} dressed_eigenvalues: dict[tuple[int, ...], Any] = {} for k, bare_label in enumerate(bare_labels): idx = int(indices_np[k]) state_map[bare_label] = idx bare_labels_by_dressed_index[idx] = bare_label assignment_overlaps[bare_label] = float(overlaps_np[k]) dressed_eigenvalues[bare_label] = eigenvalues[idx] hybridized_labels = tuple( bare_label for bare_label in bare_labels if assignment_overlaps[bare_label] < overlap_threshold ) if hybridized_labels: preview = ", ".join( f"{label} ({assignment_overlaps[label]:.3f})" for label in sorted(hybridized_labels, key=lambda item: assignment_overlaps[item])[:4] ) warnings.warn( "Strong hybridization detected during dressed-state assignment; " "bare labels are approximate for " f"{len(hybridized_labels)} states. Lowest-overlap labels: " f"{preview}. Inspect DressedResult.assignment_overlaps for " "full assignment quality.", UserWarning, stacklevel=2, ) result = DressedResult( eigenvalues=eigenvalues, state_map=state_map, dressed_eigenvalues=dressed_eigenvalues, assignment_overlaps=assignment_overlaps, hybridized_labels=hybridized_labels, bare_labels=bare_labels, bare_labels_by_dressed_index=bare_labels_by_dressed_index, eigenvector_matrix=eigenvector_matrix, overlap_threshold=float(overlap_threshold), labeling=labeling, _eigensystem=eigensystem, ) self._dressed_result = result self._dressed_signature = signature return result
def _ensure_dressed(self) -> DressedResult: """Return the cached :class:`DressedResult`, diagonalizing if needed. Under JAX tracing the dict view cannot be materialized (its assignment indices are tracers), so this raises :class:`RuntimeError` in that case — trace-sensitive consumers should route through :meth:`_compute_array_labeled`. """ _, _, _, kernel_labeling = self._compute_array_labeled() if not self._array_labeled_concrete(kernel_labeling): raise RuntimeError(_DRESS_TRACING_ERROR) if self._dressed_result is None or self._dressed_signature != self._analysis_signature(): self.dress() assert self._dressed_result is not None return self._dressed_result @property def is_dressed(self) -> bool: """True if a cached dressed result is present and consistent.""" return self._dressed_result is not None
[docs] def energy( self, device_states: Mapping[str | BaseDevice, int] | None = None, /, **device_state_kwargs: int, ) -> Any: """Dressed eigenvalue (GHz) for the given bare-state label. Unspecified devices default to Fock index 0. Routes through the :func:`quchip.chip.dressing.label_eigensystem` array kernel, so this is safe inside ``jax.jit``/``grad``/``vmap`` — gradients flow through ``eigenvalues[labeling.indices[bare_idx]]`` to any traced chip parameters. """ resolved = normalize_device_state_mapping(self._chip, device_states, device_state_kwargs) label_t = self._label_from_plain_mapping(resolved) return self._eigenvalue_of_label(label_t)
[docs] def dressed_spectrum(self) -> Any: """Raw sorted eigenvalue array of the dressed Hamiltonian (GHz).""" return self._ensure_dressed().eigenvalues
def _dressed_state(self, **device_states: int) -> Any: """Dressed eigenstate (as a backend ket) for a bare-state label. Eager: the cached dict view (with hybridization warnings). Traced: selects the assigned eigenvector column straight through the :func:`label_eigensystem` array kernel — ``evecs[:, labeling.indices[bare_idx]]`` — so dressed initial states stay differentiable end-to-end. The eigenvector's global phase is gauge-dependent (``eigh`` column convention); populations and ``|overlap|`` are unaffected. """ label_t = self._label_from_plain_mapping(device_states) _, eigenvector_matrix, _, kernel_labeling = self._compute_array_labeled() if contains_tracer((eigenvector_matrix, kernel_labeling.indices)): bare_idx = self._bare_label_index(label_t) column = jnp.asarray(eigenvector_matrix)[:, kernel_labeling.indices[bare_idx]] dims = [device.levels for device in self._chip.devices] return self._chip.backend.from_array(column.reshape(-1, 1), dims=[dims, [1] * len(dims)]) dressed = self._ensure_dressed() try: eigen_idx = dressed.state_map[label_t] except KeyError: available = list(dressed.state_map.keys())[:10] raise KeyError( f"State label {label_t} not found in state map. Available (first 10): {available}" ) from None return dressed.eigenstates[eigen_idx] def _dressed_frequencies(self) -> dict[str, float]: """Per-device dressed 0 → 1 transition frequencies (GHz).""" return {device.label: self._transition_freq(device) for device in self._chip.devices}
[docs] def dressed_index( self, device_states: Mapping[str | BaseDevice, int] | None = None, /, **device_state_kwargs: int, ) -> int | None: """Dressed-state index assigned to a bare label, or ``None`` if unassigned.""" label = self._state_label_from_mapping(device_states, **device_state_kwargs) return self._ensure_dressed().state_map.get(label)
[docs] def bare_label(self, dressed_index: int) -> tuple[int, ...]: """Bare-state label assigned to a dressed-state index.""" if isinstance(dressed_index, bool) or not isinstance(dressed_index, int): raise TypeError(f"dressed_index must be an integer, got {type(dressed_index).__name__}") try: return self._ensure_dressed().bare_labels_by_dressed_index[dressed_index] except KeyError: raise ValueError(f"No bare-state label assigned to dressed index {dressed_index}") from None
[docs] def operator_in_dressed_basis( self, device: str | BaseDevice, op: str | Any, *, truncate: int | None = None, ) -> Operator: """Transform a local operator into the dressed eigenbasis. Computes ``U† O_embedded U`` where ``U`` is the dressed eigenvector matrix (columns are dressed eigenstates in the bare product basis), phase-fixed to the assigned bare-state convention used by :meth:`drive_matrix_elements`. Optional truncation keeps the lowest ``truncate`` dressed levels. Parameters ---------- device : str or BaseDevice Device whose local operator is embedded and transformed. op : str or Operator Operator name resolved off the device (e.g. ``"n"``, ``"a"``) or an already-built local-space operator. truncate : int, optional Keep only the lowest ``truncate`` dressed levels of the result. """ chip = self._chip backend = chip.backend dressed = self._ensure_dressed() idx, dev = chip._resolve_device_index(device) xp = backend.array_module raw_eigenvectors = xp.asarray(dressed.eigenvector_matrix, dtype=complex) U = xp.stack([ _phase_fixed_state( raw_eigenvectors[:, dressed_index], self._bare_label_index(dressed.bare_labels_by_dressed_index[dressed_index]), xp, ) for dressed_index in range(raw_eigenvectors.shape[1]) ], axis=1) # Resolve the operator name straight off the device (it owns the # vocabulary) rather than round-tripping through chip.observable. with _backend_context(backend): local_op = dev.local_operator(op) if isinstance(op, str) else op embedded = backend.embed(local_op, idx, chip.dims) op_array = backend.array_module.asarray(backend.to_array(embedded), dtype=complex) transformed = xp.conj(U).T @ op_array @ U if truncate is not None: if truncate <= 0: raise ValueError(f"truncate must be positive, got {truncate}") transformed = transformed[:truncate, :truncate] dims = [[truncate], [truncate]] else: dims = [[transformed.shape[0]], [transformed.shape[1]]] return backend.from_array(transformed, dims=dims)
[docs] def drive_matrix_elements( self, transition: str | BaseDevice | tuple[Mapping[str | BaseDevice, int], Mapping[str | BaseDevice, int]], *, drives: Sequence[str | "BaseDrive"] | None = None, ) -> LabelKeyedDict: """Return dressed matrix elements of wired drive operators. The matrix convention is ``m_j^{fi} = <f~|D_j|i~>``: the final dressed state is the row index and the initial dressed state is the column index. Each dressed eigenvector is phase-fixed so that its overlap with the assigned bare state is real and nonnegative. This makes relative matrix elements between different conditioned transitions independent of the backend's eigenvector phases. Passing a device selects the dressed ground-to-first-excitation transition of that device with every other device in its ground state. Passing ``(initial, final)`` mappings selects an arbitrary transition. Each drive must expose exactly one local Hamiltonian channel. The signal chain is not applied: this method returns the physical matrix element of each control line's drive operator, which can then be combined with declared signal-chain phasors. This is the weak-drive projection used for effective driven-Hamiltonian coefficients; see Magesan and Gambetta, Phys. Rev. A 101, 052308 (2020), DOI 10.1103/PhysRevA.101.052308. Parameters ---------- transition : str, BaseDevice, or tuple[mapping, mapping] Device shorthand, or ``(initial, final)`` bare-state mappings. Unspecified devices in either mapping default to level zero. drives : sequence[str or BaseDrive], optional Wired control lines to evaluate. ``None`` evaluates every line. Original or rebound drive objects resolve by label. Returns ------- LabelKeyedDict Drive-label mapping of backend-native scalar matrix elements. Values remain JAX-traceable on a JAX-capable backend; each entry is addressable by the drive object or its label. Raises ------ ValueError If no control equipment is attached, a selected line is not a device-target drive, or a drive exposes zero or multiple channels. KeyError If a requested drive label is absent from the attached equipment. TypeError If ``transition`` is neither a device reference nor a pair of state mappings. Examples -------- >>> from quchip import Chip, ChargeDrive, ControlEquipment, DuffingTransmon >>> q = DuffingTransmon(freq=5.0, anharmonicity=-0.25, levels=3, label="q") >>> drive = ChargeDrive(q, label="xy") >>> chip = Chip([q], control_equipment=ControlEquipment([drive])) >>> elements = chip.drive_matrix_elements(q, drives=[drive]) >>> abs(elements["xy"]) > 0 True """ equipment = self._chip.control_equipment if equipment is None: raise ValueError("drive_matrix_elements requires attached control equipment with wired drive lines") lines = equipment.lines by_label: dict[str, list[BaseDrive]] = {} for line in lines: by_label.setdefault(line.label, []).append(line) if drives is None: selected = lines else: selected = [] available = [line.label for line in lines] for drive in drives: label = resolve_label(drive) matches = by_label.get(label, []) if not matches: raise KeyError(f"No wired drive labeled '{label}'. Available drive lines: {available}") if len(matches) > 1: raise ValueError(f"Drive label '{label}' is ambiguous across {len(matches)} wired lines") selected.append(matches[0]) if isinstance(transition, (str, BaseDevice)): _, device = self._chip._resolve_device_index(transition) initial_label = self._state_label_from_mapping({}) final_label = self._state_label_from_mapping({device: 1}) elif ( isinstance(transition, tuple) and len(transition) == 2 and isinstance(transition[0], Mapping) and isinstance(transition[1], Mapping) ): initial_label = self._state_label_from_mapping(transition[0]) final_label = self._state_label_from_mapping(transition[1]) else: raise TypeError( "transition must be a device reference or an (initial_mapping, final_mapping) pair" ) _, eigenvectors, _, labeling = self._compute_array_labeled() xp = self._chip.backend.array_module U = xp.asarray(eigenvectors, dtype=complex) def phase_fixed_state(label: tuple[int, ...]) -> Any: bare_index = self._bare_label_index(label) state = U[:, labeling.indices[bare_index]] return _phase_fixed_state(state, bare_index, xp) initial = phase_fixed_state(initial_label) final = phase_fixed_state(final_label) elements = LabelKeyedDict() backend = self._chip.backend for drive in selected: if drive.target_kind != "device" or drive.device_label is None: raise ValueError( f"Drive '{drive.label}' targets {drive.target_kind!r}; dressed drive matrix elements " "currently require a device-target line" ) device_index, device = self._chip._resolve_device_index(drive.device_label) with _backend_context(backend): channels = drive.local_channels(device) if len(channels) != 1: raise ValueError( f"Drive '{drive.label}' exposes {len(channels)} local Hamiltonian channels; " "drive_matrix_elements requires exactly one unambiguous operator" ) operator = xp.asarray(backend.to_array(channels[0].operator), dtype=complex) initial_tensor = initial.reshape(self._chip.dims) acted = xp.tensordot(operator, initial_tensor, axes=((1,), (device_index,))) acted = xp.moveaxis(acted, 0, device_index).reshape(-1) elements[drive.label] = xp.vdot(final, acted) return elements
[docs] def state_components( self, state: int | Mapping[str | BaseDevice, int] | None = None, /, *, n_components: int = 5, **device_state_kwargs: int, ) -> dict[tuple[int, ...], float]: """Leading bare-basis probabilities ``|⟨bare|dressed⟩|²`` of a dressed eigenstate. ``state`` may be an ``int`` (direct dressed index) or a mapping of ``{device: Fock}`` (dressed index resolved via label matching). """ if n_components <= 0: raise ValueError(f"n_components must be positive, got {n_components}") dressed = self._ensure_dressed() if isinstance(state, int) and not isinstance(state, bool): dressed_idx: int = state else: if state is not None and not isinstance(state, Mapping): raise TypeError(f"state must be an int or mapping, got {type(state).__name__}") mapping = state if isinstance(state, Mapping) else None resolved_idx = self.dressed_index(mapping, **device_state_kwargs) if resolved_idx is None: label = self._state_label_from_mapping(mapping, **device_state_kwargs) raise ValueError(f"No dressed-state index assigned to bare label {label}") dressed_idx = resolved_idx if dressed_idx < 0 or dressed_idx >= len(dressed.eigenvalues): raise ValueError(f"dressed state index {dressed_idx} out of range for dimension {len(dressed.eigenvalues)}") return top_components(dressed.eigenvector_matrix, dressed.bare_labels, dressed_idx, n_components)
[docs] def dispersive_shift(self, device_a: str | BaseDevice, device_b: str | BaseDevice) -> float: """Dressed cross-Kerr shift (GHz): ``E(1,1) − E(1,0) − E(0,1) + E(0,0)``. Equivalent to the static ZZ interaction strength between the two devices with all others grounded. See Blais et al., RMP 93, 025005 (2021), §IV.C. Parameters ---------- device_a, device_b : str or BaseDevice The two devices whose cross-Kerr shift is evaluated. Examples -------- >>> from quchip import DuffingTransmon, Capacitive, Chip >>> q0 = DuffingTransmon(freq=5.0, anharmonicity=-0.25, levels=3, label="q0") >>> q1 = DuffingTransmon(freq=5.2, anharmonicity=-0.22, levels=3, label="q1") >>> chip = Chip([q0, q1], couplings=[Capacitive(q0, q1, g=0.02)]) >>> zz = chip.dispersive_shift(q0, q1) # residual ZZ in GHz """ eigenvalues, _, _, kernel_labeling = self._compute_array_labeled() precomputed = (eigenvalues, kernel_labeling) def e(a: int, b: int) -> Any: label = self._label_from_plain_mapping({device_a: a, device_b: b}) return self._eigenvalue_of_label(label, precomputed=precomputed) return e(1, 1) - e(1, 0) - e(0, 1) + e(0, 0)
[docs] def effective_subspace_hamiltonian( self, states: ( list[Mapping[str | BaseDevice, int] | tuple[int, ...]] | tuple[Mapping[str | BaseDevice, int] | tuple[int, ...], ...] ), ) -> np.ndarray: """Effective Hamiltonian projected onto a labeled bare subspace. Returns a Hermitian matrix in the user-chosen bare-state basis whose eigenvalues are exactly the dressed eigenenergies of the listed bare-label states. The truncated overlap block is Löwdin (``S^{-1/2}``) orthonormalized — the same des-Cloizeaux construction as :func:`quchip.analysis.effective_hamiltonian` — so hybridization with states *outside* the subspace cannot leak absolute dressed energies into the off-diagonal elements. Bridges full-chip dressed physics to the kind of low-dimensional effective model used in dispersive gate design and state-transfer analysis. Parameters ---------- states : sequence of mapping or tuple[int, ...] The bare-state labels spanning the subspace, each a ``{device: Fock}`` mapping or a full chip-ordered index tuple. """ dressed = self._ensure_dressed() label_to_bare_index = {label: idx for idx, label in enumerate(dressed.bare_labels)} dressed_indices: list[int] = [] bare_indices: list[int] = [] for state in states: label = state if isinstance(state, tuple) else self._state_label_from_mapping(state) if label not in dressed.state_map: raise ValueError(f"No dressed-state assignment found for bare label {label}") dressed_indices.append(dressed.state_map[label]) bare_indices.append(label_to_bare_index[label]) evec = np.asarray(dressed.eigenvector_matrix, dtype=complex) overlap = evec[np.asarray(bare_indices), :][:, np.asarray(dressed_indices)] energies = np.asarray(dressed.eigenvalues, dtype=complex)[np.asarray(dressed_indices)] # Löwdin-orthonormalize the truncated block: S^{-1/2} makes the row # vectors unitary on the subspace, so the result is unitarily similar # to diag(energies) no matter how much weight the dressed states carry # outside the chosen bare labels. gram_vals, gram_vecs = np.linalg.eigh(overlap @ overlap.conj().T) inv_sqrt = gram_vecs @ np.diag(gram_vals**-0.5) @ gram_vecs.conj().T effective = inv_sqrt @ (overlap @ np.diag(energies) @ overlap.conj().T) @ inv_sqrt return 0.5 * (effective + effective.conj().T)
[docs] def dressed_anharmonicity(self, device: str | BaseDevice) -> float: """Dressed anharmonicity (GHz): ``E_2 − 2·E_1 + E_0``, others grounded.""" eigenvalues, _, _, kernel_labeling = self._compute_array_labeled() precomputed = (eigenvalues, kernel_labeling) def e(n: int) -> Any: label = self._label_from_plain_mapping({device: n}) return self._eigenvalue_of_label(label, precomputed=precomputed) return e(2) - 2.0 * e(1) + e(0)
def _transition_freq( self, target: str | BaseDevice, when: dict[str | BaseDevice, int] | None = None, ) -> Any: """Conditional 0 → 1 dressed transition frequency of *target*. Other devices are grounded by default; *when* specifies any non-ground spectators. Traceable under ``jit``/``grad``/``vmap``. """ idx_target, _ = self._chip._resolve_device_index(target) ground_label = [0] * len(self._chip.devices) if when is not None: for device_key, value in when.items(): if isinstance(value, bool): raise ValueError(f"Fock index must be an integer, got bool: {value!r}") idx, _ = self._chip._resolve_device_index(device_key) ground_label[idx] = value excited_label = list(ground_label) excited_label[idx_target] += 1 eigenvalues, _, _, kernel_labeling = self._compute_array_labeled() precomputed = (eigenvalues, kernel_labeling) return ( self._eigenvalue_of_label(tuple(excited_label), precomputed=precomputed) - self._eigenvalue_of_label(tuple(ground_label), precomputed=precomputed) )
[docs] def freq( self, target: str | BaseDevice | None = None, when: dict[str | BaseDevice, int] | None = None, ) -> dict[str, Any] | Any: """All dressed 0→1 frequencies (GHz), or one conditional transition. Parameters ---------- target : str or BaseDevice, optional Device whose 0→1 transition is returned. ``None`` returns the full ``{device_label: frequency}`` dict for every device. when : dict[str | BaseDevice, int], optional Spectator Fock indices held fixed while the transition is evaluated; unlisted devices stay in their ground state. """ if target is None: return self._dressed_frequencies() return self._transition_freq(target, when=when)
[docs] def frame_info(self) -> dict[str, Any]: """Per-device frame reference frequency ``ω_ref,i`` (GHz). Resolves the chip's current frame spec through the same path the engine uses (:func:`quchip.engine.stage1_frames.resolve_frame`) and returns a flat ``{device_label: ω_ref,i}`` dict. These are the concrete frequencies the assembler will subtract as ``-Σ_i ω_ref,i n̂_i`` in :func:`stage2_assembly._build_static_h0`, so it exposes what will be solved without running the solver. Values are returned as produced by the frame resolver: concrete Python floats in ``"lab"``, ``"rotating"``, or scalar modes, and potentially JAX tracers in ``dict`` mode when the user wired a traced reference frequency through. Traced values are passed through unchanged to preserve differentiability. """ resolved = resolve_frame(self._chip, self._chip.frame) return dict(resolved.frequencies)
[docs] def state( self, device_states: Mapping[str | BaseDevice, int] | str | None = None, /, **device_state_kwargs: int, ) -> State: """Dressed eigenstate for Fock-indexed bare-state labels. Validates the mapping (rejects ``bool``, non-int, and out-of-range indices) and returns the assigned dressed eigenvector. A ``str`` shorthand (e.g. ``"eg1"``) is parsed through :func:`~quchip.chip.states.normalize_device_state_mapping` when :meth:`Chip.set_state_order` has been called. Use :meth:`Chip.bare_state` for arbitrary kets. Safe inside ``jax.jit``/``grad``/``vmap``: under tracing the eigenvector column is selected through the array kernel, so a dressed initial state is differentiable end-to-end. """ resolved = normalize_device_state_mapping(self._chip, device_states, device_state_kwargs) self._label_from_resolved(resolved) return self._dressed_state(**resolved)