QProgram QDAC¶
qprogram-qdac adds QDevil QDAC support to QProgram, the pulse-level quantum programming DSL. The QDAC is a slow high-precision DAC, used most often for flux biasing.
The core DSL knows nothing about any instrument. This package plugs into it through the vendor hooks and contributes three things: a qdac namespace on every program, a capability profile describing what a QDAC channel can do, and .qp serialization for the operations it adds. Importing the package is what turns those on.
What the qdac namespace gives you¶
| Operation | What it does |
|---|---|
program.qdac.set_offset |
Hold a DC voltage on a channel until something changes it. The value may be a swept variable. |
program.qdac.play |
Upload an envelope to the channel's waveform engine, with dwell, delay, repetitions, stepped. |
program.qdac.set_trigger |
Arm chassis trigger outputs to fire at a sequence position, for a given duration. |
program.qdac.wait_trigger |
Halt the channel's sequencer until an external trigger arrives on an input port. |
Two facts about the instrument are declared in the profile rather than left in a comment. The QDAC has no FPGA, so a swept parameter is re-uploaded from the host between iterations, and qdac-default-v1 puts the enclosing loop host-side. A QDAC channel is single-channel, so the profile carries no IQ waveform tokens at all: an IQ pulse on a QDAC bus is a missing-capability error from qp.validate.
A first program¶
import qprogram as qp
from qprogram import BusSchema
from qprogram.waveforms import Ramp
from qprogram_qdac import QProgram
schema = BusSchema.flux_tunable_transmon()
flux = schema.q[0].flux
program = QProgram(label="flux-ramp", schema=schema)
program.qdac.set_offset(flux, 0.42)
program.qdac.set_trigger(flux, duration=50, position="start", outputs={1, 2})
program.qdac.play(flux, Ramp(from_amplitude=0.0, to_amplitude=1.0, duration=1000), dwell=100)
program.qdac.wait_trigger(flux, port=3)
print(qp.dumps(program))
That prints the program as .qp text:
#!QProgram 0.2
require qdac 0.2
metadata:
label: "flux-ramp"
schema:
element q:
drive info=IQ
readout info=IQ+acquires
flux info=single
body:
qdac.set_offset q[0].flux 0.42
qdac.set_trigger q[0].flux 50 outputs=[1, 2]
qdac.play q[0].flux Ramp(from_amplitude=0.0, to_amplitude=1.0, duration=1000) dwell=100
qdac.wait_trigger q[0].flux 3
The require qdac 0.2 line is the whole compatibility story. qp.load checks it against the installed extension, and imports this package on demand if the reader never did.
Where to go next¶
| If you want to ... | Read |
|---|---|
| install the package and run something | Getting started |
| see every operation and its arguments | Operations |
| know which programs a QDAC platform will accept | Capabilities and profiles |
read the .qp wire form of each operation |
Saving and loading |
| browse the generated API | API reference |
| map the operations onto QDAC hardware | Lowering onto hardware |
| work on this package | Contributing |
Status¶
The vendor protocol version is this package's own version, and .qp files carry it as require qdac <major>.<minor>. A file loads against any installed qprogram-qdac at that version or above: when a release changes an operation's wire form it registers a migration for it, and the older spelling is rewritten on the way in. A file asking for more than the installed package provides is a ParseError rather than a partial load. The Python API is pre-1.0 and allowed to move; the wire form is the steadier of the two.