In this module, you will learn how to use the qBraid-SDK to interface with quantum circuits across various frontends. We will demonstrate how to load quantum programs, register new quantum program types, and highlight a few other circuit-level convenience features.

API Reference: qbraid.programs

Quantum Program Registry

The QPROGRAM_REGISTRY contains dictionary mappings of shorthand identifiers for “registered” quantum program types. By default (i.e. without any optional dependencies installed) this simply includes three variations of OpenQASM programs:

>>> from qbraid import QPROGRAM_REGISTRY
>>> QPROGRAM_REGISTRY
{'openqasm3': openqasm3.ast.Program,
 'qasm2': str,
 'qasm3': str}

In this example, ‘openqasm3’ serves as a type alias for an openqasm3.ast.Program object, while ‘qasm2’ and ‘qasm3’ are aliases for raw OpenQASM 2 and OpenQASM 3 strings, respectively.

For each of its 10 natively supported program type aliases, the qBraid-SDK will automatically detect whether the corresponding library is installed, and add that program type along with its type alias to the QPROGRAM_REGISTRY. For example, after installing the following optional dependencies,

pip install cirq-core qiskit amazon-braket-sdk pennylane pyquil pytket pyqir

the QPROGRAM_REGISTRY is automatically updated:

>>> from qbraid import QPROGRAM_REGISTRY
>>> QPROGRAM_REGISTRY
{'cirq': cirq.circuits.circuit.Circuit,
 'qiskit': qiskit.circuit.quantumcircuit.QuantumCircuit,
 'pennylane': pennylane.tape.tape.QuantumTape,
 'pyquil': pyquil.quil.Program,
 'pytket': pytket._tket.circuit.Circuit,
 'braket': braket.circuits.circuit.Circuit,
 'openqasm3': openqasm3.ast.Program,
 'pyqir': Module,
 'qasm2': str,
 'qasm3': str}

This arrangement simplifies the process of targeting and transpiling between various quantum programming frameworks, which will be discussed in detail in the transpiler, interface, and runtime tutorials.

Register New Program Types

You can register new quantum program types using either custom or default type aliases, and overwrite existing type aliases when necessary. For example:

import cirq
import stim

from qbraid import register_program_type

# register new program type with default module alias
register_program_type(stim.Circuit)

# register native program type with custom alias
register_program_type(cirq.Circuit, alias="myalias")

# overwrite existing type alias with new program type
register_program_type(dict, alias="qasm2", overwrite=True)

Load Program

Any quantum program of a natively supported type can be encapsulated within a qbraid.programs.QbraidProgram object. This encapsulation provides a unified framework for circuit manipulation—such as removing idle qubits and reversing qubit order—and for extracting valuable metadata, including the number of qubits, circuit depth, and the unitary representation.

As an example, we’ll start with a simple qiskit circuit that creates the bell state, load the quantum circuit object into the qBraid representation, verify the circuit depth number of qubits, calculate the circuit’s unitary representation, then reverse the qubit order.

from qbraid import load_program

print(qiskit_circuit.draw())
#      ┌───┐
# q_0: ┤ H ├──■──
#      └───┘┌─┴─┐
# q_1: ─────┤ X ├
#           └───┘

qprogram = load_program(qiskit_circuit)

print(qprogram.num_qubits)  # 2
print(qprogram.depth)  # 2
print(qprogram.unitary().shape)  # (4, 4)

qprogram.reverse_qubit_order()

print(qprogram.program.draw())
#           ┌───┐
# q_0: ─────┤ X ├
#      ┌───┐└─┬─┘
# q_1: ┤ H ├──■──
#      └───┘

Any quantum program type listed in the qbraid.NATIVE_REGISTRY can be loaded and utilized in this manner. Each QbraidProgram object has num_qubits and depth attributes and unitary() method, regardless of the input circuit type.

Some wrapped program types support additional methods that can help resolve compatibility issues, such as differences in qubit indexing rules across frontends. For instance, the remove_idle_qubits() method allows you to remap qubit indices without having to reconstruct the circuit:

from qbraid import load_program

print(braket_circuit)
# T  : │  0  │  1  │  2  │
#       ┌───┐
# q0 : ─┤ H ├───●─────────
#       └───┘   │
#             ┌─┴─┐
# q2 : ───────┤ X ├───●───
#             └───┘   │
#                   ┌─┴─┐
# q4 : ─────────────┤ X ├─
#                   └───┘
# T  : │  0  │  1  │  2  │

qprogram = load_program(braket_circuit)
qprogram.remove_idle_qubits()

print(qprogram.program)
# T  : │  0  │  1  │  2  │
#       ┌───┐
# q0 : ─┤ H ├───●─────────
#       └───┘   │
#             ┌─┴─┐
# q1 : ───────┤ X ├───●───
#             └───┘   │
#                   ┌─┴─┐
# q2 : ─────────────┤ X ├─
#                   └───┘
# T  : │  0  │  1  │  2  │

Using the remove_idle_qubits method, we mapped the qubit indices of the Amazon Braket circuit from [0, 2, 4] to the contiguous [0, 1, 2] convention.