Source code for quchip.chip.partition

"""Exact subsystem partitioning into connected components of a chip's independence graph.

Two device labels share a component when *any* structural object couples them.
Such an object is a coupling edge, a non-separable bath whose target set
contains both, classical drive crosstalk between their lines, or the captured
maps of one effective contribution, which can mix the devices they act on. All
decisions use only labels and object presence, never parameter values, which
can be JAX tracers.

When observables are split across components, the user's own indices pass through unchanged, and
any injected factor is appended after them. A scalar local value colliding with one or more
factors is re-indexed to 0, with each factor appended after it in encounter order.

The independence graph only recognizes line-mixing as ``Crosstalk`` entries. It
does not see a user-authored :class:`~quchip.control.signal.SignalTransform`
that mixes drive lines in another way. Such mixing must be expressed as
``Crosstalk`` so the affected devices land in one component.
"""
# When split_e_ops splits observables, keys are resolved and validated while
# local observables and cross-component factors are grouped. Factor indices are
# assigned afterward, so collisions never depend on dict iteration order. A user
# key whose value is already a list keeps index=None in its LocalEop. The key
# plan is always keyed by the resolved label (or label pair), matching how
# `PartitionedSimulationResult` normalizes lookups. The key plan is never keyed
# by the raw user key, which can be an object rather than its label.

from __future__ import annotations

from dataclasses import dataclass, field
from typing import TYPE_CHECKING, Any, Iterable, Sequence

from quchip.chip.effective import lineage_labels
from quchip.utils.labeling import resolve_label

if TYPE_CHECKING:
    from quchip.chip.chip import Chip
    from quchip.control.signal import SignalTransform


def _line_device_labels(chip: "Chip", line: Any) -> tuple[str, ...]:
    """Device labels a control line acts on (edge lines resolve to both endpoints)."""
    from quchip.control.drive import CouplingDrive

    target = line.target_label
    if target is None:
        return ()
    if isinstance(line, CouplingDrive):
        coupling = chip.coupling_map[target]
        return (coupling.device_a_label, coupling.device_b_label)
    return (target,)


