Download

Download this file as Jupyter notebook: simulated_experiment.ipynb.

Loading Simulated Experiment data

This section explains how to load simulated experiment data using the mock_data parameter. The mock_data parameter allows you to test and analyze experiments without requiring actual hardware execution.

The mock_data parameter should be structured as follows:

  • Keys: Instances of Channels representing the channels used in the experiment.

  • Values: Iterables containing the simulated data for each channel.

Example usage:

mock_data = {
    channel_1: [trace_data_1, trace_data_2, ...],
    channel_2: [trace_data_1, trace_data_2, ...],
    qubit_1: [trace_data_1, trace_data_2, ...],
    ...
}
[2]:
import keysight.qcs as qcs
from keysight.qcs.experiments import Experiment, make_calibration_set
import numpy as np

Before creating the Experiment with mock_data, we need a ChannelMapper and Program object to tell the Experiment class for what data structure to expect for mock_data.

Creating a dummy ChannelMapper

ChannelMapper is required to configure the number of channels available for acquisition, and how many number of channel-data to expect in mock_data. The dimension of the accepted program is [n_shots, sweep_a, sweep_b, …, n_points]

  • n_shots: Number of program repetitions

  • sweep_a, sweep_b, …: Sweep variables

  • n_points: number of accepted points by the IntegrationFilter to covert trace data into IQ data.

[3]:
n_channels = 2
n_qubits = 2
n_points = 240  # points accepted by the integration filter

qubits = qcs.Qudits(range(n_qubits))
channels = qcs.Channels(range(n_channels), "xy_channels", absolute_phase=False)

mapper = qcs.ChannelMapper("dummy")

# assigning dummy physical addresses to the channels
for i in range(n_channels):
    mapper.add_channel_mapping(
        channels=channels[i],
        addresses=qcs.Address(1, 1, i + 1),
        instrument_types=qcs.InstrumentEnum.M5300AWG,
    )

Loading a simple Sweep Program and Data

The Program class gives information what dimension of data to expect from mock_data. The program can be a simple non-iterating program, a normal sweep, a nested sweep, or a zipped sweep. The data shape is determined by the structure of program.repetitions.items.

Here, we will load data for a simple amplitude sweep program.

[4]:
n_shots = 7
num_amps = 13  # number of amplitude points to sweep on

program = qcs.Program()
amplitude = qcs.Scalar("amplitude", dtype=float)
phase = qcs.Scalar("phase", dtype=float)
amps = qcs.Array("amps", value=np.linspace(0, 0.8, num_amps), dtype=float)
waveform = qcs.RFWaveform(
    duration=20e-9,
    envelope=qcs.GaussianEnvelope(),
    amplitude=amplitude,
    rf_frequency=5.1e9,
)
program.add_waveform(waveform, channels)
int_filter = qcs.RFWaveform(10e-8, qcs.ConstantEnvelope(), 1, 5.15e9)
program.add_acquisition(int_filter, channels)
program.sweep(amps, amplitude).n_shots(n_shots)

# The shape of the accepted data must account for these dimensions
for i in program.repetitions.items:
    print(i)
Repeat(7)
Sweep(amplitude=Array(name=amps, shape=(13,), dtype=float, unit=none))

Defining mock data and loading into the Experiment:

[5]:
hw_demod = False

# acceptable data shape
shape = [n_shots, num_amps, n_points]

# generate mock trace data
mock_data = dict()
for chan in channels:
    mock_data[chan] = np.random.random(shape)

# load the mock_data using the mock_data parameter in the Experiment class
backend = qcs.HclBackend(mapper, hw_demod=hw_demod)
exp = Experiment(backend, make_calibration_set(2), qubits, program, mock_data=mock_data)

exp.get_iq()  # or exp.get_trace()
[5]:
(((Channels(labels=[0], name=xy_channels, absolute_phase=False)))) ... (((Channels(labels=[1], name=xy_channels, absolute_phase=False))))
0 ... 6
(amplitude, 0) (amplitude, 0.0667) (amplitude, 0.1333) (amplitude, 0.2) (amplitude, 0.2667) (amplitude, 0.3333) (amplitude, 0.4) (amplitude, 0.4667) (amplitude, 0.5333) (amplitude, 0.6) ... (amplitude, 0.2) (amplitude, 0.2667) (amplitude, 0.3333) (amplitude, 0.4) (amplitude, 0.4667) (amplitude, 0.5333) (amplitude, 0.6) (amplitude, 0.6667) (amplitude, 0.7333) (amplitude, 0.8)
0 0.031249-0.026000j -0.031621-0.012998j -0.011636-0.015886j 0.004438-0.019826j 0.023532+0.034597j -0.019214+0.049735j -0.006032-0.034850j 0.026803+0.008326j 0.002084+0.007049j 0.000282-0.017490j ... 0.004098+0.004213j -0.006963-0.041617j -0.017787+0.002688j 0.025167+0.012283j 0.029283+0.059102j -0.039216+0.030935j -0.024009-0.028680j 0.018269+0.040368j -0.020776+0.012157j -0.007685-0.010754j

