quchip

Public package surface for quchip.

This module re-exports the primary user-facing objects that make up a quchip study: device models (DuffingTransmon, Resonator, …), chip topology and analysis (Chip, Capacitive, DressedResult, …), classical control (drives, envelopes, sequences, crosstalk), the engine entry points (simulate(), build_problem(), solve_problem(), solve_many()), backend selection helpers, backend-agnostic result containers, sweep helpers (Sweep, SpectrumSweep), post-hoc analysis (effective_hamiltonian(), analyze_cross_resonance(), …), the immutable physical constants, and enable_compilation_cache().

Visualization helpers and optional third-party interop (pyvis, scqubits, matplotlib-based plots, …) are loaded lazily through the module-level __getattr__() so that import quchip stays fast and does not force any optional dependency on the core install.

Functions

enable_compilation_cache([path, ...])

Enable JAX's on-disk persistent compilation cache (opt-in).

class quchip.CouplingModel(device_a, device_b, *, label=None, rwa=None, **params)[source]

Bases: BaseCoupling

Declarative two-body coupling base.

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.

Examples

>>> from quchip.declarative import CouplingModel, parameter, Scalar
>>> class ExchangeCoupling(CouplingModel):
...     g: Scalar = parameter(unit="GHz")
...     def interaction(self, a, b):
...         return self.g * (a.a * b.adag + a.adag * b.a)
>>> c = ExchangeCoupling("q0", "q1", g=0.01)
>>> c.coupling_strength
0.01
Parameters:
  • device_a (Any)

  • device_b (Any)

  • label (str | None)

  • rwa (bool | None)

  • params (Any)

property coupling_strength: Any

Primary scalar coupling strength, defaulting to the first parameter.

property coupling_strength_name: str

Display name of coupling_strength, defaulting to the first parameter field.

interaction(a, b)[source]

Return the full two-body interaction expression.

Parameters:
  • 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).

Return type:

PhysicsExpr

time_dependent(a, b)[source]

Return an optional time-dependent interaction expression.

Parameters:
  • a (EndpointOps) – Operator namespaces for the two coupled endpoints.

  • b (EndpointOps) – Operator namespaces for the two coupled endpoints.

Returns:

An expression carrying exactly one dynamic source (envelope or modulation), or None when the coupling is purely static.

Return type:

PhysicsExpr or None

rwa_time_dependent(a, b)[source]

Return the RWA time-dependent expression, defaulting to full form.

Mirrors the parametric RWA split for the dynamic term: subclasses whose parametric drive keeps only the co-rotating operators under RWA override this.

Parameters:
  • a (EndpointOps) – Operator namespaces for the two coupled endpoints.

  • b (EndpointOps) – Operator namespaces for the two coupled endpoints.

Return type:

Any

parametric_interaction(a, b)[source]

Return the parametric interaction structure, or None when this coupling is not modulable.

The coupling-side mirror of the device drive-dispatch protocols: a ParametricDrive accepts any coupling whose hook returns a PhysicsExpr.

Parameters:
Return type:

Any

rwa_parametric_interaction(a, b)[source]

RWA-retained parametric structure; defaults to the full form.

Parameters:
Return type:

Any

parametric_operator(chip)[source]

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()).

Parameters:

chip (Any)

Return type:

Any | None

interaction_hamiltonian()[source]

Compile the full interaction expression for the default backend.

Return type:

Any

classmethod from_dict(d, device_a, device_b)[source]

Reconstruct a coupling from to_dict() output.

Default implementation: forward declared parameters straight into __init__. Subclasses with bespoke serialization (e.g. envelope modulations) override this.

Parameters:
Return type:

CouplingModel

to_dict()[source]

Serialize common coupling state plus declared parameter values.

Return type:

dict[str, Any]

dynamic_interaction_terms(chip)[source]

Compile the optional single-source dynamic interaction term.

Parameters:

chip (Chip) – Owning chip, consulted via Chip.resolve_rwa() to select the static or RWA operator structure of the dynamic term.

Returns:

One (static_operator, scalar_modulation) pair when time_dependent() returns an expression, else an empty list.

Return type:

list of (operator, modulation)

class quchip.DeviceModel(*, levels=2, label=None, T1=None, T2=None, thermal_population=None, **params)[source]

Bases: BaseDevice

Declarative base for physics device models.

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.

Examples

>>> from quchip.declarative import DeviceModel, parameter, Scalar
>>> class DuffingOscillator(DeviceModel):
...     freq: Scalar = parameter(positive=True, unit="GHz")
...     anharmonicity: Scalar = parameter(unit="GHz")
...     def local_hamiltonian(self, op):
...         return self.freq * op.n + 0.5 * self.anharmonicity * op.n @ (op.n - op.I)
>>> device = DuffingOscillator(freq=5.0, anharmonicity=-0.3, levels=4)
>>> device.freq
5.0
Parameters:
  • levels (int)

  • label (str | None)

  • T1 (Any)

  • T2 (Any)

  • thermal_population (Any)

  • params (Any)

approximation: str | None = None

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.

computational: bool = False

Whether this device represents a computational qubit, as opposed to e.g. a bus resonator or a coupler element.

validate()[source]

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.

Return type:

None

local_hamiltonian(op)[source]

Return this device’s local Hamiltonian as a declarative expression.

Parameters:

op (LocalOps) – Operator namespace for this device’s endpoint, exposing a, adag, n, I and the Pauli handles as composable PhysicsExpr nodes.

Returns:

The local Hamiltonian expression, in ordinary-frequency units (GHz).

Return type:

PhysicsExpr

hamiltonian()[source]

Compile local_hamiltonian() for the active default backend.

Return type:

Any

to_dict()[source]

Serialize common device state plus declared parameter values.

Return type:

dict[str, Any]

classmethod from_dict(d)[source]

Reconstruct the device from to_dict() output.

Parameters:

d (dict[str, Any])

Return type:

DeviceModel

physics_notes()[source]

Return base device notes plus the declared approximation, if any.

Return type:

list[str]

class quchip.EnvelopeShape(**params)[source]

Bases: BaseEnvelope

Declarative base for pulse envelope shapes.

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.

Examples

>>> from quchip.declarative import EnvelopeShape, parameter, Scalar
>>> class LinearRise(EnvelopeShape):
...     duration: Scalar = parameter(positive=True)
...     amplitude: Scalar = parameter(default=1.0)
...     def value(self, t):
...         return self.amplitude * (t / self.duration)
>>> env = LinearRise(duration=20.0, amplitude=0.5)
>>> float(env.value(10.0))
0.25
Parameters:

params (Any)

validate()[source]

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.

Return type:

None

value(t)[source]

Evaluate the envelope at time points t in ns.

Parameters:

t (Any) – Time or array of times in ns. May be a JAX tracer; the implementation must stay traceable (use quchip.declarative.qnp).

Returns:

The complex envelope value at t, matching the shape of t.

Return type:

Any

waveform(t, *, xp=None)[source]

Evaluate value() through the BaseEnvelope waveform contract.

Parameters:
  • t (Any) – Time or array of times in ns, coerced to an array before dispatch.

  • xp (module or None, optional) – Array-namespace hint from the caller; ignored here since value() selects its own namespace via quchip.declarative.qnp.

Returns:

The complex envelope value at t.

Return type:

Any

to_dict()[source]

Serialize the envelope type and declared parameter values.

Return type:

dict[str, Any]

classmethod from_dict(d)[source]

Reconstruct the envelope from to_dict() output.

Parameters:

d (dict[str, Any])

Return type:

EnvelopeShape

class quchip.Parameter(default=<object object>, positive=False, nonnegative=False, serialize=True, unit=None)[source]

Bases: object

Metadata for a declarative model parameter field.

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.

Parameters:
default: Any = <object object>
positive: bool = False
nonnegative: bool = False
serialize: bool = True
unit: str | None = None
property has_default: bool

Return whether this field has a declared default value.

quchip.Scalar

alias of Any

quchip.Modulation

alias of Any

quchip.parameter(*, default=<object object>, positive=False, nonnegative=False, serialize=True, unit=None)[source]

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__.

  • positive (bool, optional) – Reject concrete values <= 0. Traced values pass unchecked.

  • nonnegative (bool, optional) – Reject concrete values < 0. Traced values pass unchecked.

  • serialize (bool, optional) – Include the field in to_dict() output.

  • unit (str or None, optional) – Display-only unit label (e.g. "GHz").

Return type:

Any

Examples

>>> from quchip.declarative import DeviceModel, parameter, Scalar
>>> class Oscillator(DeviceModel):
...     freq: Scalar = parameter(positive=True, unit="GHz")
...     def local_hamiltonian(self, op):
...         return self.freq * op.n
>>> Oscillator(freq=5.0, levels=3).freq
5.0
class quchip.ChargeBasisTransmon(E_C, E_J, n_g=0.0, levels=3, label=None, *, num_basis=61, collapse_model='fermi_golden', coupling_channel=None, collapse_rate_threshold=1e-08, **noise_kwargs)[source]

Bases: CircuitDevice

Transmon in the integer charge basis — exact diagonalization.

Parameters:
  • E_C (float) – Charging energy in GHz. Must be positive. May be a JAX tracer.

  • E_J (float) – Josephson energy in GHz. Must be positive. May be a JAX tracer.

  • n_g (float, default 0.0) – Offset charge (units of \(2e\)). May be a JAX tracer.

  • levels (int, default 3) – Truncated eigenbasis size.

  • label (str or None) – Auto-generated as charge_basis_transmon_{n} if omitted.

  • num_basis (int, default 61) – Charge-basis cutoff — must be odd, corresponding to \(n \in [-n_\mathrm{cut}, +n_\mathrm{cut}]\) with \(n_\mathrm{cut} = (\text{num\_basis} - 1)/2\).

  • collapse_model ("fermi_golden" or "ladder", default "fermi_golden") – See CircuitDevice.

  • collapse_rate_threshold (float, default 1e-8) – See CircuitDevice.

  • **noise_kwargs – Forwarded to BaseDevice (T1, T2, thermal_population).

  • coupling_channel (Literal['charge', 'flux'] | None)

Notes

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'.

tunable_param_names = ('E_C', 'E_J', 'n_g')

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 the DeviceModel lineage — 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").

approximation = 'Exact diagonalization in the truncated integer charge basis; accuracy governed by num_basis.'

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.

tunable_param_bounds(name, value)[source]

n_g lives in [-0.5, 0.5] (one charge period); other params delegate.

Parameters:
Return type:

tuple[float, float]

physics_notes()[source]

Return declared integer-charge-basis assumptions.

Return type:

list[str]

classmethod from_frequency(freq, anharmonicity, n_g=0.0, levels=3, label=None, *, num_basis=61, collapse_model='fermi_golden', coupling_channel=None, collapse_rate_threshold=1e-08, **noise_kwargs)[source]

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\).

Parameters:
  • freq (float)

  • anharmonicity (float)

  • n_g (float)

  • levels (int)

  • label (str | None)

  • num_basis (int)

  • collapse_model (Literal['fermi_golden', 'ladder'])

  • coupling_channel (Literal['charge', 'flux'] | None)

  • collapse_rate_threshold (float)

  • noise_kwargs (float | None)

Return type:

ChargeBasisTransmon

property computational: bool

Charge-basis transmon is a computational qubit.

to_dict()[source]

Extend CircuitDevice.to_dict() with the charge-basis circuit parameters.

Return type:

dict[str, Any]

classmethod from_dict(d)[source]

Reconstruct from to_dict() output.

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.

Parameters:

d (dict[str, Any])

Return type:

ChargeBasisTransmon

class quchip.CircuitDevice(levels, label=None, *, collapse_model='fermi_golden', coupling_channel=None, collapse_rate_threshold=1e-08, **noise_kwargs)[source]

Bases: BaseDevice

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().

Subclasses must implement:

  • _build_native_hamiltonian()

  • _native_charge_operator()

  • _native_phase_operator()

Parameters:
  • levels (int)

  • label (str | None)

  • collapse_model (Literal['fermi_golden', 'ladder'])

  • coupling_channel (Literal['charge', 'flux'] | None)

  • collapse_rate_threshold (float)

  • noise_kwargs (float | None)

approximation: str | None = None

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.

hamiltonian()[source]

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 type:

Any

eigenenergies()[source]

Return the truncated eigenvalue array, shape (levels,), with \(E_0 = 0\).

Return type:

Any

eigenvectors()[source]

Return the truncated eigenvector matrix V, shape (num_basis, levels).

Return type:

Any

project_operator(native_op)[source]

Transform a native-basis operator O into the truncated eigenbasis.

Returns \(V^\dagger O V\).

Parameters:

native_op (Any)

Return type:

Any

property freq: Any

Bare 0→1 transition frequency \(E_1 - E_0\) (GHz).

charge_coupling_operator()[source]

Return the physical charge operator \(V^\dagger \hat n V\) in the eigenbasis.

Returned as a dense, trace-safe array-like (see quchip.devices.protocols); backend composition entry points coerce it to native form on use.

Return type:

Any

phase_coupling_operator()[source]

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.

Return type:

Any

physics_notes()[source]

Return declared diagonalization and collapse-model assumptions.

Return type:

list[str]

to_dict()[source]

Extend BaseDevice.to_dict() with circuit-level collapse params.

Return type:

dict[str, Any]

class quchip.DuffingTransmon(freq, anharmonicity, *, levels=3, label=None, T1=None, T2=None, thermal_population=None)[source]

Bases: DeviceModel

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 BaseDeviceT1, T2, thermal_population.

Example

>>> from quchip.devices import DuffingTransmon
>>> q = DuffingTransmon(freq=5.0, anharmonicity=-0.25, T1=30_000.0, T2=20_000.0)
>>> len(q.collapse_operators()) >= 1
True
tunable_param_names = ('freq', 'anharmonicity')

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 the DeviceModel lineage — 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").

approximation = 'Duffing expansion: cosine Josephson potential truncated at 4th order.'

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.

computational = True

Whether this device represents a computational qubit, as opposed to e.g. a bus resonator or a coupler element.

freq: Scalar = Parameter(default=<object object>, positive=True, nonnegative=False, serialize=True, unit='GHz')
anharmonicity: Scalar = Parameter(default=<object object>, positive=False, nonnegative=False, serialize=True, unit='GHz')
local_hamiltonian(op)[source]

Return the local Duffing Hamiltonian H = omega n + (alpha/2) n (n - I).

Parameters:

op (LocalOps)

Return type:

PhysicsExpr

physics_notes()[source]

Return declared Duffing-approximation validity notes.

Return type:

list[str]

class quchip.FluxTunableTransmon(freq, anharmonicity, flux_bias=0.0, asymmetry=0.0, *, levels=3, label=None, T1=None, T2=None, thermal_population=None)[source]

Bases: DeviceModel

SQUID-dispersion flux-tunable transmon.

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).

  • levels (int, default 3) – Fock-space truncation.

  • label (str | None, default None) – Auto-generated as fluxtunable_{idx} when omitted.

  • **noise_kwargs – Forwarded to BaseDeviceT1, T2, thermal_population.

tunable_param_names = ('freq', 'anharmonicity')

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 the DeviceModel lineage — 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").

computational = True

Whether this device represents a computational qubit, as opposed to e.g. a bus resonator or a coupler element.

approximation = 'Duffing-approximated SQUID transmon; adiabatic flux (calibration-anchor, no Landau-Zener).'

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.

freq: Scalar = Parameter(default=<object object>, positive=True, nonnegative=False, serialize=True, unit='GHz')
anharmonicity: Scalar = Parameter(default=<object object>, positive=False, nonnegative=False, serialize=True, unit='GHz')
flux_bias: Scalar = Parameter(default=0.0, positive=False, nonnegative=False, serialize=True, unit='Phi_0')
asymmetry: Scalar = Parameter(default=0.0, positive=False, nonnegative=False, serialize=True, unit=None)
validate()[source]

Range checks on concrete scalars only; traced values pass unchecked.

Return type:

None

local_hamiltonian(op)[source]

Return the Duffing Hamiltonian built from the calibrated freq and anharmonicity.

H = ω n + (α/2) n(n I). Does not reference flux_bias.

Parameters:

op (LocalOps)

Return type:

PhysicsExpr

frequency_at(flux)[source]

SQUID dispersion ω(Φ/Φ₀) in GHz, using derived E_C and E_J_max.

Parameters:

flux (float) – Reduced flux Φ/Φ₀. JAX-traceable.

Return type:

Any

flux_for_frequency(target_freq)[source]

Inverse SQUID dispersion on the monotonic lobe Φ/Φ₀ ∈ [0, 0.5).

Derivation:

ω(Φ) = sqrt(8 E_C E_J_max sqrt(cos²(πΦ) + d²sin²(πΦ))) − E_C → let S = (ω + E_C)² / (8 E_C E_J_max) → cos²(πΦ)(1 − d²) + d² = S² → cos²(πΦ) = (S² − d²) / (1 − d²)

Raises:

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.

Parameters:

target_freq (Any)

Return type:

Any

physics_notes()[source]

Return declared SQUID-transmon calibration-anchor assumptions.

Return type:

list[str]

class quchip.Fluxonium(E_C, E_J, E_L, phi_ext=0.0, levels=10, label=None, *, num_basis=400, phi_max=None, collapse_model='fermi_golden', coupling_channel=None, collapse_rate_threshold=1e-08, **noise_kwargs)[source]

Bases: CircuitDevice

Fluxonium qubit on a non-periodic plane-wave phase basis.

Parameters:
  • E_C (float) – Charging energy in GHz. Positive. JAX-traceable.

  • E_J (float) – Josephson energy in GHz. Positive. JAX-traceable.

  • E_L (float) – Inductive energy in GHz. Positive. JAX-traceable.

  • phi_ext (float, default 0.0) – External flux in units of \(\Phi_0\) (0.5 = half-flux sweet spot). JAX-traceable.

  • levels (int, default 10) – Truncated eigenbasis size.

  • label (str or None)

  • num_basis (int, default 400) – Phase-basis grid points.

  • phi_max (float, default 5 * pi) – Phase grid half-range; grid is [-phi_max, +phi_max).

  • 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.

tunable_param_names = ('E_C', 'E_J', 'E_L', 'phi_ext')

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 the DeviceModel lineage — 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").

approximation = 'Exact diagonalization on a finite phase grid; 2nd-order central finite differences for the kinetic term; accuracy governed by num_basis.'

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.

flux_coupling_operator()[source]

Return the flux-line coupling operator \(V^\dagger \hat\varphi V\).

Return type:

Any

physics_notes()[source]

Return declared phase-basis discretization assumptions.

Return type:

list[str]

property computational: bool

Fluxonium is a computational qubit.

to_dict()[source]

Extend CircuitDevice.to_dict() with the fluxonium circuit parameters.

Return type:

dict[str, Any]

classmethod from_dict(d)[source]

Reconstruct from to_dict() output.

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.

Parameters:

d (dict[str, Any])

Return type:

Fluxonium

class quchip.KerrCavity(freq, kerr, *, levels=30, label=None, T1=None, T2=None, thermal_population=None)[source]

Bases: DeviceModel

Kerr-nonlinear resonator supporting cat-qubit stabilisation.

Hamiltonian:

\[H = \omega \, \hat{n} - K \, \hat{n}(\hat{n} - I)\]

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.

  • label (str | None) – Human-readable label. None → auto-generated kerr_cavity_0, kerr_cavity_1, …

  • **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.

References

Examples

>>> from quchip.devices.kerr_cavity import KerrCavity
>>> cav = KerrCavity(freq=5.0, kerr=1.0, levels=10, label="cav")
>>> cav.freq, cav.kerr, cav.levels
(5.0, 1.0, 10)
tunable_param_names = ('freq', 'kerr')

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 the DeviceModel lineage — 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").

approximation = 'Kerr-nonlinear cavity effective single-mode model; SNAIL/STS-SQUID adiabatically eliminated.'

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.

computational = False

Whether this device represents a computational qubit, as opposed to e.g. a bus resonator or a coupler element.

freq: Scalar = Parameter(default=<object object>, positive=True, nonnegative=False, serialize=True, unit='GHz')
kerr: Scalar = Parameter(default=<object object>, positive=False, nonnegative=True, serialize=True, unit='GHz')
local_hamiltonian(op)[source]

Return \(H = \omega \hat{n} - K \hat{n}(\hat{n} - I)\).

The Kerr term \(\hat{n}(\hat{n}-I) = \hat{n}^2 - \hat{n}\) gives eigenvalue contributions \(-K n(n-1)\) for the \(n\)-photon Fock state.

Returns:

Declarative expression for the Hermitian operator H = omega*n - K*n*(n-1) (GHz), diagonal in the Fock basis.

Return type:

PhysicsExpr

Parameters:

op (LocalOps)

physics_notes()[source]

Return declared Kerr-cavity approximation notes.

Return type:

list[str]

class quchip.NoiseChannel(name, params, build)[source]

Bases: object

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.

name: str
params: tuple[str, ...]
build: Callable[[Any], list[Any]]
class quchip.Resonator(freq, quality_factor=None, *, levels=10, label=None, T1=None, T2=None, thermal_population=None)[source]

Bases: DeviceModel

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 BaseDeviceT1, T2, thermal_population.

Example

>>> from quchip.devices import Resonator
>>> r = Resonator(freq=7.2, quality_factor=10_000, levels=8)
>>> len(r.collapse_operators()) >= 1
True
tunable_param_names = ('freq',)

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 the DeviceModel lineage — 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").

freq: Scalar = Parameter(default=<object object>, positive=True, nonnegative=False, serialize=True, unit='GHz')
quality_factor: Scalar = Parameter(default=None, positive=True, nonnegative=False, serialize=True, unit=None)
approximation = 'Linear harmonic oscillator with no Kerr or cross-Kerr self-interaction.'

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.

local_hamiltonian(op)[source]

Return the harmonic oscillator Hamiltonian H = freq * n.

Parameters:

op (LocalOps)

Return type:

PhysicsExpr

physics_notes()[source]

Return declared harmonic-oscillator and dissipation assumptions.

Return type:

list[str]

intrinsic_decay_rate()[source]

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_loss NoiseChannel, 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.

Return type:

Any | None

class quchip.ChargeCoupled(*args, **kwargs)[source]

Bases: Protocol

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().

charge_coupling_operator()[source]

Return the physical charge operator in the truncated eigenbasis.

Return type:

Any

class quchip.PhaseCoupled(*args, **kwargs)[source]

Bases: Protocol

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.

phase_coupling_operator()[source]

Return the physical phase-space coupling operator in the eigenbasis.

Return type:

Any

class quchip.FluxCoupled(*args, **kwargs)[source]

Bases: Protocol

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.

flux_coupling_operator()[source]

Return the physical flux-line coupling operator in the eigenbasis.

Return type:

Any

class quchip.Chip(devices, couplings=None, control_equipment=None, label=None, frame='lab', rwa=True, backend=None, baths=None)[source]

Bases: object

Composite quantum system: devices + couplings + (optional) control.

The chip is the primary user-facing object. It exposes:

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().

  • label (str, optional) – Human-readable chip label.

  • frame (FrameSpec) –

    Initial frame specification:

    • "lab" — all reference frequencies 0 GHz (default).

    • "rotating" — per-device rotating frame at dressed drive frequencies.

    • scalar-like — one shared reference frequency for all devices.

    • dict — per-device references keyed by label or device.

  • rwa (bool) – Default RWA policy for couplings and drives. Per-component rwa overrides inherit this (None means inherit).

  • backend (str or Backend, optional) – Chip-specific backend. None uses the process default.

  • baths (list[Bath] | None)

Examples

>>> from quchip import DuffingTransmon, Resonator, Capacitive, Chip
>>> q = DuffingTransmon(freq=5.0, anharmonicity=-0.25, levels=3, label="q")
>>> r = Resonator(freq=7.0, levels=5, label="r")
>>> chip = Chip([q, r], couplings=[Capacitive(q, r, g=0.05)])
>>> chip.energy({q: 1})  # dressed |1,0⟩ eigenvalue, GHz
hamiltonian()[source]

Lab-frame static Hamiltonian.

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 Σ_i ω_ref,i n̂_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.

Returns:

Backend-native operator on ⨂_d H_d.

Return type:

Operator

dynamic_contributions()[source]

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.

Return type:

list[tuple[Any, Any, tuple[int, …], str, str]]

collapse_contributions()[source]

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().

Return type:

list[tuple[Any, tuple[int, …]]]

property device_map: dict[str, BaseDevice]

Label → device mapping (the chip’s own dict; do not mutate).

property coupling_map: dict[str, BaseCoupling]

Couplings by label, insertion-ordered (do not mutate).

coupling(coupling)[source]

Return a coupling by label or object.

Parameters:

coupling (str | BaseCoupling)

Return type:

BaseCoupling

property backend: Backend

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.

Type:

Active backend

property frame: FrameSpec

Current frame specification (see set_frame()).

property rwa: bool

Default rotating-wave approximation policy for couplings and drives.

property control_equipment: ControlEquipment | None

Attached control equipment, if any.

property devices: tuple[BaseDevice, ...]

Ordered tuple of devices; index matches tensor-product position.

property couplings: tuple[BaseCoupling, ...]

Tuple of couplings in insertion order.

property dims: tuple[int, ...]

Per-device Hilbert-space dimensions (same order as devices).

property total_dim: int

Total Hilbert-space dimension (product of per-device dims).

property baths: tuple[Bath, ...]

Chip-level baths (shared/collective dissipation), insertion order.

add_bath(bath)[source]

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).

Parameters:

bath (Bath)

Return type:

Bath

set_noise(config=None, *, baths=None)[source]

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()).

Return type:

None

property crosstalks: list[Crosstalk]

Convenience view — Crosstalk entries from the signal chain.

device_index(label)[source]

