Download

Download this file as Jupyter notebook: hello_hardware.ipynb.

Running a program on hardware

This guide gives an overview of the initial steps to set up and run a “Hello Quantum World!” program on hardware using QCS. We will assume that the physical configuration of the hardware is already set up and that all required software has been installed via the Test Station Manager (TSM).

Setting up

The first step is to define the software instances of the channels used to send and receive signals. In this example, we use two AWG channels and two digitizers.

[2]:
import keysight.qcs as qcs

# set the following to True when connected to hardware:
run_on_hw = False

# instantiate channels representing two AWGs and two digitizers
awgs = qcs.Channels(range(2), "readoutawg")
digs = qcs.Channels(range(2), "readoutreceiver")

The virtual channels above then have to be linked to the physical channels present in hardware. See Channel mappers for details. For the purposes of this demonstration, we load a previously created ChannelMapper:

[3]:
mapper = qcs.load("../../assets/channel_map.qcs")

Defining a program

In this section, we define a program to run on the hardware configuration specified above. For this example, we send a Gaussian waveform through the AWG channels and connect its output to our downconverter/digitizer channels to collect the results. The local oscillator (LO) frequency of our AWG and downconverter is set to 4 GHz.

[4]:
# instantiate an empty program
program = qcs.Program()

# add a 4.15 GHz RF waveform with a Gaussian envelope to the program
gauss = qcs.RFWaveform(80e-9, qcs.GaussianEnvelope(num_sigma=4), 1, 4.15e9)
program.add_waveform(gauss, awgs)

# add an acquisition to the program
program.add_acquisition(80e-9, digs)

# specify the number of shots
program.n_shots(10)

program.draw()
keysight-logo-svg
Program
Program
Duration 80 ns
Layers 1
Targets 4
Repetitions Repeat with 10 repetitions
Layer #0
Layer #0
Duration 80 ns
readoutawg 0
RFWaveform on ('readoutawg', 0)

Parameters
Duration 80 ns
Amplitude 1
Frequency 4.15 GHz
Envelope GaussianEnvelope(4.0)
Instantaneous Phase 0 rad
Post-phase 0 rad
1
RFWaveform on ('readoutawg', 1)

Parameters
Duration 80 ns
Amplitude 1
Frequency 4.15 GHz
Envelope GaussianEnvelope(4.0)
Instantaneous Phase 0 rad
Post-phase 0 rad
readoutreceiver 0
Acquisition on ('readoutreceiver', 0)

Parameters
Duration 80 ns
1
Acquisition on ('readoutreceiver', 1)

Parameters
Duration 80 ns

Running on hardware

To execute a program, we need to instantiate the HclBackend with the ChannelMapper specified above and add it to the Executor as a pass.

[5]:
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("program1.hdf5")

# we are loading a previously run program here for this example
program = qcs.load("program1.hdf5")

Note

When executing multiple programs with HCL, it is possible to execute them in sequence or in parallel. This is specified with the blocking property of HclBackend. When blocking = True, programs are run sequentially and Python blocks any further execution of the current script. When blocking = False, the execution exits immediately and the program continues to run in the background, enabling the execution of parallel programs on separate chassis.

Retrieving data

After a program is run, output data is stored in a database managed by QCS. Users can access the data through the program’s get_* methods, or export the data to an HDF5 file for later access using the to_hdf5() method on the program, as shown above. A saved program is then loaded using the load()method.

The program’s data can be accessed through the following methods:

All these methods take an optional argument avg which when set to True will average the data over shots.

Note

If the I/Q demodulation is performed on hardware, as can be specified by setting hw_demod=True in the HclBackend, no trace data will be stored.

These methods all return Pandas dataframes:

