quchip.control.sequence¶
Declarative pulse programming for a Chip.
QuantumSequence is the user-facing scheduling API. It tracks
per-(device, drive) timing cursors, per-device virtual-Z phase
frames, and cross-device barriers, then materializes the schedule into
DriveOp objects for the engine.
Conventions¶
Times are ns, frequencies GHz (ordinary, not angular).
Virtual-Z shifts accumulate into later microwave pulses on the same device (charge and phase drives); baseband flux pulses are unaffected, matching the lab-frame semantics of software-Z (McKay et al., PRA 96, 022330 (2017)).
All sweep axes stay JAX-traceable: pulse parameters, delays, and envelope fields flow into
SolveProblemwithout Python-side concretization.
Examples
>>> from quchip import (
... DuffingTransmon, ChargeDrive, Chip, QuantumSequence, Gaussian
... )
>>> q = DuffingTransmon(freq=5.0, anharmonicity=-0.25, levels=3)
>>> drive = ChargeDrive(target=q)
>>> chip = Chip([q])
>>> seq = QuantumSequence(chip)
>>> _ = seq.schedule(q, envelope=Gaussian(duration=20.0, amplitude=0.05))
>>> seq.total_duration
20.0
Classes
|
Declarative pulse sequence builder for a |
- class quchip.control.sequence.QuantumSequence(chip)[source]¶
Bases:
objectDeclarative pulse sequence builder for a
Chip.Tracks per-
(device, drive)timing cursors and per-device virtual-Z phase frames. Append pulses withschedule()(or the conveniencescharge(),phase(),flux()); synchronize channels withbarrier()anddelay(). The schedule is materialized lazily intoDriveOpobjects bybuild_problem()andbuild_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:simulate()/simulate_batch()— schedule and solve in one call (the example-facing path).solve()/solve_many()— solve aSolveProblem/ batch you already hold.The module-level
simulate()/solve_problem()/solve_many()— the low-level “I already havedrive_ops/ aSolveProblem” tier.
- Parameters:
chip (Chip) – Chip this sequence schedules against. Supplies the device and coupling maps used to resolve scheduling targets, the wired
ControlEquipmentlines, and the frame/backend settings used bybuild_problem()andsimulate().
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 (aParametricDrivepumps 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 uniqueParametricDriveline 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 — seeeliminate()’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_freqforChargeDrive; always required (no device-frequency fallback) forPhaseDriveandTwoPhotonDrive; ignored forFluxDriveand basebandParametricDrivepumps.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:
- charge(target, *, envelope, freq=None, phase=0.0)[source]¶
Schedule a
ChargeDrivepulse; freq defaults todevice.drive_freq.- Parameters:
target (str | BaseDevice)
envelope (BaseEnvelope)
freq (float | None)
phase (float)
- Return type:
- phase(target, *, envelope, freq, phase=0.0)[source]¶
Schedule a
PhaseDrivepulse; freq is required.- Parameters:
target (str | BaseDevice)
envelope (BaseEnvelope)
freq (float)
phase (float)
- Return type:
- flux(target, *, envelope)[source]¶
Schedule a
FluxDrivepulse (no carrier frequency).- Parameters:
target (str | BaseDevice)
envelope (BaseEnvelope)
- Return type:
- pump(coupling, *, envelope, freq=None, start_time=None, phase=0.0)[source]¶
Schedule an edge pump: baseband δ(t)=A(t) when
freqis None, else A(t)·cos(2πft-φ).- Parameters:
coupling (str | BaseCoupling)
envelope (BaseEnvelope)
freq (float | None)
start_time (float | None)
phase (float)
- Return type:
- 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 aFluxDrivepulse whose envelope amplitude is that frequency shift in GHz. The resulting Hamiltonian contribution is the linear detuning termδω(t) n̂.envelopeshould be passed withamplitude=None(or any placeholder) — this method replaces the amplitude with the computed δω.target_freqmay be a JAX tracer (e.g.chip.freq(other_device)).- Parameters:
target (str | BaseDevice) – Device to flux-pulse.
target_freq (float) – Target
0 → 1frequency in GHz.envelope (BaseEnvelope) – Envelope template with all timing parameters set. Its
amplitudeattribute is replaced by the computed δω; any value passed asamplitudeis ignored.
- Returns:
Handle to the scheduled entry, usable with
.vary().- Return type:
- 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:
target (str | BaseDevice)
angle (float)
- Return type:
None
- delay(scope, duration)[source]¶
Insert an idle delay on all drive channels of scope.
- Parameters:
scope (str | BaseDevice)
duration (float)
- Return type:
- 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 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
SolveProblemfrom this sequence.- Parameters:
tlist (array-like, optional) – Save/output time grid in ns. Defaults to the grid built by
_resolve_tlist()fromtotal_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 forsolve()orsolve_problem().- Return type:
- vary(field, values, *, name=None)[source]¶
Create a
BatchAxisover 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 viaPulseHandle.vary()on the handle returned byschedule()instead.values (array-like) – Sequence of values for
field, one per batch point.name (str, optional) – Axis name recorded in
ProblemBatch.axesandProblemBatch.params_at(). Defaults to field.
- Returns:
Sequence-level axis, consumable by
build_batch()orzip().- Return type:
- 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(), orDelayHandle.vary()). Every axis must have the same length.- Returns:
Combined axis that
build_batch()treats as a single dimension: pointisupplies pointifrom every zipped axis simultaneously, rather than the outer product.- Return type:
- 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
ProblemBatchwraps aSolveBatch: 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:
- 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 aninitial_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(); passcheck_truncation=Falseto opt out or retunetruncation_threshold.partition, defaultTrue, forwards tosimulate(): when the chip splits into independent sub-chips (Chip.partition()), each component is dispatched separately and combined into aPartitionedSimulationResult. This only engages wheninitial_stateisNoneor aMapping— a string shorthand or a concrete state is resolved via_resolve_initial_state_spec()first and always takes the joint path. Passpartition=Falseto force the joint solve unconditionally.
- 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(...)).backendscopes this one call exactly as insimulate().The batched solve does not pass through
solve_problem(), so the Hilbert-truncation safety net is applied per batch element here (default on); passcheck_truncation=Falseto opt out or retunetruncation_threshold.- Parameters:
- Return type:
- 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:
- clone()[source]¶
Return a deep copy.
_entriesis the single source of truth, so only it is deep-copied; the chip is shared, matching the previous shallow-copy behavior.- Return type:
- 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: