Download
Download this file as Jupyter notebook: multiplexing.ipynb.
[2]:
import keysight.qcs as qcs
# set the following to True when connected to hardware:
run_on_hw = False
Multiplexing RF waveforms
This tutorial demonstrates how multiple virtual channels can be mapped to a single physical channel to enable control of e.g. multiple qudits through a single device channel.
We begin by defining our virtual channels:
[3]:
num_channels = 3
# we will map three virtual channels to one physical channel
virtual_awgs = qcs.Channels(range(num_channels), "awgs", True)
virtual_digs = qcs.Channels(range(num_channels), "digs")
# specify our physical channel addresses for AWG, digitizer & downconverter
physical_awg = (1, 1, 1)
physical_dig = (1, 2, 1)
physical_dnc = (1, 3, 1)
Next up, we define our channel mapper and assign all virtual channels to a single physical channel:
[4]:
lo_frequency = 5e9
mapper = qcs.ChannelMapper()
mapper.add_channel_mapping(
virtual_awgs, [physical_awg] * num_channels, qcs.InstrumentEnum.M5300AWG
)
mapper.add_channel_mapping(
virtual_digs, [physical_dig] * num_channels, qcs.InstrumentEnum.M5200Digitizer
)
mapper.add_downconverters(physical_dig, physical_dnc)
mapper.set_lo_frequencies([physical_awg, physical_dnc], lo_frequency)
We create a program with a set of three readout pulses followed by an acquisition:
[5]:
program = qcs.Program()
# define the pulse frequency as an array with three values
freq = qcs.Array("rf", value=[5.1e9, 5.2e9, 5.3e9])
# instantiate the RF waveform
waveform = qcs.RFWaveform(80e-9, qcs.GaussianEnvelope(), 0.25, freq)
program.add_waveform(waveform, virtual_awgs)
# add an acquisition with an integration filter that matches those frequencies
program.add_acquisition(qcs.IntegrationFilter(waveform), virtual_digs)
program.draw()
Program
Program
|
||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Layer #0
Layer #0
|
||||||||||||||||||
|
|
RFWaveform on ('awgs', 0)
Parameters
|
||||||||||||||||
|
RFWaveform on ('awgs', 1)
Parameters
|
|||||||||||||||||
|
RFWaveform on ('awgs', 2)
Parameters
|
|||||||||||||||||
|
|
Acquisition on ('digs', 0)
Parameters
|
||||||||||||||||
|
Acquisition on ('digs', 1)
Parameters
|
|||||||||||||||||
|
Acquisition on ('digs', 2)
Parameters
|
An equivalent way of writing this program would be to specify the frequency on each pulse (channel) individually, and add the waveforms separately:
[6]:
program = qcs.Program()
# instantiate the RF waveforms
waveform_1 = qcs.RFWaveform(80e-9, qcs.GaussianEnvelope(), 0.25, 5.1e9)
waveform_2 = qcs.RFWaveform(80e-9, qcs.GaussianEnvelope(), 0.25, 5.2e9)
waveform_3 = qcs.RFWaveform(80e-9, qcs.GaussianEnvelope(), 0.25, 5.3e9)
# add them to the program
program.add_waveform(waveform_1, virtual_awgs[0])
program.add_waveform(waveform_2, virtual_awgs[1])
program.add_waveform(waveform_3, virtual_awgs[2])
# add acquisitions
program.add_acquisition(qcs.IntegrationFilter(waveform_1), virtual_digs[0])
program.add_acquisition(qcs.IntegrationFilter(waveform_2), virtual_digs[1])
program.add_acquisition(qcs.IntegrationFilter(waveform_3), virtual_digs[2])
program.draw()
Program
Program
|
||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Layer #0
Layer #0
|
||||||||||||||||||
|
|
RFWaveform on ('awgs', 0)
Parameters
|
||||||||||||||||
|
RFWaveform on ('awgs', 1)
Parameters
|
|||||||||||||||||
|
RFWaveform on ('awgs', 2)
Parameters
|
|||||||||||||||||
|
|
Acquisition on ('digs', 0)
Parameters
|
||||||||||||||||
|
Acquisition on ('digs', 1)
Parameters
|
|||||||||||||||||
|
Acquisition on ('digs', 2)
Parameters
|
Rendering the program will display the three different waveforms on each virtual channel:
[7]:
program.render(channel_subplots=False, mapper=mapper)
We can run this program on hardware and acquire the resulting trace by again connecting our AWG output to our downconverter and digitizer.
[8]:
if run_on_hw:
# initialize the backend pass
backend = qcs.HclBackend(channel_mapper=mapper)
# the executor returns the program populated with results
program = qcs.Executor(backend).execute(program)
# (optional) export the data to an HDF5 file
program.to_hdf5("multiplexed_program.hdf5")
# we are loading a previously run program here for this example
program = qcs.load("multiplexed_program.hdf5")
The acquired trace is a superposition of the three waveforms on each virtual channel.
[9]:
program.plot_trace(channel_subplots=False)
Plotting the spectrum confirms that the tree tones are present. Note that since our LO frequency was set to 5 GHz, and our waveform frequencies are at 5.1 GHz, 5.2 GHz and 5.3 GHz respectively, we expect to measure the frequencies 100 MHz, 200 MHz and 300 MHz.
[10]:
program.plot_spectrum(channel_subplots=False)
This strategy is commonly used to multiplex qubit readout. To infer the different qubit states, users will typically look at the I/Q scatter diagrams for both the ground and excited state and classify based on where the points land on the I/Q plane.
[11]:
program.plot_iq(plot_type="scatter", channel_subplots=False)
Download
Download this file as Jupyter notebook: multiplexing.ipynb.