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
|
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:
objectComposite quantum system: devices + couplings + (optional) control.
The chip is the primary user-facing object. It exposes:
Hamiltonian construction (
hamiltonian()),dressed-state analysis (
dress(),energy(),freq(), …) delegated toChipAnalysis,state factories (
state(),bare_state()),observable construction (
observable(),e_ops()),solver surface (
solve(),solve_many()),serialization / cloning (
to_dict(),from_dict(),clone(),updated()).
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()orconnect().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
rwaoverrides inherit this (Nonemeans inherit).backend (str or Backend, optional) – Chip-specific backend.
Noneuses the process default.
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 itsrwa_keeps_band()retains — theapply_rwa_mask()of its full interaction. The same bands drop from stage 2’s decomposition, so the two views agree.This is the lab-frame Hamiltonian. At solve time, the engine applies a rotating-frame transformation and subtracts
2π Σ_i ω_ref,i n̂_ifor each device, whereω_ref,iis the frame reference resolved byquchip.engine.stage1_frames.resolve_frame(). The 2π boundary crossing and the subtraction both live inquchip.engine.stage2_assembly._build_static_h0(). Useframe_info()to inspect which reference frequency each device will use.- Returns:
Backend-native operator on
⨂_d H_d.- Return type:
- 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.
- 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 ofphysics_notes().
- 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:
- property backend: Backend¶
per-call override > chip-specific > process default.
The per-call override is the
backend=argument ofsimulate()/simulate_batch(scoped throughquchip.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 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 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-
Bathargument, 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 thephysics_notes()audit surface).
- 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 toNoneotherwise, 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 aNoiseChannelentry (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
- device_index(label)[source]¶
Tensor-product index of a device. Accepts a label string or object.
- Parameters:
label (str | BaseDevice)
- Return type:
- plot_graph(path='chip_topology.html', *, full=True, exclude=None, **kwargs)[source]¶
Render chip topology — delegates to
quchip.viz.chip.
- plot_energy_levels(*, ax=None, **kwargs)[source]¶
Render dressed spectrum — delegates to
quchip.viz.chip.
- 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 forchip.analysisfor 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:
- 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().
- dressed_spectrum()[source]¶
Raw dressed eigenvalue array without Python scalar coercion. See
ChipAnalysis.dressed_spectrum().- Return type:
- dressed_index(device_states=None, /, **device_state_kwargs)[source]¶
Dressed-state index matching a bare-state label, or
None. SeeChipAnalysis.dressed_index().
- bare_label(dressed_index)[source]¶
Bare-state label assigned to a dressed-state index. See
ChipAnalysis.bare_label().
- operator_in_dressed_basis(device, op, *, truncate=None)[source]¶
Embedded device operator transformed to the dressed eigenbasis.
See
ChipAnalysis.operator_in_dressed_basis().
- 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:
transition (str | BaseDevice | tuple[Mapping[str | BaseDevice, int], Mapping[str | BaseDevice, int]])
- Return type:
- state_components(state=None, /, *, n_components=5, **device_state_kwargs)[source]¶
Leading bare-basis probabilities for a dressed eigenstate. See
ChipAnalysis.state_components().
- 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:
device_a (str | BaseDevice)
device_b (str | BaseDevice)
- Return type:
- 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:
device_a (str | BaseDevice)
device_b (str | BaseDevice)
- Return type:
- dressed_anharmonicity(device)[source]¶
Dressed anharmonicity of one device with others grounded (GHz).
See
ChipAnalysis.dressed_anharmonicity().- Parameters:
device (str | BaseDevice)
- Return type:
- effective_subspace_hamiltonian(states)[source]¶
Dressed effective Hamiltonian in a labeled bare subspace.
See
ChipAnalysis.effective_subspace_hamiltonian().
- 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
targetreturns the full{label: freq}dict; a singletarget(label or device) returns one scalar 0→1 frequency. The runtime body is unchanged — underjax.jitthe scalar is a traced 0-d array, so the overload is type-only and does not alter traceability.- Parameters:
target (str | BaseDevice | None)
when (dict[str | BaseDevice, int] | None)
- Return type:
- frame_info()[source]¶
Per-device frame reference frequency
ω_ref,i(GHz). SeeChipAnalysis.frame_info().
- physics_notes()[source]¶
Aggregate
physics_notes()across every component.Returns a dict keyed
"chip"for the chip-level entry, and"<kind>:<label>"—kindone 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 fromcontrol_equipment’s wiring rather than per-deviceconnected_drives, so an edge-targetParametricDrive(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 (seecollapse_contributions()) — present even with no baths, since it applies regardless. Intended for inspection/audit rather than for runtime dispatch.
- 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.simulateconsult this automatically; call it directly to orchestrate solves yourself.- Return type:
- 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))
- 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=Nonethe array must already span the full chip Hilbert space.- Parameters:
data (Any)
device (str | BaseDevice | None)
- Return type:
- 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 usee_ops(), which keeps operators local so the demodulation pipeline can band-decompose and embed them correctly.- Parameters:
device (str | BaseDevice)
- Return type:
- e_ops(*, correlators=None, **specs)[source]¶
Build a dict-form
e_opsmapping 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")}, ... )
- set_state_order(*devices, levels=None)[source]¶
Declare the device order used to parse string-state shorthands.
After this is called,
bare_state(),state(), andsuperposition()accept single-string specifications where each character is one level per device in devices order. Level symbols default tog=0, e=1, f=2, h=3; digits0..9are 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:
devices (str | BaseDevice)
- 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}), ... )
- 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") whenset_state_order()has been called.Safe inside
jax.jit/grad/vmap: under tracing the assigned eigenvector column is selected through thelabel_eigensystem()array kernel, so dressed initial states are differentiable end-to-end. The global phase is gauge-dependent (eighcolumn convention) — populations and|overlap|figures of merit are unaffected.
- 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). Unlikestate()this does not diagonalize the coupled system.Accepts a string shorthand (e.g.
"eg1") whenset_state_order()has been called.
- 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:
lines (BaseDrive)
signal_chain (Sequence[SignalTransform] | None)
- Return type:
- 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_equipmentbecomesNone).
- 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_equipmentbecomesNone). Returns the detached equipment so it can be reconnected later.- Returns:
The equipment that was attached before detachment.
- Return type:
- solve(problem, *, check_truncation=True, truncation_threshold=0.001)[source]¶
Solve a typed
SolveProblemthrough this chip’s backend.Routes through the common
solve_problem()chokepoint, so the Hilbert-truncation safety net applies by default; passcheck_truncation=Falseto opt out or retunetruncation_threshold.- Parameters:
problem (SolveProblem)
check_truncation (bool)
truncation_threshold (float)
- Return type:
- 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 toquchip.engine.solve_many(), which owns the single ProblemBatch / SolveBatch / list ladder.- Parameters:
batch_or_problems (Any)
progress (bool)
- Return type:
- 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: