02 - Sweeping variables

Objective: Sweep the amplitude of a pulse in loopback to demonstrate control over the QICK.

We will sweep in two ways:

  • Within the QICK assembly language (advantage: faster, disadvantage: cannot be used to sweep the readout frequency)

  • With a Python outer loop (advantage: can sweep any parameter, disadvantage: slower)

[1]:
# Import the QICK drivers and auxiliary libraries
from qick import *
from tqdm.notebook import tqdm
%pylab inline
Populating the interactive namespace from numpy and matplotlib
[2]:
# Load bitstream with custom overlay
soc = QickSoc()
# Since we're running locally on the QICK, we don't need a separate QickConfig object.
# If running remotely, you could generate a QickConfig from the QickSoc:
#     soccfg = QickConfig(soc.get_cfg())
# or save the config to file, and load it later:
#     with open("qick_config.json", "w") as f:
#         f.write(soc.dump_cfg())
#     soccfg = QickConfig("qick_config.json")
soccfg = soc
# print(soccfg)

Hardware Configuration

tProc channel 7 : DAC 229 CH3 <-> Readout channel 0 : ADC 224 CH0

[3]:
class LoopbackProgram(AveragerProgram):
    def initialize(self):
        cfg=self.cfg

        # set the nyquist zone
        self.declare_gen(ch=cfg["res_ch"], nqz=1)

        #configure the readout lengths and downconversion frequencies
        self.declare_readout(ch=cfg["ro_ch"], length=self.cfg["readout_length"],
                             freq=self.cfg["pulse_freq"], gen_ch=cfg["res_ch"])

        freq=self.freq2reg(cfg["pulse_freq"], gen_ch=cfg["res_ch"], ro_ch=cfg["ro_ch"])  # convert frequency to dac frequency (ensuring it is an available adc frequency)
        self.set_pulse_registers(ch=cfg["res_ch"], style="const", freq=freq, phase=0, gain=cfg["pulse_gain"],
                                 length=cfg["length"])
        self.synci(200)  # give processor some time to configure pulses

    def body(self):
        self.measure(pulse_ch=self.cfg["res_ch"],
             adcs=[self.cfg["ro_ch"]],
             adc_trig_offset=self.cfg["adc_trig_offset"],
             wait=True,
             syncdelay=self.us2cycles(self.cfg["relax_delay"]))
[4]:
class SweepProgram(RAveragerProgram):
    def initialize(self):
        cfg=self.cfg

        # set the nyquist zone
        self.declare_gen(ch=cfg["res_ch"], nqz=1)

        self.r_rp=self.ch_page(self.cfg["res_ch"])     # get register page for res_ch
        self.r_gain=self.sreg(cfg["res_ch"], "gain")   #Get gain register for res_ch

        #configure the readout lengths and downconversion frequencies
        self.declare_readout(ch=cfg["ro_ch"], length=self.cfg["readout_length"],
                             freq=self.cfg["pulse_freq"], gen_ch=cfg["res_ch"])

        freq=self.freq2reg(cfg["pulse_freq"], gen_ch=cfg["res_ch"], ro_ch=cfg["ro_ch"])  # convert frequency to dac frequency (ensuring it is an available adc frequency)
        self.set_pulse_registers(ch=cfg["res_ch"], style="const", freq=freq, phase=0, gain=cfg["start"],
                                 length=cfg["length"])
        self.synci(200)  # give processor some time to configure pulses

    def body(self):
        self.measure(pulse_ch=self.cfg["res_ch"],
             adcs=[self.cfg["ro_ch"]],
             adc_trig_offset=self.cfg["adc_trig_offset"],
             wait=True,
             syncdelay=self.us2cycles(self.cfg["relax_delay"]))

    def update(self):
        self.mathi(self.r_rp, self.r_gain, self.r_gain, '+', self.cfg["step"]) # update gain of the pulse

Sweeping via the QICK assembly language

[5]:
config={"res_ch":6, # --Fixed
        "ro_ch":0, # --Fixed
        "relax_delay":1, # --Fixed
        "res_phase":0, # --Fixed
        "pulse_style": "const", # --Fixed
        "length":100, # [Clock ticks]
        "readout_length":200, # [Clock ticks]
        "pulse_gain":0, # [DAC units]
        "pulse_freq": 100, # [MHz]
        "adc_trig_offset": 100, # [Clock ticks]
        "reps":50,
        # New variables
        "expts": 20,
        "start":0, # [DAC units]
        "step":100 # [DAC units]
       }

prog =SweepProgram(soccfg, config)
expt_pts, avgi, avgq = prog.acquire(soc, progress=True)
[6]:
# Plot results.
sig = avgi[0][0] + 1j*avgq[0][0]
avgamp0 = np.abs(sig)
plt.figure(1)
plt.plot(expt_pts, avgi[0][0], label="I value; ADC 0")
plt.plot(expt_pts, avgq[0][0], label="Q value; ADC 0")
plt.plot(expt_pts, avgamp0, label="Amplitude; ADC 0")
# plt.plot(expt_pts, avg_di1, label="I value; ADC 1")
# plt.plot(expt_pts, avg_dq1, label="Q value; ADC 1")
# plt.plot(expt_pts, avg_amp1, label="Amplitude; ADC 1")
plt.ylabel("a.u.")
plt.xlabel("Pulse gain (DAC units)")
plt.title("Averages = " + str(config["reps"]))
plt.legend()
plt.savefig("images/Gain_sweep.pdf", dpi=350)
../../_images/tutorials_tprocv1_02_Sweeping_variables_8_0.png

Sweeping via a Python outer loop

[7]:
config={"res_ch":6, # --Fixed
        "ro_ch":0, # --Fixed
        "relax_delay":1, # --Fixed
        "res_phase":0, # --Fixed
        "pulse_style": "const", # --Fixed
        "length":100, # [Clock ticks]
        "readout_length":200, # [Clock ticks]
        "pulse_gain":0, # [DAC units]
        "pulse_freq": 100, # [MHz]
        "adc_trig_offset": 100, # [Clock ticks]
        "reps":50,
       }

sweep_cfg={"start":0, "step":100, "expts":20}
gpts=sweep_cfg["start"] + sweep_cfg["step"]*np.arange(sweep_cfg["expts"])

results=[]
for g in tqdm(gpts):
    config["pulse_gain"]=int(g)
    prog =LoopbackProgram(soccfg, config)
    results.append(prog.acquire(soc, progress=False))
results=np.transpose(results)
[8]:
sig = results[0][0][0] + 1j*results[0][0][1]
avgamp0 = np.abs(sig)
plt.figure(2)
plt.plot(gpts, results[0][0][0],label="I value; ADC 0")
plt.plot(gpts, results[0][0][1],label="Q value; ADC 0")
plt.plot(gpts, avgamp0,label="Amplitude; ADC 0")
plt.ylabel("a.u.")
plt.xlabel("Pulse gain (DAC units)")
plt.title("Averages = " + str(config["reps"]))
plt.legend()
plt.savefig("images/Gain_sweep_python.pdf", dpi=350)
../../_images/tutorials_tprocv1_02_Sweeping_variables_11_0.png