From 54ff7099128b1fa618ed224e1ca4c2a1ec14a28f Mon Sep 17 00:00:00 2001 From: Atharva Satpute <55058959+atharva-satpute@users.noreply.github.com> Date: Sat, 8 Jun 2024 00:05:17 +0530 Subject: [PATCH 1/6] Fix: Append '_' to the variable names that are reserved keywords 1. Fixed few existing test cases that got affected due to this change 2. Added reserved keyword list --- src/autoqasm/api.py | 84 ++++++++++++++++++++- test/unit_tests/autoqasm/test_parameters.py | 8 +- test/unit_tests/autoqasm/test_program.py | 4 +- test/unit_tests/autoqasm/test_types.py | 12 +-- 4 files changed, 95 insertions(+), 13 deletions(-) diff --git a/src/autoqasm/api.py b/src/autoqasm/api.py index ce5cb33..5132d64 100644 --- a/src/autoqasm/api.py +++ b/src/autoqasm/api.py @@ -35,6 +35,63 @@ from autoqasm.program.gate_calibrations import GateCalibration from autoqasm.types import QubitIdentifierType as Qubit +reserved_keywords = [ + "angle", + "array", + "barrier", + "bit", + "bool", + "box", + "cal", + "case", + "complex", + "const", + "creg", + "ctrl", + "default", + "defcal", + "defcalgrammar", + "delay", + "duration", + "durationof", + "end", + "euler", + "extern", + "false", + "float", + "frame", + "gate", + "gphase", + "im", + "include", + "input", + "int", + "inv", + "let", + "OPENQASM", + "measure", + "mutable", + "negctrl", + "output", + "pi", + "port", + "pragma", + "qreg", # For backward compatibility + "qubit", + "readonly", + "reset", + "return", + "sizeof", + "stretch", + "switch", + "tau", + "true", + "U", + "uint", + "void", + "waveform", +] + def main( func: Callable | None = None, @@ -400,6 +457,19 @@ def _convert_subroutine( return program_conversion_context.return_variable +def is_reserved_keyword(name: str) -> bool: + """ + Method to check whether or not 'name' is a reserved keyword + + Args: + name (str): Name of the variable to be checked + + Returns: + bool: True, if 'name' is a reserved keyword, False otherwise + """ + return name in reserved_keywords + + def _wrap_for_oqpy_subroutine(f: Callable, options: converter.ConversionOptions) -> Callable: """Wraps the given function into a callable expected by oqpy.subroutine. @@ -419,6 +489,12 @@ def _wrap_for_oqpy_subroutine(f: Callable, options: converter.ConversionOptions) def _func(*args, **kwargs) -> Any: inner_program: oqpy.Program = args[0] with aq_program.get_program_conversion_context().push_oqpy_program(inner_program): + # Bind args and kwargs to '_func' signature + sig = inspect.signature(_func) + bound_args = sig.bind(*args, **kwargs) + bound_args.apply_defaults() + args = bound_args.args + kwargs = bound_args.kwargs result = aq_transpiler.converted_call(f, args[1:], kwargs, options=options) inner_program.autodeclare() return result @@ -440,9 +516,15 @@ def _func(*args, **kwargs) -> Any: f'Parameter "{param.name}" for subroutine "{_func.__name__}" ' "is missing a required type hint." ) + # Check whether 'param.name' is a OpenQasm keyword + if is_reserved_keyword(param.name): + _name = f"{param.name}_" + _func.__annotations__.pop(param.name) + else: + _name = param.name new_param = inspect.Parameter( - name=param.name, + name=_name, kind=param.kind, annotation=aq_types.map_parameter_type(param.annotation), ) diff --git a/test/unit_tests/autoqasm/test_parameters.py b/test/unit_tests/autoqasm/test_parameters.py index a0e1d24..727513a 100644 --- a/test/unit_tests/autoqasm/test_parameters.py +++ b/test/unit_tests/autoqasm/test_parameters.py @@ -401,8 +401,8 @@ def sub(float[64] alpha, float[64] theta) { rx(theta) __qubits__[0]; rx(alpha) __qubits__[1]; } -def rx_alpha(int[32] qubit) { - rx(alpha) __qubits__[qubit]; +def rx_alpha(int[32] qubit_) { + rx(alpha) __qubits__[qubit_]; } float alpha = 0.5; float beta = 1.5; @@ -427,8 +427,8 @@ def parametric(alpha: float, beta: float): bound_prog = parametric.build().make_bound_program({"beta": np.pi}) expected = """OPENQASM 3.0; -def rx_alpha(int[32] qubit, float[64] theta) { - rx(theta) __qubits__[qubit]; +def rx_alpha(int[32] qubit_, float[64] theta) { + rx(theta) __qubits__[qubit_]; } input float alpha; float beta = 3.141592653589793; diff --git a/test/unit_tests/autoqasm/test_program.py b/test/unit_tests/autoqasm/test_program.py index 6f3d1b5..a0d96db 100644 --- a/test/unit_tests/autoqasm/test_program.py +++ b/test/unit_tests/autoqasm/test_program.py @@ -95,8 +95,8 @@ def zne() -> aq.BitVar: def expected(scale, angle): return ( """OPENQASM 3.0; -def circuit(float[64] angle) { - rx(angle) __qubits__[0]; +def circuit(float[64] angle_) { + rx(angle_) __qubits__[0]; cnot __qubits__[0], __qubits__[1]; } output bit return_value; diff --git a/test/unit_tests/autoqasm/test_types.py b/test/unit_tests/autoqasm/test_types.py index 28c56df..322aee8 100644 --- a/test/unit_tests/autoqasm/test_types.py +++ b/test/unit_tests/autoqasm/test_types.py @@ -309,7 +309,7 @@ def main(): annotation_test(True) expected = """OPENQASM 3.0; -def annotation_test(bool input) { +def annotation_test(bool input_) { } annotation_test(true);""" @@ -328,7 +328,7 @@ def main(): annotation_test(1) expected = """OPENQASM 3.0; -def annotation_test(int[32] input) { +def annotation_test(int[32] input_) { } annotation_test(1);""" @@ -347,7 +347,7 @@ def main(): annotation_test(1.0) expected = """OPENQASM 3.0; -def annotation_test(float[64] input) { +def annotation_test(float[64] input_) { } annotation_test(1.0);""" @@ -366,7 +366,7 @@ def main(): annotation_test(1) expected = """OPENQASM 3.0; -def annotation_test(qubit input) { +def annotation_test(qubit input_) { } qubit[2] __qubits__; annotation_test(__qubits__[1]);""" @@ -403,7 +403,7 @@ def main(): annotation_test(a) expected = """OPENQASM 3.0; -def annotation_test(bit input) { +def annotation_test(bit input_) { } bit a = 1; annotation_test(a);""" @@ -423,7 +423,7 @@ def main(): annotation_test(aq.BitVar(1)) expected = """OPENQASM 3.0; -def annotation_test(bit input) { +def annotation_test(bit input_) { } bit __bit_0__ = 1; annotation_test(__bit_0__);""" From 3d477b005fb46c8c830dd29cd2476d8ac135a8a2 Mon Sep 17 00:00:00 2001 From: Atharva Satpute <55058959+atharva-satpute@users.noreply.github.com> Date: Tue, 11 Jun 2024 00:51:07 +0530 Subject: [PATCH 2/6] Add test to verify name mangling works for reserved keywords 1. Created a new file for the reserved keywords --- src/autoqasm/api.py | 85 ++++------------------------ src/autoqasm/reserved_keywords.py | 74 ++++++++++++++++++++++++ test/unit_tests/autoqasm/test_api.py | 26 +++++++++ 3 files changed, 110 insertions(+), 75 deletions(-) create mode 100644 src/autoqasm/reserved_keywords.py diff --git a/src/autoqasm/api.py b/src/autoqasm/api.py index 5132d64..26ead82 100644 --- a/src/autoqasm/api.py +++ b/src/autoqasm/api.py @@ -33,65 +33,9 @@ import autoqasm.types as aq_types from autoqasm import errors from autoqasm.program.gate_calibrations import GateCalibration +from autoqasm.reserved_keywords import is_reserved_keyword from autoqasm.types import QubitIdentifierType as Qubit -reserved_keywords = [ - "angle", - "array", - "barrier", - "bit", - "bool", - "box", - "cal", - "case", - "complex", - "const", - "creg", - "ctrl", - "default", - "defcal", - "defcalgrammar", - "delay", - "duration", - "durationof", - "end", - "euler", - "extern", - "false", - "float", - "frame", - "gate", - "gphase", - "im", - "include", - "input", - "int", - "inv", - "let", - "OPENQASM", - "measure", - "mutable", - "negctrl", - "output", - "pi", - "port", - "pragma", - "qreg", # For backward compatibility - "qubit", - "readonly", - "reset", - "return", - "sizeof", - "stretch", - "switch", - "tau", - "true", - "U", - "uint", - "void", - "waveform", -] - def main( func: Callable | None = None, @@ -380,6 +324,12 @@ def _convert_subroutine( with aq_program.build_program() as program_conversion_context: oqpy_program = program_conversion_context.get_oqpy_program() + # Iterate over list of dictionary keys to avoid runtime error + for key in list(kwargs): + is_keyword, new_name = is_reserved_keyword(key) + if is_keyword: + kwargs[new_name] = kwargs.pop(key) + if f not in program_conversion_context.subroutines_processing: # Mark that we are starting to process this function to short-circuit recursion program_conversion_context.subroutines_processing.add(f) @@ -457,19 +407,6 @@ def _convert_subroutine( return program_conversion_context.return_variable -def is_reserved_keyword(name: str) -> bool: - """ - Method to check whether or not 'name' is a reserved keyword - - Args: - name (str): Name of the variable to be checked - - Returns: - bool: True, if 'name' is a reserved keyword, False otherwise - """ - return name in reserved_keywords - - def _wrap_for_oqpy_subroutine(f: Callable, options: converter.ConversionOptions) -> Callable: """Wraps the given function into a callable expected by oqpy.subroutine. @@ -516,12 +453,10 @@ def _func(*args, **kwargs) -> Any: f'Parameter "{param.name}" for subroutine "{_func.__name__}" ' "is missing a required type hint." ) - # Check whether 'param.name' is a OpenQasm keyword - if is_reserved_keyword(param.name): - _name = f"{param.name}_" + # Check whether 'param.name' is a reserved keyword + is_keyword, _name = is_reserved_keyword(param.name) + if is_keyword: _func.__annotations__.pop(param.name) - else: - _name = param.name new_param = inspect.Parameter( name=_name, diff --git a/src/autoqasm/reserved_keywords.py b/src/autoqasm/reserved_keywords.py new file mode 100644 index 0000000..e829ed0 --- /dev/null +++ b/src/autoqasm/reserved_keywords.py @@ -0,0 +1,74 @@ +# Permalink: https://github.com/openqasm/openqasm/blob/main/source/grammar/qasm3Lexer.g4 +reserved_keywords = { + "angle", + "array", + "barrier", + "bit", + "bool", + "box", + "cal", + "case", + "complex", + "const", + "creg", + "ctrl", + "default", + "defcal", + "defcalgrammar", + "delay", + "duration", + "durationof", + "end", + "euler", + "extern", + "false", + "float", + "frame", + "gate", + "gphase", + "im", + "include", + "input", + "int", + "inv", + "let", + "OPENQASM", + "measure", + "mutable", + "negctrl", + "output", + "pi", + "port", + "pragma", + "qreg", + "qubit", + "readonly", + "reset", + "return", + "sizeof", + "stretch", + "switch", + "tau", + "true", + "U", + "uint", + "void", + "waveform", +} + + +def is_reserved_keyword(name: str) -> bool: + """ + Method to check whether or not 'name' is a reserved keyword + + Args: + name (str): Name of the variable to be checked + + Returns: + tuple[bool, str]: Returns a tuple containing a boolean indicating + whether the input 'name' is a reserved keyword and the modified name. + If 'name' is a reserved keyword, the modified name has an underscore + ('_') appended to it; otherwise, it remains unchanged. + """ + is_keyword = name in reserved_keywords + return (is_keyword, f"{name}_" if is_keyword else name) diff --git a/test/unit_tests/autoqasm/test_api.py b/test/unit_tests/autoqasm/test_api.py index ee7ec1b..eff2eae 100644 --- a/test/unit_tests/autoqasm/test_api.py +++ b/test/unit_tests/autoqasm/test_api.py @@ -16,6 +16,8 @@ the local simulator. """ +import math + import pytest from braket.devices import LocalSimulator from braket.tasks.local_quantum_task import LocalQuantumTask @@ -1266,3 +1268,27 @@ def test(int[32] a, int[32] b) { test(2, 3); test(4, 5);""" assert main.build().to_ir() == expected + + +def test_subroutine_call_with_reserved_keyword(): + """Test that subroutine call works with reserved keyword as a variable name""" + + @aq.subroutine + def make_input_state(input: int, theta: float): + rx(input, theta) + measure(input) + + @aq.main(num_qubits=3) + def teleportation(): + input, theta = 0, math.pi / 2 + make_input_state(theta=theta, input=input) + + expected = """OPENQASM 3.0; +def make_input_state(int[32] input_, float[64] theta) { + rx(theta) __qubits__[input_]; + bit __bit_0__; + __bit_0__ = measure __qubits__[input_]; +} +qubit[3] __qubits__; +make_input_state(0, 1.5707963267948966);""" + assert teleportation.build().to_ir() == expected From 19084a99e0fb874a24e903217b090eb809267399 Mon Sep 17 00:00:00 2001 From: Atharva Satpute <55058959+atharva-satpute@users.noreply.github.com> Date: Tue, 11 Jun 2024 18:31:47 +0530 Subject: [PATCH 3/6] Change return type to tuple --- src/autoqasm/reserved_keywords.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/autoqasm/reserved_keywords.py b/src/autoqasm/reserved_keywords.py index e829ed0..f3839d9 100644 --- a/src/autoqasm/reserved_keywords.py +++ b/src/autoqasm/reserved_keywords.py @@ -57,7 +57,7 @@ } -def is_reserved_keyword(name: str) -> bool: +def is_reserved_keyword(name: str) -> tuple[bool, str]: """ Method to check whether or not 'name' is a reserved keyword From 7cdc39dadf97b0a0f8862a5f136a07555eee377e Mon Sep 17 00:00:00 2001 From: Atharva Satpute <55058959+atharva-satpute@users.noreply.github.com> Date: Tue, 11 Jun 2024 22:32:36 +0530 Subject: [PATCH 4/6] Change function signature to return new name --- src/autoqasm/api.py | 15 +++++++-------- src/autoqasm/reserved_keywords.py | 15 +++++++-------- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/src/autoqasm/api.py b/src/autoqasm/api.py index 26ead82..9de784b 100644 --- a/src/autoqasm/api.py +++ b/src/autoqasm/api.py @@ -33,7 +33,7 @@ import autoqasm.types as aq_types from autoqasm import errors from autoqasm.program.gate_calibrations import GateCalibration -from autoqasm.reserved_keywords import is_reserved_keyword +from autoqasm.reserved_keywords import sanitize_parameter_name from autoqasm.types import QubitIdentifierType as Qubit @@ -326,9 +326,8 @@ def _convert_subroutine( # Iterate over list of dictionary keys to avoid runtime error for key in list(kwargs): - is_keyword, new_name = is_reserved_keyword(key) - if is_keyword: - kwargs[new_name] = kwargs.pop(key) + new_name = sanitize_parameter_name(key) + kwargs[new_name] = kwargs.pop(key) if f not in program_conversion_context.subroutines_processing: # Mark that we are starting to process this function to short-circuit recursion @@ -453,13 +452,13 @@ def _func(*args, **kwargs) -> Any: f'Parameter "{param.name}" for subroutine "{_func.__name__}" ' "is missing a required type hint." ) + # Check whether 'param.name' is a reserved keyword - is_keyword, _name = is_reserved_keyword(param.name) - if is_keyword: - _func.__annotations__.pop(param.name) + new_name = sanitize_parameter_name(param.name) + _func.__annotations__.pop(param.name) new_param = inspect.Parameter( - name=_name, + name=new_name, kind=param.kind, annotation=aq_types.map_parameter_type(param.annotation), ) diff --git a/src/autoqasm/reserved_keywords.py b/src/autoqasm/reserved_keywords.py index f3839d9..ecd86a1 100644 --- a/src/autoqasm/reserved_keywords.py +++ b/src/autoqasm/reserved_keywords.py @@ -1,4 +1,4 @@ -# Permalink: https://github.com/openqasm/openqasm/blob/main/source/grammar/qasm3Lexer.g4 +# Copied from: https://github.com/openqasm/openqasm/blob/main/source/grammar/qasm3Lexer.g4 reserved_keywords = { "angle", "array", @@ -57,18 +57,17 @@ } -def is_reserved_keyword(name: str) -> tuple[bool, str]: +def sanitize_parameter_name(name: str) -> str: """ - Method to check whether or not 'name' is a reserved keyword + Method to modify the variable name if it is a + reserved keyword Args: name (str): Name of the variable to be checked Returns: - tuple[bool, str]: Returns a tuple containing a boolean indicating - whether the input 'name' is a reserved keyword and the modified name. - If 'name' is a reserved keyword, the modified name has an underscore - ('_') appended to it; otherwise, it remains unchanged. + str: Returns a modified 'name' that has an underscore ('_') appended to it; + otherwise, it returns the original 'name' unchanged """ is_keyword = name in reserved_keywords - return (is_keyword, f"{name}_" if is_keyword else name) + return f"{name}_" if is_keyword else name From beb480138a21ef9ea341d90aabba0bda239e8d4a Mon Sep 17 00:00:00 2001 From: Atharva Satpute <55058959+atharva-satpute@users.noreply.github.com> Date: Wed, 12 Jun 2024 22:22:18 +0530 Subject: [PATCH 5/6] Separate keywords using comments --- src/autoqasm/reserved_keywords.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/autoqasm/reserved_keywords.py b/src/autoqasm/reserved_keywords.py index ecd86a1..016d362 100644 --- a/src/autoqasm/reserved_keywords.py +++ b/src/autoqasm/reserved_keywords.py @@ -1,5 +1,9 @@ -# Copied from: https://github.com/openqasm/openqasm/blob/main/source/grammar/qasm3Lexer.g4 +# Copied from: +# https://github.com/openqasm/openqasm/blob/main/source/grammar/qasm3Lexer.g4 +# https://github.com/openqasm/openpulse-python/blob/main/source/grammar/openpulseLexer.g4 + reserved_keywords = { + # openQASM keywords "angle", "array", "barrier", @@ -23,7 +27,6 @@ "extern", "false", "float", - "frame", "gate", "gphase", "im", @@ -38,7 +41,6 @@ "negctrl", "output", "pi", - "port", "pragma", "qreg", "qubit", @@ -53,6 +55,9 @@ "U", "uint", "void", + # openpulse keywords + "frame", + "port", "waveform", } From cd088a6ee21e1d0fbb967e807e4f74b12ba9586c Mon Sep 17 00:00:00 2001 From: Jean-Christophe Jaskula <99367153+jcjaskula-aws@users.noreply.github.com> Date: Wed, 12 Jun 2024 17:24:30 -0400 Subject: [PATCH 6/6] turn into single-liner Co-authored-by: Lauren Capelluto --- src/autoqasm/reserved_keywords.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/autoqasm/reserved_keywords.py b/src/autoqasm/reserved_keywords.py index 016d362..916e398 100644 --- a/src/autoqasm/reserved_keywords.py +++ b/src/autoqasm/reserved_keywords.py @@ -74,5 +74,4 @@ def sanitize_parameter_name(name: str) -> str: str: Returns a modified 'name' that has an underscore ('_') appended to it; otherwise, it returns the original 'name' unchanged """ - is_keyword = name in reserved_keywords - return f"{name}_" if is_keyword else name + return f"{name}_" if name in reserved_keywords else name