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_mapper.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()
Program
Program
|
||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Layer #0
Layer #0
|
||||||||||||||
|
|
RFWaveform on ('readoutawg', 0)
Parameters
|
||||||||||||
|
RFWaveform on ('readoutawg', 1)
Parameters
|
|||||||||||||
|
|
Acquisition on ('readoutreceiver', 0)
Parameters
|
||||||||||||
|
Acquisition on ('readoutreceiver', 1)
Parameters
|
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 ofHclBackend
. Whenblocking = True
, programs are run sequentially and Python blocks any further execution of the current script. Whenblocking = False
, the execution exits immediately and the program continues to run in the background, enabling the execution of parallel programs on separate chassis.
If you are running the program for the first time, you may need to log in. You will be prompted to enter your username and password. See User Management. for a guide on the login process,
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:
get_trace()
to access trace data,get_spectrum()
to calculate the power spectrum of the trace data,get_iq()
to access the I/Q data after demodulation on either hardware or software with a user-specifiedIntegrationFilter
,get_classified()
to access classified I/Q data using a user-specified classifier, such as theMinimumDistanceClassifier
.
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 theHclBackend
, 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.014062 | 0.013623 | 0.012305 | 0.012524 | 0.014722 | 0.013623 | 0.014941 | 0.017358 | 0.015381 | 0.014722 | -0.007251 | -0.000659 | -0.005273 | -0.006812 | -0.007251 | -0.005713 | -0.004834 | -0.007690 | -0.006372 | -0.008569 |
1 | 0.014722 | 0.014502 | 0.014941 | 0.013403 | 0.015381 | 0.013843 | 0.014502 | 0.014502 | 0.014722 | 0.012964 | -0.002856 | -0.006372 | -0.008569 | -0.001978 | -0.001099 | -0.005273 | -0.007690 | -0.003296 | -0.007251 | -0.005713 |
2 | 0.015161 | 0.014062 | 0.014282 | 0.013403 | 0.016260 | 0.011646 | 0.016699 | 0.014062 | 0.014941 | 0.013843 | 0.000000 | -0.008569 | -0.003076 | -0.002417 | -0.001978 | -0.001978 | 0.000220 | -0.003516 | -0.002637 | -0.004395 |
3 | 0.017578 | 0.016699 | 0.012524 | 0.012305 | 0.012524 | 0.014722 | 0.013403 | 0.012085 | 0.014722 | 0.013403 | -0.001978 | 0.002197 | -0.006152 | -0.001978 | 0.000220 | 0.001099 | 0.002197 | -0.004175 | -0.000659 | -0.001758 |
4 | 0.013403 | 0.014941 | 0.012305 | 0.011646 | 0.014062 | 0.012305 | 0.012085 | 0.014282 | 0.012305 | 0.012085 | -0.001978 | 0.003955 | -0.000659 | -0.003516 | -0.001538 | 0.002637 | -0.002197 | -0.000439 | -0.003516 | 0.003955 |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
379 | 0.005054 | 0.006812 | 0.007251 | 0.008789 | 0.006592 | 0.004395 | 0.006372 | 0.008350 | 0.007910 | 0.007690 | 0.003076 | 0.005493 | 0.004175 | 0.005933 | 0.005273 | 0.000000 | -0.001758 | -0.001978 | 0.002197 | -0.001758 |
380 | 0.005933 | 0.008130 | 0.008569 | 0.005933 | 0.007031 | 0.007251 | 0.004175 | 0.008350 | 0.008130 | 0.009229 | 0.001318 | 0.004175 | -0.000439 | -0.001099 | -0.003955 | 0.000659 | 0.001318 | -0.003076 | 0.000439 | 0.000879 |
381 | 0.006372 | 0.004834 | 0.007910 | 0.005493 | 0.006152 | 0.008789 | 0.007690 | 0.006152 | 0.009448 | 0.005273 | 0.002856 | 0.005273 | 0.000879 | 0.002856 | 0.000659 | 0.003076 | 0.004395 | 0.005273 | 0.002637 | 0.001758 |
382 | 0.007031 | 0.007471 | 0.007690 | 0.006812 | 0.004834 | 0.005054 | 0.009229 | 0.005054 | 0.006372 | 0.009229 | -0.000659 | -0.003076 | 0.005273 | -0.000659 | -0.002637 | 0.000879 | 0.006592 | 0.000879 | 0.004395 | 0.000879 |
383 | 0.005933 | 0.007690 | 0.008569 | 0.006152 | 0.005273 | 0.008130 | 0.005713 | 0.007251 | 0.007251 | 0.007690 | 0.005054 | 0.005713 | 0.005054 | 0.002417 | -0.000220 | 0.000659 | 0.003076 | 0.002197 | -0.000220 | 0.004395 |
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.014062
1 0.014722
2 0.015161
3 0.017578
4 0.013403
...
379 0.005054
380 0.005933
381 0.006372
382 0.007031
383 0.005933
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.168735-0.031064j | 0.168644-0.030996j | 0.168705-0.031182j | 0.168763-0.031254j | 0.168906-0.031031j | 0.168954-0.031106j | 0.168780-0.031181j | 0.168882-0.031142j | 0.168682-0.031142j | 0.168895-0.031105j |
(((Channels(labels=[1], name=readoutreceiver, absolute_phase=False)))) | -0.165355-0.087149j | -0.165008-0.087113j | -0.165079-0.086906j | -0.165286-0.087213j | -0.164922-0.087373j | -0.165004-0.087016j | -0.164774-0.087134j | -0.165637-0.086843j | -0.165098-0.087637j | -0.165546-0.087272j |
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.