[docs] def independence_edges(chip: "Chip", resolved: Any | None = None) -> list[tuple[str, str]]: """Label pairs that must share one solve. A clique over N labels is emitted as an (N-1)-edge star, giving identical connected components with fewer edges. """ resolved = chip.resolve() if resolved is None else resolved supports = list(resolved.dynamical_supports) # A captured map can mix the devices it acts on even when no term couples # them, so its contribution and those devices stay in one solve. for terms in chip.effective_terms: if terms.projection is not None: reach = set(terms.labels) | lineage_labels(terms.projection) supports.append(tuple(device.label for device in chip.devices if device.label in reach)) edges: list[tuple[str, str]] = [] for support in supports: edges.extend((support[0], other) for other in support[1:]) equipment = chip.control_equipment if equipment is not None: line_devices = {line.label: _line_device_labels(chip, line) for line in equipment.lines} for xt in chip.crosstalks: for a in line_devices.get(xt.source, ()): for b in line_devices.get(xt.victim, ()): edges.append((a, b)) return edges
[docs] def connected_components(labels: Sequence[str], edges: Iterable[tuple[str, str]]) -> list[list[str]]: """Union-find components, each in ``labels`` order, ordered by first member.""" parent = {label: label for label in labels} def find(x: str) -> str: while parent[x] != x: parent[x] = parent[parent[x]] x = parent[x] return x for a, b in edges: root_a, root_b = find(a), find(b) if root_a != root_b: parent[root_b] = root_a groups: dict[str, list[str]] = {} for label in labels: groups.setdefault(find(label), []).append(label) return list(groups.values())
[docs] @dataclass(frozen=True) class PartitionComponent: """One independent block: its device labels (chip order) and its solve-ready sub-chip.""" labels: tuple[str, ...] chip: "Chip"
[docs] @dataclass(frozen=True) class PartitionResult: """Connected components of a chip's independence graph, as solve-ready sub-chips. ``chip_order`` is the parent chip's original device-label order (its ``chip.devices`` order). It is *not* the concatenation of the ``components`` labels, which follows connected-component discovery order. The two can interleave differently when the chip's device order does not already group each component's members together. Anything that reconstructs a joint state from the per-component pieces (for example :class:`~quchip.results.partitioned.PartitionedSimulationResult`) must permute into ``chip_order`` to match the joint solve. """ components: tuple[PartitionComponent, ...] chip_order: tuple[str, ...] notes: tuple[str, ...] = () _owner: dict = field(default_factory=dict, repr=False, init=False) def __post_init__(self) -> None: for i, comp in enumerate(self.components): for label in comp.labels: self._owner[label] = i @property def is_trivial(self) -> bool: return len(self.components) == 1
[docs] def owner_of(self, device: Any) -> int: label = resolve_label(device) if label not in self._owner: raise ValueError(f"Device '{label}' is not in this partition. Known: {sorted(self._owner)}") return self._owner[label]
def __len__(self) -> int: return len(self.components) def __iter__(self): return iter(self.components)
def _component_baths(clone: "Chip", member_set: set[str]) -> list[Any]: """Baths restricted to one component; explicit target lists are filtered.""" kept: list[Any] = [] for bath in clone.baths: if bath._targets is None: kept.append(bath.copy()) # "all devices" re-scopes naturally to the sub-chip continue targets = [t for t in bath.resolve_targets(clone) if t in member_set] if not targets: continue data = bath.to_dict() data["targets"] = targets kept.append(type(bath).from_dict(data)) return kept def _split_network(chip: "Chip", groups: list[list[str]]) -> list[Any]: """Restrict the chip's field network to each device group, or explain why that changes the physics.""" network = chip.port_network if network is None: return [None] * len(groups) per_group: list[Any] = [] covered: set[str] = set() for group in groups: members = set(group) labels = [port.label for port in network.ports if set(port.resolve_targets(chip)).issubset(members)] if not labels: per_group.append(None) continue try: restricted = network.restrict(labels) except ValueError as error: raise ValueError(f"{error} Those ports belong to different device groups") from error covered.update(component.label for component in restricted.components) per_group.append(restricted) leftover = sorted({component.label for component in network.components} - covered) if leftover: raise ValueError( f"PortNetwork components {leftover} are not reachable from one device group's ports; " "restricting the field network would change the SLH dynamics" ) return per_group def _transform_drive_labels(transform: "SignalTransform") -> tuple[str, ...]: """Drive labels a signal transform references.""" return transform.referenced_lines() def _distribute_control_lines( clone: "Chip", groups: list[list[str]], owner: dict[str, int] ) -> tuple[list[list[Any]], list[str]]: """Bucket each control line by its target device's owning group; note lines with no target.""" lines_per: list[list[Any]] = [[] for _ in groups] notes: list[str] = [] equipment = clone.control_equipment if equipment is None: return lines_per, notes for line in equipment.lines: target_labels = _line_device_labels(clone, line) if not target_labels: notes.append(f"control line '{line.label}' has no target; dropped from the partition") continue lines_per[owner[target_labels[0]]].append(line) return lines_per, notes def _build_component_chip( chip: "Chip", clone: "Chip", index: int, group: list[str], lines: list[Any], network: Any ) -> "Chip": """Assemble one component's solve-ready sub-chip: its devices, internal couplings, baths, frame, equipment.""" from quchip.chip.chip import Chip as _Chip from quchip.control.equipment import ControlEquipment from quchip.utils.values import copy_value member_set = set(group) devices = [clone[label] for label in group] couplings = [ c for c in clone.couplings if c.device_a_label in member_set and c.device_b_label in member_set ] frame: Any = ( {k: v for k, v in clone.frame.items() if k in member_set} if isinstance(clone.frame, dict) else clone.frame ) sub = _Chip( devices=devices, couplings=couplings or None, label=f"{chip.label}[{index}]" if chip.label else None, frame=copy_value(frame), approximation=clone.approximation, basis=clone.basis, backend=clone._backend, baths=_component_baths(clone, member_set) or None, port_network=network, effective_terms=[terms for terms in clone.effective_terms if set(terms.labels) <= member_set], space=clone.space, ) equipment = clone.control_equipment if equipment is not None and lines: line_labels = {ln.label for ln in lines} chain = [ t for t in equipment.signal_chain if all(lbl in line_labels for lbl in _transform_drive_labels(t)) ] sub.connect(ControlEquipment(lines=lines, signal_chain=chain).copy(sub.device_map, sub.coupling_map)) from quchip.chip.states import copy_state_configuration copy_state_configuration(chip, sub) return sub
[docs] def partition_chip( chip: "Chip", *, resolved: Any | None = None, extra_supports: tuple[tuple[str, ...], ...] = (), clone_trivial: bool = True, ) -> PartitionResult: """Split a chip into independent sub-chips along its independence graph. Exact: the joint solve of the original chip factorizes as the tensor product of the component solves. Public results contain independent models even with one component. """ # Internal dispatch can reuse a trivial component when it will immediately # return to the joint solve. labels = [d.label for d in chip.devices] chip_order = tuple(labels) resolved = chip.resolve() if resolved is None else resolved edges = independence_edges(chip, resolved) for support in extra_supports: edges.extend((support[0], other) for other in support[1:]) groups = connected_components(labels, edges) if len(groups) == 1: return PartitionResult( components=(PartitionComponent(labels=chip_order, chip=chip.clone() if clone_trivial else chip),), chip_order=chip_order, ) try: networks = _split_network(chip, groups) except ValueError as error: return PartitionResult( components=(PartitionComponent(labels=chip_order, chip=chip.clone() if clone_trivial else chip),), chip_order=chip_order, notes=(f"Kept a joint solve because {error}.",), ) clone = chip.clone() owner: dict[str, int] = {} for i, group in enumerate(groups): for label in group: owner[label] = i lines_per, notes = _distribute_control_lines(clone, groups, owner) components = tuple( PartitionComponent( labels=tuple(group), chip=_build_component_chip(chip, clone, i, group, lines_per[i], networks[i]) ) for i, group in enumerate(groups) ) return PartitionResult(components=components, chip_order=chip_order, notes=tuple(notes))
[docs] @dataclass(frozen=True) class LocalEop: """Where one user e_ops key landed: component, component-dict key, optional list index.""" component: int key: Any index: int | None = None
[docs] @dataclass(frozen=True) class CrossEop: """A cross-component correlator: its two local factors, multiplied at recombine time.""" a: LocalEop b: LocalEop
[docs] def split_drive_ops(part: PartitionResult, chip: "Chip", drive_ops: list) -> list[list]: """Route drive ops to their owning component (edge targets through an endpoint).""" per: list[list] = [[] for _ in part.components] for op in drive_ops: label = op.target_label if label in chip.coupling_map: label = chip.coupling_map[label].device_a_label per[part.owner_of(label)].append(op) return per
@dataclass class _LabelGroup: """Local observables and correlator factors sharing one (component, label) slot. ``local`` is the plain (non-cross) entry for this slot, if any, as ``(resolved_key, value)``. ``factors`` are cross-component correlator halves that also land on this slot, each as ``(resolved_key, side, op)`` with ``side`` one of ``"a"``/``"b"``. """ local: tuple[Any, Any] | None = None factors: list[tuple[Any, str, Any]] = field(default_factory=list)
[docs] def split_e_ops(part: PartitionResult, e_ops: dict | None) -> tuple[list[dict], dict]: """Group local observables and cross-component factors before assigning indices.""" per: list[dict] = [dict() for _ in part.components] plan: dict[Any, LocalEop | CrossEop] = {} if not e_ops: return per, plan groups: dict[tuple[int, str], _LabelGroup] = {} cross_owners: dict[tuple[str, str], tuple[int, int]] = {} seen: dict[Any, Any] = {} def group(component: int, label: str) -> _LabelGroup: return groups.setdefault((component, label), _LabelGroup()) for key, value in e_ops.items(): if isinstance(key, tuple): label_a, label_b = (resolve_label(k) for k in key) resolved: Any = (label_a, label_b) else: label_a = label_b = resolve_label(key) resolved = label_a if resolved in seen: raise ValueError( f"e_ops keys {seen[resolved]!r} and {key!r} both resolve to {resolved!r}; " "pass one merged entry instead of two keys for the same target." ) seen[resolved] = key if not isinstance(key, tuple): group(part.owner_of(label_a), label_a).local = (resolved, value) continue comp_a, comp_b = part.owner_of(label_a), part.owner_of(label_b) if comp_a == comp_b: per[comp_a][resolved] = value plan[resolved] = LocalEop(component=comp_a, key=resolved) continue if not isinstance(value, (tuple, list)) or len(value) != 2: raise ValueError( f"Cross-component e_ops key {key!r} needs a 2-element (op_a, op_b) value; " f"got {value!r}" ) op_a, op_b = value group(comp_a, label_a).factors.append((resolved, "a", op_a)) group(comp_b, label_b).factors.append((resolved, "b", op_b)) cross_owners[resolved] = (comp_a, comp_b) factor_index: dict[tuple[Any, str], int | None] = {} for (component, label), entries in groups.items(): local, factors = entries.local, entries.factors if not factors: assert local is not None resolved, value = local per[component][label] = value plan[resolved] = LocalEop(component=component, key=label) continue if local is None and len(factors) == 1: key, side, op = factors[0] per[component][label] = op factor_index[(key, side)] = None continue values = [] if local is not None: resolved, value = local values = list(value) if isinstance(value, list) else [value] plan[resolved] = LocalEop( component=component, key=label, index=None if isinstance(value, list) else 0, ) for key, side, op in factors: factor_index[(key, side)] = len(values) values.append(op) per[component][label] = values for (label_a, label_b), (comp_a, comp_b) in cross_owners.items(): key = (label_a, label_b) plan[key] = CrossEop( a=LocalEop(component=comp_a, key=label_a, index=factor_index[(key, "a")]), b=LocalEop(component=comp_b, key=label_b, index=factor_index[(key, "b")]), ) return per, plan
[docs] def split_state_mapping(part: PartitionResult, mapping: Any) -> list[dict]: """Distribute a device-keyed state mapping over components.""" per: list[dict] = [dict() for _ in part.components] for key, value in mapping.items(): label = resolve_label(key) per[part.owner_of(label)][label] = value return per