Reference to control stack in transmon element
Description
After discussions between me and @kel85uk, we propose to add a new parameter to the transmon_element class, which provides a reference to a control stack (as a string), which can be used to run all experiments with said transmon object. This will enable both real and mock experiments to be run using the same code, simply by choosing either a real or mock transmon element.
Motivation
As we have been developing the code for transmon experiments, there has been some discussion about how to structure the measurement functions and transmon_element object, and the role of the control stack in particular. As a recap, the transmon_element is an object which contains all the parameters necessary to carry out an experiment on a given transmon device (and generates a device_config file listing these parameters). This transmon element is given as an argument to a measurement function which runs a particular experiments. The measurement function takes all the parameters it needs from the transmon element, then initialises a gettable and calls the run method of the measurement control, where the control stack is called via the get method of the gettable, generating the experimental data. Here, we have a simple example of a measurement function, where the transmon element is given by the argument device.
def measure_heterodyne_spectroscopy(
MC, device, frequencies, hardware_averages=6000, analysis=True
):
ro_freq = ManualParameter("ro_freq", label="Readout frequency", unit="Hz")
# Disable sync mode on any QRMs or QCMs that may be running
for instr in Pulsar_qrm_CS_Component.instances():
instr.sequencer0_sync_en(False)
for instr in Pulsar_qcm_CS_Component.instances():
instr.sequencer0_sync_en(False)
for instr in Pulsar_qcm_CS_Component.instances():
instr.sequencer1_sync_en(False)
spec_gettable = ScheduleVectorAcqGettable(
device=device,
schedule_function=sps.heterodyne_spec_sched,
schedule_kwargs={
"pulse_amp": device.ro_pulse_amp,
"pulse_duration": device.ro_pulse_duration,
"frequency": ro_freq,
"acquisition_delay": device.ro_acq_delay,
"integration_time": device.ro_acq_integration_time,
"port": device.ro_port,
"clock": device.ro_clock,
},
device_cfg=device.generate_device_config(),
mapping_cfg=device.MAPPING_CFG,
control_stack=device.control_stack,
real_imag=False,
acq_instr="qrm0",
hardware_averages=hardware_averages,
)
MC.settables(ro_freq)
MC.setpoints(frequencies)
MC.gettables([spec_gettable])
label = f"Resonator spectroscopy {device.name}"
MC.run(label)
ba.Basic1DAnalysis(label=label).run()
if analysis:
return sa.ResonatorSpectroscopyAnalysis(label=label).run()
The issue we have is how to handle both real and mock experiments. In mock transmon experiments, we use a transmock device element, which inherits from the transmon base class and contains a number of additional parameters for mocking the characteristics of a transmon device. I have also written a mock control stack for transmon experiments (in the experiments_transmon repo https://gitlab.com/orange-quantum-systems/experiments_transmon/-/merge_requests/4#684346006ab1d86d070413087fe8e377db879aca) which can be used in place of the real control stack and generates mock experimental data. A key principle is that we want the measurement code to be exactly the same for a real experiment and a mock experiment, so that mock experiment is a true test of the measurement function. The only difference is that we use a transmock device element instead of the base transmon_element class, everything else remains the same.
The question then becomes how to tell the gettable to use the mock control stack instead of the real one when the transmock object is used. In the prototype code above, we can see that I have stored the ControlStack object itself in the transmock object and am giving this as an argument to the gettable. The idea is the real transmon element would have a real control stack and the transmock would have a mock control stack, and the gettable would simply access the correct control stack via device.control_stack. This initial idea has been rejected on the grounds that it violates the principle of single responsibility.
After discussions between me and @kel85uk, we have come up with a design pattern which we think preserves the design philosophy of the mock experiments (no code should change between real and mock, except the transmon element) while still obeying software design principles. We propose to include a new qcodes parameter in the transmon_element base class which contains a string which references the correct control stack by name. Then, in the init method of the gettable, we could use find_instrument to get a reference to this control stack. This would allow us to distinguish between real and mock experiments, and could also enable us to specify whether to use the qblox or zhinst control stack, for example. This obeys the principle of single responsibility because, from a user perspective, the transmon element has only one responsibility: to contain all the information necessary for the running of a transmon experiment, and the name of the control stack is one such necessary piece of information.
If no-one has any objections to this design pattern, I can create a new MR where I add this new control stack parameter to the transmon element base class. This proposal also requires a change in scheduler: in @dcrielaard's MR quantify-scheduler!109 (diffs) (not yet merged), there will have to be a line in the init method where name of the control stack is used as input to a qcodes find_instrument function to get the control stack object.
Finally, we can see in the code example above that the mapping config is also included in the transmon element object and given as an input to the gettable, just like the control stack. A proposed change to this design pattern will be put forward in another issue in quantify scheduler.