Skip to content

Commit 0938455

Browse files
Add Gaussian OpenPulse waveform generator with PyQASM module export (#77)
* Add Gaussian OpenPulse waveform generator with PyQASM module export * Resolving to the correct dependencies * Made the implementation more modular and futureproof for other types of pulses * Added unit tests for Gaussian OpenPulse generator. The tests cover generation, overrides, and unrolling. * Added an example notebook of how to use the generator on its own, and as a building block for larger experiments, such as Qubit Spectroscopy * Fixed tests to address both structure and content of the generated QASM
1 parent fe6768a commit 0938455

7 files changed

Lines changed: 558 additions & 1 deletion

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,3 +164,4 @@ cython_debug/
164164
# and can be added to the global gitignore or merged into this file. For a more nuclear
165165
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
166166
#.idea/
167+
qbraid_algorithms/openpulse/scripts/
Lines changed: 316 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,316 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "markdown",
5+
"id": "8818805c",
6+
"metadata": {},
7+
"source": [
8+
"# Gaussian OpenPulse waveform generator\n",
9+
"\n",
10+
"This notebook shows how to use `qbraid_algorithms.openpulse` to generate an **OpenQASM 3 + OpenPulse** program that defines a **Gaussian** waveform and a `defcal` that plays it on a target qubit."
11+
]
12+
},
13+
{
14+
"cell_type": "code",
15+
"execution_count": 2,
16+
"id": "b14ce19a",
17+
"metadata": {},
18+
"outputs": [],
19+
"source": [
20+
"import pyqasm\n",
21+
"\n",
22+
"from qbraid_algorithms.openpulse import GaussianPulse, PulseParams, generate_program\n"
23+
]
24+
},
25+
{
26+
"cell_type": "markdown",
27+
"id": "0eec9271",
28+
"metadata": {},
29+
"source": [
30+
"## Generate a standalone Gaussian pulse QASM program"
31+
]
32+
},
33+
{
34+
"cell_type": "markdown",
35+
"id": "08e0e059",
36+
"metadata": {},
37+
"source": [
38+
"First we define the parameters of the pulse we want to play on a target qubit."
39+
]
40+
},
41+
{
42+
"cell_type": "code",
43+
"execution_count": 12,
44+
"id": "30727e52",
45+
"metadata": {},
46+
"outputs": [],
47+
"source": [
48+
"pulse = GaussianPulse(amplitude=1.0 + 2.0j, duration=\"16ns\", sigma=\"4ns\")\n",
49+
"params = PulseParams(\n",
50+
" frame_frequency=5.0e9,\n",
51+
" frame_phase=0.0,\n",
52+
" port_name=\"d0\",\n",
53+
" frame_name=\"driveframe\",\n",
54+
" waveform_name=\"wf\",\n",
55+
" defcal_name=\"play_gaussian\",\n",
56+
" qubit=0,\n",
57+
")"
58+
]
59+
},
60+
{
61+
"cell_type": "markdown",
62+
"id": "276571ed",
63+
"metadata": {},
64+
"source": [
65+
"Then we pass the parameters to a `generate_program()` call to load a PyQASM module that defines and plays the pulse."
66+
]
67+
},
68+
{
69+
"cell_type": "code",
70+
"execution_count": 13,
71+
"id": "d6f8d58a",
72+
"metadata": {},
73+
"outputs": [],
74+
"source": [
75+
"module = generate_program(pulse=pulse, params=params)"
76+
]
77+
},
78+
{
79+
"cell_type": "markdown",
80+
"id": "7df56c25",
81+
"metadata": {},
82+
"source": [
83+
"After loading the program as a PyQASM module, we can run all the standard PyQASM operations on it, such as displaying it:"
84+
]
85+
},
86+
{
87+
"cell_type": "code",
88+
"execution_count": 14,
89+
"id": "16944412",
90+
"metadata": {},
91+
"outputs": [
92+
{
93+
"name": "stdout",
94+
"output_type": "stream",
95+
"text": [
96+
"OPENQASM 3.0;\n",
97+
"defcalgrammar \"openpulse\";\n",
98+
"cal {\n",
99+
" port d0;\n",
100+
" frame driveframe = newframe(d0, 5000000000.0, 0.0);\n",
101+
" waveform wf = gaussian(1.0 + 2.0im, 16ns, 4ns);\n",
102+
"}\n",
103+
"defcal play_gaussian() $0 {\n",
104+
" play(driveframe, wf);\n",
105+
"}\n",
106+
"\n"
107+
]
108+
}
109+
],
110+
"source": [
111+
"print(pyqasm.dumps(module))"
112+
]
113+
},
114+
{
115+
"cell_type": "markdown",
116+
"id": "4535603f",
117+
"metadata": {},
118+
"source": [
119+
"or unrolling it:"
120+
]
121+
},
122+
{
123+
"cell_type": "code",
124+
"execution_count": 15,
125+
"id": "7f3c924f",
126+
"metadata": {},
127+
"outputs": [
128+
{
129+
"name": "stdout",
130+
"output_type": "stream",
131+
"text": [
132+
"OPENQASM 3.0;\n",
133+
"qubit[1] __PYQASM_QUBITS__;\n",
134+
"defcalgrammar \"openpulse\";\n",
135+
"cal {\n",
136+
" port d0;\n",
137+
" frame driveframe = newframe(d0, 5000000000.0, 0.0, 0ns);\n",
138+
" waveform wf = gaussian(1.0 + 2.0im, 16.0ns, 4.0ns);\n",
139+
"}\n",
140+
"defcal play_gaussian() $0 {\n",
141+
" play(driveframe, wf);\n",
142+
"}\n",
143+
"\n"
144+
]
145+
}
146+
],
147+
"source": [
148+
"unrolled = module.copy()\n",
149+
"unrolled.unroll()\n",
150+
"\n",
151+
"print(pyqasm.dumps(unrolled))"
152+
]
153+
},
154+
{
155+
"cell_type": "markdown",
156+
"id": "a76f9284",
157+
"metadata": {},
158+
"source": [
159+
"## Example: Qubit Spectroscopy"
160+
]
161+
},
162+
{
163+
"cell_type": "markdown",
164+
"id": "52d3f2e8",
165+
"metadata": {},
166+
"source": [
167+
"Qubit spectroscopy is a common pulse-level experiment where the qubit is driven with the same pulse\n",
168+
"while the drive frequency is swept. By observing the qubit’s response as a function of frequency,\n",
169+
"its transition frequency can be identified.\n",
170+
"\n",
171+
"In this example, we use the Gaussian OpenPulse generator to define a reusable pulse waveform and a\n",
172+
"`defcal` routine that plays it on a drive frame. This calibration is then called inside a frequency\n",
173+
"sweep loop, showing how the generator can be used as a building block for larger pulse-level\n",
174+
"experiments."
175+
]
176+
},
177+
{
178+
"cell_type": "code",
179+
"execution_count": 18,
180+
"id": "3362ad37",
181+
"metadata": {},
182+
"outputs": [
183+
{
184+
"name": "stdout",
185+
"output_type": "stream",
186+
"text": [
187+
"OPENQASM 3.0;\n",
188+
"defcalgrammar \"openpulse\";\n",
189+
"cal {\n",
190+
" port d0;\n",
191+
" frame driveframe = newframe(d0, 5000000000.0, 0.0);\n",
192+
" waveform wf = gaussian(1.0 + 2.0im, 16ns, 4ns);\n",
193+
"}\n",
194+
"defcal play_gaussian() $0 {\n",
195+
" play(driveframe, wf);\n",
196+
"}\n",
197+
"\n",
198+
"const float frequency_start = 4.5e9;\n",
199+
"const float frequency_step = 1e6;\n",
200+
"const int frequency_num_steps = 3;\n",
201+
"\n",
202+
"cal {\n",
203+
" set_frequency(driveframe, frequency_start);\n",
204+
"}\n",
205+
"\n",
206+
"for int i in [1:frequency_num_steps] {\n",
207+
" cal {\n",
208+
" shift_frequency(driveframe, frequency_step);\n",
209+
" }\n",
210+
" play_gaussian $0;\n",
211+
" measure $0;\n",
212+
"}\n",
213+
"\n"
214+
]
215+
}
216+
],
217+
"source": [
218+
"import pyqasm\n",
219+
"\n",
220+
"base_module = generate_program(pulse=pulse, params=params)\n",
221+
"base_qasm = pyqasm.dumps(base_module).rstrip()\n",
222+
"\n",
223+
"qubit_spectroscopy_qasm_code = base_qasm + f\"\"\"\n",
224+
"\n",
225+
"const float frequency_start = 4.5e9;\n",
226+
"const float frequency_step = 1e6;\n",
227+
"const int frequency_num_steps = 3;\n",
228+
"\n",
229+
"cal {{\n",
230+
" set_frequency({params.frame_name}, frequency_start);\n",
231+
"}}\n",
232+
"\n",
233+
"for int i in [1:frequency_num_steps] {{\n",
234+
" cal {{\n",
235+
" shift_frequency({params.frame_name}, frequency_step);\n",
236+
" }}\n",
237+
" {params.defcal_name} ${params.qubit};\n",
238+
" measure ${params.qubit};\n",
239+
"}}\n",
240+
"\"\"\"\n",
241+
"\n",
242+
"print(qubit_spectroscopy_qasm_code)"
243+
]
244+
},
245+
{
246+
"cell_type": "code",
247+
"execution_count": 19,
248+
"id": "deb69867",
249+
"metadata": {},
250+
"outputs": [
251+
{
252+
"name": "stdout",
253+
"output_type": "stream",
254+
"text": [
255+
"OPENQASM 3.0;\n",
256+
"qubit[1] __PYQASM_QUBITS__;\n",
257+
"defcalgrammar \"openpulse\";\n",
258+
"cal {\n",
259+
" port d0;\n",
260+
" frame driveframe = newframe(d0, 5000000000.0, 0.0, 0ns);\n",
261+
" waveform wf = gaussian(1.0 + 2.0im, 16.0ns, 4.0ns);\n",
262+
"}\n",
263+
"defcal play_gaussian() $0 {\n",
264+
" play(driveframe, wf);\n",
265+
"}\n",
266+
"cal {\n",
267+
" set_frequency(driveframe, 4500000000.0);\n",
268+
"}\n",
269+
"cal {\n",
270+
" shift_frequency(driveframe, 4501000000.0);\n",
271+
"}\n",
272+
"play_gaussian __PYQASM_QUBITS__[0];\n",
273+
"measure __PYQASM_QUBITS__[0];\n",
274+
"cal {\n",
275+
" shift_frequency(driveframe, 4502000000.0);\n",
276+
"}\n",
277+
"play_gaussian __PYQASM_QUBITS__[0];\n",
278+
"measure __PYQASM_QUBITS__[0];\n",
279+
"cal {\n",
280+
" shift_frequency(driveframe, 4503000000.0);\n",
281+
"}\n",
282+
"play_gaussian __PYQASM_QUBITS__[0];\n",
283+
"measure __PYQASM_QUBITS__[0];\n",
284+
"\n"
285+
]
286+
}
287+
],
288+
"source": [
289+
"qubit_spectroscopy_module = pyqasm.loads(qubit_spectroscopy_qasm_code)\n",
290+
"qubit_spectroscopy_module.unroll()\n",
291+
"print(pyqasm.dumps(qubit_spectroscopy_module))"
292+
]
293+
}
294+
],
295+
"metadata": {
296+
"kernelspec": {
297+
"display_name": "qbraid-algos",
298+
"language": "python",
299+
"name": "python3"
300+
},
301+
"language_info": {
302+
"codemirror_mode": {
303+
"name": "ipython",
304+
"version": 3
305+
},
306+
"file_extension": ".py",
307+
"mimetype": "text/x-python",
308+
"name": "python",
309+
"nbconvert_exporter": "python",
310+
"pygments_lexer": "ipython3",
311+
"version": "3.11.13"
312+
}
313+
},
314+
"nbformat": 4,
315+
"nbformat_minor": 5
316+
}

pyproject.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ cli = [
3737
"rich>=10.11.0",
3838
"typing-extensions"
3939
]
40+
pulse = [
41+
"pyqasm[pulse]"
42+
]
4043

4144
[project.scripts]
4245
qbraid-algorithms = "qbraid_algorithms.cli.main:app"
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Copyright 2026 qBraid
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""OpenPulse generators for qbraid-algorithms."""
16+
from .gaussian import GaussianPulse, PulseParams, generate_program
17+
18+
__all__ = ["GaussianPulse", "PulseParams", "generate_program"]

0 commit comments

Comments
 (0)