Tensor-product index of a device. Accepts a label string or object.

Parameters:

label (str | BaseDevice)

Return type:

int

plot_graph(path='chip_topology.html', *, full=True, exclude=None, **kwargs)[source]

Render chip topology — delegates to quchip.viz.chip.

Parameters:
Return type:

str

plot_energy_levels(*, ax=None, **kwargs)[source]

Render dressed spectrum — delegates to quchip.viz.chip.

Parameters:
Return type:

Any

set_frame(frame)[source]

Set the frame used when assembling simulation inputs.

Supported values:

  • "lab" — all reference frequencies are 0.0 GHz.

  • "rotating" — per-device references use dressed drive frequencies.

  • scalar-like — shared reference frequency for all devices.

  • dict — per-device references keyed by label or device.

Frame changes never alter dressed-state data — dressing is always computed from the lab-frame static Hamiltonian.

Parameters:

frame (FrameSpec)

Return type:

None

property analysis: ChipAnalysis

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.

dress(*, overlap_threshold=0.5, force=False, labeling='DE')[source]

Compute (or retrieve) the dressed-state decomposition. See ChipAnalysis.dress().

Parameters:
Return type:

DressedResult

property is_dressed: bool

Whether a valid dressed-state result is cached. See ChipAnalysis.is_dressed.

energy(device_states=None, /, **device_state_kwargs)[source]

Dressed eigenenergy (GHz) for a bare-state label. See ChipAnalysis.energy().

Parameters:
Return type:

float

dressed_spectrum()[source]

Raw dressed eigenvalue array without Python scalar coercion. See ChipAnalysis.dressed_spectrum().

Return type:

Any

dressed_index(device_states=None, /, **device_state_kwargs)[source]

Dressed-state index matching a bare-state label, or None. See ChipAnalysis.dressed_index().

Parameters:
Return type:

int | None

bare_label(dressed_index)[source]

Bare-state label assigned to a dressed-state index. See ChipAnalysis.bare_label().

Parameters:

dressed_index (int)

Return type:

tuple[int, …]

operator_in_dressed_basis(device, op, *, truncate=None)[source]

Embedded device operator transformed to the dressed eigenbasis.

See ChipAnalysis.operator_in_dressed_basis().

Parameters:
Return type:

Any

drive_matrix_elements(transition, *, drives=None)[source]

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.

Parameters:
Return type:

LabelKeyedDict

state_components(state=None, /, *, n_components=5, **device_state_kwargs)[source]

Leading bare-basis probabilities for a dressed eigenstate. See ChipAnalysis.state_components().

Parameters:
Return type:

dict[tuple[int, …], float]

dispersive_shift(device_a, device_b)[source]

Dressed cross-Kerr shift (GHz): E(1,1) E(1,0) E(0,1) + E(0,0).

See ChipAnalysis.dispersive_shift().

Parameters:
Return type:

float

static_zz(device_a, device_b)

Dressed cross-Kerr shift (GHz): E(1,1) E(1,0) E(0,1) + E(0,0).

See ChipAnalysis.dispersive_shift().

Parameters:
Return type:

float

dressed_anharmonicity(device)[source]

Dressed anharmonicity of one device with others grounded (GHz).

See ChipAnalysis.dressed_anharmonicity().

Parameters:

device (str | BaseDevice)

Return type:

float

effective_subspace_hamiltonian(states)[source]

Dressed effective Hamiltonian in a labeled bare subspace.

See ChipAnalysis.effective_subspace_hamiltonian().

Parameters:

states (list[Mapping[str | BaseDevice, int] | tuple[int, ...]] | tuple[Mapping[str | BaseDevice, int] | tuple[int, ...], ...])

Return type:

Any

freq(target: None = None, when: dict[str | BaseDevice, int] | None = None) dict[str, float][source]
freq(target: str | BaseDevice, when: dict[str | BaseDevice, int] | None = None) float

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.

Parameters:
Return type:

dict[str, float] | float

frame_info()[source]

Per-device frame reference frequency ω_ref,i (GHz). See ChipAnalysis.frame_info().

Return type:

dict[str, Any]

physics_notes()[source]

Aggregate physics_notes() across every component.

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.

Return type:

dict[str, list[str]]

to_dict()[source]

Serialize chip topology into a JSON-safe dictionary.

Return type:

dict[str, Any]

classmethod from_dict(d)[source]

Reconstruct a chip from to_dict() output.

Parameters:

d (dict[str, Any])

Return type:

Chip

clone()[source]

Isolated structural clone suitable for sweep evaluation.

Return type:

Chip

partition()[source]

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.

Return type:

PartitionResult

updated(update_fn)[source]

Return a cloned chip after applying one structural update callback.

Convenience for sweeps:

modified = chip.updated(lambda c: setattr(c["q"], "freq", 5.1))
Parameters:

update_fn (Callable[[Chip], None])

Return type:

Chip

status()[source]

Print a lightweight diagnostic dashboard for the chip.

Return type:

None

from_array(data, device=None)[source]

Build a backend operator from a raw NumPy array.

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.

Parameters:
Return type:

Any

observable(device, op)[source]

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.

Parameters:
Return type:

Any

e_ops(*, correlators=None, **specs)[source]

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.

Examples

>>> from quchip import DuffingTransmon, Capacitive, Chip
>>> q1 = DuffingTransmon(freq=5.0, anharmonicity=-0.25, levels=3, label="q1")
>>> q2 = DuffingTransmon(freq=5.2, anharmonicity=-0.22, levels=3, label="q2")
>>> chip = Chip([q1, q2], couplings=[Capacitive(q1, q2, g=0.02)])
>>> e = chip.e_ops(
...     q1=["X", "Y", "Z"], q2=["X", "Y", "Z"],
...     correlators={("q1", "q2"): ("Z", "Z")},
... )
Parameters:
Return type:

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

set_state_order(*devices, levels=None)[source]

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.

Every chip device must be named exactly once.

Examples

>>> from quchip import DuffingTransmon, Resonator, Chip
>>> qb = DuffingTransmon(freq=5.0, anharmonicity=-0.25, levels=3, label="qb")
>>> tc = DuffingTransmon(freq=5.5, anharmonicity=-0.20, levels=3, label="tc")
>>> cr = Resonator(freq=7.0, levels=4, label="cr")
>>> chip = Chip([qb, tc, cr])
>>> chip.set_state_order(qb, tc, cr)
>>> _ = chip.bare_state("eg1")  # {qb: 1, tc: 0, cr: 1}
Parameters:
Return type:

None

superposition(*components)[source]

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.

Examples

>>> import numpy as np
>>> from quchip import DuffingTransmon, Resonator, Chip
>>> qb = DuffingTransmon(freq=5.0, anharmonicity=-0.25, levels=3, label="qb")
>>> cr = Resonator(freq=7.0, levels=4, label="cr")
>>> chip = Chip([qb, cr])
>>> _ = chip.superposition({qb: 0}, {qb: 1})  # equal |00> + |10>
>>> _ = chip.superposition(                   # weighted mix
...     (np.sqrt(0.3), {qb: 1, cr: 0}),
...     (np.sqrt(0.7), {qb: 1, cr: 1}),
... )
Parameters:

components (Mapping[str | BaseDevice, int] | str | tuple[Any, Any])

Return type:

Any

state(device_states=None, /, **device_state_kwargs)[source]

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.

Parameters:
Return type:

Any

bare_state(device_states=None, /, **device_state_kwargs)[source]

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.

Parameters:
Return type:

Any

wire(*lines, signal_chain=None)[source]

Attach or replace classical control wiring.

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.

Examples

>>> # chip.wire(drive_q, drive_r, signal_chain=[Crosstalk(drive_q, drive_r, c=0.05)])
Parameters:
Return type:

ControlEquipment

unwire(line)[source]

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).

Parameters:

line (BaseDrive | str)

Return type:

BaseDrive

connect(control_equipment)[source]

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().

Parameters:

control_equipment (ControlEquipment)

Return type:

None

disconnect()[source]

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.

Return type:

ControlEquipment

solve(problem, *, check_truncation=True, truncation_threshold=0.001)[source]

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.

Parameters:
Return type:

SimulationResult

solve_many(batch_or_problems, *, progress=True)[source]

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.

Parameters:
  • batch_or_problems (Any)

  • progress (bool)

Return type:

SimulationBatchResult

describe()[source]

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.

Return type:

str

resolve_rwa(term_owner)[source]

Resolve a coupling’s or drive’s RWA flag against the chip default.

Returns the chip’s default when the owner’s rwa is None; otherwise returns the owner’s explicit flag.

Parameters:

term_owner (Any)

Return type:

bool

class quchip.Capacitive(device_a, device_b, *, g, rwa=None, label=None)[source]

Bases: CouplingModel

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 + a b†)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 + a b†). The RWA drops the counter-rotating terms a b 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 + a b†) 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

>>> from quchip import DuffingTransmon, 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
folds_exchange: bool = True
reduces_to_crosskerr: bool = True
g: Any = Parameter(default=<object object>, positive=False, nonnegative=False, serialize=True, unit='GHz')
interaction(a, b)[source]

Return the full capacitive interaction g * (a + a†)(b + b†).

Parameters:
Return type:

PhysicsExpr

physics_notes()[source]

Return declared capacitive-coupling and RWA assumptions.

Return type:

list[str]

classmethod from_dict(d, device_a, device_b)[source]

Reconstruct a capacitive coupling from serialized state.

Parameters:
Return type:

Capacitive

class quchip.Coupling(device_a, device_b, g, *, op_a=None, op_b=None, interaction=None, rwa=None, label=None)[source]

Bases: BaseCoupling

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 formH_int = g · op_a(device_a) op_b(device_b):

Coupling(q, r, g=0.02,
    op_a=lambda d: d.number_operator(),
    op_b=lambda d: d.number_operator())

Callable formH_int = g · interaction(device_a, device_b, backend):

Coupling(q, r, g=0.02,
    interaction=lambda a, b, bk: (
        bk.tensor(bk.dag(a.lowering_operator()), b.lowering_operator())
        + bk.tensor(a.lowering_operator(), bk.dag(b.lowering_operator()))
    ))

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.

Parameters:
property coupling_strength: float

Scalar prefactor g supplied by the user.

interaction_hamiltonian()[source]

User-defined interaction on H_a H_b, scaled by g.

Return type:

Any

physics_notes()[source]

Return notes describing the user-supplied interaction mode.

Return type:

list[str]

to_dict()[source]

Reject serialization because callables cannot be made persistent.

Return type:

dict[str, Any]

classmethod from_dict(d, device_a, device_b)[source]

Reject deserialization because callables cannot be reconstructed.

Parameters:
Return type:

Coupling

class quchip.CrossKerr(device_a, device_b, *, chi, rwa=None, label=None)[source]

Bases: CouplingModel

Cross-Kerr (dispersive) coupling H_int = χ · n̂_a n̂_b.

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}".

is_effective: bool = True
chi: Any = Parameter(default=<object object>, positive=False, nonnegative=False, serialize=True, unit='GHz')
interaction(a, b)[source]

Full form χ · n̂_a n̂_b (diagonal; identical under RWA).

Parameters:
Return type:

PhysicsExpr

parametric_interaction(a, b)[source]

Modulable structure n̂_a n̂_b — δχ(t) pumps ride this.

Parameters:
Return type:

PhysicsExpr

physics_notes()[source]

Return the declared dispersive-approximation provenance.

Return type:

list[str]

class quchip.TunableCapacitive(device_a, device_b, *, g_0, rwa=None, label=None)[source]

Bases: CouplingModel

Capacitive coupling with a scheduled parametric pump.

Effective two-body interaction with a static mean coupling strength:

