Skip to content

Commit

Permalink
Add py2qasm CLI tool to qlasskit (#59)
Browse files Browse the repository at this point in the history
* Add py2qasm CLI tool to qlasskit

- Implemented py2qasm CLI tool in the qlasskit/tools package.
- Added support for converting Python scripts with qlassf functions to QASM code.
- Integrated argparse for argument parsing with support for input file, entrypoint, output file, compiler, and QASM version options.
- Implemented default behavior for reading from stdin and writing to stdout.
- Provided support for internal and tweedledum compilers and QASM version 3.0 and 2.0.
- Added test cases in test_tools.py to verify functionality.
- Updated setup.py to include py2qasm entry point.
  • Loading branch information
tomv42 authored Jun 12, 2024
1 parent fe64388 commit aa87d14
Show file tree
Hide file tree
Showing 3 changed files with 250 additions and 1 deletion.
100 changes: 100 additions & 0 deletions qlasskit/tools/py2qasm.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
#!/usr/bin/env python3

# Copyright 2023-2024 Davide Gessa
#
# 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.

import argparse
import sys

import qlasskit
from qlasskit.qcircuit import exporter_qasm
from qlasskit.qlassfun import QlassF
from qlasskit.tools.utils import parse_str

from .tools import find_last_qlassf


def read_input(input_file):
if input_file == "-":
return sys.stdin.read()
with open(input_file, "r") as file:
return file.read()


def convert_to_quasm(qlassf: QlassF, compiler="internal", version=3):
qlassf.compile(compiler=compiler)
qcirc = qlassf.circuit()
exporter = exporter_qasm.QasmExporter(version=version)
return exporter.export(qcirc, mode="circuit")


def output_result(result, output_file):
if output_file == "-":
print(result)
else:
with open(output_file, "w") as file:
file.write(str(result))


def main():
parser = argparse.ArgumentParser(
description="Convert qlassf functions in a Python script to qasm code expressions."
)
parser.add_argument(
"-i", "--input-file", default="-", help="Input file (default: stdin)"
)
parser.add_argument("-e", "--entrypoint", help="Entrypoint function name")
parser.add_argument(
"-o", "--output", default="-", help="Output file (default: stdout)"
)
parser.add_argument(
"-c",
"--compiler",
choices=["internal", "tweedledum", "recompiler"],
default="internal",
help="QASM compiler (default: internal)",
)
parser.add_argument(
"-q",
"--qasm-version",
choices=["2.0", "3.0"],
default="3.0",
help="QASM version (default: 3.0)",
)
parser.add_argument(
"-v", "--version", action="version", version=f"qlasskit {qlasskit.__version__}"
)

args = parser.parse_args()

script = read_input(args.input_file)
qlassf_list = parse_str(script)

if args.entrypoint:
qlassf = next((f[1] for f in qlassf_list if f[0] == args.entrypoint), None)
else:
qlassf = find_last_qlassf(qlassf_list)

compiler = args.compiler
version = 3 if args.qasm_version == "3.0" else 2

if qlassf:
bool_expr = convert_to_quasm(qlassf, compiler=compiler, version=version)
output_result(bool_expr, args.output)
else:
print("No qlassf function found", file=sys.stderr)


if __name__ == "__main__":
main()
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
entry_points={
"console_scripts": [
"py2bexp=qlasskit.tools.py2bexp:main",
"py2qasm=qlasskit.tools.py2qasm:main",
]
},
)
150 changes: 149 additions & 1 deletion test/test_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,13 @@

import sympy
from sympy.logic.boolalg import And, Not, Or, is_cnf, is_dnf, is_nnf

# from sympy.logic.boolalg import to_anf
from sympy.logic.utilities.dimacs import load

import qlasskit
from qlasskit.qcircuit import exporter_qasm
from qlasskit.qlassfun import qlassf

from qlasskit.tools import utils

