quchip.control.sequence

Declarative pulse programming for a Chip.

QuantumSequence is the user-facing scheduling API. It tracks per-endpoint timing cursors, per-device virtual-Z phase frames, and cross-device barriers, then materializes the schedule into classical DriveOp and field CoherentOp records for the engine.

Conventions

  • Times are in ns. Frequencies are in GHz (ordinary, not angular).

  • Each carrier pulse follows the virtual-Z frame of one device. By default, this is the device that its line drives. Virtual-Z shifts accumulate into later carrier pulses in that frame on any line, but not into baseband flux pulses. This matches 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 go into SolveProblem without 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

QuantumSequence(chip)

Declarative pulse sequence builder for a Chip.

class quchip.control.sequence.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(). build_problem() and build_batch() materialize the schedule lazily into engine control-operation records.

Where a device/drive is expected, you can use the object or its string label.

Simulation uses 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 that this sequence schedules against. It 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)
>>> drive = ChargeDrive(target=q)
>>> chip = Chip([q])
>>> chip.wire(drive)
>>> 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, frame=None, detuning=None)[source]

Schedule a pulse on target.

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

    Accepted forms, resolved in this order:

    • network.expose(...).input: scheduled at that external reference plane without entering ControlEquipment.

    • BaseDrive: scheduled directly on that drive.

    • BaseDevice: uses the device’s first connected drive. If a device has multiple drives, pass the drive object explicitly.

    • BaseCoupling — uses the unique CouplingDrive that targets that coupling. If a coupling has multiple control lines, pass the drive object explicitly.

    • str: a label, resolved in this order. First, a device label. Then, only when the label is absent from the device map, a coupling label (the two label spaces are disjoint). Then, only when the label is absent from both, a control-equipment line label, scheduled directly on that line. This third fallback lets a caller schedule by a drive’s label after its device or coupling target is eliminated. See the retarget registry of eliminate(), which keeps a converted line’s label. A control-line label that collides with a device/coupling label is shadowed by the device/coupling resolution.

  • envelope (Envelope) – Pulse envelope.

  • freq (float, optional) – Optional carrier frequency in GHz. Omitting it leaves the signal carrier-free. Drive-specific conveniences can supply their own explicit default, such as charge() using chip.freq(device).

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

  • frame (str or BaseDevice, optional) – Device whose virtual-Z frame the carrier follows. vz() on that device shifts this pulse’s phase. Defaults to the device that the drive line targets. Coupling-drive and coherent-input pulses have no default frame. A cross-resonance tone on the control’s line names the target device, because its phase sets the axis of the target’s conditional rotation. A frame requires freq.

  • detuning (float, optional) – Carrier offset δ in GHz, referenced to the pulse start t0. The pulse delivers E(t - t0) exp(i phase) exp(-2πi freq t) exp(-2πi δ (t - t0)). freq stays the frame frequency, so the pulse is the same in that frame at every t0. A carrier at freq = f + δ without detuning is a fixed oscillator instead, with phase phase - 2π δ t0 relative to the frame at f. None adds no offset term. pulse.<index>.detuning rebinds, sweeps, and differentiates like freq. A detuning requires freq.

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

Schedule a charge-drive pulse.

Parameters:
  • target (str or BaseDevice) – Driven device.

  • envelope (Envelope) – Pulse envelope; time parameters are in ns and amplitudes in GHz.

  • freq (float or None, default=None) – Carrier frequency in GHz; None uses chip.freq(target).

  • phase (float, default=0.0) – Carrier phase in radians.

  • frame (str or BaseDevice or None, default=None) – Device whose virtual-Z frame the carrier follows. None uses target. See schedule().

  • detuning (float or None, default=None) – Carrier offset in GHz, referenced to the pulse start. See schedule().

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

Schedule a phase-drive pulse.

