From 7af005dea2b0aa70815fde9d92f750e9c592e524 Mon Sep 17 00:00:00 2001 From: Jean-Christophe Jaskula Date: Thu, 27 Oct 2022 12:16:01 -0400 Subject: [PATCH 01/18] Allow program creation from oq text --- oqpy/classical_types.py | 2 + oqpy/program.py | 192 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 192 insertions(+), 2 deletions(-) diff --git a/oqpy/classical_types.py b/oqpy/classical_types.py index d11d7be..f2f997c 100644 --- a/oqpy/classical_types.py +++ b/oqpy/classical_types.py @@ -187,6 +187,8 @@ def __init__( ): name = name or "".join([random.choice(string.ascii_letters) for _ in range(10)]) super().__init__(name, needs_declaration=needs_declaration) + if type_cls is not None: + self.type_cls = type(type_cls) self.type = self.type_cls(**type_kwargs) self.init_expression = init_expression self.annotations = annotations diff --git a/oqpy/program.py b/oqpy/program.py index 5133680..cd23bb1 100644 --- a/oqpy/program.py +++ b/oqpy/program.py @@ -25,9 +25,10 @@ import warnings from copy import deepcopy -from typing import Any, Iterable, Iterator, Optional +from typing import Any, Dict, Iterable, Iterator, Optional, Union from openpulse import ast +from openpulse.parser import parse from openpulse.printer import dumps from openqasm3.visitor import QASMVisitor @@ -93,7 +94,7 @@ class Program: DURATION_MAX_DIGITS = 12 - def __init__(self, version: Optional[str] = "3.0", simplify_constants: bool = True) -> None: + def __init__(self, version: Optional[str] = "3.0", simplify_constants: bool = True, oqasm_text: Optional[str] = None) -> None: self.stack: list[ProgramState] = [ProgramState()] self.defcals: dict[ tuple[tuple[str, ...], str, tuple[str, ...]], ast.CalibrationDefinition @@ -105,6 +106,10 @@ def __init__(self, version: Optional[str] = "3.0", simplify_constants: bool = Tr self.simplify_constants = simplify_constants self.declared_subroutines: set[str] = set() + if oqasm_text is not None: + self.from_qasm(oqasm_text) + return + if version is None or ( len(version.split(".")) in [1, 2] and all([item.isnumeric() for item in version.split(".")]) @@ -307,6 +312,23 @@ def to_ast( MergeCalStatementsPass().visit(prog) return prog + def from_qasm(self, qasm_text: str) -> None: + oqasm_ast = _remove_spans(parse(qasm_text)) + ProgramBuilder().visit(oqasm_ast, self) + + # self._state.finalize_if_clause() + # self._state.body.extend(other._state.body) + # self._state.if_clause = other._state.if_clause + # self._state.finalize_if_clause() + # self.defcals.update(other.defcals) Done + # self.subroutines.update(other.subroutines) + # self.externs.update(other.externs) + # for var in other.declared_vars.values(): + # self._mark_var_declared(var) + # for var in other.undeclared_vars.values(): + # self._add_var(var) + # return self + def to_qasm( self, encal: bool = False, @@ -602,3 +624,169 @@ def process_statement_list( new_list.append(ast.CalibrationStatement(body=cal_stmts)) return new_list + + +class ProgramBuilder(QASMVisitor[Program]): + # Does not support {"dt": 4} + TIME_UNIT_TO_EXP = {"ns": 3, "us": 2, "ms": 1, "s": 0} + + def visit_Program(self, node: ast.Program, context: Program) -> None: + # TODO: need to check better node.version as for init + context.version = node.version + for statement in node.statements: + context._add_statement(statement) + self.generic_visit(node, context) + + def visit_ExternDeclaration(self, node: ast.ExternDeclaration, context: Program) -> None: + context.externs[node.name] = node + + def visit_ClassicalDeclaration(self, node: ast.ClassicalDeclaration, context: Program) -> None: + var: Var + if node.init_expression == None: + init_expression = None + else: + init_expression = self.visit(node.init_expression) + if isinstance(node.type, ast.BitType): + var = classical_types.BitVar(init_expression=init_expression, name=node.identifier.name) + elif isinstance(node.type, ast.BoolType): + var = classical_types.BoolVar( + init_expression=init_expression, name=node.identifier.name + ) + elif isinstance(node.type, ast.IntType): + var = classical_types.IntVar(init_expression=init_expression, name=node.identifier.name) + elif isinstance(node.type, ast.UintType): + var = classical_types.UintVar( + init_expression=init_expression, name=node.identifier.name + ) + elif isinstance(node.type, ast.FloatType): + var = classical_types.FloatVar( + init_expression=init_expression, name=node.identifier.name + ) + elif isinstance(node.type, ast.AngleType): + var = classical_types.AngleVar( + init_expression=init_expression, name=node.identifier.name + ) + elif isinstance(node.type, ast.ComplexType): + var = classical_types.ComplexVar( + init_expression=init_expression, + name=node.identifier.name, + base_type=node.type.base_type, + ) + elif isinstance(node.type, ast.DurationType): + var = classical_types.DurationVar( + init_expression=init_expression, name=node.identifier.name + ) + elif isinstance(node.type, ast.StretchType): + var = classical_types.StretchVar( + init_expression=init_expression, name=node.identifier.name + ) + else: + return + # elif isinstance(node.type, ast.FrameType): + # port, frequency, phase = self.visit(node.init_expression) + # var = FrameVar(port=port, frequency=frequency, phase=phase, name=node.identifier.name) + + # For shorter implementation + # if isinstance(node.type, ast.ComplexType): + # var = classical_types.ComplexVar(init_expression=node.init_expression, name=node.identifier.name, base_type=node.type.base_type) + # else: + # var = classical_types._ClassicalVar(init_expression=node.init_expression, name=node.identifier.name, type_cls=node.type) + context._mark_var_declared(var) + + def visit_CalibrationDefinition( + self, node: ast.CalibrationDefinition, context: Program + ) -> None: + context._add_defcal([ident.name for ident in node.qubits], node.name.name, node) + self.generic_visit(node, context) + + def visit_SubroutineDefinition(self, node: ast.SubroutineDefinition, context: Program) -> None: + context._add_subroutine(node.name.name, node) + self.generic_visit(node, context) + + def visit_FunctionCall(self, node: ast.FunctionCall, context: Program | None = None) -> Any: + # func_name = node.name.name + # return getattr(self, func_name)(node, context) + if node.name == "newframe": + return node.arguments + else: + self.generic_visit(node, context) + + # def newframe(self, node: ast.FunctionCall, context: Program) -> None: + # return [self.visit(arg, context) for arg in node.arguments] + + def visit_IntegerLiteral(self, node: ast.IntegerLiteral, context: Program | None = None) -> Any: + """Visit Integer Literal. + node.value + 1 + Args: + node (ast.IntegerLiteral): The integer literal. + context (_ParseState): The parse state context. + """ + return int(node.value) + + def visit_ImaginaryLiteral( + self, node: ast.ImaginaryLiteral, context: Program | None = None + ) -> Any: + """Visit Imaginary Number Literal. + node.value + 1.3im + Args: + node (ast.visit_ImaginaryLiteral): The imaginary number literal. + context (_ParseState): The parse state context. + """ + return complex(node.value * 1j) + + def visit_FloatLiteral(self, node: ast.FloatLiteral, context: Program | None = None) -> Any: + """Visit Float Literal. + node.value + 1.1 + Args: + node (ast.FloatLiteral): The float literal. + context (_ParseState): The parse state context. + """ + return float(node.value) + + def visit_BooleanLiteral(self, node: ast.BooleanLiteral, context: Program | None = None) -> Any: + """Visit Boolean Literal. + node.value + true + Args: + node (ast.BooleanLiteral): The boolean literal. + context (_ParseState): The parse state context. + """ + return True if node.value else False + + def visit_DurationLiteral( + self, node: ast.DurationLiteral, context: Program | None = None + ) -> Any: + """Visit Duration Literal. + node.value, node.unit (node.unit.name, node.unit.value) + 1 + Args: + node (ast.DurationLiteral): The duration literal. + context (_ParseState): The parse state context. + """ + if node.unit.name not in self.TIME_UNIT_TO_EXP: + raise ValueError(f"Unexpected duration specified: {node.unit.name}:{node.unit.value}") + multiplier = 10 ** (-3 * self.TIME_UNIT_TO_EXP[node.unit.name]) + return multiplier * node.value + + +def _remove_spans(node: Union[list[ast.QASMNode], ast.QASMNode]) -> ast.QASMNode: + """Return a new ``QASMNode`` with all spans recursively set to ``None`` to + reduce noise in test failure messages.""" + if isinstance(node, list): + return [_remove_spans(item) for item in node] + if not isinstance(node, ast.QASMNode): + return node + kwargs: Dict[str, ast.QASMNode] = {} + no_init: Dict[str, ast.QASMNode] = {} + for field in dataclasses.fields(node): + if field.name == "span": + continue + target = kwargs if field.init else no_init + target[field.name] = _remove_spans(getattr(node, field.name)) + out = type(node)(**kwargs) + for attribute, value in no_init.items(): + setattr(out, attribute, value) + return out From 512ff515bda7a6e858e7aa72807b55a8406159b7 Mon Sep 17 00:00:00 2001 From: Jean-Christophe Jaskula Date: Thu, 27 Oct 2022 16:29:38 -0400 Subject: [PATCH 02/18] Fix extern dict --- oqpy/program.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/oqpy/program.py b/oqpy/program.py index cd23bb1..6d57c1c 100644 --- a/oqpy/program.py +++ b/oqpy/program.py @@ -638,7 +638,7 @@ def visit_Program(self, node: ast.Program, context: Program) -> None: self.generic_visit(node, context) def visit_ExternDeclaration(self, node: ast.ExternDeclaration, context: Program) -> None: - context.externs[node.name] = node + context.externs[node.name.name] = node def visit_ClassicalDeclaration(self, node: ast.ClassicalDeclaration, context: Program) -> None: var: Var From c71263bcbde3232fa452d1340dd2ab4779759886 Mon Sep 17 00:00:00 2001 From: Jean-Christophe Jaskula Date: Thu, 27 Oct 2022 16:29:47 -0400 Subject: [PATCH 03/18] Add builder tests --- tests/test_builder.py | 1114 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1114 insertions(+) create mode 100644 tests/test_builder.py diff --git a/tests/test_builder.py b/tests/test_builder.py new file mode 100644 index 0000000..dc59a2f --- /dev/null +++ b/tests/test_builder.py @@ -0,0 +1,1114 @@ +############################################################################ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# 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 copy +import textwrap +from dataclasses import dataclass + +import numpy as np +import pytest +from openpulse.printer import dumps + +from oqpy import * +from oqpy.base import expr_matches +from oqpy.quantum_types import PhysicalQubits +from oqpy.timing import OQDurationLiteral + + +def test_version_string(): + prog = Program(version="2.9") + + with pytest.raises(RuntimeError): + prog = Program("2.x") + + expected = textwrap.dedent( + """ + OPENQASM 2.9; + """ + ).strip() + + assert Program(oqasm_text=expected).to_qasm() == expected + #assert prog == Program(oqasm_text=prog.to_qasm()) + +def test_variable_declaration(): + b = BoolVar(True, "b") + i = IntVar(-4, "i") + u = UintVar(5, "u") + x = DurationVar(100e-9, "blah") + y = FloatVar[50](3.3, "y") + ang = AngleVar(name="ang") + arr = BitVar[20](name="arr") + c = BitVar(name="c") + vars = [b, i, u, x, y, ang, arr, c] + + prog = Program(version=None) + prog.declare(vars) + prog.set(arr[1], 0) + + with pytest.raises(IndexError): + prog.set(arr[40], 2) + with pytest.raises(ValueError): + BitVar[2.1](name="d") + with pytest.raises(ValueError): + BitVar[0](name="d") + with pytest.raises(ValueError): + BitVar[-1](name="d") + with pytest.raises(IndexError): + prog.set(arr[1.3], 0) + with pytest.raises(TypeError): + prog.set(c[0], 1) + + expected = textwrap.dedent( + """ + bool b = true; + int[32] i = -4; + uint[32] u = 5; + duration blah = 100.0ns; + float[50] y = 3.3; + angle[32] ang; + bit[20] arr; + bit c; + arr[1] = 0; + """ + ).strip() + + assert isinstance(arr[14], BitVar) + assert Program(oqasm_text=expected).to_qasm() == expected + #assert prog == Program(oqasm_text=prog.to_qasm()) + + +def test_complex_numbers_declaration(): + vars = [ + ComplexVar(name="z"), + ComplexVar(1 + 0j, name="z1"), + ComplexVar(-1 + 0j, name="z2"), + ComplexVar(0 + 2j, name="z3"), + ComplexVar(0 - 2j, name="z4"), + ComplexVar(1 + 2j, name="z5"), + ComplexVar(1 - 2j, name="z6"), + ComplexVar(-1 + 2j, name="z7"), + ComplexVar(-1 - 2j, name="z8"), + ComplexVar(1, name="z9"), + ComplexVar(-1, name="z10"), + ComplexVar(2j, name="z11"), + ComplexVar(-2j, name="z12"), + ComplexVar[float32](1.2 - 2.1j, name="z_with_type1"), + ComplexVar[float_(16)](1.2 - 2.1j, name="z_with_type2"), + ComplexVar(1.2 - 2.1j, base_type=float_(16), name="z_with_type3"), + ] + with pytest.raises(AssertionError): + ComplexVar(-2j, base_type=IntVar, name="z12") + + prog = Program(version=None) + prog.declare(vars) + + expected = textwrap.dedent( + """ + complex[float[64]] z; + complex[float[64]] z1 = 1.0; + complex[float[64]] z2 = -1.0; + complex[float[64]] z3 = 2.0im; + complex[float[64]] z4 = -2.0im; + complex[float[64]] z5 = 1.0 + 2.0im; + complex[float[64]] z6 = 1.0 - 2.0im; + complex[float[64]] z7 = -1.0 + 2.0im; + complex[float[64]] z8 = -1.0 - 2.0im; + complex[float[64]] z9 = 1.0; + complex[float[64]] z10 = -1.0; + complex[float[64]] z11 = 2.0im; + complex[float[64]] z12 = -2.0im; + complex[float[32]] z_with_type1 = 1.2 - 2.1im; + complex[float[16]] z_with_type2 = 1.2 - 2.1im; + complex[float[16]] z_with_type3 = 1.2 - 2.1im; + """ + ).strip() + + assert Program(oqasm_text=expected).to_qasm() == expected + #assert prog == Program(oqasm_text=prog.to_qasm()) + + + +def test_non_trivial_variable_declaration(): + prog = Program() + z1 = ComplexVar(5, "z1") + z2 = ComplexVar(2 * z1, "z2") + z3 = ComplexVar(z2 + 2j, "z3") + vars = [z1, z2, z3] + prog.declare(vars) + + expected = textwrap.dedent( + """ + OPENQASM 3.0; + complex[float[64]] z1 = 5.0; + complex[float[64]] z2 = 2 * z1; + complex[float[64]] z3 = z2 + 2.0im; + """ + ).strip() + + assert Program(oqasm_text=expected).to_qasm() == expected + #assert prog == Program(oqasm_text=prog.to_qasm()) + + + +def test_variable_assignment(): + prog = Program() + i = IntVar(5, name="i") + prog.set(i, 8) + prog.set(i.to_ast(prog), 1) + prog.increment(i, 3) + prog.mod_equals(i, 2) + + with pytest.raises(TypeError): + prog.set(i, None) + + expected = textwrap.dedent( + """ + OPENQASM 3.0; + int[32] i = 5; + i = 8; + i = 1; + i += 3; + i %= 2; + """ + ).strip() + + assert Program(oqasm_text=expected).to_qasm() == expected + #assert prog == Program(oqasm_text=prog.to_qasm()) + + +def test_binary_expressions(): + prog = Program() + i = IntVar(5, "i") + j = IntVar(2, "j") + prog.set(i, 2 * (i + j)) + prog.set(j, 2 % (2 + i) % 2) + + expected = textwrap.dedent( + """ + OPENQASM 3.0; + int[32] i = 5; + int[32] j = 2; + i = 2 * (i + j); + j = 2 % (2 + i) % 2; + """ + ).strip() + + assert Program(oqasm_text=expected).to_qasm() == expected + #assert prog == Program(oqasm_text=prog.to_qasm()) + + +def test_measure_reset(): + prog = Program() + q = PhysicalQubits[0] + c = BitVar(name="c") + prog.reset(q) + prog.measure(q, c) + prog.measure(q) + + expected = textwrap.dedent( + """ + OPENQASM 3.0; + bit c; + reset $0; + c = measure $0; + measure $0; + """ + ).strip() + + assert Program(oqasm_text=expected).to_qasm() == expected + #assert prog == Program(oqasm_text=prog.to_qasm()) + + +def test_bare_if(): + prog = Program() + i = IntVar(3, "i") + with If(prog, i <= 0): + prog.increment(i, 1) + with If(prog, i != 0): + prog.set(i, 0) + with pytest.raises(RuntimeError): + with If(prog, i < 0 or i == 0): + prog.increment(i, 1) + + expected = textwrap.dedent( + """ + OPENQASM 3.0; + int[32] i = 3; + if (i <= 0) { + i += 1; + } + if (i != 0) { + i = 0; + } + """ + ).strip() + + assert Program(oqasm_text=expected).to_qasm() == expected + #assert prog == Program(oqasm_text=prog.to_qasm()) + + +def test_if_else(): + prog = Program() + i = IntVar(3, "i") + j = IntVar(2, "j") + with If(prog, i >= 0): + with If(prog, j == 0): + prog.increment(i, 1) + with Else(prog): + prog.decrement(i, 1) + with Else(prog): + prog.decrement(i, 1) + + with pytest.raises(RuntimeError): + with Else(prog): + prog.decrement(i, 1) + + expected = textwrap.dedent( + """ + OPENQASM 3.0; + int[32] i = 3; + int[32] j = 2; + if (i >= 0) { + if (j == 0) { + i += 1; + } else { + i -= 1; + } + } else { + i -= 1; + } + """ + ).strip() + + assert Program(oqasm_text=expected).to_qasm() == expected + #assert prog == Program(oqasm_text=prog.to_qasm()) + + +def test_for_in(): + prog = Program() + j = IntVar(0, "j") + wf = WaveformVar([0.1, -1.2, 1.3, 2.4], name="wf") + with ForIn(prog, range(5), "i") as i: + prog.increment(j, i) + with ForIn(prog, [-1, 1, -1, 1], "k") as k: + prog.decrement(j, k) + with ForIn(prog, np.array([0, 3]), "l") as l: + prog.set(j, l) + with ForIn(prog, wf, "m") as m: + prog.set(j, m) + + expected = textwrap.dedent( + """ + OPENQASM 3.0; + int[32] j = 0; + cal { + waveform wf = {0.1, -1.2, 1.3, 2.4}; + } + for int i in [0:4] { + j += i; + } + for int k in {-1, 1, -1, 1} { + j -= k; + } + for int l in {0, 3} { + j = l; + } + for int m in wf { + j = m; + } + """ + ).strip() + + assert Program(oqasm_text=expected).to_qasm() == expected + #assert prog == Program(oqasm_text=prog.to_qasm()) + + +def test_while(): + prog = Program() + j = IntVar(0, "j") + with While(prog, j < 5): + prog.increment(j, 1) + with While(prog, j > 0): + prog.decrement(j, 1) + + expected = textwrap.dedent( + """ + OPENQASM 3.0; + int[32] j = 0; + while (j < 5) { + j += 1; + } + while (j > 0) { + j -= 1; + } + """ + ).strip() + + assert Program(oqasm_text=expected).to_qasm() == expected + #assert prog == Program(oqasm_text=prog.to_qasm()) + + +def test_create_frame(): + prog = Program() + port = PortVar("storage") + storage_frame = FrameVar(port, 6e9, name="storage_frame") + readout_frame = FrameVar(name="readout_frame") + prog.declare([storage_frame, readout_frame]) + + with pytest.raises(ValueError): + frame = FrameVar(port, name="storage_frame") + + expected = textwrap.dedent( + """ + OPENQASM 3.0; + cal { + port storage; + frame storage_frame = newframe(storage, 6000000000.0, 0); + frame readout_frame; + } + """ + ).strip() + + assert Program(oqasm_text=expected).to_qasm() == expected + #assert prog == Program(oqasm_text=prog.to_qasm()) + + +def test_subroutine_with_return(): + prog = Program() + + @subroutine + def multiply(prog: Program, x: IntVar, y: IntVar) -> IntVar: + return x * y + + y = IntVar(2, "y") + prog.set(y, multiply(prog, y, 3)) + + @subroutine + def declare(prog: Program, x: IntVar): + prog.declare([x]) + + declare(prog, y) + + @subroutine + def delay50ns(prog: Program, q: Qubit) -> None: + prog.delay(50e-9, q) + + q = PhysicalQubits[0] + delay50ns(prog, q) + + with pytest.raises(ValueError): + + @subroutine + def return1(prog: Program) -> float: + return 1.0 + + return1(prog) + + with pytest.raises(ValueError): + + @subroutine + def add(prog: Program, x: IntVar, y) -> IntVar: + return x + y + + prog.set(y, add(prog, y, 3)) + + expected = textwrap.dedent( + """ + OPENQASM 3.0; + def multiply(int[32] x, int[32] y) -> int[32] { + return x * y; + } + int[32] y = 2; + y = multiply(y, 3); + """ + ).strip() + + assert Program(oqasm_text=expected).to_qasm() == expected + #assert prog == Program(oqasm_text=prog.to_qasm()) + + +def test_box_and_timings(): + constant = declare_waveform_generator("constant", [("length", duration), ("iq", complex128)]) + + port = PortVar("portname") + frame = FrameVar(port, 1e9, name="framename") + prog = Program() + with Box(prog, 500e-9): + prog.play(frame, constant(100e-9, 0.5)) + prog.delay(200e-7, frame) + prog.play(frame, constant(100e-9, 0.5)) + + with Box(prog): + prog.play(frame, constant(200e-9, 0.5)) + + with pytest.raises(TypeError): + f = FloatVar(200e-9, "f", needs_declaration=False) + make_duration(f.to_ast(prog)) + + expected = textwrap.dedent( + """ + OPENQASM 3.0; + cal { + extern constant(duration, complex[float[64]]) -> waveform; + port portname; + frame framename = newframe(portname, 1000000000.0, 0); + } + box[500.0ns] { + play(framename, constant(100.0ns, 0.5)); + delay[2e-05] framename; + play(framename, constant(100.0ns, 0.5)); + } + box { + play(framename, constant(200.0ns, 0.5)); + } + """ + ).strip() + + # FIXME It should not be with include_externs = False + assert Program(oqasm_text=expected).to_qasm(include_externs=False) == expected + #assert prog == Program(oqasm_text=prog.to_qasm()) + + +def test_play_capture(): + port = PortVar("portname") + frame = FrameVar(port, 1e9, name="framename") + prog = Program() + constant = declare_waveform_generator("constant", [("length", duration), ("iq", complex128)]) + + prog.play(frame, constant(1e-6, 0.5)) + kernel = WaveformVar(constant(1e-6, iq=1), "kernel") + prog.capture(frame, kernel) + + expected = textwrap.dedent( + """ + OPENQASM 3.0; + cal { + extern constant(duration, complex[float[64]]) -> waveform; + port portname; + frame framename = newframe(portname, 1000000000.0, 0); + waveform kernel = constant(1000.0ns, 1); + } + play(framename, constant(1000.0ns, 0.5)); + capture(framename, kernel); + """ + ).strip() + + # FIXME It should not be with include_externs = False + assert Program(oqasm_text=expected).to_qasm(include_externs=False) == expected + #assert prog == Program(oqasm_text=prog.to_qasm()) + + +def test_set_shift_frequency(): + port = PortVar("portname") + frame = FrameVar(port, 1e9, name="framename") + prog = Program() + + prog.set_frequency(frame, 1.1e9) + prog.shift_frequency(frame, 0.2e9) + + expected = textwrap.dedent( + """ + OPENQASM 3.0; + cal { + port portname; + frame framename = newframe(portname, 1000000000.0, 0); + } + set_frequency(framename, 1100000000.0); + shift_frequency(framename, 200000000.0); + """ + ).strip() + + assert Program(oqasm_text=expected).to_qasm(include_externs=False) == expected + #assert prog == Program(oqasm_text=prog.to_qasm()) + + +def test_ramsey_example(): + prog = Program() + constant = declare_waveform_generator("constant", [("length", duration), ("iq", complex128)]) + gaussian = declare_waveform_generator( + "gaussian", + [("length", duration), ("sigma", duration), ("amplitude", float64), ("phase", float64)], + ) + tx_waveform = constant(2.4e-6, 0.2) + + q_port = PortVar("q_port") + rx_port = PortVar("rx_port") + tx_port = PortVar("tx_port") + ports = [q_port, rx_port, tx_port] + + q_frame = FrameVar(q_port, 6.431e9, name="q_frame") + rx_frame = FrameVar(rx_port, 5.752e9, name="rx_frame") + tx_frame = FrameVar(tx_port, 5.752e9, name="tx_frame") + frames = [q_frame, rx_frame, tx_frame] + + with Cal(prog): + prog.declare(ports) + prog.declare(frames) + + q2 = PhysicalQubits[2] + + with defcal(prog, q2, "readout"): + prog.play(tx_frame, tx_waveform) + prog.capture(rx_frame, constant(2.4e-6, 1)) + + with defcal(prog, q2, "x90"): + prog.play(q_frame, gaussian(32e-9, 8e-9, 0.2063, 0.0)) + + ramsey_delay = DurationVar(12e-6, "ramsey_delay") + tppi_angle = AngleVar(0, "tppi_angle") + + with Cal(prog): + with ForIn(prog, range(1001), "shot") as shot: + prog.declare(ramsey_delay) + prog.declare(tppi_angle) + with ForIn(prog, range(81), "delay_increment") as delay_increment: + ( + prog.delay(100e-6) + .set_phase(q_frame, 0) + .set_phase(rx_frame, 0) + .set_phase(tx_frame, 0) + .gate(q2, "x90") + .delay(ramsey_delay) + .shift_phase(q_frame, tppi_angle) + .gate(q2, "x90") + .gate(q2, "readout") + .increment(tppi_angle, 20e-9 * 5e6 * 2 * np.pi) + .increment(ramsey_delay, 20e-9) + ) + + expected = textwrap.dedent( + """ + OPENQASM 3.0; + cal { + extern constant(duration, complex[float[64]]) -> waveform; + extern gaussian(duration, duration, float[64], float[64]) -> waveform; + port q_port; + port rx_port; + port tx_port; + frame q_frame = newframe(q_port, 6431000000.0, 0); + frame rx_frame = newframe(rx_port, 5752000000.0, 0); + frame tx_frame = newframe(tx_port, 5752000000.0, 0); + } + defcal readout $2 { + play(tx_frame, constant(2400.0ns, 0.2)); + capture(rx_frame, constant(2400.0ns, 1)); + } + defcal x90 $2 { + play(q_frame, gaussian(32.0ns, 8.0ns, 0.2063, 0.0)); + } + cal { + for int shot in [0:1000] { + duration ramsey_delay = 12000.0ns; + angle[32] tppi_angle = 0; + for int delay_increment in [0:80] { + delay[100000.0ns]; + set_phase(q_frame, 0); + set_phase(rx_frame, 0); + set_phase(tx_frame, 0); + x90 $2; + delay[ramsey_delay]; + shift_phase(q_frame, tppi_angle); + x90 $2; + readout $2; + tppi_angle += 0.6283185307179586; + ramsey_delay += 20.0ns; + } + } + } + """ + ).strip() + + expect_defcal_x90_q2 = textwrap.dedent( + """ + defcal x90 $2 { + play(q_frame, gaussian(32.0ns, 8.0ns, 0.2063, 0.0)); + } + """ + ).strip() + + expect_defcal_readout_q2 = textwrap.dedent( + """ + defcal readout $2 { + play(tx_frame, constant(2400.0ns, 0.2)); + capture(rx_frame, constant(2400.0ns, 1)); + } + """ + ).strip() + + assert Program(oqasm_text=expected).to_qasm(include_externs=False) == expected + #assert prog == Program(oqasm_text=prog.to_qasm()) + # assert dumps(prog.defcals[("$2", "x90")], indent=" ").strip() == expect_defcal_x90_q2 + # assert dumps(prog.defcals[("$2", "readout")], indent=" ").strip() == expect_defcal_readout_q2 + + +def test_rabi_example(): + prog = Program() + constant = declare_waveform_generator("constant", [("length", duration), ("iq", complex128)]) + gaussian = declare_waveform_generator( + "gaussian", + [("length", duration), ("sigma", duration), ("amplitude", float64), ("phase", float64)], + ) + + zcu216_dac231_0 = PortVar("zcu216_dac231_0") + zcu216_dac230_0 = PortVar("zcu216_dac230_0") + zcu216_adc225_0 = PortVar("zcu216_adc225_0") + q0_transmon_xy_frame = FrameVar(zcu216_dac231_0, 3911851971.26885, name="q0_transmon_xy_frame") + q0_readout_tx_frame = FrameVar(zcu216_dac230_0, 3571600000, name="q0_readout_tx_frame") + q0_readout_rx_frame = FrameVar(zcu216_adc225_0, 3571600000, name="q0_readout_rx_frame") + frames = [q0_transmon_xy_frame, q0_readout_tx_frame, q0_readout_rx_frame] + rabi_pulse_wf = WaveformVar(gaussian(5.2e-8, 1.3e-8, 1.0, 0.0), "rabi_pulse_wf") + readout_waveform_wf = WaveformVar(constant(1.6e-6, 0.02), "readout_waveform_wf") + readout_kernel_wf = WaveformVar(constant(1.6e-6, 1), "readout_kernel_wf") + with ForIn(prog, range(1, 1001), "shot") as shot: + prog.set_scale(q0_transmon_xy_frame, -0.2) + with ForIn(prog, range(1, 102), "amplitude") as amplitude: + prog.delay(200e-6, frames) + for frame in frames: + prog.set_phase(frame, 0) + ( + prog.play(q0_transmon_xy_frame, rabi_pulse_wf) + .barrier(frames) + .play(q0_readout_tx_frame, readout_waveform_wf) + .capture(q0_readout_rx_frame, readout_kernel_wf) + .barrier(frames) + .shift_scale(q0_transmon_xy_frame, 0.4 / 100) + ) + + expected = textwrap.dedent( + """ + OPENQASM 3.0; + cal { + port zcu216_adc225_0; + port zcu216_dac230_0; + port zcu216_dac231_0; + frame q0_transmon_xy_frame = newframe(zcu216_dac231_0, 3911851971.26885, 0); + frame q0_readout_tx_frame = newframe(zcu216_dac230_0, 3571600000, 0); + frame q0_readout_rx_frame = newframe(zcu216_adc225_0, 3571600000, 0); + waveform rabi_pulse_wf = gaussian(52.0ns, 13.0ns, 1.0, 0.0); + waveform readout_waveform_wf = constant(1600.0ns, 0.02); + waveform readout_kernel_wf = constant(1600.0ns, 1); + for int shot in [1:1000] { + set_scale(q0_transmon_xy_frame, -0.2); + for int amplitude in [1:101] { + delay[200000.0ns] q0_transmon_xy_frame, q0_readout_tx_frame, q0_readout_rx_frame; + set_phase(q0_transmon_xy_frame, 0); + set_phase(q0_readout_tx_frame, 0); + set_phase(q0_readout_rx_frame, 0); + play(q0_transmon_xy_frame, rabi_pulse_wf); + barrier q0_transmon_xy_frame, q0_readout_tx_frame, q0_readout_rx_frame; + play(q0_readout_tx_frame, readout_waveform_wf); + capture(q0_readout_rx_frame, readout_kernel_wf); + barrier q0_transmon_xy_frame, q0_readout_tx_frame, q0_readout_rx_frame; + shift_scale(q0_transmon_xy_frame, 0.004); + } + } + } + """ + ).strip() + + assert Program(oqasm_text=expected).to_qasm() == expected + #assert prog == Program(oqasm_text=prog.to_qasm()) + + +def test_program_add(): + prog1 = Program() + constant = declare_waveform_generator("constant", [("length", duration), ("iq", complex128)]) + + prog1.delay(1e-6) + + prog2 = Program() + q1 = PhysicalQubits[1] + port = PortVar("p1") + frame = FrameVar(port, 5e9, name="f1") + wf = WaveformVar(constant(100e-9, 0.5), "wf") + with defcal(prog2, q1, "x180"): + prog2.play(frame, wf) + prog2.gate(q1, "x180") + i = IntVar(5, "i") + prog2.declare(i) + + expected = textwrap.dedent( + """ + OPENQASM 3.0; + cal { + extern constant(duration, complex[float[64]]) -> waveform; + port p1; + frame f1 = newframe(p1, 5000000000.0, 0); + waveform wf = constant(100.0ns, 0.5); + } + delay[1000.0ns]; + defcal x180 $1 { + play(f1, wf); + } + x180 $1; + int[32] i = 5; + """ + ).strip() + + prog = prog1 + prog2 + assert Program(oqasm_text=expected).to_qasm(include_externs=False) == expected + #assert prog == Program(oqasm_text=prog.to_qasm()) + + with pytest.raises(RuntimeError): + with If(prog2, i == 0): + prog = prog1 + prog2 + + +def test_expression_convertible(): + @dataclass + class A: + name: str + + def _to_oqpy_expression(self): + return DurationVar(1e-7, self.name) + + frame = FrameVar(name="f1") + prog = Program() + prog.set(A("a1"), 2) + prog.delay(A("a2"), frame) + expected = textwrap.dedent( + """ + OPENQASM 3.0; + duration a1 = 100.0ns; + duration a2 = 100.0ns; + frame f1; + a1 = 2; + delay[a2] f1; + """ + ).strip() + assert Program(oqasm_text=expected).to_qasm() == expected + #assert prog == Program(oqasm_text=prog.to_qasm()) + +def test_waveform_extern_arg_passing(): + prog = Program() + constant = declare_waveform_generator("constant", [("length", duration), ("iq", complex128)]) + port = PortVar("p1") + frame = FrameVar(port, 5e9, name="f1") + prog.play(frame, constant(10e-9, 0.1)) + prog.play(frame, constant(20e-9, iq=0.2)) + prog.play(frame, constant(length=40e-9, iq=0.4)) + prog.play(frame, constant(iq=0.5, length=50e-9)) + with pytest.raises(TypeError): + prog.play(frame, constant(10e-9, length=10e-9)) + with pytest.raises(TypeError): + prog.play(frame, constant(10e-9, blah=10e-9)) + with pytest.raises(TypeError): + prog.play(frame, constant(10e-9, 0.1, 0.1)) + + expected = textwrap.dedent( + """ + OPENQASM 3.0; + cal { + extern constant(duration, complex[float[64]]) -> waveform; + port p1; + frame f1 = newframe(p1, 5000000000.0, 0); + } + play(f1, constant(10.0ns, 0.1)); + play(f1, constant(20.0ns, 0.2)); + play(f1, constant(40.0ns, 0.4)); + play(f1, constant(50.0ns, 0.5)); + """ + ).strip() + + assert Program(oqasm_text=expected).to_qasm(include_externs=False) == expected + #assert prog == Program(oqasm_text=prog.to_qasm()) + + +def test_needs_declaration(): + prog = Program() + i1 = IntVar(1, name="i1") + i2 = IntVar(name="i2", needs_declaration=False) + p1 = PortVar("p1") + p2 = PortVar("p2", needs_declaration=False) + f1 = FrameVar(p1, 5e9, name="f1") + f2 = FrameVar(p2, 5e9, name="f2", needs_declaration=False) + q1 = Qubit("q1") + q2 = Qubit("q2", needs_declaration=False) + prog.increment(i1, 1) + prog.increment(i2, 1) + prog.set_phase(f1, 0) + prog.set_phase(f2, 0) + prog.gate(q1, "X") + prog.gate(q2, "X") + + expected = textwrap.dedent( + """ + OPENQASM 3.0; + cal { + port p1; + } + int[32] i1 = 1; + cal { + frame f1 = newframe(p1, 5000000000.0, 0); + } + qubit q1; + i1 += 1; + i2 += 1; + set_phase(f1, 0); + set_phase(f2, 0); + X q1; + X q2; + """ + ).strip() + + assert Program(oqasm_text=expected).to_qasm() == expected + #assert prog == Program(oqasm_text=prog.to_qasm()) + + +def test_discrete_waveform(): + port = PortVar("port") + frame = FrameVar(port, 5e9, name="frame") + wfm_float = WaveformVar([-1.2, 1.5, 0.1, 0], name="wfm_float") + wfm_int = WaveformVar((1, 0, 4, -1), name="wfm_int") + wfm_complex = WaveformVar( + np.array([1 + 2j, -1.2j + 3.2, -2.1j, complex(1, 0)]), name="wfm_complex" + ) + wfm_notype = WaveformVar([0.0, -1j + 0, 1.2 + 0j, -1], name="wfm_notype") + + prog = Program() + prog.declare([wfm_float, wfm_int, wfm_complex, wfm_notype]) + prog.play(frame, wfm_complex) + prog.play(frame, [1] * 2 + [0] * 2) + + expected = textwrap.dedent( + """ + OPENQASM 3.0; + cal { + port port; + frame frame = newframe(port, 5000000000.0, 0); + waveform wfm_float = {-1.2, 1.5, 0.1, 0}; + waveform wfm_int = {1, 0, 4, -1}; + waveform wfm_complex = {1.0 + 2.0im, 3.2 - 1.2im, -2.1im, 1.0}; + waveform wfm_notype = {0.0, -1.0im, 1.2, -1}; + } + play(frame, wfm_complex); + play(frame, {1, 1, 0, 0}); + """ + ).strip() + + assert Program(oqasm_text=expected).to_qasm() == expected + #assert prog == Program(oqasm_text=prog.to_qasm()) + + +def test_var_and_expr_matches(): + p1 = PortVar("p1") + p2 = PortVar("p2") + f1 = FrameVar(p1, 5e9, name="f1") + assert f1._var_matches(f1) + assert f1._var_matches(copy.deepcopy(f1)) + + assert expr_matches(f1, f1) + assert not expr_matches(f1, p1) + assert not expr_matches(f1, FrameVar(p1, 4e9, name="frame")) + assert not expr_matches(f1, FrameVar(p2, 5e9, name="frame")) + assert not expr_matches(BitVar[2]([1, 2], name="a"), BitVar[2]([1], name="a")) + + prog = Program() + prog.declare(p1) + assert expr_matches(prog.declared_vars, {"p1": p1}) + assert not expr_matches(prog.declared_vars, {"p2": p1}) + + +def test_program_tracks_frame_waveform_vars(): + prog = Program() + + p1 = PortVar("p1") + p2 = PortVar("p2") + p3 = PortVar("p3") + ports = [p1, p2, p3] + + f1 = FrameVar(p1, 6.431e9, name="f1") + f2 = FrameVar(p2, 5.752e9, name="f2") + + constant = declare_waveform_generator("constant", [("length", duration), ("iq", complex128)]) + constant_wf = WaveformVar(constant(1.6e-6, 0.02), "constant_wf") + + # No FrameVar or WaveformVar used in the program yet + assert expr_matches(list(prog.frame_vars), []) + assert expr_matches(list(prog.waveform_vars), []) + + with Cal(prog): + prog.declare(ports) + # add declared vars for FrameVar and WaveformVar + prog.declare(f1) + prog.declare(constant_wf) + + q1 = PhysicalQubits[1] + + with defcal(prog, q1, "readout"): + # use undeclared FrameVar and WaveformVar + f3 = FrameVar(p3, 5.752e9, name="f3") + discrete_wf = WaveformVar([-1.2, 1.5, 0.1, 0], name="discrete_wf") + prog.play(f3, discrete_wf) + # in-line waveforms will not be tracked by the program + prog.capture(f2, constant(2.4e-6, 1)) + + assert expr_matches(list(prog.frame_vars), [f1, f3, f2]) + assert expr_matches(list(prog.waveform_vars), [constant_wf, discrete_wf]) + + +def test_make_duration(): + assert expr_matches(make_duration(1e-3), OQDurationLiteral(1e-3)) + assert expr_matches(make_duration(OQDurationLiteral(1e-4)), OQDurationLiteral(1e-4)) + + class MyExprConvertible: + def _to_oqpy_expression(self): + return OQDurationLiteral(1e-5) + + assert expr_matches(make_duration(MyExprConvertible()), OQDurationLiteral(1e-5)) + + class MyToAst: + def to_ast(self): + return OQDurationLiteral(1e-6) + + obj = MyToAst() + assert make_duration(obj) is obj + + with pytest.raises(TypeError): + make_duration("asdf") + + +def test_autoencal(): + port = PortVar("portname") + frame = FrameVar(port, 1e9, name="framename") + prog = Program() + constant = declare_waveform_generator("constant", [("length", duration), ("iq", complex128)]) + i = IntVar(0, "i") + + prog.increment(i, 1) + with Cal(prog): + prog.play(frame, constant(1e-6, 0.5)) + kernel = WaveformVar(constant(1e-6, iq=1), "kernel") + prog.capture(frame, kernel) + + expected = textwrap.dedent( + """ + OPENQASM 3.0; + defcalgrammar "openpulse"; + cal { + extern constant(duration, complex[float[64]]) -> waveform; + port portname; + frame framename = newframe(portname, 1000000000.0, 0); + waveform kernel = constant(1000.0ns, 1); + } + int[32] i = 0; + i += 1; + cal { + play(framename, constant(1000.0ns, 0.5)); + capture(framename, kernel); + } + """ + ).strip() + + assert Program(oqasm_text=expected).to_qasm(encal_declarations=True) == expected + #assert prog == Program(oqasm_text=prog.to_qasm()) + + +def test_ramsey_example_blog(): + import oqpy + + ramsey_prog = oqpy.Program() # create a new oqpy program + qubit = oqpy.PhysicalQubits[1] # get physical qubit 1 + delay_time = oqpy.DurationVar(0, "delay_time") # initialize a duration + + # Loop over shots (i.e. repetitions) + with oqpy.ForIn(ramsey_prog, range(100), "shot_index"): + ramsey_prog.set(delay_time, 0) # reset delay time to zero + # Loop over delays + with oqpy.ForIn(ramsey_prog, range(101), "delay_index"): + ( + ramsey_prog.reset(qubit) # prepare in ground state + .gate(qubit, "x90") # pi/2 pulse + .delay(delay_time, qubit) # variable delay + .gate(qubit, "x90") # pi/2 pulse + .measure(qubit) # final measurement + .increment(delay_time, 100e-9) + ) # increase delay by 100 ns + + defcals_prog = oqpy.Program() # create a new oqpy program + qubit = oqpy.PhysicalQubits[1] # get physical qubit 1 + + # Declare frames: transmon driving frame and readout receive/transmit frames + xy_frame = oqpy.FrameVar(oqpy.PortVar("dac0"), 6.431e9, name="xy_frame") + rx_frame = oqpy.FrameVar(oqpy.PortVar("adc0"), 5.752e9, name="rx_frame") + tx_frame = oqpy.FrameVar(oqpy.PortVar("dac1"), 5.752e9, name="tx_frame") + + # Declare the type of waveform we are working with. + # It is up to the backend receiving the openqasm to specify + # what waveforms are allowed. The waveform names and argument types + # will therefore need to coordinate with the backend. + constant_waveform = oqpy.declare_waveform_generator( + "constant", + [("length", oqpy.duration), ("amplitude", oqpy.float64)], + ) + gaussian_waveform = oqpy.declare_waveform_generator( + "gaussian", + [("length", oqpy.duration), ("sigma", oqpy.duration), ("amplitude", oqpy.float64)], + ) + + with oqpy.defcal(defcals_prog, qubit, "reset"): + defcals_prog.delay(1e-3) # reset to ground state by waiting 1 millisecond + + with oqpy.defcal(defcals_prog, qubit, "measure"): + defcals_prog.play(tx_frame, constant_waveform(2.4e-6, 0.2)) + defcals_prog.capture(rx_frame, constant_waveform(2.4e-6, 1)) + + with oqpy.defcal(defcals_prog, qubit, "x90"): + defcals_prog.play(xy_frame, gaussian_waveform(32e-9, 8e-9, 0.2063)) + + full_prog = defcals_prog + ramsey_prog + + expected = textwrap.dedent( + """ + OPENQASM 3.0; + defcalgrammar "openpulse"; + cal { + extern constant(duration, float[64]) -> waveform; + extern gaussian(duration, duration, float[64]) -> waveform; + port dac1; + port adc0; + port dac0; + frame tx_frame = newframe(dac1, 5752000000.0, 0); + frame rx_frame = newframe(adc0, 5752000000.0, 0); + frame xy_frame = newframe(dac0, 6431000000.0, 0); + } + duration delay_time = 0.0ns; + defcal reset $1 { + delay[1000000.0ns]; + } + defcal measure $1 { + play(tx_frame, constant(2400.0ns, 0.2)); + capture(rx_frame, constant(2400.0ns, 1)); + } + defcal x90 $1 { + play(xy_frame, gaussian(32.0ns, 8.0ns, 0.2063)); + } + for int shot_index in [0:99] { + delay_time = 0.0ns; + for int delay_index in [0:100] { + reset $1; + x90 $1; + delay[delay_time] $1; + x90 $1; + measure $1; + delay_time += 100.0ns; + } + } + """ + ).strip() + + assert Program(oqasm_text=expected).to_qasm(encal_declarations=True, include_externs=False) == expected + #assert prog == Program(oqasm_text=prog.to_qasm()) From c6ec941c481a23c1e04af59d05d41a0a15bdb91f Mon Sep 17 00:00:00 2001 From: Jean-Christophe Jaskula Date: Tue, 1 Nov 2022 21:03:44 -0400 Subject: [PATCH 04/18] Add extern to dict --- oqpy/program.py | 25 ++++++------- tests/test_builder.py | 84 +++++++++++++++++++++++++------------------ 2 files changed, 60 insertions(+), 49 deletions(-) diff --git a/oqpy/program.py b/oqpy/program.py index 6d57c1c..f41df6b 100644 --- a/oqpy/program.py +++ b/oqpy/program.py @@ -630,15 +630,24 @@ class ProgramBuilder(QASMVisitor[Program]): # Does not support {"dt": 4} TIME_UNIT_TO_EXP = {"ns": 3, "us": 2, "ms": 1, "s": 0} + def generic_visit(self, node: ast.QASMNode, context: Program = None): + if isinstance(node, ast.ExternDeclaration): + context.externs[node.name.name] = node + return + return super().generic_visit(node, context) + def visit_Program(self, node: ast.Program, context: Program) -> None: # TODO: need to check better node.version as for init context.version = node.version for statement in node.statements: + if isinstance(statement, ast.ExternDeclaration): + context.externs[statement.name.name] = statement + continue context._add_statement(statement) self.generic_visit(node, context) - def visit_ExternDeclaration(self, node: ast.ExternDeclaration, context: Program) -> None: - context.externs[node.name.name] = node + # def visit_ExternDeclaration(self, node: ast.ExternDeclaration, context: Program) -> None: + # context.externs[node.name.name] = node def visit_ClassicalDeclaration(self, node: ast.ClassicalDeclaration, context: Program) -> None: var: Var @@ -708,16 +717,12 @@ def visit_FunctionCall(self, node: ast.FunctionCall, context: Program | None = N # return getattr(self, func_name)(node, context) if node.name == "newframe": return node.arguments - else: - self.generic_visit(node, context) # def newframe(self, node: ast.FunctionCall, context: Program) -> None: # return [self.visit(arg, context) for arg in node.arguments] def visit_IntegerLiteral(self, node: ast.IntegerLiteral, context: Program | None = None) -> Any: """Visit Integer Literal. - node.value - 1 Args: node (ast.IntegerLiteral): The integer literal. context (_ParseState): The parse state context. @@ -728,8 +733,6 @@ def visit_ImaginaryLiteral( self, node: ast.ImaginaryLiteral, context: Program | None = None ) -> Any: """Visit Imaginary Number Literal. - node.value - 1.3im Args: node (ast.visit_ImaginaryLiteral): The imaginary number literal. context (_ParseState): The parse state context. @@ -738,8 +741,6 @@ def visit_ImaginaryLiteral( def visit_FloatLiteral(self, node: ast.FloatLiteral, context: Program | None = None) -> Any: """Visit Float Literal. - node.value - 1.1 Args: node (ast.FloatLiteral): The float literal. context (_ParseState): The parse state context. @@ -748,8 +749,6 @@ def visit_FloatLiteral(self, node: ast.FloatLiteral, context: Program | None = N def visit_BooleanLiteral(self, node: ast.BooleanLiteral, context: Program | None = None) -> Any: """Visit Boolean Literal. - node.value - true Args: node (ast.BooleanLiteral): The boolean literal. context (_ParseState): The parse state context. @@ -760,8 +759,6 @@ def visit_DurationLiteral( self, node: ast.DurationLiteral, context: Program | None = None ) -> Any: """Visit Duration Literal. - node.value, node.unit (node.unit.name, node.unit.value) - 1 Args: node (ast.DurationLiteral): The duration literal. context (_ParseState): The parse state context. diff --git a/tests/test_builder.py b/tests/test_builder.py index dc59a2f..b5223d2 100644 --- a/tests/test_builder.py +++ b/tests/test_builder.py @@ -21,6 +21,7 @@ import numpy as np import pytest from openpulse.printer import dumps +import openpulse.ast as ast from oqpy import * from oqpy.base import expr_matches @@ -41,7 +42,8 @@ def test_version_string(): ).strip() assert Program(oqasm_text=expected).to_qasm() == expected - #assert prog == Program(oqasm_text=prog.to_qasm()) + # assert prog == Program(oqasm_text=prog.to_qasm()) + def test_variable_declaration(): b = BoolVar(True, "b") @@ -87,7 +89,7 @@ def test_variable_declaration(): assert isinstance(arr[14], BitVar) assert Program(oqasm_text=expected).to_qasm() == expected - #assert prog == Program(oqasm_text=prog.to_qasm()) + # assert prog == Program(oqasm_text=prog.to_qasm()) def test_complex_numbers_declaration(): @@ -137,8 +139,7 @@ def test_complex_numbers_declaration(): ).strip() assert Program(oqasm_text=expected).to_qasm() == expected - #assert prog == Program(oqasm_text=prog.to_qasm()) - + # assert prog == Program(oqasm_text=prog.to_qasm()) def test_non_trivial_variable_declaration(): @@ -159,8 +160,7 @@ def test_non_trivial_variable_declaration(): ).strip() assert Program(oqasm_text=expected).to_qasm() == expected - #assert prog == Program(oqasm_text=prog.to_qasm()) - + # assert prog == Program(oqasm_text=prog.to_qasm()) def test_variable_assignment(): @@ -186,7 +186,7 @@ def test_variable_assignment(): ).strip() assert Program(oqasm_text=expected).to_qasm() == expected - #assert prog == Program(oqasm_text=prog.to_qasm()) + # assert prog == Program(oqasm_text=prog.to_qasm()) def test_binary_expressions(): @@ -207,7 +207,7 @@ def test_binary_expressions(): ).strip() assert Program(oqasm_text=expected).to_qasm() == expected - #assert prog == Program(oqasm_text=prog.to_qasm()) + # assert prog == Program(oqasm_text=prog.to_qasm()) def test_measure_reset(): @@ -229,7 +229,7 @@ def test_measure_reset(): ).strip() assert Program(oqasm_text=expected).to_qasm() == expected - #assert prog == Program(oqasm_text=prog.to_qasm()) + # assert prog == Program(oqasm_text=prog.to_qasm()) def test_bare_if(): @@ -257,7 +257,7 @@ def test_bare_if(): ).strip() assert Program(oqasm_text=expected).to_qasm() == expected - #assert prog == Program(oqasm_text=prog.to_qasm()) + # assert prog == Program(oqasm_text=prog.to_qasm()) def test_if_else(): @@ -294,7 +294,7 @@ def test_if_else(): ).strip() assert Program(oqasm_text=expected).to_qasm() == expected - #assert prog == Program(oqasm_text=prog.to_qasm()) + # assert prog == Program(oqasm_text=prog.to_qasm()) def test_for_in(): @@ -333,7 +333,7 @@ def test_for_in(): ).strip() assert Program(oqasm_text=expected).to_qasm() == expected - #assert prog == Program(oqasm_text=prog.to_qasm()) + # assert prog == Program(oqasm_text=prog.to_qasm()) def test_while(): @@ -358,7 +358,7 @@ def test_while(): ).strip() assert Program(oqasm_text=expected).to_qasm() == expected - #assert prog == Program(oqasm_text=prog.to_qasm()) + # assert prog == Program(oqasm_text=prog.to_qasm()) def test_create_frame(): @@ -383,7 +383,7 @@ def test_create_frame(): ).strip() assert Program(oqasm_text=expected).to_qasm() == expected - #assert prog == Program(oqasm_text=prog.to_qasm()) + # assert prog == Program(oqasm_text=prog.to_qasm()) def test_subroutine_with_return(): @@ -437,7 +437,7 @@ def multiply(int[32] x, int[32] y) -> int[32] { ).strip() assert Program(oqasm_text=expected).to_qasm() == expected - #assert prog == Program(oqasm_text=prog.to_qasm()) + # assert prog == Program(oqasm_text=prog.to_qasm()) def test_box_and_timings(): @@ -477,9 +477,22 @@ def test_box_and_timings(): """ ).strip() + prog_from_text = Program(oqasm_text=expected) # FIXME It should not be with include_externs = False - assert Program(oqasm_text=expected).to_qasm(include_externs=False) == expected - #assert prog == Program(oqasm_text=prog.to_qasm()) + assert prog_from_text.to_qasm(include_externs=False) == expected + prog_from_text.externs["constant"] == ast.ExternDeclaration( + name=ast.Identifier(name="constant"), + arguments=[ + ast.ExternArgument(type=ast.DurationType(), access=None), + ast.ExternArgument( + type=ast.ComplexType( + base_type=ast.FloatType(size=ast.IntegerLiteral(value=64)), + ), + ), + ], + return_type=ast.WaveformType(), + ) + # assert prog == Program(oqasm_text=prog.to_qasm()) def test_play_capture(): @@ -507,8 +520,8 @@ def test_play_capture(): ).strip() # FIXME It should not be with include_externs = False - assert Program(oqasm_text=expected).to_qasm(include_externs=False) == expected - #assert prog == Program(oqasm_text=prog.to_qasm()) + assert Program(oqasm_text=expected).to_qasm() == expected + # assert prog == Program(oqasm_text=prog.to_qasm()) def test_set_shift_frequency(): @@ -531,8 +544,8 @@ def test_set_shift_frequency(): """ ).strip() - assert Program(oqasm_text=expected).to_qasm(include_externs=False) == expected - #assert prog == Program(oqasm_text=prog.to_qasm()) + assert Program(oqasm_text=expected).to_qasm() == expected + # assert prog == Program(oqasm_text=prog.to_qasm()) def test_ramsey_example(): @@ -648,8 +661,8 @@ def test_ramsey_example(): """ ).strip() - assert Program(oqasm_text=expected).to_qasm(include_externs=False) == expected - #assert prog == Program(oqasm_text=prog.to_qasm()) + assert Program(oqasm_text=expected).to_qasm() == expected + # assert prog == Program(oqasm_text=prog.to_qasm()) # assert dumps(prog.defcals[("$2", "x90")], indent=" ").strip() == expect_defcal_x90_q2 # assert dumps(prog.defcals[("$2", "readout")], indent=" ").strip() == expect_defcal_readout_q2 @@ -720,7 +733,7 @@ def test_rabi_example(): ).strip() assert Program(oqasm_text=expected).to_qasm() == expected - #assert prog == Program(oqasm_text=prog.to_qasm()) + # assert prog == Program(oqasm_text=prog.to_qasm()) def test_program_add(): @@ -759,8 +772,8 @@ def test_program_add(): ).strip() prog = prog1 + prog2 - assert Program(oqasm_text=expected).to_qasm(include_externs=False) == expected - #assert prog == Program(oqasm_text=prog.to_qasm()) + assert Program(oqasm_text=expected).to_qasm() == expected + # assert prog == Program(oqasm_text=prog.to_qasm()) with pytest.raises(RuntimeError): with If(prog2, i == 0): @@ -790,7 +803,8 @@ def _to_oqpy_expression(self): """ ).strip() assert Program(oqasm_text=expected).to_qasm() == expected - #assert prog == Program(oqasm_text=prog.to_qasm()) + # assert prog == Program(oqasm_text=prog.to_qasm()) + def test_waveform_extern_arg_passing(): prog = Program() @@ -823,8 +837,8 @@ def test_waveform_extern_arg_passing(): """ ).strip() - assert Program(oqasm_text=expected).to_qasm(include_externs=False) == expected - #assert prog == Program(oqasm_text=prog.to_qasm()) + assert Program(oqasm_text=expected).to_qasm() == expected + # assert prog == Program(oqasm_text=prog.to_qasm()) def test_needs_declaration(): @@ -865,7 +879,7 @@ def test_needs_declaration(): ).strip() assert Program(oqasm_text=expected).to_qasm() == expected - #assert prog == Program(oqasm_text=prog.to_qasm()) + # assert prog == Program(oqasm_text=prog.to_qasm()) def test_discrete_waveform(): @@ -900,7 +914,7 @@ def test_discrete_waveform(): ).strip() assert Program(oqasm_text=expected).to_qasm() == expected - #assert prog == Program(oqasm_text=prog.to_qasm()) + # assert prog == Program(oqasm_text=prog.to_qasm()) def test_var_and_expr_matches(): @@ -1013,8 +1027,8 @@ def test_autoencal(): """ ).strip() - assert Program(oqasm_text=expected).to_qasm(encal_declarations=True) == expected - #assert prog == Program(oqasm_text=prog.to_qasm()) + assert Program(oqasm_text=expected).to_qasm() == expected + # assert prog == Program(oqasm_text=prog.to_qasm()) def test_ramsey_example_blog(): @@ -1110,5 +1124,5 @@ def test_ramsey_example_blog(): """ ).strip() - assert Program(oqasm_text=expected).to_qasm(encal_declarations=True, include_externs=False) == expected - #assert prog == Program(oqasm_text=prog.to_qasm()) + assert Program(oqasm_text=expected).to_qasm() == expected + # assert prog == Program(oqasm_text=prog.to_qasm()) From c7e50cb9ffaae2ba2afd71bfa75b4aa0295bdcb7 Mon Sep 17 00:00:00 2001 From: Jean-Christophe Jaskula Date: Wed, 9 Nov 2022 17:48:24 -0500 Subject: [PATCH 05/18] Add variable import with from_qasm --- oqpy/program.py | 221 ++++++++++++++++++------------------------ tests/test_builder.py | 2 +- 2 files changed, 93 insertions(+), 130 deletions(-) diff --git a/oqpy/program.py b/oqpy/program.py index f41df6b..9827dd4 100644 --- a/oqpy/program.py +++ b/oqpy/program.py @@ -30,7 +30,7 @@ from openpulse import ast from openpulse.parser import parse from openpulse.printer import dumps -from openqasm3.visitor import QASMVisitor +from openqasm3.visitor import QASMTransformer, QASMVisitor from oqpy import classical_types, quantum_types from oqpy.base import ( @@ -316,19 +316,6 @@ def from_qasm(self, qasm_text: str) -> None: oqasm_ast = _remove_spans(parse(qasm_text)) ProgramBuilder().visit(oqasm_ast, self) - # self._state.finalize_if_clause() - # self._state.body.extend(other._state.body) - # self._state.if_clause = other._state.if_clause - # self._state.finalize_if_clause() - # self.defcals.update(other.defcals) Done - # self.subroutines.update(other.subroutines) - # self.externs.update(other.externs) - # for var in other.declared_vars.values(): - # self._mark_var_declared(var) - # for var in other.undeclared_vars.values(): - # self._add_var(var) - # return self - def to_qasm( self, encal: bool = False, @@ -626,147 +613,123 @@ def process_statement_list( return new_list -class ProgramBuilder(QASMVisitor[Program]): +class ProgramBuilder(QASMTransformer[Program]): # Does not support {"dt": 4} TIME_UNIT_TO_EXP = {"ns": 3, "us": 2, "ms": 1, "s": 0} - def generic_visit(self, node: ast.QASMNode, context: Program = None): - if isinstance(node, ast.ExternDeclaration): - context.externs[node.name.name] = node - return + def generic_visit(self, node: ast.QASMNode, context: Program | None = None) -> ast.QASMNode: + var: Var | None = None + if not isinstance(node, ast.ClassicalDeclaration) and hasattr(node, "type"): + if hasattr(node, "identifier"): + var = self.create_oqpy_var(node.type, node.identifier.name, needs_declaration=False) + elif hasattr(node, "name"): + var = self.create_oqpy_var(node.type, node.name.name, needs_declaration=False) + if context is not None and var is not None: + context._add_var(var) + return super().generic_visit(node, context) def visit_Program(self, node: ast.Program, context: Program) -> None: # TODO: need to check better node.version as for init - context.version = node.version - for statement in node.statements: - if isinstance(statement, ast.ExternDeclaration): - context.externs[statement.name.name] = statement - continue + if node.version is None or ( + len(node.version.split(".")) in [1, 2] + and all([item.isnumeric() for item in node.version.split(".")]) + ): + context.version = node.version + else: + raise RuntimeError("Version number does not match the X[.y] format.") + + res = self.generic_visit(node, context) + for statement in res.statements: context._add_statement(statement) - self.generic_visit(node, context) + return res - # def visit_ExternDeclaration(self, node: ast.ExternDeclaration, context: Program) -> None: - # context.externs[node.name.name] = node + def visit_ExternDeclaration(self, node: ast.ExternDeclaration, context: Program) -> None: + context.externs[node.name.name] = node def visit_ClassicalDeclaration(self, node: ast.ClassicalDeclaration, context: Program) -> None: - var: Var - if node.init_expression == None: - init_expression = None - else: - init_expression = self.visit(node.init_expression) - if isinstance(node.type, ast.BitType): - var = classical_types.BitVar(init_expression=init_expression, name=node.identifier.name) - elif isinstance(node.type, ast.BoolType): + var: Var | None + + var = self.create_oqpy_var(node.type, node.identifier.name, node.init_expression) + if var is None: + return self.generic_visit(node, context) + context._mark_var_declared(var) + return self.generic_visit(node, context) + + def visit_CalibrationDefinition( + self, node: ast.CalibrationDefinition, context: Program + ) -> None: + context._add_defcal([ident.name for ident in node.qubits], node.name.name, node) + return self.generic_visit(node, context) + + def visit_SubroutineDefinition(self, node: ast.SubroutineDefinition, context: Program) -> None: + context._add_subroutine(node.name.name, node) + return self.generic_visit(node, context) + + def create_oqpy_var( + self, + node_type: ast.ClassicalType, + name: str, + init_expression: ast.Expression | None = None, + needs_declaration: bool = True, + ) -> Var | None: + var: Var | None = None + if isinstance(node_type, ast.BitType): + var = classical_types.BitVar( + init_expression=init_expression, name=name, needs_declaration=needs_declaration + ) + elif isinstance(node_type, ast.BoolType): var = classical_types.BoolVar( - init_expression=init_expression, name=node.identifier.name + init_expression=init_expression, name=name, needs_declaration=needs_declaration ) - elif isinstance(node.type, ast.IntType): - var = classical_types.IntVar(init_expression=init_expression, name=node.identifier.name) - elif isinstance(node.type, ast.UintType): + elif isinstance(node_type, ast.IntType): + var = classical_types.IntVar( + init_expression=init_expression, name=name, needs_declaration=needs_declaration + ) + elif isinstance(node_type, ast.UintType): var = classical_types.UintVar( - init_expression=init_expression, name=node.identifier.name + init_expression=init_expression, name=name, needs_declaration=needs_declaration ) - elif isinstance(node.type, ast.FloatType): + elif isinstance(node_type, ast.FloatType): var = classical_types.FloatVar( - init_expression=init_expression, name=node.identifier.name + init_expression=init_expression, name=name, needs_declaration=needs_declaration ) - elif isinstance(node.type, ast.AngleType): + elif isinstance(node_type, ast.AngleType): var = classical_types.AngleVar( - init_expression=init_expression, name=node.identifier.name + init_expression=init_expression, name=name, needs_declaration=needs_declaration ) - elif isinstance(node.type, ast.ComplexType): + elif isinstance(node_type, ast.ComplexType): var = classical_types.ComplexVar( init_expression=init_expression, - name=node.identifier.name, - base_type=node.type.base_type, + name=name, + base_type=node_type.base_type, + needs_declaration=needs_declaration, ) - elif isinstance(node.type, ast.DurationType): + elif isinstance(node_type, ast.DurationType): + if isinstance(init_expression, ast.DurationLiteral): + if init_expression.unit.name not in self.TIME_UNIT_TO_EXP: + raise ValueError( + f"Unexpected duration specified: {init_expression.unit.name}:{init_expression.unit.value}" + ) + multiplier = 10 ** (-3 * self.TIME_UNIT_TO_EXP[init_expression.unit.name]) + value = multiplier * init_expression.value var = classical_types.DurationVar( - init_expression=init_expression, name=node.identifier.name + init_expression=value, name=name, needs_declaration=needs_declaration ) - elif isinstance(node.type, ast.StretchType): + elif isinstance(node_type, ast.StretchType): var = classical_types.StretchVar( - init_expression=init_expression, name=node.identifier.name + init_expression=init_expression, name=name, needs_declaration=needs_declaration ) - else: - return - # elif isinstance(node.type, ast.FrameType): - # port, frequency, phase = self.visit(node.init_expression) - # var = FrameVar(port=port, frequency=frequency, phase=phase, name=node.identifier.name) - - # For shorter implementation - # if isinstance(node.type, ast.ComplexType): - # var = classical_types.ComplexVar(init_expression=node.init_expression, name=node.identifier.name, base_type=node.type.base_type) - # else: - # var = classical_types._ClassicalVar(init_expression=node.init_expression, name=node.identifier.name, type_cls=node.type) - context._mark_var_declared(var) - - def visit_CalibrationDefinition( - self, node: ast.CalibrationDefinition, context: Program - ) -> None: - context._add_defcal([ident.name for ident in node.qubits], node.name.name, node) - self.generic_visit(node, context) - - def visit_SubroutineDefinition(self, node: ast.SubroutineDefinition, context: Program) -> None: - context._add_subroutine(node.name.name, node) - self.generic_visit(node, context) - - def visit_FunctionCall(self, node: ast.FunctionCall, context: Program | None = None) -> Any: - # func_name = node.name.name - # return getattr(self, func_name)(node, context) - if node.name == "newframe": - return node.arguments - - # def newframe(self, node: ast.FunctionCall, context: Program) -> None: - # return [self.visit(arg, context) for arg in node.arguments] - - def visit_IntegerLiteral(self, node: ast.IntegerLiteral, context: Program | None = None) -> Any: - """Visit Integer Literal. - Args: - node (ast.IntegerLiteral): The integer literal. - context (_ParseState): The parse state context. - """ - return int(node.value) - - def visit_ImaginaryLiteral( - self, node: ast.ImaginaryLiteral, context: Program | None = None - ) -> Any: - """Visit Imaginary Number Literal. - Args: - node (ast.visit_ImaginaryLiteral): The imaginary number literal. - context (_ParseState): The parse state context. - """ - return complex(node.value * 1j) - - def visit_FloatLiteral(self, node: ast.FloatLiteral, context: Program | None = None) -> Any: - """Visit Float Literal. - Args: - node (ast.FloatLiteral): The float literal. - context (_ParseState): The parse state context. - """ - return float(node.value) - - def visit_BooleanLiteral(self, node: ast.BooleanLiteral, context: Program | None = None) -> Any: - """Visit Boolean Literal. - Args: - node (ast.BooleanLiteral): The boolean literal. - context (_ParseState): The parse state context. - """ - return True if node.value else False - - def visit_DurationLiteral( - self, node: ast.DurationLiteral, context: Program | None = None - ) -> Any: - """Visit Duration Literal. - Args: - node (ast.DurationLiteral): The duration literal. - context (_ParseState): The parse state context. - """ - if node.unit.name not in self.TIME_UNIT_TO_EXP: - raise ValueError(f"Unexpected duration specified: {node.unit.name}:{node.unit.value}") - multiplier = 10 ** (-3 * self.TIME_UNIT_TO_EXP[node.unit.name]) - return multiplier * node.value + elif isinstance(node_type, ast.FrameType): + if isinstance(init_expression, ast.FunctionCall): + if init_expression.name.name == "newframe": + port, frequency, phase = (lambda x: (x[0].name, x[1].value, x[2].value))( + init_expression.arguments + ) + var = FrameVar(port=port, frequency=frequency, phase=phase, name=name) + else: + var = FrameVar(name=name) + return var def _remove_spans(node: Union[list[ast.QASMNode], ast.QASMNode]) -> ast.QASMNode: diff --git a/tests/test_builder.py b/tests/test_builder.py index b5223d2..9efa74a 100644 --- a/tests/test_builder.py +++ b/tests/test_builder.py @@ -19,9 +19,9 @@ from dataclasses import dataclass import numpy as np +import openpulse.ast as ast import pytest from openpulse.printer import dumps -import openpulse.ast as ast from oqpy import * from oqpy.base import expr_matches From a2bbdd76297c6ba549d16df93787b1ab48c9ec28 Mon Sep 17 00:00:00 2001 From: Jean-Christophe Jaskula Date: Wed, 9 Nov 2022 18:18:38 -0500 Subject: [PATCH 06/18] Mark some tests as xfail --- oqpy/classical_types.py | 4 ++-- oqpy/program.py | 5 ++--- tests/test_builder.py | 37 +++++++++++++++++++++++++++++-------- 3 files changed, 33 insertions(+), 13 deletions(-) diff --git a/oqpy/classical_types.py b/oqpy/classical_types.py index f2f997c..068a84a 100644 --- a/oqpy/classical_types.py +++ b/oqpy/classical_types.py @@ -311,8 +311,8 @@ def __init__( assert isinstance(base_type, ast.FloatType) self.base_type = base_type - if not isinstance(init_expression, (complex, type(None), str, OQPyExpression)): - init_expression = complex(init_expression) # type: ignore[arg-type] + if not isinstance(init_expression, (complex, type(None), OQPyExpression, ast.Expression)): + init_expression = complex(init_expression) # type: ignore[call-overload] super().__init__(init_expression, *args, **kwargs, base_type=base_type) diff --git a/oqpy/program.py b/oqpy/program.py index 9827dd4..5a1d4de 100644 --- a/oqpy/program.py +++ b/oqpy/program.py @@ -313,6 +313,7 @@ def to_ast( return prog def from_qasm(self, qasm_text: str) -> None: + """Build an OQPy program by parsing OpenQASM text.""" oqasm_ast = _remove_spans(parse(qasm_text)) ProgramBuilder().visit(oqasm_ast, self) @@ -630,7 +631,6 @@ def generic_visit(self, node: ast.QASMNode, context: Program | None = None) -> a return super().generic_visit(node, context) def visit_Program(self, node: ast.Program, context: Program) -> None: - # TODO: need to check better node.version as for init if node.version is None or ( len(node.version.split(".")) in [1, 2] and all([item.isnumeric() for item in node.version.split(".")]) @@ -733,8 +733,7 @@ def create_oqpy_var( def _remove_spans(node: Union[list[ast.QASMNode], ast.QASMNode]) -> ast.QASMNode: - """Return a new ``QASMNode`` with all spans recursively set to ``None`` to - reduce noise in test failure messages.""" + """Return a new ``QASMNode`` with all spans recursively set to ``None``.""" if isinstance(node, list): return [_remove_spans(item) for item in node] if not isinstance(node, ast.QASMNode): diff --git a/tests/test_builder.py b/tests/test_builder.py index 9efa74a..f7fc2e3 100644 --- a/tests/test_builder.py +++ b/tests/test_builder.py @@ -385,7 +385,7 @@ def test_create_frame(): assert Program(oqasm_text=expected).to_qasm() == expected # assert prog == Program(oqasm_text=prog.to_qasm()) - +@pytest.mark.xfail(reason="Y is redefined, must be investigated") def test_subroutine_with_return(): prog = Program() @@ -440,6 +440,7 @@ def multiply(int[32] x, int[32] y) -> int[32] { # assert prog == Program(oqasm_text=prog.to_qasm()) +@pytest.mark.xfail(reason="Extern must be included in a cal block") def test_box_and_timings(): constant = declare_waveform_generator("constant", [("length", duration), ("iq", complex128)]) @@ -495,6 +496,7 @@ def test_box_and_timings(): # assert prog == Program(oqasm_text=prog.to_qasm()) +@pytest.mark.xfail(reason="Extern must be included in a cal block") def test_play_capture(): port = PortVar("portname") frame = FrameVar(port, 1e9, name="framename") @@ -548,6 +550,7 @@ def test_set_shift_frequency(): # assert prog == Program(oqasm_text=prog.to_qasm()) +@pytest.mark.xfail(reason="Extern must be included in a cal block") def test_ramsey_example(): prog = Program() constant = declare_waveform_generator("constant", [("length", duration), ("iq", complex128)]) @@ -736,6 +739,7 @@ def test_rabi_example(): # assert prog == Program(oqasm_text=prog.to_qasm()) +@pytest.mark.xfail(reason="Extern must be included in a cal block") def test_program_add(): prog1 = Program() constant = declare_waveform_generator("constant", [("length", duration), ("iq", complex128)]) @@ -806,6 +810,7 @@ def _to_oqpy_expression(self): # assert prog == Program(oqasm_text=prog.to_qasm()) +@pytest.mark.xfail(reason="Extern must be included in a cal block") def test_waveform_extern_arg_passing(): prog = Program() constant = declare_waveform_generator("constant", [("length", duration), ("iq", complex128)]) @@ -883,8 +888,8 @@ def test_needs_declaration(): def test_discrete_waveform(): - port = PortVar("port") - frame = FrameVar(port, 5e9, name="frame") + port = PortVar("portname") + frame = FrameVar(port, 5e9, name="framename") wfm_float = WaveformVar([-1.2, 1.5, 0.1, 0], name="wfm_float") wfm_int = WaveformVar((1, 0, 4, -1), name="wfm_int") wfm_complex = WaveformVar( @@ -895,21 +900,20 @@ def test_discrete_waveform(): prog = Program() prog.declare([wfm_float, wfm_int, wfm_complex, wfm_notype]) prog.play(frame, wfm_complex) - prog.play(frame, [1] * 2 + [0] * 2) + #prog.play(frame, [1] * 2 + [0] * 2) expected = textwrap.dedent( """ OPENQASM 3.0; cal { - port port; - frame frame = newframe(port, 5000000000.0, 0); + port portname; + frame framename = newframe(portname, 5000000000.0, 0); waveform wfm_float = {-1.2, 1.5, 0.1, 0}; waveform wfm_int = {1, 0, 4, -1}; waveform wfm_complex = {1.0 + 2.0im, 3.2 - 1.2im, -2.1im, 1.0}; waveform wfm_notype = {0.0, -1.0im, 1.2, -1}; } play(frame, wfm_complex); - play(frame, {1, 1, 0, 0}); """ ).strip() @@ -917,6 +921,22 @@ def test_discrete_waveform(): # assert prog == Program(oqasm_text=prog.to_qasm()) +@pytest.mark.xfail(reason="Inline arbitrary waveforms are not parsed") +def test_discrete_inline_waveform(): + oq_text = textwrap.dedent( + """ + OPENQASM 3.0; + cal { + port portname; + frame framename = newframe(portname, 5000000000.0, 0); + } + play(framename, {0,1,0,1}); + """ + ).strip() + + assert Program(oqasm_text=oq_text).to_qasm() == oq_text + + def test_var_and_expr_matches(): p1 = PortVar("p1") p2 = PortVar("p2") @@ -995,6 +1015,7 @@ def to_ast(self): make_duration("asdf") +@pytest.mark.xfail(reason="Extern must be included in a cal block") def test_autoencal(): port = PortVar("portname") frame = FrameVar(port, 1e9, name="framename") @@ -1030,7 +1051,7 @@ def test_autoencal(): assert Program(oqasm_text=expected).to_qasm() == expected # assert prog == Program(oqasm_text=prog.to_qasm()) - +@pytest.mark.xfail(reason="Extern must be included in a cal block") def test_ramsey_example_blog(): import oqpy From 41e4ac70848928e3afcce619f8a789659060f73c Mon Sep 17 00:00:00 2001 From: Jean-Christophe Jaskula Date: Wed, 9 Nov 2022 18:20:14 -0500 Subject: [PATCH 07/18] Expecting the parser to validate the version --- oqpy/program.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/oqpy/program.py b/oqpy/program.py index 5a1d4de..2db583c 100644 --- a/oqpy/program.py +++ b/oqpy/program.py @@ -631,13 +631,7 @@ def generic_visit(self, node: ast.QASMNode, context: Program | None = None) -> a return super().generic_visit(node, context) def visit_Program(self, node: ast.Program, context: Program) -> None: - if node.version is None or ( - len(node.version.split(".")) in [1, 2] - and all([item.isnumeric() for item in node.version.split(".")]) - ): - context.version = node.version - else: - raise RuntimeError("Version number does not match the X[.y] format.") + context.version = node.version res = self.generic_visit(node, context) for statement in res.statements: From 0c94de07e21352200ab13ad0e884c32411e533f6 Mon Sep 17 00:00:00 2001 From: Jean-Christophe Jaskula Date: Thu, 10 Nov 2022 15:28:38 -0500 Subject: [PATCH 08/18] Fix ExternStatement return type --- oqpy/program.py | 26 +++++--------------------- 1 file changed, 5 insertions(+), 21 deletions(-) diff --git a/oqpy/program.py b/oqpy/program.py index 2db583c..35f8b4e 100644 --- a/oqpy/program.py +++ b/oqpy/program.py @@ -25,7 +25,7 @@ import warnings from copy import deepcopy -from typing import Any, Dict, Iterable, Iterator, Optional, Union +from typing import Any, Iterable, Iterator, Optional from openpulse import ast from openpulse.parser import parse @@ -314,7 +314,7 @@ def to_ast( def from_qasm(self, qasm_text: str) -> None: """Build an OQPy program by parsing OpenQASM text.""" - oqasm_ast = _remove_spans(parse(qasm_text)) + oqasm_ast = parse(qasm_text) ProgramBuilder().visit(oqasm_ast, self) def to_qasm( @@ -620,6 +620,8 @@ class ProgramBuilder(QASMTransformer[Program]): def generic_visit(self, node: ast.QASMNode, context: Program | None = None) -> ast.QASMNode: var: Var | None = None + if hasattr(node, "span"): + node.span = None if not isinstance(node, ast.ClassicalDeclaration) and hasattr(node, "type"): if hasattr(node, "identifier"): var = self.create_oqpy_var(node.type, node.identifier.name, needs_declaration=False) @@ -639,6 +641,7 @@ def visit_Program(self, node: ast.Program, context: Program) -> None: return res def visit_ExternDeclaration(self, node: ast.ExternDeclaration, context: Program) -> None: + node = self.generic_visit(node, context) # Clear spans first context.externs[node.name.name] = node def visit_ClassicalDeclaration(self, node: ast.ClassicalDeclaration, context: Program) -> None: @@ -724,22 +727,3 @@ def create_oqpy_var( else: var = FrameVar(name=name) return var - - -def _remove_spans(node: Union[list[ast.QASMNode], ast.QASMNode]) -> ast.QASMNode: - """Return a new ``QASMNode`` with all spans recursively set to ``None``.""" - if isinstance(node, list): - return [_remove_spans(item) for item in node] - if not isinstance(node, ast.QASMNode): - return node - kwargs: Dict[str, ast.QASMNode] = {} - no_init: Dict[str, ast.QASMNode] = {} - for field in dataclasses.fields(node): - if field.name == "span": - continue - target = kwargs if field.init else no_init - target[field.name] = _remove_spans(getattr(node, field.name)) - out = type(node)(**kwargs) - for attribute, value in no_init.items(): - setattr(out, attribute, value) - return out From 1c6431bcbc3ac371c930d2aabe9329a23c23dd03 Mon Sep 17 00:00:00 2001 From: Jean-Christophe Jaskula Date: Thu, 10 Nov 2022 15:37:09 -0500 Subject: [PATCH 09/18] Clean some tests up --- tests/test_builder.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/test_builder.py b/tests/test_builder.py index f7fc2e3..78b2577 100644 --- a/tests/test_builder.py +++ b/tests/test_builder.py @@ -479,8 +479,7 @@ def test_box_and_timings(): ).strip() prog_from_text = Program(oqasm_text=expected) - # FIXME It should not be with include_externs = False - assert prog_from_text.to_qasm(include_externs=False) == expected + assert prog_from_text.to_qasm(encal_declarations=True) == expected prog_from_text.externs["constant"] == ast.ExternDeclaration( name=ast.Identifier(name="constant"), arguments=[ @@ -521,8 +520,7 @@ def test_play_capture(): """ ).strip() - # FIXME It should not be with include_externs = False - assert Program(oqasm_text=expected).to_qasm() == expected + assert Program(oqasm_text=expected).to_qasm(encal_declarations=True) == expected # assert prog == Program(oqasm_text=prog.to_qasm()) From b97f8ffada3fb5f24bd8beaa85ec07bbcf5b4531 Mon Sep 17 00:00:00 2001 From: Jean-Christophe Jaskula Date: Thu, 17 Nov 2022 22:58:23 -0500 Subject: [PATCH 10/18] Add Port and Waveform vars to ProgramBuilder --- oqpy/program.py | 20 +++++++++++++++++++- tests/test_builder.py | 2 +- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/oqpy/program.py b/oqpy/program.py index 35f8b4e..04a104f 100644 --- a/oqpy/program.py +++ b/oqpy/program.py @@ -615,7 +615,15 @@ def process_statement_list( class ProgramBuilder(QASMTransformer[Program]): - # Does not support {"dt": 4} + """ AST Transformer class that modifies the tree created from parsing opeqasm input text. + + It separates: + - extern declarations and stores them in Program().externs. + - subroutines and stores them in Program().subroutines + - defcals and stores in Program().defcals + It also creates the corresponding OQpy variables everytime it encounters a classical + or pulse type. + """ TIME_UNIT_TO_EXP = {"ns": 3, "us": 2, "ms": 1, "s": 0} def generic_visit(self, node: ast.QASMNode, context: Program | None = None) -> ast.QASMNode: @@ -640,6 +648,9 @@ def visit_Program(self, node: ast.Program, context: Program) -> None: context._add_statement(statement) return res + def visit_CalibrationGrammarDeclaration(self, node: ast.CalibrationGrammarDeclaration, context: Program): + pass + def visit_ExternDeclaration(self, node: ast.ExternDeclaration, context: Program) -> None: node = self.generic_visit(node, context) # Clear spans first context.externs[node.name.name] = node @@ -726,4 +737,11 @@ def create_oqpy_var( var = FrameVar(port=port, frequency=frequency, phase=phase, name=name) else: var = FrameVar(name=name) + elif isinstance(node_type, ast.PortType): + var = PortVar(name=name) + elif isinstance(node_type, ast.WaveformType): + # init_expression must be transformed + var = WaveformVar(init_expression=init_expression, name=name) + else: + raise TypeError(f"Unsupported type {type(node_type)} was used in the OpenQASM program.") return var diff --git a/tests/test_builder.py b/tests/test_builder.py index 78b2577..f437def 100644 --- a/tests/test_builder.py +++ b/tests/test_builder.py @@ -495,7 +495,6 @@ def test_box_and_timings(): # assert prog == Program(oqasm_text=prog.to_qasm()) -@pytest.mark.xfail(reason="Extern must be included in a cal block") def test_play_capture(): port = PortVar("portname") frame = FrameVar(port, 1e9, name="framename") @@ -509,6 +508,7 @@ def test_play_capture(): expected = textwrap.dedent( """ OPENQASM 3.0; + defcalgrammar "openpulse"; cal { extern constant(duration, complex[float[64]]) -> waveform; port portname; From d095a2d1b616dcf6ded99108b6703452b3c809f2 Mon Sep 17 00:00:00 2001 From: Jean-Christophe Jaskula Date: Thu, 17 Nov 2022 23:41:17 -0500 Subject: [PATCH 11/18] Fix extern that were outside cal blocks --- oqpy/program.py | 28 ++++--- tests/test_builder.py | 182 +++++++++++++++++++++++++++++++++++++++--- 2 files changed, 187 insertions(+), 23 deletions(-) diff --git a/oqpy/program.py b/oqpy/program.py index 04a104f..1b282cc 100644 --- a/oqpy/program.py +++ b/oqpy/program.py @@ -615,15 +615,16 @@ def process_statement_list( class ProgramBuilder(QASMTransformer[Program]): - """ AST Transformer class that modifies the tree created from parsing opeqasm input text. - - It separates: - - extern declarations and stores them in Program().externs. - - subroutines and stores them in Program().subroutines - - defcals and stores in Program().defcals - It also creates the corresponding OQpy variables everytime it encounters a classical - or pulse type. + """AST Transformer class that modifies the tree created from parsing opeqasm input text. + + It separates: + - extern declarations and stores them in Program().externs. + - subroutines and stores them in Program().subroutines + - defcals and stores in Program().defcals + It also creates the corresponding OQpy variables everytime it encounters a classical + or pulse type. """ + TIME_UNIT_TO_EXP = {"ns": 3, "us": 2, "ms": 1, "s": 0} def generic_visit(self, node: ast.QASMNode, context: Program | None = None) -> ast.QASMNode: @@ -648,7 +649,9 @@ def visit_Program(self, node: ast.Program, context: Program) -> None: context._add_statement(statement) return res - def visit_CalibrationGrammarDeclaration(self, node: ast.CalibrationGrammarDeclaration, context: Program): + def visit_CalibrationGrammarDeclaration( + self, node: ast.CalibrationGrammarDeclaration, context: Program + ) -> None: pass def visit_ExternDeclaration(self, node: ast.ExternDeclaration, context: Program) -> None: @@ -667,7 +670,12 @@ def visit_ClassicalDeclaration(self, node: ast.ClassicalDeclaration, context: Pr def visit_CalibrationDefinition( self, node: ast.CalibrationDefinition, context: Program ) -> None: - context._add_defcal([ident.name for ident in node.qubits], node.name.name, node) + context._add_defcal( + [ident.name for ident in node.qubits], + node.name.name, + [dumps(a) for a in node.arguments], + node, + ) return self.generic_visit(node, context) def visit_SubroutineDefinition(self, node: ast.SubroutineDefinition, context: Program) -> None: diff --git a/tests/test_builder.py b/tests/test_builder.py index f437def..53b054b 100644 --- a/tests/test_builder.py +++ b/tests/test_builder.py @@ -440,7 +440,6 @@ def multiply(int[32] x, int[32] y) -> int[32] { # assert prog == Program(oqasm_text=prog.to_qasm()) -@pytest.mark.xfail(reason="Extern must be included in a cal block") def test_box_and_timings(): constant = declare_waveform_generator("constant", [("length", duration), ("iq", complex128)]) @@ -462,6 +461,7 @@ def test_box_and_timings(): expected = textwrap.dedent( """ OPENQASM 3.0; + defcalgrammar "openpulse"; cal { extern constant(duration, complex[float[64]]) -> waveform; port portname; @@ -548,7 +548,162 @@ def test_set_shift_frequency(): # assert prog == Program(oqasm_text=prog.to_qasm()) -@pytest.mark.xfail(reason="Extern must be included in a cal block") +def test_defcals(): + prog = Program() + constant = declare_waveform_generator("constant", [("length", duration), ("iq", complex128)]) + + q_port = PortVar("q_port") + rx_port = PortVar("rx_port") + tx_port = PortVar("tx_port") + q_frame = FrameVar(q_port, 6.431e9, name="q_frame") + rx_frame = FrameVar(rx_port, 5.752e9, name="rx_frame") + tx_frame = FrameVar(tx_port, 5.752e9, name="tx_frame") + + q1 = PhysicalQubits[1] + q2 = PhysicalQubits[2] + + with defcal(prog, q2, "x"): + prog.play(q_frame, constant(1e-6, 0.1)) + + with defcal(prog, q2, "rx", [AngleVar(name="theta")]) as theta: + prog.increment(theta, 0.1) + prog.play(q_frame, constant(1e-6, 0.1)) + + with defcal(prog, q2, "rx", [pi / 3]): + prog.play(q_frame, constant(1e-6, 0.1)) + + with defcal(prog, [q1, q2], "xy", [AngleVar(name="theta"), +pi / 2]) as theta: + prog.increment(theta, 0.1) + prog.play(q_frame, constant(1e-6, 0.1)) + + with defcal(prog, [q1, q2], "xy", [AngleVar(name="theta"), FloatVar(name="phi"), 10]) as params: + theta, phi = params + prog.increment(theta, 0.1) + prog.increment(phi, 0.2) + prog.play(q_frame, constant(1e-6, 0.1)) + + with defcal(prog, q2, "readout", return_type=bit): + prog.play(tx_frame, constant(2.4e-6, 0.2)) + prog.capture(rx_frame, constant(2.4e-6, 1)) + + with pytest.raises(AssertionError): + + with defcal(prog, q2, "readout", return_type=bool): + prog.play(tx_frame, constant(2.4e-6, 0.2)) + prog.capture(rx_frame, constant(2.4e-6, 1)) + + expected = textwrap.dedent( + """ + OPENQASM 3.0; + defcalgrammar "openpulse"; + cal { + extern constant(duration, complex[float[64]]) -> waveform; + port rx_port; + port tx_port; + port q_port; + frame q_frame = newframe(q_port, 6431000000.0, 0); + frame tx_frame = newframe(tx_port, 5752000000.0, 0); + frame rx_frame = newframe(rx_port, 5752000000.0, 0); + } + defcal x $2 { + play(q_frame, constant(1000.0ns, 0.1)); + } + defcal rx(angle[32] theta) $2 { + theta += 0.1; + play(q_frame, constant(1000.0ns, 0.1)); + } + defcal rx(pi / 3) $2 { + play(q_frame, constant(1000.0ns, 0.1)); + } + defcal xy(angle[32] theta, pi / 2) $1, $2 { + theta += 0.1; + play(q_frame, constant(1000.0ns, 0.1)); + } + defcal xy(angle[32] theta, float[64] phi, 10) $1, $2 { + theta += 0.1; + phi += 0.2; + play(q_frame, constant(1000.0ns, 0.1)); + } + defcal readout $2 -> bit { + play(tx_frame, constant(2400.0ns, 0.2)); + capture(rx_frame, constant(2400.0ns, 1)); + } + """ + ).strip() + + prog_from_text = Program(oqasm_text=expected) + + assert prog_from_text.to_qasm(encal_declarations=True) == expected + # assert prog == Program(oqasm_text=prog.to_qasm()) + + expect_defcal_rx_theta = textwrap.dedent( + """ + defcal rx(angle[32] theta) $2 { + theta += 0.1; + play(q_frame, constant(1000.0ns, 0.1)); + } + """ + ).strip() + assert ( + dumps(prog_from_text.defcals[(("$2",), "rx", ("angle[32] theta",))], indent=" ").strip() + == expect_defcal_rx_theta + ) + expect_defcal_rx_pio2 = textwrap.dedent( + """ + defcal rx(pi / 3) $2 { + play(q_frame, constant(1000.0ns, 0.1)); + } + """ + ).strip() + assert ( + dumps(prog_from_text.defcals[(("$2",), "rx", ("pi / 3",))], indent=" ").strip() + == expect_defcal_rx_pio2 + ) + expect_defcal_xy_theta_pio2 = textwrap.dedent( + """ + defcal xy(angle[32] theta, pi / 2) $1, $2 { + theta += 0.1; + play(q_frame, constant(1000.0ns, 0.1)); + } + """ + ).strip() + assert ( + dumps( + prog_from_text.defcals[(("$1", "$2"), "xy", ("angle[32] theta", "pi / 2"))], indent=" " + ).strip() + == expect_defcal_xy_theta_pio2 + ) + expect_defcal_xy_theta_phi = textwrap.dedent( + """ + defcal xy(angle[32] theta, float[64] phi, 10) $1, $2 { + theta += 0.1; + phi += 0.2; + play(q_frame, constant(1000.0ns, 0.1)); + } + """ + ).strip() + assert ( + dumps( + prog_from_text.defcals[(("$1", "$2"), "xy", ("angle[32] theta", "float[64] phi", "10"))], + indent=" ", + ).strip() + == expect_defcal_xy_theta_phi + ) + expect_defcal_readout_q2 = textwrap.dedent( + """ + defcal readout $2 -> bit { + play(tx_frame, constant(2400.0ns, 0.2)); + capture(rx_frame, constant(2400.0ns, 1)); + } + """ + ).strip() + assert ( + dumps(prog_from_text.defcals[(("$2",), "readout", ())], indent=" ").strip() + == expect_defcal_readout_q2 + ) + + + def test_ramsey_example(): prog = Program() constant = declare_waveform_generator("constant", [("length", duration), ("iq", complex128)]) @@ -606,6 +761,7 @@ def test_ramsey_example(): expected = textwrap.dedent( """ OPENQASM 3.0; + defcalgrammar "openpulse"; cal { extern constant(duration, complex[float[64]]) -> waveform; extern gaussian(duration, duration, float[64], float[64]) -> waveform; @@ -662,7 +818,7 @@ def test_ramsey_example(): """ ).strip() - assert Program(oqasm_text=expected).to_qasm() == expected + assert Program(oqasm_text=expected).to_qasm(encal_declarations=True) == expected # assert prog == Program(oqasm_text=prog.to_qasm()) # assert dumps(prog.defcals[("$2", "x90")], indent=" ").strip() == expect_defcal_x90_q2 # assert dumps(prog.defcals[("$2", "readout")], indent=" ").strip() == expect_defcal_readout_q2 @@ -737,7 +893,7 @@ def test_rabi_example(): # assert prog == Program(oqasm_text=prog.to_qasm()) -@pytest.mark.xfail(reason="Extern must be included in a cal block") +# @pytest.mark.xfail(reason="Extern must be included in a cal block") def test_program_add(): prog1 = Program() constant = declare_waveform_generator("constant", [("length", duration), ("iq", complex128)]) @@ -758,6 +914,7 @@ def test_program_add(): expected = textwrap.dedent( """ OPENQASM 3.0; + defcalgrammar "openpulse"; cal { extern constant(duration, complex[float[64]]) -> waveform; port p1; @@ -774,7 +931,7 @@ def test_program_add(): ).strip() prog = prog1 + prog2 - assert Program(oqasm_text=expected).to_qasm() == expected + assert Program(oqasm_text=expected).to_qasm(encal_declarations=True) == expected # assert prog == Program(oqasm_text=prog.to_qasm()) with pytest.raises(RuntimeError): @@ -808,7 +965,6 @@ def _to_oqpy_expression(self): # assert prog == Program(oqasm_text=prog.to_qasm()) -@pytest.mark.xfail(reason="Extern must be included in a cal block") def test_waveform_extern_arg_passing(): prog = Program() constant = declare_waveform_generator("constant", [("length", duration), ("iq", complex128)]) @@ -828,6 +984,7 @@ def test_waveform_extern_arg_passing(): expected = textwrap.dedent( """ OPENQASM 3.0; + defcalgrammar "openpulse"; cal { extern constant(duration, complex[float[64]]) -> waveform; port p1; @@ -840,7 +997,7 @@ def test_waveform_extern_arg_passing(): """ ).strip() - assert Program(oqasm_text=expected).to_qasm() == expected + assert Program(oqasm_text=expected).to_qasm(encal_declarations=True) == expected # assert prog == Program(oqasm_text=prog.to_qasm()) @@ -924,15 +1081,16 @@ def test_discrete_inline_waveform(): oq_text = textwrap.dedent( """ OPENQASM 3.0; + defcalgrammar "openpulse"; cal { port portname; frame framename = newframe(portname, 5000000000.0, 0); + play(framename, {0,1,0,1}); } - play(framename, {0,1,0,1}); """ ).strip() - assert Program(oqasm_text=oq_text).to_qasm() == oq_text + assert Program(oqasm_text=oq_text).to_qasm(encal_declarations=True) == oq_text def test_var_and_expr_matches(): @@ -1013,7 +1171,6 @@ def to_ast(self): make_duration("asdf") -@pytest.mark.xfail(reason="Extern must be included in a cal block") def test_autoencal(): port = PortVar("portname") frame = FrameVar(port, 1e9, name="framename") @@ -1046,10 +1203,9 @@ def test_autoencal(): """ ).strip() - assert Program(oqasm_text=expected).to_qasm() == expected + assert Program(oqasm_text=expected).to_qasm(encal_declarations=True) == expected # assert prog == Program(oqasm_text=prog.to_qasm()) -@pytest.mark.xfail(reason="Extern must be included in a cal block") def test_ramsey_example_blog(): import oqpy @@ -1143,5 +1299,5 @@ def test_ramsey_example_blog(): """ ).strip() - assert Program(oqasm_text=expected).to_qasm() == expected + assert Program(oqasm_text=expected).to_qasm(encal_declarations=True) == expected # assert prog == Program(oqasm_text=prog.to_qasm()) From 0c3b6e73597395e2245532f0b522a37c8828a66f Mon Sep 17 00:00:00 2001 From: Jean-Christophe Jaskula Date: Fri, 18 Nov 2022 12:26:42 -0500 Subject: [PATCH 12/18] Consider variables in def{cal} local --- oqpy/program.py | 16 +++++++++++++--- tests/test_builder.py | 13 ++++++++----- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/oqpy/program.py b/oqpy/program.py index 1b282cc..e7f7d04 100644 --- a/oqpy/program.py +++ b/oqpy/program.py @@ -625,6 +625,8 @@ class ProgramBuilder(QASMTransformer[Program]): or pulse type. """ + inside_def_block: bool = False + TIME_UNIT_TO_EXP = {"ns": 3, "us": 2, "ms": 1, "s": 0} def generic_visit(self, node: ast.QASMNode, context: Program | None = None) -> ast.QASMNode: @@ -670,17 +672,22 @@ def visit_ClassicalDeclaration(self, node: ast.ClassicalDeclaration, context: Pr def visit_CalibrationDefinition( self, node: ast.CalibrationDefinition, context: Program ) -> None: + self.inside_def_block = True context._add_defcal( [ident.name for ident in node.qubits], node.name.name, [dumps(a) for a in node.arguments], node, ) - return self.generic_visit(node, context) + visited_node = self.generic_visit(node, context) + self.inside_def_block = False + return visited_node def visit_SubroutineDefinition(self, node: ast.SubroutineDefinition, context: Program) -> None: - context._add_subroutine(node.name.name, node) - return self.generic_visit(node, context) + self.inside_def_block = True + visited_node = self.generic_visit(node, context) + self.inside_def_block = False + return visited_node def create_oqpy_var( self, @@ -689,6 +696,9 @@ def create_oqpy_var( init_expression: ast.Expression | None = None, needs_declaration: bool = True, ) -> Var | None: + if self.inside_def_block: + return None + var: Var | None = None if isinstance(node_type, ast.BitType): var = classical_types.BitVar( diff --git a/tests/test_builder.py b/tests/test_builder.py index 53b054b..73adcd7 100644 --- a/tests/test_builder.py +++ b/tests/test_builder.py @@ -385,7 +385,7 @@ def test_create_frame(): assert Program(oqasm_text=expected).to_qasm() == expected # assert prog == Program(oqasm_text=prog.to_qasm()) -@pytest.mark.xfail(reason="Y is redefined, must be investigated") + def test_subroutine_with_return(): prog = Program() @@ -669,7 +669,8 @@ def test_defcals(): ).strip() assert ( dumps( - prog_from_text.defcals[(("$1", "$2"), "xy", ("angle[32] theta", "pi / 2"))], indent=" " + prog_from_text.defcals[(("$1", "$2"), "xy", ("angle[32] theta", "pi / 2"))], + indent=" ", ).strip() == expect_defcal_xy_theta_pio2 ) @@ -684,7 +685,9 @@ def test_defcals(): ).strip() assert ( dumps( - prog_from_text.defcals[(("$1", "$2"), "xy", ("angle[32] theta", "float[64] phi", "10"))], + prog_from_text.defcals[ + (("$1", "$2"), "xy", ("angle[32] theta", "float[64] phi", "10")) + ], indent=" ", ).strip() == expect_defcal_xy_theta_phi @@ -703,7 +706,6 @@ def test_defcals(): ) - def test_ramsey_example(): prog = Program() constant = declare_waveform_generator("constant", [("length", duration), ("iq", complex128)]) @@ -1055,7 +1057,7 @@ def test_discrete_waveform(): prog = Program() prog.declare([wfm_float, wfm_int, wfm_complex, wfm_notype]) prog.play(frame, wfm_complex) - #prog.play(frame, [1] * 2 + [0] * 2) + prog.play(frame, [1] * 2 + [0] * 2) expected = textwrap.dedent( """ @@ -1206,6 +1208,7 @@ def test_autoencal(): assert Program(oqasm_text=expected).to_qasm(encal_declarations=True) == expected # assert prog == Program(oqasm_text=prog.to_qasm()) + def test_ramsey_example_blog(): import oqpy From 8705804f901307bf5e427ca8d803de1f2c1feb56 Mon Sep 17 00:00:00 2001 From: Jean-Christophe Jaskula Date: Fri, 18 Nov 2022 13:00:45 -0500 Subject: [PATCH 13/18] Remove unnecessary type_cls --- oqpy/classical_types.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/oqpy/classical_types.py b/oqpy/classical_types.py index 068a84a..e53c20e 100644 --- a/oqpy/classical_types.py +++ b/oqpy/classical_types.py @@ -187,8 +187,6 @@ def __init__( ): name = name or "".join([random.choice(string.ascii_letters) for _ in range(10)]) super().__init__(name, needs_declaration=needs_declaration) - if type_cls is not None: - self.type_cls = type(type_cls) self.type = self.type_cls(**type_kwargs) self.init_expression = init_expression self.annotations = annotations From 6aae0798a625510debc1c7966569d47321011554 Mon Sep 17 00:00:00 2001 From: Jean-Christophe Jaskula Date: Tue, 21 Feb 2023 09:26:01 -0500 Subject: [PATCH 14/18] Initial implementation that removes ast nodes --- oqpy/program.py | 255 ++++++++++++++++++++++++++++++++++++------ tests/test_builder.py | 29 ++++- 2 files changed, 245 insertions(+), 39 deletions(-) diff --git a/oqpy/program.py b/oqpy/program.py index e7f7d04..d057754 100644 --- a/oqpy/program.py +++ b/oqpy/program.py @@ -30,7 +30,7 @@ from openpulse import ast from openpulse.parser import parse from openpulse.printer import dumps -from openqasm3.visitor import QASMTransformer, QASMVisitor +from openqasm3.visitor import QASMVisitor from oqpy import classical_types, quantum_types from oqpy.base import ( @@ -614,7 +614,7 @@ def process_statement_list( return new_list -class ProgramBuilder(QASMTransformer[Program]): +class ProgramBuilder(QASMVisitor[Program]): """AST Transformer class that modifies the tree created from parsing opeqasm input text. It separates: @@ -629,7 +629,52 @@ class ProgramBuilder(QASMTransformer[Program]): TIME_UNIT_TO_EXP = {"ns": 3, "us": 2, "ms": 1, "s": 0} - def generic_visit(self, node: ast.QASMNode, context: Program | None = None) -> ast.QASMNode: + def generic_visit(self, node: ast.QASMNode, context: Program | None = None) -> dict[str, Any]: + res_value: dict[str, Any] = {} + for field, old_value in node.__dict__.items(): + if isinstance(old_value, list): + new_values = [] + res_value[field] = [] + for value in old_value: + if isinstance(value, ast.QASMNode): + res = self.visit(value, context) if context else self.visit(value) + value = res["node"] + if "value" in res: + res_value[field].append(res["value"]) + if value is None: + continue + elif not isinstance(value, ast.QASMNode): + new_values.extend(value) + continue + elif isinstance(value, list): + my_table = [] + for idx, element in enumerate(value): + res = self.visit(element, context) if context else self.visit(element) + value[idx] = res["node"] + if "value" in res: + my_table.append(res["value"]) + new_values.append(value) + res_value[field].append(my_table) + continue + else: + raise TypeError(f"Got {type(value)} for {field}") + new_values.append(value) + old_value[:] = new_values + elif isinstance(old_value, ast.QASMNode): + res = self.visit(old_value, context) if context else self.visit(old_value) + if isinstance(res, ast.QASMNode): + new_node = res + else: + new_node = res["node"] + res_value[field] = res["value"] + if new_node is None: + delattr(node, field) + else: + setattr(node, field, new_node) + return {"node": node, "value": res_value if res_value is not {} else None} + + def visit(self, node: ast.QASMNode, context: Optional[Program] = None) -> dict[str, Any]: + """Visit a node.""" var: Var | None = None if hasattr(node, "span"): node.span = None @@ -641,37 +686,59 @@ def generic_visit(self, node: ast.QASMNode, context: Program | None = None) -> a if context is not None and var is not None: context._add_var(var) - return super().generic_visit(node, context) + method = "visit_" + node.__class__.__name__ + visitor = getattr(self, method, self.generic_visit) + # The visitor method may not have the context argument. + if context: + res = visitor(node, context) + else: + res = visitor(node) + if isinstance(res, ast.QASMNode): + return {"node": res} + else: + return res + + def visit_Program(self, node: ast.Program, context: Program) -> ast.QASMNode: + node = self.generic_visit(node, context)["node"] - def visit_Program(self, node: ast.Program, context: Program) -> None: context.version = node.version - res = self.generic_visit(node, context) - for statement in res.statements: + for statement in node.statements: context._add_statement(statement) - return res + return node def visit_CalibrationGrammarDeclaration( self, node: ast.CalibrationGrammarDeclaration, context: Program - ) -> None: - pass + ) -> dict[str, Any]: + return {"node": None} - def visit_ExternDeclaration(self, node: ast.ExternDeclaration, context: Program) -> None: - node = self.generic_visit(node, context) # Clear spans first + def visit_ExternDeclaration( + self, node: ast.ExternDeclaration, context: Program + ) -> dict[str, Any]: + node = self.generic_visit(node, context)["node"] # Clear spans first context.externs[node.name.name] = node + return {"node": None} - def visit_ClassicalDeclaration(self, node: ast.ClassicalDeclaration, context: Program) -> None: + def visit_ClassicalDeclaration( + self, node: ast.ClassicalDeclaration, context: Program + ) -> dict[str, Any]: var: Var | None - var = self.create_oqpy_var(node.type, node.identifier.name, node.init_expression) - if var is None: - return self.generic_visit(node, context) - context._mark_var_declared(var) - return self.generic_visit(node, context) + res = self.generic_visit(node, context) + node = res["node"] + + if "init_expression" in res["value"]: + init_expression_value = res["value"]["init_expression"] + else: + init_expression_value = None + var = self.create_oqpy_var(node.type, node.identifier.name, init_expression_value) + if var is not None: + context._mark_var_declared(var) + return {"node": node} def visit_CalibrationDefinition( self, node: ast.CalibrationDefinition, context: Program - ) -> None: + ) -> dict[str, Any]: self.inside_def_block = True context._add_defcal( [ident.name for ident in node.qubits], @@ -679,21 +746,24 @@ def visit_CalibrationDefinition( [dumps(a) for a in node.arguments], node, ) - visited_node = self.generic_visit(node, context) + visited_node = self.generic_visit(node, context)["node"] self.inside_def_block = False - return visited_node + return {"node": visited_node} - def visit_SubroutineDefinition(self, node: ast.SubroutineDefinition, context: Program) -> None: + def visit_SubroutineDefinition( + self, node: ast.SubroutineDefinition, context: Program + ) -> dict[str, Any]: self.inside_def_block = True - visited_node = self.generic_visit(node, context) + visited_node = self.generic_visit(node, context)["node"] self.inside_def_block = False - return visited_node + context._add_subroutine(visited_node.name.name, visited_node) + return {"node": None} def create_oqpy_var( self, node_type: ast.ClassicalType, name: str, - init_expression: ast.Expression | None = None, + init_expression: Any | None = None, needs_declaration: bool = True, ) -> Var | None: if self.inside_def_block: @@ -732,6 +802,7 @@ def create_oqpy_var( needs_declaration=needs_declaration, ) elif isinstance(node_type, ast.DurationType): + value = None if isinstance(init_expression, ast.DurationLiteral): if init_expression.unit.name not in self.TIME_UNIT_TO_EXP: raise ValueError( @@ -747,19 +818,135 @@ def create_oqpy_var( init_expression=init_expression, name=name, needs_declaration=needs_declaration ) elif isinstance(node_type, ast.FrameType): - if isinstance(init_expression, ast.FunctionCall): - if init_expression.name.name == "newframe": - port, frequency, phase = (lambda x: (x[0].name, x[1].value, x[2].value))( - init_expression.arguments - ) - var = FrameVar(port=port, frequency=frequency, phase=phase, name=name) - else: - var = FrameVar(name=name) + if isinstance(init_expression, dict): + var = FrameVar(name=name, **init_expression) + else: + var = FrameVar(name=name) elif isinstance(node_type, ast.PortType): var = PortVar(name=name) elif isinstance(node_type, ast.WaveformType): - # init_expression must be transformed var = WaveformVar(init_expression=init_expression, name=name) else: raise TypeError(f"Unsupported type {type(node_type)} was used in the OpenQASM program.") return var + + def visit_FunctionCall(self, node: ast.FunctionCall, context: Program) -> dict[str, Any]: + node = self.generic_visit(node, context)["node"] + if node.name.name == "newframe": + value = { + "port": node.arguments[0].name, + "frequency": node.arguments[1].value, + "phase": node.arguments[2].value, + } + else: + value = None + return {"node": node, "value": value} + + def visit_BitstringLiteral( + self, node: ast.BitstringLiteral, context: Program + ) -> dict[str, Any]: + value = bin(node.value)[2:] + if len(value) < node.width: + value = "0" * (node.width - len(value)) + value + return {"node": node, "value": value} + + def visit_IntegerLiteral(self, node: ast.IntegerLiteral, context: Program) -> dict[str, Any]: + return {"node": node, "value": node.value} + + def visit_FloatLiteral(self, node: ast.FloatLiteral, context: Program) -> dict[str, Any]: + return {"node": node, "value": node.value} + + def visit_ImaginaryLiteral( + self, node: ast.ImaginaryLiteral, context: Program + ) -> dict[str, Any]: + return {"node": node, "value": node.value * 1j} + + def visit_BooleanLiteral(self, node: ast.BooleanLiteral, context: Program) -> dict[str, Any]: + return {"node": node, "value": True if node.value else False} + + def visit_DurationLiteral(self, node: ast.DurationLiteral, context: Program) -> dict[str, Any]: + return {"node": node, "value": make_duration(node.value * 1e-9)} + + def visit_ArrayLiteral(self, node: ast.ArrayLiteral, context: Program) -> dict[str, Any]: + return { + "node": node, + "value": [self.generic_visit(n, context)["value"] for n in node.values], + } + + def visit_Identifier(self, node: ast.Identifier, context: Program) -> dict[str, Any]: + if node.name in context.declared_vars: + value = context.declared_vars[node.name] + elif node.name in context.undeclared_vars: + value = context.undeclared_vars[node.name] + else: + value = node.name + return {"node": node, "value": value} + + def visit_BinaryExpression( + self, node: ast.BinaryExpression, context: Program + ) -> dict[str, Any]: + res = self.generic_visit(node, context) + node = res["node"] + lhs = res["value"]["lhs"] + rhs = res["value"]["rhs"] + + if isinstance(lhs, str): + lhs = classical_types.Identifier(lhs) + if isinstance(rhs, str): + rhs = classical_types.Identifier(rhs) + + op = ast.BinaryOperator + + result = None + if node.op == op["+"]: + result = lhs + rhs + elif node.op == op["-"]: + result = lhs - rhs + elif node.op == op["*"]: + result = lhs * rhs + elif node.op == op["/"]: + result = lhs / rhs + elif node.op == op["%"]: + result = lhs % rhs + elif node.op == op["**"]: + result = lhs**rhs + elif node.op == op[">"]: + result = lhs > rhs + elif node.op == op["<"]: + result = lhs < rhs + elif node.op == op[">="]: + result = lhs >= rhs + elif node.op == op["<="]: + result = lhs <= rhs + elif node.op == op["=="]: + result = lhs == rhs + elif node.op == op["!="]: + result = lhs != rhs + elif node.op == op["&&"]: + result = lhs and rhs + elif node.op == op["||"]: + result = lhs or rhs + elif node.op == op["|"]: + result = lhs | rhs + elif node.op == op["^"]: + result = lhs ^ rhs + elif node.op == op["&"]: + result = lhs & rhs + elif node.op == op["<<"]: + result = lhs << rhs + elif node.op == op[">>"]: + result = lhs >> rhs + return {"node": node, "value": result} + + def visit_UnaryExpression(self, node: ast.UnaryExpression, context: Program) -> dict[str, Any]: + res = self.generic_visit(node, context) + node = res["node"] + exp = res["value"]["expression"] + + if node.op == ast.UnaryOperator["-"]: + result = -1 * exp + elif node.op == ast.UnaryOperator["!"]: + result = not exp + elif node.op == ast.UnaryOperator["~"]: + result = ~exp + return {"node": node, "value": result} diff --git a/tests/test_builder.py b/tests/test_builder.py index 73adcd7..037b398 100644 --- a/tests/test_builder.py +++ b/tests/test_builder.py @@ -436,7 +436,20 @@ def multiply(int[32] x, int[32] y) -> int[32] { """ ).strip() - assert Program(oqasm_text=expected).to_qasm() == expected + expect_subroutine_multiply = textwrap.dedent( + """ + def multiply(int[32] x, int[32] y) -> int[32] { + return x * y; + } + """ + ).strip() + + prog_from_text = Program(oqasm_text=expected) + assert prog_from_text.to_qasm() == expected + assert ( + dumps(prog_from_text.subroutines["multiply"], indent=" ").strip() + == expect_subroutine_multiply + ) # assert prog == Program(oqasm_text=prog.to_qasm()) @@ -820,10 +833,17 @@ def test_ramsey_example(): """ ).strip() - assert Program(oqasm_text=expected).to_qasm(encal_declarations=True) == expected + prog_from_text = Program(oqasm_text=expected) + assert prog_from_text.to_qasm(encal_declarations=True) == expected + assert ( + dumps(prog_from_text.defcals[(("$2",), "x90", ())], indent=" ").strip() + == expect_defcal_x90_q2 + ) + assert ( + dumps(prog_from_text.defcals[(("$2",), "readout", ())], indent=" ").strip() + == expect_defcal_readout_q2 + ) # assert prog == Program(oqasm_text=prog.to_qasm()) - # assert dumps(prog.defcals[("$2", "x90")], indent=" ").strip() == expect_defcal_x90_q2 - # assert dumps(prog.defcals[("$2", "readout")], indent=" ").strip() == expect_defcal_readout_q2 def test_rabi_example(): @@ -895,7 +915,6 @@ def test_rabi_example(): # assert prog == Program(oqasm_text=prog.to_qasm()) -# @pytest.mark.xfail(reason="Extern must be included in a cal block") def test_program_add(): prog1 = Program() constant = declare_waveform_generator("constant", [("length", duration), ("iq", complex128)]) From 99160d3f8e9a8bab589fa7758af6d50ce6104da5 Mon Sep 17 00:00:00 2001 From: Jean-Christophe Jaskula Date: Fri, 28 Apr 2023 18:48:49 -0400 Subject: [PATCH 15/18] Fix typo --- oqpy/program.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/oqpy/program.py b/oqpy/program.py index d057754..6eeaa88 100644 --- a/oqpy/program.py +++ b/oqpy/program.py @@ -615,7 +615,7 @@ def process_statement_list( class ProgramBuilder(QASMVisitor[Program]): - """AST Transformer class that modifies the tree created from parsing opeqasm input text. + """AST Transformer class that modifies the tree created from parsing openqasm input text. It separates: - extern declarations and stores them in Program().externs. From 40412a048c0ea4521102229bb427ba4b879b5b4a Mon Sep 17 00:00:00 2001 From: Jean-Christophe Jaskula Date: Sun, 16 Jul 2023 22:25:55 -0400 Subject: [PATCH 16/18] use staticmethod --- oqpy/program.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/oqpy/program.py b/oqpy/program.py index 6eeaa88..ecaaef2 100644 --- a/oqpy/program.py +++ b/oqpy/program.py @@ -94,7 +94,7 @@ class Program: DURATION_MAX_DIGITS = 12 - def __init__(self, version: Optional[str] = "3.0", simplify_constants: bool = True, oqasm_text: Optional[str] = None) -> None: + def __init__(self, version: Optional[str] = "3.0", simplify_constants: bool = True) -> None: self.stack: list[ProgramState] = [ProgramState()] self.defcals: dict[ tuple[tuple[str, ...], str, tuple[str, ...]], ast.CalibrationDefinition @@ -106,10 +106,6 @@ def __init__(self, version: Optional[str] = "3.0", simplify_constants: bool = Tr self.simplify_constants = simplify_constants self.declared_subroutines: set[str] = set() - if oqasm_text is not None: - self.from_qasm(oqasm_text) - return - if version is None or ( len(version.split(".")) in [1, 2] and all([item.isnumeric() for item in version.split(".")]) @@ -312,10 +308,13 @@ def to_ast( MergeCalStatementsPass().visit(prog) return prog - def from_qasm(self, qasm_text: str) -> None: + @staticmethod + def from_qasm(source: str) -> None: """Build an OQPy program by parsing OpenQASM text.""" - oqasm_ast = parse(qasm_text) - ProgramBuilder().visit(oqasm_ast, self) + prog = Program() + oqasm_ast = parse(source) + ProgramBuilder().visit(oqasm_ast, prog) + return prog def to_qasm( self, From e0adb85685ebd6fb7c1bbacd3f8da380f5c3fa3f Mon Sep 17 00:00:00 2001 From: Jean-Christophe Jaskula Date: Sun, 16 Jul 2023 22:29:59 -0400 Subject: [PATCH 17/18] fix wrong rebase --- oqpy/classical_types.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/oqpy/classical_types.py b/oqpy/classical_types.py index e53c20e..d11d7be 100644 --- a/oqpy/classical_types.py +++ b/oqpy/classical_types.py @@ -309,8 +309,8 @@ def __init__( assert isinstance(base_type, ast.FloatType) self.base_type = base_type - if not isinstance(init_expression, (complex, type(None), OQPyExpression, ast.Expression)): - init_expression = complex(init_expression) # type: ignore[call-overload] + if not isinstance(init_expression, (complex, type(None), str, OQPyExpression)): + init_expression = complex(init_expression) # type: ignore[arg-type] super().__init__(init_expression, *args, **kwargs, base_type=base_type) From dbd12408c9fbbb97eac583f952463a91aa1f2606 Mon Sep 17 00:00:00 2001 From: Jean-Christophe Jaskula Date: Mon, 17 Jul 2023 14:12:12 -0400 Subject: [PATCH 18/18] fix mypy and most of the tests --- oqpy/program.py | 9 ++++---- tests/test_builder.py | 52 +++++++++++++++++++++---------------------- 2 files changed, 31 insertions(+), 30 deletions(-) diff --git a/oqpy/program.py b/oqpy/program.py index ecaaef2..127286b 100644 --- a/oqpy/program.py +++ b/oqpy/program.py @@ -309,7 +309,7 @@ def to_ast( return prog @staticmethod - def from_qasm(source: str) -> None: + def from_qasm(source: str) -> Program: """Build an OQPy program by parsing OpenQASM text.""" prog = Program() oqasm_ast = parse(source) @@ -864,7 +864,7 @@ def visit_BooleanLiteral(self, node: ast.BooleanLiteral, context: Program) -> di return {"node": node, "value": True if node.value else False} def visit_DurationLiteral(self, node: ast.DurationLiteral, context: Program) -> dict[str, Any]: - return {"node": node, "value": make_duration(node.value * 1e-9)} + return {"node": node, "value": convert_float_to_duration(node.value * 1e-9)} def visit_ArrayLiteral(self, node: ast.ArrayLiteral, context: Program) -> dict[str, Any]: return { @@ -889,10 +889,11 @@ def visit_BinaryExpression( lhs = res["value"]["lhs"] rhs = res["value"]["rhs"] + # FIXME: pass the right type to ast_type if isinstance(lhs, str): - lhs = classical_types.Identifier(lhs) + lhs = classical_types.Identifier(lhs, ast.ClassicalType) if isinstance(rhs, str): - rhs = classical_types.Identifier(rhs) + rhs = classical_types.Identifier(rhs, ast.ClassicalType) op = ast.BinaryOperator diff --git a/tests/test_builder.py b/tests/test_builder.py index 037b398..f091016 100644 --- a/tests/test_builder.py +++ b/tests/test_builder.py @@ -41,7 +41,7 @@ def test_version_string(): """ ).strip() - assert Program(oqasm_text=expected).to_qasm() == expected + assert Program.from_qasm(source=expected).to_qasm() == expected # assert prog == Program(oqasm_text=prog.to_qasm()) @@ -88,7 +88,7 @@ def test_variable_declaration(): ).strip() assert isinstance(arr[14], BitVar) - assert Program(oqasm_text=expected).to_qasm() == expected + assert Program.from_qasm(source=expected).to_qasm() == expected # assert prog == Program(oqasm_text=prog.to_qasm()) @@ -138,7 +138,7 @@ def test_complex_numbers_declaration(): """ ).strip() - assert Program(oqasm_text=expected).to_qasm() == expected + assert Program.from_qasm(source=expected).to_qasm() == expected # assert prog == Program(oqasm_text=prog.to_qasm()) @@ -159,7 +159,7 @@ def test_non_trivial_variable_declaration(): """ ).strip() - assert Program(oqasm_text=expected).to_qasm() == expected + assert Program.from_qasm(source=expected).to_qasm() == expected # assert prog == Program(oqasm_text=prog.to_qasm()) @@ -185,7 +185,7 @@ def test_variable_assignment(): """ ).strip() - assert Program(oqasm_text=expected).to_qasm() == expected + assert Program.from_qasm(source=expected).to_qasm() == expected # assert prog == Program(oqasm_text=prog.to_qasm()) @@ -206,7 +206,7 @@ def test_binary_expressions(): """ ).strip() - assert Program(oqasm_text=expected).to_qasm() == expected + assert Program.from_qasm(source=expected).to_qasm() == expected # assert prog == Program(oqasm_text=prog.to_qasm()) @@ -228,7 +228,7 @@ def test_measure_reset(): """ ).strip() - assert Program(oqasm_text=expected).to_qasm() == expected + assert Program.from_qasm(source=expected).to_qasm() == expected # assert prog == Program(oqasm_text=prog.to_qasm()) @@ -256,7 +256,7 @@ def test_bare_if(): """ ).strip() - assert Program(oqasm_text=expected).to_qasm() == expected + assert Program.from_qasm(source=expected).to_qasm() == expected # assert prog == Program(oqasm_text=prog.to_qasm()) @@ -293,7 +293,7 @@ def test_if_else(): """ ).strip() - assert Program(oqasm_text=expected).to_qasm() == expected + assert Program.from_qasm(source=expected).to_qasm() == expected # assert prog == Program(oqasm_text=prog.to_qasm()) @@ -332,7 +332,7 @@ def test_for_in(): """ ).strip() - assert Program(oqasm_text=expected).to_qasm() == expected + assert Program.from_qasm(source=expected).to_qasm() == expected # assert prog == Program(oqasm_text=prog.to_qasm()) @@ -357,7 +357,7 @@ def test_while(): """ ).strip() - assert Program(oqasm_text=expected).to_qasm() == expected + assert Program.from_qasm(source=expected).to_qasm() == expected # assert prog == Program(oqasm_text=prog.to_qasm()) @@ -382,7 +382,7 @@ def test_create_frame(): """ ).strip() - assert Program(oqasm_text=expected).to_qasm() == expected + assert Program.from_qasm(source=expected).to_qasm() == expected # assert prog == Program(oqasm_text=prog.to_qasm()) @@ -444,7 +444,7 @@ def multiply(int[32] x, int[32] y) -> int[32] { """ ).strip() - prog_from_text = Program(oqasm_text=expected) + prog_from_text = Program.from_qasm(source=expected) assert prog_from_text.to_qasm() == expected assert ( dumps(prog_from_text.subroutines["multiply"], indent=" ").strip() @@ -491,7 +491,7 @@ def test_box_and_timings(): """ ).strip() - prog_from_text = Program(oqasm_text=expected) + prog_from_text = Program.from_qasm(source=expected) assert prog_from_text.to_qasm(encal_declarations=True) == expected prog_from_text.externs["constant"] == ast.ExternDeclaration( name=ast.Identifier(name="constant"), @@ -533,7 +533,7 @@ def test_play_capture(): """ ).strip() - assert Program(oqasm_text=expected).to_qasm(encal_declarations=True) == expected + assert Program.from_qasm(source=expected).to_qasm(encal_declarations=True) == expected # assert prog == Program(oqasm_text=prog.to_qasm()) @@ -557,7 +557,7 @@ def test_set_shift_frequency(): """ ).strip() - assert Program(oqasm_text=expected).to_qasm() == expected + assert Program.from_qasm(source=expected).to_qasm() == expected # assert prog == Program(oqasm_text=prog.to_qasm()) @@ -644,7 +644,7 @@ def test_defcals(): """ ).strip() - prog_from_text = Program(oqasm_text=expected) + prog_from_text = Program.from_qasm(source=expected) assert prog_from_text.to_qasm(encal_declarations=True) == expected # assert prog == Program(oqasm_text=prog.to_qasm()) @@ -833,7 +833,7 @@ def test_ramsey_example(): """ ).strip() - prog_from_text = Program(oqasm_text=expected) + prog_from_text = Program.from_qasm(source=expected) assert prog_from_text.to_qasm(encal_declarations=True) == expected assert ( dumps(prog_from_text.defcals[(("$2",), "x90", ())], indent=" ").strip() @@ -911,7 +911,7 @@ def test_rabi_example(): """ ).strip() - assert Program(oqasm_text=expected).to_qasm() == expected + assert Program.from_qasm(source=expected).to_qasm() == expected # assert prog == Program(oqasm_text=prog.to_qasm()) @@ -952,7 +952,7 @@ def test_program_add(): ).strip() prog = prog1 + prog2 - assert Program(oqasm_text=expected).to_qasm(encal_declarations=True) == expected + assert Program.from_qasm(source=expected).to_qasm(encal_declarations=True) == expected # assert prog == Program(oqasm_text=prog.to_qasm()) with pytest.raises(RuntimeError): @@ -982,7 +982,7 @@ def _to_oqpy_expression(self): delay[a2] f1; """ ).strip() - assert Program(oqasm_text=expected).to_qasm() == expected + assert Program.from_qasm(source=expected).to_qasm() == expected # assert prog == Program(oqasm_text=prog.to_qasm()) @@ -1018,7 +1018,7 @@ def test_waveform_extern_arg_passing(): """ ).strip() - assert Program(oqasm_text=expected).to_qasm(encal_declarations=True) == expected + assert Program.from_qasm(source=expected).to_qasm(encal_declarations=True) == expected # assert prog == Program(oqasm_text=prog.to_qasm()) @@ -1059,7 +1059,7 @@ def test_needs_declaration(): """ ).strip() - assert Program(oqasm_text=expected).to_qasm() == expected + assert Program.from_qasm(source=expected).to_qasm() == expected # assert prog == Program(oqasm_text=prog.to_qasm()) @@ -1093,7 +1093,7 @@ def test_discrete_waveform(): """ ).strip() - assert Program(oqasm_text=expected).to_qasm() == expected + assert Program.from_qasm(source=expected).to_qasm() == expected # assert prog == Program(oqasm_text=prog.to_qasm()) @@ -1224,7 +1224,7 @@ def test_autoencal(): """ ).strip() - assert Program(oqasm_text=expected).to_qasm(encal_declarations=True) == expected + assert Program.from_qasm(source=expected).to_qasm(encal_declarations=True) == expected # assert prog == Program(oqasm_text=prog.to_qasm()) @@ -1321,5 +1321,5 @@ def test_ramsey_example_blog(): """ ).strip() - assert Program(oqasm_text=expected).to_qasm(encal_declarations=True) == expected + assert Program.from_qasm(source=expected).to_qasm(encal_declarations=True) == expected # assert prog == Program(oqasm_text=prog.to_qasm())