dummy_script = """
Expand Down Expand Up @@ -230,3 +232,149 @@ def test_stdin_input(self):
expr = sympy.parse_expr(result.stdout)
expected = sympy.parse_expr("(x | y | ~z) & (z | ~y)")
self.assertTrue(expr.equals(expected))


dummy_qlassf = """
def c(x: bool, y: bool, z: bool) -> bool:
return (x or y or not z) and (not y or z)
"""


class TestPy2Qasm(unittest.TestCase):

def setUp(self):
# Create a temporary file to hold the dummy script
self.temp_file = tempfile.NamedTemporaryFile(delete=False, suffix=".py")
self.temp_file.write(dummy_script.encode())
self.temp_file.close()

def tearDown(self):
# Remove the temporary file
os.unlink(self.temp_file.name)

def run_command(self, args, stdin_input=None):
try:
result = subprocess.run(
args, input=stdin_input, capture_output=True, text=True, check=True
)
return result
except subprocess.CalledProcessError as e:
print(
f"Command '{' '.join(e.cmd)}' returned non-zero exit status {e.returncode}"
)
print(f"Standard output:\n{e.stdout}")
print(f"Standard error:\n{e.stderr}")
raise

# def test_help(self):
# result = self.run_command(["python", "-m", "qlasskit.tools.py2qasm", "--help"])

def test_version(self):
result = self.run_command(
["python", "-m", "qlasskit.tools.py2qasm", "--version"]
)
self.assertTrue(result.stdout == f"qlasskit {qlasskit.__version__}\n")

def test_output_to_stdout(self):
result = self.run_command(
["python", "-m", "qlasskit.tools.py2qasm", "-i", self.temp_file.name]
)
print(result.stdout)
qf = qlassf(dummy_qlassf, to_compile=True, compiler="internal")
exporter = exporter_qasm.QasmExporter(version=3)
expected = exporter.export(qf.circuit(), mode="circuit") + "\n"
print(expected)
self.assertTrue(result.stdout == expected)

def test_specific_entrypoint(self):
result = self.run_command(
[
"python",
"-m",
"qlasskit.tools.py2qasm",
"-i",
self.temp_file.name,
"-e",
"a",
]
)
print(result.stdout)
qf = qlassf(
"def a(b: bool) -> bool:\n\treturn not b",
to_compile=True,
compiler="internal",
)
exporter = exporter_qasm.QasmExporter(version=3)
expected = exporter.export(qf.circuit(), mode="circuit") + "\n"
self.assertTrue(result.stdout == expected)

def test_output_to_file(self):
with tempfile.NamedTemporaryFile(delete=False) as temp_output:
output_file = temp_output.name
try:
self.run_command(
[
"python",
"-m",
"qlasskit.tools.py2qasm",
"-i",
self.temp_file.name,
"-o",
output_file,
]
)
with open(output_file, "r") as f:
content = f.read()
print(content)
qf = qlassf(dummy_qlassf, to_compile=True, compiler="internal")
exporter = exporter_qasm.QasmExporter(version=3)
expected = exporter.export(qf.circuit(), mode="circuit")
self.assertTrue(content == expected)
finally:
os.unlink(output_file)

def test_stdin_input(self):
result = self.run_command(
["python", "-m", "qlasskit.tools.py2qasm"], stdin_input=dummy_script
)
print(result.stdout)
qf = qlassf(dummy_qlassf, to_compile=True, compiler="internal")
exporter = exporter_qasm.QasmExporter(version=3)
expected = exporter.export(qf.circuit(), mode="circuit") + "\n"
self.assertTrue(result.stdout == expected)

def test_qasm_version_2(self):
result = self.run_command(
[
"python",
"-m",
"qlasskit.tools.py2qasm",
"-i",
self.temp_file.name,
"-q",
"2.0",
]
)
print(result.stdout)
qf = qlassf(dummy_qlassf, to_compile=True, compiler="internal")
exporter = exporter_qasm.QasmExporter(version=2)
expected = exporter.export(qf.circuit(), mode="circuit") + "\n"
self.assertTrue(result.stdout == expected)

def test_qasm_version_3(self):
result = self.run_command(
[
"python",
"-m",
"qlasskit.tools.py2qasm",
"-i",
self.temp_file.name,
"-q",
"3.0",
]
)
print(result.stdout)
qf = qlassf(dummy_qlassf, to_compile=True, compiler="internal")
exporter = exporter_qasm.QasmExporter(version=3)
expected = exporter.export(qf.circuit(), mode="circuit") + "\n"
self.assertTrue(result.stdout == expected)

0 comments on commit aa87d14

Please sign in to comment.