\[\begin{split}H_{\text{int}} \;=\; g_0 \cdot \left\{\begin{array}{ll} (a + a^\dagger)(b + b^\dagger) & \text{if } \texttt{rwa} = \texttt{False} \\ (a^\dagger b + a b^\dagger) & \text{if } \texttt{rwa} = \texttt{True} \end{array}\right.\end{split}\]

where \(g_0\) is the static coupling strength in GHz; it may be a JAX tracer and flows through jax.grad() without concretization.

Time-dependence is not a construction-time parameter: a ParametricDrive wired onto this coupling schedules a pump δ(t) via pump(), multiplying the same operator structure the static term uses (parametric_interaction() / rwa_parametric_interaction()).

Parameters:
  • device_a (BaseDevice) – The two coupled devices.

  • device_b (BaseDevice) – The two coupled devices.

  • 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.

Examples

>>> from quchip import Chip, ControlEquipment, DuffingTransmon, ParametricDrive, TunableCapacitive
>>> q0 = DuffingTransmon(freq=5.0, anharmonicity=-0.25, levels=3, label="q0")
>>> q1 = DuffingTransmon(freq=5.2, anharmonicity=-0.22, levels=3, label="q1")
>>> tc = TunableCapacitive(q0, q1, g_0=0.0, label="tc")
>>> pump = ParametricDrive(tc, label="pump")
>>> chip = Chip([q0, q1], couplings=[tc], control_equipment=ControlEquipment([pump]))
>>> # seq.pump(tc, envelope=..., freq=...) schedules δg(t); see QuantumSequence.pump.
is_effective: bool = True
folds_exchange: bool = True
reduces_to_crosskerr: bool = True
g_0: Any = Parameter(default=<object object>, positive=False, nonnegative=False, serialize=True, unit='GHz')
interaction(a, b)[source]

Static contribution g_0 · (a + a†)(b + b†).

Parameters:
Return type:

PhysicsExpr

parametric_interaction(a, b)[source]

Modulable structure (a + a†)(b + b†) a scheduled pump multiplies.

Parameters:
Return type:

PhysicsExpr

rwa_parametric_interaction(a, b)[source]

Beam-splitter structure a†b + a b† retained under RWA.

Parameters:
Return type:

PhysicsExpr

physics_notes()[source]

Return the tunable-coupling, RWA, and effective-model assumptions.

Return type:

list[str]

class quchip.DressedResult(eigenvalues, state_map, dressed_eigenvalues, assignment_overlaps, hybridized_labels, bare_labels, bare_labels_by_dressed_index, eigenvector_matrix=None, overlap_threshold=0.5, labeling='DE', _eigensystem=None)[source]

Bases: object

Store a frozen snapshot of a dressed-state diagonalization.

Parameters:
eigenvalues

Sorted dressed eigenvalues in GHz.

Type:

array-like

eigenstates

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.

Type:

array-like

state_map

Assignment from bare product-basis label (one int per device) to the dressed eigenstate index it overlaps most with.

Type:

dict[tuple[int, …], int]

dressed_eigenvalues

Dressed eigenvalue for each assigned bare label — direct lookup path for Chip.energy().

Type:

dict[tuple[int, …], Any]

assignment_overlaps

|⟨bare|dressed⟩|² of each assignment; values below overlap_threshold flag hybridization.

Type:

dict[tuple[int, …], float]

hybridized_labels

Bare labels whose assignment quality is below overlap_threshold. A non-empty tuple triggers a user warning at dress time.

Type:

tuple[tuple[int, …], …]

bare_labels

Full canonical product-basis label set (all combinations of range(device.levels) for every device, in chip order).

Type:

tuple[tuple[int, …], …]

bare_labels_by_dressed_index

Inverse of state_map — dressed index → assigned bare label.

Type:

dict[int, tuple[int, …]]

eigenvector_matrix

Columns = dressed eigenvectors in the bare product basis. Used for ChipAnalysis.operator_in_dressed_basis() and ChipAnalysis.state_components().

Type:

array-like or None

overlap_threshold

Minimum overlap for a confident assignment.

Type:

float

labeling

Algorithm id (currently only "DE"), resolved to the confidence-ordered row-greedy overlap-matching policy actually run by ChipAnalysis.dress() (assign_rowwise_greedy()).

Type:

str

eigenvalues: Any
state_map: dict[tuple[int, ...], int]
dressed_eigenvalues: dict[tuple[int, ...], Any]
assignment_overlaps: dict[tuple[int, ...], float]
hybridized_labels: tuple[tuple[int, ...], ...]
bare_labels: tuple[tuple[int, ...], ...]
bare_labels_by_dressed_index: dict[int, tuple[int, ...]]
eigenvector_matrix: Any = None
overlap_threshold: float = 0.5
labeling: str = 'DE'
property eigenstates: Any

Backend eigenstate kets, materialized lazily from the eigensystem.

class quchip.Bath(recipe, targets=None, *, temperature=None, rate=None, correlated=False, label=None)[source]

Bases: object

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

>>> from quchip import DuffingTransmon, 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))
resolve_targets(chip)[source]

Return the ordered target device labels (defaults to all devices).

Parameters:

chip (Chip)

Return type:

list[str]

property separable: bool

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.

to_dict()[source]

Serialize the bath; targets are stored as label strings.

Return type:

dict[str, Any]

classmethod from_dict(d)[source]

Reconstruct a bath from serialized state (targets as label strings).

Parameters:

d (dict[str, Any])

Return type:

Bath

physics_notes()[source]

Return human-readable declarations of this bath’s recipe and scope.

Mirrors physics_notes(): one entry naming the recipe and its targets, plus a recipe-specific assumption a user of this bath should be aware of.

Return type:

list[str]

copy()[source]

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.

Return type:

Bath

collapse_operators(chip)[source]

Fully-embedded collapse operators for this bath.

"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_i a_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. A 2, 883 (1970), for the general collective-radiative-decay construction).

  • "correlated_dephasing": L = sqrt(gamma) * sum_i n_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.

Parameters:

chip (Chip)

Return type:

list[Operator]

class quchip.ChipTransform(*args, **kwargs)[source]

Bases: Protocol

Structural protocol for any transformation result that yields a chip.

property chip: Chip
class quchip.EliminationResult(chip, effective_params, validity, notes=<factory>)[source]

Bases: object

Store the result of eliminate().

Parameters:
chip

The reduced chip (eliminated mode removed; effect folded into ordinary surviving devices). The input chip is never mutated.

Type:

Chip

effective_params

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(survivor in |1⟩) f_mode(survivor in |0⟩) — the full resonator pull per survivor excitation, i.e. 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_a g_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).

Type:

dict[str, Any]

validity

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.

Type:

dict[str, Any]

notes

Explicitly dropped physics (counter-rotating, transients, higher order).

Type:

list[str]

Every mapping above is a :class:`~quchip.utils.labeling.LabelKeyedDict`
the device or coupling *object* is as good a key as its label, and pair
keys match in either order
``res.effective_params[q1]``, ``res.validity[leg]``, and
``exchange[(q1, q0)]`` all resolve.
chip: Chip
effective_params: dict[str, Any]
validity: dict[str, Any]
notes: list[str]
describe()[source]

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.

Return type:

str

quchip.eliminate(chip, target, *, method='sw')[source]

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_a g_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.

Parameters:
  • chip (Chip) – Source chip (never mutated).

  • 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.

Return type:

EliminationResult

Examples

>>> from quchip import DuffingTransmon, Resonator, Capacitive, Chip
>>> from quchip.chip.transformations import eliminate
>>> q = DuffingTransmon(freq=5.0, anharmonicity=-0.25, levels=3, label="q")
>>> r = Resonator(freq=7.0, levels=5, label="r")
>>> chip = Chip([q, r], couplings=[Capacitive(q, r, g=0.05)])
>>> result = eliminate(chip, r)          # r removed; Lamb shift folded into q.freq
>>> reduced = result.chip
>>> [d.label for d in reduced.devices]
['q']
class quchip.ActivePatchResult(chip, sequence, active_labels, eliminated_labels, steps, notes)[source]

Bases: object

A reduced patch chip, the re-bound sequence, and the reduction’s honesty record.

Parameters:
chip

The patch chip: schedule-active devices plus whatever spectators active_patch() could not eliminate (unreachable, or where elimination itself declined — see notes).

Type:

Any

sequence

A QuantumSequence bound to chip, replaying the source sequence’s entries verbatim.

Type:

Any

active_labels

Sorted schedule-active device labels (never eliminated).

Type:

tuple[str, …]

eliminated_labels

Device labels actually folded away, in elimination order (farthest-from-active first).

Type:

tuple[str, …]

steps

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.

Type:

tuple

notes

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.

Type:

tuple[str, …]

chip: Any
sequence: Any
active_labels: tuple[str, ...]
eliminated_labels: tuple[str, ...]
steps: tuple
notes: tuple[str, ...]
property validity: dict[str, Any]

{eliminated label: that step's .validity} — verbatim, per-coupling shape untouched.

property effective_params: dict[str, Any]

{eliminated label: that step's .effective_params} — verbatim, per-survivor shape untouched.

simulate(**kwargs)[source]

Solve the patch sequence (automatic partitioning still applies inside).

Parameters:

kwargs (Any)

Return type:

Any

quchip.active_patch(sequence, *, hops=1, method='sw')[source]

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.

Parameters:
  • sequence (QuantumSequence) – The schedule to reduce around.

  • hops (int) – Coupling-graph hops the active set expands beyond the scheduled targets (see active_labels()).

  • method (str) – Forwarded to eliminate() for every device elimination.

Return type:

ActivePatchResult

quchip.register_retarget_rule(drive_type, target_type, result_kind, rule)[source]

Register a converter for (drive type, eliminated-target type, result kind).

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".

  • rule (callable) – rule(line, ctx: RetargetContext) -> RetargetResult.

Return type:

None

quchip.register_elimination_target(target)[source]

Register an EliminationTarget kind for eliminate() to dispatch on.

Parameters:

target (EliminationTarget)

Return type:

None

quchip.register_reduction_method(method)[source]

Register a reduction strategy under its ReductionMethod.name.

Parameters:

method (ReductionMethod)

Return type:

None

class quchip.Gaussian(duration, sigmas=3, amplitude=1.0)[source]

Bases: EnvelopeShape

Centered Gaussian pulse.

\[E(t) = A \exp\!\left[-\frac{(t - \tau/2)^2}{2 \sigma^2}\right], \qquad \sigma = \frac{\tau}{2 N_\sigma}.\]

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.

Parameters:
  • duration (Any)

  • sigmas (Any)

  • amplitude (Any)

duration: Any = Parameter(default=<object object>, positive=True, nonnegative=False, serialize=True, unit='ns')
sigmas: Any = Parameter(default=3, positive=True, nonnegative=False, serialize=True, unit=None)
amplitude: Any = Parameter(default=1.0, positive=False, nonnegative=False, serialize=True, unit=None)
value(t)[source]

Evaluate the centered Gaussian envelope at time points t.

Parameters:

t (Any)

Return type:

Any

class quchip.LinearRamp(duration, ramp_duration, amplitude=1.0)[source]

Bases: EnvelopeShape

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.

  • amplitude (float) – Peak amplitude \(A\) (default 1.0).

Notes

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.

Examples

>>> from quchip.control.envelopes import LinearRamp
>>> ramp = LinearRamp(duration=60.0, ramp_duration=50.0, amplitude=4.0)
>>> import numpy as np
>>> t = np.array([0.0, 25.0, 50.0, 55.0])
>>> np.real(ramp.waveform(t)).tolist()
[0.0, 2.0, 4.0, 4.0]
duration: Any = Parameter(default=<object object>, positive=True, nonnegative=False, serialize=True, unit='ns')
ramp_duration: Any = Parameter(default=<object object>, positive=True, nonnegative=False, serialize=True, unit='ns')
amplitude: Any = Parameter(default=1.0, positive=False, nonnegative=False, serialize=True, unit=None)
validate()[source]

Reject ramps longer than the pulse (ramp_duration > duration).

Return type:

None

value(t)[source]

Evaluate the linear-ramp envelope at time points t (ns).

Parameters:

t (array-like) – 1-D array of time points in nanoseconds.

Returns:

Complex-valued waveform: rises linearly over ramp_duration, holds constant at amplitude afterward.

Return type:

array

class quchip.GaussianEdge(duration, edge_duration, sigmas=3, amplitude=1.0)[source]

Bases: EnvelopeShape

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.

  • amplitude (float) – Plateau amplitude \(A\).

See also

SquareWithGaussianEdges

Same shape parameterized by edge_frac (fraction) instead of absolute edge_duration.

References

  • Krantz et al., APR 6, 021318 (2019), Sec. IV.C.

duration: Any = Parameter(default=<object object>, positive=True, nonnegative=False, serialize=True, unit='ns')
edge_duration: Any = Parameter(default=<object object>, positive=True, nonnegative=False, serialize=True, unit='ns')
sigmas: Any = Parameter(default=3, positive=True, nonnegative=False, serialize=True, unit=None)
amplitude: Any = Parameter(default=1.0, positive=False, nonnegative=False, serialize=True, unit=None)
validate()[source]

Reject edges that overrun the pulse (2 * edge_duration > duration).

Return type:

None

value(t)[source]

Evaluate the flat-top Gaussian-edge envelope at time points t.

Parameters:

t (Any)

Return type:

Any

class quchip.Square(duration, amplitude=1.0, phase=0.0)[source]

Bases: EnvelopeShape

Constant-amplitude pulse with optional global phase.

\[E(t) = A\, e^{i\phi}, \qquad 0 \le t \le \tau.\]
Parameters:
  • duration (float) – Pulse length \(\tau\) in ns.

  • amplitude (float) – Real amplitude \(A\) applied on top of value().

  • phase (float) – Global phase \(\phi\) in radians.

duration: Any = Parameter(default=<object object>, positive=True, nonnegative=False, serialize=True, unit='ns')
amplitude: Any = Parameter(default=1.0, positive=False, nonnegative=False, serialize=True, unit=None)
phase: Any = Parameter(default=0.0, positive=False, nonnegative=False, serialize=True, unit='rad')
value(t)[source]

Evaluate the constant complex envelope at time points t.

Parameters:

t (Any)

Return type:

Any

class quchip.SquareWithGaussianEdges(duration, amplitude=1.0, edge_frac=0.25, sigmas=3)[source]

Bases: EnvelopeShape

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).

  • amplitude (float) – Plateau amplitude \(A\).

  • edge_frac (float) – Ramp length as a fraction of the total duration. Must satisfy 0 < edge_frac and 2 * edge_frac <= 1.

  • sigmas (float) – Number of standard deviations spanned by each ramp.

duration: Any = Parameter(default=<object object>, positive=True, nonnegative=False, serialize=True, unit='ns')
amplitude: Any = Parameter(default=1.0, positive=False, nonnegative=False, serialize=True, unit=None)
edge_frac: Any = Parameter(default=0.25, positive=True, nonnegative=False, serialize=True, unit=None)
sigmas: Any = Parameter(default=3, positive=True, nonnegative=False, serialize=True, unit=None)
validate()[source]

Reject ramps that overrun the pulse (2 * edge_frac > 1).

Return type:

None

property edge_duration: float

Ramp duration in ns (edge_frac * duration).

value(t)[source]

Evaluate the fraction-parameterized Gaussian-edge envelope.

Parameters:

t (Any)

Return type:

Any

quchip.simulate(chip, drive_ops, tlist, *, solver=None, options=None, e_ops=None, initial_state=None, check_truncation=True, truncation_threshold=0.001, partition=True)[source]

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.

Parameters:
  • chip (Chip) – The chip to simulate.

  • drive_ops (list of DriveOp) – Scheduled drive operations, typically produced by a QuantumSequence.

  • tlist (array_like) – Solver time grid in ns.

  • 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.

Returns:

The wrapped solver output.

Return type:

SimulationResult or PartitionedSimulationResult

Raises:
  • 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()).

  • RuntimeError – If the backend solve fails.

