Download

Download this file as Jupyter notebook: qasm.ipynb.

QASM Integration Guide for QCS#

OpenQASM (Open Quantum Assembly Language) is a low-level quantum programming language designed to describe quantum circuits in a hardware-agnostic way. It provides a structured way to define quantum operations, including gate applications, measurements, and classical control flow, making it a standard for running quantum programs on real hardware and simulators.

This language is used in several quantum programming frameworks, including Qiskit, Cirq, PyQuil, Pennylane, and others. The Keysight QCS platform supports OpenQASM and allows users to run QASM programs on QCS hardware optimally.

This tutorial will cover how to use the QASM interface to convert QASM programs (from strings or files) to QCS programs for execution in Keysight Hardware.

Running a QASM Program in QCS#

First, we need to import all the necessary modules to convert QASM to QCS programs

[2]:
# Import all the necessary modules
import keysight.qcs as qcs

from keysight.qcs.channels import ChannelMapper
from keysight.qcs.executor import Executor, HclBackend
from keysight.qcs.experiments import make_calibration_set

from keysight.qcs.interfaces import QASMConverter as translate

Next, we need a QASM program, either as string or a path pointing to a QASM file

[3]:
# Path to QASM file
qasm_path = "<path/to/qasm/file.qasm>"

# QASM code in string format
qasm_str = """
OPENQASM 2.0;
include "qelib1.inc";

qreg q[1];  // A quantum register with 5 qubits
creg c[1];  // A classical register with 5 bits for measurement

// Apply single qubit rotation gates
rx(pi) q[0];
ry(pi) q[0];

// Measure the output
measure q[0] -> c[0];
"""

The QASM code can now be translated to a QCS program as follows-

[4]:
# Set this to True if you want to use a QASM file from a path
use_program_from_path = False

if use_program_from_path:
    qasm_program = translate(qasm_path)
else:
    qasm_program = translate(qasm_str)

# Visualizes the QCS program
qasm_program.qcs_program
Program
keysight-logo-svg
Program Body
Program
Duration undefined
Layers 1
Targets 1
Layer #0
Layer #0
Duration undefined
q 0
RX
ParameterizedGate RX on ('q', 0)

Parameters phi_x
Values 3.141592653589793

Matrices
0 1
1 0
RY
ParameterizedGate RY on ('q', 0)

Parameters phi_y
Values 3.141592653589793

Matrices
0 1j
-1j 0
Measure on ('q', 0)

Parameters
Dim 2
[4]:
Program([Layer([ParameterizedGate(gate=ParametricGate(layers=[[Hamiltonian(matrix=[[(-1.1102230246251565e-16+0j), (0.9999999999999994+0j)], [(0.9999999999999994+0j), (-5.551115123125783e-17+0j)]]), name=SIGMA_X)]], parameters=[[phi_x]], name=RX), parameters=(Scalar(name=_implicit, value=3.141592653589793, dtype=float, unit=none),), name=RX), ParameterizedGate(gate=ParametricGate(layers=[[Hamiltonian(matrix=[[(-1.1102230246251565e-16+0j), 0.9999999999999994j], [-0.9999999999999994j, (-5.551115123125783e-17+0j)]]), name=SIGMA_Y)]], parameters=[[phi_y]], name=RY), parameters=(Scalar(name=_implicit, value=3.141592653589793, dtype=float, unit=none),), name=RY), Measure(2)])])

The QASMConverter also transpiles the QCS program into native gates using Euler (ZXZ) decomposition for single-qubit SU(2) operations and CX for two-qubit gates. The native gate basis consists of X, Z, and CX.

[5]:
# View the transpiled QCS program
qasm_program.transpiled_program
Program
keysight-logo-svg
Program Body
Program
Duration undefined
Layers 1
Targets 1
Layer #0
Layer #0
Duration undefined
q 0
RZ
ParameterizedGate RZ on ('q', 0)

Parameters phi_z
Values 0.0

Matrices
1 0
0 -1
RX
ParameterizedGate RX on ('q', 0)

Parameters phi_x
Values 3.141592653589793

Matrices
0 1
1 0
RZ
ParameterizedGate RZ on ('q', 0)

Parameters phi_z
Values 0.0

Matrices
1 0
0 -1
RZ
ParameterizedGate RZ on ('q', 0)

Parameters phi_z
Values 3.141592653589793

Matrices
1 0
0 -1
RX
ParameterizedGate RX on ('q', 0)

Parameters phi_x
Values 3.141592653589793

Matrices
0 1
1 0
RZ
ParameterizedGate RZ on ('q', 0)

Parameters phi_z
Values 0.0

Matrices
1 0
0 -1
Measure on ('q', 0)

Parameters
Dim 2
[5]:
Program([Layer([ParameterizedGate(gate=ParametricGate(layers=[[Hamiltonian(matrix=[[(1+0j), 0j], [0j, (-1+0j)]]), name=SIGMA_Z)]], parameters=[[phi_z]], name=RZ), parameters=(Scalar(name=_implicit, value=0.0, dtype=float, unit=none),), name=RZ), ParameterizedGate(gate=ParametricGate(layers=[[Hamiltonian(matrix=[[(-1.1102230246251565e-16+0j), (0.9999999999999994+0j)], [(0.9999999999999994+0j), (-5.551115123125783e-17+0j)]]), name=SIGMA_X)]], parameters=[[phi_x]], name=RX), parameters=(Scalar(name=_implicit, value=3.141592653589793, dtype=float, unit=none),), name=RX), ParameterizedGate(gate=ParametricGate(layers=[[Hamiltonian(matrix=[[(1+0j), 0j], [0j, (-1+0j)]]), name=SIGMA_Z)]], parameters=[[phi_z]], name=RZ), parameters=(Scalar(name=_implicit, value=0.0, dtype=float, unit=none),), name=RZ), ParameterizedGate(gate=ParametricGate(layers=[[Hamiltonian(matrix=[[(1+0j), 0j], [0j, (-1+0j)]]), name=SIGMA_Z)]], parameters=[[phi_z]], name=RZ), parameters=(Scalar(name=_implicit, value=3.141592653589793, dtype=float, unit=none),), name=RZ), ParameterizedGate(gate=ParametricGate(layers=[[Hamiltonian(matrix=[[(-1.1102230246251565e-16+0j), (0.9999999999999994+0j)], [(0.9999999999999994+0j), (-5.551115123125783e-17+0j)]]), name=SIGMA_X)]], parameters=[[phi_x]], name=RX), parameters=(Scalar(name=_implicit, value=3.141592653589793, dtype=float, unit=none),), name=RX), ParameterizedGate(gate=ParametricGate(layers=[[Hamiltonian(matrix=[[(1+0j), 0j], [0j, (-1+0j)]]), name=SIGMA_Z)]], parameters=[[phi_z]], name=RZ), parameters=(Scalar(name=_implicit, value=0.0, dtype=float, unit=none),), name=RZ), Measure(2)])])

Once the QCS program is transpiled, it must be compiled into hardware operations, such as waveforms and delays, for execution on QCS hardware. This requires a calibration set that includes system-specific linkers and parameters to map the program gates to hardware operations.

[6]:
# Set this to True if calibration set already exists
calibration_set_exists = False
if calibration_set_exists:
    calibration_set = qcs.load("<path/to/calibration_set.qcs>")
else:
    # Make a template calibration set
    calibration_set = make_calibration_set(qcs.Qudits(range(5), name="q"))

# Compile the program for execution on QCS hardware
compiled_program = qasm_program.compile_program(calibration_set)
compiled_program.draw()
Program
keysight-logo-svg
Program Body
Program
Duration 165 ns
Layers 1
Targets 3
Layer #0
Layer #0
Duration 165 ns
xy_pulse 0
PHASE
PhaseIncrement on ('xy_pulse', 0)

Parameters
Phase ScalarRef(name=_implicit, value=0, dtype=float, unit=rad)
RFWaveform on ('xy_pulse', 0)

