Skip to content

Commit

Permalink
Update entrypoint methods (#76)
Browse files Browse the repository at this point in the history
* update entrypoints

* update changelog

* replace qasm_str with __str__
  • Loading branch information
TheGupta2012 authored Nov 14, 2024
1 parent 442ec51 commit cc88012
Show file tree
Hide file tree
Showing 33 changed files with 426 additions and 257 deletions.
62 changes: 62 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,78 @@ Types of changes:
- Added a `dumps` and `formatted_qasm` method to the `QasmModule` class to allow for the conversion of a `QasmModule` object to a string representation of the QASM code ([#71](https://github.com/qBraid/pyqasm/pull/71))
- Added the `populate_idle_qubits` method to the `QasmModule` class to populate idle qubits with an `id` gate ([#72](https://github.com/qBraid/pyqasm/pull/72))
- Added gate definitions for "c3sqrtx", "u1", "rxx", "cu3", "csx", "rccx" , "ch" , "cry", "cp", "cu", "cu1", "rzz" in `maps.py` ([#74](https://github.com/qBraid/pyqasm/pull/74))
- Added support for skipping the unrolling for externally linked gates. The `QasmModule.unroll()` method now accepts an `external_gates` parameter which is a list of gate names that should not be unrolled ([#59](https://github.com/qBraid/pyqasm/pull/59)). Usage -

```python
In [30]: import pyqasm

In [31]: qasm_str = """OPENQASM 3.0;
...: include "stdgates.inc";
...: gate custom q1, q2, q3{
...: x q1;
...: y q2;
...: z q3;
...: }
...:
...: qubit[4] q;
...: custom q[0], q[1], q[2];
...: cx q[1], q[2];"""

In [32]: module = pyqasm.loads(qasm_str)

In [33]: module.unroll(external_gates= ["custom"])

In [34]: pyqasm.dumps(module).splitlines()
Out[34]:
['OPENQASM 3.0;',
'include "stdgates.inc";',
'qubit[4] q;',
'custom q[0], q[1], q[2];',
'cx q[1], q[2];']
```
- **Major Change**: Added the `load`, `loads`, `dump`, and `dumps` functions to the `pyqasm` module to allow for the loading and dumping of QASM code ([#76](https://github.com/qBraid/pyqasm/pull/76)). Usage -

```python
In [18]: import pyqasm

In [19]: qasm_str = """OPENQASM 3.0;
...: include "stdgates.inc";
...: qreg q1[2];
...: qubit[2] q2;"""

In [20]: module = pyqasm.loads(qasm_str)

In [21]: print(pyqasm.dumps(module))
OPENQASM 3.0;
include "stdgates.inc";
qubit[2] q1;
qubit[2] q2;


In [22]: file_path = "test.qasm"

In [23]: pyqasm.dump(module, file_path)

In [24]: module = pyqasm.load(file_path)

In [25]: print(pyqasm.dumps(module))
OPENQASM 3.0;
include "stdgates.inc";
qubit[2] q1;
qubit[2] q2;
```

### Improved / Modified
- Changed the `__init__` method for the `QasmModule` class to only accept an `openqasm3.ast.Program` object as input ([#71](https://github.com/qBraid/pyqasm/pull/71))
- Changed `DepthNode`, `QubitDepthNode`, `ClbitDepthNode`, and `Variable` to dataclasses. `__repr__` method is therefore handled automatically and you don't need all of the redundant private / public attribute and setters ([#79](https://github.com/qBraid/pyqasm/pull/79))
- Simplified `map_qasm_op_to_callable` redundant `KeyError` handling with loop ([#79](https://github.com/qBraid/pyqasm/pull/79))
- The `load` function has been renamed to `loads` and `load` is now used to load a QASM file. `QasmModule.dumps()` has been replaced with `__str__` method ([#76](https://github.com/qBraid/pyqasm/pull/76))

### Deprecated

### Removed
- Removed the `from_program` method from the `QasmModule` class ([#71](https://github.com/qBraid/pyqasm/pull/71))
- `QasmModule.formatted_qasm()` method has been removed ([#76](https://github.com/qBraid/pyqasm/pull/76))

### Fixed
- Updated docs custom CSS used for sphinx to make version stable/latest drop-down visible. Previously was set white so blended into background and wasn't visible. ([#78](https://github.com/qBraid/pyqasm/pull/78))
Expand Down
4 changes: 2 additions & 2 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -118,10 +118,10 @@ Example
result = measure q;
"""
module = pyqasm.load(qasm)
module = pyqasm.loads(qasm)
module.unroll()
unrolled_qasm = module.dumps()
unrolled_qasm = pyqasm.dumps(module)
print(unrolled_qasm)
Expand Down
6 changes: 3 additions & 3 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ bit[4] result;
result = measure q;
"""

qasm_module = pyqasm.load(program)
qasm_module = pyqasm.loads(program)
qasm_module.unroll()
print(qasm_module.dumps())
print(pyqasm.dumps(qasm_module))
```

```text
Expand Down Expand Up @@ -81,7 +81,7 @@ h q[2];
c = measure q;
"""

pyqasm.load(program).validate()
pyqasm.loads(program).validate()
```

```text
Expand Down
4 changes: 2 additions & 2 deletions examples/unroll_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ def deutsch_jozsa(qubit[N] q_func, qubit[1] ancilla_q) {
result = measure q;
"""

qasm_module = pyqasm.load(qasm_program)
qasm_module = pyqasm.loads(qasm_program)
qasm_module.unroll()

print(qasm_module.dumps())
print(pyqasm.dumps(qasm_module))
2 changes: 1 addition & 1 deletion examples/validate_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,4 +84,4 @@ def generic_operation(qubit[N] q) {
c = measure q;
"""

pyqasm.load(qasm_program).validate()
pyqasm.loads(qasm_program).validate()
8 changes: 7 additions & 1 deletion pyqasm/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@
:toctree: ../stubs/
load
loads
dump
dumps
Classes
---------
Expand Down Expand Up @@ -51,7 +54,7 @@
warnings.warn("Importing 'pyqasm' outside a proper installation.")
__version__ = "dev"

from .entrypoint import load
from .entrypoint import dump, dumps, load, loads
from .exceptions import PyQasmError, QasmParsingError, ValidationError
from .modules import Qasm2Module, Qasm3Module, QasmModule

Expand All @@ -60,6 +63,9 @@
"ValidationError",
"QasmParsingError",
"load",
"loads",
"dump",
"dumps",
"QasmModule",
"Qasm2Module",
"Qasm3Module",
Expand Down
63 changes: 58 additions & 5 deletions pyqasm/entrypoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,35 @@
import openqasm3.ast


def load(program: openqasm3.ast.Program | str) -> QasmModule:
"""Loads an OpenQASM 3 program into a `Qasm3Module` object.
def load(filename: str) -> QasmModule:
"""Loads an OpenQASM program into a `QasmModule` object.
Args:
program (openqasm3.ast.Program or str): The OpenQASM 3 program to validate.
filename (str): The filename of the OpenQASM program to validate.
Returns:
QasmModule: An object containing the parsed qasm representation along with
some useful metadata and methods
"""
if not isinstance(filename, str):
raise TypeError("Input 'filename' must be of type 'str'.")
with open(filename, "r", encoding="utf-8") as file:
program = file.read()
return loads(program)


def loads(program: openqasm3.ast.Program | str) -> QasmModule:
"""Loads an OpenQASM program into a `QasmModule` object.
Args:
program (openqasm3.ast.Program or str): The OpenQASM program to validate.
Raises:
TypeError: If the input is not a string or an `openqasm3.ast.Program` instance.
ValidationError: If the program fails parsing or semantic validation.
Returns:
Qasm3Module: An object containing the parsed qasm representation along with
QasmModule: An object containing the parsed qasm representation along with
some useful metadata and methods
"""
if isinstance(program, str):
Expand All @@ -48,7 +65,10 @@ def load(program: openqasm3.ast.Program | str) -> QasmModule:
elif not isinstance(program, openqasm3.ast.Program):
raise TypeError("Input quantum program must be of type 'str' or 'openqasm3.ast.Program'.")
if program.version not in SUPPORTED_QASM_VERSIONS:
raise ValidationError(f"Unsupported OpenQASM version: {program.version}")
raise ValidationError(
f"Unsupported OpenQASM version: {program.version}. "
f"Supported versions are: {SUPPORTED_QASM_VERSIONS}"
)

# change version string to x.0 format
program.version = str(float(program.version))
Expand All @@ -57,3 +77,36 @@ def load(program: openqasm3.ast.Program | str) -> QasmModule:
module = qasm_module("main", program)

return module


def dump(module: QasmModule, filename: str = "main.qasm") -> None:
"""Dumps the `QasmModule` object to a file.
Args:
module (QasmModule): The module to dump.
filename (str): The filename to dump to.
Returns:
None
"""
qasm_string = dumps(module)
with open(filename, "w", encoding="utf-8") as file:
file.write(qasm_string)


def dumps(module: QasmModule) -> str:
"""Dumps the `QasmModule` object to a string.
Args:
module (QasmModule): The module to dump.
Raises:
TypeError: If the input is not a `QasmModule` instance
Returns:
str: The dumped module as string.
"""
if not isinstance(module, QasmModule):
raise TypeError("Input 'module' must be of type pyqasm.modules.base.QasmModule")

return str(module)
11 changes: 1 addition & 10 deletions pyqasm/modules/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -474,7 +474,7 @@ def unroll(self, **kwargs):
)
raise err

def dumps(self) -> str:
def __str__(self) -> str:
"""Return the string representation of the QASM program
Returns:
Expand All @@ -485,15 +485,6 @@ def dumps(self) -> str:
return self._qasm_ast_to_str(self.unrolled_ast)
return self._qasm_ast_to_str(self.original_program)

def formatted_qasm(self) -> str:
"""Return the formatted QASM program
Removes empty lines and comments from the QASM program
Returns:
str: The formatted QASM program
"""
return self.dumps()

def copy(self):
"""Return a deep copy of the module"""
return deepcopy(self)
Expand Down
16 changes: 8 additions & 8 deletions tests/qasm2/test_declarations.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"""
import pytest

from pyqasm.entrypoint import load
from pyqasm.entrypoint import dumps, loads
from pyqasm.exceptions import ValidationError
from tests.utils import check_unrolled_qasm

Expand All @@ -36,9 +36,9 @@ def test_qubit_declarations():
qreg q[1];
"""

result = load(qasm2_string)
result = loads(qasm2_string)
result.unroll()
unrolled_qasm = result.dumps()
unrolled_qasm = dumps(result)

check_unrolled_qasm(unrolled_qasm, expected_qasm)

Expand All @@ -60,9 +60,9 @@ def test_clbit_declarations():
creg c[1];
"""

result = load(qasm2_string)
result = loads(qasm2_string)
result.unroll()
unrolled_qasm = result.dumps()
unrolled_qasm = dumps(result)

check_unrolled_qasm(unrolled_qasm, expected_qasm)

Expand Down Expand Up @@ -91,9 +91,9 @@ def test_qubit_clbit_declarations():
creg c2[2];
"""

result = load(qasm2_string)
result = loads(qasm2_string)
result.unroll()
unrolled_qasm = result.dumps()
unrolled_qasm = dumps(result)

check_unrolled_qasm(unrolled_qasm, expected_qasm)

Expand All @@ -109,4 +109,4 @@ def test_invalid_qasm2_declarations():
qubit q3;
"""
with pytest.raises(ValidationError):
load(invalid_qasm2).validate()
loads(invalid_qasm2).validate()
8 changes: 4 additions & 4 deletions tests/qasm2/test_depth.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"""

from pyqasm.entrypoint import load
from pyqasm.entrypoint import loads


def test_gate_depth():
Expand All @@ -36,7 +36,7 @@ def test_gate_depth():
qreg q[1];
my_gate(1, 2, 3) q;
"""
result = load(qasm3_string)
result = loads(qasm3_string)
result.unroll()
assert result.num_qubits == 1
assert result.num_clbits == 0
Expand All @@ -57,7 +57,7 @@ def test_qubit_depth_with_unrelated_measure_op():
// This should affect the depth as measurement will have to wait
measure q1 -> c;
"""
result = load(qasm3_string)
result = loads(qasm3_string)
result.unroll()
assert result.num_qubits == 4
assert result.num_clbits == 1
Expand All @@ -70,7 +70,7 @@ def test_depth_with_no_ops():
include "stdgates.inc";
qreg q;
"""
result = load(qasm3_string)
result = loads(qasm3_string)
result.unroll()
assert result.num_qubits == 1
assert result.num_clbits == 0
Expand Down
10 changes: 5 additions & 5 deletions tests/qasm2/test_format.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"""

from pyqasm.entrypoint import load
from pyqasm.entrypoint import dumps, loads
from tests.utils import check_unrolled_qasm


Expand All @@ -33,8 +33,8 @@ def test_comments_removed_from_qasm():
h q[0];
"""

result = load(qasm2_string)
actual_qasm2_string = result.formatted_qasm()
result = loads(qasm2_string)
actual_qasm2_string = dumps(result)

check_unrolled_qasm(actual_qasm2_string, expected_qasm2_string)

Expand Down Expand Up @@ -62,7 +62,7 @@ def test_empty_lines_removed_from_qasm():
h q[0];
"""

result = load(qasm2_string)
actual_qasm2_string = result.formatted_qasm()
result = loads(qasm2_string)
actual_qasm2_string = dumps(result)

check_unrolled_qasm(actual_qasm2_string, expected_qasm2_string)
Loading

0 comments on commit cc88012

Please sign in to comment.