Source code for quchip.chip.transformations.methods

"""Reduction-method registry for device elimination (extension seam).

:func:`~quchip.chip.transformations.dispatch.eliminate`'s device path is
method-agnostic except at a few decision points. They are how to extract the
surviving pair parameters and how to carry the eliminated mode's jump operator
into the reduced frame. They also include whether a residual ZZ and a pathway
attribution are available, and what higher-order physics the reduction drops.
This module puts those decision points into a :class:`ReductionMethod`
strategy. New routes, such as a higher-order Schrieffer-Wolff method or numeric
fit, register with :func:`register_reduction_method`, following the rule
registry in :mod:`quchip.chip.retarget`.

Dispatch keys on the *static* ``method`` string, never on a traced value. The
two shipped strategies, :class:`SchriefferWolffMethod` and
:class:`ExactReduction`, are selected once by name and then operate on the
:class:`DeviceReductionContext` that the caller computed.
"""
# Reduction method strategies are keyed in `_REDUCTION_METHODS`.

from __future__ import annotations

from dataclasses import dataclass
from collections.abc import Callable
from functools import cached_property, partial
from typing import Any, ClassVar

from jax.scipy.linalg import expm
import numpy as np
import scipy.linalg

from quchip.chip.sw import (
    _exact_eigensystem,
    bare_index,
    degeneracy_error,
    exact_mode_subspace,
    exact_pair_parameters,
    extract_pair_parameters,
    h_effective_second_order,
    interaction_generator,
    pathway_attribution,
    sylvester_generator,
)
from quchip.utils.jax_utils import concrete_array_module, contains_tracer


def _sw_dressed_transition(h: Any, lowering: Any, rows: np.ndarray, *, error: Any = None) -> tuple[Any, Any]:
    """Read a second-order pole and unitary first-order weight on the supplied transition-state support."""
    xp = concrete_array_module(h, lowering)
    selected = np.isin(np.arange(h.shape[0]), rows)
    energies = xp.real(xp.diagonal(h))
    # Include every intermediate state coupled to either transition state, including Q-Q paths.
    interaction = xp.where(selected[:, None] | selected[None, :], h, 0.0)
    s = interaction_generator(energies, interaction, error=error)
    corrected = energies[rows] + xp.real(xp.sum(s[rows, :] * h[:, rows].T, axis=1))
    rotation = expm(-s) if contains_tracer(s) else scipy.linalg.expm(-s)
    states = rotation[:, rows]
    weight = xp.abs(states[:, 0].conj() @ xp.asarray(lowering) @ states[:, 1]) ** 2
    return corrected[1] - corrected[0], weight