Parameters:
  • target (str or BaseDevice) – Driven device.

  • envelope (Envelope) – Pulse envelope; time parameters are in ns and amplitudes in GHz.

  • freq (float) – Carrier frequency in GHz.

  • phase (float, default=0.0) – Carrier phase in radians.

  • frame (str or BaseDevice or None, default=None) – Device whose virtual-Z frame the carrier follows. None uses target. See schedule().

  • detuning (float or None, default=None) – Carrier offset in GHz, referenced to the pulse start. See schedule().

flux(target, *, envelope)[source]

Schedule a baseband flux-drive pulse.

Parameters:
  • target (str or BaseDevice) – Driven flux-tunable device.

  • envelope (Envelope) – Baseband pulse envelope in GHz versus time in ns.

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

Schedule an edge pump.

Parameters:
  • coupling (str or BaseCoupling) – Modulable coupling to pump.

  • envelope (Envelope) – Pump-amplitude envelope in GHz versus time in ns.

  • freq (float or None, default=None) – Carrier frequency in GHz; None applies the envelope at baseband.

  • start_time (float or None, default=None) – Absolute start time in ns; None uses the channel cursor.

  • phase (float, default=0.0) – Carrier phase in radians.

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 calculates δω = target_freq − chip.freq(device) and schedules a FluxDrive pulse whose envelope amplitude is that frequency shift in GHz. The resulting Hamiltonian contribution is the linear detuning term δω(t) n̂. Pass envelope with amplitude=None (or any placeholder). This method replaces the amplitude with the calculated δω.

target_freq can be a JAX tracer (for example chip.freq(other_device)).

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

  • target_freq (float) – Target 0 → 1 frequency in GHz.

  • envelope (Envelope) – Envelope template with all timing parameters set. Its amplitude is replaced by the calculated δω, so any passed value 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). It accumulates into the phase_offset of every later carrier pulse whose frame is target, on any line. A pulse’s frame defaults to the device that its line drives, and schedule(..., frame=...) names another device. This matches the standard software-Z trick for transmons (McKay et al., PRA 96, 022330 (2017)). Baseband flux pulses are not affected.

Parameters:
  • target (str or BaseDevice) – Device receiving the virtual frame shift.

  • angle (float) – Z-frame shift in radians.

delay(scope, duration)[source]

Insert an idle delay on all drive channels of a target.

Parameters:
  • scope (str or BaseDevice) – Device label or object whose channels advance.

  • duration (float) – Delay in ns.

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. This guarantees that pulses scheduled after the barrier start no earlier than any pulse scheduled before it.

Parameters:

*labels (str or BaseDevice) – Devices to synchronize. If omitted, all device-drive cursors are synchronized.

property total_duration: Any

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

property scheduled_ops: tuple[DriveOp | CoherentOp, ...]

Materialize scheduled classical-drive and coherent-field operations.

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, approximation=None, frame=None, states=None, dissipation=True, run_args=None, *, duration=None)[source]

Build a single SolveProblem from this sequence.

Parameters:
  • tlist (array-like, optional) – Grid passed to the numerical solver, in ns. Its first time is the initial-state time and its last time ends the calculation. Scheduled signals keep their absolute times in a partial interval.

  • duration (float, optional) – Interval from zero in ns, with automatic sampling. It can extend the schedule but cannot cut it short, and cannot be combined with tlist. Without either argument, the scheduled duration is used.

  • solver (str, optional) – Backend solver name; defaults to the backend’s own default solver.

  • options (dict, optional) – Backend numerical options. Use states to select state retention.

  • states ({"all", "final", "none"} or None, default None) – None saves all deterministic states and preserves native stochastic defaults. Explicit values keep the full state history, only the final state, or neither. Requested observable traces are kept independently.

  • run_args (dict or None, default None) – Native trajectory call keywords: QuTiP seeds, ntraj, heterodyne, target_tol, timeout; Dynamiqs keys, method, gradient, etas. Assembled physics and native options cannot be overridden here.

  • e_ops (dict, optional) – Expectation operators keyed by device label (or a 2-tuple of device labels for a two-body operator), mapped to a local operator (or a pair of local operators). They are decomposed into per-band terms before reaching the solver.

  • initial_state (Any, optional) – Initial state; see simulate() for accepted forms, coordinate conventions, and the meaning of None.

  • approximation (Approximation, optional) – Engine approximation override. None uses the chip default.

  • frame (FrameSpec, optional) – Integration-frame override; defaults to the chip’s declared frame. "auto" derives constraints from delivered scheduled signals and weights them over tlist[-1] - tlist[0].

  • dissipation (bool, default=True) – Include authored collapse channels when true.

