docs/pulse.md gives a pulse program, its schedule, its run as a job, its check and a refusal, all run by the docs checker against the command line (the receipt a block writes verified), and states the reader's constructs, the model, the integrator and its bound, what a receipt binds, and the options. The engine's and the command line's READMEs (with their Simplified Chinese translations), the service's README, the docs index and the OpenQASM 3 feature matrix name the pulse layer; the agent docs are regenerated from the matrix. The reader's module documentation states the qubit timeline a gate waits for. Co-Authored-By: Claude Opus 5.5 <[email protected]>
7.6 KiB
Pulse and dynamics
A pulse program is OpenQASM 3 whose gates are given as the control signals
that make them: OpenPulse calibrations (defcal) on physical qubits, played
on frames of ports. The engine reads one into a schedule, integrates the
schedule under a Lindblad master equation, and answers as a job of the
method pulse: the result is the same bits on every platform, its error is
bounded and stated, and its receipt binds the control program, its
calibrations, the device and target snapshot it ran on, and the integrator
and its step. A pulse result can be checked by anyone by running its request
again.
This is a simulation. The engine reaches no device's pulse interface.
A program
OPENQASM 3.0;
defcalgrammar "openpulse";
cal {
extern port d0;
extern port a0;
frame q0 = newframe(d0, 5.0e9, 0);
frame c0 = newframe(a0, 5.0e9, 0);
}
defcal x $0 { play(q0, gaussian(0.4, 64dt, 16dt)); }
defcal measure $0 -> bit { return capture_v2(c0, 100dt); }
bit c;
x $0;
c = measure $0;
wai-quantum pulse schedule x.qasm --demo-key # each frame's timeline, the gate calls, the waveforms
wai-quantum run x.qasm --method pulse --shots 1000 --demo-key # integrated: the capture's reading, the bound, what the receipt binds
wai-quantum run x.qasm --method pulse --shots 1000 --demo-key --json > job.json
wai-quantum pulse check job.json --demo-key # the receipt, the program's digest, and the run again, bit for bit
wai-quantum run x.qasm --method pulse --target synthetic-line-5 --demo-key # the qubit's frequency, T1 and T2 from a target's calibration
wai-quantum run x.qasm --method pulse --pulse '{"levels": 3, "qubits": {"0": {"anharmonicity_mhz": -220}}}' --demo-key # three levels: the leakage shown
wai-quantum run x.qasm --method pulse --pulse '{"tolerance": 1e-15}' --demo-key # exits 1
The last run is refused with J041: the rounding of a schedule this long in
double precision is estimated above the tolerance asked, and the message says
so rather than giving a number it cannot stand behind.
What the reader takes
qasm::pulse runs the program in order, each calibration's body at its call:
- Ports reach a qubit by name:
d3drives qubit 3,m3is its measurement port anda3its acquisition port. The job'spulse.portsmaps other names ({"u0": {"kind": "drive", "qubit": 1}}). - Frames are made in
calblocks withnewframe(port, frequency, phase)(hertz and radians). Each has a time of its own. A play starts at its frame's time and moves it on by the waveform's length;delay[d] fmoves it on;barrier f, gmoves both to the later. A gate starts when its qubits are free, all the frames its calibration names together. - Waveforms:
constant,gaussian,sech,gaussian_square,dragandsine, and explicit samples{s0, s1, …}, real or complex. A template's sample k is taken at t = k − (l − 1)/2 samples, centred and not lifted; the exponential and the sine are the engine's own, so every sample is the same bits everywhere. - Frame changes:
shift_phase,set_phase,shift_frequencyandset_frequency. A frame's carrier runs on continuously across a frequency change. - Captures:
captureandcapture_v0tocapture_v4, each read as a discriminated bit, written to the program's bit when a measurement's calibration returns it. - Time is counted in samples of
dt(pulse.dt_ns, 0.5 ns unless given). A duration in nanoseconds that is not a whole number of samples is refused (Q458).
What it does not take is refused with a code and its place: Q454 to
Q461, listed in the diagnostic codes. The circuit
methods keep the same calibrations verbatim and run none of them (Q450,
Q451); the pulse reader runs them (Q453).
The model and the bound
quantum_pulse holds each qubit as a ladder of two or three levels in the
frame rotating at its frequency, under the rotating-wave approximation, the
envelope held for each sample. The drive is what the qubit's drive frames
play, at their carriers' phases; at phase 0 a real envelope turns the qubit
about +X. Relaxation and pure dephasing come from T1 and T2, and pairs of
qubits may have a static ZZ coupling. A capture reads its qubit's state at
its first sample and ends that qubit's schedule, since the readout's
back-action is not modelled; plays on measurement ports are scheduled and
timed and drive nothing.
Within a sample each driven qubit hears one tone, so in the frame of that
tone the generator is constant. Each sample is a fixed number of steps of a
Taylor series of order 12, then a diagonal phase back to the qubits' frames.
Only + − × ÷ and sqrt are used, in a fixed order, so the density matrix
is the same bits on x86_64, arm64 and in wasm32; CI pins its digest on each.
The bound: with λ an upper bound on the generator's norm, each step leaves
out at most (hλ)^13/13! / (1 − hλ/14) of the state, and the exact evolution
is a contraction in the trace norm, so the final state is within T(1 + T) of
the exact solution of the model's equation, T the sum over every step. Every
probability the result gives is within that, stated as
accuracy.truncation_bound. Rounding is stated beside it as an estimate
(accuracy.rounding_estimate), not a proof; accuracy.bound is their sum.
A step longer than 1/λ, or a schedule whose bound and rounding would pass
the tolerance, is refused (J041), naming the steps that would meet it.
The bound is the distance from the model's exact solution; it says nothing of how far the model is from a device. The tests hold the integrator to closed forms within the bound it states: a Rabi turn, the axis a quarter turn of phase gives, a detuned drive, T1 decay and T2 dephasing.
What a receipt binds
A pulse run's result holds, inside what its receipt seals:
| Field | What it is |
|---|---|
pulse.calibration.program |
the BLAKE3 of the control program's text |
pulse.calibration.calibrations |
the BLAKE3 of its cal, defcal and defcalgrammar statements as written |
pulse.calibration.schedule |
the BLAKE3 of the schedule's JSON |
pulse.calibration.device |
the BLAKE3 of the device's JSON (pulse.device, each parameter with where it came from) |
pulse.calibration.target |
the target snapshot's name, hash and calibration id, when one is named |
pulse.integrator |
the integrator, its order, its step and the steps taken |
pulse.density_hash |
the BLAKE3 of the final density matrix's bits |
wai-quantum pulse check, the pulse_check tool on both MCP servers and
POST /v1/pulse/check verify the receipt, hold the program's digest to the
request's text, and run the request again here, naming every field whose
value differs. A pulse result that does not reproduce was not made from that
request by this engine.
The options
The job's pulse object (every field optional): dt_ns, levels (2 or 3),
tolerance (1e-6 unless given), substeps (chosen so each step times λ is
at most 0.5 unless given), qubits (by index: frequency_ghz, rabi_mhz,
anharmonicity_mhz, t1_us, t2_us), couplings
([{"qubits": [0, 1], "zz_khz": 50}]) and ports. A qubit's parameters come
from pulse.qubits, then the target's calibration, then the defaults: 5 GHz
plus 100 MHz per index, a Rabi frequency of 50 MHz at full amplitude, an
anharmonicity of −300 MHz, and no decay.
The simulator holds a space of up to 16 (four qubits at two levels, two at three), a frame within 1 GHz of its qubit, one tone per qubit at a time, and schedules of up to 2²² samples.