Visualization helpers and optional third-party interop (pyvis, scqubits,
matplotlib-based plots, …) are loaded lazily through the module-level
__getattr__() so that importquchip stays fast and does not
force any optional dependency on the core install.
Subclasses declare physics parameters via parameter() and
implement interaction() (returning a
PhysicsExpr over the two endpoint
operators). The RWA is applied structurally by the chip and engine
via rwa_keeps_band();
only the parametric RWA structures remain author-declared, because
pump sideband selection depends on frequency intent rather than
operator structure: which sideband a pump activates (red: Δa+Δb=0
exchange; blue: |Δa+Δb|=2 two-photon) depends on where the pump
frequency sits relative to traced device frequencies, and a structural
rule cannot infer it without branching on traced values.
Optional overrides:
time_dependent() — parametric (time-dependent) modulation, as
a PhysicsExpr carrying a single dynamic source.
coupling_strength defaults to the first declared parameter
field (suited for the common case of one g-like scalar). Override
the property in subclasses with a different convention.
Note
Coupling instances are not registered as JAX pytrees and cannot be
passed as dynamic jax.jit / jax.vmap / jax.grad
arguments. Coupling parameters remain differentiable when the
coupling (and the devices or chip it couples) is constructed from
traced arguments inside the transformed function.
a (EndpointOps) – Operator namespaces for the two coupled endpoints. Same-endpoint
operators compose with @; cross-endpoint operators combine
with * (tensor product).
b (EndpointOps) – Operator namespaces for the two coupled endpoints. Same-endpoint
operators compose with @; cross-endpoint operators combine
with * (tensor product).
Returns:
The interaction Hamiltonian expression, in ordinary-frequency
units (GHz).
Compile the parametric structure for chip’s resolved RWA policy.
Returns the backend-native operator on the local two-body space, or
None when parametric_interaction() declines. Valid only once
the coupling is chip-resolved (same contract as
interaction_hamiltonian()).
Subclasses declare their parameters as annotated class attributes using
parameter() (e.g. freq:Scalar=parameter(positive=True)) and
implement local_hamiltonian(). The declared parameters become
positional-or-keyword __init__ arguments and JAX pytree leaves so the
full instance is traceable / differentiable / sweepable end-to-end.
The hamiltonian() adapter compiles the declarative expression
returned by local_hamiltonian() into an operator for the active
default backend.
Declared approximation-regime statement surfaced by
physics_notes() — the mechanism that keeps a model’s stated
validity range attached to the class rather than buried in a
docstring a caller may not read.
Cross-field validation hook, run at the end of construction.
Default is a no-op. Subclasses override to enforce constraints that
span multiple declared parameters (e.g. 2*edge<=duration).
Checks must be gated on concrete scalars via
quchip.utils.jax_utils.maybe_concrete_scalar() so traced
parameters never force concretization.
Subclasses declare their parameters as annotated class attributes via
parameter() (e.g. duration:Scalar=parameter(positive=True))
and implement value(), which returns the complex envelope at a
given time. The waveform() method is supplied by this base and
delegates to value(), keeping the signal pipeline JAX-traceable
end-to-end.
Cross-field validation hook, run at the end of construction.
Default is a no-op. Subclasses override to enforce constraints that
span multiple declared parameters (e.g. 2*edge_duration<=duration). Checks must be gated on concrete scalars via
quchip.utils.jax_utils.maybe_concrete_scalar() so traced
parameters never force concretization.
The metadata is intentionally lightweight: it records validation and
serialization intent while leaving the runtime value fully traceable.
Sign constraints (positive / nonnegative) are enforced only on
concrete scalars, so traced values flow through unchecked.
Declare a traceable scalar or modulation parameter on a model class.
unit is display metadata for human-readable surfaces such as
Chip.describe() — the package-wide units contract (GHz, ns, mK)
still governs the value itself. None means dimensionless or unknown.
Returns a Parameter field descriptor that parameter_fields()
collects at class-definition time.
Parameters:
default (Any, optional) – Declared default value. When omitted the field is required in the
synthesized __init__.
The T1 collapse-operator normalization assumes T1 is set by
charge-operator-coupled relaxation (Breuer & Petruccione §3.4).
For phonon / quasiparticle / flux-dominated T1, override
collapse_operators() or pass collapse_model='ladder'.
Bare parameters this device exposes as differentiable / tunable
scalars. fit_a_dress walks this tuple to discover what it is
allowed to optimize on each device, decoupling the inverse-design
surface from any specific device model. Three states, keyed on
whether the value is explicitly declared:
No explicit declaration anywhere in theDeviceModellineage — the
default is derived: every declared
parameter() field, in
declaration order (see DeviceModel.__init_subclass__).
Explicit tuple on the class or an ancestor — exact curation,
validated at class-definition time; authoritative and inherited
until a subclass explicitly replaces it.
Explicit empty tuple — deliberately freezes the device (and its
subclasses, until one replaces it) out of inverse design.
On a plain (non-DeviceModel) BaseDevice subclass there is
no derivation; the default stays empty unless the subclass declares
its own tuple — e.g. Fluxonium uses
("E_C","E_J","E_L","phi_ext").
Declared approximation-regime statement surfaced by
physics_notes(), mirroring
approximation — this
class does not inherit from DeviceModel, so the attribute and its
surfacing are declared here directly.
Construct from (freq, anharmonicity) using the Koch-regime inversion.
Uses \(E_C = -\alpha\) and
\(E_J = (\omega + E_C)^2 / (8 E_C)\). Residual between the
Duffing approximation and the exact diagonalized spectrum is
typically <1% for \(E_J/E_C > 50\), growing below that.
A concrete-scalar warning fires at \(E_J/E_C < 20\).
On the registry root, dispatch to the concrete subclass named by
data["type"] (forwarding *args / **kwargs). On a concrete
subclass, defer to _from_dict_payload(). Concrete subclasses
that carry payload override this method directly.
Abstract base for devices built by diagonalizing a native-basis circuit.
Conforms to the ChargeCoupled and
PhaseCoupled Protocols by default;
subclasses may additionally conform to FluxCoupled by
defining flux_coupling_operator().
Declared approximation-regime statement surfaced by
physics_notes(), mirroring
approximation — this
class does not inherit from DeviceModel, so the attribute and its
surfacing are declared here directly.
Diagonal diag(0,E_01,E_02,…) in the truncated eigenbasis.
Returned as a backend-native diagonal operator — the engine relies
on chip.hamiltonian() producing the same sparse layout as
number_operator() so layout-aware backends (e.g. dynamiqs
sparse-DIA) do not silently densify when assembling H₀.
Return the physical phase-coupling operator in the eigenbasis.
Returns \(V^\dagger \hat\varphi V\) on a phase-basis device
(fluxonium); returns \(V^\dagger \sin\hat\varphi V\) on an
integer-charge-basis device (charge-basis transmon), since
\(\hat\varphi\) is not single-valued there.
Transmon modelled as a weakly anharmonic Duffing oscillator.
Parameters:
freq (float) – Bare 0->1 transition frequency ω in GHz. Must be positive.
May be a JAX tracer for sweeps / gradients.
anharmonicity (float) – Anharmonicity α in GHz. Typically negative for superconducting
transmons (e.g. -0.25 GHz). May be a JAX tracer.
levels (int, default 3) – Fock-space truncation. Three levels suffice for leakage-aware
single-qubit modelling; increase for higher-level physics
(e.g. iSWAP-family gates via the |02>-|11> crossing).
label (str | None, default None) – If omitted, auto-generated as duffing_{idx} via the shared
labeling counter.
**noise_kwargs – Forwarded to BaseDevice — T1, T2,
thermal_population.
Bare parameters this device exposes as differentiable / tunable
scalars. fit_a_dress walks this tuple to discover what it is
allowed to optimize on each device, decoupling the inverse-design
surface from any specific device model. Three states, keyed on
whether the value is explicitly declared:
No explicit declaration anywhere in theDeviceModellineage — the
default is derived: every declared
parameter() field, in
declaration order (see DeviceModel.__init_subclass__).
Explicit tuple on the class or an ancestor — exact curation,
validated at class-definition time; authoritative and inherited
until a subclass explicitly replaces it.
Explicit empty tuple — deliberately freezes the device (and its
subclasses, until one replaces it) out of inverse design.
On a plain (non-DeviceModel) BaseDevice subclass there is
no derivation; the default stays empty unless the subclass declares
its own tuple — e.g. Fluxonium uses
("E_C","E_J","E_L","phi_ext").
Declared approximation-regime statement surfaced by
physics_notes() — the mechanism that keeps a model’s stated
validity range attached to the class rather than buried in a
docstring a caller may not read.
The constructor takes the calibrated local physical parameters; SQUID
metadata is derived on read and is not part of the public interface.
Parameters:
freq (float) – Calibrated local 0->1 transition frequency ω in GHz, at the
stored flux_bias. Must be positive. May be a JAX tracer.
anharmonicity (float) – Calibrated local anharmonicity α in GHz, at the stored
flux_bias. Must be negative (α ≈ −E_C). May be a JAX tracer.
flux_bias (float, default 0.0) – Calibration-anchor operating point Φ/Φ₀. Any real value; the SQUID
inversion is undefined only at the symmetric-SQUID degenerate point
(asymmetry==0 and flux_bias a half-integer — see
validate()). The local Hamiltonian does not reference this
value directly — freq and anharmonicity already carry it. A
pytree leaf, so it is differentiable / sweepable like every other
device parameter.
asymmetry (float, default 0.0) – SQUID junction asymmetry d = (E_{J1}−E_{J2})/(E_{J1}+E_{J2}).
Must be in [0, 1).
Bare parameters this device exposes as differentiable / tunable
scalars. fit_a_dress walks this tuple to discover what it is
allowed to optimize on each device, decoupling the inverse-design
surface from any specific device model. Three states, keyed on
whether the value is explicitly declared:
No explicit declaration anywhere in theDeviceModellineage — the
default is derived: every declared
parameter() field, in
declaration order (see DeviceModel.__init_subclass__).
Explicit tuple on the class or an ancestor — exact curation,
validated at class-definition time; authoritative and inherited
until a subclass explicitly replaces it.
Explicit empty tuple — deliberately freezes the device (and its
subclasses, until one replaces it) out of inverse design.
On a plain (non-DeviceModel) BaseDevice subclass there is
no derivation; the default stays empty unless the subclass declares
its own tuple — e.g. Fluxonium uses
("E_C","E_J","E_L","phi_ext").
Declared approximation-regime statement surfaced by
physics_notes() — the mechanism that keeps a model’s stated
validity range attached to the class rather than buried in a
docstring a caller may not read.
ValueError – If target_freq is concrete and lands outside the frequency
range frequency_at() reaches over Φ/Φ₀ ∈ [0, 0.5) at the
current calibration anchor. A traced target_freq (or a traced
anchor) skips this check; the returned flux clips to the lobe
endpoint, so out-of-domain behavior is undefined for traced
inputs.
collapse_model (see) – CircuitDevice. coupling_channel is required when
collapse_model='fermi_golden' (the default) with T1 set
— pick 'flux' at or near the flux sweet spot
(phi_ext=0.5), where relaxation is flux-noise-dominated,
and 'charge' for charge-operator-limited T1 regimes.
coupling_channel (see) – CircuitDevice. coupling_channel is required when
collapse_model='fermi_golden' (the default) with T1 set
— pick 'flux' at or near the flux sweet spot
(phi_ext=0.5), where relaxation is flux-noise-dominated,
and 'charge' for charge-operator-limited T1 regimes.
collapse_rate_threshold (see) – CircuitDevice. coupling_channel is required when
collapse_model='fermi_golden' (the default) with T1 set
— pick 'flux' at or near the flux sweet spot
(phi_ext=0.5), where relaxation is flux-noise-dominated,
and 'charge' for charge-operator-limited T1 regimes.
**noise_kwargs – Forwarded to BaseDevice.
Notes
The T1 collapse-operator model depends on coupling_channel:
'charge' uses \(\hat n\) matrix elements (Breuer-Petruccione
§3.4, Smith 2020 §III.B); 'flux' uses \(\hat\varphi\) matrix
elements (proportional to \(\partial H/\partial\varphi_\mathrm{ext}\)
since only the \(\hat\varphi\) term is operator-valued there).
Inherited pure dephasing uses level-index scaling — physically
incomplete for fluxonium away from sweet spot, where
flux-noise-weighted dephasing is the physical channel. Sweet-spot
accurate dephasing is a follow-up PR.
Bare parameters this device exposes as differentiable / tunable
scalars. fit_a_dress walks this tuple to discover what it is
allowed to optimize on each device, decoupling the inverse-design
surface from any specific device model. Three states, keyed on
whether the value is explicitly declared:
No explicit declaration anywhere in theDeviceModellineage — the
default is derived: every declared
parameter() field, in
declaration order (see DeviceModel.__init_subclass__).
Explicit tuple on the class or an ancestor — exact curation,
validated at class-definition time; authoritative and inherited
until a subclass explicitly replaces it.
Explicit empty tuple — deliberately freezes the device (and its
subclasses, until one replaces it) out of inverse design.
On a plain (non-DeviceModel) BaseDevice subclass there is
no derivation; the default stays empty unless the subclass declares
its own tuple — e.g. Fluxonium uses
("E_C","E_J","E_L","phi_ext").
Declared approximation-regime statement surfaced by
physics_notes(), mirroring
approximation — this
class does not inherit from DeviceModel, so the attribute and its
surfacing are declared here directly.
On the registry root, dispatch to the concrete subclass named by
data["type"] (forwarding *args / **kwargs). On a concrete
subclass, defer to _from_dict_payload(). Concrete subclasses
that carry payload override this method directly.
The nonlinearity \(K\) shifts the photon-number eigenenergies,
making the cavity anharmonic. Combined with a two-photon parametric
drive at \(2\omega\), the steady state becomes a cat state with
amplitude \(\alpha = \sqrt{\varepsilon_2 / K}\).
Parameters:
freq (float) – Cavity frequency \(\omega\) in GHz. Must be positive.
May be a JAX tracer for sweeps / gradients.
kerr (float) – Kerr nonlinearity \(K\) in GHz. Non-negative; positive
value shifts even-photon levels downward. Typically 1–100 MHz
in superconducting circuits.
levels (int) – Fock-space truncation dimension. Choose at least
4*(eps2/K)+10 to avoid truncation artefacts.
Default 30.
**noise_kwargs – Forwarded to BaseDevice:
T1, T2, thermal_population, etc.
Notes
This Hamiltonian is diagonal in the Fock basis and does not itself
define a computational subspace. Combined with a two-photon parametric
drive, the steady state can be engineered into a cat-code manifold
spanned by the even cat state \(|C^+_\alpha\rangle\) and the odd
cat state \(|C^-_\alpha\rangle\). Bit-flip errors within that
manifold are exponentially suppressed, \(\sim e^{-2|\alpha|^2}\),
in the stabilized regime. This class’s inherited Pauli surface
(computational is False) addresses the bare Fock |0>,
|1> subspace; see physics_notes() for the caveat.
Bare parameters this device exposes as differentiable / tunable
scalars. fit_a_dress walks this tuple to discover what it is
allowed to optimize on each device, decoupling the inverse-design
surface from any specific device model. Three states, keyed on
whether the value is explicitly declared:
No explicit declaration anywhere in theDeviceModellineage — the
default is derived: every declared
parameter() field, in
declaration order (see DeviceModel.__init_subclass__).
Explicit tuple on the class or an ancestor — exact curation,
validated at class-definition time; authoritative and inherited
until a subclass explicitly replaces it.
Explicit empty tuple — deliberately freezes the device (and its
subclasses, until one replaces it) out of inverse design.
On a plain (non-DeviceModel) BaseDevice subclass there is
no derivation; the default stays empty unless the subclass declares
its own tuple — e.g. Fluxonium uses
("E_C","E_J","E_L","phi_ext").
Declared approximation-regime statement surfaced by
physics_notes() — the mechanism that keeps a model’s stated
validity range attached to the class rather than buried in a
docstring a caller may not read.
Declarative spec for one family of Lindblad collapse channels.
A device class composes its dissipation from a tuple of these in
_noise_channels; BaseDevice.collapse_operators() concatenates
each channel’s build(device) in declaration order. Adding a noise
type to a device is therefore one declaration — a parameter field plus
a channel entry — with no method override and no change outside the
device’s own class.
Parameters:
name (str) – Human-readable channel-family name (diagnostics only).
params (tuple[str, ...]) – Device attribute names this channel consumes — declarative
metadata; the build reads the attributes directly.
build (callable) – build(device)->list[Operator]: the channel’s collapse
operators, empty when its parameters are unset.
Linear microwave / photonic resonator — pure harmonic oscillator.
Parameters:
freq (float) – Bare cavity frequency ω in GHz. Must be positive. May be a JAX
tracer for sweeps / gradients.
quality_factor (float | None, optional) – Loaded Q, defined against the ordinary frequency freq
(GHz). When set, adds a photon-loss Lindblad channel
sqrt(2*pi*freq/Q)a — i.e. decay rate
kappa=2*pi*freq/Q (angular, rad/ns). The 2*pi here is
part of the physical definition of Q (energy e-folds per
ordinary cycle divided by Q), not a units-boundary conversion.
Must be positive. Like every noise parameter it may be set — or
cleared with None — after construction; the next simulate
reflects the current value.
levels (int, default 10) – Fock-space truncation. Choose comfortably above the maximum
expected photon occupation.
label (str | None, default None) – If omitted, auto-generated as resonator_{idx} via the shared
labeling counter.
**noise_kwargs – Forwarded verbatim to BaseDevice — T1, T2,
thermal_population.
Bare parameters this device exposes as differentiable / tunable
scalars. fit_a_dress walks this tuple to discover what it is
allowed to optimize on each device, decoupling the inverse-design
surface from any specific device model. Three states, keyed on
whether the value is explicitly declared:
No explicit declaration anywhere in theDeviceModellineage — the
default is derived: every declared
parameter() field, in
declaration order (see DeviceModel.__init_subclass__).
Explicit tuple on the class or an ancestor — exact curation,
validated at class-definition time; authoritative and inherited
until a subclass explicitly replaces it.
Explicit empty tuple — deliberately freezes the device (and its
subclasses, until one replaces it) out of inverse design.
On a plain (non-DeviceModel) BaseDevice subclass there is
no derivation; the default stays empty unless the subclass declares
its own tuple — e.g. Fluxonium uses
("E_C","E_J","E_L","phi_ext").
Declared approximation-regime statement surfaced by
physics_notes() — the mechanism that keeps a model’s stated
validity range attached to the class rather than buried in a
docstring a caller may not read.
Combined lowering-channel rate: κ=2π·freq/Q photon loss plus the thermal-emission rate.
Both quality_factor and T1/thermal_population build
independent lowering-operator collapse channels on this device (the
photon_lossNoiseChannel, a pure
loss channel unaffected by thermal_population, and the inherited
thermal-emission channel — see
intrinsic_decay_rate() for its
(n̄+1)/T1 / n̄+1 formulas); this hook reports their summed
rate rather than either alone, so a caller reading one scalar decay
rate (e.g. an adiabatic-elimination Purcell fold) does not
under-count decay when both are set. None only when neither is
set.
Device exposes the physical charge operator in its truncated eigenbasis.
ChargeDrive dispatches against this
Protocol and emits drives using charge_coupling_operator()
rather than the structural 1j*(a-a†) from
BaseDevice.lowering_operator().
Device exposes the physical phase-space coupling operator.
Returns \(V^\dagger \sin\hat\varphi V\) on a charge-basis transmon
(where \(\hat\varphi\) is not single-valued in the integer charge
basis) or \(V^\dagger \hat\varphi V\) on a fluxonium (where
\(\hat\varphi\) is well-defined). Used by
PhaseDrive.
Device exposes the physical flux-line coupling operator.
For a fluxonium this is \(V^\dagger \hat\varphi V\); for a
flux-tunable transmon (future follow-up) it will be a flux-modulated
term. Used by FluxDrive.
Methods that accept a device reference take either a device object
or its label string; object references are preferred in examples.
Parameters:
devices (list[BaseDevice]) – Ordered device list. Tensor-product position equals list index;
labels must be unique.
couplings (list[BaseCoupling], optional) – Two-body couplings. Each coupling’s referenced devices must be
in devices.
control_equipment (ControlEquipment, optional) – Aggregates drive lines and signal-chain transforms (crosstalk,
delays, gains). Can also be attached later via wire() or
connect().
Embeds every device Hamiltonian into the full tensor space and
adds each coupling’s embedded interaction. Does not apply
the rotating-frame transform or any drive envelopes.
Under resolved RWA (resolve_rwa()) each coupling
contributes only the bands its
rwa_keeps_band()
retains — the apply_rwa_mask() of its full
interaction. The same bands drop from stage 2’s decomposition, so
the two views agree.
This is the lab-frame Hamiltonian. At solve time, the engine
applies a rotating-frame transformation and subtracts
2πΣ_iω_ref,in̂_i for each device, where ω_ref,i is the
frame reference resolved by
quchip.engine.stage1_frames.resolve_frame(). The 2π boundary
crossing and the subtraction both live in
quchip.engine.stage2_assembly._build_static_h0(). Use
frame_info() to inspect which reference frequency each
device will use.
Chip-owned time-dependent Hamiltonian contributions with their support.
Returns (local_op,time_dependence,support,origin,tag)
tuples: one support index for a device-local operator, two for a
coupling’s two-body operator. Operators are lab-frame, ordinary
GHz — the engine applies the 2π boundary. Drive terms are not
included; they are schedule-owned and enter through stage 2’s
drive compilation.
Every Lindblad collapse operator on the chip, with its support.
Returns (operator,support) pairs: one device index for a
device- or drive-line-local operator, two for a coupling’s
two-body operator, and an empty tuple for an operator already
embedded in the full space (baths). Rates are in 1/ns,
Lindblad-ready — each component owns its rate physics, including
any intrinsic 2π (e.g. a resonator’s κ = 2π·f/Q).
Every returned operator is a component’s LOCAL (bare-basis)
operator; the engine combines them with the dressed interacting
Hamiltonian at solve time. This is the standard local-Lindblad
approximation (Breuer & Petruccione, The Theory of Open Quantum
Systems, Oxford, 2002, Ch. 3) rather than a dressed-basis
(polaron-frame) master equation, and applies chip-wide regardless of
which components carry noise — see the "chip" entry of
physics_notes().
per-call override > chip-specific > process default.
The per-call override is the backend= argument of
simulate() /
simulate_batch (scoped through
quchip.backend._backend_context()); it outranks even a
chip-constructed backend so one chip can serve, e.g., QuTiP sweeps
and dynamiqs gradient solves without global state flips.
Attach a bath to this chip and return it (for fluent use).
Baths may be added at any time after construction — the next
simulate/solve collects the bath’s collapse operators automatically,
no rebuild needed. The bath is validated immediately: a non-Bath
argument, a target label not on this chip, or a label colliding
with an already-attached bath fails here rather than cryptically at
solve time (or, for a label collision, silently overwriting an
entry on the physics_notes() audit surface).
Replace this chip’s entire noise description in one call.
The call is the chip’s complete noise state: for every device, each
noise parameter (see
noise_parameter_names()) is set
from config when given and reset to None otherwise, and the
chip’s baths become exactly baths (omitted → no baths).
chip.set_noise() therefore clears all noise. Re-running the same
call is a silent no-op, so notebook cells converge instead of
stacking duplicate baths.
Only dissipation knobs are reachable — a key that is not a noise
parameter of that device (e.g. freq) raises, so an optimized
Hamiltonian cannot be perturbed by this call. The full target state
is validated before anything is written (the same checks the
constructors run); on error the chip is left exactly as it was.
Applied changes are printed one per line; a no-op prints nothing.
Custom dissipation is supported by declaring it on the device class
beforehand — a parameter(...) rate field plus a
NoiseChannel entry (see the extension
guide). The declared rate is then an ordinary noise parameter here:
sweepable, differentiable, serializable. Runtime closure-style
channels are deliberately not supported — their rates could be
neither swept, nor differentiated, nor serialized.
Parameters:
config (mapping, optional) – {device_or_label:{noise_param:value}}. Devices may appear
as objects or labels, once each.
baths (list[Bath], optional) – The chip’s complete new bath list (validated like
add_bath()).
Dressed-state analysis namespace — the chip’s ChipAnalysis.
Canonical entry point for the full dressed-analysis surface
(power users, less-common methods). The common quantities are
also exposed as flat chip.* forwarders — energy(),
freq(), dress(), dispersive_shift(), … — which
delegate here; reach for chip.analysis for everything else.
Return <final~|D_j|initial~> for wired drive lines.
The final dressed state is the row index and the initial dressed
state is the column index. In the weak-drive projection these matrix
elements set the effective driven-Hamiltonian coefficients. See
E. Magesan and J. M. Gambetta, Phys. Rev. A 101, 052308 (2020),
DOI 10.1103/PhysRevA.101.052308, and
ChipAnalysis.drive_matrix_elements() for the transition
shorthand, parameters, return type, and error conditions.
All dressed 0→1 frequencies (GHz), or one conditional transition. See ChipAnalysis.freq().
Overloaded: no target returns the full {label:freq} dict;
a single target (label or device) returns one scalar 0→1
frequency. The runtime body is unchanged — under jax.jit the
scalar is a traced 0-d array, so the overload is type-only and
does not alter traceability.
Returns a dict keyed "chip" for the chip-level entry, and
"<kind>:<label>" — kind one of "device", "coupling",
"drive", "bath" — for every component, mapping to that
component’s declared approximations: Hilbert truncation, model
regime, RWA status, noise-channel selection, and any other
non-obvious assumption the component explicitly declares. Keys are
kind-qualified rather than bare labels because the label namespaces
are not globally disjoint — a device, coupling, drive, and bath may
share a label — and this is an audit surface, so one component’s
entry silently overwriting another’s is unacceptable. Drives are
enumerated from control_equipment’s wiring rather than
per-device connected_drives, so an edge-target
ParametricDrive (pumping a coupling,
not a device) is included too. The chip-level entry keyed "chip"
states the local-Lindblad approximation every collapse operator this
chip assembles is built under (see collapse_contributions()) —
present even with no baths, since it applies regardless. Intended for
inspection/audit rather than for runtime dispatch.
Split into independent sub-chips along the independence graph.
Couplings, non-separable bath target sets, and drive-crosstalk pairs
all count as connections. Exact — the joint solve factorizes as the
tensor product of the component solves. simulate/seq.simulate
consult this automatically; call it directly to orchestrate solves
yourself.
With device, the array is interpreted as a local operator on
that device’s subspace and embedded into the full tensor-product
space. With device=None the array must already span the full
chip Hilbert space.
Embed a device operator onto the full chip Hilbert space.
Accepts either an operator name ("X", "Y", "Z",
"n", "a", "a_dag", "I") or an already-built
local-space operator, and returns it embedded on the chip’s
tensor-product space.
This is for manual full-space operator construction and
analysis; it is not a solver e_op. For solver expectation
values use e_ops(), which keeps operators local so the
demodulation pipeline can band-decompose and embed them
correctly.
Build a dict-form e_ops mapping for the solver pipeline.
Each keyword maps a device label to an operator specification:
a name string, a list of names, a raw local-space operator, or
a mixed list of strings and operators. Two-device correlators
(e.g. ⟨Z₁⊗Z₂⟩) are specified via correlators as
device-label pairs → operator pairs. Returns local-space
operators (not embedded) — the demodulation pipeline embeds as
needed.
Declare the device order used to parse string-state shorthands.
After this is called, bare_state(), state(), and
superposition() accept single-string specifications where
each character is one level per device in devices order.
Level symbols default to g=0,e=1,f=2,h=3; digits 0..9
are always accepted as raw Fock indices.
Normalized bare-basis superposition of tensor-product states.
Each component is either a bare-state spec (dict keyed by device
or label, or a string when set_state_order() has been
called) or an (amplitude,spec) tuple for weighted mixing.
Uniform weights by default; results are normalized to unit norm.
Unlike state(), this stays in the bare product basis — no
dressed diagonalization — so the probe basis is explicit.
Dressed eigenstate for the given Fock-indexed bare-state labels.
Accepts a string shorthand (e.g. "eg1") when
set_state_order() has been called.
Safe inside jax.jit/grad/vmap: under tracing the
assigned eigenvector column is selected through the
label_eigensystem() array kernel, so
dressed initial states are differentiable end-to-end. The global
phase is gauge-dependent (eigh column convention) —
populations and |overlap| figures of merit are unaffected.
Bare tensor-product state from per-device Fock indices or kets.
Each device may be specified as either a Fock index (int) or
a ket vector in that device’s local space. Devices not mentioned
default to the ground state (Fock index 0). Unlike state()
this does not diagonalize the coupled system.
Accepts a string shorthand (e.g. "eg1") when
set_state_order() has been called.
Preferred user-facing API for attaching control to a chip. If
lines is omitted, the existing connected lines are reused and
only the signal chain is replaced.
Remove one control line and every signal-chain transform referencing it.
The inverse of wire() for a single line. Accepts the drive
object or its label. Returns the removed drive so it can be rewired
later. Removing the last line detaches the equipment entirely
(control_equipment becomes None).
Attach control equipment to this chip (low-level API).
Validates every drive target and rejects duplicate drive labels,
then reconnects each drive to this chip’s canonical device
instances. User-facing code should prefer wire().
Detach control equipment entirely (low-level API).
The inverse of connect()/wire(): removes all lines and
the signal chain at once (control_equipment becomes None).
Returns the detached equipment so it can be reconnected later.
Returns:
The equipment that was attached before detachment.
Solve a typed SolveProblem through this chip’s backend.
Routes through the common solve_problem()
chokepoint, so the Hilbert-truncation safety net applies by default;
pass check_truncation=False to opt out or retune
truncation_threshold.
Solve a SolveBatch, ProblemBatch, or list of problems.
Chip-level validation only enforces what needs self (every input
was built for this chip); the input-shape dispatch and batching are
delegated to quchip.engine.solve_many(), which owns the single
ProblemBatch / SolveBatch / list ladder.
Sectioned plain-text report of everything on the chip.
Devices with their declared parameters (units included), noise
settings, couplings, control wiring, and baths — the “what did I
just build?” view. Returns a string; print(chip.describe()).
Traced parameters render as <traced> and are never
concretized.
Capacitive (charge-charge) coupling between two devices.
The capacitive interaction between two electromagnetic modes takes the
canonical dipole-dipole form in the raising/lowering basis:
Full form: H_int=g·(a+a†)(b+b†)
RWA form: H_int=g·(a†b+ab†) — derived, not authored
The coupling authors only the full form; the RWA form is the engine
retaining the Δa+Δb==0 bands of it, which is exactly
g·(a†b+ab†). The RWA drops the counter-rotating terms ab
and a†b†, valid when ω_a+ω_b≫g — the sum-frequency
condition that makes those terms fast-rotating and hence negligible.
This is distinct from the dispersive condition |ω_a−ω_b|≫g,
which instead governs whether the retained exchange term
g·(a†b+ab†) can be treated perturbatively (see
TunableCapacitive / eliminate()
for the dispersive reduction). Whether to take the RWA is a per-coupling
policy; resolution against the chip default happens in
Chip.resolve_rwa().
Parameters:
device_a (BaseDevice or str) – The two coupled devices, given as objects or label strings.
Label-string references are late-bound via Chip.
device_b (BaseDevice or str) – The two coupled devices, given as objects or label strings.
Label-string references are late-bound via Chip.
g (float) – Coupling strength in GHz. May be a traced JAX scalar for
sweeps / autodiff.
rwa (bool or None) – Per-coupling RWA override. None inherits the chip default.
label (str, optional) – Human-readable label; defaults to "cap_{n}".
References
Blais et al., PRA 69, 062320 (2004), Eq. 11.
Krantz et al., Appl. Phys. Rev. 6, 021318 (2019), §V.B.
Blais et al., Rev. Mod. Phys. 93, 025005 (2021), §II.B.
Examples
>>> fromquchipimportDuffingTransmon,Resonator,Capacitive>>> q=DuffingTransmon(freq=5.0,anharmonicity=-0.25,levels=3,label="q")>>> r=Resonator(freq=7.0,levels=5,label="r")>>> coupling=Capacitive(q,r,g=0.05)# RWA inherited from chip>>> _strong=Capacitive(q,r,g=0.05,rwa=False)# force full quantum-Rabi form
Generic two-body coupling with a user-supplied interaction.
Use this escape hatch when no concrete coupling class models the
desired physics (inductive, longitudinal, cross-Kerr test forms,
synthetic spin-spin couplings, photonics-style beam-splitters, …).
The user supplies the operator structure; this class only provides
the g scaling, RWA pass-through, and bookkeeping.
Two mutually exclusive modes:
Product form — H_int=g·op_a(device_a)⊗op_b(device_b):
The resolved RWA applies the structural band filter to the
user-supplied operator too, keeping only the bands
rwa_keeps_band()
accepts. Supply rwa=False (or override rwa_keeps_band) to
keep a hand-built form untouched.
The effective diagonal interaction left when an exchange coupling is
reduced in the dispersive regime — the natural coupling for effective
readout chips (qubit + resonator + CrossKerr probed by an ordinary
charge line) and static-ZZ modeling. Diagonal in both endpoints, so the
RWA and full forms coincide and the term is frame-trivial.
Declared approximation: this is a uniform-pull model —
one χ per edge, the same shift per endpoint excitation; per-level χ
differences, dispersive breakdown at the critical photon number, and
Purcell decay are not represented (fold Purcell into endpoint T1
via eliminate() when it matters).
Parameters:
device_a (BaseDevice or str) – The two coupled devices, as objects or label strings.
device_b (BaseDevice or str) – The two coupled devices, as objects or label strings.
chi (float) – Cross-Kerr shift in GHz per excitation pair, sign included
(convention: full pull E₁₁−E₁₀−E₀₁+E₀₀). May be a JAX
tracer.
rwa (bool or None) – Per-coupling RWA override; irrelevant to the produced operator
(both forms coincide) but stored for policy uniformity.
label (str, optional) – Defaults to "crosskerr_{n}".
g_0 (float) – Static (mean) coupling strength in GHz. May be a JAX tracer.
rwa (bool or None) – Per-coupling RWA override; None inherits the chip default.
label (str, optional) – Human-readable label; defaults to "tunable_cap_{n}".
Notes
The pump multiplies parametric_interaction() /
rwa_parametric_interaction() in the chip’s frame natively —
no separate frame or carrier logic lives on the coupling; a pump
tone at the qubits’ difference frequency |ω_a−ω_b| activates the
parametric beam-splitter / iSWAP exchange, while a tone at the sum
frequency ω_a+ω_b instead activates two-mode-squeezing
(a†b†) terms. Either is expressed via the drive’s freq
argument, not a coupling-side carrier.
References
McKay, Filipp, Mezzacapo, Magesan, Chow & Gambetta,
Universal Gate for Fixed-Frequency Qubits via a Tunable Bus,
Phys. Rev. Applied 6, 064007 (2016) — parametric two-qubit
gates via coupler flux modulation.
Krantz et al., A quantum engineer’s guide to superconducting
qubits, Appl. Phys. Rev. 6, 021318 (2019), §V.D — tunable
couplers.
Backend eigenstate objects in the same order as eigenvalues.
Materialized lazily on first access from the underlying
EigensystemData — the dressing /
sweep hot path never touches them, so the per-column backend kets are
only built when a caller actually asks for states.
Algorithm id (currently only "DE"), resolved to the
confidence-ordered row-greedy overlap-matching policy actually run
by ChipAnalysis.dress() (assign_rowwise_greedy()).
A shared environment coupling a set of devices to a common bath.
Attach at construction (Chip(...,baths=[...])) or at any time
after via add_bath() — the next
simulate/solve collects the bath’s collapse operators automatically.
Parameters:
recipe (str) – One of "thermal", "collective_decay", "correlated_dephasing".
targets (list[BaseDevice | str] | None) – Devices the bath couples to (objects or labels). None (default)
means every device in the chip — natural for a global thermal bath.
temperature (float | None) – Bath temperature in mK (required for "thermal"). May be a JAX
tracer for sweeps / gradients.
rate (float | None) – Bath–device coupling rate γ in 1/ns. For "thermal" it is the
environmental coupling rate (explicit — never silently borrowed from a
device T1, so it cannot double-count device-level noise). For the
collective recipes it is the overall jump rate. None defaults to
1.0 (user controls the absolute scale elsewhere).
correlated (bool) – "thermal" only: False (default) emits independent per-device
channels sharing one temperature. True is reserved for a genuinely
collective thermal jump operator and currently raises
NotImplementedError — it is a documented future refinement, not
a silent no-op. The collective recipes always emit a single correlated
operator regardless of this flag.
label (str | None) – Auto-generated "bath_{n}" when omitted.
Examples
>>> fromquchipimportDuffingTransmon,Chip,Bath>>> q=DuffingTransmon(freq=5.0,anharmonicity=-0.25,levels=3,label="q")>>> chip=Chip([q])>>> _=chip.add_bath(Bath("thermal",temperature=20.0))# global 20 mK bath>>> _=chip.add_bath(Bath("collective_decay",targets=[q],rate=0.01))
Whether this bath factorizes into independent per-target channels.
True for recipes that emit one collapse operator per target
("thermal" with independent channels); False for recipes that
emit a single jump operator summed over targets ("collective_decay",
"correlated_dephasing"). Partitioning treats a non-separable bath’s
target set as one inseparable block.
Independent copy of this bath (targets normalize to label strings).
Used by Chip.clone and eliminate so a transformed chip never
shares live Bath objects with its source — mutating one chip’s
bath must not silently change another chip’s physics. Parameter
values (temperature, rate) are carried by reference, so traced
values stay traced.
"thermal" emits independent per-target relaxation/absorption
pairs sharing one bath temperature (_bose()). The two
collective recipes instead each emit a single jump operator summed
over the resolved targets:
"collective_decay": L=sqrt(gamma)*sum_ia_i — an
equal-phase, equal-weight rank-one collective channel, not
general collective (super/subradiant) decay, which requires
per-pair phase and weight factors set by the target geometry
(Lehmberg, Phys. Rev. A2, 883 (1970), for the general
collective-radiative-decay construction).
"correlated_dephasing": L=sqrt(gamma)*sum_in_i —
maximally correlated common-mode dephasing (every target shares
the identical dephasing fluctuation), not general correlated
dephasing with a target-dependent correlation structure (Breuer &
Petruccione, The Theory of Open Quantum Systems, Oxford, 2002,
Ch. 3, for the general Lindblad construction).
Always called from inside with_backend_context(chip.backend): (see
quchip.engine.stage4_problem._collect_c_ops()), so this method must
not open its own backend context.
Derived quantities per survivor:
{label:{"lamb_shift","purcell_rate","freq_after","chi","kappa"}}
(GHz for frequencies, 1/ns for rates). chi is the dispersive pull
χ_pull≡f_mode(survivorin|1⟩)−f_mode(survivorin|0⟩) — the
full resonator pull per survivor excitation, i.e. 2× the
σ_z-convention χ of H_disp=(ω_r+χσ_z)a†a. For a mode touching
more than one survivor, the mode is a bus / coupler,
not a readout mode, so chi is reported as 0.0 for every
survivor. Otherwise chi is a deferred entry: computed from the
input chip’s dressed spectrum (one full-chip diagonalization) on the
first ["chi"] access and cached, so reductions that never read it
stay pure algebra (LazyEffectiveParams); gradients through
it follow Chip.dispersive_shift’s backend rule, while every other
entry remains backend-independent algebra. kappa is the
eliminated mode’s own decay rate, from its
intrinsic_decay_rate() (e.g. a
resonator’s combined 2π·f_mode/Q photon loss plus 1/T1), or
0.0 when the mode declares no decay channel.
A mode touching two or more survivors additionally emits an
"exchange" entry describing the mediated survivor-survivor
coupling(s) derived from the Sylvester/exact pair extraction
(quchip.chip.sw). For exactly two survivors (the bridge case)
this is a single dict: {"j_eff","dJ_domega_c","between","folded_into","zz","pathways"}. For three or more survivors,
every survivor pair induces its own mediated exchange, so
"exchange" is instead a dict keyed by the pair
(label_a,label_b), each value the same per-pair schema. In both
cases: j_eff is the mediated exchange J=g_ag_b/2·(1/Δ_a+1/Δ_b) (F. Yan et al., Phys. Rev. Applied 10, 054062 (2018)), read
off the method="sw"/"exact" pair extraction; dJ_domega_c
is its analytic derivative w.r.t. the eliminated mode’s frequency
(used by the flux-drive retarget rule); folded_into is the label
of the effective edge the exchange landed on (an existing direct
coupling between the pair, or a freshly emitted
Capacitive or
TunableCapacitive); zz is the
exact residual ZZ between the pair (method="exact" only —
None under "sw", where it is
a higher-order correction not represented); pathways is the
top virtual-state attribution of the exchange (method="sw" only,
from quchip.chip.sw.pathway_attribution() — None under
"exact", which has no perturbative generator to attribute).
Per eliminated coupling:
{coupling_label:{"g_over_delta","is_valid","min_block_gap"}}.
min_block_gap is the smallest bare-energy gap the Sylvester
generator crossed for this mode (quchip.chip.sw.sylvester_generator()),
shared across every coupling touching the mode. When eliminate
runs under jax.jit/grad, is_valid (g_over_delta<0.1)
is a traced boolean, not a Python bool — read it outside the
traced region, or branch on it with jnp.where rather than if.
Plain-text fold report: every fold stated explicitly, before -> after.
Per survivor: freq (and T1 when either side carries one), each
Lamb-shifted/Purcell-folded value read back from effective_params
rather than a stored “before” chip — freq_before=freq_after-lamb_shift and T1_before from purcell_rate are exact
identities of how eliminate() derives freq_after/T1, not
an approximation. Multi-survivor targets add the emitted exchange edge
(Yan-formula tag) and a ZZ line (placeholder under method="sw",
the exact residual under method="exact"); any control-line
retarget and the per-coupling validity verdict follow. Traced
parameters render as <traced> and are never concretized; for use
outside jit/grad regions, like every other describe() in
the package.
Reduce a far-detuned device or an edge coupling, returning a reduced chip.
target is resolved against the chip’s device and coupling namespaces
(disjoint by construction — Chip rejects a
coupling label that collides with a device label) and dispatches to one
of two model reductions:
Device target — adiabatic elimination of a far-detuned mode, via
quchip.chip.sw. A mode touching one survivor folds into a
Lamb shift (and a Purcell channel when the mode dissipates). A mode
touching two or more survivors — bus / tunable-coupler (bridge) or
several at once — additionally induces a mediated exchange J=g_ag_b/2·(1/Δ_a+1/Δ_b) between every survivor pair (with
∂J/∂ω_c recorded alongside it), folded into the direct coupling
between that pair when one exists or added as a new edge otherwise.
A fixed eliminated mode emits a
Capacitive; a mode that declares
frequency control, has a retargeted flux line, or folds into an
already-modulable edge emits a
TunableCapacitive. At a tunable
coupler’s idle point, its g_0 is the net coupling the pair feels,
including any direct-edge cancellation. Eliminating several couplers
is sequential composition:
eliminate(eliminate(chip,"TC1").chip,"TC2").
Coupling target — dispersive reduction of an exchange edge to its
dressed cross-Kerr shift: both endpoint devices survive (Lamb-shifted),
and the coupling itself is replaced by a
CrossKerr carrying the dressed pull
(see reduce_coupling()).
This is the effective-readout-chip flow — reduce a qubit-resonator
exchange edge to the diagonal interaction an ordinary charge line
probes. method has no effect here: no mode is removed, so the
reduction always reads the chip’s exact dressed spectrum.
target (Any) – The device or coupling to eliminate — label string or object.
method (str) – Device targets only. "sw" (default) is the 2nd-order
Schrieffer-Wolff reduction (Bravyi, DiVincenzo & Loss, Ann. Phys. 326,
2793 (2011)) — cheap, differentiable, and what every effective
parameter above is derived from perturbatively. "exact" instead
reads the reduced parameters off the chip’s exact dressed spectrum
(exact-from-dressing, quchip.chip.sw.exact_reduction()) —
exact kept-block energies (what residual ZZ needs) at the cost of a
full diagonalization, and it raises when near-degenerate dressed
states make the bare labeling ambiguous. Any other value raises
ValueError.
The patch chip: schedule-active devices plus whatever spectators
active_patch() could not eliminate (unreachable, or where
elimination itself declined — see notes).
The EliminationResult
from each successful eliminate()
call, verbatim and in eliminated_labels order — reshape
nothing here; read .validity/.effective_params off the
step objects through the convenience properties below.
Every explicitly dropped or deferred piece of physics: unreachable
spectators left in place, control lines stripped as unused, and
(if elimination itself couldn’t proceed past some spectator) why
the reduction stopped early — plus every step’s own notes.
Reduce a chip to its schedule-active patch by eliminating spectators.
Spectators are eliminated one at a time via
eliminate(), farthest-from-active
first; every step’s validity metrics ride on the result untouched.
Explicit opt-in — this approximates (Schrieffer-Wolff, or the exact
dressed-spectrum route under method="exact"), unlike the exact
automatic partitioning simulate()
performs internally at solve time.
The elimination order and reachability are recomputed from the
current reduced chip before every step, not just once up front: a
fold can only ever add a bridging edge between the eliminated mode’s
neighbors, never remove one, so a label’s distance from the active set
can shorten but never make a previously reachable label unreachable —
still, reading the graph fresh each time means a label that has
already been folded away is never re-considered, and the order always
reflects what eliminate will actually see. Bridging edges created
by earlier folds may be Capacitive
edges carrying g or TunableCapacitive
edges carrying g_0. Both fold onward, so cycles among spectators
reduce all the way down. If eliminate still declines a step (its
typed NotImplementedError signal for an unsupported
elimination — e.g. a Purcell decay fold onto a survivor that carries
thermal occupation, which no collapse-channel API can represent without
inventing physics that was never present), the reduction stops
there: everything eliminated so far stays folded, and the remaining
spectators — including the one that failed — are left on the patch
chip untouched, with the reason recorded in notes.
Register a converter for (drivetype,eliminated-targettype,resultkind).
Lookup (lookup_retarget_rule()) walks both types’ MROs, so a rule
registered for a base type also covers its subclasses; result_kind
matches exactly. This is the extension point for teaching
eliminate() to carry a new kind of
stranded control line without modifying it.
Parameters:
drive_type (type) – Control-line class the rule handles.
target_type (type) – Eliminated-target class the rule handles: a device type (the
eliminated mode itself) for "edge"/"leaf-fold".
result_kind (str) – "edge", "leaf-fold", or "crosskerr".
The sigmas parameter \(N_\sigma\) is the number of standard
deviations from the pulse center to its edge at t=0 or
t=duration. Gaussian pulses minimize spectral leakage onto
higher transmon levels and are the starting point for DRAG
corrections (Motzoi et al., PRL 103, 110501 (2009)).
The scheduled window [0,duration] starts and ends at
amplitude*exp(-sigmas**2/2), not zero — about
0.011*amplitude at the default sigmas=3. The pulse turns
on and off with that jump; the Gaussian waveform itself is
unchanged.
Linearly rising ramp that holds at peak amplitude.
The envelope rises linearly from 0 to amplitude over the first
ramp_duration nanoseconds, then holds constant at amplitude
for the remainder of the pulse.
\[E(t) = A \cdot \min\!\left(\frac{t}{\tau_r},\, 1\right),
\qquad 0 \le t \le \tau,\]
where \(\tau_r\) is ramp_duration and \(\tau\) is
duration.
Parameters:
duration (float) – Total pulse duration in ns. Must be > 0.
ramp_duration (float) – Duration of the linear rise in ns. Must satisfy
0<ramp_duration<=duration.
For an adiabatic ramp into a Kerr-cat qubit, choose ramp_duration
long compared to 1/(2*K) (the inverse gap at the bifurcation
point). See Grimm et al., Nature 584, 205 (2020).
The waveform is JAX-traceable: ramp_duration and amplitude
may be JAX tracers so the ramp parameters are differentiable.
Flat-top pulse with Gaussian ramp-up and ramp-down edges.
Each edge is a Gaussian of width \(\sigma = \tau_e / (2 N_\sigma)\)
where \(\tau_e\) = edge_duration; the plateau between edges
holds a constant amplitude \(A\). Total duration includes
both edges. Commonly used for two-qubit gates (Krantz et al. 2019,
Sec. IV.C) because the flat top sets the gate area while the
Gaussian edges suppress spectral leakage.
Parameters:
duration (float) – Total pulse length, including both edges, in ns.
edge_duration (float) – Ramp time \(\tau_e\) per edge, in ns. Must satisfy
2*edge_duration<=duration.
sigmas (float) – Number of standard deviations spanned by each edge.
Flat-top pulse with Gaussian ramp-up and ramp-down edges.
Each ramp has duration \(\tau_e = f_e \cdot \tau\) with
\(f_e\) = edge_frac; the plateau between ramps holds
amplitude \(A\). Total duration includes both edges. The
Gaussian width is \(\sigma = \tau_e / (2 N_\sigma)\) with
\(N_\sigma\) = sigmas.
This is the canonical shape used in Krantz et al. 2019
(Sec. IV.C) for two-qubit gates — the flat top sets the gate area
while the Gaussian edges suppress spectral leakage. Parametrizing
the ramp as a fraction of the total duration makes the shape
shape-invariant under changes of duration.
Parameters:
duration (float) – Total pulse length in ns (includes both ramps).
Build a SolveProblem, dispatch it, and wrap the solver output.
Parameters mirror build_problem(). solver is "sesolve"
or "mesolve"; None auto-selects mesolve when collapse
operators exist. e_ops is dict-form, keyed by device label (or
a 2-tuple of labels for two-body observables), and favors object
references via resolve_label(). The
Hilbert-truncation safety net is inherited from solve_problem();
check_truncation / truncation_threshold are threaded down.
solver ({"sesolve", "mesolve"}, optional) – Solver selection; None auto-selects mesolve when collapse
operators are present, else sesolve.
options (dict, optional) – Backend solver options. Must not contain a "backend" key
(backend selection is chip-owned).
e_ops (dict, optional) – Observables keyed by device label (or a 2-tuple of labels for a
two-body observable).
initial_state (optional) – Initial state. None defaults to the chip ground state. A
Mapping (device label/object -> Fock index, e.g.
{"q0":1}) resolves through Chip.state() — the same
dressed-state semantics
_resolve_initial_state_spec()
uses — on both the joint and the partitioned path. Any other
value (a raw backend state) is passed through unchanged.
check_truncation (bool, default True) – Screen the result for over-populated top Fock levels.
truncation_threshold (float, default 1e-3) – Top-level population above which the truncation check warns.
partition (bool, default True) – When the chip splits into independent sub-chips (see
Chip.partition()), dispatch one solve per component and
combine them into a PartitionedSimulationResult
instead of solving the full tensor-product space. Declines back
to the joint solve (returning a plain
SimulationResult) when the
partition is trivial or initial_state is a raw backend state
rather than None/a Mapping. Set False to force the
joint solve unconditionally. simulate_batch/solve_many
never partition in v1 — batched dispatch always solves the full
chip.
ValueError – If solver is neither "sesolve" nor "mesolve", if
tlist is not one-dimensional, finite, strictly increasing,
and at least two points long, or if any drive_ops entry’s
pulse window does not overlap tlist with positive measure
(both concrete-only checks; see build_problem()).
ValueError – If tlist is not one-dimensional, finite, strictly increasing,
and at least two points long, or if any drive_ops entry’s
pulse window [start_time,start_time+envelope.duration]
does not overlap tlist with positive measure. Both checks are
concrete-only and skip silently under JAX tracing.
This is the common single-solve chokepoint, so the Hilbert-truncation
safety net lives here: unless check_truncation=False, the wrapped
result is screened for over-populated top Fock levels (warning above
truncation_threshold). Every example-facing single-solve path
(chip.solve, seq.simulate) inherits the check by routing through here.
Batch-dispatch typed solve requests that share one chip configuration.
Accepts a SolveBatch, a ProblemBatch,
or a flat list of SolveProblem objects. The batched paths are
preferred: backends convert shared operators exactly once and stitch
per-element coefficients into one parallel solve.
Ordered, immutable batch of SimulationResult with stacked helpers.
Returned by solve_many() and by any sweep that
solves many problems in one call. The batch preserves iteration order
so that per-element results map one-to-one onto the inputs that
produced them.
The final_* helpers stack along a new leading batch axis in the
backend’s array module, and the grid-aware expect() /
population() accept reduce='last' for a final-value slice, so
a loss function that sums over the batch stays JAX-traceable end-to-end.
Backend-agnostic container for the output of one solve.
A SimulationResult bundles everything a user needs after a
solve finishes:
times — the time grid the solver stored (ns).
states — the list of stored states (kets or density matrices),
or None if store_states was off.
solver — the name reported by the backend.
stats — a plain dict of solver statistics.
dims — the per-device Hilbert-space dimensions, in chip order.
device_info — [(label,computational),…] for partial-trace
helpers to resolve device indices by label or by object.
Expectation values live in observable_traces when e_ops was
passed as a dict. Each entry is an ObservableTrace or a list
of them (list-valued observables, one per band, etc.).
None when e_ops was not passed as a dict. Visualization and
analysis code that wants the full dict should read this property
rather than the private _expect_data attribute.
Return the overlap with target at every stored time.
For ket trajectories returns |<target|psi(t)>|**2; for density
matrices returns <target|rho(t)|target>. Stays in the backend’s
array module so the result is differentiable. One batched op over the
leading time axis — no per-point loop.
Return the phase-sensitive complex projection <target|psi(t)> for kets.
Density-matrix trajectories raise TypeError — there is no
single phase-sensitive amplitude for a mixed state; use
overlap_array() instead. One batched op, no per-point loop.
Return per-basis-state populations |<n1,n2,...|psi(t)>|**2 over time.
Returns a dict keyed by Fock tuple (n1,n2,...,nK) — one per
computational basis vector of the full chip — mapping to a real
numpy.ndarray of length len(self.times).
Requires store_states; density-matrix trajectories are handled
transparently by reading the diagonal of each timestep’s DM.
Returns a NumPy array under the QuTiP backend; under JAX (dynamiqs)
returns the backend-native array so the call stays JIT/grad-friendly.
Use population_array() directly when you want to keep gradient
flow regardless of context.
Emit a UserWarning per device whose top-top_levels population exceeds threshold.
Uses the final stored state only — cheap, one full-chip diagonal
read, no per-timestep loop, no partial traces. Silently no-ops
when no final state is available (e.g. store_states and
store_final_state both disabled).
Returns the per-device top-level population actually observed,
keyed by device label, so callers can surface the numbers without
re-parsing the warning text.
Return the parent chip’s original device-label order.
This is not the concatenation of each component’s labels (which
follows connected-component discovery order and can interleave
differently whenever the chip’s device order doesn’t already group
each component’s members together) — it is the order
states and final_state are permuted into so they
match the joint solve exactly.
Reconstruct the joint-state trajectory (ket trajectories only), in device_order.
Component states tensor together in connected-component discovery
order, which can interleave differently from the parent chip’s own
device order; each reconstructed step is permuted
(permute_state()) into
device_order so the result matches a joint solve of the
original chip exactly.
See also final_state, which — unlike this accessor —
intentionally also accepts density-matrix components: a tensor
product of component density matrices is itself a valid joint
state, whereas a per-step list of joint kets is only well-defined
when every component stayed pure.
Reconstruct the joint final state, in device_order — a ket if
every component stayed pure, otherwise a density matrix.
When components disagree, every component is first promoted to a
density matrix (_promote_to_common_state_kind()) before
tensoring, giving a valid joint density matrix. Components tensor
together in connected-component discovery order, which can
interleave differently from the parent chip’s own device order; the
result is permuted (permute_state())
into device_order so it matches a joint solve of the
original chip exactly.
chip (Chip) – Chip this sequence schedules against. Supplies the device and
coupling maps used to resolve scheduling targets, the wired
ControlEquipment lines, and
the frame/backend settings used by build_problem() and
simulate().
BaseDevice — uses the device’s first connected
drive; pass the drive object explicitly when a device
has multiple drives.
BaseCoupling — uses
the unique ParametricDrive
line pumping that coupling; pass the drive object
explicitly when a coupling has multiple pump lines.
str — a label, resolved in order: a device label; then,
only when absent from the device map, a coupling label
(the two label spaces are disjoint); then, only when
absent from both, a control-equipment line label,
scheduling directly on that line. This third fallback is
what lets a caller schedule by a drive’s own label after
its device or coupling target has been eliminated — see
eliminate()’s retarget
registry, which preserves a converted line’s label. A
control-line label that collides with a device/coupling
label is shadowed by the device/coupling resolution.
freq (float, optional) – Carrier frequency in GHz for microwave drives. Defaults to
the target device’s drive_freq for
ChargeDrive; always required (no device-frequency
fallback) for PhaseDrive and
TwoPhotonDrive;
ignored for FluxDrive and baseband
ParametricDrive pumps.
start_time (float, optional) – Pulse start time in ns. Defaults to the current cursor;
earlier times are rejected.
phase (float) – Per-pulse phase offset, composed with any accumulated
virtual-Z.
Schedule a flux-drive frequency-shift pulse to target_freq.
This is not an inverse-SQUID flux calibration. It computes
δω=target_freq−chip.freq(device) and schedules a
FluxDrive pulse whose envelope amplitude is that frequency
shift in GHz. The resulting Hamiltonian contribution is the linear
detuning term δω(t)n̂. envelope should be passed with
amplitude=None (or any placeholder) — this method replaces the
amplitude with the computed δω.
target_freq may be a JAX tracer (e.g. chip.freq(other_device)).
target_freq (float) – Target 0→1 frequency in GHz.
envelope (BaseEnvelope) – Envelope template with all timing parameters set. Its
amplitude attribute is replaced by the computed δω; any
value passed as amplitude is ignored.
Returns:
Handle to the scheduled entry, usable with .vary().
Apply a virtual-Z frame shift of angle rad on target.
The shift is free (no pulse is emitted) and accumulates into
every subsequent microwave pulse on the device via its
phase_offset. This is the standard software-Z trick for
transmons (McKay et al., PRA 96, 022330 (2017)). Baseband flux
pulses are unaffected.
With no arguments, every (device,drive) cursor on the
chip is advanced to the current maximum. With explicit
device targets, only those devices’ cursors are aligned. Use
this to guarantee that pulses scheduled after the barrier
start no earlier than any pulse scheduled before it.
tlist (array-like, optional) – Save/output time grid in ns. Defaults to the grid built by
_resolve_tlist() from total_duration (10
points/ns, 100-point floor).
solver (str, optional) – Backend solver name. Defaults to the backend’s own default
solver when omitted.
options (dict, optional) – Solver options, merged on top of the defaults
{"store_states":True,"store_final_state":True}.
e_ops (dict, optional) – Expectation operators keyed by device label (or a 2-tuple of
device labels for a two-body operator), mapping to a local
operator (or a pair of local operators). Decomposed into
per-band terms before reaching the solver.
initial_state (Any, optional) – A ket, density matrix, or mapping for
state(). Defaults to the chip’s
default initial state when omitted.
Returns:
Frozen problem — chip, compiled Hamiltonian description,
initial state, tlist, collapse operators, and
expectation operators — ready for
solve() or
solve_problem().
field (str) – Sequence-level field to sweep. Only "initial_state" is
supported; per-pulse fields (freq, phase,
amplitude, …) are swept via PulseHandle.vary()
on the handle returned by schedule() instead.
values (array-like) – Sequence of values for field, one per batch point.
*axes (BatchAxis) – Two or more axes to zip, each created by this sequence
(vary(), PulseHandle.vary(), or
DelayHandle.vary()). Every axis must have the same
length.
Returns:
Combined axis that build_batch() treats as a single
dimension: point i supplies point i from every
zipped axis simultaneously, rather than the outer product.
Build a batched solve request from sweep axes without solving.
The returned ProblemBatch wraps a
SolveBatch: shared static/dynamic operators
are collected exactly once and only per-element scalar modulations
(and per-element initial states, when varied) change across the batch.
Build and solve a single simulation, routed through simulate().
backend — name ("qutip"/"dynamiqs") or instance — scopes
this one call, outranking the chip’s and the process default (so a
gradient solve can run on dynamiqs while everything around it stays
on QuTiP). Foreign-native initial states are coerced at the solve
boundary. Caveat: Python evaluates arguments before the scope
opens, so an initial_state=chip.state(...) expression inline in
the call is built under the surrounding backend — fine for concrete
chips (the state is coerced), but a chip carrying JAX tracers needs
a JAX-capable surrounding backend (chip-level or process default)
for that construction itself.
Inherits the Hilbert-truncation safety net from solve_problem();
pass check_truncation=False to opt out or retune
truncation_threshold.
partition, default True, forwards to
simulate(): when the chip splits into
independent sub-chips (Chip.partition()), each component is
dispatched separately and combined into a
PartitionedSimulationResult.
This only engages when initial_state is None or a
Mapping — a string shorthand or a concrete state is resolved
via _resolve_initial_state_spec() first and always takes the
joint path. Pass partition=False to force the joint solve
unconditionally.
Build and solve a batched sweep. Equivalent to chip.solve_many(seq.build_batch(...)).
backend scopes this one call exactly as in simulate().
The batched solve does not pass through solve_problem(),
so the Hilbert-truncation safety net is applied per batch element here
(default on); pass check_truncation=False to opt out or retune
truncation_threshold.
One row per pulse — resolved time window, drive → device,
envelope with its declared parameters, carrier frequency — plus
a trailing count of delays, barriers, and virtual-Z entries.
Returns a string; print(seq.describe()).
Returns (in order) the nearest _backend_context() override, the
user-set default from set_default_backend(), or a freshly
instantiated QuTiPBackend (cached for subsequent calls).
JAX compiles each traced kernel (the label_eigensystem scan, eigh /
diag / broadcast kernels, dynamiqs solver steps, …) to XLA when it
is called for the first time in a process. Those compilations are
memoized in memory within a process, but every new process re-pays
them. This helper points JAX at a per-user on-disk cache so a later
process whose (shape,policy,jaxlib) fingerprint matches loads the
compiled executable instead of recompiling.
Fully transparent to jit / grad / vmap — it only changes where
compiled artifacts are stored, never traced values or physics. It is opt-in:
the package never writes to the user’s disk unless this is called
explicitly. JAX keys cache entries on the
jaxlib version, so a toolchain upgrade produces fresh entries rather
than loading a stale executable.
Parameters:
path (str | None) – Cache directory. Defaults to $XDG_CACHE_HOME/quchip/jax (or
~/.cache/quchip/jax) — a per-user path, never a shared/system one,
so the cache stays inside the user’s trust boundary.
min_compile_time_secs (float) – Skip caching kernels that compile faster than this. The default
0.01 captures quchip’s sub-second kernels that JAX’s ~1s default
would otherwise never persist.
Base class for classical control lines driving a single device.
Drives own their local Hamiltonian contribution and
are auto-labelled from their _type_prefix (e.g. charge_0,
flux_0) unless label is given. Subclasses are auto-registered
for serialization via the shared
Registrable mixin.
Parameters:
target (BaseDevice | None) – Device to attach this drive to. May be connected later via
connect().
If the drive was previously attached to another device, it is
detached from that device’s _connected_drives list so that
only the new attachment is visible on the chip.
Return the Hamiltonian channels this drive exposes on device.
A simple single-channel drive declares _coupling_protocol,
_coupling_accessor, _fallback, and _modulation;
this method then dispatches generically — the device’s physical
coupling operator when it conforms to the Protocol, the structural
fallback otherwise. Returned operators are in the device’s local
basis (no chip-wide embedding). Drives with a non-standard
decomposition override this method instead.
Return human-readable declarations of this drive’s approximations.
Returns the shared target/RWA lines plus this drive’s
_coupling_note when declared. Subclasses with extra notes
super().physics_notes() and append their own. Aggregated by
Chip.physics_notes().
Build the frame-agnostic signal spec for one scheduled pulse.
Parameters:
drive_op (DriveOp) – Scheduled pulse with envelope, start time, phase offset,
and (for microwave drives) carrier frequency. Typed as
Any because DriveOp
lives in the engine IR, which drives must not import.
device (BaseDevice) – Target device. Unused in the default implementation, kept
as an extension hook so subclasses can emit device-aware
specs (e.g. flux-tunable drives that query the device’s
tuning curve).
Returns:
Frame-agnostic description of the pulse — envelope, start
time, duration, phase offset, and carrier frequency (or
None for baseband drives) — built directly from
drive_op, with no frame or IR commitment.
The default implementation produces a
DriveSignalSpec describing
a phase-rotated, time-shifted, finite-duration envelope. Stage 2
of the engine turns the spec into the raw
SignalProgramScale(Shift(Window(env,0,duration),start),exp(i·phi))
before applying the modulation dispatch.
Abstract base for signal-map transforms, auto-registered for serialization.
The type registry, the {"type":...}to_dict() stamp, and the
from_dict dispatch are owned by the shared
Registrable mixin; the parameter-less
default reconstruction (cls()) covers transforms that carry no
persisted state, while payload-carrying transforms override
to_dict() / from_dict().
On the registry root, dispatch to the concrete subclass named by
data["type"] (forwarding *args / **kwargs). On a concrete
subclass, defer to _from_dict_payload(). Concrete subclasses
that carry payload override this method directly.
On the registry root, dispatch to the concrete subclass named by
data["type"] (forwarding *args / **kwargs). On a concrete
subclass, defer to _from_dict_payload(). Concrete subclasses
that carry payload override this method directly.
Frame-agnostic description of one scheduled drive pulse.
The spec carries everything the engine needs to build the raw line
signal and later compose it with the frame + carrier during
modulation — expressed in ordinary GHz (frequencies) and ns (times),
with no IR references.
Parameters:
envelope (BaseEnvelope) – Pulse envelope reference. Its waveform(t) is called at
evaluation time; all differentiable pulse parameters live as
leaves on the envelope (quchip.control.envelopes).
SINGLE_TONE — microwave IQ-style carrier mixing. In the
lab frame the raw signal is mixed with exp(-i·2π·ν_d·t) and
projected to its real part; under RWA the signal is decomposed
into co- and counter-rotating components per Fourier band
(Krantz et al. 2019, Eq. 89–90).
DIRECT_REAL — real-valued baseband coupling (no carrier,
no RWA), appropriate for flux-like drives (Koch et al. 2007,
Sec. II; Krantz et al. 2019, Sec. V.A on flux tunability).
EDGE_PUMP — real pump δ(t) on a modulable coupling’s
strength: baseband δ(t)=Res(t) when the scheduled op has no
carrier, single-tone δ(t)=Re[s(t)·e^{-i·2π·ν_d·t}] when it
does. The tone is never RWA-split — the coupling’s parametric hook
already selected the retained operator structure (Didier et al.,
PRA 97, 022330 (2018) on parametric gate activation).
The engine dispatches on this tag in a single code path; adding a
new modulation kind is an infrastructure change, not a per-drive
special case.
Contributes the standard charge-coupling Hamiltonian
\[H_d(t) = \epsilon(t)\, i(\hat a - \hat a^\dagger)\]
with \(\epsilon(t)\) the (real-projected) mixed signal built by
the SINGLE_TONE
dispatch in stage 2. This is the canonical transmon microwave drive
(Koch et al., PRA 76, 042319 (2007); Krantz et al., APR 6, 021318
(2019), Eq. 90).
One Hamiltonian channel exposed by a drive on a target device.
A channel pairs a local-basis operator with a
DriveModulation tag that tells the
engine how the channel’s coefficient is built from the drive’s line
signal. A drive may expose multiple channels if, for example, it
contributes both in-phase and quadrature couplings.
Uses DIRECT_REAL —
no carrier, no RWA — as appropriate for a baseband flux line that
modulates the device frequency through its number operator
(Koch et al. 2007, Sec. II; Krantz et al. 2019, Sec. V.A on flux
tunability).
The rwa kwarg is rejected: applying RWA to a baseband drive
is ill-defined.
Control line pumping a modulable coupling’s strength δ(t) in GHz.
Targets a coupling (object or label string; labels late-bind via
Chip.connect()). The scheduled envelope is the real amplitudeA(t): with an explicit freq the pump is
δ(t)=A(t)·cos(2π·freq·t-phase); with freq omitted the pump is
baseband, δ(t)=A(t) directly. The pump tone is never RWA-split —
the coupling’s RWA policy selects the operator structure via its
parametric hook; the tone keeps both sidebands (declared, the solver
integrates the fast one).
Accepted couplings implement
parametric_interaction();
a static coupling raises TypeError naming the hook.
Parameters:
coupling (BaseCoupling | str) – Modulable coupling to pump, given as the coupling object or its
label. A string label late-binds to the coupling instance via
Chip.connect().
Microwave phase drive coupling to \(\hat a + \hat a^\dagger\).
Same carrier machinery as ChargeDrive but with an
in-phase (rather than quadrature) coupling. Useful when modeling
phase-noise channels or drives whose physical coupling is already
referenced to the field quadrature. See Krantz et al. 2019, Sec.
IV.A for the two conventions.
Parametric two-photon drive for Kerr-cat qubit stabilisation.
Coupling operator: a^2+a_dag^2
The drive should be scheduled at twice the cavity frequency
(freq=2*cavity.freq) so that in the rotating frame the
interaction is static: eps2(t)*(a_dag^2+a^2). This combination
of Kerr nonlinearity and two-photon drive creates and stabilises cat states.
Uses the standard DriveModulation.SINGLE_TONE carrier dispatch – no engine
modifications required. The engine band-decomposes a^2+a_dag^2 into
bands of excitation weight Delta_n = +2 and Delta_n = -2, attaching the
correct two-photon carrier automatically.
SINGLE_TONE’s real-field projection contributes only half the
scheduled envelope amplitude to each band: the coefficient landing on
a_dag^2+a^2 in the rotating frame is A(t)/2, where A(t)
is the amplitude scheduled on this drive’s envelope. Schedule
amplitude=2*eps2(t) to realize the target two-photon drive
strength eps2(t) used above and in alpha^2=eps2/K.
Parameters:
target (BaseDevice | None) – Device to connect this drive to. None means unconnected.
Apply every signal-chain transform to signals, in order.
Each transform receives the previous transform’s output, so
transforms compose sequentially: reordering signal_chain
changes the result (e.g. a Delay applied before a
Gain sees the undelayed signal).
Parameters:
signals (SignalMap) – {(drive_label,drive_index):SignalProgram} map of raw
line signals. drive_index distinguishes multiple ops on
the same drive line and is assigned by the engine when it
enumerates the chip’s scheduled drive ops.
Returns:
Transformed signal map. May contain keys absent from
signals: a Crosstalk transform, for example,
adds an entry under the victim drive’s label for every
source entry it leaks from.
Return a dense matrix view of the Crosstalk transforms.
The matrix uses wiring order (self.lines) as the stable axis
ordering. Column index = source drive, row index = victim drive.
Diagonal entries are beta=1, theta=0, delay=0 by
convention (self-coupling). Off-diagonal entries aggregate every
Crosstalk transform present in the signal chain; lines
with no corresponding transform contribute zeros.
Non-Crosstalk transforms (Gain, Delay) are
ignored here; this is strictly a view of the crosstalk edges.
Returns:
labels (wiring order), beta, theta, delay
as [n,n] arrays. Arrays use jax.numpy when any
stored entry is a JAX tracer or array, otherwise
numpy.
Rehydrate the crosstalk edges from dense matrices.
Removes every crosstalk transform currently in the signal chain and
replaces them with one CrosstalkMatrix. Other signal-chain
transforms (Gain, Delay, and user-defined subclasses) are
preserved in order.
Parameters:
beta (Any) – [n,n] amplitude matrix. beta[i,j] is emitted as
the leakage amplitude from source labels[j] onto victim
labels[i]. Diagonal entries are ignored (self-coupling
belongs to the drive itself, not to a crosstalk edge).
Reconstruct from to_dict() output, rebinding drives via dev_map / coupling_map.
Each line’s target_label is resolved against dev_map first,
then coupling_map — device and coupling labels are disjoint by
Chip construction, so at most one map holds the label.
onto the victim line, where \(s_\mathrm{src}\) is the source’s
raw line signal — the frame-agnostic, carrier-free envelope signal
the signal chain operates on, before stage 2 attaches any carrier
or RWA modulation. Fidelity to classical microwave crosstalk is
preserved downstream instead: stage 2 builds the victim’s
Hamiltonian term using the source drive’s own carrier frequency
for the leaked entry, not the victim’s, so a leaked pulse still
lands at the source’s frequency (Balewski et al., arXiv:2502.05362,
Eq. (3); Sheldon et al., PRA 93, 060302 (2016); Sarovar et al.,
Quantum 4, 321 (2020)). The delay shifts the baseband
envelope; the carrier-phase part of a physical path delay
(\(2\pi f \tau\)) belongs in theta.
Parameters:
source (str | BaseDrive) – Source drive or its label.
victim (str | BaseDrive) – Victim drive or its label.
On the registry root, dispatch to the concrete subclass named by
data["type"] (forwarding *args / **kwargs). On a concrete
subclass, defer to _from_dict_payload(). Concrete subclasses
that carry payload override this method directly.
Every off-diagonal edge reads the same input signal map, so reciprocal
entries form one linear mixing stage without recursively leaking one
another’s output. Matrix entries flow directly into the signal-program IR
(PolarScale/Shift), preserving end-to-end JAX traceability.
On the registry root, dispatch to the concrete subclass named by
data["type"] (forwarding *args / **kwargs). On a concrete
subclass, defer to _from_dict_payload(). Concrete subclasses
that carry payload override this method directly.
At each grid point the chip is cloned via Chip.updated(),
update_fn mutates the clone with the swept values, and
Chip.dress() computes the lab-frame dressed spectrum. The
per-point eigenvalues and overlap-based bare→dressed assignments
are collected into a SpectrumSweepResult.
This is the standard tool for two-tone spectroscopy maps, avoided
crossings, and any study that requires following dressed states
across parameter space. See Blais, Grimsmo, Girvin & Wallraff,
Rev. Mod. Phys. 93, 025005 (2021), for the dressed-state picture;
the overlap-based labeling follows the usual practice of assigning
a dressed eigenstate to the bare state with which it has maximum
overlap.
Examples
>>> # Sweep a qubit frequency and record dressed levels>>> # sweep = SpectrumSweep(>>> # chip,>>> # [Sweep(np.linspace(4.8, 5.2, 41), name="freq")],>>> # update_fn=lambda c, p: setattr(c.device(qubit), "frequency", p["freq"]),>>> # )>>> # result = sweep.run()
Declarative 1-D sweep axis over a named parameter.
values are in the swept parameter’s own physical units (the
package-wide contract: GHz for frequencies and couplings, ns for
times, mK for temperatures).
values may be a Python sequence, a NumPy array, or a JAX array;
JAX arrays are preserved as-is so any consumer that threads the
sweep through a JAX-traced path keeps full differentiability.
Non-JAX inputs are normalized through numpy.asarray() for
uniform len/indexing behavior.
At least two sweeps are required; all sweeps must have equal size.
A ValueError is raised otherwise.
Parameters:
*sweeps (Sweep) – Two or more Sweep axes to bundle element-wise. Their
name attributes must all be distinct — a ZippedSweep
with repeated axis names raises ValueError when it is
later enumerated (expand(), SpectrumSweep).
Returns:
Bundle iterated element-wise instead of taken in Cartesian
product with other axes.
Expand sweep axes into a flat list of parameter dicts.
The Cartesian product of independent Sweep axes is
taken; ZippedSweep bundles remain element-wise. The
return order is the grid’s C-order (last axis varies fastest).
This is the params-only view of _iter_axis_points() — the
single enumeration code path — with the grid coordinates dropped.
Parameters:
axes (Sequence[Sweep | ZippedSweep]) – Sweep and/or ZippedSweep axes. Every axis name
(including each member of a zipped bundle) must be unique across
axes, else ValueError is raised — a repeated name
would silently overwrite itself in each point’s parameter dict.
Returns:
One parameter dict per grid point, length equal to the product
of the independent axes’ sizes (zipped bundles contribute their
shared size once).
Built via Sweep.zip(). All bundled axes must share the same
size; iteration steps through them together, producing one
parameter dict per element rather than a Cartesian-product grid.
Fit bare chip parameters so dressed observables match targets.
With fit_parameters=None (the default), every device freq /
anharmonicity and every coupling’s scalar strength
(coupling_strength) is a
free variable in a bounded non-linear least-squares problem (scipy
Trust-Region Reflective). A fit_parameters mapping instead is the
complete free-parameter allowlist — see fit_parameters below — so
any device or coupling it does not list is frozen instead of free. The
fitted chip is returned as a clone — the input chip is never mutated.
Parameters:
chip (Chip) – Seed chip. Seeds only set the optimizer’s starting point.
coupling_targets (Mapping | None) – Mapping from coupling (or its label) to a target mode:
"chi", "zz", or "g". For listed couplings, the
coupling’s current strength is interpreted as the target value
for that mode. With fit_parameters=None, couplings not listed
here are still free — they are optimized, just without a dedicated
anchor; a fit_parameters mapping can freeze them regardless (a
coupling target does not itself make a coupling free). A "chi"
target requires the coupling to have exactly one computational
endpoint; both-computational or neither-computational raises
ValueError at construction.
observable_targets (Mapping | None) – Mapping of target observables. Keys are devices/labels or
(device_a,device_b) tuples; values are {kind:value}
dicts. Supported kinds: "freq", "anharmonicity"
(device), "exchange", "zz" (pair). Device-level
targets override the auto-targeted defaults for the same
(kind,label).
fit_parameters (Mapping | None) – None (default): every declared device tunable
(tunable_params()) and every
coupling’s scalar strength is free — the pre-existing behavior.
A mapping is instead the complete free-parameter allowlist:
{component_or_label:name_collection}, where a device’s
name_collection is a subset of its declared tunable names and
a coupling’s is a subset of
(coupling.coupling_strength_name,). A component (device or
coupling, given as the object or its label) absent from the
mapping is fully frozen — it does not default to free. An
empty name_collection explicitly freezes a listed component.
A bare string value (e.g. "E_J" instead of ("E_J",)) is
rejected, since a string is itself a collection of characters.
Selected parameters are packed in chip order and each component’s
own declared parameter order, not mapping or tuple order.
initial_params
/ final_params
contain only the selected (free) parameters.
max_hilbert_dim (int) – Above this total Hilbert-space size the fit switches from
dressing the whole chip to dressing one-hop subsystems per
target (see quchip.inverse_design.subsystems).
seed_strength_bounds (tuple[float, float]) – (lo,hi) magnitude bounds for the bare-coupling-strength
seed root solve (_estimate_bare_g()) used for chi/zz
coupling targets. The target observable must be bracketed by the
values at these two endpoints, or seeding raises
ValueError rather than silently returning a saturated
endpoint.
max_nfev (int) – Maximum number of residual evaluations for the SciPy
Trust-Region Reflective solver.
ValueError – A "chi" coupling target does not have exactly one
computational endpoint, or a chi/zz seed’s target
observable is not bracketed within seed_strength_bounds; a
fit_parameters key does not resolve to a device or coupling
label on chip, names a parameter the resolved component does
not declare, resolves the same label twice, or is a bare string
rather than a name collection; or fit_parameters selects zero
free parameters overall.
Warns:
UserWarning – The number of free parameters exceeds the number of target
residuals (underdetermined by count). This is a necessary, not
sufficient, identifiability condition: it does not analyze the
Jacobian’s rank, so a count-sufficient fit can still be
practically underdetermined.
Notes
Residuals are normalized by max(|target|,1e-9) so every anchor
contributes on equal relative-error footing. A coupling’s scalar
strength bounds are symmetric around zero — the sign of a
capacitive-type coupling is physical and must not be constrained.
The solver’s convergence tolerances (ftol/xtol/gtol =
1e-11) and its x_scale floor (1e-3, applied per parameter
as max(abs(x0),1e-3)) are fixed fitter policy, not exposed as
options.
Two identifiability hazards the count check above does not catch.
The free-parameter-vs-residual count is necessary but not sufficient: it
cannot detect a flat Jacobian direction — a free parameter no target
observable actually responds to — which stays underdetermined regardless
of the count. And a custom DeviceModel
whose tunable_param_names is discovered (the derived default, not an
explicit declaration) is not automatically fit-ready: an unbounded
parameter still needs a tunable_param_bounds()
rule before the optimizer can search it.
JAX traceability. When the chip uses a JAX-native backend, the
complete parameter-to-residual map and its exact Jacobian are
JAX-traceable; SciPy receives their concrete values for bounded
trust-region control. The optimizer itself is not differentiated.
Other backends retain SciPy’s numerical Jacobian. The returned chip
remains fully traceable and differentiable in either case.
Fitted chip — a clone of the seed with updated
freq/anharmonicity and coupling-strength values. The seed
chip is never mutated. Exposing .chip makes this satisfy
ChipTransform structurally,
with no inheritance required.
1-D numpy array holding [loss_initial,loss_final].
A compact record of how far the solver moved; it is not a
per-iteration trace because scipy.optimize.least_squares
does not expose one.
{parameter_name:fitted_value} — the optimizer output.
Parameter names follow "<device>.freq",
"<device>.anharmonicity", and
"<coupling>.<coupling_strength_name>" — "<coupling>.g" for
Capacitive, "<coupling>.g_0"
for TunableCapacitive,
"<coupling>.chi" for CrossKerr.
scipy solver metadata (method, status,
message, nfev, jacobian), plus the identifiability
receipt recorded for every fit_a_dress()
call: n_free_parameters (length of final_params),
n_target_residuals (length of final_targets), and
underdetermined_by_count (True when the former exceeds the
latter — a necessary, not sufficient, identifiability condition; no
Jacobian-rank analysis is performed). jacobian is "jax"
when a JAX-native backend supplies the exact residual Jacobian and
"finite-difference" otherwise.
Target locator — a device label for single-device observables,
a (label_a,label_b) tuple for pair observables, or a
coupling label for coupling-keyed observables.
Extract CR effective Hamiltonian coefficients from Bloch tomography data.
Fits the target-qubit Bloch trajectory under a CR pulse to the
analytic model in bloch_model() — once with the control qubit in
|0⟩ and once with it in |1⟩ — and combines the two fits to isolate the
six coefficients {IX, IY, IZ, ZX, ZY, ZZ}.
The decomposition is:
p_ctrl0 = (I/2) part → ω_IX, ω_IY, ω_IZ
p_ctrl1 = (Z/2) part → ω_ZX, ω_ZY, ω_ZZ
durations ((N,) ndarray) – CR pulse durations in ns (the package convention). Must be
monotone; need not be equally spaced. Converted once to seconds at
the function boundary for the fit.
ctrl0 (dict or 2D ndarray) – Target-qubit Bloch trajectory with control in |0⟩, as a dict with
keys "x", "y", "z" or a 2D array shaped (3,N) or
(N,3) (see _as_xyz()).
ctrl1 (dict or 2D ndarray) – Same with control in |1⟩.
sigma_ctrl0 (dict with keys "x", "y", "z" (optional)) – Per-point standard deviations for the control-|0⟩ data (used as
inverse weights in the least-squares fit).
sigma_ctrl1 (dict with keys "x", "y", "z" (optional)) – Same for the control-|1⟩ data.
t_offset (float) – Subtract this offset (in ns) from durations before fitting
(useful to exclude a pulse-ramp transient).
Returns:
Six coefficients in GHz (package units contract; durations are
taken in ns and the Hz-valued fit internals are converted exactly
once at this boundary), each with a one-sigma uncertainty.
Return the dressed weak-drive CR response of one directed edge.
The analysis projects the physical control-line operator onto the target’s
dressed 0->1 transition twice: once with the control in |0> and
once in |1>. No pulse, rotating-frame solve, or time evolution is
performed.
Parameters:
chip (Chip) – Coupled chip with attached control equipment.
control (str | 'BaseDevice') – Directed CR control and target, supplied as device objects or labels.
target (str | 'BaseDevice') – Directed CR control and target, supplied as device objects or labels.
drive (str | 'BaseDrive' | None) – Control line to project. When omitted, the unique wired device-target
line attached to control is selected.
Returns:
Conditional matrix elements and the corresponding IX and ZX
coefficients per unit programmed amplitude.
ValueError – If the edge is a self-edge, control equipment is absent, implicit
drive resolution is ambiguous, or the selected line does not target
the control.
References
Magesan and Gambetta, Phys. Rev. A 101, 052308 (2020).
Malekakhlagh, Magesan, and McKay, Phys. Rev. A 102, 042605 (2020).
Compute closed-form steady-state readout figures of merit from (chi,kappa).
Parameters:
chi (Any) – Dispersive pull χ_pull =f_r(qubit|1⟩)−f_r(qubit|0⟩) in GHz —
2× the σ_z-convention χ. Take it from
eliminate(...).effective_params[qubit]["chi"].
kappa (Any) – Resonator linewidth (rate) in 1/ns, e.g. effective_params[...]["kappa"].
n_photons (Any) – Target steady-state photon number with the qubit in |0⟩. Exactly one
of n_photons and eps must be given; the drive rate is then
ε=√(n̄₀·((κ/2)²+δ₀²)).
eps (Any) – Readout drive rate in rad/ns (power-user path; exactly one of
n_photons and eps).
delta_r (Any) – Detuning of the qubit-in-ground resonator from the drive,
delta_r=f_r|0−f_drive in GHz — the literature’s
Δ_r=ω_r−ω_d. 0.0 drives on the qubit-in-ground resonance;
positive values place the drive belowf_r|0.
n_crit (Any) – Critical photon number Δ²/(4g²). When given, the strong-drive
collapse chi_eff=chi/(1+n̄₀/n_crit) is applied and
validity reports n_over_ncrit/below_ncrit.
levels (int) – Number of qubit levels to compute pointer states for (static Python
int — it fixes array shapes; 3 includes |f⟩). The pull of level
j is the linear-dispersive chi·j.
Compute exact static ZZ between two devices, plus its 2nd-order SW pathway attribution.
zz is dispersive_shift(), unchanged —
the exact, all-orders residual coupling. pathways decomposes the
2nd-order SW correction to the (1_a,1_b) diagonal energy into its
virtual-intermediate-state contributions
(pathway_attribution()), read off a partition that
keeps only the four computational states of (a,b) (every other
device grounded) — the natural loss primitive for a calibration sweep
that holds zz near zero while some other exchange (e.g. a
eliminate()-mediated J) stays on
target.
Compute the des-Cloizeaux effective Hamiltonian on a user-chosen computational subspace.
Reuses the chip’s dressed spectrum
(_compute_array_labeled()) rather
than re-diagonalizing: one full-chip diagonalization drives both this and
dispersive_shift(). See
EffectiveHamiltonianResult for the construction and its exactness
guarantee.
subspace (mapping or sequence) – {device:levels} keeps range(levels) of each named device
(spectators grounded); a bare sequence of devices keeps each one’s
full qubit (Fock 0/1) subspace (spectators grounded). Both accept a
device label string or the object itself.
Store the six CR effective Hamiltonian coefficients in GHz, with uncertainties.
All six quantities are ordinary frequency (not angular); multiply by
2π to convert to rad/ns. The convention matches Sheldon et al.
(PRA 93, 060302(R), 2016):
H_eff = (I ⊗ A + Z ⊗ B) / 2
where A = ω_IX X + ω_IY Y + ω_IZ Z and B = ω_ZX X + ω_ZY Y + ω_ZZ Z.
params_ctrl0 / params_ctrl1 hold the raw per-control-state fit
output [px,py,pz,td,bx,by,bz] and are diagnostics only: they are
in the fit’s internal units (px/py/pz in Hz, td in seconds), not converted.
Store steady-state dispersive-readout figures of merit.
All numeric fields stay in the array namespace of the inputs (JAX in, JAX
out), so the whole result traces under jax.jit/grad. validity
holds {"n_over_ncrit","below_ncrit"} when n_crit was given —
below_ncrit is then a traced boolean under jit/grad; read it outside
the traced region or branch with jnp.where — and is empty otherwise.
chi here is χ_pull (f_r|1−f_r|0, 2× the σ_z-convention χ).
Store the des-Cloizeaux effective Hamiltonian on a labeled computational subspace.
h_eff is built as S^-1/2(WEW^dagger)S^-1/2 with W the
overlap block between the requested bare states and their assigned
dressed states, E the labeled dressed energies, and S=WW^dagger
the (generally non-orthonormal) overlap Gram matrix — the symmetric
(Löwdin) orthonormalization of exact_reduction()’s
pairwise construction, generalized to an arbitrary number of kept states.
S^-1/2W is unitary by construction, so h_eff is unitarily similar
to diag(E): its eigenvalues are exactly the labeled dressed energies,
to numerical precision, regardless of how strongly the kept states
hybridize with the rest of the chip. Off-diagonal entries carry the
effective couplings between kept states; the diagonal is not, in
general, individually equal to any one dressed energy once couplings mix
the kept states.
(bare_occupation,amount) pairs: the contribution of each virtual
intermediate state to the 2nd-order SW correction of the (1_a,1_b) diagonal matrix element, amount=1/2*V_ik*V_ki*(1/(E_i-E_k)+1/(E_i-E_k)) for i the (1_a,1_b) bare index —
a decomposition of that one energy correction, not of zz itself
(which combines four dressed energies exactly). bare_occupation
is a full chip-length Fock tuple, in device order.
Name of the third-party library this mapping exports for. Defaults
to source.split(".")[0] when source is set; export-only
mappings must set it explicitly.
under (library,target); overriding without setting target
raises TypeError, overriding without a resolvable library
(no source to default it from) raises TypeError, and a
second subclass reusing the same (library,target) pair raises
TypeError naming both classes.
Device wrapping constant eigenenergies and eigenbasis operator matrices.
Parameters:
energies (array_like) – 1-D array of eigenenergies, any offset (stored ground-shifted so
\(E_0 = 0\)). Length sets the native-basis dimension.
charge_operator (array_like, optional) – Charge-like coupling operator \(\hat n\), expressed in the same
eigenbasis as energies, shape (len(energies),len(energies)).
Omit if the source model has no charge-like operator to hand over.
phase_operator (array_like, optional) – Phase-like coupling operator, expressed in the same eigenbasis as
energies, same shape convention as charge_operator.
levels (int, optional) – Truncated eigenbasis size. Defaults to len(energies); must not
exceed it.
source_type (str or None) – Free-form identifier of the originating third-party model (e.g.
"scqubits.ZeroPi"), surfaced in physics_notes() and
preserved through serialization. Purely descriptive.
ValueError – If levels exceeds len(energies), or if charge_operator /
phase_operator is supplied with the wrong shape.
Notes
Since the eigenbasis is the native basis here, _native_charge_operator
and _native_phase_operator return the stored matrix directly (or raise
if it was never supplied, at the point a drive first asks for it) rather
than computing one from circuit parameters.
Bare parameters this device exposes as differentiable / tunable
scalars. fit_a_dress walks this tuple to discover what it is
allowed to optimize on each device, decoupling the inverse-design
surface from any specific device model. Three states, keyed on
whether the value is explicitly declared:
No explicit declaration anywhere in theDeviceModellineage — the
default is derived: every declared
parameter() field, in
declaration order (see DeviceModel.__init_subclass__).
Explicit tuple on the class or an ancestor — exact curation,
validated at class-definition time; authoritative and inherited
until a subclass explicitly replaces it.
Explicit empty tuple — deliberately freezes the device (and its
subclasses, until one replaces it) out of inverse design.
On a plain (non-DeviceModel) BaseDevice subclass there is
no derivation; the default stays empty unless the subclass declares
its own tuple — e.g. Fluxonium uses
("E_C","E_J","E_L","phi_ext").
Convert a scqubits object into the matching quchip device.
Dispatches on type(obj) through the ModelMapping registry.
Keyword options (levels, label, noise parameters) are forwarded to
the mapping’s import_model.
Convert a quchip device into the matching scqubits object.
Dispatches on type(device_or_chip) through the export registry for the
"scqubits" library. Parameters must be concrete: a device carrying JAX
tracers (inside jit/grad) raises ValueError.
A Chip exports to an scqubits HilbertSpace
(devices as subsystems, couplings as interaction terms); see
export_chip().
Plot dressed chip eigenenergies relative to the ground state.
Each level is rendered as a horizontal bar annotated with its dressed
represented-basis tuple label |n_1n_2...> — the per-device
bare-basis assignment selected by the chip’s dressing analysis (see
chip.analysis). The y-axis is energy in GHz; the ground state is
shifted to zero.
Parameters:
chip (Chip) – The chip whose dressed spectrum should be plotted.
ax (matplotlib.axes.Axes, optional) – Existing axes to draw onto. When None a new figure is created.
max_states (int, optional) – How many of the lowest-lying levels to show. Defaults to
min(12,total_levels).
Returns:
The figure holding the energy-ladder axes (ax.figure when
ax was given).
The x-axis is time (ns); the y-axis is
observable_traces[key].values — the post-processed recorded trace for each
observable O registered in the solver’s e_ops dict. This is
not unconditionally Tr(Orho(t)): depending on how the
observable was requested, .values may already include
demodulation, phase correction, or band summation (see
ObservableTrace; its .raw
field holds the pre-processing quantity instead). When real is
True (the default) only Re of the trace is drawn; when
False both the real part (solid) and imaginary part (dashed,
lower alpha) are drawn in the same colour per key.
keys (list, optional) – Each entry is either a bare key ("cav") or a (key,index)
tuple selecting one element of a list-valued observable, matched
against the registered observable_traces keys first — see
_collect_expectation_traces(). String and device/drive
keys are resolved with resolve_label so both are accepted
interchangeably. Defaults to every registered trace.
ax (matplotlib.axes.Axes, optional) – Existing axes to draw onto. When None a new figure is created.
Write an interactive HTML chip topology and return its path.
The rendered graph is a standalone, offline-capable HTML file: devices
are drawn as dots and drives as diamonds, with couplings, control
wiring, and crosstalk distinguished by edge colour and dash pattern.
Node colours are auto-assigned per class (DuffingTransmon,
Resonator, ChargeDrive, …) so new device/drive kinds are
visually distinct without user intervention.
Layouts:
"force_atlas" (default) — force_atlas_2based physics with
larger/heavier computational devices so the register sits centrally
and auxiliary couplers/drives orbit.
"hierarchical" — pyvis hrepulsion on explicit levels
(computational devices → other devices/couplings → drives), useful
for chips with many auxiliary drives or cross-resonance lines.
exclude takes priority over full; passing full=False with no
exclude hides drives and crosstalk (chip-only view). Every coupling
is rendered as its own junction node splitting the device-device edge
(so an edge-pump control has a node to attach to); excluding
couplings also removes any edge-pump controls that would otherwise
dangle (see _collect_topology()).
The figure has time (ns) on the x-axis and population
p_n(t)=Tr(|n><n|rho(t)) on the y-axis, one line per
represented-basis ket |n>=|n_1n_2...> of the retained
subsystems. States whose peak population stays below threshold are
hidden; set threshold to 0 to show every state.
trace_out (device, label, or list thereof, optional) – Subsystems to partial-trace over before computing populations.
Accepts either device objects or their string labels (UX favourability).
Requires options={"store_states":True} on the solver call.
computational (bool) – When True, restricts computational subsystems to their
{|0>,|1>} subspace.
ax (matplotlib.axes.Axes, optional) – Existing axes to draw onto. When None a new figure is created.
linewidth (float) – Line width for every population trace.
legend (bool) – Whether to draw a legend of the visible states.
colors (dict, optional) – Per-state colour overrides, keyed by the same basis-state tuples
used internally; unlisted states fall back to a tab20 cycle.
threshold (float) – Populations whose time-max falls below this value are omitted.
Returns:
The figure holding the population-trace axes (ax.figure when
ax was given).
Return type:
Figure
Raises:
RuntimeError – trace_out is given but no states were stored (pass
options={"store_states":True} to the solver).
ValueError – trace_out would remove every subsystem.
Each row (“lane”) is a (target_label,drive_label) channel; each
bar is one scheduled envelope, drawn from its start_time to
start_time+envelope.duration on the x-axis (ns), annotated with
the envelope class name and carrier frequency in GHz. Idle channels
that were created (channel_cursors advanced) but never scheduled
show up as empty lanes.
"population" — a bar chart of diagonal elements p_n.
"dm" — side-by-side heatmaps of Re(rho) and Im(rho);
the colormap cmap is divergent, and both heatmaps share one
symmetric normalization (vmin=-m,vmax=+m for
m=max(|Re(rho)|,|Im(rho)|)) so their colours are directly
comparable — an entry that looks equally saturated in both panels
really is equal in magnitude.
When both computational and non-computational subsystems are present,
pass trace_out to focus on a target register and/or computational=True to restrict to the {|0>,|1>} subspace on computational
devices (see Nielsen & Chuang, Ch. 2).
index (int) – Stored-time index to plot. Supports Python-style negative
indexing (-1 is the last stored time); must satisfy
-N<=index<N for N=len(result.times).
trace_out (device, label, or list thereof, optional) – Subsystems to partial-trace over before plotting.
computational (bool) – When True, restricts computational subsystems to their
{|0>,|1>} subspace.
mode ({"population", "dm"}) – Which representation to draw.
ax (matplotlib.axes.Axes, optional) – For mode="population": a single axes (or None for a new
figure). For mode="dm": an iterable of exactly two axes
(real_ax,imag_ax) (or None for a new 1x2 figure).
cmap (str) – Divergent colormap for mode="dm" heatmaps.
color (str, optional) – Bar colour override for mode="population". Defaults to a
per-state tab10 cycle.
Returns:
The figure holding the plotted axes (ax.figure when ax
was given).
Return type:
Figure
Raises:
IndexError – index is outside [-N,N) for N=len(result.times).
ValueError – mode is not "population" or "dm", or trace_out
would remove every subsystem.
RuntimeError – No states were stored (pass options={"store_states":True}
to the solver).
quchip.plot_wavefunction(device, n, *, ax=None, color=None)[source]¶
Plot the represented-basis probability weights of eigenstate n.
Shows |<k|psi_n>|^2 for each bare basis state |k>, k=0...levels-1, where |k> indexes whatever basis
device.hamiltonian() is expressed in. Every stock device model
shipped with quchip — including DuffingTransmon,
ChargeBasisTransmon, Fluxonium, and Resonator —
returns a Hamiltonian that is already diagonal in its own retained
eigenbasis, so eigenstate n is exactly |n> and the bar chart
is a single delta bar for every one of them: this plot cannot show
“mixing” for any built-in model. Mixing becomes visible only for a
custom device whose hamiltonian() returns a matrix that is
not diagonal in its represented basis.
Parameters:
device (BaseDevice) – The device whose eigenstates are diagonalised.
Plot the Wigner quasi-probability distribution of a stored state.
Axes are the phase-space quadratures x (position-like) and
p (momentum-like); the colourmap is divergent and symmetric about
zero so negative regions — the hallmark of non-classical states —
stand out directly.
When xvec is not supplied the plot window is auto-sized from the
mean photon number <n>=Tr(rhon_hat) of the reduced state
(computed directly from diag(rho) to avoid an O(d^2) matmul for
a diagonal-only observable), extending to at least +/-3.
Exactly one subsystem must remain after trace_out — a Wigner
function is a single-mode phase-space picture, and its basis indices
are interpreted directly as photon numbers n=0,1,2,....
This is checked; what is not, and cannot be, checked from result
metadata alone is the remaining precondition: the retained
subsystem’s represented basis must actually be a photon-number
ladder (true for a bosonic mode such as Resonator, false for a
device whose represented basis is not Fock, e.g. a charge- or
flux-basis qubit) — passing such a device silently produces a
Wigner-shaped plot with no such physical meaning.
index (int) – Stored-time index to plot. Supports Python-style negative
indexing (-1, the default, is the last stored time); must
satisfy -N<=index<N for N=len(result.times).
trace_out (device, label, or list thereof, optional) – Subsystems to partial-trace over before plotting. Required
whenever more than one subsystem is stored — see Raises.
xvec (ndarray, optional) – Phase-space grids for the x/p quadratures. Defaults to
an auto-sized, evenly spaced grid (see above); yvec defaults
to xvec when only xvec is given.
yvec (ndarray, optional) – Phase-space grids for the x/p quadratures. Defaults to
an auto-sized, evenly spaced grid (see above); yvec defaults
to xvec when only xvec is given.
ax (matplotlib.axes.Axes, optional) – Existing axes to draw onto. When None a new figure is created.
cmap (str) – Divergent colormap, symmetric about zero.
The figure holding the Wigner-function axes (ax.figure when
ax was given).
Return type:
Figure
Raises:
IndexError – index is outside [-N,N) for N=len(result.times).
ValueError – More or fewer than one subsystem remains after trace_out; the
message lists the retained device labels and a trace_out
value that isolates a single one of them.
RuntimeError – No states were stored (pass options={"store_states":True}
to the solver).