Parameters
Duration ScalarRef(name=xy_pulse_durations, value=30 ns, dtype=float, unit=s)
Amplitude ScalarRef(name=_implicit, value=0.5, dtype=float, unit=none)
Frequency ScalarRef(name=xy_pulse_frequencies, value=5.1 GHz, dtype=float, unit=Hz)
Envelope GaussianEnvelope(2.0)
Instantaneous Phase ScalarRef(name=rx_phase, value=0 rad, dtype=float, unit=rad)
Post-phase ScalarRef(name=rx_post_phase, value=0 rad, dtype=float, unit=rad)
PHASE
PhaseIncrement on ('xy_pulse', 0)

Parameters
Phase ScalarRef(name=_implicit, value=0, dtype=float, unit=rad)
PHASE
PhaseIncrement on ('xy_pulse', 0)

Parameters
Phase ScalarRef(name=_implicit, value=3.14159, dtype=float, unit=rad)
RFWaveform on ('xy_pulse', 0)

Parameters
Duration ScalarRef(name=xy_pulse_durations, value=30 ns, dtype=float, unit=s)
Amplitude ScalarRef(name=_implicit, value=0.5, dtype=float, unit=none)
Frequency ScalarRef(name=xy_pulse_frequencies, value=5.1 GHz, dtype=float, unit=Hz)
Envelope GaussianEnvelope(2.0)
Instantaneous Phase ScalarRef(name=rx_phase, value=0 rad, dtype=float, unit=rad)
Post-phase ScalarRef(name=rx_post_phase, value=0 rad, dtype=float, unit=rad)
PHASE
PhaseIncrement on ('xy_pulse', 0)

Parameters
Phase ScalarRef(name=_implicit, value=0, dtype=float, unit=rad)
Delay on ('xy_pulse', 0)

Parameters
Duration Max(Scalar(name=_implicit, value=105 ns, dtype=float, unit=s), Scalar(name=_implicit, value=105 ns, dtype=float, unit=s))
readout_pulse 0
Delay on ('readout_pulse', 0)

Parameters
Duration Scalar(name=_implicit, value=60 ns, dtype=float, unit=s)
Delay on ('readout_pulse', 0)

Parameters
Duration ScalarRef(name=readout_pulse_delay, value=0 s, dtype=float, unit=s)
RFWaveform on ('readout_pulse', 0)

Parameters
Duration ScalarRef(name=readout_pulse_duration, value=100 ns, dtype=float, unit=s)
Amplitude ScalarRef(name=readout_pulse_amplitudes, value=0.1, dtype=float, unit=none)
Frequency ScalarRef(name=readout_frequencies, value=5.15 GHz, dtype=float, unit=Hz)
Envelope SineEnvelope()
Instantaneous Phase ScalarRef(name=readout_pulse_phases, value=0 rad, dtype=float, unit=rad)
Post-phase ScalarRef(name=measurement_post_phase, value=0 rad, dtype=float, unit=rad)
Delay on ('readout_pulse', 0)

Parameters
Duration Scalar(name=_implicit, value=5 ns, dtype=float, unit=s)
readout_acquisition 0
Delay on ('readout_acquisition', 0)

Parameters
Duration Scalar(name=_implicit, value=60 ns, dtype=float, unit=s)
Delay on ('readout_acquisition', 0)

Parameters
Duration ScalarRef(name=acquisition_delay, value=5 ns, dtype=float, unit=s)
Acquisition on ('readout_acquisition', 0)

Parameters
Duration ScalarRef(name=acquisition_duration, value=100 ns, dtype=float, unit=s)
Integration Filter
RFWaveform

Parameters
Duration ScalarRef(name=acquisition_duration, value=100 ns, dtype=float, unit=s)
Amplitude ScalarRef(name=measurement_integrator_amplitude, value=1, dtype=float, unit=none)
Frequency ScalarRef(name=readout_frequencies, value=5.15 GHz, dtype=float, unit=Hz)
Envelope ConstantEnvelope()
Instantaneous Phase ScalarRef(name=measurement_integrator_phase, value=0 rad, dtype=float, unit=rad)
Post-phase ScalarRef(name=measurement_integrator_post_phase, value=0 rad, dtype=float, unit=rad)
Classifier Classifier(ArraySlice(name=references, shape=(2,), dtype=complex, unit=none))
Delay on ('readout_acquisition', 0)