1 rows × 182 columns

Nested Sweeps with mock_data

Moving on to more complicated sweeps, we just have to that the shape of program.repetitions.items and our data matches.

[6]:
n_shots = 7
num_a = 13  # number of points to sweep on
num_b = 17  # number of points to sweep on

qubits = qcs.Qudits(range(n_qubits))
channels = qcs.Channels(range(n_channels), "xy_channels", absolute_phase=False)

program = qcs.Program()
amplitude = qcs.Scalar("amplitude", dtype=float)
phase = qcs.Scalar("phase", dtype=float)
amps = qcs.Array("amps", value=np.linspace(0, 0.8, num_a), dtype=float)
phases = qcs.Array("phases", value=np.linspace(0, np.pi, num_b), dtype=float)

waveform = qcs.RFWaveform(
    duration=20e-9,
    envelope=qcs.GaussianEnvelope(),
    amplitude=amplitude,
    rf_frequency=5.1e9,
    instantaneous_phase=phase,
)
program.add_waveform(waveform, channels)
int_filter = qcs.RFWaveform(10e-8, qcs.ConstantEnvelope(), 1, 5.15e9)
program.add_acquisition(int_filter, channels)
program.sweep(amps, amplitude).sweep(phases, phase).n_shots(n_shots)
for i in program.repetitions.items:
    print(i)
Repeat(7)
Sweep(phase=Array(name=phases, shape=(17,), dtype=float, unit=none))
Sweep(amplitude=Array(name=amps, shape=(13,), dtype=float, unit=none))

Defining and loading mock_data

[7]:
hw_demod = False
shape = [n_shots, num_a, num_b, n_points]

mock_data = dict()
for chan in channels:
    mock_data[chan] = np.random.random(shape)

backend = qcs.HclBackend(mapper, hw_demod=hw_demod)
exp = Experiment(backend, make_calibration_set(2), qubits, program, mock_data=mock_data)

exp.get_iq()  # OR exp.get_trace()
[7]:
(((Channels(labels=[0], name=xy_channels, absolute_phase=False)))) ... (((Channels(labels=[1], name=xy_channels, absolute_phase=False))))
0 ... 6
(phase, 0 rad) ... (phase, 3.1415926536 rad)
(amplitude, 0) (amplitude, 0.0667) (amplitude, 0.1333) (amplitude, 0.2) (amplitude, 0.2667) (amplitude, 0.3333) (amplitude, 0.4) (amplitude, 0.4667) (amplitude, 0.5333) (amplitude, 0.6) ... (amplitude, 0.2) (amplitude, 0.2667) (amplitude, 0.3333) (amplitude, 0.4) (amplitude, 0.4667) (amplitude, 0.5333) (amplitude, 0.6) (amplitude, 0.6667) (amplitude, 0.7333) (amplitude, 0.8)
0 -0.019448+0.013503j -0.002012-0.028937j -0.021066+0.015633j 0.007827+0.058362j 0.090904+0.036831j -0.004793-0.021340j 0.037485+0.056948j 0.014964+0.028572j -0.023797+0.026929j -0.044831+0.006169j ... 0.012186+0.042002j 0.011547-0.022397j 0.025953+0.012802j -0.045448-0.013421j -0.052819-0.031184j -0.005990+0.024468j 0.037878-0.016779j 0.017988-0.010031j -0.089399-0.027104j -0.025709-0.016623j

1 rows × 3094 columns

Zipped Sweeps with mock_data

Zipped sweeps have special structure in program.repetitions.items

[8]:
num_a = 13

# program
qubits = qcs.Qudits(range(n_channels))
channels = qcs.Channels(range(n_channels), "xy_channels", absolute_phase=False)

program = qcs.Program()
amplitude = qcs.Scalar("amplitude", dtype=float)
phase = qcs.Scalar("phase", dtype=float)
amps = qcs.Array("amps", value=np.linspace(0, 0.8, num_a), dtype=float)
phases = qcs.Array("phases", value=np.linspace(0, np.pi, num_a), dtype=float)

waveform = qcs.RFWaveform(
    duration=20e-9,
    envelope=qcs.GaussianEnvelope(),
    amplitude=amplitude,
    rf_frequency=5.1e9,
    instantaneous_phase=phase,
)
program.add_waveform(waveform, channels)
int_filter = qcs.RFWaveform(10e-8, qcs.ConstantEnvelope(), 1, 5.15e9)
program.add_acquisition(int_filter, channels)
program.sweep((amps, phases), (amplitude, phase)).n_shots(n_shots)

for i in program.repetitions.items:
    print(i)
Repeat(7)
Sweep(amplitude=Array(name=amps, shape=(13,), dtype=float, unit=none), phase=Array(name=phases, shape=(13,), dtype=float, unit=none))

Defining and loading mock_data