[docs] @dataclass(frozen=True) class DeviceReductionContext: """Method-agnostic inputs of one device elimination. The device path of :func:`~quchip.chip.transformations.dispatch.eliminate` computes this context once and passes it to the chosen :class:`ReductionMethod`. The perturbative generator is computed only when a method requests it, and exact reduction does not use it. Attributes ---------- mode_label Its label. survivor_labels The touching survivors in bare-label order. The retained Hamiltonian and the pair diagnostics use the same order, so every ``("J", a, b)`` lookup agrees for all coupling-scan orders. labels Device labels in the bare product-basis order. dims Per-device Hilbert-space dimensions, aligned with ``labels``. h The bare chip Hamiltonian as a dense array. s The Sylvester generator rotating the P/Q partition of ``h``. p_mask Boolean mask selecting the kept (P) block of the product basis. sectors Total excitation number of each product state when ``h`` conserves it, else ``None``. """ mode_label: str survivor_labels: list[str] labels: list[str] dims: tuple[int, ...] h: Any p_mask: Any sectors: np.ndarray | None = None @cached_property def s(self) -> Any: """Return the SW generator, computed once per reduction on request.""" return sylvester_generator(self.h, self.p_mask, error=degeneracy_error(self.labels, self.dims))[0] @cached_property def sw_embedding(self) -> Any: rotation = expm(-self.s) if contains_tracer(self.s) else scipy.linalg.expm(-self.s) return rotation[:, np.flatnonzero(self.p_mask)] @cached_property def sw_hamiltonian(self) -> Any: return h_effective_second_order(self.h, self.s, self.p_mask) @cached_property def eigensystem(self) -> Any: """Diagonalize and label the captured Hamiltonian once per reduction.""" return _exact_eigensystem(self.h, self.dims, self.sectors) @cached_property def exact(self) -> Any: return exact_mode_subspace(self.h, self.labels, self.dims, self.mode_label, self.survivor_labels, self.sectors, eigensystem=self.eigensystem)
[docs] class ReductionMethod: """Compute a retained Hamiltonian, embedding and reduction diagnostics. A concrete strategy declares its :attr:`name` (the ``method`` string that :func:`eliminate` dispatches on) and implements the hooks below. Each hook receives the :class:`DeviceReductionContext` the caller assembled and uses its fixed product energy coordinates. Every hook body runs on ``jax.grad``/``jit`` paths and must stay traceable. Do not use ``float()``/``int()``/``bool()`` or Python branching on a traced value. """ name: ClassVar[str]
[docs] def source_approximation(self, chip: Any) -> Any: """Approximation strategy of the static model this route reads.""" return chip.approximation
[docs] def retained_hamiltonian(self, ctx: DeviceReductionContext) -> Any: """Return the complete Hamiltonian on the retained product coordinates.""" raise NotImplementedError
[docs] def embedding(self, ctx: DeviceReductionContext) -> Any: """Map retained energy coordinates into the captured full energy basis.""" raise NotImplementedError
[docs] def pair_parameters(self, ctx: DeviceReductionContext) -> dict: """Reduced per-survivor and per-pair parameters. Returns a mapping that matches :func:`~quchip.chip.sw.extract_pair_parameters` /:func:`~quchip.chip.sw.exact_pair_parameters`, with ``{survivor: {"freq_after": ...}}`` for each survivor and a ``("J", a, b)`` entry for each survivor pair. A route that resolves it also adds a ``("zz", a, b)`` entry. """ raise NotImplementedError
[docs] def transform_operator(self, ctx: DeviceReductionContext, operator: Any) -> Any: """Carry a full operator into the kept mode-ground manifold.""" embedding = self.embedding(ctx) return embedding.conj().T @ concrete_array_module(embedding, operator).asarray(operator) @ embedding
[docs] def dressed_transition(self, ctx: DeviceReductionContext, lowering: Any) -> tuple[Any, Any]: """Compute the eliminated mode's dressed transition and lowering weight. The default reads the labeled exact eigensystem of ``ctx.h``. A route can override it with its own approximation, as SW does. Parameters ---------- ctx Captured reduction inputs in product energy coordinates. lowering Mode lowering operator embedded in the full product space. Returns ------- tuple Transition frequency in GHz and dimensionless squared matrix element. """ eigenvalues, eigenvectors, labeling = ctx.eigensystem xp = concrete_array_module(eigenvalues, eigenvectors, labeling.indices, lowering) rows = np.array([bare_index(ctx.labels, ctx.dims), bare_index(ctx.labels, ctx.dims, ctx.mode_label)]) indices = xp.asarray(labeling.indices)[rows] energies = xp.asarray(eigenvalues)[indices] states = xp.asarray(eigenvectors)[:, indices] weight = xp.abs(states[:, 0].conj() @ xp.asarray(lowering) @ states[:, 1]) ** 2 return energies[1] - energies[0], weight
[docs] def residual_zz(self, ctx: DeviceReductionContext, pair_params: dict, a: str, b: str) -> Any | None: """Residual ZZ between survivor pair ``(a, b)``, or ``None`` if the route cannot resolve it.""" raise NotImplementedError
[docs] def pathways(self, ctx: DeviceReductionContext, pair_params: dict, a: str, b: str) -> list | None: """Top virtual-state attribution of the pair's mediated exchange, or ``None``.""" raise NotImplementedError
[docs] def pathway_source(self, ctx: DeviceReductionContext) -> Callable[[int, int], list] | None: """Attribution of any retained element to virtual states, or ``None`` when the route has none. The function takes two product-state indices of ``ctx`` and returns ``(index, amount)`` pairs. The elimination result keeps it, so it holds only the arrays it reads. """ return None
[docs] def label_margins(self, ctx: DeviceReductionContext) -> dict | None: """Margins of the route's dressed-state labeling checks, or ``None`` when it has none.""" return None
[docs] def dropped_suffix(self) -> str: """Trailing clause of the ``"Dropped: ..."`` note: the physics this route omits.""" raise NotImplementedError
[docs] class SchriefferWolffMethod(ReductionMethod): """2nd-order Schrieffer-Wolff reduction (``method="sw"``). This reduction is perturbative and differentiable, and the pair parameters come from the projected 2nd-order effective Hamiltonian. The mediated exchange carries a virtual-state pathway attribution. Residual ZZ is a higher-order correction that this route does not represent (Bravyi, DiVincenzo & Loss, Ann. Phys. 326, 2793 (2011)). """ name: ClassVar[str] = "sw"
[docs] def retained_hamiltonian(self, ctx: DeviceReductionContext) -> Any: return ctx.sw_hamiltonian
[docs] def embedding(self, ctx: DeviceReductionContext) -> Any: return ctx.sw_embedding
[docs] def dressed_transition(self, ctx: DeviceReductionContext, lowering: Any) -> tuple[Any, Any]: """Compute second-order energies and a unitary first-order transition weight.""" rows = np.array([bare_index(ctx.labels, ctx.dims), bare_index(ctx.labels, ctx.dims, ctx.mode_label)]) return _sw_dressed_transition(ctx.h, lowering, rows, error=degeneracy_error(ctx.labels, ctx.dims))
[docs] def pair_parameters(self, ctx: DeviceReductionContext) -> dict: h_eff = self.retained_hamiltonian(ctx) p_index = np.flatnonzero(ctx.p_mask) return extract_pair_parameters(h_eff, p_index, ctx.labels, ctx.dims, ctx.mode_label)
[docs] def residual_zz(self, ctx: DeviceReductionContext, pair_params: dict, a: str, b: str) -> Any | None: return None
[docs] def pathways(self, ctx: DeviceReductionContext, pair_params: dict, a: str, b: str) -> list | None: i_idx = bare_index(ctx.labels, ctx.dims, a) j_idx = bare_index(ctx.labels, ctx.dims, b) return pathway_attribution(ctx.h, ctx.s, ctx.p_mask, i_idx, j_idx)
[docs] def pathway_source(self, ctx: DeviceReductionContext) -> Callable[[int, int], list] | None: return partial(pathway_attribution, ctx.h, None, ctx.p_mask)
[docs] def dropped_suffix(self) -> str: return ", higher-order (>2) corrections."
[docs] class ExactReduction(ReductionMethod): """Exact-from-dressing reduction (``method="exact"``). This reduction reads reduced parameters from an exact diagonalization of the same engine-consumed static model as the SW route. It gives exact kept-block energies (which residual ZZ needs), but requires a full diagonalization. It has no perturbative generator, so no pathway attribution is available (:func:`~quchip.chip.sw.exact_pair_parameters`). """ name: ClassVar[str] = "exact"
[docs] def retained_hamiltonian(self, ctx: DeviceReductionContext) -> Any: return ctx.exact.hamiltonian
[docs] def embedding(self, ctx: DeviceReductionContext) -> Any: return ctx.exact.embedding
[docs] def pair_parameters(self, ctx: DeviceReductionContext) -> dict: return exact_pair_parameters(ctx.exact, ctx.labels, ctx.dims, ctx.mode_label, ctx.survivor_labels)
[docs] def residual_zz(self, ctx: DeviceReductionContext, pair_params: dict, a: str, b: str) -> Any | None: zz = pair_params[("zz", a, b)] return concrete_array_module(zz).real(zz)
[docs] def pathways(self, ctx: DeviceReductionContext, pair_params: dict, a: str, b: str) -> list | None: return None
[docs] def label_margins(self, ctx: DeviceReductionContext) -> dict | None: return ctx.exact.label_margins
[docs] def dropped_suffix(self) -> str: return "."
_REDUCTION_METHODS: dict[str, ReductionMethod] = {}
[docs] def register_reduction_method(method: ReductionMethod) -> None: """Register a reduction strategy under its name. Parameters ---------- method : ReductionMethod Strategy added under its unique ``name``. """ _REDUCTION_METHODS[method.name] = method
[docs] def lookup_reduction_method(name: str) -> ReductionMethod | None: """Return the strategy registered under ``name``, or ``None`` if none is.""" return _REDUCTION_METHODS.get(name)
[docs] def reduction_method_names() -> tuple[str, ...]: """The registered method names, in registration order.""" return tuple(_REDUCTION_METHODS)
register_reduction_method(SchriefferWolffMethod()) register_reduction_method(ExactReduction())