Examples

>>> import numpy as np
>>> from quchip import Chip, DuffingTransmon, ChargeDrive, Gaussian, QuantumSequence
>>> from quchip.engine import simulate
>>> q = DuffingTransmon(freq=5.0, anharmonicity=-0.3, levels=3)
>>> chip = Chip([q], frame="rotating", rwa=True)
>>> ctrl = ChargeDrive(target=q)
>>> chip.wire(ctrl)
>>> seq = QuantumSequence(chip)
>>> _ = seq.schedule(ctrl, envelope=Gaussian(duration=20.0, sigmas=3, amplitude=0.02), freq=chip.freq(q))
>>> tlist = np.linspace(0.0, 20.0, 41)
>>> result = simulate(chip, list(seq.scheduled_ops), tlist, e_ops={q: q.number_operator()})
>>> populations = result.expect(q)
quchip.build_problem(chip, drive_ops, tlist, *, solver=None, options=None, e_ops=None, initial_state=None)[source]

Run stages 1-4 and return a frozen SolveProblem.

Returns an immutable request that can be passed to solve_problem(), batched with solve_many(), or serialized. No solver is invoked.

Parameters:
  • chip (Chip) – The chip whose Hamiltonian, frame, and backend are assembled.

  • drive_ops (list of DriveOp) – Scheduled drive operations, typically produced by a QuantumSequence.

  • tlist (array_like) – Solver time grid in ns.

  • 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.

Returns:

The frozen request handed to a backend.

Return type:

SolveProblem

Raises:

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.

Examples

>>> import numpy as np
>>> from quchip import Chip, DuffingTransmon, ChargeDrive, Gaussian, QuantumSequence
>>> from quchip.engine import build_problem, solve_problem
>>> q = DuffingTransmon(freq=5.0, anharmonicity=-0.3, levels=3)
>>> chip = Chip([q], frame="rotating", rwa=True)
>>> ctrl = ChargeDrive(target=q)
>>> chip.wire(ctrl)
>>> seq = QuantumSequence(chip)
>>> _ = seq.schedule(ctrl, envelope=Gaussian(duration=20.0, sigmas=3, amplitude=0.02), freq=chip.freq(q))
>>> problem = build_problem(chip, list(seq.scheduled_ops), np.linspace(0.0, 20.0, 41))
>>> result = solve_problem(problem)
quchip.solve_problem(problem, *, check_truncation=True, truncation_threshold=0.001)[source]

Dispatch a SolveProblem through its chip backend.

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.

Parameters:
Return type:

SimulationResult

quchip.solve_many(batch_or_problems, *, progress=True)[source]

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.

Parameters:
Return type:

SimulationBatchResult

class quchip.ObservableTrace(values, raw)[source]

Bases: object

One named expectation-value trace, with pre- and post-processing values.

Parameters:
values

Post-processed expectation values over time (e.g. demodulated, phase-corrected, band-summed). This is what user code normally wants.

Type:

Any

raw

The same quantity before post-processing — useful for debugging frame conventions, band decomposition, and demodulation.

Type:

Any

values: Any
raw: Any
class quchip.SimulationBatchResult(results, *, shape=None, axes=None)[source]

Bases: object

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.

Parameters:
property results: tuple[SimulationResult, ...]

Return the per-element SimulationResult objects in input order.

property shape: tuple[int, ...]

Return the natural sweep-grid shape for this batch.

property axes: tuple[tuple[str, Any], ...]

Return sweep-axis metadata as (name, values) pairs.

property backend: Backend

Return the backend shared by every element (raises on an empty batch).

with_sweep_metadata(*, shape, axes)[source]

Return an equivalent batch annotated with sweep-axis metadata.

Parameters:
Return type:

SimulationBatchResult

expect(key, index=None, *, reduce=None)[source]

Return expectation traces reshaped to the natural sweep grid.

Parameters:
  • key (Any)

  • index (int | None)

  • reduce (str | None)

Return type:

Any

population(device, level=0, *, reduce=None)[source]

Return population traces reshaped to the natural sweep grid.

Parameters:
Return type:

Any

final_overlap_magnitudes(targets)[source]

Return stacked final overlap magnitudes, one per (result, target) pair.

Parameters:

targets (list[Any] | tuple[Any, ...])

Return type:

Any

final_amplitudes(targets)[source]

Return stacked final complex amplitudes (phase-sensitive), one per (result, target) pair.

Parameters:

targets (list[Any] | tuple[Any, ...])

Return type:

Any

class quchip.SimulationResult(solver_result, backend, dims, *, device_info=None, observable_traces=None)[source]

Bases: object

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.).

Parameters:
property observable_traces: dict[Any, ObservableTrace | list[ObservableTrace]] | None

Return the dict of named ObservableTrace entries, or None.

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.

expect(key, index=None)[source]

Return the full expectation-value array for observable key over self.times.

Parameters:
  • key (Any)

  • index (int | None)

Return type:

Any

expect_final(key, index=None)[source]

Return the final expectation value for observable key (self.expect(key)[-1]).

Parameters:
  • key (Any)

  • index (int | None)

Return type:

Any

expect_values(key, index=None)

Return the full expectation-value array for observable key over self.times.

Parameters:
  • key (Any)

  • index (int | None)

Return type:

Any

overlap_array(target)[source]

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.

Parameters:

target (Any)

Return type:

Any

amplitude_array(target)[source]

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.

Parameters:

target (Any)

Return type:

Any

overlap(target)[source]

Wrap overlap_array() for convenience.

Returns a NumPy array under the QuTiP backend; under JAX (dynamiqs) returns the backend-native array so the call stays JIT/grad-friendly.

Parameters:

target (Any)

Return type:

Any

state(t=None, *, dm=False)[source]

Return the state at time t (or final state if t is None); optionally coerced to a DM.

Parameters:
Return type:

Any

state_at(t)[source]

Return the state at the stored time nearest to t (ns).

Parameters:

t (float)

Return type:

Any

dm_at(t)[source]

Return the density matrix at the stored time nearest to t (ns) — promotes kets on demand.

Parameters:

t (float)

Return type:

Any

property final_state: Any

Return the final state — explicit final_state if stored, else the last stored trajectory entry.

reduced_state(t, device)[source]

Partial-trace the state at time t down to device’s subspace.

Parameters:
Return type:

Any

property populations: dict[tuple[int, ...], ndarray]

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.

population_array(device, level=0)[source]

Return the population of Fock level on device over time, in the backend’s array module.

Parameters:
Return type:

Any

population(device, level=0)[source]

Wrap population_array() for convenience.

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.

Parameters:
Return type:

Any

plot_populations(*, trace_out=None, computational=False, ax=None, **kwargs)[source]

Plot per-basis-state populations over time (delegates to quchip.viz.plot_populations()).

Parameters:
Return type:

Any

plot_state(index, *, trace_out=None, computational=False, mode='population', ax=None, **kwargs)[source]

Plot one stored state as populations or a density matrix (delegates to quchip.viz.plot_state()).

Parameters:
Return type:

Any

plot_expectation(*, keys=None, ax=None, **kwargs)[source]

Plot expectation-value traces over time (delegates to quchip.viz.plot_expectation()).

Parameters:
Return type:

Any

plot_wigner(index=-1, *, trace_out=None, ax=None, **kwargs)[source]

Plot the Wigner function of one stored state (delegates to quchip.viz.plot_wigner()).

Parameters:
Return type:

Any

check_truncation(*, threshold=0.001, top_levels=1)[source]

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.

Parameters:
Return type:

dict[str, float]

class quchip.PartitionedSimulationResult(component_results, partition, key_plan)[source]

Bases: object

Result of one partitioned solve: K component results + a key plan.

Parameters:
  • component_results (list)

  • partition (Any)

  • key_plan (dict)

property components: tuple
property partition: Any
property device_order: tuple[str, ...]

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.

expect(key, index=None)[source]
Parameters:
  • key (Any)

  • index (int | None)

Return type:

Any

expect_final(key, index=None)[source]
Parameters:
  • key (Any)

  • index (int | None)

Return type:

Any

expect_values(key, index=None)
Parameters:
  • key (Any)

  • index (int | None)

Return type:

Any

population(device, level=0)[source]
Parameters:
Return type:

Any

population_array(device, level=0)[source]
Parameters:
Return type:

Any

check_truncation(threshold=0.001)[source]

Run each component’s truncation check and merge the per-device results.

Mirrors check_truncation()’s return shape (a dict keyed by device label) for duck-typing parity between a joint and a partitioned result.

Parameters:

threshold (float)

Return type:

dict[str, float]

property states: list

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.

property final_state: Any

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.

describe()[source]
Return type:

str

class quchip.QuantumSequence(chip)[source]

Bases: object

Declarative pulse sequence builder for a Chip.

Tracks per-(device, drive) timing cursors and per-device virtual-Z phase frames. Append pulses with schedule() (or the conveniences charge(), phase(), flux()); synchronize channels with barrier() and delay(). The schedule is materialized lazily into DriveOp objects by build_problem() and build_batch().

Wherever a device/drive is expected, either the object itself or its string label works, and examples should prefer object references.

Simulation runs through one consistent verb — simulate — across three tiers, from most ergonomic to most explicit:

  1. simulate() / simulate_batch() — schedule and solve in one call (the example-facing path).

  2. solve() / solve_many() — solve a SolveProblem / batch you already hold.

  3. The module-level simulate() / solve_problem() / solve_many() — the low-level “I already have drive_ops / a SolveProblem” tier.

Parameters:

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().

Examples

>>> from quchip import (
...     DuffingTransmon, ChargeDrive, Chip, QuantumSequence, Gaussian
... )
>>> q = DuffingTransmon(freq=5.0, anharmonicity=-0.25, levels=3)
>>> _ = ChargeDrive(target=q)
>>> chip = Chip([q])
>>> seq = QuantumSequence(chip)
>>> _ = seq.charge(q, envelope=Gaussian(duration=20.0, amplitude=0.05))
>>> seq.vz(q, angle=0.5)
>>> _ = seq.charge(q, envelope=Gaussian(duration=10.0, amplitude=0.03))
schedule(target, *, envelope, freq=None, start_time=None, phase=0.0)[source]

Schedule a pulse on target.

Parameters:
  • target (str | BaseDrive | BaseDevice | BaseCoupling) –

    Accepted forms, resolved in this order:

    • BaseDrive — scheduled directly on that drive (a ParametricDrive pumps its bound coupling).

    • 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.

  • envelope (BaseEnvelope) – Pulse envelope.

  • 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.

Return type:

PulseHandle

charge(target, *, envelope, freq=None, phase=0.0)[source]

Schedule a ChargeDrive pulse; freq defaults to device.drive_freq.

Parameters:
Return type:

PulseHandle

phase(target, *, envelope, freq, phase=0.0)[source]

Schedule a PhaseDrive pulse; freq is required.

Parameters:
Return type:

PulseHandle

flux(target, *, envelope)[source]

Schedule a FluxDrive pulse (no carrier frequency).

Parameters:
Return type:

PulseHandle

pump(coupling, *, envelope, freq=None, start_time=None, phase=0.0)[source]

Schedule an edge pump: baseband δ(t)=A(t) when freq is None, else A(t)·cos(2πft-φ).

Parameters:
Return type:

PulseHandle

