Generative and Symbolic Descriptions in Qmod
Qmod is Classiq's quantum programming language. It is possible to write Qmod programs in Python using Qmod's Python embedding available through the Classiq SDK. This post covers the powerful integration of Qmod and the Python language and ecosystem realized by Qmod's generative descriptions.
Let's start with a simple example of Qmod's Python embedding. The following program sets two qubits in a Bell state.
@qfunc
def bell_state(a: QBit, b: QBit):
H(a)
CX(a, b)
@qfunc
def main():
a = QBit()
b = QBit()
allocate(a)
allocate(b)
bell_state(a, b)
This Qmod program contains a main
entry point and a bell_state
function. After compiling the program, we get the following QASM code:
gate main__bell_state q0,q1 {
h q0;
cx q0,q1;
}
qreg q[2];
main__bell_state q[0],q[1];
Observe that the bell_state
function was compiled directly into a QASM gate.
It is also possible to create parametric QASMs by adding classical parameters to the main
entry point:
@qfunc
def bell_entangler(angle: CReal, a: QBit, b: QArray):
H(a)
for i in range(b.len):
CRX(angle, a, b[i])
@qfunc
def main(angle: CReal):
a = QBit()
b = QArray(length=4)
allocate(a)
allocate(b)
bell_entangler(angle, a, b)
This program implements a modified version of the Bell state called bell_entangler
, in which the CX gate was replaced by an CRX operation controlled by an angle
parameter and applied to every qubit of the quantum array b
. Notice how a regular Python for-loop is used to describe the quantum function in this case. This could have been written with a native Qmod 'repeat' statement, but it's often more convenient to just use Python. Here's the resulting QASM:
input float[64] angle_param;
gate main__bell_entangler(angle_param) _gate_q_0, _gate_q_1, _gate_q_2, _gate_q_3, _gate_q_4 {
h _gate_q_0;
crx(angle_param) _gate_q_0, _gate_q_1;
crx(angle_param) _gate_q_0, _gate_q_2;
crx(angle_param) _gate_q_0, _gate_q_3;
crx(angle_param) _gate_q_0, _gate_q_4;
}
qubit[5] q;
main__bell_entangler(angle_param) q[0], q[1], q[2], q[3], q[4];
Qmod's generative descriptions make it possible to integrate Python objects and control flow into quantum functions. For example, we can add a Python list
parameter to our bell_entangler
function:
@qfunc
def bell_entangler(multipliers: list[float], angle: CReal, a: QBit, b: QArray):
H(a)
for i in range(b.len):
CRX(angle * numpy.arctan(multipliers[i]), a, b[i])
@qfunc
def main(angle: CReal):
a = QBit()
b = QArray(length=4)
allocate(a)
allocate(b)
bell_entangler([1, 2, 3, 4], angle, a, b)
bell_entangler(numpy.arange(0, 1, 0.3), angle, a, b)
Function bell_entangler
now accepts a Pythonic parameter multipliers
comprising a list of float values. Since multipliers
is a Python object, we can send its elements to a third-party library, Numpy. Unlike the previous example, here the Python loop and iteration variable are mandatory, because Numpy cannot handle a Qmod symbolic expression. We call bell_entangler
a generative description because it generates a different gate for each value of multipliers
. As bell_entangler
is called twice in the program above, the Qmod compiler generates two instances of the function, one for multipliers=[1, 2, 3, 4]
and one for multipliers=numpy.arange(0, 1, 0.3)
.
input float[64] angle_param;
gate main__bell_entangler_0(angle_param) _gate_q_0, _gate_q_1, _gate_q_2, _gate_q_3, _gate_q_4 {
h _gate_q_0;
crx(pi/4*angle_param) _gate_q_0, _gate_q_1;
crx(1.1071*angle_param) _gate_q_0, _gate_q_2;
crx(1.2490*angle_param) _gate_q_0, _gate_q_3;
crx(1.3258*angle_param) _gate_q_0, _gate_q_4;
}
gate main__bell_entangler_1(angle_param) _gate_q_0, _gate_q_1, _gate_q_2, _gate_q_3, _gate_q_4 {
h _gate_q_0;
crx(0) _gate_q_0, _gate_q_1;
crx(0.2914*angle_param) _gate_q_0, _gate_q_2;
crx(0.5404*angle_param) _gate_q_0, _gate_q_3;
crx(0.7328*angle_param) _gate_q_0, _gate_q_4;
}
qubit[5] q;
main__bell_entangler_0(angle_param) q[0], q[1], q[2], q[3], q[4];
main__bell_entangler_1(angle_param) q[0], q[1], q[2], q[3], q[4];
Because the multipliers
parameter is Pythonic, it was interpreted and erased by the Qmod compiler. On the other hand, the symbolic angle
parameter is preserved in the final QASM.
The Classiq library includes several models that employ generative descriptions, such as our Decoded Quantum Interferometry implementation. This and other Qmod models use generative descriptions to enrich quantum functions with the awsome features of the Python ecosystem.
Qmod is Classiq's quantum programming language. It is possible to write Qmod programs in Python using Qmod's Python embedding available through the Classiq SDK. This post covers the powerful integration of Qmod and the Python language and ecosystem realized by Qmod's generative descriptions.
Let's start with a simple example of Qmod's Python embedding. The following program sets two qubits in a Bell state.
@qfunc
def bell_state(a: QBit, b: QBit):
H(a)
CX(a, b)
@qfunc
def main():
a = QBit()
b = QBit()
allocate(a)
allocate(b)
bell_state(a, b)
This Qmod program contains a main
entry point and a bell_state
function. After compiling the program, we get the following QASM code:
gate main__bell_state q0,q1 {
h q0;
cx q0,q1;
}
qreg q[2];
main__bell_state q[0],q[1];
Observe that the bell_state
function was compiled directly into a QASM gate.
It is also possible to create parametric QASMs by adding classical parameters to the main
entry point:
@qfunc
def bell_entangler(angle: CReal, a: QBit, b: QArray):
H(a)
for i in range(b.len):
CRX(angle, a, b[i])
@qfunc
def main(angle: CReal):
a = QBit()
b = QArray(length=4)
allocate(a)
allocate(b)
bell_entangler(angle, a, b)
This program implements a modified version of the Bell state called bell_entangler
, in which the CX gate was replaced by an CRX operation controlled by an angle
parameter and applied to every qubit of the quantum array b
. Notice how a regular Python for-loop is used to describe the quantum function in this case. This could have been written with a native Qmod 'repeat' statement, but it's often more convenient to just use Python. Here's the resulting QASM:
input float[64] angle_param;
gate main__bell_entangler(angle_param) _gate_q_0, _gate_q_1, _gate_q_2, _gate_q_3, _gate_q_4 {
h _gate_q_0;
crx(angle_param) _gate_q_0, _gate_q_1;
crx(angle_param) _gate_q_0, _gate_q_2;
crx(angle_param) _gate_q_0, _gate_q_3;
crx(angle_param) _gate_q_0, _gate_q_4;
}
qubit[5] q;
main__bell_entangler(angle_param) q[0], q[1], q[2], q[3], q[4];
Qmod's generative descriptions make it possible to integrate Python objects and control flow into quantum functions. For example, we can add a Python list
parameter to our bell_entangler
function:
@qfunc
def bell_entangler(multipliers: list[float], angle: CReal, a: QBit, b: QArray):
H(a)
for i in range(b.len):
CRX(angle * numpy.arctan(multipliers[i]), a, b[i])
@qfunc
def main(angle: CReal):
a = QBit()
b = QArray(length=4)
allocate(a)
allocate(b)
bell_entangler([1, 2, 3, 4], angle, a, b)
bell_entangler(numpy.arange(0, 1, 0.3), angle, a, b)
Function bell_entangler
now accepts a Pythonic parameter multipliers
comprising a list of float values. Since multipliers
is a Python object, we can send its elements to a third-party library, Numpy. Unlike the previous example, here the Python loop and iteration variable are mandatory, because Numpy cannot handle a Qmod symbolic expression. We call bell_entangler
a generative description because it generates a different gate for each value of multipliers
. As bell_entangler
is called twice in the program above, the Qmod compiler generates two instances of the function, one for multipliers=[1, 2, 3, 4]
and one for multipliers=numpy.arange(0, 1, 0.3)
.
input float[64] angle_param;
gate main__bell_entangler_0(angle_param) _gate_q_0, _gate_q_1, _gate_q_2, _gate_q_3, _gate_q_4 {
h _gate_q_0;
crx(pi/4*angle_param) _gate_q_0, _gate_q_1;
crx(1.1071*angle_param) _gate_q_0, _gate_q_2;
crx(1.2490*angle_param) _gate_q_0, _gate_q_3;
crx(1.3258*angle_param) _gate_q_0, _gate_q_4;
}
gate main__bell_entangler_1(angle_param) _gate_q_0, _gate_q_1, _gate_q_2, _gate_q_3, _gate_q_4 {
h _gate_q_0;
crx(0) _gate_q_0, _gate_q_1;
crx(0.2914*angle_param) _gate_q_0, _gate_q_2;
crx(0.5404*angle_param) _gate_q_0, _gate_q_3;
crx(0.7328*angle_param) _gate_q_0, _gate_q_4;
}
qubit[5] q;
main__bell_entangler_0(angle_param) q[0], q[1], q[2], q[3], q[4];
main__bell_entangler_1(angle_param) q[0], q[1], q[2], q[3], q[4];
Because the multipliers
parameter is Pythonic, it was interpreted and erased by the Qmod compiler. On the other hand, the symbolic angle
parameter is preserved in the final QASM.
The Classiq library includes several models that employ generative descriptions, such as our Decoded Quantum Interferometry implementation. This and other Qmod models use generative descriptions to enrich quantum functions with the awsome features of the Python ecosystem.
About "The Qubit Guy's Podcast"
Hosted by The Qubit Guy (Yuval Boger, our Chief Marketing Officer), the podcast hosts thought leaders in quantum computing to discuss business and technical questions that impact the quantum computing ecosystem. Our guests provide interesting insights about quantum computer software and algorithm, quantum computer hardware, key applications for quantum computing, market studies of the quantum industry and more.
If you would like to suggest a guest for the podcast, please contact us.