Parameters
Duration Scalar(name=_implicit, value=4.96308e-24 s, dtype=float, unit=s)

The compiled program can be rendered to see the waveform operations on the hardware.

[7]:
# Render the compiled program
compiled_program.render(sample_rate=5e9, channel_subplots=False)

A channel mapper, describing the physical addresses anc connections is required to submit the program to QCS hardware.

[8]:
# Set this to True if channel mapper exists
channel_mapper_exists = False

if channel_mapper_exists:
    mapper = qcs.load("<path/to/channel_mapper.qcs>")
else:
    # generate an empty channel mapper with the correct ip address (for remote jobs)
    mapper = ChannelMapper("<ip_address>")

The compiled program can now be executed in hardware using the HCL backend

[9]:
# Set this to True if you want to run on hardware
run_on_hw = False
fpga_postprocessing = (
    False  # Refers to hardware demodulation, if True returns only I/Q data
)

if run_on_hw:
    backend = HclBackend(channel_mapper=mapper, fpga_postprocessing=fpga_postprocessing)
    qasm_executed = Executor(backend).execute(compiled_program)
else:
    # load in a previously executed version of this experiment
    qasm_executed = qcs.load("QASM_program.hdf5")

Now let’s plot the data to see if the pulses are what we expect, note that the ancilla refers to the control channel pulses.

[10]:
qasm_executed.plot_trace()

Running a group of QASM Programs in QCS#

A list of QASM programs can be compiled to a QCS program with sweepable parameters to run on hardware. This is possible only if the following conditions are met.

  1. The programs must have the same number of hardware operations (circuit depth)

  2. The hardware operations in the programs must be of similar type and follow the same order. For example, if one program is of the format phase, waveform and phase, then the following programs must be of a similar format and order. However, the operations can have different parameters.

Since the Euler Decomposition for all SU(2) gates yields the same format of hardware operations this allows most single qubit gate programs with same circuit depth to be compiled to run faster on QCS hardware. This is especially useful for running randomized benchmarking protocols on QCS hardware.

Let’s take a look at how we can run a bunch of qasm programs in QCS

[11]:
qasm_1 = """
OPENQASM 2.0;
include "qelib1.inc";

qreg q[1];  // A quantum register with 5 qubits
creg c[1];  // A classical register with 5 bits for measurement

// Apply single qubit rotation gates
rx(pi) q[0];
ry(pi) q[0];

// Measure the output
measure q[0] -> c[0];
"""
[12]:
qasm_2 = """
OPENQASM 2.0;
include "qelib1.inc";

qreg q[1];  // A quantum register with 5 qubits
creg c[1];  // A classical register with 5 bits for measurement

// Apply single qubit rotation gates
rx(pi/2) q[0];
ry(pi/2) q[0];

// Measure the output
measure q[0] -> c[0];
"""
[13]:
qasm_3 = """
OPENQASM 2.0;
include "qelib1.inc";

qreg q[1];  // A quantum register with 5 qubits
creg c[1];  // A classical register with 5 bits for measurement

// Apply single qubit rotation gates
rx(pi/4) q[0];
ry(pi/4) q[0];

// Measure the output
measure q[0] -> c[0];
"""
[14]:
# Path to QASM programs
qasm_dir = "<path/to/list/of/qasm/programs>"

# List of QASM circuits defined above
qasm_circuits = [qasm_1, qasm_2, qasm_3]

# Set this to True if you want to use QASM files from a directory path
use_program_from_path = False

qasm_path = qasm_dir if use_program_from_path else qasm_circuits