flux_to(target, *, target_freq, envelope)[source]

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) . 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)).

Parameters:
  • target (str | BaseDevice) – Device to flux-pulse.

  • 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().

Return type:

PulseHandle

vz(target, angle)[source]

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.

Parameters:
Return type:

None

delay(scope, duration)[source]

Insert an idle delay on all drive channels of scope.

Parameters:
Return type:

DelayHandle

barrier(*labels)[source]

Synchronize channel cursors.

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.

Parameters:

labels (str | BaseDevice)

Return type:

None

property total_duration: Any

Total sequence duration in ns (maximum across all cursors).

property scheduled_ops: tuple[DriveOp, ...]

Materialize and return the scheduled DriveOp tuple.

property channel_cursors: dict[tuple[str, str], Any]

Current time cursors keyed by (target_label, drive_label).

build_problem(tlist=None, solver=None, options=None, e_ops=None, initial_state=None)[source]

Build a single SolveProblem from this sequence.

Parameters:
  • 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().

Return type:

SolveProblem

vary(field, values, *, name=None)[source]

Create a BatchAxis over a sequence-level field.

Parameters:
  • 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.

  • name (str, optional) – Axis name recorded in ProblemBatch.axes and ProblemBatch.params_at(). Defaults to field.

Returns:

Sequence-level axis, consumable by build_batch() or zip().

Return type:

BatchAxis

zip(*axes)[source]

Zip axes into one pairwise dimension.

Parameters:

*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.

Return type:

ZippedBatchAxis

build_batch(*axes, tlist=None, solver=None, options=None, e_ops=None, initial_state=None)[source]

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.

Parameters:
Return type:

ProblemBatch

simulate(tlist=None, solver=None, options=None, e_ops=None, initial_state=None, *, backend=None, check_truncation=True, truncation_threshold=0.001, partition=True)[source]

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.

Parameters:
  • tlist (Any | None)

  • solver (str | None)

  • options (dict | None)

  • e_ops (dict | None)

  • initial_state (Any | None)

  • backend (Any | None)

  • check_truncation (bool)

  • truncation_threshold (float)

  • partition (bool)

Return type:

SimulationResult

simulate_batch(*axes, tlist=None, solver=None, options=None, e_ops=None, initial_state=None, backend=None, progress=True, check_truncation=True, truncation_threshold=0.001)[source]

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.

Parameters:
Return type:

SimulationBatchResult

active_patch(*, hops=1, method='sw')[source]

Reduce the chip to this schedule’s active patch (spectators eliminated).

Convenience for quchip.chip.transformations.active_patch(); see it for the activity rule, elimination order, and validity reporting.

Parameters:
Return type:

ActivePatchResult

clone()[source]

Return a deep copy.

_entries is the single source of truth, so only it is deep-copied; the chip is shared, matching the previous shallow-copy behavior.

Return type:

QuantumSequence

describe()[source]

Plain-text timeline of the scheduled pulses.

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()).

Return type:

str

class quchip.ProblemBatch(batch, params, shape, axes)[source]

Bases: Sequence[SolveProblem]

List-like view of a batched solve request with sweep bookkeeping.

Wraps a SolveBatch (the structural source of truth); per-element SolveProblem views are materialised on demand.

Parameters:
batch
params
shape
axes
params_at(point)[source]

Return the sweep parameter dictionary at the given grid coordinate.

Parameters:

point (int | tuple[int, ...])

Return type:

dict[str, Any]

quchip.get_default_backend()[source]

Return the active default backend.

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).

Return type:

Backend

quchip.set_default_backend(backend)[source]

Set the active default backend by name ("qutip"/"dynamiqs") or instance.

Parameters:

backend (str | Backend)

Return type:

None

quchip.enable_compilation_cache(path=None, *, min_compile_time_secs=0.01)[source]

Enable JAX’s on-disk persistent compilation cache (opt-in).

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.

Returns:

The absolute cache directory now in use.

Return type:

str

class quchip.BaseDrive(target=None, *, label=None, rwa=None)[source]

Bases: Registrable

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().

  • label (str | None) – Optional explicit label; otherwise auto-generated.

  • rwa (bool | None) – Per-drive RWA override. None means follow the chip-level setting.

Examples

>>> from quchip import DuffingTransmon, ChargeDrive
>>> q = DuffingTransmon(freq=5.0, anharmonicity=-0.25, levels=3)
>>> drive = ChargeDrive(target=q)
>>> drive.device_label == q.label
True
target_kind: ClassVar[str] = 'device'

"device" for drives targeting a device (the default), "edge" for drives targeting a coupling (see ParametricDrive).

Type:

Structural dispatch key for equipment/chip/sequence code

connect(device)[source]

Attach (or re-attach) this drive to device.

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.

Parameters:

device (BaseDevice)

Return type:

None

property device_label: str | None

Label of the connected device, or None if unconnected.

property target_label: str | None

Label of this drive’s target, or None if unconnected.

Device-target drives (target_kind == "device") alias device_label; ParametricDrive overrides this to resolve its coupling target instead.

collapse_operators(device)[source]

Lindblad collapse operators contributed by this drive.

Default: none. Override to model drive-induced dissipation (e.g. photon loss on a drive line).

Parameters:

device (BaseDevice)

Return type:

list[Any]

local_channels(device)[source]

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.

Parameters:

device (BaseDevice)

Return type:

list[DriveChannel]

physics_notes()[source]

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().

Return type:

list[str]

signal_spec(drive_op, device)[source]

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.

Return type:

DriveSignalSpec

Notes

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 SignalProgram Scale(Shift(Window(env, 0, duration), start), exp(i·phi)) before applying the modulation dispatch.

copy(*, target=None)[source]

Return a shallow copy, optionally rebound to a new target.

Parameters:

target (BaseDevice | None)

Return type:

BaseDrive

to_dict()[source]

Serialize into a JSON-safe dictionary.

Return type:

dict[str, Any]

class quchip.SignalTransform[source]

Bases: Registrable, ABC

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().

abstractmethod apply(signals)[source]

Return the transformed signal map.

Parameters:

signals (dict[tuple[str, int], SignalNode])

Return type:

dict[tuple[str, int], SignalNode]

referenced_lines()[source]

Return control-line labels referenced by this transform.

Return type:

tuple[str, …]

without_line(line)[source]

Return this transform without line, or None when it must be dropped.

Parameters:

line (str)

Return type:

SignalTransform | None

class quchip.Delay(line, delta_t)[source]

Bases: SignalTransform

Shift every signal on line in time by delta_t ns.

Parameters:
line: str
delta_t: float
apply(signals)[source]

Time-shift every signal on line by delta_t ns.

Parameters:

signals (dict[tuple[str, int], SignalNode])

Return type:

dict[tuple[str, int], SignalNode]

referenced_lines()[source]

Return control-line labels referenced by this transform.

Return type:

tuple[str, …]

to_dict()[source]

Serialize into a JSON-safe dictionary.

Return type:

dict[str, Any]

classmethod from_dict(d)[source]

Reconstruct from to_dict() output.

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.

Parameters:

d (dict[str, Any])

Return type:

Delay

class quchip.Gain(line, factor)[source]

Bases: SignalTransform

Scale every signal on line by a complex factor.

Parameters:
line: str
factor: complex
apply(signals)[source]

Scale every signal on line by the complex factor.

Parameters:

signals (dict[tuple[str, int], SignalNode])

Return type:

dict[tuple[str, int], SignalNode]

referenced_lines()[source]

Return control-line labels referenced by this transform.

Return type:

tuple[str, …]

to_dict()[source]

Serialize into a JSON-safe dictionary.

Return type:

dict[str, Any]

classmethod from_dict(d)[source]

Reconstruct from to_dict() output.

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.

Parameters:

d (dict[str, Any])

Return type:

Gain

class quchip.DriveSignalSpec(envelope, start_time, duration, phase_offset, drive_freq)[source]

Bases: object

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).

  • start_time (float) – Onset of the pulse in ns.

  • duration (float) – Envelope duration in ns (the pulse is zero outside [start_time, start_time + duration]).

  • phase_offset (float) – Phase rotation applied to the raw line signal, in radians.

  • drive_freq (float | None) – Microwave carrier frequency in ordinary GHz for DriveModulation.SINGLE_TONE channels; None for baseband (DriveModulation.DIRECT_REAL) drives.

envelope: BaseEnvelope
start_time: float
duration: float
phase_offset: float
drive_freq: float | None
class quchip.DriveModulation(value)[source]

Bases: Enum

How a drive channel’s operator is time-modulated.

  • 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) = Re s(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.

SINGLE_TONE = 'single_tone'
DIRECT_REAL = 'direct_real'
EDGE_PUMP = 'edge_pump'
class quchip.ChargeDrive(target=None, *, label=None, rwa=None)[source]

Bases: BaseDrive

Microwave charge drive on a transmon-like device.

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).

Examples

>>> from quchip import DuffingTransmon, ChargeDrive, Gaussian
>>> q = DuffingTransmon(freq=5.0, anharmonicity=-0.25, levels=3)
>>> drive = ChargeDrive(target=q)
>>> channels = drive.local_channels(q)
>>> len(channels) == 1
True
Parameters:
class quchip.DriveChannel(operator, modulation)[source]

Bases: object

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.

Parameters:
operator: Any
modulation: DriveModulation
class quchip.FluxDrive(target=None, *, label=None, rwa=None)[source]

Bases: BaseDrive

Real-valued flux drive coupling to \(\hat n\).

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.

Examples

>>> from quchip import DuffingTransmon, FluxDrive
>>> q = DuffingTransmon(freq=5.0, anharmonicity=-0.25, levels=3)
>>> flux = FluxDrive(target=q)
>>> flux.rwa is None
True
Parameters:
class quchip.ParametricDrive(coupling, *, label=None, **kwargs)[source]

Bases: BaseDrive

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 amplitude A(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().

  • label (str | None) – Optional explicit label; otherwise auto-generated from "parametric".

  • kwargs (Any)

Raises:
  • TypeErrorcoupling does not implement parametric_interaction() (a static coupling), or an unexpected keyword argument is passed.

  • ValueError – An explicit rwa keyword argument is passed — the RWA policy is fixed by the coupling’s parametric hook, not per-drive.

target_kind: ClassVar[str] = 'edge'

"device" for drives targeting a device (the default), "edge" for drives targeting a coupling (see ParametricDrive).

Type:

Structural dispatch key for equipment/chip/sequence code

property device_label: None

an edge pump has no device target.

Device-keyed paths (drive-noise collection, device-line bookkeeping) treat None as “skip this line”; edge code reads target_label instead.

Type:

Always None

property target_label: str

Label of the pumped coupling (resolves objects and label strings).

connect(coupling)[source]

Rebind to coupling (no device-side handshake — couplings hold no drive list).

Parameters:

coupling (Any)

Return type:

None

local_channels(device)[source]

Edge pumps have no device channel; stage 2 compiles them from the coupling’s hook.

Parameters:

device (Any)

Return type:

list[DriveChannel]

class quchip.PhaseDrive(target=None, *, label=None, rwa=None)[source]

Bases: BaseDrive

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.

Parameters:
class quchip.TwoPhotonDrive(target=None, *, label=None, rwa=None)[source]

Bases: BaseDrive

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.

  • label (str | None) – Optional explicit label; otherwise auto-generated.

  • rwa (bool | None) – Per-drive RWA override. None means follow the chip-level setting.

References

Examples

>>> from quchip.devices.kerr_cavity import KerrCavity
>>> from quchip.control.drives_two_photon import TwoPhotonDrive
>>> cav = KerrCavity(freq=5.0, kerr=1.0, levels=10, label="cav")
>>> d2 = TwoPhotonDrive(target=cav)
>>> channels = d2.local_channels(cav)
>>> len(channels)
1
local_channels(device)[source]

Return the two-photon coupling channel a^2 + a_dag^2.

Parameters:

device (BaseDevice) – The cavity device being driven.

Returns:

One channel with operator a^2 + a_dag^2 and DriveModulation.SINGLE_TONE modulation.

Return type:

list[DriveChannel]

physics_notes()[source]

Return the base drive notes plus the two-photon coupling declaration.

Return type:

list[str]

class quchip.ControlEquipment(lines, *, signal_chain=None)[source]

Bases: object

Ordered drive lines plus a sequence of signal-chain transforms.

Drives produce raw line signals; the equipment pipes them through signal_chain in order before the engine assembles Hamiltonian terms.

Parameters:
property lines: list[BaseDrive]

Ordered drive lines (defensive copy).

property signal_chain: list[SignalTransform]

Signal-chain transforms (defensive copy).

apply_signal_chain(signals)[source]

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 type:

SignalMap

property crosstalks: list[Crosstalk]

Directed crosstalk edges represented by the signal chain.

crosstalk_matrix()[source]

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.

Return type:

CrosstalkMatrix

set_crosstalk_matrix(beta, theta=None, delay=None, *, labels=None)[source]

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).

  • theta (Any, optional) – [n, n] phase matrix (radians). Defaults to zeros.

  • delay (Any, optional) – [n, n] delay matrix (ns). Defaults to zeros.

  • labels (tuple[str, ...] | list[str] | None, optional) – Axis ordering. Defaults to wiring order (self.lines). Must match beta.shape[0].

