Source code for quchip.engine.stage1_frames
"""Stage 1: resolve a :data:`FrameSpec` into a :class:`ResolvedFrame`.
This stage is purely combinatorial: it decides which rotating frame to
work in.
Supported specs
---------------
* ``"lab"`` — every reference frequency is zero; stage 2 emits the
bare chip Hamiltonian unchanged.
* ``"rotating"`` — each device's reference is its
:attr:`~quchip.devices.base.BaseDevice.reference_freq` (the device's
readout/LO reference, defaulting to the dressed drive frequency
``ω_d``), so stage 2 builds
.. math::
H(t) \\;=\\; H_0 - \\sum_i \\omega_{\\text{ref},i} n_i
+ V_{\\text{drive}}(t) + V_{\\text{coupling}}(t),
the standard rotating-frame form used in cQED / driven multi-level
systems (e.g. Gambetta et al., *PRA* **74**, 042318 (2006);
Krantz et al., *Appl. Phys. Rev.* **6**, 021318 (2019)). Setting a
device's ``reference_freq`` off its transition surfaces a residual
detuning ``Δ = ω − ω_ref`` in ``H₀`` — idle Ramsey precession.
* Scalar — every device uses the same shared reference frequency.
* ``dict[str | BaseDevice, scalar]`` — per-device references; missing
entries default to ``0.0``.
The demodulation frequencies are ``ω_ref − ω_frame`` per device: observables
are always reported co-rotating at ``reference_freq`` (the readout LO),
independent of which frame the solver integrated in. In the default
``"rotating"`` mode the integration frame *is* the reference frame, so the
demodulation is a no-op and the raw stored states already sit in the readout
frame (``result.states`` and ``result.expect`` agree). Transverse observables
(``<a>``, ``<σ_x>``) thus come back as the non-oscillatory demodulated
envelope; only an explicitly overridden non-reference integration frame
leaves ``result.states`` in that other frame.
Whether a coupling band folds into ``H₀`` is decided per band in stage
2, from the concreteness of its frame carrier ``Δa·ω_a + Δb·ω_b`` — not
here (see :func:`~quchip.engine.stage2_assembly._collect_coupling_terms`).
"""
from __future__ import annotations
from typing import TYPE_CHECKING, Any
from quchip.engine.ir import ResolvedFrame, _is_scalar_like
from quchip.utils.labeling import resolve_label
if TYPE_CHECKING:
from quchip.chip.chip import Chip
from quchip.engine.ir import FrameSpec
[docs]
def resolve_frame(chip: Chip, frame_spec: FrameSpec) -> ResolvedFrame:
"""Resolve *frame_spec* into a :class:`ResolvedFrame`.
Dispatches on ``frame_spec`` shape (``str`` / scalar-like / dict),
fills a per-device ``frequencies`` dict, and computes the
demodulation frequencies ``reference_freq − ω_frame``. See the
module docstring for the physical meaning of each mode.
Dressing happens lazily: reading a device's
:attr:`~quchip.devices.base.BaseDevice.reference_freq` diagonalizes
the chip only when its default (the dressed drive frequency) is
actually consulted. A chip whose devices all carry explicit
``reference_freq`` overrides resolves any frame spec without ever
diagonalizing.
"""
devices = chip.devices
labels = [dev.label for dev in devices]
mode: str
frequencies: dict[str, Any]
# Dispatch by spec shape. Keep the isinstance(str) check ahead of any
# equality comparison so no JAX tracer is compared to a string
# (which would yield a traced bool and force concretization).
if isinstance(frame_spec, str):
if frame_spec == "lab":
mode = "lab"
frequencies = {label: 0.0 for label in labels}
elif frame_spec == "rotating":
mode = "rotating"
frequencies = {dev.label: dev.reference_freq for dev in devices}
else:
raise ValueError(f"Unknown frame string. Expected 'lab' or 'rotating', got {frame_spec!r}")
elif _is_scalar_like(frame_spec):
mode = "float"
frequencies = {label: frame_spec for label in labels}
elif isinstance(frame_spec, dict):
mode = "dict"
normalized = {resolve_label(key): value for key, value in frame_spec.items()}
frequencies = {label: normalized.get(label, 0.0) for label in labels}
else:
raise TypeError(
"frame_spec must be 'lab', 'rotating', a scalar-like frequency, or a "
f"dict[str|BaseDevice, scalar-like], got {type(frame_spec).__name__}"
)
demod_freqs = {dev.label: dev.reference_freq - frequencies[dev.label] for dev in devices}
return ResolvedFrame(
frequencies=frequencies,
demod_freqs=demod_freqs,
mode=mode,
)