compiled_program = translate.from_group(qasm_path, calibration_set)
compiled_program[0].draw()
Program
Sweep Details:
Repetitions Sweep with 3 repetitions
Associations
amplitude_0 Array(name=amplitude_0[], shape=(3,), dtype=float, unit=none, value=[0.5, 0.25, 0.125])
phase_1 Array(name=phase_1[], shape=(3,), dtype=float, unit=none, value=[3.14159 rad, 1.5708 rad, 1.5708 rad])
amplitude_2 Array(name=amplitude_2[], shape=(3,), dtype=float, unit=none, value=[0.5, 0.25, 0.125])
phase_3 Array(name=phase_3[], shape=(3,), dtype=float, unit=none, value=[0 rad, -1.5708 rad, -1.5708 rad])
(SW)Sweep_amplitude_0_phase_1_amplitude_2_phase_3
Sweep Details:
Repetitions Repeat with 1 repetitions
(HW)Repeat(1)
keysight-logo-svg
Program Body
Program
Duration 165 ns
Layers 1
Targets 3
Layer #0
Layer #0
Duration 165 ns
xy_pulse 0
PHASE
PhaseIncrement on ('xy_pulse', 0)

Parameters
Phase Scalar(name=_implicit, value=0, dtype=float, unit=rad)
RFWaveform on ('xy_pulse', 0)

Parameters
Duration Scalar(name=_implicit, value=30 ns, dtype=float, unit=s)
Amplitude Scalar(name=amplitude_0, value=0.5, dtype=float, unit=none)
Frequency Scalar(name=_implicit, value=5.1 GHz, dtype=float, unit=Hz)
Envelope GaussianEnvelope(2.0)
Instantaneous Phase Scalar(name=_implicit, value=0 rad, dtype=float, unit=rad)
Post-phase Scalar(name=_implicit, value=0 rad, dtype=float, unit=rad)
PHASE
PhaseIncrement on ('xy_pulse', 0)

Parameters
Phase Scalar(name=_implicit, value=0, dtype=float, unit=rad)
PHASE
PhaseIncrement on ('xy_pulse', 0)

Parameters
Phase Scalar(name=phase_1, value=3.14159, dtype=float, unit=rad)
RFWaveform on ('xy_pulse', 0)

Parameters
Duration Scalar(name=_implicit, value=30 ns, dtype=float, unit=s)
Amplitude Scalar(name=amplitude_2, value=0.5, dtype=float, unit=none)
Frequency Scalar(name=_implicit, value=5.1 GHz, dtype=float, unit=Hz)
Envelope GaussianEnvelope(2.0)
Instantaneous Phase Scalar(name=_implicit, value=0 rad, dtype=float, unit=rad)
Post-phase Scalar(name=_implicit, value=0 rad, dtype=float, unit=rad)
PHASE
PhaseIncrement on ('xy_pulse', 0)

Parameters
Phase Scalar(name=phase_3, value=0, dtype=float, unit=rad)
Delay on ('xy_pulse', 0)

Parameters
Duration Scalar(name=_implicit, value=105 ns, dtype=float, unit=s)
readout_pulse 0
Delay on ('readout_pulse', 0)

Parameters
Duration Scalar(name=_implicit, value=60 ns, dtype=float, unit=s)
Delay on ('readout_pulse', 0)

Parameters
Duration Scalar(name=_implicit, value=0 s, dtype=float, unit=s)
RFWaveform on ('readout_pulse', 0)

Parameters
Duration Scalar(name=_implicit, value=100 ns, dtype=float, unit=s)
Amplitude Scalar(name=_implicit, value=0.1, dtype=float, unit=none)
Frequency Scalar(name=_implicit, value=5.15 GHz, dtype=float, unit=Hz)
Envelope SineEnvelope()
Instantaneous Phase Scalar(name=_implicit, value=0 rad, dtype=float, unit=rad)
Post-phase Scalar(name=_implicit, value=0 rad, dtype=float, unit=rad)
Delay on ('readout_pulse', 0)

Parameters
Duration Scalar(name=_implicit, value=5 ns, dtype=float, unit=s)
readout_acquisition 0
Delay on ('readout_acquisition', 0)

Parameters
Duration Scalar(name=_implicit, value=60 ns, dtype=float, unit=s)
Delay on ('readout_acquisition', 0)

Parameters
Duration Scalar(name=_implicit, value=5 ns, dtype=float, unit=s)
Acquisition on ('readout_acquisition', 0)

Parameters
Duration ScalarRef(name=acquisition_duration, value=100 ns, dtype=float, unit=s)
Integration Filter
RFWaveform

