quchip.chip.chip

Composite quantum system — Chip bundles devices, couplings, and control.

A chip owns the devices (which own their local Hamiltonians), the couplings between them (which own their two-body interaction Hamiltonians), and, optionally, the ControlEquipment wiring classical control lines. The engine consumes a chip to produce a solver-ready problem.

All public parameters — device frequencies, coupling strengths, drive amplitudes, crosstalk coefficients — remain JAX-traceable and sweepable so a single loss function can span any of them.

Classes

Chip(devices[, couplings, ...])

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

class quchip.chip.chip.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