Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,26 @@ PyQASM requires Python 3.10 or greater, and can be installed with pip as follows
pip install pyqasm
```

### Optional Dependencies
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove these README changes, I have already consolidated these extra installs under 1 head.


PyQASM provides an optional extra called pyqasm[pulse] that adds pulse/calibration features.

```bash
pip install pyqasm[pulse]
```

PyQASM also offers optional extras for command-line interface (CLI) functionality and for program visualization.

To install the CLI tools:
```bash
pip install pyqasm[cli]
```

To install the visualization tools:
```bash
pip install pyqasm[visualization]
```

### Install from source

You can also install from source by cloning this repository and running a pip install command
Expand Down
141 changes: 141 additions & 0 deletions examples/pulse_unroll_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
# Copyright 2025 qBraid
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# pylint: disable=invalid-name

"""
Script demonstrating how to unroll a QASM 3 program using pyqasm.

"""

from pyqasm import dumps, loads

complex_example = """
OPENQASM 3;
defcalgrammar "openpulse";

cal {
extern port q0_drive;
extern port q0_readout;
extern frame q0_frame = newframe(q0_drive, 5.2e9, 0.0);
extern frame q0_readout_frame = newframe(q0_readout, 6.1e9, 0.0);
extern frame q0_acquire = newframe(q0_readout, 6.1e9, 0.0);
}

const duration pulse_length = 60ns;
const duration meas_length = 800ns;
const duration buffer_time = 20ns;

waveform gaussian_pulse = gaussian(pulse_length, 0.3, 10ns);
waveform drag_pulse = gaussian(pulse_length, 0.25, 10ns, alpha=0.5);
waveform meas_pulse = constant(meas_length, 0.2);
waveform zero_pad = constant(buffer_time, 0.0);

defcal rb_sequence $q0 {
for int i in [0:20] {
bit[2] selector = random[2];

switch(selector) {
case 0: play(q0_frame, gaussian_pulse);
case 1: play(q0_frame, drag_pulse);
case 2: {
play(q0_frame, gaussian_pulse);
delay[pulse_length] q0_frame;
play(q0_frame, drag_pulse);
}
default: {
play(q0_frame, drag_pulse);
delay[pulse_length] q0_frame;
play(q0_frame, gaussian_pulse);
}
}
delay[buffer_time] q0_frame;
}

play(q0_readout_frame, meas_pulse);
capture(q0_acquire, meas_pulse);

box {
bit result = get_measure(q0_acquire);
stream result;
if (result) {
delay[1ms] q0_frame;
}
}
}

qubit[1] q;
bit[1] c;

rb_sequence q[0];
c[0] = measure q[0];
"""

simple_example = """
OPENQASM 3;
defcalgrammar "openpulse";

cal {
port tx_port;
frame tx_frame = newframe(tx_port2, 7883050000.0, 0);
waveform readout_waveform_wf = constant(5e-06, 0.03);
for int shot in [0:499] {
play(readout_waveform_wf, tx_frame2);
barrier tx_frame2;
}
}
"""

# cal {
# extern drag(complex[size] amp, duration l, duration sigma, float[size] beta) -> waveform;
# extern gaussian_square(complex[size] amp, duration l, duration square_width, duration sigma) -> waveform;

# extern port q0;
# extern port q1;

# frame q0_frame = newframe(q0, q0_freq, 0);
# frame q1_frame = newframe(q1, q1_freq, 0);
# }

example = """
OPENQASM 3;
defcalgrammar "openpulse";

const float q0_freq = 5.0e9;
const float q1_freq = 5.1e9;

defcal rz(angle theta) $0 {
shift_phase(q0_frame, theta);
}

defcal rz(angle theta) $1 {
shift_phase(q1_frame, theta);
}

defcal sx $0 {
waveform sx_wf = drag(0.2+0.1im, 160dt, 40dt, 0.05);
play(q0_frame, sx_wf);
}

defcal sx $1 {
waveform sx_wf = drag(0.1+0.05im, 160dt, 40dt, 0.1);
play(q1_frame, sx_wf);
}
"""

program = loads(simple_example)

program.unroll()

print(dumps(program))
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ test = ["pytest", "pytest-cov", "pytest-mpl", "matplotlib"]
lint = ["black", "isort>=6.0.0", "pylint", "mypy", "qbraid-cli>=0.10.2"]
docs = ["sphinx>=7.3.7,<8.3.0", "sphinx-autodoc-typehints>=1.24,<3.2", "sphinx-rtd-theme>=2.0.0,<4.0.0", "docutils<0.22", "sphinx-copybutton"]
visualization = ["matplotlib"]
pulse = ["openpulse[parser]>=1.0.1"]

[tool.setuptools.package-data]
pyqasm = ["py.typed", "*.pyx"]
Expand Down
14 changes: 9 additions & 5 deletions src/pyqasm/entrypoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from typing import TYPE_CHECKING

import openqasm3
import openpulse

from pyqasm.exceptions import ValidationError
from pyqasm.maps import SUPPORTED_QASM_VERSIONS
Expand Down Expand Up @@ -63,11 +64,14 @@ def loads(program: openqasm3.ast.Program | str) -> QasmModule:
"""
if isinstance(program, str):
try:
program = openqasm3.parse(program)
except openqasm3.parser.QASM3ParsingError as err:
raise ValidationError(f"Failed to parse OpenQASM string: {err}") from err
elif not isinstance(program, openqasm3.ast.Program):
raise TypeError("Input quantum program must be of type 'str' or 'openqasm3.ast.Program'.")
if 'defcalgrammar "openpulse"' in program:
program = openpulse.parse(program)
else:
program = openqasm3.parse(program)
except (openqasm3.parser.QASM3ParsingError, openpulse.parser.OpenPulseParsingError) as err:
raise ValidationError(f"Failed to parse the OpenQASM/Openpulse string: {err}") from err
elif not isinstance(program, openqasm3.ast.Program):
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we check for both the ast types here i.e. for openqasm3.ast.Program and openpulse.ast.Program?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we don’t need to parse inputs separately; we can simply call openpulse.parser.parse_openpulse() inside calibration functions to handle their statement body.
Suggestion: since openpulse.parse() parses both generic QASM AST's and OpenPulse ASTs, we can replace our current call to openqasm.parse() with openpulse.parse().

raise TypeError("Input quantum program must be of type 'str' or 'openqasm3.ast.Program' or 'openpulse.ast.Program'.")
if program.version not in SUPPORTED_QASM_VERSIONS:
raise ValidationError(
f"Unsupported OpenQASM version: {program.version}. "
Expand Down
3 changes: 3 additions & 0 deletions src/pyqasm/modules/qasm3.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ def accept(self, visitor):
visitor (QasmVisitor): The visitor to accept
"""
unrolled_stmt_list = visitor.visit_basic_block(self._statements)
# print("--------------------------------")
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove comments

# print("Unrolled stmt list: ", unrolled_stmt_list)
# print("--------------------------------")
final_stmt_list = visitor.finalize(unrolled_stmt_list)

self._unrolled_ast.statements = final_stmt_list
Loading