[9]:
# mock_data
shape = [n_shots, num_a, n_points]
mock_data = dict()
for chan in channels:
    mock_data[chan] = np.random.random(shape)

# test for each mock_data
backend = qcs.HclBackend(mapper, hw_demod=hw_demod)
exp = Experiment(backend, make_calibration_set(2), qubits, program, mock_data=mock_data)

exp.get_iq()  # OR exp.get_trace()
[9]:
(((Channels(labels=[0], name=xy_channels, absolute_phase=False)))) ... (((Channels(labels=[1], name=xy_channels, absolute_phase=False))))
0 ... 6
(amplitude, 0), (phase, 0 rad) (amplitude, 0.0667), (phase, 261.8 mrad) (amplitude, 0.1333), (phase, 523.6 mrad) (amplitude, 0.2), (phase, 785.4 mrad) (amplitude, 0.2667), (phase, 1.0471975512 rad) (amplitude, 0.3333), (phase, 1.308996939 rad) (amplitude, 0.4), (phase, 1.5707963268 rad) (amplitude, 0.4667), (phase, 1.8325957146 rad) (amplitude, 0.5333), (phase, 2.0943951024 rad) (amplitude, 0.6), (phase, 2.3561944902 rad) ... (amplitude, 0.2), (phase, 785.4 mrad) (amplitude, 0.2667), (phase, 1.0471975512 rad) (amplitude, 0.3333), (phase, 1.308996939 rad) (amplitude, 0.4), (phase, 1.5707963268 rad) (amplitude, 0.4667), (phase, 1.8325957146 rad) (amplitude, 0.5333), (phase, 2.0943951024 rad) (amplitude, 0.6), (phase, 2.3561944902 rad) (amplitude, 0.6667), (phase, 2.617993878 rad) (amplitude, 0.7333), (phase, 2.8797932658 rad) (amplitude, 0.8), (phase, 3.1415926536 rad)
0 -0.010586-0.003072j 0.002063-0.035717j -0.026939-0.029382j 0.013823+0.022923j -0.026898-0.002183j 0.018640-0.039584j -0.002380+0.035292j -0.010906+0.019501j 0.021480+0.010561j 0.016323-0.012088j ... 0.030539+0.008261j 0.027897-0.048971j 0.020939+0.028002j -0.004342+0.002680j -0.008071-0.035649j 0.005053-0.000899j -0.038410-0.023571j 0.006008+0.012163j 0.035101-0.045239j -0.014720+0.011348j

1 rows × 182 columns

Example for a Qubit level program

For qubit level program, the user need to additionally take care of the calibration set and the application of LinkerPass to the program, as shown below:

[10]:
# channel mapper
n_channels = 2
n_qubits = 2
n_points = 240  # points accepted by the integration filter

mapper = qcs.ChannelMapper("dummy")

# channel_acq required for qubit-type mock_data
# our default function qcs.experiments.make_calibration_set` creates channels with
# name="readout_acquisition", and supporting linkers for it.
channels_acq = qcs.Channels(
    range(n_channels), "readout_acquisition", absolute_phase=True
)
# assigning dummy physical addresses to the channels
for i in range(n_channels):
    mapper.add_channel_mapping(
        channels=channels_acq[i],
        addresses=qcs.Address(2, 1, i + 1),
        instrument_types=qcs.InstrumentEnum.M5300AWG,
    )

Defining the program and other components:

[11]:
n_shots = 7
num_amps = 13  # number of amplitude points to sweep on
hw_demod = False

qubits = qcs.Qudits(range(n_channels))
channels_acq = qcs.Channels(
    range(n_channels), "readout_acquisition", absolute_phase=True
)

program = qcs.Program()
program.add_measurement(qubits)
program.n_shots(n_shots)

# Additional steps: Defining the calibration set and applying LinkerPass
cal_set = make_calibration_set(n_channels)
program = qcs.LinkerPass(*cal_set.linkers.values()).apply(program)

# mock_data for qubits
mock_data = dict()
for qub in qubits:
    mock_data[qub] = np.random.random((n_shots, n_points))

backend = qcs.HclBackend(mapper, hw_demod=hw_demod)

exp = Experiment(backend, make_calibration_set(2), qubits, program, mock_data=mock_data)
exp.get_iq()
[11]:
(((Qudits(labels=[0], name=qudits, dim=2)))) (((Qudits(labels=[1], name=qudits, dim=2))))
0 1 2 3 4 5 6 0 1 2 3 4 5 6
0 -0.029511+0.003492j 0.000815-0.028222j -0.015486-0.010311j -0.052808+0.017443j 0.017927+0.033257j 0.007270-0.001728j 0.002164+0.004423j -0.030739-0.010229j 0.028088+0.035851j 0.021961-0.000595j 0.032344-0.029078j -0.001332-0.001276j -0.002384+0.003196j -0.008209-0.009716j

Download

Download this file as Jupyter notebook: simulated_experiment.ipynb.

On this page