Source code for quchip.inverse_design.observables

"""Target compilation for :func:`quchip.fit_a_dress`.

The desired-chip path reads component-declared numbers without dressing the
input chip. The deprecated compatibility path remains separate so its
seed-and-target semantics are not silently reinterpreted during migration.
"""

from __future__ import annotations

from dataclasses import dataclass
from typing import Any

from quchip.utils.labeling import resolve_label


_DRESSED_KIND_ALIASES = {
    "g": "coupling_strength",
    "coupling_strength": "coupling_strength",
    "chi": "cross_kerr",
    "cross_kerr": "cross_kerr",
    "static_zz": "cross_kerr",
    "zz": "cross_kerr",
    "exchange": "exchange_rate",
    "exchange_rate": "exchange_rate",
    "freq": "freq",
    "anharmonicity": "anharmonicity",
}


[docs] @dataclass(frozen=True) class TargetSpec: """A single optimization target. Attributes ---------- kind Observable kind — one of ``"freq"``, ``"anharmonicity"``, ``"chi"``, ``"zz"``, ``"exchange"``, ``"g"``. label Device label, ``(label_a, label_b)`` tuple, or coupling label that locates this target on the chip. target Desired value in GHz. source Where the target came from: ``"component default"``, ``"explicit"``, or ``"legacy"`` for the deprecated compatibility contract. """ kind: str label: Any target: float source: str = "legacy"
def _normalize_dressed_kind(kind: object) -> str: """Return the canonical desired-chip observable name for *kind*.""" name = str(kind) try: return _DRESSED_KIND_ALIASES[name] except KeyError as exc: available = sorted(set(_DRESSED_KIND_ALIASES.values())) raise ValueError(f"Unknown dressed constraint {name!r}. Available canonical names: {available}.") from exc def _resolve_target_locator(locator: Any) -> Any: """Normalize a component or pair locator without evaluating a chip.""" if isinstance(locator, tuple): if len(locator) != 2: raise ValueError(f"Pair constraints require exactly two device objects or labels, got {locator!r}.") return tuple(resolve_label(part) for part in locator) return resolve_label(locator)
[docs] def build_dressed_target_specs( chip, constraints: dict | None = None, ) -> tuple[TargetSpec, ...]: """Compile a desired chip's declared numbers into dressed constraints. Unlike :func:`build_target_specs`, this desired-chip path never calls a dressed-analysis method on ``chip``. Devices and couplings provide their component-owned defaults; explicit constraints extend those defaults, replace the same ``(kind, locator)`` entry, or remove it with ``None``. Parameters ---------- chip Numerical desired-chip specification. constraints Optional ``{component_or_pair: {observable: value_or_none}}`` mapping. Pair locators need not correspond to a direct coupling edge. """ keyed: dict[tuple[str, Any], TargetSpec] = {} for device in chip.devices: for kind, value in device.default_dressed_targets().items(): canonical = _normalize_dressed_kind(kind) spec = TargetSpec(canonical, device.label, float(value), "component default") keyed[(spec.kind, spec.label)] = spec for coupling in chip.couplings: kind, value = coupling.default_dressed_target() canonical = _normalize_dressed_kind(kind) spec = TargetSpec(canonical, coupling.label, float(value), "component default") keyed[(spec.kind, spec.label)] = spec if constraints is None: return tuple(keyed.values()) if not isinstance(constraints, dict): raise TypeError(f"constraints must be None or a dict, got {type(constraints).__name__}") for raw_locator, metrics in constraints.items(): locator = _resolve_target_locator(raw_locator) if not isinstance(metrics, dict): raise TypeError( "constraints values must be dicts mapping observable names to " f"numeric values or None, got {type(metrics).__name__} for {locator!r}." ) for raw_kind, value in metrics.items(): kind = _normalize_dressed_kind(raw_kind) key = (kind, locator) if value is None: keyed.pop(key, None) else: keyed[key] = TargetSpec(kind, locator, float(value), "explicit") return tuple(keyed.values())
[docs] def infer_coupling_mode(coupling, override: str | None = None) -> str: """Infer whether a coupling's strength is targeted as ``"g"``, ``"chi"``, or ``"zz"``. When both devices are computational, the user almost always cares about the static ``zz``; a qubit-resonator pair is instead best anchored by the dispersive ``chi``; everything else is fit through the bare coupling strength. An explicit ``override`` short circuits the heuristic — including to ``"chi"`` on a pair that would not otherwise infer it; :func:`build_target_specs` validates that a ``"chi"`` target always has exactly one computational endpoint, whichever path selected it. Parameters ---------- coupling The coupling whose mode is being inferred. override Explicit mode (``"g"``, ``"chi"``, or ``"zz"``), or ``None`` to use the computational-endpoint heuristic. Returns ------- str ``"g"``, ``"chi"``, or ``"zz"``. """ if override is not None: return override a_comp = getattr(coupling.device_a, "computational", False) b_comp = getattr(coupling.device_b, "computational", False) if a_comp and b_comp: return "zz" if a_comp or b_comp: return "chi" return "g"
def _validate_chi_target(chip, spec: TargetSpec) -> None: """Ensure a ``"chi"`` :class:`TargetSpec` resolves to a coupling with exactly one computational endpoint. Applied to every ``"chi"`` spec regardless of origin — an auto device-implied target, a ``coupling_targets`` entry, or an explicit ``observable_targets`` entry — since ``_chi`` (the observable this spec anchors) is only physically meaningful between one qubit (computational) endpoint and one readout (non-computational) endpoint. """ coupling = next((c for c in chip.couplings if c.label == spec.label), None) if coupling is None: raise ValueError( f"'chi' target label {spec.label!r} does not match any coupling on the chip; " "'chi' (dispersive shift) targets must be keyed by a coupling label." ) a_comp = getattr(coupling.device_a, "computational", False) b_comp = getattr(coupling.device_b, "computational", False) if a_comp == b_comp: raise ValueError( f"Coupling {coupling.label!r} is targeted as 'chi', which requires exactly one " "computational endpoint (chi is the dispersive shift of a readout mode " f"conditioned on a qubit state); got device_a={coupling.device_a_label!r} " f"computational={a_comp}, device_b={coupling.device_b_label!r} computational={b_comp}." ) def _coerce_explicit_target_specs(observable_targets: dict | None) -> tuple[TargetSpec, ...]: """Normalize ``observable_targets`` into a tuple of :class:`TargetSpec`.""" if not observable_targets: return () specs: list[TargetSpec] = [] for key, metrics in observable_targets.items(): if not isinstance(metrics, dict): raise TypeError( "observable_targets values must be dicts mapping observable kinds to numeric targets, " f"got {type(metrics).__name__}" ) resolved_key = tuple(resolve_label(part) for part in key) if isinstance(key, tuple) else resolve_label(key) for kind, target in metrics.items(): specs.append(TargetSpec(str(kind), resolved_key, float(target))) return tuple(specs)
[docs] def build_target_specs(chip, coupling_targets: dict, observable_targets: dict | None = None) -> tuple[TargetSpec, ...]: """Merge device defaults, coupling targets, and explicit observables. The returned ordering is: 1. For every device, the *dressed* 0→1 transition frequency (``chip.freq(device)``) is anchored. Computational devices also anchor on the dressed anharmonicity (``chip.dressed_anharmonicity(device)``). Anchoring the dressed observable rather than the bare device attribute makes the defaults model-agnostic — Duffing, charge-basis transmon, and fluxonium all expose a dressed 0→1 spacing, even when the bare parametrization is wholly different (no ``freq`` or ``anharmonicity`` attribute). 2. One coupling target per entry in ``coupling_targets``, with mode resolved via :func:`infer_coupling_mode` (override if a string mode is supplied, heuristic otherwise). 3. Every explicit entry from ``observable_targets``. An explicit ``(kind, label)`` in ``observable_targets`` suppresses the same-``(kind, label)`` default from steps 1 and 2, so the user never ends up with duplicate anchors for the same quantity. Returns ------- tuple[TargetSpec, ...] Merged specs in the order documented above. Raises ------ ValueError A coupling target mode is not one of ``"chi"``, ``"zz"``, ``"g"``; or any ``"chi"`` target in the merged result — from an auto device-implied default, a ``coupling_targets`` entry, or an explicit ``observable_targets`` entry — does not resolve to a coupling with exactly one computational endpoint (see :func:`_validate_chi_target`). """ explicit_specs = _coerce_explicit_target_specs(observable_targets) explicit_keys = {(spec.kind, spec.label) for spec in explicit_specs} normalized_coupling_targets = {resolve_label(key): value for key, value in coupling_targets.items()} specs: list[TargetSpec] = [] for device in chip.devices: spec = TargetSpec("freq", device.label, float(chip.freq(device))) if (spec.kind, spec.label) not in explicit_keys: specs.append(spec) if device.computational and device.levels >= 3: spec = TargetSpec( "anharmonicity", device.label, float(chip.dressed_anharmonicity(device)), ) if (spec.kind, spec.label) not in explicit_keys: specs.append(spec) for coupling in chip.couplings: if coupling.label not in normalized_coupling_targets: continue mode = infer_coupling_mode(coupling, normalized_coupling_targets[coupling.label]) if mode not in ("chi", "zz", "g"): raise ValueError(f"Unsupported coupling target mode {mode!r} for {coupling.label!r}") spec = TargetSpec(mode, coupling.label, float(coupling.coupling_strength)) if (spec.kind, spec.label) not in explicit_keys: specs.append(spec) specs.extend(explicit_specs) for spec in specs: if spec.kind == "chi": _validate_chi_target(chip, spec) return tuple(specs)