quchip.engine.stage3_observables

Stage 3: band-decompose dict-form e_ops and demodulate expectations post-solve.

The simulation is performed in the rotating frame chosen by stage 1; user-facing observables, however, live in the control frame (where matrix elements are labeled by detunings Δ = ω_drive ω_frame). This stage performs the two operations that tie those frames together:

  • Pre-solve (:func:`decompose_eops`): each user operator is split into excitation-change bands w = col row (single-mode) or (Δa, Δb) (two-mode) via quchip.engine.bands. Each band is embedded into the full product Hilbert space and carries BandMeta so the post-solve step knows which phase to apply.

  • Post-solve (:func:`recombine_expect`): each band expectation ⟨O_w⟩(t) is multiplied by exp(±i · · ω_demod · w · t) (the sign selects demodulation vs. remodulation) and summed over bands. Physically this is the inverse rotating-frame transformation applied to each band — equivalent to moving the observable from the simulation frame to the control frame.

Stage 3 reads per-device demodulation frequencies from ResolvedFrame and forms the demodulation phase exp(i · · ω · w · t). This is the inverse of the rotating-frame shift that stage 2 applied to the Hamiltonian.

Functions

build_observable_traces(solver_result, ...)

Wrap solver expectations into phase-corrected ObservableTrace objects.

decompose_eops(e_ops_dict, chip, backend)

Flatten dict-form e_ops into (ops, meta) ready for the solver.

recombine_expect(flat_expect, meta_list, ...)

Recombine flattened expectations into (band_sum, phase_corrected) dicts.

Classes

BandMeta(key, weight, device_labels[, sub_index])

Post-solve metadata for one flattened e_ops band.

class quchip.engine.stage3_observables.BandMeta(key, weight, device_labels, sub_index=None)[source]

Bases: object

Post-solve metadata for one flattened e_ops band.

key is the original dict key, weight is the excitation-change weight (int single-device, tuple[int, int] two-device), device_labels drives phase lookup, and sub_index distinguishes entries when the user passed a list of operators for the same key.

Parameters:
key: str | tuple[str, str]
weight: int | tuple[int, int]
device_labels: str | tuple[str, str]
sub_index: int | None = None
quchip.engine.stage3_observables.decompose_eops(e_ops_dict, chip, backend)[source]

Flatten dict-form e_ops into (ops, meta) ready for the solver.

Supported key shapes:

  • "device_label" (or a device object resolved to its label) with a single local operator — the operator is split into single-mode bands on that device.

  • ("label_a", "label_b") with a tuple (op_a, op_b) — each operator is split into single-mode bands on its device and all band pairs are tensored, producing two-body (w_a, w_b) bands.

For each band, the returned BandMeta records the original key, the weight, and the sub-index (distinguishing entries when the user passed a list of operators for the same key). The matching ops list is ready for direct consumption by the backend solver.

Parameters:
Return type:

tuple[list[Any], list[BandMeta]]

quchip.engine.stage3_observables.recombine_expect(flat_expect, meta_list, tlist, frame_freqs, *, direction='demodulate')[source]

Recombine flattened expectations into (band_sum, phase_corrected) dicts.

For each band with weight w on device(s) with demodulation frequency ω_demod, the correction factor is

\[\exp\!\big(\pm i \cdot 2\pi \cdot \omega_{\text{demod}} \cdot w \cdot t \big),\]

with the sign selected by direction: "demodulate" (+1) moves the expectation from the simulation’s rotating frame back to the control frame; "remodulate" (−1) does the reverse.

Returns two dicts: band_sum is the raw per-key sum over bands (no phase correction) and phase_corrected is the physically meaningful expectation in the control frame.

Parameters:
Return type:

tuple[dict[str | tuple[str, str], Any], dict[str | tuple[str, str], Any]]

quchip.engine.stage3_observables.build_observable_traces(solver_result, tlist, chip, *, dict_meta, resolved_frame)[source]

Wrap solver expectations into phase-corrected ObservableTrace objects.

Pulls the flat per-band expectations out of solver_result, calls recombine_expect() with the demodulation frequencies from resolved_frame, and returns a dict keyed by the user’s original e_ops keys. Each value is an ObservableTrace (or a list when the user supplied multiple operators for the same key).

Parameters:
Return type:

dict[str | tuple[str, str], ObservableTrace | list[ObservableTrace]]