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")
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()
keysight-logo-svg
Program
Program
Duration 80 ns
Layers 1
Targets 6
Repetitions
Layer #0
Layer #0
Duration 80 ns
awgs 0
RFWaveform on ('awgs', 0)

Parameters
Duration 80 ns
Amplitude 0.25
Frequency Array(name=rf, shape=(3,), dtype=float, unit=Hz, value=[5.1 GHz, 5.2 GHz, 5.3 GHz])
Envelope GaussianEnvelope(2.0)
Instantaneous Phase 0 rad
Post-phase 0 rad
1
RFWaveform on ('awgs', 1)

Parameters
Duration 80 ns
Amplitude 0.25
Frequency Array(name=rf, shape=(3,), dtype=float, unit=Hz, value=[5.1 GHz, 5.2 GHz, 5.3 GHz])
Envelope GaussianEnvelope(2.0)
Instantaneous Phase 0 rad
Post-phase 0 rad
2
RFWaveform on ('awgs', 2)

Parameters
Duration 80 ns
Amplitude 0.25
Frequency Array(name=rf, shape=(3,), dtype=float, unit=Hz, value=[5.1 GHz, 5.2 GHz, 5.3 GHz])
Envelope GaussianEnvelope(2.0)
Instantaneous Phase 0 rad
Post-phase 0 rad
digs 0
Acquisition on ('digs', 0)

Parameters
Duration 80 ns
Integration Filter
RFWaveform

Parameters
Duration 80 ns
Amplitude 0.25
Frequency Array(name=rf, shape=(3,), dtype=float, unit=Hz, value=[5.1 GHz, 5.2 GHz, 5.3 GHz])
Envelope GaussianEnvelope(2.0)
Instantaneous Phase 0 rad
Post-phase 0 rad
1
Acquisition on ('digs', 1)

Parameters
Duration 80 ns
Integration Filter
RFWaveform

Parameters
Duration 80 ns
Amplitude 0.25
Frequency Array(name=rf, shape=(3,), dtype=float, unit=Hz, value=[5.1 GHz, 5.2 GHz, 5.3 GHz])
Envelope GaussianEnvelope(2.0)
Instantaneous Phase 0 rad
Post-phase 0 rad
2
Acquisition on ('digs', 2)

Parameters
Duration 80 ns
Integration Filter
RFWaveform

Parameters
Duration 80 ns
Amplitude 0.25
Frequency Array(name=rf, shape=(3,), dtype=float, unit=Hz, value=[5.1 GHz, 5.2 GHz, 5.3 GHz])
Envelope GaussianEnvelope(2.0)
Instantaneous Phase 0 rad
Post-phase 0 rad

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()
keysight-logo-svg
Program
Program
Duration 80 ns
Layers 1
Targets 6
Repetitions
Layer #0
Layer #0
Duration 80 ns
awgs 0
RFWaveform on ('awgs', 0)

Parameters
Duration 80 ns
Amplitude 0.25
Frequency 5.1 GHz
Envelope GaussianEnvelope(2.0)
Instantaneous Phase 0 rad
Post-phase 0 rad
1
RFWaveform on ('awgs', 1)

Parameters
Duration 80 ns
Amplitude 0.25
Frequency 5.2 GHz
Envelope GaussianEnvelope(2.0)
Instantaneous Phase 0 rad
Post-phase 0 rad
2
RFWaveform on ('awgs', 2)

Parameters
Duration 80 ns
Amplitude 0.25
Frequency 5.3 GHz
Envelope GaussianEnvelope(2.0)
Instantaneous Phase 0 rad
Post-phase 0 rad
digs 0
Acquisition on ('digs', 0)

Parameters
Duration 80 ns
Integration Filter
RFWaveform

Parameters
Duration 80 ns
Amplitude 0.25
Frequency 5.1 GHz
Envelope GaussianEnvelope(2.0)
Instantaneous Phase 0 rad
Post-phase 0 rad
1
Acquisition on ('digs', 1)

Parameters
Duration 80 ns
Integration Filter
RFWaveform

Parameters
Duration 80 ns
Amplitude 0.25
Frequency 5.2 GHz
Envelope GaussianEnvelope(2.0)
Instantaneous Phase 0 rad
Post-phase 0 rad
2
Acquisition on ('digs', 2)

Parameters
Duration 80 ns
Integration Filter
RFWaveform

Parameters
Duration 80 ns
Amplitude 0.25
Frequency 5.3 GHz
Envelope GaussianEnvelope(2.0)
Instantaneous Phase 0 rad
Post-phase 0 rad

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.

On this page