Returns:

Frozen problem (chip, compiled engine result, initial state, tlist, collapse operators, and expectation operators), ready for solve() or solve_problem().

Return type:

SolveProblem

resolve(*, frame=None, approximation=None)[source]

Resolve the backend-neutral Hamiltonian and noise description.

Parameters:
  • frame (FrameSpec or None, default=None) – Resolution frame, or None to use the chip default.

  • approximation (Approximation or None, default=None) – Engine approximation, or None to use the chip default.

hamiltonian()[source]

Return this sequence’s canonical time-dependent Hamiltonian.

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

Create a BatchAxis over a public parameter path or state.

Parameters:
  • field (str) – "initial_state" or any dotted path exposed by parameters. Pulse handles remain the concise way to vary a pulse field immediately after scheduling it.

  • values (array-like) – Sequence of values for field, one per batch point.

  • name (str, optional) – Axis name recorded in SolveBatch.axes and SolveBatch.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()). All axes need the same length.

Returns:

Combined axis that build_batch() treats as a single dimension: point i supplies point i from every zipped axis together, not the outer product.

Return type:

ZippedBatchAxis

build_batch(*axes, tlist=None, solver=None, options=None, e_ops=None, initial_state=None, approximation=None, states=None, dissipation=True, run_args=None, duration=None)[source]

Build a batched solve request from explicit sweep axes.

Parameters:
  • *axes (BatchAxis or ZippedBatchAxis) – Independent or pairwise-zipped sweep axes.

  • tlist (array-like or None, default=None) – Solver time grid in ns.

  • solver (str or None, default=None) – Backend solver name; None uses its default.

  • options (dict or None, default=None) – Backend numerical options.

  • run_args (dict or None, default None) – Native trajectory call keywords: QuTiP seeds, ntraj, heterodyne, target_tol, timeout; Dynamiqs keys, method, gradient, etas. Assembled physics and native options cannot be overridden here.

  • e_ops (dict or None, default=None) – Local expectation-operator specifications.

  • initial_state (Any or None, default=None) – State reused at every batch point; see simulate() for accepted forms and coordinate conventions. An initial_state axis supplies one state per point.

  • approximation (Approximation or None, default=None) – Engine approximation override.

  • states ({"all", "final", "none"} or None, default=None) – None saves all deterministic states and preserves native stochastic defaults. Explicit values select state retention; native storage conflicts raise.

  • dissipation (bool, default=True) – Include authored collapse channels when true.

  • duration (float or None, default=None) – Automatically sampled interval from zero, in ns.

simulate(tlist=None, solver=None, options=None, e_ops=None, initial_state=None, *, backend=None, partition=True, approximation=None, states=None, dissipation=True, run_args=None, duration=None)[source]

Build and solve one scheduled simulation.