Parameters
Duration ScalarRef(name=acquisition_duration, value=100 ns, dtype=float, unit=s)
Amplitude ScalarRef(name=measurement_integrator_amplitude, value=1, dtype=float, unit=none)
Frequency ScalarRef(name=readout_frequencies, value=5.15 GHz, dtype=float, unit=Hz)
Envelope ConstantEnvelope()
Instantaneous Phase ScalarRef(name=measurement_integrator_phase, value=0 rad, dtype=float, unit=rad)
Post-phase ScalarRef(name=measurement_integrator_post_phase, value=0 rad, dtype=float, unit=rad)
Classifier Classifier(ArraySlice(name=references, shape=(2,), dtype=complex, unit=none))
Delay on ('readout_acquisition', 0)

Parameters
Duration Scalar(name=_implicit, value=4.96308e-24 s, dtype=float, unit=s)

To see the gate level and transpiled programs, set gate_level = True. This will return the original gate level and transpiled QCS programs if you would like to debug or better understand the transformation prior to compilation.

[15]:
compiled_program, gate_programs, transpiled_programs = translate.from_group(
    batch=qasm_path, calibration_set=calibration_set, gate_level=True
)

for program in gate_programs:
    program.draw()
Program
keysight-logo-svg
Program Body
Program
Duration undefined
Layers 1
Targets 1
Layer #0
Layer #0
Duration undefined
q 0
RX
ParameterizedGate RX on ('q', 0)

Parameters phi_x
Values 3.141592653589793

Matrices
0 1
1 0
RY
ParameterizedGate RY on ('q', 0)

Parameters phi_y
Values 3.141592653589793

Matrices
0 1j
-1j 0
Measure on ('q', 0)

Parameters
Dim 2
Program
keysight-logo-svg
Program Body
Program
Duration undefined
Layers 1
Targets 1
Layer #0
Layer #0
Duration undefined
q 0
RX
ParameterizedGate RX on ('q', 0)

Parameters phi_x
Values 1.5707963267948966

Matrices
0 1
1 0
RY
ParameterizedGate RY on ('q', 0)

Parameters phi_y
Values 1.5707963267948966

Matrices
0 1j
-1j 0
Measure on ('q', 0)

Parameters
Dim 2
Program
keysight-logo-svg
Program Body
Program
Duration undefined
Layers 1
Targets 1
Layer #0
Layer #0
Duration undefined
q 0
RX
ParameterizedGate RX on ('q', 0)

Parameters phi_x
Values 0.7853981633974483

Matrices
0 1
1 0
RY
ParameterizedGate RY on ('q', 0)

Parameters phi_y
Values 0.7853981633974483

Matrices
0 1j
-1j 0
Measure on ('q', 0)

Parameters
Dim 2

The compiled program can be rendered to see the waveform operations on the hardware.

[16]:
# Render the compiled program
compiled_program[0].render(sample_rate=5e9, channel_subplots=False, sweep_index=1)

As before, we need a channel mapper to submit the program to QCS hardware.

[17]:
# Set this to True if channel mapper exists
channel_mapper_exists = False

if channel_mapper_exists:
    mapper = qcs.load("<path/to/channel_mapper.qcs>")
else:
    # generate an empty channel mapper with the correct ip address (for remote jobs)
    mapper = ChannelMapper("<ip_address>")

The compiled program can now be executed in hardware using the HCL backend

[18]:
# Set this to True if you want to run on hardware
run_on_hw = False
fpga_postprocessing = (
    False  # Refers to hardware demodulation, if True returns only I/Q data
)

if run_on_hw:
    backend = HclBackend(channel_mapper=mapper, fpga_postprocessing=fpga_postprocessing)
    qasm_executed = Executor(backend).execute(compiled_program)
else:
    # load in a previously executed version of this experiment
    qasm_executed = qcs.load("QASM_program_from_group.hdf5")

The executed program results can be viewed using the normal plotting functions

[19]:
# Turn this to true to see the legend
legend = False

fig = qasm_executed.plot_trace()
fig.update_layout(showlegend=legend)
fig.show()

That brings us to the end of this tutorial. Congratulations, now you know how to run QASM programs in QCS!


Download

Download this file as Jupyter notebook: qasm.ipynb.