[6]:
program.get_trace()
[6]:
(((Channels(labels=[0], name=readoutreceiver, absolute_phase=False)))) (((Channels(labels=[1], name=readoutreceiver, absolute_phase=False))))
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9
0 0.000220 0.001758 0.000439 0.000000 0.001758 0.000000 -0.000220 0.000879 -0.000659 -0.001318 0.002637 0.001099 0.002197 0.000439 0.002417 0.001538 0.003516 0.005054 0.003735 0.000879
1 -0.000439 0.001318 0.000000 -0.000220 -0.001978 0.001318 -0.000879 0.000220 0.000659 0.000439 0.002197 0.003296 0.002637 0.001318 0.001318 0.002637 0.001978 0.002417 0.003735 0.001538
2 0.000000 0.001318 -0.000439 -0.000659 -0.000659 0.000659 0.000439 -0.000659 0.000659 -0.000879 0.002417 0.002637 0.002637 0.001538 0.001978 0.002197 0.001758 0.002197 0.001318 0.002417
3 0.000439 0.001099 0.000879 0.002637 -0.000439 0.000439 0.000659 -0.000879 -0.000439 -0.001978 0.002197 0.002197 0.002417 0.002197 0.003076 0.003076 0.000659 0.001758 0.002637 0.001978
4 0.000220 0.001538 0.000439 0.001758 -0.000439 0.000000 0.001758 0.001099 -0.000439 -0.000220 0.000879 0.001318 0.001538 0.002856 0.000659 0.001318 0.001978 0.001538 0.004175 0.003296
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
379 0.000220 0.000220 0.000879 -0.002637 -0.000220 -0.000220 0.000000 0.000659 -0.000439 0.001758 0.001758 0.001758 0.002637 0.000220 0.002197 0.002637 0.003296 -0.000220 0.000439 0.003076
380 -0.000659 0.000879 -0.001318 0.000220 -0.000439 -0.000220 -0.000439 0.000000 0.000439 0.001758 0.002417 0.000879 0.003076 0.003516 0.000659 0.001978 0.001978 0.002637 0.001758 0.001758
381 0.000659 0.000000 -0.000220 -0.000220 0.000439 -0.001978 -0.000220 -0.000879 -0.000220 -0.001758 0.001758 0.001318 0.001758 0.001099 0.002417 0.000000 0.000879 0.003516 0.002197 0.001978
382 0.000220 0.000879 -0.000439 0.000220 0.000879 0.001318 0.000220 0.000000 0.000220 -0.000220 0.002856 0.002637 0.001978 0.001318 0.002417 0.002637 0.000879 0.004395 0.005054 0.001758
383 -0.001538 0.000879 -0.000220 -0.000659 0.000879 -0.001318 -0.000879 0.000439 0.000000 -0.001758 0.002417 0.002417 0.002197 0.002856 0.001318 0.002417 0.001099 0.002417 0.002856 0.001978

384 rows × 20 columns

This dataframe has the channels as its first column index, followed by the program’s repetitions (shots). Individual columns in the dataframe can be accessed by indexing the dataframe with first the channel and then the shot number. Note that other than the first column index (the channel), all other column indices are strings.

[7]:
df = program.get_trace()

# access trace data for channel 0, first shot
df[digs[0]]["0"]
[7]:
0      0.000220
1     -0.000439
2      0.000000
3      0.000439
4      0.000220
         ...
379    0.000220
380   -0.000659
381    0.000659
382    0.000220
383   -0.001538
Name: 0, Length: 384, dtype: float64

This data can also be plotted using:

[8]:
program.plot_trace(channel_subplots=False)

The trace above has the expected Gaussian envelope and oscillates with a frequency of 150 MHz, which is exactly the difference frequency between our LO frequency (4 GHz) and our pulse frequency (4.15 GHz).

The channel_subplots argument in qcs.programs.Program.plot_trace() determines whether or not the results from different channels are put on the same plot or whether subplots are created. By default, plotted data is averaged over shots. To disable this behavior, set avg=False:

[9]:
program.plot_trace(avg=False)

Trace data can also be plotted in terms of the power spectrum using the qcs.programs.Program.plot_spectrum() method:

[10]:
program.plot_spectrum(channel_subplots=False)

In most applications, we are more interested in I/Q-demodulated and classified data rather than the full trace data. Here the trace is integrated after being multiplied by an integration filter, which allows us to extract phase, frequency and amplitude information. Classification is performed by preparing each quantum state, measuring its distribution on the I/Q plane and using a classifier to distinguish them.

This demodulation can be performed either in software or hardware, and requires the use of an IntegrationFilter. For the program above, we can define an integration filter using the same RF waveform and use it to retrieve software-demodulated I/Q data:

[11]:
int_filter = qcs.IntegrationFilter(gauss)

program.get_iq(integration_filter=int_filter, stack_channels=True)
[11]:
0 1 2 3 4 5 6 7 8 9
(((Channels(labels=[0], name=readoutreceiver, absolute_phase=False)))) -0.208354-0.091978j -0.207841-0.093049j -0.207405-0.093960j -0.206787-0.094929j -0.208112-0.091943j -0.207649-0.093948j -0.206596-0.095823j -0.207161-0.094855j -0.208213-0.092252j -0.208361-0.091554j
(((Channels(labels=[1], name=readoutreceiver, absolute_phase=False)))) 0.081295-0.194943j 0.082409-0.194152j 0.083156-0.194026j 0.084314-0.193498j 0.081304-0.194818j 0.082934-0.193860j 0.084916-0.193247j 0.084050-0.193561j 0.081635-0.194723j 0.080858-0.194921j

Note that the get_iq() method can stack the channel index as rows in the dataframe, to allow users to spot correlations in the data more easily.

To enable I/Q demodulation in hardware, we need to pass the integration filter to the program’s acquisition, as shown in Sweeping program variables.


Download

Download this file as Jupyter notebook: hello_hardware.ipynb.

On this page