Parameters:
  • tlist (array-like, optional) – Sample times in ns. Do not use with duration. If omitted, quchip builds an automatic grid through the schedule end.

  • solver (str, optional) – Backend solver name, such as "mesolve" or "sesolve".

  • options (dict, optional) – Backend-specific solver options.

  • run_args (dict or None, default None) – Native trajectory call keywords: QuTiP seeds, ntraj, heterodyne, target_tol, timeout; Dynamiqs keys, method, gradient, etas. Assembled physics and native options cannot be overridden here.

  • e_ops (dict, optional) – Named observables to evaluate.

  • initial_state (object or mapping, optional) – A label of local energy levels, such as {"q0": 1}, or configured string shorthand names its dressed state: the eigenstate of the solve’s undriven static lab-frame Hamiltonian, as kept by its approximation and excitation cutoff, that is assigned to the label. Its overlap with the bare product is made real and nonnegative, and it is placed in the solve frame at tlist[0] as the label target of overlap(). None and {} give the all-ground label. With ordinary couplings under the default RWA, that state is the bare vacuum. When the solve uses the chip’s approximation, a label start is the same physical state as chip.state() before the frame transform. At a nonzero rotating-frame start time, the frame transform is applied at tlist[0], so the state need not match the lab-frame vector returned by chip.state(). chip.bare_state(...) gives a bare product, and a mapping with a local ket raises. QuTiP Qobj and dynamiqs QArray kets or density matrices use resolved solver coordinates. Arrays, symbolic expressions, and callables are authored-space kets projected onto kept levels. A CompactState reuses captured excitation coordinates and requires the same local energy bases.

  • backend (object or {"qutip", "dynamiqs"}, optional) – Per-call backend override.

  • partition (bool, default=True) – Solve independent chip components separately when the initial state permits it.

  • approximation (Approximation, optional) – Override the chip’s approximation for this solve.

  • states ({"all", "final", "none"} or None, default=None) – None saves all deterministic states and preserves native stochastic defaults. State storage policy for the returned result.

  • dissipation (bool, default=True) – Include declared collapse channels when True.

  • duration (float, optional) – Positive simulation end time in ns when tlist is omitted.

Returns:

Time samples, requested observables, and states according to states.

Return type:

SimulationResult

Raises:

ValueError – If tlist and duration are both supplied, timing is invalid, or automatic sampling lacks the concrete timing it requires.

Notes

A per-call backend outranks the chip and process defaults, and foreign-native initial states are coerced at the solve boundary. Python evaluates an inline initial_state=chip.state(...) before this scope opens, so a traced state still requires a JAX-capable surrounding backend.

Partitioning returns a PartitionedSimulationResult and requires initial_state to be None or a mapping; string shorthand and concrete states take the joint path.

simulate_batch(*axes, tlist=None, solver=None, options=None, e_ops=None, initial_state=None, backend=None, progress=True, approximation=None, states=None, dissipation=True, run_args=None, duration=None, max_workers=None)[source]

Build and solve a batched sweep over pulse or sequence parameters.

Parameters:
  • *axes (BatchAxis or ZippedBatchAxis) – Cartesian sweep axes. A ZippedBatchAxis pairs member axes pointwise and contributes one batch dimension.

  • tlist (Any | None) – As for simulate(); one initial state is reused for every batch point unless it is a supported mapping.

  • solver (str | None) – As for simulate(); one initial state is reused for every batch point unless it is a supported mapping.

  • options (dict | None) – As for simulate(); one initial state is reused for every batch point unless it is a supported mapping.

  • run_args (dict | None) – As for simulate(); one initial state is reused for every batch point unless it is a supported mapping.

  • e_ops (dict | None) – As for simulate(); one initial state is reused for every batch point unless it is a supported mapping.

  • initial_state (Any | None) – As for simulate(); one initial state is reused for every batch point unless it is a supported mapping.

  • backend (Any | None) – As for simulate(); one initial state is reused for every batch point unless it is a supported mapping.

  • progress (bool, default=True) – Show backend batch progress when supported.

  • approximation (Approximation, optional) – Per-batch approximation override.

  • states ({"all", "final", "none"} or None, default=None) – None saves all deterministic states and preserves native stochastic defaults. State storage policy for each batch result.

  • dissipation (bool, default=True) – Include declared collapse channels.

  • duration (float, optional) – Positive end time in ns when tlist is omitted.

  • max_workers (int or None, default=None) – Worker processes for a backend that solves points in parallel, such as QuTiP. None takes the CPU count that loky reports, which honors LOKY_MAX_CPU_COUNT, or fewer when the available memory cannot hold them. 1 solves in this process.