Return type:

None

Notes

Traced JAX entries flow unchanged into CrosstalkMatrix and therefore into the signal-program IR. No concretization occurs.

copy(device_map, coupling_map=None)[source]

Return a structural copy with drive lines rebound to device_map / coupling_map.

Edge lines (target_kind == "edge") rebind via coupling_map, keyed by coupling label; device lines rebind via device_map as before.

Parameters:
Return type:

ControlEquipment

to_dict()[source]

Serialize into a JSON-safe dictionary.

Return type:

dict[str, Any]

classmethod from_dict(d, dev_map, coupling_map=None)[source]

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.

Parameters:
Return type:

ControlEquipment

class quchip.Crosstalk(source, victim, beta, theta=0.0, delay=0.0)[source]

Bases: SignalTransform

Linear crosstalk from a source drive line onto a victim line.

For each scheduled operation on the source line, adds

\[\beta\, e^{i\theta}\, s_\mathrm{src}(t - \Delta t)\]

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.

  • beta (float) – Leakage amplitude (dimensionless).

  • theta (float) – Phase shift applied to the leaked signal, radians.

  • delay (float) – Time shift of the leaked signal relative to the source, ns.

source: str
victim: str
beta: float
theta: float = 0.0
delay: float = 0.0
apply(signals)[source]

Add the phase-rotated, delayed source signal onto the victim line.

Parameters:

signals (dict[tuple[str, int], SignalNode])

Return type:

dict[tuple[str, int], SignalNode]

referenced_lines()[source]

Return control-line labels referenced by this transform.

Return type:

tuple[str, …]

to_dict()[source]

Serialize into a JSON-safe dictionary.

Return type:

dict[str, Any]

classmethod from_dict(d)[source]

Reconstruct from to_dict() output.

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.

Parameters:

d (dict[str, Any])

Return type:

Crosstalk

class quchip.CrosstalkMatrix(labels, beta, theta, delay)[source]

Bases: SignalTransform

Dense crosstalk transform and matrix view in control-line order.

Parameters:
labels

Drive labels in wiring order (the order drives appear in ControlEquipment.lines). Row / column i corresponds to labels[i].

Type:

tuple[str, …]

beta

[n, n] amplitude matrix. beta[i, j] is the leakage amplitude from source labels[j] onto victim labels[i] (column = source, row = victim). Diagonals represent self-coupling and are conventionally 1.0.

Type:

Any

theta

[n, n] phase matrix (radians), same indexing as beta.

Type:

Any

delay

[n, n] delay matrix (ns), same indexing as beta.

Type:

Any

Notes

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.

labels: tuple[str, ...]
beta: Any
theta: Any
delay: Any
apply(signals)[source]

Apply all directed leakage edges to one shared input snapshot.

Parameters:

signals (dict[tuple[str, int], SignalNode])

Return type:

dict[tuple[str, int], SignalNode]

referenced_lines()[source]

Return control-line labels referenced by this transform.

Return type:

tuple[str, …]

without_line(line)[source]

Return this transform without line, or None when it must be dropped.

Parameters:

line (str)

Return type:

CrosstalkMatrix | None

edges()[source]

Return the directed-edge view used by topology and visualization.

Return type:

list[Crosstalk]

to_dict()[source]

Serialize into a JSON-safe dictionary.

Return type:

dict[str, Any]

classmethod from_dict(d)[source]

Reconstruct from to_dict() output.

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.

Parameters:

d (dict[str, Any])

Return type:

CrosstalkMatrix

class quchip.SpectrumSweep(chip, axes, *, update_fn, evals_count=None, store_eigenstates=False, overlap_threshold=0.5)[source]

Bases: object

Sequential dressed-spectrum sweep driver.

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()
Parameters:
run(progress=True)[source]

Execute the sweep and collect the dressed-spectrum analysis.

Parameters:

progress (bool) – Whether to display a tqdm progress bar.

Returns:

Populated result.

Return type:

SpectrumSweepResult

class quchip.Sweep(values, *, name=None)[source]

Bases: object

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.

Examples

>>> import numpy as np
>>> from quchip.sweep import Sweep
>>> freqs = Sweep(np.linspace(4.9, 5.1, 5), name="freq")
>>> freqs.size
5
>>> drives = Sweep([0.01, 0.02, 0.03], name="amp")
>>> points = Sweep.expand([freqs, drives])
>>> len(points)
15
Parameters:
  • values (Any)

  • name (str | None)

static zip(*sweeps)[source]

Pair axes for element-wise iteration.

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.

Return type:

ZippedSweep

static expand(axes)[source]

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).

Return type:

list[dict[str, Any]]

class quchip.ZippedSweep(sweeps)[source]

Bases: object

Element-wise pairing of sweep axes.

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.

Parameters:

sweeps (tuple[Sweep, ...])

quchip.fit_a_dress(chip, *, coupling_targets=None, observable_targets=None, fit_parameters=None, max_hilbert_dim=10000, seed_strength_bounds=(1e-06, 0.25), max_nfev=1000)[source]

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.

Returns:

Fitted chip clone, loss, residual history, per-target ObservableReport tuples, packed parameters, and solver metadata.

Return type:

FitADressResult

Raises:

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.

class quchip.FitADressResult(chip, loss, history, initial_targets, final_targets, initial_params, final_params, solver_info)[source]

Bases: object

Result of a fit_a_dress() optimization run.

Parameters:
chip

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.

Type:

Chip

loss

Final objective (sum of squared, scale-normalized residuals).

Type:

float

history

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.

Type:

Any

initial_targets

One ObservableReport per target, evaluated on the seed chip.

Type:

tuple[ObservableReport, …]

final_targets

One ObservableReport per target, evaluated on the fitted chip.

Type:

tuple[ObservableReport, …]

initial_params

{parameter_name: seed_value} — the starting point passed to the optimizer.

Type:

dict[str, float]

final_params

{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.

Type:

dict[str, float]

solver_info

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.

Type:

dict[str, Any]

chip: Chip
loss: float
history: Any
initial_targets: tuple[ObservableReport, ...]
final_targets: tuple[ObservableReport, ...]
initial_params: dict[str, float]
final_params: dict[str, float]
solver_info: dict[str, Any]
rebind(seed: BaseDevice | str, /) BaseDevice[source]
rebind(seed: BaseDevice | str, /, *more: BaseDevice | str) tuple[BaseDevice, ...]

Look up the fitted clones matching one or more seed devices.

fit.rebind(qb, tc, cr) replaces the qb_f = chip.device_map[qb.label] triple most tutorials opened with.

Parameters:

*seeds (BaseDevice or str) – One or more devices (or their labels) from the seed chip passed to fit_a_dress(). At least one is required.

Returns:

The matching device(s) on chip (the fitted clone), in input order. A single positional seed returns that device directly; two or more return a tuple.

Return type:

BaseDevice or tuple[BaseDevice, …]

Raises:

ValueError – No seeds were given.

class quchip.ObservableReport(kind, label, target, initial, final, evaluator)[source]

Bases: object

Per-target record from a fit_a_dress run.

Parameters:
kind

Observable kind: "freq", "anharmonicity", "chi", "zz", "exchange", or "g".

Type:

str

label

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.

Type:

Any

target

The value the optimizer tried to match (GHz).

Type:

float

initial

Observable value at the seed chip, before optimization (GHz).

Type:

float

final

Observable value at the fitted chip, after optimization (GHz).

Type:

float

evaluator

"full" if this target was evaluated on the whole chip or "local" if it was evaluated on a one-hop subsystem (see max_hilbert_dim in fit_a_dress()).

Type:

str

kind: str
label: Any
target: float
initial: float
final: float
evaluator: str
quchip.analyze_cross_resonance(durations, ctrl0, ctrl1, sigma_ctrl0=None, sigma_ctrl1=None, t_offset=0.0)[source]

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

Specifically:

ω_ZQ = (p_ctrl0_Q - p_ctrl1_Q) / 2 ω_IQ = (p_ctrl0_Q + p_ctrl1_Q) / 2

for Q ∈ {X, Y, Z}.

Parameters:
  • 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 type:

CRHamiltonianResult

Raises:

RuntimeError – If all initial guesses fail to converge for either control state.

Examples

>>> import numpy as np
>>> from quchip import analyze_cross_resonance
>>> durations = np.linspace(0, 400, 20)  # ns
>>> ctrl0 = {"x": np.sin(0.02 * durations), "y": np.zeros_like(durations),
...          "z": np.cos(0.02 * durations)}
>>> ctrl1 = {"x": np.sin(0.03 * durations), "y": np.zeros_like(durations),
...          "z": np.cos(0.03 * durations)}
>>> result = analyze_cross_resonance(durations, ctrl0, ctrl1)
>>> zx, zx_err = result.coeffs()["ZX"]  # GHz
quchip.analyze_cr_susceptibility(chip, control, target, *, drive=None)[source]

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.

Return type:

CRSusceptibilityResult

Raises:

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).

quchip.analyze_dispersive_readout(chi, kappa, tau, *, n_photons=None, eps=None, delta_r=0.0, n_crit=None, levels=2)[source]

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"].

  • tau (Any) – Integration time in ns.

  • 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 below f_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.

Return type:

DispersiveReadoutResult

Raises:

ValueError – If both or neither of n_photons and eps are given (a static argument-presence check — never a traced-value comparison).

Examples

>>> from quchip import analyze_dispersive_readout
>>> ro = analyze_dispersive_readout(chi=0.002, kappa=0.005, tau=500.0, n_photons=2.0)
>>> snr, p_err = ro.snr, ro.assignment_error

chi and kappa are typically taken from eliminate(chip, "readout_res").effective_params[qubit].

quchip.analyze_static_zz(chip, device_a, device_b)[source]

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.

Parameters:
  • chip (Chip)

  • device_a (str or BaseDevice) – The two devices whose static ZZ is analyzed.

  • device_b (str or BaseDevice) – The two devices whose static ZZ is analyzed.

Return type:

StaticZZResult

Examples

>>> from quchip import Capacitive, Chip, DuffingTransmon
>>> from quchip.analysis import analyze_static_zz
>>> q0 = DuffingTransmon(freq=5.0, anharmonicity=-0.25, levels=3, label="q0")
>>> q1 = DuffingTransmon(freq=5.2, anharmonicity=-0.24, levels=3, label="q1")
>>> chip = Chip([q0, q1], couplings=[Capacitive(q0, q1, g=0.01)])
>>> result = analyze_static_zz(chip, "q0", "q1")
>>> zz = result.zz  # exact residual ZZ, GHz
quchip.effective_hamiltonian(chip, subspace)[source]

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.

Parameters:
  • chip (Chip)

  • 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.

Return type:

EffectiveHamiltonianResult

Examples

>>> from quchip import Capacitive, Chip, DuffingTransmon
>>> from quchip.analysis import effective_hamiltonian
>>> q0 = DuffingTransmon(freq=5.0, anharmonicity=-0.25, levels=3, label="q0")
>>> q1 = DuffingTransmon(freq=5.2, anharmonicity=-0.24, levels=3, label="q1")
>>> chip = Chip([q0, q1], couplings=[Capacitive(q0, q1, g=0.01)])
>>> result = effective_hamiltonian(chip, ["q0", "q1"])
>>> result.h_eff.shape
(4, 4)
class quchip.CRHamiltonianResult(IX, IY, IZ, ZX, ZY, ZZ, IX_err, IY_err, IZ_err, ZX_err, ZY_err, ZZ_err, params_ctrl0=None, params_ctrl1=None, cov_ctrl0=None, cov_ctrl1=None, cost_ctrl0=0.0, cost_ctrl1=0.0)[source]

Bases: object

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.

Parameters:
IX: float
IY: float
IZ: float
ZX: float
ZY: float
ZZ: float
IX_err: float
IY_err: float
IZ_err: float
ZX_err: float
ZY_err: float
ZZ_err: float
params_ctrl0: ndarray | None = None
params_ctrl1: ndarray | None = None
cov_ctrl0: ndarray | None = None
cov_ctrl1: ndarray | None = None
cost_ctrl0: float = 0.0
cost_ctrl1: float = 0.0
coeffs()[source]

Return {name: (value_ghz, err_ghz)} for all six coefficients.

Return type:

dict[str, tuple[float, float]]

summary()[source]

Print and return a formatted summary of all six coefficients.

Return type:

str

class quchip.CRSusceptibilityResult(m_control_0, m_control_1, IX_per_amplitude, ZX_per_amplitude, control, target, drive)[source]

Bases: object

Store weak-drive CR coefficients per unit programmed drive amplitude.

For control-state-conditioned target-transition matrix elements

\[m_z = \langle\widetilde{z_c,1_t}|D_c|\widetilde{z_c,0_t}\rangle,\]

