Source code for quchip.chip.transformations.coordinate_change

"""Keep a captured change of coordinates on a rebuilt chip.

Coupling and effective-term reductions rotate the whole product space by a
unitary from one isolated interaction. Devices and kept edges keep their
authored terms. One :class:`~quchip.chip.effective.EffectiveTerms` contribution
holds the remaining part of the rotated Hamiltonian, which includes earlier
effective terms and carries their channels and notes through the rotation.
"""

from __future__ import annotations

from functools import reduce
from typing import Any

import jax.numpy as jnp

from quchip.engine.dressing import BareProductReference, label_eigensystem
from quchip.chip.effective import EffectiveTerms, OperatorProjection
from quchip.chip.sw import bare_hamiltonian
from quchip.chip.transformations.plumbing import inherited_notes, rebuild_chip
from quchip.chip.transformations.result import ReductionMap
from quchip.declarative.dissipation import CollapseChannel
from quchip.declarative.expr import materialize_expr
from quchip.engine.bands import embed_on_support
from quchip.engine.basis import _lowest_eigenpairs
from quchip.utils.values import DeferredValue


[docs] def product_energies(bases: Any, labels: Any) -> Any: """Return the local energies of ``labels`` summed over their product basis, in label order.""" return reduce(lambda total, energies: (total[:, None] + energies[None, :]).reshape(-1), (bases[label].energies for label in labels))
[docs] def isolated_hamiltonian(chip: Any, devices: Any, approximation: Any, *, couplings: Any = (), effective_terms: Any = ()) -> tuple[Any, Any, tuple[int, ...]]: """Return ``devices`` with the selected interaction alone: matrix, local product energies and dims. The diagonal holds the local product energies. The selected ``couplings`` or ``effective_terms`` supply all other elements. Uses ``chip``'s basis and backend. """ from quchip.chip.chip import Chip settings = dict(basis=chip.basis, backend=chip.backend, approximation=approximation) group = Chip(devices, list(couplings), effective_terms=effective_terms, **settings) matrix, labels, dims = bare_hamiltonian(group) energies = product_energies(group.resolve(frame="lab").bases, labels) # Local energies define H0. Subtract their assembled matrix before adding # that diagonal: basis roundoff must not masquerade as a selected coupling # between exactly degenerate isolated levels. local = bare_hamiltonian(Chip(devices, **settings))[0] return jnp.diag(energies) + (matrix - local), energies, dims
[docs] def labeled_eigenvectors(matrix: Any, dims: tuple[int, ...]) -> Any: """Return the eigenvectors of ``matrix`` ordered by the bare product label of largest overlap.""" _, vectors = _lowest_eigenpairs(matrix, matrix.shape[0]) return vectors[:, label_eigensystem(vectors, BareProductReference(dims)).indices]
[docs] def embed_on_labels(backend: Any, labels: Any, dims: tuple[int, ...], operator: Any, support: tuple[str, ...]) -> Any: """Embed a dense ``operator`` on ``support`` in the product space ordered by ``labels``.""" indices = tuple(labels.index(label) for label in support) local_dims = [dims[index] for index in indices] native = backend.from_array(operator, dims=[local_dims, local_dims]) return jnp.asarray(backend.to_array(embed_on_support(backend, native, indices, dims)))
[docs] def retain_coordinate_change( chip: Any, *, source: Any, labels: Any, dims: tuple[int, ...], rotation: Any, retained_h: Any, approximation: Any, devices: Any, couplings: Any, label: str, notes: tuple[str, ...], removed_owners: tuple[Any, ...] = (), ) -> tuple[Any, ReductionMap]: """Rebuild ``chip`` and keep in one contribution everything that its authored terms miss. ``rotation`` and ``retained_h`` use the source's local energy product basis in ``labels`` order. ``retained_h`` is the source Hamiltonian in the rotated coordinates. The rebuilt chip keeps ``devices`` and ``couplings`` with their authored parameters. ``retained_h`` minus their Hamiltonian, assembled at ``approximation``, becomes one :class:`EffectiveTerms` named ``label``. This contribution absorbs the source's effective terms, so their channels follow the rotation and their notes come before ``notes``. The channels of ``removed_owners`` also follow the rotation. Returns the rebuilt chip and its captured :class:`ReductionMap`. """ labels, dims = tuple(labels), tuple(dims) final = rebuild_chip(chip, devices=devices, couplings=couplings, effective_terms=()) retained_dims = tuple(final.authored_dims) resolved = final.resolve(frame="lab", approximation=approximation) lift = reduce(jnp.kron, (source.bases[name].energy_vectors for name in labels)) final_lift = reduce(jnp.kron, (resolved.bases[name].vectors for name in labels)) remaining_h = jnp.asarray(resolved.hamiltonian().matrix(backend=chip.backend)) correction = lift @ retained_h @ lift.conj().T - final_lift @ remaining_h @ final_lift.conj().T channels = [] responses: dict[str, tuple[str, ...]] = {} contributions = chip._collapse_contributions_with_owners(source.bases) for operator, rate, support, owner_label, name, _paths, owner in contributions: if not (isinstance(owner, EffectiveTerms) or any(owner is removed for removed in removed_owners)): continue support_labels = tuple(labels[index] for index in support) if support else labels local = jnp.asarray(chip.backend.to_array(materialize_expr(operator, chip.backend))) basis = reduce(jnp.kron, (source.bases[support_label].energy_vectors for support_label in support_labels)) embedded = embed_on_labels(chip.backend, labels, dims, basis.conj().T @ local @ basis, support_labels) transformed = rotation.conj().T @ embedded @ rotation channels.append(CollapseChannel(lift @ transformed @ lift.conj().T, materialize_expr(rate, chip.backend), f"{owner_label}.{name}")) if isinstance(owner, EffectiveTerms) and name in owner.omitted_responses: responses[f"{owner_label}.{name}"] = owner.omitted_responses[name] projection = OperatorProjection.capture(chip, labels, retained_dims, lift @ rotation @ lift.conj().T) terms = EffectiveTerms(labels, retained_dims, correction, tuple(channels), label=label, projection=projection, notes=(*inherited_notes(chip), *notes), omitted_responses=responses) terms.validate_for(final) final._effective_terms = (terms,) source_to_solver = reduce(jnp.kron, (source.bases[name].vectors.conj().T @ source.bases[name].energy_vectors for name in labels)) target_to_solver = final_lift.conj().T @ lift embedding = source_to_solver @ rotation @ target_to_solver.conj().T mapping = ReductionMap(labels, dims, labels, dims, chip.backend, DeferredValue(lambda: embedding)) return final, mapping