Returns:

Results indexed by the Cartesian or zipped axis shape.

Return type:

SimulationBatchResult

Raises:

ValueError – If an axis is invalid, timing is inconsistent, or a sweep changes a structural quantity that cannot vary in one batch.

subspace_propagator(devices, levels=(0, 1), *, frame='dressed', tlist=None, duration=None, solver=None, options=None, backend=None, approximation=None, dissipation=None, progress=False, max_workers=None)[source]

Return the propagator of this sequence on the dressed states of a product of levels.

The inputs are the labels that give each selected device one of levels and every other device level 0, in the order of itertools.product(levels, repeat=len(devices)). One batch starts each input in its dressed state and projects every final state on the label target of every input, as amplitude_array() does. The record converts the matrix to frame. The dressed frame takes the dressed frequencies and the dressed ground energy from the static model of the solve, with its approximation and excitation cutoff. The solve leaves out collapse channels, so the matrix is the propagator of the Hamiltonian alone.

Parameters:
  • devices (device object, str, or sequence of them) – Selected devices, in the order of the levels of each label.

  • levels (sequence of int, default=(0, 1)) – Levels of each selected device.

  • frame ({"dressed", "lab", "integration"}, default="dressed") – Frame of the returned matrix.

  • tlist (Any | None) – As for simulate(). The propagator runs from tlist[0] to tlist[-1].

  • duration (Any | None) – As for simulate(). The propagator runs from tlist[0] to tlist[-1].

  • solver (str | None) – As for simulate(). The propagator runs from tlist[0] to tlist[-1].

  • options (dict | None) – As for simulate(). The propagator runs from tlist[0] to tlist[-1].

  • backend (Any | None) – As for simulate(). The propagator runs from tlist[0] to tlist[-1].

  • approximation (Approximation | None) – As for simulate(). The propagator runs from tlist[0] to tlist[-1].

  • dissipation (bool or None, default=None) – None leaves out the chip’s collapse channels and warns when it has any. False leaves them out without a warning. True raises, because the matrix cannot include them. The result’s omitted_channels names the channels that the solve left out.

  • progress (bool, default=False) – Show backend batch progress when supported.

  • max_workers (int or None, default=None) – As for simulate_batch().

Returns:

Matrix, labels, leakage of each input, frame frequencies, and the batch of solves.

Return type:

PropagatorResult

Raises:

ValueError – If dissipation is True, frame is unknown, or a device or level is missing or repeated.

active_patch(*, hops=1, method='sw', local=False, order=None)[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:
  • hops (int, default=1) – Coupling-graph expansion beyond scheduled targets.

  • method ({"sw", "exact"}, default="sw") – Reduction method forwarded to each elimination.

  • local (bool, default=False) – Forwarded to each elimination. Each step then computes its generator on the patch around the eliminated device.

  • order (sequence of devices or labels, optional) – Spectators to eliminate, in this order. By default, quchip chooses the order as eliminate_spectators() does.

clone()[source]

Return an independently editable model and pulse schedule.

property parameters: Mapping[str, Any]

Chip and scheduled-pulse values available to with_params().

property settings: Mapping[str, Any]

Read-only sequence structure, separate from numerical parameters.

with_params(bindings)[source]

Return a cloned sequence with values rebound.

Parameters:

bindings (mapping[str, Any]) – Chip parameter paths or pulse.<index> fields and new values.

describe()[source]

Plain-text timeline of the scheduled pulses.

One row per pulse shows the resolved time window, drive → device, envelope with its declared parameters, and carrier frequency, followed by a count of delays, barriers, and virtual-Z entries. A pulse whose frame differs from its device also names that frame. Returns a string: print(seq.describe()).