Download

Download this file as Jupyter notebook: parallel_program.ipynb.

Running Programs in Parallel

The QCS is able to execute programs in parallel so long as they act on separate chassis.

To submit programs to run in parallel, users can submit from either one SDK client or from two separate SDK clients. When executing from two separate clients, no additional setup is required from the clients. The targeted QCS system will receive the program submissions independently and manage the queues for each chassis accordingly. When executing from one client, the user must set the blocking property of their HclBackend instance to False. This allows the code to continue to a run second execution submission without being blocked by the completion of the first program. See below for an example of this.

The QCS reserves hardware for execution on a per-chassis basis. When the system receives separate programs that each act on a single chassis in the system, it is able to execute those programs in parallel. Parallel execution of programs that act on multiple chassis is dependent on the synchronization topology between the chassis.

Submitting programs with blocking set to False

In this example we will be executing on a two chassis system. One program will use channels exclusive to chassis 1, and the other program will use channels exclusive to chassis 2.

First we will load the channel mapper that we will use for both programs

[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
# Index `0` of these virtual channels are mapped to channels on chassis 1
# Index `1` of these virtual channels are mapped to channels on chassis 2
awgs = qcs.Channels(range(2), "readoutawg")
digs = qcs.Channels(range(2), "readoutreceiver")

mapper = qcs.load("../../assets/channel_map.qcs")
[3]:
# create a program for the first chassis
program1 = qcs.Program()
gauss = qcs.RFWaveform(80e-9, qcs.GaussianEnvelope(num_sigma=4), 1, 4.15e9)
program1.add_waveform(gauss, awgs[0])
program1.add_acquisition(80e-9, digs[0])
program1.n_shots(10)
keysight-logo-svg
Program
Program
Duration 80 ns
Layers 1
Targets 2
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
readoutreceiver 0
Acquisition on ('readoutreceiver', 0)

Parameters
Duration 80 ns
[3]:
Program([Layer(Channels(labels=[0], name=readoutawg, absolute_phase=False)=[RFWaveform(duration=Scalar(name=_implicit, value=8e-08, dtype=float, unit=s), envelope={GaussianEnvelope(4.0): Scalar(name=_implicit, value=1.0, dtype=float, unit=none)}, rf_frequency=Scalar(name=_implicit, value=4150000000.0, dtype=float, unit=Hz), instantaneous_phase={GaussianEnvelope(4.0): Scalar(name=_implicit, value=0.0, dtype=float, unit=rad)}, post_phase=Scalar(name=_implicit, value=0.0, dtype=float, unit=rad))], Channels(labels=[0], name=readoutreceiver, absolute_phase=False)=[Acquisition(Scalar(name=_implicit, value=8e-08, dtype=float, unit=s))])])
[4]:
# create a program for the second chassis
program2 = qcs.Program()
gauss = qcs.RFWaveform(80e-9, qcs.GaussianEnvelope(num_sigma=4), 1, 4.15e9)
program2.add_waveform(gauss, awgs[1])
program2.add_acquisition(80e-9, digs[1])
program2.n_shots(10)
keysight-logo-svg
Program
Program
Duration 80 ns
Layers 1
Targets 2
Repetitions Repeat with 10 repetitions
Layer #0
Layer #0
Duration 80 ns
readoutawg 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 1
Acquisition on ('readoutreceiver', 1)

Parameters
Duration 80 ns
[4]:
Program([Layer(Channels(labels=[1], name=readoutawg, absolute_phase=False)=[RFWaveform(duration=Scalar(name=_implicit, value=8e-08, dtype=float, unit=s), envelope={GaussianEnvelope(4.0): Scalar(name=_implicit, value=1.0, dtype=float, unit=none)}, rf_frequency=Scalar(name=_implicit, value=4150000000.0, dtype=float, unit=Hz), instantaneous_phase={GaussianEnvelope(4.0): Scalar(name=_implicit, value=0.0, dtype=float, unit=rad)}, post_phase=Scalar(name=_implicit, value=0.0, dtype=float, unit=rad))], Channels(labels=[1], name=readoutreceiver, absolute_phase=False)=[Acquisition(Scalar(name=_implicit, value=8e-08, dtype=float, unit=s))])])

To execute these programs in parallel, we make sure to instantiate our HclBackend with the blocking property set to False.

[5]:
if run_on_hw:
    # initialize the backend pass
    backend = qcs.HclBackend(channel_mapper=mapper, blocking=False)
    # Submit the first program for execution
    program1 = qcs.Executor(backend).execute(program1)
    # Submit the secon program for execution before while the first program is running.
    program2 = qcs.Executor(backend).execute(program2)

We can check the status of these programs with two methods. get_program_state() will return the status of the program in string form (i.e. “Queued”, “Running”, “CompletedInSuccess”). is_program_completed() will return a boolean representing if the program is completed.

[6]:
if run_on_hw:

    prog1_state = backend.get_program_state(program1.results.accession_id)
    prog1_completed = backend.is_program_completed(program1.results.accession_id)
    print(f"Program Chassis 1 state: {prog1_state}. Completed: {prog1_completed}")

    prog2_state = backend.get_program_state(program2.results.accession_id)
    prog2_completed = backend.is_program_completed(program2.results.accession_id)
    print(f"Program Chassis 2 state: {prog2_state}. Completed: {prog2_completed}")

After the programs complete we can fetch results.

[7]:
if run_on_hw:
    if prog1_completed:
        trace_data1 = program1.get_trace()
    if prog2_completed:
        trace_data2 = program2.get_trace()

Download

Download this file as Jupyter notebook: parallel_program.ipynb.

On this page