the effective-Hamiltonian convention

\[H_\mathrm{eff} = \tfrac{1}{2}(IX\,I\!X + ZX\,Z\!X)\]

gives IX_per_amplitude = m_0 + m_1 and ZX_per_amplitude = m_0 - m_1. Values are backend-native complex scalars and remain JAX-traceable.

Parameters:
  • m_control_0 (Any)

  • m_control_1 (Any)

  • IX_per_amplitude (Any)

  • ZX_per_amplitude (Any)

  • control (str)

  • target (str)

  • drive (str)

m_control_0, m_control_1

Dressed target-transition matrix elements conditioned on the control occupying |0> and |1>.

IX_per_amplitude, ZX_per_amplitude

Weak-drive Pauli coefficients per unit signal amplitude.

control, target, drive

Resolved labels.

m_control_0: Any
m_control_1: Any
IX_per_amplitude: Any
ZX_per_amplitude: Any
control: str
target: str
drive: str
class quchip.DispersiveReadoutResult(pointer_states, photon_numbers, sigma, snr, assignment_error, dephasing_rate, chi_eff, validity, notes)[source]

Bases: object

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 χ).

Parameters:
pointer_states

Complex α_j in the IQ plane, shape (levels,).

Type:

Any

photon_numbers

Steady-state photons |α_j|², shape (levels,).

Type:

Any

sigma

Integrated vacuum-noise blob width 1/√(2κτ) (dimensionless, α-plane units).

Type:

Any

snr

|α₁ α₀|·√(2κτ).

Type:

Any

assignment_error

½·erfc(SNR/(2√2)) — optimal linear discriminant between two equal Gaussians.

Type:

Any

dephasing_rate

Measurement-induced dephasing Γ_m = κ·|α₁ α₀|²/2 in 1/ns.

Type:

Any

chi_eff

chi, or chi/(1 + n̄₀/n_crit) when n_crit was given (GHz).

Type:

Any

validity

See above.

Type:

dict[str, Any]

notes

Explicitly dropped physics.

Type:

list[str]

pointer_states: Any
photon_numbers: Any
sigma: Any
snr: Any
assignment_error: Any
dephasing_rate: Any
chi_eff: Any
validity: dict[str, Any]
notes: list[str]
summary()[source]

Print and return a formatted summary.

Concrete values only — call it outside jax.jit/grad regions.

Return type:

str

class quchip.EffectiveHamiltonianResult(h_eff, basis, device_labels)[source]

Bases: object

Store the des-Cloizeaux effective Hamiltonian on a labeled computational subspace.

h_eff is built as S^-1/2 (W E W^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 = W W^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/2 W 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.

Parameters:
h_eff

Dense Hermitian matrix, GHz, ordered as basis.

Type:

Any

basis

Full chip-length bare-occupation tuples spanning the subspace, in h_eff row/column order.

Type:

tuple[tuple[int, …], …]

device_labels

Chip device labels in tensor-product order, for reading basis tuples.

Type:

tuple[str, …]

h_eff: Any
basis: tuple[tuple[int, ...], ...]
device_labels: tuple[str, ...]
describe()[source]

Print and return the matrix with labeled row/column kets.

Concrete values only — call outside jax.jit/grad regions; a traced matrix renders as <traced>.

Return type:

str

class quchip.StaticZZResult(zz, pathways, device_a, device_b, device_labels)[source]

Bases: object

Store exact static ZZ between two devices, plus its 2nd-order SW pathway attribution.

Parameters:
zz

E(1,1) - E(1,0) - E(0,1) + E(0,0), identical to dispersive_shift(device_a, device_b)() — exact, not perturbative.

Type:

Any

pathways

(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.

Type:

list[tuple[tuple[int, …], Any]]

device_a, device_b

Resolved device labels.

device_labels

Chip device labels in tensor-product order, for reading pathways’ occupation tuples.

Type:

tuple[str, …]

Amounts stay in the array namespace of the chip's parameters (JAX in, JAX
out) traceable and differentiable, precision-filtered only on the
concrete path (:func:`~quchip.chip.sw.pathway_attribution`).
zz: Any
pathways: list[tuple[tuple[int, ...], Any]]
device_a: str
device_b: str
device_labels: tuple[str, ...]
describe()[source]

Print and return the exact ZZ plus the leading virtual pathways.

Concrete values only — call outside jax.jit/grad regions; traced amounts render as <traced>.

Return type:

str

class quchip.ModelMapping[source]

Bases: object

Base class for a single third-party <-> quchip device conversion.

source

Registry key (see source_key()) of the third-party type this mapping imports from. None means the mapping supports export only.

Type:

str or None

target

The quchip device type this mapping exports to. Required whenever export_model() is overridden; None means import-only.

Type:

type or None

library

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.

Type:

str or None

Subclassing registers the mapping automatically
\* setting ``source`` registers it for :func:`import_object` under that

key; a second subclass reusing the same source raises TypeError at class-definition time.

\* overriding :meth:`export_model` registers it for :func:`export_object`

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.

The abstract base itself (``source is None`` and no ``export_model``
override) registers nothing.
source: ClassVar[str | None] = None
target: ClassVar[type | None] = None
library: ClassVar[str | None] = None
import_model(obj, **opts)[source]

Convert third-party object obj into a quchip device.

Override to support import. The base implementation raises NotImplementedError.

Parameters:
Return type:

Any

export_model(device, **opts)[source]

Convert quchip device into a third-party object.

Override to support export; overriding requires setting target. The base implementation raises NotImplementedError.

Parameters:
Return type:

Any

class quchip.EigenbasisDevice(energies, *, charge_operator=None, phase_operator=None, levels=None, label=None, source_type=None, collapse_model='fermi_golden', coupling_channel=None, collapse_rate_threshold=1e-08, **noise_kwargs)[source]

Bases: CircuitDevice

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.

  • label (str or None)

  • 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.

  • collapse_model (see) – CircuitDevice.

  • coupling_channel (see) – CircuitDevice.

  • collapse_rate_threshold (see) – CircuitDevice.

  • **noise_kwargs – Forwarded to BaseDevice.

Raises:

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.

tunable_param_names = ()

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 the DeviceModel lineage — 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").

physics_notes()[source]

Return declared assumptions, including the frozen-import snapshot note.

Return type:

list[str]

to_dict()[source]

Extend CircuitDevice.to_dict() with the eigenbasis snapshot data.

Return type:

dict[str, Any]

classmethod from_dict(d)[source]

Reconstruct an EigenbasisDevice from to_dict() output.

Parameters:

d (dict[str, Any]) – Dict produced by to_dict().

Return type:

EigenbasisDevice

quchip.from_scqubits(obj, **opts)[source]

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.

Raises:
Parameters:
Return type:

Any

quchip.to_scqubits(device_or_chip, **opts)[source]

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().

Raises:
Parameters:
  • device_or_chip (Any)

  • opts (Any)

Return type:

Any

quchip.plot_energy_levels(chip, *, ax=None, max_states=None)[source]

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_1 n_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).

Return type:

Figure

Examples

>>> import quchip as qc
>>> from quchip.viz.chip import plot_energy_levels
>>> qb0 = qc.DuffingTransmon(freq=5.0, anharmonicity=-0.3, levels=3)
>>> qb1 = qc.DuffingTransmon(freq=5.2, anharmonicity=-0.3, levels=3)
>>> chip = qc.Chip([qb0, qb1], [qc.Capacitive(qb0, qb1, g=0.005)])
>>> fig = plot_energy_levels(chip, max_states=6)
quchip.plot_expectation(result, *, keys=None, ax=None, linewidth=2.5, legend=True, real=True)[source]

Plot dict-form expectation values over time.

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(O rho(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.

Parameters:
  • result (SimulationResult) – Output of quchip.engine.simulate(), with e_ops passed as a dict.

  • 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.

  • linewidth (float) – Line width for every trace.

  • legend (bool) – Whether to draw a legend of the plotted keys.

  • real (bool) – When True, draw only the real part of each trace; when False, draw both the real (solid) and imaginary (dashed) parts.

Returns:

The figure holding the expectation-trace axes (ax.figure when ax was given).

Return type:

Figure

Raises:
  • TypeErrore_ops was not passed as a dict (observable_traces is None).

  • KeyError – An entry in keys does not resolve to a registered trace or a valid (key, index) selector.

quchip.plot_graph(chip, path='chip_topology.html', *, full=True, exclude=None, layout='force_atlas', height='800px', width='100%')[source]

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()).

Parameters:
  • chip (Chip) – The chip to visualise.

  • path (str) – Destination .html path. Returned for convenience.

  • full (bool) – If False, hide drives and crosstalk unless exclude is given.

  • exclude (set of str, optional) – Any of {"coupling", "drive", "crosstalk"} to hide entirely.

  • layout (str) – "force_atlas" or "hierarchical".

  • height (str) – CSS dimensions forwarded to pyvis.network.Network.

  • width (str) – CSS dimensions forwarded to pyvis.network.Network.

Returns:

path, unchanged — returned for convenience so the call can be chained into whatever opens/serves the file.

Return type:

str

Raises:
  • ImportErrorpyvis is not installed.

  • ValueErrorlayout is not one of "force_atlas" or "hierarchical".

quchip.plot_populations(result, *, trace_out=None, computational=False, ax=None, linewidth=2.5, legend=True, colors=None, threshold=0.01)[source]

Plot basis-state populations over time.

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_1 n_2 ...> of the retained subsystems. States whose peak population stays below threshold are hidden; set threshold to 0 to show every state.

Parameters:
  • result (SimulationResult) – Output of quchip.engine.simulate().

  • 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:
  • RuntimeErrortrace_out is given but no states were stored (pass options={"store_states": True} to the solver).

  • ValueErrortrace_out would remove every subsystem.

quchip.plot_sequence(sequence, *, ax=None, lane_order=None, color_by='device')[source]

Plot a QuantumSequence as a lane chart.

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.

Parameters:
  • sequence (QuantumSequence) – The schedule to visualise.

  • ax (matplotlib.axes.Axes, optional) – Existing axes to draw onto. When None a new figure is created.

  • lane_order (list of (target_label, drive_label), optional) – Explicit ordering (top-to-bottom). Defaults to sorted lane keys.

  • color_by ({"device", "line"}) – Group bars by target device or by drive line for colour mapping.

Returns:

The figure holding the lane-chart axes (ax.figure when ax was given).

Return type:

Figure

Raises:

ValueErrorcolor_by is not "device" or "line".

quchip.plot_state(result, index, *, trace_out=None, computational=False, mode='population', ax=None, cmap='RdBu_r', color=None)[source]

Plot a single stored state at time-index index.

Two modes are supported:

  • "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).

Parameters:
  • result (SimulationResult) – Output of quchip.engine.simulate(), with options={"store_states": True}.

  • 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:
  • IndexErrorindex is outside [-N, N) for N = len(result.times).

  • ValueErrormode 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.

  • n (int) – Eigenstate index (0 <= n < device.levels).

  • ax (matplotlib.axes.Axes, optional) – Existing axes to draw onto. When None a new figure is created.

  • color (str, optional) – Bar colour. Defaults to a per-index tab10 cycle.

Returns:

The figure holding the bar-chart axes (ax.figure when ax was given).

Return type:

Figure

Raises:

IndexErrorn is outside [0, device.levels).

Examples

>>> import quchip as qc
>>> transmon = qc.DuffingTransmon(freq=5.0, anharmonicity=-0.3, levels=4)
>>> transmon.plot_wavefunction(n=1)
quchip.plot_wigner(result, index=-1, *, trace_out=None, xvec=None, yvec=None, ax=None, cmap='RdBu_r', colorbar=True)[source]

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(rho n_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.

Parameters:
  • result (SimulationResult) – Output of quchip.engine.simulate(), with options={"store_states": True}.

  • 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.

  • colorbar (bool) – Whether to attach a colorbar.

Returns:

The figure holding the Wigner-function axes (ax.figure when ax was given).

Return type:

Figure

Raises:
  • IndexErrorindex 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).

References

  • Wigner, Phys. Rev. 40, 749 (1932).

  • Cahill & Glauber, Phys. Rev. 177, 1882 (1969).

  • Leonhardt, Essential Quantum Optics (2010), Ch. 3.

Modules

analysis

quchip.analysis — physics analysis tools.

backend

Backend selection and concrete backend implementations.

chip

Chip topology — the composite quantum system and its two-body couplings.

control

Classical control surface: lines, signal chain, and envelopes.

declarative

Declarative physics model API.

devices

Device models — truncated-Hilbert-space quantum systems owned by a chip.

engine

Engine pipeline: Chip ResolvedFrame HamiltonianDescription SolveProblem.

interop

Third-party model interoperability: ModelMapping authoring surface and registry.

inverse_design

Inverse-design utilities.

results

Backend-agnostic simulation output containers.

sweep

Declarative parameter sweeps for chip studies.

utils

Physical constants and shared utilities.

viz

Lazy-loaded Matplotlib/pyvis visualization helpers.